@brainervirus/workit-cursor 8.7.0 → 8.8.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.
@@ -17906,12 +17906,48 @@ function writeGate(cwd, paths) {
17906
17906
  return NONE2;
17907
17907
  }
17908
17908
  }
17909
+ // packages/workit-core/src/hooks/stop.ts
17910
+ import { spawnSync as spawnSync12 } from "node:child_process";
17911
+
17909
17912
  // packages/workit-core/src/hooks/raw-git.ts
17910
17913
  import { spawnSync as spawnSync11 } from "node:child_process";
17914
+ import { createHash as createHash13 } from "node:crypto";
17915
+ import fs15 from "node:fs";
17916
+ import os4 from "node:os";
17917
+ import path19 from "node:path";
17918
+
17919
+ // packages/workit-core/src/hooks/session-marker.ts
17911
17920
  import { createHash as createHash12 } from "node:crypto";
17912
17921
  import fs14 from "node:fs";
17913
- import os4 from "node:os";
17914
17922
  import path18 from "node:path";
17923
+ var sessionMarkerFile = (input, session, store) => {
17924
+ let dir = store;
17925
+ if (dir === undefined) {
17926
+ const location = resolveStore(input.cwd);
17927
+ if (location instanceof Error || !location.shared)
17928
+ return null;
17929
+ dir = location.dir;
17930
+ }
17931
+ const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
17932
+ return path18.join(dir, "hooks", `skills-${name}.json`);
17933
+ };
17934
+ var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
17935
+ var readSessionMarker = (file) => {
17936
+ try {
17937
+ const value = JSON.parse(fs14.readFileSync(file, "utf8"));
17938
+ return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
17939
+ } catch {
17940
+ return { nudged: [], loaded: [], raw: [] };
17941
+ }
17942
+ };
17943
+ var writeSessionMarker = (file, marker) => {
17944
+ try {
17945
+ fs14.mkdirSync(path18.dirname(file), { recursive: true });
17946
+ fs14.writeFileSync(file, JSON.stringify(marker));
17947
+ } catch {}
17948
+ };
17949
+
17950
+ // packages/workit-core/src/hooks/raw-git.ts
17915
17951
  var NONE3 = { kind: "none" };
17916
17952
  var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
17917
17953
  var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
@@ -17928,12 +17964,12 @@ var GIT_VALUE_OPTIONS = new Set([
17928
17964
  "--config-env",
17929
17965
  "--super-prefix"
17930
17966
  ]);
17931
- var isAbsolute = (value) => path18.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
17967
+ var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
17932
17968
  var joinDir = (base, next) => {
17933
17969
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
17934
17970
  return;
17935
- const expanded = next === "~" || next.startsWith("~/") ? path18.join(os4.homedir(), next.slice(1)) : next;
17936
- return base === null || isAbsolute(expanded) ? expanded : path18.join(base, expanded);
17971
+ const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
17972
+ return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
17937
17973
  };
17938
17974
  var cdTarget = (words) => {
17939
17975
  const args = words.slice(1);
@@ -18029,6 +18065,37 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
18029
18065
  }
18030
18066
  return found;
18031
18067
  }
