pi-onlyne 1.1.2 → 1.2.0

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/README.md CHANGED
@@ -9,7 +9,7 @@ length-prefixed JSON codec, and the runtime has no npm dependencies.
9
9
 
10
10
  Outside an onlyne session the extension is inert. The client injects `ONLYNE_ROLE`,
11
11
  `ONLYNE_SESSION_ID` and `ONLYNE_TASK_ID` into every process it spawns
12
- (`crates/onlyne-client/src/dispatch.rs`). With any of the three missing, this is an
12
+ (`crates/onlyne-client/src/session/dispatch.rs`). With any of the three missing, this is an
13
13
  ordinary pi session: the plugin registers nothing and opens nothing.
14
14
 
15
15
  ```
@@ -25,8 +25,10 @@ hello{protocol:1, plugin:"pi-onlyne", kind:"agent", capabilities:[…], mount:{r
25
25
  ├─ task text (+ image path) ──► pi user message (deliverAs:"followUp")
26
26
  ├─ assign_ack{accepted:true}
27
27
  ├─ report.heartbeat{running|idle} — per turn, and every 10s while a task is live
28
+ ├─ a turn that ends without `onlyne_complete` ──► the idle ladder: the same
29
+ │ message again (at most `idleReminders` times), then `failed` and out
28
30
  ├─ report.complete{outcome, head} — the ledger's terminal fact
29
- │ └─ then one report.heartbeat{agent:"idle"} carrying the settled tuple
31
+ │ └─ then one report.heartbeat{agent:"idle"} stating the agent only
30
32
  │ └─ the client's answer is the handover: pi is asked to shut down, then detaches
31
33
  ├─ probe ──► one heartbeat
32
34
  ◀── recycle ──► complete (if unsettled) → stop → pi exits
@@ -102,9 +104,10 @@ pi --session-id <id> -e /abs/path/to/plugins/onlyne-agent-pi -ns -nc
102
104
  | --- | --- | --- |
103
105
  | `enabled` | `true` | `false` turns the extension off for this workspace |
104
106
  | `watch.autoStart` | `true` | `false` registers the tools but opens no socket until `/onlyne connect` |
107
+ | `idleReminders` | `2` | how many times an idle turn end re-sends the assignment before the task fails (§4); 0 means the first idle without a completion fails it |
105
108
 
106
- A missing file means both defaults. A malformed file prints one warning on stderr and
107
- keeps both defaults: a typo must not silently disable a role. The client does not read
109
+ A missing file means every default. A malformed file prints one warning on stderr and
110
+ keeps the defaults: a typo must not silently disable a role. The client does not read
108
111
  this file (§11 of the plan downgraded the old readiness gates to generate-time template
109
112
  advice), so only this extension consumes it; the key shape stays the one the templates
110
113
  carry.
@@ -154,7 +157,8 @@ png/jpeg/gif/webp file: the plugin reads it, base64-encodes it and attaches it a
154
157
 
155
158
  ### `onlyne_complete{outcome?, text?, force?, reason?}`
156
159
 
157
- Ends the current task with an explicit outcome (`done` by default, or `failed`). A
160
+ Ends the current task with an explicit outcome (`done` by default, or `failed`). It is the
161
+ only path to `done`: a turn that ends without it is reminded and then failed (§4). A
158
162
  non-empty `text` becomes the ledger `head` verbatim: whitespace collapses to one line and
159
163
  the text stops at 200 characters. An absent or blank `text` carries no summary, so the
160
164
  completion falls back to the last assistant text. The call also ends the session's
@@ -163,28 +167,52 @@ pi to shut down through `ctx.shutdown()`. pi 0.85.1 has no tool-result `terminat
163
167
  handling. When the workspace carries a relay policy (§5), `force: true` with a non-empty
164
168
  `reason` is the deliberate way past a handoff the session still owes.
165
169
 
