@agentuity/coder-tui 2.0.11 → 2.0.13
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 -39
- 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 -2
- package/dist/protocol.d.ts.map +1 -1
- 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/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 -39
- package/src/local-init-filter.ts +54 -0
- package/src/protocol.ts +4 -2
- package/src/remote-session.ts +17 -9
- 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/auth.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const API_KEY_HEADER = 'x-agentuity-auth-api-key';
|
|
2
|
+
const AUTHORIZATION_HEADER = 'Authorization';
|
|
3
|
+
const ORG_HEADER = 'x-agentuity-orgid';
|
|
4
|
+
const DEFAULT_ORG_ID_ENV_VARS = ['AGENTUITY_ORGID', 'AGENTUITY_CLOUD_ORG_ID'] as const;
|
|
5
|
+
|
|
6
|
+
function normalizeApiKey(apiKey?: string | null): string | null {
|
|
7
|
+
const trimmed = apiKey?.trim();
|
|
8
|
+
return trimmed ? trimmed : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeOrgId(orgId?: string | null): string | null {
|
|
12
|
+
const trimmed = orgId?.trim();
|
|
13
|
+
return trimmed ? trimmed : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveCoderOrgId(orgId?: string | null): string | null {
|
|
17
|
+
const explicitOrgId = normalizeOrgId(orgId);
|
|
18
|
+
if (explicitOrgId) {
|
|
19
|
+
return explicitOrgId;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const envName of DEFAULT_ORG_ID_ENV_VARS) {
|
|
23
|
+
const envOrgId = normalizeOrgId(process.env[envName]);
|
|
24
|
+
if (envOrgId) {
|
|
25
|
+
return envOrgId;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isHubApiKey(apiKey?: string | null): boolean {
|
|
33
|
+
const token = normalizeApiKey(apiKey);
|
|
34
|
+
return token?.startsWith('agc_') ?? false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function applyCoderAuthHeaders(
|
|
38
|
+
headers: Record<string, string>,
|
|
39
|
+
apiKey?: string | null,
|
|
40
|
+
orgId?: string | null
|
|
41
|
+
): Record<string, string> {
|
|
42
|
+
const token = normalizeApiKey(apiKey);
|
|
43
|
+
const normalizedOrgId = resolveCoderOrgId(orgId);
|
|
44
|
+
const nextHeaders: Record<string, string> = { ...headers };
|
|
45
|
+
if (normalizedOrgId) {
|
|
46
|
+
nextHeaders[ORG_HEADER] = normalizedOrgId;
|
|
47
|
+
}
|
|
48
|
+
if (!token) return nextHeaders;
|
|
49
|
+
|
|
50
|
+
if (isHubApiKey(token)) {
|
|
51
|
+
nextHeaders[API_KEY_HEADER] = token;
|
|
52
|
+
return nextHeaders;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
nextHeaders[AUTHORIZATION_HEADER] = `Bearer ${token}`;
|
|
56
|
+
return nextHeaders;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getCoderAuthCurlArgs(apiKey?: string | null, orgId?: string | null): string[] {
|
|
60
|
+
const token = normalizeApiKey(apiKey);
|
|
61
|
+
const args: string[] = [];
|
|
62
|
+
const normalizedOrgId = resolveCoderOrgId(orgId);
|
|
63
|
+
if (normalizedOrgId) {
|
|
64
|
+
args.push('-H', `${ORG_HEADER}: ${normalizedOrgId}`);
|
|
65
|
+
}
|
|
66
|
+
if (!token) return args;
|
|
67
|
+
|
|
68
|
+
if (isHubApiKey(token)) {
|
|
69
|
+
args.push('-H', `${API_KEY_HEADER}: ${token}`);
|
|
70
|
+
return args;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
args.push('-H', `${AUTHORIZATION_HEADER}: Bearer ${token}`);
|
|
74
|
+
return args;
|
|
75
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HubClientMessage, HubRequest, HubResponse, InitMessage } from './protocol.ts';
|
|
2
|
+
import { applyCoderAuthHeaders } from './auth.ts';
|
|
2
3
|
|
|
3
4
|
/** How long to wait for a response before rejecting the pending promise (ms). */
|
|
4
5
|
const SEND_TIMEOUT_MS = 30_000;
|
|
@@ -67,8 +68,7 @@ export class HubClient {
|
|
|
67
68
|
/** Called when an unsolicited server message arrives (broadcast, presence, hydration). */
|
|
68
69
|
public onServerMessage?: (message: Record<string, unknown>) => void;
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
-
// TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
|
|
71
|
+
/** Hub auth token from the CLI environment. */
|
|
72
72
|
public apiKey: string | null = null;
|
|
73
73
|
|
|
74
74
|
private setConnectionState(state: ConnectionState): void {
|
|
@@ -259,11 +259,11 @@ export class HubClient {
|
|
|
259
259
|
|
|
260
260
|
private async connectInternal(url: string, isReconnect = false): Promise<InitMessage> {
|
|
261
261
|
const wsUrl = this.buildWebSocketUrl(url);
|
|
262
|
-
|
|
262
|
+
const orgId = process.env.AGENTUITY_ORGID;
|
|
263
263
|
// Bun extension: custom headers on WebSocket upgrade request
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
: new WebSocket(wsUrl);
|
|
264
|
+
const headers = applyCoderAuthHeaders({}, this.apiKey, orgId);
|
|
265
|
+
const ws =
|
|
266
|
+
Object.keys(headers).length > 0 ? new WebSocket(wsUrl, { headers }) : new WebSocket(wsUrl);
|
|
267
267
|
this.ws = ws;
|
|
268
268
|
|
|
269
269
|
return new Promise((resolve, reject) => {
|
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
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
type StreamProjection,
|
|
10
10
|
type StreamProjectionSource,
|
|
11
11
|
} from './hub-overlay-state.ts';
|
|
12
|
+
import { applyCoderAuthHeaders } from './auth.ts';
|
|
13
|
+
import { formatToolDisplay } from './agentuity-cli.ts';
|
|
12
14
|
import { truncateToWidth } from './renderers.ts';
|
|
13
15
|
import type {
|
|
14
16
|
ConversationEntry as HubConversationEntry,
|
|
@@ -1155,15 +1157,18 @@ export class HubOverlay implements Component, Focusable {
|
|
|
1155
1157
|
const controller = new AbortController();
|
|
1156
1158
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1157
1159
|
try {
|
|
1158
|
-
// TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
|
|
1159
1160
|
const apiKey = process.env.AGENTUITY_CODER_API_KEY;
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1161
|
+
const orgId = process.env.AGENTUITY_ORGID;
|
|
1162
|
+
const headers = applyCoderAuthHeaders(
|
|
1163
|
+
{
|
|
1164
|
+
accept: 'application/json',
|
|
1165
|
+
...(init?.headers && typeof init.headers === 'object'
|
|
1166
|
+
? (init.headers as Record<string, string>)
|
|
1167
|
+
: {}),
|
|
1168
|
+
},
|
|
1169
|
+
apiKey,
|
|
1170
|
+
orgId
|
|
1171
|
+
);
|
|
1167
1172
|
const signal = init?.signal
|
|
1168
1173
|
? AbortSignal.any([controller.signal, init.signal])
|
|
1169
1174
|
: controller.signal;
|
|
@@ -1764,10 +1769,9 @@ export class HubOverlay implements Component, Focusable {
|
|
|
1764
1769
|
const subscribe = mode === 'full' ? '*' : 'session_*,task_*,agent_*';
|
|
1765
1770
|
|
|
1766
1771
|
try {
|
|
1767
|
-
// TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
|
|
1768
1772
|
const apiKey = process.env.AGENTUITY_CODER_API_KEY;
|
|
1769
|
-
const
|
|
1770
|
-
|
|
1773
|
+
const orgId = process.env.AGENTUITY_ORGID;
|
|
1774
|
+
const sseHeaders = applyCoderAuthHeaders({ accept: 'text/event-stream' }, apiKey, orgId);
|
|
1771
1775
|
const response = await fetch(
|
|
1772
1776
|
`${this.baseUrl}/api/hub/session/${encodeURIComponent(sessionId)}/events?subscribe=${encodeURIComponent(subscribe)}`,
|
|
1773
1777
|
{
|
|
@@ -2125,6 +2129,13 @@ export class HubOverlay implements Component, Focusable {
|
|
|
2125
2129
|
? data.toolName
|
|
2126
2130
|
: 'tool';
|
|
2127
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}`;
|
|
2128
2139
|
const summarized = summarizeToolCall(name, input);
|
|
2129
2140
|
if (summarized) return summarized;
|
|
2130
2141
|
const argsPreview = summarizeArgs(input, 90);
|
|
@@ -2168,15 +2179,18 @@ export class HubOverlay implements Component, Focusable {
|
|
|
2168
2179
|
const failed = data?.isError === true || details?.error === true;
|
|
2169
2180
|
return `${header}\n${failed ? 'failed' : `done${duration}`}`;
|
|
2170
2181
|
}
|
|
2171
|
-
|
|
2182
|
+
const display = formatToolDisplay(name, input);
|
|
2183
|
+
return `tool_result ${display.toolName}`;
|
|
2172
2184
|
}
|
|
2173
2185
|
|
|
2174
2186
|
if (eventName === 'agent_progress') {
|
|
2175
2187
|
const agent = typeof data?.agentName === 'string' ? data.agentName : 'agent';
|
|
2176
2188
|
const status = typeof data?.status === 'string' ? data.status : 'progress';
|
|
2177
|
-
const
|
|
2189
|
+
const rawToolName = typeof data?.currentTool === 'string' ? data.currentTool : '';
|
|
2178
2190
|
const toolArgsRaw = typeof data?.currentToolArgs === 'string' ? data.currentToolArgs : '';
|
|
2179
|
-
const
|
|
2191
|
+
const display = formatToolDisplay(rawToolName, toolArgsRaw || undefined);
|
|
2192
|
+
const toolName = display.toolName;
|
|
2193
|
+
const toolArgs = display.toolArgs ? truncateToWidth(normalize(display.toolArgs), 80) : '';
|
|
2180
2194
|
|
|
2181
2195
|
// Deltas are already represented in rendered stream mode; skip them in event mode
|
|
2182
2196
|
// to avoid noisy, low-signal token lines.
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
createBashToolDefinition,
|
|
2
3
|
AgentToolResult,
|
|
3
4
|
ExtensionAPI,
|
|
4
5
|
ExtensionContext,
|
|
@@ -21,6 +22,10 @@ import { OutputViewerOverlay, type StoredResult } from './output-viewer.ts';
|
|
|
21
22
|
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';
|
|
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';
|
|
24
29
|
import type {
|
|
25
30
|
HubAction,
|
|
26
31
|
HubResponse,
|
|
@@ -43,9 +48,8 @@ const HUB_URL_ENV = 'AGENTUITY_CODER_HUB_URL';
|
|
|
43
48
|
const AGENT_ENV = 'AGENTUITY_CODER_AGENT';
|
|
44
49
|
const REMOTE_SESSION_ENV = 'AGENTUITY_CODER_REMOTE_SESSION';
|
|
45
50
|
const NATIVE_REMOTE_ENV = 'AGENTUITY_CODER_NATIVE_REMOTE';
|
|
46
|
-
//
|
|
51
|
+
// Populated by `agentuity coder start` for hub bootstrap and runtime auth.
|
|
47
52
|
const API_KEY_ENV = 'AGENTUITY_CODER_API_KEY';
|
|
48
|
-
const API_KEY_HEADER = 'x-agentuity-auth-api-key';
|
|
49
53
|
const RECONNECT_WAIT_TIMEOUT_MS = 120_000;
|
|
50
54
|
|
|
51
55
|
type HubUiStatus = 'connected' | 'reconnecting' | 'offline';
|
|
@@ -117,12 +121,11 @@ function log(msg: string): void {
|
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
/** Build headers object with API key if available. Merges with any existing headers. */
|
|
120
|
-
// TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
|
|
121
124
|
function authHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
122
125
|
const apiKey = process.env[API_KEY_ENV];
|
|
126
|
+
const orgId = process.env.AGENTUITY_ORGID;
|
|
123
127
|
const headers: Record<string, string> = { ...extra };
|
|
124
|
-
|
|
125
|
-
return headers;
|
|
128
|
+
return applyCoderAuthHeaders(headers, apiKey, orgId);
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
// ══════════════════════════════════════════════
|
|
@@ -173,9 +176,9 @@ function fetchInitMessageSync(hubUrl: string, agentRole?: string): InitMessage |
|
|
|
173
176
|
'node:child_process'
|
|
174
177
|
) as typeof import('node:child_process');
|
|
175
178
|
const apiKey = process.env[API_KEY_ENV];
|
|
179
|
+
const orgId = process.env.AGENTUITY_ORGID;
|
|
176
180
|
const curlArgs = ['-s', '--connect-timeout', '3', '--max-time', '5'];
|
|
177
|
-
|
|
178
|
-
if (apiKey) curlArgs.push('-H', `${API_KEY_HEADER}: ${apiKey}`);
|
|
181
|
+
curlArgs.push(...getCoderAuthCurlArgs(apiKey, orgId));
|
|
179
182
|
curlArgs.push(httpUrl);
|
|
180
183
|
const result = execFileSync('curl', curlArgs, { encoding: 'utf-8' });
|
|
181
184
|
|
|
@@ -284,6 +287,7 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
284
287
|
// to an existing sandbox session. The full UI is set up (tools, commands, /hub)
|
|
285
288
|
// but user input is relayed to the remote sandbox instead of the local Pi agent.
|
|
286
289
|
const remoteSessionId = process.env[REMOTE_SESSION_ENV] || null;
|
|
290
|
+
const isRemoteSession = Boolean(remoteSessionId);
|
|
287
291
|
const isNativeRemote = !!process.env[NATIVE_REMOTE_ENV];
|
|
288
292
|
if (remoteSessionId) {
|
|
289
293
|
log(`Remote mode: will connect as controller to session ${remoteSessionId}`);
|
|
@@ -299,7 +303,10 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
299
303
|
// This is how we discover what tools/agents the server provides.
|
|
300
304
|
// ══════════════════════════════════════════════
|
|
301
305
|
|
|
302
|
-
const
|
|
306
|
+
const initialInitMsg = fetchInitMessageSync(hubUrl, agentRole);
|
|
307
|
+
const initMsg = initialInitMsg
|
|
308
|
+
? adaptInitMessageForLocalTui(initialInitMsg, { isRemoteSession })
|
|
309
|
+
: null;
|
|
303
310
|
|
|
304
311
|
if (!initMsg) {
|
|
305
312
|
log('Hub not reachable — no tools or agents registered');
|
|
@@ -412,12 +419,23 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
412
419
|
// Titlebar: branding + spinner (registers its own event handlers)
|
|
413
420
|
setupTitlebar(pi);
|
|
414
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
|
+
|
|
415
434
|
// ══════════════════════════════════════════════
|
|
416
435
|
// WebSocket client for runtime communication (tool execution + events)
|
|
417
436
|
// ══════════════════════════════════════════════
|
|
418
437
|
|
|
419
438
|
const client = new HubClient();
|
|
420
|
-
// TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
|
|
421
439
|
client.apiKey = process.env[API_KEY_ENV] || null;
|
|
422
440
|
let cachedInitMessage: InitMessage | null = initMsg;
|
|
423
441
|
let currentSessionId: string | null = initMsg.sessionId ?? null;
|
|
@@ -455,9 +473,10 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
455
473
|
}
|
|
456
474
|
|
|
457
475
|
function applyInitMessage(nextInit: InitMessage): void {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (
|
|
476
|
+
const effectiveInit = adaptInitMessageForLocalTui(nextInit, { isRemoteSession });
|
|
477
|
+
cachedInitMessage = effectiveInit;
|
|
478
|
+
if (effectiveInit.sessionId) currentSessionId = effectiveInit.sessionId;
|
|
479
|
+
if (effectiveInit.config) hubConfig = effectiveInit.config;
|
|
461
480
|
}
|
|
462
481
|
|
|
463
482
|
client.onInitMessage = (nextInit) => {
|
|
@@ -1757,13 +1776,7 @@ async function runSubAgent(
|
|
|
1757
1776
|
const { piSdk, piAi } = await loadPiSdk();
|
|
1758
1777
|
// Runtime-resolved dynamic imports — exact types unavailable statically
|
|
1759
1778
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1760
|
-
const {
|
|
1761
|
-
createAgentSession,
|
|
1762
|
-
DefaultResourceLoader,
|
|
1763
|
-
SessionManager,
|
|
1764
|
-
createCodingTools,
|
|
1765
|
-
createReadOnlyTools,
|
|
1766
|
-
} = piSdk as any;
|
|
1779
|
+
const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = piSdk as any;
|
|
1767
1780
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1768
1781
|
const { getModel } = piAi as any;
|
|
1769
1782
|
|
|
@@ -1785,13 +1798,16 @@ async function runSubAgent(
|
|
|
1785
1798
|
// Sub-agents get Hub tools (memory, context7, etc.) via extensionFactories
|
|
1786
1799
|
// so they work in both driver and TUI mode.
|
|
1787
1800
|
const hubTools = agentConfig.hubTools ?? [];
|
|
1801
|
+
const cwd = process.cwd();
|
|
1802
|
+
const agentDir = getAgentDir();
|
|
1788
1803
|
|
|
1789
1804
|
// Resource loader — no extensions (prevents recursive task tool registration),
|
|
1790
1805
|
// no skills, agent's system prompt injected directly.
|
|
1791
1806
|
// Hub tools are injected via extensionFactories so sub-agents can use
|
|
1792
1807
|
// memory_recall, context7_search, etc.
|
|
1793
1808
|
const subLoader = new DefaultResourceLoader({
|
|
1794
|
-
cwd
|
|
1809
|
+
cwd,
|
|
1810
|
+
agentDir,
|
|
1795
1811
|
noExtensions: true,
|
|
1796
1812
|
extensionFactories:
|
|
1797
1813
|
hubTools.length > 0
|
|
@@ -1810,9 +1826,12 @@ async function runSubAgent(
|
|
|
1810
1826
|
});
|
|
1811
1827
|
await subLoader.reload();
|
|
1812
1828
|
|
|
1813
|
-
//
|
|
1814
|
-
const
|
|
1815
|
-
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]));
|
|
1816
1835
|
|
|
1817
1836
|
const { session } = await createAgentSession({
|
|
1818
1837
|
// subModel is already untyped (from dynamic import) — createAgentSession is also dynamically imported
|
|
@@ -1826,7 +1845,8 @@ async function runSubAgent(
|
|
|
1826
1845
|
| 'xhigh',
|
|
1827
1846
|
tools,
|
|
1828
1847
|
resourceLoader: subLoader,
|
|
1829
|
-
|
|
1848
|
+
// Pi now tracks cwd per session, so bind in-memory sub-agents to the actual repo cwd.
|
|
1849
|
+
sessionManager: SessionManager.inMemory(cwd),
|
|
1830
1850
|
});
|
|
1831
1851
|
await session.bindExtensions({});
|
|
1832
1852
|
|
|
@@ -1862,25 +1882,19 @@ async function runSubAgent(
|
|
|
1862
1882
|
|
|
1863
1883
|
if (evt.type === 'tool_execution_start') {
|
|
1864
1884
|
const toolName = evt.toolName || evt.name || evt.tool || 'unknown';
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
else if (args.pattern) toolArgs = String(args.pattern).slice(0, 40);
|
|
1872
|
-
else {
|
|
1873
|
-
const first = Object.values(args)[0];
|
|
1874
|
-
if (first) toolArgs = String(first).slice(0, 40);
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
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
|
+
);
|
|
1877
1891
|
|
|
1878
1892
|
onProgress({
|
|
1879
1893
|
agentName: agentConfig.name,
|
|
1880
1894
|
status: 'tool_start',
|
|
1881
1895
|
toolCallId: typeof evt.toolCallId === 'string' ? evt.toolCallId : undefined,
|
|
1882
|
-
currentTool: toolName,
|
|
1883
|
-
currentToolArgs: toolArgs,
|
|
1896
|
+
currentTool: display.toolName,
|
|
1897
|
+
currentToolArgs: display.toolArgs,
|
|
1884
1898
|
elapsed,
|
|
1885
1899
|
});
|
|
1886
1900
|
} else if (evt.type === 'tool_execution_end') {
|