@myagentroam/node 0.11.45 → 0.11.47
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/dist/connector.js +4 -4
- package/dist/database.js +7 -5
- package/dist/marctl-guidance.js +2 -2
- package/dist/migrations/v023.js +25 -0
- package/dist/runner/abstract-runner.js +1 -1
- package/dist/runner/claude/managed-run-controller.js +1 -1
- package/dist/runner/claude-code-runner.js +1 -1
- package/dist/runner/codex/managed-run-controller.js +1 -1
- package/dist/runner/codex-runner.js +10 -5
- package/dist/runner/mar-agent/agent-network-extension.js +54 -14
- package/dist/runner/mar-agent/conversation-normalizer.js +43 -3
- package/dist/runner/mar-agent/history-projection.js +1 -1
- package/dist/runner/mar-agent/hosted-conversation-runtime.js +1 -1
- package/dist/runner/mar-agent/managed-run-controller.js +2 -2
- package/dist/runner/mar-agent-runner.js +5 -5
- package/dist/runner/opencode/managed-run-controller.js +2 -2
- package/dist/runner/opencode-runner.js +5 -5
- package/dist/runner-profiles.js +1 -1
- package/dist/runtime-state.js +7 -7
- package/dist/service/agent-network-bindings.js +8 -7
- package/dist/service/agent-network-invoke.js +9 -9
- package/dist/service/agent-network-service.js +12 -7
- package/dist/service/continuous-run-coordinator.js +1 -1
- package/dist/service/external-session-resume-service.js +1 -1
- package/dist/service/goal-continuation-coordinator.js +3 -3
- package/dist/service/native-session-watch-service.js +6 -3
- package/dist/service/planned-restart-handoff.js +24 -5
- package/dist/service/restart-session-restorer.js +2 -2
- package/dist/service/session-recent-activity.js +1 -1
- package/dist/session/native-session-projection-service.js +39 -30
- package/dist/session/session-catalog-service.js +116 -90
- package/dist/session/session-command-service.js +1 -0
- package/dist/session/session-context-service.js +1 -1
- package/dist/session/session-lifecycle-service.js +12 -10
- package/dist/session/session-message-service.js +4 -3
- package/dist/workspace/workspace-session-service.js +1 -1
- package/package.json +4 -4
package/dist/runtime-state.js
CHANGED
|
@@ -103,7 +103,7 @@ export class NodeRuntimeState {
|
|
|
103
103
|
nativeControl: input.externalSessionId === undefined ? 'MAR_MANAGED' : (input.nativeControl ?? 'EXTERNAL'),
|
|
104
104
|
channelToken: null,
|
|
105
105
|
cwd: input.cwd,
|
|
106
|
-
|
|
106
|
+
modelId: input.modelId ?? null,
|
|
107
107
|
effort: input.effort ?? null,
|
|
108
108
|
access: input.access ?? null,
|
|
109
109
|
customTitle,
|
|
@@ -139,7 +139,7 @@ export class NodeRuntimeState {
|
|
|
139
139
|
nativeControl: input.externalSessionId === undefined ? 'MAR_MANAGED' : 'EXTERNAL',
|
|
140
140
|
channelToken: null,
|
|
141
141
|
cwd: input.cwd,
|
|
142
|
-
|
|
142
|
+
modelId: null,
|
|
143
143
|
effort: null,
|
|
144
144
|
access: null,
|
|
145
145
|
customTitle: null,
|
|
@@ -214,7 +214,7 @@ export class NodeRuntimeState {
|
|
|
214
214
|
}
|
|
215
215
|
updateAgentSessionConfiguration(input) {
|
|
216
216
|
return this.replaceSession(input.sessionId, {
|
|
217
|
-
...(input.
|
|
217
|
+
...(input.modelId === undefined ? {} : { modelId: input.modelId }),
|
|
218
218
|
...(input.effort === undefined ? {} : { effort: input.effort }),
|
|
219
219
|
...(input.access === undefined ? {} : { access: input.access })
|
|
220
220
|
});
|
|
@@ -456,7 +456,7 @@ export class NodeRuntimeState {
|
|
|
456
456
|
runId: run.id,
|
|
457
457
|
userItemId: user.id,
|
|
458
458
|
status: 'PREPARING',
|
|
459
|
-
model: session.
|
|
459
|
+
model: session.modelId,
|
|
460
460
|
effort: session.effort,
|
|
461
461
|
access: session.access,
|
|
462
462
|
items: [user],
|
|
@@ -1059,7 +1059,7 @@ export class NodeRuntimeState {
|
|
|
1059
1059
|
runId: null,
|
|
1060
1060
|
userItemId: null,
|
|
1061
1061
|
status: input.failed ? 'FAILED' : 'SUCCEEDED',
|
|
1062
|
-
model: session.
|
|
1062
|
+
model: session.modelId,
|
|
1063
1063
|
effort: session.effort,
|
|
1064
1064
|
access: session.access,
|
|
1065
1065
|
items: [],
|
|
@@ -1374,7 +1374,7 @@ export class NodeRuntimeState {
|
|
|
1374
1374
|
runId: run.id,
|
|
1375
1375
|
userItemId: null,
|
|
1376
1376
|
status: terminal(run.status) ? run.status : 'RUNNING',
|
|
1377
|
-
model: session.
|
|
1377
|
+
model: session.modelId,
|
|
1378
1378
|
effort: session.effort,
|
|
1379
1379
|
access: session.access,
|
|
1380
1380
|
items: [],
|
|
@@ -1416,7 +1416,7 @@ export class NodeRuntimeState {
|
|
|
1416
1416
|
runId: run.id,
|
|
1417
1417
|
userItemId: userId,
|
|
1418
1418
|
status: 'PREPARING',
|
|
1419
|
-
model: session.
|
|
1419
|
+
model: session.modelId,
|
|
1420
1420
|
effort: session.effort,
|
|
1421
1421
|
access: session.access,
|
|
1422
1422
|
items: [user],
|
|
@@ -167,9 +167,9 @@ export function createNodeAgentNetwork(options) {
|
|
|
167
167
|
const workspace = workspaceId === undefined ? undefined : database?.getWorkspace(workspaceId);
|
|
168
168
|
if (!runnerAdapter || !database || !workspace)
|
|
169
169
|
throw new Error('SESSION_INVALID');
|
|
170
|
-
const model = input.
|
|
171
|
-
database.getRunnerDefaults().find((entry) => entry.runner === runner)?.
|
|
172
|
-
runnerAdapter.defaultConfiguration(workspace, actor.secretEnvironment ?? {}, runnerAdapter.prepareExecutionContext(actor.runnerConfiguration ?? {})).
|
|
170
|
+
const model = input.modelId ??
|
|
171
|
+
database.getRunnerDefaults().find((entry) => entry.runner === runner)?.modelId ??
|
|
172
|
+
runnerAdapter.defaultConfiguration(workspace, actor.secretEnvironment ?? {}, runnerAdapter.prepareExecutionContext(actor.runnerConfiguration ?? {})).modelId;
|
|
173
173
|
const request = {
|
|
174
174
|
...executionInput(actor, runner, model, parent !== undefined),
|
|
175
175
|
workspaceId: workspace.id,
|
|
@@ -182,7 +182,7 @@ export function createNodeAgentNetwork(options) {
|
|
|
182
182
|
__suppressNotifications: true,
|
|
183
183
|
...(!terminal && parent ? { __marctlOrigin: origin(parent) } : {}),
|
|
184
184
|
...(input.title === undefined ? {} : { title: input.title }),
|
|
185
|
-
...(model ? { model } : {}),
|
|
185
|
+
...(model ? { modelId: model } : {}),
|
|
186
186
|
...(input.effort === undefined ? {} : { effort: input.effort }),
|
|
187
187
|
...(input.attachments === undefined ? {} : { attachments: input.attachments }),
|
|
188
188
|
...(input.attachmentIds === undefined ? {} : { attachmentIds: input.attachmentIds })
|
|
@@ -229,7 +229,7 @@ export function createNodeAgentNetwork(options) {
|
|
|
229
229
|
const current = await options.resume.resolveForMessage(session.id);
|
|
230
230
|
const executionContext = options.runners
|
|
231
231
|
.require(current.runner)
|
|
232
|
-
.prepareExecutionContext(executionInput(actor, current.runner, current.
|
|
232
|
+
.prepareExecutionContext(executionInput(actor, current.runner, current.modelId, current.parentSessionId != null));
|
|
233
233
|
signal?.throwIfAborted();
|
|
234
234
|
const result = await options.withGrant(actor, () => options.coordinator.start({
|
|
235
235
|
session: current,
|
|
@@ -262,7 +262,7 @@ export function createNodeAgentNetwork(options) {
|
|
|
262
262
|
const parent = terminalCalls.has(actor) ? undefined : await actorSession(actor);
|
|
263
263
|
signal?.throwIfAborted();
|
|
264
264
|
const result = await options.withGrant(actor, () => options.messages.operations()['session.message']({
|
|
265
|
-
...executionInput(actor, session.runner, session.
|
|
265
|
+
...executionInput(actor, session.runner, session.modelId, session.parentSessionId != null),
|
|
266
266
|
...input,
|
|
267
267
|
...(signal ? { [SESSION_ADMISSION_SIGNAL]: signal } : {}),
|
|
268
268
|
sessionId: session.id,
|
|
@@ -383,10 +383,11 @@ export function createNodeAgentNetwork(options) {
|
|
|
383
383
|
truncated: page.nextCursor !== null
|
|
384
384
|
};
|
|
385
385
|
},
|
|
386
|
-
view: async (actor, input) => {
|
|
386
|
+
view: async (actor, input, onResolved) => {
|
|
387
387
|
const source = terminalCalls.has(actor) ? undefined : await actorSession(actor);
|
|
388
388
|
const session = input.agentId === source?.id ? source : await target(actor, input.agentId);
|
|
389
389
|
assertSessionControlOwner(options.runtime, session.id, actor.requestUserId);
|
|
390
|
+
await onResolved?.([session]);
|
|
390
391
|
return {
|
|
391
392
|
session: marctlSessionDetail(options.runtime, session, options.runtime.sessionQueue(session.id).itemIds.length),
|
|
392
393
|
goal: publicGoal(options.goals.get(session.id)) ?? null,
|
|
@@ -4,9 +4,9 @@ import { randomUUID } from 'node:crypto';
|
|
|
4
4
|
import { modelUsageGrantSchema } from '@myagentroam/protocol';
|
|
5
5
|
/** Bind each native invocation to the actual live Node Execution and its captured resources. */
|
|
6
6
|
export function agentNetworkInvoke(input) {
|
|
7
|
-
return async (executionId, action, payload, context) => {
|
|
7
|
+
return async (executionId, action, payload, context, onResolved) => {
|
|
8
8
|
const actor = executionAgentActor({ ...input, executionId });
|
|
9
|
-
return invokeAgentNetwork(input.service, actor, action, payload, context);
|
|
9
|
+
return invokeAgentNetwork(input.service, actor, action, payload, context, undefined, onResolved);
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
/** Read actor resources from the current Node-owned Execution, not caller arguments. */
|
|
@@ -51,7 +51,7 @@ export function executionAgentActor(input) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
/** Shared command dispatch; callers must bind actor authority at their trusted boundary. */
|
|
54
|
-
export async function invokeAgentNetwork(service, actor, action, payload, context, requestSignal) {
|
|
54
|
+
export async function invokeAgentNetwork(service, actor, action, payload, context, requestSignal, onResolved) {
|
|
55
55
|
const signal = context?.signal ?? requestSignal;
|
|
56
56
|
switch (action) {
|
|
57
57
|
case 'start':
|
|
@@ -63,16 +63,16 @@ export async function invokeAgentNetwork(service, actor, action, payload, contex
|
|
|
63
63
|
typeof payload.title === 'string' &&
|
|
64
64
|
!payload.title.trim()) {
|
|
65
65
|
const { title: _title, ...withoutTitle } = payload;
|
|
66
|
-
return service.start(actor, withoutTitle, signal);
|
|
66
|
+
return service.start(actor, withoutTitle, signal, onResolved);
|
|
67
67
|
}
|
|
68
68
|
return service.start(actor, {
|
|
69
69
|
...payload,
|
|
70
70
|
...(typeof payload.title === 'string' ? { title: payload.title.trim() } : {})
|
|
71
|
-
}, signal);
|
|
71
|
+
}, signal, onResolved);
|
|
72
72
|
case 'send':
|
|
73
|
-
return service.send(actor, payload, signal);
|
|
73
|
+
return service.send(actor, payload, signal, onResolved);
|
|
74
74
|
case 'stop':
|
|
75
|
-
return service.stop(actor, payload);
|
|
75
|
+
return service.stop(actor, payload, onResolved);
|
|
76
76
|
case 'wait':
|
|
77
77
|
return service.wait(actor, {
|
|
78
78
|
...payload,
|
|
@@ -81,11 +81,11 @@ export async function invokeAgentNetwork(service, actor, action, payload, contex
|
|
|
81
81
|
: requestSignal
|
|
82
82
|
? { signal: requestSignal }
|
|
83
83
|
: {})
|
|
84
|
-
});
|
|
84
|
+
}, onResolved);
|
|
85
85
|
case 'list':
|
|
86
86
|
return service.list(actor, payload);
|
|
87
87
|
case 'view':
|
|
88
|
-
return service.view(actor, payload);
|
|
88
|
+
return service.view(actor, payload, onResolved);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
/** Node broker injects the caller only after validating the Server-issued Execution credential. */
|
|
@@ -12,10 +12,10 @@ export class AgentNetworkService {
|
|
|
12
12
|
list(actor, input = {}) {
|
|
13
13
|
return this.options.list(actor, input);
|
|
14
14
|
}
|
|
15
|
-
view(actor, input) {
|
|
16
|
-
return this.options.view(actor, input);
|
|
15
|
+
view(actor, input, onResolved) {
|
|
16
|
+
return this.options.view(actor, input, onResolved);
|
|
17
17
|
}
|
|
18
|
-
async start(actor, input, signal) {
|
|
18
|
+
async start(actor, input, signal, onResolved) {
|
|
19
19
|
signal?.throwIfAborted();
|
|
20
20
|
const goal = parseAgentGoal(input.goal);
|
|
21
21
|
const content = input.content ?? '';
|
|
@@ -27,7 +27,8 @@ export class AgentNetworkService {
|
|
|
27
27
|
input.attachmentIds.some((id) => typeof id !== 'string' || !id))) ||
|
|
28
28
|
(!!input.attachments?.length && !!input.attachmentIds?.length) ||
|
|
29
29
|
!validOptionalString(input.runner) ||
|
|
30
|
-
|
|
30
|
+
'model' in input ||
|
|
31
|
+
!validOptionalString(input.modelId) ||
|
|
31
32
|
!validOptionalString(input.effort) ||
|
|
32
33
|
!validOptionalString(input.title) ||
|
|
33
34
|
!validOptionalString(input.parentSessionId) ||
|
|
@@ -40,6 +41,7 @@ export class AgentNetworkService {
|
|
|
40
41
|
clientMessageId: input.clientMessageId ?? randomUUID(),
|
|
41
42
|
goal
|
|
42
43
|
}, signal);
|
|
44
|
+
await onResolved?.([created.session]);
|
|
43
45
|
try {
|
|
44
46
|
if (created.session.parentSessionId === actor.sessionId)
|
|
45
47
|
this.options.observe(actor, created.session);
|
|
@@ -53,7 +55,7 @@ export class AgentNetworkService {
|
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
|
-
async send(actor, input, signal) {
|
|
58
|
+
async send(actor, input, signal, onResolved) {
|
|
57
59
|
signal?.throwIfAborted();
|
|
58
60
|
const goal = parseAgentGoal(input.goal);
|
|
59
61
|
const content = input.content ?? '';
|
|
@@ -91,6 +93,7 @@ export class AgentNetworkService {
|
|
|
91
93
|
if (input.approval !== undefined)
|
|
92
94
|
await this.options.validateApproval(actor, session, input.approval);
|
|
93
95
|
signal?.throwIfAborted();
|
|
96
|
+
await onResolved?.([session]);
|
|
94
97
|
if (session.parentSessionId === actor.sessionId)
|
|
95
98
|
this.options.observe(actor, session);
|
|
96
99
|
const approvalResult = input.approval === undefined
|
|
@@ -190,7 +193,7 @@ export class AgentNetworkService {
|
|
|
190
193
|
};
|
|
191
194
|
}
|
|
192
195
|
}
|
|
193
|
-
async stop(actor, input) {
|
|
196
|
+
async stop(actor, input, onResolved) {
|
|
194
197
|
if (typeof input.agentId !== 'string' ||
|
|
195
198
|
!input.agentId ||
|
|
196
199
|
typeof input.executionId !== 'string' ||
|
|
@@ -198,6 +201,7 @@ export class AgentNetworkService {
|
|
|
198
201
|
throw new Error('AGENT_STOP_INVALID');
|
|
199
202
|
const session = await this.options.target(actor, input.agentId);
|
|
200
203
|
await this.options.authorize(actor, session, 'stop');
|
|
204
|
+
await onResolved?.([session]);
|
|
201
205
|
const goalBefore = this.options.goal(session.id);
|
|
202
206
|
try {
|
|
203
207
|
const result = this.options.stop(actor, session, input.executionId);
|
|
@@ -223,7 +227,7 @@ export class AgentNetworkService {
|
|
|
223
227
|
};
|
|
224
228
|
}
|
|
225
229
|
}
|
|
226
|
-
async wait(actor, input) {
|
|
230
|
+
async wait(actor, input, onResolved) {
|
|
227
231
|
validateAgentWaitOptions(input);
|
|
228
232
|
if ((input.mode !== undefined && input.agentIds === undefined) ||
|
|
229
233
|
(input.agentId !== undefined && input.agentIds !== undefined) ||
|
|
@@ -267,6 +271,7 @@ export class AgentNetworkService {
|
|
|
267
271
|
if (!targets.length)
|
|
268
272
|
throw new Error('AGENT_EXECUTION_NOT_FOUND');
|
|
269
273
|
const selected = new Set(targets.map((target) => target.agentId));
|
|
274
|
+
await onResolved?.(targets.map((target) => sessions.find((session) => session.id === target.agentId)));
|
|
270
275
|
const directChildren = new Set();
|
|
271
276
|
for (const session of sessions)
|
|
272
277
|
if (selected.has(session.id) && session.parentSessionId === actor.sessionId) {
|
|
@@ -717,7 +717,7 @@ export class ContinuousRunCoordinator {
|
|
|
717
717
|
...(pending.externalSessionId === null
|
|
718
718
|
? {}
|
|
719
719
|
: { externalSessionId: pending.externalSessionId }),
|
|
720
|
-
...(pending.
|
|
720
|
+
...(pending.modelId === null ? {} : { model: pending.modelId }),
|
|
721
721
|
...(pending.effort === null ? {} : { effort: pending.effort }),
|
|
722
722
|
...(pending.access === null ? {} : { access: pending.access }),
|
|
723
723
|
collaborationMode: pending.collaborationMode,
|
|
@@ -106,7 +106,7 @@ export class ExternalSessionResumeService {
|
|
|
106
106
|
cwd,
|
|
107
107
|
createdAt: session.createdAt,
|
|
108
108
|
lastActivityAt: session.lastActivityAt,
|
|
109
|
-
|
|
109
|
+
modelId: configuration.modelId,
|
|
110
110
|
effort: configuration.effort,
|
|
111
111
|
access: configuration.access,
|
|
112
112
|
...(session.runnerTitle === null ? {} : { title: session.runnerTitle }),
|
|
@@ -184,7 +184,7 @@ export class GoalContinuationCoordinator {
|
|
|
184
184
|
this.options
|
|
185
185
|
.runners()
|
|
186
186
|
.require(session.runner)
|
|
187
|
-
.assertChildModel(session.
|
|
187
|
+
.assertChildModel(session.modelId, executionContext);
|
|
188
188
|
this.invalidateGoalContinuation(session.id);
|
|
189
189
|
const prepared = this.prepareGoalSession(session);
|
|
190
190
|
const goalSession = prepared.session;
|
|
@@ -558,13 +558,13 @@ export class GoalContinuationCoordinator {
|
|
|
558
558
|
: session;
|
|
559
559
|
if (changed &&
|
|
560
560
|
prepared.externalSessionId !== null &&
|
|
561
|
-
prepared.
|
|
561
|
+
prepared.modelId !== null &&
|
|
562
562
|
prepared.effort !== null)
|
|
563
563
|
this.options.database()?.saveNativeSessionMetadata({
|
|
564
564
|
runner: prepared.runner,
|
|
565
565
|
externalSessionId: prepared.externalSessionId,
|
|
566
566
|
workspaceId: prepared.workspaceId,
|
|
567
|
-
|
|
567
|
+
modelId: prepared.modelId,
|
|
568
568
|
effort: prepared.effort,
|
|
569
569
|
access
|
|
570
570
|
});
|
|
@@ -5,6 +5,7 @@ import { SessionWatchSlotCoordinator } from './session-watch-slot-coordinator.js
|
|
|
5
5
|
const INTERVAL_MS = 2_000;
|
|
6
6
|
const TTL_MS = 45_000;
|
|
7
7
|
const MAX_FAILURES = 3;
|
|
8
|
+
const INITIAL_SEGMENT_LIMIT = 3;
|
|
8
9
|
export class NativeSessionWatchService {
|
|
9
10
|
options;
|
|
10
11
|
watches = new Map();
|
|
@@ -323,7 +324,9 @@ export class NativeSessionWatchService {
|
|
|
323
324
|
return;
|
|
324
325
|
stage = 'project_snapshot';
|
|
325
326
|
const page = update.page;
|
|
326
|
-
const payload = lease.unit === 'SEGMENT'
|
|
327
|
+
const payload = lease.unit === 'SEGMENT'
|
|
328
|
+
? projectInitialPage(session, page, 'SEGMENT', INITIAL_SEGMENT_LIMIT)
|
|
329
|
+
: page;
|
|
327
330
|
this.emitSnapshot(lease, session, payload);
|
|
328
331
|
this.releaseSnapshotPendingEvents(lease, page);
|
|
329
332
|
const hydration = watch.observation.hydrate();
|
|
@@ -405,7 +408,7 @@ export class NativeSessionWatchService {
|
|
|
405
408
|
if (!lease.ready)
|
|
406
409
|
continue;
|
|
407
410
|
const payload = lease.unit === 'SEGMENT'
|
|
408
|
-
? this.options.projectInitialPage?.(session, page, 'SEGMENT',
|
|
411
|
+
? this.options.projectInitialPage?.(session, page, 'SEGMENT', INITIAL_SEGMENT_LIMIT)
|
|
409
412
|
: page;
|
|
410
413
|
if (payload === undefined)
|
|
411
414
|
continue;
|
|
@@ -431,7 +434,7 @@ export class NativeSessionWatchService {
|
|
|
431
434
|
if (!lease.snapshotSent)
|
|
432
435
|
continue;
|
|
433
436
|
const payload = lease.unit === 'SEGMENT'
|
|
434
|
-
? this.options.projectInitialPage?.(session, page, 'SEGMENT',
|
|
437
|
+
? this.options.projectInitialPage?.(session, page, 'SEGMENT', INITIAL_SEGMENT_LIMIT)
|
|
435
438
|
: page;
|
|
436
439
|
if (payload === undefined)
|
|
437
440
|
continue;
|
|
@@ -51,7 +51,7 @@ const sessionSchema = z
|
|
|
51
51
|
id: identifier,
|
|
52
52
|
workspaceId: identifier,
|
|
53
53
|
cwd: z.string().min(1),
|
|
54
|
-
|
|
54
|
+
modelId: z.string().nullable(),
|
|
55
55
|
effort: z.string().nullable(),
|
|
56
56
|
access: z.string().nullable()
|
|
57
57
|
})
|
|
@@ -68,7 +68,7 @@ const sessionSchema = z
|
|
|
68
68
|
.strict();
|
|
69
69
|
export const plannedRestartSchema = z
|
|
70
70
|
.object({
|
|
71
|
-
schemaVersion: z.literal(
|
|
71
|
+
schemaVersion: z.literal(2),
|
|
72
72
|
id: identifier,
|
|
73
73
|
nodeId: identifier,
|
|
74
74
|
serverUrl: z.string().min(1),
|
|
@@ -76,6 +76,14 @@ export const plannedRestartSchema = z
|
|
|
76
76
|
sessions: z.array(sessionSchema)
|
|
77
77
|
})
|
|
78
78
|
.strict();
|
|
79
|
+
const legacyPlannedRestartSchema = plannedRestartSchema.extend({
|
|
80
|
+
schemaVersion: z.literal(1),
|
|
81
|
+
sessions: z.array(sessionSchema.extend({
|
|
82
|
+
session: sessionSchema.shape.session.omit({ modelId: true }).extend({
|
|
83
|
+
model: z.string().nullable()
|
|
84
|
+
})
|
|
85
|
+
}))
|
|
86
|
+
});
|
|
79
87
|
export const RESTART_NOTICE = 'The previous execution was interrupted by a system restart. Check the session history and current workspace, then continue unfinished work. Verify completed or uncertain actions before proceeding; do not blindly repeat actions with external side effects.';
|
|
80
88
|
/** A one-shot orderly-shutdown handoff, not a crash-recovery journal. */
|
|
81
89
|
export class PlannedRestartHandoff {
|
|
@@ -129,7 +137,18 @@ export class PlannedRestartHandoff {
|
|
|
129
137
|
return;
|
|
130
138
|
}
|
|
131
139
|
try {
|
|
132
|
-
const
|
|
140
|
+
const saved = JSON.parse(text);
|
|
141
|
+
const legacy = legacyPlannedRestartSchema.safeParse(saved);
|
|
142
|
+
const value = plannedRestartSchema.parse(legacy.success
|
|
143
|
+
? {
|
|
144
|
+
...legacy.data,
|
|
145
|
+
schemaVersion: 2,
|
|
146
|
+
sessions: legacy.data.sessions.map((entry) => {
|
|
147
|
+
const { model, ...session } = entry.session;
|
|
148
|
+
return { ...entry, session: { ...session, modelId: model } };
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
: saved);
|
|
133
152
|
const identity = this.options.identity();
|
|
134
153
|
if (value.nodeId !== identity.nodeId || value.serverUrl !== identity.serverUrl)
|
|
135
154
|
throw new Error('RESTART_HANDOFF_IDENTITY_MISMATCH');
|
|
@@ -190,7 +209,7 @@ export class PlannedRestartHandoff {
|
|
|
190
209
|
id: session.externalSessionId,
|
|
191
210
|
workspaceId: session.workspaceId,
|
|
192
211
|
cwd: session.cwd,
|
|
193
|
-
|
|
212
|
+
modelId: session.modelId,
|
|
194
213
|
effort: session.effort,
|
|
195
214
|
access: session.access
|
|
196
215
|
},
|
|
@@ -207,7 +226,7 @@ export class PlannedRestartHandoff {
|
|
|
207
226
|
});
|
|
208
227
|
}
|
|
209
228
|
return plannedRestartSchema.parse({
|
|
210
|
-
schemaVersion:
|
|
229
|
+
schemaVersion: 2,
|
|
211
230
|
id: randomUUID(),
|
|
212
231
|
...this.options.identity(),
|
|
213
232
|
createdAt: Date.now(),
|
|
@@ -18,7 +18,7 @@ export class RestartSessionRestorer {
|
|
|
18
18
|
workspaceId: entry.session.workspaceId,
|
|
19
19
|
ownerUserId,
|
|
20
20
|
runner: 'mar-agent',
|
|
21
|
-
|
|
21
|
+
modelId: entry.session.modelId
|
|
22
22
|
}));
|
|
23
23
|
return contexts.get(ownerUserId);
|
|
24
24
|
};
|
|
@@ -35,7 +35,7 @@ export class RestartSessionRestorer {
|
|
|
35
35
|
throw new Error('RESTART_SESSION_MISMATCH');
|
|
36
36
|
session = this.options.runtime.updateAgentSessionConfiguration({
|
|
37
37
|
sessionId: session.id,
|
|
38
|
-
|
|
38
|
+
modelId: entry.session.modelId ?? session.modelId,
|
|
39
39
|
effort: entry.session.effort ?? session.effort,
|
|
40
40
|
access: entry.session.access ?? session.access
|
|
41
41
|
});
|
|
@@ -29,11 +29,19 @@ export class NativeSessionProjectionService {
|
|
|
29
29
|
return accepted === undefined ? [] : [accepted];
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
const candidates = new Map(ordered.map((session) => [session.id, session]));
|
|
32
33
|
return ordered.flatMap((session) => {
|
|
33
34
|
const record = database.getSessionRecord(session.id);
|
|
34
|
-
if (record === undefined && session
|
|
35
|
+
if (record === undefined && !this.validParent(session, candidates))
|
|
35
36
|
return [];
|
|
36
|
-
return [
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
...session,
|
|
40
|
+
parentSessionId: record === undefined
|
|
41
|
+
? (session.parentSessionId ?? null)
|
|
42
|
+
: (record.metadata.parentSessionId ?? null)
|
|
43
|
+
}
|
|
44
|
+
];
|
|
37
45
|
});
|
|
38
46
|
}
|
|
39
47
|
async discoverCodex(workspaceId) {
|
|
@@ -89,7 +97,7 @@ export class NativeSessionProjectionService {
|
|
|
89
97
|
nativeControl: 'EXTERNAL',
|
|
90
98
|
channelToken: null,
|
|
91
99
|
cwd: workspace.path,
|
|
92
|
-
|
|
100
|
+
modelId: record.metadata.modelId,
|
|
93
101
|
effort: record.metadata.effort,
|
|
94
102
|
access: record.metadata.access,
|
|
95
103
|
customTitle: record.metadata.customTitle,
|
|
@@ -123,7 +131,7 @@ export class NativeSessionProjectionService {
|
|
|
123
131
|
cwd: direct.cwd,
|
|
124
132
|
createdAt: direct.createdAt,
|
|
125
133
|
lastActivityAt: direct.lastActivityAt,
|
|
126
|
-
...(direct.
|
|
134
|
+
...(direct.modelId === null ? {} : { modelId: direct.modelId }),
|
|
127
135
|
...(direct.effort === null ? {} : { effort: direct.effort }),
|
|
128
136
|
...(direct.access === null ? {} : { access: direct.access }),
|
|
129
137
|
...(direct.runnerTitle === null ? {} : { title: direct.runnerTitle }),
|
|
@@ -142,9 +150,9 @@ export class NativeSessionProjectionService {
|
|
|
142
150
|
const fallback = saved ?? runner.defaultConfiguration(workspace);
|
|
143
151
|
const configuration = {
|
|
144
152
|
runner: session.runner,
|
|
145
|
-
|
|
146
|
-
? session.
|
|
147
|
-
: fallback.
|
|
153
|
+
modelId: typeof session.modelId === 'string' && session.modelId.trim().length > 0
|
|
154
|
+
? session.modelId
|
|
155
|
+
: fallback.modelId,
|
|
148
156
|
effort: typeof session.effort === 'string' ? session.effort : fallback.effort,
|
|
149
157
|
access: runner.restoreStoredAccess(session.access, fallback.access)
|
|
150
158
|
};
|
|
@@ -152,13 +160,13 @@ export class NativeSessionProjectionService {
|
|
|
152
160
|
}
|
|
153
161
|
ensureResumedConfiguration(session) {
|
|
154
162
|
const configuration = this.resumedConfiguration(session);
|
|
155
|
-
if (session.
|
|
163
|
+
if (session.modelId === configuration.modelId &&
|
|
156
164
|
session.effort === configuration.effort &&
|
|
157
165
|
session.access === configuration.access)
|
|
158
166
|
return session;
|
|
159
167
|
return this.options.runtime.updateAgentSessionConfiguration({
|
|
160
168
|
sessionId: session.id,
|
|
161
|
-
|
|
169
|
+
modelId: configuration.modelId,
|
|
162
170
|
effort: configuration.effort,
|
|
163
171
|
access: configuration.access
|
|
164
172
|
});
|
|
@@ -214,7 +222,7 @@ export class NativeSessionProjectionService {
|
|
|
214
222
|
nativeControl: 'EXTERNAL',
|
|
215
223
|
channelToken: null,
|
|
216
224
|
cwd: input.cwd,
|
|
217
|
-
|
|
225
|
+
modelId: metadata?.modelId ?? null,
|
|
218
226
|
effort: metadata?.effort ?? null,
|
|
219
227
|
access: metadata?.access ?? null,
|
|
220
228
|
customTitle: metadata?.customTitle ?? null,
|
|
@@ -237,29 +245,30 @@ export class NativeSessionProjectionService {
|
|
|
237
245
|
createdAt: timestamp
|
|
238
246
|
};
|
|
239
247
|
}
|
|
248
|
+
validParent(session, candidates) {
|
|
249
|
+
const database = this.options.database();
|
|
250
|
+
const visited = new Set([session.id]);
|
|
251
|
+
let parentId = session.parentSessionId ?? null;
|
|
252
|
+
let depth = 1;
|
|
253
|
+
while (parentId !== null) {
|
|
254
|
+
if (visited.has(parentId) || ++depth > 3)
|
|
255
|
+
return false;
|
|
256
|
+
visited.add(parentId);
|
|
257
|
+
const stored = database?.getSessionRecord(parentId);
|
|
258
|
+
const candidate = stored === undefined ? candidates.get(parentId) : undefined;
|
|
259
|
+
if ((stored?.workspaceId ?? candidate?.workspaceId) !== session.workspaceId)
|
|
260
|
+
return false;
|
|
261
|
+
parentId =
|
|
262
|
+
stored === undefined
|
|
263
|
+
? (candidate?.parentSessionId ?? null)
|
|
264
|
+
: (stored.metadata.parentSessionId ?? null);
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
240
268
|
remember(sessions) {
|
|
241
269
|
const database = this.options.database();
|
|
242
270
|
const candidates = new Map(sessions.map((session) => [session.id, session]));
|
|
243
|
-
const
|
|
244
|
-
const visited = new Set([session.id]);
|
|
245
|
-
let parentId = session.parentSessionId ?? null;
|
|
246
|
-
let depth = 1;
|
|
247
|
-
while (parentId !== null) {
|
|
248
|
-
if (visited.has(parentId) || ++depth > 3)
|
|
249
|
-
return false;
|
|
250
|
-
visited.add(parentId);
|
|
251
|
-
const stored = database?.getSessionRecord(parentId);
|
|
252
|
-
const candidate = stored === undefined ? candidates.get(parentId) : undefined;
|
|
253
|
-
if ((stored?.workspaceId ?? candidate?.workspaceId) !== session.workspaceId)
|
|
254
|
-
return false;
|
|
255
|
-
parentId =
|
|
256
|
-
stored === undefined
|
|
257
|
-
? (candidate?.parentSessionId ?? null)
|
|
258
|
-
: (stored.metadata.parentSessionId ?? null);
|
|
259
|
-
}
|
|
260
|
-
return true;
|
|
261
|
-
};
|
|
262
|
-
const valid = new Map(sessions.map((session) => [session.id, validParent(session)]));
|
|
271
|
+
const valid = new Map(sessions.map((session) => [session.id, this.validParent(session, candidates)]));
|
|
263
272
|
const accepted = sessions.flatMap((proposed) => {
|
|
264
273
|
const metadata = database === undefined || proposed.externalSessionId === null
|
|
265
274
|
? undefined
|