@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
package/src/core/constants.ts
CHANGED
|
@@ -18,12 +18,12 @@ export const PEERS_SCHEMA_VERSION = 2
|
|
|
18
18
|
// 450 (was 250): self-documenting API-peer descriptions (notifier timer/watcher)
|
|
19
19
|
// must fit "who the peer is + registration format + a live example" — dense full
|
|
20
20
|
// texts run to ~408 chars; 250 cut them mid-word so the caller could not compose
|
|
21
|
-
// the call.
|
|
21
|
+
// the call. NB: this is COMPILE-TIME
|
|
22
22
|
// baked — the live daemon re-clamps descriptions on read (registry parsePeerRecord),
|
|
23
23
|
// so the running router keeps the OLD limit until restarted onto the new binary.
|
|
24
24
|
export const MAX_DESCRIPTION_LEN = 450
|
|
25
25
|
|
|
26
|
-
// Contract vocabulary (docs
|
|
26
|
+
// Contract vocabulary (docs/Идентичность): the nature of the
|
|
27
27
|
// intelligence expressing itself through a runtime.
|
|
28
28
|
// artificial — AI agent · natural — human · absent — programmatic source
|
|
29
29
|
export const INTELLIGENCE_VALUES = ['artificial', 'natural', 'absent'] as const
|
|
@@ -127,7 +127,7 @@ export const DEFAULT_SOCK_DIR = '/tmp'
|
|
|
127
127
|
// agree — a site that hardcodes DEFAULT_SOCK_DIR would look in /tmp while a sandbox
|
|
128
128
|
// (IAPEER_SOCK_DIR set) created the session elsewhere → a false "offline".
|
|
129
129
|
//
|
|
130
|
-
// IAPEER_ROOT IMPLIES SOCKET ISOLATION
|
|
130
|
+
// IAPEER_ROOT IMPLIES SOCKET ISOLATION: an alt-root used
|
|
131
131
|
// to inherit the GLOBAL /tmp, so a sandboxed `list` saw PROD sessions live by name
|
|
132
132
|
// collision, and a sandboxed stop/start would have HIT a prod session. A set root
|
|
133
133
|
// now derives `<root>/socks` unless IAPEER_SOCK_DIR explicitly says otherwise; the
|
package/src/core/normalize.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// and system `uconv`/`iconv` are forbidden by the zone (OS-nondeterministic) and
|
|
12
12
|
// absent here. `transliteration` is the one pure-JS engine that reproduces ICU
|
|
13
13
|
// across WRITING SYSTEMS — CJK → pinyin WITH syllable spacing (项目 → "Xiang Mu"),
|
|
14
|
-
// Cyrillic (
|
|
14
|
+
// Cyrillic (Привет → "Privet"), Greek (Ω → "O"), Latin diacritics (Köln → "Koln") —
|
|
15
15
|
// and it bundles its own CLDR/unidecode-derived tables, so it is deterministic
|
|
16
16
|
// across OSes (exactly the property the zone's iconv ban is protecting). It is the
|
|
17
17
|
// deterministic CLDR-EQUIVALENT of ICU, NOT a literal ICU engine.
|
|
@@ -75,7 +75,7 @@ function slug(value: string): string {
|
|
|
75
75
|
* derivation/comparison routes through (basename(cwd) → personality, PEER_*
|
|
76
76
|
* comparisons, profile re-validation).
|
|
77
77
|
*
|
|
78
|
-
* IDEMPOTENT on already-valid names: normalize("
|
|
78
|
+
* IDEMPOTENT on already-valid names: normalize("alice") === "alice",
|
|
79
79
|
* normalize("notifier-timer") === "notifier-timer" — required because
|
|
80
80
|
* validatePersonality re-normalizes stored profile names.
|
|
81
81
|
*
|
package/src/create/index.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// NO-CLOBBER: making the folder is mkdir-recursive (never deletes); init is
|
|
12
12
|
// idempotent (profile kept, .mcp.json merged, doctrine never overwritten). The one
|
|
13
13
|
// hard refusal: a target that already holds a DIFFERENT peer's profile — creating
|
|
14
|
-
// "
|
|
14
|
+
// "alice" into a folder that is already "bob" would either silently adopt bob or
|
|
15
15
|
// split an identity, so it fails loudly instead.
|
|
16
16
|
//
|
|
17
17
|
// INFRA (telegram human via operator-add / notifier function via a declared set):
|
package/src/daemon/index.ts
CHANGED
|
@@ -79,7 +79,7 @@ export function resolveCallerFromHeader(value: string | undefined, index: PeersI
|
|
|
79
79
|
// The roster of known peers is DELIBERATELY NOT in this description. It is rendered
|
|
80
80
|
// ONCE, in the peer's system prompt (composeSystemPrompt → Layer 3 "## Known peers").
|
|
81
81
|
// Embedding it here ALSO duplicated the full list into every agent's context on every
|
|
82
|
-
// turn —
|
|
82
|
+
// turn — confirmed live: an agent saw the identical roster twice. The
|
|
83
83
|
// description stays light: purpose + parameter semantics only; the model gets the
|
|
84
84
|
// roster from its system prompt. Likewise the MCP server-level `instructions` field is
|
|
85
85
|
// left unset (createMcpServer) — it would re-inject doctrine into the client system
|
|
@@ -211,7 +211,7 @@ export async function callTool(
|
|
|
211
211
|
// Ф-#8a: ONE durable outcome line per delivery attempt (delivery.log, sibling
|
|
212
212
|
// to lifecycle.log) — metadata only, never the body. Both branches of the
|
|
213
213
|
// routeSend result are recorded, so a suspected loss is reconstructable from
|
|
214
|
-
// disk (the gap
|
|
214
|
+
// disk (the gap a delivery-loss investigation hit). No-op when no dir is wired
|
|
215
215
|
// (library/test daemons); appendDeliveryEvent itself never throws.
|
|
216
216
|
appendDeliveryEvent(deps.deliveryLogDir, {
|
|
217
217
|
ev: 'delivery',
|
|
@@ -221,8 +221,8 @@ export async function callTool(
|
|
|
221
221
|
ok: String(sent.ok),
|
|
222
222
|
via: sent.ok ? `${sent.value.delivered_to.runtime}-${sent.value.delivered_to.personality}` : undefined,
|
|
223
223
|
woke: sent.ok ? String(sent.value.woke) : undefined,
|
|
224
|
-
// M3 queue observability
|
|
225
|
-
//
|
|
224
|
+
// M3 queue observability: a queued accept is marked and carries the queue
|
|
225
|
+
// depth at accept time — a stuck queue is visible.
|
|
226
226
|
queued: sent.ok && sent.value.queued ? 'true' : undefined,
|
|
227
227
|
qkind: sent.ok ? sent.value.queuedBy : undefined,
|
|
228
228
|
qd: sent.ok ? sent.value.queueDepth : undefined,
|
|
@@ -345,7 +345,7 @@ export interface StartDaemonOptions extends StorageOptions {
|
|
|
345
345
|
* `Authorization: Bearer <token>` or it is rejected 401 BEFORE any MCP dispatch.
|
|
346
346
|
* Unset → no auth (the same-uid unix-socket base / loopback default). This is the
|
|
347
347
|
* structural layer for closing H8 on the TCP loopback listener — kept OFF until
|
|
348
|
-
*
|
|
348
|
+
* explicitly enabled (the production main reads IAPEER_BEARER_TOKEN). Loopback is
|
|
349
349
|
* broader than same-uid, so a token gates it without changing the on-wire MCP.
|
|
350
350
|
*/
|
|
351
351
|
bearerToken?: string
|
package/src/daemon/main.ts
CHANGED
|
@@ -118,10 +118,10 @@ export function makeArmEphemeralOnDelivered(cfg: LifecycleConfig): (caller: Reso
|
|
|
118
118
|
* makeEphemeralRouteDeps): record a LIVE-delivered topic as the target identity's
|
|
119
119
|
* `.topic` marker, so the marker tracks the topic the session LAST WORKED ON — the
|
|
120
120
|
* semantics resolveWakeMode's executor discriminator actually wants — not merely the
|
|
121
|
-
* topic it woke with.
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
121
|
+
* topic it woke with. Defect: an executor could wake topicless, work a delivered
|
|
122
|
+
* topic for a session, be stop-parked clean, and a later topic-matching resume-wake
|
|
123
|
+
* compared against the STALE wake-time marker → cause=idle-reaped-new-topic → FRESH;
|
|
124
|
+
* the session's context was lost despite the clean park.
|
|
125
125
|
* Same-topic deliveries are a no-op (no marker churn, no log spam) — the lifecycle.log
|
|
126
126
|
* line `ev=topic-note` therefore marks exactly the moments the session's working
|
|
127
127
|
* topic SHIFTED, which is what a fresh-vs-resume postmortem needs. Best-effort: a
|
|
@@ -307,7 +307,7 @@ export async function startConfiguredDaemon(opts: ConfiguredDaemonOptions = {}):
|
|
|
307
307
|
ephemeral,
|
|
308
308
|
// Live-delivered topic → the target's .topic marker (see makeNoteLiveTopic):
|
|
309
309
|
// the wake resolver's executor discriminator then compares against the topic
|
|
310
|
-
// the session last WORKED ON, not the one it woke with
|
|
310
|
+
// the session last WORKED ON, not the one it woke with.
|
|
311
311
|
noteLiveTopic,
|
|
312
312
|
// Busy-human-composer queue: fast queued ack, async drain, fail all pending
|
|
313
313
|
// on daemon close/update so a queued envelope is never silently lost.
|
|
@@ -371,7 +371,7 @@ export interface InstallDaemonPlistOptions {
|
|
|
371
371
|
export function buildDaemonPlistSpec(opts: InstallDaemonPlistOptions = {}): LaunchdPlistSpec {
|
|
372
372
|
const env = opts.env ?? process.env
|
|
373
373
|
const home = env.HOME?.trim() || homedir()
|
|
374
|
-
const defaultPath = `${home}/.bun/bin:${home}/.local/bin:/opt/homebrew/bin:/usr/bin:/bin`
|
|
374
|
+
const defaultPath = `${home}/.bun/bin:${home}/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin`
|
|
375
375
|
const logDir = pluginLogsDir('iapeer', { env })
|
|
376
376
|
const environment: Record<string, string> = {
|
|
377
377
|
PATH: opts.path ?? env.PATH ?? defaultPath,
|
|
@@ -417,9 +417,9 @@ export interface InstallDaemonPlistResult {
|
|
|
417
417
|
* ~/Library/LaunchAgents/com.agfpd.iapeer.plist (or IAPEER_LAUNCHAGENTS_DIR),
|
|
418
418
|
* returning {path, changed}. Writes the FILE only — does NOT bootstrap it (load
|
|
419
419
|
* is a deliberate operator step). IDEMPOTENT BY CONTENT: writes only when the
|
|
420
|
-
* rendered plist differs from what is already on disk (
|
|
421
|
-
*
|
|
422
|
-
*
|
|
420
|
+
* rendered plist differs from what is already on disk (an unconditional rewrite
|
|
421
|
+
* on every deploy spams the owner's Background Task Management notification for
|
|
422
|
+
* zero benefit). Collision guard: refuses to
|
|
423
423
|
* overwrite an existing com.agfpd.iapeer.plist that is not foundation-owned
|
|
424
424
|
* (lacks the sentinel) — the same ownership proof the peer-plist installer uses.
|
|
425
425
|
* com.agfpd.* is the foundation-exclusive daemon namespace, so this never
|
package/src/enable/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// enable — per-peer capability install (contract Установка §3 + Per-рантайм
|
|
2
2
|
// install-асимметрия; verb `enable <capability> [peer]`). The init→enable
|
|
3
|
-
// capability story: a peer gains a capability
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// capability story: a peer gains a capability FROM our marketplace. iapeer
|
|
4
|
+
// ORCHESTRATES — install + enable + call `setup` ONLY if the plugin declares it
|
|
5
|
+
// — and deliberately does NOT resolve the plugin's dep-graph (`requires`/`setup`
|
|
6
|
+
// internals are the plugin's job, → Стандарт iapeer плагина).
|
|
7
7
|
//
|
|
8
8
|
// Per-рантайм install-асимметрия (contract table):
|
|
9
9
|
// claude → `plugin install <p>@agfpd --scope project` run IN the peer's cwd; the
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
// against the real host under fleet-guard without an isolated CODEX_HOME.
|
|
16
16
|
//
|
|
17
17
|
// SIMPLE vs СЛОЖНЫЙ plugin (contract): a plugin MAY ship `iapeer.json` at its root
|
|
18
|
-
// declaring `setup` (a multi-step installer) and `requires`. SIMPLE (
|
|
19
|
-
// iapeer.json/no setup): install + enable → works. СЛОЖНЫЙ (
|
|
20
|
-
// enable → call `setup`. iapeer reads the manifest
|
|
18
|
+
// declaring `setup` (a multi-step installer) and `requires`. SIMPLE (no
|
|
19
|
+
// iapeer.json/no setup): install + enable → works. СЛОЖНЫЙ (a multi-step
|
|
20
|
+
// capability plugin): install + enable → call `setup`. iapeer reads the manifest
|
|
21
|
+
// and calls setup ONLY if declared.
|
|
21
22
|
|
|
22
23
|
import { spawnSync } from 'child_process'
|
|
23
24
|
import { existsSync, readFileSync, realpathSync } from 'fs'
|
|
@@ -369,10 +370,10 @@ export interface CwdEnableOptions {
|
|
|
369
370
|
}
|
|
370
371
|
|
|
371
372
|
/** One install attempt for one runtime, WITH the stale-snapshot resilience the
|
|
372
|
-
* contract requires
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
373
|
+
* contract requires: a failed attempt refreshes the runtime's local marketplace
|
|
374
|
+
* snapshot (claude `marketplace update` / codex `marketplace upgrade`) and
|
|
375
|
+
* retries ONCE — a plugin registered in the marketplace after the host's last
|
|
376
|
+
* pull reads as «unknown plugin» until then. */
|
|
376
377
|
function enableRuntimeWithRetry(
|
|
377
378
|
rt: CapabilityRuntime,
|
|
378
379
|
plugin: string,
|
|
@@ -399,8 +400,8 @@ function enableRuntimeWithRetry(
|
|
|
399
400
|
* codex global) + enable + `setup` ONLY if declared — with marketplace ensure
|
|
400
401
|
* (marketplaceRef given + not registered → add) and the stale-snapshot retry.
|
|
401
402
|
* Consumer: enableCapability (the peer-resolved generic `iapeer enable` verb) —
|
|
402
|
-
* ONE home of the install forms. (The
|
|
403
|
-
*
|
|
403
|
+
* ONE home of the install forms. (The provider birth-hook and verb consumers were
|
|
404
|
+
* removed — the provider plugin form is retired, ADR-017.)
|
|
404
405
|
*/
|
|
405
406
|
export function enableCapabilityForCwd(o: CwdEnableOptions): EnableResult {
|
|
406
407
|
const env = o.env ?? process.env
|
|
@@ -473,9 +474,9 @@ export function enableCapability(opts: EnableOptions): EnableResult {
|
|
|
473
474
|
}
|
|
474
475
|
|
|
475
476
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
476
|
-
// Removal forms — REMOVED
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
477
|
+
// Removal forms — REMOVED with the provider verb (their only caller; ADR-017
|
|
478
|
+
// retired the provider plugin form). Historical verb names, for a future generic
|
|
479
|
+
// `disable`: claude `plugin uninstall <id> --scope project` (run in the peer
|
|
480
|
+
// cwd), codex `plugin remove <id>` (host-global). See git history ≤v0.2.41 for
|
|
481
|
+
// the implementations.
|
|
481
482
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
// Provision-command executor — the v1.2 inversion joint (
|
|
2
|
-
// §Provision провайдера; ADR-009
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// forms. The four contract requirements live here:
|
|
1
|
+
// Provision-command executor — the v1.2 inversion joint (contract «Слот памяти»
|
|
2
|
+
// §Provision провайдера; ADR-009). The slot declares provision/unprovision
|
|
3
|
+
// COMMANDS; the core shells into them at the lifecycle joints and knows NOTHING
|
|
4
|
+
// about surface forms. The four contract requirements live here:
|
|
6
5
|
// 1. argv-form {command, args[]} with PER-ARG placeholder substitution —
|
|
7
|
-
// spawned WITHOUT a shell (injection/quoting class
|
|
6
|
+
// spawned WITHOUT a shell (injection/quoting class — a backtick in an arg
|
|
7
|
+
// must never reach a shell).
|
|
8
8
|
// 2. `command` is ABSOLUTE (parser refuses relative — launchd minimal PATH).
|
|
9
9
|
// 3. Timeout 120 s; best-effort semantics live at the CALL SITES (LOUD warn,
|
|
10
10
|
// the birth/remove flow continues) — this module only reports structurally.
|
|
11
11
|
// 4. {occasion} vocabulary (финален, contract §Provision): birth | sweep-on |
|
|
12
12
|
// off-peer | off-all | remove — ref-counting of host-global surfaces is the
|
|
13
|
-
// PROVIDER's business. Since the
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// PROVIDER's business. Since the provider-verb removal (ADR-017) the CORE
|
|
14
|
+
// emits only `birth` and `remove`; the sweep occasions remain part of the
|
|
15
|
+
// provider-command interface (their CLI may use them internally).
|
|
16
16
|
// Idempotency is the provider's obligation by construction; the provider holds
|
|
17
17
|
// its own lock against concurrent provisions.
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ import { spawnSync } from 'child_process'
|
|
|
20
20
|
import { accessSync, constants as FS } from 'fs'
|
|
21
21
|
import type { MemoryProviderProvision } from '../status/index.ts'
|
|
22
22
|
|
|
23
|
-
/** The agreed occasion vocabulary (
|
|
23
|
+
/** The agreed occasion vocabulary (финален). */
|
|
24
24
|
export type ProvisionOccasion = 'birth' | 'sweep-on' | 'off-peer' | 'off-all' | 'remove'
|
|
25
25
|
|
|
26
26
|
export const PROVISION_TIMEOUT_MS = 120_000
|
package/src/identity/index.ts
CHANGED
|
@@ -481,12 +481,12 @@ export function ensurePeerProfile(options: EnsurePeerProfileOptions): PeerProfil
|
|
|
481
481
|
const mergedRuntimes = uniqueRuntimes([...existing.runtimes, ...discoveredRuntimes])
|
|
482
482
|
ensureLocalRuntimeScopes(cwd, mergedRuntimes)
|
|
483
483
|
// RE-PROVISION parity with the new-profile branch: an EXISTING infra peer (a
|
|
484
|
-
// migration / re-provision — e.g. `iapeer create
|
|
484
|
+
// migration / re-provision — e.g. `iapeer create alice --runtime telegram` to move
|
|
485
485
|
// a live telegram human onto the foundation) must ALSO get its always-on plist
|
|
486
486
|
// installed and its intelligence set to the runtime's foundation default
|
|
487
487
|
// (telegram→natural, notifier→absent). The old code only merged runtimes here, so
|
|
488
488
|
// re-provisioning an infra peer wrote NO plist (bootstrap → refused-foreign on the
|
|
489
|
-
// missing plist) and left a stale legacy intelligence — the
|
|
489
|
+
// missing plist) and left a stale legacy intelligence — the cutover had to
|
|
490
490
|
// install the plist + flip vocab by hand. Install BEFORE the write so a
|
|
491
491
|
// collision-guard refusal (a foreign plist still sitting at the label) fails loudly
|
|
492
492
|
// and leaves no half-updated profile. Idempotent for our own (sentinel) plist.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// (telegram/capability passport — PASSPORT ONLY: aliases are misfiled there and the
|
|
12
12
|
// contract relocates them to `expansion.aliases`, the runtime-agnostic plugin-config
|
|
13
13
|
// section owned by the chat-runtime — telegram-runtime today, future discord/matrix
|
|
14
|
-
// read the SAME section per the
|
|
14
|
+
// read the SAME section per the expansion design), notifier (notifier-runtime),
|
|
15
15
|
// expansion (chat-runtime aliases), <plugin> sections — each plugin writes only its
|
|
16
16
|
// own field via atomic merge. Plugin-config sections are NOT projected into the
|
|
17
17
|
// registry index (private config is read from the LOCAL profile by its owner).
|
|
@@ -130,12 +130,12 @@ export function validateProfileStandard(raw: unknown, cwd: string): ProfileIssue
|
|
|
130
130
|
message: 'legacy top-level `aliases` — contract relocates to expansion.aliases (/alias-* namespace); owner migration pending',
|
|
131
131
|
})
|
|
132
132
|
}
|
|
133
|
-
// aliases misfiled in the PASSPORT (interfaces.telegram) —
|
|
134
|
-
// Taxonomy
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
133
|
+
// aliases misfiled in the PASSPORT (interfaces.telegram) — an interim home.
|
|
134
|
+
// Taxonomy: aliases are PRIVATE plugin config (one reader, the chat-runtime at
|
|
135
|
+
// expansion), not a public passport attribute — the contract relocates them to the
|
|
136
|
+
// runtime-agnostic `expansion.aliases` section (future discord/matrix runtimes read
|
|
137
|
+
// the SAME aliases). Transition observability: this warn goes silent once the owner
|
|
138
|
+
// migration moves the data.
|
|
139
139
|
const interfacesObj =
|
|
140
140
|
obj.interfaces && typeof obj.interfaces === 'object' && !Array.isArray(obj.interfaces)
|
|
141
141
|
? (obj.interfaces as Record<string, unknown>)
|
|
@@ -254,7 +254,7 @@ export function reconcileIndex(options: PeersUpdateOptions = {}): ReconcileEntry
|
|
|
254
254
|
* Self-heal the index: REPLACE every registry record with the projection from its
|
|
255
255
|
* local profile (the source of truth). Unlike upsertPeer (which UNIONs runtimes so a
|
|
256
256
|
* boot never drops one), reindex is a deliberate REWRITE — it is how a local profile's
|
|
257
|
-
* narrowed runtimes (e.g.
|
|
257
|
+
* narrowed runtimes (e.g. dropped to [telegram]) propagate to the index. A peer
|
|
258
258
|
* whose local profile is missing is left untouched (and surfaced in `missing`). The
|
|
259
259
|
* legacy-safe intelligenceRaw is preserved when the nature is unchanged (never silently
|
|
260
260
|
* migrate the live vocab — the human→natural incident class).
|
package/src/init/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// + registry + infra plist) and adds the per-peer ECOSYSTEM wiring a peer needs to
|
|
4
4
|
// talk: the HTTP-MCP transport config + the local doctrine template.
|
|
5
5
|
//
|
|
6
|
-
// THE BIG SIMPLIFICATION (install-gate
|
|
6
|
+
// THE BIG SIMPLIFICATION (install-gate, proven live): a peer gets send_to_peer
|
|
7
7
|
// purely from a project-scope `.mcp.json` pointing at the host-wide HTTP-MCP daemon —
|
|
8
8
|
// NO plugin install, NO /reload-plugins, NO GC version-snapshots (the whole legacy
|
|
9
9
|
// persistent-peer install-gate class is gone). Verified: a cold-start claude session
|
|
@@ -120,8 +120,8 @@ export function writeClaudeMcpConfig(cwd: string, personality: string, daemonUrl
|
|
|
120
120
|
//
|
|
121
121
|
// codex's CLI does NOT import tools from an OPEN streamable-HTTP MCP server — it marks
|
|
122
122
|
// it authStatus=unsupported (and BLOCKS on startup) unless an auth scheme is configured
|
|
123
|
-
// (codex bug #21532 / #4707). The token-free fix (PROVEN LIVE, codex 0.136
|
|
124
|
-
//
|
|
123
|
+
// (codex bug #21532 / #4707). The token-free fix (PROVEN LIVE, codex 0.136; a real
|
|
124
|
+
// host-wide bearer was rejected as a localhost crutch): set a
|
|
125
125
|
// FIXED, NON-SECRET bearer (CODEX_DUMMY_BEARER). Setting `bearer_token_env_var` flips
|
|
126
126
|
// authStatus to `bearer_token` purely from the config FACT — codex does NOT require the
|
|
127
127
|
// server to validate the token. The daemon stays OPEN: it ignores `Authorization` and
|
|
@@ -183,16 +183,16 @@ export function writeCodexMcpConfig(daemonUrl: string, options: StorageOptions =
|
|
|
183
183
|
* Idempotently ensure codex's startup update-check DIALOG is disabled host-wide:
|
|
184
184
|
* `check_for_update_on_startup = false` as a TOP-LEVEL key in config.toml.
|
|
185
185
|
*
|
|
186
|
-
* Why
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
186
|
+
* Why: the interactive "✨ Update available! … Press enter to continue" dialog is
|
|
187
|
+
* a BOOT GATE — a daemon-woken codex session sits on it until a human attaches.
|
|
188
|
+
* Observed: the wake never became ready after 4+ minutes and a notifier alert of
|
|
189
|
+
* the EMERGENCY contour was lost; the session came alive only from a manual
|
|
190
|
+
* attach. Warm-on-demand peers
|
|
191
191
|
* must reach ready hands-free — codex updates stay operator-driven (`codex
|
|
192
192
|
* update` / brew), never a boot-time question to a headless session.
|
|
193
193
|
*
|
|
194
194
|
* Mechanics: the key is a documented top-level config.toml option (present in
|
|
195
|
-
* the binary's own config schema; suppression PROVEN LIVE
|
|
195
|
+
* the binary's own config schema; suppression PROVEN LIVE on codex 0.138
|
|
196
196
|
* with the 0.139 update pending — control boot showed the dialog, the keyed
|
|
197
197
|
* boot went straight past it). A TOML top-level key must precede the first
|
|
198
198
|
* table header, so the key is PREPENDED — appending would land it inside the
|
|
@@ -346,7 +346,7 @@ export async function initPeer(opts: InitPeerOptions): Promise<InitPeerResult> {
|
|
|
346
346
|
let codexMcpConfigPath: string | undefined
|
|
347
347
|
if (provisioned.runtime === 'codex' || hasCodexMarker(provisioned.cwd)) {
|
|
348
348
|
codexMcpConfigPath = writeCodexMcpConfig(daemonUrl, { env }).path
|
|
349
|
-
// Boot-gate hygiene
|
|
349
|
+
// Boot-gate hygiene: a codex peer must never sit on the
|
|
350
350
|
// interactive update dialog at wake — disable the startup check host-wide.
|
|
351
351
|
ensureCodexUpdateCheckDisabled({ env })
|
|
352
352
|
}
|
package/src/install/index.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
//
|
|
10
10
|
// macOS TCC: a stable PATH is NOT enough to keep grants through updates — TCC keys
|
|
11
11
|
// on the code requirement, and an ad-hoc bun-compiled binary's requirement is its
|
|
12
|
-
// CDHash (changes every build → re-prompts; live-proven
|
|
13
|
-
//
|
|
12
|
+
// CDHash (changes every build → re-prompts; live-proven).
|
|
13
|
+
// signInstalledBinary (signing.ts) re-signs each install with the
|
|
14
14
|
// stable local identity so the designated requirement — and the grants — survive.
|
|
15
15
|
|
|
16
16
|
import { copyFileSync, existsSync, mkdirSync, renameSync, statSync } from 'fs'
|
package/src/install/signing.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Stable code-signing for the installed binary — TCC grants must SURVIVE updates
|
|
2
|
-
// (
|
|
3
|
-
// опять триггерить»).
|
|
2
|
+
// (grant once at install; subsequent updates must not re-trigger the TCC prompts).
|
|
4
3
|
//
|
|
5
4
|
// ROOT CAUSE (proven on the host): `bun build --compile` output is ad-hoc
|
|
6
5
|
// linker-signed (Identifier=a.out, no cert chain) — its only stable identity is
|
|
@@ -8,7 +7,7 @@
|
|
|
8
7
|
// requirement; for an ad-hoc binary that collapses to the cdhash → every update
|
|
9
8
|
// is a NEW TCC subject → re-prompts.
|
|
10
9
|
//
|
|
11
|
-
// FIX (proven live
|
|
10
|
+
// FIX (proven live): a LOCAL self-signed code-signing
|
|
12
11
|
// identity ("iapeer Local Codesign", created once at install) re-signs the binary
|
|
13
12
|
// after every build. Two different binaries signed by it carry the IDENTICAL
|
|
14
13
|
// designated requirement:
|
|
@@ -26,7 +25,7 @@ import { tmpdir } from 'os'
|
|
|
26
25
|
import { join } from 'path'
|
|
27
26
|
import { spawnSync } from 'child_process'
|
|
28
27
|
|
|
29
|
-
/** CROSS-PRODUCT CONTRACT
|
|
28
|
+
/** CROSS-PRODUCT CONTRACT: this CN is the
|
|
30
29
|
* SHARED signing identity of the whole agfpd stack. Each product signs with its
|
|
31
30
|
* OWN --identifier (foundation: com.agfpd.iapeer; memory: com.agfpd.iapeer-memory),
|
|
32
31
|
* so TCC subjects stay separate while the host carries ONE key (one keychain
|
|
@@ -91,8 +91,8 @@ export const claudeAdapter: RuntimeAdapter = {
|
|
|
91
91
|
usesDoctrine: true,
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Delivery markers for submitIntoTui (
|
|
95
|
-
*
|
|
94
|
+
* Delivery markers for submitIntoTui (moved out of transport's PROMPT_GLYPHS
|
|
95
|
+
* union into the adapter; docs/Рантайм claude "Доставка"):
|
|
96
96
|
* - promptGlyphs ['❯'] — the same U+276F input-prompt glyph as isInputReady;
|
|
97
97
|
* submitIntoTui finds the prompt row by it. claude-only (codex uses '›'), so
|
|
98
98
|
* a stray codex glyph in a claude pane no longer false-matches.
|
|
@@ -151,10 +151,10 @@ export const claudeAdapter: RuntimeAdapter = {
|
|
|
151
151
|
* - RESUME COMPACT-PICKER ('Resume from summary …') → CURSOR-VERIFIED two-step.
|
|
152
152
|
* This menu's cursor DEFAULTS to "1. Resume from summary (recommended)",
|
|
153
153
|
* and selecting it COMPACTS the session (claude implements that choice as an
|
|
154
|
-
* internal /compact) —
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
154
|
+
* internal /compact) — but a resume must NOT trigger a compact. The original
|
|
155
|
+
* blind ['Down','Enter'] burst (0e67e1f) put BOTH keys into one pty chunk;
|
|
156
|
+
* under a heavy resume (a 313k-token session) the TUI dropped the Down and
|
|
157
|
+
* the Enter confirmed the DEFAULT →
|
|
158
158
|
* silent /compact (proven: pane log shows the picker frame with ❯ on
|
|
159
159
|
* option 1 followed by "❯ /compact"; transcript compactMetadata
|
|
160
160
|
* trigger=manual). Fix: ONE key per boot-iteration — Down while the cursor
|
|
@@ -256,7 +256,7 @@ export const claudeAdapter: RuntimeAdapter = {
|
|
|
256
256
|
* + docs/TUI-взаимодействие):
|
|
257
257
|
* - interrupt → ['Escape'] (claude interrupts the current turn with ONE Escape;
|
|
258
258
|
* the session + context stay intact — distinct from the `stop` verb which halts
|
|
259
|
-
* the session).
|
|
259
|
+
* the session). Interrupts a runaway turn without losing context.
|
|
260
260
|
* - compact → type '/compact' then Enter (claude's context-compaction slash).
|
|
261
261
|
* - anything else → null (unsupported → explicit refusal upstream).
|
|
262
262
|
*/
|
|
@@ -154,8 +154,7 @@ export const codexAdapter: RuntimeAdapter = {
|
|
|
154
154
|
usesDoctrine: true,
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* Delivery markers for submitIntoTui (
|
|
158
|
-
* "Доставка"):
|
|
157
|
+
* Delivery markers for submitIntoTui (docs/Рантайм codex "Доставка"):
|
|
159
158
|
* - promptGlyphs ['›'] — codex's input-row arrow (the same stable invariant as
|
|
160
159
|
* isInputReady's '› ' check; the exact glyph the prompt row starts with).
|
|
161
160
|
* codex-only, so a stray claude '❯' no longer false-matches.
|
|
@@ -234,12 +233,12 @@ export const codexAdapter: RuntimeAdapter = {
|
|
|
234
233
|
* the input arrow is the stable ready invariant — not any single tip.)
|
|
235
234
|
*
|
|
236
235
|
* DELIBERATELY no 'OpenAI Codex' splash check (the watcher.codexInputReady
|
|
237
|
-
* heritage requirement, removed
|
|
236
|
+
* heritage requirement, since removed): `resume --last` replays the prior
|
|
238
237
|
* transcript ABOVE the composer, and a history taller than the pane scrolls
|
|
239
238
|
* the splash box off the top of capture-pane — the predicate then stays false
|
|
240
239
|
* for the entire boot deadline while the composer sits ready, failing the wake
|
|
241
|
-
* `never-became-ready` and LOSING the triggering message (live
|
|
242
|
-
*
|
|
240
|
+
* `never-became-ready` and LOSING the triggering message (observed live with a
|
|
241
|
+
* tall replayed history). A replayed history message also
|
|
243
242
|
* renders with a leading '› ', so this can fire a frame before the composer
|
|
244
243
|
* activates — benign: the paste/Enter queue in the pty and land in the
|
|
245
244
|
* composer, and the ready-gate still verifies a real model turn (activity
|
|
@@ -302,7 +301,7 @@ export const codexAdapter: RuntimeAdapter = {
|
|
|
302
301
|
* interrupts the turn — NOT a double. (Codex's own footer says "esc to
|
|
303
302
|
* interrupt"; one Escape yields "■ Conversation interrupted". The contract's
|
|
304
303
|
* open "×1/×2?" is resolved to ×1, same as claude.) Session + context intact.
|
|
305
|
-
* - compact → type '/compact' then Enter. SNAPPED LIVE
|
|
304
|
+
* - compact → type '/compact' then Enter. SNAPPED LIVE (codex-cli
|
|
306
305
|
* 0.138.0): the slash EXISTS now — autocomplete documents it («summarize
|
|
307
306
|
* conversation to prevent hitting the context limit») and a live run
|
|
308
307
|
* yields «• Context compacted». The 0.136-era "codex has no /compact"
|
package/src/launch/canary.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// Server-death canary — the SERVER-level catcher for the exit-cause gap the
|
|
2
2
|
// pane-died hook structurally cannot close (контракт: pane-died needs a living
|
|
3
3
|
// tmux event loop; SIGKILL/OOM to the tmux SERVER runs no hook). Raised from the
|
|
4
|
-
// deferred backlog after the SECOND live case of the class (
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// deferred backlog after the SECOND live case of the class (both: a healthy
|
|
5
|
+
// session with active Bash subprocessing, the whole server gone silently, zero
|
|
6
|
+
// system traces).
|
|
7
7
|
//
|
|
8
8
|
// Mechanism: one tiny detached `sh` per LIVE tmux server, holding a blocking
|
|
9
9
|
// client `tmux -S <sock> wait-for iap-canary-<identity>` — a process OUTSIDE the
|
|
10
10
|
// dying server, connected via its socket, so the server's death (any cause,
|
|
11
11
|
// including SIGKILL) is observed the moment the connection drops. Protocol (v2 —
|
|
12
|
-
//
|
|
12
|
+
// a live death proved v1's exit-code trust wrong twice over: the killer
|
|
13
13
|
// took the SERVER and the canary CLIENT together, AND a TERMed tmux client
|
|
14
14
|
// returns rc=0 — indistinguishable from a clean channel signal — so the rc-based
|
|
15
15
|
// `0 || ≥128 → silent` guard silenced a real death):
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
// into exits.log (the per-peer death-cause home, next to pane-died's
|
|
27
27
|
// `ev=session-exit`) + a forensics snapshot (vm_stat / swap / top-RSS ps /
|
|
28
28
|
// fresh DiagnosticReports) captured within seconds — the evidence the 60 s
|
|
29
|
-
// supervise tick can never recover (
|
|
29
|
+
// supervise tick can never recover (zero system traces was the recurring
|
|
30
30
|
// investigation outcome). PID identity matters: a wake/attach can replace the
|
|
31
31
|
// tmux server on the same socket before the 2 s grace elapses; probing only
|
|
32
32
|
// the socket would mistake the successor for the dead original and lose the
|
|
33
|
-
// canary record (live canary-gap
|
|
33
|
+
// canary record (a live canary-gap).
|
|
34
34
|
// The raw wait_rc still ATTRIBUTES the death (cause=server-vanished /
|
|
35
35
|
// signaled-server-gone / client-killed-server-gone).
|
|
36
36
|
// Residual blind spot (structural): a killer that SIGKILLs the sh recorder
|
|
@@ -103,7 +103,7 @@ export function canaryScript(o: CanaryScriptOptions): string {
|
|
|
103
103
|
`'${o.tmuxBin}' -S '${o.sock}' wait-for '${ch}'`,
|
|
104
104
|
`rc=$?`,
|
|
105
105
|
// NO wait-for exit code is trusted as "deliberate" by itself — PROVEN live
|
|
106
|
-
// (death
|
|
106
|
+
// (a death postmortem): a TERM to the tmux CLIENT returns rc=0, identical
|
|
107
107
|
// to a clean channel signal, so an external killer sweeping server+client
|
|
108
108
|
// rides the clean-looking code straight past any rc-based guard (v1's
|
|
109
109
|
// `rc=0 || rc>=128 → silent` was exactly that hole). v2 contract instead:
|
|
@@ -217,7 +217,7 @@ export function signalCanaryClean(sock: string, identity: string): void {
|
|
|
217
217
|
/** The pgrep/pkill ERE matching BOTH canary processes of ONE identity — the sh
|
|
218
218
|
* wrapper (its -c script quotes the channel: `…'iap-canary-<id>'…`) and the
|
|
219
219
|
* tmux client (argv ends with the bare channel). Anchored so an identity can
|
|
220
|
-
* never match another identity's prefix (claude-
|
|
220
|
+
* never match another identity's prefix (claude-alice ≠ claude-alice-bob). */
|
|
221
221
|
export function canaryProcessPattern(identity: string): string {
|
|
222
222
|
return `${canaryChannel(identity)}([^a-z0-9-]|$)`
|
|
223
223
|
}
|
package/src/launch/cmdlog.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// tmux command-log — the SOCKET-COMMAND observability layer of the killer hunt.
|
|
2
2
|
//
|
|
3
|
-
// Why (
|
|
3
|
+
// Why (a session-death class): a tmux server killed by a
|
|
4
4
|
// CLIENT COMMAND (`kill-server` / `kill-session` over the unix socket) dies with
|
|
5
5
|
// zero signals — structurally invisible to ALL THREE existing layers: the eslogger
|
|
6
6
|
// trap (no kill(2) anywhere; kernel-originated SIGHUP to the panes is also proven
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// the attribution (client pid + exact command + timestamp) that decides the class
|
|
12
12
|
// binarily: "killed by client X" vs "no command ⇒ internal exit".
|
|
13
13
|
//
|
|
14
|
-
// Sensitivity
|
|
14
|
+
// Sensitivity: the -v log carries FULL send-keys payloads
|
|
15
15
|
// (every delivered envelope, plaintext) — tmux has no commands-only level and the
|
|
16
16
|
// server writes the file itself (name tmux-server-<pid>.log in the STARTING
|
|
17
17
|
// client's cwd — no FIFO/pipe interposition possible). Containment instead:
|
|
@@ -63,7 +63,7 @@ export function prepareCmdLogDir(eventLogDir: string, identity: string): string
|
|
|
63
63
|
|
|
64
64
|
/** Clamp every file the freshly-started server/client dropped into the dir to
|
|
65
65
|
* 0600 (the dir's 0700 already blocks traversal; this is the belt to that
|
|
66
|
-
* suspender —
|
|
66
|
+
* suspender — the acceptance criterion: no file wider than 0600). */
|
|
67
67
|
export function hardenCmdLogDir(dir: string): void {
|
|
68
68
|
try {
|
|
69
69
|
for (const name of readdirSync(dir)) {
|
|
@@ -158,7 +158,8 @@ function renderRegistry(peers: readonly PublicPeerSummary[]): string {
|
|
|
158
158
|
// • Layer 4: every non-IAPEER `<DOMAIN>.md` pair at the .iapeer/ root (operator /
|
|
159
159
|
// plugin user-settings; SPAWNER_INSTRUCTIONS.md, … flow in organically).
|
|
160
160
|
// • Layer 5: every `<STEM>.md` pair in .iapeer/fragments/ (primitive-owned,
|
|
161
|
-
// machine-regenerated doctrine fragments —
|
|
161
|
+
// machine-regenerated doctrine fragments — e.g. a memory provider's guide +
|
|
162
|
+
// note index).
|
|
162
163
|
// Each block contributes its GLOBAL half then its LOCAL half (general → specific),
|
|
163
164
|
// EACH as its own marked file-section (a 0-byte / newline-only file → no section,
|
|
164
165
|
// no dangling marker). Blocks are appended in the order given (the gatherer sorts
|
package/src/launch/index.ts
CHANGED
|
@@ -143,8 +143,8 @@ function ready(identity: string): LaunchResult {
|
|
|
143
143
|
// Exit-cause observability — capture WHY a session's process died, AT THE MOMENT
|
|
144
144
|
// of death. The daemon's 60 s supervise tick only learns of a death post-factum
|
|
145
145
|
// (reaped-gone), by which time the exit code/signal — and often the whole tmux
|
|
146
|
-
// server — is gone (
|
|
147
|
-
//
|
|
146
|
+
// server — is gone (a blind spot one level deeper than the supervise log). A
|
|
147
|
+
// tmux `pane-died` hook closes it: it fires the instant the
|
|
148
148
|
// pane's leader process exits (with `remain-on-exit on` retaining the dead pane so
|
|
149
149
|
// `#{pane_dead_status}`/`#{pane_dead_signal}` are populated), logs one logfmt line,
|
|
150
150
|
// then kill-sessions the now-dead pane so the daemon's `has-session` death
|
|
@@ -394,14 +394,13 @@ export const launch: LaunchFn = async (
|
|
|
394
394
|
// `run-shell -b -d <maxAgeSecs>` → `kill-session`, intended to bound a zombie
|
|
395
395
|
// even if no supervise sweep runs. It was a BLIND wall-clock kill armed ONCE
|
|
396
396
|
// at birth and never re-armed, so it guillotined HEALTHY long-lived sessions
|
|
397
|
-
// at exactly birth+maxAge (4 h) — the recurrent
|
|
397
|
+
// at exactly birth+maxAge (4 h) — the recurrent "mystery deaths".
|
|
398
398
|
// By construction any session that survives to maxAge is active: idle ones are
|
|
399
399
|
// already idle-reaped at idleSecs (1 h) by the activity-aware supervisor — so
|
|
400
400
|
// the blind kill could ONLY ever destroy live work (its sole "useful" case,
|
|
401
401
|
// daemon dead the whole interval, leaves an inert orphan nothing can deliver
|
|
402
402
|
// to, self-healed by idle-reap on daemon restart). The activity-aware idle-reap
|
|
403
|
-
// (superviseTick) is now the SOLE lifecycle bound.
|
|
404
|
-
// note «Четырёхчасовой TTL-backstop …». (alwaysOn infra never armed it.)
|
|
403
|
+
// (superviseTick) is now the SOLE lifecycle bound. (alwaysOn infra never armed it.)
|
|
405
404
|
|
|
406
405
|
// (5) Router runtime (telegram): no TUI input surface — there is no boot dialog
|
|
407
406
|
// and no ready marker to gate on. The process is up; return READY.
|
|
@@ -488,7 +487,7 @@ export const launch: LaunchFn = async (
|
|
|
488
487
|
}
|
|
489
488
|
}
|
|
490
489
|
if (!delivered) {
|
|
491
|
-
// Surface WHAT the pane showed when the gate gave up —
|
|
490
|
+
// Surface WHAT the pane showed when the gate gave up — a codex-resume
|
|
492
491
|
// incident (splash scrolled off, composer ready, predicate false for 240 s) was
|
|
493
492
|
// undiagnosable from the bare reason alone. Last two non-empty lines, quotes
|
|
494
493
|
// flattened (the reason lands inside a logfmt-quoted err field), clipped.
|
package/src/launch/invocation.ts
CHANGED
|
@@ -50,7 +50,7 @@ export function buildLaunchInvocation(
|
|
|
50
50
|
PEER_PERSONALITY: spec.personality,
|
|
51
51
|
PEER_RUNTIME: spec.runtime,
|
|
52
52
|
PEER_IDENTITY: spec.identity,
|
|
53
|
-
PATH: env.PATH ?? `${homedir()}/.bun/bin:${homedir()}/.local/bin:/opt/homebrew/bin:/usr/bin:/bin`,
|
|
53
|
+
PATH: env.PATH ?? `${homedir()}/.bun/bin:${homedir()}/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin`,
|
|
54
54
|
}
|
|
55
55
|
// Strip the claude-code-internal session namespace from the inherited env. When a launch is
|
|
56
56
|
// INITIATED from inside a running claude session (an operator CLI, or a claude peer shelling out),
|