@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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Supports AbortSignal for cascading cancellation.
|
|
7
7
|
* Supports onStream for real-time output passthrough.
|
|
8
8
|
*/
|
|
9
|
-
import { asyncAgentRegistry } from "./agent-registry.js";
|
|
9
|
+
import { asyncAgentRegistry, MAX_BACKGROUND_AGENTS } from "./agent-registry.js";
|
|
10
10
|
import { createTranscriptTranslator } from "./agent-transcript-translator.js";
|
|
11
11
|
import { notificationQueue } from "./agent-notifications.js";
|
|
12
12
|
import { nanoid } from "nanoid";
|
|
@@ -32,18 +32,107 @@ function safeEmit(sink, event) {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/** Preferred default role when the caller omits agent_type but roles exist. */
|
|
36
|
+
const DEFAULT_AGENT_TYPE = "general-purpose";
|
|
37
|
+
/**
|
|
38
|
+
* Resolve an `agent_type` against the role registry into spawn overrides.
|
|
39
|
+
*
|
|
40
|
+
* - Omitted type + non-empty registry → fall back to a configured role
|
|
41
|
+
* ("general-purpose" if present, else the first available) instead of
|
|
42
|
+
* running a nameless ephemeral agent. Relaxed from the earlier "throw when
|
|
43
|
+
* agent_type omitted": the model habitually omits it, and a hard error just
|
|
44
|
+
* turned every spawn into a failure. Falling back to a real role keeps the
|
|
45
|
+
* tool-allowlist / system-prompt benefits without breaking the call.
|
|
46
|
+
* - Omitted type + empty registry → empty overrides (true ephemeral; nothing
|
|
47
|
+
* to fall back to).
|
|
48
|
+
* - Unknown explicit type → throw, so the LLM gets a clear correction rather
|
|
49
|
+
* than silently running a generic agent.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveAgentTypeOverrides(agentType, registry) {
|
|
52
|
+
const available = registry?.list().map((d) => d.name) ?? [];
|
|
53
|
+
let resolvedType = agentType;
|
|
54
|
+
if (!resolvedType) {
|
|
55
|
+
if (available.length === 0)
|
|
56
|
+
return {};
|
|
57
|
+
resolvedType = available.includes(DEFAULT_AGENT_TYPE) ? DEFAULT_AGENT_TYPE : available[0];
|
|
58
|
+
}
|
|
59
|
+
const def = registry?.get(resolvedType);
|
|
60
|
+
if (!def) {
|
|
61
|
+
const list = available.join(", ") || "(none defined)";
|
|
62
|
+
throw new Error(`unknown agent_type '${resolvedType}'. Available: ${list}`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
resolvedType,
|
|
66
|
+
model: def.model,
|
|
67
|
+
maxTurns: def.maxTurns,
|
|
68
|
+
toolAllowlist: def.tools,
|
|
69
|
+
appendSystemPrompt: def.systemPrompt,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Render the "Available agent types" block injected into the Agent tool's
|
|
74
|
+
* description, listing the roles defined in .code-shell/agents/*.md so the
|
|
75
|
+
* model knows it can pass `agent_type` instead of hand-rolling an ad-hoc
|
|
76
|
+
* agent. Without this the model never sees the registry (it lives per-engine,
|
|
77
|
+
* not in the static tool def) and falls back to nameless ephemeral agents —
|
|
78
|
+
* see the Core A/B/C incident. Returns "" when no roles are defined, so the
|
|
79
|
+
* base description is left untouched.
|
|
80
|
+
*/
|
|
81
|
+
export function buildAgentTypesBlock(registry) {
|
|
82
|
+
const defs = registry?.list() ?? [];
|
|
83
|
+
if (defs.length === 0)
|
|
84
|
+
return "";
|
|
85
|
+
const lines = defs.map((d) => {
|
|
86
|
+
const tools = d.tools && d.tools.length > 0 ? d.tools.join(", ") : "all parent tools";
|
|
87
|
+
return `- ${d.name}: ${d.description} (tools: ${tools})`;
|
|
88
|
+
});
|
|
89
|
+
return [
|
|
90
|
+
"",
|
|
91
|
+
"Available agent types (pass one as `agent_type` to reuse its role, tool allowlist, and turn cap):",
|
|
92
|
+
...lines,
|
|
93
|
+
"Pass the closest matching agent_type (e.g. read-only investigation → researcher/explorer, planning → planner, full multi-step work → general-purpose). If you omit agent_type it defaults to general-purpose (or the first available role) — passing an explicit one is preferred.",
|
|
94
|
+
].join("\n");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Produce an Agent tool definition whose description ends with the live
|
|
98
|
+
* available-agent-types listing. Pure: takes the registry, returns a new def
|
|
99
|
+
* (the base `agentToolDef` const is never mutated). When no roles exist the
|
|
100
|
+
* base def is returned unchanged.
|
|
101
|
+
*/
|
|
102
|
+
export function agentToolDefWithTypes(registry) {
|
|
103
|
+
const block = buildAgentTypesBlock(registry);
|
|
104
|
+
if (!block)
|
|
105
|
+
return agentToolDef;
|
|
106
|
+
return { ...agentToolDef, description: agentToolDef.description + "\n" + block };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Emit a sub-agent lifecycle event via the existing `notification` hook,
|
|
110
|
+
* tagged with a `kind`. No-op when hooks are absent. Fire-and-forget: emit is
|
|
111
|
+
* async, we deliberately `void` it so bookkeeping never blocks on a handler
|
|
112
|
+
* (mirrors the background-completion notification below).
|
|
113
|
+
*/
|
|
114
|
+
export function emitSubAgentHook(hooks, kind, payload) {
|
|
115
|
+
void hooks?.emit("notification", { kind, ...payload });
|
|
116
|
+
}
|
|
35
117
|
export const agentToolDef = {
|
|
36
118
|
name: "Agent",
|
|
37
|
-
description: "Launch a sub-agent to handle a
|
|
38
|
-
"The sub-agent
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
119
|
+
description: "Launch a sub-agent to handle a task in its own clean, isolated context. " +
|
|
120
|
+
"The sub-agent runs independently with access to the same tools, and its final " +
|
|
121
|
+
"report is the ONLY thing returned to you — it is stateless, so you cannot send " +
|
|
122
|
+
"follow-up messages. Write a complete, self-contained task description.\n\n" +
|
|
123
|
+
"PRIMARY USE — context isolation: when a task needs to read many files or run a " +
|
|
124
|
+
"broad investigation but you only need the conclusion, delegate it. The sub-agent " +
|
|
125
|
+
"absorbs the noisy intermediate output in its own context; you keep the answer, not " +
|
|
126
|
+
"the file dumps. This protects your main context from being flooded.\n\n" +
|
|
127
|
+
"DON'T use this for a quick lookup where you know the file/symbol and expect a few " +
|
|
128
|
+
"matches — use Read/Grep/Glob directly instead; spawning an agent wastes a turn.\n\n" +
|
|
129
|
+
"Parallel fan-out is the EXCEPTION: only launch several agents in one message when " +
|
|
130
|
+
"the work truly splits into independent pieces with no shared state. Prefer one " +
|
|
131
|
+
"well-scoped delegation over a swarm.\n\n" +
|
|
132
|
+
"You can optionally run an agent in the background using the run_in_background " +
|
|
133
|
+
"parameter. When it runs in the background, you will be automatically notified when " +
|
|
134
|
+
"it completes — do NOT sleep, poll, or proactively check on its progress. Continue " +
|
|
135
|
+
"with other work or respond to the user instead.",
|
|
47
136
|
inputSchema: {
|
|
48
137
|
type: "object",
|
|
49
138
|
properties: {
|
|
@@ -53,6 +142,15 @@ export const agentToolDef = {
|
|
|
53
142
|
"Shown in the agent dock to identify what kind of work this sub-agent is doing. " +
|
|
54
143
|
"Keep it 1-2 words. Defaults to 'Agent' if omitted.",
|
|
55
144
|
},
|
|
145
|
+
agent_type: {
|
|
146
|
+
type: "string",
|
|
147
|
+
description: "Optional reusable role defined in .code-shell/agents/*.md (e.g. 'researcher'). " +
|
|
148
|
+
"Loads that role's model, tool allowlist, turn cap, and system prompt. " +
|
|
149
|
+
"Disabled roles are not available. If you pass an unknown role you'll get " +
|
|
150
|
+
"an error listing the currently available roles. " +
|
|
151
|
+
"If omitted, defaults to a configured role (general-purpose or the first available); " +
|
|
152
|
+
"with no roles configured it runs an ephemeral agent. Passing an explicit role is preferred.",
|
|
153
|
+
},
|
|
56
154
|
description: {
|
|
57
155
|
type: "string",
|
|
58
156
|
description: "A short (3-5 word) description of the task",
|
|
@@ -97,16 +195,18 @@ uiStream,
|
|
|
97
195
|
* sub-agents.
|
|
98
196
|
*/
|
|
99
197
|
streamOverride) {
|
|
100
|
-
const { agentId, name, description } = opts;
|
|
198
|
+
const { agentId, name, description, agentType } = opts;
|
|
101
199
|
const startEndSink = uiStream ?? spawner.parentStream;
|
|
102
|
-
safeEmit(startEndSink, { type: "agent_start", agentId, name, description });
|
|
200
|
+
safeEmit(startEndSink, { type: "agent_start", agentId, name, description, agentType });
|
|
201
|
+
emitSubAgentHook(opts.hooks, "subagent_start", { agentId, description });
|
|
103
202
|
// `resetPlanMode` / `restorePlanMode` operated on a module-level singleton
|
|
104
203
|
// that no longer exists. The child Engine is a fresh instance; plan-mode
|
|
105
204
|
// isolation between parent and child is enforced via separate Engine
|
|
106
205
|
// instances (finalized in T6).
|
|
107
206
|
const text = await spawner.spawn({ ...opts, streamOverride });
|
|
108
207
|
const finalText = text || `Agent completed but produced no text output.`;
|
|
109
|
-
safeEmit(startEndSink, { type: "agent_end", agentId, name, description, text: finalText });
|
|
208
|
+
safeEmit(startEndSink, { type: "agent_end", agentId, name, description, text: finalText, agentType });
|
|
209
|
+
emitSubAgentHook(opts.hooks, "subagent_finish", { agentId, description, text: finalText });
|
|
110
210
|
return finalText;
|
|
111
211
|
}
|
|
112
212
|
export async function agentTool(args, ctx) {
|
|
@@ -132,7 +232,15 @@ export async function agentTool(args, ctx) {
|
|
|
132
232
|
if (parentSignal?.aborted) {
|
|
133
233
|
return "Agent aborted before starting.";
|
|
134
234
|
}
|
|
135
|
-
const
|
|
235
|
+
const agentType = args.agent_type?.trim() || undefined;
|
|
236
|
+
let overrides;
|
|
237
|
+
try {
|
|
238
|
+
overrides = resolveAgentTypeOverrides(agentType, ctx?.agentDefinitions);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
return `Error: ${err.message}`;
|
|
242
|
+
}
|
|
243
|
+
const maxTurns = args.max_turns || overrides.maxTurns || 15;
|
|
136
244
|
const runInBackground = args.run_in_background === true;
|
|
137
245
|
const agentId = nanoid(8);
|
|
138
246
|
const parentStream = spawner.parentStream;
|
|
@@ -142,11 +250,19 @@ export async function agentTool(args, ctx) {
|
|
|
142
250
|
// (background agents survive the spawning turn). Cancellation goes
|
|
143
251
|
// through AgentCancel(agent_id).
|
|
144
252
|
if (runInBackground) {
|
|
253
|
+
if (asyncAgentRegistry.runningCount() >= MAX_BACKGROUND_AGENTS) {
|
|
254
|
+
return `Error: too many background agents running (limit ${MAX_BACKGROUND_AGENTS}). ` +
|
|
255
|
+
`Wait for some to finish or cancel one with AgentCancel(agent_id) before launching more.`;
|
|
256
|
+
}
|
|
145
257
|
const controller = new AbortController();
|
|
146
258
|
asyncAgentRegistry.register({
|
|
147
259
|
agentId,
|
|
148
260
|
name,
|
|
261
|
+
agentType: overrides.resolvedType,
|
|
149
262
|
description,
|
|
263
|
+
// Tag with the spawning session so the parent Engine.run waits only on
|
|
264
|
+
// its own background agents (hasRunningForSession).
|
|
265
|
+
sessionId: ctx?.sessionId,
|
|
150
266
|
status: "running",
|
|
151
267
|
startedAt: Date.now(),
|
|
152
268
|
abort: () => controller.abort(),
|
|
@@ -169,8 +285,14 @@ export async function agentTool(args, ctx) {
|
|
|
169
285
|
agentId,
|
|
170
286
|
name,
|
|
171
287
|
description,
|
|
288
|
+
agentType: overrides.resolvedType,
|
|
172
289
|
prompt,
|
|
173
290
|
maxTurns,
|
|
291
|
+
model: overrides.model,
|
|
292
|
+
toolAllowlist: overrides.toolAllowlist,
|
|
293
|
+
appendSystemPrompt: overrides.appendSystemPrompt,
|
|
294
|
+
readOnlySession: overrides.resolvedType === "researcher" || overrides.resolvedType === "explorer",
|
|
295
|
+
hooks: ctx?.hooks,
|
|
174
296
|
signal: controller.signal,
|
|
175
297
|
}, parentStream, // uiStream: agent_start/end → main feed
|
|
176
298
|
transcriptSink)
|
|
@@ -262,22 +384,48 @@ export async function agentTool(args, ctx) {
|
|
|
262
384
|
].join("\n");
|
|
263
385
|
}
|
|
264
386
|
// ─── Synchronous path ──────────────────────────────────────────
|
|
387
|
+
// No wall-clock timeout on the sub-agent lifecycle — that matches Claude
|
|
388
|
+
// Code / Codex, where a sub-agent (Task) is bounded by maxTurns + per-tool
|
|
389
|
+
// timeouts (Bash etc. carry their own) + parent/user abort, NOT by a global
|
|
390
|
+
// countdown that kills legitimate heavy work mid-task. The old
|
|
391
|
+
// runWithTimeout(5min) both (a) murdered a "read 40 files" agent at 5:00 and
|
|
392
|
+
// (b) raced with the normal completion path: the timeout aborted the child,
|
|
393
|
+
// the catch emitted agent_end{error}, AND the aborted child still let
|
|
394
|
+
// runSubAgent reach its line-280 agent_end{text} — two agent_end events for
|
|
395
|
+
// one agent, the later text one overwriting the error so a timed-out agent
|
|
396
|
+
// rendered as "done". Dropping the timeout removes the race entirely: a
|
|
397
|
+
// genuine spawn error throws BEFORE the success emit, so only the catch
|
|
398
|
+
// fires; a clean run emits agent_end{text} exactly once. (fix:
|
|
399
|
+
// subagent-timeout-double-agent-end)
|
|
400
|
+
const syncController = new AbortController();
|
|
401
|
+
const onParentAbort = () => syncController.abort();
|
|
402
|
+
parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
265
403
|
try {
|
|
266
404
|
return await runSubAgent(spawner, {
|
|
267
405
|
agentId,
|
|
268
406
|
name,
|
|
269
407
|
description,
|
|
408
|
+
agentType: overrides.resolvedType,
|
|
270
409
|
prompt,
|
|
271
410
|
maxTurns,
|
|
272
|
-
|
|
411
|
+
model: overrides.model,
|
|
412
|
+
toolAllowlist: overrides.toolAllowlist,
|
|
413
|
+
appendSystemPrompt: overrides.appendSystemPrompt,
|
|
414
|
+
readOnlySession: overrides.resolvedType === "researcher" || overrides.resolvedType === "explorer",
|
|
415
|
+
hooks: ctx?.hooks,
|
|
416
|
+
signal: syncController.signal,
|
|
273
417
|
});
|
|
274
418
|
}
|
|
275
419
|
catch (err) {
|
|
276
|
-
|
|
420
|
+
emitSubAgentHook(ctx?.hooks, "subagent_error", { agentId, description, error: err.message });
|
|
421
|
+
safeEmit(parentStream, { type: "agent_end", agentId, name, description, error: err.message, agentType: overrides.resolvedType });
|
|
277
422
|
if (parentSignal?.aborted)
|
|
278
423
|
return "Agent was aborted.";
|
|
279
424
|
return `Agent error: ${err.message}`;
|
|
280
425
|
}
|
|
426
|
+
finally {
|
|
427
|
+
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
428
|
+
}
|
|
281
429
|
}
|
|
282
430
|
// ─── AgentStatus / AgentCancel — companions to run_in_background ─
|
|
283
431
|
export const agentStatusToolDef = {
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
import { fileCache } from "../file-cache.js";
|
|
18
18
|
import { applyPatch } from "./applier.js";
|
|
19
19
|
import { parsePatch } from "./parser.js";
|
|
20
|
+
import { enforcePathPolicy } from "../../path-policy.js";
|
|
21
|
+
import { resolve as resolvePath } from "node:path";
|
|
20
22
|
export const applyPatchToolDef = {
|
|
21
23
|
name: "ApplyPatch",
|
|
22
24
|
description: "Apply a V4A-format patch atomically across one or more files. " +
|
|
@@ -68,6 +70,20 @@ export async function applyPatchTool(args, ctx) {
|
|
|
68
70
|
// A4: resolve relative patch paths against the Engine's cwd, not the
|
|
69
71
|
// host process cwd. See standard §S5.
|
|
70
72
|
const cwd = ctx?.cwd ?? process.cwd();
|
|
73
|
+
// Path-policy gate every target before any IO. A multi-file patch is
|
|
74
|
+
// atomic — if *any* target is blocked the whole patch must reject so we
|
|
75
|
+
// don't end up writing some files and refusing others.
|
|
76
|
+
for (const hunk of parsed.hunks) {
|
|
77
|
+
const targets = [resolvePath(cwd, hunk.path)];
|
|
78
|
+
if (hunk.kind === "update" && hunk.movePath) {
|
|
79
|
+
targets.push(resolvePath(cwd, hunk.movePath));
|
|
80
|
+
}
|
|
81
|
+
for (const t of targets) {
|
|
82
|
+
const blocked = enforcePathPolicy(t, "write", cwd);
|
|
83
|
+
if (blocked)
|
|
84
|
+
return blocked;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
71
87
|
let result;
|
|
72
88
|
try {
|
|
73
89
|
result = await applyPatch(parsed.hunks, { cwd });
|
|
@@ -75,11 +91,14 @@ export async function applyPatchTool(args, ctx) {
|
|
|
75
91
|
catch (err) {
|
|
76
92
|
return `Error applying patch: ${err.message}`;
|
|
77
93
|
}
|
|
78
|
-
// Invalidate file cache for every touched path.
|
|
94
|
+
// Invalidate file cache for every touched path. The cache is keyed by
|
|
95
|
+
// ABSOLUTE path (see file-cache.ts), and patch paths are relative to the
|
|
96
|
+
// Engine cwd — resolve them the same way the path-policy gate did above, or
|
|
97
|
+
// the stale entry survives and a later Read returns pre-patch content.
|
|
79
98
|
for (const hunk of parsed.hunks) {
|
|
80
|
-
fileCache.invalidate(hunk.path);
|
|
99
|
+
fileCache.invalidate(resolvePath(cwd, hunk.path));
|
|
81
100
|
if (hunk.kind === "update" && hunk.movePath) {
|
|
82
|
-
fileCache.invalidate(hunk.movePath);
|
|
101
|
+
fileCache.invalidate(resolvePath(cwd, hunk.movePath));
|
|
83
102
|
}
|
|
84
103
|
}
|
|
85
104
|
const parts = [];
|
|
@@ -123,9 +123,8 @@ function resolveParticipant(nameOrPath, llmConfig, pool) {
|
|
|
123
123
|
maxTokens: preset.maxOutputTokens,
|
|
124
124
|
baseUrl: fallbackBaseUrl,
|
|
125
125
|
apiKey: fallbackApiKey,
|
|
126
|
-
temperature: ARENA_TEMPERATURE,
|
|
127
|
-
enableStreaming: false,
|
|
128
126
|
},
|
|
127
|
+
clientDefaults: { temperature: ARENA_TEMPERATURE },
|
|
129
128
|
};
|
|
130
129
|
}
|
|
131
130
|
// (3) Raw model path
|
|
@@ -138,9 +137,8 @@ function resolveParticipant(nameOrPath, llmConfig, pool) {
|
|
|
138
137
|
maxTokens: getMaxOutputTokens(nameOrPath),
|
|
139
138
|
baseUrl: fallbackBaseUrl,
|
|
140
139
|
apiKey: fallbackApiKey,
|
|
141
|
-
temperature: ARENA_TEMPERATURE,
|
|
142
|
-
enableStreaming: false,
|
|
143
140
|
},
|
|
141
|
+
clientDefaults: { temperature: ARENA_TEMPERATURE },
|
|
144
142
|
};
|
|
145
143
|
}
|
|
146
144
|
function participantFromPool(entry, llmConfig) {
|
|
@@ -156,9 +154,8 @@ function participantFromPool(entry, llmConfig) {
|
|
|
156
154
|
maxTokens: entry.maxOutputTokens ?? getMaxOutputTokens(entry.model),
|
|
157
155
|
baseUrl: entry.baseUrl ?? llmConfig.baseUrl,
|
|
158
156
|
apiKey: entry.apiKey ?? llmConfig.apiKey,
|
|
159
|
-
temperature: ARENA_TEMPERATURE,
|
|
160
|
-
enableStreaming: false,
|
|
161
157
|
},
|
|
158
|
+
clientDefaults: { temperature: ARENA_TEMPERATURE },
|
|
162
159
|
};
|
|
163
160
|
}
|
|
164
161
|
/**
|
|
@@ -411,6 +408,8 @@ export async function arenaTool(args, ctx) {
|
|
|
411
408
|
catch (err) {
|
|
412
409
|
if (signal?.aborted)
|
|
413
410
|
return "Arena aborted.";
|
|
414
|
-
|
|
411
|
+
// err may be a non-Error (string/null); `(err as Error).message` would be
|
|
412
|
+
// undefined → "Arena error: undefined", masking the real cause.
|
|
413
|
+
return `Arena error: ${err instanceof Error ? err.message : String(err)}`;
|
|
415
414
|
}
|
|
416
415
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* complete_goal — Goal mode P0.
|
|
3
|
+
*
|
|
4
|
+
* The model calls this to explicitly DECLARE the current goal complete
|
|
5
|
+
* (mirrors Codex's update_goal(status=Complete)). When the turn loop sees
|
|
6
|
+
* this tool call it short-circuits to a "completed" stop WITHOUT running the
|
|
7
|
+
* judge LLM (wired in Task 4). This tool itself just records an
|
|
8
|
+
* acknowledgement string into the transcript; the loop-level short-circuit
|
|
9
|
+
* lives in turn-loop.ts.
|
|
10
|
+
*
|
|
11
|
+
* Shape note: builtin tools in this codebase are a `ToolDefinition`
|
|
12
|
+
* (name/description/inputSchema only — see types.ts) plus a separate executor
|
|
13
|
+
* `(args: Record<string, unknown>) => Promise<string>`. The runtime metadata
|
|
14
|
+
* (source/permissionDefault/isReadOnly/...) is attached in builtin/index.ts at
|
|
15
|
+
* registration time, NOT on the def. Registration is Task 6.
|
|
16
|
+
*/
|
|
17
|
+
import type { ToolDefinition } from "../../types.js";
|
|
18
|
+
/**
|
|
19
|
+
* The tool name, shared with turn-loop.ts's short-circuit check so a rename
|
|
20
|
+
* can't silently break completion detection (the name lives in exactly one
|
|
21
|
+
* place).
|
|
22
|
+
*/
|
|
23
|
+
export declare const COMPLETE_GOAL_TOOL_NAME = "complete_goal";
|
|
24
|
+
export declare const completeGoalToolDef: ToolDefinition;
|
|
25
|
+
export declare function completeGoalTool(args: Record<string, unknown>): Promise<string>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* complete_goal — Goal mode P0.
|
|
3
|
+
*
|
|
4
|
+
* The model calls this to explicitly DECLARE the current goal complete
|
|
5
|
+
* (mirrors Codex's update_goal(status=Complete)). When the turn loop sees
|
|
6
|
+
* this tool call it short-circuits to a "completed" stop WITHOUT running the
|
|
7
|
+
* judge LLM (wired in Task 4). This tool itself just records an
|
|
8
|
+
* acknowledgement string into the transcript; the loop-level short-circuit
|
|
9
|
+
* lives in turn-loop.ts.
|
|
10
|
+
*
|
|
11
|
+
* Shape note: builtin tools in this codebase are a `ToolDefinition`
|
|
12
|
+
* (name/description/inputSchema only — see types.ts) plus a separate executor
|
|
13
|
+
* `(args: Record<string, unknown>) => Promise<string>`. The runtime metadata
|
|
14
|
+
* (source/permissionDefault/isReadOnly/...) is attached in builtin/index.ts at
|
|
15
|
+
* registration time, NOT on the def. Registration is Task 6.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The tool name, shared with turn-loop.ts's short-circuit check so a rename
|
|
19
|
+
* can't silently break completion detection (the name lives in exactly one
|
|
20
|
+
* place).
|
|
21
|
+
*/
|
|
22
|
+
export const COMPLETE_GOAL_TOOL_NAME = "complete_goal";
|
|
23
|
+
export const completeGoalToolDef = {
|
|
24
|
+
name: COMPLETE_GOAL_TOOL_NAME,
|
|
25
|
+
description: "声明当前目标已完全达成。仅当目标确实完整完成时才调用本工具(模型在目标完全达成时显式调用以声明完成)。" +
|
|
26
|
+
"调用后应当停止。可选传入 summary 作为一句话完成总结。\n" +
|
|
27
|
+
"Declare the current goal complete. Call this ONLY when the goal is fully achieved. " +
|
|
28
|
+
"After calling, you should stop. Optionally pass a one-line summary of what was accomplished.",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
summary: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "可选:一句话总结所完成的工作。Optional one-line summary of what was accomplished.",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: [],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export async function completeGoalTool(args) {
|
|
41
|
+
const summary = typeof args.summary === "string" ? args.summary.trim() : "";
|
|
42
|
+
return summary
|
|
43
|
+
? `目标已完成 (goal complete). Summary: ${summary}`
|
|
44
|
+
: "目标已完成 (goal complete).";
|
|
45
|
+
}
|
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cron tools — CronCreate, CronDelete, CronList.
|
|
3
3
|
*/
|
|
4
|
-
import { cronScheduler } from "../../
|
|
4
|
+
import { cronScheduler } from "../../automation/scheduler.js";
|
|
5
5
|
export const cronCreateToolDef = {
|
|
6
6
|
name: "CronCreate",
|
|
7
|
-
description: "Create a scheduled
|
|
8
|
-
"
|
|
7
|
+
description: "Create a scheduled automation job that runs a prompt on a recurring schedule. " +
|
|
8
|
+
"Use this when the user asks to set up a recurring/automated/scheduled task " +
|
|
9
|
+
"(monitoring, daily reports, periodic checks, etc.).\n\n" +
|
|
10
|
+
"Translate the user's natural-language timing into the `schedule` field. Two forms:\n" +
|
|
11
|
+
" • Interval: '30s', '5m', '1h', '1d' (runs every N from creation).\n" +
|
|
12
|
+
" • Cron expression (5 fields: minute hour day-of-month month day-of-week) for " +
|
|
13
|
+
"calendar times. Examples: '0 9 * * 1-5' = 9am every weekday; '0 */6 * * *' = every 6 hours; " +
|
|
14
|
+
"'30 8 * * 1' = 8:30am every Monday. Day-of-week: 0=Sunday..6=Saturday.\n\n" +
|
|
15
|
+
"For calendar schedules, set `timezone` to the user's IANA zone (e.g. 'Asia/Shanghai', " +
|
|
16
|
+
"'America/New_York'); ask the user if unknown. Set `cwd` to the project the job operates on. " +
|
|
17
|
+
"Leave `permissionLevel` as 'read-only' unless the user explicitly wants the job to modify code.",
|
|
9
18
|
inputSchema: {
|
|
10
19
|
type: "object",
|
|
11
20
|
properties: {
|
|
12
|
-
name: { type: "string", description: "
|
|
13
|
-
schedule: {
|
|
14
|
-
|
|
21
|
+
name: { type: "string", description: "Short human-readable name for the job (e.g. '工作日晨间简报')" },
|
|
22
|
+
schedule: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Interval ('5m','1h','1d') or a 5-field cron expression ('0 9 * * 1-5'). " +
|
|
25
|
+
"Derive this from the user's described timing.",
|
|
26
|
+
},
|
|
27
|
+
prompt: { type: "string", description: "The task prompt the agent runs on each execution" },
|
|
28
|
+
timezone: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "IANA timezone for cron-expression schedules (e.g. 'Asia/Shanghai'). Optional; defaults to UTC.",
|
|
31
|
+
},
|
|
32
|
+
cwd: { type: "string", description: "Working directory / project the job runs in. Optional." },
|
|
33
|
+
permissionLevel: {
|
|
34
|
+
type: "string",
|
|
35
|
+
enum: ["read-only", "workspace-write", "full"],
|
|
36
|
+
description: "What the job may do: 'read-only' (monitoring; default), 'workspace-write' (edit files), " +
|
|
37
|
+
"'full' (edit + run git/gh to open PRs). Use the least privilege the task needs.",
|
|
38
|
+
},
|
|
15
39
|
},
|
|
16
40
|
required: ["name", "schedule", "prompt"],
|
|
17
41
|
},
|
|
@@ -22,8 +46,27 @@ export async function cronCreateTool(args) {
|
|
|
22
46
|
const prompt = args.prompt;
|
|
23
47
|
if (!name || !schedule || !prompt)
|
|
24
48
|
return "Error: name, schedule, and prompt are required";
|
|
25
|
-
const
|
|
26
|
-
|
|
49
|
+
const timezone = typeof args.timezone === "string" ? args.timezone : undefined;
|
|
50
|
+
const cwd = typeof args.cwd === "string" ? args.cwd : undefined;
|
|
51
|
+
const permissionLevel = args.permissionLevel === "read-only" ||
|
|
52
|
+
args.permissionLevel === "workspace-write" ||
|
|
53
|
+
args.permissionLevel === "full"
|
|
54
|
+
? args.permissionLevel
|
|
55
|
+
: undefined;
|
|
56
|
+
let job;
|
|
57
|
+
try {
|
|
58
|
+
job = cronScheduler.create(name, schedule, prompt, {
|
|
59
|
+
...(timezone !== undefined ? { timezone } : {}),
|
|
60
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
61
|
+
...(permissionLevel !== undefined ? { permissionLevel } : {}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
66
|
+
}
|
|
67
|
+
const tz = job.timezone ? ` (${job.timezone})` : "";
|
|
68
|
+
const next = job.nextRun ? new Date(job.nextRun).toLocaleString() : "n/a";
|
|
69
|
+
return `Cron job #${job.id} "${job.name}" created. Schedule: ${job.schedule}${tz}. Next run: ${next}.`;
|
|
27
70
|
}
|
|
28
71
|
export const cronDeleteToolDef = {
|
|
29
72
|
name: "CronDelete",
|
|
@@ -55,7 +98,8 @@ export async function cronListTool(_args) {
|
|
|
55
98
|
const lines = jobs.map((j) => {
|
|
56
99
|
const status = j.enabled ? "active" : "paused";
|
|
57
100
|
const lastRun = j.lastRun ? new Date(j.lastRun).toLocaleString() : "never";
|
|
58
|
-
|
|
101
|
+
const tz = j.timezone ? ` ${j.timezone}` : "";
|
|
102
|
+
return ` #${j.id} "${j.name}" [${status}] ${j.schedule}${tz} | runs: ${j.runCount} | last: ${lastRun}`;
|
|
59
103
|
});
|
|
60
104
|
return `Cron Jobs (${jobs.length}):\n${lines.join("\n")}`;
|
|
61
105
|
}
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* Built-in Edit file tool — exact string replacement.
|
|
3
3
|
*/
|
|
4
4
|
import type { ToolDefinition } from "../../types.js";
|
|
5
|
+
import type { ToolContext } from "../context.js";
|
|
5
6
|
export declare const editToolDef: ToolDefinition;
|
|
6
|
-
export declare function editTool(args: Record<string, unknown
|
|
7
|
+
export declare function editTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
6
|
import { fileCache } from "./file-cache.js";
|
|
7
|
+
import { enforcePathPolicy } from "../path-policy.js";
|
|
7
8
|
export const editToolDef = {
|
|
8
9
|
name: "Edit",
|
|
9
10
|
description: "Perform exact string replacements in a file. " +
|
|
@@ -30,7 +31,7 @@ export const editToolDef = {
|
|
|
30
31
|
required: ["file_path", "old_string", "new_string"],
|
|
31
32
|
},
|
|
32
33
|
};
|
|
33
|
-
export async function editTool(args) {
|
|
34
|
+
export async function editTool(args, ctx) {
|
|
34
35
|
const filePath = args.file_path;
|
|
35
36
|
const oldString = args.old_string;
|
|
36
37
|
const newString = args.new_string;
|
|
@@ -45,6 +46,10 @@ export async function editTool(args) {
|
|
|
45
46
|
return "Error: old_string and new_string must be different";
|
|
46
47
|
if (!existsSync(filePath))
|
|
47
48
|
return `Error: File not found: ${filePath}`;
|
|
49
|
+
// Path policy gate before any IO.
|
|
50
|
+
const blocked = enforcePathPolicy(filePath, "write", ctx?.cwd);
|
|
51
|
+
if (blocked)
|
|
52
|
+
return blocked;
|
|
48
53
|
try {
|
|
49
54
|
const content = await readFile(filePath, "utf-8");
|
|
50
55
|
if (!content.includes(oldString)) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in GenerateImage tool — text-to-image via the OpenAI Images API
|
|
3
|
+
* (`gpt-image-2`).
|
|
4
|
+
*
|
|
5
|
+
* Credentials come from config, not env: the tool finds the `kind: "openai"`
|
|
6
|
+
* entry in `settings.providers[]` and uses its `apiKey` + `baseUrl`. No
|
|
7
|
+
* `OPENAI_API_KEY` required, no new config — an existing OpenAI provider is
|
|
8
|
+
* reused as-is.
|
|
9
|
+
*
|
|
10
|
+
* The generated PNG is decoded from the API's base64 payload and written to
|
|
11
|
+
* `<cwd>/.code-shell/generated_images/<timestamp>.png`; the tool returns the
|
|
12
|
+
* absolute path so the model can Read or reference it on later turns. This
|
|
13
|
+
* mirrors codex's imagegen CLI fallback (`scripts/image_gen.py`): call API →
|
|
14
|
+
* take b64_json → decode → write → report path.
|
|
15
|
+
*
|
|
16
|
+
* Scope is deliberately narrow (text-to-image only). Edit/mask/transparent/
|
|
17
|
+
* batch are out of scope — see the design doc.
|
|
18
|
+
*
|
|
19
|
+
* Uses the native `fetch` (codeshell does not depend on the `openai` SDK).
|
|
20
|
+
*/
|
|
21
|
+
import type { ToolDefinition } from "../../types.js";
|
|
22
|
+
import type { ToolContext } from "../context.js";
|
|
23
|
+
export declare const generateImageToolDef: ToolDefinition;
|
|
24
|
+
export declare function isGenerateImageAvailable(cwd?: string, nowMs?: number): boolean;
|
|
25
|
+
export declare function generateImageTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
|