circuschief 0.8.0 → 1.1.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/AgentGateway.js +2 -0
- package/packages/server/src/agents/adapters/GeminiAdapter.js +105 -0
- package/packages/server/src/agents/adapters/cliUtils.js +15 -0
- package/packages/server/src/agents/adapters/codexCliRunner.js +1 -8
- package/packages/server/src/agents/adapters/geminiCliRunner.js +183 -0
- package/packages/server/src/agents/adapters/geminiEventMapper.js +195 -0
- package/packages/server/src/api/commandButtons.js +16 -15
- package/packages/server/src/api/projects-commandButtons.js +6 -6
- package/packages/server/src/api/projects-session-create.js +109 -0
- package/packages/server/src/api/projects-session-defaults.js +51 -0
- package/packages/server/src/api/projects-session-helpers.js +47 -1
- package/packages/server/src/api/projects-templates.js +38 -0
- package/packages/server/src/api/projects.js +28 -180
- package/packages/server/src/api/sessions-commands.js +21 -18
- package/packages/server/src/api/sessions-patch.js +41 -1
- package/packages/server/src/db/ProviderRepository.js +4 -2
- package/packages/server/src/db/SessionRepository.js +1 -1
- package/packages/server/src/db/SessionTemplateRepository.js +23 -2
- package/packages/server/src/db/migrations/canvasItemsMigrations.js +109 -0
- package/packages/server/src/db/migrations/conversationsMigrations.js +187 -0
- package/packages/server/src/db/migrations/index.js +234 -6
- package/packages/server/src/db/migrations/kanbanMigrations.js +99 -0
- package/packages/server/src/db/migrations/miscMigrations.js +244 -0
- package/packages/server/src/db/migrations/projectsMigrations.js +130 -0
- package/packages/server/src/db/migrations/providerCommitAttributionMigrations.js +30 -0
- package/packages/server/src/db/migrations/providerMigrations.js +250 -0
- package/packages/server/src/db/migrations/sessionTableRecreate.js +136 -0
- package/packages/server/src/db/migrations/sessionsMigrations.js +300 -0
- package/packages/server/src/db/seedBaselineData.js +23 -1
- package/packages/server/src/db/session-helpers.js +26 -1
- package/packages/server/src/schema.sql +5 -1
- package/packages/server/src/services/commandButtonPrompts.js +9 -7
- package/packages/server/src/services/e2eSpawnCapture.js +47 -6
- package/packages/server/src/services/geminiSpawnHelper.js +47 -0
- package/packages/server/src/services/gitCommitAttribution.js +38 -8
- package/packages/server/src/services/gitDiff.js +107 -0
- package/packages/server/src/services/gitRepoUrl.js +174 -0
- package/packages/server/src/services/gitService.js +43 -311
- package/packages/server/src/services/gitWorktree.js +127 -0
- package/packages/server/src/services/providerTestService.js +59 -1
- package/packages/server/src/services/queryParamBuilder.js +33 -1
- package/packages/server/src/services/sessionExecution.js +4 -0
- package/packages/server/src/services/sessionPrompts.js +23 -1
- package/packages/server/src/services/sessionProvider.js +41 -1
- package/packages/shared/src/constants.js +1 -1
- package/packages/shared/src/contracts/providers.js +1 -1
- package/packages/shared/src/contracts/sessions.js +27 -1
- package/packages/shared/src/contracts/templates.js +10 -0
- package/packages/shared/src/types.js +7 -0
- package/packages/web/dist/assets/{ActiveSessionsView-B0XHqLmv.js → ActiveSessionsView-EdNxmPmZ.js} +1 -1
- package/packages/web/dist/assets/{AgentLogsView-DmsjUMlB.js → AgentLogsView-C2wX0JPP.js} +2 -2
- package/packages/web/dist/assets/ApiClient-DfbJwzpz.js +1 -0
- package/packages/web/dist/assets/ArchiveConfirmModal-DJERn5XO.js +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-CBPI8-US.js +1 -0
- package/packages/web/dist/assets/CommandButtonDetailView-D9zjx9ME.css +1 -0
- package/packages/web/dist/assets/EffortLevelSelector-PaBpUveC.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-D1nI8_zk.js → GeneralSettingsView-Dw-x83R0.js} +1 -1
- package/packages/web/dist/assets/{InputWithButton-CAkttyqx.js → InputWithButton-CHHcpF4I.js} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-BO1j9Z3_.js → InterpolationHelp-CLNPz8s8.js} +1 -1
- package/packages/web/dist/assets/MarkdownEditor-DYi1igfT.js +2 -0
- package/packages/web/dist/assets/ModelSelector-Cko_yTO5.js +1 -0
- package/packages/web/dist/assets/{ModelSelector-BSxKUSus.css → ModelSelector-Dtwe5xLH.css} +1 -1
- package/packages/web/dist/assets/{NewSessionView-BDPb-1qr.css → NewSessionView-DBl7T2Xp.css} +1 -1
- package/packages/web/dist/assets/NewSessionView-DwUfBg70.js +3 -0
- package/packages/web/dist/assets/ProjectEditView-CSbsea3U.js +1 -0
- package/packages/web/dist/assets/ProjectEditView-DbqTbA0q.css +1 -0
- package/packages/web/dist/assets/{ProjectListView-DcNyuINs.js → ProjectListView-CEc_LWZL.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-B5YV62hv.js → ProjectNewView-D4U0uRlp.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-2KCOiY6Q.css +1 -0
- package/packages/web/dist/assets/ProvidersView-CD1j8BOv.js +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-Dp39f12o.js +1 -0
- package/packages/web/dist/assets/QuickResponsesPanel-dk-Rj8xx.css +1 -0
- package/packages/web/dist/assets/ResizableTextarea-BWywIqOv.js +1 -0
- package/packages/web/dist/assets/ResizableTextarea-DERSH3Wz.css +1 -0
- package/packages/web/dist/assets/SessionCard-B6d5ijDW.js +1 -0
- package/packages/web/dist/assets/SessionDetailView-DWbXdx7A.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-ULeIkWS0.css +1 -0
- package/packages/web/dist/assets/{SessionFormOptions-B6AxyREh.js → SessionFormOptions-Dz9ik4Fo.js} +1 -1
- package/packages/web/dist/assets/{SessionListView-B5_6gW49.css → SessionListView-3-xx6EVs.css} +1 -1
- package/packages/web/dist/assets/SessionListView-C129buBe.js +1 -0
- package/packages/web/dist/assets/{SessionLogStream-LlZ3z_Xj.js → SessionLogStream-BvXUNNBZ.js} +6 -6
- package/packages/web/dist/assets/{SettingsView-CTGiGvR2.js → SettingsView-DW1NvpX_.js} +1 -1
- package/packages/web/dist/assets/SlashCommandWizard-DleYBxrE.js +1 -0
- package/packages/web/dist/assets/{SummarySettingsView-BR2ZjEa3.js → SummarySettingsView-CLUfcWvf.js} +1 -1
- package/packages/web/dist/assets/TemplateDetailView-B5NI2oTR.css +1 -0
- package/packages/web/dist/assets/TemplateDetailView-Cukb205e.js +1 -0
- package/packages/web/dist/assets/{commandButtons-BfqR-fqq.js → commandButtons-DejH0rVN.js} +1 -1
- package/packages/web/dist/assets/index-BD7Y3rBE.js +3 -0
- package/packages/web/dist/assets/{index-BY174HVJ.css → index-Bd20AzX1.css} +1 -1
- package/packages/web/dist/assets/index-BgJiarKe.js +1 -0
- package/packages/web/dist/assets/index-Bk32fSSG.js +1 -0
- package/packages/web/dist/assets/index-BkA6pF2Z.js +1 -0
- package/packages/web/dist/assets/index-Cltr-Ldt.js +7 -0
- package/packages/web/dist/assets/index-Co-46Tp3.js +1 -0
- package/packages/web/dist/assets/index-Cpykk857.js +1 -0
- package/packages/web/dist/assets/index-CtABl0D1.js +1 -0
- package/packages/web/dist/assets/index-Cuqk5m9S.js +1 -0
- package/packages/web/dist/assets/{index-fK8FIZgP.js → index-CvXApbVC.js} +15 -15
- package/packages/web/dist/assets/index-D2gN-xEH.js +1 -0
- package/packages/web/dist/assets/index-Dd3WpmyQ.js +1 -0
- package/packages/web/dist/assets/index-Dk6--9rj.js +1 -0
- package/packages/web/dist/assets/{index-DgkC10TW.js → index-MZf7MlPX.js} +3 -3
- package/packages/web/dist/assets/{index-DtfUt785.js → index-NShCcwfj.js} +1 -1
- package/packages/web/dist/assets/index-hA3VEuSq.js +1 -0
- package/packages/web/dist/assets/index-p0mp3nca.js +1 -0
- package/packages/web/dist/assets/index-qntNa5r_.js +1 -0
- package/packages/web/dist/assets/index-qq9ceNSK.js +1 -0
- package/packages/web/dist/assets/projectDefaults-D9xkp2XR.js +1 -0
- package/packages/web/dist/assets/{projects-DXYQNJIi.js → projects-BvLADGKx.js} +1 -1
- package/packages/web/dist/assets/{providers-1bnH-exJ.js → providers-DZ-fOa4G.js} +1 -1
- package/packages/web/dist/assets/{sessions-6zGUlFrt.js → sessions-DETEyjPI.js} +1 -1
- package/packages/web/dist/assets/{settings-MbfRir0d.js → settings-TWfbahn5.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/ApiClient-C3ztI9s9.js +0 -1
- package/packages/web/dist/assets/ArchiveConfirmModal-BlCyn5Vt.js +0 -1
- package/packages/web/dist/assets/CommandButtonDetailView-CdSCPp78.js +0 -1
- package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +0 -1
- package/packages/web/dist/assets/EffortLevelSelector-hc2MNKg6.js +0 -1
- package/packages/web/dist/assets/MarkdownEditor-ucRAP_UM.js +0 -2
- package/packages/web/dist/assets/ModelSelector-CwTz8ZWO.js +0 -1
- package/packages/web/dist/assets/NewSessionView-BsDrp8mj.js +0 -3
- package/packages/web/dist/assets/ProjectEditView-CwTOeSun.js +0 -1
- package/packages/web/dist/assets/ProjectEditView-J15mcsWz.css +0 -1
- package/packages/web/dist/assets/ProvidersView-bZemq_Rv.css +0 -1
- package/packages/web/dist/assets/ProvidersView-nY9GnDdO.js +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-B352c75l.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-BQwQXuL7.js +0 -1
- package/packages/web/dist/assets/QuickResponsesPanel-BlFDvnZ2.css +0 -1
- package/packages/web/dist/assets/QuickResponsesPanel-BzSYcCSP.js +0 -1
- package/packages/web/dist/assets/ResizableTextarea-B3YIdIXv.js +0 -1
- package/packages/web/dist/assets/ResizableTextarea-DsU3TVwF.css +0 -1
- package/packages/web/dist/assets/SessionCard-CjE1tXiT.js +0 -1
- package/packages/web/dist/assets/SessionDetailView-3cPZrbS3.js +0 -36
- package/packages/web/dist/assets/SessionDetailView-CZRZMrfM.css +0 -1
- package/packages/web/dist/assets/SessionListView-CLXBfLcq.js +0 -1
- package/packages/web/dist/assets/SlashCommandWizard-Cy04d7-o.js +0 -1
- package/packages/web/dist/assets/TemplateDetailView-DH6Oswsp.js +0 -1
- package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +0 -1
- package/packages/web/dist/assets/index-1zziPL6l.js +0 -1
- package/packages/web/dist/assets/index-7kzHPxSF.js +0 -1
- package/packages/web/dist/assets/index-B0N_obMc.js +0 -1
- package/packages/web/dist/assets/index-BNk_gdfI.js +0 -1
- package/packages/web/dist/assets/index-CSqaAH-0.js +0 -1
- package/packages/web/dist/assets/index-C_q4WlK8.js +0 -1
- package/packages/web/dist/assets/index-D1wpU4y0.js +0 -7
- package/packages/web/dist/assets/index-D5zCA8sD.js +0 -1
- package/packages/web/dist/assets/index-DGR8ELWY.js +0 -1
- package/packages/web/dist/assets/index-DHga8pXo.js +0 -1
- package/packages/web/dist/assets/index-DSby02Wl.js +0 -1
- package/packages/web/dist/assets/index-DqjXJTVI.js +0 -1
- package/packages/web/dist/assets/index-_4S2uLDI.js +0 -1
- package/packages/web/dist/assets/index-gmiZeFXN.js +0 -1
- package/packages/web/dist/assets/index-irD539ZM.js +0 -3
- package/packages/web/dist/assets/index-yq-E1Y00.js +0 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrations for the sessions table and closely related session tables.
|
|
3
|
+
* Each export is an array of { name, up(db) } migration objects.
|
|
4
|
+
*/
|
|
5
|
+
import { addColumnIfMissing, getColumns, getTableSql } from './migrationUtils.js';
|
|
6
|
+
import { migrateSessionsDefaultModeAndThinking } from './sessionTableRecreate.js';
|
|
7
|
+
|
|
8
|
+
// Table name constants for migrations
|
|
9
|
+
const TABLE_SESSIONS = 'sessions';
|
|
10
|
+
|
|
11
|
+
// Column type constants
|
|
12
|
+
const COL_INTEGER_DEFAULT_0 = 'INTEGER DEFAULT 0';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* SQL column definition for the sessions table with updated status CHECK constraint.
|
|
16
|
+
*/
|
|
17
|
+
const SESSIONS_BASE_COLUMNS = `
|
|
18
|
+
id TEXT PRIMARY KEY,
|
|
19
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
20
|
+
name TEXT NOT NULL,
|
|
21
|
+
status TEXT NOT NULL DEFAULT 'starting' CHECK (status IN ('starting', 'running', 'waiting', 'stopped', 'completed', 'error', 'scheduled')),
|
|
22
|
+
mode TEXT NOT NULL DEFAULT 'yolo' CHECK (mode IN ('plan', 'standard', 'yolo')),
|
|
23
|
+
thinking_enabled INTEGER NOT NULL DEFAULT 1,
|
|
24
|
+
git_branch TEXT,
|
|
25
|
+
git_worktree TEXT,
|
|
26
|
+
pr_url TEXT,
|
|
27
|
+
pr_url_auto_link_disabled INTEGER NOT NULL DEFAULT 0,
|
|
28
|
+
error TEXT,
|
|
29
|
+
effort_level TEXT CHECK(effort_level IN ('low', 'medium', 'high', 'max', 'auto')),
|
|
30
|
+
cost_usd REAL DEFAULT 0,
|
|
31
|
+
claude_session_id TEXT,
|
|
32
|
+
model TEXT,
|
|
33
|
+
next_template_id TEXT REFERENCES session_templates(id) ON DELETE SET NULL,
|
|
34
|
+
parent_session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
|
|
35
|
+
input_tokens INTEGER DEFAULT 0,
|
|
36
|
+
output_tokens INTEGER DEFAULT 0,
|
|
37
|
+
thinking_tokens INTEGER DEFAULT 0,
|
|
38
|
+
cache_read_input_tokens INTEGER DEFAULT 0,
|
|
39
|
+
cache_creation_input_tokens INTEGER DEFAULT 0,
|
|
40
|
+
web_search_requests INTEGER DEFAULT 0,
|
|
41
|
+
context_window INTEGER DEFAULT 200000,
|
|
42
|
+
archived INTEGER NOT NULL DEFAULT 0,
|
|
43
|
+
starred INTEGER NOT NULL DEFAULT 0,
|
|
44
|
+
manually_named INTEGER NOT NULL DEFAULT 0,
|
|
45
|
+
scheduled_at INTEGER DEFAULT NULL,
|
|
46
|
+
reschedule_delay_minutes INTEGER DEFAULT 60, -- keep in sync with DEFAULT_RESCHEDULE_DELAY_MINUTES in shared/constants.js
|
|
47
|
+
auto_reschedule_enabled INTEGER DEFAULT 0,
|
|
48
|
+
reschedule_on_token_limit INTEGER DEFAULT 1,
|
|
49
|
+
reschedule_on_service_error INTEGER DEFAULT 1,
|
|
50
|
+
max_reschedule_count INTEGER DEFAULT NULL,
|
|
51
|
+
max_total_tokens INTEGER DEFAULT NULL,
|
|
52
|
+
reschedule_count INTEGER DEFAULT 0,
|
|
53
|
+
reschedule_at_token_count INTEGER DEFAULT NULL,
|
|
54
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
|
|
55
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* All possible column names that may exist in the sessions table for migration SELECT.
|
|
60
|
+
*/
|
|
61
|
+
const SESSIONS_ALL_COLUMNS = [
|
|
62
|
+
'id', 'project_id', 'name', 'status', 'mode', 'thinking_enabled',
|
|
63
|
+
'git_branch', 'git_worktree', 'pr_url', 'pr_url_auto_link_disabled', 'error', 'effort_level',
|
|
64
|
+
'cost_usd', 'claude_session_id', 'model', 'next_template_id',
|
|
65
|
+
'parent_session_id', 'input_tokens', 'output_tokens', 'thinking_tokens',
|
|
66
|
+
'cache_read_input_tokens', 'cache_creation_input_tokens',
|
|
67
|
+
'web_search_requests', 'context_window', 'archived', 'starred',
|
|
68
|
+
'manually_named', 'scheduled_at', 'reschedule_delay_minutes',
|
|
69
|
+
'auto_reschedule_enabled', 'reschedule_on_token_limit',
|
|
70
|
+
'reschedule_on_service_error', 'max_reschedule_count',
|
|
71
|
+
'max_total_tokens', 'reschedule_count', 'reschedule_at_token_count',
|
|
72
|
+
'created_at', 'updated_at',
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Migrate sessions table to include 'stopped' and 'scheduled' in status CHECK constraint.
|
|
77
|
+
* SQLite doesn't support ALTER TABLE to modify constraints, so we recreate the table.
|
|
78
|
+
*/
|
|
79
|
+
function migrateSessionsStatusConstraint(db) {
|
|
80
|
+
const tableSql = getTableSql(db, TABLE_SESSIONS);
|
|
81
|
+
|
|
82
|
+
// If schema already includes 'scheduled', no migration needed
|
|
83
|
+
if (tableSql?.includes("'scheduled'")) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const columnNames = getColumns(db, TABLE_SESSIONS);
|
|
88
|
+
const selectColumns = SESSIONS_ALL_COLUMNS
|
|
89
|
+
.filter((col) => columnNames.includes(col))
|
|
90
|
+
.join(', ');
|
|
91
|
+
|
|
92
|
+
db.exec(`
|
|
93
|
+
CREATE TABLE sessions_new (${SESSIONS_BASE_COLUMNS});
|
|
94
|
+
INSERT INTO sessions_new (${selectColumns})
|
|
95
|
+
SELECT ${selectColumns} FROM sessions;
|
|
96
|
+
DROP TABLE sessions;
|
|
97
|
+
ALTER TABLE sessions_new RENAME TO sessions;
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
|
|
99
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_archived ON sessions(archived);
|
|
101
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_starred ON sessions(archived, starred);
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_next_template ON sessions(next_template_id);
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_scheduled ON sessions(scheduled_at) WHERE scheduled_at IS NOT NULL;
|
|
105
|
+
`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @type {Array<{name: string, up: (db: import('better-sqlite3').Database) => void}>} */
|
|
109
|
+
export const sessionsMigrations = [
|
|
110
|
+
// --- Initial sessions columns ---
|
|
111
|
+
{
|
|
112
|
+
name: 'sessions-add-cost_usd',
|
|
113
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'cost_usd', 'REAL DEFAULT 0'); },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'sessions-add-claude_session_id',
|
|
117
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'claude_session_id', 'TEXT'); },
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'sessions-add-model',
|
|
121
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'model', 'TEXT'); },
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'sessions-add-provider_id-early',
|
|
125
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'provider_id', 'TEXT'); },
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'sessions-add-effort_level',
|
|
129
|
+
up(db) {
|
|
130
|
+
addColumnIfMissing(
|
|
131
|
+
db, TABLE_SESSIONS, 'effort_level',
|
|
132
|
+
"TEXT CHECK(effort_level IN ('low', 'medium', 'high', 'max', 'auto'))"
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// --- Scheduling columns ---
|
|
138
|
+
{
|
|
139
|
+
name: 'sessions-add-scheduled_at',
|
|
140
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'scheduled_at', 'INTEGER DEFAULT NULL'); },
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'sessions-add-reschedule_delay_minutes',
|
|
144
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'reschedule_delay_minutes', 'INTEGER DEFAULT 60'); /* keep in sync with DEFAULT_RESCHEDULE_DELAY_MINUTES */ },
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'sessions-add-auto_reschedule_enabled',
|
|
148
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'auto_reschedule_enabled', COL_INTEGER_DEFAULT_0); },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'sessions-add-reschedule_on_token_limit',
|
|
152
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'reschedule_on_token_limit', 'INTEGER DEFAULT 1'); },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'sessions-add-reschedule_on_service_error',
|
|
156
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'reschedule_on_service_error', 'INTEGER DEFAULT 1'); },
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'sessions-add-max_reschedule_count',
|
|
160
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'max_reschedule_count', 'INTEGER DEFAULT NULL'); },
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'sessions-add-max_total_tokens',
|
|
164
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'max_total_tokens', 'INTEGER DEFAULT NULL'); },
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'sessions-add-reschedule_count',
|
|
168
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'reschedule_count', COL_INTEGER_DEFAULT_0); },
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'sessions-add-reschedule_at_token_count',
|
|
172
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'reschedule_at_token_count', 'INTEGER DEFAULT NULL'); },
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// --- Status constraint migration (table recreation) ---
|
|
176
|
+
{
|
|
177
|
+
name: 'sessions-migrate-status-constraint',
|
|
178
|
+
up(db) { migrateSessionsStatusConstraint(db); },
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// --- Template chaining ---
|
|
182
|
+
{
|
|
183
|
+
name: 'sessions-add-next_template_id',
|
|
184
|
+
up(db) {
|
|
185
|
+
addColumnIfMissing(
|
|
186
|
+
db, TABLE_SESSIONS, 'next_template_id',
|
|
187
|
+
'TEXT REFERENCES session_templates(id) ON DELETE SET NULL'
|
|
188
|
+
);
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'sessions-add-parent_session_id',
|
|
193
|
+
up(db) {
|
|
194
|
+
addColumnIfMissing(
|
|
195
|
+
db, TABLE_SESSIONS, 'parent_session_id',
|
|
196
|
+
'TEXT REFERENCES sessions(id) ON DELETE SET NULL'
|
|
197
|
+
);
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: 'sessions-template-chaining-indexes',
|
|
202
|
+
up(db) {
|
|
203
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_next_template ON sessions(next_template_id)');
|
|
204
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id)');
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// --- Token usage columns ---
|
|
209
|
+
{
|
|
210
|
+
name: 'sessions-add-input_tokens',
|
|
211
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'input_tokens', COL_INTEGER_DEFAULT_0); },
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'sessions-add-output_tokens',
|
|
215
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'output_tokens', COL_INTEGER_DEFAULT_0); },
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'sessions-add-thinking_tokens',
|
|
219
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'thinking_tokens', COL_INTEGER_DEFAULT_0); },
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'sessions-add-cache_read_input_tokens',
|
|
223
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'cache_read_input_tokens', COL_INTEGER_DEFAULT_0); },
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: 'sessions-add-cache_creation_input_tokens',
|
|
227
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'cache_creation_input_tokens', COL_INTEGER_DEFAULT_0); },
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'sessions-add-web_search_requests',
|
|
231
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'web_search_requests', COL_INTEGER_DEFAULT_0); },
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: 'sessions-add-context_window',
|
|
235
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'context_window', 'INTEGER DEFAULT 200000'); },
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// --- Archived / starred / manually_named ---
|
|
239
|
+
{
|
|
240
|
+
name: 'sessions-add-archived',
|
|
241
|
+
up(db) {
|
|
242
|
+
addColumnIfMissing(db, TABLE_SESSIONS, 'archived', 'INTEGER NOT NULL DEFAULT 0');
|
|
243
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_archived ON sessions(archived)');
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'sessions-add-starred',
|
|
248
|
+
up(db) {
|
|
249
|
+
addColumnIfMissing(db, TABLE_SESSIONS, 'starred', 'INTEGER NOT NULL DEFAULT 0');
|
|
250
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_sessions_starred ON sessions(archived, starred)');
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'sessions-add-manually_named',
|
|
255
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'manually_named', 'INTEGER NOT NULL DEFAULT 0'); },
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'sessions-add-pr_url_auto_link_disabled',
|
|
259
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'pr_url_auto_link_disabled', 'INTEGER NOT NULL DEFAULT 0'); },
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
// --- Pending prompt / slash commands / pending model / auto send ---
|
|
263
|
+
{
|
|
264
|
+
name: 'sessions-add-pending_prompt',
|
|
265
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'pending_prompt', 'TEXT'); },
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: 'sessions-add-slash_commands',
|
|
269
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'slash_commands', 'TEXT'); },
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'sessions-add-pending_model',
|
|
273
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'pending_model', 'TEXT'); },
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'sessions-add-auto_send_pending_prompt',
|
|
277
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'auto_send_pending_prompt', COL_INTEGER_DEFAULT_0); },
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
// --- Provider ID (from providers table, added later in sequence) ---
|
|
281
|
+
{
|
|
282
|
+
name: 'sessions-add-provider_id-from-providers',
|
|
283
|
+
up(db) {
|
|
284
|
+
addColumnIfMissing(db, TABLE_SESSIONS, 'provider_id', 'TEXT REFERENCES providers(id)');
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// --- Agent type ---
|
|
289
|
+
{
|
|
290
|
+
name: 'sessions-add-agent_type',
|
|
291
|
+
up(db) { addColumnIfMissing(db, TABLE_SESSIONS, 'agent_type', "TEXT DEFAULT 'claude-code'"); },
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// --- Default mode / thinking changes ---
|
|
295
|
+
{
|
|
296
|
+
name: 'sessions-migrate-default-mode-thinking',
|
|
297
|
+
up(db) { migrateSessionsDefaultModeAndThinking(db); },
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { OPENAI_MODELS } from '../../../shared/src/index.js';
|
|
2
|
+
import { OPENAI_MODELS, GEMINI_MODELS } from '../../../shared/src/index.js';
|
|
3
3
|
|
|
4
4
|
export const BUILT_IN_ANTHROPIC_PROVIDER = {
|
|
5
5
|
id: 'anthropic-default',
|
|
@@ -13,6 +13,12 @@ export const BUILT_IN_OPENAI_PROVIDER = {
|
|
|
13
13
|
kind: 'openai',
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
export const BUILT_IN_GOOGLE_PROVIDER = {
|
|
17
|
+
id: 'google-default',
|
|
18
|
+
name: 'Google (Official)',
|
|
19
|
+
kind: 'google',
|
|
20
|
+
};
|
|
21
|
+
|
|
16
22
|
export const BUILT_IN_ANTHROPIC_MODELS = [
|
|
17
23
|
{ id: 'anthropic-haiku', providerId: BUILT_IN_ANTHROPIC_PROVIDER.id, modelId: 'claude-haiku-4-5-20251001', displayName: 'Haiku 4.5', description: 'Fast & lightweight', tier: 'haiku' },
|
|
18
24
|
{ id: 'anthropic-sonnet', providerId: BUILT_IN_ANTHROPIC_PROVIDER.id, modelId: 'claude-sonnet-4-6', displayName: 'Sonnet 4.6', description: 'Balanced', tier: 'sonnet' },
|
|
@@ -29,6 +35,15 @@ export const BUILT_IN_OPENAI_MODELS = OPENAI_MODELS.map((model) => ({
|
|
|
29
35
|
tier: 'custom',
|
|
30
36
|
}));
|
|
31
37
|
|
|
38
|
+
export const BUILT_IN_GOOGLE_MODELS = GEMINI_MODELS.map((model) => ({
|
|
39
|
+
id: model.seedId,
|
|
40
|
+
providerId: BUILT_IN_GOOGLE_PROVIDER.id,
|
|
41
|
+
modelId: model.id,
|
|
42
|
+
displayName: model.name,
|
|
43
|
+
description: model.description,
|
|
44
|
+
tier: 'custom',
|
|
45
|
+
}));
|
|
46
|
+
|
|
32
47
|
export const DEFAULT_QUICK_RESPONSES = [
|
|
33
48
|
{ label: 'Put a plan on the canvas', content: 'Put a plan on the canvas to get this done', autoSubmit: false, sortOrder: 0 },
|
|
34
49
|
{ label: 'Yes', content: 'Yes', autoSubmit: true, sortOrder: 1 },
|
|
@@ -87,6 +102,13 @@ function seedBuiltInProviders(db) {
|
|
|
87
102
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
88
103
|
);
|
|
89
104
|
|
|
105
|
+
// Note: Google provider and models are NOT seeded here because seedBaselineData
|
|
106
|
+
// runs before migrations. On existing databases the providers table still has
|
|
107
|
+
// CHECK(kind IN ('anthropic','openai')), so an INSERT with kind='google' would
|
|
108
|
+
// be silently ignored by INSERT OR IGNORE, and the subsequent model inserts
|
|
109
|
+
// would fail with a FOREIGN KEY constraint. The 'providers-seed-built-in-google'
|
|
110
|
+
// migration handles seeding Google for both fresh and existing databases after
|
|
111
|
+
// the 'providers-widen-kind-check-google' migration has widened the CHECK constraint.
|
|
90
112
|
for (const model of [...BUILT_IN_ANTHROPIC_MODELS, ...BUILT_IN_OPENAI_MODELS]) {
|
|
91
113
|
insertModel.run(model.id, model.providerId, model.modelId, model.displayName, model.description, model.tier, now);
|
|
92
114
|
}
|
|
@@ -7,7 +7,32 @@ import { DEFAULT_RESCHEDULE_DELAY_MINUTES } from '../../../shared/src/index.js';
|
|
|
7
7
|
|
|
8
8
|
/** Reusable SQL fragment for computed activity fields on sessions */
|
|
9
9
|
export const ACTIVITY_FIELDS_SQL = `
|
|
10
|
-
(
|
|
10
|
+
(
|
|
11
|
+
SELECT MAX(activity_at)
|
|
12
|
+
FROM (
|
|
13
|
+
SELECT cm.timestamp AS activity_at
|
|
14
|
+
FROM conversation_messages cm
|
|
15
|
+
WHERE cm.session_id = s.id
|
|
16
|
+
UNION ALL
|
|
17
|
+
SELECT ss.generated_at AS activity_at
|
|
18
|
+
FROM session_summaries ss
|
|
19
|
+
WHERE ss.session_id = s.id
|
|
20
|
+
UNION ALL
|
|
21
|
+
SELECT ss.updated_at AS activity_at
|
|
22
|
+
FROM session_summaries ss
|
|
23
|
+
WHERE ss.session_id = s.id
|
|
24
|
+
UNION ALL
|
|
25
|
+
SELECT COALESCE(cr.completed_at, cr.started_at) AS activity_at
|
|
26
|
+
FROM command_runs cr
|
|
27
|
+
WHERE cr.session_id = s.id
|
|
28
|
+
)
|
|
29
|
+
WHERE activity_at IS NOT NULL
|
|
30
|
+
) AS last_activity_at,
|
|
31
|
+
(
|
|
32
|
+
SELECT MAX(cm.timestamp)
|
|
33
|
+
FROM conversation_messages cm
|
|
34
|
+
WHERE cm.session_id = s.id
|
|
35
|
+
) AS last_message_at,
|
|
11
36
|
(CAST(
|
|
12
37
|
CASE
|
|
13
38
|
WHEN (SELECT MAX(cm2.timestamp) FROM conversation_messages cm2 WHERE cm2.session_id = s.id) IS NOT NULL
|
|
@@ -26,6 +26,10 @@ CREATE TABLE IF NOT EXISTS session_templates (
|
|
|
26
26
|
mode TEXT DEFAULT 'yolo' CHECK(mode IN ('plan', 'standard', 'yolo')),
|
|
27
27
|
effort_level TEXT CHECK(effort_level IN ('low', 'medium', 'high', 'max', 'auto')),
|
|
28
28
|
target_lane_id TEXT REFERENCES kanban_lanes(id) ON DELETE SET NULL,
|
|
29
|
+
show_in_quick_responses INTEGER NOT NULL DEFAULT 0,
|
|
30
|
+
quick_response_auto_submit INTEGER NOT NULL DEFAULT 0,
|
|
31
|
+
quick_response_sort_order INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
legacy_quick_response_id TEXT UNIQUE,
|
|
29
33
|
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
|
|
30
34
|
updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
31
35
|
);
|
|
@@ -39,7 +43,7 @@ CREATE TABLE IF NOT EXISTS providers (
|
|
|
39
43
|
additional_env_vars TEXT,
|
|
40
44
|
commit_attribution_override TEXT,
|
|
41
45
|
is_built_in INTEGER NOT NULL DEFAULT 0,
|
|
42
|
-
kind TEXT NOT NULL DEFAULT 'anthropic' CHECK(kind IN ('anthropic','openai')),
|
|
46
|
+
kind TEXT NOT NULL DEFAULT 'anthropic' CHECK(kind IN ('anthropic','openai','google')),
|
|
43
47
|
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
|
|
44
48
|
updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
|
|
45
49
|
);
|
|
@@ -13,36 +13,38 @@ export function buildCommandButtonApiInstructions(apiUrl, sessionId, projectId)
|
|
|
13
13
|
return '';
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
return `## Commands
|
|
16
|
+
return `## Circus Commands
|
|
17
17
|
|
|
18
|
-
This project has
|
|
18
|
+
This project has Circus Commands configured - reusable shell commands you can execute. Use the Bash tool to run these curl commands.
|
|
19
|
+
|
|
20
|
+
> When the user asks to "run a command", "what commands are available", "list circus commands", or similar, use the Commands API below to discover and execute them.
|
|
19
21
|
|
|
20
22
|
### List Available Commands
|
|
21
23
|
\`\`\`bash
|
|
22
|
-
curl ${apiUrl}/api/sessions/${sessionId}/
|
|
24
|
+
curl ${apiUrl}/api/sessions/${sessionId}/circus-commands
|
|
23
25
|
\`\`\`
|
|
24
26
|
|
|
25
27
|
### Run a Command
|
|
26
28
|
\`\`\`bash
|
|
27
|
-
curl -X POST ${apiUrl}/api/sessions/${sessionId}/
|
|
29
|
+
curl -X POST ${apiUrl}/api/sessions/${sessionId}/circus-commands/<button_id>/run
|
|
28
30
|
\`\`\`
|
|
29
31
|
|
|
30
32
|
Response: { runId, buttonId, status: "running", output: "" }
|
|
31
33
|
|
|
32
34
|
### Check Run Status & Output
|
|
33
35
|
\`\`\`bash
|
|
34
|
-
curl ${apiUrl}/api/sessions/${sessionId}/
|
|
36
|
+
curl ${apiUrl}/api/sessions/${sessionId}/circus-commands/runs/<run_id>
|
|
35
37
|
\`\`\`
|
|
36
38
|
|
|
37
39
|
Response: { runId, buttonId, status, exitCode, output, startedAt, completedAt }
|
|
38
40
|
|
|
39
41
|
### List Command Runs
|
|
40
42
|
\`\`\`bash
|
|
41
|
-
curl ${apiUrl}/api/sessions/${sessionId}/
|
|
43
|
+
curl ${apiUrl}/api/sessions/${sessionId}/circus-commands/runs
|
|
42
44
|
\`\`\`
|
|
43
45
|
|
|
44
46
|
### Kill a Running Command
|
|
45
47
|
\`\`\`bash
|
|
46
|
-
curl -X POST ${apiUrl}/api/sessions/${sessionId}/
|
|
48
|
+
curl -X POST ${apiUrl}/api/sessions/${sessionId}/circus-commands/runs/<run_id>/kill
|
|
47
49
|
\`\`\``;
|
|
48
50
|
}
|
|
@@ -49,9 +49,12 @@ export function createCapturedSpawnProcess(agentType) {
|
|
|
49
49
|
});
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
if (agentType === 'claude-code') {
|
|
52
|
+
if (agentType === 'claude-code' || agentType === 'gemini') {
|
|
53
|
+
// Claude Code and Gemini don't pass prompts via stdin (they use CLI args).
|
|
54
|
+
// Complete after a short delay to simulate process execution.
|
|
53
55
|
setTimeout(complete, 10);
|
|
54
56
|
} else {
|
|
57
|
+
// Codex passes the prompt via stdin; complete when stdin is closed.
|
|
55
58
|
stdin.once('finish', complete);
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -59,12 +62,15 @@ export function createCapturedSpawnProcess(agentType) {
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
function summarizeSpawnEnv(env = {}) {
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
const summary = {};
|
|
66
|
+
if (Object.prototype.hasOwnProperty.call(env, 'CIRCUSCHIEF_COMMIT_ATTRIBUTION')) {
|
|
67
|
+
summary.CIRCUSCHIEF_COMMIT_ATTRIBUTION = env.CIRCUSCHIEF_COMMIT_ATTRIBUTION;
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
69
|
+
if (Object.prototype.hasOwnProperty.call(env, 'GEMINI_CLI_TRUST_WORKSPACE')) {
|
|
70
|
+
summary.GEMINI_CLI_TRUST_WORKSPACE = env.GEMINI_CLI_TRUST_WORKSPACE;
|
|
71
|
+
}
|
|
72
|
+
if (Object.keys(summary).length === 0) return {};
|
|
73
|
+
return summary;
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
function summarizeSpawnOptions(agentType, spawnOptions) {
|
|
@@ -78,6 +84,15 @@ function summarizeSpawnOptions(agentType, spawnOptions) {
|
|
|
78
84
|
};
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
if (agentType === 'gemini') {
|
|
88
|
+
return {
|
|
89
|
+
model: valueAfter(args, '-m'),
|
|
90
|
+
outputFormat: valueAfter(args, '--output-format'),
|
|
91
|
+
approvalMode: optionValue(args, '--approval-mode'),
|
|
92
|
+
prompt: valueAfter(args, '-p'),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
return {
|
|
82
97
|
model: valueAfter(args, '-m'),
|
|
83
98
|
sandbox: valueAfter(args, '--sandbox'),
|
|
@@ -91,6 +106,14 @@ function valueAfter(args, flag) {
|
|
|
91
106
|
return args[index + 1] ?? null;
|
|
92
107
|
}
|
|
93
108
|
|
|
109
|
+
function optionValue(args, flag) {
|
|
110
|
+
const separateValue = valueAfter(args, flag);
|
|
111
|
+
if (separateValue !== null) return separateValue;
|
|
112
|
+
const prefix = `${flag}=`;
|
|
113
|
+
const arg = args.find((value) => value.startsWith(prefix));
|
|
114
|
+
return arg ? arg.slice(prefix.length) : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
94
117
|
function valuesAfter(args, flag) {
|
|
95
118
|
const values = [];
|
|
96
119
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -116,6 +139,24 @@ function writeCapturedAgentEvents(agentType, stdout) {
|
|
|
116
139
|
return;
|
|
117
140
|
}
|
|
118
141
|
|
|
142
|
+
if (agentType === 'gemini') {
|
|
143
|
+
// Gemini CLI stream-json format: init → message → result
|
|
144
|
+
writeJsonLine(stdout, {
|
|
145
|
+
type: 'init',
|
|
146
|
+
session_id: `e2e-gemini-${Date.now()}`,
|
|
147
|
+
});
|
|
148
|
+
writeJsonLine(stdout, {
|
|
149
|
+
type: 'message',
|
|
150
|
+
role: 'assistant',
|
|
151
|
+
content: 'E2E spawn capture response.',
|
|
152
|
+
});
|
|
153
|
+
writeJsonLine(stdout, {
|
|
154
|
+
type: 'result',
|
|
155
|
+
stats: { input_tokens: 0, output_tokens: 0 },
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
119
160
|
writeJsonLine(stdout, {
|
|
120
161
|
type: 'system',
|
|
121
162
|
subtype: 'init',
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { createRobustEnv } from './nodeSpawnHelper.js';
|
|
3
|
+
import {
|
|
4
|
+
captureSpawnAttempt,
|
|
5
|
+
createCapturedSpawnProcess,
|
|
6
|
+
isE2ESpawnCaptureEnabled,
|
|
7
|
+
} from './e2eSpawnCapture.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a custom spawn function for the Gemini CLI.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors {@link createCodexSpawner} but for the `gemini` command.
|
|
13
|
+
*
|
|
14
|
+
* As with other CLI helpers:
|
|
15
|
+
* - The command 'node' is replaced with {@link process.execPath} so child
|
|
16
|
+
* processes use the same Node binary.
|
|
17
|
+
* - `createRobustEnv` guarantees the Node bin directory is on PATH.
|
|
18
|
+
*
|
|
19
|
+
* @returns {Function} Spawn function of shape (options) => childProcess
|
|
20
|
+
*/
|
|
21
|
+
export function createGeminiSpawner() {
|
|
22
|
+
return (options) => {
|
|
23
|
+
const { command, args, cwd, env, signal } = options;
|
|
24
|
+
// Replace 'node' with the absolute path to the current Node executable
|
|
25
|
+
const actualCommand = command === 'node' ? process.execPath : command;
|
|
26
|
+
|
|
27
|
+
// Ensure PATH includes the directory containing Node
|
|
28
|
+
const robustEnv = createRobustEnv(env);
|
|
29
|
+
|
|
30
|
+
// Trust the workspace automatically in headless/automated mode.
|
|
31
|
+
// Without this, Gemini CLI refuses to run in untrusted directories.
|
|
32
|
+
robustEnv.GEMINI_CLI_TRUST_WORKSPACE = 'true';
|
|
33
|
+
|
|
34
|
+
if (isE2ESpawnCaptureEnabled()) {
|
|
35
|
+
captureSpawnAttempt('gemini', { ...options, env: robustEnv });
|
|
36
|
+
return createCapturedSpawnProcess('gemini');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return spawn(actualCommand, args, {
|
|
40
|
+
cwd,
|
|
41
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
42
|
+
signal,
|
|
43
|
+
env: robustEnv,
|
|
44
|
+
windowsHide: true,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
+
import os from 'os';
|
|
3
4
|
import path from 'path';
|
|
4
5
|
import { chmod, mkdir, writeFile } from 'fs/promises';
|
|
5
6
|
|
|
6
7
|
const execAsync = promisify(exec);
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
const DEFAULT_MANAGED_HOOKS_PATH = path.join(os.homedir(), '.circuschief', 'hooks');
|
|
10
|
+
let _managedHooksPath = DEFAULT_MANAGED_HOOKS_PATH;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the managed hooks directory path.
|
|
14
|
+
* Production code uses the real home directory; tests can override via _setManagedHooksPath().
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
export function getManagedHooksPath() {
|
|
18
|
+
return _managedHooksPath;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Override the managed hooks path (for testing only).
|
|
23
|
+
* Restores the default when called with no arguments.
|
|
24
|
+
* @param {string} [overridePath]
|
|
25
|
+
*/
|
|
26
|
+
export function _setManagedHooksPath(overridePath) {
|
|
27
|
+
_managedHooksPath = overridePath ?? DEFAULT_MANAGED_HOOKS_PATH;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Legacy managed hooks path (pre-migration). Used by ensureWorktreeCommitAttributionHook()
|
|
32
|
+
* to recognize and auto-upgrade worktrees that still have the old relative path set.
|
|
33
|
+
*/
|
|
34
|
+
const LEGACY_MANAGED_HOOKS_PATH = '.circuschief-hooks';
|
|
35
|
+
|
|
8
36
|
const ATTRIBUTION_CONFIG_KEY = 'circuschief.commitAttribution';
|
|
9
37
|
const ATTRIBUTION_ENV_KEY = 'CIRCUSCHIEF_COMMIT_ATTRIBUTION';
|
|
10
38
|
|
|
@@ -54,9 +82,10 @@ git interpret-trailers --trailer "$trailer" --in-place "$msg_file"
|
|
|
54
82
|
}
|
|
55
83
|
|
|
56
84
|
export async function clearWorktreeCommitAttribution(worktreePath) {
|
|
85
|
+
const managedHooksPath = getManagedHooksPath();
|
|
57
86
|
const currentAttribution = await gitConfigValue(worktreePath, ATTRIBUTION_CONFIG_KEY);
|
|
58
87
|
const currentHooksPath = await gitConfigValue(worktreePath, 'core.hooksPath');
|
|
59
|
-
if (!currentAttribution && currentHooksPath !==
|
|
88
|
+
if (!currentAttribution && currentHooksPath !== managedHooksPath) {
|
|
60
89
|
return false;
|
|
61
90
|
}
|
|
62
91
|
|
|
@@ -70,7 +99,7 @@ export async function clearWorktreeCommitAttribution(worktreePath) {
|
|
|
70
99
|
}
|
|
71
100
|
}
|
|
72
101
|
|
|
73
|
-
if (currentHooksPath ===
|
|
102
|
+
if (currentHooksPath === managedHooksPath) {
|
|
74
103
|
try {
|
|
75
104
|
await git(worktreePath, 'config --worktree --unset core.hooksPath');
|
|
76
105
|
} catch {
|
|
@@ -90,10 +119,12 @@ export async function clearWorktreeCommitAttribution(worktreePath) {
|
|
|
90
119
|
* @returns {Promise<boolean>} True when a hook is installed or updated
|
|
91
120
|
*/
|
|
92
121
|
export async function ensureWorktreeCommitAttributionHook(worktreePath) {
|
|
122
|
+
const managedHooksPath = getManagedHooksPath();
|
|
123
|
+
|
|
93
124
|
await git(worktreePath, 'config extensions.worktreeConfig true');
|
|
94
125
|
|
|
95
126
|
const currentHooksPath = await gitConfigValue(worktreePath, 'core.hooksPath');
|
|
96
|
-
if (currentHooksPath && currentHooksPath !==
|
|
127
|
+
if (currentHooksPath && currentHooksPath !== managedHooksPath && currentHooksPath !== LEGACY_MANAGED_HOOKS_PATH) {
|
|
97
128
|
throw new Error(
|
|
98
129
|
`Cannot install managed commit attribution hook: worktree already has core.hooksPath set to "${currentHooksPath}"`
|
|
99
130
|
);
|
|
@@ -105,11 +136,10 @@ export async function ensureWorktreeCommitAttributionHook(worktreePath) {
|
|
|
105
136
|
// Unset is idempotent for stale worktrees that never stored attribution.
|
|
106
137
|
}
|
|
107
138
|
|
|
108
|
-
await git(worktreePath, `config --worktree core.hooksPath ${shellQuote(
|
|
139
|
+
await git(worktreePath, `config --worktree core.hooksPath ${shellQuote(managedHooksPath)}`);
|
|
109
140
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
await mkdir(hooksDir, { recursive: true });
|
|
141
|
+
const hookPath = path.join(managedHooksPath, 'commit-msg');
|
|
142
|
+
await mkdir(managedHooksPath, { recursive: true });
|
|
113
143
|
await writeFile(hookPath, buildCommitMsgHook(), 'utf8');
|
|
114
144
|
await chmod(hookPath, 0o755);
|
|
115
145
|
return true;
|