pi-post 0.6.1 → 0.6.3

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/DESIGN.md CHANGED
@@ -51,9 +51,9 @@ is directory information, not lifecycle management — pi-post still
51
51
  never spawns or resumes anything itself.
52
52
 
53
53
  v0.2.0 had a second kind — standing addresses, one per directory, so
54
- mail could wait for sessions that did not exist yet. Removed in v0.3.0:
54
+ messages could wait for sessions that did not exist yet. Removed in v0.3.0:
55
55
  in a busy repository, directory identity is not task identity, so
56
- standing mail raced among concurrent sessions, delivered to the wrong
56
+ standing messages raced among concurrent sessions, delivered to the wrong
57
57
  successor, and — because consumption is the receipt — misdelivered
58
58
  *silently and destructively*. The lesson is recorded as a non-goal
59
59
  below: how sessions come to exist is not the transport's business.
@@ -93,11 +93,11 @@ One message per file, named `<sentAt ms, 13 digits>-<8 hex nonce>.json`:
93
93
  it. Nothing is delivered twice; consumption is the receipt.
94
94
  5. Each message passes the inbound guard (mode + loop caps), then enters
95
95
  context wrapped in the boundary preamble:
96
- - live mail → `deliverAs: "steer"`, `triggerTurn: true` — lands between
96
+ - live messages → `deliverAs: "steer"`, `triggerTurn: true` — land between
97
97
  tool calls; **wakes an idle session**, so a freshly spawned worker's
98
98
  first turn can be the brief itself
99
99
  - startup/resume drain → `deliverAs: "nextTurn"` — waits in context for
100
- the next prompt; queued mail never starts a turn on its own
100
+ the next prompt; a queued message never starts a turn on its own
101
101
 
102
102
  ## The boundary
103
103
 
@@ -116,8 +116,8 @@ Each is pinned by a test.
116
116
  - **A reader never sees half a message.** Rename-into-place; only
117
117
  `.json` is read.
118
118
  - **Nothing is delivered twice.** Unlink before handling.
119
- - **Mail outranks tidiness.** No sweep deletes a non-empty mailbox, and
120
- queued mail pins the target's registry record: a record is swept only
119
+ - **Messages outrank tidiness.** No sweep deletes a non-empty mailbox, and
120
+ queued messages pin the target's registry record: a record is swept only
121
121
  when it is offline, stale (7 days), *and* its mailbox is empty. Empty
122
122
  inbox directories no record names are removed.
123
123
  - **Loops terminate structurally.** Identical body from one sender inside
package/README.md CHANGED
@@ -39,7 +39,7 @@ smuggling state between sessions.
39
39
 
40
40
  **An address that outlives the process.** A session address names a
41
41
  conversation, not a process: the same session resumed tomorrow answers to
42
- the same address, and mail queued while it was closed lands in-context on
42
+ the same address, and messages queued while it was closed land in-context on
43
43
  resume. A directory path as a target is a *query* — it resolves to the
44
44
  session registered in that directory, live sessions first, ambiguity
45
45
  refused.
@@ -52,15 +52,15 @@ you is a dead end: anything you can see, you can message *and* reopen.
52
52
 
53
53
  **Wake-on-idle delivery.** A message to an idle session starts its turn.
54
54
  Spawn a worker in its worktree, send the brief — the brief *is* the
55
- worker's first turn. No "check your mail" incantations.
55
+ worker's first turn. No "check your messages" incantations.
56
56
 
57
57
  **Two tools.** `send_message` sends text to a session, path, address, or
58
58
  session id and reports **delivered** (consumed now) or **queued** (waiting
59
- on disk). `list_sessions` shows known sessions, presence, queued mail, and
59
+ on disk). `list_sessions` shows known sessions, presence, queued messages, and
60
60
  resume handles. `/inbox` peeks without consuming.
61
61
 
62
62
  **A CLI for everything that isn't a pi session.** `pi-post send` lets an
63
- autonomous run's exit hook, a Claude Code hook, or any script mail a
63
+ autonomous run's exit hook, a Claude Code hook, or any script message a
64
64
  session. `--reply-to` defaults from `PI_SESSION_ID`, so a message sent from
65
65
  inside a pi bash tool routes replies home automatically.
66
66
 
@@ -81,7 +81,7 @@ Nothing to enable; every session registers itself on startup.
81
81
  | Surface | Effect |
82
82
  |---|---|
83
83
  | `send_message` (tool) | Send text to one or more sessions (name, path, address, or session id); reports **delivered** or **queued** per target |
