@myagentroam/node 0.10.61 → 0.10.63

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.
@@ -0,0 +1,622 @@
1
+ import { composerVisibleText } from '@myagentroam/protocol';
2
+ import { nodeLog } from '../operational.js';
3
+ import { isPlainRecord } from '../util/node-operation-parsers.js';
4
+ import { safeErrorCode } from '../util/safe-error.js';
5
+ import { NODE_MCP } from '../mcp/naming.js';
6
+ import { goalUnrestrictedAccess } from './goal-runtime.js';
7
+ import { composerResourceInputTags } from './composer-message-input.js';
8
+ /** Owns transient Goal orchestration, not the domain state or execution queue. */
9
+ export class GoalContinuationCoordinator {
10
+ options;
11
+ goalRetryTimers = new Map();
12
+ goalContinuationPending = new Map();
13
+ goalSubmissionAttempts = new Map();
14
+ goalContinuationEpochs = new Map();
15
+ goalExecutionProgress = new Set();
16
+ nativeGoalBindings = new Map();
17
+ nextEpoch = 0;
18
+ frozen = false;
19
+ disposed = false;
20
+ constructor(options) {
21
+ this.options = options;
22
+ }
23
+ keepRunActive(sessionId) {
24
+ return ((this.goalRetryTimers.has(sessionId) || this.goalContinuationPending.has(sessionId)) &&
25
+ this.options.goals.get(sessionId)?.status === 'active');
26
+ }
27
+ changed(sessionId) {
28
+ const goal = this.options.goals.get(sessionId);
29
+ const session = this.options.runtime.getAgentSession(sessionId);
30
+ if (!goal || !session || this.disposed)
31
+ return;
32
+ if (goal.status !== 'active') {
33
+ this.nativeGoalBindings.delete(goal.id);
34
+ this.retireGoalContinuation(sessionId);
35
+ this.options.queue().removeGoalQueue(sessionId);
36
+ }
37
+ this.publishGoalState(session, goal);
38
+ }
39
+ executionProgress(executionId, item) {
40
+ if (this.disposed)
41
+ return;
42
+ if (this.options.queue().pending(executionId)?.goal && isGoalProgressItem(item))
43
+ this.goalExecutionProgress.add(executionId);
44
+ }
45
+ releaseExecution(executionId) {
46
+ this.goalExecutionProgress.delete(executionId);
47
+ }
48
+ freeze() {
49
+ this.frozen = true;
50
+ }
51
+ dispose() {
52
+ this.frozen = true;
53
+ this.disposed = true;
54
+ for (const retry of this.goalRetryTimers.values())
55
+ clearTimeout(retry.timer);
56
+ this.goalRetryTimers.clear();
57
+ this.goalContinuationPending.clear();
58
+ this.goalSubmissionAttempts.clear();
59
+ this.goalContinuationEpochs.clear();
60
+ this.goalExecutionProgress.clear();
61
+ this.nativeGoalBindings.clear();
62
+ }
63
+ reference(sessionId) {
64
+ const goal = this.options.goals.get(sessionId);
65
+ if (!goal)
66
+ throw new Error('GOAL_NOT_ACTIVE');
67
+ return { id: goal.id, revision: goal.revision, epoch: this.continuationEpoch(sessionId) };
68
+ }
69
+ executionTerminating(executionId, status, permissionStop) {
70
+ if (this.frozen)
71
+ return;
72
+ const pending = this.options.queue().pending(executionId);
73
+ if (pending?.goal === undefined)
74
+ return;
75
+ const started = this.options.queue().hasExecutionStarted(executionId);
76
+ const progressed = this.goalExecutionProgress.delete(executionId);
77
+ if (!this.goalGenerationCurrent(pending.sessionId, pending.goal))
78
+ return;
79
+ if (permissionStop !== undefined) {
80
+ this.retireGoalContinuation(pending.sessionId);
81
+ this.options.queue().removeGoalQueue(pending.sessionId);
82
+ return;
83
+ }
84
+ const disposition = this.options.goals.recordExecutionResult(pending.sessionId, {
85
+ failed: status === 'FAILED',
86
+ progressed: status !== 'SUCCEEDED' || progressed
87
+ });
88
+ if (disposition === 'blocked' || disposition === 'completed') {
89
+ this.nativeGoalBindings.delete(pending.goal.id);
90
+ this.retireGoalContinuation(pending.sessionId);
91
+ this.options.queue().removeGoalQueue(pending.sessionId);
92
+ const terminalGoal = this.options.goals.get(pending.sessionId);
93
+ const session = this.options.runtime.getAgentSession(pending.sessionId);
94
+ if (terminalGoal !== undefined && session !== undefined)
95
+ this.publishGoalState(session, terminalGoal);
96
+ return;
97
+ }
98
+ if (status !== 'FAILED' || started)
99
+ return;
100
+ this.clearGoalRetry(pending.sessionId);
101
+ const continuationEpoch = this.continuationEpoch(pending.sessionId);
102
+ const retry = {
103
+ sessionId: pending.sessionId,
104
+ timer: setTimeout(() => {
105
+ this.goalRetryTimers.delete(retry.sessionId);
106
+ void this.enqueueGoalContinuation({
107
+ sessionId: retry.sessionId,
108
+ goal: pending.goal,
109
+ ...(pending.usageGrant === undefined ? {} : { usageGrant: pending.usageGrant }),
110
+ secretEnvironment: pending.secretEnvironment,
111
+ ...(pending.personalInstructions === undefined
112
+ ? {}
113
+ : { personalInstructions: pending.personalInstructions }),
114
+ ...(pending.marAgentModel === undefined ? {} : { marAgentModel: pending.marAgentModel }),
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 }),
124
+ continuationEpoch,
125
+ deliveryIntent: 'SEND'
126
+ });
127
+ }, 15_000)
128
+ };
129
+ this.goalRetryTimers.set(pending.sessionId, retry);
130
+ retry.timer.unref();
131
+ }
132
+ migrateSession(previousId, nextId) {
133
+ this.options.goals.migrateSession(previousId, nextId);
134
+ const retry = this.goalRetryTimers.get(previousId);
135
+ if (retry !== undefined) {
136
+ this.goalRetryTimers.delete(previousId);
137
+ retry.sessionId = nextId;
138
+ this.goalRetryTimers.set(nextId, retry);
139
+ }
140
+ const pendingContinuation = this.goalContinuationPending.get(previousId);
141
+ if (pendingContinuation !== undefined) {
142
+ this.goalContinuationPending.delete(previousId);
143
+ pendingContinuation.sessionId = nextId;
144
+ this.goalContinuationPending.set(nextId, pendingContinuation);
145
+ }
146
+ const submissionAttempts = this.goalSubmissionAttempts.get(previousId);
147
+ if (submissionAttempts !== undefined) {
148
+ this.goalSubmissionAttempts.delete(previousId);
149
+ const currentAttempts = this.goalSubmissionAttempts.get(nextId) ?? new Set();
150
+ for (const attempt of submissionAttempts) {
151
+ attempt.sessionId = nextId;
152
+ currentAttempts.add(attempt);
153
+ }
154
+ this.goalSubmissionAttempts.set(nextId, currentAttempts);
155
+ }
156
+ const continuationEpoch = this.goalContinuationEpochs.get(previousId);
157
+ if (continuationEpoch !== undefined) {
158
+ this.goalContinuationEpochs.delete(previousId);
159
+ this.goalContinuationEpochs.set(nextId, continuationEpoch);
160
+ }
161
+ }
162
+ begin(session) {
163
+ const existing = this.options.commands.get(session.id, 'goal');
164
+ if (existing?.active === true)
165
+ return {
166
+ session: this.options.present(session),
167
+ goalAlreadyActive: true
168
+ };
169
+ if (existing !== undefined) {
170
+ const previousGoal = this.options.goals.get(session.id);
171
+ if (previousGoal !== undefined)
172
+ this.nativeGoalBindings.delete(previousGoal.id);
173
+ this.options.goals.clear(session.id);
174
+ this.options.commands.clear(session.id, 'goal');
175
+ }
176
+ const prepared = this.prepareGoalSession(session);
177
+ this.options.queue().removePendingQueue(session.id);
178
+ this.options.commands.set(session.id, this.pendingGoalCommandState());
179
+ this.options.emitSession(prepared.session);
180
+ return {
181
+ session: this.options.present(prepared.session),
182
+ goalAccessChanged: prepared.changed
183
+ };
184
+ }
185
+ async start({ session, objective, initiatedByUserId, suppressNotifications, secretEnvironment, personalInstructions, marAgentModel, marAgentModels, marAgentCompact, marAgentRuntimeScopeId, usageGrant }) {
186
+ this.invalidateGoalContinuation(session.id);
187
+ const prepared = this.prepareGoalSession(session);
188
+ const goalSession = prepared.session;
189
+ const activeRun = this.options.runtime.activeSessionRun(session.id);
190
+ const replacing = activeRun !== undefined &&
191
+ this.options.runtime.currentExecutionId(activeRun.id) !== undefined;
192
+ this.options.queue().removeGoalQueue(session.id);
193
+ const goal = this.options.goals.start(session.id, objective);
194
+ this.publishGoalState(goalSession, goal);
195
+ const attempt = this.beginGoalSubmission(session.id);
196
+ const goalRef = { id: goal.id, revision: goal.revision, epoch: attempt.epoch };
197
+ let result;
198
+ try {
199
+ result = await this.options.submit({
200
+ sessionId: attempt.sessionId,
201
+ kind: 'start',
202
+ content: this.options.goals.prompt(attempt.sessionId, 'start'),
203
+ objective: goal.objective,
204
+ deliveryIntent: replacing ? 'REPLACE_CURRENT' : 'SEND',
205
+ goal: goalRef,
206
+ ...(suppressNotifications === true ? { suppressNotifications: true } : {}),
207
+ ...(usageGrant === undefined ? {} : { usageGrant }),
208
+ ...(initiatedByUserId === undefined ? {} : { initiatedByUserId }),
209
+ secretEnvironment,
210
+ ...(personalInstructions === undefined ? {} : { personalInstructions }),
211
+ ...(marAgentModel === undefined ? {} : { marAgentModel }),
212
+ ...(marAgentModels === undefined ? {} : { marAgentModels }),
213
+ ...(marAgentCompact === undefined ? {} : { marAgentCompact }),
214
+ ...(marAgentRuntimeScopeId === undefined ? {} : { marAgentRuntimeScopeId })
215
+ });
216
+ if (!this.goalGenerationCurrent(attempt.sessionId, goalRef)) {
217
+ this.removeStaleGoalSubmission(attempt.sessionId, result);
218
+ throw new Error('GOAL_REVISION_STALE');
219
+ }
220
+ }
221
+ finally {
222
+ this.finishGoalSubmissionAttempt(attempt);
223
+ }
224
+ return {
225
+ ...(isPlainRecord(result) ? result : { result }),
226
+ goalAccessChanged: prepared.changed
227
+ };
228
+ }
229
+ clear(session) {
230
+ const goalExit = this.clearGoalState(session.id, 'removed');
231
+ this.finishIdleGoalRun(session.id, 'SUCCEEDED');
232
+ return {
233
+ session: this.options.present(session),
234
+ cleared: true,
235
+ ...(goalExit === undefined ? {} : { goalExit })
236
+ };
237
+ }
238
+ goalMessage(session, content, intent) {
239
+ const state = this.options.commands.get(session.id, 'goal');
240
+ if (state?.active !== true)
241
+ return undefined;
242
+ const current = this.options.goals.get(session.id);
243
+ if (current?.status === 'active') {
244
+ const idlePausedGoal = this.options.runtime.activeSessionRun(session.id) === undefined &&
245
+ this.options.runtime.sessionQueue(session.id).paused;
246
+ if (intent !== 'REPLACE_CURRENT' && !idlePausedGoal)
247
+ return undefined;
248
+ this.invalidateGoalContinuation(session.id);
249
+ const resumed = this.options.goals.resume(session.id);
250
+ if (resumed !== current)
251
+ this.publishGoalState(session, resumed);
252
+ return {
253
+ runnerContent: content,
254
+ goal: {
255
+ id: resumed.id,
256
+ revision: resumed.revision,
257
+ epoch: this.continuationEpoch(session.id)
258
+ },
259
+ activated: false
260
+ };
261
+ }
262
+ const objective = composerVisibleText(content);
263
+ if (objective.length === 0)
264
+ return undefined;
265
+ this.invalidateGoalContinuation(session.id);
266
+ const goal = this.options.goals.start(session.id, objective, composerResourceInputTags(content));
267
+ this.publishGoalState(session, goal);
268
+ return {
269
+ runnerContent: this.options.goals.prompt(session.id, 'start'),
270
+ goal: {
271
+ id: goal.id,
272
+ revision: goal.revision,
273
+ epoch: this.continuationEpoch(session.id)
274
+ },
275
+ activated: true
276
+ };
277
+ }
278
+ prepareInsertion(sessionId, submissionId, goal) {
279
+ if (!this.goalGenerationCurrent(sessionId, goal))
280
+ return false;
281
+ this.options.queue().removeGoalQueue(sessionId, undefined, submissionId);
282
+ return true;
283
+ }
284
+ clearGoalRetry(sessionId) {
285
+ const retry = this.goalRetryTimers.get(sessionId);
286
+ if (retry !== undefined)
287
+ clearTimeout(retry.timer);
288
+ this.goalRetryTimers.delete(sessionId);
289
+ this.goalContinuationPending.delete(sessionId);
290
+ }
291
+ continuationEpoch(sessionId) {
292
+ let epoch = this.goalContinuationEpochs.get(sessionId);
293
+ if (epoch === undefined) {
294
+ epoch = ++this.nextEpoch;
295
+ this.goalContinuationEpochs.set(sessionId, epoch);
296
+ }
297
+ return epoch;
298
+ }
299
+ invalidateGoalContinuation(sessionId) {
300
+ this.goalContinuationEpochs.set(sessionId, ++this.nextEpoch);
301
+ this.clearGoalRetry(sessionId);
302
+ }
303
+ retireGoalContinuation(sessionId) {
304
+ this.clearGoalRetry(sessionId);
305
+ this.goalContinuationEpochs.delete(sessionId);
306
+ }
307
+ goalGenerationCurrent(sessionId, goal) {
308
+ const current = this.options.goals.get(sessionId);
309
+ return (!this.disposed &&
310
+ this.goalContinuationEpochs.get(sessionId) === goal.epoch &&
311
+ current?.status === 'active' &&
312
+ current.id === goal.id &&
313
+ current.revision === goal.revision);
314
+ }
315
+ async maintainGoalContinuation(input, started) {
316
+ if (this.frozen || input.goal === undefined)
317
+ return;
318
+ if (!this.goalGenerationCurrent(input.sessionId, input.goal))
319
+ return;
320
+ const updated = started ? this.options.goals.recordTurn(input.sessionId) : undefined;
321
+ const session = this.options.runtime.getAgentSession(input.sessionId);
322
+ const run = this.options.runtime.getRun(input.runId);
323
+ await this.enqueueGoalContinuation({
324
+ sessionId: input.sessionId,
325
+ goal: { id: input.goal.id, revision: input.goal.revision },
326
+ continuationEpoch: input.goal.epoch,
327
+ ...(run?.initiatedByUserId === undefined ? {} : { initiatedByUserId: run.initiatedByUserId }),
328
+ ...(input.usageGrant === undefined ? {} : { usageGrant: input.usageGrant }),
329
+ secretEnvironment: input.secretEnvironment,
330
+ ...(input.personalInstructions === undefined
331
+ ? {}
332
+ : { personalInstructions: input.personalInstructions }),
333
+ ...(input.marAgentModel === undefined ? {} : { marAgentModel: input.marAgentModel }),
334
+ ...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
335
+ ...(input.marAgentCompact === undefined ? {} : { marAgentCompact: input.marAgentCompact }),
336
+ ...(input.marAgentRuntimeScopeId === undefined
337
+ ? {}
338
+ : { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
339
+ });
340
+ if (updated !== undefined &&
341
+ session !== undefined &&
342
+ this.goalGenerationCurrent(session.id, input.goal))
343
+ this.publishGoalState(session, this.options.goals.get(session.id));
344
+ }
345
+ handleNativeGoalUpdate(runId, nativeGoal) {
346
+ const pending = this.options.queue().pending(runId);
347
+ if (pending?.goal === undefined || !this.goalGenerationCurrent(pending.sessionId, pending.goal))
348
+ return;
349
+ const goal = this.options.goals.get(pending.sessionId);
350
+ if (nativeGoal.status === 'active') {
351
+ if ((nativeGoal.createdAt + 1) * 1_000 < goal.revisionStartedAt)
352
+ return;
353
+ const previous = this.nativeGoalBindings.get(goal.id);
354
+ if (previous !== undefined &&
355
+ previous.revision !== goal.revision &&
356
+ previous.threadId === nativeGoal.threadId &&
357
+ previous.createdAt === nativeGoal.createdAt &&
358
+ previous.objective === nativeGoal.objective)
359
+ return;
360
+ this.nativeGoalBindings.set(goal.id, {
361
+ revision: goal.revision,
362
+ threadId: nativeGoal.threadId,
363
+ createdAt: nativeGoal.createdAt,
364
+ objective: nativeGoal.objective
365
+ });
366
+ return;
367
+ }
368
+ const bound = this.nativeGoalBindings.get(goal.id);
369
+ if (bound?.revision !== goal.revision ||
370
+ bound.threadId !== nativeGoal.threadId ||
371
+ bound.createdAt !== nativeGoal.createdAt ||
372
+ bound.objective !== nativeGoal.objective)
373
+ return;
374
+ this.nativeGoalBindings.delete(goal.id);
375
+ const completed = this.options.goals.update(pending.sessionId, goal.id, goal.revision, 'completed');
376
+ this.retireGoalContinuation(pending.sessionId);
377
+ this.options.queue().removeGoalQueue(pending.sessionId);
378
+ const session = this.options.runtime.getAgentSession(pending.sessionId);
379
+ if (session !== undefined)
380
+ this.publishGoalState(session, completed);
381
+ }
382
+ beginGoalSubmission(sessionId, continuationEpoch = this.continuationEpoch(sessionId)) {
383
+ const attempt = { sessionId, epoch: continuationEpoch };
384
+ const attempts = this.goalSubmissionAttempts.get(sessionId) ?? new Set();
385
+ attempts.add(attempt);
386
+ this.goalSubmissionAttempts.set(sessionId, attempts);
387
+ return attempt;
388
+ }
389
+ finishGoalSubmissionAttempt(attempt) {
390
+ const attempts = this.goalSubmissionAttempts.get(attempt.sessionId);
391
+ attempts?.delete(attempt);
392
+ if (attempts?.size === 0)
393
+ this.goalSubmissionAttempts.delete(attempt.sessionId);
394
+ }
395
+ finishGoalContinuationAttempt(attempt) {
396
+ if (this.goalContinuationPending.get(attempt.sessionId) === attempt)
397
+ this.goalContinuationPending.delete(attempt.sessionId);
398
+ this.finishGoalSubmissionAttempt(attempt);
399
+ }
400
+ removeStaleGoalSubmission(sessionId, result) {
401
+ if (this.disposed)
402
+ return;
403
+ if (!isPlainRecord(result))
404
+ return;
405
+ if (typeof result.queueItemId === 'string') {
406
+ const item = this.options.runtime.getQueueItem(result.queueItemId);
407
+ if (item?.sessionId === sessionId)
408
+ this.options.queue().deleteQueueItem(item.id);
409
+ return;
410
+ }
411
+ if (!isPlainRecord(result.run) || typeof result.run.id !== 'string')
412
+ return;
413
+ const run = this.options.runtime.getRun(result.run.id);
414
+ if (run?.sessionId === sessionId &&
415
+ this.options.runtime.currentExecutionId(run.id) === undefined)
416
+ this.options.emitRun(run.id, 'run.completed', { code: 'GOAL_SUBMISSION_STALE' }, 'INTERRUPTED');
417
+ }
418
+ async enqueueGoalContinuation(input) {
419
+ if (this.frozen)
420
+ return;
421
+ const attempt = this.beginGoalSubmission(input.sessionId, input.continuationEpoch);
422
+ const generation = { ...input.goal, epoch: attempt.epoch };
423
+ if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
424
+ this.finishGoalSubmissionAttempt(attempt);
425
+ return;
426
+ }
427
+ this.goalContinuationPending.set(input.sessionId, attempt);
428
+ try {
429
+ const result = await this.options.submit({
430
+ sessionId: attempt.sessionId,
431
+ kind: 'continue',
432
+ content: this.options.goals.prompt(attempt.sessionId, 'continue'),
433
+ deliveryIntent: input.deliveryIntent ?? 'QUEUE',
434
+ goal: generation,
435
+ ...(input.initiatedByUserId === undefined
436
+ ? {}
437
+ : { initiatedByUserId: input.initiatedByUserId }),
438
+ ...(input.usageGrant === undefined ? {} : { usageGrant: input.usageGrant }),
439
+ secretEnvironment: input.secretEnvironment,
440
+ ...(input.personalInstructions === undefined
441
+ ? {}
442
+ : { personalInstructions: input.personalInstructions }),
443
+ ...(input.marAgentModel === undefined ? {} : { marAgentModel: input.marAgentModel }),
444
+ ...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
445
+ ...(input.marAgentCompact === undefined ? {} : { marAgentCompact: input.marAgentCompact }),
446
+ ...(input.marAgentRuntimeScopeId === undefined
447
+ ? {}
448
+ : { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
449
+ });
450
+ if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
451
+ this.removeStaleGoalSubmission(attempt.sessionId, result);
452
+ this.finishGoalContinuationAttempt(attempt);
453
+ return;
454
+ }
455
+ if (this.goalContinuationPending.get(attempt.sessionId) === attempt) {
456
+ const retry = this.goalRetryTimers.get(attempt.sessionId);
457
+ if (retry !== undefined)
458
+ clearTimeout(retry.timer);
459
+ this.goalRetryTimers.delete(attempt.sessionId);
460
+ }
461
+ this.finishGoalContinuationAttempt(attempt);
462
+ }
463
+ catch (error) {
464
+ const failedAttempts = (input.failedAttempts ?? 0) + 1;
465
+ nodeLog('goal.continuation.enqueue.failed', {
466
+ sessionId: attempt.sessionId,
467
+ goalId: input.goal.id,
468
+ revision: input.goal.revision,
469
+ attempt: failedAttempts,
470
+ code: safeErrorCode(error, 'GOAL_CONTINUATION_ENQUEUE_FAILED')
471
+ });
472
+ if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
473
+ this.finishGoalContinuationAttempt(attempt);
474
+ return;
475
+ }
476
+ // A newer attempt owns retry decisions, even within the same Goal generation.
477
+ if (this.goalContinuationPending.get(attempt.sessionId) !== attempt)
478
+ return;
479
+ const session = this.options.runtime.getAgentSession(attempt.sessionId);
480
+ if (failedAttempts > 3 || session === undefined) {
481
+ this.clearGoalRetry(attempt.sessionId);
482
+ if (failedAttempts > 3) {
483
+ const blocked = this.options.goals.blockAfterRuntimeFailure(attempt.sessionId, input.goal.id, input.goal.revision);
484
+ this.nativeGoalBindings.delete(input.goal.id);
485
+ this.options.queue().removeGoalQueue(attempt.sessionId);
486
+ this.goalContinuationEpochs.delete(attempt.sessionId);
487
+ this.finishIdleGoalRun(attempt.sessionId, 'FAILED');
488
+ if (session !== undefined)
489
+ this.publishGoalState(session, blocked);
490
+ }
491
+ return;
492
+ }
493
+ this.finishGoalContinuationAttempt(attempt);
494
+ if (this.frozen)
495
+ return;
496
+ const previousRetry = this.goalRetryTimers.get(attempt.sessionId);
497
+ if (previousRetry !== undefined)
498
+ clearTimeout(previousRetry.timer);
499
+ const retry = {
500
+ sessionId: attempt.sessionId,
501
+ timer: setTimeout(() => {
502
+ if (!this.goalGenerationCurrent(retry.sessionId, generation))
503
+ return;
504
+ void this.enqueueGoalContinuation({
505
+ ...input,
506
+ sessionId: retry.sessionId,
507
+ failedAttempts,
508
+ continuationEpoch: attempt.epoch
509
+ });
510
+ }, 15_000)
511
+ };
512
+ this.goalRetryTimers.set(attempt.sessionId, retry);
513
+ retry.timer.unref();
514
+ }
515
+ finally {
516
+ this.finishGoalSubmissionAttempt(attempt);
517
+ }
518
+ }
519
+ handleGoalPreparationFailed(session, goal, activated) {
520
+ if (!activated || !this.goalGenerationCurrent(session.id, goal))
521
+ return;
522
+ this.options.goals.clear(session.id);
523
+ this.finishIdleGoalRun(session.id, 'FAILED');
524
+ this.options.commands.set(session.id, this.pendingGoalCommandState());
525
+ this.options.emitSession(session);
526
+ }
527
+ finishIdleGoalRun(sessionId, status) {
528
+ const run = this.options.runtime.activeSessionRun(sessionId);
529
+ if (run === undefined || this.options.runtime.currentExecutionId(run.id) !== undefined)
530
+ return;
531
+ this.options.emitRun(run.id, status === 'SUCCEEDED' ? 'run.completed' : 'run.failed', status === 'SUCCEEDED' ? {} : { code: 'GOAL_CONTINUATION_FAILED' }, status);
532
+ }
533
+ clearGoalState(sessionId, status) {
534
+ const goalExit = this.options.goals.exitSummary(sessionId, status);
535
+ const goal = this.options.goals.get(sessionId);
536
+ if (goal !== undefined)
537
+ this.nativeGoalBindings.delete(goal.id);
538
+ this.retireGoalContinuation(sessionId);
539
+ const removed = this.options.goals.clear(sessionId);
540
+ this.options.queue().removeGoalQueue(sessionId);
541
+ const commandState = this.options.commands.get(sessionId, 'goal');
542
+ if (commandState !== undefined)
543
+ this.options.commands.clear(sessionId, 'goal');
544
+ if (!removed && commandState === undefined)
545
+ return goalExit;
546
+ const session = this.options.runtime.getAgentSession(sessionId);
547
+ if (session !== undefined)
548
+ this.options.emitSession(session);
549
+ return goalExit;
550
+ }
551
+ prepareGoalSession(session) {
552
+ const access = goalUnrestrictedAccess(session.runner, session.access);
553
+ const requiresUpdate = session.access !== access;
554
+ const changed = session.runner !== 'mar-agent' && requiresUpdate;
555
+ const prepared = requiresUpdate
556
+ ? this.options.runtime.updateAgentSessionAccess(session.id, access)
557
+ : session;
558
+ if (changed &&
559
+ prepared.externalSessionId !== null &&
560
+ prepared.model !== null &&
561
+ prepared.effort !== null)
562
+ this.options.database()?.saveNativeSessionMetadata({
563
+ runner: prepared.runner,
564
+ externalSessionId: prepared.externalSessionId,
565
+ workspaceId: prepared.workspaceId,
566
+ model: prepared.model,
567
+ effort: prepared.effort,
568
+ access
569
+ });
570
+ return { session: prepared, changed };
571
+ }
572
+ pendingGoalCommandState() {
573
+ return {
574
+ commandId: 'goal',
575
+ active: true,
576
+ label: 'Goal',
577
+ detail: null,
578
+ closable: true,
579
+ closeInput: '/goal --clear'
580
+ };
581
+ }
582
+ publishGoalState(session, goal) {
583
+ if (goal.status !== 'active') {
584
+ this.options.commands.clear(session.id, 'goal');
585
+ this.options.emitSession(session);
586
+ return;
587
+ }
588
+ this.options.commands.set(session.id, {
589
+ commandId: 'goal',
590
+ active: goal.status === 'active',
591
+ label: 'Goal',
592
+ detail: goal.objective.slice(0, 500),
593
+ closable: true,
594
+ closeInput: '/goal',
595
+ goal: {
596
+ id: goal.id,
597
+ objective: goal.objective,
598
+ revision: goal.revision,
599
+ status: goal.status,
600
+ turns: goal.turns
601
+ }
602
+ });
603
+ this.options.emitSession(session);
604
+ }
605
+ }
606
+ const goalReadToolNames = new Set([
607
+ `${NODE_MCP.goal.name}.${NODE_MCP.goal.tools.get}`,
608
+ `${NODE_MCP.goal.name}_${NODE_MCP.goal.tools.get}`,
609
+ `mcp__${NODE_MCP.goal.name}__${NODE_MCP.goal.tools.get}`
610
+ ]);
611
+ function isGoalProgressItem(item) {
612
+ if (!isPlainRecord(item) || typeof item.kind !== 'string')
613
+ return false;
614
+ if (item.kind === 'command_execution' || item.kind === 'file_change')
615
+ return true;
616
+ if (item.kind !== 'tool_call' || !isPlainRecord(item.payload))
617
+ return false;
618
+ const toolName = item.payload.toolName;
619
+ return (typeof toolName === 'string' &&
620
+ !goalReadToolNames.has(toolName) &&
621
+ !(item.payload.namespace === NODE_MCP.goal.name && toolName === NODE_MCP.goal.tools.get));
622
+ }