circuschief 0.5.0 → 0.6.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.js +7 -0
- package/packages/server/src/api/providers.js +1 -0
- package/packages/server/src/api/sessions-messages.js +6 -0
- package/packages/server/src/db/ProviderRepository.js +62 -6
- package/packages/server/src/db/SessionRepository.js +67 -11
- package/packages/server/src/db/migrations/index.js +2 -0
- package/packages/server/src/db/migrations/miscMigrations.js +53 -3
- package/packages/server/src/db/session-helpers.js +5 -0
- package/packages/server/src/schema.sql +1 -0
- package/packages/server/src/services/codexSpawnHelper.js +37 -0
- package/packages/server/src/services/conversationContext.js +27 -0
- package/packages/server/src/services/draftSessionService.js +13 -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 +124 -32
- package/packages/server/src/services/sessionManager.js +45 -8
- package/packages/server/src/services/sessionPrompts.js +25 -0
- package/packages/server/src/services/sessionProvider.js +162 -41
- package/packages/server/src/services/streamEventHandler.js +3 -0
- package/packages/server/src/services/templateTriggerService.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/web/dist/assets/{ActiveSessionsView-BafIafEu.js → ActiveSessionsView-UCbQrF1b.js} +1 -1
- package/packages/web/dist/assets/{AgentLogsView-DCF2WvP2.js → AgentLogsView-Cdw4nmvd.js} +1 -1
- package/packages/web/dist/assets/ApiClient-CWbXWDUY.js +1 -0
- package/packages/web/dist/assets/{ArchiveConfirmModal-fgoEQhfq.js → ArchiveConfirmModal-J48eh3zw.js} +1 -1
- package/packages/web/dist/assets/{CommandButtonDetailView-DAg07cDQ.js → CommandButtonDetailView-DnFhJY5A.js} +1 -1
- package/packages/web/dist/assets/EffortLevelSelector-bXbPo4Zw.js +1 -0
- package/packages/web/dist/assets/{GeneralSettingsView-Cn9VI2du.js → GeneralSettingsView-CQkmdczf.js} +1 -1
- package/packages/web/dist/assets/{InputWithButton-BvboBGbz.js → InputWithButton-XyM3k6lN.js} +1 -1
- package/packages/web/dist/assets/{InterpolationHelp-0GoSBPgf.js → InterpolationHelp-PfYR3KJo.js} +1 -1
- package/packages/web/dist/assets/MarkdownEditor-P8F5kO-o.js +2 -0
- package/packages/web/dist/assets/{ModelSelector-DPPD-92R.css → ModelSelector-BZOT1Jc6.css} +1 -1
- package/packages/web/dist/assets/ModelSelector-CowKfGMP.js +1 -0
- package/packages/web/dist/assets/{NewSessionView-C77YVqgY.js → NewSessionView-DkjFLvHU.js} +1 -1
- package/packages/web/dist/assets/{ProjectEditView-BBHOsgBV.js → ProjectEditView-embVT7NC.js} +1 -1
- package/packages/web/dist/assets/{ProjectListView-CLwtuJ0J.js → ProjectListView-CuYMmd3O.js} +1 -1
- package/packages/web/dist/assets/{ProjectNewView-CzDtVibO.js → ProjectNewView-CNaA4Maf.js} +1 -1
- package/packages/web/dist/assets/ProvidersView-C7rydtOd.js +1 -0
- package/packages/web/dist/assets/ProvidersView-DE82G_5W.css +1 -0
- package/packages/web/dist/assets/{QuickResponseSettings-BBHMapcA.js → QuickResponseSettings-BTQEKhwJ.js} +1 -1
- package/packages/web/dist/assets/{QuickResponsesPanel-CTXYjMF-.js → QuickResponsesPanel-BqMYSHb0.js} +1 -1
- package/packages/web/dist/assets/ResizableTextarea-DsU3TVwF.css +1 -0
- package/packages/web/dist/assets/{ResizableTextarea-Cw6aL4rp.js → ResizableTextarea-wYF3K2RO.js} +1 -1
- package/packages/web/dist/assets/SessionCard-BMGC2HqI.css +1 -0
- package/packages/web/dist/assets/SessionCard-bLaQEWWX.js +1 -0
- package/packages/web/dist/assets/{SessionDetailView-BL83oPiI.css → SessionDetailView-Cv-xMzXp.css} +1 -1
- package/packages/web/dist/assets/{SessionDetailView-CrZvMb3j.js → SessionDetailView-CvQOUsW2.js} +27 -27
- package/packages/web/dist/assets/SessionFormOptions-3pzbgI2Q.js +1 -0
- package/packages/web/dist/assets/SessionFormOptions-DhhIkjIS.css +1 -0
- package/packages/web/dist/assets/SessionListView-Dranfb72.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-DTnDAF95.js} +1 -1
- package/packages/web/dist/assets/{SettingsView-CmJ5JPd5.js → SettingsView-DNLUSsHV.js} +1 -1
- package/packages/web/dist/assets/SlashCommandWizard-CRGFaO8t.js +1 -0
- package/packages/web/dist/assets/SlashCommandWizard-Dn7sNaBd.css +1 -0
- package/packages/web/dist/assets/{SummarySettingsView-DQM1n3bc.js → SummarySettingsView-C7G_suHp.js} +1 -1
- package/packages/web/dist/assets/{TemplateDetailView-B8clSBPk.js → TemplateDetailView-B78_DLMR.js} +1 -1
- package/packages/web/dist/assets/{commandButtons-D74TkPNU.js → commandButtons-Bbjf3fCt.js} +1 -1
- package/packages/web/dist/assets/{index-CefzeYRE.js → index--V7c-VZf.js} +1 -1
- package/packages/web/dist/assets/{index-BELtFs3n.js → index-8Q04yd7H.js} +1 -1
- package/packages/web/dist/assets/{index-rjbX81sm.js → index-B47XRBDH.js} +1 -1
- package/packages/web/dist/assets/index-BQL_L4gL.js +82 -0
- package/packages/web/dist/assets/{index-BsDR4w2c.js → index-BXbgZrhS.js} +1 -1
- package/packages/web/dist/assets/{index-DQMHi05L.js → index-Bs7Qf5D6.js} +1 -1
- package/packages/web/dist/assets/{index-Dz7jFUYU.js → index-CGhDVPen.js} +1 -1
- package/packages/web/dist/assets/{index-f315nDFm.js → index-CKcRO1A6.js} +1 -1
- package/packages/web/dist/assets/{index-Gre8tUfC.js → index-CTq-SLIW.js} +1 -1
- package/packages/web/dist/assets/{index-_Lv79l46.js → index-CYyos3iC.js} +1 -1
- package/packages/web/dist/assets/{index-DMZZCi2u.js → index-Cf6vdW-B.js} +3 -3
- package/packages/web/dist/assets/{index-DIvveuSK.js → index-CsCREAxF.js} +1 -1
- package/packages/web/dist/assets/{index-CVozYqQ-.js → index-DJTTk_8T.js} +1 -1
- package/packages/web/dist/assets/{index-DPt6qBRK.js → index-DPqUJ5JK.js} +1 -1
- package/packages/web/dist/assets/{index-B5ocUoPf.js → index-EwAe1dKg.js} +1 -1
- package/packages/web/dist/assets/{index-CrLh8vw5.js → index-JBA8axyA.js} +1 -1
- package/packages/web/dist/assets/{index-DrlwE0Zo.js → index-JkVHFtK5.js} +1 -1
- package/packages/web/dist/assets/{index-DuXChAe-.js → index-gMPUwT55.js} +1 -1
- package/packages/web/dist/assets/{index-DYWZ8lD-.js → index-wadc_0zT.js} +1 -1
- package/packages/web/dist/assets/{projects-D_C9dE9s.js → projects-CPt3AB7U.js} +1 -1
- package/packages/web/dist/assets/providers-ChfeMvUq.js +1 -0
- package/packages/web/dist/assets/sessions-CwPsJOb1.js +1 -0
- package/packages/web/dist/assets/{settings-6Rw9xt-G.js → settings-BOj6wq6t.js} +1 -1
- package/packages/web/dist/index.html +1 -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/ProvidersView-Eg93KbyC.js +0 -1
- package/packages/web/dist/assets/ProvidersView-uD8SKWpA.css +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/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/index-BGAW2Nqa.js +0 -82
- package/packages/web/dist/assets/providers-BdvbPVdE.js +0 -1
- package/packages/web/dist/assets/sessions-Bs5FA6JZ.js +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sessions, messages, attachments, conversations } from '../database.js';
|
|
2
2
|
import { createClaudeCodeSpawner } from './nodeSpawnHelper.js';
|
|
3
|
+
import { createCodexSpawner } from './codexSpawnHelper.js';
|
|
3
4
|
import { resolveProviderFromModel, buildSessionEnv } from './sessionProvider.js';
|
|
4
5
|
import { agentGateway } from '../agents/AgentGateway.js';
|
|
5
6
|
import { LoggingAgentWrapper } from '../agents/LoggingAgentWrapper.js';
|
|
@@ -7,6 +8,7 @@ import { VCRAgentAdapter } from '../agents/vcr/VCRAgentAdapter.js';
|
|
|
7
8
|
import {
|
|
8
9
|
buildSystemPromptConfig,
|
|
9
10
|
getPermissionModeForSession,
|
|
11
|
+
getSandboxModeForSession,
|
|
10
12
|
buildPromptWithAttachments,
|
|
11
13
|
} from './sessionPrompts.js';
|
|
12
14
|
import {
|
|
@@ -20,18 +22,38 @@ import {
|
|
|
20
22
|
} from './streamEventHandler.js';
|
|
21
23
|
import { shouldRescheduleOnError, _checkProactiveReschedule } from './sessionErrors.js';
|
|
22
24
|
import { schedulerService } from './schedulerService.js';
|
|
23
|
-
import { buildConversationContextForModelSwitch } from './conversationContext.js';
|
|
25
|
+
import { buildConversationContextForModelSwitch, buildConversationContextForContinuation } from './conversationContext.js';
|
|
24
26
|
import { broadcastToSession } from '../websocket.js';
|
|
25
27
|
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Build the adapter-specific default config object for
|
|
31
|
+
* {@link createAgentForSession}. Callers may pass an explicit `config` to
|
|
32
|
+
* override these defaults.
|
|
33
|
+
* @param {string} agentType
|
|
34
|
+
* @returns {Object}
|
|
35
|
+
*/
|
|
36
|
+
function buildAgentConfig(agentType) {
|
|
37
|
+
if (agentType === 'codex') {
|
|
38
|
+
return { spawnCodexProcess: createCodexSpawner() };
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
/**
|
|
28
44
|
* Create the agent for a session, using gateway + logging + VCR.
|
|
29
45
|
*
|
|
30
|
-
*
|
|
46
|
+
* If `config` is empty, the adapter-specific default config is applied
|
|
47
|
+
* (e.g. codex receives a fresh `spawnCodexProcess` spawner). Explicit
|
|
48
|
+
* `config` keys win over defaults.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} agentType - The agent type (e.g., 'claude-code', 'codex')
|
|
51
|
+
* @param {Object} [config] - Optional adapter config forwarded to the gateway.
|
|
31
52
|
* @returns {{ execute: (queryParams: any, meta?: any) => AsyncGenerator }}
|
|
32
53
|
*/
|
|
33
|
-
export function createAgentForSession(agentType = 'claude-code') {
|
|
34
|
-
const
|
|
54
|
+
export function createAgentForSession(agentType = 'claude-code', config = {}) {
|
|
55
|
+
const mergedConfig = { ...buildAgentConfig(agentType), ...config };
|
|
56
|
+
const baseAgent = agentGateway.createAgent(agentType, mergedConfig);
|
|
35
57
|
|
|
36
58
|
// Wrap with VCR adapter if in VCR mode
|
|
37
59
|
const agent = process.env.VCR_MODE
|
|
@@ -43,22 +65,13 @@ export function createAgentForSession(agentType = 'claude-code') {
|
|
|
43
65
|
}
|
|
44
66
|
|
|
45
67
|
/**
|
|
46
|
-
* Build query parameters for
|
|
47
|
-
*
|
|
48
|
-
* @param {Object} options
|
|
49
|
-
* @param {string} options.prompt - The prompt text to send
|
|
50
|
-
* @param {string} options.workingDirectory - Session working directory
|
|
51
|
-
* @param {AbortController} options.controller - Abort controller for the session
|
|
52
|
-
* @param {Object} options.session - Session object from DB
|
|
53
|
-
* @param {string} options.sessionId - Session ID
|
|
54
|
-
* @param {string|null} options.systemPrompt - Custom system prompt from project settings
|
|
55
|
-
* @param {string|null} options.model - Model to use
|
|
56
|
-
* @param {Object} options.sessionEnv - Environment variables for the session
|
|
57
|
-
* @param {string|null} [options.resumeSessionId] - Claude session ID to resume (null for new session)
|
|
58
|
-
* @returns {Object} Query parameters for agent.execute()
|
|
68
|
+
* Build query parameters for the Claude Code adapter.
|
|
69
|
+
* @returns {Object}
|
|
59
70
|
*/
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
function buildClaudeCodeQueryParams({
|
|
72
|
+
prompt, workingDirectory, controller, session, sessionId, systemPrompt,
|
|
73
|
+
model, sessionEnv, resumeSessionId = null,
|
|
74
|
+
}) {
|
|
62
75
|
const isVCR = Boolean(process.env.VCR_MODE);
|
|
63
76
|
const effectiveModel = isVCR ? 'claude-haiku-4-5-20251001' : model;
|
|
64
77
|
|
|
@@ -79,6 +92,67 @@ export function buildQueryParams({ prompt, workingDirectory, controller, session
|
|
|
79
92
|
};
|
|
80
93
|
}
|
|
81
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Build query parameters for the Codex adapter.
|
|
97
|
+
*
|
|
98
|
+
* Codex in v1 is a simple Chat-Completions-shaped executor — it doesn't need
|
|
99
|
+
* or accept Claude-specific options (permissionMode, settingSources,
|
|
100
|
+
* includePartialMessages, spawnClaudeCodeProcess, resume).
|
|
101
|
+
*
|
|
102
|
+
* Codex does have its own sandboxing model, driven from {@code session.mode}
|
|
103
|
+
* via {@link getSandboxModeForSession}. Codex CLI v0.124.0 also supports
|
|
104
|
+
* resume via `codex resume` / `codex exec resume`, but Circus Chief v1
|
|
105
|
+
* intentionally does NOT pass a resume token — wiring is deferred to a
|
|
106
|
+
* later phase (see canvas plan §Phase 4.5).
|
|
107
|
+
*
|
|
108
|
+
* @returns {Object}
|
|
109
|
+
*/
|
|
110
|
+
function buildCodexQueryParams({
|
|
111
|
+
prompt, workingDirectory, controller, session, sessionId, systemPrompt, model, sessionEnv,
|
|
112
|
+
}) {
|
|
113
|
+
const isVCR = Boolean(process.env.VCR_MODE);
|
|
114
|
+
// In VCR mode, force the cheapest commonly-cassetted OpenAI model.
|
|
115
|
+
const effectiveModel = isVCR ? 'gpt-4o-mini' : model;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
prompt,
|
|
119
|
+
options: {
|
|
120
|
+
cwd: workingDirectory,
|
|
121
|
+
abortController: controller,
|
|
122
|
+
env: sessionEnv,
|
|
123
|
+
model: effectiveModel,
|
|
124
|
+
effortLevel: session?.effortLevel ?? null,
|
|
125
|
+
systemPrompt: buildSystemPromptConfig(sessionId, session.projectId, systemPrompt, session.mode),
|
|
126
|
+
sandboxMode: getSandboxModeForSession(session?.mode),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Build query parameters for executing a session via the configured agent.
|
|
133
|
+
* Shared by runSession, continueSession, and continueSessionWithExistingMessage.
|
|
134
|
+
*
|
|
135
|
+
* @param {Object} options
|
|
136
|
+
* @param {string} options.prompt - The prompt text to send
|
|
137
|
+
* @param {string} options.workingDirectory - Session working directory
|
|
138
|
+
* @param {AbortController} options.controller - Abort controller for the session
|
|
139
|
+
* @param {Object} options.session - Session object from DB
|
|
140
|
+
* @param {string} options.sessionId - Session ID
|
|
141
|
+
* @param {string|null} options.systemPrompt - Custom system prompt from project settings
|
|
142
|
+
* @param {string|null} options.model - Model to use
|
|
143
|
+
* @param {Object} options.sessionEnv - Environment variables for the session
|
|
144
|
+
* @param {string|null} [options.resumeSessionId] - Session ID to resume (null for new session)
|
|
145
|
+
* @param {string} [options.agentType] - 'claude-code' (default) | 'codex'
|
|
146
|
+
* @returns {Object} Query parameters for agent.execute()
|
|
147
|
+
*/
|
|
148
|
+
export function buildQueryParams(options) {
|
|
149
|
+
const { agentType = 'claude-code' } = options || {};
|
|
150
|
+
if (agentType === 'codex') {
|
|
151
|
+
return buildCodexQueryParams(options);
|
|
152
|
+
}
|
|
153
|
+
return buildClaudeCodeQueryParams(options);
|
|
154
|
+
}
|
|
155
|
+
|
|
82
156
|
/**
|
|
83
157
|
* Execute the agent stream loop and handle post-turn completion, errors, and cleanup.
|
|
84
158
|
* This is the shared core of runSession, continueSession, and continueSessionWithExistingMessage.
|
|
@@ -146,17 +220,26 @@ export async function _executeSession({
|
|
|
146
220
|
}
|
|
147
221
|
|
|
148
222
|
/**
|
|
149
|
-
* Build prompt with conversation context
|
|
223
|
+
* Build prompt with conversation context for a continuation.
|
|
150
224
|
* When the model changes, we can't resume the previous session, so we include
|
|
151
225
|
* conversation history as context so the new model can continue naturally.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* @param {
|
|
226
|
+
* When the adapter cannot resume, we include conversation history so the
|
|
227
|
+
* model has context of previous turns.
|
|
228
|
+
* @param {Object} opts
|
|
229
|
+
* @param {boolean} opts.modelChanged
|
|
230
|
+
* @param {Object} opts.agent - Agent instance
|
|
231
|
+
* @param {string} opts.conversationId
|
|
232
|
+
* @param {string} opts.prompt
|
|
155
233
|
* @returns {Promise<string>}
|
|
156
234
|
*/
|
|
157
|
-
async function buildPromptForContinue(modelChanged, conversationId,
|
|
158
|
-
if (
|
|
159
|
-
|
|
235
|
+
async function buildPromptForContinue({ modelChanged, agent, conversationId, prompt }) {
|
|
236
|
+
if (modelChanged) {
|
|
237
|
+
return buildConversationContextForModelSwitch(conversationId) + prompt;
|
|
238
|
+
}
|
|
239
|
+
if (agent.needsConversationContext()) {
|
|
240
|
+
return buildConversationContextForContinuation(conversationId) + prompt;
|
|
241
|
+
}
|
|
242
|
+
return prompt;
|
|
160
243
|
}
|
|
161
244
|
|
|
162
245
|
/**
|
|
@@ -201,13 +284,16 @@ function buildContinueModelAndEnv(session, sessionId, model) {
|
|
|
201
284
|
async function buildContinueParams({
|
|
202
285
|
sessionId, session, model, systemPrompt, effectiveModel, sessionEnv,
|
|
203
286
|
modelChanged, activeConversation, promptWithAttachments,
|
|
204
|
-
workingDirectory, controller, agentType,
|
|
287
|
+
workingDirectory, controller, agentType, agent,
|
|
205
288
|
}) {
|
|
206
|
-
// Only resume if we have a session ID AND model hasn't changed
|
|
207
|
-
|
|
289
|
+
// Only resume if we have a session ID AND model hasn't changed AND the
|
|
290
|
+
// agent supports resume.
|
|
291
|
+
const canResume = activeConversation.claudeSessionId && !modelChanged && agent.supportsResume();
|
|
208
292
|
|
|
209
|
-
// Build prompt with conversation context when model changes
|
|
210
|
-
const promptWithContext = await buildPromptForContinue(
|
|
293
|
+
// Build prompt with conversation context when model changes or adapter needs it
|
|
294
|
+
const promptWithContext = await buildPromptForContinue({
|
|
295
|
+
modelChanged, agent, conversationId: activeConversation.id, prompt: promptWithAttachments,
|
|
296
|
+
});
|
|
211
297
|
|
|
212
298
|
const queryParams = buildQueryParams({
|
|
213
299
|
prompt: promptWithContext,
|
|
@@ -219,6 +305,7 @@ async function buildContinueParams({
|
|
|
219
305
|
model: effectiveModel,
|
|
220
306
|
sessionEnv,
|
|
221
307
|
resumeSessionId: canResume ? activeConversation.claudeSessionId : null,
|
|
308
|
+
agentType,
|
|
222
309
|
});
|
|
223
310
|
|
|
224
311
|
// Logging metadata for agent call tracking
|
|
@@ -246,6 +333,10 @@ async function setupConversationAndMessage(sessionId, content, fileAttachments)
|
|
|
246
333
|
activeConversationIds.set(sessionId, activeConversation.id);
|
|
247
334
|
|
|
248
335
|
const message = messages.create(sessionId, 'user', content, { toolUse: null, conversationId: activeConversation.id });
|
|
336
|
+
|
|
337
|
+
// Touch the session to update its updated_at timestamp so it sorts to the top
|
|
338
|
+
sessions.touch(sessionId);
|
|
339
|
+
|
|
249
340
|
broadcastToSession(sessionId, WS_MESSAGE_TYPES.SESSION_MESSAGE, {
|
|
250
341
|
message,
|
|
251
342
|
conversationId: activeConversation.id,
|
|
@@ -307,7 +398,7 @@ export async function continueSessionCore(sessionId, content, workingDirectory,
|
|
|
307
398
|
sessionId, session, model, systemPrompt,
|
|
308
399
|
effectiveModel: modelEnv.effectiveModel, sessionEnv: modelEnv.sessionEnv,
|
|
309
400
|
modelChanged: modelEnv.modelChanged, activeConversation, promptWithAttachments,
|
|
310
|
-
workingDirectory, controller, agentType,
|
|
401
|
+
workingDirectory, controller, agentType, agent,
|
|
311
402
|
});
|
|
312
403
|
|
|
313
404
|
await _executeSession({
|
|
@@ -384,6 +475,7 @@ export async function runSessionCore(sessionId, prompt, workingDirectory, config
|
|
|
384
475
|
systemPrompt,
|
|
385
476
|
model: effectiveModel,
|
|
386
477
|
sessionEnv,
|
|
478
|
+
agentType,
|
|
387
479
|
});
|
|
388
480
|
|
|
389
481
|
// Log query params for debugging third-party provider issues
|
|
@@ -3,7 +3,7 @@ import { broadcastToSession, broadcastToProject } from '../websocket.js';
|
|
|
3
3
|
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
4
4
|
import * as summaryService from './summaryService.js';
|
|
5
5
|
import { checkAndTriggerNextTemplate } from './templateTriggerService.js';
|
|
6
|
-
import { resolveProviderFromModel, buildSessionEnv } from './sessionProvider.js';
|
|
6
|
+
import { resolveProviderFromModel, buildSessionEnv, resolveAgentTypeFromModel } from './sessionProvider.js';
|
|
7
7
|
import {
|
|
8
8
|
shouldRescheduleOnError,
|
|
9
9
|
_checkProactiveReschedule,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
buildPromptWithAttachments,
|
|
19
19
|
getApiBaseUrl,
|
|
20
20
|
} from './sessionPrompts.js';
|
|
21
|
-
import { buildConversationContextForModelSwitch, buildConversationContextForBranch } from './conversationContext.js';
|
|
21
|
+
import { buildConversationContextForModelSwitch, buildConversationContextForBranch, buildConversationContextForContinuation } from './conversationContext.js';
|
|
22
22
|
import {
|
|
23
23
|
activeSessions,
|
|
24
24
|
activeConversationIds,
|
|
@@ -220,6 +220,13 @@ function validateAndFetchContinueContext(sessionId, conversationId) {
|
|
|
220
220
|
/**
|
|
221
221
|
* Resolve the effective model, provider, and session env from a model override.
|
|
222
222
|
* Detects model changes and updates the session record when needed.
|
|
223
|
+
*
|
|
224
|
+
* Defense in depth: when a new model arrives and the session has no assistant
|
|
225
|
+
* messages yet (i.e. it is still effectively a draft), re-derive agent_type
|
|
226
|
+
* and persist it together with model. Once the session has produced at least
|
|
227
|
+
* one assistant message we MUST NOT mutate agent_type — that would corrupt
|
|
228
|
+
* resume/context state across kinds.
|
|
229
|
+
*
|
|
223
230
|
* @param {Object} session - Current session object
|
|
224
231
|
* @param {string} sessionId - Session ID
|
|
225
232
|
* @param {string|null} model - Requested model (null to keep current)
|
|
@@ -233,13 +240,35 @@ function buildModelAndProvider(session, sessionId, model) {
|
|
|
233
240
|
|
|
234
241
|
let updatedSession = session;
|
|
235
242
|
if (model) {
|
|
236
|
-
|
|
243
|
+
const update = { model };
|
|
244
|
+
|
|
245
|
+
// Defense in depth: if this is still a draft (no assistant messages),
|
|
246
|
+
// also re-derive agent_type so it stays in sync with the chosen model.
|
|
247
|
+
// After the first assistant turn this is locked.
|
|
248
|
+
if (sessionHasNoAssistantMessages(sessionId)) {
|
|
249
|
+
const derivedAgentType = resolveAgentTypeFromModel(model);
|
|
250
|
+
if (derivedAgentType && derivedAgentType !== session.agentType) {
|
|
251
|
+
update.agentType = derivedAgentType;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
sessions.update(sessionId, update);
|
|
237
256
|
updatedSession = sessions.getById(sessionId);
|
|
238
257
|
}
|
|
239
258
|
|
|
240
259
|
return { effectiveModel, sessionEnv, modelChanged, session: updatedSession };
|
|
241
260
|
}
|
|
242
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Check whether a session has no assistant messages (i.e. is still a draft).
|
|
264
|
+
* @param {string} sessionId
|
|
265
|
+
* @returns {boolean}
|
|
266
|
+
*/
|
|
267
|
+
function sessionHasNoAssistantMessages(sessionId) {
|
|
268
|
+
const allMessages = messages.getBySessionId(sessionId);
|
|
269
|
+
return !allMessages.some(m => m.role === 'assistant');
|
|
270
|
+
}
|
|
271
|
+
|
|
243
272
|
/**
|
|
244
273
|
* Build query params for continueSessionWithExistingMessage.
|
|
245
274
|
* Handles context building (model switch / branch) and resume detection.
|
|
@@ -248,18 +277,26 @@ function buildModelAndProvider(session, sessionId, model) {
|
|
|
248
277
|
function buildExistingMessageQueryParams({
|
|
249
278
|
sessionId, conversationId, session, model, systemPrompt,
|
|
250
279
|
effectiveModel, sessionEnv, modelChanged, conversation,
|
|
251
|
-
lastUserMessage, workingDirectory, controller, agentType,
|
|
280
|
+
lastUserMessage, workingDirectory, controller, agentType, agent,
|
|
252
281
|
}) {
|
|
253
282
|
// Determine context needs and build context
|
|
254
283
|
const { needsContext, contextType } = determineContextNeed(conversation, modelChanged);
|
|
255
284
|
if (needsContext) {
|
|
256
285
|
console.log(`[SESSION] ${contextType === 'modelSwitch' ? 'Model changed' : 'Branched conversation'} - including context`);
|
|
257
286
|
}
|
|
258
|
-
|
|
287
|
+
let conversationContext = buildContextForType(conversationId, contextType);
|
|
288
|
+
|
|
289
|
+
// Fallback: if no specific context was built but the adapter needs
|
|
290
|
+
// conversation context (i.e. it can't resume), inject continuation history.
|
|
291
|
+
if (!conversationContext && agent.needsConversationContext()) {
|
|
292
|
+
conversationContext = buildConversationContextForContinuation(conversationId);
|
|
293
|
+
}
|
|
294
|
+
|
|
259
295
|
const promptWithContext = conversationContext + lastUserMessage.content;
|
|
260
296
|
|
|
261
|
-
// Only resume if we have a session ID AND model hasn't changed
|
|
262
|
-
|
|
297
|
+
// Only resume if we have a session ID AND model hasn't changed AND the
|
|
298
|
+
// agent supports resume.
|
|
299
|
+
const canResume = conversation.claudeSessionId && !modelChanged && agent.supportsResume();
|
|
263
300
|
|
|
264
301
|
const queryParams = buildQueryParams({
|
|
265
302
|
prompt: promptWithContext,
|
|
@@ -319,7 +356,7 @@ export async function continueSessionWithExistingMessage(sessionId, conversation
|
|
|
319
356
|
sessionId, conversationId, session, model, systemPrompt,
|
|
320
357
|
effectiveModel: modelEnv.effectiveModel, sessionEnv: modelEnv.sessionEnv,
|
|
321
358
|
modelChanged: modelEnv.modelChanged, conversation,
|
|
322
|
-
lastUserMessage, workingDirectory, controller, agentType,
|
|
359
|
+
lastUserMessage, workingDirectory, controller, agentType, agent,
|
|
323
360
|
});
|
|
324
361
|
|
|
325
362
|
await _executeSession({
|
|
@@ -108,6 +108,31 @@ export function getPermissionModeForSession(mode) {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Map session mode to the Codex CLI --sandbox flag value.
|
|
113
|
+
*
|
|
114
|
+
* plan → read-only (parity with Claude plan mode's read-mostly posture)
|
|
115
|
+
* standard → workspace-write (default; Codex can edit files in cwd)
|
|
116
|
+
* yolo → danger-full-access (parallels Claude's bypassPermissions)
|
|
117
|
+
*
|
|
118
|
+
* Note: `--full-auto` is intentionally NOT used — it is a shorthand that also
|
|
119
|
+
* overrides approval policies, which would conflate two orthogonal concerns.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} mode - Session mode ('plan', 'standard', 'yolo')
|
|
122
|
+
* @returns {string} Codex sandbox mode
|
|
123
|
+
*/
|
|
124
|
+
export function getSandboxModeForSession(mode) {
|
|
125
|
+
switch (mode) {
|
|
126
|
+
case 'plan':
|
|
127
|
+
return 'read-only';
|
|
128
|
+
case 'yolo':
|
|
129
|
+
return 'danger-full-access';
|
|
130
|
+
case 'standard':
|
|
131
|
+
default:
|
|
132
|
+
return 'workspace-write';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
111
136
|
/** Plan mode system prompt instructions */
|
|
112
137
|
export const PLAN_MODE_PROMPT = `## Plan Mode Active
|
|
113
138
|
|
|
@@ -12,7 +12,34 @@ export function resolveProviderFromModel(modelId) {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Resolve the agent type (claude-code vs codex) for a given model ID.
|
|
16
|
+
* Uses the owning provider's kind:
|
|
17
|
+
* - anthropic → claude-code
|
|
18
|
+
* - openai → codex
|
|
19
|
+
* Falls back to 'claude-code' for null / unknown / tier-name inputs.
|
|
20
|
+
* @param {string|null} modelId
|
|
21
|
+
* @returns {string} 'claude-code' | 'codex'
|
|
22
|
+
*/
|
|
23
|
+
export function resolveAgentTypeFromModel(modelId) {
|
|
24
|
+
if (!modelId) return 'claude-code';
|
|
25
|
+
const provider = modelProviders.getProviderByModelId(modelId);
|
|
26
|
+
if (!provider) return 'claude-code';
|
|
27
|
+
if (typeof modelProviders.getAgentTypeForProvider === 'function') {
|
|
28
|
+
const agentType = modelProviders.getAgentTypeForProvider(provider.id);
|
|
29
|
+
return agentType || 'claude-code';
|
|
30
|
+
}
|
|
31
|
+
// Fallback for test doubles that don't implement getAgentTypeForProvider:
|
|
32
|
+
// derive from kind directly.
|
|
33
|
+
if (provider.kind === 'openai') return 'codex';
|
|
34
|
+
return 'claude-code';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build environment variables from provider configuration.
|
|
39
|
+
* Branches on provider.kind so Anthropic-kind and OpenAI-kind providers
|
|
40
|
+
* emit only their own wire-protocol env vars (no cross-kind leaks).
|
|
41
|
+
* Providers without a `kind` field default to Anthropic behavior for
|
|
42
|
+
* backward compatibility.
|
|
16
43
|
* @param {Object|null} provider - Provider object
|
|
17
44
|
* @returns {Object} Environment variables to add to session env
|
|
18
45
|
*/
|
|
@@ -22,41 +49,68 @@ export function buildProviderEnv(provider) {
|
|
|
22
49
|
return {}; // Use SDK defaults
|
|
23
50
|
}
|
|
24
51
|
|
|
25
|
-
const
|
|
52
|
+
const kind = provider.kind || 'anthropic';
|
|
53
|
+
const env = kind === 'openai'
|
|
54
|
+
? buildOpenAIProviderEnv(provider)
|
|
55
|
+
: buildAnthropicProviderEnv(provider);
|
|
26
56
|
|
|
27
|
-
if (provider.
|
|
28
|
-
env.
|
|
57
|
+
if (provider.apiTimeoutMs) {
|
|
58
|
+
env.API_TIMEOUT_MS = String(provider.apiTimeoutMs);
|
|
29
59
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
env.ANTHROPIC_API_KEY = provider.authToken;
|
|
35
|
-
env.ANTHROPIC_AUTH_TOKEN = provider.authToken;
|
|
60
|
+
|
|
61
|
+
// Parse additional env vars (applied last so users can override anything above)
|
|
62
|
+
if (provider.additionalEnvVars) {
|
|
63
|
+
Object.assign(env, provider.additionalEnvVars);
|
|
36
64
|
}
|
|
37
65
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
66
|
+
logProviderEnv(provider, kind, env);
|
|
67
|
+
|
|
68
|
+
return env;
|
|
69
|
+
}
|
|
42
70
|
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
function buildOpenAIProviderEnv(provider) {
|
|
72
|
+
const env = {};
|
|
73
|
+
if (provider.baseUrl) env.OPENAI_BASE_URL = provider.baseUrl;
|
|
74
|
+
if (provider.authToken) env.OPENAI_API_KEY = provider.authToken;
|
|
75
|
+
return env;
|
|
76
|
+
}
|
|
45
77
|
|
|
46
|
-
|
|
47
|
-
|
|
78
|
+
function buildAnthropicProviderEnv(provider) {
|
|
79
|
+
const env = {};
|
|
80
|
+
if (provider.baseUrl) env.ANTHROPIC_BASE_URL = provider.baseUrl;
|
|
81
|
+
if (provider.authToken) {
|
|
82
|
+
env.ANTHROPIC_API_KEY = provider.authToken;
|
|
83
|
+
env.ANTHROPIC_AUTH_TOKEN = provider.authToken;
|
|
48
84
|
}
|
|
85
|
+
addAnthropicModelEnv(env, provider.models);
|
|
86
|
+
return env;
|
|
87
|
+
}
|
|
49
88
|
|
|
50
|
-
|
|
51
|
-
|
|
89
|
+
function addAnthropicModelEnv(env, models) {
|
|
90
|
+
if (!Array.isArray(models)) return;
|
|
91
|
+
const target = env;
|
|
92
|
+
const tiers = {
|
|
93
|
+
opus: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
|
|
94
|
+
sonnet: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
|
|
95
|
+
haiku: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
|
|
96
|
+
};
|
|
97
|
+
for (const [tier, envKey] of Object.entries(tiers)) {
|
|
98
|
+
const model = models.find((entry) => entry.tier === tier);
|
|
99
|
+
if (model) target[envKey] = model.modelId;
|
|
52
100
|
}
|
|
101
|
+
}
|
|
53
102
|
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
103
|
+
function logProviderEnv(provider, kind, env) {
|
|
104
|
+
if (kind === 'openai') {
|
|
105
|
+
console.log(`[SessionManager] buildProviderEnv: Provider "${provider.name}" (openai) env vars:`, {
|
|
106
|
+
OPENAI_BASE_URL: env.OPENAI_BASE_URL,
|
|
107
|
+
OPENAI_API_KEY: env.OPENAI_API_KEY ? '[SET]' : '[NOT SET]',
|
|
108
|
+
API_TIMEOUT_MS: env.API_TIMEOUT_MS,
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
57
111
|
}
|
|
58
112
|
|
|
59
|
-
console.log(`[SessionManager] buildProviderEnv: Provider "${provider.name}" env vars:`, {
|
|
113
|
+
console.log(`[SessionManager] buildProviderEnv: Provider "${provider.name}" (anthropic) env vars:`, {
|
|
60
114
|
ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL,
|
|
61
115
|
ANTHROPIC_API_KEY: env.ANTHROPIC_API_KEY ? '[SET]' : '[NOT SET]',
|
|
62
116
|
ANTHROPIC_AUTH_TOKEN: env.ANTHROPIC_AUTH_TOKEN ? '[SET]' : '[NOT SET]',
|
|
@@ -64,15 +118,24 @@ export function buildProviderEnv(provider) {
|
|
|
64
118
|
ANTHROPIC_DEFAULT_OPUS_MODEL: env.ANTHROPIC_DEFAULT_OPUS_MODEL,
|
|
65
119
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: env.ANTHROPIC_DEFAULT_HAIKU_MODEL,
|
|
66
120
|
});
|
|
67
|
-
|
|
68
|
-
return env;
|
|
69
121
|
}
|
|
70
122
|
|
|
71
123
|
/**
|
|
72
|
-
* Build environment variables for
|
|
124
|
+
* Build environment variables for the agent runtime based on provider and session settings.
|
|
73
125
|
* Always returns a robust env with Node in PATH to prevent ENOENT errors.
|
|
74
|
-
*
|
|
126
|
+
*
|
|
127
|
+
* Kind-aware behavior:
|
|
128
|
+
* - provider.kind === 'anthropic' (or legacy/unspecified): keeps today's behavior
|
|
129
|
+
* (MAX_THINKING_TOKENS + CLAUDE_CODE_EFFORT_LEVEL are applied as before).
|
|
130
|
+
* - provider.kind === 'openai': Claude-only envs (MAX_THINKING_TOKENS,
|
|
131
|
+
* CLAUDE_CODE_EFFORT_LEVEL) are NOT set, and any ANTHROPIC_* vars from
|
|
132
|
+
* process.env are stripped so Claude env doesn't leak into Codex sessions.
|
|
133
|
+
* - provider === null: strip BOTH kinds' auth/base-url vars so host env
|
|
134
|
+
* doesn't bleed into the SDK defaults.
|
|
135
|
+
*
|
|
136
|
+
* @param {Object|null} provider - Provider object or null for agent defaults
|
|
75
137
|
* @param {boolean} thinkingEnabled - Whether thinking mode is enabled
|
|
138
|
+
* @param {string|null} effortLevel - Optional effort level
|
|
76
139
|
* @returns {Object}
|
|
77
140
|
*/
|
|
78
141
|
export function buildSessionEnv(provider, thinkingEnabled = false, effortLevel = null) {
|
|
@@ -82,26 +145,84 @@ export function buildSessionEnv(provider, thinkingEnabled = false, effortLevel =
|
|
|
82
145
|
// Combine all env vars
|
|
83
146
|
const sessionEnv = {
|
|
84
147
|
...baseEnv,
|
|
85
|
-
...providerEnv, // Add provider env vars
|
|
148
|
+
...providerEnv, // Add provider env vars (wins over host env for its own keys)
|
|
86
149
|
};
|
|
87
150
|
|
|
88
|
-
|
|
89
|
-
|
|
151
|
+
const kind = provider?.kind || (provider ? 'anthropic' : null);
|
|
152
|
+
|
|
90
153
|
if (!provider) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
154
|
+
stripProviderRuntimeEnv(sessionEnv);
|
|
155
|
+
} else if (kind === 'openai') {
|
|
156
|
+
applyOpenAISessionEnv(sessionEnv, providerEnv);
|
|
157
|
+
} else {
|
|
158
|
+
stripOpenAIHostEnv(sessionEnv);
|
|
94
159
|
}
|
|
95
160
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
161
|
+
// Claude-only session env vars. Only set for Anthropic-kind providers
|
|
162
|
+
// (or when no provider is configured → Claude-default flow).
|
|
163
|
+
const isClaudeFlow = !provider || kind === 'anthropic';
|
|
100
164
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
165
|
+
if (isClaudeFlow) {
|
|
166
|
+
// Add thinking tokens if enabled (but suppress in VCR mode to minimize cost)
|
|
167
|
+
if (thinkingEnabled && !process.env.VCR_MODE) {
|
|
168
|
+
sessionEnv.MAX_THINKING_TOKENS = '10240';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Set effort level if provided
|
|
172
|
+
if (effortLevel) {
|
|
173
|
+
sessionEnv.CLAUDE_CODE_EFFORT_LEVEL = effortLevel;
|
|
174
|
+
}
|
|
104
175
|
}
|
|
105
176
|
|
|
106
177
|
return sessionEnv;
|
|
107
178
|
}
|
|
179
|
+
|
|
180
|
+
function stripProviderRuntimeEnv(env) {
|
|
181
|
+
const target = env;
|
|
182
|
+
delete target.ANTHROPIC_API_KEY;
|
|
183
|
+
delete target.ANTHROPIC_AUTH_TOKEN;
|
|
184
|
+
delete target.ANTHROPIC_BASE_URL;
|
|
185
|
+
delete target.OPENAI_API_KEY;
|
|
186
|
+
delete target.OPENAI_BASE_URL;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function applyOpenAISessionEnv(sessionEnv, providerEnv) {
|
|
190
|
+
stripAnthropicHostEnv(sessionEnv);
|
|
191
|
+
if (!providerEnv.OPENAI_API_KEY) {
|
|
192
|
+
replaceWithCodexCliEnv(sessionEnv, providerEnv);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
stripOpenAIBaseUrlUnlessProvided(sessionEnv, providerEnv);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function stripAnthropicHostEnv(env) {
|
|
199
|
+
const target = env;
|
|
200
|
+
delete target.ANTHROPIC_API_KEY;
|
|
201
|
+
delete target.ANTHROPIC_AUTH_TOKEN;
|
|
202
|
+
delete target.ANTHROPIC_BASE_URL;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function replaceWithCodexCliEnv(sessionEnv, providerEnv) {
|
|
206
|
+
const target = sessionEnv;
|
|
207
|
+
const allowed = ['HOME', 'PATH', 'USER', 'LOGNAME', 'SHELL', 'TERM', 'LANG', 'LC_ALL', 'TMPDIR'];
|
|
208
|
+
const cleaned = {};
|
|
209
|
+
for (const key of allowed) {
|
|
210
|
+
if (target[key] !== undefined) cleaned[key] = target[key];
|
|
211
|
+
}
|
|
212
|
+
Object.assign(cleaned, providerEnv);
|
|
213
|
+
for (const key of Object.keys(target)) delete target[key];
|
|
214
|
+
Object.assign(target, cleaned);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function stripOpenAIBaseUrlUnlessProvided(sessionEnv, providerEnv) {
|
|
218
|
+
if (providerEnv.OPENAI_BASE_URL || providerEnv.OPENAI_API_BASE) return;
|
|
219
|
+
const target = sessionEnv;
|
|
220
|
+
delete target.OPENAI_BASE_URL;
|
|
221
|
+
delete target.OPENAI_API_BASE;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function stripOpenAIHostEnv(env) {
|
|
225
|
+
const target = env;
|
|
226
|
+
delete target.OPENAI_API_KEY;
|
|
227
|
+
delete target.OPENAI_BASE_URL;
|
|
228
|
+
}
|
|
@@ -206,6 +206,9 @@ function handleAssistantTextContent(sessionId, textContent, toolUseBlocks) {
|
|
|
206
206
|
const currentModel = currentModels.get(sessionId) || null;
|
|
207
207
|
const message = messages.create(sessionId, 'assistant', textContent, { toolUse, conversationId, model: currentModel });
|
|
208
208
|
|
|
209
|
+
// Touch the session to update its updated_at timestamp so it sorts to the top
|
|
210
|
+
sessions.touch(sessionId);
|
|
211
|
+
|
|
209
212
|
// Associate pending work logs with this message immediately
|
|
210
213
|
// This ensures work logs are attached to the correct message, not just the last one
|
|
211
214
|
associateAndBroadcastWorkLogs(sessionId, message.id);
|
|
@@ -4,6 +4,7 @@ import { setupGitForSession } from './gitSessionSetup.js';
|
|
|
4
4
|
import { runSession } from './sessionManager.js';
|
|
5
5
|
import { broadcastToProject } from '../websocket.js';
|
|
6
6
|
import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
|
|
7
|
+
import { resolveAgentTypeFromModel } from './sessionProvider.js';
|
|
7
8
|
|
|
8
9
|
const liquid = new Liquid();
|
|
9
10
|
|
|
@@ -157,6 +158,7 @@ function buildChildSessionOptions(template, parentSession, settings) {
|
|
|
157
158
|
status: 'starting',
|
|
158
159
|
model: settings.model,
|
|
159
160
|
effortLevel: settings.effortLevel,
|
|
161
|
+
agentType: resolveAgentTypeFromModel(settings.model),
|
|
160
162
|
};
|
|
161
163
|
|
|
162
164
|
const postCreateUpdate = {
|