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 +1 -1
- package/src/agent/internalRunner.js +26 -0
- package/src/chat/agentViewController.js +156 -2
- package/src/chat/commandExecutor.js +12 -4
- package/src/chat/daemonMessageRouter.js +13 -1
- package/src/chat/index.js +42 -7
- package/src/chat/transientAgentState.js +32 -9
- package/src/daemon/groupOrchestrator.js +2 -0
- package/src/daemon/index.js +2 -0
- package/src/daemon/ops.js +24 -3
package/package.json
CHANGED
|
@@ -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(-
|
|
462
|
-
for (let i = 0; i <
|
|
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
|
-
|
|
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
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
30
|
+
updatedAt: options.now,
|
|
31
|
+
detail: options.detail,
|
|
17
32
|
});
|
|
18
33
|
}
|
|
19
34
|
|
|
20
|
-
function
|
|
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") {
|
package/src/daemon/index.js
CHANGED
|
@@ -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 = {
|
|
1317
|
+
module.exports = {
|
|
1318
|
+
launchAgent,
|
|
1319
|
+
closeAgent,
|
|
1320
|
+
getRecoverableAgents,
|
|
1321
|
+
resumeAgents,
|
|
1322
|
+
__private: {
|
|
1323
|
+
resolveConfiguredLaunchMode,
|
|
1324
|
+
resolveHostLaunchContext,
|
|
1325
|
+
resolveNativeTerminalApp,
|
|
1326
|
+
},
|
|
1327
|
+
};
|