thepopebot 1.2.75 → 1.2.76-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/api/index.js +22 -8
  2. package/bin/cli.js +27 -2
  3. package/bin/sync.js +5 -1
  4. package/drizzle/0021_coding_agent_workspace.sql +1 -0
  5. package/drizzle/meta/_journal.json +7 -0
  6. package/lib/ai/index.js +138 -3
  7. package/lib/ai/model.js +6 -0
  8. package/lib/ai/sdk-adapters/CLAUDE.md +113 -0
  9. package/lib/ai/sdk-adapters/claude-code.js +268 -0
  10. package/lib/ai/sdk-adapters/index.js +13 -0
  11. package/lib/ai/session-manager.js +33 -0
  12. package/lib/ai/system-prompt.js +16 -0
  13. package/lib/ai/tools.js +5 -2
  14. package/lib/ai/workspace-setup.js +143 -0
  15. package/lib/channels/telegram.js +78 -7
  16. package/lib/chat/actions.js +223 -10
  17. package/lib/chat/api.js +117 -15
  18. package/lib/chat/components/chat-input.js +78 -33
  19. package/lib/chat/components/chat-input.jsx +74 -23
  20. package/lib/chat/components/chat.js +27 -5
  21. package/lib/chat/components/chat.jsx +27 -3
  22. package/lib/chat/components/chats-page.js +3 -12
  23. package/lib/chat/components/chats-page.jsx +4 -7
  24. package/lib/chat/components/code-mode-toggle.js +110 -14
  25. package/lib/chat/components/code-mode-toggle.jsx +104 -13
  26. package/lib/chat/components/index.js +1 -1
  27. package/lib/chat/components/message.js +3 -3
  28. package/lib/chat/components/message.jsx +3 -3
  29. package/lib/chat/components/settings-chat-page.js +7 -117
  30. package/lib/chat/components/settings-chat-page.jsx +13 -132
  31. package/lib/chat/components/settings-secrets-layout.js +1 -1
  32. package/lib/chat/components/settings-secrets-layout.jsx +1 -1
  33. package/lib/chat/components/settings-secrets-page.js +274 -75
  34. package/lib/chat/components/settings-secrets-page.jsx +327 -65
  35. package/lib/chat/components/ui/combobox.js +18 -2
  36. package/lib/chat/components/ui/combobox.jsx +17 -1
  37. package/lib/code/actions.js +26 -6
  38. package/lib/code/terminal-view.js +36 -9
  39. package/lib/code/terminal-view.jsx +42 -10
  40. package/lib/config.js +12 -1
  41. package/lib/db/chats.js +9 -17
  42. package/lib/db/code-workspaces.js +6 -2
  43. package/lib/db/schema.js +1 -0
  44. package/lib/llm-providers.js +8 -0
  45. package/lib/maintenance.js +31 -21
  46. package/lib/tools/docker.js +24 -9
  47. package/lib/tools/github.js +16 -0
  48. package/lib/tools/telegram.js +115 -0
  49. package/lib/utils/render-md.js +1 -1
  50. package/package.json +2 -1
  51. package/setup/lib/telegram.mjs +9 -69
  52. package/templates/.github/workflows/rebuild-event-handler.yml +1 -1
  53. package/templates/.gitignore.template +1 -1
  54. package/templates/CLAUDE.md +1 -1
  55. package/templates/CLAUDE.md.template +2 -1
  56. package/templates/agent-job/CLAUDE.md.template +1 -2
  57. package/templates/agent-job/SYSTEM.md +2 -2
  58. package/templates/coding-workspace/CLAUDE.md.template +7 -0
  59. package/templates/coding-workspace/SYSTEM.md +0 -0
  60. package/templates/agent-job/SOUL.md +0 -17
package/api/index.js CHANGED
@@ -3,7 +3,7 @@ import { createAgentJob } from '../lib/tools/create-agent-job.js';
3
3
  import { setWebhook } from '../lib/tools/telegram.js';
4
4
  import { getAgentJobStatus, fetchAgentJobLog } from '../lib/tools/github.js';
5
5
  import { getTelegramAdapter } from '../lib/channels/index.js';
6
- import { chat, summarizeAgentJob } from '../lib/ai/index.js';
6
+ import { chat, chatStream, summarizeAgentJob } from '../lib/ai/index.js';
7
7
  import { createNotification } from '../lib/db/notifications.js';
