@agentuity/pi 1.0.22
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/dist/client.d.ts +11 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +118 -0
- package/dist/client.js.map +1 -0
- package/dist/handlers.d.ts +19 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/handlers.js +58 -0
- package/dist/handlers.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +198 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +68 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +3 -0
- package/dist/protocol.js.map +1 -0
- package/package.json +42 -0
- package/src/client.ts +150 -0
- package/src/handlers.ts +87 -0
- package/src/index.ts +251 -0
- package/src/protocol.ts +95 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { InitMessage, HubRequest, HubResponse } from './protocol.ts';
|
|
2
|
+
export declare class HubClient {
|
|
3
|
+
private ws;
|
|
4
|
+
private pending;
|
|
5
|
+
connect(url: string): Promise<InitMessage>;
|
|
6
|
+
nextId(): string;
|
|
7
|
+
send(request: HubRequest): Promise<HubResponse>;
|
|
8
|
+
close(): void;
|
|
9
|
+
get connected(): boolean;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAQ1E,qBAAa,SAAS;IACrB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAOX;IACE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IA6FhD,MAAM,IAAI,MAAM;IAIV,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAwBrD,KAAK,IAAI,IAAI;IAOb,IAAI,SAAS,IAAI,OAAO,CAEvB;CACD"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** How long to wait for a response before rejecting the pending promise (ms). */
|
|
2
|
+
const SEND_TIMEOUT_MS = 30_000;
|
|
3
|
+
/** How long to wait for the init message after connecting (ms). */
|
|
4
|
+
const CONNECT_TIMEOUT_MS = 30_000;
|
|
5
|
+
export class HubClient {
|
|
6
|
+
ws = null;
|
|
7
|
+
pending = new Map();
|
|
8
|
+
async connect(url) {
|
|
9
|
+
// Guard against overlapping connections — old socket handlers would corrupt shared state
|
|
10
|
+
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
|
|
11
|
+
throw new Error('Already connected or connecting — call close() first');
|
|
12
|
+
}
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
// Ensure ws:// or wss:// protocol — upgrade http(s) URLs automatically
|
|
15
|
+
let wsUrl = url;
|
|
16
|
+
if (wsUrl.startsWith('http://')) {
|
|
17
|
+
wsUrl = 'ws://' + wsUrl.slice(7);
|
|
18
|
+
}
|
|
19
|
+
else if (wsUrl.startsWith('https://')) {
|
|
20
|
+
wsUrl = 'wss://' + wsUrl.slice(8);
|
|
21
|
+
}
|
|
22
|
+
else if (!wsUrl.startsWith('ws://') &&
|
|
23
|
+
!wsUrl.startsWith('wss://')) {
|
|
24
|
+
wsUrl = 'ws://' + wsUrl;
|
|
25
|
+
}
|
|
26
|
+
this.ws = new WebSocket(wsUrl);
|
|
27
|
+
// Guard against duplicate init messages re-invoking the resolve
|
|
28
|
+
let initResolved = false;
|
|
29
|
+
const connectTimer = setTimeout(() => {
|
|
30
|
+
if (!initResolved) {
|
|
31
|
+
reject(new Error(`Hub did not send init message within ${CONNECT_TIMEOUT_MS}ms`));
|
|
32
|
+
this.ws?.close();
|
|
33
|
+
}
|
|
34
|
+
}, CONNECT_TIMEOUT_MS);
|
|
35
|
+
this.ws.onmessage = (event) => {
|
|
36
|
+
let data;
|
|
37
|
+
try {
|
|
38
|
+
const raw = typeof event.data === 'string'
|
|
39
|
+
? event.data
|
|
40
|
+
: new TextDecoder().decode(event.data);
|
|
41
|
+
data = JSON.parse(raw);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Malformed or non-JSON frame — ignore
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// First message should be init
|
|
48
|
+
if (data.type === 'init' && !initResolved) {
|
|
49
|
+
initResolved = true;
|
|
50
|
+
clearTimeout(connectTimer);
|
|
51
|
+
resolve(data);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// Otherwise it's a response to a pending request
|
|
55
|
+
const response = data;
|
|
56
|
+
const entry = this.pending.get(response.id);
|
|
57
|
+
if (entry) {
|
|
58
|
+
clearTimeout(entry.timer);
|
|
59
|
+
this.pending.delete(response.id);
|
|
60
|
+
entry.resolve(response);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
this.ws.onerror = (err) => {
|
|
64
|
+
// ErrorEvent has a message property; plain Event does not
|
|
65
|
+
const message = 'message' in err &&
|
|
66
|
+
typeof err.message === 'string'
|
|
67
|
+
? err.message
|
|
68
|
+
: `connection to ${wsUrl} failed`;
|
|
69
|
+
clearTimeout(connectTimer);
|
|
70
|
+
reject(new Error(`WebSocket error: ${message}`));
|
|
71
|
+
};
|
|
72
|
+
this.ws.onclose = () => {
|
|
73
|
+
clearTimeout(connectTimer);
|
|
74
|
+
// Reject connect() promise if init was never received
|
|
75
|
+
if (!initResolved) {
|
|
76
|
+
reject(new Error('WebSocket closed before init message received'));
|
|
77
|
+
}
|
|
78
|
+
// Reject all pending requests
|
|
79
|
+
for (const [id, entry] of this.pending) {
|
|
80
|
+
clearTimeout(entry.timer);
|
|
81
|
+
entry.reject(new Error('WebSocket closed'));
|
|
82
|
+
this.pending.delete(id);
|
|
83
|
+
}
|
|
84
|
+
this.ws = null;
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
nextId() {
|
|
89
|
+
return crypto.randomUUID();
|
|
90
|
+
}
|
|
91
|
+
async send(request) {
|
|
92
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
93
|
+
// Return a default ACK if not connected
|
|
94
|
+
return { id: request.id, actions: [{ action: 'ACK' }] };
|
|
95
|
+
}
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
const entry = this.pending.get(request.id);
|
|
99
|
+
if (entry) {
|
|
100
|
+
this.pending.delete(request.id);
|
|
101
|
+
entry.reject(new Error(`Hub response timeout after ${SEND_TIMEOUT_MS}ms for request ${request.id}`));
|
|
102
|
+
}
|
|
103
|
+
}, SEND_TIMEOUT_MS);
|
|
104
|
+
this.pending.set(request.id, { resolve, reject, timer });
|
|
105
|
+
this.ws.send(JSON.stringify(request));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
close() {
|
|
109
|
+
if (this.ws) {
|
|
110
|
+
this.ws.close();
|
|
111
|
+
this.ws = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
get connected() {
|
|
115
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,iFAAiF;AACjF,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,mEAAmE;AACnE,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,OAAO,SAAS;IACb,EAAE,GAAqB,IAAI,CAAC;IAC5B,OAAO,GAAG,IAAI,GAAG,EAOtB,CAAC;IACJ,KAAK,CAAC,OAAO,CAAC,GAAW;QACxB,yFAAyF;QACzF,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QACzE,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtC,uEAAuE;YACvE,IAAI,KAAK,GAAG,GAAG,CAAC;YAChB,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACjC,KAAK,GAAG,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAClC,CAAC;iBAAM,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBACzC,KAAK,GAAG,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACnC,CAAC;iBAAM,IACN,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;gBAC1B,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,EAC1B,CAAC;gBACF,KAAK,GAAG,OAAO,GAAG,KAAK,CAAC;YACzB,CAAC;YAED,IAAI,CAAC,EAAE,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC;YAE/B,gEAAgE;YAChE,IAAI,YAAY,GAAG,KAAK,CAAC;YAEzB,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,EAAE;gBACpC,IAAI,CAAC,YAAY,EAAE,CAAC;oBACnB,MAAM,CAAC,IAAI,KAAK,CAAC,wCAAwC,kBAAkB,IAAI,CAAC,CAAC,CAAC;oBAClF,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC;gBAClB,CAAC;YACF,CAAC,EAAE,kBAAkB,CAAC,CAAC;YAEvB,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;gBAC3C,IAAI,IAA6B,CAAC;gBAClC,IAAI,CAAC;oBACJ,MAAM,GAAG,GACR,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;wBAC7B,CAAC,CAAC,KAAK,CAAC,IAAI;wBACZ,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CACxB,KAAK,CAAC,IAAmB,CACzB,CAAC;oBACL,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;gBACnD,CAAC;gBAAC,MAAM,CAAC;oBACR,uCAAuC;oBACvC,OAAO;gBACR,CAAC;gBAED,+BAA+B;gBAC/B,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;oBAC3C,YAAY,GAAG,IAAI,CAAC;oBACpB,YAAY,CAAC,YAAY,CAAC,CAAC;oBAC3B,OAAO,CAAC,IAA8B,CAAC,CAAC;oBACxC,OAAO;gBACR,CAAC;gBAED,iDAAiD;gBACjD,MAAM,QAAQ,GAAG,IAA8B,CAAC;gBAChD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;gBAC5C,IAAI,KAAK,EAAE,CAAC;oBACX,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;oBACjC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACzB,CAAC;YACF,CAAC,CAAC;YAEF,IAAI,CAAC,EAAE,CAAC,OAAO,GAAG,CAAC,GAAU,EAAE,EAAE;gBAChC,0DAA0D;gBAC1D,MAAM,OAAO,GACZ,SAAS,IAAI,GAAG;oBAChB,OAAQ,GAAkB,CAAC,OAAO,KAAK,QAAQ;oBAC9C,CAAC,CAAE,GAAkB,CAAC,OAAO;oBAC7B,CAAC,CAAC,iBAAiB,KAAK,SAAS,CAAC;gBACpC,YAAY,CAAC,YAAY,CAAC,CAAC;gBAC3B,MAAM,CAAC,IAAI,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC,CAAC;YAClD,CAAC,CAAC;YAEF,IAAI,CAAC,EAAE,CAAC,OAAO,GAAG,GAAG,EAAE;gBACtB,YAAY,CAAC,YAAY,CAAC,CAAC;gBAC3B,sDAAsD;gBACtD,IAAI,CAAC,YAAY,EAAE,CAAC;oBACnB,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC,CAAC;gBACpE,CAAC;gBACD,8BAA8B;gBAC9B,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACxC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBAC1B,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC;oBAC5C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACzB,CAAC;gBACD,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YAChB,CAAC,CAAC;QACH,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,MAAM;QACL,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAmB;QAC7B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YACvD,wCAAwC;YACxC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACzD,CAAC;QAED,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC3C,IAAI,KAAK,EAAE,CAAC;oBACX,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBAChC,KAAK,CAAC,MAAM,CACX,IAAI,KAAK,CACR,8BAA8B,eAAe,kBAAkB,OAAO,CAAC,EAAE,EAAE,CAC3E,CACD,CAAC;gBACH,CAAC;YACF,CAAC,EAAE,eAAe,CAAC,CAAC;YAEpB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YACzD,IAAI,CAAC,EAAG,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,KAAK;QACJ,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QAChB,CAAC;IACF,CAAC;IAED,IAAI,SAAS;QACZ,OAAO,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,CAAC;IAClE,CAAC;CACD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { HubAction } from './protocol.ts';
|
|
2
|
+
export interface ActionResult {
|
|
3
|
+
block?: {
|
|
4
|
+
block: true;
|
|
5
|
+
reason: string;
|
|
6
|
+
};
|
|
7
|
+
returnValue?: unknown;
|
|
8
|
+
}
|
|
9
|
+
/** Minimal UI surface used by action handlers — avoids a hard dep on pi-coding-agent. */
|
|
10
|
+
interface ActionContext {
|
|
11
|
+
ui?: {
|
|
12
|
+
notify(message: string, level?: 'info' | 'warning' | 'error'): void;
|
|
13
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
14
|
+
setStatus(key: string, text?: string): void;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare function processActions(actions: HubAction[], ctx: ActionContext): Promise<ActionResult>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC5B,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,WAAW,CAAC,EAAE,OAAO,CAAC;CAEtB;AAED,yFAAyF;AACzF,UAAU,aAAa;IACtB,EAAE,CAAC,EAAE;QACJ,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,IAAI,CAAC;QACpE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAC1D,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5C,CAAC;CACF;AAED,wBAAsB,cAAc,CACnC,OAAO,EAAE,SAAS,EAAE,EACpB,GAAG,EAAE,aAAa,GAChB,OAAO,CAAC,YAAY,CAAC,CAkEvB"}
|
package/dist/handlers.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export async function processActions(actions, ctx) {
|
|
2
|
+
let result = {};
|
|
3
|
+
for (const action of actions) {
|
|
4
|
+
switch (action.action) {
|
|
5
|
+
case 'ACK':
|
|
6
|
+
// Terminal: proceed normally
|
|
7
|
+
result = {};
|
|
8
|
+
break;
|
|
9
|
+
case 'BLOCK':
|
|
10
|
+
// Terminal: block
|
|
11
|
+
result = { block: { block: true, reason: action.reason } };
|
|
12
|
+
break;
|
|
13
|
+
case 'RETURN':
|
|
14
|
+
// Terminal: return a specific result
|
|
15
|
+
result = { returnValue: action.result };
|
|
16
|
+
break;
|
|
17
|
+
case 'NOTIFY':
|
|
18
|
+
// Side effect: show notification, continue
|
|
19
|
+
if (ctx?.ui) {
|
|
20
|
+
ctx.ui.notify(action.message, action.level ?? 'info');
|
|
21
|
+
}
|
|
22
|
+
break;
|
|
23
|
+
case 'STATUS':
|
|
24
|
+
// Side effect: set status, continue
|
|
25
|
+
if (ctx?.ui) {
|
|
26
|
+
ctx.ui.setStatus(action.key, action.text);
|
|
27
|
+
}
|
|
28
|
+
break;
|
|
29
|
+
case 'CONFIRM': {
|
|
30
|
+
// Gate: if user denies, stop and block
|
|
31
|
+
if (ctx?.ui) {
|
|
32
|
+
const confirmed = await ctx.ui.confirm(action.title, action.message);
|
|
33
|
+
if (!confirmed) {
|
|
34
|
+
return {
|
|
35
|
+
block: {
|
|
36
|
+
block: true,
|
|
37
|
+
reason: action.deny_reason ?? 'Denied by user',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
// No UI available — block by default for safety
|
|
44
|
+
result = {
|
|
45
|
+
block: {
|
|
46
|
+
block: true,
|
|
47
|
+
reason: action.deny_reason ??
|
|
48
|
+
'Confirmation required but no UI available',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../src/handlers.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,OAAoB,EACpB,GAAkB;IAElB,IAAI,MAAM,GAAiB,EAAE,CAAC;IAE9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;YACvB,KAAK,KAAK;gBACT,6BAA6B;gBAC7B,MAAM,GAAG,EAAE,CAAC;gBACZ,MAAM;YAEP,KAAK,OAAO;gBACX,kBAAkB;gBAClB,MAAM,GAAG,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC3D,MAAM;YAEP,KAAK,QAAQ;gBACZ,qCAAqC;gBACrC,MAAM,GAAG,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;gBACxC,MAAM;YAEP,KAAK,QAAQ;gBACZ,2CAA2C;gBAC3C,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC;oBACb,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC;gBACvD,CAAC;gBACD,MAAM;YAEP,KAAK,QAAQ;gBACZ,oCAAoC;gBACpC,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC;oBACb,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC3C,CAAC;gBACD,MAAM;YAEP,KAAK,SAAS,CAAC,CAAC,CAAC;gBAChB,uCAAuC;gBACvC,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC;oBACb,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,OAAO,CACrC,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,OAAO,CACd,CAAC;oBACF,IAAI,CAAC,SAAS,EAAE,CAAC;wBAChB,OAAO;4BACN,KAAK,EAAE;gCACN,KAAK,EAAE,IAAI;gCACX,MAAM,EAAE,MAAM,CAAC,WAAW,IAAI,gBAAgB;6BAC9C;yBACD,CAAC;oBACH,CAAC;gBACF,CAAC;qBAAM,CAAC;oBACP,gDAAgD;oBAChD,MAAM,GAAG;wBACR,KAAK,EAAE;4BACN,KAAK,EAAE,IAAI;4BACX,MAAM,EACL,MAAM,CAAC,WAAW;gCAClB,2CAA2C;yBAC5C;qBACD,CAAC;gBACH,CAAC;gBACD,MAAM;YACP,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEX,YAAY,EAGZ,MAAM,+BAA+B,CAAC;AA6CvC,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,YAAY,QAsMjD;AAED,eAAe,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { HubClient } from "./client.js";
|
|
2
|
+
import { processActions } from "./handlers.js";
|
|
3
|
+
const HUB_URL_ENV = 'AGENTUITY_CODER_HUB_URL';
|
|
4
|
+
// All Pi events we subscribe to (order matters — session_start is handled separately)
|
|
5
|
+
const PROXY_EVENTS = [
|
|
6
|
+
'session_shutdown',
|
|
7
|
+
'session_before_switch',
|
|
8
|
+
'session_switch',
|
|
9
|
+
'session_before_fork',
|
|
10
|
+
'session_fork',
|
|
11
|
+
'session_before_compact',
|
|
12
|
+
'session_compact',
|
|
13
|
+
'before_agent_start',
|
|
14
|
+
'agent_start',
|
|
15
|
+
'agent_end',
|
|
16
|
+
'turn_start',
|
|
17
|
+
'turn_end',
|
|
18
|
+
'tool_call',
|
|
19
|
+
'tool_result',
|
|
20
|
+
'tool_execution_start',
|
|
21
|
+
'tool_execution_update',
|
|
22
|
+
'tool_execution_end',
|
|
23
|
+
'message_start',
|
|
24
|
+
'message_update',
|
|
25
|
+
'message_end',
|
|
26
|
+
'input',
|
|
27
|
+
'model_select',
|
|
28
|
+
'context',
|
|
29
|
+
];
|
|
30
|
+
function log(msg) {
|
|
31
|
+
console.error(`[agentuity-pi] ${msg}`);
|
|
32
|
+
}
|
|
33
|
+
export function agentuityCoderHub(pi) {
|
|
34
|
+
const hubUrl = process.env[HUB_URL_ENV];
|
|
35
|
+
// No-op if not configured
|
|
36
|
+
if (!hubUrl) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
log(`Hub URL: ${hubUrl}`);
|
|
40
|
+
const client = new HubClient();
|
|
41
|
+
// Connect to the Hub and register tools/commands
|
|
42
|
+
async function connectAndRegister() {
|
|
43
|
+
if (client.connected)
|
|
44
|
+
return;
|
|
45
|
+
log('Connecting to Hub...');
|
|
46
|
+
let initMsg;
|
|
47
|
+
try {
|
|
48
|
+
initMsg = await client.connect(hubUrl);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
52
|
+
log(`Failed to connect: ${msg}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
log(`Connected. Init: ${initMsg.tools?.length ?? 0} tools, ${initMsg.commands?.length ?? 0} commands`);
|
|
56
|
+
// Register tools from Hub
|
|
57
|
+
if (initMsg.tools) {
|
|
58
|
+
for (const toolDef of initMsg.tools) {
|
|
59
|
+
log(`Registering tool: ${toolDef.name}`);
|
|
60
|
+
pi.registerTool({
|
|
61
|
+
name: toolDef.name,
|
|
62
|
+
label: toolDef.label,
|
|
63
|
+
description: toolDef.description,
|
|
64
|
+
parameters: toolDef.parameters,
|
|
65
|
+
async execute(toolCallId, params, _signal, _onUpdate, ctx) {
|
|
66
|
+
log(`Tool execute: ${toolDef.name}`);
|
|
67
|
+
const id = client.nextId();
|
|
68
|
+
let response;
|
|
69
|
+
try {
|
|
70
|
+
response = await client.send({
|
|
71
|
+
id,
|
|
72
|
+
type: 'tool',
|
|
73
|
+
name: toolDef.name,
|
|
74
|
+
toolCallId,
|
|
75
|
+
params: params,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: 'text',
|
|
83
|
+
text: 'Error: Hub connection lost',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
details: {},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const result = await processActions(response.actions, ctx);
|
|
90
|
+
if (result.returnValue !== undefined) {
|
|
91
|
+
return result.returnValue;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{ type: 'text', text: 'Done' },
|
|
96
|
+
],
|
|
97
|
+
details: {},
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Register commands from Hub
|
|
104
|
+
if (initMsg.commands) {
|
|
105
|
+
for (const cmdDef of initMsg.commands) {
|
|
106
|
+
log(`Registering command: /${cmdDef.name}`);
|
|
107
|
+
pi.registerCommand(cmdDef.name, {
|
|
108
|
+
description: cmdDef.description,
|
|
109
|
+
handler: async (args, ctx) => {
|
|
110
|
+
log(`Command execute: /${cmdDef.name}`);
|
|
111
|
+
const id = client.nextId();
|
|
112
|
+
let response;
|
|
113
|
+
try {
|
|
114
|
+
response = await client.send({
|
|
115
|
+
id,
|
|
116
|
+
type: 'command',
|
|
117
|
+
name: cmdDef.name,
|
|
118
|
+
args,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
ctx.ui.notify('Hub connection lost', 'error');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await processActions(response.actions, ctx);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
log('Registration complete');
|
|
131
|
+
}
|
|
132
|
+
// Helper to send event and process response actions
|
|
133
|
+
async function sendEvent(eventName, data, ctx) {
|
|
134
|
+
if (!client.connected)
|
|
135
|
+
return undefined;
|
|
136
|
+
const id = client.nextId();
|
|
137
|
+
let response;
|
|
138
|
+
try {
|
|
139
|
+
response = await client.send({
|
|
140
|
+
id,
|
|
141
|
+
type: 'event',
|
|
142
|
+
event: eventName,
|
|
143
|
+
data,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
const result = await processActions(response.actions, ctx);
|
|
150
|
+
if (result.block)
|
|
151
|
+
return result.block;
|
|
152
|
+
if (result.returnValue !== undefined)
|
|
153
|
+
return result.returnValue;
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
// Serialize event data — strip non-serializable fields
|
|
157
|
+
function serializeEvent(event) {
|
|
158
|
+
const data = {};
|
|
159
|
+
if (event && typeof event === 'object') {
|
|
160
|
+
for (const [key, value] of Object.entries(event)) {
|
|
161
|
+
if (typeof value !== 'function' && key !== 'signal') {
|
|
162
|
+
try {
|
|
163
|
+
JSON.stringify(value);
|
|
164
|
+
data[key] = value;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Skip non-serializable values
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return data;
|
|
173
|
+
}
|
|
174
|
+
// Cast pi.on to generic handler since we iterate over a union of event names
|
|
175
|
+
// TypeScript overloads require exact string literal matching which doesn't work in loops
|
|
176
|
+
const onEvent = pi.on.bind(pi);
|
|
177
|
+
// ── session_start: connect + register BEFORE Pi builds the tool list ──
|
|
178
|
+
onEvent('session_start', async (event, ctx) => {
|
|
179
|
+
await connectAndRegister();
|
|
180
|
+
if (client.connected) {
|
|
181
|
+
return sendEvent('session_start', serializeEvent(event), ctx);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
for (const eventName of PROXY_EVENTS) {
|
|
185
|
+
onEvent(eventName, async (event, ctx) => {
|
|
186
|
+
if (!client.connected)
|
|
187
|
+
return undefined;
|
|
188
|
+
return sendEvent(eventName, serializeEvent(event), ctx);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
// Clean up on shutdown
|
|
192
|
+
pi.on('session_shutdown', async () => {
|
|
193
|
+
log('Shutting down');
|
|
194
|
+
client.close();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
export default agentuityCoderHub;
|
|
198
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAE9C,sFAAsF;AACtF,MAAM,YAAY,GAAG;IACpB,kBAAkB;IAClB,uBAAuB;IACvB,gBAAgB;IAChB,qBAAqB;IACrB,cAAc;IACd,wBAAwB;IACxB,iBAAiB;IACjB,oBAAoB;IACpB,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU;IACV,WAAW;IACX,aAAa;IACb,sBAAsB;IACtB,uBAAuB;IACvB,oBAAoB;IACpB,eAAe;IACf,gBAAgB;IAChB,aAAa;IACb,OAAO;IACP,cAAc;IACd,SAAS;CACA,CAAC;AAQX,SAAS,GAAG,CAAC,GAAW;IACvB,OAAO,CAAC,KAAK,CAAC,kBAAkB,GAAG,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAgB;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAExC,0BAA0B;IAC1B,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO;IACR,CAAC;IAED,GAAG,CAAC,YAAY,MAAM,EAAE,CAAC,CAAC;IAE1B,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;IAE/B,iDAAiD;IACjD,KAAK,UAAU,kBAAkB;QAChC,IAAI,MAAM,CAAC,SAAS;YAAE,OAAO;QAE7B,GAAG,CAAC,sBAAsB,CAAC,CAAC;QAC5B,IAAI,OAAoB,CAAC;QAEzB,IAAI,CAAC;YACJ,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,MAAO,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,GAAG,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;YACjC,OAAO;QACR,CAAC;QAED,GAAG,CACF,oBAAoB,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,WAAW,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,WAAW,CACjG,CAAC;QAEF,0BAA0B;QAC1B,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YACnB,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;gBACrC,GAAG,CAAC,qBAAqB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;gBACzC,EAAE,CAAC,YAAY,CAAC;oBACf,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,UAAU,EAAE,OAAO,CAAC,UAAgC;oBACpD,KAAK,CAAC,OAAO,CACZ,UAAkB,EAClB,MAAe,EACf,OAAgC,EAChC,SAAkB,EAClB,GAAqB;wBAErB,GAAG,CAAC,iBAAiB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;wBACrC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;wBAC3B,IAAI,QAAqB,CAAC;wBAE1B,IAAI,CAAC;4BACJ,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC;gCAC5B,EAAE;gCACF,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,OAAO,CAAC,IAAI;gCAClB,UAAU;gCACV,MAAM,EAAE,MAAiC;6BACzC,CAAC,CAAC;wBACJ,CAAC;wBAAC,MAAM,CAAC;4BACR,OAAO;gCACN,OAAO,EAAE;oCACR;wCACC,IAAI,EAAE,MAAe;wCACrB,IAAI,EAAE,4BAA4B;qCAClC;iCACD;gCACD,OAAO,EAAE,EAAE;6BACX,CAAC;wBACH,CAAC;wBAED,MAAM,MAAM,GAAG,MAAM,cAAc,CAClC,QAAQ,CAAC,OAAO,EAChB,GAAG,CACH,CAAC;wBAEF,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;4BACtC,OAAO,MAAM,CAAC,WAAuC,CAAC;wBACvD,CAAC;wBAED,OAAO;4BACN,OAAO,EAAE;gCACR,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,MAAM,EAAE;6BACvC;4BACD,OAAO,EAAE,EAAE;yBACX,CAAC;oBACH,CAAC;iBACD,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QAED,6BAA6B;QAC7B,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACvC,GAAG,CAAC,yBAAyB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC5C,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,EAAE;oBAC/B,WAAW,EAAE,MAAM,CAAC,WAAW;oBAC/B,OAAO,EAAE,KAAK,EACb,IAAY,EACZ,GAA4B,EAC3B,EAAE;wBACH,GAAG,CAAC,qBAAqB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;wBACxC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;wBAC3B,IAAI,QAAqB,CAAC;wBAE1B,IAAI,CAAC;4BACJ,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC;gCAC5B,EAAE;gCACF,IAAI,EAAE,SAAS;gCACf,IAAI,EAAE,MAAM,CAAC,IAAI;gCACjB,IAAI;6BACJ,CAAC,CAAC;wBACJ,CAAC;wBAAC,MAAM,CAAC;4BACR,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;4BAC9C,OAAO;wBACR,CAAC;wBAED,MAAM,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;oBAC7C,CAAC;iBACD,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QAED,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAC9B,CAAC;IAED,oDAAoD;IACpD,KAAK,UAAU,SAAS,CACvB,SAAiB,EACjB,IAA6B,EAC7B,GAAqB;QAErB,IAAI,CAAC,MAAM,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC;QAExC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAC3B,IAAI,QAAqB,CAAC;QAE1B,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC;gBAC5B,EAAE;gBACF,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,SAAS;gBAChB,IAAI;aACJ,CAAC,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,SAAS,CAAC;QAClB,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAE3D,IAAI,MAAM,CAAC,KAAK;YAAE,OAAO,MAAM,CAAC,KAAK,CAAC;QACtC,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,WAAW,CAAC;QAChE,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,uDAAuD;IACvD,SAAS,cAAc,CAAC,KAAc;QACrC,MAAM,IAAI,GAA4B,EAAE,CAAC;QACzC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClD,IAAI,OAAO,KAAK,KAAK,UAAU,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;oBACrD,IAAI,CAAC;wBACJ,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;wBACtB,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;oBACnB,CAAC;oBAAC,MAAM,CAAC;wBACR,+BAA+B;oBAChC,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAED,6EAA6E;IAC7E,yFAAyF;IACzF,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAwB,CAAC;IAEtD,yEAAyE;IACzE,OAAO,CAAC,eAAe,EAAE,KAAK,EAAE,KAAc,EAAE,GAAqB,EAAE,EAAE;QACxE,MAAM,kBAAkB,EAAE,CAAC;QAE3B,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,OAAO,SAAS,CAAC,eAAe,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;QAC/D,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,KAAK,MAAM,SAAS,IAAI,YAAY,EAAE,CAAC;QACtC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,KAAc,EAAE,GAAqB,EAAE,EAAE;YAClE,IAAI,CAAC,MAAM,CAAC,SAAS;gBAAE,OAAO,SAAS,CAAC;YACxC,OAAO,SAAS,CAAC,SAAS,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,uBAAuB;IACvB,EAAE,CAAC,EAAE,CAAC,kBAAkB,EAAE,KAAK,IAAI,EAAE;QACpC,GAAG,CAAC,eAAe,CAAC,CAAC;QACrB,MAAM,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface HubToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface HubCommandDefinition {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
export interface InitMessage {
|
|
12
|
+
type: 'init';
|
|
13
|
+
tools?: HubToolDefinition[];
|
|
14
|
+
commands?: HubCommandDefinition[];
|
|
15
|
+
}
|
|
16
|
+
export interface EventRequest {
|
|
17
|
+
id: string;
|
|
18
|
+
type: 'event';
|
|
19
|
+
event: string;
|
|
20
|
+
data: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export interface ToolRequest {
|
|
23
|
+
id: string;
|
|
24
|
+
type: 'tool';
|
|
25
|
+
name: string;
|
|
26
|
+
toolCallId: string;
|
|
27
|
+
params: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export interface CommandRequest {
|
|
30
|
+
id: string;
|
|
31
|
+
type: 'command';
|
|
32
|
+
name: string;
|
|
33
|
+
args: string;
|
|
34
|
+
}
|
|
35
|
+
export type HubRequest = EventRequest | ToolRequest | CommandRequest;
|
|
36
|
+
export interface AckAction {
|
|
37
|
+
action: 'ACK';
|
|
38
|
+
}
|
|
39
|
+
export interface BlockAction {
|
|
40
|
+
action: 'BLOCK';
|
|
41
|
+
reason: string;
|
|
42
|
+
}
|
|
43
|
+
export interface ConfirmAction {
|
|
44
|
+
action: 'CONFIRM';
|
|
45
|
+
title: string;
|
|
46
|
+
message: string;
|
|
47
|
+
deny_reason?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface NotifyAction {
|
|
50
|
+
action: 'NOTIFY';
|
|
51
|
+
message: string;
|
|
52
|
+
level?: 'info' | 'warning' | 'error';
|
|
53
|
+
}
|
|
54
|
+
export interface ReturnAction {
|
|
55
|
+
action: 'RETURN';
|
|
56
|
+
result: unknown;
|
|
57
|
+
}
|
|
58
|
+
export interface StatusAction {
|
|
59
|
+
action: 'STATUS';
|
|
60
|
+
key: string;
|
|
61
|
+
text?: string;
|
|
62
|
+
}
|
|
63
|
+
export type HubAction = AckAction | BlockAction | ConfirmAction | NotifyAction | ReturnAction | StatusAction;
|
|
64
|
+
export interface HubResponse {
|
|
65
|
+
id: string;
|
|
66
|
+
actions: HubAction[];
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,oBAAoB,EAAE,CAAC;CAClC;AAID,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;AAIrE,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,KAAK,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,QAAQ,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,QAAQ,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAClB,SAAS,GACT,WAAW,GACX,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,YAAY,CAAC;AAIhB,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,SAAS,EAAE,CAAC;CACrB"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,sDAAsD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentuity/pi",
|
|
3
|
+
"version": "1.0.22",
|
|
4
|
+
"description": "Agentuity Coder Hub extension for Pi coding agent",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Agentuity employees and contributors",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"README.md",
|
|
12
|
+
"src",
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
23
|
+
"build": "bunx tsc --build",
|
|
24
|
+
"typecheck": "bunx tsc --noEmit",
|
|
25
|
+
"prepublishOnly": "bun run clean && bun run build"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
29
|
+
"@sinclair/typebox": "*"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@mariozechner/pi-coding-agent": "^0.54.2",
|
|
33
|
+
"@sinclair/typebox": "^0.34.48",
|
|
34
|
+
"@types/bun": "^1.3.9",
|
|
35
|
+
"bun-types": "^1.3.9",
|
|
36
|
+
"typescript": "^5.9.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"sideEffects": false
|
|
42
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { InitMessage, HubRequest, HubResponse } from './protocol.ts';
|
|
2
|
+
|
|
3
|
+
/** How long to wait for a response before rejecting the pending promise (ms). */
|
|
4
|
+
const SEND_TIMEOUT_MS = 30_000;
|
|
5
|
+
|
|
6
|
+
/** How long to wait for the init message after connecting (ms). */
|
|
7
|
+
const CONNECT_TIMEOUT_MS = 30_000;
|
|
8
|
+
|
|
9
|
+
export class HubClient {
|
|
10
|
+
private ws: WebSocket | null = null;
|
|
11
|
+
private pending = new Map<
|
|
12
|
+
string,
|
|
13
|
+
{
|
|
14
|
+
resolve: (resp: HubResponse) => void;
|
|
15
|
+
reject: (err: Error) => void;
|
|
16
|
+
timer: ReturnType<typeof setTimeout>;
|
|
17
|
+
}
|
|
18
|
+
>();
|
|
19
|
+
async connect(url: string): Promise<InitMessage> {
|
|
20
|
+
// Guard against overlapping connections — old socket handlers would corrupt shared state
|
|
21
|
+
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
|
|
22
|
+
throw new Error('Already connected or connecting — call close() first');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
// Ensure ws:// or wss:// protocol — upgrade http(s) URLs automatically
|
|
27
|
+
let wsUrl = url;
|
|
28
|
+
if (wsUrl.startsWith('http://')) {
|
|
29
|
+
wsUrl = 'ws://' + wsUrl.slice(7);
|
|
30
|
+
} else if (wsUrl.startsWith('https://')) {
|
|
31
|
+
wsUrl = 'wss://' + wsUrl.slice(8);
|
|
32
|
+
} else if (
|
|
33
|
+
!wsUrl.startsWith('ws://') &&
|
|
34
|
+
!wsUrl.startsWith('wss://')
|
|
35
|
+
) {
|
|
36
|
+
wsUrl = 'ws://' + wsUrl;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.ws = new WebSocket(wsUrl);
|
|
40
|
+
|
|
41
|
+
// Guard against duplicate init messages re-invoking the resolve
|
|
42
|
+
let initResolved = false;
|
|
43
|
+
|
|
44
|
+
const connectTimer = setTimeout(() => {
|
|
45
|
+
if (!initResolved) {
|
|
46
|
+
reject(new Error(`Hub did not send init message within ${CONNECT_TIMEOUT_MS}ms`));
|
|
47
|
+
this.ws?.close();
|
|
48
|
+
}
|
|
49
|
+
}, CONNECT_TIMEOUT_MS);
|
|
50
|
+
|
|
51
|
+
this.ws.onmessage = (event: MessageEvent) => {
|
|
52
|
+
let data: Record<string, unknown>;
|
|
53
|
+
try {
|
|
54
|
+
const raw =
|
|
55
|
+
typeof event.data === 'string'
|
|
56
|
+
? event.data
|
|
57
|
+
: new TextDecoder().decode(
|
|
58
|
+
event.data as ArrayBuffer,
|
|
59
|
+
);
|
|
60
|
+
data = JSON.parse(raw) as Record<string, unknown>;
|
|
61
|
+
} catch {
|
|
62
|
+
// Malformed or non-JSON frame — ignore
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// First message should be init
|
|
67
|
+
if (data.type === 'init' && !initResolved) {
|
|
68
|
+
initResolved = true;
|
|
69
|
+
clearTimeout(connectTimer);
|
|
70
|
+
resolve(data as unknown as InitMessage);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Otherwise it's a response to a pending request
|
|
75
|
+
const response = data as unknown as HubResponse;
|
|
76
|
+
const entry = this.pending.get(response.id);
|
|
77
|
+
if (entry) {
|
|
78
|
+
clearTimeout(entry.timer);
|
|
79
|
+
this.pending.delete(response.id);
|
|
80
|
+
entry.resolve(response);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
this.ws.onerror = (err: Event) => {
|
|
85
|
+
// ErrorEvent has a message property; plain Event does not
|
|
86
|
+
const message =
|
|
87
|
+
'message' in err &&
|
|
88
|
+
typeof (err as ErrorEvent).message === 'string'
|
|
89
|
+
? (err as ErrorEvent).message
|
|
90
|
+
: `connection to ${wsUrl} failed`;
|
|
91
|
+
clearTimeout(connectTimer);
|
|
92
|
+
reject(new Error(`WebSocket error: ${message}`));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
this.ws.onclose = () => {
|
|
96
|
+
clearTimeout(connectTimer);
|
|
97
|
+
// Reject connect() promise if init was never received
|
|
98
|
+
if (!initResolved) {
|
|
99
|
+
reject(new Error('WebSocket closed before init message received'));
|
|
100
|
+
}
|
|
101
|
+
// Reject all pending requests
|
|
102
|
+
for (const [id, entry] of this.pending) {
|
|
103
|
+
clearTimeout(entry.timer);
|
|
104
|
+
entry.reject(new Error('WebSocket closed'));
|
|
105
|
+
this.pending.delete(id);
|
|
106
|
+
}
|
|
107
|
+
this.ws = null;
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
nextId(): string {
|
|
113
|
+
return crypto.randomUUID();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async send(request: HubRequest): Promise<HubResponse> {
|
|
117
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
118
|
+
// Return a default ACK if not connected
|
|
119
|
+
return { id: request.id, actions: [{ action: 'ACK' }] };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return new Promise<HubResponse>((resolve, reject) => {
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
const entry = this.pending.get(request.id);
|
|
125
|
+
if (entry) {
|
|
126
|
+
this.pending.delete(request.id);
|
|
127
|
+
entry.reject(
|
|
128
|
+
new Error(
|
|
129
|
+
`Hub response timeout after ${SEND_TIMEOUT_MS}ms for request ${request.id}`,
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}, SEND_TIMEOUT_MS);
|
|
134
|
+
|
|
135
|
+
this.pending.set(request.id, { resolve, reject, timer });
|
|
136
|
+
this.ws!.send(JSON.stringify(request));
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
close(): void {
|
|
141
|
+
if (this.ws) {
|
|
142
|
+
this.ws.close();
|
|
143
|
+
this.ws = null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get connected(): boolean {
|
|
148
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
149
|
+
}
|
|
150
|
+
}
|
package/src/handlers.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { HubAction } from './protocol.ts';
|
|
2
|
+
|
|
3
|
+
export interface ActionResult {
|
|
4
|
+
block?: { block: true; reason: string };
|
|
5
|
+
returnValue?: unknown;
|
|
6
|
+
// undefined means ACK (proceed normally)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Minimal UI surface used by action handlers — avoids a hard dep on pi-coding-agent. */
|
|
10
|
+
interface ActionContext {
|
|
11
|
+
ui?: {
|
|
12
|
+
notify(message: string, level?: 'info' | 'warning' | 'error'): void;
|
|
13
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
14
|
+
setStatus(key: string, text?: string): void;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function processActions(
|
|
19
|
+
actions: HubAction[],
|
|
20
|
+
ctx: ActionContext,
|
|
21
|
+
): Promise<ActionResult> {
|
|
22
|
+
let result: ActionResult = {};
|
|
23
|
+
|
|
24
|
+
for (const action of actions) {
|
|
25
|
+
switch (action.action) {
|
|
26
|
+
case 'ACK':
|
|
27
|
+
// Terminal: proceed normally
|
|
28
|
+
result = {};
|
|
29
|
+
break;
|
|
30
|
+
|
|
31
|
+
case 'BLOCK':
|
|
32
|
+
// Terminal: block
|
|
33
|
+
result = { block: { block: true, reason: action.reason } };
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case 'RETURN':
|
|
37
|
+
// Terminal: return a specific result
|
|
38
|
+
result = { returnValue: action.result };
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
case 'NOTIFY':
|
|
42
|
+
// Side effect: show notification, continue
|
|
43
|
+
if (ctx?.ui) {
|
|
44
|
+
ctx.ui.notify(action.message, action.level ?? 'info');
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case 'STATUS':
|
|
49
|
+
// Side effect: set status, continue
|
|
50
|
+
if (ctx?.ui) {
|
|
51
|
+
ctx.ui.setStatus(action.key, action.text);
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case 'CONFIRM': {
|
|
56
|
+
// Gate: if user denies, stop and block
|
|
57
|
+
if (ctx?.ui) {
|
|
58
|
+
const confirmed = await ctx.ui.confirm(
|
|
59
|
+
action.title,
|
|
60
|
+
action.message,
|
|
61
|
+
);
|
|
62
|
+
if (!confirmed) {
|
|
63
|
+
return {
|
|
64
|
+
block: {
|
|
65
|
+
block: true,
|
|
66
|
+
reason: action.deny_reason ?? 'Denied by user',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// No UI available — block by default for safety
|
|
72
|
+
result = {
|
|
73
|
+
block: {
|
|
74
|
+
block: true,
|
|
75
|
+
reason:
|
|
76
|
+
action.deny_reason ??
|
|
77
|
+
'Confirmation required but no UI available',
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentToolResult,
|
|
3
|
+
ExtensionAPI,
|
|
4
|
+
ExtensionContext,
|
|
5
|
+
ExtensionCommandContext,
|
|
6
|
+
} from '@mariozechner/pi-coding-agent';
|
|
7
|
+
import type { TSchema } from '@sinclair/typebox';
|
|
8
|
+
import { HubClient } from './client.ts';
|
|
9
|
+
import { processActions } from './handlers.ts';
|
|
10
|
+
import type { HubResponse, InitMessage } from './protocol.ts';
|
|
11
|
+
|
|
12
|
+
const HUB_URL_ENV = 'AGENTUITY_CODER_HUB_URL';
|
|
13
|
+
|
|
14
|
+
// All Pi events we subscribe to (order matters — session_start is handled separately)
|
|
15
|
+
const PROXY_EVENTS = [
|
|
16
|
+
'session_shutdown',
|
|
17
|
+
'session_before_switch',
|
|
18
|
+
'session_switch',
|
|
19
|
+
'session_before_fork',
|
|
20
|
+
'session_fork',
|
|
21
|
+
'session_before_compact',
|
|
22
|
+
'session_compact',
|
|
23
|
+
'before_agent_start',
|
|
24
|
+
'agent_start',
|
|
25
|
+
'agent_end',
|
|
26
|
+
'turn_start',
|
|
27
|
+
'turn_end',
|
|
28
|
+
'tool_call',
|
|
29
|
+
'tool_result',
|
|
30
|
+
'tool_execution_start',
|
|
31
|
+
'tool_execution_update',
|
|
32
|
+
'tool_execution_end',
|
|
33
|
+
'message_start',
|
|
34
|
+
'message_update',
|
|
35
|
+
'message_end',
|
|
36
|
+
'input',
|
|
37
|
+
'model_select',
|
|
38
|
+
'context',
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
// Generic event handler type for the iteration loop
|
|
42
|
+
type GenericEventHandler = (
|
|
43
|
+
event: string,
|
|
44
|
+
handler: (event: unknown, ctx: ExtensionContext) => Promise<unknown>,
|
|
45
|
+
) => void;
|
|
46
|
+
|
|
47
|
+
function log(msg: string): void {
|
|
48
|
+
console.error(`[agentuity-pi] ${msg}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
52
|
+
const hubUrl = process.env[HUB_URL_ENV];
|
|
53
|
+
|
|
54
|
+
// No-op if not configured
|
|
55
|
+
if (!hubUrl) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
log(`Hub URL: ${hubUrl}`);
|
|
60
|
+
|
|
61
|
+
const client = new HubClient();
|
|
62
|
+
|
|
63
|
+
// Connect to the Hub and register tools/commands
|
|
64
|
+
async function connectAndRegister(): Promise<void> {
|
|
65
|
+
if (client.connected) return;
|
|
66
|
+
|
|
67
|
+
log('Connecting to Hub...');
|
|
68
|
+
let initMsg: InitMessage;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
initMsg = await client.connect(hubUrl!);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
+
log(`Failed to connect: ${msg}`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log(
|
|
79
|
+
`Connected. Init: ${initMsg.tools?.length ?? 0} tools, ${initMsg.commands?.length ?? 0} commands`,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Register tools from Hub
|
|
83
|
+
if (initMsg.tools) {
|
|
84
|
+
for (const toolDef of initMsg.tools) {
|
|
85
|
+
log(`Registering tool: ${toolDef.name}`);
|
|
86
|
+
pi.registerTool({
|
|
87
|
+
name: toolDef.name,
|
|
88
|
+
label: toolDef.label,
|
|
89
|
+
description: toolDef.description,
|
|
90
|
+
parameters: toolDef.parameters as unknown as TSchema,
|
|
91
|
+
async execute(
|
|
92
|
+
toolCallId: string,
|
|
93
|
+
params: unknown,
|
|
94
|
+
_signal: AbortSignal | undefined,
|
|
95
|
+
_onUpdate: unknown,
|
|
96
|
+
ctx: ExtensionContext,
|
|
97
|
+
) {
|
|
98
|
+
log(`Tool execute: ${toolDef.name}`);
|
|
99
|
+
const id = client.nextId();
|
|
100
|
+
let response: HubResponse;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
response = await client.send({
|
|
104
|
+
id,
|
|
105
|
+
type: 'tool',
|
|
106
|
+
name: toolDef.name,
|
|
107
|
+
toolCallId,
|
|
108
|
+
params: params as Record<string, unknown>,
|
|
109
|
+
});
|
|
110
|
+
} catch {
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: 'text' as const,
|
|
115
|
+
text: 'Error: Hub connection lost',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
details: {},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const result = await processActions(
|
|
123
|
+
response.actions,
|
|
124
|
+
ctx,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (result.returnValue !== undefined) {
|
|
128
|
+
return result.returnValue as AgentToolResult<unknown>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{ type: 'text' as const, text: 'Done' },
|
|
134
|
+
],
|
|
135
|
+
details: {},
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Register commands from Hub
|
|
143
|
+
if (initMsg.commands) {
|
|
144
|
+
for (const cmdDef of initMsg.commands) {
|
|
145
|
+
log(`Registering command: /${cmdDef.name}`);
|
|
146
|
+
pi.registerCommand(cmdDef.name, {
|
|
147
|
+
description: cmdDef.description,
|
|
148
|
+
handler: async (
|
|
149
|
+
args: string,
|
|
150
|
+
ctx: ExtensionCommandContext,
|
|
151
|
+
) => {
|
|
152
|
+
log(`Command execute: /${cmdDef.name}`);
|
|
153
|
+
const id = client.nextId();
|
|
154
|
+
let response: HubResponse;
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
response = await client.send({
|
|
158
|
+
id,
|
|
159
|
+
type: 'command',
|
|
160
|
+
name: cmdDef.name,
|
|
161
|
+
args,
|
|
162
|
+
});
|
|
163
|
+
} catch {
|
|
164
|
+
ctx.ui.notify('Hub connection lost', 'error');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
await processActions(response.actions, ctx);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
log('Registration complete');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Helper to send event and process response actions
|
|
178
|
+
async function sendEvent(
|
|
179
|
+
eventName: string,
|
|
180
|
+
data: Record<string, unknown>,
|
|
181
|
+
ctx: ExtensionContext,
|
|
182
|
+
): Promise<unknown> {
|
|
183
|
+
if (!client.connected) return undefined;
|
|
184
|
+
|
|
185
|
+
const id = client.nextId();
|
|
186
|
+
let response: HubResponse;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
response = await client.send({
|
|
190
|
+
id,
|
|
191
|
+
type: 'event',
|
|
192
|
+
event: eventName,
|
|
193
|
+
data,
|
|
194
|
+
});
|
|
195
|
+
} catch {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const result = await processActions(response.actions, ctx);
|
|
200
|
+
|
|
201
|
+
if (result.block) return result.block;
|
|
202
|
+
if (result.returnValue !== undefined) return result.returnValue;
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Serialize event data — strip non-serializable fields
|
|
207
|
+
function serializeEvent(event: unknown): Record<string, unknown> {
|
|
208
|
+
const data: Record<string, unknown> = {};
|
|
209
|
+
if (event && typeof event === 'object') {
|
|
210
|
+
for (const [key, value] of Object.entries(event)) {
|
|
211
|
+
if (typeof value !== 'function' && key !== 'signal') {
|
|
212
|
+
try {
|
|
213
|
+
JSON.stringify(value);
|
|
214
|
+
data[key] = value;
|
|
215
|
+
} catch {
|
|
216
|
+
// Skip non-serializable values
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return data;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Cast pi.on to generic handler since we iterate over a union of event names
|
|
225
|
+
// TypeScript overloads require exact string literal matching which doesn't work in loops
|
|
226
|
+
const onEvent = pi.on.bind(pi) as GenericEventHandler;
|
|
227
|
+
|
|
228
|
+
// ── session_start: connect + register BEFORE Pi builds the tool list ──
|
|
229
|
+
onEvent('session_start', async (event: unknown, ctx: ExtensionContext) => {
|
|
230
|
+
await connectAndRegister();
|
|
231
|
+
|
|
232
|
+
if (client.connected) {
|
|
233
|
+
return sendEvent('session_start', serializeEvent(event), ctx);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
for (const eventName of PROXY_EVENTS) {
|
|
238
|
+
onEvent(eventName, async (event: unknown, ctx: ExtensionContext) => {
|
|
239
|
+
if (!client.connected) return undefined;
|
|
240
|
+
return sendEvent(eventName, serializeEvent(event), ctx);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Clean up on shutdown
|
|
245
|
+
pi.on('session_shutdown', async () => {
|
|
246
|
+
log('Shutting down');
|
|
247
|
+
client.close();
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default agentuityCoderHub;
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// ---- Init Message (Server → Client on connect) ----
|
|
2
|
+
|
|
3
|
+
export interface HubToolDefinition {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description: string;
|
|
7
|
+
parameters: Record<string, unknown>; // JSON Schema object
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface HubCommandDefinition {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface InitMessage {
|
|
16
|
+
type: 'init';
|
|
17
|
+
tools?: HubToolDefinition[];
|
|
18
|
+
commands?: HubCommandDefinition[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ---- Request Messages (Client → Server) ----
|
|
22
|
+
|
|
23
|
+
export interface EventRequest {
|
|
24
|
+
id: string;
|
|
25
|
+
type: 'event';
|
|
26
|
+
event: string;
|
|
27
|
+
data: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ToolRequest {
|
|
31
|
+
id: string;
|
|
32
|
+
type: 'tool';
|
|
33
|
+
name: string;
|
|
34
|
+
toolCallId: string;
|
|
35
|
+
params: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CommandRequest {
|
|
39
|
+
id: string;
|
|
40
|
+
type: 'command';
|
|
41
|
+
name: string;
|
|
42
|
+
args: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type HubRequest = EventRequest | ToolRequest | CommandRequest;
|
|
46
|
+
|
|
47
|
+
// ---- Actions ----
|
|
48
|
+
|
|
49
|
+
export interface AckAction {
|
|
50
|
+
action: 'ACK';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface BlockAction {
|
|
54
|
+
action: 'BLOCK';
|
|
55
|
+
reason: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ConfirmAction {
|
|
59
|
+
action: 'CONFIRM';
|
|
60
|
+
title: string;
|
|
61
|
+
message: string;
|
|
62
|
+
deny_reason?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface NotifyAction {
|
|
66
|
+
action: 'NOTIFY';
|
|
67
|
+
message: string;
|
|
68
|
+
level?: 'info' | 'warning' | 'error';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ReturnAction {
|
|
72
|
+
action: 'RETURN';
|
|
73
|
+
result: unknown;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface StatusAction {
|
|
77
|
+
action: 'STATUS';
|
|
78
|
+
key: string;
|
|
79
|
+
text?: string; // undefined = clear status
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type HubAction =
|
|
83
|
+
| AckAction
|
|
84
|
+
| BlockAction
|
|
85
|
+
| ConfirmAction
|
|
86
|
+
| NotifyAction
|
|
87
|
+
| ReturnAction
|
|
88
|
+
| StatusAction;
|
|
89
|
+
|
|
90
|
+
// ---- Response Message (Server → Client) ----
|
|
91
|
+
|
|
92
|
+
export interface HubResponse {
|
|
93
|
+
id: string;
|
|
94
|
+
actions: HubAction[];
|
|
95
|
+
}
|