zelari-code 2.19.0 → 2.21.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.
Files changed (37) hide show
  1. package/dist/cli/budget/retrievalPolicy.js +82 -0
  2. package/dist/cli/budget/retrievalPolicy.js.map +1 -0
  3. package/dist/cli/hooks/permissionPicker.js +9 -1
  4. package/dist/cli/hooks/permissionPicker.js.map +1 -1
  5. package/dist/cli/hooks/useChatTurn.js +10 -2
  6. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  7. package/dist/cli/main.bundled.js +1330 -859
  8. package/dist/cli/main.bundled.js.map +4 -4
  9. package/dist/cli/main.js +22 -5
  10. package/dist/cli/main.js.map +1 -1
  11. package/dist/cli/mcp/httpTransport.js +184 -0
  12. package/dist/cli/mcp/httpTransport.js.map +1 -0
  13. package/dist/cli/mcp/mcpClient.js +153 -54
  14. package/dist/cli/mcp/mcpClient.js.map +1 -1
  15. package/dist/cli/mcp/mcpConfigIo.js +26 -5
  16. package/dist/cli/mcp/mcpConfigIo.js.map +1 -1
  17. package/dist/cli/mcp/mcpManager.js +51 -3
  18. package/dist/cli/mcp/mcpManager.js.map +1 -1
  19. package/dist/cli/mcp/mcpPresets.js +42 -1
  20. package/dist/cli/mcp/mcpPresets.js.map +1 -1
  21. package/dist/cli/provider/capabilities.js +3 -0
  22. package/dist/cli/provider/capabilities.js.map +1 -1
  23. package/dist/cli/provider/openai-compatible.js +27 -1
  24. package/dist/cli/provider/openai-compatible.js.map +1 -1
  25. package/dist/cli/runHeadless.js +7 -2
  26. package/dist/cli/runHeadless.js.map +1 -1
  27. package/dist/cli/safety/resourceClaims.js +33 -2
  28. package/dist/cli/safety/resourceClaims.js.map +1 -1
  29. package/dist/cli/safety/toolPermissions.js +11 -4
  30. package/dist/cli/safety/toolPermissions.js.map +1 -1
  31. package/dist/cli/skillConfigIo.js +1 -0
  32. package/dist/cli/skillConfigIo.js.map +1 -1
  33. package/dist/cli/toolRegistry.js +16 -2
  34. package/dist/cli/toolRegistry.js.map +1 -1
  35. package/dist/cli/tools/skillTool.js +20 -6
  36. package/dist/cli/tools/skillTool.js.map +1 -1
  37. package/package.json +2 -2
