pi-onlyne 0.8.0 → 0.8.1

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
@@ -189,14 +189,14 @@ The extension also supports `.onlyne/channels/loopback/in` when FIFO IO is enabl
189
189
 
190
190
  ## Swarm mode
191
191
 
192
- Swarm mode lets `onlyne-swarm` own task routing for a generated agent workspace. Enable it in `.onlyne/config.toml`:
192
+ Swarm mode lets `onlyne-swarm >= 0.5.0` own task routing for a generated agent workspace. `pi-onlyne 0.8.1` uses the scheduler's `delivery: scheduler` header for task claims, so these versions upgrade together. Enable it in `.onlyne/config.toml`:
193
193
 
194
194
  ```toml
195
195
  [swarm]
196
196
  enabled = true
197
197
  ```
198
198
 
199
- A swarm Pi session subscribes to loopback events, reports `swarm_ready`, accepts one hop atomically, spawns continuations with `swarm_send` (fire-and-forget), and writes `swarm_complete` (done signal). Scheduler then sends a header-only recycle control wire; pi-onlyne intercepts it before model delivery, sends `swarm_recycled`, stops the watch, clears the slot, and self-exits. `swarm_quit` sends the same ack with `quit:<reason>` then self-exits, so the scheduler records failed immediately. Downstream results travel through files and the ledger; nothing waits.
199
+ A swarm Pi session subscribes to loopback events, reports `swarm_ready`, accepts one scheduler-delivered hop atomically, spawns continuations with `swarm_send` (fire-and-forget), and writes `swarm_complete` (done signal). A raw `swarm_send` relay carries no `delivery` header and only creates the scheduler task row; the scheduler's second delivery adds `delivery: scheduler`, which pi-onlyne uses as the claim gate. This keeps arbitrary role prose out of transport authentication. Scheduler then sends a header-only recycle control wire; pi-onlyne intercepts it before model delivery, sends `swarm_recycled`, stops the watch, clears the slot, and self-exits. `swarm_quit` sends the same ack with `quit:<reason>` then self-exits, so the scheduler records failed immediately. Downstream results travel through files and the ledger; nothing waits.
200
200
 
201
201
  For automatic startup in a generated workspace, add `.pi/onlyne.json`:
202
202
 
package/SPEC.md CHANGED
@@ -84,10 +84,12 @@ One session sees one toolset. The surface is chosen at `session_start` from
84
84
  - When swarm is on, generic in/out auto-handling is disabled: the scheduler owns
85
85
  input/output. Only loopback messages carrying a `---swarm` body header enter
86
86
  the session, via the `followUp` task queue.
87
- - Session model: one session carries exactly one hop, no exceptions. A new
88
- header claims the slot; any further header while claimed is ignored
89
- (the scheduler always opens a new session, so this guard never fires
90
- on the normal path). No waiting, no callbacks, no parent bookkeeping.
87
+ - Session model: one session carries exactly one scheduler-delivered hop. A
88
+ `delivery: scheduler` header field claims the slot; raw `swarm_send` relay
89
+ wires and out wires omit it and never claim. Any further delivered header
90
+ while claimed is ignored (the scheduler always opens a new session, so this
91
+ guard never fires on the normal path). No waiting, no callbacks, no parent
92
+ bookkeeping.
91
93
  - Startup handshake: swarm watch sends the `swarm_ready` op
92
94
  (`{workspace, terminal_handle}`) so the scheduler can match a pending task.
93
95
  `ONLYNE_SWARM_TASK` env and `ORCA_TERMINAL_HANDLE`/`ONLYNE_TERMINAL_HANDLE`
@@ -15,7 +15,12 @@ export class SwarmSlot {
15
15
  const parsed = parseSwarmHeader(text);
16
16
  if (!parsed)
17
17
  return "not-swarm";
18
- const { header, payload } = parsed;
18
+ const { header, delivery, payload } = parsed;
19
+ // Only the scheduler's second delivery is claimable. A worker's raw
20
+ // swarm_send relay and every out wire omit this structured field, so
21
+ // history catchup cannot claim either one as an executable task.
22
+ if (delivery !== "scheduler")
23
+ return "not-swarm";
19
24
  if (!this.taskId) {
20
25
  this.taskId = header.task_id;
21
26
  this.from = header.from;
package/dist/swarm.d.ts CHANGED
@@ -5,8 +5,11 @@ export interface SwarmHeader {
5
5
  transfer_send_to: string;
6
6
  attempt: number;
7
7
  }
8
+ /** `scheduler` identifies the scheduler's second delivery. Raw relay and out
9
+ * wires omit it, so a cold-start history scan cannot claim them. */
8
10
  export interface SwarmMessage {
9
11
  header: SwarmHeader;
12
+ delivery: string;
10
13
  payload: string;
11
14
  }
12
15
  /** Parse a `---swarm` body header. Returns null for non-swarm messages. */
package/dist/swarm.js CHANGED
@@ -28,6 +28,7 @@ export function parseSwarmHeader(text) {
28
28
  let from = ".";
29
29
  let transfer_send_to = "";
30
30
  let attempt = 1;
31
+ let delivery = "";
31
32
  let sawTransfer = false;
32
33
  for (const line of headRaw.split("\n")) {
33
34
  const t = line.trim();
@@ -50,12 +51,14 @@ export function parseSwarmHeader(text) {
50
51
  return null;
51
52
  else if (k === "attempt")
52
53
  attempt = Number.parseInt(v, 10) || 1;
54
+ else if (k === "delivery")
55
+ delivery = v;
53
56
  }
54
57
  if (!sawTransfer)
55
58
  return null;
56
59
  if (!task_id || !UUID_RE.test(task_id.trim()))
57
60
  return null;
58
- return { header: { task_id: task_id.trim(), from, transfer_send_to, attempt }, payload };
61
+ return { header: { task_id: task_id.trim(), from, transfer_send_to, attempt }, delivery, payload };
59
62
  }
60
63
  export function parseSwarmCtl(text) {
61
64
  if (!text.startsWith("---swarm-ctl\n"))
@@ -89,9 +92,13 @@ export function parseSwarmCtl(text) {
89
92
  }
90
93
  /** Render a swarm body header in front of a Markdown payload. */
91
94
  export function renderSwarmHeader(header, role, payloadMarkdown) {
95
+ // Raw relay and out wires intentionally omit `delivery`. Scheduler owns the
96
+ // second delivery step and adds `delivery: scheduler` there.
92
97
  let s = `---swarm\ntask_id: ${header.task_id}\nfrom: ${header.from}\ntransfer_send_to: ${header.transfer_send_to}\nattempt: ${header.attempt}\n---\n`;
93
- if (role)
94
- s += `## role: ${role}\n\n${role}\n`;
98
+ // Flat role text follows the delimiter once, with no duplicate echo.
99
+ if (role) {
100
+ s += role.endsWith("\n") ? role + "\n" : role + "\n\n";
101
+ }
95
102
  s += payloadMarkdown;
96
103
  if (!s.endsWith("\n"))
97
104
  s += "\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-onlyne",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Pi extension tools for sending messages through Onlyne.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",