skyloom 1.13.5 → 1.13.7
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/.github/workflows/ci.yml +36 -36
- package/README.md +220 -159
- package/config/providers.yaml +39 -39
- package/config/skills/api_integrator/SKILL.md +15 -15
- package/config/skills/arch_designer/SKILL.md +13 -13
- package/config/skills/ci_cd_manager/SKILL.md +14 -14
- package/config/skills/code_analysis/SKILL.md +13 -13
- package/config/skills/code_generator/SKILL.md +12 -12
- package/config/skills/code_reviewer/SKILL.md +13 -13
- package/config/skills/content_writer/SKILL.md +14 -14
- package/config/skills/data_transformer/SKILL.md +15 -15
- package/config/skills/document_analysis/SKILL.md +13 -13
- package/config/skills/emotional_companion/SKILL.md +15 -15
- package/config/skills/performance_checker/SKILL.md +14 -14
- package/config/skills/security_auditor/SKILL.md +14 -14
- package/config/skills/self_evolve/SKILL.md +13 -13
- package/config/skills/sys_operator/SKILL.md +15 -15
- package/config/skills/task_planner/SKILL.md +14 -14
- package/config/skills/web_research/SKILL.md +14 -14
- package/config/skills/workflow_designer/SKILL.md +13 -13
- package/dist/agents/dew.js +52 -52
- package/dist/agents/fair.js +84 -84
- package/dist/agents/fog.js +30 -30
- package/dist/agents/frost.js +32 -32
- package/dist/agents/rain.js +32 -32
- package/dist/agents/snow.js +68 -68
- package/dist/cli/commands_md.d.ts +41 -0
- package/dist/cli/commands_md.d.ts.map +1 -0
- package/dist/cli/commands_md.js +140 -0
- package/dist/cli/commands_md.js.map +1 -0
- package/dist/cli/input_macros.d.ts +28 -0
- package/dist/cli/input_macros.d.ts.map +1 -0
- package/dist/cli/input_macros.js +120 -0
- package/dist/cli/input_macros.js.map +1 -0
- package/dist/cli/loom.d.ts +220 -0
- package/dist/cli/loom.d.ts.map +1 -0
- package/dist/cli/loom.js +1094 -0
- package/dist/cli/loom.js.map +1 -0
- package/dist/cli/loom_chat.d.ts +20 -0
- package/dist/cli/loom_chat.d.ts.map +1 -0
- package/dist/cli/loom_chat.js +685 -0
- package/dist/cli/loom_chat.js.map +1 -0
- package/dist/cli/main.js +310 -14
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/tui.d.ts.map +1 -1
- package/dist/cli/tui.js +7 -1
- package/dist/cli/tui.js.map +1 -1
- package/dist/core/agent/guard.d.ts +45 -0
- package/dist/core/agent/guard.d.ts.map +1 -0
- package/dist/core/agent/guard.js +113 -0
- package/dist/core/agent/guard.js.map +1 -0
- package/dist/core/agent.d.ts +17 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +182 -93
- package/dist/core/agent.js.map +1 -1
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +34 -2
- package/dist/core/factory.js.map +1 -1
- package/dist/core/file_checkpoint.d.ts +57 -0
- package/dist/core/file_checkpoint.d.ts.map +1 -0
- package/dist/core/file_checkpoint.js +162 -0
- package/dist/core/file_checkpoint.js.map +1 -0
- package/dist/core/hooks.d.ts +43 -0
- package/dist/core/hooks.d.ts.map +1 -0
- package/dist/core/hooks.js +110 -0
- package/dist/core/hooks.js.map +1 -0
- package/dist/core/llm.d.ts.map +1 -1
- package/dist/core/llm.js +15 -9
- package/dist/core/llm.js.map +1 -1
- package/dist/core/longdoc.js +5 -5
- package/dist/core/mcp.d.ts +16 -0
- package/dist/core/mcp.d.ts.map +1 -1
- package/dist/core/mcp.js +55 -0
- package/dist/core/mcp.js.map +1 -1
- package/dist/core/model_config.d.ts +40 -0
- package/dist/core/model_config.d.ts.map +1 -0
- package/dist/core/model_config.js +191 -0
- package/dist/core/model_config.js.map +1 -0
- package/dist/core/skill.d.ts +7 -0
- package/dist/core/skill.d.ts.map +1 -1
- package/dist/core/skill.js +47 -0
- package/dist/core/skill.js.map +1 -1
- package/dist/core/skymd.d.ts +39 -0
- package/dist/core/skymd.d.ts.map +1 -0
- package/dist/core/skymd.js +177 -0
- package/dist/core/skymd.js.map +1 -0
- package/dist/core/tool.d.ts +12 -0
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +30 -0
- package/dist/core/tool.js.map +1 -1
- package/dist/core/verify.d.ts +27 -0
- package/dist/core/verify.d.ts.map +1 -0
- package/dist/core/verify.js +62 -0
- package/dist/core/verify.js.map +1 -0
- package/dist/skills/loader.d.ts +22 -2
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +45 -15
- package/dist/skills/loader.js.map +1 -1
- package/dist/tools/builtin.d.ts.map +1 -1
- package/dist/tools/builtin.js +13 -3
- package/dist/tools/builtin.js.map +1 -1
- package/dist/tools/model_tool.d.ts +11 -0
- package/dist/tools/model_tool.d.ts.map +1 -0
- package/dist/tools/model_tool.js +71 -0
- package/dist/tools/model_tool.js.map +1 -0
- package/dist/tools/todo.d.ts +30 -0
- package/dist/tools/todo.d.ts.map +1 -0
- package/dist/tools/todo.js +78 -0
- package/dist/tools/todo.js.map +1 -0
- package/docs/AESTHETIC_DESIGN.md +152 -144
- package/docs/OPTIMIZATION_PLAN.md +178 -178
- package/package.json +1 -1
- package/scripts/install.js +48 -48
- package/scripts/link.js +10 -10
- package/setup.bat +79 -79
- package/skill-test-ty2fOA/test.md +10 -10
- package/src/agents/dew.ts +70 -70
- package/src/agents/fair.ts +102 -102
- package/src/agents/fog.ts +48 -48
- package/src/agents/frost.ts +50 -50
- package/src/agents/rain.ts +50 -50
- package/src/agents/snow.ts +239 -239
- package/src/cli/commands_md.ts +112 -0
- package/src/cli/input_macros.ts +83 -0
- package/src/cli/loom.ts +982 -0
- package/src/cli/loom_chat.ts +598 -0
- package/src/cli/main.ts +255 -9
- package/src/cli/mode.ts +58 -58
- package/src/cli/tui.ts +228 -222
- package/src/core/agent/guard.ts +134 -0
- package/src/core/agent/task.ts +100 -100
- package/src/core/agent.ts +177 -95
- package/src/core/arbitrate.ts +162 -162
- package/src/core/catalog.ts +178 -178
- package/src/core/checkpoint.ts +94 -94
- package/src/core/estimate.ts +104 -104
- package/src/core/evolve.ts +191 -191
- package/src/core/factory.ts +31 -2
- package/src/core/file_checkpoint.ts +136 -0
- package/src/core/filter.ts +103 -103
- package/src/core/graph.ts +156 -156
- package/src/core/hooks.ts +126 -0
- package/src/core/icons.ts +53 -53
- package/src/core/index.ts +37 -37
- package/src/core/learn.ts +146 -146
- package/src/core/llm.ts +15 -9
- package/src/core/longdoc.ts +155 -155
- package/src/core/mcp.ts +48 -0
- package/src/core/mcp_server.ts +176 -176
- package/src/core/model_config.ts +157 -0
- package/src/core/profile.ts +255 -255
- package/src/core/router.ts +124 -124
- package/src/core/sandbox.ts +142 -142
- package/src/core/security.ts +243 -243
- package/src/core/skill.ts +42 -0
- package/src/core/skymd.ts +143 -0
- package/src/core/theme.ts +65 -65
- package/src/core/tool.ts +30 -0
- package/src/core/tool_router.ts +193 -193
- package/src/core/vector.ts +152 -152
- package/src/core/verify.ts +71 -0
- package/src/core/workspace.ts +150 -150
- package/src/plugins/loader.ts +66 -66
- package/src/skills/loader.ts +45 -16
- package/src/sql.js.d.ts +29 -29
- package/src/tools/builtin.ts +13 -3
- package/src/tools/computer.ts +269 -269
- package/src/tools/delegate.ts +49 -49
- package/src/tools/model_tool.ts +74 -0
- package/src/tools/todo.ts +76 -0
- package/src/web/tts.ts +93 -93
- package/tests/agent.test.ts +159 -159
- package/tests/agent_helpers.test.ts +48 -48
- package/tests/bus.test.ts +121 -121
- package/tests/catalog.test.ts +86 -86
- package/tests/checkpoint_commands.test.ts +124 -0
- package/tests/claude_compat.test.ts +110 -0
- package/tests/config.test.ts +41 -41
- package/tests/guard.test.ts +75 -0
- package/tests/icons.test.ts +45 -45
- package/tests/loom.test.ts +248 -0
- package/tests/memory.test.ts +170 -170
- package/tests/model_config.test.ts +109 -0
- package/tests/router.test.ts +86 -86
- package/tests/schemas.test.ts +51 -51
- package/tests/semantic.test.ts +83 -83
- package/tests/setup.ts +10 -10
- package/tests/skill.test.ts +172 -172
- package/tests/skymd.test.ts +146 -0
- package/tests/task.test.ts +60 -60
- package/tests/todo_toolstats.test.ts +94 -0
- package/tests/tool.test.ts +108 -108
- package/tests/tool_router.test.ts +71 -71
- package/tests/tui.test.ts +67 -67
- package/vitest.config.ts +17 -17
package/src/core/agent/task.ts
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent domain model — states and the Task DAG node.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from the monolithic agent.ts (Phase 3). Pure, dependency-free,
|
|
5
|
-
* and unit-testable in isolation. `agent.ts` re-exports these so external
|
|
6
|
-
* importers of `../core/agent` are unaffected.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** Lifecycle state of a running agent. */
|
|
10
|
-
export enum AgentState {
|
|
11
|
-
IDLE = 'idle',
|
|
12
|
-
THINKING = 'thinking',
|
|
13
|
-
ACTING = 'acting',
|
|
14
|
-
WAITING = 'waiting',
|
|
15
|
-
ERROR = 'error',
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Lifecycle state of an orchestrated task. */
|
|
19
|
-
export enum TaskState {
|
|
20
|
-
PENDING = 'pending',
|
|
21
|
-
RUNNING = 'running',
|
|
22
|
-
COMPLETED = 'completed',
|
|
23
|
-
FAILED = 'failed',
|
|
24
|
-
SKIPPED = 'skipped',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Allowed task state transitions. A terminal state has no outgoing edges. */
|
|
28
|
-
export const VALID_TRANSITIONS: Record<TaskState, Set<TaskState>> = {
|
|
29
|
-
[TaskState.PENDING]: new Set([TaskState.RUNNING, TaskState.SKIPPED, TaskState.FAILED]),
|
|
30
|
-
[TaskState.RUNNING]: new Set([TaskState.RUNNING, TaskState.COMPLETED, TaskState.FAILED]),
|
|
31
|
-
[TaskState.FAILED]: new Set([TaskState.RUNNING, TaskState.SKIPPED]),
|
|
32
|
-
[TaskState.COMPLETED]: new Set(),
|
|
33
|
-
[TaskState.SKIPPED]: new Set(),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/** A node in the orchestration DAG. */
|
|
37
|
-
export class Task {
|
|
38
|
-
id: string;
|
|
39
|
-
description: string;
|
|
40
|
-
assignedTo: string | null = null;
|
|
41
|
-
parentId: string | null = null;
|
|
42
|
-
dependsOn: string[] = [];
|
|
43
|
-
status: TaskState = TaskState.PENDING;
|
|
44
|
-
priority: number = 0;
|
|
45
|
-
result: string | null = null;
|
|
46
|
-
metadata: Record<string, any> = {};
|
|
47
|
-
|
|
48
|
-
constructor(config: {
|
|
49
|
-
id: string;
|
|
50
|
-
description: string;
|
|
51
|
-
assignedTo?: string | null;
|
|
52
|
-
parentId?: string | null;
|
|
53
|
-
dependsOn?: string[];
|
|
54
|
-
status?: TaskState;
|
|
55
|
-
priority?: number;
|
|
56
|
-
result?: string | null;
|
|
57
|
-
metadata?: Record<string, any>;
|
|
58
|
-
}) {
|
|
59
|
-
this.id = config.id;
|
|
60
|
-
this.description = config.description;
|
|
61
|
-
this.assignedTo = config.assignedTo ?? null;
|
|
62
|
-
this.parentId = config.parentId ?? null;
|
|
63
|
-
this.dependsOn = config.dependsOn || [];
|
|
64
|
-
this.status = config.status ?? TaskState.PENDING;
|
|
65
|
-
this.priority = config.priority ?? 0;
|
|
66
|
-
this.result = config.result ?? null;
|
|
67
|
-
this.metadata = config.metadata || {};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
transitionTo(newState: TaskState): void {
|
|
71
|
-
const allowed = VALID_TRANSITIONS[this.status] || new Set();
|
|
72
|
-
if (!allowed.has(newState)) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Invalid task state transition: ${this.status} -> ${newState}`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
this.status = newState;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
get allDeps(): string[] {
|
|
81
|
-
const deps = [...this.dependsOn];
|
|
82
|
-
if (this.parentId && !deps.includes(this.parentId)) {
|
|
83
|
-
deps.push(this.parentId);
|
|
84
|
-
}
|
|
85
|
-
return deps;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Result of executing a single task. */
|
|
90
|
-
export class TaskResult {
|
|
91
|
-
success: boolean;
|
|
92
|
-
content: string;
|
|
93
|
-
data: Record<string, any> = {};
|
|
94
|
-
|
|
95
|
-
constructor(success: boolean, content: string, data?: Record<string, any>) {
|
|
96
|
-
this.success = success;
|
|
97
|
-
this.content = content;
|
|
98
|
-
this.data = data || {};
|
|
99
|
-
}
|
|
100
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Agent domain model — states and the Task DAG node.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the monolithic agent.ts (Phase 3). Pure, dependency-free,
|
|
5
|
+
* and unit-testable in isolation. `agent.ts` re-exports these so external
|
|
6
|
+
* importers of `../core/agent` are unaffected.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Lifecycle state of a running agent. */
|
|
10
|
+
export enum AgentState {
|
|
11
|
+
IDLE = 'idle',
|
|
12
|
+
THINKING = 'thinking',
|
|
13
|
+
ACTING = 'acting',
|
|
14
|
+
WAITING = 'waiting',
|
|
15
|
+
ERROR = 'error',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Lifecycle state of an orchestrated task. */
|
|
19
|
+
export enum TaskState {
|
|
20
|
+
PENDING = 'pending',
|
|
21
|
+
RUNNING = 'running',
|
|
22
|
+
COMPLETED = 'completed',
|
|
23
|
+
FAILED = 'failed',
|
|
24
|
+
SKIPPED = 'skipped',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Allowed task state transitions. A terminal state has no outgoing edges. */
|
|
28
|
+
export const VALID_TRANSITIONS: Record<TaskState, Set<TaskState>> = {
|
|
29
|
+
[TaskState.PENDING]: new Set([TaskState.RUNNING, TaskState.SKIPPED, TaskState.FAILED]),
|
|
30
|
+
[TaskState.RUNNING]: new Set([TaskState.RUNNING, TaskState.COMPLETED, TaskState.FAILED]),
|
|
31
|
+
[TaskState.FAILED]: new Set([TaskState.RUNNING, TaskState.SKIPPED]),
|
|
32
|
+
[TaskState.COMPLETED]: new Set(),
|
|
33
|
+
[TaskState.SKIPPED]: new Set(),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** A node in the orchestration DAG. */
|
|
37
|
+
export class Task {
|
|
38
|
+
id: string;
|
|
39
|
+
description: string;
|
|
40
|
+
assignedTo: string | null = null;
|
|
41
|
+
parentId: string | null = null;
|
|
42
|
+
dependsOn: string[] = [];
|
|
43
|
+
status: TaskState = TaskState.PENDING;
|
|
44
|
+
priority: number = 0;
|
|
45
|
+
result: string | null = null;
|
|
46
|
+
metadata: Record<string, any> = {};
|
|
47
|
+
|
|
48
|
+
constructor(config: {
|
|
49
|
+
id: string;
|
|
50
|
+
description: string;
|
|
51
|
+
assignedTo?: string | null;
|
|
52
|
+
parentId?: string | null;
|
|
53
|
+
dependsOn?: string[];
|
|
54
|
+
status?: TaskState;
|
|
55
|
+
priority?: number;
|
|
56
|
+
result?: string | null;
|
|
57
|
+
metadata?: Record<string, any>;
|
|
58
|
+
}) {
|
|
59
|
+
this.id = config.id;
|
|
60
|
+
this.description = config.description;
|
|
61
|
+
this.assignedTo = config.assignedTo ?? null;
|
|
62
|
+
this.parentId = config.parentId ?? null;
|
|
63
|
+
this.dependsOn = config.dependsOn || [];
|
|
64
|
+
this.status = config.status ?? TaskState.PENDING;
|
|
65
|
+
this.priority = config.priority ?? 0;
|
|
66
|
+
this.result = config.result ?? null;
|
|
67
|
+
this.metadata = config.metadata || {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
transitionTo(newState: TaskState): void {
|
|
71
|
+
const allowed = VALID_TRANSITIONS[this.status] || new Set();
|
|
72
|
+
if (!allowed.has(newState)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Invalid task state transition: ${this.status} -> ${newState}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
this.status = newState;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get allDeps(): string[] {
|
|
81
|
+
const deps = [...this.dependsOn];
|
|
82
|
+
if (this.parentId && !deps.includes(this.parentId)) {
|
|
83
|
+
deps.push(this.parentId);
|
|
84
|
+
}
|
|
85
|
+
return deps;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Result of executing a single task. */
|
|
90
|
+
export class TaskResult {
|
|
91
|
+
success: boolean;
|
|
92
|
+
content: string;
|
|
93
|
+
data: Record<string, any> = {};
|
|
94
|
+
|
|
95
|
+
constructor(success: boolean, content: string, data?: Record<string, any>) {
|
|
96
|
+
this.success = success;
|
|
97
|
+
this.content = content;
|
|
98
|
+
this.data = data || {};
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/core/agent.ts
CHANGED
|
@@ -14,25 +14,41 @@ import { Skill, SkillRegistry } from './skill';
|
|
|
14
14
|
import { type ToolDefinition, ToolRegistry } from './tool';
|
|
15
15
|
import {
|
|
16
16
|
parseToolArgs,
|
|
17
|
-
looksLikeFailedToolResult,
|
|
18
17
|
extractFilePathsFromMessages,
|
|
19
18
|
enrichResponseWithArtifacts,
|
|
20
|
-
toolCallSignature,
|
|
21
|
-
textSimilarity,
|
|
22
19
|
formatArgsParseError,
|
|
23
20
|
suggestToolNames,
|
|
24
21
|
toolStatusLabel,
|
|
25
22
|
synthesizeDelegationSummary,
|
|
26
23
|
parseExtractedFacts,
|
|
27
|
-
SIG_WINDOW,
|
|
28
|
-
SIG_LOOP_HINT,
|
|
29
|
-
SIG_LOOP_HARDSTOP,
|
|
30
24
|
} from './agent_helpers';
|
|
31
25
|
import { selectRelevantTools } from './tool_router';
|
|
32
26
|
import { getModelInfo } from './catalog';
|
|
27
|
+
import { LoopGuard } from './agent/guard';
|
|
33
28
|
|
|
34
29
|
const log = getLogger('agent');
|
|
35
30
|
|
|
31
|
+
/** Tools whose success means the filesystem changed (triggers the verify loop). */
|
|
32
|
+
const WRITE_TOOL_RE = /^(write_|edit_|delete_|create_)|^run_bash$|^git_commit$/;
|
|
33
|
+
|
|
34
|
+
/** Tools with side effects, hidden from the model while in plan mode. */
|
|
35
|
+
const SIDE_EFFECT_TOOL_RE = /^(write_|edit_|delete_|create_|kill_|launch_|service_|browser_)|^run_bash$|^git_commit$|^open_path$|^delegate_to$/;
|
|
36
|
+
|
|
37
|
+
/** Default context budget per recorded tool result (chars; ~3k tokens). */
|
|
38
|
+
const TOOL_RESULT_LIMIT = 12000;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Clamp an oversized tool result before it enters the context window:
|
|
42
|
+
* keep head + tail, tell the model what was cut and how to fetch precisely.
|
|
43
|
+
*/
|
|
44
|
+
export function clampToolResult(s: string, limit: number = TOOL_RESULT_LIMIT): string {
|
|
45
|
+
if (s.length <= limit) return s;
|
|
46
|
+
const head = s.slice(0, Math.floor(limit * 0.72));
|
|
47
|
+
const tail = s.slice(-Math.floor(limit * 0.18));
|
|
48
|
+
const cut = s.length - head.length - tail.length;
|
|
49
|
+
return `${head}\n…[工具结果过长,中间省略 ${cut} 字符 — 需要该部分时用更精确的参数重新调用(read_file 的 offset/limit、grep 定位、缩小查询范围)]\n${tail}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
// Domain model lives in ./agent/task — re-exported here so importers of
|
|
37
53
|
// '../core/agent' are unaffected by the Phase 3 split.
|
|
38
54
|
import { AgentState, TaskState, Task, TaskResult } from './agent/task';
|
|
@@ -70,6 +86,11 @@ export class BaseAgent {
|
|
|
70
86
|
protected _pendingRequests: Map<string, { resolve: (value: string) => void; reject: (err: Error) => void }> = new Map();
|
|
71
87
|
protected _bgTasks: Set<Promise<void>> = new Set();
|
|
72
88
|
approvalCallback: ((toolName: string, args: Record<string, any>) => Promise<boolean>) | null = null;
|
|
89
|
+
/** Plan mode: read-only tool set + plan-first instructions on each turn. */
|
|
90
|
+
planMode: boolean = false;
|
|
91
|
+
/** Set when this turn executed a tool that mutates the filesystem (verify trigger). */
|
|
92
|
+
protected _turnWroteFiles: boolean = false;
|
|
93
|
+
private _hooks: import('./hooks').Hooks | null = null;
|
|
73
94
|
protected _turnLock: Promise<void> = Promise.resolve();
|
|
74
95
|
private _turnLockCounter: number = 0;
|
|
75
96
|
private _turnLockResolve: (() => void) | null = null;
|
|
@@ -150,10 +171,10 @@ export class BaseAgent {
|
|
|
150
171
|
const lang = (this.config as any).llm?.language || 'zh';
|
|
151
172
|
if (lang === 'en') {
|
|
152
173
|
return prompt +
|
|
153
|
-
`\n\n## Thinking Protocol\nBefore acting, briefly weigh: (1) **What** is the actual need? (2) **How** sure am I? If <80%, flag with [uncertain] and ask.\nIf stuck, admit it — propose a partial answer or ask the user. Never fabricate.\n\n## Behavior\n- Act, don't narrate. No "I will..." before tool calls.\n- Stay in scope. Do what's asked, then stop.\n- Batch independent tool calls in one response.\n- Verify writes: read back, report verified state.\n- Call list_skills when the task needs specialized capabilities.`;
|
|
174
|
+
`\n\n## Thinking Protocol\nBefore acting, briefly weigh: (1) **What** is the actual need? (2) **How** sure am I? If <80%, flag with [uncertain] and ask.\nIf stuck, admit it — propose a partial answer or ask the user. Never fabricate.\n\n## Behavior\n- Act, don't narrate. No "I will..." before tool calls.\n- Stay in scope. Do what's asked, then stop.\n- Batch independent tool calls in one response.\n- For tasks with 3+ steps, plan with todo_write first and update item status as you go.\n- Verify writes: read back, report verified state.\n- Call list_skills when the task needs specialized capabilities.`;
|
|
154
175
|
}
|
|
155
176
|
return prompt +
|
|
156
|
-
`\n\n## 思考协议\n行动前快速判断:(1) 用户真实需求是什么?(2) 我有多大把握?低于80%标注 [不确定] 并主动询问。\n卡住时承认,给出部分答案或请求用户指导。绝不编造。\n\n## 行为守则\n- 直接行动,不预告。不说「我将要...」,直接调用工具\n- 不擅自扩大范围。用户要什么做什么,核心完成即止\n- 独立的工具调用一次发出,并行执行\n- 写入后回读验证,汇报已验证状态而非仅尝试\n- 任务涉及专业能力时(PPT/Excel/PDF/网页设计/代码审查等),先调 list_skills 查看可用技能,再用 use_skill 激活`;
|
|
177
|
+
`\n\n## 思考协议\n行动前快速判断:(1) 用户真实需求是什么?(2) 我有多大把握?低于80%标注 [不确定] 并主动询问。\n卡住时承认,给出部分答案或请求用户指导。绝不编造。\n\n## 行为守则\n- 直接行动,不预告。不说「我将要...」,直接调用工具\n- 不擅自扩大范围。用户要什么做什么,核心完成即止\n- 独立的工具调用一次发出,并行执行\n- 3 步以上的任务先用 todo_write 列任务清单,开工/完成时逐项更新状态\n- 写入后回读验证,汇报已验证状态而非仅尝试\n- 任务涉及专业能力时(PPT/Excel/PDF/网页设计/代码审查等),先调 list_skills 查看可用技能,再用 use_skill 激活`;
|
|
157
178
|
}
|
|
158
179
|
|
|
159
180
|
protected injectProgrammingWisdom(prompt: string): string {
|
|
@@ -164,16 +185,34 @@ export class BaseAgent {
|
|
|
164
185
|
return prompt + `\n\n## 工程能力\n顶级工程师:类型安全、真实的错误处理、按根因调试、按安全与性能审查。你可以阅读和修改 Skyloom 自身源码。`;
|
|
165
186
|
}
|
|
166
187
|
|
|
188
|
+
/** Layered SKY.md / CLAUDE.md / AGENTS.md project memory (see core/skymd). */
|
|
189
|
+
protected injectProjectMemory(prompt: string): string {
|
|
190
|
+
try {
|
|
191
|
+
const { loadProjectMemory } = require('./skymd');
|
|
192
|
+
const mem = loadProjectMemory();
|
|
193
|
+
if (!mem.text) return prompt;
|
|
194
|
+
return prompt + `\n\n## 项目记忆 (SKY.md)\n用户与项目维护的约定,优先级高于你的通用习惯:\n\n${mem.text}`;
|
|
195
|
+
} catch {
|
|
196
|
+
return prompt;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
167
200
|
reinitLanguage(): void {
|
|
168
201
|
this._baseSystemPrompt = '';
|
|
169
202
|
this._baseSystemPrompt = this.resolveSystemPrompt();
|
|
170
203
|
this._baseSystemPrompt = this.injectWorkspaceInfo(this._baseSystemPrompt);
|
|
171
204
|
this._baseSystemPrompt = this.injectBehaviorRules(this._baseSystemPrompt);
|
|
172
205
|
this._baseSystemPrompt = this.injectProgrammingWisdom(this._baseSystemPrompt);
|
|
206
|
+
this._baseSystemPrompt = this.injectProjectMemory(this._baseSystemPrompt);
|
|
173
207
|
this._baseSystemPrompt += '\n\n' + this.currentTimeTag();
|
|
174
208
|
this.rebuildSystemPrompt();
|
|
175
209
|
}
|
|
176
210
|
|
|
211
|
+
/** Re-read SKY.md layers into the system prompt (after `#` quick memory / edits). */
|
|
212
|
+
reloadProjectMemory(): void {
|
|
213
|
+
this.reinitLanguage();
|
|
214
|
+
}
|
|
215
|
+
|
|
177
216
|
async init(): Promise<void> {
|
|
178
217
|
if (this._baseSystemPrompt) return;
|
|
179
218
|
await this.memory.initDb();
|
|
@@ -191,6 +230,7 @@ export class BaseAgent {
|
|
|
191
230
|
this._baseSystemPrompt = this.injectWorkspaceInfo(this._baseSystemPrompt);
|
|
192
231
|
this._baseSystemPrompt = this.injectBehaviorRules(this._baseSystemPrompt);
|
|
193
232
|
this._baseSystemPrompt = this.injectProgrammingWisdom(this._baseSystemPrompt);
|
|
233
|
+
this._baseSystemPrompt = this.injectProjectMemory(this._baseSystemPrompt);
|
|
194
234
|
this._baseSystemPrompt += '\n\n' + this.currentTimeTag();
|
|
195
235
|
this.rebuildSystemPrompt();
|
|
196
236
|
this._tools = this.toolRegistry.getTools();
|
|
@@ -217,6 +257,13 @@ export class BaseAgent {
|
|
|
217
257
|
description: 'List all available skills with their names and descriptions. Use this first to discover what skills you can activate.',
|
|
218
258
|
parameters: [],
|
|
219
259
|
handler: async () => {
|
|
260
|
+
// live change detection: re-scan user/project skill folders so a
|
|
261
|
+
// SKILL.md edit or drop-in applies without restarting the session
|
|
262
|
+
try {
|
|
263
|
+
const { registerDynamicSkills } = require('../skills/loader');
|
|
264
|
+
registerDynamicSkills(self.skillRegistry);
|
|
265
|
+
self.loadSkills();
|
|
266
|
+
} catch { /* live reload is best-effort */ }
|
|
220
267
|
const skills = self.getAvailableSkills();
|
|
221
268
|
if (!skills.length) return 'No skills available.';
|
|
222
269
|
const maxName = Math.max(...skills.map(s => s.name.length), 1);
|
|
@@ -558,9 +605,37 @@ export class BaseAgent {
|
|
|
558
605
|
if (onStatus) onStatus(p.label);
|
|
559
606
|
await this.setState(AgentState.ACTING);
|
|
560
607
|
|
|
608
|
+
// File checkpoint: snapshot the target before any mutating file tool
|
|
609
|
+
// runs, so /rewind can restore the pre-turn state.
|
|
610
|
+
try {
|
|
611
|
+
const { getFileCheckpoints } = require('./file_checkpoint');
|
|
612
|
+
const cp = getFileCheckpoints();
|
|
613
|
+
const snapPath = cp.pathToSnapshot(p.toolName, p.toolArgs || {});
|
|
614
|
+
if (snapPath) cp.snapshot(snapPath);
|
|
615
|
+
} catch { /* checkpointing must never block execution */ }
|
|
616
|
+
|
|
617
|
+
// pre_tool hooks are enforced policy: a non-zero exit blocks the call.
|
|
618
|
+
const hooks = this.getHooks();
|
|
619
|
+
if (hooks.preTool.length > 0) {
|
|
620
|
+
try {
|
|
621
|
+
const { runPreToolHooks } = require('./hooks');
|
|
622
|
+
const pre = runPreToolHooks(hooks, p.toolName, p.toolArgs || {}, this.name);
|
|
623
|
+
if (!pre.allowed) {
|
|
624
|
+
return { idx, result: { tc: p.tc, result: `[blocked by pre_tool hook] ${pre.reason}`, success: false, toolName: p.toolName } };
|
|
625
|
+
}
|
|
626
|
+
} catch { /* hook machinery must never break tool execution */ }
|
|
627
|
+
}
|
|
628
|
+
|
|
561
629
|
try {
|
|
562
630
|
const toolResult = await this.toolRegistry.execute(p.toolName, p.toolArgs || {});
|
|
563
631
|
const resultStr = toolResult.result || toolResult.error || '(no output)';
|
|
632
|
+
if (toolResult.success && WRITE_TOOL_RE.test(p.toolName)) this._turnWroteFiles = true;
|
|
633
|
+
if (hooks.postTool.length > 0) {
|
|
634
|
+
try {
|
|
635
|
+
const { runPostToolHooks } = require('./hooks');
|
|
636
|
+
runPostToolHooks(hooks, p.toolName, p.toolArgs || {}, this.name);
|
|
637
|
+
} catch { /* best-effort */ }
|
|
638
|
+
}
|
|
564
639
|
return { idx, result: { tc: p.tc, result: resultStr, success: toolResult.success, toolName: p.toolName } };
|
|
565
640
|
} catch (e) {
|
|
566
641
|
return { idx, result: { tc: p.tc, result: `Tool '${p.toolName}' execution failed: ${e}`, success: false, toolName: p.toolName } };
|
|
@@ -583,7 +658,9 @@ export class BaseAgent {
|
|
|
583
658
|
}
|
|
584
659
|
}
|
|
585
660
|
|
|
586
|
-
// Phase D: Record results to memory
|
|
661
|
+
// Phase D: Record results to memory (clamped — one runaway read_file or
|
|
662
|
+
// http_get must not flood the context window)
|
|
663
|
+
const resultLimit = Number((this.config as any)?.llm?.tool_result_limit) || undefined;
|
|
587
664
|
for (const r of results) {
|
|
588
665
|
if (!r) continue;
|
|
589
666
|
|
|
@@ -591,7 +668,7 @@ export class BaseAgent {
|
|
|
591
668
|
if (suppressed) suppressed.add(r.toolName);
|
|
592
669
|
}
|
|
593
670
|
|
|
594
|
-
this.memory.addMessage('tool', r.result, {
|
|
671
|
+
this.memory.addMessage('tool', clampToolResult(r.result, resultLimit), {
|
|
595
672
|
name: r.toolName,
|
|
596
673
|
toolCallId: r.tc.id,
|
|
597
674
|
ephemeral,
|
|
@@ -731,7 +808,15 @@ export class BaseAgent {
|
|
|
731
808
|
signal?: AbortSignal
|
|
732
809
|
): AsyncGenerator<Record<string, any>> {
|
|
733
810
|
await this.setState(AgentState.THINKING);
|
|
734
|
-
|
|
811
|
+
// Plan mode: the tag travels with the message so the model plans instead
|
|
812
|
+
// of acting, and the read-only tool filter below removes the temptation.
|
|
813
|
+
const userMessage = this.planMode
|
|
814
|
+
? `[计划模式] 只读调研,不要执行任何修改。请输出一份编号的执行计划(涉及哪些文件、每步做什么、风险点),等待用户批准后再实施。\n\n${message}`
|
|
815
|
+
: message;
|
|
816
|
+
this.memory.addMessage('user', userMessage);
|
|
817
|
+
try {
|
|
818
|
+
require('./file_checkpoint').getFileCheckpoints().beginTurn(message);
|
|
819
|
+
} catch { /* optional */ }
|
|
735
820
|
let assistantStored = false;
|
|
736
821
|
|
|
737
822
|
if (this.shouldAutoCompact()) {
|
|
@@ -749,20 +834,22 @@ export class BaseAgent {
|
|
|
749
834
|
);
|
|
750
835
|
}
|
|
751
836
|
|
|
752
|
-
const
|
|
753
|
-
let stuckHintInjected = false;
|
|
754
|
-
const recentResponseTexts: string[] = [];
|
|
755
|
-
let repetitionHintInjected = false;
|
|
756
|
-
const recentToolSigs: string[] = [];
|
|
757
|
-
let toolLoopHintInjected = false;
|
|
837
|
+
const guard = new LoopGuard();
|
|
758
838
|
|
|
759
839
|
let toolNamesCache: string[] | null = null;
|
|
760
840
|
let cacheKey: string | null = null;
|
|
761
841
|
|
|
762
842
|
const resolveToolNames = (): string[] => {
|
|
763
|
-
const key = JSON.stringify([[...suppressedTools].sort(), [...this._activeSkills].sort()]);
|
|
843
|
+
const key = JSON.stringify([[...suppressedTools].sort(), [...this._activeSkills].sort(), this.planMode]);
|
|
764
844
|
if (toolNamesCache !== null && cacheKey === key) return toolNamesCache;
|
|
765
845
|
let candidates = this.activeToolNames().filter(t => !suppressedTools.has(t));
|
|
846
|
+
if (this.planMode) {
|
|
847
|
+
candidates = candidates.filter(n => {
|
|
848
|
+
if (SIDE_EFFECT_TOOL_RE.test(n)) return false;
|
|
849
|
+
const t = this.toolRegistry.get(n);
|
|
850
|
+
return !(t as any)?.dangerous;
|
|
851
|
+
});
|
|
852
|
+
}
|
|
766
853
|
const must = new Set<string>();
|
|
767
854
|
for (const s of this._skills) {
|
|
768
855
|
if (this._activeSkills.has(s.name)) {
|
|
@@ -914,82 +1001,12 @@ export class BaseAgent {
|
|
|
914
1001
|
return;
|
|
915
1002
|
}
|
|
916
1003
|
|
|
917
|
-
// ──
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
repetitionHintInjected = true;
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
recentResponseTexts.push(normalizedRound);
|
|
927
|
-
if (recentResponseTexts.length > 3) recentResponseTexts.shift();
|
|
928
|
-
|
|
929
|
-
// ── Tool-signature loop detection ──
|
|
930
|
-
for (const tc of toolCallsReceived) {
|
|
931
|
-
const tName = tc.function.name;
|
|
932
|
-
if (['task_done', 'list_skills', 'use_skill'].includes(tName)) continue;
|
|
933
|
-
const rawArgs = tc.function.arguments;
|
|
934
|
-
const tArgs = typeof rawArgs === 'string' ? parseToolArgs(rawArgs) : rawArgs;
|
|
935
|
-
const sig = toolCallSignature(tName, tArgs);
|
|
936
|
-
if (sig) recentToolSigs.push(sig);
|
|
937
|
-
}
|
|
938
|
-
if (recentToolSigs.length > SIG_WINDOW) {
|
|
939
|
-
recentToolSigs.splice(0, recentToolSigs.length - SIG_WINDOW);
|
|
940
|
-
}
|
|
941
|
-
if (recentToolSigs.length > 0) {
|
|
942
|
-
const counts = new Map<string, number>();
|
|
943
|
-
for (const s of recentToolSigs) counts.set(s, (counts.get(s) || 0) + 1);
|
|
944
|
-
let topSig = '';
|
|
945
|
-
let topCount = 0;
|
|
946
|
-
for (const [s, c] of counts) { if (c > topCount) { topSig = s; topCount = c; } }
|
|
947
|
-
if (topCount >= SIG_LOOP_HINT && !toolLoopHintInjected) {
|
|
948
|
-
this.memory.addMessage('system', `[Tool loop] You have called \`${topSig}\` ${topCount}x in the last ${recentToolSigs.length} tool calls — you are iterating without converging. STOP repeating it.`);
|
|
949
|
-
toolLoopHintInjected = true;
|
|
950
|
-
}
|
|
951
|
-
if (topCount >= SIG_LOOP_HARDSTOP) {
|
|
952
|
-
this.memory.addMessage('assistant', `I have repeated \`${topSig}\` ${topCount} times without converging. Stopping.`);
|
|
953
|
-
yield { type: 'content', text: `\n\n[stuck] tool \`${topSig}\` repeated ${topCount}x — stopping.` };
|
|
954
|
-
await this.setState(AgentState.IDLE);
|
|
955
|
-
yield { type: 'done' };
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
// ── Stuck-loop detection ──
|
|
961
|
-
for (const r of execResults) {
|
|
962
|
-
if (!r || r.toolName === 'task_done') continue;
|
|
963
|
-
const failed = !r.success || (typeof r.result === 'string' && looksLikeFailedToolResult(r.result));
|
|
964
|
-
recentToolOutcomes.push(!failed);
|
|
965
|
-
if (recentToolOutcomes.length > 6) recentToolOutcomes.shift();
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (!stuckHintInjected && recentToolOutcomes.length >= 5 &&
|
|
969
|
-
recentToolOutcomes.filter(Boolean).length <= 1) {
|
|
970
|
-
this.memory.addMessage('system', '[Recovery hint] Your last several tool calls have mostly failed. Synthesize a partial answer from what worked or ask the user for guidance.');
|
|
971
|
-
stuckHintInjected = true;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
if (recentToolOutcomes.length >= 8 && recentToolOutcomes.every(x => !x)) {
|
|
975
|
-
this.memory.addMessage('assistant', 'Every recent tool call failed. Please give me more context.');
|
|
976
|
-
yield { type: 'content', text: '\n\n[stuck] every recent tool call failed — stopping.\n' };
|
|
977
|
-
await this.setState(AgentState.IDLE);
|
|
978
|
-
yield { type: 'done' };
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// ── Search-storm detection ──
|
|
983
|
-
const searchStormCount = recentToolSigs.filter(s =>
|
|
984
|
-
s.startsWith('web_search:') || ['fetch_page', 'http_get'].includes(s)
|
|
985
|
-
).length;
|
|
986
|
-
if (searchStormCount >= 8 && !toolLoopHintInjected) {
|
|
987
|
-
this.memory.addMessage('system', `[Search storm] ${searchStormCount} search calls. STOP searching and synthesize.`);
|
|
988
|
-
toolLoopHintInjected = true;
|
|
989
|
-
}
|
|
990
|
-
if (searchStormCount >= 12) {
|
|
991
|
-
this.memory.addMessage('assistant', 'Too many search requests. Synthesizing best answer.');
|
|
992
|
-
yield { type: 'content', text: `\n\n[stuck] excessive web searching (${searchStormCount} calls) — stopping.\n` };
|
|
1004
|
+
// ── Anti-loop guard (narration / tool-signature / stuck / search-storm) ──
|
|
1005
|
+
const decision = guard.observe(roundContent, toolCallsReceived, execResults);
|
|
1006
|
+
for (const hint of decision.hints) this.memory.addMessage('system', hint);
|
|
1007
|
+
if (decision.stop) {
|
|
1008
|
+
this.memory.addMessage('assistant', decision.stop.note);
|
|
1009
|
+
yield { type: 'content', text: decision.stop.contentLine };
|
|
993
1010
|
await this.setState(AgentState.IDLE);
|
|
994
1011
|
yield { type: 'done' };
|
|
995
1012
|
return;
|
|
@@ -1109,6 +1126,25 @@ export class BaseAgent {
|
|
|
1109
1126
|
};
|
|
1110
1127
|
}
|
|
1111
1128
|
|
|
1129
|
+
/** Per-role token breakdown for the /context command. */
|
|
1130
|
+
contextDetail(): Record<string, any> {
|
|
1131
|
+
const byRole: Record<string, { tokens: number; count: number }> = {};
|
|
1132
|
+
for (const m of this.memory.shortTerm) {
|
|
1133
|
+
const extra = (m as any).toolCalls ? JSON.stringify((m as any).toolCalls).length : 0;
|
|
1134
|
+
const tokens = Math.ceil(((m.content || '').length + extra) / 4);
|
|
1135
|
+
const slot = byRole[m.role] || (byRole[m.role] = { tokens: 0, count: 0 });
|
|
1136
|
+
slot.tokens += tokens;
|
|
1137
|
+
slot.count += 1;
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
...this.contextUsage(),
|
|
1141
|
+
byRole,
|
|
1142
|
+
systemPromptTokens: Math.ceil(this._baseSystemPrompt.length / 4),
|
|
1143
|
+
toolCount: this.activeToolNames().length,
|
|
1144
|
+
activeSkills: [...this._activeSkills],
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1112
1148
|
protected shouldAutoCompact(): boolean {
|
|
1113
1149
|
const usage = this.memory.getContextWindowUsage();
|
|
1114
1150
|
// Compact before hitting the real window — leave ~20% headroom for the reply.
|
|
@@ -1339,9 +1375,43 @@ export class BaseAgent {
|
|
|
1339
1375
|
|
|
1340
1376
|
this.memory.addMessage('user', prompt);
|
|
1341
1377
|
const preLen = this.memory.shortTerm.length;
|
|
1378
|
+
this._turnWroteFiles = false;
|
|
1379
|
+
try {
|
|
1380
|
+
require('./file_checkpoint').getFileCheckpoints().beginTurn(`[task] ${task.description}`);
|
|
1381
|
+
} catch { /* optional */ }
|
|
1342
1382
|
|
|
1343
1383
|
try {
|
|
1344
|
-
|
|
1384
|
+
let response = await this.llmLoop({ onStatus, ephemeral: true });
|
|
1385
|
+
|
|
1386
|
+
// ── 验证闭环: if this task touched the filesystem and verify commands
|
|
1387
|
+
// are configured (config.verify or SKY.md "## Verify"), run them and
|
|
1388
|
+
// feed failures back for a bounded number of fix rounds. ──
|
|
1389
|
+
try {
|
|
1390
|
+
const { resolveVerifyConfig, runVerify } = require('./verify');
|
|
1391
|
+
const vc = resolveVerifyConfig(this.config);
|
|
1392
|
+
if (vc.commands.length > 0 && this._turnWroteFiles) {
|
|
1393
|
+
for (let round = 0; round <= vc.maxFixRounds; round++) {
|
|
1394
|
+
if (onStatus) onStatus(`verify: ${vc.commands.length} 条命令`);
|
|
1395
|
+
const vr = runVerify(vc);
|
|
1396
|
+
if (vr.ok) {
|
|
1397
|
+
response.content += `\n\n[verify ✓ 全部通过]\n${vr.report}`;
|
|
1398
|
+
break;
|
|
1399
|
+
}
|
|
1400
|
+
if (round === vc.maxFixRounds) {
|
|
1401
|
+
response.content += `\n\n[verify ✗ 经 ${vc.maxFixRounds} 轮修复仍未通过]\n${vr.report.slice(0, 1500)}`;
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1404
|
+
if (onStatus) onStatus(`verify 失败 — 修复第 ${round + 1}/${vc.maxFixRounds} 轮`);
|
|
1405
|
+
log.warn('verify_failed_fixing', { agent: this.name, round: round + 1 });
|
|
1406
|
+
this.memory.addMessage('user',
|
|
1407
|
+
`[自动验证失败] 以下验证命令未通过。请定位根因并修复,确保它们全部通过:\n\n${vr.report}`);
|
|
1408
|
+
response = await this.llmLoop({ onStatus, ephemeral: true });
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
} catch (e) {
|
|
1412
|
+
log.warn('verify_loop_error', { error: String(e) });
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1345
1415
|
const filePaths = extractFilePathsFromMessages(this.memory.shortTerm.slice(preLen));
|
|
1346
1416
|
const enriched = enrichResponseWithArtifacts(response.content, filePaths);
|
|
1347
1417
|
this.memory.addMessage('assistant', enriched, { toolCalls: response.toolCalls, reasoningContent: response.reasoningContent });
|
|
@@ -1365,6 +1435,18 @@ export class BaseAgent {
|
|
|
1365
1435
|
private _security: any = null;
|
|
1366
1436
|
get security(): any { if (!this._security) { try { const { getSecurity } = require('./security'); this._security = getSecurity(); } catch { this._security = {}; } } return this._security; }
|
|
1367
1437
|
|
|
1438
|
+
protected getHooks(): import('./hooks').Hooks {
|
|
1439
|
+
if (!this._hooks) {
|
|
1440
|
+
try {
|
|
1441
|
+
const { loadHooks } = require('./hooks');
|
|
1442
|
+
this._hooks = loadHooks(this.config);
|
|
1443
|
+
} catch {
|
|
1444
|
+
this._hooks = { sessionStart: [], preTool: [], postTool: [] };
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return this._hooks!;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1368
1450
|
protected async checkToolApproval(toolName: string, toolArgs: Record<string, any>): Promise<boolean> {
|
|
1369
1451
|
try {
|
|
1370
1452
|
const sec = this.security;
|