claude-task-worker 0.68.0 → 0.69.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.
Files changed (3) hide show
  1. package/README.md +17 -0
  2. package/dist/index.js +316 -249
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -185,6 +185,7 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
185
185
  {
186
186
  "mode": "default",
187
187
  "advisor": false,
188
+ "permission": "bypassPermissions",
188
189
  "projects": {
189
190
  "app-a": "/Users/me/repos/app-a",
190
191
  "app-b": "/Users/me/repos/app-b"
@@ -215,6 +216,7 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
215
216
  | `projectGroups` | `{}` | グループ名 → プロジェクト名配列 |
216
217
  | `mode` | `"default"` | タスクの実行形態(下記) |
217
218
  | `advisor` | `false` | `--advisor` を渡すか(下記) |
219
+ | `permission` | `"bypassPermissions"` | Claude CLI の権限モード(下記) |
218
220
 
219
221
  #### `mode`(タスクの実行形態)
220
222
 
@@ -235,6 +237,21 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
235
237
 
236
238
  advisor は main モデル以上の能力が必要(Claude CLI の制約)。全ワーカーの既定 `model` が `opus` なので、`advisorModel` の既定値はすべて空文字(advisor なし)。`model` を `sonnet` 等へ下げたワーカーには `advisorModel: "opus"` を指定できる。
237
239
 
240
+ #### `permission`(権限モード)
241
+
242
+ タスク起動時に Claude CLI へ渡す[権限モード](https://code.claude.com/docs/ja/permission-modes)。`mode` / `advisor` と同じくトップレベル一括で、プロジェクト単位・ワーカー単位の指定はできない。
243
+
244
+ | `permission` | 挙動 |
245
+ |---|---|
246
+ | `"bypassPermissions"`(既定) | 全許可。承認するユーザーが常駐しない自律実行のため既定 |
247
+ | `"dontAsk"` | 許可されていない操作は確認せずスキップする |
248
+ | `"auto"` | 安全な操作は自動承認、危険な操作のみ確認 |
249
+ | `"acceptEdits"` | ファイル編集は自動承認、それ以外は都度確認 |
250
+ | `"manual"` | 標準の権限確認 |
251
+ | `"plan"` | 読み取りのみ。変更は行わない |
252
+
253
+ 値は Claude CLI の `--permission-mode` にそのまま渡される(choices と同じ綴り)。ワーカーには承認するユーザーがいないため、`bypassPermissions` / `dontAsk` 以外ではタスクが承認待ちで止まりうる(`mode: "herdr"` なら herdr のタブを開いて手動で承認できる)。
254
+
238
255
  ### `claude-task-worker.json`(リポジトリ)
239
256
 
240
257
  | キー | 型 | 既定 | 説明 |
package/dist/index.js CHANGED
@@ -16,11 +16,16 @@ __export(table_exports, {
16
16
  TASK_DISPLAY_LIMIT: () => TASK_DISPLAY_LIMIT,
17
17
  buildLogTableLines: () => buildLogTableLines,
18
18
  buildTaskTableLines: () => buildTaskTableLines,
19
+ captureConsole: () => captureConsole,
19
20
  getDisplayWidth: () => getDisplayWidth,
21
+ logLines: () => logLines,
20
22
  padToWidth: () => padToWidth,
23
+ pushLogLine: () => pushLogLine,
21
24
  selectRecentTasks: () => selectRecentTasks,
22
- truncateToWidth: () => truncateToWidth
25
+ truncateToWidth: () => truncateToWidth,
26
+ writeScreen: () => writeScreen
23
27
  });
28
+ import { format } from "node:util";
24
29
  function getDisplayWidth(str) {
25
30
  let width = 0;
26
31
  for (const char of str) {
@@ -132,6 +137,32 @@ function buildTaskTableLines(entries, now = /* @__PURE__ */ new Date()) {
132
137
  function taskRow(t, title, path2, hasPath, status, time, duration) {
133
138
  return hasPath ? [`#${t.id}`, title, t.workerName, path2, status, time, duration] : [`#${t.id}`, title, t.workerName, status, time, duration];
134
139
  }
140
+ function pushLogLine(entry) {
141
+ if (entry.text.trim().length === 0) return;
142
+ logLines.push(entry);
143
+ if (logLines.length > LOG_DISPLAY_LIMIT) {
144
+ logLines.splice(0, logLines.length - LOG_DISPLAY_LIMIT);
145
+ }
146
+ }
147
+ function writeScreen(lines) {
148
+ rawClear();
149
+ rawLog(lines.join("\n"));
150
+ }
151
+ function captureConsole() {
152
+ if (consoleCaptured) return;
153
+ consoleCaptured = true;
154
+ process.on("exit", () => {
155
+ if (logLines.length > 0) rawLog(buildLogTableLines(logLines).join("\n"));
156
+ });
157
+ const streams = { log: "stdout", info: "stdout", warn: "stderr", error: "stderr" };
158
+ for (const [method, stream] of Object.entries(streams)) {
159
+ console[method] = (...args) => {
160
+ for (const line of format(...args).split("\n")) {
161
+ pushLogLine({ stream, text: line, time: /* @__PURE__ */ new Date() });
162
+ }
163
+ };
164
+ }
165
+ }
135
166
  function sanitizeLogText(text) {
136
167
  return text.replace(CONTROL_CHARS, " ").replace(/\t/g, " ");
137
168
  }
@@ -142,19 +173,23 @@ function buildLogTableLines(entries) {
142
173
  const text = sanitizeLogText(e.text);
143
174
  return [
144
175
  formatTime(e.time),
145
- `#${e.id}`,
176
+ e.id === void 0 ? "-" : `#${e.id}`,
146
177
  e.stream,
147
178
  getDisplayWidth(text) > maxTextWidth ? truncateToWidth(text, maxTextWidth) : text
148
179
  ];
149
180
  });
150
181
  return renderBoxTable(["Time", "#", "Stream", "Log"], [rows]);
151
182
  }
152
- var TASK_DISPLAY_LIMIT, LOG_DISPLAY_LIMIT, CONTROL_CHARS;
183
+ var TASK_DISPLAY_LIMIT, LOG_DISPLAY_LIMIT, logLines, rawLog, rawClear, consoleCaptured, CONTROL_CHARS;
153
184
  var init_table = __esm({
154
185
  "src/table.ts"() {
155
186
  "use strict";
156
187
  TASK_DISPLAY_LIMIT = 20;
157
188
  LOG_DISPLAY_LIMIT = 20;
189
+ logLines = [];
190
+ rawLog = console.log.bind(console);
191
+ rawClear = console.clear.bind(console);
192
+ consoleCaptured = false;
158
193
  CONTROL_CHARS = /\x1b\[[0-9;?]*[ -/]*[@-~]|[\x00-\x08\x0b-\x1f\x7f]/g;
159
194
  }
160
195
  });
@@ -1001,7 +1036,11 @@ function formatUptime(start, now) {
1001
1036
  }
1002
1037
  function renderSessionTable(sessions) {
1003
1038
  const entries = [...sessions.values()];
1004
- if (entries.length === 0) return;
1039
+ const logTableLines = buildLogTableLines2(logLines2);
1040
+ if (entries.length === 0) {
1041
+ if (logTableLines.length > 0) writeScreen2(["Logs", ...logTableLines]);
1042
+ return;
1043
+ }
1005
1044
  const maxProjectWidth = 20;
1006
1045
  const rows = entries.map((session) => ({
1007
1046
  project: getDisplayWidth2(session.name) > maxProjectWidth ? truncateToWidth2(session.name, maxProjectWidth) : session.name,
@@ -1028,8 +1067,8 @@ function renderSessionTable(sessions) {
1028
1067
  lines.push(row(r.project, r.workspace, r.pane, r.status, r.uptime));
1029
1068
  }
1030
1069
  lines.push(line("\u2514", "\u2534", "\u2518", "\u2500"));
1031
- console.clear();
1032
- console.log(lines.join("\n"));
1070
+ if (logTableLines.length > 0) lines.push("", "Logs", ...logTableLines);
1071
+ writeScreen2(lines);
1033
1072
  }
1034
1073
  function monitorSessions(sessions, herdr, options) {
1035
1074
  const pollIntervalMs = options?.pollIntervalMs ?? POLL_INTERVAL_MS;
@@ -1179,11 +1218,11 @@ function createDispatcherShutdownHandler(shutdown2) {
1179
1218
  };
1180
1219
  return { handle, isShuttingDown: () => shuttingDown2 };
1181
1220
  }
1182
- var getDisplayWidth2, truncateToWidth2, padToWidth2, POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS, PANE_READY_TIMEOUT_MS2, PANE_READY_POLL_INTERVAL_MS2, WORKER_STARTUP_TIMEOUT_MS, WORKER_STARTUP_POLL_INTERVAL_MS, SEND_MAX_ATTEMPTS, LABEL_PREFIX, SHELL_NAME_PATTERN, SHUTDOWN_RETRY_TIMEOUT_MS, CTRL_C_KEY, shutdownPromise;
1221
+ var getDisplayWidth2, truncateToWidth2, padToWidth2, buildLogTableLines2, logLines2, writeScreen2, POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS, PANE_READY_TIMEOUT_MS2, PANE_READY_POLL_INTERVAL_MS2, WORKER_STARTUP_TIMEOUT_MS, WORKER_STARTUP_POLL_INTERVAL_MS, SEND_MAX_ATTEMPTS, LABEL_PREFIX, SHELL_NAME_PATTERN, SHUTDOWN_RETRY_TIMEOUT_MS, CTRL_C_KEY, shutdownPromise;
1183
1222
  var init_dispatcher = __esm({
1184
1223
  async "src/dispatcher.ts"() {
1185
1224
  "use strict";
1186
- ({ getDisplayWidth: getDisplayWidth2, truncateToWidth: truncateToWidth2, padToWidth: padToWidth2 } = await loadTable());
1225
+ ({ getDisplayWidth: getDisplayWidth2, truncateToWidth: truncateToWidth2, padToWidth: padToWidth2, buildLogTableLines: buildLogTableLines2, logLines: logLines2, writeScreen: writeScreen2 } = await loadTable());
1187
1226
  POLL_INTERVAL_MS = 7 * 1e3;
1188
1227
  SHUTDOWN_TIMEOUT_MS = 10 * 60 * 1e3;
1189
1228
  PANE_READY_TIMEOUT_MS2 = 30 * 1e3;
@@ -1489,6 +1528,244 @@ async function createLabel(name, color, force) {
1489
1528
  import { mkdirSync, renameSync, writeFileSync } from "node:fs";
1490
1529
  import os from "node:os";
1491
1530
  import path from "node:path";
1531
+
1532
+ // src/user-config.ts
1533
+ import { readFileSync, statSync } from "node:fs";
1534
+ import { homedir } from "node:os";
1535
+ import { isAbsolute, join, resolve } from "node:path";
1536
+ var RESERVED_ALL = "all";
1537
+ var DEFAULT_RUN_MODE = "default";
1538
+ var DEFAULT_ADVISOR_ENABLED = false;
1539
+ var PERMISSION_MODES = [
1540
+ "manual",
1541
+ "auto",
1542
+ "acceptEdits",
1543
+ "dontAsk",
1544
+ "plan",
1545
+ "bypassPermissions"
1546
+ ];
1547
+ var DEFAULT_PERMISSION_MODE = "bypassPermissions";
1548
+ var UserConfigError = class extends Error {
1549
+ constructor(message) {
1550
+ super(message);
1551
+ this.name = "UserConfigError";
1552
+ }
1553
+ };
1554
+ function getConfigDir() {
1555
+ const xdg = process.env.XDG_CONFIG_HOME;
1556
+ const configHome = xdg && xdg.length > 0 ? xdg : join(homedir(), ".config");
1557
+ return join(configHome, "claude-task-worker");
1558
+ }
1559
+ function getUserConfigPath() {
1560
+ return join(getConfigDir(), "config.json");
1561
+ }
1562
+ function isDirectory(path2) {
1563
+ try {
1564
+ return statSync(path2).isDirectory();
1565
+ } catch {
1566
+ return false;
1567
+ }
1568
+ }
1569
+ function parseConfigFile(path2) {
1570
+ let content;
1571
+ try {
1572
+ content = readFileSync(path2, "utf-8");
1573
+ } catch (err) {
1574
+ if (err.code === "ENOENT") return void 0;
1575
+ throw err;
1576
+ }
1577
+ try {
1578
+ return JSON.parse(content);
1579
+ } catch (err) {
1580
+ if (err instanceof SyntaxError) {
1581
+ throw new UserConfigError(`config file contains invalid JSON: ${path2}: ${err.message}`);
1582
+ }
1583
+ throw err;
1584
+ }
1585
+ }
1586
+ function readRawConfig() {
1587
+ return parseConfigFile(getUserConfigPath());
1588
+ }
1589
+ function parseMode(raw, path2) {
1590
+ if (!("mode" in raw)) return DEFAULT_RUN_MODE;
1591
+ const value = raw["mode"];
1592
+ if (value === "default" || value === "herdr") return value;
1593
+ console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_RUN_MODE}"`);
1594
+ return DEFAULT_RUN_MODE;
1595
+ }
1596
+ function parseAdvisor(raw, path2) {
1597
+ if (!("advisor" in raw)) return DEFAULT_ADVISOR_ENABLED;
1598
+ const value = raw["advisor"];
1599
+ if (typeof value === "boolean") return value;
1600
+ console.warn(`[config] invalid advisor: ${JSON.stringify(value)} in ${path2}, using ${DEFAULT_ADVISOR_ENABLED}`);
1601
+ return DEFAULT_ADVISOR_ENABLED;
1602
+ }
1603
+ function parsePermission(raw, path2) {
1604
+ if (!("permission" in raw)) return DEFAULT_PERMISSION_MODE;
1605
+ const value = raw["permission"];
1606
+ if (typeof value === "string" && PERMISSION_MODES.includes(value)) return value;
1607
+ console.warn(`[config] invalid permission: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_PERMISSION_MODE}"`);
1608
+ return DEFAULT_PERMISSION_MODE;
1609
+ }
1610
+ function loadUserConfig() {
1611
+ const path2 = getUserConfigPath();
1612
+ const raw = readRawConfig();
1613
+ if (raw === void 0) {
1614
+ throw new UserConfigError(`config.json not found: ${path2}`);
1615
+ }
1616
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1617
+ throw new UserConfigError(`config.json must contain a JSON object: ${path2}`);
1618
+ }
1619
+ if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
1620
+ throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path2}`);
1621
+ }
1622
+ if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
1623
+ throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
1624
+ }
1625
+ const mode = parseMode(raw, path2);
1626
+ const advisor = parseAdvisor(raw, path2);
1627
+ const permission = parsePermission(raw, path2);
1628
+ const rawProjects = raw["projects"];
1629
+ const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
1630
+ const projectKeys = Object.keys(rawProjects);
1631
+ const groupKeys = Object.keys(rawProjectGroups);
1632
+ if (projectKeys.includes(RESERVED_ALL) || groupKeys.includes(RESERVED_ALL)) {
1633
+ throw new UserConfigError(
1634
+ `"${RESERVED_ALL}" is a reserved word and cannot be used as a key in "projects" or "projectGroups"`
1635
+ );
1636
+ }
1637
+ const groupKeySet = new Set(groupKeys);
1638
+ const duplicateKeys = projectKeys.filter((key) => groupKeySet.has(key));
1639
+ if (duplicateKeys.length > 0) {
1640
+ throw new UserConfigError(
1641
+ `"projects" and "projectGroups" share the same key namespace; duplicate keys are not allowed: ${duplicateKeys.join(", ")}`
1642
+ );
1643
+ }
1644
+ const projects = {};
1645
+ for (const [name, value] of Object.entries(rawProjects)) {
1646
+ if (name === "__proto__") {
1647
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path2}`);
1648
+ }
1649
+ if (typeof value !== "string" || !isAbsolute(value)) {
1650
+ console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
1651
+ continue;
1652
+ }
1653
+ if (!isDirectory(value)) {
1654
+ console.warn(`[config] projects.${name} does not exist as a directory: ${value}, skipping`);
1655
+ continue;
1656
+ }
1657
+ projects[name] = value;
1658
+ }
1659
+ const projectGroups = {};
1660
+ for (const [groupName, value] of Object.entries(rawProjectGroups)) {
1661
+ if (groupName === "__proto__") {
1662
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path2}`);
1663
+ }
1664
+ if (!Array.isArray(value)) {
1665
+ console.warn(`[config] invalid projectGroups.${groupName}: expected an array, skipping`);
1666
+ continue;
1667
+ }
1668
+ const members = [];
1669
+ for (const member of value) {
1670
+ if (typeof member !== "string" || !Object.prototype.hasOwnProperty.call(projects, member)) {
1671
+ console.warn(`[config] projectGroups.${groupName} references unknown project "${String(member)}", skipping`);
1672
+ continue;
1673
+ }
1674
+ members.push(member);
1675
+ }
1676
+ projectGroups[groupName] = members;
1677
+ }
1678
+ return { mode, advisor, permission, projects, projectGroups };
1679
+ }
1680
+ var cachedRunMode;
1681
+ function getRunMode() {
1682
+ if (cachedRunMode === void 0) {
1683
+ cachedRunMode = readRunMode();
1684
+ }
1685
+ return cachedRunMode;
1686
+ }
1687
+ var cachedAdvisorEnabled;
1688
+ function isAdvisorEnabled() {
1689
+ if (cachedAdvisorEnabled === void 0) {
1690
+ cachedAdvisorEnabled = readAdvisorEnabled();
1691
+ }
1692
+ return cachedAdvisorEnabled;
1693
+ }
1694
+ var cachedPermissionMode;
1695
+ function getPermissionMode() {
1696
+ if (cachedPermissionMode === void 0) {
1697
+ cachedPermissionMode = readTopLevel(parsePermission, DEFAULT_PERMISSION_MODE, "permission");
1698
+ }
1699
+ return cachedPermissionMode;
1700
+ }
1701
+ function readTopLevel(parse, fallback, label) {
1702
+ let raw;
1703
+ try {
1704
+ raw = readRawConfig();
1705
+ } catch (err) {
1706
+ console.warn(`[config] failed to read config file, using default ${label}: ${err}`);
1707
+ return fallback;
1708
+ }
1709
+ if (raw === void 0) return fallback;
1710
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1711
+ return fallback;
1712
+ }
1713
+ return parse(raw, getUserConfigPath());
1714
+ }
1715
+ function readAdvisorEnabled() {
1716
+ return readTopLevel(parseAdvisor, DEFAULT_ADVISOR_ENABLED, "advisor");
1717
+ }
1718
+ function readRunMode() {
1719
+ return readTopLevel(parseMode, DEFAULT_RUN_MODE, "mode");
1720
+ }
1721
+ function findProjectNameByPath(path2) {
1722
+ let config;
1723
+ try {
1724
+ config = loadUserConfig();
1725
+ } catch {
1726
+ return void 0;
1727
+ }
1728
+ const target = resolve(path2);
1729
+ for (const [name, projectPath] of Object.entries(config.projects)) {
1730
+ if (resolve(projectPath) === target) return name;
1731
+ }
1732
+ return void 0;
1733
+ }
1734
+ function resolveTargetProjects(requested, config) {
1735
+ const resolved = /* @__PURE__ */ new Map();
1736
+ for (const name of requested) {
1737
+ if (name === RESERVED_ALL) {
1738
+ for (const [projectName, projectPath] of Object.entries(config.projects)) {
1739
+ resolved.set(projectName, { name: projectName, path: projectPath });
1740
+ }
1741
+ continue;
1742
+ }
1743
+ if (Object.prototype.hasOwnProperty.call(config.projects, name)) {
1744
+ resolved.set(name, { name, path: config.projects[name] });
1745
+ continue;
1746
+ }
1747
+ if (Object.prototype.hasOwnProperty.call(config.projectGroups, name)) {
1748
+ for (const projectName of config.projectGroups[name]) {
1749
+ const projectPath = config.projects[projectName];
1750
+ if (projectPath === void 0) continue;
1751
+ resolved.set(projectName, { name: projectName, path: projectPath });
1752
+ }
1753
+ continue;
1754
+ }
1755
+ const availableProjects = Object.keys(config.projects).join(", ") || "(none)";
1756
+ const availableGroups = Object.keys(config.projectGroups).join(", ") || "(none)";
1757
+ throw new UserConfigError(
1758
+ `Unknown project or group: "${name}". Available projects: ${availableProjects}. Available groups: ${availableGroups}.`
1759
+ );
1760
+ }
1761
+ const resolvedProjects = Array.from(resolved.values());
1762
+ if (resolvedProjects.length === 0) {
1763
+ throw new UserConfigError(`No projects resolved from requested targets: ${requested.join(", ")}`);
1764
+ }
1765
+ return resolvedProjects;
1766
+ }
1767
+
1768
+ // src/claude-args.ts
1492
1769
  var DISALLOWED_TOOLS = [
1493
1770
  // 遅延 / yield: 後続ウェイクアップ前提。print モードではウェイクアップが発火せず、
1494
1771
  // 呼ぶと処理未完のままプロセスが終了する。
@@ -1565,12 +1842,21 @@ function systemPromptFilePath(model) {
1565
1842
  cachedSystemPromptFilePaths.set(variant, target);
1566
1843
  return target;
1567
1844
  }
1568
- function buildClaudeArgs({ mode, prompt, model, effort, advisorModel }) {
1845
+ function buildClaudeArgs({
1846
+ mode,
1847
+ prompt,
1848
+ model,
1849
+ effort,
1850
+ advisorModel,
1851
+ permissionMode
1852
+ }) {
1569
1853
  const advisor = advisorModel?.trim() ?? "";
1854
+ const permission = permissionMode ?? DEFAULT_PERMISSION_MODE;
1570
1855
  return [
1571
1856
  ...mode === "herdr" ? [] : ["-p"],
1572
1857
  prompt,
1573
- "--dangerously-skip-permissions",
1858
+ "--permission-mode",
1859
+ permission,
1574
1860
  "--chrome",
1575
1861
  "--disallowedTools",
1576
1862
  DISALLOWED_TOOLS_ARG,
@@ -1593,8 +1879,8 @@ function buildClaudeEnv(mode) {
1593
1879
  }
1594
1880
 
1595
1881
  // src/config.ts
1596
- import { readFileSync } from "node:fs";
1597
- import { isAbsolute, join, normalize, sep as SEP } from "node:path";
1882
+ import { readFileSync as readFileSync2 } from "node:fs";
1883
+ import { isAbsolute as isAbsolute2, join as join2, normalize, sep as SEP } from "node:path";
1598
1884
  var DEFAULT_UI_DESIGN_CONFIG = {
1599
1885
  enabled: false,
1600
1886
  designDir: "designs",
@@ -1724,7 +2010,7 @@ var DEFAULT_CONFIG = {
1724
2010
  uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG },
1725
2011
  workers: {}
1726
2012
  };
1727
- var CONFIG_PATH = join(process.cwd(), "claude-task-worker.json");
2013
+ var CONFIG_PATH = join2(process.cwd(), "claude-task-worker.json");
1728
2014
  function defaultsFor(name) {
1729
2015
  return WORKER_DEFAULTS[name] ?? DEFAULT_WORKER_CONFIG;
1730
2016
  }
@@ -1825,7 +2111,7 @@ function parseUiDesignEntry(val) {
1825
2111
  }
1826
2112
  if ("designDir" in entry) {
1827
2113
  const normalized = typeof entry.designDir === "string" && entry.designDir.length > 0 ? normalize(entry.designDir) : null;
1828
- const isContained = normalized !== null && !isAbsolute(normalized) && normalized !== ".." && !normalized.startsWith(`..${SEP}`);
2114
+ const isContained = normalized !== null && !isAbsolute2(normalized) && normalized !== ".." && !normalized.startsWith(`..${SEP}`);
1829
2115
  if (isContained) {
1830
2116
  result.designDir = normalized;
1831
2117
  } else {
@@ -1840,7 +2126,7 @@ function loadConfig() {
1840
2126
  const configPath = CONFIG_PATH;
1841
2127
  let raw;
1842
2128
  try {
1843
- raw = JSON.parse(readFileSync(configPath, "utf-8"));
2129
+ raw = JSON.parse(readFileSync2(configPath, "utf-8"));
1844
2130
  } catch (err) {
1845
2131
  if (err.code === "ENOENT") {
1846
2132
  return { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, workers: {} };
@@ -1951,239 +2237,13 @@ function buildTaskResult(code, stdout, stderrTail) {
1951
2237
  return { status: completed ? "completed" : "failed", output };
1952
2238
  }
1953
2239
 
1954
- // src/user-config.ts
1955
- import { readFileSync as readFileSync2, statSync } from "node:fs";
1956
- import { homedir } from "node:os";
1957
- import { isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
1958
- var RESERVED_ALL = "all";
1959
- var DEFAULT_RUN_MODE = "default";
1960
- var DEFAULT_ADVISOR_ENABLED = false;
1961
- var UserConfigError = class extends Error {
1962
- constructor(message) {
1963
- super(message);
1964
- this.name = "UserConfigError";
1965
- }
1966
- };
1967
- function getConfigDir() {
1968
- const xdg = process.env.XDG_CONFIG_HOME;
1969
- const configHome = xdg && xdg.length > 0 ? xdg : join2(homedir(), ".config");
1970
- return join2(configHome, "claude-task-worker");
1971
- }
1972
- function getUserConfigPath() {
1973
- return join2(getConfigDir(), "config.json");
1974
- }
1975
- function isDirectory(path2) {
1976
- try {
1977
- return statSync(path2).isDirectory();
1978
- } catch {
1979
- return false;
1980
- }
1981
- }
1982
- function parseConfigFile(path2) {
1983
- let content;
1984
- try {
1985
- content = readFileSync2(path2, "utf-8");
1986
- } catch (err) {
1987
- if (err.code === "ENOENT") return void 0;
1988
- throw err;
1989
- }
1990
- try {
1991
- return JSON.parse(content);
1992
- } catch (err) {
1993
- if (err instanceof SyntaxError) {
1994
- throw new UserConfigError(`config file contains invalid JSON: ${path2}: ${err.message}`);
1995
- }
1996
- throw err;
1997
- }
1998
- }
1999
- function readRawConfig() {
2000
- return parseConfigFile(getUserConfigPath());
2001
- }
2002
- function parseMode(raw, path2) {
2003
- if (!("mode" in raw)) return DEFAULT_RUN_MODE;
2004
- const value = raw["mode"];
2005
- if (value === "default" || value === "herdr") return value;
2006
- console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_RUN_MODE}"`);
2007
- return DEFAULT_RUN_MODE;
2008
- }
2009
- function parseAdvisor(raw, path2) {
2010
- if (!("advisor" in raw)) return DEFAULT_ADVISOR_ENABLED;
2011
- const value = raw["advisor"];
2012
- if (typeof value === "boolean") return value;
2013
- console.warn(`[config] invalid advisor: ${JSON.stringify(value)} in ${path2}, using ${DEFAULT_ADVISOR_ENABLED}`);
2014
- return DEFAULT_ADVISOR_ENABLED;
2015
- }
2016
- function loadUserConfig() {
2017
- const path2 = getUserConfigPath();
2018
- const raw = readRawConfig();
2019
- if (raw === void 0) {
2020
- throw new UserConfigError(`config.json not found: ${path2}`);
2021
- }
2022
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2023
- throw new UserConfigError(`config.json must contain a JSON object: ${path2}`);
2024
- }
2025
- if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
2026
- throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path2}`);
2027
- }
2028
- if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
2029
- throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
2030
- }
2031
- const mode = parseMode(raw, path2);
2032
- const advisor = parseAdvisor(raw, path2);
2033
- const rawProjects = raw["projects"];
2034
- const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
2035
- const projectKeys = Object.keys(rawProjects);
2036
- const groupKeys = Object.keys(rawProjectGroups);
2037
- if (projectKeys.includes(RESERVED_ALL) || groupKeys.includes(RESERVED_ALL)) {
2038
- throw new UserConfigError(
2039
- `"${RESERVED_ALL}" is a reserved word and cannot be used as a key in "projects" or "projectGroups"`
2040
- );
2041
- }
2042
- const groupKeySet = new Set(groupKeys);
2043
- const duplicateKeys = projectKeys.filter((key) => groupKeySet.has(key));
2044
- if (duplicateKeys.length > 0) {
2045
- throw new UserConfigError(
2046
- `"projects" and "projectGroups" share the same key namespace; duplicate keys are not allowed: ${duplicateKeys.join(", ")}`
2047
- );
2048
- }
2049
- const projects = {};
2050
- for (const [name, value] of Object.entries(rawProjects)) {
2051
- if (name === "__proto__") {
2052
- throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path2}`);
2053
- }
2054
- if (typeof value !== "string" || !isAbsolute2(value)) {
2055
- console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
2056
- continue;
2057
- }
2058
- if (!isDirectory(value)) {
2059
- console.warn(`[config] projects.${name} does not exist as a directory: ${value}, skipping`);
2060
- continue;
2061
- }
2062
- projects[name] = value;
2063
- }
2064
- const projectGroups = {};
2065
- for (const [groupName, value] of Object.entries(rawProjectGroups)) {
2066
- if (groupName === "__proto__") {
2067
- throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path2}`);
2068
- }
2069
- if (!Array.isArray(value)) {
2070
- console.warn(`[config] invalid projectGroups.${groupName}: expected an array, skipping`);
2071
- continue;
2072
- }
2073
- const members = [];
2074
- for (const member of value) {
2075
- if (typeof member !== "string" || !Object.prototype.hasOwnProperty.call(projects, member)) {
2076
- console.warn(`[config] projectGroups.${groupName} references unknown project "${String(member)}", skipping`);
2077
- continue;
2078
- }
2079
- members.push(member);
2080
- }
2081
- projectGroups[groupName] = members;
2082
- }
2083
- return { mode, advisor, projects, projectGroups };
2084
- }
2085
- var cachedRunMode;
2086
- function getRunMode() {
2087
- if (cachedRunMode === void 0) {
2088
- cachedRunMode = readRunMode();
2089
- }
2090
- return cachedRunMode;
2091
- }
2092
- var cachedAdvisorEnabled;
2093
- function isAdvisorEnabled() {
2094
- if (cachedAdvisorEnabled === void 0) {
2095
- cachedAdvisorEnabled = readAdvisorEnabled();
2096
- }
2097
- return cachedAdvisorEnabled;
2098
- }
2099
- function readAdvisorEnabled() {
2100
- let raw;
2101
- try {
2102
- raw = readRawConfig();
2103
- } catch (err) {
2104
- console.warn(`[config] failed to read config file, advisor disabled: ${err}`);
2105
- return DEFAULT_ADVISOR_ENABLED;
2106
- }
2107
- if (raw === void 0) return DEFAULT_ADVISOR_ENABLED;
2108
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2109
- return DEFAULT_ADVISOR_ENABLED;
2110
- }
2111
- return parseAdvisor(raw, getUserConfigPath());
2112
- }
2113
- function readRunMode() {
2114
- let raw;
2115
- try {
2116
- raw = readRawConfig();
2117
- } catch (err) {
2118
- console.warn(`[config] failed to read config file, using "${DEFAULT_RUN_MODE}" mode: ${err}`);
2119
- return DEFAULT_RUN_MODE;
2120
- }
2121
- if (raw === void 0) return DEFAULT_RUN_MODE;
2122
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2123
- return DEFAULT_RUN_MODE;
2124
- }
2125
- return parseMode(raw, getUserConfigPath());
2126
- }
2127
- function findProjectNameByPath(path2) {
2128
- let config;
2129
- try {
2130
- config = loadUserConfig();
2131
- } catch {
2132
- return void 0;
2133
- }
2134
- const target = resolve(path2);
2135
- for (const [name, projectPath] of Object.entries(config.projects)) {
2136
- if (resolve(projectPath) === target) return name;
2137
- }
2138
- return void 0;
2139
- }
2140
- function resolveTargetProjects(requested, config) {
2141
- const resolved = /* @__PURE__ */ new Map();
2142
- for (const name of requested) {
2143
- if (name === RESERVED_ALL) {
2144
- for (const [projectName, projectPath] of Object.entries(config.projects)) {
2145
- resolved.set(projectName, { name: projectName, path: projectPath });
2146
- }
2147
- continue;
2148
- }
2149
- if (Object.prototype.hasOwnProperty.call(config.projects, name)) {
2150
- resolved.set(name, { name, path: config.projects[name] });
2151
- continue;
2152
- }
2153
- if (Object.prototype.hasOwnProperty.call(config.projectGroups, name)) {
2154
- for (const projectName of config.projectGroups[name]) {
2155
- const projectPath = config.projects[projectName];
2156
- if (projectPath === void 0) continue;
2157
- resolved.set(projectName, { name: projectName, path: projectPath });
2158
- }
2159
- continue;
2160
- }
2161
- const availableProjects = Object.keys(config.projects).join(", ") || "(none)";
2162
- const availableGroups = Object.keys(config.projectGroups).join(", ") || "(none)";
2163
- throw new UserConfigError(
2164
- `Unknown project or group: "${name}". Available projects: ${availableProjects}. Available groups: ${availableGroups}.`
2165
- );
2166
- }
2167
- const resolvedProjects = Array.from(resolved.values());
2168
- if (resolvedProjects.length === 0) {
2169
- throw new UserConfigError(`No projects resolved from requested targets: ${requested.join(", ")}`);
2170
- }
2171
- return resolvedProjects;
2172
- }
2173
-
2174
2240
  // src/process-manager.ts
2175
2241
  var childProcesses = /* @__PURE__ */ new Map();
2176
2242
  var herdrTasks = /* @__PURE__ */ new Map();
2177
2243
  var herdrAbortSignal = { aborted: false };
2178
2244
  var tasks = /* @__PURE__ */ new Map();
2179
- var logLines = [];
2180
- function pushLogLine(id, stream, text) {
2181
- const trimmed = text.replace(/\r$/, "");
2182
- if (trimmed.trim().length === 0) return;
2183
- logLines.push({ id, stream, text: trimmed, time: /* @__PURE__ */ new Date() });
2184
- if (logLines.length > LOG_DISPLAY_LIMIT) {
2185
- logLines.splice(0, logLines.length - LOG_DISPLAY_LIMIT);
2186
- }
2245
+ function pushTaskLogLine(id, stream, text) {
2246
+ pushLogLine({ id, stream, text: text.replace(/\r$/, ""), time: /* @__PURE__ */ new Date() });
2187
2247
  }
2188
2248
  function makeLogFeeder(id, stream) {
2189
2249
  let partial = "";
@@ -2193,12 +2253,12 @@ function makeLogFeeder(id, stream) {
2193
2253
  partial += decoder.write(chunk);
2194
2254
  const parts = partial.split("\n");
2195
2255
  partial = parts.pop() ?? "";
2196
- for (const part of parts) pushLogLine(id, stream, part);
2256
+ for (const part of parts) pushTaskLogLine(id, stream, part);
2197
2257
  },
2198
2258
  flush() {
2199
2259
  partial += decoder.end();
2200
2260
  if (partial.length > 0) {
2201
- pushLogLine(id, stream, partial);
2261
+ pushTaskLogLine(id, stream, partial);
2202
2262
  partial = "";
2203
2263
  }
2204
2264
  }
@@ -2245,8 +2305,7 @@ function renderTable() {
2245
2305
  if (lines.length > 0) lines.push("");
2246
2306
  lines.push("Logs", ...logTableLines);
2247
2307
  }
2248
- console.clear();
2249
- console.log(lines.join("\n"));
2308
+ writeScreen(lines);
2250
2309
  }
2251
2310
  var renderInterval;
2252
2311
  function ensureRenderInterval() {
@@ -3076,7 +3135,8 @@ function createIssuePollingWorker(config) {
3076
3135
  model,
3077
3136
  effort,
3078
3137
  // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3079
- advisorModel: isAdvisorEnabled() ? advisorModel : ""
3138
+ advisorModel: isAdvisorEnabled() ? advisorModel : "",
3139
+ permissionMode: getPermissionMode()
3080
3140
  });
3081
3141
  let baseBranch = defaultBranch;
3082
3142
  if (parentNumber !== void 0) {
@@ -3249,7 +3309,8 @@ function createPrPollingWorker(config) {
3249
3309
  model,
3250
3310
  effort,
3251
3311
  // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3252
- advisorModel: isAdvisorEnabled() ? advisorModel : ""
3312
+ advisorModel: isAdvisorEnabled() ? advisorModel : "",
3313
+ permissionMode: getPermissionMode()
3253
3314
  });
3254
3315
  run(
3255
3316
  execution.command,
@@ -3707,6 +3768,9 @@ var applyUiDesignWorker = async (opts = {}) => {
3707
3768
  })();
3708
3769
  };
3709
3770
 
3771
+ // src/index.ts
3772
+ init_table();
3773
+
3710
3774
  // src/commands/init.ts
3711
3775
  import { mkdir as mkdir2, writeFile as writeFile2, access } from "node:fs/promises";
3712
3776
 
@@ -4217,6 +4281,8 @@ async function assertRunModeAvailable() {
4217
4281
  console.log("[worker] run mode: herdr (each task runs as a TUI session in its own herdr tab)");
4218
4282
  }
4219
4283
  async function assertRunPrerequisites() {
4284
+ captureConsole();
4285
+ ensureRenderInterval();
4220
4286
  await assertRunModeAvailable();
4221
4287
  }
4222
4288
  if (!hasProjectFilter()) {
@@ -4254,6 +4320,7 @@ if (hasProjectFilter()) {
4254
4320
  let monitorHandle;
4255
4321
  const herdr = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
4256
4322
  const dispatcher = await init_dispatcher().then(() => dispatcher_exports);
4323
+ captureConsole();
4257
4324
  const shutdownController = dispatcher.createDispatcherShutdownHandler(
4258
4325
  (options) => dispatcher.shutdownDispatcher(sessions, monitorHandle, options)
4259
4326
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.68.0",
3
+ "version": "0.69.0",
4
4
  "description": "CLI tool that polls GitHub Issues/PRs and delegates work to Claude CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",