dual-brain 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/dispatch.mjs CHANGED
@@ -15,6 +15,7 @@ import { createHash } from 'node:crypto';
15
15
  import { markHot, markDegraded, markHealthy, recordDispatch } from './health.mjs';
16
16
  import { redact } from './redact.mjs';
17
17
  import { getFailoverOrder } from './decide.mjs';
18
+ import { getTemplate, renderPrompt, quickRender } from './templates.mjs';
18
19
 
19
20
  const __dirname = dirname(fileURLToPath(import.meta.url));
20
21
  const USAGE_DIR = join(__dirname, '..', '.dualbrain', 'usage');
@@ -675,6 +676,30 @@ function runProcess(cmd, cwd, timeoutMs, env) {
675
676
  });
676
677
  }
677
678
 
679
+ // ─── Template-based prompt rendering ─────────────────────────────────────────
680
+
681
+ function _renderTemplatedPrompt(prompt, decision, context = {}) {
682
+ const tier = decision.tier ?? 'execute';
683
+ const template = getTemplate(tier);
684
+ if (!template) return prompt;
685
+
686
+ if (decision.contract) {
687
+ const rendered = renderPrompt(tier, decision.contract, context);
688
+ if (rendered.valid) return rendered.prompt;
689
+ }
690
+
691
+ const rendered = quickRender(tier, prompt, {
692
+ scope: decision.owns || decision.scope || [],
693
+ files: decision.files || [],
694
+ risk: decision.risk || 'medium',
695
+ criteria: decision.acceptanceCriteria || [],
696
+ nonGoals: decision.nonGoals || [],
697
+ context: decision.taskContext || '',
698
+ });
699
+
700
+ return rendered.valid ? rendered.prompt : prompt;
701
+ }
702
+
678
703
  // ─── Dispatch marker ─────────────────────────────────────────────────────────
679
704
  // Prepend a marker to every prompt that goes through the official dispatch pipeline.
680
705
  // The enforce-tier hook checks for this marker to distinguish legitimate dispatches
@@ -725,6 +750,11 @@ async function dispatch(input = {}) {
725
750
  // Safety gate: redact secrets before anything reaches a subprocess or log
726
751
  prompt = redact(prompt);
727
752
 
753
+ // ── Template-based prompt rendering ─────────────────────────────────────────
754
+ // When a tier and/or contract are present, render through templates.mjs for
755
+ // structured, typed prompts. Falls back to raw prompt when no template matches.
756
+ prompt = _renderTemplatedPrompt(prompt, decision);
757
+
728
758
  // ── Resume brief injection ───────────────────────────────────────────────────
729
759
  // Inject the last session's receipt as context when no situationBrief is already set.
730
760
  // This closes the receipt → brief → next session loop automatically.
@@ -738,15 +768,27 @@ async function dispatch(input = {}) {
738
768
  }
739
769
  } catch { /* non-blocking */ }
740
770
 
741
- // Continuity fallback: check handoff from continuity.mjs if still no brief
771
+ // Provider-aware continuity fallback: adapts resume format for target provider
742
772
  if (!input.situationBrief) {
743
773
  try {
744
- const { buildResumeBrief: buildHandoffBrief } = await import('./continuity.mjs');
745
- const handoffBrief = buildHandoffBrief(cwd);
746
- if (handoffBrief) {
747
- input = { ...input, situationBrief: handoffBrief };
774
+ const { buildProviderResumeBrief } = await import('./provider-context.mjs');
775
+ const targetProvider = decision.provider || 'claude';
776
+ const providerBrief = buildProviderResumeBrief(cwd, targetProvider);
777
+ if (providerBrief) {
778
+ input = { ...input, situationBrief: providerBrief };
748
779
  }
749
780
  } catch { /* non-blocking */ }
781
+
782
+ // Legacy fallback: continuity.mjs handoff (provider-unaware)
783
+ if (!input.situationBrief) {
784
+ try {
785
+ const { buildResumeBrief: buildHandoffBrief } = await import('./continuity.mjs');
786
+ const handoffBrief = buildHandoffBrief(cwd);
787
+ if (handoffBrief) {
788
+ input = { ...input, situationBrief: handoffBrief };
789
+ }
790
+ } catch { /* non-blocking */ }
791
+ }
750
792
  }
751
793
  }
752
794
  // ── End resume brief injection ───────────────────────────────────────────────