circuschief 0.5.0 → 0.7.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 +2 -1
- package/packages/server/src/agents/AgentGateway.js +36 -3
- package/packages/server/src/agents/BaseAgent.js +15 -1
- package/packages/server/src/agents/LoggingAgentWrapper.js +4 -0
- package/packages/server/src/agents/adapters/ClaudeCodeAdapter.js +9 -6
- package/packages/server/src/agents/adapters/CodexAdapter.js +262 -14
- package/packages/server/src/agents/adapters/codexCliRunner.js +185 -0
- package/packages/server/src/agents/adapters/codexEventMapper.js +235 -0
- package/packages/server/src/agents/types.js +1 -0
- package/packages/server/src/agents/vcr/VCRAgentAdapter.js +8 -0
- package/packages/server/src/api/agents.js +27 -0
- package/packages/server/src/api/canvas.js +20 -0
- package/packages/server/src/api/index.js +2 -0
- package/packages/server/src/api/projects-session-helpers.js +25 -0
- package/packages/server/src/api/projects.js +8 -0
- package/packages/server/src/api/providers.js +1 -0
- package/packages/server/src/api/sessions-draft.js +1 -0
- package/packages/server/src/api/sessions-messages.js +6 -0
- package/packages/server/src/api/settings.js +52 -4
- package/packages/server/src/db/ConversationRepository.js +16 -3
- package/packages/server/src/db/ProjectDefaultsRepository.js +47 -37
- package/packages/server/src/db/ProviderRepository.js +62 -6
- package/packages/server/src/db/SessionRepository.js +74 -14
- package/packages/server/src/db/SettingsRepository.js +44 -16
- package/packages/server/src/db/conversation-helpers.js +1 -0
- package/packages/server/src/db/migrations/conversationsMigrations.js +4 -0
- package/packages/server/src/db/migrations/index.js +4 -0
- package/packages/server/src/db/migrations/miscMigrations.js +53 -3
- package/packages/server/src/db/migrations/sessionsMigrations.js +6 -1
- package/packages/server/src/db/session-helpers.js +8 -0
- package/packages/server/src/schema.sql +9 -0
- package/packages/server/src/services/agentCallLogger.js +1 -1
- package/packages/server/src/services/codexSpawnHelper.js +37 -0
- package/packages/server/src/services/commandButtonPrompts.js +48 -0
- package/packages/server/src/services/conversationContext.js +27 -0
- package/packages/server/src/services/draftSessionService.js +15 -2
- package/packages/server/src/services/kanbanTriggers.js +3 -0
- package/packages/server/src/services/providerTestService.js +115 -15
- package/packages/server/src/services/sessionAgentGuard.js +38 -0
- package/packages/server/src/services/sessionExecution.js +127 -33
- package/packages/server/src/services/sessionManager.js +45 -8
- package/packages/server/src/services/sessionPrompts.js +29 -0
- package/packages/server/src/services/sessionProvider.js +160 -41
- package/packages/server/src/services/streamEventCallbacks.js +72 -40
- package/packages/server/src/services/streamEventHandler.js +16 -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 +6 -4
- package/packages/server/src/services/templateTriggerService.js +2 -0
- package/packages/server/src/services/usageTracker.js +5 -1
- package/packages/server/src/services/visibleFinalErrorMessage.js +123 -0
- package/packages/shared/src/constants.js +1 -2
- package/packages/shared/src/contracts/projects.js +2 -0
- package/packages/shared/src/contracts/providers.js +24 -7
- package/packages/shared/src/contracts/sessions.js +1 -1
- package/packages/shared/src/index.js +1 -0
- package/packages/shared/src/types.js +28 -0
- package/packages/shared/src/utils.js +9 -17
- package/packages/web/dist/assets/ActiveSessionsView-UJsCILDL.js +1 -0
- package/packages/web/dist/assets/{AgentLogsView-DCF2WvP2.js → AgentLogsView-BGFPLjLa.js} +1 -1
- package/packages/web/dist/assets/ApiClient-B4YTtyY4.js +1 -0
- package/packages/web/dist/assets/{ArchiveConfirmModal-fgoEQhfq.js → ArchiveConfirmModal-OFaj_uX5.js} +1 -1
- package/packages/web/dist/assets/{CommandButtonDetailView-DAg07cDQ.js → CommandButtonDetailView-D8S258uP.js} +1 -1
- package/packages/web/dist/assets/EffortLevelSelector-C2378L8e.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-Cn9VI2du.js → GeneralSettingsView-DsHChEhv.js} +1 -1
- package/packages/web/dist/assets/{InputWithButton-BvboBGbz.js → InputWithButton-Ci15ox0a.js} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-0GoSBPgf.js → InterpolationHelp-CIkOSkWX.js} +1 -1
- package/packages/web/dist/assets/MarkdownEditor-5-bexzUT.js +2 -0
- package/packages/web/dist/assets/ModelSelector-BMpR0DPr.js +1 -0
- package/packages/web/dist/assets/{ModelSelector-DPPD-92R.css → ModelSelector-D8hbTRIt.css} +1 -1
- package/packages/web/dist/assets/{NewSessionView-C77YVqgY.js → NewSessionView-BCqtIgWH.js} +2 -2
- package/packages/web/dist/assets/{NewSessionView-D_Hi7M9g.css → NewSessionView-CUUdHkfv.css} +1 -1
- package/packages/web/dist/assets/ProjectEditView-D9sK0fdH.css +1 -0
- package/packages/web/dist/assets/ProjectEditView-RFaxHhAX.js +1 -0
- package/packages/web/dist/assets/{ProjectListView-CLwtuJ0J.js → ProjectListView-B9FuWESY.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-CzDtVibO.js → ProjectNewView-D62jYlBL.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-DDKMIQWZ.js +1 -0
- package/packages/web/dist/assets/ProvidersView-DE82G_5W.css +1 -0
- package/packages/web/dist/assets/QuickResponseSettings-CDm5vwP7.js +1 -0
- package/packages/web/dist/assets/{QuickResponsesPanel-DIBQFj0W.css → QuickResponsesPanel-BlFDvnZ2.css} +1 -1
- package/packages/web/dist/assets/{QuickResponsesPanel-CTXYjMF-.js → QuickResponsesPanel-DZ_Lre_l.js} +1 -1
- package/packages/web/dist/assets/{ResizableTextarea-Cw6aL4rp.js → ResizableTextarea-DiIOEGjN.js} +1 -1
- package/packages/web/dist/assets/ResizableTextarea-DsU3TVwF.css +1 -0
- package/packages/web/dist/assets/SessionCard-BMGC2HqI.css +1 -0
- package/packages/web/dist/assets/SessionCard-DmjnVYWn.js +1 -0
- package/packages/web/dist/assets/SessionDetailView-CL7nmfiB.js +36 -0
- package/packages/web/dist/assets/SessionDetailView-CupIkI7u.css +1 -0
- package/packages/web/dist/assets/SessionFormOptions-BpUALRKn.css +1 -0
- package/packages/web/dist/assets/SessionFormOptions-DYUISplS.js +1 -0
- package/packages/web/dist/assets/SessionListView-BcxGz4aC.js +1 -0
- package/packages/web/dist/assets/{SessionListView-DVhoZHN9.css → SessionListView-fHlQyecX.css} +1 -1
- package/packages/web/dist/assets/{SessionLogStream-DIndOyFR.js → SessionLogStream-DpUE6Xsh.js} +1 -1
- package/packages/web/dist/assets/{SettingsView-CmJ5JPd5.js → SettingsView-BC055tIA.js} +1 -1
- package/packages/web/dist/assets/SlashCommandWizard-DmTyNG9O.js +1 -0
- package/packages/web/dist/assets/SlashCommandWizard-Dn7sNaBd.css +1 -0
- package/packages/web/dist/assets/SummarySettingsView-BgnRCwlq.js +1 -0
- package/packages/web/dist/assets/SummarySettingsView-l2bxHmZZ.css +1 -0
- package/packages/web/dist/assets/TemplateDetailView-BlhOmLUX.js +1 -0
- package/packages/web/dist/assets/{commandButtons-D74TkPNU.js → commandButtons-D4RPpLiu.js} +1 -1
- package/packages/web/dist/assets/index-4rhEeO0B.js +1 -0
- package/packages/web/dist/assets/index-9vb2KaAd.js +1 -0
- package/packages/web/dist/assets/index-B0CvZXuN.js +7 -0
- package/packages/web/dist/assets/index-B6G18FqB.js +82 -0
- package/packages/web/dist/assets/{index-DMZZCi2u.js → index-BGwH4Cfn.js} +3 -3
- package/packages/web/dist/assets/index-BUhvkAdF.js +1 -0
- package/packages/web/dist/assets/index-BcnkUk2o.js +1 -0
- package/packages/web/dist/assets/{index-DQMHi05L.js → index-Bn5xdGFM.js} +2 -2
- package/packages/web/dist/assets/index-CNwkdB0T.js +1 -0
- package/packages/web/dist/assets/index-CfL84oGW.js +1 -0
- package/packages/web/dist/assets/index-CkmxO8Mm.js +1 -0
- package/packages/web/dist/assets/index-Cpy4-yv3.js +1 -0
- package/packages/web/dist/assets/index-CrAQJmoZ.js +1 -0
- package/packages/web/dist/assets/{index-gmCCsCQ1.css → index-Cs2nxhrT.css} +1 -1
- package/packages/web/dist/assets/index-D6Ky9vJe.js +3 -0
- package/packages/web/dist/assets/index-DfrE0gAC.js +1 -0
- package/packages/web/dist/assets/index-KwEyz0F3.js +1 -0
- package/packages/web/dist/assets/index-OfCywayk.js +1 -0
- package/packages/web/dist/assets/index-PDesaJc6.js +1 -0
- package/packages/web/dist/assets/index-uB6nhSvz.js +1 -0
- package/packages/web/dist/assets/{projects-D_C9dE9s.js → projects-BUiOGmmb.js} +1 -1
- package/packages/web/dist/assets/providers-Bh1ZiiJi.js +1 -0
- package/packages/web/dist/assets/sessions-DH1R-NhV.js +1 -0
- package/packages/web/dist/assets/settings-Z4AVVmkJ.js +1 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/ActiveSessionsView-BafIafEu.js +0 -1
- package/packages/web/dist/assets/ApiClient-CcqJ-GAv.js +0 -1
- package/packages/web/dist/assets/EffortLevelSelector-xE3gidpq.js +0 -1
- package/packages/web/dist/assets/MarkdownEditor-HCKnwRye.js +0 -2
- package/packages/web/dist/assets/ModelSelector-B0RdlCHT.js +0 -1
- package/packages/web/dist/assets/ProjectEditView-BBHOsgBV.js +0 -1
- package/packages/web/dist/assets/ProjectEditView-CpeKj-_w.css +0 -1
- package/packages/web/dist/assets/ProvidersView-Eg93KbyC.js +0 -1
- package/packages/web/dist/assets/ProvidersView-uD8SKWpA.css +0 -1
- package/packages/web/dist/assets/QuickResponseSettings-BBHMapcA.js +0 -1
- package/packages/web/dist/assets/ResizableTextarea-B5nAA0RV.css +0 -1
- package/packages/web/dist/assets/SessionCard-CCapYVjy.js +0 -1
- package/packages/web/dist/assets/SessionCard-CcqIjL8q.css +0 -1
- package/packages/web/dist/assets/SessionDetailView-BL83oPiI.css +0 -1
- package/packages/web/dist/assets/SessionDetailView-CrZvMb3j.js +0 -36
- package/packages/web/dist/assets/SessionFormOptions-BuLlDF-7.css +0 -1
- package/packages/web/dist/assets/SessionFormOptions-Em7sQCGb.js +0 -1
- package/packages/web/dist/assets/SessionListView-3zdDtqhw.js +0 -1
- package/packages/web/dist/assets/SlashCommandWizard-BB30cSvo.css +0 -1
- package/packages/web/dist/assets/SlashCommandWizard-C_cSgF-P.js +0 -1
- package/packages/web/dist/assets/SummarySettingsView-DQM1n3bc.js +0 -1
- package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +0 -1
- package/packages/web/dist/assets/TemplateDetailView-B8clSBPk.js +0 -1
- package/packages/web/dist/assets/index-B5ocUoPf.js +0 -1
- package/packages/web/dist/assets/index-BELtFs3n.js +0 -1
- package/packages/web/dist/assets/index-BGAW2Nqa.js +0 -82
- package/packages/web/dist/assets/index-BsDR4w2c.js +0 -1
- package/packages/web/dist/assets/index-CVozYqQ-.js +0 -3
- package/packages/web/dist/assets/index-CefzeYRE.js +0 -1
- package/packages/web/dist/assets/index-CrLh8vw5.js +0 -1
- package/packages/web/dist/assets/index-DIvveuSK.js +0 -1
- package/packages/web/dist/assets/index-DPt6qBRK.js +0 -1
- package/packages/web/dist/assets/index-DYWZ8lD-.js +0 -1
- package/packages/web/dist/assets/index-DrlwE0Zo.js +0 -7
- package/packages/web/dist/assets/index-DuXChAe-.js +0 -1
- package/packages/web/dist/assets/index-Dz7jFUYU.js +0 -1
- package/packages/web/dist/assets/index-Gre8tUfC.js +0 -1
- package/packages/web/dist/assets/index-_Lv79l46.js +0 -1
- package/packages/web/dist/assets/index-f315nDFm.js +0 -1
- package/packages/web/dist/assets/index-rjbX81sm.js +0 -1
- package/packages/web/dist/assets/providers-BdvbPVdE.js +0 -1
- package/packages/web/dist/assets/sessions-Bs5FA6JZ.js +0 -1
- package/packages/web/dist/assets/settings-6Rw9xt-G.js +0 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex event mapper.
|
|
3
|
+
*
|
|
4
|
+
* Translates the real Codex CLI JSON-line event schema (as emitted by
|
|
5
|
+
* `codex exec --json`) into the normalized SDK-shaped events that Circus
|
|
6
|
+
* Chief's stream event handler already understands for Claude Code:
|
|
7
|
+
*
|
|
8
|
+
* - {@code system(init)}
|
|
9
|
+
* - {@code stream_event(content_block_delta)}
|
|
10
|
+
* - {@code assistant}
|
|
11
|
+
* - {@code result(success, usage)}
|
|
12
|
+
*
|
|
13
|
+
* Real Codex event types (v0.124.0, confirmed via
|
|
14
|
+
* `codex app-server generate-json-schema`):
|
|
15
|
+
*
|
|
16
|
+
* - {@code thread.started} — { thread_id }
|
|
17
|
+
* - {@code turn.started} — no payload
|
|
18
|
+
* - {@code item.started} — { item: ThreadItem }
|
|
19
|
+
* - {@code item.completed} — { item: ThreadItem }
|
|
20
|
+
* - {@code turn.completed} — { usage: { input_tokens, cached_input_tokens, output_tokens } }
|
|
21
|
+
* - {@code turn.failed} — { error: { message } }
|
|
22
|
+
* - {@code error} — { message } (transient, treated as fatal)
|
|
23
|
+
*
|
|
24
|
+
* ThreadItem.type variants handled in v1:
|
|
25
|
+
* - {@code agent_message} — { id, text, ... } → emitted as text_delta + assistant
|
|
26
|
+
* - {@code command_execution} — { id, command, aggregated_output, exit_code, status } → tool_result
|
|
27
|
+
* - {@code file_change} — { id, changes: [{path, kind}] } → tool_result
|
|
28
|
+
* - {@code reasoning} — { id, text } or legacy { content: [{type, text}] } → tool_result
|
|
29
|
+
*
|
|
30
|
+
* All other variants (mcp_tool_call,
|
|
31
|
+
* dynamic_tool_call, collab_agent_tool_call, web_search, image_view, image_generation,
|
|
32
|
+
* plan, context_compaction, hook_prompt, entered_review_mode, exited_review_mode,
|
|
33
|
+
* user_message) are currently ignored.
|
|
34
|
+
*
|
|
35
|
+
* The mapper is stateful across calls so it can accumulate agent message
|
|
36
|
+
* text across multiple `item.completed` events within a turn and stash
|
|
37
|
+
* usage counters until the terminal `turn.completed`.
|
|
38
|
+
*
|
|
39
|
+
* Pure in-process — no I/O, no timers, no child processes.
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} [options]
|
|
42
|
+
* @param {string} [options.model] - Optional model name to surface in the
|
|
43
|
+
* {@code system(init)} event. Codex's {@code thread.started} event does
|
|
44
|
+
* not carry the model, so the adapter must pass it in.
|
|
45
|
+
* @returns {{
|
|
46
|
+
* map: (codexEvent: Object) => Array<Object>,
|
|
47
|
+
* reset: () => void,
|
|
48
|
+
* finalize: () => Array<Object>
|
|
49
|
+
* }}
|
|
50
|
+
*/
|
|
51
|
+
export function createCodexEventMapper({ model } = {}) {
|
|
52
|
+
const mapperState = new MapperState();
|
|
53
|
+
const warnedUnknownItemTypes = new Set();
|
|
54
|
+
|
|
55
|
+
const handlers = {
|
|
56
|
+
'thread.started': (evt) => handleThreadStarted(evt, model),
|
|
57
|
+
'turn.started': () => [],
|
|
58
|
+
'item.started': () => [],
|
|
59
|
+
'item.completed': (evt) => handleItemCompleted(evt, mapperState, warnedUnknownItemTypes),
|
|
60
|
+
'turn.completed': (evt) => mapperState.onTurnCompleted(evt),
|
|
61
|
+
'turn.failed': (evt) => handleTurnFailed(evt),
|
|
62
|
+
'error': (evt) => handleError(evt),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function map(codexEvent) {
|
|
66
|
+
if (!codexEvent || typeof codexEvent !== 'object') return [];
|
|
67
|
+
const handler = handlers[codexEvent.type];
|
|
68
|
+
if (!handler) {
|
|
69
|
+
console.warn(`[codexEventMapper] Unknown Codex event type: "${codexEvent.type}"`);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
return handler(codexEvent);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
map,
|
|
77
|
+
reset: () => mapperState.reset(),
|
|
78
|
+
finalize: () => mapperState.finalize(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --- Mapper state class ----------------------------------------------------
|
|
83
|
+
|
|
84
|
+
class MapperState {
|
|
85
|
+
constructor() {
|
|
86
|
+
this.reset();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
reset() {
|
|
90
|
+
this.lastUsage = null;
|
|
91
|
+
this.terminated = false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Called by the adapter when the underlying stream ends without an explicit
|
|
96
|
+
* {@code turn.completed}. Returns a terminal result event if one hasn't been
|
|
97
|
+
* emitted yet; otherwise an empty array.
|
|
98
|
+
*/
|
|
99
|
+
finalize() {
|
|
100
|
+
if (this.terminated) return [];
|
|
101
|
+
this.terminated = true;
|
|
102
|
+
return [this.buildResultEvent()];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
onTurnCompleted(evt) {
|
|
106
|
+
if (evt && evt.usage) {
|
|
107
|
+
this.lastUsage = {
|
|
108
|
+
input_tokens: evt.usage.input_tokens,
|
|
109
|
+
output_tokens: evt.usage.output_tokens,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
this.terminated = true;
|
|
113
|
+
return [this.buildResultEvent()];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
buildResultEvent() {
|
|
117
|
+
const usage = this.lastUsage || { input_tokens: 0, output_tokens: 0 };
|
|
118
|
+
return {
|
|
119
|
+
type: 'result',
|
|
120
|
+
subtype: 'success',
|
|
121
|
+
usage: {
|
|
122
|
+
input_tokens: usage.input_tokens || 0,
|
|
123
|
+
output_tokens: usage.output_tokens || 0,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// --- Pure event handlers ---------------------------------------------------
|
|
130
|
+
|
|
131
|
+
function handleThreadStarted(evt, model) {
|
|
132
|
+
const init = {
|
|
133
|
+
type: 'system',
|
|
134
|
+
subtype: 'init',
|
|
135
|
+
session_id: evt.thread_id,
|
|
136
|
+
};
|
|
137
|
+
if (model) init.model = model;
|
|
138
|
+
return [init];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function handleItemCompleted(evt, _state, warnedTypes) {
|
|
142
|
+
const item = evt.item;
|
|
143
|
+
if (!item || typeof item !== 'object') return [];
|
|
144
|
+
|
|
145
|
+
if (isAgentMessageItem(item)) {
|
|
146
|
+
const text = typeof item.text === 'string' ? item.text : '';
|
|
147
|
+
return [
|
|
148
|
+
{
|
|
149
|
+
type: 'stream_event',
|
|
150
|
+
event: {
|
|
151
|
+
type: 'content_block_delta',
|
|
152
|
+
delta: { type: 'text_delta', text },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'assistant',
|
|
157
|
+
message: { content: [{ type: 'text', text }] },
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (item.type === 'command_execution') {
|
|
163
|
+
return [mapCommandExecution(item)];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (item.type === 'file_change') {
|
|
167
|
+
return [mapFileChange(item)];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (item.type === 'reasoning') {
|
|
171
|
+
return [mapReasoning(item)];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Unknown types — warn once per type
|
|
175
|
+
if (item.type && !warnedTypes.has(item.type)) {
|
|
176
|
+
warnedTypes.add(item.type);
|
|
177
|
+
console.warn(`[codexEventMapper] Ignoring unsupported item.type "${item.type}"`);
|
|
178
|
+
}
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isAgentMessageItem(item) {
|
|
183
|
+
return item.type === 'agent_message' || item.type === 'agentMessage';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function handleTurnFailed(evt) {
|
|
187
|
+
const message = evt?.error?.message || 'Codex turn failed';
|
|
188
|
+
throw new Error(message);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function handleError(evt) {
|
|
192
|
+
const message = evt?.message || 'Codex error';
|
|
193
|
+
throw new Error(message);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// --- Tool-use mapping helpers -----------------------------------------------
|
|
197
|
+
|
|
198
|
+
function mapCommandExecution(item) {
|
|
199
|
+
const cmd = item.command || '';
|
|
200
|
+
const parts = [`$ ${cmd}`];
|
|
201
|
+
if (item.exit_code !== undefined && item.exit_code !== 0) {
|
|
202
|
+
parts.push(`exit code: ${item.exit_code}`);
|
|
203
|
+
}
|
|
204
|
+
if (item.aggregated_output) parts.push(item.aggregated_output);
|
|
205
|
+
return {
|
|
206
|
+
type: 'tool_result',
|
|
207
|
+
tool_name: 'command_execution',
|
|
208
|
+
content: parts.join('\n'),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function mapFileChange(item) {
|
|
213
|
+
const changes = Array.isArray(item.changes)
|
|
214
|
+
? item.changes.map((c) => `${c.kind || 'change'}: ${c.path || 'unknown'}`).join('\n')
|
|
215
|
+
: 'unknown file change';
|
|
216
|
+
return {
|
|
217
|
+
type: 'tool_result',
|
|
218
|
+
tool_name: 'file_change',
|
|
219
|
+
content: changes,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function mapReasoning(item) {
|
|
224
|
+
// v0.124.0 shape: plain `text` string (e.g. gpt-5.2)
|
|
225
|
+
// Legacy shape: `content` array of {type, text} objects
|
|
226
|
+
const text = item.text
|
|
227
|
+
|| (Array.isArray(item.content)
|
|
228
|
+
? item.content.map((c) => c.text || '').join('\n')
|
|
229
|
+
: '');
|
|
230
|
+
return {
|
|
231
|
+
type: 'tool_result',
|
|
232
|
+
tool_name: 'reasoning',
|
|
233
|
+
content: text,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* @property {Object} env - Environment variables
|
|
25
25
|
* @property {Function} spawnClaudeCodeProcess - Process spawner function
|
|
26
26
|
* @property {string} [model] - Model to use
|
|
27
|
+
* @property {string|null} [effortLevel] - Reasoning effort override, or null/auto for provider default
|
|
27
28
|
* @property {string} systemPrompt - System prompt string
|
|
28
29
|
*/
|
|
29
30
|
|
|
@@ -116,6 +116,14 @@ export class VCRAgentAdapter {
|
|
|
116
116
|
return this.innerAgent.supportsResume?.() ?? false;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Proxy conversation context need to inner agent
|
|
121
|
+
* @returns {boolean}
|
|
122
|
+
*/
|
|
123
|
+
needsConversationContext() {
|
|
124
|
+
return this.innerAgent.needsConversationContext?.() ?? true;
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
/**
|
|
120
128
|
* Proxy capabilities to inner agent
|
|
121
129
|
* @returns {object}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { agentGateway } from '../agents/AgentGateway.js';
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GET /api/agents
|
|
8
|
+
*
|
|
9
|
+
* Returns the capabilities of every registered agent adapter, sourced from the
|
|
10
|
+
* adapter's static `capabilities` field (no adapter instantiation).
|
|
11
|
+
*
|
|
12
|
+
* Response shape:
|
|
13
|
+
* [
|
|
14
|
+
* { agentType: 'claude-code', capabilities: { streaming, thinking, reasoningEffort, toolUse, resume } },
|
|
15
|
+
* { agentType: 'codex', capabilities: { streaming, thinking, reasoningEffort, toolUse, resume } },
|
|
16
|
+
* ]
|
|
17
|
+
*/
|
|
18
|
+
router.get('/', (_req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const agents = agentGateway.getAllAgentCapabilities();
|
|
21
|
+
res.json(agents);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
res.status(500).json({ error: error.message });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default router;
|
|
@@ -279,6 +279,26 @@ router.get('/:id/canvas/file/:filename/content', (req, res) => {
|
|
|
279
279
|
});
|
|
280
280
|
});
|
|
281
281
|
|
|
282
|
+
// GET /api/sessions/:id/canvas/:itemId/content - Get one canvas item content inline
|
|
283
|
+
router.get('/:id/canvas/:itemId/content', (req, res) => {
|
|
284
|
+
const session = sessions.getById(req.params.id);
|
|
285
|
+
if (!session) return res.status(404).json({ error: ERR_SESSION_NOT_FOUND });
|
|
286
|
+
|
|
287
|
+
const item = canvasItems.getById(req.params.itemId);
|
|
288
|
+
if (!item) return res.status(404).json({ error: 'Canvas item not found' });
|
|
289
|
+
if (item.sessionId !== req.params.id) {
|
|
290
|
+
return res.status(400).json({ error: 'Canvas item does not belong to this session' });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
res.json({
|
|
294
|
+
content: item.content ?? null,
|
|
295
|
+
data: item.data ?? null,
|
|
296
|
+
type: item.type,
|
|
297
|
+
mimeType: item.mimeType,
|
|
298
|
+
filename: item.filename,
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
282
302
|
// GET /api/sessions/:id/canvas/file/:filename - Get canvas file by filename
|
|
283
303
|
// Writes the file to /tmp and returns the file path for Claude's Read tool
|
|
284
304
|
// Always returns the latest version
|
|
@@ -11,6 +11,7 @@ import providersRouter from './providers.js';
|
|
|
11
11
|
import commandsRouter from './commands.js';
|
|
12
12
|
import metricsRouter from './metrics.js';
|
|
13
13
|
import kanbanRouter from './kanban.js';
|
|
14
|
+
import agentsRouter from './agents.js';
|
|
14
15
|
import { getDbPath } from '../database.js';
|
|
15
16
|
import { schedulerService } from '../services/schedulerService.js';
|
|
16
17
|
|
|
@@ -38,6 +39,7 @@ router.use('/git', gitRouter);
|
|
|
38
39
|
router.use('/filesystem', filesystemRouter);
|
|
39
40
|
router.use('/settings', settingsRouter);
|
|
40
41
|
router.use('/providers', providersRouter);
|
|
42
|
+
router.use('/agents', agentsRouter);
|
|
41
43
|
router.use('/commands', commandsRouter)
|
|
42
44
|
|
|
43
45
|
// Canvas routes are nested under sessions
|
|
@@ -44,6 +44,26 @@ export function resolveDefault(explicit, projectDefault, systemDefault) {
|
|
|
44
44
|
return systemDefault;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Normalize provider IDs from JSON or multipart request fields.
|
|
49
|
+
* @param {*} value - Raw providerId value
|
|
50
|
+
* @returns {string|null|undefined}
|
|
51
|
+
*/
|
|
52
|
+
export function normalizeProviderId(value) {
|
|
53
|
+
if (value === undefined) return undefined;
|
|
54
|
+
if (value === null || value === '') return null;
|
|
55
|
+
if (typeof value !== 'string') {
|
|
56
|
+
throw new TypeError('providerId must be a string or null');
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveProviderDefault(explicit, projectDefault, systemDefault) {
|
|
62
|
+
if (explicit !== undefined) return explicit;
|
|
63
|
+
if (projectDefault !== undefined && projectDefault !== null) return projectDefault;
|
|
64
|
+
return systemDefault ?? null;
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
/**
|
|
48
68
|
* Resolve thinkingEnabled with special boolean handling.
|
|
49
69
|
* @param {object} body - Request body
|
|
@@ -114,11 +134,16 @@ export function prepareSessionConfig(body, projectDefs, systemDefaults) {
|
|
|
114
134
|
effortLevel = null;
|
|
115
135
|
}
|
|
116
136
|
|
|
137
|
+
const explicitProviderId = normalizeProviderId(body.providerId);
|
|
138
|
+
const projectProviderId = normalizeProviderId(projectDefs?.providerId);
|
|
139
|
+
const systemProviderId = normalizeProviderId(systemDefaults.providerId);
|
|
140
|
+
|
|
117
141
|
return {
|
|
118
142
|
prompt: body.prompt,
|
|
119
143
|
name: body.name,
|
|
120
144
|
mode: resolveDefault(body.mode, projectDefs?.mode, systemDefaults.mode),
|
|
121
145
|
model: resolveDefault(body.model, projectDefs?.model, systemDefaults.model || null),
|
|
146
|
+
providerId: resolveProviderDefault(explicitProviderId, projectProviderId, systemProviderId),
|
|
122
147
|
effortLevel,
|
|
123
148
|
gitBranch: resolveDefault(body.gitBranch, projectDefs?.gitBranch, null),
|
|
124
149
|
gitMode: resolveDefault(body.gitMode, projectDefs?.gitMode, null),
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
setupAndStartSession,
|
|
19
19
|
} from './projects-session-helpers.js';
|
|
20
20
|
import { validateGitSettings, buildRunsBySession } from './projects-helpers.js';
|
|
21
|
+
import { resolveAgentTypeFromModel } from '../services/sessionProvider.js';
|
|
21
22
|
import { access, constants } from 'fs/promises';
|
|
22
23
|
import { dirname, isAbsolute } from 'path';
|
|
23
24
|
|
|
@@ -242,7 +243,9 @@ function createSessionRow(projectId, config, nextTemplateId, initialStatus) {
|
|
|
242
243
|
parentSessionId: config.parentSessionId,
|
|
243
244
|
status: initialStatus,
|
|
244
245
|
model: config.model,
|
|
246
|
+
providerId: config.providerId,
|
|
245
247
|
effortLevel: config.effortLevel,
|
|
248
|
+
agentType: config.agentType,
|
|
246
249
|
});
|
|
247
250
|
|
|
248
251
|
const postCreateUpdate = {
|
|
@@ -298,6 +301,11 @@ router.post('/:id/sessions', uploadMiddleware('files', 10), handleUploadError, a
|
|
|
298
301
|
}
|
|
299
302
|
|
|
300
303
|
const { config, nextTemplateId } = prepared;
|
|
304
|
+
// Derive the agent type from the resolved model (after template overrides
|
|
305
|
+
// have been applied inside validateAndPrepareSessionConfig). Null/unknown
|
|
306
|
+
// model IDs fall back to 'claude-code'. This is the single source of truth
|
|
307
|
+
// for which adapter the session will use; sessions.create() persists it.
|
|
308
|
+
config.agentType = resolveAgentTypeFromModel(config.model);
|
|
301
309
|
const initialStatus = determineInitialStatus(config);
|
|
302
310
|
session = createSessionRow(req.params.id, config, nextTemplateId, initialStatus);
|
|
303
311
|
return await startSessionOrFail(req, res, { session, config, project });
|
|
@@ -139,6 +139,7 @@ router.post('/:id/test', async (req, res) => {
|
|
|
139
139
|
// Pick the sonnet-tiered model (if any) as the test model, falling back to any first model
|
|
140
140
|
const sonnetModel = provider.models?.find((m) => m.tier === 'sonnet');
|
|
141
141
|
const testConfig = {
|
|
142
|
+
kind: provider.kind || 'anthropic',
|
|
142
143
|
baseUrl: provider.baseUrl,
|
|
143
144
|
authToken: provider.authToken,
|
|
144
145
|
defaultSonnetModel: sonnetModel?.modelId,
|
|
@@ -57,6 +57,7 @@ router.post('/:id/start', requireSession, async (req, res) => {
|
|
|
57
57
|
const updatedSession = await startDraft(req.session_, {
|
|
58
58
|
prompt: req.body.prompt,
|
|
59
59
|
model: req.body.model,
|
|
60
|
+
providerId: req.body.providerId,
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
res.json({ success: true, session: updatedSession });
|
|
@@ -4,6 +4,7 @@ import { continueSession } from '../services/sessionManager.js';
|
|
|
4
4
|
import { upload as _upload, handleUploadError } from '../middleware/upload.js';
|
|
5
5
|
import { requireSession, requireSessionAndProject } from '../middleware/sessionLookup.js';
|
|
6
6
|
import * as slashCommandService from '../services/slashCommandService.js';
|
|
7
|
+
import { checkCrossKindSwitch } from '../services/sessionAgentGuard.js';
|
|
7
8
|
|
|
8
9
|
const router = Router();
|
|
9
10
|
|
|
@@ -61,6 +62,11 @@ router.post('/:id/message', _upload.array('files', 10), handleUploadError, requi
|
|
|
61
62
|
return res.status(400).json({ error: 'Session is not waiting for input' });
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
const crossKindError = checkCrossKindSwitch(req.session_, model);
|
|
66
|
+
if (crossKindError) {
|
|
67
|
+
return res.status(400).json(crossKindError);
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
try {
|
|
65
71
|
// Store file attachments if any - saves to disk in workingDirectory/.attachments
|
|
66
72
|
const messageAttachments = attachments.createBatch(req.session_.id, null, files, req.workingDirectory);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import { settings } from '../db/index.js';
|
|
2
|
+
import { modelProviders, settings } from '../db/index.js';
|
|
3
3
|
import { DEFAULT_SESSION_TITLE_PROMPT } from '../services/summaryService.js';
|
|
4
4
|
|
|
5
5
|
const router = Router();
|
|
6
|
+
const SUPPORTED_SUMMARY_PROVIDER_KINDS = new Set(['anthropic', 'openai']);
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* GET /api/settings/token-weights
|
|
@@ -93,19 +94,42 @@ router.get('/summary', (req, res) => {
|
|
|
93
94
|
*/
|
|
94
95
|
router.put('/summary', (req, res) => {
|
|
95
96
|
try {
|
|
96
|
-
const
|
|
97
|
+
const body = req.body || {};
|
|
98
|
+
const {
|
|
99
|
+
disableSessionSummaries,
|
|
100
|
+
sessionTitlePrompt,
|
|
101
|
+
summaryModel,
|
|
102
|
+
summaryProviderId,
|
|
103
|
+
} = body;
|
|
97
104
|
|
|
98
105
|
// Validate that all required fields are present
|
|
99
106
|
if (typeof disableSessionSummaries !== 'boolean' ||
|
|
100
|
-
typeof sessionTitlePrompt !== 'string'
|
|
107
|
+
typeof sessionTitlePrompt !== 'string' ||
|
|
108
|
+
!Object.prototype.hasOwnProperty.call(body, 'summaryModel') ||
|
|
109
|
+
!Object.prototype.hasOwnProperty.call(body, 'summaryProviderId')) {
|
|
101
110
|
return res.status(400).json({
|
|
102
|
-
error: 'Invalid summary settings. disableSessionSummaries must be a boolean, sessionTitlePrompt must be a string'
|
|
111
|
+
error: 'Invalid summary settings. disableSessionSummaries must be a boolean, sessionTitlePrompt must be a string, summaryModel must be present, and summaryProviderId must be present'
|
|
103
112
|
});
|
|
104
113
|
}
|
|
105
114
|
|
|
115
|
+
if (typeof summaryModel !== 'string') {
|
|
116
|
+
return res.status(400).json({ error: 'summaryModel must be a string' });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (summaryProviderId !== null && typeof summaryProviderId !== 'string') {
|
|
120
|
+
return res.status(400).json({ error: 'summaryProviderId must be a string or null' });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const validationError = validateSummaryModelSelection(summaryModel, summaryProviderId);
|
|
124
|
+
if (validationError) {
|
|
125
|
+
return res.status(400).json({ error: validationError });
|
|
126
|
+
}
|
|
127
|
+
|
|
106
128
|
const updatedSettings = settings.setSummarySettings({
|
|
107
129
|
disableSessionSummaries,
|
|
108
130
|
sessionTitlePrompt,
|
|
131
|
+
summaryModel,
|
|
132
|
+
summaryProviderId,
|
|
109
133
|
});
|
|
110
134
|
|
|
111
135
|
// Include the default prompt for UI display/editing
|
|
@@ -119,6 +143,30 @@ router.put('/summary', (req, res) => {
|
|
|
119
143
|
}
|
|
120
144
|
});
|
|
121
145
|
|
|
146
|
+
function validateSummaryModelSelection(summaryModel, summaryProviderId) {
|
|
147
|
+
if (!summaryModel) {
|
|
148
|
+
if (summaryProviderId !== null) {
|
|
149
|
+
return 'summaryProviderId must be null when summaryModel is empty';
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!summaryProviderId) {
|
|
155
|
+
return 'summaryProviderId is required when summaryModel is set';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const provider = modelProviders.getById(summaryProviderId);
|
|
159
|
+
if (!provider) return `Unknown summary provider: ${summaryProviderId}`;
|
|
160
|
+
if (!SUPPORTED_SUMMARY_PROVIDER_KINDS.has(provider.kind || 'anthropic')) {
|
|
161
|
+
return `Unsupported summary provider kind: ${provider.kind}`;
|
|
162
|
+
}
|
|
163
|
+
const ownsModel = provider.models?.some((model) => model.modelId === summaryModel);
|
|
164
|
+
if (!ownsModel) {
|
|
165
|
+
return `Provider ${summaryProviderId} does not own summary model ${summaryModel}`;
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
122
170
|
/**
|
|
123
171
|
* DELETE /api/settings/summary
|
|
124
172
|
* Reset summary settings to defaults
|
|
@@ -237,6 +237,7 @@ export class ConversationRepository extends BaseRepository {
|
|
|
237
237
|
* @param {Object} usage - Usage data
|
|
238
238
|
* @param {number} usage.inputTokens
|
|
239
239
|
* @param {number} usage.outputTokens
|
|
240
|
+
* @param {number} usage.thinkingTokens
|
|
240
241
|
* @param {number} usage.cacheReadInputTokens
|
|
241
242
|
* @param {number} usage.cacheCreationInputTokens
|
|
242
243
|
* @param {number} usage.webSearchRequests
|
|
@@ -255,6 +256,7 @@ export class ConversationRepository extends BaseRepository {
|
|
|
255
256
|
`UPDATE conversations SET
|
|
256
257
|
input_tokens = ?,
|
|
257
258
|
output_tokens = ?,
|
|
259
|
+
thinking_tokens = ?,
|
|
258
260
|
cache_read_input_tokens = ?,
|
|
259
261
|
cache_creation_input_tokens = ?,
|
|
260
262
|
web_search_requests = ?,
|
|
@@ -265,6 +267,7 @@ export class ConversationRepository extends BaseRepository {
|
|
|
265
267
|
.run(
|
|
266
268
|
usage.inputTokens,
|
|
267
269
|
usage.outputTokens,
|
|
270
|
+
usage.thinkingTokens || 0,
|
|
268
271
|
usage.cacheReadInputTokens,
|
|
269
272
|
usage.cacheCreationInputTokens,
|
|
270
273
|
usage.webSearchRequests,
|
|
@@ -290,9 +293,12 @@ export class ConversationRepository extends BaseRepository {
|
|
|
290
293
|
const now = Date.now();
|
|
291
294
|
|
|
292
295
|
this.db
|
|
293
|
-
|
|
294
|
-
`INSERT INTO conversations (id, session_id, name, summary, is_active,
|
|
295
|
-
|
|
296
|
+
.prepare(
|
|
297
|
+
`INSERT INTO conversations (id, session_id, name, summary, is_active,
|
|
298
|
+
input_tokens, output_tokens, thinking_tokens,
|
|
299
|
+
cache_read_input_tokens, cache_creation_input_tokens,
|
|
300
|
+
web_search_requests, context_window, created_at, updated_at)
|
|
301
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
296
302
|
)
|
|
297
303
|
.run(
|
|
298
304
|
id,
|
|
@@ -300,6 +306,13 @@ export class ConversationRepository extends BaseRepository {
|
|
|
300
306
|
conv.name,
|
|
301
307
|
conv.summary,
|
|
302
308
|
conv.isActive ? 1 : 0,
|
|
309
|
+
conv.inputTokens,
|
|
310
|
+
conv.outputTokens,
|
|
311
|
+
conv.thinkingTokens,
|
|
312
|
+
conv.cacheReadInputTokens,
|
|
313
|
+
conv.cacheCreationInputTokens,
|
|
314
|
+
conv.webSearchRequests,
|
|
315
|
+
conv.contextWindow,
|
|
303
316
|
now,
|
|
304
317
|
now
|
|
305
318
|
);
|