circuschief 0.6.0 → 0.8.0
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.
- package/package.json +1 -1
- package/packages/server/src/agents/adapters/CodexAdapter.js +5 -4
- package/packages/server/src/api/canvas.js +22 -57
- package/packages/server/src/api/index.js +2 -0
- package/packages/server/src/api/kanban.js +4 -2
- package/packages/server/src/api/projects-helpers.js +20 -3
- package/packages/server/src/api/projects-session-helpers.js +35 -4
- package/packages/server/src/api/projects.js +11 -0
- package/packages/server/src/api/providers.js +11 -1
- package/packages/server/src/api/sessions-commands.js +35 -17
- package/packages/server/src/api/sessions-draft.js +1 -0
- package/packages/server/src/api/sessions-lifecycle.js +10 -10
- package/packages/server/src/api/sessions-patch.js +4 -0
- package/packages/server/src/api/sessions.js +6 -5
- package/packages/server/src/api/settings.js +52 -4
- package/packages/server/src/database.js +0 -2
- package/packages/server/src/db/ConversationRepository.js +16 -3
- package/packages/server/src/db/DatabaseManager.js +5 -1
- package/packages/server/src/db/ProjectDefaultsRepository.js +50 -40
- package/packages/server/src/db/ProviderRepository.js +87 -32
- package/packages/server/src/db/SessionRepository.js +13 -8
- package/packages/server/src/db/SettingsRepository.js +44 -16
- package/packages/server/src/db/conversation-helpers.js +1 -0
- package/packages/server/src/db/index.js +0 -3
- package/packages/server/src/db/migrations/index.js +36 -200
- package/packages/server/src/db/seedBaselineData.js +137 -0
- package/packages/server/src/db/session-helpers.js +9 -3
- package/packages/server/src/middleware/sessionLookup.js +81 -8
- package/packages/server/src/schema.sql +157 -132
- package/packages/server/src/scripts/backupDatabase.js +21 -0
- package/packages/server/src/scripts/dbUtils.js +81 -0
- package/packages/server/src/scripts/inspectDatabaseSchema.js +81 -0
- package/packages/server/src/scripts/validateDatabaseBaseline.js +212 -0
- package/packages/server/src/services/agentCallLogger.js +1 -1
- package/packages/server/src/services/codexSpawnHelper.js +9 -0
- package/packages/server/src/services/commandButtonPrompts.js +48 -0
- package/packages/server/src/services/commandRunner.js +7 -1
- package/packages/server/src/services/draftSessionService.js +2 -0
- package/packages/server/src/services/e2eSpawnCapture.js +147 -0
- package/packages/server/src/services/gitCommitAttribution.js +120 -0
- package/packages/server/src/services/gitService.js +11 -2
- package/packages/server/src/services/gitSessionSetup.js +11 -1
- package/packages/server/src/services/kanbanTriggers.js +6 -3
- package/packages/server/src/services/nodeSpawnHelper.js +9 -0
- package/packages/server/src/services/prUrlService.js +3 -3
- package/packages/server/src/services/queryParamBuilder.js +90 -0
- package/packages/server/src/services/sessionDuplicator.js +1 -5
- package/packages/server/src/services/sessionExecution.js +56 -106
- package/packages/server/src/services/sessionPrompts.js +16 -47
- package/packages/server/src/services/sessionProvider.js +16 -8
- package/packages/server/src/services/streamEventCallbacks.js +72 -40
- package/packages/server/src/services/streamEventHandler.js +13 -2
- package/packages/server/src/services/streamUsageHandler.js +6 -0
- package/packages/server/src/services/summaryClaudeClient.js +37 -12
- package/packages/server/src/services/summaryModelClient.js +154 -0
- package/packages/server/src/services/summaryModelResolver.js +148 -0
- package/packages/server/src/services/summaryService.js +11 -7
- package/packages/server/src/services/summaryStaleCheck.js +23 -4
- package/packages/server/src/services/templateTriggerService.js +3 -1
- package/packages/server/src/services/usageTracker.js +5 -1
- package/packages/server/src/services/visibleFinalErrorMessage.js +123 -0
- package/packages/shared/src/constants.js +4 -2
- package/packages/shared/src/contracts/commandButtons.js +16 -2
- package/packages/shared/src/contracts/projects.js +4 -2
- package/packages/shared/src/contracts/providers.js +60 -0
- package/packages/shared/src/contracts/sessions.js +2 -1
- package/packages/shared/src/contracts/templates.js +2 -2
- package/packages/shared/src/types.js +1 -9
- package/packages/shared/src/utils.js +11 -19
- package/packages/web/dist/assets/ActiveSessionsView-B0XHqLmv.js +1 -0
- package/packages/web/dist/assets/{AgentLogsView-Cdw4nmvd.js → AgentLogsView-DmsjUMlB.js} +2 -2
- package/packages/web/dist/assets/ApiClient-C3ztI9s9.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-BlCyn5Vt.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-DeoCVGXt.css +1 -0
- package/packages/web/dist/assets/{CommandButtonDetailView-DnFhJY5A.js → CommandButtonDetailView-CdSCPp78.js} +1 -1
- package/packages/web/dist/assets/EffortLevelSelector-hc2MNKg6.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-CQkmdczf.js → GeneralSettingsView-D1nI8_zk.js} +1 -1
- package/packages/web/dist/assets/InputWithButton-CAkttyqx.js +1 -0
- package/packages/web/dist/assets/{InputWithButton-cYdrEmTs.css → InputWithButton-D9HMvfR5.css} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-PfYR3KJo.js → InterpolationHelp-BO1j9Z3_.js} +1 -1
- package/packages/web/dist/assets/MarkdownEditor-ucRAP_UM.js +2 -0
- package/packages/web/dist/assets/{ModelSelector-BZOT1Jc6.css → ModelSelector-BSxKUSus.css} +1 -1
- package/packages/web/dist/assets/ModelSelector-CwTz8ZWO.js +1 -0
- package/packages/web/dist/assets/NewSessionView-BDPb-1qr.css +1 -0
- package/packages/web/dist/assets/NewSessionView-BsDrp8mj.js +3 -0
- package/packages/web/dist/assets/ProjectEditView-CwTOeSun.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-J15mcsWz.css +1 -0
- package/packages/web/dist/assets/{ProjectListView-CuYMmd3O.js → ProjectListView-DcNyuINs.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-CNaA4Maf.js → ProjectNewView-B5YV62hv.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +1 -0
- package/packages/web/dist/assets/ProvidersView-nY9GnDdO.js +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-B352c75l.css +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-BQwQXuL7.js +1 -0
- package/packages/web/dist/assets/{QuickResponsesPanel-DIBQFj0W.css → QuickResponsesPanel-BlFDvnZ2.css} +1 -1
- package/packages/web/dist/assets/{QuickResponsesPanel-BqMYSHb0.js → QuickResponsesPanel-BzSYcCSP.js} +1 -1
- package/packages/web/dist/assets/{ResizableTextarea-wYF3K2RO.js → ResizableTextarea-B3YIdIXv.js} +1 -1
- package/packages/web/dist/assets/{SessionCard-bLaQEWWX.js → SessionCard-CjE1tXiT.js} +1 -1
- package/packages/web/dist/assets/SessionDetailView-3cPZrbS3.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-CZRZMrfM.css +1 -0
- package/packages/web/dist/assets/SessionFormOptions-B6AxyREh.js +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BpUALRKn.css +1 -0
- package/packages/web/dist/assets/SessionListView-B5_6gW49.css +1 -0
- package/packages/web/dist/assets/SessionListView-CLXBfLcq.js +1 -0
- package/packages/web/dist/assets/{SessionLogStream-DTnDAF95.js → SessionLogStream-LlZ3z_Xj.js} +1 -1
- package/packages/web/dist/assets/{SettingsView-DNLUSsHV.js → SettingsView-CTGiGvR2.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-CRGFaO8t.js → SlashCommandWizard-Cy04d7-o.js} +1 -1
- package/packages/web/dist/assets/{SlashCommandWizard-Dn7sNaBd.css → SlashCommandWizard-DJzw3LP5.css} +1 -1
- package/packages/web/dist/assets/SummarySettingsView-BR2ZjEa3.js +1 -0
- package/packages/web/dist/assets/SummarySettingsView-l2bxHmZZ.css +1 -0
- package/packages/web/dist/assets/TemplateDetailView-DH6Oswsp.js +1 -0
- package/packages/web/dist/assets/{commandButtons-Bbjf3fCt.js → commandButtons-BfqR-fqq.js} +1 -1
- package/packages/web/dist/assets/index-1zziPL6l.js +1 -0
- package/packages/web/dist/assets/index-7kzHPxSF.js +1 -0
- package/packages/web/dist/assets/index-B0N_obMc.js +1 -0
- package/packages/web/dist/assets/index-BNk_gdfI.js +1 -0
- package/packages/web/dist/assets/{index-gmCCsCQ1.css → index-BY174HVJ.css} +1 -1
- package/packages/web/dist/assets/index-CSqaAH-0.js +1 -0
- package/packages/web/dist/assets/index-C_q4WlK8.js +1 -0
- package/packages/web/dist/assets/index-D1wpU4y0.js +7 -0
- package/packages/web/dist/assets/index-D5zCA8sD.js +1 -0
- package/packages/web/dist/assets/index-DGR8ELWY.js +1 -0
- package/packages/web/dist/assets/index-DHga8pXo.js +1 -0
- package/packages/web/dist/assets/index-DSby02Wl.js +1 -0
- package/packages/web/dist/assets/{index-Cf6vdW-B.js → index-DgkC10TW.js} +3 -3
- package/packages/web/dist/assets/index-DqjXJTVI.js +1 -0
- package/packages/web/dist/assets/{index-Bs7Qf5D6.js → index-DtfUt785.js} +2 -2
- package/packages/web/dist/assets/index-_4S2uLDI.js +1 -0
- package/packages/web/dist/assets/{index-BQL_L4gL.js → index-fK8FIZgP.js} +15 -14
- package/packages/web/dist/assets/index-gmiZeFXN.js +1 -0
- package/packages/web/dist/assets/index-irD539ZM.js +3 -0
- package/packages/web/dist/assets/index-yq-E1Y00.js +1 -0
- package/packages/web/dist/assets/{projects-CPt3AB7U.js → projects-DXYQNJIi.js} +1 -1
- package/packages/web/dist/assets/{providers-ChfeMvUq.js → providers-1bnH-exJ.js} +1 -1
- package/packages/web/dist/assets/sessions-6zGUlFrt.js +1 -0
- package/packages/web/dist/assets/settings-MbfRir0d.js +1 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/server/src/api/sessions-notes.js +0 -51
- package/packages/server/src/db/SessionNoteRepository.js +0 -60
- package/packages/server/src/db/migrations/canvasItemsMigrations.js +0 -109
- package/packages/server/src/db/migrations/conversationsMigrations.js +0 -183
- package/packages/server/src/db/migrations/kanbanMigrations.js +0 -99
- package/packages/server/src/db/migrations/miscMigrations.js +0 -369
- package/packages/server/src/db/migrations/projectsMigrations.js +0 -99
- package/packages/server/src/db/migrations/sessionsMigrations.js +0 -282
- package/packages/web/dist/assets/ActiveSessionsView-UCbQrF1b.js +0 -1
- package/packages/web/dist/assets/ApiClient-CWbXWDUY.js +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-BQ-4gI0R.css +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-J48eh3zw.js +0 -1
- package/packages/web/dist/assets/EffortLevelSelector-bXbPo4Zw.js +0 -1
- package/packages/web/dist/assets/InputWithButton-XyM3k6lN.js +0 -1
- package/packages/web/dist/assets/MarkdownEditor-P8F5kO-o.js +0 -2
- package/packages/web/dist/assets/ModelSelector-CowKfGMP.js +0 -1
- package/packages/web/dist/assets/NewSessionView-D_Hi7M9g.css +0 -1
- package/packages/web/dist/assets/NewSessionView-DkjFLvHU.js +0 -3
- package/packages/web/dist/assets/ProjectEditView-CpeKj-_w.css +0 -1
- package/packages/web/dist/assets/ProjectEditView-embVT7NC.js +0 -1
- package/packages/web/dist/assets/ProvidersView-C7rydtOd.js +0 -1
- package/packages/web/dist/assets/ProvidersView-DE82G_5W.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-BTQEKhwJ.js +0 -1
- package/packages/web/dist/assets/SessionDetailView-Cv-xMzXp.css +0 -1
- package/packages/web/dist/assets/SessionDetailView-CvQOUsW2.js +0 -36
- package/packages/web/dist/assets/SessionFormOptions-3pzbgI2Q.js +0 -1
- package/packages/web/dist/assets/SessionFormOptions-DhhIkjIS.css +0 -1
- package/packages/web/dist/assets/SessionListView-Dranfb72.js +0 -1
- package/packages/web/dist/assets/SessionListView-fHlQyecX.css +0 -1
- package/packages/web/dist/assets/SummarySettingsView-C7G_suHp.js +0 -1
- package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +0 -1
- package/packages/web/dist/assets/TemplateDetailView-B78_DLMR.js +0 -1
- package/packages/web/dist/assets/index--V7c-VZf.js +0 -1
- package/packages/web/dist/assets/index-8Q04yd7H.js +0 -1
- package/packages/web/dist/assets/index-B47XRBDH.js +0 -1
- package/packages/web/dist/assets/index-BXbgZrhS.js +0 -1
- package/packages/web/dist/assets/index-CGhDVPen.js +0 -1
- package/packages/web/dist/assets/index-CKcRO1A6.js +0 -1
- package/packages/web/dist/assets/index-CTq-SLIW.js +0 -1
- package/packages/web/dist/assets/index-CYyos3iC.js +0 -1
- package/packages/web/dist/assets/index-CsCREAxF.js +0 -1
- package/packages/web/dist/assets/index-DJTTk_8T.js +0 -3
- package/packages/web/dist/assets/index-DPqUJ5JK.js +0 -1
- package/packages/web/dist/assets/index-EwAe1dKg.js +0 -1
- package/packages/web/dist/assets/index-JBA8axyA.js +0 -1
- package/packages/web/dist/assets/index-JkVHFtK5.js +0 -7
- package/packages/web/dist/assets/index-gMPUwT55.js +0 -1
- package/packages/web/dist/assets/index-wadc_0zT.js +0 -1
- package/packages/web/dist/assets/sessions-CwPsJOb1.js +0 -1
- package/packages/web/dist/assets/settings-BOj6wq6t.js +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, copyFileSync, mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
|
+
import { getDefaultDbPath } from '../config.js';
|
|
5
|
+
import { DatabaseManager } from '../db/DatabaseManager.js';
|
|
6
|
+
|
|
7
|
+
export function getActiveDbPath() {
|
|
8
|
+
return process.env.DB_PATH || getDefaultDbPath();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getSqliteSidecarPaths(dbPath) {
|
|
12
|
+
return [`${dbPath}-wal`, `${dbPath}-shm`];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getBackupDir() {
|
|
16
|
+
return join(homedir(), '.circuschief', 'backups');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function copyDatabaseBackups(dbPath = getActiveDbPath(), timestamp = new Date()) {
|
|
20
|
+
if (!existsSync(dbPath)) {
|
|
21
|
+
return { dbPath, backupDir: getBackupDir(), copied: [], missing: [dbPath] };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const backupDir = getBackupDir();
|
|
25
|
+
mkdirSync(backupDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
const stamp = timestamp.toISOString().replace(/[:.]/g, '-');
|
|
28
|
+
const candidates = [dbPath, ...getSqliteSidecarPaths(dbPath)];
|
|
29
|
+
const copied = [];
|
|
30
|
+
const missing = [];
|
|
31
|
+
|
|
32
|
+
for (const source of candidates) {
|
|
33
|
+
if (!existsSync(source)) {
|
|
34
|
+
missing.push(source);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const target = join(backupDir, `${basename(source)}.${stamp}.bak`);
|
|
38
|
+
copyFileSync(source, target);
|
|
39
|
+
copied.push({ source, target });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { dbPath, backupDir, copied, missing };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createFreshBaselineDb() {
|
|
46
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'circuschief-baseline-'));
|
|
47
|
+
const dbPath = join(tempDir, 'baseline.db');
|
|
48
|
+
const manager = new DatabaseManager();
|
|
49
|
+
const db = manager.init(dbPath);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
db,
|
|
53
|
+
dbPath,
|
|
54
|
+
close() {
|
|
55
|
+
manager.close();
|
|
56
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getSchemaObjects(db) {
|
|
62
|
+
return db.prepare(`
|
|
63
|
+
SELECT type, name, tbl_name, sql
|
|
64
|
+
FROM sqlite_master
|
|
65
|
+
WHERE type IN ('table', 'index', 'trigger', 'view')
|
|
66
|
+
AND name NOT LIKE 'sqlite_%'
|
|
67
|
+
ORDER BY type, name
|
|
68
|
+
`).all();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getTableColumns(db, tableName) {
|
|
72
|
+
return db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getIndexColumns(db, indexName) {
|
|
76
|
+
return db.prepare(`PRAGMA index_info(${indexName})`).all().map((row) => row.name);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function normalizeSql(sql) {
|
|
80
|
+
return (sql || '').replace(/\s+/g, ' ').trim();
|
|
81
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import {
|
|
5
|
+
createFreshBaselineDb,
|
|
6
|
+
getActiveDbPath,
|
|
7
|
+
getSchemaObjects,
|
|
8
|
+
} from './dbUtils.js';
|
|
9
|
+
|
|
10
|
+
const BASELINE_TABLES = [
|
|
11
|
+
'sessions',
|
|
12
|
+
'projects',
|
|
13
|
+
'project_session_defaults',
|
|
14
|
+
'session_templates',
|
|
15
|
+
'canvas_items',
|
|
16
|
+
'providers',
|
|
17
|
+
'provider_models',
|
|
18
|
+
'kanban_lanes',
|
|
19
|
+
'agent_call_logs',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function printDatabase(db, label) {
|
|
23
|
+
console.log(`# ${label}`);
|
|
24
|
+
|
|
25
|
+
for (const row of getSchemaObjects(db)) {
|
|
26
|
+
console.log(`\n-- ${row.type} ${row.name} (${row.tbl_name})`);
|
|
27
|
+
console.log(row.sql || '');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const table of BASELINE_TABLES) {
|
|
31
|
+
const exists = db.prepare(
|
|
32
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?"
|
|
33
|
+
).get(table);
|
|
34
|
+
if (!exists) continue;
|
|
35
|
+
|
|
36
|
+
console.log(`\n-- PRAGMA table_info(${table})`);
|
|
37
|
+
console.table(db.prepare(`PRAGMA table_info(${table})`).all());
|
|
38
|
+
console.log(`-- PRAGMA foreign_key_list(${table})`);
|
|
39
|
+
console.table(db.prepare(`PRAGMA foreign_key_list(${table})`).all());
|
|
40
|
+
console.log(`-- PRAGMA index_list(${table})`);
|
|
41
|
+
const indexes = db.prepare(`PRAGMA index_list(${table})`).all();
|
|
42
|
+
console.table(indexes);
|
|
43
|
+
|
|
44
|
+
for (const index of indexes) {
|
|
45
|
+
console.log(`-- PRAGMA index_info(${index.name})`);
|
|
46
|
+
console.table(db.prepare(`PRAGMA index_info(${index.name})`).all());
|
|
47
|
+
console.log(`-- PRAGMA index_xinfo(${index.name})`);
|
|
48
|
+
console.table(db.prepare(`PRAGMA index_xinfo(${index.name})`).all());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function main(argv = process.argv.slice(2)) {
|
|
54
|
+
if (argv.includes('--fresh')) {
|
|
55
|
+
const fresh = createFreshBaselineDb();
|
|
56
|
+
try {
|
|
57
|
+
printDatabase(fresh.db, `fresh baseline ${fresh.dbPath}`);
|
|
58
|
+
} finally {
|
|
59
|
+
fresh.close();
|
|
60
|
+
}
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const dbPath = getActiveDbPath();
|
|
65
|
+
if (!existsSync(dbPath)) {
|
|
66
|
+
console.log(`No database found at ${dbPath}; use --fresh to inspect a fresh baseline.`);
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
71
|
+
try {
|
|
72
|
+
printDatabase(db, `active database ${dbPath}`);
|
|
73
|
+
} finally {
|
|
74
|
+
db.close();
|
|
75
|
+
}
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
80
|
+
process.exit(main());
|
|
81
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import {
|
|
5
|
+
createFreshBaselineDb,
|
|
6
|
+
getActiveDbPath,
|
|
7
|
+
getIndexColumns,
|
|
8
|
+
getSchemaObjects,
|
|
9
|
+
getTableColumns,
|
|
10
|
+
normalizeSql,
|
|
11
|
+
} from './dbUtils.js';
|
|
12
|
+
import {
|
|
13
|
+
BUILT_IN_ANTHROPIC_MODELS,
|
|
14
|
+
BUILT_IN_OPENAI_MODELS,
|
|
15
|
+
BUILT_IN_ANTHROPIC_PROVIDER,
|
|
16
|
+
BUILT_IN_OPENAI_PROVIDER,
|
|
17
|
+
} from '../db/seedBaselineData.js';
|
|
18
|
+
|
|
19
|
+
export const DRIFT_SENSITIVE_TABLES = [
|
|
20
|
+
'sessions',
|
|
21
|
+
'project_session_defaults',
|
|
22
|
+
'session_templates',
|
|
23
|
+
'canvas_items',
|
|
24
|
+
'providers',
|
|
25
|
+
'provider_models',
|
|
26
|
+
'agent_call_logs',
|
|
27
|
+
'kanban_lanes',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const REQUIRED_INDEXES = {
|
|
31
|
+
idx_sessions_project: ['project_id'],
|
|
32
|
+
idx_sessions_status: ['status'],
|
|
33
|
+
idx_sessions_archived: ['archived'],
|
|
34
|
+
idx_sessions_starred: ['archived', 'starred'],
|
|
35
|
+
idx_sessions_next_template: ['next_template_id'],
|
|
36
|
+
idx_sessions_parent: ['parent_session_id'],
|
|
37
|
+
idx_sessions_scheduled: ['scheduled_at'],
|
|
38
|
+
idx_messages_conversation: ['conversation_id'],
|
|
39
|
+
idx_canvas_deleted: ['deleted_at'],
|
|
40
|
+
idx_todos_conversation: ['conversation_id'],
|
|
41
|
+
idx_project_defaults_projectId: ['project_id'],
|
|
42
|
+
idx_conversations_parent: ['parent_conversation_id'],
|
|
43
|
+
idx_agent_call_logs_agent_type: ['agent_type'],
|
|
44
|
+
idx_agent_call_logs_call_type: ['call_type'],
|
|
45
|
+
idx_agent_call_logs_status: ['status'],
|
|
46
|
+
idx_agent_call_logs_model: ['model'],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function byName(rows) {
|
|
50
|
+
return new Map(rows.map((row) => [row.name, row]));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function compareTables(actualDb, expectedDb, errors) {
|
|
54
|
+
const actualObjects = byName(getSchemaObjects(actualDb).filter((row) => row.type === 'table'));
|
|
55
|
+
const expectedObjects = getSchemaObjects(expectedDb).filter((row) => row.type === 'table');
|
|
56
|
+
|
|
57
|
+
for (const expected of expectedObjects) {
|
|
58
|
+
const actual = actualObjects.get(expected.name);
|
|
59
|
+
if (!actual) {
|
|
60
|
+
errors.push(`Missing table: ${expected.name}`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const actualColumns = new Map(
|
|
65
|
+
getTableColumns(actualDb, expected.name).map((col) => [col.name, col])
|
|
66
|
+
);
|
|
67
|
+
const expectedColumns = getTableColumns(expectedDb, expected.name);
|
|
68
|
+
|
|
69
|
+
for (const expectedColumn of expectedColumns) {
|
|
70
|
+
const actualColumn = actualColumns.get(expectedColumn.name);
|
|
71
|
+
if (!actualColumn) {
|
|
72
|
+
errors.push(`Missing column: ${expected.name}.${expectedColumn.name}`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const actualSummary = {
|
|
77
|
+
type: actualColumn.type,
|
|
78
|
+
notnull: actualColumn.notnull,
|
|
79
|
+
dflt_value: actualColumn.dflt_value,
|
|
80
|
+
pk: actualColumn.pk,
|
|
81
|
+
};
|
|
82
|
+
const expectedSummary = {
|
|
83
|
+
type: expectedColumn.type,
|
|
84
|
+
notnull: expectedColumn.notnull,
|
|
85
|
+
dflt_value: expectedColumn.dflt_value,
|
|
86
|
+
pk: expectedColumn.pk,
|
|
87
|
+
};
|
|
88
|
+
if (JSON.stringify(actualSummary) !== JSON.stringify(expectedSummary)) {
|
|
89
|
+
errors.push(`Column mismatch for ${expected.name}.${expectedColumn.name}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function compareIndexes(actualDb, expectedDb, errors) {
|
|
96
|
+
const actualObjects = byName(getSchemaObjects(actualDb).filter((row) => row.type === 'index'));
|
|
97
|
+
const expectedObjects = byName(getSchemaObjects(expectedDb).filter((row) => row.type === 'index'));
|
|
98
|
+
|
|
99
|
+
for (const [name, expectedColumns] of Object.entries(REQUIRED_INDEXES)) {
|
|
100
|
+
const actual = actualObjects.get(name);
|
|
101
|
+
if (!actual) {
|
|
102
|
+
errors.push(`Missing index: ${name}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const actualColumns = getIndexColumns(actualDb, name);
|
|
106
|
+
if (JSON.stringify(actualColumns) !== JSON.stringify(expectedColumns)) {
|
|
107
|
+
errors.push(`Index column mismatch for ${name}: ${actualColumns.join(',')} !== ${expectedColumns.join(',')}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const expectedScheduledSql = normalizeSql(expectedObjects.get('idx_sessions_scheduled')?.sql);
|
|
112
|
+
const actualScheduledSql = normalizeSql(actualObjects.get('idx_sessions_scheduled')?.sql);
|
|
113
|
+
if (expectedScheduledSql !== actualScheduledSql) {
|
|
114
|
+
errors.push('Partial index SQL mismatch for idx_sessions_scheduled');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validateProviders(actualDb, errors) {
|
|
119
|
+
for (const provider of [BUILT_IN_ANTHROPIC_PROVIDER, BUILT_IN_OPENAI_PROVIDER]) {
|
|
120
|
+
const row = actualDb.prepare(
|
|
121
|
+
'SELECT id, name, kind, base_url, auth_token, commit_attribution_override FROM providers WHERE id = ?'
|
|
122
|
+
).get(provider.id);
|
|
123
|
+
if (!row) {
|
|
124
|
+
errors.push(`Missing provider seed row: ${provider.id}`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (row.name !== provider.name || row.kind !== provider.kind) {
|
|
128
|
+
errors.push(`Provider seed mismatch: ${provider.id}`);
|
|
129
|
+
}
|
|
130
|
+
if (row.base_url !== null || row.auth_token !== null) {
|
|
131
|
+
errors.push(`Provider nullable seed columns mismatch: ${provider.id}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function validateProviderModels(actualDb, errors) {
|
|
137
|
+
for (const model of [...BUILT_IN_ANTHROPIC_MODELS, ...BUILT_IN_OPENAI_MODELS]) {
|
|
138
|
+
const row = actualDb.prepare(
|
|
139
|
+
'SELECT provider_id, model_id, display_name, description, tier FROM provider_models WHERE id = ?'
|
|
140
|
+
).get(model.id);
|
|
141
|
+
if (!row) {
|
|
142
|
+
errors.push(`Missing provider model seed row: ${model.id}`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const expected = {
|
|
146
|
+
provider_id: model.providerId,
|
|
147
|
+
model_id: model.modelId,
|
|
148
|
+
display_name: model.displayName,
|
|
149
|
+
description: model.description,
|
|
150
|
+
tier: model.tier,
|
|
151
|
+
};
|
|
152
|
+
if (JSON.stringify(row) !== JSON.stringify(expected)) {
|
|
153
|
+
errors.push(`Provider model seed mismatch: ${model.id}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function compareSeedRows(actualDb, errors) {
|
|
159
|
+
const tables = new Set(actualDb.prepare(
|
|
160
|
+
"SELECT name FROM sqlite_master WHERE type = 'table'"
|
|
161
|
+
).all().map((row) => row.name));
|
|
162
|
+
if (!tables.has('providers') || !tables.has('provider_models') || !tables.has('quick_responses') || !tables.has('session_templates')) {
|
|
163
|
+
errors.push('Required seed tables are missing');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
validateProviders(actualDb, errors);
|
|
167
|
+
validateProviderModels(actualDb, errors);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function validateDatabaseBaseline(actualDb) {
|
|
171
|
+
const fresh = createFreshBaselineDb();
|
|
172
|
+
const errors = [];
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
compareTables(actualDb, fresh.db, errors);
|
|
176
|
+
compareIndexes(actualDb, fresh.db, errors);
|
|
177
|
+
compareSeedRows(actualDb, errors);
|
|
178
|
+
} finally {
|
|
179
|
+
fresh.close();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return errors;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function main() {
|
|
186
|
+
const dbPath = getActiveDbPath();
|
|
187
|
+
if (!existsSync(dbPath)) {
|
|
188
|
+
console.log(`No database found at ${dbPath}; nothing to validate.`);
|
|
189
|
+
return 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
193
|
+
try {
|
|
194
|
+
const errors = validateDatabaseBaseline(db);
|
|
195
|
+
if (errors.length === 0) {
|
|
196
|
+
console.log(`Database baseline validation passed: ${dbPath}`);
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.error(`Database baseline validation failed: ${dbPath}`);
|
|
201
|
+
for (const error of errors) {
|
|
202
|
+
console.error(`- ${error}`);
|
|
203
|
+
}
|
|
204
|
+
return 1;
|
|
205
|
+
} finally {
|
|
206
|
+
db.close();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
211
|
+
process.exit(main());
|
|
212
|
+
}
|
|
@@ -20,7 +20,7 @@ export class AgentCallLogger {
|
|
|
20
20
|
const callId = nanoid();
|
|
21
21
|
|
|
22
22
|
// Build metadata object - only include keys with defined values
|
|
23
|
-
const metadata = {};
|
|
23
|
+
const metadata = { ...(meta.metadata || {}) };
|
|
24
24
|
if (meta.effortLevel !== undefined && meta.effortLevel !== null) {
|
|
25
25
|
metadata.effortLevel = meta.effortLevel;
|
|
26
26
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { createRobustEnv } from './nodeSpawnHelper.js';
|
|
3
|
+
import {
|
|
4
|
+
captureSpawnAttempt,
|
|
5
|
+
createCapturedSpawnProcess,
|
|
6
|
+
isE2ESpawnCaptureEnabled,
|
|
7
|
+
} from './e2eSpawnCapture.js';
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* Create a custom spawn function for the Codex CLI.
|
|
@@ -19,6 +24,10 @@ import { createRobustEnv } from './nodeSpawnHelper.js';
|
|
|
19
24
|
export function createCodexSpawner() {
|
|
20
25
|
return (options) => {
|
|
21
26
|
const { command, args, cwd, env, signal } = options;
|
|
27
|
+
if (isE2ESpawnCaptureEnabled()) {
|
|
28
|
+
captureSpawnAttempt('codex', options);
|
|
29
|
+
return createCapturedSpawnProcess('codex');
|
|
30
|
+
}
|
|
22
31
|
|
|
23
32
|
// Replace 'node' with the absolute path to the current Node executable
|
|
24
33
|
const actualCommand = command === 'node' ? process.execPath : command;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { commandButtons } from '../database.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build Command API instructions for system prompt if the project has commands.
|
|
5
|
+
* @param {string} apiUrl - Base API URL
|
|
6
|
+
* @param {string} sessionId - Current session ID
|
|
7
|
+
* @param {string} projectId - Current project ID
|
|
8
|
+
* @returns {string} Command instructions or empty string if no commands configured
|
|
9
|
+
*/
|
|
10
|
+
export function buildCommandButtonApiInstructions(apiUrl, sessionId, projectId) {
|
|
11
|
+
const buttons = commandButtons.getByProjectId(projectId);
|
|
12
|
+
if (!buttons || buttons.length === 0) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return `## Commands API
|
|
17
|
+
|
|
18
|
+
This project has commands configured - reusable shell commands you can execute. Use the Bash tool to run these curl commands.
|
|
19
|
+
|
|
20
|
+
### List Available Commands
|
|
21
|
+
\`\`\`bash
|
|
22
|
+
curl ${apiUrl}/api/sessions/${sessionId}/command-buttons
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
### Run a Command
|
|
26
|
+
\`\`\`bash
|
|
27
|
+
curl -X POST ${apiUrl}/api/sessions/${sessionId}/command-buttons/<button_id>/run
|
|
28
|
+
\`\`\`
|
|
29
|
+
|
|
30
|
+
Response: { runId, buttonId, status: "running", output: "" }
|
|
31
|
+
|
|
32
|
+
### Check Run Status & Output
|
|
33
|
+
\`\`\`bash
|
|
34
|
+
curl ${apiUrl}/api/sessions/${sessionId}/command-buttons/runs/<run_id>
|
|
35
|
+
\`\`\`
|
|
36
|
+
|
|
37
|
+
Response: { runId, buttonId, status, exitCode, output, startedAt, completedAt }
|
|
38
|
+
|
|
39
|
+
### List Command Runs
|
|
40
|
+
\`\`\`bash
|
|
41
|
+
curl ${apiUrl}/api/sessions/${sessionId}/command-buttons/runs
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
### Kill a Running Command
|
|
45
|
+
\`\`\`bash
|
|
46
|
+
curl -X POST ${apiUrl}/api/sessions/${sessionId}/command-buttons/runs/<run_id>/kill
|
|
47
|
+
\`\`\``;
|
|
48
|
+
}
|
|
@@ -7,6 +7,12 @@ import { TerminalOutputProcessor } from './terminalOutput.js';
|
|
|
7
7
|
// Re-export for backward compatibility
|
|
8
8
|
export { stripAnsiCodes, TerminalOutputProcessor } from './terminalOutput.js';
|
|
9
9
|
|
|
10
|
+
export function createCommandRunnerEnv(baseEnv = process.env) {
|
|
11
|
+
const env = createRobustEnv(baseEnv);
|
|
12
|
+
delete env.CIRCUSCHIEF_COMMIT_ATTRIBUTION;
|
|
13
|
+
return env;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
/**
|
|
11
17
|
* Service for running commands and managing their execution
|
|
12
18
|
*/
|
|
@@ -141,7 +147,7 @@ export class CommandRunner {
|
|
|
141
147
|
cwd: workingDirectory,
|
|
142
148
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
143
149
|
detached: true,
|
|
144
|
-
env:
|
|
150
|
+
env: createCommandRunnerEnv(),
|
|
145
151
|
});
|
|
146
152
|
|
|
147
153
|
const entry = this.#createProcessEntry(child, sessionId, buttonId);
|
|
@@ -112,6 +112,7 @@ function getOrCreateInitialMessage(session, options) {
|
|
|
112
112
|
* @param {object} options
|
|
113
113
|
* @param {string} [options.prompt] - Optional new prompt to use/override
|
|
114
114
|
* @param {string} [options.model] - Optional model override
|
|
115
|
+
* @param {string|null} [options.providerId] - Optional provider override
|
|
115
116
|
* @returns {Promise<object>} The updated session
|
|
116
117
|
*/
|
|
117
118
|
export async function startDraft(session, options = {}) {
|
|
@@ -144,6 +145,7 @@ export async function startDraft(session, options = {}) {
|
|
|
144
145
|
status: 'starting',
|
|
145
146
|
pendingModel: null,
|
|
146
147
|
...(model ? { model, agentType } : {}),
|
|
148
|
+
...(options.providerId !== undefined ? { providerId: options.providerId } : {}),
|
|
147
149
|
});
|
|
148
150
|
|
|
149
151
|
// Resolve skill/command invocations so skill body goes into system prompt
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { appendFileSync } from 'fs';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { PassThrough } from 'stream';
|
|
4
|
+
|
|
5
|
+
export function isE2ESpawnCaptureEnabled() {
|
|
6
|
+
return Boolean(process.env.E2E_AGENT_SPAWN_CAPTURE_FILE);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function captureSpawnAttempt(agentType, spawnOptions) {
|
|
10
|
+
const filePath = process.env.E2E_AGENT_SPAWN_CAPTURE_FILE;
|
|
11
|
+
if (!filePath) return;
|
|
12
|
+
|
|
13
|
+
const record = {
|
|
14
|
+
agentType,
|
|
15
|
+
command: spawnOptions.command,
|
|
16
|
+
args: spawnOptions.args || [],
|
|
17
|
+
cwd: spawnOptions.cwd || null,
|
|
18
|
+
env: summarizeSpawnEnv(spawnOptions.env),
|
|
19
|
+
options: summarizeSpawnOptions(agentType, spawnOptions),
|
|
20
|
+
capturedAt: new Date().toISOString(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
appendFileSync(filePath, `${JSON.stringify(record)}\n`, 'utf8');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createCapturedSpawnProcess(agentType) {
|
|
27
|
+
const processStub = new EventEmitter();
|
|
28
|
+
const stdin = new PassThrough();
|
|
29
|
+
const stdout = new PassThrough();
|
|
30
|
+
const stderr = new PassThrough();
|
|
31
|
+
|
|
32
|
+
processStub.stdin = stdin;
|
|
33
|
+
processStub.stdout = stdout;
|
|
34
|
+
processStub.stderr = stderr;
|
|
35
|
+
processStub.killed = false;
|
|
36
|
+
processStub.exitCode = null;
|
|
37
|
+
processStub.kill = (signal = 'SIGTERM') => {
|
|
38
|
+
if (processStub.killed || processStub.exitCode !== null) return true;
|
|
39
|
+
processStub.killed = true;
|
|
40
|
+
finishProcess({ processStub, stdout, stderr, code: null, signal });
|
|
41
|
+
return true;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const complete = () => {
|
|
45
|
+
setImmediate(() => {
|
|
46
|
+
if (processStub.killed || processStub.exitCode !== null) return;
|
|
47
|
+
writeCapturedAgentEvents(agentType, stdout);
|
|
48
|
+
finishProcess({ processStub, stdout, stderr, code: 0, signal: null });
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (agentType === 'claude-code') {
|
|
53
|
+
setTimeout(complete, 10);
|
|
54
|
+
} else {
|
|
55
|
+
stdin.once('finish', complete);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return processStub;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function summarizeSpawnEnv(env = {}) {
|
|
62
|
+
if (!Object.prototype.hasOwnProperty.call(env, 'CIRCUSCHIEF_COMMIT_ATTRIBUTION')) {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
CIRCUSCHIEF_COMMIT_ATTRIBUTION: env.CIRCUSCHIEF_COMMIT_ATTRIBUTION,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function summarizeSpawnOptions(agentType, spawnOptions) {
|
|
71
|
+
const args = spawnOptions.args || [];
|
|
72
|
+
if (agentType === 'claude-code') {
|
|
73
|
+
return {
|
|
74
|
+
model: valueAfter(args, '--model'),
|
|
75
|
+
settings: valueAfter(args, '--settings'),
|
|
76
|
+
permissionMode: valueAfter(args, '--permission-mode'),
|
|
77
|
+
settingSources: valueAfter(args, '--setting-sources'),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
model: valueAfter(args, '-m'),
|
|
83
|
+
sandbox: valueAfter(args, '--sandbox'),
|
|
84
|
+
config: valuesAfter(args, '-c'),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function valueAfter(args, flag) {
|
|
89
|
+
const index = args.indexOf(flag);
|
|
90
|
+
if (index === -1) return null;
|
|
91
|
+
return args[index + 1] ?? null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function valuesAfter(args, flag) {
|
|
95
|
+
const values = [];
|
|
96
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
97
|
+
if (args[index] === flag && args[index + 1] !== undefined) {
|
|
98
|
+
values.push(args[index + 1]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return values;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function writeCapturedAgentEvents(agentType, stdout) {
|
|
105
|
+
if (agentType === 'codex') {
|
|
106
|
+
writeJsonLine(stdout, { type: 'thread.started', thread_id: `e2e-codex-${Date.now()}` });
|
|
107
|
+
writeJsonLine(stdout, { type: 'turn.started' });
|
|
108
|
+
writeJsonLine(stdout, {
|
|
109
|
+
type: 'item.completed',
|
|
110
|
+
item: { type: 'agent_message', text: 'E2E spawn capture response.' },
|
|
111
|
+
});
|
|
112
|
+
writeJsonLine(stdout, {
|
|
113
|
+
type: 'turn.completed',
|
|
114
|
+
usage: { input_tokens: 0, cached_input_tokens: 0, output_tokens: 0 },
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
writeJsonLine(stdout, {
|
|
120
|
+
type: 'system',
|
|
121
|
+
subtype: 'init',
|
|
122
|
+
session_id: `e2e-claude-${Date.now()}`,
|
|
123
|
+
});
|
|
124
|
+
writeJsonLine(stdout, {
|
|
125
|
+
type: 'assistant',
|
|
126
|
+
message: { content: [{ type: 'text', text: 'E2E spawn capture response.' }] },
|
|
127
|
+
});
|
|
128
|
+
writeJsonLine(stdout, {
|
|
129
|
+
type: 'result',
|
|
130
|
+
subtype: 'success',
|
|
131
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function writeJsonLine(stream, value) {
|
|
136
|
+
stream.write(`${JSON.stringify(value)}\n`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function finishProcess({ processStub, stdout, stderr, code, signal }) {
|
|
140
|
+
Object.assign(processStub, { exitCode: code });
|
|
141
|
+
stdout.end();
|
|
142
|
+
stderr.end();
|
|
143
|
+
setImmediate(() => {
|
|
144
|
+
processStub.emit('exit', code, signal);
|
|
145
|
+
processStub.emit('close', code, signal);
|
|
146
|
+
});
|
|
147
|
+
}
|