specrails-desktop 2.61.0 → 2.62.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/client/dist/assets/ActivityFeedPage-C_pqc281.js +1 -0
- package/client/dist/assets/{AgentBrowserCapture-CkNWDZT3.js → AgentBrowserCapture-vz6rbDtu.js} +1 -1
- package/client/dist/assets/AgentModeAnalyticsPane-BHCcUahS.js +2 -0
- package/client/dist/assets/AgentModeCodePane-DhmxgG3W.js +2 -0
- package/client/dist/assets/{AgentModeJobsPane-U1J6PaBo.js → AgentModeJobsPane-99fBBLri.js} +1 -1
- package/client/dist/assets/{AgentsPage-CXrqu5vo.js → AgentsPage-CTwm_6T8.js} +3 -3
- package/client/dist/assets/{AnalyticsPage-Cvtt-LOO.js → AnalyticsPage-YD8MLoPa.js} +1 -1
- package/client/dist/assets/CodePage-CLJn2F2e.js +3 -0
- package/client/dist/assets/{DesktopAnalyticsPage-CsDFy5rp.js → DesktopAnalyticsPage-MWGQVbwp.js} +1 -1
- package/client/dist/assets/{DocsDialog-CSlMPemn.js → DocsDialog-XUCYKoq0.js} +1 -1
- package/client/dist/assets/{DocsPage-BKlYAvWq.js → DocsPage-dOLC8bXA.js} +1 -1
- package/client/dist/assets/{ExportDropdown-CweP_1GP.js → ExportDropdown-BvKQgwM9.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-DxOvwmBF.js +19 -0
- package/client/dist/assets/{JobDetailModal-DYyGq1Vp.js → JobDetailModal-CkiubrkZ.js} +1 -1
- package/client/dist/assets/JobDetailPage-CNe4Wn7l.js +1 -0
- package/client/dist/assets/JobsPage-DSts_L_d.js +1 -0
- package/client/dist/assets/LoopBuilderPage-C9D9eer5.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-DkLRfsYX.js → LoopPreviewModal-Dv3dxHr_.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-CuACkXFV.js → LoopWindowSurface-DN2lbSnL.js} +1 -1
- package/client/dist/assets/{LoopsPage-ClSrWzDd.js → LoopsPage-DHgIreEW.js} +2 -2
- package/client/dist/assets/MinimizedChatsContext-Ds2-R6qJ.js +1 -0
- package/client/dist/assets/PluginsPage-D1EJ9oja.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-DKpa1wKk.js +1 -0
- package/client/dist/assets/{RepositoryDeliveries-DJCm1pZr.js → RepositoryDeliveries-CW4eKG3x.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-CeCr6Jz-.js → RepositoryScopeSelector-DrtPqvc_.js} +1 -1
- package/client/dist/assets/ReviewPacketPage-BtjPI0Cc.js +4 -0
- package/client/dist/assets/{TemplatePreviewModal-B8yd1AGU.js → TemplatePreviewModal-DcFlhUxy.js} +1 -1
- package/client/dist/assets/TicketDetailModalContext-Dea22CI-.js +16 -0
- package/client/dist/assets/{Trans-D5eOjfvG.js → Trans-CAY2Rd2f.js} +1 -1
- package/client/dist/assets/agent-5bSixVzz.js +1 -0
- package/client/dist/assets/agent-BR4EHfhq.js +1 -0
- package/client/dist/assets/agent-C0IKkJP8.js +1 -0
- package/client/dist/assets/agent-C7li9ayC.js +1 -0
- package/client/dist/assets/agent-D0dRmEen.js +1 -0
- package/client/dist/assets/{agent-BjttZLIl.js → agent-DZVGHm7H.js} +1 -1
- package/client/dist/assets/agent-Dip4h2YE.js +1 -0
- package/client/dist/assets/agent-Pp-P-n1p.js +1 -0
- package/client/dist/assets/ban-E4EaZe3a.js +1 -0
- package/client/dist/assets/chevron-up-CwGUHd6D.js +1 -0
- package/client/dist/assets/dist-Bq10nS2m.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-DZ3VWsHq.js → formatDistanceToNow-DlQY1Lm_.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-9JiGlFrx.js → getTimezoneOffsetInMilliseconds-COUeq65G.js} +1 -1
- package/client/dist/assets/index-BacrO_ub.css +2 -0
- package/client/dist/assets/index-CONHvEJw.js +83 -0
- package/client/dist/assets/{jira-api-Bp0zXwjg.js → jira-api-C94VNHNY.js} +1 -1
- package/client/dist/assets/layers-eDMjG41I.js +1 -0
- package/client/dist/assets/{project-repositories-CadLZOnr.js → project-repositories-Cc3oNv8X.js} +1 -1
- package/client/dist/assets/save-B6wGWwBR.js +1 -0
- package/client/dist/assets/{settings-Dn-pXukr.js → settings-BKJZ2KEM.js} +1 -1
- package/client/dist/assets/{settings-BBj_kZaD.js → settings-BV3jocIx.js} +1 -1
- package/client/dist/assets/{settings-COlH_P33.js → settings-BjM2Dw8_.js} +1 -1
- package/client/dist/assets/settings-CKu0LTib.js +1 -0
- package/client/dist/assets/{settings-D77mC_Lb.js → settings-DA77M48m.js} +1 -1
- package/client/dist/assets/{settings-ByVE7jdZ.js → settings-LfFnl1UH.js} +1 -1
- package/client/dist/assets/{settings-DZKy7MqN.js → settings-P1IQvhnL.js} +1 -1
- package/client/dist/assets/{settings-B5Hy_Kol.js → settings-eIMJjyK_.js} +1 -1
- package/client/dist/assets/{spending-DQDNtM8q.js → spending-DemeFfrH.js} +1 -1
- package/client/dist/assets/{useDesktop-DKNbOF0u.js → useDesktop-9Lwoel_8.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-D3YMw9qx.js → useRuntimeRuns-DL0xYoZ2.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-DaWuiO0S.js → useSharedWebSocket-BEHPeU1Y.js} +2 -2
- package/client/dist/assets/zap-CHo3aCD1.js +1 -0
- package/client/dist/index.html +23 -19
- package/docs/guide/de/integrations/6-agent-chat.md +11 -0
- package/docs/guide/en/integrations/6-agent-chat.md +11 -0
- package/docs/guide/es/integrations/6-agent-chat.md +11 -0
- package/docs/guide/fr/integrations/6-agent-chat.md +11 -0
- package/docs/guide/it/integrations/6-agent-chat.md +11 -0
- package/docs/guide/ja/integrations/6-agent-chat.md +11 -0
- package/docs/guide/pt/integrations/6-agent-chat.md +11 -0
- package/docs/guide/zh/integrations/6-agent-chat.md +11 -0
- package/docs/internals/README.md +1 -0
- package/docs/internals/agent-sessions.md +157 -0
- package/docs/internals/api-reference.md +33 -4
- package/docs/internals/configuration.md +34 -0
- package/docs/internals/core-runtime-updates.md +10 -3
- package/docs/internals/operations-runbook.md +11 -0
- package/docs/internals/programmatic-agent-runtime.md +1 -1
- package/docs/internals/source-map.md +40 -1
- package/mcp-bridge/dist/specrails-mcp.js +82 -15
- package/package.json +1 -1
- package/server/dist/agent-mcp-config.js +41 -0
- package/server/dist/core-compat.js +2 -0
- package/server/dist/desktop-db.js +101 -4
- package/server/dist/desktop-router.js +31 -2
- package/server/dist/index.js +58 -0
- package/server/dist/mcp/tools/app-settings.js +11 -0
- package/server/dist/mcp/tools/mission.js +45 -2
- package/server/dist/mcp/tools/specs.js +2 -2
- package/server/dist/modules/accounting/runtime/result-event.js +9 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +25 -0
- package/server/dist/modules/agent-sessions/adapters/host-process.js +73 -0
- package/server/dist/modules/agent-sessions/adapters/http.js +31 -0
- package/server/dist/modules/agent-sessions/adapters/rpc-client.js +147 -0
- package/server/dist/modules/agent-sessions/application/session-event-pump.js +99 -0
- package/server/dist/modules/agent-sessions/domain/availability.js +25 -0
- package/server/dist/modules/agent-sessions/domain/errors.js +23 -0
- package/server/dist/modules/agent-sessions/domain/host-state.js +80 -0
- package/server/dist/modules/agent-sessions/domain/projection.js +147 -0
- package/server/dist/modules/agent-sessions/domain/protocol.js +59 -0
- package/server/dist/modules/agent-sessions/domain/subagent-policy.js +34 -0
- package/server/dist/modules/agent-sessions/index.js +31 -0
- package/server/dist/modules/agent-sessions/ports.js +2 -0
- package/server/dist/modules/agent-sessions/runtime/core-sessions-availability.js +43 -0
- package/server/dist/modules/agent-sessions/runtime/session-host-registry.js +226 -0
- package/server/dist/modules/agents/runtime/agent-store.js +3 -1
- package/server/dist/modules/loops/runtime/loop-implement-recipe.js +15 -4
- package/server/dist/modules/loops/runtime/loop-run-manager.js +4 -0
- package/server/dist/modules/missions/runtime/agent-chat-manager.js +137 -40
- package/server/dist/modules/missions/runtime/agent-chat-router.js +68 -0
- package/server/dist/modules/missions/runtime/agent-operator-prompt.js +8 -9
- package/server/dist/modules/missions/runtime/agent-session-store.js +133 -0
- package/server/dist/modules/missions/runtime/core-session-runner.js +204 -0
- package/server/dist/modules/missions/runtime/mission-core-sessions.js +286 -0
- package/server/dist/modules/missions/runtime/mission-session-projector.js +168 -0
- package/server/dist/modules/project-settings/adapters/http.js +14 -2
- package/server/dist/modules/project-settings/adapters/sqlite.js +30 -2
- package/server/dist/modules/project-settings/domain.js +24 -0
- package/server/dist/modules/project-settings/index.js +2 -1
- package/server/dist/modules/specs/runtime/spec-models.js +1 -1
- package/server/dist/plugins/codex-spawn.js +15 -7
- package/server/dist/project-registry.js +32 -0
- package/server/dist/project-router-settings.js +7 -1
- package/server/dist/providers/claude-adapter.js +6 -1
- package/client/dist/assets/ActivityFeedPage-Wi5XNgoG.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-CXz2j-Bt.js +0 -2
- package/client/dist/assets/AgentModeCodePane-DnsBPtpS.js +0 -2
- package/client/dist/assets/CodePage-D35o-MFO.js +0 -3
- package/client/dist/assets/InteractiveJobComposer-CFKvbQeJ.js +0 -19
- package/client/dist/assets/JobDetailPage-C6gpdYvj.js +0 -1
- package/client/dist/assets/JobsPage-CgS0pnKd.js +0 -1
- package/client/dist/assets/LoopBuilderPage-jUp20DSZ.js +0 -1
- package/client/dist/assets/MinimizedChatsContext-CroAs6Kh.js +0 -1
- package/client/dist/assets/PluginsPage-nCKQOwRL.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-BVDYwllU.js +0 -1
- package/client/dist/assets/ReviewPacketPage-kTErrEjx.js +0 -4
- package/client/dist/assets/TicketDetailModalContext-YBJX3RmP.js +0 -16
- package/client/dist/assets/agent--8QAfoXL.js +0 -1
- package/client/dist/assets/agent-3HxXkIWX.js +0 -1
- package/client/dist/assets/agent-CpFCz32T.js +0 -1
- package/client/dist/assets/agent-DVnw6ybO.js +0 -1
- package/client/dist/assets/agent-Z5s_GTP0.js +0 -1
- package/client/dist/assets/agent-hv57-zyG.js +0 -1
- package/client/dist/assets/agent-xGrZ_aN1.js +0 -1
- package/client/dist/assets/index-Bfunvi6w.css +0 -2
- package/client/dist/assets/index-lTGJJTuk.js +0 -83
- package/client/dist/assets/save-OsUqVn99.js +0 -1
- package/client/dist/assets/settings-Dbyd6gmj.js +0 -1
- package/client/dist/assets/zap-CUOlvtT1.js +0 -1
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AgentChatManager = void 0;
|
|
6
|
+
exports.AgentChatManager = exports.SessionRebuildBusyError = void 0;
|
|
7
7
|
exports.sanitizeAgentTitle = sanitizeAgentTitle;
|
|
8
8
|
const crypto_1 = require("crypto");
|
|
9
9
|
const readline_1 = require("readline");
|
|
@@ -35,6 +35,8 @@ const agent_store_1 = require("../../agents/runtime/agent-store");
|
|
|
35
35
|
const explore_draft_title_1 = require("../../conversations/runtime/explore-draft-title");
|
|
36
36
|
const agent_input_store_1 = require("./agent-input-store");
|
|
37
37
|
const agent_steering_1 = require("./agent-steering");
|
|
38
|
+
const core_session_runner_1 = require("./core-session-runner");
|
|
39
|
+
const agent_session_store_1 = require("./agent-session-store");
|
|
38
40
|
/**
|
|
39
41
|
* Providers report an expired or foreign resume token as a normalized error.
|
|
40
42
|
* Those errors are safe to recover from by clearing the token and retrying the
|
|
@@ -47,12 +49,18 @@ function isStaleResumeError(message) {
|
|
|
47
49
|
/unknown (?:session|thread)(?: id)?\b/i.test(message) ||
|
|
48
50
|
/(?:session|thread)(?: id)?\b[^\n]{0,160}\b(?:not found|does not exist)\b/i.test(message));
|
|
49
51
|
}
|
|
52
|
+
/** A projection rebuild was requested while a turn is running. */
|
|
53
|
+
class SessionRebuildBusyError extends Error {
|
|
54
|
+
}
|
|
55
|
+
exports.SessionRebuildBusyError = SessionRebuildBusyError;
|
|
50
56
|
class AgentChatManager {
|
|
51
57
|
_broadcast;
|
|
52
58
|
_db;
|
|
53
59
|
_port;
|
|
54
60
|
_registry;
|
|
55
61
|
_active = new Map();
|
|
62
|
+
/** Missions' Core agent sessions (null = legacy transports only). */
|
|
63
|
+
_coreSessions = null;
|
|
56
64
|
_nativeInputNotifiers = new Map();
|
|
57
65
|
/** Conversations with a turn in-flight but not yet spawned. Closes the TOCTOU
|
|
58
66
|
* window the attachment-extraction await opens between the busy guard and
|
|
@@ -89,6 +97,21 @@ class AgentChatManager {
|
|
|
89
97
|
// A sidecar restart must retain unsent input without replaying operations.
|
|
90
98
|
(0, agent_input_store_1.recoverPendingAgentInputs)(db);
|
|
91
99
|
}
|
|
100
|
+
/** Composition-time wiring of Core agent sessions (see agent-sessions module). */
|
|
101
|
+
setCoreSessions(coreSessions) {
|
|
102
|
+
this._coreSessions = coreSessions;
|
|
103
|
+
}
|
|
104
|
+
/** Conversation deleted: close its Core session (reads the row before it is dropped). */
|
|
105
|
+
closeCoreSession(conversationId) {
|
|
106
|
+
const conversation = (0, agent_store_1.getAgentConversation)(this._db, conversationId);
|
|
107
|
+
if (!conversation || !this._coreSessions)
|
|
108
|
+
return Promise.resolve();
|
|
109
|
+
return this._coreSessions.close(conversation);
|
|
110
|
+
}
|
|
111
|
+
/** A turn is live while its provider child or its Core session turn is running. */
|
|
112
|
+
_isTurnLive(conversationId) {
|
|
113
|
+
return this._active.has(conversationId) || !!this._coreSessions?.handle(conversationId);
|
|
114
|
+
}
|
|
92
115
|
async _awaitWhileLive(work) {
|
|
93
116
|
if (this._disposed)
|
|
94
117
|
return { disposed: true };
|
|
@@ -160,11 +183,11 @@ class AgentChatManager {
|
|
|
160
183
|
}
|
|
161
184
|
/** True while a turn is streaming for this conversation. */
|
|
162
185
|
isStreaming(conversationId) {
|
|
163
|
-
return !this._disposed && this.
|
|
186
|
+
return !this._disposed && this._isTurnLive(conversationId);
|
|
164
187
|
}
|
|
165
188
|
/** True while a turn is in flight (spawned or reserved). */
|
|
166
189
|
isBusy(conversationId) {
|
|
167
|
-
return !this._disposed && (this.
|
|
190
|
+
return !this._disposed && (this._isTurnLive(conversationId) || this._reserved.has(conversationId));
|
|
168
191
|
}
|
|
169
192
|
pendingMessages(conversationId) {
|
|
170
193
|
return (this._queue.get(conversationId) ?? []).map((item) => ({
|
|
@@ -192,8 +215,62 @@ class AgentChatManager {
|
|
|
192
215
|
pendingMessages: this.pendingMessages(conversationId),
|
|
193
216
|
streamingText: this._streamingText.get(conversationId) ?? '',
|
|
194
217
|
})),
|
|
218
|
+
// Core agent sessions with background work: the client restores the
|
|
219
|
+
// background indicator and live sub-agent rows from this after reconnecting.
|
|
220
|
+
sessions: (0, agent_session_store_1.listActiveSessionCursors)(this._db).map((cursor) => ({
|
|
221
|
+
conversationId: cursor.conversationId,
|
|
222
|
+
residentPhase: cursor.residentPhase,
|
|
223
|
+
processAlive: cursor.processAlive,
|
|
224
|
+
liveSubagents: cursor.liveSubagents,
|
|
225
|
+
subagents: (0, agent_session_store_1.listSubagents)(this._db, cursor.conversationId).filter((node) => node.phase === 'running'),
|
|
226
|
+
})),
|
|
195
227
|
};
|
|
196
228
|
}
|
|
229
|
+
/** Projected Core session state of one mission (null when it never ran in Core). */
|
|
230
|
+
sessionState(conversationId) {
|
|
231
|
+
const cursor = (0, agent_session_store_1.getSessionCursor)(this._db, conversationId);
|
|
232
|
+
if (!cursor)
|
|
233
|
+
return null;
|
|
234
|
+
return { residentPhase: cursor.residentPhase, processAlive: cursor.processAlive, liveSubagents: cursor.liveSubagents, subagents: (0, agent_session_store_1.listSubagents)(this._db, conversationId) };
|
|
235
|
+
}
|
|
236
|
+
/** Stop a mission's background sub-agents through its Core session. */
|
|
237
|
+
async stopSubagents(conversationId, subagentIds) {
|
|
238
|
+
const conversation = (0, agent_store_1.getAgentConversation)(this._db, conversationId);
|
|
239
|
+
if (!conversation || !this._coreSessions)
|
|
240
|
+
return [];
|
|
241
|
+
return this._coreSessions.stopSubagents(conversation, subagentIds);
|
|
242
|
+
}
|
|
243
|
+
/** Delegated runtime: launch a sub-agent on the mission's configured provider. */
|
|
244
|
+
async delegateSubagent(conversationId, params) {
|
|
245
|
+
const conversation = (0, agent_store_1.getAgentConversation)(this._db, conversationId);
|
|
246
|
+
if (!conversation || !this._coreSessions)
|
|
247
|
+
throw new Error('This mission does not run in a Core session.');
|
|
248
|
+
return this._coreSessions.delegate(conversation, params);
|
|
249
|
+
}
|
|
250
|
+
/** Delegated runtime: wait for sub-agents (results are delivered to the agent once). */
|
|
251
|
+
async waitSubagents(conversationId, subagentIds, timeoutMs) {
|
|
252
|
+
const conversation = (0, agent_store_1.getAgentConversation)(this._db, conversationId);
|
|
253
|
+
if (!conversation || !this._coreSessions)
|
|
254
|
+
throw new Error('This mission does not run in a Core session.');
|
|
255
|
+
return this._coreSessions.waitSubagents(conversation, subagentIds, timeoutMs);
|
|
256
|
+
}
|
|
257
|
+
/** Plugin MCP servers for a project-pinned mission (empty for providers without plugin MCP entries). */
|
|
258
|
+
_pluginMcpServers(providerId, projectId) {
|
|
259
|
+
const project = projectId ? this._registry?.getProjectRow?.(projectId) : null;
|
|
260
|
+
if (!project)
|
|
261
|
+
return [];
|
|
262
|
+
const execution = (0, workspace_resolution_1.resolveProjectExecution)(project);
|
|
263
|
+
return (0, codex_spawn_1.resolveCodexPluginMcpServers)({ providerId, stateRoot: execution.cwd, repositoryPath: project.path, legacyProviderId: project.provider });
|
|
264
|
+
}
|
|
265
|
+
/** Rebuild a mission's session projection from Core's journal (operator repair). */
|
|
266
|
+
async rebuildSessionProjection(conversationId) {
|
|
267
|
+
const conversation = (0, agent_store_1.getAgentConversation)(this._db, conversationId);
|
|
268
|
+
if (!conversation || !this._coreSessions)
|
|
269
|
+
return null;
|
|
270
|
+
if (this._isTurnLive(conversationId))
|
|
271
|
+
throw new SessionRebuildBusyError('Wait for the current turn to finish before rebuilding');
|
|
272
|
+
return this._coreSessions.rebuildProjection(conversation);
|
|
273
|
+
}
|
|
197
274
|
notifyConversationCreated(conversationId) {
|
|
198
275
|
this._broadcast({ type: 'agent_conversation_created', conversationId, timestamp: new Date().toISOString() });
|
|
199
276
|
}
|
|
@@ -478,25 +555,31 @@ class AgentChatManager {
|
|
|
478
555
|
const invocationId = (0, crypto_1.randomUUID)();
|
|
479
556
|
currentCapability = agentCapability;
|
|
480
557
|
currentInvocationId = invocationId;
|
|
481
|
-
let mcpArgs;
|
|
482
|
-
let mcpEnv;
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
558
|
+
let mcpArgs = [];
|
|
559
|
+
let mcpEnv = {};
|
|
560
|
+
// Core agent sessions: a resident session presents this turn's capability
|
|
561
|
+
// through structured MCP specs (no provider-specific argv).
|
|
562
|
+
const coreContext = this._coreSessions
|
|
563
|
+
? await this._coreSessions.prepareTurn(conversation, adapter, { capability: agentCapability, external: (0, external_mcp_1.resolveExternalEntries)(adapter.id, this._db), plugins: this._pluginMcpServers(adapter.id, conversation.pinned_project_id) })
|
|
564
|
+
: null;
|
|
565
|
+
if (!coreContext)
|
|
566
|
+
try {
|
|
567
|
+
const wiring = (0, agent_mcp_config_1.prepareAgentMcp)({
|
|
568
|
+
adapterId: adapter.id, conversationId, invocationId, cwd,
|
|
569
|
+
port: this._port, capability: agentCapability,
|
|
570
|
+
external: (0, external_mcp_1.resolveExternalEntries)(adapter.id, this._db),
|
|
571
|
+
});
|
|
572
|
+
mcpArgs = wiring.extraArgs;
|
|
573
|
+
mcpEnv = wiring.env;
|
|
574
|
+
const project = conversation.pinned_project_id ? this._registry?.getProjectRow?.(conversation.pinned_project_id) : null;
|
|
575
|
+
if (adapter.id === 'codex' && project) {
|
|
576
|
+
const execution = (0, workspace_resolution_1.resolveProjectExecution)(project);
|
|
577
|
+
mcpArgs.push(...(0, codex_spawn_1.buildCodexPluginArgs)({ providerId: adapter.id, stateRoot: execution.cwd, repositoryPath: project.path, legacyProviderId: project.provider }));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
throw new Error('Specrails could not prepare its MCP tools. The agent was not launched. Check the app installation and retry. ' + (err instanceof Error ? err.message : String(err)));
|
|
495
582
|
}
|
|
496
|
-
}
|
|
497
|
-
catch (err) {
|
|
498
|
-
throw new Error('Specrails could not prepare its MCP tools. The agent was not launched. Check the app installation and retry. ' + (err instanceof Error ? err.message : String(err)));
|
|
499
|
-
}
|
|
500
583
|
const action = useResume ? 'chat-resume' : 'chat-turn';
|
|
501
584
|
const startedAt = new Date().toISOString();
|
|
502
585
|
let streamed = '';
|
|
@@ -505,7 +588,7 @@ class AgentChatManager {
|
|
|
505
588
|
let capturedSessionId = useResume ? conversation.session_id ?? null : null;
|
|
506
589
|
let capturedError = null;
|
|
507
590
|
console.log(`[agent-chat] turn start conv=${conversationId} provider=${adapter.id} binary=${adapter.binary} ` +
|
|
508
|
-
`action=${action} model=${model} cwd=${cwd} mcp=${mcpArgs.length ? 'mcp-config' : Object.keys(mcpEnv).join(',') || 'none'}`);
|
|
591
|
+
`action=${action} model=${model} cwd=${cwd} mcp=${coreContext ? `core-session:${coreContext.mcpServers.map((server) => server.name).join(',') || 'none'}` : mcpArgs.length ? 'mcp-config' : Object.keys(mcpEnv).join(',') || 'none'}`);
|
|
509
592
|
const buildOpts = {
|
|
510
593
|
prompt: effectivePrompt(useResume),
|
|
511
594
|
systemPrompt: adapter.capabilities.systemPromptArg ? systemPrompt : undefined,
|
|
@@ -515,7 +598,7 @@ class AgentChatManager {
|
|
|
515
598
|
imagePaths: spawnImagePaths,
|
|
516
599
|
reasoning_effort: reasoningEffort,
|
|
517
600
|
};
|
|
518
|
-
const nativeRunner = (0, live_session_1.nativeLiveSessionRunner)(adapter);
|
|
601
|
+
const nativeRunner = coreContext ? (0, core_session_runner_1.createCoreSessionRunner)(coreContext) : (0, live_session_1.nativeLiveSessionRunner)(adapter);
|
|
519
602
|
let invocationLive = true;
|
|
520
603
|
let nativeSink;
|
|
521
604
|
let nativeDrain = Promise.resolve();
|
|
@@ -782,13 +865,13 @@ class AgentChatManager {
|
|
|
782
865
|
// A killed child (abort / conversation DELETE) keeps flushing its
|
|
783
866
|
// buffered stdout — suppress the broadcasts once it left _active
|
|
784
867
|
// so stragglers can't resurrect client-side streaming state.
|
|
785
|
-
if (this.
|
|
868
|
+
if (this._isTurnLive(conversationId)) {
|
|
786
869
|
this._streamingText.set(conversationId, streamed.slice(segmentStart));
|
|
787
870
|
this._broadcast({ type: 'agent_stream', conversationId, delta: ev.text, timestamp: timestamp() });
|
|
788
871
|
}
|
|
789
872
|
break;
|
|
790
873
|
case 'tool-use':
|
|
791
|
-
if (this.
|
|
874
|
+
if (this._isTurnLive(conversationId)) {
|
|
792
875
|
this._broadcast({
|
|
793
876
|
type: 'agent_tool',
|
|
794
877
|
conversationId,
|
|
@@ -802,7 +885,7 @@ class AgentChatManager {
|
|
|
802
885
|
case 'tool-result':
|
|
803
886
|
// Feeds the activity-log modal's output column. Claude-only
|
|
804
887
|
// today; other adapters never emit this kind.
|
|
805
|
-
if (this.
|
|
888
|
+
if (this._isTurnLive(conversationId)) {
|
|
806
889
|
this._broadcast({
|
|
807
890
|
type: 'agent_tool_result',
|
|
808
891
|
conversationId,
|
|
@@ -845,7 +928,10 @@ class AgentChatManager {
|
|
|
845
928
|
this._nativeInputNotifiers.delete(conversationId);
|
|
846
929
|
stopSteering();
|
|
847
930
|
this._steeringCapabilities.delete(conversationId);
|
|
848
|
-
|
|
931
|
+
// A resident Core session keeps presenting this capability for its
|
|
932
|
+
// background turns until the next turn rotates it or Core retires it.
|
|
933
|
+
if (!this._coreSessions?.retains(conversationId, agentCapability))
|
|
934
|
+
(0, agent_capability_1.revokeAgentCapability)(agentCapability);
|
|
849
935
|
(0, agent_mcp_config_1.removeAgentCapabilityFile)(conversationId, invocationId);
|
|
850
936
|
currentCapability = null;
|
|
851
937
|
currentInvocationId = undefined;
|
|
@@ -874,7 +960,8 @@ class AgentChatManager {
|
|
|
874
960
|
if (result.stderrTail && (result.spawnFailed || (result.code ?? 0) !== 0 || !text)) {
|
|
875
961
|
console.error(`[agent-chat] ${adapter.id} stderr:\n${result.stderrTail}`);
|
|
876
962
|
}
|
|
877
|
-
|
|
963
|
+
const coreResult = result;
|
|
964
|
+
return { disposed: false, text, fullText: streamed.trim(), checkpointed, sessionId: capturedSessionId, error: capturedError, code: result.code, spawnFailed: result.spawnFailed, stderrTail: result.stderrTail, events: result.events, startedAt, ...(coreResult.usage ? { usage: coreResult.usage } : {}), ...(coreResult.coreTurnId ? { coreTurnId: coreResult.coreTurnId } : {}), ...(coreResult.errorCode ? { errorCode: coreResult.errorCode } : {}) };
|
|
878
965
|
};
|
|
879
966
|
// Conversation CONFIG (provider/model/tier) is owned by the PATCH route —
|
|
880
967
|
// never write turn-START snapshots back at settle, or a mid-turn provider
|
|
@@ -896,9 +983,9 @@ class AgentChatManager {
|
|
|
896
983
|
// Deliberate user Stop: keep any partial text (it was already streamed to
|
|
897
984
|
// the client), never auto-heal, never surface an error.
|
|
898
985
|
if (r.text && (0, agent_store_1.getAgentConversation)(this._db, conversationId)) {
|
|
899
|
-
const message = (0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'assistant', content: r.text });
|
|
986
|
+
const message = (0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'assistant', content: r.text, ...(r.coreTurnId ? { coreTurnId: r.coreTurnId, turnOrigin: 'user' } : {}) });
|
|
900
987
|
persistSession(r.sessionId);
|
|
901
|
-
this._broadcast({ type: 'agent_done', conversationId, fullText: r.text, messageId: message.id, timestamp: timestamp() });
|
|
988
|
+
this._broadcast({ type: 'agent_done', conversationId, fullText: r.text, messageId: message.id, ...(r.coreTurnId ? { coreTurnId: r.coreTurnId } : {}), timestamp: timestamp() });
|
|
902
989
|
}
|
|
903
990
|
};
|
|
904
991
|
// Cost accounting (HIGH-3): exactly one row per settled turn, whichever
|
|
@@ -964,14 +1051,15 @@ class AgentChatManager {
|
|
|
964
1051
|
const message = (0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'assistant', content: r.text });
|
|
965
1052
|
this._broadcast({ type: 'agent_partial', conversationId, fullText: r.text, messageId: message.id, error: reason, timestamp: timestamp() });
|
|
966
1053
|
}
|
|
967
|
-
this._emitError(conversationId, reason);
|
|
1054
|
+
this._emitError(conversationId, reason, r.errorCode ? { code: r.errorCode, provider: adapter.id } : undefined);
|
|
968
1055
|
record(r, 'failed');
|
|
969
1056
|
return;
|
|
970
1057
|
}
|
|
971
1058
|
if (r.text || r.checkpointed) {
|
|
972
|
-
const message = r.text ? (0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'assistant', content: r.text }) : undefined;
|
|
1059
|
+
const message = r.text ? (0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'assistant', content: r.text, ...(r.coreTurnId ? { coreTurnId: r.coreTurnId, turnOrigin: 'user' } : {}) }) : undefined;
|
|
973
1060
|
persistSession(r.sessionId);
|
|
974
|
-
|
|
1061
|
+
// The Core turn id anchors this turn's sub-agents under the reply in every client.
|
|
1062
|
+
this._broadcast({ type: 'agent_done', conversationId, fullText: r.text, ...(message ? { messageId: message.id } : {}), ...(r.coreTurnId ? { coreTurnId: r.coreTurnId } : {}), timestamp: timestamp() });
|
|
975
1063
|
record(r, 'success');
|
|
976
1064
|
// AI title after the FIRST completed turn (industry standard — ChatGPT /
|
|
977
1065
|
// Claude.ai), on the conversation's own provider. The deterministic title
|
|
@@ -989,7 +1077,7 @@ class AgentChatManager {
|
|
|
989
1077
|
}
|
|
990
1078
|
finally {
|
|
991
1079
|
this._steeringCapabilities.delete(conversationId);
|
|
992
|
-
if (currentCapability)
|
|
1080
|
+
if (currentCapability && !this._coreSessions?.retains(conversationId, currentCapability))
|
|
993
1081
|
(0, agent_capability_1.revokeAgentCapability)(currentCapability);
|
|
994
1082
|
if (currentInvocationId)
|
|
995
1083
|
(0, agent_mcp_config_1.removeAgentCapabilityFile)(conversationId, currentInvocationId);
|
|
@@ -1009,10 +1097,11 @@ class AgentChatManager {
|
|
|
1009
1097
|
return;
|
|
1010
1098
|
try {
|
|
1011
1099
|
const finishedAt = new Date().toISOString();
|
|
1012
|
-
const {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1100
|
+
const finaliseOptions = { fallbackModel: model, durationMs: Math.max(0, Date.parse(finishedAt) - Date.parse(outcome.startedAt)) };
|
|
1101
|
+
// Core sessions report a normalized per-turn delta; never reparse provider frames for them.
|
|
1102
|
+
const { result, estimated } = outcome.usage
|
|
1103
|
+
? (0, result_event_1.finaliseNormalisedResult)(adapter, outcome.usage, finaliseOptions)
|
|
1104
|
+
: (0, result_event_1.finaliseInvocationResult)(adapter, outcome.events, finaliseOptions);
|
|
1016
1105
|
(0, desktop_db_1.recordAgentInvocation)(this._db, {
|
|
1017
1106
|
id: (0, crypto_1.randomUUID)(),
|
|
1018
1107
|
conversation_id: conversation.id,
|
|
@@ -1437,6 +1526,12 @@ class AgentChatManager {
|
|
|
1437
1526
|
(0, agent_capability_1.revokeAgentCapability)(capability);
|
|
1438
1527
|
this._steeringCapabilities.delete(conversationId);
|
|
1439
1528
|
this._nativeInputNotifiers.delete(conversationId);
|
|
1529
|
+
const coreTurn = this._coreSessions?.handle(conversationId);
|
|
1530
|
+
if (coreTurn) {
|
|
1531
|
+
// Stop the turn inside the resident session; never kill a process by PID.
|
|
1532
|
+
void coreTurn.interrupt();
|
|
1533
|
+
return true;
|
|
1534
|
+
}
|
|
1440
1535
|
const child = this._active.get(conversationId);
|
|
1441
1536
|
if (!child)
|
|
1442
1537
|
return false;
|
|
@@ -1478,6 +1573,7 @@ class AgentChatManager {
|
|
|
1478
1573
|
this._closeChildIo(child);
|
|
1479
1574
|
}
|
|
1480
1575
|
this._active.clear();
|
|
1576
|
+
this._coreSessions?.shutdown();
|
|
1481
1577
|
for (const child of this._auxProcesses) {
|
|
1482
1578
|
const reader = this._auxReaders.get(child);
|
|
1483
1579
|
try {
|
|
@@ -1490,10 +1586,11 @@ class AgentChatManager {
|
|
|
1490
1586
|
this._auxReaders.clear();
|
|
1491
1587
|
this._auxProcesses.clear();
|
|
1492
1588
|
}
|
|
1493
|
-
|
|
1589
|
+
/** `detail.code` lets clients explain known failures (e.g. `policy_unenforceable`) in the user's language. */
|
|
1590
|
+
_emitError(conversationId, error, detail) {
|
|
1494
1591
|
if (this._disposed)
|
|
1495
1592
|
return;
|
|
1496
|
-
this._broadcast({ type: 'agent_error', conversationId, error, timestamp: new Date().toISOString() });
|
|
1593
|
+
this._broadcast({ type: 'agent_error', conversationId, error, ...(detail ?? {}), timestamp: new Date().toISOString() });
|
|
1497
1594
|
}
|
|
1498
1595
|
}
|
|
1499
1596
|
exports.AgentChatManager = AgentChatManager;
|
|
@@ -6,8 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.isAgentChatEnabled = isAgentChatEnabled;
|
|
7
7
|
exports.createAgentChatRouter = createAgentChatRouter;
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const agent_session_store_1 = require("./agent-session-store");
|
|
9
10
|
const multer_1 = __importDefault(require("multer"));
|
|
10
11
|
const express_1 = require("express");
|
|
12
|
+
const agent_chat_manager_1 = require("./agent-chat-manager");
|
|
11
13
|
const providers_1 = require("../../../providers");
|
|
12
14
|
const provider_selection_1 = require("../../../provider-selection");
|
|
13
15
|
const agent_tier_1 = require("./agent-tier");
|
|
@@ -213,8 +215,72 @@ function createAgentChatRouter(deps) {
|
|
|
213
215
|
messages: (0, agent_input_store_1.decorateAgentInputMessages)(desktopDb, (0, agent_store_1.listAgentMessages)(desktopDb, conversation.id)),
|
|
214
216
|
pendingMessages: manager.pendingMessages(conversation.id),
|
|
215
217
|
live: manager.conversationLive(conversation.id),
|
|
218
|
+
session: manager.sessionState(conversation.id),
|
|
216
219
|
});
|
|
217
220
|
});
|
|
221
|
+
// ── Sub-agents of a mission's Core agent session (core-agent-sessions-host) ──
|
|
222
|
+
router.get('/conversations/:id/subagents', (req, res) => {
|
|
223
|
+
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
224
|
+
if (!conversation) {
|
|
225
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
res.json({ subagents: (0, agent_session_store_1.listSubagents)(desktopDb, conversation.id), session: manager.sessionState(conversation.id) });
|
|
229
|
+
});
|
|
230
|
+
router.get('/conversations/:id/subagents/:subagentId/events', (req, res) => {
|
|
231
|
+
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
232
|
+
if (!conversation) {
|
|
233
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const after = Number.parseInt(String(req.query.after ?? '0'), 10);
|
|
237
|
+
const limit = Number.parseInt(String(req.query.limit ?? '200'), 10);
|
|
238
|
+
if (!Number.isSafeInteger(after) || after < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 1000) {
|
|
239
|
+
res.status(400).json({ error: 'after must be ≥ 0 and limit between 1 and 1000' });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
res.json((0, agent_session_store_1.pageSubagentEvents)(desktopDb, conversation.id, String(req.params.subagentId), after, limit));
|
|
243
|
+
});
|
|
244
|
+
router.post('/conversations/:id/subagents/stop', async (req, res) => {
|
|
245
|
+
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
246
|
+
if (!conversation) {
|
|
247
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const ids = req.body?.subagentIds;
|
|
251
|
+
if (ids !== undefined && (!Array.isArray(ids) || ids.length > 500 || !ids.every((id) => typeof id === 'string' && id.length > 0 && id.length <= 200))) {
|
|
252
|
+
res.status(400).json({ error: 'subagentIds must be an array of ids' });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
res.json({ stopped: await manager.stopSubagents(conversation.id, ids) });
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
res.status(502).json({ error: err instanceof Error ? err.message : 'Could not stop the sub-agents' });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
router.post('/conversations/:id/session/rebuild', async (req, res) => {
|
|
263
|
+
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
264
|
+
if (!conversation) {
|
|
265
|
+
res.status(404).json({ error: 'Unknown conversation' });
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const result = await manager.rebuildSessionProjection(conversation.id);
|
|
270
|
+
if (!result) {
|
|
271
|
+
res.status(409).json({ error: 'This mission does not run in a Core session' });
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
res.json(result);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
if (err instanceof agent_chat_manager_1.SessionRebuildBusyError) {
|
|
278
|
+
res.status(409).json({ error: err.message });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
res.status(502).json({ error: err instanceof Error ? err.message : 'Could not rebuild the session projection' });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
218
284
|
router.patch('/conversations/:id', (req, res) => {
|
|
219
285
|
const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
|
|
220
286
|
if (!conversation) {
|
|
@@ -289,6 +355,8 @@ function createAgentChatRouter(deps) {
|
|
|
289
355
|
router.delete('/conversations/:id', (req, res) => {
|
|
290
356
|
const id = String(req.params.id);
|
|
291
357
|
manager.abort(id);
|
|
358
|
+
// Must run before the row is deleted: it reads the conversation's scope.
|
|
359
|
+
void manager.closeCoreSession(id).catch((e) => console.error('[agent-chat] Core session close failed:', e));
|
|
292
360
|
(0, transient_children_1.killBackgroundProcessesForChat)(id);
|
|
293
361
|
(0, transient_children_1.purgeBackgroundProcessHistory)({ chatId: id });
|
|
294
362
|
(0, agent_store_1.deleteAgentConversation)(desktopDb, id);
|
|
@@ -546,13 +546,12 @@ the user's language.
|
|
|
546
546
|
a description following the template, at least one acceptance criterion, and
|
|
547
547
|
you have no outstanding question. Then — and only then — emit the complete
|
|
548
548
|
final \`spec-draft\` block one last time, state the one-line short summary you
|
|
549
|
-
will attach, and ask exactly one question: whether to create it.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
answers yes to that render. If they edit, update and re-render.
|
|
549
|
+
will attach, and ask exactly one question: whether to create it. The
|
|
550
|
+
Contract Layer is always added: never ask about it, never offer to skip it and
|
|
551
|
+
never present it as an option (the user does not want that question). Pass
|
|
552
|
+
\`contractRefine: false\` only when the user asked for no contract layer on
|
|
553
|
+
their own initiative. Do not call any persisting tool until the user answers
|
|
554
|
+
yes to that render. If they edit, update and re-render.
|
|
556
555
|
|
|
557
556
|
**Pausing.** There is no draft-ticket path for you (\`save_draft\` needs a real
|
|
558
557
|
app Explore conversation). If the user pauses, summarize the full draft state
|
|
@@ -569,8 +568,8 @@ summary later.
|
|
|
569
568
|
author — the same post-persist enrichment the app's Add Spec runs. After the
|
|
570
569
|
spec lands, one short background AI pass appends a \`## Contract Layer\`
|
|
571
570
|
section (naming contract, data shapes, state machine, invariants, file touch
|
|
572
|
-
list).
|
|
573
|
-
|
|
571
|
+
list). Never ask whether to add it. Pass \`contractRefine: false\` only when
|
|
572
|
+
the user asked, unprompted, for no contract layer or zero AI spend. It requires a structured-action provider (currently
|
|
574
573
|
Claude), respects the app-wide kill switch, and leaves the spec unenriched when
|
|
575
574
|
it cannot run.
|
|
576
575
|
- The enrichment is asynchronous: the commit returns immediately and the
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSessionCursor = getSessionCursor;
|
|
4
|
+
exports.listActiveSessionCursors = listActiveSessionCursors;
|
|
5
|
+
exports.ensureSessionCursor = ensureSessionCursor;
|
|
6
|
+
exports.advanceSessionCursor = advanceSessionCursor;
|
|
7
|
+
exports.setResidentState = setResidentState;
|
|
8
|
+
exports.resetSessionProjection = resetSessionProjection;
|
|
9
|
+
exports.coreTurnInvocationId = coreTurnInvocationId;
|
|
10
|
+
exports.isCoreTurnRecorded = isCoreTurnRecorded;
|
|
11
|
+
exports.upsertSubagent = upsertSubagent;
|
|
12
|
+
exports.appendSubagentEvent = appendSubagentEvent;
|
|
13
|
+
exports.listSubagents = listSubagents;
|
|
14
|
+
exports.pageSubagentEvents = pageSubagentEvents;
|
|
15
|
+
function getSessionCursor(db, conversationId) {
|
|
16
|
+
const row = db.prepare('SELECT * FROM agent_session_cursors WHERE conversation_id = ?').get(conversationId);
|
|
17
|
+
if (!row)
|
|
18
|
+
return null;
|
|
19
|
+
return {
|
|
20
|
+
conversationId: String(row.conversation_id),
|
|
21
|
+
coreSessionId: String(row.core_session_id),
|
|
22
|
+
scope: String(row.scope),
|
|
23
|
+
lastSeq: Number(row.last_seq),
|
|
24
|
+
residentPhase: row.resident_phase,
|
|
25
|
+
processAlive: Number(row.process_alive) === 1,
|
|
26
|
+
liveSubagents: Number(row.live_subagents),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Missions whose Core session still has work in flight (non-idle or live sub-agents). */
|
|
30
|
+
function listActiveSessionCursors(db) {
|
|
31
|
+
const rows = db.prepare("SELECT conversation_id FROM agent_session_cursors WHERE resident_phase != 'idle' OR live_subagents > 0").all();
|
|
32
|
+
return rows.map((row) => getSessionCursor(db, row.conversation_id)).filter((row) => row !== null);
|
|
33
|
+
}
|
|
34
|
+
function ensureSessionCursor(db, conversationId, coreSessionId, scope) {
|
|
35
|
+
db.prepare(`INSERT INTO agent_session_cursors (conversation_id, core_session_id, scope) VALUES (?, ?, ?)
|
|
36
|
+
ON CONFLICT(conversation_id) DO UPDATE SET core_session_id = excluded.core_session_id, scope = excluded.scope`).run(conversationId, coreSessionId, scope);
|
|
37
|
+
}
|
|
38
|
+
function advanceSessionCursor(db, conversationId, seq) {
|
|
39
|
+
db.prepare("UPDATE agent_session_cursors SET last_seq = ?, updated_at = datetime('now') WHERE conversation_id = ?").run(seq, conversationId);
|
|
40
|
+
}
|
|
41
|
+
function setResidentState(db, conversationId, phase, processAlive, liveSubagents) {
|
|
42
|
+
db.prepare("UPDATE agent_session_cursors SET resident_phase = ?, process_alive = ?, live_subagents = ?, updated_at = datetime('now') WHERE conversation_id = ?")
|
|
43
|
+
.run(phase, processAlive ? 1 : 0, liveSubagents, conversationId);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Drop a mission's derived session rows so the journal can be replayed into
|
|
47
|
+
* them. Messages and invocations stay: replay recognizes turns it already
|
|
48
|
+
* recorded (see `coreTurnInvocationId`).
|
|
49
|
+
*/
|
|
50
|
+
function resetSessionProjection(db, conversationId) {
|
|
51
|
+
db.transaction(() => {
|
|
52
|
+
db.prepare('DELETE FROM agent_subagent_events WHERE conversation_id = ?').run(conversationId);
|
|
53
|
+
db.prepare('DELETE FROM agent_subagents WHERE conversation_id = ?').run(conversationId);
|
|
54
|
+
db.prepare("UPDATE agent_session_cursors SET last_seq = 0, resident_phase = 'idle', process_alive = 0, live_subagents = 0, updated_at = datetime('now') WHERE conversation_id = ?").run(conversationId);
|
|
55
|
+
})();
|
|
56
|
+
}
|
|
57
|
+
/** Deterministic invocation id of a Core turn: recording it twice is a no-op. */
|
|
58
|
+
function coreTurnInvocationId(conversationId, turnId) {
|
|
59
|
+
return `core-turn:${conversationId}:${turnId}`;
|
|
60
|
+
}
|
|
61
|
+
function isCoreTurnRecorded(db, conversationId, turnId) {
|
|
62
|
+
return !!db.prepare('SELECT 1 FROM agent_invocations WHERE id = ?').get(coreTurnInvocationId(conversationId, turnId))
|
|
63
|
+
|| !!db.prepare('SELECT 1 FROM agent_messages WHERE conversation_id = ? AND core_turn_id = ?').get(conversationId, turnId);
|
|
64
|
+
}
|
|
65
|
+
function upsertSubagent(db, conversationId, view) {
|
|
66
|
+
db.prepare(`INSERT INTO agent_subagents (conversation_id, subagent_id, parent_id, kind, agent_type, description, phase, reason, restarts, launched_turn_id, started_at, ended_at, usage_json, tool_uses, duration_ms, result_summary, delegated_driver, delegated_model, updated_at)
|
|
67
|
+
VALUES (@conversationId, @subagentId, @parentId, @kind, @agentType, @description, @phase, @reason, @restarts, @launchedInTurnId, @startedAt, @endedAt, @usage, @toolUses, @durationMs, @resultSummary, @delegatedDriver, @delegatedModel, datetime('now'))
|
|
68
|
+
ON CONFLICT(conversation_id, subagent_id) DO UPDATE SET
|
|
69
|
+
phase = excluded.phase, reason = excluded.reason, restarts = excluded.restarts, ended_at = excluded.ended_at,
|
|
70
|
+
usage_json = excluded.usage_json, tool_uses = excluded.tool_uses, duration_ms = excluded.duration_ms,
|
|
71
|
+
result_summary = excluded.result_summary, updated_at = excluded.updated_at`).run({
|
|
72
|
+
conversationId,
|
|
73
|
+
subagentId: view.subagentId,
|
|
74
|
+
parentId: view.parentId,
|
|
75
|
+
kind: view.kind,
|
|
76
|
+
agentType: view.agentType,
|
|
77
|
+
description: view.description,
|
|
78
|
+
phase: view.phase,
|
|
79
|
+
reason: view.reason,
|
|
80
|
+
restarts: view.restarts,
|
|
81
|
+
launchedInTurnId: view.launchedInTurnId,
|
|
82
|
+
startedAt: view.startedAt,
|
|
83
|
+
endedAt: view.endedAt,
|
|
84
|
+
usage: view.usage ? JSON.stringify(view.usage) : null,
|
|
85
|
+
toolUses: view.toolUses,
|
|
86
|
+
durationMs: view.durationMs,
|
|
87
|
+
resultSummary: view.resultSummary,
|
|
88
|
+
delegatedDriver: view.delegated?.driver ?? null,
|
|
89
|
+
delegatedModel: view.delegated?.model ?? null,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function appendSubagentEvent(db, conversationId, subagentId, seq, channel, delta, tool) {
|
|
93
|
+
db.prepare('INSERT OR IGNORE INTO agent_subagent_events (conversation_id, subagent_id, seq, channel, delta, tool_json) VALUES (?, ?, ?, ?, ?, ?)')
|
|
94
|
+
.run(conversationId, subagentId, seq, channel, delta, tool ? JSON.stringify(tool) : null);
|
|
95
|
+
}
|
|
96
|
+
function mapSubagent(row) {
|
|
97
|
+
return {
|
|
98
|
+
conversationId: String(row.conversation_id),
|
|
99
|
+
subagentId: String(row.subagent_id),
|
|
100
|
+
parentId: row.parent_id ?? null,
|
|
101
|
+
kind: row.kind,
|
|
102
|
+
agentType: row.agent_type ?? null,
|
|
103
|
+
description: String(row.description),
|
|
104
|
+
phase: row.phase,
|
|
105
|
+
reason: row.reason ?? null,
|
|
106
|
+
restarts: Number(row.restarts),
|
|
107
|
+
startedAt: String(row.started_at),
|
|
108
|
+
endedAt: row.ended_at ?? null,
|
|
109
|
+
usage: row.usage_json ? JSON.parse(String(row.usage_json)) : null,
|
|
110
|
+
toolUses: row.tool_uses === null ? null : Number(row.tool_uses),
|
|
111
|
+
durationMs: row.duration_ms === null ? null : Number(row.duration_ms),
|
|
112
|
+
resultSummary: row.result_summary ?? null,
|
|
113
|
+
launchedInTurnId: row.launched_turn_id ?? null,
|
|
114
|
+
delegated: row.delegated_driver ? { driver: String(row.delegated_driver), model: String(row.delegated_model ?? '') } : null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function listSubagents(db, conversationId) {
|
|
118
|
+
return db.prepare('SELECT * FROM agent_subagents WHERE conversation_id = ? ORDER BY started_at, rowid').all(conversationId).map(mapSubagent);
|
|
119
|
+
}
|
|
120
|
+
function pageSubagentEvents(db, conversationId, subagentId, afterSeq, limit) {
|
|
121
|
+
const rows = db.prepare('SELECT seq, channel, delta, tool_json, created_at FROM agent_subagent_events WHERE conversation_id = ? AND subagent_id = ? AND seq > ? ORDER BY seq LIMIT ?')
|
|
122
|
+
.all(conversationId, subagentId, afterSeq, limit + 1);
|
|
123
|
+
return {
|
|
124
|
+
events: rows.slice(0, limit).map((row) => ({
|
|
125
|
+
seq: Number(row.seq),
|
|
126
|
+
channel: row.channel,
|
|
127
|
+
delta: row.delta ?? null,
|
|
128
|
+
tool: row.tool_json ? JSON.parse(String(row.tool_json)) : null,
|
|
129
|
+
createdAt: String(row.created_at),
|
|
130
|
+
})),
|
|
131
|
+
hasMore: rows.length > limit,
|
|
132
|
+
};
|
|
133
|
+
}
|