@agfpd/iapeer 0.4.101 → 0.4.102
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/docs/06-storage.md
CHANGED
|
@@ -95,7 +95,7 @@ A peer's system prompt points here — `~/.iapeer/docs/`, one folder per package
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
- **`peer-profile.json`** — the peer's card and the single source of truth about it (the fields are covered in [03 — Peers, runtimes, identity](03-peers-runtimes-identity.md)).
|
|
98
|
-
- **`launch.env`** — a `KEY=VALUE` file for a specific peer+runtime pair: extra launch arguments (`PEER_START_ARGS`) and environment variables. So one peer can be given special launch flags without touching the rest.
|
|
98
|
+
- **`launch.env`** — a `KEY=VALUE` file for a specific peer+runtime pair: extra launch arguments (`PEER_START_ARGS`) and environment variables. So one peer can be given special launch flags without touching the rest. `create`/`init` scaffold it as an all-comment skeleton for every agentic runtime scope (behaves exactly like no file until edited), so fleet-wide launch.env operations never skip a peer for lack of the file; an existing file is never overwritten.
|
|
99
99
|
|
|
100
100
|
A peer's working folder doesn't have to live under `~/.iapeer/peers/`. By default `iapeer create` puts it there, but any folder can be made a peer with `iapeer init` — then `.iapeer/` appears right inside it.
|
|
101
101
|
|
package/docs/19-notices.md
CHANGED
|
@@ -124,6 +124,46 @@ a stable *transition* clock, and the `updated_at_ms > since` boundary fires exac
|
|
|
124
124
|
then falls behind forever. A goal blocked while the daemon was down is missed — the same
|
|
125
125
|
honest boundary `peer-mute` already has (§7).
|
|
126
126
|
|
|
127
|
+
**Only the peer's CURRENT thread counts.** `thread_goals` is keyed by thread_id, so a thread the
|
|
128
|
+
peer has abandoned keeps its last goal row forever. Measured live 16.07.2026: a peer escaped a
|
|
129
|
+
blocked goal the only way its tools allowed — a fresh session — and ended up with two rows,
|
|
130
|
+
`blocked` on the dead thread and `active` on the live one. Reporting the fossil would tell the
|
|
131
|
+
owner an objective is stalled while the peer is working it. So a goal is attributed only on the
|
|
132
|
+
cwd's newest thread (`recency_at_ms`); a fossil is silent.
|
|
133
|
+
|
|
134
|
+
### Recovery — `iapeer goal <peer> <resume|clear|pause>`
|
|
135
|
+
|
|
136
|
+
A notice states a fact the peer cannot state. For `peer-goal-stalled` the owner then hits a second
|
|
137
|
+
wall: **the peer cannot fix it either.** Codex splits goal control, and the model's half has no
|
|
138
|
+
exit — `update_goal` may only mark complete|blocked ("pause, resume, budget-limited, and
|
|
139
|
+
usage-limited status changes are controlled by the user or system"), and `create_goal` is refused
|
|
140
|
+
while a goal is unfinished. An agent peer whose goal blocks can only lie (mark an unachieved
|
|
141
|
+
objective `complete`, which its own prompt forbids) or restart its session and lose the thread.
|
|
142
|
+
|
|
143
|
+
The transition out exists — it is human-only. The TUI's `/goal [<objective>|clear|edit|pause|
|
|
144
|
+
resume]` calls `thread/goal/set`, an unconditional upsert that sets Active from any status, and
|
|
145
|
+
`thread_goal_actions.rs` treats `Paused | Blocked | UsageLimited` as resumable. The daemon is the
|
|
146
|
+
peer's keyboard, so the verb presses it:
|
|
147
|
+
|
|
148
|
+
| sub | effect |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `resume` | the SAME goal, `blocked` → `active`; codex resumes injecting its own continuation turns |
|
|
151
|
+
| `clear` | the goal is removed ⇒ the peer's OWN `create_goal` stops being refused — the supported route to a *new* objective, authored by the agent rather than by us |
|
|
152
|
+
| `pause` | operator park |
|
|
153
|
+
|
|
154
|
+
`/goal <objective>` is deliberately NOT offered: replacing an unfinished goal opens a "Replace
|
|
155
|
+
goal?" modal, and blind-typing past a modal is how the wrong option gets picked. Subcommands are a
|
|
156
|
+
closed whitelist — caller text is never interpolated into a keystroke stream. A claude peer returns
|
|
157
|
+
`unsupported` (it has no thread goals; `/goal` would land as literal text in its composer).
|
|
158
|
+
|
|
159
|
+
**Success is gated on codex's goal store, never on keystrokes being accepted** — a pty write proves
|
|
160
|
+
the bytes left us, not that the TUI acted (§the delivery contour learned this the hard way). The
|
|
161
|
+
verb reads the goal row before and after and reports the transition it can see; a session that took
|
|
162
|
+
the text but never acted returns `unchanged`, not success. iapeer only ever READS codex's sqlite.
|
|
163
|
+
|
|
164
|
+
Verified live 16.07.2026 on a real codex peer: `blocked → active` in ~1 s, same `goal_id`, same
|
|
165
|
+
thread, no self-fresh, and auto-continuation genuinely resumed.
|
|
166
|
+
|
|
127
167
|
## 3. What the owner is told — and what is deliberately withheld
|
|
128
168
|
|
|
129
169
|
A notice states who, which runtime, which error type, which model, and when the wall lifts.
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
- **`peer-profile.json`** — карточка пира и единственный источник истины о нём (поля разобраны в [03 — Пиры, рантаймы, идентичность](03-пиры-рантаймы-идентичность.md)).
|
|
98
|
-
- **`launch.env`** — файл вида `KEY=VALUE` для конкретной пары пир+рантайм: дополнительные аргументы запуска (`PEER_START_ARGS`) и переменные окружения. Так одному пиру можно задать особые флаги запуска, не трогая остальных.
|
|
98
|
+
- **`launch.env`** — файл вида `KEY=VALUE` для конкретной пары пир+рантайм: дополнительные аргументы запуска (`PEER_START_ARGS`) и переменные окружения. Так одному пиру можно задать особые флаги запуска, не трогая остальных. `create`/`init` скаффолдят его как скелет из одних комментариев для каждого агентского рантайм-скоупа (до правки ведёт себя ровно как отсутствие файла), поэтому массовые операции над launch.env не пропускают пира из-за отсутствия файла; существующий файл никогда не перезаписывается.
|
|
99
99
|
|
|
100
100
|
Рабочая папка пира не обязана лежать в `~/.iapeer/peers/`. По умолчанию `iapeer create` кладёт её туда, но пиром можно сделать любую папку командой `iapeer init` — тогда `.iapeer/` появится прямо в ней.
|
|
101
101
|
|
package/package.json
CHANGED
package/src/storage/index.ts
CHANGED
|
@@ -197,6 +197,10 @@ export function ensureLocalRuntimeScopes(cwd: string, runtimes: readonly Runtime
|
|
|
197
197
|
mkdirSync(root, { recursive: true, mode: DIR_MODE })
|
|
198
198
|
for (const runtime of supportedLocalRuntimeScopes(runtimes)) {
|
|
199
199
|
mkdirSync(join(root, runtime, PLUGINS_DIR, IAP_PLUGIN_DIR), { recursive: true, mode: DIR_MODE })
|
|
200
|
+
// Default launch.env skeleton — every agentic runtime scope carries the knob
|
|
201
|
+
// from birth, so fleet-wide launch.env operations (e.g. mass PEER_START_ARGS
|
|
202
|
+
// edits) never skip a peer for lack of the file. Existing files are untouched.
|
|
203
|
+
ensureLaunchEnv(cwd, runtime)
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
|
|
@@ -207,11 +211,45 @@ export function ensureLocalRuntimeScopes(cwd: string, runtimes: readonly Runtime
|
|
|
207
211
|
export const LAUNCH_ENV_FILE = 'launch.env'
|
|
208
212
|
|
|
209
213
|
/** `<cwd>/.iapeer/runtimes/<runtime>/launch.env` — the per-peer per-runtime launch
|
|
210
|
-
* fragment (PEER_START_ARGS + extra peer env).
|
|
214
|
+
* fragment (PEER_START_ARGS + extra peer env). Scaffolded as an all-comment
|
|
215
|
+
* skeleton by create/init (ensureLocalRuntimeScopes) for every agentic runtime
|
|
216
|
+
* scope; the operator uncomments/edits it. Read at launch; missing → empty. */
|
|
211
217
|
export function peerLaunchEnvPath(cwd: string, runtime: Runtime): string {
|
|
212
218
|
return join(cwd, IAPEER_DIR, RUNTIMES_DIR, runtime, LAUNCH_ENV_FILE)
|
|
213
219
|
}
|
|
214
220
|
|
|
221
|
+
/** The default launch.env skeleton. ALL lines are comments — deliberately: it must
|
|
222
|
+
* parse to exactly the missing-file semantics ({startArgs: [], env: {}}, and
|
|
223
|
+
* crucially NO PEER_DISALLOWED_TOOLS key, whose present-empty ≠ absent is the
|
|
224
|
+
* opt-in gate). No trailing comments on the example lines — the parser keeps
|
|
225
|
+
* everything after `=` as the value, so `KEY=x # note` would poison the value. */
|
|
226
|
+
export const LAUNCH_ENV_TEMPLATE = `# launch.env — optional per-peer per-runtime launch overrides (iapeer, docs/06-storage).
|
|
227
|
+
# Format: one KEY=VALUE per line (bash-style; an \`export \` prefix is allowed).
|
|
228
|
+
# Blank lines and lines starting with # are ignored. Do NOT put a trailing
|
|
229
|
+
# comment after a value — everything after \`=\` is taken verbatim as the value.
|
|
230
|
+
# This skeleton is all comments: it behaves exactly like no file at all.
|
|
231
|
+
#
|
|
232
|
+
# Extra CLI args appended after the runtime's base argv (whitespace-split):
|
|
233
|
+
#PEER_START_ARGS=""
|
|
234
|
+
#
|
|
235
|
+
# claude only — --disallowedTools override. Key absent = adapter default for the
|
|
236
|
+
# whole fleet; present-empty ("") = omit the flag; a value = pass it verbatim:
|
|
237
|
+
#PEER_DISALLOWED_TOOLS=""
|
|
238
|
+
#
|
|
239
|
+
# Any other KEY=VALUE becomes an env var of the peer's session:
|
|
240
|
+
#MY_VAR=value
|
|
241
|
+
`
|
|
242
|
+
|
|
243
|
+
/** Create the default launch.env skeleton if (and only if) the file does not
|
|
244
|
+
* exist yet — an existing operator-edited file is NEVER touched. 0600 like the
|
|
245
|
+
* rest of the tree (an operator may later put secrets in it). */
|
|
246
|
+
export function ensureLaunchEnv(cwd: string, runtime: Runtime): void {
|
|
247
|
+
const path = peerLaunchEnvPath(cwd, runtime)
|
|
248
|
+
if (existsSync(path)) return
|
|
249
|
+
mkdirSync(dirname(path), { recursive: true, mode: DIR_MODE })
|
|
250
|
+
writeFileAtomic(path, LAUNCH_ENV_TEMPLATE)
|
|
251
|
+
}
|
|
252
|
+
|
|
215
253
|
export interface LaunchEnv {
|
|
216
254
|
/** Tokens from PEER_START_ARGS, appended AFTER the adapter's base argv flags. */
|
|
217
255
|
startArgs: string[]
|
|
@@ -232,9 +270,10 @@ export interface LaunchEnv {
|
|
|
232
270
|
* reproduced here with a whitespace split (faithful to that semantics; a value
|
|
233
271
|
* with embedded spaces was never one arg in the bash either). Surrounding single/
|
|
234
272
|
* double quotes around a value are stripped. Lines that are blank or start with
|
|
235
|
-
* `#` are ignored. A missing/unreadable file → empty (no flags, no env)
|
|
236
|
-
*
|
|
237
|
-
*
|
|
273
|
+
* `#` are ignored. A missing/unreadable file → empty (no flags, no env); the
|
|
274
|
+
* scaffolded all-comment skeleton (LAUNCH_ENV_TEMPLATE) parses to the same. This
|
|
275
|
+
* is a deliberately MINIMAL parser (assignments only — no command substitution, no
|
|
276
|
+
* conditionals); the format is a controlled convention, not arbitrary shell.
|
|
238
277
|
*/
|
|
239
278
|
export function readLaunchEnv(cwd: string, runtime: Runtime): LaunchEnv {
|
|
240
279
|
let text: string
|