170
+ ### `onlyne_handoff{to, text, image?}`
171
+
172
+ Hands this session's task on to the next hop of its family. The plugin sends one `handoff`
173
+ frame naming the task the session currently holds, and the host mints one child task for
174
+ `to` under it: the child names this task as its `parent_task`, sits one hop further along,
175
+ and carries the same family id, hop budget, origin, deadline and labels. The tool's result
176
+ names the child task id and its hop. A client refusal comes back as the tool's error,
177
+ verbatim. `image` is the same absolute png/jpeg/gif/webp path the send tool takes. An
178
+ assignment whose causality names a hop budget states the hop and the budget in its
179
+ injected header line. `onlyne_send{kind: "task"}` is the other way to reach a role: that
180
+ envelope starts a family of its own at hop 0.
181
+
166
182
  ## 4. Outcome rules
167
183
 
168
- The plugin sends one completion per task, at the first of these events:
169
-
170
- 1. **`onlyne_complete`** — the model gives an explicit outcome. It wins over everything
171
- else, and a later completion for the same task is refused (not re-reported). Its
172
- non-empty `text` is the head.
173
- 2. **`agent_settled`** — pi will not continue on its own: no retry, compaction or queued
174
- continuation is pending. The plugin reports:
175
- - `failed` when the turn ended with a provider error (`stopReason: "error"`), with the
176
- error as the head;
177
- - `done` otherwise, with the last assistant text as the head;
178
- - nothing at all when the task was assigned but no turn has run yet. The injected
179
- message has not executed, so completing now would claim work that never happened.
180
- 3. **`recycle{outcome}`** — the host is tearing the session down. The plugin settles an
184
+ `onlyne_complete` is the only path to `done`. The plugin sends one completion per task,
185
+ at the first of these events:
186
+
187
+ 1. **`onlyne_complete`** — the model gives an explicit outcome (`done` by default, or
188
+ `failed` / `cancelled`). A later completion for the same task is refused (not
189
+ re-reported). Its non-empty `text` is the head.
190
+ 2. **An errored turn** — the turn ended with a provider error (`stopReason: "error"`).
191
+ That is proof on its own, so the plugin reports `failed` at once, with the error as
192
+ the head.
193
+ 3. **The idle ladder** — the turn ended cleanly without a completion, and the task is
194
+ still open. The plugin re-sends the assignment and counts the rung. The idle that
195
+ finds the bound `idleReminders` names already spent reports `failed` — head
196
+ `no completion after <n> idle reminders` — and the session exits the way any
197
+ completion makes it exit.
198
+ 4. **`recycle{outcome}`** — the host is tearing the session down. The plugin settles an
181
199
  unsettled task with the host's outcome first, then stops and exits pi.
182
200
 
201
+ Two things settle nothing: a task that was assigned but whose turn has not run yet (the
202
+ injected message has not executed, so completing now would claim work that never
203
+ happened), and an idle the ladder still has a rung for. The ladder re-sends the
204
+ assignment the task arrived with — the same header, task text and attachment paths the
205
+ injection carried, under one line saying the previous turn ended without a completion —
206
+ and never the role prose, which is already in the session's context. Its images are not
207
+ re-attached: the paths travel as text, so the same bytes are not put into the context
208
+ twice. A new envelope for the same task restarts the count.
209
+
183
210
  `head` is a single line, capped at 200 characters; it matches what the client puts in
184
211
  `out_head` and what the receipt carries. Each task has one source for it: the `text` of
185
- the explicit `onlyne_complete` call when that call carried one, and the last assistant
186
- text otherwise. The auto rule is that fallback path: it reports the text of the turn it
187
- settles, and a sentence spoken after the call cannot replace what the call handed over.
212
+ the explicit `onlyne_complete` call when that call carried one, the error a failed turn
213
+ reported, or the ladder's own line. The last assistant text is the fallback for an
214
+ `onlyne_complete` call that carried no text at all — a sentence spoken after such a call
215
+ cannot replace what the call handed over, and nothing else reads it.
188
216
 
