@agentuity/coder-tui 3.0.0-alpha.6 → 3.0.0-alpha.7
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/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 +12 -3
- 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 +45 -30
- 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 +2 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/remote-session.d.ts.map +1 -1
- package/dist/remote-session.js +12 -6
- package/dist/remote-session.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/hub-overlay-state.ts +4 -1
- package/src/hub-overlay.ts +14 -3
- package/src/index.ts +48 -32
- package/src/local-init-filter.ts +54 -0
- package/src/protocol.ts +2 -0
- package/src/remote-session.ts +12 -6
- package/src/renderers.ts +61 -1
- package/src/subagent-tool-selection.ts +33 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
const SHELL_ASSIGNMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S+)$/;
|
|
2
|
+
|
|
3
|
+
export const AGENTUITY_CLI_MARK = '⨺';
|
|
4
|
+
|
|
5
|
+
type ToolArgsInput = string | Record<string, unknown> | undefined;
|
|
6
|
+
|
|
7
|
+
export interface ToolDisplayDescriptor {
|
|
8
|
+
toolName: string;
|
|
9
|
+
toolArgs?: string;
|
|
10
|
+
fullLabel: string;
|
|
11
|
+
branded: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function stripShellQuotes(value: string): string {
|
|
15
|
+
if (
|
|
16
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
17
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
18
|
+
) {
|
|
19
|
+
return value.slice(1, -1);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function tokenizeShellLike(command: string): string[] {
|
|
25
|
+
return (command.match(/"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|&&|\|\||[;|()]|[^\s;|()]+/g) ?? []).map(
|
|
26
|
+
stripShellQuotes
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function splitShellSegments(command: string): string[] {
|
|
31
|
+
return command
|
|
32
|
+
.split(/(?:\r?\n|&&|\|\||;|\|)/)
|
|
33
|
+
.map((segment) => segment.trim())
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function optionConsumesValue(token: string): boolean {
|
|
38
|
+
return (
|
|
39
|
+
token === '-c' ||
|
|
40
|
+
token === '-g' ||
|
|
41
|
+
token === '-h' ||
|
|
42
|
+
token === '-p' ||
|
|
43
|
+
token === '-r' ||
|
|
44
|
+
token === '-t' ||
|
|
45
|
+
token === '-u' ||
|
|
46
|
+
token === '--chdir' ||
|
|
47
|
+
token === '--config' ||
|
|
48
|
+
token === '--group' ||
|
|
49
|
+
token === '--host' ||
|
|
50
|
+
token === '--package' ||
|
|
51
|
+
token === '--registry' ||
|
|
52
|
+
token === '--user'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function trimShellPrefix(tokens: string[]): string[] {
|
|
57
|
+
let index = 0;
|
|
58
|
+
|
|
59
|
+
while (index < tokens.length) {
|
|
60
|
+
const token = tokens[index];
|
|
61
|
+
if (!token || token === '!' || token === '(' || token === ')') {
|
|
62
|
+
index += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (token === 'env' || token === 'sudo') {
|
|
67
|
+
index += 1;
|
|
68
|
+
while (tokens[index]?.startsWith('-')) {
|
|
69
|
+
const option = tokens[index] ?? '';
|
|
70
|
+
index += optionConsumesValue(option) ? 2 : 1;
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (SHELL_ASSIGNMENT_PATTERN.test(token)) {
|
|
76
|
+
index += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return tokens.slice(index);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractAgentuityCliRemainderFromTokens(tokens: string[]): string | null {
|
|
87
|
+
const trimmed = trimShellPrefix(tokens);
|
|
88
|
+
if (trimmed.length === 0) return null;
|
|
89
|
+
|
|
90
|
+
const first = trimmed[0]?.toLowerCase();
|
|
91
|
+
if (first === 'agentuity') {
|
|
92
|
+
return trimmed.slice(1).join(' ').trim();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (first === 'bunx' || first === 'npx') {
|
|
96
|
+
let index = 1;
|
|
97
|
+
while (trimmed[index]?.startsWith('-')) {
|
|
98
|
+
const option = trimmed[index] ?? '';
|
|
99
|
+
index += optionConsumesValue(option) ? 2 : 1;
|
|
100
|
+
}
|
|
101
|
+
if (trimmed[index] === '@agentuity/cli') {
|
|
102
|
+
return trimmed
|
|
103
|
+
.slice(index + 1)
|
|
104
|
+
.join(' ')
|
|
105
|
+
.trim();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (first === 'pnpm' && trimmed[1]?.toLowerCase() === 'dlx') {
|
|
110
|
+
let index = 2;
|
|
111
|
+
while (trimmed[index]?.startsWith('-')) {
|
|
112
|
+
const option = trimmed[index] ?? '';
|
|
113
|
+
index += optionConsumesValue(option) ? 2 : 1;
|
|
114
|
+
}
|
|
115
|
+
if (trimmed[index] === '@agentuity/cli') {
|
|
116
|
+
return trimmed
|
|
117
|
+
.slice(index + 1)
|
|
118
|
+
.join(' ')
|
|
119
|
+
.trim();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeDisplayWhitespace(value: string): string {
|
|
127
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function trimDisplay(value: string, max: number): string {
|
|
131
|
+
const normalized = normalizeDisplayWhitespace(value);
|
|
132
|
+
if (normalized.length <= max) return normalized;
|
|
133
|
+
return `${normalized.slice(0, max - 3)}...`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getCommandArg(rawArgs?: ToolArgsInput): string | undefined {
|
|
137
|
+
if (typeof rawArgs === 'string' && rawArgs.trim()) {
|
|
138
|
+
return rawArgs.trim();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!rawArgs || typeof rawArgs !== 'object') {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const value = rawArgs.command ?? rawArgs.cmd;
|
|
146
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getGenericToolArgsPreview(rawArgs?: ToolArgsInput): string | undefined {
|
|
150
|
+
if (typeof rawArgs === 'string' && rawArgs.trim()) {
|
|
151
|
+
return trimDisplay(rawArgs.trim(), 60);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!rawArgs || typeof rawArgs !== 'object') {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (typeof rawArgs.command === 'string' && rawArgs.command.trim()) {
|
|
159
|
+
return trimDisplay(rawArgs.command.trim(), 60);
|
|
160
|
+
}
|
|
161
|
+
if (typeof rawArgs.filePath === 'string' && rawArgs.filePath.trim()) {
|
|
162
|
+
return rawArgs.filePath.trim();
|
|
163
|
+
}
|
|
164
|
+
if (typeof rawArgs.path === 'string' && rawArgs.path.trim()) {
|
|
165
|
+
return rawArgs.path.trim();
|
|
166
|
+
}
|
|
167
|
+
if (typeof rawArgs.pattern === 'string' && rawArgs.pattern.trim()) {
|
|
168
|
+
return trimDisplay(rawArgs.pattern.trim(), 40);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const first = Object.values(rawArgs)[0];
|
|
172
|
+
if (typeof first === 'string' && first.trim()) {
|
|
173
|
+
return trimDisplay(first.trim(), 40);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function isCommandToolName(toolName: string): boolean {
|
|
180
|
+
const normalized = toolName.trim().toLowerCase();
|
|
181
|
+
return normalized === 'bash' || normalized === 'execute_command' || normalized.includes('shell');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function getAgentuityCliCommandRemainder(command?: string): string | null {
|
|
185
|
+
if (!command?.trim()) return null;
|
|
186
|
+
|
|
187
|
+
for (const segment of splitShellSegments(command)) {
|
|
188
|
+
const remainder = extractAgentuityCliRemainderFromTokens(tokenizeShellLike(segment));
|
|
189
|
+
if (remainder !== null) {
|
|
190
|
+
return remainder;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function formatToolDisplay(
|
|
198
|
+
toolName: string,
|
|
199
|
+
rawArgs?: ToolArgsInput
|
|
200
|
+
): ToolDisplayDescriptor {
|
|
201
|
+
const normalizedToolName = normalizeDisplayWhitespace(toolName) || 'tool';
|
|
202
|
+
const command = getCommandArg(rawArgs);
|
|
203
|
+
|
|
204
|
+
if (isCommandToolName(normalizedToolName) && command) {
|
|
205
|
+
const remainder = getAgentuityCliCommandRemainder(command);
|
|
206
|
+
if (remainder !== null) {
|
|
207
|
+
const brandedToolName = `${AGENTUITY_CLI_MARK} agentuity`;
|
|
208
|
+
const toolArgs = remainder ? trimDisplay(remainder, 60) : undefined;
|
|
209
|
+
return {
|
|
210
|
+
toolName: brandedToolName,
|
|
211
|
+
toolArgs,
|
|
212
|
+
fullLabel: toolArgs ? `${brandedToolName} ${toolArgs}` : brandedToolName,
|
|
213
|
+
branded: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const toolArgs = getGenericToolArgsPreview(rawArgs);
|
|
219
|
+
return {
|
|
220
|
+
toolName: normalizedToolName,
|
|
221
|
+
toolArgs,
|
|
222
|
+
fullLabel: toolArgs ? `${normalizedToolName} ${toolArgs}` : normalizedToolName,
|
|
223
|
+
branded: false,
|
|
224
|
+
};
|
|
225
|
+
}
|
package/src/hub-overlay-state.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { formatToolDisplay } from './agentuity-cli.ts';
|
|
2
|
+
|
|
1
3
|
export interface StreamBuffer {
|
|
2
4
|
output: string;
|
|
3
5
|
thinking: string;
|
|
@@ -78,7 +80,8 @@ export function buildProjectionFromEntries(
|
|
|
78
80
|
const type = typeof entry.type === 'string' ? entry.type : '';
|
|
79
81
|
if (type === 'tool_call') {
|
|
80
82
|
const toolName = typeof entry.toolName === 'string' ? entry.toolName : 'tool';
|
|
81
|
-
|
|
83
|
+
const display = formatToolDisplay(toolName, entry.toolArgs);
|
|
84
|
+
append('output', `[tool_call] ${display.fullLabel}\n\n`, entry.taskId);
|
|
82
85
|
continue;
|
|
83
86
|
}
|
|
84
87
|
|
package/src/hub-overlay.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type StreamProjectionSource,
|
|
11
11
|
} from './hub-overlay-state.ts';
|
|
12
12
|
import { applyCoderAuthHeaders } from './auth.ts';
|
|
13
|
+
import { formatToolDisplay } from './agentuity-cli.ts';
|
|
13
14
|
import { truncateToWidth } from './renderers.ts';
|
|
14
15
|
import type {
|
|
15
16
|
ConversationEntry as HubConversationEntry,
|
|
@@ -2128,6 +2129,13 @@ export class HubOverlay implements Component, Focusable {
|
|
|
2128
2129
|
? data.toolName
|
|
2129
2130
|
: 'tool';
|
|
2130
2131
|
const input = data?.args ?? data?.input;
|
|
2132
|
+
const display = formatToolDisplay(
|
|
2133
|
+
name,
|
|
2134
|
+
typeof input === 'string' || (input && typeof input === 'object')
|
|
2135
|
+
? (input as string | Record<string, unknown>)
|
|
2136
|
+
: undefined
|
|
2137
|
+
);
|
|
2138
|
+
if (display.branded) return `tool_call ${display.fullLabel}`;
|
|
2131
2139
|
const summarized = summarizeToolCall(name, input);
|
|
2132
2140
|
if (summarized) return summarized;
|
|
2133
2141
|
const argsPreview = summarizeArgs(input, 90);
|
|
@@ -2171,15 +2179,18 @@ export class HubOverlay implements Component, Focusable {
|
|
|
2171
2179
|
const failed = data?.isError === true || details?.error === true;
|
|
2172
2180
|
return `${header}\n${failed ? 'failed' : `done${duration}`}`;
|
|
2173
2181
|
}
|
|
2174
|
-
|
|
2182
|
+
const display = formatToolDisplay(name, input);
|
|
2183
|
+
return `tool_result ${display.toolName}`;
|
|
2175
2184
|
}
|
|
2176
2185
|
|
|
2177
2186
|
if (eventName === 'agent_progress') {
|
|
2178
2187
|
const agent = typeof data?.agentName === 'string' ? data.agentName : 'agent';
|
|
2179
2188
|
const status = typeof data?.status === 'string' ? data.status : 'progress';
|
|
2180
|
-
const
|
|
2189
|
+
const rawToolName = typeof data?.currentTool === 'string' ? data.currentTool : '';
|
|
2181
2190
|
const toolArgsRaw = typeof data?.currentToolArgs === 'string' ? data.currentToolArgs : '';
|
|
2182
|
-
const
|
|
2191
|
+
const display = formatToolDisplay(rawToolName, toolArgsRaw || undefined);
|
|
2192
|
+
const toolName = display.toolName;
|
|
2193
|
+
const toolArgs = display.toolArgs ? truncateToWidth(normalize(display.toolArgs), 80) : '';
|
|
2183
2194
|
|
|
2184
2195
|
// Deltas are already represented in rendered stream mode; skip them in event mode
|
|
2185
2196
|
// to avoid noisy, low-signal token lines.
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
createBashToolDefinition,
|
|
2
3
|
AgentToolResult,
|
|
3
4
|
ExtensionAPI,
|
|
4
5
|
ExtensionContext,
|
|
5
6
|
ExtensionCommandContext,
|
|
6
7
|
ToolDefinition,
|
|
7
8
|
} from '@mariozechner/pi-coding-agent';
|
|
8
|
-
import { Type, type TSchema } from '
|
|
9
|
+
import { Type, type TSchema } from 'typebox';
|
|
9
10
|
import { createRequire } from 'node:module';
|
|
10
11
|
import { HubClient } from './client.ts';
|
|
11
12
|
import type { ConnectionState } from './client.ts';
|
|
@@ -22,6 +23,9 @@ import { setNativeRemoteExtensionContext } from './native-remote-ui-context.ts';
|
|
|
22
23
|
import { handleRemoteUiRequest } from './remote-ui-handler.ts';
|
|
23
24
|
import { buildInboundRpcPromptText, getInboundRpcDeliverAs } from './inbound-rpc.ts';
|
|
24
25
|
import { applyCoderAuthHeaders, getCoderAuthCurlArgs } from './auth.ts';
|
|
26
|
+
import { formatToolDisplay } from './agentuity-cli.ts';
|
|
27
|
+
import { adaptInitMessageForLocalTui } from './local-init-filter.ts';
|
|
28
|
+
import { selectSubAgentToolNames } from './subagent-tool-selection.ts';
|
|
25
29
|
import type {
|
|
26
30
|
HubAction,
|
|
27
31
|
HubResponse,
|
|
@@ -283,6 +287,7 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
283
287
|
// to an existing sandbox session. The full UI is set up (tools, commands, /hub)
|
|
284
288
|
// but user input is relayed to the remote sandbox instead of the local Pi agent.
|
|
285
289
|
const remoteSessionId = process.env[REMOTE_SESSION_ENV] || null;
|
|
290
|
+
const isRemoteSession = Boolean(remoteSessionId);
|
|
286
291
|
const isNativeRemote = !!process.env[NATIVE_REMOTE_ENV];
|
|
287
292
|
if (remoteSessionId) {
|
|
288
293
|
log(`Remote mode: will connect as controller to session ${remoteSessionId}`);
|
|
@@ -298,7 +303,10 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
298
303
|
// This is how we discover what tools/agents the server provides.
|
|
299
304
|
// ══════════════════════════════════════════════
|
|
300
305
|
|
|
301
|
-
const
|
|
306
|
+
const initialInitMsg = fetchInitMessageSync(hubUrl, agentRole);
|
|
307
|
+
const initMsg = initialInitMsg
|
|
308
|
+
? adaptInitMessageForLocalTui(initialInitMsg, { isRemoteSession })
|
|
309
|
+
: null;
|
|
302
310
|
|
|
303
311
|
if (!initMsg) {
|
|
304
312
|
log('Hub not reachable — no tools or agents registered');
|
|
@@ -411,6 +419,18 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
411
419
|
// Titlebar: branding + spinner (registers its own event handlers)
|
|
412
420
|
setupTitlebar(pi);
|
|
413
421
|
|
|
422
|
+
// Override Pi's built-in bash call-row rendering so local transcript rows
|
|
423
|
+
// can brand Agentuity CLI invocations without changing bash execution/result behavior.
|
|
424
|
+
const hasBashTool = serverTools.some((tool) => tool.name === 'bash');
|
|
425
|
+
const localBashRenderers = hasBashTool ? getToolRenderers('bash') : undefined;
|
|
426
|
+
if (hasBashTool && localBashRenderers?.renderCall) {
|
|
427
|
+
const bashToolDefinition = createBashToolDefinition(process.cwd());
|
|
428
|
+
pi.registerTool({
|
|
429
|
+
...bashToolDefinition,
|
|
430
|
+
renderCall: localBashRenderers.renderCall as ToolDefinition['renderCall'],
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
414
434
|
// ══════════════════════════════════════════════
|
|
415
435
|
// WebSocket client for runtime communication (tool execution + events)
|
|
416
436
|
// ══════════════════════════════════════════════
|
|
@@ -453,9 +473,10 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
453
473
|
}
|
|
454
474
|
|
|
455
475
|
function applyInitMessage(nextInit: InitMessage): void {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (
|
|
476
|
+
const effectiveInit = adaptInitMessageForLocalTui(nextInit, { isRemoteSession });
|
|
477
|
+
cachedInitMessage = effectiveInit;
|
|
478
|
+
if (effectiveInit.sessionId) currentSessionId = effectiveInit.sessionId;
|
|
479
|
+
if (effectiveInit.config) hubConfig = effectiveInit.config;
|
|
459
480
|
}
|
|
460
481
|
|
|
461
482
|
client.onInitMessage = (nextInit) => {
|
|
@@ -1755,13 +1776,7 @@ async function runSubAgent(
|
|
|
1755
1776
|
const { piSdk, piAi } = await loadPiSdk();
|
|
1756
1777
|
// Runtime-resolved dynamic imports — exact types unavailable statically
|
|
1757
1778
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1758
|
-
const {
|
|
1759
|
-
createAgentSession,
|
|
1760
|
-
DefaultResourceLoader,
|
|
1761
|
-
SessionManager,
|
|
1762
|
-
createCodingTools,
|
|
1763
|
-
createReadOnlyTools,
|
|
1764
|
-
} = piSdk as any;
|
|
1779
|
+
const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = piSdk as any;
|
|
1765
1780
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1766
1781
|
const { getModel } = piAi as any;
|
|
1767
1782
|
|
|
@@ -1783,13 +1798,16 @@ async function runSubAgent(
|
|
|
1783
1798
|
// Sub-agents get Hub tools (memory, context7, etc.) via extensionFactories
|
|
1784
1799
|
// so they work in both driver and TUI mode.
|
|
1785
1800
|
const hubTools = agentConfig.hubTools ?? [];
|
|
1801
|
+
const cwd = process.cwd();
|
|
1802
|
+
const agentDir = getAgentDir();
|
|
1786
1803
|
|
|
1787
1804
|
// Resource loader — no extensions (prevents recursive task tool registration),
|
|
1788
1805
|
// no skills, agent's system prompt injected directly.
|
|
1789
1806
|
// Hub tools are injected via extensionFactories so sub-agents can use
|
|
1790
1807
|
// memory_recall, context7_search, etc.
|
|
1791
1808
|
const subLoader = new DefaultResourceLoader({
|
|
1792
|
-
cwd
|
|
1809
|
+
cwd,
|
|
1810
|
+
agentDir,
|
|
1793
1811
|
noExtensions: true,
|
|
1794
1812
|
extensionFactories:
|
|
1795
1813
|
hubTools.length > 0
|
|
@@ -1808,9 +1826,12 @@ async function runSubAgent(
|
|
|
1808
1826
|
});
|
|
1809
1827
|
await subLoader.reload();
|
|
1810
1828
|
|
|
1811
|
-
//
|
|
1812
|
-
const
|
|
1813
|
-
const
|
|
1829
|
+
// Pi v0.68.x uses a name allowlist for both built-in and extension/custom tools.
|
|
1830
|
+
const builtInToolNames = selectSubAgentToolNames(agentConfig);
|
|
1831
|
+
const hubToolNames = hubTools
|
|
1832
|
+
.map((tool) => (typeof tool.name === 'string' ? tool.name.trim() : ''))
|
|
1833
|
+
.filter((name): name is string => name.length > 0);
|
|
1834
|
+
const tools = Array.from(new Set([...builtInToolNames, ...hubToolNames]));
|
|
1814
1835
|
|
|
1815
1836
|
const { session } = await createAgentSession({
|
|
1816
1837
|
// subModel is already untyped (from dynamic import) — createAgentSession is also dynamically imported
|
|
@@ -1824,7 +1845,8 @@ async function runSubAgent(
|
|
|
1824
1845
|
| 'xhigh',
|
|
1825
1846
|
tools,
|
|
1826
1847
|
resourceLoader: subLoader,
|
|
1827
|
-
|
|
1848
|
+
// Pi now tracks cwd per session, so bind in-memory sub-agents to the actual repo cwd.
|
|
1849
|
+
sessionManager: SessionManager.inMemory(cwd),
|
|
1828
1850
|
});
|
|
1829
1851
|
await session.bindExtensions({});
|
|
1830
1852
|
|
|
@@ -1860,25 +1882,19 @@ async function runSubAgent(
|
|
|
1860
1882
|
|
|
1861
1883
|
if (evt.type === 'tool_execution_start') {
|
|
1862
1884
|
const toolName = evt.toolName || evt.name || evt.tool || 'unknown';
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
else if (args.pattern) toolArgs = String(args.pattern).slice(0, 40);
|
|
1870
|
-
else {
|
|
1871
|
-
const first = Object.values(args)[0];
|
|
1872
|
-
if (first) toolArgs = String(first).slice(0, 40);
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1885
|
+
const display = formatToolDisplay(
|
|
1886
|
+
toolName,
|
|
1887
|
+
typeof evt.args === 'string' || (evt.args && typeof evt.args === 'object')
|
|
1888
|
+
? (evt.args as string | Record<string, unknown>)
|
|
1889
|
+
: undefined
|
|
1890
|
+
);
|
|
1875
1891
|
|
|
1876
1892
|
onProgress({
|
|
1877
1893
|
agentName: agentConfig.name,
|
|
1878
1894
|
status: 'tool_start',
|
|
1879
1895
|
toolCallId: typeof evt.toolCallId === 'string' ? evt.toolCallId : undefined,
|
|
1880
|
-
currentTool: toolName,
|
|
1881
|
-
currentToolArgs: toolArgs,
|
|
1896
|
+
currentTool: display.toolName,
|
|
1897
|
+
currentToolArgs: display.toolArgs,
|
|
1882
1898
|
elapsed,
|
|
1883
1899
|
});
|
|
1884
1900
|
} else if (evt.type === 'tool_execution_end') {
|
|
@@ -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
|
|
package/src/remote-session.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type RemoteLifecycleState,
|
|
24
24
|
} from './remote-lifecycle.ts';
|
|
25
25
|
import { resolveCoderOrgId } from './auth.ts';
|
|
26
|
+
import { formatToolDisplay } from './agentuity-cli.ts';
|
|
26
27
|
|
|
27
28
|
const DEBUG = !!process.env['AGENTUITY_DEBUG'];
|
|
28
29
|
|
|
@@ -1001,18 +1002,23 @@ export async function setupRemoteMode(
|
|
|
1001
1002
|
break;
|
|
1002
1003
|
|
|
1003
1004
|
case 'tool_execution_start': {
|
|
1004
|
-
const
|
|
1005
|
-
|
|
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;
|
|
1006
1009
|
if (extensionCtxRef?.hasUI) {
|
|
1007
|
-
setNonLifecycleWorkingMessage(`Running ${
|
|
1008
|
-
extensionCtxRef.ui.setStatus('remote_activity', `Running ${
|
|
1010
|
+
setNonLifecycleWorkingMessage(`Running ${display.fullLabel}...`);
|
|
1011
|
+
extensionCtxRef.ui.setStatus('remote_activity', `Running ${display.fullLabel}...`);
|
|
1009
1012
|
}
|
|
1010
|
-
log(`Tool: ${
|
|
1013
|
+
log(`Tool: ${display.fullLabel}`);
|
|
1011
1014
|
break;
|
|
1012
1015
|
}
|
|
1013
1016
|
|
|
1014
1017
|
case 'tool_execution_end': {
|
|
1015
|
-
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;
|
|
1016
1022
|
currentTool = null;
|
|
1017
1023
|
if (extensionCtxRef?.hasUI) {
|
|
1018
1024
|
clearWorkingMessage();
|
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
|
+
}
|