@comput/pi-intercom 0.3.0 → 0.4.2

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.
Files changed (58) hide show
  1. package/README.md +137 -25
  2. package/dist/activity.d.ts +12 -0
  3. package/dist/activity.js +73 -0
  4. package/dist/activity.js.map +1 -0
  5. package/dist/config.d.ts +39 -0
  6. package/dist/config.js +182 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +186 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/launcher.d.ts +17 -0
  12. package/dist/launcher.js +77 -0
  13. package/dist/launcher.js.map +1 -0
  14. package/dist/monitor-launcher.d.ts +17 -0
  15. package/dist/monitor-launcher.js +136 -0
  16. package/dist/monitor-launcher.js.map +1 -0
  17. package/dist/monitor-view.d.ts +6 -0
  18. package/dist/monitor-view.js +134 -0
  19. package/dist/monitor-view.js.map +1 -0
  20. package/dist/monitor.d.ts +22 -0
  21. package/dist/monitor.js +171 -0
  22. package/dist/monitor.js.map +1 -0
  23. package/dist/observability.d.ts +79 -0
  24. package/dist/observability.js +208 -0
  25. package/dist/observability.js.map +1 -0
  26. package/dist/reports.d.ts +12 -0
  27. package/dist/reports.js +160 -0
  28. package/dist/reports.js.map +1 -0
  29. package/dist/runtime.d.ts +59 -0
  30. package/dist/runtime.js +458 -0
  31. package/dist/runtime.js.map +1 -0
  32. package/dist/snapshot.d.ts +15 -0
  33. package/dist/snapshot.js +128 -0
  34. package/dist/snapshot.js.map +1 -0
  35. package/dist/transport.d.ts +24 -0
  36. package/dist/transport.js +122 -0
  37. package/dist/transport.js.map +1 -0
  38. package/dist/worker-status.d.ts +26 -0
  39. package/dist/worker-status.js +76 -0
  40. package/dist/worker-status.js.map +1 -0
  41. package/package.json +13 -7
  42. package/references/observability.md +66 -60
  43. package/references/roadmap.md +68 -0
  44. package/src/activity.ts +66 -0
  45. package/src/config.ts +1 -1
  46. package/src/index.ts +51 -23
  47. package/src/monitor-launcher.ts +110 -0
  48. package/src/monitor-view.ts +123 -0
  49. package/src/monitor.ts +143 -0
  50. package/src/observability.ts +6 -1
  51. package/src/reports.ts +122 -0
  52. package/src/runtime.ts +45 -7
  53. package/src/{dashboard.ts → snapshot.ts} +9 -60
  54. package/src/transport.ts +11 -2
  55. package/src/worker-status.ts +66 -0
  56. package/dashboard/app.js +0 -182
  57. package/dashboard/index.html +0 -41
  58. package/dashboard/style.css +0 -82
