u-foo 2.3.24 → 2.3.26

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "2.3.24",
3
+ "version": "2.3.26",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
@@ -349,6 +349,27 @@ async function handleEvent(
349
349
  await busSender.flush();
350
350
  }
351
351
 
352
+ function compactToolDetail(value = "", maxLength = 120) {
353
+ const text = String(value || "").replace(/\s+/g, " ").trim();
354
+ if (!text) return "";
355
+ if (text.length <= maxLength) return text;
356
+ return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
357
+ }
358
+
359
+ function summarizeThreadToolCall(event = {}) {
360
+ const name = String(event.name || event.tool || event.tool_name || "tool").trim() || "tool";
361
+ const args = event.args && typeof event.args === "object" ? event.args : {};
362
+ const detail = args.command
363
+ || args.cmd
364
+ || args.code
365
+ || args.path
366
+ || args.file
367
+ || args.target
368
+ || args.query
369
+ || "";
370
+ return [name, compactToolDetail(detail)].filter(Boolean).join(" · ");
371
+ }
372
+
352
373
  async function handleThreadedEvent({
353
374
  agentType,
354
375
  provider,
@@ -372,6 +393,11 @@ async function handleThreadedEvent({
372
393
  } else {
373
394
  plainReplyParts.push(String(event.delta));
374
395
  }
396
+ } else if (event.type === "tool_call") {
397
+ const summary = summarizeThreadToolCall(event);
398
+ if (streamToPublisher && summary) {
399
+ emitStreamDelta(`\nTool: ${summary}\n`);
400
+ }
375
401
  } else if (event.type === "turn_failed") {
376
402
  throw new Error(event.error || `thread turn failed for ${agentType}`);
377
403
  }
@@ -3,6 +3,12 @@ const { version: packageVersion } = require("../../package.json");
3
3
 
4
4
  const ANSI_RESET = "\x1b[0m";
5
5
  const CLAUDE_ORANGE = "\x1b[38;2;217;119;87m";
6
+ const BUS_STATUS_INDICATORS = {
7
+ working: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
8
+ starting: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
9
+ waiting_input: ["∙", "∙∙", "∙∙∙", "∙∙", "∙"],
10
+ blocked: ["!"],
11
+ };
6
12
 
7
13
  function createAgentViewController(options = {}) {
8
14
  const {
@@ -11,6 +17,8 @@ function createAgentViewController(options = {}) {
11
17
  processStdout = process.stdout,
12
18
  now = () => Date.now(),
13
19
  setTimeoutFn = setTimeout,
20
+ setIntervalFn = setInterval,
21
+ clearIntervalFn = clearInterval,
14
22
  computeAgentBar = () => ({ bar: "", windowStart: 0 }),
15
23
  agentBarHints = { normal: "", dashboard: "" },
16
24
  maxAgentWindow = 4,
@@ -23,6 +31,7 @@ function createAgentViewController(options = {}) {
23
31
  setAgentListWindowStart = () => {},
24
32
  getAgentLabel = (id) => id,
25
33
  getAgentStates = () => ({}),
34
+ getAgentActivityMeta = () => ({}),
26
35
  getProjectRoot = () => process.cwd(),
27
36
  setDashboardView = () => {},
28
37
  setScreenGrabKeys = (value) => {
@@ -62,6 +71,10 @@ function createAgentViewController(options = {}) {
62
71
  let busStartupAgentId = "";
63
72
  let busStartupLineCount = 0;
64
73
  let busAgentReplyActive = false;
74
+ let busStatusInterval = null;
75
+ let busStatusIndex = 0;
76
+ let busStatusKey = "";
77
+ let busStatusLocalStartedAt = 0;
65
78
  const originalRender = screen.render.bind(screen);
66
79
  let renderFrozen = false;
67
80
 
@@ -202,6 +215,130 @@ function createAgentViewController(options = {}) {
202
215
  return hasAnsi(text) ? fitAnsiText(text, normalizedWidth) : plainLine(text, normalizedWidth);
203
216
  }
204
217
 
218
+ function parseTimeMs(value) {
219
+ if (Number.isFinite(value)) return Number(value);
220
+ const text = String(value || "").trim();
221
+ if (!text) return NaN;
222
+ const parsed = Date.parse(text);
223
+ return Number.isFinite(parsed) ? parsed : NaN;
224
+ }
225
+
226
+ function formatElapsed(ms = 0) {
227
+ const totalSeconds = Math.max(0, Math.floor(Number(ms) / 1000));
228
+ return `${totalSeconds} s`;
229
+ }
230
+
231
+ function normalizeActivityState(value = "") {
232
+ const state = String(value || "").trim().toLowerCase();
233
+ if (state === "waiting") return "waiting_input";
234
+ if (state === "busy" || state === "processing") return "working";
235
+ return state;
236
+ }
237
+
238
+ function getActivityLabel(state = "") {
239
+ if (state === "working") return "working";
240
+ if (state === "waiting_input") return "waiting";
241
+ if (state === "blocked") return "blocked";
242
+ if (state === "starting") return "starting";
243
+ if (state === "idle" || state === "ready") return "ready";
244
+ return state || "ready";
245
+ }
246
+
247
+ function isTimedActivityState(state = "") {
248
+ return state === "working"
249
+ || state === "waiting_input"
250
+ || state === "blocked"
251
+ || state === "starting";
252
+ }
253
+
254
+ function asActivityObject(value) {
255
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
256
+ }
257
+
258
+ function pickActivityDetail(meta = {}) {
259
+ const candidates = [
260
+ meta.activity_detail,
261
+ meta.detail,
262
+ meta.status_text,
263
+ meta.command,
264
+ meta.tool_name,
265
+ meta.tool,
266
+ ];
267
+ return String(candidates.find((item) => String(item || "").trim()) || "").trim();
268
+ }
269
+
270
+ function getViewingAgentActivity() {
271
+ const states = getAgentStates() || {};
272
+ const stateEntry = viewingAgent && states ? states[viewingAgent] : "";
273
+ const stateObject = asActivityObject(stateEntry);
274
+ const meta = {
275
+ ...(stateObject || {}),
276
+ ...(asActivityObject(getAgentActivityMeta(viewingAgent)) || {}),
277
+ };
278
+ const state = normalizeActivityState(meta.activity_state || meta.state || (stateObject ? "" : stateEntry) || "");
279
+ const detail = pickActivityDetail(meta);
280
+ const sinceMs = parseTimeMs(meta.activity_since || meta.since || meta.updated_at || meta.updatedAt);
281
+ return { state: state || "ready", detail, sinceMs };
282
+ }
283
+
284
+ function resolveBusStatus() {
285
+ const activity = getViewingAgentActivity();
286
+ const state = activity.state || "ready";
287
+ const timed = isTimedActivityState(state);
288
+ const key = `${viewingAgent || ""}:${state}:${activity.detail || ""}`;
289
+ if (key !== busStatusKey) {
290
+ busStatusKey = key;
291
+ busStatusIndex = 0;
292
+ busStatusLocalStartedAt = now();
293
+ }
294
+ const startedAt = timed && Number.isFinite(activity.sinceMs)
295
+ ? activity.sinceMs
296
+ : busStatusLocalStartedAt;
297
+ return {
298
+ ...activity,
299
+ state,
300
+ label: getActivityLabel(state),
301
+ timed,
302
+ startedAt,
303
+ };
304
+ }
305
+
306
+ function buildBusStatusLine(width = 80, status = resolveBusStatus()) {
307
+ const normalizedWidth = Math.max(1, width);
308
+ const detail = status.detail ? ` · ${status.detail}` : "";
309
+ if (status.timed) {
310
+ const indicators = BUS_STATUS_INDICATORS[status.state] || BUS_STATUS_INDICATORS.working;
311
+ const indicator = indicators[busStatusIndex % indicators.length] || "";
312
+ const elapsed = formatElapsed(now() - status.startedAt);
313
+ return fitText(`${indicator} ${status.label} · ${elapsed}${detail}`, normalizedWidth);
314
+ }
315
+ if (normalizedWidth < 32) return fitText(`ufoo · ${status.label}`, normalizedWidth);
316
+ if (normalizedWidth < 48) return fitText(`ufoo · ${status.label} · Enter send`, normalizedWidth);
317
+ return fitText(`ufoo · ${status.label} · Enter send · Esc back${detail}`, normalizedWidth);
318
+ }
319
+
320
+ function stopBusStatusTimer() {
321
+ if (!busStatusInterval) return;
322
+ clearIntervalFn(busStatusInterval);
323
+ busStatusInterval = null;
324
+ }
325
+
326
+ function syncBusStatusTimer(status) {
327
+ const shouldTick = currentView === "agent" && agentViewUsesBus && status && status.timed;
328
+ if (!shouldTick) {
329
+ stopBusStatusTimer();
330
+ return;
331
+ }
332
+ if (busStatusInterval) return;
333
+ busStatusInterval = setIntervalFn(() => {
334
+ busStatusIndex += 1;
335
+ renderBusView();
336
+ }, 1000);
337
+ if (busStatusInterval && typeof busStatusInterval.unref === "function") {
338
+ busStatusInterval.unref();
339
+ }
340
+ }
341
+
205
342
  function sliceDisplayCells(text = "", startCell = 0, maxCells = 1) {
206
343
  const targetStart = Math.max(0, startCell);
207
344
  const targetWidth = Math.max(1, maxCells);
@@ -456,12 +593,16 @@ function createAgentViewController(options = {}) {
456
593
  const logContentTop = 1;
457
594
  const logContentBottom = Math.max(logContentTop, inputTop - 1);
458
595
  const logContentHeight = Math.max(1, logContentBottom - logContentTop + 1);
596
+ const status = resolveBusStatus();
597
+ const logRows = Math.max(0, logContentHeight - 1);
598
+ const statusRow = logContentTop + logRows;
459
599
 
460
600
  processStdout.write("\x1b[?25l");
461
- const visibleLines = getWrappedBusLogLines(width).slice(-logContentHeight);
462
- for (let i = 0; i < logContentHeight; i += 1) {
601
+ const visibleLines = getWrappedBusLogLines(width).slice(-logRows);
602
+ for (let i = 0; i < logRows; i += 1) {
463
603
  writeAt(logContentTop + i, logLine(visibleLines[i] || "", width));
464
604
  }
605
+ writeAt(statusRow, logLine(buildBusStatusLine(width, status), width));
465
606
 
466
607
  writeAt(inputTop, horizontalLine(width));
467
608
  const viewport = getBusInputViewport(width);
@@ -471,6 +612,7 @@ function createAgentViewController(options = {}) {
471
612
  renderAgentDashboard();
472
613
  const cursorCol = clamp(3 + viewport.cursorCol, 1, width);
473
614
  processStdout.write(`\x1b[${inputTop + 1};${cursorCol}H\x1b[?25h`);
615
+ syncBusStatusTimer(status);
474
616
  }
475
617
 
476
618
  function renderAgentDashboard() {
@@ -566,6 +708,7 @@ function createAgentViewController(options = {}) {
566
708
  agentViewUsesBus = false;
567
709
  agentOutputSuppressed = false;
568
710
  agentBarVisible = false;
711
+ stopBusStatusTimer();
569
712
  busInputValue = "";
570
713
  busInputCursor = 0;
571
714
  busLogLines = [];
@@ -871,6 +1014,16 @@ function createAgentViewController(options = {}) {
871
1014
  }
872
1015
  }
873
1016
 
1017
+ function refreshAgentView() {
1018
+ if (currentView !== "agent") return false;
1019
+ if (agentViewUsesBus) {
1020
+ renderBusView();
1021
+ } else {
1022
+ renderAgentDashboard();
1023
+ }
1024
+ return true;
1025
+ }
1026
+
874
1027
  function isAgentBarVisible() {
875
1028
  return agentBarVisible;
876
1029
  }
@@ -882,6 +1035,7 @@ function createAgentViewController(options = {}) {
882
1035
  getAgentInputSuppressUntil,
883
1036
  getAgentOutputSuppressed,
884
1037
  setAgentOutputSuppressed,
1038
+ refreshAgentView,
885
1039
  isAgentBarVisible,
886
1040
  renderAgentDashboard,
887
1041
  setAgentBarVisible,
@@ -78,6 +78,14 @@ function collectHostLaunchRequestContext(env = process.env) {
78
78
  return context;
79
79
  }
80
80
 
81
+ function collectTerminalLaunchRequestContext(resolveTerminalApp = defaultResolveTerminalApp) {
82
+ const terminalApp = String(resolveTerminalApp() || "").trim().toLowerCase();
83
+ if (terminalApp === "terminal" || terminalApp === "iterm2") {
84
+ return { terminal_app: terminalApp };
85
+ }
86
+ return {};
87
+ }
88
+
81
89
  async function withCapturedConsole(capture, fn) {
82
90
  const originalLog = console.log;
83
91
  const originalError = console.error;
@@ -551,11 +559,8 @@ function createCommandExecutor(options = {}) {
551
559
  prompt_profile: promptProfile,
552
560
  launch_scope: launchScope,
553
561
  ...collectHostLaunchRequestContext(),
562
+ ...collectTerminalLaunchRequestContext(resolveTerminalApp),
554
563
  };
555
- const terminalApp = String(resolveTerminalApp() || "").trim().toLowerCase();
556
- if (terminalApp === "terminal" || terminalApp === "iterm2") {
557
- request.terminal_app = terminalApp;
558
- }
559
564
  send(request);
560
565
  schedule(requestStatus, 1000);
561
566
  } catch (err) {
@@ -696,6 +701,7 @@ function createCommandExecutor(options = {}) {
696
701
  prompt_profile: profile,
697
702
  launch_scope: launchScope,
698
703
  ...collectHostLaunchRequestContext(),
704
+ ...collectTerminalLaunchRequestContext(resolveTerminalApp),
699
705
  });
700
706
  schedule(requestStatus, 1000);
701
707
  } catch (err) {
@@ -1106,6 +1112,7 @@ function createCommandExecutor(options = {}) {
1106
1112
  instance,
1107
1113
  dry_run: dryRun,
1108
1114
  ...collectHostLaunchRequestContext(),
1115
+ ...collectTerminalLaunchRequestContext(resolveTerminalApp),
1109
1116
  });
1110
1117
  schedule(requestStatus, 1000);
1111
1118
  return;
@@ -1638,4 +1645,5 @@ function createCommandExecutor(options = {}) {
1638
1645
  module.exports = {
1639
1646
  createCommandExecutor,
1640
1647
  collectHostLaunchRequestContext,
1648
+ collectTerminalLaunchRequestContext,
1641
1649
  };
@@ -76,7 +76,7 @@ function createDaemonMessageRouter(options = {}) {
76
76
  const key = typeof data.key === "string" ? data.key : "";
77
77
  if (isLikelySubscriberId(key)) {
78
78
  if (data.phase === BUS_STATUS_PHASES.START) {
79
- setTransientAgentState(key, "working");
79
+ setTransientAgentState(key, "working", { detail: text });
80
80
  } else if (data.phase === BUS_STATUS_PHASES.DONE || data.phase === BUS_STATUS_PHASES.ERROR) {
81
81
  clearTransientAgentState(key);
82
82
  }
@@ -354,6 +354,18 @@ function createDaemonMessageRouter(options = {}) {
354
354
  function handleBusMessage(msg) {
355
355
  const data = msg.data || {};
356
356
  if (data.event === "activity_state_changed") {
357
+ const agentId = String(data.subscriber || data.publisher || "").trim();
358
+ const state = String(data.state || data.activity_state || "").trim();
359
+ const detailSource = data.detail || (data.data && data.data.detail) || data.message || "";
360
+ if (agentId && state) {
361
+ const normalized = state.toLowerCase();
362
+ if (normalized === "idle" || normalized === "ready") {
363
+ clearTransientAgentState(agentId);
364
+ } else {
365
+ setTransientAgentState(agentId, state, { detail: detailSource });
366
+ }
367
+ refreshDashboard();
368
+ }
357
369
  requestStatus();
358
370
  return true;
359
371
  }
package/src/chat/index.js CHANGED
@@ -61,6 +61,7 @@ const { loadPromptProfileRegistry } = require("../group/promptProfiles");
61
61
  const {
62
62
  DEFAULT_TRANSIENT_AGENT_STATE_TTL_MS,
63
63
  setTransientAgentState: setTransientAgentStateValue,
64
+ getTransientAgentStateEntry,
64
65
  getTransientAgentState,
65
66
  pruneTransientAgentStates,
66
67
  } = require("./transientAgentState");
@@ -1340,7 +1341,11 @@ async function runChat(projectRoot, options = {}) {
1340
1341
  selectedAgentIndex = 0;
1341
1342
  }
1342
1343
  }
1343
- renderAgentDashboard();
1344
+ if (agentViewController && typeof agentViewController.refreshAgentView === "function") {
1345
+ agentViewController.refreshAgentView();
1346
+ } else {
1347
+ renderAgentDashboard();
1348
+ }
1344
1349
  return;
1345
1350
  }
1346
1351
  if (focusMode === "dashboard") {
@@ -1537,13 +1542,39 @@ async function runChat(projectRoot, options = {}) {
1537
1542
  getAgentLabel,
1538
1543
  getAgentStates: () => {
1539
1544
  const states = {};
1540
- if (activeAgentMetaMap) {
1541
- for (const [id, meta] of activeAgentMetaMap) {
1542
- if (meta && meta.activity_state) states[id] = meta.activity_state;
1545
+ for (const id of activeAgents) {
1546
+ let state = "";
1547
+ if (activeAgentMetaMap) {
1548
+ const meta = activeAgentMetaMap.get(id);
1549
+ if (meta && meta.activity_state) state = meta.activity_state;
1550
+ }
1551
+ if (!state) {
1552
+ state = getTransientAgentState(transientAgentStateMap, id, {
1553
+ ttlMs: DEFAULT_TRANSIENT_AGENT_STATE_TTL_MS,
1554
+ });
1543
1555
  }
1556
+ if (state) states[id] = state;
1544
1557
  }
1545
1558
  return states;
1546
1559
  },
1560
+ getAgentActivityMeta: (agentId) => {
1561
+ const id = String(agentId || "").trim();
1562
+ const meta = activeAgentMetaMap && activeAgentMetaMap.get(id)
1563
+ ? { ...activeAgentMetaMap.get(id) }
1564
+ : {};
1565
+ const transient = getTransientAgentStateEntry(transientAgentStateMap, id, {
1566
+ ttlMs: DEFAULT_TRANSIENT_AGENT_STATE_TTL_MS,
1567
+ });
1568
+ if (transient) {
1569
+ const previousState = meta.activity_state;
1570
+ meta.activity_state = transient.state;
1571
+ if ((!meta.activity_since || previousState !== transient.state) && Number.isFinite(transient.updatedAt)) {
1572
+ meta.activity_since = new Date(transient.updatedAt).toISOString();
1573
+ }
1574
+ if (transient.detail) meta.activity_detail = transient.detail;
1575
+ }
1576
+ return meta;
1577
+ },
1547
1578
  getProjectRoot: () => activeProjectRoot,
1548
1579
  setDashboardView: (value) => {
1549
1580
  dashboardView = value;
@@ -1643,9 +1674,9 @@ async function runChat(projectRoot, options = {}) {
1643
1674
  appendStreamDelta,
1644
1675
  finalizeStream,
1645
1676
  hasStream: (publisher) => streamTracker.hasStream(publisher),
1646
- setTransientAgentState: (agentId, state) => {
1677
+ setTransientAgentState: (agentId, state, options) => {
1647
1678
  if (!agentId || !state) return;
1648
- setTransientAgentStateValue(transientAgentStateMap, agentId, state);
1679
+ setTransientAgentStateValue(transientAgentStateMap, agentId, state, options);
1649
1680
  },
1650
1681
  clearTransientAgentState: (agentId) => {
1651
1682
  if (!agentId) return;
@@ -1653,7 +1684,11 @@ async function runChat(projectRoot, options = {}) {
1653
1684
  },
1654
1685
  refreshDashboard: () => {
1655
1686
  if (getCurrentView() === "agent") {
1656
- renderAgentDashboard();
1687
+ if (agentViewController && typeof agentViewController.refreshAgentView === "function") {
1688
+ agentViewController.refreshAgentView();
1689
+ } else {
1690
+ renderAgentDashboard();
1691
+ }
1657
1692
  return;
1658
1693
  }
1659
1694
  renderDashboard();
@@ -6,23 +6,38 @@ function normalizeNow(now) {
6
6
  return Number.isFinite(now) ? now : Date.now();
7
7
  }
8
8
 
9
- function setTransientAgentState(store, agentId, state, now = Date.now()) {
9
+ function normalizeSetOptions(nowOrOptions, detailArg = "") {
10
+ if (nowOrOptions && typeof nowOrOptions === "object") {
11
+ return {
12
+ now: normalizeNow(nowOrOptions.now),
13
+ detail: String(nowOrOptions.detail || "").trim(),
14
+ };
15
+ }
16
+ return {
17
+ now: normalizeNow(nowOrOptions),
18
+ detail: String(detailArg || "").trim(),
19
+ };
20
+ }
21
+
22
+ function setTransientAgentState(store, agentId, state, nowOrOptions = Date.now(), detailArg = "") {
10
23
  if (!(store instanceof Map)) return;
11
24
  const id = String(agentId || "").trim();
12
25
  const nextState = String(state || "").trim();
13
26
  if (!id || !nextState) return;
27
+ const options = normalizeSetOptions(nowOrOptions, detailArg);
14
28
  store.set(id, {
15
29
  state: nextState,
16
- updatedAt: normalizeNow(now),
30
+ updatedAt: options.now,
31
+ detail: options.detail,
17
32
  });
18
33
  }
19
34
 
20
- function getTransientAgentState(store, agentId, options = {}) {
21
- if (!(store instanceof Map)) return "";
35
+ function getTransientAgentStateEntry(store, agentId, options = {}) {
36
+ if (!(store instanceof Map)) return null;
22
37
  const id = String(agentId || "").trim();
23
- if (!id) return "";
38
+ if (!id) return null;
24
39
  const entry = store.get(id);
25
- if (!entry) return "";
40
+ if (!entry) return null;
26
41
 
27
42
  const ttlMs = Number.isFinite(options.ttlMs)
28
43
  ? Math.max(0, Math.trunc(options.ttlMs))
@@ -32,16 +47,23 @@ function getTransientAgentState(store, agentId, options = {}) {
32
47
  const updatedAt = typeof entry === "object" && Number.isFinite(entry.updatedAt)
33
48
  ? entry.updatedAt
34
49
  : now;
50
+ const detail = typeof entry === "object" ? String(entry.detail || "").trim() : "";
35
51
 
36
52
  if (!state) {
37
53
  store.delete(id);
38
- return "";
54
+ return null;
39
55
  }
40
56
  if (ttlMs > 0 && now - updatedAt > ttlMs) {
41
57
  store.delete(id);
42
- return "";
58
+ return null;
43
59
  }
44
- return state;
60
+ return { state, updatedAt, detail };
61
+ }
62
+
63
+ function getTransientAgentState(store, agentId, options = {}) {
64
+ const entry = getTransientAgentStateEntry(store, agentId, options);
65
+ if (!entry) return "";
66
+ return entry.state;
45
67
  }
46
68
 
47
69
  function pruneTransientAgentStates(store, activeAgentIds = [], options = {}) {
@@ -59,6 +81,7 @@ function pruneTransientAgentStates(store, activeAgentIds = [], options = {}) {
59
81
  module.exports = {
60
82
  DEFAULT_TRANSIENT_AGENT_STATE_TTL_MS,
61
83
  setTransientAgentState,
84
+ getTransientAgentStateEntry,
62
85
  getTransientAgentState,
63
86
  pruneTransientAgentStates,
64
87
  };
@@ -242,11 +242,13 @@ function buildLaunchHostContext(params = {}) {
242
242
  const hostDaemonSock = asTrimmedString(params.host_daemon_sock || params.hostDaemonSock);
243
243
  const hostName = asTrimmedString(params.host_name || params.hostName);
244
244
  const hostSessionId = asTrimmedString(params.host_session_id || params.hostSessionId);
245
+ const terminalApp = asTrimmedString(params.terminal_app || params.terminalApp);
245
246
  const context = {};
246
247
  if (hostInjectSock) context.host_inject_sock = hostInjectSock;
247
248
  if (hostDaemonSock) context.host_daemon_sock = hostDaemonSock;
248
249
  if (hostName) context.host_name = hostName;
249
250
  if (hostSessionId) context.host_session_id = hostSessionId;
251
+ if (terminalApp) context.terminal_app = terminalApp;
250
252
  if (params.host_capabilities && typeof params.host_capabilities === "object") {
251
253
  context.host_capabilities = { ...params.host_capabilities };
252
254
  } else if (params.hostCapabilities && typeof params.hostCapabilities === "object") {
@@ -1753,6 +1753,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1753
1753
  const hostDaemonSock = req.host_daemon_sock || req.hostDaemonSock || "";
1754
1754
  const hostName = req.host_name || req.hostName || "";
1755
1755
  const hostSessionId = req.host_session_id || req.hostSessionId || "";
1756
+ const terminalApp = req.terminal_app || req.terminalApp || "";
1756
1757
  const hostCapabilities =
1757
1758
  req.host_capabilities && typeof req.host_capabilities === "object"
1758
1759
  ? req.host_capabilities
@@ -1768,6 +1769,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1768
1769
  host_daemon_sock: hostDaemonSock,
1769
1770
  host_name: hostName,
1770
1771
  host_session_id: hostSessionId,
1772
+ terminal_app: terminalApp,
1771
1773
  host_capabilities: hostCapabilities,
1772
1774
  });
1773
1775
  const ok = result && result.ok !== false;
package/src/daemon/ops.js CHANGED
@@ -152,14 +152,25 @@ function resolveHostLaunchContext(options = {}) {
152
152
  };
153
153
  }
154
154
 
155
+ function resolveNativeTerminalApp(options = {}) {
156
+ const explicit = normalizeTerminalAppPreference(options.terminalApp);
157
+ if (explicit) return explicit;
158
+
159
+ const termProgram = normalizeTerminalAppPreference(process.env.TERM_PROGRAM || "");
160
+ if (termProgram) return termProgram;
161
+ if (process.env.ITERM_SESSION_ID) return "iterm2";
162
+ return "";
163
+ }
164
+
155
165
  function resolveConfiguredLaunchMode(configuredMode = "", options = {}) {
156
166
  const mode = normalizeOptionalString(configuredMode);
157
167
  if (mode === "internal" || mode === "internal-pty" || mode === "tmux" || mode === "terminal" || mode === "host") {
158
168
  return mode;
159
169
  }
160
- const hostContext = resolveHostLaunchContext(options);
161
- if (hostContext.hostDaemonSock) return "host";
162
170
  if (process.env.TMUX_PANE) return "tmux";
171
+ const hostContext = resolveHostLaunchContext(options);
172
+ const nativeTerminalApp = resolveNativeTerminalApp(options);
173
+ if (hostContext.hostDaemonSock && !nativeTerminalApp) return "host";
163
174
  return "terminal";
164
175
  }
165
176
 
@@ -1303,4 +1314,14 @@ async function closeAgent(projectRoot, agentId) {
1303
1314
  };
1304
1315
  }
1305
1316
 
1306
- module.exports = { launchAgent, closeAgent, getRecoverableAgents, resumeAgents };
1317
+ module.exports = {
1318
+ launchAgent,
1319
+ closeAgent,
1320
+ getRecoverableAgents,
1321
+ resumeAgents,
1322
+ __private: {
1323
+ resolveConfiguredLaunchMode,
1324
+ resolveHostLaunchContext,
1325
+ resolveNativeTerminalApp,
1326
+ },
1327
+ };