pulse-coder-engine 0.0.1-alpha.4 → 0.0.1-alpha.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/LICENSE +21 -0
- package/dist/built-in/index.cjs +465 -42
- package/dist/built-in/index.cjs.map +1 -1
- package/dist/built-in/index.d.cts +1 -1
- package/dist/built-in/index.d.ts +1 -1
- package/dist/built-in/index.js +447 -26
- package/dist/built-in/index.js.map +1 -1
- package/dist/index-BkkQyz2L.d.cts +291 -0
- package/dist/index-BkkQyz2L.d.ts +291 -0
- package/dist/index.cjs +523 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -95
- package/dist/index.d.ts +12 -95
- package/dist/index.js +499 -37
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/dist/index-DDqISE31.d.cts +0 -120
- package/dist/index-DDqISE31.d.ts +0 -120
package/dist/index.js
CHANGED
|
@@ -28,8 +28,9 @@ var CLARIFICATION_TIMEOUT = Number(process.env.CLARIFICATION_TIMEOUT ?? 3e5);
|
|
|
28
28
|
var CLARIFICATION_ENABLED = process.env.CLARIFICATION_ENABLED !== "false";
|
|
29
29
|
|
|
30
30
|
// src/prompt/system.ts
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
import fs from "fs";
|
|
32
|
+
import path from "path";
|
|
33
|
+
var DEFAULT_PROMPT = `
|
|
33
34
|
You are Pulse Coder, the best coding agent on the planet.
|
|
34
35
|
|
|
35
36
|
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
|
@@ -109,7 +110,7 @@ Use the 'clarify' tool when you genuinely need information from the user to proc
|
|
|
109
110
|
- Explain briefly what would change based on the answer
|
|
110
111
|
|
|
111
112
|
Example usage: Call clarify with a question, optional context, and optional default answer. The tool will pause and wait for the user's response.
|
|
112
|
-
- For substantial work, summarize clearly; follow final
|
|
113
|
+
- For substantial work, summarize clearly; follow final-answer formatting.
|
|
113
114
|
- Skip heavy formatting for simple confirmations.
|
|
114
115
|
- Don't dump large files you've written; reference paths only.
|
|
115
116
|
- No "save/copy this file" - User is on the same machine.
|
|
@@ -123,19 +124,19 @@ Example usage: Call clarify with a question, optional context, and optional defa
|
|
|
123
124
|
## Final answer structure and style guidelines
|
|
124
125
|
|
|
125
126
|
- Plain text; CLI handles styling. Use structure only when it helps scanability.
|
|
126
|
-
- Headers: optional; short Title Case (1-3 words) wrapped in
|
|
127
|
-
- Bullets: use - ; merge related points; keep to one line when possible; 4
|
|
127
|
+
- Headers: optional; short Title Case (1-3 words) wrapped in **...**; no blank line before the first bullet; add only if they truly help.
|
|
128
|
+
- Bullets: use - ; merge related points; keep to one line when possible; 4-6 per list ordered by importance; keep phrasing consistent.
|
|
128
129
|
- Monospace: backticks for commands/paths/env vars/code ids and inline examples; use for literal keyword bullets; never combine with **.
|
|
129
130
|
- Code samples or multi-line snippets should be wrapped in fenced code blocks; include an info string as often as possible.
|
|
130
|
-
- Structure: group related bullets; order sections general
|
|
131
|
-
- Tone: collaborative, concise, factual; present tense, active voice; self
|
|
132
|
-
- Don'ts: no nested bullets/hierarchies; no ANSI codes; don't cram unrelated keywords; keep keyword lists short
|
|
133
|
-
- Adaptation: code explanations
|
|
131
|
+
- Structure: group related bullets; order sections general -> specific -> supporting; for subsections, start with a bolded keyword bullet, then items; match complexity to the task.
|
|
132
|
+
- Tone: collaborative, concise, factual; present tense, active voice; self-contained; no "above/below"; parallel wording.
|
|
133
|
+
- Don'ts: no nested bullets/hierarchies; no ANSI codes; don't cram unrelated keywords; keep keyword lists short-wrap/reformat if long; avoid naming formatting styles in answers.
|
|
134
|
+
- Adaptation: code explanations -> precise, structured with code refs; simple tasks -> lead with outcome; big changes -> logical walkthrough + rationale + next actions; casual one-offs -> plain sentences, no headers/bullets.
|
|
134
135
|
- File References: When referencing files in your response follow the below rules:
|
|
135
136
|
* Use inline code to make file paths clickable.
|
|
136
137
|
* Each reference should have a stand alone path. Even if it's the same file.
|
|
137
|
-
* Accepted: absolute, workspace
|
|
138
|
-
* Optionally include line/column (1
|
|
138
|
+
* Accepted: absolute, workspace-relative, a/ or b/ diff prefixes, or bare filename/suffix.
|
|
139
|
+
* Optionally include line/column (1-based): :line[:column] or #Lline[Ccolumn] (column defaults to 1).
|
|
139
140
|
* Do not use URIs like file://, vscode://, or https://.
|
|
140
141
|
* Do not provide range of lines
|
|
141
142
|
* Examples: src/app.ts, src/app.ts:42, b/server/index.js#L10, C:\repoprojectmain.rs:12:5
|
|
@@ -149,7 +150,24 @@ Here is some useful information about the environment you are running in:
|
|
|
149
150
|
<files>
|
|
150
151
|
|
|
151
152
|
</files>`;
|
|
152
|
-
|
|
153
|
+
var AGENTS_FILE_REGEX = /^agents\.md$/i;
|
|
154
|
+
var loadAgentsPrompt = () => {
|
|
155
|
+
try {
|
|
156
|
+
const cwd = process.cwd();
|
|
157
|
+
const entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
158
|
+
const target = entries.find((entry) => entry.isFile() && AGENTS_FILE_REGEX.test(entry.name));
|
|
159
|
+
if (!target) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const filePath = path.join(cwd, target.name);
|
|
163
|
+
const content = fs.readFileSync(filePath, "utf8").trim();
|
|
164
|
+
return content.length > 0 ? content : null;
|
|
165
|
+
} catch {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var generateSystemPrompt = () => {
|
|
170
|
+
return loadAgentsPrompt() ?? DEFAULT_PROMPT;
|
|
153
171
|
};
|
|
154
172
|
|
|
155
173
|
// src/ai/index.ts
|
|
@@ -624,7 +642,7 @@ var GrepTool = {
|
|
|
624
642
|
}),
|
|
625
643
|
execute: async ({
|
|
626
644
|
pattern,
|
|
627
|
-
path:
|
|
645
|
+
path: path5 = ".",
|
|
628
646
|
glob: glob2,
|
|
629
647
|
type,
|
|
630
648
|
outputMode = "files_with_matches",
|
|
@@ -659,11 +677,11 @@ var GrepTool = {
|
|
|
659
677
|
if (type) {
|
|
660
678
|
args.push("--type", type);
|
|
661
679
|
}
|
|
662
|
-
if (
|
|
663
|
-
if (!existsSync3(
|
|
664
|
-
throw new Error(`Path does not exist: ${
|
|
680
|
+
if (path5 && path5 !== ".") {
|
|
681
|
+
if (!existsSync3(path5)) {
|
|
682
|
+
throw new Error(`Path does not exist: ${path5}`);
|
|
665
683
|
}
|
|
666
|
-
args.push(
|
|
684
|
+
args.push(path5);
|
|
667
685
|
}
|
|
668
686
|
let command = args.map((arg) => {
|
|
669
687
|
if (arg.includes(" ") || arg.includes("$") || arg.includes("*")) {
|
|
@@ -718,8 +736,8 @@ var LsTool = {
|
|
|
718
736
|
inputSchema: z5.object({
|
|
719
737
|
path: z5.string().optional().describe("The path to list files from (defaults to current directory)")
|
|
720
738
|
}),
|
|
721
|
-
execute: async ({ path:
|
|
722
|
-
const files = readdirSync(
|
|
739
|
+
execute: async ({ path: path5 = "." }) => {
|
|
740
|
+
const files = readdirSync(path5);
|
|
723
741
|
return { files };
|
|
724
742
|
}
|
|
725
743
|
};
|
|
@@ -891,8 +909,8 @@ var getFinalToolsMap = (customTools) => {
|
|
|
891
909
|
};
|
|
892
910
|
|
|
893
911
|
// src/plugin/PluginManager.ts
|
|
894
|
-
import { promises as
|
|
895
|
-
import
|
|
912
|
+
import { promises as fs2 } from "fs";
|
|
913
|
+
import path2 from "path";
|
|
896
914
|
import { glob } from "glob";
|
|
897
915
|
import { EventEmitter } from "events";
|
|
898
916
|
|
|
@@ -978,6 +996,7 @@ var PluginManager = class {
|
|
|
978
996
|
enginePlugins = /* @__PURE__ */ new Map();
|
|
979
997
|
userConfigPlugins = [];
|
|
980
998
|
tools = /* @__PURE__ */ new Map();
|
|
999
|
+
runHooks = /* @__PURE__ */ new Map();
|
|
981
1000
|
services = /* @__PURE__ */ new Map();
|
|
982
1001
|
protocols = /* @__PURE__ */ new Map();
|
|
983
1002
|
config = /* @__PURE__ */ new Map();
|
|
@@ -1087,6 +1106,10 @@ var PluginManager = class {
|
|
|
1087
1106
|
});
|
|
1088
1107
|
},
|
|
1089
1108
|
getTool: (name) => this.tools.get(name),
|
|
1109
|
+
registerRunHook: (name, hook) => {
|
|
1110
|
+
this.runHooks.set(name, hook);
|
|
1111
|
+
},
|
|
1112
|
+
getRunHook: (name) => this.runHooks.get(name),
|
|
1090
1113
|
registerProtocol: (name, handler) => {
|
|
1091
1114
|
this.protocols.set(name, handler);
|
|
1092
1115
|
},
|
|
@@ -1165,8 +1188,8 @@ var PluginManager = class {
|
|
|
1165
1188
|
* 加载单个用户配置文件
|
|
1166
1189
|
*/
|
|
1167
1190
|
async loadUserConfigFile(filePath) {
|
|
1168
|
-
const content = await
|
|
1169
|
-
const ext =
|
|
1191
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
1192
|
+
const ext = path2.extname(filePath);
|
|
1170
1193
|
let config;
|
|
1171
1194
|
try {
|
|
1172
1195
|
if (ext === ".json") {
|
|
@@ -1268,6 +1291,12 @@ var PluginManager = class {
|
|
|
1268
1291
|
getTools() {
|
|
1269
1292
|
return Object.fromEntries(this.tools);
|
|
1270
1293
|
}
|
|
1294
|
+
/**
|
|
1295
|
+
* 运行时钩子获取
|
|
1296
|
+
*/
|
|
1297
|
+
getRunHooks() {
|
|
1298
|
+
return Array.from(this.runHooks.values());
|
|
1299
|
+
}
|
|
1271
1300
|
/**
|
|
1272
1301
|
* 服务获取
|
|
1273
1302
|
*/
|
|
@@ -1288,6 +1317,7 @@ var PluginManager = class {
|
|
|
1288
1317
|
enginePlugins: Array.from(this.enginePlugins.keys()),
|
|
1289
1318
|
userConfigPlugins: this.userConfigPlugins.map((c) => c.name || "unnamed"),
|
|
1290
1319
|
tools: Array.from(this.tools.keys()),
|
|
1320
|
+
runHooks: Array.from(this.runHooks.keys()),
|
|
1291
1321
|
services: Array.from(this.services.keys()),
|
|
1292
1322
|
protocols: Array.from(this.protocols.keys())
|
|
1293
1323
|
};
|
|
@@ -1297,19 +1327,19 @@ var PluginManager = class {
|
|
|
1297
1327
|
*/
|
|
1298
1328
|
resolvePath(dir) {
|
|
1299
1329
|
if (dir.startsWith("~/")) {
|
|
1300
|
-
return
|
|
1330
|
+
return path2.join(process.env.HOME || process.env.USERPROFILE || "", dir.slice(2));
|
|
1301
1331
|
}
|
|
1302
|
-
return
|
|
1332
|
+
return path2.resolve(dir);
|
|
1303
1333
|
}
|
|
1304
1334
|
};
|
|
1305
1335
|
|
|
1306
1336
|
// src/built-in/mcp-plugin/index.ts
|
|
1307
1337
|
import { createMCPClient } from "@ai-sdk/mcp";
|
|
1308
1338
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1309
|
-
import * as
|
|
1339
|
+
import * as path3 from "path";
|
|
1310
1340
|
async function loadMCPConfig(cwd) {
|
|
1311
|
-
const newConfigPath =
|
|
1312
|
-
const legacyConfigPath =
|
|
1341
|
+
const newConfigPath = path3.join(cwd, ".pulse-coder", "mcp.json");
|
|
1342
|
+
const legacyConfigPath = path3.join(cwd, ".coder", "mcp.json");
|
|
1313
1343
|
const configPath = existsSync4(newConfigPath) ? newConfigPath : legacyConfigPath;
|
|
1314
1344
|
if (!existsSync4(configPath)) {
|
|
1315
1345
|
return { servers: {} };
|
|
@@ -1538,17 +1568,417 @@ var builtInSkillsPlugin = {
|
|
|
1538
1568
|
}
|
|
1539
1569
|
};
|
|
1540
1570
|
|
|
1571
|
+
// src/built-in/plan-mode-plugin/index.ts
|
|
1572
|
+
var PLANNING_POLICY = {
|
|
1573
|
+
mode: "planning",
|
|
1574
|
+
allowedCategories: ["read", "search", "other"],
|
|
1575
|
+
disallowedCategories: ["write", "execute"],
|
|
1576
|
+
notes: "Planning mode is prompt-constrained only. Disallowed tool attempts are observed and logged, not hard-blocked."
|
|
1577
|
+
};
|
|
1578
|
+
var EXECUTING_POLICY = {
|
|
1579
|
+
mode: "executing",
|
|
1580
|
+
allowedCategories: ["read", "search", "write", "execute", "other"],
|
|
1581
|
+
disallowedCategories: []
|
|
1582
|
+
};
|
|
1583
|
+
var EXECUTE_PATTERNS = [
|
|
1584
|
+
/开始执行/i,
|
|
1585
|
+
/按这个计划做/i,
|
|
1586
|
+
/可以改代码了/i,
|
|
1587
|
+
/直接实现/i,
|
|
1588
|
+
/go\s+ahead/i,
|
|
1589
|
+
/proceed/i,
|
|
1590
|
+
/implement\s+it/i,
|
|
1591
|
+
/start\s+implement/i,
|
|
1592
|
+
/start\s+coding/i
|
|
1593
|
+
];
|
|
1594
|
+
var NEGATIVE_PATTERNS = [
|
|
1595
|
+
/先不(要)?执行/i,
|
|
1596
|
+
/先别执行/i,
|
|
1597
|
+
/不要执行/i,
|
|
1598
|
+
/暂时不要执行/i,
|
|
1599
|
+
/先别改代码/i,
|
|
1600
|
+
/先不要改代码/i,
|
|
1601
|
+
/先不要实现/i,
|
|
1602
|
+
/not\s+now/i,
|
|
1603
|
+
/hold\s+off/i,
|
|
1604
|
+
/do\s+not\s+(start|execute|implement|proceed)/i,
|
|
1605
|
+
/don't\s+(start|execute|implement|proceed)/i
|
|
1606
|
+
];
|
|
1607
|
+
var PLAN_PATTERNS = [/先计划/i, /先分析/i, /先出方案/i, /plan\s+first/i, /analysis\s+first/i];
|
|
1608
|
+
function appendSystemPrompt(base, append) {
|
|
1609
|
+
if (!append.trim()) {
|
|
1610
|
+
return base ?? { append: "" };
|
|
1611
|
+
}
|
|
1612
|
+
if (!base) {
|
|
1613
|
+
return { append };
|
|
1614
|
+
}
|
|
1615
|
+
if (typeof base === "string") {
|
|
1616
|
+
return `${base}
|
|
1617
|
+
|
|
1618
|
+
${append}`;
|
|
1619
|
+
}
|
|
1620
|
+
if (typeof base === "function") {
|
|
1621
|
+
return () => `${base()}
|
|
1622
|
+
|
|
1623
|
+
${append}`;
|
|
1624
|
+
}
|
|
1625
|
+
const currentAppend = base.append.trim();
|
|
1626
|
+
return {
|
|
1627
|
+
append: currentAppend ? `${currentAppend}
|
|
1628
|
+
|
|
1629
|
+
${append}` : append
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
var KNOWN_TOOL_META = {
|
|
1633
|
+
read: {
|
|
1634
|
+
category: "read",
|
|
1635
|
+
risk: "low",
|
|
1636
|
+
description: "Read file contents from the workspace."
|
|
1637
|
+
},
|
|
1638
|
+
ls: {
|
|
1639
|
+
category: "read",
|
|
1640
|
+
risk: "low",
|
|
1641
|
+
description: "List files and directories."
|
|
1642
|
+
},
|
|
1643
|
+
grep: {
|
|
1644
|
+
category: "search",
|
|
1645
|
+
risk: "low",
|
|
1646
|
+
description: "Search text content across files."
|
|
1647
|
+
},
|
|
1648
|
+
tavily: {
|
|
1649
|
+
category: "search",
|
|
1650
|
+
risk: "low",
|
|
1651
|
+
description: "Search web results from external sources."
|
|
1652
|
+
},
|
|
1653
|
+
skill: {
|
|
1654
|
+
category: "search",
|
|
1655
|
+
risk: "low",
|
|
1656
|
+
description: "Load procedural guidance from installed skills."
|
|
1657
|
+
},
|
|
1658
|
+
write: {
|
|
1659
|
+
category: "write",
|
|
1660
|
+
risk: "high",
|
|
1661
|
+
description: "Create or overwrite file content."
|
|
1662
|
+
},
|
|
1663
|
+
edit: {
|
|
1664
|
+
category: "write",
|
|
1665
|
+
risk: "high",
|
|
1666
|
+
description: "Modify existing files using exact replacements."
|
|
1667
|
+
},
|
|
1668
|
+
bash: {
|
|
1669
|
+
category: "execute",
|
|
1670
|
+
risk: "high",
|
|
1671
|
+
description: "Execute shell commands."
|
|
1672
|
+
},
|
|
1673
|
+
clarify: {
|
|
1674
|
+
category: "other",
|
|
1675
|
+
risk: "low",
|
|
1676
|
+
description: "Ask the user a targeted clarification question."
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
var BuiltInPlanModeService = class {
|
|
1680
|
+
constructor(logger, eventEmitter, initialMode = "executing") {
|
|
1681
|
+
this.logger = logger;
|
|
1682
|
+
this.eventEmitter = eventEmitter;
|
|
1683
|
+
this.mode = initialMode;
|
|
1684
|
+
this.emitEvent("mode_entered", { reason: "initialize" });
|
|
1685
|
+
}
|
|
1686
|
+
mode;
|
|
1687
|
+
events = [];
|
|
1688
|
+
getMode() {
|
|
1689
|
+
return this.mode;
|
|
1690
|
+
}
|
|
1691
|
+
setMode(mode, reason = "manual") {
|
|
1692
|
+
if (this.mode === mode) {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
this.mode = mode;
|
|
1696
|
+
this.emitEvent("mode_entered", { reason });
|
|
1697
|
+
}
|
|
1698
|
+
detectIntent(input) {
|
|
1699
|
+
const text = input.trim();
|
|
1700
|
+
if (!text) {
|
|
1701
|
+
return "UNCLEAR";
|
|
1702
|
+
}
|
|
1703
|
+
if (NEGATIVE_PATTERNS.some((pattern) => pattern.test(text))) {
|
|
1704
|
+
return "PLAN_ONLY";
|
|
1705
|
+
}
|
|
1706
|
+
if (EXECUTE_PATTERNS.some((pattern) => pattern.test(text))) {
|
|
1707
|
+
return "EXECUTE_NOW";
|
|
1708
|
+
}
|
|
1709
|
+
if (PLAN_PATTERNS.some((pattern) => pattern.test(text))) {
|
|
1710
|
+
return "PLAN_ONLY";
|
|
1711
|
+
}
|
|
1712
|
+
return "UNCLEAR";
|
|
1713
|
+
}
|
|
1714
|
+
processContextMessages(messages) {
|
|
1715
|
+
const userInput = this.getLatestUserText(messages);
|
|
1716
|
+
const modeBefore = this.mode;
|
|
1717
|
+
if (!userInput) {
|
|
1718
|
+
return {
|
|
1719
|
+
modeBefore,
|
|
1720
|
+
modeAfter: this.mode,
|
|
1721
|
+
switched: false,
|
|
1722
|
+
intent: "UNCLEAR",
|
|
1723
|
+
userInput: ""
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
const intent = this.detectIntent(userInput);
|
|
1727
|
+
if (intent === "EXECUTE_NOW") {
|
|
1728
|
+
this.emitEvent("execution_intent_detected", { intent, userInput });
|
|
1729
|
+
if (this.mode === "planning") {
|
|
1730
|
+
this.mode = "executing";
|
|
1731
|
+
this.emitEvent("mode_switched_by_intent", {
|
|
1732
|
+
from: "planning",
|
|
1733
|
+
to: "executing",
|
|
1734
|
+
userInput
|
|
1735
|
+
});
|
|
1736
|
+
this.emitEvent("mode_entered", {
|
|
1737
|
+
reason: "intent",
|
|
1738
|
+
from: "planning"
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return {
|
|
1743
|
+
modeBefore,
|
|
1744
|
+
modeAfter: this.mode,
|
|
1745
|
+
switched: modeBefore !== this.mode,
|
|
1746
|
+
intent,
|
|
1747
|
+
userInput
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
getModePolicy(mode = this.mode) {
|
|
1751
|
+
return mode === "planning" ? PLANNING_POLICY : EXECUTING_POLICY;
|
|
1752
|
+
}
|
|
1753
|
+
getToolMetadata(toolNames) {
|
|
1754
|
+
return Array.from(new Set(toolNames)).sort().map((name) => this.inferToolMeta(name));
|
|
1755
|
+
}
|
|
1756
|
+
buildPromptAppend(toolNames, transition) {
|
|
1757
|
+
const mode = this.mode;
|
|
1758
|
+
const policy = this.getModePolicy(mode);
|
|
1759
|
+
const toolMeta = this.getToolMetadata(toolNames);
|
|
1760
|
+
const shownTools = toolMeta.slice(0, 40);
|
|
1761
|
+
const omittedCount = toolMeta.length - shownTools.length;
|
|
1762
|
+
const lines = [
|
|
1763
|
+
"## Plan Mode Policy (Built-in Plugin)",
|
|
1764
|
+
`Current mode: ${mode.toUpperCase()}`,
|
|
1765
|
+
`Allowed tool categories: ${policy.allowedCategories.join(", ")}`,
|
|
1766
|
+
`Disallowed tool categories: ${policy.disallowedCategories.join(", ") || "none"}`,
|
|
1767
|
+
policy.notes ? `Policy notes: ${policy.notes}` : ""
|
|
1768
|
+
].filter(Boolean);
|
|
1769
|
+
if (mode === "planning") {
|
|
1770
|
+
lines.push(
|
|
1771
|
+
"Planning objective: prioritize reading, analysis, and plan generation.",
|
|
1772
|
+
"In planning mode, do not intentionally perform write/edit/execute actions.",
|
|
1773
|
+
"If implementation is requested but intent is not explicit, ask for execution authorization.",
|
|
1774
|
+
"Before each tool call in planning mode, self-check category compliance.",
|
|
1775
|
+
"Plan format must include: goals, assumptions, steps, risks, validation approach."
|
|
1776
|
+
);
|
|
1777
|
+
} else {
|
|
1778
|
+
lines.push(
|
|
1779
|
+
"Executing objective: follow the agreed plan before broad exploration.",
|
|
1780
|
+
"Make targeted edits and keep changes scoped.",
|
|
1781
|
+
"Report what changed and how it was validated."
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
if (transition?.switched && transition.modeAfter === "executing") {
|
|
1785
|
+
lines.push(
|
|
1786
|
+
"Mode transition: the latest user message explicitly authorized execution.",
|
|
1787
|
+
"In your next reply, acknowledge switching to EXECUTING mode before implementation details."
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
lines.push("Tool metadata (prompt-level policy reference):");
|
|
1791
|
+
for (const meta of shownTools) {
|
|
1792
|
+
lines.push(`- ${meta.name}: category=${meta.category}, risk=${meta.risk}, ${meta.description}`);
|
|
1793
|
+
}
|
|
1794
|
+
if (omittedCount > 0) {
|
|
1795
|
+
lines.push(`- ... ${omittedCount} additional tool(s) omitted for brevity.`);
|
|
1796
|
+
}
|
|
1797
|
+
return lines.join("\n");
|
|
1798
|
+
}
|
|
1799
|
+
applyHooks(baseHooks) {
|
|
1800
|
+
return {
|
|
1801
|
+
onBeforeToolCall: async (name, input) => {
|
|
1802
|
+
this.observePotentialPolicyViolation(name, input);
|
|
1803
|
+
if (baseHooks?.onBeforeToolCall) {
|
|
1804
|
+
const nextInput = await baseHooks.onBeforeToolCall(name, input);
|
|
1805
|
+
return nextInput ?? input;
|
|
1806
|
+
}
|
|
1807
|
+
return input;
|
|
1808
|
+
},
|
|
1809
|
+
onAfterToolCall: async (name, input, output) => {
|
|
1810
|
+
if (baseHooks?.onAfterToolCall) {
|
|
1811
|
+
const nextOutput = await baseHooks.onAfterToolCall(name, input, output);
|
|
1812
|
+
return nextOutput ?? output;
|
|
1813
|
+
}
|
|
1814
|
+
return output;
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
getEvents(limit = 50) {
|
|
1819
|
+
return this.events.slice(-Math.max(0, limit));
|
|
1820
|
+
}
|
|
1821
|
+
observePotentialPolicyViolation(toolName, input) {
|
|
1822
|
+
if (this.mode !== "planning") {
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const meta = this.inferToolMeta(toolName);
|
|
1826
|
+
const policy = this.getModePolicy("planning");
|
|
1827
|
+
if (!policy.disallowedCategories.includes(meta.category)) {
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
this.emitEvent("disallowed_tool_attempt_in_planning", {
|
|
1831
|
+
toolName,
|
|
1832
|
+
category: meta.category,
|
|
1833
|
+
risk: meta.risk,
|
|
1834
|
+
input
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
inferToolMeta(name) {
|
|
1838
|
+
const knownMeta = KNOWN_TOOL_META[name];
|
|
1839
|
+
if (knownMeta) {
|
|
1840
|
+
return {
|
|
1841
|
+
name,
|
|
1842
|
+
...knownMeta
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
if (name.startsWith("mcp_")) {
|
|
1846
|
+
return {
|
|
1847
|
+
name,
|
|
1848
|
+
category: "execute",
|
|
1849
|
+
risk: "medium",
|
|
1850
|
+
description: "MCP external tool invocation."
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
if (name.endsWith("_agent")) {
|
|
1854
|
+
return {
|
|
1855
|
+
name,
|
|
1856
|
+
category: "execute",
|
|
1857
|
+
risk: "high",
|
|
1858
|
+
description: "Sub-agent task execution tool."
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
if (/read|list|cat/i.test(name)) {
|
|
1862
|
+
return {
|
|
1863
|
+
name,
|
|
1864
|
+
category: "read",
|
|
1865
|
+
risk: "low",
|
|
1866
|
+
description: "Likely a read-only inspection tool inferred by name."
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
if (/search|find|query|grep/i.test(name)) {
|
|
1870
|
+
return {
|
|
1871
|
+
name,
|
|
1872
|
+
category: "search",
|
|
1873
|
+
risk: "low",
|
|
1874
|
+
description: "Likely a search tool inferred by name."
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
if (/write|edit|patch|update|create|delete|remove/i.test(name)) {
|
|
1878
|
+
return {
|
|
1879
|
+
name,
|
|
1880
|
+
category: "write",
|
|
1881
|
+
risk: "high",
|
|
1882
|
+
description: "Likely a file-modifying tool inferred by name."
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
if (/bash|exec|run|shell|deploy|build|test/i.test(name)) {
|
|
1886
|
+
return {
|
|
1887
|
+
name,
|
|
1888
|
+
category: "execute",
|
|
1889
|
+
risk: "high",
|
|
1890
|
+
description: "Likely a command execution tool inferred by name."
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
return {
|
|
1894
|
+
name,
|
|
1895
|
+
category: "other",
|
|
1896
|
+
risk: "low",
|
|
1897
|
+
description: "Tool category could not be inferred with high confidence."
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
getLatestUserText(messages) {
|
|
1901
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
1902
|
+
const message = messages[i];
|
|
1903
|
+
if (message.role !== "user") {
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
return this.messageContentToText(message.content);
|
|
1907
|
+
}
|
|
1908
|
+
return "";
|
|
1909
|
+
}
|
|
1910
|
+
messageContentToText(content) {
|
|
1911
|
+
if (typeof content === "string") {
|
|
1912
|
+
return content;
|
|
1913
|
+
}
|
|
1914
|
+
if (!Array.isArray(content)) {
|
|
1915
|
+
return "";
|
|
1916
|
+
}
|
|
1917
|
+
const textParts = [];
|
|
1918
|
+
for (const part of content) {
|
|
1919
|
+
if (typeof part === "string") {
|
|
1920
|
+
textParts.push(part);
|
|
1921
|
+
continue;
|
|
1922
|
+
}
|
|
1923
|
+
if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
|
|
1924
|
+
textParts.push(part.text);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return textParts.join("\n");
|
|
1928
|
+
}
|
|
1929
|
+
emitEvent(name, payload) {
|
|
1930
|
+
const event = {
|
|
1931
|
+
name,
|
|
1932
|
+
mode: this.mode,
|
|
1933
|
+
timestamp: Date.now(),
|
|
1934
|
+
payload
|
|
1935
|
+
};
|
|
1936
|
+
this.events.push(event);
|
|
1937
|
+
if (this.events.length > 500) {
|
|
1938
|
+
this.events.shift();
|
|
1939
|
+
}
|
|
1940
|
+
this.eventEmitter.emit(name, event);
|
|
1941
|
+
this.eventEmitter.emit("plan_mode_event", event);
|
|
1942
|
+
if (name === "disallowed_tool_attempt_in_planning") {
|
|
1943
|
+
this.logger.warn("[PlanMode] Soft violation detected in planning mode", payload);
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
this.logger.info(`[PlanMode] ${name}`, payload);
|
|
1947
|
+
}
|
|
1948
|
+
};
|
|
1949
|
+
var builtInPlanModePlugin = {
|
|
1950
|
+
name: "pulse-coder-engine/built-in-plan-mode",
|
|
1951
|
+
version: "1.0.0",
|
|
1952
|
+
async initialize(context) {
|
|
1953
|
+
const service = new BuiltInPlanModeService(context.logger, context.events, "executing");
|
|
1954
|
+
context.registerRunHook("plan-mode", ({ context: runContext, tools, systemPrompt, hooks }) => {
|
|
1955
|
+
const transition = service.processContextMessages(runContext.messages);
|
|
1956
|
+
const append = service.buildPromptAppend(Object.keys(tools), transition);
|
|
1957
|
+
const finalSystemPrompt = appendSystemPrompt(systemPrompt, append);
|
|
1958
|
+
return {
|
|
1959
|
+
systemPrompt: finalSystemPrompt,
|
|
1960
|
+
hooks: service.applyHooks(hooks)
|
|
1961
|
+
};
|
|
1962
|
+
});
|
|
1963
|
+
context.registerService("planMode", service);
|
|
1964
|
+
context.registerService("planModeService", service);
|
|
1965
|
+
context.logger.info("[PlanMode] Built-in plan mode plugin initialized", {
|
|
1966
|
+
mode: service.getMode()
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1541
1971
|
// src/built-in/sub-agent-plugin/index.ts
|
|
1542
1972
|
import { z as z11 } from "zod";
|
|
1543
|
-
import { promises as
|
|
1544
|
-
import
|
|
1973
|
+
import { promises as fs3 } from "fs";
|
|
1974
|
+
import path4 from "path";
|
|
1545
1975
|
var ConfigLoader = class {
|
|
1546
1976
|
async getAgentFilesInfo(configDirs) {
|
|
1547
1977
|
const fileInfos = [];
|
|
1548
1978
|
for (let configDir of configDirs) {
|
|
1549
1979
|
try {
|
|
1550
|
-
await
|
|
1551
|
-
const files = await
|
|
1980
|
+
await fs3.access(configDir);
|
|
1981
|
+
const files = await fs3.readdir(configDir);
|
|
1552
1982
|
fileInfos.push({ files, configDir });
|
|
1553
1983
|
} catch {
|
|
1554
1984
|
continue;
|
|
@@ -1565,7 +1995,7 @@ var ConfigLoader = class {
|
|
|
1565
1995
|
const files = fileInfo.files;
|
|
1566
1996
|
for (const file of files) {
|
|
1567
1997
|
if (file.endsWith(".md")) {
|
|
1568
|
-
const config = await this.parseConfig(
|
|
1998
|
+
const config = await this.parseConfig(path4.join(fileInfo.configDir, file));
|
|
1569
1999
|
if (config) configs.push(config);
|
|
1570
2000
|
}
|
|
1571
2001
|
}
|
|
@@ -1577,7 +2007,7 @@ var ConfigLoader = class {
|
|
|
1577
2007
|
}
|
|
1578
2008
|
async parseConfig(filePath) {
|
|
1579
2009
|
try {
|
|
1580
|
-
const content = await
|
|
2010
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
1581
2011
|
const lines = content.split("\n");
|
|
1582
2012
|
let name = "";
|
|
1583
2013
|
let description = "";
|
|
@@ -1606,7 +2036,7 @@ var ConfigLoader = class {
|
|
|
1606
2036
|
}
|
|
1607
2037
|
}
|
|
1608
2038
|
if (!name) {
|
|
1609
|
-
name =
|
|
2039
|
+
name = path4.basename(filePath, ".md");
|
|
1610
2040
|
}
|
|
1611
2041
|
return {
|
|
1612
2042
|
name: name.trim(),
|
|
@@ -1689,6 +2119,7 @@ var SubAgentPlugin = class {
|
|
|
1689
2119
|
var builtInPlugins = [
|
|
1690
2120
|
builtInMCPPlugin,
|
|
1691
2121
|
builtInSkillsPlugin,
|
|
2122
|
+
builtInPlanModePlugin,
|
|
1692
2123
|
new SubAgentPlugin()
|
|
1693
2124
|
];
|
|
1694
2125
|
|
|
@@ -1742,18 +2173,47 @@ var Engine = class {
|
|
|
1742
2173
|
// 默认启用扫描
|
|
1743
2174
|
};
|
|
1744
2175
|
}
|
|
2176
|
+
async applyRunHooks(context, systemPrompt, hooks) {
|
|
2177
|
+
let nextSystemPrompt = systemPrompt;
|
|
2178
|
+
let nextHooks = hooks;
|
|
2179
|
+
for (const runHook of this.pluginManager.getRunHooks()) {
|
|
2180
|
+
const result = await runHook({
|
|
2181
|
+
context,
|
|
2182
|
+
messages: context.messages,
|
|
2183
|
+
tools: this.tools,
|
|
2184
|
+
systemPrompt: nextSystemPrompt,
|
|
2185
|
+
hooks: nextHooks
|
|
2186
|
+
});
|
|
2187
|
+
if (!result) {
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2190
|
+
if ("systemPrompt" in result) {
|
|
2191
|
+
nextSystemPrompt = result.systemPrompt;
|
|
2192
|
+
}
|
|
2193
|
+
if ("hooks" in result) {
|
|
2194
|
+
nextHooks = result.hooks;
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
return {
|
|
2198
|
+
systemPrompt: nextSystemPrompt,
|
|
2199
|
+
hooks: nextHooks
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
1745
2202
|
/**
|
|
1746
2203
|
* 运行AI循环
|
|
1747
2204
|
*/
|
|
1748
2205
|
async run(context, options) {
|
|
2206
|
+
const baseSystemPrompt = options?.systemPrompt ?? this.options.systemPrompt;
|
|
2207
|
+
const baseHooks = options?.hooks ?? this.options.hooks;
|
|
2208
|
+
const { systemPrompt, hooks } = await this.applyRunHooks(context, baseSystemPrompt, baseHooks);
|
|
1749
2209
|
return loop(context, {
|
|
1750
2210
|
...options,
|
|
1751
2211
|
tools: this.tools,
|
|
1752
2212
|
// Engine 级别选项作为默认值;调用方通过 options 传入可在单次调用中覆盖
|
|
1753
2213
|
provider: options?.provider ?? this.options.llmProvider,
|
|
1754
2214
|
model: options?.model ?? this.options.model,
|
|
1755
|
-
systemPrompt
|
|
1756
|
-
hooks
|
|
2215
|
+
systemPrompt,
|
|
2216
|
+
hooks,
|
|
1757
2217
|
onToolCall: (toolCall) => {
|
|
1758
2218
|
options?.onToolCall?.(toolCall);
|
|
1759
2219
|
},
|
|
@@ -1793,6 +2253,7 @@ var Engine = class {
|
|
|
1793
2253
|
};
|
|
1794
2254
|
export {
|
|
1795
2255
|
BashTool,
|
|
2256
|
+
BuiltInPlanModeService,
|
|
1796
2257
|
BuiltInSkillRegistry,
|
|
1797
2258
|
BuiltinTools,
|
|
1798
2259
|
BuiltinToolsMap,
|
|
@@ -1807,6 +2268,7 @@ export {
|
|
|
1807
2268
|
TavilyTool,
|
|
1808
2269
|
WriteTool,
|
|
1809
2270
|
builtInMCPPlugin,
|
|
2271
|
+
builtInPlanModePlugin,
|
|
1810
2272
|
builtInPlugins,
|
|
1811
2273
|
builtInSkillsPlugin,
|
|
1812
2274
|
getFinalToolsMap,
|