openhorse 0.2.13 → 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 +217 -49
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/types.d.ts +4 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/core/checkpoint.d.ts +7 -0
- package/dist/core/checkpoint.d.ts.map +1 -1
- package/dist/core/checkpoint.js +10 -0
- 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/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/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-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 +4 -0
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +199 -67
- package/dist/runtime/chat-controller.js.map +1 -1
- 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 +7 -0
- 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/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/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/session-storage.d.ts +1 -0
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js.map +1 -1
- package/dist/services/verification-profile.d.ts +15 -0
- package/dist/services/verification-profile.d.ts.map +1 -1
- package/dist/services/verification-profile.js +67 -0
- package/dist/services/verification-profile.js.map +1 -1
- 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
|
@@ -53,14 +53,17 @@ const checkpoint_1 = require("../core/checkpoint");
|
|
|
53
53
|
const skills_1 = require("../skills");
|
|
54
54
|
const file_context_1 = require("../services/file-context");
|
|
55
55
|
const prompt_context_1 = require("../services/prompt-context");
|
|
56
|
+
const format_1 = require("../services/format");
|
|
56
57
|
const workspace_state_1 = require("../services/workspace-state");
|
|
57
58
|
const verification_profile_1 = require("../services/verification-profile");
|
|
58
59
|
const ui_events_1 = require("./ui-events");
|
|
60
|
+
const ui_view_model_1 = require("./ui-view-model");
|
|
59
61
|
const agent_status_1 = require("./agent-status");
|
|
60
62
|
const loop_budget_1 = require("./loop-budget");
|
|
61
63
|
const ANSI_PATTERN = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
62
64
|
const LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES = 2048;
|
|
63
65
|
const TRACE_ARGS_ARTIFACT_THRESHOLD_BYTES = 160;
|
|
66
|
+
const TOOL_TRANSCRIPT_ARG_BUDGET = 512;
|
|
64
67
|
function stripAnsi(text) {
|
|
65
68
|
return text.replace(ANSI_PATTERN, '');
|
|
66
69
|
}
|
|
@@ -72,6 +75,29 @@ function isAbortError(error, abortSignal) {
|
|
|
72
75
|
}
|
|
73
76
|
return false;
|
|
74
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
|
+
}
|
|
75
101
|
function formatChatError(error) {
|
|
76
102
|
const message = error instanceof Error ? error.message : String(error);
|
|
77
103
|
if (/NotEnoughCvError|code:\s*11210/i.test(message)) {
|
|
@@ -170,10 +196,11 @@ function checkpointTargetsFromToolCalls(cwd, toolCalls) {
|
|
|
170
196
|
function createPreToolCheckpoint(events, sessionId, turnId, checkpointId, cwd, toolCalls) {
|
|
171
197
|
const targets = checkpointTargetsFromToolCalls(cwd, toolCalls);
|
|
172
198
|
if (targets.length === 0)
|
|
173
|
-
return false;
|
|
199
|
+
return { created: false, targetCount: 0, risky: false };
|
|
200
|
+
const risky = (0, checkpoint_1.shouldCreateMultiFileCheckpoint)(targets.length);
|
|
174
201
|
const checkpoint = (0, checkpoint_1.createCheckpoint)(cwd, checkpointId, targets);
|
|
175
202
|
if (!sessionId)
|
|
176
|
-
return true;
|
|
203
|
+
return { created: true, targetCount: targets.length, risky };
|
|
177
204
|
const relativeTargets = targets.map(target => path.relative(cwd, target));
|
|
178
205
|
recordTraceEvent(events, sessionId, {
|
|
179
206
|
turnId,
|
|
@@ -183,10 +210,10 @@ function createPreToolCheckpoint(events, sessionId, turnId, checkpointId, cwd, t
|
|
|
183
210
|
checkpointFiles: checkpoint?.files.map(file => file.path) ?? [],
|
|
184
211
|
workspaceFiles: relativeTargets,
|
|
185
212
|
note: checkpoint
|
|
186
|
-
? 'pre_edit_checkpoint'
|
|
213
|
+
? (risky ? 'risky_multi_file_checkpoint' : 'pre_edit_checkpoint')
|
|
187
214
|
: 'pre_edit_checkpoint_skipped',
|
|
188
215
|
});
|
|
189
|
-
return true;
|
|
216
|
+
return { created: true, targetCount: targets.length, risky };
|
|
190
217
|
}
|
|
191
218
|
function byteLength(text) {
|
|
192
219
|
return Buffer.byteLength(text, 'utf8');
|
|
@@ -273,6 +300,7 @@ function appendVerificationProfileTrace(events, sessionId, turnId, profile) {
|
|
|
273
300
|
type: 'verification_profile',
|
|
274
301
|
verificationProfile: profile.profile,
|
|
275
302
|
verificationRequired: profile.required,
|
|
303
|
+
verificationRisky: (0, verification_profile_1.isRiskyEdit)(profile.changedFiles),
|
|
276
304
|
verificationCommands: compactPathList(profile.commands, 8),
|
|
277
305
|
verificationChangedFiles: compactPathList(profile.changedFiles),
|
|
278
306
|
note: profile.reason,
|
|
@@ -416,34 +444,11 @@ function toHarnessDiagnostics(state) {
|
|
|
416
444
|
function emitHarnessDiagnostics(events, state) {
|
|
417
445
|
events.harnessDiagnosticsUpdated?.(toHarnessDiagnostics(state));
|
|
418
446
|
}
|
|
419
|
-
function toolStartContent(
|
|
420
|
-
|
|
421
|
-
return `Running ${name}\n $ ${args.command}`;
|
|
422
|
-
}
|
|
423
|
-
const detail = compactToolArgs(args);
|
|
424
|
-
return `Running ${name}${detail ? ` ${detail}` : ''}`;
|
|
425
|
-
}
|
|
426
|
-
function toolSummary(name, args, success, duration) {
|
|
427
|
-
const details = compactToolArgs(args);
|
|
428
|
-
const suffix = details ? ` ${details}` : '';
|
|
429
|
-
return `${success ? '✓' : '✗'} ${name}${suffix} (${duration}ms)`;
|
|
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)));
|
|
430
449
|
}
|
|
431
450
|
function toolFinishContent(event) {
|
|
432
|
-
|
|
433
|
-
const lines = [summary];
|
|
434
|
-
if (event.name === 'exec_command' && typeof event.args.command === 'string') {
|
|
435
|
-
lines.push(` $ ${event.args.command}`);
|
|
436
|
-
}
|
|
437
|
-
if (event.artifactRef) {
|
|
438
|
-
lines.push(` artifact ${event.artifactRef.id} (${event.artifactRef.outputBytes}B full output)`);
|
|
439
|
-
}
|
|
440
|
-
else if (typeof event.outputBytes === 'number') {
|
|
441
|
-
lines.push(` output ${event.outputBytes}B`);
|
|
442
|
-
}
|
|
443
|
-
if (event.error) {
|
|
444
|
-
lines.push(`Error: ${event.error}`);
|
|
445
|
-
}
|
|
446
|
-
return lines.filter(Boolean).join('\n');
|
|
451
|
+
return (0, ui_view_model_1.formatToolActivityTranscript)((0, ui_view_model_1.toolActivityFromFinished)(event, compactToolArgs(event.args, TOOL_TRANSCRIPT_ARG_BUDGET)));
|
|
447
452
|
}
|
|
448
453
|
function isSyntheticCompactContext(content) {
|
|
449
454
|
return content.startsWith('[OpenHorse Context State v2]')
|
|
@@ -645,7 +650,8 @@ function parseLocalFastPath(input) {
|
|
|
645
650
|
}
|
|
646
651
|
function formatLocalFastPathAssistantContent(action, rawResult, projectPath) {
|
|
647
652
|
const envelope = (0, tool_serializer_1.parseToolResultEnvelope)(rawResult);
|
|
648
|
-
const
|
|
653
|
+
const rawOutput = typeof envelope.output === 'string' ? envelope.output : '';
|
|
654
|
+
const output = rawOutput.trim();
|
|
649
655
|
const summary = envelope.summary || `${action.tool} ${envelope.success ? 'completed' : 'failed'}`;
|
|
650
656
|
const lines = [
|
|
651
657
|
envelope.success
|
|
@@ -658,41 +664,67 @@ function formatLocalFastPathAssistantContent(action, rawResult, projectPath) {
|
|
|
658
664
|
lines.push(`Error: ${envelope.error}`);
|
|
659
665
|
}
|
|
660
666
|
if (!output) {
|
|
661
|
-
return lines.join('\n');
|
|
667
|
+
return { content: lines.join('\n'), artifactRef: envelope.artifactRef };
|
|
662
668
|
}
|
|
663
669
|
let artifactRef = envelope.artifactRef;
|
|
664
670
|
let preview = output;
|
|
665
|
-
const outputBytes = envelope.outputBytes ?? byteLength(
|
|
666
|
-
if (byteLength(
|
|
671
|
+
const outputBytes = envelope.outputBytes ?? byteLength(rawOutput);
|
|
672
|
+
if (byteLength(rawOutput) > LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES) {
|
|
667
673
|
if (!artifactRef) {
|
|
668
|
-
const artifact = (0, tool_artifacts_1.storeArtifact)(projectPath, action.tool,
|
|
674
|
+
const artifact = (0, tool_artifacts_1.storeArtifact)(projectPath, action.tool, rawOutput, outputBytes);
|
|
669
675
|
artifactRef = artifact ? { id: artifact.id, outputBytes: artifact.outputBytes } : undefined;
|
|
670
676
|
}
|
|
671
677
|
preview = (0, tool_artifacts_1.truncateForContext)(output, LOCAL_FAST_PATH_INLINE_OUTPUT_BYTES);
|
|
672
678
|
}
|
|
673
679
|
if (artifactRef) {
|
|
674
|
-
lines.push('', `Full output: /artifacts show ${artifactRef.id} --full (${artifactRef.outputBytes}
|
|
680
|
+
lines.push('', `Full output: /artifacts show ${artifactRef.id} --full (${(0, format_1.formatBytes)(artifactRef.outputBytes)})`);
|
|
675
681
|
}
|
|
676
682
|
lines.push('', 'Preview:', preview);
|
|
677
|
-
return lines.join('\n');
|
|
683
|
+
return { content: lines.join('\n'), artifactRef };
|
|
678
684
|
}
|
|
679
685
|
function createToolEventPresenter(events) {
|
|
680
686
|
const runningToolEntries = new Map();
|
|
681
687
|
return {
|
|
682
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
|
+
});
|
|
683
701
|
events.toolStarted?.({
|
|
684
702
|
callId: event.callId,
|
|
685
703
|
name: event.name,
|
|
686
704
|
args: event.args,
|
|
705
|
+
batchCount: event.batchCount,
|
|
706
|
+
batchIndex: event.batchIndex,
|
|
687
707
|
});
|
|
688
|
-
const entryId = events.append({
|
|
689
|
-
role: 'tool',
|
|
690
|
-
title: 'tool',
|
|
691
|
-
content: toolStartContent(event.name, event.args),
|
|
692
|
-
});
|
|
693
|
-
runningToolEntries.set(event.callId, entryId);
|
|
694
708
|
},
|
|
695
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
|
+
}
|
|
696
728
|
events.toolFinished?.({
|
|
697
729
|
callId: event.callId,
|
|
698
730
|
name: event.name,
|
|
@@ -703,24 +735,30 @@ function createToolEventPresenter(events) {
|
|
|
703
735
|
error: event.error,
|
|
704
736
|
outputBytes: event.outputBytes,
|
|
705
737
|
artifactRef: event.artifactRef,
|
|
738
|
+
batchCount: event.batchCount,
|
|
739
|
+
batchIndex: event.batchIndex,
|
|
706
740
|
});
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
events.finalize(
|
|
711
|
-
role:
|
|
741
|
+
},
|
|
742
|
+
finalizePendingAsSkipped(reason = 'permission denied') {
|
|
743
|
+
for (const [callId, entry] of runningToolEntries) {
|
|
744
|
+
events.finalize(entry.entryId, {
|
|
745
|
+
role: 'tool',
|
|
712
746
|
title: 'tool',
|
|
713
|
-
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,
|
|
714
759
|
});
|
|
715
|
-
runningToolEntries.delete(event.callId);
|
|
716
|
-
return;
|
|
717
760
|
}
|
|
718
|
-
|
|
719
|
-
role: event.success ? 'tool' : 'error',
|
|
720
|
-
title: 'tool',
|
|
721
|
-
content,
|
|
722
|
-
});
|
|
723
|
-
events.finalize(entryId);
|
|
761
|
+
runningToolEntries.clear();
|
|
724
762
|
},
|
|
725
763
|
};
|
|
726
764
|
}
|
|
@@ -791,6 +829,7 @@ class AgentChatController {
|
|
|
791
829
|
content: suggestions.length > 0
|
|
792
830
|
? `Unknown command: /${parsed.name}\nDid you mean: ${suggestions.map(item => `/${item}`).join(', ')}?`
|
|
793
831
|
: `Unknown command: /${parsed.name}`,
|
|
832
|
+
errorLayer: 'runtime',
|
|
794
833
|
});
|
|
795
834
|
return;
|
|
796
835
|
}
|
|
@@ -815,6 +854,7 @@ class AgentChatController {
|
|
|
815
854
|
role: 'error',
|
|
816
855
|
title: `/${command.name}`,
|
|
817
856
|
content: result.error,
|
|
857
|
+
errorLayer: 'runtime',
|
|
818
858
|
});
|
|
819
859
|
}
|
|
820
860
|
if (result.sessionPicker) {
|
|
@@ -898,7 +938,8 @@ class AgentChatController {
|
|
|
898
938
|
const outputBytes = typeof envelope.outputBytes === 'number'
|
|
899
939
|
? envelope.outputBytes
|
|
900
940
|
: Buffer.byteLength(result, 'utf8');
|
|
901
|
-
const
|
|
941
|
+
const formattedLocalResult = formatLocalFastPathAssistantContent(action, result, this.runtime.cwd);
|
|
942
|
+
const assistantContent = formattedLocalResult.content;
|
|
902
943
|
const stats = (0, framework_1.createLocalFastPathLoopStats)({
|
|
903
944
|
finishReason: envelope.success ? 'completed' : 'failed',
|
|
904
945
|
toolCalls: 1,
|
|
@@ -923,6 +964,7 @@ class AgentChatController {
|
|
|
923
964
|
error: envelope.error,
|
|
924
965
|
summary: envelope.summary,
|
|
925
966
|
outputBytes,
|
|
967
|
+
artifactRef: formattedLocalResult.artifactRef,
|
|
926
968
|
}),
|
|
927
969
|
});
|
|
928
970
|
this.runtime.store.addMessage({ role: 'assistant', content: assistantContent });
|
|
@@ -940,6 +982,7 @@ class AgentChatController {
|
|
|
940
982
|
duration,
|
|
941
983
|
outputBytes,
|
|
942
984
|
modelVisibleBytes: 0,
|
|
985
|
+
artifactId: formattedLocalResult.artifactRef?.id,
|
|
943
986
|
error: envelope.error ? compactMiddle(envelope.error, 240) : undefined,
|
|
944
987
|
});
|
|
945
988
|
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace);
|
|
@@ -967,7 +1010,7 @@ class AgentChatController {
|
|
|
967
1010
|
}
|
|
968
1011
|
catch (error) {
|
|
969
1012
|
const message = error instanceof Error ? error.message : String(error);
|
|
970
|
-
this.events.append({ role: 'error', title: 'local', content: message });
|
|
1013
|
+
this.events.append({ role: 'error', title: 'local', content: message, errorLayer: 'tool' });
|
|
971
1014
|
this.events.setStatus('Local command failed. Ready for the next input.');
|
|
972
1015
|
const assistantContent = `Local fast path failed for ${action.label}.\n\n${message}`;
|
|
973
1016
|
this.runtime.store.addMessage({ role: 'assistant', content: assistantContent });
|
|
@@ -1031,18 +1074,20 @@ class AgentChatController {
|
|
|
1031
1074
|
this.events.append({ role: 'system', content: text });
|
|
1032
1075
|
}
|
|
1033
1076
|
},
|
|
1034
|
-
|
|
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),
|
|
1035
1079
|
};
|
|
1036
1080
|
}
|
|
1037
1081
|
async runChat(input, options = {}) {
|
|
1038
1082
|
if (!input) {
|
|
1039
|
-
this.events.append({ role: 'error', content: 'Usage: /chat <message>' });
|
|
1083
|
+
this.events.append({ role: 'error', content: 'Usage: /chat <message>', errorLayer: 'runtime' });
|
|
1040
1084
|
return;
|
|
1041
1085
|
}
|
|
1042
1086
|
if (!this.runtime.llm || !(0, config_1.isConfigured)(this.runtime.config)) {
|
|
1043
1087
|
this.events.append({
|
|
1044
1088
|
role: 'error',
|
|
1045
1089
|
content: 'LLM is not configured. Set OPENHORSE_API_KEY in ~/.openhorse/openhorse.json or environment.',
|
|
1090
|
+
errorLayer: 'provider',
|
|
1046
1091
|
});
|
|
1047
1092
|
return;
|
|
1048
1093
|
}
|
|
@@ -1090,6 +1135,10 @@ class AgentChatController {
|
|
|
1090
1135
|
});
|
|
1091
1136
|
const intent = harness.updateContractFromUserInput(input);
|
|
1092
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
|
+
}
|
|
1093
1142
|
const promptCtx = {
|
|
1094
1143
|
cwd: this.runtime.cwd,
|
|
1095
1144
|
platform: process.platform,
|
|
@@ -1212,16 +1261,40 @@ class AgentChatController {
|
|
|
1212
1261
|
case 'assistant_tool_calls':
|
|
1213
1262
|
assistantStream.ensureMessage(event.content || '');
|
|
1214
1263
|
assistantStream.closeSegment();
|
|
1215
|
-
|
|
1216
|
-
|
|
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
|
+
}
|
|
1217
1275
|
}
|
|
1218
1276
|
const checkpointId = checkpointSequence === 0
|
|
1219
1277
|
? turnId
|
|
1220
1278
|
: `${turnId}-checkpoint-${checkpointSequence + 1}`;
|
|
1221
|
-
|
|
1279
|
+
const checkpointResult = createPreToolCheckpoint(this.events, sessionId, turnId, checkpointId, this.runtime.cwd, event.toolCalls);
|
|
1280
|
+
if (checkpointResult.created) {
|
|
1222
1281
|
checkpointIds.push(checkpointId);
|
|
1223
1282
|
checkpointSequence++;
|
|
1224
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
|
+
}
|
|
1225
1298
|
if (sessionId) {
|
|
1226
1299
|
recordTraceEvent(this.events, sessionId, {
|
|
1227
1300
|
turnId,
|
|
@@ -1353,6 +1426,9 @@ class AgentChatController {
|
|
|
1353
1426
|
}
|
|
1354
1427
|
break;
|
|
1355
1428
|
case 'complete':
|
|
1429
|
+
if (event.stats?.finishReason === 'blocked') {
|
|
1430
|
+
toolEvents.finalizePendingAsSkipped('permission denied');
|
|
1431
|
+
}
|
|
1356
1432
|
if (event.content && !finalContent) {
|
|
1357
1433
|
if (event.stats?.finishReason === 'budget_exceeded') {
|
|
1358
1434
|
assistantStream.replaceMessage(event.content);
|
|
@@ -1412,12 +1488,22 @@ class AgentChatController {
|
|
|
1412
1488
|
this.events.setStatus('Interrupted.');
|
|
1413
1489
|
removeTrailingUserMessage(this.runtime);
|
|
1414
1490
|
if (sessionId) {
|
|
1415
|
-
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1491
|
+
const { delta } = appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1416
1492
|
recordTraceEvent(this.events, sessionId, {
|
|
1417
1493
|
turnId,
|
|
1418
1494
|
type: 'aborted',
|
|
1419
1495
|
note: 'aborted_after_query',
|
|
1420
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
|
+
}
|
|
1421
1507
|
(0, session_storage_1.removeLastIncompleteAssistantMessage)(sessionId);
|
|
1422
1508
|
}
|
|
1423
1509
|
return;
|
|
@@ -1425,6 +1511,10 @@ class AgentChatController {
|
|
|
1425
1511
|
assistantStream.closeSegment();
|
|
1426
1512
|
if (sessionId) {
|
|
1427
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
|
+
}
|
|
1428
1518
|
if (shouldRecordVerificationLoopStats(profile, summary)) {
|
|
1429
1519
|
const stats = pendingCompleteStats ?? this.runtime.store.getSnapshot().lastLoopStats;
|
|
1430
1520
|
if (stats) {
|
|
@@ -1432,6 +1522,8 @@ class AgentChatController {
|
|
|
1432
1522
|
}
|
|
1433
1523
|
}
|
|
1434
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');
|
|
1435
1527
|
const notice = (0, verification_profile_1.formatVerificationGateNotice)(summary);
|
|
1436
1528
|
this.events.append({
|
|
1437
1529
|
role: 'status',
|
|
@@ -1453,6 +1545,36 @@ class AgentChatController {
|
|
|
1453
1545
|
};
|
|
1454
1546
|
}
|
|
1455
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);
|
|
1456
1578
|
}
|
|
1457
1579
|
if (pendingCompleteStats) {
|
|
1458
1580
|
this.setLoopStats(pendingCompleteStats);
|
|
@@ -1494,18 +1616,28 @@ class AgentChatController {
|
|
|
1494
1616
|
this.events.setStatus('Interrupted.');
|
|
1495
1617
|
removeTrailingUserMessage(this.runtime);
|
|
1496
1618
|
if (sessionId) {
|
|
1497
|
-
appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1619
|
+
const { delta } = appendPostWorkspaceTrace(this.events, sessionId, turnId, this.runtime.cwd, preWorkspace, verificationResults);
|
|
1498
1620
|
recordTraceEvent(this.events, sessionId, {
|
|
1499
1621
|
turnId,
|
|
1500
1622
|
type: 'aborted',
|
|
1501
1623
|
note: 'abort_error',
|
|
1502
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
|
+
}
|
|
1503
1635
|
(0, session_storage_1.removeLastIncompleteAssistantMessage)(sessionId);
|
|
1504
1636
|
}
|
|
1505
1637
|
return;
|
|
1506
1638
|
}
|
|
1507
1639
|
assistantStream.discardSegment();
|
|
1508
|
-
this.events.append({ role: 'error', content: formatChatError(error) });
|
|
1640
|
+
this.events.append({ role: 'error', content: formatChatError(error), errorLayer: errorLayerForChatError(error) });
|
|
1509
1641
|
this.events.setStatus('Turn failed. Ready for the next input.');
|
|
1510
1642
|
const failedStats = error instanceof framework_1.QueryLoopError
|
|
1511
1643
|
? error.stats
|