@staff0rd/assist 0.551.0 → 0.553.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.
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.551.0",
9
+ version: "0.553.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -7564,7 +7564,7 @@ var BACKLOG_MARKERS = [
7564
7564
  function findBacklogUp(startDir) {
7565
7565
  let current = startDir;
7566
7566
  while (current !== dirname14(current)) {
7567
- if (BACKLOG_MARKERS.some((marker) => existsSync21(join17(current, marker)))) {
7567
+ if (BACKLOG_MARKERS.some((marker2) => existsSync21(join17(current, marker2)))) {
7568
7568
  return current;
7569
7569
  }
7570
7570
  current = dirname14(current);
@@ -9376,9 +9376,9 @@ function printPhaseTasks(phase) {
9376
9376
  // src/commands/backlog/show/printPlan.ts
9377
9377
  function phaseHeader(index3, name, isCurrent) {
9378
9378
  const phaseNumber = index3 + 1;
9379
- const marker = isCurrent ? chalk56.green("\u25B6 ") : " ";
9379
+ const marker2 = isCurrent ? chalk56.green("\u25B6 ") : " ";
9380
9380
  const label2 = isCurrent ? chalk56.green.bold(`Phase ${phaseNumber}: ${name}`) : `${chalk56.bold(`Phase ${phaseNumber}:`)} ${name}`;
9381
- return `${marker}${label2}`;
9381
+ return `${marker2}${label2}`;
9382
9382
  }
9383
9383
  function printPhaseSessions(sessions) {
9384
9384
  if (sessions.length === 0) return;
@@ -11933,6 +11933,60 @@ async function revertDiffFile(req, res) {
11933
11933
  }
11934
11934
  }
11935
11935
 
11936
+ // src/commands/sessions/web/revertDiffPaths.ts
11937
+ import { relative as relative4 } from "path";
11938
+
11939
+ // src/commands/sessions/web/readJsonBody.ts
11940
+ async function readJsonBody(req) {
11941
+ const chunks = [];
11942
+ for await (const chunk of req) chunks.push(chunk);
11943
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
11944
+ }
11945
+
11946
+ // src/commands/sessions/web/revertDiffPaths.ts
11947
+ function requestedPaths(body) {
11948
+ const paths = body.paths;
11949
+ if (!Array.isArray(paths)) return null;
11950
+ if (paths.some((path73) => typeof path73 !== "string")) return null;
11951
+ return paths;
11952
+ }
11953
+ async function revertDiffPaths(req, res) {
11954
+ const cwd = getCwdParam(req, res);
11955
+ if (!cwd) return;
11956
+ let paths;
11957
+ try {
11958
+ paths = requestedPaths(await readJsonBody(req));
11959
+ } catch {
11960
+ respondJson(res, 400, { error: "Invalid JSON body" });
11961
+ return;
11962
+ }
11963
+ if (!paths) {
11964
+ respondJson(res, 400, { error: "Missing paths" });
11965
+ return;
11966
+ }
11967
+ const root = repoRoot(cwd);
11968
+ const reverted = [];
11969
+ const failed2 = [];
11970
+ for (const path73 of paths) {
11971
+ const target = resolveWithinCwd(cwd, path73);
11972
+ if (!target) {
11973
+ failed2.push({ path: path73, error: "Path outside cwd" });
11974
+ continue;
11975
+ }
11976
+ const relativePath = relative4(root, target);
11977
+ try {
11978
+ await revertPathChanges(cwd, relativePath);
11979
+ reverted.push(relativePath);
11980
+ } catch (error) {
11981
+ failed2.push({
11982
+ path: relativePath,
11983
+ error: error instanceof Error ? error.message : "Failed to revert file"
11984
+ });
11985
+ }
11986
+ }
11987
+ respondJson(res, 200, { reverted, failed: failed2 });
11988
+ }
11989
+
11936
11990
  // src/commands/sessions/web/sessionLayout.ts
11937
11991
  function sessionLayout(_req, res) {
11938
11992
  const topBar = loadConfig().sessions?.topBar ?? true;
@@ -12292,13 +12346,6 @@ function isConfigWriteScope(value) {
12292
12346
  return value === "project" || value === "global" || value === "repo";
12293
12347
  }
12294
12348
 
12295
- // src/commands/sessions/web/readJsonBody.ts
12296
- async function readJsonBody(req) {
12297
- const chunks = [];
12298
- for await (const chunk of req) chunks.push(chunk);
12299
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
12300
- }
12301
-
12302
12349
  // src/commands/sessions/web/parseConfigWriteRequest.ts
12303
12350
  async function parseConfigWriteRequest(req) {
12304
12351
  let body;
@@ -12778,6 +12825,7 @@ var routes2 = {
12778
12825
  "GET /api/diff": diff2,
12779
12826
  "GET /api/diff-scopes": diffScopes,
12780
12827
  "POST /api/diff/revert": revertDiffFile,
12828
+ "POST /api/diff/revert-all": revertDiffPaths,
12781
12829
  "GET /api/file": fileContent,
12782
12830
  "POST /api/file": writeFileContent,
12783
12831
  "GET /api/files": listFiles,
@@ -14678,12 +14726,12 @@ async function launchMode(slashCommand, options2) {
14678
14726
  resumeSessionId,
14679
14727
  cwd: process.cwd()
14680
14728
  });
14681
- const marker = watchForMarker(child, { actOnDone: options2?.once });
14729
+ const marker2 = watchForMarker(child, { actOnDone: options2?.once });
14682
14730
  const exitCode = await awaitClaude(done2, `/${slashCommand}`);
14683
14731
  stopWatching();
14684
14732
  if (exitCode === CLAUDE_SPAWN_FAILED) return;
14685
14733
  await handleLaunchSignal(slashCommand, options2?.once);
14686
- if (exitCode !== 0 && !marker.killedOnMarker()) process.exitCode = exitCode;
14734
+ if (exitCode !== 0 && !marker2.killedOnMarker()) process.exitCode = exitCode;
14687
14735
  }
14688
14736
 
14689
14737
  // src/commands/backlog/refine.ts
@@ -17262,15 +17310,15 @@ function codeCommentConfirm(pin) {
17262
17310
  process.exitCode = 1;
17263
17311
  return;
17264
17312
  }
17265
- const marker = isHashCommentFile(state.file) ? "#" : "//";
17313
+ const marker2 = isHashCommentFile(state.file) ? "#" : "//";
17266
17314
  const indentSource = lines2[index3] ?? "";
17267
17315
  const indent3 = indentSource.match(/^\s*/)?.[0] ?? "";
17268
- lines2.splice(index3, 0, `${indent3}${marker} ${state.text}`);
17316
+ lines2.splice(index3, 0, `${indent3}${marker2} ${state.text}`);
17269
17317
  writeFileSync24(state.file, lines2.join("\n"));
17270
17318
  unlinkSync8(getPinStatePath(pin));
17271
17319
  console.log(
17272
17320
  chalk121.green(
17273
- `Inserted "${marker} ${state.text}" at ${state.file}:${state.line}`
17321
+ `Inserted "${marker2} ${state.text}" at ${state.file}:${state.line}`
17274
17322
  )
17275
17323
  );
17276
17324
  }
@@ -17332,7 +17380,7 @@ function codeCommentSet(file, line, text17) {
17332
17380
  return;
17333
17381
  }
17334
17382
  const hash = isHashCommentFile(file);
17335
- const marker = hash ? "#" : "//";
17383
+ const marker2 = hash ? "#" : "//";
17336
17384
  const validation = validateCommentText(text17, hash);
17337
17385
  if (!validation.ok) {
17338
17386
  console.error(chalk122.red(`Refused: ${validation.reason}`));
@@ -17357,7 +17405,7 @@ function codeCommentSet(file, line, text17) {
17357
17405
  }
17358
17406
  console.log(
17359
17407
  `A confirmation pin was sent to your desktop notifications.
17360
- To insert "${marker} ${validation.text}" at ${file}:${lineNumber}, run:
17408
+ To insert "${marker2} ${validation.text}" at ${file}:${lineNumber}, run:
17361
17409
  ${chalk122.cyan(" assist code-comment confirm <PIN>")}
17362
17410
  using the pin from that notification.`
17363
17411
  );
@@ -19826,9 +19874,9 @@ function selectCommentExtractor(filePath) {
19826
19874
  }
19827
19875
 
19828
19876
  // src/commands/editHook/decideCommentGuard.ts
19829
- function denyReason(marker) {
19830
- const blockClause = marker === "//" ? ", no block comments" : "";
19831
- return `This edit introduces a code comment (${marker}), which is blocked by the comment gate. Comments are a last resort \u2014 prefer a clearer name, a smaller function, or a test that makes the comment unnecessary. The comment must not appear in your edit itself. If this one line genuinely earns its keep, use the escape hatch: run \`assist code-comment set <file> <line> "<text>"\` (single line, max 50 chars${blockClause}) to get a pin, then \`assist code-comment confirm <pin>\` to insert it.`;
19877
+ function denyReason(marker2) {
19878
+ const blockClause = marker2 === "//" ? ", no block comments" : "";
19879
+ return `This edit introduces a code comment (${marker2}), which is blocked by the comment gate. Comments are a last resort \u2014 prefer a clearer name, a smaller function, or a test that makes the comment unnecessary. The comment must not appear in your edit itself. If this one line genuinely earns its keep, use the escape hatch: run \`assist code-comment set <file> <line> "<text>"\` (single line, max 50 chars${blockClause}) to get a pin, then \`assist code-comment confirm <pin>\` to insert it.`;
19832
19880
  }
19833
19881
  function decideCommentGuard(input, existingContent) {
19834
19882
  const { file_path, content } = input.tool_input;
@@ -20439,15 +20487,15 @@ function renderNodes(nodes, indent3) {
20439
20487
  function isListNode(node) {
20440
20488
  return node.type === "orderedList" || node.type === "bulletList";
20441
20489
  }
20442
- function renderListChild(child, indent3, pad, marker, isFirst) {
20490
+ function renderListChild(child, indent3, pad, marker2, isFirst) {
20443
20491
  if (isListNode(child)) return renderNodes([child], indent3 + 1);
20444
20492
  if (child.type !== "paragraph") return renderNode(child, indent3);
20445
20493
  const text17 = renderChildren(child, indent3);
20446
- return isFirst ? `${pad}${marker} ${text17}` : `${pad} ${text17}`;
20494
+ return isFirst ? `${pad}${marker2} ${text17}` : `${pad} ${text17}`;
20447
20495
  }
20448
- function renderListItem(node, indent3, marker) {
20496
+ function renderListItem(node, indent3, marker2) {
20449
20497
  const pad = " ".repeat(indent3);
20450
- return (node.content ?? []).map((child, i) => renderListChild(child, indent3, pad, marker, i === 0)).join("\n");
20498
+ return (node.content ?? []).map((child, i) => renderListChild(child, indent3, pad, marker2, i === 0)).join("\n");
20451
20499
  }
20452
20500
  function adfToText(doc) {
20453
20501
  return renderNodes([doc], 0);
@@ -29545,9 +29593,9 @@ function renderWatchReport({
29545
29593
  } else {
29546
29594
  lines2.push("| SHA | When | Subject |", "| --- | --- | --- |");
29547
29595
  for (const commit2 of commits2) {
29548
- const marker = isNew.has(commit2.sha) ? " \u2190 new" : "";
29596
+ const marker2 = isNew.has(commit2.sha) ? " \u2190 new" : "";
29549
29597
  lines2.push(
29550
- `| \`${commit2.short}\` | ${commit2.when} | ${escapeCell(commit2.subject)}${marker} |`
29598
+ `| \`${commit2.short}\` | ${commit2.when} | ${escapeCell(commit2.subject)}${marker2} |`
29551
29599
  );
29552
29600
  }
29553
29601
  }
@@ -31079,7 +31127,8 @@ function toSessionInfo(session) {
31079
31127
  design,
31080
31128
  verifying,
31081
31129
  pendingPrPreview,
31082
- undurable
31130
+ undurable,
31131
+ lastUserMessage
31083
31132
  } = session;
31084
31133
  return {
31085
31134
  ...toSessionRunInfo(session),
@@ -31103,6 +31152,7 @@ function toSessionInfo(session) {
31103
31152
  verifying,
31104
31153
  pendingPrPreview,
31105
31154
  undurable,
31155
+ lastUserMessage,
31106
31156
  joinable: joinRefusal(session) === void 0
31107
31157
  };
31108
31158
  }
@@ -32549,6 +32599,61 @@ function resolvedToolUseIds(events, afterIdx) {
32549
32599
  return ids;
32550
32600
  }
32551
32601
 
32602
+ // src/commands/sessions/shared/extractLastUserMessage.ts
32603
+ var MAX_LENGTH2 = 2048;
32604
+ var INTERRUPT_PREFIX2 = "[Request interrupted";
32605
+ function extractLastUserMessage(entries, maxLength = MAX_LENGTH2) {
32606
+ for (let i = entries.length - 1; i >= 0; i--) {
32607
+ const entry = entries[i];
32608
+ if (entry.isSidechain || entry.isMeta) continue;
32609
+ if (entry.type !== "user") continue;
32610
+ const raw = promptTextOrNull(entry);
32611
+ if (raw === null) continue;
32612
+ const message3 = renderPrompt(raw);
32613
+ if (message3) return cap(message3, maxLength);
32614
+ }
32615
+ return void 0;
32616
+ }
32617
+ function promptTextOrNull(entry) {
32618
+ const content = asRecord3(entry.message)?.content;
32619
+ if (typeof content === "string") return isInterrupt(content) ? null : content;
32620
+ if (!Array.isArray(content)) return null;
32621
+ const parts = [];
32622
+ for (const block of content) {
32623
+ const b = asRecord3(block);
32624
+ if (b?.type !== "text") continue;
32625
+ const text17 = typeof b.text === "string" ? b.text : "";
32626
+ if (isInterrupt(text17)) return null;
32627
+ parts.push(text17);
32628
+ }
32629
+ return parts.length === 0 ? null : parts.join("\n");
32630
+ }
32631
+ function renderPrompt(raw) {
32632
+ const command = marker(raw, "command-name");
32633
+ if (command) {
32634
+ const args = marker(raw, "command-args");
32635
+ const name = `/${command.replace(/^\/+/, "")}`;
32636
+ return args ? `${name} ${args}` : name;
32637
+ }
32638
+ return stripBlocks(raw);
32639
+ }
32640
+ function stripBlocks(raw) {
32641
+ return raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").replace(/<command-[^>]*>[\s\S]*?<\/command-[^>]*>/g, "").replace(/<local-command-[^>]*>[\s\S]*?<\/local-command-[^>]*>/g, "").trim();
32642
+ }
32643
+ function marker(raw, tag) {
32644
+ const match = raw.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
32645
+ return match ? match[1].trim() : "";
32646
+ }
32647
+ function cap(message3, maxLength) {
32648
+ return message3.length <= maxLength ? message3 : `${message3.slice(0, maxLength).trimEnd()}\u2026`;
32649
+ }
32650
+ function isInterrupt(value) {
32651
+ return value.trimStart().startsWith(INTERRUPT_PREFIX2);
32652
+ }
32653
+ function asRecord3(value) {
32654
+ return value && typeof value === "object" ? value : null;
32655
+ }
32656
+
32552
32657
  // src/commands/sessions/shared/readTranscriptTail.ts
32553
32658
  import * as fs46 from "fs";
32554
32659
  var DEFAULT_MAX_BYTES = 256 * 1024;
@@ -32632,9 +32737,19 @@ async function resolveTranscriptPath(session) {
32632
32737
  if (filePath) session.transcriptPath = filePath;
32633
32738
  return filePath;
32634
32739
  }
32635
- async function reconcileTranscriptStatus(session, onStatusChange) {
32636
- if (session.status === "done" || session.status === "error" || session.status === "stopped")
32637
- return;
32740
+ function updateLastUserMessage(session, entries, notify2) {
32741
+ const message3 = extractLastUserMessage(entries);
32742
+ if (message3 === session.lastUserMessage) return;
32743
+ session.lastUserMessage = message3;
32744
+ daemonLog(
32745
+ `session ${session.id} last user message: ${JSON.stringify(message3?.slice(0, 120) ?? null)}`
32746
+ );
32747
+ notify2?.();
32748
+ }
32749
+ function isFinished(session) {
32750
+ return session.status === "done" || session.status === "error" || session.status === "stopped";
32751
+ }
32752
+ async function reconcileTranscriptStatus(session, onStatusChange, notify2) {
32638
32753
  const filePath = await resolveTranscriptPath(session);
32639
32754
  if (!filePath) return;
32640
32755
  const entries = await readTranscriptTail(filePath);
@@ -32642,6 +32757,8 @@ async function reconcileTranscriptStatus(session, onStatusChange) {
32642
32757
  if (fingerprint !== null && fingerprint === session.transcriptFingerprint)
32643
32758
  return;
32644
32759
  session.transcriptFingerprint = fingerprint ?? void 0;
32760
+ updateLastUserMessage(session, entries, notify2);
32761
+ if (isFinished(session)) return;
32645
32762
  const derived = deriveTranscriptStatus(entries, {
32646
32763
  permissionActive: session.permissionActive
32647
32764
  });
@@ -32812,7 +32929,7 @@ function watchTranscript(session, notify2, onStatusChange) {
32812
32929
  let timer = null;
32813
32930
  const run4 = () => {
32814
32931
  timer = null;
32815
- void reconcileTranscriptStatus(session, onStatusChange);
32932
+ void reconcileTranscriptStatus(session, onStatusChange, notify2);
32816
32933
  startTranscriptTitleGeneration(session, notify2);
32817
32934
  };
32818
32935
  try {
@@ -33421,7 +33538,7 @@ function restoreBase(id, persisted) {
33421
33538
  }
33422
33539
 
33423
33540
  // src/commands/sessions/daemon/deferredSession.ts
33424
- function deferredSession(id, persisted, cap) {
33541
+ function deferredSession(id, persisted, cap2) {
33425
33542
  return {
33426
33543
  ...restoreBase(id, persisted),
33427
33544
  status: "stopped",
@@ -33435,13 +33552,13 @@ function deferredSession(id, persisted, cap) {
33435
33552
  runArgs: persisted.runArgs,
33436
33553
  activity: persisted.activity,
33437
33554
  restored: false,
33438
- scrollback: deferralNotice(persisted, cap)
33555
+ scrollback: deferralNotice(persisted, cap2)
33439
33556
  };
33440
33557
  }
33441
- function deferralNotice(persisted, cap) {
33558
+ function deferralNotice(persisted, cap2) {
33442
33559
  return [
33443
33560
  `\r
33444
- \x1B[33mNot resumed: restore was already at its cap of ${cap} session(s) (raise it with: assist config set sessions.maxLive <n> -g)\x1B[0m\r
33561
+ \x1B[33mNot resumed: restore was already at its cap of ${cap2} session(s) (raise it with: assist config set sessions.maxLive <n> -g)\x1B[0m\r
33445
33562
  `,
33446
33563
  `This card is the handle for it. Its command, working directory (${persisted.cwd})\r
33447
33564
  `,
@@ -33677,23 +33794,23 @@ function logRestoreError(persisted, error) {
33677
33794
  // src/commands/sessions/daemon/restoreAll.ts
33678
33795
  function restoreAll(spawner, sessions) {
33679
33796
  const persisted = loadPersistedSessions();
33680
- const cap = sessionLimits.maxRestore();
33797
+ const cap2 = sessionLimits.maxRestore();
33681
33798
  const restoredIds = /* @__PURE__ */ new Map();
33682
- const names = persisted.slice(0, cap).map((entry) => {
33799
+ const names = persisted.slice(0, cap2).map((entry) => {
33683
33800
  const id = restoreOne(entry, spawner, sessions);
33684
33801
  if (entry.id != null && id != null) restoredIds.set(entry.id, id);
33685
33802
  return entry.name;
33686
33803
  });
33687
- for (const over of persisted.slice(cap))
33688
- deferOne(over, spawner.recoveryCard, cap);
33804
+ for (const over of persisted.slice(cap2))
33805
+ deferOne(over, spawner.recoveryCard, cap2);
33689
33806
  remapLaunchedFrom(sessions, restoredIds);
33690
33807
  return names;
33691
33808
  }
33692
- function deferOne(persisted, spawnRecoveryCard, cap) {
33809
+ function deferOne(persisted, spawnRecoveryCard, cap2) {
33693
33810
  try {
33694
- const id = spawnRecoveryCard((sid) => deferredSession(sid, persisted, cap));
33811
+ const id = spawnRecoveryCard((sid) => deferredSession(sid, persisted, cap2));
33695
33812
  daemonLog(
33696
- `restore capped at ${cap} (sessions.maxLive): ${describePersistedSession(persisted)} deferred to stopped card ${id}`
33813
+ `restore capped at ${cap2} (sessions.maxLive): ${describePersistedSession(persisted)} deferred to stopped card ${id}`
33697
33814
  );
33698
33815
  } catch (error) {
33699
33816
  logDroppedSession(persisted, error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.551.0",
3
+ "version": "0.553.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {