input-kanban 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ENVIRONMENT.md CHANGED
@@ -43,6 +43,7 @@ input-kanban \
43
43
  - `input-kanban serve` starts a lightweight background scheduler that uses the same orchestrator auto-advance path as CLI `submit --auto` / `input-kanban auto <runId>`. It advances planned runs, serial batches, final judge startup, and bounded automatic retries without relying on an open browser tab.
44
44
  - `KANBAN_RUNNER` / `--runner tmux` runs Codex tasks inside tmux windows while keeping scheduling and status tracking in the Node.js orchestrator.
45
45
  - `KANBAN_RUNNER=tmux` is optional. Use it when you want live terminal visibility into planner, worker, and final judge sessions.
46
+ - With `KANBAN_RUNNER=tmux`, stopping and restarting `input-kanban serve` does not interrupt already-running Codex sessions; tmux keeps them alive and the scheduler resumes after restart. Do not assume the same safety for `headless` runner child processes.
46
47
  - tmux mode uses one session per run and one window for planner, each batch, and judge. Batch windows contain an overview pane plus worker panes.
47
48
  - tmux role windows stay open after the Codex command exits. The runner writes `exit_code` before entering the keep-open shell so Node.js status refresh can continue to advance from filesystem state.
48
49
  - The dashboard exposes the run-level `tmux attach-session` copy action after tmux metadata is available. File viewer panels do not repeat tmux terminal details.
package/README.en.md CHANGED
@@ -117,6 +117,8 @@ Defaults:
117
117
 
118
118
  tmux mode still leaves batch barriers, `maxParallel`, final judge sequencing, and `judge_input.json` generation in Node.js. Each role output directory gets `run.sh` and `tmux.json`; status continues to be driven by `events.jsonl`, `stderr.log`, `last_message.md`, `exit_code`, and existing artifact files. After a tmux role command finishes, it writes `exit_code` first and then keeps the window open for inspection; the user closes the window manually from tmux.
119
119
 
120
+ If you are using `--runner tmux`, stopping and restarting `input-kanban serve` does not interrupt Codex sessions that are already running; the tmux session keeps going, and the scheduler resumes orchestration after the server comes back. With the `headless` runner, do not assume that restarting the service is safe for in-flight child processes.
121
+
120
122
  tmux mode is optional and intended for live terminal viewing of each Codex role. `codex exec` is currently non-interactive and does not normally show manual approval prompts; if you select `danger-full-access` when creating a run, you explicitly relax the worker sandbox and should only do so in a controlled test workspace.
121
123
 
122
124
  After run-level tmux metadata is available, the dashboard shows `Copy tmux attach command`. The file viewer no longer repeats tmux terminal details; use the run detail header to copy the attach command and inspect the tmux session.
package/README.md CHANGED
@@ -117,6 +117,8 @@ input-kanban --open
117
117
 
118
118
  tmux 模式仍由 Node.js 负责 batch barrier、`maxParallel`、final judge 顺序和 `judge_input.json` 生成。每个角色输出目录会写入 `run.sh` 和 `tmux.json`,状态继续由 `events.jsonl`、`stderr.log`、`last_message.md`、`exit_code` 和既有 artifact 文件驱动。tmux 角色命令完成后会先写入 `exit_code`,再保留 window,方便查看现场;需要关闭时由用户在 tmux 里手动退出。
119
119
 
120
+ 如果当前使用的是 `--runner tmux`,中断并重新启动 `input-kanban serve` 不会中断正在执行中的 Codex 会话;tmux session 会继续运行,服务重启后 scheduler 会重新接管后续推进。若使用 `headless` runner,则不应假设服务重启对正在运行的子进程是安全的。
121
+
120
122
  tmux 模式是可选能力,主要用于在终端里实时查看每个 Codex 角色的执行过程。`codex exec` 当前属于非交互模式,默认不会弹出人工 approval;如果创建任务时选择 `danger-full-access`,表示显式放开 worker sandbox 限制,应只在受控测试工作区中使用。
121
123
 
122
124
  看板会在 run 生成 tmux 元数据后显示 `复制tmux attach指令`。文件查看区域不再重复展示 tmux 终端信息;如需查看现场,请从批次详情顶部复制 attach 指令进入 tmux session。
package/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Release Notes
2
2
 