@@ -0,0 +1,24 @@
1
+ import type { ReportStatus } from './reports.js';
2
+ export declare const BODY_LIMIT: number;
3
+ export declare const RECEIPT_TIMEOUT = 5000;
4
+ export type Kind = 'message' | 'report' | 'registration' | 'status' | 'request_status' | 'reload' | 'stop' | 'close';
5
+ export interface Envelope {
6
+ version: 1;
7
+ kind: Kind;
8
+ from: string;
9
+ to: string;
10
+ payload: Record<string, unknown>;
11
+ /** Optional diagnostics correlation only; never authentication or agent identity. */
12
+ correlationId?: string;
13
+ }
14
+ export declare function reportPayload(payload: Record<string, unknown>): {
15
+ status: ReportStatus;
16
+ summary: string;
17
+ };
18
+ export declare function envelope(value: unknown): Envelope;
19
+ export interface Endpoint {
20
+ port: number;
21
+ close(): Promise<void>;
22
+ }
23
+ export declare function listen(preferred: number | undefined, accept: (message: Envelope) => Promise<void>): Promise<Endpoint>;
24
+ export declare function send(destinationPort: number, message: Envelope, timeout?: number): Promise<void>;
@@ -0,0 +1,122 @@
1
+ import http from 'node:http';
2
+ import { fail, port, text } from './config.js';
3
+ export const BODY_LIMIT = 64 * 1024;
4
+ export const RECEIPT_TIMEOUT = 5000;
5
+ export function reportPayload(payload) {
6
+ if (!['blocked', 'needs_decision', 'ready_for_review', 'clear'].includes(payload.status))
7
+ fail('invalid report status');
8
+ const status = payload.status;
9
+ const summary = payload.summary ?? '';
10
+ if (typeof summary !== 'string' || summary.length > 2000 || (status !== 'clear' && !summary.trim()) || (status === 'clear' && summary !== ''))
11
+ fail('report requires a public summary of 1–2000 characters, or empty summary for clear');
12
+ return { status, summary };
13
+ }
14
+ export function envelope(value) {
15
+ const m = value;
16
+ if (!m || m.version !== 1 || !['message', 'report', 'registration', 'status', 'request_status', 'reload', 'stop', 'close'].includes(m.kind))
17
+ fail('invalid wire schema/version/kind');
18
+ text(m.from, 'sender sessionId', 256);
19
+ text(m.to, 'recipient sessionId', 256);
20
+ if (m.correlationId !== undefined && (typeof m.correlationId !== 'string' || !/^[a-zA-Z0-9-]{1,128}$/.test(m.correlationId)))
21
+ fail('invalid correlation ID');
22
+ if (!m.payload || Array.isArray(m.payload) || typeof m.payload !== 'object')
23
+ fail('invalid payload');
24
+ if (m.kind === 'message')
25
+ text(m.payload.message, 'message', 48000);
26
+ if (m.kind === 'report')
27
+ reportPayload(m.payload);
28
+ if (m.kind === 'registration' || m.kind === 'status')
29
+ port(m.payload.port);
30
+ if (m.kind === 'registration')
31
+ text(m.payload.projectDirectory, 'projectDirectory');
32
+ if (m.kind === 'status' && typeof m.payload.busy !== 'boolean')
33
+ fail('invalid busy flag');
34
+ return m;
35
+ }
36
+ export async function listen(preferred, accept) {
37
+ const server = http.createServer(async (req, res) => {
38
+ const reply = (status, data) => { if (!res.destroyed) {
39
+ res.writeHead(status, { 'content-type': 'application/json', connection: 'close' });
40
+ res.end(JSON.stringify(data));
41
+ } };
42
+ if (req.method !== 'POST' || req.url !== '/intercom') {
43
+ reply(404, { error: 'POST /intercom required' });
44
+ req.resume();
45
+ return;
46
+ }
47
+ let bytes = 0;
48
+ const chunks = [];
49
+ try {
50
+ for await (const chunk of req) {
51
+ bytes += chunk.length;
52
+ if (bytes > BODY_LIMIT) {
53
+ reply(413, { error: 'body exceeds 64 KiB' });
54
+ return;
55
+ }
56
+ chunks.push(Buffer.from(chunk));
57
+ }
58
+ await accept(envelope(JSON.parse(Buffer.concat(chunks).toString('utf8'))));
59
+ reply(202, { accepted: true });
60
+ }
61
+ catch (error) {
62
+ reply(400, { error: String(error) });
63
+ }
64
+ });
65
+ server.requestTimeout = RECEIPT_TIMEOUT;
66
+ server.headersTimeout = RECEIPT_TIMEOUT;
67
+ server.setTimeout(RECEIPT_TIMEOUT, socket => socket.destroy());
68
+ const bind = (p) => new Promise((resolve, reject) => {
69
+ const error = (e) => { server.off('listening', ready); reject(e); };
70
+ const ready = () => { server.off('error', error); resolve(); };
71
+ server.once('error', error);
72
+ server.once('listening', ready);
73
+ server.listen(p, '127.0.0.1');
74
+ });
75
+ try {
76
+ await bind(preferred ?? 0);
77
+ }
78
+ catch (e) {
79
+ if (preferred && e.code === 'EADDRINUSE')
80
+ await bind(0);
81
+ else
82
+ throw e;
83
+ }
84
+ const address = server.address();
85
+ if (!address || typeof address === 'string')
86
+ fail('listener has no port');
87
+ return { port: address.port, close: () => new Promise((resolve, reject) => {
88
+ server.close(e => e ? reject(e) : resolve());
89
+ server.closeAllConnections();
90
+ }) };
91
+ }
92
+ export async function send(destinationPort, message, timeout = RECEIPT_TIMEOUT) {
93
+ port(destinationPort);
94
+ envelope(message);
95
+ const body = JSON.stringify(message);
96
+ if (Buffer.byteLength(body) > BODY_LIMIT)
97
+ fail('message exceeds 64 KiB');
98
+ // Node HTTP bypasses proxy environment variables; never redirects or retries.
99
+ await new Promise((resolve, reject) => {
100
+ const req = http.request({ hostname: '127.0.0.1', port: destinationPort, path: '/intercom', method: 'POST', headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body) } }, res => {
101
+ let result = '';
102
+ res.on('data', chunk => { result += chunk; if (result.length > BODY_LIMIT)
103
+ req.destroy(new Error('oversized acknowledgment')); });
104
+ res.on('error', reject);
105
+ res.on('end', () => {
106
+ try {
107
+ if (res.statusCode !== 202 || JSON.parse(result).accepted !== true)
108
+ fail(`receipt rejected (${res.statusCode}): ${result}`);
109
+ resolve();
110
+ }
111
+ catch (e) {
112
+ reject(e);
113
+ }
114
+ });
115
+ });
116
+ const timer = setTimeout(() => req.destroy(new Error('PiIntercom: receipt timeout; outcome unknown; no automatic retry')), timeout);
117
+ req.on('close', () => clearTimeout(timer));
118
+ req.on('error', reject);
119
+ req.end(body);
120
+ });
121
+ }
122
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ObservationSnapshot } from './snapshot.js';
2
+ export declare function workerObservation(snapshot: ObservationSnapshot, sessionId: string, now?: number): {
3
+ observedStatus: string;
4
+ lastActivity: string;
5
+ observedAt: string | null;
6
+ observationAgeSeconds: number | null;
7
+ stale: boolean | null;
8
+ evidence: string;
9
+ };
10
+ /** Bounded JSON data for agents/any presentation layer. No Telegram, UI, probing or model calls. */
11
+ export declare function workerStatusPage(snapshot: ObservationSnapshot, options?: {
12
+ name?: string;
13
+ offset?: number;
14
+ limit?: number;
15
+ }, now?: number): {
16
+ version: number;
17
+ source: string;
18
+ generatedAt: string;
19
+ statusMeaning: string;
20
+ truncated: boolean;
21
+ errors: string[];
22
+ totalInSnapshot: number;
23
+ offset: number;
24
+ nextOffset: number | null;
25
+ workers: Record<string, unknown>[];
26
+ };
@@ -0,0 +1,76 @@
1
+ const labels = {
2
+ processing: 'Processing', thinking: 'Thinking', responding: 'Writing response',
3
+ reading_files: 'Reading files', editing_files: 'Editing files', running_command: 'Running command',
4
+ using_tool: 'Using a tool', multiple_tools: 'Running multiple tools', settled: 'Turn settled',
5
+ };
6
+ export function workerObservation(snapshot, sessionId, now = Date.now()) {
7
+ const events = snapshot.events.filter(e => e.sessionId === sessionId &&
8
+ ['runtime.ready', 'host.activity', 'runtime.closed'].includes(e.event) &&
9
+ (e.event === 'runtime.closed' || typeof e.busy === 'boolean') && Number.isFinite(Date.parse(e.timestamp)));
10
+ const latest = events.reduce((best, e) => !best || Date.parse(e.timestamp) > Date.parse(best.timestamp) ? e : best, undefined);
11
+ let observedStatus = 'unknown', lastActivity = 'no observation';
12
+ let observationAgeSeconds = null, stale = null;
13
+ let evidence = 'missing';
14
+ if (latest) {
15
+ const elapsed = now - Date.parse(latest.timestamp);
16
+ if (elapsed < 0) {
17
+ evidence = 'clock_uncertain';
18
+ lastActivity = 'clock uncertain';
19
+ }
20
+ else {
21
+ observationAgeSeconds = Math.floor(elapsed / 1000);
22
+ stale = elapsed > snapshot.staleAfterMs;
23
+ const ties = events.filter(e => e.timestamp === latest.timestamp);
24
+ const conflict = ties.some(e => (e.event === 'runtime.closed') !== (latest.event === 'runtime.closed') || e.busy !== latest.busy || (e.phase && latest.phase && e.phase !== latest.phase));
25
+ if (conflict) {
26
+ evidence = 'conflicting';
27
+ lastActivity = 'conflicting records';
28
+ }
29
+ else {
30
+ evidence = stale ? 'stale' : 'recent';
31
+ observedStatus = latest.event === 'runtime.closed' ? 'closed' : !latest.busy ? 'idle' : latest.phase === 'thinking' ? 'thinking' : 'working';
32
+ const detail = events.filter(e => e.writerId === latest.writerId && e.detail && !['processing', 'settled'].includes(e.detail) && Date.parse(e.timestamp) <= Date.parse(latest.timestamp))
33
+ .reduce((best, e) => !best || e.timestamp > best.timestamp ? e : best, undefined);
34
+ lastActivity = latest.event === 'runtime.closed' ? 'Runtime closed'
35
+ : (detail?.detail ? labels[detail.detail] : latest.detail ? labels[latest.detail] : latest.busy ? 'Processing' : 'No active turn');
36
+ }
37
+ }
38
+ }
39
+ return { observedStatus, lastActivity, observedAt: latest?.timestamp ?? null, observationAgeSeconds, stale, evidence };
40
+ }
41
+ /** Bounded JSON data for agents/any presentation layer. No Telegram, UI, probing or model calls. */
42
+ export function workerStatusPage(snapshot, options = {}, now = Date.now()) {
43
+ const offset = options.offset ?? 0, limit = options.limit ?? 10;
44
+ if (!Number.isInteger(offset) || offset < 0 || !Number.isInteger(limit) || limit < 1 || limit > 20)
45
+ throw new Error('Invalid pagination: offset >= 0, limit 1–20');
46
+ if (options.name !== undefined && (typeof options.name !== 'string' || !options.name.trim()))
47
+ throw new Error('Worker name must be nonblank');
48
+ let roster = snapshot.config?.agents.filter(a => !a.coordinator) ?? [];
49
+ if (options.name !== undefined) {
50
+ roster = roster.filter(a => a.name.toLowerCase() === options.name.toLowerCase());
51
+ if (snapshot.config && !roster.length)
52
+ throw new Error('Worker name not found in the retained snapshot');
53
+ }
54
+ const workers = [];
55
+ const result = { version: 1, source: 'local-observations', generatedAt: new Date(now).toISOString(),
56
+ statusMeaning: 'Last observed, not live liveness or task completion. Reports are self-reported, not approval.',
57
+ truncated: snapshot.truncated, errors: snapshot.errors, totalInSnapshot: roster.length, offset,
58
+ nextOffset: null, workers };
59
+ for (const agent of roster.slice(offset, offset + limit)) {
60
+ const report = snapshot.reports?.find(r => r.sessionId === agent.sessionId && r.status !== 'clear');
61
+ const reportAge = report ? now - Date.parse(report.updatedAt) : NaN;
62
+ const entry = { sessionId: agent.sessionId, name: agent.name, ...workerObservation(snapshot, agent.sessionId, now),
63
+ report: report ? { status: report.status, summary: report.summary, updatedAt: report.updatedAt,
64
+ ageSeconds: Number.isFinite(reportAge) && reportAge >= 0 ? Math.floor(reportAge / 1000) : null,
65
+ selfReported: true } : null };
66
+ workers.push(entry);
67
+ if (Buffer.byteLength(JSON.stringify(result, null, 2)) > 40000) {
68
+ workers.pop();
69
+ break;
70
+ }
71
+ }
72
+ if (offset + workers.length < roster.length)
73
+ result.nextOffset = offset + workers.length;
74
+ return result;
75
+ }
76
+ //# sourceMappingURL=worker-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-status.js","sourceRoot":"","sources":["../src/worker-status.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -12,8 +12,8 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/comput-sh/pi-intercom/issues"
14
14
  },