189
217
  A reported completion ends the session's process. `report.complete` goes out as a request,
190
218
  and the client answers it only after it has settled the session row, acked the delivery
@@ -193,7 +221,7 @@ outcome the socket could not carry is queued and flushed after the next `hello`,
193
221
  flush's answer is the handover that ends the process. A completion the host refused leaves
194
222
  the process running, so an exit never loses the task.
195
223
 
196
- The last report is one observation with `agent: "idle"` beside the settled outcome, sent
224
+ The last report is one observation with `agent: "idle"`, sent
197
225
  after the completion is acknowledged and before the process leaves. The completion settles
198
226
  the row from the tuple the client holds, and that tuple still reads `running` when the
199
227
  finishing turn was the last heartbeat. Nothing observes the process afterwards, so without
@@ -253,7 +281,7 @@ file its turn.
253
281
  | scope | this session's own sends, in process memory: a reconnect keeps them, a restarted session starts empty rather than guessing at what an earlier process sent |
254
282
  | waiver | `force: true` with a non-empty `reason`; it only matters when the guard refuses |
255
283
  | audit | a waived completion's ledger head starts with `relay-guard-forced: <reason>`, followed by the model's `text` when the call carried one |
256
- | not guarded | the automatic outcomes: `agent_settled` and `recycle{outcome}` still complete a task that owes a handoff |
284
+ | not guarded | outcomes the plugin reports without the model: an errored turn, the idle ladder's failure, and `recycle{outcome}` |
257
285
 
258
286
  `relay.toml` is a closed subset of TOML: flat `key = value` lines, the two keys above,
259
287
  one-line arrays of double-quoted strings, `#` comments. Anything outside that warns on
@@ -271,18 +299,22 @@ the shipped client.
271
299
  - **Report sequence base.** The plugin's own `report` sequence starts at 1000, not 1. The
272
300
  client stamps its own dispatch events (`created`, resource attach, `ready`) into the
273
301
  same `(generation, seq)` watermark, and the reducer silently drops any report at or
274
- below it (`crates/onlyne-session/src/reconcile.rs`). A plugin sequence starting at 1
302
+ below it (`crates/onlyne-session/src/reconcile/`). A plugin sequence starting at 1
275
303
  would lose its first observations. Everything else about the versioning is per spec.
276
- - **`observed` is a full `Observation`.** `report.heartbeat` carries the whole legal state
304
+ - **`observed` is a full `Observation`.** `report.heartbeat` carries the state
277
305
  tuple (`version`, `generation_live`, `isolate_after`, `terminate_after`,
278
- `mismatch_count`, `agent`, `delivery`, `resource`, `recovery`, `outcome`, `public`), not
279
- a `{"state": "running"}` shorthand: the host deserialises it and rejects anything
280
- `is_legal` refuses. This plugin owns only the `agent` dimension (turn hooks). It leaves
281
- `delivery` at `none` and `outcome` at `pending`, which is its own truth until it reports
282
- a completion. It reports `resource` as `attached` because the host's own dispatch path
283
- already recorded the attach.
306
+ `mismatch_count`, `agent`, `delivery`, `resource`, `recovery`), not
307
+ a `{"state": "running"}` shorthand: the host deserialises it, overwrites the six
308
+ keys the client owns, and applies only a tuple `is_legal` accepts. This plugin owns the `agent` dimension (turn hooks), the
309
+ `resource` claim — its process is live in the pane the attach was recorded on —
310
+ and the `host` binding. It has no witness for `delivery`, `recovery`,
311
+ `generation_live`, `isolate_after`, `terminate_after` or `mismatch_count`: the
312
+ client rewrites all six from its own intent queue, reducer history and role
313
+ config before the tuple is applied, so whatever this plugin sends there is
314
+ never read. Neither a
315
+ task outcome nor a public view travels in a tuple at all.
284
316
  - **`ready` is reported once per connection.** The host's own hand-off path
285
- (`crates/onlyne-client/src/dispatch.rs::hand_session`) already reports `ready` when the
317
+ (`crates/onlyne-client/src/session/dispatch/delivery.rs::hand_session`) already reports `ready` when the
286
318
  client stages the session for a mounting plugin, so a second report from the plugin is a
287
319
  no-op at the host. The plugin sends it anyway: a plugin that mounts *before* any work
288
320
  exists is the case the ready barrier names, and it costs one frame.
@@ -354,12 +386,12 @@ path the client's daemon bound, read when the environment carried none, §8).
354
386
  | `reconnecting in 4000ms` in a loop | the client is down or the socket was replaced | `onlyne --server-root … roles` |
355
387
  | `ready refused: internal: unknown session for …` | the plugin mounted and reported for a task the client never staged (normal when pi is started by hand outside a task) | start pi under the client, not by hand |
356
388
  | `assign` never arrives | the client's `session_command` did not spawn pi, or `inject` was dropped | the client log for the spawn line; `/onlyne status` for the capability set |
357
- | ledger stays `in_flight` | no completion was reported: no turn ran, or `agent_settled` never fired | the pi session file for `onlyne-assign` / `onlyne-complete` entries |
389
+ | ledger stays `in_flight` | no completion yet: no turn has run (the injected message has not executed), or the ladder is still reminding it (`idleReminders`) | the pi session file for the `onlyne-assign` entry and the reminders injected after it; the `onlyne` panel for `reminder n of m`; `/onlyne status` for the task and phase |
358
390
  | `onlyne_complete` answers `relay guard: missing handoff to: …` | the workspace's spec (or a `relay.toml` standing in for it) names a role this session never sent to | routine notices appear in the `onlyne` panel; stderr keeps refusals such as `relay guard from …`, socket errors, timeouts and framing faults; `required=…` names the policy; `relay guard: missing handoff …` names the delivered set |
359
391
  | `hello … forbidden` / connection closed right after `hello` | the mount role does not match the client's role | `hello.args.mount.role` vs the workspace's role |
360
392
  | `frame_too_large` | a body above 8 MiB | only reachable through an oversize outbound image; the ceiling is the core's |
361
393
  | tools missing | `pi.registerTool` is absent in that pi version | `/onlyne status`; the capability table above |
362
- | session reads `idle` again after `exited` | a heartbeat snapshot landed after the completion, carrying `outcome: pending` | the session log for the report order after `completion`; the plugin stops reporting for a completed task |
394
+ | session reads `idle` again after `exited` | a turn-end heartbeat landed after the completion, moving the agent dimension back | the session log for the report order after `completion`; the plugin stops reporting for a completed task, and the client's own `delivery` survives either way |
363
395
  | the supervisor board lists no tabs | no live session reported a pane: the adapter predates the report, or this pi is not inside an Orca pane | `onlyne --server-root … sessions --json` for `projection.observed.host.orca.pane_key`; `env \| grep ORCA_` inside the pane |
364
396
 
