claude-task-worker 0.95.0 → 0.96.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 +68 -13
  2. package/dist/index.js +1124 -533
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -587,12 +587,14 @@ __export(herdr_runner_exports, {
587
587
  PROMPT_ACCEPT_TIMEOUT_MS: () => PROMPT_ACCEPT_TIMEOUT_MS,
588
588
  buildHerdrTaskResult: () => buildHerdrTaskResult,
589
589
  createCompletionTracker: () => createCompletionTracker,
590
+ extractCloudSessionId: () => extractCloudSessionId,
590
591
  observeAgentStatus: () => observeAgentStatus,
591
592
  startHerdrTask: () => startHerdrTask,
592
593
  stopHerdrTask: () => stopHerdrTask,
593
594
  taskTabLabel: () => taskTabLabel,
594
595
  toAgentName: () => toAgentName,
595
- waitForHerdrTask: () => waitForHerdrTask
596
+ waitForHerdrTask: () => waitForHerdrTask,
597
+ waitForPaneReady: () => waitForPaneReady
596
598
  });
597
599
  async function loadHerdr() {
598
600
  return await Promise.resolve().then(() => (init_herdr(), herdr_exports));
@@ -624,6 +626,9 @@ function observeAgentStatus(tracker, status) {
624
626
  }
625
627
  return { tracker, decision: "running" };
626
628
  }
