@staff0rd/assist 0.326.1 → 0.327.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.
Files changed (3) hide show
  1. package/README.md +5 -4
  2. package/dist/index.js +151 -77
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -277,13 +277,14 @@ From WSL, the selector can also surface and drive Windows-host repos (requires `
277
277
 
278
278
  - `sessions.windowsProjectsRoot` — the Windows `.claude/projects` directory as seen from WSL (e.g. `/mnt/c/Users/<user>/.claude/projects`); enables discovery of Windows-host repos, tagged with a `Windows` badge. Selecting one launches a native assist daemon on Windows and runs an interactive session there.
279
279
  - `sessions.windowsDaemonHost` / `sessions.windowsDaemonPort` — where the WSL daemon reaches the native Windows daemon (defaults `127.0.0.1` / `51764`; set the host to the Windows IP on WSL2 NAT-mode networking).
280
+ - `sessions.windowsVersionCheck` — how the WSL↔Windows daemon handshake reacts to a protocol-version mismatch: `block` (default) refuses creates and auto-heals the host, `warn` logs and proceeds anyway, `off` skips the check. Use `warn`/`off` to keep working across an unfixable version gap.
280
281
 
281
- When iterating on assist itself: web server changes only need the `assist sessions` process restarted — sessions survive. Daemon/session-core changes need `assist daemon restart` to load the new code; this kills the PTYs, then claude sessions — including assist sessions that wrap claude, like `assist draft` — are auto-respawned via `claude --resume` with scrollback starting fresh, while run sessions (and assist sessions whose claude sessionId was never discovered) reappear as not-restored tiles that can be retried.
282
+ When iterating on assist itself: web server changes only need the `assist sessions` process restarted — sessions survive. Daemon/session-core changes need `assist daemon restart` to load the new code; this kills the PTYs, then claude sessions — including assist sessions that wrap claude, like `assist draft` — are auto-respawned via `claude --resume` with scrollback starting fresh, while run sessions (and assist sessions whose claude sessionId was never discovered) reappear as not-restored tiles that can be retried. A `--once` draft/bug/refine session is respawned through its assist wrapper instead of bare `claude --resume`, so its `assist signal done` watcher is re-established and the card still auto-closes.
282
283
 
283
284
  - `assist next [id] [--once]` - Alias for `backlog next [id]`; `--once` exits after the first completed item run instead of prompting for another
284
- - `assist draft [description] [--once]` (alias: `feat`) - Launch Claude in `/draft` mode, chain into next on `/next` signal; an optional `description` is forwarded as `/draft <description>`; `--once` exits when the done signal arrives after the initial draft completes
285
- - `assist bug [description] [--once]` - Launch Claude in `/bug` mode, chain into next on `/next` signal; an optional `description` is forwarded as `/bug <description>`; `--once` exits when the done signal arrives after the initial bug report completes
286
- - `assist refine [id] [--once]` - Launch Claude in `/refine` mode to refine a backlog item; prompts for selection when no id given; `--once` exits when the done signal arrives after refinement completes
285
+ - `assist draft [description] [--once]` (alias: `feat`) - Launch Claude in `/draft` mode, chain into next on `/next` signal; an optional `description` is forwarded as `/draft <description>`; `--once` exits when the done signal arrives after the initial draft completes; `--resume-session <id>` resumes an interrupted Claude session (used by the sessions daemon when it restarts a running item)
286
+ - `assist bug [description] [--once]` - Launch Claude in `/bug` mode, chain into next on `/next` signal; an optional `description` is forwarded as `/bug <description>`; `--once` exits when the done signal arrives after the initial bug report completes; `--resume-session <id>` resumes an interrupted Claude session (used by the sessions daemon when it restarts a running item)
287
+ - `assist refine [id] [--once]` - Launch Claude in `/refine` mode to refine a backlog item; prompts for selection when no id given; `--once` exits when the done signal arrives after refinement completes; `--resume-session <id>` resumes an interrupted Claude session (used by the sessions daemon when it restarts a running item)
287
288
  - `assist review-comments [number]` - Launch Claude in `/review-comments` mode to process PR review comments (single session, no chaining); when a PR number is supplied, checks out that PR via `gh pr checkout` first
288
289
  - `assist signal next [id]` - Write a next signal to chain into `assist next`; when `id` is supplied, the parent launcher runs that backlog item directly
289
290
  - `assist signal done [id]` - Write a done signal marking the session's initial task complete; an optional `id` surfaces the backlog item the session created onto its session card; `--once` launch sessions exit when it arrives, plain sessions ignore it
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.326.1",
9
+ version: "0.327.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -285,7 +285,8 @@ var assistConfigSchema = z2.strictObject({
285
285
  // why: host the WSL daemon dials to reach the native Windows daemon over TCP (WSL can't use the Windows named pipe); defaults to 127.0.0.1 (WSL2 mirrored networking). NAT-mode users set the Windows host IP.
286
286
  windowsDaemonHost: z2.string().optional(),
287
287
  // why: TCP port the native Windows daemon listens on for the WSL bridge; defaults to 51764
288
- windowsDaemonPort: z2.number().optional()
288
+ windowsDaemonPort: z2.number().optional(),
289
+ windowsVersionCheck: z2.enum(["block", "warn", "off"]).default("block")
289
290
  }).optional(),
290
291
  database: z2.strictObject({
291
292
  url: z2.string().optional()
@@ -7571,9 +7572,10 @@ function buildSlashCommand(slashCommand, description) {
7571
7572
  return trimmed ? `/${slashCommand} ${trimmed}` : `/${slashCommand}`;
7572
7573
  }
7573
7574
  async function launchMode(slashCommand, options2) {
7574
- pullIfConfigured();
7575
+ const resumeSessionId = options2?.resumeSessionId;
7576
+ if (!resumeSessionId) pullIfConfigured();
7575
7577
  process.env.ASSIST_SESSION_ID ??= String(process.pid);
7576
- const claudeSessionId = randomUUID2();
7578
+ const claudeSessionId = resumeSessionId ?? randomUUID2();
7577
7579
  emitActivity({
7578
7580
  kind: "command",
7579
7581
  name: slashCommand,
@@ -7582,8 +7584,8 @@ async function launchMode(slashCommand, options2) {
7582
7584
  claudeSessionId
7583
7585
  });
7584
7586
  const { child, done: done2 } = spawnClaude(
7585
- buildSlashCommand(slashCommand, options2?.description),
7586
- { allowEdits: true, sessionId: claudeSessionId }
7587
+ resumeSessionId ? buildResumePrompt() : buildSlashCommand(slashCommand, options2?.description),
7588
+ { allowEdits: true, sessionId: claudeSessionId, resumeSessionId }
7587
7589
  );
7588
7590
  watchForMarker(child, { actOnDone: options2?.once });
7589
7591
  const launched = await awaitClaude(done2, `/${slashCommand}`);
@@ -12242,23 +12244,51 @@ async function reviewComments(number) {
12242
12244
  }
12243
12245
 
12244
12246
  // src/commands/registerLaunch.ts
12245
- function registerLaunch(program2) {
12247
+ var RESUME_SESSION_FLAG = "Resume an interrupted Claude session (used by the sessions daemon on restart)";
12248
+ function registerNext(program2) {
12246
12249
  program2.command("next").argument("[id]", "Backlog item ID to run first").description("Alias for backlog next").option("--once", "Exit after the first completed item run").action(
12247
12250
  (id2, opts) => next({ allowEdits: true, once: opts.once }, id2)
12248
12251
  );
12249
- program2.command("draft").alias("feat").argument("[description]", "Text to forward to the /draft slash command").description(
12250
- "Launch Claude in /draft mode, chain into next on /next signal"
12251
- ).option("--once", "Exit when the initial task completes").action(
12252
- (description, opts) => launchMode("draft", { once: opts.once, description })
12253
- );
12254
- program2.command("bug").argument("[description]", "Text to forward to the /bug slash command").description("Launch Claude in /bug mode, chain into next on /next signal").option("--once", "Exit when the initial task completes").action(
12255
- (description, opts) => launchMode("bug", { once: opts.once, description })
12252
+ }
12253
+ function registerDescriptionLaunch(program2, name, slashCommand, description, alias) {
12254
+ const command = program2.command(name).argument(
12255
+ "[description]",
12256
+ `Text to forward to the /${slashCommand} slash command`
12257
+ ).description(description).option("--once", "Exit when the initial task completes").option("--resume-session <id>", RESUME_SESSION_FLAG).action(
12258
+ (text6, opts) => launchMode(slashCommand, {
12259
+ once: opts.once,
12260
+ description: text6,
12261
+ resumeSessionId: opts.resumeSession
12262
+ })
12256
12263
  );
12264
+ if (alias) command.alias(alias);
12265
+ }
12266
+ function registerReviewComments(program2) {
12257
12267
  program2.command("review-comments").argument("[number]", "PR number to check out first").description("Launch Claude in /review-comments mode (single session)").action((number) => reviewComments(number));
12258
- program2.command("refine").argument("[id]", "Backlog item ID").description("Launch Claude in /refine mode to refine a backlog item").option("--once", "Exit when the initial task completes").action(
12259
- (id2, opts) => refine(id2, { once: opts.once })
12268
+ }
12269
+ function registerRefine(program2) {
12270
+ program2.command("refine").argument("[id]", "Backlog item ID").description("Launch Claude in /refine mode to refine a backlog item").option("--once", "Exit when the initial task completes").option("--resume-session <id>", RESUME_SESSION_FLAG).action(
12271
+ (id2, opts) => refine(id2, { once: opts.once, resumeSessionId: opts.resumeSession })
12260
12272
  );
12261
12273
  }
12274
+ function registerLaunch(program2) {
12275
+ registerNext(program2);
12276
+ registerDescriptionLaunch(
12277
+ program2,
12278
+ "draft",
12279
+ "draft",
12280
+ "Launch Claude in /draft mode, chain into next on /next signal",
12281
+ "feat"
12282
+ );
12283
+ registerDescriptionLaunch(
12284
+ program2,
12285
+ "bug",
12286
+ "bug",
12287
+ "Launch Claude in /bug mode, chain into next on /next signal"
12288
+ );
12289
+ registerReviewComments(program2);
12290
+ registerRefine(program2);
12291
+ }
12262
12292
 
12263
12293
  // src/commands/registerList.ts
12264
12294
  function registerList(program2) {
@@ -20882,8 +20912,8 @@ function errorSession(id2, persisted, error) {
20882
20912
  };
20883
20913
  }
20884
20914
 
20885
- // src/commands/sessions/daemon/backlogRunArgs.ts
20886
- function backlogRunArgs(persisted) {
20915
+ // src/commands/sessions/daemon/assistResumeArgs.ts
20916
+ function assistResumeArgs(persisted) {
20887
20917
  const args = ["assist", ...persisted.assistArgs];
20888
20918
  return persisted.claudeSessionId ? [...args, "--resume-session", persisted.claudeSessionId] : args;
20889
20919
  }
@@ -20904,6 +20934,46 @@ function runningSession(base, persisted, pty2) {
20904
20934
  };
20905
20935
  }
20906
20936
 
20937
+ // src/commands/sessions/daemon/restoreInteractiveSession.ts
20938
+ function restoreInteractiveSession(id2, persisted, base) {
20939
+ if (persisted.commandType !== "run" && persisted.claudeSessionId) {
20940
+ return resumeViaClaude(id2, persisted, base);
20941
+ }
20942
+ if (persisted.commandType === "claude") {
20943
+ return unrecoverableClaude(id2, persisted);
20944
+ }
20945
+ return notRestoredStub(base, persisted);
20946
+ }
20947
+ function resumeViaClaude(id2, persisted, base) {
20948
+ const pty2 = spawnClaude2({
20949
+ resumeSessionId: persisted.claudeSessionId,
20950
+ prompt: buildResumePrompt(),
20951
+ cwd: persisted.cwd,
20952
+ sessionId: id2
20953
+ });
20954
+ return runningSession(base, persisted, pty2);
20955
+ }
20956
+ function unrecoverableClaude(id2, persisted) {
20957
+ return errorSession(
20958
+ id2,
20959
+ persisted,
20960
+ "no claude session id was recorded before the daemon stopped, so the conversation cannot be resumed"
20961
+ );
20962
+ }
20963
+ function notRestoredStub(base, persisted) {
20964
+ return {
20965
+ ...base,
20966
+ status: "done",
20967
+ startedAt: persisted.startedAt,
20968
+ runningMs: persisted.runningMs ?? 0,
20969
+ runningSince: null,
20970
+ pty: null,
20971
+ runName: persisted.runName,
20972
+ runArgs: persisted.runArgs,
20973
+ restored: false
20974
+ };
20975
+ }
20976
+
20907
20977
  // src/commands/sessions/daemon/updatedSession.ts
20908
20978
  function updatedSession(id2, persisted) {
20909
20979
  return {
@@ -20923,41 +20993,22 @@ function isUpdate(persisted) {
20923
20993
  function restoreSession(id2, persisted) {
20924
20994
  const base = restoreBase(id2, persisted);
20925
20995
  if (isUpdate(persisted)) return updatedSession(id2, persisted);
20926
- if (isBacklogRun(persisted)) {
20927
- const pty2 = spawnPty(backlogRunArgs(persisted), persisted.cwd, id2);
20996
+ if (needsWrapperRelaunch(persisted)) {
20997
+ const pty2 = spawnPty(assistResumeArgs(persisted), persisted.cwd, id2);
20928
20998
  return runningSession(base, persisted, pty2);
20929
20999
  }
20930
- if (persisted.commandType !== "run" && persisted.claudeSessionId) {
20931
- const pty2 = spawnClaude2({
20932
- resumeSessionId: persisted.claudeSessionId,
20933
- prompt: buildResumePrompt(),
20934
- cwd: persisted.cwd,
20935
- sessionId: id2
20936
- });
20937
- return runningSession(base, persisted, pty2);
20938
- }
20939
- if (persisted.commandType === "claude") {
20940
- return errorSession(
20941
- id2,
20942
- persisted,
20943
- "no claude session id was recorded before the daemon stopped, so the conversation cannot be resumed"
20944
- );
20945
- }
20946
- return {
20947
- ...base,
20948
- status: "done",
20949
- startedAt: persisted.startedAt,
20950
- runningMs: persisted.runningMs ?? 0,
20951
- runningSince: null,
20952
- pty: null,
20953
- runName: persisted.runName,
20954
- runArgs: persisted.runArgs,
20955
- restored: false
20956
- };
21000
+ return restoreInteractiveSession(id2, persisted, base);
21001
+ }
21002
+ function needsWrapperRelaunch(persisted) {
21003
+ return isBacklogRun(persisted) || isOnceLaunch(persisted) && !!persisted.claudeSessionId;
20957
21004
  }
20958
21005
  function isBacklogRun(persisted) {
20959
21006
  return persisted.commandType === "assist" && persisted.assistArgs?.[0] === "backlog" && persisted.assistArgs?.[1] === "run";
20960
21007
  }
21008
+ var LAUNCH_COMMANDS = ["draft", "feat", "bug", "refine"];
21009
+ function isOnceLaunch(persisted) {
21010
+ return persisted.commandType === "assist" && !!persisted.assistArgs && LAUNCH_COMMANDS.includes(persisted.assistArgs[0]) && persisted.assistArgs.includes("--once");
21011
+ }
20961
21012
 
20962
21013
  // src/commands/sessions/daemon/restoreOne.ts
20963
21014
  function restoreOne(persisted, spawn12, sessions) {
@@ -21342,18 +21393,6 @@ function stripOutboundSessionId(data) {
21342
21393
  return { ...data, sessionId: stripWindowsSessionId(data.sessionId) };
21343
21394
  }
21344
21395
 
21345
- // src/commands/sessions/daemon/buildHello.ts
21346
- var ASSIST_VERSION = package_default.version;
21347
- function buildHello() {
21348
- return { type: "hello", version: ASSIST_VERSION };
21349
- }
21350
- function isHello(msg) {
21351
- return msg.type === "hello" && typeof msg.version === "string";
21352
- }
21353
- function versionsMatch(a, b) {
21354
- return a === b;
21355
- }
21356
-
21357
21396
  // src/commands/sessions/daemon/WindowsProxyState.ts
21358
21397
  var MAX_SCROLLBACK2 = 256 * 1024;
21359
21398
  var DEFAULT_CREATE_TIMEOUT_MS = 5e3;
@@ -21399,6 +21438,59 @@ function appendScrollback2(state, sessionId, data) {
21399
21438
  );
21400
21439
  }
21401
21440
 
21441
+ // src/commands/sessions/daemon/handleSessions.ts
21442
+ function handleSessions(state, msg) {
21443
+ state.windowsSessions = msg.sessions.map((s) => ({
21444
+ ...s,
21445
+ id: toWindowsSessionId(s.id)
21446
+ }));
21447
+ const live = new Set(state.windowsSessions.map((s) => s.id));
21448
+ for (const id2 of state.scrollback.keys())
21449
+ if (!live.has(id2)) state.scrollback.delete(id2);
21450
+ state.onSessionsChanged();
21451
+ }
21452
+
21453
+ // src/commands/sessions/daemon/buildHello.ts
21454
+ var ASSIST_VERSION = package_default.version;
21455
+ var PROTOCOL_VERSION = 1;
21456
+ function buildHello() {
21457
+ return { type: "hello", version: ASSIST_VERSION, protocol: PROTOCOL_VERSION };
21458
+ }
21459
+ function isHello(msg) {
21460
+ return msg.type === "hello" && typeof msg.version === "string" && (msg.protocol === void 0 || typeof msg.protocol === "number");
21461
+ }
21462
+ function helloCompatible(msg) {
21463
+ if (typeof msg.protocol === "number")
21464
+ return msg.protocol === PROTOCOL_VERSION;
21465
+ return msg.version === ASSIST_VERSION;
21466
+ }
21467
+
21468
+ // src/commands/sessions/daemon/windowsVersionCheck.ts
21469
+ function windowsVersionCheck() {
21470
+ return loadConfig().sessions?.windowsVersionCheck ?? "block";
21471
+ }
21472
+
21473
+ // src/commands/sessions/daemon/handleHello.ts
21474
+ function handleHello(state, msg) {
21475
+ if (!isHello(msg) || helloCompatible(msg)) return;
21476
+ const mode = windowsVersionCheck();
21477
+ const detail = `protocol ${msg.protocol ?? "legacy"} version ${msg.version} (wsl protocol ${PROTOCOL_VERSION} version ${ASSIST_VERSION})`;
21478
+ if (mode === "off") {
21479
+ daemonLog(
21480
+ `windows daemon protocol mismatch: ${detail}; check disabled (sessions.windowsVersionCheck=off), proceeding`
21481
+ );
21482
+ return;
21483
+ }
21484
+ if (mode === "warn") {
21485
+ daemonLog(
21486
+ `windows daemon protocol mismatch: ${detail}; proceeding with warning (sessions.windowsVersionCheck=warn)`
21487
+ );
21488
+ return;
21489
+ }
21490
+ daemonLog(`windows daemon protocol mismatch: ${detail}`);
21491
+ state.onVersionMismatch(msg.version);
21492
+ }
21493
+
21402
21494
  // src/commands/sessions/daemon/handleInbound.ts
21403
21495
  function handleInbound(state, line) {
21404
21496
  let msg;
@@ -21424,14 +21516,6 @@ var inbound = {
21424
21516
  state.broadcast(msg);
21425
21517
  }
21426
21518
  };
21427
- function handleHello(state, msg) {
21428
- if (isHello(msg) && !versionsMatch(msg.version, ASSIST_VERSION)) {
21429
- daemonLog(
21430
- `windows daemon version mismatch: ${msg.version} (wsl ${ASSIST_VERSION})`
21431
- );
21432
- state.onVersionMismatch(msg.version);
21433
- }
21434
- }
21435
21519
  function handleCreated(state, msg) {
21436
21520
  daemonLog(`windows daemon: created session ${nsId(msg)}`);
21437
21521
  const client = takePendingCreator(state);
@@ -21443,16 +21527,6 @@ function handleCreated(state, msg) {
21443
21527
  });
21444
21528
  else daemonLog("windows daemon: created with no pending creator (dropped)");
21445
21529
  }
21446
- function handleSessions(state, msg) {
21447
- state.windowsSessions = msg.sessions.map((s) => ({
21448
- ...s,
21449
- id: toWindowsSessionId(s.id)
21450
- }));
21451
- const live = new Set(state.windowsSessions.map((s) => s.id));
21452
- for (const id2 of state.scrollback.keys())
21453
- if (!live.has(id2)) state.scrollback.delete(id2);
21454
- state.onSessionsChanged();
21455
- }
21456
21530
  function handleOutput(state, msg) {
21457
21531
  const sessionId = nsId(msg);
21458
21532
  const data = msg.data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.326.1",
3
+ "version": "0.327.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {