@agentuity/coder-tui 2.0.10 → 2.0.12
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/agentuity-cli.d.ts +12 -0
- package/dist/agentuity-cli.d.ts.map +1 -0
- package/dist/agentuity-cli.js +178 -0
- package/dist/agentuity-cli.js.map +1 -0
- package/dist/auth.d.ts +5 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +62 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -6
- package/dist/client.js.map +1 -1
- package/dist/hub-overlay-state.d.ts.map +1 -1
- package/dist/hub-overlay-state.js +3 -1
- package/dist/hub-overlay-state.js.map +1 -1
- package/dist/hub-overlay.d.ts.map +1 -1
- package/dist/hub-overlay.js +18 -12
- package/dist/hub-overlay.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -41
- package/dist/index.js.map +1 -1
- package/dist/local-init-filter.d.ts +5 -0
- package/dist/local-init-filter.d.ts.map +1 -0
- package/dist/local-init-filter.js +40 -0
- package/dist/local-init-filter.js.map +1 -0
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/remote-runtime.d.ts +16 -0
- package/dist/remote-runtime.d.ts.map +1 -0
- package/dist/remote-runtime.js +18 -0
- package/dist/remote-runtime.js.map +1 -0
- package/dist/remote-session.d.ts.map +1 -1
- package/dist/remote-session.js +17 -9
- package/dist/remote-session.js.map +1 -1
- package/dist/remote-tui.d.ts +4 -4
- package/dist/remote-tui.d.ts.map +1 -1
- package/dist/remote-tui.js +72 -27
- package/dist/remote-tui.js.map +1 -1
- package/dist/renderers.d.ts.map +1 -1
- package/dist/renderers.js +53 -1
- package/dist/renderers.js.map +1 -1
- package/dist/subagent-tool-selection.d.ts +3 -0
- package/dist/subagent-tool-selection.d.ts.map +1 -0
- package/dist/subagent-tool-selection.js +22 -0
- package/dist/subagent-tool-selection.js.map +1 -0
- package/package.json +5 -5
- package/src/agentuity-cli.ts +225 -0
- package/src/auth.ts +75 -0
- package/src/client.ts +6 -6
- package/src/hub-overlay-state.ts +4 -1
- package/src/hub-overlay.ts +28 -14
- package/src/index.ts +53 -41
- package/src/local-init-filter.ts +54 -0
- package/src/protocol.ts +4 -0
- package/src/remote-runtime.ts +45 -0
- package/src/remote-session.ts +17 -9
- package/src/remote-tui.ts +92 -32
- package/src/renderers.ts +61 -1
- package/src/subagent-tool-selection.ts +33 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AgentDefinition, HubToolDefinition, InitMessage } from './protocol.ts';
|
|
2
|
+
|
|
3
|
+
const LOCAL_TUI_HIDDEN_HUB_TOOL_NAMES = new Set(['sandbox_exec']);
|
|
4
|
+
|
|
5
|
+
function filterHubToolsForLocalTui(tools?: HubToolDefinition[]): HubToolDefinition[] | undefined {
|
|
6
|
+
if (!tools) return tools;
|
|
7
|
+
|
|
8
|
+
const filtered = tools.filter((tool) => !LOCAL_TUI_HIDDEN_HUB_TOOL_NAMES.has(tool.name));
|
|
9
|
+
return filtered.length === tools.length ? tools : filtered;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function filterAgentHubToolsForLocalTui(agents?: AgentDefinition[]): AgentDefinition[] | undefined {
|
|
13
|
+
if (!agents) return agents;
|
|
14
|
+
|
|
15
|
+
let changed = false;
|
|
16
|
+
const filteredAgents = agents.map((agent) => {
|
|
17
|
+
const filteredHubTools = filterHubToolsForLocalTui(agent.hubTools);
|
|
18
|
+
if (filteredHubTools === agent.hubTools) {
|
|
19
|
+
return agent;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
changed = true;
|
|
23
|
+
return {
|
|
24
|
+
...agent,
|
|
25
|
+
hubTools: filteredHubTools && filteredHubTools.length > 0 ? filteredHubTools : undefined,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return changed ? filteredAgents : agents;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function adaptInitMessageForLocalTui(
|
|
33
|
+
init: InitMessage,
|
|
34
|
+
options: {
|
|
35
|
+
isRemoteSession: boolean;
|
|
36
|
+
}
|
|
37
|
+
): InitMessage {
|
|
38
|
+
if (options.isRemoteSession) {
|
|
39
|
+
return init;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tools = filterHubToolsForLocalTui(init.tools);
|
|
43
|
+
const agents = filterAgentHubToolsForLocalTui(init.agents);
|
|
44
|
+
|
|
45
|
+
if (tools === init.tools && agents === init.agents) {
|
|
46
|
+
return init;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...init,
|
|
51
|
+
tools,
|
|
52
|
+
agents,
|
|
53
|
+
};
|
|
54
|
+
}
|
package/src/protocol.ts
CHANGED
|
@@ -75,6 +75,7 @@ export type HubAction =
|
|
|
75
75
|
export interface AgentDefinition {
|
|
76
76
|
name: string;
|
|
77
77
|
displayName?: string;
|
|
78
|
+
source?: 'builtin' | 'custom';
|
|
78
79
|
description: string;
|
|
79
80
|
systemPrompt: string;
|
|
80
81
|
model?: string;
|
|
@@ -84,6 +85,7 @@ export interface AgentDefinition {
|
|
|
84
85
|
readOnly?: boolean;
|
|
85
86
|
hubTools?: HubToolDefinition[];
|
|
86
87
|
capabilities?: string[];
|
|
88
|
+
strictToolSelection?: boolean;
|
|
87
89
|
status?: 'available' | 'busy' | 'offline';
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -381,6 +383,7 @@ export interface SessionListItem {
|
|
|
381
383
|
participantCount: number;
|
|
382
384
|
tags: string[];
|
|
383
385
|
skills: SessionSkillRef[];
|
|
386
|
+
enabledAgents: string[];
|
|
384
387
|
defaultAgent?: string;
|
|
385
388
|
bucket: SessionBucket;
|
|
386
389
|
runtimeAvailable: boolean;
|
|
@@ -529,6 +532,7 @@ export interface SessionSnapshotMetadataExtensions {
|
|
|
529
532
|
product?: SessionProductProjection;
|
|
530
533
|
tags: string[];
|
|
531
534
|
skills: SessionSkillRef[];
|
|
535
|
+
enabledAgents: string[];
|
|
532
536
|
defaultAgent?: string;
|
|
533
537
|
workers?: SessionListItem[];
|
|
534
538
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { RpcEvent } from './remote-session.ts';
|
|
2
|
+
|
|
3
|
+
export const REMOTE_RUNTIME_OPERATION_MESSAGE =
|
|
4
|
+
'Remote controller mode only supports the current sandbox session.';
|
|
5
|
+
|
|
6
|
+
type RemoteRuntimeOperation = '/new' | '/resume' | '/fork' | '/import';
|
|
7
|
+
|
|
8
|
+
interface RemoteRuntimeGuards {
|
|
9
|
+
newSession?: () => Promise<unknown>;
|
|
10
|
+
switchSession?: () => Promise<unknown>;
|
|
11
|
+
fork?: () => Promise<unknown>;
|
|
12
|
+
importFromJsonl?: () => Promise<unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface RemoteSessionEventTarget {
|
|
16
|
+
_handleAgentEvent?: (event: RpcEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function dispatchRemoteSessionEvent(
|
|
20
|
+
session: RemoteSessionEventTarget,
|
|
21
|
+
event: RpcEvent
|
|
22
|
+
): void {
|
|
23
|
+
if (typeof session._handleAgentEvent !== 'function') {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'Pi AgentSession no longer exposes _handleAgentEvent; remote TUI event dispatch needs an update.'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
session._handleAgentEvent(event);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function installRemoteRuntimeOperationGuards(
|
|
33
|
+
runtime: RemoteRuntimeGuards,
|
|
34
|
+
onBlocked: (operation: RemoteRuntimeOperation) => void
|
|
35
|
+
): void {
|
|
36
|
+
const block = (operation: RemoteRuntimeOperation) => async (): Promise<{ cancelled: true }> => {
|
|
37
|
+
onBlocked(operation);
|
|
38
|
+
return { cancelled: true };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
runtime.newSession = block('/new');
|
|
42
|
+
runtime.switchSession = block('/resume');
|
|
43
|
+
runtime.fork = block('/fork');
|
|
44
|
+
runtime.importFromJsonl = block('/import');
|
|
45
|
+
}
|
package/src/remote-session.ts
CHANGED
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
syncRemoteLifecycleWorkingMessage,
|
|
23
23
|
type RemoteLifecycleState,
|
|
24
24
|
} from './remote-lifecycle.ts';
|
|
25
|
+
import { resolveCoderOrgId } from './auth.ts';
|
|
26
|
+
import { formatToolDisplay } from './agentuity-cli.ts';
|
|
25
27
|
|
|
26
28
|
const DEBUG = !!process.env['AGENTUITY_DEBUG'];
|
|
27
29
|
|
|
@@ -267,9 +269,10 @@ export class RemoteSession {
|
|
|
267
269
|
wsHeaders['Authorization'] = `Bearer ${this.apiKey}`;
|
|
268
270
|
}
|
|
269
271
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
const orgId = resolveCoderOrgId(this.orgId);
|
|
273
|
+
if (orgId) {
|
|
274
|
+
url.searchParams.set('orgId', orgId);
|
|
275
|
+
wsHeaders['x-agentuity-orgid'] = orgId;
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
log(`${isReconnect ? 'Reconnecting' : 'Connecting'} to ${url.toString()}`);
|
|
@@ -999,18 +1002,23 @@ export async function setupRemoteMode(
|
|
|
999
1002
|
break;
|
|
1000
1003
|
|
|
1001
1004
|
case 'tool_execution_start': {
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1005
|
+
const rawTool = (event as { toolName?: string }).toolName ?? 'tool';
|
|
1006
|
+
const rawArgs = (event as { args?: string | Record<string, unknown> }).args;
|
|
1007
|
+
const display = formatToolDisplay(rawTool, rawArgs);
|
|
1008
|
+
currentTool = display.toolName;
|
|
1004
1009
|
if (extensionCtxRef?.hasUI) {
|
|
1005
|
-
setNonLifecycleWorkingMessage(`Running ${
|
|
1006
|
-
extensionCtxRef.ui.setStatus('remote_activity', `Running ${
|
|
1010
|
+
setNonLifecycleWorkingMessage(`Running ${display.fullLabel}...`);
|
|
1011
|
+
extensionCtxRef.ui.setStatus('remote_activity', `Running ${display.fullLabel}...`);
|
|
1007
1012
|
}
|
|
1008
|
-
log(`Tool: ${
|
|
1013
|
+
log(`Tool: ${display.fullLabel}`);
|
|
1009
1014
|
break;
|
|
1010
1015
|
}
|
|
1011
1016
|
|
|
1012
1017
|
case 'tool_execution_end': {
|
|
1013
|
-
const
|
|
1018
|
+
const rawTool = (event as { toolName?: string }).toolName ?? currentTool ?? 'tool';
|
|
1019
|
+
const rawArgs = (event as { args?: string | Record<string, unknown> }).args;
|
|
1020
|
+
const display = rawArgs ? formatToolDisplay(rawTool, rawArgs) : null;
|
|
1021
|
+
const tool = display?.fullLabel ?? currentTool ?? rawTool;
|
|
1014
1022
|
currentTool = null;
|
|
1015
1023
|
if (extensionCtxRef?.hasUI) {
|
|
1016
1024
|
clearWorkingMessage();
|
package/src/remote-tui.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Remote TUI — Native Pi Coding Agent Renderer for Remote Sessions
|
|
3
3
|
*
|
|
4
|
-
* Creates a real
|
|
4
|
+
* Creates a real AgentSessionRuntime + InteractiveMode backed by a remote sandbox
|
|
5
5
|
* via Hub WebSocket, with the coder extension loaded for Hub UI (footer,
|
|
6
6
|
* /hub overlay, commands, titlebar).
|
|
7
7
|
*
|
|
8
8
|
* Architecture:
|
|
9
9
|
* Remote TUI → Hub WebSocket (controller) → Sandbox (Pi RPC mode)
|
|
10
10
|
* - User input → agent.prompt() (monkey-patched) → RPC `prompt` → Hub → sandbox
|
|
11
|
-
* - Sandbox Pi → AgentEvent stream → Hub broadcast →
|
|
11
|
+
* - Sandbox Pi → AgentEvent stream → Hub broadcast → AgentSession event handling → InteractiveMode renders natively
|
|
12
12
|
* - Hub UI → coder extension (loaded via DefaultResourceLoader) provides footer, /hub, commands
|
|
13
13
|
*
|
|
14
14
|
* The local Agent never calls an LLM. Its prompt/steer/abort are monkey-patched
|
|
@@ -22,14 +22,16 @@
|
|
|
22
22
|
*
|
|
23
23
|
* IMPORTANT: Initialization order matters!
|
|
24
24
|
* 1. Create RemoteSession (no connection yet)
|
|
25
|
-
* 2. Create
|
|
25
|
+
* 2. Create AgentSessionRuntime, patch Agent/Session methods
|
|
26
26
|
* 3. Register ALL event handlers on RemoteSession
|
|
27
27
|
* 4. THEN connect — so hydration + replay events are captured
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
createAgentSessionFromServices,
|
|
32
|
+
createAgentSessionRuntime,
|
|
33
|
+
createAgentSessionServices,
|
|
34
|
+
getAgentDir,
|
|
33
35
|
InteractiveMode,
|
|
34
36
|
SessionManager,
|
|
35
37
|
} from '@mariozechner/pi-coding-agent';
|
|
@@ -49,6 +51,11 @@ import { RemoteSession } from './remote-session.ts';
|
|
|
49
51
|
import type { RpcEvent } from './remote-session.ts';
|
|
50
52
|
import { agentuityCoderHub } from './index.ts';
|
|
51
53
|
import { handleRemoteUiRequest, REMOTE_FIRE_AND_FORGET_UI_METHODS } from './remote-ui-handler.ts';
|
|
54
|
+
import {
|
|
55
|
+
dispatchRemoteSessionEvent,
|
|
56
|
+
installRemoteRuntimeOperationGuards,
|
|
57
|
+
REMOTE_RUNTIME_OPERATION_MESSAGE,
|
|
58
|
+
} from './remote-runtime.ts';
|
|
52
59
|
|
|
53
60
|
const DEBUG = !!process.env['AGENTUITY_DEBUG'];
|
|
54
61
|
|
|
@@ -60,7 +67,7 @@ function log(msg: string): void {
|
|
|
60
67
|
* Run the native Pi TUI connected to a remote sandbox session.
|
|
61
68
|
*
|
|
62
69
|
* This is the entry point for `agentuity coder start --remote <sessionId>`.
|
|
63
|
-
* Creates an
|
|
70
|
+
* Creates an AgentSessionRuntime with the coder extension loaded (Hub UI), then
|
|
64
71
|
* monkey-patches the Agent for remote-backed execution.
|
|
65
72
|
*/
|
|
66
73
|
export async function runRemoteTui(options: {
|
|
@@ -90,33 +97,74 @@ export async function runRemoteTui(options: {
|
|
|
90
97
|
let hydrationStreamingDetected = false;
|
|
91
98
|
let sessionResumeSeen = false;
|
|
92
99
|
|
|
93
|
-
// ── 2. Create
|
|
100
|
+
// ── 2. Create AgentSessionRuntime with coder extension loaded ──
|
|
94
101
|
// The extension provides Hub UI (footer, /hub overlay, commands, titlebar).
|
|
95
102
|
// AGENTUITY_CODER_NATIVE_REMOTE=1 tells it to skip legacy event rendering.
|
|
96
103
|
const cwd = process.cwd();
|
|
97
|
-
const
|
|
98
|
-
cwd,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
const runtime = await createAgentSessionRuntime(
|
|
105
|
+
async ({ cwd, agentDir, sessionManager, sessionStartEvent }) => {
|
|
106
|
+
const services = await createAgentSessionServices({
|
|
107
|
+
cwd,
|
|
108
|
+
agentDir,
|
|
109
|
+
resourceLoaderOptions: {
|
|
110
|
+
noExtensions: true, // Skip file-system extension discovery
|
|
111
|
+
extensionFactories: [agentuityCoderHub], // Load coder extension directly
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
...(await createAgentSessionFromServices({
|
|
117
|
+
services,
|
|
118
|
+
sessionManager,
|
|
119
|
+
sessionStartEvent,
|
|
120
|
+
tools: [], // No local tools — sandbox has all the tools
|
|
121
|
+
})),
|
|
122
|
+
services,
|
|
123
|
+
diagnostics: services.diagnostics,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
cwd,
|
|
128
|
+
agentDir: getAgentDir(),
|
|
129
|
+
sessionManager: SessionManager.inMemory(),
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
const session = runtime.session;
|
|
133
|
+
const agent: any = session.agent;
|
|
134
|
+
let runtimeDisposed = false;
|
|
135
|
+
log('AgentSessionRuntime created');
|
|
103
136
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
function notifyBlockedRuntimeOperation(operation: string): void {
|
|
138
|
+
const message = `${REMOTE_RUNTIME_OPERATION_MESSAGE} (${operation})`;
|
|
139
|
+
const ctx = getNativeRemoteExtensionContext();
|
|
140
|
+
if (ctx?.hasUI) {
|
|
141
|
+
ctx.ui.notify(message, 'warning');
|
|
142
|
+
ctx.ui.setStatus('remote_runtime', operation);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
log(message);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
installRemoteRuntimeOperationGuards(runtime as any, (operation) =>
|
|
149
|
+
notifyBlockedRuntimeOperation(operation)
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
async function disposeRuntime(): Promise<void> {
|
|
153
|
+
if (runtimeDisposed) return;
|
|
154
|
+
runtimeDisposed = true;
|
|
155
|
+
await runtime.dispose();
|
|
156
|
+
}
|
|
110
157
|
|
|
111
158
|
// NOTE: Do NOT call session.bindExtensions() here.
|
|
112
159
|
// InteractiveMode.initExtensions() calls it with the proper uiContext.
|
|
113
160
|
// Calling it early fires session_start twice, duplicating extension init.
|
|
114
|
-
|
|
115
|
-
// Access the Agent instance (typed as `any` for monkey-patching)
|
|
116
|
-
const agent: any = session.agent;
|
|
117
161
|
let lifecycleState = remote.getLifecycleState();
|
|
118
162
|
let lifecycleOwnsWorkingMessage = false;
|
|
119
163
|
|
|
164
|
+
function emitSessionEvent(event: RpcEvent): void {
|
|
165
|
+
dispatchRemoteSessionEvent(session as any, event);
|
|
166
|
+
}
|
|
167
|
+
|
|
120
168
|
function applyLifecycleUi(state: RemoteLifecycleState): void {
|
|
121
169
|
const ctx = getNativeRemoteExtensionContext();
|
|
122
170
|
if (!ctx?.hasUI) return;
|
|
@@ -174,7 +222,7 @@ export async function runRemoteTui(options: {
|
|
|
174
222
|
|
|
175
223
|
// Emit synthetic agent_start so InteractiveMode shows "working" immediately
|
|
176
224
|
syntheticAgentStartEmitted = true;
|
|
177
|
-
|
|
225
|
+
emitSessionEvent({ type: 'agent_start', agentName: 'lead', timestamp: Date.now() } as any);
|
|
178
226
|
|
|
179
227
|
// Send RPC command to sandbox
|
|
180
228
|
remote.prompt(text);
|
|
@@ -268,7 +316,7 @@ export async function runRemoteTui(options: {
|
|
|
268
316
|
//
|
|
269
317
|
// Events that arrive before InteractiveMode is initialized are buffered
|
|
270
318
|
// and flushed after init (InteractiveMode registers listeners during init,
|
|
271
|
-
// so
|
|
319
|
+
// so session event dispatch before that fires into the void).
|
|
272
320
|
let interactiveModeReady = false;
|
|
273
321
|
let eventBuffer: RpcEvent[] = [];
|
|
274
322
|
let seenMessageStart = false;
|
|
@@ -315,7 +363,7 @@ export async function runRemoteTui(options: {
|
|
|
315
363
|
}
|
|
316
364
|
|
|
317
365
|
for (const event of syntheticEvents) {
|
|
318
|
-
|
|
366
|
+
emitSessionEvent(event);
|
|
319
367
|
}
|
|
320
368
|
}
|
|
321
369
|
|
|
@@ -507,10 +555,14 @@ export async function runRemoteTui(options: {
|
|
|
507
555
|
) {
|
|
508
556
|
log(`Live ${rpcEvent.type} without prior message_start — injecting synthetics`);
|
|
509
557
|
if (!hadSeenAgentStart) {
|
|
510
|
-
|
|
558
|
+
emitSessionEvent({
|
|
559
|
+
type: 'agent_start',
|
|
560
|
+
agentName: 'lead',
|
|
561
|
+
timestamp: Date.now(),
|
|
562
|
+
} as any);
|
|
511
563
|
seenAgentStart = true;
|
|
512
564
|
}
|
|
513
|
-
|
|
565
|
+
emitSessionEvent({
|
|
514
566
|
type: 'message_start',
|
|
515
567
|
message: {
|
|
516
568
|
role: 'assistant',
|
|
@@ -535,7 +587,7 @@ export async function runRemoteTui(options: {
|
|
|
535
587
|
}
|
|
536
588
|
|
|
537
589
|
// Emit to subscribers — InteractiveMode.handleEvent processes this
|
|
538
|
-
|
|
590
|
+
emitSessionEvent(rpcEvent);
|
|
539
591
|
if (injectedSyntheticMessageStart && rpcEvent.type === 'message_end') {
|
|
540
592
|
seenMessageStart = false;
|
|
541
593
|
seenAgentStart = hadSeenAgentStart;
|
|
@@ -787,7 +839,7 @@ export async function runRemoteTui(options: {
|
|
|
787
839
|
|
|
788
840
|
// ── 9. Start InteractiveMode — full native Pi TUI ──
|
|
789
841
|
log('Creating InteractiveMode');
|
|
790
|
-
const interactive = new InteractiveMode(
|
|
842
|
+
const interactive = new InteractiveMode(runtime);
|
|
791
843
|
log('InteractiveMode created, calling init...');
|
|
792
844
|
await interactive.init();
|
|
793
845
|
|
|
@@ -803,8 +855,8 @@ export async function runRemoteTui(options: {
|
|
|
803
855
|
// the streaming indicator right away, before any buffered events flush.
|
|
804
856
|
// This prevents the blank screen gap between connect and first event.
|
|
805
857
|
log('Hydration detected streaming — emitting immediate synthetics');
|
|
806
|
-
|
|
807
|
-
|
|
858
|
+
emitSessionEvent({ type: 'agent_start', agentName: 'lead', timestamp: Date.now() } as any);
|
|
859
|
+
emitSessionEvent({
|
|
808
860
|
type: 'message_start',
|
|
809
861
|
message: {
|
|
810
862
|
role: 'assistant',
|
|
@@ -836,7 +888,7 @@ export async function runRemoteTui(options: {
|
|
|
836
888
|
if (eventBuffer.length > 0) {
|
|
837
889
|
log(`Flushing ${eventBuffer.length} events: ${eventBuffer.map((e) => e.type).join(', ')}`);
|
|
838
890
|
for (const buffered of eventBuffer) {
|
|
839
|
-
|
|
891
|
+
emitSessionEvent(buffered);
|
|
840
892
|
if (buffered.type === 'agent_end') {
|
|
841
893
|
resolveRunningPrompt();
|
|
842
894
|
}
|
|
@@ -851,6 +903,11 @@ export async function runRemoteTui(options: {
|
|
|
851
903
|
remote.close();
|
|
852
904
|
setNativeRemoteExtensionContext(null);
|
|
853
905
|
interactive.stop();
|
|
906
|
+
void disposeRuntime().catch((err) => {
|
|
907
|
+
log(
|
|
908
|
+
`Runtime dispose error during cleanup: ${err instanceof Error ? err.message : String(err)}`
|
|
909
|
+
);
|
|
910
|
+
});
|
|
854
911
|
};
|
|
855
912
|
process.on('SIGINT', cleanup);
|
|
856
913
|
process.on('SIGTERM', cleanup);
|
|
@@ -863,6 +920,9 @@ export async function runRemoteTui(options: {
|
|
|
863
920
|
} finally {
|
|
864
921
|
remote.close();
|
|
865
922
|
setNativeRemoteExtensionContext(null);
|
|
923
|
+
await disposeRuntime().catch((err) => {
|
|
924
|
+
log(`Runtime dispose error: ${err instanceof Error ? err.message : String(err)}`);
|
|
925
|
+
});
|
|
866
926
|
log('Remote TUI exited');
|
|
867
927
|
}
|
|
868
928
|
|
package/src/renderers.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
AgentToolResult,
|
|
13
13
|
} from '@mariozechner/pi-coding-agent';
|
|
14
14
|
import { Box, Text, Container, type Component } from '@mariozechner/pi-tui';
|
|
15
|
+
import { formatToolDisplay } from './agentuity-cli.ts';
|
|
15
16
|
|
|
16
17
|
// ──────────────────────────────────────────────
|
|
17
18
|
// Line-safety helper — must be declared before SimpleText so
|
|
@@ -125,6 +126,32 @@ function truncate(str: string, max: number): string {
|
|
|
125
126
|
return str.slice(0, max - 1) + '\u2026';
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
function toSingleLinePreview(value: string): string {
|
|
130
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isCommandToolName(toolName: string): boolean {
|
|
134
|
+
const normalized = toolName.trim().toLowerCase();
|
|
135
|
+
return normalized === 'bash' || normalized === 'execute_command' || normalized.includes('shell');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getCommandArg(args: Record<string, unknown>): string | undefined {
|
|
139
|
+
const value = args['command'] ?? args['cmd'];
|
|
140
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getCommandTimeoutLabel(args: Record<string, unknown>): string | undefined {
|
|
144
|
+
const timeout = args['timeout'];
|
|
145
|
+
if (typeof timeout === 'number' && Number.isFinite(timeout) && timeout > 0) {
|
|
146
|
+
return `${timeout}s`;
|
|
147
|
+
}
|
|
148
|
+
if (typeof timeout === 'string' && timeout.trim()) {
|
|
149
|
+
const value = timeout.trim();
|
|
150
|
+
return /s$/i.test(value) ? value : `${value}s`;
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
128
155
|
// ──────────────────────────────────────────────
|
|
129
156
|
// Individual tool renderers
|
|
130
157
|
// ──────────────────────────────────────────────
|
|
@@ -709,6 +736,37 @@ function parallelTasksRenderers(): ToolRenderers {
|
|
|
709
736
|
};
|
|
710
737
|
}
|
|
711
738
|
|
|
739
|
+
function commandToolRenderers(toolName: string): ToolRenderers {
|
|
740
|
+
return {
|
|
741
|
+
renderCall(args, theme) {
|
|
742
|
+
const display = formatToolDisplay(toolName, args);
|
|
743
|
+
const timeout = getCommandTimeoutLabel(args);
|
|
744
|
+
|
|
745
|
+
if (display.branded) {
|
|
746
|
+
let text = theme.fg('toolTitle', theme.bold(display.toolName));
|
|
747
|
+
if (display.toolArgs) {
|
|
748
|
+
text += theme.fg('accent', ` ${display.toolArgs}`);
|
|
749
|
+
}
|
|
750
|
+
if (timeout) {
|
|
751
|
+
text += theme.fg('dim', ` (timeout ${timeout})`);
|
|
752
|
+
}
|
|
753
|
+
return new SimpleText(text);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
let text = theme.fg('toolTitle', theme.bold('$ '));
|
|
757
|
+
const commandPreview = getCommandArg(args);
|
|
758
|
+
text += theme.fg(
|
|
759
|
+
'accent',
|
|
760
|
+
truncate(commandPreview ? toSingleLinePreview(commandPreview) : display.toolName, 80)
|
|
761
|
+
);
|
|
762
|
+
if (timeout) {
|
|
763
|
+
text += theme.fg('dim', ` (timeout ${timeout})`);
|
|
764
|
+
}
|
|
765
|
+
return new SimpleText(text);
|
|
766
|
+
},
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
712
770
|
// ──────────────────────────────────────────────
|
|
713
771
|
|
|
714
772
|
const RENDERERS: Record<string, () => ToolRenderers> = {
|
|
@@ -736,5 +794,7 @@ const RENDERERS: Record<string, () => ToolRenderers> = {
|
|
|
736
794
|
*/
|
|
737
795
|
export function getToolRenderers(toolName: string): ToolRenderers | undefined {
|
|
738
796
|
const factory = RENDERERS[toolName];
|
|
739
|
-
return factory
|
|
797
|
+
if (factory) return factory();
|
|
798
|
+
if (isCommandToolName(toolName)) return commandToolRenderers(toolName);
|
|
799
|
+
return undefined;
|
|
740
800
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AgentDefinition } from './protocol.ts';
|
|
2
|
+
|
|
3
|
+
const READ_ONLY_TOOL_NAMES = ['read', 'grep', 'find', 'ls'] as const;
|
|
4
|
+
const CODING_TOOL_NAMES = ['read', 'bash', 'edit', 'write'] as const;
|
|
5
|
+
|
|
6
|
+
function normalizeToolName(name: string): string {
|
|
7
|
+
return name.trim().toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function selectSubAgentToolNames(agentConfig: AgentDefinition): string[] {
|
|
11
|
+
const declared = new Set(
|
|
12
|
+
(agentConfig.tools ?? [])
|
|
13
|
+
.filter((name): name is string => typeof name === 'string' && name.trim().length > 0)
|
|
14
|
+
.map(normalizeToolName)
|
|
15
|
+
);
|
|
16
|
+
const needsBash = declared.has('bash');
|
|
17
|
+
const baseToolNames =
|
|
18
|
+
agentConfig.readOnly && !needsBash ? READ_ONLY_TOOL_NAMES : CODING_TOOL_NAMES;
|
|
19
|
+
const allowLegacyFallback =
|
|
20
|
+
agentConfig.strictToolSelection !== true && agentConfig.source === 'builtin';
|
|
21
|
+
|
|
22
|
+
if (declared.size === 0) {
|
|
23
|
+
return allowLegacyFallback ? [...baseToolNames] : [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const filtered = baseToolNames.filter((toolName) => declared.has(toolName));
|
|
27
|
+
|
|
28
|
+
if (filtered.length > 0) {
|
|
29
|
+
return filtered;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return allowLegacyFallback ? [...baseToolNames] : [];
|
|
33
|
+
}
|