8
8
  import { loadTriggers } from '../lib/triggers.js';
9
9
  import { verifyApiKey } from '../lib/db/api-keys.js';
@@ -211,19 +211,33 @@ async function handleTelegramWebhook(request) {
211
211
  /**
212
212
  * Process a normalized message through the AI layer with channel UX.
213
213
  * Message persistence is handled centrally by the AI layer.
214
+ *
215
+ * Uses chatStream() for progressive tool-call rendering when the adapter
216
+ * supports it (Telegram: sends each tool call as a message, reacts on completion).
217
+ * Falls back to chat() for adapters without streamChatResponse.
214
218
  */
215
219
  async function processChannelMessage(adapter, normalized) {
216
220
  await adapter.acknowledge(normalized.metadata);
217
221
  const stopIndicator = adapter.startProcessingIndicator(normalized.metadata);
218
222
 
219
223
  try {
220
- const response = await chat(
221
- normalized.threadId,
222
- normalized.text,
223
- normalized.attachments,
224
- { userId: 'telegram', chatTitle: 'Telegram' }
225
- );
226
- await adapter.sendResponse(normalized.threadId, response, normalized.metadata);
224
+ if (adapter.streamChatResponse) {
225
+ const chunks = chatStream(
226
+ normalized.threadId,
227
+ normalized.text,
228
+ normalized.attachments,
229
+ { userId: 'telegram', chatTitle: 'Telegram' }
230
+ );
231
+ await adapter.streamChatResponse(normalized.metadata.chatId, chunks);
232
+ } else {
233
+ const response = await chat(
234
+ normalized.threadId,
235
+ normalized.text,
236
+ normalized.attachments,
237
+ { userId: 'telegram', chatTitle: 'Telegram' }
238
+ );
239
+ await adapter.sendResponse(normalized.threadId, response, normalized.metadata);
240
+ }
227
241
  } catch (err) {
228
242
  console.error('Failed to process message with AI:', err);
229
243
  await adapter
package/bin/cli.js CHANGED
@@ -305,6 +305,31 @@ async function init() {
305
305
  console.log(' Created .claude/skills → ../skills/active');
306
306
  }
307
307
 
308
+ // Create .codex/skills → ../skills/active symlink
309
+ const codexSkillsLink = path.join(cwd, '.codex', 'skills');
310
+ if (!fs.existsSync(codexSkillsLink)) {
311
+ fs.mkdirSync(path.dirname(codexSkillsLink), { recursive: true });
312
+ createDirLink('../skills/active', codexSkillsLink);
313
+ console.log(' Created .codex/skills → ../skills/active');
314
+ }
315
+
316
+ // Create .gemini/skills → ../skills/active symlink
317
+ const geminiSkillsLink = path.join(cwd, '.gemini', 'skills');
318
+ if (!fs.existsSync(geminiSkillsLink)) {
319
+ fs.mkdirSync(path.dirname(geminiSkillsLink), { recursive: true });
320
+ createDirLink('../skills/active', geminiSkillsLink);
321
+ console.log(' Created .gemini/skills → ../skills/active');
322
+ }
323
+
324
+ // Create .kimi/skills → ../skills/active symlink
325
+ const kimiSkillsLink = path.join(cwd, '.kimi', 'skills');
326
+ if (!fs.existsSync(kimiSkillsLink)) {
327
+ fs.mkdirSync(path.dirname(kimiSkillsLink), { recursive: true });
328
+ createDirLink('../skills/active', kimiSkillsLink);
329
+ console.log(' Created .kimi/skills → ../skills/active');
330
+ }
331
+
332
+
308
333
  // Report backed-up files
309
334
  if (backedUp.length > 0) {
310
335
  console.log(`\n Backed up ${backedUp.length} file(s) to ${path.relative(cwd, backupDir)}/`);
@@ -420,7 +445,7 @@ function reset(filePath) {
420
445
  console.log(` ${destPath(file)}`);
421
446
  }
422
447
  console.log('\nUsage: thepopebot reset <file>');
423
- console.log('Example: thepopebot reset agent-job/SOUL.md\n');
448
+ console.log('Example: thepopebot reset agent-job/SYSTEM.md\n');
424
449
  return;
425
450
  }
426
451
 
@@ -502,7 +527,7 @@ function diff(filePath) {
502
527
  console.log(' All files match package templates.');
503
528
  }
504
529
  console.log('\nUsage: thepopebot diff <file>');
505
- console.log('Example: thepopebot diff agent-job/SOUL.md\n');
530
+ console.log('Example: thepopebot diff agent-job/SYSTEM.md\n');
506
531
  return;
507
532
  }
508
533
 
package/bin/sync.js CHANGED
@@ -271,10 +271,13 @@ function buildDockerImage(projectPath) {
271
271
  const version = pkg.version;
272
272
  const imageTag = `stephengpope/thepopebot:event-handler-${version}`;
273
273
 
274
- // Copy web/ to project for Docker build context
274
+ // Copy web/ and docker/ to project for Docker build context
275
275
  const webSrc = path.join(PACKAGE_DIR, 'web');
276
276
  const webDest = path.join(projectPath, 'web');
277
+ const dockerSrc = path.join(PACKAGE_DIR, 'docker');
278
+ const dockerDest = path.join(projectPath, 'docker');
277
279
  fs.cpSync(webSrc, webDest, { recursive: true });
280
+ fs.cpSync(dockerSrc, dockerDest, { recursive: true });
278
281
 
279
282
  try {
280
283
  execSync(`docker build -f - -t ${imageTag} .`, {
@@ -284,6 +287,7 @@ function buildDockerImage(projectPath) {
284
287
  });
285
288
  } finally {
286
289
  fs.rmSync(webDest, { recursive: true, force: true });
290
+ fs.rmSync(dockerDest, { recursive: true, force: true });
287
291
  }
288
292
 
289
293
  // Clean up dangling images from previous builds
@@ -0,0 +1 @@
1
+ ALTER TABLE `code_workspaces` ADD `coding_agent` text;
@@ -148,6 +148,13 @@
148
148
  "when": 1774327178886,
149
149
  "tag": "0020_natural_fabian_cortez",
150
150
  "breakpoints": true
151
+ },
152
+ {
153
+ "idx": 21,
154
+ "version": "6",
155
+ "when": 1775865600000,
156
+ "tag": "0021_coding_agent_workspace",
157
+ "breakpoints": true
151
158
  }
152
159
  ]
153
160
  }
package/lib/ai/index.js CHANGED
@@ -4,9 +4,16 @@ import { z } from 'zod';
4
4
  import { getAgentChat, getCodeChat } from './agent.js';
5
5
  import { createModel } from './model.js';
6
6
  import path from 'path';
7
+ import { existsSync } from 'fs';
7
8
  import { PROJECT_ROOT } from '../paths.js';
8
9
  import { render_md } from '../utils/render-md.js';
10
+ import { buildCodingAgentSystemPrompt } from './system-prompt.js';
9
11
  import { getChatById, createChat, saveMessage, updateChatTitle, linkChatToWorkspace } from '../db/chats.js';
12
+ import { getConfig } from '../config.js';
13
+ import { getSdkAdapter } from './sdk-adapters/index.js';
14
+ import { ensureWorkspaceRepo, ensureSkills } from './workspace-setup.js';
15
+ import { readSessionId, writeSessionId } from './session-manager.js';
16
+ import { workspaceDir as getWorkspaceDir } from '../tools/docker.js';
10
17
 
11
18
  /**
12
19
  * Ensure a chat exists in the DB and save a message.
@@ -39,6 +46,18 @@ function persistMessage(threadId, role, text, options = {}) {
39
46
  * @returns {Promise<string>} AI response text
40
47
  */
41
48
  async function chat(threadId, message, attachments = [], options = {}) {
49
+ // SDK path: delegate to chatStream and collect text
50
+ const sdkAdapter = getSdkAdapter(getConfig('CODING_AGENT'));
51
+
52
+ if (sdkAdapter) {
53
+ let fullText = '';
54
+ for await (const chunk of chatStream(threadId, message, attachments, options)) {
55
+ if (chunk.type === 'text') fullText += chunk.text;
56
+ }
57
+ return fullText;
58
+ }
59
+
60
+ // Legacy LangGraph path
42
61
  const agent = await getAgentChat();
43
62
 
44
63
  // Save user message to DB
@@ -136,15 +155,131 @@ async function* chatStream(threadId, message, attachments = [], options = {}) {
136
155
  workspaceId = workspaceId || existingChat.codeWorkspaceId;
137
156
  }
138
157
 
139
- const agent = isCodeMode
140
- ? await getCodeChat()
141
- : await getAgentChat();
158
+ // ── SDK path: direct in-process SDK call (no LangGraph, no Docker) ──
159
+ const sdkAdapter = getSdkAdapter(getConfig('CODING_AGENT'));
160
+ const useSDK = !!sdkAdapter;
142
161
 
143
162
  // Save user message to DB (skip on regeneration — message already exists)
144
163
  if (!options.skipUserPersist) {
145
164
  persistMessage(threadId, 'user', message || '[attachment]', options);
146
165
  }
147
166
 
167
+ if (useSDK) {
168
+ // 1. Workspace setup (idempotent — skips if .git exists)
169
+ const wsBaseDir = getWorkspaceDir(workspaceId);
170
+ const repoDir = path.join(wsBaseDir, 'workspace');
171
+
172
+ const { getCodeWorkspaceById } = await import('../db/code-workspaces.js');
173
+ const workspace = getCodeWorkspaceById(workspaceId);
174
+ const featureBranch = workspace?.featureBranch;
175
+
176
+ const needsSetup = !existsSync(path.join(repoDir, '.git'));
177
+ const setupToolCallId = `setup-${workspaceId.slice(0, 8)}`;
178
+ const setupArgs = { repo, branch, featureBranch };
179
+
180
+ if (needsSetup) {
181
+ yield { type: 'tool-call', toolCallId: setupToolCallId, toolName: 'workspace', args: setupArgs };
182
+ }
183
+
184
+ try {
185
+ const setupOutput = await ensureWorkspaceRepo({ workspaceDir: repoDir, repo, branch, featureBranch });
186
+ ensureSkills(repoDir, isCodeMode ? 'code' : 'agent');
187
+ if (needsSetup) {
188
+ const result = setupOutput || `Workspace ready on ${featureBranch || branch}`;
189
+ yield { type: 'tool-result', toolCallId: setupToolCallId, result };
190
+ persistMessage(threadId, 'assistant', JSON.stringify({
191
+ type: 'tool-invocation',
192
+ toolCallId: setupToolCallId,
193
+ toolName: 'workspace',
194
+ state: 'output-available',
195
+ input: setupArgs,
196
+ output: result,
197
+ }), options);
198
+ }
199
+ } catch (err) {
200
+ if (needsSetup) {
201
+ yield { type: 'tool-result', toolCallId: setupToolCallId, result: `Setup failed: ${err.message}` };
202
+ }
203
+ throw err;
204
+ }
205
+
206
+ // 2. Session continuity
207
+ const sessionId = readSessionId(wsBaseDir);
208
+
209
+ // 3. System prompt
210
+ const systemPrompt = buildCodingAgentSystemPrompt(isCodeMode ? 'code' : 'agent');
211
+
212
+ // 4. Stream from SDK adapter
213
+ let pendingText = '';
214
+ const pendingToolCalls = new Map();
215
+
216
+ try {
217
+ for await (const chunk of sdkAdapter({
218
+ prompt: message,
219
+ workspaceDir: repoDir,
220
+ systemPrompt,
221
+ sessionId,
222
+ permissionMode: codeModeType,
223
+ attachments,
224
+ workspaceId,
225
+ chatMode: isCodeMode ? 'code' : 'agent',
226
+ })) {
227
+ // Write session ID on first meta chunk
228
+ if (chunk.type === 'meta' && chunk.sessionId) {
229
+ writeSessionId(wsBaseDir, chunk.sessionId);
230
+ }
231
+
232
+ // DB persistence
233
+ if (chunk.type === 'text') {
234
+ pendingText += chunk.text;
235
+ } else if (chunk.type === 'tool-call') {
236
+ // Flush accumulated text before tool call
237
+ if (pendingText) {
238
+ persistMessage(threadId, 'assistant', pendingText, options);
239
+ pendingText = '';
240
+ }
241
+ pendingToolCalls.set(chunk.toolCallId, { toolName: chunk.toolName, args: chunk.args });
242
+ } else if (chunk.type === 'tool-result') {
243
+ const tc = pendingToolCalls.get(chunk.toolCallId);
244
+ if (tc) {
245
+ persistMessage(threadId, 'assistant', JSON.stringify({
246
+ type: 'tool-invocation',
247
+ toolCallId: chunk.toolCallId,
248
+ toolName: tc.toolName,
249
+ state: 'output-available',
250
+ input: tc.args,
251
+ output: chunk.result,
252
+ }), options);
253
+ pendingToolCalls.delete(chunk.toolCallId);
254
+ }
255
+ }
256
+
257
+ yield chunk;
258
+ }
259
+ } catch (err) {
260
+ console.error('[chatStream] error:', err);
261
+ throw err;
262
+ } finally {
263
+ // Flush remaining text
264
+ if (pendingText) {
265
+ persistMessage(threadId, 'assistant', pendingText, options);
266
+ }
267
+ }
268
+
269
+ // Auto-generate title for new chats
270
+ if (options.userId && message) {
271
+ autoTitle(threadId, message).catch(() => {});
272
+ }
273
+
274
+ return;
275
+ }
276
+
277
+ // ── Legacy path: LangGraph + Docker (non-claude-code agents + job mode) ──
278
+
279
+ const agent = isCodeMode
280
+ ? await getCodeChat()
281
+ : await getAgentChat();
282
+
148
283
  // Build content blocks: text + any image/PDF attachments as vision
149
284
  const content = [];
150
285
 
package/lib/ai/model.js CHANGED
@@ -118,6 +118,12 @@ export async function createModel(options = {}) {
118
118
  if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
119
119
  return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://openrouter.ai/api/v1' } });
120
120
  }
121
+ case 'nvidia': {
122
+ const { ChatOpenAI } = await import('@langchain/openai');
123
+ const apiKey = getConfig('NVIDIA_API_KEY');
124
+ if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
125
+ return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://integrate.api.nvidia.com/v1' } });
126
+ }
121
127
  default:
122
128
  throw new Error(`Unknown LLM provider: ${provider}`);
123
129
  }
@@ -0,0 +1,113 @@
1
+ # lib/ai/sdk-adapters/ — SDK Adapter System
2
+
3
+ In-process SDK adapters that replace the legacy LangGraph + Docker path for chat. Each adapter wraps a coding agent's SDK and yields a unified chunk stream consumed by `chatStream()` in `lib/ai/index.js`.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Browser → POST /stream/chat (api.js)
9
+ → chatStream() (index.js)
10
+ → getSdkAdapter() returns adapter function or null
11
+ → if adapter: workspace setup → SDK adapter streaming → DB persistence
12
+ → if null: falls back to legacy LangGraph/Docker path
13
+ ```
14
+
15
+ The adapter is a pure stream translator — it receives a prompt and options, calls the SDK, and yields normalized chunks. Everything else (workspace setup, DB persistence, session continuity, system prompts) is handled by `chatStream()` in `index.js`.
16
+
17
+ ## Existing Adapter
18
+
19
+ | File | Agent | SDK |
20
+ |------|-------|-----|
21
+ | `claude-code.js` | `claude-code` | `@anthropic-ai/claude-agent-sdk` |
22
+
23
+ ## Adding a New SDK Adapter
24
+
25
+ ### 1. Create the adapter file
26
+
27
+ Create `{agent-name}.js` in this directory. Export a single async generator function:
28
+
29
+ ```js
30
+ export async function* myAgentStream({ prompt, workspaceDir, systemPrompt, sessionId, permissionMode, attachments }) {
31
+ // ... call the SDK, yield chunks
32
+ }
33
+ ```
34
+
35
+ ### 2. Required chunk types to yield
36
+
37
+ The adapter MUST yield these chunk types for `chatStream()` and `api.js` to work correctly:
38
+
39
+ | Chunk | Shape | When | Purpose |
40
+ |-------|-------|------|---------|
41
+ | `meta` | `{ type: 'meta', sessionId: string }` | First event | Session ID for continuity across messages. `chatStream()` writes this to disk via `writeSessionId()` so subsequent messages resume the session. |
42
+ | `text` | `{ type: 'text', text: string }` | Text output | Streamed to UI as deltas. Accumulated by `chatStream()` and flushed to DB as assistant messages at tool boundaries and stream end. |
43
+ | `tool-call` | `{ type: 'tool-call', toolCallId: string, toolName: string, args: object }` | Tool invocation starts | Triggers tool UI in the browser. May be yielded twice: once at start with `args: {}`, once at `content_block_stop` with complete args. `chatStream()` tracks these in `pendingToolCalls` for pairing with results. |
44
+ | `tool-result` | `{ type: 'tool-result', toolCallId: string, result: string }` | Tool completes | Paired with the matching `tool-call` by `toolCallId`. `chatStream()` persists the pair as a `tool-invocation` JSON message in the DB. |
45
+ | `result` | `{ type: 'result', text: string, cost?: number, duration?: number, subtype?: string }` | Stream ends | Final summary. Logged by `chatStream()`, not persisted or sent to UI. |
46
+
47
+ Optional:
48
+ | `unknown` | `{ type: 'unknown', raw: any }` | Unrecognized events | `api.js` renders these as collapsible boxes in the UI. Use for debugging unhandled SDK events. |
49
+
50
+ ### 3. Register in index.js
51
+
52
+ Add the import and mapping in `getSdkAdapter()`:
53
+
54
+ ```js
55
+ import { myAgentStream } from './my-agent.js';
56
+
57
+ export function getSdkAdapter(agentType) {
58
+ if (agentType === 'claude-code') return claudeCodeStream;
59
+ if (agentType === 'my-agent') return myAgentStream;
60
+ return null;
61
+ }
62
+ ```
63
+
64
+ The `agentType` string comes from the `CODING_AGENT` config value set in the admin UI.
65
+
66
+ ### 4. Auth resolution
67
+
68
+ Use `buildAgentAuthEnv(agentType)` from `lib/tools/docker.js` to get credentials from the settings DB. This returns `{ env: string[], backendApi: string }` where `env` is an array of `KEY=value` strings. Parse them into an env object:
69
+
70
+ ```js
71
+ import { buildAgentAuthEnv } from '../../tools/docker.js';
72
+
73
+ const env = { ...process.env };
74
+ const { env: authEnvPairs } = buildAgentAuthEnv('my-agent');
75
+ for (const pair of authEnvPairs) {
76
+ const eqIdx = pair.indexOf('=');
77
+ if (eqIdx > 0) env[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
78
+ }
79
+ ```
80
+
81
+ The agent's auth config (API keys, OAuth tokens, provider selection) is managed in the admin UI at `/admin/event-handler/coding-agents` and stored in the settings DB. `buildAgentAuthEnv()` reads it — you don't need to access the settings DB directly.
82
+
83
+ ### 5. Function parameters
84
+
85
+ | Param | Type | Description |
86
+ |-------|------|-------------|
87
+ | `prompt` | `string` | User message text |
88
+ | `workspaceDir` | `string` | Absolute path to git repo root (the SDK should execute here) |
89
+ | `systemPrompt` | `string\|null` | System prompt for agent mode (null in code mode) |
90
+ | `sessionId` | `string\|null` | Previous session ID to resume (null on first message) |
91
+ | `permissionMode` | `string` | `'plan'` (read-only) or `'code'` (read-write). Map to the SDK's equivalent permission concept. |
92
+ | `attachments` | `Array` | Image attachments: `{ category: 'image', mimeType, dataUrl }` |
93
+
94
+ ### 6. Session continuity contract
95
+
96
+ Multi-turn conversation works via session IDs:
97
+
98
+ 1. First message: `sessionId` param is `null`. Adapter yields `{ type: 'meta', sessionId: '<new-id>' }`.
99
+ 2. `chatStream()` writes the session ID to `{workspaceBaseDir}/.claude-ttyd-sessions/7681`.
100
+ 3. Next message: `sessionId` param contains the saved ID. Adapter passes it to the SDK's resume mechanism.
101
+
102
+ If the SDK doesn't support session resume, the adapter can ignore `sessionId` — but multi-turn context will be lost between messages.
103
+
104
+ ## What the adapter does NOT handle
105
+
106
+ These are managed by `chatStream()` in `index.js` — adapters should not duplicate them:
107
+
108
+ - **Workspace git setup** — `ensureWorkspaceRepo()` clones/checkouts before the adapter is called
109
+ - **DB persistence** — `chatStream()` saves user messages, assistant text, and tool invocations
110
+ - **Chat creation** — `chatStream()` creates the chat and workspace DB records
111
+ - **Auto-titling** — `chatStream()` generates a title after the first message
112
+ - **System prompt loading** — `chatStream()` calls `buildCodingAgentSystemPrompt()` and passes the result as `systemPrompt`
113
+ - **Skill activation** — `ensureSkills()` runs before the adapter is called