@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.
- package/dist/connector.js +106 -873
- package/dist/database.js +4 -2
- package/dist/runner/claude-code-runner.js +15 -1
- package/dist/runner/mar-agent/managed-run-controller.js +126 -521
- package/dist/runner/mar-agent/session-runtime-registry.js +271 -0
- package/dist/runner/mar-agent/session-runtime.js +282 -0
- package/dist/service/conversation-hosting-lifecycle-service.js +195 -0
- package/dist/service/conversation-observation.js +240 -0
- package/dist/service/goal-continuation-coordinator.js +622 -0
- package/dist/service/native-session-watch-service.js +88 -270
- package/dist/service/restart-session-restorer.js +1 -1
- package/dist/service/runner-interaction-service.js +27 -1
- package/dist/service/scheduled-session-coordinator.js +119 -48
- package/dist/service/session-lifecycle-service.js +5 -2
- package/package.json +4 -4
package/dist/connector.js
CHANGED
|
@@ -3,8 +3,8 @@ import { isImageAttachment } from './util/node-operation-parsers.js';
|
|
|
3
3
|
import { ModelUsageOutbox } from './service/model-usage-outbox.js';
|
|
4
4
|
import { NodeModelUsageService } from './service/model-usage-service.js';
|
|
5
5
|
import { dirname, resolve } from 'node:path';
|
|
6
|
-
import { createEnvelope,
|
|
7
|
-
import {
|
|
6
|
+
import { createEnvelope, directNodeCommandSchema } from '@myagentroam/protocol';
|
|
7
|
+
import { loadNodeConfig, computerUseRuntimeExecutable, directTransferUdpPortRange, nodeDataDirectory, nodeDatabasePath } from './config.js';
|
|
8
8
|
import { NodeMetrics, nodeLog } from './operational.js';
|
|
9
9
|
import { assertLocalSqlitePath } from './storage.js';
|
|
10
10
|
import { detectCapabilitiesAsync, unavailableCapabilities } from './capabilities.js';
|
|
@@ -12,9 +12,9 @@ import { NodeDatabase } from './database.js';
|
|
|
12
12
|
import { TerminalManager } from './terminal.js';
|
|
13
13
|
import { NodeRuntimeState } from './runtime-state.js';
|
|
14
14
|
import { CommandStateStore } from './runner-command-engine.js';
|
|
15
|
-
import {
|
|
15
|
+
import { GoalRuntime } from './service/goal-runtime.js';
|
|
16
|
+
import { GoalContinuationCoordinator } from './service/goal-continuation-coordinator.js';
|
|
16
17
|
import { GoalMcpBridge } from './service/goal-mcp-bridge.js';
|
|
17
|
-
import { NODE_MCP } from './mcp/naming.js';
|
|
18
18
|
import { RunnerUsageReader } from './runner-usage.js';
|
|
19
19
|
import { ClaudeCodeRunner } from './runner/claude-code-runner.js';
|
|
20
20
|
import { ClaudeManagedRunController } from './runner/claude/managed-run-controller.js';
|
|
@@ -61,13 +61,12 @@ import { WorkspaceQueueWorkbenchService } from './service/workspace-queue-workbe
|
|
|
61
61
|
import { ContinuousRunCoordinator } from './service/continuous-run-coordinator.js';
|
|
62
62
|
import { SessionIdentityService } from './service/session-identity-service.js';
|
|
63
63
|
import { NativeSessionProjectionService } from './service/native-session-projection-service.js';
|
|
64
|
-
import {
|
|
64
|
+
import { ConversationHistoryService, hasManagedActiveRun } from './service/conversation-history-service.js';
|
|
65
65
|
import { ConversationSegmentService } from './service/conversation-segment-service.js';
|
|
66
66
|
import { RunnerInteractionService } from './service/runner-interaction-service.js';
|
|
67
67
|
import { SessionLifecycleService } from './service/session-lifecycle-service.js';
|
|
68
68
|
import { ExternalSessionResumeService } from './service/external-session-resume-service.js';
|
|
69
69
|
import { SessionMessageService } from './service/session-message-service.js';
|
|
70
|
-
import { composerResourceInputTags } from './service/composer-message-input.js';
|
|
71
70
|
import { SessionScheduleService } from './service/session-schedule-service.js';
|
|
72
71
|
import { ScheduledSessionCoordinator } from './service/scheduled-session-coordinator.js';
|
|
73
72
|
import { ScheduledSessionStore } from './service/scheduled-session-store.js';
|
|
@@ -94,14 +93,8 @@ import { RunnerChannelMessageService } from './service/runner-channel-message-se
|
|
|
94
93
|
import { ManagedRunnerSessionService } from './service/managed-runner-session-service.js';
|
|
95
94
|
import { NodeConnectionLifecycleService } from './service/node-connection-lifecycle-service.js';
|
|
96
95
|
import { NodeUpgradeCoordinator, nodeUpgradeRequestPath } from './service/node-upgrade-coordinator.js';
|
|
97
|
-
import {
|
|
98
|
-
import {
|
|
99
|
-
import { HostedConversationNodeService } from './service/hosted-conversation-node-service.js';
|
|
100
|
-
import { BubblewrapSandboxExecutor } from '@myagentroam/agent/sandbox/linux';
|
|
101
|
-
import { hostedConversationSandboxProfile } from './service/hosted-conversation-sandbox-profile.js';
|
|
102
|
-
import { MarAgentHostedConversationRuntime } from './runner/mar-agent/hosted-conversation-runtime.js';
|
|
103
|
-
import { conversationHostingCapability, probeBubblewrap } from './service/conversation-hosting-capability-service.js';
|
|
104
|
-
import { ConversationHostingMaintenanceService } from './service/conversation-hosting-maintenance-service.js';
|
|
96
|
+
import { probeBubblewrap } from './service/conversation-hosting-capability-service.js';
|
|
97
|
+
import { ConversationHostingLifecycleService } from './service/conversation-hosting-lifecycle-service.js';
|
|
105
98
|
import { ConversationHostingEnablementService } from './service/conversation-hosting-enablement-service.js';
|
|
106
99
|
import { ComputerUseEnablementService } from './service/computer-use-enablement-service.js';
|
|
107
100
|
import { safeErrorCode } from './util/safe-error.js';
|
|
@@ -205,14 +198,7 @@ export class NodeConnector {
|
|
|
205
198
|
capabilities;
|
|
206
199
|
capabilityDetector;
|
|
207
200
|
conversationHostingProbe;
|
|
208
|
-
|
|
209
|
-
conversationHostingFailureCode;
|
|
210
|
-
conversationTemplateOperations;
|
|
211
|
-
hostedConversationOperations;
|
|
212
|
-
conversationHostingMaintenanceOperations;
|
|
213
|
-
hostedConversationRuntime;
|
|
214
|
-
hostedConversationService;
|
|
215
|
-
conversationHostingMaintenanceService;
|
|
201
|
+
conversationHosting;
|
|
216
202
|
configPath;
|
|
217
203
|
reconnect;
|
|
218
204
|
claudeApprovalTimeoutMs;
|
|
@@ -283,25 +269,8 @@ export class NodeConnector {
|
|
|
283
269
|
workspaceService;
|
|
284
270
|
commandStates = new CommandStateStore();
|
|
285
271
|
goalRuntime = new GoalRuntime(() => undefined);
|
|
286
|
-
goalRetryTimers = new Map();
|
|
287
|
-
goalContinuationPending = new Map();
|
|
288
|
-
goalSubmissionAttempts = new Map();
|
|
289
|
-
goalContinuationEpochs = new Map();
|
|
290
|
-
goalExecutionProgress = new Set();
|
|
291
|
-
nativeGoalBindings = new Map();
|
|
292
272
|
sessionScheduleService;
|
|
293
|
-
goalMcpBridge = new GoalMcpBridge(this.goalRuntime, (sessionId) =>
|
|
294
|
-
const goal = this.goalRuntime.get(sessionId);
|
|
295
|
-
const session = this.runtime.getAgentSession(sessionId);
|
|
296
|
-
if (goal === undefined || session === undefined)
|
|
297
|
-
return;
|
|
298
|
-
if (goal.status !== 'active') {
|
|
299
|
-
this.nativeGoalBindings.delete(goal.id);
|
|
300
|
-
this.retireGoalContinuation(sessionId);
|
|
301
|
-
this.continuousRunCoordinator.removeGoalQueue(sessionId);
|
|
302
|
-
}
|
|
303
|
-
this.publishGoalState(session, goal);
|
|
304
|
-
});
|
|
273
|
+
goalMcpBridge = new GoalMcpBridge(this.goalRuntime, (sessionId) => this.goalCoordinator.changed(sessionId));
|
|
305
274
|
scheduleMcpBridge = new ScheduleMcpBridge(() => {
|
|
306
275
|
if (this.database === undefined)
|
|
307
276
|
throw new Error('NODE_DATABASE_UNAVAILABLE');
|
|
@@ -313,7 +282,7 @@ export class NodeConnector {
|
|
|
313
282
|
session: this.sessionPresentationService.present(session)
|
|
314
283
|
});
|
|
315
284
|
this.scheduledSessions.changed();
|
|
316
|
-
}, (sessionId, proposalId) => this.
|
|
285
|
+
}, (sessionId, proposalId) => this.scheduledSessions.proposalCreated(sessionId, proposalId), (input) => {
|
|
317
286
|
if (this.sessionScheduleService === undefined)
|
|
318
287
|
throw new Error('SCHEDULE_SERVICE_UNAVAILABLE');
|
|
319
288
|
return this.sessionScheduleService.createFromMcp(input);
|
|
@@ -355,6 +324,17 @@ export class NodeConnector {
|
|
|
355
324
|
recentRunProjectionService = new RecentRunProjectionService((message) => this.send('recent-runs', message));
|
|
356
325
|
/** Runtime/session state is intentionally not persisted to Node SQLite. */
|
|
357
326
|
runtime = new NodeRuntimeState(() => this.config?.nodeId ?? 'runtime-node', (workspaceId) => this.database?.getWorkspace(workspaceId), Date.now, (run, session, userMessage) => this.recentRunProjectionService.record(run, session, userMessage));
|
|
327
|
+
goalCoordinator = new GoalContinuationCoordinator({
|
|
328
|
+
goals: this.goalRuntime,
|
|
329
|
+
runtime: this.runtime,
|
|
330
|
+
commands: this.commandStates,
|
|
331
|
+
queue: () => this.continuousRunCoordinator,
|
|
332
|
+
submit: (input) => this.sessionMessageService.submitGoal(input),
|
|
333
|
+
emitRun: (...args) => this.runEventBridge.emitRun(...args),
|
|
334
|
+
present: (session) => this.sessionPresentationService.present(session),
|
|
335
|
+
emitSession: (session) => this.emitSessionWorkbenchEvent({ session: this.sessionPresentationService.present(session) }),
|
|
336
|
+
database: () => this.database
|
|
337
|
+
});
|
|
358
338
|
metrics = new NodeMetrics();
|
|
359
339
|
runEventService = new RunEventService({
|
|
360
340
|
runtime: this.runtime,
|
|
@@ -375,21 +355,8 @@ export class NodeConnector {
|
|
|
375
355
|
scheduleContextRefresh: (sessionId) => this.sessionContextService.schedule(sessionId),
|
|
376
356
|
refreshWorkspaceSessions: (workspaceId) => this.workspaceWorkbenchService.ensureTopicSnapshot(workspaceId, 'sessions'),
|
|
377
357
|
onTerminalRun: (run, status) => {
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
const store = new ScheduledSessionStore(this.database.raw);
|
|
381
|
-
store.finish(run.clientMessageId, status === 'SUCCEEDED' ? 'SUCCEEDED' : 'FAILED', status === 'SUCCEEDED' ? undefined : `SCHEDULE_RUN_${status}`, run.id);
|
|
382
|
-
const task = store.getById(run.clientMessageId.split(':', 1)[0]);
|
|
383
|
-
if (task !== undefined) {
|
|
384
|
-
if (task.notifyOnTerminal)
|
|
385
|
-
this.publishScheduledTaskNotification(task, run.clientMessageId, {
|
|
386
|
-
kind: 'SCHEDULE_TERMINAL',
|
|
387
|
-
outcome: status,
|
|
388
|
-
runId: run.id
|
|
389
|
-
});
|
|
390
|
-
else if (status !== 'SUCCEEDED')
|
|
391
|
-
this.emitScheduledNotification(task, run.clientMessageId, `SCHEDULE_RUN_${status}`, 'FAILED');
|
|
392
|
-
}
|
|
358
|
+
if (this.database !== undefined)
|
|
359
|
+
this.scheduledSessions.executionTerminated(run, status);
|
|
393
360
|
},
|
|
394
361
|
dispatchQueue: async (sessionId, delayMs = 0) => {
|
|
395
362
|
if (this.upgradeCoordinator.pending())
|
|
@@ -401,77 +368,14 @@ export class NodeConnector {
|
|
|
401
368
|
await this.continuousRunCoordinator.dispatch(sessionId);
|
|
402
369
|
},
|
|
403
370
|
executionStarted: (executionId) => this.continuousRunCoordinator.executionStarted(executionId),
|
|
404
|
-
keepRunActive: (sessionId) =>
|
|
405
|
-
this.goalRuntime.get(sessionId)?.status === 'active',
|
|
371
|
+
keepRunActive: (sessionId) => this.goalCoordinator.keepRunActive(sessionId),
|
|
406
372
|
executionTerminating: (executionId, status, permissionStop) => {
|
|
407
373
|
this.gitOperationService.executionFinished(executionId);
|
|
408
|
-
|
|
409
|
-
return;
|
|
410
|
-
const pending = this.continuousRunCoordinator.pending(executionId);
|
|
411
|
-
if (pending?.goal === undefined)
|
|
412
|
-
return;
|
|
413
|
-
const started = this.continuousRunCoordinator.hasExecutionStarted(executionId);
|
|
414
|
-
const progressed = this.goalExecutionProgress.delete(executionId);
|
|
415
|
-
if (!this.goalGenerationCurrent(pending.sessionId, pending.goal))
|
|
416
|
-
return;
|
|
417
|
-
if (permissionStop !== undefined) {
|
|
418
|
-
this.retireGoalContinuation(pending.sessionId);
|
|
419
|
-
this.continuousRunCoordinator.removeGoalQueue(pending.sessionId);
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const disposition = this.goalRuntime.recordExecutionResult(pending.sessionId, {
|
|
423
|
-
failed: status === 'FAILED',
|
|
424
|
-
progressed: status !== 'SUCCEEDED' || progressed
|
|
425
|
-
});
|
|
426
|
-
if (disposition === 'blocked' || disposition === 'completed') {
|
|
427
|
-
this.nativeGoalBindings.delete(pending.goal.id);
|
|
428
|
-
this.retireGoalContinuation(pending.sessionId);
|
|
429
|
-
this.continuousRunCoordinator.removeGoalQueue(pending.sessionId);
|
|
430
|
-
const terminalGoal = this.goalRuntime.get(pending.sessionId);
|
|
431
|
-
const session = this.runtime.getAgentSession(pending.sessionId);
|
|
432
|
-
if (terminalGoal !== undefined && session !== undefined)
|
|
433
|
-
this.publishGoalState(session, terminalGoal);
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
if (status !== 'FAILED' || started)
|
|
437
|
-
return;
|
|
438
|
-
this.clearGoalRetry(pending.sessionId);
|
|
439
|
-
const continuationEpoch = this.goalContinuationEpochs.get(pending.sessionId) ?? 0;
|
|
440
|
-
const retry = {
|
|
441
|
-
sessionId: pending.sessionId,
|
|
442
|
-
timer: setTimeout(() => {
|
|
443
|
-
this.goalRetryTimers.delete(retry.sessionId);
|
|
444
|
-
void this.enqueueGoalContinuation({
|
|
445
|
-
sessionId: retry.sessionId,
|
|
446
|
-
goal: pending.goal,
|
|
447
|
-
...(pending.usageGrant === undefined ? {} : { usageGrant: pending.usageGrant }),
|
|
448
|
-
secretEnvironment: pending.secretEnvironment,
|
|
449
|
-
...(pending.personalInstructions === undefined
|
|
450
|
-
? {}
|
|
451
|
-
: { personalInstructions: pending.personalInstructions }),
|
|
452
|
-
...(pending.marAgentModel === undefined
|
|
453
|
-
? {}
|
|
454
|
-
: { marAgentModel: pending.marAgentModel }),
|
|
455
|
-
...(pending.marAgentModels === undefined
|
|
456
|
-
? {}
|
|
457
|
-
: { marAgentModels: pending.marAgentModels }),
|
|
458
|
-
...(pending.marAgentCompact === undefined
|
|
459
|
-
? {}
|
|
460
|
-
: { marAgentCompact: pending.marAgentCompact }),
|
|
461
|
-
...(pending.marAgentRuntimeScopeId === undefined
|
|
462
|
-
? {}
|
|
463
|
-
: { marAgentRuntimeScopeId: pending.marAgentRuntimeScopeId }),
|
|
464
|
-
continuationEpoch,
|
|
465
|
-
deliveryIntent: 'SEND'
|
|
466
|
-
});
|
|
467
|
-
}, 15_000)
|
|
468
|
-
};
|
|
469
|
-
this.goalRetryTimers.set(pending.sessionId, retry);
|
|
470
|
-
retry.timer.unref();
|
|
374
|
+
this.goalCoordinator.executionTerminating(executionId, status, permissionStop);
|
|
471
375
|
},
|
|
472
376
|
releaseExecution: (executionId) => {
|
|
473
377
|
this.modelUsage.release(executionId);
|
|
474
|
-
this.
|
|
378
|
+
this.goalCoordinator.releaseExecution(executionId);
|
|
475
379
|
this.revokeExecutionMcps(executionId);
|
|
476
380
|
this.continuousRunCoordinator.remove(executionId);
|
|
477
381
|
}
|
|
@@ -491,8 +395,6 @@ export class NodeConnector {
|
|
|
491
395
|
present: (session) => this.sessionPresentationService.present(session),
|
|
492
396
|
readPage: (session, limit) => this.conversationHistoryService.read(session, { limit }),
|
|
493
397
|
readHistoryPage: (session, limit) => this.conversationHistoryService.readHistoryPage(session, { limit }),
|
|
494
|
-
convergePage: convergeConversationWatchPage,
|
|
495
|
-
composeRuntimePage: composeConversationRuntimePage,
|
|
496
398
|
projectInitialPage: (session, page, _unit, limit) => {
|
|
497
399
|
const context = this.sessionContextService.snapshot(session.id);
|
|
498
400
|
this.sessionContextService.schedule(session);
|
|
@@ -526,6 +428,35 @@ export class NodeConnector {
|
|
|
526
428
|
this.capabilitiesProvided = options.capabilities !== undefined;
|
|
527
429
|
this.capabilities = options.capabilities ?? unavailableCapabilities();
|
|
528
430
|
this.conversationHostingProbe = options.conversationHostingProbe ?? probeBubblewrap;
|
|
431
|
+
this.conversationHosting = new ConversationHostingLifecycleService({
|
|
432
|
+
config: () => this.config,
|
|
433
|
+
configPath: this.configPath,
|
|
434
|
+
database: () => this.database,
|
|
435
|
+
capabilities: () => this.capabilities,
|
|
436
|
+
probe: this.conversationHostingProbe,
|
|
437
|
+
runtime: {
|
|
438
|
+
webSearch: (input, signal) => this.webToolService.webSearch(input, signal),
|
|
439
|
+
webFetch: (input, signal) => this.webToolService.webFetch(input, signal, false),
|
|
440
|
+
modelFactory: this.marAgentRuntimeModels,
|
|
441
|
+
registerNativeImages: (runId, roots, images) => this.runAttachmentService.registerNativeImages(runId, roots, images, {
|
|
442
|
+
reuseCachedRunImages: false
|
|
443
|
+
})
|
|
444
|
+
},
|
|
445
|
+
host: {
|
|
446
|
+
modelUsage: (grant, usage) => {
|
|
447
|
+
const runId = usage.executionId ?? usage.requestId;
|
|
448
|
+
this.modelUsage.bind(runId, grant, usage.sessionId, 'mar-agent');
|
|
449
|
+
this.modelUsage.record(runId, normalizeMarAgentUsage(usage));
|
|
450
|
+
this.modelUsage.release(runId);
|
|
451
|
+
},
|
|
452
|
+
nodeGeneration: () => this.nodeGeneration,
|
|
453
|
+
sessionWatchSlots: this.sessionWatchSlots,
|
|
454
|
+
emitWatchEvent: (payload) => this.send('watch.event', payload),
|
|
455
|
+
nextWorkbenchEvent: (payload) => this.workbenchEventService.next('hosted-conversation', payload),
|
|
456
|
+
emitWorkbenchEvent: (event) => this.workbenchEventService.publish(event),
|
|
457
|
+
readImage: (runId, imageRef) => this.runAttachmentService.readNative(runId, imageRef)
|
|
458
|
+
}
|
|
459
|
+
});
|
|
529
460
|
this.upgradeCoordinator = new NodeUpgradeCoordinator(() => {
|
|
530
461
|
if (this.config === undefined)
|
|
531
462
|
throw new Error('NODE_CONFIG_UNAVAILABLE');
|
|
@@ -547,7 +478,7 @@ export class NodeConnector {
|
|
|
547
478
|
const detect = options.capabilityDetector ?? detectCapabilitiesAsync;
|
|
548
479
|
this.capabilityDetector = async () => {
|
|
549
480
|
const capabilities = await detect();
|
|
550
|
-
return this.projectComputerUseCapability(await this.
|
|
481
|
+
return this.projectComputerUseCapability(await this.conversationHosting.project({
|
|
551
482
|
...capabilities,
|
|
552
483
|
metadata: upgradeMetadata(capabilities.metadata, this.upgradeCoordinator.snapshot())
|
|
553
484
|
}));
|
|
@@ -576,10 +507,10 @@ export class NodeConnector {
|
|
|
576
507
|
promoteSession: (sessionId, nativeSessionId) => this.sessionIdentityService.promote(sessionId, nativeSessionId),
|
|
577
508
|
send: (type, payload) => this.send(type, payload),
|
|
578
509
|
emit: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
579
|
-
nativeGoalUpdated: (runId, goal) => this.handleNativeGoalUpdate(runId, goal),
|
|
510
|
+
nativeGoalUpdated: (runId, goal) => this.goalCoordinator.handleNativeGoalUpdate(runId, goal),
|
|
580
511
|
cancel: (runId, threadId, turnId, cwd, status) => this.runnerInteractionService.cancelCodexTurn(runId, threadId, turnId, cwd, status),
|
|
581
512
|
createApproval: (input) => this.runtime.createApproval(input),
|
|
582
|
-
publishApproval: (input) => this.
|
|
513
|
+
publishApproval: (input) => this.runnerInteractionService.publishApproval(input),
|
|
583
514
|
emitItem: (runId, item) => this.emitConversationItem(runId, item),
|
|
584
515
|
appendImages: (runId, images) => this.runAttachmentService.append(runId, images.filter(isRunnerImageAttachment)),
|
|
585
516
|
syncTitle: (sessionId, nativeSessionId, cwd) => this.managedRunnerSessions.refreshTitle(sessionId, nativeSessionId, cwd),
|
|
@@ -620,7 +551,7 @@ export class NodeConnector {
|
|
|
620
551
|
this.capabilities = this.projectComputerUseCapability(this.capabilities);
|
|
621
552
|
this.marAgentRunController = new MarAgentManagedRunController(this.marAgentClient, {
|
|
622
553
|
createApproval: (input) => this.runtime.createApproval(input),
|
|
623
|
-
publishApproval: (input) => this.
|
|
554
|
+
publishApproval: (input) => this.runnerInteractionService.publishApproval(input),
|
|
624
555
|
active: this.runState.active,
|
|
625
556
|
adopt: (input) => this.runtime.adoptRun(input),
|
|
626
557
|
promote: (sessionId, nativeSessionId) => this.sessionIdentityService.promote(sessionId, nativeSessionId),
|
|
@@ -683,7 +614,7 @@ export class NodeConnector {
|
|
|
683
614
|
expireApproval: (approvalId) => {
|
|
684
615
|
this.runtime.expireApproval(approvalId);
|
|
685
616
|
},
|
|
686
|
-
publishApproval: (input) => this.
|
|
617
|
+
publishApproval: (input) => this.runnerInteractionService.publishApproval(input),
|
|
687
618
|
commandState: (sessionId, commandId) => this.commandStates.get(sessionId, commandId),
|
|
688
619
|
setCommandState: (sessionId, state) => this.commandStates.set(sessionId, state),
|
|
689
620
|
clearCommandState: (sessionId, commandId) => this.commandStates.clear(sessionId, commandId),
|
|
@@ -732,7 +663,7 @@ export class NodeConnector {
|
|
|
732
663
|
emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
733
664
|
emitItem: (runId, item) => this.emitConversationItem(runId, item),
|
|
734
665
|
approvalTimeoutMs: this.claudeApprovalTimeoutMs,
|
|
735
|
-
publishApproval: (input) => this.
|
|
666
|
+
publishApproval: (input) => this.runEventBridge.publishApproval(input)
|
|
736
667
|
});
|
|
737
668
|
this.runnerChannelMessages = new RunnerChannelMessageService({
|
|
738
669
|
interactions: this.runnerInteractionService,
|
|
@@ -781,7 +712,7 @@ export class NodeConnector {
|
|
|
781
712
|
respondUserInput: (runId, requestId, answers) => this.runnerInteractionService.respondUserInput(runId, requestId, answers),
|
|
782
713
|
pauseQueue: (sessionId) => this.workspaceQueueWorkbenchService.pauseSessionQueue(sessionId),
|
|
783
714
|
runInterrupted: (sessionId) => {
|
|
784
|
-
const goalExit = this.clearGoalState(sessionId, 'stopped');
|
|
715
|
+
const goalExit = this.goalCoordinator.clearGoalState(sessionId, 'stopped');
|
|
785
716
|
return goalExit === undefined ? undefined : { goalExit };
|
|
786
717
|
},
|
|
787
718
|
emitRun: (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status),
|
|
@@ -857,10 +788,11 @@ export class NodeConnector {
|
|
|
857
788
|
markInterrupted: (runId) => this.runState.interruptRequested.add(runId),
|
|
858
789
|
mcps: (workspaceId) => this.database?.effectiveMcpInstallations(workspaceId) ?? [],
|
|
859
790
|
retainEnvironment: (environment) => this.gitExecution().retain(environment),
|
|
860
|
-
goalCurrent: (input) => input.goal === undefined ||
|
|
791
|
+
goalCurrent: (input) => input.goal === undefined ||
|
|
792
|
+
this.goalCoordinator.goalGenerationCurrent(input.sessionId, input.goal),
|
|
861
793
|
goalActive: (sessionId) => this.goalRuntime.get(sessionId)?.status === 'active',
|
|
862
|
-
afterExecutionStarted: (input) => this.maintainGoalContinuation(input, true),
|
|
863
|
-
afterExecutionSteered: (input) => this.maintainGoalContinuation(input, false),
|
|
794
|
+
afterExecutionStarted: (input) => this.goalCoordinator.maintainGoalContinuation(input, true),
|
|
795
|
+
afterExecutionSteered: (input) => this.goalCoordinator.maintainGoalContinuation(input, false),
|
|
864
796
|
afterExecutionStartFailed: (input, error) => {
|
|
865
797
|
nodeLog('goal.execution-start.failed', {
|
|
866
798
|
sessionId: input.sessionId,
|
|
@@ -939,35 +871,8 @@ export class NodeConnector {
|
|
|
939
871
|
migrateCommands: (previousId, nextId) => this.commandStates.promoteSessionIdentity(previousId, nextId),
|
|
940
872
|
migrateGoal: (previousId, nextId) => {
|
|
941
873
|
this.runAttachmentService.migrateSession(previousId, nextId);
|
|
942
|
-
this.
|
|
874
|
+
this.goalCoordinator.migrateSession(previousId, nextId);
|
|
943
875
|
this.goalMcpBridge.migrateSession(previousId, nextId);
|
|
944
|
-
const retry = this.goalRetryTimers.get(previousId);
|
|
945
|
-
if (retry !== undefined) {
|
|
946
|
-
this.goalRetryTimers.delete(previousId);
|
|
947
|
-
retry.sessionId = nextId;
|
|
948
|
-
this.goalRetryTimers.set(nextId, retry);
|
|
949
|
-
}
|
|
950
|
-
const pendingContinuation = this.goalContinuationPending.get(previousId);
|
|
951
|
-
if (pendingContinuation !== undefined) {
|
|
952
|
-
this.goalContinuationPending.delete(previousId);
|
|
953
|
-
pendingContinuation.sessionId = nextId;
|
|
954
|
-
this.goalContinuationPending.set(nextId, pendingContinuation);
|
|
955
|
-
}
|
|
956
|
-
const submissionAttempts = this.goalSubmissionAttempts.get(previousId);
|
|
957
|
-
if (submissionAttempts !== undefined) {
|
|
958
|
-
this.goalSubmissionAttempts.delete(previousId);
|
|
959
|
-
const currentAttempts = this.goalSubmissionAttempts.get(nextId) ?? new Set();
|
|
960
|
-
for (const attempt of submissionAttempts) {
|
|
961
|
-
attempt.sessionId = nextId;
|
|
962
|
-
currentAttempts.add(attempt);
|
|
963
|
-
}
|
|
964
|
-
this.goalSubmissionAttempts.set(nextId, currentAttempts);
|
|
965
|
-
}
|
|
966
|
-
const continuationEpoch = this.goalContinuationEpochs.get(previousId);
|
|
967
|
-
if (continuationEpoch !== undefined) {
|
|
968
|
-
this.goalContinuationEpochs.delete(previousId);
|
|
969
|
-
this.goalContinuationEpochs.set(nextId, continuationEpoch);
|
|
970
|
-
}
|
|
971
876
|
},
|
|
972
877
|
migrateQueue: (previousId, nextId) => this.continuousRunCoordinator.migrateSession(previousId, nextId),
|
|
973
878
|
migrateRunnerState: (previousId, nextId) => {
|
|
@@ -990,9 +895,12 @@ export class NodeConnector {
|
|
|
990
895
|
projection: this.nativeSessionProjectionService,
|
|
991
896
|
identity: this.sessionIdentityService,
|
|
992
897
|
present: (session) => this.sessionPresentationService.present(session),
|
|
993
|
-
emitSession: (session) => this.
|
|
898
|
+
emitSession: (session) => this.publishWorkbenchEvent('session', {
|
|
994
899
|
session: this.sessionPresentationService.present(session)
|
|
995
900
|
}),
|
|
901
|
+
invalidateWorkspaceSessions: (workspaceId) => {
|
|
902
|
+
void this.workspaceWorkbenchService?.invalidateTopic(workspaceId, 'sessions');
|
|
903
|
+
},
|
|
996
904
|
execute: (operation, data) => this.modelUsage.withGrant(isPlainRecord(data) ? data.__usageGrant : undefined, () => this.executeNodeOperation(operation, data)),
|
|
997
905
|
interceptedSessionId: (clientMessageId) => this.fakeRunner === undefined ? undefined : `fake:${clientMessageId}`
|
|
998
906
|
});
|
|
@@ -1026,83 +934,9 @@ export class NodeConnector {
|
|
|
1026
934
|
emitSession: (session) => this.emitSessionWorkbenchEvent({
|
|
1027
935
|
session: this.sessionPresentationService.present(session)
|
|
1028
936
|
}),
|
|
1029
|
-
beginGoal: (session) =>
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
return {
|
|
1033
|
-
session: this.sessionPresentationService.present(session),
|
|
1034
|
-
goalAlreadyActive: true
|
|
1035
|
-
};
|
|
1036
|
-
if (existing !== undefined) {
|
|
1037
|
-
const previousGoal = this.goalRuntime.get(session.id);
|
|
1038
|
-
if (previousGoal !== undefined)
|
|
1039
|
-
this.nativeGoalBindings.delete(previousGoal.id);
|
|
1040
|
-
this.goalRuntime.clear(session.id);
|
|
1041
|
-
this.commandStates.clear(session.id, 'goal');
|
|
1042
|
-
}
|
|
1043
|
-
const prepared = this.prepareGoalSession(session);
|
|
1044
|
-
this.continuousRunCoordinator.removePendingQueue(session.id);
|
|
1045
|
-
this.commandStates.set(session.id, this.pendingGoalCommandState());
|
|
1046
|
-
this.emitWorkbenchEvent('session', {
|
|
1047
|
-
session: this.sessionPresentationService.present(prepared.session)
|
|
1048
|
-
});
|
|
1049
|
-
return {
|
|
1050
|
-
session: this.sessionPresentationService.present(prepared.session),
|
|
1051
|
-
goalAccessChanged: prepared.changed
|
|
1052
|
-
};
|
|
1053
|
-
},
|
|
1054
|
-
goal: async ({ session, objective, initiatedByUserId, suppressNotifications, secretEnvironment, personalInstructions, marAgentModel, marAgentModels, marAgentCompact, marAgentRuntimeScopeId, usageGrant }) => {
|
|
1055
|
-
this.invalidateGoalContinuation(session.id);
|
|
1056
|
-
const prepared = this.prepareGoalSession(session);
|
|
1057
|
-
const goalSession = prepared.session;
|
|
1058
|
-
const activeRun = this.runtime.activeSessionRun(session.id);
|
|
1059
|
-
const replacing = activeRun !== undefined && this.runtime.currentExecutionId(activeRun.id) !== undefined;
|
|
1060
|
-
this.continuousRunCoordinator.removeGoalQueue(session.id);
|
|
1061
|
-
const goal = this.goalRuntime.start(session.id, objective);
|
|
1062
|
-
this.publishGoalState(goalSession, goal);
|
|
1063
|
-
const attempt = this.beginGoalSubmission(session.id);
|
|
1064
|
-
const goalRef = { id: goal.id, revision: goal.revision, epoch: attempt.epoch };
|
|
1065
|
-
let result;
|
|
1066
|
-
try {
|
|
1067
|
-
result = await this.sessionMessageService.submitGoal({
|
|
1068
|
-
sessionId: attempt.sessionId,
|
|
1069
|
-
kind: 'start',
|
|
1070
|
-
content: this.goalRuntime.prompt(attempt.sessionId, 'start'),
|
|
1071
|
-
objective: goal.objective,
|
|
1072
|
-
deliveryIntent: replacing ? 'REPLACE_CURRENT' : 'SEND',
|
|
1073
|
-
goal: goalRef,
|
|
1074
|
-
...(suppressNotifications === true ? { suppressNotifications: true } : {}),
|
|
1075
|
-
...(usageGrant === undefined ? {} : { usageGrant }),
|
|
1076
|
-
...(initiatedByUserId === undefined ? {} : { initiatedByUserId }),
|
|
1077
|
-
secretEnvironment,
|
|
1078
|
-
...(personalInstructions === undefined ? {} : { personalInstructions }),
|
|
1079
|
-
...(marAgentModel === undefined ? {} : { marAgentModel }),
|
|
1080
|
-
...(marAgentModels === undefined ? {} : { marAgentModels }),
|
|
1081
|
-
...(marAgentCompact === undefined ? {} : { marAgentCompact }),
|
|
1082
|
-
...(marAgentRuntimeScopeId === undefined ? {} : { marAgentRuntimeScopeId })
|
|
1083
|
-
});
|
|
1084
|
-
if (!this.goalGenerationCurrent(attempt.sessionId, goalRef)) {
|
|
1085
|
-
this.removeStaleGoalSubmission(attempt.sessionId, result);
|
|
1086
|
-
throw new Error('GOAL_REVISION_STALE');
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
finally {
|
|
1090
|
-
this.finishGoalSubmissionAttempt(attempt);
|
|
1091
|
-
}
|
|
1092
|
-
return {
|
|
1093
|
-
...(isPlainRecord(result) ? result : { result }),
|
|
1094
|
-
goalAccessChanged: prepared.changed
|
|
1095
|
-
};
|
|
1096
|
-
},
|
|
1097
|
-
clearGoal: (session) => {
|
|
1098
|
-
const goalExit = this.clearGoalState(session.id, 'removed');
|
|
1099
|
-
this.finishIdleGoalRun(session.id, 'SUCCEEDED');
|
|
1100
|
-
return {
|
|
1101
|
-
session: this.sessionPresentationService.present(session),
|
|
1102
|
-
cleared: true,
|
|
1103
|
-
...(goalExit === undefined ? {} : { goalExit })
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
937
|
+
beginGoal: (session) => this.goalCoordinator.begin(session),
|
|
938
|
+
goal: (input) => this.goalCoordinator.start(input),
|
|
939
|
+
clearGoal: (session) => this.goalCoordinator.clear(session)
|
|
1106
940
|
});
|
|
1107
941
|
this.sessionMessageService = new SessionMessageService({
|
|
1108
942
|
runtime: this.runtime,
|
|
@@ -1121,53 +955,9 @@ export class NodeConnector {
|
|
|
1121
955
|
const session = this.runtime.getAgentSession(sessionId);
|
|
1122
956
|
return session?.channelToken ?? undefined;
|
|
1123
957
|
},
|
|
1124
|
-
goalMessage: (session, content, intent) =>
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
return undefined;
|
|
1128
|
-
const current = this.goalRuntime.get(session.id);
|
|
1129
|
-
if (current?.status === 'active') {
|
|
1130
|
-
const idlePausedGoal = this.runtime.activeSessionRun(session.id) === undefined &&
|
|
1131
|
-
this.runtime.sessionQueue(session.id).paused;
|
|
1132
|
-
if (intent !== 'REPLACE_CURRENT' && !idlePausedGoal)
|
|
1133
|
-
return undefined;
|
|
1134
|
-
this.invalidateGoalContinuation(session.id);
|
|
1135
|
-
const resumed = this.goalRuntime.resume(session.id);
|
|
1136
|
-
if (resumed !== current)
|
|
1137
|
-
this.publishGoalState(session, resumed);
|
|
1138
|
-
return {
|
|
1139
|
-
runnerContent: content,
|
|
1140
|
-
goal: {
|
|
1141
|
-
id: resumed.id,
|
|
1142
|
-
revision: resumed.revision,
|
|
1143
|
-
epoch: this.goalContinuationEpochs.get(session.id) ?? 0
|
|
1144
|
-
},
|
|
1145
|
-
activated: false
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
const objective = composerVisibleText(content);
|
|
1149
|
-
if (objective.length === 0)
|
|
1150
|
-
return undefined;
|
|
1151
|
-
this.invalidateGoalContinuation(session.id);
|
|
1152
|
-
const goal = this.goalRuntime.start(session.id, objective, composerResourceInputTags(content));
|
|
1153
|
-
this.publishGoalState(session, goal);
|
|
1154
|
-
return {
|
|
1155
|
-
runnerContent: this.goalRuntime.prompt(session.id, 'start'),
|
|
1156
|
-
goal: {
|
|
1157
|
-
id: goal.id,
|
|
1158
|
-
revision: goal.revision,
|
|
1159
|
-
epoch: this.goalContinuationEpochs.get(session.id) ?? 0
|
|
1160
|
-
},
|
|
1161
|
-
activated: true
|
|
1162
|
-
};
|
|
1163
|
-
},
|
|
1164
|
-
prepareGoalInsertion: (sessionId, submissionId, goal) => {
|
|
1165
|
-
if (!this.goalGenerationCurrent(sessionId, goal))
|
|
1166
|
-
return false;
|
|
1167
|
-
this.continuousRunCoordinator.removeGoalQueue(sessionId, undefined, submissionId);
|
|
1168
|
-
return true;
|
|
1169
|
-
},
|
|
1170
|
-
goalPreparationFailed: (session, goal, activated) => this.handleGoalPreparationFailed(session, goal, activated)
|
|
958
|
+
goalMessage: (session, content, intent) => this.goalCoordinator.goalMessage(session, content, intent),
|
|
959
|
+
prepareGoalInsertion: (sessionId, submissionId, goal) => this.goalCoordinator.prepareInsertion(sessionId, submissionId, goal),
|
|
960
|
+
goalPreparationFailed: (session, goal, activated) => this.goalCoordinator.handleGoalPreparationFailed(session, goal, activated)
|
|
1171
961
|
});
|
|
1172
962
|
const inputState = (entry, message) => this.marAgentClient.restartInputState(entry.session.id, message.clientMessageId);
|
|
1173
963
|
const restorer = new RestartSessionRestorer({
|
|
@@ -1180,7 +970,8 @@ export class NodeConnector {
|
|
|
1180
970
|
authorize: (request) => this.restartAuthorization.authorize(request),
|
|
1181
971
|
inputState,
|
|
1182
972
|
withGrant: (context, work) => this.modelUsage.withGrant(context.usageGrant, work),
|
|
1183
|
-
publishGoal: (session, goal) => this.publishGoalState(session, goal),
|
|
973
|
+
publishGoal: (session, goal) => this.goalCoordinator.publishGoalState(session, goal),
|
|
974
|
+
goalReference: (sessionId) => this.goalCoordinator.reference(sessionId),
|
|
1184
975
|
publishQueue: (session) => this.continuousRunCoordinator.publish(session.id)
|
|
1185
976
|
});
|
|
1186
977
|
this.restartHandoff = new PlannedRestartHandoff({
|
|
@@ -1284,7 +1075,7 @@ export class NodeConnector {
|
|
|
1284
1075
|
runtime: this.runtime,
|
|
1285
1076
|
state: this.runState,
|
|
1286
1077
|
emitRun: (runId, type, payload, status) => this.runEventBridge.emitRun(runId, type, payload, status),
|
|
1287
|
-
publishApproval: (input) => this.
|
|
1078
|
+
publishApproval: (input) => this.runnerInteractionService.publishApproval(input),
|
|
1288
1079
|
disconnect: () => this.controlChannel.close(4010, 'FAKE_RUNNER_DISCONNECT')
|
|
1289
1080
|
});
|
|
1290
1081
|
this.fakeRunner?.bindControl({
|
|
@@ -1437,7 +1228,8 @@ export class NodeConnector {
|
|
|
1437
1228
|
heartbeatMs: HEARTBEAT_MS,
|
|
1438
1229
|
refreshMs: CAPABILITY_REFRESH_MS,
|
|
1439
1230
|
onRegistered: async () => {
|
|
1440
|
-
await this.
|
|
1231
|
+
await this.conversationHosting.initialize();
|
|
1232
|
+
this.capabilities = await this.conversationHosting.project(this.capabilities);
|
|
1441
1233
|
this.computerUseEnablement.initialize();
|
|
1442
1234
|
},
|
|
1443
1235
|
onConnected: () => {
|
|
@@ -1499,7 +1291,11 @@ export class NodeConnector {
|
|
|
1499
1291
|
}));
|
|
1500
1292
|
return { runId: result.run.id };
|
|
1501
1293
|
},
|
|
1502
|
-
|
|
1294
|
+
sessionTitle: (sessionId) => {
|
|
1295
|
+
const session = this.runtime.getAgentSession(sessionId);
|
|
1296
|
+
return session?.customTitle ?? session?.runnerTitle ?? sessionId;
|
|
1297
|
+
},
|
|
1298
|
+
emitNotification: (payload) => this.emitWorkbenchEvent('conversation', payload)
|
|
1503
1299
|
});
|
|
1504
1300
|
this.runnerService = new RunnerService(requireDatabase, () => this.capabilities, this.runners, this.runnerUsageReader);
|
|
1505
1301
|
this.workbenchManifestService = new WorkbenchManifestService({
|
|
@@ -1588,21 +1384,21 @@ export class NodeConnector {
|
|
|
1588
1384
|
this.operationRouter.registerAll(this.promptCompletion.operations());
|
|
1589
1385
|
for (const operation of CONVERSATION_TEMPLATE_OPERATIONS)
|
|
1590
1386
|
this.operationRouter.register(operation, (data) => {
|
|
1591
|
-
const handler = this.
|
|
1387
|
+
const handler = this.conversationHosting.handler(operation);
|
|
1592
1388
|
if (handler === undefined)
|
|
1593
1389
|
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
1594
1390
|
return handler(data);
|
|
1595
1391
|
});
|
|
1596
1392
|
for (const operation of HOSTED_CONVERSATION_OPERATIONS)
|
|
1597
1393
|
this.operationRouter.register(operation, (data) => {
|
|
1598
|
-
const handler = this.
|
|
1394
|
+
const handler = this.conversationHosting.handler(operation);
|
|
1599
1395
|
if (handler === undefined)
|
|
1600
1396
|
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
1601
1397
|
return handler(data);
|
|
1602
1398
|
});
|
|
1603
1399
|
for (const operation of CONVERSATION_HOSTING_MAINTENANCE_OPERATIONS)
|
|
1604
1400
|
this.operationRouter.register(operation, (data) => {
|
|
1605
|
-
const handler = this.
|
|
1401
|
+
const handler = this.conversationHosting.handler(operation);
|
|
1606
1402
|
if (handler === undefined)
|
|
1607
1403
|
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
1608
1404
|
return handler(data);
|
|
@@ -1630,7 +1426,7 @@ export class NodeConnector {
|
|
|
1630
1426
|
session: this.sessionPresentationService.present(session)
|
|
1631
1427
|
}),
|
|
1632
1428
|
changed: () => this.scheduledSessions.changed(),
|
|
1633
|
-
proposed: (sessionId, proposalId) => this.
|
|
1429
|
+
proposed: (sessionId, proposalId) => this.scheduledSessions.proposalCreated(sessionId, proposalId)
|
|
1634
1430
|
});
|
|
1635
1431
|
this.operationRouter.registerAll(this.sessionScheduleService.operations());
|
|
1636
1432
|
this.operationRouter.registerAll(this.sessionMessageService.operations());
|
|
@@ -1645,8 +1441,11 @@ export class NodeConnector {
|
|
|
1645
1441
|
configPath: this.configPath,
|
|
1646
1442
|
platform: () => this.capabilities.platform,
|
|
1647
1443
|
capability: () => this.capabilities.conversationHosting,
|
|
1648
|
-
initialize: () =>
|
|
1649
|
-
|
|
1444
|
+
initialize: async () => {
|
|
1445
|
+
await this.conversationHosting.initialize();
|
|
1446
|
+
this.capabilities = await this.conversationHosting.project(this.capabilities);
|
|
1447
|
+
},
|
|
1448
|
+
prepareForDisable: () => this.conversationHosting.prepareForDisable(),
|
|
1650
1449
|
publishCapabilities: () => this.send('capabilities', this.capabilities)
|
|
1651
1450
|
}).operations());
|
|
1652
1451
|
this.operationRouter.registerAll(this.computerUseEnablement.operations());
|
|
@@ -1670,147 +1469,12 @@ export class NodeConnector {
|
|
|
1670
1469
|
binaryTerminal: true
|
|
1671
1470
|
}));
|
|
1672
1471
|
}
|
|
1673
|
-
async initializeConversationHosting() {
|
|
1674
|
-
const previousRuntime = this.hostedConversationRuntime;
|
|
1675
|
-
const previousService = this.hostedConversationService;
|
|
1676
|
-
const previousMaintenance = this.conversationHostingMaintenanceService;
|
|
1677
|
-
this.hostedConversationRuntime = undefined;
|
|
1678
|
-
this.hostedConversationService = undefined;
|
|
1679
|
-
this.conversationHostingMaintenanceService = undefined;
|
|
1680
|
-
this.hostedConversationOperations = undefined;
|
|
1681
|
-
this.conversationHostingMaintenanceOperations = undefined;
|
|
1682
|
-
this.conversationTemplateOperations = undefined;
|
|
1683
|
-
this.conversationHostingStorageReady = false;
|
|
1684
|
-
this.conversationHostingFailureCode = undefined;
|
|
1685
|
-
await previousMaintenance?.dispose();
|
|
1686
|
-
previousService?.dispose();
|
|
1687
|
-
await previousRuntime?.dispose().catch(() => undefined);
|
|
1688
|
-
const config = this.config;
|
|
1689
|
-
if (config?.conversationHosting?.enabled === true && this.capabilities.platform === 'linux') {
|
|
1690
|
-
if (config.nodeId === undefined) {
|
|
1691
|
-
this.conversationHostingFailureCode = 'NODE_UNAUTHENTICATED';
|
|
1692
|
-
}
|
|
1693
|
-
else {
|
|
1694
|
-
let runtime;
|
|
1695
|
-
let hostedService;
|
|
1696
|
-
let maintenanceService;
|
|
1697
|
-
try {
|
|
1698
|
-
if (this.database === undefined)
|
|
1699
|
-
throw new Error('NODE_DATABASE_UNAVAILABLE');
|
|
1700
|
-
const rootDirectory = hostedConversationStorePath(config, this.configPath);
|
|
1701
|
-
if (rootDirectory === undefined)
|
|
1702
|
-
throw new Error('CONVERSATION_HOSTING_STORAGE_UNAVAILABLE');
|
|
1703
|
-
const store = new HostedConversationStore({
|
|
1704
|
-
dataDirectory: nodeDataDirectory(config, this.configPath),
|
|
1705
|
-
rootDirectory,
|
|
1706
|
-
nodeId: config.nodeId,
|
|
1707
|
-
database: this.database
|
|
1708
|
-
});
|
|
1709
|
-
await store.initialize();
|
|
1710
|
-
const templateService = new ConversationTemplateNodeService({
|
|
1711
|
-
database: this.database,
|
|
1712
|
-
store,
|
|
1713
|
-
nodeId: () => this.config?.nodeId ?? 'unregistered'
|
|
1714
|
-
});
|
|
1715
|
-
await templateService.recoverInterruptedPublications();
|
|
1716
|
-
await store.rebuildConversationIndex();
|
|
1717
|
-
runtime = new MarAgentHostedConversationRuntime({
|
|
1718
|
-
webSearch: (input, signal) => this.webToolService.webSearch(input, signal),
|
|
1719
|
-
webFetch: (input, signal) => this.webToolService.webFetch(input, signal, false),
|
|
1720
|
-
modelFactory: this.marAgentRuntimeModels,
|
|
1721
|
-
registerNativeImages: (runId, roots, images) => this.runAttachmentService.registerNativeImages(runId, roots, images, {
|
|
1722
|
-
reuseCachedRunImages: false
|
|
1723
|
-
})
|
|
1724
|
-
});
|
|
1725
|
-
hostedService = new HostedConversationNodeService({
|
|
1726
|
-
modelUsage: (grant, usage) => {
|
|
1727
|
-
const runId = usage.executionId ?? usage.requestId;
|
|
1728
|
-
this.modelUsage.bind(runId, grant, usage.sessionId, 'mar-agent');
|
|
1729
|
-
this.modelUsage.record(runId, normalizeMarAgentUsage(usage));
|
|
1730
|
-
this.modelUsage.release(runId);
|
|
1731
|
-
},
|
|
1732
|
-
database: this.database,
|
|
1733
|
-
store,
|
|
1734
|
-
runtime,
|
|
1735
|
-
nodeId: () => this.config?.nodeId ?? 'unregistered',
|
|
1736
|
-
nodeAppVersion: () => this.capabilities.nodeAppVersion ?? 'unknown',
|
|
1737
|
-
nodeGeneration: () => this.nodeGeneration,
|
|
1738
|
-
sessionWatchSlots: this.sessionWatchSlots,
|
|
1739
|
-
emitWatchEvent: (payload) => this.send('watch.event', payload),
|
|
1740
|
-
nextWorkbenchEvent: (payload) => this.workbenchEventService.next('hosted-conversation', payload),
|
|
1741
|
-
emitWorkbenchEvent: (event) => this.workbenchEventService.publish(event),
|
|
1742
|
-
readImage: (runId, imageRef) => this.runAttachmentService.readNative(runId, imageRef),
|
|
1743
|
-
processExecutorFactory: (input) => new BubblewrapSandboxExecutor({
|
|
1744
|
-
profile: hostedConversationSandboxProfile(input)
|
|
1745
|
-
}),
|
|
1746
|
-
assertCanCreate: () => {
|
|
1747
|
-
if (maintenanceService === undefined)
|
|
1748
|
-
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
1749
|
-
return maintenanceService.assertCanCreate();
|
|
1750
|
-
}
|
|
1751
|
-
});
|
|
1752
|
-
maintenanceService = new ConversationHostingMaintenanceService({
|
|
1753
|
-
store,
|
|
1754
|
-
inactiveRetentionDays: config.conversationHosting.inactiveRetentionDays ?? 60,
|
|
1755
|
-
isConversationInUse: (conversationId) => hostedService?.isConversationInUse(conversationId) ?? true,
|
|
1756
|
-
beginConversationCleanup: (conversationId) => hostedService?.beginConversationCleanup(conversationId),
|
|
1757
|
-
releaseConversation: (conversation) => {
|
|
1758
|
-
if (hostedService === undefined)
|
|
1759
|
-
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
1760
|
-
return hostedService.releaseConversationForCleanup(conversation);
|
|
1761
|
-
},
|
|
1762
|
-
onAutomaticCleanupError: (error) => nodeLog('conversation.hosting.cleanup.failed', {
|
|
1763
|
-
nodeId: config.nodeId,
|
|
1764
|
-
code: safeErrorCode(error, 'CONVERSATION_HOSTING_CLEANUP_FAILED')
|
|
1765
|
-
})
|
|
1766
|
-
});
|
|
1767
|
-
this.conversationTemplateOperations = templateService.operations();
|
|
1768
|
-
this.hostedConversationOperations = hostedService.operations();
|
|
1769
|
-
this.conversationHostingMaintenanceOperations = maintenanceService.operations();
|
|
1770
|
-
this.hostedConversationRuntime = runtime;
|
|
1771
|
-
this.hostedConversationService = hostedService;
|
|
1772
|
-
this.conversationHostingMaintenanceService = maintenanceService;
|
|
1773
|
-
await maintenanceService.start();
|
|
1774
|
-
this.conversationHostingStorageReady = true;
|
|
1775
|
-
}
|
|
1776
|
-
catch (error) {
|
|
1777
|
-
await maintenanceService?.dispose();
|
|
1778
|
-
hostedService?.dispose();
|
|
1779
|
-
await runtime?.dispose().catch(() => undefined);
|
|
1780
|
-
this.conversationHostingFailureCode = safeErrorCode(error, 'CONVERSATION_HOSTING_RECOVERY_FAILED');
|
|
1781
|
-
nodeLog('conversation.hosting.initialization.failed', {
|
|
1782
|
-
nodeId: config.nodeId,
|
|
1783
|
-
code: this.conversationHostingFailureCode
|
|
1784
|
-
});
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
this.capabilities = await this.projectConversationHostingCapability(this.capabilities);
|
|
1789
|
-
}
|
|
1790
1472
|
projectComputerUseCapability(capabilities) {
|
|
1791
1473
|
return {
|
|
1792
1474
|
...capabilities,
|
|
1793
1475
|
computerUse: this.computerUseEnablement.capability()
|
|
1794
1476
|
};
|
|
1795
1477
|
}
|
|
1796
|
-
async projectConversationHostingCapability(capabilities) {
|
|
1797
|
-
let storageReady = this.conversationHostingStorageReady;
|
|
1798
|
-
let storageFailureCode = this.conversationHostingFailureCode;
|
|
1799
|
-
let capacity = 'AVAILABLE';
|
|
1800
|
-
if (storageReady && this.conversationHostingMaintenanceService !== undefined) {
|
|
1801
|
-
try {
|
|
1802
|
-
capacity = (await this.conversationHostingMaintenanceService.storageCapacity()).capacity;
|
|
1803
|
-
}
|
|
1804
|
-
catch (error) {
|
|
1805
|
-
storageReady = false;
|
|
1806
|
-
storageFailureCode = safeErrorCode(error, 'CONVERSATION_HOSTING_STORAGE_UNAVAILABLE');
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
return {
|
|
1810
|
-
...capabilities,
|
|
1811
|
-
conversationHosting: await conversationHostingCapability(capabilities, this.config, storageReady, storageFailureCode, this.conversationHostingProbe, capacity)
|
|
1812
|
-
};
|
|
1813
|
-
}
|
|
1814
1478
|
async start() {
|
|
1815
1479
|
if (this.stopping !== undefined)
|
|
1816
1480
|
await this.stopping;
|
|
@@ -1837,7 +1501,8 @@ export class NodeConnector {
|
|
|
1837
1501
|
await this.restartHandoff.load();
|
|
1838
1502
|
this.scheduledSessions.recoverInterrupted();
|
|
1839
1503
|
this.scheduledSessions.start();
|
|
1840
|
-
await this.
|
|
1504
|
+
await this.conversationHosting.initialize();
|
|
1505
|
+
this.capabilities = await this.conversationHosting.project(this.capabilities);
|
|
1841
1506
|
this.computerUseEnablement.initialize();
|
|
1842
1507
|
this.directTransferService ??= await this.createDirectTransferService();
|
|
1843
1508
|
this.connectionLifecycle.start();
|
|
@@ -1914,287 +1579,12 @@ export class NodeConnector {
|
|
|
1914
1579
|
};
|
|
1915
1580
|
return this.directTransferService?.handleTerminal(command) ?? false;
|
|
1916
1581
|
}
|
|
1917
|
-
clearGoalRetry(sessionId) {
|
|
1918
|
-
const retry = this.goalRetryTimers.get(sessionId);
|
|
1919
|
-
if (retry !== undefined)
|
|
1920
|
-
clearTimeout(retry.timer);
|
|
1921
|
-
this.goalRetryTimers.delete(sessionId);
|
|
1922
|
-
this.goalContinuationPending.delete(sessionId);
|
|
1923
|
-
}
|
|
1924
|
-
invalidateGoalContinuation(sessionId) {
|
|
1925
|
-
this.goalContinuationEpochs.set(sessionId, (this.goalContinuationEpochs.get(sessionId) ?? 0) + 1);
|
|
1926
|
-
this.clearGoalRetry(sessionId);
|
|
1927
|
-
}
|
|
1928
|
-
retireGoalContinuation(sessionId) {
|
|
1929
|
-
this.invalidateGoalContinuation(sessionId);
|
|
1930
|
-
this.goalContinuationEpochs.delete(sessionId);
|
|
1931
|
-
}
|
|
1932
|
-
goalGenerationCurrent(sessionId, goal) {
|
|
1933
|
-
const current = this.goalRuntime.get(sessionId);
|
|
1934
|
-
return ((this.goalContinuationEpochs.get(sessionId) ?? 0) === goal.epoch &&
|
|
1935
|
-
current?.status === 'active' &&
|
|
1936
|
-
current.id === goal.id &&
|
|
1937
|
-
current.revision === goal.revision);
|
|
1938
|
-
}
|
|
1939
|
-
async maintainGoalContinuation(input, started) {
|
|
1940
|
-
if (this.stopped || input.goal === undefined)
|
|
1941
|
-
return;
|
|
1942
|
-
if (!this.goalGenerationCurrent(input.sessionId, input.goal))
|
|
1943
|
-
return;
|
|
1944
|
-
const updated = started ? this.goalRuntime.recordTurn(input.sessionId) : undefined;
|
|
1945
|
-
const session = this.runtime.getAgentSession(input.sessionId);
|
|
1946
|
-
const run = this.runtime.getRun(input.runId);
|
|
1947
|
-
await this.enqueueGoalContinuation({
|
|
1948
|
-
sessionId: input.sessionId,
|
|
1949
|
-
goal: { id: input.goal.id, revision: input.goal.revision },
|
|
1950
|
-
continuationEpoch: input.goal.epoch,
|
|
1951
|
-
...(run?.initiatedByUserId === undefined ? {} : { initiatedByUserId: run.initiatedByUserId }),
|
|
1952
|
-
...(input.usageGrant === undefined ? {} : { usageGrant: input.usageGrant }),
|
|
1953
|
-
secretEnvironment: input.secretEnvironment,
|
|
1954
|
-
...(input.personalInstructions === undefined
|
|
1955
|
-
? {}
|
|
1956
|
-
: { personalInstructions: input.personalInstructions }),
|
|
1957
|
-
...(input.marAgentModel === undefined ? {} : { marAgentModel: input.marAgentModel }),
|
|
1958
|
-
...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
|
|
1959
|
-
...(input.marAgentCompact === undefined ? {} : { marAgentCompact: input.marAgentCompact }),
|
|
1960
|
-
...(input.marAgentRuntimeScopeId === undefined
|
|
1961
|
-
? {}
|
|
1962
|
-
: { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
|
|
1963
|
-
});
|
|
1964
|
-
if (updated !== undefined && session !== undefined)
|
|
1965
|
-
this.publishGoalState(session, updated);
|
|
1966
|
-
}
|
|
1967
|
-
handleNativeGoalUpdate(runId, nativeGoal) {
|
|
1968
|
-
const pending = this.continuousRunCoordinator.pending(runId);
|
|
1969
|
-
if (pending?.goal === undefined || !this.goalGenerationCurrent(pending.sessionId, pending.goal))
|
|
1970
|
-
return;
|
|
1971
|
-
const goal = this.goalRuntime.get(pending.sessionId);
|
|
1972
|
-
if (nativeGoal.status === 'active') {
|
|
1973
|
-
if ((nativeGoal.createdAt + 1) * 1_000 < goal.revisionStartedAt)
|
|
1974
|
-
return;
|
|
1975
|
-
const previous = this.nativeGoalBindings.get(goal.id);
|
|
1976
|
-
if (previous !== undefined &&
|
|
1977
|
-
previous.revision !== goal.revision &&
|
|
1978
|
-
previous.threadId === nativeGoal.threadId &&
|
|
1979
|
-
previous.createdAt === nativeGoal.createdAt &&
|
|
1980
|
-
previous.objective === nativeGoal.objective)
|
|
1981
|
-
return;
|
|
1982
|
-
this.nativeGoalBindings.set(goal.id, {
|
|
1983
|
-
revision: goal.revision,
|
|
1984
|
-
threadId: nativeGoal.threadId,
|
|
1985
|
-
createdAt: nativeGoal.createdAt,
|
|
1986
|
-
objective: nativeGoal.objective
|
|
1987
|
-
});
|
|
1988
|
-
return;
|
|
1989
|
-
}
|
|
1990
|
-
const bound = this.nativeGoalBindings.get(goal.id);
|
|
1991
|
-
if (bound?.revision !== goal.revision ||
|
|
1992
|
-
bound.threadId !== nativeGoal.threadId ||
|
|
1993
|
-
bound.createdAt !== nativeGoal.createdAt ||
|
|
1994
|
-
bound.objective !== nativeGoal.objective)
|
|
1995
|
-
return;
|
|
1996
|
-
this.nativeGoalBindings.delete(goal.id);
|
|
1997
|
-
const completed = this.goalRuntime.update(pending.sessionId, goal.id, goal.revision, 'completed');
|
|
1998
|
-
this.retireGoalContinuation(pending.sessionId);
|
|
1999
|
-
this.continuousRunCoordinator.removeGoalQueue(pending.sessionId);
|
|
2000
|
-
const session = this.runtime.getAgentSession(pending.sessionId);
|
|
2001
|
-
if (session !== undefined)
|
|
2002
|
-
this.publishGoalState(session, completed);
|
|
2003
|
-
}
|
|
2004
|
-
beginGoalSubmission(sessionId, continuationEpoch = this.goalContinuationEpochs.get(sessionId) ?? 0) {
|
|
2005
|
-
const attempt = { sessionId, epoch: continuationEpoch };
|
|
2006
|
-
const attempts = this.goalSubmissionAttempts.get(sessionId) ?? new Set();
|
|
2007
|
-
attempts.add(attempt);
|
|
2008
|
-
this.goalSubmissionAttempts.set(sessionId, attempts);
|
|
2009
|
-
return attempt;
|
|
2010
|
-
}
|
|
2011
|
-
finishGoalSubmissionAttempt(attempt) {
|
|
2012
|
-
const attempts = this.goalSubmissionAttempts.get(attempt.sessionId);
|
|
2013
|
-
attempts?.delete(attempt);
|
|
2014
|
-
if (attempts?.size === 0)
|
|
2015
|
-
this.goalSubmissionAttempts.delete(attempt.sessionId);
|
|
2016
|
-
}
|
|
2017
|
-
finishGoalContinuationAttempt(attempt) {
|
|
2018
|
-
if (this.goalContinuationPending.get(attempt.sessionId) === attempt)
|
|
2019
|
-
this.goalContinuationPending.delete(attempt.sessionId);
|
|
2020
|
-
this.finishGoalSubmissionAttempt(attempt);
|
|
2021
|
-
}
|
|
2022
|
-
removeStaleGoalSubmission(sessionId, result) {
|
|
2023
|
-
if (!isPlainRecord(result))
|
|
2024
|
-
return;
|
|
2025
|
-
if (typeof result.queueItemId === 'string') {
|
|
2026
|
-
const item = this.runtime.getQueueItem(result.queueItemId);
|
|
2027
|
-
if (item?.sessionId === sessionId)
|
|
2028
|
-
this.continuousRunCoordinator.deleteQueueItem(item.id);
|
|
2029
|
-
return;
|
|
2030
|
-
}
|
|
2031
|
-
if (!isPlainRecord(result.run) || typeof result.run.id !== 'string')
|
|
2032
|
-
return;
|
|
2033
|
-
const run = this.runtime.getRun(result.run.id);
|
|
2034
|
-
if (run?.sessionId === sessionId && this.runtime.currentExecutionId(run.id) === undefined)
|
|
2035
|
-
this.runEventBridge.emitRun(run.id, 'run.completed', { code: 'GOAL_SUBMISSION_STALE' }, 'INTERRUPTED');
|
|
2036
|
-
}
|
|
2037
|
-
async enqueueGoalContinuation(input) {
|
|
2038
|
-
const attempt = this.beginGoalSubmission(input.sessionId, input.continuationEpoch);
|
|
2039
|
-
const generation = { ...input.goal, epoch: attempt.epoch };
|
|
2040
|
-
if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
|
|
2041
|
-
this.finishGoalSubmissionAttempt(attempt);
|
|
2042
|
-
return;
|
|
2043
|
-
}
|
|
2044
|
-
this.goalContinuationPending.set(input.sessionId, attempt);
|
|
2045
|
-
try {
|
|
2046
|
-
const result = await this.sessionMessageService.submitGoal({
|
|
2047
|
-
sessionId: attempt.sessionId,
|
|
2048
|
-
kind: 'continue',
|
|
2049
|
-
content: this.goalRuntime.prompt(attempt.sessionId, 'continue'),
|
|
2050
|
-
deliveryIntent: input.deliveryIntent ?? 'QUEUE',
|
|
2051
|
-
goal: generation,
|
|
2052
|
-
...(input.initiatedByUserId === undefined
|
|
2053
|
-
? {}
|
|
2054
|
-
: { initiatedByUserId: input.initiatedByUserId }),
|
|
2055
|
-
...(input.usageGrant === undefined ? {} : { usageGrant: input.usageGrant }),
|
|
2056
|
-
secretEnvironment: input.secretEnvironment,
|
|
2057
|
-
...(input.personalInstructions === undefined
|
|
2058
|
-
? {}
|
|
2059
|
-
: { personalInstructions: input.personalInstructions }),
|
|
2060
|
-
...(input.marAgentModel === undefined ? {} : { marAgentModel: input.marAgentModel }),
|
|
2061
|
-
...(input.marAgentModels === undefined ? {} : { marAgentModels: input.marAgentModels }),
|
|
2062
|
-
...(input.marAgentCompact === undefined ? {} : { marAgentCompact: input.marAgentCompact }),
|
|
2063
|
-
...(input.marAgentRuntimeScopeId === undefined
|
|
2064
|
-
? {}
|
|
2065
|
-
: { marAgentRuntimeScopeId: input.marAgentRuntimeScopeId })
|
|
2066
|
-
});
|
|
2067
|
-
if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
|
|
2068
|
-
this.removeStaleGoalSubmission(attempt.sessionId, result);
|
|
2069
|
-
this.finishGoalContinuationAttempt(attempt);
|
|
2070
|
-
return;
|
|
2071
|
-
}
|
|
2072
|
-
const retry = this.goalRetryTimers.get(attempt.sessionId);
|
|
2073
|
-
if (retry !== undefined)
|
|
2074
|
-
clearTimeout(retry.timer);
|
|
2075
|
-
this.goalRetryTimers.delete(attempt.sessionId);
|
|
2076
|
-
this.finishGoalContinuationAttempt(attempt);
|
|
2077
|
-
}
|
|
2078
|
-
catch (error) {
|
|
2079
|
-
const failedAttempts = (input.failedAttempts ?? 0) + 1;
|
|
2080
|
-
nodeLog('goal.continuation.enqueue.failed', {
|
|
2081
|
-
sessionId: attempt.sessionId,
|
|
2082
|
-
goalId: input.goal.id,
|
|
2083
|
-
revision: input.goal.revision,
|
|
2084
|
-
attempt: failedAttempts,
|
|
2085
|
-
code: safeErrorCode(error, 'GOAL_CONTINUATION_ENQUEUE_FAILED')
|
|
2086
|
-
});
|
|
2087
|
-
if (!this.goalGenerationCurrent(attempt.sessionId, generation)) {
|
|
2088
|
-
this.finishGoalContinuationAttempt(attempt);
|
|
2089
|
-
return;
|
|
2090
|
-
}
|
|
2091
|
-
const session = this.runtime.getAgentSession(attempt.sessionId);
|
|
2092
|
-
if (failedAttempts > 3 || session === undefined) {
|
|
2093
|
-
this.clearGoalRetry(attempt.sessionId);
|
|
2094
|
-
if (failedAttempts > 3) {
|
|
2095
|
-
const blocked = this.goalRuntime.blockAfterRuntimeFailure(attempt.sessionId, input.goal.id, input.goal.revision);
|
|
2096
|
-
this.nativeGoalBindings.delete(input.goal.id);
|
|
2097
|
-
this.continuousRunCoordinator.removeGoalQueue(attempt.sessionId);
|
|
2098
|
-
this.goalContinuationEpochs.delete(attempt.sessionId);
|
|
2099
|
-
this.finishIdleGoalRun(attempt.sessionId, 'FAILED');
|
|
2100
|
-
if (session !== undefined)
|
|
2101
|
-
this.publishGoalState(session, blocked);
|
|
2102
|
-
}
|
|
2103
|
-
return;
|
|
2104
|
-
}
|
|
2105
|
-
this.finishGoalContinuationAttempt(attempt);
|
|
2106
|
-
const previousRetry = this.goalRetryTimers.get(attempt.sessionId);
|
|
2107
|
-
if (previousRetry !== undefined)
|
|
2108
|
-
clearTimeout(previousRetry.timer);
|
|
2109
|
-
const retry = {
|
|
2110
|
-
sessionId: attempt.sessionId,
|
|
2111
|
-
timer: setTimeout(() => {
|
|
2112
|
-
if (!this.goalGenerationCurrent(retry.sessionId, generation))
|
|
2113
|
-
return;
|
|
2114
|
-
void this.enqueueGoalContinuation({
|
|
2115
|
-
...input,
|
|
2116
|
-
sessionId: retry.sessionId,
|
|
2117
|
-
failedAttempts,
|
|
2118
|
-
continuationEpoch: attempt.epoch
|
|
2119
|
-
});
|
|
2120
|
-
}, 15_000)
|
|
2121
|
-
};
|
|
2122
|
-
this.goalRetryTimers.set(attempt.sessionId, retry);
|
|
2123
|
-
retry.timer.unref();
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
handleGoalPreparationFailed(session, goal, activated) {
|
|
2127
|
-
if (!activated || !this.goalGenerationCurrent(session.id, goal))
|
|
2128
|
-
return;
|
|
2129
|
-
this.goalRuntime.clear(session.id);
|
|
2130
|
-
this.finishIdleGoalRun(session.id, 'FAILED');
|
|
2131
|
-
this.commandStates.set(session.id, this.pendingGoalCommandState());
|
|
2132
|
-
this.emitWorkbenchEvent('session', {
|
|
2133
|
-
session: this.sessionPresentationService.present(session)
|
|
2134
|
-
});
|
|
2135
|
-
}
|
|
2136
|
-
finishIdleGoalRun(sessionId, status) {
|
|
2137
|
-
const run = this.runtime.activeSessionRun(sessionId);
|
|
2138
|
-
if (run === undefined || this.runtime.currentExecutionId(run.id) !== undefined)
|
|
2139
|
-
return;
|
|
2140
|
-
this.runEventBridge.emitRun(run.id, status === 'SUCCEEDED' ? 'run.completed' : 'run.failed', status === 'SUCCEEDED' ? {} : { code: 'GOAL_CONTINUATION_FAILED' }, status);
|
|
2141
|
-
}
|
|
2142
|
-
clearGoalState(sessionId, status) {
|
|
2143
|
-
const goalExit = this.goalRuntime.exitSummary(sessionId, status);
|
|
2144
|
-
const goal = this.goalRuntime.get(sessionId);
|
|
2145
|
-
if (goal !== undefined)
|
|
2146
|
-
this.nativeGoalBindings.delete(goal.id);
|
|
2147
|
-
this.retireGoalContinuation(sessionId);
|
|
2148
|
-
const removed = this.goalRuntime.clear(sessionId);
|
|
2149
|
-
this.continuousRunCoordinator.removeGoalQueue(sessionId);
|
|
2150
|
-
const commandState = this.commandStates.get(sessionId, 'goal');
|
|
2151
|
-
if (commandState !== undefined)
|
|
2152
|
-
this.commandStates.clear(sessionId, 'goal');
|
|
2153
|
-
if (!removed && commandState === undefined)
|
|
2154
|
-
return goalExit;
|
|
2155
|
-
const session = this.runtime.getAgentSession(sessionId);
|
|
2156
|
-
if (session !== undefined)
|
|
2157
|
-
this.emitWorkbenchEvent('session', {
|
|
2158
|
-
session: this.sessionPresentationService.present(session)
|
|
2159
|
-
});
|
|
2160
|
-
return goalExit;
|
|
2161
|
-
}
|
|
2162
|
-
prepareGoalSession(session) {
|
|
2163
|
-
const access = goalUnrestrictedAccess(session.runner, session.access);
|
|
2164
|
-
const requiresUpdate = session.access !== access;
|
|
2165
|
-
const changed = session.runner !== 'mar-agent' && requiresUpdate;
|
|
2166
|
-
const prepared = requiresUpdate
|
|
2167
|
-
? this.runtime.updateAgentSessionAccess(session.id, access)
|
|
2168
|
-
: session;
|
|
2169
|
-
if (changed &&
|
|
2170
|
-
prepared.externalSessionId !== null &&
|
|
2171
|
-
prepared.model !== null &&
|
|
2172
|
-
prepared.effort !== null)
|
|
2173
|
-
this.database?.saveNativeSessionMetadata({
|
|
2174
|
-
runner: prepared.runner,
|
|
2175
|
-
externalSessionId: prepared.externalSessionId,
|
|
2176
|
-
workspaceId: prepared.workspaceId,
|
|
2177
|
-
model: prepared.model,
|
|
2178
|
-
effort: prepared.effort,
|
|
2179
|
-
access
|
|
2180
|
-
});
|
|
2181
|
-
return { session: prepared, changed };
|
|
2182
|
-
}
|
|
2183
|
-
pendingGoalCommandState() {
|
|
2184
|
-
return {
|
|
2185
|
-
commandId: 'goal',
|
|
2186
|
-
active: true,
|
|
2187
|
-
label: 'Goal',
|
|
2188
|
-
detail: null,
|
|
2189
|
-
closable: true,
|
|
2190
|
-
closeInput: '/goal --clear'
|
|
2191
|
-
};
|
|
2192
|
-
}
|
|
2193
1582
|
stop(options = {}) {
|
|
2194
1583
|
if (this.stopping !== undefined)
|
|
2195
1584
|
return this.stopping;
|
|
2196
1585
|
this.stopped = true;
|
|
2197
1586
|
this.continuousRunCoordinator.suspend();
|
|
1587
|
+
this.goalCoordinator.freeze();
|
|
2198
1588
|
this.scheduledSessions.stop();
|
|
2199
1589
|
this.stopping = (async () => {
|
|
2200
1590
|
let snapshot;
|
|
@@ -2219,43 +1609,18 @@ export class NodeConnector {
|
|
|
2219
1609
|
this.recentRunProjectionService.dispose();
|
|
2220
1610
|
this.upgradeCoordinator.stop();
|
|
2221
1611
|
this.connectionLifecycle.stop();
|
|
2222
|
-
for (const approval of this.claudeClient.execution.approvals.values()) {
|
|
2223
|
-
clearTimeout(approval.timer);
|
|
2224
|
-
approval.resolve('REJECTED');
|
|
2225
|
-
}
|
|
2226
|
-
this.claudeClient.execution.approvals.clear();
|
|
2227
|
-
for (const pending of this.claudeClient.execution.userInputs.values()) {
|
|
2228
|
-
clearTimeout(pending.timer);
|
|
2229
|
-
pending.reject('NODE_STOPPED');
|
|
2230
|
-
}
|
|
2231
|
-
this.claudeClient.execution.userInputs.clear();
|
|
2232
1612
|
this.nativeSessionWatchService.clear();
|
|
2233
1613
|
this.promptCompletion.dispose();
|
|
2234
1614
|
this.sessionIdentityService.dispose();
|
|
2235
1615
|
this.sessionLifecycleService.clear();
|
|
2236
1616
|
this.continuousRunCoordinator.clear();
|
|
2237
1617
|
this.gitOperationService.clear();
|
|
2238
|
-
|
|
2239
|
-
clearTimeout(retry.timer);
|
|
2240
|
-
this.goalRetryTimers.clear();
|
|
2241
|
-
this.goalContinuationPending.clear();
|
|
2242
|
-
this.goalSubmissionAttempts.clear();
|
|
2243
|
-
this.goalContinuationEpochs.clear();
|
|
1618
|
+
this.goalCoordinator.dispose();
|
|
2244
1619
|
this.runEventService.clear();
|
|
2245
1620
|
this.sessionContextService.dispose();
|
|
2246
|
-
for (const run of this.claudeClient.execution.runs.values())
|
|
2247
|
-
run.handle.cancel();
|
|
2248
|
-
this.claudeClient.execution.runs.clear();
|
|
2249
1621
|
this.runAttachmentService.dispose();
|
|
2250
1622
|
const marAgentDrain = this.marAgentRunController.dispose();
|
|
2251
|
-
const
|
|
2252
|
-
this.conversationHostingMaintenanceService = undefined;
|
|
2253
|
-
this.hostedConversationService?.dispose();
|
|
2254
|
-
this.hostedConversationService = undefined;
|
|
2255
|
-
const hostedConversationRuntime = this.hostedConversationRuntime;
|
|
2256
|
-
this.hostedConversationRuntime = undefined;
|
|
2257
|
-
this.hostedConversationOperations = undefined;
|
|
2258
|
-
this.conversationHostingMaintenanceOperations = undefined;
|
|
1623
|
+
const conversationHostingDrain = this.conversationHosting.dispose();
|
|
2259
1624
|
const uploadDrain = this.workspaceUploadService.shutdown();
|
|
2260
1625
|
this.workspaceFileService.dispose();
|
|
2261
1626
|
this.workspaceWatchService.clearAll();
|
|
@@ -2271,7 +1636,7 @@ export class NodeConnector {
|
|
|
2271
1636
|
this.terminalChannel.stop();
|
|
2272
1637
|
this.runtimeCredentials.close();
|
|
2273
1638
|
this.chatgptLogins.close();
|
|
2274
|
-
this.scheduledSessions.stop();
|
|
1639
|
+
const scheduledDrain = this.scheduledSessions.stop();
|
|
2275
1640
|
this.scheduledRunAuthorization.close();
|
|
2276
1641
|
this.restartAuthorization.close();
|
|
2277
1642
|
this.mailNotificationControl.close();
|
|
@@ -2281,8 +1646,8 @@ export class NodeConnector {
|
|
|
2281
1646
|
this.directTransferService = undefined;
|
|
2282
1647
|
this.controlChannel.stop();
|
|
2283
1648
|
const database = this.database;
|
|
2284
|
-
this.database = undefined;
|
|
2285
1649
|
const drains = Promise.allSettled([
|
|
1650
|
+
scheduledDrain,
|
|
2286
1651
|
marAgentDrain,
|
|
2287
1652
|
runnerDrain,
|
|
2288
1653
|
terminalDrain,
|
|
@@ -2295,11 +1660,11 @@ export class NodeConnector {
|
|
|
2295
1660
|
return (async () => {
|
|
2296
1661
|
let failed;
|
|
2297
1662
|
try {
|
|
2298
|
-
await
|
|
1663
|
+
await conversationHostingDrain;
|
|
2299
1664
|
}
|
|
2300
1665
|
finally {
|
|
2301
|
-
await hostedConversationRuntime?.dispose().catch(() => undefined);
|
|
2302
1666
|
const results = await drains;
|
|
1667
|
+
this.database = undefined;
|
|
2303
1668
|
await this.gitExecutionService?.close();
|
|
2304
1669
|
this.usageOutbox?.disconnected();
|
|
2305
1670
|
this.usageOutbox = undefined;
|
|
@@ -2313,97 +1678,9 @@ export class NodeConnector {
|
|
|
2313
1678
|
}
|
|
2314
1679
|
/** @internal Thin compatibility delegates; all behavior remains in the extracted services. */
|
|
2315
1680
|
handleMessage = (raw) => this.controlMessages.handle(raw);
|
|
2316
|
-
publishApprovalForRun = (input) => {
|
|
2317
|
-
const run = this.runtime.getRun(this.runtime.canonicalRunId(input.runId));
|
|
2318
|
-
if (run?.source !== 'SCHEDULED') {
|
|
2319
|
-
this.runEventBridge.publishApproval(input);
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
|
-
this.runEventBridge.emitRun(input.runId, 'approval.request', input);
|
|
2323
|
-
queueMicrotask(() => {
|
|
2324
|
-
try {
|
|
2325
|
-
this.runtime.decideApproval(input.approvalId, 'APPROVED');
|
|
2326
|
-
this.runEventBridge.emitRun(input.runId, 'approval.decided', {
|
|
2327
|
-
approvalId: input.approvalId,
|
|
2328
|
-
decision: 'APPROVED',
|
|
2329
|
-
source: 'SCHEDULED'
|
|
2330
|
-
});
|
|
2331
|
-
this.runners.decideApproval({
|
|
2332
|
-
runId: input.runId,
|
|
2333
|
-
approvalId: input.approvalId,
|
|
2334
|
-
decision: 'APPROVED'
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
|
-
catch {
|
|
2338
|
-
// A cancelled execution cannot receive an approval after its pending request expires.
|
|
2339
|
-
}
|
|
2340
|
-
});
|
|
2341
|
-
};
|
|
2342
|
-
emitScheduledNotification = (task, occurrenceId, code, status) => this.publishScheduledTaskNotification(task, occurrenceId, {
|
|
2343
|
-
kind: status === 'FAILED' ? 'SCHEDULE_FAILED' : 'SCHEDULE_SKIPPED',
|
|
2344
|
-
code
|
|
2345
|
-
});
|
|
2346
|
-
publishScheduledTaskNotification = (task, occurrenceId, event) => {
|
|
2347
|
-
const occurredAt = Date.now();
|
|
2348
|
-
const recipient = (this.database === undefined
|
|
2349
|
-
? undefined
|
|
2350
|
-
: new ScheduledSessionStore(this.database.raw).getById(task.id))?.ownerUserId ?? task.ownerUserId;
|
|
2351
|
-
const session = this.runtime.getAgentSession(task.sessionId);
|
|
2352
|
-
this.emitWorkbenchEvent('conversation', {
|
|
2353
|
-
event: {
|
|
2354
|
-
runId: event.kind === 'SCHEDULE_TERMINAL' ? event.runId : occurrenceId,
|
|
2355
|
-
sequence: 1,
|
|
2356
|
-
eventType: 'notification',
|
|
2357
|
-
payload: {
|
|
2358
|
-
targetType: 'WORKSPACE_SESSION',
|
|
2359
|
-
userId: recipient,
|
|
2360
|
-
workspaceId: task.workspaceId,
|
|
2361
|
-
sessionId: task.sessionId,
|
|
2362
|
-
sessionTitle: session?.customTitle ?? session?.runnerTitle ?? task.sessionId,
|
|
2363
|
-
runId: event.kind === 'SCHEDULE_TERMINAL' ? event.runId : occurrenceId,
|
|
2364
|
-
notificationId: event.kind === 'SCHEDULE_TERMINAL'
|
|
2365
|
-
? `schedule-terminal:${task.id}:${occurrenceId}`
|
|
2366
|
-
: `schedule:${task.id}:${occurrenceId}`,
|
|
2367
|
-
...event,
|
|
2368
|
-
occurredAt
|
|
2369
|
-
},
|
|
2370
|
-
createdAt: occurredAt
|
|
2371
|
-
}
|
|
2372
|
-
});
|
|
2373
|
-
};
|
|
2374
|
-
emitScheduledProposalNotification = (sessionId, proposalId) => {
|
|
2375
|
-
if (this.database === undefined)
|
|
2376
|
-
return;
|
|
2377
|
-
const task = new ScheduledSessionStore(this.database.raw).get(sessionId);
|
|
2378
|
-
if (task === undefined)
|
|
2379
|
-
return;
|
|
2380
|
-
const occurredAt = Date.now();
|
|
2381
|
-
const session = this.runtime.getAgentSession(sessionId);
|
|
2382
|
-
this.emitWorkbenchEvent('conversation', {
|
|
2383
|
-
event: {
|
|
2384
|
-
runId: proposalId,
|
|
2385
|
-
sequence: 1,
|
|
2386
|
-
eventType: 'notification',
|
|
2387
|
-
payload: {
|
|
2388
|
-
targetType: 'WORKSPACE_SESSION',
|
|
2389
|
-
userId: task.ownerUserId,
|
|
2390
|
-
workspaceId: task.workspaceId,
|
|
2391
|
-
sessionId,
|
|
2392
|
-
sessionTitle: session?.customTitle ?? session?.runnerTitle ?? sessionId,
|
|
2393
|
-
runId: proposalId,
|
|
2394
|
-
notificationId: `schedule-proposal:${task.id}:${proposalId}`,
|
|
2395
|
-
kind: 'SCHEDULE_PROPOSAL',
|
|
2396
|
-
occurredAt
|
|
2397
|
-
},
|
|
2398
|
-
createdAt: occurredAt
|
|
2399
|
-
}
|
|
2400
|
-
});
|
|
2401
|
-
};
|
|
2402
1681
|
emitRunEvent = (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status);
|
|
2403
1682
|
emitConversationItem = (runId, item) => {
|
|
2404
|
-
|
|
2405
|
-
isGoalProgressItem(item))
|
|
2406
|
-
this.goalExecutionProgress.add(runId);
|
|
1683
|
+
this.goalCoordinator.executionProgress(runId, item);
|
|
2407
1684
|
this.runEventBridge.emitConversationItem(runId, item);
|
|
2408
1685
|
};
|
|
2409
1686
|
emitConversationTurn = (turn) => {
|
|
@@ -2483,33 +1760,6 @@ export class NodeConnector {
|
|
|
2483
1760
|
});
|
|
2484
1761
|
return this.gitExecutionService;
|
|
2485
1762
|
}
|
|
2486
|
-
publishGoalState(session, goal) {
|
|
2487
|
-
if (goal.status !== 'active') {
|
|
2488
|
-
this.commandStates.clear(session.id, 'goal');
|
|
2489
|
-
this.emitWorkbenchEvent('session', {
|
|
2490
|
-
session: this.sessionPresentationService.present(session)
|
|
2491
|
-
});
|
|
2492
|
-
return;
|
|
2493
|
-
}
|
|
2494
|
-
this.commandStates.set(session.id, {
|
|
2495
|
-
commandId: 'goal',
|
|
2496
|
-
active: goal.status === 'active',
|
|
2497
|
-
label: 'Goal',
|
|
2498
|
-
detail: goal.objective.slice(0, 500),
|
|
2499
|
-
closable: true,
|
|
2500
|
-
closeInput: '/goal',
|
|
2501
|
-
goal: {
|
|
2502
|
-
id: goal.id,
|
|
2503
|
-
objective: goal.objective,
|
|
2504
|
-
revision: goal.revision,
|
|
2505
|
-
status: goal.status,
|
|
2506
|
-
turns: goal.turns
|
|
2507
|
-
}
|
|
2508
|
-
});
|
|
2509
|
-
this.emitWorkbenchEvent('session', {
|
|
2510
|
-
session: this.sessionPresentationService.present(session)
|
|
2511
|
-
});
|
|
2512
|
-
}
|
|
2513
1763
|
send(type, payload, replyTo) {
|
|
2514
1764
|
this.controlChannel.send(type, payload, replyTo);
|
|
2515
1765
|
}
|
|
@@ -2517,20 +1767,3 @@ export class NodeConnector {
|
|
|
2517
1767
|
this.controlChannel.sendEnvelope(envelope);
|
|
2518
1768
|
}
|
|
2519
1769
|
}
|
|
2520
|
-
const goalReadToolNames = new Set([
|
|
2521
|
-
`${NODE_MCP.goal.name}.${NODE_MCP.goal.tools.get}`,
|
|
2522
|
-
`${NODE_MCP.goal.name}_${NODE_MCP.goal.tools.get}`,
|
|
2523
|
-
`mcp__${NODE_MCP.goal.name}__${NODE_MCP.goal.tools.get}`
|
|
2524
|
-
]);
|
|
2525
|
-
function isGoalProgressItem(item) {
|
|
2526
|
-
if (!isPlainRecord(item) || typeof item.kind !== 'string')
|
|
2527
|
-
return false;
|
|
2528
|
-
if (item.kind === 'command_execution' || item.kind === 'file_change')
|
|
2529
|
-
return true;
|
|
2530
|
-
if (item.kind !== 'tool_call' || !isPlainRecord(item.payload))
|
|
2531
|
-
return false;
|
|
2532
|
-
const toolName = item.payload.toolName;
|
|
2533
|
-
return (typeof toolName === 'string' &&
|
|
2534
|
-
!goalReadToolNames.has(toolName) &&
|
|
2535
|
-
!(item.payload.namespace === NODE_MCP.goal.name && toolName === NODE_MCP.goal.tools.get));
|
|
2536
|
-
}
|