@myagentroam/node 0.9.116 → 0.9.118
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 -224
- package/dist/native-session-history.js +2 -2
- package/dist/runner/abstract-runner.js +3 -3
- package/dist/runner/codex/conversation-parser.js +2 -2
- package/dist/runner/codex/managed-run-controller.js +6 -6
- package/dist/runner/mar-agent/history-projection.js +2 -2
- package/dist/runner/mar-agent-runner.js +5 -5
- package/dist/runner/opencode/conversation-parser.js +3 -3
- package/dist/runner-command-engine.js +1 -1
- package/dist/runner-profiles.js +8 -8
- package/dist/runtime-state.js +1 -1
- package/dist/service/continuous-run-coordinator.js +20 -21
- package/dist/service/{mission-mcp-server.js → goal-mcp-server.js} +28 -28
- package/dist/service/{mission-mcp-stdio.js → goal-mcp-stdio.js} +8 -8
- package/dist/service/goal-runtime.js +250 -0
- package/dist/service/mar-agent-model-transport-test-service.js +2 -19
- package/dist/service/session-command-service.js +4 -4
- package/dist/service/session-identity-service.js +1 -1
- package/dist/service/session-message-service.js +25 -29
- package/dist/service/workspace-queue-workbench-service.js +1 -1
- package/dist/util/runner-native-session-parsers.js +3 -3
- package/package.json +4 -4
- package/dist/service/mission-runtime.js +0 -225
package/dist/connector.js
CHANGED
|
@@ -8,8 +8,8 @@ import { NodeDatabase } from './database.js';
|
|
|
8
8
|
import { TerminalManager } from './terminal.js';
|
|
9
9
|
import { NodeRuntimeState } from './runtime-state.js';
|
|
10
10
|
import { CommandStateStore } from './runner-command-engine.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { goalUnrestrictedAccess, GoalRuntime } from './service/goal-runtime.js';
|
|
12
|
+
import { GoalMcpServer } from './service/goal-mcp-server.js';
|
|
13
13
|
import { RunnerUsageReader } from './runner-usage.js';
|
|
14
14
|
import { ClaudeCodeRunner } from './runner/claude-code-runner.js';
|
|
15
15
|
import { ClaudeManagedRunController } from './runner/claude/managed-run-controller.js';
|
|
@@ -247,22 +247,22 @@ export class NodeConnector {
|
|
|
247
247
|
terminalService;
|
|
248
248
|
workspaceService;
|
|
249
249
|
commandStates = new CommandStateStore();
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
250
|
+
goalRuntime = new GoalRuntime(() => undefined);
|
|
251
|
+
goalRetryTimers = new Map();
|
|
252
|
+
goalContinuationPending = new Map();
|
|
253
|
+
goalSubmissionAttempts = new Map();
|
|
254
|
+
goalContinuationEpochs = new Map();
|
|
255
|
+
goalExecutionProgress = new Set();
|
|
256
|
+
goalMcpServer = new GoalMcpServer(this.goalRuntime, (sessionId) => {
|
|
257
|
+
const goal = this.goalRuntime.get(sessionId);
|
|
258
258
|
const session = this.runtime.getAgentSession(sessionId);
|
|
259
|
-
if (
|
|
259
|
+
if (goal === undefined || session === undefined)
|
|
260
260
|
return;
|
|
261
|
-
if (
|
|
262
|
-
this.
|
|
263
|
-
this.continuousRunCoordinator.
|
|
261
|
+
if (goal.status !== 'active') {
|
|
262
|
+
this.retireGoalContinuation(sessionId);
|
|
263
|
+
this.continuousRunCoordinator.removeGoalQueue(sessionId);
|
|
264
264
|
}
|
|
265
|
-
this.
|
|
265
|
+
this.publishGoalState(session, goal);
|
|
266
266
|
});
|
|
267
267
|
runnerUsageReader;
|
|
268
268
|
terminalManager;
|
|
@@ -324,44 +324,44 @@ export class NodeConnector {
|
|
|
324
324
|
await this.continuousRunCoordinator.dispatch(sessionId);
|
|
325
325
|
},
|
|
326
326
|
executionStarted: (executionId) => this.continuousRunCoordinator.executionStarted(executionId),
|
|
327
|
-
keepRunActive: (sessionId) => (this.
|
|
328
|
-
this.
|
|
327
|
+
keepRunActive: (sessionId) => (this.goalRetryTimers.has(sessionId) || this.goalContinuationPending.has(sessionId)) &&
|
|
328
|
+
this.goalRuntime.get(sessionId)?.status === 'active',
|
|
329
329
|
executionTerminating: (executionId, status) => {
|
|
330
330
|
this.gitOperationService.executionFinished(executionId);
|
|
331
331
|
const pending = this.continuousRunCoordinator.pending(executionId);
|
|
332
|
-
if (pending?.
|
|
332
|
+
if (pending?.goal === undefined)
|
|
333
333
|
return;
|
|
334
334
|
const started = this.continuousRunCoordinator.hasExecutionStarted(executionId);
|
|
335
|
-
const progressed = this.
|
|
336
|
-
if (!this.
|
|
335
|
+
const progressed = this.goalExecutionProgress.delete(executionId);
|
|
336
|
+
if (!this.goalGenerationCurrent(pending.sessionId, pending.goal))
|
|
337
337
|
return;
|
|
338
|
-
const disposition = this.
|
|
338
|
+
const disposition = this.goalRuntime.recordExecutionResult(pending.sessionId, {
|
|
339
339
|
failed: status === 'FAILED',
|
|
340
340
|
progressed: status !== 'SUCCEEDED' || progressed
|
|
341
341
|
});
|
|
342
342
|
if (disposition === 'blocked' || disposition === 'paused') {
|
|
343
343
|
if (disposition === 'blocked')
|
|
344
|
-
this.
|
|
344
|
+
this.retireGoalContinuation(pending.sessionId);
|
|
345
345
|
else
|
|
346
|
-
this.
|
|
347
|
-
this.continuousRunCoordinator.
|
|
348
|
-
const
|
|
346
|
+
this.invalidateGoalContinuation(pending.sessionId);
|
|
347
|
+
this.continuousRunCoordinator.removeGoalQueue(pending.sessionId);
|
|
348
|
+
const terminalGoal = this.goalRuntime.get(pending.sessionId);
|
|
349
349
|
const session = this.runtime.getAgentSession(pending.sessionId);
|
|
350
|
-
if (
|
|
351
|
-
this.
|
|
350
|
+
if (terminalGoal !== undefined && session !== undefined)
|
|
351
|
+
this.publishGoalState(session, terminalGoal);
|
|
352
352
|
return;
|
|
353
353
|
}
|
|
354
354
|
if (status !== 'FAILED' || started)
|
|
355
355
|
return;
|
|
356
|
-
this.
|
|
357
|
-
const continuationEpoch = this.
|
|
356
|
+
this.clearGoalRetry(pending.sessionId);
|
|
357
|
+
const continuationEpoch = this.goalContinuationEpochs.get(pending.sessionId) ?? 0;
|
|
358
358
|
const retry = {
|
|
359
359
|
sessionId: pending.sessionId,
|
|
360
360
|
timer: setTimeout(() => {
|
|
361
|
-
this.
|
|
362
|
-
void this.
|
|
361
|
+
this.goalRetryTimers.delete(retry.sessionId);
|
|
362
|
+
void this.enqueueGoalContinuation({
|
|
363
363
|
sessionId: retry.sessionId,
|
|
364
|
-
|
|
364
|
+
goal: pending.goal,
|
|
365
365
|
secretEnvironment: pending.secretEnvironment,
|
|
366
366
|
...(pending.personalInstructions === undefined
|
|
367
367
|
? {}
|
|
@@ -380,12 +380,12 @@ export class NodeConnector {
|
|
|
380
380
|
});
|
|
381
381
|
}, 15_000)
|
|
382
382
|
};
|
|
383
|
-
this.
|
|
383
|
+
this.goalRetryTimers.set(pending.sessionId, retry);
|
|
384
384
|
retry.timer.unref();
|
|
385
385
|
},
|
|
386
386
|
releaseExecution: (executionId) => {
|
|
387
|
-
this.
|
|
388
|
-
this.
|
|
387
|
+
this.goalExecutionProgress.delete(executionId);
|
|
388
|
+
this.goalMcpServer.revoke(executionId);
|
|
389
389
|
this.continuousRunCoordinator.remove(executionId);
|
|
390
390
|
}
|
|
391
391
|
});
|
|
@@ -669,7 +669,7 @@ export class NodeConnector {
|
|
|
669
669
|
(await this.runAttachmentService.readNative(runId, imageIndex)),
|
|
670
670
|
respondUserInput: (runId, requestId, answers) => this.runnerInteractionService.respondUserInput(runId, requestId, answers),
|
|
671
671
|
pauseQueue: (sessionId) => this.workspaceQueueWorkbenchService.pauseSessionQueue(sessionId),
|
|
672
|
-
runInterrupted: (sessionId) => this.
|
|
672
|
+
runInterrupted: (sessionId) => this.clearGoalState(sessionId),
|
|
673
673
|
emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
674
674
|
control: (operation, payload) => this.controlMessages.handle(JSON.stringify(createEnvelope(operation, payload))),
|
|
675
675
|
markInterrupted: (runId) => this.runState.interruptRequested.add(runId)
|
|
@@ -698,19 +698,18 @@ export class NodeConnector {
|
|
|
698
698
|
markInterrupted: (runId) => this.runState.interruptRequested.add(runId),
|
|
699
699
|
mcps: (workspaceId) => this.database?.effectiveMcpInstallations(workspaceId) ?? [],
|
|
700
700
|
retainEnvironment: (environment) => this.gitExecution().retain(environment),
|
|
701
|
-
|
|
702
|
-
this.missionGenerationCurrent(input.sessionId, input.mission),
|
|
701
|
+
goalCurrent: (input) => input.goal === undefined || this.goalGenerationCurrent(input.sessionId, input.goal),
|
|
703
702
|
afterExecutionStarted: async (input) => {
|
|
704
|
-
if (input.
|
|
703
|
+
if (input.goal === undefined)
|
|
705
704
|
return;
|
|
706
|
-
if (!this.
|
|
705
|
+
if (!this.goalGenerationCurrent(input.sessionId, input.goal))
|
|
707
706
|
return;
|
|
708
|
-
const updated = this.
|
|
707
|
+
const updated = this.goalRuntime.recordTurn(input.sessionId);
|
|
709
708
|
const session = this.runtime.getAgentSession(input.sessionId);
|
|
710
709
|
const run = this.runtime.getRun(input.runId);
|
|
711
|
-
await this.
|
|
710
|
+
await this.enqueueGoalContinuation({
|
|
712
711
|
sessionId: input.sessionId,
|
|
713
|
-
|
|
712
|
+
goal: { id: input.goal.id, revision: input.goal.revision },
|
|
714
713
|
...(run?.initiatedByUserId === undefined
|
|
715
714
|
? {}
|
|
716
715
|
: { initiatedByUserId: run.initiatedByUserId }),
|
|
@@ -725,33 +724,33 @@ export class NodeConnector {
|
|
|
725
724
|
: { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
|
|
726
725
|
});
|
|
727
726
|
if (updated !== undefined && session !== undefined)
|
|
728
|
-
this.
|
|
727
|
+
this.publishGoalState(session, updated);
|
|
729
728
|
},
|
|
730
729
|
afterExecutionStartFailed: (input, error) => {
|
|
731
|
-
nodeLog('
|
|
730
|
+
nodeLog('goal.execution-start.failed', {
|
|
732
731
|
sessionId: input.sessionId,
|
|
733
732
|
runId: input.runId,
|
|
734
|
-
|
|
735
|
-
revision: input.
|
|
736
|
-
code: safeErrorCode(error, '
|
|
733
|
+
goalId: input.goal?.id,
|
|
734
|
+
revision: input.goal?.revision,
|
|
735
|
+
code: safeErrorCode(error, 'GOAL_EXECUTION_START_FAILED')
|
|
737
736
|
});
|
|
738
737
|
},
|
|
739
|
-
|
|
740
|
-
if (input.
|
|
741
|
-
throw new Error('
|
|
742
|
-
const resolved = await this.
|
|
738
|
+
goalMcp: async (input) => {
|
|
739
|
+
if (input.goal === undefined)
|
|
740
|
+
throw new Error('GOAL_NOT_ACTIVE');
|
|
741
|
+
const resolved = await this.goalMcpServer.installation({
|
|
743
742
|
executionId: input.runId,
|
|
744
743
|
sessionId: input.sessionId,
|
|
745
|
-
|
|
746
|
-
revision: input.
|
|
744
|
+
goalId: input.goal.id,
|
|
745
|
+
revision: input.goal.revision
|
|
747
746
|
});
|
|
748
747
|
return {
|
|
749
748
|
installation: {
|
|
750
749
|
targetKind: 'WORKSPACE',
|
|
751
750
|
targetId: input.workspaceId,
|
|
752
|
-
catalogEntryId: 'mar-internal-
|
|
753
|
-
serverName: '
|
|
754
|
-
displayName: '
|
|
751
|
+
catalogEntryId: 'mar-internal-goal',
|
|
752
|
+
serverName: 'myagentroam_goal',
|
|
753
|
+
displayName: 'Goal',
|
|
755
754
|
version: '1.0.0',
|
|
756
755
|
enabled: true,
|
|
757
756
|
runtime: { transport: 'HTTP', url: 'https://127.0.0.1.invalid/mcp', headers: {} },
|
|
@@ -784,35 +783,35 @@ export class NodeConnector {
|
|
|
784
783
|
});
|
|
785
784
|
},
|
|
786
785
|
migrateCommands: (previousId, nextId) => this.commandStates.promoteSessionIdentity(previousId, nextId),
|
|
787
|
-
|
|
788
|
-
this.
|
|
789
|
-
this.
|
|
790
|
-
const retry = this.
|
|
786
|
+
migrateGoal: (previousId, nextId) => {
|
|
787
|
+
this.goalRuntime.migrateSession(previousId, nextId);
|
|
788
|
+
this.goalMcpServer.migrateSession(previousId, nextId);
|
|
789
|
+
const retry = this.goalRetryTimers.get(previousId);
|
|
791
790
|
if (retry !== undefined) {
|
|
792
|
-
this.
|
|
791
|
+
this.goalRetryTimers.delete(previousId);
|
|
793
792
|
retry.sessionId = nextId;
|
|
794
|
-
this.
|
|
793
|
+
this.goalRetryTimers.set(nextId, retry);
|
|
795
794
|
}
|
|
796
|
-
const pendingContinuation = this.
|
|
795
|
+
const pendingContinuation = this.goalContinuationPending.get(previousId);
|
|
797
796
|
if (pendingContinuation !== undefined) {
|
|
798
|
-
this.
|
|
797
|
+
this.goalContinuationPending.delete(previousId);
|
|
799
798
|
pendingContinuation.sessionId = nextId;
|
|
800
|
-
this.
|
|
799
|
+
this.goalContinuationPending.set(nextId, pendingContinuation);
|
|
801
800
|
}
|
|
802
|
-
const submissionAttempts = this.
|
|
801
|
+
const submissionAttempts = this.goalSubmissionAttempts.get(previousId);
|
|
803
802
|
if (submissionAttempts !== undefined) {
|
|
804
|
-
this.
|
|
805
|
-
const currentAttempts = this.
|
|
803
|
+
this.goalSubmissionAttempts.delete(previousId);
|
|
804
|
+
const currentAttempts = this.goalSubmissionAttempts.get(nextId) ?? new Set();
|
|
806
805
|
for (const attempt of submissionAttempts) {
|
|
807
806
|
attempt.sessionId = nextId;
|
|
808
807
|
currentAttempts.add(attempt);
|
|
809
808
|
}
|
|
810
|
-
this.
|
|
809
|
+
this.goalSubmissionAttempts.set(nextId, currentAttempts);
|
|
811
810
|
}
|
|
812
|
-
const continuationEpoch = this.
|
|
811
|
+
const continuationEpoch = this.goalContinuationEpochs.get(previousId);
|
|
813
812
|
if (continuationEpoch !== undefined) {
|
|
814
|
-
this.
|
|
815
|
-
this.
|
|
813
|
+
this.goalContinuationEpochs.delete(previousId);
|
|
814
|
+
this.goalContinuationEpochs.set(nextId, continuationEpoch);
|
|
816
815
|
}
|
|
817
816
|
},
|
|
818
817
|
migrateQueue: (previousId, nextId) => this.continuousRunCoordinator.migrateSession(previousId, nextId),
|
|
@@ -868,48 +867,48 @@ export class NodeConnector {
|
|
|
868
867
|
emitSession: (session) => this.emitSessionWorkbenchEvent({
|
|
869
868
|
session: this.sessionPresentationService.present(session)
|
|
870
869
|
}),
|
|
871
|
-
|
|
872
|
-
const existing = this.commandStates.get(session.id, '
|
|
870
|
+
beginGoal: (session) => {
|
|
871
|
+
const existing = this.commandStates.get(session.id, 'goal');
|
|
873
872
|
if (existing?.active === true)
|
|
874
873
|
return {
|
|
875
874
|
session: this.sessionPresentationService.present(session),
|
|
876
|
-
|
|
875
|
+
goalAlreadyActive: true
|
|
877
876
|
};
|
|
878
877
|
if (existing !== undefined) {
|
|
879
|
-
this.
|
|
880
|
-
this.commandStates.clear(session.id, '
|
|
878
|
+
this.goalRuntime.clear(session.id);
|
|
879
|
+
this.commandStates.clear(session.id, 'goal');
|
|
881
880
|
}
|
|
882
|
-
const prepared = this.
|
|
881
|
+
const prepared = this.prepareGoalSession(session);
|
|
883
882
|
this.continuousRunCoordinator.removePendingQueue(session.id);
|
|
884
|
-
this.commandStates.set(session.id, this.
|
|
883
|
+
this.commandStates.set(session.id, this.pendingGoalCommandState());
|
|
885
884
|
this.emitWorkbenchEvent('session', {
|
|
886
885
|
session: this.sessionPresentationService.present(prepared.session)
|
|
887
886
|
});
|
|
888
887
|
return {
|
|
889
888
|
session: this.sessionPresentationService.present(prepared.session),
|
|
890
|
-
|
|
889
|
+
goalAccessChanged: prepared.changed
|
|
891
890
|
};
|
|
892
891
|
},
|
|
893
|
-
|
|
894
|
-
this.
|
|
895
|
-
const prepared = this.
|
|
896
|
-
const
|
|
892
|
+
goal: async ({ session, objective, initiatedByUserId, secretEnvironment, personalInstructions, marAgentModel, marAgentModels, marAgentRuntimeScopeId }) => {
|
|
893
|
+
this.invalidateGoalContinuation(session.id);
|
|
894
|
+
const prepared = this.prepareGoalSession(session);
|
|
895
|
+
const goalSession = prepared.session;
|
|
897
896
|
const activeRun = this.runtime.activeSessionRun(session.id);
|
|
898
897
|
const replacing = activeRun !== undefined && this.runtime.currentExecutionId(activeRun.id) !== undefined;
|
|
899
|
-
this.continuousRunCoordinator.
|
|
900
|
-
const
|
|
901
|
-
this.
|
|
902
|
-
const attempt = this.
|
|
903
|
-
const
|
|
898
|
+
this.continuousRunCoordinator.removeGoalQueue(session.id);
|
|
899
|
+
const goal = this.goalRuntime.start(session.id, objective);
|
|
900
|
+
this.publishGoalState(goalSession, goal);
|
|
901
|
+
const attempt = this.beginGoalSubmission(session.id);
|
|
902
|
+
const goalRef = { id: goal.id, revision: goal.revision, epoch: attempt.epoch };
|
|
904
903
|
let result;
|
|
905
904
|
try {
|
|
906
|
-
result = await this.sessionMessageService.
|
|
905
|
+
result = await this.sessionMessageService.submitGoal({
|
|
907
906
|
sessionId: attempt.sessionId,
|
|
908
907
|
kind: 'start',
|
|
909
|
-
content: this.
|
|
910
|
-
objective:
|
|
908
|
+
content: this.goalRuntime.prompt(attempt.sessionId, 'start'),
|
|
909
|
+
objective: goal.objective,
|
|
911
910
|
deliveryIntent: replacing ? 'REPLACE_CURRENT' : 'SEND',
|
|
912
|
-
|
|
911
|
+
goal: goalRef,
|
|
913
912
|
...(initiatedByUserId === undefined ? {} : { initiatedByUserId }),
|
|
914
913
|
secretEnvironment,
|
|
915
914
|
...(personalInstructions === undefined ? {} : { personalInstructions }),
|
|
@@ -917,22 +916,22 @@ export class NodeConnector {
|
|
|
917
916
|
...(marAgentModels === undefined ? {} : { marAgentModels }),
|
|
918
917
|
...(marAgentRuntimeScopeId === undefined ? {} : { marAgentRuntimeScopeId })
|
|
919
918
|
});
|
|
920
|
-
if (!this.
|
|
921
|
-
this.
|
|
922
|
-
throw new Error('
|
|
919
|
+
if (!this.goalGenerationCurrent(attempt.sessionId, goalRef)) {
|
|
920
|
+
this.removeStaleGoalSubmission(attempt.sessionId, result);
|
|
921
|
+
throw new Error('GOAL_REVISION_STALE');
|
|
923
922
|
}
|
|
924
923
|
}
|
|
925
924
|
finally {
|
|
926
|
-
this.
|
|
925
|
+
this.finishGoalSubmissionAttempt(attempt);
|
|
927
926
|
}
|
|
928
927
|
return {
|
|
929
928
|
...(isPlainRecord(result) ? result : { result }),
|
|
930
|
-
|
|
929
|
+
goalAccessChanged: prepared.changed
|
|
931
930
|
};
|
|
932
931
|
},
|
|
933
|
-
|
|
934
|
-
this.
|
|
935
|
-
this.
|
|
932
|
+
clearGoal: (session) => {
|
|
933
|
+
this.clearGoalState(session.id);
|
|
934
|
+
this.finishIdleGoalRun(session.id, 'SUCCEEDED');
|
|
936
935
|
return { session: this.sessionPresentationService.present(session), cleared: true };
|
|
937
936
|
}
|
|
938
937
|
});
|
|
@@ -952,46 +951,46 @@ export class NodeConnector {
|
|
|
952
951
|
const session = this.runtime.getAgentSession(sessionId);
|
|
953
952
|
return session?.channelToken ?? undefined;
|
|
954
953
|
},
|
|
955
|
-
|
|
956
|
-
const state = this.commandStates.get(session.id, '
|
|
954
|
+
goalMessage: (session, content) => {
|
|
955
|
+
const state = this.commandStates.get(session.id, 'goal');
|
|
957
956
|
if (state?.active !== true)
|
|
958
957
|
return undefined;
|
|
959
|
-
const current = this.
|
|
958
|
+
const current = this.goalRuntime.get(session.id);
|
|
960
959
|
if (current?.status === 'active') {
|
|
961
|
-
this.
|
|
962
|
-
const resumed = this.
|
|
960
|
+
this.invalidateGoalContinuation(session.id);
|
|
961
|
+
const resumed = this.goalRuntime.resume(session.id);
|
|
963
962
|
if (resumed !== current)
|
|
964
|
-
this.
|
|
963
|
+
this.publishGoalState(session, resumed);
|
|
965
964
|
return {
|
|
966
965
|
runnerContent: content,
|
|
967
|
-
|
|
966
|
+
goal: {
|
|
968
967
|
id: resumed.id,
|
|
969
968
|
revision: resumed.revision,
|
|
970
|
-
epoch: this.
|
|
969
|
+
epoch: this.goalContinuationEpochs.get(session.id) ?? 0
|
|
971
970
|
},
|
|
972
971
|
activated: false
|
|
973
972
|
};
|
|
974
973
|
}
|
|
975
|
-
this.
|
|
976
|
-
const
|
|
977
|
-
this.
|
|
974
|
+
this.invalidateGoalContinuation(session.id);
|
|
975
|
+
const goal = this.goalRuntime.start(session.id, content);
|
|
976
|
+
this.publishGoalState(session, goal);
|
|
978
977
|
return {
|
|
979
|
-
runnerContent: this.
|
|
980
|
-
|
|
981
|
-
id:
|
|
982
|
-
revision:
|
|
983
|
-
epoch: this.
|
|
978
|
+
runnerContent: this.goalRuntime.prompt(session.id, 'start'),
|
|
979
|
+
goal: {
|
|
980
|
+
id: goal.id,
|
|
981
|
+
revision: goal.revision,
|
|
982
|
+
epoch: this.goalContinuationEpochs.get(session.id) ?? 0
|
|
984
983
|
},
|
|
985
984
|
activated: true
|
|
986
985
|
};
|
|
987
986
|
},
|
|
988
|
-
|
|
989
|
-
if (!this.
|
|
987
|
+
prepareGoalInsertion: (sessionId, submissionId, goal) => {
|
|
988
|
+
if (!this.goalGenerationCurrent(sessionId, goal))
|
|
990
989
|
return false;
|
|
991
990
|
this.continuousRunCoordinator.removePendingQueue(sessionId, submissionId);
|
|
992
991
|
return true;
|
|
993
992
|
},
|
|
994
|
-
|
|
993
|
+
goalPreparationFailed: (session, goal, activated) => this.handleGoalPreparationFailed(session, goal, activated)
|
|
995
994
|
});
|
|
996
995
|
this.sessionCatalogService = new SessionCatalogService({
|
|
997
996
|
database: () => {
|
|
@@ -1523,48 +1522,48 @@ export class NodeConnector {
|
|
|
1523
1522
|
};
|
|
1524
1523
|
return this.directTransferService?.handleTerminal(command) ?? false;
|
|
1525
1524
|
}
|
|
1526
|
-
|
|
1527
|
-
const retry = this.
|
|
1525
|
+
clearGoalRetry(sessionId) {
|
|
1526
|
+
const retry = this.goalRetryTimers.get(sessionId);
|
|
1528
1527
|
if (retry !== undefined)
|
|
1529
1528
|
clearTimeout(retry.timer);
|
|
1530
|
-
this.
|
|
1531
|
-
this.
|
|
1529
|
+
this.goalRetryTimers.delete(sessionId);
|
|
1530
|
+
this.goalContinuationPending.delete(sessionId);
|
|
1532
1531
|
}
|
|
1533
|
-
|
|
1534
|
-
this.
|
|
1535
|
-
this.
|
|
1532
|
+
invalidateGoalContinuation(sessionId) {
|
|
1533
|
+
this.goalContinuationEpochs.set(sessionId, (this.goalContinuationEpochs.get(sessionId) ?? 0) + 1);
|
|
1534
|
+
this.clearGoalRetry(sessionId);
|
|
1536
1535
|
}
|
|
1537
|
-
|
|
1538
|
-
this.
|
|
1539
|
-
this.
|
|
1536
|
+
retireGoalContinuation(sessionId) {
|
|
1537
|
+
this.invalidateGoalContinuation(sessionId);
|
|
1538
|
+
this.goalContinuationEpochs.delete(sessionId);
|
|
1540
1539
|
}
|
|
1541
|
-
|
|
1542
|
-
const current = this.
|
|
1543
|
-
return ((this.
|
|
1540
|
+
goalGenerationCurrent(sessionId, goal) {
|
|
1541
|
+
const current = this.goalRuntime.get(sessionId);
|
|
1542
|
+
return ((this.goalContinuationEpochs.get(sessionId) ?? 0) === goal.epoch &&
|
|
1544
1543
|
current?.status === 'active' &&
|
|
1545
1544
|
!current.continuationPaused &&
|
|
1546
|
-
current.id ===
|
|
1547
|
-
current.revision ===
|
|
1545
|
+
current.id === goal.id &&
|
|
1546
|
+
current.revision === goal.revision);
|
|
1548
1547
|
}
|
|
1549
|
-
|
|
1548
|
+
beginGoalSubmission(sessionId, continuationEpoch = this.goalContinuationEpochs.get(sessionId) ?? 0) {
|
|
1550
1549
|
const attempt = { sessionId, epoch: continuationEpoch };
|
|
1551
|
-
const attempts = this.
|
|
1550
|
+
const attempts = this.goalSubmissionAttempts.get(sessionId) ?? new Set();
|
|
1552
1551
|
attempts.add(attempt);
|
|
1553
|
-
this.
|
|
1552
|
+
this.goalSubmissionAttempts.set(sessionId, attempts);
|
|
1554
1553
|
return attempt;
|
|
1555
1554
|
}
|
|
1556
|
-
|
|
1557
|
-
const attempts = this.
|
|
1555
|
+
finishGoalSubmissionAttempt(attempt) {
|
|
1556
|
+
const attempts = this.goalSubmissionAttempts.get(attempt.sessionId);
|
|
1558
1557
|
attempts?.delete(attempt);
|
|
1559
1558
|
if (attempts?.size === 0)
|
|
1560
|
-
this.
|
|
1559
|
+
this.goalSubmissionAttempts.delete(attempt.sessionId);
|
|
1561
1560
|
}
|
|
1562
|
-
|
|
1563
|
-
if (this.
|
|
1564
|
-
this.
|
|
1565
|
-
this.
|
|
1561
|
+
finishGoalContinuationAttempt(attempt) {
|
|
1562
|
+
if (this.goalContinuationPending.get(attempt.sessionId) === attempt)
|
|
1563
|
+
this.goalContinuationPending.delete(attempt.sessionId);
|
|
1564
|
+
this.finishGoalSubmissionAttempt(attempt);
|
|
1566
1565
|
}
|
|
1567
|
-
|
|
1566
|
+
removeStaleGoalSubmission(sessionId, result) {
|
|
1568
1567
|
if (!isPlainRecord(result))
|
|
1569
1568
|
return;
|
|
1570
1569
|
if (typeof result.queueItemId === 'string') {
|
|
@@ -1577,23 +1576,23 @@ export class NodeConnector {
|
|
|
1577
1576
|
return;
|
|
1578
1577
|
const run = this.runtime.getRun(result.run.id);
|
|
1579
1578
|
if (run?.sessionId === sessionId && this.runtime.currentExecutionId(run.id) === undefined)
|
|
1580
|
-
this.runEventBridge.emitRun(run.id, 'run.completed', { code: '
|
|
1579
|
+
this.runEventBridge.emitRun(run.id, 'run.completed', { code: 'GOAL_SUBMISSION_STALE' }, 'INTERRUPTED');
|
|
1581
1580
|
}
|
|
1582
|
-
async
|
|
1583
|
-
const attempt = this.
|
|
1584
|
-
const generation = { ...input.
|
|
1585
|
-
if (!this.
|
|
1586
|
-
this.
|
|
1581
|
+
async enqueueGoalContinuation(input) {
|
|
1582
|
+
const attempt = this.beginGoalSubmission(input.sessionId, input.continuationEpoch);
|
|
1583
|
+
const generation = { ...input.goal, epoch: attempt.epoch };
|
|
1584
|
+
if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
|
|
1585
|
+
this.finishGoalSubmissionAttempt(attempt);
|
|
1587
1586
|
return;
|
|
1588
1587
|
}
|
|
1589
|
-
this.
|
|
1588
|
+
this.goalContinuationPending.set(input.sessionId, attempt);
|
|
1590
1589
|
try {
|
|
1591
|
-
const result = await this.sessionMessageService.
|
|
1590
|
+
const result = await this.sessionMessageService.submitGoal({
|
|
1592
1591
|
sessionId: attempt.sessionId,
|
|
1593
1592
|
kind: 'continue',
|
|
1594
|
-
content: this.
|
|
1593
|
+
content: this.goalRuntime.prompt(attempt.sessionId, 'continue'),
|
|
1595
1594
|
deliveryIntent: input.deliveryIntent ?? 'QUEUE',
|
|
1596
|
-
|
|
1595
|
+
goal: generation,
|
|
1597
1596
|
...(input.initiatedByUserId === undefined
|
|
1598
1597
|
? {}
|
|
1599
1598
|
: { initiatedByUserId: input.initiatedByUserId }),
|
|
@@ -1607,53 +1606,53 @@ export class NodeConnector {
|
|
|
1607
1606
|
? {}
|
|
1608
1607
|
: { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
|
|
1609
1608
|
});
|
|
1610
|
-
if (!this.
|
|
1611
|
-
this.
|
|
1612
|
-
this.
|
|
1609
|
+
if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
|
|
1610
|
+
this.removeStaleGoalSubmission(attempt.sessionId, result);
|
|
1611
|
+
this.finishGoalContinuationAttempt(attempt);
|
|
1613
1612
|
return;
|
|
1614
1613
|
}
|
|
1615
|
-
const retry = this.
|
|
1614
|
+
const retry = this.goalRetryTimers.get(attempt.sessionId);
|
|
1616
1615
|
if (retry !== undefined)
|
|
1617
1616
|
clearTimeout(retry.timer);
|
|
1618
|
-
this.
|
|
1619
|
-
this.
|
|
1617
|
+
this.goalRetryTimers.delete(attempt.sessionId);
|
|
1618
|
+
this.finishGoalContinuationAttempt(attempt);
|
|
1620
1619
|
}
|
|
1621
1620
|
catch (error) {
|
|
1622
1621
|
const failedAttempts = (input.failedAttempts ?? 0) + 1;
|
|
1623
|
-
nodeLog('
|
|
1622
|
+
nodeLog('goal.continuation.enqueue.failed', {
|
|
1624
1623
|
sessionId: attempt.sessionId,
|
|
1625
|
-
|
|
1626
|
-
revision: input.
|
|
1624
|
+
goalId: input.goal.id,
|
|
1625
|
+
revision: input.goal.revision,
|
|
1627
1626
|
attempt: failedAttempts,
|
|
1628
|
-
code: safeErrorCode(error, '
|
|
1627
|
+
code: safeErrorCode(error, 'GOAL_CONTINUATION_ENQUEUE_FAILED')
|
|
1629
1628
|
});
|
|
1630
|
-
if (!this.
|
|
1631
|
-
this.
|
|
1629
|
+
if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
|
|
1630
|
+
this.finishGoalContinuationAttempt(attempt);
|
|
1632
1631
|
return;
|
|
1633
1632
|
}
|
|
1634
1633
|
const session = this.runtime.getAgentSession(attempt.sessionId);
|
|
1635
1634
|
if (failedAttempts > 3 || session === undefined) {
|
|
1636
|
-
this.
|
|
1635
|
+
this.clearGoalRetry(attempt.sessionId);
|
|
1637
1636
|
if (failedAttempts > 3) {
|
|
1638
|
-
const blocked = this.
|
|
1639
|
-
this.continuousRunCoordinator.
|
|
1640
|
-
this.
|
|
1641
|
-
this.
|
|
1637
|
+
const blocked = this.goalRuntime.blockAfterRuntimeFailure(attempt.sessionId, input.goal.id, input.goal.revision);
|
|
1638
|
+
this.continuousRunCoordinator.removeGoalQueue(attempt.sessionId);
|
|
1639
|
+
this.goalContinuationEpochs.delete(attempt.sessionId);
|
|
1640
|
+
this.finishIdleGoalRun(attempt.sessionId, 'FAILED');
|
|
1642
1641
|
if (session !== undefined)
|
|
1643
|
-
this.
|
|
1642
|
+
this.publishGoalState(session, blocked);
|
|
1644
1643
|
}
|
|
1645
1644
|
return;
|
|
1646
1645
|
}
|
|
1647
|
-
this.
|
|
1648
|
-
const previousRetry = this.
|
|
1646
|
+
this.finishGoalContinuationAttempt(attempt);
|
|
1647
|
+
const previousRetry = this.goalRetryTimers.get(attempt.sessionId);
|
|
1649
1648
|
if (previousRetry !== undefined)
|
|
1650
1649
|
clearTimeout(previousRetry.timer);
|
|
1651
1650
|
const retry = {
|
|
1652
1651
|
sessionId: attempt.sessionId,
|
|
1653
1652
|
timer: setTimeout(() => {
|
|
1654
|
-
if (!this.
|
|
1653
|
+
if (!this.goalGenerationCurrent(retry.sessionId, generation))
|
|
1655
1654
|
return;
|
|
1656
|
-
void this.
|
|
1655
|
+
void this.enqueueGoalContinuation({
|
|
1657
1656
|
...input,
|
|
1658
1657
|
sessionId: retry.sessionId,
|
|
1659
1658
|
failedAttempts,
|
|
@@ -1661,33 +1660,33 @@ export class NodeConnector {
|
|
|
1661
1660
|
});
|
|
1662
1661
|
}, 15_000)
|
|
1663
1662
|
};
|
|
1664
|
-
this.
|
|
1663
|
+
this.goalRetryTimers.set(attempt.sessionId, retry);
|
|
1665
1664
|
retry.timer.unref();
|
|
1666
1665
|
}
|
|
1667
1666
|
}
|
|
1668
|
-
|
|
1669
|
-
if (!activated || !this.
|
|
1667
|
+
handleGoalPreparationFailed(session, goal, activated) {
|
|
1668
|
+
if (!activated || !this.goalGenerationCurrent(session.id, goal))
|
|
1670
1669
|
return;
|
|
1671
|
-
this.
|
|
1672
|
-
this.
|
|
1673
|
-
this.commandStates.set(session.id, this.
|
|
1670
|
+
this.goalRuntime.clear(session.id);
|
|
1671
|
+
this.finishIdleGoalRun(session.id, 'FAILED');
|
|
1672
|
+
this.commandStates.set(session.id, this.pendingGoalCommandState());
|
|
1674
1673
|
this.emitWorkbenchEvent('session', {
|
|
1675
1674
|
session: this.sessionPresentationService.present(session)
|
|
1676
1675
|
});
|
|
1677
1676
|
}
|
|
1678
|
-
|
|
1677
|
+
finishIdleGoalRun(sessionId, status) {
|
|
1679
1678
|
const run = this.runtime.activeSessionRun(sessionId);
|
|
1680
1679
|
if (run === undefined || this.runtime.currentExecutionId(run.id) !== undefined)
|
|
1681
1680
|
return;
|
|
1682
|
-
this.runEventBridge.emitRun(run.id, status === 'SUCCEEDED' ? 'run.completed' : 'run.failed', status === 'SUCCEEDED' ? {} : { code: '
|
|
1681
|
+
this.runEventBridge.emitRun(run.id, status === 'SUCCEEDED' ? 'run.completed' : 'run.failed', status === 'SUCCEEDED' ? {} : { code: 'GOAL_CONTINUATION_FAILED' }, status);
|
|
1683
1682
|
}
|
|
1684
|
-
|
|
1685
|
-
this.
|
|
1686
|
-
const removed = this.
|
|
1687
|
-
this.continuousRunCoordinator.
|
|
1688
|
-
const commandState = this.commandStates.get(sessionId, '
|
|
1683
|
+
clearGoalState(sessionId) {
|
|
1684
|
+
this.retireGoalContinuation(sessionId);
|
|
1685
|
+
const removed = this.goalRuntime.clear(sessionId);
|
|
1686
|
+
this.continuousRunCoordinator.removeGoalQueue(sessionId);
|
|
1687
|
+
const commandState = this.commandStates.get(sessionId, 'goal');
|
|
1689
1688
|
if (commandState !== undefined)
|
|
1690
|
-
this.commandStates.clear(sessionId, '
|
|
1689
|
+
this.commandStates.clear(sessionId, 'goal');
|
|
1691
1690
|
if (!removed && commandState === undefined)
|
|
1692
1691
|
return;
|
|
1693
1692
|
const session = this.runtime.getAgentSession(sessionId);
|
|
@@ -1696,8 +1695,8 @@ export class NodeConnector {
|
|
|
1696
1695
|
session: this.sessionPresentationService.present(session)
|
|
1697
1696
|
});
|
|
1698
1697
|
}
|
|
1699
|
-
|
|
1700
|
-
const access =
|
|
1698
|
+
prepareGoalSession(session) {
|
|
1699
|
+
const access = goalUnrestrictedAccess(session.runner);
|
|
1701
1700
|
const requiresUpdate = session.access !== access;
|
|
1702
1701
|
const changed = session.runner !== 'mar-agent' && requiresUpdate;
|
|
1703
1702
|
const prepared = requiresUpdate
|
|
@@ -1717,14 +1716,14 @@ export class NodeConnector {
|
|
|
1717
1716
|
});
|
|
1718
1717
|
return { session: prepared, changed };
|
|
1719
1718
|
}
|
|
1720
|
-
|
|
1719
|
+
pendingGoalCommandState() {
|
|
1721
1720
|
return {
|
|
1722
|
-
commandId: '
|
|
1721
|
+
commandId: 'goal',
|
|
1723
1722
|
active: true,
|
|
1724
|
-
label: '
|
|
1723
|
+
label: 'Goal',
|
|
1725
1724
|
detail: null,
|
|
1726
1725
|
closable: true,
|
|
1727
|
-
closeInput: '/
|
|
1726
|
+
closeInput: '/goal --clear'
|
|
1728
1727
|
};
|
|
1729
1728
|
}
|
|
1730
1729
|
stop() {
|
|
@@ -1749,12 +1748,12 @@ export class NodeConnector {
|
|
|
1749
1748
|
this.sessionLifecycleService.clear();
|
|
1750
1749
|
this.continuousRunCoordinator.clear();
|
|
1751
1750
|
this.gitOperationService.clear();
|
|
1752
|
-
for (const retry of this.
|
|
1751
|
+
for (const retry of this.goalRetryTimers.values())
|
|
1753
1752
|
clearTimeout(retry.timer);
|
|
1754
|
-
this.
|
|
1755
|
-
this.
|
|
1756
|
-
this.
|
|
1757
|
-
this.
|
|
1753
|
+
this.goalRetryTimers.clear();
|
|
1754
|
+
this.goalContinuationPending.clear();
|
|
1755
|
+
this.goalSubmissionAttempts.clear();
|
|
1756
|
+
this.goalContinuationEpochs.clear();
|
|
1758
1757
|
this.runEventService.clear();
|
|
1759
1758
|
this.sessionContextService.dispose();
|
|
1760
1759
|
for (const run of this.claudeClient.execution.runs.values())
|
|
@@ -1777,7 +1776,7 @@ export class NodeConnector {
|
|
|
1777
1776
|
this.workspaceSessionService.clear();
|
|
1778
1777
|
this.workspaceChangeService.clear();
|
|
1779
1778
|
const runnerDrain = this.runners.stop();
|
|
1780
|
-
void this.
|
|
1779
|
+
void this.goalMcpServer.close();
|
|
1781
1780
|
// close() transitions metadata before awaiting process-tree termination,
|
|
1782
1781
|
// so the lifecycle is persisted before this synchronous shutdown returns.
|
|
1783
1782
|
const terminalDrain = this.terminalManager.shutdown();
|
|
@@ -1805,9 +1804,9 @@ export class NodeConnector {
|
|
|
1805
1804
|
handleMessage = (raw) => this.controlMessages.handle(raw);
|
|
1806
1805
|
emitRunEvent = (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status);
|
|
1807
1806
|
emitConversationItem = (runId, item) => {
|
|
1808
|
-
if (this.continuousRunCoordinator.pending(runId)?.
|
|
1809
|
-
|
|
1810
|
-
this.
|
|
1807
|
+
if (this.continuousRunCoordinator.pending(runId)?.goal !== undefined &&
|
|
1808
|
+
isGoalProgressItem(item))
|
|
1809
|
+
this.goalExecutionProgress.add(runId);
|
|
1811
1810
|
this.runEventBridge.emitConversationItem(runId, item);
|
|
1812
1811
|
};
|
|
1813
1812
|
emitConversationTurn = (turn) => {
|
|
@@ -1888,27 +1887,27 @@ export class NodeConnector {
|
|
|
1888
1887
|
const result = await response.json();
|
|
1889
1888
|
return { content: JSON.stringify(result), data: result };
|
|
1890
1889
|
}
|
|
1891
|
-
|
|
1892
|
-
if (
|
|
1893
|
-
this.commandStates.clear(session.id, '
|
|
1890
|
+
publishGoalState(session, goal) {
|
|
1891
|
+
if (goal.status !== 'active') {
|
|
1892
|
+
this.commandStates.clear(session.id, 'goal');
|
|
1894
1893
|
this.emitWorkbenchEvent('session', {
|
|
1895
1894
|
session: this.sessionPresentationService.present(session)
|
|
1896
1895
|
});
|
|
1897
1896
|
return;
|
|
1898
1897
|
}
|
|
1899
1898
|
this.commandStates.set(session.id, {
|
|
1900
|
-
commandId: '
|
|
1901
|
-
active:
|
|
1902
|
-
label: '
|
|
1903
|
-
detail:
|
|
1899
|
+
commandId: 'goal',
|
|
1900
|
+
active: goal.status === 'active',
|
|
1901
|
+
label: 'Goal',
|
|
1902
|
+
detail: goal.objective.slice(0, 500),
|
|
1904
1903
|
closable: true,
|
|
1905
|
-
closeInput: '/
|
|
1906
|
-
|
|
1907
|
-
id:
|
|
1908
|
-
objective:
|
|
1909
|
-
revision:
|
|
1910
|
-
status:
|
|
1911
|
-
turns:
|
|
1904
|
+
closeInput: '/goal',
|
|
1905
|
+
goal: {
|
|
1906
|
+
id: goal.id,
|
|
1907
|
+
objective: goal.objective,
|
|
1908
|
+
revision: goal.revision,
|
|
1909
|
+
status: goal.status,
|
|
1910
|
+
turns: goal.turns
|
|
1912
1911
|
}
|
|
1913
1912
|
});
|
|
1914
1913
|
this.emitWorkbenchEvent('session', {
|
|
@@ -1922,7 +1921,7 @@ export class NodeConnector {
|
|
|
1922
1921
|
this.controlChannel.sendEnvelope(envelope);
|
|
1923
1922
|
}
|
|
1924
1923
|
}
|
|
1925
|
-
function
|
|
1924
|
+
function isGoalProgressItem(item) {
|
|
1926
1925
|
if (!isPlainRecord(item) || typeof item.kind !== 'string')
|
|
1927
1926
|
return false;
|
|
1928
1927
|
if (item.kind === 'command_execution' || item.kind === 'file_change')
|
|
@@ -1930,5 +1929,5 @@ function isMissionProgressItem(item) {
|
|
|
1930
1929
|
if (item.kind !== 'tool_call' || !isPlainRecord(item.payload))
|
|
1931
1930
|
return false;
|
|
1932
1931
|
const toolName = item.payload.toolName;
|
|
1933
|
-
return typeof toolName === 'string' && !toolName.endsWith('
|
|
1932
|
+
return typeof toolName === 'string' && !toolName.endsWith('mar_get_goal');
|
|
1934
1933
|
}
|