@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.
Files changed (258) hide show
  1. package/README.md +25 -6
  2. package/dist/axom-patch/cli/README.md +129 -12
  3. package/dist/axom-patch/cli/account-limits/generate.py +15 -12
  4. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
  5. package/dist/axom-patch/cli/claude-account-limits.json +9 -9
  6. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  7. package/dist/axom-patch/cli/codex-account-limits.json +11 -11
  8. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  9. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  10. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  11. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  12. package/dist/axom-patch/cli/nightly-task.md +55 -13
  13. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  14. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  15. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  16. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  17. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  18. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
  19. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
  20. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  21. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  22. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  23. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  24. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  25. package/dist/axom-patch/cli/runtime.json +19 -19
  26. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  27. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  28. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  29. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  30. package/dist/axom-patch/cli/zcode-models.json +9 -10
  31. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
  32. package/dist/cli.js +11 -45
  33. package/dist/commands/auth.js +4 -1
  34. package/dist/commands/knowledge.js +47 -0
  35. package/dist/commands/update.js +126 -3
  36. package/dist/commands/workstation.js +101 -164
  37. package/dist/commands/workstationRegistrations.js +116 -0
  38. package/dist/credentialBroker.js +13 -2
  39. package/dist/credentials.js +16 -0
  40. package/dist/install/claude/commands.js +15 -15
  41. package/dist/keepers/terminal/keeper.mjs +1089 -673
  42. package/dist/keepers/terminal/manifest.json +4 -4
  43. package/dist/mcp.js +1 -1
  44. package/dist/native/darwin-x64/axom-quic +0 -0
  45. package/dist/workstation/acp/acpAgents.js +113 -102
  46. package/dist/workstation/acp/acpClient.js +26 -0
  47. package/dist/workstation/acp/builds/install/npm.js +99 -10
  48. package/dist/workstation/acp/initializeResponse.js +6 -1
  49. package/dist/workstation/acp/managedPatches.js +189 -25
  50. package/dist/workstation/acp/managedRuntime.js +314 -0
  51. package/dist/workstation/acp/outbox.js +12 -5
  52. package/dist/workstation/acp/patchAssets.js +40 -0
  53. package/dist/workstation/acp/permission.js +2 -2
  54. package/dist/workstation/acp/processor/checkpoint.js +26 -34
  55. package/dist/workstation/acp/processor/control.js +7 -80
  56. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  57. package/dist/workstation/acp/processor/runtime.js +5 -81
  58. package/dist/workstation/acp/processor/session.js +54 -82
  59. package/dist/workstation/acp/processor/state.js +7 -13
  60. package/dist/workstation/acp/processor/step.js +3 -20
  61. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  62. package/dist/workstation/acp/processor/transport/local.js +19 -3
  63. package/dist/workstation/acp/processor/updates.js +156 -105
  64. package/dist/workstation/acp/processorSession.js +20 -173
  65. package/dist/workstation/acp/registry/catalog.js +65 -39
  66. package/dist/workstation/acp/registry/launch.js +16 -1
  67. package/dist/workstation/acp/switching.js +0 -3
  68. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  69. package/dist/workstation/acp/test/keeperWorld.js +32 -3
  70. package/dist/workstation/acp/test/raw/sources.js +16 -130
  71. package/dist/workstation/acp/test/realAdapter.js +1 -47
  72. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  73. package/dist/workstation/agentFlow.js +1 -1
  74. package/dist/workstation/agentSessions.js +114 -166
  75. package/dist/workstation/channels/agent.js +9 -1
  76. package/dist/workstation/channels/computer.js +27 -4
  77. package/dist/workstation/channels/management.js +130 -15
  78. package/dist/workstation/channels/url.js +1 -1
  79. package/dist/workstation/config.js +33 -9
  80. package/dist/workstation/connection/encryptedChannel.js +4 -1
  81. package/dist/workstation/control/bindingStore.js +204 -0
  82. package/dist/workstation/control/commands.js +125 -0
  83. package/dist/workstation/control/daemonIdentity.js +143 -0
  84. package/dist/workstation/control/deviceCommands.js +191 -0
  85. package/dist/workstation/control/deviceProtocol.js +68 -0
  86. package/dist/workstation/control/enrollment.js +106 -0
  87. package/dist/workstation/control/inspect.js +71 -0
  88. package/dist/workstation/control/locks.js +110 -0
  89. package/dist/workstation/control/protocol.js +40 -0
  90. package/dist/workstation/control/restartGuard.js +46 -0
  91. package/dist/workstation/control/rootFormat.js +16 -0
  92. package/dist/workstation/control/runtimeClient.js +152 -0
  93. package/dist/workstation/control/runtimeHost.js +235 -0
  94. package/dist/workstation/control/serverIdentity.js +35 -0
  95. package/dist/workstation/control/updateTransition.js +91 -0
  96. package/dist/workstation/daemon.js +266 -264
  97. package/dist/workstation/daemonctl.js +226 -25
  98. package/dist/workstation/desktopControl/audio.js +99 -0
  99. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  100. package/dist/workstation/desktopControl/automationJson.js +74 -0
  101. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  102. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  103. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  104. package/dist/workstation/desktopControl/automationSession.js +81 -0
  105. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  106. package/dist/workstation/desktopControl/capabilities.js +93 -0
  107. package/dist/workstation/desktopControl/captureModes.js +9 -0
  108. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  109. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  110. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  111. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  112. package/dist/workstation/desktopControl/helperProcess.js +602 -0
  113. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  114. package/dist/workstation/desktopControl/lobby.js +131 -0
  115. package/dist/workstation/desktopControl/macProvider.js +156 -0
  116. package/dist/workstation/desktopControl/media.js +331 -0
  117. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  118. package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
  119. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  120. package/dist/workstation/desktopControl/overview.js +58 -0
  121. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  122. package/dist/workstation/desktopControl/protocol.js +311 -0
  123. package/dist/workstation/desktopControl/provider.js +1 -0
  124. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  125. package/dist/workstation/desktopControl/rateController.js +226 -0
  126. package/dist/workstation/desktopControl/supervisor.js +1774 -0
  127. package/dist/workstation/desktopControl/transport.js +27 -0
  128. package/dist/workstation/desktopControl/wallpaper.js +98 -0
  129. package/dist/workstation/exec/policy.js +5 -0
  130. package/dist/workstation/files/client.js +10 -1
  131. package/dist/workstation/files/credentials.js +142 -0
  132. package/dist/workstation/files/host.js +9 -2
  133. package/dist/workstation/files/text.js +42 -8
  134. package/dist/workstation/journal.js +5 -2
  135. package/dist/workstation/keepers/acp/client.js +38 -2
  136. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  137. package/dist/workstation/keepers/kit/bundle.js +21 -17
  138. package/dist/workstation/keepers/kit/client.js +2 -2
  139. package/dist/workstation/keepers/kit/launcher.js +123 -7
  140. package/dist/workstation/keepers/kit/paths.js +2 -1
  141. package/dist/workstation/keepers/legacy.js +16 -2
  142. package/dist/workstation/keepers/policy.js +5 -34
  143. package/dist/workstation/keepers/terminal/client.js +115 -23
  144. package/dist/workstation/keepers/terminal/exec.js +6 -3
  145. package/dist/workstation/keepers/terminal/frames.js +73 -0
  146. package/dist/workstation/keepers/terminal/generations.js +5 -8
  147. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  148. package/dist/workstation/keepers/terminal/input.js +146 -0
  149. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  150. package/dist/workstation/keepers/terminal/main.js +1 -1
  151. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  152. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  153. package/dist/workstation/keepers/terminal/pty.js +1 -330
  154. package/dist/workstation/keepers/terminal/screen.js +246 -0
  155. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  156. package/dist/workstation/managedInstall.js +57 -0
  157. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  158. package/dist/workstation/protocol/agent.js +2 -15
  159. package/dist/workstation/protocol/management.js +113 -5
  160. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  161. package/dist/workstation/rd/artifact.js +120 -0
  162. package/dist/workstation/rd/control.js +117 -0
  163. package/dist/workstation/rd/display.js +29 -0
  164. package/dist/workstation/rd/host.js +213 -0
  165. package/dist/workstation/rd/migrationReadiness.js +46 -0
  166. package/dist/workstation/rd/provider.js +256 -0
  167. package/dist/workstation/rd/sessions.js +391 -0
  168. package/dist/workstation/rd/state.js +57 -0
  169. package/dist/workstation/rd/update.js +168 -0
  170. package/dist/workstation/rd/upkeep.js +80 -0
  171. package/dist/workstation/register.js +27 -37
  172. package/dist/workstation/registrations/enrollment.js +250 -0
  173. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  174. package/dist/workstation/registrations/migration.js +356 -0
  175. package/dist/workstation/registrations/model.js +34 -0
  176. package/dist/workstation/registrations/removal.js +222 -0
  177. package/dist/workstation/registrations/store.js +737 -0
  178. package/dist/workstation/runtime/desktopBinding.js +20 -0
  179. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  180. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  181. package/dist/workstation/runtime/deviceServices.js +117 -0
  182. package/dist/workstation/runtime/events.js +20 -0
  183. package/dist/workstation/runtime/ownership.js +100 -0
  184. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  185. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  186. package/dist/workstation/runtime/removalActivity.js +184 -0
  187. package/dist/workstation/service/installer.js +197 -14
  188. package/dist/workstation/service/launchd.js +2 -0
  189. package/dist/workstation/service/systemd.js +1 -0
  190. package/dist/workstation/storageMetrics.js +118 -0
  191. package/dist/workstation/systemMetrics.js +47 -92
  192. package/dist/workstation/toolchain.js +94 -0
  193. package/dist/workstation/tools/browser.js +28 -4
  194. package/dist/workstation/tools/client.js +8 -27
  195. package/dist/workstation/tools/info.js +6 -13
  196. package/dist/workstation/tools/terminalRouter.js +17 -172
  197. package/dist/workstation/tools/validation.js +55 -1
  198. package/dist/workstation/update/archiveEvidence.js +206 -0
  199. package/dist/workstation/update/artifact.js +2 -1
  200. package/dist/workstation/update/coordinator.js +34 -13
  201. package/dist/workstation/update/deviceCompatibility.js +119 -0
  202. package/dist/workstation/update/deviceCoordinator.js +369 -0
  203. package/dist/workstation/update/deviceHealth.js +104 -0
  204. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  205. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  206. package/dist/workstation/update/deviceRollback.js +78 -0
  207. package/dist/workstation/update/globalInstall.js +186 -0
  208. package/dist/workstation/update/legacyBridge.js +414 -0
  209. package/dist/workstation/update/legacyOwner.js +39 -0
  210. package/dist/workstation/update/npmInstaller.js +51 -25
  211. package/dist/workstation/update/privateDurability.js +61 -0
  212. package/dist/workstation/update/rdRollback.js +145 -0
  213. package/dist/workstation/update/recoveryPlan.js +124 -0
  214. package/dist/workstation/update/runUpdate.js +10 -3
  215. package/dist/workstation/update/taskRegistry.js +161 -3
  216. package/dist/workstation/update/watchdog.js +29806 -333
  217. package/dist/workstation/update/watchdogProcess.js +72 -0
  218. package/package.json +11 -7
  219. package/skills/axom/SKILL.md +11 -11
  220. package/skills/axom-definition/SKILL.md +3 -3
  221. package/skills/axom-platform/SKILL.md +3 -3
  222. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  223. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  224. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  225. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  226. package/dist/commands/guide.js +0 -38
  227. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  228. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  229. package/dist/workstation/acp/delegationMcp.js +0 -24
  230. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  231. package/dist/workstation/acp/qoderStartup.js +0 -45
  232. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  233. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  234. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  235. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  236. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  237. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  238. package/dist/workstation/qoderAdapter/contract.js +0 -78
  239. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  240. package/dist/workstation/qoderAdapter/history.js +0 -85
  241. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  242. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  243. package/dist/workstation/qoderAdapter/main.js +0 -95
  244. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  245. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  246. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  247. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  248. package/dist/workstation/qoderAdapter/profile.js +0 -93
  249. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  250. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  251. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  252. package/dist/workstation/qoderAdapter/session.js +0 -319
  253. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  254. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  255. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  256. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  257. package/dist/workstation/qoderAdapter/usage.js +0 -37
  258. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
