open-agents-ai 0.11.3 → 0.11.5
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/index.js +1143 -42
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6587,7 +6587,8 @@ Commands run non-interactively (CI=true). When running scaffolding tools:
|
|
|
6587
6587
|
requestTimeoutMs: options?.requestTimeoutMs ?? 3e5,
|
|
6588
6588
|
taskTimeoutMs: options?.taskTimeoutMs ?? 12e5,
|
|
6589
6589
|
compactionThreshold: options?.compactionThreshold ?? 4e4,
|
|
6590
|
-
dynamicContext: options?.dynamicContext ?? ""
|
|
6590
|
+
dynamicContext: options?.dynamicContext ?? "",
|
|
6591
|
+
streamEnabled: options?.streamEnabled ?? false
|
|
6591
6592
|
};
|
|
6592
6593
|
}
|
|
6593
6594
|
/** Register a tool for the agent to use */
|
|
@@ -6683,13 +6684,14 @@ Integrate this guidance into your current approach. Continue working on the task
|
|
|
6683
6684
|
});
|
|
6684
6685
|
}
|
|
6685
6686
|
const compacted = this.compactMessages(messages);
|
|
6686
|
-
const
|
|
6687
|
+
const chatRequest = {
|
|
6687
6688
|
messages: compacted,
|
|
6688
6689
|
tools: toolDefs,
|
|
6689
6690
|
temperature: this.options.temperature,
|
|
6690
6691
|
maxTokens: this.options.maxTokens,
|
|
6691
6692
|
timeoutMs: this.options.requestTimeoutMs
|
|
6692
|
-
}
|
|
6693
|
+
};
|
|
6694
|
+
const response = this.options.streamEnabled && this.hasStreamingSupport() ? await this.streamingRequest(chatRequest, turn) : await this.backend.chatCompletion(chatRequest);
|
|
6693
6695
|
totalTokens += response.usage?.totalTokens ?? 0;
|
|
6694
6696
|
const choice = response.choices[0];
|
|
6695
6697
|
if (!choice)
|
|
@@ -6829,36 +6831,112 @@ ${marker}` : marker);
|
|
|
6829
6831
|
const middle = messages.slice(2, -keepRecent);
|
|
6830
6832
|
if (middle.length === 0)
|
|
6831
6833
|
return messages;
|
|
6832
|
-
|
|
6834
|
+
let previousSummary = "";
|
|
6835
|
+
const nonCompactionMiddle = [];
|
|
6836
|
+
for (const msg of middle) {
|
|
6837
|
+
if (msg.role === "system" && typeof msg.content === "string" && msg.content.startsWith("[Context compacted")) {
|
|
6838
|
+
previousSummary = msg.content.replace(/^\[Context compacted[^\]]*\]\s*/, "").replace(/\n\n\[Continue from[^\]]*\]\s*$/, "").trim();
|
|
6839
|
+
} else {
|
|
6840
|
+
nonCompactionMiddle.push(msg);
|
|
6841
|
+
}
|
|
6842
|
+
}
|
|
6843
|
+
const newSummary = this.summarizeCompactedMessages(nonCompactionMiddle);
|
|
6844
|
+
const combinedSummary = previousSummary ? this.progressiveSummarize(previousSummary, newSummary) : newSummary;
|
|
6833
6845
|
this.emit({
|
|
6834
6846
|
type: "compaction",
|
|
6835
|
-
content: `Compacted ${middle.length} messages`,
|
|
6847
|
+
content: `Compacted ${middle.length} messages${previousSummary ? " (progressive)" : ""}`,
|
|
6836
6848
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6837
6849
|
});
|
|
6838
6850
|
const compactionMsg = {
|
|
6839
6851
|
role: "system",
|
|
6840
6852
|
content: `[Context compacted \u2014 summary of earlier work]
|
|
6841
6853
|
|
|
6842
|
-
${
|
|
6854
|
+
${combinedSummary}
|
|
6843
6855
|
|
|
6844
6856
|
[Continue from the recent context below. Do not repeat work already completed above.]`
|
|
6845
6857
|
};
|
|
6846
6858
|
return [...head, compactionMsg, ...recent];
|
|
6847
6859
|
}
|
|
6848
6860
|
/**
|
|
6849
|
-
*
|
|
6850
|
-
*
|
|
6851
|
-
|
|
6852
|
-
|
|
6861
|
+
* Progressive summarization: merge an older compacted summary with a newer one.
|
|
6862
|
+
* When the combined text exceeds the budget, condense the older summary.
|
|
6863
|
+
*/
|
|
6864
|
+
progressiveSummarize(olderSummary, newerSummary) {
|
|
6865
|
+
const MAX_SUMMARY_CHARS = 4e3;
|
|
6866
|
+
const combined = `${olderSummary}
|
|
6867
|
+
|
|
6868
|
+
---
|
|
6869
|
+
|
|
6870
|
+
${newerSummary}`;
|
|
6871
|
+
if (combined.length <= MAX_SUMMARY_CHARS) {
|
|
6872
|
+
return combined;
|
|
6873
|
+
}
|
|
6874
|
+
const condensed = this.condenseSummary(olderSummary);
|
|
6875
|
+
const result = `${condensed}
|
|
6876
|
+
|
|
6877
|
+
---
|
|
6878
|
+
|
|
6879
|
+
${newerSummary}`;
|
|
6880
|
+
if (result.length > MAX_SUMMARY_CHARS) {
|
|
6881
|
+
const budget = MAX_SUMMARY_CHARS - newerSummary.length - 60;
|
|
6882
|
+
return budget > 200 ? `[Earlier work, condensed]
|
|
6883
|
+
${olderSummary.slice(0, budget)}...
|
|
6884
|
+
|
|
6885
|
+
---
|
|
6886
|
+
|
|
6887
|
+
${newerSummary}` : newerSummary;
|
|
6888
|
+
}
|
|
6889
|
+
return result;
|
|
6890
|
+
}
|
|
6891
|
+
/**
|
|
6892
|
+
* Condense a summary by keeping section headings and only first 2 items under each.
|
|
6893
|
+
*/
|
|
6894
|
+
condenseSummary(summary) {
|
|
6895
|
+
const lines = summary.split("\n");
|
|
6896
|
+
const condensed = [];
|
|
6897
|
+
let itemCount = 0;
|
|
6898
|
+
for (const line of lines) {
|
|
6899
|
+
if (line.startsWith("##") || line.startsWith("---") || line.trim() === "") {
|
|
6900
|
+
condensed.push(line);
|
|
6901
|
+
itemCount = 0;
|
|
6902
|
+
} else if (line.startsWith("- ") || line.startsWith(" - ")) {
|
|
6903
|
+
if (itemCount < 2) {
|
|
6904
|
+
condensed.push(line);
|
|
6905
|
+
} else if (itemCount === 2) {
|
|
6906
|
+
condensed.push(" - ...(condensed)");
|
|
6907
|
+
}
|
|
6908
|
+
itemCount++;
|
|
6909
|
+
} else {
|
|
6910
|
+
condensed.push(line);
|
|
6911
|
+
}
|
|
6912
|
+
}
|
|
6913
|
+
return condensed.join("\n");
|
|
6914
|
+
}
|
|
6915
|
+
/**
|
|
6916
|
+
* Extract a rich structured summary from compacted messages, preserving:
|
|
6917
|
+
* - Assistant analysis/reasoning text
|
|
6918
|
+
* - File contents examined (key snippets, not just names)
|
|
6919
|
+
* - Specific code changes (old_string → new_string)
|
|
6920
|
+
* - Shell command results (test pass/fail, build errors)
|
|
6921
|
+
* - Search findings (grep/find results)
|
|
6853
6922
|
* - Errors encountered
|
|
6854
6923
|
*/
|
|
6855
6924
|
summarizeCompactedMessages(messages) {
|
|
6856
|
-
const
|
|
6857
|
-
const
|
|
6858
|
-
const
|
|
6925
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
6926
|
+
const assistantAnalysis = [];
|
|
6927
|
+
const filesRead = /* @__PURE__ */ new Map();
|
|
6928
|
+
const filesModified = /* @__PURE__ */ new Map();
|
|
6929
|
+
const commandResults = [];
|
|
6930
|
+
const searchFindings = [];
|
|
6859
6931
|
const errors = [];
|
|
6860
6932
|
let toolCallCount = 0;
|
|
6861
6933
|
for (const msg of messages) {
|
|
6934
|
+
if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.trim()) {
|
|
6935
|
+
const text = msg.content.trim();
|
|
6936
|
+
if (text.length > 20) {
|
|
6937
|
+
assistantAnalysis.push(text.length > 400 ? text.slice(0, 400) + "..." : text);
|
|
6938
|
+
}
|
|
6939
|
+
}
|
|
6862
6940
|
if (msg.tool_calls) {
|
|
6863
6941
|
for (const tc of msg.tool_calls) {
|
|
6864
6942
|
toolCallCount++;
|
|
@@ -6869,37 +6947,134 @@ ${summary}
|
|
|
6869
6947
|
return {};
|
|
6870
6948
|
}
|
|
6871
6949
|
})();
|
|
6950
|
+
toolCallMap.set(tc.id, { name: tc.function.name, args });
|
|
6872
6951
|
const name = tc.function.name;
|
|
6873
|
-
if (name === "
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6952
|
+
if (name === "file_edit") {
|
|
6953
|
+
const path = String(args.path || "");
|
|
6954
|
+
const oldStr = String(args.old_string || "").slice(0, 100);
|
|
6955
|
+
const newStr = String(args.new_string || "").slice(0, 100);
|
|
6956
|
+
if (path) {
|
|
6957
|
+
const changes = filesModified.get(path) || [];
|
|
6958
|
+
changes.push(`"${oldStr}" \u2192 "${newStr}"`);
|
|
6959
|
+
filesModified.set(path, changes);
|
|
6960
|
+
}
|
|
6961
|
+
} else if (name === "file_write") {
|
|
6962
|
+
const path = String(args.path || "");
|
|
6963
|
+
if (path) {
|
|
6964
|
+
if (!filesModified.has(path))
|
|
6965
|
+
filesModified.set(path, []);
|
|
6966
|
+
filesModified.get(path).push("(full file write)");
|
|
6967
|
+
}
|
|
6968
|
+
} else if (name === "batch_edit") {
|
|
6969
|
+
const path = String(args.path || "");
|
|
6970
|
+
if (path) {
|
|
6971
|
+
if (!filesModified.has(path))
|
|
6972
|
+
filesModified.set(path, []);
|
|
6973
|
+
filesModified.get(path).push("(batch edit)");
|
|
6974
|
+
}
|
|
6881
6975
|
}
|
|
6882
6976
|
}
|
|
6883
6977
|
}
|
|
6884
|
-
if (msg.role === "tool" && typeof msg.content === "string") {
|
|
6885
|
-
|
|
6886
|
-
|
|
6978
|
+
if (msg.role === "tool" && typeof msg.content === "string" && msg.tool_call_id) {
|
|
6979
|
+
const tc = toolCallMap.get(msg.tool_call_id);
|
|
6980
|
+
const content = msg.content;
|
|
6981
|
+
if (!tc) {
|
|
6982
|
+
if (content.startsWith("Error:"))
|
|
6983
|
+
errors.push(content.slice(0, 200));
|
|
6984
|
+
continue;
|
|
6985
|
+
}
|
|
6986
|
+
switch (tc.name) {
|
|
6987
|
+
case "file_read": {
|
|
6988
|
+
const path = String(tc.args.path || "");
|
|
6989
|
+
const lines = content.split("\n");
|
|
6990
|
+
const summary = lines.length > 5 ? `${lines.length} lines \u2014 ${lines.slice(0, 2).join("; ").slice(0, 120)}...` : content.slice(0, 150);
|
|
6991
|
+
if (path)
|
|
6992
|
+
filesRead.set(path, summary);
|
|
6993
|
+
break;
|
|
6994
|
+
}
|
|
6995
|
+
case "shell":
|
|
6996
|
+
case "background_run": {
|
|
6997
|
+
const cmd = String(tc.args.command || "").slice(0, 100);
|
|
6998
|
+
const hasError = content.startsWith("Error:") || /FAIL|ERR!/i.test(content);
|
|
6999
|
+
const hasPass = /PASS|passed|✓|success/i.test(content);
|
|
7000
|
+
let outcome;
|
|
7001
|
+
if (hasError) {
|
|
7002
|
+
const errorLines = content.split("\n").filter((l) => /error|FAIL|✗|×|ERR!/i.test(l)).slice(0, 3);
|
|
7003
|
+
outcome = errorLines.length > 0 ? errorLines.join("; ").slice(0, 200) : content.slice(0, 200);
|
|
7004
|
+
errors.push(`\`${cmd}\`: ${outcome.slice(0, 150)}`);
|
|
7005
|
+
} else if (hasPass) {
|
|
7006
|
+
outcome = "passed";
|
|
7007
|
+
} else {
|
|
7008
|
+
outcome = content.slice(0, 150);
|
|
7009
|
+
}
|
|
7010
|
+
commandResults.push({ cmd, outcome });
|
|
7011
|
+
break;
|
|
7012
|
+
}
|
|
7013
|
+
case "grep_search": {
|
|
7014
|
+
const pattern = String(tc.args.pattern || "");
|
|
7015
|
+
const matchCount = (content.match(/\n/g) || []).length;
|
|
7016
|
+
searchFindings.push(`grep "${pattern}": ${matchCount} matches \u2014 ${content.slice(0, 150)}`);
|
|
7017
|
+
break;
|
|
7018
|
+
}
|
|
7019
|
+
case "find_files": {
|
|
7020
|
+
const pattern = String(tc.args.pattern || "");
|
|
7021
|
+
const files = content.split("\n").filter(Boolean);
|
|
7022
|
+
searchFindings.push(`find "${pattern}": ${files.length} files \u2014 ${files.slice(0, 5).join(", ")}`);
|
|
7023
|
+
break;
|
|
7024
|
+
}
|
|
7025
|
+
default: {
|
|
7026
|
+
if (content.startsWith("Error:"))
|
|
7027
|
+
errors.push(`${tc.name}: ${content.slice(0, 200)}`);
|
|
7028
|
+
}
|
|
6887
7029
|
}
|
|
6888
7030
|
}
|
|
6889
7031
|
}
|
|
6890
7032
|
const parts = [];
|
|
6891
|
-
parts.push(
|
|
7033
|
+
parts.push(`## Compacted Work Summary (${toolCallCount} tool calls)
|
|
7034
|
+
`);
|
|
7035
|
+
if (assistantAnalysis.length > 0) {
|
|
7036
|
+
parts.push("### Agent Analysis");
|
|
7037
|
+
for (const analysis of assistantAnalysis.slice(-5)) {
|
|
7038
|
+
parts.push(`- ${analysis}`);
|
|
7039
|
+
}
|
|
7040
|
+
parts.push("");
|
|
7041
|
+
}
|
|
6892
7042
|
if (filesRead.size > 0) {
|
|
6893
|
-
parts.push(
|
|
7043
|
+
parts.push("### Files Examined");
|
|
7044
|
+
for (const [path, summary] of Array.from(filesRead).slice(0, 15)) {
|
|
7045
|
+
parts.push(`- \`${path}\`: ${summary}`);
|
|
7046
|
+
}
|
|
7047
|
+
parts.push("");
|
|
6894
7048
|
}
|
|
6895
7049
|
if (filesModified.size > 0) {
|
|
6896
|
-
parts.push(
|
|
7050
|
+
parts.push("### Code Changes Made");
|
|
7051
|
+
for (const [path, changes] of Array.from(filesModified).slice(0, 10)) {
|
|
7052
|
+
parts.push(`- **${path}**:`);
|
|
7053
|
+
for (const change of changes.slice(0, 3)) {
|
|
7054
|
+
parts.push(` - ${change}`);
|
|
7055
|
+
}
|
|
7056
|
+
}
|
|
7057
|
+
parts.push("");
|
|
6897
7058
|
}
|
|
6898
|
-
if (
|
|
6899
|
-
parts.push(
|
|
7059
|
+
if (commandResults.length > 0) {
|
|
7060
|
+
parts.push("### Commands Executed");
|
|
7061
|
+
for (const { cmd, outcome } of commandResults.slice(0, 8)) {
|
|
7062
|
+
parts.push(`- \`${cmd}\` \u2192 ${outcome}`);
|
|
7063
|
+
}
|
|
7064
|
+
parts.push("");
|
|
7065
|
+
}
|
|
7066
|
+
if (searchFindings.length > 0) {
|
|
7067
|
+
parts.push("### Search Findings");
|
|
7068
|
+
for (const finding of searchFindings.slice(0, 5)) {
|
|
7069
|
+
parts.push(`- ${finding}`);
|
|
7070
|
+
}
|
|
7071
|
+
parts.push("");
|
|
6900
7072
|
}
|
|
6901
7073
|
if (errors.length > 0) {
|
|
6902
|
-
parts.push(
|
|
7074
|
+
parts.push("### Errors Encountered");
|
|
7075
|
+
for (const error of errors.slice(0, 5)) {
|
|
7076
|
+
parts.push(`- ${error}`);
|
|
7077
|
+
}
|
|
6903
7078
|
}
|
|
6904
7079
|
return parts.join("\n");
|
|
6905
7080
|
}
|
|
@@ -6916,6 +7091,90 @@ ${summary}
|
|
|
6916
7091
|
}
|
|
6917
7092
|
}));
|
|
6918
7093
|
}
|
|
7094
|
+
// -------------------------------------------------------------------------
|
|
7095
|
+
// Streaming support — parallel path that emits token events
|
|
7096
|
+
// -------------------------------------------------------------------------
|
|
7097
|
+
/** Check whether the backend supports SSE streaming */
|
|
7098
|
+
hasStreamingSupport() {
|
|
7099
|
+
return typeof this.backend.chatCompletionStream === "function";
|
|
7100
|
+
}
|
|
7101
|
+
/**
|
|
7102
|
+
* Streaming request: calls the SSE endpoint, emits stream events,
|
|
7103
|
+
* assembles and returns the same response format as chatCompletion().
|
|
7104
|
+
* The non-streaming chatCompletion path is NEVER touched by this code.
|
|
7105
|
+
*/
|
|
7106
|
+
async streamingRequest(request, turn) {
|
|
7107
|
+
const backend = this.backend;
|
|
7108
|
+
let content = "";
|
|
7109
|
+
let inThinkTag = false;
|
|
7110
|
+
const toolCallAccumulators = /* @__PURE__ */ new Map();
|
|
7111
|
+
this.emit({ type: "stream_start", turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7112
|
+
for await (const chunk of backend.chatCompletionStream(request)) {
|
|
7113
|
+
if (this.aborted)
|
|
7114
|
+
break;
|
|
7115
|
+
if (chunk.type === "content" && chunk.content) {
|
|
7116
|
+
content += chunk.content;
|
|
7117
|
+
let kind = inThinkTag ? "thinking" : "content";
|
|
7118
|
+
const fragment = chunk.content;
|
|
7119
|
+
if (fragment.includes("<think>")) {
|
|
7120
|
+
inThinkTag = true;
|
|
7121
|
+
kind = "thinking";
|
|
7122
|
+
}
|
|
7123
|
+
if (fragment.includes("</think>")) {
|
|
7124
|
+
inThinkTag = false;
|
|
7125
|
+
kind = "content";
|
|
7126
|
+
}
|
|
7127
|
+
this.emit({
|
|
7128
|
+
type: "stream_token",
|
|
7129
|
+
content: fragment,
|
|
7130
|
+
streamKind: kind,
|
|
7131
|
+
turn,
|
|
7132
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7133
|
+
});
|
|
7134
|
+
}
|
|
7135
|
+
if (chunk.type === "tool_call_delta") {
|
|
7136
|
+
const idx = chunk.toolCallIndex ?? 0;
|
|
7137
|
+
if (!toolCallAccumulators.has(idx)) {
|
|
7138
|
+
toolCallAccumulators.set(idx, {
|
|
7139
|
+
id: chunk.toolCallId ?? crypto.randomUUID(),
|
|
7140
|
+
name: chunk.toolCallName ?? "",
|
|
7141
|
+
args: ""
|
|
7142
|
+
});
|
|
7143
|
+
}
|
|
7144
|
+
const acc = toolCallAccumulators.get(idx);
|
|
7145
|
+
if (chunk.toolCallName)
|
|
7146
|
+
acc.name = chunk.toolCallName;
|
|
7147
|
+
if (chunk.toolCallId)
|
|
7148
|
+
acc.id = chunk.toolCallId;
|
|
7149
|
+
if (chunk.toolCallArgs) {
|
|
7150
|
+
acc.args += chunk.toolCallArgs;
|
|
7151
|
+
this.emit({
|
|
7152
|
+
type: "stream_token",
|
|
7153
|
+
content: chunk.toolCallArgs,
|
|
7154
|
+
streamKind: "tool_args",
|
|
7155
|
+
turn,
|
|
7156
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7157
|
+
});
|
|
7158
|
+
}
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
this.emit({ type: "stream_end", content, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7162
|
+
const cleanContent = content.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
7163
|
+
const toolCalls = toolCallAccumulators.size > 0 ? Array.from(toolCallAccumulators.values()).map((tc) => {
|
|
7164
|
+
let args;
|
|
7165
|
+
try {
|
|
7166
|
+
args = JSON.parse(tc.args);
|
|
7167
|
+
} catch {
|
|
7168
|
+
args = { _raw: tc.args };
|
|
7169
|
+
}
|
|
7170
|
+
return { id: tc.id, name: tc.name, arguments: args };
|
|
7171
|
+
}) : void 0;
|
|
7172
|
+
return {
|
|
7173
|
+
choices: [{ message: { content: cleanContent || null, toolCalls } }],
|
|
7174
|
+
usage: void 0
|
|
7175
|
+
// SSE responses typically don't include usage in chunks
|
|
7176
|
+
};
|
|
7177
|
+
}
|
|
6919
7178
|
};
|
|
6920
7179
|
OllamaAgenticBackend = class {
|
|
6921
7180
|
baseUrl;
|
|
@@ -6975,6 +7234,79 @@ ${summary}
|
|
|
6975
7234
|
usage: usage ? { totalTokens: usage.total_tokens ?? 0 } : void 0
|
|
6976
7235
|
};
|
|
6977
7236
|
}
|
|
7237
|
+
/**
|
|
7238
|
+
* SSE streaming variant — yields StreamChunks as tokens arrive.
|
|
7239
|
+
* Uses `stream: true` and `think: true` so thinking tokens are visible.
|
|
7240
|
+
* The existing chatCompletion() method is completely unmodified.
|
|
7241
|
+
*/
|
|
7242
|
+
async *chatCompletionStream(request) {
|
|
7243
|
+
const body = {
|
|
7244
|
+
model: this.model,
|
|
7245
|
+
messages: request.messages,
|
|
7246
|
+
tools: request.tools,
|
|
7247
|
+
temperature: request.temperature,
|
|
7248
|
+
max_tokens: request.maxTokens,
|
|
7249
|
+
stream: true,
|
|
7250
|
+
think: true
|
|
7251
|
+
};
|
|
7252
|
+
const resp = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
7253
|
+
method: "POST",
|
|
7254
|
+
headers: { "Content-Type": "application/json" },
|
|
7255
|
+
body: JSON.stringify(body),
|
|
7256
|
+
signal: AbortSignal.timeout(request.timeoutMs)
|
|
7257
|
+
});
|
|
7258
|
+
if (!resp.ok) {
|
|
7259
|
+
const text = await resp.text().catch(() => "");
|
|
7260
|
+
const isHtml = text.trimStart().startsWith("<!") || text.trimStart().startsWith("<html");
|
|
7261
|
+
const detail = isHtml ? `(received HTML error page \u2014 backend may be behind a proxy/CDN that is timing out)` : text.slice(0, 200);
|
|
7262
|
+
throw new Error(`Backend HTTP ${resp.status}: ${detail}`);
|
|
7263
|
+
}
|
|
7264
|
+
let sseBuffer = "";
|
|
7265
|
+
const decoder = new TextDecoder();
|
|
7266
|
+
for await (const rawChunk of resp.body) {
|
|
7267
|
+
sseBuffer += decoder.decode(rawChunk, { stream: true });
|
|
7268
|
+
const parts = sseBuffer.split("\n\n");
|
|
7269
|
+
sseBuffer = parts.pop();
|
|
7270
|
+
for (const part of parts) {
|
|
7271
|
+
const line = part.trim();
|
|
7272
|
+
if (!line)
|
|
7273
|
+
continue;
|
|
7274
|
+
if (line === "data: [DONE]")
|
|
7275
|
+
return;
|
|
7276
|
+
if (!line.startsWith("data: "))
|
|
7277
|
+
continue;
|
|
7278
|
+
try {
|
|
7279
|
+
const data = JSON.parse(line.slice(6));
|
|
7280
|
+
const choices = data.choices ?? [];
|
|
7281
|
+
const choice = choices[0];
|
|
7282
|
+
if (!choice)
|
|
7283
|
+
continue;
|
|
7284
|
+
const delta = choice.delta;
|
|
7285
|
+
const finishReason = choice.finish_reason;
|
|
7286
|
+
if (delta?.content) {
|
|
7287
|
+
yield { type: "content", content: delta.content };
|
|
7288
|
+
}
|
|
7289
|
+
const tcDeltas = delta?.tool_calls;
|
|
7290
|
+
if (tcDeltas) {
|
|
7291
|
+
for (const tcd of tcDeltas) {
|
|
7292
|
+
const fn = tcd.function;
|
|
7293
|
+
yield {
|
|
7294
|
+
type: "tool_call_delta",
|
|
7295
|
+
toolCallIndex: tcd.index ?? 0,
|
|
7296
|
+
toolCallId: tcd.id || void 0,
|
|
7297
|
+
toolCallName: fn?.name || void 0,
|
|
7298
|
+
toolCallArgs: fn?.arguments || void 0
|
|
7299
|
+
};
|
|
7300
|
+
}
|
|
7301
|
+
}
|
|
7302
|
+
if (finishReason) {
|
|
7303
|
+
yield { type: "finish", finishReason };
|
|
7304
|
+
}
|
|
7305
|
+
} catch {
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
6978
7310
|
};
|
|
6979
7311
|
}
|
|
6980
7312
|
});
|
|
@@ -7232,6 +7564,7 @@ function renderSlashHelp() {
|
|
|
7232
7564
|
["/update", "Check for updates and auto-install"],
|
|
7233
7565
|
["/voice", "Toggle TTS voice feedback (GLaDOS)"],
|
|
7234
7566
|
["/voice <model>", "Set voice: glados, overwatch"],
|
|
7567
|
+
["/stream", "Toggle real-time token streaming (pastel syntax highlighting)"],
|
|
7235
7568
|
["/verbose", "Toggle verbose mode"],
|
|
7236
7569
|
["/clear", "Clear the screen"],
|
|
7237
7570
|
["/help", "Show this help"],
|
|
@@ -7509,6 +7842,7 @@ var init_render = __esm({
|
|
|
7509
7842
|
"/config",
|
|
7510
7843
|
"/update",
|
|
7511
7844
|
"/voice",
|
|
7845
|
+
"/stream",
|
|
7512
7846
|
"/verbose",
|
|
7513
7847
|
"/clear",
|
|
7514
7848
|
"/help",
|
|
@@ -7595,6 +7929,13 @@ async function handleSlashCommand(input, ctx) {
|
|
|
7595
7929
|
}
|
|
7596
7930
|
return "handled";
|
|
7597
7931
|
}
|
|
7932
|
+
case "stream": {
|
|
7933
|
+
const isOn = ctx.streamToggle();
|
|
7934
|
+
const save = hasLocal ? ctx.saveLocalSettings.bind(ctx) : ctx.saveSettings.bind(ctx);
|
|
7935
|
+
save({ stream: isOn });
|
|
7936
|
+
renderInfo(`Token streaming: ${isOn ? "on" : "off"}${hasLocal ? " (project-local)" : ""}` + (isOn ? " \u2014 thinking tokens in grey italics, responses with pastel syntax highlighting" : ""));
|
|
7937
|
+
return "handled";
|
|
7938
|
+
}
|
|
7598
7939
|
default:
|
|
7599
7940
|
renderWarning(`Unknown command: /${cmd}. Type /help for available commands.`);
|
|
7600
7941
|
return "handled";
|
|
@@ -8647,14 +8988,62 @@ function getEnvironment(repoRoot) {
|
|
|
8647
8988
|
];
|
|
8648
8989
|
return lines.join("\n");
|
|
8649
8990
|
}
|
|
8650
|
-
function
|
|
8991
|
+
function loadTaskMemories(repoRoot, store) {
|
|
8992
|
+
try {
|
|
8993
|
+
let tasks = store.listByRepo(repoRoot);
|
|
8994
|
+
if (tasks.length === 0) {
|
|
8995
|
+
tasks = store.recent(10);
|
|
8996
|
+
}
|
|
8997
|
+
if (tasks.length === 0)
|
|
8998
|
+
return "";
|
|
8999
|
+
const lines = ["Recent agent tasks (cross-session memory):"];
|
|
9000
|
+
for (const t of tasks.slice(0, 10)) {
|
|
9001
|
+
const date = t.createdAt.split("T")[0];
|
|
9002
|
+
const files = t.filesTouched.slice(0, 5).join(", ");
|
|
9003
|
+
lines.push(`- [${date}] ${t.goal.slice(0, 100)} \u2192 ${t.outcome}${files ? ` (files: ${files})` : ""}`);
|
|
9004
|
+
if (t.notes) {
|
|
9005
|
+
lines.push(` Notes: ${t.notes.slice(0, 150)}`);
|
|
9006
|
+
}
|
|
9007
|
+
}
|
|
9008
|
+
return lines.join("\n");
|
|
9009
|
+
} catch {
|
|
9010
|
+
return "";
|
|
9011
|
+
}
|
|
9012
|
+
}
|
|
9013
|
+
function loadFailurePatterns(store) {
|
|
9014
|
+
try {
|
|
9015
|
+
const unresolved = store.listUnresolved();
|
|
9016
|
+
if (unresolved.length === 0)
|
|
9017
|
+
return "";
|
|
9018
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9019
|
+
const unique = unresolved.filter((f) => {
|
|
9020
|
+
if (seen.has(f.fingerprint))
|
|
9021
|
+
return false;
|
|
9022
|
+
seen.add(f.fingerprint);
|
|
9023
|
+
return true;
|
|
9024
|
+
});
|
|
9025
|
+
if (unique.length === 0)
|
|
9026
|
+
return "";
|
|
9027
|
+
const lines = ["Known failure patterns (avoid repeating these):"];
|
|
9028
|
+
for (const f of unique.slice(0, 8)) {
|
|
9029
|
+
const file = f.filePath ? ` in ${f.filePath}` : "";
|
|
9030
|
+
lines.push(`- [${f.failureType}]${file}: ${f.errorMessage.slice(0, 150)}`);
|
|
9031
|
+
}
|
|
9032
|
+
return lines.join("\n");
|
|
9033
|
+
} catch {
|
|
9034
|
+
return "";
|
|
9035
|
+
}
|
|
9036
|
+
}
|
|
9037
|
+
function buildProjectContext(repoRoot, stores) {
|
|
8651
9038
|
return {
|
|
8652
9039
|
projectInstructions: loadProjectFiles(repoRoot),
|
|
8653
9040
|
projectMap: loadProjectMap(repoRoot),
|
|
8654
9041
|
gitInfo: getGitInfo(repoRoot),
|
|
8655
9042
|
memoryContext: loadMemoryContext(repoRoot),
|
|
8656
9043
|
sessionHistory: loadSessionHistory(repoRoot),
|
|
8657
|
-
environment: getEnvironment(repoRoot)
|
|
9044
|
+
environment: getEnvironment(repoRoot),
|
|
9045
|
+
taskMemories: stores?.taskMemoryStore ? loadTaskMemories(repoRoot, stores.taskMemoryStore) : "",
|
|
9046
|
+
failurePatterns: stores?.failureStore ? loadFailurePatterns(stores.failureStore) : ""
|
|
8658
9047
|
};
|
|
8659
9048
|
}
|
|
8660
9049
|
function formatContextForPrompt(ctx) {
|
|
@@ -8688,6 +9077,20 @@ Use this context to avoid re-learning known patterns. Update with memory_write i
|
|
|
8688
9077
|
sections.push(`## Session History
|
|
8689
9078
|
|
|
8690
9079
|
${ctx.sessionHistory}`);
|
|
9080
|
+
}
|
|
9081
|
+
if (ctx.taskMemories) {
|
|
9082
|
+
sections.push(`## Cross-Session Task Memory
|
|
9083
|
+
|
|
9084
|
+
${ctx.taskMemories}
|
|
9085
|
+
|
|
9086
|
+
Use this history to avoid re-doing completed work and to learn from past approaches.`);
|
|
9087
|
+
}
|
|
9088
|
+
if (ctx.failurePatterns) {
|
|
9089
|
+
sections.push(`## Known Failure Patterns
|
|
9090
|
+
|
|
9091
|
+
${ctx.failurePatterns}
|
|
9092
|
+
|
|
9093
|
+
Avoid approaches that led to these failures. If you encounter these errors, try a different strategy.`);
|
|
8691
9094
|
}
|
|
8692
9095
|
return sections.join("\n\n");
|
|
8693
9096
|
}
|
|
@@ -8698,6 +9101,354 @@ var init_project_context = __esm({
|
|
|
8698
9101
|
}
|
|
8699
9102
|
});
|
|
8700
9103
|
|
|
9104
|
+
// packages/memory/dist/db.js
|
|
9105
|
+
import Database from "better-sqlite3";
|
|
9106
|
+
function initDb(dbPath) {
|
|
9107
|
+
const db = new Database(dbPath);
|
|
9108
|
+
db.pragma("journal_mode = WAL");
|
|
9109
|
+
db.pragma("foreign_keys = ON");
|
|
9110
|
+
runMigrations(db);
|
|
9111
|
+
return db;
|
|
9112
|
+
}
|
|
9113
|
+
function closeDb(db) {
|
|
9114
|
+
db.close();
|
|
9115
|
+
}
|
|
9116
|
+
function runMigrations(db) {
|
|
9117
|
+
db.exec(`
|
|
9118
|
+
-- repo_profiles: one row per repository root.
|
|
9119
|
+
CREATE TABLE IF NOT EXISTS repo_profiles (
|
|
9120
|
+
repo_root TEXT PRIMARY KEY,
|
|
9121
|
+
languages TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9122
|
+
frameworks TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9123
|
+
build_system TEXT NOT NULL DEFAULT '',
|
|
9124
|
+
test_commands TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9125
|
+
lint_commands TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9126
|
+
package_manager TEXT NOT NULL DEFAULT '',
|
|
9127
|
+
notes TEXT,
|
|
9128
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
9129
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
9130
|
+
);
|
|
9131
|
+
|
|
9132
|
+
-- file_summaries: one row per (repo_root, file_path) pair.
|
|
9133
|
+
CREATE TABLE IF NOT EXISTS file_summaries (
|
|
9134
|
+
file_path TEXT NOT NULL,
|
|
9135
|
+
repo_root TEXT NOT NULL,
|
|
9136
|
+
purpose TEXT NOT NULL DEFAULT '',
|
|
9137
|
+
exports TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9138
|
+
imports TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9139
|
+
domain TEXT NOT NULL DEFAULT '',
|
|
9140
|
+
risk_level TEXT NOT NULL DEFAULT 'low', -- low | medium | high
|
|
9141
|
+
related_tests TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9142
|
+
notes TEXT,
|
|
9143
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
9144
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
|
9145
|
+
PRIMARY KEY (file_path)
|
|
9146
|
+
);
|
|
9147
|
+
|
|
9148
|
+
CREATE INDEX IF NOT EXISTS idx_file_summaries_repo
|
|
9149
|
+
ON file_summaries (repo_root);
|
|
9150
|
+
|
|
9151
|
+
CREATE INDEX IF NOT EXISTS idx_file_summaries_domain
|
|
9152
|
+
ON file_summaries (domain);
|
|
9153
|
+
|
|
9154
|
+
CREATE INDEX IF NOT EXISTS idx_file_summaries_risk
|
|
9155
|
+
ON file_summaries (risk_level);
|
|
9156
|
+
|
|
9157
|
+
-- task_memory: historical record of agent tasks.
|
|
9158
|
+
CREATE TABLE IF NOT EXISTS task_memory (
|
|
9159
|
+
id TEXT PRIMARY KEY,
|
|
9160
|
+
session_id TEXT NOT NULL,
|
|
9161
|
+
repo_root TEXT NOT NULL,
|
|
9162
|
+
goal TEXT NOT NULL DEFAULT '',
|
|
9163
|
+
constraints TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9164
|
+
files_touched TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9165
|
+
patches TEXT NOT NULL DEFAULT '[]', -- JSON array
|
|
9166
|
+
outcome TEXT NOT NULL DEFAULT 'unknown', -- success | failure | partial | unknown
|
|
9167
|
+
quality_score REAL,
|
|
9168
|
+
notes TEXT,
|
|
9169
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
9170
|
+
);
|
|
9171
|
+
|
|
9172
|
+
CREATE INDEX IF NOT EXISTS idx_task_memory_session
|
|
9173
|
+
ON task_memory (session_id);
|
|
9174
|
+
|
|
9175
|
+
CREATE INDEX IF NOT EXISTS idx_task_memory_repo
|
|
9176
|
+
ON task_memory (repo_root);
|
|
9177
|
+
|
|
9178
|
+
CREATE INDEX IF NOT EXISTS idx_task_memory_outcome
|
|
9179
|
+
ON task_memory (outcome);
|
|
9180
|
+
|
|
9181
|
+
-- patch_history: individual file diffs applied by the agent.
|
|
9182
|
+
CREATE TABLE IF NOT EXISTS patch_history (
|
|
9183
|
+
id TEXT PRIMARY KEY,
|
|
9184
|
+
task_id TEXT NOT NULL,
|
|
9185
|
+
session_id TEXT NOT NULL,
|
|
9186
|
+
repo_root TEXT NOT NULL,
|
|
9187
|
+
file_path TEXT NOT NULL,
|
|
9188
|
+
diff TEXT NOT NULL DEFAULT '',
|
|
9189
|
+
status TEXT NOT NULL DEFAULT 'applied', -- applied | reverted | pending | failed
|
|
9190
|
+
applied_at TEXT,
|
|
9191
|
+
reverted_at TEXT,
|
|
9192
|
+
notes TEXT,
|
|
9193
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
9194
|
+
);
|
|
9195
|
+
|
|
9196
|
+
CREATE INDEX IF NOT EXISTS idx_patch_history_task
|
|
9197
|
+
ON patch_history (task_id);
|
|
9198
|
+
|
|
9199
|
+
CREATE INDEX IF NOT EXISTS idx_patch_history_file
|
|
9200
|
+
ON patch_history (file_path);
|
|
9201
|
+
|
|
9202
|
+
CREATE INDEX IF NOT EXISTS idx_patch_history_status
|
|
9203
|
+
ON patch_history (status);
|
|
9204
|
+
|
|
9205
|
+
-- failures: fingerprinted failure events for pattern detection.
|
|
9206
|
+
CREATE TABLE IF NOT EXISTS failures (
|
|
9207
|
+
id TEXT PRIMARY KEY,
|
|
9208
|
+
task_id TEXT NOT NULL,
|
|
9209
|
+
session_id TEXT NOT NULL,
|
|
9210
|
+
repo_root TEXT NOT NULL,
|
|
9211
|
+
failure_type TEXT NOT NULL DEFAULT '',
|
|
9212
|
+
fingerprint TEXT NOT NULL DEFAULT '',
|
|
9213
|
+
file_path TEXT,
|
|
9214
|
+
error_message TEXT NOT NULL DEFAULT '',
|
|
9215
|
+
context TEXT, -- JSON object or null
|
|
9216
|
+
resolved INTEGER NOT NULL DEFAULT 0, -- 0 = false, 1 = true
|
|
9217
|
+
resolved_at TEXT,
|
|
9218
|
+
notes TEXT,
|
|
9219
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
9220
|
+
);
|
|
9221
|
+
|
|
9222
|
+
CREATE INDEX IF NOT EXISTS idx_failures_type
|
|
9223
|
+
ON failures (failure_type);
|
|
9224
|
+
|
|
9225
|
+
CREATE INDEX IF NOT EXISTS idx_failures_fingerprint
|
|
9226
|
+
ON failures (fingerprint);
|
|
9227
|
+
|
|
9228
|
+
CREATE INDEX IF NOT EXISTS idx_failures_resolved
|
|
9229
|
+
ON failures (resolved);
|
|
9230
|
+
|
|
9231
|
+
-- validation_runs: results of lint / test / build / typecheck runs.
|
|
9232
|
+
CREATE TABLE IF NOT EXISTS validation_runs (
|
|
9233
|
+
id TEXT PRIMARY KEY,
|
|
9234
|
+
task_id TEXT NOT NULL,
|
|
9235
|
+
session_id TEXT NOT NULL,
|
|
9236
|
+
repo_root TEXT NOT NULL,
|
|
9237
|
+
run_type TEXT NOT NULL DEFAULT '', -- test | lint | build | typecheck
|
|
9238
|
+
command TEXT NOT NULL DEFAULT '',
|
|
9239
|
+
passed INTEGER NOT NULL DEFAULT 0, -- 0 = false, 1 = true
|
|
9240
|
+
duration_ms INTEGER,
|
|
9241
|
+
output TEXT,
|
|
9242
|
+
error_output TEXT,
|
|
9243
|
+
coverage_percent REAL,
|
|
9244
|
+
notes TEXT,
|
|
9245
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
|
|
9246
|
+
);
|
|
9247
|
+
|
|
9248
|
+
CREATE INDEX IF NOT EXISTS idx_validation_runs_task
|
|
9249
|
+
ON validation_runs (task_id);
|
|
9250
|
+
|
|
9251
|
+
CREATE INDEX IF NOT EXISTS idx_validation_runs_run_type
|
|
9252
|
+
ON validation_runs (run_type);
|
|
9253
|
+
|
|
9254
|
+
CREATE INDEX IF NOT EXISTS idx_validation_runs_passed
|
|
9255
|
+
ON validation_runs (passed);
|
|
9256
|
+
`);
|
|
9257
|
+
}
|
|
9258
|
+
var init_db = __esm({
|
|
9259
|
+
"packages/memory/dist/db.js"() {
|
|
9260
|
+
"use strict";
|
|
9261
|
+
}
|
|
9262
|
+
});
|
|
9263
|
+
|
|
9264
|
+
// packages/memory/dist/repoProfileStore.js
|
|
9265
|
+
var init_repoProfileStore = __esm({
|
|
9266
|
+
"packages/memory/dist/repoProfileStore.js"() {
|
|
9267
|
+
"use strict";
|
|
9268
|
+
}
|
|
9269
|
+
});
|
|
9270
|
+
|
|
9271
|
+
// packages/memory/dist/fileSummaryStore.js
|
|
9272
|
+
var init_fileSummaryStore = __esm({
|
|
9273
|
+
"packages/memory/dist/fileSummaryStore.js"() {
|
|
9274
|
+
"use strict";
|
|
9275
|
+
}
|
|
9276
|
+
});
|
|
9277
|
+
|
|
9278
|
+
// packages/memory/dist/taskMemoryStore.js
|
|
9279
|
+
import { randomUUID } from "node:crypto";
|
|
9280
|
+
function rowToTask(row) {
|
|
9281
|
+
return {
|
|
9282
|
+
id: row.id,
|
|
9283
|
+
sessionId: row.session_id,
|
|
9284
|
+
repoRoot: row.repo_root,
|
|
9285
|
+
goal: row.goal,
|
|
9286
|
+
constraints: JSON.parse(row.constraints),
|
|
9287
|
+
filesTouched: JSON.parse(row.files_touched),
|
|
9288
|
+
patches: JSON.parse(row.patches),
|
|
9289
|
+
outcome: row.outcome,
|
|
9290
|
+
qualityScore: row.quality_score ?? null,
|
|
9291
|
+
notes: row.notes ?? null,
|
|
9292
|
+
createdAt: row.created_at
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
var TaskMemoryStore;
|
|
9296
|
+
var init_taskMemoryStore = __esm({
|
|
9297
|
+
"packages/memory/dist/taskMemoryStore.js"() {
|
|
9298
|
+
"use strict";
|
|
9299
|
+
TaskMemoryStore = class {
|
|
9300
|
+
db;
|
|
9301
|
+
constructor(db) {
|
|
9302
|
+
this.db = db;
|
|
9303
|
+
}
|
|
9304
|
+
/**
|
|
9305
|
+
* Insert a new task record and return its auto-generated id.
|
|
9306
|
+
*/
|
|
9307
|
+
insert(input) {
|
|
9308
|
+
const id = randomUUID();
|
|
9309
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9310
|
+
this.db.prepare(`INSERT INTO task_memory
|
|
9311
|
+
(id, session_id, repo_root, goal, constraints,
|
|
9312
|
+
files_touched, patches, outcome, quality_score, notes, created_at)
|
|
9313
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.sessionId, input.repoRoot, input.goal, JSON.stringify(input.constraints), JSON.stringify(input.filesTouched), JSON.stringify(input.patches), input.outcome, input.qualityScore ?? null, input.notes ?? null, now);
|
|
9314
|
+
return id;
|
|
9315
|
+
}
|
|
9316
|
+
/** Retrieve a task record by id, or `null` if not found. */
|
|
9317
|
+
getById(id) {
|
|
9318
|
+
const row = this.db.prepare("SELECT * FROM task_memory WHERE id = ?").get(id);
|
|
9319
|
+
return row ? rowToTask(row) : null;
|
|
9320
|
+
}
|
|
9321
|
+
/** List all tasks associated with a session. */
|
|
9322
|
+
listBySession(sessionId) {
|
|
9323
|
+
const rows = this.db.prepare("SELECT * FROM task_memory WHERE session_id = ? ORDER BY created_at DESC").all(sessionId);
|
|
9324
|
+
return rows.map(rowToTask);
|
|
9325
|
+
}
|
|
9326
|
+
/** List all tasks for a repository root. */
|
|
9327
|
+
listByRepo(repoRoot) {
|
|
9328
|
+
const rows = this.db.prepare("SELECT * FROM task_memory WHERE repo_root = ? ORDER BY created_at DESC").all(repoRoot);
|
|
9329
|
+
return rows.map(rowToTask);
|
|
9330
|
+
}
|
|
9331
|
+
/** List tasks filtered by outcome. */
|
|
9332
|
+
listByOutcome(outcome) {
|
|
9333
|
+
const rows = this.db.prepare("SELECT * FROM task_memory WHERE outcome = ? ORDER BY created_at DESC").all(outcome);
|
|
9334
|
+
return rows.map(rowToTask);
|
|
9335
|
+
}
|
|
9336
|
+
/**
|
|
9337
|
+
* Return the N most recently created tasks across all sessions and repos.
|
|
9338
|
+
* Useful for building a rolling context window.
|
|
9339
|
+
*/
|
|
9340
|
+
recent(limit) {
|
|
9341
|
+
const rows = this.db.prepare("SELECT * FROM task_memory ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
9342
|
+
return rows.map(rowToTask);
|
|
9343
|
+
}
|
|
9344
|
+
};
|
|
9345
|
+
}
|
|
9346
|
+
});
|
|
9347
|
+
|
|
9348
|
+
// packages/memory/dist/patchHistoryStore.js
|
|
9349
|
+
var init_patchHistoryStore = __esm({
|
|
9350
|
+
"packages/memory/dist/patchHistoryStore.js"() {
|
|
9351
|
+
"use strict";
|
|
9352
|
+
}
|
|
9353
|
+
});
|
|
9354
|
+
|
|
9355
|
+
// packages/memory/dist/failureStore.js
|
|
9356
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
9357
|
+
function rowToFailure(row) {
|
|
9358
|
+
return {
|
|
9359
|
+
id: row.id,
|
|
9360
|
+
taskId: row.task_id,
|
|
9361
|
+
sessionId: row.session_id,
|
|
9362
|
+
repoRoot: row.repo_root,
|
|
9363
|
+
failureType: row.failure_type,
|
|
9364
|
+
fingerprint: row.fingerprint,
|
|
9365
|
+
filePath: row.file_path ?? null,
|
|
9366
|
+
errorMessage: row.error_message,
|
|
9367
|
+
context: row.context !== null ? JSON.parse(row.context) : null,
|
|
9368
|
+
resolved: row.resolved === 1,
|
|
9369
|
+
resolvedAt: row.resolved_at ?? null,
|
|
9370
|
+
notes: row.notes ?? null,
|
|
9371
|
+
createdAt: row.created_at
|
|
9372
|
+
};
|
|
9373
|
+
}
|
|
9374
|
+
var FailureStore;
|
|
9375
|
+
var init_failureStore = __esm({
|
|
9376
|
+
"packages/memory/dist/failureStore.js"() {
|
|
9377
|
+
"use strict";
|
|
9378
|
+
FailureStore = class {
|
|
9379
|
+
db;
|
|
9380
|
+
constructor(db) {
|
|
9381
|
+
this.db = db;
|
|
9382
|
+
}
|
|
9383
|
+
/**
|
|
9384
|
+
* Record a new failure event and return its auto-generated id.
|
|
9385
|
+
*/
|
|
9386
|
+
insert(input) {
|
|
9387
|
+
const id = randomUUID2();
|
|
9388
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9389
|
+
this.db.prepare(`INSERT INTO failures
|
|
9390
|
+
(id, task_id, session_id, repo_root, failure_type,
|
|
9391
|
+
fingerprint, file_path, error_message, context,
|
|
9392
|
+
resolved, resolved_at, notes, created_at)
|
|
9393
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, input.taskId, input.sessionId, input.repoRoot, input.failureType, input.fingerprint, input.filePath ?? null, input.errorMessage, input.context !== null ? JSON.stringify(input.context) : null, input.resolved ? 1 : 0, input.resolvedAt ?? null, input.notes ?? null, now);
|
|
9394
|
+
return id;
|
|
9395
|
+
}
|
|
9396
|
+
/** Retrieve a failure by id, or `null` if not found. */
|
|
9397
|
+
getById(id) {
|
|
9398
|
+
const row = this.db.prepare("SELECT * FROM failures WHERE id = ?").get(id);
|
|
9399
|
+
return row ? rowToFailure(row) : null;
|
|
9400
|
+
}
|
|
9401
|
+
/** List failures filtered by type. */
|
|
9402
|
+
listByType(failureType) {
|
|
9403
|
+
const rows = this.db.prepare("SELECT * FROM failures WHERE failure_type = ? ORDER BY created_at DESC").all(failureType);
|
|
9404
|
+
return rows.map(rowToFailure);
|
|
9405
|
+
}
|
|
9406
|
+
/** List all failures sharing the same content fingerprint. */
|
|
9407
|
+
listByFingerprint(fingerprint) {
|
|
9408
|
+
const rows = this.db.prepare("SELECT * FROM failures WHERE fingerprint = ? ORDER BY created_at DESC").all(fingerprint);
|
|
9409
|
+
return rows.map(rowToFailure);
|
|
9410
|
+
}
|
|
9411
|
+
/** List all failures that have not yet been marked as resolved. */
|
|
9412
|
+
listUnresolved() {
|
|
9413
|
+
const rows = this.db.prepare("SELECT * FROM failures WHERE resolved = 0 ORDER BY created_at DESC").all();
|
|
9414
|
+
return rows.map(rowToFailure);
|
|
9415
|
+
}
|
|
9416
|
+
/**
|
|
9417
|
+
* Mark a failure as resolved.
|
|
9418
|
+
*
|
|
9419
|
+
* @param id - The failure record id.
|
|
9420
|
+
* @param resolvedAt - ISO timestamp string of resolution time.
|
|
9421
|
+
*/
|
|
9422
|
+
markResolved(id, resolvedAt) {
|
|
9423
|
+
this.db.prepare(`UPDATE failures
|
|
9424
|
+
SET resolved = 1, resolved_at = ?
|
|
9425
|
+
WHERE id = ?`).run(resolvedAt, id);
|
|
9426
|
+
}
|
|
9427
|
+
};
|
|
9428
|
+
}
|
|
9429
|
+
});
|
|
9430
|
+
|
|
9431
|
+
// packages/memory/dist/validationStore.js
|
|
9432
|
+
var init_validationStore = __esm({
|
|
9433
|
+
"packages/memory/dist/validationStore.js"() {
|
|
9434
|
+
"use strict";
|
|
9435
|
+
}
|
|
9436
|
+
});
|
|
9437
|
+
|
|
9438
|
+
// packages/memory/dist/index.js
|
|
9439
|
+
var init_dist6 = __esm({
|
|
9440
|
+
"packages/memory/dist/index.js"() {
|
|
9441
|
+
"use strict";
|
|
9442
|
+
init_db();
|
|
9443
|
+
init_repoProfileStore();
|
|
9444
|
+
init_fileSummaryStore();
|
|
9445
|
+
init_taskMemoryStore();
|
|
9446
|
+
init_patchHistoryStore();
|
|
9447
|
+
init_failureStore();
|
|
9448
|
+
init_validationStore();
|
|
9449
|
+
}
|
|
9450
|
+
});
|
|
9451
|
+
|
|
8701
9452
|
// packages/cli/dist/tui/carousel.js
|
|
8702
9453
|
function fg(code, text) {
|
|
8703
9454
|
return isTTY3 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
|
|
@@ -9502,6 +10253,266 @@ Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
9502
10253
|
}
|
|
9503
10254
|
});
|
|
9504
10255
|
|
|
10256
|
+
// packages/cli/dist/tui/stream-renderer.js
|
|
10257
|
+
function fg256(code, text) {
|
|
10258
|
+
return isTTY4 ? `\x1B[38;5;${code}m${text}\x1B[0m` : text;
|
|
10259
|
+
}
|
|
10260
|
+
function dimText(text) {
|
|
10261
|
+
return isTTY4 ? `\x1B[2m${text}\x1B[0m` : text;
|
|
10262
|
+
}
|
|
10263
|
+
function dimItalic(text) {
|
|
10264
|
+
return isTTY4 ? `\x1B[2;3m${text}\x1B[0m` : text;
|
|
10265
|
+
}
|
|
10266
|
+
var isTTY4, PASTEL, StreamRenderer;
|
|
10267
|
+
var init_stream_renderer = __esm({
|
|
10268
|
+
"packages/cli/dist/tui/stream-renderer.js"() {
|
|
10269
|
+
"use strict";
|
|
10270
|
+
isTTY4 = process.stdout.isTTY ?? false;
|
|
10271
|
+
PASTEL = {
|
|
10272
|
+
key: 222,
|
|
10273
|
+
// light gold — JSON keys
|
|
10274
|
+
string: 183,
|
|
10275
|
+
// light lavender — "string values"
|
|
10276
|
+
number: 156,
|
|
10277
|
+
// soft green — 42, 3.14
|
|
10278
|
+
boolean: 114,
|
|
10279
|
+
// mint green — true, false
|
|
10280
|
+
null: 109,
|
|
10281
|
+
// grey-blue — null
|
|
10282
|
+
bracket: 75,
|
|
10283
|
+
// soft blue — { } [ ]
|
|
10284
|
+
colon: 245,
|
|
10285
|
+
// neutral grey — : ,
|
|
10286
|
+
keyword: 117,
|
|
10287
|
+
// sky blue — function, return, if, else
|
|
10288
|
+
comment: 243,
|
|
10289
|
+
// dim grey — // comments
|
|
10290
|
+
thinking: 245,
|
|
10291
|
+
// neutral grey for thinking tokens
|
|
10292
|
+
toolArg: 111
|
|
10293
|
+
// dim periwinkle for tool arg tokens
|
|
10294
|
+
};
|
|
10295
|
+
StreamRenderer = class {
|
|
10296
|
+
lineBuffer = "";
|
|
10297
|
+
inThinkBlock = false;
|
|
10298
|
+
inCodeBlock = false;
|
|
10299
|
+
codeLang = "";
|
|
10300
|
+
lineStarted = false;
|
|
10301
|
+
flushTimer = null;
|
|
10302
|
+
enabled = false;
|
|
10303
|
+
tokenCount = 0;
|
|
10304
|
+
startTime = 0;
|
|
10305
|
+
/** Track if we're mid-tool-arg display */
|
|
10306
|
+
inToolArgs = false;
|
|
10307
|
+
/** Called when a new model response starts streaming */
|
|
10308
|
+
onStreamStart() {
|
|
10309
|
+
this.lineBuffer = "";
|
|
10310
|
+
this.inThinkBlock = false;
|
|
10311
|
+
this.inCodeBlock = false;
|
|
10312
|
+
this.codeLang = "";
|
|
10313
|
+
this.lineStarted = false;
|
|
10314
|
+
this.inToolArgs = false;
|
|
10315
|
+
this.enabled = true;
|
|
10316
|
+
this.tokenCount = 0;
|
|
10317
|
+
this.startTime = Date.now();
|
|
10318
|
+
this.cancelFlush();
|
|
10319
|
+
}
|
|
10320
|
+
/**
|
|
10321
|
+
* Feed a streamed token into the renderer.
|
|
10322
|
+
* Tokens are buffered per-line and flushed with syntax highlighting.
|
|
10323
|
+
*/
|
|
10324
|
+
write(token, kind) {
|
|
10325
|
+
if (!this.enabled)
|
|
10326
|
+
return;
|
|
10327
|
+
this.tokenCount++;
|
|
10328
|
+
if (kind === "tool_args" && !this.inToolArgs) {
|
|
10329
|
+
this.flushPartial(kind);
|
|
10330
|
+
this.inToolArgs = true;
|
|
10331
|
+
} else if (kind !== "tool_args" && this.inToolArgs) {
|
|
10332
|
+
this.flushPartial(kind);
|
|
10333
|
+
this.inToolArgs = false;
|
|
10334
|
+
}
|
|
10335
|
+
for (const char of token) {
|
|
10336
|
+
this.lineBuffer += char;
|
|
10337
|
+
if (char === "\n") {
|
|
10338
|
+
this.flushLine(kind);
|
|
10339
|
+
}
|
|
10340
|
+
}
|
|
10341
|
+
this.scheduleFlush(kind);
|
|
10342
|
+
}
|
|
10343
|
+
/** Called when streaming ends for this response */
|
|
10344
|
+
onStreamEnd() {
|
|
10345
|
+
if (!this.enabled)
|
|
10346
|
+
return;
|
|
10347
|
+
this.cancelFlush();
|
|
10348
|
+
if (this.lineBuffer.length > 0) {
|
|
10349
|
+
const kind = this.inThinkBlock ? "thinking" : this.inToolArgs ? "tool_args" : "content";
|
|
10350
|
+
this.writeHighlighted(this.lineBuffer, kind);
|
|
10351
|
+
this.lineBuffer = "";
|
|
10352
|
+
}
|
|
10353
|
+
if (this.lineStarted) {
|
|
10354
|
+
process.stdout.write("\n");
|
|
10355
|
+
this.lineStarted = false;
|
|
10356
|
+
}
|
|
10357
|
+
this.enabled = false;
|
|
10358
|
+
}
|
|
10359
|
+
/** Get streaming stats */
|
|
10360
|
+
getStats() {
|
|
10361
|
+
return {
|
|
10362
|
+
tokens: this.tokenCount,
|
|
10363
|
+
durationMs: Date.now() - this.startTime
|
|
10364
|
+
};
|
|
10365
|
+
}
|
|
10366
|
+
// -------------------------------------------------------------------------
|
|
10367
|
+
// Internal rendering
|
|
10368
|
+
// -------------------------------------------------------------------------
|
|
10369
|
+
/**
|
|
10370
|
+
* Flush a complete line (ending with \n) with full syntax highlighting.
|
|
10371
|
+
*/
|
|
10372
|
+
flushLine(kind) {
|
|
10373
|
+
const line = this.lineBuffer;
|
|
10374
|
+
this.lineBuffer = "";
|
|
10375
|
+
if (!line || line === "\n") {
|
|
10376
|
+
if (this.lineStarted) {
|
|
10377
|
+
process.stdout.write("\n");
|
|
10378
|
+
this.lineStarted = false;
|
|
10379
|
+
} else {
|
|
10380
|
+
process.stdout.write("\n");
|
|
10381
|
+
}
|
|
10382
|
+
return;
|
|
10383
|
+
}
|
|
10384
|
+
if (line.includes("<think>")) {
|
|
10385
|
+
this.inThinkBlock = true;
|
|
10386
|
+
const after = line.replace(/<think>/g, "");
|
|
10387
|
+
if (after.trim()) {
|
|
10388
|
+
this.writeHighlighted(after, "thinking");
|
|
10389
|
+
}
|
|
10390
|
+
return;
|
|
10391
|
+
}
|
|
10392
|
+
if (line.includes("</think>")) {
|
|
10393
|
+
this.inThinkBlock = false;
|
|
10394
|
+
const after = line.replace(/<\/think>/g, "");
|
|
10395
|
+
if (after.trim()) {
|
|
10396
|
+
this.writeHighlighted(after, "content");
|
|
10397
|
+
}
|
|
10398
|
+
return;
|
|
10399
|
+
}
|
|
10400
|
+
const trimmedLine = line.replace(/\n$/, "");
|
|
10401
|
+
if (trimmedLine.trimStart().startsWith("```")) {
|
|
10402
|
+
if (this.inCodeBlock) {
|
|
10403
|
+
this.writeRaw(dimText(" \u23BF ") + dimText("```") + "\n");
|
|
10404
|
+
this.inCodeBlock = false;
|
|
10405
|
+
this.codeLang = "";
|
|
10406
|
+
this.lineStarted = false;
|
|
10407
|
+
} else {
|
|
10408
|
+
this.codeLang = trimmedLine.replace(/```/g, "").trim();
|
|
10409
|
+
this.writeRaw(dimText(" \u23BF ") + dimText("```" + this.codeLang) + "\n");
|
|
10410
|
+
this.inCodeBlock = true;
|
|
10411
|
+
this.lineStarted = false;
|
|
10412
|
+
}
|
|
10413
|
+
return;
|
|
10414
|
+
}
|
|
10415
|
+
const effectiveKind = this.inThinkBlock ? "thinking" : kind;
|
|
10416
|
+
this.writeHighlighted(line, effectiveKind);
|
|
10417
|
+
}
|
|
10418
|
+
/**
|
|
10419
|
+
* Write a highlighted line/fragment to stdout.
|
|
10420
|
+
*/
|
|
10421
|
+
writeHighlighted(text, kind) {
|
|
10422
|
+
const raw = text.replace(/\n$/, "");
|
|
10423
|
+
if (!raw)
|
|
10424
|
+
return;
|
|
10425
|
+
const prefix = this.lineStarted ? "" : " \u23BF ";
|
|
10426
|
+
let rendered;
|
|
10427
|
+
switch (kind) {
|
|
10428
|
+
case "thinking":
|
|
10429
|
+
rendered = dimItalic(raw);
|
|
10430
|
+
break;
|
|
10431
|
+
case "tool_args":
|
|
10432
|
+
rendered = this.highlightJson(raw, true);
|
|
10433
|
+
break;
|
|
10434
|
+
case "content":
|
|
10435
|
+
if (this.inCodeBlock) {
|
|
10436
|
+
rendered = this.highlightCode(raw);
|
|
10437
|
+
} else if (this.looksLikeJson(raw)) {
|
|
10438
|
+
rendered = this.highlightJson(raw, false);
|
|
10439
|
+
} else {
|
|
10440
|
+
rendered = raw;
|
|
10441
|
+
}
|
|
10442
|
+
break;
|
|
10443
|
+
}
|
|
10444
|
+
const hasNewline = text.endsWith("\n");
|
|
10445
|
+
this.writeRaw(dimText(prefix) + rendered + (hasNewline ? "\n" : ""));
|
|
10446
|
+
this.lineStarted = !hasNewline;
|
|
10447
|
+
}
|
|
10448
|
+
/** Write raw ANSI text to stdout */
|
|
10449
|
+
writeRaw(text) {
|
|
10450
|
+
process.stdout.write(text);
|
|
10451
|
+
}
|
|
10452
|
+
/** Flush partial buffer (non-newline-terminated tokens) */
|
|
10453
|
+
flushPartial(kind) {
|
|
10454
|
+
if (this.lineBuffer.length === 0)
|
|
10455
|
+
return;
|
|
10456
|
+
const effectiveKind = this.inThinkBlock ? "thinking" : kind;
|
|
10457
|
+
this.writeHighlighted(this.lineBuffer, effectiveKind);
|
|
10458
|
+
this.lineBuffer = "";
|
|
10459
|
+
}
|
|
10460
|
+
/** Schedule a timer to flush partial buffer (for streaming smoothness) */
|
|
10461
|
+
scheduleFlush(kind) {
|
|
10462
|
+
this.cancelFlush();
|
|
10463
|
+
this.flushTimer = setTimeout(() => {
|
|
10464
|
+
if (this.lineBuffer.length > 0) {
|
|
10465
|
+
this.flushPartial(kind);
|
|
10466
|
+
}
|
|
10467
|
+
}, 80);
|
|
10468
|
+
}
|
|
10469
|
+
cancelFlush() {
|
|
10470
|
+
if (this.flushTimer) {
|
|
10471
|
+
clearTimeout(this.flushTimer);
|
|
10472
|
+
this.flushTimer = null;
|
|
10473
|
+
}
|
|
10474
|
+
}
|
|
10475
|
+
// -------------------------------------------------------------------------
|
|
10476
|
+
// Syntax highlighting — pastel palette
|
|
10477
|
+
// -------------------------------------------------------------------------
|
|
10478
|
+
/** Check if a string looks like JSON (starts with { [ " or has key: patterns) */
|
|
10479
|
+
looksLikeJson(text) {
|
|
10480
|
+
const trimmed = text.trimStart();
|
|
10481
|
+
return trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("}") || trimmed.startsWith("]") || /^\s*"[^"]+"\s*:/.test(trimmed);
|
|
10482
|
+
}
|
|
10483
|
+
/**
|
|
10484
|
+
* Highlight a JSON line with pastel colors.
|
|
10485
|
+
* @param dim If true, apply dimmer colors (for tool args)
|
|
10486
|
+
*/
|
|
10487
|
+
highlightJson(line, dim) {
|
|
10488
|
+
const colorKey = dim ? PASTEL.toolArg : PASTEL.key;
|
|
10489
|
+
const colorStr = dim ? PASTEL.toolArg : PASTEL.string;
|
|
10490
|
+
let result = line;
|
|
10491
|
+
result = result.replace(/"([^"]*)"(\s*:)/g, (_m, key, colon) => fg256(colorKey, `"${key}"`) + fg256(PASTEL.colon, colon));
|
|
10492
|
+
result = result.replace(/(:\s*)"([^"]*)"/g, (_m, prefix, val) => fg256(PASTEL.colon, prefix) + fg256(colorStr, `"${val}"`));
|
|
10493
|
+
result = result.replace(/(:\s*)(\d+\.?\d*)/g, (_m, prefix, num) => fg256(PASTEL.colon, prefix) + fg256(PASTEL.number, num));
|
|
10494
|
+
result = result.replace(/(:\s*)(true|false)/g, (_m, prefix, bool) => fg256(PASTEL.colon, prefix) + fg256(PASTEL.boolean, bool));
|
|
10495
|
+
result = result.replace(/(:\s*)(null)/g, (_m, prefix, n) => fg256(PASTEL.colon, prefix) + fg256(PASTEL.null, n));
|
|
10496
|
+
result = result.replace(/([{}[\]])/g, (_m, b) => fg256(PASTEL.bracket, b));
|
|
10497
|
+
return dim ? dimText(result) : result;
|
|
10498
|
+
}
|
|
10499
|
+
/**
|
|
10500
|
+
* Highlight a code line with basic pastel syntax coloring.
|
|
10501
|
+
*/
|
|
10502
|
+
highlightCode(line) {
|
|
10503
|
+
let result = line;
|
|
10504
|
+
result = result.replace(/"([^"]*)"/g, (_m, s) => fg256(PASTEL.string, `"${s}"`));
|
|
10505
|
+
result = result.replace(/'([^']*)'/g, (_m, s) => fg256(PASTEL.string, `'${s}'`));
|
|
10506
|
+
result = result.replace(/\b(\d+\.?\d*)\b/g, (_m, n) => fg256(PASTEL.number, n));
|
|
10507
|
+
result = result.replace(/\b(true|false|null|undefined|None|True|False)\b/g, (_m, kw) => fg256(PASTEL.boolean, kw));
|
|
10508
|
+
result = result.replace(/\b(function|const|let|var|return|if|else|for|while|import|export|from|class|async|await|def|self|try|catch|finally|throw|new|typeof|instanceof)\b/g, (_m, kw) => fg256(PASTEL.keyword, kw));
|
|
10509
|
+
result = result.replace(/(\/\/.*$|#.*$)/gm, (_m, c3) => fg256(PASTEL.comment, c3));
|
|
10510
|
+
return result;
|
|
10511
|
+
}
|
|
10512
|
+
};
|
|
10513
|
+
}
|
|
10514
|
+
});
|
|
10515
|
+
|
|
9505
10516
|
// packages/cli/dist/tui/interactive.js
|
|
9506
10517
|
import * as readline2 from "node:readline";
|
|
9507
10518
|
import { cwd } from "node:process";
|
|
@@ -9669,8 +10680,8 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
|
|
|
9669
10680
|
}
|
|
9670
10681
|
};
|
|
9671
10682
|
}
|
|
9672
|
-
function startTask(task, config, repoRoot, voice) {
|
|
9673
|
-
const projectCtx = buildProjectContext(repoRoot);
|
|
10683
|
+
function startTask(task, config, repoRoot, voice, stream, taskStores) {
|
|
10684
|
+
const projectCtx = buildProjectContext(repoRoot, taskStores?.contextStores);
|
|
9674
10685
|
const dynamicContext = formatContextForPrompt(projectCtx);
|
|
9675
10686
|
const backend = new OllamaAgenticBackend(config.backendUrl.replace(/\/$/, ""), config.model);
|
|
9676
10687
|
const runner = new AgenticRunner(backend, {
|
|
@@ -9680,12 +10691,20 @@ function startTask(task, config, repoRoot, voice) {
|
|
|
9680
10691
|
requestTimeoutMs: config.timeoutMs,
|
|
9681
10692
|
taskTimeoutMs: config.timeoutMs * 4,
|
|
9682
10693
|
compactionThreshold: 4e4,
|
|
9683
|
-
dynamicContext
|
|
10694
|
+
dynamicContext,
|
|
10695
|
+
streamEnabled: stream?.enabled ?? false
|
|
9684
10696
|
});
|
|
9685
10697
|
runner.registerTools(buildTools(repoRoot, config));
|
|
10698
|
+
const filesTouched = /* @__PURE__ */ new Set();
|
|
9686
10699
|
runner.onEvent((event) => {
|
|
9687
10700
|
switch (event.type) {
|
|
9688
10701
|
case "tool_call":
|
|
10702
|
+
if (event.toolArgs?.path && typeof event.toolArgs.path === "string") {
|
|
10703
|
+
const name = event.toolName ?? "";
|
|
10704
|
+
if (name === "file_write" || name === "file_edit" || name === "batch_edit") {
|
|
10705
|
+
filesTouched.add(event.toolArgs.path);
|
|
10706
|
+
}
|
|
10707
|
+
}
|
|
9689
10708
|
if (voice?.enabled) {
|
|
9690
10709
|
const desc = describeToolCall(event.toolName ?? "unknown", event.toolArgs ?? {});
|
|
9691
10710
|
renderVoiceText(desc);
|
|
@@ -9704,10 +10723,23 @@ function startTask(task, config, repoRoot, voice) {
|
|
|
9704
10723
|
}
|
|
9705
10724
|
break;
|
|
9706
10725
|
case "model_response":
|
|
9707
|
-
if (config.verbose && event.content) {
|
|
10726
|
+
if (config.verbose && !stream?.enabled && event.content) {
|
|
9708
10727
|
renderAssistantText(event.content);
|
|
9709
10728
|
}
|
|
9710
10729
|
break;
|
|
10730
|
+
case "stream_start":
|
|
10731
|
+
if (stream?.enabled)
|
|
10732
|
+
stream.renderer.onStreamStart();
|
|
10733
|
+
break;
|
|
10734
|
+
case "stream_token":
|
|
10735
|
+
if (stream?.enabled) {
|
|
10736
|
+
stream.renderer.write(event.content ?? "", event.streamKind ?? "content");
|
|
10737
|
+
}
|
|
10738
|
+
break;
|
|
10739
|
+
case "stream_end":
|
|
10740
|
+
if (stream?.enabled)
|
|
10741
|
+
stream.renderer.onStreamEnd();
|
|
10742
|
+
break;
|
|
9711
10743
|
case "user_interrupt":
|
|
9712
10744
|
break;
|
|
9713
10745
|
case "compaction":
|
|
@@ -9747,6 +10779,22 @@ function startTask(task, config, repoRoot, voice) {
|
|
|
9747
10779
|
});
|
|
9748
10780
|
} catch {
|
|
9749
10781
|
}
|
|
10782
|
+
if (taskStores?.taskMemoryStore) {
|
|
10783
|
+
try {
|
|
10784
|
+
taskStores.taskMemoryStore.insert({
|
|
10785
|
+
sessionId,
|
|
10786
|
+
repoRoot,
|
|
10787
|
+
goal: task.slice(0, 500),
|
|
10788
|
+
constraints: [],
|
|
10789
|
+
filesTouched: Array.from(filesTouched).slice(0, 50),
|
|
10790
|
+
patches: [],
|
|
10791
|
+
outcome: result.completed ? "success" : "partial",
|
|
10792
|
+
qualityScore: null,
|
|
10793
|
+
notes: result.summary.slice(0, 500)
|
|
10794
|
+
});
|
|
10795
|
+
} catch {
|
|
10796
|
+
}
|
|
10797
|
+
}
|
|
9750
10798
|
});
|
|
9751
10799
|
return { runner, promise };
|
|
9752
10800
|
}
|
|
@@ -9758,6 +10806,17 @@ async function startInteractive(config, repoPath) {
|
|
|
9758
10806
|
}
|
|
9759
10807
|
initOaDirectory(repoRoot);
|
|
9760
10808
|
const savedSettings = resolveSettings(repoRoot);
|
|
10809
|
+
let memoryDb = null;
|
|
10810
|
+
let taskMemoryStore = null;
|
|
10811
|
+
let failureStore = null;
|
|
10812
|
+
let contextStores;
|
|
10813
|
+
try {
|
|
10814
|
+
memoryDb = initDb(config.dbPath);
|
|
10815
|
+
taskMemoryStore = new TaskMemoryStore(memoryDb);
|
|
10816
|
+
failureStore = new FailureStore(memoryDb);
|
|
10817
|
+
contextStores = { taskMemoryStore, failureStore };
|
|
10818
|
+
} catch {
|
|
10819
|
+
}
|
|
9761
10820
|
if (savedSettings.model)
|
|
9762
10821
|
config = { ...config, model: savedSettings.model };
|
|
9763
10822
|
if (savedSettings.backendUrl)
|
|
@@ -9777,6 +10836,7 @@ async function startInteractive(config, repoPath) {
|
|
|
9777
10836
|
config = { ...config, dryRun: savedSettings.dryRun };
|
|
9778
10837
|
if (savedSettings.dbPath)
|
|
9779
10838
|
config = { ...config, dbPath: savedSettings.dbPath };
|
|
10839
|
+
let streamEnabled = savedSettings.stream ?? false;
|
|
9780
10840
|
if (!isResumed) {
|
|
9781
10841
|
const needsSetup = isFirstRun() || !await isModelAvailable(config);
|
|
9782
10842
|
if (needsSetup && config.backendType === "ollama") {
|
|
@@ -9820,6 +10880,7 @@ async function startInteractive(config, repoPath) {
|
|
|
9820
10880
|
`);
|
|
9821
10881
|
}
|
|
9822
10882
|
const voiceEngine = new VoiceEngine();
|
|
10883
|
+
const streamRenderer = new StreamRenderer();
|
|
9823
10884
|
if (savedSettings.voice) {
|
|
9824
10885
|
voiceEngine.toggle().catch(() => {
|
|
9825
10886
|
});
|
|
@@ -9886,6 +10947,12 @@ async function startInteractive(config, repoPath) {
|
|
|
9886
10947
|
if (carousel.isRunning)
|
|
9887
10948
|
carousel.stop();
|
|
9888
10949
|
voiceEngine.dispose();
|
|
10950
|
+
if (memoryDb) {
|
|
10951
|
+
try {
|
|
10952
|
+
closeDb(memoryDb);
|
|
10953
|
+
} catch {
|
|
10954
|
+
}
|
|
10955
|
+
}
|
|
9889
10956
|
rl.close();
|
|
9890
10957
|
},
|
|
9891
10958
|
async voiceToggle() {
|
|
@@ -9894,6 +10961,10 @@ async function startInteractive(config, repoPath) {
|
|
|
9894
10961
|
async voiceSetModel(id) {
|
|
9895
10962
|
return voiceEngine.setModel(id);
|
|
9896
10963
|
},
|
|
10964
|
+
streamToggle() {
|
|
10965
|
+
streamEnabled = !streamEnabled;
|
|
10966
|
+
return streamEnabled;
|
|
10967
|
+
},
|
|
9897
10968
|
saveSettings(settings) {
|
|
9898
10969
|
try {
|
|
9899
10970
|
saveProjectSettings(repoRoot, settings);
|
|
@@ -9970,12 +11041,39 @@ ${c2.dim("Goodbye!")}
|
|
|
9970
11041
|
}
|
|
9971
11042
|
renderUserMessage(isImage ? `[Image: ${cleanPath}]` : fullInput);
|
|
9972
11043
|
try {
|
|
9973
|
-
const task = startTask(fullInput, currentConfig, repoRoot, voiceEngine
|
|
11044
|
+
const task = startTask(fullInput, currentConfig, repoRoot, voiceEngine, {
|
|
11045
|
+
enabled: streamEnabled,
|
|
11046
|
+
renderer: streamRenderer
|
|
11047
|
+
}, {
|
|
11048
|
+
contextStores,
|
|
11049
|
+
taskMemoryStore: taskMemoryStore ?? void 0,
|
|
11050
|
+
failureStore: failureStore ?? void 0
|
|
11051
|
+
});
|
|
9974
11052
|
activeTask = task;
|
|
9975
11053
|
showPrompt();
|
|
9976
11054
|
await task.promise;
|
|
9977
11055
|
} catch (err) {
|
|
9978
|
-
|
|
11056
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
11057
|
+
renderError(errMsg);
|
|
11058
|
+
if (failureStore) {
|
|
11059
|
+
try {
|
|
11060
|
+
const { createHash: createHash2 } = await import("node:crypto");
|
|
11061
|
+
failureStore.insert({
|
|
11062
|
+
taskId: "",
|
|
11063
|
+
sessionId: `${Date.now()}`,
|
|
11064
|
+
repoRoot,
|
|
11065
|
+
failureType: "runtime-error",
|
|
11066
|
+
fingerprint: createHash2("sha256").update(errMsg.slice(0, 200)).digest("hex").slice(0, 16),
|
|
11067
|
+
filePath: null,
|
|
11068
|
+
errorMessage: errMsg.slice(0, 500),
|
|
11069
|
+
context: null,
|
|
11070
|
+
resolved: false,
|
|
11071
|
+
resolvedAt: null,
|
|
11072
|
+
notes: `Task: ${fullInput.slice(0, 200)}`
|
|
11073
|
+
});
|
|
11074
|
+
} catch {
|
|
11075
|
+
}
|
|
11076
|
+
}
|
|
9979
11077
|
} finally {
|
|
9980
11078
|
activeTask = null;
|
|
9981
11079
|
}
|
|
@@ -10062,10 +11160,12 @@ var init_interactive = __esm({
|
|
|
10062
11160
|
init_commands();
|
|
10063
11161
|
init_setup();
|
|
10064
11162
|
init_project_context();
|
|
11163
|
+
init_dist6();
|
|
10065
11164
|
init_oa_directory();
|
|
10066
11165
|
init_render();
|
|
10067
11166
|
init_carousel();
|
|
10068
11167
|
init_voice();
|
|
11168
|
+
init_stream_renderer();
|
|
10069
11169
|
taskManager = new BackgroundTaskManager();
|
|
10070
11170
|
}
|
|
10071
11171
|
});
|
|
@@ -10263,7 +11363,7 @@ var init_embeddings = __esm({
|
|
|
10263
11363
|
});
|
|
10264
11364
|
|
|
10265
11365
|
// packages/indexer/dist/index.js
|
|
10266
|
-
var
|
|
11366
|
+
var init_dist7 = __esm({
|
|
10267
11367
|
"packages/indexer/dist/index.js"() {
|
|
10268
11368
|
"use strict";
|
|
10269
11369
|
init_codebase_indexer();
|
|
@@ -10370,7 +11470,7 @@ async function indexRepoCommand(opts, _config) {
|
|
|
10370
11470
|
var init_index_repo = __esm({
|
|
10371
11471
|
"packages/cli/dist/commands/index-repo.js"() {
|
|
10372
11472
|
"use strict";
|
|
10373
|
-
|
|
11473
|
+
init_dist7();
|
|
10374
11474
|
init_spinner();
|
|
10375
11475
|
init_output();
|
|
10376
11476
|
}
|
|
@@ -10671,10 +11771,11 @@ var init_config3 = __esm({
|
|
|
10671
11771
|
verbose: "Verbose output (true/false)",
|
|
10672
11772
|
dbPath: "Path to SQLite memory database",
|
|
10673
11773
|
voice: "Enable TTS voice feedback (true/false)",
|
|
10674
|
-
voiceModel: "TTS voice model: glados, overwatch"
|
|
11774
|
+
voiceModel: "TTS voice model: glados, overwatch",
|
|
11775
|
+
stream: "Enable real-time token streaming with pastel syntax highlighting (true/false)"
|
|
10675
11776
|
};
|
|
10676
11777
|
INT_KEYS = /* @__PURE__ */ new Set(["maxRetries", "timeoutMs"]);
|
|
10677
|
-
BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice"]);
|
|
11778
|
+
BOOL_KEYS = /* @__PURE__ */ new Set(["dryRun", "verbose", "voice", "stream"]);
|
|
10678
11779
|
}
|
|
10679
11780
|
});
|
|
10680
11781
|
|