@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.
Files changed (50) hide show
  1. package/dist/agentuity-cli.d.ts +12 -0
  2. package/dist/agentuity-cli.d.ts.map +1 -0
  3. package/dist/agentuity-cli.js +178 -0
  4. package/dist/agentuity-cli.js.map +1 -0
  5. package/dist/auth.d.ts +5 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +62 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/client.d.ts +1 -1
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +5 -6
  12. package/dist/client.js.map +1 -1
  13. package/dist/hub-overlay-state.d.ts.map +1 -1
  14. package/dist/hub-overlay-state.js +3 -1
  15. package/dist/hub-overlay-state.js.map +1 -1
  16. package/dist/hub-overlay.d.ts.map +1 -1
  17. package/dist/hub-overlay.js +18 -12
  18. package/dist/hub-overlay.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +50 -39
  22. package/dist/index.js.map +1 -1
  23. package/dist/local-init-filter.d.ts +5 -0
  24. package/dist/local-init-filter.d.ts.map +1 -0
  25. package/dist/local-init-filter.js +40 -0
  26. package/dist/local-init-filter.js.map +1 -0
  27. package/dist/protocol.d.ts +4 -2
  28. package/dist/protocol.d.ts.map +1 -1
  29. package/dist/remote-session.d.ts.map +1 -1
  30. package/dist/remote-session.js +17 -9
  31. package/dist/remote-session.js.map +1 -1
  32. package/dist/renderers.d.ts.map +1 -1
  33. package/dist/renderers.js +53 -1
  34. package/dist/renderers.js.map +1 -1
  35. package/dist/subagent-tool-selection.d.ts +3 -0
  36. package/dist/subagent-tool-selection.d.ts.map +1 -0
  37. package/dist/subagent-tool-selection.js +22 -0
  38. package/dist/subagent-tool-selection.js.map +1 -0
  39. package/package.json +5 -5
  40. package/src/agentuity-cli.ts +225 -0
  41. package/src/auth.ts +75 -0
  42. package/src/client.ts +6 -6
  43. package/src/hub-overlay-state.ts +4 -1
  44. package/src/hub-overlay.ts +28 -14
  45. package/src/index.ts +53 -39
  46. package/src/local-init-filter.ts +54 -0
  47. package/src/protocol.ts +4 -2
  48. package/src/remote-session.ts +17 -9
  49. package/src/renderers.ts +61 -1
  50. 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
- /** API key for Hub authentication (sent as x-agentuity-auth-api-key header) */
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
- // TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
262
+ const orgId = process.env.AGENTUITY_ORGID;
263
263
  // Bun extension: custom headers on WebSocket upgrade request
264
- const ws = this.apiKey
265
- ? new WebSocket(wsUrl, { headers: { 'x-agentuity-auth-api-key': this.apiKey } })
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) => {
@@ -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
- append('output', `[tool_call] ${toolName}\n\n`, entry.taskId);
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
 
@@ -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 headers: Record<string, string> = {
1161
- accept: 'application/json',
1162
- ...(init?.headers && typeof init.headers === 'object'
1163
- ? (init.headers as Record<string, string>)
1164
- : {}),
1165
- };
1166
- if (apiKey) headers['x-agentuity-auth-api-key'] = apiKey;
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 sseHeaders: Record<string, string> = { accept: 'text/event-stream' };
1770
- if (apiKey) sseHeaders['x-agentuity-auth-api-key'] = apiKey;
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
- return `tool_result ${name}`;
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 toolName = typeof data?.currentTool === 'string' ? data.currentTool : '';
2189
+ const rawToolName = typeof data?.currentTool === 'string' ? data.currentTool : '';
2178
2190
  const toolArgsRaw = typeof data?.currentToolArgs === 'string' ? data.currentToolArgs : '';
2179
- const toolArgs = toolArgsRaw ? truncateToWidth(normalize(toolArgsRaw), 80) : '';
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 type {
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
- // TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
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
- if (apiKey) headers[API_KEY_HEADER] = apiKey;
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
- // TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
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 initMsg = fetchInitMessageSync(hubUrl, agentRole);
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
- cachedInitMessage = nextInit;
459
- if (nextInit.sessionId) currentSessionId = nextInit.sessionId;
460
- if (nextInit.config) hubConfig = nextInit.config;
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: process.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
- // Select tools based on readOnly flag
1814
- const cwd = process.cwd();
1815
- const tools = agentConfig.readOnly ? createReadOnlyTools(cwd) : createCodingTools(cwd);
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
- sessionManager: SessionManager.inMemory('/tmp'),
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
- let toolArgs = '';
1866
- if (evt.args && typeof evt.args === 'object') {
1867
- const args = evt.args as Record<string, unknown>;
1868
- if (args.command) toolArgs = String(args.command).slice(0, 60);
1869
- else if (args.filePath || args.path)
1870
- toolArgs = String(args.filePath || args.path);
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') {