@agfpd/iapeer 0.2.76 → 0.2.78
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 +47 -48
- 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 +20 -13
- 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 +14 -8
- 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 -213
- 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,288 +0,0 @@
|
|
|
1
|
-
// provisionPeer — one-call peer creation: profile + registry (+ infra plist with a
|
|
2
|
-
// PINNED launcher). All registry/plist writes go under IAPEER_ROOT /
|
|
3
|
-
// IAPEER_LAUNCHAGENTS_DIR temp dirs so the suite never touches the live fleet.
|
|
4
|
-
|
|
5
|
-
import { afterEach, describe, expect, test } from 'bun:test'
|
|
6
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
|
|
7
|
-
import { tmpdir } from 'os'
|
|
8
|
-
import { join } from 'path'
|
|
9
|
-
import { provisionPeer } from './index.ts'
|
|
10
|
-
import { peerProfilePath } from '../storage/index.ts'
|
|
11
|
-
import { readPeerProfile } from '../identity/index.ts'
|
|
12
|
-
import { findPeer, readPeersIndex } from '../registry/index.ts'
|
|
13
|
-
import { isFoundationOwnedPlist, launchdPlistPath } from '../launch/index.ts'
|
|
14
|
-
|
|
15
|
-
const roots: string[] = []
|
|
16
|
-
function mkTmp(): string {
|
|
17
|
-
const d = mkdtempSync(join(tmpdir(), 'iapeer-prov-'))
|
|
18
|
-
roots.push(d)
|
|
19
|
-
return d
|
|
20
|
-
}
|
|
21
|
-
afterEach(() => {
|
|
22
|
-
while (roots.length) rmSync(roots.pop()!, { recursive: true, force: true })
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
function fakeBinDir(name = 'notifier-runtime'): { dir: string; bin: string } {
|
|
26
|
-
const dir = mkTmp()
|
|
27
|
-
const bin = join(dir, name)
|
|
28
|
-
writeFileSync(bin, '#!/bin/sh\nexit 0\n', { mode: 0o755 })
|
|
29
|
-
return { dir, bin }
|
|
30
|
-
}
|
|
31
|
-
function envFor(root: string, path?: string): NodeJS.ProcessEnv {
|
|
32
|
-
return {
|
|
33
|
-
IAPEER_ROOT: join(root, 'iapeer'),
|
|
34
|
-
IAPEER_LAUNCHAGENTS_DIR: join(root, 'LA'),
|
|
35
|
-
HOME: root,
|
|
36
|
-
...(path ? { PATH: path } : {}),
|
|
37
|
-
} as NodeJS.ProcessEnv
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
describe('provisionPeer', () => {
|
|
41
|
-
test('infra (notifier): writes profile + registry + plist with a pinned abs launcher', async () => {
|
|
42
|
-
const root = mkTmp()
|
|
43
|
-
const { dir: bindir, bin } = fakeBinDir()
|
|
44
|
-
const env = envFor(root, bindir)
|
|
45
|
-
const cwd = join(root, 'timer') // basename → personality 'timer'
|
|
46
|
-
|
|
47
|
-
const r = await provisionPeer({ cwd, runtime: 'notifier', env })
|
|
48
|
-
|
|
49
|
-
expect(r.personality).toBe('timer')
|
|
50
|
-
expect(r.intelligence).toBe('absent') // notifier zone default
|
|
51
|
-
expect(r.runtimeBin).toBe(bin)
|
|
52
|
-
// local profile
|
|
53
|
-
expect(existsSync(peerProfilePath(cwd))).toBe(true)
|
|
54
|
-
expect(readPeerProfile(cwd)!.runtime).toBe('notifier')
|
|
55
|
-
// registry entry (daemon reads this for tool-list / findPeer / wake)
|
|
56
|
-
expect(findPeer(readPeersIndex({ env }), 'timer')?.cwd).toBe(cwd)
|
|
57
|
-
// plist pins the launcher to an abs path (launchd minimal PATH safe)
|
|
58
|
-
const plist = launchdPlistPath('timer', env)
|
|
59
|
-
expect(existsSync(plist)).toBe(true)
|
|
60
|
-
expect(isFoundationOwnedPlist(plist)).toBe(true)
|
|
61
|
-
const xml = readFileSync(plist, 'utf8')
|
|
62
|
-
expect(xml).toContain('<key>NOTIFIER_RUNTIME_BIN</key>')
|
|
63
|
-
expect(xml).toContain(`<string>${bin}</string>`)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('infra with unresolvable launcher → REFUSED (no profile, no registry, no plist)', async () => {
|
|
67
|
-
const root = mkTmp()
|
|
68
|
-
const env = envFor(root, join(root, 'empty')) // PATH dir without notifier-runtime
|
|
69
|
-
const cwd = join(root, 'timer')
|
|
70
|
-
|
|
71
|
-
await expect(provisionPeer({ cwd, runtime: 'notifier', env })).rejects.toThrow(/not found on PATH|launcher/i)
|
|
72
|
-
|
|
73
|
-
expect(existsSync(peerProfilePath(cwd))).toBe(false)
|
|
74
|
-
expect(findPeer(readPeersIndex({ env }), 'timer')).toBeNull()
|
|
75
|
-
expect(existsSync(launchdPlistPath('timer', env))).toBe(false)
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
test('warm-on-demand (claude): profile + registry, NO plist (no launcher needed)', async () => {
|
|
79
|
-
const root = mkTmp()
|
|
80
|
-
const env = envFor(root)
|
|
81
|
-
const cwd = join(root, 'worker')
|
|
82
|
-
|
|
83
|
-
const r = await provisionPeer({ cwd, runtime: 'claude', env })
|
|
84
|
-
|
|
85
|
-
expect(r.plistPath).toBeUndefined()
|
|
86
|
-
expect(r.runtimeBin).toBeUndefined()
|
|
87
|
-
expect(existsSync(peerProfilePath(cwd))).toBe(true)
|
|
88
|
-
expect(findPeer(readPeersIndex({ env }), 'worker')?.runtime).toBe('claude')
|
|
89
|
-
expect(existsSync(launchdPlistPath('worker', env))).toBe(false)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test('explicit personality override (not basename) provisions under that name', async () => {
|
|
93
|
-
const root = mkTmp()
|
|
94
|
-
const { dir: bindir } = fakeBinDir()
|
|
95
|
-
const env = envFor(root, bindir)
|
|
96
|
-
const cwd = join(root, 'some-dir')
|
|
97
|
-
|
|
98
|
-
const r = await provisionPeer({ cwd, runtime: 'notifier', personality: 'timer', env })
|
|
99
|
-
|
|
100
|
-
expect(r.personality).toBe('timer')
|
|
101
|
-
expect(existsSync(launchdPlistPath('timer', env))).toBe(true)
|
|
102
|
-
expect(findPeer(readPeersIndex({ env }), 'timer')?.cwd).toBe(cwd)
|
|
103
|
-
})
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
107
|
-
// wake_policy:"ephemeral" provision warnings (M2 edge cases — warn, not refuse)
|
|
108
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
109
|
-
|
|
110
|
-
describe('provisionPeer ephemeral warnings', () => {
|
|
111
|
-
test('ephemeral + interfaces.telegram → WARN (ephemeral wins; human dialogue should not die-after-reply)', async () => {
|
|
112
|
-
const root = mkTmp()
|
|
113
|
-
const env = envFor(root)
|
|
114
|
-
const cwd = join(root, 'wtg')
|
|
115
|
-
// pre-existing profile (provision returns it unchanged) carrying the bad combo
|
|
116
|
-
mkdirSync(join(cwd, '.iapeer'), { recursive: true })
|
|
117
|
-
writeFileSync(
|
|
118
|
-
peerProfilePath(cwd),
|
|
119
|
-
JSON.stringify({
|
|
120
|
-
personality: 'wtg',
|
|
121
|
-
runtime: 'claude',
|
|
122
|
-
intelligence: 'natural',
|
|
123
|
-
interfaces: { telegram: { user_id: 1 } },
|
|
124
|
-
wake_policy: 'ephemeral',
|
|
125
|
-
}),
|
|
126
|
-
)
|
|
127
|
-
const warns: string[] = []
|
|
128
|
-
await provisionPeer({ cwd, runtime: 'claude', env, warn: m => warns.push(m) })
|
|
129
|
-
expect(warns.some(w => /ephemeral/.test(w) && /telegram/.test(w))).toBe(true)
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
test('plain ephemeral worker (claude, no telegram) → NO ephemeral warn', async () => {
|
|
133
|
-
const root = mkTmp()
|
|
134
|
-
const env = envFor(root)
|
|
135
|
-
const cwd = join(root, 'weph')
|
|
136
|
-
mkdirSync(join(cwd, '.iapeer'), { recursive: true })
|
|
137
|
-
writeFileSync(
|
|
138
|
-
peerProfilePath(cwd),
|
|
139
|
-
JSON.stringify({ personality: 'weph', runtime: 'claude', wake_policy: 'ephemeral' }),
|
|
140
|
-
)
|
|
141
|
-
const warns: string[] = []
|
|
142
|
-
await provisionPeer({ cwd, runtime: 'claude', env, warn: m => warns.push(m) })
|
|
143
|
-
expect(warns.filter(w => /ephemeral/.test(w))).toEqual([])
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
test('ephemeral + always-on infra runtime → WARN (H4: launchd owns it, the policy is inert)', async () => {
|
|
147
|
-
const root = mkTmp()
|
|
148
|
-
const { dir: bindir } = fakeBinDir()
|
|
149
|
-
const env = envFor(root, bindir)
|
|
150
|
-
const cwd = join(root, 'winf')
|
|
151
|
-
mkdirSync(join(cwd, '.iapeer'), { recursive: true })
|
|
152
|
-
writeFileSync(
|
|
153
|
-
peerProfilePath(cwd),
|
|
154
|
-
JSON.stringify({ personality: 'winf', runtime: 'notifier', wake_policy: 'ephemeral' }),
|
|
155
|
-
)
|
|
156
|
-
const warns: string[] = []
|
|
157
|
-
await provisionPeer({ cwd, runtime: 'notifier', env, warn: m => warns.push(m) })
|
|
158
|
-
expect(warns.some(w => /ephemeral/.test(w) && /inert|launchd/i.test(w))).toBe(true)
|
|
159
|
-
})
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
163
|
-
// Birth-time native-memory lever (slot contract §Native-память): an OCCUPIED
|
|
164
|
-
// slot gates the automatic lever at provision; empty slot → untouched.
|
|
165
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
-
|
|
167
|
-
describe('provisionPeer birth-time native-memory lever', () => {
|
|
168
|
-
test('slot OCCUPIED → claude+codex levers written + codex cwd pre-trusted', async () => {
|
|
169
|
-
const root = mkTmp()
|
|
170
|
-
const env = envFor(root)
|
|
171
|
-
// occupy the slot (as the provider's init would)
|
|
172
|
-
mkdirSync(join(root, 'iapeer'), { recursive: true })
|
|
173
|
-
writeFileSync(
|
|
174
|
-
join(root, 'iapeer', 'memory-provider.json'),
|
|
175
|
-
JSON.stringify({ provider: 'iapeer-memory', package: '@agfpd/iapeer-memory', version: '0.1.0', registeredAt: 'x' }),
|
|
176
|
-
)
|
|
177
|
-
const cwd = join(root, 'wnat')
|
|
178
|
-
mkdirSync(join(cwd, '.iapeer'), { recursive: true })
|
|
179
|
-
writeFileSync(
|
|
180
|
-
peerProfilePath(cwd),
|
|
181
|
-
JSON.stringify({ personality: 'wnat', runtime: 'claude', runtimes: ['claude', 'codex'] }),
|
|
182
|
-
)
|
|
183
|
-
await provisionPeer({ cwd, runtime: 'claude', env })
|
|
184
|
-
expect(JSON.parse(readFileSync(join(cwd, '.claude', 'settings.json'), 'utf8')).autoMemoryEnabled).toBe(false)
|
|
185
|
-
expect(readFileSync(join(cwd, '.codex', 'config.toml'), 'utf8')).toContain('memories = false')
|
|
186
|
-
// codex newborn pre-trusted in the GLOBAL codex config (HOME-scoped → temp root).
|
|
187
|
-
// Codex keys trust on the RESOLVED real path (macOS /var → /private/var), so the
|
|
188
|
-
// entry must carry realpath(cwd) — the literal-path miss was a live-caught bug.
|
|
189
|
-
const { realpathSync } = await import('fs')
|
|
190
|
-
expect(readFileSync(join(root, '.codex', 'config.toml'), 'utf8')).toContain(`[projects."${realpathSync(cwd)}"]`)
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
test('slot EMPTY → native memory untouched (legitimate without a provider)', async () => {
|
|
194
|
-
const root = mkTmp()
|
|
195
|
-
const env = envFor(root)
|
|
196
|
-
const cwd = join(root, 'wbare')
|
|
197
|
-
await provisionPeer({ cwd, runtime: 'claude', env })
|
|
198
|
-
expect(existsSync(join(cwd, '.claude', 'settings.json'))).toBe(false)
|
|
199
|
-
expect(existsSync(join(cwd, '.codex', 'config.toml'))).toBe(false)
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
test('LEGACY v1.1 plugin block in the declaration → ignored entirely (form removed 11.06, ADR-017); native lever still applies', async () => {
|
|
203
|
-
const root = mkTmp()
|
|
204
|
-
const env = envFor(root)
|
|
205
|
-
mkdirSync(join(root, 'iapeer'), { recursive: true })
|
|
206
|
-
writeFileSync(
|
|
207
|
-
join(root, 'iapeer', 'memory-provider.json'),
|
|
208
|
-
JSON.stringify({
|
|
209
|
-
provider: 'iapeer-memory',
|
|
210
|
-
package: '@agfpd/iapeer-memory',
|
|
211
|
-
version: '0.1.0',
|
|
212
|
-
registeredAt: 'x',
|
|
213
|
-
// a stale v1.1 declaration on a legacy host — the core no longer parses it
|
|
214
|
-
plugin: { name: 'iapeer-memory', marketplace: 'agfpd', marketplaceRef: 'agfpd/agfpd-marketplace' },
|
|
215
|
-
}),
|
|
216
|
-
)
|
|
217
|
-
const cwd = join(root, 'wplug')
|
|
218
|
-
const warns: string[] = []
|
|
219
|
-
const r = await provisionPeer({ cwd, runtime: 'claude', env, warn: m => warns.push(m) })
|
|
220
|
-
expect(r.personality).toBe('wplug')
|
|
221
|
-
expect(warns).toEqual([]) // no install attempt, no warn — the block is unknown noise
|
|
222
|
-
// the native lever still applied (same slot-gated block)
|
|
223
|
-
expect(JSON.parse(readFileSync(join(cwd, '.claude', 'settings.json'), 'utf8')).autoMemoryEnabled).toBe(false)
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
test('v1.2: slot with provision command → birth shells into it per runtime (occasion=birth); a legacy plugin block alongside is inert', async () => {
|
|
227
|
-
const root = mkTmp()
|
|
228
|
-
const env = envFor(root)
|
|
229
|
-
mkdirSync(join(root, 'iapeer'), { recursive: true })
|
|
230
|
-
const journal = join(root, 'journal.txt')
|
|
231
|
-
const script = join(root, 'fake-provider.sh')
|
|
232
|
-
const { chmodSync } = await import('fs')
|
|
233
|
-
writeFileSync(script, `#!/bin/sh\nprintf '%s\\n' "$@" >> '${journal}'\n`)
|
|
234
|
-
chmodSync(script, 0o755)
|
|
235
|
-
writeFileSync(
|
|
236
|
-
join(root, 'iapeer', 'memory-provider.json'),
|
|
237
|
-
JSON.stringify({
|
|
238
|
-
provider: 'iapeer-memory',
|
|
239
|
-
package: '@agfpd/iapeer-memory',
|
|
240
|
-
version: '0.2.0',
|
|
241
|
-
registeredAt: 'x',
|
|
242
|
-
// legacy plugin block alongside (stale declaration) — must be inert
|
|
243
|
-
plugin: { name: 'iapeer-memory', marketplace: 'agfpd', marketplaceRef: 'agfpd/agfpd-marketplace' },
|
|
244
|
-
provision: {
|
|
245
|
-
command: script,
|
|
246
|
-
args: ['provision-peer', '--cwd', '{cwd}', '--runtime', '{runtime}', '--personality', '{personality}', '--occasion', '{occasion}'],
|
|
247
|
-
},
|
|
248
|
-
}),
|
|
249
|
-
)
|
|
250
|
-
const cwd = join(root, 'wprov')
|
|
251
|
-
const warns: string[] = []
|
|
252
|
-
const r = await provisionPeer({ cwd, runtime: 'claude', env, warn: m => warns.push(m) })
|
|
253
|
-
expect(r.personality).toBe('wprov')
|
|
254
|
-
expect(warns).toEqual([]) // provision ok → no warn at all
|
|
255
|
-
const j = readFileSync(journal, 'utf8').split('\n')
|
|
256
|
-
expect(j[0]).toBe('provision-peer')
|
|
257
|
-
expect(j.slice(1, 9)).toEqual(['--cwd', cwd, '--runtime', 'claude', '--personality', 'wprov', '--occasion', 'birth'])
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
test('v1.2: provision command FAILS → LOUD warn (provider-side repair), provision of the peer still SUCCEEDS (best-effort)', async () => {
|
|
261
|
-
const root = mkTmp()
|
|
262
|
-
const env = envFor(root)
|
|
263
|
-
mkdirSync(join(root, 'iapeer'), { recursive: true })
|
|
264
|
-
const script = join(root, 'fake-provider.sh')
|
|
265
|
-
const { chmodSync } = await import('fs')
|
|
266
|
-
writeFileSync(script, `#!/bin/sh\necho 'vault offline' >&2\nexit 9\n`)
|
|
267
|
-
chmodSync(script, 0o755)
|
|
268
|
-
writeFileSync(
|
|
269
|
-
join(root, 'iapeer', 'memory-provider.json'),
|
|
270
|
-
JSON.stringify({
|
|
271
|
-
provider: 'iapeer-memory',
|
|
272
|
-
package: '@agfpd/iapeer-memory',
|
|
273
|
-
version: '0.2.0',
|
|
274
|
-
registeredAt: 'x',
|
|
275
|
-
provision: { command: script, args: ['{occasion}'] },
|
|
276
|
-
}),
|
|
277
|
-
)
|
|
278
|
-
const cwd = join(root, 'wfail')
|
|
279
|
-
const warns: string[] = []
|
|
280
|
-
const r = await provisionPeer({ cwd, runtime: 'claude', env, warn: m => warns.push(m) })
|
|
281
|
-
expect(r.personality).toBe('wfail') // birth completed despite the provider hiccup
|
|
282
|
-
const w = warns.find(x => x.includes('memory provision'))
|
|
283
|
-
expect(w).toBeDefined()
|
|
284
|
-
expect(w).toContain('FAILED')
|
|
285
|
-
expect(w).toContain('vault offline')
|
|
286
|
-
expect(w).toContain("re-run the provider's provision") // provider-side repair, no core verb named
|
|
287
|
-
})
|
|
288
|
-
})
|