@myagentroam/node 0.9.89 → 0.9.90
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 +223 -95
- package/dist/runner/mar-agent/managed-run-controller.js +22 -18
- package/dist/service/continuous-run-coordinator.js +31 -0
- package/dist/service/mission-runtime.js +59 -9
- package/dist/service/run-workbench-service.js +14 -8
- package/dist/service/session-message-service.js +5 -3
- package/package.json +3 -3
package/dist/connector.js
CHANGED
|
@@ -244,13 +244,18 @@ export class NodeConnector {
|
|
|
244
244
|
missionRuntime = new MissionRuntime(() => undefined);
|
|
245
245
|
missionRetryTimers = new Map();
|
|
246
246
|
missionContinuationPending = new Map();
|
|
247
|
+
missionSubmissionAttempts = new Map();
|
|
248
|
+
missionContinuationEpochs = new Map();
|
|
249
|
+
missionExecutionProgress = new Set();
|
|
247
250
|
missionMcpServer = new MissionMcpServer(this.missionRuntime, (sessionId) => {
|
|
248
251
|
const mission = this.missionRuntime.get(sessionId);
|
|
249
252
|
const session = this.runtime.getAgentSession(sessionId);
|
|
250
253
|
if (mission === undefined || session === undefined)
|
|
251
254
|
return;
|
|
252
|
-
if (mission.status !== 'active')
|
|
255
|
+
if (mission.status !== 'active') {
|
|
256
|
+
this.retireMissionContinuation(sessionId);
|
|
253
257
|
this.continuousRunCoordinator.removeMissionQueue(sessionId);
|
|
258
|
+
}
|
|
254
259
|
this.publishMissionState(session, mission);
|
|
255
260
|
});
|
|
256
261
|
runnerUsageReader;
|
|
@@ -317,9 +322,18 @@ export class NodeConnector {
|
|
|
317
322
|
if (pending?.mission === undefined)
|
|
318
323
|
return;
|
|
319
324
|
const started = this.continuousRunCoordinator.hasExecutionStarted(executionId);
|
|
320
|
-
const
|
|
321
|
-
if (
|
|
322
|
-
|
|
325
|
+
const progressed = this.missionExecutionProgress.delete(executionId);
|
|
326
|
+
if (!this.missionGenerationCurrent(pending.sessionId, pending.mission))
|
|
327
|
+
return;
|
|
328
|
+
const disposition = this.missionRuntime.recordExecutionResult(pending.sessionId, {
|
|
329
|
+
failed: status === 'FAILED',
|
|
330
|
+
progressed: status !== 'SUCCEEDED' || progressed
|
|
331
|
+
});
|
|
332
|
+
if (disposition === 'blocked' || disposition === 'paused') {
|
|
333
|
+
if (disposition === 'blocked')
|
|
334
|
+
this.retireMissionContinuation(pending.sessionId);
|
|
335
|
+
else
|
|
336
|
+
this.invalidateMissionContinuation(pending.sessionId);
|
|
323
337
|
this.continuousRunCoordinator.removeMissionQueue(pending.sessionId);
|
|
324
338
|
const terminalMission = this.missionRuntime.get(pending.sessionId);
|
|
325
339
|
const session = this.runtime.getAgentSession(pending.sessionId);
|
|
@@ -330,22 +344,14 @@ export class NodeConnector {
|
|
|
330
344
|
if (status !== 'FAILED' || started)
|
|
331
345
|
return;
|
|
332
346
|
this.clearMissionRetry(pending.sessionId);
|
|
347
|
+
const continuationEpoch = this.missionContinuationEpochs.get(pending.sessionId) ?? 0;
|
|
333
348
|
const retry = {
|
|
334
349
|
sessionId: pending.sessionId,
|
|
335
350
|
timer: setTimeout(() => {
|
|
336
351
|
this.missionRetryTimers.delete(retry.sessionId);
|
|
337
|
-
|
|
338
|
-
if (mission?.status !== 'active' ||
|
|
339
|
-
mission.id !== pending.mission?.id ||
|
|
340
|
-
mission.revision !== pending.mission.revision)
|
|
341
|
-
return;
|
|
342
|
-
void this.sessionMessageService
|
|
343
|
-
.submitMission({
|
|
352
|
+
void this.enqueueMissionContinuation({
|
|
344
353
|
sessionId: retry.sessionId,
|
|
345
|
-
|
|
346
|
-
content: this.missionRuntime.prompt(retry.sessionId, 'continue'),
|
|
347
|
-
deliveryIntent: 'SEND',
|
|
348
|
-
mission: { id: mission.id, revision: mission.revision },
|
|
354
|
+
mission: pending.mission,
|
|
349
355
|
secretEnvironment: pending.secretEnvironment,
|
|
350
356
|
...(pending.personalInstructions === undefined
|
|
351
357
|
? {}
|
|
@@ -358,15 +364,17 @@ export class NodeConnector {
|
|
|
358
364
|
: { marAgentModels: pending.marAgentModels }),
|
|
359
365
|
...(pending.marAgentRuntimeScopeId === undefined
|
|
360
366
|
? {}
|
|
361
|
-
: { marAgentRuntimeScopeId: pending.marAgentRuntimeScopeId })
|
|
362
|
-
|
|
363
|
-
|
|
367
|
+
: { marAgentRuntimeScopeId: pending.marAgentRuntimeScopeId }),
|
|
368
|
+
continuationEpoch,
|
|
369
|
+
deliveryIntent: 'SEND'
|
|
370
|
+
});
|
|
364
371
|
}, 15_000)
|
|
365
372
|
};
|
|
366
373
|
this.missionRetryTimers.set(pending.sessionId, retry);
|
|
367
374
|
retry.timer.unref();
|
|
368
375
|
},
|
|
369
376
|
releaseExecution: (executionId) => {
|
|
377
|
+
this.missionExecutionProgress.delete(executionId);
|
|
370
378
|
this.missionMcpServer.revoke(executionId);
|
|
371
379
|
this.continuousRunCoordinator.remove(executionId);
|
|
372
380
|
}
|
|
@@ -634,6 +642,7 @@ export class NodeConnector {
|
|
|
634
642
|
(await this.runAttachmentService.readNative(runId, imageIndex)),
|
|
635
643
|
respondUserInput: (runId, requestId, answers) => this.runnerInteractionService.respondUserInput(runId, requestId, answers),
|
|
636
644
|
pauseQueue: (sessionId) => this.workspaceQueueWorkbenchService.pauseSessionQueue(sessionId),
|
|
645
|
+
runInterrupted: (sessionId) => this.clearMissionState(sessionId),
|
|
637
646
|
emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
638
647
|
control: (operation, payload) => this.controlMessages.handle(JSON.stringify(createEnvelope(operation, payload))),
|
|
639
648
|
markInterrupted: (runId) => this.runState.interruptRequested.add(runId)
|
|
@@ -662,20 +671,19 @@ export class NodeConnector {
|
|
|
662
671
|
markInterrupted: (runId) => this.runState.interruptRequested.add(runId),
|
|
663
672
|
mcps: (workspaceId) => this.database?.effectiveMcpInstallations(workspaceId) ?? [],
|
|
664
673
|
retainEnvironment: (environment) => this.gitExecution().retain(environment),
|
|
674
|
+
missionCurrent: (input) => input.mission === undefined ||
|
|
675
|
+
this.missionGenerationCurrent(input.sessionId, input.mission),
|
|
665
676
|
afterExecutionStarted: async (input) => {
|
|
666
677
|
if (input.mission === undefined)
|
|
667
678
|
return;
|
|
668
|
-
|
|
669
|
-
if (mission?.status !== 'active' ||
|
|
670
|
-
mission.id !== input.mission.id ||
|
|
671
|
-
mission.revision !== input.mission.revision)
|
|
679
|
+
if (!this.missionGenerationCurrent(input.sessionId, input.mission))
|
|
672
680
|
return;
|
|
673
681
|
const updated = this.missionRuntime.recordTurn(input.sessionId);
|
|
674
682
|
const session = this.runtime.getAgentSession(input.sessionId);
|
|
675
683
|
const run = this.runtime.getRun(input.runId);
|
|
676
684
|
await this.enqueueMissionContinuation({
|
|
677
685
|
sessionId: input.sessionId,
|
|
678
|
-
mission: { id: mission.id, revision: mission.revision },
|
|
686
|
+
mission: { id: input.mission.id, revision: input.mission.revision },
|
|
679
687
|
...(run?.initiatedByUserId === undefined
|
|
680
688
|
? {}
|
|
681
689
|
: { initiatedByUserId: run.initiatedByUserId }),
|
|
@@ -761,8 +769,24 @@ export class NodeConnector {
|
|
|
761
769
|
const pendingContinuation = this.missionContinuationPending.get(previousId);
|
|
762
770
|
if (pendingContinuation !== undefined) {
|
|
763
771
|
this.missionContinuationPending.delete(previousId);
|
|
772
|
+
pendingContinuation.sessionId = nextId;
|
|
764
773
|
this.missionContinuationPending.set(nextId, pendingContinuation);
|
|
765
774
|
}
|
|
775
|
+
const submissionAttempts = this.missionSubmissionAttempts.get(previousId);
|
|
776
|
+
if (submissionAttempts !== undefined) {
|
|
777
|
+
this.missionSubmissionAttempts.delete(previousId);
|
|
778
|
+
const currentAttempts = this.missionSubmissionAttempts.get(nextId) ?? new Set();
|
|
779
|
+
for (const attempt of submissionAttempts) {
|
|
780
|
+
attempt.sessionId = nextId;
|
|
781
|
+
currentAttempts.add(attempt);
|
|
782
|
+
}
|
|
783
|
+
this.missionSubmissionAttempts.set(nextId, currentAttempts);
|
|
784
|
+
}
|
|
785
|
+
const continuationEpoch = this.missionContinuationEpochs.get(previousId);
|
|
786
|
+
if (continuationEpoch !== undefined) {
|
|
787
|
+
this.missionContinuationEpochs.delete(previousId);
|
|
788
|
+
this.missionContinuationEpochs.set(nextId, continuationEpoch);
|
|
789
|
+
}
|
|
766
790
|
},
|
|
767
791
|
migrateQueue: (previousId, nextId) => this.continuousRunCoordinator.migrateSession(previousId, nextId),
|
|
768
792
|
migrateRunnerState: (previousId, nextId) => this.runEventBridge.migrateRunnerSessionIdentity(previousId, nextId),
|
|
@@ -855,7 +879,7 @@ export class NodeConnector {
|
|
|
855
879
|
};
|
|
856
880
|
},
|
|
857
881
|
mission: async ({ session, objective, initiatedByUserId, secretEnvironment, personalInstructions, marAgentModel, marAgentModels, marAgentRuntimeScopeId }) => {
|
|
858
|
-
this.
|
|
882
|
+
this.invalidateMissionContinuation(session.id);
|
|
859
883
|
const prepared = this.prepareMissionSession(session);
|
|
860
884
|
const missionSession = prepared.session;
|
|
861
885
|
const activeRun = this.runtime.activeSessionRun(session.id);
|
|
@@ -863,33 +887,39 @@ export class NodeConnector {
|
|
|
863
887
|
this.continuousRunCoordinator.removeMissionQueue(session.id);
|
|
864
888
|
const mission = this.missionRuntime.start(session.id, objective);
|
|
865
889
|
this.publishMissionState(missionSession, mission);
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
890
|
+
const attempt = this.beginMissionSubmission(session.id);
|
|
891
|
+
const missionRef = { id: mission.id, revision: mission.revision, epoch: attempt.epoch };
|
|
892
|
+
let result;
|
|
893
|
+
try {
|
|
894
|
+
result = await this.sessionMessageService.submitMission({
|
|
895
|
+
sessionId: attempt.sessionId,
|
|
896
|
+
kind: 'start',
|
|
897
|
+
content: this.missionRuntime.prompt(attempt.sessionId, 'start'),
|
|
898
|
+
deliveryIntent: replacing ? 'REPLACE_CURRENT' : 'SEND',
|
|
899
|
+
mission: missionRef,
|
|
900
|
+
...(initiatedByUserId === undefined ? {} : { initiatedByUserId }),
|
|
901
|
+
secretEnvironment,
|
|
902
|
+
...(personalInstructions === undefined ? {} : { personalInstructions }),
|
|
903
|
+
...(marAgentModel === undefined ? {} : { marAgentModel }),
|
|
904
|
+
...(marAgentModels === undefined ? {} : { marAgentModels }),
|
|
905
|
+
...(marAgentRuntimeScopeId === undefined ? {} : { marAgentRuntimeScopeId })
|
|
906
|
+
});
|
|
907
|
+
if (!this.missionGenerationCurrent(attempt.sessionId, missionRef)) {
|
|
908
|
+
this.removeStaleMissionSubmission(attempt.sessionId, result);
|
|
909
|
+
throw new Error('MISSION_REVISION_STALE');
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
finally {
|
|
913
|
+
this.finishMissionSubmissionAttempt(attempt);
|
|
914
|
+
}
|
|
879
915
|
return {
|
|
880
916
|
...(isPlainRecord(result) ? result : { result }),
|
|
881
917
|
missionAccessChanged: prepared.changed
|
|
882
918
|
};
|
|
883
919
|
},
|
|
884
920
|
clearMission: (session) => {
|
|
885
|
-
this.
|
|
886
|
-
this.missionRuntime.clear(session.id);
|
|
887
|
-
this.continuousRunCoordinator.removeMissionQueue(session.id);
|
|
921
|
+
this.clearMissionState(session.id);
|
|
888
922
|
this.finishIdleMissionRun(session.id, 'SUCCEEDED');
|
|
889
|
-
this.commandStates.clear(session.id, 'mission');
|
|
890
|
-
this.emitWorkbenchEvent('session', {
|
|
891
|
-
session: this.sessionPresentationService.present(session)
|
|
892
|
-
});
|
|
893
923
|
return { session: this.sessionPresentationService.present(session), cleared: true };
|
|
894
924
|
}
|
|
895
925
|
});
|
|
@@ -915,34 +945,40 @@ export class NodeConnector {
|
|
|
915
945
|
return undefined;
|
|
916
946
|
const current = this.missionRuntime.get(session.id);
|
|
917
947
|
if (current?.status === 'active') {
|
|
948
|
+
this.invalidateMissionContinuation(session.id);
|
|
949
|
+
const resumed = this.missionRuntime.resume(session.id);
|
|
950
|
+
if (resumed !== current)
|
|
951
|
+
this.publishMissionState(session, resumed);
|
|
918
952
|
return {
|
|
919
953
|
runnerContent: content,
|
|
920
|
-
mission: {
|
|
954
|
+
mission: {
|
|
955
|
+
id: resumed.id,
|
|
956
|
+
revision: resumed.revision,
|
|
957
|
+
epoch: this.missionContinuationEpochs.get(session.id) ?? 0
|
|
958
|
+
},
|
|
921
959
|
activated: false
|
|
922
960
|
};
|
|
923
961
|
}
|
|
962
|
+
this.invalidateMissionContinuation(session.id);
|
|
924
963
|
const mission = this.missionRuntime.start(session.id, content);
|
|
925
964
|
this.publishMissionState(session, mission);
|
|
926
965
|
return {
|
|
927
966
|
runnerContent: this.missionRuntime.prompt(session.id, 'start'),
|
|
928
|
-
mission: {
|
|
967
|
+
mission: {
|
|
968
|
+
id: mission.id,
|
|
969
|
+
revision: mission.revision,
|
|
970
|
+
epoch: this.missionContinuationEpochs.get(session.id) ?? 0
|
|
971
|
+
},
|
|
929
972
|
activated: true
|
|
930
973
|
};
|
|
931
974
|
},
|
|
932
|
-
prepareMissionInsertion: (sessionId, submissionId) =>
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
this.missionRuntime.clear(session.id);
|
|
940
|
-
this.finishIdleMissionRun(session.id, 'FAILED');
|
|
941
|
-
this.commandStates.set(session.id, this.pendingMissionCommandState());
|
|
942
|
-
this.emitWorkbenchEvent('session', {
|
|
943
|
-
session: this.sessionPresentationService.present(session)
|
|
944
|
-
});
|
|
945
|
-
}
|
|
975
|
+
prepareMissionInsertion: (sessionId, submissionId, mission) => {
|
|
976
|
+
if (!this.missionGenerationCurrent(sessionId, mission))
|
|
977
|
+
return false;
|
|
978
|
+
this.continuousRunCoordinator.removePendingQueue(sessionId, submissionId);
|
|
979
|
+
return true;
|
|
980
|
+
},
|
|
981
|
+
missionPreparationFailed: (session, mission, activated) => this.handleMissionPreparationFailed(session, mission, activated)
|
|
946
982
|
});
|
|
947
983
|
this.sessionCatalogService = new SessionCatalogService({
|
|
948
984
|
database: () => {
|
|
@@ -1461,16 +1497,70 @@ export class NodeConnector {
|
|
|
1461
1497
|
this.missionRetryTimers.delete(sessionId);
|
|
1462
1498
|
this.missionContinuationPending.delete(sessionId);
|
|
1463
1499
|
}
|
|
1500
|
+
invalidateMissionContinuation(sessionId) {
|
|
1501
|
+
this.missionContinuationEpochs.set(sessionId, (this.missionContinuationEpochs.get(sessionId) ?? 0) + 1);
|
|
1502
|
+
this.clearMissionRetry(sessionId);
|
|
1503
|
+
}
|
|
1504
|
+
retireMissionContinuation(sessionId) {
|
|
1505
|
+
this.invalidateMissionContinuation(sessionId);
|
|
1506
|
+
this.missionContinuationEpochs.delete(sessionId);
|
|
1507
|
+
}
|
|
1508
|
+
missionGenerationCurrent(sessionId, mission) {
|
|
1509
|
+
const current = this.missionRuntime.get(sessionId);
|
|
1510
|
+
return ((this.missionContinuationEpochs.get(sessionId) ?? 0) === mission.epoch &&
|
|
1511
|
+
current?.status === 'active' &&
|
|
1512
|
+
!current.continuationPaused &&
|
|
1513
|
+
current.id === mission.id &&
|
|
1514
|
+
current.revision === mission.revision);
|
|
1515
|
+
}
|
|
1516
|
+
beginMissionSubmission(sessionId, continuationEpoch = this.missionContinuationEpochs.get(sessionId) ?? 0) {
|
|
1517
|
+
const attempt = { sessionId, epoch: continuationEpoch };
|
|
1518
|
+
const attempts = this.missionSubmissionAttempts.get(sessionId) ?? new Set();
|
|
1519
|
+
attempts.add(attempt);
|
|
1520
|
+
this.missionSubmissionAttempts.set(sessionId, attempts);
|
|
1521
|
+
return attempt;
|
|
1522
|
+
}
|
|
1523
|
+
finishMissionSubmissionAttempt(attempt) {
|
|
1524
|
+
const attempts = this.missionSubmissionAttempts.get(attempt.sessionId);
|
|
1525
|
+
attempts?.delete(attempt);
|
|
1526
|
+
if (attempts?.size === 0)
|
|
1527
|
+
this.missionSubmissionAttempts.delete(attempt.sessionId);
|
|
1528
|
+
}
|
|
1529
|
+
finishMissionContinuationAttempt(attempt) {
|
|
1530
|
+
if (this.missionContinuationPending.get(attempt.sessionId) === attempt)
|
|
1531
|
+
this.missionContinuationPending.delete(attempt.sessionId);
|
|
1532
|
+
this.finishMissionSubmissionAttempt(attempt);
|
|
1533
|
+
}
|
|
1534
|
+
removeStaleMissionSubmission(sessionId, result) {
|
|
1535
|
+
if (!isPlainRecord(result))
|
|
1536
|
+
return;
|
|
1537
|
+
if (typeof result.queueItemId === 'string') {
|
|
1538
|
+
const item = this.runtime.getQueueItem(result.queueItemId);
|
|
1539
|
+
if (item?.sessionId === sessionId)
|
|
1540
|
+
this.continuousRunCoordinator.deleteQueueItem(item.id);
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (!isPlainRecord(result.run) || typeof result.run.id !== 'string')
|
|
1544
|
+
return;
|
|
1545
|
+
const run = this.runtime.getRun(result.run.id);
|
|
1546
|
+
if (run?.sessionId === sessionId && this.runtime.currentExecutionId(run.id) === undefined)
|
|
1547
|
+
this.runEventBridge.emitRun(run.id, 'run.completed', { code: 'MISSION_SUBMISSION_STALE' }, 'INTERRUPTED');
|
|
1548
|
+
}
|
|
1464
1549
|
async enqueueMissionContinuation(input) {
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1550
|
+
const attempt = this.beginMissionSubmission(input.sessionId, input.continuationEpoch);
|
|
1551
|
+
const generation = { ...input.mission, epoch: attempt.epoch };
|
|
1552
|
+
if (!this.missionGenerationCurrent(attempt.sessionId, generation)) {
|
|
1553
|
+
this.finishMissionSubmissionAttempt(attempt);
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
this.missionContinuationPending.set(input.sessionId, attempt);
|
|
1467
1557
|
try {
|
|
1468
|
-
await this.sessionMessageService.submitMission({
|
|
1469
|
-
sessionId:
|
|
1558
|
+
const result = await this.sessionMessageService.submitMission({
|
|
1559
|
+
sessionId: attempt.sessionId,
|
|
1470
1560
|
kind: 'continue',
|
|
1471
|
-
content: this.missionRuntime.prompt(
|
|
1472
|
-
deliveryIntent: 'QUEUE',
|
|
1473
|
-
mission:
|
|
1561
|
+
content: this.missionRuntime.prompt(attempt.sessionId, 'continue'),
|
|
1562
|
+
deliveryIntent: input.deliveryIntent ?? 'QUEUE',
|
|
1563
|
+
mission: generation,
|
|
1474
1564
|
...(input.initiatedByUserId === undefined
|
|
1475
1565
|
? {}
|
|
1476
1566
|
: { initiatedByUserId: input.initiatedByUserId }),
|
|
@@ -1484,72 +1574,95 @@ export class NodeConnector {
|
|
|
1484
1574
|
? {}
|
|
1485
1575
|
: { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
|
|
1486
1576
|
});
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
current.revision !== input.mission.revision) {
|
|
1491
|
-
this.continuousRunCoordinator.removeMissionQueue(input.sessionId, current?.status === 'active' ? { id: current.id, revision: current.revision } : undefined);
|
|
1492
|
-
if (this.missionContinuationPending.get(input.sessionId) === attemptKey)
|
|
1493
|
-
this.missionContinuationPending.delete(input.sessionId);
|
|
1577
|
+
if (!this.missionGenerationCurrent(attempt.sessionId, generation)) {
|
|
1578
|
+
this.removeStaleMissionSubmission(attempt.sessionId, result);
|
|
1579
|
+
this.finishMissionContinuationAttempt(attempt);
|
|
1494
1580
|
return;
|
|
1495
1581
|
}
|
|
1496
|
-
this.
|
|
1582
|
+
const retry = this.missionRetryTimers.get(attempt.sessionId);
|
|
1583
|
+
if (retry !== undefined)
|
|
1584
|
+
clearTimeout(retry.timer);
|
|
1585
|
+
this.missionRetryTimers.delete(attempt.sessionId);
|
|
1586
|
+
this.finishMissionContinuationAttempt(attempt);
|
|
1497
1587
|
}
|
|
1498
1588
|
catch (error) {
|
|
1499
|
-
const current = this.missionRuntime.get(input.sessionId);
|
|
1500
1589
|
const failedAttempts = (input.failedAttempts ?? 0) + 1;
|
|
1501
1590
|
nodeLog('mission.continuation.enqueue.failed', {
|
|
1502
|
-
sessionId:
|
|
1591
|
+
sessionId: attempt.sessionId,
|
|
1503
1592
|
missionId: input.mission.id,
|
|
1504
1593
|
revision: input.mission.revision,
|
|
1505
1594
|
attempt: failedAttempts,
|
|
1506
1595
|
code: safeErrorCode(error, 'MISSION_CONTINUATION_ENQUEUE_FAILED')
|
|
1507
1596
|
});
|
|
1508
|
-
if (
|
|
1509
|
-
|
|
1510
|
-
current.revision !== input.mission.revision) {
|
|
1511
|
-
if (this.missionContinuationPending.get(input.sessionId) === attemptKey)
|
|
1512
|
-
this.missionContinuationPending.delete(input.sessionId);
|
|
1597
|
+
if (!this.missionGenerationCurrent(attempt.sessionId, generation)) {
|
|
1598
|
+
this.finishMissionContinuationAttempt(attempt);
|
|
1513
1599
|
return;
|
|
1514
1600
|
}
|
|
1515
|
-
const session = this.runtime.getAgentSession(
|
|
1601
|
+
const session = this.runtime.getAgentSession(attempt.sessionId);
|
|
1516
1602
|
if (failedAttempts > 3 || session === undefined) {
|
|
1517
|
-
this.clearMissionRetry(
|
|
1603
|
+
this.clearMissionRetry(attempt.sessionId);
|
|
1518
1604
|
if (failedAttempts > 3) {
|
|
1519
|
-
const blocked = this.missionRuntime.blockAfterRuntimeFailure(
|
|
1520
|
-
this.continuousRunCoordinator.removeMissionQueue(
|
|
1521
|
-
this.
|
|
1605
|
+
const blocked = this.missionRuntime.blockAfterRuntimeFailure(attempt.sessionId, input.mission.id, input.mission.revision);
|
|
1606
|
+
this.continuousRunCoordinator.removeMissionQueue(attempt.sessionId);
|
|
1607
|
+
this.missionContinuationEpochs.delete(attempt.sessionId);
|
|
1608
|
+
this.finishIdleMissionRun(attempt.sessionId, 'FAILED');
|
|
1522
1609
|
if (session !== undefined)
|
|
1523
1610
|
this.publishMissionState(session, blocked);
|
|
1524
1611
|
}
|
|
1525
1612
|
return;
|
|
1526
1613
|
}
|
|
1527
|
-
this.
|
|
1614
|
+
this.finishMissionContinuationAttempt(attempt);
|
|
1615
|
+
const previousRetry = this.missionRetryTimers.get(attempt.sessionId);
|
|
1616
|
+
if (previousRetry !== undefined)
|
|
1617
|
+
clearTimeout(previousRetry.timer);
|
|
1528
1618
|
const retry = {
|
|
1529
|
-
sessionId:
|
|
1619
|
+
sessionId: attempt.sessionId,
|
|
1530
1620
|
timer: setTimeout(() => {
|
|
1531
|
-
|
|
1532
|
-
if (current?.status !== 'active' ||
|
|
1533
|
-
current.id !== input.mission.id ||
|
|
1534
|
-
current.revision !== input.mission.revision)
|
|
1621
|
+
if (!this.missionGenerationCurrent(retry.sessionId, generation))
|
|
1535
1622
|
return;
|
|
1536
1623
|
void this.enqueueMissionContinuation({
|
|
1537
1624
|
...input,
|
|
1538
1625
|
sessionId: retry.sessionId,
|
|
1539
|
-
failedAttempts
|
|
1626
|
+
failedAttempts,
|
|
1627
|
+
continuationEpoch: attempt.epoch
|
|
1540
1628
|
});
|
|
1541
1629
|
}, 15_000)
|
|
1542
1630
|
};
|
|
1543
|
-
this.missionRetryTimers.set(
|
|
1631
|
+
this.missionRetryTimers.set(attempt.sessionId, retry);
|
|
1544
1632
|
retry.timer.unref();
|
|
1545
1633
|
}
|
|
1546
1634
|
}
|
|
1635
|
+
handleMissionPreparationFailed(session, mission, activated) {
|
|
1636
|
+
if (!activated || !this.missionGenerationCurrent(session.id, mission))
|
|
1637
|
+
return;
|
|
1638
|
+
this.missionRuntime.clear(session.id);
|
|
1639
|
+
this.finishIdleMissionRun(session.id, 'FAILED');
|
|
1640
|
+
this.commandStates.set(session.id, this.pendingMissionCommandState());
|
|
1641
|
+
this.emitWorkbenchEvent('session', {
|
|
1642
|
+
session: this.sessionPresentationService.present(session)
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1547
1645
|
finishIdleMissionRun(sessionId, status) {
|
|
1548
1646
|
const run = this.runtime.activeSessionRun(sessionId);
|
|
1549
1647
|
if (run === undefined || this.runtime.currentExecutionId(run.id) !== undefined)
|
|
1550
1648
|
return;
|
|
1551
1649
|
this.runEventBridge.emitRun(run.id, status === 'SUCCEEDED' ? 'run.completed' : 'run.failed', status === 'SUCCEEDED' ? {} : { code: 'MISSION_CONTINUATION_FAILED' }, status);
|
|
1552
1650
|
}
|
|
1651
|
+
clearMissionState(sessionId) {
|
|
1652
|
+
this.retireMissionContinuation(sessionId);
|
|
1653
|
+
const removed = this.missionRuntime.clear(sessionId);
|
|
1654
|
+
this.continuousRunCoordinator.removeMissionQueue(sessionId);
|
|
1655
|
+
const commandState = this.commandStates.get(sessionId, 'mission');
|
|
1656
|
+
if (commandState !== undefined)
|
|
1657
|
+
this.commandStates.clear(sessionId, 'mission');
|
|
1658
|
+
if (!removed && commandState === undefined)
|
|
1659
|
+
return;
|
|
1660
|
+
const session = this.runtime.getAgentSession(sessionId);
|
|
1661
|
+
if (session !== undefined)
|
|
1662
|
+
this.emitWorkbenchEvent('session', {
|
|
1663
|
+
session: this.sessionPresentationService.present(session)
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1553
1666
|
prepareMissionSession(session) {
|
|
1554
1667
|
const access = missionUnrestrictedAccess(session.runner);
|
|
1555
1668
|
const changed = session.access !== access;
|
|
@@ -1604,6 +1717,8 @@ export class NodeConnector {
|
|
|
1604
1717
|
clearTimeout(retry.timer);
|
|
1605
1718
|
this.missionRetryTimers.clear();
|
|
1606
1719
|
this.missionContinuationPending.clear();
|
|
1720
|
+
this.missionSubmissionAttempts.clear();
|
|
1721
|
+
this.missionContinuationEpochs.clear();
|
|
1607
1722
|
this.runEventService.clear();
|
|
1608
1723
|
this.sessionContextService.dispose();
|
|
1609
1724
|
for (const run of this.claudeClient.execution.runs.values())
|
|
@@ -1655,6 +1770,9 @@ export class NodeConnector {
|
|
|
1655
1770
|
emitRunEvent = (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status);
|
|
1656
1771
|
emitConversationItem = (runId, item) => {
|
|
1657
1772
|
this.runEventService.flushPendingText(runId);
|
|
1773
|
+
if (this.continuousRunCoordinator.pending(runId)?.mission !== undefined &&
|
|
1774
|
+
isMissionProgressItem(item))
|
|
1775
|
+
this.missionExecutionProgress.add(runId);
|
|
1658
1776
|
const itemId = isPlainRecord(item) && typeof item.itemId === 'string' ? item.itemId : undefined;
|
|
1659
1777
|
const itemKind = isPlainRecord(item) && typeof item.kind === 'string' ? item.kind : undefined;
|
|
1660
1778
|
const turn = this.runtime.conversationTurnForRun(runId);
|
|
@@ -1832,3 +1950,13 @@ export class NodeConnector {
|
|
|
1832
1950
|
this.controlChannel.sendEnvelope(envelope);
|
|
1833
1951
|
}
|
|
1834
1952
|
}
|
|
1953
|
+
function isMissionProgressItem(item) {
|
|
1954
|
+
if (!isPlainRecord(item) || typeof item.kind !== 'string')
|
|
1955
|
+
return false;
|
|
1956
|
+
if (item.kind === 'command_execution' || item.kind === 'file_change')
|
|
1957
|
+
return true;
|
|
1958
|
+
if (item.kind !== 'tool_call' || !isPlainRecord(item.payload))
|
|
1959
|
+
return false;
|
|
1960
|
+
const toolName = item.payload.toolName;
|
|
1961
|
+
return typeof toolName === 'string' && !toolName.endsWith('mar_get_mission');
|
|
1962
|
+
}
|
|
@@ -30,7 +30,7 @@ export class MarAgentManagedRunController {
|
|
|
30
30
|
constructor(runner, host, options = {}) {
|
|
31
31
|
this.runner = runner;
|
|
32
32
|
this.host = host;
|
|
33
|
-
this.#resourceIdleTtlMs = options.resourceIdleTtlMs ?? 60_000;
|
|
33
|
+
this.#resourceIdleTtlMs = options.resourceIdleTtlMs ?? 10 * 60_000;
|
|
34
34
|
this.#createAgent = options.createAgent ?? createMarAgent;
|
|
35
35
|
this.#modelFactory = options.modelFactory ?? {
|
|
36
36
|
create: (configurations) => configurations.map((configuration) => {
|
|
@@ -241,6 +241,8 @@ export class MarAgentManagedRunController {
|
|
|
241
241
|
});
|
|
242
242
|
this.host.emit(input.runId, 'run.started', {}, 'STARTING');
|
|
243
243
|
let runtime;
|
|
244
|
+
let executionMcpLease;
|
|
245
|
+
let executionMcpBinding;
|
|
244
246
|
try {
|
|
245
247
|
const sessionKey = input.externalSessionId ?? input.sessionId;
|
|
246
248
|
const acquisitionKeys = [...new Set([sessionKey, input.sessionId])];
|
|
@@ -268,6 +270,17 @@ export class MarAgentManagedRunController {
|
|
|
268
270
|
throw new Error('MAR_AGENT_SESSION_RUNTIME_ACTIVE');
|
|
269
271
|
runtime.activeRunId = input.runId;
|
|
270
272
|
runtime.activeAccess = input.access;
|
|
273
|
+
const executionMcpServers = input.mcpServers.filter((server) => server.lifecycle === 'EXECUTION');
|
|
274
|
+
if (executionMcpServers.length > 0) {
|
|
275
|
+
executionMcpLease = await this.#mcpClients.acquire(executionMcpServers, input.cwd, {
|
|
276
|
+
reusable: false
|
|
277
|
+
});
|
|
278
|
+
const mcp = executionMcpLease.pool;
|
|
279
|
+
executionMcpBinding = runtime.host.bindExecutionMcp({
|
|
280
|
+
tools: mcp.definitions(),
|
|
281
|
+
call: async (call) => mcp.call(call.name, call.arguments)
|
|
282
|
+
});
|
|
283
|
+
}
|
|
271
284
|
const session = runtime.session;
|
|
272
285
|
const reasoningEffort = input.effort
|
|
273
286
|
? marAgentReasoningEffortSchema.parse(input.effort)
|
|
@@ -332,6 +345,8 @@ export class MarAgentManagedRunController {
|
|
|
332
345
|
this.#normalizers.delete(input.runId);
|
|
333
346
|
this.host.active.delete(input.runId);
|
|
334
347
|
this.#executions.delete(input.runId);
|
|
348
|
+
runtime?.host.unbindExecutionMcp(executionMcpBinding);
|
|
349
|
+
await executionMcpLease?.release().catch(() => undefined);
|
|
335
350
|
if (runtime !== undefined)
|
|
336
351
|
await this.#finishRuntimeExecution(input.runId, runtime).catch(() => undefined);
|
|
337
352
|
}
|
|
@@ -341,20 +356,14 @@ export class MarAgentManagedRunController {
|
|
|
341
356
|
delete runtime.activeRunId;
|
|
342
357
|
delete runtime.activeAccess;
|
|
343
358
|
}
|
|
344
|
-
|
|
345
|
-
this.#scheduleRuntimeRelease(runtime);
|
|
346
|
-
else
|
|
347
|
-
void this.#releaseRuntime(runtime).catch(() => undefined);
|
|
359
|
+
this.#scheduleRuntimeRelease(runtime);
|
|
348
360
|
}
|
|
349
361
|
async #finishRuntimeExecution(runId, runtime) {
|
|
350
362
|
if (runtime.activeRunId === runId) {
|
|
351
363
|
delete runtime.activeRunId;
|
|
352
364
|
delete runtime.activeAccess;
|
|
353
365
|
}
|
|
354
|
-
|
|
355
|
-
this.#scheduleRuntimeRelease(runtime);
|
|
356
|
-
else
|
|
357
|
-
await this.#releaseRuntime(runtime);
|
|
366
|
+
this.#scheduleRuntimeRelease(runtime);
|
|
358
367
|
}
|
|
359
368
|
async #acquireRuntime(input) {
|
|
360
369
|
if (this.#disposed)
|
|
@@ -424,10 +433,8 @@ export class MarAgentManagedRunController {
|
|
|
424
433
|
async #createRuntime(input, sessionKey, fingerprint, hostConfiguration) {
|
|
425
434
|
const dynamicCredentials = input.models.some((model) => model.credential?.type === 'RUNTIME_TICKET' ||
|
|
426
435
|
model.imageGeneration?.credential?.type === 'RUNTIME_TICKET');
|
|
427
|
-
const
|
|
428
|
-
const mcpLease = await this.#mcpClients.acquire(
|
|
429
|
-
reusable: retainable
|
|
430
|
-
});
|
|
436
|
+
const sharedMcpServers = input.mcpServers.filter((server) => server.lifecycle !== 'EXECUTION');
|
|
437
|
+
const mcpLease = await this.#mcpClients.acquire(sharedMcpServers, input.cwd);
|
|
431
438
|
let agent;
|
|
432
439
|
let runtime;
|
|
433
440
|
try {
|
|
@@ -473,8 +480,8 @@ export class MarAgentManagedRunController {
|
|
|
473
480
|
fingerprint,
|
|
474
481
|
agent,
|
|
475
482
|
session,
|
|
483
|
+
host: localHost,
|
|
476
484
|
mcpLease,
|
|
477
|
-
retainable,
|
|
478
485
|
dynamicCredentials,
|
|
479
486
|
released: false
|
|
480
487
|
};
|
|
@@ -672,7 +679,7 @@ function sessionRuntimeFingerprint(input, hostConfiguration) {
|
|
|
672
679
|
defaultModelId: input.model.id,
|
|
673
680
|
models: input.models.map(runtimeModelFingerprint),
|
|
674
681
|
personalInstructions: input.personalInstructions ?? '',
|
|
675
|
-
mcpServers: input.mcpServers,
|
|
682
|
+
mcpServers: input.mcpServers.filter((server) => server.lifecycle !== 'EXECUTION'),
|
|
676
683
|
environment: Object.entries(hostConfiguration.environment)
|
|
677
684
|
.filter((entry) => entry[1] !== undefined)
|
|
678
685
|
.sort(([left], [right]) => left.localeCompare(right)),
|
|
@@ -683,9 +690,6 @@ function sessionRuntimeFingerprint(input, hostConfiguration) {
|
|
|
683
690
|
export function allowsUntrustedWebFetchTls(environment) {
|
|
684
691
|
return environment['MAR_AGENT_WEB_FETCH_ALLOW_UNTRUSTED_TLS'] !== '0';
|
|
685
692
|
}
|
|
686
|
-
function executionScopedMcp(servers) {
|
|
687
|
-
return servers.some((server) => server.lifecycle === 'EXECUTION');
|
|
688
|
-
}
|
|
689
693
|
async function resolveMarAgentHostRuntimeConfiguration(secretEnvironment = {}) {
|
|
690
694
|
const environment = { ...process.env, ...secretEnvironment };
|
|
691
695
|
const ripgrep = await availableBundledRipgrep();
|
|
@@ -242,6 +242,11 @@ export class ContinuousRunCoordinator {
|
|
|
242
242
|
const item = this.options.runtime.getQueueItem(itemId);
|
|
243
243
|
if (item === undefined)
|
|
244
244
|
return;
|
|
245
|
+
if (!this.missionCurrent(pending)) {
|
|
246
|
+
this.deleteQueueItem(item.id);
|
|
247
|
+
this.schedule(sessionId);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
245
250
|
if (this.options.runtime.claimNextQueueItem(sessionId, withNonImageAttachmentPrompt(item.content, pending.attachmentPaths)) === undefined)
|
|
246
251
|
return;
|
|
247
252
|
const execution = this.options.runtime.createExecution(active.id);
|
|
@@ -257,6 +262,10 @@ export class ContinuousRunCoordinator {
|
|
|
257
262
|
this.options.emitConversation(turn);
|
|
258
263
|
}
|
|
259
264
|
else if (executionId === undefined && pending !== undefined) {
|
|
265
|
+
if (!this.missionCurrent(pending)) {
|
|
266
|
+
this.rejectStaleMission(active.id);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
260
269
|
const execution = this.options.runtime.createExecution(active.id);
|
|
261
270
|
executionId = execution.id;
|
|
262
271
|
this.preparedInputs.delete(active.id);
|
|
@@ -268,11 +277,20 @@ export class ContinuousRunCoordinator {
|
|
|
268
277
|
return;
|
|
269
278
|
if (this.dispatchedExecutions.has(executionId))
|
|
270
279
|
return;
|
|
280
|
+
if (!this.missionCurrent(pending)) {
|
|
281
|
+
this.rejectStaleMission(executionId);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
271
284
|
this.dispatchedExecutions.add(executionId);
|
|
272
285
|
try {
|
|
273
286
|
const missionMcp = pending.mission === undefined
|
|
274
287
|
? undefined
|
|
275
288
|
: await this.options.missionMcp?.(pending);
|
|
289
|
+
if (!this.missionCurrent(pending)) {
|
|
290
|
+
this.dispatchedExecutions.delete(executionId);
|
|
291
|
+
this.rejectStaleMission(executionId);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
276
294
|
this.options.control('run.start', {
|
|
277
295
|
runId: executionId,
|
|
278
296
|
sessionId: pending.sessionId,
|
|
@@ -395,6 +413,11 @@ export class ContinuousRunCoordinator {
|
|
|
395
413
|
const item = this.options.runtime.getQueueItem(itemId);
|
|
396
414
|
if (item === undefined)
|
|
397
415
|
throw new Error('QUEUE_ITEM_INVALID');
|
|
416
|
+
if (!this.missionCurrent(pending)) {
|
|
417
|
+
this.deleteQueueItem(item.id);
|
|
418
|
+
this.startIdleQueue(sessionId);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
398
421
|
const { run, turn } = this.options.runtime.startRunFromQueueItem(itemId, withNonImageAttachmentPrompt(item.content, pending.attachmentPaths));
|
|
399
422
|
this.preparedInputs.delete(itemId);
|
|
400
423
|
this.preparedInputs.set(run.id, { ...pending, runId: run.id });
|
|
@@ -410,6 +433,14 @@ export class ContinuousRunCoordinator {
|
|
|
410
433
|
throw new Error('QUEUE_INPUT_LOST');
|
|
411
434
|
return item;
|
|
412
435
|
}
|
|
436
|
+
missionCurrent(pending) {
|
|
437
|
+
return (pending.mission === undefined ||
|
|
438
|
+
this.options.missionCurrent?.(pending) !== false);
|
|
439
|
+
}
|
|
440
|
+
rejectStaleMission(runId) {
|
|
441
|
+
this.remove(runId);
|
|
442
|
+
this.options.emitRun(runId, 'run.completed', { code: 'MISSION_SUBMISSION_STALE' }, 'INTERRUPTED');
|
|
443
|
+
}
|
|
413
444
|
isMessageRun(run) {
|
|
414
445
|
return this.options.runtime.findMessageRun(run.sessionId, run.clientMessageId)?.id === run.id;
|
|
415
446
|
}
|
|
@@ -62,7 +62,9 @@ export class MissionRuntime {
|
|
|
62
62
|
status: 'active',
|
|
63
63
|
turns: 0,
|
|
64
64
|
retryRequired: false,
|
|
65
|
-
retryStartedAtTurn: null
|
|
65
|
+
retryStartedAtTurn: null,
|
|
66
|
+
consecutiveNoProgressTurns: 0,
|
|
67
|
+
continuationPaused: false
|
|
66
68
|
};
|
|
67
69
|
this.missions.set(sessionId, mission);
|
|
68
70
|
this.executionFailures.delete(sessionId);
|
|
@@ -77,22 +79,70 @@ export class MissionRuntime {
|
|
|
77
79
|
this.missions.set(sessionId, mission);
|
|
78
80
|
return mission;
|
|
79
81
|
}
|
|
80
|
-
recordExecutionResult(sessionId,
|
|
82
|
+
recordExecutionResult(sessionId, input) {
|
|
81
83
|
const current = this.missions.get(sessionId);
|
|
82
84
|
if (current?.status !== 'active')
|
|
83
|
-
return
|
|
84
|
-
if (!failed) {
|
|
85
|
+
return 'continue';
|
|
86
|
+
if (!input.failed && !input.progressed) {
|
|
85
87
|
this.executionFailures.delete(sessionId);
|
|
86
|
-
|
|
88
|
+
const consecutiveNoProgressTurns = current.consecutiveNoProgressTurns + 1;
|
|
89
|
+
const paused = consecutiveNoProgressTurns >= 3;
|
|
90
|
+
const mission = {
|
|
91
|
+
...current,
|
|
92
|
+
consecutiveNoProgressTurns,
|
|
93
|
+
continuationPaused: paused
|
|
94
|
+
};
|
|
95
|
+
this.missions.set(sessionId, mission);
|
|
96
|
+
this.changed(mission);
|
|
97
|
+
return paused ? 'paused' : 'continue';
|
|
98
|
+
}
|
|
99
|
+
if (!input.failed) {
|
|
100
|
+
this.executionFailures.delete(sessionId);
|
|
101
|
+
if (current.consecutiveNoProgressTurns > 0 || current.continuationPaused) {
|
|
102
|
+
const mission = {
|
|
103
|
+
...current,
|
|
104
|
+
consecutiveNoProgressTurns: 0,
|
|
105
|
+
continuationPaused: false
|
|
106
|
+
};
|
|
107
|
+
this.missions.set(sessionId, mission);
|
|
108
|
+
this.changed(mission);
|
|
109
|
+
}
|
|
110
|
+
return 'continue';
|
|
111
|
+
}
|
|
112
|
+
if (current.consecutiveNoProgressTurns > 0 || current.continuationPaused) {
|
|
113
|
+
const mission = {
|
|
114
|
+
...current,
|
|
115
|
+
consecutiveNoProgressTurns: 0,
|
|
116
|
+
continuationPaused: false
|
|
117
|
+
};
|
|
118
|
+
this.missions.set(sessionId, mission);
|
|
87
119
|
}
|
|
88
120
|
const failures = (this.executionFailures.get(sessionId) ?? 0) + 1;
|
|
89
121
|
this.executionFailures.set(sessionId, failures);
|
|
90
122
|
if (failures <= 3)
|
|
91
|
-
return
|
|
92
|
-
const blocked = {
|
|
123
|
+
return 'continue';
|
|
124
|
+
const blocked = {
|
|
125
|
+
...(this.missions.get(sessionId) ?? current),
|
|
126
|
+
status: 'blocked'
|
|
127
|
+
};
|
|
93
128
|
this.missions.set(sessionId, blocked);
|
|
94
129
|
this.changed(blocked);
|
|
95
|
-
return
|
|
130
|
+
return 'blocked';
|
|
131
|
+
}
|
|
132
|
+
resume(sessionId) {
|
|
133
|
+
const current = this.missions.get(sessionId);
|
|
134
|
+
if (current?.status !== 'active')
|
|
135
|
+
return current;
|
|
136
|
+
if (!current.continuationPaused && current.consecutiveNoProgressTurns === 0)
|
|
137
|
+
return current;
|
|
138
|
+
const mission = {
|
|
139
|
+
...current,
|
|
140
|
+
consecutiveNoProgressTurns: 0,
|
|
141
|
+
continuationPaused: false
|
|
142
|
+
};
|
|
143
|
+
this.missions.set(sessionId, mission);
|
|
144
|
+
this.changed(mission);
|
|
145
|
+
return mission;
|
|
96
146
|
}
|
|
97
147
|
blockAfterRuntimeFailure(sessionId, missionId, revision) {
|
|
98
148
|
const current = this.requireCurrent(sessionId, missionId, revision);
|
|
@@ -137,7 +187,7 @@ export class MissionRuntime {
|
|
|
137
187
|
if (mission?.status !== 'active')
|
|
138
188
|
throw new Error('MISSION_NOT_ACTIVE');
|
|
139
189
|
const heading = kind === 'start' ? 'A Mission has started or changed.' : 'Continue the active Mission.';
|
|
140
|
-
return `${START_OPEN}\n${heading}\n\nOriginal objective:\n${mission.objective}\n\
|
|
190
|
+
return `${START_OPEN}\n${heading}\n\nOriginal objective:\n${mission.objective}\n\nContinue executing this Mission across turns:\n- Work on the objective now. If it is incomplete, use tools to finish a coherent unit or obtain decisive evidence; do not stop at an acknowledgement, status, plan, or promise.\n- Preserve the original scope. Before completion, review the full objective and verify it with real evidence, then call mar_update_mission with status=completed.\n- Adapt and retry after individual failures. Request blocked only after the same blocker persists for three turns and the required additional retry.\n- Use mar_get_mission only when you need to reread the authoritative objective or status.\n${START_CLOSE}`;
|
|
141
191
|
}
|
|
142
192
|
requireCurrent(sessionId, missionId, revision) {
|
|
143
193
|
const current = this.missions.get(sessionId);
|
|
@@ -55,10 +55,11 @@ export class RunWorkbenchService {
|
|
|
55
55
|
const run = this.options.runtime.activeSessionRun(session.id);
|
|
56
56
|
if (run === undefined || isTerminalRunStatus(run.status))
|
|
57
57
|
throw new Error('RUN_NOT_FOUND');
|
|
58
|
-
|
|
58
|
+
const result = this.cancelOrInterrupt('run.interrupt', {
|
|
59
59
|
runId: run.id,
|
|
60
60
|
__requestUserId: input.__requestUserId
|
|
61
61
|
});
|
|
62
|
+
return result;
|
|
62
63
|
}
|
|
63
64
|
cancelOrInterrupt(operation, input) {
|
|
64
65
|
const run = this.requireOwnedRun(input.runId, input.__requestUserId);
|
|
@@ -66,14 +67,19 @@ export class RunWorkbenchService {
|
|
|
66
67
|
const executionId = this.options.runtime.currentExecutionId(run.id);
|
|
67
68
|
const turn = this.options.runtime.conversationTurnForRun(run.id);
|
|
68
69
|
const controlRunId = executionId ?? (turn === undefined || turn.userItemId === null ? run.id : undefined);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
try {
|
|
71
|
+
this.options.pauseQueue(run.sessionId);
|
|
72
|
+
if (controlRunId === undefined) {
|
|
73
|
+
this.options.markInterrupted(run.id);
|
|
74
|
+
this.options.emitRun(run.id, 'run.interrupted', { code: 'USER_INTERRUPTED' }, 'INTERRUPTED');
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
this.options.markInterrupted(controlRunId);
|
|
78
|
+
this.options.control(operation, { runId: controlRunId });
|
|
79
|
+
}
|
|
73
80
|
}
|
|
74
|
-
|
|
75
|
-
this.options.
|
|
76
|
-
this.options.control(operation, { runId: controlRunId });
|
|
81
|
+
finally {
|
|
82
|
+
this.options.runInterrupted(run.sessionId);
|
|
77
83
|
}
|
|
78
84
|
return { run: next };
|
|
79
85
|
}
|
|
@@ -168,8 +168,9 @@ export class SessionMessageService {
|
|
|
168
168
|
...(runner.serviceTier() === 'fast' ? { serviceTier: 'fast' } : {})
|
|
169
169
|
});
|
|
170
170
|
this.options.attachments.cache(submissionId, imageAttachments);
|
|
171
|
-
if (missionMessage !== undefined
|
|
172
|
-
this.options.prepareMissionInsertion(session.id, submissionId)
|
|
171
|
+
if (missionMessage !== undefined &&
|
|
172
|
+
!this.options.prepareMissionInsertion(session.id, submissionId, missionMessage.mission))
|
|
173
|
+
throw new Error('MISSION_REVISION_STALE');
|
|
173
174
|
const delivery = this.options.coordinator.settle({
|
|
174
175
|
submissionId,
|
|
175
176
|
intent,
|
|
@@ -269,5 +270,6 @@ function isMissionRef(value) {
|
|
|
269
270
|
return (value !== null &&
|
|
270
271
|
typeof value === 'object' &&
|
|
271
272
|
typeof value.id === 'string' &&
|
|
272
|
-
Number.isInteger(value.revision)
|
|
273
|
+
Number.isInteger(value.revision) &&
|
|
274
|
+
Number.isInteger(value.epoch));
|
|
273
275
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.90",
|
|
4
4
|
"description": "MyAgentRoam Node runtime CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"node-pty": "1.1.0",
|
|
29
29
|
"ws": "^8.21.3",
|
|
30
30
|
"zod": "4.4.3",
|
|
31
|
-
"@myagentroam/agent": "0.9.
|
|
32
|
-
"@myagentroam/protocol": "0.9.
|
|
31
|
+
"@myagentroam/agent": "0.9.90",
|
|
32
|
+
"@myagentroam/protocol": "0.9.90"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/ws": "^8.18.1"
|