@scotthuang/agent-knock-knock-pi 0.1.0-poc.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agent Knock Knock contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,396 @@
1
+ # Agent Knock Knock for Pi — POC
2
+
3
+ This Pi Extension makes Pi an Agent Knock Knock (AKK) orchestration Host. Pi
4
+ can use `/akk` and AKK's 16 semantic tools to control Codex and Claude Code
5
+ terminals that are already running in tmux or Herdr.
6
+
7
+ The connector does not modify Pi, launch or authenticate a coding agent, or
8
+ depend on an OpenClaw Gateway. It uses AKK's public HostAdapter and returns
9
+ callbacks to the exact initiating Pi session through a private local Unix
10
+ socket.
11
+
12
+ ## Status and compatibility
13
+
14
+ This is a POC prerelease distributed on npm as
15
+ `@scotthuang/agent-knock-knock-pi`. Prereleases use the `next` dist-tag:
16
+
17
+ ```sh
18
+ pi install npm:@scotthuang/agent-knock-knock-pi@next
19
+ ```
20
+
21
+ The current compatibility contract is intentionally exact:
22
+
23
+ - Pi `0.84.4`, installed from the official
24
+ `@earendil-works/pi-coding-agent` package.
25
+ - Node.js `22.19.0` or newer.
26
+ - AKK `0.12.23`, pinned by this connector's package and lockfile. Running
27
+ `npm ci` in `connectors/pi` installs that exact AKK runtime; a different
28
+ version at the repository root is not substituted automatically.
29
+ - macOS or Linux. The POC requires a POSIX Unix socket and does not support
30
+ Windows named pipes.
31
+ - At least one authenticated Codex or Claude Code process already running in a
32
+ supported tmux or Herdr terminal under the same OS user.
33
+ - A configured Pi model/provider. Pi model authentication is separate from
34
+ AKK and is not performed by this connector.
35
+
36
+ The connector rejects a Pi runtime other than `0.84.4` before it creates a
37
+ socket or starts AKK lifecycle work. Pi's own packages remain `*` peer
38
+ dependencies as required by Pi's package contract; the runtime check is the
39
+ compatibility authority.
40
+
41
+ ## Five-minute quick start
42
+
43
+ ### 1. Install the accepted Pi version
44
+
45
+ ```sh
46
+ npm install -g @earendil-works/pi-coding-agent@0.84.4
47
+ node --version
48
+ pi --version
49
+ ```
50
+
51
+ Configure a model using Pi's normal `/login`, provider environment variable,
52
+ or settings flow before continuing.
53
+
54
+ ### 2. Start a coding agent in a shared terminal
55
+
56
+ AKK discovers an existing process; it never starts one for you. For example:
57
+
58
+ ```sh
59
+ tmux new-session -s akk-pi-work -c "$(pwd -P)" codex
60
+ ```
61
+
62
+ Wait for Codex to reach its interactive prompt, then detach with `Ctrl-b`
63
+ followed by `d`. Use `claude` instead of `codex` to test Claude Code. An
64
+ existing supported Herdr terminal works too.
65
+
66
+ ### 3. Install the connector
67
+
68
+ ```sh
69
+ pi install npm:@scotthuang/agent-knock-knock-pi@next
70
+ pi list
71
+ ```
72
+
73
+ Using `next` follows compatible Pi connector prereleases. To pin this exact
74
+ build instead, install
75
+ `npm:@scotthuang/agent-knock-knock-pi@0.1.0-poc.1`; exact versions are pinned
76
+ and are not advanced by Pi's bulk update command.
77
+
78
+ ### 4. Start Pi
79
+
80
+ ```sh
81
+ pi
82
+ ```
83
+
84
+ Pi should show `AKK ready`. The package manifest registers the Extension, so
85
+ no `-e` path or repository checkout is required.
86
+
87
+ ### 5. Complete the first round trip
88
+
89
+ In Pi, list the live targets:
90
+
91
+ ```text
92
+ /akk list
93
+ ```
94
+
95
+ If exactly one terminal is send-ready, submit a small task:
96
+
97
+ ```text
98
+ /akk reply with exactly AKK_PI_OK after inspecting the current workspace
99
+ ```
100
+
101
+ If multiple targets are present, use the fresh selector printed by
102
+ `/akk list`:
103
+
104
+ ```text
105
+ /akk @a1b2c3d4: reply with exactly AKK_PI_OK after inspecting the current workspace
106
+ ```
107
+
108
+ A managed Send returns a `turn_id`; when the coding agent settles, its callback
109
+ is injected into the same Pi session. Inspect it at any time with the exact
110
+ turn selector printed by AKK:
111
+
112
+ ```text
113
+ /akk status <turn-selector>
114
+ ```
115
+
116
+ To observe work that you started manually in the terminal, copy the complete
117
+ `terminal_id` from a fresh list and start a read-only Watch:
118
+
119
+ ```text
120
+ /akk watch <exact-terminal-id>
121
+ /akk status <watch-id>
122
+ /akk unwatch <watch-id>
123
+ ```
124
+
125
+ Watch does not send, adopt, approve, interrupt, or reserve the task. Do not add
126
+ a second Watch merely to make a normal managed Send callback: managed Send
127
+ already has a callback. When a user-priority Send must fall back to unmanaged
128
+ physical delivery, AKK instead tries to attach the request-bound Watch itself
129
+ and returns its `watch_id`.
130
+
131
+ ## Tool workflow
132
+
133
+ The usual product loop is:
134
+
135
+ 1. **List** to discover live terminals and their current `available_actions`.
136
+ 2. **Send** using the fresh action's prefilled `session_id` or `terminal_id`.
137
+ Do not invent semantic IDs or retry an uncertain terminal mutation.
138
+ 3. Wait for the managed **Callback**. An unmanaged user-priority fallback may
139
+ return a Terminal Watch callback instead of a managed Turn callback.
140
+ 4. Use **Status** with the exact `turn_id` or `watch_id` when the callback is
141
+ delayed or the Pi route changed.
142
+ 5. Use **Respond** for a question in that same Turn. Use **Approve** only after
143
+ reviewing the live approval prompt and making an explicit decision.
144
+ 6. Use **Cancel**, **Close**, **Renew**, or **Retry Callback** only when the
145
+ current list/status output advertises that exact action.
146
+
147
+ The `/akk` command is the direct human surface. The following 16 tools are also
148
+ registered for model-driven orchestration:
149
+
150
+ | Tool | Purpose |
151
+ | --- | --- |
152
+ | `agent_knock_knock_list` | List verified Codex/Claude terminals, managed work, Watches, and currently available semantic actions. |
153
+ | `agent_knock_knock_watch` | Start a durable, read-only Watch for one exact `terminal_id`. |
154
+ | `agent_knock_knock_unwatch` | Stop observation by exact `watch_id`; it does not interrupt the coding agent. |
155
+ | `agent_knock_knock_list_resumable_threads` | List structurally verified native threads that can be resumed in one terminal. |
156
+ | `agent_knock_knock_native_inspect` | Run the closed native `status` inspection for one exact terminal. Arbitrary slash commands are not accepted. |
157
+ | `agent_knock_knock_new_thread` | Start and verify a clean native coding-agent thread after explicit user intent. |
158
+ | `agent_knock_knock_reconcile_binding` | Detach one exact conflicting AKK Session binding after explicit confirmation, without adopting another thread. |
159
+ | `agent_knock_knock_resume_thread` | Resume one exact complete `native_thread_id` returned by the current discovery result. |
160
+ | `agent_knock_knock_status` | Inspect one exact managed Turn or Terminal Watch. |
161
+ | `agent_knock_knock_send` | Send a new task through the exact currently advertised Session or terminal action. |
162
+ | `agent_knock_knock_respond` | Answer a question or blocked callback in one existing Turn; it does not start a new Turn. |
163
+ | `agent_knock_knock_approve` | Approve one current permission request after Pi presents a native user-decision gate. |
164
+ | `agent_knock_knock_renew` | Renew monitoring for one stalled but still-live Turn without terminal input. |
165
+ | `agent_knock_knock_retry_callback` | Retry one persisted managed callback with its original message identity. |
166
+ | `agent_knock_knock_cancel` | Interrupt one exact task after Pi obtains native confirmation; the shared pane stays open. |
167
+ | `agent_knock_knock_close` | Release AKK management for one Turn without sending terminal input or stopping the coding agent. |
168
+
169
+ Except for the deliberately user-intent-first, read-only Watch path, treat the
170
+ fresh `available_actions` output as authority. The connector preserves AKK's
171
+ semantic-ID, idempotency, terminal identity, Store, Session, Turn, and
172
+ completion rules rather than recreating them in Pi.
173
+
174
+ ## Active approval, response, and cancellation
175
+
176
+ When a managed task needs permission, its callback is queued into the
177
+ originating Pi session. A model-facing `agent_knock_knock_approve` call does not
178
+ immediately type into the terminal. The connector first refreshes AKK Status,
179
+ shows the current prompt in Pi's native UI, and requires a selection:
180
+
181
+ - **Approve once** sends the current prompt-scoped AKK approval.
182
+ - **Keep pending** sends no terminal input.
183
+ - For a managed `turn_id`, **Reject and cancel task** asks for a second
184
+ confirmation, then invokes AKK Cancel.
185
+
186
+ A terminal-scoped approval has no terminal-scoped Cancel authority, so its
187
+ dialog offers only Approve or Keep pending. A separately advertised Cancel
188
+ action gets its own Pi confirmation dialog.
189
+
190
+ `agent_knock_knock_respond` answers an ordinary question in the exact
191
+ in-flight Turn; it is not an approval denial. AKK currently has no generic
192
+ "select the target's No option and continue this Turn" tool. Reject-and-cancel
193
+ interrupts the task instead.
194
+
195
+ The native confirmation flow requires an interactive Pi UI. In a context where
196
+ `ctx.hasUI` is false, the connector refuses model-facing Approve and Cancel
197
+ instead of mutating the terminal without a person. Headless/non-interactive Pi
198
+ is not a product-acceptance target for this POC.
199
+
200
+ AKK keeps the coding agent's existing permission mode. This connector does not
201
+ enable `--yolo`, change a target's permission settings, or expose AKK's
202
+ OpenClaw-specific `autoApprove` configuration.
203
+
204
+ ## Callback delivery and recovery
205
+
206
+ The callback path is:
207
+
208
+ ```text
209
+ AKK callback outbox
210
+ -> command_json_v1 callback helper
211
+ -> authenticated private Unix socket
212
+ -> fsync-backed Pi callback inbox
213
+ -> originating Pi session
214
+ ```
215
+
216
+ When Pi is idle, the callback is injected with `triggerTurn: true` and starts a
217
+ new Pi turn. When Pi is already producing a response, the connector uses Pi's
218
+ follow-up queue so the callback is not mixed into the current model turn.
219
+
220
+ An `accepted` callback acknowledgement means that the connector durably
221
+ admitted the exact delivery to its local inbox and returned an acceptance ID.
222
+ It does **not** mean the Pi model consumed the message: Pi `0.84.4` exposes a
223
+ synchronous `sendMessage()` call without a model-consumption receipt. The last
224
+ hop is therefore at-least-once or uncertain if Pi crashes between
225
+ `sendMessage()` and recording the inbox entry as delivered. Delivery and
226
+ idempotency IDs let the connector reject collisions and suppress known exact
227
+ duplicates.
228
+
229
+ For a missing callback:
230
+
231
+ 1. If Pi is busy, wait for the current response to end; the callback may be a
232
+ queued follow-up.
233
+ 2. Refresh `/akk list`, then run `/akk status <turn-selector-or-watch-id>`.
234
+ Status is the authoritative manual recovery path and does not need the
235
+ callback to have reached the model.
236
+ 3. If a still-live task has no managed callback route, start a new read-only
237
+ Watch on its exact current `terminal_id` and retain the returned `watch_id`.
238
+ 4. Use `agent_knock_knock_retry_callback` only when the current managed Turn
239
+ advertises it and the original Pi callback route is still live. Never retry
240
+ an uncertain terminal Send to recover a callback.
241
+
242
+ The POC owns one live Pi runtime and one branch epoch. Explicit session-tree
243
+ navigation rotates the controller route; Pi shutdown removes the socket and
244
+ invalidates that runtime's route. The durable inbox does not migrate callbacks
245
+ from an old random controller identity into a new Pi runtime or branch. After a
246
+ restart or branch change, use fresh List/Status and, for still-running terminal
247
+ work, a fresh Watch. Do not expect the old route's callback to jump to the new
248
+ conversation automatically.
249
+
250
+ ## Configuration, security, and storage
251
+
252
+ The connector needs no project-specific configuration. These optional
253
+ environment variables must be set **before** Pi starts and must contain
254
+ absolute paths:
255
+
256
+ | Variable | Default | Purpose |
257
+ | --- | --- | --- |
258
+ | `AKK_PI_STORE_DIR` | `~/.agent-knock-knock/store` | Override AKK's shared Session, Turn, Watch, receipt, and callback-outbox Store. |
259
+ | `AKK_PI_STATE_DIR` | `~/.pi/agent/akk` | Override the Pi connector's durable callback-inbox directory. |
260
+
261
+ Example with isolated development state:
262
+
263
+ ```sh
264
+ AKK_PI_STORE_DIR=/absolute/private/path/akk-store \
265
+ AKK_PI_STATE_DIR=/absolute/private/path/pi-akk-state \
266
+ pi
267
+ ```
268
+
269
+ Security and retention boundaries:
270
+
271
+ - Pi Extensions execute with the same OS privileges as Pi. Review local
272
+ connector source before loading it.
273
+ - Each Pi runtime creates a random directory under the OS temporary directory
274
+ with mode `0700`. Its Host Profile and authenticated Unix socket use mode
275
+ `0600`, and the directory is removed on clean Pi shutdown.
276
+ - The socket token is random and runtime-private. It is not written to the
277
+ durable inbox or accepted as a model/tool argument. The callback helper is
278
+ allowed only the socket and token environment variables it needs.
279
+ - The persistent state directory is forced to `0700`; its
280
+ `callback-inbox.json` is written atomically with mode `0600`. It can retain
281
+ callback bodies, which may contain task results. The inbox is bounded to 256
282
+ entries and 64 MiB, pruning older delivered entries as new callbacks arrive.
283
+ - The AKK Store is separate from the Pi inbox and may contain terminal/session
284
+ metadata and callback results. Use a dedicated private directory for either
285
+ override; do not point it at a shared or broad filesystem root.
286
+ - The connector strips the supported GLM credential environment variables
287
+ from AKK relay/callback subprocess environments. Model credentials remain
288
+ Pi-owned and are never connector configuration.
289
+
290
+ ## Troubleshooting
291
+
292
+ | Symptom | Check and recovery |
293
+ | --- | --- |
294
+ | `requires Pi 0.84.4` | Run `pi --version`, then reinstall the exact accepted release with `npm install -g @earendil-works/pi-coding-agent@0.84.4`. Newer Pi releases are not silently accepted by this POC. |
295
+ | Pi never shows `AKK ready` | Confirm Node is at least 22.19, run `pi list`, update or reinstall `npm:@scotthuang/agent-knock-knock-pi@next`, and fully restart Pi. For a source checkout, follow the local-development build commands below. |
296
+ | Module or `lib/index.js` not found | Remove and reinstall the npm package. For a source checkout, confirm Pi was given `connectors/pi`, not the repository root, and rebuild with the documented cwd-specific command. |
297
+ | `/akk list` finds no terminal | Start authenticated `codex` or `claude` inside supported tmux/Herdr under the same OS user and leave the process running. The connector never launches it. |
298
+ | Send says the target is ambiguous | Run a fresh `/akk list` and use the exact displayed selector for `/akk`, or the prefilled semantic ID from that row's current tool action. |
299
+ | Callback does not appear immediately | Pi may be busy, so the callback is queued as a follow-up. Wait for the current turn, then use fresh List and Status. After a Pi restart or branch change, the old callback route is intentionally not migrated. |
300
+ | Approval or cancellation says an interactive UI is required | Use Pi's interactive TUI. The POC deliberately blocks model-facing Approve/Cancel when `ctx.hasUI` is false. |
301
+ | Approval changed before confirmation | Refresh Status and review the newly observed prompt. Do not reuse an old approval offer or blindly retry an interrupted approval. |
302
+ | Callback socket/profile is unavailable | The owning Pi runtime may have stopped or its temporary private directory may have disappeared. Restart Pi to create a fresh route; recover existing work with List/Status rather than copying socket paths or tokens. |
303
+ | State-directory or Store permission error | Both override variables must be absolute and point to dedicated real directories. Stop Pi, inspect ownership and contents, then choose a new private directory if the existing path is unsafe or incompatible. |
304
+ | Updated connector still behaves like the old POC | Run `pi update npm:@scotthuang/agent-knock-knock-pi` and fully restart Pi. For a source checkout, re-run `npm ci` and the connector-specific build first. A running Extension is not hot-replaced. |
305
+ | Send result is uncertain | Do not automatically send the task again. Inspect Status, the exact pane, or the attached Watch; retrying terminal input could duplicate work. |
306
+
307
+ ## Upgrade and uninstall
308
+
309
+ Inspect and update the npm installation with Pi's package manager:
310
+
311
+ ```sh
312
+ pi list
313
+ pi update npm:@scotthuang/agent-knock-knock-pi
314
+ ```
315
+
316
+ Keep Pi at exactly `0.84.4` until this README declares another accepted
317
+ version. Do not use an unverified Pi upgrade as a connector update.
318
+
319
+ Remove the npm package by its package identity:
320
+
321
+ ```sh
322
+ pi remove npm:@scotthuang/agent-knock-knock-pi
323
+ ```
324
+
325
+ `pi uninstall` is an alias for `pi remove`. Removing the package does not stop
326
+ or delete Codex/Claude terminals, model credentials, `~/.pi/agent/akk`, or the
327
+ AKK Store. Those durable directories are retained for inspection/recovery and
328
+ should be reviewed separately before any manual deletion.
329
+
330
+ ## Development and verification
331
+
332
+ To load a source checkout for one Pi run, build the connector explicitly:
333
+
334
+ ```sh
335
+ AKK_REPO=/absolute/path/to/agent-knock-knock
336
+ cd "$AKK_REPO"
337
+ npm --prefix connectors/pi ci
338
+ npm run pi:build
339
+ pi -e "$AKK_REPO/connectors/pi"
340
+ ```
341
+
342
+ The equivalent child-package build is:
343
+
344
+ ```sh
345
+ cd "$AKK_REPO/connectors/pi"
346
+ npm ci
347
+ npm run build
348
+ ```
349
+
350
+ Do not run a bare root `npm run build` and assume it built this connector. The
351
+ unambiguous root command is `npm run pi:build`; inside `connectors/pi`, use
352
+ `npm run build`. `pi -e` is temporary and changes no package settings. To
353
+ persist a local source path, use `pi install "$AKK_REPO/connectors/pi"`; remove
354
+ that same absolute source with `pi remove` when finished.
355
+
356
+ From the repository root:
357
+
358
+ ```sh
359
+ npm run pi:typecheck
360
+ npm run pi:test:fast
361
+ npm run pi:pack:check
362
+ ```
363
+
364
+ Or from `connectors/pi`:
365
+
366
+ ```sh
367
+ npm run typecheck
368
+ npm run test:fast
369
+ npm run pack:check
370
+ ```
371
+
372
+ The [PR #266](https://github.com/scotthuang/agent-knock-knock/pull/266)
373
+ acceptance record on 2026-08-30 included:
374
+
375
+ - root fast tests: 1,462/1,462 passed;
376
+ - connector fast tests: 23/23 passed;
377
+ - root and connector TypeScript typechecks, architecture/evidence validators,
378
+ and connector package dry-run passed;
379
+ - a real Claude Code 2.1.251 flow covering Send, approval callback, Pi native
380
+ approval, exact marker execution, completion callback, and delivered Status;
381
+ - a real Codex flow in a clean isolated environment covering native
382
+ `agent_accepted`, exact marker execution, Unix Socket callback into Pi, and
383
+ durable high-confidence delivered Status.
384
+
385
+ These are POC acceptance results, not a promise of compatibility with every
386
+ future Pi, Codex, Claude Code, tmux, or Herdr release. Version
387
+ `0.1.0-poc.1` is the first npm prerelease and is distributed through the
388
+ `next` dist-tag; it does not change the AKK main package or ClawHub artifact.
389
+
390
+ ## Deliberate POC limits
391
+
392
+ The POC intentionally does not provide a standalone Supervisor, multi-Host
393
+ leader election, cross-session or cross-branch callback migration, remote
394
+ terminal access, Windows named pipes, configuration-driven auto-approval, or
395
+ coding-agent `--yolo` adaptation. It controls only the Codex and Claude Code
396
+ terminal surfaces already supported by AKK.
@@ -0,0 +1,14 @@
1
+ import type { CallbackAcknowledgement } from "./routes.js";
2
+ export interface CallbackHelperArguments {
3
+ readonly controllerId: string;
4
+ readonly deliveryId: string;
5
+ readonly messageId: string;
6
+ readonly idempotencyKey: string;
7
+ }
8
+ /** Execute the Host Profile callback helper without putting authority in argv. */
9
+ export declare function runCallbackHelper(options: {
10
+ readonly arguments: CallbackHelperArguments;
11
+ readonly body: string;
12
+ readonly environment: Readonly<NodeJS.ProcessEnv>;
13
+ }): Promise<CallbackAcknowledgement>;
14
+ export declare function parseCallbackHelperArguments(argv: readonly string[]): CallbackHelperArguments;
@@ -0,0 +1,133 @@
1
+ import fs from "node:fs";
2
+ import process from "node:process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { CALLBACK_MAX_BODY_BYTES, CALLBACK_SOCKET_ENV, CALLBACK_TOKEN_ENV, sendCallbackOverIpc, } from "./ipc.js";
5
+ /** Execute the Host Profile callback helper without putting authority in argv. */
6
+ export async function runCallbackHelper(options) {
7
+ if (Buffer.byteLength(options.body, "utf8") > CALLBACK_MAX_BODY_BYTES) {
8
+ throw new Error("Pi callback body exceeded its limit");
9
+ }
10
+ const socketPath = requiredEnvironment(options.environment[CALLBACK_SOCKET_ENV], CALLBACK_SOCKET_ENV);
11
+ const token = requiredEnvironment(options.environment[CALLBACK_TOKEN_ENV], CALLBACK_TOKEN_ENV);
12
+ const response = await sendCallbackOverIpc({
13
+ socketPath,
14
+ request: {
15
+ ...options.arguments,
16
+ body: options.body,
17
+ token,
18
+ },
19
+ });
20
+ if (response.deliveryId !== options.arguments.deliveryId ||
21
+ response.messageId !== options.arguments.messageId) {
22
+ throw new Error("Pi callback IPC acknowledgement identity did not match");
23
+ }
24
+ return {
25
+ request: {
26
+ delivery_id: response.deliveryId,
27
+ message_id: response.messageId,
28
+ },
29
+ result: {
30
+ status: response.status,
31
+ ...(response.acceptanceId ? { acceptance_id: response.acceptanceId } : {}),
32
+ ...(response.errorCode ? { error_code: response.errorCode } : {}),
33
+ },
34
+ };
35
+ }
36
+ export function parseCallbackHelperArguments(argv) {
37
+ const values = new Map();
38
+ const supported = new Set([
39
+ "--controller-id",
40
+ "--delivery-id",
41
+ "--message-id",
42
+ "--idempotency-key",
43
+ ]);
44
+ if (argv.length % 2 !== 0) {
45
+ throw new Error("invalid Pi AKK callback helper arguments");
46
+ }
47
+ for (let index = 0; index < argv.length; index += 2) {
48
+ const flag = argv[index];
49
+ const value = argv[index + 1];
50
+ if (!flag || !supported.has(flag) || !value || values.has(flag)) {
51
+ throw new Error("invalid Pi AKK callback helper arguments");
52
+ }
53
+ values.set(flag, value);
54
+ }
55
+ return {
56
+ controllerId: requiredArgument(values, "--controller-id"),
57
+ deliveryId: requiredArgument(values, "--delivery-id"),
58
+ messageId: requiredArgument(values, "--message-id"),
59
+ idempotencyKey: requiredArgument(values, "--idempotency-key"),
60
+ };
61
+ }
62
+ async function main() {
63
+ let parsed;
64
+ try {
65
+ parsed = parseCallbackHelperArguments(process.argv.slice(2));
66
+ const body = await readStandardInput();
67
+ const acknowledgement = await runCallbackHelper({
68
+ arguments: parsed,
69
+ body,
70
+ environment: process.env,
71
+ });
72
+ process.stdout.write(`${JSON.stringify(acknowledgement)}\n`);
73
+ }
74
+ catch {
75
+ if (parsed) {
76
+ const fallback = {
77
+ request: {
78
+ delivery_id: parsed.deliveryId,
79
+ message_id: parsed.messageId,
80
+ },
81
+ result: {
82
+ status: "retry",
83
+ error_code: "callback_ipc_unavailable",
84
+ },
85
+ };
86
+ process.stdout.write(`${JSON.stringify(fallback)}\n`);
87
+ return;
88
+ }
89
+ process.stderr.write("agent-knock-knock Pi callback helper configuration is invalid\n");
90
+ process.exitCode = 2;
91
+ }
92
+ }
93
+ async function readStandardInput() {
94
+ const chunks = [];
95
+ let bytes = 0;
96
+ for await (const chunk of process.stdin) {
97
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
98
+ bytes += buffer.length;
99
+ if (bytes > CALLBACK_MAX_BODY_BYTES) {
100
+ throw new Error("Pi callback body exceeded its limit");
101
+ }
102
+ chunks.push(buffer);
103
+ }
104
+ return Buffer.concat(chunks, bytes).toString("utf8");
105
+ }
106
+ function requiredArgument(values, flag) {
107
+ const value = values.get(flag);
108
+ if (!value || value.trim() !== value || /[\u0000-\u001f\u007f]/u.test(value)) {
109
+ throw new Error(`missing ${flag}`);
110
+ }
111
+ return value;
112
+ }
113
+ function requiredEnvironment(value, name) {
114
+ if (!value || value.trim() !== value || /[\u0000\r\n]/u.test(value)) {
115
+ throw new Error(`missing ${name}`);
116
+ }
117
+ return value;
118
+ }
119
+ if (isDirectExecution()) {
120
+ void main();
121
+ }
122
+ function isDirectExecution() {
123
+ if (!process.argv[1])
124
+ return false;
125
+ try {
126
+ return fs.realpathSync(fileURLToPath(import.meta.url)) ===
127
+ fs.realpathSync(process.argv[1]);
128
+ }
129
+ catch {
130
+ return false;
131
+ }
132
+ }
133
+ //# sourceMappingURL=callback-helper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-helper.js","sourceRoot":"","sources":["../src/callback-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAUlB,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAIvC;IACC,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,uBAAuB,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,UAAU,GAAG,mBAAmB,CACpC,OAAO,CAAC,WAAW,CAAC,mBAAmB,CAAC,EACxC,mBAAmB,CACpB,CAAC;IACF,MAAM,KAAK,GAAG,mBAAmB,CAC/B,OAAO,CAAC,WAAW,CAAC,kBAAkB,CAAC,EACvC,kBAAkB,CACnB,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;QACzC,UAAU;QACV,OAAO,EAAE;YACP,GAAG,OAAO,CAAC,SAAS;YACpB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,KAAK;SACN;KACF,CAAC,CAAC;IACH,IACE,QAAQ,CAAC,UAAU,KAAK,OAAO,CAAC,SAAS,CAAC,UAAU;QACpD,QAAQ,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS,CAAC,SAAS,EAClD,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO;QACL,OAAO,EAAE;YACP,WAAW,EAAE,QAAQ,CAAC,UAAU;YAChC,UAAU,EAAE,QAAQ,CAAC,SAAS;SAC/B;QACD,MAAM,EAAE;YACN,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1E,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,IAAuB;IAClE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;QACxB,iBAAiB;QACjB,eAAe;QACf,cAAc;QACd,mBAAmB;KACpB,CAAC,CAAC;IACH,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO;QACL,YAAY,EAAE,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,CAAC;QACzD,UAAU,EAAE,gBAAgB,CAAC,MAAM,EAAE,eAAe,CAAC;QACrD,SAAS,EAAE,gBAAgB,CAAC,MAAM,EAAE,cAAc,CAAC;QACnD,cAAc,EAAE,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,CAAC;KAC9D,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,MAA2C,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,4BAA4B,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7D,MAAM,IAAI,GAAG,MAAM,iBAAiB,EAAE,CAAC;QACvC,MAAM,eAAe,GAAG,MAAM,iBAAiB,CAAC;YAC9C,SAAS,EAAE,MAAM;YACjB,IAAI;YACJ,WAAW,EAAE,OAAO,CAAC,GAAG;SACzB,CAAC,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,QAAQ,GAA4B;gBACxC,OAAO,EAAE;oBACP,WAAW,EAAE,MAAM,CAAC,UAAU;oBAC9B,UAAU,EAAE,MAAM,CAAC,SAAS;iBAC7B;gBACD,MAAM,EAAE;oBACN,MAAM,EAAE,OAAO;oBACf,UAAU,EAAE,0BAA0B;iBACvC;aACF,CAAC;YACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACtD,OAAO;QACT,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iEAAiE,CAAC,CAAC;QACxF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,iBAAiB;IAC9B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnE,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC;QACvB,IAAI,KAAK,GAAG,uBAAuB,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAmC,EAAE,IAAY;IACzE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAyB,EAAE,IAAY;IAClE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,IAAI,iBAAiB,EAAE,EAAE,CAAC;IACxB,KAAK,IAAI,EAAE,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB;IACxB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpD,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { CallbackRequest } from "./routes.js";
2
+ export type CallbackInboxState = "pending" | "delivered";
3
+ export interface CallbackInboxEntry extends CallbackRequest {
4
+ readonly fingerprint: string;
5
+ readonly acceptanceId: string;
6
+ readonly state: CallbackInboxState;
7
+ readonly acceptedAt: string;
8
+ readonly deliveredAt?: string;
9
+ }
10
+ export type CallbackInboxAdmission = {
11
+ readonly disposition: "admitted" | "duplicate";
12
+ readonly entry: CallbackInboxEntry;
13
+ } | {
14
+ readonly disposition: "collision";
15
+ };
16
+ /**
17
+ * A small, process-owned durable inbox for the last callback hop into Pi.
18
+ *
19
+ * `admit()` returns only after an atomic, fsync-backed file replacement. This
20
+ * lets the callback helper acknowledge connector admission even though Pi's
21
+ * `sendMessage()` API itself does not return an admission receipt.
22
+ */
23
+ export declare class CallbackInbox {
24
+ private entries;
25
+ private transactionTail;
26
+ private active;
27
+ private readonly filePath;
28
+ private readonly maxEntries;
29
+ private readonly maxFileBytes;
30
+ constructor(options: {
31
+ readonly filePath: string;
32
+ readonly maxEntries?: number;
33
+ readonly maxFileBytes?: number;
34
+ });
35
+ admit(request: CallbackRequest): Promise<CallbackInboxAdmission>;
36
+ markDelivered(entry: CallbackInboxEntry): Promise<CallbackInboxEntry>;
37
+ listPending(controllerId?: string): Promise<readonly CallbackInboxEntry[]>;
38
+ close(): Promise<void>;
39
+ private withTransaction;
40
+ }