@zelari/core 0.7.12 → 0.7.14
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/dist/agents/councilApi.d.ts +135 -0
- package/dist/agents/councilApi.d.ts.map +1 -1
- package/dist/agents/councilApi.js +520 -7
- package/dist/agents/councilApi.js.map +1 -1
- package/dist/agents/roles.d.ts.map +1 -1
- package/dist/agents/roles.js +11 -3
- package/dist/agents/roles.js.map +1 -1
- package/dist/agents/toolSchemas.d.ts.map +1 -1
- package/dist/agents/toolSchemas.js +32 -3
- package/dist/agents/toolSchemas.js.map +1 -1
- package/dist/core/AgentHarness.d.ts +21 -0
- package/dist/core/AgentHarness.d.ts.map +1 -1
- package/dist/core/AgentHarness.js +153 -0
- package/dist/core/AgentHarness.js.map +1 -1
- package/dist/core/tools/builtin/filesystem.d.ts.map +1 -1
- package/dist/core/tools/builtin/filesystem.js +4 -2
- package/dist/core/tools/builtin/filesystem.js.map +1 -1
- package/dist/council/completion/buildCompletion.d.ts +23 -0
- package/dist/council/completion/buildCompletion.d.ts.map +1 -0
- package/dist/council/completion/buildCompletion.js +75 -0
- package/dist/council/completion/buildCompletion.js.map +1 -0
- package/dist/council/completion/index.d.ts +3 -0
- package/dist/council/completion/index.d.ts.map +1 -0
- package/dist/council/completion/index.js +3 -0
- package/dist/council/completion/index.js.map +1 -0
- package/dist/council/completion/types.d.ts +38 -0
- package/dist/council/completion/types.d.ts.map +1 -0
- package/dist/council/completion/types.js +2 -0
- package/dist/council/completion/types.js.map +1 -0
- package/dist/council/index.d.ts +4 -0
- package/dist/council/index.d.ts.map +1 -1
- package/dist/council/index.js +4 -0
- package/dist/council/index.js.map +1 -1
- package/dist/council/lessons/index.d.ts +7 -0
- package/dist/council/lessons/index.d.ts.map +1 -0
- package/dist/council/lessons/index.js +7 -0
- package/dist/council/lessons/index.js.map +1 -0
- package/dist/council/lessons/io.d.ts +5 -0
- package/dist/council/lessons/io.d.ts.map +1 -0
- package/dist/council/lessons/io.js +30 -0
- package/dist/council/lessons/io.js.map +1 -0
- package/dist/council/lessons/isAnswerLeak.d.ts +7 -0
- package/dist/council/lessons/isAnswerLeak.d.ts.map +1 -0
- package/dist/council/lessons/isAnswerLeak.js +26 -0
- package/dist/council/lessons/isAnswerLeak.js.map +1 -0
- package/dist/council/lessons/recallLessons.d.ts +8 -0
- package/dist/council/lessons/recallLessons.d.ts.map +1 -0
- package/dist/council/lessons/recallLessons.js +56 -0
- package/dist/council/lessons/recallLessons.js.map +1 -0
- package/dist/council/lessons/recordFailure.d.ts +7 -0
- package/dist/council/lessons/recordFailure.d.ts.map +1 -0
- package/dist/council/lessons/recordFailure.js +86 -0
- package/dist/council/lessons/recordFailure.js.map +1 -0
- package/dist/council/lessons/signatures.d.ts +6 -0
- package/dist/council/lessons/signatures.d.ts.map +1 -0
- package/dist/council/lessons/signatures.js +31 -0
- package/dist/council/lessons/signatures.js.map +1 -0
- package/dist/council/lessons/types.d.ts +27 -0
- package/dist/council/lessons/types.d.ts.map +1 -0
- package/dist/council/lessons/types.js +2 -0
- package/dist/council/lessons/types.js.map +1 -0
- package/dist/council/modeBanners.d.ts +24 -1
- package/dist/council/modeBanners.d.ts.map +1 -1
- package/dist/council/modeBanners.js +31 -4
- package/dist/council/modeBanners.js.map +1 -1
- package/dist/council/scope/extractTaskScope.d.ts +35 -0
- package/dist/council/scope/extractTaskScope.d.ts.map +1 -0
- package/dist/council/scope/extractTaskScope.js +174 -0
- package/dist/council/scope/extractTaskScope.js.map +1 -0
- package/dist/council/scope/index.d.ts +3 -0
- package/dist/council/scope/index.d.ts.map +1 -0
- package/dist/council/scope/index.js +3 -0
- package/dist/council/scope/index.js.map +1 -0
- package/dist/council/scope/nfrSpecWarn.d.ts +6 -0
- package/dist/council/scope/nfrSpecWarn.d.ts.map +1 -0
- package/dist/council/scope/nfrSpecWarn.js +21 -0
- package/dist/council/scope/nfrSpecWarn.js.map +1 -0
- package/dist/council/verification/autofix.d.ts +12 -0
- package/dist/council/verification/autofix.d.ts.map +1 -0
- package/dist/council/verification/autofix.js +41 -0
- package/dist/council/verification/autofix.js.map +1 -0
- package/dist/council/verification/citeVerify.d.ts +13 -0
- package/dist/council/verification/citeVerify.d.ts.map +1 -0
- package/dist/council/verification/citeVerify.js +73 -0
- package/dist/council/verification/citeVerify.js.map +1 -0
- package/dist/council/verification/completion.d.ts +19 -0
- package/dist/council/verification/completion.d.ts.map +1 -0
- package/dist/council/verification/completion.js +50 -0
- package/dist/council/verification/completion.js.map +1 -0
- package/dist/council/verification/degraded.d.ts +26 -0
- package/dist/council/verification/degraded.d.ts.map +1 -0
- package/dist/council/verification/degraded.js +35 -0
- package/dist/council/verification/degraded.js.map +1 -0
- package/dist/council/verification/honesty.d.ts +6 -0
- package/dist/council/verification/honesty.d.ts.map +1 -0
- package/dist/council/verification/honesty.js +28 -0
- package/dist/council/verification/honesty.js.map +1 -0
- package/dist/council/verification/implementationDelivery.d.ts +26 -0
- package/dist/council/verification/implementationDelivery.d.ts.map +1 -0
- package/dist/council/verification/implementationDelivery.js +70 -0
- package/dist/council/verification/implementationDelivery.js.map +1 -0
- package/dist/council/verification/index.d.ts +15 -0
- package/dist/council/verification/index.d.ts.map +1 -0
- package/dist/council/verification/index.js +15 -0
- package/dist/council/verification/index.js.map +1 -0
- package/dist/council/verification/inlineJsAutofix.d.ts +8 -0
- package/dist/council/verification/inlineJsAutofix.d.ts.map +1 -0
- package/dist/council/verification/inlineJsAutofix.js +85 -0
- package/dist/council/verification/inlineJsAutofix.js.map +1 -0
- package/dist/council/verification/microGate.d.ts +18 -0
- package/dist/council/verification/microGate.d.ts.map +1 -0
- package/dist/council/verification/microGate.js +81 -0
- package/dist/council/verification/microGate.js.map +1 -0
- package/dist/council/verification/motionAutofix.d.ts +8 -0
- package/dist/council/verification/motionAutofix.d.ts.map +1 -0
- package/dist/council/verification/motionAutofix.js +160 -0
- package/dist/council/verification/motionAutofix.js.map +1 -0
- package/dist/council/verification/parseCssMotion.d.ts +22 -0
- package/dist/council/verification/parseCssMotion.d.ts.map +1 -0
- package/dist/council/verification/parseCssMotion.js +142 -0
- package/dist/council/verification/parseCssMotion.js.map +1 -0
- package/dist/council/verification/runChecks.d.ts +10 -0
- package/dist/council/verification/runChecks.d.ts.map +1 -0
- package/dist/council/verification/runChecks.js +254 -0
- package/dist/council/verification/runChecks.js.map +1 -0
- package/dist/council/verification/synthesisAudit.d.ts +14 -0
- package/dist/council/verification/synthesisAudit.d.ts.map +1 -0
- package/dist/council/verification/synthesisAudit.js +106 -0
- package/dist/council/verification/synthesisAudit.js.map +1 -0
- package/dist/council/verification/tiers.d.ts +8 -0
- package/dist/council/verification/tiers.d.ts.map +1 -0
- package/dist/council/verification/tiers.js +46 -0
- package/dist/council/verification/tiers.js.map +1 -0
- package/dist/council/verification/types.d.ts +53 -0
- package/dist/council/verification/types.d.ts.map +1 -0
- package/dist/council/verification/types.js +5 -0
- package/dist/council/verification/types.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
1
3
|
import { getCouncilAgents, swapMembers } from './roles.js';
|
|
2
4
|
import { getProviderTools } from './toolSchemas.js';
|
|
3
5
|
import { buildSystemPrompt, computeAgentTools } from './systemPromptBuilder.js';
|
|
4
6
|
import { getAllTools } from './tools.js';
|
|
5
7
|
import { createBrainEvent } from '../shared/events.js';
|
|
6
|
-
import { AgentHarness } from '../core/AgentHarness.js';
|
|
8
|
+
import { AgentHarness, normalizeTextToolArgs, parseTextToolCalls, } from '../core/AgentHarness.js';
|
|
7
9
|
import { councilModeBanner } from '../council/modeBanners.js';
|
|
8
10
|
import { councilTierFromSize } from '../council/runMode.js';
|
|
11
|
+
import { parseProjectRootFromWorkspaceContext, runChairmanMicroGate, } from '../council/verification/microGate.js';
|
|
12
|
+
import { buildImplementationVerifyRetryPrompt, checkImplementationCompletion, resolveVerifyRetryTool, } from '../council/verification/completion.js';
|
|
13
|
+
import { warnIfNfrSpecMissing } from '../council/scope/nfrSpecWarn.js';
|
|
14
|
+
import { loadNfrSpec, DEFAULT_NFR_SPEC, runImplementationVerification, } from '../council/verification/runChecks.js';
|
|
15
|
+
import { buildDeliveryFixPrompt, buildImplementationWriteRetryPrompt, checkImplementationDelivery, countEmittedWriteTools, filterDeliveryBlockingFails, } from '../council/verification/implementationDelivery.js';
|
|
16
|
+
import { applyInlineJsAutofix } from '../council/verification/inlineJsAutofix.js';
|
|
9
17
|
/**
|
|
10
18
|
* Council members whose tool-emission retry is DISABLED.
|
|
11
19
|
*
|
|
@@ -60,6 +68,24 @@ export function cleanAgentContent(text) {
|
|
|
60
68
|
.replace(/---QUESTION---[\s\S]*?---END---/g, '')
|
|
61
69
|
.trim();
|
|
62
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Tools that mutate project files. In implementation-mode council runs only the
|
|
73
|
+
* chairman (Lucifero) implements; specialists + Minosse analyze and hand off.
|
|
74
|
+
* We strip these from every non-implementer so multiple agents never edit the
|
|
75
|
+
* same files (multi-writer chaos) and "who implemented" stays unambiguous.
|
|
76
|
+
* Note: specialists inherit write tools via skill `requiredTools`, so this must
|
|
77
|
+
* filter the RESULT of computeAgentTools, not just the declared role tools.
|
|
78
|
+
*/
|
|
79
|
+
export const MUTATING_PROJECT_TOOLS = ['write_file', 'edit_file'];
|
|
80
|
+
/**
|
|
81
|
+
* Remove file-mutating tools for non-implementer members in implementation mode.
|
|
82
|
+
* Design-phase and the implementer (chairman) keep the full set unchanged.
|
|
83
|
+
*/
|
|
84
|
+
export function restrictImplementationWrites(toolNames, opts) {
|
|
85
|
+
if (opts.runMode !== 'implementation' || opts.isImplementer)
|
|
86
|
+
return toolNames;
|
|
87
|
+
return toolNames.filter((t) => !MUTATING_PROJECT_TOOLS.includes(t));
|
|
88
|
+
}
|
|
63
89
|
function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, priorOutputs, aiConfig, executableTools, runMode = 'implementation') {
|
|
64
90
|
// v0.7.5: the AVAILABLE TOOLS prompt block must match the schemas the
|
|
65
91
|
// harness actually advertises. The v0.7.3 fix filtered the schemas
|
|
@@ -80,7 +106,7 @@ function buildAgentMessages(agent, userMessage, ragContext, workspaceContext, pr
|
|
|
80
106
|
});
|
|
81
107
|
const messages = [
|
|
82
108
|
{ role: 'system', content: enhancedSystemPrompt },
|
|
83
|
-
{ role: 'system', content: councilModeBanner(runMode) },
|
|
109
|
+
{ role: 'system', content: councilModeBanner(runMode, { isImplementer: agent.id === 'lucifer' }) },
|
|
84
110
|
{ role: 'system', content: 'IMPORTANT: Before making any tool calls or expensive operations, check if the information already exists in the shared context from previous agents. Avoid redundant work.' },
|
|
85
111
|
];
|
|
86
112
|
if (ragContext) {
|
|
@@ -202,7 +228,13 @@ export async function* runCouncilPure(userMessage, config, callbacks = {}) {
|
|
|
202
228
|
const override = config.agentModels?.[agent.id];
|
|
203
229
|
const effectiveProvider = override?.providerId ?? config.provider ?? 'minimax';
|
|
204
230
|
const effectiveModel = override?.model ?? config.model;
|
|
205
|
-
|
|
231
|
+
// Specialists analyze and hand off; only the chairman (Lucifero) implements
|
|
232
|
+
// in implementation mode. Strip write/edit so members don't all edit the
|
|
233
|
+
// same files. (Design-phase and the chairman keep the full set.)
|
|
234
|
+
const agentToolNames = filterExecutable(restrictImplementationWrites(computeAgentTools(agent, config.aiConfig), {
|
|
235
|
+
runMode,
|
|
236
|
+
isImplementer: false,
|
|
237
|
+
}));
|
|
206
238
|
const agentTools = agentToolNames.length > 0
|
|
207
239
|
? getProviderTools(agentToolNames).map((t) => ({
|
|
208
240
|
name: t.function.name,
|
|
@@ -300,6 +332,9 @@ export async function* runCouncilPure(userMessage, config, callbacks = {}) {
|
|
|
300
332
|
else if (isDesignPhase) {
|
|
301
333
|
enforceDesignPhaseToolEmissions(agent.id, emittedToolNames);
|
|
302
334
|
}
|
|
335
|
+
if (isDesignPhase) {
|
|
336
|
+
warnIfNfrSpecMissing(agent.id, userMessage, emittedToolNames);
|
|
337
|
+
}
|
|
303
338
|
const memberDuration = Date.now() - memberStart;
|
|
304
339
|
emitMemberCost({
|
|
305
340
|
memberId: agent.id,
|
|
@@ -369,11 +404,11 @@ export async function* runCouncilPure(userMessage, config, callbacks = {}) {
|
|
|
369
404
|
sessionId,
|
|
370
405
|
messages: buildAgentMessages(oracle, `Review these proposals for: "${userMessage}"`, '', '', anonymized, config.aiConfig, executableNames, runMode),
|
|
371
406
|
tools: (() => {
|
|
372
|
-
const oracleToolNames = filterExecutable(Array.from(new Set([
|
|
407
|
+
const oracleToolNames = filterExecutable(restrictImplementationWrites(Array.from(new Set([
|
|
373
408
|
'createDocument',
|
|
374
409
|
'searchDocuments',
|
|
375
410
|
...computeAgentTools(oracle, config.aiConfig),
|
|
376
|
-
])));
|
|
411
|
+
])), { runMode, isImplementer: false }));
|
|
377
412
|
return oracleToolNames.length > 0
|
|
378
413
|
? getProviderTools(oracleToolNames).map((tool) => ({
|
|
379
414
|
name: tool.function.name,
|
|
@@ -550,6 +585,14 @@ export async function* runCouncilPure(userMessage, config, callbacks = {}) {
|
|
|
550
585
|
// check below (Lucifero MUST emit at least one createDocument for
|
|
551
586
|
// the synthesis; see enforceDesignPhaseToolEmissions).
|
|
552
587
|
const emittedToolNames = [];
|
|
588
|
+
const pendingChairmanWrites = new Map();
|
|
589
|
+
let successfulWriteCount = 0;
|
|
590
|
+
// Increment 3: accumulate deduped motion violations across the whole turn
|
|
591
|
+
// (keyed by id|file|line) and remember the target files written, so we can
|
|
592
|
+
// run ONE post-turn fix pass instead of emitting a repeated per-write flood.
|
|
593
|
+
const chairmanViolations = new Map();
|
|
594
|
+
const changedTargetFiles = new Set();
|
|
595
|
+
let chairmanProjectRoot = parseProjectRootFromWorkspaceContext(config.workspaceContext ?? '');
|
|
553
596
|
const memberStart = Date.now();
|
|
554
597
|
try {
|
|
555
598
|
for await (const event of chairmanHarness.run()) {
|
|
@@ -557,6 +600,47 @@ export async function* runCouncilPure(userMessage, config, callbacks = {}) {
|
|
|
557
600
|
if (event.type === 'tool_execution_start') {
|
|
558
601
|
toolCalls += 1;
|
|
559
602
|
emittedToolNames.push(event.toolName);
|
|
603
|
+
if (!isDesignPhase &&
|
|
604
|
+
(event.toolName === 'write_file' || event.toolName === 'edit_file') &&
|
|
605
|
+
typeof event.args.path === 'string') {
|
|
606
|
+
pendingChairmanWrites.set(event.toolCallId, {
|
|
607
|
+
path: event.args.path,
|
|
608
|
+
toolName: event.toolName,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (!isDesignPhase && event.type === 'tool_execution_end') {
|
|
613
|
+
const pending = pendingChairmanWrites.get(event.toolCallId);
|
|
614
|
+
if (pending) {
|
|
615
|
+
pendingChairmanWrites.delete(event.toolCallId);
|
|
616
|
+
if (!event.isError) {
|
|
617
|
+
let wrote = pending.toolName === 'write_file';
|
|
618
|
+
if (pending.toolName === 'edit_file' && typeof event.result === 'string') {
|
|
619
|
+
try {
|
|
620
|
+
const parsed = JSON.parse(event.result);
|
|
621
|
+
wrote = (parsed.occurrencesReplaced ?? 0) > 0;
|
|
622
|
+
}
|
|
623
|
+
catch {
|
|
624
|
+
wrote = false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (wrote) {
|
|
628
|
+
successfulWriteCount += 1;
|
|
629
|
+
const projectRoot = parseProjectRootFromWorkspaceContext(config.workspaceContext);
|
|
630
|
+
if (projectRoot) {
|
|
631
|
+
chairmanProjectRoot = projectRoot;
|
|
632
|
+
changedTargetFiles.add(pending.path);
|
|
633
|
+
for (const w of runChairmanMicroGate({
|
|
634
|
+
projectRoot,
|
|
635
|
+
relPath: pending.path,
|
|
636
|
+
zelariRoot: `${projectRoot}/.zelari`,
|
|
637
|
+
})) {
|
|
638
|
+
chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ''}`, w);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
560
644
|
}
|
|
561
645
|
if (event.type === 'message_end' && event.usage) {
|
|
562
646
|
usage = event.usage;
|
|
@@ -572,7 +656,8 @@ export async function* runCouncilPure(userMessage, config, callbacks = {}) {
|
|
|
572
656
|
// We must detect this and mark the chairman as errored so the
|
|
573
657
|
// member_cost reflects reality, otherwise the synthesis appears
|
|
574
658
|
// successful when in fact the model never produced text.
|
|
575
|
-
|
|
659
|
+
// text_tools_parse_failed is advisory — the post-turn fix loop still runs.
|
|
660
|
+
if (event.severity !== 'cancelled' && event.code !== 'text_tools_parse_failed') {
|
|
576
661
|
errored = true;
|
|
577
662
|
lastErrorMessage = event.message;
|
|
578
663
|
}
|
|
@@ -616,6 +701,99 @@ export async function* runCouncilPure(userMessage, config, callbacks = {}) {
|
|
|
616
701
|
else if (isDesignPhase) {
|
|
617
702
|
enforceDesignPhaseToolEmissions(chairman.id, emittedToolNames);
|
|
618
703
|
}
|
|
704
|
+
else if (!errored) {
|
|
705
|
+
// Increment 5: implementation delivery — force writes when prose-only,
|
|
706
|
+
// replay ---TOOLS---, motion fix, then verify-driven delivery loop.
|
|
707
|
+
if (chairmanProjectRoot) {
|
|
708
|
+
const zelariRoot = `${chairmanProjectRoot}/.zelari`;
|
|
709
|
+
const spec = loadNfrSpec(zelariRoot) ?? DEFAULT_NFR_SPEC;
|
|
710
|
+
for (const rel of spec.targets) {
|
|
711
|
+
if (!existsSync(join(chairmanProjectRoot, rel)))
|
|
712
|
+
continue;
|
|
713
|
+
changedTargetFiles.add(rel);
|
|
714
|
+
for (const w of runChairmanMicroGate({ projectRoot: chairmanProjectRoot, relPath: rel, zelariRoot })) {
|
|
715
|
+
chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ''}`, w);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (fullText.includes('---TOOLS---') &&
|
|
720
|
+
chairmanProjectRoot &&
|
|
721
|
+
config.tools) {
|
|
722
|
+
const replayed = yield* replayChairmanTextTools({
|
|
723
|
+
synthesisText: fullText,
|
|
724
|
+
projectRoot: chairmanProjectRoot,
|
|
725
|
+
toolRegistry: config.tools,
|
|
726
|
+
sessionId,
|
|
727
|
+
memberId: chairman.id,
|
|
728
|
+
});
|
|
729
|
+
successfulWriteCount += replayed;
|
|
730
|
+
const zelariRootReplay = `${chairmanProjectRoot}/.zelari`;
|
|
731
|
+
const specReplay = loadNfrSpec(zelariRootReplay) ?? DEFAULT_NFR_SPEC;
|
|
732
|
+
for (const rel of specReplay.targets) {
|
|
733
|
+
if (!existsSync(join(chairmanProjectRoot, rel)))
|
|
734
|
+
continue;
|
|
735
|
+
changedTargetFiles.add(rel);
|
|
736
|
+
for (const w of runChairmanMicroGate({
|
|
737
|
+
projectRoot: chairmanProjectRoot,
|
|
738
|
+
relPath: rel,
|
|
739
|
+
zelariRoot: zelariRootReplay,
|
|
740
|
+
})) {
|
|
741
|
+
chairmanViolations.set(`${w.id}|${w.file}|${w.line ?? ''}`, w);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (chairmanProjectRoot) {
|
|
746
|
+
const deliveryCheck = checkImplementationDelivery(successfulWriteCount, countEmittedWriteTools(emittedToolNames));
|
|
747
|
+
if (!deliveryCheck.ok) {
|
|
748
|
+
yield* applyImplementationWriteRetry({
|
|
749
|
+
chairman,
|
|
750
|
+
check: deliveryCheck,
|
|
751
|
+
sessionId,
|
|
752
|
+
userMessage,
|
|
753
|
+
agentOutputs,
|
|
754
|
+
config,
|
|
755
|
+
effectiveProvider,
|
|
756
|
+
effectiveModel,
|
|
757
|
+
executableNames,
|
|
758
|
+
onToolCall: () => { toolCalls += 1; },
|
|
759
|
+
onSuccessfulWrite: () => { successfulWriteCount += 1; },
|
|
760
|
+
onCouncilStatus: callbacks.onCouncilStatus,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (chairmanViolations.size > 0 && chairmanProjectRoot) {
|
|
765
|
+
yield* runChairmanFixLoop({
|
|
766
|
+
chairman,
|
|
767
|
+
violations: chairmanViolations,
|
|
768
|
+
changedFiles: changedTargetFiles,
|
|
769
|
+
projectRoot: chairmanProjectRoot,
|
|
770
|
+
executableNames,
|
|
771
|
+
sessionId,
|
|
772
|
+
userMessage,
|
|
773
|
+
agentOutputs,
|
|
774
|
+
config,
|
|
775
|
+
effectiveProvider,
|
|
776
|
+
effectiveModel,
|
|
777
|
+
onToolCall: () => { toolCalls += 1; },
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
if (chairmanProjectRoot) {
|
|
781
|
+
yield* runChairmanDeliveryLoop({
|
|
782
|
+
chairman,
|
|
783
|
+
projectRoot: chairmanProjectRoot,
|
|
784
|
+
changedFiles: changedTargetFiles,
|
|
785
|
+
executableNames,
|
|
786
|
+
sessionId,
|
|
787
|
+
userMessage,
|
|
788
|
+
agentOutputs,
|
|
789
|
+
config,
|
|
790
|
+
effectiveProvider,
|
|
791
|
+
effectiveModel,
|
|
792
|
+
onToolCall: () => { toolCalls += 1; },
|
|
793
|
+
onCouncilStatus: callbacks.onCouncilStatus,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
619
797
|
const memberDuration = Date.now() - memberStart;
|
|
620
798
|
// If the chairman errored mid-flight but produced some text, keep it
|
|
621
799
|
// (don't lose partial synthesis) but mark the run as errored.
|
|
@@ -653,6 +831,338 @@ export async function* runCouncilPure(userMessage, config, callbacks = {}) {
|
|
|
653
831
|
durationMs: 0,
|
|
654
832
|
};
|
|
655
833
|
}
|
|
834
|
+
/**
|
|
835
|
+
* Implementation-mode anti-resa retry: force grep/bash after writes.
|
|
836
|
+
*/
|
|
837
|
+
export async function* applyCompletionRetry(args) {
|
|
838
|
+
const check = checkImplementationCompletion(args.emittedToolNames);
|
|
839
|
+
if (check.ok)
|
|
840
|
+
return;
|
|
841
|
+
const retryTool = resolveVerifyRetryTool(args.executableNames);
|
|
842
|
+
if (!retryTool) {
|
|
843
|
+
// eslint-disable-next-line no-console
|
|
844
|
+
console.warn('[council] implementation verify retry skipped — no grep_content/bash in registry');
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (!shouldRetryMember([retryTool], 0))
|
|
848
|
+
return;
|
|
849
|
+
// eslint-disable-next-line no-console
|
|
850
|
+
console.warn(`[council] ${args.agent.id} retrying missing verify tool: ${retryTool}`);
|
|
851
|
+
try {
|
|
852
|
+
const retryGenerator = runRetryTurnForMember({
|
|
853
|
+
agent: args.agent,
|
|
854
|
+
missingToolNames: [retryTool],
|
|
855
|
+
executableTools: args.executableNames,
|
|
856
|
+
userMessage: args.userMessage,
|
|
857
|
+
ragContext: args.config.ragContext,
|
|
858
|
+
workspaceContext: args.config.workspaceContext,
|
|
859
|
+
priorOutputs: args.agentOutputs,
|
|
860
|
+
aiConfig: args.config.aiConfig,
|
|
861
|
+
sessionId: args.sessionId,
|
|
862
|
+
effectiveModel: args.effectiveModel,
|
|
863
|
+
effectiveProvider: args.effectiveProvider,
|
|
864
|
+
eventBus: args.config.eventBus,
|
|
865
|
+
toolRegistry: args.config.tools,
|
|
866
|
+
providerStream: args.config.providerStream,
|
|
867
|
+
runMode: args.config.runMode,
|
|
868
|
+
retryPrompt: buildImplementationVerifyRetryPrompt(retryTool),
|
|
869
|
+
});
|
|
870
|
+
for await (const event of retryGenerator) {
|
|
871
|
+
if (event.type === 'tool_execution_start') {
|
|
872
|
+
args.onToolCall();
|
|
873
|
+
args.emittedToolNames.push(event.toolName);
|
|
874
|
+
}
|
|
875
|
+
yield event;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
catch (retryErr) {
|
|
879
|
+
// eslint-disable-next-line no-console
|
|
880
|
+
console.error(`[council] ${args.agent.id} verify retry failed:`, retryErr);
|
|
881
|
+
}
|
|
882
|
+
const after = checkImplementationCompletion(args.emittedToolNames);
|
|
883
|
+
if (!after.ok) {
|
|
884
|
+
// eslint-disable-next-line no-console
|
|
885
|
+
console.warn(`[council] ${args.agent.id} still missing verify after retry: ${after.reason}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Build the scoped fix instruction for the chairman from deterministic motion
|
|
890
|
+
* violations. Groups by file, lists file:line + rule, and constrains the model
|
|
891
|
+
* to fix ONLY these (no new features, no rewrites). Exported for testing.
|
|
892
|
+
*/
|
|
893
|
+
export function buildMotionFixPrompt(violations) {
|
|
894
|
+
const byFile = new Map();
|
|
895
|
+
for (const v of violations) {
|
|
896
|
+
const file = v.file || 'index.html';
|
|
897
|
+
const loc = v.line ? `${file}:L${v.line}` : file;
|
|
898
|
+
const list = byFile.get(file) ?? [];
|
|
899
|
+
list.push(` - ${loc}: ${v.message}`);
|
|
900
|
+
byFile.set(file, list);
|
|
901
|
+
}
|
|
902
|
+
const blocks = Array.from(byFile.values()).map((lines) => lines.join('\n')).join('\n');
|
|
903
|
+
return (`Deterministic verification found ${violations.length} motion violation(s) in the file(s) you just edited. ` +
|
|
904
|
+
`Fix ONLY these — do not add features, do not rewrite sections, do not touch anything else:\n${blocks}\n\n` +
|
|
905
|
+
`Rules: animate ONLY transform and opacity. Replace any box-shadow / background / background-position / ` +
|
|
906
|
+
`filter / color / border-color / width / height / grid-template-rows used in @keyframes or transitions with ` +
|
|
907
|
+
`transform/opacity equivalents (e.g. render a glow via a pseudo-element that scales and fades). For every ` +
|
|
908
|
+
`classList.add('x') in the script, add a matching '.x' CSS rule. Use read_file to see the exact lines, then ` +
|
|
909
|
+
`edit_file. When the listed items are fixed, stop — no summary.`);
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Forced retry when Lucifero finished without a successful write_file/edit_file.
|
|
913
|
+
*/
|
|
914
|
+
export async function* applyImplementationWriteRetry(args) {
|
|
915
|
+
if (args.check.ok)
|
|
916
|
+
return;
|
|
917
|
+
if (!shouldRetryMember(['write_file'], 0))
|
|
918
|
+
return;
|
|
919
|
+
const statusMsg = `[council] ${args.chairman.id} implementation write retry: ${args.check.missing.join(', ')}`;
|
|
920
|
+
args.onCouncilStatus?.(statusMsg);
|
|
921
|
+
// eslint-disable-next-line no-console
|
|
922
|
+
console.warn(statusMsg);
|
|
923
|
+
try {
|
|
924
|
+
const retryGenerator = runRetryTurnForMember({
|
|
925
|
+
agent: args.chairman,
|
|
926
|
+
missingToolNames: ['read_file', 'write_file', 'edit_file'],
|
|
927
|
+
minPerTool: { read_file: 3, edit_file: 8, write_file: 1 },
|
|
928
|
+
executableTools: args.executableNames,
|
|
929
|
+
userMessage: args.userMessage,
|
|
930
|
+
ragContext: args.config.ragContext,
|
|
931
|
+
workspaceContext: args.config.workspaceContext,
|
|
932
|
+
priorOutputs: args.agentOutputs,
|
|
933
|
+
aiConfig: args.config.aiConfig,
|
|
934
|
+
sessionId: args.sessionId,
|
|
935
|
+
effectiveModel: args.effectiveModel,
|
|
936
|
+
effectiveProvider: args.effectiveProvider,
|
|
937
|
+
eventBus: args.config.eventBus,
|
|
938
|
+
toolRegistry: args.config.tools,
|
|
939
|
+
providerStream: args.config.providerStream,
|
|
940
|
+
runMode: 'implementation',
|
|
941
|
+
retryPrompt: buildImplementationWriteRetryPrompt(args.userMessage),
|
|
942
|
+
});
|
|
943
|
+
for await (const event of retryGenerator) {
|
|
944
|
+
if (event.type === 'tool_execution_start')
|
|
945
|
+
args.onToolCall?.();
|
|
946
|
+
if (event.type === 'tool_execution_end' &&
|
|
947
|
+
!event.isError &&
|
|
948
|
+
typeof event.result === 'string') {
|
|
949
|
+
try {
|
|
950
|
+
const parsed = JSON.parse(event.result);
|
|
951
|
+
if ((parsed.bytesWritten ?? 0) > 0 ||
|
|
952
|
+
(parsed.occurrencesReplaced ?? 0) > 0) {
|
|
953
|
+
args.onSuccessfulWrite?.();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
catch {
|
|
957
|
+
if (event.result.includes('bytesWritten') || event.result.includes('occurrencesReplaced')) {
|
|
958
|
+
args.onSuccessfulWrite?.();
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
yield event;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
catch (retryErr) {
|
|
966
|
+
// eslint-disable-next-line no-console
|
|
967
|
+
console.error(`[council] ${args.chairman.id} implementation write retry failed:`, retryErr);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/** Max verify-driven delivery passes after the chairman turn (inline-js, motion, etc.). */
|
|
971
|
+
export const MAX_DELIVERY_ATTEMPTS = 2;
|
|
972
|
+
/**
|
|
973
|
+
* Increment 5 — verify-driven delivery loop. Re-runs deterministic verification
|
|
974
|
+
* and forces scoped chairman fix turns until blocking technical issues clear
|
|
975
|
+
* or the attempt cap is hit.
|
|
976
|
+
*/
|
|
977
|
+
export async function* runChairmanDeliveryLoop(args) {
|
|
978
|
+
const maxAttempts = args.maxAttempts ?? MAX_DELIVERY_ATTEMPTS;
|
|
979
|
+
const zelariRoot = `${args.projectRoot}/.zelari`;
|
|
980
|
+
let attempt = 0;
|
|
981
|
+
while (attempt < maxAttempts) {
|
|
982
|
+
const report = runImplementationVerification({
|
|
983
|
+
projectRoot: args.projectRoot,
|
|
984
|
+
zelariRoot,
|
|
985
|
+
});
|
|
986
|
+
const blocking = filterDeliveryBlockingFails(report.results);
|
|
987
|
+
if (blocking.length === 0)
|
|
988
|
+
return true;
|
|
989
|
+
if (blocking.some((b) => b.id === 'inline-js.budget')) {
|
|
990
|
+
const jsFix = applyInlineJsAutofix(args.projectRoot, report);
|
|
991
|
+
if (jsFix.applied) {
|
|
992
|
+
args.onCouncilStatus?.(`[council] ${args.chairman.id} inline-js autofix: ${jsFix.fixes.join('; ')}`);
|
|
993
|
+
const afterJs = runImplementationVerification({
|
|
994
|
+
projectRoot: args.projectRoot,
|
|
995
|
+
zelariRoot,
|
|
996
|
+
});
|
|
997
|
+
if (filterDeliveryBlockingFails(afterJs.results).length === 0)
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
attempt++;
|
|
1002
|
+
const statusMsg = `[council] ${args.chairman.id} delivery pass ${attempt}/${maxAttempts}: ${blocking.map((b) => b.id).join(', ')}`;
|
|
1003
|
+
args.onCouncilStatus?.(statusMsg);
|
|
1004
|
+
// eslint-disable-next-line no-console
|
|
1005
|
+
console.warn(statusMsg);
|
|
1006
|
+
try {
|
|
1007
|
+
const fixGenerator = runRetryTurnForMember({
|
|
1008
|
+
agent: args.chairman,
|
|
1009
|
+
missingToolNames: ['read_file', 'edit_file'],
|
|
1010
|
+
minPerTool: { read_file: 3, edit_file: 10 },
|
|
1011
|
+
executableTools: args.executableNames,
|
|
1012
|
+
userMessage: args.userMessage,
|
|
1013
|
+
ragContext: args.config.ragContext,
|
|
1014
|
+
workspaceContext: args.config.workspaceContext,
|
|
1015
|
+
priorOutputs: args.agentOutputs,
|
|
1016
|
+
aiConfig: args.config.aiConfig,
|
|
1017
|
+
sessionId: args.sessionId,
|
|
1018
|
+
effectiveModel: args.effectiveModel,
|
|
1019
|
+
effectiveProvider: args.effectiveProvider,
|
|
1020
|
+
eventBus: args.config.eventBus,
|
|
1021
|
+
toolRegistry: args.config.tools,
|
|
1022
|
+
providerStream: args.config.providerStream,
|
|
1023
|
+
runMode: 'implementation',
|
|
1024
|
+
retryPrompt: buildDeliveryFixPrompt(blocking, args.userMessage),
|
|
1025
|
+
});
|
|
1026
|
+
for await (const event of fixGenerator) {
|
|
1027
|
+
if (event.type === 'tool_execution_start')
|
|
1028
|
+
args.onToolCall?.();
|
|
1029
|
+
yield event;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
catch (deliveryErr) {
|
|
1033
|
+
// eslint-disable-next-line no-console
|
|
1034
|
+
console.error(`[council] ${args.chairman.id} delivery pass ${attempt} failed:`, deliveryErr);
|
|
1035
|
+
break;
|
|
1036
|
+
}
|
|
1037
|
+
for (const rel of args.changedFiles) {
|
|
1038
|
+
for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath: rel, zelariRoot })) {
|
|
1039
|
+
// refresh changed set — delivery may touch same targets
|
|
1040
|
+
args.changedFiles.add(w.file ?? rel);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
const finalReport = runImplementationVerification({
|
|
1045
|
+
projectRoot: args.projectRoot,
|
|
1046
|
+
zelariRoot,
|
|
1047
|
+
});
|
|
1048
|
+
return filterDeliveryBlockingFails(finalReport.results).length === 0;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Re-execute edit_file/write_file calls from a `---TOOLS---` block after the
|
|
1052
|
+
* chairman turn. Safety net when the harness path parsed the block but edits
|
|
1053
|
+
* failed (oldString drift) or the block was not fully executed.
|
|
1054
|
+
*/
|
|
1055
|
+
export async function* replayChairmanTextTools(args) {
|
|
1056
|
+
const tools = parseTextToolCalls(args.synthesisText);
|
|
1057
|
+
if (tools.length === 0)
|
|
1058
|
+
return 0;
|
|
1059
|
+
let applied = 0;
|
|
1060
|
+
for (const tt of tools) {
|
|
1061
|
+
if (tt.name !== 'edit_file' && tt.name !== 'write_file')
|
|
1062
|
+
continue;
|
|
1063
|
+
const normalized = normalizeTextToolArgs(tt.name, tt.args);
|
|
1064
|
+
const toolCallId = `replay-${crypto.randomUUID().slice(0, 8)}`;
|
|
1065
|
+
yield createBrainEvent('tool_execution_start', args.sessionId, {
|
|
1066
|
+
toolCallId,
|
|
1067
|
+
toolName: tt.name,
|
|
1068
|
+
args: normalized,
|
|
1069
|
+
...(args.memberId ? { memberId: args.memberId } : {}),
|
|
1070
|
+
});
|
|
1071
|
+
const startMs = Date.now();
|
|
1072
|
+
let resultStr = '';
|
|
1073
|
+
let isError = false;
|
|
1074
|
+
try {
|
|
1075
|
+
const result = await args.toolRegistry.invoke(tt.name, normalized, {
|
|
1076
|
+
cwd: args.projectRoot,
|
|
1077
|
+
sessionId: args.sessionId,
|
|
1078
|
+
});
|
|
1079
|
+
if (result.ok) {
|
|
1080
|
+
const val = result.value;
|
|
1081
|
+
if (tt.name === 'edit_file' && val.occurrencesReplaced === 0) {
|
|
1082
|
+
resultStr = `edit_file: no match for oldString (replay)`;
|
|
1083
|
+
isError = true;
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
resultStr =
|
|
1087
|
+
typeof result.value === 'string'
|
|
1088
|
+
? result.value
|
|
1089
|
+
: JSON.stringify(result.value, null, 2);
|
|
1090
|
+
applied += 1;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
else {
|
|
1094
|
+
resultStr = result.error;
|
|
1095
|
+
isError = true;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
catch (err) {
|
|
1099
|
+
resultStr = err instanceof Error ? err.message : String(err);
|
|
1100
|
+
isError = true;
|
|
1101
|
+
}
|
|
1102
|
+
yield createBrainEvent('tool_execution_end', args.sessionId, {
|
|
1103
|
+
toolCallId,
|
|
1104
|
+
result: resultStr,
|
|
1105
|
+
isError,
|
|
1106
|
+
durationMs: Date.now() - startMs,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
return applied;
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Increment 4 — bounded deterministic fix loop for the chairman. When the
|
|
1113
|
+
* micro-gate flagged motion violations in Lucifero's writes, force a scoped
|
|
1114
|
+
* fix turn (read_file + edit_file only), re-scan the changed files, and repeat
|
|
1115
|
+
* until clean or the attempt cap. Emits only the fix turn's own events; the
|
|
1116
|
+
* post-council verification reports the final PASS/FAIL to the user.
|
|
1117
|
+
*/
|
|
1118
|
+
export async function* runChairmanFixLoop(args) {
|
|
1119
|
+
const maxAttempts = args.maxAttempts ?? 3;
|
|
1120
|
+
const zelariRoot = `${args.projectRoot}/.zelari`;
|
|
1121
|
+
let current = Array.from(args.violations.values());
|
|
1122
|
+
let attempt = 0;
|
|
1123
|
+
while (current.length > 0 && attempt < maxAttempts) {
|
|
1124
|
+
attempt++;
|
|
1125
|
+
try {
|
|
1126
|
+
const fixGenerator = runRetryTurnForMember({
|
|
1127
|
+
agent: args.chairman,
|
|
1128
|
+
missingToolNames: ['read_file', 'edit_file'],
|
|
1129
|
+
minPerTool: { read_file: 2, edit_file: 8 },
|
|
1130
|
+
executableTools: args.executableNames,
|
|
1131
|
+
userMessage: args.userMessage,
|
|
1132
|
+
ragContext: args.config.ragContext,
|
|
1133
|
+
workspaceContext: args.config.workspaceContext,
|
|
1134
|
+
priorOutputs: args.agentOutputs,
|
|
1135
|
+
aiConfig: args.config.aiConfig,
|
|
1136
|
+
sessionId: args.sessionId,
|
|
1137
|
+
effectiveModel: args.effectiveModel,
|
|
1138
|
+
effectiveProvider: args.effectiveProvider,
|
|
1139
|
+
eventBus: args.config.eventBus,
|
|
1140
|
+
toolRegistry: args.config.tools,
|
|
1141
|
+
providerStream: args.config.providerStream,
|
|
1142
|
+
runMode: 'implementation',
|
|
1143
|
+
retryPrompt: buildMotionFixPrompt(current),
|
|
1144
|
+
});
|
|
1145
|
+
for await (const event of fixGenerator) {
|
|
1146
|
+
if (event.type === 'tool_execution_start')
|
|
1147
|
+
args.onToolCall?.();
|
|
1148
|
+
yield event;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
catch (fixErr) {
|
|
1152
|
+
// eslint-disable-next-line no-console
|
|
1153
|
+
console.error(`[council] chairman fix pass ${attempt} failed:`, fixErr);
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
// Re-scan the changed target files for the next iteration / termination.
|
|
1157
|
+
const rescanned = new Map();
|
|
1158
|
+
for (const relPath of args.changedFiles) {
|
|
1159
|
+
for (const w of runChairmanMicroGate({ projectRoot: args.projectRoot, relPath, zelariRoot })) {
|
|
1160
|
+
rescanned.set(`${w.id}|${w.file}|${w.line ?? ''}`, w);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
current = Array.from(rescanned.values());
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
656
1166
|
/**
|
|
657
1167
|
* Pure helper: given the list of tool names a member emitted during its
|
|
658
1168
|
* turn, and a list of requirements, return whether all requirements are
|
|
@@ -843,7 +1353,10 @@ export async function* runRetryTurnForMember(args) {
|
|
|
843
1353
|
const baseMessages = buildAgentMessages(args.agent, args.userMessage, args.ragContext, args.workspaceContext, args.priorOutputs, args.aiConfig, args.executableTools, args.runMode ?? 'implementation');
|
|
844
1354
|
const retryMessages = [
|
|
845
1355
|
...baseMessages,
|
|
846
|
-
{
|
|
1356
|
+
{
|
|
1357
|
+
role: 'user',
|
|
1358
|
+
content: args.retryPrompt ?? buildRetryPrompt(executableMissing),
|
|
1359
|
+
},
|
|
847
1360
|
];
|
|
848
1361
|
// Budget the retry turn so the model can satisfy ALL minimums in a
|
|
849
1362
|
// single tool_calls turn. For createDocument min:1 this is 1; for
|