adhdev 0.9.82-rc.422 → 0.9.82-rc.423

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.
@@ -1273,10 +1273,10 @@ function readInjected(value) {
1273
1273
  }
1274
1274
  function getDaemonBuildInfo() {
1275
1275
  if (cached) return cached;
1276
- const commit = readInjected(true ? "308e099c65d2de2491f3db46913f9ced6aa3992c" : void 0) ?? "unknown";
1277
- const commitShort = readInjected(true ? "308e099c" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
1278
- const version2 = readInjected(true ? "0.9.82-rc.422" : void 0) ?? readInjected(typeof process !== "undefined" ? "0.9.82-rc.422" : void 0) ?? "unknown";
1279
- const builtAt = readInjected(true ? "2026-06-29T05:26:42.949Z" : void 0);
1276
+ const commit = readInjected(true ? "b8cb72411ebf328f26357d963c53d2fa07f19c78" : void 0) ?? "unknown";
1277
+ const commitShort = readInjected(true ? "b8cb7241" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
1278
+ const version2 = readInjected(true ? "0.9.82-rc.423" : void 0) ?? readInjected(typeof process !== "undefined" ? "0.9.82-rc.423" : void 0) ?? "unknown";
1279
+ const builtAt = readInjected(true ? "2026-06-29T07:40:56.706Z" : void 0);
1280
1280
  cached = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
1281
1281
  return cached;
1282
1282
  }
@@ -137165,7 +137165,7 @@ var init_adhdev_daemon = __esm({
137165
137165
  init_version();
137166
137166
  init_src();
137167
137167
  init_runtime_defaults();
137168
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.422" });
137168
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.423" });
137169
137169
  LOCAL_DAEMON_HEALTH_TIMEOUT_MS = 8e3;
137170
137170
  AdhdevDaemon = class _AdhdevDaemon {
137171
137171
  localHttpServer = null;
package/dist/cli/index.js CHANGED
@@ -1273,10 +1273,10 @@ function readInjected(value) {
1273
1273
  }
1274
1274
  function getDaemonBuildInfo() {
1275
1275
  if (cached) return cached;
1276
- const commit = readInjected(true ? "308e099c65d2de2491f3db46913f9ced6aa3992c" : void 0) ?? "unknown";
1277
- const commitShort = readInjected(true ? "308e099c" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
1278
- const version2 = readInjected(true ? "0.9.82-rc.422" : void 0) ?? readInjected(typeof process !== "undefined" ? "0.9.82-rc.422" : void 0) ?? "unknown";
1279
- const builtAt = readInjected(true ? "2026-06-29T05:26:42.949Z" : void 0);
1276
+ const commit = readInjected(true ? "b8cb72411ebf328f26357d963c53d2fa07f19c78" : void 0) ?? "unknown";
1277
+ const commitShort = readInjected(true ? "b8cb7241" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
1278
+ const version2 = readInjected(true ? "0.9.82-rc.423" : void 0) ?? readInjected(typeof process !== "undefined" ? "0.9.82-rc.423" : void 0) ?? "unknown";
1279
+ const builtAt = readInjected(true ? "2026-06-29T07:40:56.706Z" : void 0);
1280
1280
  cached = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
1281
1281
  return cached;
1282
1282
  }
@@ -137135,7 +137135,7 @@ var init_adhdev_daemon = __esm({
137135
137135
  init_version();
137136
137136
  init_src();
137137
137137
  init_runtime_defaults();
137138
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.422" });
137138
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.423" });
137139
137139
  LOCAL_DAEMON_HEALTH_TIMEOUT_MS = 8e3;
137140
137140
  AdhdevDaemon = class _AdhdevDaemon {
137141
137141
  localHttpServer = null;
package/dist/index.js CHANGED
@@ -753,10 +753,10 @@ function readInjected(value) {
753
753
  }
754
754
  function getDaemonBuildInfo() {
755
755
  if (cached) return cached;
756
- const commit = readInjected(true ? "308e099c65d2de2491f3db46913f9ced6aa3992c" : void 0) ?? "unknown";
757
- const commitShort = readInjected(true ? "308e099c" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
758
- const version2 = readInjected(true ? "0.9.82-rc.422" : void 0) ?? readInjected(typeof process !== "undefined" ? "0.9.82-rc.422" : void 0) ?? "unknown";
759
- const builtAt = readInjected(true ? "2026-06-29T05:26:42.949Z" : void 0);
756
+ const commit = readInjected(true ? "b8cb72411ebf328f26357d963c53d2fa07f19c78" : void 0) ?? "unknown";
757
+ const commitShort = readInjected(true ? "b8cb7241" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
758
+ const version2 = readInjected(true ? "0.9.82-rc.423" : void 0) ?? readInjected(typeof process !== "undefined" ? "0.9.82-rc.423" : void 0) ?? "unknown";
759
+ const builtAt = readInjected(true ? "2026-06-29T07:40:56.706Z" : void 0);
760
760
  cached = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
761
761
  return cached;
762
762
  }
@@ -105957,7 +105957,7 @@ var init_adhdev_daemon = __esm({
105957
105957
  init_version();
105958
105958
  init_src();
105959
105959
  init_runtime_defaults();
105960
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.422" });
105960
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.423" });
105961
105961
  LOCAL_DAEMON_HEALTH_TIMEOUT_MS = 8e3;
105962
105962
  AdhdevDaemon = class _AdhdevDaemon {
105963
105963
  localHttpServer = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adhdev",
3
- "version": "0.9.82-rc.422",
3
+ "version": "0.9.82-rc.423",
4
4
  "description": "ADHDev — Agent Dashboard Hub for Dev. The control hub for your AI coding agents (Claude Code, Codex, Hermes, …).",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -49,9 +49,9 @@
49
49
  "node": ">=18"
50
50
  },
51
51
  "dependencies": {
52
- "@adhdev/daemon-core": "0.9.82-rc.422",
52
+ "@adhdev/daemon-core": "0.9.82-rc.423",
53
53
  "@adhdev/ghostty-vt-node": "*",
54
- "@adhdev/mesh-shared": "0.9.82-rc.422",
54
+ "@adhdev/mesh-shared": "0.9.82-rc.423",
55
55
  "@modelcontextprotocol/sdk": "^1.0.0",
56
56
  "@xterm/addon-serialize": "^0.14.0",
57
57
  "@xterm/xterm": "^6.0.0",
@@ -1172,7 +1172,9 @@ var MESH_MAGI_REVIEW_TOOL = {
1172
1172
  }
1173
1173
  },
1174
1174
  n: { type: "number", description: "Global replica override per member (clamped by the total-replica guard cap, default 12)." },
1175
- mode: { type: "string", enum: ["rca", "investigation", "claim_audit", "design_review", "code_audit"], description: "Synthesis emphasis hint \u2014 affects labels only, never the agent count or schema." },
1175
+ task_kind: { type: "string", enum: ["claim_audit", "rca", "design", "freeform"], description: "Selects the SINGLE output schema injected into each replica prompt and the strict parser used at collection (no schema-on-schema conflict). claim_audit (DEFAULT, backward-compatible): {claims[],top_findings[],open_questions[]}. rca: {rootCause,failsAt,mechanism,evidence[],fixDirection,confidence}. design: {recommendation,rationale,alternatives[],tradeoffs[],risks[],evidence[],confidence}. freeform: no schema \u2014 natural-language answer, parsing/evidence checks waived, cross-verification is weak. Every kind except freeform requires non-empty evidence[]; an empty-evidence or schema-invalid answer triggers ONE delta re-request before being dropped as unparseable. Do NOT also embed an output-format schema in the question \u2014 it collides with this contract (a warning is surfaced if detected)." },
1176
+ mode: { type: "string", enum: ["rca", "investigation", "claim_audit", "design_review", "code_audit"], description: "Synthesis emphasis hint \u2014 affects labels only, never the agent count or schema. Distinct from task_kind (which selects the output schema)." },
1177
+ use_judge: { type: "boolean", description: "Default false (clustering synthesis). STUB: judge synthesis is not yet implemented \u2014 passing true currently falls back to clustering with a warning. Reserved interface only." },
1176
1178
  require_independent_evidence: { type: "boolean", description: "Default true \u2014 high-impact claims with no file:line/source evidence are routed to needs_verification." },
1177
1179
  include_stale: { type: "boolean", description: "Default false. By default, panel members whose node HEAD commit differs from the coordinator reference commit are EXCLUDED (they would investigate different code). Set true to fan out to them anyway \u2014 results will be git-skewed and a warning is surfaced. If exclusion drops the panel below 2 independent targets the call errors rather than degrading to N=1; include_stale=true is one way to recover." },
1178
1180
  wait: { type: "boolean", description: "Default true \u2014 collect replica outputs and return the synthesis. Set false to dispatch async and return a consensusGroupId handle; collect later with mesh_magi_collect." },
@@ -1188,6 +1190,7 @@ var MESH_MAGI_COLLECT_TOOL = {
1188
1190
  type: "object",
1189
1191
  properties: {
1190
1192
  consensus_group_id: { type: "string", description: "The consensusGroupId returned by a wait=false mesh_magi_review." },
1193
+ task_kind: { type: "string", enum: ["claim_audit", "rca", "design", "freeform"], description: "Optional override of the task_kind used to parse replica answers. Normally recovered automatically from the original dispatch \u2014 only set this if the dispatched ledger entry was pruned and auto-recovery falls back to claim_audit incorrectly." },
1191
1194
  require_independent_evidence: { type: "boolean", description: "Default true \u2014 high-impact claims with no file:line/source evidence are routed to needs_verification." },
1192
1195
  wait: { type: "boolean", description: "Default false (snapshot). Set true to block for outstanding replicas up to wait_timeout_ms before synthesizing." },
1193
1196
  wait_timeout_ms: { type: "number", description: "When wait=true, max time to wait for remaining replica completion. Default ~4 min." }
@@ -4071,6 +4074,12 @@ var MAGI_CLUSTER_JACCARD = 0.5;
4071
4074
  var MAGI_DEFAULT_WAIT_MS = 18e4;
4072
4075
  var MAGI_MAX_WAIT_MS = 6e5;
4073
4076
  var MAGI_POLL_INTERVAL_MS = 5e3;
4077
+ var VALID_TASK_KINDS = ["claim_audit", "rca", "design", "freeform"];
4078
+ var DEFAULT_TASK_KIND = "claim_audit";
4079
+ function normalizeMagiTaskKind(raw) {
4080
+ const s = typeof raw === "string" ? raw.trim().toLowerCase() : "";
4081
+ return VALID_TASK_KINDS.includes(s) ? s : DEFAULT_TASK_KIND;
4082
+ }
4074
4083
  var VALID_STANCES = /* @__PURE__ */ new Set(["support", "oppose", "uncertain"]);
4075
4084
  function coerceClaim(raw) {
4076
4085
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
@@ -4145,6 +4154,130 @@ function parseMagiResponse(text) {
4145
4154
  }
4146
4155
  return null;
4147
4156
  }
4157
+ function asStringArray(raw) {
4158
+ return Array.isArray(raw) ? raw.map((e) => typeof e === "string" ? e.trim() : "").filter(Boolean) : [];
4159
+ }
4160
+ function asConfidence(raw) {
4161
+ return typeof raw === "number" && Number.isFinite(raw) ? Math.min(1, Math.max(0, raw)) : 0.5;
4162
+ }
4163
+ function asTrimmedString(raw) {
4164
+ return typeof raw === "string" ? raw.trim() : "";
4165
+ }
4166
+ function coerceRcaResponse(raw) {
4167
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4168
+ const r = raw;
4169
+ const rootCause = asTrimmedString(r.rootCause);
4170
+ const mechanism = asTrimmedString(r.mechanism);
4171
+ const failsAt = asTrimmedString(r.failsAt);
4172
+ const fixDirection = asTrimmedString(r.fixDirection);
4173
+ const evidence = asStringArray(r.evidence);
4174
+ const confidence = asConfidence(r.confidence);
4175
+ if (!rootCause && !mechanism && !failsAt && !fixDirection && evidence.length === 0) return null;
4176
+ const payload = { rootCause, failsAt, mechanism, evidence, fixDirection, confidence };
4177
+ if (!rootCause || !mechanism) return { payload, failReason: "missing_required_fields" };
4178
+ if (evidence.length === 0) return { payload, failReason: "empty_evidence" };
4179
+ return { payload };
4180
+ }
4181
+ function coerceDesignResponse(raw) {
4182
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
4183
+ const r = raw;
4184
+ const recommendation = asTrimmedString(r.recommendation);
4185
+ const rationale = asTrimmedString(r.rationale);
4186
+ const alternatives = asStringArray(r.alternatives);
4187
+ const tradeoffs = asStringArray(r.tradeoffs);
4188
+ const risks = asStringArray(r.risks);
4189
+ const evidence = asStringArray(r.evidence);
4190
+ const confidence = asConfidence(r.confidence);
4191
+ if (!recommendation && !rationale && alternatives.length === 0 && tradeoffs.length === 0 && risks.length === 0 && evidence.length === 0) return null;
4192
+ const payload = { recommendation, rationale, alternatives, tradeoffs, risks, evidence, confidence };
4193
+ if (!recommendation || !rationale) return { payload, failReason: "missing_required_fields" };
4194
+ if (evidence.length === 0) return { payload, failReason: "empty_evidence" };
4195
+ return { payload };
4196
+ }
4197
+ function rcaToCommonSchema(p) {
4198
+ return {
4199
+ claims: [{ claim: p.rootCause, stance: "support", evidence: p.evidence, confidence: p.confidence }],
4200
+ top_findings: [
4201
+ ...p.failsAt ? [`fails at: ${p.failsAt}`] : [],
4202
+ ...p.mechanism ? [`mechanism: ${p.mechanism}`] : [],
4203
+ ...p.fixDirection ? [`fix direction: ${p.fixDirection}`] : []
4204
+ ],
4205
+ open_questions: []
4206
+ };
4207
+ }
4208
+ function designToCommonSchema(p) {
4209
+ return {
4210
+ claims: [{ claim: p.recommendation, stance: "support", evidence: p.evidence, confidence: p.confidence }],
4211
+ top_findings: [
4212
+ ...p.rationale ? [`rationale: ${p.rationale}`] : [],
4213
+ ...p.alternatives.map((a) => `alternative: ${a}`),
4214
+ ...p.tradeoffs.map((t) => `tradeoff: ${t}`)
4215
+ ],
4216
+ open_questions: p.risks.map((r) => `risk: ${r}`)
4217
+ };
4218
+ }
4219
+ function firstJsonCandidate(text, coerce) {
4220
+ if (typeof text !== "string" || !text.trim()) return null;
4221
+ try {
4222
+ const direct = coerce(JSON.parse(text));
4223
+ if (direct) return direct;
4224
+ } catch {
4225
+ }
4226
+ for (const candidate of extractJsonObjectCandidates(text)) {
4227
+ try {
4228
+ const parsed = coerce(JSON.parse(candidate));
4229
+ if (parsed) return parsed;
4230
+ } catch {
4231
+ }
4232
+ }
4233
+ return null;
4234
+ }
4235
+ function parseMagiResponseForKind(text, kind) {
4236
+ if (kind === "freeform") {
4237
+ const trimmed = typeof text === "string" ? text.trim() : "";
4238
+ if (!trimmed) return { ok: false, failReason: "no_parseable_output" };
4239
+ const payload = { text: trimmed };
4240
+ return { ok: true, response: { claims: [], top_findings: [trimmed], open_questions: [] }, payload };
4241
+ }
4242
+ if (kind === "claim_audit") {
4243
+ const parsed = parseMagiResponse(text);
4244
+ if (!parsed) return { ok: false, failReason: "no_parseable_output" };
4245
+ const hasEvidence = parsed.claims.some((c) => c.evidence.length > 0) || parsed.top_findings.length > 0;
4246
+ if (!hasEvidence && parsed.claims.length > 0) return { ok: false, payload: parsed, failReason: "empty_evidence" };
4247
+ return { ok: true, response: parsed, payload: parsed };
4248
+ }
4249
+ if (kind === "rca") {
4250
+ const result2 = firstJsonCandidate(text, coerceRcaResponse);
4251
+ if (!result2) return { ok: false, failReason: "no_parseable_output" };
4252
+ if (result2.failReason) return { ok: false, payload: result2.payload, failReason: result2.failReason };
4253
+ return { ok: true, response: rcaToCommonSchema(result2.payload), payload: result2.payload };
4254
+ }
4255
+ const result = firstJsonCandidate(text, coerceDesignResponse);
4256
+ if (!result) return { ok: false, failReason: "no_parseable_output" };
4257
+ if (result.failReason) return { ok: false, payload: result.payload, failReason: result.failReason };
4258
+ return { ok: true, response: designToCommonSchema(result.payload), payload: result.payload };
4259
+ }
4260
+ function parseFirstMagiCandidateForKind(payload, kind, opts = {}) {
4261
+ const rawCandidates = collectMagiCandidateTexts(payload);
4262
+ let compactCandidates = [];
4263
+ try {
4264
+ compactCandidates = collectMagiCandidateTexts(
4265
+ compactChatPayload(payload, { sessionId: opts.sessionId ?? null })
4266
+ );
4267
+ } catch {
4268
+ }
4269
+ const seen = /* @__PURE__ */ new Set();
4270
+ let lastFail = { ok: false, failReason: "no_parseable_output" };
4271
+ for (const candidate of [...rawCandidates, ...compactCandidates]) {
4272
+ const trimmed = candidate.trim();
4273
+ if (!trimmed || seen.has(trimmed)) continue;
4274
+ seen.add(trimmed);
4275
+ const result = parseMagiResponseForKind(candidate, kind);
4276
+ if (result.ok) return result;
4277
+ if (result.failReason !== "no_parseable_output") lastFail = result;
4278
+ }
4279
+ return lastFail;
4280
+ }
4148
4281
  var CLAIM_STOPWORDS = /* @__PURE__ */ new Set([
4149
4282
  "the",
4150
4283
  "a",
@@ -4476,16 +4609,72 @@ function resolveMagiReferenceCommit(ctx) {
4476
4609
  const node = resolveCoordinatorNode(ctx);
4477
4610
  return nodeHeadCommit(node);
4478
4611
  }
4479
- var MAGI_OUTPUT_CONTRACT = `When done, respond with ONLY a single JSON object (no prose, no code fence) matching this exact schema:
4612
+ var MAGI_CLAIM_AUDIT_CONTRACT = `When done, respond with ONLY a single JSON object (no prose, no code fence) matching this exact schema:
4480
4613
  {
4481
4614
  "claims": [ { "claim": "string", "stance": "support | oppose | uncertain", "evidence": ["file:line or external source"], "confidence": 0.0 } ],
4482
4615
  "top_findings": ["string"],
4483
4616
  "open_questions": ["string"]
4484
4617
  }
4485
- Each claim MUST carry concrete evidence (file:line or a cited source) where possible \u2014 unevidenced high-confidence claims are flagged for re-verification. "stance" is your stance toward the claim being true. Do not invent agreement; report uncertainty honestly.`;
4618
+ Each claim MUST carry concrete evidence (file:line or a cited source) \u2014 unevidenced claims are flagged for re-verification. "stance" is your stance toward the claim being true. Do not invent agreement; report uncertainty honestly.`;
4619
+ var MAGI_RCA_CONTRACT = `When done, respond with ONLY a single JSON object (no prose, no code fence) matching this exact schema:
4620
+ {
4621
+ "rootCause": "string \u2014 the single underlying root cause",
4622
+ "failsAt": "file:line \u2014 the precise location the failure manifests",
4623
+ "mechanism": "string \u2014 how the root cause produces the observed symptom",
4624
+ "evidence": ["file:line or external source"],
4625
+ "fixDirection": "string \u2014 the direction a fix should take (do NOT write the fix)",
4626
+ "confidence": 0.0
4627
+ }
4628
+ "rootCause" and "mechanism" are REQUIRED. "evidence" MUST be non-empty (concrete file:line or cited source) \u2014 an empty evidence array is rejected and re-requested. Report uncertainty honestly.`;
4629
+ var MAGI_DESIGN_CONTRACT = `When done, respond with ONLY a single JSON object (no prose, no code fence) matching this exact schema:
4630
+ {
4631
+ "recommendation": "string \u2014 the recommended approach",
4632
+ "rationale": "string \u2014 why this approach",
4633
+ "alternatives": ["string \u2014 approaches considered and not chosen"],
4634
+ "tradeoffs": ["string"],
4635
+ "risks": ["string"],
4636
+ "evidence": ["file:line or external source backing the recommendation"],
4637
+ "confidence": 0.0
4638
+ }
4639
+ "recommendation" and "rationale" are REQUIRED. "evidence" MUST be non-empty \u2014 an empty evidence array is rejected and re-requested. Report uncertainty honestly.`;
4640
+ var MAGI_FREEFORM_CONTRACT = `Answer the question in natural language. No JSON schema is required for this task \u2014 write your analysis directly. (Note: a freeform answer is cross-verified only weakly, because it is unstructured.)`;
4641
+ function magiOutputContractFor(kind) {
4642
+ switch (kind) {
4643
+ case "rca":
4644
+ return MAGI_RCA_CONTRACT;
4645
+ case "design":
4646
+ return MAGI_DESIGN_CONTRACT;
4647
+ case "freeform":
4648
+ return MAGI_FREEFORM_CONTRACT;
4649
+ case "claim_audit":
4650
+ default:
4651
+ return MAGI_CLAIM_AUDIT_CONTRACT;
4652
+ }
4653
+ }
4654
+ function detectQuestionOutputSchemaConflict(question) {
4655
+ const q = typeof question === "string" ? question : "";
4656
+ if (!q.trim()) return null;
4657
+ const lower = q.toLowerCase();
4658
+ const signals = [
4659
+ "respond with only",
4660
+ "respond with a single json",
4661
+ "single json object",
4662
+ "output format",
4663
+ "output schema",
4664
+ "reply with only",
4665
+ '"claims"',
4666
+ '"top_findings"',
4667
+ "matching this exact schema"
4668
+ ];
4669
+ const hit = signals.find((s) => lower.includes(s));
4670
+ if (!hit) return null;
4671
+ return `The question text appears to embed an output-format schema (matched "${hit}"). MAGI already injects exactly one output contract for the selected task_kind, so a second schema in the question collides with it and replicas may fuse the two into unparseable output. Move any output-format instructions OUT of the question \u2014 describe only WHAT to investigate.`;
4672
+ }
4486
4673
  function buildMagiTaskPrompt(args) {
4674
+ const kind = args.taskKind ?? DEFAULT_TASK_KIND;
4487
4675
  const parts = [];
4488
4676
  parts.push("You are one independent member of a multi-agent cross-verification quorum (MAGI). Several other agents on different machines/providers are answering the SAME question independently; your job is a rigorous, READ-ONLY investigation. Do NOT write, edit, commit, or push anything.");
4677
+ parts.push(`Task kind: ${kind}.`);
4489
4678
  if (args.mode) parts.push(`Investigation mode: ${args.mode}.`);
4490
4679
  parts.push(`
4491
4680
  ## Question
@@ -4500,7 +4689,7 @@ ${args.artifacts.map((a) => String(a)).join("\n\n---\n\n")}`);
4500
4689
  }
4501
4690
  parts.push(`
4502
4691
  ## Output
4503
- ${MAGI_OUTPUT_CONTRACT}`);
4692
+ ${magiOutputContractFor(kind)}`);
4504
4693
  return parts.join("\n");
4505
4694
  }
4506
4695
  function collectMagiCandidateTexts(payload) {
@@ -4536,25 +4725,6 @@ function collectMagiCandidateTexts(payload) {
4536
4725
  push(p.text);
4537
4726
  return out;
4538
4727
  }
4539
- function parseFirstMagiCandidateWithCompactFallback(payload, opts = {}) {
4540
- const rawCandidates = collectMagiCandidateTexts(payload);
4541
- let compactCandidates = [];
4542
- try {
4543
- compactCandidates = collectMagiCandidateTexts(
4544
- compactChatPayload(payload, { sessionId: opts.sessionId ?? null })
4545
- );
4546
- } catch {
4547
- }
4548
- const seen = /* @__PURE__ */ new Set();
4549
- for (const candidate of [...rawCandidates, ...compactCandidates]) {
4550
- const trimmed = candidate.trim();
4551
- if (!trimmed || seen.has(trimmed)) continue;
4552
- seen.add(trimmed);
4553
- const parsed = parseMagiResponse(candidate);
4554
- if (parsed) return parsed;
4555
- }
4556
- return null;
4557
- }
4558
4728
  function replicaCompletionIsWeak(meshId, taskId) {
4559
4729
  try {
4560
4730
  const entries = (0, import_daemon_core4.readLedgerEntries)(meshId, { kind: ["task_completed"], tail: 200 });
@@ -4659,6 +4829,10 @@ async function meshMagiPanelList(ctx, args = {}) {
4659
4829
  async function meshMagiReview(ctx, args) {
4660
4830
  const question = readString(args.question);
4661
4831
  if (!question) return JSON.stringify({ success: false, error: "question required" });
4832
+ const taskKind = normalizeMagiTaskKind(args.task_kind ?? args.taskKind);
4833
+ const questionSchemaWarning = detectQuestionOutputSchemaConflict(question);
4834
+ const useJudge = (args.use_judge ?? args.useJudge) === true;
4835
+ const judgeWarning = useJudge ? "use_judge=true requested, but judge synthesis is not yet implemented \u2014 falling back to clustering synthesis." : null;
4662
4836
  await refreshMeshFromDaemon(ctx);
4663
4837
  const hasInlineMembers = Array.isArray(args.members) && args.members.length > 0;
4664
4838
  let panel;
@@ -4713,7 +4887,7 @@ async function meshMagiReview(ctx, args) {
4713
4887
  goal: `Cross-verify (read-only) across panel '${panelName}': ${question}${args.target ? `
4714
4888
  Target: ${args.target}` : ""}`
4715
4889
  });
4716
- const prompt = buildMagiTaskPrompt({ question, target: args.target, artifacts: args.artifacts, mode: mode || void 0 });
4890
+ const prompt = buildMagiTaskPrompt({ question, target: args.target, artifacts: args.artifacts, mode: mode || void 0, taskKind });
4717
4891
  const replicaRecords = [];
4718
4892
  for (const replica of plan.replicas) {
4719
4893
  try {
@@ -4745,7 +4919,8 @@ Target: ${args.target}` : ""}`
4745
4919
  missionId: mission.id,
4746
4920
  panel: panelName,
4747
4921
  question,
4748
- replicaCount: replicaRecords.length
4922
+ replicaCount: replicaRecords.length,
4923
+ taskKind
4749
4924
  });
4750
4925
  const queueTrigger = await triggerMeshQueueAndReport(ctx);
4751
4926
  const baseResult = {
@@ -4754,6 +4929,9 @@ Target: ${args.target}` : ""}`
4754
4929
  missionId: mission.id,
4755
4930
  panel: panelName,
4756
4931
  ...hasInlineMembers ? { inline: true } : {},
4932
+ taskKind,
4933
+ ...questionSchemaWarning ? { questionSchemaWarning } : {},
4934
+ ...judgeWarning ? { judgeWarning } : {},
4757
4935
  question,
4758
4936
  replicaCount: replicaRecords.length,
4759
4937
  replicas: replicaRecords.map((r) => ({ taskId: r.taskId, provider: r.provider, targetNodeId: r.targetNodeId })),
@@ -4791,12 +4969,14 @@ Target: ${args.target}` : ""}`
4791
4969
  }
4792
4970
  const collected = await collectMagiResponses(ctx, {
4793
4971
  replicaTaskIds: replicaRecords.map((r) => r.taskId),
4794
- timeoutMs: waitTimeoutMs
4972
+ timeoutMs: waitTimeoutMs,
4973
+ taskKind
4795
4974
  });
4796
4975
  const synthesis = synthesizeMagiResponses(collected.responses, {
4797
4976
  replicasExpected: replicaRecords.length,
4798
4977
  requireIndependentEvidence
4799
4978
  });
4979
+ const freeformBanner = taskKind === "freeform" ? "task_kind=freeform: answers are unstructured natural language; cross-verification is WEAK (no claim clustering / independence scoring). Treat the collected answers as parallel opinions, not a verified consensus." : null;
4800
4980
  persistMagiSynthesis(ctx, {
4801
4981
  consensusGroupId,
4802
4982
  missionId: mission.id,
@@ -4815,8 +4995,10 @@ Target: ${args.target}` : ""}`
4815
4995
  missing: synthesis.replicasMissing,
4816
4996
  staleReplicas: collected.staleCount,
4817
4997
  ...collected.staleCount > 0 ? { staleNote: `${collected.staleCount} replica(s) were detected STALE \u2014 assigned to a node/session no longer present in the live mesh; collection stopped early rather than waiting out the timeout.` } : {},
4818
- ...synthesis.replicasMissing > 0 ? { missingNote: `Partial synthesis \u2014 ${synthesis.replicasMissing} of ${replicaRecords.length} replicas did not return a parseable response (timed out / failed / unparseable / stale).` } : {}
4998
+ ...collected.retriedCount > 0 ? { retriedReplicas: collected.retriedCount, retryNote: `${collected.retriedCount} replica(s) failed the ${taskKind} schema and were sent one delta re-request for a corrected single-JSON answer.` } : {},
4999
+ ...synthesis.replicasMissing > 0 ? { missingNote: `Partial synthesis \u2014 ${synthesis.replicasMissing} of ${replicaRecords.length} replicas did not return a parseable response (timed out / failed / unparseable / schema-invalid / stale).` } : {}
4819
5000
  },
5001
+ ...freeformBanner ? { freeformBanner } : {},
4820
5002
  synthesis
4821
5003
  }, null, 2);
4822
5004
  }
@@ -4824,6 +5006,8 @@ async function meshMagiCollect(ctx, args) {
4824
5006
  const consensusGroupId = readString(args.consensus_group_id) || readString(args.consensusGroupId);
4825
5007
  if (!consensusGroupId) return JSON.stringify({ success: false, error: "consensus_group_id required" });
4826
5008
  await refreshMeshFromDaemon(ctx);
5009
+ const explicitKind = args.task_kind ?? args.taskKind;
5010
+ const taskKind = explicitKind !== void 0 ? normalizeMagiTaskKind(explicitKind) : recoverMagiTaskKind(ctx, consensusGroupId);
4827
5011
  const replicaTasks = findMagiReplicaTasks((0, import_daemon_core4.getQueue)(ctx.mesh.id), consensusGroupId);
4828
5012
  if (replicaTasks.length === 0) {
4829
5013
  return JSON.stringify({
@@ -4837,11 +5021,12 @@ async function meshMagiCollect(ctx, args) {
4837
5021
  const wait = args.wait === true;
4838
5022
  const timeoutMs = wait ? Math.min(MAGI_MAX_WAIT_MS, Math.max(MAGI_POLL_INTERVAL_MS, Number(args.wait_timeout_ms ?? args.waitTimeoutMs) || MAGI_DEFAULT_WAIT_MS)) : 0;
4839
5023
  const replicaTaskIds = replicaTasks.map((t) => readString(t.id)).filter(Boolean);
4840
- const collected = await collectMagiResponses(ctx, { replicaTaskIds, timeoutMs });
5024
+ const collected = await collectMagiResponses(ctx, { replicaTaskIds, timeoutMs, taskKind });
4841
5025
  const synthesis = synthesizeMagiResponses(collected.responses, {
4842
5026
  replicasExpected: replicaTaskIds.length,
4843
5027
  requireIndependentEvidence
4844
5028
  });
5029
+ const freeformBanner = taskKind === "freeform" ? "task_kind=freeform: answers are unstructured natural language; cross-verification is WEAK (no claim clustering / independence scoring). Treat the collected answers as parallel opinions, not a verified consensus." : null;
4845
5030
  persistMagiSynthesis(ctx, {
4846
5031
  consensusGroupId,
4847
5032
  missionId: readString(replicaTasks[0]?.missionId),
@@ -4851,6 +5036,7 @@ async function meshMagiCollect(ctx, args) {
4851
5036
  return JSON.stringify({
4852
5037
  success: true,
4853
5038
  consensusGroupId,
5039
+ taskKind,
4854
5040
  replicaCount: replicaTaskIds.length,
4855
5041
  waited: wait,
4856
5042
  collection: {
@@ -4860,8 +5046,10 @@ async function meshMagiCollect(ctx, args) {
4860
5046
  missing: synthesis.replicasMissing,
4861
5047
  staleReplicas: collected.staleCount,
4862
5048
  ...collected.staleCount > 0 ? { staleNote: `${collected.staleCount} replica(s) were detected STALE \u2014 assigned to a node/session no longer present in the live mesh.` } : {},
5049
+ ...collected.retriedCount > 0 ? { retriedReplicas: collected.retriedCount, retryNote: `${collected.retriedCount} replica(s) failed the ${taskKind} schema and were sent one delta re-request for a corrected single-JSON answer.` } : {},
4863
5050
  ...!collected.terminal ? { pendingNote: "Not all replicas are terminal yet \u2014 this is a partial snapshot. Re-collect once mission/pendingCoordinatorEvents report more completions." } : {}
4864
5051
  },
5052
+ ...freeformBanner ? { freeformBanner } : {},
4865
5053
  synthesis
4866
5054
  }, null, 2);
4867
5055
  }
@@ -4896,12 +5084,29 @@ function persistMagiDispatched(ctx, args) {
4896
5084
  ...args.missionId ? { missionId: args.missionId } : {},
4897
5085
  ...args.panel ? { panel: args.panel } : {},
4898
5086
  ...args.question ? { question: args.question.slice(0, 300) } : {},
4899
- replicaCount: args.replicaCount
5087
+ replicaCount: args.replicaCount,
5088
+ // MAGI-REDESIGN: persist the task_kind so a later mesh_magi_collect
5089
+ // (which rediscovers replicas from the queue, not the original call)
5090
+ // re-derives the right schema parser for this group.
5091
+ ...args.taskKind ? { taskKind: args.taskKind } : {}
4900
5092
  }
4901
5093
  });
4902
5094
  } catch {
4903
5095
  }
4904
5096
  }
5097
+ function recoverMagiTaskKind(ctx, consensusGroupId) {
5098
+ try {
5099
+ const entries = (0, import_daemon_core4.readLedgerEntries)(ctx.mesh.id, { kind: ["magi_dispatched"], tail: 200 });
5100
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
5101
+ const payload = entries[i]?.payload;
5102
+ if (!payload || typeof payload !== "object") continue;
5103
+ if (readString(payload.consensusGroupId) !== consensusGroupId) continue;
5104
+ return normalizeMagiTaskKind(payload.taskKind);
5105
+ }
5106
+ } catch {
5107
+ }
5108
+ return DEFAULT_TASK_KIND;
5109
+ }
4905
5110
  function persistMagiSynthesis(ctx, args) {
4906
5111
  try {
4907
5112
  (0, import_daemon_core4.appendLedgerEntry)(ctx.mesh.id, {
@@ -4935,7 +5140,9 @@ async function collectMagiResponses(ctx, args) {
4935
5140
  const ids = new Set(args.replicaTaskIds);
4936
5141
  const deadline = Date.now() + args.timeoutMs;
4937
5142
  const TERMINAL = MAGI_TERMINAL_STATUSES;
5143
+ const kind = args.taskKind ?? DEFAULT_TASK_KIND;
4938
5144
  const emptyResponse = () => ({ claims: [], top_findings: [], open_questions: [] });
5145
+ const retried = /* @__PURE__ */ new Set();
4939
5146
  const finalized = /* @__PURE__ */ new Map();
4940
5147
  const provisional = /* @__PURE__ */ new Map();
4941
5148
  const buildSource = (task) => {
@@ -4949,6 +5156,42 @@ async function collectMagiResponses(ctx, args) {
4949
5156
  ...gitRef ? { git: gitRef } : {}
4950
5157
  };
4951
5158
  };
5159
+ const sendKindRetry = async (task, failReason) => {
5160
+ const node = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, task.assignedNodeId));
5161
+ if (!node || !task.assignedSessionId) return false;
5162
+ const why = failReason === "empty_evidence" ? "your previous answer had an empty evidence array" : failReason === "missing_required_fields" ? "your previous answer was missing required fields" : "your previous answer did not parse as the required JSON";
5163
+ const message = `Your previous MAGI answer could not be accepted (${why}). Respond NOW with ONLY a single JSON object (no prose, no code fence) matching EXACTLY this schema, with non-empty evidence:
5164
+
5165
+ ${magiOutputContractFor(kind)}`;
5166
+ try {
5167
+ const coordinatorDaemonId = ctx.localDaemonId;
5168
+ await commandForNode(ctx, node, "agent_command", {
5169
+ targetSessionId: task.assignedSessionId,
5170
+ providerType: task.assignedProviderType,
5171
+ cliType: task.assignedProviderType,
5172
+ agentType: task.assignedProviderType,
5173
+ action: "send_chat",
5174
+ message,
5175
+ meshContext: {
5176
+ meshId: ctx.mesh.id,
5177
+ nodeId: task.assignedNodeId,
5178
+ taskId: task.id,
5179
+ ...coordinatorDaemonId ? { coordinatorDaemonId } : {},
5180
+ ...ctx.coordinatorSessionId ? { coordinatorSessionId: ctx.coordinatorSessionId } : {}
5181
+ }
5182
+ });
5183
+ try {
5184
+ (0, import_daemon_core4.appendLedgerEntry)(ctx.mesh.id, {
5185
+ kind: "magi_replica_retry",
5186
+ payload: { taskId: task.id, kind, failReason }
5187
+ });
5188
+ } catch {
5189
+ }
5190
+ return true;
5191
+ } catch {
5192
+ return false;
5193
+ }
5194
+ };
4952
5195
  const tryResolveReplica = async (task, staleTaskIds2, staleReasons2, force) => {
4953
5196
  const taskId = task.id;
4954
5197
  const source = buildSource(task);
@@ -4971,7 +5214,7 @@ async function collectMagiResponses(ctx, args) {
4971
5214
  }
4972
5215
  return false;
4973
5216
  }
4974
- let parsed = null;
5217
+ let kindResult;
4975
5218
  try {
4976
5219
  const node = ctx.mesh.nodes.find((n) => (0, import_daemon_core4.meshNodeIdMatches)(n, task.assignedNodeId));
4977
5220
  if (!node) throw new Error("assigned node not in mesh");
@@ -4982,7 +5225,7 @@ async function collectMagiResponses(ctx, args) {
4982
5225
  tailLimit: 6
4983
5226
  });
4984
5227
  const payload = unwrapCommandPayload(result);
4985
- parsed = parseFirstMagiCandidateWithCompactFallback(payload, {
5228
+ kindResult = parseFirstMagiCandidateForKind(payload, kind, {
4986
5229
  sessionId: task.assignedSessionId
4987
5230
  });
4988
5231
  } catch (e) {
@@ -4993,22 +5236,28 @@ async function collectMagiResponses(ctx, args) {
4993
5236
  }
4994
5237
  return false;
4995
5238
  }
4996
- if (parsed) {
5239
+ if (kindResult.ok && kindResult.response) {
4997
5240
  const weak = replicaCompletionIsWeak(ctx.mesh.id, taskId);
4998
5241
  if (weak && !force) {
4999
- provisional.set(taskId, { source: { ...source, ok: true }, response: parsed });
5242
+ provisional.set(taskId, { source: { ...source, ok: true }, response: kindResult.response });
5000
5243
  return false;
5001
5244
  }
5002
- finalized.set(taskId, { source: { ...source, ok: true }, response: parsed });
5245
+ finalized.set(taskId, { source: { ...source, ok: true }, response: kindResult.response });
5003
5246
  return true;
5004
5247
  }
5248
+ const isSchemaFailure = kindResult.failReason === "missing_required_fields" || kindResult.failReason === "empty_evidence";
5249
+ if (isSchemaFailure && !retried.has(taskId) && !force) {
5250
+ retried.add(taskId);
5251
+ const sent = await sendKindRetry(task, kindResult.failReason);
5252
+ if (sent) return false;
5253
+ }
5005
5254
  if (force) {
5006
5255
  const prov = provisional.get(taskId);
5007
5256
  if (prov) {
5008
5257
  finalized.set(taskId, prov);
5009
5258
  return true;
5010
5259
  }
5011
- source.error = "unparseable_output";
5260
+ source.error = isSchemaFailure ? `schema_invalid: ${kindResult.failReason}` : "unparseable_output";
5012
5261
  finalized.set(taskId, { source, response: emptyResponse() });
5013
5262
  return true;
5014
5263
  }
@@ -5044,7 +5293,7 @@ async function collectMagiResponses(ctx, args) {
5044
5293
  const responses = args.replicaTaskIds.map((id) => finalized.get(id)).filter((r) => !!r);
5045
5294
  const terminal = presentIds.size === ids.size && finalTasks.every((t) => TERMINAL.has(String(t.status)));
5046
5295
  const staleCount = responses.filter((r) => r.source.stale === true).length;
5047
- return { responses, terminal, timedOut: !terminal, staleCount };
5296
+ return { responses, terminal, timedOut: !terminal, staleCount, retriedCount: retried.size };
5048
5297
  }
5049
5298
 
5050
5299
  // src/tools/mesh-tools-session.ts