@cjhyy/code-shell-core 0.5.0-rc.0 → 0.5.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-definition-registry.d.ts +29 -0
- package/dist/agent/agent-definition-registry.js +74 -0
- package/dist/agent/agent-definition.d.ts +37 -0
- package/dist/agent/agent-definition.js +59 -0
- package/dist/arena/arena.js +11 -12
- package/dist/arena/context/context-tools.js +2 -4
- package/dist/arena/context/within-root.d.ts +7 -0
- package/dist/arena/context/within-root.js +15 -0
- package/dist/arena/iterate/phases/argue.js +8 -2
- package/dist/arena/iterate/phases/revise.js +1 -1
- package/dist/arena/iterate/phases/tournament.js +3 -3
- package/dist/arena/phases/adjudication.js +1 -4
- package/dist/arena/phases/build-consensus.js +1 -4
- package/dist/arena/phases/cross-review.js +2 -8
- package/dist/arena/phases/debate-rounds.js +1 -4
- package/dist/arena/phases/participant-research.js +1 -4
- package/dist/arena/phases/planning-detail-expansion.js +8 -10
- package/dist/arena/planner.js +0 -1
- package/dist/arena/providers/docs.d.ts +1 -0
- package/dist/arena/providers/docs.js +5 -2
- package/dist/arena/providers/repo.d.ts +1 -0
- package/dist/arena/providers/repo.js +5 -2
- package/dist/arena/strategies/utils.js +36 -3
- package/dist/arena/transitions.d.ts +7 -2
- package/dist/arena/transitions.js +9 -4
- package/dist/arena/types.d.ts +8 -2
- package/dist/automation/cron-expr.d.ts +31 -0
- package/dist/automation/cron-expr.js +151 -0
- package/dist/automation/index.d.ts +41 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/runner.d.ts +67 -0
- package/dist/automation/runner.js +56 -0
- package/dist/automation/scheduler.d.ts +156 -0
- package/dist/automation/scheduler.js +546 -0
- package/dist/automation/store.d.ts +39 -0
- package/dist/automation/store.js +119 -0
- package/dist/automation/write-policy.d.ts +34 -0
- package/dist/automation/write-policy.js +113 -0
- package/dist/automation/write-run.d.ts +51 -0
- package/dist/automation/write-run.js +38 -0
- package/dist/capability-control/index.d.ts +5 -0
- package/dist/capability-control/index.js +3 -0
- package/dist/capability-control/overlay.d.ts +49 -0
- package/dist/capability-control/overlay.js +89 -0
- package/dist/capability-control/project.d.ts +65 -0
- package/dist/capability-control/project.js +147 -0
- package/dist/capability-control/service.d.ts +70 -0
- package/dist/capability-control/service.js +147 -0
- package/dist/capability-control/types.d.ts +62 -0
- package/dist/capability-control/types.js +17 -0
- package/dist/cli/agent-server-stdio.d.ts +12 -1
- package/dist/cli/agent-server-stdio.js +118 -18
- package/dist/cli/agent-server-tcp.d.ts +20 -0
- package/dist/cli/agent-server-tcp.js +120 -0
- package/dist/cli/graceful-shutdown.d.ts +27 -0
- package/dist/cli/graceful-shutdown.js +30 -0
- package/dist/context/compaction.js +23 -15
- package/dist/context/manager.d.ts +0 -20
- package/dist/context/manager.js +0 -37
- package/dist/cron/cron-runtime.d.ts +2 -0
- package/dist/cron/cron-runtime.js +2 -0
- package/dist/cron/cron-store.d.ts +2 -0
- package/dist/cron/cron-store.js +2 -0
- package/dist/cron/scheduler.d.ts +5 -32
- package/dist/cron/scheduler.js +5 -113
- package/dist/data/openrouter-sync.js +1 -1
- package/dist/engine/engine.d.ts +256 -16
- package/dist/engine/engine.js +856 -200
- package/dist/engine/goal.d.ts +42 -0
- package/dist/engine/goal.js +52 -0
- package/dist/engine/image-compression.d.ts +81 -0
- package/dist/engine/image-compression.js +186 -0
- package/dist/engine/image-policy.d.ts +152 -0
- package/dist/engine/image-policy.js +165 -0
- package/dist/engine/reactive-threshold.d.ts +13 -0
- package/dist/engine/reactive-threshold.js +17 -0
- package/dist/engine/runtime.js +7 -0
- package/dist/engine/session-title.d.ts +13 -0
- package/dist/engine/session-title.js +34 -0
- package/dist/engine/turn-loop.d.ts +29 -8
- package/dist/engine/turn-loop.js +498 -316
- package/dist/git/parse-log.d.ts +13 -0
- package/dist/git/parse-log.js +21 -0
- package/dist/git/utils.d.ts +8 -6
- package/dist/git/utils.js +57 -26
- package/dist/git/worktree.js +33 -18
- package/dist/hooks/events.d.ts +23 -1
- package/dist/hooks/goal-stop-hook.d.ts +30 -0
- package/dist/hooks/goal-stop-hook.js +102 -0
- package/dist/hooks/hook-output.d.ts +33 -0
- package/dist/hooks/hook-output.js +79 -0
- package/dist/hooks/registry.d.ts +7 -0
- package/dist/hooks/registry.js +38 -1
- package/dist/hooks/shell-runner.d.ts +2 -0
- package/dist/hooks/shell-runner.js +43 -2
- package/dist/index.d.ts +32 -7
- package/dist/index.js +30 -3
- package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
- package/dist/llm/capabilities/reasoning-control.js +38 -0
- package/dist/llm/capabilities/rules.js +33 -3
- package/dist/llm/capabilities/types.d.ts +22 -2
- package/dist/llm/clamp-max-tokens.d.ts +11 -0
- package/dist/llm/clamp-max-tokens.js +17 -0
- package/dist/llm/client-base.d.ts +41 -4
- package/dist/llm/client-base.js +67 -13
- package/dist/llm/client-factory.d.ts +3 -3
- package/dist/llm/client-factory.js +2 -2
- package/dist/llm/model-pool.d.ts +15 -11
- package/dist/llm/model-pool.js +23 -20
- package/dist/llm/provider-catalog.d.ts +2 -2
- package/dist/llm/providers/anthropic.d.ts +32 -2
- package/dist/llm/providers/anthropic.js +147 -8
- package/dist/llm/providers/openai.d.ts +11 -2
- package/dist/llm/providers/openai.js +253 -52
- package/dist/llm/reasoning-setting.d.ts +51 -0
- package/dist/llm/reasoning-setting.js +32 -0
- package/dist/llm/stop-reason.d.ts +13 -0
- package/dist/llm/stop-reason.js +21 -0
- package/dist/llm/strip-vision.d.ts +32 -0
- package/dist/llm/strip-vision.js +94 -0
- package/dist/llm/types.d.ts +5 -6
- package/dist/logging/logger.js +18 -4
- package/dist/logging/sanitize-messages.d.ts +10 -0
- package/dist/logging/sanitize-messages.js +90 -0
- package/dist/lsp/client.js +28 -8
- package/dist/lsp/manager.js +2 -1
- package/dist/lsp/root-path.d.ts +9 -0
- package/dist/lsp/root-path.js +12 -0
- package/dist/onboarding.js +18 -11
- package/dist/plugins/gitOps.d.ts +34 -0
- package/dist/plugins/gitOps.js +59 -3
- package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
- package/dist/plugins/installer/codex/convertAgents.js +55 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
- package/dist/plugins/installer/codex/convertMcp.js +45 -0
- package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
- package/dist/plugins/installer/codex/convertSkills.js +33 -0
- package/dist/plugins/installer/detectFormat.d.ts +2 -0
- package/dist/plugins/installer/detectFormat.js +6 -0
- package/dist/plugins/installer/install.d.ts +7 -0
- package/dist/plugins/installer/install.js +99 -0
- package/dist/plugins/installer/installFromSource.d.ts +13 -0
- package/dist/plugins/installer/installFromSource.js +45 -0
- package/dist/plugins/installer/list.d.ts +9 -0
- package/dist/plugins/installer/list.js +30 -0
- package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
- package/dist/plugins/installer/loadPluginAgents.js +23 -0
- package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
- package/dist/plugins/installer/loadPluginMcp.js +74 -0
- package/dist/plugins/installer/parseSource.d.ts +20 -0
- package/dist/plugins/installer/parseSource.js +74 -0
- package/dist/plugins/installer/paths.d.ts +5 -0
- package/dist/plugins/installer/paths.js +25 -0
- package/dist/plugins/installer/types.d.ts +49 -0
- package/dist/plugins/installer/types.js +27 -0
- package/dist/plugins/installer/uninstall.d.ts +2 -0
- package/dist/plugins/installer/uninstall.js +14 -0
- package/dist/plugins/installer/update.d.ts +14 -0
- package/dist/plugins/installer/update.js +65 -0
- package/dist/plugins/loadPluginHooks.d.ts +8 -1
- package/dist/plugins/loadPluginHooks.js +11 -1
- package/dist/plugins/marketplaceManager.d.ts +8 -1
- package/dist/plugins/marketplaceManager.js +38 -4
- package/dist/plugins/pluginCommandHook.js +34 -0
- package/dist/plugins/pluginInstaller.d.ts +21 -0
- package/dist/plugins/pluginInstaller.js +128 -29
- package/dist/plugins/schemas.js +40 -6
- package/dist/plugins/types.d.ts +4 -0
- package/dist/preset/index.js +16 -0
- package/dist/prompt/composer.d.ts +10 -0
- package/dist/prompt/composer.js +29 -4
- package/dist/prompt/sections/orchestration.md +8 -0
- package/dist/protocol/chat-session-manager.d.ts +8 -1
- package/dist/protocol/chat-session-manager.js +10 -0
- package/dist/protocol/chat-session.d.ts +17 -0
- package/dist/protocol/chat-session.js +28 -0
- package/dist/protocol/redact.d.ts +50 -0
- package/dist/protocol/redact.js +71 -0
- package/dist/protocol/server.d.ts +41 -2
- package/dist/protocol/server.js +231 -33
- package/dist/protocol/tcp-transport.d.ts +45 -0
- package/dist/protocol/tcp-transport.js +74 -0
- package/dist/protocol/types.d.ts +23 -0
- package/dist/protocol/types.js +6 -0
- package/dist/remote/bridge.d.ts +11 -2
- package/dist/remote/bridge.js +61 -33
- package/dist/run/ArtifactTracker.js +5 -4
- package/dist/run/EngineRunner.d.ts +27 -1
- package/dist/run/EngineRunner.js +46 -8
- package/dist/run/FileRunStore.js +32 -8
- package/dist/run/RunLock.d.ts +28 -2
- package/dist/run/RunLock.js +49 -7
- package/dist/run/RunManager.js +51 -3
- package/dist/run/RunQueue.d.ts +1 -0
- package/dist/run/RunQueue.js +8 -2
- package/dist/run/factory.d.ts +7 -0
- package/dist/run/factory.js +1 -0
- package/dist/run/index.d.ts +2 -2
- package/dist/run/index.js +1 -1
- package/dist/run/redirect-target.d.ts +7 -0
- package/dist/run/redirect-target.js +13 -0
- package/dist/run/types.js +1 -1
- package/dist/services/browser-open.d.ts +13 -0
- package/dist/services/browser-open.js +16 -0
- package/dist/services/dream-consolidation.d.ts +57 -0
- package/dist/services/dream-consolidation.js +151 -0
- package/dist/services/memory-orchestrator.js +18 -0
- package/dist/services/notifier.d.ts +8 -0
- package/dist/services/notifier.js +39 -25
- package/dist/services/oauth.js +5 -7
- package/dist/services/session-memory-sort.d.ts +8 -0
- package/dist/services/session-memory-sort.js +9 -0
- package/dist/services/session-memory.js +8 -9
- package/dist/session/file-history.js +10 -3
- package/dist/session/session-manager.d.ts +23 -1
- package/dist/session/session-manager.js +73 -2
- package/dist/settings/disk-defaults.d.ts +35 -0
- package/dist/settings/disk-defaults.js +24 -0
- package/dist/settings/manager.d.ts +45 -1
- package/dist/settings/manager.js +121 -17
- package/dist/settings/manager.test.d.ts +1 -0
- package/dist/settings/manager.test.js +73 -0
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema.d.ts +1163 -126
- package/dist/settings/schema.js +126 -10
- package/dist/skills/scanner.d.ts +13 -6
- package/dist/skills/scanner.js +23 -4
- package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
- package/dist/tool-system/builtin/add-marketplace.js +76 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +15 -0
- package/dist/tool-system/builtin/agent-registry.js +14 -4
- package/dist/tool-system/builtin/agent.d.ts +55 -0
- package/dist/tool-system/builtin/agent.js +165 -17
- package/dist/tool-system/builtin/apply-patch/index.js +22 -3
- package/dist/tool-system/builtin/arena.js +6 -7
- package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
- package/dist/tool-system/builtin/complete-goal.js +45 -0
- package/dist/tool-system/builtin/cron.js +53 -9
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +6 -1
- package/dist/tool-system/builtin/generate-image.d.ts +25 -0
- package/dist/tool-system/builtin/generate-image.js +146 -0
- package/dist/tool-system/builtin/glob.js +7 -0
- package/dist/tool-system/builtin/grep.js +6 -0
- package/dist/tool-system/builtin/index.d.ts +18 -1
- package/dist/tool-system/builtin/index.js +62 -1
- package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
- package/dist/tool-system/builtin/notebook-edit.js +8 -1
- package/dist/tool-system/builtin/read.d.ts +2 -1
- package/dist/tool-system/builtin/read.js +9 -1
- package/dist/tool-system/builtin/skill.js +23 -5
- package/dist/tool-system/builtin/sleep.js +9 -3
- package/dist/tool-system/builtin/task.js +4 -1
- package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
- package/dist/tool-system/builtin/update-automation-memory.js +60 -0
- package/dist/tool-system/builtin/view-image.d.ts +18 -0
- package/dist/tool-system/builtin/view-image.js +96 -0
- package/dist/tool-system/builtin/web-search.d.ts +4 -1
- package/dist/tool-system/builtin/web-search.js +36 -3
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +7 -1
- package/dist/tool-system/context.d.ts +43 -0
- package/dist/tool-system/executor.d.ts +0 -1
- package/dist/tool-system/executor.js +34 -40
- package/dist/tool-system/investigation-guard.d.ts +4 -1
- package/dist/tool-system/investigation-guard.js +19 -3
- package/dist/tool-system/mcp-manager.d.ts +36 -12
- package/dist/tool-system/mcp-manager.js +173 -10
- package/dist/tool-system/path-policy.d.ts +94 -0
- package/dist/tool-system/path-policy.js +279 -0
- package/dist/tool-system/permission.js +43 -50
- package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
- package/dist/tool-system/plan-mode-allowlist.js +43 -0
- package/dist/tool-system/registry.js +12 -1
- package/dist/tool-system/validation.d.ts +10 -0
- package/dist/tool-system/validation.js +16 -3
- package/dist/types.d.ts +90 -15
- package/dist/utils/format.js +4 -2
- package/dist/utils/lockfile.js +9 -2
- package/dist/utils/theme.d.ts +6 -0
- package/dist/utils/theme.js +4 -2
- package/package.json +2 -1
|
@@ -21,5 +21,8 @@ export interface ResolvedSearchConfig {
|
|
|
21
21
|
* the user where the value came from when debugging.
|
|
22
22
|
*/
|
|
23
23
|
export declare function resolveSearchConfig(cwd?: string): ResolvedSearchConfig;
|
|
24
|
+
export declare function isWebSearchAvailable(cwd?: string, nowMs?: number): boolean;
|
|
24
25
|
export declare const webSearchToolDef: ToolDefinition;
|
|
25
|
-
export declare function webSearchTool(args: Record<string, unknown
|
|
26
|
+
export declare function webSearchTool(args: Record<string, unknown>, ctx?: {
|
|
27
|
+
cwd?: string;
|
|
28
|
+
}): Promise<string>;
|
|
@@ -18,7 +18,11 @@ export function resolveSearchConfig(cwd = process.cwd()) {
|
|
|
18
18
|
let settingsKey;
|
|
19
19
|
let settingsBaseUrl;
|
|
20
20
|
try {
|
|
21
|
-
|
|
21
|
+
// scope "full" so the USER-level ~/.code-shell/settings.json is read, not
|
|
22
|
+
// just ${cwd}/.code-shell — the search key is typically a personal/global
|
|
23
|
+
// credential. Default "project" scope would miss it (the "No search provider
|
|
24
|
+
// configured" bug, esp. for automation runs rooted at the app dir).
|
|
25
|
+
const merged = new SettingsManager(cwd, "full").get();
|
|
22
26
|
const search = merged.search ?? {};
|
|
23
27
|
const p = search.provider;
|
|
24
28
|
if (p === "serper" || p === "tavily" || p === "searxng")
|
|
@@ -68,6 +72,33 @@ export function resolveSearchConfig(cwd = process.cwd()) {
|
|
|
68
72
|
return { provider: "searxng", baseUrl: envSearxng, source: "env" };
|
|
69
73
|
return { provider: "serper", source: "none" };
|
|
70
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Tool-visibility guard: WebSearch is only useful when a search provider is
|
|
77
|
+
* configured. Mirrors the runtime check in the tool itself (source === "none"
|
|
78
|
+
* means no provider).
|
|
79
|
+
*
|
|
80
|
+
* Cached per-cwd with a short TTL: this runs on EVERY message's toolDefs
|
|
81
|
+
* assembly (engine.ts), and resolveSearchConfig reads + parses settings from
|
|
82
|
+
* disk each call. The 1s TTL collapses back-to-back reads while still picking
|
|
83
|
+
* up a newly-configured provider before the user's next message. The execution
|
|
84
|
+
* path (resolveSearchConfig) is intentionally NOT cached — only this boolean.
|
|
85
|
+
*/
|
|
86
|
+
const availCache = new Map();
|
|
87
|
+
const AVAIL_TTL_MS = 1000;
|
|
88
|
+
export function isWebSearchAvailable(cwd = process.cwd(), nowMs = Date.now()) {
|
|
89
|
+
const hit = availCache.get(cwd);
|
|
90
|
+
if (hit && nowMs - hit.at < AVAIL_TTL_MS)
|
|
91
|
+
return hit.value;
|
|
92
|
+
let value = false;
|
|
93
|
+
try {
|
|
94
|
+
value = resolveSearchConfig(cwd).source !== "none";
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
value = false; // unresolved config → treat as unavailable
|
|
98
|
+
}
|
|
99
|
+
availCache.set(cwd, { at: nowMs, value });
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
71
102
|
export const webSearchToolDef = {
|
|
72
103
|
name: "WebSearch",
|
|
73
104
|
description: "Search the web for information. Returns a list of search results with titles, URLs, and snippets. " +
|
|
@@ -84,12 +115,14 @@ export const webSearchToolDef = {
|
|
|
84
115
|
required: ["query"],
|
|
85
116
|
},
|
|
86
117
|
};
|
|
87
|
-
export async function webSearchTool(args) {
|
|
118
|
+
export async function webSearchTool(args, ctx) {
|
|
88
119
|
const query = args.query;
|
|
89
120
|
if (!query)
|
|
90
121
|
return "Error: query is required";
|
|
91
122
|
const numResults = Math.min(args.num_results || 10, 20);
|
|
92
|
-
|
|
123
|
+
// Use the run's cwd (e.g. an automation job's project dir) so per-project
|
|
124
|
+
// settings resolve correctly; falls back to process.cwd() when absent.
|
|
125
|
+
const config = resolveSearchConfig(ctx?.cwd);
|
|
93
126
|
if (config.source === "none") {
|
|
94
127
|
return "Error: No search provider configured. Open desktop 设置 → 连接 to add credentials, or set SERPER_API_KEY / TAVILY_API_KEY / SEARXNG_URL.";
|
|
95
128
|
}
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* Built-in Write file tool.
|
|
3
3
|
*/
|
|
4
4
|
import type { ToolDefinition } from "../../types.js";
|
|
5
|
+
import type { ToolContext } from "../context.js";
|
|
5
6
|
export declare const writeToolDef: ToolDefinition;
|
|
6
|
-
export declare function writeTool(args: Record<string, unknown
|
|
7
|
+
export declare function writeTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
5
5
|
import { dirname } from "node:path";
|
|
6
6
|
import { fileCache } from "./file-cache.js";
|
|
7
|
+
import { enforcePathPolicy } from "../path-policy.js";
|
|
7
8
|
export const writeToolDef = {
|
|
8
9
|
name: "Write",
|
|
9
10
|
description: "Write content to a file. Creates the file if it doesn't exist, " +
|
|
@@ -17,13 +18,18 @@ export const writeToolDef = {
|
|
|
17
18
|
required: ["file_path", "content"],
|
|
18
19
|
},
|
|
19
20
|
};
|
|
20
|
-
export async function writeTool(args) {
|
|
21
|
+
export async function writeTool(args, ctx) {
|
|
21
22
|
const filePath = args.file_path;
|
|
22
23
|
const content = args.content;
|
|
23
24
|
if (!filePath)
|
|
24
25
|
return "Error: file_path is required";
|
|
25
26
|
if (content === undefined)
|
|
26
27
|
return "Error: content is required";
|
|
28
|
+
// Path policy gate: deny writes to sensitive paths, refuse writes outside
|
|
29
|
+
// workspace until approved. See tool-system/path-policy.ts.
|
|
30
|
+
const blocked = enforcePathPolicy(filePath, "write", ctx?.cwd);
|
|
31
|
+
if (blocked)
|
|
32
|
+
return blocked;
|
|
27
33
|
try {
|
|
28
34
|
await mkdir(dirname(filePath), { recursive: true });
|
|
29
35
|
await writeFile(filePath, content, "utf-8");
|
|
@@ -65,6 +65,25 @@ export interface SubAgentSpawnRequest {
|
|
|
65
65
|
* gets `agent_start` / `agent_end` markers via `spawner.parentStream`.
|
|
66
66
|
*/
|
|
67
67
|
streamOverride?: StreamCallback;
|
|
68
|
+
/**
|
|
69
|
+
* Optional ModelPool key for the child Engine's LLM (e.g. "flash").
|
|
70
|
+
* Undefined → child inherits the parent's model (current behavior).
|
|
71
|
+
*/
|
|
72
|
+
model?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Optional tool-name allowlist for the child. When set, the child's tool
|
|
75
|
+
* pool is restricted to these names (still minus the nested-agent tools).
|
|
76
|
+
* Undefined → child inherits the parent's full tool set (current behavior).
|
|
77
|
+
*/
|
|
78
|
+
toolAllowlist?: string[];
|
|
79
|
+
/**
|
|
80
|
+
* Optional per-call system prompt appended to the child Engine's prompt
|
|
81
|
+
* (the role definition's Markdown body). Undefined → child inherits only
|
|
82
|
+
* the parent's appendSystemPrompt (current behavior).
|
|
83
|
+
*/
|
|
84
|
+
appendSystemPrompt?: string;
|
|
85
|
+
/** True for read-only reviewer/researcher children; tunes investigation guard reminders. */
|
|
86
|
+
readOnlySession?: boolean;
|
|
68
87
|
}
|
|
69
88
|
export interface SubAgentSpawner {
|
|
70
89
|
/** Run a sub-agent synchronously and return its text output. */
|
|
@@ -97,6 +116,12 @@ export interface ToolContext {
|
|
|
97
116
|
askUser?: AskUserFn;
|
|
98
117
|
/** Sub-agent spawner (Agent tool). Undefined → Agent tool unavailable. */
|
|
99
118
|
subAgentSpawner?: SubAgentSpawner;
|
|
119
|
+
/**
|
|
120
|
+
* Reusable sub-agent role definitions (loaded from .code-shell/agents/*.md).
|
|
121
|
+
* The Agent tool reads this to resolve `agent_type`. Undefined → only the
|
|
122
|
+
* ephemeral (inline prompt) mode is available.
|
|
123
|
+
*/
|
|
124
|
+
agentDefinitions?: import("../agent/agent-definition-registry.js").AgentDefinitionRegistry;
|
|
100
125
|
/**
|
|
101
126
|
* True when this Engine is itself a sub-agent. Set from EngineConfig.
|
|
102
127
|
* The Agent tool refuses to spawn when this is true — runtime check
|
|
@@ -150,6 +175,24 @@ export interface ToolContext {
|
|
|
150
175
|
* Populated from `settings.disabledSkills` by Engine.run().
|
|
151
176
|
*/
|
|
152
177
|
disabledSkills?: string[];
|
|
178
|
+
/**
|
|
179
|
+
* Plugin names the user has totally disabled. Every skill whose
|
|
180
|
+
* namespaced name starts with `${pluginName}:` is hidden from the
|
|
181
|
+
* LLM and rejected at dispatch by the skill builtin tool with a
|
|
182
|
+
* distinct "disabled plugin" message. Populated from
|
|
183
|
+
* `settings.disabledPlugins` by Engine.run().
|
|
184
|
+
*/
|
|
185
|
+
disabledPlugins?: string[];
|
|
186
|
+
/**
|
|
187
|
+
* Builtin tool names the project's capabilityOverrides marked `off` for
|
|
188
|
+
* this cwd. Engine.run() already HIDES these from the LLM-visible tool
|
|
189
|
+
* list, but the model can still NAME a hidden builtin (hallucinated, or
|
|
190
|
+
* remembered from an earlier turn when it was visible). The executor reads
|
|
191
|
+
* this set and rejects such a call — turning `off` into a real execution
|
|
192
|
+
* gate, not just a prompt-visibility filter. Empty/undefined for
|
|
193
|
+
* sub-agents and no-cwd contexts (same as readBuiltinOverride).
|
|
194
|
+
*/
|
|
195
|
+
disabledBuiltins?: Set<string>;
|
|
153
196
|
}
|
|
154
197
|
/**
|
|
155
198
|
* Per-Engine container that produces a fresh ToolContext on demand.
|
|
@@ -40,7 +40,6 @@ export declare class ToolExecutor {
|
|
|
40
40
|
isConcurrencySafe(toolName: string): boolean;
|
|
41
41
|
executeSingle(callIn: ToolCall): Promise<ToolResult>;
|
|
42
42
|
private isReadOnlyBashCommand;
|
|
43
|
-
executeAll(calls: ToolCall[]): Promise<ToolResult[]>;
|
|
44
43
|
/**
|
|
45
44
|
* Convert tool results into Message entries for the transcript.
|
|
46
45
|
*/
|
|
@@ -5,6 +5,7 @@ import { setMaxListeners } from "node:events";
|
|
|
5
5
|
import { logger as rootLogger, getCurrentSid } from "../logging/logger.js";
|
|
6
6
|
import { recordToolCall, recordToolResult } from "../logging/session-recorder.js";
|
|
7
7
|
import { validateToolArgs } from "./validation.js";
|
|
8
|
+
import { PLAN_MODE_ALLOWED_TOOLS } from "./plan-mode-allowlist.js";
|
|
8
9
|
// A1 hardening: hooks must never promote a non-`allow` classifier
|
|
9
10
|
// decision to `allow`. They may otherwise adjust the decision freely
|
|
10
11
|
// (e.g. tighten `allow` to `deny`/`ask`, or relax `deny` to `ask` to
|
|
@@ -83,22 +84,41 @@ export class ToolExecutor {
|
|
|
83
84
|
// Local `call` so we can rewrite args via pre_tool_use updatedInput
|
|
84
85
|
// without mutating the caller's object.
|
|
85
86
|
let call = callIn;
|
|
87
|
+
// 0. Abort fast-path: if the run signal is already aborted, return a
|
|
88
|
+
// synthetic error result WITHOUT running pre_tool_use hooks, the
|
|
89
|
+
// permission classifier, or the handler. registry.executeTool also checks
|
|
90
|
+
// abort, but only after this method has paid for all the per-tool
|
|
91
|
+
// hook/permission round-trips. For an aborted sub-agent whose turn queued
|
|
92
|
+
// a batch of tools (e.g. 10 Reads), this collapses the whole batch
|
|
93
|
+
// instantly instead of round-tripping each one — part of the sub-agent
|
|
94
|
+
// leak fix that keeps an aborted child from burning work post-abort.
|
|
95
|
+
if (this.signal?.aborted) {
|
|
96
|
+
return {
|
|
97
|
+
id: call.id,
|
|
98
|
+
toolName: call.toolName,
|
|
99
|
+
error: `Tool aborted before execution: ${call.toolName}`,
|
|
100
|
+
isError: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// 0. Capability override: a builtin the project marked `off` is HIDDEN from
|
|
104
|
+
// the model's tool list (engine.ts applyBuiltinOverrideVisibility), but the
|
|
105
|
+
// model can still NAME it (hallucination, or a remembered earlier turn). The
|
|
106
|
+
// registry still holds the tool, so without a gate here it would execute.
|
|
107
|
+
// Reject it the same way plan mode rejects a disallowed tool — return an
|
|
108
|
+
// error result, never run the handler.
|
|
109
|
+
if (this.toolCtx?.disabledBuiltins?.has(call.toolName)) {
|
|
110
|
+
return {
|
|
111
|
+
id: call.id,
|
|
112
|
+
toolName: call.toolName,
|
|
113
|
+
error: `Tool ${call.toolName} is disabled by this project's capability override and cannot be used. Do NOT retry this tool call.`,
|
|
114
|
+
isError: true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
86
117
|
// 0. Plan mode: only allow read-only tools — no file writes at all
|
|
87
118
|
if (this.toolCtx?.planMode) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"Read",
|
|
92
|
-
"Glob",
|
|
93
|
-
"Grep",
|
|
94
|
-
"WebSearch",
|
|
95
|
-
"WebFetch",
|
|
96
|
-
"AskUserQuestion",
|
|
97
|
-
"Agent",
|
|
98
|
-
"ToolSearch",
|
|
99
|
-
"TodoWrite",
|
|
100
|
-
]);
|
|
101
|
-
if (!allowedInPlan.has(call.toolName)) {
|
|
119
|
+
// Shared with engine.ts's tool-visibility filter so the set the model
|
|
120
|
+
// SEES and the set the executor RUNS can't drift (they had).
|
|
121
|
+
if (!PLAN_MODE_ALLOWED_TOOLS.has(call.toolName)) {
|
|
102
122
|
if (call.toolName === "Bash" && this.isReadOnlyBashCommand(call.args)) {
|
|
103
123
|
// Read-only bash is fine
|
|
104
124
|
}
|
|
@@ -447,32 +467,6 @@ export class ToolExecutor {
|
|
|
447
467
|
}
|
|
448
468
|
return false;
|
|
449
469
|
}
|
|
450
|
-
async executeAll(calls) {
|
|
451
|
-
const results = [];
|
|
452
|
-
// Separate safe (concurrent) and unsafe (sequential) tools
|
|
453
|
-
const safe = [];
|
|
454
|
-
const unsafe = [];
|
|
455
|
-
for (const call of calls) {
|
|
456
|
-
const tool = this.registry.getTool(call.toolName);
|
|
457
|
-
if (tool?.isConcurrencySafe) {
|
|
458
|
-
safe.push(call);
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
unsafe.push(call);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// Execute safe tools concurrently
|
|
465
|
-
if (safe.length > 0) {
|
|
466
|
-
const safeResults = await Promise.all(safe.map((c) => this.executeSingle(c)));
|
|
467
|
-
results.push(...safeResults);
|
|
468
|
-
}
|
|
469
|
-
// Execute unsafe tools sequentially
|
|
470
|
-
for (const call of unsafe) {
|
|
471
|
-
const result = await this.executeSingle(call);
|
|
472
|
-
results.push(result);
|
|
473
|
-
}
|
|
474
|
-
return results;
|
|
475
|
-
}
|
|
476
470
|
/**
|
|
477
471
|
* Convert tool results into Message entries for the transcript.
|
|
478
472
|
*/
|
|
@@ -21,6 +21,7 @@ export interface GuardDecision {
|
|
|
21
21
|
block?: string;
|
|
22
22
|
prepend?: string;
|
|
23
23
|
}
|
|
24
|
+
export type InvestigationGuardPolicy = "normal" | "soft" | "read-only-review";
|
|
24
25
|
export declare class InvestigationGuard {
|
|
25
26
|
private readHistory;
|
|
26
27
|
private consecutiveReads;
|
|
@@ -35,8 +36,10 @@ export declare class InvestigationGuard {
|
|
|
35
36
|
* be killed by the guard — it should still get the signal to change
|
|
36
37
|
* strategy, but the tool call goes through.
|
|
37
38
|
*/
|
|
38
|
-
private
|
|
39
|
+
private policy;
|
|
40
|
+
private readOnlyReviewBudgetReminderShown;
|
|
39
41
|
setSoftMode(soft: boolean): void;
|
|
42
|
+
setPolicy(policy: InvestigationGuardPolicy): void;
|
|
40
43
|
preToolCheck(call: ToolCall): GuardDecision | undefined;
|
|
41
44
|
noteText(text: string | undefined): void;
|
|
42
45
|
noteToolResult(_call: ToolCall, _result: ToolResult): void;
|
|
@@ -38,9 +38,13 @@ export class InvestigationGuard {
|
|
|
38
38
|
* be killed by the guard — it should still get the signal to change
|
|
39
39
|
* strategy, but the tool call goes through.
|
|
40
40
|
*/
|
|
41
|
-
|
|
41
|
+
policy = "normal";
|
|
42
|
+
readOnlyReviewBudgetReminderShown = false;
|
|
42
43
|
setSoftMode(soft) {
|
|
43
|
-
this.
|
|
44
|
+
this.policy = soft ? "soft" : "normal";
|
|
45
|
+
}
|
|
46
|
+
setPolicy(policy) {
|
|
47
|
+
this.policy = policy;
|
|
44
48
|
}
|
|
45
49
|
preToolCheck(call) {
|
|
46
50
|
const tool = call.toolName;
|
|
@@ -68,7 +72,7 @@ export class InvestigationGuard {
|
|
|
68
72
|
`Re-reading the same content will not produce new information. ` +
|
|
69
73
|
`Switch strategy: run a command with side effects (Bash, debug log, repro), make a code change, or ask the user a specific question. ` +
|
|
70
74
|
`If you genuinely need this content, summarize what you already know about it from prior reads instead of re-fetching.`;
|
|
71
|
-
if (this.
|
|
75
|
+
if (this.policy !== "normal") {
|
|
72
76
|
// Soft mode (headless): deliver the same signal as a prepend so the
|
|
73
77
|
// model still sees it, but allow the tool call to proceed. Hard-
|
|
74
78
|
// blocking an unattended `code-shell run …` would dead-end the task
|
|
@@ -84,6 +88,14 @@ export class InvestigationGuard {
|
|
|
84
88
|
`Re-reading rarely yields new information. After this result, change strategy (Bash side-effect, edit, or ask user) rather than reading further.</system-reminder>`);
|
|
85
89
|
}
|
|
86
90
|
if (this.consecutiveReads > READ_BUDGET) {
|
|
91
|
+
if (this.policy === "read-only-review") {
|
|
92
|
+
if (!this.readOnlyReviewBudgetReminderShown) {
|
|
93
|
+
this.readOnlyReviewBudgetReminderShown = true;
|
|
94
|
+
prependParts.push(`<system-reminder>Investigation guard: ${this.consecutiveReads} consecutive read-only calls during an explicit read-only review. ` +
|
|
95
|
+
`Continue only if each read is adding new evidence, and surface a concise status update when useful.</system-reminder>`);
|
|
96
|
+
}
|
|
97
|
+
return prependParts.length ? { prepend: prependParts.join("\n") } : undefined;
|
|
98
|
+
}
|
|
87
99
|
prependParts.push(`<system-reminder>Investigation guard: ${this.consecutiveReads} consecutive read-only calls with no action taken. ` +
|
|
88
100
|
`Per coding.md, change strategy now — make a code change, run a command with side effects, or ask the user.</system-reminder>`);
|
|
89
101
|
}
|
|
@@ -106,6 +118,10 @@ export class InvestigationGuard {
|
|
|
106
118
|
this.turnHasSideEffect = false;
|
|
107
119
|
if (wasSilent >= SILENT_TURN_BUDGET && turnNumber !== this.lastReminderSilent) {
|
|
108
120
|
this.lastReminderSilent = turnNumber;
|
|
121
|
+
if (this.policy === "read-only-review") {
|
|
122
|
+
return (`<system-reminder>Investigation guard: you have taken ${wasSilent} consecutive turns of read-only investigation ` +
|
|
123
|
+
`without any text update to the user. Surface a brief status update with what you have confirmed, ruled out, and will inspect next.</system-reminder>`);
|
|
124
|
+
}
|
|
109
125
|
return (`<system-reminder>Investigation guard: you have taken ${wasSilent} consecutive turns of read-only investigation ` +
|
|
110
126
|
`without any text update to the user or side-effecting action. ` +
|
|
111
127
|
`Either surface a status update (what have you ruled out, what's your current hypothesis?), ` +
|
|
@@ -7,25 +7,38 @@ import type { Tool as McpTool } from "@modelcontextprotocol/sdk/types.js";
|
|
|
7
7
|
import type { MCPServerConfig, RegisteredTool } from "../types.js";
|
|
8
8
|
import { ToolRegistry } from "./registry.js";
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Persist an MCP-returned image to disk and return the textual
|
|
11
|
+
* reference the LLM should see. Images larger than
|
|
12
|
+
* MAX_MCP_IMAGE_BYTES are dropped with a placeholder so a misbehaving
|
|
13
|
+
* MCP server (e.g. a screenshot agent in a loop) can't blow up disk
|
|
14
|
+
* or the context.
|
|
11
15
|
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* safe-by-default for unknown servers that may hold mutable state.
|
|
15
|
-
* - Opt-in: when the MCP server explicitly declares
|
|
16
|
-
* `annotations.readOnlyHint === true` per the MCP spec, we honour
|
|
17
|
-
* that hint and set BOTH isConcurrencySafe and isReadOnly to true,
|
|
18
|
-
* enabling parallel execution for provably read-only tools.
|
|
19
|
-
* - Anything other than the boolean literal `true`
|
|
20
|
-
* (undefined, null, false, "true", missing annotations) stays false.
|
|
21
|
-
*
|
|
22
|
-
* @internal exported for unit testing without spinning up a real transport.
|
|
16
|
+
* Returns a one-line note like:
|
|
17
|
+
* [mcp-image] server=playwright tool=screenshot saved=/abs/path.png (123 KB)
|
|
23
18
|
*/
|
|
19
|
+
export declare function spillMcpImage(serverName: string, toolName: string, base64: string, mimeType: string, opts?: {
|
|
20
|
+
baseDir?: string;
|
|
21
|
+
now?: () => number;
|
|
22
|
+
}): Promise<string>;
|
|
23
|
+
export declare function wrapMcpOutput(serverName: string, toolName: string, body: string): string;
|
|
24
|
+
export declare function stripInternalToolArgs(args: Record<string, unknown>): Record<string, unknown>;
|
|
24
25
|
export declare function buildRegisteredTool(serverName: string, tool: McpTool): RegisteredTool;
|
|
25
26
|
export declare class MCPManager {
|
|
26
27
|
private readonly toolRegistry;
|
|
27
28
|
private static instance;
|
|
28
29
|
private connections;
|
|
30
|
+
/**
|
|
31
|
+
* In-flight connect()s keyed by server name. When the broadcast config
|
|
32
|
+
* reload (server.ts forEachSession → every session's refreshRuntimeConfig)
|
|
33
|
+
* calls connectAll for the SAME `added` server on this ONE shared pool, K
|
|
34
|
+
* concurrent connect(name) calls would each start a fresh handshake because
|
|
35
|
+
* `connections.has(name)` only becomes true AFTER the handshake completes —
|
|
36
|
+
* a thundering herd of duplicate connections racing to set(). Coalescing by
|
|
37
|
+
* name here collapses them to a SINGLE underlying connection; the late
|
|
38
|
+
* callers await the same promise and return. Cleared in finally so a failed
|
|
39
|
+
* connect can be retried later.
|
|
40
|
+
*/
|
|
41
|
+
private connecting;
|
|
29
42
|
constructor(toolRegistry: ToolRegistry);
|
|
30
43
|
static getInstance(): MCPManager;
|
|
31
44
|
/**
|
|
@@ -34,8 +47,19 @@ export declare class MCPManager {
|
|
|
34
47
|
connectAll(servers: Record<string, MCPServerConfig>): Promise<void>;
|
|
35
48
|
/**
|
|
36
49
|
* Connect to a single MCP server.
|
|
50
|
+
*
|
|
51
|
+
* Coalesces concurrent calls for the same `name`: an already-connected server
|
|
52
|
+
* returns immediately, and a connect already in flight for this name is
|
|
53
|
+
* awaited rather than restarted (#5 — thundering-herd guard on the shared
|
|
54
|
+
* pool). The actual handshake lives in `performConnect`.
|
|
37
55
|
*/
|
|
38
56
|
connect(name: string, config: MCPServerConfig): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Perform the actual handshake + tool discovery for one server. Separated
|
|
59
|
+
* from `connect` so the coalescing/dedup logic stays in one place. Override
|
|
60
|
+
* `connect` (not this) in test doubles that want to count handshakes.
|
|
61
|
+
*/
|
|
62
|
+
protected performConnect(name: string, config: MCPServerConfig): Promise<void>;
|
|
39
63
|
/**
|
|
40
64
|
* Discover tools from an MCP server and register them.
|
|
41
65
|
*/
|