@@ -4,11 +4,14 @@
4
4
  * that version, a copy of pristine is patched into `patched/<digest>/`. Launch then runs the package's
5
5
  * `bin` directly with the daemon's own Node — no npx, no network, nothing resolved at open time.
6
6
  */
7
+ import { createHash } from 'node:crypto';
7
8
  import { execFile } from 'node:child_process';
8
9
  import { existsSync, statSync } from 'node:fs';
9
- import { open, readFile } from 'node:fs/promises';
10
+ import { open, readFile, readdir, rm, writeFile } from 'node:fs/promises';
10
11
  import path from 'node:path';
11
12
  import { createPatchedTree, expectedPatchedFiles, patchDigest, selectPackagePatches, verifyPatchedFiles } from '../../managedPatches.js';
13
+ import { installPolicyFor } from '../../patchAssets.js';
14
+ import { assertRuntimeRequest, loadRuntimeRelease, runtimeRecipes, verifyRuntimeTree, verifyManagedRuntime } from '../../managedRuntime.js';
12
15
  import { assertSafePackageName, assertSafeVersion, buildIdFor, inside, npmPatchedDir, npmPristineDir, } from '../model.js';
13
16
  import { assertBuildRunsOn } from '../nodeGate.js';
14
17
  import { publishStaged } from '../store.js';
