@myagentroam/node 0.10.60 → 0.10.62
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 +103 -873
- 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/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,7 +895,7 @@ 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
|
}),
|
|
996
901
|
execute: (operation, data) => this.modelUsage.withGrant(isPlainRecord(data) ? data.__usageGrant : undefined, () => this.executeNodeOperation(operation, data)),
|
|
@@ -1026,83 +931,9 @@ export class NodeConnector {
|
|
|
1026
931
|
emitSession: (session) => this.emitSessionWorkbenchEvent({
|
|
1027
932
|
session: this.sessionPresentationService.present(session)
|
|
1028
933
|
}),
|
|
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
|
-
}
|
|
934
|
+
beginGoal: (session) => this.goalCoordinator.begin(session),
|
|
935
|
+
goal: (input) => this.goalCoordinator.start(input),
|
|
936
|
+
clearGoal: (session) => this.goalCoordinator.clear(session)
|
|
1106
937
|
});
|
|
1107
938
|
this.sessionMessageService = new SessionMessageService({
|
|
1108
939
|
runtime: this.runtime,
|
|
@@ -1121,53 +952,9 @@ export class NodeConnector {
|
|
|
1121
952
|
const session = this.runtime.getAgentSession(sessionId);
|
|
1122
953
|
return session?.channelToken ?? undefined;
|
|
1123
954
|
},
|
|
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)
|
|
955
|
+
goalMessage: (session, content, intent) => this.goalCoordinator.goalMessage(session, content, intent),
|
|
956
|
+
prepareGoalInsertion: (sessionId, submissionId, goal) => this.goalCoordinator.prepareInsertion(sessionId, submissionId, goal),
|
|
957
|
+
goalPreparationFailed: (session, goal, activated) => this.goalCoordinator.handleGoalPreparationFailed(session, goal, activated)
|
|
1171
958
|
});
|
|
1172
959
|
const inputState = (entry, message) => this.marAgentClient.restartInputState(entry.session.id, message.clientMessageId);
|
|
1173
960
|
const restorer = new RestartSessionRestorer({
|
|
@@ -1180,7 +967,8 @@ export class NodeConnector {
|
|
|
1180
967
|
authorize: (request) => this.restartAuthorization.authorize(request),
|
|
1181
968
|
inputState,
|
|
1182
969
|
withGrant: (context, work) => this.modelUsage.withGrant(context.usageGrant, work),
|
|
1183
|
-
publishGoal: (session, goal) => this.publishGoalState(session, goal),
|
|
970
|
+
publishGoal: (session, goal) => this.goalCoordinator.publishGoalState(session, goal),
|
|
971
|
+
goalReference: (sessionId) => this.goalCoordinator.reference(sessionId),
|
|
1184
972
|
publishQueue: (session) => this.continuousRunCoordinator.publish(session.id)
|
|
1185
973
|
});
|
|
1186
974
|
this.restartHandoff = new PlannedRestartHandoff({
|
|
@@ -1284,7 +1072,7 @@ export class NodeConnector {
|
|
|
1284
1072
|
runtime: this.runtime,
|
|
1285
1073
|
state: this.runState,
|
|
1286
1074
|
emitRun: (runId, type, payload, status) => this.runEventBridge.emitRun(runId, type, payload, status),
|
|
1287
|
-
publishApproval: (input) => this.
|
|
1075
|
+
publishApproval: (input) => this.runnerInteractionService.publishApproval(input),
|
|
1288
1076
|
disconnect: () => this.controlChannel.close(4010, 'FAKE_RUNNER_DISCONNECT')
|
|
1289
1077
|
});
|
|
1290
1078
|
this.fakeRunner?.bindControl({
|
|
@@ -1437,7 +1225,8 @@ export class NodeConnector {
|
|
|
1437
1225
|
heartbeatMs: HEARTBEAT_MS,
|
|
1438
1226
|
refreshMs: CAPABILITY_REFRESH_MS,
|
|
1439
1227
|
onRegistered: async () => {
|
|
1440
|
-
await this.
|
|
1228
|
+
await this.conversationHosting.initialize();
|
|
1229
|
+
this.capabilities = await this.conversationHosting.project(this.capabilities);
|
|
1441
1230
|
this.computerUseEnablement.initialize();
|
|
1442
1231
|
},
|
|
1443
1232
|
onConnected: () => {
|
|
@@ -1499,7 +1288,11 @@ export class NodeConnector {
|
|
|
1499
1288
|
}));
|
|
1500
1289
|
return { runId: result.run.id };
|
|
1501
1290
|
},
|
|
1502
|
-
|
|
1291
|
+
sessionTitle: (sessionId) => {
|
|
1292
|
+
const session = this.runtime.getAgentSession(sessionId);
|
|
1293
|
+
return session?.customTitle ?? session?.runnerTitle ?? sessionId;
|
|
1294
|
+
},
|
|
1295
|
+
emitNotification: (payload) => this.emitWorkbenchEvent('conversation', payload)
|
|
1503
1296
|
});
|
|
1504
1297
|
this.runnerService = new RunnerService(requireDatabase, () => this.capabilities, this.runners, this.runnerUsageReader);
|
|
1505
1298
|
this.workbenchManifestService = new WorkbenchManifestService({
|
|
@@ -1588,21 +1381,21 @@ export class NodeConnector {
|
|
|
1588
1381
|
this.operationRouter.registerAll(this.promptCompletion.operations());
|
|
1589
1382
|
for (const operation of CONVERSATION_TEMPLATE_OPERATIONS)
|
|
1590
1383
|
this.operationRouter.register(operation, (data) => {
|
|
1591
|
-
const handler = this.
|
|
1384
|
+
const handler = this.conversationHosting.handler(operation);
|
|
1592
1385
|
if (handler === undefined)
|
|
1593
1386
|
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
1594
1387
|
return handler(data);
|
|
1595
1388
|
});
|
|
1596
1389
|
for (const operation of HOSTED_CONVERSATION_OPERATIONS)
|
|
1597
1390
|
this.operationRouter.register(operation, (data) => {
|
|
1598
|
-
const handler = this.
|
|
1391
|
+
const handler = this.conversationHosting.handler(operation);
|
|
1599
1392
|
if (handler === undefined)
|
|
1600
1393
|
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
1601
1394
|
return handler(data);
|
|
1602
1395
|
});
|
|
1603
1396
|
for (const operation of CONVERSATION_HOSTING_MAINTENANCE_OPERATIONS)
|
|
1604
1397
|
this.operationRouter.register(operation, (data) => {
|
|
1605
|
-
const handler = this.
|
|
1398
|
+
const handler = this.conversationHosting.handler(operation);
|
|
1606
1399
|
if (handler === undefined)
|
|
1607
1400
|
throw new Error('CONVERSATION_HOSTING_UNAVAILABLE');
|
|
1608
1401
|
return handler(data);
|
|
@@ -1630,7 +1423,7 @@ export class NodeConnector {
|
|
|
1630
1423
|
session: this.sessionPresentationService.present(session)
|
|
1631
1424
|
}),
|
|
1632
1425
|
changed: () => this.scheduledSessions.changed(),
|
|
1633
|
-
proposed: (sessionId, proposalId) => this.
|
|
1426
|
+
proposed: (sessionId, proposalId) => this.scheduledSessions.proposalCreated(sessionId, proposalId)
|
|
1634
1427
|
});
|
|
1635
1428
|
this.operationRouter.registerAll(this.sessionScheduleService.operations());
|
|
1636
1429
|
this.operationRouter.registerAll(this.sessionMessageService.operations());
|
|
@@ -1645,8 +1438,11 @@ export class NodeConnector {
|
|
|
1645
1438
|
configPath: this.configPath,
|
|
1646
1439
|
platform: () => this.capabilities.platform,
|
|
1647
1440
|
capability: () => this.capabilities.conversationHosting,
|
|
1648
|
-
initialize: () =>
|
|
1649
|
-
|
|
1441
|
+
initialize: async () => {
|
|
1442
|
+
await this.conversationHosting.initialize();
|
|
1443
|
+
this.capabilities = await this.conversationHosting.project(this.capabilities);
|
|
1444
|
+
},
|
|
1445
|
+
prepareForDisable: () => this.conversationHosting.prepareForDisable(),
|
|
1650
1446
|
publishCapabilities: () => this.send('capabilities', this.capabilities)
|
|
1651
1447
|
}).operations());
|
|
1652
1448
|
this.operationRouter.registerAll(this.computerUseEnablement.operations());
|
|
@@ -1670,147 +1466,12 @@ export class NodeConnector {
|
|
|
1670
1466
|
binaryTerminal: true
|
|
1671
1467
|
}));
|
|
1672
1468
|
}
|
|
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
1469
|
projectComputerUseCapability(capabilities) {
|
|
1791
1470
|
return {
|
|
1792
1471
|
...capabilities,
|
|
1793
1472
|
computerUse: this.computerUseEnablement.capability()
|
|
1794
1473
|
};
|
|
1795
1474
|
}
|
|
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
1475
|
async start() {
|
|
1815
1476
|
if (this.stopping !== undefined)
|
|
1816
1477
|
await this.stopping;
|
|
@@ -1837,7 +1498,8 @@ export class NodeConnector {
|
|
|
1837
1498
|
await this.restartHandoff.load();
|
|
1838
1499
|
this.scheduledSessions.recoverInterrupted();
|
|
1839
1500
|
this.scheduledSessions.start();
|
|
1840
|
-
await this.
|
|
1501
|
+
await this.conversationHosting.initialize();
|
|
1502
|
+
this.capabilities = await this.conversationHosting.project(this.capabilities);
|
|
1841
1503
|
this.computerUseEnablement.initialize();
|
|
1842
1504
|
this.directTransferService ??= await this.createDirectTransferService();
|
|
1843
1505
|
this.connectionLifecycle.start();
|
|
@@ -1914,287 +1576,12 @@ export class NodeConnector {
|
|
|
1914
1576
|
};
|
|
1915
1577
|
return this.directTransferService?.handleTerminal(command) ?? false;
|
|
1916
1578
|
}
|
|
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
1579
|
stop(options = {}) {
|
|
2194
1580
|
if (this.stopping !== undefined)
|
|
2195
1581
|
return this.stopping;
|
|
2196
1582
|
this.stopped = true;
|
|
2197
1583
|
this.continuousRunCoordinator.suspend();
|
|
1584
|
+
this.goalCoordinator.freeze();
|
|
2198
1585
|
this.scheduledSessions.stop();
|
|
2199
1586
|
this.stopping = (async () => {
|
|
2200
1587
|
let snapshot;
|
|
@@ -2219,43 +1606,18 @@ export class NodeConnector {
|
|
|
2219
1606
|
this.recentRunProjectionService.dispose();
|
|
2220
1607
|
this.upgradeCoordinator.stop();
|
|
2221
1608
|
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
1609
|
this.nativeSessionWatchService.clear();
|
|
2233
1610
|
this.promptCompletion.dispose();
|
|
2234
1611
|
this.sessionIdentityService.dispose();
|
|
2235
1612
|
this.sessionLifecycleService.clear();
|
|
2236
1613
|
this.continuousRunCoordinator.clear();
|
|
2237
1614
|
this.gitOperationService.clear();
|
|
2238
|
-
|
|
2239
|
-
clearTimeout(retry.timer);
|
|
2240
|
-
this.goalRetryTimers.clear();
|
|
2241
|
-
this.goalContinuationPending.clear();
|
|
2242
|
-
this.goalSubmissionAttempts.clear();
|
|
2243
|
-
this.goalContinuationEpochs.clear();
|
|
1615
|
+
this.goalCoordinator.dispose();
|
|
2244
1616
|
this.runEventService.clear();
|
|
2245
1617
|
this.sessionContextService.dispose();
|
|
2246
|
-
for (const run of this.claudeClient.execution.runs.values())
|
|
2247
|
-
run.handle.cancel();
|
|
2248
|
-
this.claudeClient.execution.runs.clear();
|
|
2249
1618
|
this.runAttachmentService.dispose();
|
|
2250
1619
|
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;
|
|
1620
|
+
const conversationHostingDrain = this.conversationHosting.dispose();
|
|
2259
1621
|
const uploadDrain = this.workspaceUploadService.shutdown();
|
|
2260
1622
|
this.workspaceFileService.dispose();
|
|
2261
1623
|
this.workspaceWatchService.clearAll();
|
|
@@ -2271,7 +1633,7 @@ export class NodeConnector {
|
|
|
2271
1633
|
this.terminalChannel.stop();
|
|
2272
1634
|
this.runtimeCredentials.close();
|
|
2273
1635
|
this.chatgptLogins.close();
|
|
2274
|
-
this.scheduledSessions.stop();
|
|
1636
|
+
const scheduledDrain = this.scheduledSessions.stop();
|
|
2275
1637
|
this.scheduledRunAuthorization.close();
|
|
2276
1638
|
this.restartAuthorization.close();
|
|
2277
1639
|
this.mailNotificationControl.close();
|
|
@@ -2281,8 +1643,8 @@ export class NodeConnector {
|
|
|
2281
1643
|
this.directTransferService = undefined;
|
|
2282
1644
|
this.controlChannel.stop();
|
|
2283
1645
|
const database = this.database;
|
|
2284
|
-
this.database = undefined;
|
|
2285
1646
|
const drains = Promise.allSettled([
|
|
1647
|
+
scheduledDrain,
|
|
2286
1648
|
marAgentDrain,
|
|
2287
1649
|
runnerDrain,
|
|
2288
1650
|
terminalDrain,
|
|
@@ -2295,11 +1657,11 @@ export class NodeConnector {
|
|
|
2295
1657
|
return (async () => {
|
|
2296
1658
|
let failed;
|
|
2297
1659
|
try {
|
|
2298
|
-
await
|
|
1660
|
+
await conversationHostingDrain;
|
|
2299
1661
|
}
|
|
2300
1662
|
finally {
|
|
2301
|
-
await hostedConversationRuntime?.dispose().catch(() => undefined);
|
|
2302
1663
|
const results = await drains;
|
|
1664
|
+
this.database = undefined;
|
|
2303
1665
|
await this.gitExecutionService?.close();
|
|
2304
1666
|
this.usageOutbox?.disconnected();
|
|
2305
1667
|
this.usageOutbox = undefined;
|
|
@@ -2313,97 +1675,9 @@ export class NodeConnector {
|
|
|
2313
1675
|
}
|
|
2314
1676
|
/** @internal Thin compatibility delegates; all behavior remains in the extracted services. */
|
|
2315
1677
|
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
1678
|
emitRunEvent = (runId, eventType, payload, status) => this.runEventBridge.emitRun(runId, eventType, payload, status);
|
|
2403
1679
|
emitConversationItem = (runId, item) => {
|
|
2404
|
-
|
|
2405
|
-
isGoalProgressItem(item))
|
|
2406
|
-
this.goalExecutionProgress.add(runId);
|
|
1680
|
+
this.goalCoordinator.executionProgress(runId, item);
|
|
2407
1681
|
this.runEventBridge.emitConversationItem(runId, item);
|
|
2408
1682
|
};
|
|
2409
1683
|
emitConversationTurn = (turn) => {
|
|
@@ -2483,33 +1757,6 @@ export class NodeConnector {
|
|
|
2483
1757
|
});
|
|
2484
1758
|
return this.gitExecutionService;
|
|
2485
1759
|
}
|
|
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
1760
|
send(type, payload, replyTo) {
|
|
2514
1761
|
this.controlChannel.send(type, payload, replyTo);
|
|
2515
1762
|
}
|
|
@@ -2517,20 +1764,3 @@ export class NodeConnector {
|
|
|
2517
1764
|
this.controlChannel.sendEnvelope(envelope);
|
|
2518
1765
|
}
|
|
2519
1766
|
}
|
|
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
|
-
}
|