15
- "version": "0.3.0",
16
- "description": "Explicit local communication between Pi sessions; Pi orchestrates.",
15
+ "version": "0.4.2",
16
+ "description": "A coordinator-led workspace for Pi. Delegate to workers with separate contexts, evaluate their reports, and keep the main conversation focused—with the team visible alongside it.",
17
17
  "type": "module",
18
18
  "keywords": [
19
19
  "pi-package"
@@ -25,14 +25,15 @@
25
25
  },
26
26
  "files": [
27
27
  "src",
28
- "dashboard",
28
+ "dist",
29
29
  "README.md",
30
30
  "references/observability.md",
31
+ "references/roadmap.md",
31
32
  "references/v1-specification.md",
32
33
  "references/implementation-blocker.md"
33
34
  ],
34
35
  "engines": {
35
- "node": ">=22"
36
+ "node": ">=22.19.0"
36
37
  },
37
38
  "peerDependencies": {
38
39
  "@earendil-works/pi-coding-agent": "*",
@@ -45,10 +46,15 @@
45
46
  "typescript": "^5.9.3"
46
47
  },
47
48
  "scripts": {
48
- "build": "tsc",
49
+ "build": "node -e \"require('node:fs').rmSync('dist', {recursive:true, force:true})\" && tsc",
50
+ "prepack": "npm run build",
49
51
  "typecheck": "tsc --noEmit",
50
- "test": "npm run build && node --test tests/core.test.mjs tests/integration.test.mjs tests/launcher.test.mjs tests/linux-launcher.test.mjs tests/extension.test.mjs tests/runtime-http.test.mjs tests/resume-storage.test.mjs tests/observability.test.mjs tests/dashboard.test.mjs tests/dashboard-security.test.mjs tests/observability-integration.test.mjs",
52
+ "test": "npm run build && node --test tests/core.test.mjs tests/integration.test.mjs tests/launcher.test.mjs tests/linux-launcher.test.mjs tests/activity.test.mjs tests/reports.test.mjs tests/worker-status.test.mjs tests/monitor.test.mjs tests/monitor-view.test.mjs tests/monitor-launcher.test.mjs tests/extension.test.mjs tests/runtime-http.test.mjs tests/resume-storage.test.mjs tests/observability.test.mjs tests/snapshot.test.mjs tests/snapshot-security.test.mjs tests/observability-integration.test.mjs",
51
53
  "test:host": "node --test tests/pi-cancellation-probe.test.mjs",
52
- "check:package": "npm pack --dry-run --ignore-scripts"
54
+ "test:package": "node --test tests/packaged-monitor.test.mjs",
55
+ "check:package": "npm run build && npm pack --dry-run --ignore-scripts && npm run test:package"
56
+ },
57
+ "dependencies": {
58
+ "pi-intercom-tui": "npm:@earendil-works/pi-tui@0.84.4"
53
59
  }
