zelari-code 1.13.0 → 1.14.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/dist/cli/headless.js +29 -2
- package/dist/cli/headless.js.map +1 -1
- package/dist/cli/hooks/conversationContext.js +94 -17
- package/dist/cli/hooks/conversationContext.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +12 -3
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/main.bundled.js +283 -91
- package/dist/cli/main.bundled.js.map +3 -3
- package/dist/cli/runHeadless.js +223 -100
- package/dist/cli/runHeadless.js.map +1 -1
- package/package.json +2 -2
package/dist/cli/main.bundled.js
CHANGED
|
@@ -18345,7 +18345,9 @@ Earlier members' outputs are shared context. Build on them; do not re-derive or
|
|
|
18345
18345
|
|
|
18346
18346
|
- Be concise and structured. Prefer short markdown sections and bullets over walls of text.
|
|
18347
18347
|
- Be proactive but not reckless: if a single missing fact would change the design, ask one focused question; otherwise make a documented assumption and proceed.
|
|
18348
|
-
- Prefer action over description when the user wants code, fixes, or repo changes \u2014 use tools.
|
|
18348
|
+
- Prefer action over description when the user wants code, fixes, or repo changes \u2014 use tools (write_file/edit_file/bash), not prose-only plans.
|
|
18349
|
+
- When the user confirms a plan ("procedi", "s\xEC", "implementa"), the prior plan is work TO DO on disk. Reading alone is incomplete.
|
|
18350
|
+
- Never claim work is "already implemented" without verifying the real files (and writing if gaps remain).
|
|
18349
18351
|
- Think step by step internally; surface conclusions and a brief rationale, not a full chain of thought.`
|
|
18350
18352
|
};
|
|
18351
18353
|
BEHAVIOR_COUNCIL = {
|
|
@@ -18416,11 +18418,12 @@ Earlier members' outputs are shared context. Build on them; do not re-derive or
|
|
|
18416
18418
|
content: `# Coding Practices
|
|
18417
18419
|
|
|
18418
18420
|
- **Read before edit**: open relevant files (and nearby callers/tests) before changing code.
|
|
18421
|
+
- **Then write**: if the task is to implement, follow reads with write_file/edit_file in the same turn. Do not end after exploration.
|
|
18419
18422
|
- **Minimal diffs**: change only what the task requires; match existing style and patterns.
|
|
18420
18423
|
- **Don't invent**: no fake APIs, deps, or config keys \u2014 discover from the tree or package manifests.
|
|
18421
18424
|
- **Verify**: after non-trivial edits, run the project's tests/typecheck/build when available; fix failures you introduced.
|
|
18422
18425
|
- **Use project scripts**: prefer package.json / Makefile / existing tooling over ad-hoc commands.
|
|
18423
|
-
- **Finish**:
|
|
18426
|
+
- **Finish**: list the paths you wrote/edited and how to verify. If you wrote nothing, say so honestly \u2014 do not invent a done report.`
|
|
18424
18427
|
};
|
|
18425
18428
|
CLARIFICATION_PROTOCOL_MODULE = {
|
|
18426
18429
|
type: "custom",
|
|
@@ -26988,14 +26991,18 @@ var conversationContext_exports = {};
|
|
|
26988
26991
|
__export(conversationContext_exports, {
|
|
26989
26992
|
_resetConversationContextForTests: () => _resetConversationContextForTests,
|
|
26990
26993
|
appendMessages: () => appendMessages,
|
|
26994
|
+
buildAgentUserWithHistory: () => buildAgentUserWithHistory,
|
|
26995
|
+
buildContinueUserMessage: () => buildContinueUserMessage,
|
|
26991
26996
|
buildCouncilTaskWithHistory: () => buildCouncilTaskWithHistory,
|
|
26992
26997
|
clearHistory: () => clearHistory,
|
|
26993
26998
|
compactInPlace: () => compactInPlace,
|
|
26999
|
+
expectsDiskImplementation: () => expectsDiskImplementation,
|
|
26994
27000
|
formatHistoryForCouncil: () => formatHistoryForCouncil,
|
|
26995
27001
|
formatHistoryMessages: () => formatHistoryMessages,
|
|
26996
27002
|
getHistory: () => getHistory,
|
|
26997
27003
|
getLastClarification: () => getLastClarification,
|
|
26998
27004
|
hydrateHistory: () => hydrateHistory,
|
|
27005
|
+
isShortContinueReply: () => isShortContinueReply,
|
|
26999
27006
|
maybeAnchorShortAnswer: () => maybeAnchorShortAnswer,
|
|
27000
27007
|
serializeHistory: () => serializeHistory,
|
|
27001
27008
|
setHistory: () => setHistory,
|
|
@@ -27078,22 +27085,67 @@ ${body.slice(body.length - maxTotalChars)}`;
|
|
|
27078
27085
|
}
|
|
27079
27086
|
return body;
|
|
27080
27087
|
}
|
|
27081
|
-
function
|
|
27082
|
-
const messages = prior ?? [];
|
|
27088
|
+
function isShortContinueReply(task) {
|
|
27083
27089
|
const trimmed = task.trim();
|
|
27084
|
-
|
|
27085
|
-
if (
|
|
27086
|
-
|
|
27087
|
-
|
|
27088
|
-
|
|
27090
|
+
if (!trimmed) return false;
|
|
27091
|
+
if (SHORT_CONTINUE.test(trimmed)) return true;
|
|
27092
|
+
if (trimmed.length <= 80 && !trimmed.includes("\n") && SHORT_CONTINUE_LOOSE.test(trimmed)) {
|
|
27093
|
+
return true;
|
|
27094
|
+
}
|
|
27095
|
+
return false;
|
|
27096
|
+
}
|
|
27097
|
+
function buildContinueUserMessage(task, prior, opts) {
|
|
27098
|
+
if (!isShortContinueReply(task) || prior.length === 0) return null;
|
|
27099
|
+
const lastAsst = [...prior].reverse().find((m) => m.role === "assistant" && (m.content ?? "").trim());
|
|
27100
|
+
if (!lastAsst) return null;
|
|
27101
|
+
const max = opts?.maxPriorChars ?? 8e3;
|
|
27102
|
+
const trimmed = task.trim();
|
|
27103
|
+
return `The user says "${trimmed}" \u2014 this is a CONTINUATION of an existing multi-turn session (the Desktop phase may have switched plan\u2194build; mode may have changed). This is NOT a new conversation and you DO have prior context below.
|
|
27104
|
+
|
|
27105
|
+
## CRITICAL \u2014 plan text \u2260 done on disk
|
|
27106
|
+
The prior assistant output is a PLAN / SPEC / PROPOSAL (or analysis). It is NOT proof that project files already contain those changes.
|
|
27107
|
+
The user CONFIRMED the plan and wants you to IMPLEMENT it ON DISK NOW.
|
|
27108
|
+
- You MUST use write_file and/or edit_file (and bash when needed) to apply every planned change.
|
|
27109
|
+
- Reading files alone is incomplete. Do not stop after read_file/list_files/grep.
|
|
27110
|
+
- Do NOT claim "already implemented" / "tutto fatto" unless you verified the changes exist on disk in THIS turn (read after your own successful writes).
|
|
27111
|
+
- Do NOT restart from zero or re-ask for the overall goal.
|
|
27089
27112
|
|
|
27090
|
-
## Prior assistant output (authoritative
|
|
27091
|
-
${truncate(lastAsst.content,
|
|
27113
|
+
## Prior assistant output (plan to implement \u2014 authoritative)
|
|
27114
|
+
${truncate(lastAsst.content, max)}
|
|
27092
27115
|
|
|
27093
27116
|
## Instruction
|
|
27094
|
-
|
|
27117
|
+
Implement the plan on disk now with mutating tools, then briefly list the files you wrote/edited.`;
|
|
27118
|
+
}
|
|
27119
|
+
function expectsDiskImplementation(task, phase2, prior) {
|
|
27120
|
+
if ((phase2 ?? "build") === "plan") return false;
|
|
27121
|
+
const trimmed = task.trim();
|
|
27122
|
+
if (!trimmed) return false;
|
|
27123
|
+
if (isShortContinueReply(trimmed)) return true;
|
|
27124
|
+
if (/\b(implement|implementa|scrivi|scriviamo|applica|modifica|fix|write|edit|crea|aggiungi|aggiorna|apply|patch)\b/i.test(
|
|
27125
|
+
trimmed
|
|
27126
|
+
)) {
|
|
27127
|
+
return true;
|
|
27128
|
+
}
|
|
27129
|
+
if (prior && prior.length > 0 && isShortContinueReply(trimmed)) {
|
|
27130
|
+
const lastAsst = [...prior].reverse().find((m) => m.role === "assistant" && (m.content ?? "").trim());
|
|
27131
|
+
if (lastAsst && /\b(se confermi|passo alla|scriv|implement|on disk|write_file|modifiche proposte|riepilogo delle modifiche)\b/i.test(
|
|
27132
|
+
lastAsst.content
|
|
27133
|
+
)) {
|
|
27134
|
+
return true;
|
|
27095
27135
|
}
|
|
27096
27136
|
}
|
|
27137
|
+
return false;
|
|
27138
|
+
}
|
|
27139
|
+
function buildCouncilTaskWithHistory(task, prior) {
|
|
27140
|
+
const messages = prior ?? [];
|
|
27141
|
+
const trimmed = task.trim();
|
|
27142
|
+
let userPart = maybeAnchorShortAnswer(task) ?? task;
|
|
27143
|
+
const continued = buildContinueUserMessage(trimmed, messages, {
|
|
27144
|
+
maxPriorChars: 4500
|
|
27145
|
+
});
|
|
27146
|
+
if (continued) {
|
|
27147
|
+
userPart = continued;
|
|
27148
|
+
}
|
|
27097
27149
|
const block = formatHistoryMessages(messages, 6, 12e3);
|
|
27098
27150
|
if (!block) return userPart;
|
|
27099
27151
|
if (userPart.includes("Prior assistant output")) {
|
|
@@ -27104,6 +27156,12 @@ Proceed with the next concrete steps implied by that context (implementation whe
|
|
|
27104
27156
|
## Current user request
|
|
27105
27157
|
${userPart}`;
|
|
27106
27158
|
}
|
|
27159
|
+
function buildAgentUserWithHistory(task, prior) {
|
|
27160
|
+
const messages = prior ?? [];
|
|
27161
|
+
const anchored = maybeAnchorShortAnswer(task);
|
|
27162
|
+
if (anchored) return anchored;
|
|
27163
|
+
return buildContinueUserMessage(task, messages, { maxPriorChars: 8e3 }) ?? task;
|
|
27164
|
+
}
|
|
27107
27165
|
function truncate(s, max) {
|
|
27108
27166
|
const t = s.replace(/\s+/g, " ").trim();
|
|
27109
27167
|
if (t.length <= max) return t;
|
|
@@ -27113,14 +27171,15 @@ function _resetConversationContextForTests() {
|
|
|
27113
27171
|
history = [];
|
|
27114
27172
|
lastClarification = null;
|
|
27115
27173
|
}
|
|
27116
|
-
var history, lastClarification, SHORT_CONTINUE;
|
|
27174
|
+
var history, lastClarification, SHORT_CONTINUE, SHORT_CONTINUE_LOOSE;
|
|
27117
27175
|
var init_conversationContext = __esm({
|
|
27118
27176
|
"src/cli/hooks/conversationContext.ts"() {
|
|
27119
27177
|
"use strict";
|
|
27120
27178
|
init_historyCompaction();
|
|
27121
27179
|
history = [];
|
|
27122
27180
|
lastClarification = null;
|
|
27123
|
-
SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed)$/i;
|
|
27181
|
+
SHORT_CONTINUE = /^(procedi|continua|continue|go\s*ahead|go|ok|okay|sì|si|yes|vai|avanti|next|proceed|conferma|confermo|applica|fai|scrivi|esegui|implementa|vai pure|fai pure|ok procedi|sì procedi|si procedi)$/i;
|
|
27182
|
+
SHORT_CONTINUE_LOOSE = /\b(procedi|continua|continue|conferma|confermo|applica|implementa|scriv[ia]|esegui|vai pure|fai pure|go ahead|proceed)\b/i;
|
|
27124
27183
|
}
|
|
27125
27184
|
});
|
|
27126
27185
|
|
|
@@ -35293,11 +35352,16 @@ function useChatTurn(params) {
|
|
|
35293
35352
|
"- write_file / edit_file / bash / apply_diff are unavailable.",
|
|
35294
35353
|
"- Produce a clear plan, ask clarifying questions (---QUESTION---), use workspace plan tools when relevant.",
|
|
35295
35354
|
"- When the plan is ready, tell the user to run /build to implement."
|
|
35296
|
-
].join("\n") : workPhase === "build"
|
|
35355
|
+
].join("\n") : workPhase === "build" ? [
|
|
35297
35356
|
"",
|
|
35298
35357
|
"# Work Phase: BUILD",
|
|
35299
|
-
"Implement
|
|
35300
|
-
|
|
35358
|
+
"Implement on disk. Prefer acting over describing.",
|
|
35359
|
+
"- Prior plan/synthesis text is a SPEC to apply \u2014 not proof files already changed.",
|
|
35360
|
+
"- You MUST use write_file/edit_file for every file you change before claiming done.",
|
|
35361
|
+
"- After read_file: if the planned change is missing, WRITE it \u2014 do not stop at analysis.",
|
|
35362
|
+
"- Never claim already-implemented based only on reading a plan or skimming code.",
|
|
35363
|
+
planSummary ? "- An approved plan exists in the workspace \u2014 implement it and update task statuses as you go." : ""
|
|
35364
|
+
].filter(Boolean).join("\n") : "";
|
|
35301
35365
|
const shellContextBlock = [
|
|
35302
35366
|
"# Platform & Shell",
|
|
35303
35367
|
`platform: ${process.platform}`,
|
|
@@ -39471,7 +39535,21 @@ function parseHeadlessFlags(argv) {
|
|
|
39471
39535
|
const parsedHist = JSON.parse(raw);
|
|
39472
39536
|
if (Array.isArray(parsedHist)) {
|
|
39473
39537
|
history2 = parsedHist.filter(
|
|
39474
|
-
(m) => m && typeof m === "object" && typeof m.role === "string"
|
|
39538
|
+
(m) => !!m && typeof m === "object" && typeof m.role === "string"
|
|
39539
|
+
).map((m) => {
|
|
39540
|
+
const role = String(m.role);
|
|
39541
|
+
const raw2 = m.content;
|
|
39542
|
+
const content = typeof raw2 === "string" ? raw2 : raw2 == null ? "" : typeof raw2 === "object" ? JSON.stringify(raw2) : String(raw2);
|
|
39543
|
+
const msg = {
|
|
39544
|
+
role,
|
|
39545
|
+
content
|
|
39546
|
+
};
|
|
39547
|
+
if (typeof m.toolCallId === "string") {
|
|
39548
|
+
msg.toolCallId = m.toolCallId;
|
|
39549
|
+
}
|
|
39550
|
+
return msg;
|
|
39551
|
+
}).filter(
|
|
39552
|
+
(m) => m.role === "user" || m.role === "assistant" || m.role === "tool" || m.role === "system"
|
|
39475
39553
|
);
|
|
39476
39554
|
}
|
|
39477
39555
|
} catch {
|
|
@@ -39535,6 +39613,7 @@ function emitEvent(event) {
|
|
|
39535
39613
|
// src/cli/runHeadless.ts
|
|
39536
39614
|
init_harness();
|
|
39537
39615
|
init_conversationContext();
|
|
39616
|
+
init_council();
|
|
39538
39617
|
init_toolRegistry();
|
|
39539
39618
|
init_skills2();
|
|
39540
39619
|
init_envNumber();
|
|
@@ -39708,7 +39787,19 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
39708
39787
|
"The shell is NON-INTERACTIVE (stdin closed): pass non-interactive flags (--yes, --force, --template).",
|
|
39709
39788
|
"",
|
|
39710
39789
|
`# Work phase: ${opts.phase ?? "build"}`,
|
|
39711
|
-
(opts.phase ?? "build") === "plan" ?
|
|
39790
|
+
(opts.phase ?? "build") === "plan" ? [
|
|
39791
|
+
"PLAN phase: explore and design only.",
|
|
39792
|
+
"Do not write project source files (write_file/edit_file/bash blocked).",
|
|
39793
|
+
"Plan artifacts under .zelari are allowed.",
|
|
39794
|
+
"When the plan is ready, tell the user to switch to BUILD to implement on disk."
|
|
39795
|
+
].join(" ") : [
|
|
39796
|
+
"BUILD phase \u2014 IMPLEMENT ON DISK (mandatory when the user wants code/file changes).",
|
|
39797
|
+
"Prior chat may contain a plan or synthesis: that text is a SPEC to apply, NOT proof that files already changed.",
|
|
39798
|
+
"You MUST call write_file and/or edit_file for every file you change before saying you are done.",
|
|
39799
|
+
"After read_file: if the planned change is missing, WRITE it \u2014 do not stop at analysis.",
|
|
39800
|
+
'Never claim "already implemented" / "tutto fatto" based only on reading a plan or skimming code.',
|
|
39801
|
+
"Only claim done after successful mutating tool calls in THIS turn (or after proving the exact planned diff already exists on disk via read_file of the real files)."
|
|
39802
|
+
].join(" ")
|
|
39712
39803
|
].join("\n")
|
|
39713
39804
|
};
|
|
39714
39805
|
const { loadProjectInstructions: loadProjectInstructions2 } = await Promise.resolve().then(() => (init_projectInstructions(), projectInstructions_exports));
|
|
@@ -39753,95 +39844,196 @@ async function runHeadlessSingle(opts, provider, model, providerStream) {
|
|
|
39753
39844
|
languageDirectiveContent
|
|
39754
39845
|
].join("\n");
|
|
39755
39846
|
}
|
|
39756
|
-
const historySeed = (opts.history ?? []).map(
|
|
39757
|
-
(m) => m.role === "assistant" && m.content ? {
|
|
39758
|
-
|
|
39759
|
-
|
|
39760
|
-
|
|
39761
|
-
|
|
39762
|
-
|
|
39763
|
-
|
|
39764
|
-
|
|
39765
|
-
|
|
39766
|
-
|
|
39767
|
-
|
|
39768
|
-
|
|
39769
|
-
|
|
39770
|
-
|
|
39771
|
-
|
|
39772
|
-
|
|
39773
|
-
|
|
39774
|
-
|
|
39775
|
-
|
|
39776
|
-
|
|
39777
|
-
|
|
39778
|
-
|
|
39779
|
-
|
|
39780
|
-
|
|
39781
|
-
|
|
39782
|
-
|
|
39783
|
-
|
|
39784
|
-
|
|
39785
|
-
|
|
39786
|
-
|
|
39787
|
-
|
|
39788
|
-
|
|
39789
|
-
|
|
39790
|
-
|
|
39791
|
-
|
|
39792
|
-
}
|
|
39793
|
-
} else if (opts.output === "plain") {
|
|
39794
|
-
if (cleanDelta.length > 0) process.stdout.write(cleanDelta);
|
|
39795
|
-
} else {
|
|
39796
|
-
if (cleanDelta.length > 0) textBuffer.push(cleanDelta);
|
|
39847
|
+
const historySeed = (opts.history ?? []).filter((m) => m.role === "user" || m.role === "assistant").map(
|
|
39848
|
+
(m) => m.role === "assistant" && m.content ? {
|
|
39849
|
+
role: "assistant",
|
|
39850
|
+
content: cleanAgentContent(m.content, { stripQuestion: false })
|
|
39851
|
+
} : { role: m.role, content: m.content ?? "" }
|
|
39852
|
+
).filter((m) => (m.content ?? "").trim().length > 0);
|
|
39853
|
+
const effectiveTask = buildAgentUserWithHistory(opts.task, historySeed);
|
|
39854
|
+
const maxToolLoop = (() => {
|
|
39855
|
+
const n = envNumber(process.env.ZELARI_MAX_TOOL_LOOP_ITERATIONS, {
|
|
39856
|
+
default: 30,
|
|
39857
|
+
min: 1
|
|
39858
|
+
});
|
|
39859
|
+
return n;
|
|
39860
|
+
})();
|
|
39861
|
+
async function runSinglePass(messages, passSessionId) {
|
|
39862
|
+
const harness = new AgentHarness({
|
|
39863
|
+
model,
|
|
39864
|
+
provider,
|
|
39865
|
+
sessionId: passSessionId,
|
|
39866
|
+
messages,
|
|
39867
|
+
tools,
|
|
39868
|
+
toolRegistry,
|
|
39869
|
+
providerStream,
|
|
39870
|
+
maxToolLoopIterations: maxToolLoop
|
|
39871
|
+
});
|
|
39872
|
+
let finalReason = "completed";
|
|
39873
|
+
let exitCode = 0;
|
|
39874
|
+
const textBuffer = [];
|
|
39875
|
+
let successfulWrites = 0;
|
|
39876
|
+
let emittedWrites = 0;
|
|
39877
|
+
const pendingToolNames = /* @__PURE__ */ new Map();
|
|
39878
|
+
const scrub = createStreamScrubber();
|
|
39879
|
+
try {
|
|
39880
|
+
for await (const event of harness.run()) {
|
|
39881
|
+
if (event.type === "message_start") {
|
|
39882
|
+
scrub.reset();
|
|
39797
39883
|
}
|
|
39798
|
-
|
|
39799
|
-
|
|
39800
|
-
|
|
39884
|
+
if (event.type === "tool_execution_start") {
|
|
39885
|
+
const name = event.toolName ?? "";
|
|
39886
|
+
const id = event.toolCallId ?? "";
|
|
39887
|
+
if (id && name) pendingToolNames.set(id, name);
|
|
39888
|
+
if (name === "write_file" || name === "edit_file" || name === "apply_diff") {
|
|
39889
|
+
emittedWrites += 1;
|
|
39890
|
+
}
|
|
39891
|
+
}
|
|
39892
|
+
if (event.type === "tool_execution_end") {
|
|
39893
|
+
const id = event.toolCallId ?? "";
|
|
39894
|
+
const name = pendingToolNames.get(id) ?? "";
|
|
39895
|
+
pendingToolNames.delete(id);
|
|
39896
|
+
const isError = !!event.isError;
|
|
39897
|
+
const result = String(event.result ?? "");
|
|
39898
|
+
if ((name === "write_file" || name === "edit_file" || name === "apply_diff") && !isError) {
|
|
39899
|
+
const zeroEdit = name === "edit_file" && /occurrencesReplaced["']?\s*[:=]\s*0\b|0 occurrence|no changes/i.test(
|
|
39900
|
+
result
|
|
39901
|
+
);
|
|
39902
|
+
if (!zeroEdit) successfulWrites += 1;
|
|
39903
|
+
}
|
|
39801
39904
|
}
|
|
39802
|
-
if (event.type === "
|
|
39803
|
-
const
|
|
39804
|
-
if (
|
|
39805
|
-
if (
|
|
39806
|
-
|
|
39905
|
+
if (event.type === "message_delta" && typeof event.delta === "string") {
|
|
39906
|
+
const cleanDelta = scrub.push(event.delta);
|
|
39907
|
+
if (opts.output === "json") {
|
|
39908
|
+
if (cleanDelta.length > 0) {
|
|
39909
|
+
emitEvent({ ...event, delta: cleanDelta });
|
|
39910
|
+
}
|
|
39911
|
+
} else if (opts.output === "plain") {
|
|
39912
|
+
if (cleanDelta.length > 0) process.stdout.write(cleanDelta);
|
|
39913
|
+
} else {
|
|
39914
|
+
if (cleanDelta.length > 0) textBuffer.push(cleanDelta);
|
|
39807
39915
|
}
|
|
39808
|
-
|
|
39809
|
-
if (
|
|
39810
|
-
|
|
39811
|
-
|
|
39812
|
-
|
|
39916
|
+
} else {
|
|
39917
|
+
if (opts.output === "json") {
|
|
39918
|
+
emitEvent(event);
|
|
39919
|
+
}
|
|
39920
|
+
if (event.type === "agent_end") {
|
|
39921
|
+
const tail = scrub.flush();
|
|
39922
|
+
if (tail.length > 0) {
|
|
39923
|
+
if (opts.output === "plain") process.stdout.write(tail);
|
|
39924
|
+
else textBuffer.push(tail);
|
|
39925
|
+
}
|
|
39926
|
+
finalReason = event.reason;
|
|
39927
|
+
if (event.reason === "error") exitCode = 3;
|
|
39928
|
+
} else if (event.type === "error") {
|
|
39929
|
+
if (event.severity === "fatal") {
|
|
39930
|
+
exitCode = 2;
|
|
39931
|
+
}
|
|
39813
39932
|
}
|
|
39814
39933
|
}
|
|
39815
39934
|
}
|
|
39935
|
+
} catch (err) {
|
|
39936
|
+
process.stderr.write(
|
|
39937
|
+
`[zelari-code --headless] runtime error: ${err instanceof Error ? err.message : String(err)}
|
|
39938
|
+
`
|
|
39939
|
+
);
|
|
39940
|
+
return {
|
|
39941
|
+
finalReason: "error",
|
|
39942
|
+
exitCode: 2,
|
|
39943
|
+
textBuffer,
|
|
39944
|
+
successfulWrites,
|
|
39945
|
+
emittedWrites,
|
|
39946
|
+
messages: harness.getMessages()
|
|
39947
|
+
};
|
|
39816
39948
|
}
|
|
39817
|
-
|
|
39818
|
-
|
|
39819
|
-
|
|
39949
|
+
return {
|
|
39950
|
+
finalReason,
|
|
39951
|
+
exitCode,
|
|
39952
|
+
textBuffer,
|
|
39953
|
+
successfulWrites,
|
|
39954
|
+
emittedWrites,
|
|
39955
|
+
messages: harness.getMessages()
|
|
39956
|
+
};
|
|
39957
|
+
}
|
|
39958
|
+
const initialMessages = [
|
|
39959
|
+
{ role: "system", content: systemPrompt },
|
|
39960
|
+
...historySeed,
|
|
39961
|
+
{ role: "user", content: effectiveTask }
|
|
39962
|
+
];
|
|
39963
|
+
let pass = await runSinglePass(initialMessages, sessionId);
|
|
39964
|
+
const wantWrites = expectsDiskImplementation(
|
|
39965
|
+
opts.task,
|
|
39966
|
+
opts.phase,
|
|
39967
|
+
historySeed
|
|
39968
|
+
);
|
|
39969
|
+
if (wantWrites && pass.successfulWrites === 0 && pass.finalReason === "completed" && pass.exitCode === 0) {
|
|
39970
|
+
const retryPrompt = buildImplementationWriteRetryPrompt(opts.task);
|
|
39971
|
+
if (opts.output === "json") {
|
|
39972
|
+
emitEvent({
|
|
39973
|
+
type: "log",
|
|
39974
|
+
message: "[headless] BUILD: no successful write_file/edit_file \u2014 forcing implementation retry"
|
|
39975
|
+
});
|
|
39976
|
+
} else {
|
|
39977
|
+
process.stderr.write(
|
|
39978
|
+
`[zelari-code --headless] BUILD: no successful writes \u2014 forcing implementation retry
|
|
39820
39979
|
`
|
|
39821
|
-
|
|
39822
|
-
|
|
39980
|
+
);
|
|
39981
|
+
}
|
|
39982
|
+
const retryMessages = [
|
|
39983
|
+
...pass.messages.filter((m) => m.role !== "system")
|
|
39984
|
+
];
|
|
39985
|
+
const withSystem = [
|
|
39986
|
+
{ role: "system", content: systemPrompt },
|
|
39987
|
+
...retryMessages,
|
|
39988
|
+
{ role: "user", content: retryPrompt }
|
|
39989
|
+
];
|
|
39990
|
+
const retry = await runSinglePass(withSystem, `${sessionId}-write-retry`);
|
|
39991
|
+
pass = {
|
|
39992
|
+
...retry,
|
|
39993
|
+
textBuffer: [...pass.textBuffer, ...retry.textBuffer],
|
|
39994
|
+
successfulWrites: pass.successfulWrites + retry.successfulWrites,
|
|
39995
|
+
emittedWrites: pass.emittedWrites + retry.emittedWrites
|
|
39996
|
+
};
|
|
39823
39997
|
}
|
|
39824
|
-
if (opts.output === "plain" && textBuffer.length > 0) {
|
|
39825
|
-
process.stdout.write(textBuffer.join(""));
|
|
39998
|
+
if (opts.output === "plain" && pass.textBuffer.length > 0) {
|
|
39999
|
+
process.stdout.write(pass.textBuffer.join(""));
|
|
39826
40000
|
}
|
|
39827
40001
|
process.stdout.write("");
|
|
39828
|
-
if (finalReason !== "error" && opts.output === "json") {
|
|
40002
|
+
if (pass.finalReason !== "error" && opts.output === "json") {
|
|
39829
40003
|
try {
|
|
39830
|
-
const all =
|
|
39831
|
-
|
|
39832
|
-
|
|
39833
|
-
const
|
|
39834
|
-
|
|
39835
|
-
|
|
39836
|
-
|
|
39837
|
-
emitEvent({ type: "history_snapshot", messages: tail });
|
|
40004
|
+
const all = pass.messages;
|
|
40005
|
+
let lastAsst = "";
|
|
40006
|
+
for (let i = all.length - 1; i >= 0; i--) {
|
|
40007
|
+
const m = all[i];
|
|
40008
|
+
if (m?.role === "assistant" && (m.content ?? "").trim()) {
|
|
40009
|
+
lastAsst = cleanAgentContent(m.content, { stripQuestion: false });
|
|
40010
|
+
break;
|
|
39838
40011
|
}
|
|
39839
40012
|
}
|
|
40013
|
+
if (!lastAsst.trim() && pass.textBuffer.length > 0) {
|
|
40014
|
+
lastAsst = pass.textBuffer.join("").trim();
|
|
40015
|
+
}
|
|
40016
|
+
if (wantWrites && pass.successfulWrites === 0) {
|
|
40017
|
+
lastAsst = (lastAsst ? `${lastAsst}
|
|
40018
|
+
|
|
40019
|
+
` : "") + "[zelari] WARNING: BUILD turn ended with zero successful file writes. The planned changes may still need to be applied on disk.";
|
|
40020
|
+
emitEvent({
|
|
40021
|
+
type: "log",
|
|
40022
|
+
message: "[headless] BUILD warning: still zero successful writes after retry"
|
|
40023
|
+
});
|
|
40024
|
+
}
|
|
40025
|
+
const snapshot = [
|
|
40026
|
+
{ role: "user", content: opts.task },
|
|
40027
|
+
...lastAsst ? [{ role: "assistant", content: lastAsst }] : []
|
|
40028
|
+
];
|
|
40029
|
+
if (snapshot.length > 0) {
|
|
40030
|
+
emitEvent({ type: "history_snapshot", messages: snapshot });
|
|
40031
|
+
}
|
|
39840
40032
|
} catch {
|
|
39841
40033
|
}
|
|
39842
40034
|
}
|
|
39843
|
-
if (finalReason === "error") return 3;
|
|
39844
|
-
return exitCode;
|
|
40035
|
+
if (pass.finalReason === "error") return 3;
|
|
40036
|
+
return pass.exitCode;
|
|
39845
40037
|
}
|
|
39846
40038
|
async function buildCouncilToolRegistry(planMode, opts) {
|
|
39847
40039
|
const { registry: toolRegistry } = createBuiltinToolRegistry({ planMode });
|