@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.
Files changed (60) 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 -41
  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 -0
  28. package/dist/protocol.d.ts.map +1 -1
  29. package/dist/remote-runtime.d.ts +16 -0
  30. package/dist/remote-runtime.d.ts.map +1 -0
  31. package/dist/remote-runtime.js +18 -0
  32. package/dist/remote-runtime.js.map +1 -0
  33. package/dist/remote-session.d.ts.map +1 -1
  34. package/dist/remote-session.js +17 -9
  35. package/dist/remote-session.js.map +1 -1
  36. package/dist/remote-tui.d.ts +4 -4
  37. package/dist/remote-tui.d.ts.map +1 -1
  38. package/dist/remote-tui.js +72 -27
  39. package/dist/remote-tui.js.map +1 -1
  40. package/dist/renderers.d.ts.map +1 -1
  41. package/dist/renderers.js +53 -1
  42. package/dist/renderers.js.map +1 -1
  43. package/dist/subagent-tool-selection.d.ts +3 -0
  44. package/dist/subagent-tool-selection.d.ts.map +1 -0
  45. package/dist/subagent-tool-selection.js +22 -0
  46. package/dist/subagent-tool-selection.js.map +1 -0
  47. package/package.json +5 -5
  48. package/src/agentuity-cli.ts +225 -0
  49. package/src/auth.ts +75 -0
  50. package/src/client.ts +6 -6
  51. package/src/hub-overlay-state.ts +4 -1
  52. package/src/hub-overlay.ts +28 -14
  53. package/src/index.ts +53 -41
  54. package/src/local-init-filter.ts +54 -0
  55. package/src/protocol.ts +4 -0
  56. package/src/remote-runtime.ts +45 -0
  57. package/src/remote-session.ts +17 -9
  58. package/src/remote-tui.ts +92 -32
  59. package/src/renderers.ts +61 -1
  60. 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';
@@ -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
- if (apiKey) headers[API_KEY_HEADER] = apiKey;
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
- // TODO: Remove/Change when we get Agentuity service level auth enabled, this is just temporary
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 initMsg = fetchInitMessageSync(hubUrl, agentRole);
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
- cachedInitMessage = nextInit;
461
- if (nextInit.sessionId) currentSessionId = nextInit.sessionId;
462
- 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;
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: process.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
- // Select tools based on readOnly flag
1816
- const cwd = process.cwd();
1817
- 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]));
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
- 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),
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
- let toolArgs = '';
1868
- if (evt.args && typeof evt.args === 'object') {
1869
- const args = evt.args as Record<string, unknown>;
1870
- if (args.command) toolArgs = String(args.command).slice(0, 60);
1871
- else if (args.filePath || args.path)
1872
- toolArgs = String(args.filePath || args.path);
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') {