54
60
  }
@@ -1,104 +1,110 @@
1
- # Local event logs and read-only dashboard
1
+ # Local observations and terminal monitor
2
2
 
3
- Available starting in **version 0.2.0**; not part of version 0.1.3. Validation status is tracked in [README](../README.md). This is observation, not orchestration, a durable task journal, or proof of agent liveness.
3
+ Intercom's terminal monitor is a standalone Node/pi-tui process above the coordinator in Herdr. It reads local configuration and metadata logs; it is not an agent, scheduler, durable task journal, or proof of liveness. Included in npm release 0.4.1, replacing the browser dashboard. See [README](../README.md) for installation and validation scope.
4
4
 
5
- ## Opening the dashboard
5
+ ## Terminal-first monitoring
6
6
 
7
- Coordinator startup automatically binds a separate HTTP listener to `127.0.0.1` on an OS-selected port, writes the actual bound port to its shared coordinator entry as `dashboardPort`, and prints the local dashboard URL in Pi. Open that exact URL in a local browser. No browser is opened automatically. Workers write local event metadata but do not start dashboard listeners. Extension teardown closes the dashboard and its active connections; a later startup can use a different URL.
7
+ Coordinator startup checks its saved monitor ownership and either preserves the existing pane or creates one above the coordinator. Workers do not create monitors. The pane is named `Intercom monitor`; its generic label alone is not proof of ownership. Machine-local `monitor-*.json` files record the coordinator/workspace/pane identities and partial-launch state. Uncertain operations are fenced rather than blindly repeated.
8
8
 
9
- `dashboardPort` is optional and coordinator-only, an integer from 1 through 65535. Existing configuration without it remains valid. It is **not the messaging `port`** and does not change agent-message routing. The saved port is last-known metadata; it is not a requested bind port or a guarantee that any process is listening.
9
+ The monitor refreshes from disk approximately every three seconds after the previous read finishes. It never probes workers, launches model turns, or resends messages. Arrow keys select a worker; Enter toggles its details; Escape closes details before quitting, while q/Ctrl+C always quit the monitor only. Selection follows the worker's session ID across roster reorder. Details show configured responsibility, observations and explicit public worker reports, not inferred task state or approval controls.
10
10
 
11
- Configured coordinators and workers can call existing `intercom_list` to obtain top-level `dashboardUrl`, derived as `http://127.0.0.1:<saved dashboardPort>/`, or `null` if the coordinator has no saved dashboard port. The result retains `version`, `multiplexer` and `agents`. Listing only reads shared config: it does not probe, start, rebind or control the dashboard. A previously saved address remains after closure or startup failure, so a non-null URL is **not proof of dashboard liveness**. No new tool or dashboard control is added.
11
+ - **Worker:** configured name. A roster entry does not prove a process exists.
12
+ - **Seen:** age of the status observation, not the last redraw. Evidence older than 60 seconds is marked `(old)`.
13
+ - **Status:** last observed working, thinking, idle, closed or unknown. Closed means extension runtime closure, not guaranteed process death. Idle is not a task-completion assertion.
14
+ - **Last activity:** a fixed public description of observed activity, which may remain after settlement. No reasoning or tool payload is displayed.
15
+ - **Report:** explicitly worker-authored blocked, needs-decision or ready-for-review state, with its own timestamp and public summary in details. It is never coordinator approval or inferred from observed activity.
16
+ - **Unknown/partial:** missing, conflicting, future-dated or unreadable evidence is not guessed. Bounded history and dropped observations can make the view incomplete.
12
17
 
13
- The URL is announced only after the new bound port has been saved and startup lifecycle checks still pass. If saving fails, the newly created dashboard is closed and a local error is shown; Intercom messaging remains available. The previous saved URL, if any, is not cleared and can be stale. Shutdown likewise leaves the saved field intact. Startup does not request or reuse the saved dashboard port.
18
+ Names and descriptions are sanitized for terminal controls and clipped to available display columns. The UI supports narrow/short panes and `NO_COLOR`. Details are read-only: user intent to inspect a worker does not authorize messaging, focusing unrelated panes, stopping agents, or approving work.
14
19
 
15
- The dashboard polls its read-only snapshot endpoint every three seconds after the preceding request settles, with an eight-second client timeout. It has no agent controls, assignment forms, send buttons, stop/close, recovery actions, or retry controls. Its refresh loop only rereads telemetry; it does not resend Intercom messages or probe workers.
20
+ ## Retired browser dashboard
16
21
 
17
- ### Reading the screen
22
+ Version 0.4.1 no longer starts a dashboard HTTP server, ships browser assets, or returns `dashboardUrl` from `intercom_list`. Only Intercom's messaging HTTP endpoints remain.
18
23
 
19
- - **Configured agents:** names, responsibilities, saved endpoints and Pi session IDs come from shared configuration. Saved entries do not establish whether a process exists. Responsibility is not an assignment.
20
- - **Observed busy / observed idle:** the most recent `runtime.ready` or `host.activity` event in the retained window for that Pi session. The event's own timestamp and age are shown. `status.received` is not attributed to its writer as that writer's busy state; its `busy` value describes the reporting peer.
21
- - **Stale evidence:** an observation older than 60 seconds. It does not mean offline: a legitimately idle host may emit no new activity. **Unknown** means no eligible observation in the retained window, not idle or unavailable. Future-dated observations are marked clock-uncertain.
22
- - **Exchange ledger:** newest metadata first; filter by agent (including either side of an exchange), event type, or errors. Expand a row for allowlisted fields and correlation. Receipt, submission, and host activity are separate facts, not task-completion states.
23
- - **Disconnected / partial snapshot:** the banner states the problem. Previously received data may remain visible and continues to age; it must not be treated as current process status. An empty timeline does not prove no activity occurred.
24
-
25
- The UI uses local HTML/CSS/JavaScript, with no CDN or remote fonts. Event/config text is inserted as text, never as executable HTML. It displays selected roster fields, not a raw config dump.
24
+ Old coordinator-only `dashboardPort` fields are accepted for compatibility and removed through a guarded coordinator config update at startup. Listing never advertises the retired port, even if migration failed; a migration warning does not disable messaging. Reload an older running coordinator to close its existing dashboard listener and load the new implementation.
26
25
 
27
26
  ## Privacy and event contract
28
27
 
29
- Logs live under `<coordinator-root>/.pi-intercom/logs/`. They are **metadata-only**: there is no prompt-body logging mode in this implementation. Message bodies, tool arguments/results, credentials, raw error messages/stacks, and responsibility text are not copied into log events. The dashboard separately reads configured names and responsibilities for its roster; do not put secrets in those fields. Metadata can still be sensitive: session IDs, human labels, endpoints, timings and communication patterns may identify your work. Keep logs local and do not commit or publish them. Shared `config.json` remains the source-controlled configuration; logging does not move IDs or ports out of it.
28
+ Logs live under `<coordinator-root>/.pi-intercom/logs/`. They are metadata-only. Message bodies, tool arguments/results, reasoning/text deltas, credentials, raw errors/stacks and responsibility text are not copied into activity logs. The monitor separately reads configured responsibility for details; do not put secrets in configuration. Names, session IDs, timings, endpoints and communication patterns can still be sensitive. Do not commit or publish logs.
29
+
30
+ Shared `config.json` remains the source-controlled agent configuration; logging and monitor pane ownership do not move agent IDs/ports into a separate runtime registry.
30
31
 
31
- Version 1 event fields:
32
+ Version 1 fields:
32
33
 
33
34
  | Field | Meaning |
34
35
  |---|---|
35
36
  | `version` | `1` |
36
37
  | `timestamp` | UTC ISO observation time, not a global ordering guarantee |
37
- | `sessionId` | Actual Pi agent/session identity |
38
- | `writerId` | Ephemeral UUID for this log writer/file group only; **not a new agent or transport identity** |
38
+ | `sessionId` | Pi session identity |
39
+ | `writerId` | Ephemeral log-writer UUID, not agent identity |
39
40
  | `event` | Allowlisted event type |
40
41
  | `peerSessionId`, `peerName` | Optional peer metadata |
41
- | `correlationId` | Optional identifier for one transport attempt, not a task, reply promise or completion token |
42
- | `kind`, `operation`, `role`, `outcome` | Optional allowlisted metadata values |
43
- | `busy`, `port` | Optional observed boolean / port |
44
- | `errorCode` | Optional fixed safe error label; no raw exception text |
42
+ | `correlationId` | One transport attempt, not a task/completion token |
43
+ | `kind`, `operation`, `role`, `outcome` | Allowlisted metadata values |
44
+ | `busy`, `port` | Observed boolean / port |
45
+ | `phase`, `detail` | Fixed public activity enums |
46
+ | `errorCode` | Fixed safe error label |
45
47
 
46
- Current event types: `runtime.starting`, `runtime.ready`, `runtime.closed`, `runtime.failed`, `config.changed`, `config.reloaded`, `transport.send`, `transport.receipt`, `transport.failed`, `transport.received`, `transport.rejected`, `registration.received`, `status.received`, `launch.result`, `host.submission`, `host.activity`, `host.ui_prompt`.
48
+ Event types: `runtime.starting`, `runtime.ready`, `runtime.closed`, `runtime.failed`, `config.changed`, `config.reloaded`, `transport.send`, `transport.receipt`, `transport.failed`, `transport.received`, `transport.rejected`, `registration.received`, `status.received`, `launch.result`, `host.submission`, `host.activity`, `host.ui_prompt`.
47
49
 
