@myagentroam/node 0.11.6 → 0.11.8
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 +18 -7
- package/dist/marctl-guidance.js +36 -0
- package/dist/mcp/marctl-guide-stdio.js +18 -0
- package/dist/mcp/marctl-guide.js +30 -0
- package/dist/mcp/naming.js +1 -0
- package/dist/opencode-server.js +50 -8
- package/dist/runner/abstract-runner.js +8 -0
- package/dist/runner/claude-code-runner.js +2 -1
- package/dist/runner/codex-runner.js +27 -1
- package/dist/runner/execution-context.js +1 -0
- package/dist/runner/mar-agent/execution-input.js +46 -0
- package/dist/runner/mar-agent/managed-run-controller.js +10 -36
- package/dist/runner/mar-agent/marctl-skill.js +3 -34
- package/dist/runner/mar-agent-runner.js +4 -0
- package/dist/runner/opencode/managed-run-controller.js +117 -11
- package/dist/runner/opencode-runner.js +2 -1
- package/dist/runtime-state.js +32 -6
- package/dist/service/continuous-run-coordinator.js +2 -10
- package/dist/service/conversation-history-service.js +23 -40
- package/dist/service/conversation-segment-service.js +3 -1
- package/dist/service/goal-continuation-coordinator.js +5 -27
- package/dist/service/restart-session-restorer.js +3 -8
- package/dist/service/scheduled-session-coordinator.js +5 -3
- package/dist/service/scheduled-session-recurrence.js +41 -19
- package/dist/service/scheduled-session-store.js +6 -4
- package/dist/service/session-command-service.js +4 -12
- package/dist/service/session-lifecycle-service.js +4 -6
- package/dist/service/session-message-service.js +7 -18
- package/dist/service/workspace-queue-workbench-service.js +1 -1
- package/dist/util/conversation-identity.js +11 -0
- package/package.json +4 -4
|
@@ -18,6 +18,7 @@ export class OpenCodeManagedRunController {
|
|
|
18
18
|
startingCwds = new Map();
|
|
19
19
|
promptAdmitted = new Set();
|
|
20
20
|
pendingIdle = new Set();
|
|
21
|
+
cancelling = new Set();
|
|
21
22
|
messageRoles = new Map();
|
|
22
23
|
messageParents = new Map();
|
|
23
24
|
compactionPartIds = new Map();
|
|
@@ -158,11 +159,12 @@ export class OpenCodeManagedRunController {
|
|
|
158
159
|
const execution = this.runner.execution.get(runId);
|
|
159
160
|
if (execution === undefined)
|
|
160
161
|
return false;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
if (this.cancelling.has(runId))
|
|
163
|
+
return true;
|
|
164
|
+
this.cancelling.add(runId);
|
|
165
|
+
void execution.client.abort(execution.cwd, execution.nativeSessionId).then(() => this.finish(runId, intent === 'INTERRUPT' ? 'INTERRUPTED' : 'CANCELLED', 'run.completed', {
|
|
166
|
+
status: intent === 'INTERRUPT' ? 'INTERRUPTED' : 'CANCELLED'
|
|
167
|
+
}), (error) => this.finish(runId, 'FAILED', 'run.failed', error, 'OPENCODE_CANCEL_FAILED'));
|
|
166
168
|
return true;
|
|
167
169
|
}
|
|
168
170
|
decideApproval(input) {
|
|
@@ -207,6 +209,10 @@ export class OpenCodeManagedRunController {
|
|
|
207
209
|
this.startingCwds.set(input.runId, input.cwd);
|
|
208
210
|
this.host.active.add(input.runId);
|
|
209
211
|
this.host.emit(input.runId, 'run.started', {}, 'STARTING');
|
|
212
|
+
let settlePrompt;
|
|
213
|
+
const promptSettled = new Promise((resolve) => {
|
|
214
|
+
settlePrompt = resolve;
|
|
215
|
+
});
|
|
210
216
|
try {
|
|
211
217
|
if (hasMcpConfiguration(input.mcp))
|
|
212
218
|
await client.configureMcp(input.cwd, input.mcp);
|
|
@@ -251,16 +257,22 @@ export class OpenCodeManagedRunController {
|
|
|
251
257
|
});
|
|
252
258
|
void client
|
|
253
259
|
.subscribe(input.cwd, abort.signal, (event) => this.event(input.runId, nativeSessionId, event), ready)
|
|
254
|
-
.
|
|
255
|
-
|
|
256
|
-
|
|
260
|
+
.then(() => {
|
|
261
|
+
throw new Error('OPENCODE_EVENT_STREAM_ENDED');
|
|
262
|
+
})
|
|
263
|
+
.catch(async (error) => {
|
|
257
264
|
if (!subscriptionStarted) {
|
|
258
265
|
rejectReady(error);
|
|
259
266
|
return;
|
|
260
267
|
}
|
|
261
|
-
|
|
268
|
+
await promptSettled;
|
|
269
|
+
if (abort.signal.aborted || this.cancelling.has(input.runId))
|
|
270
|
+
return;
|
|
271
|
+
await this.recoverSubscription(input.runId);
|
|
262
272
|
});
|
|
263
273
|
await subscriptionReady;
|
|
274
|
+
if (abort.signal.aborted || this.cancelling.has(input.runId))
|
|
275
|
+
return;
|
|
264
276
|
this.host.emit(input.runId, 'run.running', {}, 'RUNNING');
|
|
265
277
|
// The initial SSE snapshot can contain an idle event from before this prompt.
|
|
266
278
|
this.pendingIdle.delete(input.runId);
|
|
@@ -277,7 +289,7 @@ export class OpenCodeManagedRunController {
|
|
|
277
289
|
? existingImagePaths
|
|
278
290
|
: await this.host.persistAttachments(input.cwd, input.runId, input.attachments);
|
|
279
291
|
const execution = this.runner.execution.get(input.runId);
|
|
280
|
-
if (execution?.abort !== abort || abort.signal.aborted)
|
|
292
|
+
if (execution?.abort !== abort || abort.signal.aborted || this.cancelling.has(input.runId))
|
|
281
293
|
return;
|
|
282
294
|
execution.responseLatency = new ResponseLatencyTracker();
|
|
283
295
|
execution.responsePartKinds = new Map();
|
|
@@ -294,12 +306,103 @@ export class OpenCodeManagedRunController {
|
|
|
294
306
|
...(input.personalInstructions === undefined ? {} : { system: input.personalInstructions }),
|
|
295
307
|
attachments: input.attachments
|
|
296
308
|
});
|
|
309
|
+
if (this.runner.execution.get(input.runId) !== execution || abort.signal.aborted)
|
|
310
|
+
return;
|
|
297
311
|
this.promptAdmitted.add(input.runId);
|
|
298
312
|
if (this.pendingIdle.delete(input.runId))
|
|
299
313
|
this.finish(input.runId, 'SUCCEEDED', 'run.completed', { status: 'SUCCEEDED' });
|
|
300
314
|
}
|
|
301
315
|
catch (error) {
|
|
302
|
-
this.
|
|
316
|
+
if (!this.cancelling.has(input.runId))
|
|
317
|
+
this.finish(input.runId, 'FAILED', 'run.failed', error, 'OPENCODE_RUN_START_FAILED');
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
settlePrompt();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async recoverSubscription(runId) {
|
|
324
|
+
const execution = this.runner.execution.get(runId);
|
|
325
|
+
if (execution === undefined)
|
|
326
|
+
return;
|
|
327
|
+
const { client, cwd, nativeSessionId, abort } = execution;
|
|
328
|
+
// A missing event interval and historical replay cannot produce live performance timings.
|
|
329
|
+
delete execution.responseLatency;
|
|
330
|
+
delete execution.generationTiming;
|
|
331
|
+
const buffered = [];
|
|
332
|
+
let reconciling = true;
|
|
333
|
+
let reconciliation;
|
|
334
|
+
const fail = (error) => {
|
|
335
|
+
if (!abort.signal.aborted && !this.cancelling.has(runId))
|
|
336
|
+
this.finish(runId, 'FAILED', 'run.failed', error, 'OPENCODE_EVENT_FAILED');
|
|
337
|
+
};
|
|
338
|
+
try {
|
|
339
|
+
await client.subscribe(cwd, abort.signal, (event) => {
|
|
340
|
+
if (reconciling)
|
|
341
|
+
buffered.push(event);
|
|
342
|
+
else
|
|
343
|
+
this.event(runId, nativeSessionId, event);
|
|
344
|
+
}, () => {
|
|
345
|
+
reconciliation = (async () => {
|
|
346
|
+
const signal = AbortSignal.any([abort.signal, AbortSignal.timeout(10_000)]);
|
|
347
|
+
const status = record(await client.sessionStatus(cwd, nativeSessionId, signal));
|
|
348
|
+
if (abort.signal.aborted || this.cancelling.has(runId))
|
|
349
|
+
return;
|
|
350
|
+
const messages = await client.messages(cwd, nativeSessionId, 100, {
|
|
351
|
+
since: execution.responseStartedAt ?? Infinity,
|
|
352
|
+
signal
|
|
353
|
+
});
|
|
354
|
+
if (abort.signal.aborted || this.cancelling.has(runId))
|
|
355
|
+
return;
|
|
356
|
+
// Fold queued content before the authoritative snapshot; terminal events must
|
|
357
|
+
// wait until the recovered items have been published.
|
|
358
|
+
const terminalEvents = [];
|
|
359
|
+
for (const event of buffered) {
|
|
360
|
+
const type = record(event)?.type;
|
|
361
|
+
if (type === 'session.idle' || type === 'session.error')
|
|
362
|
+
terminalEvents.push(event);
|
|
363
|
+
else
|
|
364
|
+
this.event(runId, nativeSessionId, event);
|
|
365
|
+
}
|
|
366
|
+
buffered.length = 0;
|
|
367
|
+
for (const value of messages) {
|
|
368
|
+
const message = record(value);
|
|
369
|
+
const info = record(message?.info);
|
|
370
|
+
// Do not replay earlier turns into the current Run.
|
|
371
|
+
const created = record(info?.time)?.created;
|
|
372
|
+
if (typeof created !== 'number' ||
|
|
373
|
+
created < (execution.responseStartedAt ?? Infinity))
|
|
374
|
+
continue;
|
|
375
|
+
this.event(runId, nativeSessionId, { type: 'message.updated', properties: { info } });
|
|
376
|
+
if (Array.isArray(message?.parts))
|
|
377
|
+
for (const part of message.parts)
|
|
378
|
+
this.event(runId, nativeSessionId, {
|
|
379
|
+
type: 'message.part.updated',
|
|
380
|
+
properties: { part }
|
|
381
|
+
});
|
|
382
|
+
if (info?.role === 'assistant' && info.error !== undefined) {
|
|
383
|
+
this.finish(runId, 'FAILED', 'run.failed', info.error);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
reconciling = false;
|
|
388
|
+
for (const event of terminalEvents)
|
|
389
|
+
this.event(runId, nativeSessionId, event);
|
|
390
|
+
if (abort.signal.aborted)
|
|
391
|
+
return;
|
|
392
|
+
if (status?.type === 'idle')
|
|
393
|
+
this.event(runId, nativeSessionId, {
|
|
394
|
+
type: 'session.idle',
|
|
395
|
+
properties: { sessionID: nativeSessionId }
|
|
396
|
+
});
|
|
397
|
+
else if (status?.type !== 'busy' && status?.type !== 'retry')
|
|
398
|
+
throw new Error('OPENCODE_SESSION_STATUS_INVALID');
|
|
399
|
+
})().catch(fail);
|
|
400
|
+
});
|
|
401
|
+
await reconciliation;
|
|
402
|
+
fail(new Error('OPENCODE_EVENT_STREAM_ENDED'));
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
fail(error);
|
|
303
406
|
}
|
|
304
407
|
}
|
|
305
408
|
event(runId, nativeSessionId, value) {
|
|
@@ -307,6 +410,8 @@ export class OpenCodeManagedRunController {
|
|
|
307
410
|
const properties = record(event?.properties);
|
|
308
411
|
const nested = record(properties?.part) ?? record(properties?.info);
|
|
309
412
|
const execution = this.runner.execution.get(runId);
|
|
413
|
+
if (execution === undefined)
|
|
414
|
+
return;
|
|
310
415
|
const receiptAt = execution?.generationTiming?.timestamp();
|
|
311
416
|
const sessionID = typeof properties?.sessionID === 'string'
|
|
312
417
|
? properties.sessionID
|
|
@@ -648,6 +753,7 @@ export class OpenCodeManagedRunController {
|
|
|
648
753
|
this.startingClients.delete(runId);
|
|
649
754
|
this.promptAdmitted.delete(runId);
|
|
650
755
|
this.pendingIdle.delete(runId);
|
|
756
|
+
this.cancelling.delete(runId);
|
|
651
757
|
this.messageRoles.delete(runId);
|
|
652
758
|
this.messageParents.delete(runId);
|
|
653
759
|
this.compactionPartIds.delete(runId);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withMarctlGuide } from '../mcp/marctl-guide.js';
|
|
1
2
|
import { OpenCodeServerClient } from '../opencode-server.js';
|
|
2
3
|
import { openCodeContextLimits, openCodeProfileModels } from '../opencode-runtime.js';
|
|
3
4
|
import { declaredRunnerProfiles } from '../runner-profiles.js';
|
|
@@ -169,7 +170,7 @@ export class OpenCodeRunner extends AbstractRunner {
|
|
|
169
170
|
this.profileRefreshTimer.unref();
|
|
170
171
|
}
|
|
171
172
|
mcpConfiguration(raw, secrets, executionServers = []) {
|
|
172
|
-
const servers = super.mcpConfiguration(raw, secrets, executionServers);
|
|
173
|
+
const servers = withMarctlGuide(super.mcpConfiguration(raw, secrets, executionServers), secrets);
|
|
173
174
|
return Object.fromEntries(servers.map((server) => [
|
|
174
175
|
server.name,
|
|
175
176
|
server.transport === 'STDIO'
|
package/dist/runtime-state.js
CHANGED
|
@@ -25,6 +25,7 @@ export class NodeRuntimeState {
|
|
|
25
25
|
recentUserMessages = new Map();
|
|
26
26
|
messageSubmissions = new Map();
|
|
27
27
|
failedMessageSubmissions = new Map();
|
|
28
|
+
terminalMessageSubmissions = new Map();
|
|
28
29
|
queueItems = new Map();
|
|
29
30
|
executions = new Map();
|
|
30
31
|
retainedExecutionTurns = new Map();
|
|
@@ -286,6 +287,10 @@ export class NodeRuntimeState {
|
|
|
286
287
|
this.messageSubmissions.delete(key);
|
|
287
288
|
this.messageSubmissions.set(`${nativeSessionId}${key.slice(id.length)}`, submission);
|
|
288
289
|
}
|
|
290
|
+
const terminalSubmissions = [...this.terminalMessageSubmissions];
|
|
291
|
+
this.terminalMessageSubmissions.clear();
|
|
292
|
+
for (const [key, runId] of terminalSubmissions)
|
|
293
|
+
this.terminalMessageSubmissions.set(key.startsWith(`${id}\u0000`) ? `${nativeSessionId}${key.slice(id.length)}` : key, runId);
|
|
289
294
|
for (const [executionId, reference] of this.retainedExecutionTurns)
|
|
290
295
|
if (reference.sessionId === id)
|
|
291
296
|
this.retainedExecutionTurns.set(executionId, {
|
|
@@ -689,9 +694,13 @@ export class NodeRuntimeState {
|
|
|
689
694
|
return submission?.kind === 'run' ? this.runs.get(submission.id) : undefined;
|
|
690
695
|
}
|
|
691
696
|
findMessageSubmission(sessionId, clientMessageId) {
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
697
|
+
const key = `${sessionId}\u0000${clientMessageId}`;
|
|
698
|
+
const submission = this.messageSubmissions.get(key);
|
|
699
|
+
if (submission === undefined) {
|
|
700
|
+
const runId = this.terminalMessageSubmissions.get(key);
|
|
701
|
+
const run = runId === undefined ? undefined : this.terminalRunIds.get(runId);
|
|
702
|
+
return run === undefined ? undefined : { kind: 'run', run };
|
|
703
|
+
}
|
|
695
704
|
if (submission.kind === 'run')
|
|
696
705
|
return { kind: 'run', run: this.requireRun(submission.id) };
|
|
697
706
|
const item = this.requireQueueItem(submission.id);
|
|
@@ -1016,8 +1025,13 @@ export class NodeRuntimeState {
|
|
|
1016
1025
|
if (!terminal(run.status))
|
|
1017
1026
|
throw new Error('RUN_ACTIVE');
|
|
1018
1027
|
this.terminalRunIds.set(runId, run);
|
|
1019
|
-
if (this.terminalRunIds.size > 1_024)
|
|
1020
|
-
this.terminalRunIds.
|
|
1028
|
+
if (this.terminalRunIds.size > 1_024) {
|
|
1029
|
+
const oldest = this.terminalRunIds.keys().next().value;
|
|
1030
|
+
this.terminalRunIds.delete(oldest);
|
|
1031
|
+
for (const [key, id] of this.terminalMessageSubmissions)
|
|
1032
|
+
if (id === oldest)
|
|
1033
|
+
this.terminalMessageSubmissions.delete(key);
|
|
1034
|
+
}
|
|
1021
1035
|
const executionIds = new Set([...this.executions.values()]
|
|
1022
1036
|
.filter((execution) => execution.runId === runId)
|
|
1023
1037
|
.map((execution) => execution.id));
|
|
@@ -1051,12 +1065,21 @@ export class NodeRuntimeState {
|
|
|
1051
1065
|
this.recentUserMessages.delete(runId);
|
|
1052
1066
|
this.runSequences.delete(runId);
|
|
1053
1067
|
for (const [key, candidate] of this.messageSubmissions)
|
|
1054
|
-
if (candidate.kind === 'run' && candidate.id === runId)
|
|
1068
|
+
if (candidate.kind === 'run' && candidate.id === runId) {
|
|
1055
1069
|
this.messageSubmissions.delete(key);
|
|
1070
|
+
this.terminalMessageSubmissions.set(key, runId);
|
|
1071
|
+
if (this.terminalMessageSubmissions.size > 1_024)
|
|
1072
|
+
this.terminalMessageSubmissions.delete(this.terminalMessageSubmissions.keys().next().value);
|
|
1073
|
+
}
|
|
1056
1074
|
for (const [approvalId, approval] of this.approvals)
|
|
1057
1075
|
if (approval.runId === runId || executionIds.has(approval.runId))
|
|
1058
1076
|
this.approvals.delete(approvalId);
|
|
1059
1077
|
}
|
|
1078
|
+
clearTerminalMessageSubmissions(sessionId) {
|
|
1079
|
+
for (const key of this.terminalMessageSubmissions.keys())
|
|
1080
|
+
if (key.startsWith(`${sessionId}\u0000`))
|
|
1081
|
+
this.terminalMessageSubmissions.delete(key);
|
|
1082
|
+
}
|
|
1060
1083
|
hasActiveWorkspaceLease(workspaceId) {
|
|
1061
1084
|
return [...this.leases.values()]
|
|
1062
1085
|
.map((runId) => this.runs.get(runId))
|
|
@@ -1073,6 +1096,7 @@ export class NodeRuntimeState {
|
|
|
1073
1096
|
.filter((execution) => runIds.has(execution.runId))
|
|
1074
1097
|
.map((execution) => execution.id));
|
|
1075
1098
|
this.sessions.delete(sessionId);
|
|
1099
|
+
this.clearTerminalMessageSubmissions(sessionId);
|
|
1076
1100
|
this.completeHistorySessions.delete(sessionId);
|
|
1077
1101
|
this.removePromotedSessionAliases(sessionId);
|
|
1078
1102
|
this.turns.delete(sessionId);
|
|
@@ -1125,6 +1149,7 @@ export class NodeRuntimeState {
|
|
|
1125
1149
|
if (this.sessionQueue(sessionId).itemIds.length > 0)
|
|
1126
1150
|
throw new Error('SESSION_QUEUE_NOT_EMPTY');
|
|
1127
1151
|
this.turns.delete(sessionId);
|
|
1152
|
+
this.clearTerminalMessageSubmissions(sessionId);
|
|
1128
1153
|
for (const [id, run] of this.terminalRunIds)
|
|
1129
1154
|
if (run?.sessionId === sessionId)
|
|
1130
1155
|
this.terminalRunIds.set(id, undefined);
|
|
@@ -1148,6 +1173,7 @@ export class NodeRuntimeState {
|
|
|
1148
1173
|
const queueItemIds = new Set([...sessionIds].flatMap((sessionId) => this.queuedItems.get(sessionId) ?? []));
|
|
1149
1174
|
for (const sessionId of sessionIds) {
|
|
1150
1175
|
this.sessions.delete(sessionId);
|
|
1176
|
+
this.clearTerminalMessageSubmissions(sessionId);
|
|
1151
1177
|
this.completeHistorySessions.delete(sessionId);
|
|
1152
1178
|
this.removePromotedSessionAliases(sessionId);
|
|
1153
1179
|
this.turns.delete(sessionId);
|
|
@@ -605,7 +605,7 @@ export class ContinuousRunCoordinator {
|
|
|
605
605
|
runId: executionId,
|
|
606
606
|
sessionId: pending.sessionId,
|
|
607
607
|
workspaceId: pending.workspaceId,
|
|
608
|
-
|
|
608
|
+
...pending.executionContext,
|
|
609
609
|
input: pending.input,
|
|
610
610
|
...(pending.clientMessageId === undefined
|
|
611
611
|
? {}
|
|
@@ -626,17 +626,9 @@ export class ContinuousRunCoordinator {
|
|
|
626
626
|
MAR_CTL_WORKSPACE_ID: pending.workspaceId,
|
|
627
627
|
MAR_CTL_SESSION_ID: this.options.runtime.getAgentSession(pending.sessionId)?.id ?? pending.sessionId,
|
|
628
628
|
MAR_CTL_RUN_ID: active.id,
|
|
629
|
-
MAR_CTL_RUNNER: pending.runner
|
|
629
|
+
MAR_CTL_RUNNER: pending.executionContext.runner
|
|
630
630
|
},
|
|
631
631
|
...(personalInstructions === undefined ? {} : { personalInstructions }),
|
|
632
|
-
...(pending.marAgentModel === undefined ? {} : { marAgentModel: pending.marAgentModel }),
|
|
633
|
-
...(pending.marAgentModels === undefined ? {} : { marAgentModels: pending.marAgentModels }),
|
|
634
|
-
...(pending.marAgentCompact === undefined
|
|
635
|
-
? {}
|
|
636
|
-
: { marAgentCompact: pending.marAgentCompact }),
|
|
637
|
-
...(pending.marAgentRuntimeScopeId === undefined
|
|
638
|
-
? {}
|
|
639
|
-
: { marAgentRuntimeScopeId: pending.marAgentRuntimeScopeId }),
|
|
640
632
|
...(pending.computerUse === undefined ? {} : { computerUse: pending.computerUse }),
|
|
641
633
|
mcpInstallations: this.options.mcps(pending.workspaceId),
|
|
642
634
|
executionMcpServers: [goalMcp, scheduleMcp, notificationMcp].filter((server) => server !== undefined)
|
|
@@ -2,6 +2,7 @@ import { projectConversationWorkProcesses } from './conversation-work-process.js
|
|
|
2
2
|
import { discoverNativeSessions, readNativeSession } from '../native-session-history.js';
|
|
3
3
|
import { nativeConversationPage } from '../util/runner-native-session-parsers.js';
|
|
4
4
|
import { isPlainRecord } from '../util/node-operation-parsers.js';
|
|
5
|
+
import { conversationClientMessageId } from '../util/conversation-identity.js';
|
|
5
6
|
import { nodeLog } from '../operational.js';
|
|
6
7
|
export function hasManagedActiveRun(runtime, session) {
|
|
7
8
|
return (session.nativeControl === 'MAR_MANAGED' &&
|
|
@@ -475,34 +476,29 @@ export class ConversationHistoryService {
|
|
|
475
476
|
}
|
|
476
477
|
async readManagedRuntime(session, input, cursor, snapshotSequence, readAt) {
|
|
477
478
|
const page = await this.readManagedActive(session, input, cursor, snapshotSequence, readAt);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
479
|
+
try {
|
|
480
|
+
const supplements = await this.options.runners
|
|
481
|
+
.require(session.runner)
|
|
482
|
+
.readConversationRewind({
|
|
483
|
+
session,
|
|
484
|
+
turns: page.turns,
|
|
485
|
+
managedActive: this.options.hasManagedActiveRun(session),
|
|
486
|
+
firstPage: input.cursor === undefined,
|
|
487
|
+
readHistory: async (historyInput) => (await this.readRunnerHistory(session, historyInput)).page
|
|
488
|
+
});
|
|
489
|
+
if (supplements.size === 0)
|
|
490
|
+
return page;
|
|
491
|
+
return {
|
|
492
|
+
...page,
|
|
493
|
+
turns: page.turns.map((turn) => {
|
|
494
|
+
const rewind = supplements.get(turn.id);
|
|
495
|
+
return turn.rewind !== undefined || rewind === undefined ? turn : { ...turn, rewind };
|
|
496
|
+
})
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
491
500
|
return page;
|
|
492
|
-
|
|
493
|
-
...page,
|
|
494
|
-
turns: page.turns.map((turn) => {
|
|
495
|
-
if (turn.rewind !== undefined || !terminalConversationTurn(turn.status))
|
|
496
|
-
return turn;
|
|
497
|
-
const messageId = conversationClientMessageId(turn);
|
|
498
|
-
if (messageId === null)
|
|
499
|
-
return turn;
|
|
500
|
-
const native = authoritative.turns.find((candidate) => candidate.rewind !== undefined &&
|
|
501
|
-
candidate.userItemId !== null &&
|
|
502
|
-
conversationClientMessageId(candidate) === messageId);
|
|
503
|
-
return native?.rewind === undefined ? turn : { ...turn, rewind: native.rewind };
|
|
504
|
-
})
|
|
505
|
-
};
|
|
501
|
+
}
|
|
506
502
|
}
|
|
507
503
|
async refreshExternalActivity(session) {
|
|
508
504
|
const runner = this.options.runners.require(session.runner);
|
|
@@ -754,19 +750,6 @@ function sameConversationTurn(left, right) {
|
|
|
754
750
|
candidate.id === `${local.runId}:${item.id}` &&
|
|
755
751
|
((native.userItemId === null && local.userItemId === null) || item.kind === 'error')));
|
|
756
752
|
}
|
|
757
|
-
function terminalConversationTurn(status) {
|
|
758
|
-
return ['SUCCEEDED', 'FAILED', 'CANCELLED', 'INTERRUPTED', 'LOST'].includes(status);
|
|
759
|
-
}
|
|
760
|
-
function conversationClientMessageId(turn) {
|
|
761
|
-
for (const item of turn.items) {
|
|
762
|
-
if (item.kind !== 'user_message' || !isPlainRecord(item.payload))
|
|
763
|
-
continue;
|
|
764
|
-
const value = item.payload.clientMessageId;
|
|
765
|
-
if (typeof value === 'string' && value.length > 0)
|
|
766
|
-
return value;
|
|
767
|
-
}
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
753
|
function sourceRank(source) {
|
|
771
754
|
if (source === 'official')
|
|
772
755
|
return 3;
|
|
@@ -43,7 +43,9 @@ export function conversationSegments(turn) {
|
|
|
43
43
|
...(turn.rewind === undefined ? {} : { rewind: turn.rewind }),
|
|
44
44
|
items: projectedItems,
|
|
45
45
|
startedAt: projectedItems[0]?.startedAt ?? turn.startedAt,
|
|
46
|
-
completedAt:
|
|
46
|
+
completedAt: latest
|
|
47
|
+
? (turn.completedAt ?? null)
|
|
48
|
+
: (projectedItems.at(-1)?.completedAt ?? null)
|
|
47
49
|
};
|
|
48
50
|
});
|
|
49
51
|
}
|
|
@@ -111,16 +111,7 @@ export class GoalContinuationCoordinator {
|
|
|
111
111
|
...(pending.personalInstructions === undefined
|
|
112
112
|
? {}
|
|
113
113
|
: { personalInstructions: pending.personalInstructions }),
|
|
114
|
-
|
|
115
|
-
...(pending.marAgentModels === undefined
|
|
116
|
-
? {}
|
|
117
|
-
: { marAgentModels: pending.marAgentModels }),
|
|
118
|
-
...(pending.marAgentCompact === undefined
|
|
119
|
-
? {}
|
|
120
|
-
: { marAgentCompact: pending.marAgentCompact }),
|
|
121
|
-
...(pending.marAgentRuntimeScopeId === undefined
|
|
122
|
-
? {}
|
|
123
|
-
: { marAgentRuntimeScopeId: pending.marAgentRuntimeScopeId }),
|
|
114
|
+
executionContext: pending.executionContext,
|
|
124
115
|
continuationEpoch,
|
|
125
116
|
deliveryIntent: 'SEND'
|
|
126
117
|
});
|
|
@@ -182,7 +173,7 @@ export class GoalContinuationCoordinator {
|
|
|
182
173
|
goalAccessChanged: prepared.changed
|
|
183
174
|
};
|
|
184
175
|
}
|
|
185
|
-
async start({ session, objective, initiatedByUserId, suppressNotifications, secretEnvironment, personalInstructions,
|
|
176
|
+
async start({ session, objective, initiatedByUserId, suppressNotifications, secretEnvironment, personalInstructions, executionContext, usageGrant }) {
|
|
186
177
|
this.invalidateGoalContinuation(session.id);
|
|
187
178
|
const prepared = this.prepareGoalSession(session);
|
|
188
179
|
const goalSession = prepared.session;
|
|
@@ -208,10 +199,7 @@ export class GoalContinuationCoordinator {
|
|
|
208
199
|
...(initiatedByUserId === undefined ? {} : { initiatedByUserId }),
|
|
209
200
|
secretEnvironment,
|
|
210
201
|
...(personalInstructions === undefined ? {} : { personalInstructions }),
|
|
211
|
-
|
|
212
|
-
...(marAgentModels === undefined ? {} : { marAgentModels }),
|
|
213
|
-
...(marAgentCompact === undefined ? {} : { marAgentCompact }),
|
|
214
|
-
...(marAgentRuntimeScopeId === undefined ? {} : { marAgentRuntimeScopeId })
|
|
202
|
+
executionContext
|
|
215
203
|
});
|
|
216
204
|
if (!this.goalGenerationCurrent(attempt.sessionId, goalRef)) {
|
|
217
205
|
this.removeStaleGoalSubmission(attempt.sessionId, result);
|
|
@@ -330,12 +318,7 @@ export class GoalContinuationCoordinator {
|
|
|
330
318
|
...(input.personalInstructions === undefined
|
|
331
319
|
? {}
|
|
332
320
|
: { personalInstructions: input.personalInstructions }),
|
|
333
|
-
|
|
334
|
-
...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
|
|
335
|
-
...(input.marAgentCompact === undefined ? {} : { marAgentCompact: input.marAgentCompact }),
|
|
336
|
-
...(input.marAgentRuntimeScopeId === undefined
|
|
337
|
-
? {}
|
|
338
|
-
: { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
|
|
321
|
+
executionContext: input.executionContext
|
|
339
322
|
});
|
|
340
323
|
if (updated !== undefined &&
|
|
341
324
|
session !== undefined &&
|
|
@@ -440,12 +423,7 @@ export class GoalContinuationCoordinator {
|
|
|
440
423
|
...(input.personalInstructions === undefined
|
|
441
424
|
? {}
|
|
442
425
|
: { personalInstructions: input.personalInstructions }),
|
|
443
|
-
|
|
444
|
-
...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
|
|
445
|
-
...(input.marAgentCompact === undefined ? {} : { marAgentCompact: input.marAgentCompact }),
|
|
446
|
-
...(input.marAgentRuntimeScopeId === undefined
|
|
447
|
-
? {}
|
|
448
|
-
: { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
|
|
426
|
+
executionContext: input.executionContext
|
|
449
427
|
});
|
|
450
428
|
if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
|
|
451
429
|
this.removeStaleGoalSubmission(attempt.sessionId, result);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseComposerTags, serializeComposerTag } from '@myagentroam/protocol';
|
|
2
2
|
import { parseMessageAttachments } from '../util/node-operation-parsers.js';
|
|
3
|
+
import { RUNNER_EXECUTION_CONTEXT } from '../runner/execution-context.js';
|
|
3
4
|
import { RESTART_NOTICE } from './planned-restart-handoff.js';
|
|
4
5
|
/** Rebuild inputs through the existing preparation and FIFO boundary. */
|
|
5
6
|
export class RestartSessionRestorer {
|
|
@@ -79,10 +80,7 @@ export class RestartSessionRestorer {
|
|
|
79
80
|
__suppressNotifications: input.suppressNotifications === true,
|
|
80
81
|
secretEnvironment: context.secretEnvironment,
|
|
81
82
|
personalInstructions: context.personalInstructions,
|
|
82
|
-
|
|
83
|
-
marAgentModels: context.marAgentModels,
|
|
84
|
-
marAgentCompact: context.marAgentCompact,
|
|
85
|
-
marAgentRuntimeScopeId: context.marAgentRuntimeScopeId,
|
|
83
|
+
[RUNNER_EXECUTION_CONTEXT]: context.executionContext,
|
|
86
84
|
__usageGrant: context.usageGrant
|
|
87
85
|
}));
|
|
88
86
|
if (input.immediate)
|
|
@@ -124,10 +122,7 @@ ${RESTART_NOTICE}`,
|
|
|
124
122
|
...(typeof context.personalInstructions === 'string'
|
|
125
123
|
? { personalInstructions: context.personalInstructions }
|
|
126
124
|
: {}),
|
|
127
|
-
|
|
128
|
-
marAgentModels: context.marAgentModels,
|
|
129
|
-
marAgentCompact: context.marAgentCompact,
|
|
130
|
-
marAgentRuntimeScopeId: context.marAgentRuntimeScopeId
|
|
125
|
+
executionContext: context.executionContext
|
|
131
126
|
}));
|
|
132
127
|
}
|
|
133
128
|
if (!entry.paused)
|
|
@@ -14,7 +14,9 @@ export class ScheduledSessionCoordinator {
|
|
|
14
14
|
if (run.source !== 'SCHEDULED')
|
|
15
15
|
return;
|
|
16
16
|
const store = this.options.store();
|
|
17
|
-
store.finish(run.clientMessageId, status === 'SUCCEEDED' ? 'SUCCEEDED' : 'FAILED', status === 'SUCCEEDED' ? undefined : `SCHEDULE_RUN_${status}`, run.id);
|
|
17
|
+
const finished = store.finish(run.clientMessageId, status === 'SUCCEEDED' ? 'SUCCEEDED' : 'FAILED', status === 'SUCCEEDED' ? undefined : `SCHEDULE_RUN_${status}`, run.id);
|
|
18
|
+
if (!finished)
|
|
19
|
+
return;
|
|
18
20
|
const task = store.getById(run.clientMessageId.split(':', 1)[0]);
|
|
19
21
|
if (task !== undefined)
|
|
20
22
|
this.publishTaskNotification(task, run.clientMessageId, {
|
|
@@ -169,8 +171,8 @@ export class ScheduledSessionCoordinator {
|
|
|
169
171
|
}
|
|
170
172
|
if (failureCode !== undefined) {
|
|
171
173
|
const status = submitting && failureCode !== 'SCHEDULE_SESSION_BUSY' ? 'FAILED' : 'SKIPPED';
|
|
172
|
-
this.options.store().finish(occurrenceId, status, failureCode)
|
|
173
|
-
|
|
174
|
+
if (this.options.store().finish(occurrenceId, status, failureCode))
|
|
175
|
+
this.notify(task, occurrenceId, failureCode, status);
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
}
|
|
@@ -37,17 +37,44 @@ export function nextScheduledAt(cron, timezone, after) {
|
|
|
37
37
|
year: 'numeric',
|
|
38
38
|
month: '2-digit',
|
|
39
39
|
day: '2-digit',
|
|
40
|
-
weekday: 'short',
|
|
41
40
|
hour: '2-digit',
|
|
42
41
|
minute: '2-digit',
|
|
43
42
|
hourCycle: 'h23'
|
|
44
43
|
});
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
const start = localParts(formatter, after);
|
|
45
|
+
const dayMs = 24 * 60 * 60_000;
|
|
46
|
+
const limit = Math.floor(after / 60_000) * 60_000 + 8 * 366 * dayMs;
|
|
47
|
+
const firstDay = Date.UTC(start.year, start.month - 1, start.day);
|
|
48
|
+
const hours = [...schedule.hour].sort((a, b) => a - b);
|
|
49
|
+
const minutes = [...schedule.minute].sort((a, b) => a - b);
|
|
50
|
+
// These are calendar dates represented in UTC, not fixed-length local days.
|
|
51
|
+
for (let day = firstDay; day <= limit + dayMs; day += dayMs) {
|
|
52
|
+
const date = new Date(day);
|
|
53
|
+
if (!matchesDate(schedule, date))
|
|
54
|
+
continue;
|
|
55
|
+
// Sample both sides of this date to include offsets on either side of a
|
|
56
|
+
// transition, including half-hour folds and whole-day calendar jumps.
|
|
57
|
+
const offsets = new Set([day - dayMs, day, day + 2 * dayMs].map((time) => localParts(formatter, time).timestamp - time));
|
|
58
|
+
for (const hour of hours) {
|
|
59
|
+
if (day === firstDay && hour < start.hour)
|
|
60
|
+
continue;
|
|
61
|
+
for (const minute of minutes) {
|
|
62
|
+
if (day === firstDay && hour === start.hour && minute < start.minute)
|
|
63
|
+
continue;
|
|
64
|
+
const localTime = day + (hour * 60 + minute) * 60_000;
|
|
65
|
+
let earliest = Infinity;
|
|
66
|
+
for (const offset of offsets) {
|
|
67
|
+
const candidate = localTime - offset;
|
|
68
|
+
if (localParts(formatter, candidate).timestamp === localTime) {
|
|
69
|
+
earliest = Math.min(earliest, candidate);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Resolve an ambiguous minute before filtering by after: the second
|
|
73
|
+
// physical occurrence must never become a replacement for the first.
|
|
74
|
+
if (earliest > after && earliest <= limit)
|
|
75
|
+
return earliest;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
51
78
|
}
|
|
52
79
|
throw new Error('SCHEDULE_NEXT_TIME_UNAVAILABLE');
|
|
53
80
|
}
|
|
@@ -135,23 +162,21 @@ function localParts(formatter, time) {
|
|
|
135
162
|
const minute = Number(fields['minute']);
|
|
136
163
|
const day = Number(fields['day']);
|
|
137
164
|
const month = Number(fields['month']);
|
|
138
|
-
const
|
|
165
|
+
const year = Number(fields['year']);
|
|
139
166
|
return {
|
|
167
|
+
year,
|
|
140
168
|
hour,
|
|
141
169
|
minute,
|
|
142
170
|
day,
|
|
143
171
|
month,
|
|
144
|
-
|
|
145
|
-
key: `${fields['year']}-${fields['month']}-${fields['day']}T${fields['hour']}:${fields['minute']}`
|
|
172
|
+
timestamp: Date.UTC(year, month - 1, day, hour, minute)
|
|
146
173
|
};
|
|
147
174
|
}
|
|
148
|
-
function
|
|
149
|
-
if (!schedule.
|
|
150
|
-
!schedule.hour.has(local.hour) ||
|
|
151
|
-
!schedule.month.has(local.month))
|
|
175
|
+
function matchesDate(schedule, date) {
|
|
176
|
+
if (!schedule.month.has(date.getUTCMonth() + 1))
|
|
152
177
|
return false;
|
|
153
|
-
const dayOfMonthMatches = schedule.dayOfMonth.has(
|
|
154
|
-
const dayOfWeekMatches = schedule.dayOfWeek.has(
|
|
178
|
+
const dayOfMonthMatches = schedule.dayOfMonth.has(date.getUTCDate());
|
|
179
|
+
const dayOfWeekMatches = schedule.dayOfWeek.has(date.getUTCDay());
|
|
155
180
|
const dayMatches = schedule.dayOfMonthWildcard && schedule.dayOfWeekWildcard
|
|
156
181
|
? true
|
|
157
182
|
: schedule.dayOfMonthWildcard
|
|
@@ -161,6 +186,3 @@ function matches(schedule, local) {
|
|
|
161
186
|
: dayOfMonthMatches || dayOfWeekMatches;
|
|
162
187
|
return dayMatches;
|
|
163
188
|
}
|
|
164
|
-
function localKey(formatter, time) {
|
|
165
|
-
return localParts(formatter, time).key;
|
|
166
|
-
}
|