@agfpd/iapeer 0.4.70 → 0.4.71

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer",
3
- "version": "0.4.70",
3
+ "version": "0.4.71",
4
4
  "description": "Run Claude Code and Codex CLI as local background agents that message each other, wake on demand, and share one memory.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "agfpd",
@@ -65,6 +65,27 @@ export function defaultIntelligenceForRuntime(runtime: string): Intelligence {
65
65
  return 'artificial'
66
66
  }
67
67
 
68
+ /**
69
+ * The intelligences a runtime may LEGITIMATELY carry (the single Ф0 source; provision
70
+ * validates against it, the launch gate enforces it). Identity (natural/absent/artificial)
71
+ * is ORTHOGONAL to the channel: a human-channel runtime can carry a HUMAN (natural) OR a
72
+ * FACELESS SERVICE bot (absent) — e.g. a Telegram approval-card bot — but never an LLM agent
73
+ * (artificial) on that channel. A service-only runtime carries services (absent). An agentic
74
+ * runtime carries an LLM (artificial). The DEFAULT (defaultIntelligenceForRuntime) stays the
75
+ * first of each set, so an existing peer with no explicit nature is unchanged — `absent` on a
76
+ * channel runtime is strictly OPT-IN (an explicit `--intelligence absent` / manifest decl).
77
+ */
78
+ export function allowedIntelligencesForRuntime(runtime: string): readonly Intelligence[] {
79
+ if (NATURAL_RUNTIMES.has(runtime)) return ['natural', 'absent']
80
+ if (ABSENT_RUNTIMES.has(runtime)) return ['absent']
81
+ return ['artificial']
82
+ }
83
+
84
+ /** Is `intelligence` a legitimate nature for `runtime`? (provision fail-loud + launch gate.) */
85
+ export function isIntelligenceAllowedForRuntime(runtime: string, intelligence: Intelligence): boolean {
86
+ return allowedIntelligencesForRuntime(runtime).includes(intelligence)
87
+ }
88
+
68
89
  // Infra runtimes are ALWAYS-ON (held live by launchd KeepAlive), as opposed to the
69
90
  // warm-on-demand agentic runtimes (claude/codex, woken by the daemon). Liveness is
70
91
  // a property of the RUNTIME, not the personality (zone Идентичность). Infra runtimes