48
- A sender generates an optional wire-envelope `correlationId` for each send; the receiver can record it too. Legacy envelopes without it are accepted. It provides trace correlation only: no deduplication, replay, durable delivery or synchronous request/reply behavior is introduced.
50
+ Activity phases are `working`, `thinking`, `responding`, `tool`, `idle`. Details are `processing`, `thinking`, `responding`, `reading_files`, `editing_files`, `running_command`, `using_tool`, `multiple_tools`, `settled`. Only event discriminants and built-in tool categories are inspected. Repeated streaming phases are deduplicated, and concurrent tools take precedence over streaming hints. Thinking events are provider-dependent; missing events do not establish absence of reasoning.
49
51
 
50
- ### What the records do not establish
52
+ ## Read-only JSON status
51
53
 
52
- - `transport.receipt` / `http_receipt` means the sender saw HTTP acceptance. It does **not** prove Pi accepted or queued the input, nor that a model turn or assignment completed.
53
- - `host.submission` with `attempted`, `returned` or `failed` records a synchronous host call. Pi 0.84.4's void `sendUserMessage` binding can report an asynchronous manual-compaction rejection only locally after HTTP acceptance. Logging does not fix or reliably observe that asynchronous rejection.
54
- - `host.activity` `started`/`settled` observes host activity, not a particular message's result. Busy snapshots are not availability guarantees.
55
- - `launch.result` does not establish agent readiness. `host.ui_prompt` does not authorize work. Neither monitoring nor errors trigger orchestration.
56
- - Stop/close remain disabled. Telemetry does not add cancellation, forced shutdown, retries, recovery or rollback.
54
+ `intercom_worker_status` reads this same bounded snapshot, using the same `workerObservation` projection as the monitor. It returns configured workers only, optionally selected by name, with last-observed status/activity, timestamp, age, stale/unknown/conflicting evidence, and independent public reports. Missing or future evidence does not imply health. Clear tombstones appear as null reports.
57
55
 
58
- ## Storage bounds and failure behavior
56
+ Pages default to 10 entries (limit 1–20), capped at 40 KiB before the final pagination field, below Pi's tool truncation limits. Follow `nextOffset`; each page is a new local snapshot, not a transaction spanning changing rosters. Snapshot truncation/errors are retained. No model calls, status requests, worker messages, Telegram dependencies or hidden reasoning are involved. Any presentation layer can consume the JSON.
59
57
 
60
- Each writer appends newline-delimited JSON to `writer-<UUID>.jsonl` and rotates through `.jsonl.1` and `.jsonl.2`.
58
+ ## Explicit public reports
61
59
 
62
- | Bound | Policy |
63
- |---|---|
64
- | Active/rotated file size | 1 MiB each, up to three data files per writer |
65
- | Event size | At most 4 KiB per JSONL record |
66
- | In-memory writer queue | At most 256 queued entries plus one in-flight entry; excess records are dropped |
67
- | Closed-group retention | Seven days; cleanup can remove older closed groups earlier to target 32 MiB aggregate storage |
68
- | Cleanup scan | At most 4096 directory entries per maintenance pass |
69
- | Maintenance | Coordinator only, at startup and every 60 seconds |
60
+ `intercom_report_work` sends a dedicated `report` envelope from a configured, responsibility-loaded worker to the coordinator. Sender identity comes from the envelope/config, not a payload-supplied session ID. The coordinator rechecks permission before saving; writes are serialized with a bounded 64-report in-flight/queued limit and lifecycle guards. One atomic per-worker report file lives under `.pi-intercom/reports/`, named by a SHA-256 hash of the Pi session ID. Files use owner-only permissions where the platform supports them. A clear report is an empty tombstone.
61
+
62
+ These files are **not metadata-only**: they intentionally contain worker-authored public summaries of at most 2000 characters. Notifications can also place summaries into Pi conversation history. No report body is copied into observation events. Do not put secrets or private reasoning into reports. This is current report state, not an assignment history or durable delivery journal; there is no automatic report retry, acceptance, approval or task completion.
70
63
 
71
- A graceful writer close renames its active file to `.jsonl.closed`; that suffix is the cleanup eligibility marker, not a live process probe. Cleanup removes only closed writer groups, oldest first. Active and crash-orphan groups are left untouched. The 32 MiB directory target is therefore **best effort, not a hard disk cap**, especially if active/orphan groups or scan limits dominate. Filesystem failures may leave files behind.
64
+ Readers consider only configured workers, at most 256 IDs and 8 KiB per file. Unsafe, invalid, oversized or unreadable files are skipped. Such absence is not proof no blocker exists. Reports can remain after process closure; display report age independently. Retired workers' files are not automatically pruned. Atomic replacement and path checks do not guarantee crash durability or defend against hostile concurrent filesystem manipulation.
72
65
 
73
- Logging is non-authoritative and best effort. Discovery failures before root resolution and invalid HTTP envelopes rejected before runtime handling are not emitted as runtime log events. Sanitization/size/queue failures can discard observations. An append/rotation I/O failure disables that writer and drops queued records without retrying or failing Intercom communication. Maintenance failures are ignored by the communication path. Ordinary record/send/delivery operations never await logging I/O. During teardown, the runtime invalidates its lifecycle and closes its HTTP endpoint before waiting at most **250 ms** for logger shutdown. Concurrent close callers share the same close operation. This is not a guaranteed flush: timed-out I/O cannot be cancelled and may finish later.
66
+ ## What observations do not establish
74
67
 
