@wrongstack/cli 0.303.0 → 0.305.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,7 +29,7 @@ import {
29
29
  } from "./chunk-V3XH6XBJ.js";
30
30
  import {
31
31
  startCliHqConnection
32
- } from "./chunk-JW75HY4F.js";
32
+ } from "./chunk-FKWHSFX4.js";
33
33
  import {
34
34
  advanceToNextTask,
35
35
  findSpec,
@@ -117,7 +117,7 @@ import { gatedEnhancerReasoning } from "@wrongstack/core/execution";
117
117
  import { TOKENS as TOKENS9 } from "@wrongstack/core/kernel";
118
118
  import { ToolRegistry as ToolRegistry2 } from "@wrongstack/core/registry";
119
119
  import { getSessionRegistry } from "@wrongstack/core/storage";
120
- import { startSharedHeapWatchdog, writeErr as writeErr2 } from "@wrongstack/core/utils";
120
+ import { startSharedHeapWatchdog, writeErr as writeErr3 } from "@wrongstack/core/utils";
121
121
 
122
122
  // src/auth-menu/panel-service.ts
123
123
  var ANSI_RE = /\u001b\[[0-9;]*m/g;
@@ -900,6 +900,12 @@ function bindSystemPromptBuilder(deps) {
900
900
  // full-shape interfaces; the helper just needs the
901
901
  // passthrough.
902
902
  memoryStore: deps.memoryStore,
903
+ // Thread the narrow domain-term adapter so the builder emits a
904
+ // compact `[Project Jargon Dictionary]` block. `deps.domainGlossary`
905
+ // is an optional closure over the resolved SAGE `memoryStore`
906
+ // that returns only entries tagged `domain-term`; when omitted
907
+ // (e.g. in tests or subagent prompts) the builder emits no block.
908
+ domainGlossary: deps.domainGlossary,
903
909
  // SAGE's turn middleware is the single memory-injection channel.
904
910
  // Disable the builder's static "# Relevant Memory" section so memories
905
911
  // are injected once, per-turn, relevance-scored — not duplicated here.
@@ -2904,6 +2910,38 @@ import {
2904
2910
  } from "@wrongstack/core/types";
2905
2911
  import { wstackGlobalRoot } from "@wrongstack/core/utils";
2906
2912
 
2913
+ // src/services/dispatch-classifier.ts
2914
+ import { makeLLMClassifier } from "@wrongstack/core/coordination";
2915
+ function makeProviderClassifier(provider, model) {
2916
+ return makeLLMClassifier(async (prompt) => {
2917
+ const ctrl = new AbortController();
2918
+ const timeout = setTimeout(() => ctrl.abort(), 15e3);
2919
+ try {
2920
+ const resp = await provider.complete(
2921
+ {
2922
+ model,
2923
+ system: [
2924
+ {
2925
+ type: "text",
2926
+ text: 'You are an agent router. Choose the single best agent for the task. Reply with ONLY a compact JSON object {"role":"...","reason":"..."}.'
2927
+ }
2928
+ ],
2929
+ messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
2930
+ maxTokens: 120,
2931
+ temperature: 0
2932
+ },
2933
+ { signal: ctrl.signal }
2934
+ );
2935
+ const content = resp.content;
2936
+ return Array.isArray(content) ? content[0]?.text ?? "" : "";
2937
+ } catch {
2938
+ return "";
2939
+ } finally {
2940
+ clearTimeout(timeout);
2941
+ }
2942
+ });
2943
+ }
2944
+
2907
2945
  // src/fleet/host-acp.ts
2908
2946
  import {
2909
2947
  ACP_AGENT_COMMANDS,
@@ -3536,6 +3574,7 @@ function createHostStatusBroadcaster(input) {
3536
3574
  // src/fleet/host-learning.ts
3537
3575
  import {
3538
3576
  captureLearnedFromAgentOutputDetailed,
3577
+ recordDirectiveOutcomes,
3539
3578
  recordSkillOutcome
3540
3579
  } from "@wrongstack/core/agent-catalog";
3541
3580
  import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
@@ -3543,20 +3582,38 @@ function captureCompletedTaskLearningForHost(result, deps, subjects, onCaptured)
3543
3582
  const subject = subjects.get(result.subagentId);
3544
3583
  if (!subject) return;
3545
3584
  const logger = deps.container.safeResolve(TOKENS2.Logger);
3546
- if (subject.skills.length > 0) {
3585
+ const graded = result.status !== "stopped";
3586
+ const succeeded = result.status === "success";
3587
+ if (graded && subject.skills.length > 0) {
3547
3588
  try {
3548
- recordSkillOutcome(
3549
- subject.role,
3550
- subject.skills,
3551
- result.status === "success",
3552
- deps.projectRoot
3553
- );
3589
+ recordSkillOutcome(subject.role, subject.skills, succeeded, deps.projectRoot);
3554
3590
  } catch (error) {
3555
- logger?.debug?.(`skill affinity update failed for role "${subject.role}": ${describe(error)}`);
3591
+ logger?.debug?.(
3592
+ `skill affinity update failed for role "${subject.role}": ${describe(error)}`
3593
+ );
3556
3594
  }
3557
3595
  }
3558
3596
  const finalText = typeof result.result === "string" ? result.result : result.partial?.text;
3559
3597
  if (!finalText) return;
3598
+ if (graded) {
3599
+ try {
3600
+ const outcome = recordDirectiveOutcomes(subject.role, finalText, succeeded, deps.projectRoot);
3601
+ if (outcome.attributed > 0) {
3602
+ logger?.debug?.(
3603
+ `directive outcomes: ${outcome.attributed} applied for role "${subject.role}" (${succeeded ? "success" : "failure"})`
3604
+ );
3605
+ }
3606
+ for (const retired of outcome.quarantined) {
3607
+ logger?.info?.(
3608
+ `retired a directive for role "${subject.role}" after repeated failures: ${retired.slice(0, 120)}`
3609
+ );
3610
+ }
3611
+ } catch (error) {
3612
+ logger?.debug?.(
3613
+ `directive outcome update failed for role "${subject.role}": ${describe(error)}`
3614
+ );
3615
+ }
3616
+ }
3560
3617
  try {
3561
3618
  const capture = captureLearnedFromAgentOutputDetailed(
3562
3619
  finalText,
@@ -3636,6 +3693,84 @@ function applyFleetRootDefaults(opts) {
3636
3693
  }
3637
3694
  }
3638
3695
 
3696
+ // src/fleet/host-provider.ts
3697
+ import { makeProviderFromConfig, withCatalogCapabilities } from "@wrongstack/providers";
3698
+ async function buildHostSubagentProvider(deps, config, overrideId, model) {
3699
+ const requestedProviderId = overrideId ?? config.provider;
3700
+ const providerId = requestedProviderId === config.provider || config.providers?.[requestedProviderId] !== void 0 || deps.providerRegistry.has(requestedProviderId) ? requestedProviderId : config.provider;
3701
+ const newCfg = config.providers?.[providerId] ?? {
3702
+ type: providerId,
3703
+ apiKey: config.apiKey,
3704
+ baseUrl: config.baseUrl
3705
+ };
3706
+ const cfgWithType = {
3707
+ ...newCfg,
3708
+ type: providerId,
3709
+ ...model ? { model } : {}
3710
+ };
3711
+ let provider = deps.providerRegistry.has(providerId) ? deps.providerRegistry.create(cfgWithType) : makeProviderFromConfig(providerId, cfgWithType);
3712
+ if (deps.modelsRegistry) {
3713
+ const resolvedModel = model ?? config.model;
3714
+ provider = await withCatalogCapabilities(deps.modelsRegistry, providerId, provider, {
3715
+ ...cfgWithType,
3716
+ model: resolvedModel
3717
+ });
3718
+ await refreshRuntimeModelCatalog({
3719
+ modelsRegistry: deps.modelsRegistry,
3720
+ reason: `${providerId}/${resolvedModel}`
3721
+ });
3722
+ const mc = await resolveRuntimeMaxContext({
3723
+ modelsRegistry: deps.modelsRegistry,
3724
+ config,
3725
+ provider,
3726
+ runtimeProviderConfig: cfgWithType,
3727
+ providerId,
3728
+ modelId: resolvedModel
3729
+ });
3730
+ if (mc && mc > 0) provider.capabilities.maxContext = mc;
3731
+ }
3732
+ return provider;
3733
+ }
3734
+ async function resolveHostSubagentReasoningConfig(deps, providerId, modelId) {
3735
+ if (!deps.modelsRegistry) return void 0;
3736
+ try {
3737
+ return (await deps.modelsRegistry.getModel(providerId, modelId))?.capabilities.reasoningConfig;
3738
+ } catch {
3739
+ return void 0;
3740
+ }
3741
+ }
3742
+ function resolveHostSubagentModelSelection(liveConfig, effectiveCfg, matrixTarget) {
3743
+ let effProvider = effectiveCfg.provider ?? matrixTarget?.provider ?? liveConfig.provider;
3744
+ let effModel = effectiveCfg.model ?? matrixTarget?.model ?? liveConfig.model;
3745
+ const modelPolicy = effectiveCfg.modelPolicy;
3746
+ const closedModelPolicy = modelPolicy?.strict === true;
3747
+ const allowedModels = modelPolicy?.allowed ?? [];
3748
+ if (modelPolicy && !allowedModels.some((target) => target.provider === effProvider && target.model === effModel)) {
3749
+ const firstAllowed = allowedModels[0];
3750
+ if (!firstAllowed) throw new Error(`Agent "${effectiveCfg.role}" has no allowed models.`);
3751
+ effProvider = firstAllowed.provider;
3752
+ effModel = firstAllowed.model;
3753
+ }
3754
+ const fallbackProfile = modelPolicy ? void 0 : effectiveCfg.fallbackProfile ?? matrixTarget?.fallbackProfile;
3755
+ const runtimeOverride = effectiveCfg.modelRuntime ?? matrixTarget?.modelRuntime;
3756
+ const startupTargets = modelPolicy ? [
3757
+ { provider: effProvider, model: effModel },
3758
+ ...modelPolicy.fallbacks ?? [],
3759
+ ...closedModelPolicy || effProvider === liveConfig.provider && effModel === liveConfig.model ? [] : [{ provider: liveConfig.provider, model: liveConfig.model }]
3760
+ ] : [
3761
+ { provider: effProvider, model: effModel },
3762
+ ...effProvider === liveConfig.provider && effModel === liveConfig.model ? [] : [{ provider: liveConfig.provider, model: liveConfig.model }]
3763
+ ];
3764
+ return {
3765
+ effProvider,
3766
+ effModel,
3767
+ fallbackProfile,
3768
+ runtimeOverride,
3769
+ closedModelPolicy,
3770
+ startupTargets
3771
+ };
3772
+ }
3773
+
3639
3774
  // src/fleet/host-shadow-pass.ts
3640
3775
  async function runHostShadowPass(ctx, reason) {
3641
3776
  try {
@@ -3801,84 +3936,6 @@ function aggregateFleetUsage(completed) {
3801
3936
  return { rows, totals };
3802
3937
  }
3803
3938
 
3804
- // src/fleet/host-provider.ts
3805
- import { makeProviderFromConfig, withCatalogCapabilities } from "@wrongstack/providers";
3806
- async function buildHostSubagentProvider(deps, config, overrideId, model) {
3807
- const requestedProviderId = overrideId ?? config.provider;
3808
- const providerId = requestedProviderId === config.provider || config.providers?.[requestedProviderId] !== void 0 || deps.providerRegistry.has(requestedProviderId) ? requestedProviderId : config.provider;
3809
- const newCfg = config.providers?.[providerId] ?? {
3810
- type: providerId,
3811
- apiKey: config.apiKey,
3812
- baseUrl: config.baseUrl
3813
- };
3814
- const cfgWithType = {
3815
- ...newCfg,
3816
- type: providerId,
3817
- ...model ? { model } : {}
3818
- };
3819
- let provider = deps.providerRegistry.has(providerId) ? deps.providerRegistry.create(cfgWithType) : makeProviderFromConfig(providerId, cfgWithType);
3820
- if (deps.modelsRegistry) {
3821
- const resolvedModel = model ?? config.model;
3822
- provider = await withCatalogCapabilities(deps.modelsRegistry, providerId, provider, {
3823
- ...cfgWithType,
3824
- model: resolvedModel
3825
- });
3826
- await refreshRuntimeModelCatalog({
3827
- modelsRegistry: deps.modelsRegistry,
3828
- reason: `${providerId}/${resolvedModel}`
3829
- });
3830
- const mc = await resolveRuntimeMaxContext({
3831
- modelsRegistry: deps.modelsRegistry,
3832
- config,
3833
- provider,
3834
- runtimeProviderConfig: cfgWithType,
3835
- providerId,
3836
- modelId: resolvedModel
3837
- });
3838
- if (mc && mc > 0) provider.capabilities.maxContext = mc;
3839
- }
3840
- return provider;
3841
- }
3842
- async function resolveHostSubagentReasoningConfig(deps, providerId, modelId) {
3843
- if (!deps.modelsRegistry) return void 0;
3844
- try {
3845
- return (await deps.modelsRegistry.getModel(providerId, modelId))?.capabilities.reasoningConfig;
3846
- } catch {
3847
- return void 0;
3848
- }
3849
- }
3850
- function resolveHostSubagentModelSelection(liveConfig, effectiveCfg, matrixTarget) {
3851
- let effProvider = effectiveCfg.provider ?? matrixTarget?.provider ?? liveConfig.provider;
3852
- let effModel = effectiveCfg.model ?? matrixTarget?.model ?? liveConfig.model;
3853
- const modelPolicy = effectiveCfg.modelPolicy;
3854
- const closedModelPolicy = modelPolicy?.strict === true;
3855
- const allowedModels = modelPolicy?.allowed ?? [];
3856
- if (modelPolicy && !allowedModels.some((target) => target.provider === effProvider && target.model === effModel)) {
3857
- const firstAllowed = allowedModels[0];
3858
- if (!firstAllowed) throw new Error(`Agent "${effectiveCfg.role}" has no allowed models.`);
3859
- effProvider = firstAllowed.provider;
3860
- effModel = firstAllowed.model;
3861
- }
3862
- const fallbackProfile = modelPolicy ? void 0 : effectiveCfg.fallbackProfile ?? matrixTarget?.fallbackProfile;
3863
- const runtimeOverride = effectiveCfg.modelRuntime ?? matrixTarget?.modelRuntime;
3864
- const startupTargets = modelPolicy ? [
3865
- { provider: effProvider, model: effModel },
3866
- ...modelPolicy.fallbacks ?? [],
3867
- ...closedModelPolicy || effProvider === liveConfig.provider && effModel === liveConfig.model ? [] : [{ provider: liveConfig.provider, model: liveConfig.model }]
3868
- ] : [
3869
- { provider: effProvider, model: effModel },
3870
- ...effProvider === liveConfig.provider && effModel === liveConfig.model ? [] : [{ provider: liveConfig.provider, model: liveConfig.model }]
3871
- ];
3872
- return {
3873
- effProvider,
3874
- effModel,
3875
- fallbackProfile,
3876
- runtimeOverride,
3877
- closedModelPolicy,
3878
- startupTargets
3879
- };
3880
- }
3881
-
3882
3939
  // src/fleet/host-subagent-factory.ts
3883
3940
  import { randomUUID as randomUUID2 } from "node:crypto";
3884
3941
  import { existsSync, statSync } from "node:fs";
@@ -3914,6 +3971,7 @@ import { AutoApprovePermissionPolicy } from "@wrongstack/core/security";
3914
3971
 
3915
3972
  // src/fleet/host-context.ts
3916
3973
  import {
3974
+ DEFAULT_EAGER_SKILL_LIMIT,
3917
3975
  loadProjectSkillAugmentation,
3918
3976
  missingRequiredRuntimeTools,
3919
3977
  missingRuntimeCapabilities,
@@ -3924,7 +3982,8 @@ import {
3924
3982
  } from "@wrongstack/core/agent-catalog";
3925
3983
  import { TOKENS as TOKENS3 } from "@wrongstack/core/kernel";
3926
3984
  import { getSageRetrieval } from "@wrongstack/sage";
3927
- var EAGER_SKILL_LIMIT = 3;
3985
+ var EAGER_SKILL_LIMIT = DEFAULT_EAGER_SKILL_LIMIT;
3986
+ var MIN_TRIMMED_BODY_CHARS = 800;
3928
3987
  async function resolveHostSubagentSkillResolution(deps, roster, subCfg, availableToolNames = []) {
3929
3988
  const role = subCfg.role;
3930
3989
  const rosterEntry = role ? roster[role] : void 0;
@@ -3940,10 +3999,11 @@ async function resolveHostSubagentSkillResolution(deps, roster, subCfg, availabl
3940
3999
  ];
3941
4000
  const skillNames = role ? rankRoleSkills(role, pool, deps.projectRoot, EAGER_SKILL_LIMIT) : pool.slice(0, EAGER_SKILL_LIMIT);
3942
4001
  if (skillNames.length === 0 || !deps.skillLoader) {
3943
- return { content: directContent ?? "", selected: [], dropped };
4002
+ return { content: directContent ?? "", selected: [], dropped, trimmed: [] };
3944
4003
  }
3945
4004
  const resolved = [];
3946
4005
  const selected = [];
4006
+ const trimmed = [];
3947
4007
  let usedChars = 0;
3948
4008
  const maxChars = 16e3;
3949
4009
  const maxCharsPerSkill = 4e3;
@@ -3972,10 +4032,12 @@ async function resolveHostSubagentSkillResolution(deps, roster, subCfg, availabl
3972
4032
  continue;
3973
4033
  }
3974
4034
  const augmentation = role ? loadProjectSkillAugmentation(role, skillName, deps.projectRoot) : "";
3975
- const entry = [
4035
+ const compose = (bodyChars) => [
3976
4036
  `## Skill: ${skillName}`,
3977
4037
  "",
3978
- body.slice(0, maxCharsPerSkill),
4038
+ body.length > bodyChars ? `${body.slice(0, bodyChars).trimEnd()}
4039
+
4040
+ _(body trimmed)_` : body,
3979
4041
  ...augmentation ? [
3980
4042
  "",
3981
4043
  `### Project practice for \`${skillName}\``,
@@ -3985,9 +4047,16 @@ async function resolveHostSubagentSkillResolution(deps, roster, subCfg, availabl
3985
4047
  augmentation
3986
4048
  ] : []
3987
4049
  ].join("\n");
4050
+ let entry = compose(maxCharsPerSkill);
3988
4051
  if (usedChars + entry.length > maxChars) {
3989
- dropped[skillName] = "budget";
3990
- continue;
4052
+ const overhead = entry.length - Math.min(body.length, maxCharsPerSkill);
4053
+ const room = maxChars - usedChars - overhead;
4054
+ if (!augmentation || room < MIN_TRIMMED_BODY_CHARS) {
4055
+ dropped[skillName] = "budget";
4056
+ continue;
4057
+ }
4058
+ entry = compose(room);
4059
+ trimmed.push(skillName);
3991
4060
  }
3992
4061
  resolved.push(entry);
3993
4062
  selected.push(skillName);
@@ -4010,7 +4079,7 @@ Apply these skills first for this assignment.
4010
4079
 
4011
4080
  ${resolved.join("\n\n---\n\n")}` : void 0
4012
4081
  ].filter((section) => Boolean(section));
4013
- return { content: sections.join("\n\n"), selected, dropped };
4082
+ return { content: sections.join("\n\n"), selected, dropped, trimmed };
4014
4083
  }
4015
4084
  async function retrieveHostSubagentMemory(deps, getLeaderMode, subCfg, taskContext) {
4016
4085
  const memoryPort = deps.container.safeResolve(TOKENS3.MemoryStore);
@@ -4222,10 +4291,8 @@ function createParentSubagentSessionWriter(parentSession) {
4222
4291
  flush: () => parentSession.flush(),
4223
4292
  close: async () => {
4224
4293
  },
4225
- recordFileChange: () => {
4226
- },
4227
- recordSideEffect: () => {
4228
- },
4294
+ recordFileChange: (input) => parentSession.recordFileChange(input),
4295
+ recordSideEffect: (input) => parentSession.recordSideEffect(input),
4229
4296
  writeCheckpoint: async () => {
4230
4297
  },
4231
4298
  writeFileSnapshot: async () => {
@@ -4239,6 +4306,21 @@ function createParentSubagentSessionWriter(parentSession) {
4239
4306
  }
4240
4307
  };
4241
4308
  }
4309
+ function withParentFileSnapshots(subagentSession, parentSession) {
4310
+ if (subagentSession === parentSession) return subagentSession;
4311
+ return new Proxy(subagentSession, {
4312
+ get(target, property, receiver) {
4313
+ if (property === "recordFileChange") {
4314
+ return (input) => {
4315
+ target.recordFileChange(input);
4316
+ parentSession.recordFileChange(input);
4317
+ };
4318
+ }
4319
+ const value = Reflect.get(target, property, receiver);
4320
+ return typeof value === "function" ? value.bind(target) : value;
4321
+ }
4322
+ });
4323
+ }
4242
4324
 
4243
4325
  // src/fleet/host-subagent-factory.ts
4244
4326
  function createHostSubagentFactory(config, host) {
@@ -4370,12 +4452,13 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
4370
4452
  baseSystem.push({ type: "text", text: skillResolution.content });
4371
4453
  }
4372
4454
  const droppedSkills = Object.entries(skillResolution.dropped);
4373
- if (droppedSkills.length > 0) {
4455
+ if (droppedSkills.length > 0 || skillResolution.trimmed.length > 0) {
4374
4456
  host.deps.events.emit("subagent.skills.dropped", {
4375
4457
  sessionId: host.deps.session.id,
4376
4458
  role: effectiveCfg.role,
4377
4459
  selected: skillResolution.selected,
4378
- dropped: Object.fromEntries(droppedSkills)
4460
+ dropped: Object.fromEntries(droppedSkills),
4461
+ ...skillResolution.trimmed.length > 0 ? { trimmed: skillResolution.trimmed } : {}
4379
4462
  });
4380
4463
  }
4381
4464
  const rawRolePrompt = effectiveCfg.systemPromptOverride ?? effectiveCfg.prompt ?? (effectiveCfg.role ? host.roster[effectiveCfg.role]?.prompt : void 0);
@@ -4391,12 +4474,15 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
4391
4474
  }
4392
4475
  let subSession;
4393
4476
  if (host.sessionFactory) {
4394
- subSession = await host.sessionFactory.createSubagentSession({
4395
- subagentId: subagentName,
4396
- provider: effProvider,
4397
- model: effModel,
4398
- title: `subagent: ${subagentName}`
4399
- });
4477
+ subSession = withParentFileSnapshots(
4478
+ await host.sessionFactory.createSubagentSession({
4479
+ subagentId: subagentName,
4480
+ provider: effProvider,
4481
+ model: effModel,
4482
+ title: `subagent: ${subagentName}`
4483
+ }),
4484
+ host.deps.session
4485
+ );
4400
4486
  } else {
4401
4487
  subSession = createParentSubagentSessionWriter(host.deps.session);
4402
4488
  }
@@ -4445,7 +4531,11 @@ ${audienceMemory.map((text) => `- ${text}`).join("\n")}`
4445
4531
  maxToolTimeoutMs: config.tools?.maxToolTimeoutMs ?? 3e5,
4446
4532
  perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 1e5,
4447
4533
  tracer: void 0,
4448
- requireKanbanGovernance: true
4534
+ // Kanban tracks work; it does not gate it. Off unless the operator opts
4535
+ // in — and then subagents inherit it, because a worker dispatched by
4536
+ // `kanban_queue` already carries board/task/lease identity in ctx.meta
4537
+ // and can satisfy the same gate its leader was held to.
4538
+ requireKanbanGovernance: config.tools?.kanbanGovernance ?? false
4449
4539
  });
4450
4540
  const subagentConfigStore = host.deps.configStore;
4451
4541
  const pipelines = createDefaultPipelines();
@@ -4836,6 +4926,12 @@ var MultiAgentHost = class {
4836
4926
  // pass so director.fleetManager is never undefined
4837
4927
  brain: this.opts.brain,
4838
4928
  roster: this.roster,
4929
+ // The dispatcher is two-stage by design — keyword heuristic, then a model
4930
+ // to break the tie — but nothing ever supplied stage two here, so every
4931
+ // description the keywords could not resolve fell through to the
4932
+ // `executor` generalist. That is one half of why a 77-role roster was
4933
+ // being served by a handful of agents.
4934
+ dispatchClassifier: (task, candidates) => this.classifyDispatch(task, candidates),
4839
4935
  // Fire-and-forget report-back: when an assign_task completes with no
4840
4936
  // pending await, post the result to this session's leader via the
4841
4937
  // project mailbox (injected inline before the leader's next step).
@@ -5324,6 +5420,28 @@ var MultiAgentHost = class {
5324
5420
  }
5325
5421
  return this.learningOptimizer;
5326
5422
  }
5423
+ /**
5424
+ * Break a dispatch tie with a model when the keyword heuristic is ambiguous.
5425
+ *
5426
+ * Routes through the `dispatcher` model-matrix slot when one is configured —
5427
+ * picking a role is a short classification, so it belongs on a cheap fast
5428
+ * model rather than the leader's — and the session default otherwise.
5429
+ * Returning `null` on any failure leaves the dispatcher on its heuristic
5430
+ * result, so routing degrades rather than breaking a spawn.
5431
+ */
5432
+ async classifyDispatch(task, candidates) {
5433
+ try {
5434
+ const config = this.deps.configStore.get();
5435
+ const target = resolveSubagentModelTarget2(config, "dispatcher");
5436
+ const providerId = target?.provider ?? config.provider;
5437
+ const model = target?.model ?? config.model;
5438
+ if (!providerId || !model) return null;
5439
+ const provider = await buildHostSubagentProvider(this.deps, config, providerId, model);
5440
+ return await makeProviderClassifier(provider, model)(task, candidates);
5441
+ } catch {
5442
+ return null;
5443
+ }
5444
+ }
5327
5445
  /**
5328
5446
  * Resolve a model for the distillation pass. Uses the `memory-curator` slot
5329
5447
  * of the model matrix when one is configured — curating learned knowledge is
@@ -6417,6 +6535,58 @@ function setupDirectorAndAutonomy(deps) {
6417
6535
  };
6418
6536
  }
6419
6537
 
6538
+ // src/wiring/domain-glossary.ts
6539
+ import { getSageService } from "@wrongstack/sage";
6540
+ var DOMAIN_GLOSSARY_LIMIT = 16;
6541
+ function createDomainGlossaryAdapter(memoryStore) {
6542
+ return {
6543
+ async list(scope, limit) {
6544
+ try {
6545
+ const service = getSageService(memoryStore);
6546
+ if (!service) return [];
6547
+ if (scope !== "project-memory") return [];
6548
+ const effectiveLimit = Math.min(
6549
+ Math.max(1, limit ?? DOMAIN_GLOSSARY_LIMIT),
6550
+ DOMAIN_GLOSSARY_LIMIT
6551
+ );
6552
+ const hits = await service.searchSage("domain-term", { limit: effectiveLimit });
6553
+ return hits.map((hit) => sageToGlossaryEntry(hit));
6554
+ } catch {
6555
+ return [];
6556
+ }
6557
+ }
6558
+ };
6559
+ }
6560
+ function sageToGlossaryEntry(sage) {
6561
+ const priority = sage.importance >= 0.9 ? "critical" : sage.importance >= 0.75 ? "high" : sage.importance >= 0.4 ? "medium" : "low";
6562
+ return {
6563
+ scope: "project-memory",
6564
+ text: sage.text,
6565
+ ts: sage.updatedAt || sage.createdAt,
6566
+ type: "reference",
6567
+ tags: sage.tags.slice(),
6568
+ priority,
6569
+ confidence: sage.confidence,
6570
+ lastAccessed: sage.lastAccessedAt
6571
+ };
6572
+ }
6573
+
6574
+ // src/wiring/domain-terms-mirror.ts
6575
+ import { writeErr as writeErr2 } from "@wrongstack/core/utils";
6576
+ import { SageDomainTermExtractor } from "@wrongstack/sage";
6577
+ async function refreshDomainTermsMirror(options) {
6578
+ try {
6579
+ const extractor = new SageDomainTermExtractor();
6580
+ return await extractor.writeDomainTermsFile(options.projectRoot, options.memoryStore);
6581
+ } catch (err) {
6582
+ writeErr2(
6583
+ `[wrongstack] domain-terms mirror refresh failed: ${err instanceof Error ? err.message : String(err)}
6584
+ `
6585
+ );
6586
+ return null;
6587
+ }
6588
+ }
6589
+
6420
6590
  // src/wiring/eternal-command-handlers.ts
6421
6591
  import { EternalAutonomyEngine as EternalAutonomyEngine2, ParallelEternalEngine } from "@wrongstack/core/execution";
6422
6592
  function createEternalCommandHandlers(input) {
@@ -7750,7 +7920,20 @@ function createAgent(params) {
7750
7920
  tracer: params.tracer,
7751
7921
  logger,
7752
7922
  hookRunner: params.hookRunner,
7753
- requireKanbanGovernance: true
7923
+ // Kanban tracks work; it does not gate it. When this was hard-wired `true`
7924
+ // a mutating tool was refused until a managed card existed, carried a
7925
+ // description and acceptance criteria, and had been started — so the
7926
+ // session spent its effort on card ceremony instead of the work the card
7927
+ // describes. Cards still advance and boards still mirror; path-scoped
7928
+ // `boundary` policies (the actual access control) are unaffected and still
7929
+ // enforced.
7930
+ //
7931
+ // It stays OFF by default, but is now an operator switch rather than a
7932
+ // literal: `tools.kanbanGovernance: true` opts an installation in. The
7933
+ // four hosts (this pipeline, mcp-serve, acp-server-agent, and the fleet
7934
+ // subagent factory) must resolve it identically, or a subagent would run
7935
+ // under a different contract than the leader that dispatched it.
7936
+ requireKanbanGovernance: params.config.tools.kanbanGovernance ?? false
7754
7937
  };
7755
7938
  const toolExecutor = new ToolExecutor2(params.tools, toolExecutorOptions);
7756
7939
  void bootstrapMailboxBridgeAtStartup({
@@ -7771,6 +7954,7 @@ function createAgent(params) {
7771
7954
  events: params.events,
7772
7955
  pipelines: params.pipelines,
7773
7956
  context: params.context,
7957
+ refreshSystemPrompt: true,
7774
7958
  maxIterations: params.config.tools.maxIterations,
7775
7959
  iterationTimeoutMs: params.config.tools.iterationTimeoutMs,
7776
7960
  executionStrategy: params.config.tools.defaultExecutionStrategy,
@@ -8262,7 +8446,7 @@ import {
8262
8446
  watchProviderConfig
8263
8447
  } from "@wrongstack/core/storage";
8264
8448
  import { withCatalogCapabilities as withCatalogCapabilities2 } from "@wrongstack/providers";
8265
- import { getSageService } from "@wrongstack/sage";
8449
+ import { getSageService as getSageService2 } from "@wrongstack/sage";
8266
8450
 
8267
8451
  // src/wiring/fallback-gate.ts
8268
8452
  function createFallbackGate(_events) {
@@ -8401,7 +8585,7 @@ function setupProviderRuntime(deps) {
8401
8585
  })
8402
8586
  );
8403
8587
  if (cfg.features.memory && cfg.features.memoryConsolidation !== false) {
8404
- const consSage = getSageService(memoryStore);
8588
+ const consSage = getSageService2(memoryStore);
8405
8589
  agent.extensions.register(
8406
8590
  new SessionMemoryConsolidator({
8407
8591
  memoryStore,
@@ -8818,88 +9002,11 @@ async function setupCodebaseIndexing(deps) {
8818
9002
 
8819
9003
  // src/wiring/sage.ts
8820
9004
  import {
8821
- createSageContextMonitorMiddleware,
8822
- createSageToolCallMiddleware,
8823
- createSageTurnMiddleware,
8824
- getSageRetrieval as getSageRetrieval2,
8825
- InjectionTracker
9005
+ AUTO_HYGIENE_INTERVAL_MS,
9006
+ _resetAutoHygieneThrottleForTesting,
9007
+ sageHygieneOptionsFromConfig,
9008
+ setupSage
8826
9009
  } from "@wrongstack/sage";
8827
- var lastAutoHygieneAt = 0;
8828
- var AUTO_HYGIENE_INTERVAL_MS = 60 * 6e4;
8829
- function setupSage(deps) {
8830
- const cfg = deps.config.Sage;
8831
- const noop = async () => {
8832
- };
8833
- if (deps.config.features.memory === false) return noop;
8834
- if (cfg?.enabled === false) return noop;
8835
- if (!deps.memoryStore) return noop;
8836
- const memoryStore = deps.memoryStore;
8837
- const retrieval = getSageRetrieval2(memoryStore);
8838
- if (!retrieval) {
8839
- deps.logger.debug("sage middleware skipped: memory store does not support retrieval");
8840
- return noop;
8841
- }
8842
- const injectionTracker = new InjectionTracker();
8843
- if (cfg?.inject?.toolResults !== false) {
8844
- deps.pipelines.toolCall.use(
8845
- createSageToolCallMiddleware({
8846
- memory: retrieval,
8847
- maxHintsPerTool: cfg?.inject?.maxHintsPerTool,
8848
- maxCharsPerTool: cfg?.inject?.maxCharsPerTool,
8849
- taskAware: cfg?.inject?.taskAware,
8850
- minScore: cfg?.inject?.minScore,
8851
- minImportance: cfg?.inject?.minImportance,
8852
- relationFloor: cfg?.inject?.relationFloor,
8853
- repeatCooldownMs: cfg?.inject?.repeatCooldownMs,
8854
- verifyOnMutation: cfg?.hygiene?.autoOnFileChange,
8855
- triggers: cfg?.inject?.triggers,
8856
- tracker: injectionTracker,
8857
- events: deps.events,
8858
- getSessionId: deps.getSessionId
8859
- })
8860
- );
8861
- }
8862
- if (cfg?.inject?.turnContext === true) {
8863
- deps.pipelines.request.use(
8864
- createSageTurnMiddleware({
8865
- memory: retrieval,
8866
- maxMemories: cfg?.inject?.maxTurnMemories,
8867
- maxChars: cfg?.inject?.maxCharsPerTurn,
8868
- minScore: cfg?.inject?.minScore,
8869
- metadataWeight: cfg?.retrieval?.metadataWeight,
8870
- tracker: injectionTracker,
8871
- getSessionId: deps.getSessionId
8872
- })
8873
- );
8874
- }
8875
- deps.pipelines.request.use(
8876
- createSageContextMonitorMiddleware({
8877
- tracker: injectionTracker,
8878
- events: deps.events,
8879
- getSessionId: deps.getSessionId
8880
- })
8881
- );
8882
- return async () => {
8883
- await retrieval.flushPendingCounters?.();
8884
- if (cfg?.hygiene?.autoAfterSession === false) return;
8885
- const now = Date.now();
8886
- if (now - lastAutoHygieneAt < AUTO_HYGIENE_INTERVAL_MS) {
8887
- deps.logger.debug(
8888
- `sage auto-hygiene skipped: last run ${Math.round((now - lastAutoHygieneAt) / 1e3)}s ago (throttle: ${AUTO_HYGIENE_INTERVAL_MS / 1e3}s)`
8889
- );
8890
- return;
8891
- }
8892
- await memoryStore.hygiene?.({
8893
- retentionDays: cfg?.hygiene?.retentionDays,
8894
- sessionRetentionDays: cfg?.hygiene?.sessionRetentionDays,
8895
- archiveLowConfidenceAfterDays: cfg?.hygiene?.archiveLowConfidenceAfterDays,
8896
- archiveUnusedAfterDays: cfg?.hygiene?.archiveUnusedAfterDays,
8897
- unusedMinInjections: cfg?.hygiene?.unusedMinInjections,
8898
- purgeDeletedAfterDays: cfg?.hygiene?.purgeDeletedAfterDays
8899
- });
8900
- lastAutoHygieneAt = now;
8901
- };
8902
- }
8903
9010
 
8904
9011
  // src/wiring/runtime-dispatch-state.ts
8905
9012
  async function prepareRuntimeDispatch(input) {
@@ -8909,7 +9016,8 @@ async function prepareRuntimeDispatch(input) {
8909
9016
  memoryStore: input.memoryStore,
8910
9017
  logger: input.logger,
8911
9018
  events: input.events,
8912
- getSessionId: () => input.agent.ctx.session.id
9019
+ getSessionId: () => input.agent.ctx.session.id,
9020
+ projectRoot: input.projectRoot
8913
9021
  });
8914
9022
  const disposeIndexing = await setupCodebaseIndexing({
8915
9023
  config: input.getConfig(),
@@ -8954,6 +9062,7 @@ async function prepareRuntimeDispatch(input) {
8954
9062
  owner,
8955
9063
  category: tool.category ?? "Other",
8956
9064
  enabled: !input.toolRegistry.isDisabled(tool.name),
9065
+ exposure: input.toolRegistry.isDisabled(tool.name) ? "disabled" : input.toolRegistry.isExposedToProvider(tool.name) ? "direct" : "lazy",
8957
9066
  mutating: tool.mutating,
8958
9067
  permission: tool.permission,
8959
9068
  descMode: getToolDescriptionMode(input.toolRegistry, tool.name),
@@ -9688,38 +9797,6 @@ ${diff}`;
9688
9797
  return "chore: update";
9689
9798
  }
9690
9799
 
9691
- // src/services/dispatch-classifier.ts
9692
- import { makeLLMClassifier } from "@wrongstack/core/coordination";
9693
- function makeProviderClassifier(provider, model) {
9694
- return makeLLMClassifier(async (prompt) => {
9695
- const ctrl = new AbortController();
9696
- const timeout = setTimeout(() => ctrl.abort(), 15e3);
9697
- try {
9698
- const resp = await provider.complete(
9699
- {
9700
- model,
9701
- system: [
9702
- {
9703
- type: "text",
9704
- text: 'You are an agent router. Choose the single best agent for the task. Reply with ONLY a compact JSON object {"role":"...","reason":"..."}.'
9705
- }
9706
- ],
9707
- messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
9708
- maxTokens: 120,
9709
- temperature: 0
9710
- },
9711
- { signal: ctrl.signal }
9712
- );
9713
- const content = resp.content;
9714
- return Array.isArray(content) ? content[0]?.text ?? "" : "";
9715
- } catch {
9716
- return "";
9717
- } finally {
9718
- clearTimeout(timeout);
9719
- }
9720
- });
9721
- }
9722
-
9723
9800
  // src/services/mcp-management.ts
9724
9801
  import {
9725
9802
  color as color8,
@@ -10681,10 +10758,17 @@ ${lines.join("\n")}`;
10681
10758
  const learned = loadProjectAgentLearned(role, projectRoot);
10682
10759
  const kn = loadRoleKnowledgeManifest(role, projectRoot);
10683
10760
  const developedSkills = listProjectSkillAugmentations(role, projectRoot);
10761
+ const stats = getProjectAgentLearnStats(role, projectRoot);
10684
10762
  const lines = [`${color11.bold(role)} project identity:`];
10685
10763
  if (developedSkills.length > 0) {
10686
10764
  lines.push(` skills developed: ${developedSkills.join(", ")}`);
10687
10765
  }
10766
+ if (stats.entryCount > 0) {
10767
+ const hitRate = stats.directiveHitRate === null ? "no directive exercised yet" : `${Math.round(stats.directiveHitRate * 100)}% hit rate over ${stats.appliedEntryCount} exercised`;
10768
+ lines.push(
10769
+ ` directives: ${stats.entryCount} buffered \xB7 ${hitRate} \xB7 ${stats.deadEntryCount} never used`
10770
+ );
10771
+ }
10688
10772
  if (cfg) lines.push(` config: ${JSON.stringify(cfg)}`);
10689
10773
  if (kn)
10690
10774
  lines.push(
@@ -12886,6 +12970,7 @@ var SOURCE_LABELS = {
12886
12970
  "leader-after-task": "leader-after-task (post-tool summary)",
12887
12971
  contributor: "contributor",
12888
12972
  ledger: "completed-work ledger",
12973
+ glossary: "project jargon dictionary",
12889
12974
  nextsteps: "next-steps gate",
12890
12975
  other: "other (untagged)"
12891
12976
  };
@@ -13697,6 +13782,8 @@ var KNOWN_TOP_LEVEL_KEYS = [
13697
13782
  "fallbackBridge",
13698
13783
  "fallbackProfiles",
13699
13784
  "fallbackAuto",
13785
+ "fallbackMaxLastResortCandidates",
13786
+ "fallbackStickiness",
13700
13787
  "hooks",
13701
13788
  "plugins",
13702
13789
  "pluginManager",
@@ -13842,6 +13929,37 @@ function diagnoseConfig(cfg, plugins = []) {
13842
13929
  }
13843
13930
  }
13844
13931
  }
13932
+ if ("fallbackMaxLastResortCandidates" in fixed) {
13933
+ const v = fixed["fallbackMaxLastResortCandidates"];
13934
+ const n = coerceNumber(v);
13935
+ if (n === void 0) {
13936
+ delete fixed["fallbackMaxLastResortCandidates"];
13937
+ findings.push({
13938
+ path: "fallbackMaxLastResortCandidates",
13939
+ problem: `expected a non-negative number, got ${JSON.stringify(v)}`,
13940
+ severity: "error",
13941
+ fix: "removed (built-in default of 12 applies)"
13942
+ });
13943
+ } else {
13944
+ const clamped = Math.max(0, Math.floor(n));
13945
+ if (clamped !== v) {
13946
+ fixed["fallbackMaxLastResortCandidates"] = clamped;
13947
+ findings.push({
13948
+ path: "fallbackMaxLastResortCandidates",
13949
+ problem: `expected a non-negative integer, got ${JSON.stringify(v)}`,
13950
+ severity: "error",
13951
+ fix: `set to ${clamped}`
13952
+ });
13953
+ } else if (clamped === 0) {
13954
+ findings.push({
13955
+ path: "fallbackMaxLastResortCandidates",
13956
+ problem: "is 0 \u2014 last-resort auto-discovery append is disabled",
13957
+ severity: "warning",
13958
+ fix: "no change (explicit user choice)"
13959
+ });
13960
+ }
13961
+ }
13962
+ }
13845
13963
  if ("fleet" in fixed) {
13846
13964
  if (!isPlainObject(fixed["fleet"])) {
13847
13965
  findings.push({
@@ -14673,6 +14791,9 @@ function buildFallbackCommand(opts) {
14673
14791
  const favorites = config.favoriteModels ?? [];
14674
14792
  const bridge = config.fallbackBridge?.trim();
14675
14793
  const auto = config.fallbackAuto !== false;
14794
+ const rawCap = config.fallbackMaxLastResortCandidates;
14795
+ const capValue = typeof rawCap === "number" && Number.isFinite(rawCap) && rawCap >= 0 ? Math.floor(rawCap) : 12;
14796
+ const capLabel = capValue === 0 ? color24.dim("disabled") : color24.green(String(capValue));
14676
14797
  const filteredReason = (ref) => refInactiveReason(ref, config);
14677
14798
  const lines = [
14678
14799
  `${color24.bold("WrongStack")} ${color24.dim("\u2014 Fallback chain")}`,
@@ -14710,6 +14831,7 @@ function buildFallbackCommand(opts) {
14710
14831
  "",
14711
14832
  ` ${color24.bold("auto")} ${auto ? color24.green("on") : color24.dim("off")} ${color24.dim("/fallback auto on|off")}`,
14712
14833
  ` ${color24.bold("favorites only")} ${config.favoriteModelsOnly ? color24.green("on") : color24.dim("off")} ${color24.dim("/fallback fav only on|off")}`,
14834
+ ` ${color24.bold("last-resort cap")} ${capLabel} ${color24.dim("(max auto-discovered models appended, 0=disabled)")}`,
14713
14835
  "",
14714
14836
  ` ${color24.bold("profiles")} ${Object.keys(profiles).length ? "" : color24.dim("(none)")}`,
14715
14837
  ...Object.entries(profiles).sort(([a], [b]) => a.localeCompare(b)).flatMap(([name, chain]) => {
@@ -17298,7 +17420,6 @@ function buildIntakeCommand(opts) {
17298
17420
  import { color as color32 } from "@wrongstack/core/utils";
17299
17421
  import {
17300
17422
  addCheckToTask,
17301
- addColumn,
17302
17423
  addDependency,
17303
17424
  addGoalMetricToTask,
17304
17425
  addNoteToTask,
@@ -17324,7 +17445,6 @@ import {
17324
17445
  parseLinesIntoTasks,
17325
17446
  releaseTaskClaim,
17326
17447
  removeBoard,
17327
- removeColumn,
17328
17448
  removeTask,
17329
17449
  setTaskChain,
17330
17450
  splitTask,
@@ -17752,9 +17872,6 @@ var KANBAN_COMMAND_HELP = [
17752
17872
  "",
17753
17873
  " /kanban task check add <boardId> <taskId> <desc> Add success check",
17754
17874
  "",
17755
- " /kanban column add <boardId> <title> Add column",
17756
- " /kanban column rm <boardId> <colId> Remove column",
17757
- "",
17758
17875
  " /kanban generate <description> Auto-generate board from text",
17759
17876
  " /kanban export <boardId> Export board as markdown",
17760
17877
  " /kanban graph export <boardId> [graphId] Save board as SDD TaskGraph",
@@ -17930,9 +18047,6 @@ function buildKanbanCommand(opts) {
17930
18047
  if (cmd === "task" || cmd === "t") {
17931
18048
  return handleTaskSubcommand(opts, projectRoot, rest, showHelp);
17932
18049
  }
17933
- if (cmd === "column" || cmd === "col" || cmd === "c") {
17934
- return handleColumnSubcommand(projectRoot, rest, showHelp);
17935
- }
17936
18050
  return {
17937
18051
  message: unknownSubcommand(
17938
18052
  cmd,
@@ -18612,27 +18726,6 @@ ${summary}` };
18612
18726
  }
18613
18727
  return showHelp();
18614
18728
  }
18615
- async function handleColumnSubcommand(projectRoot, args, showHelp) {
18616
- const [sub, boardId, ...rest] = args;
18617
- if (!sub || !boardId) return showHelp();
18618
- if (sub === "add") {
18619
- const title = rest.join(" ");
18620
- if (!title) return { message: color32.red("Usage: /kanban column add <boardId> <title>") };
18621
- const result = await addColumn(projectRoot, boardId, { title });
18622
- if (!result) return { message: color32.red(`Board not found: ${boardId}`) };
18623
- return { message: color32.green(`\u2705 Column added: ${result.column.title}`) };
18624
- }
18625
- if (sub === "rm" || sub === "remove" || sub === "delete") {
18626
- const colId = rest[0];
18627
- if (!colId) return { message: color32.red("Usage: /kanban column rm <boardId> <columnId>") };
18628
- const updated = await removeColumn(projectRoot, boardId, colId, {
18629
- moveTasksToColumnId: rest[1]
18630
- });
18631
- if (!updated) return { message: color32.red(`Column not found: ${colId}`) };
18632
- return { message: color32.green(`\u2705 Column removed: ${colId}`) };
18633
- }
18634
- return showHelp();
18635
- }
18636
18729
  function extractSpawnedSubagentId(summary) {
18637
18730
  return summary.match(/Spawned subagent\s+([^\s]+)/)?.[1];
18638
18731
  }
@@ -18650,12 +18743,6 @@ function formatLifecycleDiagnosis(err, action) {
18650
18743
  if (field === "assignee") {
18651
18744
  return `\u274C /kanban task ${action} needs an assignee. Run \`/kanban task assign <boardId> <taskId> <agent>\` first.`;
18652
18745
  }
18653
- if (field === "dueDate") {
18654
- return `\u274C /kanban task ${action} needs a valid due date. Add one via the \`kanban.update_task\` tool action (patch.dueDate = "YYYY-MM-DD").`;
18655
- }
18656
- if (field === "labels") {
18657
- return `\u274C /kanban task ${action} needs at least one label. Add one via the \`kanban.update_task\` tool action (patch.labels = ["..."]).`;
18658
- }
18659
18746
  if (field === "childTaskIds") {
18660
18747
  return `\u274C /kanban task ${action} is an atomic parent with no persisted children. Decompose it via the kanban tool (\`split_atomic\`) before moving it forward.`;
18661
18748
  }
@@ -20253,7 +20340,11 @@ function formatSageShow(stats, memories) {
20253
20340
 
20254
20341
  // src/slash-commands/memory-triage.ts
20255
20342
  import { toErrorMessage as toErrorMessage17 } from "@wrongstack/core/utils";
20256
- import { formatTriageReport, runTriage } from "@wrongstack/sage";
20343
+ import {
20344
+ fileTriageProposals,
20345
+ formatTriageReport,
20346
+ runTriage
20347
+ } from "@wrongstack/sage";
20257
20348
  var DEFAULT_MAX_PHASE3 = 1e3;
20258
20349
  var DEFAULT_MAX_PHASE4_PAIRS = 50;
20259
20350
  async function runTriageCommand(opts, args) {
@@ -20450,41 +20541,7 @@ async function applyDispatch(Sage, report) {
20450
20541
  return lines.join("\n");
20451
20542
  }
20452
20543
  async function fileProposals(Sage, proposals) {
20453
- const inputs = [];
20454
- const failures = [];
20455
- let filed = 0;
20456
- for (const proposal of proposals) {
20457
- const input = {
20458
- text: proposal.memoryText,
20459
- targetMemoryId: proposal.memoryId,
20460
- reviewReason: proposal.reason,
20461
- suggestedAction: proposal.suggestedAction,
20462
- kind: "memory_review",
20463
- scope: "project",
20464
- importance: 0.5,
20465
- confidence: 0.9,
20466
- tags: ["triage"],
20467
- anchors: [],
20468
- sources: [{ type: "project_instruction" }]
20469
- };
20470
- inputs.push(input);
20471
- try {
20472
- await Sage.createCandidate(input);
20473
- filed++;
20474
- } catch (err) {
20475
- failures.push({
20476
- memoryId: proposal.memoryId,
20477
- error: toErrorMessage17(err)
20478
- });
20479
- }
20480
- }
20481
- return {
20482
- filed,
20483
- failed: failures.length,
20484
- total: proposals.length,
20485
- failures,
20486
- inputs
20487
- };
20544
+ return fileTriageProposals(Sage, proposals);
20488
20545
  }
20489
20546
 
20490
20547
  // src/slash-commands/memory.ts
@@ -31404,7 +31461,7 @@ async function runInteractive(cliCtx) {
31404
31461
  activeMode
31405
31462
  });
31406
31463
  if (modeResult.kind === "exit") {
31407
- writeErr2(`${modeResult.message}
31464
+ writeErr3(`${modeResult.message}
31408
31465
  `);
31409
31466
  await reader.close();
31410
31467
  return modeResult.code;
@@ -31425,6 +31482,15 @@ async function runInteractive(cliCtx) {
31425
31482
  container,
31426
31483
  modeStore,
31427
31484
  memoryStore,
31485
+ // Provide a narrow domain-term adapter so the prompt builder emits a
31486
+ // compact `[Project Jargon Dictionary]` block. Closes over the resolved
31487
+ // SAGE `memoryStore` (a `MemoryPort`) and uses the typed
31488
+ // `SageServiceLike` capability (NOT `@wrongstack/sage-mcp` — the
31489
+ // in-process consumer rule from `packages/sage/docs/direct-icp-usage.md`).
31490
+ // Returns only memories tagged `domain-term` via the capability's
31491
+ // `searchSage` op, which performs the tag-filter at the SQL layer rather
31492
+ // than scanning the whole corpus on every prompt build.
31493
+ domainGlossary: createDomainGlossaryAdapter(memoryStore),
31428
31494
  skillLoader,
31429
31495
  sessionRef,
31430
31496
  autonomyModeRef,
@@ -31445,6 +31511,7 @@ async function runInteractive(cliCtx) {
31445
31511
  pathJoiner: { join: (a, b) => path31.join(a, b) },
31446
31512
  systemPromptBuilderToken: TOKENS9.SystemPromptBuilder
31447
31513
  });
31514
+ await refreshDomainTermsMirror({ projectRoot, memoryStore });
31448
31515
  const toolRegistry = new ToolRegistry2();
31449
31516
  registerBuiltinTools({
31450
31517
  toolRegistry,
@@ -31540,7 +31607,26 @@ async function runInteractive(cliCtx) {
31540
31607
  }
31541
31608
  });
31542
31609
  const { context, planPath, session } = sessResult;
31610
+ context.meta["promptOnlineAgents"] = onlineAgents;
31543
31611
  sessionRef.current = session;
31612
+ const replayFlag = flags["replay"];
31613
+ const recordFlag = flags["record"];
31614
+ if (typeof replayFlag === "string" || recordFlag === true) {
31615
+ const { bindReplayToContainer } = await import("./replay-XU6ZD7GM.js");
31616
+ const mode = recordFlag === true ? "record" : "replay";
31617
+ bindReplayToContainer({
31618
+ container,
31619
+ wpaths,
31620
+ // Recording follows the live session across `/resume` and new-session
31621
+ // swaps; replaying is pinned to the id the user named.
31622
+ sessionId: typeof replayFlag === "string" ? replayFlag : () => sessionRef.current?.id ?? "",
31623
+ mode,
31624
+ logger
31625
+ });
31626
+ logger.info(
31627
+ `replay: ProviderRunner bound in '${mode}' mode for session ${typeof replayFlag === "string" ? replayFlag : session.id}`
31628
+ );
31629
+ }
31544
31630
  const governanceHandle = await setupCliGovernance({
31545
31631
  projectRoot,
31546
31632
  projectId: wpaths.projectSlug,
@@ -32075,7 +32161,7 @@ async function runInteractive(cliCtx) {
32075
32161
  onEvent: evOn
32076
32162
  });
32077
32163
  const savedProviderCfg = config.providers?.[config.provider];
32078
- const { execute } = await import("./execution-MVOH6PAQ.js");
32164
+ const { execute } = await import("./execution-YMBD7YWC.js");
32079
32165
  const stopHeapWatchdog = startSharedHeapWatchdog({
32080
32166
  collectStats: () => {
32081
32167
  const hqQueue = hqPublisherRef.current?.getQueueStats();
@@ -32126,6 +32212,11 @@ async function runInteractive(cliCtx) {
32126
32212
  events,
32127
32213
  slashRegistry,
32128
32214
  tokenCounter,
32215
+ // Forward the boot sessionRef so `resumeSession` can repoint
32216
+ // it when an in-process `/resume` swaps the active writer.
32217
+ // Without this, provider-side `getSessionId` callbacks and the
32218
+ // record-mode binding keep referring to the boot session.
32219
+ sessionRef,
32129
32220
  activateSessionIdentity: activateSession,
32130
32221
  config,
32131
32222
  configStore,
@@ -32308,4 +32399,4 @@ export {
32308
32399
  CLI_VERSION,
32309
32400
  runInteractive
32310
32401
  };
32311
- //# sourceMappingURL=cli-main-37XO26GK.js.map
32402
+ //# sourceMappingURL=cli-main-W3T56YCY.js.map