codemini-cli 0.3.4 → 0.3.6
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/README.md +20 -18
- package/package.json +6 -6
- package/souls/anime.md +12 -9
- package/souls/caveman.md +6 -6
- package/souls/ceo.md +10 -9
- package/souls/default.md +1 -1
- package/souls/pirate.md +6 -6
- package/souls/playful.md +7 -7
- package/souls/professional.md +1 -1
- package/src/cli.js +3 -1
- package/src/commands/run.js +229 -16
- package/src/core/agent-loop.js +167 -49
- package/src/core/ast.js +40 -0
- package/src/core/chat-runtime.js +720 -126
- package/src/core/command-policy.js +56 -0
- package/src/core/config-store.js +0 -3
- package/src/core/crypto-utils.js +6 -2
- package/src/core/memory-store.js +3 -3
- package/src/core/project-index.js +4 -18
- package/src/core/provider/anthropic.js +15 -2
- package/src/core/provider/anthropic.sdk-backup.js +439 -0
- package/src/core/provider/openai-compatible.js +93 -11
- package/src/core/provider/openai-compatible.sdk-backup.js +412 -0
- package/src/core/session-store.js +90 -25
- package/src/core/shell-profile.js +26 -6
- package/src/core/string-utils.js +37 -0
- package/src/core/tools.js +216 -405
- package/src/tui/chat-app.js +490 -146
- package/src/tui/tool-activity/presenters/files.js +2 -2
- package/src/tui/tool-narration.js +0 -3
- package/src/tui/tool-narration/presenters/patch.js +0 -3
package/src/core/agent-loop.js
CHANGED
|
@@ -2,15 +2,22 @@ import os from 'node:os';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
4
|
import { BoundedCache } from './bounded-cache.js';
|
|
5
|
+
import { trimInline as _trimInline, normalizePath } from './string-utils.js';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* 安全解析 JSON 字符串。
|
|
9
|
+
* 解析失败时返回带 _raw 和 _invalid_json 标记的对象,
|
|
10
|
+
* 调用方可据此决定是回退到原始文本还是报告错误。
|
|
11
|
+
*/
|
|
6
12
|
function safeJsonParse(raw) {
|
|
7
13
|
if (!raw || typeof raw !== 'string') return {};
|
|
8
14
|
try {
|
|
9
15
|
return JSON.parse(raw);
|
|
10
|
-
} catch {
|
|
16
|
+
} catch (parseError) {
|
|
11
17
|
return {
|
|
12
18
|
_raw: String(raw),
|
|
13
|
-
_invalid_json: true
|
|
19
|
+
_invalid_json: true,
|
|
20
|
+
_parseError: parseError.message
|
|
14
21
|
};
|
|
15
22
|
}
|
|
16
23
|
}
|
|
@@ -29,6 +36,41 @@ function parseInlineRangePath(value) {
|
|
|
29
36
|
return { path: maybePath, start_line: start, end_line: end };
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
function buildDeleteApprovalDetails(source, rawPath) {
|
|
40
|
+
const existing =
|
|
41
|
+
source?.approval && typeof source.approval === 'object' && !Array.isArray(source.approval)
|
|
42
|
+
? source.approval
|
|
43
|
+
: {};
|
|
44
|
+
const approvalPath = String(existing.path || rawPath || '').trim();
|
|
45
|
+
const approvalName = String(existing.name || (approvalPath ? path.basename(approvalPath) : '') || '').trim();
|
|
46
|
+
const approvalType = String(existing.type || '').trim();
|
|
47
|
+
|
|
48
|
+
const approval = {};
|
|
49
|
+
if (approvalPath) approval.path = approvalPath;
|
|
50
|
+
if (approvalName) approval.name = approvalName;
|
|
51
|
+
if (approvalType) approval.type = approvalType;
|
|
52
|
+
return Object.keys(approval).length > 0 ? approval : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildDeleteCancellationResult(args) {
|
|
56
|
+
const approval =
|
|
57
|
+
args?.approval && typeof args.approval === 'object' && !Array.isArray(args.approval)
|
|
58
|
+
? args.approval
|
|
59
|
+
: undefined;
|
|
60
|
+
const pathValue = String(approval?.path || args?.path || '').trim();
|
|
61
|
+
const nameValue = String(approval?.name || (pathValue ? path.basename(pathValue) : '') || '').trim();
|
|
62
|
+
const typeValue = String(approval?.type || '').trim();
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
...(pathValue ? { path: pathValue } : {}),
|
|
66
|
+
...(nameValue ? { name: nameValue } : {}),
|
|
67
|
+
...(typeValue ? { type: typeValue } : {}),
|
|
68
|
+
deleted: false,
|
|
69
|
+
cancelled: true,
|
|
70
|
+
reason: 'User denied deletion approval'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
32
74
|
function normalizeToolArguments(toolName, args, rawArguments) {
|
|
33
75
|
const rawText = typeof rawArguments === 'string' ? rawArguments.trim() : '';
|
|
34
76
|
const primitive =
|
|
@@ -102,6 +144,14 @@ function normalizeToolArguments(toolName, args, rawArguments) {
|
|
|
102
144
|
return source;
|
|
103
145
|
}
|
|
104
146
|
|
|
147
|
+
if (toolName === 'delete') {
|
|
148
|
+
const value = String(source.path || source.file_path || source.file || source.target || source.directory || source.dir || stringValue || '').trim();
|
|
149
|
+
if (value) source.path = value;
|
|
150
|
+
const approval = buildDeleteApprovalDetails(source, source.path);
|
|
151
|
+
if (approval) source.approval = approval;
|
|
152
|
+
return source;
|
|
153
|
+
}
|
|
154
|
+
|
|
105
155
|
return source;
|
|
106
156
|
}
|
|
107
157
|
|
|
@@ -307,7 +357,7 @@ export function checkReadDedup(filePath, startLine, endLine, mtimeMs) {
|
|
|
307
357
|
|
|
308
358
|
const READ_ONLY_TOOLS = new Set([
|
|
309
359
|
'read', 'grep', 'glob', 'list',
|
|
310
|
-
'ast_query', 'read_ast_node',
|
|
360
|
+
'ast_query', 'read_ast_node',
|
|
311
361
|
'list_background_tasks', 'get_background_task'
|
|
312
362
|
]);
|
|
313
363
|
|
|
@@ -322,6 +372,12 @@ export function summarizeToolResult(result) {
|
|
|
322
372
|
if (typeof result === 'object') {
|
|
323
373
|
const obj = result;
|
|
324
374
|
if (Array.isArray(obj)) return `array(${obj.length})`;
|
|
375
|
+
if ('deleted' in obj && 'path' in obj) {
|
|
376
|
+
const kind = trimInline(obj.type || 'item', 16);
|
|
377
|
+
const target = trimInline(obj.path || '', 96);
|
|
378
|
+
if (obj.deleted) return target ? `deleted ${kind} ${target}` : `deleted ${kind}`;
|
|
379
|
+
if (obj.cancelled) return target ? `cancelled delete ${target}` : 'cancelled delete';
|
|
380
|
+
}
|
|
325
381
|
if ('path' in obj && 'action' in obj) {
|
|
326
382
|
const p = String(obj.path || '');
|
|
327
383
|
const action = String(obj.action || 'write');
|
|
@@ -354,11 +410,12 @@ export function summarizeToolResult(result) {
|
|
|
354
410
|
: start;
|
|
355
411
|
const rangeText = start > 0 && end >= start ? ` lines ${start}-${end}` : '';
|
|
356
412
|
const totalText = total > 0 ? ` of ${total}` : '';
|
|
413
|
+
const enclosingText = obj.enclosing_symbol ? ` in ${obj.enclosing_symbol}` : '';
|
|
357
414
|
const errorText = obj.error ? ` (${trimInline(obj.error, 64)})` : '';
|
|
358
415
|
const truncatedText = obj.truncated ? ' [truncated]' : '';
|
|
359
416
|
return phase === 'metadata'
|
|
360
417
|
? `metadata for ${p}${rangeText}${totalText}${errorText}`
|
|
361
|
-
: `content from ${p}${rangeText}${totalText}${truncatedText}`;
|
|
418
|
+
: `content from ${p}${rangeText}${totalText}${enclosingText}${truncatedText}`;
|
|
362
419
|
}
|
|
363
420
|
if ('stdout' in obj || 'stderr' in obj || 'code' in obj) {
|
|
364
421
|
const stdout = trimInline(obj.stdout || '', 96);
|
|
@@ -405,12 +462,7 @@ export function summarizeToolResult(result) {
|
|
|
405
462
|
return String(result);
|
|
406
463
|
}
|
|
407
464
|
|
|
408
|
-
export
|
|
409
|
-
const s = String(value || '').replace(/\s+/g, ' ').trim();
|
|
410
|
-
if (!s) return '';
|
|
411
|
-
if (s.length <= maxLen) return s;
|
|
412
|
-
return `${s.slice(0, maxLen - 3)}...`;
|
|
413
|
-
}
|
|
465
|
+
export const trimInline = _trimInline;
|
|
414
466
|
|
|
415
467
|
function normalizeAssistantText(value) {
|
|
416
468
|
return String(value || '').trim();
|
|
@@ -503,12 +555,12 @@ function createAnalysisGuardState(userPrompt) {
|
|
|
503
555
|
}
|
|
504
556
|
|
|
505
557
|
function topLevelPath(value) {
|
|
506
|
-
const normalized =
|
|
558
|
+
const normalized = normalizePath(value).trim();
|
|
507
559
|
return normalized.split('/')[0] || '';
|
|
508
560
|
}
|
|
509
561
|
|
|
510
562
|
function isRelevantSourcePath(filePath, state) {
|
|
511
|
-
const normalized =
|
|
563
|
+
const normalized = normalizePath(filePath).trim();
|
|
512
564
|
if (!normalized) return false;
|
|
513
565
|
if (state.candidateFiles.has(normalized) || state.entryCandidates.has(normalized)) return true;
|
|
514
566
|
for (const root of state.sourceRoots) {
|
|
@@ -519,20 +571,17 @@ function isRelevantSourcePath(filePath, state) {
|
|
|
519
571
|
|
|
520
572
|
function blockedExplorationReason(toolName, args, state) {
|
|
521
573
|
if (!state.active) return '';
|
|
522
|
-
if (!state.indexQueried && toolName !== 'query_project_index') {
|
|
523
|
-
return 'Use query_project_index before broad repository exploration so the next reads stay focused on relevant source files.';
|
|
524
|
-
}
|
|
525
574
|
|
|
526
|
-
|
|
575
|
+
// Always note when query_project_index is used, but never force it
|
|
576
|
+
if (toolName === 'query_project_index') return '';
|
|
577
|
+
|
|
578
|
+
const target = normalizePath(String(args?.path || args?.pattern || args?.query || '')).trim();
|
|
527
579
|
const top = topLevelPath(target);
|
|
528
580
|
if (!top) return '';
|
|
529
581
|
|
|
530
582
|
if (['skills', 'souls', 'templates', '.codemini', '.codemini-project'].includes(top)) {
|
|
531
583
|
return `Skip ${top}/ for broad repository analysis unless the user explicitly asks for it. Inspect relevant source files first.`;
|
|
532
584
|
}
|
|
533
|
-
if (top === 'tests' && state.relevantSourceReads.size < 2) {
|
|
534
|
-
return 'Inspect the next relevant source files before reading tests. Broad analysis should be grounded in production code first.';
|
|
535
|
-
}
|
|
536
585
|
return '';
|
|
537
586
|
}
|
|
538
587
|
|
|
@@ -603,13 +652,13 @@ function formatToolDisplayName(name, args) {
|
|
|
603
652
|
const target = trimInline(args?.path || args?.file || '.', 96) || '.';
|
|
604
653
|
return `edit(${target})`;
|
|
605
654
|
}
|
|
655
|
+
if (name === 'delete') {
|
|
656
|
+
const target = trimInline(args?.path || args?.target || '.', 96) || '.';
|
|
657
|
+
return `delete(${target})`;
|
|
658
|
+
}
|
|
606
659
|
if (name === 'update_todos') {
|
|
607
660
|
return 'update_todos';
|
|
608
661
|
}
|
|
609
|
-
if (name === 'patch') {
|
|
610
|
-
const target = trimInline(args?.path || args?.file || args?.patch || '', 96) || '.';
|
|
611
|
-
return `patch(${target})`;
|
|
612
|
-
}
|
|
613
662
|
if (name === 'list_background_tasks') {
|
|
614
663
|
return name;
|
|
615
664
|
}
|
|
@@ -617,10 +666,6 @@ function formatToolDisplayName(name, args) {
|
|
|
617
666
|
const taskId = trimInline(args?.task_id || args?.taskId || '', 96);
|
|
618
667
|
return taskId ? `${name}(${taskId})` : name;
|
|
619
668
|
}
|
|
620
|
-
if (name === 'read' || name === 'write' || name === 'run' || name === 'grep' || name === 'glob' || name === 'list' || name === 'edit' || name === 'patch' || name === 'generate_diff') {
|
|
621
|
-
const target = trimInline(args?.path || args?.query || args?.symbol || '', 96);
|
|
622
|
-
return target ? `${name}(${target})` : name;
|
|
623
|
-
}
|
|
624
669
|
return name;
|
|
625
670
|
}
|
|
626
671
|
|
|
@@ -654,7 +699,9 @@ export async function runAgentLoop({
|
|
|
654
699
|
requestToolApproval,
|
|
655
700
|
toolResultMaxChars = 12000,
|
|
656
701
|
toolFormatters = {},
|
|
657
|
-
deferredDefinitions = {}
|
|
702
|
+
deferredDefinitions = {},
|
|
703
|
+
signal,
|
|
704
|
+
skipAnalysisNudge = false
|
|
658
705
|
}) {
|
|
659
706
|
const messages = [];
|
|
660
707
|
if (systemPrompt) {
|
|
@@ -677,13 +724,25 @@ export async function runAgentLoop({
|
|
|
677
724
|
const activeTools = [...toolDefinitions];
|
|
678
725
|
|
|
679
726
|
for (let step = 0; step < maxSteps; step += 1) {
|
|
727
|
+
// 检查是否已被用户中止
|
|
728
|
+
if (signal?.aborted) {
|
|
729
|
+
if (onEvent) onEvent({ type: 'aborted', step: step + 1 });
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
680
732
|
if (onEvent) onEvent({ type: 'step:start', step: step + 1 });
|
|
681
733
|
const completion = await requestCompletion({
|
|
682
734
|
model,
|
|
683
735
|
messages,
|
|
684
|
-
tools: activeTools
|
|
736
|
+
tools: activeTools,
|
|
737
|
+
signal
|
|
685
738
|
});
|
|
686
739
|
|
|
740
|
+
// 流式请求完成后再次检查中止状态
|
|
741
|
+
if (signal?.aborted) {
|
|
742
|
+
if (onEvent) onEvent({ type: 'aborted', step: step + 1 });
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
|
|
687
746
|
if (completion?.incomplete) {
|
|
688
747
|
continue;
|
|
689
748
|
}
|
|
@@ -692,8 +751,14 @@ export async function runAgentLoop({
|
|
|
692
751
|
const assistantText = completion.text || '';
|
|
693
752
|
lastAssistantText = assistantText || lastAssistantText;
|
|
694
753
|
|
|
695
|
-
const assistantMessage =
|
|
696
|
-
|
|
754
|
+
const assistantMessage = completion?.assistantMessage
|
|
755
|
+
? {
|
|
756
|
+
...completion.assistantMessage,
|
|
757
|
+
role: 'assistant',
|
|
758
|
+
content: completion.assistantMessage.content ?? completion?.content ?? assistantText
|
|
759
|
+
}
|
|
760
|
+
: { role: 'assistant', content: completion?.content ?? assistantText };
|
|
761
|
+
if (!Array.isArray(assistantMessage.tool_calls) && toolCalls.length > 0) {
|
|
697
762
|
assistantMessage.tool_calls = toolCalls.map((tc) => ({
|
|
698
763
|
id: tc.id,
|
|
699
764
|
type: 'function',
|
|
@@ -712,7 +777,7 @@ export async function runAgentLoop({
|
|
|
712
777
|
}
|
|
713
778
|
|
|
714
779
|
if (toolCalls.length === 0) {
|
|
715
|
-
if (needsMoreAnalysisEvidence(analysisGuard) && pendingSummaryNudges < 2) {
|
|
780
|
+
if (!skipAnalysisNudge && needsMoreAnalysisEvidence(analysisGuard) && pendingSummaryNudges < 2) {
|
|
716
781
|
pendingSummaryNudges += 1;
|
|
717
782
|
messages.push({
|
|
718
783
|
role: 'user',
|
|
@@ -721,7 +786,7 @@ export async function runAgentLoop({
|
|
|
721
786
|
});
|
|
722
787
|
continue;
|
|
723
788
|
}
|
|
724
|
-
if (shouldAskForConcreteFinalAnswer(assistantText, messages.slice(0, -1)) && pendingSummaryNudges < 2) {
|
|
789
|
+
if (!skipAnalysisNudge && shouldAskForConcreteFinalAnswer(assistantText, messages.slice(0, -1)) && pendingSummaryNudges < 2) {
|
|
725
790
|
pendingSummaryNudges += 1;
|
|
726
791
|
messages.push({
|
|
727
792
|
role: 'user',
|
|
@@ -764,19 +829,44 @@ export async function runAgentLoop({
|
|
|
764
829
|
const approvalResults = new Map();
|
|
765
830
|
for (const { call, toolName, displayName, args } of callsWithMeta) {
|
|
766
831
|
let approved = true;
|
|
767
|
-
|
|
832
|
+
let approvalArgs = args;
|
|
833
|
+
let preflightErrorContent = '';
|
|
834
|
+
const needsApproval = toolName === 'delete' || (executionMode === 'normal' && !alwaysAllowSet.has(toolName));
|
|
835
|
+
if (needsApproval) {
|
|
768
836
|
approved = false;
|
|
837
|
+
const handler = toolHandlers[toolName];
|
|
838
|
+
if (toolName === 'delete' && typeof handler?.prepareApproval === 'function') {
|
|
839
|
+
try {
|
|
840
|
+
const approval = await handler.prepareApproval(args);
|
|
841
|
+
const normalizedApproval = buildDeleteApprovalDetails({ approval }, args?.path);
|
|
842
|
+
if (normalizedApproval) {
|
|
843
|
+
approvalArgs = { ...args, approval: normalizedApproval };
|
|
844
|
+
}
|
|
845
|
+
} catch (error) {
|
|
846
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
847
|
+
preflightErrorContent = clipToolResult({ error: message }, toolResultMaxChars);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (preflightErrorContent) {
|
|
851
|
+
approvalResults.set(call.id, {
|
|
852
|
+
approved: false,
|
|
853
|
+
args: approvalArgs,
|
|
854
|
+
errorContent: preflightErrorContent
|
|
855
|
+
});
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
769
858
|
if (typeof requestToolApproval === 'function') {
|
|
770
859
|
const decision = await requestToolApproval({
|
|
771
860
|
id: call.id,
|
|
772
861
|
name: toolName,
|
|
773
862
|
displayName,
|
|
774
|
-
arguments:
|
|
863
|
+
arguments: approvalArgs,
|
|
864
|
+
approvalDetails: toolName === 'delete' ? approvalArgs.approval : undefined
|
|
775
865
|
});
|
|
776
866
|
approved = Boolean(decision?.approved);
|
|
777
867
|
}
|
|
778
868
|
}
|
|
779
|
-
approvalResults.set(call.id, approved);
|
|
869
|
+
approvalResults.set(call.id, { approved, args: approvalArgs });
|
|
780
870
|
}
|
|
781
871
|
|
|
782
872
|
// Collect results keyed by call.id, then write to messages in original order
|
|
@@ -785,28 +875,45 @@ export async function runAgentLoop({
|
|
|
785
875
|
// Helper to execute a single tool call
|
|
786
876
|
async function executeOne({ call, args, toolName, displayName, isReadOnly }) {
|
|
787
877
|
const startedAt = Date.now();
|
|
878
|
+
const approvalState = approvalResults.get(call.id) || { approved: true, args };
|
|
879
|
+
const effectiveArgs = approvalState.args || args;
|
|
880
|
+
|
|
881
|
+
if (approvalState.errorContent) {
|
|
882
|
+
if (onEvent) {
|
|
883
|
+
onEvent({ type: 'tool:error', name: displayName, id: call.id, arguments: effectiveArgs, durationMs: 0, summary: trimInline(approvalState.errorContent, 120) });
|
|
884
|
+
}
|
|
885
|
+
return {
|
|
886
|
+
callId: call.id,
|
|
887
|
+
content: approvalState.errorContent,
|
|
888
|
+
error: true
|
|
889
|
+
};
|
|
890
|
+
}
|
|
788
891
|
|
|
789
|
-
if (!
|
|
790
|
-
if (onEvent) onEvent({ type: 'tool:blocked', name: displayName, id: call.id, arguments:
|
|
892
|
+
if (!approvalState.approved) {
|
|
893
|
+
if (onEvent) onEvent({ type: 'tool:blocked', name: displayName, id: call.id, arguments: effectiveArgs });
|
|
894
|
+
const blockedPayload =
|
|
895
|
+
toolName === 'delete'
|
|
896
|
+
? buildDeleteCancellationResult(effectiveArgs)
|
|
897
|
+
: { blocked: true, reason: 'Tool call requires approval in normal mode' };
|
|
791
898
|
return {
|
|
792
899
|
callId: call.id,
|
|
793
|
-
content: JSON.stringify(
|
|
900
|
+
content: JSON.stringify(blockedPayload),
|
|
794
901
|
blocked: true
|
|
795
902
|
};
|
|
796
903
|
}
|
|
797
904
|
|
|
798
|
-
if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id, arguments:
|
|
905
|
+
if (onEvent) onEvent({ type: 'tool:start', name: displayName, id: call.id, arguments: effectiveArgs });
|
|
799
906
|
const handler = toolHandlers[toolName];
|
|
800
907
|
if (!handler) {
|
|
801
908
|
throw new Error(`Unknown tool: ${call.name}`);
|
|
802
909
|
}
|
|
803
910
|
|
|
804
|
-
const blockedReason = blockedExplorationReason(toolName,
|
|
911
|
+
const blockedReason = blockedExplorationReason(toolName, effectiveArgs, analysisGuard);
|
|
805
912
|
if (blockedReason) {
|
|
806
913
|
analysisGuard.blockedExplorations += 1;
|
|
807
914
|
const content = clipToolResult({ error: blockedReason }, toolResultMaxChars);
|
|
808
915
|
if (onEvent) {
|
|
809
|
-
onEvent({ type: 'tool:error', name: displayName, id: call.id, arguments:
|
|
916
|
+
onEvent({ type: 'tool:error', name: displayName, id: call.id, arguments: effectiveArgs, durationMs: 0, summary: trimInline(blockedReason, 120) });
|
|
810
917
|
}
|
|
811
918
|
return {
|
|
812
919
|
callId: call.id,
|
|
@@ -817,12 +924,12 @@ export async function runAgentLoop({
|
|
|
817
924
|
|
|
818
925
|
let toolResult;
|
|
819
926
|
try {
|
|
820
|
-
toolResult = await handler(
|
|
927
|
+
toolResult = await handler(effectiveArgs);
|
|
821
928
|
} catch (error) {
|
|
822
929
|
const durationMs = Date.now() - startedAt;
|
|
823
930
|
const message = error instanceof Error ? error.message : String(error);
|
|
824
931
|
if (onEvent) {
|
|
825
|
-
onEvent({ type: 'tool:error', name: displayName, id: call.id, arguments:
|
|
932
|
+
onEvent({ type: 'tool:error', name: displayName, id: call.id, arguments: effectiveArgs, durationMs, summary: trimInline(message, 120) });
|
|
826
933
|
}
|
|
827
934
|
return {
|
|
828
935
|
callId: call.id,
|
|
@@ -833,12 +940,12 @@ export async function runAgentLoop({
|
|
|
833
940
|
|
|
834
941
|
const durationMs = Date.now() - startedAt;
|
|
835
942
|
if (onEvent) {
|
|
836
|
-
onEvent({ type: 'tool:end', name: displayName, id: call.id, arguments:
|
|
943
|
+
onEvent({ type: 'tool:end', name: displayName, id: call.id, arguments: effectiveArgs, durationMs, summary: summarizeToolResult(toolResult) });
|
|
837
944
|
}
|
|
838
945
|
|
|
839
946
|
// P1b: Use per-tool formatter if available, else fallback
|
|
840
|
-
let formatted = formatToolResult(toolResult, toolName,
|
|
841
|
-
noteAnalysisEvidence(analysisGuard, toolName,
|
|
947
|
+
let formatted = formatToolResult(toolResult, toolName, effectiveArgs, toolFormatters, toolResultMaxChars);
|
|
948
|
+
noteAnalysisEvidence(analysisGuard, toolName, effectiveArgs, toolResult);
|
|
842
949
|
|
|
843
950
|
// P2: If tool_search loaded deferred tools, inject their schemas into activeTools
|
|
844
951
|
if (toolName === 'tool_search' && toolResult && Array.isArray(toolResult.schemas)) {
|
|
@@ -857,8 +964,8 @@ export async function runAgentLoop({
|
|
|
857
964
|
}
|
|
858
965
|
|
|
859
966
|
// Separate read-only and write calls, preserving order
|
|
860
|
-
const readOnlyCalls = callsWithMeta.filter((c) => c.isReadOnly && approvalResults.get(c.call.id));
|
|
861
|
-
const writeCalls = callsWithMeta.filter((c) => !c.isReadOnly || !approvalResults.get(c.call.id));
|
|
967
|
+
const readOnlyCalls = callsWithMeta.filter((c) => c.isReadOnly && approvalResults.get(c.call.id)?.approved);
|
|
968
|
+
const writeCalls = callsWithMeta.filter((c) => !c.isReadOnly || !approvalResults.get(c.call.id)?.approved);
|
|
862
969
|
|
|
863
970
|
// Execute read-only calls in parallel
|
|
864
971
|
if (readOnlyCalls.length > 0) {
|
|
@@ -902,6 +1009,17 @@ export async function runAgentLoop({
|
|
|
902
1009
|
}
|
|
903
1010
|
}
|
|
904
1011
|
|
|
1012
|
+
// 如果被用户中止,返回已有内容并标记
|
|
1013
|
+
if (signal?.aborted) {
|
|
1014
|
+
const fallback = lastAssistantText || '';
|
|
1015
|
+
return {
|
|
1016
|
+
text: fallback,
|
|
1017
|
+
messages,
|
|
1018
|
+
steps: maxSteps,
|
|
1019
|
+
aborted: true
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
|
|
905
1023
|
const fallback = lastAssistantText || 'Stopped before final response.';
|
|
906
1024
|
return {
|
|
907
1025
|
text: `${fallback}\n\n[stopped] Reached max tool steps (${maxSteps}). Try a narrower prompt or increase execution.max_steps.`,
|
package/src/core/ast.js
CHANGED
|
@@ -153,6 +153,46 @@ function exactNodeForTarget(rootNode, target) {
|
|
|
153
153
|
return null;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Find the enclosing named structural symbol (function, class, method, etc.)
|
|
158
|
+
* for a given line range in already-parsed content. Returns null if not found
|
|
159
|
+
* or if the language is unsupported.
|
|
160
|
+
*/
|
|
161
|
+
export async function findEnclosingSymbol(content, filePath, line) {
|
|
162
|
+
const ext = path.extname(String(filePath || '')).toLowerCase();
|
|
163
|
+
const language = EXTENSION_LANGUAGE_MAP[ext];
|
|
164
|
+
if (!language) return null;
|
|
165
|
+
let parser = null;
|
|
166
|
+
let tree = null;
|
|
167
|
+
try {
|
|
168
|
+
const parsed = await parseContent(content, language);
|
|
169
|
+
parser = parsed.parser;
|
|
170
|
+
tree = parsed.tree;
|
|
171
|
+
const row = Math.max(0, Number(line || 1) - 1);
|
|
172
|
+
const node = tree.rootNode.descendantForPosition({ row, column: 0 });
|
|
173
|
+
let current = node;
|
|
174
|
+
while (current) {
|
|
175
|
+
if (current.type === 'program' || !current.parent) break;
|
|
176
|
+
const nameChild = current.childForFieldName('name');
|
|
177
|
+
if (nameChild) {
|
|
178
|
+
return {
|
|
179
|
+
name: nameChild.text,
|
|
180
|
+
kind: current.type,
|
|
181
|
+
start_line: current.startPosition.row + 1,
|
|
182
|
+
end_line: current.endPosition.row + 1
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
current = current.parent;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
} finally {
|
|
191
|
+
if (tree) tree.delete();
|
|
192
|
+
if (parser) parser.delete();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
156
196
|
export async function queryAst(root, args) {
|
|
157
197
|
const relativePath = String(args?.path || '').trim();
|
|
158
198
|
const querySource = String(args?.query || '').trim();
|