Initial commit: LangChain evolution tutorial deck (132 slides)
- Cover, TOC, 5 dividers, 3 recap slides - 5 sections (chains, langgraph, deepagents, openswe, ecosystem) - design-system.js with theme tokens + 9 helper functions - research/: timeline + sources + per-tech notes - final-compile.js + merge.js for rebuild pipeline - output/: langchain-evolution.pptx (2.3 MB) + langchain-evolution.pdf (1.1 MB) + 7 sample previews
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* slides/01-cover.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 01 -- Cover
|
||||
* Section divider style with stage number, title, intro paragraph.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers } = require('../../design-system');
|
||||
|
||||
function buildCover(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.addSectionDivider(slide, pres, theme, {
|
||||
number: '4',
|
||||
eyebrow: 'STAGE 4',
|
||||
title: 'Open SWE: async coding agent',
|
||||
intro:
|
||||
'Open-source фреймворк LangChain Inc. для построения внутренних ' +
|
||||
'кодинг-агентов организации. Reference architecture поверх Deep Agents, ' +
|
||||
'pluggable sandboxes, триггеры из Slack/Linear/GitHub, draft PR ' +
|
||||
'автоматически. Воспроизводит паттерны Stripe Minions, Ramp Inspect, ' +
|
||||
'Coinbase Cloudbot -- но с открытым исходным кодом.',
|
||||
});
|
||||
helpers.addPageNumber(slide, pres, theme, 1);
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildCover };
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* slides/02-what-is-openswe.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 02 -- What is Open SWE: positioning
|
||||
* One big picture slide with positioning callout.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildWhatIsOpenSWE(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Open SWE: позиционирование',
|
||||
});
|
||||
|
||||
// Left column: intro callout
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 4.5, h: 1.55,
|
||||
kind: 'info',
|
||||
title: 'Не готовый продукт',
|
||||
text:
|
||||
'Стартовый шаблон, не ' +
|
||||
'SaaS. Ops-работа: sandbox, ' +
|
||||
'модель, триггеры, промпты.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 0.5, y: 3.1, w: 4.5, h: 1.75,
|
||||
kind: 'success',
|
||||
title: 'Colleague, not copilot',
|
||||
text:
|
||||
'Внутренний кодинг-агент, ' +
|
||||
'не IDE-assistant. Slack / ' +
|
||||
'Linear / GitHub -> draft PR ' +
|
||||
'с тестами.',
|
||||
});
|
||||
|
||||
// Right column: key facts
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 5.3, y: layouts.CONTENT_TOP, w: 4.2, h: 3.4,
|
||||
language: 'python',
|
||||
code: [
|
||||
'# github.com/langchain-ai/open-swe',
|
||||
'',
|
||||
'stars: ~10k',
|
||||
'commits: 971+',
|
||||
'license: MIT',
|
||||
'language: Python + TypeScript',
|
||||
'announce: 08.2025',
|
||||
'rewrite: 03.2026',
|
||||
'',
|
||||
'# blog.langchain.com/open-swe',
|
||||
'# INSTALLATION.md',
|
||||
'# CUSTOMIZATION.md',
|
||||
].join('\n'),
|
||||
filePath: 'meta: open-swe repo',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 2);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'github.com/langchain-ai/open-swe (README + INSTALLATION.md)',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildWhatIsOpenSWE };
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* slides/03-architecture-overview.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 03 -- Architecture overview: layered model
|
||||
* Diagram + import surface code.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildArchitectureOverview(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Архитектура: 7 слоев',
|
||||
});
|
||||
|
||||
// Left: architecture diagram
|
||||
const diagX = 0.5;
|
||||
const diagY = layouts.CONTENT_TOP;
|
||||
const diagW = 4.4;
|
||||
const diagH = 3.5;
|
||||
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: diagX, y: diagY, w: diagW, h: diagH,
|
||||
fill: { color: theme.palette.bg.elevated },
|
||||
line: { color: theme.palette.border.subtle, width: 1 },
|
||||
rectRadius: 0.08,
|
||||
});
|
||||
|
||||
// 7 stacked layers
|
||||
const layers = [
|
||||
['Triggers', 'Slack / Linear / GitHub / Web UI'],
|
||||
['Validation', 'prompt + middleware (HITL, approval)'],
|
||||
['Orchestration','subagents + middleware'],
|
||||
['Context', 'AGENTS.md из репозитория'],
|
||||
['Tools', 'execute, fetch_url, linear_comment, slack_thread_reply'],
|
||||
['Sandbox', 'Modal / Daytona / Runloop / LangSmith'],
|
||||
['Harness', 'create_deep_agent (Deep Agents)'],
|
||||
];
|
||||
|
||||
const layerH = (diagH - 0.3) / layers.length;
|
||||
layers.forEach(function (layer, idx) {
|
||||
const y = diagY + 0.15 + idx * layerH;
|
||||
slide.addShape(pres.ShapeType.rect, {
|
||||
x: diagX + 0.15, y: y, w: diagW - 0.3, h: layerH - 0.05,
|
||||
fill: { color: idx === layers.length - 1
|
||||
? theme.palette.accent.primary
|
||||
: theme.palette.bg.code },
|
||||
line: { color: theme.palette.border.subtle, width: 0.5 },
|
||||
});
|
||||
slide.addText(layer[0], {
|
||||
x: diagX + 0.25, y: y + 0.02, w: diagW - 0.5, h: 0.22,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.body,
|
||||
color: idx === layers.length - 1
|
||||
? theme.palette.text.inverse
|
||||
: theme.palette.text.primary,
|
||||
bold: true,
|
||||
});
|
||||
slide.addText(layer[1], {
|
||||
x: diagX + 0.25, y: y + 0.22, w: diagW - 0.5, h: layerH - 0.27,
|
||||
fontFace: helpers.withFallback(theme.fonts.code),
|
||||
fontSize: theme.sizes.caption,
|
||||
color: idx === layers.length - 1
|
||||
? theme.palette.text.inverse
|
||||
: theme.palette.text.secondary,
|
||||
});
|
||||
});
|
||||
|
||||
// Right: import surface
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 5.2, y: layouts.CONTENT_TOP, w: 4.3, h: 3.5,
|
||||
language: 'python',
|
||||
code: [
|
||||
'from open_swe.agent import create_agent',
|
||||
'from open_swe.middleware import (',
|
||||
' check_message_queue_before_model,',
|
||||
' notify_step_limit_reached,',
|
||||
' open_pr_if_needed,',
|
||||
' ToolErrorMiddleware,',
|
||||
')',
|
||||
'from open_swe.sandbox import (',
|
||||
' SandboxBackend,',
|
||||
' ModalBackend, DaytonaBackend,',
|
||||
')',
|
||||
].join('\n'),
|
||||
filePath: 'open_swe/__init__.py',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 3);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 26-50',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildArchitectureOverview };
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* slides/04-create-deep-agent.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 04 -- create_deep_agent: composition point
|
||||
* Core entry point with sandbox + middleware.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildCreateDeepAgent(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'create_deep_agent + backend',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 6.0, h: 3.5,
|
||||
language: 'python',
|
||||
code: [
|
||||
'from deepagents import create_deep_agent',
|
||||
'from open_swe.sandbox import DaytonaBackend',
|
||||
'',
|
||||
'agent = create_deep_agent(',
|
||||
' model="anthropic:claude-opus-4-6",',
|
||||
' tools=[execute, fetch_url,',
|
||||
' linear_comment,',
|
||||
' slack_thread_reply],',
|
||||
' backend=DaytonaBackend(api_key="..."),',
|
||||
' middleware=[open_pr_if_needed],',
|
||||
')',
|
||||
].join('\n'),
|
||||
filePath: 'examples/minimal_agent.py',
|
||||
startLine: 1,
|
||||
highlightLines: [4, 8, 9],
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.8, y: layouts.CONTENT_TOP, w: 2.7, h: 1.4,
|
||||
kind: 'info',
|
||||
title: 'Один harness',
|
||||
text:
|
||||
'Март 2026: multi-agent ' +
|
||||
'(Manager/Planner/Programmer/' +
|
||||
'Reviewer) заменили на единый ' +
|
||||
'deep-agent harness.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.8, y: 2.9, w: 2.7, h: 1.55,
|
||||
kind: 'success',
|
||||
title: 'Upgrade path',
|
||||
text:
|
||||
'Подтягиваешь улучшения Deep ' +
|
||||
'Agents бесплатно. Subagents ' +
|
||||
'изолируют контекст, middleware ' +
|
||||
'дают orchestration.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 4);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 52-77',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildCreateDeepAgent };
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* slides/05-agents-md-context.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 05 -- AGENTS.md convention
|
||||
* Context injection pattern.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildAgentsMdConvention(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'AGENTS.md как system prompt',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 1.7,
|
||||
language: 'python',
|
||||
code: [
|
||||
'from pathlib import Path',
|
||||
'',
|
||||
'def construct_system_prompt(',
|
||||
' repo_dir, base_prompt',
|
||||
'):',
|
||||
' agents_md = Path(repo_dir) / "AGENTS.md"',
|
||||
' extra = agents_md.read_text() if',
|
||||
' agents_md.exists() else ""',
|
||||
' return base_prompt + extra',
|
||||
].join('\n'),
|
||||
filePath: 'open_swe/prompts.py',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: 3.3, w: 5.6, h: 1.55,
|
||||
language: 'markdown',
|
||||
code: [
|
||||
'# AGENTS.md (repo root)',
|
||||
'',
|
||||
'- use uv, not pip',
|
||||
'- run pytest before commit',
|
||||
'- never push to main directly',
|
||||
].join('\n'),
|
||||
filePath: 'AGENTS.md',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.7,
|
||||
kind: 'info',
|
||||
title: 'Convention',
|
||||
text:
|
||||
'Организационный паттерн. ' +
|
||||
'Тот же файл читают Cursor, ' +
|
||||
'Aider, Devin.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: 3.3, w: 3.2, h: 1.55,
|
||||
kind: 'warning',
|
||||
title: 'Подводный камень',
|
||||
text:
|
||||
'Open SWE доверяет AGENTS.md. ' +
|
||||
'Вредоносный блок попадает ' +
|
||||
'в system prompt.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 5);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 113-115, 200-209',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildAgentsMdConvention };
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* slides/06-middleware.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 06 -- Middleware: AgentMiddleware extension
|
||||
* Custom middleware pattern.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildMiddleware(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Middleware: точки расширения',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
|
||||
language: 'python',
|
||||
code: [
|
||||
'from langchain.agents.middleware import (',
|
||||
' AgentMiddleware,',
|
||||
')',
|
||||
'',
|
||||
'class AuditMiddleware(AgentMiddleware):',
|
||||
' def after_model(self, state, runtime):',
|
||||
' runtime.logger.info(',
|
||||
' f"step={state.get(\"step\")}"',
|
||||
' )',
|
||||
' return state',
|
||||
'',
|
||||
' def before_model(self, state, runtime):',
|
||||
' return state # inject reminder',
|
||||
].join('\n'),
|
||||
filePath: 'examples/audit_middleware.py',
|
||||
startLine: 1,
|
||||
highlightLines: [5, 6, 7, 8],
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.7,
|
||||
kind: 'info',
|
||||
title: 'Built-in middleware',
|
||||
text:
|
||||
'* check_message_queue\n' +
|
||||
'* notify_step_limit\n' +
|
||||
'* open_pr_if_needed\n' +
|
||||
'* ToolErrorMiddleware',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: 3.0, w: 3.2, h: 1.8,
|
||||
kind: 'success',
|
||||
title: 'Типичные кастомы',
|
||||
text:
|
||||
'Approval gate перед PR, cost ' +
|
||||
'guard на длину контекста, ' +
|
||||
'redaction секретов в логах.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 6);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 117-136, 213-223',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildMiddleware };
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* slides/07-sandbox-providers.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 07 -- Sandbox providers: comparison
|
||||
* Table of Modal / Daytona / Runloop / LangSmith.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildSandboxProviders(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Sandbox-провайдеры',
|
||||
});
|
||||
|
||||
// Comparison table card
|
||||
const x = 0.5;
|
||||
const y = layouts.CONTENT_TOP;
|
||||
const w = 9.0;
|
||||
const h = 3.55;
|
||||
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: x, y: y, w: w, h: h,
|
||||
fill: { color: theme.palette.bg.elevated },
|
||||
line: { color: theme.palette.border.subtle, width: 1 },
|
||||
rectRadius: 0.08,
|
||||
});
|
||||
|
||||
// Table headers
|
||||
const cols = [
|
||||
{ label: 'Provider', w: 1.6 },
|
||||
{ label: 'Setup', w: 1.9 },
|
||||
{ label: 'Pricing model', w: 1.7 },
|
||||
{ label: 'Persistent state', w: 1.6 },
|
||||
{ label: 'Best for', w: 2.2 },
|
||||
];
|
||||
|
||||
const headerY = y + 0.1;
|
||||
let cx = x + 0.15;
|
||||
cols.forEach(function (c) {
|
||||
slide.addShape(pres.ShapeType.rect, {
|
||||
x: cx, y: headerY, w: c.w, h: 0.32,
|
||||
fill: { color: theme.palette.bg.code },
|
||||
line: { color: theme.palette.border.subtle, width: 0.5 },
|
||||
});
|
||||
slide.addText(c.label, {
|
||||
x: cx + 0.05, y: headerY, w: c.w - 0.1, h: 0.32,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.caption,
|
||||
color: theme.palette.accent.tertiary,
|
||||
bold: true,
|
||||
charSpacing: 2,
|
||||
valign: 'middle',
|
||||
});
|
||||
cx += c.w;
|
||||
});
|
||||
|
||||
const rows = [
|
||||
['ModalBackend', 'token_id + token_secret', 'per-second + GB-s', 'yes (volume)', 'Python-first, GPU-доступный'],
|
||||
['DaytonaBackend','api_key', 'per-session', 'yes (volume)', 'default в README'],
|
||||
['RunloopBackend','api_key', 'per-second', 'yes (volume)', 'dev-цикл, snapshot/restart'],
|
||||
['LangSmithBackend','LANGSMITH_API_KEY', 'через LangSmith', 'через LS', 'уже платите за LangSmith'],
|
||||
];
|
||||
|
||||
const rowH = 0.7;
|
||||
rows.forEach(function (row, idx) {
|
||||
const ry = headerY + 0.32 + idx * rowH;
|
||||
cx = x + 0.15;
|
||||
row.forEach(function (cell, cidx) {
|
||||
slide.addShape(pres.ShapeType.rect, {
|
||||
x: cx, y: ry, w: cols[cidx].w, h: rowH,
|
||||
fill: { color: idx % 2 === 0
|
||||
? theme.palette.bg.primary
|
||||
: theme.palette.bg.overlay },
|
||||
line: { color: theme.palette.border.subtle, width: 0.4 },
|
||||
});
|
||||
slide.addText(cell, {
|
||||
x: cx + 0.05, y: ry, w: cols[cidx].w - 0.1, h: rowH,
|
||||
fontFace: helpers.withFallback(theme.fonts.code),
|
||||
fontSize: 9,
|
||||
color: theme.palette.text.primary,
|
||||
valign: 'middle',
|
||||
});
|
||||
cx += cols[cidx].w;
|
||||
});
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 7);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 78-89, 139-145',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildSandboxProviders };
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* slides/08-sandbox-imports.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 08 -- Sandbox imports + custom backend stub
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildSandboxImports(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Sandbox: imports + custom',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.0, h: 3.4,
|
||||
language: 'python',
|
||||
code: [
|
||||
'from open_swe.sandbox import (',
|
||||
' ModalBackend,',
|
||||
' DaytonaBackend,',
|
||||
' RunloopBackend,',
|
||||
' LangSmithBackend,',
|
||||
')',
|
||||
'',
|
||||
'# Modal (Python-first, GPU)',
|
||||
'backend = ModalBackend(',
|
||||
' token_id="..."',
|
||||
' token_secret="..."',
|
||||
')',
|
||||
].join('\n'),
|
||||
filePath: 'open_swe/sandbox/__init__.py',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 5.7, y: layouts.CONTENT_TOP, w: 3.8, h: 3.4,
|
||||
language: 'python',
|
||||
code: [
|
||||
'# свой backend: devbox-пул',
|
||||
'from open_swe.sandbox import (',
|
||||
' SandboxBackend,',
|
||||
')',
|
||||
'',
|
||||
'class MyInternalBackend(SandboxBackend):',
|
||||
' def execute(self, cmd):',
|
||||
' return self._run(cmd)',
|
||||
'',
|
||||
' def read_file(self, p):',
|
||||
' return self._fetch(p)',
|
||||
].join('\n'),
|
||||
filePath: 'examples/my_internal_backend.py',
|
||||
startLine: 1,
|
||||
highlightLines: [5, 8, 9, 11, 12, 13, 14, 15],
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 5.7, y: layouts.CONTENT_TOP, w: 3.8, h: 3.4,
|
||||
language: 'python',
|
||||
code: [
|
||||
'# свой backend: devbox-пул',
|
||||
'from open_swe.sandbox import (',
|
||||
' SandboxBackend,',
|
||||
')',
|
||||
'',
|
||||
'class MyInternalBackend(',
|
||||
' SandboxBackend',
|
||||
'):',
|
||||
' def __init__(self, conn):',
|
||||
' self.conn = conn',
|
||||
'',
|
||||
' def execute(self, cmd):',
|
||||
' return self._run(cmd)',
|
||||
'',
|
||||
' def read_file(self, p):',
|
||||
' return self._fetch(p)',
|
||||
].join('\n'),
|
||||
filePath: 'examples/my_internal_backend.py',
|
||||
startLine: 1,
|
||||
highlightLines: [11, 12, 13, 14, 15, 16, 19, 20, 21, 22],
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 8);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 80-86, 250-264',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildSandboxImports };
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* slides/09-installation-1.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 09 -- Installation: prerequisites + clone
|
||||
* Steps 1-2 of 5.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildInstallPart1(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Установка (1/2): шаги 1-2',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.5, h: 1.5,
|
||||
language: 'bash',
|
||||
code: [
|
||||
'# 1. prerequisites',
|
||||
'python --version # >= 3.11',
|
||||
'node --version # >= 20',
|
||||
'uv --version # или pip',
|
||||
'docker --version # для dev',
|
||||
].join('\n'),
|
||||
filePath: 'INSTALLATION.md: step 1',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: 3.1, w: 5.5, h: 1.75,
|
||||
language: 'bash',
|
||||
code: [
|
||||
'# 2. clone + install',
|
||||
'git clone https://github.com/',
|
||||
' langchain-ai/open-swe.git',
|
||||
'cd open-swe',
|
||||
'uv sync # или pip install -e .',
|
||||
].join('\n'),
|
||||
filePath: 'INSTALLATION.md: step 2',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.2, y: layouts.CONTENT_TOP, w: 3.3, h: 1.55,
|
||||
kind: 'info',
|
||||
title: 'Не SaaS',
|
||||
text:
|
||||
'Open SWE -- не готовый сервис. ' +
|
||||
'После клонирования нужно ' +
|
||||
'поднять backend, UI, sandbox, ' +
|
||||
'GitHub App, LangSmith.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.2, y: 3.1, w: 3.3, h: 1.75,
|
||||
kind: 'warning',
|
||||
title: 'ENV-файл',
|
||||
text:
|
||||
'cp .env.example .env, затем ' +
|
||||
'заполните:\n' +
|
||||
'- GITHUB_APP_ID\n' +
|
||||
'- LANGSMITH_API_KEY\n' +
|
||||
'- DAYTONA_API_KEY',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 9);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'github.com/langchain-ai/open-swe/blob/main/INSTALLATION.md',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildInstallPart1 };
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* slides/10-installation-2.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 10 -- Installation: services up + UI
|
||||
* Steps 3-5 of 5.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildInstallPart2(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Установка (2/2): шаги 3-5',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 6.0, h: 3.5,
|
||||
language: 'bash',
|
||||
code: [
|
||||
'# 3. backend (FastAPI)',
|
||||
'uv run apps/open-swe/main.py',
|
||||
'# -> :8000',
|
||||
'',
|
||||
'# 4. UI (TanStack Start + Vite)',
|
||||
'cd apps/open-swe-ui',
|
||||
'pnpm install && pnpm dev',
|
||||
'# -> :3000',
|
||||
'',
|
||||
'# 5. smoke-test',
|
||||
'curl -X POST :8000/webhooks/slack \\',
|
||||
' -d \'{"text":"@open-swe hello"}\'',
|
||||
'# -> {"thread_id": "..."}',
|
||||
].join('\n'),
|
||||
filePath: 'INSTALLATION.md: steps 3-5',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.7, y: layouts.CONTENT_TOP, w: 2.8, h: 1.7,
|
||||
kind: 'success',
|
||||
title: 'Готово',
|
||||
text:
|
||||
'Backend :8000\n' +
|
||||
'UI :3000\n' +
|
||||
'Webhook :8000/webhooks/*\n\n' +
|
||||
'Можно слать @open-swe.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.7, y: 3.0, w: 2.8, h: 1.85,
|
||||
kind: 'info',
|
||||
title: 'Production',
|
||||
text:
|
||||
'Локальный запуск -- только dev. ' +
|
||||
'Для prod: docker compose, k8s, ' +
|
||||
'managed Postgres, Vault, TLS.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 10);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'INSTALLATION.md + INSTALLATION in repo root',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildInstallPart2 };
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* slides/11-github-app.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 11 -- GitHub App setup
|
||||
* Manifest + permissions.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildGithubApp(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'GitHub App: manifest',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.7, h: 3.5,
|
||||
language: 'yaml',
|
||||
code: [
|
||||
'# github-app-manifest.yaml',
|
||||
'name: open-swe-internal',
|
||||
'url: https://open-swe.example.com',
|
||||
'hook_attributes:',
|
||||
' url: https://open-swe.example.com/',
|
||||
' webhooks/github',
|
||||
' events:',
|
||||
' - issue_comment',
|
||||
' - pull_request',
|
||||
' - pull_request_review',
|
||||
'default_permissions:',
|
||||
' contents: write',
|
||||
' pull_requests: write',
|
||||
].join('\n'),
|
||||
filePath: 'config/github-app-manifest.yaml',
|
||||
startLine: 1,
|
||||
highlightLines: [3, 4, 5, 6, 7, 8, 9],
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.4, y: layouts.CONTENT_TOP, w: 3.1, h: 1.5,
|
||||
kind: 'info',
|
||||
title: 'Создание App',
|
||||
text:
|
||||
'1. github.com/settings/apps/new\n' +
|
||||
'2. вставить manifest\n' +
|
||||
'3. запомнить App ID\n' +
|
||||
'4. скачать private key',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.4, y: 2.85, w: 3.1, h: 2.0,
|
||||
kind: 'warning',
|
||||
title: 'Permissions',
|
||||
text:
|
||||
'contents:write -- ветки\n' +
|
||||
'pull_requests:write -- draft PR\n' +
|
||||
'issues:write -- комментарии\n' +
|
||||
'metadata:read -- repo info',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 11);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'docs.github.com/apps/creating-github-apps',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildGithubApp };
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* slides/12-langsmith.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 12 -- LangSmith setup + API keys + snapshot
|
||||
* Account creation and runtime config.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildLangSmithSetup(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'LangSmith: setup',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
|
||||
language: 'bash',
|
||||
code: [
|
||||
'# .env (НЕ коммитить)',
|
||||
'LANGSMITH_TRACING=true',
|
||||
'LANGSMITH_ENDPOINT=https://',
|
||||
' api.smith.langchain.com',
|
||||
'LANGSMITH_API_KEY=lsv2_...',
|
||||
'LANGSMITH_PROJECT=open-swe-prod',
|
||||
'LANGSMITH_SNAPSHOT=true',
|
||||
'',
|
||||
'# для sandbox-прокси:',
|
||||
'LANGSMITH_SANDBOX_BACKEND=true',
|
||||
].join('\n'),
|
||||
filePath: '.env',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.5,
|
||||
kind: 'info',
|
||||
title: 'Зачем snapshot',
|
||||
text:
|
||||
'Каждый thread = checkpoint. ' +
|
||||
'Follow-up из всех каналов ' +
|
||||
'подхватывают состояние ' +
|
||||
'по thread_id.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: 2.85, w: 3.2, h: 2.0,
|
||||
kind: 'success',
|
||||
title: 'API keys',
|
||||
text:
|
||||
'* Personal -- smith.langchain.com\n' +
|
||||
'* Org-level -- shared tracing\n' +
|
||||
'* Service -- для CI / batch\n' +
|
||||
'Rotate каждые 90 дней.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 12);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'docs.smith.langchain.com',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildLangSmithSetup };
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* slides/13-triggers-overview.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 13 -- Triggers: 3 surface overview
|
||||
* Slack / Linear / GitHub with @open-swe pattern.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildTriggersOverview(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Triggers overview',
|
||||
});
|
||||
|
||||
// 3 trigger cards
|
||||
const cardY = layouts.CONTENT_TOP;
|
||||
const cardH = 3.5;
|
||||
const cardW = 2.95;
|
||||
const gap = 0.13;
|
||||
|
||||
const triggers = [
|
||||
{
|
||||
name: 'Slack',
|
||||
icon: '@',
|
||||
desc: 'В любом thread канала. Поддерживает синтаксис repo:owner/name.',
|
||||
syntax: '@open-swe fix the auth bug\nrepo:acme/api',
|
||||
color: theme.palette.accent.primary,
|
||||
},
|
||||
{
|
||||
name: 'Linear',
|
||||
icon: '#',
|
||||
desc: 'В комментарии к issue. Привязывает thread к Linear-тикету.',
|
||||
syntax: '@openswe implement the\nacceptance criteria',
|
||||
color: theme.palette.accent.secondary,
|
||||
},
|
||||
{
|
||||
name: 'GitHub',
|
||||
icon: 'PR',
|
||||
desc: 'В PR-комментарии для авто-ответа на review-комментарии.',
|
||||
syntax: '@openswe address\nthe review comments',
|
||||
color: theme.palette.accent.tertiary,
|
||||
},
|
||||
];
|
||||
|
||||
triggers.forEach(function (t, idx) {
|
||||
const cx = 0.5 + idx * (cardW + gap);
|
||||
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: cx, y: cardY, w: cardW, h: cardH,
|
||||
fill: { color: theme.palette.bg.elevated },
|
||||
line: { color: t.color, width: 1.2 },
|
||||
rectRadius: 0.08,
|
||||
});
|
||||
|
||||
// Icon badge
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: cx + 0.2, y: cardY + 0.2, w: 0.55, h: 0.55,
|
||||
fill: { color: t.color },
|
||||
line: { color: t.color, width: 1 },
|
||||
rectRadius: 0.08,
|
||||
});
|
||||
slide.addText(t.icon, {
|
||||
x: cx + 0.2, y: cardY + 0.2, w: 0.55, h: 0.55,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: 18,
|
||||
color: theme.palette.text.inverse,
|
||||
bold: true,
|
||||
align: 'center',
|
||||
valign: 'middle',
|
||||
});
|
||||
|
||||
// Name
|
||||
slide.addText(t.name, {
|
||||
x: cx + 0.85, y: cardY + 0.2, w: cardW - 1.0, h: 0.55,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.h3,
|
||||
color: theme.palette.text.primary,
|
||||
bold: true,
|
||||
valign: 'middle',
|
||||
});
|
||||
|
||||
// Desc
|
||||
slide.addText(t.desc, {
|
||||
x: cx + 0.2, y: cardY + 0.9, w: cardW - 0.4, h: 1.05,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.body,
|
||||
color: theme.palette.text.secondary,
|
||||
valign: 'top',
|
||||
});
|
||||
|
||||
// Syntax code
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: cx + 0.2, y: cardY + 2.05, w: cardW - 0.4, h: 1.25,
|
||||
fill: { color: theme.palette.bg.code },
|
||||
line: { color: theme.palette.border.subtle, width: 0.5 },
|
||||
rectRadius: 0.06,
|
||||
});
|
||||
slide.addText(t.syntax, {
|
||||
x: cx + 0.3, y: cardY + 2.1, w: cardW - 0.6, h: 1.15,
|
||||
fontFace: helpers.withFallback(theme.fonts.code),
|
||||
fontSize: theme.sizes.code,
|
||||
color: theme.palette.text.primary,
|
||||
valign: 'top',
|
||||
});
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 13);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 90-97',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildTriggersOverview };
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* slides/14-triggers-thread-id.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 14 -- Trigger routing + deterministic thread_id
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildTriggerRouting(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Triggers: thread_id routing',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
|
||||
language: 'python',
|
||||
code: [
|
||||
'# open_swe/triggers/router.py',
|
||||
'def thread_id_for(source, ref, comment_id):',
|
||||
' # Deterministic thread_id:',
|
||||
' # все follow-up попадают в один run.',
|
||||
' if source == "slack":',
|
||||
' return f"slack:{ref}"',
|
||||
' if source == "linear":',
|
||||
' return f"linear:{ref}"',
|
||||
' if source == "github":',
|
||||
' return f"github:{ref}:{comment_id}"',
|
||||
' raise ValueError(source)',
|
||||
].join('\n'),
|
||||
filePath: 'open_swe/triggers/router.py',
|
||||
startLine: 1,
|
||||
highlightLines: [2, 3, 4, 5, 6, 7, 8, 9],
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.55,
|
||||
kind: 'info',
|
||||
title: 'Routing',
|
||||
text:
|
||||
'thread_id из (source, ref). ' +
|
||||
'Если run бежит -- новые ' +
|
||||
'сообщения ждут в очереди.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: 3.05, w: 3.2, h: 1.8,
|
||||
kind: 'warning',
|
||||
title: 'Concurrency',
|
||||
text:
|
||||
'Один thread = один run. ' +
|
||||
'Параллельность через ' +
|
||||
'отдельный thread_id ' +
|
||||
'(например, отдельный Slack).',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 14);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 96-97, 131-133',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildTriggerRouting };
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* slides/15-webhook-endpoints.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 15 -- Webhook endpoints: FastAPI handlers
|
||||
* /webhooks/{github,linear,slack}
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildWebhookEndpoints(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Webhook endpoints',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 6.0, h: 3.5,
|
||||
language: 'python',
|
||||
code: [
|
||||
'# apps/open-swe/webhooks.py',
|
||||
'from fastapi import APIRouter, Request',
|
||||
'from open_swe.triggers.router import (',
|
||||
' thread_id_for,',
|
||||
')',
|
||||
'',
|
||||
'router = APIRouter()',
|
||||
'',
|
||||
'@router.post("/webhooks/slack")',
|
||||
'async def slack_webhook(req: Request):',
|
||||
' payload = await req.json()',
|
||||
' if "@open-swe" not in payload["text"]:',
|
||||
' return {"ok": True}',
|
||||
' await enqueue_run(thread_id_for(',
|
||||
' "slack", payload["channel"]))',
|
||||
].join('\n'),
|
||||
filePath: 'apps/open-swe/webhooks.py',
|
||||
startLine: 1,
|
||||
highlightLines: [9, 10, 11, 12, 13, 14, 15],
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.7, y: layouts.CONTENT_TOP, w: 2.8, h: 1.55,
|
||||
kind: 'success',
|
||||
title: 'Idempotency',
|
||||
text:
|
||||
'Webhook вычисляет thread_id ' +
|
||||
'и проверяет наличие run. ' +
|
||||
'Если есть -- enqueue, ' +
|
||||
'иначе новый run.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.7, y: 2.95, w: 2.8, h: 1.9,
|
||||
kind: 'warning',
|
||||
title: 'Signature',
|
||||
text:
|
||||
'Все handlers обязаны ' +
|
||||
'проверять X-Signature от ' +
|
||||
'Slack / Linear / GitHub. ' +
|
||||
'Не доверяйте без verify.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 15);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md + FastAPI webhook patterns',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildWebhookEndpoints };
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* slides/16-dashboard-ui.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 16 -- Dashboard UI: TanStack Start + Vite
|
||||
* UI structure + how it talks to backend.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildDashboardUI(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Dashboard UI',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.7, h: 3.5,
|
||||
language: 'bash',
|
||||
code: [
|
||||
'# apps/open-swe-ui/',
|
||||
'# TanStack Start + Vite + React 19',
|
||||
'',
|
||||
'src/routes/',
|
||||
' __root.tsx',
|
||||
' index.tsx # thread list',
|
||||
' thread.$id.tsx # chat',
|
||||
' settings.tsx',
|
||||
'src/components/',
|
||||
' ChatMessage.tsx',
|
||||
' DiffViewer.tsx',
|
||||
'src/lib/',
|
||||
' client.ts # OpenSWEClient',
|
||||
].join('\n'),
|
||||
filePath: 'apps/open-swe-ui/tree.sh',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.4, y: layouts.CONTENT_TOP, w: 3.1, h: 1.5,
|
||||
kind: 'info',
|
||||
title: 'Только UI',
|
||||
text:
|
||||
'Dashboard -- presentation ' +
|
||||
'layer. Агентская логика в ' +
|
||||
'Python backend. UI через ' +
|
||||
'REST + SSE.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.4, y: 2.85, w: 3.1, h: 2.0,
|
||||
kind: 'success',
|
||||
title: 'Features',
|
||||
text:
|
||||
'* GitHub OAuth login\n' +
|
||||
'* thread list + filters\n' +
|
||||
'* chat with streaming\n' +
|
||||
'* diff viewer для PR\n' +
|
||||
'* per-user settings',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 16);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 270-287',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildDashboardUI };
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* slides/17-per-user-settings.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 17 -- Per-user settings + team defaults + repos
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildPerUserSettings(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Per-user настройки',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
|
||||
language: 'python',
|
||||
code: [
|
||||
'# settings schema (per-user)',
|
||||
'USER_DEFAULTS = {',
|
||||
' "model": "anthropic:claude-opus-4-6",',
|
||||
' "profile": "balanced",',
|
||||
' "max_steps": 80,',
|
||||
' "auto_open_pr": False,',
|
||||
' "require_approval": True,',
|
||||
' "extra_repos": ["acme/internal-tools"],',
|
||||
'}',
|
||||
'',
|
||||
'# team defaults (allowlist)',
|
||||
'TEAM_DEFAULTS = {',
|
||||
' "sandbox_backend": "ModalBackend",',
|
||||
' "allowed_repos": ["acme/*"],',
|
||||
'}',
|
||||
].join('\n'),
|
||||
filePath: 'open_swe/settings.py',
|
||||
startLine: 1,
|
||||
highlightLines: [3, 4, 5, 6, 7, 11, 12],
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.55,
|
||||
kind: 'info',
|
||||
title: 'User mappings',
|
||||
text:
|
||||
'GitHub login -> Slack user -> ' +
|
||||
'Linear user. Один человек ' +
|
||||
'получает свой профиль во ' +
|
||||
'всех трех каналах.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: 2.95, w: 3.2, h: 1.9,
|
||||
kind: 'warning',
|
||||
title: 'Precedence',
|
||||
text:
|
||||
'user > team > global. Per-user ' +
|
||||
'override работает для всех ' +
|
||||
'полей, кроме allowed_repos ' +
|
||||
'-- это team-level allowlist.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 17);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 270-280',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildPerUserSettings };
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* slides/18-customization-6-points.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 18 -- 6 точек кастомизации
|
||||
* sandbox / model / tools / triggers / prompt / middleware
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildCustomization6(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: '6 точек кастомизации',
|
||||
});
|
||||
|
||||
const items = [
|
||||
{ num: '1', name: 'Sandbox provider', code: 'backend=ModalBackend(...)' },
|
||||
{ num: '2', name: 'Model', code: 'init_chat_model("anthropic:...")' },
|
||||
{ num: '3', name: 'Tools', code: 'tools=[execute, fetch_url, ...]' },
|
||||
{ num: '4', name: 'Triggers', code: 'router.add_route("/my", my_handler)' },
|
||||
{ num: '5', name: 'System prompt', code: 'construct_system_prompt(repo_dir, ...)' },
|
||||
{ num: '6', name: 'Middleware', code: 'middleware=[AuditLogger(), ...]' },
|
||||
];
|
||||
|
||||
const cardY = layouts.CONTENT_TOP;
|
||||
const cardW = 2.95;
|
||||
const cardH = 1.65;
|
||||
const gap = 0.13;
|
||||
|
||||
items.forEach(function (it, idx) {
|
||||
const col = idx % 3;
|
||||
const row = Math.floor(idx / 3);
|
||||
const cx = 0.5 + col * (cardW + gap);
|
||||
const cy = cardY + row * (cardH + gap);
|
||||
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: cx, y: cy, w: cardW, h: cardH,
|
||||
fill: { color: theme.palette.bg.elevated },
|
||||
line: { color: theme.palette.border.subtle, width: 0.75 },
|
||||
rectRadius: 0.08,
|
||||
});
|
||||
|
||||
// Number badge
|
||||
slide.addShape(pres.ShapeType.ellipse, {
|
||||
x: cx + 0.15, y: cy + 0.15, w: 0.45, h: 0.45,
|
||||
fill: { color: theme.palette.accent.primary },
|
||||
line: { color: theme.palette.accent.primary, width: 1 },
|
||||
});
|
||||
slide.addText(it.num, {
|
||||
x: cx + 0.15, y: cy + 0.15, w: 0.45, h: 0.45,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: 18,
|
||||
color: theme.palette.text.inverse,
|
||||
bold: true,
|
||||
align: 'center',
|
||||
valign: 'middle',
|
||||
});
|
||||
|
||||
slide.addText(it.name, {
|
||||
x: cx + 0.7, y: cy + 0.15, w: cardW - 0.85, h: 0.45,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.h3,
|
||||
color: theme.palette.text.primary,
|
||||
bold: true,
|
||||
valign: 'middle',
|
||||
});
|
||||
|
||||
// Code block
|
||||
slide.addShape(pres.ShapeType.rect, {
|
||||
x: cx + 0.15, y: cy + 0.7, w: cardW - 0.3, h: cardH - 0.85,
|
||||
fill: { color: theme.palette.bg.code },
|
||||
line: { color: theme.palette.border.subtle, width: 0.5 },
|
||||
});
|
||||
slide.addText(it.code, {
|
||||
x: cx + 0.25, y: cy + 0.75, w: cardW - 0.5, h: cardH - 0.95,
|
||||
fontFace: helpers.withFallback(theme.fonts.code),
|
||||
fontSize: theme.sizes.code,
|
||||
color: theme.palette.text.primary,
|
||||
valign: 'top',
|
||||
});
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 18);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'github.com/langchain-ai/open-swe/blob/main/CUSTOMIZATION.md',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildCustomization6 };
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* slides/19-three-graphs.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 19 -- Three graphs: agent / reviewer / analyzer
|
||||
* Multi-graph architecture.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildThreeGraphs(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Three graphs',
|
||||
});
|
||||
|
||||
const graphs = [
|
||||
{
|
||||
name: 'agent graph',
|
||||
desc: 'Основной deep-agent: planner + coder + tools + subagents.',
|
||||
nodes: 'START -> plan -> execute -> review -> open_pr -> END',
|
||||
},
|
||||
{
|
||||
name: 'reviewer graph',
|
||||
desc: 'CI-стиль: lint, type-check, tests, security-scan. Без LLM-агента.',
|
||||
nodes: 'START -> run_ci -> parse -> report -> END',
|
||||
},
|
||||
{
|
||||
name: 'analyzer graph',
|
||||
desc: 'Анализ ретроспективы: что заняло больше шагов, где loop, где cost spikes.',
|
||||
nodes: 'START -> load_trace -> aggregate -> summarize -> END',
|
||||
},
|
||||
];
|
||||
|
||||
const cardY = layouts.CONTENT_TOP;
|
||||
const cardW = 9.0;
|
||||
const cardH = 1.05;
|
||||
const gap = 0.13;
|
||||
|
||||
graphs.forEach(function (g, idx) {
|
||||
const cy = cardY + idx * (cardH + gap);
|
||||
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: 0.5, y: cy, w: cardW, h: cardH,
|
||||
fill: { color: theme.palette.bg.elevated },
|
||||
line: { color: theme.palette.border.subtle, width: 0.75 },
|
||||
rectRadius: 0.08,
|
||||
});
|
||||
|
||||
// Name badge
|
||||
slide.addShape(pres.ShapeType.rect, {
|
||||
x: 0.6, y: cy + 0.12, w: 2.4, h: 0.4,
|
||||
fill: { color: theme.palette.accent.primary },
|
||||
line: { color: theme.palette.accent.primary, width: 1 },
|
||||
rectRadius: 0.04,
|
||||
});
|
||||
slide.addText(g.name, {
|
||||
x: 0.6, y: cy + 0.12, w: 2.4, h: 0.4,
|
||||
fontFace: helpers.withFallback(theme.fonts.code),
|
||||
fontSize: theme.sizes.code,
|
||||
color: theme.palette.text.inverse,
|
||||
bold: true,
|
||||
align: 'center',
|
||||
valign: 'middle',
|
||||
});
|
||||
|
||||
// Desc
|
||||
slide.addText(g.desc, {
|
||||
x: 3.1, y: cy + 0.1, w: 6.3, h: 0.4,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.body,
|
||||
color: theme.palette.text.secondary,
|
||||
valign: 'middle',
|
||||
});
|
||||
|
||||
// Node line
|
||||
slide.addText(g.nodes, {
|
||||
x: 0.6, y: cy + 0.6, w: cardW - 0.2, h: 0.4,
|
||||
fontFace: helpers.withFallback(theme.fonts.code),
|
||||
fontSize: theme.sizes.code,
|
||||
color: theme.palette.text.primary,
|
||||
valign: 'middle',
|
||||
});
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 19);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 165, 226-247',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildThreeGraphs };
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* slides/20-observability.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 20 -- Observability: Datadog + LangSmith
|
||||
* Tracing + metrics.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildObservability(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Observability',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
|
||||
language: 'python',
|
||||
code: [
|
||||
'# observability.py',
|
||||
'from datadog import statsd',
|
||||
'from langchain_core.tracers import (',
|
||||
' LangChainTracer,',
|
||||
')',
|
||||
'',
|
||||
'tracer = LangChainTracer(',
|
||||
' project_name="open-swe-prod",',
|
||||
')',
|
||||
'',
|
||||
'def record_step(thread_id, duration_s):',
|
||||
' statsd.histogram(',
|
||||
' "open_swe.step.duration_s",',
|
||||
' duration_s, tags=[f"t:{thread_id}"],',
|
||||
' )',
|
||||
].join('\n'),
|
||||
filePath: 'open_swe/observability.py',
|
||||
startLine: 1,
|
||||
highlightLines: [3, 4, 5, 8, 9, 10],
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.7,
|
||||
kind: 'info',
|
||||
title: 'LangSmith',
|
||||
text:
|
||||
'Trace каждого run: model ' +
|
||||
'calls, tool calls, retries. ' +
|
||||
'Replay, debug, manual ' +
|
||||
'evaluation.',
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 6.3, y: 3.05, w: 3.2, h: 1.8,
|
||||
kind: 'success',
|
||||
title: 'Datadog',
|
||||
text:
|
||||
'P95 latency, tokens per ' +
|
||||
'run, error rate, sandbox ' +
|
||||
'spend. Alerts на cost ' +
|
||||
'spikes и длинные loops.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 20);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md + Datadog / LangSmith docs',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildObservability };
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* slides/21-production-ready.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 21 -- Production: чеклист prod-ready
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildProductionReady(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Production: prod-ready',
|
||||
});
|
||||
|
||||
// Two columns of checklist items
|
||||
const leftItems = [
|
||||
'docker-compose / k8s',
|
||||
'managed Postgres',
|
||||
'Vault / sealed-secrets',
|
||||
'TLS + reverse proxy',
|
||||
'rate limit на /webhooks/*',
|
||||
'idempotency keys',
|
||||
];
|
||||
|
||||
const rightItems = [
|
||||
'LangSmith prod-project',
|
||||
'Datadog dashboards + SLO',
|
||||
'sandbox cost budget',
|
||||
'audit log всех PR',
|
||||
'docs: runbook, on-call',
|
||||
'load test 100 threads',
|
||||
];
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 4.4, h: 1.6,
|
||||
kind: 'success',
|
||||
title: 'Infra',
|
||||
text: leftItems.map(function (i) { return '+ ' + i; }).join('\n'),
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 5.1, y: layouts.CONTENT_TOP, w: 4.4, h: 1.6,
|
||||
kind: 'info',
|
||||
title: 'Ops',
|
||||
text: rightItems.map(function (i) { return '+ ' + i; }).join('\n'),
|
||||
});
|
||||
|
||||
helpers.addCallout(slide, pres, theme, {
|
||||
x: 0.5, y: 3.15, w: 9.0, h: 1.7,
|
||||
kind: 'warning',
|
||||
title: 'Самый частый пропуск',
|
||||
text:
|
||||
'"Trust the LLM" внутри sandbox. ' +
|
||||
'Без надлежащей изоляции (seccomp, ' +
|
||||
'network policy, ephemeral FS) агент ' +
|
||||
'может сделать rm -rf или curl внутренних ' +
|
||||
'сервисов. Изоляция важнее prompts.',
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 21);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'production-readiness checklist + SRE playbook',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildProductionReady };
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* slides/22-typescript.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 22 -- TypeScript analogues: где есть, где нет
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildTypescript(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'TypeScript: только UI',
|
||||
});
|
||||
|
||||
helpers.addCodeBlock(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
|
||||
language: 'typescript',
|
||||
code: [
|
||||
'// apps/open-swe-ui/src/lib/client.ts',
|
||||
'import { OpenSWEClient } from',
|
||||
' "@openswe/client";',
|
||||
'',
|
||||
'const client = new OpenSWEClient({',
|
||||
' langsmithApiKey: process.env.',
|
||||
' LANGSMITH_API_KEY!,',
|
||||
'});',
|
||||
'',
|
||||
'await client.invoke({',
|
||||
' threadId: "issue-123",',
|
||||
' prompt: "Fix the bug",',
|
||||
'});',
|
||||
].join('\n'),
|
||||
filePath: 'apps/open-swe-ui/src/lib/client.ts',
|
||||
startLine: 1,
|
||||
});
|
||||
|
||||
helpers.addProsCons(slide, pres, theme, {
|
||||
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 3.5,
|
||||
pros: [
|
||||
'UI: полностью TS',
|
||||
'TanStack Start + Vite',
|
||||
'strict TS config',
|
||||
'streaming SDK',
|
||||
],
|
||||
cons: [
|
||||
'Нет TS для backend',
|
||||
'Агент -- Python only',
|
||||
'Webhook handlers только Py',
|
||||
'SDK -- thin wrapper',
|
||||
],
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 22);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 270-287',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildTypescript };
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* slides/23-pros-cons.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 23 -- Pros / cons vs Claude Code / Devin
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildProsConsComparison(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Open SWE vs Claude Code / Devin',
|
||||
});
|
||||
|
||||
helpers.addProsCons(slide, pres, theme, {
|
||||
x: 0.5, y: layouts.CONTENT_TOP, w: 4.4, h: 1.7,
|
||||
pros: [
|
||||
'MIT license, форкайте',
|
||||
'Pluggable sandbox',
|
||||
'Triggers: Slack/Linear/GH',
|
||||
'AGENTS.md convention',
|
||||
'Subagents + middleware',
|
||||
'Built on Deep Agents',
|
||||
],
|
||||
cons: [
|
||||
'Не finished product',
|
||||
'Sandbox = платные аккаунты',
|
||||
'OAuth setup нужен',
|
||||
'"Trust the LLM" модель',
|
||||
'Prod deployment сложный',
|
||||
'Доки быстро устаревают',
|
||||
],
|
||||
});
|
||||
|
||||
// Comparison card on the right
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: 5.1, y: layouts.CONTENT_TOP, w: 4.4, h: 3.5,
|
||||
fill: { color: theme.palette.bg.elevated },
|
||||
line: { color: theme.palette.border.subtle, width: 1 },
|
||||
rectRadius: 0.08,
|
||||
});
|
||||
|
||||
slide.addText('VS Claude Code / Devin', {
|
||||
x: 5.3, y: layouts.CONTENT_TOP + 0.1, w: 4.0, h: 0.3,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.h3,
|
||||
color: theme.palette.accent.secondary,
|
||||
bold: true,
|
||||
});
|
||||
|
||||
const compareRows = [
|
||||
['Audience', 'IDE', 'SaaS', 'org'],
|
||||
['License', 'proprietary', 'proprietary','MIT'],
|
||||
['Trigger', 'manual', 'Web UI', 'Slack/Lin/GH'],
|
||||
['Sandbox', 'local+cloud', 'remote', 'pluggable'],
|
||||
['Customize', 'config', 'no', 'full src'],
|
||||
['Runs in', 'IDE/term', 'cloud', 'your infra'],
|
||||
];
|
||||
|
||||
const tY = layouts.CONTENT_TOP + 0.55;
|
||||
compareRows.forEach(function (row, idx) {
|
||||
const ry = tY + idx * 0.45;
|
||||
row.forEach(function (cell, cidx) {
|
||||
const widths = [1.1, 1.0, 1.0, 1.1];
|
||||
let cx = 5.3;
|
||||
for (let i = 0; i < cidx; i++) cx += widths[i] + 0.05;
|
||||
slide.addText(cell, {
|
||||
x: cx, y: ry, w: widths[cidx], h: 0.4,
|
||||
fontFace: helpers.withFallback(
|
||||
cidx === 0 ? theme.fonts.ui : theme.fonts.code),
|
||||
fontSize: cidx === 0 ? theme.sizes.caption : 8,
|
||||
color: cidx === 3
|
||||
? theme.palette.accent.tertiary
|
||||
: theme.palette.text.secondary,
|
||||
bold: cidx === 0,
|
||||
valign: 'middle',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 23);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'research/per-tech/openswe.md: 291-310',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildProsConsComparison };
|
||||
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* slides/24-roadmap.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Slide 24 -- Roadmap: что ожидать
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { helpers, layouts } = require('../../design-system');
|
||||
|
||||
function buildRoadmap(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
helpers.slideBase(slide, pres, theme);
|
||||
helpers.addHeader(slide, pres, theme, {
|
||||
eyebrow: 'STAGE 4',
|
||||
sectionNumber: 4,
|
||||
title: 'Roadmap: 2026-2027',
|
||||
});
|
||||
|
||||
// Timeline cards
|
||||
const items = [
|
||||
{
|
||||
period: 'Q2 2026',
|
||||
title: 'Stable v1',
|
||||
points: [
|
||||
'deep-agent harness заморожен',
|
||||
'API стабилизирован',
|
||||
'semver commitment',
|
||||
],
|
||||
},
|
||||
{
|
||||
period: 'Q3 2026',
|
||||
title: 'Multi-tenant',
|
||||
points: [
|
||||
'per-org quota + billing',
|
||||
'team-level audit log',
|
||||
'rbac на sandbox providers',
|
||||
],
|
||||
},
|
||||
{
|
||||
period: 'Q4 2026',
|
||||
title: 'More triggers',
|
||||
points: [
|
||||
'Jira / Azure DevOps',
|
||||
'Sentry для авто-fix багов',
|
||||
'PagerDuty для incident triage',
|
||||
],
|
||||
},
|
||||
{
|
||||
period: '2027',
|
||||
title: 'SDK + Marketplace',
|
||||
points: [
|
||||
'public SDK (Python + TS)',
|
||||
'plugin marketplace',
|
||||
'shared subagent library',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const cardY = layouts.CONTENT_TOP;
|
||||
const cardW = 2.2;
|
||||
const cardH = 3.5;
|
||||
const gap = 0.13;
|
||||
|
||||
items.forEach(function (it, idx) {
|
||||
const cx = 0.5 + idx * (cardW + gap);
|
||||
|
||||
slide.addShape(pres.ShapeType.roundRect, {
|
||||
x: cx, y: cardY, w: cardW, h: cardH,
|
||||
fill: { color: theme.palette.bg.elevated },
|
||||
line: { color: theme.palette.accent.primary, width: 1 },
|
||||
rectRadius: 0.08,
|
||||
});
|
||||
|
||||
// Period header
|
||||
slide.addShape(pres.ShapeType.rect, {
|
||||
x: cx, y: cardY, w: cardW, h: 0.5,
|
||||
fill: { color: theme.palette.accent.primary },
|
||||
line: { color: theme.palette.accent.primary, width: 1 },
|
||||
});
|
||||
slide.addText(it.period, {
|
||||
x: cx, y: cardY, w: cardW, h: 0.5,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.h3,
|
||||
color: theme.palette.text.inverse,
|
||||
bold: true,
|
||||
align: 'center',
|
||||
valign: 'middle',
|
||||
});
|
||||
|
||||
// Title
|
||||
slide.addText(it.title, {
|
||||
x: cx + 0.15, y: cardY + 0.65, w: cardW - 0.3, h: 0.4,
|
||||
fontFace: helpers.withFallback(theme.fonts.ui),
|
||||
fontSize: theme.sizes.h3,
|
||||
color: theme.palette.text.primary,
|
||||
bold: true,
|
||||
});
|
||||
|
||||
// Divider
|
||||
slide.addShape(pres.ShapeType.line, {
|
||||
x: cx + 0.15, y: cardY + 1.1, w: cardW - 0.3, h: 0,
|
||||
line: { color: theme.palette.border.subtle, width: 0.75 },
|
||||
});
|
||||
|
||||
// Bullets
|
||||
const bulletItems = it.points.map(function (p) {
|
||||
return {
|
||||
text: '> ' + p + '\n',
|
||||
options: {
|
||||
fontFace: helpers.withFallback(theme.fonts.code),
|
||||
fontSize: 10,
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
};
|
||||
});
|
||||
slide.addText(bulletItems, {
|
||||
x: cx + 0.15, y: cardY + 1.2, w: cardW - 0.3, h: cardH - 1.3,
|
||||
valign: 'top',
|
||||
paraSpaceAfter: 4,
|
||||
});
|
||||
});
|
||||
|
||||
helpers.addPageNumber(slide, pres, theme, 24);
|
||||
helpers.addSourceLine(slide, pres, theme, {
|
||||
source: 'github.com/langchain-ai/open-swe/issues + blog.langchain.com',
|
||||
});
|
||||
return slide;
|
||||
}
|
||||
|
||||
module.exports = { buildRoadmap };
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* slides/section4-openswe/compile.js
|
||||
* ----------------------------------------------------------------------------
|
||||
* Compiles all Open SWE section slides into section4.pptx.
|
||||
*
|
||||
* Usage:
|
||||
* node compile.js
|
||||
*
|
||||
* Output:
|
||||
* ../section4.pptx (relative to this file)
|
||||
* ../section4.pdf (via libreoffice)
|
||||
* ../section4-page-N.png (preview images via pdftoppm)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const pptxgen = require('pptxgenjs');
|
||||
const ds = require('../../design-system');
|
||||
|
||||
const { theme, helpers } = ds;
|
||||
|
||||
// Import all slide builders in order.
|
||||
const slides = [
|
||||
require('./01-cover'),
|
||||
require('./02-what-is-openswe'),
|
||||
require('./03-architecture-overview'),
|
||||
require('./04-create-deep-agent'),
|
||||
require('./05-agents-md-context'),
|
||||
require('./06-middleware'),
|
||||
require('./07-sandbox-providers'),
|
||||
require('./08-sandbox-imports'),
|
||||
require('./09-installation-1'),
|
||||
require('./10-installation-2'),
|
||||
require('./11-github-app'),
|
||||
require('./12-langsmith'),
|
||||
require('./13-triggers-overview'),
|
||||
require('./14-triggers-thread-id'),
|
||||
require('./15-webhook-endpoints'),
|
||||
require('./16-dashboard-ui'),
|
||||
require('./17-per-user-settings'),
|
||||
require('./18-customization-6-points'),
|
||||
require('./19-three-graphs'),
|
||||
require('./20-observability'),
|
||||
require('./21-production-ready'),
|
||||
require('./22-typescript'),
|
||||
require('./23-pros-cons'),
|
||||
require('./24-roadmap'),
|
||||
];
|
||||
|
||||
async function main() {
|
||||
console.log('[compile] building section4.pptx with ' + slides.length + ' slides');
|
||||
|
||||
const pres = new pptxgen();
|
||||
pres.layout = 'LAYOUT_16x9';
|
||||
pres.title = 'lc-evo-deck / Stage 4: Open SWE';
|
||||
pres.subject = 'LangChain Evolution Deck -- Open SWE section';
|
||||
pres.company = 'lc-evo-deck';
|
||||
|
||||
for (let i = 0; i < slides.length; i++) {
|
||||
const mod = slides[i];
|
||||
const builder = pickBuilder(mod);
|
||||
if (typeof builder !== 'function') {
|
||||
throw new Error('slide ' + (i + 1) + ': no builder function');
|
||||
}
|
||||
builder(pres, theme);
|
||||
}
|
||||
|
||||
// Output: one level up from this file, into sec4-openswe/ root.
|
||||
const outDir = path.resolve(__dirname, '..', '..');
|
||||
const outPath = path.join(outDir, 'section4.pptx');
|
||||
await pres.writeFile({ fileName: outPath });
|
||||
console.log('[compile] wrote ' + outPath);
|
||||
|
||||
return outPath;
|
||||
}
|
||||
|
||||
function pickBuilder(mod) {
|
||||
// Each slide file exports either buildX or default.
|
||||
if (typeof mod.buildCover === 'function') return mod.buildCover;
|
||||
if (typeof mod.buildWhatIsOpenSWE === 'function') return mod.buildWhatIsOpenSWE;
|
||||
if (typeof mod.buildArchitectureOverview === 'function') return mod.buildArchitectureOverview;
|
||||
if (typeof mod.buildCreateDeepAgent === 'function') return mod.buildCreateDeepAgent;
|
||||
if (typeof mod.buildAgentsMdConvention === 'function') return mod.buildAgentsMdConvention;
|
||||
if (typeof mod.buildMiddleware === 'function') return mod.buildMiddleware;
|
||||
if (typeof mod.buildSandboxProviders === 'function') return mod.buildSandboxProviders;
|
||||
if (typeof mod.buildSandboxImports === 'function') return mod.buildSandboxImports;
|
||||
if (typeof mod.buildInstallPart1 === 'function') return mod.buildInstallPart1;
|
||||
if (typeof mod.buildInstallPart2 === 'function') return mod.buildInstallPart2;
|
||||
if (typeof mod.buildGithubApp === 'function') return mod.buildGithubApp;
|
||||
if (typeof mod.buildLangSmithSetup === 'function') return mod.buildLangSmithSetup;
|
||||
if (typeof mod.buildTriggersOverview === 'function') return mod.buildTriggersOverview;
|
||||
if (typeof mod.buildTriggerRouting === 'function') return mod.buildTriggerRouting;
|
||||
if (typeof mod.buildWebhookEndpoints === 'function') return mod.buildWebhookEndpoints;
|
||||
if (typeof mod.buildDashboardUI === 'function') return mod.buildDashboardUI;
|
||||
if (typeof mod.buildPerUserSettings === 'function') return mod.buildPerUserSettings;
|
||||
if (typeof mod.buildCustomization6 === 'function') return mod.buildCustomization6;
|
||||
if (typeof mod.buildThreeGraphs === 'function') return mod.buildThreeGraphs;
|
||||
if (typeof mod.buildObservability === 'function') return mod.buildObservability;
|
||||
if (typeof mod.buildProductionReady === 'function') return mod.buildProductionReady;
|
||||
if (typeof mod.buildTypescript === 'function') return mod.buildTypescript;
|
||||
if (typeof mod.buildProsConsComparison === 'function') return mod.buildProsConsComparison;
|
||||
if (typeof mod.buildRoadmap === 'function') return mod.buildRoadmap;
|
||||
return null;
|
||||
}
|
||||
|
||||
main().catch(function (err) {
|
||||
console.error('[compile] failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 67 KiB |