@bendyline/gezel-service 1.0.3 → 1.0.4

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 +486 -84
  2. package/dist/handboek-content/whats-new/1.26226.md +89 -0
  3. package/dist/index.d.ts +45 -2
  4. package/dist/index.js +486 -84
  5. package/dist/memory/embed-worker.js +22 -3
  6. package/dist/ui/assets/{TerminalCodeEditor-CXspNipu.js → TerminalCodeEditor-BuMcfTS4.js} +2 -2
  7. package/dist/ui/assets/{_.contribution-Bi8h5rS7.js → _.contribution-CZO2XAKt.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-BJzhk0Zj.js} +1 -1
  13. package/dist/ui/assets/{csharp.contribution-DDiJzXzB.js → csharp.contribution-BR9K2qKj.js} +1 -1
  14. package/dist/ui/assets/{css.contribution-Dc5E_mNx.js → css.contribution-CfkVmdah.js} +1 -1
  15. package/dist/ui/assets/{cssMode-BN_VZB6O.js → cssMode-MMeGUmV2.js} +1 -1
  16. package/dist/ui/assets/dist-8uH4DcHx.js +2 -0
  17. package/dist/ui/assets/{dist-D4PZf1Q3.js → dist-CFOmf7nD.js} +2 -2
  18. package/dist/ui/assets/{dockerfile.contribution-BQsaSuAJ.js → dockerfile.contribution-IZMl57B5.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-BxCBwhZs.js} +1 -1
  22. package/dist/ui/assets/{handlebars-DJvRMy-I.js → handlebars-CreIkams.js} +1 -1
  23. package/dist/ui/assets/handlebars.contribution-BGlyL-dm.js +2 -0
  24. package/dist/ui/assets/{html-Becff5XU.js → html-BSrz1IKi.js} +1 -1
  25. package/dist/ui/assets/html.contribution-CucOMFQ_.js +2 -0
  26. package/dist/ui/assets/{htmlMode-BFmyC6t-.js → htmlMode-DRG7XY5Z.js} +1 -1
  27. package/dist/ui/assets/{index-w_CaRVtV.js → index-Bqu-32S3.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-CGX5OdXM.js} +1 -1
  30. package/dist/ui/assets/{java.contribution-Bdvvskrf.js → java.contribution-CVhfJ_us.js} +1 -1
  31. package/dist/ui/assets/{javascript-DByfaclN.js → javascript-QelFp9t5.js} +1 -1
  32. package/dist/ui/assets/javascript.contribution--no0ijCu.js +2 -0
  33. package/dist/ui/assets/{jsonMode-CxkV6dJT.js → jsonMode-T4Ss3lDu.js} +1 -1
  34. package/dist/ui/assets/{kotlin.contribution-D8eqi2Fi.js → kotlin.contribution-DxFbGDYA.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-g9R8XRZf.js} +1 -1
  37. package/dist/ui/assets/{lua.contribution-BgktkcsF.js → lua.contribution-YqDsEy8Z.js} +1 -1
  38. package/dist/ui/assets/{markdown.contribution-BKqBKD_g.js → markdown.contribution-DboXnPnx.js} +1 -1
  39. package/dist/ui/assets/{monaco-CyhM025v.js → monaco-BdFw56DI.js} +3 -3
  40. package/dist/ui/assets/{monaco-base-hW9ht_BG.js → monaco-base-DrP3D13F.js} +1 -1
  41. package/dist/ui/assets/{monaco-setup-Cquhh1OE.js → monaco-setup-QyF22y9h.js} +1 -1
  42. package/dist/ui/assets/{monaco.contribution-D9-bPyBf.js → monaco.contribution-BkqIxESv.js} +2 -2
  43. package/dist/ui/assets/{monaco.contribution-Cle0Lbgx.js → monaco.contribution-CMzPFHtO.js} +2 -2
  44. package/dist/ui/assets/{monaco.contribution-Dl8lHLi5.js → monaco.contribution-FA_ESP-W.js} +2 -2
  45. package/dist/ui/assets/{monaco.contribution-BVrZ1Anc.js → monaco.contribution-XEpc63ty.js} +2 -2
  46. package/dist/ui/assets/{php.contribution-CcYxMawn.js → php.contribution-rnpik0Kk.js} +1 -1
  47. package/dist/ui/assets/pptx-DZdEoDP9.js +6 -0
  48. package/dist/ui/assets/{python-DivQov6J.js → python-CF7X5t9Q.js} +1 -1
  49. package/dist/ui/assets/python.contribution-CyJGCfSq.js +2 -0
  50. package/dist/ui/assets/{ruby.contribution-DNudSRrU.js → ruby.contribution-4ZyPGu2E.js} +1 -1
  51. package/dist/ui/assets/{rust.contribution-BqG2lQpP.js → rust.contribution-Cl_B3X2G.js} +1 -1
  52. package/dist/ui/assets/{scss.contribution-DDxPnF-K.js → scss.contribution-BO02gVLZ.js} +1 -1
  53. package/dist/ui/assets/{shell.contribution-Q7wVL7zv.js → shell.contribution-Dn9Y0uHs.js} +1 -1
  54. package/dist/ui/assets/{sql.contribution-B9ndj3QH.js → sql.contribution-Bog_5Rc8.js} +1 -1
  55. package/dist/ui/assets/{swift.contribution-D_FDdL6S.js → swift.contribution-CfiAr7AX.js} +1 -1
  56. package/dist/ui/assets/{terminal-monaco-setup-BdF0mUfG.js → terminal-monaco-setup-B-EnkGiy.js} +1 -1
  57. package/dist/ui/assets/{tsMode-JVR2Jo5q.js → tsMode-B2WuLuPL.js} +1 -1
  58. package/dist/ui/assets/{typescript-1ZOjdUnd.js → typescript-Bv_J1tKT.js} +1 -1
  59. package/dist/ui/assets/typescript.contribution-CM0XQCYi.js +2 -0
  60. package/dist/ui/assets/xlsx-DDBRN-IU.js +4 -0
  61. package/dist/ui/assets/{xml-DrdFGpo2.js → xml-DMV-f4tm.js} +1 -1
  62. package/dist/ui/assets/xml.contribution-QqmOB9gT.js +2 -0
  63. package/dist/ui/assets/{yaml-DYQPB5Mj.js → yaml-Bb6mZXY5.js} +1 -1
  64. package/dist/ui/assets/yaml.contribution-DCDnWhHR.js +2 -0
  65. package/dist/ui/index.html +10 -10
  66. package/package.json +11 -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;
@@ -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
  }
@@ -33330,7 +33431,7 @@ var PROJECT_MACRO_INTERCEPT_CAP = 2;
33330
33431
  var PROJECT_MACRO_FAILURE_CAP = 2;
33331
33432
  function deriveProjectMacroClosing(firstResult) {
33332
33433
  const projectMatch = firstResult.match(/Started (?:project|job) "([^"]+)"/);
33333
- const leadMatch = firstResult.match(/Recruited (\S+) as (voorman|ambachtsman|lead)/);
33434
+ const leadMatch = firstResult.match(/Recruited (\S+) as (voorman|builder|lead)/i);
33334
33435
  const projectName = projectMatch?.[1];
33335
33436
  const leadName = leadMatch?.[1];
33336
33437
  const leadRole = leadMatch?.[2];
