@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,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';
|
|
@@ -84,9 +88,7 @@ const MAX_OUTPUT_LINES = 5_000;
|
|
|
84
88
|
const PROXY_EVENTS = [
|
|
85
89
|
'session_shutdown',
|
|
86
90
|
'session_before_switch',
|
|
87
|
-
'session_switch',
|
|
88
91
|
'session_before_fork',
|
|
89
|
-
'session_fork',
|
|
90
92
|
'session_before_compact',
|
|
91
93
|
'session_compact',
|
|
92
94
|
'before_agent_start',
|
|
@@ -119,12 +121,11 @@ function log(msg: string): void {
|
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
/** Build headers object with API key if available. Merges with any existing headers. */
|
|
122
|
-
// TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
|
|
123
124
|
function authHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
124
125
|
const apiKey = process.env[API_KEY_ENV];
|
|
126
|
+
const orgId = process.env.AGENTUITY_ORGID;
|
|
125
127
|
const headers: Record<string, string> = { ...extra };
|
|
126
|
-
|
|
127
|
-
return headers;
|
|
128
|
+
return applyCoderAuthHeaders(headers, apiKey, orgId);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
// ══════════════════════════════════════════════
|
|
@@ -175,9 +176,9 @@ function fetchInitMessageSync(hubUrl: string, agentRole?: string): InitMessage |
|
|
|
175
176
|
'node:child_process'
|
|
176
177
|
) as typeof import('node:child_process');
|
|
177
178
|
const apiKey = process.env[API_KEY_ENV];
|
|
179
|
+
const orgId = process.env.AGENTUITY_ORGID;
|
|
178
180
|
const curlArgs = ['-s', '--connect-timeout', '3', '--max-time', '5'];
|
|
179
|
-
|
|
180
|
-
if (apiKey) curlArgs.push('-H', `${API_KEY_HEADER}: ${apiKey}`);
|
|
181
|
+
curlArgs.push(...getCoderAuthCurlArgs(apiKey, orgId));
|
|
181
182
|
curlArgs.push(httpUrl);
|
|
182
183
|
const result = execFileSync('curl', curlArgs, { encoding: 'utf-8' });
|
|
183
184
|
|
|
@@ -286,6 +287,7 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
286
287
|
// to an existing sandbox session. The full UI is set up (tools, commands, /hub)
|
|
287
288
|
// but user input is relayed to the remote sandbox instead of the local Pi agent.
|
|
288
289
|
const remoteSessionId = process.env[REMOTE_SESSION_ENV] || null;
|
|
290
|
+
const isRemoteSession = Boolean(remoteSessionId);
|
|
289
291
|
const isNativeRemote = !!process.env[NATIVE_REMOTE_ENV];
|
|
290
292
|
if (remoteSessionId) {
|
|
291
293
|
log(`Remote mode: will connect as controller to session ${remoteSessionId}`);
|
|
@@ -301,7 +303,10 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
301
303
|
// This is how we discover what tools/agents the server provides.
|
|
302
304
|
// ══════════════════════════════════════════════
|
|
303
305
|
|
|
304
|
-
const
|
|
306
|
+
const initialInitMsg = fetchInitMessageSync(hubUrl, agentRole);
|
|
307
|
+
const initMsg = initialInitMsg
|
|
308
|
+
? adaptInitMessageForLocalTui(initialInitMsg, { isRemoteSession })
|
|
309
|
+
: null;
|
|
305
310
|
|
|
306
311
|
if (!initMsg) {
|
|
307
312
|
log('Hub not reachable — no tools or agents registered');
|
|
@@ -414,12 +419,23 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
414
419
|
// Titlebar: branding + spinner (registers its own event handlers)
|
|
415
420
|
setupTitlebar(pi);
|
|
416
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
|
+
|
|
417
434
|
// ══════════════════════════════════════════════
|
|
418
435
|
// WebSocket client for runtime communication (tool execution + events)
|
|
419
436
|
// ══════════════════════════════════════════════
|
|
420
437
|
|
|
421
438
|
const client = new HubClient();
|
|
422
|
-
// TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
|
|
423
439
|
client.apiKey = process.env[API_KEY_ENV] || null;
|
|
424
440
|
let cachedInitMessage: InitMessage | null = initMsg;
|
|
425
441
|
let currentSessionId: string | null = initMsg.sessionId ?? null;
|
|
@@ -457,9 +473,10 @@ export function agentuityCoderHub(pi: ExtensionAPI) {
|
|
|
457
473
|
}
|
|
458
474
|
|
|
459
475
|
function applyInitMessage(nextInit: InitMessage): void {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (
|
|
476
|
+
const effectiveInit = adaptInitMessageForLocalTui(nextInit, { isRemoteSession });
|
|
477
|
+
cachedInitMessage = effectiveInit;
|
|
478
|
+
if (effectiveInit.sessionId) currentSessionId = effectiveInit.sessionId;
|
|
479
|
+
if (effectiveInit.config) hubConfig = effectiveInit.config;
|
|
463
480
|
}
|
|
464
481
|
|
|
465
482
|
client.onInitMessage = (nextInit) => {
|
|
@@ -1759,13 +1776,7 @@ async function runSubAgent(
|
|
|
1759
1776
|
const { piSdk, piAi } = await loadPiSdk();
|
|
1760
1777
|
// Runtime-resolved dynamic imports — exact types unavailable statically
|
|
1761
1778
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1762
|
-
const {
|
|
1763
|
-
createAgentSession,
|
|
1764
|
-
DefaultResourceLoader,
|
|
1765
|
-
SessionManager,
|
|
1766
|
-
createCodingTools,
|
|
1767
|
-
createReadOnlyTools,
|
|
1768
|
-
} = piSdk as any;
|
|
1779
|
+
const { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } = piSdk as any;
|
|
1769
1780
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1770
1781
|
const { getModel } = piAi as any;
|
|
1771
1782
|
|
|
@@ -1787,13 +1798,16 @@ async function runSubAgent(
|
|
|
1787
1798
|
// Sub-agents get Hub tools (memory, context7, etc.) via extensionFactories
|
|
1788
1799
|
// so they work in both driver and TUI mode.
|
|
1789
1800
|
const hubTools = agentConfig.hubTools ?? [];
|
|
1801
|
+
const cwd = process.cwd();
|
|
1802
|
+
const agentDir = getAgentDir();
|
|
1790
1803
|
|
|
1791
1804
|
// Resource loader — no extensions (prevents recursive task tool registration),
|
|
1792
1805
|
// no skills, agent's system prompt injected directly.
|
|
1793
1806
|
// Hub tools are injected via extensionFactories so sub-agents can use
|
|
1794
1807
|
// memory_recall, context7_search, etc.
|
|
1795
1808
|
const subLoader = new DefaultResourceLoader({
|
|
1796
|
-
cwd
|
|
1809
|
+
cwd,
|
|
1810
|
+
agentDir,
|
|
1797
1811
|
noExtensions: true,
|
|
1798
1812
|
extensionFactories:
|
|
1799
1813
|
hubTools.length > 0
|
|
@@ -1812,9 +1826,12 @@ async function runSubAgent(
|
|
|
1812
1826
|
});
|
|
1813
1827
|
await subLoader.reload();
|
|
1814
1828
|
|
|
1815
|
-
//
|
|
1816
|
-
const
|
|
1817
|
-
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]));
|
|
1818
1835
|
|
|
1819
1836
|
const { session } = await createAgentSession({
|
|
1820
1837
|
// subModel is already untyped (from dynamic import) — createAgentSession is also dynamically imported
|
|
@@ -1828,7 +1845,8 @@ async function runSubAgent(
|
|
|
1828
1845
|
| 'xhigh',
|
|
1829
1846
|
tools,
|
|
1830
1847
|
resourceLoader: subLoader,
|
|
1831
|
-
|
|
1848
|
+
// Pi now tracks cwd per session, so bind in-memory sub-agents to the actual repo cwd.
|
|
1849
|
+
sessionManager: SessionManager.inMemory(cwd),
|
|
1832
1850
|
});
|
|
1833
1851
|
await session.bindExtensions({});
|
|
1834
1852
|
|
|
@@ -1864,25 +1882,19 @@ async function runSubAgent(
|
|
|
1864
1882
|
|
|
1865
1883
|
if (evt.type === 'tool_execution_start') {
|
|
1866
1884
|
const toolName = evt.toolName || evt.name || evt.tool || 'unknown';
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
else if (args.pattern) toolArgs = String(args.pattern).slice(0, 40);
|
|
1874
|
-
else {
|
|
1875
|
-
const first = Object.values(args)[0];
|
|
1876
|
-
if (first) toolArgs = String(first).slice(0, 40);
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
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
|
+
);
|
|
1879
1891
|
|
|
1880
1892
|
onProgress({
|
|
1881
1893
|
agentName: agentConfig.name,
|
|
1882
1894
|
status: 'tool_start',
|
|
1883
1895
|
toolCallId: typeof evt.toolCallId === 'string' ? evt.toolCallId : undefined,
|
|
1884
|
-
currentTool: toolName,
|
|
1885
|
-
currentToolArgs: toolArgs,
|
|
1896
|
+
currentTool: display.toolName,
|
|
1897
|
+
currentToolArgs: display.toolArgs,
|
|
1886
1898
|
elapsed,
|
|
1887
1899
|
});
|
|
1888
1900
|
} else if (evt.type === 'tool_execution_end') {
|