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
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionHostRegistry = void 0;
|
|
4
|
+
const session_event_pump_1 = require("../application/session-event-pump");
|
|
5
|
+
const errors_1 = require("../domain/errors");
|
|
6
|
+
const host_state_1 = require("../domain/host-state");
|
|
7
|
+
/** Failures a restart cannot fix: the scope degrades at once instead of looping. */
|
|
8
|
+
const FATAL_CODES = new Set(['journal_locked', 'protocol_mismatch', 'driver_unavailable']);
|
|
9
|
+
/**
|
|
10
|
+
* One supervised Core session host per scope (project key or `global`).
|
|
11
|
+
* Hosts start lazily, restart with backoff after unexpected exits, re-open and
|
|
12
|
+
* replay tracked sessions, and become degraded after repeated failures so
|
|
13
|
+
* callers can fall back to legacy transports for new turns.
|
|
14
|
+
*/
|
|
15
|
+
class SessionHostRegistry {
|
|
16
|
+
options;
|
|
17
|
+
scopes = new Map();
|
|
18
|
+
stopped = false;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
status(scope) {
|
|
23
|
+
return this.scopes.get(scope)?.state.status ?? 'absent';
|
|
24
|
+
}
|
|
25
|
+
/** Every known scope with its status (operator view, UI notices). */
|
|
26
|
+
hosts() {
|
|
27
|
+
return [...this.scopes].map(([scope, entry]) => ({ scope, status: entry.state.status, detail: entry.degradedReason, code: entry.degradedCode }));
|
|
28
|
+
}
|
|
29
|
+
/** Why a degraded scope is unavailable, when known. */
|
|
30
|
+
degradedCode(scope) {
|
|
31
|
+
return this.scopes.get(scope)?.degradedCode ?? null;
|
|
32
|
+
}
|
|
33
|
+
/** Whether new turns in this scope should use Core sessions now. */
|
|
34
|
+
available(scope) {
|
|
35
|
+
return !this.stopped && (0, host_state_1.acceptsSessions)(this.scopes.get(scope)?.state ?? host_state_1.INITIAL_HOST_STATE);
|
|
36
|
+
}
|
|
37
|
+
/** A ready client for the scope, starting the host when needed. */
|
|
38
|
+
async acquire(scope) {
|
|
39
|
+
if (this.stopped)
|
|
40
|
+
throw new errors_1.SessionRequestError('Session hosts are shutting down', { code: 'busy', retryable: true });
|
|
41
|
+
const entry = this.entry(scope);
|
|
42
|
+
if (entry.client && !this.isClosed(entry.client))
|
|
43
|
+
return entry.client;
|
|
44
|
+
if (entry.state.status === 'degraded') {
|
|
45
|
+
throw new errors_1.SessionRequestError(`Core sessions are unavailable for ${scope}: ${entry.degradedReason ?? 'repeated host failures'}`, { code: 'host_degraded', retryable: false });
|
|
46
|
+
}
|
|
47
|
+
if (entry.starting)
|
|
48
|
+
return entry.starting;
|
|
49
|
+
if (entry.state.status === 'ready') {
|
|
50
|
+
// The client closed before its close event was processed: count it as a failure.
|
|
51
|
+
this.fail(scope, entry, 'Session host connection closed');
|
|
52
|
+
if (this.status(scope) === 'degraded')
|
|
53
|
+
return this.acquire(scope);
|
|
54
|
+
}
|
|
55
|
+
if (entry.state.status === 'restarting') {
|
|
56
|
+
// A caller needs the host now: start immediately instead of waiting for the backoff timer.
|
|
57
|
+
entry.restartTimer?.cancel();
|
|
58
|
+
entry.restartTimer = null;
|
|
59
|
+
entry.state = (0, host_state_1.onRestartDue)(entry.state).state;
|
|
60
|
+
}
|
|
61
|
+
else if (entry.state.status === 'absent') {
|
|
62
|
+
entry.state = (0, host_state_1.onDemand)(entry.state).state;
|
|
63
|
+
}
|
|
64
|
+
return this.start(scope, entry);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Follow a session: its events are applied to `sink` and reported to
|
|
68
|
+
* `listener`. Survives host restarts (resume + replay from the sink cursor).
|
|
69
|
+
*/
|
|
70
|
+
async track(scope, sessionId, sink, listener) {
|
|
71
|
+
const client = await this.acquire(scope);
|
|
72
|
+
const entry = this.entry(scope);
|
|
73
|
+
const tracked = entry.tracked.get(sessionId) ?? { sessionId, sink, listener, pump: null };
|
|
74
|
+
entry.tracked.set(sessionId, tracked);
|
|
75
|
+
tracked.pump?.detach();
|
|
76
|
+
tracked.pump = new session_event_pump_1.SessionEventPump(sessionId, client, sink, listener);
|
|
77
|
+
await tracked.pump.attach();
|
|
78
|
+
return tracked.pump;
|
|
79
|
+
}
|
|
80
|
+
untrack(scope, sessionId) {
|
|
81
|
+
const entry = this.scopes.get(scope);
|
|
82
|
+
const tracked = entry?.tracked.get(sessionId);
|
|
83
|
+
tracked?.pump?.detach();
|
|
84
|
+
entry?.tracked.delete(sessionId);
|
|
85
|
+
}
|
|
86
|
+
/** Manual retry of a degraded scope. */
|
|
87
|
+
async retry(scope) {
|
|
88
|
+
const entry = this.entry(scope);
|
|
89
|
+
const result = (0, host_state_1.onRetry)(entry.state);
|
|
90
|
+
if (result.decision.action !== 'start')
|
|
91
|
+
return;
|
|
92
|
+
entry.state = result.state;
|
|
93
|
+
entry.degradedReason = null;
|
|
94
|
+
entry.degradedCode = null;
|
|
95
|
+
await this.start(scope, entry);
|
|
96
|
+
}
|
|
97
|
+
/** Graceful stop of one scope (project removed). */
|
|
98
|
+
async stop(scope) {
|
|
99
|
+
const entry = this.scopes.get(scope);
|
|
100
|
+
if (!entry)
|
|
101
|
+
return;
|
|
102
|
+
entry.restartTimer?.cancel();
|
|
103
|
+
entry.state = (0, host_state_1.onStop)(entry.state);
|
|
104
|
+
for (const tracked of entry.tracked.values())
|
|
105
|
+
tracked.pump?.detach();
|
|
106
|
+
for (const unsubscribe of entry.unsubscribe.splice(0))
|
|
107
|
+
unsubscribe();
|
|
108
|
+
const client = entry.client ?? (entry.starting ? await entry.starting.catch(() => null) : null);
|
|
109
|
+
entry.client = null;
|
|
110
|
+
this.options.onStatus?.(scope, 'stopped');
|
|
111
|
+
if (client)
|
|
112
|
+
await client.close();
|
|
113
|
+
this.scopes.delete(scope);
|
|
114
|
+
}
|
|
115
|
+
/** App shutdown. */
|
|
116
|
+
async stopAll() {
|
|
117
|
+
this.stopped = true;
|
|
118
|
+
await Promise.allSettled([...this.scopes.keys()].map((scope) => this.stop(scope)));
|
|
119
|
+
}
|
|
120
|
+
// ── internals ─────────────────────────────────────────────────────────────
|
|
121
|
+
entry(scope) {
|
|
122
|
+
let entry = this.scopes.get(scope);
|
|
123
|
+
if (!entry) {
|
|
124
|
+
entry = { state: host_state_1.INITIAL_HOST_STATE, client: null, starting: null, restartTimer: null, tracked: new Map(), unsubscribe: [], degradedReason: null, degradedCode: null };
|
|
125
|
+
this.scopes.set(scope, entry);
|
|
126
|
+
}
|
|
127
|
+
return entry;
|
|
128
|
+
}
|
|
129
|
+
isClosed(client) {
|
|
130
|
+
return client.closed === true;
|
|
131
|
+
}
|
|
132
|
+
start(scope, entry) {
|
|
133
|
+
this.options.onStatus?.(scope, 'starting');
|
|
134
|
+
entry.starting = (async () => {
|
|
135
|
+
try {
|
|
136
|
+
const client = await this.options.launcher.launch(scope);
|
|
137
|
+
if (entry.state.status === 'stopped') {
|
|
138
|
+
await client.close();
|
|
139
|
+
throw new errors_1.SessionRequestError('Scope stopped', { code: 'busy', retryable: false });
|
|
140
|
+
}
|
|
141
|
+
entry.client = client;
|
|
142
|
+
entry.state = (0, host_state_1.onReady)(entry.state);
|
|
143
|
+
this.wire(scope, entry, client);
|
|
144
|
+
this.options.onStatus?.(scope, 'ready');
|
|
145
|
+
await this.reattach(entry, client);
|
|
146
|
+
return client;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (entry.state.status !== 'stopped') {
|
|
150
|
+
if ((0, errors_1.isSessionRequestError)(error) && FATAL_CODES.has(error.code))
|
|
151
|
+
this.fatal(scope, entry, error.code, error.message);
|
|
152
|
+
else
|
|
153
|
+
this.fail(scope, entry, error.message);
|
|
154
|
+
}
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
entry.starting = null;
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
return entry.starting;
|
|
162
|
+
}
|
|
163
|
+
wire(scope, entry, client) {
|
|
164
|
+
for (const unsubscribe of entry.unsubscribe.splice(0))
|
|
165
|
+
unsubscribe();
|
|
166
|
+
entry.unsubscribe.push(client.onEvent((envelope) => { void entry.tracked.get(envelope.sessionId)?.pump?.push(envelope).catch((error) => this.options.onStatus?.(scope, entry.state.status, `projection failed: ${error.message}`)); }), client.onLagged((sessionId) => { void entry.tracked.get(sessionId)?.pump?.catchUp().catch(() => undefined); }), client.onClose((reason, code) => {
|
|
167
|
+
if (entry.client !== client || entry.state.status === 'stopped')
|
|
168
|
+
return;
|
|
169
|
+
entry.client = null;
|
|
170
|
+
if (code && FATAL_CODES.has(code))
|
|
171
|
+
this.fatal(scope, entry, code, reason);
|
|
172
|
+
else
|
|
173
|
+
this.fail(scope, entry, reason);
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
/** After a (re)start: resume every tracked session and replay from its cursor. */
|
|
177
|
+
async reattach(entry, client) {
|
|
178
|
+
for (const tracked of entry.tracked.values()) {
|
|
179
|
+
tracked.pump?.detach();
|
|
180
|
+
try {
|
|
181
|
+
await client.request('session.open', { resume: { sessionId: tracked.sessionId } });
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
// Closed sessions replay as history; a session Core never opened has nothing to resume.
|
|
185
|
+
if (!(error instanceof errors_1.SessionRequestError) || (error.code !== 'session_closed' && error.code !== 'session_not_found'))
|
|
186
|
+
throw error;
|
|
187
|
+
if (error.code === 'session_not_found')
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
tracked.pump = new session_event_pump_1.SessionEventPump(tracked.sessionId, client, tracked.sink, tracked.listener);
|
|
191
|
+
await tracked.pump.attach();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
fatal(scope, entry, code, reason) {
|
|
195
|
+
const result = (0, host_state_1.onFatal)(entry.state, reason);
|
|
196
|
+
entry.state = result.state;
|
|
197
|
+
if (result.decision.action !== 'degrade')
|
|
198
|
+
return;
|
|
199
|
+
entry.restartTimer?.cancel();
|
|
200
|
+
entry.restartTimer = null;
|
|
201
|
+
entry.degradedReason = reason;
|
|
202
|
+
entry.degradedCode = code;
|
|
203
|
+
this.options.onStatus?.(scope, 'degraded', reason, code);
|
|
204
|
+
}
|
|
205
|
+
fail(scope, entry, reason) {
|
|
206
|
+
const result = (0, host_state_1.onFailure)(entry.state, this.options.clock.now(), this.options.policy ?? host_state_1.DEFAULT_SUPERVISION);
|
|
207
|
+
entry.state = result.state;
|
|
208
|
+
if (result.decision.action === 'degrade') {
|
|
209
|
+
entry.degradedReason = `${result.decision.reason}; last: ${reason}`;
|
|
210
|
+
this.options.onStatus?.(scope, 'degraded', entry.degradedReason);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (result.decision.action === 'restart') {
|
|
214
|
+
this.options.onStatus?.(scope, 'restarting', reason);
|
|
215
|
+
entry.restartTimer?.cancel();
|
|
216
|
+
entry.restartTimer = this.options.clock.after(result.decision.delayMs, () => {
|
|
217
|
+
entry.restartTimer = null;
|
|
218
|
+
const due = (0, host_state_1.onRestartDue)(entry.state);
|
|
219
|
+
entry.state = due.state;
|
|
220
|
+
if (due.decision.action === 'start')
|
|
221
|
+
void this.start(scope, entry).catch(() => undefined);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
exports.SessionHostRegistry = SessionHostRegistry;
|
|
@@ -47,6 +47,8 @@ function mapMessage(row) {
|
|
|
47
47
|
attachment_ids: ids,
|
|
48
48
|
context_refs: parseJsonArray(row.context_refs, isContextRef),
|
|
49
49
|
intents: parseIntents(row.intent),
|
|
50
|
+
...(row.core_turn_id ? { core_turn_id: row.core_turn_id } : {}),
|
|
51
|
+
...(row.turn_origin === 'user' || row.turn_origin === 'subagent' || row.turn_origin === 'system' ? { turn_origin: row.turn_origin } : {}),
|
|
50
52
|
created_at: row.created_at,
|
|
51
53
|
};
|
|
52
54
|
}
|
|
@@ -128,7 +130,7 @@ function addAgentMessage(db, input) {
|
|
|
128
130
|
const id = (0, crypto_1.randomUUID)();
|
|
129
131
|
const attachmentIds = input.attachmentIds && input.attachmentIds.length ? JSON.stringify(input.attachmentIds) : null;
|
|
130
132
|
const contextRefs = input.contextRefs && input.contextRefs.length ? JSON.stringify(input.contextRefs) : null;
|
|
131
|
-
db.prepare('INSERT INTO agent_messages (id, conversation_id, role, content, attachment_ids, context_refs) VALUES (?, ?, ?, ?, ?, ?)').run(id, input.conversationId, input.role, input.content, attachmentIds, contextRefs);
|
|
133
|
+
db.prepare('INSERT INTO agent_messages (id, conversation_id, role, content, attachment_ids, context_refs, turn_origin, core_turn_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(id, input.conversationId, input.role, input.content, attachmentIds, contextRefs, input.turnOrigin ?? null, input.coreTurnId ?? null);
|
|
132
134
|
// Touch the parent so the list stays ordered by latest activity.
|
|
133
135
|
db.prepare("UPDATE agent_conversations SET updated_at = datetime('now') WHERE id = ?").run(input.conversationId);
|
|
134
136
|
return mapMessage(db.prepare('SELECT * FROM agent_messages WHERE id = ?').get(id));
|
|
@@ -48,10 +48,21 @@ function configurableImplementGraph(capabilities) {
|
|
|
48
48
|
node('reset-confidence', 'Reset confidence after answer', 'assign', { set: { planInvestigated: false } }, { next: 'architect', failed: 'failed' });
|
|
49
49
|
node('validate', 'Validate OpenSpec', 'openspec-validate', { change }, { pass: 'freeze', fail: 'architect', failed: 'failed' });
|
|
50
50
|
node('freeze', 'Freeze approved artifacts', 'artifact-contract', { change, contractId: 'plan', action: 'freeze' }, { pass: 'initialize-corrections', fail: 'architect', failed: 'failed' });
|
|
51
|
-
node('initialize-corrections', 'Initialize automatic correction budget', 'assign', { set: { correctionAttempts: 0, correctionLimit: 3 } }, { next: 'developer', failed: 'failed' });
|
|
51
|
+
node('initialize-corrections', 'Initialize automatic correction budget', 'assign', { set: { correctionAttempts: 0, correctionLimit: 3, implementationAttempts: 0, implementationLimit: 3 } }, { next: 'developer', failed: 'failed' });
|
|
52
52
|
const implementation = objectSchema({ summary: string, incomplete: strings, blocker }, ['summary', 'incomplete']);
|
|
53
|
-
turn('developer', 'Developer', 'build', `Implement every approved task for change ${change} using the bound official apply skill. Preserve proposal, design and specs; only tick tasks you completed. Report summary and incomplete task descriptions. Prior execution evidence and human answers: {{history.text}}`, implementation, '
|
|
54
|
-
node('
|
|
53
|
+
turn('developer', 'Developer', 'build', `Implement every approved task for change ${change} using the bound official apply skill. Preserve proposal, design and specs; only tick tasks you completed. Report summary and incomplete task descriptions. If a task cannot be finished because of a host precondition or environment blocker outside this change (for example the sandbox forbids starting a local server, a credential or tool is missing), keep the work done, leave that task unchecked and return the structured blocker (kind, command, cwd, evidence, requiredAction). A failing test, a test you cannot make pass, or unexpected browser or tool behaviour is NOT a host blocker: change the test or the implementation instead. The host confirms a blocker by running its own verification before ending the run; a failure it reproduces comes back as a correction. Prior execution evidence and human answers: {{history.text}}`, implementation, 'developer-blocker', writes);
|
|
54
|
+
node('developer-blocker', 'Developer reported a host blocker', 'condition', { expr: 'exists($outputs.developer.structured.blocker) && $outputs.developer.structured.blocker != null' }, { true: 'developer-blocker-kind', false: 'tasks' });
|
|
55
|
+
// Credentials, network and variables are the person's to provide. Any other
|
|
56
|
+
// blocker is confirmed by the host's own verification: a host precondition
|
|
57
|
+
// ends at host-blocked, a reproduced failure becomes a correction, and
|
|
58
|
+
// passing checks reach the reviewer, whose rejection of unfinished tasks is
|
|
59
|
+
// corrected within the same budget.
|
|
60
|
+
node('developer-blocker-kind', 'Blocker needs the person', 'condition', { expr: ['credential', 'network', 'environment-variable'].map(kind => `$outputs.developer.structured.blocker.kind == "${kind}"`).join(' || ') }, { true: 'developer-blocked', false: 'verify' });
|
|
61
|
+
node('tasks', 'Check artifacts and completed tasks', 'artifact-contract', { change, contractId: 'plan', action: 'check', requireCompletedTasks: true }, { pass: 'verify', fail: 'implementation-budget', failed: 'failed' });
|
|
62
|
+
// Incomplete tasks send the developer back, but only while each attempt changes the candidate and within budget.
|
|
63
|
+
node('implementation-budget', 'Check implementation attempt budget', 'condition', { expr: '$vars.implementationAttempts < $vars.implementationLimit' }, { true: 'implementation-progress', false: 'implementation-exhausted' });
|
|
64
|
+
node('implementation-progress', 'Check implementation progress', 'condition', { expr: '!exists($vars.lastImplementationHash) || $outputs.developer.candidateHash != $vars.lastImplementationHash' }, { true: 'begin-implementation', false: 'implementation-stalled' });
|
|
65
|
+
node('begin-implementation', 'Count implementation attempt', 'assign', { increment: { implementationAttempts: 1 }, set: { lastImplementationHash: '{{outputs.developer.candidateHash}}' } }, { next: 'developer', failed: 'failed' });
|
|
55
66
|
node('verify', 'Verify candidate', 'verify', verifyParams, verifyEnds('reviewer', 'correction-context', 'host-blocked'));
|
|
56
67
|
turn('reviewer', 'Reviewer', 'assess', `Review change ${change} read-only against all approved artifacts and frozen ticket acceptance criteria. Inspect real code and the host verification evidence: {{outputs.verify}}. Return approved, summary, issues, score (0-100), and aspects. Acceptance policy: ${reviewPolicy}. For any rejection or below-threshold score, explain the concrete unmet requirement and actionable repair in summary and issues; passing commands do not prove every acceptance obligation. Prior execution: {{history.text}}`, objectSchema({ approved: { type: 'boolean' }, summary: string, issues: strings, score, aspects: objectSchema(Object.fromEntries(Object.keys(aspects).map(aspect => [aspect, score]))) }), 'review-policy');
|
|
57
68
|
node('review-policy', 'Review acceptance policy', 'condition', { expr: ['$outputs.reviewer.structured.approved == true', '$outputs.reviewer.structured.issues.length == 0', '$outputs.reviewer.structured.score >= 70', ...Object.entries(aspects).map(([aspect, threshold]) => `$outputs.reviewer.structured.aspects.${aspect} >= ${threshold}`)].join(' && ') }, { true: 'approve', false: 'correction-context' });
|
|
@@ -67,7 +78,7 @@ function configurableImplementGraph(capabilities) {
|
|
|
67
78
|
node('approval-policy', 'Approval decision', 'condition', { expr: '$outputs.approve.response.approved == true' }, { true: 'archive', false: 'failed' });
|
|
68
79
|
node('archive', 'Archive OpenSpec', 'openspec-archive', { change, allowArchived: true, requiresVerified: true, reviewedCandidate: '{{outputs.reviewer.candidateHash}}', approvedCandidate: '{{outputs.approve.candidateHash}}' }, { next: 'verify-archive', failed: 'verify' });
|
|
69
80
|
node('verify-archive', 'Verify archived candidate', 'verify', verifyParams, verifyEnds('done', 'failed', 'host-blocked-archive'));
|
|
70
|
-
nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 140 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 360, y: nodes.length * 140 }, data: { outcome: 'failure' } }, { id: 'correction-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction made no candidate changes. Host verification is valid: {{outputs.verify.valid}}. Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}` } }, { id: 'correction-exhausted', type: 'end', position: { x: 1080, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction limit reached ({{run.correctionAttempts}}/{{run.correctionLimit}}). Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}. Host verification: {{outputs.verify}}` } }, { id: 'fixer-blocked', type: 'end', position: { x: 1440, y: nodes.length * 140 }, data: { outcome: 'failure', reason: 'Fixer reported a host blocker ({{outputs.fixer.structured.blocker.kind}}): {{outputs.fixer.structured.blocker.requiredAction}} Evidence: {{outputs.fixer.structured.blocker.evidence}}', ...(hostBlockers ? { blockerFrom: 'fixer' } : {}) } });
|
|
81
|
+
nodes.push({ id: 'done', type: 'end', position: { x: 0, y: nodes.length * 140 }, data: { outcome: 'success', requiresVerified: true } }, { id: 'failed', type: 'end', position: { x: 360, y: nodes.length * 140 }, data: { outcome: 'failure' } }, { id: 'correction-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction made no candidate changes. Host verification is valid: {{outputs.verify.valid}}. Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}` } }, { id: 'correction-exhausted', type: 'end', position: { x: 1080, y: nodes.length * 140 }, data: { outcome: 'failure', reason: `Automatic correction limit reached ({{run.correctionAttempts}}/{{run.correctionLimit}}). Acceptance policy: ${reviewPolicy}. Latest review findings: {{run.latestReview}}. Fixer diagnosis: {{outputs.fixer.structured.summary}}. Host verification: {{outputs.verify}}` } }, { id: 'implementation-stalled', type: 'end', position: { x: 720, y: nodes.length * 140 }, data: { outcome: 'failure', reason: 'The developer made no changes while approved tasks are still incomplete. Incomplete: {{outputs.developer.structured.incomplete}}. Developer summary: {{outputs.developer.structured.summary}}' } }, { id: 'implementation-exhausted', type: 'end', position: { x: 1080, y: nodes.length * 140 }, data: { outcome: 'failure', reason: 'Implementation attempt limit reached ({{run.implementationAttempts}}/{{run.implementationLimit}}) with approved tasks still incomplete. Incomplete: {{outputs.developer.structured.incomplete}}. Developer summary: {{outputs.developer.structured.summary}}' } }, { id: 'developer-blocked', type: 'end', position: { x: 1440, y: nodes.length * 140 }, data: { outcome: 'failure', reason: 'Developer reported a host blocker ({{outputs.developer.structured.blocker.kind}}): {{outputs.developer.structured.blocker.requiredAction}} Evidence: {{outputs.developer.structured.blocker.evidence}}', ...(hostBlockers ? { blockerFrom: 'developer' } : {}) } }, { id: 'fixer-blocked', type: 'end', position: { x: 1440, y: nodes.length * 140 }, data: { outcome: 'failure', reason: 'Fixer reported a host blocker ({{outputs.fixer.structured.blocker.kind}}): {{outputs.fixer.structured.blocker.requiredAction}} Evidence: {{outputs.fixer.structured.blocker.evidence}}', ...(hostBlockers ? { blockerFrom: 'fixer' } : {}) } });
|
|
71
82
|
if (hostBlockers) {
|
|
72
83
|
const hostBlocked = (id, source) => ({ id, type: 'end', position: { x: 1800, y: nodes.length * 140 }, data: { outcome: 'failure', blockerFrom: source, reason: `Host blocker ({{outputs.${source}.blocker.kind}}): {{outputs.${source}.blocker.reason}} Required action: {{outputs.${source}.blocker.requiredAction}}` } });
|
|
73
84
|
nodes.push(hostBlocked('host-blocked', 'verify'), hostBlocked('host-blocked-archive', 'verify-archive'));
|
|
@@ -756,6 +756,10 @@ class LoopRunManager {
|
|
|
756
756
|
throw new Error('runtime_run_completed: Fork a completed workflow to continue');
|
|
757
757
|
this._cancelled.delete(runId);
|
|
758
758
|
this.claimDefinition({ ...frozen.request, runId });
|
|
759
|
+
// Cards and chips that showed the restart/interruption pause follow the resumed run live.
|
|
760
|
+
const request = frozen.request;
|
|
761
|
+
this._emit({ type: 'loop.run_resumed', projectId: request.projectId, loopRunId: runId, railIndex: request.railIndex ?? null,
|
|
762
|
+
ticketIds: request.spec?.ticketIds ?? (request.ticketId == null ? [] : [request.ticketId]) });
|
|
759
763
|
const task = this._run(frozen.request, { resume: true, ...input }).finally(() => { this.releaseDefinition(runId); this._definitionTasks.delete(runId); });
|
|
760
764
|
this._definitionTasks.set(runId, task);
|
|
761
765
|
return task;
|