@@ -29977,8 +29977,9 @@ var init_service = __esm({
29977
29977
  const text = (raw.query ?? raw.text ?? "").slice(0, 64e3);
29978
29978
  const limit = Math.max(1, Math.min(raw.limit ?? 8, 100));
29979
29979
  this.emit({ type: "memory_recall_start" });
29980
+ const { weights, ...recallQuery } = raw;
29980
29981
  const candidates = await this.backend.search({
29981
- ...raw,
29982
+ ...recallQuery,
29982
29983
  text,
29983
29984
  projectId: this.projectId,
29984
29985
  statuses: raw.statuses ?? (raw.includeHistorical ? void 0 : ["active"]),
@@ -30025,7 +30026,7 @@ var init_service = __esm({
30025
30026
  }
30026
30027
  }
30027
30028
  }
30028
- const ranked = diverse(rankMemoryCandidates([...byId.values()], text), limit);
30029
+ const ranked = diverse(rankMemoryCandidates([...byId.values()], text, weights ? { weights } : {}), limit);
30029
30030
  this.emit({
30030
30031
  type: "memory_recall_end",
30031
30032
  durationMs: Date.now() - started,
@@ -40721,25 +40722,883 @@ var init_skillsMd = __esm({
40721
40722
  }
40722
40723
  });
40723
40724
 
40725
+ // src/cli/budget/historySummary.ts
40726
+ function extractiveHistorySummary(dropped, opts) {
40727
+ const maxChars = opts?.maxChars ?? MAX_SUMMARY_CHARS;
40728
+ if (dropped.length === 0) return "No prior turns.";
40729
+ const userGoals = [];
40730
+ const assistantNotes = [];
40731
+ const userConstraints = [];
40732
+ const unresolved = [];
40733
+ const verification = [];
40734
+ const decisions = [];
40735
+ const tools = /* @__PURE__ */ new Map();
40736
+ const files = /* @__PURE__ */ new Set();
40737
+ let toolResults = 0;
40738
+ for (const m of dropped) {
40739
+ if (m.role === "user" && m.content.trim()) {
40740
+ const goal = oneLine(m.content, 220);
40741
+ userGoals.push(goal);
40742
+ if (/\b(must|never|required|only|do not|constraint|vincolo|deve|senza)\b/i.test(goal)) userConstraints.push(goal);
40743
+ } else if (m.role === "assistant") {
40744
+ if (m.content.trim()) {
40745
+ const note = oneLine(m.content, 220);
40746
+ assistantNotes.push(note);
40747
+ if (/\b(fail|failed|error|unresolved|remaining|todo|blocked|gap|errore|fallit|irrisolt|manca)\b/i.test(note)) unresolved.push(note);
40748
+ if (/\b(test|typecheck|build|verify|verification|passed|failed|green|red)\b/i.test(note)) verification.push(note);
40749
+ if (/\b(decid|decision|chosen|choose|scelt|adopt|implement)\b/i.test(note)) decisions.push(note);
40750
+ }
40751
+ if (m.toolCalls) {
40752
+ for (const tc of m.toolCalls) {
40753
+ tools.set(tc.name, (tools.get(tc.name) ?? 0) + 1);
40754
+ collectPaths3(tc.args, files);
40755
+ }
40756
+ }
40757
+ } else if (m.role === "tool") {
40758
+ toolResults += 1;
40759
+ collectPathsFromText(m.content, files);
40760
+ if (/\b(fail|failed|error|exception|blocked|errore|fallit)\b/i.test(m.content)) unresolved.push(oneLine(m.content, 220));
40761
+ if (/\b(test|typecheck|build|verify|passed|failed|success)\b/i.test(m.content)) verification.push(oneLine(m.content, 220));
40762
+ }
40763
+ }
40764
+ const parts = [
40765
+ "[history-summary] Earlier turns were compacted to stay within the context budget.",
40766
+ `Dropped ${dropped.length} message(s) (${userGoals.length} user, ${assistantNotes.length} assistant notes, ${toolResults} tool results).`
40767
+ ];
40768
+ if (userGoals.length) {
40769
+ parts.push("## User goals / requests");
40770
+ for (const g of userGoals.slice(-6)) parts.push(`- ${g}`);
40771
+ }
40772
+ if (userConstraints.length) {
40773
+ parts.push("## User constraints (preserve exactly)");
40774
+ for (const item of [...new Set(userConstraints)].slice(-8)) parts.push(`- ${item}`);
40775
+ }
40776
+ if (unresolved.length) {
40777
+ parts.push("## Unresolved failures / pending repair");
40778
+ for (const item of [...new Set(unresolved)].slice(-8)) parts.push(`- ${item}`);
40779
+ }
40780
+ if (verification.length) {
40781
+ parts.push("## Latest verification state");
40782
+ for (const item of [...new Set(verification)].slice(-6)) parts.push(`- ${item}`);
40783
+ }
40784
+ if (decisions.length) {
40785
+ parts.push("## Recent active decisions");
40786
+ for (const item of [...new Set(decisions)].slice(-6)) parts.push(`- ${item}`);
40787
+ }
40788
+ if (assistantNotes.length) {
40789
+ parts.push("## Assistant conclusions (truncated)");
40790
+ for (const a of assistantNotes.slice(-5)) parts.push(`- ${a}`);
40791
+ }
40792
+ if (tools.size) {
40793
+ const ranked = [...tools.entries()].sort((a, b) => b[1] - a[1]).slice(0, 12).map(([n, c]) => `${n}\xD7${c}`);
40794
+ parts.push(`## Tools used: ${ranked.join(", ")}`);
40795
+ }
40796
+ if (files.size) {
40797
+ const list = [...files].slice(0, 24);
40798
+ parts.push(`## Paths mentioned: ${list.join(", ")}`);
40799
+ }
40800
+ parts.push(
40801
+ "Continue from the recent messages below; do not re-ask goals already answered above unless the user changes them."
40802
+ );
40803
+ let out = parts.join("\n");
40804
+ if (out.length > maxChars) {
40805
+ out = `${out.slice(0, maxChars - 1)}\u2026`;
40806
+ }
40807
+ return out;
40808
+ }
40809
+ function oneLine(s, max) {
40810
+ const t = s.replace(/\s+/g, " ").trim();
40811
+ if (t.length <= max) return t;
40812
+ return `${t.slice(0, max - 1)}\u2026`;
40813
+ }
40814
+ function collectPaths3(args, out) {
40815
+ if (!args || typeof args !== "object") return;
40816
+ const obj = args;
40817
+ for (const key of ["path", "file", "filepath", "filePath", "target", "cwd"]) {
40818
+ const v = obj[key];
40819
+ if (typeof v === "string" && v.length > 1 && v.length < 260) {
40820
+ out.add(v.replace(/\\/g, "/"));
40821
+ }
40822
+ }
40823
+ if (typeof obj.file_path === "string") out.add(String(obj.file_path));
40824
+ }
40825
+ function collectPathsFromText(text, out) {
40826
+ const re = /(?:^|[\s"'`])((?:[\w.-]+\/)+[\w.-]+\.\w{1,8})/g;
40827
+ let m;
40828
+ let n = 0;
40829
+ while ((m = re.exec(text)) !== null && n < 8) {
40830
+ out.add(m[1]);
40831
+ n += 1;
40832
+ }
40833
+ }
40834
+ var MAX_SUMMARY_CHARS;
40835
+ var init_historySummary = __esm({
40836
+ "src/cli/budget/historySummary.ts"() {
40837
+ "use strict";
40838
+ MAX_SUMMARY_CHARS = 3500;
40839
+ }
40840
+ });
40841
+
40842
+ // src/cli/budget/llmCompact.ts
40843
+ function isLlmCompactEnabled() {
40844
+ const v = process.env.ZELARI_LLM_COMPACT?.trim().toLowerCase();
40845
+ if (v === "0" || v === "false" || v === "off" || v === "no") return false;
40846
+ return true;
40847
+ }
40848
+ function compactModelOverride() {
40849
+ const v = process.env.ZELARI_COMPACT_MODEL?.trim();
40850
+ return v ? v : void 0;
40851
+ }
40852
+ async function llmSummarizeHistoryReplay(input) {
40853
+ const override = input.overrideModel ?? compactModelOverride();
40854
+ const model = override ?? input.model;
40855
+ const cacheReuseExpected = !override;
40856
+ if (!isLlmCompactEnabled()) return { summary: null, model, cacheReuseExpected };
40857
+ if (input.droppedMessages.length === 0) {
40858
+ return { summary: null, model, cacheReuseExpected };
40859
+ }
40860
+ const messages = [
40861
+ ...input.systemMessages,
40862
+ ...input.droppedMessages,
40863
+ {
40864
+ role: "user",
40865
+ content: COMPACTION_INSTRUCTION
40866
+ }
40867
+ ];
40868
+ const controller = new AbortController();
40869
+ const timeout = setTimeout(() => controller.abort(), REPLAY_TIMEOUT_MS);
40870
+ const onOuterAbort = () => controller.abort();
40871
+ input.signal?.addEventListener("abort", onOuterAbort, { once: true });
40872
+ try {
40873
+ let text = "";
40874
+ let emittedToolCall = false;
40875
+ for await (const delta of input.providerStream({
40876
+ provider: input.provider,
40877
+ model,
40878
+ messages,
40879
+ // Tools stay advertised: dropping them would change the prefix token
40880
+ // sequence and destroy cache reuse (explicit DSH decision). They are
40881
+ // sorted canonically (same discipline as the live routed request and
40882
+ // the snapshot fingerprints) so the replay prefix is byte-identical.
40883
+ tools: [...input.tools].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
40884
+ signal: controller.signal,
40885
+ generation: {
40886
+ purpose: "compaction",
40887
+ temperature: 0.1,
40888
+ maxTokens: 900
40889
+ }
40890
+ })) {
40891
+ if (delta.kind === "text") text += delta.delta;
40892
+ if (delta.kind === "tool_call") emittedToolCall = true;
40893
+ }
40894
+ if (emittedToolCall) return { summary: null, model, cacheReuseExpected };
40895
+ if (!text.trim()) return { summary: null, model, cacheReuseExpected };
40896
+ return { summary: text.trim(), model, cacheReuseExpected };
40897
+ } catch {
40898
+ return { summary: null, model, cacheReuseExpected };
40899
+ } finally {
40900
+ clearTimeout(timeout);
40901
+ input.signal?.removeEventListener("abort", onOuterAbort);
40902
+ }
40903
+ }
40904
+ var COMPACTION_INSTRUCTION, REPLAY_TIMEOUT_MS;
40905
+ var init_llmCompact = __esm({
40906
+ "src/cli/budget/llmCompact.ts"() {
40907
+ "use strict";
40908
+ COMPACTION_INSTRUCTION = `
40909
+ You are now acting as a compaction engine for this coding-agent session.
40910
+
40911
+ Condense the conversation ABOVE into a compact checkpoint sufficient to continue the task.
40912
+
40913
+ Preserve:
40914
+ - user's goal and evolving intent
40915
+ - decisions already made
40916
+ - exact file paths and identifiers
40917
+ - code changes already completed
40918
+ - commands/errors that still matter
40919
+ - constraints
40920
+ - unfinished work
40921
+ - the single most likely next action
40922
+
40923
+ Do not call tools.
40924
+ Do not mention this summarization request.
40925
+ Output only the checkpoint.
40926
+ Be concise.
40927
+ `.trim();
40928
+ REPLAY_TIMEOUT_MS = 6e4;
40929
+ }
40930
+ });
40931
+
40932
+ // src/cli/hooks/historyCompaction.ts
40933
+ function compactedRangeFromDropped(dropped) {
40934
+ if (dropped.length === 0) return void 0;
40935
+ const seqs = [];
40936
+ const sources = [];
40937
+ for (const m of dropped) {
40938
+ const hasCompactRange = typeof m.compactedFromSeq === "number" && Number.isInteger(m.compactedFromSeq) && m.compactedFromSeq > 0 && typeof m.compactedToSeq === "number" && Number.isInteger(m.compactedToSeq) && m.compactedToSeq >= m.compactedFromSeq;
40939
+ if (hasCompactRange) {
40940
+ seqs.push(m.compactedFromSeq, m.compactedToSeq);
40941
+ sources.push(...m.sourceEventSeqs ?? []);
40942
+ if (typeof m.seq === "number" && Number.isInteger(m.seq) && m.seq > 0) {
40943
+ seqs.push(m.seq);
40944
+ sources.push(m.seq);
40945
+ }
40946
+ continue;
40947
+ }
40948
+ if (typeof m.seq !== "number" || !Number.isInteger(m.seq) || m.seq < 1) return void 0;
40949
+ seqs.push(m.seq);
40950
+ sources.push(m.seq);
40951
+ }
40952
+ return {
40953
+ fromSeq: Math.min(...seqs),
40954
+ toSeq: Math.max(...seqs),
40955
+ sourceEventSeqs: [...new Set(sources)]
40956
+ };
40957
+ }
40958
+ function withDroppedRange(result, dropped, strategy) {
40959
+ const range = compactedRangeFromDropped(dropped);
40960
+ if (!range) return { ...result, strategy };
40961
+ return { ...result, ...range, strategy };
40962
+ }
40963
+ function resolveMaxMessages(opts) {
40964
+ const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
40965
+ let turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
40966
+ if (opts?.durableStatePresent && !opts?.maxMessages && process.env.ZELARI_HISTORY_TURNS === void 0) {
40967
+ turns = Math.min(turns, 3);
40968
+ }
40969
+ if (turns <= 0) return 0;
40970
+ if (opts?.force && opts?.maxMessages) return Math.max(1, opts.maxMessages);
40971
+ return turns * 4;
40972
+ }
40973
+ function findValidCutIndex(messages, naiveCut) {
40974
+ let cut = naiveCut;
40975
+ while (cut < messages.length) {
40976
+ const kept = messages.slice(cut);
40977
+ const declared = /* @__PURE__ */ new Set();
40978
+ for (const m of kept) {
40979
+ if (m.role === "assistant" && m.toolCalls) {
40980
+ for (const tc of m.toolCalls) declared.add(tc.id);
40981
+ }
40982
+ }
40983
+ let moved = false;
40984
+ for (let k = 0; k < kept.length; k++) {
40985
+ const m = kept[k];
40986
+ if (m.role === "tool" && m.toolCallId && !declared.has(m.toolCallId)) {
40987
+ for (let j = cut - 1; j >= 0; j--) {
40988
+ const prev2 = messages[j];
40989
+ if (prev2.role === "assistant" && prev2.toolCalls && prev2.toolCalls.some((tc) => tc.id === m.toolCallId)) {
40990
+ cut = j;
40991
+ moved = true;
40992
+ break;
40993
+ }
40994
+ }
40995
+ break;
40996
+ }
40997
+ }
40998
+ if (!moved) break;
40999
+ }
41000
+ return cut;
41001
+ }
41002
+ function resolvePruneLimits(opts) {
41003
+ const maxChars = opts?.maxChars ?? envNumber(process.env.ZELARI_TOOL_RESULT_MAX_CHARS, { default: 8e3, min: 256 });
41004
+ const rawTail = opts?.tailChars ?? envNumber(process.env.ZELARI_TOOL_RESULT_TAIL_CHARS, { default: 1e3, min: 0 });
41005
+ const tailChars = Math.min(rawTail, maxChars);
41006
+ return { maxChars, tailChars };
41007
+ }
41008
+ function pruneToolResultsDetailed(messages, opts) {
41009
+ const { maxChars, tailChars } = resolvePruneLimits(opts);
41010
+ const headChars = maxChars - tailChars;
41011
+ const stats = { pruned: 0, charsOmitted: 0 };
41012
+ let changed = false;
41013
+ const out = messages.map((m) => {
41014
+ if (m.role !== "tool") return m;
41015
+ const body = m.content ?? "";
41016
+ if (body.length <= maxChars) return m;
41017
+ const head = headChars > 0 ? body.slice(0, headChars) : "";
41018
+ const tail2 = tailChars > 0 ? body.slice(-tailChars) : "";
41019
+ const omitted = body.length - head.length - tail2.length;
41020
+ changed = true;
41021
+ stats.pruned += 1;
41022
+ stats.charsOmitted += omitted;
41023
+ return {
41024
+ ...m,
41025
+ content: [head, "\u2026[pruned " + omitted + " chars]\u2026", tail2].join(String.fromCharCode(10))
41026
+ };
41027
+ });
41028
+ return {
41029
+ messages: changed ? out : messages,
41030
+ stats
41031
+ };
41032
+ }
41033
+ function compactHistory(messages, opts) {
41034
+ return compactHistoryDetailed(messages, opts).messages;
41035
+ }
41036
+ function buildCheckpointMessage(summaryText, range) {
41037
+ return {
41038
+ role: "user",
41039
+ content: CHECKPOINT_WRAPPER_PREFIX + "\n\n<compacted-summary>\n" + summaryText + "\n</compacted-summary>",
41040
+ ...range ? {
41041
+ compactedFromSeq: range.fromSeq,
41042
+ compactedToSeq: range.toSeq,
41043
+ sourceEventSeqs: [...range.sourceEventSeqs]
41044
+ } : {}
41045
+ };
41046
+ }
41047
+ function compactHistoryDetailed(messages, opts) {
41048
+ const maxMessages = resolveMaxMessages(opts);
41049
+ if (maxMessages === 0) {
41050
+ return withDroppedRange(
41051
+ { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" },
41052
+ messages,
41053
+ "extractive"
41054
+ );
41055
+ }
41056
+ if (messages.length <= maxMessages * 2 && !opts?.force) {
41057
+ return {
41058
+ messages,
41059
+ compacted: false,
41060
+ messagesRemoved: 0,
41061
+ summary: ""
41062
+ };
41063
+ }
41064
+ const naiveCut = Math.max(0, messages.length - maxMessages);
41065
+ const cut = findValidCutIndex(messages, naiveCut);
41066
+ if (cut === 0) {
41067
+ return {
41068
+ messages,
41069
+ compacted: false,
41070
+ messagesRemoved: 0,
41071
+ summary: ""
41072
+ };
41073
+ }
41074
+ const droppedMsgs = messages.slice(0, cut);
41075
+ const droppedRange = compactedRangeFromDropped(droppedMsgs);
41076
+ const pruned = pruneToolResultsDetailed(messages.slice(cut));
41077
+ const kept = pruned.messages;
41078
+ const summaryText = extractiveHistorySummary(droppedMsgs);
41079
+ const summary = buildCheckpointMessage(
41080
+ summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`,
41081
+ droppedRange
41082
+ );
41083
+ return withDroppedRange(
41084
+ {
41085
+ messages: [summary, ...kept],
41086
+ compacted: true,
41087
+ messagesRemoved: cut,
41088
+ summary: summary.content,
41089
+ prunedToolResults: pruned.stats.pruned
41090
+ },
41091
+ droppedMsgs,
41092
+ "extractive"
41093
+ );
41094
+ }
41095
+ async function compactHistoryAsync(messages, opts) {
41096
+ const base2 = compactHistoryDetailed(messages, opts);
41097
+ if (!base2.compacted || base2.messagesRemoved === 0) return base2;
41098
+ const cut = base2.messagesRemoved;
41099
+ const droppedMsgs = messages.slice(0, cut);
41100
+ const extractive = extractiveHistorySummary(droppedMsgs);
41101
+ let summaryText = extractive;
41102
+ let cacheReuseExpected;
41103
+ let replayExactPrefix;
41104
+ const canReplay = !!(opts?.providerStream && opts?.requestSnapshot);
41105
+ if (canReplay) {
41106
+ try {
41107
+ const replay = await llmSummarizeHistoryReplay({
41108
+ providerStream: opts.providerStream,
41109
+ provider: opts.requestSnapshot.provider,
41110
+ model: opts.requestSnapshot.model,
41111
+ systemMessages: opts.requestSnapshot.systemMessages,
41112
+ tools: opts.requestSnapshot.tools,
41113
+ droppedMessages: droppedMsgs,
41114
+ signal: opts?.signal
41115
+ });
41116
+ cacheReuseExpected = replay.cacheReuseExpected;
41117
+ if (replay.summary && replay.summary.trim().length > 40) {
41118
+ const sourceTokens = roughTokens(droppedMsgs);
41119
+ const summaryTok = Math.ceil(replay.summary.length / 4);
41120
+ if (summaryTok < sourceTokens) {
41121
+ summaryText = replay.summary.trim();
41122
+ }
41123
+ }
41124
+ } catch {
41125
+ }
41126
+ }
41127
+ const pruned = pruneToolResultsDetailed(messages.slice(cut));
41128
+ const kept = pruned.messages;
41129
+ const summary = buildCheckpointMessage(summaryText, compactedRangeFromDropped(droppedMsgs));
41130
+ const usedLlm = summaryText !== extractive && summaryText.trim().length > 40;
41131
+ return withDroppedRange(
41132
+ {
41133
+ messages: [summary, ...kept],
41134
+ compacted: true,
41135
+ messagesRemoved: cut,
41136
+ summary: summaryText,
41137
+ prunedToolResults: pruned.stats.pruned,
41138
+ cacheReuseExpected,
41139
+ replayExactPrefix
41140
+ },
41141
+ droppedMsgs,
41142
+ usedLlm ? "llm" : "extractive"
41143
+ );
41144
+ }
41145
+ function roughTokens(msgs) {
41146
+ let n = 0;
41147
+ for (const m of msgs) {
41148
+ n += Math.ceil((m.content ?? "").length / 4);
41149
+ if (m.toolCalls) {
41150
+ for (const tc of m.toolCalls) {
41151
+ n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
41152
+ }
41153
+ }
41154
+ }
41155
+ return Math.max(1, n);
41156
+ }
41157
+ var COMPACT_MARKER, CHECKPOINT_WRAPPER_PREFIX;
41158
+ var init_historyCompaction = __esm({
41159
+ "src/cli/hooks/historyCompaction.ts"() {
41160
+ "use strict";
41161
+ init_historySummary();
41162
+ init_llmCompact();
41163
+ init_envNumber();
41164
+ COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
41165
+ CHECKPOINT_WRAPPER_PREFIX = "This is an automatically generated checkpoint of earlier conversation. Treat it as established context and continue directly.";
41166
+ }
41167
+ });
41168
+
41169
+ // src/cli/provider/capabilities.ts
41170
+ function frozenProfile(input) {
41171
+ if (input.reasoning.levels) Object.freeze(input.reasoning.levels);
41172
+ Object.freeze(input.reasoning);
41173
+ Object.freeze(input.promptCache);
41174
+ Object.freeze(input.toolCalling);
41175
+ Object.freeze(input.buildRecovery);
41176
+ Object.freeze(input.sampling);
41177
+ Object.freeze(input.compaction);
41178
+ return Object.freeze(input);
41179
+ }
41180
+ function resolveHarnessProfile(model, providerId) {
41181
+ const provider = providerId?.trim().toLowerCase();
41182
+ if (provider === "deepseek") return "deepseek-v4";
41183
+ if (provider === "grok") return "grok";
41184
+ if (provider === "minimax") return "minimax";
41185
+ if (provider === "glm") return "glm";
41186
+ if (model && DEEPSEEK_RE.test(model)) return "deepseek-v4";
41187
+ if (model && GROK_RE.test(model)) return "grok";
41188
+ if (model && MINIMAX_RE.test(model)) return "minimax";
41189
+ if (model && GLM_RE.test(model)) return "glm";
41190
+ return "default";
41191
+ }
41192
+ function capabilitiesFor(model, providerId) {
41193
+ switch (resolveHarnessProfile(model, providerId)) {
41194
+ case "deepseek-v4":
41195
+ return DEEPSEEK_V4_CAPS;
41196
+ case "grok":
41197
+ return GROK_CAPS;
41198
+ case "minimax":
41199
+ return model && MINIMAX_M3_RE.test(model) ? MINIMAX_M3_CAPS : MINIMAX_M2_CAPS;
41200
+ case "glm":
41201
+ return GLM_CAPS;
41202
+ default:
41203
+ return DEFAULT_CAPS;
41204
+ }
41205
+ }
41206
+ var SHARED_COMPACTION, DEFAULT_CAPS, DEEPSEEK_V4_CAPS, GROK_CAPS, MINIMAX_M3_CAPS, MINIMAX_M2_CAPS, GLM_CAPS, DEEPSEEK_RE, GROK_RE, MINIMAX_RE, MINIMAX_M3_RE, GLM_RE;
41207
+ var init_capabilities = __esm({
41208
+ "src/cli/provider/capabilities.ts"() {
41209
+ "use strict";
41210
+ SHARED_COMPACTION = { warnAt: 0.7, compactAt: 0.85, hardAt: 0.95 };
41211
+ DEFAULT_CAPS = frozenProfile({
41212
+ contextWindow: 4e5,
41213
+ reasoning: { supported: true, levels: ["low", "medium", "high"], replayReasoning: true },
41214
+ promptCache: { supported: true, pricedCacheRead: false },
41215
+ toolCalling: { parallel: true },
41216
+ buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
41217
+ sampling: { temperature: 0.7 },
41218
+ compaction: { ...SHARED_COMPACTION },
41219
+ profile: "default"
41220
+ });
41221
+ DEEPSEEK_V4_CAPS = frozenProfile({
41222
+ contextWindow: 1e6,
41223
+ reasoning: { supported: true, levels: ["high", "max"], replayReasoning: true },
41224
+ promptCache: { supported: true, pricedCacheRead: true },
41225
+ toolCalling: { parallel: true },
41226
+ buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
41227
+ sampling: { temperature: 0.7 },
41228
+ compaction: { ...SHARED_COMPACTION },
41229
+ profile: "deepseek-v4"
41230
+ });
41231
+ GROK_CAPS = frozenProfile({
41232
+ contextWindow: 5e5,
41233
+ // grok-4.x truncation fix: explicit conversation ceiling (see interface
41234
+ // doc). Override at runtime via ZELARI_MAX_OUTPUT_TOKENS.
41235
+ maxOutputTokens: 32768,
41236
+ reasoning: {
41237
+ supported: true,
41238
+ levels: ["low", "medium", "high", "xhigh"],
41239
+ replayReasoning: false
41240
+ },
41241
+ promptCache: {
41242
+ supported: true,
41243
+ pricedCacheRead: true,
41244
+ conversationAffinityHeader: "x-grok-conv-id"
41245
+ },
41246
+ toolCalling: { parallel: true },
41247
+ buildRecovery: { forceToolChoice: true, maxForcedTurns: 1 },
41248
+ sampling: { temperature: 0.7 },
41249
+ compaction: { ...SHARED_COMPACTION },
41250
+ profile: "grok"
41251
+ });
41252
+ MINIMAX_M3_CAPS = frozenProfile({
41253
+ contextWindow: 1e6,
41254
+ reasoning: { supported: true, replayReasoning: true },
41255
+ promptCache: { supported: false, pricedCacheRead: false },
41256
+ toolCalling: { parallel: true },
41257
+ buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
41258
+ sampling: { temperature: 0.7 },
41259
+ compaction: { ...SHARED_COMPACTION },
41260
+ profile: "minimax"
41261
+ });
41262
+ MINIMAX_M2_CAPS = frozenProfile({
41263
+ contextWindow: 204800,
41264
+ reasoning: { supported: true, replayReasoning: true },
41265
+ promptCache: { supported: false, pricedCacheRead: false },
41266
+ toolCalling: { parallel: true },
41267
+ buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
41268
+ sampling: { temperature: 0.7 },
41269
+ compaction: { ...SHARED_COMPACTION },
41270
+ profile: "minimax"
41271
+ });
41272
+ GLM_CAPS = frozenProfile({
41273
+ contextWindow: 2e5,
41274
+ reasoning: { supported: true, replayReasoning: true },
41275
+ promptCache: { supported: true, pricedCacheRead: false },
41276
+ toolCalling: { parallel: true },
41277
+ buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
41278
+ sampling: { temperature: 0.7 },
41279
+ compaction: { ...SHARED_COMPACTION },
41280
+ profile: "glm"
41281
+ });
41282
+ DEEPSEEK_RE = /^deepseek-(?:v4(?:\.|-|$)|chat$|reasoner$)/i;
41283
+ GROK_RE = /^grok(?:\.|-|$)/i;
41284
+ MINIMAX_RE = /^minimax(?:\.|-|$)/i;
41285
+ MINIMAX_M3_RE = /^minimax-m3(?:\.|-|$)/i;
41286
+ GLM_RE = /^glm(?:\.|-|$)/i;
41287
+ }
41288
+ });
41289
+
41290
+ // src/cli/budget/tokenBudget.ts
41291
+ function mergeCompactRange(into, r) {
41292
+ if (r.fromSeq !== void 0 && r.toSeq !== void 0) {
41293
+ into.fromSeq = into.fromSeq === void 0 ? r.fromSeq : Math.min(into.fromSeq, r.fromSeq);
41294
+ into.toSeq = into.toSeq === void 0 ? r.toSeq : Math.max(into.toSeq, r.toSeq);
41295
+ if (r.sourceEventSeqs) into.sourceSeqs.push(...r.sourceEventSeqs);
41296
+ }
41297
+ if (r.strategy === "llm") into.strategy = "llm";
41298
+ else if (r.strategy && !into.strategy) into.strategy = r.strategy;
41299
+ }
41300
+ function estimateTokens2(text) {
41301
+ if (!text) return 0;
41302
+ return Math.max(1, Math.ceil(text.length / 4));
41303
+ }
41304
+ function estimateHistoryTokens(messages) {
41305
+ let n = 0;
41306
+ for (const m of messages) {
41307
+ n += estimateTokens2(m.content);
41308
+ if (m.toolCalls) {
41309
+ for (const tc of m.toolCalls) {
41310
+ n += estimateTokens2(tc.name) + estimateTokens2(JSON.stringify(tc.args ?? {}));
41311
+ }
41312
+ }
41313
+ }
41314
+ return n;
41315
+ }
41316
+ function defaultContextLimitForModel(model, provider) {
41317
+ return capabilitiesFor(model, provider).contextWindow;
41318
+ }
41319
+ function resolveContextLimit(model, provider) {
41320
+ return envNumber(process.env.ZELARI_CONTEXT_LIMIT, {
41321
+ default: defaultContextLimitForModel(model, provider),
41322
+ min: 4e3,
41323
+ max: 2e6
41324
+ });
41325
+ }
41326
+ function phaseKnobs(phase2) {
41327
+ return {
41328
+ historyTurns: phase2 === "plan" ? envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 8, min: 0 }) : envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 }),
41329
+ maxToolLoopIterations: phase2 === "plan" ? envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 60, min: 1 }) : envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
41330
+ default: 120,
41331
+ min: 1
41332
+ })
41333
+ };
41334
+ }
41335
+ async function applyBudgetPolicyAsync(history2, phase2, opts) {
41336
+ const contextLimit = resolveContextLimit(opts?.model, opts?.provider);
41337
+ const compact3 = capabilitiesFor(opts?.model, opts?.provider).compaction;
41338
+ const sessionExtra = opts?.sessionTokens ?? 0;
41339
+ const warnings = [];
41340
+ let { historyTurns, maxToolLoopIterations } = phaseKnobs(phase2);
41341
+ const envelope = opts?.requestSnapshot ?? null;
41342
+ const surface = envelope?.snapshot ?? opts?.requestSurface ?? null;
41343
+ const replayBase = surface ? {
41344
+ provider: surface.provider,
41345
+ model: surface.model,
41346
+ systemMessages: surface.systemMessages,
41347
+ tools: surface.tools
41348
+ } : opts?.providerStream ? { provider: "local", model: opts?.model ?? "unknown", systemMessages: [], tools: [] } : null;
41349
+ const headerTokens = surface ? estimateSystemTokensLite(surface.systemMessages) + estimateToolSchemaTokensLite(surface.tools) : 0;
41350
+ const convTokensOf = (h) => estimateConversationTokensLite(h);
41351
+ let hist = history2;
41352
+ let estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
41353
+ let occupancy = Math.min(1, estimated / contextLimit);
41354
+ let compactSummary = "";
41355
+ let messagesRemoved = 0;
41356
+ let cacheReuseExpected;
41357
+ let prunedTotal = 0;
41358
+ const compactRange = { sourceSeqs: [] };
41359
+ if (occupancy >= compact3.warnAt && occupancy < compact3.compactAt) {
41360
+ warnings.push(
41361
+ `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated}/${contextLimit} tok full-request est.) \u2014 consider /compact or shorter replies.`
41362
+ );
41363
+ }
41364
+ if (occupancy >= 0.8) {
41365
+ const pruned = pruneToolResultsDetailed(hist);
41366
+ if (pruned.stats.pruned > 0) {
41367
+ hist = pruned.messages;
41368
+ prunedTotal += pruned.stats.pruned;
41369
+ estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
41370
+ occupancy = Math.min(1, estimated / contextLimit);
41371
+ warnings.push(
41372
+ `[budget] pruned ${pruned.stats.pruned} oversized tool result(s) \u2192 ${Math.round(occupancy * 100)}% (${estimated} tok).`
41373
+ );
41374
+ }
41375
+ }
41376
+ const fold = (r, label, forcedTurns) => {
41377
+ hist = applySessionSurface(r.messages);
41378
+ if (r.compacted) {
41379
+ messagesRemoved += r.messagesRemoved;
41380
+ if (r.summary) compactSummary = r.summary;
41381
+ mergeCompactRange(compactRange, r);
41382
+ if (r.cacheReuseExpected !== void 0) {
41383
+ cacheReuseExpected = r.cacheReuseExpected;
41384
+ }
41385
+ }
41386
+ estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
41387
+ occupancy = Math.min(1, estimated / contextLimit);
41388
+ warnings.push(
41389
+ `[budget] ${label} \u2014 kept ~${forcedTurns} turns (${estimated} tok est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + (r.cacheReuseExpected === false ? ", cache reuse NOT expected (model override)" : "") + ")."
41390
+ );
41391
+ };
41392
+ if (occupancy >= compact3.compactAt) {
41393
+ const forcedTurns = Math.max(1, Math.floor(historyTurns / 2));
41394
+ historyTurns = forcedTurns;
41395
+ maxToolLoopIterations = Math.min(
41396
+ maxToolLoopIterations,
41397
+ phase2 === "plan" ? 24 : 40
41398
+ );
41399
+ let r = await compactHistoryAsync(hist, {
41400
+ maxMessages: Math.max(2, forcedTurns * 4),
41401
+ force: true,
41402
+ signal: opts?.signal,
41403
+ ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
41404
+ });
41405
+ if (!r.compacted) {
41406
+ r = await compactHistoryAsync(hist, {
41407
+ maxMessages: 2,
41408
+ force: true,
41409
+ signal: opts?.signal,
41410
+ ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
41411
+ });
41412
+ }
41413
+ const label = r.cacheReuseExpected === false ? "llm-compact (model override)" : "auto-compact";
41414
+ fold(r, label, forcedTurns);
41415
+ }
41416
+ if (occupancy >= compact3.hardAt) {
41417
+ const hard = await compactHistoryAsync(hist, {
41418
+ maxMessages: 2,
41419
+ force: true,
41420
+ signal: opts?.signal
41421
+ });
41422
+ fold(hard, hard.summary.includes("\xB7 llm") ? "llm-compact" : "HARD trim", 2);
41423
+ historyTurns = 2;
41424
+ maxToolLoopIterations = Math.min(maxToolLoopIterations, 16);
41425
+ warnings.push(
41426
+ "[budget] HARD context pressure (\u226595%) \u2014 prefer /clear or a new session if quality drops."
41427
+ );
41428
+ }
41429
+ const cacheMetricsLine = envelope ? [
41430
+ "compaction meter:",
41431
+ `provider/model: ${envelope.snapshot.provider}/${envelope.snapshot.model}`,
41432
+ `headerFingerprint: ${envelope.snapshot.headerFingerprint.slice(0, 12)}`,
41433
+ `occupancy: ${Math.round(occupancy * 100)}% (${estimated}/${contextLimit})`,
41434
+ ...envelope.usage?.cachedPromptTokens !== void 0 ? [`cachedPromptTokens: ${envelope.usage.cachedPromptTokens}`] : [],
41435
+ ...cacheReuseExpected !== void 0 ? [`cacheReuseExpected: ${cacheReuseExpected}`] : []
41436
+ ].join(" | ") : void 0;
41437
+ return {
41438
+ history: hist,
41439
+ warnings,
41440
+ maxToolLoopIterations,
41441
+ historyTurns,
41442
+ estimatedHistoryTokens: surface ? convTokensOf(hist) : estimated,
41443
+ contextLimit,
41444
+ occupancy,
41445
+ compactSummary: compactSummary || void 0,
41446
+ messagesRemoved: messagesRemoved || void 0,
41447
+ ...compactRange.fromSeq !== void 0 && compactRange.toSeq !== void 0 ? {
41448
+ compactedFromSeq: compactRange.fromSeq,
41449
+ compactedToSeq: compactRange.toSeq,
41450
+ compactSourceSeqs: compactRange.sourceSeqs,
41451
+ compactStrategy: compactRange.strategy
41452
+ } : {},
41453
+ ...surface ? { contextPressureTokens: estimated } : {},
41454
+ ...cacheReuseExpected !== void 0 ? { cacheReuseExpected } : {},
41455
+ ...cacheMetricsLine ? { cacheMetricsLine } : {}
41456
+ };
41457
+ }
41458
+ function estimateSystemTokensLite(systemMessages) {
41459
+ let n = 0;
41460
+ for (const m of systemMessages) n += 4 + Math.ceil((m.content ?? "").length / 4);
41461
+ return n;
41462
+ }
41463
+ function estimateToolSchemaTokensLite(tools) {
41464
+ let n = 0;
41465
+ for (const t of tools) {
41466
+ n += Math.ceil((t.name ?? "").length / 4);
41467
+ n += Math.ceil((t.description ?? "").length / 4);
41468
+ n += Math.ceil(JSON.stringify(t.parameters ?? {}).length / 4);
41469
+ }
41470
+ return n + tools.length * 4;
41471
+ }
41472
+ function estimateConversationTokensLite(messages) {
41473
+ let n = 0;
41474
+ for (const m of messages) {
41475
+ n += 4 + Math.ceil((m.content ?? "").length / 4);
41476
+ if (m.toolCalls) {
41477
+ for (const tc of m.toolCalls) {
41478
+ n += Math.ceil(tc.name.length / 4) + Math.ceil(tc.id.length / 4);
41479
+ n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
41480
+ }
41481
+ }
41482
+ if (m.reasoningContent) n += Math.ceil(m.reasoningContent.length / 4);
41483
+ if (m.toolCallId) n += Math.ceil(m.toolCallId.length / 4);
41484
+ }
41485
+ return n;
41486
+ }
41487
+ var RESERVED_OUTPUT_TOKENS;
41488
+ var init_tokenBudget = __esm({
41489
+ "src/cli/budget/tokenBudget.ts"() {
41490
+ "use strict";
41491
+ init_envNumber();
41492
+ init_historyCompaction();
41493
+ init_observationStore();
41494
+ init_capabilities();
41495
+ RESERVED_OUTPUT_TOKENS = 8192;
41496
+ }
41497
+ });
41498
+
41499
+ // src/cli/budget/retrievalPolicy.ts
41500
+ var retrievalPolicy_exports = {};
41501
+ __export(retrievalPolicy_exports, {
41502
+ BASELINE_RETRIEVAL_POLICY: () => BASELINE_RETRIEVAL_POLICY,
41503
+ RETRIEVAL_PRESSURE_THRESHOLDS: () => RETRIEVAL_PRESSURE_THRESHOLDS,
41504
+ estimateHistoryOccupancy: () => estimateHistoryOccupancy,
41505
+ filterSkillCatalogByBand: () => filterSkillCatalogByBand,
41506
+ resolveRetrievalPolicy: () => resolveRetrievalPolicy,
41507
+ retrievalBand: () => retrievalBand
41508
+ });
41509
+ function retrievalBand(occupancy) {
41510
+ const occ = Number.isFinite(occupancy) ? Math.max(0, Math.min(1, occupancy)) : 0;
41511
+ if (occ >= RETRIEVAL_PRESSURE_THRESHOLDS.high) return "high";
41512
+ if (occ >= RETRIEVAL_PRESSURE_THRESHOLDS.medium) return "medium";
41513
+ return "low";
41514
+ }
41515
+ function resolveRetrievalPolicy(occupancy) {
41516
+ const band = retrievalBand(occupancy);
41517
+ const packing = PRESSURE_PACKING[band];
41518
+ const weights = PRESSURE_WEIGHTS[band];
41519
+ return {
41520
+ band,
41521
+ ...packing,
41522
+ ...weights ? { weights } : {}
41523
+ };
41524
+ }
41525
+ function estimateHistoryOccupancy(history2, opts) {
41526
+ const limit = resolveContextLimit(opts?.model, opts?.provider);
41527
+ return Math.min(1, estimateHistoryTokens(history2) / limit);
41528
+ }
41529
+ function filterSkillCatalogByBand(skills, band) {
41530
+ if (band !== "high") return [...skills];
41531
+ return skills.filter((s) => s.estimatedCost !== "high");
41532
+ }
41533
+ var RETRIEVAL_PRESSURE_THRESHOLDS, BASELINE_RETRIEVAL_POLICY, PRESSURE_WEIGHTS, PRESSURE_PACKING;
41534
+ var init_retrievalPolicy = __esm({
41535
+ "src/cli/budget/retrievalPolicy.ts"() {
41536
+ "use strict";
41537
+ init_tokenBudget();
41538
+ RETRIEVAL_PRESSURE_THRESHOLDS = { medium: 0.5, high: 0.8 };
41539
+ BASELINE_RETRIEVAL_POLICY = {
41540
+ band: "low",
41541
+ maxChars: 2e3,
41542
+ maxMemories: 8
41543
+ };
41544
+ PRESSURE_WEIGHTS = {
41545
+ low: void 0,
41546
+ medium: {
41547
+ semanticRelevance: 0.35,
41548
+ lexicalRelevance: 0.2,
41549
+ importance: 0.15,
41550
+ confidence: 0.1,
41551
+ recency: 0.05,
41552
+ graphProximity: 0.1,
41553
+ verificationBonus: 0.05
41554
+ },
41555
+ high: {
41556
+ semanticRelevance: 0.4,
41557
+ lexicalRelevance: 0.25,
41558
+ importance: 0.15,
41559
+ confidence: 0.1,
41560
+ recency: 0,
41561
+ graphProximity: 0.05,
41562
+ verificationBonus: 0.05
41563
+ }
41564
+ };
41565
+ PRESSURE_PACKING = {
41566
+ low: { maxChars: 2e3, maxMemories: 8 },
41567
+ medium: { maxChars: 1200, maxMemories: 6 },
41568
+ high: { maxChars: 600, maxMemories: 4 }
41569
+ };
41570
+ }
41571
+ });
41572
+
40724
41573
  // src/cli/tools/skillTool.ts
40725
- function formatAvailableSkillsCatalog(cwd = process.cwd()) {
41574
+ function formatAvailableSkillsCatalog(cwd = process.cwd(), opts) {
40726
41575
  try {
40727
41576
  loadSkillMdSkills(cwd);
40728
41577
  } catch {
40729
41578
  }
40730
- const skills = listCodingSkills();
40731
- if (skills.length === 0) {
41579
+ const band = opts?.pressureBand;
41580
+ const all = listCodingSkills();
41581
+ if (all.length === 0) {
40732
41582
  return "No skills registered.";
40733
41583
  }
40734
- const lines = skills.slice(0, 80).map((s) => {
41584
+ const gated = band ? filterSkillCatalogByBand(all, band) : all;
41585
+ const hidden = all.length - gated.length;
41586
+ const lines = gated.slice(0, 80).map((s) => {
40735
41587
  const desc = (s.description || s.id).replace(/\s+/g, " ").trim().slice(0, 160);
40736
41588
  return `- ${s.id}: ${desc}`;
40737
41589
  });
41590
+ if (hidden > 0) {
41591
+ lines.push(
41592
+ `(plus ${hidden} high-cost skill(s) hidden under context pressure \u2014 still loadable by name if truly needed)`
41593
+ );
41594
+ }
40738
41595
  return lines.join("\n");
40739
41596
  }
40740
41597
  function createSkillTool(opts) {
40741
41598
  const cwd = opts?.cwd ?? process.cwd();
40742
- const catalog = formatAvailableSkillsCatalog(cwd);
41599
+ const catalog = formatAvailableSkillsCatalog(cwd, {
41600
+ pressureBand: opts?.pressureBand
41601
+ });
40743
41602
  return {
40744
41603
  name: "skill",
40745
41604
  description: "Load the full instructions for a named coding skill into this turn. Call when a skill matches the current task. Available skills:\n" + catalog,
@@ -40781,6 +41640,7 @@ var init_skillTool = __esm({
40781
41640
  init_skills2();
40782
41641
  init_toolTypes();
40783
41642
  init_skillsMd();
41643
+ init_retrievalPolicy();
40784
41644
  SkillArgsSchema = external_exports.object({
40785
41645
  name: external_exports.string().min(1).describe("Skill id/name to load (from the available skills list).")
40786
41646
  });
@@ -43862,124 +44722,6 @@ var init_semantic2 = __esm({
43862
44722
  }
43863
44723
  });
43864
44724
 
43865
- // src/cli/provider/capabilities.ts
43866
- function frozenProfile(input) {
43867
- if (input.reasoning.levels) Object.freeze(input.reasoning.levels);
43868
- Object.freeze(input.reasoning);
43869
- Object.freeze(input.promptCache);
43870
- Object.freeze(input.toolCalling);
43871
- Object.freeze(input.buildRecovery);
43872
- Object.freeze(input.sampling);
43873
- Object.freeze(input.compaction);
43874
- return Object.freeze(input);
43875
- }
43876
- function resolveHarnessProfile(model, providerId) {
43877
- const provider = providerId?.trim().toLowerCase();
43878
- if (provider === "deepseek") return "deepseek-v4";
43879
- if (provider === "grok") return "grok";
43880
- if (provider === "minimax") return "minimax";
43881
- if (provider === "glm") return "glm";
43882
- if (model && DEEPSEEK_RE.test(model)) return "deepseek-v4";
43883
- if (model && GROK_RE.test(model)) return "grok";
43884
- if (model && MINIMAX_RE.test(model)) return "minimax";
43885
- if (model && GLM_RE.test(model)) return "glm";
43886
- return "default";
43887
- }
43888
- function capabilitiesFor(model, providerId) {
43889
- switch (resolveHarnessProfile(model, providerId)) {
43890
- case "deepseek-v4":
43891
- return DEEPSEEK_V4_CAPS;
43892
- case "grok":
43893
- return GROK_CAPS;
43894
- case "minimax":
43895
- return model && MINIMAX_M3_RE.test(model) ? MINIMAX_M3_CAPS : MINIMAX_M2_CAPS;
43896
- case "glm":
43897
- return GLM_CAPS;
43898
- default:
43899
- return DEFAULT_CAPS;
43900
- }
43901
- }
43902
- var SHARED_COMPACTION, DEFAULT_CAPS, DEEPSEEK_V4_CAPS, GROK_CAPS, MINIMAX_M3_CAPS, MINIMAX_M2_CAPS, GLM_CAPS, DEEPSEEK_RE, GROK_RE, MINIMAX_RE, MINIMAX_M3_RE, GLM_RE;
43903
- var init_capabilities = __esm({
43904
- "src/cli/provider/capabilities.ts"() {
43905
- "use strict";
43906
- SHARED_COMPACTION = { warnAt: 0.7, compactAt: 0.85, hardAt: 0.95 };
43907
- DEFAULT_CAPS = frozenProfile({
43908
- contextWindow: 4e5,
43909
- reasoning: { supported: true, levels: ["low", "medium", "high"], replayReasoning: true },
43910
- promptCache: { supported: true, pricedCacheRead: false },
43911
- toolCalling: { parallel: true },
43912
- buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
43913
- sampling: { temperature: 0.7 },
43914
- compaction: { ...SHARED_COMPACTION },
43915
- profile: "default"
43916
- });
43917
- DEEPSEEK_V4_CAPS = frozenProfile({
43918
- contextWindow: 1e6,
43919
- reasoning: { supported: true, levels: ["high", "max"], replayReasoning: true },
43920
- promptCache: { supported: true, pricedCacheRead: true },
43921
- toolCalling: { parallel: true },
43922
- buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
43923
- sampling: { temperature: 0.7 },
43924
- compaction: { ...SHARED_COMPACTION },
43925
- profile: "deepseek-v4"
43926
- });
43927
- GROK_CAPS = frozenProfile({
43928
- contextWindow: 5e5,
43929
- reasoning: {
43930
- supported: true,
43931
- levels: ["low", "medium", "high", "xhigh"],
43932
- replayReasoning: false
43933
- },
43934
- promptCache: {
43935
- supported: true,
43936
- pricedCacheRead: true,
43937
- conversationAffinityHeader: "x-grok-conv-id"
43938
- },
43939
- toolCalling: { parallel: true },
43940
- buildRecovery: { forceToolChoice: true, maxForcedTurns: 1 },
43941
- sampling: { temperature: 0.7 },
43942
- compaction: { ...SHARED_COMPACTION },
43943
- profile: "grok"
43944
- });
43945
- MINIMAX_M3_CAPS = frozenProfile({
43946
- contextWindow: 1e6,
43947
- reasoning: { supported: true, replayReasoning: true },
43948
- promptCache: { supported: false, pricedCacheRead: false },
43949
- toolCalling: { parallel: true },
43950
- buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
43951
- sampling: { temperature: 0.7 },
43952
- compaction: { ...SHARED_COMPACTION },
43953
- profile: "minimax"
43954
- });
43955
- MINIMAX_M2_CAPS = frozenProfile({
43956
- contextWindow: 204800,
43957
- reasoning: { supported: true, replayReasoning: true },
43958
- promptCache: { supported: false, pricedCacheRead: false },
43959
- toolCalling: { parallel: true },
43960
- buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
43961
- sampling: { temperature: 0.7 },
43962
- compaction: { ...SHARED_COMPACTION },
43963
- profile: "minimax"
43964
- });
43965
- GLM_CAPS = frozenProfile({
43966
- contextWindow: 2e5,
43967
- reasoning: { supported: true, replayReasoning: true },
43968
- promptCache: { supported: true, pricedCacheRead: false },
43969
- toolCalling: { parallel: true },
43970
- buildRecovery: { forceToolChoice: false, maxForcedTurns: 0 },
43971
- sampling: { temperature: 0.7 },
43972
- compaction: { ...SHARED_COMPACTION },
43973
- profile: "glm"
43974
- });
43975
- DEEPSEEK_RE = /^deepseek-(?:v4(?:\.|-|$)|chat$|reasoner$)/i;
43976
- GROK_RE = /^grok(?:\.|-|$)/i;
43977
- MINIMAX_RE = /^minimax(?:\.|-|$)/i;
43978
- MINIMAX_M3_RE = /^minimax-m3(?:\.|-|$)/i;
43979
- GLM_RE = /^glm(?:\.|-|$)/i;
43980
- }
43981
- });
43982
-
43983
44725
  // src/cli/provider/openai-compatible.ts
43984
44726
  var openai_compatible_exports = {};
43985
44727
  __export(openai_compatible_exports, {
@@ -44163,6 +44905,12 @@ ${notes}
44163
44905
  }
44164
44906
  return { role: m.role, content: m.content };
44165
44907
  }
44908
+ function positiveEnvInt(name) {
44909
+ const raw = process.env[name];
44910
+ if (!raw) return void 0;
44911
+ const n = Number.parseInt(raw, 10);
44912
+ return Number.isFinite(n) && n > 0 ? n : void 0;
44913
+ }
44166
44914
  function openaiCompatibleProvider(config2) {
44167
44915
  return async function* (params) {
44168
44916
  const capabilities = capabilitiesFor(params.model, config2.providerId);
@@ -44195,6 +44943,8 @@ function openaiCompatibleProvider(config2) {
44195
44943
  };
44196
44944
  if (typeof generation?.maxTokens === "number" && generation.maxTokens > 0) {
44197
44945
  body.max_tokens = generation.maxTokens;
44946
+ } else if (typeof capabilities.maxOutputTokens === "number" && capabilities.maxOutputTokens > 0) {
44947
+ body.max_tokens = positiveEnvInt("ZELARI_MAX_OUTPUT_TOKENS") ?? capabilities.maxOutputTokens;
44198
44948
  }
44199
44949
  const thinkingSpec = config2.thinking ?? "auto";
44200
44950
  if (config2.providerId === "deepseek" && thinkingSpec === "auto") {
@@ -46386,8 +47136,8 @@ function defaultPermissionPolicy(overrides) {
46386
47136
  return {
46387
47137
  read: parseAction(process.env.ZELARI_PERMISSION_READ, "allow"),
46388
47138
  write: parseAction(process.env.ZELARI_PERMISSION_WRITE, "allow"),
46389
- execute: parseAction(process.env.ZELARI_PERMISSION_EXECUTE, "allow"),
46390
- network: parseAction(process.env.ZELARI_PERMISSION_NETWORK, "allow"),
47139
+ execute: parseAction(process.env.ZELARI_PERMISSION_EXECUTE, "ask"),
47140
+ network: parseAction(process.env.ZELARI_PERMISSION_NETWORK, "ask"),
46391
47141
  ui: "allow",
46392
47142
  auto: isAutoPermissions(),
46393
47143
  ...overrides
@@ -47353,7 +48103,7 @@ function matchResourceClaimLayered(layers, precedence, claim, root) {
47353
48103
  function resolveClaimsVerdict(layers, precedence, toolName, args, root) {
47354
48104
  const claims = resourceClaimsFor(toolName, args);
47355
48105
  const matchedRules = [];
47356
- if (!layers || claims.length === 0) return { matchedRules };
48106
+ if (!layers || claims.length === 0) return { claims, matchedRules };
47357
48107
  const effects = [];
47358
48108
  for (const claim of claims) {
47359
48109
  const hit = matchResourceClaimLayered(layers, precedence, claim, root);
@@ -47362,7 +48112,26 @@ function resolveClaimsVerdict(layers, precedence, toolName, args, root) {
47362
48112
  matchedRules.push(hit);
47363
48113
  }
47364
48114
  }
47365
- return effects.length > 0 ? { effect: intersectEffects(...effects), matchedRules } : { matchedRules };
48115
+ return effects.length > 0 ? { effect: intersectEffects(...effects), claims, matchedRules } : { claims, matchedRules };
48116
+ }
48117
+ function describeResourceClaim(claim) {
48118
+ const cap3 = (s) => s.length > 160 ? `${s.slice(0, 157)}\u2026` : s;
48119
+ switch (claim.kind) {
48120
+ case "path":
48121
+ return cap3(`path ${claim.operation}: ${claim.path}`);
48122
+ case "process":
48123
+ return cap3(`process: ${[claim.executable, ...claim.argv ?? []].join(" ")}`);
48124
+ case "network":
48125
+ return cap3(`network: ${claim.host}${claim.port !== void 0 ? `:${claim.port}` : ""}`);
48126
+ case "mcp":
48127
+ return cap3(`mcp: ${claim.server}/${claim.tool}`);
48128
+ case "ssh":
48129
+ return cap3(`ssh: ${claim.target}${claim.command ? ` (${claim.command})` : ""}`);
48130
+ case "ui":
48131
+ return cap3(`ui: ${claim.action}`);
48132
+ case "agent":
48133
+ return cap3(`agent: ${claim.role}`);
48134
+ }
47366
48135
  }
47367
48136
  var MAX_RAW_SHELL_STRIP_DEPTH, RAW_SHELL_WRAPPERS, RAW_SHELL_INTERPRETERS;
47368
48137
  var init_resourceClaims = __esm({
@@ -47819,7 +48588,7 @@ function createBuiltinToolRegistry(options = {}) {
47819
48588
  registry4.register(withPerm(askUserTool));
47820
48589
  }
47821
48590
  const enableSkill = options.enableSkill !== false && options.readOnly !== true && !gauntletParent && profile !== "explore" && profile !== "verify";
47822
- const skillTool = enableSkill ? withPerm(createSkillTool({ cwd: root })) : null;
48591
+ const skillTool = enableSkill ? withPerm(createSkillTool({ cwd: root, pressureBand: options.pressureBand })) : null;
47823
48592
  if (skillTool) {
47824
48593
  registry4.register(skillTool);
47825
48594
  }
@@ -48113,11 +48882,17 @@ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence =
48113
48882
  );
48114
48883
  }
48115
48884
  try {
48885
+ const expanded = claims?.claims ?? resourceClaimsFor(original.name, input ?? {});
48116
48886
  const ok = await onAsk({
48117
48887
  toolName: original.name,
48118
48888
  reason: decision.reason,
48119
48889
  categories: decision.categories,
48120
- args: input
48890
+ args: input,
48891
+ policyNote: rulePrefix || void 0,
48892
+ claims: expanded.slice(0, 6).map((c) => ({
48893
+ kind: c.kind,
48894
+ summary: describeResourceClaim(c)
48895
+ }))
48121
48896
  });
48122
48897
  if (!ok) {
48123
48898
  return typedErr(
@@ -50500,450 +51275,6 @@ var init_messageHelpers = __esm({
50500
51275
  }
50501
51276
  });
50502
51277
 
50503
- // src/cli/budget/historySummary.ts
50504
- function extractiveHistorySummary(dropped, opts) {
50505
- const maxChars = opts?.maxChars ?? MAX_SUMMARY_CHARS;
50506
- if (dropped.length === 0) return "No prior turns.";
50507
- const userGoals = [];
50508
- const assistantNotes = [];
50509
- const userConstraints = [];
50510
- const unresolved = [];
50511
- const verification = [];
50512
- const decisions = [];
50513
- const tools = /* @__PURE__ */ new Map();
50514
- const files = /* @__PURE__ */ new Set();
50515
- let toolResults = 0;
50516
- for (const m of dropped) {
50517
- if (m.role === "user" && m.content.trim()) {
50518
- const goal = oneLine(m.content, 220);
50519
- userGoals.push(goal);
50520
- if (/\b(must|never|required|only|do not|constraint|vincolo|deve|senza)\b/i.test(goal)) userConstraints.push(goal);
50521
- } else if (m.role === "assistant") {
50522
- if (m.content.trim()) {
50523
- const note = oneLine(m.content, 220);
50524
- assistantNotes.push(note);
50525
- if (/\b(fail|failed|error|unresolved|remaining|todo|blocked|gap|errore|fallit|irrisolt|manca)\b/i.test(note)) unresolved.push(note);
50526
- if (/\b(test|typecheck|build|verify|verification|passed|failed|green|red)\b/i.test(note)) verification.push(note);
50527
- if (/\b(decid|decision|chosen|choose|scelt|adopt|implement)\b/i.test(note)) decisions.push(note);
50528
- }
50529
- if (m.toolCalls) {
50530
- for (const tc of m.toolCalls) {
50531
- tools.set(tc.name, (tools.get(tc.name) ?? 0) + 1);
50532
- collectPaths3(tc.args, files);
50533
- }
50534
- }
50535
- } else if (m.role === "tool") {
50536
- toolResults += 1;
50537
- collectPathsFromText(m.content, files);
50538
- if (/\b(fail|failed|error|exception|blocked|errore|fallit)\b/i.test(m.content)) unresolved.push(oneLine(m.content, 220));
50539
- if (/\b(test|typecheck|build|verify|passed|failed|success)\b/i.test(m.content)) verification.push(oneLine(m.content, 220));
50540
- }
50541
- }
50542
- const parts = [
50543
- "[history-summary] Earlier turns were compacted to stay within the context budget.",
50544
- `Dropped ${dropped.length} message(s) (${userGoals.length} user, ${assistantNotes.length} assistant notes, ${toolResults} tool results).`
50545
- ];
50546
- if (userGoals.length) {
50547
- parts.push("## User goals / requests");
50548
- for (const g of userGoals.slice(-6)) parts.push(`- ${g}`);
50549
- }
50550
- if (userConstraints.length) {
50551
- parts.push("## User constraints (preserve exactly)");
50552
- for (const item of [...new Set(userConstraints)].slice(-8)) parts.push(`- ${item}`);
50553
- }
50554
- if (unresolved.length) {
50555
- parts.push("## Unresolved failures / pending repair");
50556
- for (const item of [...new Set(unresolved)].slice(-8)) parts.push(`- ${item}`);
50557
- }
50558
- if (verification.length) {
50559
- parts.push("## Latest verification state");
50560
- for (const item of [...new Set(verification)].slice(-6)) parts.push(`- ${item}`);
50561
- }
50562
- if (decisions.length) {
50563
- parts.push("## Recent active decisions");
50564
- for (const item of [...new Set(decisions)].slice(-6)) parts.push(`- ${item}`);
50565
- }
50566
- if (assistantNotes.length) {
50567
- parts.push("## Assistant conclusions (truncated)");
50568
- for (const a of assistantNotes.slice(-5)) parts.push(`- ${a}`);
50569
- }
50570
- if (tools.size) {
50571
- const ranked = [...tools.entries()].sort((a, b) => b[1] - a[1]).slice(0, 12).map(([n, c]) => `${n}\xD7${c}`);
50572
- parts.push(`## Tools used: ${ranked.join(", ")}`);
50573
- }
50574
- if (files.size) {
50575
- const list = [...files].slice(0, 24);
50576
- parts.push(`## Paths mentioned: ${list.join(", ")}`);
50577
- }
50578
- parts.push(
50579
- "Continue from the recent messages below; do not re-ask goals already answered above unless the user changes them."
50580
- );
50581
- let out = parts.join("\n");
50582
- if (out.length > maxChars) {
50583
- out = `${out.slice(0, maxChars - 1)}\u2026`;
50584
- }
50585
- return out;
50586
- }
50587
- function oneLine(s, max) {
50588
- const t = s.replace(/\s+/g, " ").trim();
50589
- if (t.length <= max) return t;
50590
- return `${t.slice(0, max - 1)}\u2026`;
50591
- }
50592
- function collectPaths3(args, out) {
50593
- if (!args || typeof args !== "object") return;
50594
- const obj = args;
50595
- for (const key of ["path", "file", "filepath", "filePath", "target", "cwd"]) {
50596
- const v = obj[key];
50597
- if (typeof v === "string" && v.length > 1 && v.length < 260) {
50598
- out.add(v.replace(/\\/g, "/"));
50599
- }
50600
- }
50601
- if (typeof obj.file_path === "string") out.add(String(obj.file_path));
50602
- }
50603
- function collectPathsFromText(text, out) {
50604
- const re = /(?:^|[\s"'`])((?:[\w.-]+\/)+[\w.-]+\.\w{1,8})/g;
50605
- let m;
50606
- let n = 0;
50607
- while ((m = re.exec(text)) !== null && n < 8) {
50608
- out.add(m[1]);
50609
- n += 1;
50610
- }
50611
- }
50612
- var MAX_SUMMARY_CHARS;
50613
- var init_historySummary = __esm({
50614
- "src/cli/budget/historySummary.ts"() {
50615
- "use strict";
50616
- MAX_SUMMARY_CHARS = 3500;
50617
- }
50618
- });
50619
-
50620
- // src/cli/budget/llmCompact.ts
50621
- function isLlmCompactEnabled() {
50622
- const v = process.env.ZELARI_LLM_COMPACT?.trim().toLowerCase();
50623
- if (v === "0" || v === "false" || v === "off" || v === "no") return false;
50624
- return true;
50625
- }
50626
- function compactModelOverride() {
50627
- const v = process.env.ZELARI_COMPACT_MODEL?.trim();
50628
- return v ? v : void 0;
50629
- }
50630
- async function llmSummarizeHistoryReplay(input) {
50631
- const override = input.overrideModel ?? compactModelOverride();
50632
- const model = override ?? input.model;
50633
- const cacheReuseExpected = !override;
50634
- if (!isLlmCompactEnabled()) return { summary: null, model, cacheReuseExpected };
50635
- if (input.droppedMessages.length === 0) {
50636
- return { summary: null, model, cacheReuseExpected };
50637
- }
50638
- const messages = [
50639
- ...input.systemMessages,
50640
- ...input.droppedMessages,
50641
- {
50642
- role: "user",
50643
- content: COMPACTION_INSTRUCTION
50644
- }
50645
- ];
50646
- const controller = new AbortController();
50647
- const timeout = setTimeout(() => controller.abort(), REPLAY_TIMEOUT_MS);
50648
- const onOuterAbort = () => controller.abort();
50649
- input.signal?.addEventListener("abort", onOuterAbort, { once: true });
50650
- try {
50651
- let text = "";
50652
- let emittedToolCall = false;
50653
- for await (const delta of input.providerStream({
50654
- provider: input.provider,
50655
- model,
50656
- messages,
50657
- // Tools stay advertised: dropping them would change the prefix token
50658
- // sequence and destroy cache reuse (explicit DSH decision). They are
50659
- // sorted canonically (same discipline as the live routed request and
50660
- // the snapshot fingerprints) so the replay prefix is byte-identical.
50661
- tools: [...input.tools].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
50662
- signal: controller.signal,
50663
- generation: {
50664
- purpose: "compaction",
50665
- temperature: 0.1,
50666
- maxTokens: 900
50667
- }
50668
- })) {
50669
- if (delta.kind === "text") text += delta.delta;
50670
- if (delta.kind === "tool_call") emittedToolCall = true;
50671
- }
50672
- if (emittedToolCall) return { summary: null, model, cacheReuseExpected };
50673
- if (!text.trim()) return { summary: null, model, cacheReuseExpected };
50674
- return { summary: text.trim(), model, cacheReuseExpected };
50675
- } catch {
50676
- return { summary: null, model, cacheReuseExpected };
50677
- } finally {
50678
- clearTimeout(timeout);
50679
- input.signal?.removeEventListener("abort", onOuterAbort);
50680
- }
50681
- }
50682
- var COMPACTION_INSTRUCTION, REPLAY_TIMEOUT_MS;
50683
- var init_llmCompact = __esm({
50684
- "src/cli/budget/llmCompact.ts"() {
50685
- "use strict";
50686
- COMPACTION_INSTRUCTION = `
50687
- You are now acting as a compaction engine for this coding-agent session.
50688
-
50689
- Condense the conversation ABOVE into a compact checkpoint sufficient to continue the task.
50690
-
50691
- Preserve:
50692
- - user's goal and evolving intent
50693
- - decisions already made
50694
- - exact file paths and identifiers
50695
- - code changes already completed
50696
- - commands/errors that still matter
50697
- - constraints
50698
- - unfinished work
50699
- - the single most likely next action
50700
-
50701
- Do not call tools.
50702
- Do not mention this summarization request.
50703
- Output only the checkpoint.
50704
- Be concise.
50705
- `.trim();
50706
- REPLAY_TIMEOUT_MS = 6e4;
50707
- }
50708
- });
50709
-
50710
- // src/cli/hooks/historyCompaction.ts
50711
- function compactedRangeFromDropped(dropped) {
50712
- if (dropped.length === 0) return void 0;
50713
- const seqs = [];
50714
- const sources = [];
50715
- for (const m of dropped) {
50716
- const hasCompactRange = typeof m.compactedFromSeq === "number" && Number.isInteger(m.compactedFromSeq) && m.compactedFromSeq > 0 && typeof m.compactedToSeq === "number" && Number.isInteger(m.compactedToSeq) && m.compactedToSeq >= m.compactedFromSeq;
50717
- if (hasCompactRange) {
50718
- seqs.push(m.compactedFromSeq, m.compactedToSeq);
50719
- sources.push(...m.sourceEventSeqs ?? []);
50720
- if (typeof m.seq === "number" && Number.isInteger(m.seq) && m.seq > 0) {
50721
- seqs.push(m.seq);
50722
- sources.push(m.seq);
50723
- }
50724
- continue;
50725
- }
50726
- if (typeof m.seq !== "number" || !Number.isInteger(m.seq) || m.seq < 1) return void 0;
50727
- seqs.push(m.seq);
50728
- sources.push(m.seq);
50729
- }
50730
- return {
50731
- fromSeq: Math.min(...seqs),
50732
- toSeq: Math.max(...seqs),
50733
- sourceEventSeqs: [...new Set(sources)]
50734
- };
50735
- }
50736
- function withDroppedRange(result, dropped, strategy) {
50737
- const range = compactedRangeFromDropped(dropped);
50738
- if (!range) return { ...result, strategy };
50739
- return { ...result, ...range, strategy };
50740
- }
50741
- function resolveMaxMessages(opts) {
50742
- const envTurns = envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 });
50743
- let turns = opts?.maxMessages ? Math.ceil(opts.maxMessages / 4) : envTurns;
50744
- if (opts?.durableStatePresent && !opts?.maxMessages && process.env.ZELARI_HISTORY_TURNS === void 0) {
50745
- turns = Math.min(turns, 3);
50746
- }
50747
- if (turns <= 0) return 0;
50748
- if (opts?.force && opts?.maxMessages) return Math.max(1, opts.maxMessages);
50749
- return turns * 4;
50750
- }
50751
- function findValidCutIndex(messages, naiveCut) {
50752
- let cut = naiveCut;
50753
- while (cut < messages.length) {
50754
- const kept = messages.slice(cut);
50755
- const declared = /* @__PURE__ */ new Set();
50756
- for (const m of kept) {
50757
- if (m.role === "assistant" && m.toolCalls) {
50758
- for (const tc of m.toolCalls) declared.add(tc.id);
50759
- }
50760
- }
50761
- let moved = false;
50762
- for (let k = 0; k < kept.length; k++) {
50763
- const m = kept[k];
50764
- if (m.role === "tool" && m.toolCallId && !declared.has(m.toolCallId)) {
50765
- for (let j = cut - 1; j >= 0; j--) {
50766
- const prev2 = messages[j];
50767
- if (prev2.role === "assistant" && prev2.toolCalls && prev2.toolCalls.some((tc) => tc.id === m.toolCallId)) {
50768
- cut = j;
50769
- moved = true;
50770
- break;
50771
- }
50772
- }
50773
- break;
50774
- }
50775
- }
50776
- if (!moved) break;
50777
- }
50778
- return cut;
50779
- }
50780
- function resolvePruneLimits(opts) {
50781
- const maxChars = opts?.maxChars ?? envNumber(process.env.ZELARI_TOOL_RESULT_MAX_CHARS, { default: 8e3, min: 256 });
50782
- const rawTail = opts?.tailChars ?? envNumber(process.env.ZELARI_TOOL_RESULT_TAIL_CHARS, { default: 1e3, min: 0 });
50783
- const tailChars = Math.min(rawTail, maxChars);
50784
- return { maxChars, tailChars };
50785
- }
50786
- function pruneToolResultsDetailed(messages, opts) {
50787
- const { maxChars, tailChars } = resolvePruneLimits(opts);
50788
- const headChars = maxChars - tailChars;
50789
- const stats = { pruned: 0, charsOmitted: 0 };
50790
- let changed = false;
50791
- const out = messages.map((m) => {
50792
- if (m.role !== "tool") return m;
50793
- const body = m.content ?? "";
50794
- if (body.length <= maxChars) return m;
50795
- const head = headChars > 0 ? body.slice(0, headChars) : "";
50796
- const tail2 = tailChars > 0 ? body.slice(-tailChars) : "";
50797
- const omitted = body.length - head.length - tail2.length;
50798
- changed = true;
50799
- stats.pruned += 1;
50800
- stats.charsOmitted += omitted;
50801
- return {
50802
- ...m,
50803
- content: [head, "\u2026[pruned " + omitted + " chars]\u2026", tail2].join(String.fromCharCode(10))
50804
- };
50805
- });
50806
- return {
50807
- messages: changed ? out : messages,
50808
- stats
50809
- };
50810
- }
50811
- function compactHistory(messages, opts) {
50812
- return compactHistoryDetailed(messages, opts).messages;
50813
- }
50814
- function buildCheckpointMessage(summaryText, range) {
50815
- return {
50816
- role: "user",
50817
- content: CHECKPOINT_WRAPPER_PREFIX + "\n\n<compacted-summary>\n" + summaryText + "\n</compacted-summary>",
50818
- ...range ? {
50819
- compactedFromSeq: range.fromSeq,
50820
- compactedToSeq: range.toSeq,
50821
- sourceEventSeqs: [...range.sourceEventSeqs]
50822
- } : {}
50823
- };
50824
- }
50825
- function compactHistoryDetailed(messages, opts) {
50826
- const maxMessages = resolveMaxMessages(opts);
50827
- if (maxMessages === 0) {
50828
- return withDroppedRange(
50829
- { messages: [], compacted: true, messagesRemoved: messages.length, summary: "" },
50830
- messages,
50831
- "extractive"
50832
- );
50833
- }
50834
- if (messages.length <= maxMessages * 2 && !opts?.force) {
50835
- return {
50836
- messages,
50837
- compacted: false,
50838
- messagesRemoved: 0,
50839
- summary: ""
50840
- };
50841
- }
50842
- const naiveCut = Math.max(0, messages.length - maxMessages);
50843
- const cut = findValidCutIndex(messages, naiveCut);
50844
- if (cut === 0) {
50845
- return {
50846
- messages,
50847
- compacted: false,
50848
- messagesRemoved: 0,
50849
- summary: ""
50850
- };
50851
- }
50852
- const droppedMsgs = messages.slice(0, cut);
50853
- const droppedRange = compactedRangeFromDropped(droppedMsgs);
50854
- const pruned = pruneToolResultsDetailed(messages.slice(cut));
50855
- const kept = pruned.messages;
50856
- const summaryText = extractiveHistorySummary(droppedMsgs);
50857
- const summary = buildCheckpointMessage(
50858
- summaryText || `${COMPACT_MARKER} ${cut} earlier message(s) dropped.`,
50859
- droppedRange
50860
- );
50861
- return withDroppedRange(
50862
- {
50863
- messages: [summary, ...kept],
50864
- compacted: true,
50865
- messagesRemoved: cut,
50866
- summary: summary.content,
50867
- prunedToolResults: pruned.stats.pruned
50868
- },
50869
- droppedMsgs,
50870
- "extractive"
50871
- );
50872
- }
50873
- async function compactHistoryAsync(messages, opts) {
50874
- const base2 = compactHistoryDetailed(messages, opts);
50875
- if (!base2.compacted || base2.messagesRemoved === 0) return base2;
50876
- const cut = base2.messagesRemoved;
50877
- const droppedMsgs = messages.slice(0, cut);
50878
- const extractive = extractiveHistorySummary(droppedMsgs);
50879
- let summaryText = extractive;
50880
- let cacheReuseExpected;
50881
- let replayExactPrefix;
50882
- const canReplay = !!(opts?.providerStream && opts?.requestSnapshot);
50883
- if (canReplay) {
50884
- try {
50885
- const replay = await llmSummarizeHistoryReplay({
50886
- providerStream: opts.providerStream,
50887
- provider: opts.requestSnapshot.provider,
50888
- model: opts.requestSnapshot.model,
50889
- systemMessages: opts.requestSnapshot.systemMessages,
50890
- tools: opts.requestSnapshot.tools,
50891
- droppedMessages: droppedMsgs,
50892
- signal: opts?.signal
50893
- });
50894
- cacheReuseExpected = replay.cacheReuseExpected;
50895
- if (replay.summary && replay.summary.trim().length > 40) {
50896
- const sourceTokens = roughTokens(droppedMsgs);
50897
- const summaryTok = Math.ceil(replay.summary.length / 4);
50898
- if (summaryTok < sourceTokens) {
50899
- summaryText = replay.summary.trim();
50900
- }
50901
- }
50902
- } catch {
50903
- }
50904
- }
50905
- const pruned = pruneToolResultsDetailed(messages.slice(cut));
50906
- const kept = pruned.messages;
50907
- const summary = buildCheckpointMessage(summaryText, compactedRangeFromDropped(droppedMsgs));
50908
- const usedLlm = summaryText !== extractive && summaryText.trim().length > 40;
50909
- return withDroppedRange(
50910
- {
50911
- messages: [summary, ...kept],
50912
- compacted: true,
50913
- messagesRemoved: cut,
50914
- summary: summaryText,
50915
- prunedToolResults: pruned.stats.pruned,
50916
- cacheReuseExpected,
50917
- replayExactPrefix
50918
- },
50919
- droppedMsgs,
50920
- usedLlm ? "llm" : "extractive"
50921
- );
50922
- }
50923
- function roughTokens(msgs) {
50924
- let n = 0;
50925
- for (const m of msgs) {
50926
- n += Math.ceil((m.content ?? "").length / 4);
50927
- if (m.toolCalls) {
50928
- for (const tc of m.toolCalls) {
50929
- n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
50930
- }
50931
- }
50932
- }
50933
- return Math.max(1, n);
50934
- }
50935
- var COMPACT_MARKER, CHECKPOINT_WRAPPER_PREFIX;
50936
- var init_historyCompaction = __esm({
50937
- "src/cli/hooks/historyCompaction.ts"() {
50938
- "use strict";
50939
- init_historySummary();
50940
- init_llmCompact();
50941
- init_envNumber();
50942
- COMPACT_MARKER = "[history] Earlier turns were compacted to stay within the context budget.";
50943
- CHECKPOINT_WRAPPER_PREFIX = "This is an automatically generated checkpoint of earlier conversation. Treat it as established context and continue directly.";
50944
- }
50945
- });
50946
-
50947
51278
  // src/cli/budget/requestSnapshotStore.ts
50948
51279
  function recordRequestSnapshot(sessionId2, snapshot) {
50949
51280
  store5.set(sessionId2, { snapshot });
@@ -51253,215 +51584,6 @@ var init_phase = __esm({
51253
51584
  }
51254
51585
  });
51255
51586
 
51256
- // src/cli/budget/tokenBudget.ts
51257
- function mergeCompactRange(into, r) {
51258
- if (r.fromSeq !== void 0 && r.toSeq !== void 0) {
51259
- into.fromSeq = into.fromSeq === void 0 ? r.fromSeq : Math.min(into.fromSeq, r.fromSeq);
51260
- into.toSeq = into.toSeq === void 0 ? r.toSeq : Math.max(into.toSeq, r.toSeq);
51261
- if (r.sourceEventSeqs) into.sourceSeqs.push(...r.sourceEventSeqs);
51262
- }
51263
- if (r.strategy === "llm") into.strategy = "llm";
51264
- else if (r.strategy && !into.strategy) into.strategy = r.strategy;
51265
- }
51266
- function estimateTokens2(text) {
51267
- if (!text) return 0;
51268
- return Math.max(1, Math.ceil(text.length / 4));
51269
- }
51270
- function estimateHistoryTokens(messages) {
51271
- let n = 0;
51272
- for (const m of messages) {
51273
- n += estimateTokens2(m.content);
51274
- if (m.toolCalls) {
51275
- for (const tc of m.toolCalls) {
51276
- n += estimateTokens2(tc.name) + estimateTokens2(JSON.stringify(tc.args ?? {}));
51277
- }
51278
- }
51279
- }
51280
- return n;
51281
- }
51282
- function defaultContextLimitForModel(model, provider) {
51283
- return capabilitiesFor(model, provider).contextWindow;
51284
- }
51285
- function resolveContextLimit(model, provider) {
51286
- return envNumber(process.env.ZELARI_CONTEXT_LIMIT, {
51287
- default: defaultContextLimitForModel(model, provider),
51288
- min: 4e3,
51289
- max: 2e6
51290
- });
51291
- }
51292
- function phaseKnobs(phase2) {
51293
- return {
51294
- historyTurns: phase2 === "plan" ? envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 8, min: 0 }) : envNumber(process.env.ZELARI_HISTORY_TURNS, { default: 6, min: 0 }),
51295
- maxToolLoopIterations: phase2 === "plan" ? envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 60, min: 1 }) : envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
51296
- default: 120,
51297
- min: 1
51298
- })
51299
- };
51300
- }
51301
- async function applyBudgetPolicyAsync(history2, phase2, opts) {
51302
- const contextLimit = resolveContextLimit(opts?.model, opts?.provider);
51303
- const compact3 = capabilitiesFor(opts?.model, opts?.provider).compaction;
51304
- const sessionExtra = opts?.sessionTokens ?? 0;
51305
- const warnings = [];
51306
- let { historyTurns, maxToolLoopIterations } = phaseKnobs(phase2);
51307
- const envelope = opts?.requestSnapshot ?? null;
51308
- const surface = envelope?.snapshot ?? opts?.requestSurface ?? null;
51309
- const replayBase = surface ? {
51310
- provider: surface.provider,
51311
- model: surface.model,
51312
- systemMessages: surface.systemMessages,
51313
- tools: surface.tools
51314
- } : opts?.providerStream ? { provider: "local", model: opts?.model ?? "unknown", systemMessages: [], tools: [] } : null;
51315
- const headerTokens = surface ? estimateSystemTokensLite(surface.systemMessages) + estimateToolSchemaTokensLite(surface.tools) : 0;
51316
- const convTokensOf = (h) => estimateConversationTokensLite(h);
51317
- let hist = history2;
51318
- let estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
51319
- let occupancy = Math.min(1, estimated / contextLimit);
51320
- let compactSummary = "";
51321
- let messagesRemoved = 0;
51322
- let cacheReuseExpected;
51323
- let prunedTotal = 0;
51324
- const compactRange = { sourceSeqs: [] };
51325
- if (occupancy >= compact3.warnAt && occupancy < compact3.compactAt) {
51326
- warnings.push(
51327
- `[budget] context ~${Math.round(occupancy * 100)}% full (${estimated}/${contextLimit} tok full-request est.) \u2014 consider /compact or shorter replies.`
51328
- );
51329
- }
51330
- if (occupancy >= 0.8) {
51331
- const pruned = pruneToolResultsDetailed(hist);
51332
- if (pruned.stats.pruned > 0) {
51333
- hist = pruned.messages;
51334
- prunedTotal += pruned.stats.pruned;
51335
- estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
51336
- occupancy = Math.min(1, estimated / contextLimit);
51337
- warnings.push(
51338
- `[budget] pruned ${pruned.stats.pruned} oversized tool result(s) \u2192 ${Math.round(occupancy * 100)}% (${estimated} tok).`
51339
- );
51340
- }
51341
- }
51342
- const fold = (r, label, forcedTurns) => {
51343
- hist = applySessionSurface(r.messages);
51344
- if (r.compacted) {
51345
- messagesRemoved += r.messagesRemoved;
51346
- if (r.summary) compactSummary = r.summary;
51347
- mergeCompactRange(compactRange, r);
51348
- if (r.cacheReuseExpected !== void 0) {
51349
- cacheReuseExpected = r.cacheReuseExpected;
51350
- }
51351
- }
51352
- estimated = surface ? headerTokens + convTokensOf(hist) + RESERVED_OUTPUT_TOKENS : estimateHistoryTokens(hist) + sessionExtra;
51353
- occupancy = Math.min(1, estimated / contextLimit);
51354
- warnings.push(
51355
- `[budget] ${label} \u2014 kept ~${forcedTurns} turns (${estimated} tok est.` + (r.messagesRemoved ? `, removed ${r.messagesRemoved} msgs` : "") + (r.cacheReuseExpected === false ? ", cache reuse NOT expected (model override)" : "") + ")."
51356
- );
51357
- };
51358
- if (occupancy >= compact3.compactAt) {
51359
- const forcedTurns = Math.max(1, Math.floor(historyTurns / 2));
51360
- historyTurns = forcedTurns;
51361
- maxToolLoopIterations = Math.min(
51362
- maxToolLoopIterations,
51363
- phase2 === "plan" ? 24 : 40
51364
- );
51365
- let r = await compactHistoryAsync(hist, {
51366
- maxMessages: Math.max(2, forcedTurns * 4),
51367
- force: true,
51368
- signal: opts?.signal,
51369
- ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
51370
- });
51371
- if (!r.compacted) {
51372
- r = await compactHistoryAsync(hist, {
51373
- maxMessages: 2,
51374
- force: true,
51375
- signal: opts?.signal,
51376
- ...replayBase ? { requestSnapshot: replayBase, providerStream: opts?.providerStream } : {}
51377
- });
51378
- }
51379
- const label = r.cacheReuseExpected === false ? "llm-compact (model override)" : "auto-compact";
51380
- fold(r, label, forcedTurns);
51381
- }
51382
- if (occupancy >= compact3.hardAt) {
51383
- const hard = await compactHistoryAsync(hist, {
51384
- maxMessages: 2,
51385
- force: true,
51386
- signal: opts?.signal
51387
- });
51388
- fold(hard, hard.summary.includes("\xB7 llm") ? "llm-compact" : "HARD trim", 2);
51389
- historyTurns = 2;
51390
- maxToolLoopIterations = Math.min(maxToolLoopIterations, 16);
51391
- warnings.push(
51392
- "[budget] HARD context pressure (\u226595%) \u2014 prefer /clear or a new session if quality drops."
51393
- );
51394
- }
51395
- const cacheMetricsLine = envelope ? [
51396
- "compaction meter:",
51397
- `provider/model: ${envelope.snapshot.provider}/${envelope.snapshot.model}`,
51398
- `headerFingerprint: ${envelope.snapshot.headerFingerprint.slice(0, 12)}`,
51399
- `occupancy: ${Math.round(occupancy * 100)}% (${estimated}/${contextLimit})`,
51400
- ...envelope.usage?.cachedPromptTokens !== void 0 ? [`cachedPromptTokens: ${envelope.usage.cachedPromptTokens}`] : [],
51401
- ...cacheReuseExpected !== void 0 ? [`cacheReuseExpected: ${cacheReuseExpected}`] : []
51402
- ].join(" | ") : void 0;
51403
- return {
51404
- history: hist,
51405
- warnings,
51406
- maxToolLoopIterations,
51407
- historyTurns,
51408
- estimatedHistoryTokens: surface ? convTokensOf(hist) : estimated,
51409
- contextLimit,
51410
- occupancy,
51411
- compactSummary: compactSummary || void 0,
51412
- messagesRemoved: messagesRemoved || void 0,
51413
- ...compactRange.fromSeq !== void 0 && compactRange.toSeq !== void 0 ? {
51414
- compactedFromSeq: compactRange.fromSeq,
51415
- compactedToSeq: compactRange.toSeq,
51416
- compactSourceSeqs: compactRange.sourceSeqs,
51417
- compactStrategy: compactRange.strategy
51418
- } : {},
51419
- ...surface ? { contextPressureTokens: estimated } : {},
51420
- ...cacheReuseExpected !== void 0 ? { cacheReuseExpected } : {},
51421
- ...cacheMetricsLine ? { cacheMetricsLine } : {}
51422
- };
51423
- }
51424
- function estimateSystemTokensLite(systemMessages) {
51425
- let n = 0;
51426
- for (const m of systemMessages) n += 4 + Math.ceil((m.content ?? "").length / 4);
51427
- return n;
51428
- }
51429
- function estimateToolSchemaTokensLite(tools) {
51430
- let n = 0;
51431
- for (const t of tools) {
51432
- n += Math.ceil((t.name ?? "").length / 4);
51433
- n += Math.ceil((t.description ?? "").length / 4);
51434
- n += Math.ceil(JSON.stringify(t.parameters ?? {}).length / 4);
51435
- }
51436
- return n + tools.length * 4;
51437
- }
51438
- function estimateConversationTokensLite(messages) {
51439
- let n = 0;
51440
- for (const m of messages) {
51441
- n += 4 + Math.ceil((m.content ?? "").length / 4);
51442
- if (m.toolCalls) {
51443
- for (const tc of m.toolCalls) {
51444
- n += Math.ceil(tc.name.length / 4) + Math.ceil(tc.id.length / 4);
51445
- n += Math.ceil(JSON.stringify(tc.args ?? {}).length / 4);
51446
- }
51447
- }
51448
- if (m.reasoningContent) n += Math.ceil(m.reasoningContent.length / 4);
51449
- if (m.toolCallId) n += Math.ceil(m.toolCallId.length / 4);
51450
- }
51451
- return n;
51452
- }
51453
- var RESERVED_OUTPUT_TOKENS;
51454
- var init_tokenBudget = __esm({
51455
- "src/cli/budget/tokenBudget.ts"() {
51456
- "use strict";
51457
- init_envNumber();
51458
- init_historyCompaction();
51459
- init_observationStore();
51460
- init_capabilities();
51461
- RESERVED_OUTPUT_TOKENS = 8192;
51462
- }
51463
- });
51464
-
51465
51587
  // src/cli/budget/requestMeter.ts
51466
51588
  function estimateTokensLocal(text) {
51467
51589
  if (!text) return 0;
@@ -55182,39 +55304,224 @@ var init_toolRegistry2 = __esm({
55182
55304
  }
55183
55305
  });
55184
55306
 
55307
+ // src/cli/mcp/httpTransport.ts
55308
+ var SSE_DATA_RE, ABORT_GRACE_MS, McpHttpTransport;
55309
+ var init_httpTransport = __esm({
55310
+ "src/cli/mcp/httpTransport.ts"() {
55311
+ "use strict";
55312
+ SSE_DATA_RE = /^data:\s?(.*)$/;
55313
+ ABORT_GRACE_MS = 250;
55314
+ McpHttpTransport = class {
55315
+ constructor(opts) {
55316
+ this.opts = opts;
55317
+ }
55318
+ sessionId = null;
55319
+ closed = false;
55320
+ reinit = null;
55321
+ controllers = /* @__PURE__ */ new Set();
55322
+ get hasSession() {
55323
+ return this.sessionId !== null;
55324
+ }
55325
+ /**
55326
+ * Deliver one JSON-RPC message. Resolves once the response (if any) has
55327
+ * been fed to onMessage. Transport-level failures are converted into
55328
+ * JSON-RPC error responses so the client's pending map rejects cleanly
55329
+ * through the same pump used for stdio.
55330
+ */
55331
+ async send(msg, timeoutMs2) {
55332
+ if (this.closed) throw new Error(`[mcp:${this.opts.serverName}] transport closed`);
55333
+ try {
55334
+ await this.post(msg, timeoutMs2, false);
55335
+ } catch (err) {
55336
+ if (msg.id !== void 0) {
55337
+ this.opts.onMessage({
55338
+ jsonrpc: "2.0",
55339
+ id: msg.id,
55340
+ error: {
55341
+ code: -32e3,
55342
+ message: err instanceof Error ? err.message : String(err)
55343
+ }
55344
+ });
55345
+ }
55346
+ }
55347
+ }
55348
+ /** Best-effort session teardown (HTTP DELETE), then abort in-flight POSTs. */
55349
+ close() {
55350
+ this.closed = true;
55351
+ for (const ac of this.controllers) ac.abort();
55352
+ this.controllers.clear();
55353
+ const sid = this.sessionId;
55354
+ this.sessionId = null;
55355
+ if (!sid) return;
55356
+ const headers2 = {
55357
+ ...this.opts.headers ?? {},
55358
+ "mcp-session-id": sid
55359
+ };
55360
+ void fetch(this.opts.url, { method: "DELETE", headers: headers2 }).catch(() => {
55361
+ });
55362
+ }
55363
+ // ── internals ────────────────────────────────────────────────────────
55364
+ async post(msg, timeoutMs2, replayed) {
55365
+ const ac = new AbortController();
55366
+ this.controllers.add(ac);
55367
+ const timer = setTimeout(() => ac.abort(), timeoutMs2 + ABORT_GRACE_MS);
55368
+ const hadSession = this.sessionId !== null;
55369
+ try {
55370
+ const headers2 = {
55371
+ "content-type": "application/json",
55372
+ accept: "application/json, text/event-stream",
55373
+ ...this.opts.headers ?? {}
55374
+ };
55375
+ if (this.sessionId) headers2["mcp-session-id"] = this.sessionId;
55376
+ const res = await fetch(this.opts.url, {
55377
+ method: "POST",
55378
+ headers: headers2,
55379
+ signal: ac.signal,
55380
+ body: JSON.stringify({ jsonrpc: "2.0", ...msg })
55381
+ });
55382
+ const sid = res.headers.get("mcp-session-id");
55383
+ if (sid) this.sessionId = sid;
55384
+ if (res.status === 404 && hadSession && !replayed && msg.id !== void 0) {
55385
+ this.sessionId = null;
55386
+ await this.ensureSession();
55387
+ return this.post(msg, timeoutMs2, true);
55388
+ }
55389
+ if (!res.ok && res.status !== 202) {
55390
+ throw new Error(`HTTP ${res.status} ${res.statusText}`.trim());
55391
+ }
55392
+ if (msg.id === void 0) {
55393
+ await res.body?.cancel();
55394
+ return;
55395
+ }
55396
+ const contentType = res.headers.get("content-type") ?? "";
55397
+ if (contentType.includes("text/event-stream")) {
55398
+ await this.readSse(res);
55399
+ } else {
55400
+ const body = await res.text();
55401
+ if (body) this.opts.onMessage(JSON.parse(body));
55402
+ }
55403
+ } finally {
55404
+ clearTimeout(timer);
55405
+ this.controllers.delete(ac);
55406
+ }
55407
+ }
55408
+ /** Dedupe concurrent session-loss recoveries into one handshake. */
55409
+ async ensureSession() {
55410
+ if (!this.reinit) {
55411
+ this.reinit = this.opts.onSessionLost().finally(() => {
55412
+ this.reinit = null;
55413
+ }).catch(() => {
55414
+ });
55415
+ }
55416
+ await this.reinit;
55417
+ }
55418
+ /** Minimal SSE reader: dispatch complete `data:` events to onMessage. */
55419
+ async readSse(res) {
55420
+ const reader = res.body?.getReader();
55421
+ if (!reader) return;
55422
+ const decoder = new TextDecoder();
55423
+ let buf = "";
55424
+ let data = "";
55425
+ const dispatch = () => {
55426
+ const payload = data.trim();
55427
+ data = "";
55428
+ if (!payload) return;
55429
+ try {
55430
+ this.opts.onMessage(JSON.parse(payload));
55431
+ } catch {
55432
+ }
55433
+ };
55434
+ for (; ; ) {
55435
+ const { done, value } = await reader.read();
55436
+ if (done) break;
55437
+ buf += decoder.decode(value, { stream: true });
55438
+ let nl;
55439
+ while ((nl = buf.indexOf("\n")) >= 0) {
55440
+ const line = buf.slice(0, nl).replace(/\r$/, "");
55441
+ buf = buf.slice(nl + 1);
55442
+ if (line === "") {
55443
+ dispatch();
55444
+ continue;
55445
+ }
55446
+ const m = SSE_DATA_RE.exec(line);
55447
+ if (m) data += (data ? "\n" : "") + m[1];
55448
+ }
55449
+ }
55450
+ dispatch();
55451
+ }
55452
+ };
55453
+ }
55454
+ });
55455
+
55185
55456
  // src/cli/mcp/mcpClient.ts
55186
55457
  import { spawn as spawn14 } from "node:child_process";
55187
- var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MCP_PROTOCOL_VERSION, McpClient;
55458
+ var DEFAULT_REQUEST_TIMEOUT_MS, INIT_TIMEOUT_MS, MAX_LIST_PAGES, MCP_PROTOCOL_VERSION, McpClient;
55188
55459
  var init_mcpClient = __esm({
55189
55460
  "src/cli/mcp/mcpClient.ts"() {
55190
55461
  "use strict";
55191
55462
  init_cmdline();
55192
55463
  init_updater();
55464
+ init_httpTransport();
55193
55465
  DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
55194
55466
  INIT_TIMEOUT_MS = 15e3;
55467
+ MAX_LIST_PAGES = 50;
55195
55468
  MCP_PROTOCOL_VERSION = "2025-03-26";
55196
55469
  McpClient = class {
55197
55470
  constructor(serverName, config2) {
55198
55471
  this.serverName = serverName;
55199
55472
  this.config = config2;
55473
+ this.defaultTimeoutMs = config2.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
55474
+ this.serial = config2.serial ?? this.transportKind() === "http";
55200
55475
  }
55201
55476
  child = null;
55477
+ transport = null;
55202
55478
  nextId = 1;
55203
55479
  pending = /* @__PURE__ */ new Map();
55204
55480
  stdoutBuffer = "";
55205
55481
  closed = false;
55206
- /** Spawn the server process and run the MCP initialize handshake. */
55482
+ /** Tail of the serial queue (config.serial); requests run one at a time. */
55483
+ queueTail = Promise.resolve();
55484
+ /** True while the 404-recovery handshake runs: bypasses the serial queue
55485
+ * (the queued request that triggered the 404 is waiting on this handshake). */
55486
+ recovering = false;
55487
+ serial;
55488
+ defaultTimeoutMs;
55489
+ transportKind() {
55490
+ if (this.config.type) return this.config.type;
55491
+ return this.config.url && !this.config.command ? "http" : "stdio";
55492
+ }
55493
+ /** Connect (spawn / HTTP session) and run the MCP initialize handshake. */
55207
55494
  async start() {
55208
- if (this.child) return;
55495
+ if (this.child || this.transport) return;
55496
+ if (this.transportKind() === "http") {
55497
+ const url2 = this.config.url;
55498
+ if (!url2) {
55499
+ throw new Error(`[mcp:${this.serverName}] http server requires a url`);
55500
+ }
55501
+ this.transport = new McpHttpTransport({
55502
+ serverName: this.serverName,
55503
+ url: url2,
55504
+ // env may carry an explicit Authorization header for remote servers.
55505
+ headers: this.config.env?.AUTHORIZATION ? { Authorization: this.config.env.AUTHORIZATION } : void 0,
55506
+ onMessage: (msg) => this.handleMessage(msg),
55507
+ onSessionLost: () => this.handshake()
55508
+ });
55509
+ await this.handshake();
55510
+ return;
55511
+ }
55512
+ const command = this.config.command;
55513
+ if (!command) {
55514
+ throw new Error(`[mcp:${this.serverName}] stdio server requires a command`);
55515
+ }
55209
55516
  const spawnOpts = {
55210
55517
  stdio: ["pipe", "pipe", "pipe"],
55211
55518
  env: { ...process.env, ...this.config.env ?? {} },
55212
55519
  windowsHide: true
55213
55520
  };
55214
- const child = process.platform === "win32" ? spawn14(buildCmdLine(this.config.command, this.config.args ?? []), {
55521
+ const child = process.platform === "win32" ? spawn14(buildCmdLine(command, this.config.args ?? []), {
55215
55522
  ...spawnOpts,
55216
55523
  shell: true
55217
- }) : spawn14(this.config.command, this.config.args ?? [], spawnOpts);
55524
+ }) : spawn14(command, this.config.args ?? [], spawnOpts);
55218
55525
  this.child = child;
55219
55526
  child.stdout.setEncoding("utf8");
55220
55527
  child.stdout.on("data", (chunk) => this.onStdout(chunk));
@@ -55233,37 +55540,60 @@ var init_mcpClient = __esm({
55233
55540
  );
55234
55541
  }
55235
55542
  });
55236
- await this.request(
55237
- "initialize",
55238
- {
55239
- protocolVersion: MCP_PROTOCOL_VERSION,
55240
- capabilities: {},
55241
- clientInfo: { name: "zelari-code", version: getCurrentVersion() }
55242
- },
55243
- INIT_TIMEOUT_MS
55244
- );
55245
- this.notify("notifications/initialized", {});
55543
+ await this.handshake();
55544
+ }
55545
+ async handshake() {
55546
+ const prev2 = this.recovering;
55547
+ this.recovering = true;
55548
+ try {
55549
+ await this.request(
55550
+ "initialize",
55551
+ {
55552
+ protocolVersion: MCP_PROTOCOL_VERSION,
55553
+ capabilities: {},
55554
+ clientInfo: { name: "zelari-code", version: getCurrentVersion() }
55555
+ },
55556
+ INIT_TIMEOUT_MS
55557
+ );
55558
+ this.notify("notifications/initialized", {});
55559
+ } finally {
55560
+ this.recovering = prev2;
55561
+ }
55246
55562
  }
55247
- /** Discover the server's tools. */
55563
+ /**
55564
+ * Discover the server's tools. Follows `nextCursor` pagination so
55565
+ * large servers (hundreds of tools) are listed completely.
55566
+ */
55248
55567
  async listTools() {
55249
- const res = await this.request("tools/list", {});
55250
- return (res.tools ?? []).filter(
55251
- (t) => !!t.name
55252
- ).map((t) => ({
55253
- name: t.name,
55254
- description: t.description ?? "",
55255
- inputSchema: t.inputSchema ?? { type: "object", properties: {} }
55256
- }));
55568
+ const tools = [];
55569
+ let cursor;
55570
+ for (let page = 0; page < MAX_LIST_PAGES; page++) {
55571
+ const res = await this.request(
55572
+ "tools/list",
55573
+ cursor ? { cursor } : {}
55574
+ );
55575
+ for (const t of res.tools ?? []) {
55576
+ if (!t.name) continue;
55577
+ tools.push({
55578
+ name: t.name,
55579
+ description: t.description ?? "",
55580
+ inputSchema: t.inputSchema ?? { type: "object", properties: {} }
55581
+ });
55582
+ }
55583
+ cursor = typeof res.nextCursor === "string" ? res.nextCursor : void 0;
55584
+ if (!cursor) break;
55585
+ }
55586
+ return tools;
55257
55587
  }
55258
55588
  /**
55259
55589
  * Call a tool. Returns the concatenated text content; non-text content
55260
55590
  * items are summarized by type. Throws when the server flags isError.
55261
55591
  */
55262
- async callTool(name, args, timeoutMs2 = DEFAULT_REQUEST_TIMEOUT_MS) {
55592
+ async callTool(name, args, timeoutMs2) {
55263
55593
  const res = await this.request(
55264
55594
  "tools/call",
55265
55595
  { name, arguments: args },
55266
- timeoutMs2
55596
+ timeoutMs2 ?? this.defaultTimeoutMs
55267
55597
  );
55268
55598
  const text = (res.content ?? []).map(
55269
55599
  (c) => c.type === "text" && typeof c.text === "string" ? c.text : `[${c.type ?? "unknown"} content]`
@@ -55272,20 +55602,39 @@ var init_mcpClient = __esm({
55272
55602
  throw new Error(text || `tool "${name}" reported an error`);
55273
55603
  return text;
55274
55604
  }
55275
- /** Terminate the server process and reject all in-flight requests. */
55605
+ /** Terminate the server / session and reject all in-flight requests. */
55276
55606
  close() {
55277
55607
  this.closed = true;
55278
55608
  this.failAll(new Error(`[mcp:${this.serverName}] client closed`));
55609
+ if (this.transport) {
55610
+ this.transport.close();
55611
+ this.transport = null;
55612
+ return;
55613
+ }
55279
55614
  this.child?.kill();
55280
55615
  this.child = null;
55281
55616
  }
55282
- // ── JSON-RPC plumbing ────────────────────────────────────────────────
55283
- request(method, params, timeoutMs2 = DEFAULT_REQUEST_TIMEOUT_MS) {
55617
+ // ── JSON-RPC plumbing (shared by both transports) ────────────────────
55618
+ request(method, params, timeoutMs2) {
55619
+ const effective = timeoutMs2 ?? this.defaultTimeoutMs;
55620
+ if (!this.serial || this.recovering)
55621
+ return this.dispatch(method, params, effective);
55622
+ const run = this.queueTail.then(
55623
+ () => this.dispatch(method, params, effective),
55624
+ () => this.dispatch(method, params, effective)
55625
+ );
55626
+ this.queueTail = run.then(
55627
+ () => void 0,
55628
+ () => void 0
55629
+ );
55630
+ return run;
55631
+ }
55632
+ dispatch(method, params, timeoutMs2) {
55633
+ const transport = this.transport;
55284
55634
  const child = this.child;
55285
- if (!child)
55635
+ if (!transport && !child)
55286
55636
  return Promise.reject(new Error(`[mcp:${this.serverName}] not started`));
55287
55637
  const id3 = this.nextId++;
55288
- const payload = JSON.stringify({ jsonrpc: "2.0", id: id3, method, params });
55289
55638
  return new Promise((resolve9, reject) => {
55290
55639
  const timer = setTimeout(() => {
55291
55640
  this.pending.delete(id3);
@@ -55296,16 +55645,25 @@ var init_mcpClient = __esm({
55296
55645
  );
55297
55646
  }, timeoutMs2);
55298
55647
  this.pending.set(id3, { resolve: resolve9, reject, timer });
55299
- child.stdin.write(payload + "\n", (err) => {
55300
- if (err) {
55301
- clearTimeout(timer);
55302
- this.pending.delete(id3);
55303
- reject(err);
55304
- }
55305
- });
55648
+ if (transport) {
55649
+ void transport.send({ id: id3, method, params }, timeoutMs2);
55650
+ } else {
55651
+ const payload = JSON.stringify({ jsonrpc: "2.0", id: id3, method, params });
55652
+ child.stdin.write(payload + "\n", (err) => {
55653
+ if (err) {
55654
+ clearTimeout(timer);
55655
+ this.pending.delete(id3);
55656
+ reject(err);
55657
+ }
55658
+ });
55659
+ }
55306
55660
  });
55307
55661
  }
55308
55662
  notify(method, params) {
55663
+ if (this.transport) {
55664
+ void this.transport.send({ method, params }, this.defaultTimeoutMs);
55665
+ return;
55666
+ }
55309
55667
  this.child?.stdin.write(
55310
55668
  JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n"
55311
55669
  );
@@ -55317,26 +55675,29 @@ var init_mcpClient = __esm({
55317
55675
  const line = this.stdoutBuffer.slice(0, nl).trim();
55318
55676
  this.stdoutBuffer = this.stdoutBuffer.slice(nl + 1);
55319
55677
  if (!line) continue;
55320
- let msg;
55321
55678
  try {
55322
- msg = JSON.parse(line);
55679
+ this.handleMessage(JSON.parse(line));
55323
55680
  } catch {
55324
55681
  continue;
55325
55682
  }
55326
- if (typeof msg.id !== "number") continue;
55327
- const pending = this.pending.get(msg.id);
55328
- if (!pending) continue;
55329
- this.pending.delete(msg.id);
55330
- clearTimeout(pending.timer);
55331
- if (msg.error) {
55332
- pending.reject(
55333
- new Error(
55334
- `[mcp:${this.serverName}] ${msg.error.message ?? "JSON-RPC error"} (code ${msg.error.code ?? "?"})`
55335
- )
55336
- );
55337
- } else {
55338
- pending.resolve(msg.result);
55339
- }
55683
+ }
55684
+ }
55685
+ /** Route one parsed JSON-RPC message to its pending request (both transports). */
55686
+ handleMessage(msg) {
55687
+ const m = msg;
55688
+ if (typeof m.id !== "number") return;
55689
+ const pending = this.pending.get(m.id);
55690
+ if (!pending) return;
55691
+ this.pending.delete(m.id);
55692
+ clearTimeout(pending.timer);
55693
+ if (m.error) {
55694
+ pending.reject(
55695
+ new Error(
55696
+ `[mcp:${this.serverName}] ${m.error.message ?? "JSON-RPC error"} (code ${m.error.code ?? "?"})`
55697
+ )
55698
+ );
55699
+ } else {
55700
+ pending.resolve(m.result);
55340
55701
  }
55341
55702
  }
55342
55703
  failAll(err) {
@@ -55371,11 +55732,17 @@ function readFile6(path91) {
55371
55732
  const parsed = JSON.parse(readFileSync29(path91, "utf8"));
55372
55733
  const out = {};
55373
55734
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
55374
- if (!cfg || typeof cfg.command !== "string" || !cfg.command.trim()) continue;
55735
+ const hasCommand = !!cfg && typeof cfg.command === "string" && !!cfg.command.trim();
55736
+ const hasUrl = !!cfg && typeof cfg.url === "string" && /^https?:\/\//i.test(cfg.url);
55737
+ if (!cfg || !hasCommand && !hasUrl) continue;
55375
55738
  out[name] = {
55376
- command: cfg.command.trim(),
55739
+ command: hasCommand ? cfg.command.trim() : void 0,
55377
55740
  args: Array.isArray(cfg.args) ? cfg.args.map(String) : void 0,
55378
55741
  env: cfg.env && typeof cfg.env === "object" ? cfg.env : void 0,
55742
+ type: hasUrl && !hasCommand ? "http" : cfg.type === "http" ? "http" : "stdio",
55743
+ url: hasUrl ? cfg.url.trim() : void 0,
55744
+ timeoutMs: typeof cfg.timeoutMs === "number" && cfg.timeoutMs > 0 ? cfg.timeoutMs : void 0,
55745
+ serial: typeof cfg.serial === "boolean" ? cfg.serial : void 0,
55379
55746
  enabled: cfg.enabled !== false
55380
55747
  };
55381
55748
  }
@@ -55417,8 +55784,13 @@ function upsertMcpServer(opts) {
55417
55784
  error: "Invalid server name (use letters, digits, _ -)"
55418
55785
  };
55419
55786
  }
55420
- if (!opts.config.command?.trim()) {
55421
- return { ok: false, error: "command is required" };
55787
+ const hasCommand = !!opts.config.command?.trim();
55788
+ const hasUrl = typeof opts.config.url === "string" && /^https?:\/\//i.test(opts.config.url);
55789
+ if (!hasCommand && !hasUrl) {
55790
+ return {
55791
+ ok: false,
55792
+ error: "either command (stdio) or url (http) is required"
55793
+ };
55422
55794
  }
55423
55795
  let path91;
55424
55796
  if (opts.scope === "user") {
@@ -55435,9 +55807,13 @@ function upsertMcpServer(opts) {
55435
55807
  }
55436
55808
  const current = readFile6(path91);
55437
55809
  current[name] = {
55438
- command: opts.config.command.trim(),
55810
+ command: hasCommand ? opts.config.command.trim() : void 0,
55439
55811
  args: opts.config.args,
55440
55812
  env: opts.config.env,
55813
+ type: hasUrl ? "http" : opts.config.type === "http" ? "http" : "stdio",
55814
+ url: hasUrl ? opts.config.url.trim() : void 0,
55815
+ timeoutMs: opts.config.timeoutMs,
55816
+ serial: opts.config.serial,
55441
55817
  enabled: opts.config.enabled !== false
55442
55818
  };
55443
55819
  writeFile2(path91, current);
@@ -55512,9 +55888,35 @@ function buildQwenMmPreset() {
55512
55888
  ]
55513
55889
  };
55514
55890
  }
55891
+ function buildUnrealPreset() {
55892
+ const url2 = process.env.UNREAL_MCP_URL?.trim() || "http://127.0.0.1:8000/mcp";
55893
+ return {
55894
+ id: "unreal-mcp",
55895
+ servers: {
55896
+ "unreal-mcp": {
55897
+ type: "http",
55898
+ url: url2,
55899
+ // Editor tool calls (asset scans, builds, PIE) can be slow.
55900
+ timeoutMs: 12e4,
55901
+ // Epic guidance: never overlap calls on the editor's game thread.
55902
+ serial: true,
55903
+ enabled: true
55904
+ }
55905
+ },
55906
+ notes: [
55907
+ "Unreal Engine 5.8+ \u2014 MCP server embedded in the editor (Experimental feature).",
55908
+ "Editor: enable the 'Model Context Protocol' plugin, then Edit \u2192 Project Settings \u2192 Plugins \u2192 MCP Server.",
55909
+ `Endpoint: ${url2} (override with UNREAL_MCP_URL; port/path configurable in the editor).`,
55910
+ "Tool Search ON by default: tools surface as list_toolsets / describe_toolset / call_tool.",
55911
+ "Requests are serialized and time out after 120s (editor tools can be slow).",
55912
+ "Start the editor before OR after zelari \u2014 unreachable servers are retried on each turn.",
55913
+ "Kill switch: disable the unreal-mcp server in mcp.json or ZELARI_MCP=0"
55914
+ ]
55915
+ };
55916
+ }
55515
55917
  function listMcpPresetIds() {
55516
55918
  return Object.keys(PRESETS).filter(
55517
- (k) => k === "cua" || k === "composio" || k === "qwen-mm-plugins"
55919
+ (k) => k === "cua" || k === "composio" || k === "qwen-mm-plugins" || k === "unreal-mcp"
55518
55920
  );
55519
55921
  }
55520
55922
  function getMcpPreset(id3) {
@@ -55583,7 +55985,10 @@ var init_mcpPresets = __esm({
55583
55985
  "cua-driver": () => CUA_DRIVER_PRESET,
55584
55986
  composio: buildComposioPreset,
55585
55987
  "qwen-mm-plugins": buildQwenMmPreset,
55586
- "qwen-mm": buildQwenMmPreset
55988
+ "qwen-mm": buildQwenMmPreset,
55989
+ unreal: buildUnrealPreset,
55990
+ "unreal-mcp": buildUnrealPreset,
55991
+ unrealEditor: buildUnrealPreset
55587
55992
  };
55588
55993
  }
55589
55994
  });
@@ -55613,7 +56018,9 @@ function readMcpConfig(projectRoot = process.cwd(), opts) {
55613
56018
  try {
55614
56019
  const parsed = JSON.parse(readFileSync30(p3, "utf8"));
55615
56020
  for (const [name, cfg] of Object.entries(parsed.mcpServers ?? {})) {
55616
- if (!cfg || typeof cfg.command !== "string" || cfg.command.length === 0) continue;
56021
+ const hasCommand = !!cfg && typeof cfg.command === "string" && cfg.command.length > 0;
56022
+ const hasUrl = !!cfg && typeof cfg.url === "string" && /^https?:\/\//i.test(cfg.url);
56023
+ if (!cfg || !hasCommand && !hasUrl) continue;
55617
56024
  merged[name] = cfg;
55618
56025
  }
55619
56026
  } catch {
@@ -55654,16 +56061,52 @@ async function ensureLoaded(projectRoot) {
55654
56061
  }
55655
56062
  } catch (err) {
55656
56063
  client.close();
55657
- state2.warnings.push(`[mcp:${name}] disabled: ${err instanceof Error ? err.message : String(err)}`);
56064
+ const msg = err instanceof Error ? err.message : String(err);
56065
+ if (isHttpConfig(cfg)) {
56066
+ state2.pendingHttp.push({ name, cfg });
56067
+ state2.warnings.push(
56068
+ `[mcp:${name}] not reachable yet (${msg}) \u2014 will retry each turn`
56069
+ );
56070
+ } else {
56071
+ state2.warnings.push(`[mcp:${name}] disabled: ${msg}`);
56072
+ }
55658
56073
  }
55659
56074
  }
55660
56075
  }
56076
+ function isHttpConfig(cfg) {
56077
+ return cfg.type === "http" || !!cfg.url && !cfg.command;
56078
+ }
56079
+ async function retryPendingHttp() {
56080
+ if (state2.pendingHttp.length === 0) return;
56081
+ const remaining = [];
56082
+ for (const p3 of state2.pendingHttp) {
56083
+ const client = new McpClient(p3.name, p3.cfg);
56084
+ try {
56085
+ await client.start();
56086
+ const tools = await client.listTools();
56087
+ state2.clients.push(client);
56088
+ for (const info of tools) {
56089
+ state2.tools.push({
56090
+ registryName: sanitizeToolName(`mcp_${p3.name}_${info.name}`),
56091
+ serverName: p3.name,
56092
+ info,
56093
+ client
56094
+ });
56095
+ }
56096
+ } catch {
56097
+ client.close();
56098
+ remaining.push(p3);
56099
+ }
56100
+ }
56101
+ state2.pendingHttp = remaining;
56102
+ }
55661
56103
  function sanitizeToolName(raw) {
55662
56104
  return raw.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
55663
56105
  }
55664
56106
  async function registerMcpTools(registry4, projectRoot = process.cwd(), opts) {
55665
56107
  if (process.env["ZELARI_MCP"] === "0") return { registered: [], warnings: [] };
55666
56108
  await ensureLoaded(projectRoot);
56109
+ await retryPendingHttp();
55667
56110
  const skipCuaForCouncil = opts?.councilMode === true && !isCuaAllowedForCouncil();
55668
56111
  const registered = [];
55669
56112
  for (const t of state2.tools) {
@@ -55701,6 +56144,7 @@ function closeMcpClients() {
55701
56144
  state2.tools = [];
55702
56145
  state2.loaded = false;
55703
56146
  state2.warnings = [];
56147
+ state2.pendingHttp = [];
55704
56148
  }
55705
56149
  function _resetMcpForTests() {
55706
56150
  closeMcpClients();
@@ -55714,7 +56158,7 @@ var init_mcpManager = __esm({
55714
56158
  init_mcpClient();
55715
56159
  init_mcpPresets();
55716
56160
  init_folderTrust();
55717
- state2 = { loaded: false, tools: [], warnings: [], clients: [] };
56161
+ state2 = { loaded: false, tools: [], warnings: [], clients: [], pendingHttp: [] };
55718
56162
  }
55719
56163
  });
55720
56164
 
@@ -66217,11 +66661,14 @@ async function runHeadlessCouncil(opts, provider, model, providerStream, extras)
66217
66661
  const { composeProjectContext: composeProjectContext2 } = await Promise.resolve().then(() => (init_composeContext(), composeContext_exports));
66218
66662
  const { loadDurableContext: loadDurableContext2 } = await Promise.resolve().then(() => (init_loadDurableContext(), loadDurableContext_exports));
66219
66663
  const durableState = await loadDurableContext2(cwd);
66664
+ const { resolveRetrievalPolicy: resolveRetrievalPolicy2 } = await Promise.resolve().then(() => (init_retrievalPolicy(), retrievalPolicy_exports));
66665
+ const retrieval = resolveRetrievalPolicy2(councilContext.budget.occupancy);
66220
66666
  const memoryContext = nativeMemory ? (await nativeMemory.buildContext({
66221
66667
  text: effectiveTask,
66222
66668
  useGraph: true,
66223
- maxChars: 2e3,
66224
- maxMemories: 8
66669
+ maxChars: retrieval.maxChars,
66670
+ maxMemories: retrieval.maxMemories,
66671
+ ...retrieval.weights ? { weights: retrieval.weights } : {}
66225
66672
  })).text : "";
66226
66673
  const composed = composeProjectContext2({
66227
66674
  mode: "council",
@@ -67412,6 +67859,7 @@ var init_skillConfigIo = __esm({
67412
67859
  "@zelari/core/skills/builtin/planning",
67413
67860
  "@zelari/core/skills/builtin/refactoring",
67414
67861
  "@zelari/core/skills/builtin/review",
67862
+ "@zelari/core/skills/builtin/unrealEditor",
67415
67863
  "@zelari/core/skills/builtin/testing",
67416
67864
  "@zelari/core/skills/builtin/schema-loop",
67417
67865
  "@zelari/core/skills/builtin/computer-use-cua",
@@ -73439,21 +73887,27 @@ function createPermissionAskHandler(opts) {
73439
73887
  const catLabel = cats.length === 1 ? cats[0] : cats.join("+") || "action";
73440
73888
  const title = `Allow tool "${req.toolName}"?`;
73441
73889
  const detail = req.reason + (cats.length ? ` [${cats.join(", ")}]` : "");
73890
+ const claimLines = (req.claims ?? []).map((c) => `\xB7 ${c.summary}`);
73891
+ const note = req.policyNote ? `
73892
+ ${req.policyNote}` : "";
73893
+ const claimsBlock = claimLines.length ? `
73894
+ Claims:
73895
+ ${claimLines.join("\n")}` : "";
73442
73896
  appendSystem2?.(
73443
73897
  `[permission] ${title}
73444
- ${detail}
73898
+ ${detail}${note}${claimsBlock}
73445
73899
  \u2192 Allow once \xB7 Always (tool) \xB7 Always (${catLabel}) \xB7 Deny`,
73446
73900
  Date.now()
73447
73901
  );
73448
73902
  let settled = false;
73449
73903
  const askTimeoutMs = askUserTimeoutMs();
73450
73904
  let cancelAskTimeout = () => void 0;
73451
- const finish2 = (ok, note) => {
73905
+ const finish2 = (ok, note2) => {
73452
73906
  if (settled) return;
73453
73907
  settled = true;
73454
73908
  cancelAskTimeout();
73455
73909
  setPicker2(null);
73456
- if (note) appendSystem2?.(note, Date.now());
73910
+ if (note2) appendSystem2?.(note2, Date.now());
73457
73911
  resolve9(ok);
73458
73912
  };
73459
73913
  cancelAskTimeout = armPickerTimeout(
@@ -74587,6 +75041,7 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
74587
75041
  let councilMemory;
74588
75042
  let councilMemoryAutoWrite = false;
74589
75043
  let nativeMemoryContext = "";
75044
+ let councilRetrievalBand = "low";
74590
75045
  try {
74591
75046
  const memoryFactory = await Promise.resolve().then(() => (init_serviceFactory(), serviceFactory_exports));
74592
75047
  if (memoryFactory.isMemoryV2Enabled()) {
@@ -74601,11 +75056,17 @@ _(${req.context})_` : "") + "\n\u2192 scegli dalla lista (il membro riprende dop
74601
75056
  });
74602
75057
  councilMemoryAutoWrite = memoryFactory.isMemoryAutoWriteEnabled();
74603
75058
  if (!overrides.ragContext) {
75059
+ const { resolveRetrievalPolicy: resolveRetrievalPolicy2 } = await Promise.resolve().then(() => (init_retrievalPolicy(), retrievalPolicy_exports));
75060
+ const retrieval = resolveRetrievalPolicy2(
75061
+ councilContext.budget.occupancy
75062
+ );
75063
+ councilRetrievalBand = retrieval.band;
74604
75064
  nativeMemoryContext = (await councilMemory.buildContext({
74605
75065
  text: effectiveText,
74606
75066
  useGraph: true,
74607
- maxChars: 2e3,
74608
- maxMemories: 8
75067
+ maxChars: retrieval.maxChars,
75068
+ maxMemories: retrieval.maxMemories,
75069
+ ...retrieval.weights ? { weights: retrieval.weights } : {}
74609
75070
  })).text;
74610
75071
  }
74611
75072
  }
@@ -80285,7 +80746,7 @@ function pickRootComponent() {
80285
80746
  }
80286
80747
  if (argv.includes("--help") || argv.includes("-h")) {
80287
80748
  console.log(
80288
- "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable (required)\n --args <json> JSON array of args (optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ZELARI_MEMORY_MCP=1 Enable the optional external memory MCP server\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
80749
+ "zelari-code \u2014 AI Council coding agent CLI.\n\nUsage: zelari-code [options]\n\nOptions:\n --version, -v Print version and exit\n --help, -h Print this help and exit\n --doctor Diagnose install health (shim, bundle, PATH, deps,\n node/git/bash in the agent shell)\n --inspect [--json] Unified project inspection (config, skills, MCP,\n hooks, plugins, AGENTS.md, trust status)\n --trust [path] Trust the cwd (or path) so project MCP + hooks load\n --fix-path Add the npm global prefix to the user PATH\n (Windows only; fixes 'command not found' after install)\n --fix-budget Set recommended ZELARI_MAX_TOOL_LOOP_HARD=180,\n ZELARI_MAX_TOOL_LOOP_ITERATIONS=60, ZELARI_CONTEXT_LIMIT=400000\n at User scope (prevents the agent stopping mid-task)\n --permission-mcp <socket> MCP stdio server for external agent permission prompts\n (spawned by claude --permission-prompt-tool)\n --memory-mcp Optional MCP stdio server for project memory\n --cwd <path> Trusted project root (requires ZELARI_MEMORY_MCP=1)\n --client-id <id> Stable local owner identity for private memories\n --memory-json <json> Read-only project-memory bridge for Zelari Desktop\n --skip-checks Skip the boot-time prerequisite check\n (alias for ZELARI_SKIP_PREFLIGHT=1)\n --no-wizard Skip the first-run wizard\n --reset-config Re-run the wizard (clears provider.json on commit)\n --headless Run a single task without mounting the TUI\n --task <text> Task prompt (required in headless mode)\n --output json|plain Output format (default: json)\n --mode kraken|council|zelari Dispatch mode (default: kraken; agent=alias)\n --council Alias for --mode council\n --phase plan|build Work phase (default: build)\n --provider <id> Provider override (default: active)\n --model <id> Model override (default: provider default)\n --profile <id> Capability profile (minimal/v1|kraken/v1|council/v1|mission/v1)\n --resume <id> Continue a 2.0 spine session\n --export-session <path> Write zelari-session-export/1 after the run\n --strict-done Evidence-based BUILD completion gate\n --session-export <id> Print a portable 2.0 session export (no LLM)\n serve Companion host for Android/remote clients (Tailscale)\n --bind <ip> Listen address (default: 127.0.0.1; use Tailscale IP)\n --port <n> Port (default: 7421)\n --token <secret> Bearer token (default: ~/.zelari-code/companion.token)\n --project <path> Allowlisted project root (repeatable)\n --save-projects Persist --project list to companion.json\n --print-config Print provider/model config as JSON (no secrets)\n --plugins-status JSON status of optional plugins (Playwright, eslint, \u2026)\n --plugins-install <id> Install plugin (playwright also fetches Chromium)\n --cwd <path> Workspace for -D installs (default: process.cwd())\n --set-config Persist provider/model/endpoint\n --provider <id> Set active provider\n --model <id> Set model for that provider\n --endpoint <url> Custom OpenAI-compatible base URL\n --endpoint-clear Remove custom endpoint override\n --thinking <spec> Thinking effort (auto|off|low|medium|high|budget:N)\n --set-key Store an API key (never printed back)\n --provider <id> Provider id (required)\n --key <secret> API key (required)\n --login-oauth Start subscription OAuth (grok, chatgpt, anthropic)\n --provider <id> grok | chatgpt | anthropic\n --code <paste> Anthropic magic-link code (CODE#STATE)\n --no-browser Do not open the system browser\n --refresh-oauth Force-refresh an OAuth access token\n --provider <id> grok | chatgpt | anthropic\n --logout-oauth Clear stored OAuth credentials\n --provider <id> grok | chatgpt | anthropic\n --discover-models Refresh model list for a provider\n --provider <id> Provider (default: active)\n --print-mcp Print MCP server config (user + project)\n --cwd <path> Project root for .zelari/mcp.json\n --set-mcp Add/update an MCP server entry\n --name <id> Server name (required)\n --command <bin> Executable for stdio servers\n --url <endpoint> Streamable HTTP endpoint (e.g. UE 5.8 editor)\n --args <json> JSON array of args (stdio, optional)\n --timeout <ms> Per-server request timeout (http, optional)\n --scope user|project Default: user\n --enabled true|false Default: true\n --cwd <path> Required when scope=project\n --set-mcp-preset Install a named MCP preset (e.g. cua)\n --preset cua Cua Driver desktop computer-use (MCP)\n --preset unreal-mcp Unreal Engine 5.8+ editor (MCP over HTTP)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-mcp Remove an MCP server entry\n --name <id> --scope user|project [--cwd <path>]\n --print-skills Print skills (builtin + user + project)\n --cwd <path> Project root for .zelari/skills\n --set-skill Add/update a SKILL.md skill\n --name <id> Skill id (required)\n --description <t> One-line description (required)\n --body <text> Markdown instructions (required)\n --category <c> plan|refactor|debug|review|test|docs|ops|git|db|maint\n --tools <csv> Comma-separated required tools (optional)\n --cost <l> low|medium|high (default: medium)\n --scope user|project Default: user\n --cwd <path> Required when scope=project\n --remove-skill Remove a user/project SKILL.md\n --name <id> --scope user|project [--cwd <path>]\n --generate-skill-from-url Fetch URL + draft skill via model (JSON)\n --url <https://...> Required\n --provider <id> Override active provider\n --model <name> Override model for the selected provider\n --print-ssh-targets Print SSH deploy/monitor targets\n --set-ssh-target Upsert target (--json '{...}' or flags)\n --remove-ssh-target --id <id>\n --test-ssh-target --id <id> (BatchMode ssh true)\n --print-ssh-pubkey --path <private-or-.pub> (display public key)\n\nEnvironment:\n ZELARI_NO_WIZARD=1 Skip the first-run wizard\n ZELARI_SKIP_PREFLIGHT=1 Skip the boot prerequisite check\n ZELARI_NO_PLUGIN_PROMPT=1 Skip the boot plugin-install prompt\n ZELARI_MEMORY_MCP=1 Enable the optional external memory MCP server\n ANATHEMA_DEV=1 Disable background update check + preflight\n"
80289
80750
  );
80290
80751
  process.exit(0);
80291
80752
  }
@@ -80311,6 +80772,8 @@ function pickRootComponent() {
80311
80772
  };
80312
80773
  const name = get("--name");
80313
80774
  const command = get("--command");
80775
+ const url2 = get("--url");
80776
+ const timeoutRaw = get("--timeout");
80314
80777
  const scopeRaw = get("--scope") ?? "user";
80315
80778
  const scope = scopeRaw === "project" ? "project" : "user";
80316
80779
  const cwd = get("--cwd") ?? process.cwd();
@@ -80323,14 +80786,22 @@ function pickRootComponent() {
80323
80786
  if (!Array.isArray(parsed)) throw new Error("--args must be a JSON array");
80324
80787
  args = parsed.map(String);
80325
80788
  }
80326
- if (!name || !command) {
80327
- throw new Error("--name and --command are required");
80789
+ const timeoutMs2 = timeoutRaw !== void 0 ? Number(timeoutRaw) : void 0;
80790
+ if (timeoutMs2 !== void 0 && (!Number.isFinite(timeoutMs2) || timeoutMs2 <= 0)) {
80791
+ throw new Error("--timeout must be a positive number of milliseconds");
80792
+ }
80793
+ if (!name) throw new Error("--name is required");
80794
+ if (!command && !url2) {
80795
+ throw new Error("either --command (stdio) or --url (http) is required");
80796
+ }
80797
+ if (url2 && !/^https?:\/\//i.test(url2)) {
80798
+ throw new Error("--url must be an http(s) endpoint");
80328
80799
  }
80329
80800
  const result = upsertMcpServer({
80330
80801
  scope,
80331
80802
  name,
80332
80803
  projectRoot: cwd,
80333
- config: { command, args, enabled }
80804
+ config: url2 ? { type: "http", url: url2, timeoutMs: timeoutMs2, serial: true, enabled } : { command, args, enabled }
80334
80805
  });
80335
80806
  if (!result.ok) throw new Error(result.error);
80336
80807
  console.log(JSON.stringify({ ok: true, path: result.path, name, scope }));