@borgee/agents-host 0.2.97 → 0.2.110
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/README.md +77 -5
- package/dist/agents-host.d.ts +16 -0
- package/dist/agents-host.js +608 -82
- package/dist/catalog-worker.js +21 -0
- package/dist/catalog-worker.js.map +7 -0
- package/dist/chat/chat-control-plane.d.ts +19 -4
- package/dist/chat/normalized-input.d.ts +9 -0
- package/dist/chat/normalized-input.js +24 -0
- package/dist/chat/sdk-chat-control-plane.d.ts +46 -6
- package/dist/chat/sdk-chat-control-plane.js +169 -6
- package/dist/cli-args.d.ts +1 -1
- package/dist/cli-args.js +13 -4
- package/dist/config.d.ts +3 -1
- package/dist/config.js +25 -0
- package/dist/context/injection.d.ts +16 -0
- package/dist/context/injection.js +37 -21
- package/dist/context/prompt.d.ts +12 -0
- package/dist/context/prompt.js +25 -0
- package/dist/context/skill-manual.js +1 -1
- package/dist/context/turn-preparation.d.ts +2 -1
- package/dist/context/turn-preparation.js +15 -0
- package/dist/execution-telemetry.d.ts +96 -0
- package/dist/execution-telemetry.js +583 -0
- package/dist/gateway/channel-file-workspace.d.ts +15 -0
- package/dist/gateway/channel-file-workspace.js +130 -0
- package/dist/gateway/localhost-gateway.js +167 -2
- package/dist/hosted-turn-content.js +3 -2
- package/dist/local-config.js +34 -1
- package/dist/managed-daemon.js +15 -0
- package/dist/native-environment.d.ts +2 -0
- package/dist/native-environment.js +9 -0
- package/dist/native-protocol-types/codec.d.ts +49 -0
- package/dist/native-protocol-types/commands.d.ts +71 -0
- package/dist/native-protocol-types/compatibility.d.ts +5 -0
- package/dist/native-protocol-types/cursor.d.ts +7 -0
- package/dist/native-protocol-types/envelope.d.ts +1105 -0
- package/dist/native-protocol-types/file-changes.d.ts +20 -0
- package/dist/native-protocol-types/history.d.ts +487 -0
- package/dist/native-protocol-types/index.d.ts +18 -0
- package/dist/native-protocol-types/interactions.d.ts +619 -0
- package/dist/native-protocol-types/messages.d.ts +2482 -0
- package/dist/native-protocol-types/public-validation.d.ts +10 -0
- package/dist/native-protocol-types/remote-host-uplink.d.ts +67 -0
- package/dist/native-protocol-types/resources.d.ts +126 -0
- package/dist/native-protocol-types/session-settings.d.ts +22 -0
- package/dist/native-protocol-types/snapshot.d.ts +575 -0
- package/dist/native-protocol-types/timeline.d.ts +1503 -0
- package/dist/native-protocol-types/tool-result.d.ts +26 -0
- package/dist/native-protocol-types/uplink.d.ts +56 -0
- package/dist/native-protocol-types/version.d.ts +5 -0
- package/dist/native-provider-types/commands.d.ts +51 -0
- package/dist/native-provider-types/control.d.ts +192 -0
- package/dist/native-provider-types/file-changes.d.ts +9 -0
- package/dist/native-provider-types/index.d.ts +8 -0
- package/dist/native-provider-types/interactions.d.ts +7 -0
- package/dist/native-provider-types/observation.d.ts +209 -0
- package/dist/native-provider-types/provider.d.ts +124 -0
- package/dist/native-provider-types/session-settings.d.ts +17 -0
- package/dist/native-provider-types/testing.d.ts +27 -0
- package/dist/native-provider-types/tool-result.d.ts +21 -0
- package/dist/native-providers.d.ts +21 -0
- package/dist/native-providers.js +18402 -0
- package/dist/native-providers.js.map +7 -0
- package/dist/plugin-sdk.js +10713 -229
- package/dist/plugin-sdk.js.map +4 -4
- package/dist/policy/copilot-permission.js +1 -8
- package/dist/policy/gateway-authorization.d.ts +1 -1
- package/dist/policy/gateway-authorization.js +22 -2
- package/dist/providers/claude/adapter.js +1 -0
- package/dist/providers/claude/cli-client.js +4 -0
- package/dist/providers/codex/adapter.js +1 -0
- package/dist/providers/codex/cli-client.js +5 -0
- package/dist/providers/copilot/adapter.js +4 -0
- package/dist/providers/copilot/cli-client.js +11 -1
- package/dist/providers/copilot/sdk-session.d.ts +12 -3
- package/dist/providers/copilot/sdk-session.js +230 -6
- package/dist/providers/create-provider.js +24 -0
- package/dist/providers/normalized/adapter.d.ts +107 -0
- package/dist/providers/normalized/adapter.js +1650 -0
- package/dist/providers/normalized/control-policy.d.ts +5 -0
- package/dist/providers/normalized/control-policy.js +12 -0
- package/dist/providers/normalized/interactions.d.ts +3 -0
- package/dist/providers/normalized/interactions.js +39 -0
- package/dist/providers/normalized/legacy-input-ownership.d.ts +34 -0
- package/dist/providers/normalized/legacy-input-ownership.js +123 -0
- package/dist/providers/normalized/session-management.d.ts +67 -0
- package/dist/providers/normalized/session-management.js +334 -0
- package/dist/providers/normalized/session-ownership.d.ts +3 -0
- package/dist/providers/normalized/session-ownership.js +90 -0
- package/dist/providers/prompt-usage.d.ts +8 -0
- package/dist/providers/prompt-usage.js +52 -0
- package/dist/providers/provider-adapter.d.ts +43 -1
- package/dist/providers/provider-adapter.js +8 -0
- package/dist/state-paths.d.ts +2 -0
- package/dist/state-paths.js +6 -0
- package/dist/types.d.ts +66 -0
- package/dist/typing-lease.d.ts +14 -0
- package/dist/typing-lease.js +31 -0
- package/dist/vendor/agent-provider-codex/LICENSE +211 -0
- package/dist/vendor/agent-provider-codex/NOTICE +13 -0
- package/package.json +22 -13
- package/skills/borgee-agent/SKILL.md +12 -4
- package/skills/borgee-agent/scripts/borgee-agent.mjs +110 -0
- package/skills/borgee-agent/scripts/borgee-agent.py +116 -0
package/dist/agents-host.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readNormalizedInputText, readDurableInputText } from './chat/normalized-input.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { chmod, link, mkdir, readFile, readdir, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
4
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -5,6 +6,10 @@ import { SdkChatControlPlane } from './chat/sdk-chat-control-plane.js';
|
|
|
5
6
|
import { HostLogger, summarizeError } from './debug.js';
|
|
6
7
|
import { toReportedActivity } from './progress-to-activity.js';
|
|
7
8
|
import { BackgroundRunTracker } from './background-runs.js';
|
|
9
|
+
import { ExecutionDeliveryJournal, ExecutionTelemetryReporter, } from './execution-telemetry.js';
|
|
10
|
+
import { startTypingLease } from './typing-lease.js';
|
|
11
|
+
import { recordProtocolTrace, NormalizedActivityPublisher, NormalizedExecutionTelemetry, sessionControlResponse, parseSessionControlRequest } from './plugin-sdk.js';
|
|
12
|
+
import { respondToHostedInteraction } from './providers/normalized/interactions.js';
|
|
8
13
|
import { ATTENTION_FOLLOW_SEMANTICS_COMPATIBILITY_GATE, COLLABORATION_CAPABILITIES_DIAGNOSTICS_COMPATIBILITY_GATE, COLLABORATION_OUTCOME_MODEL_COMPATIBILITY_GATE, COLLABORATION_SKILL_FIRST_COMPATIBILITY_GATE, CONTEXT_INJECTION_COMPATIBILITY_GATE, LOCALHOST_GATEWAY_COMPATIBILITY_GATE, POLICY_AUDIT_ENFORCEMENT_COMPATIBILITY_GATE, resolveInternalPolicyMode, resolveInternalCompatibilityGates, SKILL_RUNTIME_COMPATIBILITY_GATE, TASK_THREAD_COLLABORATION_CONTRACT_COMPATIBILITY_GATE, TOKEN_BINDING_COMPATIBILITY_GATE, } from './compatibility-gates.js';
|
|
9
14
|
import { buildNormalizedAttentionSnapshot, parsePersistedAttentionSnapshot, } from './context/attention.js';
|
|
10
15
|
import { mergeCompactionSnapshot, parsePersistedCompactionSnapshot, } from './context/compaction.js';
|
|
@@ -13,8 +18,8 @@ import { createLocalhostGatewayController, LOCALHOST_GATEWAY_COLLABORATION_TARGE
|
|
|
13
18
|
import { extractTaskIdFromTaskAssignmentContent, resolveTaskAssignmentStatePath, } from './context/injection.js';
|
|
14
19
|
import { AuthorizationAuditSink, } from './policy/authorization-audit.js';
|
|
15
20
|
import { createProvider } from './providers/create-provider.js';
|
|
16
|
-
import { ProviderTurnCancelledError } from './providers/provider-adapter.js';
|
|
17
|
-
import { resolveAttentionStatePath, resolveCompactionStatePath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
|
|
21
|
+
import { ProviderInputRejectedError, ProviderTurnCancelledError } from './providers/provider-adapter.js';
|
|
22
|
+
import { resolveAttentionStatePath, resolveCompactionStatePath, resolveExecutionDeliveryJournalPath, resolveExecutionTelemetryOutboxPath, resolveSharedProtocolKickoffDecisionPath, resolveSharedProtocolStatusPath, } from './state-paths.js';
|
|
18
23
|
import { appendVisibleMention as appendVisibleBodyMention, extractVisibleMentionIds as extractCanonicalMentionIds, } from './visible-mentions.js';
|
|
19
24
|
import { buildHostedIncomingContentText, buildHostedTurnContentParts, extractHostedMessageAttachments, normalizeHostedMessageAttachment, } from './hosted-turn-content.js';
|
|
20
25
|
import { waitForTaskForThread } from './task-thread-resolution.js';
|
|
@@ -110,6 +115,24 @@ class DefaultHostRuntime {
|
|
|
110
115
|
deps.borgee ??
|
|
111
116
|
new SdkChatControlPlane(config.borgeeBaseUrl, config.agent.agentApiKey, undefined, {
|
|
112
117
|
pluginId: `agents-host:${config.agent.provider}`,
|
|
118
|
+
executionTelemetryOutboxPath: resolveExecutionTelemetryOutboxPath(config.stateRootDir),
|
|
119
|
+
onExecutionTelemetryError: (error) => {
|
|
120
|
+
deps.logger.error('execution telemetry transport failed', {
|
|
121
|
+
error: summarizeError(error),
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
providerOutputMode: () => this.provider.outputMode ?? 'legacy',
|
|
125
|
+
connectionLiveness: () => config.agent.provider === 'codex' && this.provider.outputMode === 'normalized'
|
|
126
|
+
? { probeIntervalMs: 30_000, pongTimeoutMs: 10_000, connectTimeoutMs: 30_000 }
|
|
127
|
+
: undefined,
|
|
128
|
+
normalizedActivitySync: () => this.provider.outputMode === 'normalized' && !!this.provider.syncNormalizedActivity,
|
|
129
|
+
sessionControl: () => this.provider.outputMode === 'normalized' && !!this.provider.controlSession,
|
|
130
|
+
sessionBindings: () => this.provider.outputMode === 'normalized' && !!this.provider.setSessionPublication,
|
|
131
|
+
sessionRelations: () => this.provider.outputMode === 'normalized' && !!this.provider.describeSession,
|
|
132
|
+
sessionManagement: () => this.provider.outputMode === 'normalized' && this.provider.supportsSessionManagement === true,
|
|
133
|
+
sessionDirectory: () => this.provider.outputMode === 'normalized' && !!this.provider.listSessions,
|
|
134
|
+
sessionObservation: () => this.provider.outputMode === 'normalized' && !!this.provider.observeSession,
|
|
135
|
+
sessionInputOperations: () => this.provider.outputMode === 'normalized' ? this.provider.sessionInputOperations ?? [] : [],
|
|
113
136
|
});
|
|
114
137
|
this.gateway = createLocalhostGatewayController({
|
|
115
138
|
gateEnabled: localhostGatewayGateEnabled,
|
|
@@ -133,6 +156,12 @@ class DefaultHostRuntime {
|
|
|
133
156
|
stateRootDir: config.stateRootDir,
|
|
134
157
|
agentApiKey: config.agent.agentApiKey,
|
|
135
158
|
resolveStableAgentId: deps.resolveStableAgentId,
|
|
159
|
+
providerOutputMode: config.providerOutputMode,
|
|
160
|
+
nativeProviderCommand: config.nativeProviderCommand,
|
|
161
|
+
sessionWorkspaceRoot: config.sessionWorkspaceRoot,
|
|
162
|
+
codexConnectionMode: config.codexConnectionMode,
|
|
163
|
+
codexSharedSocketPath: config.codexSharedSocketPath,
|
|
164
|
+
codexTrustShared: config.codexTrustShared,
|
|
136
165
|
claudeCommand: config.claudeCommand,
|
|
137
166
|
claudeArgs: config.claudeArgs,
|
|
138
167
|
codexCommand: config.codexCommand,
|
|
@@ -150,16 +179,19 @@ class DefaultHostRuntime {
|
|
|
150
179
|
});
|
|
151
180
|
this.ensureStateRoot = deps.ensureStateRoot ?? ensurePrivateStateRoot;
|
|
152
181
|
}
|
|
153
|
-
async start(onMessage, onStopTurn, onStopBackgroundRun, onActivityTransportReady) {
|
|
182
|
+
async start(onMessage, onStopTurn, onStopBackgroundRun, onActivityTransportReady, onSessionControl) {
|
|
154
183
|
await this.ensureStateRoot(this.config.stateRootDir);
|
|
155
184
|
await this.gateway.start();
|
|
156
185
|
try {
|
|
186
|
+
if (this.provider.outputMode === 'normalized')
|
|
187
|
+
this.controlPlane.onServerRequest?.((action, params, context) => action === 'session_control' && onSessionControl && context ? onSessionControl(parseSessionControlRequest(params), context) : respondToHostedInteraction(this.provider, action, params, context));
|
|
157
188
|
this.controlPlane.onStopTurn?.(onStopTurn);
|
|
158
189
|
this.controlPlane.onStopBackgroundRun?.(onStopBackgroundRun);
|
|
159
190
|
this.controlPlane.onActivityTransportReady?.(onActivityTransportReady);
|
|
160
191
|
await this.controlPlane.connect(onMessage);
|
|
161
192
|
}
|
|
162
193
|
catch (error) {
|
|
194
|
+
this.controlPlane.onServerRequest?.(undefined);
|
|
163
195
|
this.controlPlane.onStopTurn?.(undefined);
|
|
164
196
|
this.controlPlane.onStopBackgroundRun?.(undefined);
|
|
165
197
|
this.controlPlane.onActivityTransportReady?.(undefined);
|
|
@@ -170,6 +202,7 @@ class DefaultHostRuntime {
|
|
|
170
202
|
async stop() {
|
|
171
203
|
let thrown;
|
|
172
204
|
try {
|
|
205
|
+
this.controlPlane.onServerRequest?.(undefined);
|
|
173
206
|
this.controlPlane.onStopTurn?.(undefined);
|
|
174
207
|
this.controlPlane.onStopBackgroundRun?.(undefined);
|
|
175
208
|
this.controlPlane.onActivityTransportReady?.(undefined);
|
|
@@ -286,11 +319,14 @@ const MAX_AUTONOMOUS_REPLY_DEDUPLICATION_KEYS = 256;
|
|
|
286
319
|
*/
|
|
287
320
|
export class AgentsHost {
|
|
288
321
|
config;
|
|
322
|
+
executionTelemetry;
|
|
323
|
+
normalizedStateOwners = new Map();
|
|
289
324
|
runtime;
|
|
290
325
|
provider;
|
|
291
326
|
borgee;
|
|
292
327
|
backgroundRuns;
|
|
293
328
|
logger;
|
|
329
|
+
executionDeliveryJournal;
|
|
294
330
|
contextInjectionEnabled;
|
|
295
331
|
collaborationEnabled;
|
|
296
332
|
collaborationOutcomeModelEnabled;
|
|
@@ -298,6 +334,8 @@ export class AgentsHost {
|
|
|
298
334
|
taskThreadCollaborationContractEnabled;
|
|
299
335
|
collaborationCapabilitiesDiagnosticsEnabled;
|
|
300
336
|
activeTurns = new Set();
|
|
337
|
+
executionTelemetryByExecutionId = new Map();
|
|
338
|
+
executionDispatches = new Map();
|
|
301
339
|
awaitingUserByChannel = new Map();
|
|
302
340
|
collaborationOutcomeByChannel = new Map();
|
|
303
341
|
/**
|
|
@@ -328,6 +366,9 @@ export class AgentsHost {
|
|
|
328
366
|
constructor(config, deps) {
|
|
329
367
|
this.config = config;
|
|
330
368
|
this.logger = new HostLogger(deps?.runtimeOptions);
|
|
369
|
+
this.executionDeliveryJournal =
|
|
370
|
+
deps?.executionDeliveryJournal ??
|
|
371
|
+
new ExecutionDeliveryJournal(resolveExecutionDeliveryJournalPath(config.stateRootDir));
|
|
331
372
|
const compatibilityGates = resolveInternalCompatibilityGates();
|
|
332
373
|
const contextInjectionGateEnabled = compatibilityGates.has(CONTEXT_INJECTION_COMPATIBILITY_GATE);
|
|
333
374
|
this.contextInjectionEnabled = contextInjectionGateEnabled;
|
|
@@ -392,7 +433,17 @@ export class AgentsHost {
|
|
|
392
433
|
const bufferedMessages = [];
|
|
393
434
|
let bufferingMessages = true;
|
|
394
435
|
try {
|
|
436
|
+
await this.executionDeliveryJournal.load();
|
|
395
437
|
this.provider.onAutonomousReply?.((reply) => this.dispatchAutonomousReply(reply));
|
|
438
|
+
this.provider.setSessionPublication?.({
|
|
439
|
+
supported: () => this.started && !this.controlPlaneClosed && this.borgee.sessionBindingsSupported === true,
|
|
440
|
+
publish: (channelId, activity) => {
|
|
441
|
+
if (!this.started || this.controlPlaneClosed || !this.borgee.sessionBindingsSupported || !this.borgee.reportActivity)
|
|
442
|
+
return false;
|
|
443
|
+
this.borgee.reportActivity({ channelId, activity });
|
|
444
|
+
return true;
|
|
445
|
+
},
|
|
446
|
+
});
|
|
396
447
|
await this.runtime.start((message) => {
|
|
397
448
|
if (!this.started) {
|
|
398
449
|
return;
|
|
@@ -402,8 +453,24 @@ export class AgentsHost {
|
|
|
402
453
|
return;
|
|
403
454
|
}
|
|
404
455
|
this.dispatchMessage(message);
|
|
405
|
-
}, async (channelId) => this.stopTurn(channelId), async (channelId, runId) => this.backgroundRuns.stop(channelId, runId), () => this.backgroundRuns.replay());
|
|
456
|
+
}, async (channelId) => this.stopTurn(channelId), async (channelId, runId) => this.backgroundRuns.stop(channelId, runId), () => { this.provider.publishSessionBindings?.(); this.backgroundRuns.replay(); }, (request, context) => this.handleSessionControl(request, context));
|
|
406
457
|
const agentId = await this.ensureSelfAgentId();
|
|
458
|
+
if (this.provider.outputMode === 'normalized' && this.borgee.reportExecutionTelemetryFrame && this.borgee.onExecutionTelemetryAck) {
|
|
459
|
+
try {
|
|
460
|
+
this.executionTelemetry = new NormalizedExecutionTelemetry({
|
|
461
|
+
outboxPath: join(this.config.stateRootDir, 'execution-telemetry.json'),
|
|
462
|
+
scope: { serverUrl: this.config.borgeeBaseUrl, agentId },
|
|
463
|
+
transport: {
|
|
464
|
+
report: frame => this.borgee.reportExecutionTelemetryFrame(frame),
|
|
465
|
+
onAck: handler => this.borgee.onExecutionTelemetryAck(handler),
|
|
466
|
+
onReady: handler => this.borgee.onExecutionTransportReady?.(handler) ?? (() => { }),
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
this.logger.error('normalized accounting unavailable', { error: summarizeError(error) });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
407
474
|
bufferingMessages = false;
|
|
408
475
|
while (bufferedMessages.length > 0) {
|
|
409
476
|
const pendingMessages = bufferedMessages.splice(0, bufferedMessages.length);
|
|
@@ -463,6 +530,9 @@ export class AgentsHost {
|
|
|
463
530
|
runtimeStopError = error;
|
|
464
531
|
}
|
|
465
532
|
await Promise.allSettled([...this.activeTurns]);
|
|
533
|
+
this.executionTelemetry?.dispose();
|
|
534
|
+
this.executionTelemetry = undefined;
|
|
535
|
+
this.normalizedStateOwners.clear();
|
|
466
536
|
await Promise.allSettled(pendingCompactionSnapshotWrites);
|
|
467
537
|
this.compactionSnapshotWriteQueueByChannel.clear();
|
|
468
538
|
if (runtimeStopError) {
|
|
@@ -471,16 +541,165 @@ export class AgentsHost {
|
|
|
471
541
|
}
|
|
472
542
|
trackActiveTurn(task) {
|
|
473
543
|
this.activeTurns.add(task);
|
|
474
|
-
void task
|
|
544
|
+
void task
|
|
545
|
+
.catch((error) => {
|
|
546
|
+
this.logger.error('dispatched turn failed', {
|
|
547
|
+
error: summarizeError(error),
|
|
548
|
+
});
|
|
549
|
+
})
|
|
550
|
+
.finally(() => {
|
|
475
551
|
this.activeTurns.delete(task);
|
|
476
552
|
});
|
|
477
553
|
}
|
|
478
554
|
dispatchMessage(message) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
555
|
+
// Self echoes must not occupy the channel queue before explicit control admission.
|
|
556
|
+
if (this.selfAgentId && String(message.user_id ?? message.sender_id ?? '') === this.selfAgentId && message.message_type !== 'task_assignment')
|
|
557
|
+
return;
|
|
558
|
+
const executionId = message.execution_grant?.executionId;
|
|
559
|
+
const existing = executionId
|
|
560
|
+
? this.executionDispatches.get(executionId)
|
|
561
|
+
: undefined;
|
|
562
|
+
if (existing) {
|
|
563
|
+
this.trackActiveTurn(existing);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const run = () => message.sessionTarget
|
|
567
|
+
? this.handleSelectedSessionMessage(message)
|
|
568
|
+
: this.collaborationEnabled
|
|
569
|
+
? this.handleCollaborationMessage(message)
|
|
570
|
+
: this.enqueueChannelTurn(message.channel_id, () => this.handleLegacyMessage(message));
|
|
571
|
+
if (this.provider.outputMode === 'normalized') {
|
|
572
|
+
this.trackActiveTurn(run());
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const work = executionId
|
|
576
|
+
? this.runClaimedExecution(message, run)
|
|
577
|
+
: this.runMessageWithExecutionTelemetry(message, run).then((result) => {
|
|
578
|
+
if (result.error)
|
|
579
|
+
throw result.error;
|
|
580
|
+
});
|
|
581
|
+
const task = executionId
|
|
582
|
+
? work.finally(() => {
|
|
583
|
+
this.executionDispatches.delete(executionId);
|
|
584
|
+
})
|
|
585
|
+
: work;
|
|
586
|
+
if (executionId)
|
|
587
|
+
this.executionDispatches.set(executionId, task);
|
|
482
588
|
this.trackActiveTurn(task);
|
|
483
589
|
}
|
|
590
|
+
async runClaimedExecution(message, run) {
|
|
591
|
+
const grant = message.execution_grant;
|
|
592
|
+
const claim = await this.executionDeliveryJournal.claim(grant.executionId);
|
|
593
|
+
if (claim === 'completed')
|
|
594
|
+
return;
|
|
595
|
+
if (claim === 'recovering') {
|
|
596
|
+
if (this.borgee.reportExecutionTelemetry) {
|
|
597
|
+
let reporter = this.executionTelemetryByExecutionId.get(grant.executionId);
|
|
598
|
+
const restoredReporter = !reporter;
|
|
599
|
+
reporter ??= this.createExecutionTelemetryReporter(grant);
|
|
600
|
+
this.executionTelemetryByExecutionId.set(grant.executionId, reporter);
|
|
601
|
+
await reporter.start();
|
|
602
|
+
if (restoredReporter) {
|
|
603
|
+
const persistedUsage = await this.executionDeliveryJournal.readUsage(grant.executionId);
|
|
604
|
+
if (persistedUsage) {
|
|
605
|
+
const { provider, ...usage } = persistedUsage;
|
|
606
|
+
await reporter.usage(provider, usage);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
reporter.noteFailure();
|
|
610
|
+
let finishError;
|
|
611
|
+
try {
|
|
612
|
+
await reporter.finish(false);
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
finishError = error;
|
|
616
|
+
}
|
|
617
|
+
if (reporter.allRequiredFramesDurable) {
|
|
618
|
+
await this.executionDeliveryJournal.complete(grant.executionId);
|
|
619
|
+
this.executionTelemetryByExecutionId.delete(grant.executionId);
|
|
620
|
+
}
|
|
621
|
+
if (finishError)
|
|
622
|
+
throw finishError;
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
throw new Error('Execution telemetry is unavailable for interrupted delivery recovery');
|
|
626
|
+
}
|
|
627
|
+
const result = await this.runMessageWithExecutionTelemetry(message, run);
|
|
628
|
+
if (result.allRequiredFramesDurable) {
|
|
629
|
+
await this.executionDeliveryJournal.complete(grant.executionId);
|
|
630
|
+
}
|
|
631
|
+
if (result.error)
|
|
632
|
+
throw result.error;
|
|
633
|
+
}
|
|
634
|
+
async runMessageWithExecutionTelemetry(message, run) {
|
|
635
|
+
const grant = message.execution_grant;
|
|
636
|
+
const reporter = grant && this.borgee.reportExecutionTelemetry
|
|
637
|
+
? this.executionTelemetryByExecutionId.get(grant.executionId) ??
|
|
638
|
+
this.createExecutionTelemetryReporter(grant)
|
|
639
|
+
: undefined;
|
|
640
|
+
if (grant && !reporter) {
|
|
641
|
+
return {
|
|
642
|
+
allRequiredFramesDurable: false,
|
|
643
|
+
error: new Error('Execution telemetry is unavailable for admitted delivery'),
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
if (grant && reporter) {
|
|
647
|
+
this.executionTelemetryByExecutionId.set(grant.executionId, reporter);
|
|
648
|
+
}
|
|
649
|
+
let runError;
|
|
650
|
+
try {
|
|
651
|
+
await run();
|
|
652
|
+
}
|
|
653
|
+
catch (error) {
|
|
654
|
+
reporter?.noteFailure();
|
|
655
|
+
runError = error;
|
|
656
|
+
}
|
|
657
|
+
let finishError;
|
|
658
|
+
if (reporter) {
|
|
659
|
+
try {
|
|
660
|
+
await reporter.finish();
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
finishError = error;
|
|
664
|
+
this.logger.error('execution telemetry finish failed', {
|
|
665
|
+
error: summarizeError(error),
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
finally {
|
|
669
|
+
if (reporter.allRequiredFramesDurable &&
|
|
670
|
+
grant &&
|
|
671
|
+
this.executionTelemetryByExecutionId.get(grant.executionId) === reporter) {
|
|
672
|
+
this.executionTelemetryByExecutionId.delete(grant.executionId);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const error = runError && finishError
|
|
677
|
+
? new AggregateError([runError, finishError], 'Execution and telemetry finalization both failed')
|
|
678
|
+
: runError ?? finishError;
|
|
679
|
+
return {
|
|
680
|
+
allRequiredFramesDurable: reporter?.allRequiredFramesDurable ?? grant === undefined,
|
|
681
|
+
...(error ? { error } : {}),
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
createExecutionTelemetryReporter(grant) {
|
|
685
|
+
return new ExecutionTelemetryReporter(grant, {
|
|
686
|
+
reportExecutionTelemetry: (frame) => this.borgee.reportExecutionTelemetry(frame),
|
|
687
|
+
}, Date.now, (provider, usage) => this.executionDeliveryJournal.recordUsage(grant.executionId, {
|
|
688
|
+
provider,
|
|
689
|
+
...usage,
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
async handleSelectedSessionMessage(msg) {
|
|
693
|
+
if (!msg.sessionTarget || !msg.sourceCurrent?.())
|
|
694
|
+
return;
|
|
695
|
+
const normalized = await this.normalizeMessage(msg);
|
|
696
|
+
if (!normalized || !msg.sourceCurrent())
|
|
697
|
+
return;
|
|
698
|
+
await this.executeTurn(msg, normalized.authorId, normalized.content, {
|
|
699
|
+
incomingParts: normalized.incomingParts,
|
|
700
|
+
selectedSession: { ...msg.sessionTarget, sourceCurrent: msg.sourceCurrent },
|
|
701
|
+
});
|
|
702
|
+
}
|
|
484
703
|
dispatchAutonomousReply(reply) {
|
|
485
704
|
if (!this.started
|
|
486
705
|
|| this.controlPlaneClosed
|
|
@@ -2309,6 +2528,254 @@ export class AgentsHost {
|
|
|
2309
2528
|
: {}),
|
|
2310
2529
|
});
|
|
2311
2530
|
}
|
|
2531
|
+
async handleSessionControl(request, context) {
|
|
2532
|
+
const response = (status, reason) => sessionControlResponse(request, status, reason);
|
|
2533
|
+
if (!this.started || this.controlPlaneClosed || context.signal.aborted || Date.now() >= request.deadline_at)
|
|
2534
|
+
return response('unconfirmed', 'session_control_expired');
|
|
2535
|
+
if (this.provider.outputMode !== 'normalized' || !this.provider.controlSession)
|
|
2536
|
+
return response('unsupported', 'session_control_not_supported');
|
|
2537
|
+
if (request.operation.kind === 'send_input')
|
|
2538
|
+
return this.handleNativeInput(request, context);
|
|
2539
|
+
if (this.borgee.sessionBindingsSupported)
|
|
2540
|
+
return this.handleRetainedControl(request, context);
|
|
2541
|
+
if (request.operation.kind === 'list_pending_inputs') {
|
|
2542
|
+
if (!this.provider.supportsSessionInput || !context.isConnectionCurrent)
|
|
2543
|
+
return response('unsupported', 'native_queue_inventory_not_supported');
|
|
2544
|
+
const connected = () => this.started && !this.controlPlaneClosed && this.selfAgentId === request.agent_id && context.isConnectionCurrent();
|
|
2545
|
+
if (!connected())
|
|
2546
|
+
return response('unconfirmed', 'session_control_expired');
|
|
2547
|
+
try {
|
|
2548
|
+
return (await this.provider.controlSession(request, { signal: context.signal, isConnectionCurrent: connected })).response;
|
|
2549
|
+
}
|
|
2550
|
+
catch {
|
|
2551
|
+
return response('unconfirmed', 'native_queue_inventory_unavailable');
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
if (request.operation.kind === 'list_commands') {
|
|
2555
|
+
try {
|
|
2556
|
+
return (await this.provider.controlSession(request, { signal: context.signal })).response;
|
|
2557
|
+
}
|
|
2558
|
+
catch {
|
|
2559
|
+
return response('unconfirmed', 'command_directory_unavailable');
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
const channelId = request.channel_id;
|
|
2563
|
+
const state = this.ensureCollaborationChannelState(channelId);
|
|
2564
|
+
if (state.processor || state.running || state.activeTurn || state.queue.length || this.channelTurnQueues.has(channelId))
|
|
2565
|
+
return response('rejected', 'session_busy');
|
|
2566
|
+
let finish;
|
|
2567
|
+
const ownership = new Promise(resolve => { finish = resolve; });
|
|
2568
|
+
state.processor = ownership;
|
|
2569
|
+
state.running = true;
|
|
2570
|
+
this.channelTurnQueues.set(channelId, ownership);
|
|
2571
|
+
let execution;
|
|
2572
|
+
let active;
|
|
2573
|
+
let retained = false;
|
|
2574
|
+
const release = () => {
|
|
2575
|
+
execution?.close();
|
|
2576
|
+
if (state.activeTurn === active)
|
|
2577
|
+
state.activeTurn = undefined;
|
|
2578
|
+
state.running = false;
|
|
2579
|
+
if (state.processor === ownership)
|
|
2580
|
+
state.processor = undefined;
|
|
2581
|
+
if (this.channelTurnQueues.get(channelId) === ownership)
|
|
2582
|
+
this.channelTurnQueues.delete(channelId);
|
|
2583
|
+
finish();
|
|
2584
|
+
};
|
|
2585
|
+
this.trackActiveTurn(ownership);
|
|
2586
|
+
try {
|
|
2587
|
+
if (request.operation.kind === 'execute_command') {
|
|
2588
|
+
if (!request.admission)
|
|
2589
|
+
return response('rejected', 'command_admission_required');
|
|
2590
|
+
const admission = request.admission;
|
|
2591
|
+
active = { generation: ++state.generation, turnExecutionId: admission.input_message_id, superseded: false, collaborationMessagesSent: 0, targetCooldowns: new Map() };
|
|
2592
|
+
state.activeTurn = active;
|
|
2593
|
+
const inputText = await readDurableInputText(this.borgee, { channelId, inputMessageId: admission.input_message_id, createdAt: admission.input_created_at });
|
|
2594
|
+
if (context.signal.aborted || Date.now() >= request.deadline_at)
|
|
2595
|
+
return response('unconfirmed', 'session_control_expired');
|
|
2596
|
+
execution = this.beginNormalizedExecution({ channelId, taskId: admission.input_message_id, inputMessageId: admission.input_message_id, inputText, activeTurn: active });
|
|
2597
|
+
}
|
|
2598
|
+
const result = await this.provider.controlSession(request, {
|
|
2599
|
+
signal: context.signal, execution: execution?.options,
|
|
2600
|
+
onCompletion: pending => {
|
|
2601
|
+
retained = true;
|
|
2602
|
+
const owner = active;
|
|
2603
|
+
const completion = pending.then(async (reply) => {
|
|
2604
|
+
if (!reply || !hasVisibleText(reply.text) || !(await this.canAcceptCompletedTurnOutput(channelId, owner)))
|
|
2605
|
+
return;
|
|
2606
|
+
await this.postTurnReply({ channelId, body: reply.text, inputMessageId: request.admission?.input_message_id, replyToId: request.admission?.input_message_id, taskId: request.admission.input_message_id, activeTurn: owner });
|
|
2607
|
+
}).catch(error => { this.logger.error('native command completion unavailable', { channelId, error: summarizeError(error) }); }).finally(release);
|
|
2608
|
+
this.trackActiveTurn(completion);
|
|
2609
|
+
},
|
|
2610
|
+
});
|
|
2611
|
+
return result.response;
|
|
2612
|
+
}
|
|
2613
|
+
catch (error) {
|
|
2614
|
+
this.logger.error('native session control unavailable', { channelId, error: summarizeError(error) });
|
|
2615
|
+
return response('unconfirmed', 'native_confirmation_unavailable');
|
|
2616
|
+
}
|
|
2617
|
+
finally {
|
|
2618
|
+
if (!retained)
|
|
2619
|
+
release();
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
async handleRetainedControl(request, context) {
|
|
2623
|
+
const response = (status, reason) => sessionControlResponse(request, status, reason);
|
|
2624
|
+
const connected = () => this.started && !this.controlPlaneClosed && this.selfAgentId === request.agent_id && !!context.isConnectionCurrent?.();
|
|
2625
|
+
let execution;
|
|
2626
|
+
let canPublish = () => false;
|
|
2627
|
+
let retained = false;
|
|
2628
|
+
try {
|
|
2629
|
+
const admission = request.admission;
|
|
2630
|
+
let inputText = null;
|
|
2631
|
+
if (request.operation.kind === 'execute_command') {
|
|
2632
|
+
if (!admission)
|
|
2633
|
+
return response('rejected', 'command_admission_required');
|
|
2634
|
+
inputText = await readDurableInputText(this.borgee, { channelId: request.channel_id, inputMessageId: admission.input_message_id, createdAt: admission.input_created_at });
|
|
2635
|
+
}
|
|
2636
|
+
if (!connected() || context.signal.aborted || Date.now() >= request.deadline_at)
|
|
2637
|
+
return response('unconfirmed', 'session_control_expired');
|
|
2638
|
+
const result = await this.provider.controlSession(request, {
|
|
2639
|
+
signal: context.signal, connectionSignal: context.connectionSignal, isConnectionCurrent: connected,
|
|
2640
|
+
activateExecution: nativeCurrent => {
|
|
2641
|
+
if (!admission)
|
|
2642
|
+
throw new Error('Native command has no admitted input.');
|
|
2643
|
+
canPublish = () => connected() && nativeCurrent();
|
|
2644
|
+
execution = this.beginNormalizedExecution({ channelId: request.channel_id, taskId: admission.input_message_id, inputMessageId: admission.input_message_id, inputText, publicationGuard: canPublish });
|
|
2645
|
+
return execution.options;
|
|
2646
|
+
},
|
|
2647
|
+
onCompletion: pending => {
|
|
2648
|
+
retained = true;
|
|
2649
|
+
const completion = pending.then(async (reply) => {
|
|
2650
|
+
if (!reply || !hasVisibleText(reply.text) || !canPublish())
|
|
2651
|
+
return;
|
|
2652
|
+
await this.postTurnReply({ channelId: request.channel_id, body: reply.text, inputMessageId: admission.input_message_id, replyToId: admission.input_message_id, taskId: admission.input_message_id });
|
|
2653
|
+
}).catch(error => this.logger.error('native command completion unavailable', { channelId: request.channel_id, error: summarizeError(error) })).finally(() => execution?.close());
|
|
2654
|
+
this.trackActiveTurn(completion);
|
|
2655
|
+
},
|
|
2656
|
+
});
|
|
2657
|
+
return result.response;
|
|
2658
|
+
}
|
|
2659
|
+
catch (error) {
|
|
2660
|
+
this.logger.error('native session control unavailable', { channelId: request.channel_id, error: summarizeError(error) });
|
|
2661
|
+
return response('unconfirmed', 'native_confirmation_unavailable');
|
|
2662
|
+
}
|
|
2663
|
+
finally {
|
|
2664
|
+
if (!retained)
|
|
2665
|
+
execution?.close();
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
async handleNativeInput(request, context) {
|
|
2669
|
+
const operation = request.operation;
|
|
2670
|
+
const admission = request.admission;
|
|
2671
|
+
const response = (status, reason) => sessionControlResponse(request, status, reason);
|
|
2672
|
+
if (operation.kind !== 'send_input' || !admission || !context.isConnectionCurrent || !this.provider.supportsSessionInput)
|
|
2673
|
+
return response('unsupported', 'native_input_not_supported');
|
|
2674
|
+
const connected = () => this.started && !this.controlPlaneClosed && this.selfAgentId === request.agent_id && context.isConnectionCurrent();
|
|
2675
|
+
let execution;
|
|
2676
|
+
let canPublish = () => false;
|
|
2677
|
+
let retained = false;
|
|
2678
|
+
try {
|
|
2679
|
+
const inputText = await readDurableInputText(this.borgee, { channelId: request.channel_id, inputMessageId: admission.input_message_id, createdAt: admission.input_created_at });
|
|
2680
|
+
if (inputText !== operation.text || !connected() || context.signal.aborted)
|
|
2681
|
+
return response('unconfirmed', 'native_input_admission_unavailable');
|
|
2682
|
+
const result = await this.provider.controlSession(request, {
|
|
2683
|
+
signal: context.signal, connectionSignal: context.connectionSignal, isConnectionCurrent: connected,
|
|
2684
|
+
activateExecution: async (nativeCurrent) => {
|
|
2685
|
+
canPublish = () => connected() && nativeCurrent();
|
|
2686
|
+
if (!canPublish())
|
|
2687
|
+
throw new Error('Native queued input connection ownership ended.');
|
|
2688
|
+
execution = this.beginNormalizedExecution({ channelId: request.channel_id, taskId: admission.input_message_id, inputMessageId: admission.input_message_id, inputText, publicationGuard: canPublish });
|
|
2689
|
+
return execution.options;
|
|
2690
|
+
},
|
|
2691
|
+
onCompletion: pending => {
|
|
2692
|
+
retained = true;
|
|
2693
|
+
const completion = pending.then(async (reply) => {
|
|
2694
|
+
if (!reply || !hasVisibleText(reply.text) || !canPublish())
|
|
2695
|
+
return;
|
|
2696
|
+
await this.postTurnReply({ channelId: request.channel_id, body: reply.text, inputMessageId: admission.input_message_id, replyToId: admission.input_message_id, taskId: admission.input_message_id });
|
|
2697
|
+
}).catch(error => this.logger.error('native input completion unavailable', { channelId: request.channel_id, error: summarizeError(error) })).finally(() => execution?.close());
|
|
2698
|
+
this.trackActiveTurn(completion);
|
|
2699
|
+
},
|
|
2700
|
+
});
|
|
2701
|
+
return result.response;
|
|
2702
|
+
}
|
|
2703
|
+
catch (error) {
|
|
2704
|
+
this.logger.error('native input confirmation unavailable', { channelId: request.channel_id, error: summarizeError(error) });
|
|
2705
|
+
return response('unconfirmed', 'native_confirmation_unavailable');
|
|
2706
|
+
}
|
|
2707
|
+
finally {
|
|
2708
|
+
if (!retained)
|
|
2709
|
+
execution?.close();
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
async postTurnReply(input, source) {
|
|
2713
|
+
const message = { channelId: input.channelId, body: input.body, replyToId: input.replyToId ?? input.activeTurn?.delivery?.replyToId };
|
|
2714
|
+
const posted = source ? await this.borgee.postMessage(message, source) : await this.borgee.postMessage(message);
|
|
2715
|
+
recordProtocolTrace({ stage: 'host.delivery', kind: 'correlation', correlation: { channelId: input.channelId, agentId: this.selfAgentId ?? undefined, inputMessageId: input.inputMessageId, taskId: input.taskId, turnExecutionId: input.activeTurn?.turnExecutionId ?? undefined, outputMessageId: posted.messageId }, payload: { delivery: 'posted' } });
|
|
2716
|
+
return posted;
|
|
2717
|
+
}
|
|
2718
|
+
beginNormalizedExecution(input) {
|
|
2719
|
+
const { channelId, taskId, activeTurn } = input;
|
|
2720
|
+
let execution;
|
|
2721
|
+
try {
|
|
2722
|
+
execution = this.executionTelemetry?.bind({ bindingId: taskId, channelId, inputMessageId: input.inputMessageId, grant: input.grant });
|
|
2723
|
+
}
|
|
2724
|
+
catch (error) {
|
|
2725
|
+
this.logger.error('normalized accounting binding unavailable', { error: summarizeError(error) });
|
|
2726
|
+
}
|
|
2727
|
+
let publisher;
|
|
2728
|
+
let statePublisher;
|
|
2729
|
+
let stateClosed = false;
|
|
2730
|
+
const owner = Symbol('normalized-state');
|
|
2731
|
+
const stateOwnerKey = input.publicationGuard ? JSON.stringify([channelId, taskId]) : channelId;
|
|
2732
|
+
this.normalizedStateOwners.set(stateOwnerKey, owner);
|
|
2733
|
+
const canPublishState = () => {
|
|
2734
|
+
const current = this.collaborationChannels.get(channelId)?.activeTurn;
|
|
2735
|
+
return !stateClosed && this.started && !this.controlPlaneClosed && this.normalizedStateOwners.get(stateOwnerKey) === owner && (input.publicationGuard ? input.publicationGuard() : !activeTurn?.superseded && (!current || current.generation === activeTurn?.generation));
|
|
2736
|
+
};
|
|
2737
|
+
return {
|
|
2738
|
+
options: {
|
|
2739
|
+
normalizedTaskId: taskId, normalizedInputMessageId: input.inputMessageId, normalizedInputText: input.inputText,
|
|
2740
|
+
normalizedStateSink: input.silent ? undefined : {
|
|
2741
|
+
snapshot: (snapshot, bindingId, timelineEpoch) => {
|
|
2742
|
+
if (!canPublishState())
|
|
2743
|
+
return false;
|
|
2744
|
+
statePublisher ??= new NormalizedActivityPublisher({ client: { reportActivity: value => { this.borgee.reportActivity?.(value); } }, channelId, taskId, agentId: snapshot.payload.id });
|
|
2745
|
+
statePublisher.publish(snapshot, Date.now(), snapshot.payload.providerId, snapshot.payload.capabilities, bindingId, timelineEpoch);
|
|
2746
|
+
return true;
|
|
2747
|
+
},
|
|
2748
|
+
sessionActivity: activity => { if (!canPublishState() || !this.borgee.reportActivity)
|
|
2749
|
+
return false; this.borgee.reportActivity({ channelId, activity }); return true; },
|
|
2750
|
+
observe: (observation, agentId, capabilities, bindingId, timelineEpoch) => {
|
|
2751
|
+
if (observation.event.type !== 'runtime_updated' || !canPublishState() || this.borgee.sessionBindingsSupported)
|
|
2752
|
+
return;
|
|
2753
|
+
statePublisher ??= new NormalizedActivityPublisher({ client: { reportActivity: value => { this.borgee.reportActivity?.(value); } }, channelId, taskId, agentId });
|
|
2754
|
+
statePublisher.observe(observation, capabilities, bindingId, timelineEpoch);
|
|
2755
|
+
},
|
|
2756
|
+
close: () => { stateClosed = true; statePublisher?.close(); if (this.normalizedStateOwners.get(stateOwnerKey) === owner)
|
|
2757
|
+
this.normalizedStateOwners.delete(stateOwnerKey); },
|
|
2758
|
+
},
|
|
2759
|
+
onNormalizedObservation: (event, agentId, observation, capabilities, bindingId, timelineEpoch, sessionPublished) => {
|
|
2760
|
+
if (input.publicationGuard && !input.publicationGuard())
|
|
2761
|
+
return;
|
|
2762
|
+
try {
|
|
2763
|
+
execution?.observe(event, observation?.occurredAt);
|
|
2764
|
+
}
|
|
2765
|
+
catch (error) {
|
|
2766
|
+
this.logger.error('normalized accounting rejected', { error: summarizeError(error) });
|
|
2767
|
+
}
|
|
2768
|
+
const acceptsOutput = !input.silent && this.canAcceptTurnOutput(channelId, activeTurn?.generation);
|
|
2769
|
+
recordProtocolTrace({ stage: 'host.dispatch', kind: 'correlation', correlation: { channelId, agentId, inputMessageId: input.inputMessageId, taskId, turnExecutionId: activeTurn?.turnExecutionId ?? undefined }, payload: { eventType: event.type }, outcome: acceptsOutput ? 'forwarded' : 'suppressed' });
|
|
2770
|
+
if (sessionPublished || !acceptsOutput || event.type === 'runtime_updated' && this.borgee.sessionBindingsSupported)
|
|
2771
|
+
return;
|
|
2772
|
+
publisher ??= new NormalizedActivityPublisher({ client: { reportActivity: value => { this.borgee.reportActivity?.(value); } }, channelId, taskId, agentId });
|
|
2773
|
+
publisher.observe(observation ?? event, capabilities, bindingId, timelineEpoch);
|
|
2774
|
+
},
|
|
2775
|
+
},
|
|
2776
|
+
close: () => { publisher?.close(); execution?.close(); },
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2312
2779
|
async executeTurn(msg, authorId, content, options) {
|
|
2313
2780
|
const channelId = msg.channel_id;
|
|
2314
2781
|
const activeTurn = options?.activeTurn;
|
|
@@ -2316,23 +2783,14 @@ export class AgentsHost {
|
|
|
2316
2783
|
const taskThreadContext = options?.taskThreadContext ?? { state: 'inactive' };
|
|
2317
2784
|
const silentTurn = options?.silent === true;
|
|
2318
2785
|
const deferDelivery = options?.deferDelivery === true;
|
|
2786
|
+
const selectedSourceCurrent = options?.selectedSession?.sourceCurrent;
|
|
2787
|
+
const selectedWriteSource = selectedSourceCurrent ? { isConnectionCurrent: selectedSourceCurrent } : undefined;
|
|
2319
2788
|
const collaborationTurnMode = activeTurn?.protocol
|
|
2320
2789
|
? 'protocol-managed'
|
|
2321
2790
|
: options?.kickoff
|
|
2322
2791
|
? 'silent-kickoff'
|
|
2323
2792
|
: undefined;
|
|
2324
2793
|
const turnExecutionId = activeTurn?.turnExecutionId ?? null;
|
|
2325
|
-
const stopTyping = silentTurn ? () => { } : this.borgee.startTyping(channelId);
|
|
2326
|
-
const collaborationGrounding = await this.buildCollaborationGrounding(authorId, options?.incomingAuthorKind, activeTurn, options?.kickoff);
|
|
2327
|
-
const publishesCollaborationDraft = !silentTurn &&
|
|
2328
|
-
!deferDelivery &&
|
|
2329
|
-
this.collaborationEnabled &&
|
|
2330
|
-
turnExecutionId != null &&
|
|
2331
|
-
activeTurn?.protocol === undefined;
|
|
2332
|
-
const supportsProviderProgress = this.provider.capabilities?.progressUpdates?.support === 'supported';
|
|
2333
|
-
const supportsBackgroundRunLifecycle = this.provider.capabilities?.backgroundRuns?.lifecycle === 'supported';
|
|
2334
|
-
const reportsProviderActivity = !silentTurn && supportsProviderProgress;
|
|
2335
|
-
const observeProviderProgress = supportsProviderProgress || supportsBackgroundRunLifecycle;
|
|
2336
2794
|
const turnContext = {
|
|
2337
2795
|
eventType: msg.type,
|
|
2338
2796
|
channelId,
|
|
@@ -2345,26 +2803,64 @@ export class AgentsHost {
|
|
|
2345
2803
|
};
|
|
2346
2804
|
const turnStartedAt = Date.now();
|
|
2347
2805
|
this.logger.debug('starting turn', turnContext);
|
|
2806
|
+
const executionTelemetry = this.provider.outputMode !== 'normalized' && msg.execution_grant
|
|
2807
|
+
? this.executionTelemetryByExecutionId.get(msg.execution_grant.executionId)
|
|
2808
|
+
: undefined;
|
|
2809
|
+
let executionSucceeded = false;
|
|
2810
|
+
let executionTelemetryStartFailed = false;
|
|
2348
2811
|
let reply;
|
|
2349
2812
|
let publicReplyText;
|
|
2350
2813
|
let visibleReplyBody;
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
?? buildHostedTurnContentParts({
|
|
2359
|
-
text: String(msg.content ?? msg.body ?? content),
|
|
2360
|
-
attachments: msg.attachments,
|
|
2361
|
-
});
|
|
2362
|
-
const activityReporter = silentTurn
|
|
2363
|
-
? undefined
|
|
2364
|
-
: this.beginTurnActivity(channelId, activeTurn?.generation);
|
|
2365
|
-
await this.ensureCompactionSnapshotLoaded(channelId);
|
|
2366
|
-
const compactionSnapshot = this.compactionSnapshotByChannel.get(channelId);
|
|
2814
|
+
let activityReporter;
|
|
2815
|
+
let typingLease;
|
|
2816
|
+
const normalizedTaskId = turnExecutionId ?? randomUUID();
|
|
2817
|
+
recordProtocolTrace({ stage: 'host.dispatch', kind: 'correlation',
|
|
2818
|
+
correlation: { channelId, agentId: this.selfAgentId ?? undefined, inputMessageId: msg.message_id, taskId: normalizedTaskId, turnExecutionId: turnExecutionId ?? undefined },
|
|
2819
|
+
payload: { silentTurn } });
|
|
2820
|
+
let normalizedExecution;
|
|
2367
2821
|
try {
|
|
2822
|
+
if (!silentTurn) {
|
|
2823
|
+
typingLease = startTypingLease(() => this.borgee.reportTyping(channelId));
|
|
2824
|
+
}
|
|
2825
|
+
const collaborationGrounding = await this.buildCollaborationGrounding(authorId, options?.incomingAuthorKind, activeTurn, options?.kickoff);
|
|
2826
|
+
const publishesCollaborationDraft = !silentTurn &&
|
|
2827
|
+
!deferDelivery &&
|
|
2828
|
+
this.collaborationEnabled &&
|
|
2829
|
+
turnExecutionId != null &&
|
|
2830
|
+
activeTurn?.protocol === undefined;
|
|
2831
|
+
const supportsProviderProgress = this.provider.capabilities?.progressUpdates?.support === 'supported';
|
|
2832
|
+
const supportsBackgroundRunLifecycle = this.provider.capabilities?.backgroundRuns?.lifecycle === 'supported';
|
|
2833
|
+
const reportsProviderActivity = !silentTurn && supportsProviderProgress;
|
|
2834
|
+
const observeProviderProgress = supportsProviderProgress || supportsBackgroundRunLifecycle;
|
|
2835
|
+
const taskThreadCollaborationContract = this.resolveTaskThreadCollaborationContractForTurn(msg, taskThreadContext);
|
|
2836
|
+
const collaborationOutcome = this.collaborationOutcomeModelEnabled
|
|
2837
|
+
? this.collaborationOutcomeByChannel.get(channelId)
|
|
2838
|
+
: undefined;
|
|
2839
|
+
const missedCollaborationDiagnostic = this.resolveMissedCollaborationDiagnosticForTurn(channelId);
|
|
2840
|
+
const collaborationCapabilities = this.resolveCollaborationCapabilityDeclarationForTurn(taskThreadContext);
|
|
2841
|
+
const incomingParts = options?.incomingParts
|
|
2842
|
+
?? buildHostedTurnContentParts({
|
|
2843
|
+
text: String(msg.content ?? msg.body ?? content),
|
|
2844
|
+
attachments: msg.attachments,
|
|
2845
|
+
});
|
|
2846
|
+
activityReporter = silentTurn
|
|
2847
|
+
? undefined
|
|
2848
|
+
: this.beginTurnActivity(channelId, activeTurn?.generation);
|
|
2849
|
+
normalizedExecution = this.provider.outputMode === 'normalized' ? this.beginNormalizedExecution({ channelId, taskId: normalizedTaskId, inputMessageId: msg.message_id, inputText: await readNormalizedInputText(this.borgee, msg), grant: msg.execution_grant, activeTurn, silent: silentTurn, ...(selectedSourceCurrent ? { publicationGuard: selectedSourceCurrent } : {}) }) : undefined;
|
|
2850
|
+
await this.ensureCompactionSnapshotLoaded(channelId);
|
|
2851
|
+
const compactionSnapshot = this.compactionSnapshotByChannel.get(channelId);
|
|
2852
|
+
if (executionTelemetry) {
|
|
2853
|
+
try {
|
|
2854
|
+
await executionTelemetry.start();
|
|
2855
|
+
}
|
|
2856
|
+
catch (error) {
|
|
2857
|
+
executionTelemetryStartFailed = true;
|
|
2858
|
+
this.logger.error('execution telemetry start failed', {
|
|
2859
|
+
error: summarizeError(error),
|
|
2860
|
+
});
|
|
2861
|
+
throw error;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2368
2864
|
reply = await this.provider.generateReply({
|
|
2369
2865
|
agentName: this.config.agent.agentName,
|
|
2370
2866
|
provider: this.config.agent.provider,
|
|
@@ -2374,6 +2870,7 @@ export class AgentsHost {
|
|
|
2374
2870
|
incomingParts,
|
|
2375
2871
|
incomingEventKind: msg.type,
|
|
2376
2872
|
incomingMessageType: msg.message_type,
|
|
2873
|
+
...(options?.selectedSession ? { selectedSession: options.selectedSession } : {}),
|
|
2377
2874
|
collaboration: turnExecutionId || options?.kickoff
|
|
2378
2875
|
? {
|
|
2379
2876
|
enabled: this.collaborationEnabled,
|
|
@@ -2396,7 +2893,8 @@ export class AgentsHost {
|
|
|
2396
2893
|
? {
|
|
2397
2894
|
taskAssignmentContextOverride: {
|
|
2398
2895
|
active: true,
|
|
2399
|
-
...(taskThreadContext.state === 'active'
|
|
2896
|
+
...((taskThreadContext.state === 'active'
|
|
2897
|
+
|| taskThreadContext.state === 'recovered') && taskThreadContext.currentTaskId
|
|
2400
2898
|
? { currentTaskId: taskThreadContext.currentTaskId }
|
|
2401
2899
|
: {}),
|
|
2402
2900
|
},
|
|
@@ -2405,42 +2903,62 @@ export class AgentsHost {
|
|
|
2405
2903
|
: { taskAssignmentContextOverridePersistence: 'ephemeral' }),
|
|
2406
2904
|
}
|
|
2407
2905
|
: {}),
|
|
2408
|
-
}, observeProviderProgress
|
|
2906
|
+
}, observeProviderProgress || executionTelemetry || this.provider.outputMode === 'normalized'
|
|
2409
2907
|
? {
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
this.
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
}
|
|
2420
|
-
}
|
|
2421
|
-
if (!this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
if (supportsProviderProgress && update.type === 'compaction') {
|
|
2425
|
-
this.noteCompactionProgress(channelId, update.compaction, {
|
|
2426
|
-
turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
|
|
2427
|
-
});
|
|
2428
|
-
return;
|
|
2908
|
+
...normalizedExecution?.options,
|
|
2909
|
+
...(executionTelemetry
|
|
2910
|
+
? {
|
|
2911
|
+
onUsage: (usage) => {
|
|
2912
|
+
void executionTelemetry.usage(this.config.agent.provider, usage);
|
|
2913
|
+
},
|
|
2914
|
+
onExecutionContinuation: (completion) => {
|
|
2915
|
+
executionTelemetry.hold(completion);
|
|
2916
|
+
},
|
|
2429
2917
|
}
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2918
|
+
: {}),
|
|
2919
|
+
...(observeProviderProgress
|
|
2920
|
+
? {
|
|
2921
|
+
onProgress: (update) => {
|
|
2922
|
+
if (supportsBackgroundRunLifecycle) {
|
|
2923
|
+
this.backgroundRuns.observe(channelId, update);
|
|
2924
|
+
if (update.type === 'activity' && update.activity.background === true) {
|
|
2925
|
+
this.borgee.reportActivity?.({
|
|
2926
|
+
channelId,
|
|
2927
|
+
activity: { shape: 'activity', ...update.activity },
|
|
2928
|
+
});
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
if ((selectedSourceCurrent && !selectedSourceCurrent()) || !this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
if (update.type === 'text'
|
|
2936
|
+
&& update.stream === 'answer'
|
|
2937
|
+
&& hasVisibleText(update.text)) {
|
|
2938
|
+
typingLease?.renew();
|
|
2939
|
+
}
|
|
2940
|
+
if (supportsProviderProgress && update.type === 'compaction') {
|
|
2941
|
+
this.noteCompactionProgress(channelId, update.compaction, {
|
|
2942
|
+
turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
|
|
2943
|
+
});
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
const reportedActivity = toReportedActivity(update);
|
|
2947
|
+
if (this.provider.outputMode !== 'normalized' && reportsProviderActivity && reportedActivity) {
|
|
2948
|
+
activityReporter?.progress(reportedActivity);
|
|
2949
|
+
}
|
|
2950
|
+
if (!supportsProviderProgress ||
|
|
2951
|
+
update.type !== 'text' ||
|
|
2952
|
+
update.stream !== 'answer') {
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
if (!publishesCollaborationDraft) {
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
this.publishCollaborationDraft(channelId, activeTurn, update.text);
|
|
2959
|
+
},
|
|
2441
2960
|
}
|
|
2442
|
-
|
|
2443
|
-
},
|
|
2961
|
+
: {}),
|
|
2444
2962
|
}
|
|
2445
2963
|
: undefined);
|
|
2446
2964
|
publicReplyText = resolvePublicReplyText(reply);
|
|
@@ -2458,7 +2976,7 @@ export class AgentsHost {
|
|
|
2458
2976
|
observedAt: Date.now(),
|
|
2459
2977
|
turnExecutionId: activeTurn?.turnExecutionId ?? undefined,
|
|
2460
2978
|
});
|
|
2461
|
-
if (!(await this.canAcceptCompletedTurnOutput(channelId, activeTurn))) {
|
|
2979
|
+
if ((selectedSourceCurrent && !selectedSourceCurrent()) || !(await this.canAcceptCompletedTurnOutput(channelId, activeTurn))) {
|
|
2462
2980
|
if (activeTurn?.turnExecutionId) {
|
|
2463
2981
|
this.clearCollaborationDraft(channelId, {
|
|
2464
2982
|
generation: activeTurn.generation,
|
|
@@ -2468,6 +2986,7 @@ export class AgentsHost {
|
|
|
2468
2986
|
return { reply, stale: true, completed: false };
|
|
2469
2987
|
}
|
|
2470
2988
|
if (silentTurn) {
|
|
2989
|
+
executionSucceeded = true;
|
|
2471
2990
|
this.logger.debug('completed turn', {
|
|
2472
2991
|
...turnContext,
|
|
2473
2992
|
durationMs: Date.now() - turnStartedAt,
|
|
@@ -2488,6 +3007,7 @@ export class AgentsHost {
|
|
|
2488
3007
|
};
|
|
2489
3008
|
}
|
|
2490
3009
|
if (deferDelivery) {
|
|
3010
|
+
executionSucceeded = true;
|
|
2491
3011
|
this.logger.debug('completed turn', {
|
|
2492
3012
|
...turnContext,
|
|
2493
3013
|
durationMs: Date.now() - turnStartedAt,
|
|
@@ -2508,11 +3028,7 @@ export class AgentsHost {
|
|
|
2508
3028
|
};
|
|
2509
3029
|
}
|
|
2510
3030
|
if (this.started && !this.controlPlaneClosed && hasVisibleText(publicReplyText)) {
|
|
2511
|
-
const posted = await this.
|
|
2512
|
-
channelId,
|
|
2513
|
-
body: visibleReplyBody,
|
|
2514
|
-
replyToId: activeTurn?.delivery?.replyToId,
|
|
2515
|
-
});
|
|
3031
|
+
const posted = await this.postTurnReply({ channelId, body: visibleReplyBody, inputMessageId: msg.message_id, taskId: normalizedTaskId, activeTurn }, selectedWriteSource);
|
|
2516
3032
|
this.logger.debug('completed turn', {
|
|
2517
3033
|
...turnContext,
|
|
2518
3034
|
durationMs: Date.now() - turnStartedAt,
|
|
@@ -2524,6 +3040,7 @@ export class AgentsHost {
|
|
|
2524
3040
|
if (publishesCollaborationDraft) {
|
|
2525
3041
|
this.finalizeCollaborationDraft(channelId, activeTurn, posted.messageId, visibleReplyBody);
|
|
2526
3042
|
}
|
|
3043
|
+
executionSucceeded = true;
|
|
2527
3044
|
return {
|
|
2528
3045
|
reply,
|
|
2529
3046
|
deliveredMessageId: posted.messageId,
|
|
@@ -2547,6 +3064,7 @@ export class AgentsHost {
|
|
|
2547
3064
|
turnExecutionId: activeTurn.turnExecutionId,
|
|
2548
3065
|
});
|
|
2549
3066
|
}
|
|
3067
|
+
executionSucceeded = true;
|
|
2550
3068
|
return {
|
|
2551
3069
|
reply,
|
|
2552
3070
|
deliveredMessageId: null,
|
|
@@ -2565,6 +3083,13 @@ export class AgentsHost {
|
|
|
2565
3083
|
turnExecutionId: activeTurn.turnExecutionId,
|
|
2566
3084
|
});
|
|
2567
3085
|
}
|
|
3086
|
+
if (executionTelemetryStartFailed) {
|
|
3087
|
+
throw error;
|
|
3088
|
+
}
|
|
3089
|
+
if (error instanceof ProviderInputRejectedError && !silentTurn && !deferDelivery && (!selectedSourceCurrent || selectedSourceCurrent()) && this.canAcceptTurnOutput(channelId, activeTurn?.generation)) {
|
|
3090
|
+
const posted = await this.postTurnReply({ channelId, body: error.publicReplyText, replyToId: activeTurn?.delivery?.replyToId, inputMessageId: msg.message_id, taskId: normalizedTaskId, activeTurn }, selectedWriteSource);
|
|
3091
|
+
return { reply: { text: error.publicReplyText }, publicReplyText: error.publicReplyText, visibleReplyBody: error.publicReplyText, deliveredMessageId: posted.messageId, deliveryState: 'posted', stale: false, completed: false };
|
|
3092
|
+
}
|
|
2568
3093
|
if (error instanceof ProviderTurnCancelledError) {
|
|
2569
3094
|
const cancelledReplyText = error.publicReplyText?.trim();
|
|
2570
3095
|
if (!silentTurn &&
|
|
@@ -2572,16 +3097,13 @@ export class AgentsHost {
|
|
|
2572
3097
|
typeof cancelledReplyText === 'string' &&
|
|
2573
3098
|
cancelledReplyText.length > 0 &&
|
|
2574
3099
|
this.started &&
|
|
2575
|
-
!this.controlPlaneClosed
|
|
3100
|
+
!this.controlPlaneClosed &&
|
|
3101
|
+
(!selectedSourceCurrent || selectedSourceCurrent())) {
|
|
2576
3102
|
reply = { text: cancelledReplyText };
|
|
2577
3103
|
publicReplyText = cancelledReplyText;
|
|
2578
3104
|
visibleReplyBody = this.resolveVisibleTurnBody(activeTurn, reply, publicReplyText);
|
|
2579
3105
|
try {
|
|
2580
|
-
const posted = await this.
|
|
2581
|
-
channelId,
|
|
2582
|
-
body: visibleReplyBody,
|
|
2583
|
-
replyToId: activeTurn?.delivery?.replyToId,
|
|
2584
|
-
});
|
|
3106
|
+
const posted = await this.postTurnReply({ channelId, body: visibleReplyBody, replyToId: activeTurn?.delivery?.replyToId, inputMessageId: msg.message_id, taskId: normalizedTaskId, activeTurn }, selectedWriteSource);
|
|
2585
3107
|
this.logger.debug('posted cancelled turn feedback', {
|
|
2586
3108
|
...turnContext,
|
|
2587
3109
|
durationMs: Date.now() - turnStartedAt,
|
|
@@ -2642,8 +3164,12 @@ export class AgentsHost {
|
|
|
2642
3164
|
};
|
|
2643
3165
|
}
|
|
2644
3166
|
finally {
|
|
2645
|
-
|
|
3167
|
+
if (executionTelemetry) {
|
|
3168
|
+
executionTelemetry.noteAttempt(executionSucceeded);
|
|
3169
|
+
}
|
|
3170
|
+
typingLease?.stop();
|
|
2646
3171
|
activityReporter?.end();
|
|
3172
|
+
normalizedExecution?.close();
|
|
2647
3173
|
}
|
|
2648
3174
|
}
|
|
2649
3175
|
/**
|