claude-task-worker 0.68.0 → 0.70.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 +342 -252
  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;
@@ -1325,6 +1364,11 @@ async function findPrStateByHeadRef(headRefName) {
1325
1364
  if (prs.length === 0) return null;
1326
1365
  return { number: prs[0].number, state: prs[0].state, mergedAt: prs[0].mergedAt ?? null };
1327
1366
  }
1367
+ async function getPrMergeable(prNumber) {
1368
+ const output = await execGh(["pr", "view", String(prNumber), "--json", "mergeable"]);
1369
+ const parsed = JSON.parse(output);
1370
+ return typeof parsed?.mergeable === "string" ? parsed.mergeable : "UNKNOWN";
1371
+ }
1328
1372
  async function getIssueBody(issueNumber) {
1329
1373
  const output = await execGh(["issue", "view", String(issueNumber), "--json", "body"]);
1330
1374
  const parsed = JSON.parse(output);
@@ -1489,6 +1533,244 @@ async function createLabel(name, color, force) {
1489
1533
  import { mkdirSync, renameSync, writeFileSync } from "node:fs";
1490
1534
  import os from "node:os";
1491
1535
  import path from "node:path";
1536
+
1537
+ // src/user-config.ts
1538
+ import { readFileSync, statSync } from "node:fs";
1539
+ import { homedir } from "node:os";
1540
+ import { isAbsolute, join, resolve } from "node:path";
1541
+ var RESERVED_ALL = "all";
1542
+ var DEFAULT_RUN_MODE = "default";
1543
+ var DEFAULT_ADVISOR_ENABLED = false;
1544
+ var PERMISSION_MODES = [
1545
+ "manual",
1546
+ "auto",
1547
+ "acceptEdits",
1548
+ "dontAsk",
1549
+ "plan",
1550
+ "bypassPermissions"
1551
+ ];
1552
+ var DEFAULT_PERMISSION_MODE = "bypassPermissions";
1553
+ var UserConfigError = class extends Error {
1554
+ constructor(message) {
1555
+ super(message);
1556
+ this.name = "UserConfigError";
1557
+ }
1558
+ };
1559
+ function getConfigDir() {
1560
+ const xdg = process.env.XDG_CONFIG_HOME;
1561
+ const configHome = xdg && xdg.length > 0 ? xdg : join(homedir(), ".config");
1562
+ return join(configHome, "claude-task-worker");
1563
+ }
1564
+ function getUserConfigPath() {
1565
+ return join(getConfigDir(), "config.json");
1566
+ }
1567
+ function isDirectory(path2) {
1568
+ try {
1569
+ return statSync(path2).isDirectory();
1570
+ } catch {
1571
+ return false;
1572
+ }
1573
+ }
1574
+ function parseConfigFile(path2) {
1575
+ let content;
1576
+ try {
1577
+ content = readFileSync(path2, "utf-8");
1578
+ } catch (err) {
1579
+ if (err.code === "ENOENT") return void 0;
1580
+ throw err;
1581
+ }
1582
+ try {
1583
+ return JSON.parse(content);
1584
+ } catch (err) {
1585
+ if (err instanceof SyntaxError) {
1586
+ throw new UserConfigError(`config file contains invalid JSON: ${path2}: ${err.message}`);
1587
+ }
1588
+ throw err;
1589
+ }
1590
+ }
1591
+ function readRawConfig() {
1592
+ return parseConfigFile(getUserConfigPath());
1593
+ }
1594
+ function parseMode(raw, path2) {
1595
+ if (!("mode" in raw)) return DEFAULT_RUN_MODE;
1596
+ const value = raw["mode"];
1597
+ if (value === "default" || value === "herdr") return value;
1598
+ console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_RUN_MODE}"`);
1599
+ return DEFAULT_RUN_MODE;
1600
+ }
1601
+ function parseAdvisor(raw, path2) {
1602
+ if (!("advisor" in raw)) return DEFAULT_ADVISOR_ENABLED;
1603
+ const value = raw["advisor"];
1604
+ if (typeof value === "boolean") return value;
1605
+ console.warn(`[config] invalid advisor: ${JSON.stringify(value)} in ${path2}, using ${DEFAULT_ADVISOR_ENABLED}`);
1606
+ return DEFAULT_ADVISOR_ENABLED;
1607
+ }
1608
+ function parsePermission(raw, path2) {
1609
+ if (!("permission" in raw)) return DEFAULT_PERMISSION_MODE;
1610
+ const value = raw["permission"];
1611
+ if (typeof value === "string" && PERMISSION_MODES.includes(value)) return value;
1612
+ console.warn(`[config] invalid permission: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_PERMISSION_MODE}"`);
1613
+ return DEFAULT_PERMISSION_MODE;
1614
+ }
1615
+ function loadUserConfig() {
1616
+ const path2 = getUserConfigPath();
1617
+ const raw = readRawConfig();
1618
+ if (raw === void 0) {
1619
+ throw new UserConfigError(`config.json not found: ${path2}`);
1620
+ }
1621
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1622
+ throw new UserConfigError(`config.json must contain a JSON object: ${path2}`);
1623
+ }
1624
+ if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
1625
+ throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path2}`);
1626
+ }
1627
+ if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
1628
+ throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
1629
+ }
1630
+ const mode = parseMode(raw, path2);
1631
+ const advisor = parseAdvisor(raw, path2);
1632
+ const permission = parsePermission(raw, path2);
1633
+ const rawProjects = raw["projects"];
1634
+ const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
1635
+ const projectKeys = Object.keys(rawProjects);
1636
+ const groupKeys = Object.keys(rawProjectGroups);
1637
+ if (projectKeys.includes(RESERVED_ALL) || groupKeys.includes(RESERVED_ALL)) {
1638
+ throw new UserConfigError(
1639
+ `"${RESERVED_ALL}" is a reserved word and cannot be used as a key in "projects" or "projectGroups"`
1640
+ );
1641
+ }
1642
+ const groupKeySet = new Set(groupKeys);
1643
+ const duplicateKeys = projectKeys.filter((key) => groupKeySet.has(key));
1644
+ if (duplicateKeys.length > 0) {
1645
+ throw new UserConfigError(
1646
+ `"projects" and "projectGroups" share the same key namespace; duplicate keys are not allowed: ${duplicateKeys.join(", ")}`
1647
+ );
1648
+ }
1649
+ const projects = {};
1650
+ for (const [name, value] of Object.entries(rawProjects)) {
1651
+ if (name === "__proto__") {
1652
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path2}`);
1653
+ }
1654
+ if (typeof value !== "string" || !isAbsolute(value)) {
1655
+ console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
1656
+ continue;
1657
+ }
1658
+ if (!isDirectory(value)) {
1659
+ console.warn(`[config] projects.${name} does not exist as a directory: ${value}, skipping`);
1660
+ continue;
1661
+ }
1662
+ projects[name] = value;
1663
+ }
1664
+ const projectGroups = {};
1665
+ for (const [groupName, value] of Object.entries(rawProjectGroups)) {
1666
+ if (groupName === "__proto__") {
1667
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path2}`);
1668
+ }
1669
+ if (!Array.isArray(value)) {
1670
+ console.warn(`[config] invalid projectGroups.${groupName}: expected an array, skipping`);
1671
+ continue;
1672
+ }
1673
+ const members = [];
1674
+ for (const member of value) {
1675
+ if (typeof member !== "string" || !Object.prototype.hasOwnProperty.call(projects, member)) {
1676
+ console.warn(`[config] projectGroups.${groupName} references unknown project "${String(member)}", skipping`);
1677
+ continue;
1678
+ }
1679
+ members.push(member);
1680
+ }
1681
+ projectGroups[groupName] = members;
1682
+ }
1683
+ return { mode, advisor, permission, projects, projectGroups };
1684
+ }
1685
+ var cachedRunMode;
1686
+ function getRunMode() {
1687
+ if (cachedRunMode === void 0) {
1688
+ cachedRunMode = readRunMode();
1689
+ }
1690
+ return cachedRunMode;
1691
+ }
1692
+ var cachedAdvisorEnabled;
1693
+ function isAdvisorEnabled() {
1694
+ if (cachedAdvisorEnabled === void 0) {
1695
+ cachedAdvisorEnabled = readAdvisorEnabled();
1696
+ }
1697
+ return cachedAdvisorEnabled;
1698
+ }
1699
+ var cachedPermissionMode;
1700
+ function getPermissionMode() {
1701
+ if (cachedPermissionMode === void 0) {
1702
+ cachedPermissionMode = readTopLevel(parsePermission, DEFAULT_PERMISSION_MODE, "permission");
1703
+ }
1704
+ return cachedPermissionMode;
1705
+ }
1706
+ function readTopLevel(parse, fallback, label) {
1707
+ let raw;
1708
+ try {
1709
+ raw = readRawConfig();
1710
+ } catch (err) {
1711
+ console.warn(`[config] failed to read config file, using default ${label}: ${err}`);
1712
+ return fallback;
1713
+ }
1714
+ if (raw === void 0) return fallback;
1715
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1716
+ return fallback;
1717
+ }
1718
+ return parse(raw, getUserConfigPath());
1719
+ }
1720
+ function readAdvisorEnabled() {
1721
+ return readTopLevel(parseAdvisor, DEFAULT_ADVISOR_ENABLED, "advisor");
1722
+ }
1723
+ function readRunMode() {
1724
+ return readTopLevel(parseMode, DEFAULT_RUN_MODE, "mode");
1725
+ }
1726
+ function findProjectNameByPath(path2) {
1727
+ let config;
1728
+ try {
1729
+ config = loadUserConfig();
1730
+ } catch {
1731
+ return void 0;
1732
+ }
1733
+ const target = resolve(path2);
1734
+ for (const [name, projectPath] of Object.entries(config.projects)) {
1735
+ if (resolve(projectPath) === target) return name;
1736
+ }
1737
+ return void 0;
1738
+ }
1739
+ function resolveTargetProjects(requested, config) {
1740
+ const resolved = /* @__PURE__ */ new Map();
1741
+ for (const name of requested) {
1742
+ if (name === RESERVED_ALL) {
1743
+ for (const [projectName, projectPath] of Object.entries(config.projects)) {
1744
+ resolved.set(projectName, { name: projectName, path: projectPath });
1745
+ }
1746
+ continue;
1747
+ }
1748
+ if (Object.prototype.hasOwnProperty.call(config.projects, name)) {
1749
+ resolved.set(name, { name, path: config.projects[name] });
1750
+ continue;
1751
+ }
1752
+ if (Object.prototype.hasOwnProperty.call(config.projectGroups, name)) {
1753
+ for (const projectName of config.projectGroups[name]) {
1754
+ const projectPath = config.projects[projectName];
1755
+ if (projectPath === void 0) continue;
1756
+ resolved.set(projectName, { name: projectName, path: projectPath });
1757
+ }
1758
+ continue;
1759
+ }
1760
+ const availableProjects = Object.keys(config.projects).join(", ") || "(none)";
1761
+ const availableGroups = Object.keys(config.projectGroups).join(", ") || "(none)";
1762
+ throw new UserConfigError(
1763
+ `Unknown project or group: "${name}". Available projects: ${availableProjects}. Available groups: ${availableGroups}.`
1764
+ );
1765
+ }
1766
+ const resolvedProjects = Array.from(resolved.values());
1767
+ if (resolvedProjects.length === 0) {
1768
+ throw new UserConfigError(`No projects resolved from requested targets: ${requested.join(", ")}`);
1769
+ }
1770
+ return resolvedProjects;
1771
+ }
1772
+
1773
+ // src/claude-args.ts
1492
1774
  var DISALLOWED_TOOLS = [
1493
1775
  // 遅延 / yield: 後続ウェイクアップ前提。print モードではウェイクアップが発火せず、
1494
1776
  // 呼ぶと処理未完のままプロセスが終了する。
@@ -1565,12 +1847,21 @@ function systemPromptFilePath(model) {
1565
1847
  cachedSystemPromptFilePaths.set(variant, target);
1566
1848
  return target;
1567
1849
  }
1568
- function buildClaudeArgs({ mode, prompt, model, effort, advisorModel }) {
1850
+ function buildClaudeArgs({
1851
+ mode,
1852
+ prompt,
1853
+ model,
1854
+ effort,
1855
+ advisorModel,
1856
+ permissionMode
1857
+ }) {
1569
1858
  const advisor = advisorModel?.trim() ?? "";
1859
+ const permission = permissionMode ?? DEFAULT_PERMISSION_MODE;
1570
1860
  return [
1571
1861
  ...mode === "herdr" ? [] : ["-p"],
1572
1862
  prompt,
1573
- "--dangerously-skip-permissions",
1863
+ "--permission-mode",
1864
+ permission,
1574
1865
  "--chrome",
1575
1866
  "--disallowedTools",
1576
1867
  DISALLOWED_TOOLS_ARG,
@@ -1593,8 +1884,8 @@ function buildClaudeEnv(mode) {
1593
1884
  }
1594
1885
 
1595
1886
  // src/config.ts
1596
- import { readFileSync } from "node:fs";
1597
- import { isAbsolute, join, normalize, sep as SEP } from "node:path";
1887
+ import { readFileSync as readFileSync2 } from "node:fs";
1888
+ import { isAbsolute as isAbsolute2, join as join2, normalize, sep as SEP } from "node:path";
1598
1889
  var DEFAULT_UI_DESIGN_CONFIG = {
1599
1890
  enabled: false,
1600
1891
  designDir: "designs",
@@ -1724,7 +2015,7 @@ var DEFAULT_CONFIG = {
1724
2015
  uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG },
1725
2016
  workers: {}
1726
2017
  };
1727
- var CONFIG_PATH = join(process.cwd(), "claude-task-worker.json");
2018
+ var CONFIG_PATH = join2(process.cwd(), "claude-task-worker.json");
1728
2019
  function defaultsFor(name) {
1729
2020
  return WORKER_DEFAULTS[name] ?? DEFAULT_WORKER_CONFIG;
1730
2021
  }
@@ -1825,7 +2116,7 @@ function parseUiDesignEntry(val) {
1825
2116
  }
1826
2117
  if ("designDir" in entry) {
1827
2118
  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}`);
