@comput/pi-intercom 0.5.2 → 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 +109 -15
- package/dist/config.d.ts +18 -1
- package/dist/config.js +141 -27
- package/dist/config.js.map +1 -1
- package/dist/handoff.d.ts +87 -0
- package/dist/handoff.js +327 -0
- package/dist/handoff.js.map +1 -0
- package/dist/index.js +92 -17
- package/dist/index.js.map +1 -1
- package/dist/launcher.js +11 -1
- package/dist/launcher.js.map +1 -1
- package/dist/monitor-view.d.ts +1 -1
- package/dist/monitor-view.js +31 -2
- package/dist/monitor-view.js.map +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +1 -1
- package/dist/observability.js.map +1 -1
- package/dist/pane-close.d.ts +22 -0
- package/dist/pane-close.js +166 -0
- package/dist/pane-close.js.map +1 -0
- package/dist/reminders.d.ts +28 -0
- package/dist/reminders.js +81 -0
- package/dist/reminders.js.map +1 -0
- package/dist/runtime.d.ts +19 -2
- package/dist/runtime.js +163 -16
- package/dist/runtime.js.map +1 -1
- package/dist/snapshot.d.ts +9 -0
- package/dist/snapshot.js +23 -1
- package/dist/snapshot.js.map +1 -1
- package/dist/transport.d.ts +2 -1
- package/dist/transport.js +4 -1
- package/dist/transport.js.map +1 -1
- package/dist/worker-status.d.ts +1 -1
- package/dist/worker-status.js +9 -1
- package/dist/worker-status.js.map +1 -1
- package/package.json +12 -6
- package/references/implementation-blocker.md +1 -1
- package/references/observability.md +13 -3
- package/references/releasing.md +90 -0
- package/references/v1-specification.md +4 -4
- package/src/config.ts +107 -19
- package/src/handoff.ts +280 -0
- package/src/index.ts +75 -15
- package/src/launcher.ts +9 -1
- package/src/monitor-view.ts +30 -3
- package/src/observability.ts +1 -1
- package/src/pane-close.ts +138 -0
- package/src/reminders.ts +69 -0
- package/src/runtime.ts +138 -18
- package/src/snapshot.ts +22 -1
- package/src/transport.ts +4 -2
- package/src/worker-status.ts +9 -2
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { constants } from 'node:fs';
|
|
4
|
+
import { open, readFile, realpath } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
const rejected = () => new Error('PiIntercom: worker pane identity could not be verified; no close submitted.');
|
|
7
|
+
const exec = async (file, args) => (await promisify(execFile)(file, args, {
|
|
8
|
+
windowsHide: true, timeout: 4000, maxBuffer: 1024 * 1024,
|
|
9
|
+
})).stdout;
|
|
10
|
+
/** Linux process start ticks distinguish a reused PID from the original worker. */
|
|
11
|
+
export async function linuxProcessIdentity(pid) {
|
|
12
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
13
|
+
throw rejected();
|
|
14
|
+
let stat;
|
|
15
|
+
try {
|
|
16
|
+
stat = await readFile(`/proc/${pid}/stat`, 'utf8');
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error.code === 'ENOENT')
|
|
20
|
+
return;
|
|
21
|
+
throw rejected();
|
|
22
|
+
}
|
|
23
|
+
const fields = stat.slice(stat.lastIndexOf(')') + 2).trim().split(/\s+/);
|
|
24
|
+
if (!/^[0-9]+$/.test(fields[19] ?? '') || !/^[A-Za-z]$/.test(fields[0] ?? ''))
|
|
25
|
+
throw rejected();
|
|
26
|
+
return { start: fields[19], state: fields[0] };
|
|
27
|
+
}
|
|
28
|
+
async function verifySessionHeader(file, id) {
|
|
29
|
+
if (!path.isAbsolute(file) || file.length > 4096 || file.includes('\0'))
|
|
30
|
+
throw rejected();
|
|
31
|
+
const handle = await open(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
32
|
+
try {
|
|
33
|
+
if (!(await handle.stat()).isFile())
|
|
34
|
+
throw rejected();
|
|
35
|
+
// Inspect only the first session-header line, never transcript messages.
|
|
36
|
+
const buffer = Buffer.alloc(4096);
|
|
37
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
38
|
+
const end = buffer.subarray(0, bytesRead).indexOf(10);
|
|
39
|
+
if (end < 0)
|
|
40
|
+
throw rejected();
|
|
41
|
+
const header = JSON.parse(buffer.subarray(0, end).toString('utf8'));
|
|
42
|
+
if (header?.type !== 'session' || header.id !== id)
|
|
43
|
+
throw rejected();
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
await handle.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const alive = (identity, start) => !!identity && identity.start === start && !['Z', 'X', 'x'].includes(identity.state);
|
|
50
|
+
/** Linux Herdr only. Identity-checked best effort; Herdr has no atomic compare-and-close API. */
|
|
51
|
+
export function createCloseProvider(options) {
|
|
52
|
+
const env = options.env ?? process.env;
|
|
53
|
+
if ((options.platform ?? process.platform) !== 'linux' || env.HERDR_ENV !== '1' || !env.HERDR_PANE_ID)
|
|
54
|
+
return;
|
|
55
|
+
const run = options.run ?? exec;
|
|
56
|
+
const processIdentity = options.processIdentity ?? linuxProcessIdentity;
|
|
57
|
+
const verifySession = options.verifySession ?? verifySessionHeader;
|
|
58
|
+
const ownPid = options.pid ?? process.pid;
|
|
59
|
+
const verificationMs = Math.max(0, Math.min(2000, options.verificationMs ?? 1500));
|
|
60
|
+
async function query(args) {
|
|
61
|
+
const output = await run('herdr', args);
|
|
62
|
+
if (Buffer.byteLength(output) > 1024 * 1024)
|
|
63
|
+
throw rejected();
|
|
64
|
+
return JSON.parse(output)?.result;
|
|
65
|
+
}
|
|
66
|
+
async function caller() {
|
|
67
|
+
const pane = (await query(['pane', 'current', '--current']))?.pane;
|
|
68
|
+
if (!pane || typeof pane.pane_id !== 'string' || typeof pane.workspace_id !== 'string' || typeof pane.terminal_id !== 'string')
|
|
69
|
+
throw rejected();
|
|
70
|
+
return pane;
|
|
71
|
+
}
|
|
72
|
+
async function verifyPane(pane, identity) {
|
|
73
|
+
if (!pane || pane.pane_id !== identity.paneId || pane.workspace_id !== identity.workspaceId ||
|
|
74
|
+
pane.terminal_id !== identity.terminalId || pane.agent !== 'pi' ||
|
|
75
|
+
pane.agent_session?.source !== 'herdr:pi' || pane.agent_session?.kind !== 'path' ||
|
|
76
|
+
typeof pane.agent_session.value !== 'string' || !path.isAbsolute(identity.sessionFile) ||
|
|
77
|
+
!Number.isSafeInteger(identity.pid) || identity.pid <= 0 || !/^[0-9]+$/.test(identity.processStart))
|
|
78
|
+
throw rejected();
|
|
79
|
+
if (await realpath(pane.agent_session.value) !== await realpath(identity.sessionFile))
|
|
80
|
+
throw rejected();
|
|
81
|
+
await verifySession(identity.sessionFile, identity.sessionId);
|
|
82
|
+
const info = (await query(['pane', 'process-info', '--pane', identity.paneId]))?.process_info;
|
|
83
|
+
if (info?.pane_id !== identity.paneId || !Array.isArray(info.foreground_processes) ||
|
|
84
|
+
!info.foreground_processes.some((p) => p.pid === identity.pid) ||
|
|
85
|
+
!alive(await processIdentity(identity.pid), identity.processStart))
|
|
86
|
+
throw rejected();
|
|
87
|
+
}
|
|
88
|
+
async function inspect(identity, expectedSessionId, assertCurrent) {
|
|
89
|
+
assertCurrent();
|
|
90
|
+
if (identity.sessionId !== expectedSessionId || expectedSessionId === options.sessionId() || identity.pid === ownPid)
|
|
91
|
+
throw rejected();
|
|
92
|
+
const own = await caller();
|
|
93
|
+
assertCurrent();
|
|
94
|
+
if (own.pane_id === identity.paneId || own.workspace_id !== identity.workspaceId)
|
|
95
|
+
throw rejected();
|
|
96
|
+
await verifyPane((await query(['pane', 'get', identity.paneId]))?.pane, identity);
|
|
97
|
+
assertCurrent();
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
async getIdentity() {
|
|
101
|
+
try {
|
|
102
|
+
const sessionId = options.sessionId(), sessionFile = options.sessionFile();
|
|
103
|
+
if (!sessionFile)
|
|
104
|
+
throw rejected();
|
|
105
|
+
const pane = await caller();
|
|
106
|
+
const process = await processIdentity(ownPid);
|
|
107
|
+
if (!process || !alive(process, process.start))
|
|
108
|
+
throw rejected();
|
|
109
|
+
const identity = { workspaceId: pane.workspace_id, paneId: pane.pane_id,
|
|
110
|
+
terminalId: pane.terminal_id, sessionId, sessionFile, pid: ownPid, processStart: process.start };
|
|
111
|
+
await verifyPane(pane, identity);
|
|
112
|
+
if (options.sessionId() !== sessionId || options.sessionFile() !== sessionFile)
|
|
113
|
+
throw rejected();
|
|
114
|
+
return identity;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
throw rejected();
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
async inspect(identity, expectedSessionId, assertCurrent) {
|
|
121
|
+
try {
|
|
122
|
+
await inspect(identity, expectedSessionId, assertCurrent);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
throw rejected();
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
async close(identity, expectedSessionId, assertCurrent, beforeSubmit) {
|
|
129
|
+
try {
|
|
130
|
+
await inspect(identity, expectedSessionId, assertCurrent);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw rejected();
|
|
134
|
+
}
|
|
135
|
+
assertCurrent();
|
|
136
|
+
// Revalidate worker settlement only AFTER potentially slow ownership reads.
|
|
137
|
+
// Herdr still has no atomic cross-process identity/idle compare-and-close.
|
|
138
|
+
await beforeSubmit();
|
|
139
|
+
assertCurrent();
|
|
140
|
+
// Exactly one explicit pane target. No tab/workspace close, retry, or force kill.
|
|
141
|
+
// A CLI timeout can mean the mutation happened: caller must persist uncertainty.
|
|
142
|
+
try {
|
|
143
|
+
await run('herdr', ['pane', 'close', identity.paneId]);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new Error('PiIntercom: pane close outcome uncertain; inspect before any retry.');
|
|
147
|
+
}
|
|
148
|
+
let paneClosed = false, workerExited = false;
|
|
149
|
+
try {
|
|
150
|
+
const panes = (await query(['pane', 'list', '--workspace', identity.workspaceId]))?.panes;
|
|
151
|
+
paneClosed = Array.isArray(panes) && panes.every(p => typeof p?.pane_id === 'string') &&
|
|
152
|
+
!panes.some(p => p.pane_id === identity.paneId);
|
|
153
|
+
const deadline = Date.now() + verificationMs;
|
|
154
|
+
do {
|
|
155
|
+
workerExited = !alive(await processIdentity(identity.pid), identity.processStart);
|
|
156
|
+
if (workerExited || Date.now() >= deadline)
|
|
157
|
+
break;
|
|
158
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
159
|
+
} while (true);
|
|
160
|
+
}
|
|
161
|
+
catch { /* Verification failure is uncertainty, never an automatic retry. */ }
|
|
162
|
+
return { paneClosed, workerExited };
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=pane-close.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pane-close.js","sourceRoot":"","sources":["../src/pane-close.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const REMINDER_INTERVAL_MS: number;
|
|
2
|
+
export declare const REMINDER_TEXT = "Intercom reminder: Review outstanding delegated work and worker reports. If follow-up is needed, take the next appropriate action. Do not assume workers have finished or stopped.\n\nIf there is genuinely nothing to follow up on, call intercom_stop_reminders. Otherwise, reminders continue every five minutes; a new worker call resets the timer.\n\nThis reminder does not authorize new work, retries, worker closure, or publication.";
|
|
3
|
+
export interface ReminderClock {
|
|
4
|
+
schedule(callback: () => void, milliseconds: number): () => void;
|
|
5
|
+
}
|
|
6
|
+
interface ReminderHost {
|
|
7
|
+
isCoordinator(): Promise<boolean>;
|
|
8
|
+
busy(): boolean;
|
|
9
|
+
deliver(text: string): void;
|
|
10
|
+
}
|
|
11
|
+
/** Ephemeral reminder only: never worker actions, network retries or a backlog. */
|
|
12
|
+
export declare class CoordinatorReminders {
|
|
13
|
+
private readonly host;
|
|
14
|
+
private readonly timer;
|
|
15
|
+
private generation;
|
|
16
|
+
private tick;
|
|
17
|
+
private checking;
|
|
18
|
+
private disposed;
|
|
19
|
+
private cancel?;
|
|
20
|
+
private detachOwner?;
|
|
21
|
+
constructor(host: ReminderHost, timer?: ReminderClock);
|
|
22
|
+
arm(signal?: AbortSignal): void;
|
|
23
|
+
stop(): void;
|
|
24
|
+
dispose(): void;
|
|
25
|
+
private current;
|
|
26
|
+
private schedule;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const REMINDER_INTERVAL_MS = 5 * 60 * 1000;
|
|
2
|
+
export const REMINDER_TEXT = 'Intercom reminder: Review outstanding delegated work and worker reports. If follow-up is needed, take the next appropriate action. Do not assume workers have finished or stopped.\n\nIf there is genuinely nothing to follow up on, call intercom_stop_reminders. Otherwise, reminders continue every five minutes; a new worker call resets the timer.\n\nThis reminder does not authorize new work, retries, worker closure, or publication.';
|
|
3
|
+
const clock = {
|
|
4
|
+
schedule(callback, milliseconds) {
|
|
5
|
+
const timer = setTimeout(callback, milliseconds);
|
|
6
|
+
timer.unref();
|
|
7
|
+
return () => clearTimeout(timer);
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
/** Ephemeral reminder only: never worker actions, network retries or a backlog. */
|
|
11
|
+
export class CoordinatorReminders {
|
|
12
|
+
host;
|
|
13
|
+
timer;
|
|
14
|
+
generation = 0;
|
|
15
|
+
tick = 0;
|
|
16
|
+
checking = false;
|
|
17
|
+
disposed = false;
|
|
18
|
+
cancel;
|
|
19
|
+
detachOwner;
|
|
20
|
+
constructor(host, timer = clock) {
|
|
21
|
+
this.host = host;
|
|
22
|
+
this.timer = timer;
|
|
23
|
+
}
|
|
24
|
+
arm(signal) {
|
|
25
|
+
if (this.disposed || signal?.aborted)
|
|
26
|
+
return;
|
|
27
|
+
this.stop();
|
|
28
|
+
const generation = this.generation;
|
|
29
|
+
if (signal) {
|
|
30
|
+
const abort = () => { if (this.generation === generation)
|
|
31
|
+
this.stop(); };
|
|
32
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
33
|
+
this.detachOwner = () => signal.removeEventListener('abort', abort);
|
|
34
|
+
}
|
|
35
|
+
this.schedule(generation);
|
|
36
|
+
}
|
|
37
|
+
stop() {
|
|
38
|
+
this.generation++;
|
|
39
|
+
this.tick++;
|
|
40
|
+
this.cancel?.();
|
|
41
|
+
this.cancel = undefined;
|
|
42
|
+
this.detachOwner?.();
|
|
43
|
+
this.detachOwner = undefined;
|
|
44
|
+
}
|
|
45
|
+
dispose() { this.stop(); this.disposed = true; }
|
|
46
|
+
current(generation, tick) {
|
|
47
|
+
return !this.disposed && this.generation === generation && this.tick === tick;
|
|
48
|
+
}
|
|
49
|
+
schedule(generation) {
|
|
50
|
+
this.cancel = this.timer.schedule(() => {
|
|
51
|
+
if (this.disposed || generation !== this.generation)
|
|
52
|
+
return;
|
|
53
|
+
this.cancel = undefined;
|
|
54
|
+
const tick = ++this.tick;
|
|
55
|
+
this.schedule(generation);
|
|
56
|
+
// A slow role read never creates concurrent reads or a queued reminder.
|
|
57
|
+
// Advancing tick also invalidates any read from the preceding interval.
|
|
58
|
+
if (this.checking)
|
|
59
|
+
return;
|
|
60
|
+
this.checking = true;
|
|
61
|
+
void (async () => {
|
|
62
|
+
try {
|
|
63
|
+
const coordinator = await this.host.isCoordinator();
|
|
64
|
+
if (!this.current(generation, tick))
|
|
65
|
+
return;
|
|
66
|
+
if (!coordinator) {
|
|
67
|
+
this.stop();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!this.host.busy())
|
|
71
|
+
this.host.deliver(REMINDER_TEXT);
|
|
72
|
+
}
|
|
73
|
+
catch { /* Unknown role/read/delivery outcome: no blind immediate replay. */ }
|
|
74
|
+
finally {
|
|
75
|
+
this.checking = false;
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
}, REMINDER_INTERVAL_MS);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=reminders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reminders.js","sourceRoot":"","sources":["../src/reminders.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { type Observer, type EventType, type EventMetadata } from './observabili
|
|
|
2
2
|
import { ConfigStore, type Agent, type Config } from './config.js';
|
|
3
3
|
import { listen, send, type Endpoint, type Envelope, type Kind } from './transport.js';
|
|
4
4
|
import { probeWorker } from './connections.js';
|
|
5
|
-
|
|
5
|
+
import { type CloseProvider } from './handoff.js';
|
|
6
|
+
import { type ReminderClock } from './reminders.js';
|
|
7
|
+
export declare const UNSUPPORTED_CANCELLATION = "Unsupported Pi host: stop_worker and legacy graceful-close control are disabled. Pi 0.84.4 extension abort does not cancel retry backoff/continuations; graceful shutdown cannot guarantee no queued work restarts. No cancellation or shutdown was performed. close_worker uses a separate supported-provider owned-pane closure contract, not graceful cancellation. See references/implementation-blocker.md. A verified supported host API is required for graceful cancellation (no version-only override).";
|
|
6
8
|
export interface Host {
|
|
7
9
|
sessionId(): string;
|
|
8
10
|
cwd: string;
|
|
@@ -14,6 +16,7 @@ export interface Host {
|
|
|
14
16
|
export interface LaunchRequest {
|
|
15
17
|
multiplexer: 'herdr' | 'none';
|
|
16
18
|
cwd: string;
|
|
19
|
+
workspaceRoot: string;
|
|
17
20
|
sessionId?: string;
|
|
18
21
|
}
|
|
19
22
|
export interface RuntimeOptions {
|
|
@@ -23,6 +26,12 @@ export interface RuntimeOptions {
|
|
|
23
26
|
probe?: typeof probeWorker;
|
|
24
27
|
store?: (root: string) => ConfigStore;
|
|
25
28
|
observe?: (root: string, sessionId: string) => Observer;
|
|
29
|
+
closeProvider?: CloseProvider;
|
|
30
|
+
/** Test seam; production defaults to 120 seconds, values above that are capped. */
|
|
31
|
+
closeTimeoutMs?: number;
|
|
32
|
+
reminderClock?: ReminderClock;
|
|
33
|
+
/** Explicit launcher-provided root must equal canonical cwd; no parent-root exception. */
|
|
34
|
+
workspaceRoot?: string;
|
|
26
35
|
}
|
|
27
36
|
export declare class Intercom {
|
|
28
37
|
readonly host: Host;
|
|
@@ -32,6 +41,8 @@ export declare class Intercom {
|
|
|
32
41
|
private active;
|
|
33
42
|
private generation;
|
|
34
43
|
private initialId;
|
|
44
|
+
private configuredRoot?;
|
|
45
|
+
private configuredFile?;
|
|
35
46
|
responsibility?: Agent;
|
|
36
47
|
private observer?;
|
|
37
48
|
private closing?;
|
|
@@ -39,6 +50,12 @@ export declare class Intercom {
|
|
|
39
50
|
private pendingReports;
|
|
40
51
|
private probeController;
|
|
41
52
|
private resumeFences;
|
|
53
|
+
private handoff?;
|
|
54
|
+
private reminders?;
|
|
55
|
+
private reminderRevision;
|
|
56
|
+
private reminderInvocation;
|
|
57
|
+
workerStarted(): void;
|
|
58
|
+
workerSettled(successfulToolCallIds: ReadonlySet<string>): void;
|
|
42
59
|
private captureObserver;
|
|
43
60
|
recordObservation(event: EventType, metadata?: EventMetadata): void;
|
|
44
61
|
constructor(host: Host, options: RuntimeOptions);
|
|
@@ -58,6 +75,6 @@ export declare class Intercom {
|
|
|
58
75
|
report(): Promise<void>;
|
|
59
76
|
receive(message: Envelope): Promise<void>;
|
|
60
77
|
private handleReceive;
|
|
61
|
-
tool(operation: string, args: Record<string, unknown
|
|
78
|
+
tool(operation: string, args: Record<string, unknown>, toolCallId?: string, reminderSignal?: AbortSignal): Promise<unknown>;
|
|
62
79
|
private launchObserved;
|
|
63
80
|
}
|