365
397
  `/onlyne status` prints the live state (`connected`, `socket`, `role`, `sessionId`,
package/README.zh.md CHANGED
@@ -6,7 +6,7 @@
6
6
  `node:net` 上重写,四字节大端长度前缀加 UTF-8 JSON 的编解码是手写的,运行时零 npm 依赖。
7
7
 
8
8
  扩展在 onlyne 之外完全静默。客户端 spawn 进程时会注入 `ONLYNE_ROLE`、`ONLYNE_SESSION_ID`、
9
- `ONLYNE_TASK_ID`(`crates/onlyne-client/src/dispatch.rs`);三者缺一,就是普通 pi session,
9
+ `ONLYNE_TASK_ID`(`crates/onlyne-client/src/session/dispatch.rs`);三者缺一,就是普通 pi session,
10
10
  插件不注册任何工具、不打开任何 socket。
11
11
 
12
12
  ```
@@ -22,6 +22,8 @@ hello{protocol:1, plugin:"pi-onlyne", kind:"agent", capabilities:[…], mount:{r
22
22
  ├─ 任务文本(含图片路径)──► pi user message(deliverAs:"followUp")
23
23
  ├─ assign_ack{accepted:true}
24
24
  ├─ report.heartbeat{running|idle} —— 每个 turn,以及任务存续期间每 10 秒
25
+ ├─ turn 结束却没有 `onlyne_complete` ──► idle 阶梯:同一条消息再注入
26
+ │ (最多 `idleReminders` 次),之后 `failed` 并退出
25
27
  ├─ report.complete{outcome, head} —— ledger 的终态事实
26
28
  │ └─ client 的应答就是交接点:插件据此让 pi 退出,随后 detach
27
29
  ├─ probe ──► 一条 heartbeat
@@ -95,8 +97,9 @@ pi --session-id <id> -e /abs/path/to/plugins/onlyne-agent-pi -ns -nc
95
97
  | --- | --- | --- |
96
98
  | `enabled` | `true` | `false` 时该工作区禁用扩展 |
97
99
  | `watch.autoStart` | `true` | `false` 时注册工具但不建连接,需 `/onlyne connect` |
100
+ | `idleReminders` | `2` | turn 结束却空闲时重发任务的次数上限(§4);`0` 表示第一次空闲就判失败 |
98
101
 
99
- 文件缺失即两个默认值。文件格式错误时打印一行警告,并保留默认值:一个笔误不该静默关掉一个
102
+ 文件缺失即取各自默认值。文件格式错误时打印一行警告,并保留默认值:一个笔误不该静默关掉一个
100
103
  role。client 不读这个文件(计划 §11 已把旧 readiness 门降级为 generate 期模板提示),所以
101
104
  只有本扩展消费它;键名沿用模板里既有的形状。
102
105
 
@@ -143,37 +146,53 @@ png/jpeg/gif/webp 的绝对路径:插件读出内容,base64 编码后挂成
143
146
 
144
147
  ### `onlyne_complete{outcome?, text?, force?, reason?}`
145
148
 
146
- 显式结束当前任务,`outcome` 缺省 `done`,也可 `failed`。`text` 非空时就是 ledger 的 `head`,
149
+ 显式结束当前任务,`outcome` 缺省 `done`,也可 `failed`。它是通向 `done` 的唯一路径:turn 结束时
150
+ 没有这次调用,任务会被重发提醒,之后判失败(§4)。`text` 非空时就是 ledger 的 `head`,
147
151
  原样写出:空白折叠成单行,截到 200 字符。`text` 缺失或全空白时不带摘要,completion 退回
148
152
  最后一段 assistant 文本。这一调用同时结束所在 session 的进程。client 应答完 completion
149
153
  报告(见 §4)之后,插件通过 `ctx.shutdown()` 让 pi 退出。pi 0.85.1 没有 tool-result
150
154
  `terminate` 处理。工作区带接力策略(§5)时,`force: true` 加非空 `reason` 是绕过一个仍欠着的
151
155
  接力的正规通道。
152
156
 
157
+ ### `onlyne_handoff{to, text, image?}`
158
+
159
+ 把本会话手上的任务交给家族的下一跳。插件发一个 `handoff` 帧,帧里点名本会话当前持有的任务,
160
+ 宿主据此为 `to` 铸一个该家族的子任务:子任务把本任务记为 `parent_task`,hop 加一,家族 id、
161
+ hop 预算、origin、deadline 与 labels 一并随行。工具结果给出子任务 id 与它的 hop。client 拒绝时
162
+ 以工具错误原样抛出。`image` 与 send 工具同一含义:png/jpeg/gif/webp 图片的绝对路径。家族带
163
+ hop 预算时,注入的标题行写明 hop 与预算。`onlyne_send{kind: "task"}` 是触达 role 的另一条路:
164
+ 那条 envelope 开一个新家族,hop 从 0 起。
165
+
153
166
  ## 4. outcome 判定规则
154
167
 
155
- 插件每个任务只发一次 completion,取以下三者的先到者:
168
+ `onlyne_complete` 是通向 `done` 的唯一路径。插件每个任务只发一次 completion,取以下四者的先到者:
156
169
 
157
- 1. **`onlyne_complete`** —— 模型给显式 outcome,优先级最高;同一任务的第二次 completion 被
158
- 拒(不重报)。`text` 非空时即 head,原样写出。
159
- 2. **`agent_settled`** —— pi 不会自己继续:没有待重试、待压缩或排队续跑。此时:
160
- - turn 以 provider 错误告终(`stopReason: "error"`)→ `failed`,错误信息当 head;
161
- - 其余 → `done`,最后一段 assistant 文本当 head;
162
- - 任务已投递但还没跑过任何 turn → 不发 completion。注入的消息尚未执行,这时报终态就是撒谎。
163
- 3. **`recycle{outcome}`** —— 宿主拆 session。插件先按宿主给的 outcome 结算未终态的任务,再停
170
+ 1. **`onlyne_complete`** —— 模型给显式 outcome(缺省 `done`,也可 `failed` / `cancelled`)。同一
171
+ 任务的第二次 completion 被拒(不重报)。`text` 非空时即 head,原样写出。
172
+ 2. **turn 出错** —— turn 以 provider 错误告终(`stopReason: "error"`)。这本身就是证据,插件立即
173
+ 报 `failed`,错误信息当 head。
174
+ 3. **idle 阶梯** —— turn 干净结束却没有 completion,任务还开着。插件重发任务并记一次;当空闲发现
175
+ `idleReminders` 给的次数已经用完,就报 `failed`(head 为 `no completion after <n> idle
176
+ reminders`),并像任何一次 completion 一样退出 session。
177
+ 4. **`recycle{outcome}`** —— 宿主拆 session。插件先按宿主给的 outcome 结算未终态的任务,再停
164
178
  插件并退出 pi。
165
179
 
166
- `head` 恒为单行、上限 200 字符,与 client 写入 `out_head` 和回执携带的内容一致。每个任务的
167
- head 只有一个来源:显式 `onlyne_complete` 带的 `text`(有则原样采用),否则是最后一段
168
- assistant 文本。自动规则就是那条退路:它报的是自己那一轮的文字,工具调用之后再说的话,顶不掉
169
- 调用交出的内容。
180
+ 两种情况都不结算:任务已投递但还没跑过任何 turn(注入的消息尚未执行,这时报终态就是撒谎),
181
+ 以及阶梯还有余额的那次空闲。阶梯重发的是任务到手时的那条消息——同样的头部、任务文本和附件路径,
182
+ 外加一行说明上一轮没有 completion——但不重发 role prose,它已经在上下文里;图片也不重挂,
183
+ 路径以文本出现,同样的字节不会进上下文两次。同一任务收到新 envelope 时计数重来。
184
+
185
+ `head` 恒为单行、上限 200 字符,与 client 写入 `out_head` 和回执携带的内容一致。每个任务的 head
186
+ 只有一个来源:显式 `onlyne_complete` 带的 `text`(有则原样采用)、出错 turn 报的错误,或阶梯自己
187
+ 的那一行。最后一段 assistant 文本只是 `text` 完全缺失的 `onlyne_complete` 的退路——调用之后再说
188
+ 的话顶不掉调用交出的内容,此外没有任何东西读它。
170
189
 
171
190
  报出去的 completion 会结束所在 session 的进程。`report.complete` 以请求形式发出,client 只有
172
191
  在结算 session 行、ack 掉投递、并写好 `Completion` envelope 之后才应答,插件就在这个应答处
173
192
  让 pi 退出。socket 当时送不出去的 outcome 会被记住,并在下一次 `hello` 后补发,那次补发的
174
193
  应答就是结束进程的交接点。被宿主拒掉的 completion 不会让进程退出,任务不会因为退出而丢失。
175
194
 
176
- 最后一条上报是:在已结算的 outcome 旁边带一个 `agent: "idle"` 的观测,发在 completion 被
195
+ 最后一条上报只说 agent 这一维:`agent: "idle"` 的观测,发在 completion 被
177
196
  ack 之后、进程退出之前。completion 是按 client 手里的元组结算 session 行的,而收尾那一轮
178
197
  就是最后一次 heartbeat 时,这个元组读到的仍是 `running`;此后没有任何东西再观测这个进程,
179
198
  所以缺了这条上报,已退出的 session 会一直说 `running`。最后一次心跳本来就是 idle 时,插件
@@ -224,7 +243,7 @@ stderr 告警并忽略,把机会让回文件。
224
243
  | 作用域 | 本会话自己的投递,仅进程内存:重连不丢,会话重启从空开始,不去猜上一个进程发过什么 |
225
244
  | 豁免 | `force: true` 加非空 `reason`;只在守卫拒绝时才起作用 |
226
245
  | 审计 | 被豁免的 completion,ledger head 以 `relay-guard-forced: <reason>` 开头;调用带了 `text` 时紧接其后 |
227
- | 不管的路 | 自动终态:`agent_settled` 与 `recycle{outcome}` 照旧结算欠着接力的任务 |
246
+ | 不管的路 | 插件不经过模型就报出的终态:turn 出错、idle 阶梯用尽,以及 `recycle{outcome}` |
228
247
 
229
248
  `relay.toml` 是 TOML 的封闭子集:扁平的 `key = value` 行、上面两个键、单行双引号字符串数组、
230
249
  `#` 注释。子集之外一律 stderr 告警并忽略。它刻意不放 `.onlyne/config.toml`:client 以
@@ -238,17 +257,19 @@ stderr 告警并忽略,把机会让回文件。
238
257
 
239
258
  - **report 序号基址。** 插件自己的 `report` 序号从 1000 起,不是 1。client 把自身的派发事件
240
259
  (`created`、资源 attach、`ready`)写进同一个 `(generation, seq)` 水位,reducer 会静默丢弃
241
- 水位及以下的报告(`crates/onlyne-session/src/reconcile.rs`),所以从 1 起会丢掉最初的观测。
260
+ 水位及以下的报告(`crates/onlyne-session/src/reconcile/`),所以从 1 起会丢掉最初的观测。
242
261
  其余版本语义与规范一致。
243
262
  - **`observed` 是完整的 `Observation`。** `report.heartbeat` 携带整个合法状态元组
244
263
  (`version`、`generation_live`、`isolate_after`、`terminate_after`、`mismatch_count`、
245
- `agent`、`delivery`、`resource`、`recovery`、`outcome`、`public`),不是
246
- `{"state": "running"}` 这种简写。宿主会反序列化它,`is_legal` 不接受的一律拒绝。本插件只管
247
- `agent` 这一维(turn hooks),`delivery` 保持 `none`、`outcome` 保持 `pending`——在它报出
248
- completion 之前这就是它的事实。`resource` 报 `attached`,因为宿主的派发路径已经记过这次
249
- attach。
264
+ `agent`、`delivery`、`resource`、`recovery`),不是
265
+ `{"state": "running"}` 这种简写。宿主会反序列化它,先改写 client 主张的那六项,再套用 `is_legal`。本插件主张的
266
+ 是 `agent`(turn hooks)、`resource`(进程还活在那块 pane 里,attach 早被宿主派发路径记过)
267
+ 与 `host` 绑定这三件事。`delivery`、`recovery`、`generation_live`、`isolate_after`、
268
+ `terminate_after`、`mismatch_count` 它一个见证都没有:client 在归约前会用自己
269
+ 的 intent 队列、reducer 历史与角色配置重写这六项,插件填什么都不会被读。任务的 outcome 与公开视图
270
+ 更已经不在元组里,所以也就不再上报。
250
271
  - **`ready` 每连接报一次。** 宿主的 hand-off 路径