18068
+ var LAUNCHERS = new Set([
18069
+ "npx",
18070
+ "bunx",
18071
+ "pnpx",
18072
+ "-y",
18073
+ "--yes",
18074
+ "pnpm",
18075
+ "dlx",
18076
+ "exec",
18077
+ "env",
18078
+ "sudo",
18079
+ "command"
18080
+ ]);
18081
+ var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
18082
+ function workitInvocations(command, dialect = "posix", depth = 0) {
18083
+ const found = [];
18084
+ for (const { words } of segmentsOf(command, dialect)) {
18085
+ const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
18086
+ if (at < 0)
18087
+ continue;
18088
+ if (WORKIT_BIN.test(words[at])) {
18089
+ found.push(words.slice(at + 1));
18090
+ continue;
18091
+ }
18092
+ const shell = SHELLS.has(commandName(words[at]));
18093
+ const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
18094
+ if (depth < 3 && script !== undefined)
18095
+ found.push(...workitInvocations(script, "posix", depth + 1));
18096
+ }
18097
+ return found;
18098
+ }
18032
18099
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
18033
18100
  var pushAction = (args) => {
18034
18101
  let force = false;
@@ -18155,7 +18222,7 @@ var repoAt = (dir) => {
18155
18222
  return { location, dir, branch: resolveTaskKey(dir, location).branch };
18156
18223
  };
18157
18224
  var isWorkitWorkspace = (repo) => {
18158
- if (fs14.existsSync(repo.location.dir))
18225
+ if (fs15.existsSync(repo.location.dir))
18159
18226
  return true;
18160
18227
  try {
18161
18228
  return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
@@ -18186,17 +18253,17 @@ var headSha2 = (repo) => {
18186
18253
  if (!git || git.reftable)
18187
18254
  return null;
18188
18255
  try {
18189
- const head = fs14.readFileSync(path18.join(git.gitDir, "HEAD"), "utf8").trim();
18256
+ const head = fs15.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
18190
18257
  if (/^[0-9a-f]{40,64}$/.test(head))
18191
18258
  return head;
18192
18259
  const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
18193
18260
  if (!ref)
18194
18261
  return null;
18195
- const common = path18.dirname(repo.location.dir);
18262
+ const common = path19.dirname(repo.location.dir);
18196
18263
  try {
18197
- return fs14.readFileSync(path18.join(common, ref), "utf8").trim() || null;
18264
+ return fs15.readFileSync(path19.join(common, ref), "utf8").trim() || null;
18198
18265
  } catch {}
18199
- const packed = fs14.readFileSync(path18.join(common, "packed-refs"), "utf8");
18266
+ const packed = fs15.readFileSync(path19.join(common, "packed-refs"), "utf8");
18200
18267
  return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
18201
18268
  } catch {
18202
18269
  return null;
@@ -18233,7 +18300,7 @@ var actionsIn = (cwd, command, dialect) => {
18233
18300
  const action = rawAction(invocation);
18234
18301
  if (!action)
18235
18302
  continue;
18236
- const dir = path18.resolve(cwd, invocation.dir ?? ".");
18303
+ const dir = path19.resolve(cwd, invocation.dir ?? ".");
18237
18304
  if (!repos.has(dir)) {
18238
18305
  const repo = repoAt(dir);
18239
18306
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
@@ -18251,12 +18318,31 @@ var inWorkitWorkspace = (dir) => {
18251
18318
  var rawDelivery = (cwd, command, dialect) => mentionsRawTool(command) && actionsIn(cwd, command, dialect).some(({ action }) => action.kind !== "commit" && action.kind !== "pr-read");
18252
18319
  var mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command);
18253
18320
  var dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix";
18254
- function rawGitPre(input, command) {
18321
+ var nudgeKind = (action) => action.kind === "commit" && action.amend ? "commit-amend" : action.kind === "push" && action.blindForce ? "push-force" : action.kind;
18322
+ var unseenNudges = (input, nudges, store) => {
18323
+ const session = input.session.id;
18324
+ const file = session ? sessionMarkerFile(input, session, store) : null;
18325
+ if (!file)
18326
+ return [...nudges.values()];
18327
+ const marker = readSessionMarker(file);
18328
+ const agent = input.session.agentId ? `${input.session.agentId}:` : "";
18329
+ const fresh = [...nudges.keys()].filter((kind) => !marker.raw.includes(agent + kind));
18330
+ if (fresh.length)
18331
+ writeSessionMarker(file, {
18332
+ ...marker,
18333
+ raw: [...marker.raw, ...fresh.map((kind) => agent + kind)]
18334
+ });
18335
+ return fresh.map((kind) => nudges.get(kind));
18336
+ };
18337
+ function rawGitPre(input, command, options = {}) {
18255
18338
  try {
18256
18339
  if (!mentionsRawTool(command))
18257
18340
  return NONE3;
18258
- const nudges = [];
18259
- for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input))) {
18341
+ const dialect = dialectOf(input);
18342
+ const nudges = new Map;
18343
+ let store = null;
18344
+ for (const { action, repo } of actionsIn(input.cwd, command, dialect)) {
18345
+ store ??= repo.location.dir;
18260
18346
  if (action.kind === "merge")
18261
18347
  return deny(refusal(action, null));
18262
18348
  if (action.kind === "push")
@@ -18266,10 +18352,15 @@ function rawGitPre(input, command) {
18266
18352
  return deny(refusal(action, branch));
18267
18353
  }
18268
18354
  const nudge = nudgeFor(action, input.host, input.session.id);
18269
- if (nudge && !nudges.includes(nudge))
18270
- nudges.push(nudge);
18355
+ if (nudge && !nudges.has(nudgeKind(action)))
18356
+ nudges.set(nudgeKind(action), nudge);
18271
18357
  }
18272
- return nudges.length ? { kind: "context", text: nudges.join(`
18358
+ if (options.nudge === false || !store || nudges.size === 0)
18359
+ return NONE3;
18360
+ if (workitInvocations(command, dialect).length > 0)
18361
+ return NONE3;
18362
+ const shown = unseenNudges(input, nudges, store);
18363
+ return shown.length ? { kind: "context", text: shown.join(`
18273
18364
  `) } : NONE3;
18274
18365
  } catch {
18275
18366
  return NONE3;
@@ -18278,8 +18369,8 @@ function rawGitPre(input, command) {
18278
18369
  var deny = (reason) => ({ kind: "deny", reason, unblock: null });
18279
18370
  var NEXT_COMMAND = "next";
18280
18371
  var MARKER_TTL_SECONDS = 3600;
18281
- var markerFile = (repo, session, key) => path18.join(repo.location.dir, "hooks", `commit-${createHash12("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
18282
- var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash12("sha256").update(command).digest("hex")}`;
18372
+ var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash13("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
18373
+ var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash13("sha256").update(command).digest("hex")}`;
18283
18374
  function noteRawCommit(input, command, key) {
18284
18375
  try {
18285
18376
  if (!input.session.id || !/\bcommit\b/.test(command))
@@ -18289,8 +18380,8 @@ function noteRawCommit(input, command, key) {
18289
18380
  if (action.kind !== "commit")
18290
18381
  continue;
18291
18382
  const file = markerFile(repo, input.session.id, key);
18292
- fs14.mkdirSync(path18.dirname(file), { recursive: true });
18293
- fs14.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
18383
+ fs15.mkdirSync(path19.dirname(file), { recursive: true });
18384
+ fs15.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
18294
18385
  `);
18295
18386
  }
18296
18387
  } catch {}
@@ -18313,11 +18404,11 @@ var headCommit = (dir) => {
18313
18404
  var settle2 = (input, repo, file) => {
18314
18405
  let marker;
18315
18406
  try {
18316
- marker = JSON.parse(fs14.readFileSync(file, "utf8"));
18407
+ marker = JSON.parse(fs15.readFileSync(file, "utf8"));
18317
18408
  } catch {
18318
18409
  return;
18319
18410
  }
18320
- fs14.rmSync(file, { force: true });
18411
+ fs15.rmSync(file, { force: true });
18321
18412
  const session = input.session.id;
18322
18413
  const now = Date.now() / 1000;
18323
18414
  if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
@@ -18366,15 +18457,165 @@ function settlePendingCommit(input) {
18366
18457
  if (!repo)
18367
18458
  return;
18368
18459
  const file = markerFile(repo, input.session.id, NEXT_COMMAND);
18369
- if (fs14.existsSync(file))
18460
+ if (fs15.existsSync(file))
18370
18461
  settle2(input, repo, file);
18371
18462
  } catch {}
18372
18463
  }
18373
- // packages/workit-core/src/hooks/skill-nudge.ts
18374
- import { createHash as createHash13 } from "node:crypto";
18375
- import fs15 from "node:fs";
18376
- import path19 from "node:path";
18377
18464
 
18465
+ // packages/workit-core/src/hooks/stop.ts
18466
+ var NONE4 = { kind: "none" };
18467
+ var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
18468
+ var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
18469
+ var YES_NO = /[[(]\s*y(?:es)?\s*\/\s*n(?:o)?\s*[\])]/i;
18470
+ var LIST_ITEM = /^\s*(?:[-*+•]|\d+[.)]|[a-z][.)])\s+/i;
18471
+ var asksUser = (message) => {
18472
+ if (!message)
18473
+ return false;
18474
+ const paragraphs = message.replace(/```[\s\S]*?(?:```|$)/g, `
18475
+
18476
+ `).split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
18477
+ while (paragraphs.length > 1 && paragraphs.at(-1).split(`
18478
+ `).every((line) => LIST_ITEM.test(line)))
18479
+ paragraphs.pop();
18480
+ const tail = paragraphs.slice(-2).join(`
18481
+ `);
18482
+ return QUESTION.test(tail) || OFFER.test(tail) || YES_NO.test(tail);
18483
+ };
18484
+ var short = (sha) => (sha ?? "?").slice(0, 12);
18485
+ var gitList = (cwd, args) => {
18486
+ const run = spawnSync12("git", args, {
18487
+ cwd,
18488
+ encoding: "utf8",
18489
+ timeout: 3000,
18490
+ windowsHide: true,
18491
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
18492
+ });
18493
+ return run.status === 0 ? run.stdout.split(`
18494
+ `).filter(Boolean) : null;
18495
+ };
18496
+ var gitLine = (cwd, args) => gitList(cwd, args)?.[0] ?? null;
18497
+ var sessionOf = (row) => (typeof row.session === "string" ? row.session : null) ?? row.actor.session;
18498
+ var prState = (row) => typeof row.state === "string" ? row.state : "open";
18499
+ var ended = (row) => row.observer === "workit_cli" && (row.type === "pr.merged" || row.type === "pr.status" && prState(row) !== "open");
18500
+ var openPr = (rows) => {
18501
+ const seen = rows.findLast((row) => (row.type === "pr.created" || row.type === "pr.status") && row.observer === "workit_cli" && typeof row.pr === "number");
18502
+ if (!seen || ended(seen))
18503
+ return null;
18504
+ const closed = rows.some((row) => ended(row) && row.pr === seen.pr && row.seq > seen.seq);
18505
+ return closed ? null : seen.pr;
18506
+ };
18507
+ var mergedHeads = (rows) => [
18508
+ ...new Set(rows.filter((row) => row.observer === "workit_cli" && typeof row.head === "string" && (row.type === "pr.merged" || row.type === "pr.status" && prState(row) === "merged")).map((row) => row.head))
18509
+ ].slice(-3);
18510
+ var localCommit = (cwd, sha) => /^[0-9a-f]{7,64}$/.test(sha) && gitList(cwd, ["cat-file", "-e", `${sha}^{commit}`]) !== null;
18511
+ var upstreamGone = (cwd, branch) => gitLine(cwd, ["for-each-ref", "--format=%(upstream:track)", `refs/heads/${branch}`]) === "[gone]";
18512
+ var landed = (cwd, branch, rows, head, mine) => {
18513
+ if (!head)
18514
+ return false;
18515
+ const merged = mergedHeads(rows);
18516
+ if (merged.length === 0)
18517
+ return upstreamGone(cwd, branch);
18518
+ if (merged.includes(head))
18519
+ return true;
18520
+ if (mine.size === 0)
18521
+ return false;
18522
+ return merged.some((at) => {
18523
+ const after = gitList(cwd, ["rev-list", `--max-count=${UNPUSHED_SCAN}`, "HEAD", "--not", at]);
18524
+ return after !== null && !after.some((sha) => mine.has(sha));
18525
+ });
18526
+ };
18527
+ var UNPUSHED_SCAN = 500;
18528
+ var unpushed = (cwd, branch, endpoint, mine, merged) => {
18529
+ const remote = pushRemoteName(cwd, branch);
18530
+ if (!remote || mine.size === 0)
18531
+ return null;
18532
+ const listed = gitList(cwd, [
18533
+ "rev-list",
18534
+ `--max-count=${UNPUSHED_SCAN}`,
18535
+ "HEAD",
18536
+ "--not",
18537
+ `--remotes=${remote}`,
18538
+ ...merged.filter((sha) => localCommit(cwd, sha))
18539
+ ]);
18540
+ const count = (listed ?? []).filter((sha) => mine.has(sha)).length;
18541
+ if (count === 0)
18542
+ return null;
18543
+ return {
18544
+ kind: "unpushed",
18545
+ reason: `${branch} has ${count} commit(s) of this session not on ${remote}, and this workspace's endpoint is \`${endpoint}\` (\`workit grant show\`): push them with \`workit git push\`${endpoint === "pr" ? ", then open or update the PR (`workit pr create --fill`)" : ""}.`
18546
+ };
18547
+ };
18548
+ var redChecks = (rows, pr, head, endpoint) => {
18549
+ const status = rows.findLast((row) => row.type === "pr.status" && row.observer === "workit_cli" && row.pr === pr);
18550
+ const state = status && status.head === head && prState(status) === "open" ? status.checks : null;
18551
+ if (state !== "failing" && state !== "pending")
18552
+ return null;
18553
+ return {
18554
+ kind: "checks",
18555
+ reason: `PR #${pr}'s checks were last seen ${state} at ${short(head)}, and this workspace's endpoint is \`${endpoint}\`: run \`workit ci wait --pr ${pr}\`${state === "failing" ? " after fixing the failures `workit pr status` lists (then push)" : ""}.`
18556
+ };
18557
+ };
18558
+ var unverified = (cwd, branch, all, descriptor) => {
18559
+ const check = checkVerdicts(cwd, branch, all);
18560
+ if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
18561
+ return null;
18562
+ const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
18563
+ const verifier = `${descriptor.subagents.agentPrefix}verifier`;
18564
+ return {
18565
+ kind: "verdict",
18566
+ reason: `the head ${short(check.head)} of ${branch}, which this session delivered, has ${has}: if no verifier is already running on it, hand it to a session that did not author it (the ${verifier} agent records \`workit ledger verdict <result> --branch ${branch} --how "<evidence>"\`), or tell the user it is unverified. Never record a verdict on your own work.`
18567
+ };
18568
+ };
18569
+ function stopObligation(input, descriptor) {
18570
+ try {
18571
+ const session = input.session.id;
18572
+ if (!session || input.session.agentId || !inWorkitWorkspace(input.cwd))
18573
+ return null;
18574
+ const branch = currentBranch(input.cwd);
18575
+ if (!branch || guardedBranch(input.cwd, branch))
18576
+ return null;
18577
+ const ledger = readLedger(input.cwd);
18578
+ if (!ledger.ok)
18579
+ return null;
18580
+ const all = ledger.value.rows;
18581
+ const rows = all.filter((row) => row.branch === branch);
18582
+ const mine = rows.filter((row) => sessionOf(row) === session);
18583
+ const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
18584
+ const delivered = mine.some((row) => row.observer === "workit_cli" && (row.type === "pr.created" || row.type === "delivery.verified"));
18585
+ if (commits.size === 0 && !delivered)
18586
+ return null;
18587
+ const head = gitLine(input.cwd, ["rev-parse", "HEAD"]);
18588
+ if (landed(input.cwd, branch, rows, head, commits))
18589
+ return null;
18590
+ const endpoint = resolveAutonomy(input.cwd).effectiveEndpoint;
18591
+ if (endpoint !== "commit") {
18592
+ const left = unpushed(input.cwd, branch, endpoint, commits, mergedHeads(rows));
18593
+ if (left)
18594
+ return left;
18595
+ }
18596
+ const pr = openPr(rows);
18597
+ if ((endpoint === "green" || endpoint === "merged") && pr !== null && head) {
18598
+ const left = redChecks(rows, pr, head, endpoint);
18599
+ if (left)
18600
+ return left;
18601
+ }
18602
+ return delivered ? unverified(input.cwd, branch, all, descriptor) : null;
18603
+ } catch {
18604
+ return null;
18605
+ }
18606
+ }
18607
+ function stopDecision(input, descriptor) {
18608
+ const event = input.event;
18609
+ if (event.kind !== "stop" || event.stopHookActive)
18610
+ return NONE4;
18611
+ if (!event.lastMessage?.trim() || asksUser(event.lastMessage))
18612
+ return NONE4;
18613
+ const left = stopObligation(input, descriptor);
18614
+ return left ? {
18615
+ kind: "continue",
18616
+ reason: `Workit: before stopping, ${left.reason} If this cannot be done now, say so to the user and stop.`
18617
+ } : NONE4;
18618
+ }
18378
18619
  // packages/workit-core/src/core/skill-manifests.ts
18379
18620
  var WORKIT_METHOD_SKILLS = [
18380
18621
  "workit-shape",
@@ -18481,27 +18722,6 @@ var promptTrigger = (prompt) => {
18481
18722
  }
18482
18723
  return null;
18483
18724
  };
18484
- var markerFile2 = (input, session) => {
18485
- const location = resolveStore(input.cwd);
18486
- if (location instanceof Error || !location.shared)
18487
- return null;
18488
- const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
18489
- return path19.join(location.dir, "hooks", `skills-${name}.json`);
18490
- };
18491
- var readMarker = (file) => {
18492
- try {
18493
- const value = JSON.parse(fs15.readFileSync(file, "utf8"));
18494
- return { nudged: value.nudged ?? [], loaded: value.loaded ?? [] };
18495
- } catch {
18496
- return { nudged: [], loaded: [] };
18497
- }
18498
- };
18499
- var writeMarker = (file, marker) => {
18500
- try {
18501
- fs15.mkdirSync(path19.dirname(file), { recursive: true });
18502
- fs15.writeFileSync(file, JSON.stringify(marker));
18503
- } catch {}
18504
- };
18505
18725
  var loadedSkills = (cwd, session) => {
18506
18726
  const ledger = readLedger(cwd);
18507
18727
  const loaded = new Set;
@@ -18549,13 +18769,13 @@ function recordSkillLoad(input, name, via) {
18549
18769
  return;
18550
18770
  if (!inWorkitWorkspace(input.cwd))
18551
18771
  return;
18552
- const file = markerFile2(input, session);
18772
+ const file = sessionMarkerFile(input, session);
18553
18773
  if (!file)
18554
18774
  return;
18555
- const marker = readMarker(file);
18775
+ const marker = readSessionMarker(file);
18556
18776
  if (marker.loaded.includes(skill))
18557
18777
  return;
18558
- writeMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
18778
+ writeSessionMarker(file, { ...marker, loaded: [...marker.loaded, skill] });
18559
18779
  if (loadedSkills(input.cwd, session).has(skill))
18560
18780
  return;
18561
18781
  appendHookObserved(input.cwd, {
@@ -18567,23 +18787,24 @@ function recordSkillLoad(input, name, via) {
18567
18787
  } catch {}
18568
18788
  }
18569
18789
  var nudgeOnce = (input, session, skill, line) => {
18570
- const file = markerFile2(input, session);
18790
+ const file = sessionMarkerFile(input, session);
18571
18791
  if (!file)
18572
18792
  return null;
18573
- const marker = readMarker(file);
18793
+ const marker = readSessionMarker(file);
18574
18794
  if (marker.nudged.includes(skill) || marker.loaded.includes(skill))
18575
18795
  return null;
18576
18796
  if (loadedSkills(input.cwd, session).has(skill))
18577
18797
  return null;
18578
- writeMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
18798
+ writeSessionMarker(file, { ...marker, nudged: [...marker.nudged, skill] });
18579
18799
  return line;
18580
18800
  };
18581
18801
  var nudgeText = (input, skill, why) => USER_INVOKED.has(skill) ? `Workit: ${why} looks like ${skill} work, which the user starts; offer it (/${skill.replace(/^workit-/, "wk-")}) rather than loading it.` : `Workit: ${why} looks like ${skill} work; if it is, ${skillLoadWording(SKILL_HOST[input.host], skill)} first.`;
18582
18802
  var promptLoads = (prompt) => /^\s*\/(?:skill:)?([\w:-]+)/.exec(prompt)?.[1] ?? /^\s*<skill name="([\w:-]+)"/.exec(prompt)?.[1] ?? /^\s*Load the (workit-[a-z-]+) skill with the skill tool\b/.exec(prompt)?.[1] ?? null;
18803
+ var relayedMessage = (prompt) => /^\s*(?:Another Claude session sent a message\b|<agent-message\s+from=|\[SYSTEM NOTIFICATION\b|<task-notification>)/.test(prompt);
18583
18804
  function promptNudge(input, prompt) {
18584
18805
  try {
18585
18806
  const session = input.session.id;
18586
- if (!prompt || !session || input.session.agentId)
18807
+ if (!prompt || !session || input.session.agentId || relayedMessage(prompt))
18587
18808
  return null;
18588
18809
  const loads = promptLoads(prompt);
18589
18810
  if (loads) {
@@ -18599,32 +18820,7 @@ function promptNudge(input, prompt) {
18599
18820
  }
18600
18821
  }
18601
18822
  var WORKIT_DELIVERY = /^(?:pr (?:create|merge)|ci wait|git push)\b/;
18602
- var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
18603
- var LAUNCHERS = new Set([
18604
- "npx",
18605
- "bunx",
18606
- "pnpx",
18607
- "-y",
18608
- "--yes",
18609
- "pnpm",
18610
- "dlx",
18611
- "exec",
18612
- "env",
18613
- "sudo",
18614
- "command"
18615
- ]);
18616
- var runsWorkitDelivery = (command, dialect, depth = 0) => segmentsOf(command, dialect).some(({ words }) => {
18617
- const at = words.findIndex((word) => !/^\w+=/.test(word) && !LAUNCHERS.has(word));
18618
- if (at >= 0 && /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/.test(words[at])) {
18619
- const args = words.slice(at + 1);
18620
- if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run"))
18621
- return false;
18622
- return WORKIT_DELIVERY.test(args.join(" "));
18623
- }
18624
- const shell = SHELLS2.has(words[at]?.split(/[\\/]/).at(-1) ?? "");
18625
- const script = shell && words[at + 1] === "-c" ? words[at + 2] : undefined;
18626
- return depth < 3 && script !== undefined && runsWorkitDelivery(script, "posix", depth + 1);
18627
- });
18823
+ var runsWorkitDelivery = (command, dialect) => workitInvocations(command, dialect).some((args) => !args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run") && WORKIT_DELIVERY.test(args.join(" ")));
18628
18824
  var isDeliveryCommand = (cwd, command, dialect = "posix") => rawDelivery(cwd, command, dialect) || runsWorkitDelivery(command, dialect) && inWorkitWorkspace(cwd);
18629
18825
  function shipNudge(input, command) {
18630
18826
  try {
@@ -18664,7 +18860,7 @@ var existingDirectory = (value) => {
18664
18860
  return null;
18665
18861
  }
18666
18862
  };
18667
- var SHELLS3 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
18863
+ var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
18668
18864
  var SCRIPT_FLAG2 = /^(?:-\w*c|-command|\/c)$/i;
18669
18865
  var quoteArg = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);
18670
18866
  var commandText = (value) => {
@@ -18676,7 +18872,7 @@ var commandText = (value) => {
18676
18872
  if (!argv.every((arg) => typeof arg === "string"))
18677
18873
  return null;
18678
18874
  const shell = argv[0].split(/[\\/]/).at(-1)?.replace(/\.exe$/i, "").toLowerCase() ?? "";
18679
- if (SHELLS3.has(shell))
18875
+ if (SHELLS2.has(shell))
18680
18876
  for (let index = 1;index < argv.length - 1; index++) {
18681
18877
  if (SCRIPT_FLAG2.test(argv[index]))
18682
18878
  return nonEmpty2(argv[index + 1]) ? argv[index + 1] : null;
@@ -18724,7 +18920,7 @@ var writeTargets = (input) => {
18724
18920
  };
18725
18921
  // packages/workit-core/src/hooks/handle.ts
18726
18922
  var usable2 = (value) => value === "native" || value === "partial";
18727
- var NONE4 = { kind: "none" };
18923
+ var NONE5 = { kind: "none" };
18728
18924
  var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
18729
18925
  var offered = new Set;
18730
18926
  var ROLES = {
@@ -18738,13 +18934,20 @@ var workitRole = (descriptor, agentType) => {
18738
18934
  return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
18739
18935
  };
18740
18936
  var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
18741
- var subagentStartText = (session, descriptor, event) => {
18937
+ var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
18938
+ var ISOLATED_GIT = 'Run git as plain, separate commands inside your worktree (`git switch -c <branch> <base>`, `git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a commit-msg hook that adds the trailer), never `workit git …`: Claude Code\'s worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (`workit check`, `workit ledger`, `workit pr`).';
18939
+ var subagentStartText = (input, descriptor, event) => {
18940
+ const session = input.session.id ?? null;
18742
18941
  const role = workitRole(descriptor, event.agentType);
18743
18942
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
18744
- if (role === "implementer") {
18745
- const where = descriptor.subagents.worktreeIsolation === "native" ? "working in its own git worktree: it may edit and commit there" : "as an implementer: it edits and commits only in its own git worktree (the lead makes one with `workit fanout worktree create <slice>`), never in the lead's checkout";
18746
- return `Workit observed ${who} ${where}, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
18747
- }
18943
+ const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
18944
+ if (role === "implementer" && isolated)
18945
+ return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} Use a policy-compliant branch (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
18946
+ if (isolated)
18947
+ return `${ISOLATED_GIT}
18948
+ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
18949
+ if (role === "implementer")
18950
+ return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
18748
18951
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
18749
18952
  if (role !== "judge" || !event.agentId)
18750
18953
  return readOnly;
@@ -18757,7 +18960,7 @@ function handleHook(input, deps) {
18757
18960
  switch (event.kind) {
18758
18961
  case "session.start": {
18759
18962
  if (!usable2(descriptor.context.sessionStart))
18760
- return NONE4;
18963
+ return NONE5;
18761
18964
  const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
18762
18965
  const offer = event.source === "startup" && (key === null || !offered.has(key));
18763
18966
  if (offer && key !== null)
@@ -18772,42 +18975,44 @@ function handleHook(input, deps) {
18772
18975
  }
18773
18976
  case "context.turn": {
18774
18977
  if (!usable2(descriptor.context.perTurn))
18775
- return NONE4;
18978
+ return NONE5;
18776
18979
  const current = turnContextText(input, descriptor);
18777
18980
  const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
18778
- return withContextLine(text ? { kind: "context", text } : NONE4, promptNudge(input, event.prompt));
18981
+ return withContextLine(text ? { kind: "context", text } : NONE5, promptNudge(input, event.prompt));
18779
18982
  }
18780
18983
  case "shell.pre": {
18781
18984
  const skillRead = skillReadIn(event.command, event.dialect);
18782
18985
  if (skillRead)
18783
18986
  recordSkillLoad(input, skillRead, "read");
18784
18987
  const canDeny = usable2(descriptor.shellPolicy.deny);
18785
- const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE4;
18988
+ const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
18786
18989
  if (policy.kind !== "none")
18787
18990
  return policy;
18788
18991
  const postTool = usable2(descriptor.events["shell.post"].support);
18789
18992
  if (!postTool)
18790
18993
  settlePendingCommit(input);
18791
- const raw = canDeny ? rawGitPre(input, event.command) : NONE4;
18994
+ const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
18792
18995
  if (raw.kind === "deny")
18793
18996
  return raw;
18794
18997
  const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
18795
- const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE4;
18998
+ const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
18796
18999
  if (gate.kind === "deny")
18797
19000
  return gate;
18798
19001
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
18799
- return withContextLine(raw, shipNudge(input, event.command));
19002
+ if (descriptor.transport !== "hook-process")
19003
+ return NONE5;
19004
+ return withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, shipNudge(input, event.command));
18800
19005
  }
18801
19006
  case "shell.post":
18802
19007
  if (usable2(descriptor.events["shell.post"].support))
18803
19008
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
18804
- return NONE4;
19009
+ return NONE5;
18805
19010
  case "write.pre":
18806
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE4;
19011
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
18807
19012
  case "subagent.start":
18808
19013
  return {
18809
19014
  kind: "context",
18810
- text: subagentStartText(input.session.id ?? null, descriptor, event)
19015
+ text: subagentStartText(input, descriptor, event)
18811
19016
  };
18812
19017
  case "compact.pre":
18813
19018
  return {
@@ -18817,19 +19022,20 @@ function handleHook(input, deps) {
18817
19022
  case "tool.pre":
18818
19023
  if (event.skill)
18819
19024
  recordSkillLoad(input, event.skill, "tool");
18820
- return NONE4;
19025
+ return NONE5;
19026
+ case "stop":
19027
+ return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE5;
18821
19028
  case "subagent.stop":
18822
19029
  case "prompt.submit":
18823
- case "stop":
18824
- return NONE4;
19030
+ return NONE5;
18825
19031
  }
18826
19032
  }
18827
19033
  var failureDecision = (descriptor, event, error) => {
18828
19034
  if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
18829
- return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE4;
19035
+ return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE5;
18830
19036
  if (event === "session.start" || event === "subagent.start")
18831
19037
  return { kind: "context", text: `[workit diagnostic: ${error}]` };
18832
- return NONE4;
19038
+ return NONE5;
18833
19039
  };
18834
19040
  var dispatchHook = (adapter, raw, env = process.env) => {
18835
19041
  const parsed = adapter.parse(raw, env);
@@ -18871,7 +19077,7 @@ var CLAUDE_CODE_DESCRIPTOR = {
18871
19077
  "prompt.submit": { support: "native", native: "UserPromptSubmit" },
18872
19078
  "subagent.stop": { support: "none", native: null },
18873
19079
  "compact.pre": { support: "none", native: null },
18874
- stop: { support: "none", native: null }
19080
+ stop: { support: "native", native: "Stop" }
18875
19081
  },
18876
19082
  shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
18877
19083
  context: {
@@ -19137,7 +19343,7 @@ var CODEX_DESCRIPTOR = {
19137
19343
  "subagent.stop": { support: "native", native: "SubagentStop" },
19138
19344
  "prompt.submit": { support: "native", native: "UserPromptSubmit" },
19139
19345
  "compact.pre": { support: "none", native: null },
19140
- stop: { support: "none", native: null }
19346
+ stop: { support: "native", native: "Stop" }
19141
19347
  },
19142
19348
  shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
19143
19349
  context: {
@@ -19157,7 +19363,7 @@ var CODEX_DESCRIPTOR = {
19157
19363
  },
19158
19364
  provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "native" },
19159
19365
  interaction: { questions: "none", writeBoundary: "partial" },
19160
- stopControl: "undocumented",
19366
+ stopControl: "native",
19161
19367
  shellAvailable: "native",
19162
19368
  perEventCost: "low",
19163
19369
  capabilities: [
@@ -19224,7 +19430,8 @@ var EVENTS2 = {
19224
19430
  PreToolUse: "shell.pre",
19225
19431
  PostToolUse: "shell.post",
19226
19432
  SubagentStart: "subagent.start",
19227
- SubagentStop: "subagent.stop"
19433
+ SubagentStop: "subagent.stop",
19434
+ Stop: "stop"
19228
19435
  };
19229
19436
  var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
19230
19437
  var isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase());
@@ -19263,6 +19470,10 @@ var parseCodexHookInput = (value) => {
19263
19470
  ...nonEmpty2(value.tool_use_id) ? { tool_use_id: value.tool_use_id } : {},
19264
19471
  ...nonEmpty2(value.agent_id) ? { agent_id: value.agent_id } : {},
19265
19472
  ...nonEmpty2(value.agent_type) ? { agent_type: value.agent_type } : {},
19473
+ ...event === "Stop" ? {
19474
+ last_assistant_message: optionalText(value.last_assistant_message),
19475
+ stop_hook_active: value.stop_hook_active === true
19476
+ } : {},
19266
19477
  ...event === "SubagentStop" ? {
19267
19478
  agent_transcript_path: optionalText(value.agent_transcript_path),
19268
19479
  last_assistant_message: optionalText(value.last_assistant_message),
@@ -19315,13 +19526,21 @@ var protocolEvent = (input) => {
19315
19526
  lastMessage: input.last_assistant_message ?? null,
19316
19527
  stopHookActive: input.stop_hook_active ?? false
19317
19528
  };
19529
+ case "Stop":
19530
+ return {
19531
+ kind: "stop",
19532
+ lastMessage: input.last_assistant_message ?? null,
19533
+ stopHookActive: input.stop_hook_active ?? false
19534
+ };
19318
19535
  }
19319
19536
  };
19320
19537
  var output = (event, extra = {}) => ({
19321
19538
  hookSpecificOutput: { hookEventName: event, ...extra }
19322
19539
  });
19323
19540
  var render2 = (decision, native) => {
19324
- if (native === "SubagentStop" || native === null)
19541
+ if (native === "Stop" && decision.kind === "continue")
19542
+ return { json: { decision: "block", reason: decision.reason }, exitCode: 0 };
19543
+ if (native === "SubagentStop" || native === "Stop" || native === null)
19325
19544
  return { json: {}, exitCode: 0 };
19326
19545
  if (decision.kind === "deny")
19327
19546
  return {
@@ -19368,6 +19587,7 @@ var codexAdapter = {
19368
19587
  };
19369
19588
 
19370
19589
  // packages/workit-core/src/hooks/hosts/cursor.ts
19590
+ import fs16 from "node:fs";
19371
19591
  import path21 from "node:path";
19372
19592
  var undocumented = { support: "undocumented", native: null };
19373
19593
  var none = { support: "none", native: null };
@@ -19388,7 +19608,7 @@ var CURSOR_DESCRIPTOR = {
19388
19608
  "subagent.stop": { support: "partial", native: "subagentStop" },
19389
19609
  "prompt.submit": undocumented,
19390
19610
  "compact.pre": { support: "partial", native: "preCompact" },
19391
- stop: none
19611
+ stop: { support: "native", native: "stop" }
19392
19612
  },
19393
19613
  shellPolicy: { deny: "native", channel: "exit2+json", failClosed: true },
19394
19614
  context: {
@@ -19412,7 +19632,7 @@ var CURSOR_DESCRIPTOR = {
19412
19632
  postToolObserve: "undocumented"
19413
19633
  },
19414
19634
  interaction: { questions: "none", writeBoundary: "partial" },
19415
- stopControl: "undocumented",
19635
+ stopControl: "native",
19416
19636
  shellAvailable: "native",
19417
19637
  perEventCost: "low",
19418
19638
  capabilities: [
@@ -19479,7 +19699,8 @@ var EVENTS3 = {
19479
19699
  beforeShellExecution: "shell.pre",
19480
19700
  subagentStart: "subagent.start",
19481
19701
  subagentStop: "subagent.stop",
19482
- preCompact: "compact.pre"
19702
+ preCompact: "compact.pre",
19703
+ stop: "stop"
19483
19704
  };
19484
19705
  var parseCursorHookInput = (value) => {
19485
19706
  if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
@@ -19515,7 +19736,12 @@ var parseCursorHookInput = (value) => {
19515
19736
  ...event === "subagentStart" && nonEmpty2(value.subagent_id) ? { subagent_id: value.subagent_id } : {},
19516
19737
  ...event === "subagentStart" && nonEmpty2(value.subagent_type) ? { subagent_type: value.subagent_type } : {},
19517
19738
  ...event === "subagentStart" && nonEmpty2(value.parent_conversation_id) ? { parent_conversation_id: value.parent_conversation_id } : {},
19518
- ...event === "subagentStart" && nonEmpty2(value.task) ? { task: value.task } : {}
19739
+ ...event === "subagentStart" && nonEmpty2(value.task) ? { task: value.task } : {},
19740
+ ...event === "stop" ? {
19741
+ ...nonEmpty2(value.status) ? { status: value.status } : {},
19742
+ ...typeof value.loop_count === "number" ? { loop_count: value.loop_count } : {},
19743
+ ...nonEmpty2(value.transcript_path) ? { transcript_path: value.transcript_path } : {}
19744
+ } : {}
19519
19745
  }
19520
19746
  };
19521
19747
  };
@@ -19549,6 +19775,51 @@ var protocolEvent2 = (input) => {
19549
19775
  };
19550
19776
  case "preCompact":
19551
19777
  return { kind: "compact.pre", trigger: "auto" };
19778
+ case "stop":
19779
+ return {
19780
+ kind: "stop",
19781
+ lastMessage: (input.status ?? "completed") === "completed" ? lastAssistantText(input.transcript_path) : null,
19782
+ stopHookActive: (input.loop_count ?? 0) > 0
19783
+ };
19784
+ }
19785
+ };
19786
+ var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
19787
+ var lastAssistantText = (file) => {
19788
+ if (!file || !path21.isAbsolute(file))
19789
+ return null;
19790
+ try {
19791
+ const size = fs16.statSync(file).size;
19792
+ const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
19793
+ const buffer = Buffer.alloc(size - start);
19794
+ const fd = fs16.openSync(file, "r");
19795
+ try {
19796
+ fs16.readSync(fd, buffer, 0, buffer.length, start);
19797
+ } finally {
19798
+ fs16.closeSync(fd);
19799
+ }
19800
+ const lines = buffer.toString("utf8").split(`
19801
+ `);
19802
+ for (let index = lines.length - 1;index >= 0; index--) {
19803
+ let entry;
19804
+ try {
19805
+ entry = JSON.parse(lines[index]);
19806
+ } catch {
19807
+ continue;
19808
+ }
19809
+ if (!isRecord4(entry) || entry.role !== "assistant")
19810
+ continue;
19811
+ const content = isRecord4(entry.message) ? entry.message.content : entry.content;
19812
+ if (typeof content === "string")
19813
+ return content;
19814
+ if (!Array.isArray(content))
19815
+ return null;
19816
+ const text = content.filter((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
19817
+ `);
19818
+ return text || null;
19819
+ }
19820
+ return null;
19821
+ } catch {
19822
+ return null;
19552
19823
  }
19553
19824
  };
19554
19825
  var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path21.sep) ? root : `${root}${path21.sep}`);
@@ -19580,6 +19851,8 @@ var render3 = (decision, native) => {
19580
19851
  return { json: { additional_context: decision.text }, exitCode: 0 };
19581
19852
  if (decision.kind === "notice")
19582
19853
  return { json: { user_message: decision.userMessage }, exitCode: 0 };
19854
+ if (decision.kind === "continue" && native === "stop")
19855
+ return { json: { followup_message: decision.reason }, exitCode: 0 };
19583
19856
  return {
19584
19857
  json: native !== null && BLOCKING2.has(native) ? { permission: "allow" } : {},
19585
19858
  exitCode: 0