machine-bridge-mcp 0.9.0 → 0.11.0
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/CHANGELOG.md +50 -0
- package/README.md +76 -23
- package/SECURITY.md +16 -2
- package/browser-extension/manifest.json +33 -0
- package/browser-extension/page-automation.js +246 -0
- package/browser-extension/pairing.js +19 -0
- package/browser-extension/service-worker.js +406 -0
- package/docs/AGENT_CONTEXT.md +134 -124
- package/docs/ARCHITECTURE.md +29 -9
- package/docs/CLIENTS.md +8 -0
- package/docs/ENGINEERING.md +10 -2
- package/docs/LOCAL_AUTOMATION.md +111 -0
- package/docs/LOGGING.md +10 -1
- package/docs/OPERATIONS.md +49 -0
- package/docs/PRIVACY.md +10 -1
- package/docs/RELEASING.md +2 -2
- package/docs/TESTING.md +7 -5
- package/package.json +13 -7
- package/scripts/sync-worker-version.mjs +27 -12
- package/src/local/agent-context.mjs +248 -15
- package/src/local/app-automation.mjs +400 -0
- package/src/local/browser-bridge.mjs +757 -0
- package/src/local/cli.mjs +156 -17
- package/src/local/default-instructions.mjs +337 -0
- package/src/local/runtime.mjs +114 -4
- package/src/local/state.mjs +8 -4
- package/src/local/stdio.mjs +12 -7
- package/src/shared/server-metadata.json +4 -1
- package/src/shared/tool-catalog.json +854 -1
- package/src/worker/index.ts +24 -4
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
3
|
import { lstat, open, opendir, realpath, stat } from "node:fs/promises";
|
|
3
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { createBuiltinInstruction, discoverAutomaticProjectInstruction } from "./default-instructions.mjs";
|
|
4
6
|
|
|
5
7
|
const CONFIG_RELATIVE_PATH = join(".machine-bridge", "agent.json");
|
|
6
8
|
const GLOBAL_CONFIG_RELATIVE_PATH = join(".config", "machine-bridge-mcp", "agent.json");
|
|
@@ -21,7 +23,8 @@ const MAX_COMMANDS = 128;
|
|
|
21
23
|
const MAX_COMMAND_ARGV = 128;
|
|
22
24
|
const MAX_COMMAND_ARGUMENT_BYTES = 256 * 1024;
|
|
23
25
|
const COMMAND_NAME_PATTERN = /^[a-z][a-z0-9._-]{0,63}$/;
|
|
24
|
-
const
|
|
26
|
+
const TOKEN_STOP_WORDS = new Set(["the", "and", "for", "with", "from", "this", "that", "use", "using", "into", "of", "to", "a", "an", "in", "on", "is", "are", "or", "及", "和", "的", "了", "在", "用", "使用", "进行", "根据"]);
|
|
27
|
+
const CONFIG_KEYS = new Set(["version", "builtin_instructions", "automatic_project_context", "model_instructions_file", "instruction_files", "instruction_max_bytes", "skill_roots", "commands"]);
|
|
25
28
|
const COMMAND_KEYS = new Set(["description", "argv", "cwd", "timeout_seconds", "allow_extra_args"]);
|
|
26
29
|
|
|
27
30
|
export class AgentContextManager {
|
|
@@ -44,8 +47,16 @@ export class AgentContextManager {
|
|
|
44
47
|
const result = {
|
|
45
48
|
target: this.displayPath(state.target),
|
|
46
49
|
scope_root: this.displayPath(state.scopeRoot),
|
|
47
|
-
precedence: "global guidance
|
|
50
|
+
precedence: "built-in defaults, automatic project facts, user-global guidance, then scope root to target directory; explicit files loaded later have higher precedence",
|
|
48
51
|
config_files: state.configFiles.map((file) => this.displayPath(file)),
|
|
52
|
+
builtin_instructions: publicVirtualInstruction(state.builtinInstructions, includeContent),
|
|
53
|
+
automatic_project_context: publicVirtualInstruction(state.automaticProjectContext, includeContent),
|
|
54
|
+
model_instructions_file: state.modelInstructions ? {
|
|
55
|
+
path: this.displayPath(state.modelInstructions.path),
|
|
56
|
+
bytes: state.modelInstructions.bytes,
|
|
57
|
+
sha256: state.modelInstructions.sha256,
|
|
58
|
+
...(includeContent ? { content: state.modelInstructions.content } : {}),
|
|
59
|
+
} : null,
|
|
49
60
|
instruction_files: state.instructions.map((item) => ({
|
|
50
61
|
scope: item.scope,
|
|
51
62
|
path: this.displayPath(item.path),
|
|
@@ -60,15 +71,95 @@ export class AgentContextManager {
|
|
|
60
71
|
instructions_truncated: state.instructionsTruncated,
|
|
61
72
|
commands: publicCommands(state.commands, this.displayPath),
|
|
62
73
|
guidance: [
|
|
74
|
+
"Apply built-in instructions and automatic project context as lower-precedence defaults; explicit global/project instruction files loaded later take precedence.",
|
|
63
75
|
"Treat instruction_files as authoritative workspace guidance in the returned precedence order.",
|
|
64
76
|
"Load a relevant skill with load_local_skill before following its workflow; SKILL.md is an instruction bundle, not executable code by itself.",
|
|
65
77
|
"Prefer run_local_command for registered repeatable commands. Use run_process or exec_command only when no registered command fits and policy permits it.",
|
|
66
78
|
],
|
|
67
79
|
};
|
|
68
|
-
if (includeContent) result.effective_instructions = renderEffectiveInstructions(state
|
|
80
|
+
if (includeContent) result.effective_instructions = renderEffectiveInstructions(effectiveInstructionItems(state), this.displayPath);
|
|
69
81
|
return result;
|
|
70
82
|
}
|
|
71
83
|
|
|
84
|
+
|
|
85
|
+
async sessionBootstrap(args = {}, context = {}) {
|
|
86
|
+
const state = await this.discoverState(args.path || ".", context);
|
|
87
|
+
const fingerprint = capabilityFingerprint(state, []);
|
|
88
|
+
return {
|
|
89
|
+
target: this.displayPath(state.target),
|
|
90
|
+
instructions: renderEffectiveInstructions(effectiveInstructionItems(state), this.displayPath),
|
|
91
|
+
builtin_instructions: publicVirtualInstruction(state.builtinInstructions, false),
|
|
92
|
+
automatic_project_context: publicVirtualInstruction(state.automaticProjectContext, false),
|
|
93
|
+
model_instructions_file: state.modelInstructions ? this.displayPath(state.modelInstructions.path) : null,
|
|
94
|
+
capability_refresh: {
|
|
95
|
+
strategy: "resolve_task_capabilities-rescans-on-every-call",
|
|
96
|
+
instruction_and_command_fingerprint: fingerprint,
|
|
97
|
+
skills_scanned: false,
|
|
98
|
+
generated_at: new Date().toISOString(),
|
|
99
|
+
},
|
|
100
|
+
guidance: [
|
|
101
|
+
"Built-in working agreements and bounded automatic project facts are present by default unless disabled in the user-global agent config.",
|
|
102
|
+
"Call resolve_task_capabilities with the current user task before substantive local work or at the start of a reused-host conversation.",
|
|
103
|
+
"The resolver returns the effective instructions again and rescans skill and command metadata on every call; the runtime supplements application and browser capability metadata.",
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async resolveTaskCapabilities(args = {}, context = {}) {
|
|
109
|
+
const task = requiredString(args.task, "task");
|
|
110
|
+
if (task.length > 20_000) throw new Error("task exceeds 20000 characters");
|
|
111
|
+
const state = await this.discoverState(args.path || ".", context);
|
|
112
|
+
const discovered = await this.discoverSkills(state, { maxResults: MAX_SKILL_RESULTS }, context);
|
|
113
|
+
const maxSkills = clampInt(args.max_skills, 10, 1, 50);
|
|
114
|
+
const skillMatches = discovered.skills
|
|
115
|
+
.map((skill) => ({ skill, score: relevanceScore(task, `${skill.name} ${skill.description}`) }))
|
|
116
|
+
.filter((item) => item.score > 0)
|
|
117
|
+
.sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name))
|
|
118
|
+
.slice(0, maxSkills);
|
|
119
|
+
const commandMatches = [...state.commands.values()]
|
|
120
|
+
.map((command) => ({ command, score: relevanceScore(task, `${command.name} ${command.description} ${command.argv.join(" ")}`) }))
|
|
121
|
+
.filter((item) => item.score > 0)
|
|
122
|
+
.sort((a, b) => b.score - a.score || a.command.name.localeCompare(b.command.name))
|
|
123
|
+
.slice(0, 20);
|
|
124
|
+
const selected = skillMatches[0]?.score >= 3 ? skillMatches[0].skill : null;
|
|
125
|
+
let selectedSkill = null;
|
|
126
|
+
if (selected && args.include_selected_skill !== false) {
|
|
127
|
+
const content = await readRegularUtf8(selected.entrypoint, MAX_SKILL_ENTRY_BYTES, "skill entrypoint");
|
|
128
|
+
selectedSkill = { ...publicSkill(selected, this.displayPath), instructions: content.text };
|
|
129
|
+
}
|
|
130
|
+
const recommendedTools = recommendTools(task, commandMatches.length > 0, skillMatches.length > 0);
|
|
131
|
+
const refresh = capabilityFingerprint(state, discovered.skills);
|
|
132
|
+
return {
|
|
133
|
+
task,
|
|
134
|
+
target: this.displayPath(state.target),
|
|
135
|
+
effective_instructions: renderEffectiveInstructions(effectiveInstructionItems(state), this.displayPath),
|
|
136
|
+
builtin_instructions: publicVirtualInstruction(state.builtinInstructions, false),
|
|
137
|
+
automatic_project_context: publicVirtualInstruction(state.automaticProjectContext, false),
|
|
138
|
+
model_instructions_file: state.modelInstructions ? this.displayPath(state.modelInstructions.path) : null,
|
|
139
|
+
instruction_files: state.instructions.map((item) => ({ path: this.displayPath(item.path), scope: item.scope, bytes: item.bytes, sha256: item.sha256, precedence: item.precedence })),
|
|
140
|
+
instructions_truncated: state.instructionsTruncated,
|
|
141
|
+
refresh: { strategy: "rescan-on-every-call", fingerprint: refresh, generated_at: new Date().toISOString() },
|
|
142
|
+
selected_skill: selectedSkill,
|
|
143
|
+
skill_matches: skillMatches.map(({ skill, score }) => ({ ...publicSkill(skill, this.displayPath), score })),
|
|
144
|
+
command_matches: commandMatches.map(({ command, score }) => ({ ...publicCommands(new Map([[command.name, command]]), this.displayPath)[0], score })),
|
|
145
|
+
recommended_tools: recommendedTools,
|
|
146
|
+
host_semantics: "Machine Bridge can discover, rank, and load capabilities automatically. The MCP host remains responsible for deciding whether to call the recommended tools.",
|
|
147
|
+
warnings: publicSkillWarnings(discovered.warnings, this.displayPath),
|
|
148
|
+
truncated: discovered.truncated,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async collectModelInstructions(state, context) {
|
|
153
|
+
this.throwIfCancelled(context);
|
|
154
|
+
const path = state.modelInstructionsFile;
|
|
155
|
+
const info = await lstat(path).catch((error) => error?.code === "ENOENT" ? null : Promise.reject(error));
|
|
156
|
+
if (!info) throw new Error(`model_instructions_file does not exist: ${path}`);
|
|
157
|
+
if (info.isSymbolicLink() || !info.isFile()) throw new Error(`model_instructions_file must be a regular non-symbolic-link file: ${path}`);
|
|
158
|
+
const content = await readRegularUtf8(path, MAX_INSTRUCTION_FILE_BYTES, "model instructions file");
|
|
159
|
+
if (!content.text.trim()) throw new Error(`model_instructions_file is empty: ${path}`);
|
|
160
|
+
state.modelInstructions = { scope: "model", path, bytes: content.bytes, sha256: sha256(content.text), content: content.text, precedence: 0 };
|
|
161
|
+
}
|
|
162
|
+
|
|
72
163
|
async listLocalSkills(args = {}, context = {}) {
|
|
73
164
|
const state = await this.discoverState(args.path || ".", context);
|
|
74
165
|
const result = await this.discoverSkills(state, {
|
|
@@ -150,31 +241,69 @@ export class AgentContextManager {
|
|
|
150
241
|
instructionMaxBytes: DEFAULT_INSTRUCTION_MAX_BYTES,
|
|
151
242
|
skillRoots: defaultSkillRoots(directories, this.home, this.policy.unrestrictedPaths === true),
|
|
152
243
|
commands: new Map(),
|
|
244
|
+
builtinInstructionsEnabled: true,
|
|
245
|
+
automaticProjectContextEnabled: true,
|
|
246
|
+
builtinInstructions: null,
|
|
247
|
+
automaticProjectContext: null,
|
|
248
|
+
modelInstructionsFile: "",
|
|
249
|
+
modelInstructions: null,
|
|
153
250
|
configFiles: [],
|
|
154
251
|
instructions: [],
|
|
155
252
|
instructionBytes: 0,
|
|
156
253
|
instructionsTruncated: false,
|
|
157
254
|
};
|
|
158
255
|
|
|
159
|
-
if (this.
|
|
256
|
+
if (this.home) {
|
|
160
257
|
const globalConfig = join(this.home, GLOBAL_CONFIG_RELATIVE_PATH);
|
|
161
258
|
const config = await readOptionalConfig(globalConfig, this.home, false);
|
|
162
|
-
if (config)
|
|
163
|
-
|
|
259
|
+
if (config) {
|
|
260
|
+
if (this.policy.unrestrictedPaths === true) this.applyConfig(state, config, globalConfig, this.home, { global: true });
|
|
261
|
+
else {
|
|
262
|
+
state.configFiles.push(globalConfig);
|
|
263
|
+
if (config.builtinInstructions !== null) state.builtinInstructionsEnabled = config.builtinInstructions;
|
|
264
|
+
if (config.automaticProjectContext !== null) state.automaticProjectContextEnabled = config.automaticProjectContext;
|
|
265
|
+
if (config.modelInstructionsFile) state.modelInstructionsFile = resolveConfiguredPath(config.modelInstructionsFile, this.home, this.home, this.workspace, true);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
state.builtinInstructions = createBuiltinInstruction(state.builtinInstructionsEnabled);
|
|
271
|
+
state.automaticProjectContext = await discoverAutomaticProjectInstruction({
|
|
272
|
+
scopeRoot,
|
|
273
|
+
targetDir,
|
|
274
|
+
enabled: state.automaticProjectContextEnabled,
|
|
275
|
+
throwIfCancelled: () => this.throwIfCancelled(context),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (this.home) {
|
|
279
|
+
if (state.modelInstructionsFile) await this.collectModelInstructions(state, context);
|
|
280
|
+
if (this.policy.unrestrictedPaths === true && this.codexHome) await this.collectDirectoryInstruction(state, this.codexHome, context, "global");
|
|
164
281
|
}
|
|
165
282
|
|
|
166
283
|
for (const directory of directories) {
|
|
167
284
|
this.throwIfCancelled(context);
|
|
168
285
|
const configPath = join(directory, CONFIG_RELATIVE_PATH);
|
|
169
286
|
const config = await readOptionalConfig(configPath, directory, true);
|
|
170
|
-
if (config) this.applyConfig(state, config, configPath, directory);
|
|
287
|
+
if (config) this.applyConfig(state, config, configPath, directory, { global: false });
|
|
171
288
|
await this.collectDirectoryInstruction(state, directory, context, "project");
|
|
172
289
|
}
|
|
173
290
|
return state;
|
|
174
291
|
}
|
|
175
292
|
|
|
176
|
-
applyConfig(state, config, configPath, baseDir) {
|
|
293
|
+
applyConfig(state, config, configPath, baseDir, { global = false } = {}) {
|
|
177
294
|
state.configFiles.push(configPath);
|
|
295
|
+
if (config.builtinInstructions !== null) {
|
|
296
|
+
if (!global) throw new Error(`builtin_instructions is only allowed in the global agent config: ${configPath}`);
|
|
297
|
+
state.builtinInstructionsEnabled = config.builtinInstructions;
|
|
298
|
+
}
|
|
299
|
+
if (config.automaticProjectContext !== null) {
|
|
300
|
+
if (!global) throw new Error(`automatic_project_context is only allowed in the global agent config: ${configPath}`);
|
|
301
|
+
state.automaticProjectContextEnabled = config.automaticProjectContext;
|
|
302
|
+
}
|
|
303
|
+
if (config.modelInstructionsFile) {
|
|
304
|
+
if (!global) throw new Error(`model_instructions_file is only allowed in the global agent config: ${configPath}`);
|
|
305
|
+
state.modelInstructionsFile = resolveConfiguredPath(config.modelInstructionsFile, baseDir, this.home, this.workspace, true);
|
|
306
|
+
}
|
|
178
307
|
if (config.instructionFiles) state.instructionFiles = [...config.instructionFiles];
|
|
179
308
|
if (config.instructionMaxBytes !== null) {
|
|
180
309
|
state.instructionMaxBytes = config.instructionMaxBytes;
|
|
@@ -385,7 +514,26 @@ function normalizeConfig(value, configPath) {
|
|
|
385
514
|
if (!isPlainRecord(value)) throw new Error(`agent config must be a JSON object: ${configPath}`);
|
|
386
515
|
for (const key of Object.keys(value)) if (!CONFIG_KEYS.has(key)) throw new Error(`unknown agent config field '${key}': ${configPath}`);
|
|
387
516
|
if (value.version !== 1) throw new Error(`agent config version must be 1: ${configPath}`);
|
|
388
|
-
const result = {
|
|
517
|
+
const result = {
|
|
518
|
+
builtinInstructions: null,
|
|
519
|
+
automaticProjectContext: null,
|
|
520
|
+
modelInstructionsFile: null,
|
|
521
|
+
instructionFiles: null,
|
|
522
|
+
instructionMaxBytes: null,
|
|
523
|
+
skillRoots: null,
|
|
524
|
+
commands: new Map(),
|
|
525
|
+
};
|
|
526
|
+
if (value.builtin_instructions !== undefined) {
|
|
527
|
+
if (typeof value.builtin_instructions !== "boolean") throw new Error(`builtin_instructions must be boolean: ${configPath}`);
|
|
528
|
+
result.builtinInstructions = value.builtin_instructions;
|
|
529
|
+
}
|
|
530
|
+
if (value.automatic_project_context !== undefined) {
|
|
531
|
+
if (typeof value.automatic_project_context !== "boolean") throw new Error(`automatic_project_context must be boolean: ${configPath}`);
|
|
532
|
+
result.automaticProjectContext = value.automatic_project_context;
|
|
533
|
+
}
|
|
534
|
+
if (value.model_instructions_file !== undefined) {
|
|
535
|
+
result.modelInstructionsFile = requiredString(value.model_instructions_file, "model_instructions_file");
|
|
536
|
+
}
|
|
389
537
|
if (value.instruction_files !== undefined) {
|
|
390
538
|
result.instructionFiles = validateInstructionFiles(value.instruction_files, configPath);
|
|
391
539
|
}
|
|
@@ -556,6 +704,67 @@ async function listSkillFiles(root, maxFiles, context, throwIfCancelled) {
|
|
|
556
704
|
return { files, truncated };
|
|
557
705
|
}
|
|
558
706
|
|
|
707
|
+
function capabilityFingerprint(state, skills) {
|
|
708
|
+
return sha256(JSON.stringify({
|
|
709
|
+
configs: state.configFiles,
|
|
710
|
+
instructions: [
|
|
711
|
+
state.builtinInstructions?.sha256 || "",
|
|
712
|
+
state.automaticProjectContext?.sha256 || "",
|
|
713
|
+
state.modelInstructions?.sha256 || "",
|
|
714
|
+
...state.instructions.map((item) => item.sha256),
|
|
715
|
+
],
|
|
716
|
+
skills: skills.map((skill) => [skill.id, skill.sha256]),
|
|
717
|
+
commands: [...state.commands.values()].map((command) => [command.name, command.argv]),
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function relevanceScore(task, candidate) {
|
|
722
|
+
const taskTokens = tokenize(task);
|
|
723
|
+
const candidateTokens = tokenize(candidate);
|
|
724
|
+
if (!taskTokens.size || !candidateTokens.size) return 0;
|
|
725
|
+
let score = 0;
|
|
726
|
+
for (const token of taskTokens) {
|
|
727
|
+
if (candidateTokens.has(token)) score += token.length >= 6 ? 2 : 1;
|
|
728
|
+
}
|
|
729
|
+
const taskLower = task.toLowerCase();
|
|
730
|
+
const candidateLower = candidate.toLowerCase();
|
|
731
|
+
if (candidateLower.includes(taskLower) || taskLower.includes(candidateLower)) score += 4;
|
|
732
|
+
return score;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function tokenize(value) {
|
|
736
|
+
const text = String(value || "").toLowerCase();
|
|
737
|
+
const tokens = new Set();
|
|
738
|
+
for (const raw of text.match(/[a-z0-9_][a-z0-9_.-]{1,}/g) || []) {
|
|
739
|
+
const token = raw.replace(/^[.-]+|[.-]+$/g, "");
|
|
740
|
+
if (token.length >= 2 && !TOKEN_STOP_WORDS.has(token)) tokens.add(token);
|
|
741
|
+
}
|
|
742
|
+
for (const sequence of text.match(/[\p{Script=Han}]{1,}/gu) || []) {
|
|
743
|
+
if (!TOKEN_STOP_WORDS.has(sequence)) tokens.add(sequence);
|
|
744
|
+
const minimumSize = sequence.length === 1 ? 1 : 2;
|
|
745
|
+
for (let size = minimumSize; size <= Math.min(3, sequence.length); size += 1) {
|
|
746
|
+
for (let index = 0; index + size <= sequence.length; index += 1) {
|
|
747
|
+
const token = sequence.slice(index, index + size);
|
|
748
|
+
if (!TOKEN_STOP_WORDS.has(token)) tokens.add(token);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return tokens;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function recommendTools(task, hasCommands, hasSkills) {
|
|
756
|
+
const lower = task.toLowerCase();
|
|
757
|
+
const tools = ["agent_context"];
|
|
758
|
+
if (hasSkills) tools.push("load_local_skill");
|
|
759
|
+
if (hasCommands) tools.push("run_local_command");
|
|
760
|
+
if (/browser|chrome|edge|brave|网页|浏览器|表单|网站/.test(lower)) tools.push("browser_status", "browser_list_tabs", "browser_inspect_page", "browser_action", "browser_fill_form");
|
|
761
|
+
if (/app|application|gui|window|应用|软件|窗口|界面/.test(lower)) tools.push("list_local_applications", "inspect_local_application", "operate_local_application");
|
|
762
|
+
if (/git|commit|branch|diff|仓库|提交|分支/.test(lower)) tools.push("git_status", "git_diff");
|
|
763
|
+
if (/test|build|lint|command|terminal|测试|构建|命令|终端/.test(lower)) tools.push(hasCommands ? "run_local_command" : "run_process");
|
|
764
|
+
if (/file|code|source|edit|write|文件|代码|源码|修改|写入/.test(lower)) tools.push("read_file", "search_text", "edit_file", "apply_patch");
|
|
765
|
+
return [...new Set(tools)];
|
|
766
|
+
}
|
|
767
|
+
|
|
559
768
|
function publicSkill(skill, displayPath) {
|
|
560
769
|
return {
|
|
561
770
|
id: skill.id,
|
|
@@ -609,12 +818,36 @@ function publicCommands(commands, displayPath) {
|
|
|
609
818
|
}));
|
|
610
819
|
}
|
|
611
820
|
|
|
821
|
+
function effectiveInstructionItems(state) {
|
|
822
|
+
return [
|
|
823
|
+
...(state.builtinInstructions ? [state.builtinInstructions] : []),
|
|
824
|
+
...(state.automaticProjectContext ? [state.automaticProjectContext] : []),
|
|
825
|
+
...(state.modelInstructions ? [state.modelInstructions] : []),
|
|
826
|
+
...state.instructions,
|
|
827
|
+
];
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function publicVirtualInstruction(item, includeContent) {
|
|
831
|
+
if (!item) return null;
|
|
832
|
+
return {
|
|
833
|
+
source: item.source,
|
|
834
|
+
scope: item.scope,
|
|
835
|
+
bytes: item.bytes,
|
|
836
|
+
sha256: item.sha256,
|
|
837
|
+
precedence: item.precedence,
|
|
838
|
+
...(includeContent ? { content: item.content } : {}),
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
612
842
|
function renderEffectiveInstructions(instructions, displayPath) {
|
|
613
|
-
return instructions.map((item) =>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
843
|
+
return instructions.map((item) => {
|
|
844
|
+
const source = item.source || displayPath(item.path);
|
|
845
|
+
return [
|
|
846
|
+
`--- BEGIN ${source} (precedence ${item.precedence}) ---`,
|
|
847
|
+
item.content,
|
|
848
|
+
`--- END ${source} ---`,
|
|
849
|
+
].join("\n");
|
|
850
|
+
}).join("\n\n");
|
|
618
851
|
}
|
|
619
852
|
|
|
620
853
|
async function readOptionalRegularUtf8(filePath, maxBytes, label) {
|
|
@@ -626,7 +859,7 @@ async function readOptionalRegularUtf8(filePath, maxBytes, label) {
|
|
|
626
859
|
}
|
|
627
860
|
|
|
628
861
|
async function readRegularUtf8(filePath, maxBytes, label) {
|
|
629
|
-
const handle = await open(filePath,
|
|
862
|
+
const handle = await open(filePath, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW || 0));
|
|
630
863
|
try {
|
|
631
864
|
const info = await handle.stat();
|
|
632
865
|
if (!info.isFile()) throw new Error(`${label} is not a regular file: ${filePath}`);
|