apt-mcp-agent-setup 3.5.65 → 3.9.0
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 +1 -1
- package/bin/cli.js +1 -1
- package/build-input.json +1329 -633
- package/bundle/NOTICES.md +0 -0
- package/bundle/core.enc +0 -0
- package/bundle/mcp-rules.enc +0 -0
- package/bundle/skills-ba.enc +0 -0
- package/bundle/skills-base.enc +0 -0
- package/bundle/skills-be.enc +0 -0
- package/bundle/skills-fe.enc +0 -0
- package/bundle/skills-mobile.enc +0 -0
- package/docs/apt-3.7.0-migration.md +49 -0
- package/docs/technical-sources.md +20 -18
- package/docs/upstream-manifest.json +36 -19
- package/integrity-manifest.json +207 -181
- package/package.json +3 -3
- package/release-check.js +6 -2
- package/source-skills-lock.json +242 -24
- package/src/backends/{browser-tools-0.0.82.json → browser-tools-0.0.83.json} +375 -375
- package/src/backends/browser-tools.js +1 -1
- package/src/backends/{codegraph-1.6.0.json → codegraph-1.6.2.json} +180 -180
- package/src/backends/index-cache-authority.js +1 -1
- package/src/backends/index-cache-retention.js +1 -0
- package/src/backends/index-command.js +1 -1
- package/src/backends/index-contract.json +2 -2
- package/src/backends/index-maintenance.js +1 -1
- package/src/backends/index-store.js +1 -1
- package/src/backends/linked-worktrees.js +1 -1
- package/src/backends/machine-index-exclude.js +1 -0
- package/src/backends/memory-1.0.0.json +126 -0
- package/src/backends/profiles.js +1 -1
- package/src/backends/review_graph.py +44 -0
- package/src/backends/source-discovery.js +1 -1
- package/src/core/admission-fence.js +1 -1
- package/src/core/antigravity-subagents.js +1 -0
- package/src/core/aso-import.js +1 -1
- package/src/core/backend-lease-recovery.js +1 -1
- package/src/core/boot-evidence.js +1 -1
- package/src/core/caller-context.js +1 -1
- package/src/core/catalog.js +1 -1
- package/src/core/claude-caller-authority.js +1 -1
- package/src/core/claude-invocation.js +1 -1
- package/src/core/claude-launch-recovery.js +1 -1
- package/src/core/claude-native.js +1 -1
- package/src/core/claude-plan.js +1 -1
- package/src/core/claude-transcript-scan.js +1 -1
- package/src/core/claude-worker-transcript.js +1 -1
- package/src/core/codex-host-metadata.js +1 -1
- package/src/core/codex-transcript-receipt.js +1 -1
- package/src/core/connection-inspector.js +1 -1
- package/src/core/content-hash-cache.js +1 -1
- package/src/core/context-metrics.js +1 -1
- package/src/core/design-standard.js +1 -0
- package/src/core/doctor.js +1 -1
- package/src/core/draining-completion.js +1 -1
- package/src/core/execution.js +1 -1
- package/src/core/git-context.js +1 -1
- package/src/core/hook-bridge.js +1 -1
- package/src/core/hook-diagnostics.js +1 -1
- package/src/core/hook-lease.js +1 -1
- package/src/core/hook-phase.js +1 -0
- package/src/core/host-adapters.js +1 -1
- package/src/core/interrupted-boot-transport-proof.js +1 -1
- package/src/core/interrupted-natural-closure-proof.js +1 -1
- package/src/core/interrupted-operation-audit.js +1 -1
- package/src/core/interrupted-transport-proof.js +1 -1
- package/src/core/ipc-envelope.js +1 -1
- package/src/core/learning-cli.js +1 -0
- package/src/core/learning-proposals.js +1 -0
- package/src/core/learning-rules.js +1 -0
- package/src/core/learning-signals.js +1 -0
- package/src/core/learning-store.js +1 -0
- package/src/core/learning-sync.js +1 -0
- package/src/core/legacy-retirement-proof.js +1 -1
- package/src/core/memory-scope.js +1 -1
- package/src/core/memory-store.js +1 -1
- package/src/core/model-inventory.js +1 -1
- package/src/core/model-routing.js +1 -1
- package/src/core/native-dispatch.js +1 -1
- package/src/core/native-receipt.js +1 -1
- package/src/core/native-retirement.js +1 -1
- package/src/core/node-version.js +1 -1
- package/src/core/npm-cli.js +1 -1
- package/src/core/owned-command-child.js +1 -1
- package/src/core/owned-command.js +1 -1
- package/src/core/owned-lock.js +1 -1
- package/src/core/owned-process.js +1 -1
- package/src/core/pipeline-journal.js +1 -1
- package/src/core/presentations.js +1 -1
- package/src/core/quality-artifacts.js +1 -1
- package/src/core/quality.js +1 -1
- package/src/core/read-command.js +1 -1
- package/src/core/readonly-skill.js +1 -1
- package/src/core/recovery-archive.js +1 -1
- package/src/core/recovery-proof.js +1 -1
- package/src/core/resource-transaction.js +1 -1
- package/src/core/run-checkpoint.js +1 -1
- package/src/core/run-compatibility.js +1 -1
- package/src/core/run-journal-retention.js +1 -0
- package/src/core/run-mailbox.js +1 -0
- package/src/core/run-owner.js +1 -1
- package/src/core/run-state-codec.js +1 -1
- package/src/core/runtime-authority.js +1 -1
- package/src/core/runtime-info.js +1 -1
- package/src/core/runtime-ownership.js +1 -1
- package/src/core/session.js +1 -1
- package/src/core/shared-checkout.js +1 -1
- package/src/core/skill-companions.js +1 -1
- package/src/core/skill-names.js +1 -1
- package/src/core/storage-retry.js +1 -1
- package/src/core/tool-contracts.js +1 -1
- package/src/core/update-control.js +1 -1
- package/src/core/upgrade-runs.js +1 -1
- package/src/core/upgrade-stop.js +1 -1
- package/src/core/windows-job.js +1 -1
- package/src/core/workflow-policy.js +1 -1
- package/src/core/workspace-generation.js +1 -1
- package/src/core/workspace-layout.js +1 -0
- package/src/core/workspace-path.js +1 -1
- package/src/installer/bounded-command.js +1 -1
- package/src/installer/candidate-preflight.js +1 -1
- package/src/installer/claude-commands.js +1 -1
- package/src/installer/codex-hook-trust.js +1 -1
- package/src/installer/connection-activation.js +1 -1
- package/src/installer/connection-retention.js +1 -0
- package/src/installer/connection-supervisor.js +1 -1
- package/src/installer/deployment-draft.js +1 -1
- package/src/installer/gateway-context.js +1 -1
- package/src/installer/gateway-migration.js +1 -1
- package/src/installer/gateway-runtime.js +1 -1
- package/src/installer/global-install.js +1 -0
- package/src/installer/global-instructions.js +1 -0
- package/src/installer/global-setup.js +1 -1
- package/src/installer/hook-context-client.js +1 -1
- package/src/installer/hook-dispatcher.js +372 -179
- package/src/installer/host-hooks.js +1 -1
- package/src/installer/index-interruption-recovery.js +1 -1
- package/src/installer/index-recovery.js +1 -1
- package/src/installer/install-intent.js +1 -1
- package/src/installer/interrupted-operation-forget.js +1 -0
- package/src/installer/interrupted-operation-recovery.js +1 -1
- package/src/installer/legacy-prepared-plan.js +1 -1
- package/src/installer/legacy-registration.js +1 -1
- package/src/installer/managed-config.js +1 -1
- package/src/installer/migration-scope.js +1 -1
- package/src/installer/platform-config.js +1 -1
- package/src/installer/prerequisites.js +1 -1
- package/src/installer/presentations.js +1 -1
- package/src/installer/project-outcome.js +1 -1
- package/src/installer/project-setup.js +1 -1
- package/src/installer/recovery-backup-retention.js +1 -1
- package/src/installer/registration-closure.js +1 -1
- package/src/installer/registration-plan.js +1 -1
- package/src/installer/registration-receipt.js +1 -1
- package/src/installer/registrations.js +1 -1
- package/src/installer/runtime-activation.js +1 -1
- package/src/installer/runtime-child.js +1 -1
- package/src/installer/runtime-cleanup.js +1 -1
- package/src/installer/runtime-launcher.js +16 -16
- package/src/installer/runtime-lock.js +1 -1
- package/src/installer/runtime-store.js +1 -1
- package/src/installer/runtime-supervisor.js +1 -1
- package/src/installer/setup-wizard.js +1 -1
- package/src/installer/stale-run-release.js +1 -1
- package/src/installer/uninstall.js +1 -1
- package/src/installer/update-addons.js +1 -1
- package/src/installer/update-cleanup.js +1 -1
- package/src/installer/update-executor.js +1 -1
- package/src/installer/update-history.js +1 -1
- package/src/installer/update-registration.js +1 -1
- package/src/installer/update-report.js +1 -1
- package/src/installer/update-request.js +1 -1
- package/src/installer/update-scope.js +1 -1
- package/src/installer/workspace-registry.js +1 -1
- package/src/installer/workspace-transaction.js +1 -1
- package/src/installer/workspace-update.js +1 -1
- package/src/installer/worktree-retention.js +1 -0
- package/src/license/canonical.js +1 -0
- package/src/license/crypto.js +1 -1
- package/src/license/delegation.js +1 -0
- package/src/license/fingerprint.js +1 -1
- package/src/license/server-config.js +1 -0
- package/src/license/terms.js +1 -1
- package/src/license/trust.js +1 -0
- package/src/license/verify.js +1 -1
- package/src/packs/deploy.js +1 -0
- package/src/packs/manifest.js +1 -0
- package/src/packs/status.js +1 -0
- package/src/packs/store.js +1 -0
- package/src/packs/sync.js +1 -0
- package/src/presets/index.js +1 -1
- package/src/proxy/backends.js +1 -1
- package/src/proxy/browser-sessions.js +1 -1
- package/src/proxy/core-tools.js +1 -1
- package/src/proxy/learning-tools.js +1 -0
- package/src/proxy/pipeline.js +1 -1
- package/src/proxy/public-catalog.js +1 -1
- package/src/proxy/router.config.js +1 -1
- package/src/proxy/router.js +1 -1
- package/src/proxy/run-summary.js +1 -1
- package/src/proxy/server-instructions.js +1 -1
- package/src/proxy/server.js +1 -1
- package/src/proxy/workspace-gateway.js +1 -1
- package/src/templates/CLAUDE.md +13 -13
- package/src/templates/apt-runtime.md +31 -27
- package/src/templates/domain.md +3 -0
- package/src/templates/mcp-tools.md +4 -1
- package/bundle/skills-aso.enc +0 -0
- package/bundle/skills-design.enc +0 -0
- package/bundle/skills-marketing.enc +0 -0
- package/bundle/skills-pm.enc +0 -0
- package/src/backends/memory-2026.8.31.json +0 -34
|
@@ -1,179 +1,372 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Embedded with a trusted installation key. Only built-in modules may be used
|
|
4
|
-
// until the committed release has been verified.
|
|
5
|
-
function hookDispatcher(root, publicKey) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
function
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
//
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Embedded with a trusted installation key. Only built-in modules may be used
|
|
4
|
+
// until the committed release has been verified.
|
|
5
|
+
function hookDispatcher(root, publicKey) {
|
|
6
|
+
const fs = require('node:fs'), path = require('node:path'), crypto = require('node:crypto'), os = require('node:os');
|
|
7
|
+
const canonicalOf = value => process.platform === 'win32' ? value.toLowerCase() : value;
|
|
8
|
+
// Machine install: the hook file is the user's, shared by every folder, so
|
|
9
|
+
// the workspace comes from the host's payload. A folder with a project
|
|
10
|
+
// install has its own hooks; this one yields there, since hosts run both.
|
|
11
|
+
function machineWorkspace(input) {
|
|
12
|
+
const candidate = input && (input.cwd || (input.workspace_roots || [])[0] || (input.workspacePaths || [])[0]);
|
|
13
|
+
if (typeof candidate !== 'string' || !path.isAbsolute(candidate) || !fs.existsSync(path.join(root, 'global-install.json'))) return null;
|
|
14
|
+
let real;
|
|
15
|
+
try { real = fs.realpathSync(candidate); } catch { return null; }
|
|
16
|
+
const refused = [os.homedir(), path.parse(real).root, path.join(os.homedir(), '.anhdh'), root].map(value => canonicalOf(path.resolve(value)));
|
|
17
|
+
const inside = (parent, child) => { const relative = path.relative(parent, child); return !relative || !relative.startsWith('..') && !path.isAbsolute(relative); };
|
|
18
|
+
if (refused.includes(canonicalOf(real)) || refused.slice(2).some(parent => inside(parent, canonicalOf(real)))) return null;
|
|
19
|
+
const ancestors = [];
|
|
20
|
+
for (let cursor = real; ; cursor = path.dirname(cursor)) {
|
|
21
|
+
if (fs.existsSync(path.join(cursor, '.agents', 'manifest.json'))) return null;
|
|
22
|
+
ancestors.push(cursor);
|
|
23
|
+
if (path.dirname(cursor) === cursor) break;
|
|
24
|
+
}
|
|
25
|
+
// The folder a session bound, else the checkout around the current directory.
|
|
26
|
+
const enrolled = ancestors.find(value => fs.existsSync(path.join(root, 'projects',
|
|
27
|
+
crypto.createHash('sha256').update(canonicalOf(value)).digest('hex').slice(0, 24) + '.json')));
|
|
28
|
+
return enrolled || ancestors.find(value => fs.existsSync(path.join(value, '.git'))) || real;
|
|
29
|
+
}
|
|
30
|
+
// The step a supervised worker has reached, for the deadline message.
|
|
31
|
+
const mark = phase => {
|
|
32
|
+
if (process.env.APT_HOOK_PROCESS === '1' && typeof process.send === 'function' && process.connected)
|
|
33
|
+
try { process.send({ type: 'apt-hook-phase', phase }); } catch { /* the supervisor is gone */ }
|
|
34
|
+
};
|
|
35
|
+
function dispatch(host, event, workspace, executionOptions, machine) {
|
|
36
|
+
mark('verify-release');
|
|
37
|
+
const canonical = canonicalOf(workspace);
|
|
38
|
+
const store = path.join(root, 'global');
|
|
39
|
+
const authorityFile = machine ? path.join(store, '.agents', 'runtime', 'generation.json') : path.join(workspace, '.agents', 'runtime', 'generation.json');
|
|
40
|
+
let selected;
|
|
41
|
+
try { selected = JSON.parse(fs.readFileSync(authorityFile, 'utf8')); }
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code !== 'ENOENT') throw error;
|
|
44
|
+
// Before the first connection writes the generation: the pin install
|
|
45
|
+
// wrote, never the machine's latest pointer or a release inferred from
|
|
46
|
+
// deployed resource version. The machine install has the pin of its store.
|
|
47
|
+
const pinned = machine ? canonicalOf(fs.realpathSync(store)) : canonical;
|
|
48
|
+
selected = JSON.parse(fs.readFileSync(path.join(root, 'workspaces',
|
|
49
|
+
crypto.createHash('sha256').update(pinned).digest('hex') + '.json'), 'utf8'));
|
|
50
|
+
}
|
|
51
|
+
const owner = machine ? canonicalOf(fs.realpathSync(store)) : canonical;
|
|
52
|
+
if (selected.workspace && canonicalOf(selected.workspace) !== owner)
|
|
53
|
+
throw Error('HOOK_WORKSPACE_BINDING_CONFLICT');
|
|
54
|
+
if (!/^\d+\.\d+\.\d+$/.test(selected.version) || !/^[a-f0-9]{64}$/.test(selected.digest))
|
|
55
|
+
throw Error('HOOK_RUNTIME_BINDING_REQUIRED');
|
|
56
|
+
const release = path.join(root, 'releases', `${selected.version}-${selected.digest}`);
|
|
57
|
+
if (fs.lstatSync(release).isSymbolicLink()) throw Error('HOOK_RUNTIME_LINK_REJECTED');
|
|
58
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(release, 'integrity-manifest.json'), 'utf8'));
|
|
59
|
+
if (crypto.createHash('sha256').update(JSON.stringify(manifest)).digest('hex') !== selected.digest ||
|
|
60
|
+
// The public key may be a set of keys (several PEM blocks) while a signing key is rotated.
|
|
61
|
+
!(String(publicKey).match(/-----BEGIN PUBLIC KEY-----[\s\S]+?-----END PUBLIC KEY-----/g) || [publicKey]).some(key => { try { return crypto.verify(null, Buffer.from(JSON.stringify(manifest.files)), key, Buffer.from(manifest.signature, 'base64url')); } catch { return false; } }))
|
|
62
|
+
throw Error('HOOK_RUNTIME_SIGNATURE_INVALID');
|
|
63
|
+
for (const required of ['src/core/hook-bridge.js'])
|
|
64
|
+
if (!manifest.files[required]) throw Error('HOOK_RUNTIME_RECONNECT_REQUIRED');
|
|
65
|
+
// Verified release file identities (size, mtime, ctime, inode, signed digest)
|
|
66
|
+
// live outside releases/ so unsigned cache files never enter release trees.
|
|
67
|
+
const cacheDir = path.join(root, 'release-cache');
|
|
68
|
+
const cacheFile = path.join(cacheDir, `${selected.version}-${selected.digest}.json`);
|
|
69
|
+
let cachedFiles = null;
|
|
70
|
+
try {
|
|
71
|
+
if (!fs.lstatSync(cacheDir).isSymbolicLink() && !fs.lstatSync(cacheFile).isSymbolicLink()) {
|
|
72
|
+
const parsed = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
73
|
+
if (parsed && parsed.digest === selected.digest && parsed.files && typeof parsed.files === 'object')
|
|
74
|
+
cachedFiles = parsed.files;
|
|
75
|
+
}
|
|
76
|
+
} catch { /* cold or unreadable cache */ }
|
|
77
|
+
const nextCache = {};
|
|
78
|
+
let cacheDirty = !cachedFiles;
|
|
79
|
+
for (const [file, digest] of Object.entries(manifest.files)) {
|
|
80
|
+
if (path.isAbsolute(file) || file.includes('\\') || file.split('/').some(part => !part || part === '..' || part === '.'))
|
|
81
|
+
throw Error('HOOK_RUNTIME_PATH_INVALID');
|
|
82
|
+
// A hook runs code from src/ only. The signed list still names every
|
|
83
|
+
// file; the encrypted bundles (15 of 19 MB) were read and hashed on every
|
|
84
|
+
// hook call, and a cold read on comaker ran hooks past their deadline.
|
|
85
|
+
if (!file.startsWith('src/') && file !== 'package.json') continue;
|
|
86
|
+
let cursor = release, leafStat = null;
|
|
87
|
+
for (const part of file.split('/')) {
|
|
88
|
+
cursor = path.join(cursor, part);
|
|
89
|
+
leafStat = fs.lstatSync(cursor, { bigint: true });
|
|
90
|
+
if (leafStat.isSymbolicLink()) throw Error('HOOK_RUNTIME_LINK_REJECTED');
|
|
91
|
+
}
|
|
92
|
+
const identity = `${leafStat.size}:${leafStat.mtimeNs}:${leafStat.ctimeNs}:${leafStat.ino}:${digest}`;
|
|
93
|
+
if (cachedFiles && cachedFiles[file] === identity) {
|
|
94
|
+
nextCache[file] = identity;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const bytes = fs.readFileSync(cursor);
|
|
98
|
+
if (crypto.createHash('sha256').update(bytes).digest('hex') !== digest)
|
|
99
|
+
throw Error('HOOK_RUNTIME_INTEGRITY_INVALID');
|
|
100
|
+
const after = fs.lstatSync(cursor, { bigint: true });
|
|
101
|
+
if (!after.isSymbolicLink() && BigInt(bytes.length) === leafStat.size && after.size === leafStat.size &&
|
|
102
|
+
after.mtimeNs === leafStat.mtimeNs && after.ctimeNs === leafStat.ctimeNs && after.ino === leafStat.ino) {
|
|
103
|
+
nextCache[file] = identity;
|
|
104
|
+
cacheDirty = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (cacheDirty) {
|
|
108
|
+
let tempCache = null;
|
|
109
|
+
try {
|
|
110
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
111
|
+
if (!fs.lstatSync(cacheDir).isSymbolicLink()) {
|
|
112
|
+
tempCache = `${cacheFile}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
113
|
+
fs.writeFileSync(tempCache, JSON.stringify({ version: selected.version, digest: selected.digest, files: nextCache }), { mode: 0o600 });
|
|
114
|
+
fs.renameSync(tempCache, cacheFile);
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
try { if (tempCache && fs.existsSync(tempCache)) fs.unlinkSync(tempCache); } catch { /* observational cache */ }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
mark('load-runtime');
|
|
121
|
+
return require(path.join(release, 'src/core/hook-bridge.js')).runCLI(host, event,
|
|
122
|
+
Buffer.from(workspace, 'utf8').toString('base64'), executionOptions);
|
|
123
|
+
}
|
|
124
|
+
return { runCLI(host, event, projectPath, executionOptions = {}) {
|
|
125
|
+
// Host adapters pass the workspace as base64 so hook command shells never
|
|
126
|
+
// reinterpret path data. Keep absolute raw paths usable for internal calls.
|
|
127
|
+
const inputPath = String(projectPath);
|
|
128
|
+
if (inputPath === '-') return (async () => {
|
|
129
|
+
let text = typeof executionOptions.input === 'string' ? executionOptions.input : '';
|
|
130
|
+
if (typeof executionOptions.input !== 'string') for await (const chunk of process.stdin) {
|
|
131
|
+
text += chunk;
|
|
132
|
+
if (Buffer.byteLength(text) > 2 * 1024 * 1024) throw Error('Hook input exceeds 2 MiB');
|
|
133
|
+
}
|
|
134
|
+
let input = null;
|
|
135
|
+
try { input = JSON.parse(text || '{}'); } catch { /* the bridge reports malformed input */ }
|
|
136
|
+
const workspace = machineWorkspace(input);
|
|
137
|
+
if (!workspace) { process.stdout.write('{}\n'); return; }
|
|
138
|
+
return dispatch(host, event, workspace, { ...executionOptions, input: text }, true);
|
|
139
|
+
})();
|
|
140
|
+
const decodedProjectPath = Buffer.from(inputPath, 'base64').toString('utf8');
|
|
141
|
+
const workspaceInput = path.isAbsolute(inputPath) || !fs.existsSync(decodedProjectPath) ? inputPath : decodedProjectPath;
|
|
142
|
+
return dispatch(host, event, fs.realpathSync(workspaceInput), executionOptions, false);
|
|
143
|
+
} };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// The parent only uses built-ins. Synchronous hashing/locks in the signed
|
|
147
|
+
// worker cannot prevent the parent from producing a blocking decision.
|
|
148
|
+
function superviseHook(root, publicKey, dispatcherSource, args, timeoutMs, passive) {
|
|
149
|
+
const cp = require('node:child_process'), crypto = require('node:crypto'), fs = require('node:fs'), path = require('node:path');
|
|
150
|
+
const { performance } = require('node:perf_hooks');
|
|
151
|
+
const lease = { id: crypto.randomUUID(), notAfter: Date.now() + timeoutMs, maxDurationMs: timeoutMs,
|
|
152
|
+
...(Number.isSafeInteger(process.ppid) && process.ppid > 0 ? { hostPid: process.ppid } : {}) };
|
|
153
|
+
// The worker lives for one host event. APT_HOOK_PROCESS lets its runtime
|
|
154
|
+
// locks record a start hint instead of starting PowerShell on every tool call.
|
|
155
|
+
const program = `process.env.APT_HOOK_PROCESS = '1';
|
|
156
|
+
const dispatcher = (${dispatcherSource})(${JSON.stringify(root)}, ${JSON.stringify(publicKey)});
|
|
157
|
+
process.once('message', async message => {
|
|
158
|
+
try { await dispatcher.runCLI(...message.args, { hookLease: message.lease }); }
|
|
159
|
+
catch (error) { process.stderr.write('APT hook: ' + (error.message || String(error)) + '\\n'); process.exitCode = 2; }
|
|
160
|
+
finally { if (process.connected) process.disconnect(); }
|
|
161
|
+
});`;
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
let settled = false, answeredEarly = false, output = '', errors = '', inputBytes = 0;
|
|
164
|
+
const received = [], phases = [], started = Date.now();
|
|
165
|
+
const recordPhase = name => {
|
|
166
|
+
const clean = String(name || '').slice(0, 96);
|
|
167
|
+
if (!clean) return;
|
|
168
|
+
const now = Date.now() - started;
|
|
169
|
+
if (phases.length && phases[phases.length - 1].phase === clean) return;
|
|
170
|
+
phases.push({ phase: clean, enteredAt: now });
|
|
171
|
+
};
|
|
172
|
+
// Where a missed deadline spent its time: duration in the final step plus prior steps.
|
|
173
|
+
const buildTimeline = refused => {
|
|
174
|
+
const elapsed = Math.max(0, Date.now() - started);
|
|
175
|
+
const deadlineSec = timeoutMs / 1000;
|
|
176
|
+
const tail = refused ? '. Nothing was run; retry the call once.' : '';
|
|
177
|
+
if (!phases.length)
|
|
178
|
+
return { phase: 'startup', durationMs: elapsed, timeline: 'before-worker-start',
|
|
179
|
+
message: `HOOK_DEADLINE_EXCEEDED: before the hook worker started (deadline ${deadlineSec} s)${tail}` };
|
|
180
|
+
const last = phases[phases.length - 1];
|
|
181
|
+
const ranMs = Math.max(0, elapsed - last.enteredAt);
|
|
182
|
+
const prior = phases.slice(0, -1).map((item, idx) =>
|
|
183
|
+
`${item.phase} ${(Math.max(0, phases[idx + 1].enteredAt - item.enteredAt) / 1000).toFixed(2)} s`);
|
|
184
|
+
const beforePart = prior.length ? `; before it: ${prior.join(', ')}` : '';
|
|
185
|
+
const timeline = [...prior, `${last.phase} ${(ranMs / 1000).toFixed(2)} s (unfinished)`].join(', ');
|
|
186
|
+
return {
|
|
187
|
+
phase: last.phase,
|
|
188
|
+
durationMs: ranMs,
|
|
189
|
+
timeline,
|
|
190
|
+
message: `HOOK_DEADLINE_EXCEEDED: step "${last.phase}" ran ${(ranMs / 1000).toFixed(1)} s and did not finish ` +
|
|
191
|
+
`(entered ${(last.enteredAt / 1000).toFixed(1)} s after the hook started${beforePart}; deadline ${deadlineSec} s)${tail}`
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
const writeDeadlineDiagnostic = info => {
|
|
195
|
+
try {
|
|
196
|
+
const canonical = value => process.platform === 'win32' ? value.toLowerCase() : value;
|
|
197
|
+
const inputArg = String(args[2] || '');
|
|
198
|
+
let workspace = null;
|
|
199
|
+
if (inputArg === '-') {
|
|
200
|
+
const parsed = JSON.parse(Buffer.concat(received).toString('utf8') || '{}');
|
|
201
|
+
const candidate = parsed && (parsed.cwd || (parsed.workspace_roots || [])[0] || (parsed.workspacePaths || [])[0]);
|
|
202
|
+
if (typeof candidate === 'string' && path.isAbsolute(candidate)) workspace = fs.realpathSync(candidate);
|
|
203
|
+
} else if (inputArg) {
|
|
204
|
+
const decoded = Buffer.from(inputArg, 'base64').toString('utf8');
|
|
205
|
+
const raw = path.isAbsolute(inputArg) || !fs.existsSync(decoded) ? inputArg : decoded;
|
|
206
|
+
workspace = fs.realpathSync(raw);
|
|
207
|
+
}
|
|
208
|
+
if (!workspace) return;
|
|
209
|
+
const isProject = process.env.APT_WORKSPACE_MODE === 'project' ||
|
|
210
|
+
fs.existsSync(path.join(workspace, '.agents', 'manifest.json')) ||
|
|
211
|
+
fs.existsSync(path.join(workspace, '.agents', 'runtime')) ||
|
|
212
|
+
!fs.existsSync(path.join(root, 'global-install.json'));
|
|
213
|
+
const stateRoot = isProject
|
|
214
|
+
? workspace
|
|
215
|
+
: path.join(fs.realpathSync(root), 'workspaces', crypto.createHash('sha256').update(canonical(workspace)).digest('hex'));
|
|
216
|
+
fs.mkdirSync(stateRoot, { recursive: true });
|
|
217
|
+
let directory = stateRoot;
|
|
218
|
+
for (const part of ['.agents', 'runtime', 'diagnostics']) {
|
|
219
|
+
directory = path.join(directory, part);
|
|
220
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
221
|
+
const dirStat = fs.lstatSync(directory);
|
|
222
|
+
if (!dirStat.isDirectory() || dirStat.isSymbolicLink()) return;
|
|
223
|
+
}
|
|
224
|
+
const file = path.join(directory, `hook-${process.pid}.jsonl`);
|
|
225
|
+
let stat = null;
|
|
226
|
+
try { stat = fs.lstatSync(file); } catch { /* first entry */ }
|
|
227
|
+
if (stat && (!stat.isFile() || stat.isSymbolicLink() || stat.size > 4 * 1024 * 1024)) return;
|
|
228
|
+
const entry = {
|
|
229
|
+
at: new Date().toISOString(),
|
|
230
|
+
monotonicMs: performance.now(),
|
|
231
|
+
pid: process.pid,
|
|
232
|
+
event: 'hook-deadline',
|
|
233
|
+
phase: info.phase,
|
|
234
|
+
durationMs: info.durationMs,
|
|
235
|
+
reason: String(info.timeline || '').slice(0, 500),
|
|
236
|
+
attempts: phases.length
|
|
237
|
+
};
|
|
238
|
+
fs.appendFileSync(file, JSON.stringify(entry) + '\n', { mode: 0o600 });
|
|
239
|
+
} catch { /* Diagnostics never alter hook authority. */ }
|
|
240
|
+
};
|
|
241
|
+
// APT allows a passive host read in every session state. A fault of APT
|
|
242
|
+
// itself, a missed deadline above all, must not refuse one. The answer is
|
|
243
|
+
// the output that lets the call proceed, or null for every other call.
|
|
244
|
+
const allowance = () => {
|
|
245
|
+
try {
|
|
246
|
+
const table = passive && passive[args[0]];
|
|
247
|
+
const allow = table && table.events[args[1]];
|
|
248
|
+
if (typeof allow !== 'string') return null;
|
|
249
|
+
const input = JSON.parse(Buffer.concat(received).toString('utf8'));
|
|
250
|
+
const name = input.tool_name;
|
|
251
|
+
if (typeof name !== 'string' || input.toolCall !== undefined || input.userStopped === true) return null;
|
|
252
|
+
return table.exact.includes(name) || table.insensitive.includes(name.toLowerCase()) ? allow : null;
|
|
253
|
+
} catch { return null; }
|
|
254
|
+
};
|
|
255
|
+
const child = cp.spawn(process.execPath, ['-e', program], { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
|
|
256
|
+
const detach = () => {
|
|
257
|
+
clearTimeout(timer); process.stdin.unpipe(child.stdin); process.stdin.removeListener('data', countInput);
|
|
258
|
+
process.stdin.removeListener('end', answerPassiveEarly);
|
|
259
|
+
child.stdin.destroy();
|
|
260
|
+
};
|
|
261
|
+
const fail = (code, precomputedAllow) => {
|
|
262
|
+
if (settled) return;
|
|
263
|
+
settled = true; detach();
|
|
264
|
+
if (child.connected) child.disconnect();
|
|
265
|
+
child.kill(); child.stdout.destroy(); child.stderr.destroy(); child.unref();
|
|
266
|
+
const allow = precomputedAllow === undefined ? allowance() : precomputedAllow;
|
|
267
|
+
if (allow === null) return reject(new Error(code));
|
|
268
|
+
process.stderr.write('APT hook: ' + code + ' (not refused)\n');
|
|
269
|
+
if (!answeredEarly) process.stdout.write(allow + '\n');
|
|
270
|
+
process.exitCode = 0;
|
|
271
|
+
resolve();
|
|
272
|
+
};
|
|
273
|
+
const failDeadline = () => {
|
|
274
|
+
if (settled) return;
|
|
275
|
+
const allow = allowance();
|
|
276
|
+
const info = buildTimeline(allow === null);
|
|
277
|
+
writeDeadlineDiagnostic(info);
|
|
278
|
+
fail(info.message, allow);
|
|
279
|
+
};
|
|
280
|
+
const countInput = chunk => {
|
|
281
|
+
inputBytes += chunk.length;
|
|
282
|
+
if (inputBytes > 2 * 1024 * 1024) fail('HOOK_INPUT_LIMIT', null); else received.push(Buffer.from(chunk));
|
|
283
|
+
};
|
|
284
|
+
const answerPassiveEarly = () => {
|
|
285
|
+
if (settled || answeredEarly) return;
|
|
286
|
+
const allow = allowance();
|
|
287
|
+
if (allow !== null) {
|
|
288
|
+
answeredEarly = true;
|
|
289
|
+
process.stdout.write(allow + '\n');
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const timer = setTimeout(failDeadline, timeoutMs);
|
|
293
|
+
child.on('message', message => {
|
|
294
|
+
if (message && message.type === 'apt-hook-phase' && typeof message.phase === 'string')
|
|
295
|
+
recordPhase(message.phase);
|
|
296
|
+
});
|
|
297
|
+
child.on('error', () => fail('HOOK_WORKER_UNAVAILABLE'));
|
|
298
|
+
child.stdin.on('error', () => {});
|
|
299
|
+
child.stdout.on('data', chunk => { output += chunk.toString(); if (Buffer.byteLength(output) > 2 * 1024 * 1024) fail('HOOK_OUTPUT_LIMIT'); });
|
|
300
|
+
child.stderr.on('data', chunk => { errors += chunk.toString(); if (Buffer.byteLength(errors) > 64 * 1024) fail('HOOK_OUTPUT_LIMIT'); });
|
|
301
|
+
child.on('close', code => {
|
|
302
|
+
if (settled) return;
|
|
303
|
+
if (Date.now() >= lease.notAfter) return failDeadline();
|
|
304
|
+
settled = true; detach();
|
|
305
|
+
// A worker that failed before the bridge could decide refuses nothing
|
|
306
|
+
// that APT always allows.
|
|
307
|
+
const allow = code === 0 ? null : allowance();
|
|
308
|
+
if (code === 0) { if (output && !answeredEarly) process.stdout.write(output); }
|
|
309
|
+
if (errors) process.stderr.write(errors);
|
|
310
|
+
if (allow !== null && !answeredEarly) process.stdout.write(allow + '\n');
|
|
311
|
+
process.exitCode = code === 0 || allow !== null ? 0 : 2;
|
|
312
|
+
resolve();
|
|
313
|
+
});
|
|
314
|
+
process.stdin.on('data', countInput);
|
|
315
|
+
process.stdin.on('end', answerPassiveEarly);
|
|
316
|
+
process.stdin.pipe(child.stdin);
|
|
317
|
+
child.send({ args, lease }, error => { if (error) fail('HOOK_WORKER_UNAVAILABLE'); });
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Only a pre-tool decision may block its host. Any other event observes
|
|
322
|
+
// something that already happened, so a dispatcher or runtime fault there is a
|
|
323
|
+
// non-blocking error: it must not reject a prompt or force a continuation.
|
|
324
|
+
function blockingEvents() {
|
|
325
|
+
const { HOSTS } = require('../core/host-adapters');
|
|
326
|
+
return Object.fromEntries(Object.entries(HOSTS).map(([host, adapter]) =>
|
|
327
|
+
[host, Object.keys(adapter.events).filter(event => adapter.events[event] === 'before-tool')]));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// For each host: the events that guard a tool call, each with the output
|
|
331
|
+
// that lets the call proceed, and the tools APT allows in every session state.
|
|
332
|
+
function passiveReads() {
|
|
333
|
+
const { HOSTS, formatHookResult } = require('../core/host-adapters');
|
|
334
|
+
const { passiveHostTools } = require('../core/hook-bridge');
|
|
335
|
+
return Object.fromEntries(Object.entries(HOSTS).map(([host, adapter]) => [host, {
|
|
336
|
+
events: Object.fromEntries(Object.keys(adapter.events).filter(event => adapter.events[event] === 'before-tool')
|
|
337
|
+
.map(event => [event, JSON.stringify(formatHookResult(host, event, { allow: true }).output)])),
|
|
338
|
+
...passiveHostTools(host) }]));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function renderDispatcher(runtimeRoot, publicKey, options = {}) {
|
|
342
|
+
const timeoutMs = options.timeoutMs === undefined ? 10000 : options.timeoutMs;
|
|
343
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 10000) throw new Error('HOOK_DEADLINE_INVALID');
|
|
344
|
+
return `'use strict';
|
|
345
|
+
const dispatcher = (${hookDispatcher.toString()})(${JSON.stringify(runtimeRoot)}, ${JSON.stringify(publicKey.toString())});
|
|
346
|
+
const blockingEvents = ${JSON.stringify(blockingEvents())};
|
|
347
|
+
// An unknown host or event stays blocking.
|
|
348
|
+
const failureCode = args => !blockingEvents[args[0]] || blockingEvents[args[0]].includes(args[1]) ? 2 : 1;
|
|
349
|
+
function reportFailure(error, args) {
|
|
350
|
+
process.stderr.write('APT hook: ' + (error && error.message ? error.message : String(error)) + '\\n');
|
|
351
|
+
process.exitCode = failureCode(args);
|
|
352
|
+
}
|
|
353
|
+
module.exports = { runCLI(...args) {
|
|
354
|
+
try {
|
|
355
|
+
// Generated host commands use node --eval. Direct verifier consumers keep
|
|
356
|
+
// the synchronous interface; host execution always has an outer watchdog.
|
|
357
|
+
const hostCLI = process.argv.length === 1 && process.execArgv.some(arg => arg === '-e' || arg === '--eval');
|
|
358
|
+
const result = hostCLI
|
|
359
|
+
? (${superviseHook.toString()})(${JSON.stringify(runtimeRoot)}, ${JSON.stringify(publicKey.toString())}, ${JSON.stringify(hookDispatcher.toString())}, args, ${timeoutMs}, ${JSON.stringify(passiveReads())})
|
|
360
|
+
: dispatcher.runCLI(...args);
|
|
361
|
+
if (result && typeof result.then === 'function')
|
|
362
|
+
result.then(() => { if (hostCLI && process.exitCode) process.exitCode = failureCode(args); },
|
|
363
|
+
error => reportFailure(error, args));
|
|
364
|
+
return result;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
reportFailure(error, args);
|
|
367
|
+
}
|
|
368
|
+
} };
|
|
369
|
+
`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
module.exports = { hookDispatcher, renderDispatcher };
|