@xenosystem/agent-interface-host 0.1.4 → 0.1.6
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/acp/AcpAgentRegistryService.d.ts +43 -1
- package/dist/acp/AcpAgentRegistryService.d.ts.map +1 -1
- package/dist/acp/AcpAgentRegistryService.js +200 -1
- package/dist/acp/AcpAgentRegistryService.js.map +1 -1
- package/dist/acp/AcpAgentTurnExecutionAdapter.d.ts +66 -0
- package/dist/acp/AcpAgentTurnExecutionAdapter.d.ts.map +1 -1
- package/dist/acp/AcpAgentTurnExecutionAdapter.js +182 -1
- package/dist/acp/AcpAgentTurnExecutionAdapter.js.map +1 -1
- package/dist/acp/AcpPermissionBridge.d.ts.map +1 -1
- package/dist/acp/AcpPermissionBridge.js +3 -3
- package/dist/acp/AcpPermissionBridge.js.map +1 -1
- package/dist/acp/AcpProcessSupervisor.d.ts +81 -0
- package/dist/acp/AcpProcessSupervisor.d.ts.map +1 -1
- package/dist/acp/AcpProcessSupervisor.js +176 -2
- package/dist/acp/AcpProcessSupervisor.js.map +1 -1
- package/dist/acp/AcpProviderCatalogService.d.ts.map +1 -1
- package/dist/acp/AcpProviderCatalogService.js +68 -2
- package/dist/acp/AcpProviderCatalogService.js.map +1 -1
- package/dist/acp/AcpRuntimeEventMapper.d.ts.map +1 -1
- package/dist/acp/AcpRuntimeEventMapper.js +9 -0
- package/dist/acp/AcpRuntimeEventMapper.js.map +1 -1
- package/dist/acp/AcpSessionService.d.ts +36 -0
- package/dist/acp/AcpSessionService.d.ts.map +1 -1
- package/dist/acp/AcpSessionService.js +57 -1
- package/dist/acp/AcpSessionService.js.map +1 -1
- package/dist/acp/DurableAcpPermissionPolicyStore.d.ts.map +1 -1
- package/dist/acp/DurableAcpPermissionPolicyStore.js +5 -3
- package/dist/acp/DurableAcpPermissionPolicyStore.js.map +1 -1
- package/dist/acp/acpRedaction.d.ts.map +1 -1
- package/dist/acp/acpRedaction.js +2 -0
- package/dist/acp/acpRedaction.js.map +1 -1
- package/dist/acp/acpTypes.d.ts +111 -1
- package/dist/acp/acpTypes.d.ts.map +1 -1
- package/dist/acp/acpTypes.js +21 -1
- package/dist/acp/acpTypes.js.map +1 -1
- package/dist/index.d.ts +108 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +236 -11
- package/dist/index.js.map +1 -1
- package/dist/isolation/CommandExecution.d.ts +7 -12
- package/dist/isolation/CommandExecution.d.ts.map +1 -1
- package/dist/isolation/CommandExecution.js +7 -12
- package/dist/isolation/CommandExecution.js.map +1 -1
- package/dist/isolation/CommandStream.d.ts +100 -0
- package/dist/isolation/CommandStream.d.ts.map +1 -0
- package/dist/isolation/CommandStream.js +47 -0
- package/dist/isolation/CommandStream.js.map +1 -0
- package/dist/isolation/RunExecutionEnvironment.d.ts +15 -0
- package/dist/isolation/RunExecutionEnvironment.d.ts.map +1 -1
- package/dist/isolation/RunExecutionEnvironment.js +45 -9
- package/dist/isolation/RunExecutionEnvironment.js.map +1 -1
- package/dist/isolation/RunIsolationService.d.ts +10 -5
- package/dist/isolation/RunIsolationService.d.ts.map +1 -1
- package/dist/isolation/RunIsolationService.js +7 -3
- package/dist/isolation/RunIsolationService.js.map +1 -1
- package/dist/isolation/SandboxCapabilities.d.ts +28 -0
- package/dist/isolation/SandboxCapabilities.d.ts.map +1 -0
- package/dist/isolation/SandboxCapabilities.js +33 -0
- package/dist/isolation/SandboxCapabilities.js.map +1 -0
- package/dist/isolation/SandboxCommandExecutor.d.ts +4 -7
- package/dist/isolation/SandboxCommandExecutor.d.ts.map +1 -1
- package/dist/isolation/SandboxCommandExecutor.js +6 -7
- package/dist/isolation/SandboxCommandExecutor.js.map +1 -1
- package/dist/isolation/SandboxCommandStream.d.ts +38 -0
- package/dist/isolation/SandboxCommandStream.d.ts.map +1 -0
- package/dist/isolation/SandboxCommandStream.js +232 -0
- package/dist/isolation/SandboxCommandStream.js.map +1 -0
- package/dist/isolation/shellArgv.d.ts +15 -0
- package/dist/isolation/shellArgv.d.ts.map +1 -0
- package/dist/isolation/shellArgv.js +31 -0
- package/dist/isolation/shellArgv.js.map +1 -0
- package/dist/isolation/useIdempotency.d.ts +19 -0
- package/dist/isolation/useIdempotency.d.ts.map +1 -0
- package/dist/isolation/useIdempotency.js +57 -0
- package/dist/isolation/useIdempotency.js.map +1 -0
- package/dist/localRpcTransport.d.ts +18 -0
- package/dist/localRpcTransport.d.ts.map +1 -1
- package/dist/localRpcTransport.js +38 -4
- package/dist/localRpcTransport.js.map +1 -1
- package/dist/node.d.ts +19 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +19 -0
- package/dist/node.js.map +1 -1
- package/dist/observation/XenoUseRunObserver.d.ts +20 -0
- package/dist/observation/XenoUseRunObserver.d.ts.map +1 -1
- package/dist/observation/XenoUseRunObserver.js +27 -13
- package/dist/observation/XenoUseRunObserver.js.map +1 -1
- package/dist/runtime/RuntimeTimelineProjection.d.ts +2 -2
- package/dist/runtime/RuntimeTimelineProjection.d.ts.map +1 -1
- package/dist/runtime/RuntimeTimelineProjection.js +196 -1
- package/dist/runtime/RuntimeTimelineProjection.js.map +1 -1
- package/dist/sdk/DurablePendingInjectionStore.d.ts +24 -0
- package/dist/sdk/DurablePendingInjectionStore.d.ts.map +1 -0
- package/dist/sdk/DurablePendingInjectionStore.js +103 -0
- package/dist/sdk/DurablePendingInjectionStore.js.map +1 -0
- package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.d.ts +81 -1
- package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.d.ts.map +1 -1
- package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.js +122 -3
- package/dist/sdk/SdkNativeAgentTurnExecutionAdapter.js.map +1 -1
- package/dist/sdk/pendingInjections.d.ts +83 -0
- package/dist/sdk/pendingInjections.d.ts.map +1 -0
- package/dist/sdk/pendingInjections.js +79 -0
- package/dist/sdk/pendingInjections.js.map +1 -0
- package/dist/session/AgentSessionService.d.ts +34 -0
- package/dist/session/AgentSessionService.d.ts.map +1 -1
- package/dist/session/AgentSessionService.js +85 -1
- package/dist/session/AgentSessionService.js.map +1 -1
- package/dist/session/nativeTuiSessionPty.d.ts +40 -0
- package/dist/session/nativeTuiSessionPty.d.ts.map +1 -0
- package/dist/session/nativeTuiSessionPty.js +142 -0
- package/dist/session/nativeTuiSessionPty.js.map +1 -0
- package/dist/session/nodePtyLoader.d.ts +23 -0
- package/dist/session/nodePtyLoader.d.ts.map +1 -0
- package/dist/session/nodePtyLoader.js +41 -0
- package/dist/session/nodePtyLoader.js.map +1 -0
- package/dist/session/renderSupport.d.ts +34 -0
- package/dist/session/renderSupport.d.ts.map +1 -1
- package/dist/session/renderSupport.js +39 -1
- package/dist/session/renderSupport.js.map +1 -1
- package/dist/workspace/WorkspaceHostService.d.ts +27 -2
- package/dist/workspace/WorkspaceHostService.d.ts.map +1 -1
- package/dist/workspace/WorkspaceHostService.js +221 -25
- package/dist/workspace/WorkspaceHostService.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { AgentHostProtocolError, XENO_AGENT_HOST_MIN_PROTOCOL_VERSION, XENO_AGEN
|
|
|
8
8
|
const MAX_FLEET_COLLISIONS = 500;
|
|
9
9
|
const MAX_QUEUED_INSTRUCTIONS = 50;
|
|
10
10
|
const MAX_QUEUED_INSTRUCTION_LENGTH = 20_000;
|
|
11
|
-
import { cloneProvider, isDevelopmentProviderReady, missingDevelopmentCapabilities, resolveAgentRuntimeSelection, renderWorkspaceKnowledge, selectAvailableProviders, withHostBackedCapabilities, planFanOut, routeByCapability, } from '@xenosystem/agent-interface-core';
|
|
11
|
+
import { cloneProvider, isDevelopmentProviderReady, missingDevelopmentCapabilities, resolveAgentRuntimeSelection, renderWorkspaceKnowledge, selectAvailableProviders, selectSessionRenderMode, withHostBackedCapabilities, planFanOut, routeByCapability, } from '@xenosystem/agent-interface-core';
|
|
12
12
|
import { InMemoryInterruptedTurnStore, } from './turns/interruptedTurns.js';
|
|
13
13
|
import { evaluateTurnAdmission, resolveTurnAdmissionPolicy, } from './turns/turnAdmission.js';
|
|
14
14
|
import { boundSubagentWorkspace } from './subagents/subagentWorkspaceAuthority.js';
|
|
@@ -23,6 +23,7 @@ import { IntakeService } from './intake/IntakeService.js';
|
|
|
23
23
|
import { WorkspaceKnowledgeService } from './knowledge/WorkspaceKnowledgeService.js';
|
|
24
24
|
import { AgentSessionService } from './session/AgentSessionService.js';
|
|
25
25
|
import { createBufferedSessionPty } from './session/bufferedSessionPty.js';
|
|
26
|
+
import { createNativeTuiSessionPty } from './session/nativeTuiSessionPty.js';
|
|
26
27
|
import { renderSupportFor } from './session/renderSupport.js';
|
|
27
28
|
import { createRuntimeEventSessionChannel } from './session/runtimeEventChannel.js';
|
|
28
29
|
import { answerChooseDirectoryRequest } from './agent/chooseDirectoryBridge.js';
|
|
@@ -74,6 +75,8 @@ export class AgentHostCoordinator {
|
|
|
74
75
|
runIsolationService;
|
|
75
76
|
runObservationBinder;
|
|
76
77
|
sessionService;
|
|
78
|
+
nativeTuiLane;
|
|
79
|
+
acpAgentRegistry;
|
|
77
80
|
intakeService;
|
|
78
81
|
knowledgeService;
|
|
79
82
|
acpApprovalService;
|
|
@@ -95,6 +98,18 @@ export class AgentHostCoordinator {
|
|
|
95
98
|
*/
|
|
96
99
|
turnTimers = new Map();
|
|
97
100
|
activeTurns = new Map();
|
|
101
|
+
/**
|
|
102
|
+
* The lane a conversation last ran on, kept AFTER the turn ends.
|
|
103
|
+
*
|
|
104
|
+
* 🔴 `activeTurns` cannot answer this and that is not an oversight — it is keyed by
|
|
105
|
+
* `(conversationId, requestId)` because everything it serves is turn-scoped. Pin-forward
|
|
106
|
+
* (§2.2) is normally a BETWEEN-turns act, so resolving its target from `activeTurns` would make
|
|
107
|
+
* the feature work only during the one window where the user is least likely to want it.
|
|
108
|
+
*
|
|
109
|
+
* ⚠️ Deliberately never cleared on turn end. "Which agent is this conversation running on" stays
|
|
110
|
+
* true between turns; clearing it would reintroduce the turn scope this map exists to escape.
|
|
111
|
+
*/
|
|
112
|
+
conversationAdapters = new Map();
|
|
98
113
|
commandResults = new Map();
|
|
99
114
|
/**
|
|
100
115
|
* Instructions stacked while a turn is running, per conversation.
|
|
@@ -115,7 +130,7 @@ export class AgentHostCoordinator {
|
|
|
115
130
|
providerCatalogFingerprint = '';
|
|
116
131
|
providerCatalogRefresh = null;
|
|
117
132
|
constructor(options) {
|
|
118
|
-
const { providers, providerCatalogLoader, runtimeEvents, runtimeEventRepository, stateRepository, workspaceService, interactionService, permissionPolicyService, interruptedTurnStore, turnAdmissionPolicy, turnAbandonGraceMs, sdkControlService, engineeringControlService, turnExecutionAdapters, coordinationService, runObservationService, runIsolationService, runObservationBinder, sessionService, intakeService, knowledgeService, acpApprovalService, subagentRuns, resolveRunId, ...hostOptions } = options;
|
|
133
|
+
const { providers, providerCatalogLoader, runtimeEvents, runtimeEventRepository, stateRepository, workspaceService, interactionService, permissionPolicyService, interruptedTurnStore, turnAdmissionPolicy, turnAbandonGraceMs, sdkControlService, engineeringControlService, turnExecutionAdapters, coordinationService, runObservationService, runIsolationService, runObservationBinder, sessionService, nativeTuiLane, acpAgentRegistry, intakeService, knowledgeService, acpApprovalService, subagentRuns, resolveRunId, ...hostOptions } = options;
|
|
119
134
|
if (runtimeEventRepository && runtimeEvents && runtimeEvents.length > 0) {
|
|
120
135
|
throw new Error('Seeded runtimeEvents cannot be combined with a durable runtimeEventRepository.');
|
|
121
136
|
}
|
|
@@ -187,6 +202,8 @@ export class AgentHostCoordinator {
|
|
|
187
202
|
// Counts and completeness only — see the contract's `run.observation`.
|
|
188
203
|
this.runObservationService?.setEventSink((summary) => this.publish('run.observation', summary));
|
|
189
204
|
this.sessionService = sessionService;
|
|
205
|
+
this.nativeTuiLane = nativeTuiLane;
|
|
206
|
+
this.acpAgentRegistry = acpAgentRegistry;
|
|
190
207
|
this.intakeService = intakeService;
|
|
191
208
|
this.knowledgeService = knowledgeService;
|
|
192
209
|
// Session lifecycle on the feed: ids, modes and viewer counts — never PTY
|
|
@@ -651,8 +668,38 @@ export class AgentHostCoordinator {
|
|
|
651
668
|
if (!service)
|
|
652
669
|
return;
|
|
653
670
|
try {
|
|
654
|
-
const support = renderSupportFor(provider, {
|
|
655
|
-
|
|
671
|
+
const support = renderSupportFor(provider, {
|
|
672
|
+
adapters: [...this.turnExecutionAdapters.values()],
|
|
673
|
+
// The seam `renderSupport.ts` declares. Undefined falls back to
|
|
674
|
+
// `noNativeTuiLane`, so the default is unchanged.
|
|
675
|
+
// The predicate is DERIVED from the command, so support cannot be
|
|
676
|
+
// claimed for a provider we could not actually launch.
|
|
677
|
+
...(this.nativeTuiLane
|
|
678
|
+
? { nativeTuiLane: (candidate) => Boolean(this.nativeTuiLane?.(candidate)) }
|
|
679
|
+
: {}),
|
|
680
|
+
});
|
|
681
|
+
/**
|
|
682
|
+
* 🔴 Ask which mode before building the channel, because the two modes
|
|
683
|
+
* are EXCLUSIVE and this call site used to build both.
|
|
684
|
+
*
|
|
685
|
+
* `open()` refuses `native-tui` when a protocol channel is present — a
|
|
686
|
+
* CLI in interactive TUI mode is not also serving ACP. Passing `protocol`
|
|
687
|
+
* unconditionally was harmless only while `nativeTuiLane` was hardcoded
|
|
688
|
+
* false: the moment a lane said true, `selectSessionRenderMode` would
|
|
689
|
+
* pick `native-tui` and this site would hand it the one thing that makes
|
|
690
|
+
* it illegal, so EVERY session on that provider would be refused with
|
|
691
|
+
* `protocol_channel_forbidden`.
|
|
692
|
+
*
|
|
693
|
+
* That is the exact failure `renderSupport.ts` promised could not happen
|
|
694
|
+
* — "the mode becomes available by construction". Construction reached as
|
|
695
|
+
* far as `renderSupportFor` and stopped one caller short.
|
|
696
|
+
*
|
|
697
|
+
* ⚠️ This calls the SAME exported selector `open()` calls, rather than
|
|
698
|
+
* restating its rule. Two callers of one function cannot drift; two
|
|
699
|
+
* copies of a rule can.
|
|
700
|
+
*/
|
|
701
|
+
const selection = selectSessionRenderMode(support);
|
|
702
|
+
const createProtocol = () => createRuntimeEventSessionChannel({
|
|
656
703
|
runId,
|
|
657
704
|
subscribe: (listener) => this.subscribe((event) => {
|
|
658
705
|
if (event.type !== 'runtime.event.appended')
|
|
@@ -662,23 +709,94 @@ export class AgentHostCoordinator {
|
|
|
662
709
|
listener(payload.event);
|
|
663
710
|
}),
|
|
664
711
|
});
|
|
712
|
+
const protocol = selection.ok && selection.mode === 'acp' ? createProtocol() : undefined;
|
|
713
|
+
/**
|
|
714
|
+
* 🔴 The PTY is chosen by MODE, and the two are not interchangeable.
|
|
715
|
+
*
|
|
716
|
+
* `acp` gets a render target — our drawing of the structured stream, with
|
|
717
|
+
* a `write` that THROWS because a keystroke reaching it means a mode rule
|
|
718
|
+
* broke. `native-tui` gets a real terminal with the vendor's own binary
|
|
719
|
+
* inside it, where a write is a keystroke and must land.
|
|
720
|
+
*
|
|
721
|
+
* Handing the wrong one over is the single most likely way this mode goes
|
|
722
|
+
* wrong, which is why the buffered pty was built to fail loudly rather
|
|
723
|
+
* than no-op.
|
|
724
|
+
*/
|
|
725
|
+
const spawnRequest = selection.ok && selection.mode === 'native-tui'
|
|
726
|
+
? this.nativeTuiLane?.(provider)
|
|
727
|
+
: undefined;
|
|
665
728
|
const opened = service.open({
|
|
666
729
|
sessionId: runId,
|
|
667
730
|
runId,
|
|
668
731
|
support,
|
|
669
|
-
pty: createBufferedSessionPty(),
|
|
670
|
-
protocol,
|
|
732
|
+
pty: spawnRequest ? createNativeTuiSessionPty(spawnRequest) : createBufferedSessionPty(),
|
|
733
|
+
...(protocol ? { protocol } : {}),
|
|
734
|
+
/**
|
|
735
|
+
* Passed even when a channel was built above, because `session.setMode`
|
|
736
|
+
* CLOSES the channel on the way to `native-tui` and a closed one cannot
|
|
737
|
+
* be revived. Without the factory the switch would be one-way, and a
|
|
738
|
+
* user who tried the agent's own TUI could never get the structured
|
|
739
|
+
* view back.
|
|
740
|
+
*/
|
|
741
|
+
createProtocol,
|
|
671
742
|
});
|
|
672
743
|
// A refusal is the substrate doing its job — an unexecutable provider has
|
|
673
744
|
// nothing to render. Close the channel we built for it rather than leaving
|
|
674
745
|
// a subscription attached to a session that never existed.
|
|
675
746
|
if (!opened.ok)
|
|
676
|
-
protocol
|
|
747
|
+
protocol?.close();
|
|
677
748
|
}
|
|
678
749
|
catch {
|
|
679
750
|
// Contained: see above. The run proceeds without a session.
|
|
680
751
|
}
|
|
681
752
|
}
|
|
753
|
+
/**
|
|
754
|
+
* ADE §2.8's switch. The substrate has always accepted a preference; until
|
|
755
|
+
* `session.setMode` existed, no client could express one.
|
|
756
|
+
*/
|
|
757
|
+
/**
|
|
758
|
+
* The slice of the ACP registry this host needs — a PORT, not the service.
|
|
759
|
+
*
|
|
760
|
+
* Declared structurally so `packages/host`'s public surface does not drag the
|
|
761
|
+
* registry's own types across the protocol boundary, and so a composition can
|
|
762
|
+
* supply something else entirely.
|
|
763
|
+
*/
|
|
764
|
+
listAcpAgents() {
|
|
765
|
+
const registry = this.acpAgentRegistry;
|
|
766
|
+
// "No agents" and "this host cannot manage agents" are different facts, and
|
|
767
|
+
// a client that conflates them shows an empty picker with no way forward.
|
|
768
|
+
if (!registry)
|
|
769
|
+
return { ok: false, agents: [], reason: 'This host has no ACP agent registry mounted.' };
|
|
770
|
+
return { ok: true, agents: registry.listSummaries() };
|
|
771
|
+
}
|
|
772
|
+
addAcpPreset(request) {
|
|
773
|
+
const registry = this.acpAgentRegistry;
|
|
774
|
+
if (!registry)
|
|
775
|
+
return { ok: false, reason: 'This host has no ACP agent registry mounted.' };
|
|
776
|
+
// 🔴 Validated against the registry's OWN list, not a copy of it here. A
|
|
777
|
+
// second list of preset ids would be free to drift from the one that
|
|
778
|
+
// decides — which is exactly how the previous guard came to name three
|
|
779
|
+
// presets while six existed.
|
|
780
|
+
if (!registry.isPresetId(request.presetId)) {
|
|
781
|
+
return {
|
|
782
|
+
ok: false,
|
|
783
|
+
reason: `Unknown agent preset '${request.presetId}'. Available: ${registry.presetIds().join(', ')}.`,
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
return registry.addPreset(request.presetId);
|
|
787
|
+
}
|
|
788
|
+
removeAcpAgent(request) {
|
|
789
|
+
const registry = this.acpAgentRegistry;
|
|
790
|
+
if (!registry)
|
|
791
|
+
return { ok: false, removed: false, reason: 'This host has no ACP agent registry mounted.' };
|
|
792
|
+
return registry.remove(request.agentId);
|
|
793
|
+
}
|
|
794
|
+
setSessionMode(request) {
|
|
795
|
+
const service = this.sessionService;
|
|
796
|
+
if (!service)
|
|
797
|
+
return { ok: false, reason: 'This host has no session substrate mounted.' };
|
|
798
|
+
return service.setMode(request.sessionId, request.mode);
|
|
799
|
+
}
|
|
682
800
|
async routeSessionInput(input) {
|
|
683
801
|
const service = this.sessionService;
|
|
684
802
|
if (!service)
|
|
@@ -1072,7 +1190,7 @@ export class AgentHostCoordinator {
|
|
|
1072
1190
|
// 🔴 ALWAYS present, unlike every other optional field on this row.
|
|
1073
1191
|
// §5.3 requires the user to always know whether the thing running is
|
|
1074
1192
|
// contained, and "always" is the word that forbids omitting it when the
|
|
1075
|
-
// answer is unflattering. `none`
|
|
1193
|
+
// answer is unflattering. `none` is a real answer, not a missing field.
|
|
1076
1194
|
isolation: isolation.isolation,
|
|
1077
1195
|
...(isolation.mechanism === undefined ? {} : { isolationMechanism: isolation.mechanism }),
|
|
1078
1196
|
...(isolation.reason === undefined ? {} : { isolationReason: isolation.reason }),
|
|
@@ -1413,6 +1531,9 @@ export class AgentHostCoordinator {
|
|
|
1413
1531
|
if (method === 'turn.cancel') {
|
|
1414
1532
|
return await this.cancelTurn(input);
|
|
1415
1533
|
}
|
|
1534
|
+
if (method === 'context.inject') {
|
|
1535
|
+
return await this.injectContext(input);
|
|
1536
|
+
}
|
|
1416
1537
|
if (method === 'turn.steer') {
|
|
1417
1538
|
return await this.steerTurn(input);
|
|
1418
1539
|
}
|
|
@@ -1470,6 +1591,18 @@ export class AgentHostCoordinator {
|
|
|
1470
1591
|
if (method === 'session.release') {
|
|
1471
1592
|
return this.releaseSession(input);
|
|
1472
1593
|
}
|
|
1594
|
+
if (method === 'acp.agents.list') {
|
|
1595
|
+
return this.listAcpAgents();
|
|
1596
|
+
}
|
|
1597
|
+
if (method === 'acp.agents.addPreset') {
|
|
1598
|
+
return this.addAcpPreset(input);
|
|
1599
|
+
}
|
|
1600
|
+
if (method === 'acp.agents.remove') {
|
|
1601
|
+
return this.removeAcpAgent(input);
|
|
1602
|
+
}
|
|
1603
|
+
if (method === 'session.setMode') {
|
|
1604
|
+
return this.setSessionMode(input);
|
|
1605
|
+
}
|
|
1473
1606
|
if (method === 'intake.sources.list') {
|
|
1474
1607
|
return this.listIntakeSources();
|
|
1475
1608
|
}
|
|
@@ -2025,6 +2158,7 @@ export class AgentHostCoordinator {
|
|
|
2025
2158
|
}
|
|
2026
2159
|
const controller = new AbortController();
|
|
2027
2160
|
this.activeTurns.set(activeKey, { adapter, controller, request });
|
|
2161
|
+
this.conversationAdapters.set(request.conversationId, adapter);
|
|
2028
2162
|
// 🔴 The host ENFORCES the deadline it already validates. `timeoutMs` was checked on the way
|
|
2029
2163
|
// in (1..3_600_000) and forwarded to the adapter, but nothing here ever acted on it: the
|
|
2030
2164
|
// controller was aborted only for shutdown or an explicit cancel. A turn whose adapter
|
|
@@ -2342,9 +2476,21 @@ export class AgentHostCoordinator {
|
|
|
2342
2476
|
const failure = {
|
|
2343
2477
|
success: false,
|
|
2344
2478
|
code: 'steer_unsupported',
|
|
2479
|
+
/**
|
|
2480
|
+
* 🔴 NAME THE LANE, exactly as the no-steer branch above does (§7.1 capability
|
|
2481
|
+
* honesty). This said “The provider rejected the correction”, which names nothing an
|
|
2482
|
+
* operator can act on — with several lanes on a host, “the provider” is the one fact
|
|
2483
|
+
* they need and the one this withheld.
|
|
2484
|
+
*
|
|
2485
|
+
* ⚠️ IT ONLY BECAME REACHABLE WHEN THE ACP LANE LEARNED TO STEER. Until then no ACP
|
|
2486
|
+
* adapter implemented `steer`, so every ACP refusal took the branch above, which does
|
|
2487
|
+
* name itself. Implementing the capability moved the refusal onto a path whose message
|
|
2488
|
+
* was weaker — an improvement that quietly regressed the diagnostics beside it.
|
|
2489
|
+
* `scripts/session-attach-probe.mjs` caught it; nothing in this repo did.
|
|
2490
|
+
*/
|
|
2345
2491
|
safeError: error instanceof Error && error.message
|
|
2346
|
-
? `The
|
|
2347
|
-
:
|
|
2492
|
+
? `The '${active.adapter.id}' lane rejected the correction: ${error.message.slice(0, 300)}`
|
|
2493
|
+
: `The '${active.adapter.id}' lane rejected the correction.`,
|
|
2348
2494
|
conversationId: input.conversationId,
|
|
2349
2495
|
requestId: input.requestId,
|
|
2350
2496
|
};
|
|
@@ -2362,6 +2508,86 @@ export class AgentHostCoordinator {
|
|
|
2362
2508
|
this.commandResults.set(commandKey, { method: 'turn.steer', fingerprint, result: structuredClone(result) });
|
|
2363
2509
|
return result;
|
|
2364
2510
|
}
|
|
2511
|
+
/**
|
|
2512
|
+
* `context.inject` — push a pinned canvas node back into an agent's context (XENO SPAWN §2.2).
|
|
2513
|
+
*
|
|
2514
|
+
* 🔴 THE COMMAND THAT DID NOT EXIST. Both lanes could already inject — `AgentLoop.injectContext()`
|
|
2515
|
+
* on the SDK side, `AgentProcess.injectContext()` on the ACP side — the `context.injected` event
|
|
2516
|
+
* was defined, forwarded and PROJECTED, and nothing in this host could trigger any of it. Every
|
|
2517
|
+
* layer was built and tested; the one that lets a surface act was missing, which is the shape
|
|
2518
|
+
* this ecosystem keeps re-finding.
|
|
2519
|
+
*
|
|
2520
|
+
* ⚠️ NOT gated on an active turn, unlike `steerTurn`. Pin-forward is normally a between-turns
|
|
2521
|
+
* act, so the lane is resolved from `conversationAdapters` rather than `activeTurns`.
|
|
2522
|
+
*/
|
|
2523
|
+
async injectContext(input) {
|
|
2524
|
+
if (!this.isConnectedClient(input.command.client)) {
|
|
2525
|
+
return {
|
|
2526
|
+
success: false,
|
|
2527
|
+
code: 'client_not_connected',
|
|
2528
|
+
safeError: 'Complete the handshake before injecting context.',
|
|
2529
|
+
conversationId: input.conversationId,
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
if (typeof input.content !== 'string' || !input.content.trim()) {
|
|
2533
|
+
return {
|
|
2534
|
+
success: false,
|
|
2535
|
+
code: 'invalid_request',
|
|
2536
|
+
safeError: 'Injected content must be a non-empty string.',
|
|
2537
|
+
conversationId: input.conversationId,
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
const adapter = this.conversationAdapters.get(input.conversationId);
|
|
2541
|
+
if (!adapter) {
|
|
2542
|
+
// Nothing has run on this conversation yet, so there is no agent to hold the node. Said
|
|
2543
|
+
// plainly rather than as a generic failure: the remedy is to start a turn first.
|
|
2544
|
+
return {
|
|
2545
|
+
success: false,
|
|
2546
|
+
code: 'no_active_session',
|
|
2547
|
+
safeError: 'This conversation has no agent session yet. Start a turn before pinning a node into it.',
|
|
2548
|
+
conversationId: input.conversationId,
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
if (!adapter.injectContext) {
|
|
2552
|
+
/**
|
|
2553
|
+
* 🔴 The remedy is DERIVED from what the other lanes can do, not written by hand — the same
|
|
2554
|
+
* rule `steerTurn` follows, so a lane gaining the capability changes this message for free.
|
|
2555
|
+
*/
|
|
2556
|
+
const capable = [...this.turnExecutionAdapters.values()]
|
|
2557
|
+
.filter((entry) => entry.injectContext)
|
|
2558
|
+
.map((entry) => entry.id);
|
|
2559
|
+
return {
|
|
2560
|
+
success: false,
|
|
2561
|
+
code: 'inject_unsupported',
|
|
2562
|
+
safeError: capable.length > 0
|
|
2563
|
+
? `The '${adapter.id}' lane cannot take injected context. Lanes that can: ${capable.join(', ')}.`
|
|
2564
|
+
: `The '${adapter.id}' lane cannot take injected context, and no lane on this host can.`,
|
|
2565
|
+
conversationId: input.conversationId,
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
try {
|
|
2569
|
+
const outcome = await adapter.injectContext(structuredClone(input));
|
|
2570
|
+
return {
|
|
2571
|
+
success: true,
|
|
2572
|
+
...(outcome?.messageId ? { messageId: outcome.messageId } : {}),
|
|
2573
|
+
...(outcome?.status ? { status: outcome.status } : {}),
|
|
2574
|
+
deliveredVia: 'adapter',
|
|
2575
|
+
conversationId: input.conversationId,
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
catch (error) {
|
|
2579
|
+
// Reported, never swallowed. A caller that believes a node was pinned when it was not will
|
|
2580
|
+
// reason about what the agent knows using an edge that does not exist.
|
|
2581
|
+
return {
|
|
2582
|
+
success: false,
|
|
2583
|
+
code: 'inject_unsupported',
|
|
2584
|
+
safeError: error instanceof Error && error.message
|
|
2585
|
+
? `The provider rejected the injection: ${error.message.slice(0, 300)}`
|
|
2586
|
+
: 'The provider rejected the injection.',
|
|
2587
|
+
conversationId: input.conversationId,
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2365
2591
|
/** True while any turn is running for this conversation. */
|
|
2366
2592
|
hasActiveTurn(conversationId) {
|
|
2367
2593
|
for (const active of this.activeTurns.values()) {
|
|
@@ -3156,7 +3382,6 @@ export * from './isolation/RunIsolationService.js';
|
|
|
3156
3382
|
*/
|
|
3157
3383
|
export * from './isolation/SandboxCommandExecutor.js';
|
|
3158
3384
|
export * from './observation/XenoUseObservationSource.js';
|
|
3159
|
-
export * from './observation/XenoUseRunObserver.js';
|
|
3160
3385
|
export * from './session/AgentSessionService.js';
|
|
3161
3386
|
export * from './session/runtimeEventChannel.js';
|
|
3162
3387
|
export * from './session/renderSupport.js';
|