zelari-code 1.18.0 → 1.19.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.
- package/dist/cli/browser/driver.js +128 -2
- package/dist/cli/browser/driver.js.map +1 -1
- package/dist/cli/browser/tools.js +60 -11
- package/dist/cli/browser/tools.js.map +1 -1
- package/dist/cli/buildPolicy.js +62 -0
- package/dist/cli/buildPolicy.js.map +1 -0
- package/dist/cli/hooks/useChatTurn.js +131 -16
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/main.bundled.js +882 -138
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/missionSlice.js +295 -0
- package/dist/cli/missionSlice.js.map +1 -0
- package/dist/cli/mode.js +3 -3
- package/dist/cli/mode.js.map +1 -1
- package/dist/cli/runHeadless.js +200 -113
- package/dist/cli/runHeadless.js.map +1 -1
- package/dist/cli/zelariMission.js +18 -4
- package/dist/cli/zelariMission.js.map +1 -1
- package/package.json +2 -2
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* The "visual verification loop" for web projects: after the agent edits a UI,
|
|
5
5
|
* it navigates the running app and gets back the signals an LLM can actually
|
|
6
6
|
* act on — console errors, uncaught page exceptions, failed network requests,
|
|
7
|
-
* the final title/URL, whether an expected selector is present,
|
|
8
|
-
* screenshot path. Far stronger than "the tests pass"
|
|
7
|
+
* the final title/URL, whether an expected selector is present, evaluate
|
|
8
|
+
* results, and a saved screenshot path. Far stronger than "the tests pass"
|
|
9
|
+
* for front-end work.
|
|
9
10
|
*
|
|
10
11
|
* Playwright is an OPTIONAL dependency, loaded via dynamic import so it is not
|
|
11
12
|
* a hard requirement of the package. When it (or a browser) isn't available,
|
|
@@ -19,6 +20,12 @@
|
|
|
19
20
|
import { createRequire } from 'node:module';
|
|
20
21
|
import path from 'node:path';
|
|
21
22
|
import { pathToFileURL } from 'node:url';
|
|
23
|
+
/** Max length of an evaluate expression (chars). */
|
|
24
|
+
const MAX_EVAL_EXPR = 4_000;
|
|
25
|
+
/** Max JSON-serialized evaluate result size. */
|
|
26
|
+
const MAX_EVAL_RESULT = 8_000;
|
|
27
|
+
/** Max body text sample returned to the agent. */
|
|
28
|
+
const MAX_BODY_SNIPPET = 2_000;
|
|
22
29
|
/** Coerce a dynamic-import module shape into PlaywrightLike (or null). */
|
|
23
30
|
function asPlaywright(mod) {
|
|
24
31
|
if (!mod || typeof mod !== 'object')
|
|
@@ -74,6 +81,50 @@ export async function loadPlaywright(cwd) {
|
|
|
74
81
|
* Prefer `loadPlaywright(explicitCwd)` at call sites that know the workspace.
|
|
75
82
|
*/
|
|
76
83
|
export const defaultPlaywrightLoader = async () => loadPlaywright(process.cwd());
|
|
84
|
+
/** Cap and JSON-safe-clone evaluate return values for the agent. */
|
|
85
|
+
export function serializeEvaluateValue(value) {
|
|
86
|
+
try {
|
|
87
|
+
const json = JSON.stringify(value, (_k, v) => {
|
|
88
|
+
if (typeof v === 'function')
|
|
89
|
+
return '[Function]';
|
|
90
|
+
if (typeof v === 'bigint')
|
|
91
|
+
return v.toString();
|
|
92
|
+
if (v instanceof Error)
|
|
93
|
+
return { name: v.name, message: v.message };
|
|
94
|
+
return v;
|
|
95
|
+
});
|
|
96
|
+
if (json === undefined)
|
|
97
|
+
return null;
|
|
98
|
+
if (json.length > MAX_EVAL_RESULT) {
|
|
99
|
+
return {
|
|
100
|
+
truncated: true,
|
|
101
|
+
preview: json.slice(0, MAX_EVAL_RESULT),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return JSON.parse(json);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
return {
|
|
108
|
+
error: `non-serializable: ${err instanceof Error ? err.message : String(err)}`,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Wrap a user expression as a Playwright evaluate **function body**.
|
|
114
|
+
* Playwright treats string pageFunctions as `function(){ <body> }`, so we
|
|
115
|
+
* must `return` the value.
|
|
116
|
+
*/
|
|
117
|
+
export function wrapEvaluateExpression(expression) {
|
|
118
|
+
const expr = expression.trim();
|
|
119
|
+
if (!expr)
|
|
120
|
+
return 'return undefined';
|
|
121
|
+
// Multi-statement or already has return → use as body (ensure return if missing).
|
|
122
|
+
if (/\breturn\b/.test(expr) || expr.includes('\n')) {
|
|
123
|
+
return expr;
|
|
124
|
+
}
|
|
125
|
+
// Single expression
|
|
126
|
+
return `return (${expr})`;
|
|
127
|
+
}
|
|
77
128
|
/**
|
|
78
129
|
* Navigate to a URL (optionally running a sequence of actions) and collect
|
|
79
130
|
* verification signals. Best-effort — never throws; a missing browser or a
|
|
@@ -83,6 +134,7 @@ export async function runBrowserCheck(options, loader) {
|
|
|
83
134
|
const consoleErrors = [];
|
|
84
135
|
const pageErrors = [];
|
|
85
136
|
const failedRequests = [];
|
|
137
|
+
const evaluateResults = [];
|
|
86
138
|
const base = { ok: false, consoleErrors, pageErrors, failedRequests };
|
|
87
139
|
const resolve = loader ??
|
|
88
140
|
(() => loadPlaywright(options.cwd ?? process.cwd()));
|
|
@@ -99,6 +151,7 @@ export async function runBrowserCheck(options, loader) {
|
|
|
99
151
|
}
|
|
100
152
|
const timeout = options.timeoutMs ?? 15_000;
|
|
101
153
|
let browser;
|
|
154
|
+
let textFound;
|
|
102
155
|
try {
|
|
103
156
|
browser = await pw.chromium.launch({ headless: true });
|
|
104
157
|
const page = await browser.newPage();
|
|
@@ -126,6 +179,61 @@ export async function runBrowserCheck(options, loader) {
|
|
|
126
179
|
case 'wait':
|
|
127
180
|
await page.waitForTimeout(action.ms);
|
|
128
181
|
break;
|
|
182
|
+
case 'press': {
|
|
183
|
+
const key = action.key.trim();
|
|
184
|
+
if (!key)
|
|
185
|
+
break;
|
|
186
|
+
if (page.keyboard && typeof page.keyboard.press === 'function') {
|
|
187
|
+
await page.keyboard.press(key, { timeout });
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Fallback for fakes / surfaces without keyboard
|
|
191
|
+
await page.evaluate(wrapEvaluateExpression(`document.dispatchEvent(new KeyboardEvent('keydown',{key:${JSON.stringify(key)},bubbles:true})); true`));
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'waitForText': {
|
|
196
|
+
const needle = action.text;
|
|
197
|
+
const t = action.timeoutMs ?? timeout;
|
|
198
|
+
try {
|
|
199
|
+
// String function body: Playwright injects as function(arg) { ... }
|
|
200
|
+
await page.waitForFunction(`return (document.body && (document.body.innerText || document.body.textContent || '')).includes(arguments[0])`, needle, { timeout: t });
|
|
201
|
+
textFound = textFound === false ? false : true;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
textFound = false;
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'evaluate': {
|
|
209
|
+
const raw = action.expression ?? '';
|
|
210
|
+
if (raw.length > MAX_EVAL_EXPR) {
|
|
211
|
+
evaluateResults.push({
|
|
212
|
+
expression: raw.slice(0, 120) + '…',
|
|
213
|
+
error: `expression too long (max ${MAX_EVAL_EXPR} chars)`,
|
|
214
|
+
});
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
if (!raw.trim()) {
|
|
218
|
+
evaluateResults.push({ expression: raw, error: 'empty expression' });
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const wrapped = wrapEvaluateExpression(raw);
|
|
223
|
+
const value = await page.evaluate(wrapped);
|
|
224
|
+
evaluateResults.push({
|
|
225
|
+
expression: raw.length > 200 ? raw.slice(0, 200) + '…' : raw,
|
|
226
|
+
value: serializeEvaluateValue(value),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
evaluateResults.push({
|
|
231
|
+
expression: raw.length > 200 ? raw.slice(0, 200) + '…' : raw,
|
|
232
|
+
error: err instanceof Error ? err.message : String(err),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
129
237
|
default:
|
|
130
238
|
break;
|
|
131
239
|
}
|
|
@@ -140,6 +248,21 @@ export async function runBrowserCheck(options, loader) {
|
|
|
140
248
|
selectorFound = false;
|
|
141
249
|
}
|
|
142
250
|
}
|
|
251
|
+
let bodyTextSnippet;
|
|
252
|
+
if (options.textSample) {
|
|
253
|
+
try {
|
|
254
|
+
const text = await page.evaluate(wrapEvaluateExpression(`(document.body?.innerText || document.body?.textContent || '').trim()`));
|
|
255
|
+
if (typeof text === 'string' && text.length > 0) {
|
|
256
|
+
bodyTextSnippet =
|
|
257
|
+
text.length > MAX_BODY_SNIPPET
|
|
258
|
+
? text.slice(0, MAX_BODY_SNIPPET) + '…'
|
|
259
|
+
: text;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// ignore
|
|
264
|
+
}
|
|
265
|
+
}
|
|
143
266
|
let screenshotPath;
|
|
144
267
|
if (options.screenshotPath) {
|
|
145
268
|
try {
|
|
@@ -159,6 +282,9 @@ export async function runBrowserCheck(options, loader) {
|
|
|
159
282
|
url: page.url(),
|
|
160
283
|
...(title !== undefined ? { title } : {}),
|
|
161
284
|
...(selectorFound !== undefined ? { selectorFound } : {}),
|
|
285
|
+
...(evaluateResults.length > 0 ? { evaluateResults } : {}),
|
|
286
|
+
...(textFound !== undefined ? { textFound } : {}),
|
|
287
|
+
...(bodyTextSnippet !== undefined ? { bodyTextSnippet } : {}),
|
|
162
288
|
...(screenshotPath ? { screenshotPath } : {}),
|
|
163
289
|
};
|
|
164
290
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/cli/browser/driver.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/cli/browser/driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* browser/tools — the `browser_check` visual-verification tool.
|
|
3
3
|
*
|
|
4
4
|
* Lets the agent confirm a web change actually works in a real browser:
|
|
5
|
-
* navigate the running app, optionally click/fill, and get back
|
|
6
|
-
* errors, uncaught exceptions, failed requests, the title/URL, whether
|
|
7
|
-
* expected element appeared, and a screenshot path.
|
|
8
|
-
* degrades with install instructions when
|
|
5
|
+
* navigate the running app, optionally click/fill/evaluate/press, and get back
|
|
6
|
+
* console errors, uncaught exceptions, failed requests, the title/URL, whether
|
|
7
|
+
* an expected element/text appeared, evaluate results, and a screenshot path.
|
|
8
|
+
* Optional Playwright dep — degrades with install instructions when absent.
|
|
9
9
|
*/
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import os from 'node:os';
|
|
@@ -17,24 +17,54 @@ const ActionSchema = z.discriminatedUnion('type', [
|
|
|
17
17
|
z.object({ type: z.literal('fill'), selector: z.string().min(1), value: z.string() }),
|
|
18
18
|
z.object({ type: z.literal('wait'), ms: z.number().int().positive().max(30_000) }),
|
|
19
19
|
z.object({ type: z.literal('goto'), url: z.string().min(1) }),
|
|
20
|
+
z.object({
|
|
21
|
+
type: z.literal('evaluate'),
|
|
22
|
+
expression: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.max(4_000)
|
|
26
|
+
.describe('JS expression or statements run in the page (page.evaluate). Prefer DOM reads ' +
|
|
27
|
+
'(document.querySelector…), not assuming window.game exists. Return JSON-safe values.'),
|
|
28
|
+
}),
|
|
29
|
+
z.object({
|
|
30
|
+
type: z.literal('press'),
|
|
31
|
+
key: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe('Playwright key name, e.g. "Space", "KeyW", "ArrowLeft", "Enter".'),
|
|
35
|
+
}),
|
|
36
|
+
z.object({
|
|
37
|
+
type: z.literal('waitForText'),
|
|
38
|
+
text: z.string().min(1).describe('Substring that must appear in document body text.'),
|
|
39
|
+
timeoutMs: z.number().int().positive().max(60_000).optional(),
|
|
40
|
+
}),
|
|
20
41
|
]);
|
|
21
42
|
export function createBrowserTool(deps = {}) {
|
|
22
43
|
return {
|
|
23
44
|
name: 'browser_check',
|
|
24
45
|
description: 'Open a URL in a headless browser to VERIFY a web change: optionally run ' +
|
|
25
|
-
'click/fill/goto/wait actions, then report console errors,
|
|
26
|
-
'exceptions, failed network requests,
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'
|
|
46
|
+
'click/fill/goto/wait/press/waitForText/evaluate actions, then report console errors, ' +
|
|
47
|
+
'uncaught page exceptions, failed network requests, final title/URL, selector/text ' +
|
|
48
|
+
'presence, evaluate results, and a screenshot path. ' +
|
|
49
|
+
'Prefer DOM assertions (waitForSelector, waitForText, evaluate on document.querySelector) ' +
|
|
50
|
+
'over window.* globals — ES modules and const keep symbols off window. ' +
|
|
51
|
+
'Absence of console errors after a short wait is WEAK smoke only; assert UI state when ' +
|
|
52
|
+
'claiming a fix is verified. Requires Playwright (optional dependency).',
|
|
30
53
|
permissions: ['network'],
|
|
31
54
|
// A browser launch + navigation can take a while.
|
|
32
55
|
timeoutMs: 60_000,
|
|
33
56
|
inputSchema: z.object({
|
|
34
57
|
url: z.string().min(1).describe('URL to open (e.g. http://localhost:3000).'),
|
|
35
|
-
actions: z
|
|
58
|
+
actions: z
|
|
59
|
+
.array(ActionSchema)
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Optional sequence of interactions / probes before checking.'),
|
|
36
62
|
waitForSelector: z.string().optional().describe('Assert this CSS selector is present after actions.'),
|
|
37
63
|
screenshot: z.boolean().optional().describe('Save a screenshot (default true).'),
|
|
64
|
+
textSample: z
|
|
65
|
+
.boolean()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Include a short body.innerText snippet (HUD / Game Over text).'),
|
|
38
68
|
}),
|
|
39
69
|
execute: async (args, ctx) => {
|
|
40
70
|
const a = args;
|
|
@@ -48,6 +78,7 @@ export function createBrowserTool(deps = {}) {
|
|
|
48
78
|
...(a.actions ? { actions: a.actions } : {}),
|
|
49
79
|
...(a.waitForSelector ? { waitForSelector: a.waitForSelector } : {}),
|
|
50
80
|
...(screenshotPath ? { screenshotPath } : {}),
|
|
81
|
+
...(a.textSample ? { textSample: true } : {}),
|
|
51
82
|
}, deps.loader);
|
|
52
83
|
if (!result.ok) {
|
|
53
84
|
return typedOk({ ok: false, note: result.error ?? 'browser check failed' });
|
|
@@ -56,7 +87,14 @@ export function createBrowserTool(deps = {}) {
|
|
|
56
87
|
const clean = result.consoleErrors.length === 0 &&
|
|
57
88
|
result.pageErrors.length === 0 &&
|
|
58
89
|
result.failedRequests.length === 0 &&
|
|
59
|
-
result.selectorFound !== false
|
|
90
|
+
result.selectorFound !== false &&
|
|
91
|
+
result.textFound !== false;
|
|
92
|
+
const hadDomAssertion = result.selectorFound !== undefined ||
|
|
93
|
+
result.textFound !== undefined ||
|
|
94
|
+
(result.evaluateResults !== undefined && result.evaluateResults.length > 0) ||
|
|
95
|
+
!!result.bodyTextSnippet;
|
|
96
|
+
// Only waits / navigate / screenshot with no errors → weak smoke.
|
|
97
|
+
const onlyWeakSmoke = clean && !hadDomAssertion;
|
|
60
98
|
return typedOk({
|
|
61
99
|
ok: true,
|
|
62
100
|
clean,
|
|
@@ -66,7 +104,18 @@ export function createBrowserTool(deps = {}) {
|
|
|
66
104
|
pageErrors: result.pageErrors,
|
|
67
105
|
failedRequests: result.failedRequests,
|
|
68
106
|
...(result.selectorFound !== undefined ? { selectorFound: result.selectorFound } : {}),
|
|
107
|
+
...(result.evaluateResults ? { evaluateResults: result.evaluateResults } : {}),
|
|
108
|
+
...(result.textFound !== undefined ? { textFound: result.textFound } : {}),
|
|
109
|
+
...(result.bodyTextSnippet ? { bodyTextSnippet: result.bodyTextSnippet } : {}),
|
|
69
110
|
...(result.screenshotPath ? { screenshotPath: result.screenshotPath } : {}),
|
|
111
|
+
...(onlyWeakSmoke
|
|
112
|
+
? {
|
|
113
|
+
note: 'Weak smoke only: no selector/text/evaluate assertions. ' +
|
|
114
|
+
'No console/page errors is necessary but not sufficient to claim a logic fix. ' +
|
|
115
|
+
'Add waitForSelector, waitForText, or evaluate (DOM/read hooks) for stronger evidence.',
|
|
116
|
+
smokeStrength: 'weak',
|
|
117
|
+
}
|
|
118
|
+
: { smokeStrength: 'asserted' }),
|
|
70
119
|
});
|
|
71
120
|
},
|
|
72
121
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/cli/browser/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,OAAO,EAAuB,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,eAAe,EAA6C,MAAM,aAAa,CAAC;AAEzF,MAAM,YAAY,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAChD,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACrF,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;IAClF,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/cli/browser/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* buildPolicy — experiment: multi-agent plans, single-agent builds.
|
|
3
|
+
*
|
|
4
|
+
* On branch `experiment/plan-multiagent-build-agent`:
|
|
5
|
+
* - Zelari implementation slices default to the single-agent harness.
|
|
6
|
+
* - Council design-phase is unchanged (planning / artifacts in `.zelari/`).
|
|
7
|
+
* - Council+build is soft-gated unless ZELARI_COUNCIL_CAN_BUILD=1.
|
|
8
|
+
*
|
|
9
|
+
* Escape hatches (power users / A-B):
|
|
10
|
+
* ZELARI_BUILD_VIA_AGENT=0 → zelari impl uses full council again
|
|
11
|
+
* ZELARI_COUNCIL_CAN_BUILD=1 → allow Lucifero to implement (also forces
|
|
12
|
+
* zelari impl onto the council path)
|
|
13
|
+
*
|
|
14
|
+
* Pure module — unit-testable without LLM or TUI.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_AGENT_TOOL_BUDGET = 40;
|
|
17
|
+
/**
|
|
18
|
+
* When true, Zelari mission **implementation** slices run via single-agent
|
|
19
|
+
* harness instead of council (Lucifero). Design-phase always stays council.
|
|
20
|
+
*
|
|
21
|
+
* Experiment default: ON (opt-out with `ZELARI_BUILD_VIA_AGENT=0`).
|
|
22
|
+
* `ZELARI_COUNCIL_CAN_BUILD=1` wins and forces legacy council implementer.
|
|
23
|
+
*/
|
|
24
|
+
export function shouldBuildViaAgent(env = process.env) {
|
|
25
|
+
if (shouldAllowCouncilBuild(env))
|
|
26
|
+
return false;
|
|
27
|
+
// Explicit off
|
|
28
|
+
if (env.ZELARI_BUILD_VIA_AGENT === '0')
|
|
29
|
+
return false;
|
|
30
|
+
// Explicit on or unset (experiment default ON)
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* When true, council mode may run implementation (Lucifero writes project files).
|
|
35
|
+
* Default false on this experiment branch.
|
|
36
|
+
*/
|
|
37
|
+
export function shouldAllowCouncilBuild(env = process.env) {
|
|
38
|
+
return env.ZELARI_COUNCIL_CAN_BUILD === '1';
|
|
39
|
+
}
|
|
40
|
+
/** Tool-call budget for agent implementation slices inside a Zelari mission. */
|
|
41
|
+
export function resolveAgentMissionToolBudget(env = process.env) {
|
|
42
|
+
const raw = env.ZELARI_MODE_MAX_TOOLS_AGENT;
|
|
43
|
+
if (raw === undefined || raw === '')
|
|
44
|
+
return DEFAULT_AGENT_TOOL_BUDGET;
|
|
45
|
+
const n = Number.parseInt(raw, 10);
|
|
46
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_AGENT_TOOL_BUDGET;
|
|
47
|
+
}
|
|
48
|
+
/** Short human summary for logs / `/mode` feedback / system banners. */
|
|
49
|
+
export function describeBuildPolicy(env = process.env) {
|
|
50
|
+
const viaAgent = shouldBuildViaAgent(env);
|
|
51
|
+
const councilBuild = shouldAllowCouncilBuild(env);
|
|
52
|
+
const parts = [
|
|
53
|
+
viaAgent
|
|
54
|
+
? 'zelari build@agent (default)'
|
|
55
|
+
: 'zelari build@council (legacy)',
|
|
56
|
+
councilBuild
|
|
57
|
+
? 'council may implement'
|
|
58
|
+
: 'council plan-only unless ZELARI_COUNCIL_CAN_BUILD=1',
|
|
59
|
+
];
|
|
60
|
+
return parts.join(' · ');
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=buildPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildPolicy.js","sourceRoot":"","sources":["../../src/cli/buildPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAsB,OAAO,CAAC,GAAG;IAEjC,IAAI,uBAAuB,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,eAAe;IACf,IAAI,GAAG,CAAC,sBAAsB,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACrD,+CAA+C;IAC/C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAsB,OAAO,CAAC,GAAG;IAEjC,OAAO,GAAG,CAAC,wBAAwB,KAAK,GAAG,CAAC;AAC9C,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,6BAA6B,CAC3C,MAAsB,OAAO,CAAC,GAAG;IAEjC,MAAM,GAAG,GAAG,GAAG,CAAC,2BAA2B,CAAC;IAC5C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,yBAAyB,CAAC;IACtE,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC;AACrE,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,mBAAmB,CACjC,MAAsB,OAAO,CAAC,GAAG;IAEjC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,uBAAuB,CAAC,GAAG,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG;QACZ,QAAQ;YACN,CAAC,CAAC,8BAA8B;YAChC,CAAC,CAAC,+BAA+B;QACnC,YAAY;YACV,CAAC,CAAC,uBAAuB;YACzB,CAAC,CAAC,qDAAqD;KAC1D,CAAC;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC"}
|
|
@@ -813,8 +813,28 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
813
813
|
});
|
|
814
814
|
})
|
|
815
815
|
: undefined;
|
|
816
|
+
// Force council design-phase when UI phase is plan (and vice-versa for build).
|
|
817
|
+
// Experiment: free-form council+build is soft-gated to design-phase unless
|
|
818
|
+
// ZELARI_COUNCIL_CAN_BUILD=1 or the caller (zelari legacy path) opts in.
|
|
819
|
+
// Soft-gate also enables planMode tool registry so write_file/edit_file/bash
|
|
820
|
+
// cannot create product files while "planning".
|
|
821
|
+
let phaseRunMode = overrides.runMode ??
|
|
822
|
+
(workPhase === "plan" ? "design-phase" : "implementation");
|
|
823
|
+
let softGatedToDesign = false;
|
|
824
|
+
if (phaseRunMode === "implementation") {
|
|
825
|
+
const { shouldAllowCouncilBuild } = await import("../buildPolicy.js");
|
|
826
|
+
const allowed = overrides.allowCouncilBuild === true || shouldAllowCouncilBuild();
|
|
827
|
+
if (!allowed) {
|
|
828
|
+
phaseRunMode = "design-phase";
|
|
829
|
+
softGatedToDesign = true;
|
|
830
|
+
appendSystem(setMessages, "[council] build soft-gate: implementation disabled for free-form council " +
|
|
831
|
+
"(experiment: multi-agent = plan). Forced design-phase + plan tools. " +
|
|
832
|
+
"Set ZELARI_COUNCIL_CAN_BUILD=1 to let Lucifero implement, " +
|
|
833
|
+
"or use mode agent/zelari for on-disk work.", Date.now());
|
|
834
|
+
}
|
|
835
|
+
}
|
|
816
836
|
const { registry: councilToolRegistry } = createBuiltinToolRegistry({
|
|
817
|
-
planMode: workPhase === "plan",
|
|
837
|
+
planMode: workPhase === "plan" || softGatedToDesign,
|
|
818
838
|
onAskUser: onAskUserCouncil,
|
|
819
839
|
});
|
|
820
840
|
const workspaceCtx = createWorkspaceContext();
|
|
@@ -827,9 +847,6 @@ async function dispatchCouncilPromptImpl(text, deps, overrides = {}) {
|
|
|
827
847
|
// that are pure project-file mutators if ever added.
|
|
828
848
|
councilToolRegistry.register(td);
|
|
829
849
|
}
|
|
830
|
-
// Force council design-phase when UI phase is plan (and vice-versa for build).
|
|
831
|
-
const phaseRunMode = overrides.runMode ??
|
|
832
|
-
(workPhase === "plan" ? "design-phase" : "implementation");
|
|
833
850
|
// v0.7.5: MCP tools for the council too (same lazy singleton as the
|
|
834
851
|
// single-agent path — zero extra spawns).
|
|
835
852
|
try {
|
|
@@ -1359,25 +1376,123 @@ async function runZelariMissionInTui(userMessage, deps, emit) {
|
|
|
1359
1376
|
default: 30,
|
|
1360
1377
|
min: 1,
|
|
1361
1378
|
});
|
|
1379
|
+
const { shouldBuildViaAgent } = await import("../buildPolicy.js");
|
|
1380
|
+
const buildViaAgent = shouldBuildViaAgent();
|
|
1381
|
+
if (buildViaAgent) {
|
|
1382
|
+
emit("[zelari] policy: design@council · build@agent (ZELARI_BUILD_VIA_AGENT; set=0 for legacy council impl)");
|
|
1383
|
+
}
|
|
1362
1384
|
try {
|
|
1363
1385
|
await runZelariMission(userMessage, brief, {
|
|
1364
1386
|
projectRoot,
|
|
1365
1387
|
memory,
|
|
1366
1388
|
emit,
|
|
1389
|
+
buildViaAgent,
|
|
1367
1390
|
runSlice: async ({ userMessage: slicePrompt, runMode, ragContext, implementerRetry, }) => {
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1391
|
+
// Design always uses council. Implementation uses agent when policy says so.
|
|
1392
|
+
if (runMode === "design-phase" || !buildViaAgent) {
|
|
1393
|
+
const r = await dispatchCouncilPromptImpl(slicePrompt, deps, {
|
|
1394
|
+
ragContext,
|
|
1395
|
+
runMode,
|
|
1396
|
+
maxToolCallsChairman: chairmanBudget,
|
|
1397
|
+
...(implementerRetry ? { skipSpecialists: true } : {}),
|
|
1398
|
+
// Mission may need Lucifero to write even when free-form council is plan-only.
|
|
1399
|
+
allowCouncilBuild: true,
|
|
1400
|
+
});
|
|
1401
|
+
return {
|
|
1402
|
+
completionOk: r.completionOk,
|
|
1403
|
+
ran: r.ran,
|
|
1404
|
+
synthesisText: r.synthesisText,
|
|
1405
|
+
writeCount: r.writeCount,
|
|
1406
|
+
degraded: r.degraded,
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
// build@agent implementation slice
|
|
1410
|
+
const { runAgentMissionSlice } = await import("../missionSlice.js");
|
|
1411
|
+
// createBuiltinToolRegistry, openaiCompatibleProvider, providerFromEnv
|
|
1412
|
+
// already imported at module top.
|
|
1413
|
+
const { composeProjectContext } = await import("../workspace/composeContext.js");
|
|
1414
|
+
const { loadDurableContext } = await import("../state/loadDurableContext.js");
|
|
1415
|
+
const { createWorkspaceContext } = await import("../workspace/stubs.js");
|
|
1416
|
+
const { runPostCouncilHook } = await import("../workspace/postCouncilHook.js");
|
|
1417
|
+
const { detectDegradedRun } = await import("@zelari/core/council");
|
|
1418
|
+
const envConfig = await providerFromEnv();
|
|
1419
|
+
if (!envConfig) {
|
|
1420
|
+
emit("[zelari] build@agent aborted: missing API key for active provider");
|
|
1421
|
+
return { completionOk: false, ran: false };
|
|
1422
|
+
}
|
|
1423
|
+
const { registry: toolRegistry } = createBuiltinToolRegistry({
|
|
1424
|
+
planMode: false,
|
|
1373
1425
|
});
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
}
|
|
1426
|
+
try {
|
|
1427
|
+
const { registerMcpTools } = await import("../mcp/mcpManager.js");
|
|
1428
|
+
await registerMcpTools(toolRegistry, projectRoot);
|
|
1429
|
+
}
|
|
1430
|
+
catch {
|
|
1431
|
+
// best-effort MCP
|
|
1432
|
+
}
|
|
1433
|
+
const durableState = await loadDurableContext(projectRoot);
|
|
1434
|
+
const composed = composeProjectContext({
|
|
1435
|
+
mode: "zelari",
|
|
1436
|
+
cwd: projectRoot,
|
|
1437
|
+
userMessage: slicePrompt,
|
|
1438
|
+
memoryHits: ragContext,
|
|
1439
|
+
durableState: durableState || undefined,
|
|
1440
|
+
includeLessons: true,
|
|
1441
|
+
includeDurableState: false,
|
|
1442
|
+
});
|
|
1443
|
+
for (const w of composed.warnings) {
|
|
1444
|
+
emit(w);
|
|
1445
|
+
}
|
|
1446
|
+
const workspaceCtx = createWorkspaceContext(projectRoot);
|
|
1447
|
+
const { setMessages, writerRef, setBusy } = deps;
|
|
1448
|
+
setBusy(true);
|
|
1449
|
+
try {
|
|
1450
|
+
return await runAgentMissionSlice({
|
|
1451
|
+
projectRoot,
|
|
1452
|
+
model: envConfig.model,
|
|
1453
|
+
provider: "openai-compatible",
|
|
1454
|
+
providerStream: openaiCompatibleProvider(envConfig),
|
|
1455
|
+
toolRegistry,
|
|
1456
|
+
slicePrompt,
|
|
1457
|
+
ragContext: composed.ragContext ?? ragContext,
|
|
1458
|
+
workspaceContext: composed.workspaceContext,
|
|
1459
|
+
projectInstructions: composed.projectInstructions,
|
|
1460
|
+
emit,
|
|
1461
|
+
onEvent: async (event) => {
|
|
1462
|
+
if (writerRef.current)
|
|
1463
|
+
await writerRef.current.append(event);
|
|
1464
|
+
if (event.type === "tool_execution_start") {
|
|
1465
|
+
const name = event.toolName ?? "tool";
|
|
1466
|
+
appendSystem(setMessages, `[build@agent] → ${name}`, Date.now());
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
runCompletionHook: async ({ synthesisText, writeCount, errored }) => {
|
|
1470
|
+
const d = detectDegradedRun({
|
|
1471
|
+
chairmanErrored: errored,
|
|
1472
|
+
luciferWriteCount: writeCount,
|
|
1473
|
+
synthesisText,
|
|
1474
|
+
runMode: "implementation",
|
|
1475
|
+
});
|
|
1476
|
+
if (d.degraded) {
|
|
1477
|
+
appendSystem(setMessages, `[zelari] DEGRADED_RUN — ${d.reasons.join("; ")}`, Date.now());
|
|
1478
|
+
}
|
|
1479
|
+
const hook = await runPostCouncilHook(workspaceCtx, {
|
|
1480
|
+
runMode: "implementation",
|
|
1481
|
+
userMessage: userMessage,
|
|
1482
|
+
synthesisText: synthesisText || undefined,
|
|
1483
|
+
degradedRun: d.degraded,
|
|
1484
|
+
degradedReasons: d.reasons,
|
|
1485
|
+
});
|
|
1486
|
+
return {
|
|
1487
|
+
completionOk: hook.completion?.completion?.ok ?? false,
|
|
1488
|
+
degraded: d.degraded,
|
|
1489
|
+
};
|
|
1490
|
+
},
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
finally {
|
|
1494
|
+
setBusy(false);
|
|
1495
|
+
}
|
|
1381
1496
|
},
|
|
1382
1497
|
});
|
|
1383
1498
|
}
|