@agfpd/iapeer 0.4.12 → 0.4.14
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/CHANGELOG.md +225 -0
- package/README.md +9 -7
- package/docs/03-peers-runtimes-identity.md +1 -1
- package/docs/04-lifecycle-and-daemon.md +1 -1
- package/docs/13-architecture-and-contract.md +1 -1
- package/docs/14-configuration-and-logs.md +2 -4
- package/docs/README.md +2 -2
- package/docs/ru/03-/320/277/320/270/321/200/321/213-/321/200/320/260/320/275/321/202/320/260/320/271/320/274/321/213-/320/270/320/264/320/265/320/275/321/202/320/270/321/207/320/275/320/276/321/201/321/202/321/214.md +1 -1
- package/docs/ru/04-/320/266/320/270/320/267/320/275/320/265/320/275/320/275/321/213/320/271-/321/206/320/270/320/272/320/273-/320/270-/320/264/320/265/320/274/320/276/320/275.md +1 -1
- package/docs/ru/13-/320/260/321/200/321/205/320/270/321/202/320/265/320/272/321/202/321/203/321/200/320/260-/320/270-/320/272/320/276/320/275/321/202/321/200/320/260/320/272/321/202.md +1 -1
- package/docs/ru/14-/320/272/320/276/320/275/321/204/320/270/320/263/321/203/321/200/320/260/321/206/320/270/321/217-/320/270-/320/266/321/203/321/200/320/275/320/260/320/273/321/213.md +2 -4
- package/docs/ru/README.md +2 -2
- package/package.json +2 -1
- package/src/cli/index.ts +10 -8
- package/src/identity/profileStandard.ts +23 -0
- package/src/transport/index.ts +12 -10
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **@agfpd/iapeer** are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
> The public repository begins at the **Initial public release (0.2.98, 2026-06-18)**.
|
|
9
|
+
> Versions before 0.2.98 predate the public repo (private development) and are not itemized here.
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Fact-actualized the repo docs against the current code: pty-only (tmux references removed),
|
|
16
|
+
no canary/forensics in postmortem diagnostics, and the legacy `runtime`-mirror drop.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- This changelog.
|
|
21
|
+
|
|
22
|
+
## [0.4.13] - 2026-06-20
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- `verify --fix` now strips the legacy `runtime` mirror from **every** profile that still
|
|
27
|
+
carries it, not only the ones that triggered a warning — completes the fleet-wide cleanup.
|
|
28
|
+
|
|
29
|
+
## [0.4.12] - 2026-06-20
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- `migrateProfileRuntimeField` strips any legacy `runtime` field, so `verify --fix` cleans
|
|
34
|
+
the whole fleet.
|
|
35
|
+
|
|
36
|
+
## [0.4.11] - 2026-06-20
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Local peer profiles no longer write the legacy `runtime` mirror — `default_runtime` is the
|
|
41
|
+
single field (Phase-3, local half). A read-fallback still understands older profiles that
|
|
42
|
+
carry only `runtime`.
|
|
43
|
+
|
|
44
|
+
## [0.4.10] - 2026-06-20
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- The registry (`peers-profiles.json`) no longer writes the legacy `runtime` mirror
|
|
49
|
+
(Phase-3, registry half).
|
|
50
|
+
|
|
51
|
+
### Removed
|
|
52
|
+
|
|
53
|
+
- Two proven-dead internal symbols (audit safe-cuts).
|
|
54
|
+
|
|
55
|
+
## [0.4.9] - 2026-06-20
|
|
56
|
+
|
|
57
|
+
### Removed
|
|
58
|
+
|
|
59
|
+
- **tmux removed entirely — the foundation is now pty-only.** The tmux launch / delivery /
|
|
60
|
+
occupancy paths and the retired tmux→pty burn-in observer are gone; session hosting runs
|
|
61
|
+
through the pty supervisor (which owns the pty, the pane-log, and the delivery socket).
|
|
62
|
+
`render.ts` is kept as a shared leaf.
|
|
63
|
+
|
|
64
|
+
## [0.4.8] - 2026-06-20
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- The supervisor self-heals a pane-log that was unlinked out from under a live session
|
|
69
|
+
(Defect 2).
|
|
70
|
+
|
|
71
|
+
## [0.4.7] - 2026-06-20
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- An unreadable hosted pane-log is no longer treated as composer-busy — fixes an IAP
|
|
76
|
+
delivery stall.
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
|
|
80
|
+
- Ping-pong hero demo GIF in the README.
|
|
81
|
+
|
|
82
|
+
## [0.4.6] - 2026-06-19
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- Unified omitted-runtime resolution across `new` / `attach` / `compact`.
|
|
87
|
+
|
|
88
|
+
## [0.4.5] - 2026-06-19
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- `init` declares all installed agentic runtimes (parity with `create`).
|
|
93
|
+
|
|
94
|
+
### Changed
|
|
95
|
+
|
|
96
|
+
- README: moved "What makes it different" above Quick start; polished the Bypass Permissions
|
|
97
|
+
security disclosure.
|
|
98
|
+
|
|
99
|
+
## [0.4.4] - 2026-06-19
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- Clear the virgin-Claude bypass-accept + project-MCP first-run dialogs (unblocks a
|
|
104
|
+
fresh-machine boot).
|
|
105
|
+
- Release scripts push explicitly with `git push origin main --follow-tags`.
|
|
106
|
+
|
|
107
|
+
### Changed
|
|
108
|
+
|
|
109
|
+
- Alias namespace `/alias-*` → `/alias_*` (Telegram slash-command constraint).
|
|
110
|
+
|
|
111
|
+
### Added
|
|
112
|
+
|
|
113
|
+
- Clean-install first-run dialog reference in docs/07 (EN + RU).
|
|
114
|
+
|
|
115
|
+
## [0.4.3] - 2026-06-19
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- FU13: targeted CLI argument errors — no more generic help-wall dump.
|
|
120
|
+
|
|
121
|
+
### Fixed
|
|
122
|
+
|
|
123
|
+
- onboard: marketplace add no longer shows a bare red "failed" on a transient first run.
|
|
124
|
+
|
|
125
|
+
### Changed
|
|
126
|
+
|
|
127
|
+
- docs/07: note the one-time double-run when upgrading from a pre-cascade (<0.4.2) version.
|
|
128
|
+
|
|
129
|
+
## [0.4.2] - 2026-06-19
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
|
|
133
|
+
- **FU12: `iapeer update` cascades the whole stack** — foundation + every installed runtime +
|
|
134
|
+
the memory provider in one command. The foundation goes first and is health-checked; a hard
|
|
135
|
+
foundation failure aborts the cascade.
|
|
136
|
+
|
|
137
|
+
## [0.4.1] - 2026-06-19
|
|
138
|
+
|
|
139
|
+
### Added
|
|
140
|
+
|
|
141
|
+
- Per-package ecosystem docs scaffolded to a stable host path `~/.iapeer/docs/<pkg>/`, with a
|
|
142
|
+
host-context docs pointer in the system prompt (FU6).
|
|
143
|
+
- Global host-doctrine stub `~/.iapeer/IAPEER.md` scaffolded on install (FU11).
|
|
144
|
+
- CI-status + dynamic license badges in the README (FU10).
|
|
145
|
+
- A no-empty-acks instruction on the agent tool surface.
|
|
146
|
+
|
|
147
|
+
### Changed
|
|
148
|
+
|
|
149
|
+
- Polish batch: onboard summary ANSI, advisory install commands, help/list formatting, the
|
|
150
|
+
create-runtime prompt.
|
|
151
|
+
- Dropped `bot_username` from the telegram-interface example and fixtures.
|
|
152
|
+
|
|
153
|
+
## [0.4.0] - 2026-06-18
|
|
154
|
+
|
|
155
|
+
### Added
|
|
156
|
+
|
|
157
|
+
- `iapeer uninstall` — symmetric, namespace-safe foundation removal (S4).
|
|
158
|
+
- Onboard wizard hardening (S1–S3): host-runtime detection threading `--runtime` to memory
|
|
159
|
+
init, the notifier scheduler and Telegram as default steps, visual polish with honest
|
|
160
|
+
slow-step labels.
|
|
161
|
+
|
|
162
|
+
### Fixed
|
|
163
|
+
|
|
164
|
+
- install: exact-pin cache-bust + keychain heads-up.
|
|
165
|
+
|
|
166
|
+
## [0.3.0] - 2026-06-18
|
|
167
|
+
|
|
168
|
+
### Added
|
|
169
|
+
|
|
170
|
+
- **Public one-line installer** (`curl -fsSL … | sh`) with GitHub Pages hosting — `install.sh`
|
|
171
|
+
is deployed from `main` so the served script is identical to source.
|
|
172
|
+
- **Onboard wizard (Ink TUI)** — first slice behind an opt-in flag, then feature-complete and
|
|
173
|
+
default; `install.sh` stops launching onboard directly.
|
|
174
|
+
|
|
175
|
+
### Changed
|
|
176
|
+
|
|
177
|
+
- README documents both install paths (one-liner + manual).
|
|
178
|
+
|
|
179
|
+
## [0.2.99] - 2026-06-18
|
|
180
|
+
|
|
181
|
+
### Added
|
|
182
|
+
|
|
183
|
+
- Ship `docs/` in the npm package (excluding `docs/internals`).
|
|
184
|
+
|
|
185
|
+
### Changed
|
|
186
|
+
|
|
187
|
+
- README: humans also take part in the terminal via `iapeer attach`.
|
|
188
|
+
|
|
189
|
+
## [0.2.98] - 2026-06-18
|
|
190
|
+
|
|
191
|
+
Initial public release — the foundation core of the iapeer multi-agent ecosystem.
|
|
192
|
+
|
|
193
|
+
### Added
|
|
194
|
+
|
|
195
|
+
- **Data layer** — identity (`<runtime>-<personality>`), registry (`peers-profiles.json`,
|
|
196
|
+
single-writer), storage (the `~/.iapeer` layout with atomic writes), codec (the IAP envelope).
|
|
197
|
+
- **Always-on router daemon** — a canonical HTTP-MCP Streamable router exposing the single
|
|
198
|
+
agent tool `send_to_peer`; dual-listen — a `0600` unix socket for same-host local callers and
|
|
199
|
+
a TCP loopback for real MCP clients.
|
|
200
|
+
- **Warm-on-demand lifecycle** — wake-on-miss, idle-reap, zombie-sweep, fresh-vs-resume by
|
|
201
|
+
death cause, a crash-loop guard, and a durable decision log.
|
|
202
|
+
- **Launch primitive + runtime adapters** — Claude, Codex, Telegram, and notifier; layered
|
|
203
|
+
system-prompt composition; launchd plist rendering.
|
|
204
|
+
- **Install / onboard chain** and the `iapeer` CLI (create, init, list, verify, status, send,
|
|
205
|
+
attach, update, and more).
|
|
206
|
+
- **Heterogeneous peers** — AI agents (Claude, Codex), humans (Telegram), and services
|
|
207
|
+
(timer = cron, watcher = event) are all first-class and addressed the same way.
|
|
208
|
+
|
|
209
|
+
[Unreleased]: https://github.com/agfpd/iapeer/compare/v0.4.13...HEAD
|
|
210
|
+
[0.4.13]: https://github.com/agfpd/iapeer/compare/v0.4.12...v0.4.13
|
|
211
|
+
[0.4.12]: https://github.com/agfpd/iapeer/compare/v0.4.11...v0.4.12
|
|
212
|
+
[0.4.11]: https://github.com/agfpd/iapeer/compare/v0.4.10...v0.4.11
|
|
213
|
+
[0.4.10]: https://github.com/agfpd/iapeer/compare/v0.4.9...v0.4.10
|
|
214
|
+
[0.4.9]: https://github.com/agfpd/iapeer/compare/v0.4.8...v0.4.9
|
|
215
|
+
[0.4.8]: https://github.com/agfpd/iapeer/compare/v0.4.7...v0.4.8
|
|
216
|
+
[0.4.7]: https://github.com/agfpd/iapeer/compare/v0.4.6...v0.4.7
|
|
217
|
+
[0.4.6]: https://github.com/agfpd/iapeer/compare/v0.4.5...v0.4.6
|
|
218
|
+
[0.4.5]: https://github.com/agfpd/iapeer/compare/v0.4.4...v0.4.5
|
|
219
|
+
[0.4.4]: https://github.com/agfpd/iapeer/compare/v0.4.3...v0.4.4
|
|
220
|
+
[0.4.3]: https://github.com/agfpd/iapeer/compare/v0.4.2...v0.4.3
|
|
221
|
+
[0.4.2]: https://github.com/agfpd/iapeer/compare/v0.4.1...v0.4.2
|
|
222
|
+
[0.4.1]: https://github.com/agfpd/iapeer/compare/v0.4.0...v0.4.1
|
|
223
|
+
[0.4.0]: https://github.com/agfpd/iapeer/compare/v0.3.0...v0.4.0
|
|
224
|
+
[0.3.0]: https://github.com/agfpd/iapeer/compare/v0.2.99...v0.3.0
|
|
225
|
+
[0.2.99]: https://github.com/agfpd/iapeer/releases/tag/v0.2.99
|
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# iapeer
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**The foundation for a team of AI agents on one machine.**
|
|
4
4
|
|
|
5
5
|
[](https://github.com/agfpd/iapeer/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/@agfpd/iapeer)
|
|
7
7
|
[](./LICENSE)
|
|
8
8
|
[](#quick-start)
|
|
9
9
|
|
|
10
|
-
iapeer keeps a team of AI agents, humans, and services on one host
|
|
10
|
+
iapeer keeps a team of AI agents, humans, and services on one host always reachable, every one a single message away. Each participant is a **peer**: a Claude Code or Codex CLI agent, a person on Telegram, or a scheduler/watcher service. Any agent reaches any other with one call, `send_to_peer(<name>, <text>)`; if the recipient is asleep, the daemon wakes it and delivers.
|
|
11
|
+
|
|
12
|
+
Agents on Claude and on Codex are different AI runtimes from different vendors, yet they talk as equal peers. Humans take part over Telegram, or directly in the terminal by attaching to a peer's live session with `iapeer attach`.
|
|
11
13
|
|
|
12
14
|
<p align="center">
|
|
13
15
|
<img src=".github/assets/iapeer-pingpong.gif" width="800" alt="A person messages one peer on Telegram; heterogeneous peers (Claude + Codex) relay a baton over one protocol and report back to Telegram.">
|
|
@@ -30,8 +32,8 @@ recipient peer — asleep? the daemon wakes it and delivers on wake
|
|
|
30
32
|
## What makes it different
|
|
31
33
|
|
|
32
34
|
- **Real interactive CLI sessions, not headless one-shots.** Each AI peer is a full Claude Code or Codex CLI session, not a disposable `claude -p` / `codex exec` call (as of 2026-06-15, `claude -p` no longer counts against the subscription's main usage limits).
|
|
33
|
-
- **Warm-on-demand lifecycle.** A live session answers fast; after about an hour idle the daemon closes it; the next message brings it back with its context resumed. The message flow drives the lifecycle
|
|
34
|
-
- **One identity across runtimes, with one memory.** A peer's `personality` is decoupled from its runtime
|
|
35
|
+
- **Warm-on-demand lifecycle.** A live session answers fast; after about an hour idle the daemon closes it; the next message brings it back with its context resumed. The message flow drives the lifecycle, not a human's presence.
|
|
36
|
+
- **One identity across runtimes, with one memory.** A peer's `personality` is decoupled from its runtime: the same peer runs on Claude or Codex, switched with a command. By default (set up by `iapeer onboard`) it carries one shared memory (iapeer-memory) keyed to the personality, not the runtime — one personality, one memory, whatever runtime it runs on.
|
|
35
37
|
- **Heterogeneous peers, one protocol.** AI agents, humans (Telegram), and services (timer = cron, watcher = event) are all first-class, addressed the same way over an always-on MCP daemon that exposes exactly one tool, `send_to_peer`.
|
|
36
38
|
|
|
37
39
|
## Quick start
|
|
@@ -91,9 +93,9 @@ Agents reach each other with `send_to_peer`; you can also message any peer over
|
|
|
91
93
|
|
|
92
94
|
## Bypass Permissions mode
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
**Warning: iapeer runs your Claude and Codex peers in bypass-permissions / no-sandbox mode — they execute tools (shell commands, file edits, network calls) without asking you to approve each action.** This is by design: a peer is a background, headless session with no human watching its terminal, so there is no one to answer a per-tool permission prompt. The launcher starts Claude with `--dangerously-skip-permissions` and Codex with `--dangerously-bypass-approvals-and-sandbox`, and **auto-accepts the one-time "accept bypass mode" screen on your behalf** the first time a peer boots on a fresh machine.
|
|
95
97
|
|
|
96
|
-
What this means for you
|
|
98
|
+
What this means for you:
|
|
97
99
|
|
|
98
100
|
- A peer can run any command its runtime can run — read and write files anywhere in your home directory, install software, make network calls — with **no confirmation step**. There is no sandbox between an agent and your machine.
|
|
99
101
|
- You are trusting the model's judgment, and the doctrine you give each peer, the same way you would trust a developer with a shell on your account.
|
|
@@ -118,4 +120,4 @@ Planned, not yet built:
|
|
|
118
120
|
|
|
119
121
|
## License
|
|
120
122
|
|
|
121
|
-
Apache-2.0. Platform: macOS / launchd. iapeer is the foundation
|
|
123
|
+
Apache-2.0. Platform: macOS / launchd. iapeer is the foundation of a larger ecosystem; runtime and capability plugins build on top of it.
|
|
@@ -102,7 +102,7 @@ Identity has two storage places, and it matters which is authoritative.
|
|
|
102
102
|
|
|
103
103
|
The profile fields split into three classes by who owns them:
|
|
104
104
|
|
|
105
|
-
- **Identity core** — `personality`, `default_runtime`, `runtimes`, `description`, `intelligence`, `initial_prompt`. Owned by the core and the lifecycle. The `initial_prompt` field is the seed for a fresh session's first turn. The `runtime` field beside `default_runtime` is
|
|
105
|
+
- **Identity core** — `personality`, `default_runtime`, `runtimes`, `description`, `intelligence`, `initial_prompt`. Owned by the core and the lifecycle. The `initial_prompt` field is the seed for a fresh session's first turn. The legacy `runtime` field beside `default_runtime` is no longer written — a read-fallback still understands older profiles that carry only `runtime`.
|
|
106
106
|
- **Private plugin config** — sections like `expansion` (shortcut aliases), `notifier` (the scheduler peer's settings), and others, one per plugin. Read only from the local profile, never projected into the registry.
|
|
107
107
|
- **Public passport** — the `interfaces` section (e.g. `interfaces.telegram.bot`). These are routing attributes, and they're projected into the registry.
|
|
108
108
|
|
|
@@ -81,7 +81,7 @@ An agent with no activity for longer than an hour is slept on the next superviso
|
|
|
81
81
|
|
|
82
82
|
**Wake serialization.** Several messages may hit a sleeping agent at once; one attempt should bring it up, or two competing sessions would be born. The daemon takes a lock on waking a given peer — the rest wait for the first to be ready.
|
|
83
83
|
|
|
84
|
-
**Postmortem diagnostics.** At the moment a session dies the supervisor records its cause (exit code / signal) — what an ordinary "is the process alive" check misses.
|
|
84
|
+
**Postmortem diagnostics.** At the moment a session dies the supervisor records its cause (exit code / signal) to a log — what an ordinary "is the process alive" check misses.
|
|
85
85
|
|
|
86
86
|
## The boundary: claude/codex vs infrastructure
|
|
87
87
|
|
|
@@ -29,7 +29,7 @@ The adapter's key properties:
|
|
|
29
29
|
|
|
30
30
|
Routers skip all the TUI phases: they have no boot dialogs, no readiness markers, no delivery confirmation, no control commands. Their liveness is held by launchd, not a wake by the daemon.
|
|
31
31
|
|
|
32
|
-
The production mechanism for hosting TUI sessions is the **pty supervisor**: a supervisor daemon owns the session's pty and its pane log, and delivery onto input goes through its socket.
|
|
32
|
+
The production mechanism for hosting TUI sessions is the **pty supervisor**: a supervisor daemon owns the session's pty and its pane log, and delivery onto input goes through its socket.
|
|
33
33
|
|
|
34
34
|
## The runtime-package contract
|
|
35
35
|
|
|
@@ -71,8 +71,6 @@ The daemon's supervisory tick is fixed — once a minute, not configurable.
|
|
|
71
71
|
A few more variables the system reads but that aren't set in normal operation:
|
|
72
72
|
|
|
73
73
|
- `NOTIFIER_FALLBACK_TARGET` — a fallback recipient for notifier signals; the core resolves it into the peer's plist at onboarding (no hardcode), the operator doesn't set it by hand.
|
|
74
|
-
- `IAP_TMUX_PASTE_SETTLE_MS`, `IAP_TMUX_SUBMIT_TIMEOUT_MS` — delivery timings in the internal tmux dev branch; production uses the pty supervisor ([13 — Architecture and contract](13-architecture-and-contract.md)), so they aren't needed outside development.
|
|
75
|
-
- `IAPEER_SHADOW_PATH` — the `PATH` for a shadow-fidelity run (an internal development tool).
|
|
76
74
|
|
|
77
75
|
## Logs and diagnostics
|
|
78
76
|
|
|
@@ -84,8 +82,8 @@ The daemon keeps three durable logs in `~/.iapeer/logs/iapeer/`:
|
|
|
84
82
|
|
|
85
83
|
### Postmortem diagnostics
|
|
86
84
|
|
|
87
|
-
The cause of a session's death (exit code / signal) the supervisor records at the moment of death — what an ordinary "is the process alive" check misses.
|
|
85
|
+
The cause of a session's death (exit code / signal) the supervisor records at the moment of death to `exits.log` — what an ordinary "is the process alive" check misses. It's the material for answering "why did the agent die".
|
|
88
86
|
|
|
89
87
|
### Full Disk Access
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
The `iapeer` binary needs Full Disk Access (FDA) — a macOS permission. `iapeer status` shows whether FDA is granted and hints at what to do if not. Without FDA, peers will hit TCC prompts when accessing protected folders and third-party app containers.
|
package/docs/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[Русский](ru/README.md) · **English**
|
|
4
4
|
|
|
5
|
-
iapeer is the
|
|
5
|
+
iapeer is the foundation of a multi-agent system on one host. It maintains a registry of participants, a single router daemon for messaging between them, and a set of commands to manage participants: create, start, sleep, restart, connect to external channels.
|
|
6
6
|
|
|
7
7
|
A participant in the system is called a **peer**. A peer can be an AI agent (Claude, Codex), a human (over Telegram), or a service process (a scheduler, a watcher). iapeer doesn't distinguish them by nature at the transport level — they're all addressed the same way and exchange messages over one protocol.
|
|
8
8
|
|
|
@@ -81,7 +81,7 @@ The sections are layered: from "install and use" to "understand the architecture
|
|
|
81
81
|
|
|
82
82
|
Built and working: the single router daemon, the messaging protocol (IAP), the peer registry, identity and its ABI, the lifecycle (wake, sleep, restart, supervision), runtime adapters for Claude, Codex, Telegram, and notifier, host install and onboarding, Telegram connection, the memory slot, and capability plugins.
|
|
83
83
|
|
|
84
|
-
Planned on top of the
|
|
84
|
+
Planned on top of the foundation: a package-compatibility graph, a whole-team health doctor, and an interactive coordination interface. Until those exist, coordination operations live in the `iapeer` CLI.
|
|
85
85
|
|
|
86
86
|
## Roadmap
|
|
87
87
|
|
|
@@ -102,7 +102,7 @@ PEER_IDENTITY = PEER_RUNTIME + "-" + PEER_PERSONALITY
|
|
|
102
102
|
|
|
103
103
|
Поля профиля делятся на три класса по тому, кто ими владеет:
|
|
104
104
|
|
|
105
|
-
- **Ядро идентичности** — `personality`, `default_runtime`, `runtimes`, `description`, `intelligence`, `initial_prompt`. Этим владеет ядро и жизненный цикл. Поле `initial_prompt` — затравка первого хода свежей сессии.
|
|
105
|
+
- **Ядро идентичности** — `personality`, `default_runtime`, `runtimes`, `description`, `intelligence`, `initial_prompt`. Этим владеет ядро и жизненный цикл. Поле `initial_prompt` — затравка первого хода свежей сессии. Легаси-поле `runtime` рядом с `default_runtime` больше не пишется — фолбэк при чтении всё ещё понимает старые профили, где есть только `runtime`.
|
|
106
106
|
- **Приватная конфигурация плагина** — секции вроде `expansion` (шорткаты-алиасы), `notifier` (настройка пира-планировщика) и прочие, по одной на плагин. Читаются только из локального профиля, в реестр не попадают.
|
|
107
107
|
- **Публичный паспорт** — секция `interfaces` (например `interfaces.telegram.bot`). Это атрибуты маршрутизации, и они проецируются в реестр.
|
|
108
108
|
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
|
|
82
82
|
**Сериализация пробуждений.** На спящего агента может прилететь несколько сообщений сразу; поднять его должна одна попытка, иначе родятся две конкурирующие сессии. Демон берёт блокировку на пробуждение конкретного пира — остальные ждут готовности первой.
|
|
83
83
|
|
|
84
|
-
**Посмертная диагностика.** Супервизор в момент смерти сессии фиксирует её причину (код выхода / сигнал) — это то, что обычная проверка «жив ли процесс» не ловит.
|
|
84
|
+
**Посмертная диагностика.** Супервизор в момент смерти сессии фиксирует её причину (код выхода / сигнал) в журнал — это то, что обычная проверка «жив ли процесс» не ловит.
|
|
85
85
|
|
|
86
86
|
## Граница: claude/codex против инфраструктурных
|
|
87
87
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
Роутеры пропускают все TUI-фазы: у них нет загрузочных диалогов, маркеров готовности, подтверждения доставки и контрольных команд. Их живость держит launchd, а не пробуждение демоном.
|
|
31
31
|
|
|
32
|
-
Продакшен-механизм хостинга TUI-сессий — **pty-супервизор**: супервизор-демон владеет pty сессии и её pane-логом, через его сокет идёт доставка на ввод.
|
|
32
|
+
Продакшен-механизм хостинга TUI-сессий — **pty-супервизор**: супервизор-демон владеет pty сессии и её pane-логом, через его сокет идёт доставка на ввод.
|
|
33
33
|
|
|
34
34
|
## Контракт рантайм-пакета
|
|
35
35
|
|
|
@@ -71,8 +71,6 @@
|
|
|
71
71
|
Ещё несколько переменных система читает, но в обычной эксплуатации их не задают:
|
|
72
72
|
|
|
73
73
|
- `NOTIFIER_FALLBACK_TARGET` — запасной адресат для сигналов notifier; ядро резолвит его в плист пира при онбординге (без хардкода), вручную оператор его не выставляет.
|
|
74
|
-
- `IAP_TMUX_PASTE_SETTLE_MS`, `IAP_TMUX_SUBMIT_TIMEOUT_MS` — тайминги доставки во внутренней tmux dev-ветви; на проде используется pty-супервизор ([13 — Архитектура и контракт](13-архитектура-и-контракт.md)), вне разработки не нужны.
|
|
75
|
-
- `IAPEER_SHADOW_PATH` — `PATH` для shadow-fidelity прогона (внутренний инструмент разработки).
|
|
76
74
|
|
|
77
75
|
## Журналы и диагностика
|
|
78
76
|
|
|
@@ -84,8 +82,8 @@
|
|
|
84
82
|
|
|
85
83
|
### Посмертная диагностика
|
|
86
84
|
|
|
87
|
-
Причину смерти сессии (код выхода / сигнал) супервизор фиксирует в момент смерти — это то, что обычная проверка «жив ли процесс» не ловит.
|
|
85
|
+
Причину смерти сессии (код выхода / сигнал) супервизор фиксирует в момент смерти в `exits.log` — это то, что обычная проверка «жив ли процесс» не ловит. Это материал для ответа на вопрос «почему агент умер».
|
|
88
86
|
|
|
89
87
|
### Full Disk Access
|
|
90
88
|
|
|
91
|
-
|
|
89
|
+
Бинарю `iapeer` нужен Full Disk Access (FDA) — разрешение macOS. `iapeer status` показывает, выдан ли FDA, и подсказывает, что делать, если нет. Без FDA пиры будут натыкаться на запросы TCC при доступе к защищённым папкам и контейнерам сторонних приложений.
|
package/docs/ru/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Русский** · [English](../README.md)
|
|
4
4
|
|
|
5
|
-
iapeer —
|
|
5
|
+
iapeer — основа мультиагентной системы на одном хосте. Он держит реестр участников, единый демон-роутер для обмена сообщениями между ними и набор команд, которыми участниками управляют: создают, запускают, усыпляют, перезапускают, подключают к внешним каналам.
|
|
6
6
|
|
|
7
7
|
Участник системы называется **пиром**. Пиром может быть AI-агент (Claude, Codex), человек (через Telegram) или служебный процесс (планировщик, наблюдатель). iapeer не различает их по природе на уровне транспорта — все они адресуются одинаково и обмениваются сообщениями через один протокол.
|
|
8
8
|
|
|
@@ -81,7 +81,7 @@ iapeer send assistant --message "привет, ты на связи?"
|
|
|
81
81
|
|
|
82
82
|
Построено и работает: единый демон-роутер, протокол обмена сообщениями (IAP), реестр пиров, идентичность и её ABI, жизненный цикл (пробуждение, усыпление, перезапуск, надзор), рантайм-адаптеры для Claude, Codex, Telegram и notifier, установка и онбординг хоста, подключение Telegram, слот памяти и capability-плагины.
|
|
83
83
|
|
|
84
|
-
Запланировано поверх
|
|
84
|
+
Запланировано поверх основы: граф совместимости пакетов, доктор здоровья всей команды и интерактивный интерфейс координации. Пока этих надстроек нет, координационные операции живут в CLI `iapeer`.
|
|
85
85
|
|
|
86
86
|
## Планы
|
|
87
87
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agfpd/iapeer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
4
4
|
"description": "Foundation core for the iapeer multi-agent ecosystem: identity, registry, storage, codec.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "agfpd",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"src",
|
|
29
29
|
"bin",
|
|
30
30
|
"docs",
|
|
31
|
+
"CHANGELOG.md",
|
|
31
32
|
"tsconfig.json",
|
|
32
33
|
"!src/**/*.test.ts",
|
|
33
34
|
"!docs/internals/**"
|
package/src/cli/index.ts
CHANGED
|
@@ -1545,10 +1545,13 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
|
|
|
1545
1545
|
} else if (warns.length > 0 && flags.json !== true) {
|
|
1546
1546
|
lines.push(`⚠ ${peer.personality}: ${warns.map(i => i.field).join(', ')}`)
|
|
1547
1547
|
}
|
|
1548
|
-
// Migration candidate: conformant
|
|
1549
|
-
//
|
|
1548
|
+
// Migration candidate (Phase-3): conformant AND still carrying the legacy `runtime` field —
|
|
1549
|
+
// a warn-fired legacy-only/diverged shape OR an in-sync mirror. In Phase-3 ANY `runtime`
|
|
1550
|
+
// field is stripped, so the candidate set is "has a runtime field", not just "warn fired"
|
|
1551
|
+
// (an in-sync mirror does not warn, but must still be cleaned). Errored profiles are NEVER
|
|
1550
1552
|
// rewritten — migration heals shape, it does not guess at broken data.
|
|
1551
|
-
|
|
1553
|
+
const hasLegacyRuntime = !!raw && typeof raw === 'object' && (raw as Record<string, unknown>).runtime !== undefined
|
|
1554
|
+
if (errs.length === 0 && (warns.some(i => i.field === 'default_runtime') || hasLegacyRuntime)) {
|
|
1552
1555
|
legacyRuntimeProfiles.push({ personality: peer.personality, cwd: peer.cwd })
|
|
1553
1556
|
}
|
|
1554
1557
|
}
|
|
@@ -1568,16 +1571,15 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
|
|
|
1568
1571
|
out(`\n${index.peers.length} peers · ${errors} error(s) · ${driftCount} index↔local drift(s)\n`)
|
|
1569
1572
|
}
|
|
1570
1573
|
if (flags.fix === true) {
|
|
1571
|
-
// Phase-
|
|
1572
|
-
//
|
|
1573
|
-
//
|
|
1574
|
-
// reindex so the healed index is projected from migrated locals.
|
|
1574
|
+
// Phase-3 data migration (default_runtime story complete): rewrite conformant local profiles
|
|
1575
|
+
// still carrying the legacy `runtime` field — the writer now emits ONLY `default_runtime` and
|
|
1576
|
+
// STRIPS the mirror. Runs BEFORE the reindex so the healed index is projected from clean locals.
|
|
1575
1577
|
const migrated: string[] = []
|
|
1576
1578
|
for (const p of legacyRuntimeProfiles) {
|
|
1577
1579
|
if (migrateProfileRuntimeField(p.cwd)) migrated.push(p.personality)
|
|
1578
1580
|
}
|
|
1579
1581
|
if (migrated.length > 0) {
|
|
1580
|
-
out(`
|
|
1582
|
+
out(`stripped legacy \`runtime\` mirror (default_runtime only): ${migrated.join(', ')}\n`)
|
|
1581
1583
|
}
|
|
1582
1584
|
if (driftCount > 0 || errors === 0) {
|
|
1583
1585
|
const { healed, missing } = await reindexFromLocals({ env })
|
|
@@ -217,6 +217,23 @@ export function projectProfileToRecord(cwd: string): PeerRecord | null {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
/** Order-insensitive structural serialization for comparing the projected OWNER
|
|
221
|
+
* passport (`interfaces`) — a nested object whose key ORDER is not semantically
|
|
222
|
+
* meaningful. A plain JSON.stringify would flag an order-only difference as drift
|
|
223
|
+
* (a legacy upsertPeer write may have stored keys in a different order); canonicalize
|
|
224
|
+
* first so only real content divergence is reported — a key added, removed, or changed,
|
|
225
|
+
* e.g. a field dropped from the source of truth after a cutover that the derived index
|
|
226
|
+
* never re-projected. */
|
|
227
|
+
function canonicalJson(value: unknown): string {
|
|
228
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null'
|
|
229
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`
|
|
230
|
+
const obj = value as Record<string, unknown>
|
|
231
|
+
return `{${Object.keys(obj)
|
|
232
|
+
.sort()
|
|
233
|
+
.map(k => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`)
|
|
234
|
+
.join(',')}}`
|
|
235
|
+
}
|
|
236
|
+
|
|
220
237
|
/** The index↔local drift for one peer: the fields where the live registry record
|
|
221
238
|
* disagrees with the projection from the local profile (the self-heal source). Empty
|
|
222
239
|
* array = in sync. cwd is the registry pointer, never compared. */
|
|
@@ -227,6 +244,12 @@ export function recordDrift(indexRecord: PeerRecord, projected: PeerRecord): str
|
|
|
227
244
|
if (indexRecord.runtimes.join(',') !== projected.runtimes.join(',')) drift.push('runtimes')
|
|
228
245
|
if (indexRecord.description !== projected.description) drift.push('description')
|
|
229
246
|
if (indexRecord.intelligence !== projected.intelligence) drift.push('intelligence')
|
|
247
|
+
// The OWNER passport (`interfaces`) is projected into the registry, so a divergence
|
|
248
|
+
// here is real drift — the class that hid the bot_username cutover (source dropped
|
|
249
|
+
// `interfaces.telegram.bot`, the derived index kept it because nothing compared it).
|
|
250
|
+
// projectProfileToRecord already replaces interfaces wholesale; comparing it here makes
|
|
251
|
+
// the desync VISIBLE to verify/reconcile so it can never silently persist again.
|
|
252
|
+
if (canonicalJson(indexRecord.interfaces) !== canonicalJson(projected.interfaces)) drift.push('interfaces')
|
|
230
253
|
return drift
|
|
231
254
|
}
|
|
232
255
|
|
package/src/transport/index.ts
CHANGED
|
@@ -966,19 +966,21 @@ function validateAttachments(value: readonly string[] = []): Result<string[]> {
|
|
|
966
966
|
|
|
967
967
|
// ─── Telegram-domain sender policy ─
|
|
968
968
|
|
|
969
|
-
/** A SENDER face in the telegram domain: a non-empty interfaces.telegram.
|
|
970
|
-
* bot
|
|
971
|
-
*
|
|
972
|
-
* a user_id grants a receive/operator
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
*
|
|
976
|
-
*
|
|
969
|
+
/** A SENDER face in the telegram domain: a non-empty interfaces.telegram.bot_username
|
|
970
|
+
* (the bot binding key SINCE the bot_username-cutover) in the peer's registry passport.
|
|
971
|
+
* Deliberately bot_username-ONLY, not bot_username|user_id — the bridge picks the SENDING
|
|
972
|
+
* bot from the sender's bot_username key: a user_id grants a receive/operator identity but
|
|
973
|
+
* no ability to send, so a user_id-only sender passing the guard would still die at the
|
|
974
|
+
* bridge — a late asynchronous failure where the guard exists to give a synchronous one.
|
|
975
|
+
* The guard passes exactly what the bridge can deliver. (Legacy `bot` was the pre-cutover
|
|
976
|
+
* key; it is NO LONGER read here — the source of truth dropped it and telegram-runtime
|
|
977
|
+
* resolves the sending bot on bot_username. This guard is the foundation's only consumer
|
|
978
|
+
* of the telegram passport, so it moves with the cutover.) */
|
|
977
979
|
export function hasTelegramPresence(record: PeerRecord): boolean {
|
|
978
980
|
const tg = record.interfaces?.telegram
|
|
979
981
|
if (!tg || typeof tg !== 'object' || Array.isArray(tg)) return false
|
|
980
982
|
const t = tg as Record<string, unknown>
|
|
981
|
-
return typeof t.
|
|
983
|
+
return typeof t.bot_username === 'string' && t.bot_username.trim() !== ''
|
|
982
984
|
}
|
|
983
985
|
|
|
984
986
|
/** Sender policy for the telegram domain: a peer with NO declared telegram presence
|
|
@@ -1000,7 +1002,7 @@ function telegramSenderGuard(
|
|
|
1000
1002
|
if (targetRuntime !== 'telegram') return ok(undefined)
|
|
1001
1003
|
if (hasTelegramPresence(caller.record)) return ok(undefined)
|
|
1002
1004
|
return err(
|
|
1003
|
-
`telegram policy: sender "${caller.personality}" has no telegram face (no interfaces.telegram.
|
|
1005
|
+
`telegram policy: sender "${caller.personality}" has no telegram face (no interfaces.telegram.bot_username in its passport) — message to "${targetPersonality}" NOT delivered; route via a peer that has a telegram bot in its passport, or provision a bot for this sender`,
|
|
1004
1006
|
)
|
|
1005
1007
|
}
|
|
1006
1008
|
|