75
- The writer refuses pre-existing symbolic links at `.pi-intercom` or its `logs` directory; these checks are best effort, not race-proof protection against a hostile filesystem. The dashboard cannot certify that logging was complete or that a quiet writer is healthy; inspect local storage when diagnosing missing evidence. Logs are not fsynced as a durable delivery journal.
68
+ - HTTP acceptance is not guaranteed Pi input acceptance, injection, or task completion.
69
+ - `host.submission` records a synchronous host call. Pi 0.84.4's void `sendUserMessage` can report manual-compaction rejection asynchronously after receipt; logging does not repair or reliably observe that rejection.
70
+ - `agent_settled` supplies the idle boundary, not `agent_end`. Activity observations are not availability guarantees or assignment states.
71
+ - `status.received` describes the reporting peer, not the event writer's own activity. It is not used as the writer's status.
72
+ - Launch submission does not establish readiness. A correlation ID is not deduplication or a reply promise.
73
+ - Stop/close remain disabled. Monitoring adds no cancellation, recovery, retries, commit or rollback.
76
74
 
77
- ## HTTP snapshot boundary
75
+ ## Storage bounds and failure behavior
78
76
 
79
- Only the coordinator exposes these GET routes:
77
+ Writers append JSONL to `writer-<UUID>.jsonl`, rotating through `.jsonl.1` and `.jsonl.2`.
80
78
 
81
- | Route | Response |
79
+ | Bound | Policy |
82
80
  |---|---|
83
- | `/` | Local `dashboard/index.html` |
84
- | `/app.js`, `/style.css` | Explicitly allowlisted local assets |
85
- | `/api/snapshot` | Sanitized JSON snapshot |
81
+ | File size | 1 MiB each, up to three data files per writer |
82
+ | Record size | At most 4 KiB |
83
+ | Queue | At most 256 queued entries plus one in-flight entry; excess dropped |
84
+ | Closed-group retention | Seven days; earlier pruning can target 32 MiB aggregate |
85
+ | Cleanup scan | At most 4096 entries per maintenance pass |
86
+ | Maintenance | Coordinator startup and every 60 seconds |
87
+
88
+ A graceful close renames the active file to `.jsonl.closed`, marking the group eligible for pruning. Only closed groups are pruned; active/crash-orphan groups remain untouched. Thus 32 MiB is a best-effort target, not a hard disk cap. Filesystem failures can leave files behind.
89
+
90
+ Logging never determines communication success. Size/sanitization/queue limits can drop observations; append/rotation failure disables a writer and drops its queue without retrying. Ordinary communication does not await logging I/O. Teardown invalidates the runtime and closes messaging before waiting at most 250 ms for logging shutdown. This is not a guaranteed flush; in-flight filesystem work cannot be cancelled.
86
91
 
87
- Snapshot shape:
92
+ The writer rejects pre-existing symlinks at `.pi-intercom` and `logs`; this is best effort, not protection against hostile concurrent filesystem manipulation. Logs are not fsynced as a durable delivery journal.
93
+
94
+ ## Bounded local snapshot reader
95
+
96
+ `src/snapshot.ts` exposes `readObservationSnapshot` without starting a server:
88
97
 
89
98
  ```text
90
99
  { version: 1, generatedAt, staleAfterMs: 60000,
91
- config: { multiplexer, agents: [selected validated roster fields] } | null,
92
- events: [sanitized v1 events, oldest first], truncated: boolean,
93
- errors: [fixed safe labels] }
100
+ config: { multiplexer, agents: [selected validated fields] } | null,
101
+ events: [sanitized events, oldest first], reports: [public worker reports], truncated, errors }
94
102
  ```
95
103
 
96
- Reads are bounded: config at most 1 MiB and 256 agents; scan at most 512 log-directory entries, select up to 32 newest matching files among those scanned (not necessarily the globally newest files in an oversized directory), tail at most 128 KiB per file and 2 MiB total, and return at most 500 latest events. `truncated` indicates bounded omissions, not complete history. Unsupported, malformed or partial records are ignored. Reads can overlap rotation and are not a transactional global snapshot.
97
-
98
- Partial snapshots return HTTP 200 with `config_unavailable`, `logs_unavailable` and/or `log_read_failed` labels; no raw read errors or paths are returned. Unknown routes (including query variants) return 404; non-GET requests return 405; rejected Host/Origin/cross-site requests return 403; overload or static-asset failures return 503. The listener admits at most eight active requests and 16 connections. Concurrent snapshot requests share the pending read, not a worker probe.
104
+ Limits: config at most 1 MiB / 256 agents; scan at most 512 log entries; select up to 32 newest matching files among those scanned; tail at most 128 KiB per file / 2 MiB total; retain at most 500 latest events. Bounded directory scans do not necessarily find globally newest files. Reads can overlap rotation and are not a transactional global snapshot.
99
105
 
100
- The backend rejects linked/redirected config, log and asset path components and serves no arbitrary file route. It sets no-store, nosniff, same-origin-only content policy, no-referrer and frame-ancestor restrictions. These checks reduce exposure but are **not authentication or a security boundary against hostile local processes**; localhost remains trusted. Do not proxy or expose the dashboard remotely.
106
+ Malformed/partial/unsupported records are ignored. Safe errors include `config_unavailable`, `logs_unavailable`, and `log_read_failed`; raw paths/errors are not displayed. Linked/redirected config/log path components are rejected. This is not a security boundary against hostile local processes; the project and local processes remain trusted.
101
107
 
102
108
  ## Validation scope
103
109
 
