sessionpipe 0.12.0 → 0.13.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 +48 -6
- package/dist/ask.d.ts +40 -0
- package/dist/ask.d.ts.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3098 -1166
- package/dist/cli.js.map +4 -4
- package/dist/control/channel.d.ts +43 -0
- package/dist/control/channel.d.ts.map +1 -0
- package/dist/control/cli.d.ts +13 -1
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +189 -2
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/inbox.d.ts +119 -0
- package/dist/control/inbox.d.ts.map +1 -0
- package/dist/control/local.d.ts +69 -4
- package/dist/control/local.d.ts.map +1 -1
- package/dist/control/pair.d.ts.map +1 -1
- package/dist/control/reply.d.ts +25 -1
- package/dist/control/reply.d.ts.map +1 -1
- package/dist/control/route.d.ts +50 -2
- package/dist/control/route.d.ts.map +1 -1
- package/dist/control/sdk.d.ts.map +1 -1
- package/dist/control/store.d.ts +49 -4
- package/dist/control/store.d.ts.map +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +14 -4
- package/dist/hook.js.map +2 -2
- package/dist/index.js +819 -138
- package/dist/index.js.map +4 -4
- package/dist/run.d.ts +1 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/worker.js +828 -147
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/control/local.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
export type LocalRequest =
|
|
2
|
-
/** A waiter parks until a message for its session arrives.
|
|
2
|
+
/** A waiter parks until a message for its session arrives. `channel`: the waiter is
|
|
3
|
+
* `sessionpipe channel`, which pushes the message into the session rather than handing
|
|
4
|
+
* it to a turn that asked for it (CONTROL.md §8). */
|
|
3
5
|
{
|
|
4
6
|
op: "wait";
|
|
5
7
|
session: string;
|
|
6
8
|
pid?: number;
|
|
9
|
+
channel?: boolean;
|
|
7
10
|
}
|
|
8
|
-
/** A Stop hook asks for a queued message (turn mode).
|
|
11
|
+
/** A Stop hook asks for a queued message (turn mode). `mode` is the session's own
|
|
12
|
+
* permission mode, as its payload reports it (CONTROL.md §6, The session's mode). */
|
|
9
13
|
| {
|
|
10
14
|
op: "stop";
|
|
11
15
|
session: string;
|
|
12
16
|
active: boolean;
|
|
17
|
+
mode?: string;
|
|
13
18
|
}
|
|
14
19
|
/** A PermissionRequest hook parks until a verified answer, or until it's released. */
|
|
15
20
|
| {
|
|
@@ -18,13 +23,15 @@ export type LocalRequest =
|
|
|
18
23
|
attention: string;
|
|
19
24
|
tool?: string;
|
|
20
25
|
}
|
|
21
|
-
/** The worker reports a hook event: turn state
|
|
26
|
+
/** The worker reports a hook event: turn state, attention closure, and the session's
|
|
27
|
+
* own permission mode where its payload named one. */
|
|
22
28
|
| {
|
|
23
29
|
op: "event";
|
|
24
30
|
session: string;
|
|
25
31
|
event: string;
|
|
26
32
|
transcript?: string;
|
|
27
33
|
cwd?: string;
|
|
34
|
+
mode?: string;
|
|
28
35
|
}
|
|
29
36
|
/** `sessionpipe control status`. */
|
|
30
37
|
| {
|
|
@@ -44,8 +51,30 @@ export type LocalRequest =
|
|
|
44
51
|
account?: string;
|
|
45
52
|
order?: string[];
|
|
46
53
|
code?: string;
|
|
47
|
-
}
|
|
54
|
+
}
|
|
55
|
+
/** `sessionpipe ask` (CONTROL.md §13): a session here hands a task to another agent on
|
|
56
|
+
* this machine and gets the answer back on this connection. */
|
|
57
|
+
| AskRequest;
|
|
48
58
|
export type PoolOp = "status" | "on" | "off" | "enable" | "disable" | "remove" | "order" | "refresh" | "login.begin" | "login.complete" | "import";
|
|
59
|
+
export interface AskRequest {
|
|
60
|
+
op: "ask";
|
|
61
|
+
/** `<harness>:<id>` of the asking session, when known: the events go on it. */
|
|
62
|
+
from?: string;
|
|
63
|
+
/** The agent to ask. */
|
|
64
|
+
harness: string;
|
|
65
|
+
/** An existing `<harness>:<id>` to continue instead of a new session. */
|
|
66
|
+
session?: string;
|
|
67
|
+
/** 1–20 000 characters, not all whitespace. */
|
|
68
|
+
text: string;
|
|
69
|
+
/** Absolute; the caller's folder by default. */
|
|
70
|
+
cwd: string;
|
|
71
|
+
/** Every ancestor ref, outermost first (from env SESSIONPIPE_ASK_CHAIN). */
|
|
72
|
+
chain: string[];
|
|
73
|
+
/** Narrower than the machine's mode, never wider. */
|
|
74
|
+
permission?: "read" | "safe";
|
|
75
|
+
model?: string;
|
|
76
|
+
pid?: number;
|
|
77
|
+
}
|
|
49
78
|
export type LocalReply = {
|
|
50
79
|
op: "message";
|
|
51
80
|
text: string;
|
|
@@ -82,10 +111,46 @@ export type LocalReply = {
|
|
|
82
111
|
status?: unknown;
|
|
83
112
|
/** `import`: what was added and what was left out (control/pool.ts Imported). */
|
|
84
113
|
imported?: unknown;
|
|
114
|
+
}
|
|
115
|
+
/** The run began; `session` once it is known. */
|
|
116
|
+
| {
|
|
117
|
+
op: "asked";
|
|
118
|
+
ask_id: string;
|
|
119
|
+
session?: string;
|
|
120
|
+
}
|
|
121
|
+
/** The answer so far, whole, with a rising seq. */
|
|
122
|
+
| {
|
|
123
|
+
op: "progress";
|
|
124
|
+
text: string;
|
|
125
|
+
seq: number;
|
|
126
|
+
} | {
|
|
127
|
+
op: "answer";
|
|
128
|
+
ask_id: string;
|
|
129
|
+
session: string;
|
|
130
|
+
text: string;
|
|
131
|
+
ms: number;
|
|
132
|
+
}
|
|
133
|
+
/** ask_off · harness · folder · depth · loop · start_limit · bad_fields */
|
|
134
|
+
| {
|
|
135
|
+
op: "refused";
|
|
136
|
+
code: string;
|
|
137
|
+
detail: string;
|
|
138
|
+
} | {
|
|
139
|
+
op: "failed";
|
|
140
|
+
detail: string;
|
|
85
141
|
};
|
|
142
|
+
/** The replies that end an ask. */
|
|
143
|
+
export type AskEnd = Extract<LocalReply, {
|
|
144
|
+
op: "answer" | "refused" | "failed";
|
|
145
|
+
}>;
|
|
86
146
|
/** Where the daemon listens. `SESSIONPIPE_SOCKET` overrides (tests). */
|
|
87
147
|
export declare function socketPath(state: string, env?: NodeJS.ProcessEnv): string;
|
|
88
148
|
/** One request, one reply. `timeoutMs` bounds the whole exchange; null on any
|
|
89
149
|
* failure (no daemon, a timeout, a garbled reply) — callers treat that as "none". */
|
|
90
150
|
export declare function ask(sock: string, req: LocalRequest, timeoutMs: number): Promise<LocalReply | null>;
|
|
151
|
+
/** One request, several replies on the same connection (an ask): `onReply` sees each one
|
|
152
|
+
* in order, the promise settles with the one that ends the exchange. `timeoutMs` bounds
|
|
153
|
+
* the whole exchange; null when it ends without an end — no daemon, a timeout, a garbled
|
|
154
|
+
* line, a connection that closed mid-run. */
|
|
155
|
+
export declare function askMany(sock: string, req: LocalRequest, onReply: (r: LocalReply) => void, timeoutMs: number): Promise<AskEnd | null>;
|
|
91
156
|
//# sourceMappingURL=local.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/control/local.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/control/local.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pair.d.ts","sourceRoot":"","sources":["../../src/control/pair.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,cAAc,EAEpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,KAAK,IAAI,MAAM,2BAA2B,CAAC;AAClD,OAAO,
|
|
1
|
+
{"version":3,"file":"pair.d.ts","sourceRoot":"","sources":["../../src/control/pair.ts"],"names":[],"mappings":"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"}
|
package/dist/control/reply.d.ts
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
/** The transcript's size now: where its answer will start. */
|
|
2
2
|
export declare function transcriptEnd(file: string): number;
|
|
3
|
+
/** Who asked for the first turn the transcript gained after a delivery: `ours` — the
|
|
4
|
+
* message this daemon handed the session in place, recognised by its own mark in the row
|
|
5
|
+
* the session wrote for it: a peer row for the inbox (the mark is in the text it was
|
|
6
|
+
* written with), a channel row for `sessionpipe channel` (the tag names the message's
|
|
7
|
+
* id); `other` — any other turn, the person typing in the same second above all, or
|
|
8
|
+
* another local process's message. Null while nothing has been written yet.
|
|
9
|
+
*
|
|
10
|
+
* This is the second half of a delivery's proof. The hook's own `UserPromptSubmit` says
|
|
11
|
+
* a turn began and carries no text, so on its own it cannot tell this message's turn
|
|
12
|
+
* from the person's: confirming on it alone acked a delivery that was really a hold,
|
|
13
|
+
* and then sent the answer to THEIR prompt back to the receiver as the answer to this
|
|
14
|
+
* message. */
|
|
15
|
+
export declare function firstAsk(file: string, from: number, mark: string): "ours" | "other" | null;
|
|
16
|
+
/** The permission mode the rows written after `from` record — what the turn a relaunch
|
|
17
|
+
* just ran ACTUALLY ran in, rather than what was asked for (spec/CONTROL.md §6
|
|
18
|
+
* `prompt`). The last word wins; null when no row says. */
|
|
19
|
+
export declare function ranInMode(file: string, from: number): string | null;
|
|
3
20
|
/** Claude Code records after the delivery → the turn's answer once the turn ended
|
|
4
21
|
* (an assistant record with `stop_reason: "end_turn"`), else null. The answer is the
|
|
5
|
-
* turn's assistant text, in order; sidechains (subagents) are not the session's words.
|
|
22
|
+
* turn's assistant text, in order; sidechains (subagents) are not the session's words.
|
|
23
|
+
*
|
|
24
|
+
* One model message can be written as several records, and every one carries the
|
|
25
|
+
* message's stop reason: when the model thinks before it answers, Claude Code 2.1.314
|
|
26
|
+
* writes a thinking record and then the text, both `end_turn`. A record with no text is
|
|
27
|
+
* not where the answer ends — taking it for the end acked every such answer with no
|
|
28
|
+
* reply. The turn is over at the `end_turn` record that has text, or at whatever follows
|
|
29
|
+
* a textless one (a turn that ended with nothing said). */
|
|
6
30
|
export declare function answerIn(lines: string[]): {
|
|
7
31
|
done: boolean;
|
|
8
32
|
text: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reply.d.ts","sourceRoot":"","sources":["../../src/control/reply.ts"],"names":[],"mappings":"AASA,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;
|
|
1
|
+
{"version":3,"file":"reply.d.ts","sourceRoot":"","sources":["../../src/control/reply.ts"],"names":[],"mappings":"AASA,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;;;;;;;;eAWe;AACf,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAmC1F;AAED;;4DAE4D;AAC5D,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyBnE;AAUD;;;;;;;;;4DAS4D;AAC5D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAgCzE;AAED;;iFAEiF;AACjF,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,yEAAyE;IACzE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,GACA,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA8BxB"}
|
package/dist/control/route.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ControlKind } from "@sessionpipe/core/control";
|
|
2
|
-
export type DeliveryMode = "sdk" | "waiter" | "turn" | "api" | "resume" | "fork";
|
|
2
|
+
export type DeliveryMode = "sdk" | "waiter" | "turn" | "inbox" | "api" | "resume" | "fork";
|
|
3
3
|
export interface SessionView {
|
|
4
4
|
harness: string;
|
|
5
5
|
/** The daemon holds this session through the Agent SDK right now. */
|
|
@@ -8,12 +8,18 @@ export interface SessionView {
|
|
|
8
8
|
waiter: boolean;
|
|
9
9
|
/** Between a turn's start and its Stop (the Stop hook will ask). */
|
|
10
10
|
midTurn: boolean;
|
|
11
|
+
/** A live, idle, vetted session whose own permission class is this machine's: its own
|
|
12
|
+
* local inbox takes the message (§6.3). */
|
|
13
|
+
inbox: boolean;
|
|
11
14
|
/** A live process holds the session (registry entry or a fresh transcript write). */
|
|
12
15
|
live: boolean;
|
|
13
16
|
/** The transcript exists on this machine (Codex, Antigravity: the session was placed). */
|
|
14
17
|
known: boolean;
|
|
15
18
|
/** This Claude Code can copy a session (`--fork-session`); no other harness can. */
|
|
16
19
|
canFork: boolean;
|
|
20
|
+
/** It already has a copy, which is where its messages go: a copy is a leaf and is
|
|
21
|
+
* never itself copied (§6 `prompt`). */
|
|
22
|
+
copied: boolean;
|
|
17
23
|
/** The harness has an input API (OpenCode, Codex app-server) — none is wired yet. */
|
|
18
24
|
api: boolean;
|
|
19
25
|
}
|
|
@@ -27,8 +33,18 @@ export type Route = {
|
|
|
27
33
|
/** The harnesses the daemon can run a turn of by itself: resume an idle session, start a
|
|
28
34
|
* new one (`claude -p`, `codex exec`, `agy -p`, or any ACP agent as its client). */
|
|
29
35
|
export declare const HEADLESS: Set<string>;
|
|
36
|
+
/** The ladder in one place, so its order is a fact and not a shape of the code: the
|
|
37
|
+
* first rung that fits wins (spec/CONTROL.md §6). The in-place rungs come first,
|
|
38
|
+
* because the session answers in the person's own window or thread, in its own
|
|
39
|
+
* permission mode, with nothing started and nothing replayed. Among those, the ones
|
|
40
|
+
* the session itself opened — a parked waiter, a running turn — come before the one
|
|
41
|
+
* where the daemon writes to it unasked. */
|
|
42
|
+
export declare const DELIVERY_LADDER: readonly ["sdk", "waiter", "turn", "inbox", "api", "resume", "fork"];
|
|
43
|
+
/** Does `a` sit above `b` on the ladder? The rungs above `inbox` are all free to test,
|
|
44
|
+
* so a message that takes one of them never pays for the inbox's own look. */
|
|
45
|
+
export declare const earlier: (a: DeliveryMode, b: DeliveryMode) => boolean;
|
|
30
46
|
/** Where a prompt (or a start) goes. Never two writers on one transcript: a live
|
|
31
|
-
* session gets a fork, never a resume. */
|
|
47
|
+
* session gets an in-place lane or a fork, never a resume. */
|
|
32
48
|
export declare function routePrompt(s: SessionView): Route;
|
|
33
49
|
/** How each kind is delivered, before the session's state is looked at. */
|
|
34
50
|
export declare function kindPath(kind: ControlKind, harness: string): "prompt" | "permission" | "cancel" | "start" | "pool" | {
|
|
@@ -41,10 +57,42 @@ export declare function kindPath(kind: ControlKind, harness: string): "prompt" |
|
|
|
41
57
|
* command to reply with (a real session tried \`sessionpipe send\`, 2026-10-01). */
|
|
42
58
|
export declare const IN_PLACE_ANSWER = "Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically. Don't run a command or call a tool just to send it.";
|
|
43
59
|
export declare function frame(text: string, receiver: string): string;
|
|
60
|
+
/** frame() undone: the person's words, and the host they were sent from. A channel shows
|
|
61
|
+
* the words as the message and says where they came from beside it. Text that frame()
|
|
62
|
+
* didn't make comes back whole, from nowhere. */
|
|
63
|
+
export declare function unframe(text: string): {
|
|
64
|
+
text: string;
|
|
65
|
+
via: string | null;
|
|
66
|
+
};
|
|
67
|
+
/** What the daemon looks for in the row the session writes for the turn this message
|
|
68
|
+
* caused (§6.3): a turn is only proof of delivery when it is THIS message's turn. */
|
|
69
|
+
export declare const inboxMark: (id: string) => string;
|
|
70
|
+
/** The same for a channel's message (§8): `sessionpipe channel` names the message's id on
|
|
71
|
+
* its `<channel>` tag, and Claude Code writes the tag into the row of the turn it causes. */
|
|
72
|
+
export declare const channelMark: (id: string) => string;
|
|
73
|
+
/** The framed text for the `inbox` lane: `frame()` with that mark in it, since the
|
|
74
|
+
* message becomes a prompt of the session's own and the daemon has to recognise it
|
|
75
|
+
* again in the transcript. */
|
|
76
|
+
export declare function frameInbox(text: string, receiver: string, id: string): string;
|
|
77
|
+
/** Said with a message a copy of a session is answering, so the model knows the person
|
|
78
|
+
* is not reading this conversation: a copy is not the thread they are looking at. */
|
|
79
|
+
export declare const COPY_NOTE = "You are a copy of that session, started to answer this message: the conversation above is a replay, the original session is somewhere else, and nothing you do here appears in it. Answer the message and stop; don't carry on the original's work.";
|
|
44
80
|
/** A new session's first message: the person's words first, where they come from after.
|
|
45
81
|
* Claude Code names a session from the start of its first message, so a header in front
|
|
46
82
|
* named every started session "Message via spacesheep.dev (sessionpipe control, s…"
|
|
47
83
|
* (2026-10-02). In a new session there is no one else's conversation to tell it apart
|
|
48
84
|
* from, so the source line can follow the ask. */
|
|
49
85
|
export declare function frameStart(text: string, receiver: string): string;
|
|
86
|
+
/** What a session another session asked receives (CONTROL.md §13), new or not: the task
|
|
87
|
+
* first, as frameStart puts it, then who asked and that the answer travels by itself. */
|
|
88
|
+
export declare function frameAsk(text: string, from: string | undefined): string;
|
|
89
|
+
/** A handoff's answer as it lands in `reply_to` (CONTROL.md §6): who answered, in the
|
|
90
|
+
* short form the receivers label sessions by, and which receiver the person asked through. */
|
|
91
|
+
export declare function frameHandoff(from: {
|
|
92
|
+
harness: string;
|
|
93
|
+
id: string;
|
|
94
|
+
}, receiver: string, reply: string): string;
|
|
95
|
+
/** The receiver's own host, for a line a person reads; the whole string when it is not
|
|
96
|
+
* a URL. */
|
|
97
|
+
export declare function hostOf(receiver: string): string;
|
|
50
98
|
//# sourceMappingURL=route.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/control/route.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/control/route.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/control/sdk.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/control/sdk.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAc,MAAM,aAAa,CAAC;AAmBvD,wBAAsB,OAAO,CAAC,CAAC,EAAE;IAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1B,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAkG1B"}
|
package/dist/control/store.d.ts
CHANGED
|
@@ -31,13 +31,19 @@ export interface ControlConfig {
|
|
|
31
31
|
receivers: PairedReceiver[];
|
|
32
32
|
/** Caps this machine keeps whatever a receiver sends (defaults in daemon.ts,
|
|
33
33
|
* LIMITS): new sessions running at once and per hour, headless Claude Code runs
|
|
34
|
-
* at once. */
|
|
34
|
+
* at once, copies of sessions made in a day. */
|
|
35
35
|
limits?: {
|
|
36
36
|
start_concurrent?: number;
|
|
37
37
|
start_per_hour?: number;
|
|
38
38
|
headless?: number;
|
|
39
|
+
copies_per_day?: number;
|
|
39
40
|
};
|
|
41
|
+
/** Whether a session here may ask another agent through the daemon (CONTROL.md §13).
|
|
42
|
+
* On unless `sessionpipe ask off` set it false; the daemon says which in its hello. */
|
|
43
|
+
ask?: boolean;
|
|
40
44
|
}
|
|
45
|
+
/** `ask` as the config means it: on when unsaid. */
|
|
46
|
+
export declare const askOn: (c: Pick<ControlConfig, "ask">) => boolean;
|
|
41
47
|
export declare const controlFile: (env?: NodeJS.ProcessEnv) => string;
|
|
42
48
|
export declare const controlState: (env?: NodeJS.ProcessEnv) => string;
|
|
43
49
|
export declare function writePrivate(file: string, value: unknown): void;
|
|
@@ -83,6 +89,10 @@ export interface Ack {
|
|
|
83
89
|
reply?: string;
|
|
84
90
|
/** A start's taken and delivered: what it ran with (CONTROL.md §6, Options). */
|
|
85
91
|
applied?: StartOptions;
|
|
92
|
+
/** This answer came from a COPY of the session the command named, not from it: the
|
|
93
|
+
* person's words went somewhere other than the conversation they were looking at,
|
|
94
|
+
* and they are owed that fact (CONTROL.md §6 `fork`). */
|
|
95
|
+
copy_of?: string;
|
|
86
96
|
}
|
|
87
97
|
/** What a session the daemon started runs with: its level and, when one was asked for,
|
|
88
98
|
* its model. Recorded at the start; every later run of the session reads it. */
|
|
@@ -103,13 +113,48 @@ export declare class AckOutbox {
|
|
|
103
113
|
add(a: Ack): void;
|
|
104
114
|
keep(left: Ack[]): void;
|
|
105
115
|
}
|
|
116
|
+
/** How long a message written into a live session's own inbox keeps that lane shut for
|
|
117
|
+
* it (CONTROL.md §6.3): Claude Code holds an unmatched message that long before
|
|
118
|
+
* dropping it, and a hold can be released, so a second write inside the window would
|
|
119
|
+
* mean the person's words arriving twice. It outlives the daemon that wrote it, so a
|
|
120
|
+
* restart — an update installs one — cannot turn a resend into a second frame. */
|
|
121
|
+
export declare const HOLD_TTL_MS = 300000;
|
|
122
|
+
/** A copy the daemon made because it could not reach the original (CONTROL.md §6
|
|
123
|
+
* `fork`), and what the person is owed about it: which session it is, when it was made
|
|
124
|
+
* and why there is one at all. Daemons up to 0.10.0 wrote the id alone. */
|
|
125
|
+
export interface Copy {
|
|
126
|
+
id: string;
|
|
127
|
+
at: string;
|
|
128
|
+
/** In the machine's own words, for the ack and the board: why a copy, not the session. */
|
|
129
|
+
reason: string;
|
|
130
|
+
}
|
|
131
|
+
/** A message written into a session's own inbox that it has not taken yet
|
|
132
|
+
* (CONTROL.md §6.3), by the session it was written to. */
|
|
133
|
+
export interface Parked {
|
|
134
|
+
/** When it was written, ISO-8601. Older than HOLD_TTL_MS is no longer a hold. */
|
|
135
|
+
at: string;
|
|
136
|
+
/** The message's id, so a log or a later ack can name the one that is waiting. */
|
|
137
|
+
id: string;
|
|
138
|
+
}
|
|
106
139
|
export interface DaemonState {
|
|
107
|
-
/** Original session → the
|
|
108
|
-
copies: Record<string,
|
|
140
|
+
/** Original session → the copy its messages go to. */
|
|
141
|
+
copies: Record<string, Copy>;
|
|
109
142
|
/** Claude Code sessions the daemon started → what they run with (Codex and
|
|
110
143
|
* Antigravity keep theirs in the alias entry, aliases.ts). */
|
|
111
144
|
started: Record<string, StartOptions>;
|
|
145
|
+
/** Session ref → the permission mode its own hooks last reported (CONTROL.md §6, The
|
|
146
|
+
* session's mode). The daemon's own record, in its own state: no Claude Code home is
|
|
147
|
+
* written to. Insertion order is the age order, which is how the oldest are dropped. */
|
|
148
|
+
modes: Record<string, string>;
|
|
149
|
+
/** Session ref → a message written into its own inbox that it has not taken. Read back
|
|
150
|
+
* pruned: only the ones still inside the hold window. */
|
|
151
|
+
parked: Record<string, Parked>;
|
|
112
152
|
}
|
|
113
|
-
export declare function readState(file: string): DaemonState;
|
|
153
|
+
export declare function readState(file: string, now?: number): DaemonState;
|
|
154
|
+
/** Both shapes of `copies`, every time. A daemon up to 0.10.0 reads `copies` as a map of
|
|
155
|
+
* plain session ids and uses what it finds there as a `--resume` target, so writing
|
|
156
|
+
* only the record would make every message to a session with a copy resume
|
|
157
|
+
* `[object Object]` after a rollback — and a fleet keeps the previous build precisely
|
|
158
|
+
* so it can roll back. */
|
|
114
159
|
export declare const writeState: (file: string, s: DaemonState) => void;
|
|
115
160
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/control/store.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAqC,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC9G,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,SAAS,EAIf,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;sEACkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,IAAI,EAAE,CAAC,UAAU,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;IAC3D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;wCACwC;AACxC,eAAO,MAAM,KAAK,YAAI,MAAM,EAAE,MAAM,EAAE,SAAS,CAAU,CAAC;AAC1D,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AACjD,eAAO,MAAM,SAAS,MAAO,OAAO,KAAG,WAAW,GAAG,IACkB,CAAC;AAExE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B;;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/control/store.ts"],"names":[],"mappings":"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"}
|
package/dist/help.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA4D/E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAO5F"}
|
package/dist/hook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":";AA0BA,iFAAiF;AACjF,MAAM,WAAW,MAAM;IACrB,KAAK,IAAI,MAAM,CAAC;IAChB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;0FAE0F;AAC1F,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwRhG"}
|
package/dist/hook.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.
|
|
2
|
+
// sessionpipe 0.13.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/hook.ts
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
@@ -50,6 +50,11 @@ function ask(sock, req, timeoutMs) {
|
|
|
50
50
|
|
|
51
51
|
// src/hook.ts
|
|
52
52
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
53
|
+
var MODE_SHAPE = /^[A-Za-z]{1,40}$/;
|
|
54
|
+
var modeOf = (s) => {
|
|
55
|
+
const v = typeof s.permission_mode === "string" ? s.permission_mode : s.permissionMode;
|
|
56
|
+
return typeof v === "string" && MODE_SHAPE.test(v) ? v : void 0;
|
|
57
|
+
};
|
|
53
58
|
async function hookMain(argv, env, io) {
|
|
54
59
|
const t0 = process.hrtime.bigint();
|
|
55
60
|
let workMs = null;
|
|
@@ -126,6 +131,8 @@ async function hookMain(argv, env, io) {
|
|
|
126
131
|
// Devin's payload names no folder at all; this is the only thing that does.
|
|
127
132
|
"DEVIN_PROJECT_DIR",
|
|
128
133
|
"GEMINI_CLI_HOME",
|
|
134
|
+
// The session the control daemon ran this one for (PROTOCOL.md §3 delegated_by).
|
|
135
|
+
"SESSIONPIPE_ASKED_BY",
|
|
129
136
|
"SESSIONPIPE_CONTROL_JOB",
|
|
130
137
|
// The launcher a script named for a session it starts (PROTOCOL.md §3 launched_by).
|
|
131
138
|
"SESSIONPIPE_LAUNCHED_BY",
|
|
@@ -186,10 +193,13 @@ async function hookMain(argv, env, io) {
|
|
|
186
193
|
if (!id) return null;
|
|
187
194
|
if (typeof s.transcript_path !== "string" && !UUID.test(id)) return null;
|
|
188
195
|
const session = `claude-code:${id}`;
|
|
196
|
+
const mode = modeOf(s);
|
|
189
197
|
if (event === "Stop")
|
|
190
|
-
return ask(
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
return ask(
|
|
199
|
+
sock,
|
|
200
|
+
{ op: "stop", session, active: s.stop_hook_active === true, ...mode ? { mode } : {} },
|
|
201
|
+
300
|
|
202
|
+
).then((r) => r?.op === "block" ? JSON.stringify({ decision: "block", reason: r.reason }) : null);
|
|
193
203
|
const tool = typeof s.tool_name === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(s.tool_name) ? s.tool_name : void 0;
|
|
194
204
|
const turn = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
195
205
|
const attention = `perm-${createHash("sha256").update(`${turn ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
package/dist/hook.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/hook.ts", "../src/control/local.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n// SPDX-License-Identifier: Apache-2.0\n// The hook path. A harness runs `node hook.js <harness> <event>`; this reads stdin,\n// writes one job file, spawns the worker detached, prints what the harness needs\n// (Antigravity's answer) and exits 0 \u2014 in milliseconds, with nothing but node:\n// builtins imported (the build checks that). No network, no schema library, no\n// config parsing beyond one small file read for the throttle.\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { ask, socketPath } from \"./control/local.js\";\n\n/** A Claude Code session id (the same test as core's notClaudeCode). */\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/** The two streams the hook touches: stdin once, whole; stdout for an answer. */\nexport interface HookIO {\n stdin(): Buffer;\n stdout(text: string): void;\n}\n\n/** One hook run: `argv` is [harness, event, ...] as the harness invoked it, `env` the\n * process environment. Resolves once the job is on its way and any answer printed;\n * the process then exits 0, whatever happened (the hook is invisible to the harness). */\nexport async function hookMain(argv: string[], env: NodeJS.ProcessEnv, io: HookIO): Promise<void> {\n const t0 = process.hrtime.bigint();\n /** The hook's own work, when a control wait follows it (the wait is not the hook's cost). */\n let workMs: number | null = null;\n const [harness = \"\", event = \"\", ...rest] = argv;\n\n function stateDir(): string {\n if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);\n const home = os.homedir();\n if (process.platform === \"win32\")\n return path.join(env.LOCALAPPDATA || path.join(home, \"AppData\", \"Local\"), \"sessionpipe\");\n return path.join(env.XDG_STATE_HOME || path.join(home, \".local\", \"state\"), \"sessionpipe\");\n }\n function configFile(): string {\n if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);\n const home = os.homedir();\n if (process.platform === \"win32\")\n return path.join(env.APPDATA || path.join(home, \"AppData\", \"Roaming\"), \"sessionpipe\", \"config.json\");\n return path.join(env.XDG_CONFIG_HOME || path.join(home, \".config\"), \"sessionpipe\", \"config.json\");\n }\n\n // Antigravity parses stdout as the hook's answer; `{}` is \"carry on\" for every event we\n // register. PreToolUse is not one of them: `{}` there is a deny, so an entry an older\n // install left behind asks the person instead (core's AG_STALE_PRETOOL, same bytes).\n if (harness === \"antigravity\") {\n try {\n io.stdout(\n event === \"PreToolUse\"\n ? '{\"decision\":\"ask\",\"reason\":\"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it.\"}\\n'\n : \"{}\\n\",\n );\n } catch {}\n }\n\n const state = stateDir();\n try {\n if (!harness || !event) throw new Error(\"usage: hook <harness> <event>\");\n let stdin = \"\";\n if (harness !== \"codex\" || event !== \"notify\") {\n try {\n const buf = io.stdin();\n stdin = buf.subarray(0, 1024 * 1024).toString(\"utf8\");\n } catch {}\n }\n // Claude Code's status line (install --status-line) runs on every refresh. It shows\n // the account's limits, and only a change becomes a job (limits.observed): at most\n // one a minute per config dir, and none when no sink takes tier 1.\n if (harness === \"claude-code\" && event === \"StatusLine\") {\n const limits = statusLimits(stdin);\n if (limits.line) {\n try {\n io.stdout(`${limits.line}\\n`);\n } catch {}\n }\n if (!limits.sig || !anySinkAbove(0) || !limitsDue(limits.sig)) return finish(false);\n }\n // Tool events are the bulk of what a harness fires. When no sink takes tier \u2265 1\n // they are only a \"still working\" signal: one heartbeat a minute per session.\n const TOOL = /^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/;\n if (TOOL.test(event) && !anySinkAbove(0)) {\n const id = sessionIdOf(stdin);\n if (!id || !beatDue(id)) return finish(false);\n }\n // A Stop asks the daemon BEFORE its event goes to the worker: the worker reports the\n // Stop to the daemon too, and a message queued for this Stop must be taken here,\n // not rerouted because the event got there first.\n const stopAnswer = event === \"Stop\" ? await (controlAsk(stdin) ?? Promise.resolve(null)) : null;\n mkdirSync(path.join(state, \"jobs\"), { recursive: true, mode: 0o700 });\n const job = {\n harness,\n event,\n argv: [harness, event, ...rest],\n stdin,\n cwd: process.cwd(),\n env: pick([\n // Where a harness keeps its config and its sessions, so a job swept by a worker\n // some other harness's hook spawned still resolves this harness's own homes\n // (run.ts sweepJobs runs any stale job file, whatever spawned the worker).\n \"APPDATA\",\n \"CLAUDE_CODE_HOST_SESSION_ID\",\n // session.attended: Claude Code's own word, and SESSIONPIPE_CONTROL_JOB below, the\n // control daemon's mark on a turn it runs for a person's message.\n \"CLAUDE_CODE_SESSION_ATTENDED\",\n \"CLAUDE_CONFIG_DIR\",\n \"CLAUDE_PROJECT_DIR\",\n \"CODEX_HOME\",\n // Devin's payload names no folder at all; this is the only thing that does.\n \"DEVIN_PROJECT_DIR\",\n \"GEMINI_CLI_HOME\",\n \"SESSIONPIPE_CONTROL_JOB\",\n // The launcher a script named for a session it starts (PROTOCOL.md \u00A73 launched_by).\n \"SESSIONPIPE_LAUNCHED_BY\",\n // Devin's config file and session store are both resolved through these.\n \"XDG_CONFIG_HOME\",\n \"XDG_DATA_HOME\",\n ]),\n ppid: process.ppid,\n at: Date.now(),\n };\n const jobFile = path.join(\n state,\n \"jobs\",\n `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`,\n );\n writeFileSync(jobFile, JSON.stringify(job), { mode: 0o600 });\n if (!env.SESSIONPIPE_NO_WORKER) {\n const worker = path.join(path.dirname(fileURLToPath(import.meta.url)), \"worker.js\");\n const p = spawn(process.execPath, [worker, jobFile], { detached: true, stdio: \"ignore\", windowsHide: true, env });\n p.unref();\n }\n if (stopAnswer) {\n try {\n io.stdout(`${stopAnswer}\\n`);\n } catch {}\n }\n // A PermissionRequest waits AFTER its event is on its way, so the receiver already\n // shows the Allow / Deny this answer is for.\n const ctl = event === \"PermissionRequest\" ? controlAsk(stdin) : null;\n if (ctl) {\n workMs = Number(process.hrtime.bigint() - t0) / 1e6;\n const out = await ctl;\n if (out) {\n try {\n io.stdout(`${out}\\n`);\n } catch {}\n }\n }\n return finish(true);\n } catch {\n return finish(false); // the hook must be invisible to the harness\n }\n\n /** Control (spec/CONTROL.md \u00A76, \u00A79): only on a machine with control paired, and only\n * for a message the daemon itself verified. A Stop hook asks for a queued message and\n * answers a block with it; a PermissionRequest hook waits for the person's signed\n * answer while the terminal shows its own prompt, first answer wins. Anything that\n * goes wrong (no daemon, a timeout) prints nothing: the harness carries on as usual. */\n function controlAsk(stdin: string): Promise<string | null> | null {\n if (harness !== \"claude-code\" || (event !== \"Stop\" && event !== \"PermissionRequest\")) return null;\n // A turn the daemon itself runs (a held Agent SDK session, a headless resume) never\n // asks at its Stop: the daemon hands that session its next message itself. Its\n // permission prompts do park: nobody is at a terminal there, so the owner's signed\n // answer is the only one it can get (a session started at edits, auto or inherit\n // asks before a Bash).\n if (env.SESSIONPIPE_CONTROL_JOB && event === \"Stop\") return null;\n const sock = socketPath(state, env);\n if (\n process.platform === \"win32\"\n ? !existsSync(path.join(path.dirname(configFile()), \"control.json\"))\n : !existsSync(sock)\n )\n return null;\n let s: Record<string, unknown>;\n try {\n s = JSON.parse(stdin) as Record<string, unknown>;\n } catch {\n return null;\n }\n const id = typeof s.session_id === \"string\" ? s.session_id : \"\";\n if (!id) return null;\n // Our entry in ~/.claude/settings.json is run by every harness that imports that file\n // \u2014 Devin's `claude` hooks provider is live, not a stub \u2014 and it arrives under the\n // claude-code name. Every Claude Code payload names a transcript and a UUID session;\n // a payload with neither is somebody else's, and must not be parked for up to 125 s\n // waiting on an answer that is not coming. Same test as core's notClaudeCode\n // (claude-code.ts), which the worker applies; repeated here because this file imports\n // nothing but node builtins.\n if (typeof s.transcript_path !== \"string\" && !UUID.test(id)) return null;\n const session = `claude-code:${id}`;\n if (event === \"Stop\")\n return ask(sock, { op: \"stop\", session, active: s.stop_hook_active === true }, 300).then((r) =>\n r?.op === \"block\" ? JSON.stringify({ decision: \"block\", reason: r.reason }) : null,\n );\n // The attention id the adapter puts on attention.needed (claude-code.ts), so the\n // receiver's Allow / Deny names this very prompt.\n const tool =\n typeof s.tool_name === \"string\" && /^[A-Za-z0-9_.:-]{1,120}$/.test(s.tool_name) ? s.tool_name : undefined;\n const turn = typeof s.prompt_id === \"string\" ? s.prompt_id : undefined;\n const attention = `perm-${createHash(\"sha256\")\n .update(`${turn ?? \"\"}|${tool ?? \"\"}|${JSON.stringify(s.tool_input ?? null)}`)\n .digest(\"hex\")\n .slice(0, 16)}`;\n return ask(sock, { op: \"permission\", session, attention, ...(tool ? { tool } : {}) }, 125_000).then((r) =>\n r?.op === \"decision\"\n ? JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PermissionRequest\",\n decision:\n r.behavior === \"allow\"\n ? { behavior: \"allow\" }\n : { behavior: \"deny\", message: r.message || \"Denied by the owner (sessionpipe control).\" },\n },\n })\n : null,\n );\n }\n\n /** The timing line; the process exits 0 right after. */\n function finish(spawned: boolean): void {\n const total = Number(process.hrtime.bigint() - t0) / 1e6;\n const ms = workMs ?? total;\n try {\n mkdirSync(state, { recursive: true });\n appendFileSync(\n path.join(state, \"timing.jsonl\"),\n `${JSON.stringify({ at: Date.now(), harness, event, ms: Math.round(ms * 10) / 10, spawned, ...(workMs !== null ? { control_wait_ms: Math.round(total - workMs) } : {}) })}\\n`,\n );\n } catch {}\n }\n function pick(names: string[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const n of names) if (env[n] !== undefined) out[n] = env[n] as string;\n return out;\n }\n function anySinkAbove(tier: number): boolean {\n try {\n const c = JSON.parse(readFileSync(configFile(), \"utf8\")) as { sinks?: { tier?: number; paused?: string }[] };\n return (c.sinks ?? []).some((s) => Number(s.tier) > tier && !s.paused);\n } catch {\n return false;\n }\n }\n function sessionIdOf(stdin: string): string {\n const m = /\"(?:session_id|sessionId|conversationId)\"\\s*:\\s*\"([^\"]{1,128})\"/.exec(stdin);\n return m?.[1] ?? \"\";\n }\n /** The status line's `rate_limits` as one short line (`5h 23% \u00B7 week 41%`), and as\n * the text a change is told by. Empty when the account has none. */\n function statusLimits(stdin: string): { line: string; sig: string } {\n let rl: unknown;\n try {\n rl = (JSON.parse(stdin) as { rate_limits?: unknown }).rate_limits;\n } catch {}\n if (!rl || typeof rl !== \"object\") return { line: \"\", sig: \"\" };\n const label: Record<string, string> = { five_hour: \"5h\", seven_day: \"week\", spend_limit: \"spend\" };\n const parts = Object.entries(rl as Record<string, { used_percentage?: unknown }>)\n .filter(([, w]) => typeof w?.used_percentage === \"number\")\n .map(([id, w]) => `${label[id] ?? id} ${Math.round(w.used_percentage as number)}%`);\n return parts.length ? { line: parts.join(\" \u00B7 \"), sig: JSON.stringify(rl) } : { line: \"\", sig: \"\" };\n }\n /** A change of limits, at most one a minute per config dir (one account each): the\n * stamp holds the last limits sent, its mtime when. */\n function limitsDue(sig: string): boolean {\n const dir = createHash(\"sha256\")\n .update(env.CLAUDE_CONFIG_DIR ?? \"\")\n .digest(\"hex\")\n .slice(0, 12);\n const stamp = path.join(state, \"beats\", `${harness}-limits-${dir}`);\n try {\n if (readFileSync(stamp, \"utf8\") === sig || Date.now() - statSync(stamp).mtimeMs < 60_000) return false;\n } catch {}\n try {\n mkdirSync(path.dirname(stamp), { recursive: true, mode: 0o700 });\n writeFileSync(stamp, sig, { mode: 0o600 });\n } catch {}\n return true;\n }\n /** One heartbeat per minute per session: the stamp file's mtime is the clock. */\n function beatDue(id: string): boolean {\n const stamp = path.join(state, \"beats\", `${harness}-${id.replace(/[^A-Za-z0-9_-]/g, \"_\")}`);\n try {\n if (Date.now() - statSync(stamp).mtimeMs < 60_000) return false;\n } catch {}\n try {\n mkdirSync(path.dirname(stamp), { recursive: true, mode: 0o700 });\n writeFileSync(stamp, \"\", { mode: 0o600 });\n } catch {}\n return true;\n }\n}\n\n// Run as `node hook.js <harness> <event>`; importing this module runs nothing. The main\n// module is known by its real path: node resolves symlinks for import.meta.url, argv[1]\n// keeps the spelling it was given (/var \u2192 /private/var on macOS).\nif (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href)\n hookMain(process.argv.slice(2), process.env, {\n // fd 0 and 1 are the test runner's own (a read of 0 would block it); launcher.test.ts\n // and the bench run these two through the real binary.\n /* v8 ignore next -- see above */\n stdin: () => readFileSync(0),\n /* v8 ignore next -- see above */\n stdout: (text) => writeFileSync(1, text),\n }).then(() => process.exit(0));\n", "// SPDX-License-Identifier: Apache-2.0\n// The daemon's local socket (spec/CONTROL.md \u00A78): how the waiter, the Stop and\n// PermissionRequest hooks and the worker talk to the one daemon on this machine.\n// Line-delimited JSON, one request per connection. A Unix socket in the state\n// directory (0600, in a 0700 dir); a per-user named pipe on Windows. Only node:\n// builtins \u2014 the hook bundle imports this.\nimport net from \"node:net\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type LocalRequest =\n /** A waiter parks until a message for its session arrives. */\n | { op: \"wait\"; session: string; pid?: number }\n /** A Stop hook asks for a queued message (turn mode). */\n | { op: \"stop\"; session: string; active: boolean }\n /** A PermissionRequest hook parks until a verified answer, or until it's released. */\n | { op: \"permission\"; session: string; attention: string; tool?: string }\n /** The worker reports a hook event: turn state and attention closure. */\n | { op: \"event\"; session: string; event: string; transcript?: string; cwd?: string }\n /** `sessionpipe control status`. */\n | { op: \"status\" }\n /** `sessionpipe update` installed a new copy: restart onto it once nothing is in hand. */\n | { op: \"restart\" }\n /** `sessionpipe pool \u2026` while the daemon runs: the daemon holds the pool's engine\n * (pins, credentials, the listener), so a change goes through it. `status` reads;\n * the rest are CONTROL.md \u00A76's actions, plus `import` (reads this machine's own\n * Claude Code logins, which no receiver may ask for). */\n | { op: \"pool\"; action: PoolOp; account?: string; order?: string[]; code?: string };\n\nexport type PoolOp =\n | \"status\"\n | \"on\"\n | \"off\"\n | \"enable\"\n | \"disable\"\n | \"remove\"\n | \"order\"\n | \"refresh\"\n | \"login.begin\"\n | \"login.complete\"\n | \"import\";\n\nexport type LocalReply =\n | { op: \"message\"; text: string; id: string }\n | { op: \"block\"; reason: string; id: string }\n | { op: \"decision\"; behavior: \"allow\" | \"deny\"; message?: string; id: string }\n | { op: \"none\" }\n /** A newer waiter for the same session took over; this one stops. */\n | { op: \"replaced\" }\n | { op: \"ok\" }\n | { op: \"status\"; status: unknown }\n /** A pool op's answer: the ack it would earn (CONTROL.md \u00A76) and the pool as it now\n * stands (`status`: the fa\u00E7ade's PoolStatus; `detail`: the summary line, or a\n * login.begin's \"<pending id> <URL>\"). */\n | {\n op: \"pool\";\n outcome: \"delivered\" | \"refused\" | \"failed\" | \"unsupported\";\n code?: string;\n detail: string;\n status?: unknown;\n /** `import`: what was added and what was left out (control/pool.ts Imported). */\n imported?: unknown;\n };\n\n/** Where the daemon listens. `SESSIONPIPE_SOCKET` overrides (tests). */\nexport function socketPath(state: string, env: NodeJS.ProcessEnv = process.env): string {\n if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;\n if (process.platform === \"win32\") return `\\\\\\\\.\\\\pipe\\\\sessionpipe-control-${os.userInfo().username}`;\n return path.join(state, \"control.sock\");\n}\n\n/** One request, one reply. `timeoutMs` bounds the whole exchange; null on any\n * failure (no daemon, a timeout, a garbled reply) \u2014 callers treat that as \"none\". */\nexport function ask(sock: string, req: LocalRequest, timeoutMs: number): Promise<LocalReply | null> {\n return new Promise((resolve) => {\n let buf = \"\";\n let done = false;\n const s = net.connect(sock);\n const finish = (r: LocalReply | null) => {\n if (done) return;\n done = true;\n clearTimeout(t);\n s.destroy();\n resolve(r);\n };\n const t = setTimeout(() => finish(null), timeoutMs);\n s.on(\"connect\", () => s.write(`${JSON.stringify(req)}\\n`));\n s.on(\"data\", (d) => {\n buf += d.toString(\"utf8\");\n const i = buf.indexOf(\"\\n\");\n if (i < 0) return;\n try {\n finish(JSON.parse(buf.slice(0, i)) as LocalReply);\n } catch {\n finish(null);\n }\n });\n s.on(\"error\", () => finish(null));\n s.on(\"close\", () => finish(null));\n });\n}\n"],
|
|
5
|
-
"mappings": ";;;;AAOA,SAAS,aAAa;AACtB,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,YAAY,WAAW,cAAc,cAAc,UAAU,qBAAqB;AAC3G,OAAOA,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,eAAe,qBAAqB;;;
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n// SPDX-License-Identifier: Apache-2.0\n// The hook path. A harness runs `node hook.js <harness> <event>`; this reads stdin,\n// writes one job file, spawns the worker detached, prints what the harness needs\n// (Antigravity's answer) and exits 0 \u2014 in milliseconds, with nothing but node:\n// builtins imported (the build checks that). No network, no schema library, no\n// config parsing beyond one small file read for the throttle.\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { ask, socketPath } from \"./control/local.js\";\n\n/** A Claude Code session id (the same test as core's notClaudeCode). */\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n/** A permission mode as a hook payload spells it (core's `hookMode`, repeated because\n * this file imports nothing but node builtins). Claude Code writes `permission_mode`;\n * `permissionMode` is the spelling its transcript records use, taken too. */\nconst MODE_SHAPE = /^[A-Za-z]{1,40}$/;\nconst modeOf = (s: Record<string, unknown>): string | undefined => {\n const v = typeof s.permission_mode === \"string\" ? s.permission_mode : s.permissionMode;\n return typeof v === \"string\" && MODE_SHAPE.test(v) ? v : undefined;\n};\n\n/** The two streams the hook touches: stdin once, whole; stdout for an answer. */\nexport interface HookIO {\n stdin(): Buffer;\n stdout(text: string): void;\n}\n\n/** One hook run: `argv` is [harness, event, ...] as the harness invoked it, `env` the\n * process environment. Resolves once the job is on its way and any answer printed;\n * the process then exits 0, whatever happened (the hook is invisible to the harness). */\nexport async function hookMain(argv: string[], env: NodeJS.ProcessEnv, io: HookIO): Promise<void> {\n const t0 = process.hrtime.bigint();\n /** The hook's own work, when a control wait follows it (the wait is not the hook's cost). */\n let workMs: number | null = null;\n const [harness = \"\", event = \"\", ...rest] = argv;\n\n function stateDir(): string {\n if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);\n const home = os.homedir();\n if (process.platform === \"win32\")\n return path.join(env.LOCALAPPDATA || path.join(home, \"AppData\", \"Local\"), \"sessionpipe\");\n return path.join(env.XDG_STATE_HOME || path.join(home, \".local\", \"state\"), \"sessionpipe\");\n }\n function configFile(): string {\n if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);\n const home = os.homedir();\n if (process.platform === \"win32\")\n return path.join(env.APPDATA || path.join(home, \"AppData\", \"Roaming\"), \"sessionpipe\", \"config.json\");\n return path.join(env.XDG_CONFIG_HOME || path.join(home, \".config\"), \"sessionpipe\", \"config.json\");\n }\n\n // Antigravity parses stdout as the hook's answer; `{}` is \"carry on\" for every event we\n // register. PreToolUse is not one of them: `{}` there is a deny, so an entry an older\n // install left behind asks the person instead (core's AG_STALE_PRETOOL, same bytes).\n if (harness === \"antigravity\") {\n try {\n io.stdout(\n event === \"PreToolUse\"\n ? '{\"decision\":\"ask\",\"reason\":\"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it.\"}\\n'\n : \"{}\\n\",\n );\n } catch {}\n }\n\n const state = stateDir();\n try {\n if (!harness || !event) throw new Error(\"usage: hook <harness> <event>\");\n let stdin = \"\";\n if (harness !== \"codex\" || event !== \"notify\") {\n try {\n const buf = io.stdin();\n stdin = buf.subarray(0, 1024 * 1024).toString(\"utf8\");\n } catch {}\n }\n // Claude Code's status line (install --status-line) runs on every refresh. It shows\n // the account's limits, and only a change becomes a job (limits.observed): at most\n // one a minute per config dir, and none when no sink takes tier 1.\n if (harness === \"claude-code\" && event === \"StatusLine\") {\n const limits = statusLimits(stdin);\n if (limits.line) {\n try {\n io.stdout(`${limits.line}\\n`);\n } catch {}\n }\n if (!limits.sig || !anySinkAbove(0) || !limitsDue(limits.sig)) return finish(false);\n }\n // Tool events are the bulk of what a harness fires. When no sink takes tier \u2265 1\n // they are only a \"still working\" signal: one heartbeat a minute per session.\n const TOOL = /^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/;\n if (TOOL.test(event) && !anySinkAbove(0)) {\n const id = sessionIdOf(stdin);\n if (!id || !beatDue(id)) return finish(false);\n }\n // A Stop asks the daemon BEFORE its event goes to the worker: the worker reports the\n // Stop to the daemon too, and a message queued for this Stop must be taken here,\n // not rerouted because the event got there first.\n const stopAnswer = event === \"Stop\" ? await (controlAsk(stdin) ?? Promise.resolve(null)) : null;\n mkdirSync(path.join(state, \"jobs\"), { recursive: true, mode: 0o700 });\n const job = {\n harness,\n event,\n argv: [harness, event, ...rest],\n stdin,\n cwd: process.cwd(),\n env: pick([\n // Where a harness keeps its config and its sessions, so a job swept by a worker\n // some other harness's hook spawned still resolves this harness's own homes\n // (run.ts sweepJobs runs any stale job file, whatever spawned the worker).\n \"APPDATA\",\n \"CLAUDE_CODE_HOST_SESSION_ID\",\n // session.attended: Claude Code's own word, and SESSIONPIPE_CONTROL_JOB below, the\n // control daemon's mark on a turn it runs for a person's message.\n \"CLAUDE_CODE_SESSION_ATTENDED\",\n \"CLAUDE_CONFIG_DIR\",\n \"CLAUDE_PROJECT_DIR\",\n \"CODEX_HOME\",\n // Devin's payload names no folder at all; this is the only thing that does.\n \"DEVIN_PROJECT_DIR\",\n \"GEMINI_CLI_HOME\",\n // The session the control daemon ran this one for (PROTOCOL.md \u00A73 delegated_by).\n \"SESSIONPIPE_ASKED_BY\",\n \"SESSIONPIPE_CONTROL_JOB\",\n // The launcher a script named for a session it starts (PROTOCOL.md \u00A73 launched_by).\n \"SESSIONPIPE_LAUNCHED_BY\",\n // Devin's config file and session store are both resolved through these.\n \"XDG_CONFIG_HOME\",\n \"XDG_DATA_HOME\",\n ]),\n ppid: process.ppid,\n at: Date.now(),\n };\n const jobFile = path.join(\n state,\n \"jobs\",\n `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`,\n );\n writeFileSync(jobFile, JSON.stringify(job), { mode: 0o600 });\n if (!env.SESSIONPIPE_NO_WORKER) {\n const worker = path.join(path.dirname(fileURLToPath(import.meta.url)), \"worker.js\");\n const p = spawn(process.execPath, [worker, jobFile], { detached: true, stdio: \"ignore\", windowsHide: true, env });\n p.unref();\n }\n if (stopAnswer) {\n try {\n io.stdout(`${stopAnswer}\\n`);\n } catch {}\n }\n // A PermissionRequest waits AFTER its event is on its way, so the receiver already\n // shows the Allow / Deny this answer is for.\n const ctl = event === \"PermissionRequest\" ? controlAsk(stdin) : null;\n if (ctl) {\n workMs = Number(process.hrtime.bigint() - t0) / 1e6;\n const out = await ctl;\n if (out) {\n try {\n io.stdout(`${out}\\n`);\n } catch {}\n }\n }\n return finish(true);\n } catch {\n return finish(false); // the hook must be invisible to the harness\n }\n\n /** Control (spec/CONTROL.md \u00A76, \u00A79): only on a machine with control paired, and only\n * for a message the daemon itself verified. A Stop hook asks for a queued message and\n * answers a block with it; a PermissionRequest hook waits for the person's signed\n * answer while the terminal shows its own prompt, first answer wins. Anything that\n * goes wrong (no daemon, a timeout) prints nothing: the harness carries on as usual. */\n function controlAsk(stdin: string): Promise<string | null> | null {\n if (harness !== \"claude-code\" || (event !== \"Stop\" && event !== \"PermissionRequest\")) return null;\n // A turn the daemon itself runs (a held Agent SDK session, a headless resume) never\n // asks at its Stop: the daemon hands that session its next message itself. Its\n // permission prompts do park: nobody is at a terminal there, so the owner's signed\n // answer is the only one it can get (a session started at edits, auto or inherit\n // asks before a Bash).\n if (env.SESSIONPIPE_CONTROL_JOB && event === \"Stop\") return null;\n const sock = socketPath(state, env);\n if (\n process.platform === \"win32\"\n ? !existsSync(path.join(path.dirname(configFile()), \"control.json\"))\n : !existsSync(sock)\n )\n return null;\n let s: Record<string, unknown>;\n try {\n s = JSON.parse(stdin) as Record<string, unknown>;\n } catch {\n return null;\n }\n const id = typeof s.session_id === \"string\" ? s.session_id : \"\";\n if (!id) return null;\n // Our entry in ~/.claude/settings.json is run by every harness that imports that file\n // \u2014 Devin's `claude` hooks provider is live, not a stub \u2014 and it arrives under the\n // claude-code name. Every Claude Code payload names a transcript and a UUID session;\n // a payload with neither is somebody else's, and must not be parked for up to 125 s\n // waiting on an answer that is not coming. Same test as core's notClaudeCode\n // (claude-code.ts), which the worker applies; repeated here because this file imports\n // nothing but node builtins.\n if (typeof s.transcript_path !== \"string\" && !UUID.test(id)) return null;\n const session = `claude-code:${id}`;\n // The session's own permission mode, in its own words. Every Claude Code hook\n // payload carries it, and it is the only place a mode the person chose at the\n // keyboard can be read from (CONTROL.md \u00A76, The session's mode): the daemon records\n // it, and the in-place lane and a relaunch's narrowing both rest on it. Taken as a\n // shape, not as a value \u2014 the daemon knows the modes it acts on; this file imports\n // nothing but node builtins.\n const mode = modeOf(s);\n if (event === \"Stop\")\n return ask(\n sock,\n { op: \"stop\", session, active: s.stop_hook_active === true, ...(mode ? { mode } : {}) },\n 300,\n ).then((r) => (r?.op === \"block\" ? JSON.stringify({ decision: \"block\", reason: r.reason }) : null));\n // The attention id the adapter puts on attention.needed (claude-code.ts), so the\n // receiver's Allow / Deny names this very prompt.\n const tool =\n typeof s.tool_name === \"string\" && /^[A-Za-z0-9_.:-]{1,120}$/.test(s.tool_name) ? s.tool_name : undefined;\n const turn = typeof s.prompt_id === \"string\" ? s.prompt_id : undefined;\n const attention = `perm-${createHash(\"sha256\")\n .update(`${turn ?? \"\"}|${tool ?? \"\"}|${JSON.stringify(s.tool_input ?? null)}`)\n .digest(\"hex\")\n .slice(0, 16)}`;\n return ask(sock, { op: \"permission\", session, attention, ...(tool ? { tool } : {}) }, 125_000).then((r) =>\n r?.op === \"decision\"\n ? JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PermissionRequest\",\n decision:\n r.behavior === \"allow\"\n ? { behavior: \"allow\" }\n : { behavior: \"deny\", message: r.message || \"Denied by the owner (sessionpipe control).\" },\n },\n })\n : null,\n );\n }\n\n /** The timing line; the process exits 0 right after. */\n function finish(spawned: boolean): void {\n const total = Number(process.hrtime.bigint() - t0) / 1e6;\n const ms = workMs ?? total;\n try {\n mkdirSync(state, { recursive: true });\n appendFileSync(\n path.join(state, \"timing.jsonl\"),\n `${JSON.stringify({ at: Date.now(), harness, event, ms: Math.round(ms * 10) / 10, spawned, ...(workMs !== null ? { control_wait_ms: Math.round(total - workMs) } : {}) })}\\n`,\n );\n } catch {}\n }\n function pick(names: string[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const n of names) if (env[n] !== undefined) out[n] = env[n] as string;\n return out;\n }\n function anySinkAbove(tier: number): boolean {\n try {\n const c = JSON.parse(readFileSync(configFile(), \"utf8\")) as { sinks?: { tier?: number; paused?: string }[] };\n return (c.sinks ?? []).some((s) => Number(s.tier) > tier && !s.paused);\n } catch {\n return false;\n }\n }\n function sessionIdOf(stdin: string): string {\n const m = /\"(?:session_id|sessionId|conversationId)\"\\s*:\\s*\"([^\"]{1,128})\"/.exec(stdin);\n return m?.[1] ?? \"\";\n }\n /** The status line's `rate_limits` as one short line (`5h 23% \u00B7 week 41%`), and as\n * the text a change is told by. Empty when the account has none. */\n function statusLimits(stdin: string): { line: string; sig: string } {\n let rl: unknown;\n try {\n rl = (JSON.parse(stdin) as { rate_limits?: unknown }).rate_limits;\n } catch {}\n if (!rl || typeof rl !== \"object\") return { line: \"\", sig: \"\" };\n const label: Record<string, string> = { five_hour: \"5h\", seven_day: \"week\", spend_limit: \"spend\" };\n const parts = Object.entries(rl as Record<string, { used_percentage?: unknown }>)\n .filter(([, w]) => typeof w?.used_percentage === \"number\")\n .map(([id, w]) => `${label[id] ?? id} ${Math.round(w.used_percentage as number)}%`);\n return parts.length ? { line: parts.join(\" \u00B7 \"), sig: JSON.stringify(rl) } : { line: \"\", sig: \"\" };\n }\n /** A change of limits, at most one a minute per config dir (one account each): the\n * stamp holds the last limits sent, its mtime when. */\n function limitsDue(sig: string): boolean {\n const dir = createHash(\"sha256\")\n .update(env.CLAUDE_CONFIG_DIR ?? \"\")\n .digest(\"hex\")\n .slice(0, 12);\n const stamp = path.join(state, \"beats\", `${harness}-limits-${dir}`);\n try {\n if (readFileSync(stamp, \"utf8\") === sig || Date.now() - statSync(stamp).mtimeMs < 60_000) return false;\n } catch {}\n try {\n mkdirSync(path.dirname(stamp), { recursive: true, mode: 0o700 });\n writeFileSync(stamp, sig, { mode: 0o600 });\n } catch {}\n return true;\n }\n /** One heartbeat per minute per session: the stamp file's mtime is the clock. */\n function beatDue(id: string): boolean {\n const stamp = path.join(state, \"beats\", `${harness}-${id.replace(/[^A-Za-z0-9_-]/g, \"_\")}`);\n try {\n if (Date.now() - statSync(stamp).mtimeMs < 60_000) return false;\n } catch {}\n try {\n mkdirSync(path.dirname(stamp), { recursive: true, mode: 0o700 });\n writeFileSync(stamp, \"\", { mode: 0o600 });\n } catch {}\n return true;\n }\n}\n\n// Run as `node hook.js <harness> <event>`; importing this module runs nothing. The main\n// module is known by its real path: node resolves symlinks for import.meta.url, argv[1]\n// keeps the spelling it was given (/var \u2192 /private/var on macOS).\nif (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href)\n hookMain(process.argv.slice(2), process.env, {\n // fd 0 and 1 are the test runner's own (a read of 0 would block it); launcher.test.ts\n // and the bench run these two through the real binary.\n /* v8 ignore next -- see above */\n stdin: () => readFileSync(0),\n /* v8 ignore next -- see above */\n stdout: (text) => writeFileSync(1, text),\n }).then(() => process.exit(0));\n", "// SPDX-License-Identifier: Apache-2.0\n// The daemon's local socket (spec/CONTROL.md \u00A78, \u00A713): how the waiter, the Stop and\n// PermissionRequest hooks, the worker and `sessionpipe ask` talk to the one daemon on\n// this machine. Line-delimited JSON, one request per connection; every request but an\n// ask gets one reply, an ask gets a few in order on the same connection. A Unix socket\n// in the state directory (0600, in a 0700 dir); a per-user named pipe on Windows. Only\n// node: builtins \u2014 the hook bundle imports this.\nimport net from \"node:net\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport type LocalRequest =\n /** A waiter parks until a message for its session arrives. `channel`: the waiter is\n * `sessionpipe channel`, which pushes the message into the session rather than handing\n * it to a turn that asked for it (CONTROL.md \u00A78). */\n | { op: \"wait\"; session: string; pid?: number; channel?: boolean }\n /** A Stop hook asks for a queued message (turn mode). `mode` is the session's own\n * permission mode, as its payload reports it (CONTROL.md \u00A76, The session's mode). */\n | { op: \"stop\"; session: string; active: boolean; mode?: string }\n /** A PermissionRequest hook parks until a verified answer, or until it's released. */\n | { op: \"permission\"; session: string; attention: string; tool?: string }\n /** The worker reports a hook event: turn state, attention closure, and the session's\n * own permission mode where its payload named one. */\n | { op: \"event\"; session: string; event: string; transcript?: string; cwd?: string; mode?: string }\n /** `sessionpipe control status`. */\n | { op: \"status\" }\n /** `sessionpipe update` installed a new copy: restart onto it once nothing is in hand. */\n | { op: \"restart\" }\n /** `sessionpipe pool \u2026` while the daemon runs: the daemon holds the pool's engine\n * (pins, credentials, the listener), so a change goes through it. `status` reads;\n * the rest are CONTROL.md \u00A76's actions, plus `import` (reads this machine's own\n * Claude Code logins, which no receiver may ask for). */\n | { op: \"pool\"; action: PoolOp; account?: string; order?: string[]; code?: string }\n /** `sessionpipe ask` (CONTROL.md \u00A713): a session here hands a task to another agent on\n * this machine and gets the answer back on this connection. */\n | AskRequest;\n\nexport type PoolOp =\n | \"status\"\n | \"on\"\n | \"off\"\n | \"enable\"\n | \"disable\"\n | \"remove\"\n | \"order\"\n | \"refresh\"\n | \"login.begin\"\n | \"login.complete\"\n | \"import\";\n\nexport interface AskRequest {\n op: \"ask\";\n /** `<harness>:<id>` of the asking session, when known: the events go on it. */\n from?: string;\n /** The agent to ask. */\n harness: string;\n /** An existing `<harness>:<id>` to continue instead of a new session. */\n session?: string;\n /** 1\u201320 000 characters, not all whitespace. */\n text: string;\n /** Absolute; the caller's folder by default. */\n cwd: string;\n /** Every ancestor ref, outermost first (from env SESSIONPIPE_ASK_CHAIN). */\n chain: string[];\n /** Narrower than the machine's mode, never wider. */\n permission?: \"read\" | \"safe\";\n model?: string;\n pid?: number;\n}\n\nexport type LocalReply =\n | { op: \"message\"; text: string; id: string }\n | { op: \"block\"; reason: string; id: string }\n | { op: \"decision\"; behavior: \"allow\" | \"deny\"; message?: string; id: string }\n | { op: \"none\" }\n /** A newer waiter for the same session took over; this one stops. */\n | { op: \"replaced\" }\n | { op: \"ok\" }\n | { op: \"status\"; status: unknown }\n /** A pool op's answer: the ack it would earn (CONTROL.md \u00A76) and the pool as it now\n * stands (`status`: the fa\u00E7ade's PoolStatus; `detail`: the summary line, or a\n * login.begin's \"<pending id> <URL>\"). */\n | {\n op: \"pool\";\n outcome: \"delivered\" | \"refused\" | \"failed\" | \"unsupported\";\n code?: string;\n detail: string;\n status?: unknown;\n /** `import`: what was added and what was left out (control/pool.ts Imported). */\n imported?: unknown;\n }\n // An ask's replies, in order (askMany): `asked`, any `progress`, then exactly one of\n // `answer`, `refused` and `failed`, after which the daemon closes the connection.\n /** The run began; `session` once it is known. */\n | { op: \"asked\"; ask_id: string; session?: string }\n /** The answer so far, whole, with a rising seq. */\n | { op: \"progress\"; text: string; seq: number }\n | { op: \"answer\"; ask_id: string; session: string; text: string; ms: number }\n /** ask_off \u00B7 harness \u00B7 folder \u00B7 depth \u00B7 loop \u00B7 start_limit \u00B7 bad_fields */\n | { op: \"refused\"; code: string; detail: string }\n | { op: \"failed\"; detail: string };\n\n/** The replies that end an ask. */\nexport type AskEnd = Extract<LocalReply, { op: \"answer\" | \"refused\" | \"failed\" }>;\n\n/** Where the daemon listens. `SESSIONPIPE_SOCKET` overrides (tests). */\nexport function socketPath(state: string, env: NodeJS.ProcessEnv = process.env): string {\n if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;\n if (process.platform === \"win32\") return `\\\\\\\\.\\\\pipe\\\\sessionpipe-control-${os.userInfo().username}`;\n return path.join(state, \"control.sock\");\n}\n\n/** One request, one reply. `timeoutMs` bounds the whole exchange; null on any\n * failure (no daemon, a timeout, a garbled reply) \u2014 callers treat that as \"none\". */\nexport function ask(sock: string, req: LocalRequest, timeoutMs: number): Promise<LocalReply | null> {\n return new Promise((resolve) => {\n let buf = \"\";\n let done = false;\n const s = net.connect(sock);\n const finish = (r: LocalReply | null) => {\n if (done) return;\n done = true;\n clearTimeout(t);\n s.destroy();\n resolve(r);\n };\n const t = setTimeout(() => finish(null), timeoutMs);\n s.on(\"connect\", () => s.write(`${JSON.stringify(req)}\\n`));\n s.on(\"data\", (d) => {\n buf += d.toString(\"utf8\");\n const i = buf.indexOf(\"\\n\");\n if (i < 0) return;\n try {\n finish(JSON.parse(buf.slice(0, i)) as LocalReply);\n } catch {\n finish(null);\n }\n });\n s.on(\"error\", () => finish(null));\n s.on(\"close\", () => finish(null));\n });\n}\n\n/** One request, several replies on the same connection (an ask): `onReply` sees each one\n * in order, the promise settles with the one that ends the exchange. `timeoutMs` bounds\n * the whole exchange; null when it ends without an end \u2014 no daemon, a timeout, a garbled\n * line, a connection that closed mid-run. */\nexport function askMany(\n sock: string,\n req: LocalRequest,\n onReply: (r: LocalReply) => void,\n timeoutMs: number,\n): Promise<AskEnd | null> {\n return new Promise((resolve) => {\n let buf = \"\";\n let done = false;\n const s = net.connect(sock);\n const finish = (r: AskEnd | null) => {\n if (done) return;\n done = true;\n clearTimeout(t);\n s.destroy();\n resolve(r);\n };\n const t = setTimeout(() => finish(null), timeoutMs);\n s.on(\"connect\", () => s.write(`${JSON.stringify(req)}\\n`));\n s.on(\"data\", (d) => {\n buf += d.toString(\"utf8\");\n for (let i = buf.indexOf(\"\\n\"); i >= 0 && !done; i = buf.indexOf(\"\\n\")) {\n const line = buf.slice(0, i);\n buf = buf.slice(i + 1);\n let r: LocalReply;\n try {\n r = JSON.parse(line) as LocalReply;\n } catch {\n finish(null);\n return;\n }\n onReply(r);\n if (r.op === \"answer\" || r.op === \"refused\" || r.op === \"failed\") finish(r);\n }\n });\n s.on(\"error\", () => finish(null));\n s.on(\"close\", () => finish(null));\n });\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["os", "path", "path", "os"]
|
|
7
7
|
}
|