@@ -37282,6 +37383,10 @@ var OpenAISession = class extends StreamingSessionBase {
37282
37383
  capturedToolCalls() {
37283
37384
  return [...this.capturedCalls];
37284
37385
  }
37386
+ getRegisteredToolNames() {
37387
+ const bridgeNames = this.deps.bridges.isEmpty() ? [] : this.deps.bridges.getOpenAITools().map((tool) => tool.name);
37388
+ return [.../* @__PURE__ */ new Set([...bridgeNames, ...this.externalToolNames])];
37389
+ }
37285
37390
  async sendAndWait(prompt, opts) {
37286
37391
  return runInQueue(this.deps.queue, opts?.queue, () => this.sendAndWaitInner(prompt, opts));
37287
37392
  }
@@ -40199,14 +40304,15 @@ async function evalCheck(c, ws, deps) {
40199
40304
  }
40200
40305
  async function evalCheckInner(c, ws, deps) {
40201
40306
  const usesArtifact = c.artifact === true;
40202
- const reader = usesArtifact ? {
40307
+ const artifactReader = {
40203
40308
  read: ws.readArtifact ?? (async () => null),
40204
40309
  list: ws.listArtifacts ?? (async () => []),
40205
40310
  // Carry the byte reader across the artifact swap; without it a
40206
40311
  // `verifyImageBytes` check on an artifact deliverable would report
40207
40312
  // "no binary reads" even though the surface supports them.
40208
40313
  ...ws.readArtifactBytes ? { readBytes: ws.readArtifactBytes.bind(ws) } : {}
40209
- } : ws;
40314
+ };
40315
+ const reader = usesArtifact ? artifactReader : ws;
40210
40316
  switch (c.kind) {
40211
40317
  case "minBytes": {
40212
40318
  const r = await fileMinBytes(reader, c.file, c.bytes);
@@ -40634,7 +40740,12 @@ ${hint}` : ""}`,
40634
40740
  };
40635
40741
  }
40636
40742
  case "markdownHeadingsMatch": {
40637
- const result = await markdownHeadingsMatch(reader, c.file, c.outlineFile);
40743
+ const result = await markdownHeadingsMatch(
40744
+ reader,
40745
+ c.file,
40746
+ c.outlineFile,
40747
+ c.outlineArtifact ? artifactReader : ws
40748
+ );
40638
40749
  return {
40639
40750
  ok: result.ok,
40640
40751
  detail: result.detail,
@@ -42549,6 +42660,11 @@ function filterPromptToolDirectives(args) {
42549
42660
  if (rejectedLines.size === 0) return args.prompt;
42550
42661
  return args.prompt.split("\n").filter((_, index) => !rejectedLines.has(index + 1)).join("\n").replace(/\n{4,}/g, "\n\n\n");
42551
42662
  }
42663
+ function capabilitySafeCorrectivePrompt(args) {
42664
+ const filtered = filterPromptToolDirectives(args).trim();
42665
+ if (filtered) return filtered;
42666
+ 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.";
42667
+ }
42552
42668
 
42553
42669
  // src/chat/provider-capabilities.ts
42554
42670
  function providerUsesManagedMcpBridge(name) {
@@ -46387,6 +46503,21 @@ function sameFromBucket(a, b) {
46387
46503
  if (!a || !b) return false;
46388
46504
  return a.gezelId === b.gezelId;
46389
46505
  }
46506
+ function resolveTurnMessageOrigin(opts) {
46507
+ if (opts?.messageOrigin) return opts.messageOrigin;
46508
+ if (opts?.from) return "cross-gezel";
46509
+ if (opts?.ambient || opts?.nudge || opts?.lane === "background") return "background-nudge";
46510
+ return "direct-user";
46511
+ }
46512
+ function liveTurnToolNames(session) {
46513
+ const names = /* @__PURE__ */ new Set();
46514
+ for (const name of session?.getRegisteredToolNames?.() ?? []) {
46515
+ names.add(name);
46516
+ const qualified = name.match(/^mcp__.+?__(.+)$/)?.[1];
46517
+ if (qualified && qualified !== "*") names.add(qualified);
46518
+ }
46519
+ return [...names];
46520
+ }
46390
46521
  async function resolveCatalogParameterSize(catalog, catalogId) {
46391
46522
  if (!catalogId) return void 0;
46392
46523
  try {
@@ -47788,6 +47919,7 @@ ${JSON.stringify(run.output, null, 2)}`;
47788
47919
  coalescable: false,
47789
47920
  lane: void 0,
47790
47921
  ambient: false,
47922
+ messageOrigin: "direct-user",
47791
47923
  continuationMaxTokens: void 0,
47792
47924
  hidden: false,
47793
47925
  nudge: false,
@@ -48093,11 +48225,14 @@ ${JSON.stringify(run.output, null, 2)}`;
48093
48225
  const full = await this.store.getSession(args.gezelId, active.id);
48094
48226
  if (full) return full;
48095
48227
  }
48228
+ const parsedTaskRef = parseTaskRef2(args.taskRef);
48229
+ const task = parsedTaskRef ? await this.store.readTask(parsedTaskRef.projectId, parsedTaskRef.num).catch(() => null) : null;
48096
48230
  return this.createSession({
48097
48231
  gezelId: args.gezelId,
48098
48232
  projectId: args.projectId,
48099
48233
  taskRef: args.taskRef,
48100
- ...args.stepId ? { stepId: args.stepId } : {}
48234
+ ...args.stepId ? { stepId: args.stepId } : {},
48235
+ ...task?.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: task.roleBasedNameOnlyMode } : {}
48101
48236
  });
48102
48237
  }
48103
48238
  /** Late-bind the fitness manager (see the field's docblock). */
@@ -48224,6 +48359,7 @@ ${JSON.stringify(run.output, null, 2)}`;
48224
48359
  */
48225
48360
  async startHandoffSession(args) {
48226
48361
  const dispatchConfig = await this.store.readConfig();
48362
+ const roleBasedNameOnlyMode = args.roleBasedNameOnlyMode ?? dispatchConfig.roleBasedNameOnlyMode ?? false;
48227
48363
  const dispatchGezel = await this.store.getGezel(args.gezelId);
48228
48364
  const dispatchProviderName = await this.resolveProviderName(
48229
48365
  args.gezelId,
@@ -48272,6 +48408,7 @@ ${JSON.stringify(run.output, null, 2)}`;
48272
48408
  projectId: args.projectId,
48273
48409
  taskRef: args.taskRef,
48274
48410
  stepId: dispatchStepId,
48411
+ roleBasedNameOnlyMode,
48275
48412
  ...args.nightShift ? { nightShift: true } : {},
48276
48413
  ...routed && args.capabilityFloor ? {
48277
48414
  routedModel: {
@@ -48328,9 +48465,15 @@ ${stepArc}
48328
48465
  }
48329
48466
  }
48330
48467
  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.`;
48468
+ const previousGezel = !selfHandoff && args.fromGezelId ? await this.store.getGezel(args.fromGezelId).catch(() => null) : null;
48469
+ const fromGezelDisplayName = previousGezel ? displayName2(
48470
+ { name: previousGezel.name, roleBasedName: previousGezel.roleBasedName },
48471
+ roleBasedNameOnlyMode
48472
+ ) : roleBasedNameOnlyMode ? void 0 : args.fromGezelName;
48473
+ 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
48474
  this.trackBackground(
48333
48475
  this.sendWithBusyRetry(session.id, seed, {
48476
+ messageOrigin: "system",
48334
48477
  ...args.lane ? { lane: args.lane } : {},
48335
48478
  ...args.ambient ? { ambient: true } : {}
48336
48479
  }).catch((err) => {
@@ -49176,11 +49319,10 @@ ${stepArc}
49176
49319
  log52.info(
49177
49320
  `[questions] delivering answer to session ${args.sessionId.slice(0, 8)} (gezel ${session.gezelId}, project ${session.projectId})`
49178
49321
  );
49179
- await this.sendWithBusyRetry(
49180
- args.sessionId,
49181
- args.seed,
49182
- args.coalescable ? { coalescable: true } : void 0
49183
- );
49322
+ await this.sendWithBusyRetry(args.sessionId, args.seed, {
49323
+ messageOrigin: "question-answer",
49324
+ ...args.coalescable ? { coalescable: true } : {}
49325
+ });
49184
49326
  }
49185
49327
  /**
49186
49328
  * System-authored reaction seed into a gezel's live (gezel, project)
@@ -49206,6 +49348,7 @@ ${stepArc}
49206
49348
  this.send(session.id, args.seed, {
49207
49349
  coalescable: true,
49208
49350
  lane: "background",
49351
+ messageOrigin: "system",
49209
49352
  ...leanReactionCap ? { continuationMaxTokens: leanReactionCap } : {},
49210
49353
  ...args.hidden ? { hidden: true } : {}
49211
49354
  }).catch((err) => {
@@ -49680,12 +49823,13 @@ ${stepArc}
49680
49823
  throw new Error("engagement-off: AI is disabled in settings; re-enable to send");
49681
49824
  }
49682
49825
  const existingQueue = this.pendingSends.get(sessionId2);
49826
+ const messageOrigin = resolveTurnMessageOrigin(opts);
49683
49827
  const shouldQueue = this.inflight.has(sessionId2) || existingQueue !== void 0 && existingQueue.length > 0;
49684
49828
  if (shouldQueue) {
49685
49829
  return new Promise((resolve19, reject) => {
49686
49830
  const q2 = this.pendingSends.get(sessionId2) ?? [];
49687
49831
  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);
49832
+ const canMerge = opts?.coalescable === true && opts?.nudge !== true && tail?.coalescable === true && tail.nudge !== true && tail.messageOrigin === messageOrigin && sameFromBucket(tail.from, opts.from);
49689
49833
  if (canMerge && tail) {
49690
49834
  tail.userText = `${tail.userText}
49691
49835
 
@@ -49712,6 +49856,7 @@ ${userText}`;
49712
49856
  coalescable: opts?.coalescable === true,
49713
49857
  lane: opts?.lane,
49714
49858
  ambient: opts?.ambient === true,
49859
+ messageOrigin,
49715
49860
  continuationMaxTokens: opts?.continuationMaxTokens,
49716
49861
  hidden: opts?.hidden === true,
49717
49862
  nudge: opts?.nudge === true,
@@ -49728,7 +49873,7 @@ ${userText}`;
49728
49873
  return this.runSendAndDrain(
49729
49874
  sessionId2,
49730
49875
  userText,
49731
- opts?.nudge ? { ...opts, nudge: false } : opts
49876
+ opts?.nudge ? { ...opts, nudge: false, messageOrigin } : { ...opts, messageOrigin }
49732
49877
  );
49733
49878
  }
49734
49879
  /**
@@ -49827,7 +49972,7 @@ ${userText}`;
49827
49972
  if (next.nudge) {
49828
49973
  while (q2.length > 0) {
49829
49974
  const peek = q2[0];
49830
- if (!peek.nudge || peek.hidden !== next.hidden || !sameFromBucket(peek.from, next.from)) {
49975
+ if (!peek.nudge || peek.hidden !== next.hidden || peek.messageOrigin !== next.messageOrigin || !sameFromBucket(peek.from, next.from)) {
49831
49976
  break;
49832
49977
  }
49833
49978
  q2.shift();
@@ -49850,6 +49995,7 @@ ${peek.userText}`;
49850
49995
  if (next.continuationMaxTokens) runOpts.continuationMaxTokens = next.continuationMaxTokens;
49851
49996
  if (next.hidden) runOpts.hidden = true;
49852
49997
  if (next.nudge) runOpts.nudge = true;
49998
+ runOpts.messageOrigin = next.messageOrigin;
49853
49999
  void this.runSendAndDrain(sessionId2, next.userText, runOpts).then((msg) => {
49854
50000
  for (const w of next.waiters) {
49855
50001
  try {
@@ -50180,6 +50326,18 @@ ${peek.userText}`;
50180
50326
  try {
50181
50327
  let lastAssistantMessage = null;
50182
50328
  let promptForTurn = spliceIntoText(userText, pendingDigests);
50329
+ const correctivePromptForLiveRoster = (prompt, source) => {
50330
+ const safe = capabilitySafeCorrectivePrompt({
50331
+ prompt,
50332
+ availableTools: liveTurnToolNames(liveSession)
50333
+ });
50334
+ if (safe !== prompt.trim()) {
50335
+ log52.warn(
50336
+ `session ${sessionId2}: filtered ${source} corrective against the live tool roster`
50337
+ );
50338
+ }
50339
+ return safe;
50340
+ };
50183
50341
  const freshGameState = await this.refreshLeanGameState(state.record, userText);
50184
50342
  if (freshGameState) {
50185
50343
  promptForTurn = `${freshGameState}
@@ -50190,7 +50348,8 @@ ${promptForTurn}`;
50190
50348
  this.taskBudget.reset(state.record.taskRef);
50191
50349
  this.pendingBudgetNudge.delete(state.record.taskRef);
50192
50350
  }
50193
- const preludeForTurn = await this.resolveUserPromptPrelude(state, userText);
50351
+ const messageOrigin = resolveTurnMessageOrigin(opts);
50352
+ const preludeForTurn = await this.resolveUserPromptPrelude(state, userText, messageOrigin);
50194
50353
  if (preludeForTurn) {
50195
50354
  promptForTurn = `${preludeForTurn.text}
50196
50355
 
@@ -50213,7 +50372,7 @@ ${promptForTurn}`;
50213
50372
  }
50214
50373
  if (state.record.taskRef && this.pendingBudgetNudge.has(state.record.taskRef)) {
50215
50374
  this.pendingBudgetNudge.delete(state.record.taskRef);
50216
- promptForTurn = `${TASK_BUDGET_NUDGE}
50375
+ promptForTurn = `${correctivePromptForLiveRoster(TASK_BUDGET_NUDGE, "task-budget")}
50217
50376
 
50218
50377
  ${promptForTurn}`;
50219
50378
  log52.info(
@@ -50222,6 +50381,7 @@ ${promptForTurn}`;
50222
50381
  }
50223
50382
  let continuations = 0;
50224
50383
  const maxContinuations = resolveContinuationBudget(state);
50384
+ let voormanIdleNudged = false;
50225
50385
  const toolsAcrossContinuations = [];
50226
50386
  let compactionsThisSend = 0;
50227
50387
  let keurmeesterConsultedThisSend = false;
@@ -50485,6 +50645,7 @@ ${state.aboutSnapshot}`);
50485
50645
  const detectorVerdict = runPostTurnDetectors(state, {
50486
50646
  sessionId: sessionId2,
50487
50647
  isMeester: cfgForDetector?.meesterGezelId === state.record.gezelId,
50648
+ messageOrigin,
50488
50649
  userText,
50489
50650
  drained: toolsAcrossContinuations,
50490
50651
  assistantContent: assistantMessage.content,
@@ -50498,7 +50659,10 @@ ${state.aboutSnapshot}`);
50498
50659
  this.events.publish(scope, { type: "warning", message: warningMessage });
50499
50660
  }
50500
50661
  if (detectorVerdict?.promptForNextTurn) {
50501
- detectorReprompt = detectorVerdict.promptForNextTurn;
50662
+ detectorReprompt = correctivePromptForLiveRoster(
50663
+ detectorVerdict.promptForNextTurn,
50664
+ "post-turn detector"
50665
+ );
50502
50666
  }
50503
50667
  const advanceOutcome = await this.maybeAutoAdvanceOnObservableProgress(
50504
50668
  state,
@@ -50649,7 +50813,10 @@ ${state.aboutSnapshot}`);
50649
50813
  log52.info(
50650
50814
  `session ${sessionId2}: keurmeester intervention applied (case ${consult.caseId}) \u2014 granting one recovery continuation`
50651
50815
  );
50652
- promptForTurn = consult.correctivePrompt;
50816
+ promptForTurn = correctivePromptForLiveRoster(
50817
+ consult.correctivePrompt,
50818
+ "keurmeester"
50819
+ );
50653
50820
  continue;
50654
50821
  }
50655
50822
  }
@@ -50667,7 +50834,10 @@ ${state.aboutSnapshot}`);
50667
50834
  log52.info(
50668
50835
  `session ${sessionId2}: ${detectorVerdict?.reason ?? "detector"} \u2014 re-prompting`
50669
50836
  );
50670
- promptForTurn = detectorReprompt;
50837
+ promptForTurn = correctivePromptForLiveRoster(
50838
+ detectorReprompt,
50839
+ detectorVerdict ? "post-turn detector" : "deliverable salvage"
50840
+ );
50671
50841
  continue;
50672
50842
  }
50673
50843
  if (advanceOutcome.gateRejected && !advanceOutcome.gateRejected.paused && advanceOutcome.gateRejected.fingerprint !== state.lastGateNudgeFingerprint && !drained.some((d) => d.name === "ask_user_question" && d.success)) {
@@ -50677,7 +50847,10 @@ ${state.aboutSnapshot}`);
50677
50847
  log52.info(
50678
50848
  `session ${sessionId2}: ${advanceOutcome.gateRejected.taskRef} step "${advanceOutcome.gateRejected.stepId}" gate rejected \u2014 re-prompting with the verdict${stage > 0 ? ` (escalation stage ${stage})` : ""}`
50679
50849
  );
50680
- promptForTurn = stage >= 1 ? advanceOutcome.gateRejected.message : buildGateRejectionNudge(advanceOutcome.gateRejected.message);
50850
+ promptForTurn = correctivePromptForLiveRoster(
50851
+ stage >= 1 ? advanceOutcome.gateRejected.message : buildGateRejectionNudge(advanceOutcome.gateRejected.message),
50852
+ "task-gate"
50853
+ );
50681
50854
  continue;
50682
50855
  }
50683
50856
  if (deliverableOutcome.deliverableRejected?.stopRetrying) {
@@ -50728,7 +50901,10 @@ ${state.aboutSnapshot}`);
50728
50901
  log52.info(
50729
50902
  `session ${sessionId2}: keurmeester intervention applied on deliverable plateau (case ${consult.caseId}) \u2014 granting one recovery continuation`
50730
50903
  );
50731
- promptForTurn = consult.correctivePrompt;
50904
+ promptForTurn = correctivePromptForLiveRoster(
50905
+ consult.correctivePrompt,
50906
+ "keurmeester deliverable-plateau"
50907
+ );
50732
50908
  continue;
50733
50909
  }
50734
50910
  }
@@ -50752,7 +50928,10 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50752
50928
  ...dRej.filePath ? { filePath: dRej.filePath } : {},
50753
50929
  failingVerdict: dRej.message
50754
50930
  });
50755
- promptForTurn = clampNudge ?? (dStage >= 1 ? dRej.message : buildGateRejectionNudge(dRej.message));
50931
+ promptForTurn = correctivePromptForLiveRoster(
50932
+ clampNudge ?? (dStage >= 1 ? dRej.message : buildGateRejectionNudge(dRej.message)),
50933
+ "deliverable-gate"
50934
+ );
50756
50935
  continue;
50757
50936
  }
50758
50937
  if (advanceOutcome.unmetEditGate && (looksStalled(finalContent) || claimsCompletion(finalContent)) && !drained.some((d) => d.name === "ask_user_question" && d.success)) {
@@ -50760,7 +50939,10 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50760
50939
  log52.info(
50761
50940
  `session ${sessionId2}: ${advanceOutcome.unmetEditGate.taskRef} edit gate unmet (${advanceOutcome.unmetEditGate.file} not written) but reply claims progress \u2014 nudging to edit`
50762
50941
  );
50763
- promptForTurn = buildDeliverableEditNudge(advanceOutcome.unmetEditGate.file);
50942
+ promptForTurn = correctivePromptForLiveRoster(
50943
+ buildDeliverableEditNudge(advanceOutcome.unmetEditGate.file),
50944
+ "deliverable-edit"
50945
+ );
50764
50946
  continue;
50765
50947
  }
50766
50948
  const unresolvedToolFailures = unresolvedFailedToolCalls(drained);
@@ -50804,7 +50986,7 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50804
50986
  // the hot path synchronous for the common non-voorman case.
50805
50987
  // Also requires `proactive` — voorman idle nudges advance
50806
50988
  // project work past what the user asked for.
50807
- isProactiveAllowed(nudgeConfig) && state.record.providerName !== "copilot"
50989
+ isProactiveAllowed(nudgeConfig) && state.record.providerName !== "copilot" && !voormanIdleNudged
50808
50990
  ) {
50809
50991
  voormanIdleVerdict = await this.didVoormanIdleStall(state.record, drained, sessionId2);
50810
50992
  if (voormanIdleVerdict !== null) {
@@ -50822,15 +51004,19 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
50822
51004
  );
50823
51005
  break;
50824
51006
  }
51007
+ if (stallReason === "voorman-idle") voormanIdleNudged = true;
50825
51008
  continuations++;
50826
51009
  log52.info(
50827
- `session ${sessionId2}: response looks stalled (${stallReason}) \u2014 nudging the model to actually execute`
51010
+ `session ${sessionId2}: response looks stalled (${stallReason}) \u2014 requesting one recovery pass`
51011
+ );
51012
+ promptForTurn = correctivePromptForLiveRoster(
51013
+ stallReason === "voorman-idle" ? buildContinuationNudge(
51014
+ finalContent,
51015
+ state.record.messages,
51016
+ voormanIdleVerdict === "nothing-built" ? VOORMAN_NOT_DONE_NUDGE : VOORMAN_IDLE_NUDGE
51017
+ ) : stallReason === "tool-only" ? CLOSING_SUMMARY_NUDGE : stallReason === "tool-read-only" ? READ_ONLY_PROGRESS_NUDGE : stallReason === "tool-failed" ? buildFailedToolRecoveryNudge(unresolvedToolFailures) : buildContinuationNudge(finalContent, state.record.messages),
51018
+ `stall-${stallReason}`
50828
51019
  );
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
51020
  continue;
50835
51021
  }
50836
51022
  }
@@ -51396,7 +51582,7 @@ ${rej.message.split("\n").slice(0, 4).join("\n")}`;
51396
51582
  return null;
51397
51583
  }
51398
51584
  const tasks = await this.store.listProjectTasks(record.projectId).catch(() => []);
51399
- if (tasks.length > 0 && !tasks.some((t) => t.status === "active")) {
51585
+ if (tasks.length === 0 || !tasks.some((t) => t.status === "active")) {
51400
51586
  return null;
51401
51587
  }
51402
51588
  const workspaceFiles = await this.store.listProjectWorkspaceRecursive(record.projectId).catch(() => []);
@@ -54147,7 +54333,7 @@ ${baseSystem}` : baseSystem;
54147
54333
  return { modelId };
54148
54334
  }
54149
54335
  }
54150
- async resolveUserPromptPrelude(state, userText) {
54336
+ async resolveUserPromptPrelude(state, userText, messageOrigin) {
54151
54337
  const profile = state.profile;
54152
54338
  if (!profile) return null;
54153
54339
  const cfg = await this.store.readConfig().catch(() => null);
@@ -54156,6 +54342,9 @@ ${baseSystem}` : baseSystem;
54156
54342
  ...modelCtxFromProfile(profile, state),
54157
54343
  sessionId: state.record.id,
54158
54344
  isMeester,
54345
+ projectId: state.record.projectId,
54346
+ messageOrigin,
54347
+ availableToolNames: liveTurnToolNames(state.session),
54159
54348
  userText,
54160
54349
  drained: [],
54161
54350
  assistantContent: "",
@@ -55476,6 +55665,7 @@ Allow **${info.toolName}** this time?`,
55476
55665
  GEZEL_SESSION_ID: record.id,
55477
55666
  GEZEL_HOME: this.home,
55478
55667
  GEZEL_EXECUTION_DENSITY: executionDensity,
55668
+ GEZEL_ROLE_BASED_NAME_ONLY_MODE: record.roleBasedNameOnlyMode ?? globalConfig.roleBasedNameOnlyMode ?? false ? "1" : "0",
55479
55669
  ...record.expectedDeliverable ? { GEZEL_EXPECTED_DELIVERABLE: JSON.stringify(record.expectedDeliverable) } : {},
55480
55670
  ...record.stepId ? { GEZEL_STEP_ID: record.stepId } : {},
55481
55671
  // Scope task: lets the MCP task tools default to / recover toward
@@ -55915,6 +56105,9 @@ function runPostTurnDetectors(state, args) {
55915
56105
  ...modelCtxFromProfile(profile, state),
55916
56106
  sessionId: args.sessionId,
55917
56107
  isMeester: args.isMeester,
56108
+ projectId: state.record.projectId,
56109
+ messageOrigin: args.messageOrigin,
56110
+ availableToolNames: liveTurnToolNames(state.session),
55918
56111
  userText: args.userText,
55919
56112
  drained: args.drained,
55920
56113
  assistantContent: args.assistantContent,
@@ -56447,13 +56640,18 @@ Drop conversational filler, pleasantries, and verbatim tool outputs (just note "
56447
56640
  Earlier conversation:
56448
56641
 
56449
56642
  `;
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.';
56643
+ 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
56644
  var BOOTSTRAP_WORKSPACE_FILES = /* @__PURE__ */ new Set([
56452
56645
  "package.json",
56646
+ "package-lock.json",
56647
+ "pnpm-lock.yaml",
56648
+ "yarn.lock",
56649
+ "bun.lock",
56650
+ "bun.lockb",
56453
56651
  "tsconfig.json",
56454
56652
  ".gitignore"
56455
56653
  ]);
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.";
56654
+ 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
56655
  var READ_ONLY_MCP_TOOLS = /* @__PURE__ */ new Set([
56458
56656
  "search_memory",
56459
56657
  "list_memories",
@@ -75777,6 +75975,7 @@ ${p.content.trim()}`).join("\n\n---\n\n");
75777
75975
  async createProject(input, opts) {
75778
75976
  const release2 = await this.acquireProjectCreationLock();
75779
75977
  try {
75978
+ if (input.workingDir) this.assertSafeWorkingDir(input.workingDir);
75780
75979
  const id = opts?.id ?? await this.uniqueProjectId(slugify(input.name) || randomUUID10().slice(0, 8));
75781
75980
  if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
75782
75981
  throw new Error(`invalid project id "${id}"`);
@@ -75800,14 +75999,22 @@ ${p.content.trim()}`).join("\n\n---\n\n");
75800
75999
  projectName: input.name
75801
76000
  });
75802
76001
  }
76002
+ let github = input.github?.url ? { url: input.github.url } : void 0;
76003
+ if (input.workingDir) {
76004
+ github = await autoDetectGitHubLink(input.workingDir, github) ?? github;
76005
+ }
75803
76006
  const project = {
75804
76007
  id,
75805
76008
  name: input.name,
75806
76009
  description: input.description,
75807
76010
  ...input.workingDir ? { workingDir: input.workingDir } : {},
76011
+ // Opening an existing folder is an intake action, not a declaration
76012
+ // that its objectives are ready for active supervision. Keep the
76013
+ // Meester quiet until the user explicitly opts this project back in.
76014
+ ...input.workingDir ? { nudgeConfig: { enabled: false } } : {},
75808
76015
  ...input.mode && input.mode !== "crew" ? { mode: input.mode } : {},
75809
76016
  ...input.indexingEnabled !== void 0 ? { indexingEnabled: input.indexingEnabled } : {},
75810
- ...input.github?.url ? { github: { url: input.github.url } } : {},
76017
+ ...github ? { github } : {},
75811
76018
  createdAt: nowIso5(),
75812
76019
  updatedAt: nowIso5()
75813
76020
  };
@@ -80259,6 +80466,7 @@ var TaskManager = class {
80259
80466
  ...cron ? { cron } : {},
80260
80467
  ...nightShift ? { nightShift } : {},
80261
80468
  ...fanout ? { fanout } : {},
80469
+ ...input.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: input.roleBasedNameOnlyMode } : {},
80262
80470
  createdAt: now,
80263
80471
  updatedAt: now,
80264
80472
  createdBy: input.createdBy ?? { kind: "user" }
@@ -82253,6 +82461,7 @@ Fix one of these, then set the task active again:
82253
82461
  ...parent.nightShift?.enabled ? { nightShift: { enabled: true } } : {},
82254
82462
  activeStepId,
82255
82463
  parentTaskRef: parent.ref,
82464
+ ...parent.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: parent.roleBasedNameOnlyMode } : {},
82256
82465
  createdAt: now,
