@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.
Files changed (49) hide show
  1. package/dist/client.d.ts +2 -2
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +2 -0
  4. package/dist/client.js.map +1 -1
  5. package/dist/hub-overlay-state.d.ts +30 -0
  6. package/dist/hub-overlay-state.d.ts.map +1 -0
  7. package/dist/hub-overlay-state.js +68 -0
  8. package/dist/hub-overlay-state.js.map +1 -0
  9. package/dist/hub-overlay.d.ts +41 -2
  10. package/dist/hub-overlay.d.ts.map +1 -1
  11. package/dist/hub-overlay.js +667 -115
  12. package/dist/hub-overlay.js.map +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +19 -34
  15. package/dist/index.js.map +1 -1
  16. package/dist/native-remote-ui-context.d.ts +5 -0
  17. package/dist/native-remote-ui-context.d.ts.map +1 -0
  18. package/dist/native-remote-ui-context.js +30 -0
  19. package/dist/native-remote-ui-context.js.map +1 -0
  20. package/dist/protocol.d.ts +210 -38
  21. package/dist/protocol.d.ts.map +1 -1
  22. package/dist/protocol.js +2 -1
  23. package/dist/protocol.js.map +1 -1
  24. package/dist/remote-lifecycle.d.ts +61 -0
  25. package/dist/remote-lifecycle.d.ts.map +1 -0
  26. package/dist/remote-lifecycle.js +190 -0
  27. package/dist/remote-lifecycle.js.map +1 -0
  28. package/dist/remote-session.d.ts +15 -0
  29. package/dist/remote-session.d.ts.map +1 -1
  30. package/dist/remote-session.js +240 -35
  31. package/dist/remote-session.js.map +1 -1
  32. package/dist/remote-tui.d.ts.map +1 -1
  33. package/dist/remote-tui.js +95 -12
  34. package/dist/remote-tui.js.map +1 -1
  35. package/dist/remote-ui-handler.d.ts +5 -0
  36. package/dist/remote-ui-handler.d.ts.map +1 -0
  37. package/dist/remote-ui-handler.js +53 -0
  38. package/dist/remote-ui-handler.js.map +1 -0
  39. package/package.json +5 -5
  40. package/src/client.ts +5 -3
  41. package/src/hub-overlay-state.ts +117 -0
  42. package/src/hub-overlay.ts +974 -138
  43. package/src/index.ts +19 -50
  44. package/src/native-remote-ui-context.ts +41 -0
  45. package/src/protocol.ts +270 -56
  46. package/src/remote-lifecycle.ts +270 -0
  47. package/src/remote-session.ts +293 -38
  48. package/src/remote-tui.ts +129 -13
  49. 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
- // TODO: Bridge to InteractiveMode's extension UI context
464
- log(`UI request: ${request.method} (no handler yet)`);
465
- const fireAndForget = ['notify', 'setStatus', 'setWidget', 'setTitle', 'set_editor_text'];
466
- if (fireAndForget.includes(request.method)) return undefined;
467
- return null;
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 = resolve;
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
- setTimeout(() => {
662
- log('Hydration timeout no session_hydration received');
663
- resolve();
664
- }, HYDRATION_TIMEOUT_MS)
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
+ }