@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
package/src/launch/launchd.ts
CHANGED
|
@@ -185,7 +185,7 @@ function defaultEntrypointArgv(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
|
185
185
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
186
186
|
|
|
187
187
|
/** Resolve the current gui-domain uid for `launchctl bootstrap gui/<uid>`. NEVER
|
|
188
|
-
* falls back to 0 (that would aim the ROOT gui domain
|
|
188
|
+
* falls back to 0 (that would aim the ROOT gui domain); a non-numeric
|
|
189
189
|
* `id -u` result throws. */
|
|
190
190
|
function currentUid(): string {
|
|
191
191
|
const r = spawnSync('id', ['-u'], { encoding: 'utf8' })
|
|
@@ -216,12 +216,11 @@ function isLaunchdLoaded(label: string, uid: string): boolean {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
219
|
-
// UNDEAD-JOB-SAFE bootstrap core
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
// WHOLE fleet's telegram router down. This core makes every restart-shaped flow
|
|
219
|
+
// UNDEAD-JOB-SAFE bootstrap core: after `launchctl bootout` launchd dismantles
|
|
220
|
+
// the job ASYNCHRONOUSLY — an immediate `bootstrap` hits the still-listed
|
|
221
|
+
// "undead" job and fails with exit 5 "Input/output error" (the known
|
|
222
|
+
// persistent-peer race class). On the connect flow this left the WHOLE fleet's
|
|
223
|
+
// telegram router down. This core makes every restart-shaped flow
|
|
225
224
|
// (stop→start, connect router restart, update-runtime) survive the race:
|
|
226
225
|
// (1) WAIT-FOR-GONE: while the job is still listed, poll `print` up to
|
|
227
226
|
// goneTimeoutMs. Vanished → proceed to bootstrap. STILL listed at the
|
|
@@ -363,13 +362,13 @@ export function cycleDaemonCore(
|
|
|
363
362
|
* Restart the foundation daemon (`com.agfpd.iapeer`) onto the binary currently on
|
|
364
363
|
* disk — `launchctl bootout` + `bootstrap`, DELIBERATELY NOT `kickstart -k`.
|
|
365
364
|
*
|
|
366
|
-
* LWCR (launch constraints, the deploy bomb — PROVEN LIVE
|
|
365
|
+
* LWCR (launch constraints, the deploy bomb — PROVEN LIVE twice): after a
|
|
367
366
|
* binary replacement, launchd's managed Launch Constraint for the REGISTERED job
|
|
368
367
|
* can still pin the OLD binary's signature — a kickstart then respawns into an
|
|
369
368
|
* EX_CONFIG(78) crash-loop (`state = spawn scheduled`, exit 78 from launchd
|
|
370
|
-
* itself; the binary runs fine by hand). Reproduced on com.iapeer
|
|
371
|
-
* (telegram-runtime
|
|
372
|
-
* itself (
|
|
369
|
+
* itself; the binary runs fine by hand). Reproduced on a com.iapeer.* infra job
|
|
370
|
+
* (a telegram-runtime deploy, bridge down 6 min) AND on com.agfpd.iapeer
|
|
371
|
+
* itself (a daemon deploy — daemon down until the manual cure). The ONLY cure
|
|
373
372
|
* is a full re-registration: bootout drops the job (and its constraint),
|
|
374
373
|
* bootstrap registers it against the binary now on disk. kickstart is
|
|
375
374
|
* structurally insufficient for this class.
|
|
@@ -441,14 +440,14 @@ export function launchctlBootstrap(
|
|
|
441
440
|
// real launchd job regardless of where the plist file lives or what IAPEER_ROOT is.
|
|
442
441
|
// So the skip MUST consult BOTH the passed env AND the PROCESS env: a test harness
|
|
443
442
|
// that passes an explicit env (isolated IAPEER_ROOT) but omits the flag would
|
|
444
|
-
// otherwise bypass the guard and load a real job (this exact hole bit
|
|
443
|
+
// otherwise bypass the guard and load a real job (this exact hole bit once). The
|
|
445
444
|
// process-level flag (set by `bun test`) forces the skip even then — mirror of the
|
|
446
445
|
// registry's fail-closed sandbox lesson. A live e2e proof runs with NEITHER flag set.
|
|
447
446
|
if (env.IAPEER_TEST_SANDBOX === '1' || process.env.IAPEER_TEST_SANDBOX === '1') {
|
|
448
447
|
return { state: 'skipped-sandbox', label, detail: 'IAPEER_TEST_SANDBOX=1 — not loading a real launchd job' }
|
|
449
448
|
}
|
|
450
449
|
const uid = currentUid()
|
|
451
|
-
// UNDEAD-JOB-SAFE core
|
|
450
|
+
// UNDEAD-JOB-SAFE core: wait for a booted-out
|
|
452
451
|
// job to actually vanish, then bootstrap with backoff. A genuinely LIVE job
|
|
453
452
|
// reads 'already-loaded' (idempotent no-op, same semantics as before); only a
|
|
454
453
|
// job that stays failing through every attempt reads 'failed'.
|
|
@@ -525,7 +524,7 @@ export function installAlwaysOnPlist(opts: InstallAlwaysOnPlistOptions): string
|
|
|
525
524
|
// Resolve home the SAME way launchAgentsDir does (env.HOME first) so a test/
|
|
526
525
|
// sandbox overriding HOME keeps the PATH fallback and the plist dir in step.
|
|
527
526
|
const home = env.HOME?.trim() || homedir()
|
|
528
|
-
const defaultPath = `${home}/.bun/bin:${home}/.local/bin:/opt/homebrew/bin:/usr/bin:/bin`
|
|
527
|
+
const defaultPath = `${home}/.bun/bin:${home}/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin`
|
|
529
528
|
const environment: Record<string, string> = {
|
|
530
529
|
PEER_PERSONALITY: opts.personality,
|
|
531
530
|
PEER_RUNTIME: opts.runtime,
|
|
@@ -533,7 +532,7 @@ export function installAlwaysOnPlist(opts: InstallAlwaysOnPlistOptions): string
|
|
|
533
532
|
PATH: opts.path ?? env.PATH ?? defaultPath,
|
|
534
533
|
}
|
|
535
534
|
// Propagate the non-default path overrides into the plist env (mirror of the daemon
|
|
536
|
-
// plist
|
|
535
|
+
// plist): in PRODUCTION these are unset → nothing is baked, the always-on
|
|
537
536
|
// session uses the real ~/.iapeer + /tmp sockets (correct). In a SANDBOX they ARE set
|
|
538
537
|
// → baked, so a sandboxed infra peer's run-infra resolves the SAME isolated root +
|
|
539
538
|
// socket dir the provision used (its tmux endpoint lands in the sandbox, not /tmp).
|
package/src/launch/launchdRun.ts
CHANGED
|
@@ -46,11 +46,11 @@ function sessionAlive(sock: string, identity: string): boolean {
|
|
|
46
46
|
* last one) — the signal-exit counterpart of lifecycle.killSession, local to avoid
|
|
47
47
|
* a launch ⇆ lifecycle import. Canary-signaled first: this is a DELIBERATE stop.
|
|
48
48
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
49
|
+
* Fixes the defect where bootout did not kill the poller: `launchctl bootout`
|
|
50
|
+
* TERMs THIS watcher process, but the detached tmux server — and the runtime
|
|
51
|
+
* poller inside it — survived holding STALE in-memory state (e.g. a notifier
|
|
52
|
+
* trigger's old target after a same-id replace), so a plain bootout+bootstrap was
|
|
53
|
+
* NOT a real restart. With the teardown, the session
|
|
54
54
|
* dies with its watcher: bootout = full stop, bootstrap = fresh bring-up that
|
|
55
55
|
* re-reads durable state. `iapeer stop` (bootout + killSession) is unchanged —
|
|
56
56
|
* both paths now converge on the same end state.
|
|
@@ -196,7 +196,7 @@ export async function runAlwaysOn(personality: string, runtime: string, cwd: str
|
|
|
196
196
|
}
|
|
197
197
|
// Signal-initiated exit (bootout / shutdown / kickstart -k) tears the session
|
|
198
198
|
// down WITH this watcher — without it the detached tmux poller outlived bootout
|
|
199
|
-
// holding stale in-memory state (
|
|
199
|
+
// holding stale in-memory state (see teardownAlwaysOnSession). A natural session
|
|
200
200
|
// death (stop=false) skips this: there is nothing to tear down, exit 0 →
|
|
201
201
|
// KeepAlive respawns a fresh bring-up.
|
|
202
202
|
// Signal-initiated stop (bootout / shutdown / kickstart -k) tears the session down WITH this watcher.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Native runtime-memory levers (контракт «Слот памяти» §Native-память рантаймов
|
|
2
|
-
// при занятом
|
|
2
|
+
// при занятом слоте). The CORE canonizes the lever FORMS — runtime
|
|
3
3
|
// knowledge lives in the runtime layer, like deliveryMarkers/buildArgv:
|
|
4
4
|
//
|
|
5
5
|
// claude: merge `"autoMemoryEnabled": false` into `<cwd>/.claude/settings.json`
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Both files CARRY FOREIGN BLOCKS (plugin enables, statusline wrappers, …) —
|
|
10
10
|
// every write here is a NO-CLOBBER merge (parse/patch + atomic temp+rename),
|
|
11
|
-
// never a rewrite. Forms verified live on the fleet (12/12 + behavioral smoke
|
|
12
|
-
//
|
|
11
|
+
// never a rewrite. Forms verified live on the fleet (12/12 + behavioral smoke;
|
|
12
|
+
// codex reads project-local config when the cwd is TRUSTED — see
|
|
13
13
|
// preTrustCodexCwd).
|
|
14
14
|
//
|
|
15
15
|
// `off` writes the explicit disable; `on` REMOVES the key (restores the
|
|
@@ -130,8 +130,8 @@ function applyCodexLever(cwd: string, state: NativeMemoryState): LeverOutcome {
|
|
|
130
130
|
* `[projects."<path>"] trust_level = "trusted"` blocks. Honors $CODEX_HOME
|
|
131
131
|
* first, SAME as init's codexConfigPath — the two writers of this shared prod
|
|
132
132
|
* file must respect one override set, or a sandbox isolating one path still
|
|
133
|
-
* leaks through the other (live
|
|
134
|
-
*
|
|
133
|
+
* leaks through the other (live defect: an IAPEER_ROOT sandbox pre-trusted a
|
|
134
|
+
* throwaway cwd into the PROD config). */
|
|
135
135
|
export function codexGlobalConfigPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
136
136
|
const codexHome = env.CODEX_HOME?.trim()
|
|
137
137
|
if (codexHome) return join(codexHome, 'config.toml')
|
|
@@ -152,8 +152,8 @@ export function codexGlobalConfigPath(env: NodeJS.ProcessEnv = process.env): str
|
|
|
152
152
|
export function preTrustCodexCwd(cwd: string, env: NodeJS.ProcessEnv = process.env): LeverOutcome {
|
|
153
153
|
const path = codexGlobalConfigPath(env)
|
|
154
154
|
try {
|
|
155
|
-
// Codex keys trust on the RESOLVED real path (live fact
|
|
156
|
-
// /
|
|
155
|
+
// Codex keys trust on the RESOLVED real path (live fact: the /tmp →
|
|
156
|
+
// /private/tmp symlink made a literal-path entry MISS and left the lever
|
|
157
157
|
// inert — trust entries written by codex itself are all /private/tmp/...).
|
|
158
158
|
let real = cwd
|
|
159
159
|
try {
|
|
@@ -175,8 +175,8 @@ export function preTrustCodexCwd(cwd: string, env: NodeJS.ProcessEnv = process.e
|
|
|
175
175
|
/**
|
|
176
176
|
* Remove a peer's pre-trust entry from the codex GLOBAL config — the reap-side
|
|
177
177
|
* counterpart of preTrustCodexCwd (backlog «remove не чистит pre-trust», made
|
|
178
|
-
* live by
|
|
179
|
-
*
|
|
178
|
+
* live by a defect: every sandboxed/throwaway codex birth left a stale
|
|
179
|
+
* `[projects."<cwd>"]` block in the PROD config forever). Removes the
|
|
180
180
|
* section for BOTH the resolved and the literal path form (the writer stores
|
|
181
181
|
* realpath, but a since-deleted cwd cannot be resolved anymore — match either),
|
|
182
182
|
* strictly section-scoped: from the `[projects."<p>"]` header up to the next
|
package/src/launch/ptyHost.ts
CHANGED
|
@@ -27,7 +27,7 @@ export const HOST_COLS = 220
|
|
|
27
27
|
export const HOST_ROWS = 50
|
|
28
28
|
|
|
29
29
|
/** Per-peer pty-hosting decision. pty-supervisor hosting is the DEFAULT — a fresh peer is hosted under
|
|
30
|
-
* the supervisor (not `tmux new-session`) WITHOUT any marker (
|
|
30
|
+
* the supervisor (not `tmux new-session`) WITHOUT any marker (pty out of the box). A peer opts OUT
|
|
31
31
|
* to the legacy tmux path with a `<logDir>/<identity>.no-pty-host` marker (the dev/debug escape, valid
|
|
32
32
|
* only while tmux still exists). Read in TWO places, both of which want "is this peer pty-hosted": the
|
|
33
33
|
* spawn-gate (launch — host vs tmux) and the shadow observer (skip pty-hosted peers, they have no tmux
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// emulation of the daemon's pane-log — the SAME raw child bytes tmux renders), keeping the SAME
|
|
7
7
|
// production predicate (adapter.isInputReady) and an UNCONDITIONAL capture-pane fallback.
|
|
8
8
|
//
|
|
9
|
-
// Why this is low-radius
|
|
9
|
+
// Why this is low-radius: it is a viewport-SOURCE swap, NOT a predicate change.
|
|
10
10
|
// The model verdict was validated 0-divergence vs capture-pane on the readiness predicate by the
|
|
11
11
|
// burn-in gate (incl. the codex resume-with-history splash-off-screen center cell) before any peer
|
|
12
12
|
// is flipped. It touches ONLY the isInputReady INPUT — dialog detection upstream and the delivery +
|
|
@@ -127,10 +127,10 @@ export async function paneLogViewport(log: string, cols: number, rows: number):
|
|
|
127
127
|
* tmux predicate's capture-fail-is-busy stance — prefer a false-busy hold over a false-free paste into
|
|
128
128
|
* a human composer.
|
|
129
129
|
*
|
|
130
|
-
* NB (sequence
|
|
130
|
+
* NB (sequence): this is the DETECTION primitive. The end-to-end gate (attached-supervisor-
|
|
131
131
|
* client + enqueue + host-aware drain) lands with the attach client in Ф0b-3 — until a human can
|
|
132
132
|
* attach to a hosted session there is no human composer to protect. The caller composes the pane-log
|
|
133
|
-
* path from logDir+identity
|
|
133
|
+
* path from logDir+identity, mirroring `waitHostReady`.
|
|
134
134
|
*/
|
|
135
135
|
export async function paneLogComposerOccupied(log: string, cols: number, rows: number, runtime: string): Promise<boolean> {
|
|
136
136
|
const term = await loadPaneLogModel(log, cols, rows)
|
package/src/launch/types.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type { PublicPeerSummary } from '../registry/index.ts'
|
|
|
30
30
|
// in organically here — no special-casing.
|
|
31
31
|
// 5. Doctrine fragments: every `*.md` in the `.iapeer/fragments/` subdir, global
|
|
32
32
|
// (~/.iapeer/fragments/) + local (<cwd>/.iapeer/fragments/) merged per stem.
|
|
33
|
-
// PRIMITIVE-owned, machine-regenerated (
|
|
33
|
+
// PRIMITIVE-owned, machine-regenerated (e.g. a memory provider's guide + note index, …)
|
|
34
34
|
// — a dedicated namespace kept OUT of the hand-authored IAPEER.md doctrine so
|
|
35
35
|
// the auto-writer and the human writer never share a file. Sits LAST: the most
|
|
36
36
|
// volatile layer, so edits to it never disturb the stable prefix above.
|
|
@@ -160,13 +160,13 @@ export interface LaunchSpec {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
163
|
-
// Delivery markers — the tui submit surface (owned by the adapter
|
|
163
|
+
// Delivery markers — the tui submit surface (owned by the adapter)
|
|
164
164
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
165
165
|
|
|
166
166
|
/**
|
|
167
167
|
* Markers the transport submit path (submitIntoTui) needs to detect that a
|
|
168
168
|
* bracketed paste landed in the input row before pressing Enter. Contract refactor
|
|
169
|
-
*
|
|
169
|
+
* (docs/Рантайм-адаптеры): the ADAPTER owns delivery markers — they moved out
|
|
170
170
|
* of the transport's hardcoded `PROMPT_GLYPHS = ['❯','›']` union so the generic
|
|
171
171
|
* submit logic carries NO runtime strings and a new runtime ships its own glyph
|
|
172
172
|
* with its adapter, not by editing transport.
|
|
@@ -227,8 +227,8 @@ export interface RuntimeAdapter {
|
|
|
227
227
|
|
|
228
228
|
/**
|
|
229
229
|
* Delivery markers for the transport submit path (submitIntoTui) — the input-
|
|
230
|
-
* prompt glyph(s) and optional "paste landed" patterns. The adapter OWNS them
|
|
231
|
-
*
|
|
230
|
+
* prompt glyph(s) and optional "paste landed" patterns. The adapter OWNS them:
|
|
231
|
+
* transport reads them from here instead of a hardcoded glyph
|
|
232
232
|
* union. A router declares `{ promptGlyphs: [] }` (no submit surface).
|
|
233
233
|
*/
|
|
234
234
|
deliveryMarkers: DeliveryMarkers
|
|
@@ -236,8 +236,8 @@ export interface RuntimeAdapter {
|
|
|
236
236
|
/**
|
|
237
237
|
* If set, launch REFUSES unless the peer's intelligence equals this value
|
|
238
238
|
* (fail-loud). telegram → 'natural' — it is a human channel; launching an
|
|
239
|
-
* artificial/absent peer on it is a category error (
|
|
240
|
-
* places). Most adapters omit it (no nature gate). Source of intelligence →
|
|
239
|
+
* artificial/absent peer on it is a category error (the persistent-peer path held
|
|
240
|
+
* a FATAL guard in two places). Most adapters omit it (no nature gate). Source of intelligence →
|
|
241
241
|
* docs/Идентичность; enforced by the launch primitive against LaunchSpec.intelligence.
|
|
242
242
|
*/
|
|
243
243
|
requiresIntelligence?: Intelligence
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Lifecycle event log — the daemon's DURABLE, ROTATED trace of every lifecycle
|
|
2
|
-
// DECISION it makes. This
|
|
3
|
-
//
|
|
2
|
+
// DECISION it makes. This closes an observability gap: a peer could wake fresh
|
|
3
|
+
// with NO record of when/how its prior session ended,
|
|
4
4
|
// nor of the daemon's fresh-vs-resume reasoning, because superviseTick's outcomes
|
|
5
5
|
// were dropped and the daemon never wrote a decision line anywhere.
|
|
6
6
|
//
|
package/src/lifecycle/index.ts
CHANGED
|
@@ -76,16 +76,16 @@ export interface LifecycleConfig {
|
|
|
76
76
|
/** wake_policy:ephemeral M2 — the quiet window (seconds of activity-proxy silence)
|
|
77
77
|
* after which an ARMED ephemeral session (it has sent its outbound reply) is
|
|
78
78
|
* reaped. Long enough for the post-reply housekeeping writes (operative notes)
|
|
79
|
-
* to keep resetting it; far below idleSecs. ASSUMPTION (
|
|
79
|
+
* to keep resetting it; far below idleSecs. ASSUMPTION (by design): the
|
|
80
80
|
* transcript mtime is a LIVENESS proxy — "no longer writing" — not a semantic
|
|
81
81
|
* "done" signal. */
|
|
82
82
|
ephemeralQuietSecs: number
|
|
83
83
|
/** wake_policy:ephemeral — the UNARMED idle bound (seconds): an ephemeral session
|
|
84
84
|
* that never armed (finished silently / lost its arm to a daemon-restart window)
|
|
85
|
-
* is reaped after this much activity-proxy silence. Live case
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
85
|
+
* is reaped after this much activity-proxy silence. Live case: a worker that
|
|
86
|
+
* ended silently without its final outbound stalled its M3 FIFO for the FULL
|
|
87
|
+
* generic idleSecs (1 h) — with serial drain that blocks the whole conveyor per
|
|
88
|
+
* silent worker. Bound chosen ≫ the legitimate silent-tool case
|
|
89
89
|
* (sleep-180) and ≪ idleSecs. DOCUMENTED RISK: an ephemeral worker whose tool
|
|
90
90
|
* stays silent longer than this MID-TASK is reaped and its consumed queue item
|
|
91
91
|
* is lost — ephemeral workers must emit activity (or their reply) within it. */
|
|
@@ -215,7 +215,7 @@ export function clearStopped(cfg: LifecycleConfig, identity: string): void {
|
|
|
215
215
|
//
|
|
216
216
|
// .idle-reaped : the CLEAN-PARK marker — written when the daemon idle-reaps the
|
|
217
217
|
// session AND by the deliberate `stop` verb (both are daemon/operator-initiated
|
|
218
|
-
// parks, not faults;
|
|
218
|
+
// parks, not faults; stop→start must survive ≥ idle-reap).
|
|
219
219
|
// Presence on the next wake = session was parked cleanly = RESUME-eligible.
|
|
220
220
|
// ABSENT on a dead session = it died on its own (crash / self-close) = FRESH.
|
|
221
221
|
// (resolver branch 3.)
|
|
@@ -226,9 +226,9 @@ export function clearStopped(cfg: LifecycleConfig, identity: string): void {
|
|
|
226
226
|
// .topic : the topic the current/last session last WORKED ON (executor
|
|
227
227
|
// fresh-vs-resume discriminator). Written at wake (the boot topic) AND on
|
|
228
228
|
// every live delivery carrying a different non-empty topic (the daemon/CLI
|
|
229
|
-
// compositions wire transport's noteLiveTopic seam to writeTopic — defect
|
|
230
|
-
//
|
|
231
|
-
//
|
|
229
|
+
// compositions wire transport's noteLiveTopic seam to writeTopic — defect:
|
|
230
|
+
// a wake-only marker goes stale over a long session and false-freshes the
|
|
231
|
+
// stop→start resume the clean park promised).
|
|
232
232
|
// .ephemeral-armed : wake_policy:ephemeral M2 — set by the DAEMON when it routes
|
|
233
233
|
// an OUTBOUND send from an ephemeral worker (its single final reply, ADR-006:
|
|
234
234
|
// workers send no intermediate messages, so outbound ⇒ the task is answered).
|
|
@@ -404,8 +404,8 @@ export function writeTopic(cfg: LifecycleConfig, identity: string, topic: string
|
|
|
404
404
|
* and the M3 `.queue/` dir — everything matching `<identity>.*` (the dot
|
|
405
405
|
* delimiter keeps `claude-bob` from ever matching `claude-bob2.*`).
|
|
406
406
|
*
|
|
407
|
-
* Consumer: `iapeer remove` (live defect
|
|
408
|
-
*
|
|
407
|
+
* Consumer: `iapeer remove` (live defect): a removed peer's stale
|
|
408
|
+
* `.stopped`/`.idle-reaped` survived in state/lifecycle, and a NEWBORN peer
|
|
409
409
|
* REUSING the personality inherited the dead namesake's parking — the daemon
|
|
410
410
|
* refused to wake it (`mode=refused cause=stopped`). Identity-keyed state must
|
|
411
411
|
* die with the registry record. Deliberately NOT called at birth: provision runs
|
|
@@ -538,7 +538,7 @@ export function resolveWakeMode(
|
|
|
538
538
|
return { resume: false, cause: 'ephemeral-policy' }
|
|
539
539
|
}
|
|
540
540
|
// 3a. NOT parked-clean → FRESH either way, but tell the two cases apart in the
|
|
541
|
-
// cause
|
|
541
|
+
// cause: a peer with NO transcript at all never ran here —
|
|
542
542
|
// that is its FIRST-ever wake, not a crash. (A peer whose transcripts were rotated
|
|
543
543
|
// away also reads first-wake — equally honest: there is nothing it could resume.)
|
|
544
544
|
if (!hasIdleReaped(cfg, identity)) {
|
|
@@ -659,9 +659,9 @@ function sessionAlive(sock: string, identity: string): boolean {
|
|
|
659
659
|
return hostSessionAlive(identity) || tmux(sock, 'has-session', '-t', identity).ok
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
-
/** Death-class tag for a gone session (live case:
|
|
663
|
-
*
|
|
664
|
-
*
|
|
662
|
+
/** Death-class tag for a gone session (live case: the WHOLE tmux server died by
|
|
663
|
+
* SIGKILL-class and exits.log stayed empty, because the pane-died hook needs a
|
|
664
|
+
* living tmux event loop). Two distinguishable classes:
|
|
665
665
|
* - `session-gone` — the server on the socket still ANSWERS but the session is not
|
|
666
666
|
* there: a pane died inside a living server → the pane-died hook had its chance,
|
|
667
667
|
* exits.log should carry the cause.
|
|
@@ -1020,7 +1020,7 @@ export async function wakeOrSpawn(args: WakeArgs, deps: WakeDeps = {}): Promise<
|
|
|
1020
1020
|
if (result.status === 'FAILED') {
|
|
1021
1021
|
// enroll-on-FAILED (0.2.57): launch can return FAILED on a ready-gate false-negative
|
|
1022
1022
|
// (model-did-not-process-task) while the tmux session + its process actually SURVIVE —
|
|
1023
|
-
// observed live
|
|
1023
|
+
// observed live. Since 0.2.55 removed the self-TTL, such a remnant
|
|
1024
1024
|
// would be an UNBOUNDED orphan: no .session → invisible to superviseTick's idle-reap,
|
|
1025
1025
|
// and no wall-clock backstop. If the session is in fact ALIVE, enroll it so the
|
|
1026
1026
|
// activity-aware idle-reap stays the UNIVERSAL bound (a genuinely wedged session is
|
|
@@ -1141,7 +1141,7 @@ export function superviseTick(cfg: LifecycleConfig, deps: SuperviseDeps = {}): S
|
|
|
1141
1141
|
}
|
|
1142
1142
|
const sock = buildSocketPath(s.runtime, s.personality, cfg.sockDir)
|
|
1143
1143
|
if (!sessionAlive(sock, s.identity)) {
|
|
1144
|
-
// A DELIBERATE stop is not a death
|
|
1144
|
+
// A DELIBERATE stop is not a death: the stop verb parks clean
|
|
1145
1145
|
// (.stopped + .idle-reaped) and kills the session itself. Catch-up branch for
|
|
1146
1146
|
// a stop that raced this tick (or a pre-fix stop): drop the state quietly —
|
|
1147
1147
|
// no crash-loop entry, no death class — and ensure the clean-park marker so
|
|
@@ -1222,14 +1222,14 @@ export function superviseTick(cfg: LifecycleConfig, deps: SuperviseDeps = {}): S
|
|
|
1222
1222
|
trace({ identity: s.identity, action: 'reaped-ephemeral', age: `${ageSecs}s`, outcome: 'ephemeral-done' })
|
|
1223
1223
|
continue
|
|
1224
1224
|
}
|
|
1225
|
-
// UNARMED ephemeral idle bound (live case
|
|
1225
|
+
// UNARMED ephemeral idle bound (live case): a worker that ended
|
|
1226
1226
|
// SILENTLY (no final outbound → never armed; or its arm was lost to a CLI/
|
|
1227
1227
|
// daemon-restart window) used to wait out the FULL generic idleSecs (1 h) —
|
|
1228
1228
|
// and the M3 serial drain waits for the session's death, so ONE silent worker
|
|
1229
1229
|
// stalled its whole conveyor. This bound is the defense-in-depth backstop:
|
|
1230
1230
|
// ≫ the legitimate silent-tool case (sleep-180), ≪ idleSecs. The ШТАТНЫЙ
|
|
1231
1231
|
// silent-finish path is `iapeer self-done` (arm without waking anyone —
|
|
1232
|
-
//
|
|
1232
|
+
// the invariant «нет пустых пробуждений» stays intact); this branch only
|
|
1233
1233
|
// bounds the damage when a worker does neither. Policy reap: NO .idle-reaped
|
|
1234
1234
|
// (ephemeral never resumes), NO recordDeath (the ring counts faults).
|
|
1235
1235
|
if (isEphemeralPeer(s.cwd) && ageSecs > cfg.ephemeralUnarmedIdleSecs) {
|
|
@@ -1299,7 +1299,7 @@ export function superviseTick(cfg: LifecycleConfig, deps: SuperviseDeps = {}): S
|
|
|
1299
1299
|
* no runtime so resolveWakeRuntime falls through to the registry default (H5 — registry
|
|
1300
1300
|
* is the only source of wake-runtime truth). Pinning broke the fleet-switch procedure
|
|
1301
1301
|
* (`default-runtime <rt>` + `self-fresh`): the peer silently resurrected on the OLD
|
|
1302
|
-
* runtime, ignoring the just-flipped default (live incident
|
|
1302
|
+
* runtime, ignoring the just-flipped default (live incident). A
|
|
1303
1303
|
* session deliberately living on a non-default runtime has explicit paths back (wake
|
|
1304
1304
|
* with an explicit runtime); an owner /new returns the peer to its declared default.
|
|
1305
1305
|
*/
|
|
@@ -1376,7 +1376,7 @@ export async function drainEphemeralQueue(
|
|
|
1376
1376
|
if (sessionAlive(sock, identity)) return null // one task per session — wait for its reap
|
|
1377
1377
|
const item = peekEphemeralTask(cfg, identity)
|
|
1378
1378
|
if (!item) return null
|
|
1379
|
-
// Durable drain trace
|
|
1379
|
+
// Durable drain trace: which item, how deep the queue.
|
|
1380
1380
|
appendLifecycleEvent(
|
|
1381
1381
|
cfg.eventLogDir,
|
|
1382
1382
|
{ ev: 'ephemeral-drain', identity, seq: item.seq, depth: ephemeralQueueDepth(cfg, identity) },
|
package/src/lifecycle/queue.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// concurrent enqueues can never share a seq; the loser just advances. Content
|
|
17
17
|
// is complete before the link lands (no partial reads).
|
|
18
18
|
//
|
|
19
|
-
// Retry semantics
|
|
19
|
+
// Retry semantics: the consumer PEEKS (reads without
|
|
20
20
|
// removing), wakes the worker, and removes the item ONLY on READY — a failed
|
|
21
21
|
// wake leaves the task at the head for the next supervise-tick drain. See
|
|
22
22
|
// drainEphemeralQueue.
|
package/src/onboard/index.ts
CHANGED
|
@@ -51,7 +51,15 @@ export interface OnboardOptions {
|
|
|
51
51
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
52
|
|
|
53
53
|
function runtimeBin(runtime: OnboardRuntime, env: NodeJS.ProcessEnv): string {
|
|
54
|
-
if (runtime === 'claude')
|
|
54
|
+
if (runtime === 'claude') {
|
|
55
|
+
const override = env.IAPEER_CLAUDE_BIN?.trim()
|
|
56
|
+
if (override) return override
|
|
57
|
+
// Prefer the native installer location (~/.local/bin/claude), but fall back to the bare,
|
|
58
|
+
// PATH-resolved name so an npm/Homebrew claude installed elsewhere on PATH is not read as
|
|
59
|
+
// runtime-missing (symmetric with codex, which defaults to the bare name).
|
|
60
|
+
const native = join(env.HOME?.trim() || homedir(), '.local', 'bin', 'claude')
|
|
61
|
+
return isExecutable(native, env) ? native : 'claude'
|
|
62
|
+
}
|
|
55
63
|
return env.IAPEER_CODEX_BIN?.trim() || 'codex'
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -65,10 +73,9 @@ function isExecutable(binOrName: string, env: NodeJS.ProcessEnv = process.env):
|
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
// bare name → PRESENCE probe over PATH (`command -v` semantics), NO spawn.
|
|
68
|
-
// History
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// (final, boris+iapeer-memory): macOS held the cask-updated codex on a GUI
|
|
76
|
+
// History: the original `--version` ANSWER probe hung forever (stray probes sat
|
|
77
|
+
// 25+ min); the 10 s timeout that replaced it then DEGRADED a live-looking codex
|
|
78
|
+
// to 'runtime-missing'. ROOT CAUSE: macOS held the cask-updated codex on a GUI
|
|
72
79
|
// launch-approval dialog — EVERY invocation parked before main (observed as a
|
|
73
80
|
// dyld hang) until the owner confirmed the dialog. NOT a non-tty class, not a
|
|
74
81
|
// broken binary. The presence probe stays right regardless: the skip question is
|
|
@@ -94,8 +101,8 @@ function isExecutable(binOrName: string, env: NodeJS.ProcessEnv = process.env):
|
|
|
94
101
|
*/
|
|
95
102
|
export function isMarketplaceRegistered(runtime: OnboardRuntime, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
96
103
|
const bin = runtimeBin(runtime, env)
|
|
97
|
-
// HARD TIMEOUT — a runtime CLI can wedge before main on ANY invocation (
|
|
98
|
-
//
|
|
104
|
+
// HARD TIMEOUT — a runtime CLI can wedge before main on ANY invocation (e.g.
|
|
105
|
+
// macOS launch-approval pending after a cask update parked codex — first
|
|
99
106
|
// `--version`, then this very `plugin marketplace list` after the presence
|
|
100
107
|
// probe let the binary through). Timeout → status null → "not registered" →
|
|
101
108
|
// the add (also time-bounded) decides; never a wedge.
|
|
@@ -132,7 +139,7 @@ export function registerMarketplace(
|
|
|
132
139
|
ref: string = MARKETPLACE_REF,
|
|
133
140
|
): { ok: boolean; detail?: string } {
|
|
134
141
|
const bin = runtimeBin(runtime, env)
|
|
135
|
-
// Same hard timeout as the list probe (the pre-main wedge class — known
|
|
142
|
+
// Same hard timeout as the list probe (the pre-main wedge class — a known
|
|
136
143
|
// representative: macOS launch-approval pending after a cask update) — a wedged
|
|
137
144
|
// add degrades to a loud 'failed' line instead of freezing the host phase.
|
|
138
145
|
const r = spawnSync(bin, ['plugin', 'marketplace', 'add', ref], { encoding: 'utf8', timeout: 120_000 })
|
|
@@ -156,11 +163,10 @@ export function isMarketplaceRegisteredAs(
|
|
|
156
163
|
}
|
|
157
164
|
|
|
158
165
|
/**
|
|
159
|
-
* Refresh a runtime's local marketplace SNAPSHOT (контракт §Плагин
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* `plugin marketplace upgrade <name>`.
|
|
166
|
+
* Refresh a runtime's local marketplace SNAPSHOT (контракт §Плагин провайдера):
|
|
167
|
+
* a plugin registered in the marketplace AFTER the host's last pull reads as
|
|
168
|
+
* «unknown plugin» until the snapshot updates. Verb names: claude `plugin
|
|
169
|
+
* marketplace update <name>`, codex `plugin marketplace upgrade <name>`.
|
|
164
170
|
*/
|
|
165
171
|
export function refreshMarketplace(
|
|
166
172
|
runtime: OnboardRuntime,
|
package/src/onboard/memory.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Memory-slot onboard step (docs/Слот памяти — контракт memory provider.md
|
|
2
|
-
//
|
|
1
|
+
// Memory-slot onboard step (docs/Слот памяти — контракт memory provider.md).
|
|
2
|
+
// Host-phase, optional, DEFAULT-YES:
|
|
3
3
|
// install the default memory provider package and run ITS OWN init-verb — the
|
|
4
4
|
// provider writes the slot declaration and deploys its surfaces; the core never
|
|
5
5
|
// writes the slot itself. The provider OWNS the install questions (two-mode
|
|
@@ -17,7 +17,7 @@ import { normalizeIntelligenceValue } from '../core/constants.ts'
|
|
|
17
17
|
import { readPeersIndex } from '../registry/index.ts'
|
|
18
18
|
import { readMemoryProvider, type MemoryProvider } from '../status/index.ts'
|
|
19
19
|
|
|
20
|
-
/** The distribution default provider (
|
|
20
|
+
/** The distribution default provider (memory is a first-class core option). */
|
|
21
21
|
export const DEFAULT_MEMORY_PACKAGE = '@agfpd/iapeer-memory'
|
|
22
22
|
|
|
23
23
|
export interface MemoryOnboardOptions {
|
|
@@ -56,7 +56,7 @@ function defaultRunInit(
|
|
|
56
56
|
const probe = spawnSync('npm', ['view', `${pkg}@latest`, 'version'], { encoding: 'utf8', env })
|
|
57
57
|
if (probe.status !== 0) return { status: probe.status, unavailable: true }
|
|
58
58
|
// INHERITED stdio — the provider owns its install questions (tty interactive
|
|
59
|
-
// happens here, once). npx bin-name nuance (cf. the 0.2.9 update
|
|
59
|
+
// happens here, once). npx bin-name nuance (cf. the 0.2.9 update pitfall): with
|
|
60
60
|
// the provider bin already on PATH npx runs the INSTALLED one — acceptable for
|
|
61
61
|
// an idempotent init (unlike the self-update case where it was a structural bug).
|
|
62
62
|
const r = spawnSync('npx', ['-y', pkg, ...args], { stdio: 'inherit', env })
|
|
@@ -84,7 +84,7 @@ export async function onboardMemoryProvider(opts: MemoryOnboardOptions = {}): Pr
|
|
|
84
84
|
if (opts.skip) return { state: 'skipped-flag', provider: existing }
|
|
85
85
|
if (existing) {
|
|
86
86
|
if (existing.package === pkg) return { state: 'already', provider: existing }
|
|
87
|
-
// Never silently install over an occupied slot (contract:
|
|
87
|
+
// Never silently install over an occupied slot (contract: FORBIDDEN).
|
|
88
88
|
return {
|
|
89
89
|
state: 'refused-foreign',
|
|
90
90
|
provider: existing,
|
package/src/onboard/steps.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Default-yes onboard steps for the distribution BACKBONE (design doc
|
|
2
|
-
// docs/«Onboard костяка — notifier, telegram, каналы, update
|
|
3
|
-
//
|
|
4
|
-
// telegram self-config contract v0.10.0+).
|
|
2
|
+
// docs/«Onboard костяка — notifier, telegram, каналы, update»; baked facts:
|
|
3
|
+
// notifier «ноль вопросов», telegram self-config contract v0.10.0+).
|
|
5
4
|
//
|
|
6
5
|
// Backbone = core + memory slot + notifier-runtime + telegram-runtime. The onboard
|
|
7
6
|
// step ORDER is significant: marketplace → notifier → TELEGRAM (creates the human
|
|
@@ -25,8 +24,8 @@ import {
|
|
|
25
24
|
} from '../runtime/deploy.ts'
|
|
26
25
|
|
|
27
26
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
-
// (а) notifier — default-yes, ZERO questions (
|
|
29
|
-
//
|
|
27
|
+
// (а) notifier — default-yes, ZERO questions (the whole chain has no
|
|
28
|
+
// prompt/stdin/tty dependency; all failures are fail-closed exit≠0).
|
|
30
29
|
// Prereq (not a question): `bun` on PATH — without it the npx shim exits non-zero
|
|
31
30
|
// → SOFT SKIP with a clear message (design: unavailability is not a core error).
|
|
32
31
|
// Idempotent re-onboard: install is manifest-gated (skipped), deploy is no-clobber
|
|
@@ -97,8 +96,8 @@ export async function onboardNotifierStep(opts: NotifierStepOptions = {}): Promi
|
|
|
97
96
|
// writes interfaces.telegram.user_id itself; no prepare / interface-human calls.
|
|
98
97
|
// Bot tokens are NOT asked here — they are per-channel (`iapeer connect telegram`).
|
|
99
98
|
//
|
|
100
|
-
// IDEMPOTENT re-onboard
|
|
101
|
-
//
|
|
99
|
+
// IDEMPOTENT re-onboard: an EXISTING natural peer in the registry → the step is
|
|
100
|
+
// a no-op/skip with a clear message —
|
|
102
101
|
// NEVER an offer to create a second human peer.
|
|
103
102
|
//
|
|
104
103
|
// Interactive lives INSIDE the step (tty only); non-tty without the flags
|
|
@@ -222,8 +221,8 @@ export async function onboardTelegramStep(opts: TelegramStepOptions = {}): Promi
|
|
|
222
221
|
|
|
223
222
|
try {
|
|
224
223
|
// TELEGRAM_USER_ID rides the env into createPeer → initPeer → the package's
|
|
225
|
-
// self-config hook, which writes interfaces.telegram.user_id itself
|
|
226
|
-
//
|
|
224
|
+
// self-config hook, which writes interfaces.telegram.user_id itself
|
|
225
|
+
// (contract v0.10.0+ — no prepare / interface-human from the core).
|
|
227
226
|
const r = await createPeer({
|
|
228
227
|
personality,
|
|
229
228
|
runtime: 'telegram',
|
package/src/provision/index.ts
CHANGED
|
@@ -122,8 +122,7 @@ export async function provisionPeer(opts: ProvisionPeerOptions): Promise<Provisi
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
// Provider PROVISION command (контракт §Provision провайдера, declaration
|
|
125
|
-
// v1.2 — ADR-009 inversion
|
|
126
|
-
// iapeer-memory 11.06): an occupied slot WITH a provision block shells into
|
|
125
|
+
// v1.2 — ADR-009 inversion): an occupied slot WITH a provision block shells into
|
|
127
126
|
// the PROVIDER's command per agentic runtime (occasion=birth) — the core
|
|
128
127
|
// knows nothing about surface forms. A failure is NEVER masked (a broken
|
|
129
128
|
// provider must not hide) — LOUD warn; repair is provider-side (the core is
|
|
@@ -167,7 +166,7 @@ export async function provisionPeer(opts: ProvisionPeerOptions): Promise<Provisi
|
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
168
|
// NB: the v1.1 plugin auto-install branch (an occupied slot WITH a `plugin`
|
|
170
|
-
// block) was REMOVED
|
|
169
|
+
// block) was REMOVED — the plugin form is retired ecosystem-wide
|
|
171
170
|
// (provider's ADR-017; contract §Плагин провайдера is a tombstone). A v1.2
|
|
172
171
|
// declaration without a provision block provisions nothing here.
|
|
173
172
|
}
|
package/src/runtime/deploy.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// per-peer self-config hook + auto-bootstrap, uniformly.
|
|
7
7
|
//
|
|
8
8
|
// MODE (b) "operator-add" (telegram human) does NOT come through here — it is a direct
|
|
9
|
-
// `iapeer create
|
|
9
|
+
// `iapeer create alice --runtime telegram`. Both modes share the SAME per-peer
|
|
10
10
|
// self-config hook (invoked inside createPeer→initPeer); deployRuntime is just the
|
|
11
11
|
// enumeration of the declared set. "1 always-on infra peer = 1 plist", idempotent
|
|
12
12
|
// (re-deploy of an existing peer does not duplicate or clobber).
|
|
@@ -24,7 +24,7 @@ import { createPeer, type CreatePeerResult } from '../create/index.ts'
|
|
|
24
24
|
import { readRuntimeManifest, type RuntimeManifest, type RuntimePeerDecl } from './index.ts'
|
|
25
25
|
|
|
26
26
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
-
// Built-in runtime → npm package registry (§6
|
|
27
|
+
// Built-in runtime → npm package registry (§6): onboard AUTO-resolves
|
|
28
28
|
// the package for a known infra runtime and `npx`-installs it (the package self-
|
|
29
29
|
// deploys its bin + manifest). The operator overrides per-runtime with --package.
|
|
30
30
|
// FROZEN map; a runtime absent here needs an explicit --package.
|
package/src/runtime/index.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// declared set. Static.
|
|
11
11
|
// (b) operator-add — a package whose peers are PEOPLE the package can't know ahead
|
|
12
12
|
// (telegram: a human) declares NO peers; the operator adds one dynamically with
|
|
13
|
-
// `iapeer create
|
|
13
|
+
// `iapeer create alice --runtime telegram`. Dynamic.
|
|
14
14
|
// BOTH converge on the SAME per-peer self-config hook ("configure runtime state for
|
|
15
15
|
// peer X", idempotent). The hook is per-peer; only the enumeration/trigger differs.
|
|
16
16
|
// So manifest.peers is OPTIONAL (mode b omits it); manifest.selfConfig is the shared
|
|
@@ -51,9 +51,9 @@ export interface RuntimePeerDecl {
|
|
|
51
51
|
export interface RuntimeManifest {
|
|
52
52
|
/** The runtime id this manifest describes (must match the folder it lives in). */
|
|
53
53
|
runtime: Runtime
|
|
54
|
-
/** OPTIONAL installed package version (the
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
/** OPTIONAL installed package version (the package's self-install stamp).
|
|
55
|
+
* `update-runtime` version-gates on it; absent → no gate, the update
|
|
56
|
+
* re-installs idempotently. */
|
|
57
57
|
version?: string
|
|
58
58
|
/** OPTIONAL per-peer self-config hook (the shared contract both modes call). */
|
|
59
59
|
selfConfig?: SelfConfigDescriptor
|