251
- (`crates/onlyne-client/src/dispatch.rs::hand_session`)在把 session 交给挂载的插件时已经报过
272
+ (`crates/onlyne-client/src/session/dispatch/delivery.rs::hand_session`)在把 session 交给挂载的插件时已经报过
252
273
  `ready`,所以插件再报一次在宿主侧是 no-op。插件仍然发送:先挂载、后有活正是 ready barrier
253
274
  描述的情形,而且只花一帧。
254
275
  - **从不发 `cluster_ref`。** 本插件代表本地 role 说话,从不代表 aggregate;Rust 侧出于同样的
@@ -309,12 +330,12 @@ stderr 告警并忽略,把机会让回文件。
309
330
  | 反复 `reconnecting in 4000ms` | client 已停或 socket 被替换 | `onlyne --server-root … roles` |
310
331
  | `ready refused: internal: unknown session for …` | 插件为 client 从未暂存的任务报了 ready(手工起 pi 时的正常现象) | 让 client 拉起 pi,而不是手工起 |
311
332
  | `assign` 一直不来 | client 的 `session_command` 没能拉起 pi,或 `inject` 被降级 | client 日志里的 spawn 行;`/onlyne status` 看能力集 |
312
- | ledger 停在 `in_flight` | 没有 completion:没跑 turn,或 `agent_settled` 没触发 | pi session 文件里的 `onlyne-assign` / `onlyne-complete` 条目 |
333
+ | ledger 停在 `in_flight` | 还没有 completion:没跑过 turn(注入的消息尚未执行),或阶梯还在提醒(`idleReminders`) | pi session 文件里的 `onlyne-assign` 条目和其后注入的提醒;`onlyne` 面板里的 `reminder n of m`;`/onlyne status` 看任务与阶段 |
313
334
  | `onlyne_complete` 回答 `relay guard: missing handoff to: …` | 工作区的 spec(或顶替它的 `relay.toml`)点名了一个本会话从未触达的 role | 日常通知显示在 `onlyne` 面板;stderr 保留 `relay guard from …` 等拒绝、socket 错误、超时与帧错误;`required=…` 说明策略;`relay guard: missing handoff …` 列出已投递集合 |