3
+ ## v0.0.12
4
+
5
+ ### Highlights
6
+
7
+ - Fix Windows startup/static serving by resolving `APP_ROOT` with `fileURLToPath(import.meta.url)` instead of URL pathname parsing.
8
+ - Add a regression test for serving `/` and `/api/health` from the HTTP server.
9
+ - Add task-detail hover guidance for sandbox and network capability issues, clarifying that sandbox-denied errors are not necessarily task failures.
10
+ - Remember the last selected Web worker sandbox mode in browser local storage, so users do not need to reselect `danger-full-access` or other modes each time.
11
+ - Auto-scroll the execution process view to the end when opened, while preserving the user's scroll position during refresh if they have scrolled upward.
12
+
13
+ ### Verification
14
+
15
+ - `npm run check` passed with 64 tests.
16
+ - `npm pack --dry-run` passed before release prep.
17
+
18
+ ## v0.0.11
19
+
20
+ ### Highlights
21
+
22
+ - Simplify the Web sidebar header: show `任务批次` as the section title with a compact `新建` action on the right, removing repeated wording.
23
+ - Document safe `input-kanban serve` restarts for `tmux` runner: already-running Codex sessions in tmux continue while the server is down, and the scheduler resumes after restart.
24
+ - Clarify that `headless` runner does not provide the same safe-restart guarantee for in-flight child processes.
25
+
26
+ ### Verification
27
+
28
+ - `npm run check` passed with 63 tests.
29
+ - `npm pack --dry-run` passed before release prep.
30
+
3
31
  ## v0.0.10
4
32
 
5
33
  ### Highlights
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "input-kanban",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "input-kanban": "bin/input-kanban.js"
package/public/index.html CHANGED
@@ -60,12 +60,14 @@
60
60
  .muted { color: var(--muted); font-size: 12px; }
61
61
  .hidden { display: none; }
62
62
  .toolbar { margin: 8px 0 12px; display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
63
+ .section-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 10px 0 8px; }
64
+ .section-header h2 { margin: 0; }
63
65
  .workspace-filter-panel { margin: 4px 0 10px; display: flex; align-items: center; gap: 6px; }
64
66
  .workspace-filter-select { flex: 1 1 auto; min-width: 0; font-size: 12px; padding: 7px 9px; color: #cbd5e1; }
65
67
  .task-text { max-height: 180px; color: #cbd5e1; }
66
68
  .empty { color: var(--muted); padding: 18px 0; }
67
- .runs-load-icon { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; border: 1px solid var(--line); border-radius: 999px; color: var(--muted); font-size: 10px; cursor: help; opacity: .72; }
68
- .runs-load-icon:hover { opacity: 1; color: #cbd5e1; border-color: var(--line-strong); }
69
+ .info-icon, .runs-load-icon { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; border: 1px solid var(--line); border-radius: 999px; color: var(--muted); font-size: 10px; cursor: help; opacity: .72; }
70
+ .info-icon:hover, .runs-load-icon:hover { opacity: 1; color: #cbd5e1; border-color: var(--line-strong); }
69
71
  .run-list { display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; overflow-y: auto; padding-right: 4px; }
70
72
  .run-list-more { width: 100%; margin-top: 4px; }
71
73
  .run-card { border: 1px solid var(--line); border-radius: 12px; padding: 12px; background: var(--panel-2); cursor: pointer; transition: border-color .15s, transform .15s, background .15s; }
@@ -131,10 +133,10 @@
131
133
  <main>
132
134
  <div class="sidebar">
133
135
  <section>
134
- <div class="toolbar">
135
- <button onclick="showCreateForm()">新建任务批次</button>
136
+ <div class="section-header">
137
+ <h2>任务批次</h2>
138
+ <button class="secondary" onclick="showCreateForm()">新建</button>
136
139
  </div>
137
- <h2>任务批次</h2>
138
140
  <div class="workspace-filter-panel">
139
141
  <select id="workspaceFilterSelect" class="workspace-filter-select" onchange="setWorkspaceFilter(this.value)" title="未筛选工作区"></select>
140
142
  <span id="runsLoadHint" class="runs-load-icon" title="批次列表尚未加载" aria-label="批次列表尚未加载">ⓘ</span>
@@ -154,7 +156,7 @@
154
156
  <option value="read-only">read-only(只读)</option>
155
157
  <option value="danger-full-access">danger-full-access(高风险,跳过沙箱限制)</option>
156
158
  </select>
157
- <div class="muted">仅影响 worker;任务拆分和汇总验收仍保持 read-only。</div>
159
+ <div class="muted">仅影响 worker;任务拆分和汇总验收仍保持 read-only。若执行过程提示 Permission denied / sandbox denied,这通常不是任务本身失败,而是当前沙箱能力不足;在可信工作区可改用 danger-full-access。DNS / 网络失败则通常需要检查代理、VPN 或本地 evidence。</div>
158
160
  <label>任务说明</label><textarea id="taskText" placeholder="粘贴任务说明"></textarea>
159
161
  <div class="toolbar">
160
162
  <button onclick="createRun()">创建批次</button>
@@ -183,7 +185,10 @@
183
185
  </section>
184
186
 
185
187
  <section id="filePanel" class="log-panel">
186
- <h2>任务详情</h2>
188
+ <div class="section-header">
189
+ <h2>任务详情</h2>
190
+ <span class="info-icon" title="若执行过程提示 Permission denied / sandbox denied,这通常不是任务本身失败,而是当前 worker 沙箱能力不足;在可信工作区可改用 danger-full-access。DNS / 网络失败则通常需要检查代理、VPN 或本地 evidence。" aria-label="任务详情权限与网络提示">ⓘ</span>
191
+ </div>
187
192
  <div id="fileTitle" class="muted">点击任务后查看详情</div>
188
193
  <div id="fileTabs" class="toolbar file-tabs"></div>
189
194
  <div id="executionSummary" class="execution-summary hidden"></div>
@@ -223,6 +228,8 @@ const statusByRunId = new Map();
223
228
  const AUTO_REFRESH_MS = 3000;
224
229
  const RUN_LIST_PAGE_SIZE = 10;
225
230
  const WORKSPACE_FILTER_ALL = '';
231
+ const WORKER_SANDBOX_STORAGE_KEY = 'input-kanban.workerSandbox';
232
+ const VALID_WORKER_SANDBOXES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
226
233
  let currentWorkspacePath = '';
227
234
  let selectedWorkspaceFilter = localStorage.getItem('input-kanban.workspaceFilter') || WORKSPACE_FILTER_ALL;
228
235
 
@@ -361,7 +368,20 @@ function hideCreateForm() {
361
368
  document.getElementById('detailPanel').classList.remove('hidden');
362
369
  document.getElementById('filePanel').classList.remove('hidden');
363
370
  }
371
+ function saveWorkerSandboxPreference() {
372
+ const select = document.getElementById('workerSandbox');
373
+ const value = select?.value || '';
374
+ if (VALID_WORKER_SANDBOXES.has(value)) localStorage.setItem(WORKER_SANDBOX_STORAGE_KEY, value);
375
+ }
376
+ function initializeWorkerSandboxPreference() {
377
+ const select = document.getElementById('workerSandbox');
378
+ if (!select) return;
379
+ const saved = localStorage.getItem(WORKER_SANDBOX_STORAGE_KEY);
380
+ if (VALID_WORKER_SANDBOXES.has(saved)) select.value = saved;
381
+ select.addEventListener('change', saveWorkerSandboxPreference);
382
+ }
364
383
  async function createRun() {
384
+ saveWorkerSandboxPreference();
365
385
  const body = { label: label.value, workspace: repo.value, repo: repo.value, maxParallel: maxParallel.value, workerSandbox: workerSandbox.value, taskText: taskText.value };
366
386
  const r = await api('/api/runs', { method: 'POST', body: JSON.stringify(body) });
367
387
  selectedRun = r.runId; selectedTask = null; selectedFileName = null;
@@ -656,6 +676,7 @@ async function loadFile(name, { preserveScroll = false } = {}) {
656
676
  selectedFileName = name;
657
677
  const pre = document.getElementById('fileContent');
658
678
  const previousScrollTop = pre.scrollTop;
679
+ const wasAtBottom = pre.scrollHeight - pre.scrollTop - pre.clientHeight < 24;
659
680
  let text;
660
681
  const selected = taskById(selectedTask);
661
682
  if (name === 'result.json' && selected?.manualCompletion?.hasManualResult) {
@@ -665,7 +686,8 @@ async function loadFile(name, { preserveScroll = false } = {}) {
665
686
  text = await api(`/api/runs/${selectedRun}/tasks/${selectedTask}/file?name=${encodeURIComponent(name)}`);
666
687
  }
667
688
  pre.textContent = text;
668
- if (preserveScroll) pre.scrollTop = previousScrollTop;
689
+ if (name === 'events.pretty' && (!preserveScroll || wasAtBottom)) pre.scrollTop = pre.scrollHeight;
690
+ else if (preserveScroll) pre.scrollTop = previousScrollTop;
669
691
  else pre.scrollTop = 0;
670
692
  if (name === 'events.pretty') await renderExecutionSummary();
671
693
  else hideExecutionSummary();
@@ -974,6 +996,7 @@ async function submitManualComplete() {
974
996
  await loadFile('result.json');
975
997
  }
976
998
 
999
+ initializeWorkerSandboxPreference();
977
1000
  loadHealth().then(refreshRuns);
978
1001
  setInterval(() => { if (selectedRun) refreshSelected({auto:true}).catch(console.error); else refreshRuns().catch(console.error); }, AUTO_REFRESH_MS);
979
1002
  </script>
package/src/utils.js CHANGED
@@ -3,11 +3,12 @@ import fsp from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import crypto from 'node:crypto';
5
5
  import { createRequire } from 'node:module';
6
+ import { fileURLToPath } from 'node:url';
6
7
 
7
8
  const require = createRequire(import.meta.url);
8
9
  const { version: PACKAGE_VERSION } = require('../package.json');
9
10
 
10
- export const APP_ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
11
+ export const APP_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
11
12
  export { PACKAGE_VERSION };
12
13
  export const DEFAULT_WORKSPACE = path.resolve(process.env.KANBAN_DEFAULT_WORKSPACE || process.env.KANBAN_DEFAULT_REPO || process.cwd());
13
14
  export const DEFAULT_REPO = DEFAULT_WORKSPACE;