@@ -11,8 +11,10 @@ import {
11
11
  IAPEER_DIR,
12
12
  PEER_PROFILE_FILE,
13
13
  NAME_RE,
14
+ allowedIntelligencesForRuntime,
14
15
  defaultIntelligenceForRuntime,
15
16
  isInfraRuntime,
17
+ isIntelligenceAllowedForRuntime,
16
18
  isRuntime,
17
19
  isValidName,
18
20
  normalizeIntelligenceValue,
@@ -131,6 +133,12 @@ export interface EnsurePeerProfileOptions {
131
133
  * always-on plist so launchd's minimal PATH resolves it. Forwarded to
132
134
  * installAlwaysOnPlist; ignored for warm-on-demand runtimes. */
133
135
  runtimeBin?: string
136
+ /** Explicit peer nature (natural/absent/artificial). OPT-IN override of the runtime
137
+ * default (defaultIntelligenceForRuntime): a channel runtime defaults `natural` but may be
138
+ * provisioned `absent` for a FACELESS SERVICE bot (e.g. a Telegram approval-card bot).
139
+ * Validated against allowedIntelligencesForRuntime — an out-of-set value fails LOUD. Absent
140
+ * (undefined) ⇒ the runtime default (unchanged for every existing peer). */
141
+ intelligence?: Intelligence
134
142
  /** В36 — peer description, persisted into the LOCAL profile (the source of truth the
135
143
  * registry is reindexed FROM). Writing it only to the registry made it a stale copy:
136
144
  * the first reindexFromLocals (addRuntime on a dual-runtime create, connect telegram,
@@ -503,6 +511,21 @@ export function ensurePeerProfile(options: EnsurePeerProfileOptions): PeerProfil
503
511
  const peers = options.peers ?? []
504
512
  ensureLocalIapScaffold(cwd)
505
513
  const discoveredRuntimes = discoverPeerRuntimes(cwd, options.runtime)
514
+ // Nature = the EXPLICIT intelligence when supplied (validated against the runtime's allowed
515
+ // set — an out-of-set value, e.g. artificial on telegram, fails LOUD), else the runtime
516
+ // default. This is where a FACELESS SERVICE bot (absent) on a channel runtime becomes
517
+ // first-class (docs/Идентичность); an omitted intelligence keeps every existing peer's
518
+ // default nature unchanged.
519
+ const resolvedIntelligence = (rt: Runtime): Intelligence => {
520
+ const explicit = options.intelligence
521
+ if (explicit === undefined) return defaultIntelligenceForRuntime(rt)
522
+ if (!isIntelligenceAllowedForRuntime(rt, explicit)) {
523
+ throw new IapError(
524
+ `intelligence "${explicit}" is not valid for runtime "${rt}" — allowed: ${allowedIntelligencesForRuntime(rt).join(', ')}`,
525
+ )
526
+ }
527
+ return explicit
528
+ }
506
529
  const existing = readPeerProfile(cwd)
507
530
  if (!existing) {
508
531
  let personality: string
@@ -550,7 +573,7 @@ export function ensurePeerProfile(options: EnsurePeerProfileOptions): PeerProfil
550
573
  // В36 — the description lands in the LOCAL profile at birth (source of truth);
551
574
  // the registry row is a projection of this file, not the other way around.
552
575
  description: options.description?.trim() ?? '',
553
- intelligence: defaultIntelligenceForRuntime(options.runtime),
576
+ intelligence: resolvedIntelligence(options.runtime),
554
577
  }
555
578
  ensureLocalRuntimeScopes(cwd, profile.runtimes)
556
579
  // INFRA runtime → provision the always-on launchd plist that holds it live.
@@ -607,7 +630,10 @@ export function ensurePeerProfile(options: EnsurePeerProfileOptions): PeerProfil
607
630
  runtimeBin: options.runtimeBin,
608
631
  env: options.env,
609
632
  })
610
- intelligence = defaultIntelligenceForRuntime(options.runtime)
633
+ // Re-provision honors an EXPLICIT nature (a manifest-declared absent service bot survives
634
+ // update-runtime re-provision), else the runtime default (the legacy vocab-flip this fix
635
+ // preserved). resolvedIntelligence validates the explicit against the runtime's allowed set.
636
+ intelligence = resolvedIntelligence(options.runtime)
611
637
  }
612
638
  // В36 — an explicit description on RE-provision updates the local profile too (H1 in
613
639
  // writePeerProfileAtomic still guards: absent/empty never wipes an existing value).
@@ -23,6 +23,7 @@
23
23
  // that is install-time (blueprint §0.6 fast-wake), not session bring-up.
24
24
 
25
25
  import type { ControlCommand, ControlPlan, LaunchAdapterConfig, LaunchSpec, RuntimeAdapter } from '../types.ts'
26
+ import { allowedIntelligencesForRuntime } from '../../core/constants.ts'
26
27
 
27
28
  // ─────────────────────────────────────────────────────────────────────────────
28
29
  // telegramAdapter
@@ -38,13 +39,14 @@ export const telegramAdapter: RuntimeAdapter = {
38
39
  deliveryMarkers: { promptGlyphs: [] },
39
40
 
40
41
  /**
41
- * telegram is a HUMAN channel — launch REFUSES a non-natural peer (fail-loud).
42
- * Porting the persistent-peer FATAL guard (it stood in two places): a peer with
43
- * intelligence artificial/absent must never be brought up on telegram (it would
44
- * route a human's bot to an agent/script). The launch primitive enforces this
45
- * against LaunchSpec.intelligence; source of intelligence → docs/Идентичность.
42
+ * telegram carries a HUMAN (natural) OR a FACELESS SERVICE bot (absent) — but never an
43
+ * LLM agent (artificial), which would route a human's channel to an agent/script. Launch
44
+ * REFUSES anything outside this set (fail-loud), the persistent-peer FATAL guard relaxed to
45
+ * first-class faceless service bots (e.g. a Telegram approval-card bot). Sourced from the
46
+ * single Ф0 truth (allowedIntelligencesForRuntime); enforced by the launch primitive against
47
+ * LaunchSpec.intelligence. Source of intelligence → docs/Идентичность.
46
48
  */
47
- requiresIntelligence: 'natural',
49
+ allowedIntelligences: allowedIntelligencesForRuntime('telegram'),
48
50
 
49
51
  /**
50
52
  * argv = telegramBin + 'run' + extraArgs (telegram-start.sh:119,
@@ -9,7 +9,7 @@
9
9
  //
10
10
  // voicetalk is a presence-runtime for a HUMAN (the owner's voice channel), modelled on
11
11
  // telegram per the foundation presence-runtime contract: kind:'router',
12
- // usesDoctrine:false, requiresIntelligence:'natural', argv = `<bin> run <extra>`. The
12
+ // usesDoctrine:false, allowedIntelligences:['natural','absent'], argv = `<bin> run <extra>`. The
13
13
  // STT/TTS engines and providers live in a shared voice service (voice-connect), NOT in
14
14
  // this adapter and NOT in the peer passport — interfaces.voicetalk carries only the
15
15
  // binding ({wake_word, device}), opaque to the foundation.
@@ -19,6 +19,7 @@
19
19
  // bring-up.
20
20
 
21
21
  import type { ControlCommand, ControlPlan, LaunchAdapterConfig, LaunchSpec, RuntimeAdapter } from '../types.ts'
22
+ import { allowedIntelligencesForRuntime } from '../../core/constants.ts'
22
23
 
23
24
  // ─────────────────────────────────────────────────────────────────────────────
24
25
  // voicetalkAdapter
@@ -34,13 +35,12 @@ export const voicetalkAdapter: RuntimeAdapter = {
34
35
  deliveryMarkers: { promptGlyphs: [] },
35
36
 
36
37
  /**
37
- * voicetalk is a HUMAN channel — launch REFUSES a non-natural peer (fail-loud).
38
- * Same nature gate as telegram: a peer with intelligence artificial/absent must never
39
- * be brought up on voicetalk (it would route the owner's voice channel to an
40
- * agent/script). The launch primitive enforces this against LaunchSpec.intelligence;
41
- * source of intelligence → docs/Идентичность.
38
+ * voicetalk carries a HUMAN (natural) OR a FACELESS SERVICE bot (absent) — never an LLM
39
+ * agent (artificial), which would route the owner's voice channel to an agent/script. Same
40
+ * nature gate as telegram, sourced from the single Ф0 truth (allowedIntelligencesForRuntime);
41
+ * enforced by the launch primitive against LaunchSpec.intelligence. Source → docs/Идентичность.
42
42
  */
43
- requiresIntelligence: 'natural',
43
+ allowedIntelligences: allowedIntelligencesForRuntime('voicetalk'),
44
44
 
45
45
  /**
46
46
  * argv = voicetalkBin + 'run' + extraArgs.
@@ -131,14 +131,16 @@ export const launch: LaunchFn = async (
131
131
  const { identity, socketPath: sock, cwd } = spec
132
132
  const env = cfg.env ?? process.env
133
133
 
134
- // (0) Intelligence gate (fail-loud BEFORE any tmux work): an adapter that declares
135
- // requiresIntelligence (telegram → 'natural') refuses a peer whose nature does
136
- // not match — and refuses too when the nature is unknown (cannot confirm). Ports
137
- // the persistent-peer FATAL human-channel guard (docs/Рантайм-адаптеры).
138
- if (adapter.requiresIntelligence && spec.intelligence !== adapter.requiresIntelligence) {
134
+ // (0) Intelligence gate (fail-loud BEFORE any bring-up work): an adapter that declares
135
+ // allowedIntelligences (telegram/voicetalk → ['natural','absent']) refuses a peer whose
136
+ // nature is not IN the set — and refuses too when the nature is unknown (cannot confirm).
137
+ // A channel runtime carries a human (natural) OR a faceless service bot (absent), never an
138
+ // LLM agent (artificial). Ports the persistent-peer FATAL human-channel guard, relaxed to
139
+ // first-class faceless service bots (docs/Рантайм-адаптеры).
140
+ if (adapter.allowedIntelligences && (spec.intelligence == null || !adapter.allowedIntelligences.includes(spec.intelligence))) {
139
141
  return fail(
140
142
  identity,
141
- `runtime "${spec.runtime}" requires intelligence=${adapter.requiresIntelligence}, ` +
143
+ `runtime "${spec.runtime}" requires intelligence ∈ {${adapter.allowedIntelligences.join(', ')}}, ` +
142
144
  `peer "${spec.personality}" is ${spec.intelligence ?? 'unknown'} — refusing to launch`,
143
145
  )
144
146
  }
@@ -160,9 +160,9 @@ export interface LaunchSpec {
160
160
  /** Free-form extra CLI args (PEER_START_ARGS). */
161
161
  extraArgs?: string[]
162
162
  /** Peer intelligence (artificial/natural/absent). Used to enforce an adapter's
163
- * intelligence gate at launch (telegram requires natural). Optional: a doctrine-
164
- * less throwaway may omit it, but an adapter that declares requiresIntelligence
165
- * then refuses (cannot confirm the required nature). */
163
+ * intelligence gate at launch (telegram allows natural|absent). Optional: a doctrine-
164
+ * less throwaway may omit it, but an adapter that declares allowedIntelligences
165
+ * then refuses (cannot confirm the nature is in the set). */
166
166
  intelligence?: Intelligence
167
167
  /** Human-approval mode (docs/17): `yolo` (default/omitted) launches with the
168
168
  * runtime bypass flag (current fleet behavior); `gated` launches WITHOUT bypass
@@ -273,13 +273,14 @@ export interface RuntimeAdapter {
273
273
  deliveryConfirm?: 'transcript' | 'socket-ack'
274
274
 
275
275
  /**
276
- * If set, launch REFUSES unless the peer's intelligence equals this value
277
- * (fail-loud). telegram → 'natural' — it is a human channel; launching an
278
- * artificial/absent peer on it is a category error (the persistent-peer path held
279
- * a FATAL guard in two places). Most adapters omit it (no nature gate). Source of intelligence →
276
+ * If set, launch REFUSES unless the peer's intelligence is IN this set (fail-loud —
277
+ * also when the nature is unknown). A channel runtime carries a HUMAN (natural) OR a
278
+ * FACELESS SERVICE bot (absent) but never an LLM agent (artificial): telegram/voicetalk →
279
+ * ['natural','absent'] (sourced from allowedIntelligencesForRuntime — the single Ф0 truth).
280
+ * Most adapters omit it (no nature gate — claude/codex/notifier). Source of intelligence →
280
281
  * docs/Идентичность; enforced by the launch primitive against LaunchSpec.intelligence.
281
282
  */
282
- requiresIntelligence?: Intelligence
283
+ allowedIntelligences?: readonly Intelligence[]
283
284
 
284
285
  /**
285
286
  * Build the runtime argv: binary + flags, wiring systemPromptFile per-runtime
@@ -89,6 +89,11 @@ export async function provisionPeer(opts: ProvisionPeerOptions): Promise<Provisi
89
89
  peers,
90
90
  personality: opts.personality,
91
91
  runtimeBin,
92
+ // The EXPLICIT nature now reaches the LOCAL profile too (A1) — previously it landed only
93
+ // in the registry upsert below, so a `--intelligence absent` telegram bot got a natural
94
+ // local profile (split-brain) and its plist crash-looped the natural gate. Validated
95
+ // against the runtime's allowed set inside ensurePeerProfile.
96
+ intelligence: opts.intelligence,
92
97
  // В36 — description goes into the LOCAL profile (source of truth); the registry
93
98
  // upsert below then persists a value reindexFromLocals can no longer wipe.
94
99
  description: opts.description,