zelari-code 1.15.0 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/cli/app.js +8 -1
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/components/StatusBar.js +5 -1
- package/dist/cli/components/StatusBar.js.map +1 -1
- package/dist/cli/councilDispatcher.js +3 -0
- package/dist/cli/councilDispatcher.js.map +1 -1
- package/dist/cli/hooks/chatStats.js +36 -11
- package/dist/cli/hooks/chatStats.js.map +1 -1
- package/dist/cli/hooks/conversationContext.js +5 -2
- package/dist/cli/hooks/conversationContext.js.map +1 -1
- package/dist/cli/hooks/historyCompaction.js +8 -1
- package/dist/cli/hooks/historyCompaction.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +254 -60
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/hooks/useSlashDispatch.js +42 -0
- package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
- package/dist/cli/main.bundled.js +2509 -845
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +47 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp/mcpManager.js +11 -1
- package/dist/cli/mcp/mcpManager.js.map +1 -1
- package/dist/cli/mcp/mcpPresets.js +77 -0
- package/dist/cli/mcp/mcpPresets.js.map +1 -0
- package/dist/cli/memory/fileBackend.js +11 -2
- package/dist/cli/memory/fileBackend.js.map +1 -1
- package/dist/cli/runHeadless.js +79 -4
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/slashCommands.js +54 -1
- package/dist/cli/slashCommands.js.map +1 -1
- package/dist/cli/slashHandlers/cache.js +35 -0
- package/dist/cli/slashHandlers/cache.js.map +1 -0
- package/dist/cli/slashHandlers/state.js +97 -0
- package/dist/cli/slashHandlers/state.js.map +1 -0
- package/dist/cli/state/commitHelpers.js +73 -0
- package/dist/cli/state/commitHelpers.js.map +1 -0
- package/dist/cli/state/fileStateStore.js +262 -0
- package/dist/cli/state/fileStateStore.js.map +1 -0
- package/dist/cli/state/loadDurableContext.js +42 -0
- package/dist/cli/state/loadDurableContext.js.map +1 -0
- package/dist/cli/state/promptCacheStats.js +56 -0
- package/dist/cli/state/promptCacheStats.js.map +1 -0
- package/dist/cli/state/restoreState.js +70 -0
- package/dist/cli/state/restoreState.js.map +1 -0
- package/dist/cli/toolRegistry.js +8 -0
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/tools/askUser.js +71 -0
- package/dist/cli/tools/askUser.js.map +1 -0
- package/dist/cli/utils/doctor.js +19 -0
- package/dist/cli/utils/doctor.js.map +1 -1
- package/dist/cli/workspace/composeContext.js +216 -0
- package/dist/cli/workspace/composeContext.js.map +1 -0
- package/dist/cli/workspace/workspaceSummary.js +58 -31
- package/dist/cli/workspace/workspaceSummary.js.map +1 -1
- package/dist/cli/zelariMission.js +127 -17
- package/dist/cli/zelariMission.js.map +1 -1
- package/package.json +2 -2
|
@@ -9,7 +9,8 @@ import { resolveFailoverStream } from "../crossProviderFailover.js";
|
|
|
9
9
|
import { resolveShell } from "@zelari/core/harness/tools/builtin/shellResolver";
|
|
10
10
|
import { PROVIDERS } from "../keyStore.js";
|
|
11
11
|
import { createBuiltinToolRegistry } from "../toolRegistry.js";
|
|
12
|
-
import {
|
|
12
|
+
import { buildSystemPromptSplit, systemMessagesFromSplit, getAllTools, SINGLE_AGENT_IDENTITY_MODULE, buildLanguagePolicyModuleFor, } from "@zelari/core/skills";
|
|
13
|
+
import { hashStablePrompt } from "../state/fileStateStore.js";
|
|
13
14
|
import { parseClarificationRequest, cleanAgentContent, } from "@zelari/core";
|
|
14
15
|
import { appendOrExtendStreamingAssistant, appendSystem, appendToolStart, finalizeStreamingAssistant, updateToolMessageEnd, } from "./messageHelpers.js";
|
|
15
16
|
import { setStreaming, finalizeStreaming, startTool, completeTool, } from "./chatState.js";
|
|
@@ -78,8 +79,45 @@ export function useChatTurn(params) {
|
|
|
78
79
|
}
|
|
79
80
|
setBusy(true);
|
|
80
81
|
const workPhase = getPhase();
|
|
82
|
+
// Grok-style ask_user: block the tool-loop until picker resolves so
|
|
83
|
+
// the same harness run continues with the answer as tool_result.
|
|
84
|
+
const onAskUser = setPicker
|
|
85
|
+
? (req) => new Promise((resolve) => {
|
|
86
|
+
const choices = req.choices ?? [];
|
|
87
|
+
if (choices.length < 2) {
|
|
88
|
+
resolve(null);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
setLastClarification({
|
|
92
|
+
question: req.question,
|
|
93
|
+
choices,
|
|
94
|
+
});
|
|
95
|
+
const choiceLines = choices
|
|
96
|
+
.map((c, i) => ` ${i + 1}. ${c}`)
|
|
97
|
+
.join("\n");
|
|
98
|
+
appendSystem(setMessages, `[in attesa di risposta — ask_user]\n${req.question}\n${choiceLines}` +
|
|
99
|
+
(req.context ? `\n_(${req.context})_` : "") +
|
|
100
|
+
"\n→ scegli dalla lista (il turno continua dopo).", Date.now());
|
|
101
|
+
let settled = false;
|
|
102
|
+
const finish = (value) => {
|
|
103
|
+
if (settled)
|
|
104
|
+
return;
|
|
105
|
+
settled = true;
|
|
106
|
+
setPicker(null);
|
|
107
|
+
resolve(value);
|
|
108
|
+
};
|
|
109
|
+
setPicker({
|
|
110
|
+
kind: "clarification",
|
|
111
|
+
title: req.question,
|
|
112
|
+
items: choices.map((c) => ({ value: c, label: c })),
|
|
113
|
+
onAnswer: (value) => finish(value),
|
|
114
|
+
onCancel: () => finish(null),
|
|
115
|
+
});
|
|
116
|
+
})
|
|
117
|
+
: undefined;
|
|
81
118
|
const { registry: toolRegistry } = createBuiltinToolRegistry({
|
|
82
119
|
planMode: workPhase === "plan",
|
|
120
|
+
onAskUser,
|
|
83
121
|
});
|
|
84
122
|
const baseProviderStream = openaiCompatibleProvider(envConfig);
|
|
85
123
|
const failoverResolution = await resolveFailoverStream({
|
|
@@ -111,17 +149,39 @@ export function useChatTurn(params) {
|
|
|
111
149
|
// The plan lives in .zelari/plan.json but the agent had no idea it
|
|
112
150
|
// existed — users had to paste task-file paths by hand. Best-effort:
|
|
113
151
|
// no plan → null → zero prompt-token cost.
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
let
|
|
119
|
-
let zelariReadHint = "";
|
|
152
|
+
// v1.16: unified compose — product truth + draft plan ops; plan is
|
|
153
|
+
// NEVER mislabeled as ragContext (that slot is for real memory only).
|
|
154
|
+
let composedWorkspace = "";
|
|
155
|
+
let composedInstructions = "";
|
|
156
|
+
let hasPlan = false;
|
|
120
157
|
try {
|
|
121
|
-
const {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
const { composeProjectContext } = await import("../workspace/composeContext.js");
|
|
159
|
+
const { hasWorkspacePlan } = await import("../workspace/planDetect.js");
|
|
160
|
+
hasPlan = hasWorkspacePlan(cwd);
|
|
161
|
+
const { loadDurableContext } = await import("../state/loadDurableContext.js");
|
|
162
|
+
const durableState = await loadDurableContext(cwd);
|
|
163
|
+
const composed = composeProjectContext({
|
|
164
|
+
mode: "agent",
|
|
165
|
+
cwd,
|
|
166
|
+
userMessage: userText,
|
|
167
|
+
includeLessons: false,
|
|
168
|
+
durableState: durableState || undefined,
|
|
169
|
+
// Pre-loaded async — skip sync fallback double-read.
|
|
170
|
+
includeDurableState: false,
|
|
171
|
+
});
|
|
172
|
+
composedWorkspace = composed.workspaceContext;
|
|
173
|
+
composedInstructions = composed.projectInstructions;
|
|
174
|
+
// Put durable into volatile workspace path via rag if compose put it
|
|
175
|
+
// in ragContext — agent buildSystemPrompt uses workspaceContext only;
|
|
176
|
+
// merge rag durable into workspace for agent visibility.
|
|
177
|
+
if (composed.ragContext) {
|
|
178
|
+
composedWorkspace = [composedWorkspace, composed.ragContext]
|
|
179
|
+
.filter(Boolean)
|
|
180
|
+
.join("\n\n");
|
|
181
|
+
}
|
|
182
|
+
for (const w of composed.warnings) {
|
|
183
|
+
appendSystem(setMessages, w, Date.now());
|
|
184
|
+
}
|
|
125
185
|
// v0.7.4: close the plan loop. The single agent implements the tasks
|
|
126
186
|
// the council planned, but had no official way to advance their
|
|
127
187
|
// status — it would have to hand-edit plan.json with write_file
|
|
@@ -133,7 +193,7 @@ export function useChatTurn(params) {
|
|
|
133
193
|
// requires (opts.requiredTools), mapping the Electron-era `searchRAG`
|
|
134
194
|
// to the CLI's `searchDocuments`.
|
|
135
195
|
const wantedWorkspaceTools = new Set();
|
|
136
|
-
if (
|
|
196
|
+
if (hasPlan)
|
|
137
197
|
wantedWorkspaceTools.add("updateTask");
|
|
138
198
|
const WORKSPACE_STUB_NAMES = new Set([
|
|
139
199
|
"createPhase",
|
|
@@ -211,7 +271,6 @@ export function useChatTurn(params) {
|
|
|
211
271
|
// not "member of an AI Council".
|
|
212
272
|
const planPhaseBlock = workPhase === "plan"
|
|
213
273
|
? [
|
|
214
|
-
"",
|
|
215
274
|
"# Work Phase: PLAN",
|
|
216
275
|
"You are in PLAN mode. Explore and design only.",
|
|
217
276
|
"- Do NOT implement production code or run destructive shell commands.",
|
|
@@ -221,39 +280,42 @@ export function useChatTurn(params) {
|
|
|
221
280
|
].join("\n")
|
|
222
281
|
: workPhase === "build"
|
|
223
282
|
? [
|
|
224
|
-
"",
|
|
225
283
|
"# Work Phase: BUILD",
|
|
226
284
|
"Implement on disk. Prefer acting over describing.",
|
|
227
285
|
"- Prior plan/synthesis text is a SPEC to apply — not proof files already changed.",
|
|
228
286
|
"- You MUST use write_file/edit_file for every file you change before claiming done.",
|
|
229
287
|
"- After read_file: if the planned change is missing, WRITE it — do not stop at analysis.",
|
|
230
288
|
"- Never claim already-implemented based only on reading a plan or skimming code.",
|
|
231
|
-
|
|
232
|
-
? "-
|
|
289
|
+
hasPlan
|
|
290
|
+
? "- A draft plan exists under .zelari/ — implement grounded tasks and update statuses; do not treat design docs as shipped code."
|
|
233
291
|
: "",
|
|
234
292
|
]
|
|
235
293
|
.filter(Boolean)
|
|
236
294
|
.join("\n")
|
|
237
295
|
: "";
|
|
296
|
+
// Stable platform/shell block (session-constant). Phase/plan go in volatile.
|
|
238
297
|
const shellContextBlock = [
|
|
239
298
|
"# Platform & Shell",
|
|
240
299
|
`platform: ${process.platform}`,
|
|
241
300
|
`shell: ${resolvedShell.via}`,
|
|
242
301
|
shellGuidance,
|
|
243
302
|
nonInteractiveGuidance,
|
|
244
|
-
planPhaseBlock,
|
|
245
303
|
"",
|
|
246
304
|
"# Working Directory",
|
|
247
305
|
`You are running in: ${cwd}`,
|
|
248
306
|
"All relative file paths are resolved against this directory. Always work with real files here.",
|
|
249
|
-
|
|
307
|
+
].join("\n");
|
|
308
|
+
const volatileSessionBlock = [
|
|
309
|
+
planPhaseBlock,
|
|
310
|
+
hasPlan
|
|
250
311
|
? [
|
|
251
|
-
"",
|
|
252
312
|
"# Plan Tracking",
|
|
253
|
-
'- Plan tasks: when you START
|
|
254
|
-
]
|
|
255
|
-
:
|
|
256
|
-
]
|
|
313
|
+
'- Plan tasks (draft ops): when you START a task call updateTask {taskId, status: "in_progress"}; when complete and verified call updateTask {taskId, status: "done"}. NEVER edit .zelari/plan.json by hand. Prefer product tree over design docs.',
|
|
314
|
+
].join("\n")
|
|
315
|
+
: "",
|
|
316
|
+
]
|
|
317
|
+
.filter(Boolean)
|
|
318
|
+
.join("\n\n");
|
|
257
319
|
const singleAgentRole = {
|
|
258
320
|
id: "single",
|
|
259
321
|
name: "Zelari Code",
|
|
@@ -264,17 +326,23 @@ export function useChatTurn(params) {
|
|
|
264
326
|
tools: toolListNames,
|
|
265
327
|
systemPrompt: shellContextBlock,
|
|
266
328
|
};
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
const workspaceContext = [
|
|
329
|
+
// Composed workspace already includes product tree + draft plan ops +
|
|
330
|
+
// epistemic banner. ragContext stays empty on the agent path (memory
|
|
331
|
+
// is zelari-only) so plan is never mislabeled as "Retrieved Knowledge".
|
|
332
|
+
const workspaceContext = [
|
|
333
|
+
volatileSessionBlock,
|
|
334
|
+
composedWorkspace,
|
|
335
|
+
]
|
|
271
336
|
.filter(Boolean)
|
|
272
337
|
.join("\n\n");
|
|
273
338
|
// v1.5.3: build via the shared builder. Wrap in try/catch with a
|
|
274
339
|
// minimal fallback so a builder failure (e.g. test context without a
|
|
275
340
|
// populated catalog) never breaks dispatch — the harness still gets a
|
|
276
341
|
// usable system prompt and the turn proceeds.
|
|
277
|
-
|
|
342
|
+
// Cache Wars: stable/volatile split so plan/workspace updates do not
|
|
343
|
+
// bust the cached prefix (identity + tools + platform).
|
|
344
|
+
let systemMessages = [];
|
|
345
|
+
let lastStableHash;
|
|
278
346
|
try {
|
|
279
347
|
// v1.7.0: detect the user's language and inject the language-policy
|
|
280
348
|
// module so the agent replies in the user's language. Honors
|
|
@@ -283,13 +351,11 @@ export function useChatTurn(params) {
|
|
|
283
351
|
// — it lives in priority space (5) so it sorts BEFORE the base-identity
|
|
284
352
|
// module (10): the model sets language scaffolding before reading role text.
|
|
285
353
|
const languageModule = buildLanguagePolicyModuleFor(userText);
|
|
286
|
-
const
|
|
287
|
-
const projectInstructions = loadProjectInstructions(process.cwd()).content;
|
|
288
|
-
systemPrompt = buildSystemPrompt(singleAgentRole, {
|
|
354
|
+
const split = buildSystemPromptSplit(singleAgentRole, {
|
|
289
355
|
tools: getAllTools(),
|
|
290
356
|
toolNames: toolListNames,
|
|
291
357
|
mode: "agent",
|
|
292
|
-
projectInstructions:
|
|
358
|
+
projectInstructions: composedInstructions || undefined,
|
|
293
359
|
aiConfig: {
|
|
294
360
|
enabledSkills: [],
|
|
295
361
|
enabledTools: toolListNames,
|
|
@@ -300,14 +366,18 @@ export function useChatTurn(params) {
|
|
|
300
366
|
agentSkillConfigs: [],
|
|
301
367
|
},
|
|
302
368
|
workspaceContext: workspaceContext || undefined,
|
|
303
|
-
|
|
369
|
+
// Do NOT put plan text here — that was mislabeled as RAG and
|
|
370
|
+
// taught models to treat design vault as retrieved knowledge.
|
|
371
|
+
ragContext: undefined,
|
|
304
372
|
});
|
|
373
|
+
lastStableHash = hashStablePrompt(split.stable);
|
|
374
|
+
systemMessages = systemMessagesFromSplit(split);
|
|
305
375
|
}
|
|
306
376
|
catch {
|
|
307
377
|
// Fallback: identity + platform/shell + tool list. Keeps the turn
|
|
308
378
|
// runnable even if the builder or catalog is unavailable.
|
|
309
379
|
const languageModule = buildLanguagePolicyModuleFor(userText);
|
|
310
|
-
|
|
380
|
+
const fallback = [
|
|
311
381
|
SINGLE_AGENT_IDENTITY_MODULE.content,
|
|
312
382
|
languageModule.content,
|
|
313
383
|
shellContextBlock,
|
|
@@ -315,8 +385,9 @@ export function useChatTurn(params) {
|
|
|
315
385
|
"You can call these tools. Use them to take action and gather information autonomously:",
|
|
316
386
|
toolList,
|
|
317
387
|
...(workspaceContext ? ["", workspaceContext] : []),
|
|
318
|
-
...(planSummary ? ["", planSummary] : []),
|
|
319
388
|
].join("\n");
|
|
389
|
+
lastStableHash = hashStablePrompt(fallback);
|
|
390
|
+
systemMessages = [{ role: "system", content: fallback }];
|
|
320
391
|
}
|
|
321
392
|
// v0.7.1 (A2): per-turn tool-call budget for single-prompt turns.
|
|
322
393
|
// The council sets 5; the single-prompt path previously set NONE, so a
|
|
@@ -338,7 +409,7 @@ export function useChatTurn(params) {
|
|
|
338
409
|
model: envConfig.model,
|
|
339
410
|
provider: "openai-compatible",
|
|
340
411
|
messages: [
|
|
341
|
-
|
|
412
|
+
...systemMessages,
|
|
342
413
|
// v1.8.0: shared rolling history (agent/council/zelari) so short
|
|
343
414
|
// answers bind to prior ---QUESTION--- blocks. Possibly empty
|
|
344
415
|
// when ZELARI_HISTORY_TURNS=0.
|
|
@@ -559,6 +630,14 @@ export function useChatTurn(params) {
|
|
|
559
630
|
question: clar.question,
|
|
560
631
|
choices: clar.choices,
|
|
561
632
|
});
|
|
633
|
+
// Visible prompt while the model "waits": turn is finished,
|
|
634
|
+
// busy=false, SelectList (or typed short answer) continues.
|
|
635
|
+
const choiceLines = clar.choices
|
|
636
|
+
.map((c, i) => ` ${i + 1}. ${c}`)
|
|
637
|
+
.join("\n");
|
|
638
|
+
appendSystem(setMessages, `[in attesa di risposta]\n${clar.question}\n${choiceLines}` +
|
|
639
|
+
(clar.context ? `\n_(${clar.context})_` : "") +
|
|
640
|
+
"\n→ scegli dalla lista oppure digita la risposta e invia.", Date.now());
|
|
562
641
|
if (setPicker) {
|
|
563
642
|
setPicker({
|
|
564
643
|
kind: "clarification",
|
|
@@ -567,6 +646,9 @@ export function useChatTurn(params) {
|
|
|
567
646
|
onAnswer: (value) => {
|
|
568
647
|
void dispatchPrompt(value);
|
|
569
648
|
},
|
|
649
|
+
onCancel: () => {
|
|
650
|
+
appendSystem(setMessages, "[chiarimento annullato — puoi ancora rispondere scrivendo in chat]", Date.now());
|
|
651
|
+
},
|
|
570
652
|
});
|
|
571
653
|
}
|
|
572
654
|
}
|
|
@@ -581,7 +663,7 @@ export function useChatTurn(params) {
|
|
|
581
663
|
harnessRef.current = null;
|
|
582
664
|
setQueueCount(0);
|
|
583
665
|
setBusy(false);
|
|
584
|
-
setSessionStats((prev) => computeSessionStatsDelta(realUsage, userText, assistantContent, envConfig.model, prev));
|
|
666
|
+
setSessionStats((prev) => computeSessionStatsDelta(realUsage, userText, assistantContent, envConfig.model, prev, lastStableHash ? { stableHash: lastStableHash } : undefined));
|
|
585
667
|
}
|
|
586
668
|
}
|
|
587
669
|
catch (err) {
|
|
@@ -694,12 +776,46 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
694
776
|
const { createWorkspaceToolRegistry } = await import("../workspace/toolRegistry.js");
|
|
695
777
|
const { setWorkspaceStubs } = await import("@zelari/core/skills");
|
|
696
778
|
const { runPostCouncilHook } = await import("../workspace/postCouncilHook.js");
|
|
697
|
-
const {
|
|
698
|
-
const { buildLessonsSummary } = await import("../workspace/buildLessonsSummary.js");
|
|
779
|
+
const { composeProjectContext } = await import("../workspace/composeContext.js");
|
|
699
780
|
const { FeedbackStore } = await import("../councilFeedback.js");
|
|
700
781
|
const workPhase = getPhase();
|
|
782
|
+
const onAskUserCouncil = setPicker
|
|
783
|
+
? (req) => new Promise((resolve) => {
|
|
784
|
+
const choices = req.choices ?? [];
|
|
785
|
+
if (choices.length < 2) {
|
|
786
|
+
resolve(null);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
setLastClarification({
|
|
790
|
+
question: req.question,
|
|
791
|
+
choices,
|
|
792
|
+
});
|
|
793
|
+
const choiceLines = choices
|
|
794
|
+
.map((c, i) => ` ${i + 1}. ${c}`)
|
|
795
|
+
.join("\n");
|
|
796
|
+
appendSystem(setMessages, `[council in attesa — ask_user]\n${req.question}\n${choiceLines}` +
|
|
797
|
+
(req.context ? `\n_(${req.context})_` : "") +
|
|
798
|
+
"\n→ scegli dalla lista (il membro riprende dopo la risposta).", Date.now());
|
|
799
|
+
let settled = false;
|
|
800
|
+
const finish = (value) => {
|
|
801
|
+
if (settled)
|
|
802
|
+
return;
|
|
803
|
+
settled = true;
|
|
804
|
+
setPicker(null);
|
|
805
|
+
resolve(value);
|
|
806
|
+
};
|
|
807
|
+
setPicker({
|
|
808
|
+
kind: "clarification",
|
|
809
|
+
title: req.question,
|
|
810
|
+
items: choices.map((c) => ({ value: c, label: c })),
|
|
811
|
+
onAnswer: (value) => finish(value),
|
|
812
|
+
onCancel: () => finish(null),
|
|
813
|
+
});
|
|
814
|
+
})
|
|
815
|
+
: undefined;
|
|
701
816
|
const { registry: councilToolRegistry } = createBuiltinToolRegistry({
|
|
702
817
|
planMode: workPhase === "plan",
|
|
818
|
+
onAskUser: onAskUserCouncil,
|
|
703
819
|
});
|
|
704
820
|
const workspaceCtx = createWorkspaceContext();
|
|
705
821
|
const workspaceReg = createWorkspaceToolRegistry(workspaceCtx);
|
|
@@ -718,7 +834,10 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
718
834
|
// single-agent path — zero extra spawns).
|
|
719
835
|
try {
|
|
720
836
|
const { registerMcpTools } = await import("../mcp/mcpManager.js");
|
|
721
|
-
|
|
837
|
+
// Council: skip Cua desktop tools by default (context hygiene).
|
|
838
|
+
const mcp = await registerMcpTools(councilToolRegistry, process.cwd(), {
|
|
839
|
+
councilMode: true,
|
|
840
|
+
});
|
|
722
841
|
for (const w of mcp.warnings)
|
|
723
842
|
appendSystem(setMessages, w);
|
|
724
843
|
}
|
|
@@ -745,6 +864,11 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
745
864
|
default: 15,
|
|
746
865
|
min: 1,
|
|
747
866
|
});
|
|
867
|
+
// Wire soft/hard tool-loop budgets into every council member harness
|
|
868
|
+
// (previously only the single-agent path set these — members defaulted
|
|
869
|
+
// to harness soft=30 with unbounded soft×3 hard extension).
|
|
870
|
+
const councilMaxToolLoop = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, { default: 30, min: 1 });
|
|
871
|
+
const councilMaxToolLoopHard = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_HARD, { default: 0, min: 0 });
|
|
748
872
|
// v0.7.1 (A3): track member completion so the AGENTS.MD hook only runs when
|
|
749
873
|
// the council actually produced output. v0.7.1 (A4): track repeated provider
|
|
750
874
|
// errors to abort the remaining members instead of grinding through every
|
|
@@ -763,6 +887,37 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
763
887
|
let sliceRan = false;
|
|
764
888
|
let sliceDegraded = false;
|
|
765
889
|
const PROVIDER_ERROR_ABORT_THRESHOLD = 2;
|
|
890
|
+
// Pre-load durable HEAD once (async materialize) for all council members.
|
|
891
|
+
let councilCompose = { workspaceContext: formatHistoryForCouncil(4) || "" };
|
|
892
|
+
try {
|
|
893
|
+
const { loadDurableContext } = await import("../state/loadDurableContext.js");
|
|
894
|
+
const durableState = await loadDurableContext(process.cwd());
|
|
895
|
+
const composed = composeProjectContext({
|
|
896
|
+
mode: overrides.ragContext ? "zelari" : "council",
|
|
897
|
+
cwd: process.cwd(),
|
|
898
|
+
userMessage: effectiveText,
|
|
899
|
+
memoryHits: overrides.ragContext,
|
|
900
|
+
durableState: durableState || undefined,
|
|
901
|
+
historySnippet: formatHistoryForCouncil(4) || undefined,
|
|
902
|
+
includeLessons: true,
|
|
903
|
+
includeDurableState: false,
|
|
904
|
+
});
|
|
905
|
+
for (const w of composed.warnings) {
|
|
906
|
+
appendSystem(setMessages, w, Date.now());
|
|
907
|
+
}
|
|
908
|
+
councilCompose = {
|
|
909
|
+
workspaceContext: composed.workspaceContext,
|
|
910
|
+
...(composed.ragContext ? { ragContext: composed.ragContext } : {}),
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
catch {
|
|
914
|
+
if (overrides.ragContext) {
|
|
915
|
+
councilCompose = {
|
|
916
|
+
workspaceContext: formatHistoryForCouncil(4) || "",
|
|
917
|
+
ragContext: overrides.ragContext,
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
}
|
|
766
921
|
try {
|
|
767
922
|
for await (const event of dispatchCouncil(effectiveText, {
|
|
768
923
|
apiKey: envConfig.apiKey,
|
|
@@ -772,30 +927,20 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
772
927
|
sessionId,
|
|
773
928
|
tools: councilToolRegistry,
|
|
774
929
|
feedbackStore: councilFeedbackStore,
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
// v0.7.3: append the existing plan (if any) so a follow-up /council
|
|
780
|
-
// continues it instead of re-planning from scratch.
|
|
781
|
-
// v1.8.0: rolling conversation context so short answers bind across
|
|
782
|
-
// council turns (same history store as the single-agent path).
|
|
783
|
-
workspaceContext: [
|
|
784
|
-
buildWorkspaceSummary(process.cwd()),
|
|
785
|
-
buildPlanSummary(process.cwd(), { userMessage: effectiveText }),
|
|
786
|
-
buildLessonsSummary(process.cwd(), effectiveText),
|
|
787
|
-
formatHistoryForCouncil(4),
|
|
788
|
-
]
|
|
789
|
-
.filter(Boolean)
|
|
790
|
-
.join("\n\n"),
|
|
930
|
+
workspaceContext: councilCompose.workspaceContext,
|
|
931
|
+
...(councilCompose.ragContext
|
|
932
|
+
? { ragContext: councilCompose.ragContext }
|
|
933
|
+
: {}),
|
|
791
934
|
maxToolCallsPerTurn: councilMaxToolCalls,
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
935
|
+
maxToolLoopIterations: councilMaxToolLoop,
|
|
936
|
+
...(councilMaxToolLoopHard > 0
|
|
937
|
+
? { maxToolLoopHardCap: councilMaxToolLoopHard }
|
|
938
|
+
: {}),
|
|
795
939
|
runMode: phaseRunMode,
|
|
796
940
|
...(overrides.maxToolCallsChairman
|
|
797
941
|
? { maxToolCallsChairman: overrides.maxToolCallsChairman }
|
|
798
942
|
: {}),
|
|
943
|
+
...(overrides.skipSpecialists ? { skipSpecialists: true } : {}),
|
|
799
944
|
onCouncilStatus: (message) => {
|
|
800
945
|
appendSystem(setMessages, message, Date.now());
|
|
801
946
|
},
|
|
@@ -811,6 +956,12 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
811
956
|
question: req.question,
|
|
812
957
|
choices,
|
|
813
958
|
});
|
|
959
|
+
const choiceLines = choices
|
|
960
|
+
.map((c, i) => ` ${i + 1}. ${c}`)
|
|
961
|
+
.join("\n");
|
|
962
|
+
appendSystem(setMessages, `[council in attesa]\n${req.question}\n${choiceLines}` +
|
|
963
|
+
(req.context ? `\n_(${req.context})_` : "") +
|
|
964
|
+
"\n→ scegli dalla lista (il membro riprende dopo la risposta).", Date.now());
|
|
814
965
|
let settled = false;
|
|
815
966
|
const finish = (value) => {
|
|
816
967
|
if (settled)
|
|
@@ -1083,6 +1234,48 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
1083
1234
|
appendSystem(setMessages, `[completion] readyToCommit=false — ${n} blocking issue(s)${c.degraded ? " (degraded run)" : ""}`, Date.now());
|
|
1084
1235
|
}
|
|
1085
1236
|
}
|
|
1237
|
+
// Durable State Commit after verified council completion (Palmer).
|
|
1238
|
+
// Skip degraded / failed verification — those must not become HEAD.
|
|
1239
|
+
const verifyOk = hook.verification?.ran
|
|
1240
|
+
? hook.verification.ok === true
|
|
1241
|
+
: false;
|
|
1242
|
+
const completionOk = hook.completion?.completion?.ok === true ||
|
|
1243
|
+
hook.completion?.completion?.readyToCommit === true;
|
|
1244
|
+
if (!degraded.degraded &&
|
|
1245
|
+
!councilAborted &&
|
|
1246
|
+
(verifyOk || completionOk) &&
|
|
1247
|
+
luciferWriteCount > 0) {
|
|
1248
|
+
try {
|
|
1249
|
+
const { tryStateCommit, discoveriesFromOutcome } = await import("../state/commitHelpers.js");
|
|
1250
|
+
const res = await tryStateCommit({
|
|
1251
|
+
projectRoot: process.cwd(),
|
|
1252
|
+
mode: "council",
|
|
1253
|
+
layer: `council:${councilRunMode ?? "run"}`,
|
|
1254
|
+
label: `council ${councilRunMode ?? "run"} verified`,
|
|
1255
|
+
sessionId,
|
|
1256
|
+
verification: {
|
|
1257
|
+
ok: true,
|
|
1258
|
+
ran: hook.verification?.ran ?? completionOk,
|
|
1259
|
+
},
|
|
1260
|
+
withCheckpoint: true,
|
|
1261
|
+
discoveries: discoveriesFromOutcome({
|
|
1262
|
+
stepId: `${sessionId}-${Date.now()}`,
|
|
1263
|
+
synthesis: chairmanSynthesisText,
|
|
1264
|
+
writeCount: luciferWriteCount,
|
|
1265
|
+
note: "Council implementation verified",
|
|
1266
|
+
}),
|
|
1267
|
+
});
|
|
1268
|
+
if (res.ok && res.meta?.id) {
|
|
1269
|
+
appendSystem(setMessages, `[state] commit ${res.meta.id}` +
|
|
1270
|
+
(res.checkpointId
|
|
1271
|
+
? ` · checkpoint ${res.checkpointId}`
|
|
1272
|
+
: ""), Date.now());
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
catch {
|
|
1276
|
+
// fail-open
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1086
1279
|
}
|
|
1087
1280
|
catch {
|
|
1088
1281
|
// Best-effort — never block on AGENTS.MD errors.
|
|
@@ -1171,11 +1364,12 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
|
|
|
1171
1364
|
projectRoot,
|
|
1172
1365
|
memory,
|
|
1173
1366
|
emit,
|
|
1174
|
-
runSlice: async ({ userMessage: slicePrompt, runMode, ragContext }) => {
|
|
1367
|
+
runSlice: async ({ userMessage: slicePrompt, runMode, ragContext, implementerRetry, }) => {
|
|
1175
1368
|
const r = await dispatchCouncilPromptImpl(slicePrompt, deps, {
|
|
1176
1369
|
ragContext,
|
|
1177
1370
|
runMode,
|
|
1178
1371
|
maxToolCallsChairman: chairmanBudget,
|
|
1372
|
+
...(implementerRetry ? { skipSpecialists: true } : {}),
|
|
1179
1373
|
});
|
|
1180
1374
|
return {
|
|
1181
1375
|
completionOk: r.completionOk,
|