openhorse 0.2.12 → 0.2.15
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/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +814 -61
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/types.d.ts +7 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/core/checkpoint.d.ts +8 -0
- package/dist/core/checkpoint.d.ts.map +1 -1
- package/dist/core/checkpoint.js +69 -10
- package/dist/core/checkpoint.js.map +1 -1
- package/dist/core/cost-tracker.d.ts.map +1 -1
- package/dist/core/cost-tracker.js +11 -0
- package/dist/core/cost-tracker.js.map +1 -1
- package/dist/core/tool-artifacts.d.ts +10 -0
- package/dist/core/tool-artifacts.d.ts.map +1 -1
- package/dist/core/tool-artifacts.js +81 -3
- package/dist/core/tool-artifacts.js.map +1 -1
- package/dist/framework/index.d.ts +2 -2
- package/dist/framework/index.d.ts.map +1 -1
- package/dist/framework/index.js +5 -1
- package/dist/framework/index.js.map +1 -1
- package/dist/framework/query.d.ts +87 -2
- package/dist/framework/query.d.ts.map +1 -1
- package/dist/framework/query.js +281 -14
- package/dist/framework/query.js.map +1 -1
- package/dist/framework/tool-scheduler.d.ts +9 -0
- package/dist/framework/tool-scheduler.d.ts.map +1 -1
- package/dist/framework/tool-scheduler.js +34 -0
- package/dist/framework/tool-scheduler.js.map +1 -1
- package/dist/ink-ui/components/PromptInput.d.ts.map +1 -1
- package/dist/ink-ui/components/PromptInput.js +5 -3
- package/dist/ink-ui/components/PromptInput.js.map +1 -1
- package/dist/ink-ui/components/StatusLine.d.ts.map +1 -1
- package/dist/ink-ui/components/StatusLine.js +7 -1
- package/dist/ink-ui/components/StatusLine.js.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts +3 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.js +40 -93
- package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
- package/dist/print-ui/launch.d.ts.map +1 -1
- package/dist/print-ui/launch.js +1 -0
- package/dist/print-ui/launch.js.map +1 -1
- package/dist/runtime/agent-runtime-controller.d.ts +5 -0
- package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-controller.js +15 -3
- package/dist/runtime/agent-runtime-controller.js.map +1 -1
- package/dist/runtime/agent-runtime-protocol.d.ts +13 -1
- package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-protocol.js +24 -0
- package/dist/runtime/agent-runtime-protocol.js.map +1 -1
- package/dist/runtime/agent-status.d.ts +5 -0
- package/dist/runtime/agent-status.d.ts.map +1 -1
- package/dist/runtime/agent-status.js +28 -2
- package/dist/runtime/agent-status.js.map +1 -1
- package/dist/runtime/chat-controller.d.ts +7 -0
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +1084 -47
- package/dist/runtime/chat-controller.js.map +1 -1
- package/dist/runtime/loop-budget.d.ts +5 -0
- package/dist/runtime/loop-budget.d.ts.map +1 -0
- package/dist/runtime/loop-budget.js +77 -0
- package/dist/runtime/loop-budget.js.map +1 -0
- package/dist/runtime/turn-controller.d.ts +4 -0
- package/dist/runtime/turn-controller.d.ts.map +1 -1
- package/dist/runtime/turn-controller.js +8 -0
- package/dist/runtime/turn-controller.js.map +1 -1
- package/dist/runtime/ui-events.d.ts +41 -1
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/runtime/ui-view-model.d.ts +287 -0
- package/dist/runtime/ui-view-model.d.ts.map +1 -0
- package/dist/runtime/ui-view-model.js +642 -0
- package/dist/runtime/ui-view-model.js.map +1 -0
- package/dist/services/config-dir.d.ts +2 -0
- package/dist/services/config-dir.d.ts.map +1 -1
- package/dist/services/config-dir.js +5 -0
- package/dist/services/config-dir.js.map +1 -1
- package/dist/services/config.d.ts +4 -2
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +19 -0
- package/dist/services/config.js.map +1 -1
- package/dist/services/file-glob.d.ts +6 -2
- package/dist/services/file-glob.d.ts.map +1 -1
- package/dist/services/file-glob.js +10 -4
- package/dist/services/file-glob.js.map +1 -1
- package/dist/services/global-config.d.ts +17 -0
- package/dist/services/global-config.d.ts.map +1 -1
- package/dist/services/global-config.js.map +1 -1
- package/dist/services/llm.d.ts +16 -0
- package/dist/services/llm.d.ts.map +1 -1
- package/dist/services/llm.js +56 -11
- package/dist/services/llm.js.map +1 -1
- package/dist/services/model-catalog.d.ts +14 -0
- package/dist/services/model-catalog.d.ts.map +1 -0
- package/dist/services/model-catalog.js +73 -0
- package/dist/services/model-catalog.js.map +1 -0
- package/dist/services/model-context.d.ts.map +1 -1
- package/dist/services/model-context.js +3 -0
- package/dist/services/model-context.js.map +1 -1
- package/dist/services/redaction.d.ts +2 -0
- package/dist/services/redaction.d.ts.map +1 -0
- package/dist/services/redaction.js +27 -0
- package/dist/services/redaction.js.map +1 -0
- package/dist/services/session-index.d.ts.map +1 -1
- package/dist/services/session-index.js +7 -3
- package/dist/services/session-index.js.map +1 -1
- package/dist/services/session-storage.d.ts +99 -0
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js +153 -2
- package/dist/services/session-storage.js.map +1 -1
- package/dist/services/verification-profile.d.ts +51 -0
- package/dist/services/verification-profile.d.ts.map +1 -0
- package/dist/services/verification-profile.js +306 -0
- package/dist/services/verification-profile.js.map +1 -0
- package/dist/services/workspace-state.d.ts +25 -0
- package/dist/services/workspace-state.d.ts.map +1 -0
- package/dist/services/workspace-state.js +115 -0
- package/dist/services/workspace-state.js.map +1 -0
- package/dist/terminal-ui/completion.d.ts.map +1 -1
- package/dist/terminal-ui/completion.js +9 -6
- package/dist/terminal-ui/completion.js.map +1 -1
- package/dist/terminal-ui/launch.d.ts +15 -1
- package/dist/terminal-ui/launch.d.ts.map +1 -1
- package/dist/terminal-ui/launch.js +300 -56
- package/dist/terminal-ui/launch.js.map +1 -1
- package/dist/terminal-ui/raw-editor.d.ts +4 -0
- package/dist/terminal-ui/raw-editor.d.ts.map +1 -1
- package/dist/terminal-ui/raw-editor.js +29 -0
- package/dist/terminal-ui/raw-editor.js.map +1 -1
- package/dist/tui-ui/launch.d.ts.map +1 -1
- package/dist/tui-ui/launch.js +1 -0
- package/dist/tui-ui/launch.js.map +1 -1
- package/dist/tui-ui/layout.d.ts.map +1 -1
- package/dist/tui-ui/layout.js +21 -28
- package/dist/tui-ui/layout.js.map +1 -1
- package/dist/tui-ui/pickers.d.ts.map +1 -1
- package/dist/tui-ui/pickers.js +21 -62
- package/dist/tui-ui/pickers.js.map +1 -1
- package/dist/tui-ui/state.d.ts +2 -4
- package/dist/tui-ui/state.d.ts.map +1 -1
- package/dist/tui-ui/state.js +13 -11
- package/dist/tui-ui/state.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.InkChatController = exports.AgentChatController = void 0;
|
|
4
37
|
exports.sessionMessagesToTranscriptEntries = sessionMessagesToTranscriptEntries;
|
|
@@ -8,17 +41,29 @@ exports.loadSessionIntoRuntime = loadSessionIntoRuntime;
|
|
|
8
41
|
exports.closeSession = closeSession;
|
|
9
42
|
const commands_1 = require("../commands");
|
|
10
43
|
const parser_1 = require("../commands/parser");
|
|
44
|
+
const path = __importStar(require("path"));
|
|
11
45
|
const session_storage_1 = require("../services/session-storage");
|
|
12
46
|
const config_1 = require("../services/config");
|
|
13
47
|
const framework_1 = require("../framework");
|
|
14
48
|
const harness_1 = require("../harness");
|
|
15
49
|
const tools_1 = require("../tools");
|
|
50
|
+
const tool_serializer_1 = require("../framework/tool-serializer");
|
|
51
|
+
const tool_artifacts_1 = require("../core/tool-artifacts");
|
|
52
|
+
const checkpoint_1 = require("../core/checkpoint");
|
|
16
53
|
const skills_1 = require("../skills");
|
|
17
54
|
const file_context_1 = require("../services/file-context");
|
|
18
55
|
const prompt_context_1 = require("../services/prompt-context");
|
|
56
|
+
const format_1 = require("../services/format");
|
|
57
|
+
const workspace_state_1 = require("../services/workspace-state");
|
|
58
|
+
const verification_profile_1 = require("../services/verification-profile");
|
|
19
59
|
const ui_events_1 = require("./ui-events");
|
|
60
|
+
const ui_view_model_1 = require("./ui-view-model");
|
|
20
61
|
const agent_status_1 = require("./agent-status");
|
|
62
|
+
const loop_budget_1 = require("./loop-budget");
|
|
21
63
|
const ANSI_PATTERN = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
64
|
+
const LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES = 2048;
|
|
65
|
+
const TRACE_ARGS_ARTIFACT_THRESHOLD_BYTES = 160;
|
|
66
|
+
const TOOL_TRANSCRIPT_ARG_BUDGET = 512;
|
|
22
67
|
function stripAnsi(text) {
|
|
23
68
|
return text.replace(ANSI_PATTERN, '');
|
|
24
69
|
}
|
|
@@ -30,6 +75,29 @@ function isAbortError(error, abortSignal) {
|
|
|
30
75
|
}
|
|
31
76
|
return false;
|
|
32
77
|
}
|
|
78
|
+
function errorLayerForChatError(error) {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
const lower = message.toLowerCase();
|
|
81
|
+
if (/NotEnoughCvError|code:\s*11210|provider|quota|rate.?limit|timeout|connection/i.test(message)) {
|
|
82
|
+
return 'provider';
|
|
83
|
+
}
|
|
84
|
+
if (/tool|command|exec_command|write_file|edit_file/i.test(message)) {
|
|
85
|
+
return 'tool';
|
|
86
|
+
}
|
|
87
|
+
if (/\bmcp\b/i.test(message))
|
|
88
|
+
return 'mcp';
|
|
89
|
+
if (/\b(session|resume|compact|harness)\b/i.test(message))
|
|
90
|
+
return 'session';
|
|
91
|
+
if (/\b(skill|skills)\b/i.test(message))
|
|
92
|
+
return 'skills';
|
|
93
|
+
if (/\b(memory|vector store|recall|forget)\b/i.test(message))
|
|
94
|
+
return 'memory';
|
|
95
|
+
if (/\b(renderer|terminal|tty|prompt|resize|scrollback)\b/i.test(message))
|
|
96
|
+
return 'renderer';
|
|
97
|
+
if (lower.includes('abort') || lower.includes('interrupted'))
|
|
98
|
+
return 'runtime';
|
|
99
|
+
return 'unknown';
|
|
100
|
+
}
|
|
33
101
|
function formatChatError(error) {
|
|
34
102
|
const message = error instanceof Error ? error.message : String(error);
|
|
35
103
|
if (/NotEnoughCvError|code:\s*11210/i.test(message)) {
|
|
@@ -64,34 +132,323 @@ function compactToolArgs(args, maxLength = 160) {
|
|
|
64
132
|
}
|
|
65
133
|
return '';
|
|
66
134
|
}
|
|
67
|
-
function
|
|
135
|
+
function fullToolArgsForTrace(name, args) {
|
|
68
136
|
if (name === 'exec_command' && typeof args.command === 'string') {
|
|
69
|
-
return
|
|
137
|
+
return `$ ${args.command}`;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
return JSON.stringify(args, null, 2);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return compactToolArgs(args, 2048);
|
|
70
144
|
}
|
|
71
|
-
const detail = compactToolArgs(args);
|
|
72
|
-
return `Running ${name}${detail ? ` ${detail}` : ''}`;
|
|
73
145
|
}
|
|
74
|
-
function
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
146
|
+
function buildTraceArgsDetails(projectPath, name, args) {
|
|
147
|
+
const argsSummary = compactToolArgs(args);
|
|
148
|
+
const fullArgs = (0, session_storage_1.redactTraceText)(fullToolArgsForTrace(name, args)).trim();
|
|
149
|
+
const argsBytes = byteLength(fullArgs);
|
|
150
|
+
if (!projectPath
|
|
151
|
+
|| !fullArgs
|
|
152
|
+
|| fullArgs === (0, session_storage_1.redactTraceText)(argsSummary)
|
|
153
|
+
|| argsBytes <= TRACE_ARGS_ARTIFACT_THRESHOLD_BYTES) {
|
|
154
|
+
return { argsSummary };
|
|
155
|
+
}
|
|
156
|
+
const artifact = (0, tool_artifacts_1.storeArtifact)(projectPath, `${name}-args`, fullArgs, argsBytes);
|
|
157
|
+
return artifact
|
|
158
|
+
? { argsSummary, argsArtifactId: artifact.id, argsBytes }
|
|
159
|
+
: { argsSummary };
|
|
78
160
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
161
|
+
function parseToolCallArgsForRuntime(toolCall) {
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(toolCall.function.arguments || '{}');
|
|
164
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
165
|
+
? parsed
|
|
166
|
+
: null;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function resolveProjectScopedPath(cwd, filePath) {
|
|
173
|
+
const absolute = path.resolve(cwd, filePath);
|
|
174
|
+
const relative = path.relative(cwd, absolute);
|
|
175
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
176
|
+
return null;
|
|
177
|
+
return absolute;
|
|
178
|
+
}
|
|
179
|
+
function checkpointTargetsFromToolCalls(cwd, toolCalls) {
|
|
180
|
+
const targets = new Set();
|
|
181
|
+
for (const toolCall of toolCalls) {
|
|
182
|
+
const name = toolCall.function.name;
|
|
183
|
+
if (name !== 'write_file' && name !== 'edit_file')
|
|
184
|
+
continue;
|
|
185
|
+
const args = parseToolCallArgsForRuntime(toolCall);
|
|
186
|
+
if (!args || typeof args.path !== 'string')
|
|
187
|
+
continue;
|
|
188
|
+
if (name === 'edit_file' && args.preview === true)
|
|
189
|
+
continue;
|
|
190
|
+
const target = resolveProjectScopedPath(cwd, args.path);
|
|
191
|
+
if (target)
|
|
192
|
+
targets.add(target);
|
|
193
|
+
}
|
|
194
|
+
return Array.from(targets);
|
|
195
|
+
}
|
|
196
|
+
function createPreToolCheckpoint(events, sessionId, turnId, checkpointId, cwd, toolCalls) {
|
|
197
|
+
const targets = checkpointTargetsFromToolCalls(cwd, toolCalls);
|
|
198
|
+
if (targets.length === 0)
|
|
199
|
+
return { created: false, targetCount: 0, risky: false };
|
|
200
|
+
const risky = (0, checkpoint_1.shouldCreateMultiFileCheckpoint)(targets.length);
|
|
201
|
+
const checkpoint = (0, checkpoint_1.createCheckpoint)(cwd, checkpointId, targets);
|
|
202
|
+
if (!sessionId)
|
|
203
|
+
return { created: true, targetCount: targets.length, risky };
|
|
204
|
+
const relativeTargets = targets.map(target => path.relative(cwd, target));
|
|
205
|
+
recordTraceEvent(events, sessionId, {
|
|
206
|
+
turnId,
|
|
207
|
+
type: 'checkpoint',
|
|
208
|
+
checkpointId,
|
|
209
|
+
checkpointFileCount: checkpoint?.files.length ?? 0,
|
|
210
|
+
checkpointFiles: checkpoint?.files.map(file => file.path) ?? [],
|
|
211
|
+
workspaceFiles: relativeTargets,
|
|
212
|
+
note: checkpoint
|
|
213
|
+
? (risky ? 'risky_multi_file_checkpoint' : 'pre_edit_checkpoint')
|
|
214
|
+
: 'pre_edit_checkpoint_skipped',
|
|
215
|
+
});
|
|
216
|
+
return { created: true, targetCount: targets.length, risky };
|
|
217
|
+
}
|
|
218
|
+
function byteLength(text) {
|
|
219
|
+
return Buffer.byteLength(text, 'utf8');
|
|
220
|
+
}
|
|
221
|
+
function traceTurnId(turnId) {
|
|
222
|
+
return turnId == null ? `turn-${Date.now()}` : String(turnId);
|
|
223
|
+
}
|
|
224
|
+
function compactTraceError(error) {
|
|
225
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
226
|
+
return compactMiddle(message, 240);
|
|
227
|
+
}
|
|
228
|
+
function getLastRequestDiagnostics(llm) {
|
|
229
|
+
if (!llm)
|
|
230
|
+
return undefined;
|
|
231
|
+
const reader = llm.getLastRequestDiagnostics;
|
|
232
|
+
return typeof reader === 'function' ? reader.call(llm) : undefined;
|
|
233
|
+
}
|
|
234
|
+
function compactPathList(paths, maxItems = 40) {
|
|
235
|
+
return paths.slice(0, maxItems);
|
|
236
|
+
}
|
|
237
|
+
function formatWorkspaceFileForTrace(file) {
|
|
238
|
+
const metadata = [
|
|
239
|
+
typeof file.sizeBytes === 'number' ? `${file.sizeBytes}B` : '',
|
|
240
|
+
typeof file.mtimeMs === 'number' ? `mtime=${file.mtimeMs}` : '',
|
|
241
|
+
].filter(Boolean).join(' ');
|
|
242
|
+
return `${file.status} ${file.path}${metadata ? ` (${metadata})` : ''}`;
|
|
243
|
+
}
|
|
244
|
+
function appendWorkspaceSnapshotTrace(events, sessionId, turnId, phase, snapshot) {
|
|
245
|
+
if (!sessionId)
|
|
246
|
+
return;
|
|
247
|
+
recordTraceEvent(events, sessionId, {
|
|
248
|
+
turnId,
|
|
249
|
+
type: 'workspace_snapshot',
|
|
250
|
+
workspacePhase: phase,
|
|
251
|
+
workspaceGitAvailable: snapshot.gitAvailable,
|
|
252
|
+
workspaceDirty: snapshot.dirty,
|
|
253
|
+
workspaceBranch: snapshot.branch,
|
|
254
|
+
workspaceFileCount: snapshot.fileCount,
|
|
255
|
+
workspaceFiles: compactPathList(snapshot.files.map(formatWorkspaceFileForTrace)),
|
|
256
|
+
error: snapshot.error ? compactMiddle(snapshot.error, 240) : undefined,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function appendWorkspaceDeltaTrace(events, sessionId, turnId, before, after) {
|
|
260
|
+
const delta = (0, workspace_state_1.diffWorkspaceSnapshots)(before, after);
|
|
261
|
+
if (sessionId) {
|
|
262
|
+
recordTraceEvent(events, sessionId, {
|
|
263
|
+
turnId,
|
|
264
|
+
type: 'workspace_delta',
|
|
265
|
+
workspaceFileCount: delta.filesAfterTurn.length,
|
|
266
|
+
workspaceFiles: compactPathList(delta.filesAfterTurn),
|
|
267
|
+
workspaceNewByTurn: compactPathList(delta.newFilesByTurn),
|
|
268
|
+
workspaceChangedByTurn: compactPathList(delta.changedByTurn),
|
|
269
|
+
workspaceModifiedPreExistingByTurn: compactPathList(delta.modifiedPreExistingByTurn),
|
|
270
|
+
workspaceResolvedByTurn: compactPathList(delta.resolvedByTurn),
|
|
271
|
+
note: `pre_existing=${delta.preExistingFiles.length}`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return delta;
|
|
275
|
+
}
|
|
276
|
+
function workspaceDeltaHasTurnChanges(delta) {
|
|
277
|
+
return delta.newFilesByTurn.length > 0
|
|
278
|
+
|| delta.changedByTurn.length > 0
|
|
279
|
+
|| delta.resolvedByTurn.length > 0;
|
|
280
|
+
}
|
|
281
|
+
function formatFailureRecoveryNotice(turnId, delta, checkpointIds) {
|
|
282
|
+
const files = compactPathList([
|
|
283
|
+
...delta.newFilesByTurn,
|
|
284
|
+
...delta.changedByTurn,
|
|
285
|
+
...delta.resolvedByTurn,
|
|
286
|
+
], 8);
|
|
287
|
+
const fileText = files.length > 0
|
|
288
|
+
? files.join(', ')
|
|
289
|
+
: 'workspace changes recorded';
|
|
290
|
+
const checkpointText = checkpointIds.length > 0
|
|
291
|
+
? ` Checkpoints: ${checkpointIds.join(', ')}. Preview rollback with /checkpoint restore <id>; restore each listed checkpoint if multiple.`
|
|
292
|
+
: '';
|
|
293
|
+
return `Turn failed after modifying files: ${fileText}. Inspect /trace ${turnId}.${checkpointText}`;
|
|
294
|
+
}
|
|
295
|
+
function appendVerificationProfileTrace(events, sessionId, turnId, profile) {
|
|
296
|
+
if (!sessionId || profile.changedFiles.length === 0)
|
|
297
|
+
return;
|
|
298
|
+
recordTraceEvent(events, sessionId, {
|
|
299
|
+
turnId,
|
|
300
|
+
type: 'verification_profile',
|
|
301
|
+
verificationProfile: profile.profile,
|
|
302
|
+
verificationRequired: profile.required,
|
|
303
|
+
verificationRisky: (0, verification_profile_1.isRiskyEdit)(profile.changedFiles),
|
|
304
|
+
verificationCommands: compactPathList(profile.commands, 8),
|
|
305
|
+
verificationChangedFiles: compactPathList(profile.changedFiles),
|
|
306
|
+
note: profile.reason,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
function appendVerificationResultTrace(events, sessionId, turnId, result) {
|
|
310
|
+
if (!sessionId)
|
|
311
|
+
return;
|
|
312
|
+
recordTraceEvent(events, sessionId, {
|
|
313
|
+
turnId,
|
|
314
|
+
type: 'verification_result',
|
|
315
|
+
verificationCommand: result.command,
|
|
316
|
+
verificationPassed: result.success,
|
|
317
|
+
outputBytes: result.outputBytes,
|
|
318
|
+
error: result.error ? compactMiddle(result.error, 240) : undefined,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function appendVerificationSummaryTrace(events, sessionId, turnId, summary, changedFiles) {
|
|
322
|
+
if (!sessionId || changedFiles.length === 0)
|
|
323
|
+
return;
|
|
324
|
+
recordTraceEvent(events, sessionId, {
|
|
325
|
+
turnId,
|
|
326
|
+
type: 'verification_summary',
|
|
327
|
+
verificationProfile: summary.profile,
|
|
328
|
+
verificationRequired: summary.required,
|
|
329
|
+
verificationCommands: compactPathList(summary.commandsRun, 12),
|
|
330
|
+
verificationPassedCommands: compactPathList(summary.passedCommands, 12),
|
|
331
|
+
verificationFailedCommands: compactPathList(summary.failedCommands, 12),
|
|
332
|
+
verificationMissingCommands: compactPathList(summary.missingCommands, 12),
|
|
333
|
+
verificationChangedFiles: compactPathList(changedFiles),
|
|
334
|
+
verificationClaimAllowed: summary.claimAllowed,
|
|
335
|
+
note: summary.skippedReason,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function compactVerificationCommands(commands, maxItems = 12) {
|
|
339
|
+
return commands.slice(0, maxItems).map(session_storage_1.redactTraceText);
|
|
340
|
+
}
|
|
341
|
+
function withVerificationLoopStats(stats, summary) {
|
|
342
|
+
return {
|
|
343
|
+
...stats,
|
|
344
|
+
verificationProfile: summary.profile,
|
|
345
|
+
verificationRequired: summary.required,
|
|
346
|
+
verificationClaimAllowed: summary.claimAllowed,
|
|
347
|
+
verificationPassedCommands: compactVerificationCommands(summary.passedCommands),
|
|
348
|
+
verificationFailedCommands: compactVerificationCommands(summary.failedCommands),
|
|
349
|
+
verificationMissingCommands: compactVerificationCommands(summary.missingCommands),
|
|
350
|
+
verificationSkippedReason: summary.skippedReason ? (0, session_storage_1.redactTraceText)(summary.skippedReason) : undefined,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function shouldRecordVerificationLoopStats(profile, summary) {
|
|
354
|
+
return profile.changedFiles.length > 0
|
|
355
|
+
|| summary.commandsRun.length > 0
|
|
356
|
+
|| summary.passedCommands.length > 0
|
|
357
|
+
|| summary.failedCommands.length > 0
|
|
358
|
+
|| summary.missingCommands.length > 0;
|
|
359
|
+
}
|
|
360
|
+
function appendPostWorkspaceTrace(events, sessionId, turnId, cwd, before, verificationResults = []) {
|
|
361
|
+
const postWorkspace = (0, workspace_state_1.captureWorkspaceSnapshot)(cwd);
|
|
362
|
+
appendWorkspaceSnapshotTrace(events, sessionId, turnId, 'post_turn', postWorkspace);
|
|
363
|
+
const delta = appendWorkspaceDeltaTrace(events, sessionId, turnId, before, postWorkspace);
|
|
364
|
+
const profile = (0, verification_profile_1.selectVerificationProfile)(cwd, delta.changedByTurn);
|
|
365
|
+
const summary = (0, verification_profile_1.summarizeVerificationState)(profile, verificationResults);
|
|
366
|
+
appendVerificationProfileTrace(events, sessionId, turnId, profile);
|
|
367
|
+
appendVerificationSummaryTrace(events, sessionId, turnId, summary, profile.changedFiles);
|
|
368
|
+
return { delta, profile, summary };
|
|
369
|
+
}
|
|
370
|
+
function appendAssistantNotice(messages, notice) {
|
|
371
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
372
|
+
const message = messages[index];
|
|
373
|
+
if (message.role === 'assistant' && !message.tool_calls) {
|
|
374
|
+
message.content = message.content ? `${message.content}\n\n${notice}` : notice;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
84
377
|
}
|
|
85
|
-
|
|
86
|
-
|
|
378
|
+
messages.push({
|
|
379
|
+
role: 'assistant',
|
|
380
|
+
content: notice,
|
|
381
|
+
timestamp: Date.now(),
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function recordTraceEvent(events, sessionId, event) {
|
|
385
|
+
if (!sessionId)
|
|
386
|
+
return null;
|
|
387
|
+
const traceEvent = (0, session_storage_1.appendSessionTraceEvent)(sessionId, event);
|
|
388
|
+
if (traceEvent) {
|
|
389
|
+
events.traceEventRecorded?.(traceEvent);
|
|
87
390
|
}
|
|
88
|
-
|
|
89
|
-
|
|
391
|
+
return traceEvent;
|
|
392
|
+
}
|
|
393
|
+
function recordProviderTraceEvents(events, sessionId, turnId, stats) {
|
|
394
|
+
if ((stats.providerRetryCount ?? 0) > 0) {
|
|
395
|
+
recordTraceEvent(events, sessionId, {
|
|
396
|
+
turnId,
|
|
397
|
+
type: 'provider_retry',
|
|
398
|
+
providerRetryCount: stats.providerRetryCount,
|
|
399
|
+
providerRetryDelayMs: stats.providerRetryDelayMs,
|
|
400
|
+
providerRetryErrorTypes: stats.providerRetryErrorTypes,
|
|
401
|
+
providerLastRetryErrorType: stats.providerLastRetryErrorType,
|
|
402
|
+
providerLastRetryStatus: stats.providerLastRetryStatus,
|
|
403
|
+
providerFinalModel: stats.providerFinalModel,
|
|
404
|
+
providerUsingFallback: stats.providerUsingFallback,
|
|
405
|
+
});
|
|
90
406
|
}
|
|
91
|
-
if (
|
|
92
|
-
|
|
407
|
+
if ((stats.providerFallbackCount ?? 0) > 0 || stats.providerUsingFallback) {
|
|
408
|
+
recordTraceEvent(events, sessionId, {
|
|
409
|
+
turnId,
|
|
410
|
+
type: 'provider_fallback',
|
|
411
|
+
providerFallbackCount: stats.providerFallbackCount,
|
|
412
|
+
providerFallbackFromModel: stats.providerFallbackFromModel,
|
|
413
|
+
providerFallbackToModel: stats.providerFallbackToModel,
|
|
414
|
+
providerFinalModel: stats.providerFinalModel,
|
|
415
|
+
providerUsingFallback: stats.providerUsingFallback,
|
|
416
|
+
});
|
|
93
417
|
}
|
|
94
|
-
|
|
418
|
+
}
|
|
419
|
+
function toHarnessDiagnostics(state) {
|
|
420
|
+
const stats = state.promptAssemblyStats;
|
|
421
|
+
const redactOptional = (value) => typeof value === 'string' ? (0, session_storage_1.redactTraceText)(value) : undefined;
|
|
422
|
+
const redactList = (values) => values?.slice(0, 6).map(session_storage_1.redactTraceText);
|
|
423
|
+
return {
|
|
424
|
+
taskEpoch: state.taskEpoch,
|
|
425
|
+
rootObjective: redactOptional(state.rootObjective ?? state.contract?.objective),
|
|
426
|
+
activeInstruction: redactOptional(state.activeInstruction ?? state.contract?.userIntent),
|
|
427
|
+
openQuestions: redactList(state.openQuestions),
|
|
428
|
+
diagnostics: redactList(state.diagnostics?.slice(-6)),
|
|
429
|
+
ledgerSize: state.ledger?.length ?? 0,
|
|
430
|
+
evidenceSize: state.evidenceIndex?.length ?? 0,
|
|
431
|
+
turnSummaryCount: state.turnSummaries?.length ?? 0,
|
|
432
|
+
promptAssembly: stats
|
|
433
|
+
? {
|
|
434
|
+
modelId: stats.modelId,
|
|
435
|
+
estimatedTokens: stats.estimatedTokens,
|
|
436
|
+
budgetTokens: stats.budgetTokens,
|
|
437
|
+
sections: stats.sections.slice(0, 12),
|
|
438
|
+
includedEvidence: stats.includedEvidence.length,
|
|
439
|
+
omittedEvidence: stats.omittedEvidence.length,
|
|
440
|
+
}
|
|
441
|
+
: undefined,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function emitHarnessDiagnostics(events, state) {
|
|
445
|
+
events.harnessDiagnosticsUpdated?.(toHarnessDiagnostics(state));
|
|
446
|
+
}
|
|
447
|
+
function toolStartContent(event) {
|
|
448
|
+
return (0, ui_view_model_1.formatToolActivityTranscript)((0, ui_view_model_1.toolActivityFromStarted)(event, compactToolArgs(event.args, TOOL_TRANSCRIPT_ARG_BUDGET)));
|
|
449
|
+
}
|
|
450
|
+
function toolFinishContent(event) {
|
|
451
|
+
return (0, ui_view_model_1.formatToolActivityTranscript)((0, ui_view_model_1.toolActivityFromFinished)(event, compactToolArgs(event.args, TOOL_TRANSCRIPT_ARG_BUDGET)));
|
|
95
452
|
}
|
|
96
453
|
function isSyntheticCompactContext(content) {
|
|
97
454
|
return content.startsWith('[OpenHorse Context State v2]')
|
|
@@ -254,25 +611,120 @@ function createAssistantStreamPresenter(events, abortSignal) {
|
|
|
254
611
|
activeSegmentText = content;
|
|
255
612
|
ensureLiveEntry();
|
|
256
613
|
},
|
|
614
|
+
replaceMessage(content) {
|
|
615
|
+
if (abortSignal?.aborted || !content)
|
|
616
|
+
return;
|
|
617
|
+
activeSegmentText = content;
|
|
618
|
+
ensureLiveEntry();
|
|
619
|
+
},
|
|
257
620
|
};
|
|
258
621
|
}
|
|
622
|
+
class LocalFastPathBlockedError extends Error {
|
|
623
|
+
constructor(message) {
|
|
624
|
+
super(message);
|
|
625
|
+
this.name = 'LocalFastPathBlockedError';
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function parseLocalFastPath(input) {
|
|
629
|
+
const text = input.trim();
|
|
630
|
+
if (/^git\s+status$/i.test(text)) {
|
|
631
|
+
return { tool: 'git_status', args: {}, label: 'git status' };
|
|
632
|
+
}
|
|
633
|
+
const readMatch = /^(?:read|读取)\s+(.+)$/i.exec(text);
|
|
634
|
+
const readTarget = readMatch?.[1]?.trim();
|
|
635
|
+
const looksLikePath = Boolean(readTarget)
|
|
636
|
+
&& !/\s/.test(readTarget)
|
|
637
|
+
&& (/[/\\.]/.test(readTarget) || readTarget.startsWith('~'));
|
|
638
|
+
if (readTarget && looksLikePath) {
|
|
639
|
+
return { tool: 'read_file', args: { path: readTarget }, label: `read ${readTarget}` };
|
|
640
|
+
}
|
|
641
|
+
const grepMatch = /^(?:grep|搜索)\s+(.+)$/i.exec(text);
|
|
642
|
+
if (grepMatch?.[1]?.trim()) {
|
|
643
|
+
return { tool: 'grep', args: { pattern: grepMatch[1].trim() }, label: `grep ${grepMatch[1].trim()}` };
|
|
644
|
+
}
|
|
645
|
+
const runTestMatch = /^(?:run\s+test|运行测试)\s*[::]\s*(.+)$/i.exec(text);
|
|
646
|
+
if (runTestMatch?.[1]?.trim()) {
|
|
647
|
+
return { tool: 'exec_command', args: { command: runTestMatch[1].trim() }, label: `run test: ${runTestMatch[1].trim()}` };
|
|
648
|
+
}
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
function formatLocalFastPathAssistantContent(action, rawResult, projectPath) {
|
|
652
|
+
const envelope = (0, tool_serializer_1.parseToolResultEnvelope)(rawResult);
|
|
653
|
+
const rawOutput = typeof envelope.output === 'string' ? envelope.output : '';
|
|
654
|
+
const output = rawOutput.trim();
|
|
655
|
+
const summary = envelope.summary || `${action.tool} ${envelope.success ? 'completed' : 'failed'}`;
|
|
656
|
+
const lines = [
|
|
657
|
+
envelope.success
|
|
658
|
+
? `Local fast path completed ${action.label}.`
|
|
659
|
+
: `Local fast path failed ${action.label}.`,
|
|
660
|
+
'',
|
|
661
|
+
summary,
|
|
662
|
+
];
|
|
663
|
+
if (!envelope.success && envelope.error) {
|
|
664
|
+
lines.push(`Error: ${envelope.error}`);
|
|
665
|
+
}
|
|
666
|
+
if (!output) {
|
|
667
|
+
return { content: lines.join('\n'), artifactRef: envelope.artifactRef };
|
|
668
|
+
}
|
|
669
|
+
let artifactRef = envelope.artifactRef;
|
|
670
|
+
let preview = output;
|
|
671
|
+
const outputBytes = envelope.outputBytes ?? byteLength(rawOutput);
|
|
672
|
+
if (byteLength(rawOutput) > LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES) {
|
|
673
|
+
if (!artifactRef) {
|
|
674
|
+
const artifact = (0, tool_artifacts_1.storeArtifact)(projectPath, action.tool, rawOutput, outputBytes);
|
|
675
|
+
artifactRef = artifact ? { id: artifact.id, outputBytes: artifact.outputBytes } : undefined;
|
|
676
|
+
}
|
|
677
|
+
preview = (0, tool_artifacts_1.truncateForContext)(output, LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES);
|
|
678
|
+
}
|
|
679
|
+
if (artifactRef) {
|
|
680
|
+
lines.push('', `Full output: /artifacts show ${artifactRef.id} --full (${(0, format_1.formatBytes)(artifactRef.outputBytes)})`);
|
|
681
|
+
}
|
|
682
|
+
lines.push('', 'Preview:', preview);
|
|
683
|
+
return { content: lines.join('\n'), artifactRef };
|
|
684
|
+
}
|
|
259
685
|
function createToolEventPresenter(events) {
|
|
260
686
|
const runningToolEntries = new Map();
|
|
261
687
|
return {
|
|
262
688
|
start(event) {
|
|
689
|
+
const entryId = events.append({
|
|
690
|
+
role: 'tool',
|
|
691
|
+
title: 'tool',
|
|
692
|
+
content: toolStartContent(event),
|
|
693
|
+
});
|
|
694
|
+
runningToolEntries.set(event.callId, {
|
|
695
|
+
entryId,
|
|
696
|
+
name: event.name,
|
|
697
|
+
args: event.args,
|
|
698
|
+
batchCount: event.batchCount,
|
|
699
|
+
batchIndex: event.batchIndex,
|
|
700
|
+
});
|
|
263
701
|
events.toolStarted?.({
|
|
264
702
|
callId: event.callId,
|
|
265
703
|
name: event.name,
|
|
266
704
|
args: event.args,
|
|
705
|
+
batchCount: event.batchCount,
|
|
706
|
+
batchIndex: event.batchIndex,
|
|
267
707
|
});
|
|
268
|
-
const entryId = events.append({
|
|
269
|
-
role: 'tool',
|
|
270
|
-
title: 'tool',
|
|
271
|
-
content: toolStartContent(event.name, event.args),
|
|
272
|
-
});
|
|
273
|
-
runningToolEntries.set(event.callId, entryId);
|
|
274
708
|
},
|
|
275
709
|
finish(event) {
|
|
710
|
+
const content = toolFinishContent(event);
|
|
711
|
+
const stored = runningToolEntries.get(event.callId);
|
|
712
|
+
if (stored) {
|
|
713
|
+
events.finalize(stored.entryId, {
|
|
714
|
+
role: event.success ? 'tool' : 'error',
|
|
715
|
+
title: 'tool',
|
|
716
|
+
content,
|
|
717
|
+
});
|
|
718
|
+
runningToolEntries.delete(event.callId);
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
const entryId = events.append({
|
|
722
|
+
role: event.success ? 'tool' : 'error',
|
|
723
|
+
title: 'tool',
|
|
724
|
+
content,
|
|
725
|
+
});
|
|
726
|
+
events.finalize(entryId);
|
|
727
|
+
}
|
|
276
728
|
events.toolFinished?.({
|
|
277
729
|
callId: event.callId,
|
|
278
730
|
name: event.name,
|
|
@@ -283,24 +735,30 @@ function createToolEventPresenter(events) {
|
|
|
283
735
|
error: event.error,
|
|
284
736
|
outputBytes: event.outputBytes,
|
|
285
737
|
artifactRef: event.artifactRef,
|
|
738
|
+
batchCount: event.batchCount,
|
|
739
|
+
batchIndex: event.batchIndex,
|
|
286
740
|
});
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
events.finalize(
|
|
291
|
-
role:
|
|
741
|
+
},
|
|
742
|
+
finalizePendingAsSkipped(reason = 'permission denied') {
|
|
743
|
+
for (const [callId, entry] of runningToolEntries) {
|
|
744
|
+
events.finalize(entry.entryId, {
|
|
745
|
+
role: 'tool',
|
|
292
746
|
title: 'tool',
|
|
293
|
-
content
|
|
747
|
+
content: `Skipped · ${reason}`,
|
|
748
|
+
});
|
|
749
|
+
events.toolFinished?.({
|
|
750
|
+
callId,
|
|
751
|
+
name: entry.name,
|
|
752
|
+
args: entry.args,
|
|
753
|
+
success: false,
|
|
754
|
+
skipped: true,
|
|
755
|
+
duration: 0,
|
|
756
|
+
error: reason,
|
|
757
|
+
batchCount: entry.batchCount,
|
|
758
|
+
batchIndex: entry.batchIndex,
|
|
294
759
|
});
|
|
295
|
-
runningToolEntries.delete(event.callId);
|
|
296
|
-
return;
|
|
297
760
|
}
|
|
298
|
-
|
|
299
|
-
role: event.success ? 'tool' : 'error',
|
|
300
|
-
title: 'tool',
|
|
301
|
-
content,
|
|
302
|
-
});
|
|
303
|
-
events.finalize(entryId);
|
|
761
|
+
runningToolEntries.clear();
|
|
304
762
|
},
|
|
305
763
|
};
|
|
306
764
|
}
|
|
@@ -331,12 +789,21 @@ class AgentChatController {
|
|
|
331
789
|
this.events = events;
|
|
332
790
|
this.controllerOptions = controllerOptions;
|
|
333
791
|
}
|
|
792
|
+
setLoopStats(stats) {
|
|
793
|
+
this.runtime.store.setLastLoopStats(stats);
|
|
794
|
+
this.events.loopStatsUpdated?.(stats);
|
|
795
|
+
}
|
|
334
796
|
async runInput(input, options = {}) {
|
|
335
797
|
const text = input.trim();
|
|
336
798
|
if (!text)
|
|
337
799
|
return;
|
|
338
800
|
const parsed = (0, parser_1.parseInput)(text);
|
|
339
801
|
if (!parsed.isCommand) {
|
|
802
|
+
const localFastPath = parseLocalFastPath(text);
|
|
803
|
+
if (localFastPath) {
|
|
804
|
+
await this.runLocalFastPath(text, localFastPath, options);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
340
807
|
await this.runChat(text, options);
|
|
341
808
|
return;
|
|
342
809
|
}
|
|
@@ -362,6 +829,7 @@ class AgentChatController {
|
|
|
362
829
|
content: suggestions.length > 0
|
|
363
830
|
? `Unknown command: /${parsed.name}\nDid you mean: ${suggestions.map(item => `/${item}`).join(', ')}?`
|
|
364
831
|
: `Unknown command: /${parsed.name}`,
|
|
832
|
+
errorLayer: 'runtime',
|
|
365
833
|
});
|
|
366
834
|
return;
|
|
367
835
|
}
|
|
@@ -386,6 +854,7 @@ class AgentChatController {
|
|
|
386
854
|
role: 'error',
|
|
387
855
|
title: `/${command.name}`,
|
|
388
856
|
content: result.error,
|
|
857
|
+
errorLayer: 'runtime',
|
|
389
858
|
});
|
|
390
859
|
}
|
|
391
860
|
if (result.sessionPicker) {
|
|
@@ -400,6 +869,182 @@ class AgentChatController {
|
|
|
400
869
|
await this.runChat(result.chatInput ?? parsed.args, options);
|
|
401
870
|
}
|
|
402
871
|
}
|
|
872
|
+
async runLocalFastPath(input, action, options = {}) {
|
|
873
|
+
const activeSession = this.runtime.getSession() ?? this.runtime.ensureSession() ?? (0, session_storage_1.loadSessionMeta)(this.runtime.getSession()?.id ?? '');
|
|
874
|
+
const sessionId = activeSession?.id;
|
|
875
|
+
const turnId = traceTurnId(options.turnId);
|
|
876
|
+
const localCallId = `local-${turnId}`;
|
|
877
|
+
const start = Date.now();
|
|
878
|
+
const preWorkspace = (0, workspace_state_1.captureWorkspaceSnapshot)(this.runtime.cwd);
|
|
879
|
+
const traceArgs = buildTraceArgsDetails(this.runtime.cwd, action.tool, action.args);
|
|
880
|
+
if (sessionId) {
|
|
881
|
+
(0, session_storage_1.appendSessionMessage)(sessionId, {
|
|
882
|
+
role: 'user',
|
|
883
|
+
content: input,
|
|
884
|
+
timestamp: Date.now(),
|
|
885
|
+
});
|
|
886
|
+
recordTraceEvent(this.events, sessionId, {
|
|
887
|
+
turnId,
|
|
888
|
+
type: 'turn_start',
|
|
889
|
+
inputBytes: byteLength(input),
|
|
890
|
+
localFastPathUsed: true,
|
|
891
|
+
});
|
|
892
|
+
appendWorkspaceSnapshotTrace(this.events, sessionId, turnId, 'pre_turn', preWorkspace);
|
|
893
|
+
recordTraceEvent(this.events, sessionId, {
|
|
894
|
+
turnId,
|
|
895
|
+
type: 'local_fast_path',
|
|
896
|
+
name: action.tool,
|
|
897
|
+
...traceArgs,
|
|
898
|
+
note: compactMiddle(action.label, 160),
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
this.runtime.store.addMessage({ role: 'user', content: input });
|
|
902
|
+
this.events.setStatus(`Running local ${action.label}...`);
|
|
903
|
+
try {
|
|
904
|
+
const tool = (0, tools_1.getRuntimeTools)().find(candidate => candidate.name === action.tool);
|
|
905
|
+
const toolContext = {
|
|
906
|
+
cwd: this.runtime.cwd,
|
|
907
|
+
config: {
|
|
908
|
+
name: this.runtime.config.name,
|
|
909
|
+
mode: this.runtime.config.mode,
|
|
910
|
+
},
|
|
911
|
+
sessionId,
|
|
912
|
+
turnId,
|
|
913
|
+
};
|
|
914
|
+
const permission = tool?.checkPermissions?.(action.args, toolContext);
|
|
915
|
+
if (permission?.behavior === 'deny' || tool?.isDestructive?.(action.args) === true) {
|
|
916
|
+
const reason = permission?.reason || 'Local fast path blocked a destructive tool request.';
|
|
917
|
+
throw new LocalFastPathBlockedError(reason);
|
|
918
|
+
}
|
|
919
|
+
if (permission?.behavior === 'ask') {
|
|
920
|
+
throw new LocalFastPathBlockedError(permission.reason || 'Local fast path requires an allow-safe command.');
|
|
921
|
+
}
|
|
922
|
+
if (sessionId) {
|
|
923
|
+
recordTraceEvent(this.events, sessionId, {
|
|
924
|
+
turnId,
|
|
925
|
+
type: 'tool_call',
|
|
926
|
+
name: action.tool,
|
|
927
|
+
callId: localCallId,
|
|
928
|
+
...traceArgs,
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
const result = await (0, tools_1.executeTool)(action.tool, action.args, options.abortSignal, {
|
|
932
|
+
...toolContext,
|
|
933
|
+
sessionId,
|
|
934
|
+
turnId,
|
|
935
|
+
});
|
|
936
|
+
const duration = Date.now() - start;
|
|
937
|
+
const envelope = (0, tool_serializer_1.parseToolResultEnvelope)(result);
|
|
938
|
+
const outputBytes = typeof envelope.outputBytes === 'number'
|
|
939
|
+
? envelope.outputBytes
|
|
940
|
+
: Buffer.byteLength(result, 'utf8');
|
|
941
|
+
const formattedLocalResult = formatLocalFastPathAssistantContent(action, result, this.runtime.cwd);
|
|
942
|
+
const assistantContent = formattedLocalResult.content;
|
|
943
|
+
const stats = (0, framework_1.createLocalFastPathLoopStats)({
|
|
944
|
+
finishReason: envelope.success ? 'completed' : 'failed',
|
|
945
|
+
toolCalls: 1,
|
|
946
|
+
readOnlyToolCalls: action.tool === 'exec_command' ? 0 : 1,
|
|
947
|
+
unsafeToolCalls: action.tool === 'exec_command' ? 1 : 0,
|
|
948
|
+
toolResultBytes: outputBytes,
|
|
949
|
+
modelVisibleToolBytes: 0,
|
|
950
|
+
summarizedBytes: outputBytes,
|
|
951
|
+
});
|
|
952
|
+
this.events.append({
|
|
953
|
+
role: envelope.success ? 'tool' : 'error',
|
|
954
|
+
title: 'local',
|
|
955
|
+
content: toolFinishContent({
|
|
956
|
+
type: 'tool_result',
|
|
957
|
+
name: action.tool,
|
|
958
|
+
args: action.args,
|
|
959
|
+
callId: localCallId,
|
|
960
|
+
result,
|
|
961
|
+
modelVisibleResult: result,
|
|
962
|
+
duration,
|
|
963
|
+
success: envelope.success,
|
|
964
|
+
error: envelope.error,
|
|
965
|
+
summary: envelope.summary,
|
|
966
|
+
outputBytes,
|
|
967
|
+
artifactRef: formattedLocalResult.artifactRef,
|
|
968
|
+
}),
|
|
969
|
+
});
|
|
970
|
+
this.runtime.store.addMessage({ role: 'assistant', content: assistantContent });
|
|
971
|
+
this.setLoopStats(stats);
|
|
972
|
+
if (sessionId) {
|
|
973
|
+
recordTraceEvent(this.events, sessionId, {
|
|
974
|
+
turnId,
|
|
975
|
+
type: 'tool_result',
|
|
976
|
+
name: action.tool,
|
|
977
|
+
callId: localCallId,
|
|
978
|
+
argsSummary: traceArgs.argsSummary,
|
|
979
|
+
argsArtifactId: traceArgs.argsArtifactId,
|
|
980
|
+
argsBytes: traceArgs.argsBytes,
|
|
981
|
+
success: envelope.success,
|
|
982
|
+
duration,
|
|
983
|
+
outputBytes,
|
|
984
|
+
modelVisibleBytes: 0,
|
|
985
|
+
artifactId: formattedLocalResult.artifactRef?.id,
|
|
986
|
+
error: envelope.error ? compactMiddle(envelope.error, 240) : undefined,
|
|
987
|
+
});
|
|
988
|
+
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace);
|
|
989
|
+
recordTraceEvent(this.events, sessionId, {
|
|
990
|
+
turnId,
|
|
991
|
+
type: 'complete',
|
|
992
|
+
finishReason: stats.finishReason,
|
|
993
|
+
llmRequests: stats.llmRequests,
|
|
994
|
+
toolCalls: stats.toolCalls,
|
|
995
|
+
readOnlyToolCalls: stats.readOnlyToolCalls,
|
|
996
|
+
unsafeToolCalls: stats.unsafeToolCalls,
|
|
997
|
+
localFastPathUsed: true,
|
|
998
|
+
});
|
|
999
|
+
(0, session_storage_1.appendSessionMessage)(sessionId, {
|
|
1000
|
+
role: 'assistant',
|
|
1001
|
+
content: assistantContent,
|
|
1002
|
+
timestamp: Date.now(),
|
|
1003
|
+
});
|
|
1004
|
+
const recordedMessages = (0, session_storage_1.readSessionMessages)(sessionId);
|
|
1005
|
+
if (recordedMessages.length > 0) {
|
|
1006
|
+
(0, session_storage_1.updateSessionSummary)(sessionId, recordedMessages);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
this.events.setStatus(envelope.success ? `Completed local ${action.label}` : `Failed local ${action.label}`);
|
|
1010
|
+
}
|
|
1011
|
+
catch (error) {
|
|
1012
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1013
|
+
this.events.append({ role: 'error', title: 'local', content: message, errorLayer: 'tool' });
|
|
1014
|
+
this.events.setStatus('Local command failed. Ready for the next input.');
|
|
1015
|
+
const assistantContent = `Local fast path failed for ${action.label}.\n\n${message}`;
|
|
1016
|
+
this.runtime.store.addMessage({ role: 'assistant', content: assistantContent });
|
|
1017
|
+
const finishReason = error instanceof LocalFastPathBlockedError ? 'blocked' : 'failed';
|
|
1018
|
+
const stats = (0, framework_1.createLocalFastPathLoopStats)({
|
|
1019
|
+
finishReason,
|
|
1020
|
+
toolCalls: 0,
|
|
1021
|
+
});
|
|
1022
|
+
this.setLoopStats(stats);
|
|
1023
|
+
if (sessionId) {
|
|
1024
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1025
|
+
turnId,
|
|
1026
|
+
type: 'error',
|
|
1027
|
+
name: action.tool,
|
|
1028
|
+
error: compactTraceError(error),
|
|
1029
|
+
});
|
|
1030
|
+
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace);
|
|
1031
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1032
|
+
turnId,
|
|
1033
|
+
type: 'complete',
|
|
1034
|
+
finishReason: stats.finishReason,
|
|
1035
|
+
llmRequests: stats.llmRequests,
|
|
1036
|
+
toolCalls: stats.toolCalls,
|
|
1037
|
+
localFastPathUsed: true,
|
|
1038
|
+
note: 'local_fast_path_failed',
|
|
1039
|
+
});
|
|
1040
|
+
(0, session_storage_1.appendSessionMessage)(sessionId, {
|
|
1041
|
+
role: 'assistant',
|
|
1042
|
+
content: assistantContent,
|
|
1043
|
+
timestamp: Date.now(),
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
403
1048
|
createCommandContext(abortSignal, turnId) {
|
|
404
1049
|
return {
|
|
405
1050
|
cwd: this.runtime.cwd,
|
|
@@ -414,6 +1059,9 @@ class AgentChatController {
|
|
|
414
1059
|
this.runtime.setSession(session);
|
|
415
1060
|
this.events.replaceTranscript(sessionMessagesToTranscriptEntries(session.id));
|
|
416
1061
|
},
|
|
1062
|
+
sessionRestored: event => {
|
|
1063
|
+
this.events.sessionRestored?.(event);
|
|
1064
|
+
},
|
|
417
1065
|
getSession: this.runtime.getSession,
|
|
418
1066
|
abortSignal,
|
|
419
1067
|
writeOutput: text => {
|
|
@@ -426,25 +1074,28 @@ class AgentChatController {
|
|
|
426
1074
|
this.events.append({ role: 'system', content: text });
|
|
427
1075
|
}
|
|
428
1076
|
},
|
|
429
|
-
|
|
1077
|
+
uiRenderer: this.controllerOptions.uiRenderer ?? this.runtime.config.ui?.renderer ?? 'terminal',
|
|
1078
|
+
uiCapabilities: (0, ui_events_1.resolveUiRendererCapabilities)(this.controllerOptions.uiCapabilities, this.controllerOptions.uiRenderer ?? this.runtime.config.ui?.renderer),
|
|
430
1079
|
};
|
|
431
1080
|
}
|
|
432
1081
|
async runChat(input, options = {}) {
|
|
433
1082
|
if (!input) {
|
|
434
|
-
this.events.append({ role: 'error', content: 'Usage: /chat <message>' });
|
|
1083
|
+
this.events.append({ role: 'error', content: 'Usage: /chat <message>', errorLayer: 'runtime' });
|
|
435
1084
|
return;
|
|
436
1085
|
}
|
|
437
1086
|
if (!this.runtime.llm || !(0, config_1.isConfigured)(this.runtime.config)) {
|
|
438
1087
|
this.events.append({
|
|
439
1088
|
role: 'error',
|
|
440
1089
|
content: 'LLM is not configured. Set OPENHORSE_API_KEY in ~/.openhorse/openhorse.json or environment.',
|
|
1090
|
+
errorLayer: 'provider',
|
|
441
1091
|
});
|
|
442
1092
|
return;
|
|
443
1093
|
}
|
|
444
1094
|
const abortSignal = options.abortSignal;
|
|
445
|
-
const turnId = options.turnId;
|
|
1095
|
+
const turnId = traceTurnId(options.turnId);
|
|
446
1096
|
const activeSession = this.runtime.getSession() ?? this.runtime.ensureSession() ?? (0, session_storage_1.loadSessionMeta)(this.runtime.getSession()?.id ?? '');
|
|
447
1097
|
const sessionId = activeSession?.id;
|
|
1098
|
+
const preWorkspace = (0, workspace_state_1.captureWorkspaceSnapshot)(this.runtime.cwd);
|
|
448
1099
|
const runtimeTools = (0, tools_1.getRuntimeTools)();
|
|
449
1100
|
const skillResolution = (0, skills_1.resolveSkillsForTurn)({
|
|
450
1101
|
cwd: this.runtime.cwd,
|
|
@@ -461,6 +1112,13 @@ class AgentChatController {
|
|
|
461
1112
|
timestamp: Date.now(),
|
|
462
1113
|
appliedSkills: appliedSkillNames.length > 0 ? appliedSkillNames : undefined,
|
|
463
1114
|
});
|
|
1115
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1116
|
+
turnId,
|
|
1117
|
+
type: 'turn_start',
|
|
1118
|
+
inputBytes: byteLength(input),
|
|
1119
|
+
note: appliedSkillNames.length > 0 ? `skills=${appliedSkillNames.join(',')}` : undefined,
|
|
1120
|
+
});
|
|
1121
|
+
appendWorkspaceSnapshotTrace(this.events, sessionId, turnId, 'pre_turn', preWorkspace);
|
|
464
1122
|
}
|
|
465
1123
|
this.runtime.store.addMessage({ role: 'user', content: input });
|
|
466
1124
|
(0, prompt_context_1.refreshProjectInstructions)(this.runtime.store, this.runtime.cwd);
|
|
@@ -477,6 +1135,10 @@ class AgentChatController {
|
|
|
477
1135
|
});
|
|
478
1136
|
const intent = harness.updateContractFromUserInput(input);
|
|
479
1137
|
harness.recordAppliedSkills(skillResolution.skills);
|
|
1138
|
+
// Reconcile diagnostic: when harness state is present but objective may be incomplete
|
|
1139
|
+
if (snapshot.harnessState && !snapshot.harnessState.rootObjective && !snapshot.harnessState.contract?.objective) {
|
|
1140
|
+
this.events.setStatus('Resume diagnostic: harness state restored but objective may be incomplete. Run /harness explain to review.');
|
|
1141
|
+
}
|
|
480
1142
|
const promptCtx = {
|
|
481
1143
|
cwd: this.runtime.cwd,
|
|
482
1144
|
platform: process.platform,
|
|
@@ -497,9 +1159,14 @@ class AgentChatController {
|
|
|
497
1159
|
let finalContent = '';
|
|
498
1160
|
let finalUsage;
|
|
499
1161
|
let finalModel = '';
|
|
1162
|
+
let pendingCompleteTrace = null;
|
|
1163
|
+
let pendingCompleteStats;
|
|
1164
|
+
const verificationResults = [];
|
|
500
1165
|
const sessionMessagesToRecord = [];
|
|
501
1166
|
const assistantStream = createAssistantStreamPresenter(this.events, abortSignal);
|
|
502
1167
|
const toolEvents = createToolEventPresenter(this.events);
|
|
1168
|
+
let checkpointSequence = 0;
|
|
1169
|
+
const checkpointIds = [];
|
|
503
1170
|
const streamCallbacks = {
|
|
504
1171
|
onChunk: chunk => {
|
|
505
1172
|
assistantStream.appendChunk(chunk);
|
|
@@ -524,6 +1191,14 @@ class AgentChatController {
|
|
|
524
1191
|
turnId,
|
|
525
1192
|
});
|
|
526
1193
|
};
|
|
1194
|
+
const loopBudget = (0, loop_budget_1.resolveRuntimeLoopBudget)(input, this.runtime.config, harness.toJSON());
|
|
1195
|
+
let observedTurnsStarted = 0;
|
|
1196
|
+
let observedLlmRequests = 0;
|
|
1197
|
+
let observedToolCalls = 0;
|
|
1198
|
+
let observedReadOnlyToolCalls = 0;
|
|
1199
|
+
let observedUnsafeToolCalls = 0;
|
|
1200
|
+
let observedToolResultBytes = 0;
|
|
1201
|
+
let observedModelVisibleToolBytes = 0;
|
|
527
1202
|
try {
|
|
528
1203
|
for await (const event of (0, framework_1.query)({
|
|
529
1204
|
messages,
|
|
@@ -547,17 +1222,86 @@ class AgentChatController {
|
|
|
547
1222
|
abortSignal,
|
|
548
1223
|
harness,
|
|
549
1224
|
input,
|
|
1225
|
+
loopBudget,
|
|
550
1226
|
})) {
|
|
551
1227
|
switch (event.type) {
|
|
552
1228
|
case 'request_start':
|
|
1229
|
+
observedTurnsStarted = Math.max(observedTurnsStarted, event.turn);
|
|
1230
|
+
observedLlmRequests++;
|
|
553
1231
|
assistantStream.discardSegment();
|
|
554
1232
|
this.events.setStatus((0, agent_status_1.agentStepStatus)(event.turn));
|
|
1233
|
+
if (sessionId) {
|
|
1234
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1235
|
+
turnId,
|
|
1236
|
+
type: 'request_start',
|
|
1237
|
+
model: event.model,
|
|
1238
|
+
turn: event.turn,
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
break;
|
|
1242
|
+
case 'prompt_assembly':
|
|
1243
|
+
if (sessionId) {
|
|
1244
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1245
|
+
turnId,
|
|
1246
|
+
type: 'prompt_assembly',
|
|
1247
|
+
promptModelId: event.modelId,
|
|
1248
|
+
promptEstimatedTokens: event.estimatedTokens,
|
|
1249
|
+
promptBudgetTokens: event.budgetTokens,
|
|
1250
|
+
promptCoreTokens: event.coreTokens,
|
|
1251
|
+
promptEvidenceBudgetTokens: event.evidenceBudgetTokens,
|
|
1252
|
+
promptRecentTurnBudgetTokens: event.recentTurnBudgetTokens,
|
|
1253
|
+
promptSections: event.sections,
|
|
1254
|
+
promptIncludedEvidence: event.includedEvidence,
|
|
1255
|
+
promptOmittedEvidence: event.omittedEvidence,
|
|
1256
|
+
promptIncludedEvidenceCount: event.includedEvidenceCount,
|
|
1257
|
+
promptOmittedEvidenceCount: event.omittedEvidenceCount,
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
555
1260
|
break;
|
|
556
1261
|
case 'assistant_tool_calls':
|
|
557
1262
|
assistantStream.ensureMessage(event.content || '');
|
|
558
1263
|
assistantStream.closeSegment();
|
|
559
|
-
|
|
560
|
-
|
|
1264
|
+
this.events.setStatus((0, agent_status_1.runningToolsStatus)(event.toolCalls.length));
|
|
1265
|
+
{
|
|
1266
|
+
const batchReadOnlyCount = event.toolCalls.filter(tc => {
|
|
1267
|
+
const def = skillResolution.tools.find(t => t.name === tc.function.name);
|
|
1268
|
+
const args = parseToolCallArgsForRuntime(tc);
|
|
1269
|
+
return args && def?.isReadOnly?.(args) === true;
|
|
1270
|
+
}).length;
|
|
1271
|
+
const suggestion = (0, agent_status_1.batchingSuggestion)(batchReadOnlyCount);
|
|
1272
|
+
if (suggestion) {
|
|
1273
|
+
this.events.append({ role: 'status', content: suggestion });
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
const checkpointId = checkpointSequence === 0
|
|
1277
|
+
? turnId
|
|
1278
|
+
: `${turnId}-checkpoint-${checkpointSequence + 1}`;
|
|
1279
|
+
const checkpointResult = createPreToolCheckpoint(this.events, sessionId, turnId, checkpointId, this.runtime.cwd, event.toolCalls);
|
|
1280
|
+
if (checkpointResult.created) {
|
|
1281
|
+
checkpointIds.push(checkpointId);
|
|
1282
|
+
checkpointSequence++;
|
|
1283
|
+
}
|
|
1284
|
+
if (checkpointResult.created && checkpointResult.risky) {
|
|
1285
|
+
this.events.append({
|
|
1286
|
+
role: 'status',
|
|
1287
|
+
title: 'checkpoint',
|
|
1288
|
+
content: `Risky edit: ${checkpointResult.targetCount} files in one turn. Checkpoint ${checkpointId} created for rollback (/checkpoints restore ${checkpointId}).`,
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
else if (checkpointResult.risky) {
|
|
1292
|
+
this.events.append({
|
|
1293
|
+
role: 'status',
|
|
1294
|
+
title: 'checkpoint',
|
|
1295
|
+
content: `Risky edit: ${checkpointResult.targetCount} files in one turn, but checkpoint creation failed. Restore any pre-existing checkpoint manually or revert via git.`,
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
if (sessionId) {
|
|
1299
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1300
|
+
turnId,
|
|
1301
|
+
type: 'assistant_tool_calls',
|
|
1302
|
+
toolCallCount: event.toolCalls.length,
|
|
1303
|
+
contentBytes: byteLength(event.content || ''),
|
|
1304
|
+
});
|
|
561
1305
|
}
|
|
562
1306
|
sessionMessagesToRecord.push({
|
|
563
1307
|
role: 'assistant',
|
|
@@ -567,11 +1311,83 @@ class AgentChatController {
|
|
|
567
1311
|
});
|
|
568
1312
|
break;
|
|
569
1313
|
case 'tool_call':
|
|
1314
|
+
observedToolCalls++;
|
|
1315
|
+
{
|
|
1316
|
+
const toolDefinition = skillResolution.tools.find(tool => tool.name === event.name);
|
|
1317
|
+
if (toolDefinition?.isReadOnly?.(event.args) === true) {
|
|
1318
|
+
observedReadOnlyToolCalls++;
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
observedUnsafeToolCalls++;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
570
1324
|
assistantStream.closeSegment();
|
|
571
1325
|
toolEvents.start(event);
|
|
1326
|
+
if (sessionId) {
|
|
1327
|
+
const traceArgs = buildTraceArgsDetails(this.runtime.cwd, event.name, event.args);
|
|
1328
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1329
|
+
turnId,
|
|
1330
|
+
type: 'tool_call',
|
|
1331
|
+
name: event.name,
|
|
1332
|
+
callId: event.callId,
|
|
1333
|
+
...traceArgs,
|
|
1334
|
+
batchCount: event.batchCount,
|
|
1335
|
+
batchIndex: event.batchIndex,
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
break;
|
|
1339
|
+
case 'permission_decision':
|
|
1340
|
+
if (sessionId) {
|
|
1341
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1342
|
+
turnId,
|
|
1343
|
+
type: 'permission_decision',
|
|
1344
|
+
name: event.name,
|
|
1345
|
+
callId: event.callId,
|
|
1346
|
+
argsSummary: compactToolArgs(event.args),
|
|
1347
|
+
permissionBehavior: event.decision.behavior,
|
|
1348
|
+
permissionApproved: event.decision.approved,
|
|
1349
|
+
permissionSource: event.decision.source,
|
|
1350
|
+
permissionReason: event.decision.reason
|
|
1351
|
+
? compactMiddle(event.decision.reason, 240)
|
|
1352
|
+
: undefined,
|
|
1353
|
+
permissionDuration: event.decision.duration,
|
|
1354
|
+
batchCount: event.batchCount,
|
|
1355
|
+
batchIndex: event.batchIndex,
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
572
1358
|
break;
|
|
573
1359
|
case 'tool_result': {
|
|
1360
|
+
observedToolResultBytes += event.outputBytes ?? byteLength(event.result);
|
|
1361
|
+
observedModelVisibleToolBytes += byteLength(event.modelVisibleResult);
|
|
574
1362
|
toolEvents.finish(event);
|
|
1363
|
+
if (sessionId) {
|
|
1364
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1365
|
+
turnId,
|
|
1366
|
+
type: 'tool_result',
|
|
1367
|
+
name: event.name,
|
|
1368
|
+
callId: event.callId,
|
|
1369
|
+
argsSummary: compactToolArgs(event.args),
|
|
1370
|
+
success: event.success,
|
|
1371
|
+
duration: event.duration,
|
|
1372
|
+
outputBytes: event.outputBytes,
|
|
1373
|
+
modelVisibleBytes: byteLength(event.modelVisibleResult),
|
|
1374
|
+
artifactId: event.artifactRef?.id,
|
|
1375
|
+
error: event.error ? compactMiddle(event.error, 240) : undefined,
|
|
1376
|
+
batchCount: event.batchCount,
|
|
1377
|
+
batchIndex: event.batchIndex,
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
const verificationResult = (0, verification_profile_1.collectVerificationCommandResult)({
|
|
1381
|
+
toolName: event.name,
|
|
1382
|
+
args: event.args,
|
|
1383
|
+
success: event.success,
|
|
1384
|
+
outputBytes: event.outputBytes,
|
|
1385
|
+
error: event.error,
|
|
1386
|
+
});
|
|
1387
|
+
if (verificationResult) {
|
|
1388
|
+
verificationResults.push(verificationResult);
|
|
1389
|
+
appendVerificationResultTrace(this.events, sessionId, turnId, verificationResult);
|
|
1390
|
+
}
|
|
575
1391
|
sessionMessagesToRecord.push({
|
|
576
1392
|
role: 'tool',
|
|
577
1393
|
content: event.result,
|
|
@@ -583,10 +1399,24 @@ class AgentChatController {
|
|
|
583
1399
|
}
|
|
584
1400
|
case 'strategy_exhausted':
|
|
585
1401
|
this.events.append({ role: 'status', content: event.suggestion });
|
|
1402
|
+
if (sessionId) {
|
|
1403
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1404
|
+
turnId,
|
|
1405
|
+
type: 'strategy_exhausted',
|
|
1406
|
+
note: compactMiddle(event.suggestion, 240),
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
586
1409
|
break;
|
|
587
1410
|
case 'message':
|
|
588
1411
|
finalContent = event.content;
|
|
589
1412
|
assistantStream.ensureMessage(event.content);
|
|
1413
|
+
if (sessionId) {
|
|
1414
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1415
|
+
turnId,
|
|
1416
|
+
type: 'message',
|
|
1417
|
+
contentBytes: byteLength(event.content),
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
590
1420
|
if (event.content) {
|
|
591
1421
|
sessionMessagesToRecord.push({
|
|
592
1422
|
role: 'assistant',
|
|
@@ -596,11 +1426,58 @@ class AgentChatController {
|
|
|
596
1426
|
}
|
|
597
1427
|
break;
|
|
598
1428
|
case 'complete':
|
|
1429
|
+
if (event.stats?.finishReason === 'blocked') {
|
|
1430
|
+
toolEvents.finalizePendingAsSkipped('permission denied');
|
|
1431
|
+
}
|
|
1432
|
+
if (event.content && !finalContent) {
|
|
1433
|
+
if (event.stats?.finishReason === 'budget_exceeded') {
|
|
1434
|
+
assistantStream.replaceMessage(event.content);
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
assistantStream.ensureMessage(event.content);
|
|
1438
|
+
}
|
|
1439
|
+
sessionMessagesToRecord.push({
|
|
1440
|
+
role: 'assistant',
|
|
1441
|
+
content: event.content,
|
|
1442
|
+
timestamp: Date.now(),
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
599
1445
|
finalContent = event.content;
|
|
600
1446
|
finalUsage = event.usage;
|
|
601
1447
|
finalModel = event.model;
|
|
602
1448
|
if (event.stats) {
|
|
603
|
-
|
|
1449
|
+
pendingCompleteStats = event.stats;
|
|
1450
|
+
recordProviderTraceEvents(this.events, sessionId, turnId, event.stats);
|
|
1451
|
+
pendingCompleteTrace = {
|
|
1452
|
+
turnId,
|
|
1453
|
+
type: 'complete',
|
|
1454
|
+
model: event.model,
|
|
1455
|
+
contentBytes: byteLength(event.content || ''),
|
|
1456
|
+
finishReason: event.stats.finishReason,
|
|
1457
|
+
llmRequests: event.stats.llmRequests,
|
|
1458
|
+
toolCalls: event.stats.toolCalls,
|
|
1459
|
+
readOnlyToolCalls: event.stats.readOnlyToolCalls,
|
|
1460
|
+
unsafeToolCalls: event.stats.unsafeToolCalls,
|
|
1461
|
+
loopBudgetSource: event.stats.loopBudgetSource,
|
|
1462
|
+
loopBudgetBaseProfile: event.stats.loopBudgetBaseProfile,
|
|
1463
|
+
loopBudgetMaxLlmRequests: event.stats.loopBudgetMaxLlmRequests,
|
|
1464
|
+
loopBudgetMaxToolCalls: event.stats.loopBudgetMaxToolCalls,
|
|
1465
|
+
loopBudgetMaxReadOnlyFragmentation: event.stats.loopBudgetMaxReadOnlyFragmentation,
|
|
1466
|
+
loopBudgetMaxModelVisibleBytes: event.stats.loopBudgetMaxModelVisibleBytes,
|
|
1467
|
+
loopBudgetConfigOverride: event.stats.loopBudgetConfigOverride,
|
|
1468
|
+
budgetExceededReason: event.stats.budgetExceededReason,
|
|
1469
|
+
continuationActions: event.stats.continuationActions,
|
|
1470
|
+
continuationHint: event.stats.continuationHint,
|
|
1471
|
+
localFastPathUsed: event.stats.localFastPathUsed,
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
else {
|
|
1475
|
+
pendingCompleteTrace = {
|
|
1476
|
+
turnId,
|
|
1477
|
+
type: 'complete',
|
|
1478
|
+
model: event.model,
|
|
1479
|
+
contentBytes: byteLength(event.content || ''),
|
|
1480
|
+
};
|
|
604
1481
|
}
|
|
605
1482
|
break;
|
|
606
1483
|
}
|
|
@@ -611,11 +1488,97 @@ class AgentChatController {
|
|
|
611
1488
|
this.events.setStatus('Interrupted.');
|
|
612
1489
|
removeTrailingUserMessage(this.runtime);
|
|
613
1490
|
if (sessionId) {
|
|
1491
|
+
const { delta } = appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1492
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1493
|
+
turnId,
|
|
1494
|
+
type: 'aborted',
|
|
1495
|
+
note: 'aborted_after_query',
|
|
1496
|
+
});
|
|
1497
|
+
const recoveryNotice = workspaceDeltaHasTurnChanges(delta)
|
|
1498
|
+
? formatFailureRecoveryNotice(turnId, delta, checkpointIds)
|
|
1499
|
+
: undefined;
|
|
1500
|
+
if (recoveryNotice) {
|
|
1501
|
+
this.events.append({
|
|
1502
|
+
role: 'status',
|
|
1503
|
+
title: 'recovery',
|
|
1504
|
+
content: recoveryNotice,
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
614
1507
|
(0, session_storage_1.removeLastIncompleteAssistantMessage)(sessionId);
|
|
615
1508
|
}
|
|
616
1509
|
return;
|
|
617
1510
|
}
|
|
618
1511
|
assistantStream.closeSegment();
|
|
1512
|
+
if (sessionId) {
|
|
1513
|
+
const { profile, summary } = appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1514
|
+
if (profile.changedFiles.length > 0 && profile.required) {
|
|
1515
|
+
this.events.setStatus((0, agent_status_1.verifyingStatus)(profile.profile));
|
|
1516
|
+
this.controllerOptions.onVerificationStateChange?.('running');
|
|
1517
|
+
}
|
|
1518
|
+
if (shouldRecordVerificationLoopStats(profile, summary)) {
|
|
1519
|
+
const stats = pendingCompleteStats ?? this.runtime.store.getSnapshot().lastLoopStats;
|
|
1520
|
+
if (stats) {
|
|
1521
|
+
pendingCompleteStats = withVerificationLoopStats(stats, summary);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if ((0, verification_profile_1.shouldGateCompletion)(summary)) {
|
|
1525
|
+
this.events.setStatus((0, agent_status_1.verificationGateStatus)(summary.skippedReason ?? 'verification checks not run'));
|
|
1526
|
+
this.controllerOptions.onVerificationStateChange?.('gated');
|
|
1527
|
+
const notice = (0, verification_profile_1.formatVerificationGateNotice)(summary);
|
|
1528
|
+
this.events.append({
|
|
1529
|
+
role: 'status',
|
|
1530
|
+
title: 'verification',
|
|
1531
|
+
content: notice,
|
|
1532
|
+
});
|
|
1533
|
+
finalContent = finalContent ? `${finalContent}\n\n${notice}` : notice;
|
|
1534
|
+
appendAssistantNotice(sessionMessagesToRecord, notice);
|
|
1535
|
+
if (pendingCompleteTrace) {
|
|
1536
|
+
pendingCompleteTrace.finishReason = 'completion_gate';
|
|
1537
|
+
pendingCompleteTrace.contentBytes = byteLength(finalContent);
|
|
1538
|
+
pendingCompleteTrace.note = 'verification_incomplete';
|
|
1539
|
+
}
|
|
1540
|
+
const stats = pendingCompleteStats ?? this.runtime.store.getSnapshot().lastLoopStats;
|
|
1541
|
+
if (stats) {
|
|
1542
|
+
pendingCompleteStats = {
|
|
1543
|
+
...withVerificationLoopStats(stats, summary),
|
|
1544
|
+
finishReason: 'completion_gate',
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
else if (profile.changedFiles.length > 0 && profile.required) {
|
|
1549
|
+
this.controllerOptions.onVerificationStateChange?.('passed');
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
if (pendingCompleteStats?.finishReason === 'budget_exceeded') {
|
|
1553
|
+
const stats = pendingCompleteStats;
|
|
1554
|
+
const lines = ['Loop budget reached — stopping this turn.'];
|
|
1555
|
+
if (stats.budgetExceededReason) {
|
|
1556
|
+
lines.push(`Reason: ${stats.budgetExceededReason}`);
|
|
1557
|
+
}
|
|
1558
|
+
const progressParts = [];
|
|
1559
|
+
if (typeof stats.loopBudgetMaxLlmRequests === 'number') {
|
|
1560
|
+
progressParts.push(`${stats.llmRequests ?? 0}/${stats.loopBudgetMaxLlmRequests} LLM requests`);
|
|
1561
|
+
}
|
|
1562
|
+
if (typeof stats.loopBudgetMaxToolCalls === 'number') {
|
|
1563
|
+
progressParts.push(`${stats.toolCalls ?? 0}/${stats.loopBudgetMaxToolCalls} tool calls`);
|
|
1564
|
+
}
|
|
1565
|
+
if (progressParts.length) {
|
|
1566
|
+
lines.push(`Progress: ${progressParts.join(', ')}`);
|
|
1567
|
+
}
|
|
1568
|
+
if (stats.continuationActions?.length) {
|
|
1569
|
+
lines.push(`Next: ${stats.continuationActions.join('; ')}`);
|
|
1570
|
+
}
|
|
1571
|
+
else if (stats.continuationHint) {
|
|
1572
|
+
lines.push(`Next: ${stats.continuationHint}`);
|
|
1573
|
+
}
|
|
1574
|
+
const notice = lines.join('\n');
|
|
1575
|
+
this.events.append({ role: 'status', title: 'budget', content: notice });
|
|
1576
|
+
finalContent = finalContent ? `${finalContent}\n\n${notice}` : notice;
|
|
1577
|
+
appendAssistantNotice(sessionMessagesToRecord, notice);
|
|
1578
|
+
}
|
|
1579
|
+
if (pendingCompleteStats) {
|
|
1580
|
+
this.setLoopStats(pendingCompleteStats);
|
|
1581
|
+
}
|
|
619
1582
|
if (finalContent) {
|
|
620
1583
|
this.runtime.store.addMessage({ role: 'assistant', content: finalContent });
|
|
621
1584
|
}
|
|
@@ -633,7 +1596,11 @@ class AgentChatController {
|
|
|
633
1596
|
});
|
|
634
1597
|
const harnessState = harness.toJSON();
|
|
635
1598
|
this.runtime.store.setState({ harnessState });
|
|
1599
|
+
emitHarnessDiagnostics(this.events, harnessState);
|
|
636
1600
|
if (sessionId) {
|
|
1601
|
+
if (pendingCompleteTrace) {
|
|
1602
|
+
recordTraceEvent(this.events, sessionId, pendingCompleteTrace);
|
|
1603
|
+
}
|
|
637
1604
|
(0, session_storage_1.updateSessionSkills)(sessionId, appliedSkillNames);
|
|
638
1605
|
(0, session_storage_1.updateSessionHarnessState)(sessionId, harnessState);
|
|
639
1606
|
const recordedMessages = (0, session_storage_1.readSessionMessages)(sessionId);
|
|
@@ -649,13 +1616,83 @@ class AgentChatController {
|
|
|
649
1616
|
this.events.setStatus('Interrupted.');
|
|
650
1617
|
removeTrailingUserMessage(this.runtime);
|
|
651
1618
|
if (sessionId) {
|
|
1619
|
+
const { delta } = appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1620
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1621
|
+
turnId,
|
|
1622
|
+
type: 'aborted',
|
|
1623
|
+
note: 'abort_error',
|
|
1624
|
+
});
|
|
1625
|
+
const recoveryNotice = workspaceDeltaHasTurnChanges(delta)
|
|
1626
|
+
? formatFailureRecoveryNotice(turnId, delta, checkpointIds)
|
|
1627
|
+
: undefined;
|
|
1628
|
+
if (recoveryNotice) {
|
|
1629
|
+
this.events.append({
|
|
1630
|
+
role: 'status',
|
|
1631
|
+
title: 'recovery',
|
|
1632
|
+
content: recoveryNotice,
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
652
1635
|
(0, session_storage_1.removeLastIncompleteAssistantMessage)(sessionId);
|
|
653
1636
|
}
|
|
654
1637
|
return;
|
|
655
1638
|
}
|
|
656
1639
|
assistantStream.discardSegment();
|
|
657
|
-
this.events.append({ role: 'error', content: formatChatError(error) });
|
|
1640
|
+
this.events.append({ role: 'error', content: formatChatError(error), errorLayer: errorLayerForChatError(error) });
|
|
658
1641
|
this.events.setStatus('Turn failed. Ready for the next input.');
|
|
1642
|
+
const failedStats = error instanceof framework_1.QueryLoopError
|
|
1643
|
+
? error.stats
|
|
1644
|
+
: (0, framework_1.createFailedLoopStats)({
|
|
1645
|
+
loopBudget,
|
|
1646
|
+
diagnostics: observedLlmRequests > 0 ? getLastRequestDiagnostics(this.runtime.llm) : undefined,
|
|
1647
|
+
turnsStarted: observedTurnsStarted,
|
|
1648
|
+
llmRequests: observedLlmRequests,
|
|
1649
|
+
toolCalls: observedToolCalls,
|
|
1650
|
+
readOnlyToolCalls: observedReadOnlyToolCalls,
|
|
1651
|
+
unsafeToolCalls: observedUnsafeToolCalls,
|
|
1652
|
+
toolResultBytes: observedToolResultBytes,
|
|
1653
|
+
modelVisibleToolBytes: observedModelVisibleToolBytes,
|
|
1654
|
+
});
|
|
1655
|
+
this.setLoopStats(failedStats);
|
|
1656
|
+
if (sessionId) {
|
|
1657
|
+
recordProviderTraceEvents(this.events, sessionId, turnId, failedStats);
|
|
1658
|
+
const { delta } = appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1659
|
+
const recoveryNotice = workspaceDeltaHasTurnChanges(delta)
|
|
1660
|
+
? formatFailureRecoveryNotice(turnId, delta, checkpointIds)
|
|
1661
|
+
: undefined;
|
|
1662
|
+
if (recoveryNotice) {
|
|
1663
|
+
this.events.append({
|
|
1664
|
+
role: 'status',
|
|
1665
|
+
title: 'recovery',
|
|
1666
|
+
content: recoveryNotice,
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1670
|
+
turnId,
|
|
1671
|
+
type: 'error',
|
|
1672
|
+
error: compactTraceError(error),
|
|
1673
|
+
note: recoveryNotice,
|
|
1674
|
+
});
|
|
1675
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1676
|
+
turnId,
|
|
1677
|
+
type: 'complete',
|
|
1678
|
+
model: failedStats.providerFinalModel ?? this.runtime.llm.getModel(),
|
|
1679
|
+
contentBytes: 0,
|
|
1680
|
+
finishReason: failedStats.finishReason,
|
|
1681
|
+
llmRequests: failedStats.llmRequests,
|
|
1682
|
+
toolCalls: failedStats.toolCalls,
|
|
1683
|
+
readOnlyToolCalls: failedStats.readOnlyToolCalls,
|
|
1684
|
+
unsafeToolCalls: failedStats.unsafeToolCalls,
|
|
1685
|
+
loopBudgetSource: failedStats.loopBudgetSource,
|
|
1686
|
+
loopBudgetBaseProfile: failedStats.loopBudgetBaseProfile,
|
|
1687
|
+
loopBudgetMaxLlmRequests: failedStats.loopBudgetMaxLlmRequests,
|
|
1688
|
+
loopBudgetMaxToolCalls: failedStats.loopBudgetMaxToolCalls,
|
|
1689
|
+
loopBudgetMaxReadOnlyFragmentation: failedStats.loopBudgetMaxReadOnlyFragmentation,
|
|
1690
|
+
loopBudgetMaxModelVisibleBytes: failedStats.loopBudgetMaxModelVisibleBytes,
|
|
1691
|
+
loopBudgetConfigOverride: failedStats.loopBudgetConfigOverride,
|
|
1692
|
+
localFastPathUsed: failedStats.localFastPathUsed,
|
|
1693
|
+
});
|
|
1694
|
+
(0, session_storage_1.removeLastIncompleteAssistantMessage)(sessionId);
|
|
1695
|
+
}
|
|
659
1696
|
const history = this.runtime.store.getSnapshot().conversationHistory;
|
|
660
1697
|
if (history.length > 0) {
|
|
661
1698
|
this.runtime.store.setState({ conversationHistory: history.slice(0, -1) });
|