claude-task-worker 0.28.0 → 0.29.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 +10 -1
  2. package/dist/index.js +40 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -312,7 +312,7 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
312
312
 
313
313
  `mode: "herdr"` のときの1タスクの流れ:
314
314
 
315
- 1. worktree を作成し、`ctw:<プロジェクト名>:#<Issue/PR番号>` ラベルのタブで claude を TUI 起動する(`HERDR_DISABLE_SOUND=1` を設定するため通知音は鳴らない)
315
+ 1. worktree を作成し、`ctw:<プロジェクト名>:#<Issue/PR番号>` ラベルのタブで claude を TUI 起動する
316
316
  2. herdr が持つ agent ステータスを監視し、`working` → `idle` の遷移をタスク完了とみなす
317
317
  3. 完了したらペインの内容を回収して通知に使い、タブを閉じてラベル・worktree を後片付けする
318
318
 
@@ -321,6 +321,15 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
321
321
  - タブは `--project` で起動した場合そのプロジェクトのワークスペース内に作られる(herdrが各ペインへ注入する `HERDR_WORKSPACE_ID` を利用する)
322
322
  - `blocked`(claudeが入力待ち)になってもタスクは自動失敗にせず待機し続ける。ステータステーブルに `running:blocked` と表示されるので、herdrのタブを開いて直接対応できる
323
323
  - `mode: "herdr"` でherdrが未インストール・未起動の場合、ワーカーは起動時にエラー終了する(`"default"` へ勝手にフォールバックしない)
324
+ - **タスク完了時の通知音はワーカー側から止められない**。herdr のエージェント状態遷移音を再生するのは herdr サーバープロセスで、`HERDR_DISABLE_SOUND` もそのプロセスの環境変数として読まれるため、タスクペインへ渡しても効かない(socket API にもペイン単位のミュートは無い)。無音にしたい場合は herdr 側の設定で行う:
325
+
326
+ ```toml
327
+ # ~/.config/herdr/config.toml
328
+ [ui.sound]
329
+ enabled = false
330
+ ```
331
+
332
+ 適用は `herdr server reload-config`。この設定は herdr サーバー全体に効くため、ワーカー以外の対話セッションの完了音も鳴らなくなる(`[ui.sound.agents] claude = "off"` でも実質同じ範囲)。ワーカーだけを無音にしたい場合は、`HERDR_DISABLE_SOUND=1 herdr --session <name>` で別セッションを起動し、その中でディスパッチャーを動かす
324
333
 
325
334
  ### exec-issue
326
335
 
package/dist/index.js CHANGED
@@ -75,6 +75,7 @@ __export(herdr_exports, {
75
75
  tabRename: () => tabRename,
76
76
  workspaceClose: () => workspaceClose,
77
77
  workspaceCreate: () => workspaceCreate,
78
+ workspaceFocus: () => workspaceFocus,
78
79
  workspaceList: () => workspaceList
79
80
  });
80
81
  import { createRequire as createRequire2 } from "node:module";
@@ -210,12 +211,16 @@ async function workspaceList() {
210
211
  }
211
212
  return result.workspaces.map((workspace) => ({
212
213
  workspaceId: workspace.workspace_id,
213
- label: workspace.label ?? ""
214
+ label: workspace.label ?? "",
215
+ focused: workspace.focused === true
214
216
  }));
215
217
  }
216
218
  async function workspaceClose(workspaceId) {
217
219
  await execHerdr(["workspace", "close", workspaceId]);
218
220
  }