@@ -42,9 +45,9 @@ export function runNpmViewLatest(npmCommand, name, timeoutMs) {
42
45
  });
43
46
  }
44
47
  /** `npm install --prefix <prefix> --no-save --no-audit --no-fund <spec>`, argv only, never a shell. */
45
- export function runNpmInstall(npmCommand, prefix, spec, timeoutMs) {
48
+ export function runNpmInstall(npmCommand, prefix, spec, timeoutMs, options = {}) {
46
49
  return new Promise((resolve) => {
47
- execFile(npmCommand, ['install', '--prefix', prefix, '--no-save', '--no-audit', '--no-fund', spec], { timeout: timeoutMs, windowsHide: true, maxBuffer: 16 * 1024 * 1024 }, (error, _stdout, stderr) => {
50
+ execFile(npmCommand, ['install', '--prefix', prefix, '--no-save', '--no-audit', '--no-fund', ...(options.ignoreScripts ? ['--ignore-scripts'] : []), spec], { timeout: timeoutMs, windowsHide: true, maxBuffer: 16 * 1024 * 1024 }, (error, _stdout, stderr) => {
48
51
  if (!error) {
49
52
  resolve({ ok: true });
50
53
  return;
@@ -68,6 +71,36 @@ async function installedVersion(treeRoot, name) {
68
71
  const metadata = await readPackage(inside(path.join(treeRoot, 'node_modules'), name));
69
72
  return metadata?.name === name && typeof metadata.version === 'string' ? metadata.version : undefined;
70
73
  }
74
+ /** Download server bytes, verifying npm's base64 sha512 before writing a local archive. */
75
+ export async function fetchVerifiedTarball(options) {
76
+ const response = await fetch(options.url, { signal: AbortSignal.timeout(options.timeoutMs) });
77
+ if (!response.ok)
78
+ throw new Error(`download ${options.url} failed: HTTP ${response.status}`);
79
+ const body = Buffer.from(await response.arrayBuffer());
80
+ if (createHash('sha512').update(body).digest('base64') !== options.sha512)
81
+ throw new Error(`tarball sha512 mismatch for ${options.url}`);
82
+ const file = path.join(options.dir, 'adapter.tgz');
83
+ await writeFile(file, body);
84
+ return file;
85
+ }
86
+ /** Read actual installed top-level package identities, including scoped dependencies. */
87
+ export async function installedDependencies(treeRoot) {
88
+ const modules = path.join(treeRoot, 'node_modules');
89
+ const dependencies = {};
90
+ for (const entry of await readdir(modules, { withFileTypes: true })) {
91
+ if (entry.name.startsWith('.'))
92
+ continue;
93
+ const names = entry.name.startsWith('@')
94
+ ? (await readdir(path.join(modules, entry.name))).map((name) => `${entry.name}/${name}`)
95
+ : [entry.name];
96
+ for (const name of names) {
97
+ const metadata = await readPackage(inside(modules, name));
98
+ if (metadata?.name === name && typeof metadata.version === 'string')
99
+ dependencies[name] = metadata.version;
100
+ }
101
+ }
102
+ return dependencies;
103
+ }
71
104
  /**
72
105
  * Make sure `npm/<sha16(name)>/<version>/pristine` exists. `version` undefined = unpinned (npm's
73
106
  * `latest`, the install-time ETARGET fallback): the version is read back off what npm installed.
@@ -76,6 +109,14 @@ async function installedVersion(treeRoot, name) {
76
109
  export async function ensurePristine(options) {
77
110
  const { store, name } = options;
78
111
  assertSafePackageName(name);
112
+ if (options.policy) {
113
+ assertRuntimeRequest(options.policy, name, options.version);
114
+ const release = await loadRuntimeRelease(options.policy, options);
115
+ if (options.tarball && options.tarball.sha512 !== release.integrity.slice('sha512-'.length))
116
+ throw new Error('Managed runtime archive integrity differs from bundled release');
117
+ }
118
+ if (options.tarball && options.version === undefined)
119
+ throw new Error('Server adapter tarball requires a pinned version');
79
120
  if (options.version === undefined) {
80
121
  // Unpinned (the install-time ETARGET fallback): name the version first, so a `latest` that is
81
122
  // already installed costs one metadata request and no download.
@@ -95,13 +136,26 @@ export async function ensurePristine(options) {
95
136
  }
96
137
  if (options.version !== undefined) {
97
138
  const final = inside(store.root, npmPristineDir(name, options.version));
139
+ if (existsSync(final) && options.policy) {
140
+ await verifyRuntimeTree({ ...options, policy: options.policy, treeRoot: final, pristine: true });
141
+ return { ok: true, version: options.version, downloaded: false };
142
+ }
98
143
  if ((await installedVersion(final, name)) === options.version)
99
144
  return { ok: true, version: options.version, downloaded: false };
100
145
  }
101
146
  const staged = await store.stagingDir();
102
147
  try {
103
148
  const spec = options.version === undefined ? name : `${name}@${options.version}`;
104
- const ran = await runNpmInstall(options.npmCommand, staged, spec, options.timeoutMs);
149
+ let tarball = options.tarball;
150
+ if (options.policy) {
151
+ const release = await loadRuntimeRelease(options.policy, options);
152
+ const sha512 = release.integrity.slice('sha512-'.length);
153
+ if (tarball && tarball.sha512 !== sha512)
154
+ throw new Error('Managed runtime archive integrity differs from bundled release');
155
+ tarball ??= await runtimeArchive(options.npmCommand, spec, release.integrity, options.timeoutMs);
156
+ }
157
+ const archive = tarball ? await fetchVerifiedTarball({ ...tarball, dir: staged, timeoutMs: options.timeoutMs }) : undefined;
158
+ const ran = await runNpmInstall(options.npmCommand, staged, archive ?? spec, options.timeoutMs, { ignoreScripts: options.policy?.ignore_scripts ?? installPolicyFor(name)?.ignore_scripts });
105
159
  if (!ran.ok)
106
160
  return ran;
107
161
  const version = await installedVersion(staged, name);
@@ -111,7 +165,14 @@ export async function ensurePristine(options) {
111
165
  return { ok: false, error: `npm installed ${name}@${version} when ${spec} was asked for`, stderr: '' };
112
166
  }
113
167
  assertSafeVersion(version);
168
+ if (options.policy)
169
+ await verifyRuntimeTree({ ...options, policy: options.policy, treeRoot: staged, pristine: true, prepareTools: true });
170
+ if (archive)
171
+ await rm(archive);
114
172
  const { published } = await publishStaged(staged, inside(store.root, npmPristineDir(name, version)));
173
+ // publishStaged may choose an existing winner: verify that actual tree, never just staging.
174
+ if (options.policy)
175
+ await verifyRuntimeTree({ ...options, policy: options.policy, treeRoot: inside(store.root, npmPristineDir(name, version)), pristine: true });
115
176
  // Not published = that version's pristine was already there (we only learned the version now).
116
177
  return { ok: true, version, downloaded: published };
117
178
  }
@@ -168,9 +229,12 @@ async function runtimeOf(file) {
168
229
  */
169
230
  export async function npmBuild(options) {
170
231
  const { store, name, version } = options;
232
+ if (options.policy)
233
+ assertRuntimeRequest(options.policy, name, version);
171
234
  const pristineRel = npmPristineDir(name, version);
172
235
  const pristine = inside(store.root, pristineRel);
173
- const recipes = await selectPackagePatches(name, version, inside(path.join(pristine, 'node_modules'), name), options.recipes);
236
+ const release = options.policy ? await verifyRuntimeTree({ ...options, policy: options.policy, treeRoot: pristine, pristine: true }) : undefined;
237
+ const recipes = release && options.policy ? runtimeRecipes(options.policy, release, options.recipes) : await selectPackagePatches(name, version, inside(path.join(pristine, 'node_modules'), name), options.recipes, await installedDependencies(pristine));
174
238
  let dir = pristineRel;
175
239
  let patch = {};
176
240
  if (recipes.length) {
@@ -180,7 +244,7 @@ export async function npmBuild(options) {
180
244
  if (!existsSync(final)) {
181
245
  const staged = await store.stagingDir();
182
246
  try {
183
- await createPatchedTree(pristine, staged, name, version, recipes);
247
+ await createPatchedTree(pristine, staged, name, version, recipes, options.assetRoot);
184
248
  await publishStaged(staged, final);
185
249
  }
186
250
  finally {
@@ -189,22 +253,24 @@ export async function npmBuild(options) {
189
253
  }
190
254
  // Whoever published it, the tree's receipt must be for exactly this recipe set.
191
255
  const receipt = JSON.parse(await readFile(path.join(final, 'axom-patches.json'), 'utf8'));
256
+ if (receipt.patchedFilesBase !== undefined && receipt.patchedFilesBase !== 'node_modules')
257
+ throw new Error('Invalid Axom patched file base');
192
258
  if (receipt.digest !== digest)
193
259
  throw new Error('Axom adapter patch receipt does not match this CLI');
194
260
  // Expected hashes come from the shipped recipes, never from the writable receipt alone.
195
261
  const files = expectedPatchedFiles(recipes);
196
- await verifyPatchedFiles(inside(path.join(final, 'node_modules'), name), files);
197
- patch = { patchDigest: digest, patches: recipes.map((recipe) => recipe.id), patchedFiles: files };
262
+ await verifyPatchedFiles(path.join(final, 'node_modules'), files);
263
+ patch = { patchedFilesBase: 'node_modules', patchDigest: digest, patches: recipes.map((recipe) => recipe.id), patchedFiles: files };
198
264
  }
199
265
  const root = inside(store.root, dir);
200
266
  const packageRel = `node_modules/${name}`;
201
267
  const metadata = await readPackage(inside(root, packageRel));
202
268
  if (metadata?.name !== name || metadata.version !== version)
203
269
  throw new Error(`Adapter package identity changed under ${dir}`);
204
- const explicit = [...new Set(recipes.map((recipe) => recipe.bin).filter((bin) => bin !== undefined))];
270
+ const explicit = [...new Set(recipes.filter((recipe) => recipe.package === name).map((recipe) => recipe.bin).filter((bin) => bin !== undefined))];
205
271
  if (explicit.length > 1)
206
272
  throw new Error('Axom patches disagree about the adapter executable');
207
- const entryRel = path.posix.normalize(`${packageRel}/${selectBin(name, metadata.bin, explicit[0])}`);
273
+ const entryRel = path.posix.normalize(`${packageRel}/${selectBin(name, metadata.bin, release?.bin.name ?? explicit[0])}`);
208
274
  const entry = inside(root, entryRel);
209
275
  if (!entryRel.startsWith(`${packageRel}/`) || !statSync(entry).isFile())
210
276
  throw new Error(`Adapter executable is missing in ${dir}`);
@@ -217,6 +283,7 @@ export async function npmBuild(options) {
217
283
  };
218
284
  const build = {
219
285
  buildId: buildIdFor(version, patch.patchDigest),
286
+ ...(release ? { runtimeReleaseId: release.id } : {}),
220
287
  agentId: options.agentId,
221
288
  kind: patch.patchDigest ? 'npm-patched' : 'npm',
222
289
  version,
@@ -231,5 +298,27 @@ export async function npmBuild(options) {
231
298
  };
232
299
  // Never hand back (so never commit, never make current) a build its own Node gate refuses.
233
300
  await assertBuildRunsOn(build, store.root, options.nodePath);
301
+ if (options.policy)
302
+ await verifyManagedRuntime({ ...options, policy: options.policy, build, storeRoot: store.root, initialize: true });
234
303
  return build;
235
304
  }
305
+ /** Registry metadata supplies only a location; its digest must equal the bundled immutable release. */
306
+ async function runtimeArchive(npmCommand, spec, integrity, timeoutMs) {
307
+ return new Promise((resolve, reject) => {
308
+ execFile(npmCommand, ['view', spec, 'dist', '--json'], { timeout: timeoutMs, windowsHide: true, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
309
+ if (error) {
310
+ reject(new Error(`Managed runtime exact archive lookup failed: ${stderr.toString().slice(-2048) || error.message}`));
311
+ return;
312
+ }
313
+ try {
314
+ const dist = JSON.parse(stdout.toString());
315
+ if (dist.integrity !== integrity || typeof dist.tarball !== 'string' || !/^https?:\/\//.test(dist.tarball))
316
+ throw new Error('Managed runtime archive metadata integrity mismatch');
317
+ resolve({ url: dist.tarball, sha512: integrity.slice('sha512-'.length) });
318
+ }
319
+ catch (error) {
320
+ reject(error);
321
+ }
322
+ });
323
+ });
324
+ }
@@ -13,7 +13,7 @@
13
13
  * `processor/initialize.test.ts` then guards the processor's *wiring* (the right line, the right
14
14
  * result), not a second copy of these rules.
15
15
  *
16
- * The Qoder runtime profile in `_meta` is deliberately not read here: it belongs to the connection's
16
+ * The runtime profile in `_meta` is deliberately not read here: it belongs to the connection's
17
17
  * runtime diagnostics, which the processor takes over in Task 17b/17c.
18
18
  */
19
19
  import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk';
@@ -81,6 +81,7 @@ export function initializeRequestParams() {
81
81
  // on another machine cannot do; the standard `authenticate` methods (device-code style) are
82
82
  // unaffected and never needed this key.
83
83
  'terminal-auth': true,
84
+ terminal_output_delta: true,
84
85
  'subagent-transcript': true,
85
86
  ...airCapabilityMeta(AIR_NATIVE_SUBAGENT_SESSIONS_CAPABILITY, AIR_ASYNC_TASKS_CAPABILITY),
86
87
  },
@@ -110,6 +111,9 @@ export function parseInitializeResult(initResponse) {
110
111
  if (initResponse.protocolVersion !== PROTOCOL_VERSION) {
111
112
  return { protocolSupported: false, protocolVersion: initResponse.protocolVersion };
112
113
  }
114
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
115
+ const axomMeta = isRecord(initResponse._meta) && isRecord(initResponse._meta.axom) ? initResponse._meta.axom : undefined;
116
+ const cachedReconnect = axomMeta?.cachedReconnect === 'resume' ? 'resume' : undefined;
113
117
  const sessionCapabilities = initResponse.agentCapabilities?.sessionCapabilities;
114
118
  const capabilities = {
115
119
  // Capability markers are empty objects (`{ _meta? }`); supplying `{}` means "supported", and
@@ -155,6 +159,7 @@ export function parseInitializeResult(initResponse) {
155
159
  }
156
160
  return {
157
161
  protocolSupported: true,
162
+ ...(cachedReconnect ? { cachedReconnect } : {}),
158
163
  capabilities,
159
164
  authMethods,
160
165
  droppedAuthMethods: advertisedAuthMethods.length - authMethods.length,
@@ -5,10 +5,11 @@
5
5
  */
6
6
  import { createHash } from 'node:crypto';
7
7
  import { constants } from 'node:fs';
8
- import { cp, readFile, writeFile } from 'node:fs/promises';
8
+ import { cp, lstat, mkdir, open, realpath, writeFile } from 'node:fs/promises';
9
9
  import path from 'node:path';
10
- import { inside } from './builds/model.js';
11
- import { packagePatchMatch, packagePatches } from './patchAssets.js';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { assertSafePackageName, inside } from './builds/model.js';
12
+ import { packagePatchMatch, packagePatches, patchAssetRoot, validatePackagePatch, validatePatchPath } from './patchAssets.js';
12
13
  const sha = (content) => createHash('sha256').update(content).digest('hex');
13
14
  const warned = new Set();
14
15
  function warnOnce(key, message) {
@@ -18,30 +19,149 @@ function warnOnce(key, message) {
18
19
  console.warn(message);
19
20
  }
20
21
  function within(root, relative) {
22
+ validatePatchPath(relative);
21
23
  const target = path.resolve(root, relative);
22
24
  if (!target.startsWith(path.resolve(root) + path.sep))
23
25
  throw new Error('Axom patch path escapes its package');
24
26
  return target;
25
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
+ }
26
100
  /** A matching version and exact original bytes; already-patched or changed originals are rejected. */
27
- export async function applyPackagePatches(packageDir, recipes) {
101
+ export async function applyPackagePatches(nodeModulesDir, recipes, assetRoot = patchAssetRoot) {
28
102
  const output = {};
103
+ // Validate the complete declaration and asset closure before any writes.
104
+ expectedPatchedFiles(recipes);
105
+ const assets = new Map();
29
106
  for (const recipe of recipes) {
30
- const metadata = JSON.parse(await readFile(path.join(packageDir, 'package.json'), 'utf8'));
107
+ const packageDir = inside(nodeModulesDir, recipe.package);
108
+ const metadata = JSON.parse((await readOwnedFile(path.join(packageDir, 'package.json'))).toString('utf8'));
31
109
  if (metadata.name !== recipe.package || !packagePatchMatch(recipe, metadata.version)) {
32
110
  throw new Error(`Axom patch '${recipe.id}' does not support ${metadata.name}@${metadata.version}`);
33
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);
34
133
  for (const edit of recipe.edits) {
35
134
  const filename = within(packageDir, edit.file);
36
- const original = await readFile(filename, 'utf8');
135
+ const original = (await readOwnedFile(filename)).toString('utf8');
37
136
  if (sha(original) !== edit.before_sha256 || !edit.before || original.split(edit.before).length !== 2) {
38
137
  throw new Error(`Axom patch '${recipe.id}': original bytes changed in ${edit.file}`);
39
138
  }
40
139
  const patched = original.replace(edit.before, edit.after);
41
140
  if (sha(patched) !== edit.after_sha256)
42
141
  throw new Error(`Axom patch '${recipe.id}': invalid expected output`);
43
- await writeFile(filename, patched, 'utf8');
44
- output[edit.file] = sha(patched);
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;
45
165
  }
46
166
  }
47
167
  return output;
@@ -53,7 +173,7 @@ async function originalsFit(packageDir, recipe) {
53
173
  let content = files.get(edit.file);
54
174
  if (content === undefined) {
55
175
  try {
56
- content = await readFile(within(packageDir, edit.file), 'utf8');
176
+ content = (await readOwnedFile(within(packageDir, edit.file))).toString('utf8');
57
177
  }
58
178
  catch {
59
179
  return false;
@@ -71,22 +191,25 @@ async function originalsFit(packageDir, recipe) {
71
191
  * patch step loudly), range recipes only when the pristine bytes fit (otherwise upstream runs, with a
72
192
  * warning). An empty answer means the build is the pristine tree itself.
73
193
  */
74
- export async function selectPackagePatches(name, version, pristinePackageDir, recipes = packagePatches) {
75
- const candidates = recipes.filter((patch) => patch.package === name);
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));
76
196
  const matched = candidates.flatMap((recipe) => {
77
- const match = packagePatchMatch(recipe, version);
197
+ const match = packagePatchMatch(recipe, recipe.package === name ? version : dependencies[recipe.package]);
78
198
  return match ? [{ recipe, match }] : [];
79
199
  });
80
200
  if (matched.length && (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i.test(name) || !/^[0-9a-z.+-]+$/i.test(version))) {
81
201
  throw new Error('Invalid package identity for Axom adapter patch');
82
202
  }
83
203
  const selected = [];
204
+ const nodeModulesDir = path.resolve(pristinePackageDir, ...name.split('/').map(() => '..'));
84
205
  for (const { recipe, match } of matched) {
206
+ assertSafePackageName(recipe.package);
207
+ const packageDir = recipe.package === name ? pristinePackageDir : inside(nodeModulesDir, recipe.package);
85
208
  if (match === 'exact') {
86
209
  selected.push(recipe);
87
210
  continue;
88
211
  }
89
- if (await originalsFit(pristinePackageDir, recipe))
212
+ if (await originalsFit(packageDir, recipe))
90
213
  selected.push(recipe);
91
214
  else
92
215
  warnOnce(`${recipe.id}@${name}@${version}`, `[axom-patch] ${recipe.id} does not fit ${name}@${version}; using the upstream package. Review the patch inventory.`);
@@ -104,39 +227,80 @@ export function patchDigest(recipes) {
104
227
  * Copy the pristine `node_modules` (reflink where the filesystem supports it — `COPYFILE_FICLONE`
105
228
  * falls back to an ordinary copy by itself; relative symlinks such as `.bin/*` are kept relative so
106
229
  * the copy never points back into pristine) into `stagedRoot`, patch the copy, and write a receipt.
107
- * Returns the sha256 of every patched file, relative to the package directory.
230
+ * Returns the sha256 of every patched file, qualified by its package name under node_modules.
108
231
  */
109
- export async function createPatchedTree(pristineRoot, stagedRoot, name, version, recipes) {
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');
237
+ }
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);
242
+ try {
243
+ await lstat(destination);
244
+ throw new Error(`Axom patch staged target already exists: ${destination}`);
245
+ }
246
+ catch (error) {
247
+ if (error.code !== 'ENOENT')
248
+ throw error;
249
+ }
250
+ }
110
251
  await cp(path.join(pristineRoot, 'node_modules'), path.join(stagedRoot, 'node_modules'), {
111
252
  recursive: true, verbatimSymlinks: true, mode: constants.COPYFILE_FICLONE,
112
253
  });
113
- const packageDir = inside(path.join(stagedRoot, 'node_modules'), name);
114
- const files = await applyPackagePatches(packageDir, recipes);
254
+ const files = await applyPackagePatches(path.join(stagedRoot, 'node_modules'), recipes, assetRoot);
115
255
  const expected = expectedPatchedFiles(recipes);
116
256
  if (JSON.stringify(sortKeys(files)) !== JSON.stringify(sortKeys(expected)))
117
257
  throw new Error('Incomplete Axom adapter patch');
118
- await writeFile(path.join(stagedRoot, 'axom-patches.json'), JSON.stringify({
119
- digest: patchDigest(recipes), package: name, version, patches: recipes.map((recipe) => recipe.id), files,
120
- }) + '\n');
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' });
121
262
  console.info(`[axom-patch] Applied ${recipes.map((recipe) => recipe.id).join(', ')} to ${name}@${version}`);
122
263
  return files;
123
264
  }
124
265
  /** The files a recipe set rewrites and their expected sha256, derived from the shipped recipes. */
125
266
  export function expectedPatchedFiles(recipes) {
126
- return Object.fromEntries(recipes.flatMap((recipe) => recipe.edits.map((edit) => [edit.file, edit.after_sha256])));
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;
277
+ }
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}`);
284
+ }
285
+ assetTargets.add(key);
286
+ files[key] = asset.sha256;
287
+ }
288
+ return files;
127
289
  }
128
290
  function sortKeys(record) {
129
291
  return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
130
292
  }
131
- /** The launch-time tamper check: every patched file still hashes to what was recorded at install. */
293
+ /** Tamper check against an explicit base: node_modules for qualified keys, adapter package for legacy keys. */
132
294
  export async function verifyPatchedFiles(packageDir, files) {
133
295
  for (const [file, hash] of Object.entries(files)) {
134
296
  let content;
135
297
  try {
136
- content = await readFile(within(packageDir, file));
298
+ content = await readOwnedFile(within(packageDir, file));
137
299
  }
138
- catch {
139
- throw new Error(`Axom patched adapter is missing ${file}`);
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})`);
140
304
  }
141
305
  if (sha(content) !== hash)
142
306
  throw new Error(`Axom patched adapter was modified: ${file}`);