@agfpd/iapeer 0.2.77 → 0.2.79
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/LICENSE +202 -0
- package/README.md +2 -2
- package/package.json +21 -2
- package/src/cli/index.ts +95 -78
- package/src/connect/index.ts +4 -4
- package/src/core/constants.ts +3 -3
- package/src/core/normalize.ts +2 -2
- package/src/create/index.ts +1 -1
- package/src/daemon/index.ts +5 -5
- package/src/daemon/main.ts +9 -9
- package/src/enable/index.ts +19 -18
- package/src/enable/provisionCommand.ts +10 -10
- package/src/identity/index.ts +2 -2
- package/src/identity/profileStandard.ts +8 -8
- package/src/init/index.ts +10 -10
- package/src/install/index.ts +2 -2
- package/src/install/signing.ts +3 -4
- package/src/launch/adapters/claude.ts +7 -7
- package/src/launch/adapters/codex.ts +5 -6
- package/src/launch/canary.ts +8 -8
- package/src/launch/cmdlog.ts +3 -3
- package/src/launch/composeSystemPrompt.ts +2 -1
- package/src/launch/index.ts +5 -6
- package/src/launch/invocation.ts +1 -1
- package/src/launch/launchd.ts +14 -15
- package/src/launch/launchdRun.ts +6 -6
- package/src/launch/nativeMemory.ts +9 -9
- package/src/launch/ptyHost.ts +1 -1
- package/src/launch/readyGateModel.ts +3 -3
- package/src/launch/types.ts +7 -7
- package/src/lifecycle/eventlog.ts +2 -2
- package/src/lifecycle/index.ts +21 -21
- package/src/lifecycle/queue.ts +1 -1
- package/src/onboard/index.ts +19 -13
- package/src/onboard/memory.ts +5 -5
- package/src/onboard/steps.ts +8 -9
- package/src/provision/index.ts +2 -3
- package/src/runtime/deploy.ts +2 -2
- package/src/runtime/index.ts +4 -4
- package/src/shadow/gate.ts +6 -6
- package/src/shadow/index.ts +2 -2
- package/src/status/index.ts +11 -11
- package/src/storage/index.ts +1 -1
- package/src/supervisor/daemon.ts +20 -13
- package/src/transport/index.ts +11 -11
- package/src/update/index.ts +4 -4
- package/src/cli/cli.test.ts +0 -603
- package/src/cli/listTui.test.ts +0 -71
- package/src/codec/codec.test.ts +0 -271
- package/src/connect/connect.test.ts +0 -163
- package/src/core/constants.test.ts +0 -32
- package/src/core/normalize.test.ts +0 -98
- package/src/create/create.test.ts +0 -143
- package/src/daemon/daemon-http.test.ts +0 -114
- package/src/daemon/daemon.test.ts +0 -267
- package/src/daemon/main.test.ts +0 -413
- package/src/enable/enable.test.ts +0 -95
- package/src/enable/provisionCommand.test.ts +0 -107
- package/src/identity/identity.test.ts +0 -279
- package/src/identity/profileStandard.test.ts +0 -204
- package/src/init/init.test.ts +0 -215
- package/src/init/runtime-resolve.test.ts +0 -49
- package/src/install/install.test.ts +0 -66
- package/src/install/signing.test.ts +0 -84
- package/src/launch/bootdeliver.test.ts +0 -108
- package/src/launch/bootstrap.test.ts +0 -56
- package/src/launch/canary.test.ts +0 -346
- package/src/launch/cmdlog.test.ts +0 -94
- package/src/launch/codexHooksTrust.test.ts +0 -248
- package/src/launch/composeSystemPrompt.layers.test.ts +0 -445
- package/src/launch/composeSystemPrompt.test.ts +0 -145
- package/src/launch/hostComposerOccupied.test.ts +0 -63
- package/src/launch/invocation.test.ts +0 -174
- package/src/launch/launch.test.ts +0 -359
- package/src/launch/launchd.test.ts +0 -499
- package/src/launch/nativeMemory.test.ts +0 -193
- package/src/launch/ptyHost.test.ts +0 -38
- package/src/launch/readyGateModel.test.ts +0 -72
- package/src/launch/sockdir.test.ts +0 -74
- package/src/lifecycle/eventlog.test.ts +0 -114
- package/src/lifecycle/lifecycle.test.ts +0 -1056
- package/src/lifecycle/queue.test.ts +0 -185
- package/src/onboard/memory.test.ts +0 -157
- package/src/onboard/onboard.test.ts +0 -39
- package/src/onboard/steps.test.ts +0 -201
- package/src/provision/provision.test.ts +0 -288
- package/src/registry/registry.test.ts +0 -432
- package/src/runtime/runtime.test.ts +0 -226
- package/src/runtime/update.test.ts +0 -192
- package/src/shadow/enumerate-skip.test.ts +0 -67
- package/src/shadow/gate.test.ts +0 -286
- package/src/shadow/install.test.ts +0 -153
- package/src/shadow/shadow.test.ts +0 -42
- package/src/status/status.test.ts +0 -208
- package/src/storage/peers-home.test.ts +0 -34
- package/src/storage/rotatelog.test.ts +0 -44
- package/src/storage/storage.test.ts +0 -65
- package/src/supervisor/attached-marker.test.ts +0 -84
- package/src/supervisor/boot.test.ts +0 -66
- package/src/supervisor/client.attach.test.ts +0 -107
- package/src/supervisor/daemon.test.ts +0 -213
- package/src/supervisor/deliver.test.ts +0 -58
- package/src/supervisor/panelog.test.ts +0 -95
- package/src/supervisor/protocol.test.ts +0 -222
- package/src/supervisor/selfargv.test.ts +0 -22
- package/src/supervisor/spinner-strip.test.ts +0 -52
- package/src/transport/compactDone.test.ts +0 -144
- package/src/transport/composerQueue.test.ts +0 -204
- package/src/transport/hostControl.test.ts +0 -87
- package/src/transport/hostDeliver.test.ts +0 -137
- package/src/transport/hostLive.test.ts +0 -50
- package/src/transport/routesend-wake.test.ts +0 -357
- package/src/update/update.test.ts +0 -208
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
// codex hooks trust pre-seed — the deterministic replacement for the "Hooks
|
|
2
|
-
// need review" modal. The HARD anchor here: the GOLDEN hashes — three values
|
|
3
|
-
// codex-cli 0.138.0 itself granted live ("Trust all and continue", smoke
|
|
4
|
-
// 11.06, isolated CODEX_HOME). If the re-implemented algorithm drifts from
|
|
5
|
-
// these, we would seed state codex ignores → hooks silently skipped fleet-wide.
|
|
6
|
-
|
|
7
|
-
import { afterEach, describe, expect, test } from 'bun:test'
|
|
8
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
|
|
9
|
-
import { tmpdir } from 'os'
|
|
10
|
-
import { join } from 'path'
|
|
11
|
-
import { realpathSync } from 'fs'
|
|
12
|
-
import {
|
|
13
|
-
checkCodexHooksTrust,
|
|
14
|
-
commandHookHash,
|
|
15
|
-
computeHooksTrustEntries,
|
|
16
|
-
preSeedCodexHooksTrust,
|
|
17
|
-
removeCodexHooksTrustUnder,
|
|
18
|
-
} from './codexHooksTrust.ts'
|
|
19
|
-
import { codexGlobalConfigPath } from './nativeMemory.ts'
|
|
20
|
-
|
|
21
|
-
const dirs: string[] = []
|
|
22
|
-
function mkTmp(): string {
|
|
23
|
-
const d = mkdtempSync(join(tmpdir(), 'iapeer-hookstrust-'))
|
|
24
|
-
dirs.push(d)
|
|
25
|
-
return d
|
|
26
|
-
}
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
while (dirs.length) rmSync(dirs.pop()!, { recursive: true, force: true })
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
/** Env pointing the codex global config into an isolated CODEX_HOME. */
|
|
32
|
-
function isolatedEnv(): { env: NodeJS.ProcessEnv; configPath: string } {
|
|
33
|
-
const home = mkTmp()
|
|
34
|
-
const env = { CODEX_HOME: home } as NodeJS.ProcessEnv
|
|
35
|
-
return { env, configPath: codexGlobalConfigPath(env) }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function writeHooksJson(dir: string, content: unknown): string {
|
|
39
|
-
const path = join(dir, 'hooks.json')
|
|
40
|
-
writeFileSync(path, JSON.stringify(content, null, 2))
|
|
41
|
-
return path
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ─── golden hashes (granted LIVE by codex-cli 0.138.0) ──────────────────────
|
|
45
|
-
|
|
46
|
-
describe('commandHookHash — golden values from live-granted trust', () => {
|
|
47
|
-
test('session_start, no matcher, default timeout (V1 smoke hook)', () => {
|
|
48
|
-
expect(
|
|
49
|
-
commandHookHash('session_start', undefined, {
|
|
50
|
-
command: '/tmp/iapeer-codex-hooks-smoke/bin/stamp.sh user-hooks-json-sessionstart',
|
|
51
|
-
}),
|
|
52
|
-
).toBe('sha256:c21b34240dd15d7184bf391f13e820399cb4b8e97c22347689316f8d02207391')
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('session_start, no matcher, default timeout (V2 smoke hook)', () => {
|
|
56
|
-
expect(
|
|
57
|
-
commandHookHash('session_start', undefined, {
|
|
58
|
-
command: '/tmp/iapeer-codex-hooks-smoke/bin/stamp.sh user-hooks-json-sessionstart-V2',
|
|
59
|
-
}),
|
|
60
|
-
).toBe('sha256:5727103dc6b7409ae2a811561e28de70b625a2bdd28f8715a73e6542836f34fe')
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
test('post_tool_use, no matcher, default timeout (project-local smoke hook)', () => {
|
|
64
|
-
expect(
|
|
65
|
-
commandHookHash('post_tool_use', undefined, {
|
|
66
|
-
command: '/tmp/iapeer-codex-hooks-smoke/bin/stamp.sh proj-hooks-json-posttooluse',
|
|
67
|
-
}),
|
|
68
|
-
).toBe('sha256:86f96ab754e05851c6f2f9b162bd99c70359099cdb6c93c114d0e6df42971030')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('normalization knobs shift the hash: timeout, matcher, statusMessage', () => {
|
|
72
|
-
const base = commandHookHash('post_tool_use', undefined, { command: 'x' })
|
|
73
|
-
expect(commandHookHash('post_tool_use', undefined, { command: 'x', timeout: 30 })).not.toBe(base)
|
|
74
|
-
expect(commandHookHash('post_tool_use', 'Write|Edit', { command: 'x' })).not.toBe(base)
|
|
75
|
-
expect(commandHookHash('post_tool_use', undefined, { command: 'x', statusMessage: 's' })).not.toBe(base)
|
|
76
|
-
// explicit default timeout hashes the SAME as omitted (both normalize to 600)
|
|
77
|
-
expect(commandHookHash('post_tool_use', undefined, { command: 'x', timeout: 600 })).toBe(base)
|
|
78
|
-
// timeout 0 clamps to 1 (upstream .max(1)), distinct from the default
|
|
79
|
-
expect(commandHookHash('post_tool_use', undefined, { command: 'x', timeout: 0 })).toBe(
|
|
80
|
-
commandHookHash('post_tool_use', undefined, { command: 'x', timeout: 1 }),
|
|
81
|
-
)
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
// ─── hooks.json → entries ────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
describe('computeHooksTrustEntries', () => {
|
|
88
|
-
test('keys carry source:event_snake:group:handler; matcher passes through', () => {
|
|
89
|
-
const entries = computeHooksTrustEntries(
|
|
90
|
-
JSON.stringify({
|
|
91
|
-
hooks: {
|
|
92
|
-
PostToolUse: [
|
|
93
|
-
{ matcher: 'Write', hooks: [{ type: 'command', command: 'a' }] },
|
|
94
|
-
{ hooks: [{ type: 'command', command: 'b' }, { type: 'command', command: 'c' }] },
|
|
95
|
-
],
|
|
96
|
-
},
|
|
97
|
-
}),
|
|
98
|
-
'/some/hooks.json',
|
|
99
|
-
)
|
|
100
|
-
expect(entries.map(e => e.key)).toEqual([
|
|
101
|
-
'/some/hooks.json:post_tool_use:0:0',
|
|
102
|
-
'/some/hooks.json:post_tool_use:1:0',
|
|
103
|
-
'/some/hooks.json:post_tool_use:1:1',
|
|
104
|
-
])
|
|
105
|
-
expect(entries[0]!.hash).toBe(commandHookHash('post_tool_use', 'Write', { command: 'a' }))
|
|
106
|
-
expect(entries[1]!.hash).toBe(commandHookHash('post_tool_use', undefined, { command: 'b' }))
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
test('skipped handlers (async / prompt / empty) consume their index, get no entry', () => {
|
|
110
|
-
const entries = computeHooksTrustEntries(
|
|
111
|
-
JSON.stringify({
|
|
112
|
-
hooks: {
|
|
113
|
-
SessionStart: [
|
|
114
|
-
{
|
|
115
|
-
hooks: [
|
|
116
|
-
{ type: 'command', command: 'first', async: true }, // h=0 skipped
|
|
117
|
-
{ type: 'prompt' }, // h=1 skipped
|
|
118
|
-
{ type: 'command', command: ' ' }, // h=2 skipped (empty)
|
|
119
|
-
{ type: 'command', command: 'real' }, // h=3 KEPT
|
|
120
|
-
],
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
},
|
|
124
|
-
}),
|
|
125
|
-
'/p/hooks.json',
|
|
126
|
-
)
|
|
127
|
-
expect(entries).toHaveLength(1)
|
|
128
|
-
expect(entries[0]!.key).toBe('/p/hooks.json:session_start:0:3')
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
test('UserPromptSubmit/Stop force the matcher OFF (upstream matcher_pattern_for_event)', () => {
|
|
132
|
-
const entries = computeHooksTrustEntries(
|
|
133
|
-
JSON.stringify({
|
|
134
|
-
hooks: {
|
|
135
|
-
Stop: [{ matcher: 'ignored', hooks: [{ type: 'command', command: 'x' }] }],
|
|
136
|
-
},
|
|
137
|
-
}),
|
|
138
|
-
'/p/hooks.json',
|
|
139
|
-
)
|
|
140
|
-
expect(entries[0]!.hash).toBe(commandHookHash('stop', undefined, { command: 'x' }))
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
test('unknown event / malformed shapes are loud errors', () => {
|
|
144
|
-
expect(() => computeHooksTrustEntries(JSON.stringify({ hooks: { NotAnEvent: [] } }), '/p')).toThrow(
|
|
145
|
-
/unknown hook event/,
|
|
146
|
-
)
|
|
147
|
-
expect(() => computeHooksTrustEntries('not json', '/p')).toThrow(/not valid JSON/)
|
|
148
|
-
expect(() => computeHooksTrustEntries(JSON.stringify({}), '/p')).toThrow(/"hooks" object/)
|
|
149
|
-
})
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
// ─── pre-seed (state surgery) ────────────────────────────────────────────────
|
|
153
|
-
|
|
154
|
-
describe('preSeedCodexHooksTrust', () => {
|
|
155
|
-
test('absent config → sections appended; second run → already', () => {
|
|
156
|
-
const { env, configPath } = isolatedEnv()
|
|
157
|
-
const proj = mkTmp()
|
|
158
|
-
const hooksPath = writeHooksJson(proj, {
|
|
159
|
-
hooks: { SessionStart: [{ hooks: [{ type: 'command', command: '/x/stamp.sh' }] }] },
|
|
160
|
-
})
|
|
161
|
-
const first = preSeedCodexHooksTrust(hooksPath, env)
|
|
162
|
-
expect(first.state).toBe('written')
|
|
163
|
-
expect(first.entries).toHaveLength(1)
|
|
164
|
-
const real = realpathSync(hooksPath)
|
|
165
|
-
const text = readFileSync(configPath, 'utf8')
|
|
166
|
-
expect(text).toContain(`[hooks.state."${real}:session_start:0:0"]`)
|
|
167
|
-
expect(text).toContain(`trusted_hash = "${first.entries[0]!.hash}"`)
|
|
168
|
-
expect(preSeedCodexHooksTrust(hooksPath, env).state).toBe('already')
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
test('NO-CLOBBER: foreign sections survive; changed hook updates IN PLACE', () => {
|
|
172
|
-
const { env, configPath } = isolatedEnv()
|
|
173
|
-
const proj = mkTmp()
|
|
174
|
-
const hooksPath = writeHooksJson(proj, {
|
|
175
|
-
hooks: { SessionStart: [{ hooks: [{ type: 'command', command: 'one' }] }] },
|
|
176
|
-
})
|
|
177
|
-
mkdirSync(join(env.CODEX_HOME!), { recursive: true })
|
|
178
|
-
const foreign = '[projects."/keep/me"]\ntrust_level = "trusted"\n\n[features]\nmemories = false\n'
|
|
179
|
-
writeFileSync(configPath, foreign)
|
|
180
|
-
preSeedCodexHooksTrust(hooksPath, env)
|
|
181
|
-
// change the command → hash changes → same section updated, not duplicated
|
|
182
|
-
writeHooksJson(proj, { hooks: { SessionStart: [{ hooks: [{ type: 'command', command: 'two' }] }] } })
|
|
183
|
-
const second = preSeedCodexHooksTrust(hooksPath, env)
|
|
184
|
-
expect(second.state).toBe('written')
|
|
185
|
-
const text = readFileSync(configPath, 'utf8')
|
|
186
|
-
expect(text).toContain('[projects."/keep/me"]')
|
|
187
|
-
expect(text).toContain('memories = false')
|
|
188
|
-
const real = realpathSync(hooksPath)
|
|
189
|
-
const headers = text.split('\n').filter(l => l.includes(`${real}:session_start:0:0`))
|
|
190
|
-
expect(headers).toHaveLength(1) // updated in place — no duplicate section
|
|
191
|
-
expect(text).toContain(`trusted_hash = "${second.entries[0]!.hash}"`)
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
test('missing hooks.json → failed (loud), config untouched', () => {
|
|
195
|
-
const { env, configPath } = isolatedEnv()
|
|
196
|
-
const o = preSeedCodexHooksTrust('/nonexistent/hooks.json', env)
|
|
197
|
-
expect(o.state).toBe('failed')
|
|
198
|
-
expect(existsSync(configPath)).toBe(false)
|
|
199
|
-
})
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
// ─── check (read-only drift detector) ────────────────────────────────────────
|
|
203
|
-
|
|
204
|
-
describe('checkCodexHooksTrust', () => {
|
|
205
|
-
test('missing → trusted after seed → drift after edit without re-seed', () => {
|
|
206
|
-
const { env } = isolatedEnv()
|
|
207
|
-
const proj = mkTmp()
|
|
208
|
-
const hooksPath = writeHooksJson(proj, {
|
|
209
|
-
hooks: { PostToolUse: [{ hooks: [{ type: 'command', command: 'stamp' }] }] },
|
|
210
|
-
})
|
|
211
|
-
expect(checkCodexHooksTrust(hooksPath, env).checks[0]!.status).toBe('missing')
|
|
212
|
-
preSeedCodexHooksTrust(hooksPath, env)
|
|
213
|
-
expect(checkCodexHooksTrust(hooksPath, env).checks[0]!.status).toBe('trusted')
|
|
214
|
-
writeHooksJson(proj, { hooks: { PostToolUse: [{ hooks: [{ type: 'command', command: 'edited' }] }] } })
|
|
215
|
-
const drifted = checkCodexHooksTrust(hooksPath, env).checks[0]!
|
|
216
|
-
expect(drifted.status).toBe('drift')
|
|
217
|
-
expect(drifted.found).toBeDefined()
|
|
218
|
-
})
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
// ─── remove (reap-side cleanup) ──────────────────────────────────────────────
|
|
222
|
-
|
|
223
|
-
describe('removeCodexHooksTrustUnder', () => {
|
|
224
|
-
test('drops sections under the cwd, keeps everything else', () => {
|
|
225
|
-
const { env, configPath } = isolatedEnv()
|
|
226
|
-
const peerCwd = mkTmp()
|
|
227
|
-
const hooksPath = writeHooksJson(peerCwd, {
|
|
228
|
-
hooks: { SessionStart: [{ hooks: [{ type: 'command', command: 'x' }] }] },
|
|
229
|
-
})
|
|
230
|
-
preSeedCodexHooksTrust(hooksPath, env)
|
|
231
|
-
// a foreign hooks.state entry from ANOTHER peer must survive
|
|
232
|
-
const foreign = '[hooks.state."/other/peer/hooks.json:session_start:0:0"]\ntrusted_hash = "sha256:keep"\n'
|
|
233
|
-
writeFileSync(configPath, readFileSync(configPath, 'utf8') + foreign)
|
|
234
|
-
const o = removeCodexHooksTrustUnder(peerCwd, env)
|
|
235
|
-
expect(o.state).toBe('written')
|
|
236
|
-
expect(o.removed).toHaveLength(1)
|
|
237
|
-
const text = readFileSync(configPath, 'utf8')
|
|
238
|
-
expect(text).not.toContain(realpathSync(hooksPath))
|
|
239
|
-
expect(text).toContain('/other/peer/hooks.json')
|
|
240
|
-
// idempotent
|
|
241
|
-
expect(removeCodexHooksTrustUnder(peerCwd, env).state).toBe('already')
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
test('no config / no matches → already', () => {
|
|
245
|
-
const { env } = isolatedEnv()
|
|
246
|
-
expect(removeCodexHooksTrustUnder('/nope', env).state).toBe('already')
|
|
247
|
-
})
|
|
248
|
-
})
|