@platless/axom-cli 0.3.13 → 0.6.36
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 +18 -5
- package/dist/axom-patch/cli/README.md +119 -9
- package/dist/axom-patch/cli/account-limits/README.md +27 -0
- package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
- package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
- package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
- package/dist/axom-patch/cli/account-limits/generate.py +123 -0
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +81 -0
- package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
- package/dist/axom-patch/cli/claude-account-limits.json +37 -0
- package/dist/axom-patch/cli/codex-account-limits.json +37 -0
- package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
- 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 +194 -0
- package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
- package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +5 -1
- 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/pi-subagent-subwork.mjs +211 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -5
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -7
- 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 +39 -15
- package/dist/axom-patch/cli/zcode-account-limits.json +30 -0
- package/dist/axom-patch/cli/zcode-models.json +37 -0
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +72 -0
- package/dist/cli.js +4 -9
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +65 -3
- package/dist/commands/workstation.js +73 -7
- package/dist/install/claude/commands.js +13 -13
- package/dist/keepers/acp/keeper.mjs +3083 -0
- package/dist/keepers/acp/manifest.json +9 -0
- package/dist/keepers/terminal/keeper.mjs +8469 -0
- package/dist/keepers/terminal/manifest.json +9 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
- package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
- package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
- package/dist/mcp.js +1 -1
- package/dist/native/darwin-arm64/axom-quic +0 -0
- package/dist/test/keeperKit.js +67 -0
- package/dist/workstation/acp/acpAgents.js +2192 -636
- package/dist/workstation/acp/acpClient.js +84 -495
- package/dist/workstation/acp/adoption.js +10 -396
- package/dist/workstation/acp/binaryInstall.js +100 -27
- package/dist/workstation/acp/builds/gc.js +245 -0
- package/dist/workstation/acp/builds/install/binary.js +28 -0
- package/dist/workstation/acp/builds/install/npm.js +324 -0
- package/dist/workstation/acp/builds/install/uv.js +254 -0
- package/dist/workstation/acp/builds/migrate.js +195 -0
- package/dist/workstation/acp/builds/model.js +128 -0
- package/dist/workstation/acp/builds/nodeGate.js +100 -0
- package/dist/workstation/acp/builds/store.js +352 -0
- package/dist/workstation/acp/commandTableProbe.js +68 -0
- package/dist/workstation/acp/configOptions.js +103 -1
- package/dist/workstation/acp/envSecrets.js +27 -0
- package/dist/workstation/acp/initializeResponse.js +290 -0
- package/dist/workstation/acp/installErrors.js +12 -2
- package/dist/workstation/acp/installProbe.js +1 -1
- package/dist/workstation/acp/managedPatches.js +239 -159
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +330 -0
- package/dist/workstation/acp/patchAssets.js +60 -3
- package/dist/workstation/acp/permission.js +42 -10
- package/dist/workstation/acp/processor/checkpoint.js +78 -0
- package/dist/workstation/acp/processor/commands.js +35 -0
- package/dist/workstation/acp/processor/control.js +282 -0
- package/dist/workstation/acp/processor/emit.js +26 -0
- package/dist/workstation/acp/processor/jsonrpc.js +172 -0
- package/dist/workstation/acp/processor/memoryLog.js +49 -0
- package/dist/workstation/acp/processor/origins.js +48 -0
- package/dist/workstation/acp/processor/output.js +67 -0
- package/dist/workstation/acp/processor/projection.js +42 -0
- package/dist/workstation/acp/processor/record.js +128 -0
- package/dist/workstation/acp/processor/runtime.js +105 -0
- package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
- package/dist/workstation/acp/processor/session.js +496 -0
- package/dist/workstation/acp/processor/state.js +67 -0
- package/dist/workstation/acp/processor/step.js +442 -0
- package/dist/workstation/acp/processor/test/fixtureLog.js +58 -0
- package/dist/workstation/acp/processor/test/harness.js +120 -0
- package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
- package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
- package/dist/workstation/acp/processor/transport/generations.js +76 -0
- package/dist/workstation/acp/processor/transport/keeper.js +130 -0
- package/dist/workstation/acp/processor/transport/local.js +269 -0
- package/dist/workstation/acp/processor/transport/types.js +78 -0
- package/dist/workstation/acp/processor/updates.js +390 -0
- package/dist/workstation/acp/processorSession.js +1348 -0
- package/dist/workstation/acp/registry/catalog.js +189 -41
- package/dist/workstation/acp/registry/launch.js +76 -119
- package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
- package/dist/workstation/acp/steering.js +58 -0
- package/dist/workstation/acp/switching.js +111 -0
- package/dist/workstation/acp/terminalLogin.js +16 -42
- package/dist/workstation/acp/terminalLoginSpec.js +41 -0
- package/dist/workstation/acp/test/adapterFixtures.js +256 -0
- package/dist/workstation/acp/test/daemonLife.js +106 -0
- package/dist/workstation/acp/test/fakeAgent.js +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +295 -0
- package/dist/workstation/acp/test/memoryOutbox.js +55 -0
- package/dist/workstation/acp/test/raw/fixtures.js +196 -0
- package/dist/workstation/acp/test/raw/sanitize.js +344 -0
- package/dist/workstation/acp/test/raw/sources.js +135 -0
- package/dist/workstation/acp/test/realAdapter.js +331 -0
- package/dist/workstation/acp/test/scriptAgent.js +214 -0
- package/dist/workstation/acp/test/sessionFrames.js +42 -0
- package/dist/workstation/acp/timedCommand.js +4 -2
- package/dist/workstation/acp/uvxInstall.js +72 -122
- package/dist/workstation/agentFlow.js +103 -0
- package/dist/workstation/agentSessions.js +1289 -544
- package/dist/workstation/channels/agent.js +7 -5
- package/dist/workstation/channels/computer.js +69 -21
- package/dist/workstation/channels/computerExecs.js +200 -0
- package/dist/workstation/channels/management.js +172 -8
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +16 -4
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +200 -0
- package/dist/workstation/control/commands.js +124 -0
- package/dist/workstation/control/daemonIdentity.js +53 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +63 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/runtimeClient.js +97 -0
- package/dist/workstation/control/runtimeHost.js +148 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/daemon.js +393 -12
- package/dist/workstation/daemonctl.js +103 -14
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/capabilities.js +92 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +353 -0
- package/dist/workstation/desktopControl/lobby.js +129 -0
- package/dist/workstation/desktopControl/macProvider.js +155 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/protocol.js +302 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1681 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +78 -0
- package/dist/workstation/exec/attach.js +126 -0
- package/dist/workstation/exec/command.js +40 -0
- package/dist/workstation/exec/durable.js +517 -0
- package/dist/workstation/exec/fallback.js +106 -0
- package/dist/workstation/exec/policy.js +837 -0
- package/dist/workstation/exec/shaping.js +211 -0
- package/dist/workstation/exec.js +3 -112
- package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
- package/dist/workstation/files/adapters/toolRequests.js +121 -0
- package/dist/workstation/files/client.js +455 -0
- package/dist/workstation/files/credentials.js +142 -0
- package/dist/workstation/files/errors.js +28 -0
- package/dist/workstation/files/fence.js +350 -0
- package/dist/workstation/files/find/fileIndex.js +153 -0
- package/dist/workstation/files/find/fuzzy.js +155 -0
- package/dist/workstation/files/host.js +122 -0
- package/dist/workstation/files/hostProtocol.js +2 -0
- package/dist/workstation/files/media/cache.js +620 -0
- package/dist/workstation/files/media/encoding.js +308 -0
- package/dist/workstation/files/media/estimator.js +100 -0
- package/dist/workstation/files/media/media.js +325 -0
- package/dist/workstation/files/media/packedAudio.js +23 -0
- package/dist/workstation/files/media/scheduler.js +551 -0
- package/dist/workstation/files/media/types.js +5 -0
- package/dist/workstation/files/scope.js +45 -0
- package/dist/workstation/files/structured.js +284 -0
- package/dist/workstation/{fileOps.js → files/text.js} +152 -39
- package/dist/workstation/files/uploads.js +351 -0
- package/dist/workstation/journal.js +65 -236
- package/dist/workstation/keepers/acp/client.js +101 -0
- package/dist/workstation/keepers/acp/envelope.js +62 -0
- package/dist/workstation/keepers/acp/host.js +419 -0
- package/dist/workstation/keepers/acp/main.js +18 -0
- package/dist/workstation/keepers/acp/memoryStream.js +121 -0
- package/dist/workstation/keepers/acp/params.js +137 -0
- package/dist/workstation/keepers/acp/protocol.js +66 -0
- package/dist/workstation/keepers/acp/recover.js +120 -0
- package/dist/workstation/keepers/acp/session.js +584 -0
- package/dist/workstation/keepers/acp/stderr.js +154 -0
- package/dist/workstation/keepers/kit/activity.js +103 -0
- package/dist/workstation/keepers/kit/bootstrap.js +164 -0
- package/dist/workstation/keepers/kit/buildVersion.js +51 -0
- package/dist/workstation/keepers/kit/bundle.js +124 -0
- package/dist/workstation/keepers/kit/client.js +432 -0
- package/dist/workstation/keepers/kit/identity.js +21 -0
- package/dist/workstation/keepers/kit/ipc.js +112 -0
- package/dist/workstation/keepers/kit/launchConfig.js +8 -0
- package/dist/workstation/keepers/kit/launcher.js +289 -0
- package/dist/workstation/keepers/kit/lock.js +123 -0
- package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
- package/dist/workstation/keepers/kit/paths.js +76 -0
- package/dist/workstation/keepers/kit/protocol.js +43 -0
- package/dist/workstation/keepers/kit/server.js +518 -0
- package/dist/workstation/keepers/kit/streamLog.js +697 -0
- package/dist/workstation/keepers/legacy.js +296 -0
- package/dist/workstation/keepers/policy.js +576 -0
- package/dist/workstation/keepers/stop.js +34 -0
- package/dist/workstation/keepers/terminal/client.js +213 -0
- package/dist/workstation/keepers/terminal/exec.js +981 -0
- package/dist/workstation/keepers/terminal/generations.js +129 -0
- package/dist/workstation/keepers/terminal/keeper.js +62 -0
- package/dist/workstation/keepers/terminal/main.js +43 -0
- package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
- package/dist/workstation/keepers/terminal/params.js +27 -0
- package/dist/workstation/keepers/terminal/protocol.js +56 -0
- package/dist/workstation/keepers/terminal/pty.js +330 -0
- package/dist/workstation/managedInstall.js +57 -0
- package/dist/workstation/protocol/accountLimits.js +47 -0
- package/dist/workstation/protocol/agent.js +50 -19
- package/dist/workstation/protocol/common.js +30 -0
- package/dist/workstation/protocol/computer.js +15 -4
- package/dist/workstation/protocol/management.js +141 -4
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +119 -0
- package/dist/workstation/rd/control.js +119 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/provider.js +222 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +45 -0
- package/dist/workstation/rd/update.js +171 -0
- package/dist/workstation/rd/upkeep.js +38 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/service/installer.js +104 -13
- package/dist/workstation/service/launchd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +154 -93
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- package/dist/workstation/tools/client.js +9 -1
- package/dist/workstation/tools/info.js +50 -0
- package/dist/workstation/tools/keeperProcess.js +15 -5
- package/dist/workstation/tools/router.js +55 -0
- package/dist/workstation/tools/terminalRouter.js +222 -0
- package/dist/workstation/tools/validation.js +37 -1
- package/dist/workstation/update/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +3 -0
- package/dist/workstation/update/globalInstall.js +116 -0
- package/dist/workstation/update/npmInstaller.js +49 -23
- package/dist/workstation/update/ptyPreflight.js +21 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/runUpdate.js +3 -2
- package/dist/workstation/update/taskRegistry.js +105 -11
- package/dist/workstation/update/watchdog.js +16790 -334
- package/package.json +11 -8
- 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 -49
- 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/workstation/acp/acpSession.js +0 -1475
- package/dist/workstation/acp/contract.js +0 -499
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/draftInterceptor.js +0 -163
- package/dist/workstation/acp/qoderStartup.js +0 -45
- package/dist/workstation/acp/sessionScope.js +0 -19
- package/dist/workstation/filePaths.js +0 -174
- package/dist/workstation/fileWorker.js +0 -81
- package/dist/workstation/fileWorkerClient.js +0 -121
- package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -106
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -262
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -30
- package/dist/workstation/qoderAdapter/history.js +0 -87
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -93
- 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 -49
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -77
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- package/dist/workstation/tools/core.js +0 -50
- package/dist/workstation/tools/files.js +0 -525
- package/dist/workstation/tools/terminal.js +0 -214
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Automatic adapter patches, owned by Axom, applied only at **install** time to a copy of the
|
|
3
|
+
* daemon-owned pristine tree (spec §6.2/§3.3). The pristine tree is never patched in place, and
|
|
4
|
+
* nothing here ever runs at launch except {@link verifyPatchedFiles}, the tamper check.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { constants } from 'node:fs';
|
|
8
|
+
import { cp, lstat, mkdir, open, realpath, writeFile } from 'node:fs/promises';
|
|
4
9
|
import path from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { assertSafePackageName, inside } from './builds/model.js';
|
|
12
|
+
import { packagePatchMatch, packagePatches, patchAssetRoot, validatePackagePatch, validatePatchPath } from './patchAssets.js';
|
|
7
13
|
const sha = (content) => createHash('sha256').update(content).digest('hex');
|
|
8
|
-
const pending = new Map();
|
|
9
14
|
const warned = new Set();
|
|
10
15
|
function warnOnce(key, message) {
|
|
11
16
|
if (warned.has(key))
|
|
@@ -13,31 +18,150 @@ function warnOnce(key, message) {
|
|
|
13
18
|
warned.add(key);
|
|
14
19
|
console.warn(message);
|
|
15
20
|
}
|
|
16
|
-
function
|
|
21
|
+
function within(root, relative) {
|
|
22
|
+
validatePatchPath(relative);
|
|
17
23
|
const target = path.resolve(root, relative);
|
|
18
24
|
if (!target.startsWith(path.resolve(root) + path.sep))
|
|
19
25
|
throw new Error('Axom patch path escapes its package');
|
|
20
26
|
return target;
|
|
21
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Check every component, including the supplied root and its ancestors, without following links.
|
|
30
|
+
* The one links crossed are root-owned system symlinks (`/var -> /private/var` on macOS): stock
|
|
31
|
+
* macOS hands out per-user tmp dirs behind one, and refusing it would reject every tmp-scoped
|
|
32
|
+
* tree. A root-owned link to a root-owned, non-group-writable directory is not plantable by the
|
|
33
|
+
* running user; every user-owned symlink is still refused.
|
|
34
|
+
*/
|
|
35
|
+
async function noSymlinks(filename, missing = false) {
|
|
36
|
+
const absolute = path.resolve(filename);
|
|
37
|
+
let current = path.parse(absolute).root;
|
|
38
|
+
const systemSymlink = async (uid) => {
|
|
39
|
+
if (uid !== 0 || process.getuid?.() === 0)
|
|
40
|
+
return false;
|
|
41
|
+
try {
|
|
42
|
+
const target = await realpath(current);
|
|
43
|
+
const stat = await lstat(target);
|
|
44
|
+
return stat.isDirectory() && stat.uid === 0 && (stat.mode & 0o022) === 0;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
for (const part of absolute.slice(current.length).split(path.sep).filter(Boolean)) {
|
|
51
|
+
current = path.join(current, part);
|
|
52
|
+
try {
|
|
53
|
+
const info = await lstat(current);
|
|
54
|
+
if (info.isSymbolicLink() && !(await systemSymlink(info.uid)))
|
|
55
|
+
throw new Error(`Axom patch symlink is forbidden: ${current}`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (missing && error.code === 'ENOENT')
|
|
59
|
+
return;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function readOwnedFile(filename) {
|
|
65
|
+
await noSymlinks(filename);
|
|
66
|
+
const handle = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
67
|
+
try {
|
|
68
|
+
if (!(await handle.stat()).isFile())
|
|
69
|
+
throw new Error(`Axom patch requires a regular file: ${filename}`);
|
|
70
|
+
return await handle.readFile();
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
await handle.close();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function makeOwnedDirectories(directory) {
|
|
77
|
+
await noSymlinks(directory, true);
|
|
78
|
+
const parent = path.dirname(directory);
|
|
79
|
+
if (parent !== directory) {
|
|
80
|
+
try {
|
|
81
|
+
await lstat(directory);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error.code !== 'ENOENT')
|
|
85
|
+
throw error;
|
|
86
|
+
await makeOwnedDirectories(parent);
|
|
87
|
+
try {
|
|
88
|
+
await mkdir(directory);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error.code !== 'EEXIST')
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await noSymlinks(directory);
|
|
97
|
+
if (!(await lstat(directory)).isDirectory())
|
|
98
|
+
throw new Error(`Axom patch parent is not a directory: ${directory}`);
|
|
99
|
+
}
|
|
22
100
|
/** A matching version and exact original bytes; already-patched or changed originals are rejected. */
|
|
23
|
-
export async function applyPackagePatches(
|
|
101
|
+
export async function applyPackagePatches(nodeModulesDir, recipes, assetRoot = patchAssetRoot) {
|
|
24
102
|
const output = {};
|
|
103
|
+
// Validate the complete declaration and asset closure before any writes.
|
|
104
|
+
expectedPatchedFiles(recipes);
|
|
105
|
+
const assets = new Map();
|
|
25
106
|
for (const recipe of recipes) {
|
|
26
|
-
const
|
|
107
|
+
const packageDir = inside(nodeModulesDir, recipe.package);
|
|
108
|
+
const metadata = JSON.parse((await readOwnedFile(path.join(packageDir, 'package.json'))).toString('utf8'));
|
|
27
109
|
if (metadata.name !== recipe.package || !packagePatchMatch(recipe, metadata.version)) {
|
|
28
110
|
throw new Error(`Axom patch '${recipe.id}' does not support ${metadata.name}@${metadata.version}`);
|
|
29
111
|
}
|
|
112
|
+
for (const edit of recipe.edits)
|
|
113
|
+
await noSymlinks(within(packageDir, edit.file));
|
|
114
|
+
for (const asset of recipe.assets ?? []) {
|
|
115
|
+
const filename = within(packageDir, asset.target);
|
|
116
|
+
await noSymlinks(filename, true);
|
|
117
|
+
try {
|
|
118
|
+
await lstat(filename);
|
|
119
|
+
throw new Error(`Axom patch asset collision: target already exists: ${asset.target}`);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error.code !== 'ENOENT')
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
const bytes = await readOwnedFile(within(assetRoot instanceof URL ? fileURLToPath(assetRoot) : assetRoot, asset.source));
|
|
126
|
+
if (sha(bytes) !== asset.sha256)
|
|
127
|
+
throw new Error(`Axom patch asset hash mismatch: ${asset.source}`);
|
|
128
|
+
assets.set(`${recipe.package}/${asset.target}`, bytes);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const recipe of recipes) {
|
|
132
|
+
const packageDir = inside(nodeModulesDir, recipe.package);
|
|
30
133
|
for (const edit of recipe.edits) {
|
|
31
|
-
const filename =
|
|
32
|
-
const original = await
|
|
134
|
+
const filename = within(packageDir, edit.file);
|
|
135
|
+
const original = (await readOwnedFile(filename)).toString('utf8');
|
|
33
136
|
if (sha(original) !== edit.before_sha256 || !edit.before || original.split(edit.before).length !== 2) {
|
|
34
137
|
throw new Error(`Axom patch '${recipe.id}': original bytes changed in ${edit.file}`);
|
|
35
138
|
}
|
|
36
139
|
const patched = original.replace(edit.before, edit.after);
|
|
37
140
|
if (sha(patched) !== edit.after_sha256)
|
|
38
141
|
throw new Error(`Axom patch '${recipe.id}': invalid expected output`);
|
|
39
|
-
await
|
|
40
|
-
|
|
142
|
+
await noSymlinks(filename);
|
|
143
|
+
const handle = await open(filename, constants.O_WRONLY | constants.O_NOFOLLOW);
|
|
144
|
+
try {
|
|
145
|
+
await handle.truncate(0);
|
|
146
|
+
await handle.writeFile(patched, 'utf8');
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
await handle.close();
|
|
150
|
+
}
|
|
151
|
+
output[`${recipe.package}/${edit.file}`] = sha(patched);
|
|
152
|
+
}
|
|
153
|
+
for (const asset of recipe.assets ?? []) {
|
|
154
|
+
const filename = within(packageDir, asset.target);
|
|
155
|
+
await makeOwnedDirectories(path.dirname(filename));
|
|
156
|
+
await noSymlinks(filename, true);
|
|
157
|
+
const handle = await open(filename, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o644);
|
|
158
|
+
try {
|
|
159
|
+
await handle.writeFile(assets.get(`${recipe.package}/${asset.target}`));
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
await handle.close();
|
|
163
|
+
}
|
|
164
|
+
output[`${recipe.package}/${asset.target}`] = asset.sha256;
|
|
41
165
|
}
|
|
42
166
|
}
|
|
43
167
|
return output;
|
|
@@ -49,7 +173,7 @@ async function originalsFit(packageDir, recipe) {
|
|
|
49
173
|
let content = files.get(edit.file);
|
|
50
174
|
if (content === undefined) {
|
|
51
175
|
try {
|
|
52
|
-
content = await
|
|
176
|
+
content = (await readOwnedFile(within(packageDir, edit.file))).toString('utf8');
|
|
53
177
|
}
|
|
54
178
|
catch {
|
|
55
179
|
return false;
|
|
@@ -61,168 +185,124 @@ async function originalsFit(packageDir, recipe) {
|
|
|
61
185
|
}
|
|
62
186
|
return true;
|
|
63
187
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
for (const entry of entries) {
|
|
74
|
-
if (!entry.isDirectory())
|
|
75
|
-
continue;
|
|
76
|
-
const modules = path.join(root, entry.name, 'node_modules');
|
|
77
|
-
try {
|
|
78
|
-
const metadata = JSON.parse(await readFile(inside(modules, name + '/package.json'), 'utf8'));
|
|
79
|
-
if (metadata.name === name && metadata.version === version)
|
|
80
|
-
return modules;
|
|
81
|
-
}
|
|
82
|
-
catch { /* Interrupted/other version of npm's cache. */ }
|
|
83
|
-
}
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
/** Called by Install/Update and every real launch, including adapters installed before this CLI. */
|
|
87
|
-
export async function preparePatchedLaunch(agent, launch, adapterRoot, probe, options = {}) {
|
|
88
|
-
const distribution = agent.distribution.npx;
|
|
89
|
-
if (!distribution)
|
|
90
|
-
return launch;
|
|
91
|
-
const name = npmPackageName(distribution.package);
|
|
92
|
-
const version = npmPackageVersion(distribution.package);
|
|
93
|
-
const candidates = (options.recipes ?? packagePatches).filter((patch) => patch.package === name);
|
|
188
|
+
/**
|
|
189
|
+
* The recipes this CLI applies to `name@version`, read against the **pristine** package at
|
|
190
|
+
* `pristinePackageDir`: exact-version recipes always (their bytes are a promise — a mismatch fails the
|
|
191
|
+
* patch step loudly), range recipes only when the pristine bytes fit (otherwise upstream runs, with a
|
|
192
|
+
* warning). An empty answer means the build is the pristine tree itself.
|
|
193
|
+
*/
|
|
194
|
+
export async function selectPackagePatches(name, version, pristinePackageDir, recipes = packagePatches, dependencies = {}) {
|
|
195
|
+
const candidates = recipes.filter((patch) => patch.package === name || Object.hasOwn(dependencies, patch.package));
|
|
94
196
|
const matched = candidates.flatMap((recipe) => {
|
|
95
|
-
const match =
|
|
197
|
+
const match = packagePatchMatch(recipe, recipe.package === name ? version : dependencies[recipe.package]);
|
|
96
198
|
return match ? [{ recipe, match }] : [];
|
|
97
199
|
});
|
|
98
|
-
if (matched.length && (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i.test(name) ||
|
|
200
|
+
if (matched.length && (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i.test(name) || !/^[0-9a-z.+-]+$/i.test(version))) {
|
|
99
201
|
throw new Error('Invalid package identity for Axom adapter patch');
|
|
100
202
|
}
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
fitting.add(recipe);
|
|
110
|
-
else
|
|
111
|
-
warnOnce(`${recipe.id}@${distribution.package}`, `[axom-patch] ${recipe.id} does not fit ${distribution.package}; using the upstream package. Review the patch inventory.`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const recipes = matched.filter(({ recipe, match }) => match === 'exact' || fitting.has(recipe)).map(({ recipe }) => recipe);
|
|
115
|
-
if (!recipes.length) {
|
|
116
|
-
if (candidates.length && !matched.length) {
|
|
117
|
-
warnOnce(distribution.package, `[axom-patch] No verified patch for ${distribution.package}; using the upstream package. Review the patch inventory.`);
|
|
203
|
+
const selected = [];
|
|
204
|
+
const nodeModulesDir = path.resolve(pristinePackageDir, ...name.split('/').map(() => '..'));
|
|
205
|
+
for (const { recipe, match } of matched) {
|
|
206
|
+
assertSafePackageName(recipe.package);
|
|
207
|
+
const packageDir = recipe.package === name ? pristinePackageDir : inside(nodeModulesDir, recipe.package);
|
|
208
|
+
if (match === 'exact') {
|
|
209
|
+
selected.push(recipe);
|
|
210
|
+
continue;
|
|
118
211
|
}
|
|
119
|
-
|
|
212
|
+
if (await originalsFit(packageDir, recipe))
|
|
213
|
+
selected.push(recipe);
|
|
214
|
+
else
|
|
215
|
+
warnOnce(`${recipe.id}@${name}@${version}`, `[axom-patch] ${recipe.id} does not fit ${name}@${version}; using the upstream package. Review the patch inventory.`);
|
|
120
216
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const existing = pending.get(target);
|
|
124
|
-
if (existing) {
|
|
125
|
-
await existing;
|
|
126
|
-
return makeLaunch();
|
|
217
|
+
if (!selected.length && candidates.length && !matched.length) {
|
|
218
|
+
warnOnce(`${name}@${version}`, `[axom-patch] No verified patch for ${name}@${version}; using the upstream package. Review the patch inventory.`);
|
|
127
219
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
220
|
+
return selected;
|
|
221
|
+
}
|
|
222
|
+
/** The identity of a recipe set: any recipe change is a new digest, hence a new build. */
|
|
223
|
+
export function patchDigest(recipes) {
|
|
224
|
+
return sha(JSON.stringify(recipes));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Copy the pristine `node_modules` (reflink where the filesystem supports it — `COPYFILE_FICLONE`
|
|
228
|
+
* falls back to an ordinary copy by itself; relative symlinks such as `.bin/*` are kept relative so
|
|
229
|
+
* the copy never points back into pristine) into `stagedRoot`, patch the copy, and write a receipt.
|
|
230
|
+
* Returns the sha256 of every patched file, qualified by its package name under node_modules.
|
|
231
|
+
*/
|
|
232
|
+
export async function createPatchedTree(pristineRoot, stagedRoot, name, version, recipes, assetRoot = patchAssetRoot) {
|
|
233
|
+
const original = path.resolve(pristineRoot, 'node_modules');
|
|
234
|
+
const copy = path.resolve(stagedRoot, 'node_modules');
|
|
235
|
+
if (original === copy || original.startsWith(copy + path.sep) || copy.startsWith(original + path.sep)) {
|
|
236
|
+
throw new Error('Axom patch staged tree must not overlap pristine');
|
|
136
237
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
238
|
+
await noSymlinks(original);
|
|
239
|
+
const receipt = path.join(stagedRoot, 'axom-patches.json');
|
|
240
|
+
for (const destination of [copy, receipt]) {
|
|
241
|
+
await noSymlinks(destination, true);
|
|
141
242
|
try {
|
|
142
|
-
|
|
243
|
+
await lstat(destination);
|
|
244
|
+
throw new Error(`Axom patch staged target already exists: ${destination}`);
|
|
143
245
|
}
|
|
144
246
|
catch (error) {
|
|
145
247
|
if (error.code !== 'ENOENT')
|
|
146
248
|
throw error;
|
|
147
249
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
250
|
+
}
|
|
251
|
+
await cp(path.join(pristineRoot, 'node_modules'), path.join(stagedRoot, 'node_modules'), {
|
|
252
|
+
recursive: true, verbatimSymlinks: true, mode: constants.COPYFILE_FICLONE,
|
|
253
|
+
});
|
|
254
|
+
const files = await applyPackagePatches(path.join(stagedRoot, 'node_modules'), recipes, assetRoot);
|
|
255
|
+
const expected = expectedPatchedFiles(recipes);
|
|
256
|
+
if (JSON.stringify(sortKeys(files)) !== JSON.stringify(sortKeys(expected)))
|
|
257
|
+
throw new Error('Incomplete Axom adapter patch');
|
|
258
|
+
await noSymlinks(receipt, true);
|
|
259
|
+
await writeFile(receipt, JSON.stringify({
|
|
260
|
+
patchedFilesBase: 'node_modules', digest: patchDigest(recipes), package: name, version, patches: recipes.map((recipe) => recipe.id), files,
|
|
261
|
+
}) + '\n', { flag: 'wx' });
|
|
262
|
+
console.info(`[axom-patch] Applied ${recipes.map((recipe) => recipe.id).join(', ')} to ${name}@${version}`);
|
|
263
|
+
return files;
|
|
264
|
+
}
|
|
265
|
+
/** The files a recipe set rewrites and their expected sha256, derived from the shipped recipes. */
|
|
266
|
+
export function expectedPatchedFiles(recipes) {
|
|
267
|
+
const files = {};
|
|
268
|
+
const assetTargets = new Set();
|
|
269
|
+
const editTargets = new Set();
|
|
270
|
+
for (const recipe of recipes) {
|
|
271
|
+
assertSafePackageName(recipe.package);
|
|
272
|
+
validatePackagePatch(recipe);
|
|
273
|
+
for (const edit of recipe.edits) {
|
|
274
|
+
const key = `${recipe.package}/${edit.file}`;
|
|
275
|
+
editTargets.add(key);
|
|
276
|
+
files[key] = edit.after_sha256;
|
|
159
277
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (error.code !== 'ENOENT')
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
const modules = await pristineModules();
|
|
170
|
-
await mkdir(path.dirname(target), { recursive: true });
|
|
171
|
-
const temporary = target + '.staging-' + randomUUID();
|
|
172
|
-
try {
|
|
173
|
-
// Dependencies and .bin are part of the adapter environment; clone them as well.
|
|
174
|
-
await cp(modules, path.join(temporary, 'node_modules'), { recursive: true, dereference: true });
|
|
175
|
-
const files = await applyPackagePatches(inside(path.join(temporary, 'node_modules'), name), recipes);
|
|
176
|
-
await writeFile(path.join(temporary, 'axom-patches.json'), JSON.stringify({
|
|
177
|
-
digest, package: name, version, patches: recipes.map((recipe) => recipe.id), files,
|
|
178
|
-
}) + '\n');
|
|
179
|
-
try {
|
|
180
|
-
await rename(temporary, target);
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
// A second daemon may have published the same immutable copy while we built ours.
|
|
184
|
-
if (!['EEXIST', 'ENOTEMPTY'].includes(error.code ?? ''))
|
|
185
|
-
throw error;
|
|
186
|
-
return prepare();
|
|
187
|
-
}
|
|
188
|
-
console.info(`[axom-patch] Applied ${recipes.map((recipe) => recipe.id).join(', ')} to ${name}@${version}`);
|
|
189
|
-
}
|
|
190
|
-
finally {
|
|
191
|
-
await rm(temporary, { recursive: true, force: true });
|
|
278
|
+
}
|
|
279
|
+
for (const recipe of recipes)
|
|
280
|
+
for (const asset of recipe.assets ?? []) {
|
|
281
|
+
const key = `${recipe.package}/${asset.target}`;
|
|
282
|
+
if ([...assetTargets, ...editTargets].some(other => other === key || other.startsWith(key + '/') || key.startsWith(other + '/'))) {
|
|
283
|
+
throw new Error(`Axom patch asset collision: ${key}`);
|
|
192
284
|
}
|
|
285
|
+
assetTargets.add(key);
|
|
286
|
+
files[key] = asset.sha256;
|
|
193
287
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
throw new Error('Patched adapter package identity changed');
|
|
214
|
-
const selectedBins = [...new Set(recipes.map(recipe => recipe.bin).filter((bin) => bin !== undefined))];
|
|
215
|
-
if (selectedBins.length > 1)
|
|
216
|
-
throw new Error('Axom patches disagree about the adapter executable');
|
|
217
|
-
const binaries = selectedBins.length ? [metadata.bin?.[selectedBins[0]]] :
|
|
218
|
-
typeof metadata.bin === 'string' ? [metadata.bin] : Object.values(metadata.bin ?? {});
|
|
219
|
-
if (binaries.length !== 1 || typeof binaries[0] !== 'string')
|
|
220
|
-
throw new Error('Patched adapter has no unambiguous executable');
|
|
221
|
-
const entry = inside(packageDir, binaries[0]);
|
|
222
|
-
if (!(await stat(entry)).isFile())
|
|
223
|
-
throw new Error('Patched adapter executable is missing');
|
|
224
|
-
const env = { ...launch.env };
|
|
225
|
-
env.PATH = path.join(target, 'node_modules', '.bin') + path.delimiter + (env.PATH ?? probe.path ?? process.env.PATH ?? '');
|
|
226
|
-
return { command: process.execPath, args: [entry, ...(distribution.args ?? [])], env };
|
|
288
|
+
return files;
|
|
289
|
+
}
|
|
290
|
+
function sortKeys(record) {
|
|
291
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
292
|
+
}
|
|
293
|
+
/** Tamper check against an explicit base: node_modules for qualified keys, adapter package for legacy keys. */
|
|
294
|
+
export async function verifyPatchedFiles(packageDir, files) {
|
|
295
|
+
for (const [file, hash] of Object.entries(files)) {
|
|
296
|
+
let content;
|
|
297
|
+
try {
|
|
298
|
+
content = await readOwnedFile(within(packageDir, file));
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
if (error.code === 'ENOENT')
|
|
302
|
+
throw new Error(`Axom patched adapter is missing ${file}`);
|
|
303
|
+
throw new Error(`Axom patched adapter was modified: ${file} (${error.message})`);
|
|
304
|
+
}
|
|
305
|
+
if (sha(content) !== hash)
|
|
306
|
+
throw new Error(`Axom patched adapter was modified: ${file}`);
|
|
227
307
|
}
|
|
228
308
|
}
|