@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.
@@ -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":"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"}
@@ -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"}
@@ -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"}
@@ -0,0 +1,4 @@
1
+ import type { ExtensionAPI } from '@mariozechner/pi-coding-agent';
2
+ export declare function agentuityCoderHub(pi: ExtensionAPI): void;
3
+ export default agentuityCoderHub;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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":"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"}
@@ -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"}
@@ -0,0 +1,3 @@
1
+ // ---- Init Message (Server → Client on connect) ----
2
+ export {};
3
+ //# sourceMappingURL=protocol.js.map
@@ -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
+ }
@@ -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;
@@ -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
+ }