82257
82466
  updatedAt: now,
82258
82467
  createdBy: parent.createdBy
@@ -96153,19 +96362,19 @@ function memoryRoutes(ctx) {
96153
96362
  const app = new Hono37();
96154
96363
  app.post("/search", async (c) => {
96155
96364
  const body = await c.req.json();
96156
- const results = await ctx.memory.searchAll(
96365
+ const outcome = await ctx.memory.searchAllDetailed(
96157
96366
  body.gezelId,
96158
96367
  body.projectId,
96159
96368
  body.query,
96160
96369
  body.topK ?? 10
96161
96370
  );
96162
- return c.json({ results });
96371
+ return c.json(outcome);
96163
96372
  });
96164
96373
  app.post("/save", async (c) => {
96165
96374
  const body = await c.req.json();
96166
96375
  const kind = isMemoryKind(body.kind) ? body.kind : "fact";
96167
96376
  const outcome = await ctx.memory.save(body.scope, body.id, body.text, kind);
96168
- return c.json({ ok: true, status: outcome?.status ?? "saved" });
96377
+ return c.json({ ok: true, ...outcome });
96169
96378
  });
96170
96379
  app.get("/recent", async (c) => {
96171
96380
  const scope = c.req.query("scope") ?? "gezel";
@@ -101014,6 +101223,7 @@ import {
101014
101223
  projectGezelId as projectGezelId2,
101015
101224
  skillToCraftbookDoc
101016
101225
  } from "@bendyline/gezel";
101226
+ init_ensure();
101017
101227
  init_install();
101018
101228
 
101019
101229
  // src/workspace/instruction-scanner.ts
@@ -101196,6 +101406,30 @@ async function translateShellBlock(chat, skillName, scriptName, code) {
101196
101406
  // src/workspace/import-sync.ts
101197
101407
  var log110 = createLogger114("import-sync");
101198
101408
  var VOORMAN_TEMPLATE_ID = "voorman";
101409
+ var BUILDER_TEMPLATE_ID = "builder";
101410
+ async function ensureFolderProjectBuilder(deps, projectId) {
101411
+ const { store, catalog } = deps;
101412
+ if (!projectId || projectId === "default") return {};
101413
+ const project = await store.getProject(projectId).catch(() => null);
101414
+ if (!project || project.mode !== "solo" || !project.workingDir) return {};
101415
+ if (project.voormanGezelId) return {};
101416
+ const gezels = await store.listGezels().catch(() => []);
101417
+ const existing = gezels.find(
101418
+ (gezel) => gezel.templateId === BUILDER_TEMPLATE_ID || gezel.role?.trim().toLowerCase() === BUILDER_TEMPLATE_ID
101419
+ );
101420
+ const recruited = existing ? null : await recruitFromTemplate(store, catalog, BUILDER_TEMPLATE_ID).catch((err) => {
101421
+ log110.warn(`[import-sync] ${projectId}: Builder recruit failed: ${describe6(err)}`);
101422
+ return null;
101423
+ });
101424
+ const builder = existing ?? recruited;
101425
+ if (!builder) return {};
101426
+ await store.updateProject(projectId, {
101427
+ voormanGezelId: builder.id,
101428
+ voormanAutoAssignedAt: nowIso12()
101429
+ });
101430
+ log110.info(`[import-sync] ${projectId}: assigned ${builder.id} as Builder`);
101431
+ return recruited ? { createdGezel: recruited } : {};
101432
+ }
101199
101433
  async function syncProjectImports(deps, projectId, instructions, skills) {
101200
101434
  const { store } = deps;
101201
101435
  const gate = await store.assertWorkspaceWritable(projectId).catch(() => null);
@@ -101324,16 +101558,20 @@ async function findExistingVoorman(store) {
101324
101558
  return match?.id ?? null;
101325
101559
  }
101326
101560
  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;
101561
+ return recruitFromTemplate(store, catalog, VOORMAN_TEMPLATE_ID);
101562
+ }
101563
+ async function recruitFromTemplate(store, catalog, templateId) {
101564
+ const template = await resolveGildeTemplateForRole(catalog, templateId).catch(() => null);
101565
+ if (!template || template.templateId !== templateId) return null;
101329
101566
  const { name, gender } = pickRandomNameWithGender4();
101330
101567
  const created = await store.createGezel({
101331
101568
  name,
101332
- role: detail.manifest.role,
101569
+ role: template.role,
101333
101570
  gender,
101334
- about: detail.about ?? "",
101335
- templateId: VOORMAN_TEMPLATE_ID,
101336
- templateVersion: detail.manifest.version
101571
+ about: template.about,
101572
+ templateId,
101573
+ templateVersion: template.templateVersion,
101574
+ ...template.frontmatter ? { frontmatter: template.frontmatter } : {}
101337
101575
  });
101338
101576
  return { id: created.id, name: created.name };
101339
101577
  }
@@ -101808,6 +102046,7 @@ async function resolveActor(ctx, gezelIdHeader) {
101808
102046
  }
101809
102047
  function projectTaskRoutes(ctx) {
101810
102048
  const app = new Hono55();
102049
+ const inflightCraftbookInvocations = /* @__PURE__ */ new Map();
101811
102050
  app.get("/:projectId/tasks", async (c) => {
101812
102051
  const projectId = c.req.param("projectId");
101813
102052
  const filter = ListFilterSchema.parse({
@@ -101823,10 +102062,31 @@ function projectTaskRoutes(ctx) {
101823
102062
  });
101824
102063
  app.post("/:projectId/tasks", async (c) => {
101825
102064
  const projectId = c.req.param("projectId");
101826
- const { dispatchEntry, ...body } = CreateTaskRequestSchema.parse(await c.req.json());
102065
+ const { dispatchEntry, craftbookInvocationKey, ...body } = CreateTaskRequestSchema.parse(
102066
+ await c.req.json()
102067
+ );
101827
102068
  let task;
101828
102069
  try {
101829
- task = await ctx.tasks.create(projectId, body);
102070
+ if (craftbookInvocationKey) {
102071
+ const existing = (await ctx.tasks.list({ projectId })).find(
102072
+ (candidate) => candidate.origin?.kind === "craftbook-invocation" && candidate.origin.key === craftbookInvocationKey && (candidate.status === "draft" || candidate.status === "active" || candidate.status === "paused")
102073
+ );
102074
+ if (existing) return c.json(existing);
102075
+ const inflightKey = `${projectId}:${craftbookInvocationKey}`;
102076
+ const pending5 = inflightCraftbookInvocations.get(inflightKey);
102077
+ if (pending5) return c.json(await pending5);
102078
+ const create = ctx.tasks.create(projectId, body, {
102079
+ origin: { kind: "craftbook-invocation", key: craftbookInvocationKey }
102080
+ });
102081
+ inflightCraftbookInvocations.set(inflightKey, create);
102082
+ try {
102083
+ task = await create;
102084
+ } finally {
102085
+ inflightCraftbookInvocations.delete(inflightKey);
102086
+ }
102087
+ } else {
102088
+ task = await ctx.tasks.create(projectId, body);
102089
+ }
101830
102090
  } catch (err) {
101831
102091
  if (err instanceof CraftbookSetupRequiredError) {
101832
102092
  return c.json(
@@ -104272,12 +104532,13 @@ function projectRoutes(ctx) {
104272
104532
  app.post("/", async (c) => {
104273
104533
  const body = CreateProjectRequestSchema.parse(await c.req.json());
104274
104534
  const created = await ctx.store.createProject(body);
104275
- const ensured2 = await ensureProjectVoorman(
104535
+ const ensureLead = body.workingDir && body.mode === "solo" ? ensureFolderProjectBuilder : ensureProjectVoorman;
104536
+ const ensured2 = await ensureLead(
104276
104537
  { store: ctx.store, chat: ctx.chat, home: ctx.home, catalog: ctx.catalog },
104277
104538
  created.id
104278
104539
  ).catch((err) => {
104279
104540
  const message = err instanceof Error ? err.message : String(err);
104280
- log114.warn(`[projects] ensure-voorman failed for ${created.id}: ${message}`);
104541
+ log114.warn(`[projects] ensure-lead failed for ${created.id}: ${message}`);
104281
104542
  return {};
104282
104543
  });
104283
104544
  if (ensured2.createdGezel) {
@@ -104800,6 +105061,31 @@ function projectRoutes(ctx) {
104800
105061
  return c.json({ error: "not found" }, 404);
104801
105062
  }
104802
105063
  });
105064
+ app.post("/:id/reveal-reference", async (c) => {
105065
+ const id = c.req.param("id");
105066
+ const request = ReferenceFileLocationRequestSchema.parse({
105067
+ kind: c.req.query("kind"),
105068
+ path: c.req.query("path")
105069
+ });
105070
+ const base = request.kind === "artifact" ? ctx.store.projectArtifactsDir(id) : request.kind === "workspace" ? await ctx.store.projectWorkspaceDir(id) : ctx.store.documentsDir();
105071
+ const joined = safeJoin(base, request.path);
105072
+ if (!joined || !await realpathContained(base, joined)) {
105073
+ return c.json({ error: "path traversal" }, 400);
105074
+ }
105075
+ let path2;
105076
+ try {
105077
+ const file = await stat36(joined);
105078
+ if (!file.isFile()) return c.json({ error: "not found" }, 404);
105079
+ path2 = await realpath8(joined);
105080
+ } catch {
105081
+ return c.json({ error: "not found" }, 404);
105082
+ }
105083
+ const { execFile: execFile8 } = await import("child_process");
105084
+ const launcher = process.platform === "darwin" ? { cmd: "open", args: ["-R", path2] } : process.platform === "win32" ? { cmd: "explorer", args: ["/select,", path2] } : { cmd: "xdg-open", args: [dirname44(path2)] };
105085
+ execFile8(launcher.cmd, launcher.args, { windowsHide: true }, () => {
105086
+ });
105087
+ return c.json(ReferenceFileLocationResponseSchema.parse({ path: path2 }));
105088
+ });
104803
105089
  app.get("/:id/artifacts", async (c) => {
104804
105090
  const id = c.req.param("id");
104805
105091
  const subpath = c.req.query("path") ?? "";
@@ -116254,7 +116540,10 @@ function opaqueServerErrors(log189, onUnexpectedHttpError) {
116254
116540
  const raw = await prior.clone().text().catch(() => "");
116255
116541
  try {
116256
116542
  const parsed = JSON.parse(raw);
116257
- if (parsed.error === "internal_error" && typeof parsed.requestId === "string") return;
116543
+ if (parsed.error === "internal_error" && typeof parsed.requestId === "string") {
116544
+ c.header("x-request-id", parsed.requestId);
116545
+ return;
116546
+ }
116258
116547
  if ((parsed.error === "machine_engine_unavailable" || parsed.error === "capacity_denied") && Object.keys(parsed).length === 1) {
116259
116548
  return;
116260
116549
  }
@@ -125165,8 +125454,8 @@ var MemoryManager = class {
125165
125454
  * that text dedups, so we accept the race rather than lock.
125166
125455
  *
125167
125456
  * 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.
125457
+ * the source of truth) and the outcome reports deferred indexing. The health
125458
+ * monitor self-heals the derived index once embeddings recover.
125170
125459
  */
125171
125460
  async save(scope, id, text, kind = DEFAULT_MEMORY_KIND) {
125172
125461
  const trimmed = text.trim();
@@ -125181,9 +125470,19 @@ var MemoryManager = class {
125181
125470
  vector = await embed(trimmed);
125182
125471
  } catch (err) {
125183
125472
  await this.store.appendMemory(scope, id, trimmed, kind);
125184
- throw err;
125473
+ log155.warn(
125474
+ `[memory] saved ${scope}/${id} to Markdown; semantic indexing deferred: ${describeError3(err)}`
125475
+ );
125476
+ return deferredIndexOutcome();
125477
+ }
125478
+ let top;
125479
+ try {
125480
+ top = (await searchByVector(indexDir, vector, 1))[0];
125481
+ } catch (error) {
125482
+ log155.warn(
125483
+ `[memory] vector dedup unavailable for ${scope}/${id}; continuing save: ${describeError3(error)}`
125484
+ );
125185
125485
  }
125186
- const top = (await searchByVector(indexDir, vector, 1))[0];
125187
125486
  if (top && top.score >= this.dedupThreshold) {
125188
125487
  log155.info(
125189
125488
  `[memory] dup-skip (${top.score.toFixed(2)} vs "${top.text.slice(0, 40)}") ${scope}/${id}: ${trimmed.slice(0, 60)}`
@@ -125191,20 +125490,27 @@ var MemoryManager = class {
125191
125490
  return { status: "duplicate", match: { text: top.text, score: top.score, via: "vector" } };
125192
125491
  }
125193
125492
  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
- );
125493
+ try {
125494
+ await addToIndex(
125495
+ indexDir,
125496
+ {
125497
+ text: trimmed,
125498
+ scope,
125499
+ id,
125500
+ day: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
125501
+ at: (/* @__PURE__ */ new Date()).toISOString(),
125502
+ kind
125503
+ },
125504
+ vector
125505
+ );
125506
+ } catch (error) {
125507
+ log155.warn(
125508
+ `[memory] saved ${scope}/${id} to Markdown; semantic index write deferred: ${describeError3(error)}`
125509
+ );
125510
+ return deferredIndexOutcome();
125511
+ }
125206
125512
  log155.info(`[memory] saved ${scope}/${id} [${kind}]: ${trimmed.slice(0, 60)}`);
125207
- return { status: "saved" };
125513
+ return { status: "saved", indexed: true };
125208
125514
  }
125209
125515
  /**
125210
125516
  * Exact-text match against today's and yesterday's daily files. Two days
@@ -125226,7 +125532,15 @@ var MemoryManager = class {
125226
125532
  }
125227
125533
  async search(scope, id, query, topK = 10) {
125228
125534
  const indexDir = this.store.memoryIndexDir(scope, id);
125229
- return searchIndex(indexDir, query, topK);
125535
+ if (!this.hasIndex(scope, id)) return this.searchLexical(scope, id, query, topK);
125536
+ try {
125537
+ return await searchIndex(indexDir, query, topK);
125538
+ } catch (error) {
125539
+ log155.warn(
125540
+ `[memory] semantic search failed for ${scope}/${id}; using lexical fallback: ${describeError3(error)}`
125541
+ );
125542
+ return this.searchLexical(scope, id, query, topK);
125543
+ }
125230
125544
  }
125231
125545
  /**
125232
125546
  * Like {@link search} but with a precomputed query embedding — lets the
@@ -125255,11 +125569,64 @@ var MemoryManager = class {
125255
125569
  return existsSync28(join103(this.store.memoryIndexDir(scope, id), "mem.db"));
125256
125570
  }
125257
125571
  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)
125572
+ return (await this.searchAllDetailed(gezelId, projectId, query, topK)).results;
125573
+ }
125574
+ /**
125575
+ * Search both scopes with one query embedding. Missing indexes fall back to
125576
+ * the daily Markdown source; an unavailable/corrupt semantic cache does the
125577
+ * same and reports a degraded mode instead of turning an optional feature
125578
+ * into an HTTP 500.
125579
+ */
125580
+ async searchAllDetailed(gezelId, projectId, query, topK = 10) {
125581
+ const gezelIndexed = this.hasIndex("gezel", gezelId);
125582
+ const projectIndexed = this.hasIndex("project", projectId);
125583
+ if (!gezelIndexed && !projectIndexed) {
125584
+ return {
125585
+ results: await this.searchAllLexical(gezelId, projectId, query, topK),
125586
+ mode: "lexical"
125587
+ };
125588
+ }
125589
+ try {
125590
+ const vector = await embedQuery(query);
125591
+ const [gezelResults, projectResults] = await Promise.all([
125592
+ gezelIndexed ? this.searchVector("gezel", gezelId, vector, topK) : this.searchLexical("gezel", gezelId, query, topK),
125593
+ projectIndexed ? this.searchVector("project", projectId, vector, topK) : this.searchLexical("project", projectId, query, topK)
125594
+ ]);
125595
+ return {
125596
+ results: rankSearchResults([...gezelResults, ...projectResults], topK),
125597
+ mode: gezelIndexed && projectIndexed ? "semantic" : "hybrid"
125598
+ };
125599
+ } catch (error) {
125600
+ log155.warn(
125601
+ `[memory] semantic search unavailable; using daily-memory lexical fallback: ${describeError3(error)}`
125602
+ );
125603
+ return {
125604
+ results: await this.searchAllLexical(gezelId, projectId, query, topK),
125605
+ mode: "lexical",
125606
+ degraded: {
125607
+ code: "semantic_search_unavailable",
125608
+ message: "Semantic memory search is temporarily unavailable; searched saved memory text directly instead."
125609
+ }
125610
+ };
125611
+ }
125612
+ }
125613
+ async searchAllLexical(gezelId, projectId, query, topK) {
125614
+ const [gezelResults, projectResults] = await Promise.all([
125615
+ this.searchLexical("gezel", gezelId, query, topK),
125616
+ this.searchLexical("project", projectId, query, topK)
125261
125617
  ]);
125262
- return [...agentResults, ...projectResults].sort((a, b) => b.score - a.score).slice(0, topK);
125618
+ return rankSearchResults([...gezelResults, ...projectResults], topK);
125619
+ }
125620
+ async searchLexical(scope, id, query, topK) {
125621
+ const entries = await this.allEntries(scope, id);
125622
+ 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 }) => ({
125623
+ text: entry.text,
125624
+ score,
125625
+ day: entry.day,
125626
+ scope: entry.scope,
125627
+ id: entry.id,
125628
+ kind: entry.kind ?? DEFAULT_MEMORY_KIND
125629
+ }));
125263
125630
  }
125264
125631
  async listDays(scope, id) {
125265
125632
  return this.store.listMemoryDays(scope, id);
@@ -125324,6 +125691,36 @@ var MemoryManager = class {
125324
125691
  return entries.length;
125325
125692
  }
125326
125693
  };
125694
+ function rankSearchResults(results, topK) {
125695
+ return results.sort((a, b) => b.score - a.score).slice(0, topK);
125696
+ }
125697
+ function deferredIndexOutcome() {
125698
+ return {
125699
+ status: "saved",
125700
+ indexed: false,
125701
+ degraded: {
125702
+ code: "semantic_index_unavailable",
125703
+ message: "Memory was saved, but semantic indexing is temporarily unavailable."
125704
+ }
125705
+ };
125706
+ }
125707
+ function lexicalScore(query, text) {
125708
+ const normalizedQuery = query.trim().toLocaleLowerCase();
125709
+ const normalizedText = text.toLocaleLowerCase();
125710
+ if (!normalizedQuery || !normalizedText) return 0;
125711
+ if (normalizedText.includes(normalizedQuery)) return 1;
125712
+ const queryTokens = new Set(normalizedQuery.match(/[\p{L}\p{N}_-]+/gu) ?? []);
125713
+ if (queryTokens.size === 0) return 0;
125714
+ const textTokens = new Set(normalizedText.match(/[\p{L}\p{N}_-]+/gu) ?? []);
125715
+ let matches = 0;
125716
+ for (const token of queryTokens) {
125717
+ if (textTokens.has(token)) matches++;
125718
+ }
125719
+ return matches / queryTokens.size;
125720
+ }
125721
+ function describeError3(error) {
125722
+ return error instanceof Error ? error.message : String(error);
125723
+ }
125327
125724
 
125328
125725
  // src/models/install-jobs.ts
125329
125726
  import { createLogger as createLogger166 } from "@bendyline/gezel";
@@ -128732,7 +129129,7 @@ var VideoModelManager = class {
128732
129129
  await rm43(finalPath, { force: true });
128733
129130
  await rename17(partialPath, finalPath);
128734
129131
  } catch (err) {
128735
- yield { type: "error", error: `rename failed for ${file.name}: ${describeError3(err)}` };
129132
+ yield { type: "error", error: `rename failed for ${file.name}: ${describeError4(err)}` };
128736
129133
  return;
128737
129134
  }
128738
129135
  }
@@ -128891,7 +129288,7 @@ var VideoModelSelector = class {
128891
129288
  return (id ? await this.models.resolveModel(id) : null) ?? await this.models.resolveDefaultModel();
128892
129289
  }
128893
129290
  };
128894
- function describeError3(err) {
129291
+ function describeError4(err) {
128895
129292
  return err instanceof Error ? err.message : String(err);
128896
129293
  }
128897
129294
  function isSafeId3(id) {
@@ -133222,6 +133619,7 @@ var TaskRunner = class {
133222
133619
  ...handoff.fromGezelName ? { fromGezelName: handoff.fromGezelName } : {},
133223
133620
  ...handoff.fromGezelId ? { fromGezelId: handoff.fromGezelId } : {},
133224
133621
  ...handoff.kind ? { kind: handoff.kind } : {},
133622
+ ...task.roleBasedNameOnlyMode !== void 0 ? { roleBasedNameOnlyMode: task.roleBasedNameOnlyMode } : {},
133225
133623
  lane,
133226
133624
  ...isNight ? { ambient: true } : {},
133227
133625
  ...isNight ? { nightShift: true } : {},
@@ -133735,6 +134133,10 @@ Step "${step.name}" (\`${step.id}\`) was re-driven ${maxRedrives}\xD7 without pr
133735
134133
  } catch {
133736
134134
  }
133737
134135
  const activeTasks = projectTasks.filter((t) => t.status === "active");
134136
+ if (projectTasks.length === 0) {
134137
+ trace("skip \u2014 project has no tasks to advance");
134138
+ return;
134139
+ }
133738
134140
  if (projectTasks.length > 0 && activeTasks.length === 0) {
133739
134141
  if (projectTasks.some((t) => t.status === "draft")) {
133740
134142
  trace("skip \u2014 only draft task(s) await activation; not nudging or stabilizing");
@@ -137476,7 +137878,7 @@ Address these, then the gate re-checks automatically.`,
137476
137878
  try {
137477
137879
  const existing = await tasks.listChildren(task.ref).catch(() => []);
137478
137880
  if (existing.length === 0) {
137479
- const raw = await store.readProjectWorkspaceFile(projectId, spawn14.overFile).catch(() => null);
137881
+ const raw = await (spawn14.overArtifact ? store.readProjectArtifact(projectId, spawn14.overFile) : store.readProjectWorkspaceFile(projectId, spawn14.overFile)).catch(() => null);
137480
137882
  const items = raw ? extractSpawnItems(raw, spawn14.itemsPath) : [];
137481
137883
  if (items.length === 0) {
137482
137884
  log188.warn(
@@ -137505,7 +137907,7 @@ Address these, then the gate re-checks automatically.`,
137505
137907
 
137506
137908
  ${kids.map((k) => `- ${k.ref}: ${k.title}`).join("\n")}
137507
137909
  `;
137508
- await store.writeProjectWorkspaceFile(projectId, advanceFile, manifest).catch((err) => log188.warn(`[fanout] ${task.ref}: could not write ${advanceFile}:`, err));
137910
+ 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
137911
  }
137510
137912
  const nextStep = newStep.advanceWhen?.goto ?? newStep.next;
137511
137913
  if (nextStep) {