@bendyline/gezel-service 1.0.3 → 1.0.5

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 (82) hide show
  1. package/dist/bin/gezeld.js +781 -251
  2. package/dist/handboek-content/whats-new/1.26226.md +89 -0
  3. package/dist/index.d.ts +64 -11
  4. package/dist/index.js +781 -251
  5. package/dist/memory/embed-worker.js +22 -3
  6. package/dist/ui/assets/{TerminalCodeEditor-CXspNipu.js → TerminalCodeEditor-C_Mvaxqz.js} +2 -2
  7. package/dist/ui/assets/{_.contribution-Bi8h5rS7.js → _.contribution-fOcpgyNy.js} +1 -1
  8. package/dist/ui/assets/{chunk-6N2J7C2B-Du7clJkV.js → chunk-6N2J7C2B-BNHR_rFP.js} +1 -1
  9. package/dist/ui/assets/chunk-ILCJ3WFD-CUnKgI_I.js +4 -0
  10. package/dist/ui/assets/chunk-S5PCVMKU-i2wfHmvU.js +6 -0
  11. package/dist/ui/assets/{chunk-WATAOG4Y-BnCgSncI.js → chunk-WATAOG4Y-BbL-tDgQ.js} +1 -1
  12. package/dist/ui/assets/{cpp.contribution-CIgWElWw.js → cpp.contribution-Cx5HvoW7.js} +1 -1
  13. package/dist/ui/assets/{csharp.contribution-DDiJzXzB.js → csharp.contribution-DMuCSvzf.js} +1 -1
  14. package/dist/ui/assets/{css.contribution-Dc5E_mNx.js → css.contribution-BPXcRoVK.js} +1 -1
  15. package/dist/ui/assets/{cssMode-BN_VZB6O.js → cssMode-BHAnHChA.js} +1 -1
  16. package/dist/ui/assets/{dist-D4PZf1Q3.js → dist-DRDFuw7G.js} +2 -2
  17. package/dist/ui/assets/dist-DYn91eSO.js +2 -0
  18. package/dist/ui/assets/{dockerfile.contribution-BQsaSuAJ.js → dockerfile.contribution-DQAHazBI.js} +1 -1
  19. package/dist/ui/assets/docx-BEJpkI6I.js +2 -0
  20. package/dist/ui/assets/extract-UTAOG7LV-BY3SCkzz.js +1 -0
  21. package/dist/ui/assets/{go.contribution-CPvqBST7.js → go.contribution-Jq5pYI7r.js} +1 -1
  22. package/dist/ui/assets/{handlebars-DJvRMy-I.js → handlebars-BWC7ZXbe.js} +1 -1
  23. package/dist/ui/assets/handlebars.contribution-BSmxDPeT.js +2 -0
  24. package/dist/ui/assets/{html-Becff5XU.js → html-BzC9B_Os.js} +1 -1
  25. package/dist/ui/assets/html.contribution-CEvtdKHG.js +2 -0
  26. package/dist/ui/assets/{htmlMode-BFmyC6t-.js → htmlMode-1qtmM5J4.js} +1 -1
  27. package/dist/ui/assets/{index-w_CaRVtV.js → index-JGn32C5f.js} +177 -172
  28. package/dist/ui/assets/infer-D6e75m-s.js +2 -0
  29. package/dist/ui/assets/{ini.contribution-DFSSA0eY.js → ini.contribution-BQpzdQzE.js} +1 -1
  30. package/dist/ui/assets/{java.contribution-Bdvvskrf.js → java.contribution-r3T-8LYE.js} +1 -1
  31. package/dist/ui/assets/{javascript-DByfaclN.js → javascript-DlipM3VT.js} +1 -1
  32. package/dist/ui/assets/javascript.contribution-D4MU3K0o.js +2 -0
  33. package/dist/ui/assets/{jsonMode-CxkV6dJT.js → jsonMode-DSXTaEG9.js} +1 -1
  34. package/dist/ui/assets/{kotlin.contribution-D8eqi2Fi.js → kotlin.contribution-NI_YvQPr.js} +1 -1
  35. package/dist/ui/assets/layouts-2A7QNF6Y-CWQp8HDd.js +1 -0
  36. package/dist/ui/assets/{less.contribution-DFRb86Ld.js → less.contribution-BDz7g6GS.js} +1 -1
  37. package/dist/ui/assets/{lua.contribution-BgktkcsF.js → lua.contribution-Cr_f17YF.js} +1 -1
  38. package/dist/ui/assets/{markdown.contribution-BKqBKD_g.js → markdown.contribution-CeJFrLN_.js} +1 -1
  39. package/dist/ui/assets/{monaco-CyhM025v.js → monaco-Bv9TxWl_.js} +3 -3
  40. package/dist/ui/assets/{monaco-base-hW9ht_BG.js → monaco-base-DD1Wr24S.js} +1 -1
  41. package/dist/ui/assets/{monaco-setup-Cquhh1OE.js → monaco-setup-CSa3_fbr.js} +1 -1
  42. package/dist/ui/assets/{monaco.contribution-Cle0Lbgx.js → monaco.contribution-DKCJArGP.js} +2 -2
  43. package/dist/ui/assets/{monaco.contribution-BVrZ1Anc.js → monaco.contribution-QFH9z5g8.js} +2 -2
  44. package/dist/ui/assets/{monaco.contribution-D9-bPyBf.js → monaco.contribution-ZD-wInG3.js} +2 -2
  45. package/dist/ui/assets/{monaco.contribution-Dl8lHLi5.js → monaco.contribution-tTvEzZ5l.js} +2 -2
  46. package/dist/ui/assets/{php.contribution-CcYxMawn.js → php.contribution-B310cs3S.js} +1 -1
  47. package/dist/ui/assets/pptx-DZdEoDP9.js +6 -0
  48. package/dist/ui/assets/{python-DivQov6J.js → python-CcCNDnkj.js} +1 -1
  49. package/dist/ui/assets/python.contribution-ZHL594qQ.js +2 -0
  50. package/dist/ui/assets/{ruby.contribution-DNudSRrU.js → ruby.contribution-CRsX2xPw.js} +1 -1
  51. package/dist/ui/assets/{rust.contribution-BqG2lQpP.js → rust.contribution-CfwWiQxD.js} +1 -1
  52. package/dist/ui/assets/{scss.contribution-DDxPnF-K.js → scss.contribution-CJaLNKeq.js} +1 -1
  53. package/dist/ui/assets/{shell.contribution-Q7wVL7zv.js → shell.contribution-DLPinQHD.js} +1 -1
  54. package/dist/ui/assets/{sql.contribution-B9ndj3QH.js → sql.contribution-DsZSLfVg.js} +1 -1
  55. package/dist/ui/assets/{swift.contribution-D_FDdL6S.js → swift.contribution-DFBOOFKs.js} +1 -1
  56. package/dist/ui/assets/{terminal-monaco-setup-BdF0mUfG.js → terminal-monaco-setup-B8Xv2NV9.js} +1 -1
  57. package/dist/ui/assets/{tsMode-JVR2Jo5q.js → tsMode-DqT2Dz4k.js} +1 -1
  58. package/dist/ui/assets/{typescript-1ZOjdUnd.js → typescript-_R5MS0Lp.js} +1 -1
  59. package/dist/ui/assets/typescript.contribution-CHnXQvfG.js +2 -0
  60. package/dist/ui/assets/xlsx-DDBRN-IU.js +4 -0
  61. package/dist/ui/assets/{xml-DrdFGpo2.js → xml-CQVIKNxb.js} +1 -1
  62. package/dist/ui/assets/xml.contribution-HY4GvlWC.js +2 -0
  63. package/dist/ui/assets/{yaml-DYQPB5Mj.js → yaml-Nt3uLkWC.js} +1 -1
  64. package/dist/ui/assets/yaml.contribution-C7dllTyY.js +2 -0
  65. package/dist/ui/index.html +10 -10
  66. package/package.json +12 -11
  67. package/dist/ui/assets/chunk-ILCJ3WFD-fHFoZ9wm.js +0 -4
  68. package/dist/ui/assets/chunk-S5PCVMKU-DopU3Q9j.js +0 -6
  69. package/dist/ui/assets/dist-BwDZUtiH.js +0 -2
  70. package/dist/ui/assets/docx-IZY6hX8A.js +0 -2
  71. package/dist/ui/assets/extract-UTAOG7LV-BZ_L7lLL.js +0 -1
  72. package/dist/ui/assets/handlebars.contribution-BNbstxN6.js +0 -2
  73. package/dist/ui/assets/html.contribution-CPDzzJ0M.js +0 -2
  74. package/dist/ui/assets/infer-v-bCXeT9.js +0 -2
  75. package/dist/ui/assets/javascript.contribution-CPCuprn0.js +0 -2
  76. package/dist/ui/assets/layouts-2A7QNF6Y-D94iKLLQ.js +0 -1
  77. package/dist/ui/assets/pptx-CsdVR0xn.js +0 -6
  78. package/dist/ui/assets/python.contribution-CiRaN0sm.js +0 -2
  79. package/dist/ui/assets/typescript.contribution-_s-8TvIE.js +0 -2
  80. package/dist/ui/assets/xlsx-CdLmEC42.js +0 -4
  81. package/dist/ui/assets/xml.contribution-DUPYAKAC.js +0 -2
  82. package/dist/ui/assets/yaml.contribution-CRBo99nF.js +0 -2
package/dist/index.js CHANGED
@@ -320,6 +320,13 @@ function queryInstruction() {
320
320
  if (/bge-/i.test(model)) return "Represent this sentence for searching relevant passages: ";
321
321
  return "";
322
322
  }