2119
+ const isContained = normalized !== null && !isAbsolute2(normalized) && normalized !== ".." && !normalized.startsWith(`..${SEP}`);
1829
2120
  if (isContained) {
1830
2121
  result.designDir = normalized;
1831
2122
  } else {
@@ -1840,7 +2131,7 @@ function loadConfig() {
1840
2131
  const configPath = CONFIG_PATH;
1841
2132
  let raw;
1842
2133
  try {
1843
- raw = JSON.parse(readFileSync(configPath, "utf-8"));
2134
+ raw = JSON.parse(readFileSync2(configPath, "utf-8"));
1844
2135
  } catch (err) {
1845
2136
  if (err.code === "ENOENT") {
1846
2137
  return { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, workers: {} };
@@ -1951,239 +2242,13 @@ function buildTaskResult(code, stdout, stderrTail) {
1951
2242
  return { status: completed ? "completed" : "failed", output };
1952
2243
  }
1953
2244
 
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
2245
  // src/process-manager.ts
2175
2246
  var childProcesses = /* @__PURE__ */ new Map();
2176
2247
  var herdrTasks = /* @__PURE__ */ new Map();
2177
2248
  var herdrAbortSignal = { aborted: false };
2178
2249
  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
- }
2250
+ function pushTaskLogLine(id, stream, text) {
2251
+ pushLogLine({ id, stream, text: text.replace(/\r$/, ""), time: /* @__PURE__ */ new Date() });
2187
2252
  }
2188
2253
  function makeLogFeeder(id, stream) {
2189
2254
  let partial = "";
@@ -2193,12 +2258,12 @@ function makeLogFeeder(id, stream) {
2193
2258
  partial += decoder.write(chunk);
2194
2259
  const parts = partial.split("\n");
2195
2260
  partial = parts.pop() ?? "";
2196
- for (const part of parts) pushLogLine(id, stream, part);
2261
+ for (const part of parts) pushTaskLogLine(id, stream, part);
2197
2262
  },
2198
2263
  flush() {
2199
2264
  partial += decoder.end();
2200
2265
  if (partial.length > 0) {
2201
- pushLogLine(id, stream, partial);
2266
+ pushTaskLogLine(id, stream, partial);
2202
2267
  partial = "";
2203
2268
  }
2204
2269
  }
@@ -2245,8 +2310,7 @@ function renderTable() {
2245
2310
  if (lines.length > 0) lines.push("");
2246
2311
  lines.push("Logs", ...logTableLines);
2247
2312
  }
2248
- console.clear();
2249
- console.log(lines.join("\n"));
2313
+ writeScreen(lines);
2250
2314
  }
2251
2315
  var renderInterval;
2252
2316
  function ensureRenderInterval() {
@@ -3076,7 +3140,8 @@ function createIssuePollingWorker(config) {
3076
3140
  model,
3077
3141
  effort,
3078
3142
  // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3079
- advisorModel: isAdvisorEnabled() ? advisorModel : ""
3143
+ advisorModel: isAdvisorEnabled() ? advisorModel : "",
3144
+ permissionMode: getPermissionMode()
3080
3145
  });
3081
3146
  let baseBranch = defaultBranch;
3082
3147
  if (parentNumber !== void 0) {
@@ -3249,7 +3314,8 @@ function createPrPollingWorker(config) {
3249
3314
  model,
3250
3315
  effort,
3251
3316
  // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3252
- advisorModel: isAdvisorEnabled() ? advisorModel : ""
3317
+ advisorModel: isAdvisorEnabled() ? advisorModel : "",
3318
+ permissionMode: getPermissionMode()
3253
3319
  });
3254
3320
  run(
3255
3321
  execution.command,
@@ -3262,7 +3328,7 @@ function createPrPollingWorker(config) {
3262
3328
  lastCompletionAt = Date.now();
3263
3329
  try {
3264
3330
  if (status === "completed") {
3265
- await config.onCompleted?.(pr);
3331
+ await config.onCompleted?.(pr, output);
3266
3332
  await notifyTaskCompleted(config.name, name, pr.number, pr.title, prUrl, output);
3267
3333
  } else {
3268
3334
  await notifyTaskFailed(config.name, name, pr.number, pr.title, prUrl, output);
@@ -3407,14 +3473,32 @@ var triagePrWorker = createPrPollingWorker({
3407
3473
  name: "triage-pr",
3408
3474
  command: "/claude-task-worker:triage-pr",
3409
3475
  triggerLabel: "cc-triage-scope",
3410
- excludeLabels: ["cc-fix-onetime", "cc-resolve-conflict", "cc-release-ready"]
3476
+ excludeLabels: ["cc-fix-onetime", "cc-resolve-conflict", "cc-release-ready", "cc-need-human-check"]
3411
3477
  });
3412
3478
 
3413
3479
  // src/workers/resolve-conflict.ts
3480
+ var LABEL_NEED_HUMAN_CHECK = "cc-need-human-check";
3481
+ function shouldFlagUnresolvedConflict(output, mergeable) {
3482
+ return /aborted/i.test(output) && mergeable === "CONFLICTING";
3483
+ }
3414
3484
  var resolveConflictWorker = createPrPollingWorker({
3415
3485
  name: "resolve-conflict",
3416
3486
  command: "/claude-task-worker:resolve-pr-conflict",
3417
- triggerLabel: "cc-resolve-conflict"
3487
+ triggerLabel: "cc-resolve-conflict",
3488
+ onCompleted: async (pr, output) => {
3489
+ const mergeable = await getPrMergeable(pr.number);
3490
+ if (!shouldFlagUnresolvedConflict(output, mergeable)) return;
3491
+ await addLabel("pr", pr.number, LABEL_NEED_HUMAN_CHECK);
3492
+ await commentOnPR(
3493
+ pr.number,
3494
+ [
3495
+ "\u30B3\u30F3\u30D5\u30EA\u30AF\u30C8\u3092\u81EA\u52D5\u89E3\u6D88\u3067\u304D\u306A\u304B\u3063\u305F\u305F\u3081\u3001`cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u4ED8\u4E0E\u3057\u307E\u3057\u305F\u3002",
3496
+ "",
3497
+ "- \u624B\u52D5\u3067\u30B3\u30F3\u30D5\u30EA\u30AF\u30C8\u3092\u89E3\u6D88\u3057\u305F\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044\uFF08`triage-pr` \u304C\u518D\u958B\u3057\u307E\u3059\uFF09",
3498
+ "- \u81EA\u52D5\u89E3\u6D88\u3092\u3084\u308A\u76F4\u3059\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-resolve-conflict` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u76F4\u3057\u3066\u304F\u3060\u3055\u3044"
3499
+ ].join("\n")
3500
+ );
3501
+ }
3418
3502
  });
3419
3503
 
3420
3504
  // src/workers/check-dependabot.ts
@@ -3707,6 +3791,9 @@ var applyUiDesignWorker = async (opts = {}) => {
3707
3791
  })();
3708
3792
  };
3709
3793
 
3794
+ // src/index.ts
3795
+ init_table();
3796
+
3710
3797
  // src/commands/init.ts
3711
3798
  import { mkdir as mkdir2, writeFile as writeFile2, access } from "node:fs/promises";
3712
3799
 
@@ -4217,6 +4304,8 @@ async function assertRunModeAvailable() {
4217
4304
  console.log("[worker] run mode: herdr (each task runs as a TUI session in its own herdr tab)");
4218
4305
  }
4219
4306
  async function assertRunPrerequisites() {
4307
+ captureConsole();
4308
+ ensureRenderInterval();
4220
4309
  await assertRunModeAvailable();
4221
4310
  }
4222
4311
  if (!hasProjectFilter()) {
@@ -4254,6 +4343,7 @@ if (hasProjectFilter()) {
4254
4343
  let monitorHandle;
4255
4344
  const herdr = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
4256
4345
  const dispatcher = await init_dispatcher().then(() => dispatcher_exports);
4346
+ captureConsole();
4257
4347
  const shutdownController = dispatcher.createDispatcherShutdownHandler(
4258
4348
  (options) => dispatcher.shutdownDispatcher(sessions, monitorHandle, options)
4259
4349
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.68.0",
3
+ "version": "0.70.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",