@staff0rd/assist 0.571.1 → 0.572.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.571.1",
9
+ version: "0.572.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -16722,6 +16722,10 @@ var BUILTIN_DENIES = [
16722
16722
  pattern: "gh pr edit",
16723
16723
  message: "Do not run 'gh pr edit' directly. Use 'assist prs edit [--title <title>] [--what <what>] [--why <why>] [--how <how>]' instead \u2014 it assembles and validates the body before delegating to gh, and in an assist web session blocks on the preview pane until the user approves or rejects. Compose the sections and run it; see 'assist prs edit --help' for how to pitch each section."
16724
16724
  },
16725
+ {
16726
+ pattern: "gh issue create",
16727
+ message: "Do not run 'gh issue create' directly. Use 'assist github issue create --title <title> --body <body> [-R <owner>/<repo>]' instead \u2014 it validates the title and body before delegating to gh, and gates the issue on the user's approval itself. Run 'assist github issue create --help' and follow its guidance to compose the body; it is authoritative on how approval is handled in this environment."
16728
+ },
16725
16729
  {
16726
16730
  pattern: "git commit",
16727
16731
  message: `Do not run 'git commit' directly. Use 'assist commit "<message>"' instead.`
@@ -20625,6 +20629,72 @@ function commits(org, options2) {
20625
20629
  }
20626
20630
  }
20627
20631
 
20632
+ // src/commands/github/issue/createIssue.ts
20633
+ import { execFileSync as execFileSync6 } from "child_process";
20634
+
20635
+ // src/shared/validateProposedContent.ts
20636
+ function validateProposedContent(labels, title, body) {
20637
+ const { subject, context } = labels;
20638
+ if (title.toLowerCase().includes("claude")) {
20639
+ console.error(`Error: ${subject} title must not reference Claude`);
20640
+ process.exit(1);
20641
+ }
20642
+ if (body.toLowerCase().includes("claude")) {
20643
+ console.error(`Error: ${subject} body must not reference Claude`);
20644
+ process.exit(1);
20645
+ }
20646
+ const titleBacklogIds = findBacklogRefs(title);
20647
+ if (titleBacklogIds.length > 0) {
20648
+ console.error(
20649
+ backlogRefError(`${subject} title`, context, titleBacklogIds)
20650
+ );
20651
+ process.exit(1);
20652
+ }
20653
+ const bodyBacklogIds = findBacklogRefs(body);
20654
+ if (bodyBacklogIds.length > 0) {
20655
+ console.error(backlogRefError(`${subject} body`, context, bodyBacklogIds));
20656
+ process.exit(1);
20657
+ }
20658
+ }
20659
+
20660
+ // src/commands/github/issue/reviewProposedIssue.ts
20661
+ import { randomUUID as randomUUID7 } from "crypto";
20662
+ async function reviewProposedIssue(title, body) {
20663
+ const sessionId = process.env.ASSIST_SESSION_ID;
20664
+ if (process.env.ASSIST_SESSION !== "1" || !sessionId) return;
20665
+ await awaitPreviewApproval("GitHub issue preview", {
20666
+ sessionId,
20667
+ requestId: randomUUID7(),
20668
+ title,
20669
+ body,
20670
+ prNumber: null,
20671
+ kind: "github-issue"
20672
+ });
20673
+ }
20674
+
20675
+ // src/commands/github/issue/createIssue.ts
20676
+ var USAGE = "Usage: assist github issue create --title <title> --body <body> [-R <owner>/<repo>]";
20677
+ async function createIssue(options2) {
20678
+ if (!options2.title || !options2.body) {
20679
+ console.error(USAGE);
20680
+ process.exit(1);
20681
+ }
20682
+ const { title, body } = options2;
20683
+ validateProposedContent(
20684
+ { subject: "Issue", context: "GitHub issues" },
20685
+ title,
20686
+ body
20687
+ );
20688
+ await reviewProposedIssue(title, body);
20689
+ const args = ["issue", "create", "--title", title, "--body", body];
20690
+ if (options2.repo) args.push("--repo", options2.repo);
20691
+ try {
20692
+ execFileSync6("gh", args, { stdio: "inherit" });
20693
+ } catch {
20694
+ process.exit(1);
20695
+ }
20696
+ }
20697
+
20628
20698
  // src/commands/github/parseSinceDate.ts
20629
20699
  import { InvalidArgumentError } from "commander";
