parallel-codex-tui 0.3.1 → 0.4.0

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.
@@ -2,13 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { compactEndByDisplayWidth, displayWidth } from "./display-width.js";
4
4
  import { TUI_THEME } from "./theme.js";
5
- export function TaskSessionsView({ tasks, activeTaskId, selectedIndex, includeArchived = false, notice = null, action = null, loading = false, error = null, height = 20, terminalWidth = process.stdout.columns || 120 }) {
5
+ export function TaskSessionsView({ tasks, activeTaskId, selectedIndex, includeArchived = false, query = "", notice = null, action = null, loading = false, error = null, height = 20, terminalWidth = process.stdout.columns || 120 }) {
6
6
  const viewportHeight = Math.max(1, height);
7
7
  const width = taskSessionsContentWidth(terminalWidth);
8
8
  const lines = taskSessionsDisplayLines(tasks, activeTaskId, selectedIndex, viewportHeight, terminalWidth, {
9
9
  loading,
10
10
  error,
11
11
  includeArchived,
12
+ query,
12
13
  notice,
13
14
  action
14
15
  });
@@ -21,7 +22,10 @@ export function taskSessionsDisplayLines(tasks, activeTaskId, selectedIndex, hei
21
22
  const lines = [
22
23
  {
23
24
  text: fitTaskSessionCandidates([
24
- state.includeArchived ? "Task sessions · archived shown" : "Task sessions",
25
+ taskSessionsHeading(Boolean(state.includeArchived), state.query ?? ""),
26
+ ...((state.query ?? "").trim()
27
+ ? [`Find · ${safeTaskSessionText(state.query ?? "")}`, `/ ${safeTaskSessionText(state.query ?? "")}`]
28
+ : []),
25
29
  state.includeArchived ? "Sessions · all" : "Sessions",
26
30
  "Tasks",
27
31
  "T"
@@ -30,7 +34,7 @@ export function taskSessionsDisplayLines(tasks, activeTaskId, selectedIndex, hei
30
34
  }
31
35
  ];
32
36
  if (viewportHeight >= 3) {
33
- lines.push({ text: taskSessionSummary(tasks, width), tone: "muted" });
37
+ lines.push({ text: taskSessionSummary(tasks, width, state.query ?? ""), tone: "muted" });
34
38
  }
35
39
  if (viewportHeight >= 4 && state.action) {
36
40
  lines.push({
@@ -58,7 +62,10 @@ export function taskSessionsDisplayLines(tasks, activeTaskId, selectedIndex, hei
58
62
  }
59
63
  if (tasks.length === 0) {
60
64
  if (slots > 0) {
61
- lines.push({ text: fitTaskSessionText("No saved task sessions", width), tone: "muted" });
65
+ lines.push({
66
+ text: fitTaskSessionText(state.query?.trim() ? "No matching task sessions" : "No saved task sessions", width),
67
+ tone: "muted"
68
+ });
62
69
  }
63
70
  return lines;
64
71
  }
@@ -94,7 +101,7 @@ function TaskSessionRow({ line, width }) {
94
101
  const theme = taskSessionLineTheme(line.tone);
95
102
  return (_jsxs(Text, { children: [_jsx(Text, { ...theme, children: line.text }), trailingWidth > 0 ? _jsx(Text, { backgroundColor: TUI_THEME.surface, children: " ".repeat(trailingWidth) }) : null] }));
96
103
  }
97
- function taskSessionSummary(tasks, width) {
104
+ function taskSessionSummary(tasks, width, query) {
98
105
  const counts = new Map();
99
106
  let archived = 0;
100
107
  for (const task of tasks) {
@@ -112,11 +119,16 @@ function taskSessionSummary(tasks, width) {
112
119
  if (archived > 0) {
113
120
  parts.push(`${archived} archived`);
114
121
  }
122
+ const searching = Boolean(query.trim());
123
+ const prefix = searching
124
+ ? `${tasks.length} ${tasks.length === 1 ? "match" : "matches"}`
125
+ : `${tasks.length} ${tasks.length === 1 ? "task" : "tasks"}`;
126
+ const compactPrefix = searching ? `${tasks.length} matches` : `${tasks.length} tasks`;
115
127
  return fitTaskSessionCandidates([
116
- [`${tasks.length} ${tasks.length === 1 ? "task" : "tasks"}`, ...parts].join(" · "),
117
- `${tasks.length} tasks · ${counts.get("running") ?? 0} active · ${counts.get("failed") ?? 0} failed`,
118
- `${tasks.length} tasks`,
119
- `${tasks.length}t`
128
+ [prefix, ...parts].join(" · "),
129
+ `${compactPrefix} · ${counts.get("running") ?? 0} active · ${counts.get("failed") ?? 0} failed`,
130
+ compactPrefix,
131
+ `${tasks.length}${searching ? "m" : "t"}`
120
132
  ], width);
121
133
  }
122
134
  function taskSessionRowText(task, selected, active, width) {
@@ -130,7 +142,9 @@ function taskSessionRowText(task, selected, active, width) {
130
142
  const workers = `${task.workerCount} ${task.workerCount === 1 ? "worker" : "workers"}`;
131
143
  const native = `${task.nativeSessionCount} native`;
132
144
  const compactId = task.id.replace(/^task-/, "#");
145
+ const match = safeTaskSessionText(task.searchMatch?.summary ?? "");
133
146
  return fitTaskSessionCandidates([
147
+ ...(match ? [[marker + title, status, match].join(" · ")] : []),
134
148
  [marker + title, status, turns, workers, native, date].join(" · "),
135
149
  [marker + title, status, turns, workers, native].join(" · "),
136
150
  [marker + title, status, turns, workers].join(" · "),
@@ -139,6 +153,13 @@ function taskSessionRowText(task, selected, active, width) {
139
153
  marker.trimEnd()
140
154
  ], width);
141
155
  }
156
+ function taskSessionsHeading(includeArchived, query) {
157
+ const scope = includeArchived ? "archived shown" : "active";
158
+ const cleanQuery = safeTaskSessionText(query);
159
+ return cleanQuery
160
+ ? `Task sessions · ${scope} · find ${cleanQuery}`
161
+ : includeArchived ? "Task sessions · archived shown" : "Task sessions";
162
+ }
142
163
  function taskSessionWindowStart(selected, count, visibleCount) {
143
164
  if (visibleCount <= 0 || count <= visibleCount) {
144
165
  return 0;
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const version = "0.3.1";
1
+ export const version = "0.4.0";
@@ -10,6 +10,7 @@ export class MockWorkerAdapter {
10
10
  await spec.onNativeSession?.(nativeSessionId);
11
11
  await setStatus(spec, "running", "mock-running", `${spec.role} mock worker running`, nativeSessionId);
12
12
  await appendText(spec.outputLogPath, `[mock:${spec.role}] started\n`);
13
+ await waitForConfiguredMockDelay(spec);
13
14
  if (spec.role === "judge") {
14
15
  if (spec.prompt.includes("# Role:") && spec.prompt.includes("· Final acceptance")) {
15
16
  const criterionIds = promptJsonArray(spec.prompt, "Required acceptance criterion ids");
@@ -67,6 +68,23 @@ export class MockWorkerAdapter {
67
68
  };
68
69
  }
69
70
  }
71
+ async function waitForConfiguredMockDelay(spec) {
72
+ const configured = Number(spec.modelConfig?.env?.PCT_MOCK_DELAY_MS ?? 0);
73
+ const delayMs = Number.isFinite(configured) ? Math.min(10000, Math.max(0, Math.trunc(configured))) : 0;
74
+ if (delayMs === 0 || spec.signal?.aborted) {
75
+ return;
76
+ }
77
+ await new Promise((resolve) => {
78
+ const timer = setTimeout(finish, delayMs);
79
+ const signal = spec.signal;
80
+ function finish() {
81
+ clearTimeout(timer);
82
+ signal?.removeEventListener("abort", finish);
83
+ resolve();
84
+ }
85
+ signal?.addEventListener("abort", finish, { once: true });
86
+ });
87
+ }
70
88
  async function cancelMockWorker(spec, nativeSessionId) {
71
89
  await appendText(spec.outputLogPath, "[mock] cancelled\n");
72
90
  await setStatus(spec, "cancelled", "mock-cancelled", `${spec.role} mock worker cancelled`, nativeSessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parallel-codex-tui",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "A TypeScript TUI wrapper for routed parallel coding with Codex and Claude workers.",
5
5
  "license": "MIT",
6
6
  "type": "module",