@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
@@ -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 {
@@ -11699,7 +11758,7 @@ var ChatEventBus = class {
11699
11758
  };
11700
11759
 
11701
11760
  // src/chat/manager.ts
11702
- import { randomUUID as randomUUID7 } from "crypto";
11761
+ import { createHash as createHash12, randomUUID as randomUUID7 } from "crypto";
11703
11762
  import { createRequire } from "module";
11704
11763
  import { join as join28 } from "path";
11705
11764
  import {
@@ -14094,15 +14153,13 @@ import { createLogger as createLogger4 } from "@bendyline/gezel";
14094
14153
  // src/model-profile/behaviors/fabrication-detect-claim-without-tool.ts
14095
14154
  var PROJECT_CREATION_VERBS = [
14096
14155
  "created",
14097
- "initiated",
14098
14156
  "initialized",
14099
14157
  "set\\s+up",
14100
14158
  "spun\\s+up",
14101
- "started",
14102
14159
  "made",
14103
- "bootstrapped",
14104
- "kicked\\s+off"
14160
+ "bootstrapped"
14105
14161
  ].join("|");
14162
+ var PROJECT_WORK_START_VERBS = ["initiated", "started", "kicked\\s+off"].join("|");
14106
14163
  var TASK_CREATION_VERBS = ["created", "added", "queued", "set\\s+up"].join("|");
14107
14164
  var GEZEL_CREATION_VERBS = [
14108
14165
  "created",
@@ -14163,6 +14220,18 @@ var CLAIM_RULES = [
14163
14220
  claim: "recruited a voorman",
14164
14221
  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."
14165
14222
  },
14223
+ {
14224
+ // Ambiguous kickoff verbs only become a container-creation claim when
14225
+ // the prose explicitly says this is a new/separate project. Otherwise
14226
+ // the next rule treats them as starting work in the current project.
14227
+ pattern: new RegExp(
14228
+ 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`,
14229
+ "i"
14230
+ ),
14231
+ requiredTools: ["start_project"],
14232
+ claim: "created a project",
14233
+ 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."
14234
+ },
14166
14235
  {
14167
14236
  pattern: new RegExp(
14168
14237
  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
@@ -14184,6 +14253,15 @@ var CLAIM_RULES = [
14184
14253
  claim: "created a project",
14185
14254
  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."
14186
14255
  },
14256
+ {
14257
+ pattern: new RegExp(
14258
+ 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`,
14259
+ "i"
14260
+ ),
14261
+ requiredTools: ["invoke_craftbook", "create_task", "start_project"],
14262
+ claim: "started work in the current project",
14263
+ 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."
14264
+ },
14187
14265
  {
14188
14266
  pattern: new RegExp(
14189
14267
  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`,
@@ -15106,13 +15184,26 @@ var BUILD_REQUEST_RE = new RegExp(
15106
15184
  function looksLikeNewBuildRequest(text) {
15107
15185
  return BUILD_REQUEST_RE.test(text);
15108
15186
  }
15187
+ function explicitlyRequestsSeparateProject(text) {
15188
+ return /\b(?:new|fresh|separate|another|dedicated)\s+(?:(?:standalone|independent)\s+)?(?:project|workspace)\b/i.test(
15189
+ text
15190
+ ) || /\b(?:as|in|into|under|for)\s+(?:a|an)\s+(?:new|fresh|separate|another|dedicated)\s+(?:project|workspace)\b/i.test(
15191
+ text
15192
+ );
15193
+ }
15109
15194
  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.)';
15110
15195
  var PromptMeesterBuildPrelude = {
15111
15196
  id: "prompt.meester-build-prelude",
15112
15197
  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`.',
15113
15198
  userPromptPrelude(ctx) {
15114
15199
  if (!ctx.isMeester) return null;
15200
+ if (ctx.messageOrigin !== "direct-user") return null;
15201
+ if (/^\s*\[Answer to:/i.test(ctx.userText)) return null;
15115
15202
  if (!looksLikeNewBuildRequest(ctx.userText)) return null;
15203
+ if (ctx.projectId !== "default" && !explicitlyRequestsSeparateProject(ctx.userText)) {
15204
+ return null;
15205
+ }
15206
+ if (!ctx.availableToolNames.includes("start_project")) return null;
15116
15207
  return MEESTER_BUILD_PRELUDE;
15117
15208
  }
15118
15209
  };
@@ -18161,6 +18252,12 @@ var AnthropicCliSession = class extends StreamingSessionBase {
18161
18252
  providerState() {
18162
18253
  return this.claudeSessionIdCache ? { claudeCliSessionId: this.claudeSessionIdCache } : {};
18163
18254
  }
18255
+ getRegisteredToolNames() {
18256
+ const context = this.deps.context;
18257
+ return [
18258
+ .../* @__PURE__ */ new Set([...context.allowedBuiltinTools ?? [], ...context.allowedMcpTools ?? []])
18259
+ ];
18260
+ }
18164
18261
  async disconnect() {
18165
18262
  this.aborted = true;
18166
18263
  try {
@@ -19709,6 +19806,10 @@ var AnthropicSession = class extends StreamingSessionBase {
19709
19806
  * resolves and persisted on `ChatMessage.reasoning`.
19710
19807
  */
19711
19808
  lastTurnReasoning = "";
19809
+ getRegisteredToolNames() {
19810
+ const bridgeNames = this.deps.bridges.isEmpty() ? [] : this.deps.bridges.getAnthropicTools().map((tool) => tool.name);
19811
+ return [.../* @__PURE__ */ new Set([...bridgeNames, ...this.externalToolNames])];
19812
+ }
19712
19813
  capturedToolCalls() {
19713
19814
  return [...this.capturedCalls];
19714
19815
  }
@@ -26973,7 +27074,12 @@ var LlamaCppSession = class extends StreamingSessionBase {
26973
27074
  if (toolCalls.length > 0) {
26974
27075
  const normalized2 = normalizeMalformedStructuredToolCalls(
26975
27076
  toolCalls,
26976
- collectKnownToolNames()
27077
+ collectKnownToolNames(),
27078
+ // A malformed write at the output ceiling is not repairable: the
27079
+ // missing JSON tail may also be missing file bytes. Executing the
27080
+ // loose prefix can silently replace a complete file with a
27081
+ // truncated one. Valid, fully parseable calls remain eligible.
27082
+ finishReason !== "length"
26977
27083
  );
26978
27084
  toolCalls = normalized2.toolCalls;
26979
27085
  malformedStructuredCallIds = new Set(normalized2.sanitizedIds);
@@ -27850,7 +27956,7 @@ var ToolCallAccumulator = class {
27850
27956
  }));
27851
27957
  }
27852
27958
  };
27853
- function normalizeMalformedStructuredToolCalls(toolCalls, knownToolNames) {
27959
+ function normalizeMalformedStructuredToolCalls(toolCalls, knownToolNames, allowMalformedWriteRepair = true) {
27854
27960
  const repaired = [];
27855
27961
  const sanitized = [];
27856
27962
  const sanitizedIds = [];
@@ -27861,11 +27967,7 @@ function normalizeMalformedStructuredToolCalls(toolCalls, knownToolNames) {
27861
27967
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return call;
27862
27968
  } catch {
27863
27969
  }
27864
- const repairedArgs = tryRepairMalformedWriteToolArguments(
27865
- call.function.name,
27866
- raw,
27867
- knownToolNames
27868
- );
27970
+ const repairedArgs = allowMalformedWriteRepair ? tryRepairMalformedWriteToolArguments(call.function.name, raw, knownToolNames) : null;
27869
27971
  if (repairedArgs) {
27870
27972
  repaired.push({
27871
27973
  name: call.function.name,
@@ -32027,6 +32129,7 @@ function buildLlamaCppEngineArgs(input) {
32027
32129
  ggufHasMtp,
32028
32130
  installedDraftModelPath,
32029
32131
  reasoningFormat,
32132
+ reasoningPreserve,
32030
32133
  architecture,
32031
32134
  modelId,
32032
32135
  swaFullAutoFits
@@ -32074,6 +32177,7 @@ function buildLlamaCppEngineArgs(input) {
32074
32177
  if (typeof nMax === "number") args.push("--spec-draft-n-max", String(nMax));
32075
32178
  }
32076
32179
  if (reasoningFormat) args.push("--reasoning-format", reasoningFormat);
32180
+ if (reasoningPreserve) args.push("--reasoning-preserve");
32077
32181
  if (config.llamaCppExtraArgs) {
32078
32182
  for (const [rawKey, value] of Object.entries(config.llamaCppExtraArgs)) {
32079
32183
  if (value === false) continue;
@@ -32394,6 +32498,31 @@ function fitsSwaFullInFastMemory(input) {
32394
32498
  return Number.isFinite(required) && Number.isFinite(available) && required <= available;
32395
32499
  }
32396
32500
 
32501
+ // src/providers/llama-cpp/reasoning-launch.ts
32502
+ var POSITIVE_INTEGER = /^[1-9]\d*$/;
32503
+ function parseReasoningPreserveEnv(raw) {
32504
+ const normalized2 = raw?.trim().toLowerCase();
32505
+ return normalized2 === "1" || normalized2 === "true";
32506
+ }
32507
+ function parseReasoningBudgetEnv(raw) {
32508
+ const normalized2 = raw?.trim();
32509
+ if (!normalized2) return void 0;
32510
+ if (!POSITIVE_INTEGER.test(normalized2)) {
32511
+ throw new Error("GEZEL_LLAMA_REASONING_BUDGET_TOKENS must be a positive integer");
32512
+ }
32513
+ const parsed = Number(normalized2);
32514
+ if (!Number.isSafeInteger(parsed)) {
32515
+ throw new Error("GEZEL_LLAMA_REASONING_BUDGET_TOKENS exceeds the safe integer range");
32516
+ }
32517
+ return parsed;
32518
+ }
32519
+ function reasoningLaunchOverridesFromEnv(env = process.env) {
32520
+ return {
32521
+ preserve: parseReasoningPreserveEnv(env.GEZEL_LLAMA_REASONING_PRESERVE),
32522
+ budgetTokens: parseReasoningBudgetEnv(env.GEZEL_LLAMA_REASONING_BUDGET_TOKENS)
32523
+ };
32524
+ }
32525
+
32397
32526
  // src/providers/llama-cpp/spec-draft.ts
32398
32527
  async function resolveSpecDraft(input) {
32399
32528
  const { perModel, configSpecType, configDraftModelPath, resolveDraftPath } = input;
@@ -32452,6 +32581,8 @@ async function buildLlamaCppProvider(opts) {
32452
32581
  const { config, affinity, home } = opts;
32453
32582
  const externalBaseUrl = opts.modelOverride ? void 0 : process.env.GEZEL_LLAMA_SERVER_URL ?? config.llamaCppBaseUrl;
32454
32583
  const binary = process.env.GEZEL_LLAMA_SERVER_BIN;
32584
+ const reasoningLaunchOverrides = reasoningLaunchOverridesFromEnv();
32585
+ const reasoningPreserve = reasoningLaunchOverrides.preserve;
32455
32586
  const defaultModelId = opts.modelOverride?.modelId ?? config.defaultModel?.["llama-cpp"];
32456
32587
  const configuredSlots = config.providerConcurrency?.["llama-cpp"];
32457
32588
  const envNumCtx = (() => {
@@ -32474,6 +32605,7 @@ async function buildLlamaCppProvider(opts) {
32474
32605
  // rate for its own default engine — throughput was reachable only by
32475
32606
  // scraping stdout from the eval harness.
32476
32607
  includeUsageInStream: true,
32608
+ replayReasoningContent: reasoningPreserve,
32477
32609
  // External-baseUrl default; the supervised path overrides this with the
32478
32610
  // auto-sized `slots` computed below (after the model resolves).
32479
32611
  concurrency: configuredSlots ?? 2,
@@ -32664,10 +32796,12 @@ async function buildLlamaCppProvider(opts) {
32664
32796
  const slotSavePath = replicaSuffix ? join18(llamaCacheRoot, llamaFingerprint, replicaSuffix) : join18(llamaCacheRoot, llamaFingerprint);
32665
32797
  await mkdirAsync(slotSavePath, { recursive: true }).catch(() => {
32666
32798
  });
32667
- const reasoningBudgetTokens = opts.catalog ? await resolveCatalogReasoningBudget(
32799
+ const catalogReasoningBudgetTokens = opts.catalog ? await resolveCatalogReasoningBudget(
32668
32800
  opts.catalog,
32669
32801
  opts.modelOverride?.modelId ?? defaultModelId
32670
32802
  ) : void 0;
32803
+ const reasoningBudgetTokens = reasoningLaunchOverrides.budgetTokens ?? catalogReasoningBudgetTokens;
32804
+ const reasoningBudgetSource = reasoningLaunchOverrides.budgetTokens !== void 0 ? "env" : catalogReasoningBudgetTokens !== void 0 ? "catalog" : "unbounded";
32671
32805
  const specDraft = await resolveSpecDraft({
32672
32806
  perModel: manifestEngineConfig,
32673
32807
  configSpecType: config.llamaCppSpecType,
@@ -33057,6 +33191,7 @@ async function buildLlamaCppProvider(opts) {
33057
33191
  // disables llama-server's chat-template output parsing so mangled
33058
33192
  // model output reaches `delta.content` for provider-side salvage.
33059
33193
  reasoningFormat: process.env.GEZEL_LLAMA_REASONING_FORMAT?.trim() || void 0,
33194
+ reasoningPreserve,
33060
33195
  architecture: modelCatalogInfo?.architecture,
33061
33196
  modelId: defaultModelId ?? void 0,
33062
33197
  swaFullAutoFits
@@ -33165,6 +33300,9 @@ async function buildLlamaCppProvider(opts) {
33165
33300
  batchSize: lastArgValue(resolvedAdvancedArgs, "--batch-size") ?? "default",
33166
33301
  ubatchSize: lastArgValue(resolvedAdvancedArgs, "--ubatch-size") ?? "default",
33167
33302
  flashAttention: lastArgValue(resolvedAdvancedArgs, "--flash-attn") ?? "default",
33303
+ reasoningBudgetTokens: reasoningBudgetTokens ?? "unbounded",
33304
+ reasoningBudgetSource,
33305
+ reasoningPreserve,
33168
33306
  cudaGraphs: process.env.GGML_CUDA_DISABLE_GRAPHS ? "disabled-by-env" : "default"
33169
33307
  },
33170
33308
  env: {
@@ -33330,7 +33468,7 @@ var PROJECT_MACRO_INTERCEPT_CAP = 2;
33330
33468
  var PROJECT_MACRO_FAILURE_CAP = 2;
33331
33469
  function deriveProjectMacroClosing(firstResult) {
33332
33470
  const projectMatch = firstResult.match(/Started (?:project|job) "([^"]+)"/);
33333
- const leadMatch = firstResult.match(/Recruited (\S+) as (voorman|ambachtsman|lead)/);
33471
+ const leadMatch = firstResult.match(/Recruited (\S+) as (voorman|builder|lead)/i);
33334
33472
  const projectName = projectMatch?.[1];
33335
33473
  const leadName = leadMatch?.[1];
33336
33474
  const leadRole = leadMatch?.[2];
@@ -35953,14 +36091,14 @@ async function buildMlxProvider(opts) {
35953
36091
  const cacheBudgetMb = Math.min(config.cacheBudgetMb?.mlx ?? ramAwareDefaultMb, mlxKvHeadroomMb);
35954
36092
  const mlxReplicaSuffix = opts.modelOverride && opts.modelOverride.replicaIdx > 0 ? `replica-${opts.modelOverride.replicaIdx}` : "";
35955
36093
  const cacheRoot = mlxReplicaSuffix ? join21(store.homePath, "engines", "mlx", "cache", mlxReplicaSuffix) : join21(store.homePath, "engines", "mlx", "cache");
35956
- const { createHash: createHash47 } = await import("crypto");
36094
+ const { createHash: createHash48 } = await import("crypto");
35957
36095
  const fingerprintInput = [
35958
36096
  modelCatalogInfo?.id ?? defaultModelId ?? modelDir,
35959
36097
  modelCatalogInfo?.catalogVersion ?? "",
35960
36098
  modelCatalogInfo?.installedAt ?? "",
35961
36099
  modelDir
35962
36100
  ].join("::");
35963
- const modelFingerprint = createHash47("sha256").update(fingerprintInput, "utf8").digest("hex").slice(0, 24);
36101
+ const modelFingerprint = createHash48("sha256").update(fingerprintInput, "utf8").digest("hex").slice(0, 24);
35964
36102
  const diskCacheBudgetMb = config.mlxDiskCacheBudgetMb ?? 8192;
35965
36103
  const mlxIdleMs = config.localEngineIdleTimeoutMs ?? DEFAULT_LOCAL_ENGINE_IDLE_TIMEOUT_MS3;
35966
36104
  const mlxFreezeMs = Math.floor(mlxIdleMs / 2);
@@ -37282,6 +37420,10 @@ var OpenAISession = class extends StreamingSessionBase {
37282
37420
  capturedToolCalls() {
37283
37421
  return [...this.capturedCalls];
37284
37422
  }
37423
+ getRegisteredToolNames() {
37424
+ const bridgeNames = this.deps.bridges.isEmpty() ? [] : this.deps.bridges.getOpenAITools().map((tool) => tool.name);
37425
+ return [.../* @__PURE__ */ new Set([...bridgeNames, ...this.externalToolNames])];
37426
+ }
37285
37427
  async sendAndWait(prompt, opts) {
37286
37428
  return runInQueue(this.deps.queue, opts?.queue, () => this.sendAndWaitInner(prompt, opts));
37287
37429
  }
@@ -40199,14 +40341,15 @@ async function evalCheck(c, ws, deps) {
40199
40341
  }
40200
40342
  async function evalCheckInner(c, ws, deps) {
40201
40343
  const usesArtifact = c.artifact === true;
40202
- const reader = usesArtifact ? {
40344
+ const artifactReader = {
40203
40345
  read: ws.readArtifact ?? (async () => null),
40204
40346
  list: ws.listArtifacts ?? (async () => []),
40205
40347
  // Carry the byte reader across the artifact swap; without it a
40206
40348
  // `verifyImageBytes` check on an artifact deliverable would report
40207
40349
  // "no binary reads" even though the surface supports them.
40208
40350
  ...ws.readArtifactBytes ? { readBytes: ws.readArtifactBytes.bind(ws) } : {}
40209
- } : ws;
40351
+ };
40352
+ const reader = usesArtifact ? artifactReader : ws;
40210
40353
  switch (c.kind) {
40211
40354
  case "minBytes": {
40212
40355
  const r = await fileMinBytes(reader, c.file, c.bytes);
@@ -40634,7 +40777,12 @@ ${hint}` : ""}`,
40634
40777
  };
40635
40778
  }
40636
40779
  case "markdownHeadingsMatch": {
40637
- const result = await markdownHeadingsMatch(reader, c.file, c.outlineFile);
40780
+ const result = await markdownHeadingsMatch(
40781
+ reader,
40782
+ c.file,
40783
+ c.outlineFile,
40784
+ c.outlineArtifact ? artifactReader : ws
40785
+ );
40638
40786
  return {
40639
40787
  ok: result.ok,
40640
40788
  detail: result.detail,
@@ -42549,6 +42697,11 @@ function filterPromptToolDirectives(args) {
42549
42697
  if (rejectedLines.size === 0) return args.prompt;
42550
42698
  return args.prompt.split("\n").filter((_, index) => !rejectedLines.has(index + 1)).join("\n").replace(/\n{4,}/g, "\n\n\n");
42551
42699
  }
42700
+ function capabilitySafeCorrectivePrompt(args) {
42701
+ const filtered = filterPromptToolDirectives(args).trim();
42702
+ if (filtered) return filtered;
42703
+ 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.";
42704
+ }
42552
42705
 
42553
42706
  // src/chat/provider-capabilities.ts
42554
42707
  function providerUsesManagedMcpBridge(name) {
@@ -46387,6 +46540,21 @@ function sameFromBucket(a, b) {
46387
46540
  if (!a || !b) return false;
46388
46541
  return a.gezelId === b.gezelId;
46389
46542
  }
46543
+ function resolveTurnMessageOrigin(opts) {
46544
+ if (opts?.messageOrigin) return opts.messageOrigin;
46545
+ if (opts?.from) return "cross-gezel";
46546
+ if (opts?.ambient || opts?.nudge || opts?.lane === "background") return "background-nudge";
46547
+ return "direct-user";
46548
+ }
46549
+ function liveTurnToolNames(session) {
46550
+ const names = /* @__PURE__ */ new Set();
46551
+ for (const name of session?.getRegisteredToolNames?.() ?? []) {
46552
+ names.add(name);
46553
+ const qualified = name.match(/^mcp__.+?__(.+)$/)?.[1];
46554
+ if (qualified && qualified !== "*") names.add(qualified);
46555
+ }
46556
+ return [...names];
46557
+ }
46390
46558
  async function resolveCatalogParameterSize(catalog, catalogId) {
46391
46559
  if (!catalogId) return void 0;
46392
46560
  try {
@@ -46528,10 +46696,12 @@ var ChatManager = class {
46528
46696
  */
46529
46697
  afterSessionIdle = /* @__PURE__ */ new Map();
46530
46698
  /**
46531
- * One live async file handoff per sender/target/project/path. Models can
46699
+ * One live async file handoff per sender/target/project/path/intent. Models can
46532
46700
  * emit the same `message_gezel` call several times while the sender turn
46533
46701
  * is still active; joining that work prevents a parked-send burst from
46534
- * replaying the same assignment into the recipient session.
46702
+ * replaying the same assignment into the recipient session. A later
46703
+ * validator repair for a file that was originally missing is distinct work
46704
+ * and must never join the older create request.
46535
46705
  */
46536
46706
  inflightFileHandoffs = /* @__PURE__ */ new Map();
46537
46707
  /** Set once {@link shutdown} starts so deferred watchdogs don't fire into a tearing-down manager. */
@@ -47788,6 +47958,7 @@ ${JSON.stringify(run.output, null, 2)}`;
47788
47958
  coalescable: false,
47789
47959
  lane: void 0,
47790
47960
  ambient: false,
47961
+ messageOrigin: "direct-user",
47791
47962
  continuationMaxTokens: void 0,
47792
47963
  hidden: false,
47793
47964
  nudge: false,
@@ -48093,11 +48264,14 @@ ${JSON.stringify(run.output, null, 2)}`;
48093
48264
  const full = await this.store.getSession(args.gezelId, active.id);
48094
48265
  if (full) return full;
48095
48266
  }
48267
+ const parsedTaskRef = parseTaskRef2(args.taskRef);
48268
+ const task = parsedTaskRef ? await this.store.readTask(parsedTaskRef.projectId, parsedTaskRef.num).catch(() => null) : null;
48096
48269
  return this.createSession({
48097
48270
  gezelId: args.gezelId,
48098
48271
  projectId: args.projectId,
48099
48272
  taskRef: args.taskRef,
48100
- ...args.stepId ? { stepId: args.stepId } : {}
48273
+ ...args.stepId ? { stepId: args.stepId } : {},
48274
+ ...task?.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: task.roleBasedNameOnlyMode } : {}
48101
48275
  });
48102
48276
  }
48103
48277
  /** Late-bind the fitness manager (see the field's docblock). */
@@ -48224,6 +48398,7 @@ ${JSON.stringify(run.output, null, 2)}`;
48224
48398
  */
48225
48399
  async startHandoffSession(args) {
48226
48400
  const dispatchConfig = await this.store.readConfig();
48401
+ const roleBasedNameOnlyMode = args.roleBasedNameOnlyMode ?? dispatchConfig.roleBasedNameOnlyMode ?? false;
48227
48402
  const dispatchGezel = await this.store.getGezel(args.gezelId);
48228
48403
  const dispatchProviderName = await this.resolveProviderName(
48229
48404
  args.gezelId,
@@ -48272,6 +48447,7 @@ ${JSON.stringify(run.output, null, 2)}`;
48272
48447
  projectId: args.projectId,
48273
48448
  taskRef: args.taskRef,
48274
48449
  stepId: dispatchStepId,
48450
+ roleBasedNameOnlyMode,
48275
48451
  ...args.nightShift ? { nightShift: true } : {},
48276
48452
  ...routed && args.capabilityFloor ? {
48277
48453
  routedModel: {
@@ -48328,9 +48504,15 @@ ${stepArc}
48328
48504
  }
48329
48505
  }
48330
48506
  const selfHandoff = args.fromGezelId !== void 0 && args.fromGezelId === args.gezelId;
48331
- 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.`;
48507
+ const previousGezel = !selfHandoff && args.fromGezelId ? await this.store.getGezel(args.fromGezelId).catch(() => null) : null;
48508
+ const fromGezelDisplayName = previousGezel ? displayName2(
48509
+ { name: previousGezel.name, roleBasedName: previousGezel.roleBasedName },
48510
+ roleBasedNameOnlyMode
48511
+ ) : roleBasedNameOnlyMode ? void 0 : args.fromGezelName;
48512
+ 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.`;
48332
48513
  this.trackBackground(
48333
48514
  this.sendWithBusyRetry(session.id, seed, {
48515
+ messageOrigin: "system",
48334
48516
  ...args.lane ? { lane: args.lane } : {},
48335
48517
  ...args.ambient ? { ambient: true } : {}
48336
48518
  }).catch((err) => {
@@ -48399,7 +48581,8 @@ ${stepArc}
48399
48581
  args.fromSessionId ?? args.fromGezelId,
48400
48582
  target.id,
48401
48583
  projectId,
48402
- normalizeExpectedDeliverablePath(requestedFilePath)
48584
+ normalizeExpectedDeliverablePath(requestedFilePath),
48585
+ fileHandoffIntentKey(args.text)
48403
48586
  ].join("\0") : null;
48404
48587
  const pendingHandoff = fileHandoffKey ? this.inflightFileHandoffs.get(fileHandoffKey) : void 0;
48405
48588
  if (pendingHandoff) return { ...pendingHandoff, deduplicated: true };
@@ -48440,7 +48623,7 @@ ${stepArc}
48440
48623
  const seed = `[Message from ${fromName}]: ${args.text}${deliverableAnnotation}`;
48441
48624
  const failureNotice = { sent: false };
48442
48625
  const resolvedFromSessionId = args.fromSessionId ?? (target.id === args.fromGezelId ? null : await this.ensureOrCreateSession({ gezelId: args.fromGezelId, projectId }).then((s) => s.id).catch(() => null));
48443
- if (resolvedFromSessionId && resolvedFromSessionId !== session.id) {
48626
+ if (!args.suppressReply && resolvedFromSessionId && resolvedFromSessionId !== session.id) {
48444
48627
  this.attachReplyListener({
48445
48628
  targetSessionId: session.id,
48446
48629
  fromSessionId: resolvedFromSessionId,
@@ -49176,11 +49359,10 @@ ${stepArc}
49176
49359
  log52.info(
49177
49360
  `[questions] delivering answer to session ${args.sessionId.slice(0, 8)} (gezel ${session.gezelId}, project ${session.projectId})`
49178
49361
  );
49179
- await this.sendWithBusyRetry(
49180
- args.sessionId,
49181
- args.seed,
49182
- args.coalescable ? { coalescable: true } : void 0
49183
- );
49362
+ await this.sendWithBusyRetry(args.sessionId, args.seed, {
49363
+ messageOrigin: "question-answer",
49364
+ ...args.coalescable ? { coalescable: true } : {}
49365
+ });
49184
49366
  }
49185
49367
  /**
49186
49368
  * System-authored reaction seed into a gezel's live (gezel, project)
@@ -49206,6 +49388,7 @@ ${stepArc}
49206
49388
  this.send(session.id, args.seed, {
49207
49389
  coalescable: true,
49208
49390
  lane: "background",
49391
+ messageOrigin: "system",
49209
49392
  ...leanReactionCap ? { continuationMaxTokens: leanReactionCap } : {},
49210
49393
  ...args.hidden ? { hidden: true } : {}
49211
49394
  }).catch((err) => {
@@ -49680,12 +49863,13 @@ ${stepArc}
49680
49863
  throw new Error("engagement-off: AI is disabled in settings; re-enable to send");
49681
49864
  }
49682
49865
  const existingQueue = this.pendingSends.get(sessionId2);
49866
+ const messageOrigin = resolveTurnMessageOrigin(opts);
49683
49867
  const shouldQueue = this.inflight.has(sessionId2) || existingQueue !== void 0 && existingQueue.length > 0;
49684
49868
  if (shouldQueue) {
49685
49869
  return new Promise((resolve19, reject) => {
49686
49870
  const q2 = this.pendingSends.get(sessionId2) ?? [];
49687
49871
  const tail = q2.length > 0 ? q2[q2.length - 1] : void 0;
49688
- const canMerge = opts?.coalescable === true && opts?.nudge !== true && tail?.coalescable === true && tail.nudge !== true && sameFromBucket(tail.from, opts.from);
49872
+ const canMerge = opts?.coalescable === true && opts?.nudge !== true && tail?.coalescable === true && tail.nudge !== true && tail.messageOrigin === messageOrigin && sameFromBucket(tail.from, opts.from);
49689
49873
  if (canMerge && tail) {
49690
49874
  tail.userText = `${tail.userText}
49691
49875
 
@@ -49712,6 +49896,7 @@ ${userText}`;
49712
49896
  coalescable: opts?.coalescable === true,
49713
49897
  lane: opts?.lane,
49714
49898
  ambient: opts?.ambient === true,
49899
+ messageOrigin,
49715
49900
  continuationMaxTokens: opts?.continuationMaxTokens,
49716
49901
  hidden: opts?.hidden === true,
49717
49902
  nudge: opts?.nudge === true,
@@ -49728,7 +49913,7 @@ ${userText}`;
49728
49913
  return this.runSendAndDrain(
49729
49914
  sessionId2,
49730
49915
  userText,
49731
- opts?.nudge ? { ...opts, nudge: false } : opts
49916
+ opts?.nudge ? { ...opts, nudge: false, messageOrigin } : { ...opts, messageOrigin }
49732
49917
  );
49733
49918
  }
49734
49919
  /**
@@ -49827,7 +50012,7 @@ ${userText}`;
49827
50012
  if (next.nudge) {
49828
50013
  while (q2.length > 0) {
49829
50014
  const peek = q2[0];
49830
- if (!peek.nudge || peek.hidden !== next.hidden || !sameFromBucket(peek.from, next.from)) {
50015
+ if (!peek.nudge || peek.hidden !== next.hidden || peek.messageOrigin !== next.messageOrigin || !sameFromBucket(peek.from, next.from)) {
49831
50016
  break;
49832
50017
  }
49833
50018
  q2.shift();
@@ -49850,6 +50035,7 @@ ${peek.userText}`;
49850
50035
  if (next.continuationMaxTokens) runOpts.continuationMaxTokens = next.continuationMaxTokens;
49851
50036
  if (next.hidden) runOpts.hidden = true;
49852
50037
  if (next.nudge) runOpts.nudge = true;
50038
+ runOpts.messageOrigin = next.messageOrigin;
49853
50039
  void this.runSendAndDrain(sessionId2, next.userText, runOpts).then((msg) => {
49854
50040
  for (const w of next.waiters) {
49855
50041
  try {
@@ -49880,20 +50066,36 @@ ${peek.userText}`;
49880
50066
  return this.runFixedFunctionSend(sessionId2, userText, opts);
49881
50067
  }
49882
50068
  }
50069
+ const preflightScope = ffRecord ? {
50070
+ sessionId: sessionId2,
50071
+ gezelId: ffRecord.gezelId,
50072
+ projectId: ffRecord.projectId
50073
+ } : void 0;
50074
+ const coldStartPending = preflightScope && !this.states.get(sessionId2)?.session;
50075
+ if (preflightScope && coldStartPending) {
50076
+ this.events.publish(preflightScope, {
50077
+ type: "user_message_pending",
50078
+ preview: userText.length > 160 ? `${userText.slice(0, 157)}\u2026` : userText
50079
+ });
50080
+ }
49883
50081
  const fail2 = (err) => {
49884
50082
  const message = err instanceof Error ? err.message : String(err);
49885
50083
  log52.error("error:", message);
50084
+ const publish = (event) => {
50085
+ if (preflightScope) this.events.publish(preflightScope, event);
50086
+ else this.events.publishSessionOnly(sessionId2, event);
50087
+ };
49886
50088
  if (inflightTurn.cancelled) {
49887
- this.events.publishSessionOnly(sessionId2, { type: "cancelled" });
50089
+ publish({ type: "cancelled" });
49888
50090
  } else {
49889
50091
  const detail = describeTurnError(err);
49890
- this.events.publishSessionOnly(sessionId2, {
50092
+ publish({
49891
50093
  type: "error",
49892
50094
  error: redactCredentials4(message),
49893
50095
  ...detail ? { errorDetail: detail } : {}
49894
50096
  });
49895
50097
  }
49896
- this.events.publishSessionOnly(sessionId2, { type: "done" });
50098
+ publish({ type: "done" });
49897
50099
  throw err;
49898
50100
  };
49899
50101
  if (ffRecord) {
@@ -50180,6 +50382,18 @@ ${peek.userText}`;
50180
50382
  try {
50181
50383
  let lastAssistantMessage = null;
50182
50384
  let promptForTurn = spliceIntoText(userText, pendingDigests);
50385
+ const correctivePromptForLiveRoster = (prompt, source) => {
50386
+ const safe = capabilitySafeCorrectivePrompt({
50387
+ prompt,
50388
+ availableTools: liveTurnToolNames(liveSession)
50389
+ });
50390
+ if (safe !== prompt.trim()) {
50391
+ log52.warn(
50392
+ `session ${sessionId2}: filtered ${source} corrective against the live tool roster`
50393
+ );
50394
+ }
50395
+ return safe;
50396
+ };
50183
50397
  const freshGameState = await this.refreshLeanGameState(state.record, userText);
50184
50398
  if (freshGameState) {
50185
50399
  promptForTurn = `${freshGameState}
@@ -50190,7 +50404,8 @@ ${promptForTurn}`;
50190
50404
  this.taskBudget.reset(state.record.taskRef);
50191
50405
  this.pendingBudgetNudge.delete(state.record.taskRef);
50192
50406
  }
50193
- const preludeForTurn = await this.resolveUserPromptPrelude(state, userText);
50407
+ const messageOrigin = resolveTurnMessageOrigin(opts);
50408
+ const preludeForTurn = await this.resolveUserPromptPrelude(state, userText, messageOrigin);
50194
50409
  if (preludeForTurn) {
50195
50410
  promptForTurn = `${preludeForTurn.text}
50196
50411
 
@@ -50213,7 +50428,7 @@ ${promptForTurn}`;
50213
50428
  }
50214
50429
  if (state.record.taskRef && this.pendingBudgetNudge.has(state.record.taskRef)) {
50215
50430
  this.pendingBudgetNudge.delete(state.record.taskRef);
50216
- promptForTurn = `${TASK_BUDGET_NUDGE}
50431
+ promptForTurn = `${correctivePromptForLiveRoster(TASK_BUDGET_NUDGE, "task-budget")}
50217
50432
 
50218
50433
  ${promptForTurn}`;
50219
50434
  log52.info(
@@ -50222,6 +50437,7 @@ ${promptForTurn}`;
50222
50437
  }
50223
50438
  let continuations = 0;
50224
50439
  const maxContinuations = resolveContinuationBudget(state);
50440
+ let voormanIdleNudged = false;
50225
50441
  const toolsAcrossContinuations = [];
50226
50442
  let compactionsThisSend = 0;
50227
50443
  let keurmeesterConsultedThisSend = false;
@@ -50485,6 +50701,7 @@ ${state.aboutSnapshot}`);
50485
50701
  const detectorVerdict = runPostTurnDetectors(state, {
50486
50702
  sessionId: sessionId2,
50487
50703
  isMeester: cfgForDetector?.meesterGezelId === state.record.gezelId,
50704
+ messageOrigin,
50488
50705
  userText,
50489
50706
  drained: toolsAcrossContinuations,
50490
50707
  assistantContent: assistantMessage.content,
@@ -50498,7 +50715,10 @@ ${state.aboutSnapshot}`);
50498
50715
  this.events.publish(scope, { type: "warning", message: warningMessage });
50499
50716
  }
50500
50717
  if (detectorVerdict?.promptForNextTurn) {
50501
- detectorReprompt = detectorVerdict.promptForNextTurn;
50718
+ detectorReprompt = correctivePromptForLiveRoster(
50719
+ detectorVerdict.promptForNextTurn,
50720
+ "post-turn detector"
50721
+ );
50502
50722
  }
50503
50723
  const advanceOutcome = await this.maybeAutoAdvanceOnObservableProgress(
50504
50724
  state,
@@ -50649,7 +50869,10 @@ ${state.aboutSnapshot}`);
50649
50869
  log52.info(
50650
50870
  `session ${sessionId2}: keurmeester intervention applied (case ${consult.caseId}) \u2014 granting one recovery continuation`
50651
50871
  );
50652
- promptForTurn = consult.correctivePrompt;
50872
+ promptForTurn = correctivePromptForLiveRoster(
50873
+ consult.correctivePrompt,
50874
+ "keurmeester"
50875
+ );
50653
50876
  continue;
50654
50877
  }
50655
50878
  }
@@ -50667,7 +50890,10 @@ ${state.aboutSnapshot}`);
50667
50890
  log52.info(
50668
50891
  `session ${sessionId2}: ${detectorVerdict?.reason ?? "detector"} \u2014 re-prompting`
50669
50892
  );
50670
- promptForTurn = detectorReprompt;
50893
+ promptForTurn = correctivePromptForLiveRoster(
50894
+ detectorReprompt,
50895
+ detectorVerdict ? "post-turn detector" : "deliverable salvage"
50896
+ );
50671
50897
  continue;
50672
50898
  }
50673
50899
  if (advanceOutcome.gateRejected && !advanceOutcome.gateRejected.paused && advanceOutcome.gateRejected.fingerprint !== state.lastGateNudgeFingerprint && !drained.some((d) => d.name === "ask_user_question" && d.success)) {
@@ -50677,7 +50903,10 @@ ${state.aboutSnapshot}`);
50677
50903
  log52.info(
50678
50904
  `session ${sessionId2}: ${advanceOutcome.gateRejected.taskRef} step "${advanceOutcome.gateRejected.stepId}" gate rejected \u2014 re-prompting with the verdict${stage > 0 ? ` (escalation stage ${stage})` : ""}`
50679
50905
  );
50680
- promptForTurn = stage >= 1 ? advanceOutcome.gateRejected.message : buildGateRejectionNudge(advanceOutcome.gateRejected.message);
50906
+ promptForTurn = correctivePromptForLiveRoster(
50907
+ stage >= 1 ? advanceOutcome.gateRejected.message : buildGateRejectionNudge(advanceOutcome.gateRejected.message),
50908
+ "task-gate"
50909
+ );
50681
50910
  continue;
50682
50911
  }
50683
50912
  if (deliverableOutcome.deliverableRejected?.stopRetrying) {
@@ -50728,7 +50957,10 @@ ${state.aboutSnapshot}`);
50728
50957
  log52.info(
50729
50958
  `session ${sessionId2}: keurmeester intervention applied on deliverable plateau (case ${consult.caseId}) \u2014 granting one recovery continuation`
50730
50959
  );
50731
- promptForTurn = consult.correctivePrompt;
50960
+ promptForTurn = correctivePromptForLiveRoster(
50961
+ consult.correctivePrompt,
50962
+ "keurmeester deliverable-plateau"
50963
+ );
50732
50964
  continue;
50733
50965
  }
50734
50966
  }
@@ -50752,7 +50984,10 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50752
50984
  ...dRej.filePath ? { filePath: dRej.filePath } : {},
50753
50985
  failingVerdict: dRej.message
50754
50986
  });
50755
- promptForTurn = clampNudge ?? (dStage >= 1 ? dRej.message : buildGateRejectionNudge(dRej.message));
50987
+ promptForTurn = correctivePromptForLiveRoster(
50988
+ clampNudge ?? (dStage >= 1 ? dRej.message : buildGateRejectionNudge(dRej.message)),
50989
+ "deliverable-gate"
50990
+ );
50756
50991
  continue;
50757
50992
  }
50758
50993
  if (advanceOutcome.unmetEditGate && (looksStalled(finalContent) || claimsCompletion(finalContent)) && !drained.some((d) => d.name === "ask_user_question" && d.success)) {
@@ -50760,7 +50995,10 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50760
50995
  log52.info(
50761
50996
  `session ${sessionId2}: ${advanceOutcome.unmetEditGate.taskRef} edit gate unmet (${advanceOutcome.unmetEditGate.file} not written) but reply claims progress \u2014 nudging to edit`
50762
50997
  );
50763
- promptForTurn = buildDeliverableEditNudge(advanceOutcome.unmetEditGate.file);
50998
+ promptForTurn = correctivePromptForLiveRoster(
50999
+ buildDeliverableEditNudge(advanceOutcome.unmetEditGate.file),
51000
+ "deliverable-edit"
51001
+ );
50764
51002
  continue;
50765
51003
  }
50766
51004
  const unresolvedToolFailures = unresolvedFailedToolCalls(drained);
@@ -50804,7 +51042,7 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50804
51042
  // the hot path synchronous for the common non-voorman case.
50805
51043
  // Also requires `proactive` — voorman idle nudges advance
50806
51044
  // project work past what the user asked for.
50807
- isProactiveAllowed(nudgeConfig) && state.record.providerName !== "copilot"
51045
+ isProactiveAllowed(nudgeConfig) && state.record.providerName !== "copilot" && !voormanIdleNudged
50808
51046
  ) {
50809
51047
  voormanIdleVerdict = await this.didVoormanIdleStall(state.record, drained, sessionId2);
50810
51048
  if (voormanIdleVerdict !== null) {
@@ -50822,15 +51060,19 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50822
51060
  );
50823
51061
  break;
50824
51062
  }
51063
+ if (stallReason === "voorman-idle") voormanIdleNudged = true;
50825
51064
  continuations++;
50826
51065
  log52.info(
50827
- `session ${sessionId2}: response looks stalled (${stallReason}) \u2014 nudging the model to actually execute`
51066
+ `session ${sessionId2}: response looks stalled (${stallReason}) \u2014 requesting one recovery pass`
51067
+ );
51068
+ promptForTurn = correctivePromptForLiveRoster(
51069
+ stallReason === "voorman-idle" ? buildContinuationNudge(
51070
+ finalContent,
51071
+ state.record.messages,
51072
+ voormanIdleVerdict === "nothing-built" ? VOORMAN_NOT_DONE_NUDGE : VOORMAN_IDLE_NUDGE
51073
+ ) : stallReason === "tool-only" ? CLOSING_SUMMARY_NUDGE : stallReason === "tool-read-only" ? READ_ONLY_PROGRESS_NUDGE : stallReason === "tool-failed" ? buildFailedToolRecoveryNudge(unresolvedToolFailures) : buildContinuationNudge(finalContent, state.record.messages),
51074
+ `stall-${stallReason}`
50828
51075
  );
50829
- promptForTurn = stallReason === "voorman-idle" ? buildContinuationNudge(
50830
- finalContent,
50831
- state.record.messages,
50832
- voormanIdleVerdict === "nothing-built" ? VOORMAN_NOT_DONE_NUDGE : VOORMAN_IDLE_NUDGE
50833
- ) : stallReason === "tool-only" ? CLOSING_SUMMARY_NUDGE : stallReason === "tool-read-only" ? READ_ONLY_PROGRESS_NUDGE : stallReason === "tool-failed" ? buildFailedToolRecoveryNudge(unresolvedToolFailures) : buildContinuationNudge(finalContent, state.record.messages);
50834
51076
  continue;
50835
51077
  }
50836
51078
  }
@@ -51396,7 +51638,7 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
51396
51638
  return null;
51397
51639
  }
51398
51640
  const tasks = await this.store.listProjectTasks(record.projectId).catch(() => []);
51399
- if (tasks.length > 0 && !tasks.some((t) => t.status === "active")) {
51641
+ if (tasks.length === 0 || !tasks.some((t) => t.status === "active")) {
51400
51642
  return null;
51401
51643
  }
51402
51644
  const workspaceFiles = await this.store.listProjectWorkspaceRecursive(record.projectId).catch(() => []);
@@ -54147,7 +54389,7 @@ ${baseSystem}` : baseSystem;
54147
54389
  return { modelId };
54148
54390
  }
54149
54391
  }
54150
- async resolveUserPromptPrelude(state, userText) {
54392
+ async resolveUserPromptPrelude(state, userText, messageOrigin) {
54151
54393
  const profile = state.profile;
54152
54394
  if (!profile) return null;
54153
54395
  const cfg = await this.store.readConfig().catch(() => null);
@@ -54156,6 +54398,9 @@ ${baseSystem}` : baseSystem;
54156
54398
  ...modelCtxFromProfile(profile, state),
54157
54399
  sessionId: state.record.id,
54158
54400
  isMeester,
54401
+ projectId: state.record.projectId,
54402
+ messageOrigin,
54403
+ availableToolNames: liveTurnToolNames(state.session),
54159
54404
  userText,
54160
54405
  drained: [],
54161
54406
  assistantContent: "",
@@ -55476,6 +55721,7 @@ Allow **${info.toolName}** this time?`,
55476
55721
  GEZEL_SESSION_ID: record.id,
55477
55722
  GEZEL_HOME: this.home,
55478
55723
  GEZEL_EXECUTION_DENSITY: executionDensity,
55724
+ GEZEL_ROLE_BASED_NAME_ONLY_MODE: record.roleBasedNameOnlyMode ?? globalConfig.roleBasedNameOnlyMode ?? false ? "1" : "0",
55479
55725
  ...record.expectedDeliverable ? { GEZEL_EXPECTED_DELIVERABLE: JSON.stringify(record.expectedDeliverable) } : {},
55480
55726
  ...record.stepId ? { GEZEL_STEP_ID: record.stepId } : {},
55481
55727
  // Scope task: lets the MCP task tools default to / recover toward
@@ -55915,6 +56161,9 @@ function runPostTurnDetectors(state, args) {
55915
56161
  ...modelCtxFromProfile(profile, state),
55916
56162
  sessionId: args.sessionId,
55917
56163
  isMeester: args.isMeester,
56164
+ projectId: state.record.projectId,
56165
+ messageOrigin: args.messageOrigin,
56166
+ availableToolNames: liveTurnToolNames(state.session),
55918
56167
  userText: args.userText,
55919
56168
  drained: args.drained,
55920
56169
  assistantContent: args.assistantContent,
@@ -56163,6 +56412,14 @@ function messageExpressesModifyIntent(text) {
56163
56412
  )
56164
56413
  );
56165
56414
  }
56415
+ function fileHandoffIntentKey(text) {
56416
+ 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(
56417
+ text
56418
+ );
56419
+ if (!repair) return "create";
56420
+ const normalized2 = text.toLowerCase().replace(/\s+/g, " ").trim();
56421
+ return `repair:${createHash12("sha256").update(normalized2).digest("hex").slice(0, 16)}`;
56422
+ }
56166
56423
  function isSubstantiveExistingWorkspaceFile(filePath2, content) {
56167
56424
  const trimmed = content.trim();
56168
56425
  if (!trimmed) return false;
@@ -56447,13 +56704,18 @@ Drop conversational filler, pleasantries, and verbatim tool outputs (just note "
56447
56704
  Earlier conversation:
56448
56705
 
56449
56706
  `;
56450
- 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.';
56707
+ 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.";
56451
56708
  var BOOTSTRAP_WORKSPACE_FILES = /* @__PURE__ */ new Set([
56452
56709
  "package.json",
56710
+ "package-lock.json",
56711
+ "pnpm-lock.yaml",
56712
+ "yarn.lock",
56713
+ "bun.lock",
56714
+ "bun.lockb",
56453
56715
  "tsconfig.json",
56454
56716
  ".gitignore"
56455
56717
  ]);
56456
- 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.";
56718
+ 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.";
56457
56719
  var READ_ONLY_MCP_TOOLS = /* @__PURE__ */ new Set([
56458
56720
  "search_memory",
56459
56721
  "list_memories",
@@ -56654,7 +56916,7 @@ function growthSignature(gezel) {
56654
56916
 
56655
56917
  // src/codex-setup/manager.ts
56656
56918
  init_atomic();
56657
- import { createHash as createHash12 } from "crypto";
56919
+ import { createHash as createHash13 } from "crypto";
56658
56920
  import { realpathSync as realpathSync2 } from "fs";
56659
56921
  import { chmod as chmod4, mkdir as mkdir15, readFile as readFile16, rm as rm6 } from "fs/promises";
56660
56922
  import { homedir as homedir3 } from "os";
@@ -57405,7 +57667,7 @@ function canonicalPath(path2) {
57405
57667
  }
57406
57668
  }
57407
57669
  function sha256(value) {
57408
- return createHash12("sha256").update(value).digest("hex");
57670
+ return createHash13("sha256").update(value).digest("hex");
57409
57671
  }
57410
57672
  function catalogModelFor(model) {
57411
57673
  const contextWindow = model.contextWindow ?? 32768;
@@ -57830,7 +58092,7 @@ init_mcp_bridge();
57830
58092
 
57831
58093
  // src/connectors/writer.ts
57832
58094
  init_atomic();
57833
- import { createHash as createHash13 } from "crypto";
58095
+ import { createHash as createHash14 } from "crypto";
57834
58096
  import { mkdir as mkdir16, readFile as readFile17, readdir as readdir9, rm as rm7, stat as stat11 } from "fs/promises";
57835
58097
  import { basename as basename7, join as join30 } from "path";
57836
58098
  import { projectLocalQuarantineDir } from "@bendyline/gezel/paths";
@@ -58103,7 +58365,7 @@ var StaticContentScanner = class {
58103
58365
  var contentScanner = new StaticContentScanner();
58104
58366
 
58105
58367
  // src/connectors/writer.ts
58106
- var sha8 = (s) => createHash13("sha256").update(s).digest("hex").slice(0, 8);
58368
+ var sha8 = (s) => createHash14("sha256").update(s).digest("hex").slice(0, 8);
58107
58369
  function slug(input, max = 48) {
58108
58370
  const s = input.normalize("NFKD").replace(/[^\x20-\x7E]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, max).replace(/-+$/g, "");
58109
58371
  return s || "x";
@@ -58120,7 +58382,7 @@ function contentHash(record) {
58120
58382
  const attachments = (record.attachments ?? []).map((attachment) => ({
58121
58383
  filename: attachment.filename,
58122
58384
  size: attachment.size ?? attachment.content?.byteLength ?? 0,
58123
- sha256: attachment.sha256 ?? createHash13("sha256").update(attachment.content).digest("hex")
58385
+ sha256: attachment.sha256 ?? createHash14("sha256").update(attachment.content).digest("hex")
58124
58386
  }));
58125
58387
  return sha8(
58126
58388
  `${JSON.stringify(sortedFrontmatter)}
@@ -59191,10 +59453,10 @@ var SpectralConnectorAdapter = class {
59191
59453
  };
59192
59454
 
59193
59455
  // src/connectors/oauth.ts
59194
- import { createHash as createHash14, randomBytes as randomBytes3 } from "crypto";
59456
+ import { createHash as createHash15, randomBytes as randomBytes3 } from "crypto";
59195
59457
  function createPkce() {
59196
59458
  const verifier = randomBytes3(32).toString("base64url");
59197
- const challenge = createHash14("sha256").update(verifier).digest("base64url");
59459
+ const challenge = createHash15("sha256").update(verifier).digest("base64url");
59198
59460
  return { verifier, challenge };
59199
59461
  }
59200
59462
  function randomState() {
@@ -63879,7 +64141,7 @@ function registerGitHubPullsAdapters(runtime) {
63879
64141
  }
63880
64142
 
63881
64143
  // src/connectors/natives/github-releases.ts
63882
- import { createHash as createHash15 } from "crypto";
64144
+ import { createHash as createHash16 } from "crypto";
63883
64145
  import { mkdir as mkdir21, mkdtemp as mkdtemp3, open as open2, rm as rm13 } from "fs/promises";
63884
64146
  import { tmpdir as tmpdir3 } from "os";
63885
64147
  import { basename as basename8, join as join35 } from "path";
@@ -64072,7 +64334,7 @@ var GitHubReleasesAdapter = class {
64072
64334
  }
64073
64335
  }
64074
64336
  this.releases = releases;
64075
- this.fingerprint = createHash15("sha256").update(JSON.stringify(releases)).digest("hex");
64337
+ this.fingerprint = createHash16("sha256").update(JSON.stringify(releases)).digest("hex");
64076
64338
  }
64077
64339
  apiHeaders(accept) {
64078
64340
  return {
@@ -64135,7 +64397,7 @@ var GitHubReleasesAdapter = class {
64135
64397
  };
64136
64398
  async function streamResponseToFile(response, path2) {
64137
64399
  const handle = await open2(path2, "wx");
64138
- const hash = createHash15("sha256");
64400
+ const hash = createHash16("sha256");
64139
64401
  let size = 0;
64140
64402
  try {
64141
64403
  for await (const raw of response.body) {
@@ -66515,7 +66777,7 @@ var DebugFlag = class {
66515
66777
 
66516
66778
  // src/digest/generator.ts
66517
66779
  init_atomic();
66518
- import { createHash as createHash16 } from "crypto";
66780
+ import { createHash as createHash17 } from "crypto";
66519
66781
  import { readFile as readFile25 } from "fs/promises";
66520
66782
  import { join as join40 } from "path";
66521
66783
  import {
@@ -69664,7 +69926,7 @@ var ProjectDigestGenerator = class {
69664
69926
  if (commits.length === 0 && sessions.length === 0 && events.length < MIN_EVENTS_FOR_DIGEST) {
69665
69927
  return false;
69666
69928
  }
69667
- const inputHash = createHash16("sha256").update(
69929
+ const inputHash = createHash17("sha256").update(
69668
69930
  [
69669
69931
  week,
69670
69932
  ...commits,
@@ -69821,7 +70083,7 @@ function describe4(err) {
69821
70083
  }
69822
70084
 
69823
70085
  // src/engines/electron-native-reuse.ts
69824
- import { createHash as createHash17 } from "crypto";
70086
+ import { createHash as createHash18 } from "crypto";
69825
70087
  import { createReadStream as createReadStream3 } from "fs";
69826
70088
  import { lstat as lstat2, readdir as readdir13, readlink } from "fs/promises";
69827
70089
  import { basename as basename11, join as join41, posix, relative as relative5, resolve as resolve8, sep as sep5 } from "path";
@@ -71441,7 +71703,7 @@ function enclosingMacApp(path2) {
71441
71703
  var HASH_READ_BUFFER_BYTES = 16 * 1024 * 1024;
71442
71704
  function sha256File2(path2) {
71443
71705
  return new Promise((resolveHash, reject) => {
71444
- const hash = createHash17("sha256");
71706
+ const hash = createHash18("sha256");
71445
71707
  const stream = createReadStream3(path2, { highWaterMark: HASH_READ_BUFFER_BYTES });
71446
71708
  stream.on("data", (chunk2) => hash.update(chunk2));
71447
71709
  stream.on("error", reject);
@@ -71453,7 +71715,7 @@ function sha256File2(path2) {
71453
71715
  import { createLogger as createLogger69 } from "@bendyline/gezel";
71454
71716
 
71455
71717
  // src/engines/resolver.ts
71456
- import { createHash as createHash18 } from "crypto";
71718
+ import { createHash as createHash19 } from "crypto";
71457
71719
  import { createReadStream as createReadStream4, existsSync as existsSync7 } from "fs";
71458
71720
  import { chmod as chmod5, mkdir as mkdir24, readFile as readFile26, rename as rename7, rm as rm16, writeFile as writeFile9 } from "fs/promises";
71459
71721
  import { join as join42 } from "path";
@@ -71577,7 +71839,7 @@ async function* resolveEngine(opts) {
71577
71839
  );
71578
71840
  }
71579
71841
  const sumsBytes = await ghBytes(sumsAsset.url, baseFetch, token);
71580
- const sumsDigest = createHash18("sha256").update(sumsBytes).digest("hex");
71842
+ const sumsDigest = createHash19("sha256").update(sumsBytes).digest("hex");
71581
71843
  if (!unpinned && sumsDigest !== expectedDigest) {
71582
71844
  yield* fail(
71583
71845
  `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.`
@@ -71827,7 +72089,7 @@ function parseSums(text) {
71827
72089
  }
71828
72090
  var HASH_READ_BUFFER_BYTES2 = 16 * 1024 * 1024;
71829
72091
  async function sha256File3(path2) {
71830
- const hash = createHash18("sha256");
72092
+ const hash = createHash19("sha256");
71831
72093
  await new Promise((resolve19, reject) => {
71832
72094
  const stream = createReadStream4(path2, { highWaterMark: HASH_READ_BUFFER_BYTES2 });
71833
72095
  stream.on("data", (chunk2) => hash.update(chunk2));
@@ -72710,7 +72972,7 @@ function safeBasename(name) {
72710
72972
  }
72711
72973
 
72712
72974
  // src/fs/store.ts
72713
- import { createHash as createHash20, randomUUID as randomUUID10 } from "crypto";
72975
+ import { createHash as createHash21, randomUUID as randomUUID10 } from "crypto";
72714
72976
  import {
72715
72977
  appendFile as appendFile4,
72716
72978
  chmod as chmod7,
@@ -73119,7 +73381,7 @@ function countDiffLines(diff) {
73119
73381
  }
73120
73382
 
73121
73383
  // src/workspace/journal.ts
73122
- import { createHash as createHash19 } from "crypto";
73384
+ import { createHash as createHash20 } from "crypto";
73123
73385
  import { appendFile as appendFile2, mkdir as mkdir27, readFile as readFile29 } from "fs/promises";
73124
73386
  import { dirname as dirname26, join as join43 } from "path";
73125
73387
  import { projectPrivateDir as projectPrivateDir2 } from "@bendyline/gezel/paths";
@@ -73162,7 +73424,7 @@ async function readJournalTail(home, projectId, limit = 100) {
73162
73424
  }
73163
73425
  }
73164
73426
  function sha2562(content) {
73165
- return createHash19("sha256").update(content).digest("hex");
73427
+ return createHash20("sha256").update(content).digest("hex");
73166
73428
  }
73167
73429
 
73168
73430
  // src/workspace/template.ts
@@ -74029,7 +74291,7 @@ function boekwachterIssueFingerprint(path2, issue) {
74029
74291
  issue.category.trim().toLocaleLowerCase(),
74030
74292
  issue.message.trim().toLocaleLowerCase().replace(/\s+/g, " ")
74031
74293
  ].join("\0");
74032
- return createHash20("sha256").update(normalized2).digest("hex");
74294
+ return createHash21("sha256").update(normalized2).digest("hex");
74033
74295
  }
74034
74296
  var ConfigCorruptionError = class extends Error {
74035
74297
  code = "CONFIG_CORRUPT";
@@ -75777,6 +76039,7 @@ ${p.content.trim()}`).join("\n\n---\n\n");
75777
76039
  async createProject(input, opts) {
75778
76040
  const release2 = await this.acquireProjectCreationLock();
75779
76041
  try {
76042
+ if (input.workingDir) this.assertSafeWorkingDir(input.workingDir);
75780
76043
  const id = opts?.id ?? await this.uniqueProjectId(slugify(input.name) || randomUUID10().slice(0, 8));
75781
76044
  if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
75782
76045
  throw new Error(`invalid project id "${id}"`);
@@ -75800,14 +76063,22 @@ ${p.content.trim()}`).join("\n\n---\n\n");
75800
76063
  projectName: input.name
75801
76064
  });
75802
76065
  }
76066
+ let github = input.github?.url ? { url: input.github.url } : void 0;
76067
+ if (input.workingDir) {
76068
+ github = await autoDetectGitHubLink(input.workingDir, github) ?? github;
76069
+ }
75803
76070
  const project = {
75804
76071
  id,
75805
76072
  name: input.name,
75806
76073
  description: input.description,
75807
76074
  ...input.workingDir ? { workingDir: input.workingDir } : {},
76075
+ // Opening an existing folder is an intake action, not a declaration
76076
+ // that its objectives are ready for active supervision. Keep the
76077
+ // Meester quiet until the user explicitly opts this project back in.
76078
+ ...input.workingDir ? { nudgeConfig: { enabled: false } } : {},
75808
76079
  ...input.mode && input.mode !== "crew" ? { mode: input.mode } : {},
75809
76080
  ...input.indexingEnabled !== void 0 ? { indexingEnabled: input.indexingEnabled } : {},
75810
- ...input.github?.url ? { github: { url: input.github.url } } : {},
76081
+ ...github ? { github } : {},
75811
76082
  createdAt: nowIso5(),
75812
76083
  updatedAt: nowIso5()
75813
76084
  };
@@ -79482,7 +79753,7 @@ import { projectCodeReviewsFile } from "@bendyline/gezel/paths";
79482
79753
  import { createLogger as createLogger77, projectAllowsAmbientWork } from "@bendyline/gezel";
79483
79754
 
79484
79755
  // src/tasks/manager.ts
79485
- import { createHash as createHash21, randomUUID as randomUUID12 } from "crypto";
79756
+ import { createHash as createHash22, randomUUID as randomUUID12 } from "crypto";
79486
79757
  import {
79487
79758
  DEFAULT_NIGHT_SHIFT_WINDOW,
79488
79759
  MANAGED_WORKSPACE_WRITE_SETTING_LABEL as MANAGED_WORKSPACE_WRITE_SETTING_LABEL4,
@@ -80259,6 +80530,7 @@ var TaskManager = class {
80259
80530
  ...cron ? { cron } : {},
80260
80531
  ...nightShift ? { nightShift } : {},
80261
80532
  ...fanout ? { fanout } : {},
80533
+ ...input.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: input.roleBasedNameOnlyMode } : {},
80262
80534
  createdAt: now,
80263
80535
  updatedAt: now,
80264
80536
  createdBy: input.createdBy ?? { kind: "user" }
@@ -81333,7 +81605,7 @@ ${paramLines}` : ""}`,
81333
81605
  if (step.advanceWhen?.file) {
81334
81606
  const content = await (step.advanceWhen.artifact ? this.store.readProjectArtifact(projectId, step.advanceWhen.file) : this.store.readProjectWorkspaceFile(projectId, step.advanceWhen.file)).catch(() => null);
81335
81607
  if (content !== null) {
81336
- contentHash2 = createHash21("sha256").update(content).digest("hex");
81608
+ contentHash2 = createHash22("sha256").update(content).digest("hex");
81337
81609
  }
81338
81610
  }
81339
81611
  if (contentHash2 !== void 0 && step.lastGateReject?.contentHash !== void 0 && step.lastGateReject.contentHash === contentHash2) {
@@ -82253,6 +82525,7 @@ Fix one of these, then set the task active again:
82253
82525
  ...parent.nightShift?.enabled ? { nightShift: { enabled: true } } : {},
82254
82526
  activeStepId,
82255
82527
  parentTaskRef: parent.ref,
82528
+ ...parent.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: parent.roleBasedNameOnlyMode } : {},
82256
82529
  createdAt: now,
82257
82530
  updatedAt: now,
82258
82531
  createdBy: parent.createdBy
@@ -82999,7 +83272,7 @@ function sumStat(s, key) {
82999
83272
  }
83000
83273
 
83001
83274
  // src/grants/manager.ts
83002
- import { createHash as createHash22, randomBytes as randomBytes5, randomUUID as randomUUID13, timingSafeEqual } from "crypto";
83275
+ import { createHash as createHash23, randomBytes as randomBytes5, randomUUID as randomUUID13, timingSafeEqual } from "crypto";
83003
83276
  import { chmod as chmod8 } from "fs/promises";
83004
83277
  import { pendingGrantsFile as defaultPendingGrantsFile } from "@bendyline/gezel/paths";
83005
83278
  var PENDING_GRANT_TTL_MS = 10 * 6e4;
@@ -83071,7 +83344,7 @@ function generateVerificationCode() {
83071
83344
  return `${raw.slice(0, 3)}-${raw.slice(3)}`;
83072
83345
  }
83073
83346
  function hashVerificationCode(salt, normalizedCode) {
83074
- return createHash22("sha256").update(salt, "base64url").update("\0").update(normalizedCode, "ascii").digest();
83347
+ return createHash23("sha256").update(salt, "base64url").update("\0").update(normalizedCode, "ascii").digest();
83075
83348
  }
83076
83349
  function codeMatches(grant, suppliedCode) {
83077
83350
  const normalized2 = normalizeVerificationCode(suppliedCode);
@@ -84991,7 +85264,7 @@ function tokenize2(question) {
84991
85264
  }
84992
85265
 
84993
85266
  // src/http/cert.ts
84994
- import { createHash as createHash23 } from "crypto";
85267
+ import { createHash as createHash24 } from "crypto";
84995
85268
  import { generate as selfsignedGenerate } from "selfsigned";
84996
85269
  var CERT_VALIDITY_DAYS = 90;
84997
85270
  async function generateLoopbackCert() {
@@ -85028,7 +85301,7 @@ async function generateLoopbackCert() {
85028
85301
  ]
85029
85302
  });
85030
85303
  const der = pemToDer(pems.cert);
85031
- const digest = createHash23("sha256").update(der).digest();
85304
+ const digest = createHash24("sha256").update(der).digest();
85032
85305
  return {
85033
85306
  certPem: pems.cert,
85034
85307
  keyPem: pems.private,
@@ -85042,14 +85315,14 @@ function pemToDer(pem) {
85042
85315
  }
85043
85316
 
85044
85317
  // src/http/codex-bridge-port.ts
85045
- import { createHash as createHash24 } from "crypto";
85318
+ import { createHash as createHash25 } from "crypto";
85046
85319
  import { realpathSync as realpathSync3 } from "fs";
85047
85320
  import { resolve as resolve10 } from "path";
85048
85321
  var CODEX_BRIDGE_PORT_RANGE_START = 2e4;
85049
85322
  var CODEX_BRIDGE_PORT_RANGE_END = 49151;
85050
85323
  var CODEX_BRIDGE_PORT_RANGE_SIZE = CODEX_BRIDGE_PORT_RANGE_END - CODEX_BRIDGE_PORT_RANGE_START + 1;
85051
85324
  function codexBridgePortForHome(home) {
85052
- const digest = createHash24("sha256").update(canonicalPath2(home)).digest();
85325
+ const digest = createHash25("sha256").update(canonicalPath2(home)).digest();
85053
85326
  const bucket = digest.readUInt32BE(0) % CODEX_BRIDGE_PORT_RANGE_SIZE;
85054
85327
  return CODEX_BRIDGE_PORT_RANGE_START + bucket;
85055
85328
  }
@@ -85275,7 +85548,7 @@ async function loadGezelByRef(ref, ctx) {
85275
85548
  import { randomUUID as randomUUID15 } from "crypto";
85276
85549
 
85277
85550
  // src/http/openai-compat/responses-translate.ts
85278
- import { createHash as createHash25 } from "crypto";
85551
+ import { createHash as createHash26 } from "crypto";
85279
85552
  import { z as z11 } from "zod";
85280
85553
 
85281
85554
  // src/http/openai-compat/translate.ts
@@ -85714,7 +85987,7 @@ var CUSTOM_TOOL_INPUT_KEY = "input";
85714
85987
  function flattenResponsesNamespaceToolName(namespace, name) {
85715
85988
  const joined = `${namespace}__${name}`;
85716
85989
  if (joined.length <= MAX_LOCAL_TOOL_NAME_LENGTH) return joined;
85717
- const digest = createHash25("sha256").update(namespace).update("\0").update(name).digest("hex").slice(0, 12);
85990
+ const digest = createHash26("sha256").update(namespace).update("\0").update(name).digest("hex").slice(0, 12);
85718
85991
  const prefixLength = MAX_LOCAL_TOOL_NAME_LENGTH - digest.length - 2;
85719
85992
  return `${joined.slice(0, prefixLength)}__${digest}`;
85720
85993
  }
@@ -86944,7 +87217,7 @@ function mountMachineEngineHints(app) {
86944
87217
  }
86945
87218
 
86946
87219
  // src/http/preview-capability.ts
86947
- import { createHash as createHash26, randomBytes as randomBytes6 } from "crypto";
87220
+ import { createHash as createHash27, randomBytes as randomBytes6 } from "crypto";
86948
87221
  import { posix as posix3 } from "path";
86949
87222
  var DEFAULT_PREVIEW_CAPABILITY_TTL_MS = 10 * 60 * 1e3;
86950
87223
  var DEFAULT_PREVIEW_CAPABILITY_ABSOLUTE_TTL_MS = 2 * 60 * 60 * 1e3;
@@ -87088,7 +87361,7 @@ function pathIsInScope(path2, scopePath) {
87088
87361
  return scopePath === "" || path2 === scopePath || path2.startsWith(`${scopePath}/`);
87089
87362
  }
87090
87363
  function tokenDigest(token) {
87091
- return createHash26("sha256").update(token).digest("base64url");
87364
+ return createHash27("sha256").update(token).digest("base64url");
87092
87365
  }
87093
87366
 
87094
87367
  // src/http/routes/ai.ts
@@ -87457,6 +87730,15 @@ var KOKORO_HF_REPO = "onnx-community/Kokoro-82M-v1.0-ONNX";
87457
87730
  var KOKORO_APPROX_BYTES_BY_ID = {
87458
87731
  [KOKORO_DEFAULT_MODEL_ID]: 95e6
87459
87732
  };
87733
+ function isKokoroRuntimeAvailable(resolveModule = (specifier) => import.meta.resolve(specifier)) {
87734
+ try {
87735
+ resolveModule("kokoro-js");
87736
+ resolveModule("@huggingface/transformers");
87737
+ return true;
87738
+ } catch {
87739
+ return false;
87740
+ }
87741
+ }
87460
87742
  var KOKORO_DEFAULT_VOICES = [
87461
87743
  {
87462
87744
  id: "af_heart",
@@ -87786,7 +88068,7 @@ function parseGender(s) {
87786
88068
  }
87787
88069
 
87788
88070
  // src/providers/audio/whisper-cpp.ts
87789
- import { createHash as createHash27 } from "crypto";
88071
+ import { createHash as createHash28 } from "crypto";
87790
88072
  import { createReadStream as createReadStream5 } from "fs";
87791
88073
  import { mkdir as mkdir37, readFile as readFile39, rename as rename11, rm as rm25, writeFile as writeFile14 } from "fs/promises";
87792
88074
  import { basename as basename12, join as join52 } from "path";
@@ -88081,7 +88363,7 @@ async function* downloadWithSha256(fetchImpl, opts) {
88081
88363
  yield { type: "progress", bytesWritten: writtenAll, totalBytes: totalAllBytes };
88082
88364
  }
88083
88365
  }
88084
- const hasher = createHash27("sha256");
88366
+ const hasher = createHash28("sha256");
88085
88367
  await new Promise((resolve19, reject) => {
88086
88368
  const stream = createReadStream5(tmpPath, { highWaterMark: MODEL_HASH_READ_BUFFER_BYTES });
88087
88369
  stream.on("data", (chunk2) => hasher.update(chunk2));
@@ -88132,6 +88414,41 @@ function machineEngineProxy(ctx, sourcePrefix, targetPrefix, localPaths = [], sh
88132
88414
 
88133
88415
  // src/http/routes/audio.ts
88134
88416
  var log85 = createLogger88("audio");
88417
+ function buildAudioCatalog(options = {}) {
88418
+ const kokoroRuntimeAvailable = options.kokoroRuntimeAvailable ?? isKokoroRuntimeAvailable();
88419
+ return {
88420
+ // All Whisper weights ship under MIT (whisper.cpp / OpenAI Whisper);
88421
+ // Kokoro under Apache 2.0. Both are permissive → "Free, open".
88422
+ stt: WHISPER_MODEL_CATALOG.map((m) => ({
88423
+ id: m.id,
88424
+ name: m.name,
88425
+ description: m.description,
88426
+ approxSizeBytes: m.approxSizeBytes,
88427
+ kind: "stt",
88428
+ license: "MIT",
88429
+ licenseClass: "open",
88430
+ licenseShortName: "MIT",
88431
+ licenseUrl: "https://huggingface.co/ggerganov/whisper.cpp",
88432
+ // The Base (English) variant is the recommended STT default: ~140 MB,
88433
+ // realtime on laptop CPUs. Curated recoScore → ★ badge + auto-pick.
88434
+ ...m.id === "whisper-base.en" ? { recoScore: 20 } : {}
88435
+ })),
88436
+ tts: kokoroRuntimeAvailable ? [
88437
+ {
88438
+ id: KOKORO_DEFAULT_MODEL_ID,
88439
+ name: "Kokoro 82M v1.0",
88440
+ description: "Apache 2.0 TTS \u2014 54+ voices, ~80MB quantized, near-realtime on M1 / modern desktop CPUs.",
88441
+ approxSizeBytes: 95e6,
88442
+ kind: "tts",
88443
+ license: "Apache 2.0",
88444
+ licenseClass: "open",
88445
+ licenseShortName: "Apache 2.0",
88446
+ licenseUrl: "https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX",
88447
+ recoScore: 20
88448
+ }
88449
+ ] : []
88450
+ };
88451
+ }
88135
88452
  function audioRoutes(ctx) {
88136
88453
  const app = new Hono4();
88137
88454
  app.use(
@@ -88253,38 +88570,7 @@ function audioRoutes(ctx) {
88253
88570
  });
88254
88571
  });
88255
88572
  app.get("/catalog", (c) => {
88256
- return c.json({
88257
- // All Whisper weights ship under MIT (whisper.cpp / OpenAI Whisper);
88258
- // Kokoro under Apache 2.0. Both are permissive → "Free, open".
88259
- stt: WHISPER_MODEL_CATALOG.map((m) => ({
88260
- id: m.id,
88261
- name: m.name,
88262
- description: m.description,
88263
- approxSizeBytes: m.approxSizeBytes,
88264
- kind: "stt",
88265
- license: "MIT",
88266
- licenseClass: "open",
88267
- licenseShortName: "MIT",
88268
- licenseUrl: "https://huggingface.co/ggerganov/whisper.cpp",
88269
- // The Base (English) variant is the recommended STT default: ~140 MB,
88270
- // realtime on laptop CPUs. Curated recoScore → ★ badge + auto-pick.
88271
- ...m.id === "whisper-base.en" ? { recoScore: 20 } : {}
88272
- })),
88273
- tts: [
88274
- {
88275
- id: KOKORO_DEFAULT_MODEL_ID,
88276
- name: "Kokoro 82M v1.0",
88277
- description: "Apache 2.0 TTS \u2014 54+ voices, ~80MB quantized, near-realtime on M1 / modern desktop CPUs.",
88278
- approxSizeBytes: 95e6,
88279
- kind: "tts",
88280
- license: "Apache 2.0",
88281
- licenseClass: "open",
88282
- licenseShortName: "Apache 2.0",
88283
- licenseUrl: "https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX",
88284
- recoScore: 20
88285
- }
88286
- ]
88287
- });
88573
+ return c.json(buildAudioCatalog());
88288
88574
  });
88289
88575
  app.get("/stt/models", async (c) => {
88290
88576
  const provider = await ctx.stt.current();
@@ -90256,6 +90542,8 @@ function configRoutes(ctx) {
90256
90542
  autoUpdateChecks: config.autoUpdateChecks !== false,
90257
90543
  quitOnClose: config.quitOnClose === true,
90258
90544
  themePref: config.themePref,
90545
+ cliShowThinking: config.cliShowThinking !== false,
90546
+ cliShowWrites: config.cliShowWrites === true,
90259
90547
  documentExportOptions: config.documentExportOptions,
90260
90548
  sidebarSide: config.sidebarSide,
90261
90549
  homeGreetingCollapsed: config.homeGreetingCollapsed === true,
@@ -90602,6 +90890,8 @@ function configRoutes(ctx) {
90602
90890
  autoUpdateChecks: updated.autoUpdateChecks !== false,
90603
90891
  quitOnClose: updated.quitOnClose === true,
90604
90892
  themePref: updated.themePref,
90893
+ cliShowThinking: updated.cliShowThinking !== false,
90894
+ cliShowWrites: updated.cliShowWrites === true,
90605
90895
  documentExportOptions: updated.documentExportOptions,
90606
90896
  sidebarSide: updated.sidebarSide,
90607
90897
  homeGreetingCollapsed: updated.homeGreetingCollapsed === true,
@@ -91991,7 +92281,11 @@ function ds4Routes(ctx) {
91991
92281
  const catalogId = c.req.param("catalogId");
91992
92282
  const skipShaRaw = c.req.query("skipSha");
91993
92283
  const skipSha = skipShaRaw != null && skipShaRaw !== "" && skipShaRaw !== "0" && skipShaRaw !== "false";
91994
- ctx.chatInstalls.ds4.start(catalogId, { skipSha, includeMmproj: false });
92284
+ ctx.chatInstalls.ds4.start(catalogId, {
92285
+ skipSha,
92286
+ includeMmproj: false,
92287
+ installCompanion: false
92288
+ });
91995
92289
  return streamSSE5(c, (stream) => subscribeToInstallSse(ctx.chatInstalls.ds4, catalogId, stream));
91996
92290
  });
91997
92291
  app.delete("/models/:catalogId/install", (c) => {
@@ -93679,6 +93973,7 @@ function gezelRoutes(ctx) {
93679
93973
  toGezelIdOrName: toId,
93680
93974
  projectId: body.projectId,
93681
93975
  text: body.text,
93976
+ ...body.suppressReply ? { suppressReply: true } : {},
93682
93977
  ...body.expectedDeliverable ? { expectedDeliverable: body.expectedDeliverable } : {}
93683
93978
  });
93684
93979
  return c.json({
@@ -94620,7 +94915,7 @@ import { HandboekRenderModeSchema } from "@bendyline/gezel";
94620
94915
  import { Hono as Hono29 } from "hono";
94621
94916
 
94622
94917
  // src/handboek/narration.ts
94623
- import { createHash as createHash28 } from "crypto";
94918
+ import { createHash as createHash29 } from "crypto";
94624
94919
  import { existsSync as existsSync14 } from "fs";
94625
94920
  import { mkdir as mkdir42, readFile as readFile43, writeFile as writeFile16 } from "fs/promises";
94626
94921
  import { join as join65 } from "path";
@@ -94636,7 +94931,7 @@ function createHandboekNarrator(deps) {
94636
94931
  const pathFor = (hash) => join65(dir, `${hash}.wav`);
94637
94932
  const metaPathFor = (hash) => join65(dir, `${hash}.json`);
94638
94933
  async function ensureSegment(key, synthesize) {
94639
- const hash = createHash28("sha256").update(key).digest("hex");
94934
+ const hash = createHash29("sha256").update(key).digest("hex");
94640
94935
  const metaPath = metaPathFor(hash);
94641
94936
  if (existsSync14(pathFor(hash)) && existsSync14(metaPath)) {
94642
94937
  try {
@@ -95636,9 +95931,15 @@ function llamaCppRoutes(ctx) {
95636
95931
  const catalogId = c.req.param("catalogId");
95637
95932
  const skipShaRaw = c.req.query("skipSha");
95638
95933
  const skipSha = skipShaRaw != null && skipShaRaw !== "" && skipShaRaw !== "0" && skipShaRaw !== "false";
95934
+ const skipCompanionRaw = c.req.query("skipCompanion");
95935
+ const skipCompanion = skipCompanionRaw != null && skipCompanionRaw !== "" && skipCompanionRaw !== "0" && skipCompanionRaw !== "false";
95639
95936
  const config = await ctx.store.readConfig().catch(() => null);
95640
95937
  const includeMmproj = config?.nativeVision?.[catalogId] === true;
95641
- ctx.chatInstalls.llamaCpp.start(catalogId, { skipSha, includeMmproj });
95938
+ ctx.chatInstalls.llamaCpp.start(catalogId, {
95939
+ skipSha,
95940
+ includeMmproj,
95941
+ installCompanion: !skipCompanion
95942
+ });
95642
95943
  return streamSSE9(
95643
95944
  c,
95644
95945
  (stream) => subscribeToInstallSse(ctx.chatInstalls.llamaCpp, catalogId, stream)
@@ -95729,7 +96030,7 @@ function mcpToolRoutes(ctx) {
95729
96030
  import { Hono as Hono36 } from "hono";
95730
96031
 
95731
96032
  // src/meester/status-generator.ts
95732
- import { createHash as createHash29 } from "crypto";
96033
+ import { createHash as createHash30 } from "crypto";
95733
96034
  import {
95734
96035
  MEESTER_STATUS_HEADLINE_MAX,
95735
96036
  MeesterStatusModelOutputSchema,
@@ -96041,7 +96342,7 @@ function localDay4(date) {
96041
96342
  return `${y}-${m}-${d}`;
96042
96343
  }
96043
96344
  function actionKey(projectId, title) {
96044
- return createHash29("sha256").update(`${projectId}
96345
+ return createHash30("sha256").update(`${projectId}
96045
96346
  ${normalizeTitle(title)}`).digest("hex");
96046
96347
  }
96047
96348
  function normalizeTitle(title) {
@@ -96053,7 +96354,7 @@ function hashInput(candidates) {
96053
96354
  ...c.openTasks.map((t) => `${t.ref}:${t.status}`),
96054
96355
  ...c.events.map((e) => e.id)
96055
96356
  ]);
96056
- return createHash29("sha256").update(lines.join("\n")).digest("hex");
96357
+ return createHash30("sha256").update(lines.join("\n")).digest("hex");
96057
96358
  }
96058
96359
  function parseModelOutput(raw) {
96059
96360
  const fence = raw.match(/```(?:json)?\s*\n([\s\S]*?)\n?```/);
@@ -96153,19 +96454,19 @@ function memoryRoutes(ctx) {
96153
96454
  const app = new Hono37();
96154
96455
  app.post("/search", async (c) => {
96155
96456
  const body = await c.req.json();
96156
- const results = await ctx.memory.searchAll(
96457
+ const outcome = await ctx.memory.searchAllDetailed(
96157
96458
  body.gezelId,
96158
96459
  body.projectId,
96159
96460
  body.query,
96160
96461
  body.topK ?? 10
96161
96462
  );
96162
- return c.json({ results });
96463
+ return c.json(outcome);
96163
96464
  });
96164
96465
  app.post("/save", async (c) => {
96165
96466
  const body = await c.req.json();
96166
96467
  const kind = isMemoryKind(body.kind) ? body.kind : "fact";
96167
96468
  const outcome = await ctx.memory.save(body.scope, body.id, body.text, kind);
96168
- return c.json({ ok: true, status: outcome?.status ?? "saved" });
96469
+ return c.json({ ok: true, ...outcome });
96169
96470
  });
96170
96471
  app.get("/recent", async (c) => {
96171
96472
  const scope = c.req.query("scope") ?? "gezel";
@@ -96346,7 +96647,7 @@ import { Hono as Hono39 } from "hono";
96346
96647
  import { z as z20 } from "zod";
96347
96648
 
96348
96649
  // src/models/gezmodel.ts
96349
- import { createHash as createHash30, randomUUID as randomUUID19 } from "crypto";
96650
+ import { createHash as createHash31, randomUUID as randomUUID19 } from "crypto";
96350
96651
  import { createReadStream as createReadStream6, createWriteStream as createWriteStream5 } from "fs";
96351
96652
  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";
96352
96653
  import { basename as basename14, dirname as dirname35, extname as extname5, join as join66 } from "path";
@@ -96448,7 +96749,7 @@ var GezmodelManager = class {
96448
96749
  files.push({
96449
96750
  path: path2,
96450
96751
  sizeBytes: content.byteLength,
96451
- sha256: createHash30("sha256").update(content).digest("hex"),
96752
+ sha256: createHash31("sha256").update(content).digest("hex"),
96452
96753
  role: path2 === "manifests/installed.json" ? "installed-manifest" : path2 === "manifests/catalog.json" ? "catalog-manifest" : "catalog-file"
96453
96754
  });
96454
96755
  }
@@ -96654,7 +96955,7 @@ var GezmodelManager = class {
96654
96955
  }
96655
96956
  };
96656
96957
  async function hashFile(path2) {
96657
- const hash = createHash30("sha256");
96958
+ const hash = createHash31("sha256");
96658
96959
  const stream = createReadStream6(path2, { highWaterMark: MODEL_HASH_READ_BUFFER_BYTES });
96659
96960
  for await (const chunk2 of stream) hash.update(chunk2);
96660
96961
  return hash.digest("hex");
@@ -96852,7 +97153,7 @@ function extractOne(archivePath, wanted, dest, expected) {
96852
97153
  zip.openReadStream(entry, (streamErr, stream) => {
96853
97154
  if (streamErr || !stream)
96854
97155
  return fail2(streamErr ?? new Error("could not read ZIP entry"));
96855
- const hash = createHash30("sha256");
97156
+ const hash = createHash31("sha256");
96856
97157
  let bytes = 0;
96857
97158
  const verifier = new Transform({
96858
97159
  transform(chunk2, _encoding, callback) {
@@ -97086,7 +97387,7 @@ import {
97086
97387
  } from "@bendyline/gezel";
97087
97388
 
97088
97389
  // src/providers/mlx/models.ts
97089
- import { createHash as createHash31 } from "crypto";
97390
+ import { createHash as createHash32 } from "crypto";
97090
97391
  import { createReadStream as createReadStream7 } from "fs";
97091
97392
  import { mkdir as mkdir44, readFile as readFile47, rename as rename12, rm as rm30, stat as stat27, writeFile as writeFile18 } from "fs/promises";
97092
97393
  import { join as join68, resolve as resolvePath2 } from "path";
@@ -97648,7 +97949,7 @@ var MlxModelManager = class {
97648
97949
  }
97649
97950
  }
97650
97951
  push({ type: "verifying", file: file.name });
97651
- const hasher = createHash31("sha256");
97952
+ const hasher = createHash32("sha256");
97652
97953
  await new Promise((resolve19, reject) => {
97653
97954
  const stream = createReadStream7(tmpPath, { highWaterMark: MODEL_HASH_READ_BUFFER_BYTES });
97654
97955
  stream.on("data", (chunk2) => hasher.update(chunk2));
@@ -99488,7 +99789,7 @@ function describeHostCadence(args) {
99488
99789
  }
99489
99790
 
99490
99791
  // src/project-type/craftbooks.ts
99491
- import { createHash as createHash32 } from "crypto";
99792
+ import { createHash as createHash33 } from "crypto";
99492
99793
  import {
99493
99794
  craftbookFromDoc as craftbookFromDoc3,
99494
99795
  createLogger as createLogger109,
@@ -99512,7 +99813,7 @@ function craftbookContentHash(book) {
99512
99813
  command: book.command ?? null,
99513
99814
  requirements: book.requirements ?? null
99514
99815
  };
99515
- return `sha256:${createHash32("sha256").update(JSON.stringify(canonical)).digest("hex")}`;
99816
+ return `sha256:${createHash33("sha256").update(JSON.stringify(canonical)).digest("hex")}`;
99516
99817
  }
99517
99818
  async function resolveTypeCraftbook(catalog, input, id, now) {
99518
99819
  const embedded = await catalog.readItemFile(
@@ -100220,7 +100521,7 @@ function pageInvokeRoutes(ctx) {
100220
100521
  }
100221
100522
 
100222
100523
  // src/http/routes/page-read.ts
100223
- import { createHash as createHash33 } from "crypto";
100524
+ import { createHash as createHash34 } from "crypto";
100224
100525
  import { readFile as readFile50, readdir as readdir29, stat as stat28 } from "fs/promises";
100225
100526
  import { PageReadRequestSchema, createLogger as createLogger113 } from "@bendyline/gezel";
100226
100527
  import { Hono as Hono48 } from "hono";
@@ -100229,7 +100530,7 @@ var PAGE_READ_MAX_BYTES = 2 * 1024 * 1024;
100229
100530
  var RATE_WINDOW_MS3 = 6e4;
100230
100531
  var RATE_MAX_READS = 600;
100231
100532
  function etagOf(size, mtimeMs) {
100232
- return createHash33("sha256").update(`${size}:${Math.trunc(mtimeMs)}`).digest("base64url").slice(0, 16);
100533
+ return createHash34("sha256").update(`${size}:${Math.trunc(mtimeMs)}`).digest("base64url").slice(0, 16);
100233
100534
  }
100234
100535
  function pageReadRoutes(ctx) {
100235
100536
  const app = new Hono48();
@@ -100279,7 +100580,7 @@ function pageReadRoutes(ctx) {
100279
100580
  return `${entry.name}:${entry.isDirectory() ? "dir" : "file"}:${Math.trunc(child?.mtimeMs ?? 0)}`;
100280
100581
  })
100281
100582
  );
100282
- const digest = createHash33("sha256").update(listed.sort().join("\n")).digest("base64url").slice(0, 16);
100583
+ const digest = createHash34("sha256").update(listed.sort().join("\n")).digest("base64url").slice(0, 16);
100283
100584
  return c.json({ op: "stat", etag: digest, mtime: stats.mtimeMs });
100284
100585
  }
100285
100586
  return c.json({
@@ -100304,7 +100605,7 @@ function pageReadRoutes(ctx) {
100304
100605
  };
100305
100606
  })
100306
100607
  )).filter((entry) => entry !== null);
100307
- const digest = createHash33("sha256").update(
100608
+ const digest = createHash34("sha256").update(
100308
100609
  listed.map((e) => `${e.name}:${e.kind}:${Math.trunc(e.mtime)}`).sort().join("\n")
100309
100610
  ).digest("base64url").slice(0, 16);
100310
100611
  return c.json({ op: "list", entries: listed, etag: digest, mtime: stats.mtimeMs });
@@ -101014,10 +101315,11 @@ import {
101014
101315
  projectGezelId as projectGezelId2,
101015
101316
  skillToCraftbookDoc
101016
101317
  } from "@bendyline/gezel";
101318
+ init_ensure();
101017
101319
  init_install();
101018
101320
 
101019
101321
  // src/workspace/instruction-scanner.ts
101020
- import { createHash as createHash34 } from "crypto";
101322
+ import { createHash as createHash35 } from "crypto";
101021
101323
  import { readFile as readFile52, stat as stat30 } from "fs/promises";
101022
101324
  import { join as join73 } from "path";
101023
101325
  var MAX_CONTENT_CHARS = 64e3;
@@ -101062,7 +101364,7 @@ ${i.content.trim()}`).join("\n\n---\n\n");
101062
101364
  return { content: chosen.content, sourceFile: chosen.source, hash: chosen.hash };
101063
101365
  }
101064
101366
  function sha2563(s) {
101065
- return createHash34("sha256").update(s).digest("hex");
101367
+ return createHash35("sha256").update(s).digest("hex");
101066
101368
  }
101067
101369
 
101068
101370
  // src/workspace/skill-script-translator.ts
@@ -101196,6 +101498,30 @@ async function translateShellBlock(chat, skillName, scriptName, code) {
101196
101498
  // src/workspace/import-sync.ts
101197
101499
  var log110 = createLogger114("import-sync");
101198
101500
  var VOORMAN_TEMPLATE_ID = "voorman";
101501
+ var BUILDER_TEMPLATE_ID = "builder";
101502
+ async function ensureFolderProjectBuilder(deps, projectId) {
101503
+ const { store, catalog } = deps;
101504
+ if (!projectId || projectId === "default") return {};
101505
+ const project = await store.getProject(projectId).catch(() => null);
101506
+ if (!project || project.mode !== "solo" || !project.workingDir) return {};
101507
+ if (project.voormanGezelId) return {};
101508
+ const gezels = await store.listGezels().catch(() => []);
101509
+ const existing = gezels.find(
101510
+ (gezel) => gezel.templateId === BUILDER_TEMPLATE_ID || gezel.role?.trim().toLowerCase() === BUILDER_TEMPLATE_ID
101511
+ );
101512
+ const recruited = existing ? null : await recruitFromTemplate(store, catalog, BUILDER_TEMPLATE_ID).catch((err) => {
101513
+ log110.warn(`[import-sync] ${projectId}: Builder recruit failed: ${describe6(err)}`);
101514
+ return null;
101515
+ });
101516
+ const builder = existing ?? recruited;
101517
+ if (!builder) return {};
101518
+ await store.updateProject(projectId, {
101519
+ voormanGezelId: builder.id,
101520
+ voormanAutoAssignedAt: nowIso12()
101521
+ });
101522
+ log110.info(`[import-sync] ${projectId}: assigned ${builder.id} as Builder`);
101523
+ return recruited ? { createdGezel: recruited } : {};
101524
+ }
101199
101525
  async function syncProjectImports(deps, projectId, instructions, skills) {
101200
101526
  const { store } = deps;
101201
101527
  const gate = await store.assertWorkspaceWritable(projectId).catch(() => null);
@@ -101324,16 +101650,20 @@ async function findExistingVoorman(store) {
101324
101650
  return match?.id ?? null;
101325
101651
  }
101326
101652
  async function recruitVoorman(store, catalog) {
101327
- const detail = await catalog.get("gezel-template", VOORMAN_TEMPLATE_ID).catch(() => null);
101328
- if (!detail || detail.manifest.kind !== "gezel-template") return null;
101653
+ return recruitFromTemplate(store, catalog, VOORMAN_TEMPLATE_ID);
101654
+ }
101655
+ async function recruitFromTemplate(store, catalog, templateId) {
101656
+ const template = await resolveGildeTemplateForRole(catalog, templateId).catch(() => null);
101657
+ if (!template || template.templateId !== templateId) return null;
101329
101658
  const { name, gender } = pickRandomNameWithGender4();
101330
101659
  const created = await store.createGezel({
101331
101660
  name,
101332
- role: detail.manifest.role,
101661
+ role: template.role,
101333
101662
  gender,
101334
- about: detail.about ?? "",
101335
- templateId: VOORMAN_TEMPLATE_ID,
101336
- templateVersion: detail.manifest.version
101663
+ about: template.about,
101664
+ templateId,
101665
+ templateVersion: template.templateVersion,
101666
+ ...template.frontmatter ? { frontmatter: template.frontmatter } : {}
101337
101667
  });
101338
101668
  return { id: created.id, name: created.name };
101339
101669
  }
@@ -101808,6 +102138,7 @@ async function resolveActor(ctx, gezelIdHeader) {
101808
102138
  }
101809
102139
  function projectTaskRoutes(ctx) {
101810
102140
  const app = new Hono55();
102141
+ const inflightCraftbookInvocations = /* @__PURE__ */ new Map();
101811
102142
  app.get("/:projectId/tasks", async (c) => {
101812
102143
  const projectId = c.req.param("projectId");
101813
102144
  const filter = ListFilterSchema.parse({
@@ -101823,10 +102154,31 @@ function projectTaskRoutes(ctx) {
101823
102154
  });
101824
102155
  app.post("/:projectId/tasks", async (c) => {
101825
102156
  const projectId = c.req.param("projectId");
101826
- const { dispatchEntry, ...body } = CreateTaskRequestSchema.parse(await c.req.json());
102157
+ const { dispatchEntry, craftbookInvocationKey, ...body } = CreateTaskRequestSchema.parse(
102158
+ await c.req.json()
102159
+ );
101827
102160
  let task;
101828
102161
  try {
101829
- task = await ctx.tasks.create(projectId, body);
102162
+ if (craftbookInvocationKey) {
102163
+ const existing = (await ctx.tasks.list({ projectId })).find(
102164
+ (candidate) => candidate.origin?.kind === "craftbook-invocation" && candidate.origin.key === craftbookInvocationKey && (candidate.status === "draft" || candidate.status === "active" || candidate.status === "paused")
102165
+ );
102166
+ if (existing) return c.json(existing);
102167
+ const inflightKey = `${projectId}:${craftbookInvocationKey}`;
102168
+ const pending5 = inflightCraftbookInvocations.get(inflightKey);
102169
+ if (pending5) return c.json(await pending5);
102170
+ const create = ctx.tasks.create(projectId, body, {
102171
+ origin: { kind: "craftbook-invocation", key: craftbookInvocationKey }
102172
+ });
102173
+ inflightCraftbookInvocations.set(inflightKey, create);
102174
+ try {
102175
+ task = await create;
102176
+ } finally {
102177
+ inflightCraftbookInvocations.delete(inflightKey);
102178
+ }
102179
+ } else {
102180
+ task = await ctx.tasks.create(projectId, body);
102181
+ }
101830
102182
  } catch (err) {
101831
102183
  if (err instanceof CraftbookSetupRequiredError) {
101832
102184
  return c.json(
@@ -102897,7 +103249,7 @@ async function recoverTypedProjectCreations(store) {
102897
103249
  }
102898
103250
 
102899
103251
  // src/project-type/gzl.ts
102900
- import { createHash as createHash35 } from "crypto";
103252
+ import { createHash as createHash36 } from "crypto";
102901
103253
  import { mkdir as mkdir48, writeFile as writeFile21 } from "fs/promises";
102902
103254
  import { dirname as dirname40 } from "path";
102903
103255
  import {
@@ -102915,7 +103267,7 @@ function shard(id) {
102915
103267
  return id.slice(0, 2).toLowerCase();
102916
103268
  }
102917
103269
  function hashItemFiles(files) {
102918
- const hash = createHash35("sha256");
103270
+ const hash = createHash36("sha256");
102919
103271
  for (const { rel, content } of [...files].sort((a, b) => a.rel < b.rel ? -1 : 1)) {
102920
103272
  hash.update(rel);
102921
103273
  hash.update("\0");
@@ -103063,7 +103415,7 @@ async function importGzlBundle(deps, buffer, opts = {}) {
103063
103415
 
103064
103416
  // src/workspace/command-approvals.ts
103065
103417
  init_atomic();
103066
- import { createHash as createHash36 } from "crypto";
103418
+ import { createHash as createHash37 } from "crypto";
103067
103419
  import { existsSync as existsSync18 } from "fs";
103068
103420
  import { mkdir as mkdir49, readFile as readFile57 } from "fs/promises";
103069
103421
  import { dirname as dirname41, join as join79 } from "path";
@@ -103071,7 +103423,7 @@ import { projectPrivateDir as projectPrivateDir5 } from "@bendyline/gezel/paths"
103071
103423
  function hashCommandInvocation(body, args, inputFiles = []) {
103072
103424
  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));
103073
103425
  const payload = JSON.stringify({ version: 2, body: body ?? null, args, files });
103074
- return createHash36("sha256").update("gezel-command-invocation\0").update(payload).digest("hex");
103426
+ return createHash37("sha256").update("gezel-command-invocation\0").update(payload).digest("hex");
103075
103427
  }
103076
103428
  function approvalsPath(home, projectId) {
103077
103429
  return join79(projectPrivateDir5(home, projectId), "command-approvals.json");
@@ -103297,20 +103649,20 @@ import {
103297
103649
  } from "@bendyline/gezel";
103298
103650
  import { projectPrivateDir as projectPrivateDir6 } from "@bendyline/gezel/paths";
103299
103651
  var SHIPPED_ALLOWLIST = [
103300
- { package: "zod", allowedVersions: ["^3", "^4"] },
103301
- { package: "chalk", allowedVersions: ["^5"] },
103302
- { package: "commander", allowedVersions: ["^12", "^13"] },
103303
- { package: "date-fns", allowedVersions: ["^3", "^4"] },
103304
- { package: "yaml", allowedVersions: ["^2"] },
103305
- { package: "cheerio", allowedVersions: ["^1"] },
103306
- { package: "mime-types", allowedVersions: ["^2", "^3"] },
103307
- { package: "marked", allowedVersions: ["^12", "^13", "^14", "^15"] },
103308
- { package: "nanoid", allowedVersions: ["^5"] },
103309
- { package: "pino", allowedVersions: ["^9"] },
103310
- { package: "playwright-core", allowedVersions: ["^1"] },
103311
- { package: "tsx", allowedVersions: ["^4"] },
103312
- { package: "typescript", allowedVersions: ["^5"] },
103313
- { package: "undici", allowedVersions: ["^6", "^7"] }
103652
+ { package: "zod", allowedVersions: ["^3", "^4"], defaultVersion: "^4" },
103653
+ { package: "chalk", allowedVersions: ["^5"], defaultVersion: "^5" },
103654
+ { package: "commander", allowedVersions: ["^12", "^13"], defaultVersion: "^13" },
103655
+ { package: "date-fns", allowedVersions: ["^3", "^4"], defaultVersion: "^4" },
103656
+ { package: "yaml", allowedVersions: ["^2"], defaultVersion: "^2" },
103657
+ { package: "cheerio", allowedVersions: ["^1"], defaultVersion: "^1" },
103658
+ { package: "mime-types", allowedVersions: ["^2", "^3"], defaultVersion: "^3" },
103659
+ { package: "marked", allowedVersions: ["^12", "^13", "^14", "^15"], defaultVersion: "^15" },
103660
+ { package: "nanoid", allowedVersions: ["^5"], defaultVersion: "^5" },
103661
+ { package: "pino", allowedVersions: ["^9"], defaultVersion: "^9" },
103662
+ { package: "playwright-core", allowedVersions: ["^1"], defaultVersion: "^1" },
103663
+ { package: "tsx", allowedVersions: ["^4"], defaultVersion: "^4" },
103664
+ { package: "typescript", allowedVersions: ["^5"], defaultVersion: "^5" },
103665
+ { package: "undici", allowedVersions: ["^6", "^7"], defaultVersion: "^7" }
103314
103666
  ];
103315
103667
  function projectAllowlistPath(home, projectId) {
103316
103668
  return join81(projectPrivateDir6(home, projectId), "npm-allowlist.json");
@@ -103341,6 +103693,9 @@ function matchesAllowlist(pkg, version) {
103341
103693
  }
103342
103694
  return false;
103343
103695
  }
103696
+ function shippedDefaultVersion(pkg) {
103697
+ return SHIPPED_ALLOWLIST.find((entry) => entry.package === pkg)?.defaultVersion;
103698
+ }
103344
103699
  function matchesProjectApproval(allowlist, pkg, version) {
103345
103700
  return allowlist.approved.some((e) => e.package === pkg && e.version === version);
103346
103701
  }
@@ -103470,7 +103825,9 @@ function dedupRequests(raw) {
103470
103825
  const out = [];
103471
103826
  for (const r of raw) {
103472
103827
  const pkg = NpmPackageNameSchema.parse(r.package);
103473
- const version = NpmRegistryVersionSchema.parse(r.version ?? "latest");
103828
+ const version = NpmRegistryVersionSchema.parse(
103829
+ r.version ?? shippedDefaultVersion(pkg) ?? "latest"
103830
+ );
103474
103831
  const key = keyOf(pkg, version);
103475
103832
  if (seen.has(key)) continue;
103476
103833
  seen.add(key);
@@ -103640,7 +103997,7 @@ import { join as join82 } from "path";
103640
103997
  import { nowIso as nowIso14 } from "@bendyline/gezel";
103641
103998
 
103642
103999
  // src/workspace/command-inputs.ts
103643
- import { createHash as createHash37 } from "crypto";
104000
+ import { createHash as createHash38 } from "crypto";
103644
104001
  import { createReadStream as createReadStream8 } from "fs";
103645
104002
  import { readFile as readFile59, realpath as realpath7, stat as stat35 } from "fs/promises";
103646
104003
  import { dirname as dirname43, extname as extname6, isAbsolute as isAbsolute10, relative as relative10, resolve as resolve13, sep as sep9 } from "path";
@@ -103713,7 +104070,7 @@ function portableRelative(root, file) {
103713
104070
  return relative10(root, file).split(sep9).join("/");
103714
104071
  }
103715
104072
  async function sha256File4(file) {
103716
- const hash = createHash37("sha256");
104073
+ const hash = createHash38("sha256");
103717
104074
  await new Promise((resolveStream, reject) => {
103718
104075
  const stream = createReadStream8(file);
103719
104076
  stream.on("data", (chunk2) => hash.update(chunk2));
@@ -104272,12 +104629,13 @@ function projectRoutes(ctx) {
104272
104629
  app.post("/", async (c) => {
104273
104630
  const body = CreateProjectRequestSchema.parse(await c.req.json());
104274
104631
  const created = await ctx.store.createProject(body);
104275
- const ensured2 = await ensureProjectVoorman(
104632
+ const ensureLead = body.workingDir && body.mode === "solo" ? ensureFolderProjectBuilder : ensureProjectVoorman;
104633
+ const ensured2 = await ensureLead(
104276
104634
  { store: ctx.store, chat: ctx.chat, home: ctx.home, catalog: ctx.catalog },
104277
104635
  created.id
104278
104636
  ).catch((err) => {
104279
104637
  const message = err instanceof Error ? err.message : String(err);
104280
- log114.warn(`[projects] ensure-voorman failed for ${created.id}: ${message}`);
104638
+ log114.warn(`[projects] ensure-lead failed for ${created.id}: ${message}`);
104281
104639
  return {};
104282
104640
  });
104283
104641
  if (ensured2.createdGezel) {
@@ -104800,6 +105158,31 @@ function projectRoutes(ctx) {
104800
105158
  return c.json({ error: "not found" }, 404);
104801
105159
  }
104802
105160
  });
105161
+ app.post("/:id/reveal-reference", async (c) => {
105162
+ const id = c.req.param("id");
105163
+ const request = ReferenceFileLocationRequestSchema.parse({
105164
+ kind: c.req.query("kind"),
105165
+ path: c.req.query("path")
105166
+ });
105167
+ const base = request.kind === "artifact" ? ctx.store.projectArtifactsDir(id) : request.kind === "workspace" ? await ctx.store.projectWorkspaceDir(id) : ctx.store.documentsDir();
105168
+ const joined = safeJoin(base, request.path);
105169
+ if (!joined || !await realpathContained(base, joined)) {
105170
+ return c.json({ error: "path traversal" }, 400);
105171
+ }
105172
+ let path2;
105173
+ try {
105174
+ const file = await stat36(joined);
105175
+ if (!file.isFile()) return c.json({ error: "not found" }, 404);
105176
+ path2 = await realpath8(joined);
105177
+ } catch {
105178
+ return c.json({ error: "not found" }, 404);
105179
+ }
105180
+ const { execFile: execFile8 } = await import("child_process");
105181
+ const launcher = process.platform === "darwin" ? { cmd: "open", args: ["-R", path2] } : process.platform === "win32" ? { cmd: "explorer", args: ["/select,", path2] } : { cmd: "xdg-open", args: [dirname44(path2)] };
105182
+ execFile8(launcher.cmd, launcher.args, { windowsHide: true }, () => {
105183
+ });
105184
+ return c.json(ReferenceFileLocationResponseSchema.parse({ path: path2 }));
105185
+ });
104803
105186
  app.get("/:id/artifacts", async (c) => {
104804
105187
  const id = c.req.param("id");
104805
105188
  const subpath = c.req.query("path") ?? "";
@@ -107592,7 +107975,7 @@ async function readProjectScriptRun(home, projectId, runId) {
107592
107975
  }
107593
107976
 
107594
107977
  // src/scripts/sdk.ts
107595
- import { createHash as createHash39 } from "crypto";
107978
+ import { createHash as createHash40 } from "crypto";
107596
107979
  import { readFile as readFile67 } from "fs/promises";
107597
107980
  import { dirname as dirname45, join as join87, relative as relative11 } from "path";
107598
107981
  import { fileURLToPath as fileURLToPath7 } from "url";
@@ -107658,7 +108041,7 @@ async function readSdkTypes() {
107658
108041
  });
107659
108042
  } catch {
107660
108043
  }
107661
- const hash = createHash39("sha256");
108044
+ const hash = createHash40("sha256");
107662
108045
  for (const f of files) hash.update(f.name).update("\0").update(f.content);
107663
108046
  cachedTypes = { version: hash.digest("hex"), files };
107664
108047
  return cachedTypes;
@@ -110472,9 +110855,13 @@ async function grepWorkspace(opts) {
110472
110855
  const plan = normalizePlan(opts);
110473
110856
  const target = await resolveSearchTarget(opts.workspaceDir, opts.path);
110474
110857
  const requestedEngine = opts.engine ?? "auto";
110475
- const ripgrepPath = requestedEngine === "javascript" ? null : await resolveExecutableOnPath("rg", target.workspaceDir, Math.min(plan.timeoutMs, 2e3));
110858
+ const ripgrepPath = requestedEngine === "javascript" ? null : await resolveRipgrepExecutable(target.workspaceDir, Math.min(plan.timeoutMs, 2e3));
110476
110859
  if (requestedEngine === "ripgrep" && !ripgrepPath) {
110477
- throw new WorkspaceGrepError("ripgrep is not installed on this machine", 500, "rg-unavailable");
110860
+ throw new WorkspaceGrepError(
110861
+ "ripgrep is unavailable (the bundled platform package is missing and rg is not on PATH)",
110862
+ 500,
110863
+ "rg-unavailable"
110864
+ );
110478
110865
  }
110479
110866
  let raw;
110480
110867
  let engine;
@@ -110978,6 +111365,16 @@ async function resolveExecutableOnPath(command, forbiddenRoot, timeoutMs) {
110978
111365
  if (timer) clearTimeout(timer);
110979
111366
  }
110980
111367
  }
111368
+ async function resolveRipgrepExecutable(forbiddenRoot, timeoutMs) {
111369
+ const onPath = await resolveExecutableOnPath("rg", forbiddenRoot, timeoutMs);
111370
+ if (onPath) return onPath;
111371
+ try {
111372
+ const { rgPath } = await import("@vscode/ripgrep");
111373
+ return await validateExecutable(rgPath, forbiddenRoot);
111374
+ } catch {
111375
+ return null;
111376
+ }
111377
+ }
110981
111378
  async function findExecutableOnPath(command, forbiddenRoot) {
110982
111379
  const pathValue = process.env.PATH;
110983
111380
  if (!pathValue) return null;
@@ -110986,22 +111383,28 @@ async function findExecutableOnPath(command, forbiddenRoot) {
110986
111383
  const entry = rawEntry.replace(/^"|"$/g, "");
110987
111384
  if (!entry || !isAbsolute11(entry)) continue;
110988
111385
  for (const suffix of suffixes) {
110989
- try {
110990
- const executable = await realpath10(join89(entry, `${command}${suffix}`));
110991
- if (pathIsInside(forbiddenRoot, executable)) continue;
110992
- const executableStat = await stat39(executable);
110993
- if (!executableStat.isFile()) continue;
110994
- await access3(
110995
- executable,
110996
- process.platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK
110997
- );
110998
- return executable;
110999
- } catch {
111000
- }
111386
+ const executable = await validateExecutable(
111387
+ join89(entry, `${command}${suffix}`),
111388
+ forbiddenRoot
111389
+ );
111390
+ if (executable) return executable;
111001
111391
  }
111002
111392
  }
111003
111393
  return null;
111004
111394
  }
111395
+ async function validateExecutable(candidate, forbiddenRoot) {
111396
+ if (!isAbsolute11(candidate)) return null;
111397
+ try {
111398
+ const executable = await realpath10(candidate);
111399
+ if (pathIsInside(forbiddenRoot, executable)) return null;
111400
+ const executableStat = await stat39(executable);
111401
+ if (!executableStat.isFile()) return null;
111402
+ await access3(executable, process.platform === "win32" ? fsConstants.F_OK : fsConstants.X_OK);
111403
+ return executable;
111404
+ } catch {
111405
+ return null;
111406
+ }
111407
+ }
111005
111408
  function pathIsInside(root, candidate) {
111006
111409
  const rel = relative12(root, candidate);
111007
111410
  return rel === "" || rel !== ".." && !rel.startsWith(`..${sep10}`) && !isAbsolute11(rel);
@@ -116254,7 +116657,10 @@ function opaqueServerErrors(log189, onUnexpectedHttpError) {
116254
116657
  const raw = await prior.clone().text().catch(() => "");
116255
116658
  try {
116256
116659
  const parsed = JSON.parse(raw);
116257
- if (parsed.error === "internal_error" && typeof parsed.requestId === "string") return;
116660
+ if (parsed.error === "internal_error" && typeof parsed.requestId === "string") {
116661
+ c.header("x-request-id", parsed.requestId);
116662
+ return;
116663
+ }
116258
116664
  if ((parsed.error === "machine_engine_unavailable" || parsed.error === "capacity_denied") && Object.keys(parsed).length === 1) {
116259
116665
  return;
116260
116666
  }
@@ -118603,7 +119009,7 @@ function indexShadowBody(store, file, body) {
118603
119009
  }
118604
119010
 
118605
119011
  // src/index-store/area-pass.ts
118606
- import { createHash as createHash40 } from "crypto";
119012
+ import { createHash as createHash41 } from "crypto";
118607
119013
  var ARCHITECTURE_KEY = "::project";
118608
119014
  var MIN_AREA_FILES = 2;
118609
119015
  var AREA_INPUT_CAP = 6e3;
@@ -118665,7 +119071,7 @@ async function runAreaPass(index, deps) {
118665
119071
  return result;
118666
119072
  }
118667
119073
  function hashLines(lines) {
118668
- return createHash40("sha256").update([...lines].sort().join("\n")).digest("hex");
119074
+ return createHash41("sha256").update([...lines].sort().join("\n")).digest("hex");
118669
119075
  }
118670
119076
  function buildAreaPrompt(area, entries) {
118671
119077
  let body = "";
@@ -118855,9 +119261,9 @@ function triv(kind) {
118855
119261
  }
118856
119262
 
118857
119263
  // src/index-store/hash.ts
118858
- import { createHash as createHash41 } from "crypto";
119264
+ import { createHash as createHash42 } from "crypto";
118859
119265
  function sha2564(data) {
118860
- return createHash41("sha256").update(data).digest("hex");
119266
+ return createHash42("sha256").update(data).digest("hex");
118861
119267
  }
118862
119268
 
118863
119269
  // src/index-store/artifacts-indexer.ts
@@ -123768,7 +124174,7 @@ ${body}\r
123768
124174
  }
123769
124175
 
123770
124176
  // src/mail/mime.ts
123771
- import { createHash as createHash42 } from "crypto";
124177
+ import { createHash as createHash43 } from "crypto";
123772
124178
  import { simpleParser } from "mailparser";
123773
124179
 
123774
124180
  // src/mail/html.ts
@@ -123883,7 +124289,7 @@ function normalizeReferences(refs) {
123883
124289
  }
123884
124290
  function synthesizeMessageId(parsed, ctx) {
123885
124291
  const basis = `${addressText(parsed.from)}|${parsed.subject ?? ""}|${parsed.date?.toISOString() ?? ctx.fallbackDate ?? ""}|${ctx.id}`;
123886
- return `<${createHash42("sha256").update(basis).digest("hex").slice(0, 32)}@gezel.local>`;
124292
+ return `<${createHash43("sha256").update(basis).digest("hex").slice(0, 32)}@gezel.local>`;
123887
124293
  }
123888
124294
  async function parseRawMessage(raw, ctx) {
123889
124295
  const parsed = await simpleParser(raw);
@@ -124506,7 +124912,7 @@ async function migrateOversightTask(store, num) {
124506
124912
 
124507
124913
  // src/memory/compaction.ts
124508
124914
  init_atomic();
124509
- import { createHash as createHash43 } from "crypto";
124915
+ import { createHash as createHash44 } from "crypto";
124510
124916
  import { readFile as readFile77 } from "fs/promises";
124511
124917
  import { join as join101 } from "path";
124512
124918
  import { createLogger as createLogger160, isProactiveAllowed as isProactiveAllowed5 } from "@bendyline/gezel";
@@ -124718,7 +125124,7 @@ var MemoryCompactor = class {
124718
125124
  }
124719
125125
  }
124720
125126
  if (entries.length < minEntries) return false;
124721
- const inputHash = createHash43("sha256").update(eligibleDays.map((d) => `${d}
125127
+ const inputHash = createHash44("sha256").update(eligibleDays.map((d) => `${d}
124722
125128
  ${dayContents.get(d)}`).join("\n")).digest("hex");
124723
125129
  const state = await this.readState(scope, id);
124724
125130
  if (state.inputHash === inputHash) return false;
@@ -124824,7 +125230,7 @@ ${dayContents.get(d)}`).join("\n")).digest("hex");
124824
125230
  const notes = await this.memory.getRecent("gezel", gezelId, lookbackDays);
124825
125231
  if (!notes.trim()) return;
124826
125232
  const current = await this.store.readMemoryLessons(gezelId);
124827
- const lessonsInputHash = createHash43("sha256").update(`${notes}
125233
+ const lessonsInputHash = createHash44("sha256").update(`${notes}
124828
125234
  ---
124829
125235
  ${current}`).digest("hex");
124830
125236
  const state = await this.readState("gezel", gezelId);
@@ -125165,8 +125571,8 @@ var MemoryManager = class {
125165
125571
  * that text dedups, so we accept the race rather than lock.
125166
125572
  *
125167
125573
  * Degraded mode (embeddings disabled): markdown is still appended (it is
125168
- * the source of truth and the index self-heals on rebuild) and the
125169
- * EmbeddingsDisabledError propagates, preserving the pre-dedup contract.
125574
+ * the source of truth) and the outcome reports deferred indexing. The health
125575
+ * monitor self-heals the derived index once embeddings recover.
125170
125576
  */
125171
125577
  async save(scope, id, text, kind = DEFAULT_MEMORY_KIND) {
125172
125578
  const trimmed = text.trim();
@@ -125181,9 +125587,19 @@ var MemoryManager = class {
125181
125587
  vector = await embed(trimmed);
125182
125588
  } catch (err) {
125183
125589
  await this.store.appendMemory(scope, id, trimmed, kind);
125184
- throw err;
125590
+ log155.warn(
125591
+ `[memory] saved ${scope}/${id} to Markdown; semantic indexing deferred: ${describeError3(err)}`
125592
+ );
125593
+ return deferredIndexOutcome();
125594
+ }
125595
+ let top;
125596
+ try {
125597
+ top = (await searchByVector(indexDir, vector, 1))[0];
125598
+ } catch (error) {
125599
+ log155.warn(
125600
+ `[memory] vector dedup unavailable for ${scope}/${id}; continuing save: ${describeError3(error)}`
125601
+ );
125185
125602
  }
125186
- const top = (await searchByVector(indexDir, vector, 1))[0];
125187
125603
  if (top && top.score >= this.dedupThreshold) {
125188
125604
  log155.info(
125189
125605
  `[memory] dup-skip (${top.score.toFixed(2)} vs "${top.text.slice(0, 40)}") ${scope}/${id}: ${trimmed.slice(0, 60)}`
@@ -125191,20 +125607,27 @@ var MemoryManager = class {
125191
125607
  return { status: "duplicate", match: { text: top.text, score: top.score, via: "vector" } };
125192
125608
  }
125193
125609
  await this.store.appendMemory(scope, id, trimmed, kind);
125194
- await addToIndex(
125195
- indexDir,
125196
- {
125197
- text: trimmed,
125198
- scope,
125199
- id,
125200
- day: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
125201
- at: (/* @__PURE__ */ new Date()).toISOString(),
125202
- kind
125203
- },
125204
- vector
125205
- );
125610
+ try {
125611
+ await addToIndex(
125612
+ indexDir,
125613
+ {
125614
+ text: trimmed,
125615
+ scope,
125616
+ id,
125617
+ day: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
125618
+ at: (/* @__PURE__ */ new Date()).toISOString(),
125619
+ kind
125620
+ },
125621
+ vector
125622
+ );
125623
+ } catch (error) {
125624
+ log155.warn(
125625
+ `[memory] saved ${scope}/${id} to Markdown; semantic index write deferred: ${describeError3(error)}`
125626
+ );
125627
+ return deferredIndexOutcome();
125628
+ }
125206
125629
  log155.info(`[memory] saved ${scope}/${id} [${kind}]: ${trimmed.slice(0, 60)}`);
125207
- return { status: "saved" };
125630
+ return { status: "saved", indexed: true };
125208
125631
  }
125209
125632
  /**
125210
125633
  * Exact-text match against today's and yesterday's daily files. Two days
@@ -125226,7 +125649,15 @@ var MemoryManager = class {
125226
125649
  }
125227
125650
  async search(scope, id, query, topK = 10) {
125228
125651
  const indexDir = this.store.memoryIndexDir(scope, id);
125229
- return searchIndex(indexDir, query, topK);
125652
+ if (!this.hasIndex(scope, id)) return this.searchLexical(scope, id, query, topK);
125653
+ try {
125654
+ return await searchIndex(indexDir, query, topK);
125655
+ } catch (error) {
125656
+ log155.warn(
125657
+ `[memory] semantic search failed for ${scope}/${id}; using lexical fallback: ${describeError3(error)}`
125658
+ );
125659
+ return this.searchLexical(scope, id, query, topK);
125660
+ }
125230
125661
  }
125231
125662
  /**
125232
125663
  * Like {@link search} but with a precomputed query embedding — lets the
@@ -125255,11 +125686,64 @@ var MemoryManager = class {
125255
125686
  return existsSync28(join103(this.store.memoryIndexDir(scope, id), "mem.db"));
125256
125687
  }
125257
125688
  async searchAll(gezelId, projectId, query, topK = 10) {
125258
- const [agentResults, projectResults] = await Promise.all([
125259
- this.search("gezel", gezelId, query, topK),
125260
- this.search("project", projectId, query, topK)
125689
+ return (await this.searchAllDetailed(gezelId, projectId, query, topK)).results;
125690
+ }
125691
+ /**
125692
+ * Search both scopes with one query embedding. Missing indexes fall back to
125693
+ * the daily Markdown source; an unavailable/corrupt semantic cache does the
125694
+ * same and reports a degraded mode instead of turning an optional feature
125695
+ * into an HTTP 500.
125696
+ */
125697
+ async searchAllDetailed(gezelId, projectId, query, topK = 10) {
125698
+ const gezelIndexed = this.hasIndex("gezel", gezelId);
125699
+ const projectIndexed = this.hasIndex("project", projectId);
125700
+ if (!gezelIndexed && !projectIndexed) {
125701
+ return {
125702
+ results: await this.searchAllLexical(gezelId, projectId, query, topK),
125703
+ mode: "lexical"
125704
+ };
125705
+ }
125706
+ try {
125707
+ const vector = await embedQuery(query);
125708
+ const [gezelResults, projectResults] = await Promise.all([
125709
+ gezelIndexed ? this.searchVector("gezel", gezelId, vector, topK) : this.searchLexical("gezel", gezelId, query, topK),
125710
+ projectIndexed ? this.searchVector("project", projectId, vector, topK) : this.searchLexical("project", projectId, query, topK)
125711
+ ]);
125712
+ return {
125713
+ results: rankSearchResults([...gezelResults, ...projectResults], topK),
125714
+ mode: gezelIndexed && projectIndexed ? "semantic" : "hybrid"
125715
+ };
125716
+ } catch (error) {
125717
+ log155.warn(
125718
+ `[memory] semantic search unavailable; using daily-memory lexical fallback: ${describeError3(error)}`
125719
+ );
125720
+ return {
125721
+ results: await this.searchAllLexical(gezelId, projectId, query, topK),
125722
+ mode: "lexical",
125723
+ degraded: {
125724
+ code: "semantic_search_unavailable",
125725
+ message: "Semantic memory search is temporarily unavailable; searched saved memory text directly instead."
125726
+ }
125727
+ };
125728
+ }
125729
+ }
125730
+ async searchAllLexical(gezelId, projectId, query, topK) {
125731
+ const [gezelResults, projectResults] = await Promise.all([
125732
+ this.searchLexical("gezel", gezelId, query, topK),
125733
+ this.searchLexical("project", projectId, query, topK)
125261
125734
  ]);
125262
- return [...agentResults, ...projectResults].sort((a, b) => b.score - a.score).slice(0, topK);
125735
+ return rankSearchResults([...gezelResults, ...projectResults], topK);
125736
+ }
125737
+ async searchLexical(scope, id, query, topK) {
125738
+ const entries = await this.allEntries(scope, id);
125739
+ 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 }) => ({
125740
+ text: entry.text,
125741
+ score,
125742
+ day: entry.day,
125743
+ scope: entry.scope,
125744
+ id: entry.id,
125745
+ kind: entry.kind ?? DEFAULT_MEMORY_KIND
125746
+ }));
125263
125747
  }
125264
125748
  async listDays(scope, id) {
125265
125749
  return this.store.listMemoryDays(scope, id);
@@ -125324,6 +125808,36 @@ var MemoryManager = class {
125324
125808
  return entries.length;
125325
125809
  }
125326
125810
  };
125811
+ function rankSearchResults(results, topK) {
125812
+ return results.sort((a, b) => b.score - a.score).slice(0, topK);
125813
+ }
125814
+ function deferredIndexOutcome() {
125815
+ return {
125816
+ status: "saved",
125817
+ indexed: false,
125818
+ degraded: {
125819
+ code: "semantic_index_unavailable",
125820
+ message: "Memory was saved, but semantic indexing is temporarily unavailable."
125821
+ }
125822
+ };
125823
+ }
125824
+ function lexicalScore(query, text) {
125825
+ const normalizedQuery = query.trim().toLocaleLowerCase();
125826
+ const normalizedText = text.toLocaleLowerCase();
125827
+ if (!normalizedQuery || !normalizedText) return 0;
125828
+ if (normalizedText.includes(normalizedQuery)) return 1;
125829
+ const queryTokens = new Set(normalizedQuery.match(/[\p{L}\p{N}_-]+/gu) ?? []);
125830
+ if (queryTokens.size === 0) return 0;
125831
+ const textTokens = new Set(normalizedText.match(/[\p{L}\p{N}_-]+/gu) ?? []);
125832
+ let matches = 0;
125833
+ for (const token of queryTokens) {
125834
+ if (textTokens.has(token)) matches++;
125835
+ }
125836
+ return matches / queryTokens.size;
125837
+ }
125838
+ function describeError3(error) {
125839
+ return error instanceof Error ? error.message : String(error);
125840
+ }
125327
125841
 
125328
125842
  // src/models/install-jobs.ts
125329
125843
  import { createLogger as createLogger166 } from "@bendyline/gezel";
@@ -125486,6 +126000,7 @@ var LlamaVisionProvider = class {
125486
126000
  await mkdir57(itemDir, { recursive: true });
125487
126001
  const totalAllBytes = spec.files.reduce((n, f) => n + f.approxSizeBytes, 0);
125488
126002
  let writtenAll = 0;
126003
+ const verifiedDigests = {};
125489
126004
  for (const file of spec.files) {
125490
126005
  const result = yield* downloadWithSha256(this.fetchImpl, {
125491
126006
  url: file.downloadUrl,
@@ -125501,8 +126016,9 @@ var LlamaVisionProvider = class {
125501
126016
  return;
125502
126017
  }
125503
126018
  writtenAll = result.writtenAll;
126019
+ verifiedDigests[file.filename] = file.sha256.toLowerCase();
125504
126020
  }
125505
- const fileSha256 = await hashModelPayloadFiles(itemDir);
126021
+ const fileSha256 = await hashModelPayloadFiles(itemDir, verifiedDigests);
125506
126022
  await writeFile26(
125507
126023
  join104(itemDir, "manifest.json"),
125508
126024
  `${JSON.stringify(
@@ -125922,7 +126438,7 @@ async function* runGgufInstallWithCompanion(catalogId, o, deps) {
125922
126438
  yield event;
125923
126439
  }
125924
126440
  if (!finished) return;
125925
- yield* companionRecognitionEvents(catalogId, deps);
126441
+ if (o.installCompanion) yield* companionRecognitionEvents(catalogId, deps);
125926
126442
  yield finished;
125927
126443
  }
125928
126444
  async function* companionRecognitionEvents(catalogId, deps) {
@@ -127230,7 +127746,7 @@ function makeActionable(message) {
127230
127746
  }
127231
127747
 
127232
127748
  // src/providers/image/sd-cpp.ts
127233
- import { createHash as createHash44 } from "crypto";
127749
+ import { createHash as createHash45 } from "crypto";
127234
127750
  import { createReadStream as createReadStream10 } from "fs";
127235
127751
  import { mkdir as mkdir60, readFile as readFile81, rename as rename15, rm as rm41, writeFile as writeFile29 } from "fs/promises";
127236
127752
  import { join as join110 } from "path";
@@ -127569,7 +128085,7 @@ var StableDiffusionCppProvider = class {
127569
128085
  }
127570
128086
  }
127571
128087
  const partialPath = `${destPath}.partial`;
127572
- const hasher = createHash44("sha256");
128088
+ const hasher = createHash45("sha256");
127573
128089
  await new Promise((resolve19, reject) => {
127574
128090
  const stream = createReadStream10(partialPath, {
127575
128091
  highWaterMark: MODEL_HASH_READ_BUFFER_BYTES
@@ -128088,7 +128604,7 @@ function isMachineImageProvider(config, env) {
128088
128604
  }
128089
128605
 
128090
128606
  // src/providers/recognition/store.ts
128091
- import { createHash as createHash45 } from "crypto";
128607
+ import { createHash as createHash46 } from "crypto";
128092
128608
  import { mkdir as mkdir61, readFile as readFile82, rename as rename16, rm as rm42, writeFile as writeFile30 } from "fs/promises";
128093
128609
  import { join as join112 } from "path";
128094
128610
  import { ImageRecognitionSchema, createLogger as createLogger172 } from "@bendyline/gezel";
@@ -128100,8 +128616,8 @@ var RecognitionCache = class {
128100
128616
  this.root = join112(opts.home, "recognition");
128101
128617
  }
128102
128618
  keyFor(input) {
128103
- const imageHash = createHash45("sha256").update(input.bytes).digest("hex");
128104
- return createHash45("sha256").update(`${imageHash}\0${input.mode}\0${input.modelId}\0v${PROMPT_VERSION}`).digest("hex");
128619
+ const imageHash = createHash46("sha256").update(input.bytes).digest("hex");
128620
+ return createHash46("sha256").update(`${imageHash}\0${input.mode}\0${input.modelId}\0v${PROMPT_VERSION}`).digest("hex");
128105
128621
  }
128106
128622
  pathFor(key) {
128107
128623
  return join112(this.root, key.slice(0, 2), `${key}.json`);
@@ -128546,7 +129062,7 @@ var MockVideoProvider = class {
128546
129062
  };
128547
129063
 
128548
129064
  // src/providers/video/models.ts
128549
- import { createHash as createHash46 } from "crypto";
129065
+ import { createHash as createHash47 } from "crypto";
128550
129066
  import { createReadStream as createReadStream11 } from "fs";
128551
129067
  import { mkdir as mkdir62, readFile as readFile83, rename as rename17, rm as rm43, writeFile as writeFile31 } from "fs/promises";
128552
129068
  import { dirname as dirname50, join as join113 } from "path";
@@ -128732,7 +129248,7 @@ var VideoModelManager = class {
128732
129248
  await rm43(finalPath, { force: true });
128733
129249
  await rename17(partialPath, finalPath);
128734
129250
  } catch (err) {
128735
- yield { type: "error", error: `rename failed for ${file.name}: ${describeError3(err)}` };
129251
+ yield { type: "error", error: `rename failed for ${file.name}: ${describeError4(err)}` };
128736
129252
  return;
128737
129253
  }
128738
129254
  }
@@ -128819,7 +129335,7 @@ var VideoModelManager = class {
128819
129335
  }
128820
129336
  }
128821
129337
  push({ type: "verifying", file: file.name });
128822
- const hasher = createHash46("sha256");
129338
+ const hasher = createHash47("sha256");
128823
129339
  await new Promise((resolve19, reject) => {
128824
129340
  const stream = createReadStream11(tmpPath, { highWaterMark: MODEL_HASH_READ_BUFFER_BYTES });
128825
129341
  stream.on("data", (chunk2) => hasher.update(chunk2));
@@ -128891,7 +129407,7 @@ var VideoModelSelector = class {
128891
129407
  return (id ? await this.models.resolveModel(id) : null) ?? await this.models.resolveDefaultModel();
128892
129408
  }
128893
129409
  };
128894
- function describeError3(err) {
129410
+ function describeError4(err) {
128895
129411
  return err instanceof Error ? err.message : String(err);
128896
129412
  }
128897
129413
  function isSafeId3(id) {
@@ -133092,11 +133608,18 @@ var TaskRunner = class {
133092
133608
  /** Run one serialized dispatch pass now, sharing the interval's overlap guard. */
133093
133609
  async wake() {
133094
133610
  if (this.tickInFlight) return this.tickInFlight;
133095
- this.tickInFlight = this.tick().finally(() => {
133611
+ this.tickInFlight = this.tickOnce().finally(() => {
133096
133612
  this.tickInFlight = null;
133097
133613
  });
133098
133614
  return this.tickInFlight;
133099
133615
  }
133616
+ /**
133617
+ * Public test/operator entry point. Keep it serialized with the interval:
133618
+ * callers may drive a real, already-started service whose ticker is active.
133619
+ */
133620
+ async tick() {
133621
+ return this.wake();
133622
+ }
133100
133623
  /**
133101
133624
  * Scan every `active` task and enqueue a handoff for any whose
133102
133625
  * current step has an effective gezel owner. Call on service boot and
@@ -133143,9 +133666,9 @@ var TaskRunner = class {
133143
133666
  /**
133144
133667
  * One dispatch pass. Walks pending in FIFO order; dispatches any
133145
133668
  * whose target provider has a free slot; skips items for tasks
133146
- * that are no longer active. Public for test-driven ticks.
133669
+ * that are no longer active.
133147
133670
  */
133148
- async tick() {
133671
+ async tickOnce() {
133149
133672
  await this.pruneActiveDispatches();
133150
133673
  if (this.pending.length === 0) {
133151
133674
  this.holdReason = void 0;
@@ -133157,6 +133680,7 @@ var TaskRunner = class {
133157
133680
  return;
133158
133681
  }
133159
133682
  const snapshot = this.pending.slice();
133683
+ const snapshotIds = new Set(snapshot.map((handoff) => handoff.id));
133160
133684
  const keep = [];
133161
133685
  const inTickDispatches = /* @__PURE__ */ new Map();
133162
133686
  const nightShiftOn = this.isNightShiftActive();
@@ -133222,6 +133746,7 @@ var TaskRunner = class {
133222
133746
  ...handoff.fromGezelName ? { fromGezelName: handoff.fromGezelName } : {},
133223
133747
  ...handoff.fromGezelId ? { fromGezelId: handoff.fromGezelId } : {},
133224
133748
  ...handoff.kind ? { kind: handoff.kind } : {},
133749
+ ...task.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: task.roleBasedNameOnlyMode } : {},
133225
133750
  lane,
133226
133751
  ...isNight ? { ambient: true } : {},
133227
133752
  ...isNight ? { nightShift: true } : {},
@@ -133244,8 +133769,9 @@ var TaskRunner = class {
133244
133769
  );
133245
133770
  }
133246
133771
  }
133772
+ const enqueuedDuringTick = this.pending.filter((handoff) => !snapshotIds.has(handoff.id));
133247
133773
  this.pending.length = 0;
133248
- this.pending.push(...keep);
133774
+ this.pending.push(...keep, ...enqueuedDuringTick);
133249
133775
  this.holdReason = keep.some((handoff) => handoff.heldFor === "provider-busy") ? "provider-busy" : void 0;
133250
133776
  }
133251
133777
  async pruneActiveDispatches() {
@@ -133735,6 +134261,10 @@ Step "${step.name}" (\`${step.id}\`) was re-driven ${maxRedrives}\xD7 without pr
133735
134261
  } catch {
133736
134262
  }
133737
134263
  const activeTasks = projectTasks.filter((t) => t.status === "active");
134264
+ if (projectTasks.length === 0) {
134265
+ trace("skip \u2014 project has no tasks to advance");
134266
+ return;
134267
+ }
133738
134268
  if (projectTasks.length > 0 && activeTasks.length === 0) {
133739
134269
  if (projectTasks.some((t) => t.status === "draft")) {
133740
134270
  trace("skip \u2014 only draft task(s) await activation; not nudging or stabilizing");
@@ -137476,7 +138006,7 @@ Address these, then the gate re-checks automatically.`,
137476
138006
  try {
137477
138007
  const existing = await tasks.listChildren(task.ref).catch(() => []);
137478
138008
  if (existing.length === 0) {
137479
- const raw = await store.readProjectWorkspaceFile(projectId, spawn14.overFile).catch(() => null);
138009
+ const raw = await (spawn14.overArtifact ? store.readProjectArtifact(projectId, spawn14.overFile) : store.readProjectWorkspaceFile(projectId, spawn14.overFile)).catch(() => null);
137480
138010
  const items = raw ? extractSpawnItems(raw, spawn14.itemsPath) : [];
137481
138011
  if (items.length === 0) {
137482
138012
  log188.warn(
@@ -137505,7 +138035,7 @@ Address these, then the gate re-checks automatically.`,
137505
138035
 
137506
138036
  ${kids.map((k) => `- ${k.ref}: ${k.title}`).join("\n")}
137507
138037
  `;
137508
- await store.writeProjectWorkspaceFile(projectId, advanceFile, manifest).catch((err) => log188.warn(`[fanout] ${task.ref}: could not write ${advanceFile}:`, err));
138038
+ 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));
137509
138039
  }
137510
138040
  const nextStep = newStep.advanceWhen?.goto ?? newStep.next;
137511
138041
  if (nextStep) {