84
- | `list_sessions` (tool) | Known sessions, live first with ages, queued mail counts, resume handles; stale offline rows collapse unless `all` |
84
+ | `list_sessions` (tool) | Known sessions, live first with ages, queued message counts, resume handles; stale offline rows collapse unless `all` |
85
85
  | `/inbox` | Peek at this session's queued messages without consuming them |
86
86
  | `/sessions` | The `list_sessions` listing, without spending a model turn |
87
87
  | `pi-post send` (CLI) | Send from any process: `--to` (repeatable), `--body`/stdin, `--from`, `--reply-to` |
@@ -145,6 +145,11 @@ sessions can read state; only one can consume a message.
145
145
  | `PI_POST_FROM` | — | Default `--from` label for the CLI |
146
146
  | `PI_POST_REPLY_TO` | — | Default `--reply-to` address for the CLI |
147
147
 
148
+ The extension also *sets* one variable: `PI_SESSION_ADDRESS`, the session's
149
+ own `s-…` address, exported at session start so child processes (bash tools,
150
+ spawned scripts) can capture a reply target without shelling out to
151
+ `pi-post whoami`.
152
+
148
153
  The directory is created `0700` and messages `0600`.
149
154
 
150
155
  ## Limits
@@ -195,7 +200,8 @@ before changing behavior, and never weaken a case to make a change pass.
195
200
  - [pi-intercom](https://www.npmjs.com/package/pi-intercom) -- broker-based
196
201
  1:1 session messaging with a TUI overlay and pi-subagents integration.
197
202
  - [pi-messenger](https://www.npmjs.com/package/pi-messenger) -- a shared
198
- chat room with file reservations, built for swarms rather than mail.
203
+ chat room with file reservations, built for swarms rather than
204
+ point-to-point messaging.
199
205
 
200
206
  ## Development
201
207
 
package/bin/pi-post.mjs CHANGED
@@ -277,15 +277,15 @@ function list(args) {
277
277
  for (const record of records) {
278
278
  const live = isLive(record);
279
279
  const queued = queuedCount(record.address);
280
- // Stale offline rows collapse into a count — unless they hold mail.
280
+ // Stale offline rows collapse into a count — unless they hold messages.
281
281
  if (!args.all && !live && queued === 0 && now - record.lastSeen > DAY_MS) {
282
282
  hidden++;
283
283
  continue;
284
284
  }
285
285
  const state = live ? "live" : `offline ${relativeAge(record.lastSeen, now)}`;
286
- const mail = queued > 0 ? `, ${queued} queued` : "";
286
+ const queuedNote = queued > 0 ? `, ${queued} queued` : "";
287
287
  console.log(
288
- `${record.name} — ${record.address} (${state}${mail}) ${record.cwd} ` +
288
+ `${record.name} — ${record.address} (${state}${queuedNote}) ${record.cwd} ` +
289
289
  `[pi --session ${resumeHandle(record.sessionId)}]`,
290
290
  );
291
291
  }
@@ -88,6 +88,10 @@ export default function (pi: ExtensionAPI) {
88
88
  const canonical = canonicalPath(ctx.cwd);
89
89
  selfAddress = sessionAddress(sessionId);
90
90
  selfName = pi.getSessionName() ?? defaultSessionName(canonical, selfAddress);
91
+ // Expose the address to child processes (bash tools inherit process.env),
92
+ // so scripts can capture a send_message target without shelling out to
93
+ // `pi-post whoami` or re-deriving the hash from PI_SESSION_ID.
94
+ process.env.PI_SESSION_ADDRESS = selfAddress;
91
95
 
92
96
  ensureDirs(root, selfAddress);
93
97
  writeRecord(root, {
@@ -103,11 +107,11 @@ export default function (pi: ExtensionAPI) {
103
107
  sweepRegistry(root);
104
108
  sweepInboxes(root);
105
109
 
106
- // Queued mail waits in context for the first prompt; it never starts a turn.
110
+ // Queued messages wait in context for the first prompt; they never start a turn.
107
111
  await drainAll(ctx, "nextTurn");
108
112
 
109
- const onMail = () => void drainAll(ctx, "steer");
110
- watchers = [watchInbox(root, selfAddress, onMail)];
113
+ const onMessage = () => void drainAll(ctx, "steer");
114
+ watchers = [watchInbox(root, selfAddress, onMessage)];
111
115
  heartbeat = setInterval(() => selfAddress && touchRecord(root, selfAddress), HEARTBEAT_MS);
112
116
  heartbeat.unref?.();
113
117
  });
@@ -209,12 +213,12 @@ export default function (pi: ExtensionAPI) {
209
213
  label: "List Sessions",
210
214
  description:
211
215
  "List pi sessions known to pi-post: their names, addresses, presence (live/offline, " +
212
- "with age), queued mail counts, and each session's resume handle ([pi --session …], run " +
216
+ "with age), queued message counts, and each session's resume handle ([pi --session …], run " +
213
217
  "from the listed directory). Live sessions come first; offline sessions unseen for over " +
214
218
  "a day are collapsed into a count unless all is set. Any directory path is also a valid " +
215
219
  "send_message target even if nothing is listed for it.",
216
220
  promptSnippet:
217
- "List pi sessions reachable by message, with presence, queued mail, and resume handles",
221
+ "List pi sessions reachable by message, with presence, queued messages, and resume handles",
218
222
  parameters: Type.Object({
219
223
  all: Type.Optional(
220
224
  Type.Boolean({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-post",
3
- "version": "0.6.1",
4
- "description": "Messages between pi sessions — delivered mid-task or queued until they return. Briefs, findings, and handoffs straight into the receiving agent's context.",
3
+ "version": "0.6.3",
4
+ "description": "Messages between pi sessions — delivered mid-task or queued until they return.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi-extension",
package/src/format.ts CHANGED
@@ -68,16 +68,16 @@ export function formatListing(
68
68
  const live = presence(record) === "live";
69
69
  const queued = queuedCount(root, record.address);
70
70
  const self = record.address === selfAddress;
71
- // Stale offline rows collapse into a count — unless they hold mail
72
- // (mail outranks tidiness) or the caller asked for everything.
71
+ // Stale offline rows collapse into a count — unless they hold messages
72
+ // (messages outrank tidiness) or the caller asked for everything.
73
73
  if (!options.all && !live && !self && queued === 0 && now - record.lastSeen > DAY_MS) {
74
74
  hidden++;
75
75
  continue;
76
76
  }
77
77
  const state = live ? "live" : `offline ${relativeAge(record.lastSeen, now)}`;
78
- const mail = queued > 0 ? `, ${queued} queued` : "";
78
+ const queuedNote = queued > 0 ? `, ${queued} queued` : "";
79
79
  lines.push(
80
- `${record.name} — ${record.address} (${state}${mail})${self ? " [self]" : ""} ${record.cwd} ` +
80
+ `${record.name} — ${record.address} (${state}${queuedNote})${self ? " [self]" : ""} ${record.cwd} ` +
81
81
  `[pi --session ${resumeHandle(record.sessionId)}]`,
82
82
  );
83
83
  }
package/src/registry.ts CHANGED
@@ -93,7 +93,7 @@ export function presence(record: SessionRecord): Presence {
93
93
 
94
94
  /**
95
95
  * Remove registry records for sessions that are offline and stale. A record
96
- * whose mailbox holds mail is never swept: queued mail would still deliver on
96
+ * whose mailbox holds messages is never swept: queued messages would still deliver on
97
97
  * resume (the address derives from the session id), but losing the record
98
98
  * hides the queued count and breaks name/path resolution to the target.
99
99
  */
@@ -102,7 +102,7 @@ export function sweepRegistry(root: string, maxAgeMs = 7 * 24 * 60 * 60 * 1000):
102
102
  for (const record of listRecords(root)) {
103
103
  if (presence(record) !== "offline") continue;
104
104
  if (now - record.lastSeen <= maxAgeMs) continue;
105
- if (queuedCount(root, record.address) > 0) continue; // mail keeps the record alive
105
+ if (queuedCount(root, record.address) > 0) continue; // queued messages keep the record alive
106
106
  try {
107
107
  unlinkSync(join(registryDir(root), `${record.address}.json`));
108
108
  } catch {
@@ -114,8 +114,8 @@ export function sweepRegistry(root: string, maxAgeMs = 7 * 24 * 60 * 60 * 1000):
114
114
  /**
115
115
  * Remove empty inbox directories that no registry record names (orphans from
116
116
  * swept records or removed address schemes). `rmdirSync` refuses non-empty
117
- * directories, so queued mail or a mid-flight `.tmp` deposit blocks removal
118
- * structurally — mail outranks tidiness.
117
+ * directories, so a queued message or a mid-flight `.tmp` deposit blocks removal
118
+ * structurally — messages outrank tidiness.
119
119
  */
120
120
  export function sweepInboxes(root: string): void {
121
121
  const known = new Set(listRecords(root).map((r) => r.address));