629
+ function extractCloudSessionId(text) {
630
+ return CLOUD_SESSION_URL_RE.exec(text)?.[1] ?? CLOUD_SESSION_CREATED_RE.exec(text)?.[1];
631
+ }
627
632
  function buildHerdrTaskResult(paneOutput, options) {
628
633
  const report = options?.report?.trim() ?? "";
629
634
  if (report !== "") {
@@ -801,13 +806,15 @@ async function stopHerdrTask(task, herdr, options) {
801
806
  function sleep(ms) {
802
807
  return new Promise((resolve3) => setTimeout(resolve3, ms));
803
808
  }
804
- var AGENT_POLL_INTERVAL_MS, PANE_OUTPUT_LINES, PANE_READY_TIMEOUT_MS, PANE_READY_POLL_INTERVAL_MS, PROMPT_ACCEPT_TIMEOUT_MS, PROMPT_ACCEPT_POLL_INTERVAL_MS, CLAUDE_EXIT_TIMEOUT_MS, CLAUDE_EXIT_POLL_INTERVAL_MS;
809
+ var AGENT_POLL_INTERVAL_MS, PANE_OUTPUT_LINES, CLOUD_SESSION_URL_RE, CLOUD_SESSION_CREATED_RE, PANE_READY_TIMEOUT_MS, PANE_READY_POLL_INTERVAL_MS, PROMPT_ACCEPT_TIMEOUT_MS, PROMPT_ACCEPT_POLL_INTERVAL_MS, CLAUDE_EXIT_TIMEOUT_MS, CLAUDE_EXIT_POLL_INTERVAL_MS;
805
810
  var init_herdr_runner = __esm({
806
811
  "src/herdr-runner.ts"() {
807
812
  "use strict";
808
813
  init_transcript();
809
814
  AGENT_POLL_INTERVAL_MS = 3 * 1e3;
810
815
  PANE_OUTPUT_LINES = 300;
816
+ CLOUD_SESSION_URL_RE = /https:\/\/claude\.ai\/code\/([A-Za-z0-9_-]+)/;
817
+ CLOUD_SESSION_CREATED_RE = /Created cloud session:\s*(session_[A-Za-z0-9_-]+)/;
811
818
  PANE_READY_TIMEOUT_MS = 30 * 1e3;
812
819
  PANE_READY_POLL_INTERVAL_MS = 200;
813
820
  PROMPT_ACCEPT_TIMEOUT_MS = 20 * 1e3;
@@ -1423,12 +1430,12 @@ async function getIssueState(issueNumber) {
1423
1430
  const parsed = JSON.parse(output);
1424
1431
  return parsed.state;
1425
1432
  }
1426
- async function findPrNumberClosingIssue(issueNumber, expectedHeadRefName) {
1433
+ async function listPrsClosingIssue(issueNumber) {
1427
1434
  const { owner, name } = await getRepoInfo();
1428
1435
  const query = `query($owner: String!, $name: String!, $number: Int!) {
1429
1436
  repository(owner: $owner, name: $name) {
1430
1437
  issue(number: $number) {
1431
- closedByPullRequestsReferences(first: 10, includeClosedPrs: true) { nodes { number state headRefName } }
1438
+ closedByPullRequestsReferences(first: 10, includeClosedPrs: true) { nodes { number state headRefName baseRefName createdAt } }
1432
1439
  }
1433
1440
  }
1434
1441
  }`;
@@ -1446,10 +1453,13 @@ async function findPrNumberClosingIssue(issueNumber, expectedHeadRefName) {
1446
1453
  ]);
1447
1454
  const parsed = JSON.parse(output);
1448
1455
  const nodes = parsed?.data?.repository?.issue?.closedByPullRequestsReferences?.nodes ?? [];
1449
- const validPr = nodes.find(
1450
- (node) => (node.state === "MERGED" || node.state === "OPEN") && node.headRefName === expectedHeadRefName
1451
- );
1452
- return validPr ? validPr.number : null;
1456
+ return nodes.map((node) => ({
1457
+ number: node.number,
1458
+ state: node.state ?? "",
1459
+ headRefName: node.headRefName ?? "",
1460
+ baseRefName: node.baseRefName ?? "",
1461
+ createdAt: node.createdAt ?? ""
1462
+ }));
1453
1463
  }
1454
1464
  async function getIssueSubIssuesSummary(issueNumber) {
1455
1465
  const output = await execGh(["issue", "view", String(issueNumber), "--json", "subIssuesSummary"]);
@@ -1543,6 +1553,31 @@ async function hasLabel(type, number, label) {
1543
1553
  return labels.some((l) => l.name === label);
1544
1554
  });
1545
1555
  }
1556
+ async function findCommentSince(number, since, heading) {
1557
+ const output = await execGh(["api", `repos/{owner}/{repo}/issues/${number}/comments?since=${since.toISOString()}`]);
1558
+ const comments = JSON.parse(output);
1559
+ const matched = comments.filter((comment) => comment.body.split("\n").some((line) => line.trim() === heading));
1560
+ if (matched.length === 0) {
1561
+ return null;
1562
+ }
1563
+ return matched[matched.length - 1].body;
1564
+ }
1565
+ async function listNumbersWithLabel(type, label, limit = 50) {
1566
+ const output = await execGh([
1567
+ type,
1568
+ "list",
1569
+ "--label",
1570
+ label,
1571
+ "--state",
1572
+ "all",
1573
+ "--json",
1574
+ "number",
1575
+ "--limit",
1576
+ String(limit)
1577
+ ]);
1578
+ const parsed = JSON.parse(output);
1579
+ return parsed.map((entry) => entry.number);
1580
+ }
1546
1581
  async function hasOpenBlockers(issueNumber) {
1547
1582
  return withRetry(async () => {
1548
1583
  const output = await execGh(["issue", "view", String(issueNumber), "--json", "blockedBy"]);
@@ -1591,369 +1626,71 @@ async function createLabel(name, color, force) {
1591
1626
  }
1592
1627
 
1593
1628
  // src/claude-args.ts
1594
- import { mkdirSync, renameSync, writeFileSync } from "node:fs";
1629
+ import { mkdirSync, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
1595
1630
  import os from "node:os";
1596
1631
  import path from "node:path";
1597
1632
 
1598
- // src/user-config.ts
1599
- import { readFileSync, statSync } from "node:fs";
1600
- import { homedir } from "node:os";
1601
- import { isAbsolute, join, resolve } from "node:path";
1602
- var RESERVED_ALL = "all";
1603
- var DEFAULT_RUN_MODE = "default";
1604
- var DEFAULT_ADVISOR_ENABLED = false;
1605
- var PERMISSION_MODES = [
1606
- "manual",
1607
- "auto",
1608
- "acceptEdits",
1609
- "dontAsk",
1610
- "plan",
1611
- "bypassPermissions"
1612
- ];
1613
- var DEFAULT_PERMISSION_MODE = "bypassPermissions";
1614
- var UserConfigError = class extends Error {
1615
- constructor(message) {
1616
- super(message);
1617
- this.name = "UserConfigError";
1618
- }
1619
- };
1620
- function getConfigDir() {
1621
- const xdg = process.env.XDG_CONFIG_HOME;
1622
- const configHome = xdg && xdg.length > 0 ? xdg : join(homedir(), ".config");
1623
- return join(configHome, "claude-task-worker");
1624
- }
1625
- function getUserConfigPath() {
1626
- return join(getConfigDir(), "config.json");
1627
- }
1628
- function isDirectory(path2) {
1629
- try {
1630
- return statSync(path2).isDirectory();
1631
- } catch {
1632
- return false;
1633
- }
1634
- }
1635
- function parseConfigFile(path2) {
1636
- let content;
1637
- try {
1638
- content = readFileSync(path2, "utf-8");
1639
- } catch (err) {
1640
- if (err.code === "ENOENT") return void 0;
1641
- throw err;
1642
- }
1643
- try {
1644
- return JSON.parse(content);
1645
- } catch (err) {
1646
- if (err instanceof SyntaxError) {
1647
- throw new UserConfigError(`config file contains invalid JSON: ${path2}: ${err.message}`);
1633
+ // src/config.ts
1634
+ import { readFileSync, writeFileSync } from "node:fs";
1635
+ import { isAbsolute, join, normalize, sep as SEP } from "node:path";
1636
+
1637
+ // src/dispatch-args.ts
1638
+ var FLAG_INCOMPATIBLE_COMMANDS = ["init", "install", "update", "usage", "version"];
1639
+ function collectFlagValues(argv, flag) {
1640
+ const values = [];
1641
+ for (let i = 0; i < argv.length; i++) {
1642
+ if (argv[i] !== flag) continue;
1643
+ const raw = argv[i + 1];
1644
+ if (!raw || raw.startsWith("--")) {
1645
+ console.error(`[dispatcher] ${flag} requires a value`);
1646
+ process.exit(1);
1648
1647
  }
1649
- throw err;
1648
+ values.push(raw);
1650
1649
  }
1650
+ return values;
1651
1651
  }
1652
- function readRawConfig() {
1653
- return parseConfigFile(getUserConfigPath());
1654
- }
1655
- function parseMode(raw, path2) {
1656
- if (!("mode" in raw)) return DEFAULT_RUN_MODE;
1657
- const value = raw["mode"];
1658
- if (value === "default" || value === "herdr") return value;
1659
- console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_RUN_MODE}"`);
1660
- return DEFAULT_RUN_MODE;
1661
- }
1662
- function parseAdvisor(raw, path2) {
1663
- if (!("advisor" in raw)) return DEFAULT_ADVISOR_ENABLED;
1664
- const value = raw["advisor"];
1665
- if (typeof value === "boolean") return value;
1666
- console.warn(`[config] invalid advisor: ${JSON.stringify(value)} in ${path2}, using ${DEFAULT_ADVISOR_ENABLED}`);
1667
- return DEFAULT_ADVISOR_ENABLED;
1668
- }
1669
- function parsePermission(raw, path2) {
1670
- if (!("permission" in raw)) return DEFAULT_PERMISSION_MODE;
1671
- const value = raw["permission"];
1672
- if (typeof value === "string" && PERMISSION_MODES.includes(value)) return value;
1673
- console.warn(`[config] invalid permission: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_PERMISSION_MODE}"`);
1674
- return DEFAULT_PERMISSION_MODE;
1675
- }
1676
- function loadUserConfig() {
1677
- const path2 = getUserConfigPath();
1678
- const raw = readRawConfig();
1679
- if (raw === void 0) {
1680
- throw new UserConfigError(`config.json not found: ${path2}`);
1681
- }
1682
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1683
- throw new UserConfigError(`config.json must contain a JSON object: ${path2}`);
1684
- }
1685
- if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
1686
- throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path2}`);
1687
- }
1688
- if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
1689
- throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
1690
- }
1691
- const mode = parseMode(raw, path2);
1692
- const advisor = parseAdvisor(raw, path2);
1693
- const permission = parsePermission(raw, path2);
1694
- const rawProjects = raw["projects"];
1695
- const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
1696
- const projectKeys = Object.keys(rawProjects);
1697
- const groupKeys = Object.keys(rawProjectGroups);
1698
- if (projectKeys.includes(RESERVED_ALL) || groupKeys.includes(RESERVED_ALL)) {
1699
- throw new UserConfigError(
1700
- `"${RESERVED_ALL}" is a reserved word and cannot be used as a key in "projects" or "projectGroups"`
1701
- );
1702
- }
1703
- const groupKeySet = new Set(groupKeys);
1704
- const duplicateKeys = projectKeys.filter((key) => groupKeySet.has(key));
1705
- if (duplicateKeys.length > 0) {
1706
- throw new UserConfigError(
1707
- `"projects" and "projectGroups" share the same key namespace; duplicate keys are not allowed: ${duplicateKeys.join(", ")}`
1708
- );
1709
- }
1710
- const projects = {};
1711
- for (const [name, value] of Object.entries(rawProjects)) {
1712
- if (name === "__proto__") {
1713
- throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path2}`);
1714
- }
1715
- if (typeof value !== "string" || !isAbsolute(value)) {
1716
- console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
1717
- continue;
1718
- }
1719
- if (!isDirectory(value)) {
1720
- console.warn(`[config] projects.${name} does not exist as a directory: ${value}, skipping`);
1721
- continue;
1722
- }
1723
- projects[name] = value;
1724
- }
1725
- const projectGroups = {};
1726
- for (const [groupName, value] of Object.entries(rawProjectGroups)) {
1727
- if (groupName === "__proto__") {
1728
- throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path2}`);
1729
- }
1730
- if (!Array.isArray(value)) {
1731
- console.warn(`[config] invalid projectGroups.${groupName}: expected an array, skipping`);
1732
- continue;
1733
- }
1734
- const members = [];
1735
- for (const member of value) {
1736
- if (typeof member !== "string" || !Object.prototype.hasOwnProperty.call(projects, member)) {
1737
- console.warn(`[config] projectGroups.${groupName} references unknown project "${String(member)}", skipping`);
1738
- continue;
1739
- }
1740
- members.push(member);
1741
- }
1742
- projectGroups[groupName] = members;
1743
- }
1744
- return { mode, advisor, permission, projects, projectGroups };
1652
+ function parseProjectFilters() {
1653
+ return collectFlagValues(process.argv, "--project");
1745
1654
  }
1746
- var cachedRunMode;
1747
- function getRunMode() {
1748
- if (cachedRunMode === void 0) {
1749
- cachedRunMode = readRunMode();
1750
- }
1751
- return cachedRunMode;
1655
+ function hasProjectFilter() {
1656
+ return process.argv.includes("--project");
1752
1657
  }
1753
- var cachedAdvisorEnabled;
1754
- function isAdvisorEnabled() {
1755
- if (cachedAdvisorEnabled === void 0) {
1756
- cachedAdvisorEnabled = readAdvisorEnabled();
1658
+ function assertProjectCompatibleCommand(command) {
1659
+ if (FLAG_INCOMPATIBLE_COMMANDS.includes(command)) {
1660
+ console.error(`[dispatcher] --project cannot be used with the "${command}" command`);
1661
+ process.exit(1);
1757
1662
  }
1758
- return cachedAdvisorEnabled;
1759
1663
  }
1760
- var cachedPermissionMode;
1761
- function getPermissionMode() {
1762
- if (cachedPermissionMode === void 0) {
1763
- cachedPermissionMode = readTopLevel(parsePermission, DEFAULT_PERMISSION_MODE, "permission");
1664
+ var cachedCloudFlag;
1665
+ function hasCloudFlag() {
1666
+ if (cachedCloudFlag === void 0) {
1667
+ cachedCloudFlag = process.argv.includes("--cloud");
1764
1668
  }
1765
- return cachedPermissionMode;
1669
+ return cachedCloudFlag;
1766
1670
  }
1767
- function readTopLevel(parse, fallback, label) {
1768
- let raw;
1769
- try {
1770
- raw = readRawConfig();
1771
- } catch (err) {
1772
- console.warn(`[config] failed to read config file, using default ${label}: ${err}`);
1773
- return fallback;
1774
- }
1775
- if (raw === void 0) return fallback;
1776
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1777
- return fallback;
1671
+ function assertCloudCompatibleCommand(command) {
1672
+ if (FLAG_INCOMPATIBLE_COMMANDS.includes(command)) {
1673
+ console.error(`[worker] --cloud cannot be used with the "${command}" command`);
1674
+ process.exit(1);
1778
1675
  }
1779
- return parse(raw, getUserConfigPath());
1780
- }
1781
- function readAdvisorEnabled() {
1782
- return readTopLevel(parseAdvisor, DEFAULT_ADVISOR_ENABLED, "advisor");
1783
1676
  }
1784
- function readRunMode() {
1785
- return readTopLevel(parseMode, DEFAULT_RUN_MODE, "mode");
1786
- }
1787
- function findProjectNameByPath(path2) {
1788
- let config;
1789
- try {
1790
- config = loadUserConfig();
1791
- } catch {
1792
- return void 0;
1793
- }
1794
- const target = resolve(path2);
1795
- for (const [name, projectPath] of Object.entries(config.projects)) {
1796
- if (resolve(projectPath) === target) return name;
1797
- }
1798
- return void 0;
1677
+ function shellQuote(value) {
1678
+ if (value === "") return "''";
1679
+ return `'${value.replace(/'/g, "'\\''")}'`;
1799
1680
  }
1800
- function resolveTargetProjects(requested, config) {
1801
- const resolved = /* @__PURE__ */ new Map();
1802
- for (const name of requested) {
1803
- if (name === RESERVED_ALL) {
1804
- for (const [projectName, projectPath] of Object.entries(config.projects)) {
1805
- resolved.set(projectName, { name: projectName, path: projectPath });
1806
- }
1807
- continue;
1808
- }
1809
- if (Object.prototype.hasOwnProperty.call(config.projects, name)) {
1810
- resolved.set(name, { name, path: config.projects[name] });
1811
- continue;
1812
- }
1813
- if (Object.prototype.hasOwnProperty.call(config.projectGroups, name)) {
1814
- for (const projectName of config.projectGroups[name]) {
1815
- const projectPath = config.projects[projectName];
1816
- if (projectPath === void 0) continue;
1817
- resolved.set(projectName, { name: projectName, path: projectPath });
1818
- }
1681
+ function buildForwardedCommand(argv) {
1682
+ const tokens = [];
1683
+ for (let i = 0; i < argv.length; i++) {
1684
+ if (argv[i] === "--project") {
1685
+ i++;
1819
1686
  continue;
1820
1687
  }
1821
- const availableProjects = Object.keys(config.projects).join(", ") || "(none)";
1822
- const availableGroups = Object.keys(config.projectGroups).join(", ") || "(none)";
1823
- throw new UserConfigError(
1824
- `Unknown project or group: "${name}". Available projects: ${availableProjects}. Available groups: ${availableGroups}.`
1825
- );
1688
+ tokens.push(argv[i]);
1826
1689
  }
1827
- const resolvedProjects = Array.from(resolved.values());
1828
- if (resolvedProjects.length === 0) {
1829
- throw new UserConfigError(`No projects resolved from requested targets: ${requested.join(", ")}`);
1830
- }
1831
- return resolvedProjects;
1832
- }
1833
-
1834
- // src/claude-args.ts
1835
- var DISALLOWED_TOOLS = [
1836
- // 遅延 / yield: 後続ウェイクアップ前提。print モードではウェイクアップが発火せず、
1837
- // 呼ぶと処理未完のままプロセスが終了する。
1838
- "Monitor",
1839
- "ScheduleWakeup",
1840
- // 対話 / 承認: 自律実行セッションには回答・承認するユーザーが存在しない。
1841
- "AskUserQuestion",
1842
- "EnterPlanMode",
1843
- // スコープ外の副作用を伴う自動化: コード修正タスクに用途がなく、ユーザーの
1844
- // クラウド routine / リモート環境へ副作用を及ぼしうる。
1845
- "CronCreate",
1846
- "CronDelete",
1847
- "CronList",
1848
- "RemoteTrigger",
1849
- // 環境管理の競合: ワーカーは locked worktree の残骸問題のため claude 管理の worktree を
1850
- // 意図的に避け、自前で worktree を生成して cwd として渡している。モデルが worktree を
1851
- // 作成/切り替えると、この前提とクリーンアップが壊れる。
1852
- "EnterWorktree"
1853
- ];
1854
- var DISALLOWED_TOOLS_ARG = DISALLOWED_TOOLS.join(",");
1855
- var CLAUDE_SPAWN_ENV = {
1856
- CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
1857
- CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: "0"
1858
- };
1859
- var SYSTEM_PROMPT_BASE = `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F \`claude-task-worker\` \u306E\u30EF\u30FC\u30AB\u30FC\u304B\u3089\u81EA\u52D5\u8D77\u52D5\u3055\u308C\u3066\u3044\u308B\uFF08\u5FDC\u7B54\u3067\u304D\u308B\u30E6\u30FC\u30B6\u30FC\u306F\u5E38\u99D0\u3057\u3066\u3044\u306A\u3044\uFF09\u3002\u4EE5\u4E0B\u306E\u81EA\u5F8B\u5B9F\u884C\u539F\u5247\u3092\u5FC5\u305A\u9075\u5B88\u3059\u308B\u3053\u3068\u3002
1860
-
1861
- - \u30E6\u30FC\u30B6\u30FC\u3078\u306E\u78BA\u8A8D\u30FB\u8CEA\u554F\u306F\u884C\u308F\u305A\u3001\u8D77\u52D5\u3055\u308C\u305F\u30B9\u30AD\u30EB\u306E\u30EB\u30FC\u30EB\u306B\u5F93\u3063\u3066\u81EA\u5F8B\u7684\u306B\u5224\u65AD\u3059\u308B
1862
- - \u66D6\u6627\u306A\u5834\u5408\u306F\u300C\u3088\u308A\u5B89\u5168\u306A\u5074\uFF08\u7834\u58CA\u7684\u3067\u306A\u3044\u5074\uFF09\u300D\u3092\u9078\u629E\u3057\u3001\u305D\u306E\u5224\u65AD\u3068\u6839\u62E0\u3092\u6700\u7D42\u5831\u544A\u306B\u660E\u8A18\u3059\u308B
1863
- - \u5168\u30B9\u30C6\u30C3\u30D7\u3092\u5B8C\u9042\u3057\u3066\u304B\u3089\u7D42\u4E86\u3059\u308B\uFF08\u30B9\u30AD\u30EB\u306B\u5B9A\u7FA9\u3055\u308C\u305F\u4E2D\u65AD\u6761\u4EF6\u306B\u8A72\u5F53\u3057\u305F\u5834\u5408\u306E\u307F\u3001\u7406\u7531\u3092\u51FA\u529B\u3057\u3066\u7D42\u4E86\u3059\u308B\uFF09
1864
- - \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u4F5C\u696D\u3092\u59D4\u8B72\u3059\u308B\u5834\u5408\u306F\u3001\u4E0A\u8A18\u306E\u539F\u5247\u3092\u59D4\u8B72\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u3082\u660E\u8A18\u3057\u3066\u4F1D\u3048\u308B
1865
- - \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306E\u5B8C\u4E86\u5831\u544A\u306F\u9D5C\u5451\u307F\u306B\u3057\u306A\u3044\u3002\`git diff\` \u7B49\u3067\u5B9F\u969B\u306E\u6210\u679C\u7269\u3092\u691C\u8A3C\u3057\u3066\u304B\u3089\u5B8C\u4E86\u6271\u3044\u306B\u3059\u308B
1866
-
1867
- \u30B3\u30FC\u30C9\u306E\u63A2\u7D22\u30FB\u8ABF\u67FB\u3067\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
1868
-
1869
- - **CodeGraph \u304C\u4F7F\u3048\u308B\u5834\u5408\u306F \`Grep\`/\`Glob\` \u306B\u3088\u308B\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3088\u308A\u512A\u5148\u3059\u308B**\u3002\u30B7\u30F3\u30DC\u30EB\u306E\u5B9A\u7FA9\u5143\u30FB\u53C2\u7167\u5143\u30FB\u547C\u3073\u51FA\u3057\u95A2\u4FC2\u3092\u69CB\u9020\u3068\u3057\u3066\u8FBF\u308C\u308B\u305F\u3081\u3001\u547D\u540D\u3086\u308C\u306B\u3088\u308B\u53D6\u308A\u3053\u307C\u3057\u304C\u8D77\u304D\u306B\u304F\u304F\u3001\u5FC5\u8981\u306A\u60C5\u5831\u306B\u5C11\u306A\u3044\u8A66\u884C\u3067\u5230\u9054\u3067\u304D\u308B
1870
- - **\u5229\u7528\u53EF\u5426\u306F codegraph \u7CFB\u306E MCP \u30C4\u30FC\u30EB\uFF08\`codegraph_explore\` \u7B49\uFF09\u304C\u81EA\u5206\u306B\u4E0E\u3048\u3089\u308C\u3066\u3044\u308B\u304B\u3060\u3051\u3067\u5224\u65AD\u3059\u308B**\u3002\u7121\u3051\u308C\u3070\u300C\u5229\u7528\u4E0D\u53EF\u300D\u3068\u5373\u65AD\u3057\u3066\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3078\u9032\u307F\u3001\u5224\u5B9A\u306B\u624B\u9593\u3092\u304B\u3051\u306A\u3044
1871
- - \u672A\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u306F MCP \u30C4\u30FC\u30EB\u304C\u3042\u3063\u3066\u3082\u30A8\u30E9\u30FC\u3084\u7A7A\u306E\u7D50\u679C\u304C\u8FD4\u308B\u3002\u305D\u306E\u5834\u5408\u3082\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3078\u5207\u308A\u66FF\u3048\u308B\u3060\u3051\u3067\u3088\u304F\u3001\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3092\u7528\u610F\u3057\u3088\u3046\u3068\u3057\u306A\u3044\uFF08\u30BF\u30B9\u30AF\u306E\u8CAC\u52D9\u5916\uFF09
1872
- - CodeGraph \u304C\u8FD4\u3057\u305F\u30BD\u30FC\u30B9\u306F\u300C\u8AAD\u307F\u7D42\u3048\u305F\u3082\u306E\u300D\u3068\u3057\u3066\u6271\u3044\u3001\u540C\u3058\u7B87\u6240\u3092 \`Grep\`/\`Read\` \u3067\u88CF\u53D6\u308A\u3057\u76F4\u3055\u306A\u3044\u3002\u305F\u3060\u3057\u51FA\u529B\u306B staleness\uFF08\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u304C\u53E4\u3044\u65E8\uFF09\u306E\u8B66\u544A\u304C\u51FA\u3066\u3044\u308B\u5834\u5408\u306F\u8A72\u5F53\u30D5\u30A1\u30A4\u30EB\u3092 \`Read\` \u3057\u3066\u73FE\u7269\u3092\u78BA\u8A8D\u3059\u308B
1873
- - \u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u30FB\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u30FB\u30B3\u30E1\u30F3\u30C8/\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u30FB\u672A\u5BFE\u5FDC\u8A00\u8A9E\u306A\u3069 CodeGraph \u304C\u6271\u308F\u306A\u3044\u5BFE\u8C61\u306F\u3001\u5F93\u6765\u3069\u304A\u308A\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3067\u88DC\u3046
1874
- - \u63A2\u7D22\u3092\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u59D4\u8B72\u3059\u308B\u5834\u5408\u306F\u3001\u3053\u306E\u65B9\u91DD\u3082\u59D4\u8B72\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u660E\u8A18\u3057\u3066\u4F1D\u3048\u308B`;
1875
- var OPUS_SYSTEM_PROMPT_ADDENDUM = `\u6210\u679C\u7269\u306E\u5206\u91CF\u3068\u30B9\u30B3\u30FC\u30D7\u306B\u3064\u3044\u3066\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
1876
-
1877
- - \u4F9D\u983C\u3055\u308C\u305F\u30B9\u30B3\u30FC\u30D7\u3060\u3051\u3092\u6210\u679C\u7269\u306B\u3059\u308B\u3002\u5468\u8FBA\u306E\u30EA\u30D5\u30A1\u30AF\u30BF\u30FB\u547D\u540D\u6574\u7406\u30FB\u6C17\u3065\u3044\u305F\u5225\u306E\u6539\u5584\u3092\u52DD\u624B\u306B\u8DB3\u3055\u306A\u3044\u3002\u6C17\u3065\u3044\u305F\u70B9\u306F\u5B9F\u88C5\u305B\u305A\u3001\u6700\u7D42\u5831\u544A\u306B1\u884C\u3067\u6319\u3052\u308B\u3060\u3051\u306B\u3059\u308B
1878
- - \u4F9D\u983C\u306E\u524D\u63D0\u304C\u8AA4\u3063\u3066\u3044\u308B\u3068\u8003\u3048\u308B\u5834\u5408\u3082\u3001\u6307\u6458\u30921-2\u884C\u6DFB\u3048\u305F\u3046\u3048\u3067**\u4F9D\u983C\u3069\u304A\u308A\u306E\u30B9\u30B3\u30FC\u30D7\u3067**\u5B8C\u9042\u3059\u308B\uFF08\u9ED9\u3063\u3066\u7E2E\u5C0F\u30FB\u62E1\u5927\u30FB\u5225\u7269\u3078\u306E\u7F6E\u304D\u63DB\u3048\u3092\u3057\u306A\u3044\uFF09
1879
- - Issue\u30B3\u30E1\u30F3\u30C8\u30FBPR\u306E\u672C\u6587\u30FBdescription\u30FB\u30EC\u30DD\u30FC\u30C8\u7B49\u306E\u66F8\u304D\u7269\u306F\u3001\u5FC5\u8981\u306A\u5B9F\u8CEA\u3060\u3051\u3092\u66F8\u304F\u3002\u540C\u3058\u5185\u5BB9\u306E\u8A00\u3044\u63DB\u3048\u30FB\u57CB\u3081\u8349\u30BB\u30AF\u30B7\u30E7\u30F3\u30FB\u300C\u8A72\u5F53\u306A\u3057\u300D\u3092\u4E26\u3079\u308B\u3060\u3051\u306E\u7BC0\u3092\u8DB3\u3055\u306A\u3044
1880
- - \u6700\u7D42\u5831\u544A\u306F\u7D50\u8AD6\u304B\u3089\u66F8\u304F\u30021\u6587\u76EE\u3067\u300C\u4F55\u3092\u3057\u305F\u304B / \u3069\u3053\u3067\u6B62\u307E\u3063\u305F\u304B\u300D\u3092\u8FF0\u3079\u3001\u8A73\u7D30\u3092\u305D\u306E\u5F8C\u306B\u7F6E\u304F
1881
-
1882
- \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u306E\u59D4\u8B72\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
1883
-
1884
- - \u81EA\u5206\u3067\u6570\u56DE\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3067\u7D42\u308F\u308B\u4F5C\u696D\u306F\u59D4\u8B72\u3057\u306A\u3044\uFF08\u30D6\u30EA\u30FC\u30D5\u30A3\u30F3\u30B0\u4F5C\u6210\u3068\u59D4\u8B72\u5148\u306E\u518D\u63A2\u7D22\u3067\u30B3\u30B9\u30C8\u3068\u6642\u9593\u304C\u500D\u306B\u306A\u308B\uFF09
1885
- - \u59D4\u8B72\u3059\u308B\u306E\u306F\u3001\u72EC\u7ACB\u3057\u3066\u4E26\u5217\u5B9F\u884C\u3067\u304D\u308B\u4F5C\u696D\u30FB\u63A2\u7D22\u7BC4\u56F2\u306E\u5E83\u3044\u8ABF\u67FB\u30FB\u5C02\u9580\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306E\u524D\u63D0\u77E5\u8B58\u304C\u8981\u308B\u4F5C\u696D\u306B\u9650\u308B
1886
- - 1\u30BF\u30B9\u30AF\u306B1\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u30021\u4F53\u3067\u5B8C\u7D50\u3059\u308B\u4F5C\u696D\u306B\u8907\u6570\u4F53\u3092\u91CD\u306D\u3066\u8D77\u52D5\u3057\u306A\u3044
1887
- - **\u81EA\u5206\u306E\u4F5C\u696D\u306E\u78BA\u8A8D\u30FB\u518D\u30C1\u30A7\u30C3\u30AF\u3092\u76EE\u7684\u306B\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3092\u8D77\u52D5\u3057\u306A\u3044**\u3002\u6210\u679C\u7269\u306E\u691C\u8A3C\u306F \`git diff\` \u3084\u30C6\u30B9\u30C8\u30FBLint\u306E\u5B9F\u884C\u3067\u81EA\u5206\u3067\u884C\u3046`;
1888
- function systemPromptFor(model) {
1889
- return isOpusModel(model) ? `${SYSTEM_PROMPT_BASE}
1890
-
1891
- ${OPUS_SYSTEM_PROMPT_ADDENDUM}` : SYSTEM_PROMPT_BASE;
1892
- }
1893
- function isOpusModel(model) {
1894
- return model.toLowerCase().includes("opus");
1895
- }
1896
- var CLAUDE_COMMAND = "claude";
1897
- var cachedSystemPromptFilePaths = /* @__PURE__ */ new Map();
1898
- function systemPromptFilePath(model) {
1899
- const variant = isOpusModel(model) ? "opus" : "default";
1900
- const cached = cachedSystemPromptFilePaths.get(variant);
1901
- if (cached) return cached;
1902
- const dir = path.join(os.tmpdir(), "claude-task-worker");
1903
- mkdirSync(dir, { recursive: true });
1904
- const target = path.join(dir, `append-system-prompt-${process.pid}-${variant}.txt`);
1905
- const tmp = `${target}.tmp`;
1906
- writeFileSync(tmp, systemPromptFor(model), "utf8");
1907
- renameSync(tmp, target);
1908
- cachedSystemPromptFilePaths.set(variant, target);
1909
- return target;
1910
- }
1911
- function buildClaudeArgs({
1912
- mode,
1913
- prompt,
1914
- model,
1915
- effort,
1916
- advisorModel,
1917
- permissionMode
1918
- }) {
1919
- const advisor = advisorModel?.trim() ?? "";
1920
- const permission = permissionMode ?? DEFAULT_PERMISSION_MODE;
1921
- return [
1922
- // default モードはプロンプトを引数で渡す(print モード)。herdr モードでは渡さない:
1923
- // 引数で渡すと claude が起動と同時に作業を始めてしまい、`herdr agent start` が
1924
- // 「入力待ちになるまで」ブロックする仕様と噛み合わない(タスクが終わるまで返らず、
1925
- // 2分を超えると timeout で落ちる)。プロンプトは起動後に `herdr agent prompt` で
1926
- // 投入し、herdr にターンを追跡させる(herdr-runner.ts の startHerdrTask 参照)。
1927
- ...mode === "herdr" ? [] : ["-p", prompt],
1928
- "--permission-mode",
1929
- permission,
1930
- "--disallowedTools",
1931
- DISALLOWED_TOOLS_ARG,
1932
- "--append-system-prompt-file",
1933
- systemPromptFilePath(model),
1934
- "--model",
1935
- model,
1936
- "--effort",
1937
- effort,
1938
- // advisor 未指定(空文字)ならフラグごと省く。値なしの `--advisor` を渡すと
1939
- // 後続フラグを値として食われるため、必ずモデル名とセットでのみ付ける。
1940
- ...advisor === "" ? [] : ["--advisor", advisor]
1941
- ];
1942
- }
1943
- function buildClaudeExecution(invocation) {
1944
- return {
1945
- command: CLAUDE_COMMAND,
1946
- args: buildClaudeArgs(invocation),
1947
- ...invocation.mode === "herdr" ? { prompt: invocation.prompt } : {}
1948
- };
1949
- }
1950
- function buildClaudeEnv(mode) {
1951
- return mode === "herdr" ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
1690
+ return ["claude-task-worker", ...tokens.map(shellQuote)].join(" ");
1952
1691
  }
1953
1692
 
1954
1693
  // src/config.ts
1955
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
1956
- import { isAbsolute as isAbsolute2, join as join2, normalize, sep as SEP } from "node:path";
1957
1694
  var DEFAULT_UI_DESIGN_CONFIG = {
1958
1695
  enabled: false,
1959
1696
  designDir: "designs",
@@ -2115,13 +1852,62 @@ var SCHEDULED_WORKER_NAMES = [
2115
1852
  "update-requirement-rules",
2116
1853
  "update-design-md"
2117
1854
  ];
1855
+ var CLOUD_DONE_LABEL = "cc-cloud-done";
1856
+ var CLOUD_DENIED_WORKERS = [
1857
+ "resolve-conflict",
1858
+ "create-ui-design",
1859
+ "apply-ui-design",
1860
+ ...SCHEDULED_WORKER_NAMES
1861
+ ];
1862
+ function isCloudWorker(name) {
1863
+ return hasCloudFlag() && !CLOUD_DENIED_WORKERS.includes(name);
1864
+ }
1865
+ function checkCloudAuth(input) {
1866
+ if (input.status.kind === "unknown") return [];
1867
+ const { loggedIn, authMethod, apiProvider, apiKeySource } = input.status;
1868
+ const baseUrlSet = !!input.baseUrl;
1869
+ if (loggedIn && apiProvider === "firstParty" && authMethod === "claude.ai" && !apiKeySource && !baseUrlSet) {
1870
+ return [];
1871
+ }
1872
+ const prefix = `\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\uFF08--cloud \u30D5\u30E9\u30B0\uFF09\u306B\u306F claude.ai \u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u304C\u5FC5\u8981\u3067\u3059\u3002\u73FE\u5728\u306E\u8A8D\u8A3C\u69CB\u6210: ${authMethod} / ${apiProvider}\u3002`;
1873
+ if (apiProvider === "bedrock" || apiProvider === "vertex") {
1874
+ return [
1875
+ `${prefix} \u7B2C\u4E09\u8005\u30D7\u30ED\u30D0\u30A4\u30C0\uFF08Bedrock / Vertex\uFF09\u3092\u4F7F\u3063\u3066\u3044\u308B\u5834\u5408: \u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306F Anthropic \u306E\u30A4\u30F3\u30D5\u30E9\u4E0A\u3067\u52D5\u304F\u305F\u3081\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002CLAUDE_CODE_USE_BEDROCK / CLAUDE_CODE_USE_VERTEX \u3092\u89E3\u9664\u3059\u308B\u304B\u3001--cloud \u30D5\u30E9\u30B0\u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
1876
+ ];
1877
+ }
1878
+ if (apiKeySource || authMethod === "oauth_token") {
1879
+ return [
1880
+ `${prefix} API \u30AD\u30FC\u8A8D\u8A3C\uFF08ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN\uFF09\u306E\u5834\u5408: API \u30AD\u30FC\u3067\u306F\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3002\u74B0\u5883\u5909\u6570\u3092\u89E3\u9664\u3057\u3066 claude auth login \u3067\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
1881
+ ];
1882
+ }
1883
+ if (!loggedIn) {
1884
+ return [`${prefix} \u672A\u30B5\u30A4\u30F3\u30A4\u30F3\u306E\u5834\u5408: claude auth login \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`];
1885
+ }
1886
+ if (baseUrlSet) {
1887
+ return [
1888
+ `${prefix} ANTHROPIC_BASE_URL \u3092\u8A2D\u5B9A\u3057\u3066\u3044\u308B\u5834\u5408: \u30AB\u30B9\u30BF\u30E0\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u69CB\u6210\u3067\u306F\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u89E3\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
1889
+ ];
1890
+ }
1891
+ return [`${prefix} claude auth status --json \u306E\u51FA\u529B\u304B\u3089\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u306E\u524D\u63D0\u6761\u4EF6\u3092\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002`];
1892
+ }
1893
+ function checkCloudConfig(input) {
1894
+ if (!input.cloud) return [];
1895
+ const errors = [];
1896
+ if (input.mode !== "herdr") {
1897
+ errors.push(
1898
+ `--cloud requires mode "herdr" but mode is "${input.mode}" (creating a new cloud session requires a TTY, which "default" mode's spawn does not have). Set mode to "herdr" in config.json, or drop the --cloud flag.`
1899
+ );
1900
+ }
1901
+ if (input.auth !== void 0) errors.push(...checkCloudAuth(input.auth));
1902
+ return errors;
1903
+ }
2118
1904
  var DEFAULT_CONFIG = {
2119
1905
  fixReviewPointCallbackCommentMessage: "",
2120
1906
  uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG },
2121
1907
  lastRun: {},
2122
1908
  workers: {}
2123
1909
  };
2124
- var CONFIG_PATH = join2(process.cwd(), "claude-task-worker.json");
1910
+ var CONFIG_PATH = join(process.cwd(), "claude-task-worker.json");
2125
1911
  function defaultsFor(name) {
2126
1912
  return WORKER_DEFAULTS[name] ?? DEFAULT_WORKER_CONFIG;
2127
1913
  }
@@ -2193,6 +1979,11 @@ function parseWorkerEntry(name, val) {
2193
1979
  );
2194
1980
  }
2195
1981
  }
1982
+ if ("cloud" in entry) {
1983
+ console.warn(
1984
+ `[config] workers.${name}.cloud is removed; cloud execution now opts in via the --cloud flag at runtime. This setting is ignored.`
1985
+ );
1986
+ }
2196
1987
  return result;
2197
1988
  }
2198
1989
  function parseUiDesignEntry(val) {
@@ -2222,7 +2013,7 @@ function parseUiDesignEntry(val) {
2222
2013
  }
2223
2014
  if ("designDir" in entry) {
2224
2015
  const normalized = typeof entry.designDir === "string" && entry.designDir.length > 0 ? normalize(entry.designDir) : null;
2225
- const isContained = normalized !== null && !isAbsolute2(normalized) && normalized !== ".." && !normalized.startsWith(`..${SEP}`);
2016
+ const isContained = normalized !== null && !isAbsolute(normalized) && normalized !== ".." && !normalized.startsWith(`..${SEP}`);
2226
2017
  if (isContained) {
2227
2018
  result.designDir = normalized;
2228
2019
  } else {
@@ -2252,7 +2043,7 @@ function loadConfig() {
2252
2043
  const configPath = CONFIG_PATH;
2253
2044
  let raw;
2254
2045
  try {
2255
- raw = JSON.parse(readFileSync2(configPath, "utf-8"));
2046
+ raw = JSON.parse(readFileSync(configPath, "utf-8"));
2256
2047
  } catch (err) {
2257
2048
  if (err.code === "ENOENT") {
2258
2049
  return { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, lastRun: {}, workers: {} };
@@ -2285,47 +2076,454 @@ function loadConfig() {
2285
2076
  }
2286
2077
  return result;
2287
2078
  }
2288
- function getWorkerConfig(workerName) {
2289
- const config = loadConfig();
2290
- return config.workers[workerName] ?? { ...defaultsFor(workerName) };
2079
+ function getWorkerConfig(workerName) {
2080
+ const config = loadConfig();
2081
+ return config.workers[workerName] ?? { ...defaultsFor(workerName) };
2082
+ }
2083
+ function getLastRunAt(workerName) {
2084
+ let at;
2085
+ try {
2086
+ at = loadConfig().lastRun[workerName];
2087
+ } catch (err) {
2088
+ console.warn(`[config] failed to load lastRun, treating ${workerName} as never run: ${err}`);
2089
+ return void 0;
2090
+ }
2091
+ if (at === void 0) return void 0;
2092
+ const parsed = Date.parse(at);
2093
+ return Number.isNaN(parsed) ? void 0 : parsed;
2094
+ }
2095
+ function writeLastRun(repoRoot, workerName, at = /* @__PURE__ */ new Date()) {
2096
+ const path2 = join(repoRoot, "claude-task-worker.json");
2097
+ let raw = {};
2098
+ try {
2099
+ const parsed = JSON.parse(readFileSync(path2, "utf-8"));
2100
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
2101
+ raw = parsed;
2102
+ }
2103
+ } catch (err) {
2104
+ if (err.code !== "ENOENT") {
2105
+ console.warn(`[config] failed to read ${path2}, rewriting it with lastRun only: ${err}`);
2106
+ }
2107
+ }
2108
+ const current = typeof raw["lastRun"] === "object" && raw["lastRun"] !== null ? raw["lastRun"] : {};
2109
+ raw["lastRun"] = { ...current, [workerName]: at.toISOString() };
2110
+ writeFileSync(path2, `${JSON.stringify(raw, null, 2)}
2111
+ `, "utf-8");
2112
+ }
2113
+ function getUiDesignConfig() {
2114
+ try {
2115
+ return loadConfig().uiDesign;
2116
+ } catch (err) {
2117
+ console.warn(`[config] failed to load uiDesign config, using defaults: ${err}`);
2118
+ return { ...DEFAULT_UI_DESIGN_CONFIG };
2119
+ }
2120
+ }
2121
+
2122
+ // src/user-config.ts
2123
+ import { readFileSync as readFileSync2, statSync } from "node:fs";
2124
+ import { homedir } from "node:os";
2125
+ import { isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
2126
+ var RESERVED_ALL = "all";
2127
+ var DEFAULT_RUN_MODE = "default";
2128
+ var DEFAULT_ADVISOR_ENABLED = false;
2129
+ var PERMISSION_MODES = [
2130
+ "manual",
2131
+ "auto",
2132
+ "acceptEdits",
2133
+ "dontAsk",
2134
+ "plan",
2135
+ "bypassPermissions"
2136
+ ];
2137
+ var DEFAULT_PERMISSION_MODE = "bypassPermissions";
2138
+ var UserConfigError = class extends Error {
2139
+ constructor(message) {
2140
+ super(message);
2141
+ this.name = "UserConfigError";
2142
+ }
2143
+ };
2144
+ function getConfigDir() {
2145
+ const xdg = process.env.XDG_CONFIG_HOME;
2146
+ const configHome = xdg && xdg.length > 0 ? xdg : join2(homedir(), ".config");
2147
+ return join2(configHome, "claude-task-worker");
2148
+ }
2149
+ function getUserConfigPath() {
2150
+ return join2(getConfigDir(), "config.json");
2151
+ }
2152
+ function isDirectory(path2) {
2153
+ try {
2154
+ return statSync(path2).isDirectory();
2155
+ } catch {
2156
+ return false;
2157
+ }
2158
+ }
2159
+ function parseConfigFile(path2) {
2160
+ let content;
2161
+ try {
2162
+ content = readFileSync2(path2, "utf-8");
2163
+ } catch (err) {
2164
+ if (err.code === "ENOENT") return void 0;
2165
+ throw err;
2166
+ }
2167
+ try {
2168
+ return JSON.parse(content);
2169
+ } catch (err) {
2170
+ if (err instanceof SyntaxError) {
2171
+ throw new UserConfigError(`config file contains invalid JSON: ${path2}: ${err.message}`);
2172
+ }
2173
+ throw err;
2174
+ }
2175
+ }
2176
+ function readRawConfig() {
2177
+ return parseConfigFile(getUserConfigPath());
2178
+ }
2179
+ function parseMode(raw, path2) {
2180
+ if (!("mode" in raw)) return DEFAULT_RUN_MODE;
2181
+ const value = raw["mode"];
2182
+ if (value === "default" || value === "herdr") return value;
2183
+ console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_RUN_MODE}"`);
2184
+ return DEFAULT_RUN_MODE;
2185
+ }
2186
+ function parseAdvisor(raw, path2) {
2187
+ if (!("advisor" in raw)) return DEFAULT_ADVISOR_ENABLED;
2188
+ const value = raw["advisor"];
2189
+ if (typeof value === "boolean") return value;
2190
+ console.warn(`[config] invalid advisor: ${JSON.stringify(value)} in ${path2}, using ${DEFAULT_ADVISOR_ENABLED}`);
2191
+ return DEFAULT_ADVISOR_ENABLED;
2192
+ }
2193
+ function parsePermission(raw, path2) {
2194
+ if (!("permission" in raw)) return DEFAULT_PERMISSION_MODE;
2195
+ const value = raw["permission"];
2196
+ if (typeof value === "string" && PERMISSION_MODES.includes(value)) return value;
2197
+ console.warn(`[config] invalid permission: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_PERMISSION_MODE}"`);
2198
+ return DEFAULT_PERMISSION_MODE;
2199
+ }
2200
+ function loadUserConfig() {
2201
+ const path2 = getUserConfigPath();
2202
+ const raw = readRawConfig();
2203
+ if (raw === void 0) {
2204
+ throw new UserConfigError(`config.json not found: ${path2}`);
2205
+ }
2206
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2207
+ throw new UserConfigError(`config.json must contain a JSON object: ${path2}`);
2208
+ }
2209
+ if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
2210
+ throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path2}`);
2211
+ }
2212
+ if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
2213
+ throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
2214
+ }
2215
+ const mode = parseMode(raw, path2);
2216
+ const advisor = parseAdvisor(raw, path2);
2217
+ const permission = parsePermission(raw, path2);
2218
+ const rawProjects = raw["projects"];
2219
+ const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
2220
+ const projectKeys = Object.keys(rawProjects);
2221
+ const groupKeys = Object.keys(rawProjectGroups);
2222
+ if (projectKeys.includes(RESERVED_ALL) || groupKeys.includes(RESERVED_ALL)) {
2223
+ throw new UserConfigError(
2224
+ `"${RESERVED_ALL}" is a reserved word and cannot be used as a key in "projects" or "projectGroups"`
2225
+ );
2226
+ }
2227
+ const groupKeySet = new Set(groupKeys);
2228
+ const duplicateKeys = projectKeys.filter((key) => groupKeySet.has(key));
2229
+ if (duplicateKeys.length > 0) {
2230
+ throw new UserConfigError(
2231
+ `"projects" and "projectGroups" share the same key namespace; duplicate keys are not allowed: ${duplicateKeys.join(", ")}`
2232
+ );
2233
+ }
2234
+ const projects = {};
2235
+ for (const [name, value] of Object.entries(rawProjects)) {
2236
+ if (name === "__proto__") {
2237
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path2}`);
2238
+ }
2239
+ if (typeof value !== "string" || !isAbsolute2(value)) {
2240
+ console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
2241
+ continue;
2242
+ }
2243
+ if (!isDirectory(value)) {
2244
+ console.warn(`[config] projects.${name} does not exist as a directory: ${value}, skipping`);
2245
+ continue;
2246
+ }
2247
+ projects[name] = value;
2248
+ }
2249
+ const projectGroups = {};
2250
+ for (const [groupName, value] of Object.entries(rawProjectGroups)) {
2251
+ if (groupName === "__proto__") {
2252
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path2}`);
2253
+ }
2254
+ if (!Array.isArray(value)) {
2255
+ console.warn(`[config] invalid projectGroups.${groupName}: expected an array, skipping`);
2256
+ continue;
2257
+ }
2258
+ const members = [];
2259
+ for (const member of value) {
2260
+ if (typeof member !== "string" || !Object.prototype.hasOwnProperty.call(projects, member)) {
2261
+ console.warn(`[config] projectGroups.${groupName} references unknown project "${String(member)}", skipping`);
2262
+ continue;
2263
+ }
2264
+ members.push(member);
2265
+ }
2266
+ projectGroups[groupName] = members;
2267
+ }
2268
+ return { mode, advisor, permission, projects, projectGroups };
2269
+ }
2270
+ var cachedRunMode;
2271
+ function getRunMode() {
2272
+ if (cachedRunMode === void 0) {
2273
+ cachedRunMode = readRunMode();
2274
+ }
2275
+ return cachedRunMode;
2276
+ }
2277
+ var cachedAdvisorEnabled;
2278
+ function isAdvisorEnabled() {
2279
+ if (cachedAdvisorEnabled === void 0) {
2280
+ cachedAdvisorEnabled = readAdvisorEnabled();
2281
+ }
2282
+ return cachedAdvisorEnabled;
2283
+ }
2284
+ var cachedPermissionMode;
2285
+ function getPermissionMode() {
2286
+ if (cachedPermissionMode === void 0) {
2287
+ cachedPermissionMode = readTopLevel(parsePermission, DEFAULT_PERMISSION_MODE, "permission");
2288
+ }
2289
+ return cachedPermissionMode;
2290
+ }
2291
+ function readTopLevel(parse, fallback, label) {
2292
+ let raw;
2293
+ try {
2294
+ raw = readRawConfig();
2295
+ } catch (err) {
2296
+ console.warn(`[config] failed to read config file, using default ${label}: ${err}`);
2297
+ return fallback;
2298
+ }
2299
+ if (raw === void 0) return fallback;
2300
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2301
+ return fallback;
2302
+ }
2303
+ return parse(raw, getUserConfigPath());
2304
+ }
2305
+ function readAdvisorEnabled() {
2306
+ return readTopLevel(parseAdvisor, DEFAULT_ADVISOR_ENABLED, "advisor");
2307
+ }
2308
+ function readRunMode() {
2309
+ return readTopLevel(parseMode, DEFAULT_RUN_MODE, "mode");
2310
+ }
2311
+ function findProjectNameByPath(path2) {
2312
+ let config;
2313
+ try {
2314
+ config = loadUserConfig();
2315
+ } catch {
2316
+ return void 0;
2317
+ }
2318
+ const target = resolve(path2);
2319
+ for (const [name, projectPath] of Object.entries(config.projects)) {
2320
+ if (resolve(projectPath) === target) return name;
2321
+ }
2322
+ return void 0;
2323
+ }
2324
+ function resolveTargetProjects(requested, config) {
2325
+ const resolved = /* @__PURE__ */ new Map();
2326
+ for (const name of requested) {
2327
+ if (name === RESERVED_ALL) {
2328
+ for (const [projectName, projectPath] of Object.entries(config.projects)) {
2329
+ resolved.set(projectName, { name: projectName, path: projectPath });
2330
+ }
2331
+ continue;
2332
+ }
2333
+ if (Object.prototype.hasOwnProperty.call(config.projects, name)) {
2334
+ resolved.set(name, { name, path: config.projects[name] });
2335
+ continue;
2336
+ }
2337
+ if (Object.prototype.hasOwnProperty.call(config.projectGroups, name)) {
2338
+ for (const projectName of config.projectGroups[name]) {
2339
+ const projectPath = config.projects[projectName];
2340
+ if (projectPath === void 0) continue;
2341
+ resolved.set(projectName, { name: projectName, path: projectPath });
2342
+ }
2343
+ continue;
2344
+ }
2345
+ const availableProjects = Object.keys(config.projects).join(", ") || "(none)";
2346
+ const availableGroups = Object.keys(config.projectGroups).join(", ") || "(none)";
2347
+ throw new UserConfigError(
2348
+ `Unknown project or group: "${name}". Available projects: ${availableProjects}. Available groups: ${availableGroups}.`
2349
+ );
2350
+ }
2351
+ const resolvedProjects = Array.from(resolved.values());
2352
+ if (resolvedProjects.length === 0) {
2353
+ throw new UserConfigError(`No projects resolved from requested targets: ${requested.join(", ")}`);
2354
+ }
2355
+ return resolvedProjects;
2356
+ }
2357
+
2358
+ // src/claude-args.ts
2359
+ var CLOUD_REPORT_HEADING = "## claude-task-worker \u5B9F\u884C\u7D50\u679C";
2360
+ var DISALLOWED_TOOLS = [
2361
+ // 遅延 / yield: 後続ウェイクアップ前提。print モードではウェイクアップが発火せず、
2362
+ // 呼ぶと処理未完のままプロセスが終了する。
2363
+ "Monitor",
2364
+ "ScheduleWakeup",
2365
+ // 対話 / 承認: 自律実行セッションには回答・承認するユーザーが存在しない。
2366
+ "AskUserQuestion",
2367
+ "EnterPlanMode",
2368
+ // スコープ外の副作用を伴う自動化: コード修正タスクに用途がなく、ユーザーの
2369
+ // クラウド routine / リモート環境へ副作用を及ぼしうる。
2370
+ "CronCreate",
2371
+ "CronDelete",
2372
+ "CronList",
2373
+ "RemoteTrigger",
2374
+ // 環境管理の競合: ワーカーは locked worktree の残骸問題のため claude 管理の worktree を
2375
+ // 意図的に避け、自前で worktree を生成して cwd として渡している。モデルが worktree を
2376
+ // 作成/切り替えると、この前提とクリーンアップが壊れる。
2377
+ "EnterWorktree"
2378
+ ];
2379
+ var DISALLOWED_TOOLS_ARG = DISALLOWED_TOOLS.join(",");
2380
+ var CLAUDE_SPAWN_ENV = {
2381
+ CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
2382
+ CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: "0"
2383
+ };
2384
+ var SYSTEM_PROMPT_BASE = `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F \`claude-task-worker\` \u306E\u30EF\u30FC\u30AB\u30FC\u304B\u3089\u81EA\u52D5\u8D77\u52D5\u3055\u308C\u3066\u3044\u308B\uFF08\u5FDC\u7B54\u3067\u304D\u308B\u30E6\u30FC\u30B6\u30FC\u306F\u5E38\u99D0\u3057\u3066\u3044\u306A\u3044\uFF09\u3002\u4EE5\u4E0B\u306E\u81EA\u5F8B\u5B9F\u884C\u539F\u5247\u3092\u5FC5\u305A\u9075\u5B88\u3059\u308B\u3053\u3068\u3002
2385
+
2386
+ - \u30E6\u30FC\u30B6\u30FC\u3078\u306E\u78BA\u8A8D\u30FB\u8CEA\u554F\u306F\u884C\u308F\u305A\u3001\u8D77\u52D5\u3055\u308C\u305F\u30B9\u30AD\u30EB\u306E\u30EB\u30FC\u30EB\u306B\u5F93\u3063\u3066\u81EA\u5F8B\u7684\u306B\u5224\u65AD\u3059\u308B
2387
+ - \u66D6\u6627\u306A\u5834\u5408\u306F\u300C\u3088\u308A\u5B89\u5168\u306A\u5074\uFF08\u7834\u58CA\u7684\u3067\u306A\u3044\u5074\uFF09\u300D\u3092\u9078\u629E\u3057\u3001\u305D\u306E\u5224\u65AD\u3068\u6839\u62E0\u3092\u6700\u7D42\u5831\u544A\u306B\u660E\u8A18\u3059\u308B
2388
+ - \u5168\u30B9\u30C6\u30C3\u30D7\u3092\u5B8C\u9042\u3057\u3066\u304B\u3089\u7D42\u4E86\u3059\u308B\uFF08\u30B9\u30AD\u30EB\u306B\u5B9A\u7FA9\u3055\u308C\u305F\u4E2D\u65AD\u6761\u4EF6\u306B\u8A72\u5F53\u3057\u305F\u5834\u5408\u306E\u307F\u3001\u7406\u7531\u3092\u51FA\u529B\u3057\u3066\u7D42\u4E86\u3059\u308B\uFF09
2389
+ - \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u4F5C\u696D\u3092\u59D4\u8B72\u3059\u308B\u5834\u5408\u306F\u3001\u4E0A\u8A18\u306E\u539F\u5247\u3092\u59D4\u8B72\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u3082\u660E\u8A18\u3057\u3066\u4F1D\u3048\u308B
2390
+ - \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306E\u5B8C\u4E86\u5831\u544A\u306F\u9D5C\u5451\u307F\u306B\u3057\u306A\u3044\u3002\`git diff\` \u7B49\u3067\u5B9F\u969B\u306E\u6210\u679C\u7269\u3092\u691C\u8A3C\u3057\u3066\u304B\u3089\u5B8C\u4E86\u6271\u3044\u306B\u3059\u308B
2391
+
2392
+ \u30B3\u30FC\u30C9\u306E\u63A2\u7D22\u30FB\u8ABF\u67FB\u3067\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
2393
+
2394
+ - **CodeGraph \u304C\u4F7F\u3048\u308B\u5834\u5408\u306F \`Grep\`/\`Glob\` \u306B\u3088\u308B\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3088\u308A\u512A\u5148\u3059\u308B**\u3002\u30B7\u30F3\u30DC\u30EB\u306E\u5B9A\u7FA9\u5143\u30FB\u53C2\u7167\u5143\u30FB\u547C\u3073\u51FA\u3057\u95A2\u4FC2\u3092\u69CB\u9020\u3068\u3057\u3066\u8FBF\u308C\u308B\u305F\u3081\u3001\u547D\u540D\u3086\u308C\u306B\u3088\u308B\u53D6\u308A\u3053\u307C\u3057\u304C\u8D77\u304D\u306B\u304F\u304F\u3001\u5FC5\u8981\u306A\u60C5\u5831\u306B\u5C11\u306A\u3044\u8A66\u884C\u3067\u5230\u9054\u3067\u304D\u308B
2395
+ - **\u5229\u7528\u53EF\u5426\u306F codegraph \u7CFB\u306E MCP \u30C4\u30FC\u30EB\uFF08\`codegraph_explore\` \u7B49\uFF09\u304C\u81EA\u5206\u306B\u4E0E\u3048\u3089\u308C\u3066\u3044\u308B\u304B\u3060\u3051\u3067\u5224\u65AD\u3059\u308B**\u3002\u7121\u3051\u308C\u3070\u300C\u5229\u7528\u4E0D\u53EF\u300D\u3068\u5373\u65AD\u3057\u3066\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3078\u9032\u307F\u3001\u5224\u5B9A\u306B\u624B\u9593\u3092\u304B\u3051\u306A\u3044
2396
+ - \u672A\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3067\u306F MCP \u30C4\u30FC\u30EB\u304C\u3042\u3063\u3066\u3082\u30A8\u30E9\u30FC\u3084\u7A7A\u306E\u7D50\u679C\u304C\u8FD4\u308B\u3002\u305D\u306E\u5834\u5408\u3082\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3078\u5207\u308A\u66FF\u3048\u308B\u3060\u3051\u3067\u3088\u304F\u3001\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3092\u7528\u610F\u3057\u3088\u3046\u3068\u3057\u306A\u3044\uFF08\u30BF\u30B9\u30AF\u306E\u8CAC\u52D9\u5916\uFF09
2397
+ - CodeGraph \u304C\u8FD4\u3057\u305F\u30BD\u30FC\u30B9\u306F\u300C\u8AAD\u307F\u7D42\u3048\u305F\u3082\u306E\u300D\u3068\u3057\u3066\u6271\u3044\u3001\u540C\u3058\u7B87\u6240\u3092 \`Grep\`/\`Read\` \u3067\u88CF\u53D6\u308A\u3057\u76F4\u3055\u306A\u3044\u3002\u305F\u3060\u3057\u51FA\u529B\u306B staleness\uFF08\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u304C\u53E4\u3044\u65E8\uFF09\u306E\u8B66\u544A\u304C\u51FA\u3066\u3044\u308B\u5834\u5408\u306F\u8A72\u5F53\u30D5\u30A1\u30A4\u30EB\u3092 \`Read\` \u3057\u3066\u73FE\u7269\u3092\u78BA\u8A8D\u3059\u308B
2398
+ - \u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u30FB\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u30FB\u30B3\u30E1\u30F3\u30C8/\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u30FB\u672A\u5BFE\u5FDC\u8A00\u8A9E\u306A\u3069 CodeGraph \u304C\u6271\u308F\u306A\u3044\u5BFE\u8C61\u306F\u3001\u5F93\u6765\u3069\u304A\u308A\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3067\u88DC\u3046
2399
+ - \u63A2\u7D22\u3092\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u59D4\u8B72\u3059\u308B\u5834\u5408\u306F\u3001\u3053\u306E\u65B9\u91DD\u3082\u59D4\u8B72\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u660E\u8A18\u3057\u3066\u4F1D\u3048\u308B`;
2400
+ var OPUS_SYSTEM_PROMPT_ADDENDUM = `\u6210\u679C\u7269\u306E\u5206\u91CF\u3068\u30B9\u30B3\u30FC\u30D7\u306B\u3064\u3044\u3066\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
2401
+
2402
+ - \u4F9D\u983C\u3055\u308C\u305F\u30B9\u30B3\u30FC\u30D7\u3060\u3051\u3092\u6210\u679C\u7269\u306B\u3059\u308B\u3002\u5468\u8FBA\u306E\u30EA\u30D5\u30A1\u30AF\u30BF\u30FB\u547D\u540D\u6574\u7406\u30FB\u6C17\u3065\u3044\u305F\u5225\u306E\u6539\u5584\u3092\u52DD\u624B\u306B\u8DB3\u3055\u306A\u3044\u3002\u6C17\u3065\u3044\u305F\u70B9\u306F\u5B9F\u88C5\u305B\u305A\u3001\u6700\u7D42\u5831\u544A\u306B1\u884C\u3067\u6319\u3052\u308B\u3060\u3051\u306B\u3059\u308B
2403
+ - \u4F9D\u983C\u306E\u524D\u63D0\u304C\u8AA4\u3063\u3066\u3044\u308B\u3068\u8003\u3048\u308B\u5834\u5408\u3082\u3001\u6307\u6458\u30921-2\u884C\u6DFB\u3048\u305F\u3046\u3048\u3067**\u4F9D\u983C\u3069\u304A\u308A\u306E\u30B9\u30B3\u30FC\u30D7\u3067**\u5B8C\u9042\u3059\u308B\uFF08\u9ED9\u3063\u3066\u7E2E\u5C0F\u30FB\u62E1\u5927\u30FB\u5225\u7269\u3078\u306E\u7F6E\u304D\u63DB\u3048\u3092\u3057\u306A\u3044\uFF09
2404
+ - Issue\u30B3\u30E1\u30F3\u30C8\u30FBPR\u306E\u672C\u6587\u30FBdescription\u30FB\u30EC\u30DD\u30FC\u30C8\u7B49\u306E\u66F8\u304D\u7269\u306F\u3001\u5FC5\u8981\u306A\u5B9F\u8CEA\u3060\u3051\u3092\u66F8\u304F\u3002\u540C\u3058\u5185\u5BB9\u306E\u8A00\u3044\u63DB\u3048\u30FB\u57CB\u3081\u8349\u30BB\u30AF\u30B7\u30E7\u30F3\u30FB\u300C\u8A72\u5F53\u306A\u3057\u300D\u3092\u4E26\u3079\u308B\u3060\u3051\u306E\u7BC0\u3092\u8DB3\u3055\u306A\u3044
2405
+ - \u6700\u7D42\u5831\u544A\u306F\u7D50\u8AD6\u304B\u3089\u66F8\u304F\u30021\u6587\u76EE\u3067\u300C\u4F55\u3092\u3057\u305F\u304B / \u3069\u3053\u3067\u6B62\u307E\u3063\u305F\u304B\u300D\u3092\u8FF0\u3079\u3001\u8A73\u7D30\u3092\u305D\u306E\u5F8C\u306B\u7F6E\u304F
2406
+
2407
+ \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u306E\u59D4\u8B72\u306F\u4EE5\u4E0B\u306B\u5F93\u3046\u3053\u3068\u3002
2408
+
2409
+ - \u81EA\u5206\u3067\u6570\u56DE\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3067\u7D42\u308F\u308B\u4F5C\u696D\u306F\u59D4\u8B72\u3057\u306A\u3044\uFF08\u30D6\u30EA\u30FC\u30D5\u30A3\u30F3\u30B0\u4F5C\u6210\u3068\u59D4\u8B72\u5148\u306E\u518D\u63A2\u7D22\u3067\u30B3\u30B9\u30C8\u3068\u6642\u9593\u304C\u500D\u306B\u306A\u308B\uFF09
2410
+ - \u59D4\u8B72\u3059\u308B\u306E\u306F\u3001\u72EC\u7ACB\u3057\u3066\u4E26\u5217\u5B9F\u884C\u3067\u304D\u308B\u4F5C\u696D\u30FB\u63A2\u7D22\u7BC4\u56F2\u306E\u5E83\u3044\u8ABF\u67FB\u30FB\u5C02\u9580\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306E\u524D\u63D0\u77E5\u8B58\u304C\u8981\u308B\u4F5C\u696D\u306B\u9650\u308B
2411
+ - 1\u30BF\u30B9\u30AF\u306B1\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u30021\u4F53\u3067\u5B8C\u7D50\u3059\u308B\u4F5C\u696D\u306B\u8907\u6570\u4F53\u3092\u91CD\u306D\u3066\u8D77\u52D5\u3057\u306A\u3044
2412
+ - **\u81EA\u5206\u306E\u4F5C\u696D\u306E\u78BA\u8A8D\u30FB\u518D\u30C1\u30A7\u30C3\u30AF\u3092\u76EE\u7684\u306B\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3092\u8D77\u52D5\u3057\u306A\u3044**\u3002\u6210\u679C\u7269\u306E\u691C\u8A3C\u306F \`git diff\` \u3084\u30C6\u30B9\u30C8\u30FBLint\u306E\u5B9F\u884C\u3067\u81EA\u5206\u3067\u884C\u3046`;
2413
+ function systemPromptFor(model) {
2414
+ return isOpusModel(model) ? `${SYSTEM_PROMPT_BASE}
2415
+
2416
+ ${OPUS_SYSTEM_PROMPT_ADDENDUM}` : SYSTEM_PROMPT_BASE;
2417
+ }
2418
+ function isOpusModel(model) {
2419
+ return model.toLowerCase().includes("opus");
2420
+ }
2421
+ var CLAUDE_COMMAND = "claude";
2422
+ var cachedSystemPromptFilePaths = /* @__PURE__ */ new Map();
2423
+ function systemPromptFilePath(model) {
2424
+ const variant = isOpusModel(model) ? "opus" : "default";
2425
+ const cached = cachedSystemPromptFilePaths.get(variant);
2426
+ if (cached) return cached;
2427
+ const dir = path.join(os.tmpdir(), "claude-task-worker");
2428
+ mkdirSync(dir, { recursive: true });
2429
+ const target = path.join(dir, `append-system-prompt-${process.pid}-${variant}.txt`);
2430
+ const tmp = `${target}.tmp`;
2431
+ writeFileSync2(tmp, systemPromptFor(model), "utf8");
2432
+ renameSync(tmp, target);
2433
+ cachedSystemPromptFilePaths.set(variant, target);
2434
+ return target;
2435
+ }
2436
+ function buildClaudeArgs({
2437
+ mode,
2438
+ prompt,
2439
+ model,
2440
+ effort,
2441
+ advisorModel,
2442
+ permissionMode,
2443
+ cloud,
2444
+ baseRef,
2445
+ onBranch
2446
+ }) {
2447
+ const ref = baseRef?.trim() ?? "";
2448
+ const onBranchValue = onBranch?.trim() ?? "";
2449
+ if (cloud === true && ref !== "" && onBranchValue !== "") {
2450
+ throw new Error("--on-branch and --ref both set the cloud session's base branch; pass one or the other");
2451
+ }
2452
+ const advisor = advisorModel?.trim() ?? "";
2453
+ const permission = permissionMode ?? DEFAULT_PERMISSION_MODE;
2454
+ return [
2455
+ // default モードはプロンプトを引数で渡す(print モード)。herdr モードでは渡さない:
2456
+ // 引数で渡すと claude が起動と同時に作業を始めてしまい、`herdr agent start` が
2457
+ // 「入力待ちになるまで」ブロックする仕様と噛み合わない(タスクが終わるまで返らず、
2458
+ // 2分を超えると timeout で落ちる)。プロンプトは起動後に `herdr agent prompt` で
2459
+ // 投入し、herdr にターンを追跡させる(herdr-runner.ts の startHerdrTask 参照)。
2460
+ // クラウド実行(`cloud: true`)も print モード非対応のため同様に省く
2461
+ // (実測 T2: `Error: --cloud cannot be combined with --print.`)。
2462
+ ...mode === "herdr" || cloud === true ? [] : ["-p", prompt],
2463
+ "--permission-mode",
2464
+ permission,
2465
+ "--disallowedTools",
2466
+ DISALLOWED_TOOLS_ARG,
2467
+ "--append-system-prompt-file",
2468
+ systemPromptFilePath(model),
2469
+ "--model",
2470
+ model,
2471
+ "--effort",
2472
+ effort,
2473
+ // advisor 未指定(空文字)ならフラグごと省く。値なしの `--advisor` を渡すと
2474
+ // 後続フラグを値として食われるため、必ずモデル名とセットでのみ付ける。
2475
+ ...advisor === "" ? [] : ["--advisor", advisor],
2476
+ // クラウド実行時は「作成コマンドの共通フラグ」だけをここで返す。`--cloud` 自体は
2477
+ // 付けない(値として渡す description(=クラウドセッションの初期プロンプト。
2478
+ // appendCloudDoneInstruction() 適用後のタスクプロンプトそのもの)は
2479
+ // `src/process-manager.ts` 側でしか決まらないため。`buildCloudCreateArgs()` が
2480
+ // このフラグ列の先頭へ `--cloud <description>` を足して作成コマンドを完成させる)。
2481
+ // ベースブランチ指定は `--ref` / `--on-branch` のどちらか一方のみ(両方指定は上で例外)。
2482
+ ...cloud === true && ref !== "" ? ["--ref", ref] : [],
2483
+ ...cloud === true && onBranchValue !== "" ? ["--on-branch", onBranchValue] : []
2484
+ ];
2485
+ }
2486
+ function buildCloudCreateArgs(commonArgs, prompt) {
2487
+ return ["--cloud", prompt, ...commonArgs];
2488
+ }
2489
+ function appendCloudDoneInstruction(prompt, target) {
2490
+ const targetLabel = target.type === "issue" ? `Issue #${target.number}` : `PR #${target.number}`;
2491
+ const reportInstruction = `\`${CLOUD_DONE_LABEL}\` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u308B\u76F4\u524D\u306B\u3001${targetLabel} \u3078 \`${CLOUD_REPORT_HEADING}\` \u3092\u898B\u51FA\u3057\u3068\u3059\u308B\u30B3\u30E1\u30F3\u30C8\u30921\u4EF6\u6295\u7A3F\u3057\u3001\u672C\u6587\u306B\u6700\u7D42\u5831\u544A\uFF08\u5B8C\u4E86\u30FB\u4E2D\u65AD\u306B\u304B\u304B\u308F\u3089\u305A\uFF09\u3092\u66F8\u304F\u3053\u3068\u3002GitHub MCP\uFF08\`add_issue_comment\`\uFF09\u3092\u512A\u5148\u3057\u3001\u5931\u6557\u3057\u305F\u5834\u5408\u306E\u307F \`gh ${target.type} comment ${target.number} --body-file -\` \u3078\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3059\u308B\u3053\u3068\uFF08\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u306F1\u56DE\u307E\u3067\uFF09\u3002\u30EF\u30FC\u30AB\u30FC\u306F\u3053\u306E\u30B3\u30E1\u30F3\u30C8\u3092\u6700\u7D42\u30EC\u30DD\u30FC\u30C8\u3068\u3057\u3066\u56DE\u53CE\u3057 Slack \u901A\u77E5\u306B\u8F09\u305B\u308B\u3002`;
2492
+ const labelInstruction = `\u4E0A\u8A18\u30B3\u30E1\u30F3\u30C8\u306E\u6295\u7A3F\u5F8C\u3001\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6700\u5F8C\u306E\u64CD\u4F5C\u3068\u3057\u3066 ${targetLabel} \u306B \`${CLOUD_DONE_LABEL}\` \u30E9\u30D9\u30EB\u3092\u4ED8\u4E0E\u3059\u308B\u3053\u3068\u3002GitHub MCP\uFF08\`issue_write\` / method: \`update\`\uFF09\u3092\u512A\u5148\u3057\u3001\u5931\u6557\u3057\u305F\u5834\u5408\u306E\u307F \`gh ${target.type} edit ${target.number} --add-label ${CLOUD_DONE_LABEL}\` \u3078\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3059\u308B\u3053\u3068\uFF08\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u306F1\u56DE\u307E\u3067\uFF09\u3002\u30EF\u30FC\u30AB\u30FC\u306F\u3053\u306E\u30E9\u30D9\u30EB\u3067\u30BF\u30B9\u30AF\u306E\u7D42\u4E86\u3092\u691C\u77E5\u3057\u3066\u304A\u308A\u3001\u4ED8\u4E0E\u3055\u308C\u306A\u3044\u3068\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u307E\u3067\u5B8C\u4E86\u6271\u3044\u306B\u306A\u3089\u306A\u3044\u3002`;
2493
+ return `${prompt}
2494
+
2495
+ ${reportInstruction}
2496
+
2497
+ ${labelInstruction}`;
2498
+ }
2499
+ function buildCloudToolRestriction() {
2500
+ return `\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u3067\u306F \`--disallowedTools\` \u30D5\u30E9\u30B0\u306B\u3088\u308B\u30C4\u30FC\u30EB\u5236\u9650\u304C\u53CD\u6620\u3055\u308C\u306A\u3044\u305F\u3081\u3001\u4EE5\u4E0B\u306E\u30C4\u30FC\u30EB\u3092\u4F7F\u308F\u306A\u3044\u3053\u3068: ${DISALLOWED_TOOLS.join(", ")}`;
2291
2501
  }
2292
- function getLastRunAt(workerName) {
2293
- let at;
2294
- try {
2295
- at = loadConfig().lastRun[workerName];
2296
- } catch (err) {
2297
- console.warn(`[config] failed to load lastRun, treating ${workerName} as never run: ${err}`);
2298
- return void 0;
2299
- }
2300
- if (at === void 0) return void 0;
2301
- const parsed = Date.parse(at);
2302
- return Number.isNaN(parsed) ? void 0 : parsed;
2502
+ function buildCloudPrompt(prompt, model, target) {
2503
+ const principles = `\u4EE5\u4E0B\u306F\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u5B9F\u884C\u539F\u5247\u3067\u3042\u308B\u3002\u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u3067\u306F\u30B7\u30B9\u30C6\u30E0\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u3088\u308B\u6CE8\u5165\u304C\u53CD\u6620\u3055\u308C\u306A\u3044\u305F\u3081\u3001\u30D7\u30ED\u30F3\u30D7\u30C8\u672C\u6587\u3068\u3057\u3066\u6E21\u3057\u3066\u3044\u308B\u3002
2504
+
2505
+ ${systemPromptFor(model)}
2506
+
2507
+ ${buildCloudToolRestriction()}`;
2508
+ const withPrinciples = `${prompt}
2509
+
2510
+ ${principles}`;
2511
+ return target ? appendCloudDoneInstruction(withPrinciples, target) : withPrinciples;
2303
2512
  }
2304
- function writeLastRun(repoRoot, workerName, at = /* @__PURE__ */ new Date()) {
2305
- const path2 = join2(repoRoot, "claude-task-worker.json");
2306
- let raw = {};
2307
- try {
2308
- const parsed = JSON.parse(readFileSync2(path2, "utf-8"));
2309
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
2310
- raw = parsed;
2311
- }
2312
- } catch (err) {
2313
- if (err.code !== "ENOENT") {
2314
- console.warn(`[config] failed to read ${path2}, rewriting it with lastRun only: ${err}`);
2315
- }
2316
- }
2317
- const current = typeof raw["lastRun"] === "object" && raw["lastRun"] !== null ? raw["lastRun"] : {};
2318
- raw["lastRun"] = { ...current, [workerName]: at.toISOString() };
2319
- writeFileSync2(path2, `${JSON.stringify(raw, null, 2)}
2320
- `, "utf-8");
2513
+ function shellQuote2(value) {
2514
+ return `'${value.replace(/'/g, "'\\''")}'`;
2321
2515
  }
2322
- function getUiDesignConfig() {
2323
- try {
2324
- return loadConfig().uiDesign;
2325
- } catch (err) {
2326
- console.warn(`[config] failed to load uiDesign config, using defaults: ${err}`);
2327
- return { ...DEFAULT_UI_DESIGN_CONFIG };
2328
- }
2516
+ function buildClaudeExecution(invocation) {
2517
+ return {
2518
+ command: CLAUDE_COMMAND,
2519
+ args: buildClaudeArgs(invocation),
2520
+ ...invocation.mode === "herdr" ? { prompt: invocation.prompt } : {}
2521
+ };
2522
+ }
2523
+ function buildClaudeEnv(mode, cloud) {
2524
+ const base = mode === "herdr" ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
2525
+ if (!cloud) return base;
2526
+ return { ...base, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1" };
2329
2527
  }
2330
2528
 
2331
2529
  // src/git.ts
@@ -2379,6 +2577,12 @@ init_table();
2379
2577
 
2380
2578
  // src/task-result.ts
2381
2579
  var STDERR_TAIL_LIMIT = 8 * 1024;
2580
+ var CLOUD_FAILURE_GUIDANCE = "[worker] \u30AF\u30E9\u30A6\u30C9\u5B9F\u884C\u306E\u524D\u63D0\u6761\u4EF6\uFF08GitHub \u9023\u643A / allow_remote_sessions \u7D44\u7E54\u30DD\u30EA\u30B7\u30FC\uFF09\u304C\u6E80\u305F\u3055\u308C\u3066\u3044\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u8A73\u7D30\u306F docs/cloud-prerequisite-checks.md \u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002";
2581
+ function appendCloudFailureGuidance(result, cloud) {
2582
+ if (!cloud || result.status === "completed") return result;
2583
+ return { ...result, output: `${CLOUD_FAILURE_GUIDANCE}
2584
+ ${result.output}` };
2585
+ }
2382
2586
  function buildTaskResult(code, stdout, stderrTail) {
2383
2587
  const emptyOutput = stdout.trim() === "";
2384
2588
  const completed = code === 0 && !emptyOutput;
@@ -2475,7 +2679,7 @@ function ensureRenderInterval() {
2475
2679
  async function finishTask(id, result, onComplete) {
2476
2680
  try {
2477
2681
  await Promise.race([
2478
- onComplete?.(result.status, result.output) ?? Promise.resolve(),
2682
+ onComplete?.(result.status, result.output, result.cloudSessionId) ?? Promise.resolve(),
2479
2683
  new Promise(
2480
2684
  (_, reject) => setTimeout(() => reject(new Error("onComplete timed out after 120s")), 12e4).unref()
2481
2685
  )
@@ -2533,7 +2737,198 @@ async function runViaHerdr(args, prompt, id, onComplete, cwd, env) {
2533
2737
  await finishTask(id, result, onComplete);
2534
2738
  herdrTasks.delete(id);
2535
2739
  }
2536
- function run(command, args, id, title, workerName, path2, onComplete, cwd, env, prompt) {
2740
+ var CLOUD_SESSION_TIMEOUT_MS = Number(process.env.CTW_CLOUD_SESSION_TIMEOUT_MS) || 120 * 1e3;
2741
+ var CLOUD_SESSION_POLL_INTERVAL_MS = 1e3;
2742
+ var CLOUD_POLL_INTERVAL_MS = 30 * 1e3;
2743
+ var CLOUD_TASK_TIMEOUT_MS = Number(process.env.CTW_CLOUD_TASK_TIMEOUT_MS) || 4 * 60 * 60 * 1e3;
2744
+ var cloudWaiters = /* @__PURE__ */ new Map();
2745
+ var cloudPollLoopRunning = false;
2746
+ function ensureCloudPollLoop() {
2747
+ if (cloudPollLoopRunning) return;
2748
+ cloudPollLoopRunning = true;
2749
+ void (async () => {
2750
+ try {
2751
+ await pollCloudWaiters();
2752
+ } finally {
2753
+ cloudPollLoopRunning = false;
2754
+ }
2755
+ })();
2756
+ }
2757
+ async function pollCloudWaiters() {
2758
+ while (cloudWaiters.size > 0) {
2759
+ if (herdrAbortSignal.aborted) {
2760
+ for (const waiter of cloudWaiters.values()) waiter.settle("aborted");
2761
+ cloudWaiters.clear();
2762
+ break;
2763
+ }
2764
+ const pendingTypes = /* @__PURE__ */ new Set();
2765
+ for (const waiter of cloudWaiters.values()) pendingTypes.add(waiter.type);
2766
+ const doneKeys = /* @__PURE__ */ new Map();
2767
+ for (const type of pendingTypes) {
2768
+ try {
2769
+ const numbers = await listNumbersWithLabel(type, CLOUD_DONE_LABEL);
2770
+ doneKeys.set(type, new Set(numbers));
2771
+ } catch (err) {
2772
+ console.error(`[worker] failed to poll ${CLOUD_DONE_LABEL} for ${type}: ${err}`);
2773
+ }
2774
+ }
2775
+ const now = Date.now();
2776
+ for (const [key, waiter] of [...cloudWaiters.entries()]) {
2777
+ const done = doneKeys.get(waiter.type);
2778
+ const number = Number(key.split(":")[1]);
2779
+ if (done?.has(number)) {
2780
+ waiter.settle("completed");
2781
+ cloudWaiters.delete(key);
2782
+ } else if (now >= waiter.deadline) {
2783
+ waiter.settle("timeout");
2784
+ cloudWaiters.delete(key);
2785
+ }
2786
+ }
2787
+ if (cloudWaiters.size === 0) break;
2788
+ for (let waited = 0; waited < CLOUD_POLL_INTERVAL_MS; waited += 1e3) {
2789
+ if (herdrAbortSignal.aborted) break;
2790
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3));
2791
+ }
2792
+ }
2793
+ }
2794
+ function waitForCloudTask(id, type) {
2795
+ return new Promise((resolve3) => {
2796
+ cloudWaiters.set(`${type}:${id}`, {
2797
+ type,
2798
+ deadline: Date.now() + CLOUD_TASK_TIMEOUT_MS,
2799
+ settle: resolve3
2800
+ });
2801
+ ensureCloudPollLoop();
2802
+ });
2803
+ }
2804
+ async function flagOrphanedCloudSession(target, id, reason, cloudSessionId) {
2805
+ await addLabel(target, id, "cc-need-human-check").catch((err) => {
2806
+ console.error(`[worker] failed to add cc-need-human-check to ${target} #${id}: ${err}`);
2807
+ });
2808
+ const causeLine = reason === "session-id" ? "\u30BB\u30C3\u30B7\u30E7\u30F3ID\u306E\u62BD\u51FA\u306B\u5931\u6557\u3057\u3066\u5F85\u6A5F\u3092\u6253\u3061\u5207\u308A\u307E\u3057\u305F\u3002\u30D7\u30ED\u30F3\u30D7\u30C8\u306F\u6295\u5165\u6E08\u307F\u3067\u3001\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u72EC\u7ACB\u3057\u3066\u7A3C\u50CD\u3057\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002" : "\u30EF\u30FC\u30AB\u30FC\u306E\u30B7\u30E3\u30C3\u30C8\u30C0\u30A6\u30F3\u3067\u5F85\u6A5F\u3092\u6253\u3061\u5207\u308A\u307E\u3057\u305F\u3002\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u7D99\u7D9A\u3057\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u9AD8\u3044\u3067\u3059\u3002";
2809
+ const sessionLine = cloudSessionId ? `https://claude.ai/code/${cloudSessionId}` : "\u30BB\u30C3\u30B7\u30E7\u30F3URL\u4E0D\u660E\uFF08ID\u62BD\u51FA\u306B\u5931\u6557\uFF09";
2810
+ const body = `## \u5B64\u7ACB\u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u53EF\u80FD\u6027
2811
+
2812
+ **\u539F\u56E0**: ${causeLine}
2813
+
2814
+ **\u30BB\u30C3\u30B7\u30E7\u30F3URL**: ${sessionLine}
2815
+
2816
+ **\u518D\u958B\u65B9\u6CD5**: \u5B64\u7ACB\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6210\u679C\u7269\u304C\u5C4A\u3044\u3066\u3044\u306A\u3044\u304B\u78BA\u8A8D\u3057\u305F\u3046\u3048\u3067\u3001\`cc-need-human-check\` \u30E9\u30D9\u30EB\u3092\u5916\u3059\u3068\u30DD\u30FC\u30EA\u30F3\u30B0\u5BFE\u8C61\u306B\u623B\u308A\u307E\u3059\u3002`;
2817
+ const commentFn = target === "issue" ? commentOnIssue : commentOnPR;
2818
+ await commentFn(id, body).catch((err) => {
2819
+ console.error(`[worker] failed to comment on ${target} #${id} about the orphaned cloud session: ${err}`);
2820
+ });
2821
+ }
2822
+ async function runViaCloud(args, prompt, id, onComplete, cwd, env, cloudTarget, model) {
2823
+ const herdrRunnerMod = await Promise.resolve().then(() => (init_herdr_runner(), herdr_runner_exports));
2824
+ const { taskTabLabel: taskTabLabel2, waitForPaneReady: waitForPaneReady3, extractCloudSessionId: extractCloudSessionId2 } = herdrRunnerMod;
2825
+ const herdrMod = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
2826
+ const { tabCreate: tabCreate2, tabClose: tabClose2, paneSendText: paneSendText2, paneSendKeys: paneSendKeys2, paneRead: paneRead2, getCurrentWorkspaceId: getCurrentWorkspaceId2 } = herdrMod;
2827
+ herdrTasks.set(id, { paneId: "", tabId: "" });
2828
+ const label = taskTabLabel2(resolveProjectName(), id);
2829
+ const initialPrompt = buildCloudPrompt(
2830
+ prompt,
2831
+ model ?? "",
2832
+ cloudTarget ? { type: cloudTarget, number: id } : void 0
2833
+ );
2834
+ let result;
2835
+ let cloudSessionId;
2836
+ try {
2837
+ const created = await tabCreate2({ label, cwd: cwd ?? process.cwd(), workspaceId: getCurrentWorkspaceId2(), env });
2838
+ herdrTasks.set(id, created);
2839
+ try {
2840
+ const ready = await waitForPaneReady3(created.paneId, herdrMod);
2841
+ if (!ready) {
2842
+ console.warn(`[worker] pane ${created.paneId} produced no prompt before the timeout, launching anyway`);
2843
+ }
2844
+ const command = ["claude", ...buildCloudCreateArgs(args, initialPrompt)].map(shellQuote2).join(" ");
2845
+ await paneSendText2(created.paneId, command);
2846
+ await paneSendKeys2(created.paneId, "enter");
2847
+ const deadline = Date.now() + CLOUD_SESSION_TIMEOUT_MS;
2848
+ for (; ; ) {
2849
+ if (herdrAbortSignal.aborted) {
2850
+ throw new Error("the worker is shutting down before the cloud session could be created");
2851
+ }
2852
+ let content = "";
2853
+ try {
2854
+ content = await paneRead2(created.paneId);
2855
+ } catch (err) {
2856
+ console.error(`[worker] failed to read pane ${created.paneId} while waiting for the cloud session: ${err}`);
2857
+ }
2858
+ cloudSessionId = extractCloudSessionId2(content);
2859
+ if (cloudSessionId) break;
2860
+ if (Date.now() >= deadline) {
2861
+ throw new Error(`timed out waiting for the cloud session id (pane tail: ${content.slice(-1e3)})`);
2862
+ }
2863
+ await new Promise((resolve3) => setTimeout(resolve3, CLOUD_SESSION_POLL_INTERVAL_MS));
2864
+ }
2865
+ } finally {
2866
+ await tabClose2(created.tabId).catch((err) => {
2867
+ console.error(`[worker] failed to close cloud task tab ${created.tabId}: ${err}`);
2868
+ });
2869
+ }
2870
+ const createOutput = `[worker] created cloud session ${cloudSessionId} with the task's initial prompt`;
2871
+ if (!cloudTarget) {
2872
+ console.warn(`[worker] #${id} has no completion-detection target, treating session creation as completion`);
2873
+ result = { status: "completed", output: createOutput };
2874
+ } else {
2875
+ const outcome = await waitForCloudTask(id, cloudTarget);
2876
+ if (outcome === "completed") {
2877
+ await removeLabel(cloudTarget, id, CLOUD_DONE_LABEL).catch((err) => {
2878
+ console.error(`[worker] failed to remove ${CLOUD_DONE_LABEL} from ${cloudTarget} #${id}: ${err}`);
2879
+ });
2880
+ let reportBody = null;
2881
+ const startedAt = tasks.get(id)?.startedAt;
2882
+ if (startedAt) {
2883
+ try {
2884
+ reportBody = await findCommentSince(id, startedAt, CLOUD_REPORT_HEADING);
2885
+ } catch (err) {
2886
+ console.error(`[worker] failed to fetch the cloud report comment for ${cloudTarget} #${id}: ${err}`);
2887
+ }
2888
+ }
2889
+ result = {
2890
+ status: "completed",
2891
+ output: reportBody ?? `${createOutput}
2892
+ [worker] detected completion via the ${CLOUD_DONE_LABEL} label`
2893
+ };
2894
+ } else if (outcome === "timeout") {
2895
+ await addLabel(cloudTarget, id, "cc-need-human-check").catch((err) => {
2896
+ console.error(`[worker] failed to add cc-need-human-check to ${cloudTarget} #${id}: ${err}`);
2897
+ });
2898
+ result = {
2899
+ status: "failed",
2900
+ output: `${createOutput}
2901
+ [worker] timed out waiting for the ${CLOUD_DONE_LABEL} label after ${CLOUD_TASK_TIMEOUT_MS / 1e3 / 60} minutes. Possible causes: the session stopped on AskUserQuestion, the cloud VM crashed, the plugin was not installed, or label assignment itself failed.`
2902
+ };
2903
+ } else {
2904
+ await flagOrphanedCloudSession(cloudTarget, id, "shutdown", cloudSessionId);
2905
+ result = {
2906
+ status: "failed",
2907
+ output: `${createOutput}
2908
+ [worker] shutdown aborted the wait for the ${CLOUD_DONE_LABEL} label. The session may still be running; added cc-need-human-check.`
2909
+ };
2910
+ }
2911
+ }
2912
+ result.cloudSessionId = cloudSessionId;
2913
+ } catch (err) {
2914
+ console.error(`[worker] failed to run #${id} via cloud: ${err}`);
2915
+ let orphanNote = "[worker] note: with the 1-command launch, the cloud session may already have started working independently even though this task is being reported as failed locally (orphaned session).";
2916
+ if (cloudTarget) {
2917
+ await flagOrphanedCloudSession(cloudTarget, id, "session-id", cloudSessionId);
2918
+ orphanNote += " added cc-need-human-check so a human can verify whether it completed on its own.";
2919
+ }
2920
+ result = {
2921
+ status: "failed",
2922
+ output: `[worker] failed to run the task via cloud: ${err}
2923
+ ${orphanNote}`,
2924
+ ...cloudSessionId ? { cloudSessionId } : {}
2925
+ };
2926
+ }
2927
+ result = appendCloudFailureGuidance(result, true);
2928
+ await finishTask(id, result, onComplete);
2929
+ herdrTasks.delete(id);
2930
+ }
2931
+ function run(command, args, id, title, workerName, path2, onComplete, cwd, env, prompt, cloud, cloudTarget, model) {
2537
2932
  tasks.delete(id);
2538
2933
  tasks.set(id, {
2539
2934
  id,
@@ -2546,6 +2941,10 @@ function run(command, args, id, title, workerName, path2, onComplete, cwd, env,
2546
2941
  ensureRenderInterval();
2547
2942
  renderTable();
2548
2943
  if (getRunMode() === "herdr") {
2944
+ if (cloud) {
2945
+ void runViaCloud(args, prompt ?? "", id, onComplete, cwd, env, cloudTarget, model);
2946
+ return;
2947
+ }
2549
2948
  void runViaHerdr(args, prompt ?? "", id, onComplete, cwd, env);
2550
2949
  return;
2551
2950
  }
@@ -3058,22 +3457,59 @@ async function buildTokenLimitText() {
3058
3457
  writeRuncatUsage(usage);
3059
3458
  return formatTokenLimitText(usage);
3060
3459
  }
3061
- async function notifyTaskCompleted(workerName, repoName, id, title, url, output) {
3062
- const tokenText = await buildTokenLimitText();
3063
- const truncatedOutput = output && output.length > 1e3 ? `\u2026${output.slice(-1e3)}` : output;
3460
+ function truncateOutputForNotification(output) {
3461
+ if (output.length <= 1e3) return output;
3462
+ if (output.startsWith(CLOUD_FAILURE_GUIDANCE)) {
3463
+ const rest = output.slice(CLOUD_FAILURE_GUIDANCE.length);
3464
+ return `${CLOUD_FAILURE_GUIDANCE}\u2026${rest.slice(-1e3)}`;
3465
+ }
3466
+ return `\u2026${output.slice(-1e3)}`;
3467
+ }
3468
+ function buildTaskNotificationText(params) {
3469
+ const { status, workerName, repoName, id, title, url, tokenText, output, cloud } = params;
3470
+ const emoji = status === "completed" ? "\u2705" : "\u274C";
3471
+ const label = status === "completed" ? "completed" : "failed";
3472
+ const truncatedOutput = output ? truncateOutputForNotification(output) : output;
3064
3473
  const outputBlock = truncatedOutput ? `
3065
3474
  \`\`\`${truncatedOutput}\`\`\`` : "";
3475
+ const body = `${emoji} [${workerName}] ${repoName} | Task ${label}: <${url}|#${id} ${title}>${tokenText}${outputBlock}`;
3476
+ let sessionUrlPrefix = "";
3477
+ if (cloud) {
3478
+ sessionUrlPrefix = cloud.sessionId?.trim() ? `https://claude.ai/code/${cloud.sessionId.trim()}
3479
+ ` : "\u30BB\u30C3\u30B7\u30E7\u30F3URL\u4E0D\u660E\uFF08ID\u62BD\u51FA\u306B\u5931\u6557\uFF09\n";
3480
+ }
3481
+ return `${sessionUrlPrefix}${body}`;
3482
+ }
3483
+ async function notifyTaskCompleted(workerName, repoName, id, title, url, output, cloud) {
3484
+ const tokenText = await buildTokenLimitText();
3066
3485
  await send({
3067
- text: `\u2705 [${workerName}] ${repoName} | Task completed: <${url}|#${id} ${title}>${tokenText}${outputBlock}`
3486
+ text: buildTaskNotificationText({
3487
+ status: "completed",
3488
+ workerName,
3489
+ repoName,
3490
+ id,
3491
+ title,
3492
+ url,
3493
+ tokenText,
3494
+ output,
3495
+ cloud
3496
+ })
3068
3497
  });
3069
3498
  }
3070
- async function notifyTaskFailed(workerName, repoName, id, title, url, output) {
3499
+ async function notifyTaskFailed(workerName, repoName, id, title, url, output, cloud) {
3071
3500
  const tokenText = await buildTokenLimitText();
3072
- const truncatedOutput = output && output.length > 1e3 ? `\u2026${output.slice(-1e3)}` : output;
3073
- const outputBlock = truncatedOutput ? `
3074
- \`\`\`${truncatedOutput}\`\`\`` : "";
3075
3501
  await send({
3076
- text: `\u274C [${workerName}] ${repoName} | Task failed: <${url}|#${id} ${title}>${tokenText}${outputBlock}`
3502
+ text: buildTaskNotificationText({
3503
+ status: "failed",
3504
+ workerName,
3505
+ repoName,
3506
+ id,
3507
+ title,
3508
+ url,
3509
+ tokenText,
3510
+ output,
3511
+ cloud
3512
+ })
3077
3513
  });
3078
3514
  }
3079
3515
  async function notifyError(workerName, repoName, error) {
@@ -3292,6 +3728,7 @@ function createIssuePollingWorker(config) {
3292
3728
  }
3293
3729
  await addLabel("issue", issue.number, "cc-in-progress");
3294
3730
  const worktreeId = generateWorktreeName();
3731
+ const cloud = isCloudWorker(config.name);
3295
3732
  try {
3296
3733
  const issueUrl = `https://github.com/${owner}/${name}/issues/${issue.number}`;
3297
3734
  syncDefaultBranch(defaultBranch);
@@ -3299,6 +3736,11 @@ function createIssuePollingWorker(config) {
3299
3736
  const command = skill || config.command;
3300
3737
  const parentNumber = issue.parent?.number;
3301
3738
  const mode = getRunMode();
3739
+ let baseBranch = defaultBranch;
3740
+ if (parentNumber !== void 0) {
3741
+ baseBranch = `cc-epic-${parentNumber}`;
3742
+ await ensureEpicBranch(baseBranch, defaultBranch);
3743
+ }
3302
3744
  const execution = buildClaudeExecution({
3303
3745
  mode,
3304
3746
  prompt: `${command} ${issue.number}`,
@@ -3306,24 +3748,33 @@ function createIssuePollingWorker(config) {
3306
3748
  effort,
3307
3749
  // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3308
3750
  advisorModel: isAdvisorEnabled() ? advisorModel : "",
3309
- permissionMode: getPermissionMode()
3751
+ permissionMode: getPermissionMode(),
3752
+ ...cloud ? { cloud: true, baseRef: baseBranch } : {}
3310
3753
  });
3311
- let baseBranch = defaultBranch;
3312
- if (parentNumber !== void 0) {
3313
- baseBranch = `cc-epic-${parentNumber}`;
3314
- await ensureEpicBranch(baseBranch, defaultBranch);
3754
+ let cwd;
3755
+ if (cloud) {
3756
+ console.log(
3757
+ `[${config.name}] #${issue.number}: cloud execution, running without worktree on ${baseBranch}`
3758
+ );
3759
+ } else {
3760
+ await createWorktreeFromBranch(worktreeId, baseBranch);
3761
+ cwd = getWorktreePath(worktreeId);
3762
+ console.log(`[${config.name}] #${issue.number}: created worktree ${worktreeId} from ${baseBranch}`);
3315
3763
  }
3316
- await createWorktreeFromBranch(worktreeId, baseBranch);
3317
- const cwd = getWorktreePath(worktreeId);
3318
- console.log(`[${config.name}] #${issue.number}: created worktree ${worktreeId} from ${baseBranch}`);
3764
+ if (cloud) {
3765
+ await removeLabel("issue", issue.number, CLOUD_DONE_LABEL).catch(
3766
+ (err) => console.error(`[${config.name}] removeLabel ${CLOUD_DONE_LABEL} failed for #${issue.number}: ${err}`)
3767
+ );
3768
+ }
3769
+ const startedAt = Date.now();
3319
3770
  run(
3320
3771
  execution.command,
3321
3772
  execution.args,
3322
3773
  issue.number,
3323
3774
  issue.title,
3324
3775
  config.name,
3325
- worktreeId,
3326
- async (status, output) => {
3776
+ cloud ? void 0 : worktreeId,
3777
+ async (status, output, cloudSessionId) => {
3327
3778
  lastCompletionAt = Date.now();
3328
3779
  for (const label of consumableTriggerLabels(config.triggerLabels)) {
3329
3780
  await removeLabel("issue", issue.number, label).catch(
@@ -3332,39 +3783,82 @@ function createIssuePollingWorker(config) {
3332
3783
  }
3333
3784
  try {
3334
3785
  if (status === "completed") {
3335
- const verified = await config.onCompleted?.(issue.number, worktreeId, output) ?? true;
3786
+ const verified = await config.onCompleted?.(issue.number, worktreeId, output, {
3787
+ cloud,
3788
+ baseBranch,
3789
+ startedAt
3790
+ }) ?? true;
3336
3791
  if (verified === false) {
3337
- await notifyTaskFailed(config.name, name, issue.number, issue.title, issueUrl, output);
3792
+ await notifyTaskFailed(
3793
+ config.name,
3794
+ name,
3795
+ issue.number,
3796
+ issue.title,
3797
+ issueUrl,
3798
+ output,
3799
+ cloud ? { sessionId: cloudSessionId } : void 0
3800
+ );
3338
3801
  } else {
3339
- await notifyTaskCompleted(config.name, name, issue.number, issue.title, issueUrl, output);
3802
+ await notifyTaskCompleted(
3803
+ config.name,
3804
+ name,
3805
+ issue.number,
3806
+ issue.title,
3807
+ issueUrl,
3808
+ output,
3809
+ cloud ? { sessionId: cloudSessionId } : void 0
3810
+ );
3340
3811
  }
3341
3812
  } else {
3342
- await notifyTaskFailed(config.name, name, issue.number, issue.title, issueUrl, output);
3813
+ await notifyTaskFailed(
3814
+ config.name,
3815
+ name,
3816
+ issue.number,
3817
+ issue.title,
3818
+ issueUrl,
3819
+ output,
3820
+ cloud ? { sessionId: cloudSessionId } : void 0
3821
+ );
3343
3822
  }
3344
3823
  } catch (err) {
3345
3824
  console.error(`[${config.name}] post-task error for #${issue.number}: ${err}`);
3346
- await notifyTaskFailed(config.name, name, issue.number, issue.title, issueUrl, output).catch(
3825
+ await notifyTaskFailed(
3826
+ config.name,
3827
+ name,
3828
+ issue.number,
3829
+ issue.title,
3830
+ issueUrl,
3831
+ output,
3832
+ cloud ? { sessionId: cloudSessionId } : void 0
3833
+ ).catch(
3347
3834
  (notifyErr) => console.error(`[${config.name}] notifyTaskFailed failed for #${issue.number}: ${notifyErr}`)
3348
3835
  );
3349
3836
  } finally {
3350
3837
  await removeLabel("issue", issue.number, "cc-in-progress").catch(
3351
3838
  (err) => console.error(`[${config.name}] removeLabel cc-in-progress failed for #${issue.number}: ${err}`)
3352
3839
  );
3353
- await removeWorktree(worktreeId).catch(
3354
- (err) => console.error(`[${config.name}] removeWorktree failed for #${issue.number}: ${err}`)
3355
- );
3840
+ if (!cloud) {
3841
+ await removeWorktree(worktreeId).catch(
3842
+ (err) => console.error(`[${config.name}] removeWorktree failed for #${issue.number}: ${err}`)
3843
+ );
3844
+ }
3356
3845
  }
3357
3846
  },
3358
3847
  cwd,
3359
- buildClaudeEnv(mode),
3360
- execution.prompt
3848
+ buildClaudeEnv(mode, cloud),
3849
+ execution.prompt,
3850
+ cloud,
3851
+ cloud ? "issue" : void 0,
3852
+ model
3361
3853
  );
3362
3854
  } catch (err) {
3363
3855
  console.error(`[${config.name}] setup error for #${issue.number}: ${err}`);
3364
3856
  await removeLabel("issue", issue.number, "cc-in-progress").catch(() => {
3365
3857
  });
3366
- await removeWorktree(worktreeId).catch(() => {
3367
- });
3858
+ if (!cloud) {
3859
+ await removeWorktree(worktreeId).catch(() => {
3860
+ });
3861
+ }
3368
3862
  await notifyError(config.name, name, err);
3369
3863
  }
3370
3864
  }
@@ -3387,51 +3881,84 @@ function formatSessionReport(output) {
3387
3881
  ${trimmed.slice(-REPORT_TAIL_LIMIT)}` : trimmed;
3388
3882
  return ["```", tail, "```"].join("\n");
3389
3883
  }
3390
- function prMissingComment(worktreeId, output) {
3884
+ function selectOwnedClosingPr(candidates, ctx) {
3885
+ const open = candidates.filter((c) => c.state === "MERGED" || c.state === "OPEN");
3886
+ if (!ctx.cloud) {
3887
+ const found2 = open.find((c) => c.headRefName === ctx.expectedHeadRefName);
3888
+ return found2 ? found2.number : null;
3889
+ }
3890
+ const found = open.find((c) => {
3891
+ if (c.baseRefName !== ctx.baseBranch) return false;
3892
+ const createdAt = Date.parse(c.createdAt);
3893
+ return !Number.isNaN(createdAt) && createdAt >= ctx.startedAt && createdAt <= ctx.now;
3894
+ });
3895
+ return found ? found.number : null;
3896
+ }
3897
+ function prMissingComment(worktreeId, output, cloud) {
3898
+ const stateSection = cloud ? [
3899
+ "## \u72B6\u614B\u306E\u78BA\u8A8D",
3900
+ "- \u30AF\u30E9\u30A6\u30C9\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u4F5C\u696D\u30D6\u30E9\u30F3\u30C1\u540D\u3092\u81EA\u8EAB\u3067\u6C7A\u3081\u308B\u305F\u3081\u3001\u30ED\u30FC\u30AB\u30EB\u304B\u3089\u306F\u540D\u524D\u304C\u5206\u304B\u308A\u307E\u305B\u3093\u3002claude.ai \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u753B\u9762\u3067\u4F5C\u696D\u30D6\u30E9\u30F3\u30C1\u3068 push \u72B6\u6CC1\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044"
3901
+ ] : [
3902
+ "## \u72B6\u614B\u306E\u78BA\u8A8D",
3903
+ `- \u5909\u66F4\u304C push \u6E08\u307F\u306E\u5834\u5408\u306F\u30EA\u30E2\u30FC\u30C8\u30D6\u30E9\u30F3\u30C1 \`${worktreeId}\` \u304C\u6B8B\u3063\u3066\u3044\u307E\u3059\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089\u624B\u52D5\u3067PR\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044`
3904
+ ];
3391
3905
  return [
3392
3906
  "## PR\u672A\u4F5C\u6210\u306E\u307E\u307E\u81EA\u52D5\u5B9F\u884C\u304C\u7D42\u4E86\u3057\u307E\u3057\u305F\uFF08\u8981\u4EBA\u624B\u78BA\u8A8D\uFF09",
3393
- `exec-issue \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6B63\u5E38\u7D42\u4E86\uFF08exit 0\uFF09\u3057\u307E\u3057\u305F\u304C\u3001\u3053\u306E\u5B9F\u884C\u306E\u4F5C\u696D\u30D6\u30E9\u30F3\u30C1\uFF08\`${worktreeId}\`\uFF09\u3092 head \u3068\u3059\u308BPR\u3082\u3001\u672CIssue\u3092 closing \u53C2\u7167\u3059\u308BPR\u3082\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002PR\u4F5C\u6210\u524D\u306B\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u7D42\u4E86\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`,
3907
+ cloud ? "exec-issue \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6B63\u5E38\u7D42\u4E86\uFF08exit 0\uFF09\u3057\u307E\u3057\u305F\u304C\u3001\u672CIssue\u3092 closing \u53C2\u7167\u3059\u308BPR\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002PR\u4F5C\u6210\u524D\u306B\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u7D42\u4E86\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002" : `exec-issue \u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u6B63\u5E38\u7D42\u4E86\uFF08exit 0\uFF09\u3057\u307E\u3057\u305F\u304C\u3001\u3053\u306E\u5B9F\u884C\u306E\u4F5C\u696D\u30D6\u30E9\u30F3\u30C1\uFF08\`${worktreeId}\`\uFF09\u3092 head \u3068\u3059\u308BPR\u3082\u3001\u672CIssue\u3092 closing \u53C2\u7167\u3059\u308BPR\u3082\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002PR\u4F5C\u6210\u524D\u306B\u30BB\u30C3\u30B7\u30E7\u30F3\u304C\u7D42\u4E86\u3057\u305F\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`,
3394
3908
  "",
3395
3909
  "## PR\u3092\u4F5C\u6210\u3057\u306A\u304B\u3063\u305F\u7406\u7531\uFF08\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u6700\u7D42\u5831\u544A\uFF09",
3396
3910
  formatSessionReport(output),
3397
3911
  "",
3398
- "## \u72B6\u614B\u306E\u78BA\u8A8D",
3399
- `- \u5909\u66F4\u304C push \u6E08\u307F\u306E\u5834\u5408\u306F\u30EA\u30E2\u30FC\u30C8\u30D6\u30E9\u30F3\u30C1 \`${worktreeId}\` \u304C\u6B8B\u3063\u3066\u3044\u307E\u3059\u3002\u5185\u5BB9\u3092\u78BA\u8A8D\u3057\u3001\u5FC5\u8981\u306A\u3089\u624B\u52D5\u3067PR\u3092\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044`,
3912
+ ...stateSection,
3400
3913
  "",
3401
3914
  "## \u5BFE\u5FDC\u5F8C\u306E\u9032\u3081\u65B9",
3402
3915
  "- \u81EA\u52D5\u5B9F\u884C\u3092\u3084\u308A\u76F4\u3059\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3001`cc-exec-issue` \u30E9\u30D9\u30EB\u3092\u4ED8\u3051\u76F4\u3057\u3066\u304F\u3060\u3055\u3044",
3403
3916
  "- \u624B\u52D5\u3067PR\u3092\u4F5C\u6210\u3057\u305F\u5834\u5408\u306A\u3069\u5BFE\u5FDC\u6E08\u307F\u306E\u5834\u5408: `cc-need-human-check` \u30E9\u30D9\u30EB\u3092\u5916\u3057\u3066\u304F\u3060\u3055\u3044"
3404
3917
  ].join("\n");
3405
3918
  }
3919
+ async function verifyPrCreated(issueNumber, worktreeId, output, ctx) {
3920
+ if (await hasLabel("issue", issueNumber, "cc-need-human-check")) {
3921
+ console.log(`[exec-issue] #${issueNumber}: cc-need-human-check present, skip cc-pr-created`);
3922
+ return false;
3923
+ }
3924
+ if (await getIssueState(issueNumber) === "CLOSED") {
3925
+ console.log(`[exec-issue] #${issueNumber}: issue closed by skill (no-change path), skip cc-pr-created`);
3926
+ return;
3927
+ }
3928
+ let prNumber = null;
3929
+ if (!ctx.cloud) {
3930
+ prNumber = await findPrNumberByHeadRef(worktreeId, "all");
3931
+ }
3932
+ if (prNumber === null) {
3933
+ const candidates = await listPrsClosingIssue(issueNumber);
3934
+ prNumber = selectOwnedClosingPr(candidates, {
3935
+ cloud: ctx.cloud,
3936
+ expectedHeadRefName: worktreeId,
3937
+ baseBranch: ctx.baseBranch,
3938
+ startedAt: ctx.startedAt,
3939
+ now: Date.now()
3940
+ });
3941
+ }
3942
+ if (prNumber !== null) {
3943
+ await addLabel("issue", issueNumber, "cc-pr-created");
3944
+ return;
3945
+ }
3946
+ console.error(
3947
+ `[exec-issue] #${issueNumber}: session exited without a PR (branch: ${worktreeId}); marking cc-need-human-check`
3948
+ );
3949
+ await addLabel("issue", issueNumber, "cc-need-human-check");
3950
+ await commentOnIssue(issueNumber, prMissingComment(worktreeId, output, ctx.cloud)).catch(
3951
+ (err) => console.error(`[exec-issue] commentOnIssue failed for #${issueNumber}: ${err}`)
3952
+ );
3953
+ return false;
3954
+ }
3406
3955
  var execIssueWorker = (opts = {}) => createIssuePollingWorker({
3407
3956
  name: "exec-issue",
3408
3957
  command: "/claude-task-worker:exec-issue",
3409
3958
  triggerLabels: ["cc-exec-issue"],
3410
3959
  epicFilters: opts.epicFilters,
3411
3960
  labelFilters: opts.labelFilters,
3412
- onCompleted: async (issueNumber, worktreeId, output) => {
3413
- if (await hasLabel("issue", issueNumber, "cc-need-human-check")) {
3414
- console.log(`[exec-issue] #${issueNumber}: cc-need-human-check present, skip cc-pr-created`);
3415
- return false;
3416
- }
3417
- if (await getIssueState(issueNumber) === "CLOSED") {
3418
- console.log(`[exec-issue] #${issueNumber}: issue closed by skill (no-change path), skip cc-pr-created`);
3419
- return;
3420
- }
3421
- const prNumber = await findPrNumberByHeadRef(worktreeId, "all") ?? await findPrNumberClosingIssue(issueNumber, worktreeId);
3422
- if (prNumber !== null) {
3423
- await addLabel("issue", issueNumber, "cc-pr-created");
3424
- return;
3425
- }
3426
- console.error(
3427
- `[exec-issue] #${issueNumber}: session exited without a PR (branch: ${worktreeId}); marking cc-need-human-check`
3428
- );
3429
- await addLabel("issue", issueNumber, "cc-need-human-check");
3430
- await commentOnIssue(issueNumber, prMissingComment(worktreeId, output)).catch(
3431
- (err) => console.error(`[exec-issue] commentOnIssue failed for #${issueNumber}: ${err}`)
3432
- );
3433
- return false;
3434
- }
3961
+ onCompleted: verifyPrCreated
3435
3962
  })();
3436
3963
 
3437
3964
  // src/workers/pr-worker.ts
@@ -3460,21 +3987,24 @@ function createPrPollingWorker(config) {
3460
3987
  const prUrl = `https://github.com/${owner}/${name}/pull/${pr.number}`;
3461
3988
  const hadTriageScope = pr.labels.some((l) => l.name === LABEL_TRIAGE_SCOPE);
3462
3989
  await addLabel("pr", pr.number, LABEL_IN_PROGRESS);
3463
- const worktreeId = generateWorktreeName();
3990
+ const isCloud = isCloudWorker(config.name);
3991
+ const worktreeId = isCloud ? void 0 : generateWorktreeName();
3464
3992
  try {
3465
- await removeWorktreeByBranch(pr.headRefName);
3466
- await deleteLocalBranch(pr.headRefName);
3467
- if (await localBranchExists(pr.headRefName)) {
3468
- console.error(
3469
- `[${config.name}] PR #${pr.number}: branch ${pr.headRefName} is still checked out by another worktree; skipping this tick`
3470
- );
3471
- await removeLabel("pr", pr.number, LABEL_IN_PROGRESS).catch(() => {
3472
- });
3473
- continue;
3993
+ if (worktreeId) {
3994
+ await removeWorktreeByBranch(pr.headRefName);
3995
+ await deleteLocalBranch(pr.headRefName);
3996
+ if (await localBranchExists(pr.headRefName)) {
3997
+ console.error(
3998
+ `[${config.name}] PR #${pr.number}: branch ${pr.headRefName} is still checked out by another worktree; skipping this tick`
3999
+ );
4000
+ await removeLabel("pr", pr.number, LABEL_IN_PROGRESS).catch(() => {
4001
+ });
4002
+ continue;
4003
+ }
4004
+ syncDefaultBranch(defaultBranch);
4005
+ await createWorktreeFromBranch(worktreeId, defaultBranch);
3474
4006
  }
3475
- syncDefaultBranch(defaultBranch);
3476
- await createWorktreeFromBranch(worktreeId, defaultBranch);
3477
- const cwd = getWorktreePath(worktreeId);
4007
+ const cwd = worktreeId ? getWorktreePath(worktreeId) : void 0;
3478
4008
  const { model, effort, skill, advisorModel } = getWorkerConfig(config.name);
3479
4009
  const command = skill || config.command;
3480
4010
  const mode = getRunMode();
@@ -3485,8 +4015,15 @@ function createPrPollingWorker(config) {
3485
4015
  effort,
3486
4016
  // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3487
4017
  advisorModel: isAdvisorEnabled() ? advisorModel : "",
3488
- permissionMode: getPermissionMode()
4018
+ permissionMode: getPermissionMode(),
4019
+ cloud: isCloud,
4020
+ onBranch: isCloud ? pr.headRefName : void 0
3489
4021
  });
4022
+ if (isCloud) {
4023
+ await removeLabel("pr", pr.number, CLOUD_DONE_LABEL).catch(
4024
+ (err) => console.error(`[${config.name}] removeLabel ${CLOUD_DONE_LABEL} failed for PR #${pr.number}: ${err}`)
4025
+ );
4026
+ }
3490
4027
  run(
3491
4028
  execution.command,
3492
4029
  execution.args,
@@ -3494,14 +4031,30 @@ function createPrPollingWorker(config) {
3494
4031
  `PR #${pr.number} (${pr.headRefName})`,
3495
4032
  config.name,
3496
4033
  worktreeId,
3497
- async (status, output) => {
4034
+ async (status, output, cloudSessionId) => {
3498
4035
  lastCompletionAt = Date.now();
3499
4036
  try {
3500
4037
  if (status === "completed") {
3501
4038
  await config.onCompleted?.(pr, output);
3502
- await notifyTaskCompleted(config.name, name, pr.number, pr.title, prUrl, output);
4039
+ await notifyTaskCompleted(
4040
+ config.name,
4041
+ name,
4042
+ pr.number,
4043
+ pr.title,
4044
+ prUrl,
4045
+ output,
4046
+ isCloud ? { sessionId: cloudSessionId } : void 0
4047
+ );
3503
4048
  } else {
3504
- await notifyTaskFailed(config.name, name, pr.number, pr.title, prUrl, output);
4049
+ await notifyTaskFailed(
4050
+ config.name,
4051
+ name,
4052
+ pr.number,
4053
+ pr.title,
4054
+ prUrl,
4055
+ output,
4056
+ isCloud ? { sessionId: cloudSessionId } : void 0
4057
+ );
3505
4058
  }
3506
4059
  } catch (err) {
3507
4060
  console.error(`[${config.name}] post-task error for PR #${pr.number}: ${err}`);
@@ -3528,21 +4081,28 @@ function createPrPollingWorker(config) {
3528
4081
  `[${config.name}] removeLabel ${LABEL_IN_PROGRESS} failed for PR #${pr.number}: ${err}`
3529
4082
  )
3530
4083
  );
3531
- await removeWorktree(worktreeId).catch(
3532
- (err) => console.error(`[${config.name}] removeWorktree failed for PR #${pr.number}: ${err}`)
3533
- );
4084
+ if (worktreeId) {
4085
+ await removeWorktree(worktreeId).catch(
4086
+ (err) => console.error(`[${config.name}] removeWorktree failed for PR #${pr.number}: ${err}`)
4087
+ );
4088
+ }
3534
4089
  }
3535
4090
  },
3536
4091
  cwd,
3537
- buildClaudeEnv(mode),
3538
- execution.prompt
4092
+ buildClaudeEnv(mode, isCloud),
4093
+ execution.prompt,
4094
+ isCloud,
4095
+ isCloud ? "pr" : void 0,
4096
+ model
3539
4097
  );
3540
4098
  } catch (err) {
3541
4099
  console.error(`[${config.name}] setup error for PR #${pr.number}: ${err}`);
3542
4100
  await removeLabel("pr", pr.number, LABEL_IN_PROGRESS).catch(() => {
3543
4101
  });
3544
- await removeWorktree(worktreeId).catch(() => {
3545
- });
4102
+ if (worktreeId) {
4103
+ await removeWorktree(worktreeId).catch(() => {
4104
+ });
4105
+ }
3546
4106
  await notifyError(config.name, name, err);
3547
4107
  }
3548
4108
  }
@@ -4049,9 +4609,11 @@ function createScheduledWorker(config) {
4049
4609
  if (lastRunAt > 0 && now - lastRunAt < SCHEDULE_INTERVAL_MS) return;
4050
4610
  const worktreeId = generateWorktreeName();
4051
4611
  startedAt = now;
4612
+ let cloud = false;
4052
4613
  try {
4053
4614
  syncDefaultBranch(defaultBranch);
4054
4615
  const { model, effort, skill, advisorModel } = getWorkerConfig(config.name);
4616
+ cloud = isCloudWorker(config.name);
4055
4617
  const command = skill || config.command;
4056
4618
  const mode = getRunMode();
4057
4619
  const execution = buildClaudeExecution({
@@ -4060,14 +4622,20 @@ function createScheduledWorker(config) {
4060
4622
  model,
4061
4623
  effort,
4062
4624
  advisorModel: isAdvisorEnabled() ? advisorModel : "",
4063
- permissionMode: getPermissionMode()
4625
+ permissionMode: getPermissionMode(),
4626
+ ...cloud ? { cloud: true, baseRef: defaultBranch } : {}
4064
4627
  });
4065
4628
  await publishLastRunPr(config.name, defaultBranch, new Date(now)).catch(
4066
4629
  (err) => console.error(`[${config.name}] publishLastRunPr failed: ${err}`)
4067
4630
  );
4068
- await createWorktreeFromBranch(worktreeId, defaultBranch);
4069
- const cwd = getWorktreePath(worktreeId);
4070
- console.log(`[${config.name}] created worktree ${worktreeId} from ${defaultBranch}`);
4631
+ let cwd;
4632
+ if (cloud) {
4633
+ console.log(`[${config.name}] cloud execution, running without worktree on ${defaultBranch}`);
4634
+ } else {
4635
+ await createWorktreeFromBranch(worktreeId, defaultBranch);
4636
+ cwd = getWorktreePath(worktreeId);
4637
+ console.log(`[${config.name}] created worktree ${worktreeId} from ${defaultBranch}`);
4638
+ }
4071
4639
  const repoUrl = `https://github.com/${owner}/${repoName}`;
4072
4640
  run(
4073
4641
  execution.command,
@@ -4075,7 +4643,7 @@ function createScheduledWorker(config) {
4075
4643
  config.taskId,
4076
4644
  config.name,
4077
4645
  config.name,
4078
- worktreeId,
4646
+ cloud ? void 0 : worktreeId,
4079
4647
  async (status, output) => {
4080
4648
  try {
4081
4649
  if (status === "completed") {
@@ -4086,19 +4654,26 @@ function createScheduledWorker(config) {
4086
4654
  } catch (err) {
4087
4655
  console.error(`[${config.name}] post-task error: ${err}`);
4088
4656
  } finally {
4089
- await removeWorktree(worktreeId).catch(
4090
- (err) => console.error(`[${config.name}] removeWorktree failed: ${err}`)
4091
- );
4657
+ if (!cloud) {
4658
+ await removeWorktree(worktreeId).catch(
4659
+ (err) => console.error(`[${config.name}] removeWorktree failed: ${err}`)
4660
+ );
4661
+ }
4092
4662
  }
4093
4663
  },
4094
4664
  cwd,
4095
- buildClaudeEnv(mode),
4096
- execution.prompt
4665
+ buildClaudeEnv(mode, cloud),
4666
+ execution.prompt,
4667
+ cloud,
4668
+ void 0,
4669
+ model
4097
4670
  );
4098
4671
  } catch (err) {
4099
4672
  console.error(`[${config.name}] setup error: ${err}`);
4100
- await removeWorktree(worktreeId).catch(() => {
4101
- });
4673
+ if (!cloud) {
4674
+ await removeWorktree(worktreeId).catch(() => {
4675
+ });
4676
+ }
4102
4677
  await notifyError(config.name, repoName, err);
4103
4678
  }
4104
4679
  };
@@ -4256,8 +4831,10 @@ var LABELS = [
4256
4831
  // vivid amber (H44 S100 L57 / L*83 C*79)
4257
4832
  { name: "cc-ui-design-pr-created", color: "947100" },
4258
4833
  // vivid bronze (H46 S100 L29 / L*50 C*56)
4259
- { name: "cc-ui-design-ready", color: "0c73e9" }
4834
+ { name: "cc-ui-design-ready", color: "0c73e9" },
4260
4835
  // vivid azure (H212 S90 L48 / L*50 C*69)
4836
+ { name: CLOUD_DONE_LABEL, color: "33cfff" }
4837
+ // vivid sky blue (H194 S100 L60 / L*78 C*42)
4261
4838
  ];
4262
4839
  var ISSUE_TEMPLATE = `name: "[claude-task-worker] Issue\u4F5C\u6210\u4F9D\u983C"
4263
4840
  description: claude-task-worker\u3067GitHub Issue\u3092\u4F5C\u6210\u3059\u308B
@@ -4604,50 +5181,10 @@ function version() {
4604
5181
  }
4605
5182
  }
4606
5183
 
4607
- // src/dispatch-args.ts
4608
- var PROJECT_INCOMPATIBLE_COMMANDS = ["init", "install", "update", "usage", "version"];
4609
- function collectFlagValues(argv, flag) {
4610
- const values = [];
4611
- for (let i = 0; i < argv.length; i++) {
4612
- if (argv[i] !== flag) continue;
4613
- const raw = argv[i + 1];
4614
- if (!raw || raw.startsWith("--")) {
4615
- console.error(`[dispatcher] ${flag} requires a value`);
4616
- process.exit(1);
4617
- }
4618
- values.push(raw);
4619
- }
4620
- return values;
4621
- }
4622
- function parseProjectFilters() {
4623
- return collectFlagValues(process.argv, "--project");
4624
- }
4625
- function hasProjectFilter() {
4626
- return process.argv.includes("--project");
4627
- }
4628
- function assertProjectCompatibleCommand(command) {
4629
- if (PROJECT_INCOMPATIBLE_COMMANDS.includes(command)) {
4630
- console.error(`[dispatcher] --project cannot be used with the "${command}" command`);
4631
- process.exit(1);
4632
- }
4633
- }
4634
- function shellQuote(value) {
4635
- if (value === "") return "''";
4636
- return `'${value.replace(/'/g, "'\\''")}'`;
4637
- }
4638
- function buildForwardedCommand(argv) {
4639
- const tokens = [];
4640
- for (let i = 0; i < argv.length; i++) {
4641
- if (argv[i] === "--project") {
4642
- i++;
4643
- continue;
4644
- }
4645
- tokens.push(argv[i]);
4646
- }
4647
- return ["claude-task-worker", ...tokens.map(shellQuote)].join(" ");
4648
- }
4649
-
4650
5184
  // src/index.ts
5185
+ import { execFile as execFile4 } from "node:child_process";
5186
+ import { promisify as promisify5 } from "node:util";
5187
+ var execFileAsync4 = promisify5(execFile4);
4651
5188
  var WORKERS = {
4652
5189
  "exec-issue": execIssueWorker,
4653
5190
  "fix-review-point": fixReviewPointWorker,
@@ -4669,7 +5206,7 @@ function printUsage() {
4669
5206
  console.log(`Usage: claude-task-worker <command> [--project <name>] [--epic <issue-number>] [--label <label-name>]
4670
5207
 
4671
5208
  Commands:
4672
- init [--force] Create required GitHub labels and config file (use --force to overwrite existing files)
5209
+ init [--force] Create required GitHub labels and config file (use --force to overwrite existing files)
4673
5210
  install Add the claude-task-worker marketplace, install the plugin, and install/update the CLI
4674
5211
  update Update the claude-task-worker plugin/marketplace and the CLI itself
4675
5212
  usage Notify current usage to Slack
@@ -4730,6 +5267,9 @@ if (workerType !== "all" && workerType !== "yolo" && workerType !== "init" && wo
4730
5267
  if (hasProjectFilter()) {
4731
5268
  assertProjectCompatibleCommand(workerType);
4732
5269
  }
5270
+ if (hasCloudFlag()) {
5271
+ assertCloudCompatibleCommand(workerType);
5272
+ }
4733
5273
  function collectFlagValues2(flag) {
4734
5274
  const values = [];
4735
5275
  for (let i = 0; i < process.argv.length; i++) {
@@ -4773,10 +5313,60 @@ async function assertRunModeAvailable() {
4773
5313
  }
4774
5314
  console.log("[worker] run mode: herdr (each task runs as a TUI session in its own herdr tab)");
4775
5315
  }
5316
+ async function readCloudAuthStatus() {
5317
+ let stdout;
5318
+ try {
5319
+ const result = await execFileAsync4("claude", ["auth", "status", "--json"]);
5320
+ stdout = result.stdout;
5321
+ } catch (err) {
5322
+ const maybeStdout = err.stdout;
5323
+ if (!maybeStdout) return { kind: "unknown" };
5324
+ stdout = maybeStdout;
5325
+ }
5326
+ try {
5327
+ const parsed = JSON.parse(stdout);
5328
+ return {
5329
+ kind: "ok",
5330
+ loggedIn: parsed.loggedIn === true,
5331
+ authMethod: String(parsed.authMethod ?? ""),
5332
+ apiProvider: String(parsed.apiProvider ?? ""),
5333
+ apiKeySource: typeof parsed.apiKeySource === "string" ? parsed.apiKeySource : void 0
5334
+ };
5335
+ } catch {
5336
+ return { kind: "unknown" };
5337
+ }
5338
+ }
5339
+ async function assertCloudAvailable() {
5340
+ const cloud = hasCloudFlag();
5341
+ const labelReady = cloud ? await createLabel(CLOUD_DONE_LABEL, "33cfff", true) : true;
5342
+ const status = cloud ? await readCloudAuthStatus() : void 0;
5343
+ const errors = checkCloudConfig({
5344
+ cloud,
5345
+ mode: getRunMode(),
5346
+ auth: status ? { status, baseUrl: process.env.ANTHROPIC_BASE_URL } : void 0
5347
+ });
5348
+ if (!labelReady) {
5349
+ errors.push(
5350
+ `${CLOUD_DONE_LABEL} \u30E9\u30D9\u30EB\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002gh \u306E\u8A8D\u8A3C\u30FB\u6A29\u9650\u3092\u78BA\u8A8D\u3059\u308B\u304B\u3001claude-task-worker init \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002`
5351
+ );
5352
+ }
5353
+ if (errors.length > 0) {
5354
+ for (const message of errors) {
5355
+ console.error(`[worker] ${message}`);
5356
+ }
5357
+ process.exit(1);
5358
+ }
5359
+ if (cloud) {
5360
+ console.log(
5361
+ `[worker] cloud execution enabled (--cloud); these workers stay local: ${CLOUD_DENIED_WORKERS.join(", ")}`
5362
+ );
5363
+ }
5364
+ }
4776
5365
  async function assertRunPrerequisites() {
4777
5366
  captureConsole();
4778
5367
  ensureRenderInterval();
4779
5368
  await assertRunModeAvailable();
5369
+ await assertCloudAvailable();
4780
5370
  }
4781
5371
  if (!hasProjectFilter()) {
4782
5372
  process.on("SIGTERM", async () => {
@@ -4843,7 +5433,8 @@ if (hasProjectFilter()) {
4843
5433
  }
4844
5434
  })();
4845
5435
  } else if (workerType === "init") {
4846
- const force = process.argv.slice(3).includes("--force");
5436
+ const initArgs = process.argv.slice(3);
5437
+ const force = initArgs.includes("--force");
4847
5438
  init({ force });
4848
5439
  } else if (workerType === "install") {
4849
5440
  (async () => {