@platless/axom-cli 0.6.36 → 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 +7 -1
- package/dist/axom-patch/cli/README.md +31 -2
- package/dist/axom-patch/cli/account-limits/generate.py +2 -2
- package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
- package/dist/axom-patch/cli/codex-terminal-output.json +2 -2
- package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +1 -1
- 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 +137 -41
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +5 -88
- package/dist/axom-patch/cli/qoder-native-acp.json +23 -23
- package/dist/axom-patch/cli/runtime.json +3 -1
- 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 +7 -7
- package/dist/axom-patch/cli/zcode-env-account.json +58 -0
- package/dist/axom-patch/cli/zcode-models.json +9 -9
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -10
- package/dist/cli.js +9 -11
- package/dist/commands/auth.js +4 -1
- package/dist/commands/update.js +68 -7
- package/dist/commands/workstation.js +94 -223
- 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 +2 -2
- package/dist/keepers/terminal/keeper.mjs +1089 -673
- package/dist/keepers/terminal/manifest.json +4 -4
- package/dist/native/darwin-x64/axom-quic +0 -0
- package/dist/workstation/acp/acpAgents.js +11 -4
- package/dist/workstation/acp/outbox.js +12 -5
- package/dist/workstation/acp/permission.js +2 -2
- package/dist/workstation/acp/processor/checkpoint.js +4 -3
- package/dist/workstation/acp/processor/control.js +2 -2
- package/dist/workstation/acp/processor/jsonrpc.js +8 -0
- package/dist/workstation/acp/processor/session.js +3 -2
- package/dist/workstation/acp/processor/state.js +1 -0
- package/dist/workstation/acp/processor/transport/local.js +19 -3
- package/dist/workstation/acp/processor/updates.js +25 -0
- package/dist/workstation/acp/processorSession.js +12 -1
- package/dist/workstation/acp/registry/catalog.js +6 -6
- package/dist/workstation/acp/test/keeperWorld.js +4 -3
- package/dist/workstation/agentSessions.js +76 -23
- package/dist/workstation/channels/agent.js +9 -1
- package/dist/workstation/channels/computer.js +27 -4
- package/dist/workstation/channels/management.js +36 -5
- package/dist/workstation/config.js +18 -6
- package/dist/workstation/control/bindingStore.js +4 -0
- package/dist/workstation/control/commands.js +1 -0
- package/dist/workstation/control/daemonIdentity.js +90 -0
- package/dist/workstation/control/deviceCommands.js +191 -0
- package/dist/workstation/control/deviceProtocol.js +68 -0
- package/dist/workstation/control/locks.js +51 -4
- package/dist/workstation/control/rootFormat.js +16 -0
- package/dist/workstation/control/runtimeClient.js +60 -5
- package/dist/workstation/control/runtimeHost.js +96 -9
- package/dist/workstation/control/serverIdentity.js +2 -2
- package/dist/workstation/control/updateTransition.js +91 -0
- package/dist/workstation/daemon.js +262 -360
- package/dist/workstation/daemonctl.js +150 -12
- 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/automationPreparation.js +1 -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 +2 -1
- package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
- package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
- package/dist/workstation/desktopControl/deviceSockets.js +2 -2
- package/dist/workstation/desktopControl/helperProcess.js +250 -1
- package/dist/workstation/desktopControl/hostGrant.js +65 -0
- package/dist/workstation/desktopControl/lobby.js +4 -2
- package/dist/workstation/desktopControl/macProvider.js +41 -40
- package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
- package/dist/workstation/desktopControl/nativeProvider.js +31 -5
- package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
- package/dist/workstation/desktopControl/protocol.js +15 -6
- package/dist/workstation/desktopControl/supervisor.js +110 -17
- package/dist/workstation/desktopControl/wallpaper.js +26 -6
- package/dist/workstation/exec/policy.js +5 -0
- package/dist/workstation/files/client.js +10 -1
- package/dist/workstation/files/host.js +9 -2
- 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/client.js +2 -2
- package/dist/workstation/keepers/kit/launcher.js +80 -4
- 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/oauth/deviceAuthorization.js +31 -17
- package/dist/workstation/protocol/management.js +23 -4
- package/dist/workstation/rd/artifact.js +4 -3
- package/dist/workstation/rd/control.js +3 -5
- package/dist/workstation/rd/host.js +213 -0
- package/dist/workstation/rd/migrationReadiness.js +46 -0
- package/dist/workstation/rd/provider.js +44 -10
- package/dist/workstation/rd/state.js +14 -2
- package/dist/workstation/rd/update.js +5 -8
- package/dist/workstation/rd/upkeep.js +44 -2
- 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 +93 -1
- package/dist/workstation/service/launchd.js +1 -0
- package/dist/workstation/service/systemd.js +1 -0
- package/dist/workstation/systemMetrics.js +3 -0
- 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/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 +80 -10
- package/dist/workstation/update/legacyBridge.js +414 -0
- package/dist/workstation/update/legacyOwner.js +39 -0
- package/dist/workstation/update/npmInstaller.js +2 -2
- package/dist/workstation/update/privateDurability.js +61 -0
- package/dist/workstation/update/recoveryPlan.js +124 -0
- package/dist/workstation/update/runUpdate.js +7 -1
- package/dist/workstation/update/taskRegistry.js +161 -3
- package/dist/workstation/update/watchdog.js +27427 -14410
- package/dist/workstation/update/watchdogProcess.js +72 -0
- package/package.json +7 -3
- package/dist/native/darwin-arm64/axom-quic +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"kind": "terminal",
|
|
3
|
-
"protocolVersion":
|
|
4
|
-
"buildVersion": "
|
|
3
|
+
"protocolVersion": 2,
|
|
4
|
+
"buildVersion": "80542c28e2e2ce561c3acc8dab28422a0df482b778665e982272e923261c775c",
|
|
5
5
|
"daemonProtocol": [
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
2,
|
|
7
|
+
2
|
|
8
8
|
]
|
|
9
9
|
}
|
|
Binary file
|
|
@@ -510,6 +510,7 @@ export class AcpAgents {
|
|
|
510
510
|
/** {@link OPEN_INSTALL_BUDGET_MS}, overridable for tests. */
|
|
511
511
|
openInstallBudgetMs;
|
|
512
512
|
cacheDir;
|
|
513
|
+
ownership;
|
|
513
514
|
transports;
|
|
514
515
|
generations;
|
|
515
516
|
/** The once-per-daemon adoption's discovery ({@link adoptKeeperSessions}). */
|
|
@@ -583,9 +584,10 @@ export class AcpAgents {
|
|
|
583
584
|
this.patchAssetRoot = options.patchAssetRoot;
|
|
584
585
|
this.commandRetryFirstBroadcastMs = options.commandRetryFirstBroadcastMs ?? COMMAND_RETRY_FIRST_BROADCAST_MS;
|
|
585
586
|
this.openInstallBudgetMs = options.openInstallBudgetMs ?? OPEN_INSTALL_BUDGET_MS;
|
|
586
|
-
this.adapterRoot = path.join(options.cacheDir, 'acp-adapters');
|
|
587
|
+
this.adapterRoot = path.join(options.adapterCacheDirectory ?? options.cacheDir, 'acp-adapters');
|
|
587
588
|
this.builds = new BuildStore(this.adapterRoot);
|
|
588
589
|
this.cacheDir = options.cacheDir;
|
|
590
|
+
this.ownership = options.ownership;
|
|
589
591
|
this.transports = { ...options.transports };
|
|
590
592
|
this.generations = options.keeperGenerations;
|
|
591
593
|
this.initializeTimeoutMs = options.initializeTimeoutMs ?? DEFAULT_SESSION_INITIALIZE_TIMEOUT_MS;
|
|
@@ -1843,6 +1845,8 @@ export class AcpAgents {
|
|
|
1843
1845
|
* be listed. One at a time; never rejects (failures are logged).
|
|
1844
1846
|
*/
|
|
1845
1847
|
collectBuilds(options = {}) {
|
|
1848
|
+
if (this.ownership)
|
|
1849
|
+
return Promise.resolve({ removed: [], discarded: [], kept: [], legacyRemoved: [], skipped: 'Device-wide build references are incomplete' });
|
|
1846
1850
|
const previous = this.collecting;
|
|
1847
1851
|
let run;
|
|
1848
1852
|
run = (async () => {
|
|
@@ -2065,15 +2069,18 @@ export class AcpAgents {
|
|
|
2065
2069
|
}
|
|
2066
2070
|
/** The fallback transport (spec §5.7), made on first use. */
|
|
2067
2071
|
localTransport() {
|
|
2068
|
-
this.transports.local ??= new LocalTransport({ configDir: this.cacheDir });
|
|
2072
|
+
this.transports.local ??= new LocalTransport({ configDir: this.cacheDir, ownership: this.ownership });
|
|
2069
2073
|
return this.transports.local;
|
|
2070
2074
|
}
|
|
2071
2075
|
/** Daemon shutdown: keeper-acp's connection is dropped (its adapters keep running); local adapters are stopped. */
|
|
2072
|
-
closeTransports() {
|
|
2076
|
+
async closeTransports() {
|
|
2073
2077
|
this.clearQuotaReaders();
|
|
2074
2078
|
this.transports.keeper?.close();
|
|
2075
2079
|
this.generations?.close();
|
|
2076
|
-
this.transports.local
|
|
2080
|
+
if (this.transports.local instanceof LocalTransport)
|
|
2081
|
+
await this.transports.local.dispose();
|
|
2082
|
+
else
|
|
2083
|
+
this.transports.local?.close();
|
|
2077
2084
|
}
|
|
2078
2085
|
clearQuotaReaders(agentId) {
|
|
2079
2086
|
for (const [key, reader] of this.quotaReaders)
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* ## Residual (documented, not fixed)
|
|
23
23
|
*
|
|
24
24
|
* While the producing window is full — a server parked on an approval with ≥ 256 root frames held, a connection that is down, an
|
|
25
|
-
* applier that never acknowledges — the record loop folds nothing:
|
|
25
|
+
* applier that never acknowledges — the record loop folds nothing: an already-admitted steer or config RPC waits for the processor's reply and can
|
|
26
26
|
* time out on the server; an `agent_interrupt` note is folded (and the `session/cancel` written) only once the server unparks.
|
|
27
27
|
*/
|
|
28
28
|
/** Unacknowledged events per session (spec §5.6, `W_EVT`). `ACK_EVERY_EVENTS(32) < W_EVT ≤ HOLD_OFF_CAPACITY(512)`. */
|
|
@@ -278,16 +278,22 @@ export class SessionOutbox {
|
|
|
278
278
|
}
|
|
279
279
|
/**
|
|
280
280
|
* A new connection: everything recorded so far — sent on an earlier connection or produced while none was bound — is held until
|
|
281
|
-
* the server reopens the ref and says how far it got ({@link SENT_EARLIER})
|
|
282
|
-
*
|
|
281
|
+
* the server reopens the ref and says how far it got ({@link SENT_EARLIER}). Newly recorded frames wait behind those older
|
|
282
|
+
* entries so a higher seq cannot advance the server's watermark past retained output.
|
|
283
283
|
*/
|
|
284
284
|
connectionChanged() {
|
|
285
285
|
for (const item of this.items)
|
|
286
286
|
item.sentOn = SENT_EARLIER;
|
|
287
287
|
}
|
|
288
|
-
/** The first entry never sent
|
|
288
|
+
/** The first entry never sent, without overtaking an older entry held for reopen. */
|
|
289
289
|
nextUnsent() {
|
|
290
|
-
|
|
290
|
+
for (const item of this.items) {
|
|
291
|
+
if (item.sentOn === SENT_EARLIER)
|
|
292
|
+
return undefined;
|
|
293
|
+
if (item.sentOn === null)
|
|
294
|
+
return item;
|
|
295
|
+
}
|
|
296
|
+
return undefined;
|
|
291
297
|
}
|
|
292
298
|
/** Resolves when this life's unacknowledged backlog is below the window (immediately once {@link stopWaiting} ran). */
|
|
293
299
|
room() {
|
|
@@ -295,6 +301,7 @@ export class SessionOutbox {
|
|
|
295
301
|
return undefined;
|
|
296
302
|
return new Promise((resolve) => { this.roomWaiters.add(resolve); });
|
|
297
303
|
}
|
|
304
|
+
/** Whether another raw record can be folded now, without registering a waiter. */
|
|
298
305
|
hasRoom() {
|
|
299
306
|
if (this.stopped)
|
|
300
307
|
return true;
|
|
@@ -174,7 +174,8 @@ export function buildPermissionRequestedEvent(input) {
|
|
|
174
174
|
* forced into a selection the user never made.
|
|
175
175
|
*/
|
|
176
176
|
export async function handleRequestPermission(input, sink) {
|
|
177
|
-
|
|
177
|
+
const optionIds = new Set(input.options.map((option) => option.optionId));
|
|
178
|
+
if (input.options.length === 0 || optionIds.size !== input.options.length) {
|
|
178
179
|
return { outcome: 'rejected' };
|
|
179
180
|
}
|
|
180
181
|
if (input.signal?.aborted) {
|
|
@@ -192,7 +193,6 @@ export async function handleRequestPermission(input, sink) {
|
|
|
192
193
|
console.error(`[acp] refusing to raise a permission card that fails validateWireEvent: ${rejection.reason}`);
|
|
193
194
|
return { outcome: 'rejected' };
|
|
194
195
|
}
|
|
195
|
-
const optionIds = new Set(input.options.map((option) => option.optionId));
|
|
196
196
|
return new Promise((resolve) => {
|
|
197
197
|
let settled = false;
|
|
198
198
|
const settle = (decision) => {
|
|
@@ -8,9 +8,10 @@ import { initialState, PROCESSOR_STATE_SCHEMA } from './state.js';
|
|
|
8
8
|
* 6 — child status precedes process results; ended calls release data;
|
|
9
9
|
* 7 — install outcomes become step events;
|
|
10
10
|
* 8 — loaded-session levels fold live; output gaps become steps;
|
|
11
|
-
* 9 — adapter compatibility folds moved out and retired optional state removed
|
|
11
|
+
* 9 — adapter compatibility folds moved out and retired optional state removed;
|
|
12
|
+
* 10 — ended replay scopes cannot leak late level snapshots into the live journal.
|
|
12
13
|
*/
|
|
13
|
-
export const PROCESSOR_VERSION =
|
|
14
|
+
export const PROCESSOR_VERSION = 10;
|
|
14
15
|
export const CHECKPOINT_SCHEMA = 1;
|
|
15
16
|
export class CheckpointUnreadableError extends Error {
|
|
16
17
|
}
|
|
@@ -64,7 +65,7 @@ export function decodeCheckpoint(blob) {
|
|
|
64
65
|
|| !('activity' in state)) {
|
|
65
66
|
throw new CheckpointUnreadableError('checkpoint state has no open-session layer (Task 17b)');
|
|
66
67
|
}
|
|
67
|
-
return { schema: CHECKPOINT_SCHEMA, processorVersion, offset, state: clone({ ...(state.schema === 4 ? upgradeStateFromSchema4(state) : state), ...(!Object.hasOwn(state, 'draftSubagents') ? { draftSubagents: { children: {}, overflow: false } } : {}) }) };
|
|
68
|
+
return { schema: CHECKPOINT_SCHEMA, processorVersion, offset, state: clone({ ...(state.schema === 4 ? upgradeStateFromSchema4(state) : state), ...(!Object.hasOwn(state, 'draftSubagents') ? { draftSubagents: { children: {}, overflow: false } } : {}), ...(!Object.hasOwn(state, 'closedReplayScopes') ? { closedReplayScopes: {} } : {}) }) };
|
|
68
69
|
}
|
|
69
70
|
/** Read only the current neutral keys; retired adapter state is deliberately not copied. */
|
|
70
71
|
function upgradeStateFromSchema4(state) {
|
|
@@ -3,7 +3,7 @@ import { newSessionMetaFor } from '../activityReporting.js';
|
|
|
3
3
|
import { flatChoicesOf, modesAlreadyRepresented, modelsAlreadyRepresented, normalizeConfigOptions, SYNTHESIZED_MODES_CONFIG_ID, SYNTHESIZED_MODEL_CONFIG_ID } from '../configOptions.js';
|
|
4
4
|
import { ASYNC_TASK_STOP_METHOD, mapSteerOutcome, STEER_METHOD } from '../steering.js';
|
|
5
5
|
import { emitEvent, isRecord } from './emit.js';
|
|
6
|
-
import { settlementErrorCode } from './jsonrpc.js';
|
|
6
|
+
import { jsonRpcErrorText, settlementErrorCode } from './jsonrpc.js';
|
|
7
7
|
import { sameCause } from './record.js';
|
|
8
8
|
import { onLaunchNote } from './runtime.js';
|
|
9
9
|
import { bindRootSession, closeErrorText, connected } from './session.js';
|
|
@@ -58,7 +58,7 @@ export function settleOpen(state, entry, settlement, out) {
|
|
|
58
58
|
const { mode } = ctx;
|
|
59
59
|
if (settlement.outcome !== 'result') {
|
|
60
60
|
const code = settlementErrorCode(settlement);
|
|
61
|
-
const error = settlement.outcome === 'error' ? settlement.error
|
|
61
|
+
const error = settlement.outcome === 'error' ? jsonRpcErrorText(settlement.error) : closeErrorText(state);
|
|
62
62
|
state.open = { status: 'failed', mode, error, ...(code !== undefined ? { code } : {}) };
|
|
63
63
|
endHistory(state, out, false);
|
|
64
64
|
// A failed open closes the attached session, and its repair with it (`AcpSession.close`).
|
|
@@ -132,6 +132,14 @@ export function settlementOf(response) {
|
|
|
132
132
|
const { code, message, data } = response.error;
|
|
133
133
|
return { outcome: 'error', error: { code, message, data } };
|
|
134
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* The text an agent's error is shown as. The ACP SDK's `RequestError.internalError(details)` puts the
|
|
137
|
+
* real cause only in `data.details` behind a generic "Internal error", so a string detail is kept.
|
|
138
|
+
*/
|
|
139
|
+
export function jsonRpcErrorText(error) {
|
|
140
|
+
const details = isRecord(error.data) && typeof error.data.details === 'string' ? error.data.details.trim().slice(0, 2000) : '';
|
|
141
|
+
return details && !error.message.includes(details) ? `${error.message}: ${details}` : error.message;
|
|
142
|
+
}
|
|
135
143
|
/** `jsonRpcErrorCode(err)` for a settlement: the code an agent answered with, if a frame may carry it. */
|
|
136
144
|
export function settlementErrorCode(settlement) {
|
|
137
145
|
return settlement.outcome === 'error' ? jsonRpcErrorCodeOf(settlement.error.code) : undefined;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { buildElicitationRequestedEvent, buildPermissionRequestedEvent, elicitationFieldKeys, isBlank, permissionRequestId, } from '../permission.js';
|
|
3
3
|
import { validateWireEvent } from '../validate.js';
|
|
4
4
|
import { isRecord, nextSeq } from './emit.js';
|
|
5
|
-
import { inboundKey, settlementErrorCode } from './jsonrpc.js';
|
|
5
|
+
import { inboundKey, jsonRpcErrorText, settlementErrorCode } from './jsonrpc.js';
|
|
6
6
|
import { sameCause } from './record.js';
|
|
7
7
|
import { publishCallbacks, resetRuntime } from './runtime.js';
|
|
8
8
|
import { getOwn, initialCallbacks, initialMessage, PENDING_PERMISSION_LIMIT, SETTLED_PERMISSION_LIMIT, } from './state.js';
|
|
@@ -51,6 +51,7 @@ export function resetForSpawn(state) {
|
|
|
51
51
|
state.steered = [];
|
|
52
52
|
state.asyncTasks = {};
|
|
53
53
|
state.draftSubagents = { children: {}, overflow: false };
|
|
54
|
+
state.closedReplayScopes = {};
|
|
54
55
|
resetRuntime(state);
|
|
55
56
|
state.turns = [];
|
|
56
57
|
state.exactExecutionUnconfirmed = false;
|
|
@@ -246,7 +247,7 @@ export function settlePrompt(state, entry, settlement, out) {
|
|
|
246
247
|
// The one exit where the agent itself answered (`TurnResult.agentRefused`).
|
|
247
248
|
const errorCode = settlementErrorCode(settlement);
|
|
248
249
|
finishTurn(state, out, index, 'failed', {
|
|
249
|
-
error: settlement.error
|
|
250
|
+
error: jsonRpcErrorText(settlement.error), agentRefused: true, errorCode, executionEnded: errorCode !== undefined,
|
|
250
251
|
});
|
|
251
252
|
return;
|
|
252
253
|
}
|
|
@@ -19,6 +19,7 @@ import { AcpSession } from '../../../keepers/acp/session.js';
|
|
|
19
19
|
import { KeeperMethodError } from '../../../keepers/kit/protocol.js';
|
|
20
20
|
import { StreamLog } from '../../../keepers/kit/streamLog.js';
|
|
21
21
|
import { DAEMON_FRAME_INDEX } from '../../outbox.js';
|
|
22
|
+
import { assertOwnedDirectory } from '../../../runtime/ownership.js';
|
|
22
23
|
import { RawTruncatedError, checkpointBlob, rawRecordOf, } from './types.js';
|
|
23
24
|
const DEFAULT_WINDOW = 32;
|
|
24
25
|
/** Longest directory name used as is (file systems cap a name at 255 bytes); longer keys get a hashed name. */
|
|
@@ -43,12 +44,15 @@ export class LocalTransport {
|
|
|
43
44
|
logLine;
|
|
44
45
|
constructor(options) {
|
|
45
46
|
this.options = options;
|
|
46
|
-
this.root = path.join(options.configDir, 'agents', 'local');
|
|
47
|
+
this.root = options.outboxDirectory ?? path.join(options.configDir, 'agents', 'local');
|
|
47
48
|
this.now = options.now ?? Date.now;
|
|
48
49
|
this.logLine = options.log ?? (() => { });
|
|
49
50
|
}
|
|
50
51
|
open(sessionKey, label, baseOffset, orphan) {
|
|
51
52
|
const dir = localSessionDir(this.root, sessionKey);
|
|
53
|
+
assertOwnedDirectory(dir, this.options.ownership, 'local-outbox');
|
|
54
|
+
if (this.options.ownership)
|
|
55
|
+
label = { ...label, registrationId: this.options.ownership.registrationId };
|
|
52
56
|
if (path.basename(dir).startsWith('~')) {
|
|
53
57
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
54
58
|
writeFileSync(path.join(dir, KEY_FILE), sessionKey, { mode: 0o600 });
|
|
@@ -71,12 +75,15 @@ export class LocalTransport {
|
|
|
71
75
|
async importOutbox(key, records) {
|
|
72
76
|
const sessionKey = sessionKeyOf(key);
|
|
73
77
|
const dir = localSessionDir(this.root, sessionKey);
|
|
74
|
-
if (this.entries.has(sessionKey) || existsSync(dir))
|
|
78
|
+
if (this.entries.has(sessionKey) || existsSync(dir)) {
|
|
79
|
+
assertOwnedDirectory(dir, this.options.ownership, 'local-outbox');
|
|
75
80
|
return false;
|
|
81
|
+
}
|
|
76
82
|
mkdirSync(this.root, { recursive: true, mode: 0o700 });
|
|
77
83
|
const staging = path.join(this.root, `.staging-${randomUUID()}`);
|
|
78
84
|
try {
|
|
79
85
|
mkdirSync(staging, { mode: 0o700 });
|
|
86
|
+
assertOwnedDirectory(staging, this.options.ownership);
|
|
80
87
|
if (path.basename(dir).startsWith('~'))
|
|
81
88
|
writeFileSync(path.join(staging, KEY_FILE), sessionKey, { mode: 0o600 });
|
|
82
89
|
const log = StreamLog.open({ dir: staging, now: this.now, immediateKinds: [] });
|
|
@@ -127,6 +134,13 @@ export class LocalTransport {
|
|
|
127
134
|
catch {
|
|
128
135
|
continue;
|
|
129
136
|
}
|
|
137
|
+
try {
|
|
138
|
+
assertOwnedDirectory(path.join(this.root, name), this.options.ownership, 'local-outbox');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
this.logLine('ACP local state skipped: registration owner is unknown or mismatched');
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
130
144
|
if (!this.entries.has(key))
|
|
131
145
|
keys.push(key);
|
|
132
146
|
}
|
|
@@ -135,8 +149,10 @@ export class LocalTransport {
|
|
|
135
149
|
entry(key) {
|
|
136
150
|
const sessionKey = sessionKeyOf(key);
|
|
137
151
|
const existing = this.entries.get(sessionKey);
|
|
138
|
-
if (existing)
|
|
152
|
+
if (existing) {
|
|
153
|
+
assertOwnedDirectory(existing.dir, this.options.ownership, 'local-outbox');
|
|
139
154
|
return existing;
|
|
155
|
+
}
|
|
140
156
|
if (this.orphans().includes(sessionKey))
|
|
141
157
|
return this.open(sessionKey, {}, undefined, true);
|
|
142
158
|
throw new KeeperMethodError(AcpErrorCode.notFound, 'ACP session not found');
|
|
@@ -17,6 +17,10 @@ const DRAFT_NAMES = new Set(DRAFT_SESSION_UPDATE_NAMES);
|
|
|
17
17
|
const LEVEL_UPDATES = new Set([
|
|
18
18
|
'available_commands_update', 'current_mode_update', 'config_option_update', 'usage_update', 'session_info_update',
|
|
19
19
|
]);
|
|
20
|
+
/** Only configuration snapshots: terminal usage, titles and actual output remain observable. */
|
|
21
|
+
const CLOSED_REPLAY_LEVELS = new Set([
|
|
22
|
+
'available_commands_update', 'current_mode_update', 'config_option_update',
|
|
23
|
+
]);
|
|
20
24
|
const SESSION_NOTIFICATION = INBOUND_NOTIFICATION_SCHEMAS['session/update'];
|
|
21
25
|
function stringField(payload, key) {
|
|
22
26
|
const value = payload[key];
|
|
@@ -49,6 +53,15 @@ export function onSessionUpdate(state, params, out, t) {
|
|
|
49
53
|
state.diagnostics.droppedUpdates += 1;
|
|
50
54
|
return;
|
|
51
55
|
}
|
|
56
|
+
// A load may replay and end a child that the host did not request as backfill.
|
|
57
|
+
// Its later configuration snapshot is still historical, not a live child event.
|
|
58
|
+
// Keep this narrow: unknown scopes and actual content/tool/lifecycle events retain
|
|
59
|
+
// their normal routing and ownership checks. A live spawn removes the tombstone.
|
|
60
|
+
if (sessionId !== root && getOwn(state.closedReplayScopes, sessionId) && CLOSED_REPLAY_LEVELS.has(String(update.sessionUpdate))) {
|
|
61
|
+
state.diagnostics.droppedUpdates += 1;
|
|
62
|
+
out.unjournaled({ t: 'processor.closed_replay_level', sessionId, sessionUpdate: update.sessionUpdate });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
52
65
|
const variant = update.sessionUpdate;
|
|
53
66
|
const draft = typeof variant === 'string' && DRAFT_NAMES.has(variant);
|
|
54
67
|
if (!draft && sessionId === root) {
|
|
@@ -188,6 +201,11 @@ export function emitSessionEvent(state, out, event) {
|
|
|
188
201
|
function observe(state, event) {
|
|
189
202
|
if (event.eventName === 'subagent_spawned') {
|
|
190
203
|
observeSubagentSpawned(state, event.acpSessionId ?? state.rootSessionId, event.payload.subagentSessionId);
|
|
204
|
+
const child = event.payload.subagentSessionId;
|
|
205
|
+
if (typeof child === 'string' && getOwn(state.permissionOrigins, child) &&
|
|
206
|
+
getOwn(state.permissionParents, child) === (event.acpSessionId ?? state.rootSessionId)) {
|
|
207
|
+
delete state.closedReplayScopes[child];
|
|
208
|
+
}
|
|
191
209
|
}
|
|
192
210
|
trackAsyncTask(state, event);
|
|
193
211
|
trackDraftSubagent(state, event);
|
|
@@ -361,6 +379,13 @@ function emitHistory(state, history, out, event, sessionId) {
|
|
|
361
379
|
observeSubagentSpawned(state, scoped.acpSessionId ?? history.rootSessionId, scoped.payload.subagentSessionId, history.rootSessionId);
|
|
362
380
|
}
|
|
363
381
|
trackDraftSubagent(state, scoped);
|
|
382
|
+
if (scoped.eventName === 'subagent_state_update' && DRAFT_TERMINALS.has(String(scoped.payload.state))) {
|
|
383
|
+
const child = scoped.payload.subagentSessionId;
|
|
384
|
+
// Share the lifecycle tracker's bounded, validated identity set.
|
|
385
|
+
if (typeof child === 'string' && getOwn(state.draftSubagents.children, child) === false) {
|
|
386
|
+
putOwn(state.closedReplayScopes, child, true);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
364
389
|
observeBackgroundWriter(state, scoped);
|
|
365
390
|
out.unjournaled({
|
|
366
391
|
t: 'processor.history_event',
|
|
@@ -13,6 +13,7 @@ import { pendingRequestIds } from './processor/session.js';
|
|
|
13
13
|
import { steeredIds } from './processor/control.js';
|
|
14
14
|
import { RawTruncatedError } from './processor/transport/types.js';
|
|
15
15
|
import { AcpKeeperUnavailableError } from '../keepers/acp/client.js';
|
|
16
|
+
import { RegistrationOwnershipError } from '../runtime/ownership.js';
|
|
16
17
|
import { AcpErrorCode } from '../keepers/acp/protocol.js';
|
|
17
18
|
import { KeeperClosedError, KeeperConnectionLostError, KeeperNotConnectedError, KeeperRequestError, KeeperRequestTimeoutError, } from '../keepers/kit/client.js';
|
|
18
19
|
/** What the executor reports when a prompt's blocks are no longer in this daemon's memory (interface-contracts). */
|
|
@@ -157,7 +158,7 @@ export class ProcessorSession {
|
|
|
157
158
|
}
|
|
158
159
|
catch (error) {
|
|
159
160
|
// keeper-acp unreachable is not a launch failure: the caller falls back to the local transport.
|
|
160
|
-
if (error instanceof AcpKeeperUnavailableError)
|
|
161
|
+
if (error instanceof AcpKeeperUnavailableError || error instanceof RegistrationOwnershipError)
|
|
161
162
|
throw error;
|
|
162
163
|
throw spawnFailure(options.spawn.command, error);
|
|
163
164
|
}
|
|
@@ -575,6 +576,9 @@ export class ProcessorSession {
|
|
|
575
576
|
this.history.push(event);
|
|
576
577
|
}
|
|
577
578
|
continue;
|
|
579
|
+
case 'processor.closed_replay_level':
|
|
580
|
+
this.log(`[acp] session ${this.key}: ignored ${String(frame.sessionUpdate)} for ended replay scope ${JSON.stringify(frame.sessionId)}`);
|
|
581
|
+
continue;
|
|
578
582
|
case 'processor.history_end': {
|
|
579
583
|
const droppedCount = typeof frame.droppedCount === 'number' ? frame.droppedCount : 0;
|
|
580
584
|
this.historyEnd = { ok: frame.ok === true, droppedCount };
|
|
@@ -1015,6 +1019,13 @@ export class ProcessorSession {
|
|
|
1015
1019
|
async steer(blocks, priority, id) {
|
|
1016
1020
|
if (this.pendingReplies.has(`agent_steered:${id}`))
|
|
1017
1021
|
throw new Error(`A steer with id '${id}' is already in flight`);
|
|
1022
|
+
// A full producing window stops the raw loop, including control notes. Appending here
|
|
1023
|
+
// would leave a steer that can execute long after the server's RPC timed out. Refuse
|
|
1024
|
+
// before recording anything: promptRequired proves this attempt cannot inject later.
|
|
1025
|
+
if (!this.outbox.hasRoom()) {
|
|
1026
|
+
this.log(`[acp] session ${this.key}: steer ${id} not admitted while awaiting event acknowledgements`);
|
|
1027
|
+
return 'promptRequired';
|
|
1028
|
+
}
|
|
1018
1029
|
const answered = this.reply('agent_steered', id);
|
|
1019
1030
|
await this.send({ type: 'agent_steer', id, blocksRef: this.holdBlocks(blocks), ...(priority !== undefined ? { priority } : {}) })
|
|
1020
1031
|
.catch((error) => { this.pendingReplies.delete(`agent_steered:${id}`); throw error; });
|
|
@@ -86,7 +86,7 @@ export const AXOM_AGENT_CATALOG = [
|
|
|
86
86
|
defaultName: 'claude-agent',
|
|
87
87
|
defaultLabel: 'Claude Agent',
|
|
88
88
|
detect: { commands: ['claude'] },
|
|
89
|
-
//
|
|
89
|
+
// 钉版本的唯一理由是补丁:账号配额与 replay lifecycle 配方只核验过 0.97.0(runtime.json 的
|
|
90
90
|
// package_patches)。不钉的话 registry 一发新版,Install/Update 就装上一个配方不覆盖的包,
|
|
91
91
|
// 账号配额扩展静默消失——2026-10-14 的 antigravity(registry 已到 1.4.0、native extension
|
|
92
92
|
// 只认 1.3.0)就是这个缺口的实例。钉住后 scan 的 Update 比对(installedVersion vs 本清单的
|
|
@@ -204,19 +204,19 @@ export const AXOM_AGENT_CATALOG = [
|
|
|
204
204
|
// 2026-10-13 真机验证(ZCode 桌面 3.14.5 + 0.69.3):initialize → session/new → 首轮 prompt 通过,
|
|
205
205
|
// 日志 "serving the coding-plan API key (config.json or store)"。0.63/0.64 那轮不兼容(模型只剩
|
|
206
206
|
// 无授权的 builtin:bigmodel-coding-plan\GLM-5.3,首轮 CONFIGURATION_ERROR "Model creation failed",
|
|
207
|
-
// 2026-10-06 两台机器复现)自 0.68.1 起修复。2026-10-
|
|
208
|
-
//
|
|
209
|
-
//
|
|
207
|
+
// 2026-10-06 两台机器复现)自 0.68.1 起修复。2026-10-29 升到 0.80.0:迁移配额、模型兜底、
|
|
208
|
+
// subagent/async 补丁;env 账号包装复用上游 bridge-accounts(不写桌面配置)。
|
|
209
|
+
// ZCODE_PROVIDER + ZCODE_API_KEY 支持独立个人 Coding Plan,无需复用桌面 OAuth。
|
|
210
210
|
supplement: {
|
|
211
211
|
name: 'ZCode',
|
|
212
|
-
version: '0.
|
|
212
|
+
version: '0.80.0',
|
|
213
213
|
description: 'Community bridge that drives the real ZCode app-server (GLM) over ACP.',
|
|
214
214
|
repository: 'https://github.com/william0wang/zcode-acp',
|
|
215
215
|
website: 'https://github.com/william0wang/zcode-acp',
|
|
216
216
|
authors: ['William Wang'],
|
|
217
217
|
license: 'Apache-2.0',
|
|
218
218
|
icon: 'https://raw.githubusercontent.com/william0wang/zcode-acp/main/registry/zcode-acp/icon.svg',
|
|
219
|
-
distribution: { npx: { package: 'zcode-acp-server@0.
|
|
219
|
+
distribution: { npx: { package: 'zcode-acp-server@0.80.0' } },
|
|
220
220
|
},
|
|
221
221
|
prerequisite: {
|
|
222
222
|
label: 'ZCode',
|
|
@@ -144,12 +144,13 @@ export class KeeperWorld {
|
|
|
144
144
|
const real = new KeeperTransport(new AcpKeeperClient({ launcher: use }));
|
|
145
145
|
const keeper = (over.wrapKeeper ? over.wrapKeeper(real) : real);
|
|
146
146
|
const generations = keeperGenerations({ launcher: use, current: keeper, log: () => { } });
|
|
147
|
-
const
|
|
147
|
+
const journalRoot = use.ownership ? join(this.configDir, 'registrations', use.ownership.registrationId) : this.configDir;
|
|
148
|
+
const local = new LocalTransport({ configDir: journalRoot, ownership: use.ownership });
|
|
148
149
|
const acp = new AcpAgents({ cacheDir: this.configDir, registry: { load: async () => [] }, transports: { keeper, local }, keeperGenerations: generations,
|
|
149
150
|
sessionStartTimeoutMs: 15_000, ...over.acp });
|
|
150
151
|
const tasks = new TaskRegistry();
|
|
151
152
|
const sent = [];
|
|
152
|
-
const manager = new AgentSessionManager(acp, null, join(
|
|
153
|
+
const manager = new AgentSessionManager(acp, null, join(journalRoot, 'agents'), tasks, () => true, use.ownership);
|
|
153
154
|
manager.bindSend((frame) => { sent.push(frame); server.apply(frame); }, AGENT_WIRE_VERSION);
|
|
154
155
|
let killed = false;
|
|
155
156
|
const result = {
|
|
@@ -223,7 +224,7 @@ export class KeeperWorld {
|
|
|
223
224
|
const n = this.processes.length + 1;
|
|
224
225
|
const framesOut = join(this.configDir, `frames-daemon-${n}.jsonl`);
|
|
225
226
|
const stderrFile = join(this.configDir, `daemon-${n}.stderr`);
|
|
226
|
-
const child = spawn(join(packageRoot, 'node_modules', '.bin', 'vite-node'), [DAEMON_LIFE,
|
|
227
|
+
const child = spawn(join(packageRoot, 'node_modules', '.bin', 'vite-node'), ['--config', join(packageRoot, 'src/test/fixtures/nodeRuntime.vite.config.ts'), DAEMON_LIFE,
|
|
227
228
|
JSON.stringify({ configDir: this.configDir, distDir: realDist, framesOut, faults })], { cwd: packageRoot, stdio: ['pipe', 'ignore', 'pipe'] });
|
|
228
229
|
let stderr = '';
|
|
229
230
|
child.stderr.on('data', (chunk) => { stderr += chunk.toString('utf8'); appendFileSync(stderrFile, chunk); });
|