@agentuity/coder 1.0.39 → 1.0.41
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 +2 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -1
- package/dist/hub-overlay-state.d.ts +30 -0
- package/dist/hub-overlay-state.d.ts.map +1 -0
- package/dist/hub-overlay-state.js +68 -0
- package/dist/hub-overlay-state.js.map +1 -0
- package/dist/hub-overlay.d.ts +41 -2
- package/dist/hub-overlay.d.ts.map +1 -1
- package/dist/hub-overlay.js +667 -115
- package/dist/hub-overlay.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -34
- package/dist/index.js.map +1 -1
- package/dist/native-remote-ui-context.d.ts +5 -0
- package/dist/native-remote-ui-context.d.ts.map +1 -0
- package/dist/native-remote-ui-context.js +30 -0
- package/dist/native-remote-ui-context.js.map +1 -0
- package/dist/protocol.d.ts +210 -38
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/dist/remote-lifecycle.d.ts +61 -0
- package/dist/remote-lifecycle.d.ts.map +1 -0
- package/dist/remote-lifecycle.js +190 -0
- package/dist/remote-lifecycle.js.map +1 -0
- package/dist/remote-session.d.ts +15 -0
- package/dist/remote-session.d.ts.map +1 -1
- package/dist/remote-session.js +240 -35
- package/dist/remote-session.js.map +1 -1
- package/dist/remote-tui.d.ts.map +1 -1
- package/dist/remote-tui.js +95 -12
- package/dist/remote-tui.js.map +1 -1
- package/dist/remote-ui-handler.d.ts +5 -0
- package/dist/remote-ui-handler.d.ts.map +1 -0
- package/dist/remote-ui-handler.js +53 -0
- package/dist/remote-ui-handler.js.map +1 -0
- package/package.json +5 -5
- package/src/client.ts +5 -3
- package/src/hub-overlay-state.ts +117 -0
- package/src/hub-overlay.ts +974 -138
- package/src/index.ts +19 -50
- package/src/native-remote-ui-context.ts +41 -0
- package/src/protocol.ts +270 -56
- package/src/remote-lifecycle.ts +270 -0
- package/src/remote-session.ts +293 -38
- package/src/remote-tui.ts +129 -13
- package/src/remote-ui-handler.ts +86 -0
package/src/remote-tui.ts
CHANGED
|
@@ -33,9 +33,22 @@ import {
|
|
|
33
33
|
InteractiveMode,
|
|
34
34
|
SessionManager,
|
|
35
35
|
} from '@mariozechner/pi-coding-agent';
|
|
36
|
+
import {
|
|
37
|
+
getNativeRemoteExtensionContext,
|
|
38
|
+
setNativeRemoteExtensionContext,
|
|
39
|
+
waitForNativeRemoteExtensionContext,
|
|
40
|
+
} from './native-remote-ui-context.ts';
|
|
41
|
+
import {
|
|
42
|
+
clearRemoteLifecycleWorkingMessage,
|
|
43
|
+
getRemoteLifecycleActivityLabel,
|
|
44
|
+
getRemoteLifecycleLabel,
|
|
45
|
+
syncRemoteLifecycleWorkingMessage,
|
|
46
|
+
type RemoteLifecycleState,
|
|
47
|
+
} from './remote-lifecycle.ts';
|
|
36
48
|
import { RemoteSession } from './remote-session.ts';
|
|
37
49
|
import type { RpcEvent } from './remote-session.ts';
|
|
38
50
|
import { agentuityCoderHub } from './index.ts';
|
|
51
|
+
import { handleRemoteUiRequest, REMOTE_FIRE_AND_FORGET_UI_METHODS } from './remote-ui-handler.ts';
|
|
39
52
|
|
|
40
53
|
const DEBUG = !!process.env['AGENTUITY_DEBUG'];
|
|
41
54
|
|
|
@@ -63,6 +76,7 @@ export async function runRemoteTui(options: {
|
|
|
63
76
|
process.env.AGENTUITY_CODER_HUB_URL = hubWsUrl;
|
|
64
77
|
process.env.AGENTUITY_CODER_REMOTE_SESSION = sessionId;
|
|
65
78
|
process.env.AGENTUITY_CODER_NATIVE_REMOTE = '1';
|
|
79
|
+
setNativeRemoteExtensionContext(null);
|
|
66
80
|
|
|
67
81
|
// ── 1. Create RemoteSession (NOT connected yet) ──
|
|
68
82
|
// We register all handlers BEFORE connecting so that the hydration
|
|
@@ -71,6 +85,7 @@ export async function runRemoteTui(options: {
|
|
|
71
85
|
// TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
|
|
72
86
|
remote.apiKey = process.env.AGENTUITY_CODER_API_KEY || null;
|
|
73
87
|
let hydrationStreamingDetected = false;
|
|
88
|
+
let sessionResumeSeen = false;
|
|
74
89
|
|
|
75
90
|
// ── 2. Create AgentSession with coder extension loaded ──
|
|
76
91
|
// The extension provides Hub UI (footer, /hub overlay, commands, titlebar).
|
|
@@ -96,6 +111,41 @@ export async function runRemoteTui(options: {
|
|
|
96
111
|
|
|
97
112
|
// Access the Agent instance (typed as `any` for monkey-patching)
|
|
98
113
|
const agent: any = session.agent;
|
|
114
|
+
let lifecycleState = remote.getLifecycleState();
|
|
115
|
+
let lifecycleOwnsWorkingMessage = false;
|
|
116
|
+
|
|
117
|
+
function applyLifecycleUi(state: RemoteLifecycleState): void {
|
|
118
|
+
const ctx = getNativeRemoteExtensionContext();
|
|
119
|
+
if (!ctx?.hasUI) return;
|
|
120
|
+
|
|
121
|
+
const shortSession = state.sessionId.slice(0, 16);
|
|
122
|
+
ctx.ui.setStatus(
|
|
123
|
+
'remote_connection',
|
|
124
|
+
`Remote: ${shortSession}${shortSession.length < state.sessionId.length ? '...' : ''} ${getRemoteLifecycleLabel(state)}`
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const activity = getRemoteLifecycleActivityLabel(state);
|
|
128
|
+
if (activity) {
|
|
129
|
+
ctx.ui.setStatus('remote_activity', activity);
|
|
130
|
+
} else {
|
|
131
|
+
ctx.ui.setStatus('remote_activity', state.isStreaming ? 'agent working...' : 'idle');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
lifecycleOwnsWorkingMessage = syncRemoteLifecycleWorkingMessage(
|
|
135
|
+
state,
|
|
136
|
+
ctx.ui,
|
|
137
|
+
lifecycleOwnsWorkingMessage
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
remote.onLifecycleChange((state) => {
|
|
142
|
+
lifecycleState = state;
|
|
143
|
+
applyLifecycleUi(state);
|
|
144
|
+
});
|
|
145
|
+
void waitForNativeRemoteExtensionContext(10_000).then((ctx) => {
|
|
146
|
+
if (!ctx) return;
|
|
147
|
+
applyLifecycleUi(lifecycleState);
|
|
148
|
+
});
|
|
99
149
|
|
|
100
150
|
// ── 3. Patch Agent to be remote-backed ──
|
|
101
151
|
// Track the running prompt promise so InteractiveMode waits correctly
|
|
@@ -274,6 +324,43 @@ export async function runRemoteTui(options: {
|
|
|
274
324
|
(rpcEvent as any).isReplay === true;
|
|
275
325
|
log(`Event received: ${rpcEvent.type} (source=${source})`);
|
|
276
326
|
|
|
327
|
+
if (rpcEvent.type === 'session_resume') {
|
|
328
|
+
sessionResumeSeen = true;
|
|
329
|
+
log(
|
|
330
|
+
`Session resume signaled (${typeof (rpcEvent as any).streamId === 'string' ? (rpcEvent as any).streamId : 'no stream id'})`
|
|
331
|
+
);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (rpcEvent.type === 'session_stream_ready') {
|
|
336
|
+
log(
|
|
337
|
+
`Durable stream ready (${typeof (rpcEvent as any).streamId === 'string' ? (rpcEvent as any).streamId : 'no stream id'})`
|
|
338
|
+
);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (rpcEvent.type === 'rpc_command_error') {
|
|
343
|
+
const error =
|
|
344
|
+
typeof (rpcEvent as any).error === 'string'
|
|
345
|
+
? (rpcEvent as any).error
|
|
346
|
+
: 'Remote command failed';
|
|
347
|
+
const ctx = getNativeRemoteExtensionContext();
|
|
348
|
+
if (ctx?.hasUI) {
|
|
349
|
+
ctx.ui.notify(error, 'warning');
|
|
350
|
+
lifecycleOwnsWorkingMessage = clearRemoteLifecycleWorkingMessage(
|
|
351
|
+
ctx.ui,
|
|
352
|
+
lifecycleOwnsWorkingMessage
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
agent._state.error = error;
|
|
356
|
+
seenAgentStart = false;
|
|
357
|
+
seenMessageStart = false;
|
|
358
|
+
resolveRunningPrompt();
|
|
359
|
+
assistantStreamActive = false;
|
|
360
|
+
log(`Remote command error: ${error}`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
277
364
|
// session_hydration is handled separately below — skip it here
|
|
278
365
|
if (rpcEvent.type === 'session_hydration') return;
|
|
279
366
|
|
|
@@ -460,11 +547,23 @@ export async function runRemoteTui(options: {
|
|
|
460
547
|
|
|
461
548
|
// ── 6. Wire up UI handlers for extension dialogs from sandbox ──
|
|
462
549
|
remote.setUiHandler(async (request) => {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
550
|
+
const ctx =
|
|
551
|
+
getNativeRemoteExtensionContext() ?? (await waitForNativeRemoteExtensionContext(10_000));
|
|
552
|
+
if (!ctx) {
|
|
553
|
+
log(
|
|
554
|
+
`UI request: ${request.method} (${request.id}) timed out waiting for extension UI context`
|
|
555
|
+
);
|
|
556
|
+
return REMOTE_FIRE_AND_FORGET_UI_METHODS.has(request.method) ? undefined : null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
return await handleRemoteUiRequest(ctx, request);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
log(
|
|
563
|
+
`UI request handler error for ${request.method}: ${err instanceof Error ? err.message : String(err)}`
|
|
564
|
+
);
|
|
565
|
+
return REMOTE_FIRE_AND_FORGET_UI_METHODS.has(request.method) ? undefined : null;
|
|
566
|
+
}
|
|
468
567
|
});
|
|
469
568
|
|
|
470
569
|
// ── 7. Handle hydration (initial state from Hub) ──
|
|
@@ -474,8 +573,13 @@ export async function runRemoteTui(options: {
|
|
|
474
573
|
// InteractiveMode (which calls renderInitialMessages from SessionManager).
|
|
475
574
|
const sm = session.sessionManager;
|
|
476
575
|
let resolveHydration: () => void;
|
|
576
|
+
let hydrationComplete = false;
|
|
477
577
|
const hydrationReady = new Promise<void>((resolve) => {
|
|
478
|
-
resolveHydration =
|
|
578
|
+
resolveHydration = () => {
|
|
579
|
+
if (hydrationComplete) return;
|
|
580
|
+
hydrationComplete = true;
|
|
581
|
+
resolve();
|
|
582
|
+
};
|
|
479
583
|
});
|
|
480
584
|
|
|
481
585
|
let hydrationCount = 0;
|
|
@@ -654,15 +758,25 @@ export async function runRemoteTui(options: {
|
|
|
654
758
|
|
|
655
759
|
// Wait for hydration message (arrives right after init), with a timeout
|
|
656
760
|
// in case this is the first connection and there's nothing to hydrate.
|
|
657
|
-
const HYDRATION_TIMEOUT_MS = 2000;
|
|
658
761
|
await Promise.race([
|
|
659
762
|
hydrationReady,
|
|
660
|
-
new Promise<void>((resolve) =>
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
763
|
+
new Promise<void>((resolve) => {
|
|
764
|
+
const waitStartedAt = Date.now();
|
|
765
|
+
const poll = (): void => {
|
|
766
|
+
if (hydrationComplete) {
|
|
767
|
+
resolve();
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const timeoutMs = sessionResumeSeen ? 5000 : 2000;
|
|
771
|
+
if (Date.now() - waitStartedAt >= timeoutMs) {
|
|
772
|
+
log('Hydration timeout — no session_hydration received');
|
|
773
|
+
resolve();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
setTimeout(poll, 50);
|
|
777
|
+
};
|
|
778
|
+
poll();
|
|
779
|
+
}),
|
|
666
780
|
]);
|
|
667
781
|
const smEntries = sm.getEntries?.() ?? [];
|
|
668
782
|
log(`SessionManager has ${smEntries.length} entries after hydration`);
|
|
@@ -732,6 +846,7 @@ export async function runRemoteTui(options: {
|
|
|
732
846
|
// Handle clean shutdown
|
|
733
847
|
const cleanup = () => {
|
|
734
848
|
remote.close();
|
|
849
|
+
setNativeRemoteExtensionContext(null);
|
|
735
850
|
interactive.stop();
|
|
736
851
|
};
|
|
737
852
|
process.on('SIGINT', cleanup);
|
|
@@ -744,6 +859,7 @@ export async function runRemoteTui(options: {
|
|
|
744
859
|
throw err;
|
|
745
860
|
} finally {
|
|
746
861
|
remote.close();
|
|
862
|
+
setNativeRemoteExtensionContext(null);
|
|
747
863
|
log('Remote TUI exited');
|
|
748
864
|
}
|
|
749
865
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ExtensionContext } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import type { RpcUiRequest } from './remote-session.ts';
|
|
3
|
+
|
|
4
|
+
export const REMOTE_FIRE_AND_FORGET_UI_METHODS = new Set([
|
|
5
|
+
'notify',
|
|
6
|
+
'setStatus',
|
|
7
|
+
'setWidget',
|
|
8
|
+
'setTitle',
|
|
9
|
+
'set_editor_text',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export async function handleRemoteUiRequest(
|
|
13
|
+
ctx: ExtensionContext,
|
|
14
|
+
request: RpcUiRequest
|
|
15
|
+
): Promise<unknown> {
|
|
16
|
+
if (!ctx.hasUI) {
|
|
17
|
+
return REMOTE_FIRE_AND_FORGET_UI_METHODS.has(request.method) ? undefined : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ui = ctx.ui;
|
|
21
|
+
|
|
22
|
+
switch (request.method) {
|
|
23
|
+
case 'select': {
|
|
24
|
+
const options = Array.isArray(request.params.options)
|
|
25
|
+
? request.params.options.filter(
|
|
26
|
+
(option): option is string => typeof option === 'string'
|
|
27
|
+
)
|
|
28
|
+
: [];
|
|
29
|
+
const title = (request.params.title as string) ?? 'Select';
|
|
30
|
+
return (await ui.select(title, options)) ?? null;
|
|
31
|
+
}
|
|
32
|
+
case 'confirm':
|
|
33
|
+
return await ui.confirm(
|
|
34
|
+
(request.params.title as string) ?? 'Confirm?',
|
|
35
|
+
(request.params.message as string) ?? 'Confirm?'
|
|
36
|
+
);
|
|
37
|
+
case 'input':
|
|
38
|
+
return (
|
|
39
|
+
(await ui.input(
|
|
40
|
+
(request.params.title as string) ?? 'Input',
|
|
41
|
+
(request.params.placeholder as string) ?? ''
|
|
42
|
+
)) ?? null
|
|
43
|
+
);
|
|
44
|
+
case 'editor':
|
|
45
|
+
return (
|
|
46
|
+
(await ui.editor(
|
|
47
|
+
(request.params.title as string) ?? 'Editor',
|
|
48
|
+
(request.params.prefill as string) ?? ''
|
|
49
|
+
)) ?? null
|
|
50
|
+
);
|
|
51
|
+
case 'notify':
|
|
52
|
+
ui.notify(
|
|
53
|
+
(request.params.message as string) ?? '',
|
|
54
|
+
(request.params.notifyType as 'info' | 'warning' | 'error' | undefined) ?? 'info'
|
|
55
|
+
);
|
|
56
|
+
return undefined;
|
|
57
|
+
case 'setStatus':
|
|
58
|
+
ui.setStatus(
|
|
59
|
+
(request.params.statusKey as string) ?? 'remote',
|
|
60
|
+
(request.params.statusText as string | undefined) ?? undefined
|
|
61
|
+
);
|
|
62
|
+
return undefined;
|
|
63
|
+
case 'setWidget': {
|
|
64
|
+
const widgetLines = Array.isArray(request.params.widgetLines)
|
|
65
|
+
? request.params.widgetLines.filter((line): line is string => typeof line === 'string')
|
|
66
|
+
: undefined;
|
|
67
|
+
const widgetPlacement = request.params.widgetPlacement;
|
|
68
|
+
ui.setWidget(
|
|
69
|
+
(request.params.widgetKey as string) ?? 'remote_widget',
|
|
70
|
+
widgetLines,
|
|
71
|
+
widgetPlacement === 'aboveEditor' || widgetPlacement === 'belowEditor'
|
|
72
|
+
? { placement: widgetPlacement }
|
|
73
|
+
: undefined
|
|
74
|
+
);
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
case 'setTitle':
|
|
78
|
+
ui.setTitle((request.params.title as string) ?? '');
|
|
79
|
+
return undefined;
|
|
80
|
+
case 'set_editor_text':
|
|
81
|
+
ui.setEditorText((request.params.text as string) ?? '');
|
|
82
|
+
return undefined;
|
|
83
|
+
default:
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|