@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/lib/routes.js ADDED
@@ -0,0 +1,255 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ const RETRY_BASE_DELAY_MS = 1_000;
3
+ const RETRY_MAX_DELAY_MS = 30_000;
4
+ /**
5
+ * Exact-object callback routing for a Pi controller incarnation.
6
+ *
7
+ * Admission is durable before an accepted ACK is returned. Delivery into Pi
8
+ * is then at-least-once across the narrow `sendMessage()`/inbox-state window,
9
+ * because Pi 0.84.4 does not expose an admission receipt for that API.
10
+ */
11
+ export class PiRouteTable {
12
+ inbox;
13
+ routeByAuthority = new WeakMap();
14
+ disposedAuthorities = new WeakSet();
15
+ routeByControllerId = new Map();
16
+ deliveryByKey = new Map();
17
+ retryTimerByKey = new Map();
18
+ retryAttemptByKey = new Map();
19
+ instanceNonce;
20
+ nextRouteNonce = 0;
21
+ active = true;
22
+ constructor(inbox, instanceNonce) {
23
+ this.inbox = inbox;
24
+ this.instanceNonce = instanceNonce ?? randomBytes(12).toString("hex");
25
+ if (!validIdentity(this.instanceNonce)) {
26
+ throw new Error("Pi AKK route instance nonce is invalid");
27
+ }
28
+ }
29
+ bind(target) {
30
+ this.assertActive();
31
+ validateTarget(target);
32
+ if (this.disposedAuthorities.has(target.authority)) {
33
+ throw new Error("AKK cannot reuse a disposed Pi controller authority");
34
+ }
35
+ const existing = this.routeByAuthority.get(target.authority);
36
+ if (existing) {
37
+ if (existing.target !== target ||
38
+ existing.sessionId !== target.sessionId ||
39
+ existing.runtimeGeneration !== target.runtimeGeneration ||
40
+ existing.anchorLeafId !== target.anchorLeafId) {
41
+ throw new Error("AKK Pi controller authority changed identity");
42
+ }
43
+ return existing;
44
+ }
45
+ this.nextRouteNonce += 1;
46
+ const controllerId = [
47
+ "akk-pi",
48
+ this.instanceNonce,
49
+ this.nextRouteNonce.toString(36),
50
+ identityDigest(target.sessionId, target.runtimeGeneration),
51
+ ].join(":");
52
+ const route = Object.freeze({
53
+ controllerId,
54
+ target,
55
+ sessionId: target.sessionId,
56
+ runtimeGeneration: target.runtimeGeneration,
57
+ anchorLeafId: target.anchorLeafId,
58
+ });
59
+ this.routeByAuthority.set(target.authority, route);
60
+ this.routeByControllerId.set(controllerId, route);
61
+ void this.replayPending(route);
62
+ return route;
63
+ }
64
+ dispose(target) {
65
+ this.disposedAuthorities.add(target.authority);
66
+ const route = this.routeByAuthority.get(target.authority);
67
+ if (!route)
68
+ return;
69
+ if (this.routeByControllerId.get(route.controllerId) === route) {
70
+ this.routeByControllerId.delete(route.controllerId);
71
+ }
72
+ this.cancelRouteRetries(route.controllerId);
73
+ }
74
+ async deliver(request) {
75
+ const base = acknowledgementBase(request);
76
+ if (!this.active || !validRequest(request)) {
77
+ return rejected(base, "invalid_or_stopped_callback_route");
78
+ }
79
+ const route = this.routeByControllerId.get(request.controllerId);
80
+ if (!route || !this.routeIsLive(route)) {
81
+ return rejected(base, "pi_route_not_live");
82
+ }
83
+ let admission;
84
+ try {
85
+ admission = await this.inbox.admit(request);
86
+ }
87
+ catch {
88
+ return retry(base, "callback_inbox_unavailable");
89
+ }
90
+ if (admission.disposition === "collision") {
91
+ return rejected(base, "idempotency_collision");
92
+ }
93
+ if (admission.entry.state === "pending") {
94
+ await this.ensureDelivery(route, admission.entry);
95
+ }
96
+ return accepted(base, admission.entry.acceptanceId);
97
+ }
98
+ async close() {
99
+ if (!this.active)
100
+ return;
101
+ this.active = false;
102
+ this.routeByControllerId.clear();
103
+ for (const timer of this.retryTimerByKey.values())
104
+ clearTimeout(timer);
105
+ this.retryTimerByKey.clear();
106
+ await Promise.allSettled(this.deliveryByKey.values());
107
+ }
108
+ async replayPending(route) {
109
+ let pending;
110
+ try {
111
+ pending = await this.inbox.listPending(route.controllerId);
112
+ }
113
+ catch {
114
+ return;
115
+ }
116
+ for (const entry of pending) {
117
+ if (!this.routeIsLive(route))
118
+ return;
119
+ await this.ensureDelivery(route, entry);
120
+ }
121
+ }
122
+ ensureDelivery(route, entry) {
123
+ const key = deliveryKey(entry);
124
+ const existing = this.deliveryByKey.get(key);
125
+ if (existing)
126
+ return existing;
127
+ const delivery = this.attemptDelivery(route, entry).finally(() => {
128
+ if (this.deliveryByKey.get(key) === delivery)
129
+ this.deliveryByKey.delete(key);
130
+ });
131
+ this.deliveryByKey.set(key, delivery);
132
+ return delivery;
133
+ }
134
+ async attemptDelivery(route, entry) {
135
+ if (!this.routeIsLive(route))
136
+ return;
137
+ try {
138
+ await route.target.deliver(entry.body, callbackRequest(entry));
139
+ await this.inbox.markDelivered(entry);
140
+ this.retryAttemptByKey.delete(deliveryKey(entry));
141
+ this.clearRetry(deliveryKey(entry));
142
+ }
143
+ catch {
144
+ this.scheduleRetry(route, entry);
145
+ }
146
+ }
147
+ scheduleRetry(route, entry) {
148
+ if (!this.routeIsLive(route))
149
+ return;
150
+ const key = deliveryKey(entry);
151
+ if (this.retryTimerByKey.has(key))
152
+ return;
153
+ const attempt = (this.retryAttemptByKey.get(key) ?? 0) + 1;
154
+ this.retryAttemptByKey.set(key, attempt);
155
+ const delay = Math.min(RETRY_BASE_DELAY_MS * 2 ** Math.min(attempt - 1, 5), RETRY_MAX_DELAY_MS);
156
+ const timer = setTimeout(() => {
157
+ this.retryTimerByKey.delete(key);
158
+ void this.ensureDelivery(route, entry);
159
+ }, delay);
160
+ timer.unref();
161
+ this.retryTimerByKey.set(key, timer);
162
+ }
163
+ cancelRouteRetries(controllerId) {
164
+ for (const [key, timer] of this.retryTimerByKey) {
165
+ if (!key.startsWith(`${controllerId}\u0000`))
166
+ continue;
167
+ clearTimeout(timer);
168
+ this.retryTimerByKey.delete(key);
169
+ this.retryAttemptByKey.delete(key);
170
+ }
171
+ }
172
+ clearRetry(key) {
173
+ const timer = this.retryTimerByKey.get(key);
174
+ if (timer)
175
+ clearTimeout(timer);
176
+ this.retryTimerByKey.delete(key);
177
+ }
178
+ assertActive() {
179
+ if (!this.active)
180
+ throw new Error("Pi AKK route table is stopped");
181
+ }
182
+ routeIsLive(route) {
183
+ if (!this.active || this.routeByControllerId.get(route.controllerId) !== route) {
184
+ return false;
185
+ }
186
+ try {
187
+ return route.target.sessionId === route.sessionId &&
188
+ route.target.runtimeGeneration === route.runtimeGeneration &&
189
+ route.target.anchorLeafId === route.anchorLeafId &&
190
+ route.target.isLive();
191
+ }
192
+ catch {
193
+ return false;
194
+ }
195
+ }
196
+ }
197
+ function validateTarget(target) {
198
+ if (typeof target.authority !== "object" ||
199
+ target.authority === null ||
200
+ !validIdentity(target.sessionId) ||
201
+ !validIdentity(target.runtimeGeneration) ||
202
+ (target.anchorLeafId !== null && !validIdentity(target.anchorLeafId)) ||
203
+ typeof target.isLive !== "function" ||
204
+ typeof target.deliver !== "function") {
205
+ throw new Error("Pi AKK callback target is invalid");
206
+ }
207
+ }
208
+ function callbackRequest(entry) {
209
+ return {
210
+ controllerId: entry.controllerId,
211
+ deliveryId: entry.deliveryId,
212
+ messageId: entry.messageId,
213
+ idempotencyKey: entry.idempotencyKey,
214
+ body: entry.body,
215
+ };
216
+ }
217
+ function acknowledgementBase(request) {
218
+ return {
219
+ delivery_id: request.deliveryId,
220
+ message_id: request.messageId,
221
+ };
222
+ }
223
+ function accepted(request, acceptanceId) {
224
+ return { request, result: { status: "accepted", acceptance_id: acceptanceId } };
225
+ }
226
+ function retry(request, errorCode) {
227
+ return { request, result: { status: "retry", error_code: errorCode } };
228
+ }
229
+ function rejected(request, errorCode) {
230
+ return { request, result: { status: "rejected", error_code: errorCode } };
231
+ }
232
+ function validRequest(request) {
233
+ return validIdentity(request.controllerId) &&
234
+ validIdentity(request.deliveryId) &&
235
+ validIdentity(request.messageId) &&
236
+ validIdentity(request.idempotencyKey) &&
237
+ typeof request.body === "string";
238
+ }
239
+ function validIdentity(value) {
240
+ return typeof value === "string" &&
241
+ value.length > 0 &&
242
+ value.length <= 4096 &&
243
+ value.trim() === value &&
244
+ !/[\u0000-\u001f\u007f]/u.test(value);
245
+ }
246
+ function identityDigest(sessionId, generation) {
247
+ return createHash("sha256")
248
+ .update(JSON.stringify([sessionId, generation]))
249
+ .digest("base64url")
250
+ .slice(0, 22);
251
+ }
252
+ function deliveryKey(entry) {
253
+ return `${entry.controllerId}\u0000${entry.idempotencyKey}`;
254
+ }
255
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { type TSchema } from "typebox";
2
+ /**
3
+ * Project AKK schemas into Pi's provider-facing TypeBox subset. The untouched
4
+ * schema remains authoritative at execution time through AJV and AKK itself.
5
+ */
6
+ export declare function adaptHostToolInputSchema(schema: Readonly<Record<string, unknown>>): TSchema;
7
+ export declare function compileAuthoritativeInputValidator(schema: Readonly<Record<string, unknown>>): (value: unknown) => void;
@@ -0,0 +1,85 @@
1
+ import { Ajv } from "ajv";
2
+ import { Type } from "typebox";
3
+ const SCHEMA_TYPES = new Set([
4
+ "object",
5
+ "array",
6
+ "string",
7
+ "number",
8
+ "integer",
9
+ "boolean",
10
+ "null",
11
+ ]);
12
+ const ajv = new Ajv({
13
+ allErrors: true,
14
+ strict: false,
15
+ validateFormats: false,
16
+ });
17
+ /**
18
+ * Project AKK schemas into Pi's provider-facing TypeBox subset. The untouched
19
+ * schema remains authoritative at execution time through AJV and AKK itself.
20
+ */
21
+ export function adaptHostToolInputSchema(schema) {
22
+ const adapted = adaptNode(schema);
23
+ if (adapted.type !== "object") {
24
+ throw new Error("AKK Host tool input schema must have an object root");
25
+ }
26
+ return Type.Unsafe(adapted);
27
+ }
28
+ export function compileAuthoritativeInputValidator(schema) {
29
+ let validate;
30
+ try {
31
+ validate = ajv.compile(structuredClone(schema));
32
+ }
33
+ catch {
34
+ throw new Error("AKK Host tool exposed an invalid authoritative input schema");
35
+ }
36
+ return (value) => {
37
+ if (!validate(value)) {
38
+ throw new Error("arguments did not match the authoritative AKK tool schema");
39
+ }
40
+ };
41
+ }
42
+ function adaptNode(value) {
43
+ if (!isRecord(value))
44
+ return {};
45
+ const output = {};
46
+ if (typeof value.description === "string")
47
+ output.description = value.description;
48
+ if (typeof value.title === "string")
49
+ output.title = value.title;
50
+ const type = typeof value.type === "string" && SCHEMA_TYPES.has(value.type)
51
+ ? value.type
52
+ : undefined;
53
+ if (!type)
54
+ return output;
55
+ output.type = type;
56
+ if (type === "object") {
57
+ const inputProperties = isRecord(value.properties) ? value.properties : {};
58
+ const properties = {};
59
+ for (const [name, property] of Object.entries(inputProperties)) {
60
+ properties[name] = adaptNode(property);
61
+ }
62
+ output.properties = properties;
63
+ if (Array.isArray(value.required)) {
64
+ const required = value.required.filter((name) => typeof name === "string" && Object.hasOwn(properties, name));
65
+ if (required.length > 0)
66
+ output.required = required;
67
+ }
68
+ if (typeof value.additionalProperties === "boolean") {
69
+ output.additionalProperties = value.additionalProperties;
70
+ }
71
+ return output;
72
+ }
73
+ if (type === "array" && isRecord(value.items)) {
74
+ output.items = adaptNode(value.items);
75
+ }
76
+ if (Array.isArray(value.enum))
77
+ output.enum = structuredClone(value.enum);
78
+ if (Object.hasOwn(value, "const"))
79
+ output.const = structuredClone(value.const);
80
+ return output;
81
+ }
82
+ function isRecord(value) {
83
+ return typeof value === "object" && value !== null && !Array.isArray(value);
84
+ }
85
+ //# sourceMappingURL=schema-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-adapter.js","sourceRoot":"","sources":["../src/schema-adapter.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@scotthuang/agent-knock-knock-pi",
3
+ "version": "0.1.0-poc.1",
4
+ "description": "Pi-as-orchestrator connector for Agent Knock Knock",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "lib/index.js",
8
+ "types": "lib/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./lib/index.d.ts",
12
+ "default": "./lib/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "lib/**/*.js",
18
+ "lib/**/*.js.map",
19
+ "lib/**/*.d.ts",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "keywords": [
24
+ "pi-package",
25
+ "pi-extension",
26
+ "agent-knock-knock",
27
+ "codex",
28
+ "claude-code",
29
+ "orchestration"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/scotthuang/agent-knock-knock.git",
34
+ "directory": "connectors/pi"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/scotthuang/agent-knock-knock/issues"
38
+ },
39
+ "homepage": "https://github.com/scotthuang/agent-knock-knock/tree/main/connectors/pi#readme",
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "pi": {
44
+ "extensions": [
45
+ "./lib/index.js"
46
+ ]
47
+ },
48
+ "engines": {
49
+ "node": ">=22.19.0"
50
+ },
51
+ "scripts": {
52
+ "clean": "node -e \"fs.rmSync('lib', { recursive: true, force: true }); fs.rmSync('.test-dist', { recursive: true, force: true })\"",
53
+ "build": "npm run clean && tsc -p tsconfig.json",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
55
+ "test:fast": "npm run build && tsc -p tsconfig.test.json && node --test .test-dist/test/*.test.js",
56
+ "prepack": "npm run build",
57
+ "pack:check": "npm pack --dry-run"
58
+ },
59
+ "peerDependencies": {
60
+ "@earendil-works/pi-coding-agent": "*",
61
+ "typebox": "*"
62
+ },
63
+ "dependencies": {
64
+ "@scotthuang/agent-knock-knock": "0.12.23",
65
+ "ajv": "8.20.0"
66
+ },
67
+ "devDependencies": {
68
+ "@earendil-works/pi-coding-agent": "0.84.4",
69
+ "@types/node": "22.20.0",
70
+ "typebox": "1.3.7",
71
+ "typescript": "6.0.3"
72
+ }
73
+ }