314
335
  | `hello` 后立刻 `forbidden` / 断连 | mount role 与 client 的 role 不一致 | `hello.args.mount.role` 对该工作区的 role |
315
336
  | `frame_too_large` | 正文超过 8 MiB | 只会由超限的出站图片触发;上限来自核心 |
316
337
  | 工具缺失 | 该 pi 版本没有 `pi.registerTool` | `/onlyne status`;对照上面的能力表 |
317
- | 会话在 `exited` 之后又回到 `idle` | completion 之后还落进了一条 heartbeat 快照,带着 `outcome: pending` | 看 session 日志里 `completion` 之后的 report 顺序;插件对已完成任务不再上报 |
338
+ | 会话在 `exited` 之后又回到 `idle` | completion 之后又落进一条 turn-end heartbeat,把 agent 维搬了回去 | 看 session 日志里 `completion` 之后的 report 顺序;插件对已完成任务不再上报,而 client 自己的 `delivery` 两条都会保住 |
318
339
  | supervisor 看板一个 tab 都不列 | 没有 live session 上报过 pane:适配器版本早于这条上报,或这个 pi 不在 Orca pane 里 | `onlyne --server-root … sessions --json` 看 `projection.observed.host.orca.pane_key`;在 pane 里跑 `env \| grep ORCA_` |
319
340
 
320
341
  `/onlyne status` 打印实时状态(`connected`、`socket`、`role`、`sessionId`、`generation`、
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-onlyne",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Onlyne agent adapter for pi: the session lifecycle an onlyne role client expects from a pi host.",
5
5
  "type": "module",
6
6
  "license": "MIT",