parallel-codex-tui 0.1.4 → 0.1.5
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/.parallel-codex/config.example.toml +46 -0
- package/README.md +76 -17
- package/dist/cli-startup-preflight.js +18 -0
- package/dist/cli-startup-recovery.js +13 -1
- package/dist/cli.js +18 -6
- package/dist/core/collaboration-timeline.js +9 -2
- package/dist/core/config.js +161 -103
- package/dist/core/router.js +1 -1
- package/dist/core/session-index.js +234 -61
- package/dist/core/session-manager.js +25 -1
- package/dist/core/task-session-details.js +175 -0
- package/dist/core/task-state-machine.js +10 -9
- package/dist/doctor.js +58 -39
- package/dist/domain/schemas.js +15 -1
- package/dist/orchestrator/collaboration-channel.js +35 -3
- package/dist/orchestrator/final-acceptance.js +86 -0
- package/dist/orchestrator/orchestrator.js +405 -69
- package/dist/orchestrator/prompts.js +42 -3
- package/dist/orchestrator/workspace-sandbox.js +16 -0
- package/dist/tui/App.js +401 -52
- package/dist/tui/AppShell.js +9 -3
- package/dist/tui/FeatureBoardView.js +7 -2
- package/dist/tui/InputBar.js +82 -15
- package/dist/tui/StatusBar.js +1 -1
- package/dist/tui/StatusDetailView.js +164 -0
- package/dist/tui/TaskSessionDetailView.js +222 -0
- package/dist/tui/TaskSessionsView.js +5 -2
- package/dist/tui/WorkerOutputView.js +1 -0
- package/dist/tui/WorkerOverviewView.js +23 -4
- package/dist/tui/keyboard.js +3 -0
- package/dist/tui/status-line.js +42 -0
- package/dist/version.js +1 -1
- package/dist/workers/capabilities.js +4 -3
- package/dist/workers/live-probe.js +4 -3
- package/dist/workers/mock-adapter.js +37 -5
- package/dist/workers/native-attach.js +32 -17
- package/dist/workers/process-adapter.js +2 -0
- package/dist/workers/provider.js +26 -0
- package/dist/workers/registry.js +12 -22
- package/package.json +7 -1
package/dist/tui/AppShell.js
CHANGED
|
@@ -236,12 +236,18 @@ function viewLabel(view) {
|
|
|
236
236
|
return shortViewLabel(view);
|
|
237
237
|
}
|
|
238
238
|
function shortcutHint(view) {
|
|
239
|
+
if (view === "status") {
|
|
240
|
+
return "^S back";
|
|
241
|
+
}
|
|
239
242
|
if (view === "native") {
|
|
240
|
-
return "^] logs";
|
|
243
|
+
return "^S status · ^] logs";
|
|
241
244
|
}
|
|
242
|
-
return "^C exit";
|
|
245
|
+
return "^S status · ^C exit";
|
|
243
246
|
}
|
|
244
247
|
function shortViewLabel(view) {
|
|
248
|
+
if (view === "status") {
|
|
249
|
+
return "status";
|
|
250
|
+
}
|
|
245
251
|
if (view === "worker") {
|
|
246
252
|
return "logs";
|
|
247
253
|
}
|
|
@@ -266,7 +272,7 @@ function shortViewLabel(view) {
|
|
|
266
272
|
return "chat";
|
|
267
273
|
}
|
|
268
274
|
function shortShortcutHint(view) {
|
|
269
|
-
return view === "native" ? "^]" : "^C";
|
|
275
|
+
return view === "native" ? "^]" : view === "status" ? "^S" : "^C";
|
|
270
276
|
}
|
|
271
277
|
function compactHeaderProject(cwd, maxLength) {
|
|
272
278
|
const project = basename(cwd) || cwd;
|
|
@@ -99,6 +99,7 @@ function featureBoardSummary(timeline, width) {
|
|
|
99
99
|
const approved = timeline.features.filter((feature) => feature.state === "approved").length;
|
|
100
100
|
const active = timeline.features.filter((feature) => featureBoardStateIsActive(feature.state)).length;
|
|
101
101
|
const revision = timeline.features.filter((feature) => feature.state === "revision_needed").length;
|
|
102
|
+
const paused = timeline.features.filter((feature) => feature.state === "paused").length;
|
|
102
103
|
const failed = timeline.features.filter((feature) => feature.state === "failed" || feature.state === "cancelled").length;
|
|
103
104
|
const blocked = timeline.features.filter((feature) => featureBoardBlockedDependencies(timeline, feature).length > 0).length;
|
|
104
105
|
const full = [
|
|
@@ -106,6 +107,7 @@ function featureBoardSummary(timeline, width) {
|
|
|
106
107
|
...(approved > 0 ? [`${approved} approved`] : []),
|
|
107
108
|
...(active > 0 ? [`${active} active`] : []),
|
|
108
109
|
...(revision > 0 ? [`${revision} ${revision === 1 ? "revision" : "revisions"}`] : []),
|
|
110
|
+
...(paused > 0 ? [`${paused} paused`] : []),
|
|
109
111
|
...(blocked > 0 ? [`${blocked} blocked`] : []),
|
|
110
112
|
...(failed > 0 ? [`${failed} failed`] : [])
|
|
111
113
|
].join(" · ");
|
|
@@ -139,12 +141,15 @@ function featureBoardEvidenceText(timeline, feature, width) {
|
|
|
139
141
|
const dependencyText = dependencies.length > 0
|
|
140
142
|
? `deps ${dependencies.map((item) => safeFeatureBoardText(item.title)).join(", ")}`
|
|
141
143
|
: "independent";
|
|
144
|
+
const assignmentText = feature.actorEngine && feature.criticEngine
|
|
145
|
+
? `actor ${feature.actorEngine} · critic ${feature.criticEngine}`
|
|
146
|
+
: "";
|
|
142
147
|
const evidence = feature.latestFinding
|
|
143
148
|
? `finding · ${safeFeatureBoardText(feature.latestFinding)}`
|
|
144
149
|
: feature.latestReply
|
|
145
150
|
? `reply · ${safeFeatureBoardText(feature.latestReply)}`
|
|
146
151
|
: safeFeatureBoardText(feature.description);
|
|
147
|
-
return fitFeatureBoardText(` ${[dependencyText, evidence].filter(Boolean).join(" · ")}`, width);
|
|
152
|
+
return fitFeatureBoardText(` ${[assignmentText, dependencyText, evidence].filter(Boolean).join(" · ")}`, width);
|
|
148
153
|
}
|
|
149
154
|
function featureBoardDependencies(timeline, feature) {
|
|
150
155
|
return feature.dependsOn.flatMap((dependency) => {
|
|
@@ -168,7 +173,7 @@ function featureBoardStateTone(state) {
|
|
|
168
173
|
if (state === "failed" || state === "cancelled") {
|
|
169
174
|
return "danger";
|
|
170
175
|
}
|
|
171
|
-
if (state === "revision_needed") {
|
|
176
|
+
if (state === "revision_needed" || state === "paused") {
|
|
172
177
|
return "warning";
|
|
173
178
|
}
|
|
174
179
|
if (featureBoardStateIsActive(state)) {
|
package/dist/tui/InputBar.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { compactEndByDisplayWidth, compactTailByDisplayWidth, displayWidth } from "./display-width.js";
|
|
4
4
|
import { TUI_THEME } from "./theme.js";
|
|
5
|
-
export function InputBar({ mode, ready = true, busy = false, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCancelConfirm = false, taskSessionAction = null, taskSessionsIncludeArchived = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
|
|
5
|
+
export function InputBar({ mode, ready = true, busy = false, routeFallback = false, collaborationDetail = false, collaborationUnresolved = false, collaborationBack = "workers", featureCanCancel = false, featureCanPause = false, featureCanReassign = false, featureCancelConfirm = false, featurePauseConfirm = false, featureAssignment = false, taskSessionAction = null, taskSessionsIncludeArchived = false, taskSessionDetail = false, taskSessionDetailHasNative = false, taskSessionDetailCanFork = false, canRetry = false, hasWorkers = false, hasActiveTask = false, hasTaskResult = false, taskResultExpanded = false, chatScrollOffset = 0, chatMaxScrollOffset = 0, nativeClosed = false, searchMatchIndex = 0, searchMatchCount = 0, value, cursor, terminalWidth: providedTerminalWidth, onChange, onSubmit }) {
|
|
6
6
|
const terminalWidth = providedTerminalWidth ?? process.stdout.columns ?? 120;
|
|
7
7
|
const fillRail = providedTerminalWidth !== undefined || typeof process.stdout.columns === "number";
|
|
8
8
|
if (mode === "worker-search") {
|
|
@@ -14,6 +14,10 @@ export function InputBar({ mode, ready = true, busy = false, routeFallback = fal
|
|
|
14
14
|
const textWidth = displayWidth(`${prefix}${display.before}|${display.after}${suffix}`);
|
|
15
15
|
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: textWidth, fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: prefix }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.before }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: "|" }), _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.text, children: display.after }), suffix ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: suffix }) : null] }));
|
|
16
16
|
}
|
|
17
|
+
if (mode === "status") {
|
|
18
|
+
const hints = statusDetailInputHints(terminalWidth);
|
|
19
|
+
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
|
|
20
|
+
}
|
|
17
21
|
if (mode === "sessions") {
|
|
18
22
|
if (taskSessionAction?.type === "rename") {
|
|
19
23
|
const prefix = terminalWidth < 12 ? "> " : "rename > ";
|
|
@@ -26,6 +30,10 @@ export function InputBar({ mode, ready = true, busy = false, routeFallback = fal
|
|
|
26
30
|
const hints = taskSessionDeleteInputHints(terminalWidth, taskSessionAction.title);
|
|
27
31
|
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.danger, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
|
|
28
32
|
}
|
|
33
|
+
if (taskSessionDetail) {
|
|
34
|
+
const hints = taskSessionDetailInputHints(terminalWidth, taskSessionDetailHasNative, taskSessionDetailCanFork);
|
|
35
|
+
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
|
|
36
|
+
}
|
|
29
37
|
const hints = taskSessionsInputHints(terminalWidth, taskSessionsIncludeArchived);
|
|
30
38
|
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
|
|
31
39
|
}
|
|
@@ -38,7 +46,11 @@ export function InputBar({ mode, ready = true, busy = false, routeFallback = fal
|
|
|
38
46
|
if (mode === "features") {
|
|
39
47
|
const hints = featureBoardInputHints(terminalWidth, {
|
|
40
48
|
canCancel: featureCanCancel,
|
|
49
|
+
canPause: featureCanPause,
|
|
50
|
+
canReassign: featureCanReassign,
|
|
41
51
|
confirmCancel: featureCancelConfirm,
|
|
52
|
+
confirmPause: featurePauseConfirm,
|
|
53
|
+
assignment: featureAssignment,
|
|
42
54
|
canRetry
|
|
43
55
|
});
|
|
44
56
|
return (_jsxs(InputRail, { terminalWidth: terminalWidth, textWidth: displayWidth(`${hints.label}${hints.detail}`), fill: fillRail, children: [_jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.accent, bold: true, children: hints.label }), hints.detail ? _jsx(Text, { backgroundColor: TUI_THEME.rail, color: TUI_THEME.muted, children: hints.detail }) : null] }));
|
|
@@ -396,6 +408,15 @@ function workerInputHints(width) {
|
|
|
396
408
|
}
|
|
397
409
|
return { label: "logs", detail: " · scroll · ^F find · E err · D diff · Tab · ^B workers · ^O attach · Esc chat" };
|
|
398
410
|
}
|
|
411
|
+
function statusDetailInputHints(width) {
|
|
412
|
+
return selectInputHints(width, [
|
|
413
|
+
{ label: "status", detail: " · ^S/Esc back · ^C exit" },
|
|
414
|
+
{ label: "status", detail: " · ^S back · ^C exit" },
|
|
415
|
+
{ label: "status", detail: " · ^S back" },
|
|
416
|
+
{ label: "status", detail: "" },
|
|
417
|
+
{ label: "st", detail: "" }
|
|
418
|
+
]);
|
|
419
|
+
}
|
|
399
420
|
function workerOverviewInputHints(width) {
|
|
400
421
|
if (width < 16) {
|
|
401
422
|
return { label: "wrk", detail: "" };
|
|
@@ -421,6 +442,26 @@ function workerOverviewInputHints(width) {
|
|
|
421
442
|
return { label: "workers", detail: " · Up/Dn select · Enter logs · F features · C timeline · ^O attach · Esc back" };
|
|
422
443
|
}
|
|
423
444
|
function featureBoardInputHints(width, options) {
|
|
445
|
+
if (options.assignment) {
|
|
446
|
+
return selectInputHints(width, [
|
|
447
|
+
{ label: "assign provider", detail: " · A Actor · C Critic · M/Esc done" },
|
|
448
|
+
{ label: "assign", detail: " · A Actor · C Critic · Esc done" },
|
|
449
|
+
{ label: "assign", detail: " · A actor · C critic" },
|
|
450
|
+
{ label: "provider", detail: " · A · C · Esc" },
|
|
451
|
+
{ label: "provider", detail: "" },
|
|
452
|
+
{ label: "M", detail: "" }
|
|
453
|
+
]);
|
|
454
|
+
}
|
|
455
|
+
if (options.confirmPause) {
|
|
456
|
+
return selectInputHints(width, [
|
|
457
|
+
{ label: "pause feature?", detail: " · P confirm · Esc keep" },
|
|
458
|
+
{ label: "pause?", detail: " · P confirm · Esc keep" },
|
|
459
|
+
{ label: "pause?", detail: " · Esc keep" },
|
|
460
|
+
{ label: "Esc keep", detail: "" },
|
|
461
|
+
{ label: "hold?", detail: "" },
|
|
462
|
+
{ label: "?", detail: "" }
|
|
463
|
+
]);
|
|
464
|
+
}
|
|
424
465
|
if (options.confirmCancel) {
|
|
425
466
|
return selectInputHints(width, [
|
|
426
467
|
{ label: "cancel feature?", detail: " · X confirm · Esc keep" },
|
|
@@ -431,13 +472,21 @@ function featureBoardInputHints(width, options) {
|
|
|
431
472
|
{ label: "?", detail: "" }
|
|
432
473
|
]);
|
|
433
474
|
}
|
|
434
|
-
if (options.canCancel) {
|
|
475
|
+
if (options.canCancel || options.canPause) {
|
|
476
|
+
const controls = [
|
|
477
|
+
...(options.canPause ? ["P pause"] : []),
|
|
478
|
+
...(options.canCancel ? ["X cancel"] : [])
|
|
479
|
+
].join(" · ");
|
|
435
480
|
return selectInputHints(width, [
|
|
436
|
-
{ label: "features", detail:
|
|
437
|
-
{ label: "features", detail:
|
|
438
|
-
{ label: "features", detail:
|
|
439
|
-
{ label: "features", detail:
|
|
440
|
-
{ label: "
|
|
481
|
+
{ label: "features", detail: ` · Up/Dn select · Enter timeline · ${controls} · R refresh · Esc workers` },
|
|
482
|
+
{ label: "features", detail: ` · Up/Dn select · ${controls} · R refresh · Esc workers` },
|
|
483
|
+
{ label: "features", detail: ` · Up/Dn select · ${controls} · Esc workers` },
|
|
484
|
+
{ label: "features", detail: ` · ${controls} · Esc workers` },
|
|
485
|
+
{ label: "ft", detail: ` · ${controls} · Esc workers` },
|
|
486
|
+
{ label: "f", detail: ` · ${controls} · Esc workers` },
|
|
487
|
+
{ label: "features", detail: ` · ${controls}` },
|
|
488
|
+
{ label: "ft", detail: ` · ${controls}` },
|
|
489
|
+
{ label: "f", detail: ` · ${controls}` },
|
|
441
490
|
{ label: "features", detail: "" },
|
|
442
491
|
{ label: "ft", detail: "" },
|
|
443
492
|
{ label: "f", detail: "" }
|
|
@@ -445,10 +494,9 @@ function featureBoardInputHints(width, options) {
|
|
|
445
494
|
}
|
|
446
495
|
if (options.canRetry) {
|
|
447
496
|
return selectInputHints(width, [
|
|
448
|
-
{ label: "features", detail:
|
|
449
|
-
{ label: "features", detail:
|
|
450
|
-
{ label: "features", detail:
|
|
451
|
-
{ label: "features", detail: " · Up/Dn select · ^R retry · Esc workers" },
|
|
497
|
+
{ label: "features", detail: ` · Up/Dn select · Enter timeline · ${options.canReassign ? "M provider · " : ""}^R retry task · R refresh · Esc workers` },
|
|
498
|
+
{ label: "features", detail: ` · Up/Dn select · ${options.canReassign ? "M provider · " : ""}^R retry task · R refresh · Esc workers` },
|
|
499
|
+
{ label: "features", detail: ` · Up/Dn select · ${options.canReassign ? "M provider · " : ""}^R retry · Esc workers` },
|
|
452
500
|
{ label: "features", detail: " · ^R retry · Esc workers" },
|
|
453
501
|
{ label: "features", detail: " · Esc workers" },
|
|
454
502
|
{ label: "features", detail: "" },
|
|
@@ -507,11 +555,13 @@ function collaborationDetailInputHints(width) {
|
|
|
507
555
|
function taskSessionsInputHints(width, includeArchived) {
|
|
508
556
|
const archivedAction = includeArchived ? "H hide archived" : "H archived";
|
|
509
557
|
return selectInputHints(width, [
|
|
510
|
-
{ label: "sessions", detail: ` · Up/Dn select · Enter restore · R rename · A archive · D delete · E export · ${archivedAction} · Esc back` },
|
|
511
|
-
{ label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · A archive · D delete · E export · Esc back" },
|
|
512
|
-
{ label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · A archive · D delete · Esc back" },
|
|
513
|
-
{ label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · A archive · Esc back" },
|
|
558
|
+
{ label: "sessions", detail: ` · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · E export · ${archivedAction} · Esc back` },
|
|
559
|
+
{ label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · E export · Esc back" },
|
|
560
|
+
{ label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · D delete · Esc back" },
|
|
561
|
+
{ label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · A archive · Esc back" },
|
|
562
|
+
{ label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · R rename · Esc back" },
|
|
514
563
|
{ label: "sessions", detail: " · Up/Dn select · Enter restore · R rename · Esc back" },
|
|
564
|
+
{ label: "sessions", detail: " · Up/Dn select · Enter restore · I inspect · Esc back" },
|
|
515
565
|
{ label: "sessions", detail: " · Up/Dn select · Enter restore · Esc back" },
|
|
516
566
|
{ label: "sessions", detail: " · Up/Dn select · Esc back" },
|
|
517
567
|
{ label: "sessions", detail: " · Esc back" },
|
|
@@ -521,6 +571,23 @@ function taskSessionsInputHints(width, includeArchived) {
|
|
|
521
571
|
{ label: "s", detail: "" }
|
|
522
572
|
]);
|
|
523
573
|
}
|
|
574
|
+
function taskSessionDetailInputHints(width, hasNative, canFork) {
|
|
575
|
+
const nativeActions = hasNative
|
|
576
|
+
? `${canFork ? "C continue · B branch" : "C continue"} · `
|
|
577
|
+
: "";
|
|
578
|
+
return selectInputHints(width, [
|
|
579
|
+
{ label: "session", detail: ` · Up/Dn worker · Enter logs · ${nativeActions}R refresh · Esc tasks` },
|
|
580
|
+
{ label: "session", detail: ` · Up/Dn worker · Enter logs · ${nativeActions}Esc tasks` },
|
|
581
|
+
{ label: "session", detail: ` · Up/Dn · Enter logs · ${nativeActions}Esc tasks` },
|
|
582
|
+
{ label: "session", detail: ` · Enter logs · ${nativeActions}Esc tasks` },
|
|
583
|
+
{ label: "session", detail: ` · ${hasNative ? "C continue · " : ""}Esc tasks` },
|
|
584
|
+
{ label: "session", detail: " · Esc tasks" },
|
|
585
|
+
{ label: "Esc tasks", detail: "" },
|
|
586
|
+
{ label: "session", detail: "" },
|
|
587
|
+
{ label: "ses", detail: "" },
|
|
588
|
+
{ label: "s", detail: "" }
|
|
589
|
+
]);
|
|
590
|
+
}
|
|
524
591
|
function taskSessionDeleteInputHints(width, title) {
|
|
525
592
|
const safeTitle = title.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
|
|
526
593
|
return selectInputHints(width, [
|
package/dist/tui/StatusBar.js
CHANGED
|
@@ -772,7 +772,7 @@ function runtimeStatusTone(status) {
|
|
|
772
772
|
if (status === "fail" || status === "failed" || status === "error") {
|
|
773
773
|
return "fail";
|
|
774
774
|
}
|
|
775
|
-
if (status === "wait" || status === "waiting" || status === "queued" || status === "stopping") {
|
|
775
|
+
if (status === "wait" || status === "waiting" || status === "queued" || status === "stopping" || status === "paused") {
|
|
776
776
|
return "wait";
|
|
777
777
|
}
|
|
778
778
|
if (status === "stop" || status === "cancelled" || status === "canceled") {
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { compactEndByDisplayWidth, displayWidth, wrapByDisplayWidth } from "./display-width.js";
|
|
5
|
+
import { TUI_THEME } from "./theme.js";
|
|
6
|
+
export function StatusDetailView({ height = 20, terminalWidth = process.stdout.columns || 120, ...input }) {
|
|
7
|
+
const viewportHeight = Math.max(1, Math.trunc(height));
|
|
8
|
+
const width = Math.max(1, terminalWidth - 2);
|
|
9
|
+
const lines = statusDetailDisplayLines(input, width, viewportHeight);
|
|
10
|
+
const blankRows = Math.max(0, viewportHeight - lines.length);
|
|
11
|
+
return (_jsxs(Box, { flexDirection: "column", height: viewportHeight, children: [lines.map((line, index) => (_jsx(StatusDetailRow, { line: line, width: width }, `${line.tone}-${index}`))), Array.from({ length: blankRows }, (_, index) => (_jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(width) }, `status-detail-fill-${index}`)))] }));
|
|
12
|
+
}
|
|
13
|
+
export function statusDetailDisplayLines(input, width, height) {
|
|
14
|
+
const safeWidth = Math.max(1, Math.trunc(width));
|
|
15
|
+
const maxLines = Math.max(1, Math.trunc(height));
|
|
16
|
+
const selected = input.workers[clampWorkerIndex(input.selectedWorkerIndex, input.workers.length)];
|
|
17
|
+
const lines = [
|
|
18
|
+
{ text: "Status", tone: "heading" },
|
|
19
|
+
{
|
|
20
|
+
text: fitStatusDetailText(taskDetail(input), safeWidth),
|
|
21
|
+
tone: input.busy ? "active" : input.canRetry ? "warning" : "text"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
text: fitStatusDetailText(`workspace · ${basename(input.cwd) || input.cwd} · ${input.cwd}`, safeWidth),
|
|
25
|
+
tone: "muted"
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
if (input.routeStatus.trim()) {
|
|
29
|
+
lines.push({
|
|
30
|
+
text: fitStatusDetailText(input.routeStatus.trim(), safeWidth),
|
|
31
|
+
tone: /fallback|failed|error|timeout/i.test(input.routeStatus) ? "danger" : "text"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
lines.push({
|
|
35
|
+
text: fitStatusDetailText(workerSummary(input.workers, input.taskStatus), safeWidth),
|
|
36
|
+
tone: input.workers.some((worker) => worker.runtimeStatus?.state === "failed") ? "danger" : "text"
|
|
37
|
+
});
|
|
38
|
+
if (selected) {
|
|
39
|
+
lines.push(...selectedWorkerLines(selected, safeWidth));
|
|
40
|
+
}
|
|
41
|
+
const reason = sanitizeStatusDetailText(input.routeReason ?? "");
|
|
42
|
+
if (reason) {
|
|
43
|
+
const prefix = "reason · ";
|
|
44
|
+
const wrapped = wrapByDisplayWidth(reason, Math.max(1, safeWidth - displayWidth(prefix))).slice(0, 2);
|
|
45
|
+
wrapped.forEach((part, index) => lines.push({
|
|
46
|
+
text: fitStatusDetailText(`${index === 0 ? prefix : " "}${part}`, safeWidth),
|
|
47
|
+
tone: "muted"
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
return lines.slice(0, maxLines);
|
|
51
|
+
}
|
|
52
|
+
function taskDetail(input) {
|
|
53
|
+
if (!input.taskId) {
|
|
54
|
+
return "task · none";
|
|
55
|
+
}
|
|
56
|
+
const state = input.busy ? "running" : input.canRetry ? "retryable" : "ready";
|
|
57
|
+
return ["task", compactTaskId(input.taskId), input.mode, state].filter(Boolean).join(" · ");
|
|
58
|
+
}
|
|
59
|
+
function workerSummary(workers, taskStatus) {
|
|
60
|
+
if (workers.length === 0) {
|
|
61
|
+
return "workers · none";
|
|
62
|
+
}
|
|
63
|
+
const summary = taskStatus
|
|
64
|
+
.split("|")
|
|
65
|
+
.map((part) => part.trim())
|
|
66
|
+
.filter((part) => /^workers\s+\d+$|^(?:fail|stop|run|wait|done|idle)\s+\d+/i.test(part))
|
|
67
|
+
.join(" · ");
|
|
68
|
+
return summary || `workers · ${workers.length}`;
|
|
69
|
+
}
|
|
70
|
+
function selectedWorkerLines(worker, width) {
|
|
71
|
+
const status = worker.runtimeStatus;
|
|
72
|
+
const state = status?.state ?? "waiting";
|
|
73
|
+
const identity = [
|
|
74
|
+
"selected",
|
|
75
|
+
`${worker.role}/${worker.engine}`,
|
|
76
|
+
state,
|
|
77
|
+
status?.feature_title ?? worker.featureId ?? ""
|
|
78
|
+
].filter(Boolean).join(" · ");
|
|
79
|
+
const model = [status?.model_provider, status?.model_name].filter(Boolean).join("/");
|
|
80
|
+
const phase = status
|
|
81
|
+
? ["phase", humanizeStatusDetail(status.phase), `updated ${formatStatusDetailTime(status.last_event_at)}`].join(" · ")
|
|
82
|
+
: "phase · status pending";
|
|
83
|
+
const lines = [
|
|
84
|
+
{ text: fitStatusDetailText(identity, width), tone: workerStateTone(state) },
|
|
85
|
+
...(model ? [{ text: fitStatusDetailText(`model · ${model}`, width), tone: "muted" }] : []),
|
|
86
|
+
{ text: fitStatusDetailText(phase, width), tone: "muted" }
|
|
87
|
+
];
|
|
88
|
+
if (status?.native_session_id) {
|
|
89
|
+
lines.push({
|
|
90
|
+
text: fitStatusDetailText(`session · ${status.native_session_id}`, width),
|
|
91
|
+
tone: "muted"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const summary = sanitizeStatusDetailText(status?.summary ?? "");
|
|
95
|
+
if (summary) {
|
|
96
|
+
lines.push({ text: fitStatusDetailText(`summary · ${summary}`, width), tone: "text" });
|
|
97
|
+
}
|
|
98
|
+
return lines;
|
|
99
|
+
}
|
|
100
|
+
function StatusDetailRow({ line, width }) {
|
|
101
|
+
const text = fitStatusDetailText(line.text, width);
|
|
102
|
+
const trailingWidth = Math.max(0, width - displayWidth(text));
|
|
103
|
+
const theme = statusDetailLineTheme(line.tone);
|
|
104
|
+
return (_jsxs(Text, { children: [_jsx(Text, { ...theme, children: text }), trailingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(trailingWidth) }) : null] }));
|
|
105
|
+
}
|
|
106
|
+
export function statusDetailLineTheme(tone) {
|
|
107
|
+
return {
|
|
108
|
+
backgroundColor: TUI_THEME.surface,
|
|
109
|
+
color: tone === "heading" || tone === "active"
|
|
110
|
+
? TUI_THEME.accent
|
|
111
|
+
: tone === "success"
|
|
112
|
+
? TUI_THEME.success
|
|
113
|
+
: tone === "warning"
|
|
114
|
+
? TUI_THEME.warning
|
|
115
|
+
: tone === "danger"
|
|
116
|
+
? TUI_THEME.danger
|
|
117
|
+
: tone === "muted"
|
|
118
|
+
? TUI_THEME.muted
|
|
119
|
+
: TUI_THEME.text,
|
|
120
|
+
...(tone === "heading" || tone === "danger" ? { bold: true } : {})
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function workerStateTone(state) {
|
|
124
|
+
if (state === "done") {
|
|
125
|
+
return "success";
|
|
126
|
+
}
|
|
127
|
+
if (state === "failed") {
|
|
128
|
+
return "danger";
|
|
129
|
+
}
|
|
130
|
+
if (state === "running" || state === "starting") {
|
|
131
|
+
return "active";
|
|
132
|
+
}
|
|
133
|
+
if (state === "waiting" || state === "cancelled") {
|
|
134
|
+
return "warning";
|
|
135
|
+
}
|
|
136
|
+
return "muted";
|
|
137
|
+
}
|
|
138
|
+
function fitStatusDetailText(text, width) {
|
|
139
|
+
return compactEndByDisplayWidth(sanitizeStatusDetailText(text), Math.max(1, width));
|
|
140
|
+
}
|
|
141
|
+
function sanitizeStatusDetailText(text) {
|
|
142
|
+
return text
|
|
143
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
144
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
145
|
+
.replace(/\s+/g, " ")
|
|
146
|
+
.trim();
|
|
147
|
+
}
|
|
148
|
+
function humanizeStatusDetail(value) {
|
|
149
|
+
return sanitizeStatusDetailText(value).replace(/[-_]+/g, " ");
|
|
150
|
+
}
|
|
151
|
+
function formatStatusDetailTime(value) {
|
|
152
|
+
const parsed = new Date(value);
|
|
153
|
+
if (!Number.isFinite(parsed.getTime())) {
|
|
154
|
+
return "unknown";
|
|
155
|
+
}
|
|
156
|
+
return parsed.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z");
|
|
157
|
+
}
|
|
158
|
+
function compactTaskId(taskId) {
|
|
159
|
+
const withoutPrefix = taskId.startsWith("task-") ? taskId.slice("task-".length) : taskId;
|
|
160
|
+
return withoutPrefix.replace(/^\d{8}-/, "");
|
|
161
|
+
}
|
|
162
|
+
function clampWorkerIndex(index, count) {
|
|
163
|
+
return Math.min(Math.max(0, count - 1), Math.max(0, Math.trunc(index)));
|
|
164
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { compactEndByDisplayWidth, displayWidth } from "./display-width.js";
|
|
4
|
+
import { TUI_THEME } from "./theme.js";
|
|
5
|
+
export function TaskSessionDetailView({ details, selectedWorkerIndex, loading = false, error = null, notice = null, height = 20, terminalWidth = process.stdout.columns || 120 }) {
|
|
6
|
+
const viewportHeight = Math.max(1, Math.trunc(height));
|
|
7
|
+
const width = taskSessionDetailContentWidth(terminalWidth);
|
|
8
|
+
const lines = taskSessionDetailDisplayLines(details, selectedWorkerIndex, viewportHeight, terminalWidth, {
|
|
9
|
+
loading,
|
|
10
|
+
error,
|
|
11
|
+
notice
|
|
12
|
+
});
|
|
13
|
+
const blankRows = Math.max(0, viewportHeight - lines.length);
|
|
14
|
+
return (_jsxs(Box, { flexDirection: "column", height: viewportHeight, children: [lines.map((line, index) => (_jsx(TaskSessionDetailRow, { line: line, width: width }, `${line.kind}-${line.workerIndex ?? index}-${index}`))), Array.from({ length: blankRows }, (_, index) => (_jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(width) }, `task-session-detail-fill-${index}`)))] }));
|
|
15
|
+
}
|
|
16
|
+
export function taskSessionDetailDisplayLines(details, selectedWorkerIndex, height, terminalWidth, state = {}) {
|
|
17
|
+
const viewportHeight = Math.max(1, Math.trunc(height));
|
|
18
|
+
const width = taskSessionDetailContentWidth(terminalWidth);
|
|
19
|
+
const header = [
|
|
20
|
+
{ text: fitDetailCandidates(["Session hierarchy", "Session detail", "Session", "S"], width), tone: "heading", kind: "heading" }
|
|
21
|
+
];
|
|
22
|
+
if (details && viewportHeight >= 3) {
|
|
23
|
+
header.push({
|
|
24
|
+
text: fitDetailCandidates([
|
|
25
|
+
`project · ${safeDetailText(details.projectName)} · ${safeDetailText(details.projectPath)}`,
|
|
26
|
+
`project · ${safeDetailText(details.projectName)}`,
|
|
27
|
+
safeDetailText(details.projectName)
|
|
28
|
+
], width),
|
|
29
|
+
tone: "muted",
|
|
30
|
+
kind: "project"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (details && viewportHeight >= 4) {
|
|
34
|
+
header.push({
|
|
35
|
+
text: fitDetailCandidates([
|
|
36
|
+
`task · ${safeDetailText(details.task.title)} · ${humanizeDetailState(details.task.status)} · ${details.turns.length} turns · ${details.workers.length} workers`,
|
|
37
|
+
`task · ${safeDetailText(details.task.title)} · ${humanizeDetailState(details.task.status)}`,
|
|
38
|
+
safeDetailText(details.task.title)
|
|
39
|
+
], width),
|
|
40
|
+
tone: detailTaskTone(details.task.status),
|
|
41
|
+
kind: "task"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (state.notice && header.length < viewportHeight) {
|
|
45
|
+
header.push({ text: fitDetailText(safeDetailText(state.notice), width), tone: "active", kind: "heading" });
|
|
46
|
+
}
|
|
47
|
+
const bodySlots = Math.max(0, viewportHeight - header.length);
|
|
48
|
+
if (bodySlots === 0) {
|
|
49
|
+
return header;
|
|
50
|
+
}
|
|
51
|
+
if (state.loading) {
|
|
52
|
+
return [...header, { text: fitDetailText("loading session hierarchy", width), tone: "muted", kind: "empty" }];
|
|
53
|
+
}
|
|
54
|
+
if (state.error) {
|
|
55
|
+
return [...header, {
|
|
56
|
+
text: fitDetailText(`error · ${safeDetailText(state.error)}`, width),
|
|
57
|
+
tone: "danger",
|
|
58
|
+
kind: "error"
|
|
59
|
+
}];
|
|
60
|
+
}
|
|
61
|
+
if (!details) {
|
|
62
|
+
return [...header, { text: fitDetailText("No task selected", width), tone: "muted", kind: "empty" }];
|
|
63
|
+
}
|
|
64
|
+
if (details.turns.length === 0) {
|
|
65
|
+
return [...header, { text: fitDetailText("No persisted turns", width), tone: "muted", kind: "empty" }];
|
|
66
|
+
}
|
|
67
|
+
const selected = clampDetailWorkerIndex(selectedWorkerIndex, details.workers.length);
|
|
68
|
+
const body = taskSessionDetailBodyLines(details, selected, width);
|
|
69
|
+
const selectedLine = Math.max(0, body.findIndex((line) => line.workerIndex === selected && line.kind === "worker"));
|
|
70
|
+
let start = Math.min(Math.max(0, body.length - bodySlots), Math.max(0, selectedLine - Math.floor(bodySlots / 2)));
|
|
71
|
+
if (start > 0) {
|
|
72
|
+
for (let index = selectedLine; index >= start; index -= 1) {
|
|
73
|
+
if (body[index]?.kind === "turn" && selectedLine - index < bodySlots) {
|
|
74
|
+
start = index;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return [...header, ...body.slice(start, start + bodySlots)];
|
|
80
|
+
}
|
|
81
|
+
export function moveTaskSessionDetailSelection(current, delta, workerCount, wrap = false) {
|
|
82
|
+
if (workerCount <= 0) {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
const normalized = clampDetailWorkerIndex(current, workerCount);
|
|
86
|
+
const next = normalized + Math.trunc(delta);
|
|
87
|
+
if (wrap) {
|
|
88
|
+
return ((next % workerCount) + workerCount) % workerCount;
|
|
89
|
+
}
|
|
90
|
+
return Math.min(workerCount - 1, Math.max(0, next));
|
|
91
|
+
}
|
|
92
|
+
function taskSessionDetailBodyLines(details, selectedWorkerIndex, width) {
|
|
93
|
+
const workerIndexById = new Map(details.workers.map((worker, index) => [worker.id, index]));
|
|
94
|
+
return details.turns.flatMap((turn) => {
|
|
95
|
+
const turnNumber = Number(turn.turnId);
|
|
96
|
+
const turnLabel = Number.isInteger(turnNumber) ? `Turn ${turnNumber}` : `Turn ${turn.turnId}`;
|
|
97
|
+
const lines = [{
|
|
98
|
+
text: fitDetailCandidates([
|
|
99
|
+
`${turnLabel} · ${formatDetailTime(turn.createdAt)} · ${safeDetailText(turn.request) || "request unavailable"}`,
|
|
100
|
+
`${turnLabel} · ${safeDetailText(turn.request) || "request unavailable"}`,
|
|
101
|
+
turnLabel
|
|
102
|
+
], width),
|
|
103
|
+
tone: "active",
|
|
104
|
+
kind: "turn"
|
|
105
|
+
}];
|
|
106
|
+
if (turn.workers.length === 0) {
|
|
107
|
+
lines.push({ text: fitDetailText(" no workers", width), tone: "muted", kind: "empty" });
|
|
108
|
+
return lines;
|
|
109
|
+
}
|
|
110
|
+
for (const worker of turn.workers) {
|
|
111
|
+
const workerIndex = workerIndexById.get(worker.id) ?? 0;
|
|
112
|
+
lines.push({
|
|
113
|
+
text: detailWorkerText(worker, workerIndex === selectedWorkerIndex, width),
|
|
114
|
+
tone: detailWorkerTone(worker),
|
|
115
|
+
workerIndex,
|
|
116
|
+
kind: "worker"
|
|
117
|
+
});
|
|
118
|
+
lines.push({
|
|
119
|
+
text: detailNativeText(worker, width),
|
|
120
|
+
tone: "muted",
|
|
121
|
+
workerIndex,
|
|
122
|
+
kind: "native"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return lines;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function detailWorkerText(worker, selected, width) {
|
|
129
|
+
const marker = selected ? "> " : " ";
|
|
130
|
+
const role = `${worker.role.slice(0, 1).toUpperCase()}${worker.role.slice(1)}`;
|
|
131
|
+
const engine = [worker.engine, worker.model, worker.modelProvider].filter(Boolean).join("/");
|
|
132
|
+
const feature = worker.featureTitle ?? worker.featureId ?? "";
|
|
133
|
+
return fitDetailCandidates([
|
|
134
|
+
`${marker}${role} · ${engine} · ${feature ? `${safeDetailText(feature)} · ` : ""}${worker.state} · ${formatDetailTime(worker.lastActivityAt)}`,
|
|
135
|
+
`${marker}${role} · ${engine} · ${worker.state}`,
|
|
136
|
+
`${marker}${role} · ${worker.engine}`,
|
|
137
|
+
`${marker}${safeDetailText(worker.id)}`,
|
|
138
|
+
marker.trimEnd()
|
|
139
|
+
], width);
|
|
140
|
+
}
|
|
141
|
+
function detailNativeText(worker, width) {
|
|
142
|
+
const session = worker.nativeSession;
|
|
143
|
+
const cwd = safeDetailText(session?.cwd ?? worker.dir);
|
|
144
|
+
if (!session) {
|
|
145
|
+
return fitDetailCandidates([
|
|
146
|
+
` native · none · cwd ${cwd}`,
|
|
147
|
+
` native · none`,
|
|
148
|
+
" no native session"
|
|
149
|
+
], width);
|
|
150
|
+
}
|
|
151
|
+
return fitDetailCandidates([
|
|
152
|
+
` native · ${safeDetailText(session.sessionId)} · cwd ${cwd} · used ${formatDetailTime(session.lastUsedAt)}`,
|
|
153
|
+
` native · ${safeDetailText(session.sessionId)} · used ${formatDetailTime(session.lastUsedAt)}`,
|
|
154
|
+
` native · ${safeDetailText(session.sessionId)}`,
|
|
155
|
+
` session ${safeDetailText(session.sessionId)}`
|
|
156
|
+
], width);
|
|
157
|
+
}
|
|
158
|
+
function TaskSessionDetailRow({ line, width }) {
|
|
159
|
+
const fill = Math.max(0, width - displayWidth(line.text));
|
|
160
|
+
return (_jsxs(Text, { children: [_jsx(Text, { ...detailLineTheme(line.tone), children: line.text }), fill > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(fill) }) : null] }));
|
|
161
|
+
}
|
|
162
|
+
function detailTaskTone(status) {
|
|
163
|
+
if (status === "done")
|
|
164
|
+
return "success";
|
|
165
|
+
if (status === "failed")
|
|
166
|
+
return "danger";
|
|
167
|
+
if (status === "paused" || status === "cancelled")
|
|
168
|
+
return "warning";
|
|
169
|
+
return "active";
|
|
170
|
+
}
|
|
171
|
+
function detailWorkerTone(worker) {
|
|
172
|
+
if (worker.state === "done")
|
|
173
|
+
return "success";
|
|
174
|
+
if (worker.state === "failed")
|
|
175
|
+
return "danger";
|
|
176
|
+
if (worker.state === "cancelled")
|
|
177
|
+
return "warning";
|
|
178
|
+
if (worker.state === "running" || worker.state === "starting")
|
|
179
|
+
return "active";
|
|
180
|
+
return "muted";
|
|
181
|
+
}
|
|
182
|
+
function detailLineTheme(tone) {
|
|
183
|
+
return {
|
|
184
|
+
backgroundColor: TUI_THEME.surface,
|
|
185
|
+
color: tone === "heading" || tone === "active"
|
|
186
|
+
? TUI_THEME.accent
|
|
187
|
+
: tone === "success"
|
|
188
|
+
? TUI_THEME.success
|
|
189
|
+
: tone === "warning"
|
|
190
|
+
? TUI_THEME.warning
|
|
191
|
+
: tone === "danger"
|
|
192
|
+
? TUI_THEME.danger
|
|
193
|
+
: TUI_THEME.muted,
|
|
194
|
+
...(tone === "heading" || tone === "danger" ? { bold: true } : {})
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function clampDetailWorkerIndex(index, count) {
|
|
198
|
+
return Math.min(Math.max(0, count - 1), Math.max(0, Math.trunc(index)));
|
|
199
|
+
}
|
|
200
|
+
function humanizeDetailState(value) {
|
|
201
|
+
return value.replaceAll("_", " ");
|
|
202
|
+
}
|
|
203
|
+
function formatDetailTime(value) {
|
|
204
|
+
return value.slice(5, 16).replace("T", " ");
|
|
205
|
+
}
|
|
206
|
+
function fitDetailCandidates(candidates, width) {
|
|
207
|
+
return candidates.find((candidate) => displayWidth(candidate) <= width)
|
|
208
|
+
?? fitDetailText(candidates.at(-1) ?? "", width);
|
|
209
|
+
}
|
|
210
|
+
function fitDetailText(text, width) {
|
|
211
|
+
return compactEndByDisplayWidth(text, Math.max(1, width));
|
|
212
|
+
}
|
|
213
|
+
function safeDetailText(text) {
|
|
214
|
+
return text
|
|
215
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
216
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
217
|
+
.replace(/\s+/g, " ")
|
|
218
|
+
.trim();
|
|
219
|
+
}
|
|
220
|
+
function taskSessionDetailContentWidth(terminalWidth) {
|
|
221
|
+
return Math.max(1, terminalWidth - 2);
|
|
222
|
+
}
|