openhorse 0.2.12 → 0.2.13
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 +597 -12
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/types.d.ts +3 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/core/checkpoint.d.ts +1 -0
- package/dist/core/checkpoint.d.ts.map +1 -1
- package/dist/core/checkpoint.js +59 -10
- package/dist/core/checkpoint.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/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/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/chat-controller.d.ts +3 -0
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +907 -2
- 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/ui-events.d.ts +34 -1
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- 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/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/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 +98 -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 +36 -0
- package/dist/services/verification-profile.d.ts.map +1 -0
- package/dist/services/verification-profile.js +239 -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/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,26 @@ 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 workspace_state_1 = require("../services/workspace-state");
|
|
57
|
+
const verification_profile_1 = require("../services/verification-profile");
|
|
19
58
|
const ui_events_1 = require("./ui-events");
|
|
20
59
|
const agent_status_1 = require("./agent-status");
|
|
60
|
+
const loop_budget_1 = require("./loop-budget");
|
|
21
61
|
const ANSI_PATTERN = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
62
|
+
const LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES = 2048;
|
|
63
|
+
const TRACE_ARGS_ARTIFACT_THRESHOLD_BYTES = 160;
|
|
22
64
|
function stripAnsi(text) {
|
|
23
65
|
return text.replace(ANSI_PATTERN, '');
|
|
24
66
|
}
|
|
@@ -64,6 +106,316 @@ function compactToolArgs(args, maxLength = 160) {
|
|
|
64
106
|
}
|
|
65
107
|
return '';
|
|
66
108
|
}
|
|
109
|
+
function fullToolArgsForTrace(name, args) {
|
|
110
|
+
if (name === 'exec_command' && typeof args.command === 'string') {
|
|
111
|
+
return `$ ${args.command}`;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
return JSON.stringify(args, null, 2);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return compactToolArgs(args, 2048);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function buildTraceArgsDetails(projectPath, name, args) {
|
|
121
|
+
const argsSummary = compactToolArgs(args);
|
|
122
|
+
const fullArgs = (0, session_storage_1.redactTraceText)(fullToolArgsForTrace(name, args)).trim();
|
|
123
|
+
const argsBytes = byteLength(fullArgs);
|
|
124
|
+
if (!projectPath
|
|
125
|
+
|| !fullArgs
|
|
126
|
+
|| fullArgs === (0, session_storage_1.redactTraceText)(argsSummary)
|
|
127
|
+
|| argsBytes <= TRACE_ARGS_ARTIFACT_THRESHOLD_BYTES) {
|
|
128
|
+
return { argsSummary };
|
|
129
|
+
}
|
|
130
|
+
const artifact = (0, tool_artifacts_1.storeArtifact)(projectPath, `${name}-args`, fullArgs, argsBytes);
|
|
131
|
+
return artifact
|
|
132
|
+
? { argsSummary, argsArtifactId: artifact.id, argsBytes }
|
|
133
|
+
: { argsSummary };
|
|
134
|
+
}
|
|
135
|
+
function parseToolCallArgsForRuntime(toolCall) {
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(toolCall.function.arguments || '{}');
|
|
138
|
+
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
|
|
139
|
+
? parsed
|
|
140
|
+
: null;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function resolveProjectScopedPath(cwd, filePath) {
|
|
147
|
+
const absolute = path.resolve(cwd, filePath);
|
|
148
|
+
const relative = path.relative(cwd, absolute);
|
|
149
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
150
|
+
return null;
|
|
151
|
+
return absolute;
|
|
152
|
+
}
|
|
153
|
+
function checkpointTargetsFromToolCalls(cwd, toolCalls) {
|
|
154
|
+
const targets = new Set();
|
|
155
|
+
for (const toolCall of toolCalls) {
|
|
156
|
+
const name = toolCall.function.name;
|
|
157
|
+
if (name !== 'write_file' && name !== 'edit_file')
|
|
158
|
+
continue;
|
|
159
|
+
const args = parseToolCallArgsForRuntime(toolCall);
|
|
160
|
+
if (!args || typeof args.path !== 'string')
|
|
161
|
+
continue;
|
|
162
|
+
if (name === 'edit_file' && args.preview === true)
|
|
163
|
+
continue;
|
|
164
|
+
const target = resolveProjectScopedPath(cwd, args.path);
|
|
165
|
+
if (target)
|
|
166
|
+
targets.add(target);
|
|
167
|
+
}
|
|
168
|
+
return Array.from(targets);
|
|
169
|
+
}
|
|
170
|
+
function createPreToolCheckpoint(events, sessionId, turnId, checkpointId, cwd, toolCalls) {
|
|
171
|
+
const targets = checkpointTargetsFromToolCalls(cwd, toolCalls);
|
|
172
|
+
if (targets.length === 0)
|
|
173
|
+
return false;
|
|
174
|
+
const checkpoint = (0, checkpoint_1.createCheckpoint)(cwd, checkpointId, targets);
|
|
175
|
+
if (!sessionId)
|
|
176
|
+
return true;
|
|
177
|
+
const relativeTargets = targets.map(target => path.relative(cwd, target));
|
|
178
|
+
recordTraceEvent(events, sessionId, {
|
|
179
|
+
turnId,
|
|
180
|
+
type: 'checkpoint',
|
|
181
|
+
checkpointId,
|
|
182
|
+
checkpointFileCount: checkpoint?.files.length ?? 0,
|
|
183
|
+
checkpointFiles: checkpoint?.files.map(file => file.path) ?? [],
|
|
184
|
+
workspaceFiles: relativeTargets,
|
|
185
|
+
note: checkpoint
|
|
186
|
+
? 'pre_edit_checkpoint'
|
|
187
|
+
: 'pre_edit_checkpoint_skipped',
|
|
188
|
+
});
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
function byteLength(text) {
|
|
192
|
+
return Buffer.byteLength(text, 'utf8');
|
|
193
|
+
}
|
|
194
|
+
function traceTurnId(turnId) {
|
|
195
|
+
return turnId == null ? `turn-${Date.now()}` : String(turnId);
|
|
196
|
+
}
|
|
197
|
+
function compactTraceError(error) {
|
|
198
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
199
|
+
return compactMiddle(message, 240);
|
|
200
|
+
}
|
|
201
|
+
function getLastRequestDiagnostics(llm) {
|
|
202
|
+
if (!llm)
|
|
203
|
+
return undefined;
|
|
204
|
+
const reader = llm.getLastRequestDiagnostics;
|
|
205
|
+
return typeof reader === 'function' ? reader.call(llm) : undefined;
|
|
206
|
+
}
|
|
207
|
+
function compactPathList(paths, maxItems = 40) {
|
|
208
|
+
return paths.slice(0, maxItems);
|
|
209
|
+
}
|
|
210
|
+
function formatWorkspaceFileForTrace(file) {
|
|
211
|
+
const metadata = [
|
|
212
|
+
typeof file.sizeBytes === 'number' ? `${file.sizeBytes}B` : '',
|
|
213
|
+
typeof file.mtimeMs === 'number' ? `mtime=${file.mtimeMs}` : '',
|
|
214
|
+
].filter(Boolean).join(' ');
|
|
215
|
+
return `${file.status} ${file.path}${metadata ? ` (${metadata})` : ''}`;
|
|
216
|
+
}
|
|
217
|
+
function appendWorkspaceSnapshotTrace(events, sessionId, turnId, phase, snapshot) {
|
|
218
|
+
if (!sessionId)
|
|
219
|
+
return;
|
|
220
|
+
recordTraceEvent(events, sessionId, {
|
|
221
|
+
turnId,
|
|
222
|
+
type: 'workspace_snapshot',
|
|
223
|
+
workspacePhase: phase,
|
|
224
|
+
workspaceGitAvailable: snapshot.gitAvailable,
|
|
225
|
+
workspaceDirty: snapshot.dirty,
|
|
226
|
+
workspaceBranch: snapshot.branch,
|
|
227
|
+
workspaceFileCount: snapshot.fileCount,
|
|
228
|
+
workspaceFiles: compactPathList(snapshot.files.map(formatWorkspaceFileForTrace)),
|
|
229
|
+
error: snapshot.error ? compactMiddle(snapshot.error, 240) : undefined,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function appendWorkspaceDeltaTrace(events, sessionId, turnId, before, after) {
|
|
233
|
+
const delta = (0, workspace_state_1.diffWorkspaceSnapshots)(before, after);
|
|
234
|
+
if (sessionId) {
|
|
235
|
+
recordTraceEvent(events, sessionId, {
|
|
236
|
+
turnId,
|
|
237
|
+
type: 'workspace_delta',
|
|
238
|
+
workspaceFileCount: delta.filesAfterTurn.length,
|
|
239
|
+
workspaceFiles: compactPathList(delta.filesAfterTurn),
|
|
240
|
+
workspaceNewByTurn: compactPathList(delta.newFilesByTurn),
|
|
241
|
+
workspaceChangedByTurn: compactPathList(delta.changedByTurn),
|
|
242
|
+
workspaceModifiedPreExistingByTurn: compactPathList(delta.modifiedPreExistingByTurn),
|
|
243
|
+
workspaceResolvedByTurn: compactPathList(delta.resolvedByTurn),
|
|
244
|
+
note: `pre_existing=${delta.preExistingFiles.length}`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
return delta;
|
|
248
|
+
}
|
|
249
|
+
function workspaceDeltaHasTurnChanges(delta) {
|
|
250
|
+
return delta.newFilesByTurn.length > 0
|
|
251
|
+
|| delta.changedByTurn.length > 0
|
|
252
|
+
|| delta.resolvedByTurn.length > 0;
|
|
253
|
+
}
|
|
254
|
+
function formatFailureRecoveryNotice(turnId, delta, checkpointIds) {
|
|
255
|
+
const files = compactPathList([
|
|
256
|
+
...delta.newFilesByTurn,
|
|
257
|
+
...delta.changedByTurn,
|
|
258
|
+
...delta.resolvedByTurn,
|
|
259
|
+
], 8);
|
|
260
|
+
const fileText = files.length > 0
|
|
261
|
+
? files.join(', ')
|
|
262
|
+
: 'workspace changes recorded';
|
|
263
|
+
const checkpointText = checkpointIds.length > 0
|
|
264
|
+
? ` Checkpoints: ${checkpointIds.join(', ')}. Preview rollback with /checkpoint restore <id>; restore each listed checkpoint if multiple.`
|
|
265
|
+
: '';
|
|
266
|
+
return `Turn failed after modifying files: ${fileText}. Inspect /trace ${turnId}.${checkpointText}`;
|
|
267
|
+
}
|
|
268
|
+
function appendVerificationProfileTrace(events, sessionId, turnId, profile) {
|
|
269
|
+
if (!sessionId || profile.changedFiles.length === 0)
|
|
270
|
+
return;
|
|
271
|
+
recordTraceEvent(events, sessionId, {
|
|
272
|
+
turnId,
|
|
273
|
+
type: 'verification_profile',
|
|
274
|
+
verificationProfile: profile.profile,
|
|
275
|
+
verificationRequired: profile.required,
|
|
276
|
+
verificationCommands: compactPathList(profile.commands, 8),
|
|
277
|
+
verificationChangedFiles: compactPathList(profile.changedFiles),
|
|
278
|
+
note: profile.reason,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function appendVerificationResultTrace(events, sessionId, turnId, result) {
|
|
282
|
+
if (!sessionId)
|
|
283
|
+
return;
|
|
284
|
+
recordTraceEvent(events, sessionId, {
|
|
285
|
+
turnId,
|
|
286
|
+
type: 'verification_result',
|
|
287
|
+
verificationCommand: result.command,
|
|
288
|
+
verificationPassed: result.success,
|
|
289
|
+
outputBytes: result.outputBytes,
|
|
290
|
+
error: result.error ? compactMiddle(result.error, 240) : undefined,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function appendVerificationSummaryTrace(events, sessionId, turnId, summary, changedFiles) {
|
|
294
|
+
if (!sessionId || changedFiles.length === 0)
|
|
295
|
+
return;
|
|
296
|
+
recordTraceEvent(events, sessionId, {
|
|
297
|
+
turnId,
|
|
298
|
+
type: 'verification_summary',
|
|
299
|
+
verificationProfile: summary.profile,
|
|
300
|
+
verificationRequired: summary.required,
|
|
301
|
+
verificationCommands: compactPathList(summary.commandsRun, 12),
|
|
302
|
+
verificationPassedCommands: compactPathList(summary.passedCommands, 12),
|
|
303
|
+
verificationFailedCommands: compactPathList(summary.failedCommands, 12),
|
|
304
|
+
verificationMissingCommands: compactPathList(summary.missingCommands, 12),
|
|
305
|
+
verificationChangedFiles: compactPathList(changedFiles),
|
|
306
|
+
verificationClaimAllowed: summary.claimAllowed,
|
|
307
|
+
note: summary.skippedReason,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function compactVerificationCommands(commands, maxItems = 12) {
|
|
311
|
+
return commands.slice(0, maxItems).map(session_storage_1.redactTraceText);
|
|
312
|
+
}
|
|
313
|
+
function withVerificationLoopStats(stats, summary) {
|
|
314
|
+
return {
|
|
315
|
+
...stats,
|
|
316
|
+
verificationProfile: summary.profile,
|
|
317
|
+
verificationRequired: summary.required,
|
|
318
|
+
verificationClaimAllowed: summary.claimAllowed,
|
|
319
|
+
verificationPassedCommands: compactVerificationCommands(summary.passedCommands),
|
|
320
|
+
verificationFailedCommands: compactVerificationCommands(summary.failedCommands),
|
|
321
|
+
verificationMissingCommands: compactVerificationCommands(summary.missingCommands),
|
|
322
|
+
verificationSkippedReason: summary.skippedReason ? (0, session_storage_1.redactTraceText)(summary.skippedReason) : undefined,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function shouldRecordVerificationLoopStats(profile, summary) {
|
|
326
|
+
return profile.changedFiles.length > 0
|
|
327
|
+
|| summary.commandsRun.length > 0
|
|
328
|
+
|| summary.passedCommands.length > 0
|
|
329
|
+
|| summary.failedCommands.length > 0
|
|
330
|
+
|| summary.missingCommands.length > 0;
|
|
331
|
+
}
|
|
332
|
+
function appendPostWorkspaceTrace(events, sessionId, turnId, cwd, before, verificationResults = []) {
|
|
333
|
+
const postWorkspace = (0, workspace_state_1.captureWorkspaceSnapshot)(cwd);
|
|
334
|
+
appendWorkspaceSnapshotTrace(events, sessionId, turnId, 'post_turn', postWorkspace);
|
|
335
|
+
const delta = appendWorkspaceDeltaTrace(events, sessionId, turnId, before, postWorkspace);
|
|
336
|
+
const profile = (0, verification_profile_1.selectVerificationProfile)(cwd, delta.changedByTurn);
|
|
337
|
+
const summary = (0, verification_profile_1.summarizeVerificationState)(profile, verificationResults);
|
|
338
|
+
appendVerificationProfileTrace(events, sessionId, turnId, profile);
|
|
339
|
+
appendVerificationSummaryTrace(events, sessionId, turnId, summary, profile.changedFiles);
|
|
340
|
+
return { delta, profile, summary };
|
|
341
|
+
}
|
|
342
|
+
function appendAssistantNotice(messages, notice) {
|
|
343
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
344
|
+
const message = messages[index];
|
|
345
|
+
if (message.role === 'assistant' && !message.tool_calls) {
|
|
346
|
+
message.content = message.content ? `${message.content}\n\n${notice}` : notice;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
messages.push({
|
|
351
|
+
role: 'assistant',
|
|
352
|
+
content: notice,
|
|
353
|
+
timestamp: Date.now(),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function recordTraceEvent(events, sessionId, event) {
|
|
357
|
+
if (!sessionId)
|
|
358
|
+
return null;
|
|
359
|
+
const traceEvent = (0, session_storage_1.appendSessionTraceEvent)(sessionId, event);
|
|
360
|
+
if (traceEvent) {
|
|
361
|
+
events.traceEventRecorded?.(traceEvent);
|
|
362
|
+
}
|
|
363
|
+
return traceEvent;
|
|
364
|
+
}
|
|
365
|
+
function recordProviderTraceEvents(events, sessionId, turnId, stats) {
|
|
366
|
+
if ((stats.providerRetryCount ?? 0) > 0) {
|
|
367
|
+
recordTraceEvent(events, sessionId, {
|
|
368
|
+
turnId,
|
|
369
|
+
type: 'provider_retry',
|
|
370
|
+
providerRetryCount: stats.providerRetryCount,
|
|
371
|
+
providerRetryDelayMs: stats.providerRetryDelayMs,
|
|
372
|
+
providerRetryErrorTypes: stats.providerRetryErrorTypes,
|
|
373
|
+
providerLastRetryErrorType: stats.providerLastRetryErrorType,
|
|
374
|
+
providerLastRetryStatus: stats.providerLastRetryStatus,
|
|
375
|
+
providerFinalModel: stats.providerFinalModel,
|
|
376
|
+
providerUsingFallback: stats.providerUsingFallback,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
if ((stats.providerFallbackCount ?? 0) > 0 || stats.providerUsingFallback) {
|
|
380
|
+
recordTraceEvent(events, sessionId, {
|
|
381
|
+
turnId,
|
|
382
|
+
type: 'provider_fallback',
|
|
383
|
+
providerFallbackCount: stats.providerFallbackCount,
|
|
384
|
+
providerFallbackFromModel: stats.providerFallbackFromModel,
|
|
385
|
+
providerFallbackToModel: stats.providerFallbackToModel,
|
|
386
|
+
providerFinalModel: stats.providerFinalModel,
|
|
387
|
+
providerUsingFallback: stats.providerUsingFallback,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function toHarnessDiagnostics(state) {
|
|
392
|
+
const stats = state.promptAssemblyStats;
|
|
393
|
+
const redactOptional = (value) => typeof value === 'string' ? (0, session_storage_1.redactTraceText)(value) : undefined;
|
|
394
|
+
const redactList = (values) => values?.slice(0, 6).map(session_storage_1.redactTraceText);
|
|
395
|
+
return {
|
|
396
|
+
taskEpoch: state.taskEpoch,
|
|
397
|
+
rootObjective: redactOptional(state.rootObjective ?? state.contract?.objective),
|
|
398
|
+
activeInstruction: redactOptional(state.activeInstruction ?? state.contract?.userIntent),
|
|
399
|
+
openQuestions: redactList(state.openQuestions),
|
|
400
|
+
diagnostics: redactList(state.diagnostics?.slice(-6)),
|
|
401
|
+
ledgerSize: state.ledger?.length ?? 0,
|
|
402
|
+
evidenceSize: state.evidenceIndex?.length ?? 0,
|
|
403
|
+
turnSummaryCount: state.turnSummaries?.length ?? 0,
|
|
404
|
+
promptAssembly: stats
|
|
405
|
+
? {
|
|
406
|
+
modelId: stats.modelId,
|
|
407
|
+
estimatedTokens: stats.estimatedTokens,
|
|
408
|
+
budgetTokens: stats.budgetTokens,
|
|
409
|
+
sections: stats.sections.slice(0, 12),
|
|
410
|
+
includedEvidence: stats.includedEvidence.length,
|
|
411
|
+
omittedEvidence: stats.omittedEvidence.length,
|
|
412
|
+
}
|
|
413
|
+
: undefined,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function emitHarnessDiagnostics(events, state) {
|
|
417
|
+
events.harnessDiagnosticsUpdated?.(toHarnessDiagnostics(state));
|
|
418
|
+
}
|
|
67
419
|
function toolStartContent(name, args) {
|
|
68
420
|
if (name === 'exec_command' && typeof args.command === 'string') {
|
|
69
421
|
return `Running ${name}\n $ ${args.command}`;
|
|
@@ -254,8 +606,76 @@ function createAssistantStreamPresenter(events, abortSignal) {
|
|
|
254
606
|
activeSegmentText = content;
|
|
255
607
|
ensureLiveEntry();
|
|
256
608
|
},
|
|
609
|
+
replaceMessage(content) {
|
|
610
|
+
if (abortSignal?.aborted || !content)
|
|
611
|
+
return;
|
|
612
|
+
activeSegmentText = content;
|
|
613
|
+
ensureLiveEntry();
|
|
614
|
+
},
|
|
257
615
|
};
|
|
258
616
|
}
|
|
617
|
+
class LocalFastPathBlockedError extends Error {
|
|
618
|
+
constructor(message) {
|
|
619
|
+
super(message);
|
|
620
|
+
this.name = 'LocalFastPathBlockedError';
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function parseLocalFastPath(input) {
|
|
624
|
+
const text = input.trim();
|
|
625
|
+
if (/^git\s+status$/i.test(text)) {
|
|
626
|
+
return { tool: 'git_status', args: {}, label: 'git status' };
|
|
627
|
+
}
|
|
628
|
+
const readMatch = /^(?:read|读取)\s+(.+)$/i.exec(text);
|
|
629
|
+
const readTarget = readMatch?.[1]?.trim();
|
|
630
|
+
const looksLikePath = Boolean(readTarget)
|
|
631
|
+
&& !/\s/.test(readTarget)
|
|
632
|
+
&& (/[/\\.]/.test(readTarget) || readTarget.startsWith('~'));
|
|
633
|
+
if (readTarget && looksLikePath) {
|
|
634
|
+
return { tool: 'read_file', args: { path: readTarget }, label: `read ${readTarget}` };
|
|
635
|
+
}
|
|
636
|
+
const grepMatch = /^(?:grep|搜索)\s+(.+)$/i.exec(text);
|
|
637
|
+
if (grepMatch?.[1]?.trim()) {
|
|
638
|
+
return { tool: 'grep', args: { pattern: grepMatch[1].trim() }, label: `grep ${grepMatch[1].trim()}` };
|
|
639
|
+
}
|
|
640
|
+
const runTestMatch = /^(?:run\s+test|运行测试)\s*[::]\s*(.+)$/i.exec(text);
|
|
641
|
+
if (runTestMatch?.[1]?.trim()) {
|
|
642
|
+
return { tool: 'exec_command', args: { command: runTestMatch[1].trim() }, label: `run test: ${runTestMatch[1].trim()}` };
|
|
643
|
+
}
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
function formatLocalFastPathAssistantContent(action, rawResult, projectPath) {
|
|
647
|
+
const envelope = (0, tool_serializer_1.parseToolResultEnvelope)(rawResult);
|
|
648
|
+
const output = typeof envelope.output === 'string' ? envelope.output.trim() : '';
|
|
649
|
+
const summary = envelope.summary || `${action.tool} ${envelope.success ? 'completed' : 'failed'}`;
|
|
650
|
+
const lines = [
|
|
651
|
+
envelope.success
|
|
652
|
+
? `Local fast path completed ${action.label}.`
|
|
653
|
+
: `Local fast path failed ${action.label}.`,
|
|
654
|
+
'',
|
|
655
|
+
summary,
|
|
656
|
+
];
|
|
657
|
+
if (!envelope.success && envelope.error) {
|
|
658
|
+
lines.push(`Error: ${envelope.error}`);
|
|
659
|
+
}
|
|
660
|
+
if (!output) {
|
|
661
|
+
return lines.join('\n');
|
|
662
|
+
}
|
|
663
|
+
let artifactRef = envelope.artifactRef;
|
|
664
|
+
let preview = output;
|
|
665
|
+
const outputBytes = envelope.outputBytes ?? byteLength(output);
|
|
666
|
+
if (byteLength(output) > LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES) {
|
|
667
|
+
if (!artifactRef) {
|
|
668
|
+
const artifact = (0, tool_artifacts_1.storeArtifact)(projectPath, action.tool, output, outputBytes);
|
|
669
|
+
artifactRef = artifact ? { id: artifact.id, outputBytes: artifact.outputBytes } : undefined;
|
|
670
|
+
}
|
|
671
|
+
preview = (0, tool_artifacts_1.truncateForContext)(output, LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES);
|
|
672
|
+
}
|
|
673
|
+
if (artifactRef) {
|
|
674
|
+
lines.push('', `Full output: /artifacts show ${artifactRef.id} --full (${artifactRef.outputBytes}B)`);
|
|
675
|
+
}
|
|
676
|
+
lines.push('', 'Preview:', preview);
|
|
677
|
+
return lines.join('\n');
|
|
678
|
+
}
|
|
259
679
|
function createToolEventPresenter(events) {
|
|
260
680
|
const runningToolEntries = new Map();
|
|
261
681
|
return {
|
|
@@ -331,12 +751,21 @@ class AgentChatController {
|
|
|
331
751
|
this.events = events;
|
|
332
752
|
this.controllerOptions = controllerOptions;
|
|
333
753
|
}
|
|
754
|
+
setLoopStats(stats) {
|
|
755
|
+
this.runtime.store.setLastLoopStats(stats);
|
|
756
|
+
this.events.loopStatsUpdated?.(stats);
|
|
757
|
+
}
|
|
334
758
|
async runInput(input, options = {}) {
|
|
335
759
|
const text = input.trim();
|
|
336
760
|
if (!text)
|
|
337
761
|
return;
|
|
338
762
|
const parsed = (0, parser_1.parseInput)(text);
|
|
339
763
|
if (!parsed.isCommand) {
|
|
764
|
+
const localFastPath = parseLocalFastPath(text);
|
|
765
|
+
if (localFastPath) {
|
|
766
|
+
await this.runLocalFastPath(text, localFastPath, options);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
340
769
|
await this.runChat(text, options);
|
|
341
770
|
return;
|
|
342
771
|
}
|
|
@@ -400,6 +829,179 @@ class AgentChatController {
|
|
|
400
829
|
await this.runChat(result.chatInput ?? parsed.args, options);
|
|
401
830
|
}
|
|
402
831
|
}
|
|
832
|
+
async runLocalFastPath(input, action, options = {}) {
|
|
833
|
+
const activeSession = this.runtime.getSession() ?? this.runtime.ensureSession() ?? (0, session_storage_1.loadSessionMeta)(this.runtime.getSession()?.id ?? '');
|
|
834
|
+
const sessionId = activeSession?.id;
|
|
835
|
+
const turnId = traceTurnId(options.turnId);
|
|
836
|
+
const localCallId = `local-${turnId}`;
|
|
837
|
+
const start = Date.now();
|
|
838
|
+
const preWorkspace = (0, workspace_state_1.captureWorkspaceSnapshot)(this.runtime.cwd);
|
|
839
|
+
const traceArgs = buildTraceArgsDetails(this.runtime.cwd, action.tool, action.args);
|
|
840
|
+
if (sessionId) {
|
|
841
|
+
(0, session_storage_1.appendSessionMessage)(sessionId, {
|
|
842
|
+
role: 'user',
|
|
843
|
+
content: input,
|
|
844
|
+
timestamp: Date.now(),
|
|
845
|
+
});
|
|
846
|
+
recordTraceEvent(this.events, sessionId, {
|
|
847
|
+
turnId,
|
|
848
|
+
type: 'turn_start',
|
|
849
|
+
inputBytes: byteLength(input),
|
|
850
|
+
localFastPathUsed: true,
|
|
851
|
+
});
|
|
852
|
+
appendWorkspaceSnapshotTrace(this.events, sessionId, turnId, 'pre_turn', preWorkspace);
|
|
853
|
+
recordTraceEvent(this.events, sessionId, {
|
|
854
|
+
turnId,
|
|
855
|
+
type: 'local_fast_path',
|
|
856
|
+
name: action.tool,
|
|
857
|
+
...traceArgs,
|
|
858
|
+
note: compactMiddle(action.label, 160),
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
this.runtime.store.addMessage({ role: 'user', content: input });
|
|
862
|
+
this.events.setStatus(`Running local ${action.label}...`);
|
|
863
|
+
try {
|
|
864
|
+
const tool = (0, tools_1.getRuntimeTools)().find(candidate => candidate.name === action.tool);
|
|
865
|
+
const toolContext = {
|
|
866
|
+
cwd: this.runtime.cwd,
|
|
867
|
+
config: {
|
|
868
|
+
name: this.runtime.config.name,
|
|
869
|
+
mode: this.runtime.config.mode,
|
|
870
|
+
},
|
|
871
|
+
sessionId,
|
|
872
|
+
turnId,
|
|
873
|
+
};
|
|
874
|
+
const permission = tool?.checkPermissions?.(action.args, toolContext);
|
|
875
|
+
if (permission?.behavior === 'deny' || tool?.isDestructive?.(action.args) === true) {
|
|
876
|
+
const reason = permission?.reason || 'Local fast path blocked a destructive tool request.';
|
|
877
|
+
throw new LocalFastPathBlockedError(reason);
|
|
878
|
+
}
|
|
879
|
+
if (permission?.behavior === 'ask') {
|
|
880
|
+
throw new LocalFastPathBlockedError(permission.reason || 'Local fast path requires an allow-safe command.');
|
|
881
|
+
}
|
|
882
|
+
if (sessionId) {
|
|
883
|
+
recordTraceEvent(this.events, sessionId, {
|
|
884
|
+
turnId,
|
|
885
|
+
type: 'tool_call',
|
|
886
|
+
name: action.tool,
|
|
887
|
+
callId: localCallId,
|
|
888
|
+
...traceArgs,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
const result = await (0, tools_1.executeTool)(action.tool, action.args, options.abortSignal, {
|
|
892
|
+
...toolContext,
|
|
893
|
+
sessionId,
|
|
894
|
+
turnId,
|
|
895
|
+
});
|
|
896
|
+
const duration = Date.now() - start;
|
|
897
|
+
const envelope = (0, tool_serializer_1.parseToolResultEnvelope)(result);
|
|
898
|
+
const outputBytes = typeof envelope.outputBytes === 'number'
|
|
899
|
+
? envelope.outputBytes
|
|
900
|
+
: Buffer.byteLength(result, 'utf8');
|
|
901
|
+
const assistantContent = formatLocalFastPathAssistantContent(action, result, this.runtime.cwd);
|
|
902
|
+
const stats = (0, framework_1.createLocalFastPathLoopStats)({
|
|
903
|
+
finishReason: envelope.success ? 'completed' : 'failed',
|
|
904
|
+
toolCalls: 1,
|
|
905
|
+
readOnlyToolCalls: action.tool === 'exec_command' ? 0 : 1,
|
|
906
|
+
unsafeToolCalls: action.tool === 'exec_command' ? 1 : 0,
|
|
907
|
+
toolResultBytes: outputBytes,
|
|
908
|
+
modelVisibleToolBytes: 0,
|
|
909
|
+
summarizedBytes: outputBytes,
|
|
910
|
+
});
|
|
911
|
+
this.events.append({
|
|
912
|
+
role: envelope.success ? 'tool' : 'error',
|
|
913
|
+
title: 'local',
|
|
914
|
+
content: toolFinishContent({
|
|
915
|
+
type: 'tool_result',
|
|
916
|
+
name: action.tool,
|
|
917
|
+
args: action.args,
|
|
918
|
+
callId: localCallId,
|
|
919
|
+
result,
|
|
920
|
+
modelVisibleResult: result,
|
|
921
|
+
duration,
|
|
922
|
+
success: envelope.success,
|
|
923
|
+
error: envelope.error,
|
|
924
|
+
summary: envelope.summary,
|
|
925
|
+
outputBytes,
|
|
926
|
+
}),
|
|
927
|
+
});
|
|
928
|
+
this.runtime.store.addMessage({ role: 'assistant', content: assistantContent });
|
|
929
|
+
this.setLoopStats(stats);
|
|
930
|
+
if (sessionId) {
|
|
931
|
+
recordTraceEvent(this.events, sessionId, {
|
|
932
|
+
turnId,
|
|
933
|
+
type: 'tool_result',
|
|
934
|
+
name: action.tool,
|
|
935
|
+
callId: localCallId,
|
|
936
|
+
argsSummary: traceArgs.argsSummary,
|
|
937
|
+
argsArtifactId: traceArgs.argsArtifactId,
|
|
938
|
+
argsBytes: traceArgs.argsBytes,
|
|
939
|
+
success: envelope.success,
|
|
940
|
+
duration,
|
|
941
|
+
outputBytes,
|
|
942
|
+
modelVisibleBytes: 0,
|
|
943
|
+
error: envelope.error ? compactMiddle(envelope.error, 240) : undefined,
|
|
944
|
+
});
|
|
945
|
+
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace);
|
|
946
|
+
recordTraceEvent(this.events, sessionId, {
|
|
947
|
+
turnId,
|
|
948
|
+
type: 'complete',
|
|
949
|
+
finishReason: stats.finishReason,
|
|
950
|
+
llmRequests: stats.llmRequests,
|
|
951
|
+
toolCalls: stats.toolCalls,
|
|
952
|
+
readOnlyToolCalls: stats.readOnlyToolCalls,
|
|
953
|
+
unsafeToolCalls: stats.unsafeToolCalls,
|
|
954
|
+
localFastPathUsed: true,
|
|
955
|
+
});
|
|
956
|
+
(0, session_storage_1.appendSessionMessage)(sessionId, {
|
|
957
|
+
role: 'assistant',
|
|
958
|
+
content: assistantContent,
|
|
959
|
+
timestamp: Date.now(),
|
|
960
|
+
});
|
|
961
|
+
const recordedMessages = (0, session_storage_1.readSessionMessages)(sessionId);
|
|
962
|
+
if (recordedMessages.length > 0) {
|
|
963
|
+
(0, session_storage_1.updateSessionSummary)(sessionId, recordedMessages);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
this.events.setStatus(envelope.success ? `Completed local ${action.label}` : `Failed local ${action.label}`);
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
970
|
+
this.events.append({ role: 'error', title: 'local', content: message });
|
|
971
|
+
this.events.setStatus('Local command failed. Ready for the next input.');
|
|
972
|
+
const assistantContent = `Local fast path failed for ${action.label}.\n\n${message}`;
|
|
973
|
+
this.runtime.store.addMessage({ role: 'assistant', content: assistantContent });
|
|
974
|
+
const finishReason = error instanceof LocalFastPathBlockedError ? 'blocked' : 'failed';
|
|
975
|
+
const stats = (0, framework_1.createLocalFastPathLoopStats)({
|
|
976
|
+
finishReason,
|
|
977
|
+
toolCalls: 0,
|
|
978
|
+
});
|
|
979
|
+
this.setLoopStats(stats);
|
|
980
|
+
if (sessionId) {
|
|
981
|
+
recordTraceEvent(this.events, sessionId, {
|
|
982
|
+
turnId,
|
|
983
|
+
type: 'error',
|
|
984
|
+
name: action.tool,
|
|
985
|
+
error: compactTraceError(error),
|
|
986
|
+
});
|
|
987
|
+
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace);
|
|
988
|
+
recordTraceEvent(this.events, sessionId, {
|
|
989
|
+
turnId,
|
|
990
|
+
type: 'complete',
|
|
991
|
+
finishReason: stats.finishReason,
|
|
992
|
+
llmRequests: stats.llmRequests,
|
|
993
|
+
toolCalls: stats.toolCalls,
|
|
994
|
+
localFastPathUsed: true,
|
|
995
|
+
note: 'local_fast_path_failed',
|
|
996
|
+
});
|
|
997
|
+
(0, session_storage_1.appendSessionMessage)(sessionId, {
|
|
998
|
+
role: 'assistant',
|
|
999
|
+
content: assistantContent,
|
|
1000
|
+
timestamp: Date.now(),
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
403
1005
|
createCommandContext(abortSignal, turnId) {
|
|
404
1006
|
return {
|
|
405
1007
|
cwd: this.runtime.cwd,
|
|
@@ -414,6 +1016,9 @@ class AgentChatController {
|
|
|
414
1016
|
this.runtime.setSession(session);
|
|
415
1017
|
this.events.replaceTranscript(sessionMessagesToTranscriptEntries(session.id));
|
|
416
1018
|
},
|
|
1019
|
+
sessionRestored: event => {
|
|
1020
|
+
this.events.sessionRestored?.(event);
|
|
1021
|
+
},
|
|
417
1022
|
getSession: this.runtime.getSession,
|
|
418
1023
|
abortSignal,
|
|
419
1024
|
writeOutput: text => {
|
|
@@ -442,9 +1047,10 @@ class AgentChatController {
|
|
|
442
1047
|
return;
|
|
443
1048
|
}
|
|
444
1049
|
const abortSignal = options.abortSignal;
|
|
445
|
-
const turnId = options.turnId;
|
|
1050
|
+
const turnId = traceTurnId(options.turnId);
|
|
446
1051
|
const activeSession = this.runtime.getSession() ?? this.runtime.ensureSession() ?? (0, session_storage_1.loadSessionMeta)(this.runtime.getSession()?.id ?? '');
|
|
447
1052
|
const sessionId = activeSession?.id;
|
|
1053
|
+
const preWorkspace = (0, workspace_state_1.captureWorkspaceSnapshot)(this.runtime.cwd);
|
|
448
1054
|
const runtimeTools = (0, tools_1.getRuntimeTools)();
|
|
449
1055
|
const skillResolution = (0, skills_1.resolveSkillsForTurn)({
|
|
450
1056
|
cwd: this.runtime.cwd,
|
|
@@ -461,6 +1067,13 @@ class AgentChatController {
|
|
|
461
1067
|
timestamp: Date.now(),
|
|
462
1068
|
appliedSkills: appliedSkillNames.length > 0 ? appliedSkillNames : undefined,
|
|
463
1069
|
});
|
|
1070
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1071
|
+
turnId,
|
|
1072
|
+
type: 'turn_start',
|
|
1073
|
+
inputBytes: byteLength(input),
|
|
1074
|
+
note: appliedSkillNames.length > 0 ? `skills=${appliedSkillNames.join(',')}` : undefined,
|
|
1075
|
+
});
|
|
1076
|
+
appendWorkspaceSnapshotTrace(this.events, sessionId, turnId, 'pre_turn', preWorkspace);
|
|
464
1077
|
}
|
|
465
1078
|
this.runtime.store.addMessage({ role: 'user', content: input });
|
|
466
1079
|
(0, prompt_context_1.refreshProjectInstructions)(this.runtime.store, this.runtime.cwd);
|
|
@@ -497,9 +1110,14 @@ class AgentChatController {
|
|
|
497
1110
|
let finalContent = '';
|
|
498
1111
|
let finalUsage;
|
|
499
1112
|
let finalModel = '';
|
|
1113
|
+
let pendingCompleteTrace = null;
|
|
1114
|
+
let pendingCompleteStats;
|
|
1115
|
+
const verificationResults = [];
|
|
500
1116
|
const sessionMessagesToRecord = [];
|
|
501
1117
|
const assistantStream = createAssistantStreamPresenter(this.events, abortSignal);
|
|
502
1118
|
const toolEvents = createToolEventPresenter(this.events);
|
|
1119
|
+
let checkpointSequence = 0;
|
|
1120
|
+
const checkpointIds = [];
|
|
503
1121
|
const streamCallbacks = {
|
|
504
1122
|
onChunk: chunk => {
|
|
505
1123
|
assistantStream.appendChunk(chunk);
|
|
@@ -524,6 +1142,14 @@ class AgentChatController {
|
|
|
524
1142
|
turnId,
|
|
525
1143
|
});
|
|
526
1144
|
};
|
|
1145
|
+
const loopBudget = (0, loop_budget_1.resolveRuntimeLoopBudget)(input, this.runtime.config, harness.toJSON());
|
|
1146
|
+
let observedTurnsStarted = 0;
|
|
1147
|
+
let observedLlmRequests = 0;
|
|
1148
|
+
let observedToolCalls = 0;
|
|
1149
|
+
let observedReadOnlyToolCalls = 0;
|
|
1150
|
+
let observedUnsafeToolCalls = 0;
|
|
1151
|
+
let observedToolResultBytes = 0;
|
|
1152
|
+
let observedModelVisibleToolBytes = 0;
|
|
527
1153
|
try {
|
|
528
1154
|
for await (const event of (0, framework_1.query)({
|
|
529
1155
|
messages,
|
|
@@ -547,11 +1173,41 @@ class AgentChatController {
|
|
|
547
1173
|
abortSignal,
|
|
548
1174
|
harness,
|
|
549
1175
|
input,
|
|
1176
|
+
loopBudget,
|
|
550
1177
|
})) {
|
|
551
1178
|
switch (event.type) {
|
|
552
1179
|
case 'request_start':
|
|
1180
|
+
observedTurnsStarted = Math.max(observedTurnsStarted, event.turn);
|
|
1181
|
+
observedLlmRequests++;
|
|
553
1182
|
assistantStream.discardSegment();
|
|
554
1183
|
this.events.setStatus((0, agent_status_1.agentStepStatus)(event.turn));
|
|
1184
|
+
if (sessionId) {
|
|
1185
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1186
|
+
turnId,
|
|
1187
|
+
type: 'request_start',
|
|
1188
|
+
model: event.model,
|
|
1189
|
+
turn: event.turn,
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
break;
|
|
1193
|
+
case 'prompt_assembly':
|
|
1194
|
+
if (sessionId) {
|
|
1195
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1196
|
+
turnId,
|
|
1197
|
+
type: 'prompt_assembly',
|
|
1198
|
+
promptModelId: event.modelId,
|
|
1199
|
+
promptEstimatedTokens: event.estimatedTokens,
|
|
1200
|
+
promptBudgetTokens: event.budgetTokens,
|
|
1201
|
+
promptCoreTokens: event.coreTokens,
|
|
1202
|
+
promptEvidenceBudgetTokens: event.evidenceBudgetTokens,
|
|
1203
|
+
promptRecentTurnBudgetTokens: event.recentTurnBudgetTokens,
|
|
1204
|
+
promptSections: event.sections,
|
|
1205
|
+
promptIncludedEvidence: event.includedEvidence,
|
|
1206
|
+
promptOmittedEvidence: event.omittedEvidence,
|
|
1207
|
+
promptIncludedEvidenceCount: event.includedEvidenceCount,
|
|
1208
|
+
promptOmittedEvidenceCount: event.omittedEvidenceCount,
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
555
1211
|
break;
|
|
556
1212
|
case 'assistant_tool_calls':
|
|
557
1213
|
assistantStream.ensureMessage(event.content || '');
|
|
@@ -559,6 +1215,21 @@ class AgentChatController {
|
|
|
559
1215
|
if (event.toolCalls.length > 1) {
|
|
560
1216
|
this.events.setStatus((0, agent_status_1.runningToolsStatus)(event.toolCalls.length));
|
|
561
1217
|
}
|
|
1218
|
+
const checkpointId = checkpointSequence === 0
|
|
1219
|
+
? turnId
|
|
1220
|
+
: `${turnId}-checkpoint-${checkpointSequence + 1}`;
|
|
1221
|
+
if (createPreToolCheckpoint(this.events, sessionId, turnId, checkpointId, this.runtime.cwd, event.toolCalls)) {
|
|
1222
|
+
checkpointIds.push(checkpointId);
|
|
1223
|
+
checkpointSequence++;
|
|
1224
|
+
}
|
|
1225
|
+
if (sessionId) {
|
|
1226
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1227
|
+
turnId,
|
|
1228
|
+
type: 'assistant_tool_calls',
|
|
1229
|
+
toolCallCount: event.toolCalls.length,
|
|
1230
|
+
contentBytes: byteLength(event.content || ''),
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
562
1233
|
sessionMessagesToRecord.push({
|
|
563
1234
|
role: 'assistant',
|
|
564
1235
|
content: event.content || '',
|
|
@@ -567,11 +1238,83 @@ class AgentChatController {
|
|
|
567
1238
|
});
|
|
568
1239
|
break;
|
|
569
1240
|
case 'tool_call':
|
|
1241
|
+
observedToolCalls++;
|
|
1242
|
+
{
|
|
1243
|
+
const toolDefinition = skillResolution.tools.find(tool => tool.name === event.name);
|
|
1244
|
+
if (toolDefinition?.isReadOnly?.(event.args) === true) {
|
|
1245
|
+
observedReadOnlyToolCalls++;
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
observedUnsafeToolCalls++;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
570
1251
|
assistantStream.closeSegment();
|
|
571
1252
|
toolEvents.start(event);
|
|
1253
|
+
if (sessionId) {
|
|
1254
|
+
const traceArgs = buildTraceArgsDetails(this.runtime.cwd, event.name, event.args);
|
|
1255
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1256
|
+
turnId,
|
|
1257
|
+
type: 'tool_call',
|
|
1258
|
+
name: event.name,
|
|
1259
|
+
callId: event.callId,
|
|
1260
|
+
...traceArgs,
|
|
1261
|
+
batchCount: event.batchCount,
|
|
1262
|
+
batchIndex: event.batchIndex,
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
break;
|
|
1266
|
+
case 'permission_decision':
|
|
1267
|
+
if (sessionId) {
|
|
1268
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1269
|
+
turnId,
|
|
1270
|
+
type: 'permission_decision',
|
|
1271
|
+
name: event.name,
|
|
1272
|
+
callId: event.callId,
|
|
1273
|
+
argsSummary: compactToolArgs(event.args),
|
|
1274
|
+
permissionBehavior: event.decision.behavior,
|
|
1275
|
+
permissionApproved: event.decision.approved,
|
|
1276
|
+
permissionSource: event.decision.source,
|
|
1277
|
+
permissionReason: event.decision.reason
|
|
1278
|
+
? compactMiddle(event.decision.reason, 240)
|
|
1279
|
+
: undefined,
|
|
1280
|
+
permissionDuration: event.decision.duration,
|
|
1281
|
+
batchCount: event.batchCount,
|
|
1282
|
+
batchIndex: event.batchIndex,
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
572
1285
|
break;
|
|
573
1286
|
case 'tool_result': {
|
|
1287
|
+
observedToolResultBytes += event.outputBytes ?? byteLength(event.result);
|
|
1288
|
+
observedModelVisibleToolBytes += byteLength(event.modelVisibleResult);
|
|
574
1289
|
toolEvents.finish(event);
|
|
1290
|
+
if (sessionId) {
|
|
1291
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1292
|
+
turnId,
|
|
1293
|
+
type: 'tool_result',
|
|
1294
|
+
name: event.name,
|
|
1295
|
+
callId: event.callId,
|
|
1296
|
+
argsSummary: compactToolArgs(event.args),
|
|
1297
|
+
success: event.success,
|
|
1298
|
+
duration: event.duration,
|
|
1299
|
+
outputBytes: event.outputBytes,
|
|
1300
|
+
modelVisibleBytes: byteLength(event.modelVisibleResult),
|
|
1301
|
+
artifactId: event.artifactRef?.id,
|
|
1302
|
+
error: event.error ? compactMiddle(event.error, 240) : undefined,
|
|
1303
|
+
batchCount: event.batchCount,
|
|
1304
|
+
batchIndex: event.batchIndex,
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
const verificationResult = (0, verification_profile_1.collectVerificationCommandResult)({
|
|
1308
|
+
toolName: event.name,
|
|
1309
|
+
args: event.args,
|
|
1310
|
+
success: event.success,
|
|
1311
|
+
outputBytes: event.outputBytes,
|
|
1312
|
+
error: event.error,
|
|
1313
|
+
});
|
|
1314
|
+
if (verificationResult) {
|
|
1315
|
+
verificationResults.push(verificationResult);
|
|
1316
|
+
appendVerificationResultTrace(this.events, sessionId, turnId, verificationResult);
|
|
1317
|
+
}
|
|
575
1318
|
sessionMessagesToRecord.push({
|
|
576
1319
|
role: 'tool',
|
|
577
1320
|
content: event.result,
|
|
@@ -583,10 +1326,24 @@ class AgentChatController {
|
|
|
583
1326
|
}
|
|
584
1327
|
case 'strategy_exhausted':
|
|
585
1328
|
this.events.append({ role: 'status', content: event.suggestion });
|
|
1329
|
+
if (sessionId) {
|
|
1330
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1331
|
+
turnId,
|
|
1332
|
+
type: 'strategy_exhausted',
|
|
1333
|
+
note: compactMiddle(event.suggestion, 240),
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
586
1336
|
break;
|
|
587
1337
|
case 'message':
|
|
588
1338
|
finalContent = event.content;
|
|
589
1339
|
assistantStream.ensureMessage(event.content);
|
|
1340
|
+
if (sessionId) {
|
|
1341
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1342
|
+
turnId,
|
|
1343
|
+
type: 'message',
|
|
1344
|
+
contentBytes: byteLength(event.content),
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
590
1347
|
if (event.content) {
|
|
591
1348
|
sessionMessagesToRecord.push({
|
|
592
1349
|
role: 'assistant',
|
|
@@ -596,11 +1353,55 @@ class AgentChatController {
|
|
|
596
1353
|
}
|
|
597
1354
|
break;
|
|
598
1355
|
case 'complete':
|
|
1356
|
+
if (event.content && !finalContent) {
|
|
1357
|
+
if (event.stats?.finishReason === 'budget_exceeded') {
|
|
1358
|
+
assistantStream.replaceMessage(event.content);
|
|
1359
|
+
}
|
|
1360
|
+
else {
|
|
1361
|
+
assistantStream.ensureMessage(event.content);
|
|
1362
|
+
}
|
|
1363
|
+
sessionMessagesToRecord.push({
|
|
1364
|
+
role: 'assistant',
|
|
1365
|
+
content: event.content,
|
|
1366
|
+
timestamp: Date.now(),
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
599
1369
|
finalContent = event.content;
|
|
600
1370
|
finalUsage = event.usage;
|
|
601
1371
|
finalModel = event.model;
|
|
602
1372
|
if (event.stats) {
|
|
603
|
-
|
|
1373
|
+
pendingCompleteStats = event.stats;
|
|
1374
|
+
recordProviderTraceEvents(this.events, sessionId, turnId, event.stats);
|
|
1375
|
+
pendingCompleteTrace = {
|
|
1376
|
+
turnId,
|
|
1377
|
+
type: 'complete',
|
|
1378
|
+
model: event.model,
|
|
1379
|
+
contentBytes: byteLength(event.content || ''),
|
|
1380
|
+
finishReason: event.stats.finishReason,
|
|
1381
|
+
llmRequests: event.stats.llmRequests,
|
|
1382
|
+
toolCalls: event.stats.toolCalls,
|
|
1383
|
+
readOnlyToolCalls: event.stats.readOnlyToolCalls,
|
|
1384
|
+
unsafeToolCalls: event.stats.unsafeToolCalls,
|
|
1385
|
+
loopBudgetSource: event.stats.loopBudgetSource,
|
|
1386
|
+
loopBudgetBaseProfile: event.stats.loopBudgetBaseProfile,
|
|
1387
|
+
loopBudgetMaxLlmRequests: event.stats.loopBudgetMaxLlmRequests,
|
|
1388
|
+
loopBudgetMaxToolCalls: event.stats.loopBudgetMaxToolCalls,
|
|
1389
|
+
loopBudgetMaxReadOnlyFragmentation: event.stats.loopBudgetMaxReadOnlyFragmentation,
|
|
1390
|
+
loopBudgetMaxModelVisibleBytes: event.stats.loopBudgetMaxModelVisibleBytes,
|
|
1391
|
+
loopBudgetConfigOverride: event.stats.loopBudgetConfigOverride,
|
|
1392
|
+
budgetExceededReason: event.stats.budgetExceededReason,
|
|
1393
|
+
continuationActions: event.stats.continuationActions,
|
|
1394
|
+
continuationHint: event.stats.continuationHint,
|
|
1395
|
+
localFastPathUsed: event.stats.localFastPathUsed,
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
else {
|
|
1399
|
+
pendingCompleteTrace = {
|
|
1400
|
+
turnId,
|
|
1401
|
+
type: 'complete',
|
|
1402
|
+
model: event.model,
|
|
1403
|
+
contentBytes: byteLength(event.content || ''),
|
|
1404
|
+
};
|
|
604
1405
|
}
|
|
605
1406
|
break;
|
|
606
1407
|
}
|
|
@@ -611,11 +1412,51 @@ class AgentChatController {
|
|
|
611
1412
|
this.events.setStatus('Interrupted.');
|
|
612
1413
|
removeTrailingUserMessage(this.runtime);
|
|
613
1414
|
if (sessionId) {
|
|
1415
|
+
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1416
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1417
|
+
turnId,
|
|
1418
|
+
type: 'aborted',
|
|
1419
|
+
note: 'aborted_after_query',
|
|
1420
|
+
});
|
|
614
1421
|
(0, session_storage_1.removeLastIncompleteAssistantMessage)(sessionId);
|
|
615
1422
|
}
|
|
616
1423
|
return;
|
|
617
1424
|
}
|
|
618
1425
|
assistantStream.closeSegment();
|
|
1426
|
+
if (sessionId) {
|
|
1427
|
+
const { profile, summary } = appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1428
|
+
if (shouldRecordVerificationLoopStats(profile, summary)) {
|
|
1429
|
+
const stats = pendingCompleteStats ?? this.runtime.store.getSnapshot().lastLoopStats;
|
|
1430
|
+
if (stats) {
|
|
1431
|
+
pendingCompleteStats = withVerificationLoopStats(stats, summary);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
if ((0, verification_profile_1.shouldGateCompletion)(summary)) {
|
|
1435
|
+
const notice = (0, verification_profile_1.formatVerificationGateNotice)(summary);
|
|
1436
|
+
this.events.append({
|
|
1437
|
+
role: 'status',
|
|
1438
|
+
title: 'verification',
|
|
1439
|
+
content: notice,
|
|
1440
|
+
});
|
|
1441
|
+
finalContent = finalContent ? `${finalContent}\n\n${notice}` : notice;
|
|
1442
|
+
appendAssistantNotice(sessionMessagesToRecord, notice);
|
|
1443
|
+
if (pendingCompleteTrace) {
|
|
1444
|
+
pendingCompleteTrace.finishReason = 'completion_gate';
|
|
1445
|
+
pendingCompleteTrace.contentBytes = byteLength(finalContent);
|
|
1446
|
+
pendingCompleteTrace.note = 'verification_incomplete';
|
|
1447
|
+
}
|
|
1448
|
+
const stats = pendingCompleteStats ?? this.runtime.store.getSnapshot().lastLoopStats;
|
|
1449
|
+
if (stats) {
|
|
1450
|
+
pendingCompleteStats = {
|
|
1451
|
+
...withVerificationLoopStats(stats, summary),
|
|
1452
|
+
finishReason: 'completion_gate',
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
if (pendingCompleteStats) {
|
|
1458
|
+
this.setLoopStats(pendingCompleteStats);
|
|
1459
|
+
}
|
|
619
1460
|
if (finalContent) {
|
|
620
1461
|
this.runtime.store.addMessage({ role: 'assistant', content: finalContent });
|
|
621
1462
|
}
|
|
@@ -633,7 +1474,11 @@ class AgentChatController {
|
|
|
633
1474
|
});
|
|
634
1475
|
const harnessState = harness.toJSON();
|
|
635
1476
|
this.runtime.store.setState({ harnessState });
|
|
1477
|
+
emitHarnessDiagnostics(this.events, harnessState);
|
|
636
1478
|
if (sessionId) {
|
|
1479
|
+
if (pendingCompleteTrace) {
|
|
1480
|
+
recordTraceEvent(this.events, sessionId, pendingCompleteTrace);
|
|
1481
|
+
}
|
|
637
1482
|
(0, session_storage_1.updateSessionSkills)(sessionId, appliedSkillNames);
|
|
638
1483
|
(0, session_storage_1.updateSessionHarnessState)(sessionId, harnessState);
|
|
639
1484
|
const recordedMessages = (0, session_storage_1.readSessionMessages)(sessionId);
|
|
@@ -649,6 +1494,12 @@ class AgentChatController {
|
|
|
649
1494
|
this.events.setStatus('Interrupted.');
|
|
650
1495
|
removeTrailingUserMessage(this.runtime);
|
|
651
1496
|
if (sessionId) {
|
|
1497
|
+
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1498
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1499
|
+
turnId,
|
|
1500
|
+
type: 'aborted',
|
|
1501
|
+
note: 'abort_error',
|
|
1502
|
+
});
|
|
652
1503
|
(0, session_storage_1.removeLastIncompleteAssistantMessage)(sessionId);
|
|
653
1504
|
}
|
|
654
1505
|
return;
|
|
@@ -656,6 +1507,60 @@ class AgentChatController {
|
|
|
656
1507
|
assistantStream.discardSegment();
|
|
657
1508
|
this.events.append({ role: 'error', content: formatChatError(error) });
|
|
658
1509
|
this.events.setStatus('Turn failed. Ready for the next input.');
|
|
1510
|
+
const failedStats = error instanceof framework_1.QueryLoopError
|
|
1511
|
+
? error.stats
|
|
1512
|
+
: (0, framework_1.createFailedLoopStats)({
|
|
1513
|
+
loopBudget,
|
|
1514
|
+
diagnostics: observedLlmRequests > 0 ? getLastRequestDiagnostics(this.runtime.llm) : undefined,
|
|
1515
|
+
turnsStarted: observedTurnsStarted,
|
|
1516
|
+
llmRequests: observedLlmRequests,
|
|
1517
|
+
toolCalls: observedToolCalls,
|
|
1518
|
+
readOnlyToolCalls: observedReadOnlyToolCalls,
|
|
1519
|
+
unsafeToolCalls: observedUnsafeToolCalls,
|
|
1520
|
+
toolResultBytes: observedToolResultBytes,
|
|
1521
|
+
modelVisibleToolBytes: observedModelVisibleToolBytes,
|
|
1522
|
+
});
|
|
1523
|
+
this.setLoopStats(failedStats);
|
|
1524
|
+
if (sessionId) {
|
|
1525
|
+
recordProviderTraceEvents(this.events, sessionId, turnId, failedStats);
|
|
1526
|
+
const { delta } = appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1527
|
+
const recoveryNotice = workspaceDeltaHasTurnChanges(delta)
|
|
1528
|
+
? formatFailureRecoveryNotice(turnId, delta, checkpointIds)
|
|
1529
|
+
: undefined;
|
|
1530
|
+
if (recoveryNotice) {
|
|
1531
|
+
this.events.append({
|
|
1532
|
+
role: 'status',
|
|
1533
|
+
title: 'recovery',
|
|
1534
|
+
content: recoveryNotice,
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1538
|
+
turnId,
|
|
1539
|
+
type: 'error',
|
|
1540
|
+
error: compactTraceError(error),
|
|
1541
|
+
note: recoveryNotice,
|
|
1542
|
+
});
|
|
1543
|
+
recordTraceEvent(this.events, sessionId, {
|
|
1544
|
+
turnId,
|
|
1545
|
+
type: 'complete',
|
|
1546
|
+
model: failedStats.providerFinalModel ?? this.runtime.llm.getModel(),
|
|
1547
|
+
contentBytes: 0,
|
|
1548
|
+
finishReason: failedStats.finishReason,
|
|
1549
|
+
llmRequests: failedStats.llmRequests,
|
|
1550
|
+
toolCalls: failedStats.toolCalls,
|
|
1551
|
+
readOnlyToolCalls: failedStats.readOnlyToolCalls,
|
|
1552
|
+
unsafeToolCalls: failedStats.unsafeToolCalls,
|
|
1553
|
+
loopBudgetSource: failedStats.loopBudgetSource,
|
|
1554
|
+
loopBudgetBaseProfile: failedStats.loopBudgetBaseProfile,
|
|
1555
|
+
loopBudgetMaxLlmRequests: failedStats.loopBudgetMaxLlmRequests,
|
|
1556
|
+
loopBudgetMaxToolCalls: failedStats.loopBudgetMaxToolCalls,
|
|
1557
|
+
loopBudgetMaxReadOnlyFragmentation: failedStats.loopBudgetMaxReadOnlyFragmentation,
|
|
1558
|
+
loopBudgetMaxModelVisibleBytes: failedStats.loopBudgetMaxModelVisibleBytes,
|
|
1559
|
+
loopBudgetConfigOverride: failedStats.loopBudgetConfigOverride,
|
|
1560
|
+
localFastPathUsed: failedStats.localFastPathUsed,
|
|
1561
|
+
});
|
|
1562
|
+
(0, session_storage_1.removeLastIncompleteAssistantMessage)(sessionId);
|
|
1563
|
+
}
|
|
659
1564
|
const history = this.runtime.store.getSnapshot().conversationHistory;
|
|
660
1565
|
if (history.length > 0) {
|
|
661
1566
|
this.runtime.store.setState({ conversationHistory: history.slice(0, -1) });
|