221
+ async function workspaceFocus(workspaceId) {
222
+ await execHerdr(["workspace", "focus", workspaceId]);
223
+ }
219
224
  async function agentStart({
220
225
  name,
221
226
  cwd,
@@ -585,6 +590,7 @@ __export(dispatcher_exports, {
585
590
  WORKER_STARTUP_POLL_INTERVAL_MS: () => WORKER_STARTUP_POLL_INTERVAL_MS,
586
591
  WORKER_STARTUP_TIMEOUT_MS: () => WORKER_STARTUP_TIMEOUT_MS,
587
592
  createDispatcherShutdownHandler: () => createDispatcherShutdownHandler,
593
+ focusedWorkspaceId: () => focusedWorkspaceId,
588
594
  formatUptime: () => formatUptime,
589
595
  isShellProcess: () => isShellProcess,
590
596
  isWorkerProcess: () => isWorkerProcess,
@@ -592,6 +598,7 @@ __export(dispatcher_exports, {
592
598
  pollOnce: () => pollOnce,
593
599
  removeSession: () => removeSession,
594
600
  renderSessionTable: () => renderSessionTable,
601
+ restoreWorkspaceFocus: () => restoreWorkspaceFocus,
595
602
  runDispatcher: () => runDispatcher,
596
603
  shutdownDispatcher: () => shutdownDispatcher,
597
604
  startWorkerInPane: () => startWorkerInPane,
@@ -608,6 +615,25 @@ async function loadTable() {
608
615
  function workspaceLabelFor(projectName) {
609
616
  return `${LABEL_PREFIX}${projectName}`;
610
617
  }
618
+ async function focusedWorkspaceId(herdr) {
619
+ try {
620
+ const workspaces = await herdr.workspaceList();
621
+ return workspaces.find((workspace) => workspace.focused)?.workspaceId;
622
+ } catch (error) {
623
+ console.error(`[dispatcher] failed to read the focused workspace: ${error}`);
624
+ return void 0;
625
+ }
626
+ }
627
+ async function restoreWorkspaceFocus(herdr, previousWorkspaceId, closingWorkspaceIds) {
628
+ if (!previousWorkspaceId || closingWorkspaceIds.has(previousWorkspaceId)) return;
629
+ const currentWorkspaceId = await focusedWorkspaceId(herdr);
630
+ if (currentWorkspaceId === previousWorkspaceId) return;
631
+ try {
632
+ await herdr.workspaceFocus(previousWorkspaceId);
633
+ } catch (error) {
634
+ console.error(`[dispatcher] failed to restore focus to workspace "${previousWorkspaceId}": ${error}`);
635
+ }
636
+ }
611
637
  function sleep2(ms) {
612
638
  return new Promise((resolve3) => setTimeout(resolve3, ms));
613
639
  }
@@ -719,23 +745,27 @@ async function runDispatcher(projects, forwardedCommand, timing) {
719
745
  console.error(`[dispatcher] failed to dispatch project "${project.name}": ${error}`);
720
746
  sessions.delete(project.name);
721
747
  if (createdWorkspaceId !== void 0) {
748
+ const focusedBefore = await focusedWorkspaceId(herdr);
722
749
  try {
723
750
  await workspaceClose2(createdWorkspaceId);
724
751
  } catch (closeError) {
725
752
  console.error(`[dispatcher] failed to close dangling workspace for project "${project.name}": ${closeError}`);
726
753
  }
754
+ await restoreWorkspaceFocus(herdr, focusedBefore, /* @__PURE__ */ new Set([createdWorkspaceId]));
727
755
  }
728
756
  }
729
757
  }
730
758
  return sessions;
731
759
  }
732
- async function removeSession(sessions, name, { closeWorkspace }) {
760
+ async function removeSession(sessions, name, { closeWorkspace }, herdrModule) {
733
761
  const session = sessions.get(name);
734
762
  if (!session) return;
735
763
  sessions.delete(name);
736
764
  if (closeWorkspace) {
737
- const { workspaceClose: workspaceClose2 } = await loadHerdr2();
738
- await workspaceClose2(session.workspaceId);
765
+ const herdr = herdrModule ?? await loadHerdr2();
766
+ const focusedBefore = await focusedWorkspaceId(herdr);
767
+ await herdr.workspaceClose(session.workspaceId);
768
+ await restoreWorkspaceFocus(herdr, focusedBefore, /* @__PURE__ */ new Set([session.workspaceId]));
739
769
  }
740
770
  }
741
771
  async function pollOnce(sessions, herdr) {
@@ -743,11 +773,11 @@ async function pollOnce(sessions, herdr) {
743
773
  try {
744
774
  const { foregroundProcesses } = await herdr.paneProcessInfo(session.paneId);
745
775
  if (!foregroundProcesses.some(isWorkerProcess)) {
746
- await removeSession(sessions, name, { closeWorkspace: true });
776
+ await removeSession(sessions, name, { closeWorkspace: true }, herdr);
747
777
  }
748
778
  } catch (error) {
749
779
  if (error instanceof herdr.HerdrError && error.code === "pane_not_found") {
750
- await removeSession(sessions, name, { closeWorkspace: false });
780
+ await removeSession(sessions, name, { closeWorkspace: false }, herdr);
751
781
  continue;
752
782
  }
753
783
  console.error(`[dispatcher] failed to poll session "${name}": ${error}`);
@@ -845,6 +875,8 @@ async function waitUntilSessionsEmpty(sessions, herdr, pollIntervalMs, timeoutMs
845
875
  return true;
846
876
  }
847
877
  async function closeRemainingWorkspaces(sessions, herdr, timeoutMs) {
878
+ const closingWorkspaceIds = new Set([...sessions.values()].map((session) => session.workspaceId));
879
+ const focusedBefore = await focusedWorkspaceId(herdr);
848
880
  const results = await Promise.all(
849
881
  [...sessions.values()].map(async (session) => {
850
882
  try {
@@ -863,6 +895,7 @@ async function closeRemainingWorkspaces(sessions, herdr, timeoutMs) {
863
895
  }
864
896
  })
865
897
  );
898
+ await restoreWorkspaceFocus(herdr, focusedBefore, closingWorkspaceIds);
866
899
  return results.every((closed) => closed);
867
900
  }
868
901
  async function forceKillAllSessions(sessions, herdr, workspaceCloseTimeoutMs) {
@@ -1273,8 +1306,7 @@ function buildClaudeArgs({ mode, prompt, model, effort }) {
1273
1306
  function buildClaudeEnv(mode) {
1274
1307
  if (mode === "herdr") {
1275
1308
  return {
1276
- CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS,
1277
- HERDR_DISABLE_SOUND: "1"
1309
+ CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS
1278
1310
  };
1279
1311
  }
1280
1312
  return { ...CLAUDE_SPAWN_ENV };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "CLI tool that polls GitHub Issues/PRs and delegates work to Claude CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",