@agfpd/iapeer 0.2.20 → 0.2.21
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/package.json +1 -1
- package/src/cli/cli.test.ts +31 -0
- package/src/cli/index.ts +25 -4
- package/src/install/signing.ts +12 -0
package/package.json
CHANGED
package/src/cli/cli.test.ts
CHANGED
|
@@ -153,6 +153,37 @@ describe('send validation', () => {
|
|
|
153
153
|
})
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
+
describe('send → ephemeral target: M3 FIFO parity with the daemon path (iapeer-memory ask)', () => {
|
|
157
|
+
test('a CLI send to a wake_policy:ephemeral peer ENQUEUES (queued ack), no live/miss bypass', async () => {
|
|
158
|
+
// an ephemeral worker cwd (profile declares wake_policy) registered in the index
|
|
159
|
+
const cwd = mkdtempSync(join(tmpdir(), 'iapeer-cli-eph-'))
|
|
160
|
+
mkdirSync(join(cwd, '.iapeer'), { recursive: true })
|
|
161
|
+
writeFileSync(
|
|
162
|
+
join(cwd, '.iapeer', 'peer-profile.json'),
|
|
163
|
+
JSON.stringify({ personality: 'ephw', runtime: 'claude', runtimes: ['claude'], intelligence: 'artificial', wake_policy: 'ephemeral' }),
|
|
164
|
+
)
|
|
165
|
+
const e = env()
|
|
166
|
+
// routeSend resolves the peers index from the PROCESS env (transport reads
|
|
167
|
+
// readPeersIndex() bare) — point the process-level root at the sandbox too.
|
|
168
|
+
const prevRoot = process.env.IAPEER_ROOT
|
|
169
|
+
process.env.IAPEER_ROOT = root
|
|
170
|
+
try {
|
|
171
|
+
await upsertPeer({ personality: 'ephw', runtime: 'claude', cwd, intelligence: 'artificial' }, { rootDir: root })
|
|
172
|
+
await register('sender')
|
|
173
|
+
const r = await sendMessage({ from: 'claude-sender', target: 'ephw', message: 'task', env: e })
|
|
174
|
+
expect(r.queued).toBe(true) // serialized via the disk FIFO, exactly like the daemon path
|
|
175
|
+
expect(r.queueDepth).toBe(1)
|
|
176
|
+
// the task is durably on disk for the daemon tick to drain
|
|
177
|
+
const qdir = join(loadLifecycleConfig(e).stateDir, 'claude-ephw.queue')
|
|
178
|
+
expect(existsSync(qdir)).toBe(true)
|
|
179
|
+
} finally {
|
|
180
|
+
if (prevRoot !== undefined) process.env.IAPEER_ROOT = prevRoot
|
|
181
|
+
else delete process.env.IAPEER_ROOT
|
|
182
|
+
rmSync(cwd, { recursive: true, force: true })
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
156
187
|
describe('--help/-h global intercept (CLI hygiene — usage printed, NOTHING executed)', () => {
|
|
157
188
|
let captured: string
|
|
158
189
|
let origWrite: typeof process.stdout.write
|
package/src/cli/index.ts
CHANGED
|
@@ -323,9 +323,21 @@ export interface SendOptions extends CliEnvOptions {
|
|
|
323
323
|
const cliWake: WakeFn = req =>
|
|
324
324
|
wakeOrSpawn({ personality: req.personality, runtime: req.runtime, topic: req.topic, task: req.task })
|
|
325
325
|
|
|
326
|
-
export async function sendMessage(
|
|
326
|
+
export async function sendMessage(
|
|
327
|
+
opts: SendOptions,
|
|
328
|
+
): Promise<{ ok: true; delivered_to: { personality: string; runtime: string }; queued?: boolean; queueDepth?: number }> {
|
|
327
329
|
const env = opts.env ?? process.env
|
|
328
330
|
const caller = resolveCallerIdentity(parseIdentity(opts.from), readPeersIndex({ env }))
|
|
331
|
+
// wake_policy:ephemeral M3 parity (iapeer-memory ask, 10.06): the CLI path used
|
|
332
|
+
// to route an ephemeral target through the normal live/miss path — a notifier
|
|
333
|
+
// burst landed as TURNS in one live worker session instead of serializing
|
|
334
|
+
// through the disk FIFO the daemon path uses. Same seam, ONE difference: the
|
|
335
|
+
// drain kick is a NOOP here — a CLI process exits right after the ack, so an
|
|
336
|
+
// unawaited in-process wake would die with it; the daemon's supervise-tick
|
|
337
|
+
// drain scan (≤60 s) picks the queue up — the EXISTING retry path for failed
|
|
338
|
+
// kicks, not a new mechanism.
|
|
339
|
+
const { makeEphemeralRouteDeps } = await import('../daemon/main.ts')
|
|
340
|
+
const cfg = loadLifecycleConfig(env)
|
|
329
341
|
const result = await routeSend(
|
|
330
342
|
caller,
|
|
331
343
|
{
|
|
@@ -335,10 +347,15 @@ export async function sendMessage(opts: SendOptions): Promise<{ ok: true; delive
|
|
|
335
347
|
topic: opts.topic,
|
|
336
348
|
attachments: opts.attachments,
|
|
337
349
|
},
|
|
338
|
-
{ wake: cliWake },
|
|
350
|
+
{ wake: cliWake, ephemeral: makeEphemeralRouteDeps(cfg, env, () => {}) },
|
|
339
351
|
)
|
|
340
352
|
if (!result.ok) throw new Error(result.error.message)
|
|
341
|
-
return {
|
|
353
|
+
return {
|
|
354
|
+
ok: true,
|
|
355
|
+
delivered_to: result.value.delivered_to,
|
|
356
|
+
queued: result.value.queued,
|
|
357
|
+
queueDepth: result.value.queueDepth,
|
|
358
|
+
}
|
|
342
359
|
}
|
|
343
360
|
|
|
344
361
|
function parseIdentity(identity: string): { personality: string; runtime: Runtime } {
|
|
@@ -714,7 +731,11 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
|
|
|
714
731
|
attachments: attachments.length ? attachments : undefined,
|
|
715
732
|
env,
|
|
716
733
|
})
|
|
717
|
-
out(
|
|
734
|
+
out(
|
|
735
|
+
r.queued
|
|
736
|
+
? `queued for ${r.delivered_to.personality} (${r.delivered_to.runtime}), depth ${r.queueDepth ?? '?'} — the daemon tick drains it\n`
|
|
737
|
+
: `delivered to ${r.delivered_to.personality} (${r.delivered_to.runtime})\n`,
|
|
738
|
+
)
|
|
718
739
|
return 0
|
|
719
740
|
}
|
|
720
741
|
case 'version':
|
package/src/install/signing.ts
CHANGED
|
@@ -26,6 +26,18 @@ import { tmpdir } from 'os'
|
|
|
26
26
|
import { join } from 'path'
|
|
27
27
|
import { spawnSync } from 'child_process'
|
|
28
28
|
|
|
29
|
+
/** CROSS-PRODUCT CONTRACT (agreed with iapeer-memory, 10.06): this CN is the
|
|
30
|
+
* SHARED signing identity of the whole agfpd stack. Each product signs with its
|
|
31
|
+
* OWN --identifier (foundation: com.agfpd.iapeer; memory: com.agfpd.iapeer-memory),
|
|
32
|
+
* so TCC subjects stay separate while the host carries ONE key (one keychain
|
|
33
|
+
* prompt ever). Creation is first-needs-creates with the IDENTICAL profile (EKU
|
|
34
|
+
* codeSigning, system LibreSSL p12, import -T /usr/bin/codesign) on both sides.
|
|
35
|
+
* Changing the CN or the creation profile is a COORDINATED change across repos.
|
|
36
|
+
* Known shared costs: re-creating the identity (deleted/expired — cert is 10 y)
|
|
37
|
+
* migrates the TCC grants of EVERY stack product at once; a concurrent
|
|
38
|
+
* first-creation by two installers could duplicate the CN (codesign would then
|
|
39
|
+
* report an ambiguous identity) — installs are operator-sequential, residual
|
|
40
|
+
* risk accepted. */
|
|
29
41
|
export const SIGNING_IDENTITY_CN = 'iapeer Local Codesign'
|
|
30
42
|
export const SIGNING_IDENTIFIER = 'com.agfpd.iapeer'
|
|
31
43
|
|