20630
20700
  function parseSinceDate(value) {
@@ -20659,6 +20729,14 @@ function registerGithub(program2) {
20659
20729
  "only report the top <n> repos by commit count",
20660
20730
  parseTopCount
20661
20731
  ).option("--json", "Output as JSON").action(commits);
20732
+ const issueCommand = githubCommand.command("issue").description("GitHub issue utilities");
20733
+ issueCommand.command("create").description("Create a GitHub issue").option("--title <title>", "Issue title").option("--body <body>", "Issue body").option(
20734
+ "-R, --repo <owner/repo>",
20735
+ "Target repository (defaults to the current repo)"
20736
+ ).addHelpText(
20737
+ "after",
20738
+ "\nThere is no What/Why/How template: an issue reports a problem, and the target repo's own issue template is unknowable from here. Write the body as the repo's maintainers would expect.\nIn an assist web session the title and body are previewed for approve/reject first (with inline comments); nothing is created until it is approved."
20739
+ ).action(createIssue);
20662
20740
  }
20663
20741
 
20664
20742
  // src/commands/handover/countPendingHandovers.ts
@@ -21068,10 +21146,10 @@ function registerRefineLaunch(program2, resumeFlag) {
21068
21146
  }
21069
21147
 
21070
21148
  // src/commands/reviewPrComments.ts
21071
- import { randomUUID as randomUUID7 } from "crypto";
21149
+ import { randomUUID as randomUUID8 } from "crypto";
21072
21150
 
21073
21151
  // src/commands/review/checkoutPr.ts
21074
- import { execFileSync as execFileSync7 } from "child_process";
21152
+ import { execFileSync as execFileSync8 } from "child_process";
21075
21153
  import chalk164 from "chalk";
21076
21154
 
21077
21155
  // src/commands/sessions/daemon/daemonLog.ts
@@ -21631,10 +21709,10 @@ async function moveToPrCheckoutTree() {
21631
21709
  }
21632
21710
 
21633
21711
  // src/commands/review/prHeadBranch.ts