104
- See README for verified test results. Synthetic filesystem/HTTP/DOM fixtures do not prove actual Pi queue acceptance, live resume, host name persistence, or full interactive lifecycle correctness. No dashboard observation should be used to infer unobserved completion or availability.
110
+ Tests cover bounded filesystem reading, sanitization, metadata migration, lifecycle fences, rendering, selection and refresh behavior. They do not by themselves establish live Pi queue acceptance, resumed-agent startup, process cancellation, or every terminal client's visual behavior. Live monitor tests are reported separately; no status display proves unobserved work complete.
@@ -0,0 +1,68 @@
1
+ # Improvement topics
2
+
3
+ Pi Intercom's direction is a **coordinator-led workspace for Pi**: delegate into separate worker contexts, evaluate their reports, and keep the main conversation focused—with the team visible alongside it.
4
+
5
+ This is a proposed discussion backlog, not an implementation plan, release commitment, or description of features already shipped. Decisions should reinforce that workflow rather than duplicate every feature of a general messaging platform. The coordinator remains responsible for orchestration; transport and UI must not silently assign, retry, approve, or declare work complete.
6
+
7
+ ## First priorities
8
+
9
+ ### 1. Message identity and delivery evidence
10
+
11
+ Current transport receipt does not prove Pi accepted a message or that a worker acted on it. Introduce explicit message IDs and distinguish receipt, held delivery, injection/submission, reply, and failure. Investigate host support for truthful acknowledgments and holding messages during compaction. Deduplicate retries without silently resending. A timeout is an uncertain outcome, not cancellation.
12
+
13
+ Decide whether pending messages survive extension reload or process restart, with explicit storage bounds, expiry and privacy rules. Do not present an in-memory queue as durable delivery.
14
+
15
+ ### 2. Assignments, reports and pending decisions
16
+
17
+ Link follow-up messages and reports to explicit assignment IDs. Keep configured responsibility, current assignment, host activity, and task state distinct. Add concise structured reports: findings, changed files, validation, blockers, and decisions needed. A worker's completion claim should remain pending coordinator evaluation, not automatically become accepted work.
18
+
19
+ Consider threaded reply and a pending-decisions view. If blocking ask/reply is added, use it selectively for workers; avoid blocking the main coordinator on a long wait. Define timeout, cancellation and simultaneous-request behavior first.
20
+
21
+ ### 3. Presence versus activity
22
+
23
+ Separate last contact from the time of the last activity change. A bounded extension-level heartbeat could establish fresh contact without model turns, but it must not make an old thinking/tool observation look newly confirmed. Show unavailable, stale and unknown honestly. Think through shutdown, crash, reload and missing-log cases.
24
+
25
+ ## Workflow improvements
26
+
27
+ ### 4. Context hygiene and report quality
28
+
29
+ Provide reusable task briefs with scope, constraints, file ownership and expected output. Prefer concise reports and on-demand detail over copying full worker transcripts into the coordinator. Consider report-size budgets and summaries for long-running work. Keep original findings retrievable; never silently discard material blockers or claim that context size or responsiveness is guaranteed.
30
+
31
+ ### 5. Human-first coordination
32
+
33
+ Consider prioritizing human input over routine peer chatter. Batch low-priority progress while surfacing explicit decision requests. Define fairness so worker messages do not starve. Keep delivery policy visible and never equate background activity with permission to interrupt or expand scope.
34
+
35
+ ### 6. Shared-code safety
36
+
37
+ Separate model contexts do not isolate files. Establish explicit file ownership for concurrent assignments, avoid concurrent edits to the same files, and consider opt-in worktrees for implementation workers. Review and integration remain deliberate steps. Do not automatically merge, commit, or roll back worker changes.
38
+
39
+ ### 7. Worker and monitor lifecycle
40
+
41
+ Expand live tests for worker registration, resume, session replacement, duplicate sessions, coordinator restart and monitor-pane recovery. Distinguish a monitor pane existing from its process running; only restart into a positively identified safe target. Resolve known cancellation limitations before enabling worker stop/close—message cancellation is not process cancellation.
42
+
43
+ ## Product and maintenance
44
+
45
+ ### 8. Finish the terminal-monitor transition
46
+
47
+ **Source progress:** browser server/assets removed, snapshot reading extracted, legacy dashboard metadata retired on coordinator startup, and listing no longer advertises dashboard URLs. Package validation and a release are still required before npm users receive these changes.
48
+
49
+ Original scope: decide whether to remove the browser dashboard entirely or make it explicitly optional. Consolidate shared observation code, migrate legacy dashboard fields deliberately, and remove obsolete docs/tests only when their functionality is retired. Keep installation, source builds and published-package behavior consistent. The terminal monitor replaces the browser dashboard in 0.4.1; it was not available in npm 0.3.0.
50
+
51
+ ### 9. Make the monitor useful for decisions
52
+
53
+ **0.4.1 progress:** keyboard selection, roster scrolling and worker details are implemented. `intercom_report_work` now stores one explicitly worker-authored blocked/needs-decision/ready-for-review report (or clear tombstone), notifies the coordinator, and displays it separately from host activity. Reports do not imply acceptance or completion. Read-only `intercom_worker_status` exposes shared observation/report data as bounded JSON without chat coupling. Assignment-threaded reports, coordinator decision history and verified worker-pane navigation remain potential follow-ups.
54
+
55
+ Once assignment and report data exist, show pending decisions, blockers and review-needed work—not just host activity. Consider worker selection, keyboard navigation, and opening the correct worker pane. Preserve concise default views, terminal accessibility, reliable identity targeting, and explicit user intent for any controls.
56
+
57
+ ### 10. Integration and transport choices
58
+
59
+ Evaluate whether broker-based discovery or an existing messaging backend would reduce maintenance without weakening project boundaries or coordinator ownership. CLI access, attachments, subagent bridges and cross-machine work are possible later directions, not prerequisites for a good local coordinator workflow. Assess trust, privacy and recovery costs before expanding scope.
60
+
61
+ ## Suggested sequence
62
+
63
+ 1. Agree on assignment/message identities and truthful state meanings.
64
+ 2. Improve delivery evidence and add concise reports with pending decisions.
65
+ 3. Separate presence from activity and surface those facts in the monitor.
66
+ 4. Validate lifecycle and shared-file workflows, finish the dashboard transition, then release.
67
+
68
+ Context hygiene, human-priority delivery, and optional integrations can build on those foundations. No proposed feature should bypass project trust, hide uncertainty, expose private reasoning, or turn a status display into an implicit orchestrator.