323
+ function isRetryablePipelineLoadFailure(error) {
324
+ const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
325
+ const code = error?.code ?? "";
326
+ return /(?:ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|UND_ERR_|fetch failed|network(?: request)? failed|socket hang up|timed?\s*out|HTTP\s+(?:408|425|429|5\d\d)|TLS|SSL|certificate)/i.test(
327
+ `${code} ${message}`
328
+ );
329
+ }
323
330
  async function loadPipeline() {
324
331
  if (!pipelinePromise) {
325
332
  pipelinePromise = (async () => {
@@ -333,7 +340,11 @@ async function loadPipeline() {
333
340
  } catch (err) {
334
341
  const missing = isMissingModule(err, TRANSFORMERS_MODULE);
335
342
  const message = missing ? "Local memory embeddings are an optional npm feature. Install @huggingface/transformers@^3.8.1 alongside @bendyline/gezel-service (see the service README)." : err instanceof Error ? err.message : String(err);
336
- throw new PipelineLoadError(message, missing);
343
+ throw new PipelineLoadError(
344
+ message,
345
+ missing,
346
+ !missing && isRetryablePipelineLoadFailure(err)
347
+ );
337
348
  }
338
349
  })();
339
350
  pipelinePromise.catch(() => {
@@ -374,10 +385,17 @@ var init_embed_core = __esm({
374
385
  * damaged runtime.
375
386
  */
376
387
  optionalPeerMissing;
377
- constructor(message, optionalPeerMissing = false) {
388
+ /**
389
+ * Network/model-registry failures can clear without a process restart. The
390
+ * host uses this bit to apply a short retry cooldown instead of permanently
391
+ * disabling embeddings after one failed first-use download.
392
+ */
393
+ retryable;
394
+ constructor(message, optionalPeerMissing = false, retryable = false) {
378
395
  super(message);
379
396
  this.name = "PipelineLoadError";
380
397
  this.optionalPeerMissing = optionalPeerMissing;
398
+ this.retryable = retryable;
381
399
  }
382
400
  };
383
401
  pipelinePromise = null;
@@ -388,6 +406,7 @@ var init_embed_core = __esm({
388
406
  var embeddings_exports = {};
389
407
  __export(embeddings_exports, {
390
408
  EmbeddingsDisabledError: () => EmbeddingsDisabledError,
409
+ EmbeddingsUnavailableError: () => EmbeddingsUnavailableError,
391
410
  embed: () => embed,
392
411
  embedBatch: () => embedBatch,
393
412
  embedQuery: () => embedQuery,
@@ -400,7 +419,7 @@ function disabledByEnv() {
400
419
  return raw === "1" || raw?.toLowerCase() === "true";
401
420
  }
402
421
  function embeddingsDisabledReason() {
403
- return disabledByEnv() ? ENV_DISABLED_REASON : disabledReason;
422
+ return disabledByEnv() ? ENV_DISABLED_REASON : disabledReason ?? currentUnavailableReason();
404
423
  }
405
424
  function workerEntry() {
406
425
  if (process.env.VITEST) return null;
@@ -436,7 +455,12 @@ function onMessage(msg) {
436
455
  pending.delete(msg.id);
437
456
  if (msg.error) {
438
457
  if (msg.fatal) markDisabled(msg.error, msg.optionalPeerMissing ?? false);
439
- p.reject(new Error(msg.error));
458
+ if (msg.retryable) {
459
+ markTemporarilyUnavailable(msg.error);
460
+ p.reject(new EmbeddingsUnavailableError(unavailableReason ?? msg.error));
461
+ } else {
462
+ p.reject(new Error(msg.error));
463
+ }
440
464
  return;
441
465
  }
442
466
  p.resolve(msg.vectors ?? []);
@@ -469,12 +493,31 @@ async function embedInProcess(texts) {
469
493
  return await runEmbed(texts);
470
494
  } catch (err) {
471
495
  if (err instanceof PipelineLoadError) {
496
+ if (err.retryable) {
497
+ markTemporarilyUnavailable(err.message);
498
+ throw new EmbeddingsUnavailableError(unavailableReason ?? err.message);
499
+ }
472
500
  markDisabled(err.message, err.optionalPeerMissing);
473
501
  throw new EmbeddingsDisabledError(disabledReason ?? err.message);
474
502
  }
475
503
  throw err;
476
504
  }
477
505
  }
506
+ function currentUnavailableReason() {
507
+ if (!unavailableReason) return null;
508
+ if (Date.now() < unavailableUntil) return unavailableReason;
509
+ unavailableReason = null;
510
+ unavailableUntil = 0;
511
+ return null;
512
+ }
513
+ function markTemporarilyUnavailable(message) {
514
+ unavailableReason = firstLine(message);
515
+ unavailableUntil = Date.now() + EMBEDDING_RETRY_COOLDOWN_MS;
516
+ log10.warn(
517
+ `[memory] embedding model load failed; semantic search will retry after ${Math.ceil(EMBEDDING_RETRY_COOLDOWN_MS / 1e3)}s.`
518
+ );
519
+ log10.warn("[memory] underlying transient error:", message);
520
+ }
478
521
  function markDisabled(message, optionalPeerMissing = false) {
479
522
  if (disabledReason) return;
480
523
  disabledReason = firstLine(message);
@@ -490,11 +533,14 @@ function markDisabled(message, optionalPeerMissing = false) {
490
533
  async function embedMany(texts) {
491
534
  if (disabledByEnv()) throw new EmbeddingsDisabledError(ENV_DISABLED_REASON);
492
535
  if (disabledReason) throw new EmbeddingsDisabledError(disabledReason);
536
+ const temporaryReason = currentUnavailableReason();
537
+ if (temporaryReason) throw new EmbeddingsUnavailableError(temporaryReason);
493
538
  const w = ensureWorker();
494
539
  if (w) {
495
540
  try {
496
541
  return await sendToWorker(w, texts);
497
542
  } catch (err) {
543
+ if (err instanceof EmbeddingsUnavailableError) throw err;
498
544
  if (disabledReason) throw new EmbeddingsDisabledError(disabledReason);
499
545
  log10.warn(`[memory] embed via worker failed; retrying in-process: ${describe(err)}`);
500
546
  }
@@ -519,7 +565,7 @@ async function embedBatch(texts) {
519
565
  if (texts.length === 0) return [];
520
566
  return embedMany(texts);
521
567
  }
522
- var log10, EmbeddingsDisabledError, disabledReason, ENV_DISABLED_REASON, worker, workerUsable, crashCount, nextId, pending;
568
+ var log10, EmbeddingsDisabledError, EmbeddingsUnavailableError, disabledReason, unavailableReason, unavailableUntil, EMBEDDING_RETRY_COOLDOWN_MS, ENV_DISABLED_REASON, worker, workerUsable, crashCount, nextId, pending;
523
569
  var init_embeddings = __esm({
524
570
  "src/memory/embeddings.ts"() {
525
571
  "use strict";
@@ -528,12 +574,25 @@ var init_embeddings = __esm({
528
574
  log10 = createLogger10("memory");
529
575
  EmbeddingsDisabledError = class extends Error {
530
576
  code = "EMBEDDINGS_DISABLED";
577
+ retryable = false;
531
578
  constructor(reason) {
532
579
  super(`memory embeddings disabled: ${reason}`);
533
580
  this.name = "EmbeddingsDisabledError";
534
581
  }
535
582
  };
583
+ EmbeddingsUnavailableError = class extends EmbeddingsDisabledError {
584
+ code = "EMBEDDINGS_UNAVAILABLE";
585
+ retryable = true;
586
+ constructor(reason) {
587
+ super(reason);
588
+ this.name = "EmbeddingsUnavailableError";
589
+ this.message = `memory embeddings temporarily unavailable: ${reason}`;
590
+ }
591
+ };
536
592
  disabledReason = null;
593
+ unavailableReason = null;
594
+ unavailableUntil = 0;
595
+ EMBEDDING_RETRY_COOLDOWN_MS = 6e4;
537
596
  ENV_DISABLED_REASON = "disabled by GEZEL_DISABLE_EMBEDDINGS";
538
597
  worker = null;
539
598
  workerUsable = true;
@@ -9500,7 +9559,7 @@ __export(bootstrap_exports, {
9500
9559
  reconcileSystemToolsetFromDisk: () => reconcileSystemToolsetFromDisk,
9501
9560
  runSystemBootstrap: () => runSystemBootstrap
9502
9561
  });
9503
- import { createHash as createHash38, randomUUID as randomUUID25 } from "crypto";
9562
+ import { createHash as createHash39, randomUUID as randomUUID25 } from "crypto";
9504
9563
  import { existsSync as existsSync23 } from "fs";
9505
9564
  import { mkdir as mkdir51, open as open5, readFile as readFile62, rm as rm34, writeFile as writeFile23 } from "fs/promises";
9506
9565
  import { join as join85 } from "path";
@@ -9896,7 +9955,7 @@ async function downloadWithIntegrity(url, dest, integrity, opts = {}) {
9896
9955
  if (!["sha256", "sha384", "sha512"].includes(algo)) {
9897
9956
  throw new Error(`unsupported integrity algorithm: ${algo}`);
9898
9957
  }
9899
- const hash = createHash38(algo);
9958
+ const hash = createHash39(algo);
9900
9959
  const request = await fetchBounded(url, DOWNLOAD_TIMEOUT_MS, opts.signal);
9901
9960
  const handle = await open5(dest, "wx", 384);
9902
9961
  try {
@@ -11690,7 +11749,7 @@ var ChatEventBus = class {
11690
11749
  };
11691
11750
 
11692
11751
  // src/chat/manager.ts
11693
- import { randomUUID as randomUUID7 } from "crypto";
11752
+ import { createHash as createHash12, randomUUID as randomUUID7 } from "crypto";
11694
11753
  import { createRequire } from "module";
11695
11754
  import { join as join28 } from "path";
11696
11755
  import {
@@ -14085,15 +14144,13 @@ import { createLogger as createLogger4 } from "@bendyline/gezel";
14085
14144
  // src/model-profile/behaviors/fabrication-detect-claim-without-tool.ts
14086
14145
  var PROJECT_CREATION_VERBS = [
14087
14146
  "created",
14088
- "initiated",
14089
14147
  "initialized",
14090
14148
  "set\\s+up",
14091
14149
  "spun\\s+up",
14092
- "started",
14093
14150
  "made",
14094
- "bootstrapped",
14095
- "kicked\\s+off"
14151
+ "bootstrapped"
14096
14152
  ].join("|");
14153
+ var PROJECT_WORK_START_VERBS = ["initiated", "started", "kicked\\s+off"].join("|");
14097
14154
  var TASK_CREATION_VERBS = ["created", "added", "queued", "set\\s+up"].join("|");
14098
14155
  var GEZEL_CREATION_VERBS = [
14099
14156
  "created",
@@ -14154,6 +14211,18 @@ var CLAIM_RULES = [
14154
14211
  claim: "recruited a voorman",
14155
14212
  nudge: "You told the user you recruited a voorman, but you did not call `create_gezel`, `ensure_gezel`, or `start_project` this turn. Call the right one now, and only report recruitment after it succeeds."
14156
14213
  },
14214
+ {
14215
+ // Ambiguous kickoff verbs only become a container-creation claim when
14216
+ // the prose explicitly says this is a new/separate project. Otherwise
14217
+ // the next rule treats them as starting work in the current project.
14218
+ pattern: new RegExp(
14219
+ String.raw`\bI(?:'ve|\s+have|\s+just|\s+already)?\s+(?:` + PROJECT_WORK_START_VERBS + String.raw`)\s+(?:the|a|an)\s+(?:new|fresh|separate|another|dedicated)\s+project\b`,
14220
+ "i"
14221
+ ),
14222
+ requiredTools: ["start_project"],
14223
+ claim: "created a project",
14224
+ nudge: "You told the user you started a new project, but you did not call `start_project` this turn. Call it now with the user-intended name and project brief, or correct the claim."
14225
+ },
14157
14226
  {
14158
14227
  pattern: new RegExp(
14159
14228
  String.raw`\bI(?:'ve|\s+have|\s+just|\s+already)?\s+(?:` + PROJECT_CREATION_VERBS + String.raw`)\s+(?:the|a|an)\s+` + // Optional name placed between "the" and "project". Forms
@@ -14175,6 +14244,15 @@ var CLAIM_RULES = [
14175
14244
  claim: "created a project",
14176
14245
  nudge: "You told the user you created the project, but you did not call `start_project` this turn. Call it now with the user-intended name and a real `about` + `missionObjectives` derived from their request."
14177
14246
  },
14247
+ {
14248
+ pattern: new RegExp(
14249
+ String.raw`\bI(?:'ve|\s+have|\s+just|\s+already)?\s+(?:` + PROJECT_WORK_START_VERBS + String.raw`)\s+(?:(?:work|the\s+work)\s+(?:on|for)\s+)?(?:the|this|that|our|your|a|an)\s+project\b`,
14250
+ "i"
14251
+ ),
14252
+ requiredTools: ["invoke_craftbook", "create_task", "start_project"],
14253
+ claim: "started work in the current project",
14254
+ nudge: "You told the user project work started, but no project-local action succeeded this turn. Use `invoke_craftbook` for a selected procedure, `create_task` for an ad-hoc task, or `start_project` only when the user explicitly requested a separate project. Do not repeat the kickoff claim until the action returns successfully."
14255
+ },
14178
14256
  {
14179
14257
  pattern: new RegExp(
14180
14258
  String.raw`\bI(?:'ve|\s+have|\s+just|\s+already)?\s+(?:` + GEZEL_CREATION_VERBS + String.raw`)\s+(?:the|a|an)\s+(?:new\s+)?gezel\b`,
@@ -15097,13 +15175,26 @@ var BUILD_REQUEST_RE = new RegExp(
15097
15175
  function looksLikeNewBuildRequest(text) {
15098
15176
  return BUILD_REQUEST_RE.test(text);
15099
15177
  }
15178
+ function explicitlyRequestsSeparateProject(text) {
15179
+ return /\b(?:new|fresh|separate|another|dedicated)\s+(?:(?:standalone|independent)\s+)?(?:project|workspace)\b/i.test(
15180
+ text
15181
+ ) || /\b(?:as|in|into|under|for)\s+(?:a|an)\s+(?:new|fresh|separate|another|dedicated)\s+(?:project|workspace)\b/i.test(
15182
+ text
15183
+ );
15184
+ }
15100
15185
  var MEESTER_BUILD_PRELUDE = '(System note for this turn: the user is asking for a new substantive build. Make ONE tool call:\n \u2022 `start_project({ name, about, missionObjectives, taskDescription })`. Creates a fresh project, selects an appropriate lead/team for the effective execution mode, creates the kickoff task, and hands it off (the work starts in a task-scoped session). Use this for every "build me a website / game / app / dashboard" request, from a single-file prototype through multimodal work. `taskDescription` must ask the lead to ship the actual requested deliverable, not to create a plan. For browser games/sites, name `index.html` and the acceptance criteria (workspace-relative path; do not prefix `workspace/`).\nAfter the macro returns, your turn is done \u2014 tell the user the lead is on it. Do NOT call `create_gezel`, `update_project`, `create_task`, or `message_gezel` separately \u2014 the macro handles them. Do NOT reuse the existing "Default" project; the macro creates a fresh dedicated one.)';
15101
15186
  var PromptMeesterBuildPrelude = {
15102
15187
  id: "prompt.meester-build-prelude",
15103
15188
  description: 'Prepends a system note to the user prompt when the active gezel is the Meester and the message looks like a "build me X" request, anchoring the first-call expectation on `start_project`.',
15104
15189
  userPromptPrelude(ctx) {
15105
15190
  if (!ctx.isMeester) return null;
15191
+ if (ctx.messageOrigin !== "direct-user") return null;
15192
+ if (/^\s*\[Answer to:/i.test(ctx.userText)) return null;
15106
15193
  if (!looksLikeNewBuildRequest(ctx.userText)) return null;
15194
+ if (ctx.projectId !== "default" && !explicitlyRequestsSeparateProject(ctx.userText)) {
15195
+ return null;
15196
+ }
15197
+ if (!ctx.availableToolNames.includes("start_project")) return null;
15107
15198
  return MEESTER_BUILD_PRELUDE;
15108
15199
  }
15109
15200
  };
@@ -18152,6 +18243,12 @@ var AnthropicCliSession = class extends StreamingSessionBase {
18152
18243
  providerState() {
18153
18244
  return this.claudeSessionIdCache ? { claudeCliSessionId: this.claudeSessionIdCache } : {};
18154
18245
  }
18246
+ getRegisteredToolNames() {
18247
+ const context = this.deps.context;
18248
+ return [
18249
+ .../* @__PURE__ */ new Set([...context.allowedBuiltinTools ?? [], ...context.allowedMcpTools ?? []])
18250
+ ];
18251
+ }
18155
18252
  async disconnect() {
18156
18253
  this.aborted = true;
18157
18254
  try {
@@ -19700,6 +19797,10 @@ var AnthropicSession = class extends StreamingSessionBase {
19700
19797
  * resolves and persisted on `ChatMessage.reasoning`.
19701
19798
  */
19702
19799
  lastTurnReasoning = "";
19800
+ getRegisteredToolNames() {
19801
+ const bridgeNames = this.deps.bridges.isEmpty() ? [] : this.deps.bridges.getAnthropicTools().map((tool) => tool.name);
19802
+ return [.../* @__PURE__ */ new Set([...bridgeNames, ...this.externalToolNames])];
19803
+ }
19703
19804
  capturedToolCalls() {
19704
19805
  return [...this.capturedCalls];
19705
19806
  }
@@ -26964,7 +27065,12 @@ var LlamaCppSession = class extends StreamingSessionBase {
26964
27065
  if (toolCalls.length > 0) {
26965
27066
  const normalized2 = normalizeMalformedStructuredToolCalls(
26966
27067
  toolCalls,
26967
- collectKnownToolNames()
27068
+ collectKnownToolNames(),
27069
+ // A malformed write at the output ceiling is not repairable: the
27070
+ // missing JSON tail may also be missing file bytes. Executing the
27071
+ // loose prefix can silently replace a complete file with a
27072
+ // truncated one. Valid, fully parseable calls remain eligible.
27073
+ finishReason !== "length"
26968
27074
  );
26969
27075
  toolCalls = normalized2.toolCalls;
26970
27076
  malformedStructuredCallIds = new Set(normalized2.sanitizedIds);
@@ -27841,7 +27947,7 @@ var ToolCallAccumulator = class {
27841
27947
  }));
27842
27948
  }
27843
27949
  };
27844
- function normalizeMalformedStructuredToolCalls(toolCalls, knownToolNames) {
27950
+ function normalizeMalformedStructuredToolCalls(toolCalls, knownToolNames, allowMalformedWriteRepair = true) {
27845
27951
  const repaired = [];
27846
27952
  const sanitized = [];
27847
27953
  const sanitizedIds = [];
@@ -27852,11 +27958,7 @@ function normalizeMalformedStructuredToolCalls(toolCalls, knownToolNames) {
27852
27958
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return call;
27853
27959
  } catch {
27854
27960
  }
27855
- const repairedArgs = tryRepairMalformedWriteToolArguments(
27856
- call.function.name,
27857
- raw,
27858
- knownToolNames
27859
- );
27961
+ const repairedArgs = allowMalformedWriteRepair ? tryRepairMalformedWriteToolArguments(call.function.name, raw, knownToolNames) : null;
27860
27962
  if (repairedArgs) {
27861
27963
  repaired.push({
27862
27964
  name: call.function.name,
@@ -32018,6 +32120,7 @@ function buildLlamaCppEngineArgs(input) {
32018
32120
  ggufHasMtp,
32019
32121
  installedDraftModelPath,
32020
32122
  reasoningFormat,
32123
+ reasoningPreserve,
32021
32124
  architecture,
32022
32125
  modelId,
32023
32126
  swaFullAutoFits
@@ -32065,6 +32168,7 @@ function buildLlamaCppEngineArgs(input) {
32065
32168
  if (typeof nMax === "number") args.push("--spec-draft-n-max", String(nMax));
32066
32169
  }
32067
32170
  if (reasoningFormat) args.push("--reasoning-format", reasoningFormat);
32171
+ if (reasoningPreserve) args.push("--reasoning-preserve");
32068
32172
  if (config.llamaCppExtraArgs) {
32069
32173
  for (const [rawKey, value] of Object.entries(config.llamaCppExtraArgs)) {
32070
32174
  if (value === false) continue;
@@ -32385,6 +32489,31 @@ function fitsSwaFullInFastMemory(input) {
32385
32489
  return Number.isFinite(required) && Number.isFinite(available) && required <= available;
32386
32490
  }
32387
32491
 
32492
+ // src/providers/llama-cpp/reasoning-launch.ts
32493
+ var POSITIVE_INTEGER = /^[1-9]\d*$/;
32494
+ function parseReasoningPreserveEnv(raw) {
32495
+ const normalized2 = raw?.trim().toLowerCase();
32496
+ return normalized2 === "1" || normalized2 === "true";
32497
+ }
32498
+ function parseReasoningBudgetEnv(raw) {
32499
+ const normalized2 = raw?.trim();
32500
+ if (!normalized2) return void 0;
32501
+ if (!POSITIVE_INTEGER.test(normalized2)) {
32502
+ throw new Error("GEZEL_LLAMA_REASONING_BUDGET_TOKENS must be a positive integer");
32503
+ }
32504
+ const parsed = Number(normalized2);
32505
+ if (!Number.isSafeInteger(parsed)) {
32506
+ throw new Error("GEZEL_LLAMA_REASONING_BUDGET_TOKENS exceeds the safe integer range");
32507
+ }
32508
+ return parsed;
32509
+ }
32510
+ function reasoningLaunchOverridesFromEnv(env = process.env) {
32511
+ return {
32512
+ preserve: parseReasoningPreserveEnv(env.GEZEL_LLAMA_REASONING_PRESERVE),
32513
+ budgetTokens: parseReasoningBudgetEnv(env.GEZEL_LLAMA_REASONING_BUDGET_TOKENS)
32514
+ };
32515
+ }
32516
+
32388
32517
  // src/providers/llama-cpp/spec-draft.ts
32389
32518
  async function resolveSpecDraft(input) {
32390
32519
  const { perModel, configSpecType, configDraftModelPath, resolveDraftPath } = input;
@@ -32443,6 +32572,8 @@ async function buildLlamaCppProvider(opts) {
32443
32572
  const { config, affinity, home } = opts;
32444
32573
  const externalBaseUrl = opts.modelOverride ? void 0 : process.env.GEZEL_LLAMA_SERVER_URL ?? config.llamaCppBaseUrl;
32445
32574
  const binary = process.env.GEZEL_LLAMA_SERVER_BIN;
32575
+ const reasoningLaunchOverrides = reasoningLaunchOverridesFromEnv();
32576
+ const reasoningPreserve = reasoningLaunchOverrides.preserve;
32446
32577
  const defaultModelId = opts.modelOverride?.modelId ?? config.defaultModel?.["llama-cpp"];
32447
32578
  const configuredSlots = config.providerConcurrency?.["llama-cpp"];
32448
32579
  const envNumCtx = (() => {
@@ -32465,6 +32596,7 @@ async function buildLlamaCppProvider(opts) {
32465
32596
  // rate for its own default engine — throughput was reachable only by
32466
32597
  // scraping stdout from the eval harness.
32467
32598
  includeUsageInStream: true,
32599
+ replayReasoningContent: reasoningPreserve,
32468
32600
  // External-baseUrl default; the supervised path overrides this with the
32469
32601
  // auto-sized `slots` computed below (after the model resolves).
32470
32602
  concurrency: configuredSlots ?? 2,
@@ -32655,10 +32787,12 @@ async function buildLlamaCppProvider(opts) {
32655
32787
  const slotSavePath = replicaSuffix ? join18(llamaCacheRoot, llamaFingerprint, replicaSuffix) : join18(llamaCacheRoot, llamaFingerprint);
32656
32788
  await mkdirAsync(slotSavePath, { recursive: true }).catch(() => {
32657
32789
  });
32658
- const reasoningBudgetTokens = opts.catalog ? await resolveCatalogReasoningBudget(
32790
+ const catalogReasoningBudgetTokens = opts.catalog ? await resolveCatalogReasoningBudget(
32659
32791
  opts.catalog,
32660
32792
  opts.modelOverride?.modelId ?? defaultModelId
32661
32793
  ) : void 0;
32794
+ const reasoningBudgetTokens = reasoningLaunchOverrides.budgetTokens ?? catalogReasoningBudgetTokens;
32795
+ const reasoningBudgetSource = reasoningLaunchOverrides.budgetTokens !== void 0 ? "env" : catalogReasoningBudgetTokens !== void 0 ? "catalog" : "unbounded";
32662
32796
  const specDraft = await resolveSpecDraft({
32663
32797
  perModel: manifestEngineConfig,
32664
32798
  configSpecType: config.llamaCppSpecType,
@@ -33048,6 +33182,7 @@ async function buildLlamaCppProvider(opts) {
33048
33182
  // disables llama-server's chat-template output parsing so mangled
33049
33183
  // model output reaches `delta.content` for provider-side salvage.
33050
33184
  reasoningFormat: process.env.GEZEL_LLAMA_REASONING_FORMAT?.trim() || void 0,
33185
+ reasoningPreserve,
33051
33186
  architecture: modelCatalogInfo?.architecture,
33052
33187
  modelId: defaultModelId ?? void 0,
33053
33188
  swaFullAutoFits
@@ -33156,6 +33291,9 @@ async function buildLlamaCppProvider(opts) {
33156
33291
  batchSize: lastArgValue(resolvedAdvancedArgs, "--batch-size") ?? "default",
33157
33292
  ubatchSize: lastArgValue(resolvedAdvancedArgs, "--ubatch-size") ?? "default",
33158
33293
  flashAttention: lastArgValue(resolvedAdvancedArgs, "--flash-attn") ?? "default",
33294
+ reasoningBudgetTokens: reasoningBudgetTokens ?? "unbounded",
33295
+ reasoningBudgetSource,
33296
+ reasoningPreserve,
33159
33297
  cudaGraphs: process.env.GGML_CUDA_DISABLE_GRAPHS ? "disabled-by-env" : "default"
33160
33298
  },
33161
33299
  env: {
@@ -33321,7 +33459,7 @@ var PROJECT_MACRO_INTERCEPT_CAP = 2;
33321
33459
  var PROJECT_MACRO_FAILURE_CAP = 2;
33322
33460
  function deriveProjectMacroClosing(firstResult) {
33323
33461
  const projectMatch = firstResult.match(/Started (?:project|job) "([^"]+)"/);
33324
- const leadMatch = firstResult.match(/Recruited (\S+) as (voorman|ambachtsman|lead)/);
33462
+ const leadMatch = firstResult.match(/Recruited (\S+) as (voorman|builder|lead)/i);
33325
33463
  const projectName = projectMatch?.[1];
33326
33464
  const leadName = leadMatch?.[1];
33327
33465
  const leadRole = leadMatch?.[2];
@@ -35944,14 +36082,14 @@ async function buildMlxProvider(opts) {
35944
36082
  const cacheBudgetMb = Math.min(config.cacheBudgetMb?.mlx ?? ramAwareDefaultMb, mlxKvHeadroomMb);
35945
36083
  const mlxReplicaSuffix = opts.modelOverride && opts.modelOverride.replicaIdx > 0 ? `replica-${opts.modelOverride.replicaIdx}` : "";
35946
36084
  const cacheRoot = mlxReplicaSuffix ? join21(store.homePath, "engines", "mlx", "cache", mlxReplicaSuffix) : join21(store.homePath, "engines", "mlx", "cache");
35947
- const { createHash: createHash47 } = await import("crypto");
36085
+ const { createHash: createHash48 } = await import("crypto");
35948
36086
  const fingerprintInput = [
35949
36087
  modelCatalogInfo?.id ?? defaultModelId ?? modelDir,
35950
36088
  modelCatalogInfo?.catalogVersion ?? "",
35951
36089
  modelCatalogInfo?.installedAt ?? "",
35952
36090
  modelDir
35953
36091
  ].join("::");
35954
- const modelFingerprint = createHash47("sha256").update(fingerprintInput, "utf8").digest("hex").slice(0, 24);
36092
+ const modelFingerprint = createHash48("sha256").update(fingerprintInput, "utf8").digest("hex").slice(0, 24);
35955
36093
  const diskCacheBudgetMb = config.mlxDiskCacheBudgetMb ?? 8192;
35956
36094
  const mlxIdleMs = config.localEngineIdleTimeoutMs ?? DEFAULT_LOCAL_ENGINE_IDLE_TIMEOUT_MS3;
35957
36095
  const mlxFreezeMs = Math.floor(mlxIdleMs / 2);
@@ -37273,6 +37411,10 @@ var OpenAISession = class extends StreamingSessionBase {
37273
37411
  capturedToolCalls() {
37274
37412
  return [...this.capturedCalls];
37275
37413
  }
37414
+ getRegisteredToolNames() {
37415
+ const bridgeNames = this.deps.bridges.isEmpty() ? [] : this.deps.bridges.getOpenAITools().map((tool) => tool.name);
37416
+ return [.../* @__PURE__ */ new Set([...bridgeNames, ...this.externalToolNames])];
37417
+ }
37276
37418
  async sendAndWait(prompt, opts) {
37277
37419
  return runInQueue(this.deps.queue, opts?.queue, () => this.sendAndWaitInner(prompt, opts));
37278
37420
  }
@@ -40190,14 +40332,15 @@ async function evalCheck(c, ws, deps) {
40190
40332
  }
40191
40333
  async function evalCheckInner(c, ws, deps) {
40192
40334
  const usesArtifact = c.artifact === true;
40193
- const reader = usesArtifact ? {
40335
+ const artifactReader = {
40194
40336
  read: ws.readArtifact ?? (async () => null),
40195
40337
  list: ws.listArtifacts ?? (async () => []),
40196
40338
  // Carry the byte reader across the artifact swap; without it a
40197
40339
  // `verifyImageBytes` check on an artifact deliverable would report
40198
40340
  // "no binary reads" even though the surface supports them.
40199
40341
  ...ws.readArtifactBytes ? { readBytes: ws.readArtifactBytes.bind(ws) } : {}
40200
- } : ws;
40342
+ };
40343
+ const reader = usesArtifact ? artifactReader : ws;
40201
40344
  switch (c.kind) {
40202
40345
  case "minBytes": {
40203
40346
  const r = await fileMinBytes(reader, c.file, c.bytes);
@@ -40625,7 +40768,12 @@ ${hint}` : ""}`,
40625
40768
  };
40626
40769
  }
40627
40770
  case "markdownHeadingsMatch": {
40628
- const result = await markdownHeadingsMatch(reader, c.file, c.outlineFile);
40771
+ const result = await markdownHeadingsMatch(
40772
+ reader,
40773
+ c.file,
40774
+ c.outlineFile,
40775
+ c.outlineArtifact ? artifactReader : ws
40776
+ );
40629
40777
  return {
40630
40778
  ok: result.ok,
40631
40779
  detail: result.detail,
@@ -42540,6 +42688,11 @@ function filterPromptToolDirectives(args) {
42540
42688
  if (rejectedLines.size === 0) return args.prompt;
42541
42689
  return args.prompt.split("\n").filter((_, index) => !rejectedLines.has(index + 1)).join("\n").replace(/\n{4,}/g, "\n\n\n");
42542
42690
  }
42691
+ function capabilitySafeCorrectivePrompt(args) {
42692
+ const filtered = filterPromptToolDirectives(args).trim();
42693
+ if (filtered) return filtered;
42694
+ return "Your previous reply claimed an action succeeded without live evidence. Use only an appropriate action tool visible in your current roster, or explain that the action cannot be performed here. Do not repeat the success claim until an available action returns successfully.";
42695
+ }
42543
42696
 
42544
42697
  // src/chat/provider-capabilities.ts
42545
42698
  function providerUsesManagedMcpBridge(name) {
@@ -46378,6 +46531,21 @@ function sameFromBucket(a, b) {
46378
46531
  if (!a || !b) return false;
46379
46532
  return a.gezelId === b.gezelId;
46380
46533
  }
46534
+ function resolveTurnMessageOrigin(opts) {
46535
+ if (opts?.messageOrigin) return opts.messageOrigin;
46536
+ if (opts?.from) return "cross-gezel";
46537
+ if (opts?.ambient || opts?.nudge || opts?.lane === "background") return "background-nudge";
46538
+ return "direct-user";
46539
+ }
46540
+ function liveTurnToolNames(session) {
46541
+ const names = /* @__PURE__ */ new Set();
46542
+ for (const name of session?.getRegisteredToolNames?.() ?? []) {
46543
+ names.add(name);
46544
+ const qualified = name.match(/^mcp__.+?__(.+)$/)?.[1];
46545
+ if (qualified && qualified !== "*") names.add(qualified);
46546
+ }
46547
+ return [...names];
46548
+ }
46381
46549
  async function resolveCatalogParameterSize(catalog, catalogId) {
46382
46550
  if (!catalogId) return void 0;
46383
46551
  try {
@@ -46519,10 +46687,12 @@ var ChatManager = class {
46519
46687
  */
46520
46688
  afterSessionIdle = /* @__PURE__ */ new Map();
46521
46689
  /**
46522
- * One live async file handoff per sender/target/project/path. Models can
46690
+ * One live async file handoff per sender/target/project/path/intent. Models can
46523
46691
  * emit the same `message_gezel` call several times while the sender turn
46524
46692
  * is still active; joining that work prevents a parked-send burst from
46525
- * replaying the same assignment into the recipient session.
46693
+ * replaying the same assignment into the recipient session. A later
46694
+ * validator repair for a file that was originally missing is distinct work
46695
+ * and must never join the older create request.
46526
46696
  */
46527
46697
  inflightFileHandoffs = /* @__PURE__ */ new Map();
46528
46698
  /** Set once {@link shutdown} starts so deferred watchdogs don't fire into a tearing-down manager. */
@@ -47779,6 +47949,7 @@ ${JSON.stringify(run.output, null, 2)}`;
47779
47949
  coalescable: false,
47780
47950
  lane: void 0,
47781
47951
  ambient: false,
47952
+ messageOrigin: "direct-user",
47782
47953
  continuationMaxTokens: void 0,
47783
47954
  hidden: false,
47784
47955
  nudge: false,
@@ -48084,11 +48255,14 @@ ${JSON.stringify(run.output, null, 2)}`;
48084
48255
  const full = await this.store.getSession(args.gezelId, active.id);
48085
48256
  if (full) return full;
48086
48257
  }
48258
+ const parsedTaskRef = parseTaskRef2(args.taskRef);
48259
+ const task = parsedTaskRef ? await this.store.readTask(parsedTaskRef.projectId, parsedTaskRef.num).catch(() => null) : null;
48087
48260
  return this.createSession({
48088
48261
  gezelId: args.gezelId,
48089
48262
  projectId: args.projectId,
48090
48263
  taskRef: args.taskRef,
48091
- ...args.stepId ? { stepId: args.stepId } : {}
48264
+ ...args.stepId ? { stepId: args.stepId } : {},
48265
+ ...task?.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: task.roleBasedNameOnlyMode } : {}
48092
48266
  });
48093
48267
  }
48094
48268
  /** Late-bind the fitness manager (see the field's docblock). */
@@ -48215,6 +48389,7 @@ ${JSON.stringify(run.output, null, 2)}`;
48215
48389
  */
48216
48390
  async startHandoffSession(args) {
48217
48391
  const dispatchConfig = await this.store.readConfig();
48392
+ const roleBasedNameOnlyMode = args.roleBasedNameOnlyMode ?? dispatchConfig.roleBasedNameOnlyMode ?? false;
48218
48393
  const dispatchGezel = await this.store.getGezel(args.gezelId);
48219
48394
  const dispatchProviderName = await this.resolveProviderName(
48220
48395
  args.gezelId,
@@ -48263,6 +48438,7 @@ ${JSON.stringify(run.output, null, 2)}`;
48263
48438
  projectId: args.projectId,
48264
48439
  taskRef: args.taskRef,
48265
48440
  stepId: dispatchStepId,
48441
+ roleBasedNameOnlyMode,
48266
48442
  ...args.nightShift ? { nightShift: true } : {},
48267
48443
  ...routed && args.capabilityFloor ? {
48268
48444
  routedModel: {
@@ -48319,9 +48495,15 @@ ${stepArc}
48319
48495
  }
48320
48496
  }
48321
48497
  const selfHandoff = args.fromGezelId !== void 0 && args.fromGezelId === args.gezelId;
48322
- const seed = args.kind === "entry" ? `${entryPreface}You've been assigned task ${args.taskRef} (step \`${dispatchStepId}\`). Follow the step instructions already in your prompt \u2014 make the first tool call they name this turn. Append focused notes with \`write_task_note\` as you go. When the step is done, call \`advance_task_step\` to hand off to whoever's next.` : selfHandoff ? `Task ${args.taskRef} has advanced to the next step \u2014 \`${dispatchStepId}\`, which is yours as well. Please continue: follow the step instructions already in your prompt \u2014 make the first tool call they name this turn. Append focused notes with \`write_task_note\` as you go so the next gezel can pick up where you left off. When the step is done, call \`advance_task_step\` to hand off to whoever's next.` : `${args.fromGezelName ? `${args.fromGezelName} has` : "The previous step has been completed and"} handed step \`${dispatchStepId}\` of task ${args.taskRef} to you. Follow the step instructions already in your prompt \u2014 make the first tool call they name this turn. Append focused notes with \`write_task_note\` as you go so the next gezel can pick up where you left off. When the step is done, call \`advance_task_step\` to hand off to whoever's next.`;
48498
+ const previousGezel = !selfHandoff && args.fromGezelId ? await this.store.getGezel(args.fromGezelId).catch(() => null) : null;
48499
+ const fromGezelDisplayName = previousGezel ? displayName2(
48500
+ { name: previousGezel.name, roleBasedName: previousGezel.roleBasedName },
48501
+ roleBasedNameOnlyMode
48502
+ ) : roleBasedNameOnlyMode ? void 0 : args.fromGezelName;
48503
+ const seed = args.kind === "entry" ? `${entryPreface}You've been assigned task ${args.taskRef} (step \`${dispatchStepId}\`). Follow the step instructions already in your prompt \u2014 make the first tool call they name this turn. Append focused notes with \`write_task_note\` as you go. When the step is done, call \`advance_task_step\` to hand off to whoever's next.` : selfHandoff ? `Task ${args.taskRef} has advanced to the next step \u2014 \`${dispatchStepId}\`, which is yours as well. Please continue: follow the step instructions already in your prompt \u2014 make the first tool call they name this turn. Append focused notes with \`write_task_note\` as you go so the next gezel can pick up where you left off. When the step is done, call \`advance_task_step\` to hand off to whoever's next.` : `${fromGezelDisplayName ? `${fromGezelDisplayName} has` : "The previous step has been completed and"} handed step \`${dispatchStepId}\` of task ${args.taskRef} to you. Follow the step instructions already in your prompt \u2014 make the first tool call they name this turn. Append focused notes with \`write_task_note\` as you go so the next gezel can pick up where you left off. When the step is done, call \`advance_task_step\` to hand off to whoever's next.`;
48323
48504
  this.trackBackground(
48324
48505
  this.sendWithBusyRetry(session.id, seed, {
48506
+ messageOrigin: "system",
48325
48507
  ...args.lane ? { lane: args.lane } : {},
48326
48508
  ...args.ambient ? { ambient: true } : {}
48327
48509
  }).catch((err) => {
@@ -48390,7 +48572,8 @@ ${stepArc}
48390
48572
  args.fromSessionId ?? args.fromGezelId,
48391
48573
  target.id,
48392
48574
  projectId,
48393
- normalizeExpectedDeliverablePath(requestedFilePath)
48575
+ normalizeExpectedDeliverablePath(requestedFilePath),
48576
+ fileHandoffIntentKey(args.text)
48394
48577
  ].join("\0") : null;
48395
48578
  const pendingHandoff = fileHandoffKey ? this.inflightFileHandoffs.get(fileHandoffKey) : void 0;
48396
48579
  if (pendingHandoff) return { ...pendingHandoff, deduplicated: true };
@@ -48431,7 +48614,7 @@ ${stepArc}
48431
48614
  const seed = `[Message from ${fromName}]: ${args.text}${deliverableAnnotation}`;
48432
48615
  const failureNotice = { sent: false };
48433
48616
  const resolvedFromSessionId = args.fromSessionId ?? (target.id === args.fromGezelId ? null : await this.ensureOrCreateSession({ gezelId: args.fromGezelId, projectId }).then((s) => s.id).catch(() => null));
48434
- if (resolvedFromSessionId && resolvedFromSessionId !== session.id) {
48617
+ if (!args.suppressReply && resolvedFromSessionId && resolvedFromSessionId !== session.id) {
48435
48618
  this.attachReplyListener({
48436
48619
  targetSessionId: session.id,
48437
48620
  fromSessionId: resolvedFromSessionId,
@@ -49167,11 +49350,10 @@ ${stepArc}
49167
49350
  log52.info(
49168
49351
  `[questions] delivering answer to session ${args.sessionId.slice(0, 8)} (gezel ${session.gezelId}, project ${session.projectId})`
49169
49352
  );
49170
- await this.sendWithBusyRetry(
49171
- args.sessionId,
49172
- args.seed,
49173
- args.coalescable ? { coalescable: true } : void 0
49174
- );
49353
+ await this.sendWithBusyRetry(args.sessionId, args.seed, {
49354
+ messageOrigin: "question-answer",
49355
+ ...args.coalescable ? { coalescable: true } : {}
49356
+ });
49175
49357
  }
49176
49358
  /**
49177
49359
  * System-authored reaction seed into a gezel's live (gezel, project)
@@ -49197,6 +49379,7 @@ ${stepArc}
49197
49379
  this.send(session.id, args.seed, {
49198
49380
  coalescable: true,
49199
49381
  lane: "background",
49382
+ messageOrigin: "system",
49200
49383
  ...leanReactionCap ? { continuationMaxTokens: leanReactionCap } : {},
49201
49384
  ...args.hidden ? { hidden: true } : {}
49202
49385
  }).catch((err) => {
@@ -49671,12 +49854,13 @@ ${stepArc}
49671
49854
  throw new Error("engagement-off: AI is disabled in settings; re-enable to send");
49672
49855
  }
49673
49856
  const existingQueue = this.pendingSends.get(sessionId2);
49857
+ const messageOrigin = resolveTurnMessageOrigin(opts);
49674
49858
  const shouldQueue = this.inflight.has(sessionId2) || existingQueue !== void 0 && existingQueue.length > 0;
49675
49859
  if (shouldQueue) {
49676
49860
  return new Promise((resolve18, reject) => {
49677
49861
  const q2 = this.pendingSends.get(sessionId2) ?? [];
49678
49862
  const tail = q2.length > 0 ? q2[q2.length - 1] : void 0;
49679
- const canMerge = opts?.coalescable === true && opts?.nudge !== true && tail?.coalescable === true && tail.nudge !== true && sameFromBucket(tail.from, opts.from);
49863
+ const canMerge = opts?.coalescable === true && opts?.nudge !== true && tail?.coalescable === true && tail.nudge !== true && tail.messageOrigin === messageOrigin && sameFromBucket(tail.from, opts.from);
49680
49864
  if (canMerge && tail) {
49681
49865
  tail.userText = `${tail.userText}
49682
49866
 
@@ -49703,6 +49887,7 @@ ${userText}`;
49703
49887
  coalescable: opts?.coalescable === true,
49704
49888
  lane: opts?.lane,
49705
49889
  ambient: opts?.ambient === true,
49890
+ messageOrigin,
49706
49891
  continuationMaxTokens: opts?.continuationMaxTokens,
49707
49892
  hidden: opts?.hidden === true,
49708
49893
  nudge: opts?.nudge === true,
@@ -49719,7 +49904,7 @@ ${userText}`;
49719
49904
  return this.runSendAndDrain(
49720
49905
  sessionId2,
49721
49906
  userText,
49722
- opts?.nudge ? { ...opts, nudge: false } : opts
49907
+ opts?.nudge ? { ...opts, nudge: false, messageOrigin } : { ...opts, messageOrigin }
49723
49908
  );
49724
49909
  }
49725
49910
  /**
@@ -49818,7 +50003,7 @@ ${userText}`;
49818
50003
  if (next.nudge) {
49819
50004
  while (q2.length > 0) {
49820
50005
  const peek = q2[0];
49821
- if (!peek.nudge || peek.hidden !== next.hidden || !sameFromBucket(peek.from, next.from)) {
50006
+ if (!peek.nudge || peek.hidden !== next.hidden || peek.messageOrigin !== next.messageOrigin || !sameFromBucket(peek.from, next.from)) {
49822
50007
  break;
49823
50008
  }
49824
50009
  q2.shift();
@@ -49841,6 +50026,7 @@ ${peek.userText}`;
49841
50026
  if (next.continuationMaxTokens) runOpts.continuationMaxTokens = next.continuationMaxTokens;
49842
50027
  if (next.hidden) runOpts.hidden = true;
49843
50028
  if (next.nudge) runOpts.nudge = true;
50029
+ runOpts.messageOrigin = next.messageOrigin;
49844
50030
  void this.runSendAndDrain(sessionId2, next.userText, runOpts).then((msg) => {
49845
50031
  for (const w of next.waiters) {
49846
50032
  try {
@@ -49871,20 +50057,36 @@ ${peek.userText}`;
49871
50057
  return this.runFixedFunctionSend(sessionId2, userText, opts);
49872
50058
  }
49873
50059
  }
50060
+ const preflightScope = ffRecord ? {
50061
+ sessionId: sessionId2,
50062
+ gezelId: ffRecord.gezelId,
50063
+ projectId: ffRecord.projectId
50064
+ } : void 0;
50065
+ const coldStartPending = preflightScope && !this.states.get(sessionId2)?.session;
50066
+ if (preflightScope && coldStartPending) {
50067
+ this.events.publish(preflightScope, {
50068
+ type: "user_message_pending",
50069
+ preview: userText.length > 160 ? `${userText.slice(0, 157)}\u2026` : userText
50070
+ });
50071
+ }
49874
50072
  const fail2 = (err) => {
49875
50073
  const message = err instanceof Error ? err.message : String(err);
49876
50074
  log52.error("error:", message);
50075
+ const publish = (event) => {
50076
+ if (preflightScope) this.events.publish(preflightScope, event);
50077
+ else this.events.publishSessionOnly(sessionId2, event);
50078
+ };
49877
50079
  if (inflightTurn.cancelled) {
49878
- this.events.publishSessionOnly(sessionId2, { type: "cancelled" });
50080
+ publish({ type: "cancelled" });
49879
50081
  } else {
49880
50082
  const detail = describeTurnError(err);
49881
- this.events.publishSessionOnly(sessionId2, {
50083
+ publish({
49882
50084
  type: "error",
49883
50085
  error: redactCredentials4(message),
49884
50086
  ...detail ? { errorDetail: detail } : {}
49885
50087
  });
49886
50088
  }
49887
- this.events.publishSessionOnly(sessionId2, { type: "done" });
50089
+ publish({ type: "done" });
49888
50090
  throw err;
49889
50091
  };
49890
50092
  if (ffRecord) {
@@ -50171,6 +50373,18 @@ ${peek.userText}`;
50171
50373
  try {
50172
50374
  let lastAssistantMessage = null;
50173
50375
  let promptForTurn = spliceIntoText(userText, pendingDigests);
50376
+ const correctivePromptForLiveRoster = (prompt, source) => {
50377
+ const safe = capabilitySafeCorrectivePrompt({
50378
+ prompt,
50379
+ availableTools: liveTurnToolNames(liveSession)
50380
+ });
50381
+ if (safe !== prompt.trim()) {
50382
+ log52.warn(
50383
+ `session ${sessionId2}: filtered ${source} corrective against the live tool roster`
50384
+ );
50385
+ }
50386
+ return safe;
50387
+ };
50174
50388
  const freshGameState = await this.refreshLeanGameState(state.record, userText);
50175
50389
  if (freshGameState) {
50176
50390
  promptForTurn = `${freshGameState}
@@ -50181,7 +50395,8 @@ ${promptForTurn}`;
50181
50395
  this.taskBudget.reset(state.record.taskRef);
50182
50396
  this.pendingBudgetNudge.delete(state.record.taskRef);
50183
50397
  }
50184
- const preludeForTurn = await this.resolveUserPromptPrelude(state, userText);
50398
+ const messageOrigin = resolveTurnMessageOrigin(opts);
50399
+ const preludeForTurn = await this.resolveUserPromptPrelude(state, userText, messageOrigin);
50185
50400
  if (preludeForTurn) {
50186
50401
  promptForTurn = `${preludeForTurn.text}
50187
50402
 
@@ -50204,7 +50419,7 @@ ${promptForTurn}`;
50204
50419
  }
50205
50420
  if (state.record.taskRef && this.pendingBudgetNudge.has(state.record.taskRef)) {
50206
50421
  this.pendingBudgetNudge.delete(state.record.taskRef);
50207
- promptForTurn = `${TASK_BUDGET_NUDGE}
50422
+ promptForTurn = `${correctivePromptForLiveRoster(TASK_BUDGET_NUDGE, "task-budget")}
50208
50423
 
50209
50424
  ${promptForTurn}`;
50210
50425
  log52.info(
@@ -50213,6 +50428,7 @@ ${promptForTurn}`;
50213
50428
  }
50214
50429
  let continuations = 0;
50215
50430
  const maxContinuations = resolveContinuationBudget(state);
50431
+ let voormanIdleNudged = false;
50216
50432
  const toolsAcrossContinuations = [];
50217
50433
  let compactionsThisSend = 0;
50218
50434
  let keurmeesterConsultedThisSend = false;
@@ -50476,6 +50692,7 @@ ${state.aboutSnapshot}`);
50476
50692
  const detectorVerdict = runPostTurnDetectors(state, {
50477
50693
  sessionId: sessionId2,
50478
50694
  isMeester: cfgForDetector?.meesterGezelId === state.record.gezelId,
50695
+ messageOrigin,
50479
50696
  userText,
50480
50697
  drained: toolsAcrossContinuations,
50481
50698
  assistantContent: assistantMessage.content,
@@ -50489,7 +50706,10 @@ ${state.aboutSnapshot}`);
50489
50706
  this.events.publish(scope, { type: "warning", message: warningMessage });
50490
50707
  }
50491
50708
  if (detectorVerdict?.promptForNextTurn) {
50492
- detectorReprompt = detectorVerdict.promptForNextTurn;
50709
+ detectorReprompt = correctivePromptForLiveRoster(
50710
+ detectorVerdict.promptForNextTurn,
50711
+ "post-turn detector"
50712
+ );
50493
50713
  }
50494
50714
  const advanceOutcome = await this.maybeAutoAdvanceOnObservableProgress(
50495
50715
  state,
@@ -50640,7 +50860,10 @@ ${state.aboutSnapshot}`);
50640
50860
  log52.info(
50641
50861
  `session ${sessionId2}: keurmeester intervention applied (case ${consult.caseId}) \u2014 granting one recovery continuation`
50642
50862
  );
50643
- promptForTurn = consult.correctivePrompt;
50863
+ promptForTurn = correctivePromptForLiveRoster(
50864
+ consult.correctivePrompt,
50865
+ "keurmeester"
50866
+ );
50644
50867
  continue;
50645
50868
  }
50646
50869
  }
@@ -50658,7 +50881,10 @@ ${state.aboutSnapshot}`);
50658
50881
  log52.info(
50659
50882
  `session ${sessionId2}: ${detectorVerdict?.reason ?? "detector"} \u2014 re-prompting`
50660
50883
  );
50661
- promptForTurn = detectorReprompt;
50884
+ promptForTurn = correctivePromptForLiveRoster(
50885
+ detectorReprompt,
50886
+ detectorVerdict ? "post-turn detector" : "deliverable salvage"
50887
+ );
50662
50888
  continue;
50663
50889
  }
50664
50890
  if (advanceOutcome.gateRejected && !advanceOutcome.gateRejected.paused && advanceOutcome.gateRejected.fingerprint !== state.lastGateNudgeFingerprint && !drained.some((d) => d.name === "ask_user_question" && d.success)) {
@@ -50668,7 +50894,10 @@ ${state.aboutSnapshot}`);
50668
50894
  log52.info(
50669
50895
  `session ${sessionId2}: ${advanceOutcome.gateRejected.taskRef} step "${advanceOutcome.gateRejected.stepId}" gate rejected \u2014 re-prompting with the verdict${stage > 0 ? ` (escalation stage ${stage})` : ""}`
50670
50896
  );
50671
- promptForTurn = stage >= 1 ? advanceOutcome.gateRejected.message : buildGateRejectionNudge(advanceOutcome.gateRejected.message);
50897
+ promptForTurn = correctivePromptForLiveRoster(
50898
+ stage >= 1 ? advanceOutcome.gateRejected.message : buildGateRejectionNudge(advanceOutcome.gateRejected.message),
50899
+ "task-gate"
50900
+ );
50672
50901
  continue;
50673
50902
  }
50674
50903
  if (deliverableOutcome.deliverableRejected?.stopRetrying) {
@@ -50719,7 +50948,10 @@ ${state.aboutSnapshot}`);
50719
50948
  log52.info(
50720
50949
  `session ${sessionId2}: keurmeester intervention applied on deliverable plateau (case ${consult.caseId}) \u2014 granting one recovery continuation`
50721
50950
  );
50722
- promptForTurn = consult.correctivePrompt;
50951
+ promptForTurn = correctivePromptForLiveRoster(
50952
+ consult.correctivePrompt,
50953
+ "keurmeester deliverable-plateau"
50954
+ );
50723
50955
  continue;
50724
50956
  }
50725
50957
  }
@@ -50743,7 +50975,10 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50743
50975
  ...dRej.filePath ? { filePath: dRej.filePath } : {},
50744
50976
  failingVerdict: dRej.message
50745
50977
  });
50746
- promptForTurn = clampNudge ?? (dStage >= 1 ? dRej.message : buildGateRejectionNudge(dRej.message));
50978
+ promptForTurn = correctivePromptForLiveRoster(
50979
+ clampNudge ?? (dStage >= 1 ? dRej.message : buildGateRejectionNudge(dRej.message)),
50980
+ "deliverable-gate"
50981
+ );
50747
50982
  continue;
50748
50983
  }
50749
50984
  if (advanceOutcome.unmetEditGate && (looksStalled(finalContent) || claimsCompletion(finalContent)) && !drained.some((d) => d.name === "ask_user_question" && d.success)) {
@@ -50751,7 +50986,10 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50751
50986
  log52.info(
50752
50987
  `session ${sessionId2}: ${advanceOutcome.unmetEditGate.taskRef} edit gate unmet (${advanceOutcome.unmetEditGate.file} not written) but reply claims progress \u2014 nudging to edit`
50753
50988
  );
50754
- promptForTurn = buildDeliverableEditNudge(advanceOutcome.unmetEditGate.file);
50989
+ promptForTurn = correctivePromptForLiveRoster(
50990
+ buildDeliverableEditNudge(advanceOutcome.unmetEditGate.file),
50991
+ "deliverable-edit"
50992
+ );
50755
50993
  continue;
50756
50994
  }
50757
50995
  const unresolvedToolFailures = unresolvedFailedToolCalls(drained);
@@ -50795,7 +51033,7 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50795
51033
  // the hot path synchronous for the common non-voorman case.
50796
51034
  // Also requires `proactive` — voorman idle nudges advance
50797
51035
  // project work past what the user asked for.
50798
- isProactiveAllowed(nudgeConfig) && state.record.providerName !== "copilot"
51036
+ isProactiveAllowed(nudgeConfig) && state.record.providerName !== "copilot" && !voormanIdleNudged
50799
51037
  ) {
50800
51038
  voormanIdleVerdict = await this.didVoormanIdleStall(state.record, drained, sessionId2);
50801
51039
  if (voormanIdleVerdict !== null) {
@@ -50813,15 +51051,19 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50813
51051
  );
50814
51052
  break;
50815
51053
  }
51054
+ if (stallReason === "voorman-idle") voormanIdleNudged = true;
50816
51055
  continuations++;
50817
51056
  log52.info(
50818
- `session ${sessionId2}: response looks stalled (${stallReason}) \u2014 nudging the model to actually execute`
51057
+ `session ${sessionId2}: response looks stalled (${stallReason}) \u2014 requesting one recovery pass`
51058
+ );
51059
+ promptForTurn = correctivePromptForLiveRoster(
51060
+ stallReason === "voorman-idle" ? buildContinuationNudge(
51061
+ finalContent,
51062
+ state.record.messages,
51063
+ voormanIdleVerdict === "nothing-built" ? VOORMAN_NOT_DONE_NUDGE : VOORMAN_IDLE_NUDGE
51064
+ ) : stallReason === "tool-only" ? CLOSING_SUMMARY_NUDGE : stallReason === "tool-read-only" ? READ_ONLY_PROGRESS_NUDGE : stallReason === "tool-failed" ? buildFailedToolRecoveryNudge(unresolvedToolFailures) : buildContinuationNudge(finalContent, state.record.messages),
51065
+ `stall-${stallReason}`
50819
51066
  );
50820
- promptForTurn = stallReason === "voorman-idle" ? buildContinuationNudge(
50821
- finalContent,
50822
- state.record.messages,
50823
- voormanIdleVerdict === "nothing-built" ? VOORMAN_NOT_DONE_NUDGE : VOORMAN_IDLE_NUDGE
50824
- ) : stallReason === "tool-only" ? CLOSING_SUMMARY_NUDGE : stallReason === "tool-read-only" ? READ_ONLY_PROGRESS_NUDGE : stallReason === "tool-failed" ? buildFailedToolRecoveryNudge(unresolvedToolFailures) : buildContinuationNudge(finalContent, state.record.messages);
50825
51067
  continue;
50826
51068
  }
50827
51069
  }
@@ -51387,7 +51629,7 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
51387
51629
  return null;
51388
51630
  }
51389
51631
  const tasks = await this.store.listProjectTasks(record.projectId).catch(() => []);
51390
- if (tasks.length > 0 && !tasks.some((t) => t.status === "active")) {
51632
+ if (tasks.length === 0 || !tasks.some((t) => t.status === "active")) {
51391
51633
  return null;
51392
51634
  }
51393
51635
  const workspaceFiles = await this.store.listProjectWorkspaceRecursive(record.projectId).catch(() => []);
@@ -54138,7 +54380,7 @@ ${baseSystem}` : baseSystem;
54138
54380
  return { modelId };
54139
54381
  }
54140
54382
  }
54141
- async resolveUserPromptPrelude(state, userText) {
54383
+ async resolveUserPromptPrelude(state, userText, messageOrigin) {
54142
54384
  const profile = state.profile;
54143
54385
  if (!profile) return null;
54144
54386
  const cfg = await this.store.readConfig().catch(() => null);
@@ -54147,6 +54389,9 @@ ${baseSystem}` : baseSystem;
54147
54389
  ...modelCtxFromProfile(profile, state),
54148
54390
  sessionId: state.record.id,
54149
54391
  isMeester,
54392
+ projectId: state.record.projectId,
54393
+ messageOrigin,
54394
+ availableToolNames: liveTurnToolNames(state.session),
54150
54395
  userText,
54151
54396
  drained: [],
54152
54397
  assistantContent: "",
@@ -55467,6 +55712,7 @@ Allow **${info.toolName}** this time?`,
55467
55712
  GEZEL_SESSION_ID: record.id,
55468
55713
  GEZEL_HOME: this.home,
55469
55714
  GEZEL_EXECUTION_DENSITY: executionDensity,
55715
+ GEZEL_ROLE_BASED_NAME_ONLY_MODE: record.roleBasedNameOnlyMode ?? globalConfig.roleBasedNameOnlyMode ?? false ? "1" : "0",
55470
55716
  ...record.expectedDeliverable ? { GEZEL_EXPECTED_DELIVERABLE: JSON.stringify(record.expectedDeliverable) } : {},
55471
55717
  ...record.stepId ? { GEZEL_STEP_ID: record.stepId } : {},
55472
55718
  // Scope task: lets the MCP task tools default to / recover toward
@@ -55906,6 +56152,9 @@ function runPostTurnDetectors(state, args) {
55906
56152
  ...modelCtxFromProfile(profile, state),
55907
56153
  sessionId: args.sessionId,
55908
56154
  isMeester: args.isMeester,
56155
+ projectId: state.record.projectId,
56156
+ messageOrigin: args.messageOrigin,
56157
+ availableToolNames: liveTurnToolNames(state.session),
55909
56158
  userText: args.userText,
55910
56159
  drained: args.drained,
55911
56160
  assistantContent: args.assistantContent,
@@ -56154,6 +56403,14 @@ function messageExpressesModifyIntent(text) {
56154
56403
  )
56155
56404
  );
56156
56405
  }
56406
+ function fileHandoffIntentKey(text) {
56407
+ const repair = messageExpressesModifyIntent(text) || /\[scenario check\]|\b(?:specific failure|signals? that (?:didn't|did not) fire|success criteria (?:aren't|are not) met|repeat miss|gate_full_rewrite|rejected)\b/i.test(
56408
+ text
56409
+ );
56410
+ if (!repair) return "create";
56411
+ const normalized2 = text.toLowerCase().replace(/\s+/g, " ").trim();
56412
+ return `repair:${createHash12("sha256").update(normalized2).digest("hex").slice(0, 16)}`;
56413
+ }
56157
56414
  function isSubstantiveExistingWorkspaceFile(filePath2, content) {
56158
56415
  const trimmed = content.trim();
56159
56416
  if (!trimmed) return false;
@@ -56438,13 +56695,18 @@ Drop conversational filler, pleasantries, and verbatim tool outputs (just note "
56438
56695
  Earlier conversation:
56439
56696
 
56440
56697
  `;
56441
- var VOORMAN_IDLE_NUDGE = 'You\'re the project voorman and this turn ended with no handoff or proactive tool. If there is live work to move, pick one now: `advance_task_step` (next step), `message_gezel` (ping the next gezel), `assign_task` / `ensure_gezel` (wire up an assignee), or `ask_user_question` (surface a real blocker). But if the project is finished, or stable, or waiting on the user, that is already a COMPLETE turn \u2014 reply with ONE short sentence saying so (e.g. "From my perspective the project is done / in a stable state.") and stop. A plain "it\'s done" with no tool call is the correct answer there: it is not an idle failure and not a fabrication. Do not re-verify, do not relitigate the options, do not promise future action.';
56698
+ var VOORMAN_IDLE_NUDGE = "Before you finish, make one quick project check: is there concrete work already in flight that this turn should move? If yes, take the natural next action with the most relevant available tool \u2014 advance a ready step, message or assign the right gezel, or surface a real blocker. If no \u2014 because the project is complete, stable, or waiting on the user \u2014 say that once in one short sentence and end the turn. Do not invent work or call a tool merely to satisfy this check. Do not re-verify or promise future action.";
56442
56699
  var BOOTSTRAP_WORKSPACE_FILES = /* @__PURE__ */ new Set([
56443
56700
  "package.json",
56701
+ "package-lock.json",
56702
+ "pnpm-lock.yaml",
56703
+ "yarn.lock",
56704
+ "bun.lock",
56705
+ "bun.lockb",
56444
56706
  "tsconfig.json",
56445
56707
  ".gitignore"
56446
56708
  ]);
56447
- var VOORMAN_NOT_DONE_NUDGE = "You're the project voorman and this turn ended with no handoff or proactive tool. The workspace still holds only its bootstrap files \u2014 nothing has been built yet, so the project is NOT done, complete, or stable. Do NOT reply that it is. Move the work this turn: `message_gezel` the Builder/Developer who owns this task (or `ensure_gezel` for one first if none exists), with the exact deliverable path and acceptance criteria, and ask them to write the file and reply with the path; or `advance_task_step` if a step is genuinely ready; or, if you are truly blocked on a decision, `ask_user_question` to surface it. Pick one tool \u2014 a prose \"it's stable / done\" over an empty workspace is a false claim, not a complete turn.";
56709
+ var VOORMAN_NOT_DONE_NUDGE = "One project-state check before you finish: an active task remains, but the workspace still contains only setup files, so no deliverable has been built yet. If the user only asked for status or conversation, answer accurately and stop; a tool is not required. If this turn is supposed to move the active work, take one natural next action: message or assign the specialist who owns the deliverable, advance a genuinely ready step, or surface a real blocker with `ask_user_question`. Do not invent work or call a tool merely to appear busy, and do not describe an active empty project as done or stable.";
56448
56710
  var READ_ONLY_MCP_TOOLS = /* @__PURE__ */ new Set([
56449
56711
  "search_memory",
56450
56712
  "list_memories",
@@ -56645,7 +56907,7 @@ function growthSignature(gezel) {
56645
56907
 
56646
56908
  // src/codex-setup/manager.ts
56647
56909
  init_atomic();
56648
- import { createHash as createHash12 } from "crypto";
56910
+ import { createHash as createHash13 } from "crypto";
56649
56911
  import { realpathSync as realpathSync2 } from "fs";
56650
56912
  import { chmod as chmod4, mkdir as mkdir15, readFile as readFile16, rm as rm6 } from "fs/promises";
56651
56913
  import { homedir as homedir3 } from "os";
@@ -57396,7 +57658,7 @@ function canonicalPath(path2) {
57396
57658
  }
57397
57659
  }
57398
57660
  function sha256(value) {
57399
- return createHash12("sha256").update(value).digest("hex");
57661
+ return createHash13("sha256").update(value).digest("hex");
57400
57662
  }
57401
57663
  function catalogModelFor(model) {
57402
57664
  const contextWindow = model.contextWindow ?? 32768;
@@ -57821,7 +58083,7 @@ init_mcp_bridge();
57821
58083
 
57822
58084
  // src/connectors/writer.ts
57823
58085
  init_atomic();
57824
- import { createHash as createHash13 } from "crypto";
58086
+ import { createHash as createHash14 } from "crypto";
57825
58087
  import { mkdir as mkdir16, readFile as readFile17, readdir as readdir9, rm as rm7, stat as stat11 } from "fs/promises";
57826
58088
  import { basename as basename7, join as join30 } from "path";
57827
58089
  import { projectLocalQuarantineDir } from "@bendyline/gezel/paths";
@@ -58094,7 +58356,7 @@ var StaticContentScanner = class {
58094
58356
  var contentScanner = new StaticContentScanner();
58095
58357
 
58096
58358
  // src/connectors/writer.ts
58097
- var sha8 = (s) => createHash13("sha256").update(s).digest("hex").slice(0, 8);
58359
+ var sha8 = (s) => createHash14("sha256").update(s).digest("hex").slice(0, 8);
58098
58360
  function slug(input, max = 48) {
58099
58361
  const s = input.normalize("NFKD").replace(/[^\x20-\x7E]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, max).replace(/-+$/g, "");
58100
58362
  return s || "x";
@@ -58111,7 +58373,7 @@ function contentHash(record) {
58111
58373
  const attachments = (record.attachments ?? []).map((attachment) => ({
58112
58374
  filename: attachment.filename,
58113
58375
  size: attachment.size ?? attachment.content?.byteLength ?? 0,
58114
- sha256: attachment.sha256 ?? createHash13("sha256").update(attachment.content).digest("hex")
58376
+ sha256: attachment.sha256 ?? createHash14("sha256").update(attachment.content).digest("hex")
58115
58377
  }));
58116
58378
  return sha8(
58117
58379
  `${JSON.stringify(sortedFrontmatter)}
@@ -59182,10 +59444,10 @@ var SpectralConnectorAdapter = class {
59182
59444
  };
59183
59445
 
59184
59446
  // src/connectors/oauth.ts
59185
- import { createHash as createHash14, randomBytes as randomBytes3 } from "crypto";
59447
+ import { createHash as createHash15, randomBytes as randomBytes3 } from "crypto";
59186
59448
  function createPkce() {
59187
59449
  const verifier = randomBytes3(32).toString("base64url");
59188
- const challenge = createHash14("sha256").update(verifier).digest("base64url");
59450
+ const challenge = createHash15("sha256").update(verifier).digest("base64url");
59189
59451
  return { verifier, challenge };
59190
59452
  }
59191
59453
  function randomState() {
@@ -63870,7 +64132,7 @@ function registerGitHubPullsAdapters(runtime) {
63870
64132
  }
63871
64133
 
63872
64134
  // src/connectors/natives/github-releases.ts
63873
- import { createHash as createHash15 } from "crypto";
64135
+ import { createHash as createHash16 } from "crypto";
63874
64136
  import { mkdir as mkdir21, mkdtemp as mkdtemp3, open as open2, rm as rm13 } from "fs/promises";
63875
64137
  import { tmpdir as tmpdir3 } from "os";
63876
64138
  import { basename as basename8, join as join35 } from "path";
@@ -64063,7 +64325,7 @@ var GitHubReleasesAdapter = class {
64063
64325
  }
64064
64326
  }
64065
64327
  this.releases = releases;
64066
- this.fingerprint = createHash15("sha256").update(JSON.stringify(releases)).digest("hex");
64328
+ this.fingerprint = createHash16("sha256").update(JSON.stringify(releases)).digest("hex");
64067
64329
  }
64068
64330
  apiHeaders(accept) {
64069
64331
  return {
@@ -64126,7 +64388,7 @@ var GitHubReleasesAdapter = class {
64126
64388
  };
64127
64389
  async function streamResponseToFile(response, path2) {
64128
64390
  const handle = await open2(path2, "wx");
64129
- const hash = createHash15("sha256");
64391
+ const hash = createHash16("sha256");
64130
64392
  let size = 0;
64131
64393
  try {
64132
64394
  for await (const raw of response.body) {
@@ -66506,7 +66768,7 @@ var DebugFlag = class {
66506
66768
 
66507
66769
  // src/digest/generator.ts
66508
66770
  init_atomic();
66509
- import { createHash as createHash16 } from "crypto";
66771
+ import { createHash as createHash17 } from "crypto";
66510
66772
  import { readFile as readFile25 } from "fs/promises";
66511
66773
  import { join as join40 } from "path";
66512
66774
  import {
@@ -69655,7 +69917,7 @@ var ProjectDigestGenerator = class {
69655
69917
  if (commits.length === 0 && sessions.length === 0 && events.length < MIN_EVENTS_FOR_DIGEST) {
69656
69918
  return false;
69657
69919
  }
69658
- const inputHash = createHash16("sha256").update(
69920
+ const inputHash = createHash17("sha256").update(
69659
69921
  [
69660
69922
  week,
69661
69923
  ...commits,
@@ -69812,7 +70074,7 @@ function describe4(err) {
69812
70074
  }
69813
70075
 
69814
70076
  // src/engines/electron-native-reuse.ts
69815
- import { createHash as createHash17 } from "crypto";
70077
+ import { createHash as createHash18 } from "crypto";
69816
70078
  import { createReadStream as createReadStream3 } from "fs";
69817
70079
  import { lstat as lstat2, readdir as readdir13, readlink } from "fs/promises";
69818
70080
  import { basename as basename11, join as join41, posix, relative as relative5, resolve as resolve8, sep as sep5 } from "path";
@@ -71432,7 +71694,7 @@ function enclosingMacApp(path2) {
71432
71694
  var HASH_READ_BUFFER_BYTES = 16 * 1024 * 1024;
71433
71695
  function sha256File2(path2) {
71434
71696
  return new Promise((resolveHash, reject) => {
71435
- const hash = createHash17("sha256");
71697
+ const hash = createHash18("sha256");
71436
71698
  const stream = createReadStream3(path2, { highWaterMark: HASH_READ_BUFFER_BYTES });
71437
71699
  stream.on("data", (chunk2) => hash.update(chunk2));
71438
71700
  stream.on("error", reject);
@@ -71444,7 +71706,7 @@ function sha256File2(path2) {
71444
71706
  import { createLogger as createLogger69 } from "@bendyline/gezel";
71445
71707
 
71446
71708
  // src/engines/resolver.ts
71447
- import { createHash as createHash18 } from "crypto";
71709
+ import { createHash as createHash19 } from "crypto";
71448
71710
  import { createReadStream as createReadStream4, existsSync as existsSync7 } from "fs";
71449
71711
  import { chmod as chmod5, mkdir as mkdir24, readFile as readFile26, rename as rename7, rm as rm16, writeFile as writeFile9 } from "fs/promises";
71450
71712
  import { join as join42 } from "path";
@@ -71568,7 +71830,7 @@ async function* resolveEngine(opts) {
71568
71830
  );
71569
71831
  }
71570
71832
  const sumsBytes = await ghBytes(sumsAsset.url, baseFetch, token);
71571
- const sumsDigest = createHash18("sha256").update(sumsBytes).digest("hex");
71833
+ const sumsDigest = createHash19("sha256").update(sumsBytes).digest("hex");
71572
71834
  if (!unpinned && sumsDigest !== expectedDigest) {
71573
71835
  yield* fail(
71574
71836
  `SHA256SUMS digest mismatch for ${tag}: this build pins ${expectedDigest.slice(0, 16)}\u2026 but the release served ${sumsDigest.slice(0, 16)}\u2026. Refusing \u2014 the release may have been tampered with or re-cut.`
@@ -71818,7 +72080,7 @@ function parseSums(text) {
71818
72080
  }
71819
72081
  var HASH_READ_BUFFER_BYTES2 = 16 * 1024 * 1024;
71820
72082
  async function sha256File3(path2) {
71821
- const hash = createHash18("sha256");
72083
+ const hash = createHash19("sha256");
71822
72084
  await new Promise((resolve18, reject) => {
71823
72085
  const stream = createReadStream4(path2, { highWaterMark: HASH_READ_BUFFER_BYTES2 });
71824
72086
  stream.on("data", (chunk2) => hash.update(chunk2));
@@ -72701,7 +72963,7 @@ function safeBasename(name) {
72701
72963
  }
72702
72964
 
72703
72965
  // src/fs/store.ts
72704
- import { createHash as createHash20, randomUUID as randomUUID10 } from "crypto";
72966
+ import { createHash as createHash21, randomUUID as randomUUID10 } from "crypto";
72705
72967
  import {
72706
72968
  appendFile as appendFile4,
72707
72969
  chmod as chmod7,
@@ -73110,7 +73372,7 @@ function countDiffLines(diff) {
73110
73372
  }
73111
73373
 
73112
73374
  // src/workspace/journal.ts
73113
- import { createHash as createHash19 } from "crypto";
73375
+ import { createHash as createHash20 } from "crypto";
73114
73376
  import { appendFile as appendFile2, mkdir as mkdir27, readFile as readFile29 } from "fs/promises";
73115
73377
  import { dirname as dirname26, join as join43 } from "path";
73116
73378
  import { projectPrivateDir as projectPrivateDir2 } from "@bendyline/gezel/paths";
@@ -73153,7 +73415,7 @@ async function readJournalTail(home, projectId, limit = 100) {
73153
73415
  }
73154
73416
  }
73155
73417
  function sha2562(content) {
73156
- return createHash19("sha256").update(content).digest("hex");
73418
+ return createHash20("sha256").update(content).digest("hex");
73157
73419
  }
73158
73420
 
73159
73421
  // src/workspace/template.ts
@@ -74020,7 +74282,7 @@ function boekwachterIssueFingerprint(path2, issue) {
74020
74282
  issue.category.trim().toLocaleLowerCase(),
74021
74283
  issue.message.trim().toLocaleLowerCase().replace(/\s+/g, " ")
74022
74284
  ].join("\0");
74023
- return createHash20("sha256").update(normalized2).digest("hex");
74285
+ return createHash21("sha256").update(normalized2).digest("hex");
74024
74286
  }
74025
74287
  var ConfigCorruptionError = class extends Error {
74026
74288
  code = "CONFIG_CORRUPT";
@@ -75768,6 +76030,7 @@ ${p.content.trim()}`).join("\n\n---\n\n");
75768
76030
  async createProject(input, opts) {
75769
76031
  const release2 = await this.acquireProjectCreationLock();
75770
76032
  try {
76033
+ if (input.workingDir) this.assertSafeWorkingDir(input.workingDir);
75771
76034
  const id = opts?.id ?? await this.uniqueProjectId(slugify(input.name) || randomUUID10().slice(0, 8));
75772
76035
  if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
75773
76036
  throw new Error(`invalid project id "${id}"`);
@@ -75791,14 +76054,22 @@ ${p.content.trim()}`).join("\n\n---\n\n");
75791
76054
  projectName: input.name
75792
76055
  });
75793
76056
  }
76057
+ let github = input.github?.url ? { url: input.github.url } : void 0;
76058
+ if (input.workingDir) {
76059
+ github = await autoDetectGitHubLink(input.workingDir, github) ?? github;
76060
+ }
75794
76061
  const project = {
75795
76062
  id,
75796
76063
  name: input.name,
75797
76064
  description: input.description,
75798
76065
  ...input.workingDir ? { workingDir: input.workingDir } : {},
76066
+ // Opening an existing folder is an intake action, not a declaration
76067
+ // that its objectives are ready for active supervision. Keep the
76068
+ // Meester quiet until the user explicitly opts this project back in.
76069
+ ...input.workingDir ? { nudgeConfig: { enabled: false } } : {},
75799
76070
  ...input.mode && input.mode !== "crew" ? { mode: input.mode } : {},
75800
76071
  ...input.indexingEnabled !== void 0 ? { indexingEnabled: input.indexingEnabled } : {},
75801
- ...input.github?.url ? { github: { url: input.github.url } } : {},
76072
+ ...github ? { github } : {},
75802
76073
  createdAt: nowIso5(),
75803
76074
  updatedAt: nowIso5()
75804
76075
  };
@@ -79473,7 +79744,7 @@ import { projectCodeReviewsFile } from "@bendyline/gezel/paths";
79473
79744
  import { createLogger as createLogger77, projectAllowsAmbientWork } from "@bendyline/gezel";
79474
79745
 
79475
79746
  // src/tasks/manager.ts
79476
- import { createHash as createHash21, randomUUID as randomUUID12 } from "crypto";
79747
+ import { createHash as createHash22, randomUUID as randomUUID12 } from "crypto";
79477
79748
  import {
79478
79749
  DEFAULT_NIGHT_SHIFT_WINDOW,
79479
79750
  MANAGED_WORKSPACE_WRITE_SETTING_LABEL as MANAGED_WORKSPACE_WRITE_SETTING_LABEL4,
@@ -80250,6 +80521,7 @@ var TaskManager = class {
80250
80521
  ...cron ? { cron } : {},
80251
80522
  ...nightShift ? { nightShift } : {},
80252
80523
  ...fanout ? { fanout } : {},
80524
+ ...input.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: input.roleBasedNameOnlyMode } : {},
80253
80525
  createdAt: now,
80254
80526
  updatedAt: now,
80255
80527
  createdBy: input.createdBy ?? { kind: "user" }
@@ -81324,7 +81596,7 @@ ${paramLines}` : ""}`,
81324
81596
  if (step.advanceWhen?.file) {
81325
81597
  const content = await (step.advanceWhen.artifact ? this.store.readProjectArtifact(projectId, step.advanceWhen.file) : this.store.readProjectWorkspaceFile(projectId, step.advanceWhen.file)).catch(() => null);
81326
81598
  if (content !== null) {
81327
- contentHash2 = createHash21("sha256").update(content).digest("hex");
81599
+ contentHash2 = createHash22("sha256").update(content).digest("hex");
81328
81600
  }
81329
81601
  }
81330
81602
  if (contentHash2 !== void 0 && step.lastGateReject?.contentHash !== void 0 && step.lastGateReject.contentHash === contentHash2) {
@@ -82244,6 +82516,7 @@ Fix one of these, then set the task active again:
82244
82516
  ...parent.nightShift?.enabled ? { nightShift: { enabled: true } } : {},
82245
82517
  activeStepId,
82246
82518
  parentTaskRef: parent.ref,
82519
+ ...parent.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: parent.roleBasedNameOnlyMode } : {},
82247
82520
  createdAt: now,
82248
82521
  updatedAt: now,
82249
82522
  createdBy: parent.createdBy
@@ -82990,7 +83263,7 @@ function sumStat(s, key) {
82990
83263
  }
82991
83264
 
82992
83265
  // src/grants/manager.ts
82993
- import { createHash as createHash22, randomBytes as randomBytes5, randomUUID as randomUUID13, timingSafeEqual } from "crypto";
83266
+ import { createHash as createHash23, randomBytes as randomBytes5, randomUUID as randomUUID13, timingSafeEqual } from "crypto";
82994
83267
  import { chmod as chmod8 } from "fs/promises";
82995
83268
  import { pendingGrantsFile as defaultPendingGrantsFile } from "@bendyline/gezel/paths";
82996
83269
  var PENDING_GRANT_TTL_MS = 10 * 6e4;
@@ -83062,7 +83335,7 @@ function generateVerificationCode() {
83062
83335
  return `${raw.slice(0, 3)}-${raw.slice(3)}`;
83063
83336
  }
83064
83337
  function hashVerificationCode(salt, normalizedCode) {
83065
- return createHash22("sha256").update(salt, "base64url").update("\0").update(normalizedCode, "ascii").digest();
83338
+ return createHash23("sha256").update(salt, "base64url").update("\0").update(normalizedCode, "ascii").digest();
83066
83339
  }
83067
83340
  function codeMatches(grant, suppliedCode) {
83068
83341
  const normalized2 = normalizeVerificationCode(suppliedCode);
@@ -84982,7 +85255,7 @@ function tokenize2(question) {
84982
85255
  }
84983
85256
 
84984
85257
  // src/http/cert.ts
84985
- import { createHash as createHash23 } from "crypto";
85258
+ import { createHash as createHash24 } from "crypto";
84986
85259
  import { generate as selfsignedGenerate } from "selfsigned";
84987
85260
  var CERT_VALIDITY_DAYS = 90;
84988
85261
  async function generateLoopbackCert() {
@@ -85019,7 +85292,7 @@ async function generateLoopbackCert() {
85019
85292
  ]
85020
85293
  });
85021
85294
  const der = pemToDer(pems.cert);
85022
- const digest = createHash23("sha256").update(der).digest();
85295
+ const digest = createHash24("sha256").update(der).digest();
85023
85296
  return {
85024
85297
  certPem: pems.cert,
85025
85298
  keyPem: pems.private,
@@ -85033,14 +85306,14 @@ function pemToDer(pem) {
85033
85306
  }
85034
85307
 
85035
85308
  // src/http/codex-bridge-port.ts
85036
- import { createHash as createHash24 } from "crypto";
85309
+ import { createHash as createHash25 } from "crypto";
85037
85310
  import { realpathSync as realpathSync3 } from "fs";
85038
85311
  import { resolve as resolve10 } from "path";
85039
85312
  var CODEX_BRIDGE_PORT_RANGE_START = 2e4;
85040
85313
  var CODEX_BRIDGE_PORT_RANGE_END = 49151;
85041
85314
  var CODEX_BRIDGE_PORT_RANGE_SIZE = CODEX_BRIDGE_PORT_RANGE_END - CODEX_BRIDGE_PORT_RANGE_START + 1;
85042
85315
  function codexBridgePortForHome(home) {
85043
- const digest = createHash24("sha256").update(canonicalPath2(home)).digest();
85316
+ const digest = createHash25("sha256").update(canonicalPath2(home)).digest();
85044
85317
  const bucket = digest.readUInt32BE(0) % CODEX_BRIDGE_PORT_RANGE_SIZE;
85045
85318
  return CODEX_BRIDGE_PORT_RANGE_START + bucket;
85046
85319
  }
@@ -85266,7 +85539,7 @@ async function loadGezelByRef(ref, ctx) {
85266
85539
  import { randomUUID as randomUUID15 } from "crypto";
85267
85540
 
85268
85541
  // src/http/openai-compat/responses-translate.ts
85269
- import { createHash as createHash25 } from "crypto";
85542
+ import { createHash as createHash26 } from "crypto";
85270
85543
  import { z as z11 } from "zod";
85271
85544
 
85272
85545
  // src/http/openai-compat/translate.ts
@@ -85705,7 +85978,7 @@ var CUSTOM_TOOL_INPUT_KEY = "input";
85705
85978
  function flattenResponsesNamespaceToolName(namespace, name) {
85706
85979
  const joined = `${namespace}__${name}`;
85707
85980
  if (joined.length <= MAX_LOCAL_TOOL_NAME_LENGTH) return joined;
85708
- const digest = createHash25("sha256").update(namespace).update("\0").update(name).digest("hex").slice(0, 12);
85981
+ const digest = createHash26("sha256").update(namespace).update("\0").update(name).digest("hex").slice(0, 12);
85709
85982
  const prefixLength = MAX_LOCAL_TOOL_NAME_LENGTH - digest.length - 2;
85710
85983
  return `${joined.slice(0, prefixLength)}__${digest}`;
85711
85984
  }
@@ -86935,7 +87208,7 @@ function mountMachineEngineHints(app) {
86935
87208
  }
86936
87209
 
86937
87210
  // src/http/preview-capability.ts
86938
- import { createHash as createHash26, randomBytes as randomBytes6 } from "crypto";
87211
+ import { createHash as createHash27, randomBytes as randomBytes6 } from "crypto";
86939
87212
  import { posix as posix3 } from "path";
86940
87213
  var DEFAULT_PREVIEW_CAPABILITY_TTL_MS = 10 * 60 * 1e3;
86941
87214
  var DEFAULT_PREVIEW_CAPABILITY_ABSOLUTE_TTL_MS = 2 * 60 * 60 * 1e3;
@@ -87079,7 +87352,7 @@ function pathIsInScope(path2, scopePath) {
87079
87352
  return scopePath === "" || path2 === scopePath || path2.startsWith(`${scopePath}/`);
87080
87353
  }
87081
87354
  function tokenDigest(token) {
87082
- return createHash26("sha256").update(token).digest("base64url");
87355
+ return createHash27("sha256").update(token).digest("base64url");
87083
87356
  }
87084
87357
 
87085
87358
  // src/http/routes/ai.ts
@@ -87448,6 +87721,15 @@ var KOKORO_HF_REPO = "onnx-community/Kokoro-82M-v1.0-ONNX";
87448
87721
  var KOKORO_APPROX_BYTES_BY_ID = {
87449
87722
  [KOKORO_DEFAULT_MODEL_ID]: 95e6
87450
87723
  };
87724
+ function isKokoroRuntimeAvailable(resolveModule = (specifier) => import.meta.resolve(specifier)) {
87725
+ try {
87726
+ resolveModule("kokoro-js");
87727
+ resolveModule("@huggingface/transformers");
87728
+ return true;
87729
+ } catch {
87730
+ return false;
87731
+ }
87732
+ }
87451
87733
  var KOKORO_DEFAULT_VOICES = [
87452
87734
  {
87453
87735
  id: "af_heart",
@@ -87777,7 +88059,7 @@ function parseGender(s) {
87777
88059
  }
87778
88060
 
87779
88061
  // src/providers/audio/whisper-cpp.ts
87780
- import { createHash as createHash27 } from "crypto";
88062
+ import { createHash as createHash28 } from "crypto";
87781
88063
  import { createReadStream as createReadStream5 } from "fs";
87782
88064
  import { mkdir as mkdir37, readFile as readFile39, rename as rename11, rm as rm25, writeFile as writeFile14 } from "fs/promises";
87783
88065
  import { basename as basename12, join as join52 } from "path";
@@ -88072,7 +88354,7 @@ async function* downloadWithSha256(fetchImpl, opts) {
88072
88354
  yield { type: "progress", bytesWritten: writtenAll, totalBytes: totalAllBytes };
88073
88355
  }
88074
88356
  }
88075
- const hasher = createHash27("sha256");
88357
+ const hasher = createHash28("sha256");
88076
88358
  await new Promise((resolve18, reject) => {
88077
88359
  const stream = createReadStream5(tmpPath, { highWaterMark: MODEL_HASH_READ_BUFFER_BYTES });
88078
88360
  stream.on("data", (chunk2) => hasher.update(chunk2));
@@ -88123,6 +88405,41 @@ function machineEngineProxy(ctx, sourcePrefix, targetPrefix, localPaths = [], sh
88123
88405
 
88124
88406
  // src/http/routes/audio.ts
88125
88407
  var log85 = createLogger88("audio");
88408
+ function buildAudioCatalog(options = {}) {
88409
+ const kokoroRuntimeAvailable = options.kokoroRuntimeAvailable ?? isKokoroRuntimeAvailable();
88410
+ return {
88411
+ // All Whisper weights ship under MIT (whisper.cpp / OpenAI Whisper);
88412
+ // Kokoro under Apache 2.0. Both are permissive → "Free, open".
88413
+ stt: WHISPER_MODEL_CATALOG.map((m) => ({
88414
+ id: m.id,
88415
+ name: m.name,
88416
+ description: m.description,
88417
+ approxSizeBytes: m.approxSizeBytes,
88418
+ kind: "stt",
88419
+ license: "MIT",
88420
+ licenseClass: "open",
88421
+ licenseShortName: "MIT",
88422
+ licenseUrl: "https://huggingface.co/ggerganov/whisper.cpp",
88423
+ // The Base (English) variant is the recommended STT default: ~140 MB,
88424
+ // realtime on laptop CPUs. Curated recoScore → ★ badge + auto-pick.
88425
+ ...m.id === "whisper-base.en" ? { recoScore: 20 } : {}
88426
+ })),
88427
+ tts: kokoroRuntimeAvailable ? [
88428
+ {
88429
+ id: KOKORO_DEFAULT_MODEL_ID,
88430
+ name: "Kokoro 82M v1.0",
88431
+ description: "Apache 2.0 TTS \u2014 54+ voices, ~80MB quantized, near-realtime on M1 / modern desktop CPUs.",
88432
+ approxSizeBytes: 95e6,
88433
+ kind: "tts",
88434
+ license: "Apache 2.0",
88435
+ licenseClass: "open",
88436
+ licenseShortName: "Apache 2.0",
88437
+ licenseUrl: "https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX",
88438
+ recoScore: 20
88439
+ }
88440
+ ] : []
88441
+ };
88442
+ }
88126
88443
  function audioRoutes(ctx) {
88127
88444
  const app = new Hono4();
88128
88445
  app.use(
@@ -88244,38 +88561,7 @@ function audioRoutes(ctx) {
88244
88561
  });
88245
88562
  });
88246
88563
  app.get("/catalog", (c) => {
88247
- return c.json({
88248
- // All Whisper weights ship under MIT (whisper.cpp / OpenAI Whisper);
88249
- // Kokoro under Apache 2.0. Both are permissive → "Free, open".
88250
- stt: WHISPER_MODEL_CATALOG.map((m) => ({
88251
- id: m.id,
88252
- name: m.name,
88253
- description: m.description,
88254
- approxSizeBytes: m.approxSizeBytes,
88255
- kind: "stt",
88256
- license: "MIT",
88257
- licenseClass: "open",
88258
- licenseShortName: "MIT",
88259
- licenseUrl: "https://huggingface.co/ggerganov/whisper.cpp",
88260
- // The Base (English) variant is the recommended STT default: ~140 MB,
88261
- // realtime on laptop CPUs. Curated recoScore → ★ badge + auto-pick.
88262
- ...m.id === "whisper-base.en" ? { recoScore: 20 } : {}
88263
- })),
88264
- tts: [
88265
- {
88266
- id: KOKORO_DEFAULT_MODEL_ID,
88267
- name: "Kokoro 82M v1.0",
88268
- description: "Apache 2.0 TTS \u2014 54+ voices, ~80MB quantized, near-realtime on M1 / modern desktop CPUs.",
88269
- approxSizeBytes: 95e6,
88270
- kind: "tts",
88271
- license: "Apache 2.0",
88272
- licenseClass: "open",
88273
- licenseShortName: "Apache 2.0",
88274
- licenseUrl: "https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX",
88275
- recoScore: 20
88276
- }
88277
- ]
88278
- });
88564
+ return c.json(buildAudioCatalog());
88279
88565
  });
88280
88566
  app.get("/stt/models", async (c) => {
88281
88567
  const provider = await ctx.stt.current();
@@ -90247,6 +90533,8 @@ function configRoutes(ctx) {
90247
90533
  autoUpdateChecks: config.autoUpdateChecks !== false,
90248
90534
  quitOnClose: config.quitOnClose === true,
90249
90535
  themePref: config.themePref,
90536
+ cliShowThinking: config.cliShowThinking !== false,
90537
+ cliShowWrites: config.cliShowWrites === true,
90250
90538
  documentExportOptions: config.documentExportOptions,
90251
90539
  sidebarSide: config.sidebarSide,
90252
90540
  homeGreetingCollapsed: config.homeGreetingCollapsed === true,
@@ -90593,6 +90881,8 @@ function configRoutes(ctx) {
90593
90881
  autoUpdateChecks: updated.autoUpdateChecks !== false,
90594
90882
  quitOnClose: updated.quitOnClose === true,
90595
90883
  themePref: updated.themePref,
90884
+ cliShowThinking: updated.cliShowThinking !== false,
90885
+ cliShowWrites: updated.cliShowWrites === true,
90596
90886
  documentExportOptions: updated.documentExportOptions,
90597
90887
  sidebarSide: updated.sidebarSide,
90598
90888
  homeGreetingCollapsed: updated.homeGreetingCollapsed === true,
@@ -91982,7 +92272,11 @@ function ds4Routes(ctx) {
91982
92272
  const catalogId = c.req.param("catalogId");
91983
92273
  const skipShaRaw = c.req.query("skipSha");
91984
92274
  const skipSha = skipShaRaw != null && skipShaRaw !== "" && skipShaRaw !== "0" && skipShaRaw !== "false";
91985
- ctx.chatInstalls.ds4.start(catalogId, { skipSha, includeMmproj: false });
92275
+ ctx.chatInstalls.ds4.start(catalogId, {
92276
+ skipSha,
92277
+ includeMmproj: false,
92278
+ installCompanion: false
92279
+ });
91986
92280
  return streamSSE5(c, (stream) => subscribeToInstallSse(ctx.chatInstalls.ds4, catalogId, stream));
91987
92281
  });
91988
92282
  app.delete("/models/:catalogId/install", (c) => {
@@ -93670,6 +93964,7 @@ function gezelRoutes(ctx) {
93670
93964
  toGezelIdOrName: toId,
93671
93965
  projectId: body.projectId,
93672
93966
  text: body.text,
93967
+ ...body.suppressReply ? { suppressReply: true } : {},
93673
93968
  ...body.expectedDeliverable ? { expectedDeliverable: body.expectedDeliverable } : {}
93674
93969
  });
93675
93970
  return c.json({
@@ -94611,7 +94906,7 @@ import { HandboekRenderModeSchema } from "@bendyline/gezel";
94611
94906
  import { Hono as Hono29 } from "hono";
94612
94907
 
94613
94908
  // src/handboek/narration.ts
94614
- import { createHash as createHash28 } from "crypto";
94909
+ import { createHash as createHash29 } from "crypto";
94615
94910
  import { existsSync as existsSync14 } from "fs";
94616
94911
  import { mkdir as mkdir42, readFile as readFile43, writeFile as writeFile16 } from "fs/promises";
94617
94912
  import { join as join65 } from "path";
@@ -94627,7 +94922,7 @@ function createHandboekNarrator(deps) {
94627
94922
  const pathFor = (hash) => join65(dir, `${hash}.wav`);
94628
94923
  const metaPathFor = (hash) => join65(dir, `${hash}.json`);
94629
94924
  async function ensureSegment(key, synthesize) {
94630
- const hash = createHash28("sha256").update(key).digest("hex");
94925
+ const hash = createHash29("sha256").update(key).digest("hex");
94631
94926
  const metaPath = metaPathFor(hash);
94632
94927
  if (existsSync14(pathFor(hash)) && existsSync14(metaPath)) {
94633
94928
  try {
@@ -95627,9 +95922,15 @@ function llamaCppRoutes(ctx) {
95627
95922
  const catalogId = c.req.param("catalogId");
95628
95923
  const skipShaRaw = c.req.query("skipSha");
95629
95924
  const skipSha = skipShaRaw != null && skipShaRaw !== "" && skipShaRaw !== "0" && skipShaRaw !== "false";
95925
+ const skipCompanionRaw = c.req.query("skipCompanion");
95926
+ const skipCompanion = skipCompanionRaw != null && skipCompanionRaw !== "" && skipCompanionRaw !== "0" && skipCompanionRaw !== "false";
95630
95927
  const config = await ctx.store.readConfig().catch(() => null);
95631
95928
  const includeMmproj = config?.nativeVision?.[catalogId] === true;
95632
- ctx.chatInstalls.llamaCpp.start(catalogId, { skipSha, includeMmproj });
95929
+ ctx.chatInstalls.llamaCpp.start(catalogId, {
95930
+ skipSha,
95931
+ includeMmproj,
95932
+ installCompanion: !skipCompanion
95933
+ });
95633
95934
  return streamSSE9(
95634
95935
  c,
95635
95936
  (stream) => subscribeToInstallSse(ctx.chatInstalls.llamaCpp, catalogId, stream)
@@ -95720,7 +96021,7 @@ function mcpToolRoutes(ctx) {
95720
96021
  import { Hono as Hono36 } from "hono";
95721
96022
 
95722
96023
  // src/meester/status-generator.ts
95723
- import { createHash as createHash29 } from "crypto";
96024
+ import { createHash as createHash30 } from "crypto";
95724
96025
  import {
95725
96026
  MEESTER_STATUS_HEADLINE_MAX,
95726
96027
  MeesterStatusModelOutputSchema,
@@ -96032,7 +96333,7 @@ function localDay4(date) {
96032
96333
  return `${y}-${m}-${d}`;
96033
96334
  }
96034
96335
  function actionKey(projectId, title) {
96035
- return createHash29("sha256").update(`${projectId}
96336
+ return createHash30("sha256").update(`${projectId}
96036
96337
  ${normalizeTitle(title)}`).digest("hex");
96037
96338
  }
96038
96339
  function normalizeTitle(title) {
@@ -96044,7 +96345,7 @@ function hashInput(candidates) {
96044
96345
  ...c.openTasks.map((t) => `${t.ref}:${t.status}`),
96045
96346
  ...c.events.map((e) => e.id)
96046
96347
  ]);
96047
- return createHash29("sha256").update(lines.join("\n")).digest("hex");
96348
+ return createHash30("sha256").update(lines.join("\n")).digest("hex");
96048
96349
  }
96049
96350
  function parseModelOutput(raw) {
96050
96351
  const fence = raw.match(/```(?:json)?\s*\n([\s\S]*?)\n?```/);
@@ -96144,19 +96445,19 @@ function memoryRoutes(ctx) {
96144
96445
  const app = new Hono37();
96145
96446
  app.post("/search", async (c) => {
96146
96447
  const body = await c.req.json();
96147
- const results = await ctx.memory.searchAll(
96448
+ const outcome = await ctx.memory.searchAllDetailed(
96148
96449
  body.gezelId,
96149
96450
  body.projectId,
96150
96451
  body.query,
96151
96452
  body.topK ?? 10
96152
96453
  );
96153
- return c.json({ results });
96454
+ return c.json(outcome);
96154
96455
  });
96155
96456
  app.post("/save", async (c) => {
96156
96457
  const body = await c.req.json();
96157
96458
  const kind = isMemoryKind(body.kind) ? body.kind : "fact";
96158
96459
  const outcome = await ctx.memory.save(body.scope, body.id, body.text, kind);
96159
- return c.json({ ok: true, status: outcome?.status ?? "saved" });
96460
+ return c.json({ ok: true, ...outcome });
96160
96461
  });
96161
96462
  app.get("/recent", async (c) => {
96162
96463
  const scope = c.req.query("scope") ?? "gezel";
@@ -96337,7 +96638,7 @@ import { Hono as Hono39 } from "hono";
96337
96638
  import { z as z20 } from "zod";
96338
96639
 
96339
96640
  // src/models/gezmodel.ts
96340
- import { createHash as createHash30, randomUUID as randomUUID19 } from "crypto";
96641
+ import { createHash as createHash31, randomUUID as randomUUID19 } from "crypto";
96341
96642
  import { createReadStream as createReadStream6, createWriteStream as createWriteStream5 } from "fs";
96342
96643
  import { mkdir as mkdir43, open as open3, readFile as readFile45, readdir as readdir26, rm as rm29, stat as stat26, writeFile as writeFile17 } from "fs/promises";
96343
96644
  import { basename as basename14, dirname as dirname35, extname as extname5, join as join66 } from "path";
@@ -96439,7 +96740,7 @@ var GezmodelManager = class {
96439
96740
  files.push({
96440
96741
  path: path2,
96441
96742
  sizeBytes: content.byteLength,
96442
- sha256: createHash30("sha256").update(content).digest("hex"),
96743
+ sha256: createHash31("sha256").update(content).digest("hex"),
96443
96744
  role: path2 === "manifests/installed.json" ? "installed-manifest" : path2 === "manifests/catalog.json" ? "catalog-manifest" : "catalog-file"
96444
96745
  });
96445
96746
  }
@@ -96645,7 +96946,7 @@ var GezmodelManager = class {
96645
96946
  }
96646
96947
  };
96647
96948
  async function hashFile(path2) {
96648
- const hash = createHash30("sha256");
96949
+ const hash = createHash31("sha256");
96649
96950
  const stream = createReadStream6(path2, { highWaterMark: MODEL_HASH_READ_BUFFER_BYTES });
96650
96951
  for await (const chunk2 of stream) hash.update(chunk2);
96651
96952
  return hash.digest("hex");
@@ -96843,7 +97144,7 @@ function extractOne(archivePath, wanted, dest, expected) {
96843
97144
  zip.openReadStream(entry, (streamErr, stream) => {
96844
97145
  if (streamErr || !stream)
96845
97146
  return fail2(streamErr ?? new Error("could not read ZIP entry"));
96846
- const hash = createHash30("sha256");
97147
+ const hash = createHash31("sha256");
96847
97148
  let bytes = 0;
96848
97149
  const verifier = new Transform({
96849
97150
  transform(chunk2, _encoding, callback) {
@@ -97077,7 +97378,7 @@ import {
97077
97378
  } from "@bendyline/gezel";
97078
97379
 
97079
97380
  // src/providers/mlx/models.ts
97080
- import { createHash as createHash31 } from "crypto";
97381
+ import { createHash as createHash32 } from "crypto";
97081
97382
  import { createReadStream as createReadStream7 } from "fs";
97082
97383
  import { mkdir as mkdir44, readFile as readFile47, rename as rename12, rm as rm30, stat as stat27, writeFile as writeFile18 } from "fs/promises";
97083
97384
  import { join as join68, resolve as resolvePath2 } from "path";
@@ -97639,7 +97940,7 @@ var MlxModelManager = class {
97639
97940
  }
97640
97941
  }
97641
97942
  push({ type: "verifying", file: file.name });
97642
- const hasher = createHash31("sha256");
97943
+ const hasher = createHash32("sha256");
97643
97944
  await new Promise((resolve18, reject) => {
97644
97945
  const stream = createReadStream7(tmpPath, { highWaterMark: MODEL_HASH_READ_BUFFER_BYTES });
97645
97946
  stream.on("data", (chunk2) => hasher.update(chunk2));
@@ -99479,7 +99780,7 @@ function describeHostCadence(args) {
99479
99780
  }
99480
99781
 
99481
99782
  // src/project-type/craftbooks.ts
99482
- import { createHash as createHash32 } from "crypto";
99783
+ import { createHash as createHash33 } from "crypto";
99483
99784
  import {
99484
99785
  craftbookFromDoc as craftbookFromDoc3,
99485
99786
  createLogger as createLogger109,
@@ -99503,7 +99804,7 @@ function craftbookContentHash(book) {
99503
99804
  command: book.command ?? null,
99504
99805
  requirements: book.requirements ?? null
99505
99806
  };
99506
- return `sha256:${createHash32("sha256").update(JSON.stringify(canonical)).digest("hex")}`;
99807
+ return `sha256:${createHash33("sha256").update(JSON.stringify(canonical)).digest("hex")}`;
99507
99808
  }
99508
99809
  async function resolveTypeCraftbook(catalog, input, id, now) {
99509
99810
  const embedded = await catalog.readItemFile(
@@ -100211,7 +100512,7 @@ function pageInvokeRoutes(ctx) {
100211
100512
  }
100212
100513
 
100213
100514
  // src/http/routes/page-read.ts
100214
- import { createHash as createHash33 } from "crypto";
100515
+ import { createHash as createHash34 } from "crypto";
100215
100516
  import { readFile as readFile50, readdir as readdir29, stat as stat28 } from "fs/promises";
100216
100517
  import { PageReadRequestSchema, createLogger as createLogger113 } from "@bendyline/gezel";
100217
100518
  import { Hono as Hono48 } from "hono";
@@ -100220,7 +100521,7 @@ var PAGE_READ_MAX_BYTES = 2 * 1024 * 1024;
100220
100521
  var RATE_WINDOW_MS3 = 6e4;
100221
100522
  var RATE_MAX_READS = 600;
100222
100523
  function etagOf(size, mtimeMs) {
100223
- return createHash33("sha256").update(`${size}:${Math.trunc(mtimeMs)}`).digest("base64url").slice(0, 16);
100524
+ return createHash34("sha256").update(`${size}:${Math.trunc(mtimeMs)}`).digest("base64url").slice(0, 16);
100224
100525
  }
100225
100526
  function pageReadRoutes(ctx) {
100226
100527
  const app = new Hono48();
@@ -100270,7 +100571,7 @@ function pageReadRoutes(ctx) {
100270
100571
  return `${entry.name}:${entry.isDirectory() ? "dir" : "file"}:${Math.trunc(child?.mtimeMs ?? 0)}`;
100271
100572
  })
100272
100573
  );
100273
- const digest = createHash33("sha256").update(listed.sort().join("\n")).digest("base64url").slice(0, 16);
100574
+ const digest = createHash34("sha256").update(listed.sort().join("\n")).digest("base64url").slice(0, 16);
100274
100575
  return c.json({ op: "stat", etag: digest, mtime: stats.mtimeMs });
100275
100576
  }
100276
100577
  return c.json({
@@ -100295,7 +100596,7 @@ function pageReadRoutes(ctx) {
100295
100596
  };
100296
100597
  })
100297
100598
  )).filter((entry) => entry !== null);
100298
- const digest = createHash33("sha256").update(
100599
+ const digest = createHash34("sha256").update(
100299
100600
  listed.map((e) => `${e.name}:${e.kind}:${Math.trunc(e.mtime)}`).sort().join("\n")
100300
100601
  ).digest("base64url").slice(0, 16);
100301
100602
  return c.json({ op: "list", entries: listed, etag: digest, mtime: stats.mtimeMs });
@@ -101005,10 +101306,11 @@ import {
101005
101306
  projectGezelId as projectGezelId2,
101006
101307
  skillToCraftbookDoc
101007
101308
  } from "@bendyline/gezel";
101309
+ init_ensure();
101008
101310
  init_install();
101009
101311
 
101010
101312
  // src/workspace/instruction-scanner.ts
101011
- import { createHash as createHash34 } from "crypto";
101313
+ import { createHash as createHash35 } from "crypto";
101012
101314
  import { readFile as readFile52, stat as stat30 } from "fs/promises";
101013
101315
  import { join as join73 } from "path";
101014
101316
  var MAX_CONTENT_CHARS = 64e3;
@@ -101053,7 +101355,7 @@ ${i.content.trim()}`).join("\n\n---\n\n");
101053
101355
  return { content: chosen.content, sourceFile: chosen.source, hash: chosen.hash };
101054
101356
  }
101055
101357
  function sha2563(s) {
101056
- return createHash34("sha256").update(s).digest("hex");
101358
+ return createHash35("sha256").update(s).digest("hex");
101057
101359
  }
101058
101360
 
101059
101361
  // src/workspace/skill-script-translator.ts
@@ -101187,6 +101489,30 @@ async function translateShellBlock(chat, skillName, scriptName, code) {
101187
101489
  // src/workspace/import-sync.ts
101188
101490
  var log110 = createLogger114("import-sync");
101189
101491
  var VOORMAN_TEMPLATE_ID = "voorman";
101492
+ var BUILDER_TEMPLATE_ID = "builder";
101493
+ async function ensureFolderProjectBuilder(deps, projectId) {
101494
+ const { store, catalog } = deps;
101495
+ if (!projectId || projectId === "default") return {};
101496
+ const project = await store.getProject(projectId).catch(() => null);
101497
+ if (!project || project.mode !== "solo" || !project.workingDir) return {};
101498
+ if (project.voormanGezelId) return {};
101499
+ const gezels = await store.listGezels().catch(() => []);
101500
+ const existing = gezels.find(
101501
+ (gezel) => gezel.templateId === BUILDER_TEMPLATE_ID || gezel.role?.trim().toLowerCase() === BUILDER_TEMPLATE_ID
101502
+ );
101503
+ const recruited = existing ? null : await recruitFromTemplate(store, catalog, BUILDER_TEMPLATE_ID).catch((err) => {
101504
+ log110.warn(`[import-sync] ${projectId}: Builder recruit failed: ${describe6(err)}`);
101505
+ return null;
101506
+ });
101507
+ const builder = existing ?? recruited;
101508
+ if (!builder) return {};
101509
+ await store.updateProject(projectId, {
101510
+ voormanGezelId: builder.id,
101511
+ voormanAutoAssignedAt: nowIso12()
101512
+ });
101513
+ log110.info(`[import-sync] ${projectId}: assigned ${builder.id} as Builder`);
101514
+ return recruited ? { createdGezel: recruited } : {};
101515
+ }
101190
101516
  async function syncProjectImports(deps, projectId, instructions, skills) {
101191
101517
  const { store } = deps;
101192
101518
  const gate = await store.assertWorkspaceWritable(projectId).catch(() => null);
@@ -101315,16 +101641,20 @@ async function findExistingVoorman(store) {
101315
101641
  return match?.id ?? null;
101316
101642
  }
101317
101643
  async function recruitVoorman(store, catalog) {
101318
- const detail = await catalog.get("gezel-template", VOORMAN_TEMPLATE_ID).catch(() => null);
101319
- if (!detail || detail.manifest.kind !== "gezel-template") return null;
101644
+ return recruitFromTemplate(store, catalog, VOORMAN_TEMPLATE_ID);
101645
+ }
101646
+ async function recruitFromTemplate(store, catalog, templateId) {
101647
+ const template = await resolveGildeTemplateForRole(catalog, templateId).catch(() => null);
101648
+ if (!template || template.templateId !== templateId) return null;
101320
101649
  const { name, gender } = pickRandomNameWithGender4();
101321
101650
  const created = await store.createGezel({
101322
101651
  name,
101323
- role: detail.manifest.role,
101652
+ role: template.role,
101324
101653
  gender,
101325
- about: detail.about ?? "",
101326
- templateId: VOORMAN_TEMPLATE_ID,
101327
- templateVersion: detail.manifest.version
101654
+ about: template.about,
101655
+ templateId,
101656
+ templateVersion: template.templateVersion,
101657
+ ...template.frontmatter ? { frontmatter: template.frontmatter } : {}
101328
101658
  });
101329
101659
  return { id: created.id, name: created.name };
101330
101660
  }
@@ -101799,6 +102129,7 @@ async function resolveActor(ctx, gezelIdHeader) {
101799
102129
  }
101800
102130
  function projectTaskRoutes(ctx) {
101801
102131
  const app = new Hono55();
102132
+ const inflightCraftbookInvocations = /* @__PURE__ */ new Map();
101802
102133
  app.get("/:projectId/tasks", async (c) => {
101803
102134
  const projectId = c.req.param("projectId");
101804
102135
  const filter = ListFilterSchema.parse({
@@ -101814,10 +102145,31 @@ function projectTaskRoutes(ctx) {
101814
102145
  });
101815
102146
  app.post("/:projectId/tasks", async (c) => {
101816
102147
  const projectId = c.req.param("projectId");
101817
- const { dispatchEntry, ...body } = CreateTaskRequestSchema.parse(await c.req.json());
102148
+ const { dispatchEntry, craftbookInvocationKey, ...body } = CreateTaskRequestSchema.parse(
102149
+ await c.req.json()
102150
+ );
101818
102151
  let task;
101819
102152
  try {
101820
- task = await ctx.tasks.create(projectId, body);
102153
+ if (craftbookInvocationKey) {
102154
+ const existing = (await ctx.tasks.list({ projectId })).find(
102155
+ (candidate) => candidate.origin?.kind === "craftbook-invocation" && candidate.origin.key === craftbookInvocationKey && (candidate.status === "draft" || candidate.status === "active" || candidate.status === "paused")
102156
+ );
102157
+ if (existing) return c.json(existing);
102158
+ const inflightKey = `${projectId}:${craftbookInvocationKey}`;
102159
+ const pending5 = inflightCraftbookInvocations.get(inflightKey);
102160
+ if (pending5) return c.json(await pending5);
102161
+ const create = ctx.tasks.create(projectId, body, {
102162
+ origin: { kind: "craftbook-invocation", key: craftbookInvocationKey }
102163
+ });
102164
+ inflightCraftbookInvocations.set(inflightKey, create);
102165
+ try {
102166
+ task = await create;
102167
+ } finally {
102168
+ inflightCraftbookInvocations.delete(inflightKey);
102169
+ }
102170
+ } else {
102171
+ task = await ctx.tasks.create(projectId, body);
102172
+ }
101821
102173
  } catch (err) {
101822
102174
  if (err instanceof CraftbookSetupRequiredError) {
101823
102175
  return c.json(
@@ -102888,7 +103240,7 @@ async function recoverTypedProjectCreations(store) {
102888
103240
  }
102889
103241
 
102890
103242
  // src/project-type/gzl.ts
102891
- import { createHash as createHash35 } from "crypto";
103243
+ import { createHash as createHash36 } from "crypto";
102892
103244
  import { mkdir as mkdir48, writeFile as writeFile21 } from "fs/promises";
102893
103245
  import { dirname as dirname40 } from "path";
102894
103246
  import {
@@ -102906,7 +103258,7 @@ function shard(id) {
102906
103258
  return id.slice(0, 2).toLowerCase();
102907
103259
  }
102908
103260
  function hashItemFiles(files) {
102909
- const hash = createHash35("sha256");
103261
+ const hash = createHash36("sha256");
102910
103262
  for (const { rel, content } of [...files].sort((a, b) => a.rel < b.rel ? -1 : 1)) {
102911
103263
  hash.update(rel);
102912
103264
  hash.update("\0");
@@ -103054,7 +103406,7 @@ async function importGzlBundle(deps, buffer, opts = {}) {
103054
103406
 
103055
103407
  // src/workspace/command-approvals.ts
103056
103408
  init_atomic();
103057
- import { createHash as createHash36 } from "crypto";
103409
+ import { createHash as createHash37 } from "crypto";
103058
103410
  import { existsSync as existsSync18 } from "fs";
103059
103411
  import { mkdir as mkdir49, readFile as readFile57 } from "fs/promises";
103060
103412
  import { dirname as dirname41, join as join79 } from "path";
@@ -103062,7 +103414,7 @@ import { projectPrivateDir as projectPrivateDir5 } from "@bendyline/gezel/paths"
103062
103414
  function hashCommandInvocation(body, args, inputFiles = []) {
103063
103415
  const files = [...inputFiles].map(({ path: path2, sha256: sha2565 }) => ({ path: path2, sha256: sha2565 })).sort((a, b) => a.path.localeCompare(b.path) || a.sha256.localeCompare(b.sha256));
103064
103416
  const payload = JSON.stringify({ version: 2, body: body ?? null, args, files });
103065
- return createHash36("sha256").update("gezel-command-invocation\0").update(payload).digest("hex");
103417
+ return createHash37("sha256").update("gezel-command-invocation\0").update(payload).digest("hex");
103066
103418
  }
103067
103419
  function approvalsPath(home, projectId) {
103068
103420
  return join79(projectPrivateDir5(home, projectId), "command-approvals.json");
@@ -103288,20 +103640,20 @@ import {
103288
103640
  } from "@bendyline/gezel";
103289
103641
  import { projectPrivateDir as projectPrivateDir6 } from "@bendyline/gezel/paths";
103290
103642
  var SHIPPED_ALLOWLIST = [
103291
- { package: "zod", allowedVersions: ["^3", "^4"] },
103292
- { package: "chalk", allowedVersions: ["^5"] },
103293
- { package: "commander", allowedVersions: ["^12", "^13"] },
103294
- { package: "date-fns", allowedVersions: ["^3", "^4"] },
103295
- { package: "yaml", allowedVersions: ["^2"] },
103296
- { package: "cheerio", allowedVersions: ["^1"] },
103297
- { package: "mime-types", allowedVersions: ["^2", "^3"] },
103298
- { package: "marked", allowedVersions: ["^12", "^13", "^14", "^15"] },
103299
- { package: "nanoid", allowedVersions: ["^5"] },
103300
- { package: "pino", allowedVersions: ["^9"] },
103301
- { package: "playwright-core", allowedVersions: ["^1"] },
103302
- { package: "tsx", allowedVersions: ["^4"] },
103303
- { package: "typescript", allowedVersions: ["^5"] },
103304
- { package: "undici", allowedVersions: ["^6", "^7"] }
103643
+ { package: "zod", allowedVersions: ["^3", "^4"], defaultVersion: "^4" },
103644
+ { package: "chalk", allowedVersions: ["^5"], defaultVersion: "^5" },
103645
+ { package: "commander", allowedVersions: ["^12", "^13"], defaultVersion: "^13" },
103646
+ { package: "date-fns", allowedVersions: ["^3", "^4"], defaultVersion: "^4" },
103647
+ { package: "yaml", allowedVersions: ["^2"], defaultVersion: "^2" },
103648
+ { package: "cheerio", allowedVersions: ["^1"], defaultVersion: "^1" },
103649
+ { package: "mime-types", allowedVersions: ["^2", "^3"], defaultVersion: "^3" },
103650
+ { package: "marked", allowedVersions: ["^12", "^13", "^14", "^15"], defaultVersion: "^15" },
103651
+ { package: "nanoid", allowedVersions: ["^5"], defaultVersion: "^5" },
103652
+ { package: "pino", allowedVersions: ["^9"], defaultVersion: "^9" },
103653
+ { package: "playwright-core", allowedVersions: ["^1"], defaultVersion: "^1" },
103654
+ { package: "tsx", allowedVersions: ["^4"], defaultVersion: "^4" },
103655
+ { package: "typescript", allowedVersions: ["^5"], defaultVersion: "^5" },
103656
+ { package: "undici", allowedVersions: ["^6", "^7"], defaultVersion: "^7" }
103305
103657
  ];
103306
103658
  function projectAllowlistPath(home, projectId) {
103307
103659
  return join81(projectPrivateDir6(home, projectId), "npm-allowlist.json");
@@ -103332,6 +103684,9 @@ function matchesAllowlist(pkg, version) {
103332
103684
  }
103333
103685
  return false;
103334
103686
  }
103687
+ function shippedDefaultVersion(pkg) {
103688
+ return SHIPPED_ALLOWLIST.find((entry) => entry.package === pkg)?.defaultVersion;
103689
+ }
103335
103690
  function matchesProjectApproval(allowlist, pkg, version) {
103336
103691
  return allowlist.approved.some((e) => e.package === pkg && e.version === version);
103337
103692
  }
@@ -103461,7 +103816,9 @@ function dedupRequests(raw) {
103461
103816
  const out = [];
103462
103817
  for (const r of raw) {
103463
103818
  const pkg = NpmPackageNameSchema.parse(r.package);
103464
- const version = NpmRegistryVersionSchema.parse(r.version ?? "latest");
103819
+ const version = NpmRegistryVersionSchema.parse(
103820
+ r.version ?? shippedDefaultVersion(pkg) ?? "latest"
103821
+ );
103465
103822
  const key = keyOf(pkg, version);
103466
103823
  if (seen.has(key)) continue;
103467
103824
  seen.add(key);
@@ -103631,7 +103988,7 @@ import { join as join82 } from "path";
103631
103988
  import { nowIso as nowIso14 } from "@bendyline/gezel";
103632
103989
 
103633
103990
  // src/workspace/command-inputs.ts
103634
- import { createHash as createHash37 } from "crypto";
103991
+ import { createHash as createHash38 } from "crypto";
103635
103992
  import { createReadStream as createReadStream8 } from "fs";
103636
103993
  import { readFile as readFile59, realpath as realpath7, stat as stat35 } from "fs/promises";
103637
103994
  import { dirname as dirname43, extname as extname6, isAbsolute as isAbsolute10, relative as relative10, resolve as resolve13, sep as sep9 } from "path";
@@ -103704,7 +104061,7 @@ function portableRelative(root, file) {
103704
104061
  return relative10(root, file).split(sep9).join("/");
103705
104062
  }
103706
104063
  async function sha256File4(file) {
103707
- const hash = createHash37("sha256");
104064
+ const hash = createHash38("sha256");
103708
104065
  await new Promise((resolveStream, reject) => {
103709
104066
  const stream = createReadStream8(file);
103710
104067
  stream.on("data", (chunk2) => hash.update(chunk2));
@@ -104263,12 +104620,13 @@ function projectRoutes(ctx) {
104263
104620
  app.post("/", async (c) => {
104264
104621
  const body = CreateProjectRequestSchema.parse(await c.req.json());
104265
104622
  const created = await ctx.store.createProject(body);
104266
- const ensured2 = await ensureProjectVoorman(
104623
+ const ensureLead = body.workingDir && body.mode === "solo" ? ensureFolderProjectBuilder : ensureProjectVoorman;
104624
+ const ensured2 = await ensureLead(
104267
104625
  { store: ctx.store, chat: ctx.chat, home: ctx.home, catalog: ctx.catalog },
104268
104626
  created.id
104269
104627
  ).catch((err) => {
104270
104628
  const message = err instanceof Error ? err.message : String(err);
104271
- log114.warn(`[projects] ensure-voorman failed for ${created.id}: ${message}`);
104629
+ log114.warn(`[projects] ensure-lead failed for ${created.id}: ${message}`);
104272
104630
  return {};
104273
104631
  });
104274
104632
  if (ensured2.createdGezel) {
@@ -104791,6 +105149,31 @@ function projectRoutes(ctx) {
104791
105149
  return c.json({ error: "not found" }, 404);
104792
105150
  }
104793
105151
  });
105152
+ app.post("/:id/reveal-reference", async (c) => {
105153
+ const id = c.req.param("id");
105154
+ const request = ReferenceFileLocationRequestSchema.parse({
105155
+ kind: c.req.query("kind"),
105156
+ path: c.req.query("path")
105157
+ });
105158
+ const base = request.kind === "artifact" ? ctx.store.projectArtifactsDir(id) : request.kind === "workspace" ? await ctx.store.projectWorkspaceDir(id) : ctx.store.documentsDir();
105159
+ const joined = safeJoin(base, request.path);
105160
+ if (!joined || !await realpathContained(base, joined)) {
105161
+ return c.json({ error: "path traversal" }, 400);
105162
+ }
105163
+ let path2;
105164
+ try {
105165
+ const file = await stat36(joined);
105166
+ if (!file.isFile()) return c.json({ error: "not found" }, 404);
105167
+ path2 = await realpath8(joined);
105168
+ } catch {
105169
+ return c.json({ error: "not found" }, 404);
105170
+ }
105171
+ const { execFile: execFile8 } = await import("child_process");
105172
+ const launcher = process.platform === "darwin" ? { cmd: "open", args: ["-R", path2] } : process.platform === "win32" ? { cmd: "explorer", args: ["/select,", path2] } : { cmd: "xdg-open", args: [dirname44(path2)] };
105173
+ execFile8(launcher.cmd, launcher.args, { windowsHide: true }, () => {
105174
+ });
105175
+ return c.json(ReferenceFileLocationResponseSchema.parse({ path: path2 }));
105176
+ });
104794
105177
  app.get("/:id/artifacts", async (c) => {
104795
105178
  const id = c.req.param("id");
104796
105179
  const subpath = c.req.query("path") ?? "";
@@ -107583,7 +107966,7 @@ async function readProjectScriptRun(home, projectId, runId) {
107583
107966
  }
107584
107967
 
107585
107968
  // src/scripts/sdk.ts
107586
- import { createHash as createHash39 } from "crypto";
107969
+ import { createHash as createHash40 } from "crypto";
107587
107970
  import { readFile as readFile67 } from "fs/promises";
107588
107971
  import { dirname as dirname45, join as join87, relative as relative11 } from "path";
107589
107972
  import { fileURLToPath as fileURLToPath7 } from "url";
@@ -107649,7 +108032,7 @@ async function readSdkTypes() {
107649
108032
  });
107650
108033
  } catch {
107651
108034
  }
107652
- const hash = createHash39("sha256");
108035
+ const hash = createHash40("sha256");
107653
108036
  for (const f of files) hash.update(f.name).update("\0").update(f.content);
107654
108037
  cachedTypes = { version: hash.digest("hex"), files };
107655
108038
  return cachedTypes;
@@ -110463,9 +110846,13 @@ async function grepWorkspace(opts) {
110463
110846
  const plan = normalizePlan(opts);
110464
110847
  const target = await resolveSearchTarget(opts.workspaceDir, opts.path);
110465
110848
  const requestedEngine = opts.engine ?? "auto";
110466
- const ripgrepPath = requestedEngine === "javascript" ? null : await resolveExecutableOnPath("rg", target.workspaceDir, Math.min(plan.timeoutMs, 2e3));
110849
+ const ripgrepPath = requestedEngine === "javascript" ? null : await resolveRipgrepExecutable(target.workspaceDir, Math.min(plan.timeoutMs, 2e3));
110467
110850
  if (requestedEngine === "ripgrep" && !ripgrepPath) {
110468
- throw new WorkspaceGrepError("ripgrep is not installed on this machine", 500, "rg-unavailable");
110851
+ throw new WorkspaceGrepError(
110852
+ "ripgrep is unavailable (the bundled platform package is missing and rg is not on PATH)",
110853
+ 500,
110854
+ "rg-unavailable"
110855
+ );
110469
110856
  }
110470
110857
  let raw;
110471
110858
  let engine;
@@ -110969,6 +111356,16 @@ async function resolveExecutableOnPath(command, forbiddenRoot, timeoutMs) {
110969
111356
  if (timer) clearTimeout(timer);
110970
111357
  }
110971
111358
  }
111359
+ async function resolveRipgrepExecutable(forbiddenRoot, timeoutMs) {
111360
+ const onPath = await resolveExecutableOnPath("rg", forbiddenRoot, timeoutMs);
111361
+ if (onPath) return onPath;
111362
+ try {
111363
+ const { rgPath } = await import("@vscode/ripgrep");
111364
+ return await validateExecutable(rgPath, forbiddenRoot);
111365
+ } catch {
111366
+ return null;
111367
+ }
111368
+ }
110972
111369
  async function findExecutableOnPath(command, forbiddenRoot) {
110973
111370
  const pathValue = process.env.PATH;
110974
111371
  if (!pathValue) return null;
@@ -110977,22 +111374,28 @@ async function findExecutableOnPath(command, forbiddenRoot) {
110977
111374
  const entry = rawEntry.replace(/^"|"$/g, "");
110978
111375
  if (!entry || !isAbsolute11(entry)) continue;
110979
111376
  for (const suffix of suffixes) {
110980
- try {
110981
- const executable = await realpath10(join89(entry, `${command}${suffix}`));
110982
- if (pathIsInside(forbiddenRoot, executable)) continue;
110983
- const executableStat = await stat39(executable);
110984
- if (!executableStat.isFile()) continue;
110985
- await access3(
110986
- executable,
110987
- process.platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK
110988
- );
110989
- return executable;
110990
- } catch {
110991
- }
111377
+ const executable = await validateExecutable(
111378
+ join89(entry, `${command}${suffix}`),
111379
+ forbiddenRoot
111380
+ );
111381
+ if (executable) return executable;
110992
111382
  }
110993
111383
  }
110994
111384
  return null;
110995
111385
  }
111386
+ async function validateExecutable(candidate, forbiddenRoot) {
111387
+ if (!isAbsolute11(candidate)) return null;
111388
+ try {
111389
+ const executable = await realpath10(candidate);
111390
+ if (pathIsInside(forbiddenRoot, executable)) return null;
111391
+ const executableStat = await stat39(executable);
111392
+ if (!executableStat.isFile()) return null;
111393
+ await access3(executable, process.platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK);
111394
+ return executable;
111395
+ } catch {
111396
+ return null;
111397
+ }
111398
+ }
110996
111399
  function pathIsInside(root, candidate) {
110997
111400
  const rel = relative12(root, candidate);
110998
111401
  return rel === "" || rel !== ".." && !rel.startsWith(`..${sep10}`) && !isAbsolute11(rel);
@@ -116245,7 +116648,10 @@ function opaqueServerErrors(log189, onUnexpectedHttpError) {
116245
116648
  const raw = await prior.clone().text().catch(() => "");
116246
116649
  try {
116247
116650
  const parsed = JSON.parse(raw);
116248
- if (parsed.error === "internal_error" && typeof parsed.requestId === "string") return;
116651
+ if (parsed.error === "internal_error" && typeof parsed.requestId === "string") {
116652
+ c.header("x-request-id", parsed.requestId);
116653
+ return;
116654
+ }
116249
116655
  if ((parsed.error === "machine_engine_unavailable" || parsed.error === "capacity_denied") && Object.keys(parsed).length === 1) {
116250
116656
  return;
116251
116657
  }
@@ -118594,7 +119000,7 @@ function indexShadowBody(store, file, body) {
118594
119000
  }
118595
119001
 
118596
119002
  // src/index-store/area-pass.ts
118597
- import { createHash as createHash40 } from "crypto";
119003
+ import { createHash as createHash41 } from "crypto";
118598
119004
  var ARCHITECTURE_KEY = "::project";
118599
119005
  var MIN_AREA_FILES = 2;
118600
119006
  var AREA_INPUT_CAP = 6e3;
@@ -118656,7 +119062,7 @@ async function runAreaPass(index, deps) {
118656
119062
  return result;
118657
119063
  }
118658
119064
  function hashLines(lines) {
118659
- return createHash40("sha256").update([...lines].sort().join("\n")).digest("hex");
119065
+ return createHash41("sha256").update([...lines].sort().join("\n")).digest("hex");
118660
119066
  }
118661
119067
  function buildAreaPrompt(area, entries) {
118662
119068
  let body = "";
@@ -118846,9 +119252,9 @@ function triv(kind) {
118846
119252
  }
118847
119253
 
118848
119254
  // src/index-store/hash.ts
118849
- import { createHash as createHash41 } from "crypto";
119255
+ import { createHash as createHash42 } from "crypto";
118850
119256
  function sha2564(data) {
118851
- return createHash41("sha256").update(data).digest("hex");
119257
+ return createHash42("sha256").update(data).digest("hex");
118852
119258
  }
118853
119259
 
118854
119260
  // src/index-store/artifacts-indexer.ts
@@ -123759,7 +124165,7 @@ ${body}\r
123759
124165
  }
123760
124166
 
123761
124167
  // src/mail/mime.ts
123762
- import { createHash as createHash42 } from "crypto";
124168
+ import { createHash as createHash43 } from "crypto";
123763
124169
  import { simpleParser } from "mailparser";
123764
124170
 
123765
124171
  // src/mail/html.ts
@@ -123874,7 +124280,7 @@ function normalizeReferences(refs) {
123874
124280
  }
123875
124281
  function synthesizeMessageId(parsed, ctx) {
123876
124282
  const basis = `${addressText(parsed.from)}|${parsed.subject ?? ""}|${parsed.date?.toISOString() ?? ctx.fallbackDate ?? ""}|${ctx.id}`;
123877
- return `<${createHash42("sha256").update(basis).digest("hex").slice(0, 32)}@gezel.local>`;
124283
+ return `<${createHash43("sha256").update(basis).digest("hex").slice(0, 32)}@gezel.local>`;
123878
124284
  }
123879
124285
  async function parseRawMessage(raw, ctx) {
123880
124286
  const parsed = await simpleParser(raw);
@@ -124497,7 +124903,7 @@ async function migrateOversightTask(store, num) {
124497
124903
 
124498
124904
  // src/memory/compaction.ts
124499
124905
  init_atomic();
124500
- import { createHash as createHash43 } from "crypto";
124906
+ import { createHash as createHash44 } from "crypto";
124501
124907
  import { readFile as readFile77 } from "fs/promises";
124502
124908
  import { join as join101 } from "path";
124503
124909
  import { createLogger as createLogger160, isProactiveAllowed as isProactiveAllowed5 } from "@bendyline/gezel";
@@ -124709,7 +125115,7 @@ var MemoryCompactor = class {
124709
125115
  }
124710
125116
  }
124711
125117
  if (entries.length < minEntries) return false;
124712
- const inputHash = createHash43("sha256").update(eligibleDays.map((d) => `${d}
125118
+ const inputHash = createHash44("sha256").update(eligibleDays.map((d) => `${d}
124713
125119
  ${dayContents.get(d)}`).join("\n")).digest("hex");
124714
125120
  const state = await this.readState(scope, id);
124715
125121
  if (state.inputHash === inputHash) return false;
@@ -124815,7 +125221,7 @@ ${dayContents.get(d)}`).join("\n")).digest("hex");
124815
125221
  const notes = await this.memory.getRecent("gezel", gezelId, lookbackDays);
124816
125222
  if (!notes.trim()) return;
124817
125223
  const current = await this.store.readMemoryLessons(gezelId);
124818
- const lessonsInputHash = createHash43("sha256").update(`${notes}
125224
+ const lessonsInputHash = createHash44("sha256").update(`${notes}
124819
125225
  ---
124820
125226
  ${current}`).digest("hex");
124821
125227
  const state = await this.readState("gezel", gezelId);
@@ -125156,8 +125562,8 @@ var MemoryManager = class {
125156
125562
  * that text dedups, so we accept the race rather than lock.
125157
125563
  *
125158
125564
  * Degraded mode (embeddings disabled): markdown is still appended (it is
125159
- * the source of truth and the index self-heals on rebuild) and the
125160
- * EmbeddingsDisabledError propagates, preserving the pre-dedup contract.
125565
+ * the source of truth) and the outcome reports deferred indexing. The health
125566
+ * monitor self-heals the derived index once embeddings recover.
125161
125567
  */
125162
125568
  async save(scope, id, text, kind = DEFAULT_MEMORY_KIND) {
125163
125569
  const trimmed = text.trim();
@@ -125172,9 +125578,19 @@ var MemoryManager = class {
125172
125578
  vector = await embed(trimmed);
125173
125579
  } catch (err) {
125174
125580
  await this.store.appendMemory(scope, id, trimmed, kind);
125175
- throw err;
125581
+ log155.warn(
125582
+ `[memory] saved ${scope}/${id} to Markdown; semantic indexing deferred: ${describeError3(err)}`
125583
+ );
125584
+ return deferredIndexOutcome();
125585
+ }
125586
+ let top;
125587
+ try {
125588
+ top = (await searchByVector(indexDir, vector, 1))[0];
125589
+ } catch (error) {
125590
+ log155.warn(
125591
+ `[memory] vector dedup unavailable for ${scope}/${id}; continuing save: ${describeError3(error)}`
125592
+ );
125176
125593
  }
125177
- const top = (await searchByVector(indexDir, vector, 1))[0];
125178
125594
  if (top && top.score >= this.dedupThreshold) {
125179
125595
  log155.info(
125180
125596
  `[memory] dup-skip (${top.score.toFixed(2)} vs "${top.text.slice(0, 40)}") ${scope}/${id}: ${trimmed.slice(0, 60)}`
@@ -125182,20 +125598,27 @@ var MemoryManager = class {
125182
125598
  return { status: "duplicate", match: { text: top.text, score: top.score, via: "vector" } };
125183
125599
  }
125184
125600
  await this.store.appendMemory(scope, id, trimmed, kind);
125185
- await addToIndex(
125186
- indexDir,
125187
- {
125188
- text: trimmed,
125189
- scope,
125190
- id,
125191
- day: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
125192
- at: (/* @__PURE__ */ new Date()).toISOString(),
125193
- kind
125194
- },
125195
- vector
125196
- );
125601
+ try {
125602
+ await addToIndex(
125603
+ indexDir,
125604
+ {
125605
+ text: trimmed,
125606
+ scope,
125607
+ id,
125608
+ day: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
125609
+ at: (/* @__PURE__ */ new Date()).toISOString(),
125610
+ kind
125611
+ },
125612
+ vector
125613
+ );
125614
+ } catch (error) {
125615
+ log155.warn(
125616
+ `[memory] saved ${scope}/${id} to Markdown; semantic index write deferred: ${describeError3(error)}`
125617
+ );
125618
+ return deferredIndexOutcome();
125619
+ }
125197
125620
  log155.info(`[memory] saved ${scope}/${id} [${kind}]: ${trimmed.slice(0, 60)}`);
125198
- return { status: "saved" };
125621
+ return { status: "saved", indexed: true };
125199
125622
  }
125200
125623
  /**
125201
125624
  * Exact-text match against today's and yesterday's daily files. Two days
@@ -125217,7 +125640,15 @@ var MemoryManager = class {
125217
125640
  }
125218
125641
  async search(scope, id, query, topK = 10) {
125219
125642
  const indexDir = this.store.memoryIndexDir(scope, id);
125220
- return searchIndex(indexDir, query, topK);
125643
+ if (!this.hasIndex(scope, id)) return this.searchLexical(scope, id, query, topK);
125644
+ try {
125645
+ return await searchIndex(indexDir, query, topK);
125646
+ } catch (error) {
125647
+ log155.warn(
125648
+ `[memory] semantic search failed for ${scope}/${id}; using lexical fallback: ${describeError3(error)}`
125649
+ );
125650
+ return this.searchLexical(scope, id, query, topK);
125651
+ }
125221
125652
  }
125222
125653
  /**
125223
125654
  * Like {@link search} but with a precomputed query embedding — lets the
@@ -125246,11 +125677,64 @@ var MemoryManager = class {
125246
125677
  return existsSync28(join103(this.store.memoryIndexDir(scope, id), "mem.db"));
125247
125678
  }
125248
125679
  async searchAll(gezelId, projectId, query, topK = 10) {
125249
- const [agentResults, projectResults] = await Promise.all([
125250
- this.search("gezel", gezelId, query, topK),
125251
- this.search("project", projectId, query, topK)
125680
+ return (await this.searchAllDetailed(gezelId, projectId, query, topK)).results;
125681
+ }
125682
+ /**
125683
+ * Search both scopes with one query embedding. Missing indexes fall back to
125684
+ * the daily Markdown source; an unavailable/corrupt semantic cache does the
125685
+ * same and reports a degraded mode instead of turning an optional feature
125686
+ * into an HTTP 500.
125687
+ */
125688
+ async searchAllDetailed(gezelId, projectId, query, topK = 10) {
125689
+ const gezelIndexed = this.hasIndex("gezel", gezelId);
125690
+ const projectIndexed = this.hasIndex("project", projectId);
125691
+ if (!gezelIndexed && !projectIndexed) {
125692
+ return {
125693
+ results: await this.searchAllLexical(gezelId, projectId, query, topK),
125694
+ mode: "lexical"
125695
+ };
125696
+ }
125697
+ try {
125698
+ const vector = await embedQuery(query);
125699
+ const [gezelResults, projectResults] = await Promise.all([
125700
+ gezelIndexed ? this.searchVector("gezel", gezelId, vector, topK) : this.searchLexical("gezel", gezelId, query, topK),
125701
+ projectIndexed ? this.searchVector("project", projectId, vector, topK) : this.searchLexical("project", projectId, query, topK)
125702
+ ]);
125703
+ return {
125704
+ results: rankSearchResults([...gezelResults, ...projectResults], topK),
125705
+ mode: gezelIndexed && projectIndexed ? "semantic" : "hybrid"
125706
+ };
125707
+ } catch (error) {
125708
+ log155.warn(
125709
+ `[memory] semantic search unavailable; using daily-memory lexical fallback: ${describeError3(error)}`
125710
+ );
125711
+ return {
125712
+ results: await this.searchAllLexical(gezelId, projectId, query, topK),
125713
+ mode: "lexical",
125714
+ degraded: {
125715
+ code: "semantic_search_unavailable",
125716
+ message: "Semantic memory search is temporarily unavailable; searched saved memory text directly instead."
125717
+ }
125718
+ };
125719
+ }
125720
+ }
125721
+ async searchAllLexical(gezelId, projectId, query, topK) {
125722
+ const [gezelResults, projectResults] = await Promise.all([
125723
+ this.searchLexical("gezel", gezelId, query, topK),
125724
+ this.searchLexical("project", projectId, query, topK)
125252
125725
  ]);
125253
- return [...agentResults, ...projectResults].sort((a, b) => b.score - a.score).slice(0, topK);
125726
+ return rankSearchResults([...gezelResults, ...projectResults], topK);
125727
+ }
125728
+ async searchLexical(scope, id, query, topK) {
125729
+ const entries = await this.allEntries(scope, id);
125730
+ return entries.map((entry) => ({ entry, score: lexicalScore(query, entry.text) })).filter((candidate) => candidate.score > 0).sort((a, b) => b.score - a.score || b.entry.at.localeCompare(a.entry.at)).slice(0, topK).map(({ entry, score }) => ({
125731
+ text: entry.text,
125732
+ score,
125733
+ day: entry.day,
125734
+ scope: entry.scope,
125735
+ id: entry.id,
125736
+ kind: entry.kind ?? DEFAULT_MEMORY_KIND
125737
+ }));
125254
125738
  }
125255
125739
  async listDays(scope, id) {
125256
125740
  return this.store.listMemoryDays(scope, id);
@@ -125315,6 +125799,36 @@ var MemoryManager = class {
125315
125799
  return entries.length;
125316
125800
  }
125317
125801
  };
125802
+ function rankSearchResults(results, topK) {
125803
+ return results.sort((a, b) => b.score - a.score).slice(0, topK);
125804
+ }
125805
+ function deferredIndexOutcome() {
125806
+ return {
125807
+ status: "saved",
125808
+ indexed: false,
125809
+ degraded: {
125810
+ code: "semantic_index_unavailable",
125811
+ message: "Memory was saved, but semantic indexing is temporarily unavailable."
125812
+ }
125813
+ };
125814
+ }
125815
+ function lexicalScore(query, text) {
125816
+ const normalizedQuery = query.trim().toLocaleLowerCase();
125817
+ const normalizedText = text.toLocaleLowerCase();
125818
+ if (!normalizedQuery || !normalizedText) return 0;
125819
+ if (normalizedText.includes(normalizedQuery)) return 1;
125820
+ const queryTokens = new Set(normalizedQuery.match(/[\p{L}\p{N}_-]+/gu) ?? []);
125821
+ if (queryTokens.size === 0) return 0;
125822
+ const textTokens = new Set(normalizedText.match(/[\p{L}\p{N}_-]+/gu) ?? []);
125823
+ let matches = 0;
125824
+ for (const token of queryTokens) {
125825
+ if (textTokens.has(token)) matches++;
125826
+ }
125827
+ return matches / queryTokens.size;
125828
+ }
125829
+ function describeError3(error) {
125830
+ return error instanceof Error ? error.message : String(error);
125831
+ }
125318
125832
 
125319
125833
  // src/models/install-jobs.ts
125320
125834
  import { createLogger as createLogger166 } from "@bendyline/gezel";
@@ -125477,6 +125991,7 @@ var LlamaVisionProvider = class {
125477
125991
  await mkdir57(itemDir, { recursive: true });
125478
125992
  const totalAllBytes = spec.files.reduce((n, f) => n + f.approxSizeBytes, 0);
125479
125993
  let writtenAll = 0;
125994
+ const verifiedDigests = {};
125480
125995
  for (const file of spec.files) {
125481
125996
  const result = yield* downloadWithSha256(this.fetchImpl, {
125482
125997
  url: file.downloadUrl,
@@ -125492,8 +126007,9 @@ var LlamaVisionProvider = class {
125492
126007
  return;
125493
126008
  }
125494
126009
  writtenAll = result.writtenAll;
126010
+ verifiedDigests[file.filename] = file.sha256.toLowerCase();
125495
126011
  }
125496
- const fileSha256 = await hashModelPayloadFiles(itemDir);
126012
+ const fileSha256 = await hashModelPayloadFiles(itemDir, verifiedDigests);
125497
126013
  await writeFile26(
125498
126014
  join104(itemDir, "manifest.json"),
125499
126015
  `${JSON.stringify(
@@ -125913,7 +126429,7 @@ async function* runGgufInstallWithCompanion(catalogId, o, deps) {
125913
126429
  yield event;
125914
126430
  }
125915
126431
  if (!finished) return;
125916
- yield* companionRecognitionEvents(catalogId, deps);
126432
+ if (o.installCompanion) yield* companionRecognitionEvents(catalogId, deps);
125917
126433
  yield finished;
125918
126434
  }
125919
126435
  async function* companionRecognitionEvents(catalogId, deps) {
@@ -127221,7 +127737,7 @@ function makeActionable(message) {
127221
127737
  }
127222
127738
 
127223
127739
  // src/providers/image/sd-cpp.ts
127224
- import { createHash as createHash44 } from "crypto";
127740
+ import { createHash as createHash45 } from "crypto";
127225
127741
  import { createReadStream as createReadStream10 } from "fs";
127226
127742
  import { mkdir as mkdir60, readFile as readFile81, rename as rename15, rm as rm41, writeFile as writeFile29 } from "fs/promises";
127227
127743
  import { join as join110 } from "path";
@@ -127560,7 +128076,7 @@ var StableDiffusionCppProvider = class {
127560
128076
  }
127561
128077
  }
127562
128078
  const partialPath = `${destPath}.partial`;
127563
- const hasher = createHash44("sha256");
128079
+ const hasher = createHash45("sha256");
127564
128080
  await new Promise((resolve18, reject) => {
127565
128081
  const stream = createReadStream10(partialPath, {
127566
128082
  highWaterMark: MODEL_HASH_READ_BUFFER_BYTES
@@ -128079,7 +128595,7 @@ function isMachineImageProvider(config, env) {
128079
128595
  }
128080
128596
 
128081
128597
  // src/providers/recognition/store.ts
128082
- import { createHash as createHash45 } from "crypto";
128598
+ import { createHash as createHash46 } from "crypto";
128083
128599
  import { mkdir as mkdir61, readFile as readFile82, rename as rename16, rm as rm42, writeFile as writeFile30 } from "fs/promises";
128084
128600
  import { join as join112 } from "path";
128085
128601
  import { ImageRecognitionSchema, createLogger as createLogger172 } from "@bendyline/gezel";
@@ -128091,8 +128607,8 @@ var RecognitionCache = class {
128091
128607
  this.root = join112(opts.home, "recognition");
128092
128608
  }
128093
128609
  keyFor(input) {
128094
- const imageHash = createHash45("sha256").update(input.bytes).digest("hex");
128095
- return createHash45("sha256").update(`${imageHash}\0${input.mode}\0${input.modelId}\0v${PROMPT_VERSION}`).digest("hex");
128610
+ const imageHash = createHash46("sha256").update(input.bytes).digest("hex");
128611
+ return createHash46("sha256").update(`${imageHash}\0${input.mode}\0${input.modelId}\0v${PROMPT_VERSION}`).digest("hex");
128096
128612
  }
128097
128613
  pathFor(key) {
128098
128614
  return join112(this.root, key.slice(0, 2), `${key}.json`);
@@ -128537,7 +129053,7 @@ var MockVideoProvider = class {
128537
129053
  };
128538
129054
 
128539
129055
  // src/providers/video/models.ts
128540
- import { createHash as createHash46 } from "crypto";
129056
+ import { createHash as createHash47 } from "crypto";
128541
129057
  import { createReadStream as createReadStream11 } from "fs";
128542
129058
  import { mkdir as mkdir62, readFile as readFile83, rename as rename17, rm as rm43, writeFile as writeFile31 } from "fs/promises";
128543
129059
  import { dirname as dirname50, join as join113 } from "path";
@@ -128723,7 +129239,7 @@ var VideoModelManager = class {
128723
129239
  await rm43(finalPath, { force: true });
128724
129240
  await rename17(partialPath, finalPath);
128725
129241
  } catch (err) {
128726
- yield { type: "error", error: `rename failed for ${file.name}: ${describeError3(err)}` };
129242
+ yield { type: "error", error: `rename failed for ${file.name}: ${describeError4(err)}` };
128727
129243
  return;
128728
129244
  }
128729
129245
  }
@@ -128810,7 +129326,7 @@ var VideoModelManager = class {
128810
129326
  }
128811
129327
  }
128812
129328
  push({ type: "verifying", file: file.name });
128813
- const hasher = createHash46("sha256");
129329
+ const hasher = createHash47("sha256");
128814
129330
  await new Promise((resolve18, reject) => {
128815
129331
  const stream = createReadStream11(tmpPath, { highWaterMark: MODEL_HASH_READ_BUFFER_BYTES });
128816
129332
  stream.on("data", (chunk2) => hasher.update(chunk2));
@@ -128882,7 +129398,7 @@ var VideoModelSelector = class {
128882
129398
  return (id ? await this.models.resolveModel(id) : null) ?? await this.models.resolveDefaultModel();
128883
129399
  }
128884
129400
  };
128885
- function describeError3(err) {
129401
+ function describeError4(err) {
128886
129402
  return err instanceof Error ? err.message : String(err);
128887
129403
  }
128888
129404
  function isSafeId3(id) {
@@ -133083,11 +133599,18 @@ var TaskRunner = class {
133083
133599
  /** Run one serialized dispatch pass now, sharing the interval's overlap guard. */
133084
133600
  async wake() {
133085
133601
  if (this.tickInFlight) return this.tickInFlight;
133086
- this.tickInFlight = this.tick().finally(() => {
133602
+ this.tickInFlight = this.tickOnce().finally(() => {
133087
133603
  this.tickInFlight = null;
133088
133604
  });
133089
133605
  return this.tickInFlight;
133090
133606
  }
133607
+ /**
133608
+ * Public test/operator entry point. Keep it serialized with the interval:
133609
+ * callers may drive a real, already-started service whose ticker is active.
133610
+ */
133611
+ async tick() {
133612
+ return this.wake();
133613
+ }
133091
133614
  /**
133092
133615
  * Scan every `active` task and enqueue a handoff for any whose
133093
133616
  * current step has an effective gezel owner. Call on service boot and
@@ -133134,9 +133657,9 @@ var TaskRunner = class {
133134
133657
  /**
133135
133658
  * One dispatch pass. Walks pending in FIFO order; dispatches any
133136
133659
  * whose target provider has a free slot; skips items for tasks
133137
- * that are no longer active. Public for test-driven ticks.
133660
+ * that are no longer active.
133138
133661
  */
133139
- async tick() {
133662
+ async tickOnce() {
133140
133663
  await this.pruneActiveDispatches();
133141
133664
  if (this.pending.length === 0) {
133142
133665
  this.holdReason = void 0;
@@ -133148,6 +133671,7 @@ var TaskRunner = class {
133148
133671
  return;
133149
133672
  }
133150
133673
  const snapshot = this.pending.slice();
133674
+ const snapshotIds = new Set(snapshot.map((handoff) => handoff.id));
133151
133675
  const keep = [];
133152
133676
  const inTickDispatches = /* @__PURE__ */ new Map();
133153
133677
  const nightShiftOn = this.isNightShiftActive();
@@ -133213,6 +133737,7 @@ var TaskRunner = class {
133213
133737
  ...handoff.fromGezelName ? { fromGezelName: handoff.fromGezelName } : {},
133214
133738
  ...handoff.fromGezelId ? { fromGezelId: handoff.fromGezelId } : {},
133215
133739
  ...handoff.kind ? { kind: handoff.kind } : {},
133740
+ ...task.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: task.roleBasedNameOnlyMode } : {},
133216
133741
  lane,
133217
133742
  ...isNight ? { ambient: true } : {},
133218
133743
  ...isNight ? { nightShift: true } : {},
@@ -133235,8 +133760,9 @@ var TaskRunner = class {
133235
133760
  );
133236
133761
  }
133237
133762
  }
133763
+ const enqueuedDuringTick = this.pending.filter((handoff) => !snapshotIds.has(handoff.id));
133238
133764
  this.pending.length = 0;
133239
- this.pending.push(...keep);
133765
+ this.pending.push(...keep, ...enqueuedDuringTick);
133240
133766
  this.holdReason = keep.some((handoff) => handoff.heldFor === "provider-busy") ? "provider-busy" : void 0;
133241
133767
  }
133242
133768
  async pruneActiveDispatches() {
@@ -133726,6 +134252,10 @@ Step "${step.name}" (\`${step.id}\`) was re-driven ${maxRedrives}\xD7 without pr
133726
134252
  } catch {
133727
134253
  }
133728
134254
  const activeTasks = projectTasks.filter((t) => t.status === "active");
134255
+ if (projectTasks.length === 0) {
134256
+ trace("skip \u2014 project has no tasks to advance");
134257
+ return;
134258
+ }
133729
134259
  if (projectTasks.length > 0 && activeTasks.length === 0) {
133730
134260
  if (projectTasks.some((t) => t.status === "draft")) {
133731
134261
  trace("skip \u2014 only draft task(s) await activation; not nudging or stabilizing");
@@ -137467,7 +137997,7 @@ Address these, then the gate re-checks automatically.`,
137467
137997
  try {
137468
137998
  const existing = await tasks.listChildren(task.ref).catch(() => []);
137469
137999
  if (existing.length === 0) {
137470
- const raw = await store.readProjectWorkspaceFile(projectId, spawn14.overFile).catch(() => null);
138000
+ const raw = await (spawn14.overArtifact ? store.readProjectArtifact(projectId, spawn14.overFile) : store.readProjectWorkspaceFile(projectId, spawn14.overFile)).catch(() => null);
137471
138001
  const items = raw ? extractSpawnItems(raw, spawn14.itemsPath) : [];
137472
138002
  if (items.length === 0) {
137473
138003
  log188.warn(
@@ -137496,7 +138026,7 @@ Address these, then the gate re-checks automatically.`,
137496
138026
 
137497
138027
  ${kids.map((k) => `- ${k.ref}: ${k.title}`).join("\n")}
137498
138028
  `;
137499
- await store.writeProjectWorkspaceFile(projectId, advanceFile, manifest).catch((err) => log188.warn(`[fanout] ${task.ref}: could not write ${advanceFile}:`, err));
138029
+ await (newStep.advanceWhen?.artifact ? store.writeProjectArtifact(projectId, advanceFile, manifest) : store.writeProjectWorkspaceFile(projectId, advanceFile, manifest)).catch((err) => log188.warn(`[fanout] ${task.ref}: could not write ${advanceFile}:`, err));
137500
138030
  }
137501
138031
  const nextStep = newStep.advanceWhen?.goto ?? newStep.next;
137502
138032
  if (nextStep) {