@platless/axom-cli 0.5.9 → 0.6.52
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 +25 -6
- package/dist/axom-patch/cli/README.md +129 -12
- package/dist/axom-patch/cli/account-limits/generate.py +15 -12
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
- package/dist/axom-patch/cli/claude-account-limits.json +9 -9
- package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
- package/dist/axom-patch/cli/codex-account-limits.json +11 -11
- package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
- package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
- package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
- package/dist/axom-patch/cli/nightly-task.md +55 -13
- package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
- package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
- package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
- package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
- package/dist/axom-patch/cli/qoder-steering.md +20 -25
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
- package/dist/axom-patch/cli/runtime.json +19 -19
- package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
- package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
- package/dist/axom-patch/cli/zcode-env-account.json +58 -0
- package/dist/axom-patch/cli/zcode-models.json +9 -10
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
- package/dist/cli.js +11 -45
- package/dist/commands/auth.js +4 -1
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +126 -3
- package/dist/commands/workstation.js +101 -164
- package/dist/commands/workstationRegistrations.js +116 -0
- package/dist/credentialBroker.js +13 -2
- package/dist/credentials.js +16 -0
- package/dist/install/claude/commands.js +15 -15
- package/dist/keepers/terminal/keeper.mjs +1089 -673
- package/dist/keepers/terminal/manifest.json +4 -4
- package/dist/mcp.js +1 -1
- package/dist/native/darwin-x64/axom-quic +0 -0
- package/dist/workstation/acp/acpAgents.js +113 -102
- package/dist/workstation/acp/acpClient.js +26 -0
- package/dist/workstation/acp/builds/install/npm.js +99 -10
- package/dist/workstation/acp/initializeResponse.js +6 -1
- package/dist/workstation/acp/managedPatches.js +189 -25
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +12 -5
- package/dist/workstation/acp/patchAssets.js +40 -0
- package/dist/workstation/acp/permission.js +2 -2
- package/dist/workstation/acp/processor/checkpoint.js +26 -34
- package/dist/workstation/acp/processor/control.js +7 -80
- package/dist/workstation/acp/processor/jsonrpc.js +8 -0
- package/dist/workstation/acp/processor/runtime.js +5 -81
- package/dist/workstation/acp/processor/session.js +54 -82
- package/dist/workstation/acp/processor/state.js +7 -13
- package/dist/workstation/acp/processor/step.js +3 -20
- package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
- package/dist/workstation/acp/processor/transport/local.js +19 -3
- package/dist/workstation/acp/processor/updates.js +156 -105
- package/dist/workstation/acp/processorSession.js +20 -173
- package/dist/workstation/acp/registry/catalog.js +65 -39
- package/dist/workstation/acp/registry/launch.js +16 -1
- package/dist/workstation/acp/switching.js +0 -3
- package/dist/workstation/acp/test/fakeAgent.js +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +32 -3
- package/dist/workstation/acp/test/raw/sources.js +16 -130
- package/dist/workstation/acp/test/realAdapter.js +1 -47
- package/dist/workstation/acp/test/scriptAgent.js +11 -3
- package/dist/workstation/agentFlow.js +1 -1
- package/dist/workstation/agentSessions.js +114 -166
- package/dist/workstation/channels/agent.js +9 -1
- package/dist/workstation/channels/computer.js +27 -4
- package/dist/workstation/channels/management.js +130 -15
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +33 -9
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +204 -0
- package/dist/workstation/control/commands.js +125 -0
- package/dist/workstation/control/daemonIdentity.js +143 -0
- package/dist/workstation/control/deviceCommands.js +191 -0
- package/dist/workstation/control/deviceProtocol.js +68 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +110 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/rootFormat.js +16 -0
- package/dist/workstation/control/runtimeClient.js +152 -0
- package/dist/workstation/control/runtimeHost.js +235 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/control/updateTransition.js +91 -0
- package/dist/workstation/daemon.js +266 -264
- package/dist/workstation/daemonctl.js +226 -25
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/automationDevice.js +219 -0
- package/dist/workstation/desktopControl/automationJson.js +74 -0
- package/dist/workstation/desktopControl/automationLocalState.js +23 -0
- package/dist/workstation/desktopControl/automationProtocol.js +253 -0
- package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
- package/dist/workstation/desktopControl/automationSession.js +81 -0
- package/dist/workstation/desktopControl/automationTransport.js +138 -0
- package/dist/workstation/desktopControl/capabilities.js +93 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
- package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +602 -0
- package/dist/workstation/desktopControl/hostGrant.js +65 -0
- package/dist/workstation/desktopControl/lobby.js +131 -0
- package/dist/workstation/desktopControl/macProvider.js +156 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
- package/dist/workstation/desktopControl/protocol.js +311 -0
- package/dist/workstation/desktopControl/provider.js +1 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1774 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +98 -0
- package/dist/workstation/exec/policy.js +5 -0
- package/dist/workstation/files/client.js +10 -1
- package/dist/workstation/files/credentials.js +142 -0
- package/dist/workstation/files/host.js +9 -2
- package/dist/workstation/files/text.js +42 -8
- package/dist/workstation/journal.js +5 -2
- package/dist/workstation/keepers/acp/client.js +38 -2
- package/dist/workstation/keepers/kit/buildVersion.js +35 -11
- package/dist/workstation/keepers/kit/bundle.js +21 -17
- package/dist/workstation/keepers/kit/client.js +2 -2
- package/dist/workstation/keepers/kit/launcher.js +123 -7
- package/dist/workstation/keepers/kit/paths.js +2 -1
- package/dist/workstation/keepers/legacy.js +16 -2
- package/dist/workstation/keepers/policy.js +5 -34
- package/dist/workstation/keepers/terminal/client.js +115 -23
- package/dist/workstation/keepers/terminal/exec.js +6 -3
- package/dist/workstation/keepers/terminal/frames.js +73 -0
- package/dist/workstation/keepers/terminal/generations.js +5 -8
- package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
- package/dist/workstation/keepers/terminal/input.js +146 -0
- package/dist/workstation/keepers/terminal/keeper.js +22 -8
- package/dist/workstation/keepers/terminal/main.js +1 -1
- package/dist/workstation/keepers/terminal/metadata.js +138 -0
- package/dist/workstation/keepers/terminal/protocol.js +4 -28
- package/dist/workstation/keepers/terminal/pty.js +1 -330
- package/dist/workstation/keepers/terminal/screen.js +246 -0
- package/dist/workstation/keepers/terminal/sessions.js +526 -0
- package/dist/workstation/managedInstall.js +57 -0
- package/dist/workstation/oauth/deviceAuthorization.js +31 -17
- package/dist/workstation/protocol/agent.js +2 -15
- package/dist/workstation/protocol/management.js +113 -5
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +120 -0
- package/dist/workstation/rd/control.js +117 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/host.js +213 -0
- package/dist/workstation/rd/migrationReadiness.js +46 -0
- package/dist/workstation/rd/provider.js +256 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +57 -0
- package/dist/workstation/rd/update.js +168 -0
- package/dist/workstation/rd/upkeep.js +80 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/registrations/enrollment.js +250 -0
- package/dist/workstation/registrations/enrollmentPending.js +361 -0
- package/dist/workstation/registrations/migration.js +356 -0
- package/dist/workstation/registrations/model.js +34 -0
- package/dist/workstation/registrations/removal.js +222 -0
- package/dist/workstation/registrations/store.js +737 -0
- package/dist/workstation/runtime/desktopBinding.js +20 -0
- package/dist/workstation/runtime/deviceDesktop.js +193 -0
- package/dist/workstation/runtime/deviceRuntime.js +568 -0
- package/dist/workstation/runtime/deviceServices.js +117 -0
- package/dist/workstation/runtime/events.js +20 -0
- package/dist/workstation/runtime/ownership.js +100 -0
- package/dist/workstation/runtime/registrationRuntime.js +575 -0
- package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
- package/dist/workstation/runtime/removalActivity.js +184 -0
- package/dist/workstation/service/installer.js +197 -14
- package/dist/workstation/service/launchd.js +2 -0
- package/dist/workstation/service/systemd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +47 -92
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- package/dist/workstation/tools/client.js +8 -27
- package/dist/workstation/tools/info.js +6 -13
- package/dist/workstation/tools/terminalRouter.js +17 -172
- package/dist/workstation/tools/validation.js +55 -1
- package/dist/workstation/update/archiveEvidence.js +206 -0
- package/dist/workstation/update/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +34 -13
- package/dist/workstation/update/deviceCompatibility.js +119 -0
- package/dist/workstation/update/deviceCoordinator.js +369 -0
- package/dist/workstation/update/deviceHealth.js +104 -0
- package/dist/workstation/update/deviceHealthProtocol.js +26 -0
- package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
- package/dist/workstation/update/deviceRollback.js +78 -0
- package/dist/workstation/update/globalInstall.js +186 -0
- package/dist/workstation/update/legacyBridge.js +414 -0
- package/dist/workstation/update/legacyOwner.js +39 -0
- package/dist/workstation/update/npmInstaller.js +51 -25
- package/dist/workstation/update/privateDurability.js +61 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/recoveryPlan.js +124 -0
- package/dist/workstation/update/runUpdate.js +10 -3
- package/dist/workstation/update/taskRegistry.js +161 -3
- package/dist/workstation/update/watchdog.js +29806 -333
- package/dist/workstation/update/watchdogProcess.js +72 -0
- package/package.json +11 -7
- package/skills/axom/SKILL.md +11 -11
- package/skills/axom-definition/SKILL.md +3 -3
- package/skills/axom-platform/SKILL.md +3 -3
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
- package/dist/axom-patch/cli/qoder-steering.json +0 -52
- package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
- package/dist/commands/guide.js +0 -38
- package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
- package/dist/runtime-deps/qoder-sdk/package.json +0 -8
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/processor/sideChannel.js +0 -80
- package/dist/workstation/acp/qoderStartup.js +0 -45
- package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
- package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
- package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
- package/dist/workstation/qoderAdapter/history.js +0 -85
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -95
- package/dist/workstation/qoderAdapter/mapper.js +0 -117
- package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
- package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
- package/dist/workstation/qoderAdapter/permissions.js +0 -61
- package/dist/workstation/qoderAdapter/profile.js +0 -93
- package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
- package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -85
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
import { AGENT_WIRE_AUTH_URL_VERSION, AGENT_WIRE_HEARTBEAT_INVENTORY_VERSION, AGENT_WIRE_SCOPED_ORIGIN_VERSION, AGENT_WIRE_ORPHAN_OFFER_VERSION, AGENT_WIRE_SESSION_AUTH_METHODS_VERSION, AGENT_WIRE_SESSION_LOST_VERSION, AGENT_WIRE_VERSION, AGENT_WIRE_DELEGATION_MCP_VERSION, AGENT_WIRE_DELEGATION_RENEW_VERSION, AGENT_WIRE_PROVIDER_TASK_VERSION, AGENT_WIRE_REOPEN_REQUEST_VERSION, AGENT_WIRE_RELATION_ACK_VERSION, outboundAgentFrameRejection, outboundFailureReply, } from './protocol/agent.js';
|
|
2
|
+
import { AGENT_WIRE_AUTH_URL_VERSION, AGENT_WIRE_HEARTBEAT_INVENTORY_VERSION, AGENT_WIRE_SCOPED_ORIGIN_VERSION, AGENT_WIRE_ORPHAN_OFFER_VERSION, AGENT_WIRE_SESSION_AUTH_METHODS_VERSION, AGENT_WIRE_SESSION_LOST_VERSION, AGENT_WIRE_VERSION, AGENT_WIRE_PROVIDER_TASK_VERSION, AGENT_WIRE_REOPEN_REQUEST_VERSION, AGENT_WIRE_RELATION_ACK_VERSION, outboundAgentFrameRejection, outboundFailureReply, } from './protocol/agent.js';
|
|
4
3
|
import { listLegacyJournalRefs, readLegacyJournal, removeLegacyJournal } from './journal.js';
|
|
5
4
|
import { SESSION_WINDOW_BYTES, SESSION_WINDOW_EVENTS } from './acp/outbox.js';
|
|
6
5
|
import { SocketGate } from './agentFlow.js';
|
|
7
6
|
import { AGENT_PROBE_TIMEOUT_MS, AgentRefusedSessionError, LOCAL_SESSION_UNMOVABLE } from './acp/acpAgents.js';
|
|
8
|
-
import { AdapterStillInstallingError } from './acp/builds/model.js';
|
|
9
7
|
import { projectFrame } from './acp/processor/projection.js';
|
|
8
|
+
import { TaskRegistry } from './update/taskRegistry.js';
|
|
10
9
|
import { reportsActivity, supportsAsyncTaskStop } from './acp/activityReporting.js';
|
|
11
10
|
import { activityIsBusy } from './acp/activityBusy.js';
|
|
12
|
-
import { buildSessionMcpServers } from './acp/delegationMcp.js';
|
|
13
11
|
import { ADAPTER_LOST_REOPEN_ATTEMPTS, plainSwitchBlocker, REOPEN_REQUEST_RETRY_MS, switchBlockers, SWITCH_QUIET_MS } from './acp/switching.js';
|
|
14
|
-
function delegationLeaseId(lease) {
|
|
15
|
-
return createHash('sha256').update(JSON.stringify([
|
|
16
|
-
lease.url, lease.headers, lease.bindingId,
|
|
17
|
-
])).digest('hex');
|
|
18
|
-
}
|
|
19
12
|
/**
|
|
20
13
|
* 从线上收来的验证预算的绝对上限。
|
|
21
14
|
*
|
|
@@ -129,10 +122,6 @@ function openSpecOf(frame) {
|
|
|
129
122
|
openRequestId: frame.id,
|
|
130
123
|
sessionRef: frame.sessionRef,
|
|
131
124
|
agentKey: frame.agentKey,
|
|
132
|
-
...(frame.delegationMcp ? { delegation: {
|
|
133
|
-
leaseId: delegationLeaseId(frame.delegationMcp),
|
|
134
|
-
...(frame.delegationMcp.bindingId ? { bindingId: frame.delegationMcp.bindingId, instanceId: randomUUID() } : {}),
|
|
135
|
-
} } : {}),
|
|
136
125
|
// `frame.bridge` is the registry agent id now — the wire field keeps its name because Plan A
|
|
137
126
|
// renames nothing (Global Constraint 3); ruling R2 replaced what it *means*.
|
|
138
127
|
agentId: frame.bridge,
|
|
@@ -143,7 +132,6 @@ function openSpecOf(frame) {
|
|
|
143
132
|
args: frame.args ?? [],
|
|
144
133
|
...(frame.nativeSessionId ? { nativeSessionId: frame.nativeSessionId } : {}),
|
|
145
134
|
...(frame.requireSessionLoad ? { requireSessionLoad: true } : {}),
|
|
146
|
-
...(frame.delegationMcp ? { delegationMcp: frame.delegationMcp } : {}),
|
|
147
135
|
// Retaining a whole adopted history costs memory for the session's lifetime, so it is only
|
|
148
136
|
// kept when this open is the one that will stream it back.
|
|
149
137
|
backfill: frame.backfill === true,
|
|
@@ -163,13 +151,6 @@ function exitWords(exit) {
|
|
|
163
151
|
function errorMessage(err) {
|
|
164
152
|
return err instanceof Error ? err.message : String(err);
|
|
165
153
|
}
|
|
166
|
-
/**
|
|
167
|
-
* The reply text of an open that failed. A managed (delegation) open hides the cause behind one fixed sentence — except when
|
|
168
|
-
* the adapter is still being installed: that one is actionable ("try again shortly") and carries nothing about the lease (15c).
|
|
169
|
-
*/
|
|
170
|
-
function openFailureText(frame, err) {
|
|
171
|
-
return frame.delegationMcp && !(err instanceof AdapterStillInstallingError) ? 'Managed delegation session could not open' : errorMessage(err);
|
|
172
|
-
}
|
|
173
154
|
/**
|
|
174
155
|
* Batch size for `agent_backfill_events`. Kept modest so a session with a long history does not
|
|
175
156
|
* arrive as one giant frame; the last batch of a stream is whatever remains below this size.
|
|
@@ -250,7 +231,11 @@ export class AgentSessionManager {
|
|
|
250
231
|
agents;
|
|
251
232
|
journalDir;
|
|
252
233
|
tasks;
|
|
234
|
+
admission;
|
|
235
|
+
ownership;
|
|
253
236
|
sessions = new Map();
|
|
237
|
+
/** A failed recorded-build respawn keeps this binding even after no LiveSession remains. */
|
|
238
|
+
retainedRespawnBuilds = new Map();
|
|
254
239
|
/** In-flight opens, keyed by sessionRef, so two racing agent_open frames cannot spawn two agents. */
|
|
255
240
|
opening = new Map();
|
|
256
241
|
/**
|
|
@@ -356,10 +341,12 @@ export class AgentSessionManager {
|
|
|
356
341
|
/** `<configDir>/agents`: the legacy journals (`sessions/<ref>.jsonl`) an older CLI left are migrated from here, once. */
|
|
357
342
|
journalDir,
|
|
358
343
|
/** Shared update admission and force-cancellation authority, when this daemon supports updates. */
|
|
359
|
-
tasks) {
|
|
344
|
+
tasks, admission = () => true, ownership) {
|
|
360
345
|
this.agents = agents;
|
|
361
346
|
this.journalDir = journalDir;
|
|
362
347
|
this.tasks = tasks;
|
|
348
|
+
this.admission = admission;
|
|
349
|
+
this.ownership = ownership;
|
|
363
350
|
this.send = send;
|
|
364
351
|
}
|
|
365
352
|
/** Where an older CLI's journals live: `<journalDir>/sessions/<sessionRef>.jsonl` (read-only legacy, `journal.ts`). */
|
|
@@ -387,7 +374,7 @@ export class AgentSessionManager {
|
|
|
387
374
|
}
|
|
388
375
|
for (const sessionRef of refs) {
|
|
389
376
|
try {
|
|
390
|
-
const entries = await readLegacyJournal(this.sessionsDir, sessionRef);
|
|
377
|
+
const entries = await readLegacyJournal(this.sessionsDir, sessionRef, this.ownership);
|
|
391
378
|
const imported = await importOutbox(sessionRef, entries.map((entry) => ({
|
|
392
379
|
seq: entry.seq,
|
|
393
380
|
...(entry.src ? { src: entry.src } : {}),
|
|
@@ -578,7 +565,7 @@ export class AgentSessionManager {
|
|
|
578
565
|
this.send = send;
|
|
579
566
|
this.serverAgentWire = serverAgentWire;
|
|
580
567
|
// A new connection: its own window and socket. What was sent on the previous one waits for the server's reopen to say how far
|
|
581
|
-
// it got
|
|
568
|
+
// it got. An already-exited adapter's recovered turn is replayed in full to finish it before reopen.
|
|
582
569
|
this.epoch += 1;
|
|
583
570
|
for (const live of this.sessions.values())
|
|
584
571
|
live.session.outbox.connectionChanged();
|
|
@@ -586,6 +573,9 @@ export class AgentSessionManager {
|
|
|
586
573
|
// Before any inbound frame on this connection can be answered: a sweep notice that arrived here
|
|
587
574
|
// after the server had already re-opened its ref would invalidate a healthy generation.
|
|
588
575
|
this.flushSwept();
|
|
576
|
+
for (const live of this.sessions.values())
|
|
577
|
+
if (live.replayDeadTail)
|
|
578
|
+
live.session.outbox.resendAll();
|
|
589
579
|
this.pump();
|
|
590
580
|
// Account observations are outside the session journal. Replay the cached state on reconnect
|
|
591
581
|
// so a server restart does not depend on the provider sending another quota notification.
|
|
@@ -756,12 +746,17 @@ export class AgentSessionManager {
|
|
|
756
746
|
highestScopedJournalSeq = Math.max(highestScopedJournalSeq, item.seq);
|
|
757
747
|
}
|
|
758
748
|
const live = {
|
|
759
|
-
sessionRef: candidate.sessionRef, adopted: true, agentId: candidate.agentId, agentKey: candidate.agentKey, session,
|
|
749
|
+
sessionRef: candidate.sessionRef, adopted: true, replayDeadTail: candidate.agentAlive === false, agentId: candidate.agentId, agentKey: candidate.agentKey, session,
|
|
760
750
|
nativeSessionId: session.nativeSessionId ?? null,
|
|
761
751
|
scopedReplayBlocked: false, highestScopedJournalSeq, unwatch: () => { }, replays: Promise.resolve(), closed: false,
|
|
762
752
|
lastActivityAt: Date.now(),
|
|
763
753
|
};
|
|
764
|
-
this.register(live);
|
|
754
|
+
this.register(live, this.adoptionOrigin);
|
|
755
|
+
// An exited adapter is adopted only to finish its recovered turn. Release its entire retained
|
|
756
|
+
// prefix in order so that tail can settle the server's turn before a reopen respawns the adapter.
|
|
757
|
+
// Live adapters retain the ordinary reopen/watermark gate.
|
|
758
|
+
if (live.replayDeadTail)
|
|
759
|
+
live.session.outbox.resendAll();
|
|
765
760
|
console.info(`[acp:${live.agentId} agent:${live.agentKey}] adopted session ${live.sessionRef} from keeper-acp`);
|
|
766
761
|
}
|
|
767
762
|
/** Waits (bounded) for the start-time adoption; what is still rebuilding afterwards is reported as such. */
|
|
@@ -796,6 +791,23 @@ export class AgentSessionManager {
|
|
|
796
791
|
clearTimeout(timer);
|
|
797
792
|
}
|
|
798
793
|
async handleFrame(frame) {
|
|
794
|
+
const live = 'sessionRef' in frame && typeof frame.sessionRef === 'string' ? this.liveSession(frame.sessionRef) : undefined;
|
|
795
|
+
const openContinuation = frame.t === 'agent_open' && ((live && !this.needsRespawn(live)) || this.opening.has(frame.sessionRef) || this.adopting.has(frame.sessionRef));
|
|
796
|
+
const newWork = !openContinuation && ['agent_open', 'agent_prompt', 'agent_list_native', 'agent_delete_native', 'agent_account_read', 'agent_verify', 'agent_authenticate', 'agent_install', 'agent_switch_idle'].includes(frame.t);
|
|
797
|
+
const registry = this.tasks ?? this.acceptedTasks;
|
|
798
|
+
const ticket = newWork && this.admission() ? registry.beginAcceptedAgentWork(frame.t, frame, frame.t === 'agent_prompt' ? this.adoptionOrigin : undefined) : null;
|
|
799
|
+
const admitted = (!newWork && (frame.t !== 'agent_open' || this.admission())) || ticket !== null;
|
|
800
|
+
try {
|
|
801
|
+
await this.handleAcceptedFrame(frame, ticket, admitted, registry);
|
|
802
|
+
}
|
|
803
|
+
finally {
|
|
804
|
+
if (ticket)
|
|
805
|
+
registry.finishAcceptedAgentWork(ticket);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
acceptedTasks = new TaskRegistry();
|
|
809
|
+
adoptionOrigin = {};
|
|
810
|
+
async handleAcceptedFrame(frame, ticket, admitted, registry) {
|
|
799
811
|
// A frame for a ref keeper-acp still runs from a previous daemon waits until that session is rebuilt (Task 21): answering
|
|
800
812
|
// it "unknown session" would end a turn that is going on.
|
|
801
813
|
const frameRef = 'sessionRef' in frame && typeof frame.sessionRef === 'string' ? frame.sessionRef : undefined;
|
|
@@ -809,40 +821,8 @@ export class AgentSessionManager {
|
|
|
809
821
|
for (let parked = frameRef !== undefined ? this.switching.get(frameRef) : undefined; parked; parked = this.switching.get(frameRef))
|
|
810
822
|
await parked;
|
|
811
823
|
switch (frame.t) {
|
|
812
|
-
case 'agent_delegation_renew': {
|
|
813
|
-
if (this.serverAgentWire < AGENT_WIRE_DELEGATION_RENEW_VERSION)
|
|
814
|
-
return;
|
|
815
|
-
const live = this.liveSession(frame.sessionRef);
|
|
816
|
-
const now = Date.now();
|
|
817
|
-
// Delegation's non-secret fields are processor state (Task 19): the checks read it, an accepted renewal is a note.
|
|
818
|
-
const delegation = live?.session.delegation ?? undefined;
|
|
819
|
-
if (!live || !delegation || live.closed || !live.session.liveness.agentAlive || live.session.liveness.turnRunning ||
|
|
820
|
-
activityIsBusy(live.session.activityRaw) || delegation.refreshPending ||
|
|
821
|
-
live.nativeSessionId !== frame.nativeSessionId || delegation.instanceId !== frame.instanceId ||
|
|
822
|
-
delegation.bindingId !== frame.bindingId || !frame.bindingId ||
|
|
823
|
-
!Number.isSafeInteger(frame.renewalRevision) || frame.renewalRevision < 1 ||
|
|
824
|
-
!Number.isSafeInteger(frame.expiresAtEpochMs) || frame.expiresAtEpochMs <= now ||
|
|
825
|
-
frame.expiresAtEpochMs > now + 330_000 || frame.renewalRevision < (delegation.renewalRevision ?? 0) ||
|
|
826
|
-
frame.expiresAtEpochMs < delegation.expiresAtEpochMs ||
|
|
827
|
-
(frame.renewalRevision === delegation.renewalRevision && frame.expiresAtEpochMs !== delegation.expiresAtEpochMs)) {
|
|
828
|
-
this.emit({ t: 'agent_delegation_renewed', id: frame.id, ok: false, error: 'The original managed instance cannot renew' });
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
try {
|
|
832
|
-
await live.session.note({ type: 'agent_delegation_renewed', expiresAtEpochMs: frame.expiresAtEpochMs, renewalRevision: frame.renewalRevision });
|
|
833
|
-
}
|
|
834
|
-
catch (err) {
|
|
835
|
-
this.emit({ t: 'agent_delegation_renewed', id: frame.id, ok: false, error: errorMessage(err) });
|
|
836
|
-
return;
|
|
837
|
-
}
|
|
838
|
-
const renewed = live.session.delegation ?? delegation;
|
|
839
|
-
this.emit({ t: 'agent_delegation_renewed', id: frame.id, ok: true,
|
|
840
|
-
nativeSessionId: live.nativeSessionId, instanceId: renewed.instanceId, bindingId: renewed.bindingId,
|
|
841
|
-
renewalRevision: renewed.renewalRevision, expiresAtEpochMs: renewed.expiresAtEpochMs });
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
824
|
case 'agent_open': {
|
|
845
|
-
const admission = this.reserveOpen(frame
|
|
825
|
+
const admission = this.reserveOpen(frame, ticket, admitted, registry);
|
|
846
826
|
if (admission === 'rejected')
|
|
847
827
|
return;
|
|
848
828
|
const ref = frame.sessionRef;
|
|
@@ -852,7 +832,7 @@ export class AgentSessionManager {
|
|
|
852
832
|
}
|
|
853
833
|
finally {
|
|
854
834
|
if (admission === 'reserved')
|
|
855
|
-
|
|
835
|
+
registry.agentOpenFinished(ref);
|
|
856
836
|
const left = (this.openHandling.get(ref) ?? 1) - 1;
|
|
857
837
|
if (left > 0)
|
|
858
838
|
this.openHandling.set(ref, left);
|
|
@@ -868,7 +848,7 @@ export class AgentSessionManager {
|
|
|
868
848
|
const live = this.routableSession(frame.sessionRef, 'prompt');
|
|
869
849
|
if (!live)
|
|
870
850
|
return;
|
|
871
|
-
if (
|
|
851
|
+
if (!admitted) {
|
|
872
852
|
// The refusal is the daemon's decision, so it is a note (`agent_prompt{admitted:false}`) and the processor ends the
|
|
873
853
|
// turn `failed` with the same words (`ADMISSION_REFUSED`), numbered in the session's own sequence (spec §5.4). No
|
|
874
854
|
// `authMethods`: the processor's short frame carries no `agentRefused`, so the projection adds none — the prompt
|
|
@@ -876,6 +856,14 @@ export class AgentSessionManager {
|
|
|
876
856
|
live.session.refusePrompt(frame.platformTurnId);
|
|
877
857
|
return;
|
|
878
858
|
}
|
|
859
|
+
if (ticket)
|
|
860
|
+
try {
|
|
861
|
+
registry.consumeAcceptedAgentWork(ticket, frame.t, frame);
|
|
862
|
+
}
|
|
863
|
+
catch {
|
|
864
|
+
live.session.refusePrompt(frame.platformTurnId);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
879
867
|
live.session.prompt(promptBlocks(frame), frame.platformTurnId);
|
|
880
868
|
return;
|
|
881
869
|
}
|
|
@@ -1013,6 +1001,10 @@ export class AgentSessionManager {
|
|
|
1013
1001
|
* keep holding that row or to deliver it the ordinary way. A silent drop parks it forever.
|
|
1014
1002
|
*/
|
|
1015
1003
|
case 'agent_steer': {
|
|
1004
|
+
if (!this.admission()) {
|
|
1005
|
+
this.emit({ t: 'agent_steered', id: frame.id, ok: false, error: 'Registration admission is paused' });
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1016
1008
|
const live = this.routableSession(frame.sessionRef, 'steer');
|
|
1017
1009
|
if (!live) {
|
|
1018
1010
|
this.emit({ t: 'agent_steered', id: frame.id, ok: false, error: `no live session '${frame.sessionRef}'` });
|
|
@@ -1054,6 +1046,10 @@ export class AgentSessionManager {
|
|
|
1054
1046
|
* a request that never resolves.
|
|
1055
1047
|
*/
|
|
1056
1048
|
case 'agent_set_config_option': {
|
|
1049
|
+
if (!this.admission()) {
|
|
1050
|
+
this.emit({ t: 'agent_config_option_set', id: frame.id, ok: false, error: 'Registration admission is paused' });
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1057
1053
|
const live = this.routableSession(frame.sessionRef, 'set_config_option');
|
|
1058
1054
|
if (!live) {
|
|
1059
1055
|
this.emit({ t: 'agent_config_option_set', id: frame.id, ok: false, error: `no live session '${frame.sessionRef}'` });
|
|
@@ -1089,14 +1085,14 @@ export class AgentSessionManager {
|
|
|
1089
1085
|
return;
|
|
1090
1086
|
}
|
|
1091
1087
|
case 'agent_list_native':
|
|
1092
|
-
if (
|
|
1088
|
+
if (!admitted) {
|
|
1093
1089
|
this.emit({ t: 'agent_native_sessions', id: frame.id, ok: false, error: 'Workstation is not accepting new work' });
|
|
1094
1090
|
return;
|
|
1095
1091
|
}
|
|
1096
1092
|
await this.onListNative(frame);
|
|
1097
1093
|
return;
|
|
1098
1094
|
case 'agent_delete_native':
|
|
1099
|
-
if (
|
|
1095
|
+
if (!admitted) {
|
|
1100
1096
|
this.emit({ t: 'agent_native_deleted', id: frame.id, ok: false, error: 'Workstation is not accepting new work' });
|
|
1101
1097
|
return;
|
|
1102
1098
|
}
|
|
@@ -1105,7 +1101,7 @@ export class AgentSessionManager {
|
|
|
1105
1101
|
// Both of these spawn an agent subprocess, exactly as `agent_list_native` does, so they answer
|
|
1106
1102
|
// a draining workstation the same way rather than starting work it is trying to finish.
|
|
1107
1103
|
case 'agent_account_read': {
|
|
1108
|
-
if (
|
|
1104
|
+
if (!admitted) {
|
|
1109
1105
|
this.emit({ t: 'agent_account_read_result', id: frame.id, ok: false, error: 'Workstation is not accepting new work' });
|
|
1110
1106
|
return;
|
|
1111
1107
|
}
|
|
@@ -1127,14 +1123,14 @@ export class AgentSessionManager {
|
|
|
1127
1123
|
return;
|
|
1128
1124
|
}
|
|
1129
1125
|
case 'agent_verify':
|
|
1130
|
-
if (
|
|
1126
|
+
if (!admitted) {
|
|
1131
1127
|
this.emit({ t: 'agent_verified', id: frame.id, ok: false, error: 'Workstation is not accepting new work' });
|
|
1132
1128
|
return;
|
|
1133
1129
|
}
|
|
1134
1130
|
await this.onVerify(frame);
|
|
1135
1131
|
return;
|
|
1136
1132
|
case 'agent_authenticate':
|
|
1137
|
-
if (
|
|
1133
|
+
if (!admitted) {
|
|
1138
1134
|
this.emit({ t: 'agent_authenticated', id: frame.id, ok: false, error: 'Workstation is not accepting new work' });
|
|
1139
1135
|
return;
|
|
1140
1136
|
}
|
|
@@ -1143,7 +1139,7 @@ export class AgentSessionManager {
|
|
|
1143
1139
|
// Downloads a package; starts nothing. Still refused while draining, because a workstation on
|
|
1144
1140
|
// its way down should not begin a multi-megabyte fetch it will be killed in the middle of.
|
|
1145
1141
|
case 'agent_install':
|
|
1146
|
-
if (
|
|
1142
|
+
if (!admitted) {
|
|
1147
1143
|
this.emit({ t: 'agent_installed', id: frame.id, ok: false, error: 'Workstation is not accepting new work' });
|
|
1148
1144
|
return;
|
|
1149
1145
|
}
|
|
@@ -1151,7 +1147,7 @@ export class AgentSessionManager {
|
|
|
1151
1147
|
return;
|
|
1152
1148
|
// "Move idle sessions" (Task 25): a move spawns adapters, so a draining workstation refuses it like an install.
|
|
1153
1149
|
case 'agent_switch_idle':
|
|
1154
|
-
if (
|
|
1150
|
+
if (!admitted) {
|
|
1155
1151
|
this.emit({ t: 'agent_switch_idle_result', id: frame.id, ok: false, error: 'Workstation is not accepting new work' });
|
|
1156
1152
|
return;
|
|
1157
1153
|
}
|
|
@@ -1199,24 +1195,31 @@ export class AgentSessionManager {
|
|
|
1199
1195
|
* stop leaves attached. A ref absent from both maps is genuinely new work and still needs
|
|
1200
1196
|
* the ordinary TaskRegistry admission reservation.
|
|
1201
1197
|
*/
|
|
1202
|
-
reserveOpen(
|
|
1203
|
-
|
|
1204
|
-
|
|
1198
|
+
reserveOpen(frame, ticket, admitted, registry) {
|
|
1199
|
+
const { id: openId, sessionRef } = frame;
|
|
1200
|
+
if (!admitted) {
|
|
1201
|
+
this.emit({ t: 'agent_opened', id: openId, ok: false, error: 'Workstation is not accepting new work' });
|
|
1202
|
+
return 'rejected';
|
|
1203
|
+
}
|
|
1205
1204
|
const live = this.liveSession(sessionRef);
|
|
1206
1205
|
// A keeper-hosted session whose adapter is gone is respawned by this open: new work, not a continuation — it takes a reservation,
|
|
1207
1206
|
// and a draining workstation refuses it like any open (spec §8.2; fix round 1, minor 9).
|
|
1208
1207
|
const respawn = live !== undefined && this.needsRespawn(live);
|
|
1209
|
-
if (
|
|
1208
|
+
if (registry.isForceStopping()) {
|
|
1210
1209
|
// A keeper-hosted session a force update left attached (TaskRegistry.stopAll) is still reopened as a continuation — a reconnect
|
|
1211
1210
|
// during the download must not cut it off; anything else is refused.
|
|
1212
|
-
if (live && !respawn &&
|
|
1211
|
+
if (live && !respawn && registry.isDurableSession(sessionRef))
|
|
1213
1212
|
return 'continuation';
|
|
1214
1213
|
this.emit({ t: 'agent_opened', id: openId, ok: false, error: 'Workstation is not accepting new work' });
|
|
1215
1214
|
return 'rejected';
|
|
1216
1215
|
}
|
|
1217
1216
|
if ((live && !respawn) || this.opening.has(sessionRef) || this.adopting.has(sessionRef))
|
|
1218
1217
|
return 'continuation';
|
|
1219
|
-
if (
|
|
1218
|
+
if (ticket) {
|
|
1219
|
+
registry.reserveAcceptedAgentOpen(ticket, frame);
|
|
1220
|
+
return 'reserved';
|
|
1221
|
+
}
|
|
1222
|
+
if (registry.tryReserveAgentOpen(sessionRef))
|
|
1220
1223
|
return 'reserved';
|
|
1221
1224
|
this.emit({ t: 'agent_opened', id: openId, ok: false, error: 'Workstation is not accepting new work' });
|
|
1222
1225
|
return 'rejected';
|
|
@@ -1490,20 +1493,6 @@ export class AgentSessionManager {
|
|
|
1490
1493
|
return idle.map((live) => live.sessionRef);
|
|
1491
1494
|
}
|
|
1492
1495
|
async onOpen(frame) {
|
|
1493
|
-
if (frame.delegationMcp && (this.serverAgentWire < AGENT_WIRE_DELEGATION_MCP_VERSION ||
|
|
1494
|
-
(frame.delegationMcp.bindingId && this.serverAgentWire < AGENT_WIRE_DELEGATION_RENEW_VERSION))) {
|
|
1495
|
-
this.emit({ t: 'agent_opened', id: frame.id, ok: false, error: 'Managed delegation requires a newer server' });
|
|
1496
|
-
return;
|
|
1497
|
-
}
|
|
1498
|
-
if (frame.delegationMcp) {
|
|
1499
|
-
try {
|
|
1500
|
-
buildSessionMcpServers([], frame.delegationMcp);
|
|
1501
|
-
}
|
|
1502
|
-
catch {
|
|
1503
|
-
this.emit({ t: 'agent_opened', id: frame.id, ok: false, error: 'Delegation MCP configuration is unavailable' });
|
|
1504
|
-
return;
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
1496
|
// Rotation is server-driven. A busy reconnect resends the original process's outbox.
|
|
1508
1497
|
let existing = this.liveSession(frame.sessionRef);
|
|
1509
1498
|
// An open that would spawn the ref's adapter anew after it kept exiting on its own waits out the backoff (no crash loop).
|
|
@@ -1532,69 +1521,33 @@ export class AgentSessionManager {
|
|
|
1532
1521
|
retireForRefresh = existing;
|
|
1533
1522
|
existing = undefined;
|
|
1534
1523
|
}
|
|
1524
|
+
// A keeper session's label records the build that created its native session. Preserve that
|
|
1525
|
+
// binding across an adapter exit: the agent's current pointer may have advanced meanwhile,
|
|
1526
|
+
// but a cached reconnect must retry the recorded package before any explicit switch.
|
|
1527
|
+
const recordedBuildId = retireForRefresh
|
|
1528
|
+
? this.agents.placementOf?.(retireForRefresh.session)?.buildId
|
|
1529
|
+
: this.retainedRespawnBuilds.get(frame.sessionRef);
|
|
1535
1530
|
if (existing) {
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1531
|
+
// Spec §6.3: a session with a switch pending (a persistent intent: another adapter build, or off an older keeper-acp
|
|
1532
|
+
// generation) moves at this open — the only moment its spawn secrets are at hand — when it is strictly idle.
|
|
1533
|
+
if (await this.switchAtOpen(existing, frame))
|
|
1539
1534
|
return;
|
|
1540
|
-
|
|
1541
|
-
if (
|
|
1542
|
-
this.emit({ t: 'agent_opened', id: frame.id, ok: false, error: 'Managed delegation cannot reload the original session' });
|
|
1535
|
+
// Reusing a live session: neither the process nor the native session moved.
|
|
1536
|
+
if (!await this.resumeAndReply(existing, frame, false))
|
|
1543
1537
|
return;
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
}
|
|
1552
|
-
if (existing.session.liveness.turnRunning || activityIsBusy(existing.session.activityRaw)) {
|
|
1553
|
-
if (!delegation?.refreshPending) {
|
|
1554
|
-
try {
|
|
1555
|
-
await existing.session.note({ type: 'agent_delegation_refresh_pending' });
|
|
1556
|
-
}
|
|
1557
|
-
catch (err) {
|
|
1558
|
-
this.emit({ t: 'agent_opened', id: frame.id, ok: false, error: errorMessage(err) });
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
if (!await this.resumeAndReply(existing, frame, false))
|
|
1563
|
-
return;
|
|
1564
|
-
this.replyNoReplayNeeded(frame);
|
|
1565
|
-
return;
|
|
1566
|
-
}
|
|
1567
|
-
openFrame = { ...frame, nativeSessionId: existing.nativeSessionId, requireSessionLoad: true };
|
|
1568
|
-
retireForRefresh = existing;
|
|
1569
|
-
}
|
|
1570
|
-
else {
|
|
1571
|
-
// Spec §6.3: a session with a switch pending (a persistent intent: another adapter build, or off an older keeper-acp
|
|
1572
|
-
// generation) moves at this open — the only moment its spawn secrets are at hand — when it is strictly idle.
|
|
1573
|
-
if (await this.switchAtOpen(existing, frame))
|
|
1574
|
-
return;
|
|
1575
|
-
// Reusing a live session: neither the process nor the native session moved.
|
|
1576
|
-
if (!await this.resumeAndReply(existing, frame, false))
|
|
1577
|
-
return;
|
|
1578
|
-
// The session already exists, so any history it has was already streamed under whichever
|
|
1579
|
-
// open first established it (or never will be, if that open didn't ask for it) — replaying it
|
|
1580
|
-
// again here would duplicate what the platform already holds. But if THIS frame asked for
|
|
1581
|
-
// backfill, the server is waiting on exactly one `agent_backfill_done` for THIS open's id:
|
|
1582
|
-
// silence is not "no", it is a wait that never resolves. Answer it without replaying.
|
|
1583
|
-
this.replyNoReplayNeeded(frame);
|
|
1584
|
-
return;
|
|
1585
|
-
}
|
|
1538
|
+
// The session already exists, so any history it has was already streamed under whichever
|
|
1539
|
+
// open first established it (or never will be, if that open didn't ask for it) — replaying it
|
|
1540
|
+
// again here would duplicate what the platform already holds. But if THIS frame asked for
|
|
1541
|
+
// backfill, the server is waiting on exactly one `agent_backfill_done` for THIS open's id:
|
|
1542
|
+
// silence is not "no", it is a wait that never resolves. Answer it without replaying.
|
|
1543
|
+
this.replyNoReplayNeeded(frame);
|
|
1544
|
+
return;
|
|
1586
1545
|
}
|
|
1587
1546
|
const inFlight = this.opening.get(frame.sessionRef);
|
|
1588
1547
|
if (inFlight) {
|
|
1589
1548
|
// Piggyback on the attempt already running, and report its real failure — not a generic one.
|
|
1590
1549
|
const { live, error, agentRefused } = await inFlight;
|
|
1591
1550
|
if (live) {
|
|
1592
|
-
const delegation = live.session.delegation ?? undefined;
|
|
1593
|
-
if (!!frame.delegationMcp !== !!delegation ||
|
|
1594
|
-
(frame.delegationMcp && delegation?.leaseId !== delegationLeaseId(frame.delegationMcp))) {
|
|
1595
|
-
this.emit({ t: 'agent_opened', id: frame.id, ok: false, error: 'Managed delegation lease does not match the live session' });
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
1551
|
// Piggybacking on the attempt already running: a non-null `live` means that attempt was a
|
|
1599
1552
|
// real open (`session/load` or `session/new`), which this frame is reporting secondhand.
|
|
1600
1553
|
if (!await this.resumeAndReply(live, frame, true))
|
|
@@ -1623,9 +1576,9 @@ export class AgentSessionManager {
|
|
|
1623
1576
|
const attempt = (async () => {
|
|
1624
1577
|
if (retireForRefresh)
|
|
1625
1578
|
await this.retire(retireForRefresh, 'keep');
|
|
1626
|
-
return this.openSession(openFrame);
|
|
1579
|
+
return this.openSession(openFrame, recordedBuildId);
|
|
1627
1580
|
})()
|
|
1628
|
-
.catch((err) => ({ live: null, error:
|
|
1581
|
+
.catch((err) => ({ live: null, error: errorMessage(err) }))
|
|
1629
1582
|
.finally(() => releaseHold?.());
|
|
1630
1583
|
this.opening.set(frame.sessionRef, attempt);
|
|
1631
1584
|
let outcome;
|
|
@@ -1648,6 +1601,7 @@ export class AgentSessionManager {
|
|
|
1648
1601
|
// tombstone without anything ever closing it. Nothing but microtasks separates `attempt`
|
|
1649
1602
|
// resolving from the finally, and microtasks always drain before the next inbound frame.
|
|
1650
1603
|
if (outcome.live) {
|
|
1604
|
+
this.retainedRespawnBuilds.delete(frame.sessionRef);
|
|
1651
1605
|
// openSession returned successfully, having just done session/load or session/new.
|
|
1652
1606
|
if (!await this.resumeAndReply(outcome.live, frame, true))
|
|
1653
1607
|
return;
|
|
@@ -1658,6 +1612,8 @@ export class AgentSessionManager {
|
|
|
1658
1612
|
await this.streamBackfill(frame.id, outcome.live.session);
|
|
1659
1613
|
}
|
|
1660
1614
|
else {
|
|
1615
|
+
if (recordedBuildId !== undefined)
|
|
1616
|
+
this.retainedRespawnBuilds.set(frame.sessionRef, recordedBuildId);
|
|
1661
1617
|
this.emit({
|
|
1662
1618
|
t: 'agent_opened', id: frame.id, ok: false, error: outcome.error,
|
|
1663
1619
|
// Only when the agent is the one that said no, and then it is the one refusal that can be
|
|
@@ -1689,7 +1645,7 @@ export class AgentSessionManager {
|
|
|
1689
1645
|
*/
|
|
1690
1646
|
async switchAtOpen(live, frame) {
|
|
1691
1647
|
const agents = this.agents;
|
|
1692
|
-
if (!agents.switchSession || !agents.pendingSwitchFor
|
|
1648
|
+
if (!agents.switchSession || !agents.pendingSwitchFor)
|
|
1693
1649
|
return false;
|
|
1694
1650
|
// A switch spawns an adapter: new work, refused while the workstation drains (spec §8.2).
|
|
1695
1651
|
if (this.tasks && !this.tasks.accepting)
|
|
@@ -1847,7 +1803,7 @@ export class AgentSessionManager {
|
|
|
1847
1803
|
* "Move idle sessions" (Task 25's `agent_switch_idle`, spec §6.4): every held session of `agentId` that runs another build than
|
|
1848
1804
|
* `toBuild` (else the agent's current build) gets a persistent intent to move there; those already strictly idle get their
|
|
1849
1805
|
* `agent_reopen_request` at once, the others when they become idle. A session that can never be moved this way
|
|
1850
|
-
* ({@link unmovableWhy}: a local-fallback session,
|
|
1806
|
+
* ({@link unmovableWhy}: a local-fallback session, no native session, a target that already refused it) gets
|
|
1851
1807
|
* no intent — it would only be asked to reopen again and again — and is listed in `unmovable` with the reason. A `toBuild` that is
|
|
1852
1808
|
* not installed for the agent is refused (throws). Returns the refs registered, the ones a request went out for, and the unmovable.
|
|
1853
1809
|
*/
|
|
@@ -1889,7 +1845,7 @@ export class AgentSessionManager {
|
|
|
1889
1845
|
}
|
|
1890
1846
|
/**
|
|
1891
1847
|
* Why `live` can never be moved to `target` by a switch (spec §6.3 "当前限制"), or `undefined`: a session keeper-acp does not host
|
|
1892
|
-
* (the local fallback), a
|
|
1848
|
+
* (the local fallback), a session with no native session to load, or one a switch to the same build already
|
|
1893
1849
|
* found unmovable. `plain` is what the scan card and the result frame say (Task 25); `detail` is the switch's own words, for the
|
|
1894
1850
|
* log, when there are any.
|
|
1895
1851
|
*/
|
|
@@ -1897,8 +1853,6 @@ export class AgentSessionManager {
|
|
|
1897
1853
|
const placement = this.agents.placementOf?.call(this.agents, live.session);
|
|
1898
1854
|
if (!placement?.keeper)
|
|
1899
1855
|
return { plain: LOCAL_SESSION_UNMOVABLE };
|
|
1900
|
-
if (live.session.delegation)
|
|
1901
|
-
return { plain: 'it is a delegated session, which can\'t be moved to another build yet' };
|
|
1902
1856
|
if (!live.nativeSessionId)
|
|
1903
1857
|
return { plain: 'it has no saved conversation the new build could reload' };
|
|
1904
1858
|
const refused = this.agents.unmovableFor?.call(this.agents, live.sessionRef);
|
|
@@ -2128,7 +2082,6 @@ export class AgentSessionManager {
|
|
|
2128
2082
|
this.emit({ t: 'agent_opened', id: frame.id, ok: false, error: 'session was closed while opening' });
|
|
2129
2083
|
return false;
|
|
2130
2084
|
}
|
|
2131
|
-
const delegation = live.session.delegation ?? undefined;
|
|
2132
2085
|
const activityRaw = live.session.activityRaw;
|
|
2133
2086
|
this.emit({
|
|
2134
2087
|
t: 'agent_opened',
|
|
@@ -2140,13 +2093,6 @@ export class AgentSessionManager {
|
|
|
2140
2093
|
// Emitted only when true, the same `supportsSteering` convention as below: absent is the
|
|
2141
2094
|
// field's only "no", and it is also what an older peer sends.
|
|
2142
2095
|
...(reloaded ? { reloaded: true } : {}),
|
|
2143
|
-
// Delegation's non-secret fields, from processor state (Task 19: `agent_opened` is assembled from state).
|
|
2144
|
-
...(delegation ? { delegationExpiresAtEpochMs: delegation.expiresAtEpochMs } : {}),
|
|
2145
|
-
...(delegation?.refreshPending ? { delegationRefreshPending: true } : {}),
|
|
2146
|
-
...(this.serverAgentWire >= AGENT_WIRE_DELEGATION_RENEW_VERSION && delegation?.instanceId ? {
|
|
2147
|
-
delegationInstanceId: delegation.instanceId, delegationBindingId: delegation.bindingId,
|
|
2148
|
-
delegationRenewalRevision: delegation.renewalRevision,
|
|
2149
|
-
} : {}),
|
|
2150
2096
|
// The server learns this here and nowhere else. It used to be published only on the roster in
|
|
2151
2097
|
// the `agent_hello` handshake, which is a snapshot taken *before* any agent was connected to —
|
|
2152
2098
|
// and the capability can only be observed on a live `initialize`, whose record
|
|
@@ -2223,7 +2169,7 @@ export class AgentSessionManager {
|
|
|
2223
2169
|
});
|
|
2224
2170
|
return true;
|
|
2225
2171
|
}
|
|
2226
|
-
async openSession(frame) {
|
|
2172
|
+
async openSession(frame, recordedBuildId) {
|
|
2227
2173
|
// A previous incarnation of this ref may still be closing: its adapter stopped and its transport session (raw, outbox) kept or
|
|
2228
2174
|
// released — the new spawn goes under the same key (see {@link retiring}). This is the first statement on purpose — before the
|
|
2229
2175
|
// agent spawns, before anything touches the transport. The one-time legacy journal migration lands before any open too.
|
|
@@ -2272,13 +2218,14 @@ export class AgentSessionManager {
|
|
|
2272
2218
|
});
|
|
2273
2219
|
};
|
|
2274
2220
|
try {
|
|
2275
|
-
const result = await this.agents.open({
|
|
2221
|
+
const result = await this.agents.open({
|
|
2222
|
+
...openSpecOf(frame),
|
|
2223
|
+
...(recordedBuildId !== undefined ? { recordedBuildId } : {}),
|
|
2224
|
+
onInstallOutcome,
|
|
2225
|
+
seqBase: () => resumeFrom,
|
|
2226
|
+
});
|
|
2276
2227
|
session = result.session;
|
|
2277
2228
|
nativeSessionId = result.nativeSessionId;
|
|
2278
|
-
if (frame.delegationMcp && !nativeSessionId) {
|
|
2279
|
-
await session.close();
|
|
2280
|
-
return { live: null, error: 'Managed delegation requires a native session ID' };
|
|
2281
|
-
}
|
|
2282
2229
|
}
|
|
2283
2230
|
catch (err) {
|
|
2284
2231
|
await reportFailedInstall();
|
|
@@ -2288,7 +2235,7 @@ export class AgentSessionManager {
|
|
|
2288
2235
|
// on `session/new` arrives as; see {@link OpenOutcome.agentRefused}.
|
|
2289
2236
|
return {
|
|
2290
2237
|
live: null,
|
|
2291
|
-
error:
|
|
2238
|
+
error: errorMessage(err),
|
|
2292
2239
|
// The code comes off the marker, not off `errorMessage(err)` above: the wrapper captured it
|
|
2293
2240
|
// from the original rejection at the moment that object still existed.
|
|
2294
2241
|
...(err instanceof AgentRefusedSessionError
|
|
@@ -2347,7 +2294,7 @@ export class AgentSessionManager {
|
|
|
2347
2294
|
return { live };
|
|
2348
2295
|
}
|
|
2349
2296
|
/** A session joins: held, registered with the TaskRegistry, its frames and its outbox wired to the sender. */
|
|
2350
|
-
register(live) {
|
|
2297
|
+
register(live, adoption) {
|
|
2351
2298
|
const session = live.session;
|
|
2352
2299
|
this.detached.delete(live.sessionRef);
|
|
2353
2300
|
this.sessions.set(live.sessionRef, live);
|
|
@@ -2357,7 +2304,7 @@ export class AgentSessionManager {
|
|
|
2357
2304
|
() => this.retire(live, durable ? 'detach' : 'destroy'),
|
|
2358
2305
|
// 两个层级量取或:本进程发出的提示词是否在途(agent 自己也许在续跑一个我们没发起的回合,
|
|
2359
2306
|
// 那一种由第二项回答),以及 agent 自报的状态。两者都不累积,所以没有可泄漏的状态。
|
|
2360
|
-
() => live.session.liveness.turnRunning || activityIsBusy(live.session.activityRaw), { durable });
|
|
2307
|
+
() => live.session.liveness.turnRunning || activityIsBusy(live.session.activityRaw), { durable, adoption });
|
|
2361
2308
|
// Everything the session produces arrives as processor frames (Task 19): numbered by the processor, projected for the
|
|
2362
2309
|
// connected server here, recorded in the outbox (the numbered ones) and sent from it (Task 20).
|
|
2363
2310
|
live.unwatch = session.outbox.onChange(() => this.pump());
|
|
@@ -2843,6 +2790,7 @@ export class AgentSessionManager {
|
|
|
2843
2790
|
void this.agents.cancelSwitch?.({ sessionRef }).catch(() => false);
|
|
2844
2791
|
this.reopenRequests.delete(sessionRef);
|
|
2845
2792
|
this.adapterCrashes.delete(sessionRef);
|
|
2793
|
+
this.retainedRespawnBuilds.delete(sessionRef);
|
|
2846
2794
|
// Closing a ref that is still spawning: nothing to close yet, so tombstone it — openSession
|
|
2847
2795
|
// closes the session (and destroys its journal) the moment the bridge hands it over.
|
|
2848
2796
|
//
|
|
@@ -3,6 +3,7 @@ import { connectEncryptedChannel } from '../connection/encryptedChannel.js';
|
|
|
3
3
|
import { AGENT_WIRE_MIN_VERSION, AGENT_WIRE_RESUME_OFFER_VERSION, AGENT_WIRE_VERSION, decodeAgentFrame, encodeAgentFrame, outboundAgentFrameRejection, } from '../protocol/agent.js';
|
|
4
4
|
import { bridgesForWire, helloRoster } from '../protocol/common.js';
|
|
5
5
|
import { workstationChannelUrl } from './url.js';
|
|
6
|
+
import { DeviceEventRouter } from '../runtime/events.js';
|
|
6
7
|
/** Binds daemon-scoped sessions only to the encrypted agent channel's sender. */
|
|
7
8
|
export class AgentChannel {
|
|
8
9
|
options;
|
|
@@ -24,7 +25,14 @@ export class AgentChannel {
|
|
|
24
25
|
});
|
|
25
26
|
// Hoisted rather than inlined twice: `closeAll` compares it by identity to decide whether the
|
|
26
27
|
// binding it is being asked to drop is still this connection's. See AgentSessionManager.closeAll.
|
|
27
|
-
const
|
|
28
|
+
const registrationId = this.options.registrationId;
|
|
29
|
+
const events = registrationId ? new DeviceEventRouter((_recipient, event) => channel.send(event.payload), new Set([registrationId])) : undefined;
|
|
30
|
+
const send = (frame) => {
|
|
31
|
+
if (events && registrationId)
|
|
32
|
+
events.publish({ scope: 'registration', registrationId, payload: frame });
|
|
33
|
+
else
|
|
34
|
+
channel.send(frame);
|
|
35
|
+
};
|
|
28
36
|
// The socket's unsent bytes: the manager's socket gate holds session frames back above 1 MiB so the heartbeat (written by the
|
|
29
37
|
// channel itself) never queues behind a backlog (spec §5.6).
|
|
30
38
|
void dispatchAgentChannelFrames(channel.messages, send, this.options.agents, () => channel.close(), () => channel.bufferedAmount);
|