21634
- import { execFileSync as execFileSync6 } from "child_process";
21712
+ import { execFileSync as execFileSync7 } from "child_process";
21635
21713
  function prHeadBranch(number) {
21636
21714
  try {
21637
- const out = execFileSync6(
21715
+ const out = execFileSync7(
21638
21716
  "gh",
21639
21717
  ["pr", "view", number, "--json", "headRefName", "-q", ".headRefName"],
21640
21718
  { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
@@ -21682,7 +21760,7 @@ async function checkoutPr(number) {
21682
21760
  if (headRef && moveToExistingCheckout(number, headRef)) return;
21683
21761
  await moveToPrCheckoutTree();
21684
21762
  try {
21685
- execFileSync7("gh", ["pr", "checkout", number], { stdio: "inherit" });
21763
+ execFileSync8("gh", ["pr", "checkout", number], { stdio: "inherit" });
21686
21764
  } catch {
21687
21765
  console.error(chalk164.red(`gh pr checkout ${number} failed; aborting.`));
21688
21766
  process.exit(1);
@@ -21704,7 +21782,7 @@ async function reviewPrComments(number, options2 = {}) {
21704
21782
  const resumeSessionId = options2.resumeSessionId;
21705
21783
  validateAnnounce(number, announce);
21706
21784
  if (number && !resumeSessionId) await checkoutPr(number);
21707
- const claudeSessionId = resumeSessionId ?? randomUUID7();
21785
+ const claudeSessionId = resumeSessionId ?? randomUUID8();
21708
21786
  emitActivity({
21709
21787
  kind: "command",
21710
21788
  name: "review-pr-comments",
@@ -21722,14 +21800,14 @@ async function reviewPrComments(number, options2 = {}) {
21722
21800
  }
21723
21801
 
21724
21802
  // src/commands/fixConflict.ts
21725
- import { randomUUID as randomUUID8 } from "crypto";
21803
+ import { randomUUID as randomUUID9 } from "crypto";
21726
21804
  function buildPrompt3(rebase) {
21727
21805
  return rebase ? "/fix-conflict --rebase" : "/fix-conflict";
21728
21806
  }
21729
21807
  async function fixConflict(number, options2 = {}) {
21730
21808
  const { resumeSessionId } = options2;
21731
21809
  if (number && !resumeSessionId) await checkoutPr(number);
21732
- const claudeSessionId = resumeSessionId ?? randomUUID8();
21810
+ const claudeSessionId = resumeSessionId ?? randomUUID9();
21733
21811
  emitActivity({
21734
21812
  kind: "command",
21735
21813
  name: "fix-conflict",
@@ -22751,13 +22829,13 @@ function postReviewComment(vars) {
22751
22829
  }
22752
22830
 
22753
22831
  // src/commands/prs/reviewProposedPrComment.ts
22754
- import { randomUUID as randomUUID9 } from "crypto";
22832
+ import { randomUUID as randomUUID10 } from "crypto";
22755
22833
  async function reviewProposedPrComment(title, body, prNumber) {
22756
22834
  const sessionId = process.env.ASSIST_SESSION_ID;
22757
22835
  if (process.env.ASSIST_SESSION !== "1" || !sessionId) return;
22758
22836
  await awaitPreviewApproval("PR comment preview", {
22759
22837
  sessionId,
22760
- requestId: randomUUID9(),
22838
+ requestId: randomUUID10(),
22761
22839
  title,
22762
22840
  body,
22763
22841
  prNumber,
@@ -22883,7 +22961,7 @@ async function comment2(path80, line, body, startLine) {
22883
22961
  }
22884
22962
 
22885
22963
  // src/commands/prs/edit.ts
22886
- import { randomUUID as randomUUID10 } from "crypto";
22964
+ import { randomUUID as randomUUID11 } from "crypto";
22887
22965
 
22888
22966
  // src/commands/prs/appendScreenshots.ts
22889
22967
  function appendScreenshots(body, screenshots) {
@@ -22896,13 +22974,13 @@ ${screenshots.join("\n\n")}`;
22896
22974
  }
22897
22975
 
22898
22976
  // src/commands/prs/applyEdit.ts
22899
- import { execFileSync as execFileSync8 } from "child_process";
22977
+ import { execFileSync as execFileSync9 } from "child_process";
22900
22978
  function applyEdit(number, title, body) {
22901
22979
  const args = ["pr", "edit", String(number)];
22902
22980
  if (title) args.push("--title", title);
22903
22981
  args.push("--body", body);
22904
22982
  try {
22905
- execFileSync8("gh", args, { stdio: "inherit" });
22983
+ execFileSync9("gh", args, { stdio: "inherit" });
22906
22984
  } catch {
22907
22985
  process.exit(1);
22908
22986
  }
@@ -23053,24 +23131,7 @@ function isListLine(line) {
23053
23131
 
23054
23132
  // src/commands/prs/validatePrContent.ts
23055
23133
  function validatePrContent(title, body) {
23056
- if (title.toLowerCase().includes("claude")) {
23057
- console.error("Error: PR title must not reference Claude");
23058
- process.exit(1);
23059
- }
23060
- if (body.toLowerCase().includes("claude")) {
23061
- console.error("Error: PR body must not reference Claude");
23062
- process.exit(1);
23063
- }
23064
- const titleBacklogIds = findBacklogRefs(title);
23065
- if (titleBacklogIds.length > 0) {
23066
- console.error(backlogRefError("PR title", "PRs", titleBacklogIds));
23067
- process.exit(1);
23068
- }
23069
- const bodyBacklogIds = findBacklogRefs(body);
23070
- if (bodyBacklogIds.length > 0) {
23071
- console.error(backlogRefError("PR body", "PRs", bodyBacklogIds));
23072
- process.exit(1);
23073
- }
23134
+ validateProposedContent({ subject: "PR", context: "PRs" }, title, body);
23074
23135
  const wall = findWallOfText(body);
23075
23136
  if (wall) {
23076
23137
  console.error(
@@ -23097,7 +23158,7 @@ async function edit(options2) {
23097
23158
  if (process.env.ASSIST_SESSION === "1" && sessionId) {
23098
23159
  const decision = await awaitPreviewApproval("PR preview", {
23099
23160
  sessionId,
23100
- requestId: randomUUID10(),
23161
+ requestId: randomUUID11(),
23101
23162
  title: options2.title ?? title,
23102
23163
  body: newBody,
23103
23164
  prNumber: number
@@ -23715,7 +23776,7 @@ function buildValidatedBody(options2, usage) {
23715
23776
  }
23716
23777
 
23717
23778
  // src/commands/prs/placePr.ts
23718
- import { execFileSync as execFileSync9 } from "child_process";
23779
+ import { execFileSync as execFileSync10 } from "child_process";
23719
23780
 
23720
23781
  // src/commands/prs/buildCreateArgs.ts
23721
23782
  function buildEditArgs(number, title, body) {
@@ -23782,7 +23843,7 @@ async function recordPrActivity() {
23782
23843
  // src/commands/prs/placePr.ts
23783
23844
  function hasUpstream2() {
23784
23845
  try {
23785
- execFileSync9(
23846
+ execFileSync10(
23786
23847
  "git",
23787
23848
  ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
23788
23849
  { stdio: "pipe" }
@@ -23794,13 +23855,13 @@ function hasUpstream2() {
23794
23855
  }
23795
23856
  function ensureBranchPushed() {
23796
23857
  const args = hasUpstream2() ? ["push"] : ["push", "--set-upstream", "origin", "HEAD"];
23797
- execFileSync9("git", args, { stdio: "inherit" });
23858
+ execFileSync10("git", args, { stdio: "inherit" });
23798
23859
  }
23799
23860
  async function placePr(prNumber, title, body, options2) {
23800
23861
  const args = prNumber !== null ? buildEditArgs(prNumber, title, body) : buildCreateArgs(title, body, options2);
23801
23862
  try {
23802
23863
  if (prNumber === null && !options2.head) ensureBranchPushed();
23803
- execFileSync9("gh", args, { stdio: "inherit" });
23864
+ execFileSync10("gh", args, { stdio: "inherit" });
23804
23865
  } catch {
23805
23866
  process.exit(1);
23806
23867
  }
@@ -23808,7 +23869,7 @@ async function placePr(prNumber, title, body, options2) {
23808
23869
  }
23809
23870
 
23810
23871
  // src/commands/prs/previewAndPlace.ts
23811
- import { randomUUID as randomUUID11 } from "crypto";
23872
+ import { randomUUID as randomUUID12 } from "crypto";
23812
23873
 
23813
23874
  // src/commands/sessions/shared/requestSession.ts
23814
23875
  function parseIncoming(line, type) {
@@ -23943,7 +24004,7 @@ function warn(reason4) {
23943
24004
  async function previewAndPlace(args) {
23944
24005
  const decision = await awaitPreviewApproval("PR preview", {
23945
24006
  sessionId: args.sessionId,
23946
- requestId: randomUUID11(),
24007
+ requestId: randomUUID12(),
23947
24008
  title: args.title,
23948
24009
  body: args.body,
23949
24010
  prNumber: args.prNumber,
@@ -23963,9 +24024,9 @@ function resolveDraftState(options2, command) {
23963
24024
  }
23964
24025
 
23965
24026
  // src/commands/prs/raise.ts
23966
- var USAGE = "Usage: assist prs raise --title <title> --what <what> --why <why> [--how <how>] [--resolves <key>] [--force]";
24027
+ var USAGE2 = "Usage: assist prs raise --title <title> --what <what> --why <why> [--how <how>] [--resolves <key>] [--force]";
23967
24028
  async function raise(options2, command) {
23968
- const { title, body } = buildValidatedBody(options2, USAGE);
24029
+ const { title, body } = buildValidatedBody(options2, USAGE2);
23969
24030
  const resolved = { ...options2, draft: resolveDraftState(options2, command) };
23970
24031
  const existing = findCurrentPrNumber();
23971
24032
  const sessionId = process.env.ASSIST_SESSION_ID;
@@ -26426,10 +26487,10 @@ function registerRefactor(program2) {
26426
26487
  }
26427
26488
 
26428
26489
  // src/commands/review/checkoutOnlySession.ts
26429
- import { randomUUID as randomUUID12 } from "crypto";
26490
+ import { randomUUID as randomUUID13 } from "crypto";
26430
26491
  async function checkoutOnlySession(number) {
26431
26492
  await checkoutPr(number);
26432
- const claudeSessionId = randomUUID12();
26493
+ const claudeSessionId = randomUUID13();
26433
26494
  emitActivity({ kind: "command", name: "review", claudeSessionId });
26434
26495
  const { done: done2 } = spawnClaude("", {
26435
26496
  permissionMode: "acceptEdits",
@@ -30061,9 +30122,9 @@ function registerVoice(program2) {
30061
30122
  import { join as join83 } from "path";
30062
30123
 
30063
30124
  // src/commands/watch/resolveUpstream.ts
30064
- import { execFileSync as execFileSync10 } from "child_process";
30125
+ import { execFileSync as execFileSync11 } from "child_process";
30065
30126
  function runGit2(args, cwd) {
30066
- return execFileSync10("git", args, {
30127
+ return execFileSync11("git", args, {
30067
30128
  encoding: "utf8",
30068
30129
  stdio: ["pipe", "pipe", "pipe"],
30069
30130
  cwd
@@ -30424,13 +30485,13 @@ import { spawn as spawn9 } from "child_process";
30424
30485
  import { existsSync as existsSync68 } from "fs";
30425
30486
 
30426
30487
  // src/commands/run/resolveCommand.ts
30427
- import { execFileSync as execFileSync11 } from "child_process";
30488
+ import { execFileSync as execFileSync12 } from "child_process";
30428
30489
  import { existsSync as existsSync67 } from "fs";
30429
30490
  import { dirname as dirname35, join as join84, resolve as resolve19 } from "path";
30430
30491
  function resolveCommand2(command) {
30431
30492
  if (process.platform !== "win32" || command !== "bash") return command;
30432
30493
  try {
30433
- const gitPath = execFileSync11("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
30494
+ const gitPath = execFileSync12("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
30434
30495
  const gitRoot = resolve19(dirname35(gitPath), "..");
30435
30496
  const gitBash = join84(gitRoot, "bin", "bash.exe");
30436
30497
  if (existsSync67(gitBash)) return gitBash;
@@ -30527,11 +30588,11 @@ async function reportBuildOrExit(entry) {
30527
30588
  }
30528
30589
 
30529
30590
  // src/commands/watch/fetchQuietly.ts
30530
- import { execFileSync as execFileSync12 } from "child_process";
30591
+ import { execFileSync as execFileSync13 } from "child_process";
30531
30592
  var MIN_FETCH_TIMEOUT_MS = 6e4;
30532
30593
  function fetchQuietly(cwd, intervalMs) {
30533
30594
  try {
30534
- execFileSync12("git", ["fetch", "--quiet"], {
30595
+ execFileSync13("git", ["fetch", "--quiet"], {
30535
30596
  stdio: ["pipe", "pipe", "pipe"],
30536
30597
  cwd,
30537
30598
  timeout: Math.max(intervalMs, MIN_FETCH_TIMEOUT_MS)
@@ -30831,7 +30892,7 @@ async function auth() {
30831
30892
  }
30832
30893
 
30833
30894
  // src/commands/roam/postRoamActivity.ts
30834
- import { execFileSync as execFileSync13 } from "child_process";
30895
+ import { execFileSync as execFileSync14 } from "child_process";
30835
30896
  import { readdirSync as readdirSync20, readFileSync as readFileSync52, statSync as statSync11 } from "fs";
30836
30897
  import { join as join85 } from "path";
30837
30898
  function findPortFile(roamDir) {
@@ -30864,7 +30925,7 @@ function postRoamActivity(app, event) {
30864
30925
  }
30865
30926
  const url = `http://127.0.0.1:${port}/api/v1/activity/${app}/${event}?pid=${app === "codex" ? 99998 : 99999}`;
30866
30927
  try {
30867
- execFileSync13("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
30928
+ execFileSync14("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
30868
30929
  stdio: "ignore"
30869
30930
  });
30870
30931
  } catch {
@@ -31340,11 +31401,11 @@ function screenshot(processName) {
31340
31401
  }
31341
31402
 
31342
31403
  // src/commands/sessions/daemon/listDaemonPids.ts
31343
- import { execFileSync as execFileSync14 } from "child_process";
31404
+ import { execFileSync as execFileSync15 } from "child_process";
31344
31405
  function listDaemonPids() {
31345
31406
  if (process.platform === "win32") return [];
31346
31407
  try {
31347
- const out = execFileSync14("ps", ["-eo", "pid=,args="], {
31408
+ const out = execFileSync15("ps", ["-eo", "pid=,args="], {
31348
31409
  encoding: "utf8"
31349
31410
  });
31350
31411
  return out.split("\n").filter((line) => line.includes("assist") && / daemon run\b/.test(line)).map((line) => Number.parseInt(line.trim(), 10)).filter((pid) => Number.isInteger(pid));
@@ -31389,17 +31450,9 @@ function applyLine(result, pending, line) {
31389
31450
  }
31390
31451
  }
31391
31452
 
31392
- // src/commands/sessions/daemon/reportStolenSocket.ts
31453
+ // src/commands/sessions/daemon/readDaemonPidFile.ts
31393
31454
  import { readFileSync as readFileSync53 } from "fs";
31394
- function reportStolenSocket(socketPid) {
31395
- if (!socketPid) return;
31396
- const filePid = readPidFile();
31397
- if (filePid === void 0 || filePid === socketPid) return;
31398
- console.error(
31399
- `Warning: daemon.pid records PID ${filePid} but the socket is owned by PID ${socketPid} (stolen socket)`
31400
- );
31401
- }
31402
- function readPidFile() {
31455
+ function readDaemonPidFile() {
31403
31456
  try {
31404
31457
  const pid = Number.parseInt(
31405
31458
  readFileSync53(daemonPaths.pid, "utf8").trim(),
@@ -31410,6 +31463,24 @@ function readPidFile() {
31410
31463
  return void 0;
31411
31464
  }
31412
31465
  }
31466
+ function isPidAlive(pid) {
31467
+ try {
31468
+ process.kill(pid, 0);
31469
+ return true;
31470
+ } catch (error) {
31471
+ return error.code === "EPERM";
31472
+ }
31473
+ }
31474
+
31475
+ // src/commands/sessions/daemon/reportStolenSocket.ts
31476
+ function reportStolenSocket(socketPid) {
31477
+ if (!socketPid) return;
31478
+ const filePid = readDaemonPidFile();
31479
+ if (filePid === void 0 || filePid === socketPid) return;
31480
+ console.error(
31481
+ `Warning: daemon.pid records PID ${filePid} but the socket is owned by PID ${socketPid} (stolen socket)`
31482
+ );
31483
+ }
31413
31484
 
31414
31485
  // src/commands/sessions/daemon/daemonStatus.ts
31415
31486
  async function daemonStatus() {
@@ -31791,7 +31862,7 @@ var ClientHub = class extends Set {
31791
31862
  };
31792
31863
 
31793
31864
  // src/commands/sessions/daemon/createSession.ts
31794
- import { randomUUID as randomUUID13 } from "crypto";
31865
+ import { randomUUID as randomUUID14 } from "crypto";
31795
31866
 
31796
31867
  // src/commands/sessions/daemon/sessionBase.ts
31797
31868
  function sessionBase(id, status3) {
@@ -31987,7 +32058,7 @@ function spawnRun(opts) {
31987
32058
  function createSession(id, { prompt, cwd, design, auto, harness, holdPty } = {}) {
31988
32059
  if (harness && harness !== "claude")
31989
32060
  return createHarnessSession(id, harness, prompt, cwd, holdPty);
31990
- const claudeSessionId = randomUUID13();
32061
+ const claudeSessionId = randomUUID14();
31991
32062
  return {
31992
32063
  ...sessionBase(id, prompt ? "running" : "waiting"),
31993
32064
  name: `Session ${id}`,
@@ -32248,7 +32319,10 @@ function holdFailedDiscard(session, reason4, notify2) {
32248
32319
  // src/commands/sessions/daemon/killPtyTree.ts
32249
32320
  function killPtyTree(pty2) {
32250
32321
  if (process.platform === "win32") {
32251
- pty2.kill();
32322
+ try {
32323
+ pty2.kill();
32324
+ } catch {
32325
+ }
32252
32326
  return;
32253
32327
  }
32254
32328
  try {
@@ -32579,7 +32653,8 @@ var PREVIEW_KINDS = [
32579
32653
  "pr",
32580
32654
  "backlog-item",
32581
32655
  "backlog-comment",
32582
- "pr-comment"
32656
+ "pr-comment",
32657
+ "github-issue"
32583
32658
  ];
32584
32659
  function isPreviewKind(value) {
32585
32660
  return PREVIEW_KINDS.includes(value);
@@ -32589,6 +32664,7 @@ function isPreviewKind(value) {
32589
32664
  function previewTargetLabel(kind, itemType, prNumber, draft) {
32590
32665
  if (kind === "backlog-comment") return "backlog comment";
32591
32666
  if (kind === "pr-comment") return "pr comment";
32667
+ if (kind === "github-issue") return "github issue";
32592
32668
  if (kind === "backlog-item") return `backlog ${itemType}`;
32593
32669
  if (prNumber !== null) return `edit #${prNumber}`;
32594
32670
  return draft ? "create draft" : "create";
@@ -34048,7 +34124,7 @@ function codexRespawnPlan(session) {
34048
34124
  }
34049
34125
 
34050
34126
  // src/commands/sessions/daemon/interactiveRespawnPlan.ts
34051
- import { randomUUID as randomUUID14 } from "crypto";
34127
+ import { randomUUID as randomUUID15 } from "crypto";
34052
34128
  function interactiveRespawnPlan(session, resumes) {
34053
34129
  const { claudeSessionId, cwd, initialPrompt, design, auto } = session;
34054
34130
  if (!resumes) return null;
@@ -34068,7 +34144,7 @@ function interactiveRespawnPlan(session, resumes) {
34068
34144
  return null;
34069
34145
  }
34070
34146
  function freshClaudePlan(session, prompt, cwd) {
34071
- const claudeSessionId = randomUUID14();
34147
+ const claudeSessionId = randomUUID15();
34072
34148
  return {
34073
34149
  spawn: () => {
34074
34150
  session.claudeSessionId = claudeSessionId;
@@ -34953,10 +35029,10 @@ function startReusedRunPty(session, assistArgs, itemId2, hold, clients, onStatus
34953
35029
  }
34954
35030
 
34955
35031
  // src/commands/sessions/daemon/createWatcherSession.ts
34956
- import { randomUUID as randomUUID15 } from "crypto";
35032
+ import { randomUUID as randomUUID16 } from "crypto";
34957
35033
  var WATCH_PROMPT = "/watch";
34958
35034
  function createWatcherSession(id, cwd) {
34959
- const claudeSessionId = randomUUID15();
35035
+ const claudeSessionId = randomUUID16();
34960
35036
  return {
34961
35037
  ...sessionBase(id, "running"),
34962
35038
  name: `Session ${id}`,
@@ -35145,9 +35221,23 @@ function reuseSessionForRun(session, itemId2, clients, onStatusChange, tree) {
35145
35221
  // src/commands/sessions/daemon/shutdownSessions.ts
35146
35222
  function shutdownSessions(sessions) {
35147
35223
  daemonLog(`shutting down: killing ${sessions.size} session(s)`);
35224
+ let failures = 0;
35148
35225
  for (const session of sessions.values()) {
35149
- if (session.status !== "done") session.pty?.kill();
35226
+ if (session.status === "done") continue;
35227
+ try {
35228
+ session.pty?.kill();
35229
+ } catch (error) {
35230
+ failures++;
35231
+ const reason4 = error instanceof Error ? error.message : String(error);
35232
+ daemonLog(
35233
+ `shutting down: killing session ${session.name} (${session.id}) failed: ${reason4}`
35234
+ );
35235
+ }
35150
35236
  }
35237
+ if (failures > 0)
35238
+ daemonLog(
35239
+ `shutting down: ${failures} session(s) failed to die; continuing so the process still exits and releases its listeners`
35240
+ );
35151
35241
  }
35152
35242
 
35153
35243
  // src/commands/sessions/daemon/heldStartBlocked.ts
@@ -36648,8 +36738,13 @@ function handleFetchTranscript(client, _manager, data) {
36648
36738
  );
36649
36739
  }
36650
36740
  async function handleShutdown(client, manager) {
36651
- await manager.flushActiveMs();
36652
- manager.shutdown();
36741
+ try {
36742
+ await manager.flushActiveMs();
36743
+ manager.shutdown();
36744
+ } catch (error) {
36745
+ const reason4 = error instanceof Error ? error.message : String(error);
36746
+ daemonLog(`shutdown teardown failed: ${reason4}; exiting anyway`);
36747
+ }
36653
36748
  sendTo(client, { type: "shutting-down" });
36654
36749
  setImmediate(() => process.exit(0));
36655
36750
  }
@@ -36840,7 +36935,12 @@ function onListening(manager, checkAutoExit) {
36840
36935
  startPidFileWatchdog(() => {
36841
36936
  daemonLog("lost daemon.pid ownership; shutting down sessions and exiting");
36842
36937
  void manager.flushActiveMs().finally(() => {
36843
- manager.shutdown();
36938
+ try {
36939
+ manager.shutdown();
36940
+ } catch (error) {
36941
+ const reason4 = error instanceof Error ? error.message : String(error);
36942
+ daemonLog(`shutdown teardown failed: ${reason4}; exiting anyway`);
36943
+ }
36844
36944
  process.exit(0);
36845
36945
  });
36846
36946
  });
@@ -36870,6 +36970,45 @@ function cleanupOwnedFiles() {
36870
36970
 
36871
36971
  // src/commands/sessions/daemon/startWindowsBridge.ts
36872
36972
  import * as net3 from "net";
36973
+
36974
+ // src/commands/sessions/daemon/findPortHolderPid.ts
36975
+ import { execFileSync as execFileSync16 } from "child_process";
36976
+ var PROBE_TIMEOUT_MS = 3e3;
36977
+ function findPortHolderPid(port) {
36978
+ try {
36979
+ return process.platform === "win32" ? netstatListenerPid(probe("netstat", ["-ano"]), port) : firstPid(probe("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"]));
36980
+ } catch {
36981
+ return void 0;
36982
+ }
36983
+ }
36984
+ function probe(command, args) {
36985
+ return execFileSync16(command, args, {
36986
+ encoding: "utf8",
36987
+ timeout: PROBE_TIMEOUT_MS,
36988
+ stdio: ["ignore", "pipe", "ignore"]
36989
+ });
36990
+ }
36991
+ function netstatListenerPid(output, port) {
36992
+ const suffix = `:${port}`;
36993
+ for (const line of output.split("\n")) {
36994
+ const [protocol, local, , state, pid] = line.trim().split(/\s+/);
36995
+ if (protocol?.toUpperCase() !== "TCP") continue;
36996
+ if (state?.toUpperCase() !== "LISTENING") continue;
36997
+ if (!local?.endsWith(suffix)) continue;
36998
+ const holder = Number.parseInt(pid ?? "", 10);
36999
+ if (Number.isInteger(holder)) return holder;
37000
+ }
37001
+ return void 0;
37002
+ }
37003
+ function firstPid(output) {
37004
+ for (const line of output.split("\n")) {
37005
+ const pid = Number.parseInt(line.trim(), 10);
37006
+ if (Number.isInteger(pid)) return pid;
37007
+ }
37008
+ return void 0;
37009
+ }
37010
+
37011
+ // src/commands/sessions/daemon/startWindowsBridge.ts
36873
37012
  var BIND_ATTEMPTS = 3;
36874
37013
  var BIND_RETRY_DELAY_MS = 250;
36875
37014
  var KEEPALIVE_PROBE_MS2 = 1e4;
@@ -36887,10 +37026,14 @@ async function startWindowsBridge(manager) {
36887
37026
  if (attempt < BIND_ATTEMPTS) await delay3(BIND_RETRY_DELAY_MS);
36888
37027
  }
36889
37028
  daemonLog(
36890
- `${WINDOWS_BRIDGE_FAILURE_PREFIX} could not bind port ${port} after ${BIND_ATTEMPTS} attempts; the port is held by another process or reserved by a Hyper-V/WSL dynamic port range \u2014 set sessions.windowsDaemonPort to a free port outside 49152-65535`
37029
+ `${WINDOWS_BRIDGE_FAILURE_PREFIX} could not bind port ${port} after ${BIND_ATTEMPTS} attempts; ${describePortHolder(port)}`
36891
37030
  );
36892
37031
  return false;
36893
37032
  }
37033
+ function describePortHolder(port) {
37034
+ const holder = findPortHolderPid(port);
37035
+ return holder !== void 0 && holder !== process.pid ? `PID ${holder} is listening on it \u2014 kill PID ${holder} to free the port` : "the port is held by another process or reserved by a Hyper-V/WSL dynamic port range \u2014 set sessions.windowsDaemonPort to a free port outside 49152-65535";
37036
+ }
36894
37037
  function bindBridge(manager, port) {
36895
37038
  return new Promise((resolve24) => {
36896
37039
  const bridge = net3.createServer((socket) => {
@@ -36918,6 +37061,14 @@ function delay3(ms) {
36918
37061
  return new Promise((resolve24) => setTimeout(resolve24, ms));
36919
37062
  }
36920
37063
 
37064
+ // src/commands/sessions/daemon/describeWedgedHolder.ts
37065
+ function describeWedgedHolder() {
37066
+ const recorded = readDaemonPidFile();
37067
+ if (recorded !== void 0 && recorded !== process.pid && isPidAlive(recorded))
37068
+ return `daemon.pid records PID ${recorded}, which is still alive \u2014 kill PID ${recorded} to free the pipe and the windows bridge port`;
37069
+ return "the holding process could not be identified \u2014 look for a stray assist daemon and kill it";
37070
+ }
37071
+
36921
37072
  // src/commands/sessions/daemon/startDaemonServer.ts
36922
37073
  async function startDaemonServer(manager, checkAutoExit) {
36923
37074
  if (process.platform === "win32" && !await startWindowsBridge(manager)) {
@@ -36950,12 +37101,17 @@ async function recoverFromAddrInUse(server, manager, checkAutoExit) {
36950
37101
  daemonLog("another daemon owns the socket; exiting");
36951
37102
  process.exit(1);
36952
37103
  }
37104
+ if (process.platform === "win32") {
37105
+ daemonLog(
37106
+ `${daemonPaths.socket} is bound but not answering: ${describeWedgedHolder()}; a named pipe cannot be removed, so exiting instead of retrying the bind`
37107
+ );
37108
+ exitAfterFlush(1);
37109
+ return;
37110
+ }
36953
37111
  daemonLog("removing stale socket left by a crashed daemon");
36954
- if (process.platform !== "win32") {
36955
- try {
36956
- unlinkSync24(daemonPaths.socket);
36957
- } catch {
36958
- }
37112
+ try {
37113
+ unlinkSync24(daemonPaths.socket);
37114
+ } catch {
36959
37115
  }
36960
37116
  listenWithSingleOnListening(server, manager, checkAutoExit);
36961
37117
  }
@@ -37033,7 +37189,7 @@ function summaryPathFor(jsonlPath2) {
37033
37189
  }
37034
37190
 
37035
37191
  // src/commands/sessions/summarise/summariseSession.ts
37036
- import { execFileSync as execFileSync15 } from "child_process";
37192
+ import { execFileSync as execFileSync17 } from "child_process";
37037
37193
  function summariseSession(jsonlPath2) {
37038
37194
  const firstMessage = extractFirstUserMessage(jsonlPath2);
37039
37195
  const backlogIds = scanSessionBacklogRefs(jsonlPath2);
@@ -37042,7 +37198,7 @@ function summariseSession(jsonlPath2) {
37042
37198
  }
37043
37199
  const prompt = buildPrompt6(firstMessage, backlogIds);
37044
37200
  try {
37045
- const output = execFileSync15("claude", ["-p", "--model", "haiku", prompt], {
37201
+ const output = execFileSync17("claude", ["-p", "--model", "haiku", prompt], {
37046
37202
  encoding: "utf8",
37047
37203
  timeout: 3e4,
37048
37204
  stdio: ["ignore", "pipe", "ignore"]