@cjhyy/code-shell-core 0.5.0-rc.2 → 0.6.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/README.md +13 -10
- package/dist/agent/agent-definition-registry.d.ts +3 -0
- package/dist/agent/agent-definition-registry.js +3 -1
- package/dist/agent/agent-definition.d.ts +15 -0
- package/dist/agent/agent-definition.js +27 -3
- package/dist/arena/context/context-tools.js +47 -4
- package/dist/arena/ledger.js +9 -1
- package/dist/arena/strategies/utils.d.ts +1 -4
- package/dist/arena/strategies/utils.js +7 -84
- package/dist/automation/cron-expr.d.ts +10 -0
- package/dist/automation/cron-expr.js +23 -5
- package/dist/automation/runner.d.ts +21 -4
- package/dist/automation/runner.js +48 -12
- package/dist/automation/scheduler.d.ts +38 -0
- package/dist/automation/scheduler.js +89 -12
- package/dist/automation/store.js +5 -2
- package/dist/capability-control/disabled-lists.d.ts +25 -0
- package/dist/capability-control/disabled-lists.js +57 -0
- package/dist/capability-control/overlay.d.ts +15 -0
- package/dist/capability-control/overlay.js +27 -0
- package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
- package/dist/cc-orchestrator/agent-adapter.js +133 -0
- package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
- package/dist/cc-orchestrator/cc-capability.js +53 -0
- package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
- package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
- package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
- package/dist/cc-orchestrator/codex-session-history.js +187 -0
- package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
- package/dist/cc-orchestrator/external-agent-changes.js +214 -0
- package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
- package/dist/cc-orchestrator/external-agent-driver.js +69 -0
- package/dist/cc-orchestrator/index.d.ts +8 -0
- package/dist/cc-orchestrator/index.js +8 -0
- package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
- package/dist/cc-orchestrator/relevance-judge.js +29 -0
- package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
- package/dist/cc-orchestrator/session-discovery.js +125 -0
- package/dist/cc-orchestrator/session-history.d.ts +19 -0
- package/dist/cc-orchestrator/session-history.js +67 -0
- package/dist/cli/agent-server-stdio.d.ts +15 -1
- package/dist/cli/agent-server-stdio.js +115 -17
- package/dist/cli/agent-server-tcp.js +40 -26
- package/dist/context/compaction.d.ts +56 -0
- package/dist/context/compaction.js +186 -0
- package/dist/context/manager.js +25 -4
- package/dist/cost-tracker.js +5 -61
- package/dist/credentials/cipher.d.ts +49 -0
- package/dist/credentials/cipher.js +45 -0
- package/dist/credentials/cookie-jar.d.ts +24 -0
- package/dist/credentials/cookie-jar.js +40 -0
- package/dist/credentials/index.d.ts +7 -0
- package/dist/credentials/index.js +5 -0
- package/dist/credentials/inject-credential-tool.d.ts +20 -0
- package/dist/credentials/inject-credential-tool.js +130 -0
- package/dist/credentials/store.d.ts +72 -0
- package/dist/credentials/store.js +184 -0
- package/dist/credentials/types.d.ts +56 -0
- package/dist/credentials/use-credential-tool.d.ts +29 -0
- package/dist/credentials/use-credential-tool.js +205 -0
- package/dist/credentials/use-gate.d.ts +56 -0
- package/dist/credentials/use-gate.js +52 -0
- package/dist/data/model-metadata.d.ts +77 -0
- package/dist/data/model-metadata.js +56 -0
- package/dist/data/model-metadata.json +216 -0
- package/dist/data/openrouter-models.d.ts +18 -7
- package/dist/data/openrouter-models.js +35 -8
- package/dist/engine/aux-key.d.ts +10 -0
- package/dist/engine/aux-key.js +11 -0
- package/dist/engine/dynamic-tool-defs.d.ts +19 -0
- package/dist/engine/dynamic-tool-defs.js +36 -0
- package/dist/engine/engine.d.ts +267 -138
- package/dist/engine/engine.js +957 -191
- package/dist/engine/friendly-error.d.ts +18 -0
- package/dist/engine/friendly-error.js +63 -0
- package/dist/engine/goal.d.ts +145 -0
- package/dist/engine/goal.js +144 -0
- package/dist/engine/image-policy.d.ts +13 -0
- package/dist/engine/image-policy.js +24 -0
- package/dist/engine/model-connections-pool.d.ts +17 -0
- package/dist/engine/model-connections-pool.js +66 -0
- package/dist/engine/model-facade.d.ts +10 -0
- package/dist/engine/model-facade.js +25 -0
- package/dist/engine/patch-orphaned-tools.js +3 -0
- package/dist/engine/resolve-llm-config.d.ts +16 -0
- package/dist/engine/resolve-llm-config.js +44 -0
- package/dist/engine/runtime.js +2 -1
- package/dist/engine/sandbox-cache-key.d.ts +10 -0
- package/dist/engine/sandbox-cache-key.js +9 -0
- package/dist/engine/sandbox-config.d.ts +31 -0
- package/dist/engine/sandbox-config.js +38 -0
- package/dist/engine/session-usage.d.ts +19 -0
- package/dist/engine/session-usage.js +25 -0
- package/dist/engine/steer-queue.d.ts +32 -0
- package/dist/engine/steer-queue.js +26 -0
- package/dist/engine/streaming-tool-queue.d.ts +12 -0
- package/dist/engine/streaming-tool-queue.js +50 -10
- package/dist/engine/turn-loop.d.ts +67 -3
- package/dist/engine/turn-loop.js +292 -41
- package/dist/engine/types.d.ts +175 -0
- package/dist/engine/types.js +13 -0
- package/dist/external-agents/config.d.ts +2 -0
- package/dist/external-agents/config.js +15 -0
- package/dist/external-agents/types.d.ts +31 -0
- package/dist/external-agents/types.js +1 -0
- package/dist/git/utils.js +11 -6
- package/dist/git/worktree.d.ts +48 -0
- package/dist/git/worktree.js +86 -10
- package/dist/hooks/goal-stop-hook.d.ts +28 -0
- package/dist/hooks/goal-stop-hook.js +186 -9
- package/dist/hooks/registry.d.ts +8 -0
- package/dist/hooks/registry.js +19 -0
- package/dist/hooks/shell-runner.js +10 -21
- package/dist/index.d.ts +49 -6
- package/dist/index.js +60 -4
- package/dist/llm/capabilities/param-specs.d.ts +14 -0
- package/dist/llm/capabilities/param-specs.js +62 -0
- package/dist/llm/capabilities/rules.js +4 -0
- package/dist/llm/capabilities/types.d.ts +10 -0
- package/dist/llm/client-base.d.ts +28 -1
- package/dist/llm/client-base.js +119 -13
- package/dist/llm/model-cache.js +4 -2
- package/dist/llm/model-pool.d.ts +14 -0
- package/dist/llm/model-pool.js +13 -0
- package/dist/llm/provider-auth.d.ts +41 -0
- package/dist/llm/provider-auth.js +76 -0
- package/dist/llm/provider-catalog.d.ts +4 -0
- package/dist/llm/providers/anthropic.js +64 -14
- package/dist/llm/providers/openai.d.ts +36 -0
- package/dist/llm/providers/openai.js +257 -51
- package/dist/llm/reasoning-setting.d.ts +3 -3
- package/dist/llm/reasoning-setting.js +9 -1
- package/dist/llm/stream-watchdog.js +5 -1
- package/dist/llm/token-counter.js +9 -2
- package/dist/llm/types.d.ts +4 -0
- package/dist/logging/sanitize-messages.js +22 -0
- package/dist/lsp/manager.d.ts +1 -1
- package/dist/lsp/manager.js +40 -10
- package/dist/model-catalog/builtin.d.ts +12 -0
- package/dist/model-catalog/builtin.js +407 -0
- package/dist/model-catalog/gen-connections.d.ts +20 -0
- package/dist/model-catalog/gen-connections.js +28 -0
- package/dist/model-catalog/index.d.ts +41 -0
- package/dist/model-catalog/index.js +90 -0
- package/dist/model-catalog/params.d.ts +20 -0
- package/dist/model-catalog/params.js +45 -0
- package/dist/model-catalog/resolve.d.ts +48 -0
- package/dist/model-catalog/resolve.js +33 -0
- package/dist/model-catalog/save-entry.d.ts +32 -0
- package/dist/model-catalog/save-entry.js +104 -0
- package/dist/model-catalog/types.d.ts +561 -0
- package/dist/model-catalog/types.js +93 -0
- package/dist/model-catalog/upsert.d.ts +9 -0
- package/dist/model-catalog/upsert.js +8 -0
- package/dist/onboarding.d.ts +12 -82
- package/dist/onboarding.js +61 -326
- package/dist/plugins/gitOps.d.ts +19 -0
- package/dist/plugins/gitOps.js +73 -4
- package/dist/plugins/installer/checkUpdate.d.ts +16 -0
- package/dist/plugins/installer/checkUpdate.js +32 -0
- package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
- package/dist/plugins/installer/codex/convertCommands.js +46 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +5 -2
- package/dist/plugins/installer/codex/convertMcp.js +45 -5
- package/dist/plugins/installer/install.js +25 -1
- package/dist/plugins/installer/installFromArchive.d.ts +43 -0
- package/dist/plugins/installer/installFromArchive.js +134 -0
- package/dist/plugins/installer/installFromSource.js +8 -2
- package/dist/plugins/installer/loadPluginAgents.js +6 -2
- package/dist/plugins/installer/loadPluginMcp.d.ts +9 -2
- package/dist/plugins/installer/loadPluginMcp.js +35 -2
- package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
- package/dist/plugins/installer/pruneDisabled.js +73 -0
- package/dist/plugins/installer/types.d.ts +5 -2
- package/dist/plugins/installer/types.js +1 -0
- package/dist/plugins/installer/uninstall.js +4 -0
- package/dist/plugins/installer/unzip.d.ts +14 -0
- package/dist/plugins/installer/unzip.js +82 -0
- package/dist/plugins/installer/update.d.ts +14 -0
- package/dist/plugins/installer/update.js +53 -21
- package/dist/plugins/loadPluginHooks.d.ts +47 -1
- package/dist/plugins/loadPluginHooks.js +73 -1
- package/dist/plugins/marketplaceManager.d.ts +7 -0
- package/dist/plugins/marketplaceManager.js +20 -0
- package/dist/plugins/pluginCommandHook.js +4 -18
- package/dist/plugins/pluginContent.d.ts +30 -0
- package/dist/plugins/pluginContent.js +83 -0
- package/dist/plugins/pluginInstaller.d.ts +13 -0
- package/dist/plugins/pluginInstaller.js +42 -8
- package/dist/plugins/schemas.js +1 -0
- package/dist/plugins/types.d.ts +6 -0
- package/dist/preset/index.d.ts +7 -1
- package/dist/preset/index.js +96 -6
- package/dist/product/types.d.ts +1 -1
- package/dist/prompt/composer.d.ts +27 -0
- package/dist/prompt/composer.js +67 -21
- package/dist/prompt/instruction-scanner.js +5 -3
- package/dist/prompt/section-loader.js +1 -0
- package/dist/prompt/sections/base.md +1 -0
- package/dist/prompt/sections/browser.md +10 -0
- package/dist/prompt/sections/coding.md +4 -0
- package/dist/protocol/chat-session-manager.d.ts +8 -2
- package/dist/protocol/chat-session-manager.js +36 -0
- package/dist/protocol/chat-session.d.ts +65 -0
- package/dist/protocol/chat-session.js +94 -2
- package/dist/protocol/client.d.ts +27 -0
- package/dist/protocol/client.js +38 -0
- package/dist/protocol/server.d.ts +112 -0
- package/dist/protocol/server.js +586 -16
- package/dist/protocol/transport.js +3 -2
- package/dist/protocol/types.d.ts +46 -0
- package/dist/protocol/types.js +16 -0
- package/dist/quota/credentials.d.ts +3 -0
- package/dist/quota/credentials.js +80 -0
- package/dist/quota/index.d.ts +36 -0
- package/dist/quota/index.js +155 -0
- package/dist/quota/types.d.ts +48 -0
- package/dist/quota/types.js +13 -0
- package/dist/review/review-prompt.d.ts +28 -0
- package/dist/review/review-prompt.js +81 -0
- package/dist/run/FileRunStore.js +8 -3
- package/dist/run/RunApprovalBackend.js +25 -5
- package/dist/run/RunManager.d.ts +12 -0
- package/dist/run/RunManager.js +35 -0
- package/dist/run/factory.d.ts +1 -1
- package/dist/runtime/background-shell.d.ts +139 -0
- package/dist/runtime/background-shell.js +495 -0
- package/dist/runtime/output-clean.d.ts +24 -0
- package/dist/runtime/output-clean.js +41 -0
- package/dist/runtime/ring-file.d.ts +64 -0
- package/dist/runtime/ring-file.js +174 -0
- package/dist/runtime/safe-spawn.js +15 -34
- package/dist/runtime/spawn-common.d.ts +148 -0
- package/dist/runtime/spawn-common.js +303 -0
- package/dist/runtime/truncate-output.d.ts +22 -0
- package/dist/runtime/truncate-output.js +49 -0
- package/dist/runtime/utf8-cut.d.ts +11 -0
- package/dist/runtime/utf8-cut.js +23 -0
- package/dist/services/auto-dream.d.ts +4 -0
- package/dist/services/auto-dream.js +26 -26
- package/dist/services/diagnostics.d.ts +1 -2
- package/dist/services/diagnostics.js +12 -7
- package/dist/services/extract-memories.d.ts +14 -1
- package/dist/services/extract-memories.js +45 -6
- package/dist/services/memory-orchestrator.d.ts +21 -0
- package/dist/services/memory-orchestrator.js +125 -50
- package/dist/services/session-memory.js +24 -12
- package/dist/session/file-history.d.ts +124 -1
- package/dist/session/file-history.js +222 -6
- package/dist/session/memory.d.ts +116 -2
- package/dist/session/memory.js +250 -28
- package/dist/session/session-manager.d.ts +40 -0
- package/dist/session/session-manager.js +113 -3
- package/dist/session/simple-diff.d.ts +23 -0
- package/dist/session/simple-diff.js +84 -0
- package/dist/session/transcript.d.ts +25 -1
- package/dist/session/transcript.js +35 -2
- package/dist/session/undo-target.d.ts +67 -0
- package/dist/session/undo-target.js +144 -0
- package/dist/settings/disk-defaults.d.ts +9 -2
- package/dist/settings/disk-defaults.js +11 -2
- package/dist/settings/feature-flags.d.ts +64 -0
- package/dist/settings/feature-flags.js +61 -0
- package/dist/settings/manager.d.ts +68 -1
- package/dist/settings/manager.js +266 -29
- package/dist/settings/migrate-config.d.ts +45 -0
- package/dist/settings/migrate-config.js +125 -0
- package/dist/settings/schema-export.d.ts +25 -0
- package/dist/settings/schema-export.js +38 -0
- package/dist/settings/schema.d.ts +1186 -771
- package/dist/settings/schema.js +268 -97
- package/dist/skills/scanner.d.ts +9 -0
- package/dist/skills/scanner.js +30 -2
- package/dist/stt/resolve-transcribe.d.ts +31 -0
- package/dist/stt/resolve-transcribe.js +108 -0
- package/dist/stt/transcribe.d.ts +51 -0
- package/dist/stt/transcribe.js +65 -0
- package/dist/tool-system/browser-bridge.d.ts +226 -0
- package/dist/tool-system/browser-bridge.js +163 -0
- package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
- package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
- package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
- package/dist/tool-system/builtin/agent-notifications.js +9 -3
- package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
- package/dist/tool-system/builtin/agent-output-file.js +72 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +12 -0
- package/dist/tool-system/builtin/agent-registry.js +8 -0
- package/dist/tool-system/builtin/agent.d.ts +21 -1
- package/dist/tool-system/builtin/agent.js +489 -42
- package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
- package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
- package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
- package/dist/tool-system/builtin/apply-patch/index.js +0 -15
- package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
- package/dist/tool-system/builtin/background-jobs.js +124 -0
- package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
- package/dist/tool-system/builtin/background-shell-tools.js +108 -0
- package/dist/tool-system/builtin/background-work.d.ts +67 -0
- package/dist/tool-system/builtin/background-work.js +86 -0
- package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
- package/dist/tool-system/builtin/bash-output-style.js +40 -0
- package/dist/tool-system/builtin/bash.d.ts +5 -2
- package/dist/tool-system/builtin/bash.js +98 -62
- package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
- package/dist/tool-system/builtin/browser-tools.js +312 -0
- package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
- package/dist/tool-system/builtin/cancel-goal.js +64 -0
- package/dist/tool-system/builtin/check-quota.d.ts +15 -0
- package/dist/tool-system/builtin/check-quota.js +34 -0
- package/dist/tool-system/builtin/config.js +7 -0
- package/dist/tool-system/builtin/cron.d.ts +7 -0
- package/dist/tool-system/builtin/cron.js +55 -3
- package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
- package/dist/tool-system/builtin/drive-claude-code.js +157 -0
- package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
- package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
- package/dist/tool-system/builtin/edit.js +20 -16
- package/dist/tool-system/builtin/eol.d.ts +29 -0
- package/dist/tool-system/builtin/eol.js +37 -0
- package/dist/tool-system/builtin/file-cache.d.ts +6 -0
- package/dist/tool-system/builtin/file-cache.js +8 -0
- package/dist/tool-system/builtin/generate-image.d.ts +35 -0
- package/dist/tool-system/builtin/generate-image.js +278 -50
- package/dist/tool-system/builtin/generate-video.d.ts +55 -0
- package/dist/tool-system/builtin/generate-video.js +361 -0
- package/dist/tool-system/builtin/glob.js +0 -7
- package/dist/tool-system/builtin/grep.js +6 -8
- package/dist/tool-system/builtin/image-providers.d.ts +86 -0
- package/dist/tool-system/builtin/image-providers.js +190 -0
- package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
- package/dist/tool-system/builtin/image-uploader.js +74 -0
- package/dist/tool-system/builtin/index.d.ts +7 -1
- package/dist/tool-system/builtin/index.js +249 -25
- package/dist/tool-system/builtin/mcp-tools.js +23 -3
- package/dist/tool-system/builtin/memory.js +45 -7
- package/dist/tool-system/builtin/notebook-edit.js +0 -7
- package/dist/tool-system/builtin/powershell.js +4 -1
- package/dist/tool-system/builtin/read.js +10 -10
- package/dist/tool-system/builtin/repl.js +4 -1
- package/dist/tool-system/builtin/skill.js +9 -0
- package/dist/tool-system/builtin/sleep.js +3 -2
- package/dist/tool-system/builtin/tool-search.js +25 -7
- package/dist/tool-system/builtin/video-providers.d.ts +154 -0
- package/dist/tool-system/builtin/video-providers.js +235 -0
- package/dist/tool-system/builtin/web-fetch.js +12 -2
- package/dist/tool-system/builtin/web-search.js +21 -7
- package/dist/tool-system/builtin/worktree.d.ts +2 -1
- package/dist/tool-system/builtin/worktree.js +31 -4
- package/dist/tool-system/builtin/write.js +0 -6
- package/dist/tool-system/context.d.ts +154 -6
- package/dist/tool-system/executor.d.ts +3 -1
- package/dist/tool-system/executor.js +172 -88
- package/dist/tool-system/investigation-guard.js +1 -1
- package/dist/tool-system/mcp-manager.d.ts +42 -4
- package/dist/tool-system/mcp-manager.js +186 -20
- package/dist/tool-system/path-policy.d.ts +5 -0
- package/dist/tool-system/path-policy.js +307 -8
- package/dist/tool-system/permission.d.ts +36 -1
- package/dist/tool-system/permission.js +281 -69
- package/dist/tool-system/plan-mode-allowlist.d.ts +13 -2
- package/dist/tool-system/plan-mode-allowlist.js +24 -2
- package/dist/tool-system/registry.d.ts +1 -0
- package/dist/tool-system/registry.js +16 -2
- package/dist/tool-system/sandbox/index.d.ts +8 -0
- package/dist/tool-system/sandbox/index.js +7 -2
- package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
- package/dist/tool-system/validate-tool-metadata.js +63 -0
- package/dist/types.d.ts +217 -6
- package/dist/updater.js +20 -9
- package/dist/utils/envUtils.d.ts +0 -9
- package/dist/utils/envUtils.js +3 -28
- package/dist/utils/exec.d.ts +40 -0
- package/dist/utils/exec.js +144 -0
- package/dist/utils/json.d.ts +12 -0
- package/dist/utils/json.js +92 -0
- package/dist/utils/theme.d.ts +1 -1
- package/dist/utils/theme.js +1 -1
- package/dist/utils/toolDisplay.js +0 -1
- package/package.json +13 -7
- package/dist/agent/coordinator.d.ts +0 -49
- package/dist/agent/coordinator.js +0 -77
- package/dist/settings/manager.test.js +0 -73
- package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
- package/dist/tool-system/builtin/remote-trigger.js +0 -54
- package/dist/tool-system/builtin/send-message.d.ts +0 -6
- package/dist/tool-system/builtin/send-message.js +0 -47
- /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* Supports onStream for real-time output passthrough.
|
|
8
8
|
*/
|
|
9
9
|
import { asyncAgentRegistry, MAX_BACKGROUND_AGENTS } from "./agent-registry.js";
|
|
10
|
+
import { ensureAgentHeartbeat } from "./agent-heartbeat.js";
|
|
11
|
+
import { writeAgentOutputFile } from "./agent-output-file.js";
|
|
10
12
|
import { createTranscriptTranslator } from "./agent-transcript-translator.js";
|
|
11
13
|
import { notificationQueue } from "./agent-notifications.js";
|
|
12
14
|
import { nanoid } from "nanoid";
|
|
@@ -66,9 +68,32 @@ export function resolveAgentTypeOverrides(agentType, registry) {
|
|
|
66
68
|
model: def.model,
|
|
67
69
|
maxTurns: def.maxTurns,
|
|
68
70
|
toolAllowlist: def.tools,
|
|
71
|
+
skillAllowlist: namespacePluginSkills(def.skills, def.source, def.pluginName),
|
|
69
72
|
appendSystemPrompt: def.systemPrompt,
|
|
70
73
|
};
|
|
71
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* A plugin-bundled agent's frontmatter lists its own skills by BARE name
|
|
77
|
+
* (`skills: director-skill`), matching Claude Code's convention where a
|
|
78
|
+
* plugin agent references same-plugin skills unqualified. But the skill
|
|
79
|
+
* scanner registers plugin skills under their NAMESPACED name
|
|
80
|
+
* (`mimi-video:director-skill`), and the allowlist filter is an exact match —
|
|
81
|
+
* so a bare entry never matches and the sub-agent reports the skill "not
|
|
82
|
+
* found", forcing it to fall back to reading SKILL.md by hand.
|
|
83
|
+
*
|
|
84
|
+
* Bridge the two conventions: for a plugin-source agent, rewrite each bare
|
|
85
|
+
* skill name to `<pluginName>:<skill>`. Names that already carry a namespace
|
|
86
|
+
* (contain ":") are left untouched, so an agent may still reference another
|
|
87
|
+
* plugin's skill explicitly. undefined (inherit full pool) and [] (no skills)
|
|
88
|
+
* pass through unchanged. Non-plugin agents are never rewritten.
|
|
89
|
+
*/
|
|
90
|
+
export function namespacePluginSkills(skills, source, pluginName) {
|
|
91
|
+
if (skills === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (source !== "plugin" || !pluginName)
|
|
94
|
+
return skills;
|
|
95
|
+
return skills.map((s) => (s.includes(":") ? s : `${pluginName}:${s}`));
|
|
96
|
+
}
|
|
72
97
|
/**
|
|
73
98
|
* Render the "Available agent types" block injected into the Agent tool's
|
|
74
99
|
* description, listing the roles defined in .code-shell/agents/*.md so the
|
|
@@ -84,7 +109,12 @@ export function buildAgentTypesBlock(registry) {
|
|
|
84
109
|
return "";
|
|
85
110
|
const lines = defs.map((d) => {
|
|
86
111
|
const tools = d.tools && d.tools.length > 0 ? d.tools.join(", ") : "all parent tools";
|
|
87
|
-
|
|
112
|
+
// Only surface skills when the role restricts them — most roles inherit
|
|
113
|
+
// the full pool and listing "all skills" everywhere is noise.
|
|
114
|
+
const skillsNote = d.skills !== undefined
|
|
115
|
+
? `; skills: ${d.skills.length > 0 ? d.skills.join(", ") : "none"}`
|
|
116
|
+
: "";
|
|
117
|
+
return `- ${d.name}: ${d.description} (tools: ${tools}${skillsNote})`;
|
|
88
118
|
});
|
|
89
119
|
return [
|
|
90
120
|
"",
|
|
@@ -101,9 +131,28 @@ export function buildAgentTypesBlock(registry) {
|
|
|
101
131
|
*/
|
|
102
132
|
export function agentToolDefWithTypes(registry) {
|
|
103
133
|
const block = buildAgentTypesBlock(registry);
|
|
104
|
-
|
|
134
|
+
const names = registry?.list().map((d) => d.name) ?? [];
|
|
135
|
+
if (!block && names.length === 0)
|
|
105
136
|
return agentToolDef;
|
|
106
|
-
|
|
137
|
+
// Constrain agent_type to the loaded kind names so the model can't invent a
|
|
138
|
+
// role that doesn't exist (resolveAgentTypeOverrides would throw, wasting a
|
|
139
|
+
// turn). A free string still slipped through before; an enum surfaces the
|
|
140
|
+
// valid set directly in the schema the model sees. We rebuild the property
|
|
141
|
+
// (not mutate the shared const) so each engine's registry stays isolated.
|
|
142
|
+
const baseProps = agentToolDef.inputSchema.properties ?? {};
|
|
143
|
+
const baseAgentType = baseProps.agent_type ?? {};
|
|
144
|
+
const inputSchema = {
|
|
145
|
+
...agentToolDef.inputSchema,
|
|
146
|
+
properties: {
|
|
147
|
+
...baseProps,
|
|
148
|
+
agent_type: names.length > 0 ? { ...baseAgentType, enum: names } : baseAgentType,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
...agentToolDef,
|
|
153
|
+
description: block ? agentToolDef.description + "\n" + block : agentToolDef.description,
|
|
154
|
+
inputSchema,
|
|
155
|
+
};
|
|
107
156
|
}
|
|
108
157
|
/**
|
|
109
158
|
* Emit a sub-agent lifecycle event via the existing `notification` hook,
|
|
@@ -132,7 +181,10 @@ export const agentToolDef = {
|
|
|
132
181
|
"You can optionally run an agent in the background using the run_in_background " +
|
|
133
182
|
"parameter. When it runs in the background, you will be automatically notified when " +
|
|
134
183
|
"it completes — do NOT sleep, poll, or proactively check on its progress. Continue " +
|
|
135
|
-
"with other work or respond to the user instead."
|
|
184
|
+
"with other work or respond to the user instead. " +
|
|
185
|
+
"Even a synchronous agent that runs longer than ~2 minutes is automatically moved to " +
|
|
186
|
+
"the background and notifies you on completion the same way — so a long delegation never " +
|
|
187
|
+
"stalls you; just continue or end your turn when told it has moved to the background.",
|
|
136
188
|
inputSchema: {
|
|
137
189
|
type: "object",
|
|
138
190
|
properties: {
|
|
@@ -175,6 +227,20 @@ export const agentToolDef = {
|
|
|
175
227
|
required: ["description", "prompt"],
|
|
176
228
|
},
|
|
177
229
|
};
|
|
230
|
+
/**
|
|
231
|
+
* Auto-background threshold (ms): a synchronous agent still running after this
|
|
232
|
+
* long is detached into the background (not killed) so the main turn isn't
|
|
233
|
+
* blocked for up to the 30min tool cap (TODO 4.1). Default 120s, matching
|
|
234
|
+
* Claude Code; overridable via CODE_SHELL_AGENT_BG_MS for tests. The agent
|
|
235
|
+
* keeps running on the same signal — we just stop awaiting it inline and let it
|
|
236
|
+
* notify on finish. Shared by the first-spawn (agentTool) and continuation
|
|
237
|
+
* (agentSendInputTool) paths.
|
|
238
|
+
*/
|
|
239
|
+
function resolveAutoBgMs() {
|
|
240
|
+
const raw = process.env.CODE_SHELL_AGENT_BG_MS;
|
|
241
|
+
const n = raw !== undefined ? Number(raw) : NaN;
|
|
242
|
+
return Number.isFinite(n) && n >= 0 ? n : 120_000;
|
|
243
|
+
}
|
|
178
244
|
/**
|
|
179
245
|
* Build and run a sub-agent. Returns the produced text or throws.
|
|
180
246
|
* Used by both the synchronous and background paths.
|
|
@@ -203,7 +269,7 @@ streamOverride) {
|
|
|
203
269
|
// that no longer exists. The child Engine is a fresh instance; plan-mode
|
|
204
270
|
// isolation between parent and child is enforced via separate Engine
|
|
205
271
|
// instances (finalized in T6).
|
|
206
|
-
const text = await spawner.spawn({ ...opts, streamOverride });
|
|
272
|
+
const { text } = await spawner.spawn({ ...opts, streamOverride });
|
|
207
273
|
const finalText = text || `Agent completed but produced no text output.`;
|
|
208
274
|
safeEmit(startEndSink, { type: "agent_end", agentId, name, description, text: finalText, agentType });
|
|
209
275
|
emitSubAgentHook(opts.hooks, "subagent_finish", { agentId, description, text: finalText });
|
|
@@ -213,7 +279,9 @@ export async function agentTool(args, ctx) {
|
|
|
213
279
|
const prompt = args.prompt;
|
|
214
280
|
const description = args.description || "sub-agent";
|
|
215
281
|
const rawName = args.name?.trim();
|
|
216
|
-
|
|
282
|
+
// Resolved below: when the model omits `name`, fall back to the resolved
|
|
283
|
+
// kind name so the dock shows a meaningful label instead of bare "Agent".
|
|
284
|
+
let name = rawName && rawName.length > 0 ? rawName : undefined;
|
|
217
285
|
if (!prompt)
|
|
218
286
|
return "Error: prompt is required";
|
|
219
287
|
if (!ctx?.subAgentSpawner) {
|
|
@@ -240,10 +308,18 @@ export async function agentTool(args, ctx) {
|
|
|
240
308
|
catch (err) {
|
|
241
309
|
return `Error: ${err.message}`;
|
|
242
310
|
}
|
|
311
|
+
// Dock label fallback: an omitted `name` defaults to the resolved kind
|
|
312
|
+
// (e.g. "explorer") so background agents are identifiable in the dock even
|
|
313
|
+
// when the model didn't pass an explicit label. True ephemeral agents
|
|
314
|
+
// (empty registry, no resolvedType) keep name undefined → dock shows "Agent".
|
|
315
|
+
if (name === undefined && overrides.resolvedType) {
|
|
316
|
+
name = overrides.resolvedType;
|
|
317
|
+
}
|
|
243
318
|
const maxTurns = args.max_turns || overrides.maxTurns || 15;
|
|
244
319
|
const runInBackground = args.run_in_background === true;
|
|
245
320
|
const agentId = nanoid(8);
|
|
246
321
|
const parentStream = spawner.parentStream;
|
|
322
|
+
const autoBgMs = resolveAutoBgMs();
|
|
247
323
|
// ─── Background path ───────────────────────────────────────────
|
|
248
324
|
// Register the agent, kick off execution detached from the current turn,
|
|
249
325
|
// return immediately with the agent_id. Parent abort does NOT cascade
|
|
@@ -267,6 +343,8 @@ export async function agentTool(args, ctx) {
|
|
|
267
343
|
startedAt: Date.now(),
|
|
268
344
|
abort: () => controller.abort(),
|
|
269
345
|
});
|
|
346
|
+
// B: liveness heartbeat for this explicit background agent too.
|
|
347
|
+
ensureAgentHeartbeat();
|
|
270
348
|
// Translate this background agent's per-event stream into ChatEntry-
|
|
271
349
|
// shaped rows inside its own transcript. The dock detail view reuses
|
|
272
350
|
// App.renderEntry which only understands ChatEntry types — storing
|
|
@@ -290,6 +368,7 @@ export async function agentTool(args, ctx) {
|
|
|
290
368
|
maxTurns,
|
|
291
369
|
model: overrides.model,
|
|
292
370
|
toolAllowlist: overrides.toolAllowlist,
|
|
371
|
+
skillAllowlist: overrides.skillAllowlist,
|
|
293
372
|
appendSystemPrompt: overrides.appendSystemPrompt,
|
|
294
373
|
readOnlySession: overrides.resolvedType === "researcher" || overrides.resolvedType === "explorer",
|
|
295
374
|
hooks: ctx?.hooks,
|
|
@@ -298,6 +377,15 @@ export async function agentTool(args, ctx) {
|
|
|
298
377
|
transcriptSink)
|
|
299
378
|
.then((text) => {
|
|
300
379
|
asyncAgentRegistry.markCompleted(agentId);
|
|
380
|
+
// External-readable copy of the result (tail / cross-session history).
|
|
381
|
+
// Best-effort; never blocks the completion path.
|
|
382
|
+
void writeAgentOutputFile(agentId, {
|
|
383
|
+
status: "completed",
|
|
384
|
+
body: text,
|
|
385
|
+
description,
|
|
386
|
+
name,
|
|
387
|
+
onError: (m, meta) => logger.warn(m, meta),
|
|
388
|
+
});
|
|
301
389
|
// B2 / Gate 1: attribute completion to the session that spawned
|
|
302
390
|
// this agent so concurrent sessions don't drain each other's
|
|
303
391
|
// notifications. Engine.run() always populates ctx.sessionId; the
|
|
@@ -334,6 +422,19 @@ export async function agentTool(args, ctx) {
|
|
|
334
422
|
});
|
|
335
423
|
})
|
|
336
424
|
.catch((err) => {
|
|
425
|
+
// runSubAgent emits its terminal agent_end only on the RESOLVE path
|
|
426
|
+
// (line ~344). On fail/cancel it threw before that, so the main-feed
|
|
427
|
+
// card started by agent_start is still 'working' — seal it here or the
|
|
428
|
+
// TUI spins forever. Emit once for BOTH branches (mirrors the sync→bg
|
|
429
|
+
// detach path ~885), then branch on abort for the notification only.
|
|
430
|
+
safeEmit(parentStream, {
|
|
431
|
+
type: "agent_end",
|
|
432
|
+
agentId,
|
|
433
|
+
name,
|
|
434
|
+
description,
|
|
435
|
+
error: err.message,
|
|
436
|
+
agentType: overrides.resolvedType,
|
|
437
|
+
});
|
|
337
438
|
if (controller.signal.aborted) {
|
|
338
439
|
// User-initiated cancel: mark status but do NOT enqueue —
|
|
339
440
|
// the main agent doesn't need a follow-up turn. Dock still
|
|
@@ -348,6 +449,13 @@ export async function agentTool(args, ctx) {
|
|
|
348
449
|
return;
|
|
349
450
|
}
|
|
350
451
|
asyncAgentRegistry.markFailed(agentId);
|
|
452
|
+
void writeAgentOutputFile(agentId, {
|
|
453
|
+
status: "failed",
|
|
454
|
+
body: err.message,
|
|
455
|
+
description,
|
|
456
|
+
name,
|
|
457
|
+
onError: (m, meta) => logger.warn(m, meta),
|
|
458
|
+
});
|
|
351
459
|
if (ctx?.sessionId) {
|
|
352
460
|
notificationQueue.enqueue({
|
|
353
461
|
agentId,
|
|
@@ -383,38 +491,159 @@ export async function agentTool(args, ctx) {
|
|
|
383
491
|
`If you need to stop it: AgentCancel(agent_id="${agentId}").`,
|
|
384
492
|
].join("\n");
|
|
385
493
|
}
|
|
386
|
-
// ─── Synchronous path
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
494
|
+
// ─── Synchronous path (with auto-background handoff) ────────────
|
|
495
|
+
return runSyncSubAgent({
|
|
496
|
+
spawner,
|
|
497
|
+
agentId,
|
|
498
|
+
name,
|
|
499
|
+
description,
|
|
500
|
+
prompt,
|
|
501
|
+
maxTurns,
|
|
502
|
+
overrides,
|
|
503
|
+
autoBgMs,
|
|
504
|
+
parentSignal,
|
|
505
|
+
parentStream,
|
|
506
|
+
ctx,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Record a finished SYNCHRONOUS sub-agent in the registry so AgentSendInput's
|
|
511
|
+
* fast path can later find it — its agentType (for role-scope re-resolution on
|
|
512
|
+
* resume) and dock label. Background agents already register on spawn; sync
|
|
513
|
+
* agents historically didn't (they returned text and were forgotten), which
|
|
514
|
+
* left continuation unable to recover the role's tool/skill constraints.
|
|
515
|
+
*
|
|
516
|
+
* This is a SILENT entry: status already `completed`, no notification enqueued,
|
|
517
|
+
* so it doesn't flash a dock card or wake the parent. It's pure lookup state.
|
|
518
|
+
* Keyed by agentId === childSid, so it agrees with the on-disk session that the
|
|
519
|
+
* cross-restart disk-probe finds. Skipped when the agent moved to the
|
|
520
|
+
* background (that path registers its own live entry) or was aborted.
|
|
521
|
+
*/
|
|
522
|
+
function recordCompletedSyncAgent(opts) {
|
|
523
|
+
// Don't clobber a live/handed-off entry of the same id.
|
|
524
|
+
if (asyncAgentRegistry.get(opts.agentId))
|
|
525
|
+
return;
|
|
526
|
+
const now = Date.now();
|
|
527
|
+
asyncAgentRegistry.register({
|
|
528
|
+
agentId: opts.agentId,
|
|
529
|
+
name: opts.name,
|
|
530
|
+
agentType: opts.agentType,
|
|
531
|
+
description: opts.description,
|
|
532
|
+
sessionId: opts.parentSessionId,
|
|
533
|
+
// agent_id === childSid: the resumable session lives at sessions/<agentId>/.
|
|
534
|
+
childSessionId: opts.agentId,
|
|
535
|
+
status: "completed",
|
|
536
|
+
startedAt: now,
|
|
537
|
+
finishedAt: now,
|
|
538
|
+
// No fade window / no notification: this entry exists only so
|
|
539
|
+
// AgentSendInput can resolve the role on continuation, not to render.
|
|
540
|
+
abort: () => { },
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* The synchronous sub-agent run path, shared by `agentTool` (first spawn) and
|
|
545
|
+
* `agentSendInputTool` (resume/continuation). Races the run against the
|
|
546
|
+
* auto-background timer and hands off to the background registry if it runs
|
|
547
|
+
* long. `resumeSessionId`, when set, resumes an existing child session
|
|
548
|
+
* (transcript replay) instead of cold-starting.
|
|
549
|
+
*
|
|
550
|
+
* No wall-clock timeout on the lifecycle — matches Claude Code / Codex, where a
|
|
551
|
+
* sub-agent is bounded by maxTurns + per-tool timeouts + parent/user abort, NOT
|
|
552
|
+
* a global countdown that kills legitimate heavy work mid-task. (The old
|
|
553
|
+
* runWithTimeout(5min) murdered slow agents AND raced the completion path into
|
|
554
|
+
* a double agent_end; dropping it removed the race — fix
|
|
555
|
+
* subagent-timeout-double-agent-end.)
|
|
556
|
+
*/
|
|
557
|
+
async function runSyncSubAgent(args) {
|
|
558
|
+
const { spawner, agentId, name, description, prompt, maxTurns, overrides, autoBgMs, parentSignal, parentStream, ctx, resumeSessionId, } = args;
|
|
400
559
|
const syncController = new AbortController();
|
|
401
560
|
const onParentAbort = () => syncController.abort();
|
|
402
561
|
parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
562
|
+
// Per-call flag (NOT module-level): true once we've detached the agent into
|
|
563
|
+
// the background, so the finally block leaves the parent-abort listener with
|
|
564
|
+
// the background handlers instead of removing it.
|
|
565
|
+
let handedOff = false;
|
|
566
|
+
// Run the agent, but don't necessarily await it to completion: race the run
|
|
567
|
+
// against the auto-background timer. The run promise is shared between the
|
|
568
|
+
// inline await and (if we hand off) the background completion handlers, so
|
|
569
|
+
// the agent is never started twice and never killed by the handoff.
|
|
570
|
+
const runPromise = runSubAgent(spawner, {
|
|
571
|
+
agentId,
|
|
572
|
+
name,
|
|
573
|
+
description,
|
|
574
|
+
agentType: overrides.resolvedType,
|
|
575
|
+
prompt,
|
|
576
|
+
maxTurns,
|
|
577
|
+
model: overrides.model,
|
|
578
|
+
toolAllowlist: overrides.toolAllowlist,
|
|
579
|
+
skillAllowlist: overrides.skillAllowlist,
|
|
580
|
+
appendSystemPrompt: overrides.appendSystemPrompt,
|
|
581
|
+
readOnlySession: overrides.resolvedType === "researcher" || overrides.resolvedType === "explorer",
|
|
582
|
+
resumeSessionId,
|
|
583
|
+
hooks: ctx?.hooks,
|
|
584
|
+
signal: syncController.signal,
|
|
585
|
+
});
|
|
586
|
+
// Sentinel that the timer resolves with, so we can tell "agent finished" from
|
|
587
|
+
// "threshold elapsed" without rejecting either side.
|
|
588
|
+
const BG_HANDOFF = Symbol("bg-handoff");
|
|
589
|
+
let bgTimer;
|
|
590
|
+
const timerPromise = autoBgMs > 0 && autoBgMs !== Infinity
|
|
591
|
+
? new Promise((resolve) => {
|
|
592
|
+
bgTimer = setTimeout(() => resolve(BG_HANDOFF), autoBgMs);
|
|
593
|
+
})
|
|
594
|
+
: null;
|
|
403
595
|
try {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
596
|
+
// If there's no timer (threshold 0/∞ disabled), just await as before.
|
|
597
|
+
const winner = timerPromise
|
|
598
|
+
? await Promise.race([runPromise.then((t) => ({ ok: true, text: t })).catch((e) => ({ ok: false, err: e })), timerPromise])
|
|
599
|
+
: { ok: true, text: await runPromise };
|
|
600
|
+
if (winner === BG_HANDOFF) {
|
|
601
|
+
// Threshold elapsed while the agent is STILL running. Detach it into the
|
|
602
|
+
// background registry (it keeps executing on syncController.signal — we
|
|
603
|
+
// do NOT abort it) and wire its eventual completion into the
|
|
604
|
+
// notification queue, exactly like an explicit run_in_background agent.
|
|
605
|
+
handedOff = true;
|
|
606
|
+
handoffToBackground(runPromise, syncController, {
|
|
607
|
+
agentId,
|
|
608
|
+
name,
|
|
609
|
+
description,
|
|
610
|
+
agentType: overrides.resolvedType,
|
|
611
|
+
sessionId: ctx?.sessionId,
|
|
612
|
+
hooks: ctx?.hooks,
|
|
613
|
+
parentSignal,
|
|
614
|
+
onParentAbort,
|
|
615
|
+
// The agent_start fired in runSubAgent put the UI card into 'working'.
|
|
616
|
+
// runSubAgent's own agent_end (line ~299) only fires if spawn() RESOLVES;
|
|
617
|
+
// on failure/cancel it throws before that, so the card would hang
|
|
618
|
+
// 'working' forever. Hand the UI sink to the background completion
|
|
619
|
+
// handlers so they emit the terminal agent_end the card needs.
|
|
620
|
+
uiStream: parentStream,
|
|
621
|
+
});
|
|
622
|
+
return [
|
|
623
|
+
`Task is taking a while (>${Math.round(autoBgMs / 1000)}s) — moved it to the background so I'm not blocked.`,
|
|
624
|
+
`agent_id: ${agentId} (internal — do not show to user)`,
|
|
625
|
+
`description: ${description}`,
|
|
626
|
+
``,
|
|
627
|
+
`It is still running and you will be notified automatically when it completes.`,
|
|
628
|
+
`Briefly tell the user it's running in the background and either continue with other work or end your response. Do not sleep or poll.`,
|
|
629
|
+
`If you need to stop it: AgentCancel(agent_id="${agentId}").`,
|
|
630
|
+
].join("\n");
|
|
631
|
+
}
|
|
632
|
+
// Agent finished within the threshold. Record a silent completed entry so
|
|
633
|
+
// AgentSendInput can continue this sub-agent (recover its role scope). Both
|
|
634
|
+
// a first spawn and a resume land here; re-registering a resumed id is a
|
|
635
|
+
// no-op (recordCompletedSyncAgent skips when an entry already exists).
|
|
636
|
+
if (winner.ok) {
|
|
637
|
+
recordCompletedSyncAgent({
|
|
638
|
+
agentId,
|
|
639
|
+
name,
|
|
640
|
+
agentType: overrides.resolvedType,
|
|
641
|
+
description,
|
|
642
|
+
parentSessionId: ctx?.sessionId,
|
|
643
|
+
});
|
|
644
|
+
return winner.text;
|
|
645
|
+
}
|
|
646
|
+
throw winner.err;
|
|
418
647
|
}
|
|
419
648
|
catch (err) {
|
|
420
649
|
emitSubAgentHook(ctx?.hooks, "subagent_error", { agentId, description, error: err.message });
|
|
@@ -424,32 +653,131 @@ export async function agentTool(args, ctx) {
|
|
|
424
653
|
return `Agent error: ${err.message}`;
|
|
425
654
|
}
|
|
426
655
|
finally {
|
|
427
|
-
|
|
656
|
+
if (bgTimer)
|
|
657
|
+
clearTimeout(bgTimer);
|
|
658
|
+
// Only detach the parent-abort listener when we awaited to completion.
|
|
659
|
+
// On handoff, handoffToBackground already detached it (the agent is still
|
|
660
|
+
// running and now follows the background contract, not parent-turn abort).
|
|
661
|
+
if (!handedOff)
|
|
662
|
+
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
428
663
|
}
|
|
429
664
|
}
|
|
665
|
+
/**
|
|
666
|
+
* Detach a still-running synchronous sub-agent into the background registry
|
|
667
|
+
* (TODO 4.1). The agent KEEPS running on its existing signal — we attach
|
|
668
|
+
* completion handlers that mirror the explicit run_in_background path so its
|
|
669
|
+
* result/error arrives via the notification queue and the registry shows it as
|
|
670
|
+
* a running (then completed/failed) background agent. Never aborts the agent.
|
|
671
|
+
*/
|
|
672
|
+
function handoffToBackground(runPromise, controller, meta) {
|
|
673
|
+
const { agentId, name, description, agentType, sessionId, hooks, uiStream } = meta;
|
|
674
|
+
// The agent outlives the spawning turn now, so parent-turn abort must NOT
|
|
675
|
+
// cascade to it (same contract as an explicit background agent). Detach the
|
|
676
|
+
// parent listener; cancellation from here on goes through AgentCancel.
|
|
677
|
+
meta.parentSignal?.removeEventListener("abort", meta.onParentAbort);
|
|
678
|
+
asyncAgentRegistry.register({
|
|
679
|
+
agentId,
|
|
680
|
+
name,
|
|
681
|
+
agentType,
|
|
682
|
+
description,
|
|
683
|
+
sessionId,
|
|
684
|
+
status: "running",
|
|
685
|
+
startedAt: Date.now(),
|
|
686
|
+
abort: () => controller.abort(),
|
|
687
|
+
});
|
|
688
|
+
// Tell the UI this agent is now running in the BACKGROUND (still working,
|
|
689
|
+
// just detached from the parent turn). Without this the card's only signals
|
|
690
|
+
// are agent_start (looks foreground) and the eventual agent_end — so during
|
|
691
|
+
// the handoff→completion gap turn_complete's done-sweep marks it done and the
|
|
692
|
+
// card collapses / shows no "running". (A: background-agent-visibility)
|
|
693
|
+
safeEmit(uiStream, { type: "agent_backgrounded", agentId, name, description, agentType });
|
|
694
|
+
// B: keep a liveness heartbeat going while this (and any sibling) background
|
|
695
|
+
// agent runs, so the UI shows "alive" even through long LLM-request silence.
|
|
696
|
+
// Idempotent; self-stops when no agent remains.
|
|
697
|
+
ensureAgentHeartbeat();
|
|
698
|
+
runPromise
|
|
699
|
+
.then((text) => {
|
|
700
|
+
asyncAgentRegistry.markCompleted(agentId);
|
|
701
|
+
void writeAgentOutputFile(agentId, {
|
|
702
|
+
status: "completed",
|
|
703
|
+
body: text,
|
|
704
|
+
description,
|
|
705
|
+
name,
|
|
706
|
+
onError: (m, meta) => logger.warn(m, meta),
|
|
707
|
+
});
|
|
708
|
+
if (sessionId) {
|
|
709
|
+
notificationQueue.enqueue({ agentId, name, description, status: "completed", finalText: text, enqueuedAt: Date.now() }, sessionId);
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
logger.warn("agent_completion_without_session", { agentId, name, status: "completed" });
|
|
713
|
+
}
|
|
714
|
+
void hooks?.emit("notification", { kind: "agent_completed", agentId, name, description, finalText: text });
|
|
715
|
+
})
|
|
716
|
+
.catch((err) => {
|
|
717
|
+
// runSubAgent threw before its success agent_end, so the UI card is still
|
|
718
|
+
// 'working'. Close it out with a terminal agent_end{error} (mirrors the
|
|
719
|
+
// synchronous catch at the agentTool level).
|
|
720
|
+
safeEmit(uiStream, { type: "agent_end", agentId, name, description, error: err.message, agentType });
|
|
721
|
+
if (controller.signal.aborted) {
|
|
722
|
+
asyncAgentRegistry.markCancelled(agentId);
|
|
723
|
+
void hooks?.emit("notification", { kind: "agent_cancelled", agentId, name, description });
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
asyncAgentRegistry.markFailed(agentId);
|
|
727
|
+
void writeAgentOutputFile(agentId, {
|
|
728
|
+
status: "failed",
|
|
729
|
+
body: err.message,
|
|
730
|
+
description,
|
|
731
|
+
name,
|
|
732
|
+
onError: (m, meta) => logger.warn(m, meta),
|
|
733
|
+
});
|
|
734
|
+
if (sessionId) {
|
|
735
|
+
notificationQueue.enqueue({ agentId, name, description, status: "failed", error: err.message, enqueuedAt: Date.now() }, sessionId);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
logger.warn("agent_completion_without_session", { agentId, name, status: "failed" });
|
|
739
|
+
}
|
|
740
|
+
void hooks?.emit("notification", { kind: "agent_failed", agentId, name, description, error: err.message });
|
|
741
|
+
});
|
|
742
|
+
}
|
|
430
743
|
// ─── AgentStatus / AgentCancel — companions to run_in_background ─
|
|
431
744
|
export const agentStatusToolDef = {
|
|
432
745
|
name: "AgentStatus",
|
|
433
746
|
description: "Check the status of a background agent launched with Agent(run_in_background=true). " +
|
|
434
747
|
"Returns running / completed / failed / cancelled, plus the result text once finished. " +
|
|
435
|
-
"Omit agent_id to list
|
|
748
|
+
"Omit agent_id to list this session's background agents (pass all=true for every session in the process). " +
|
|
749
|
+
"Results arrive automatically when an agent finishes — use this for an on-demand check, not to poll.",
|
|
436
750
|
inputSchema: {
|
|
437
751
|
type: "object",
|
|
438
752
|
properties: {
|
|
439
753
|
agent_id: {
|
|
440
754
|
type: "string",
|
|
441
|
-
description: "The agent_id returned by Agent(run_in_background=true). Omit to list
|
|
755
|
+
description: "The agent_id returned by Agent(run_in_background=true). Omit to list.",
|
|
756
|
+
},
|
|
757
|
+
all: {
|
|
758
|
+
type: "boolean",
|
|
759
|
+
description: "When listing (no agent_id): true lists background agents from every session in this process; " +
|
|
760
|
+
"default (false) lists only the current session's.",
|
|
442
761
|
},
|
|
443
762
|
},
|
|
444
763
|
},
|
|
445
764
|
};
|
|
446
|
-
export async function agentStatusTool(args) {
|
|
765
|
+
export async function agentStatusTool(args, ctx) {
|
|
447
766
|
const agentId = args.agent_id;
|
|
448
767
|
if (!agentId) {
|
|
449
|
-
const all =
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
768
|
+
const all = args.all === true;
|
|
769
|
+
// Default to the current session's agents so concurrent sessions don't
|
|
770
|
+
// leak into each other's listings; fall back to process-wide only when
|
|
771
|
+
// there's no session context or the caller explicitly asks for `all`.
|
|
772
|
+
const list = all || !ctx?.sessionId
|
|
773
|
+
? asyncAgentRegistry.list()
|
|
774
|
+
: asyncAgentRegistry.listForSession(ctx.sessionId);
|
|
775
|
+
if (list.length === 0) {
|
|
776
|
+
return all || !ctx?.sessionId
|
|
777
|
+
? "No background agents in this process."
|
|
778
|
+
: "No background agents in this session.";
|
|
779
|
+
}
|
|
780
|
+
return list
|
|
453
781
|
.map((e) => {
|
|
454
782
|
const dur = ((e.finishedAt ?? Date.now()) - e.startedAt) / 1000;
|
|
455
783
|
return `${e.agentId} [${e.status}] ${e.description} (${dur.toFixed(1)}s)`;
|
|
@@ -510,3 +838,122 @@ export async function agentCancelTool(args) {
|
|
|
510
838
|
? `Agent ${agentId} cancelled.`
|
|
511
839
|
: `Failed to cancel agent ${agentId}.`;
|
|
512
840
|
}
|
|
841
|
+
// ─── AgentSendInput — continue a previously-spawned sub-agent ────
|
|
842
|
+
export const agentSendInputToolDef = {
|
|
843
|
+
name: "AgentSendInput",
|
|
844
|
+
description: "Continue a sub-agent you previously launched with Agent — send it a follow-up " +
|
|
845
|
+
"instruction that it answers WITH FULL MEMORY of its earlier work (its prior " +
|
|
846
|
+
"messages, tool calls, and results). Use this instead of spawning a fresh agent " +
|
|
847
|
+
"with a re-pasted context dump: it resumes the same conversation by replaying the " +
|
|
848
|
+
"agent's saved transcript, so nothing is lost and you don't re-send what it already " +
|
|
849
|
+
"knows.\n\n" +
|
|
850
|
+
"Typical use: you got a partial result or a review verdict from a sub-agent, and now " +
|
|
851
|
+
"you want it to revise / answer a follow-up / act on feedback. Pass only the " +
|
|
852
|
+
"incremental instruction.\n\n" +
|
|
853
|
+
"Pass the agent_id the Agent tool returned. Works for both synchronous and background " +
|
|
854
|
+
"agents, and even after a process restart (the transcript is persisted on disk). " +
|
|
855
|
+
"Returns the agent's new reply, synchronously (a long continuation auto-moves to the " +
|
|
856
|
+
"background and notifies you on completion, like Agent does).",
|
|
857
|
+
inputSchema: {
|
|
858
|
+
type: "object",
|
|
859
|
+
properties: {
|
|
860
|
+
agent_id: {
|
|
861
|
+
type: "string",
|
|
862
|
+
description: "The agent_id returned by a prior Agent(...) call.",
|
|
863
|
+
},
|
|
864
|
+
prompt: {
|
|
865
|
+
type: "string",
|
|
866
|
+
description: "The follow-up instruction. The agent already remembers its earlier work — " +
|
|
867
|
+
"send only what's new (feedback, the next step, a question).",
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
required: ["agent_id", "prompt"],
|
|
871
|
+
},
|
|
872
|
+
};
|
|
873
|
+
export async function agentSendInputTool(args, ctx) {
|
|
874
|
+
const agentId = args.agent_id?.trim();
|
|
875
|
+
const prompt = args.prompt;
|
|
876
|
+
if (!agentId)
|
|
877
|
+
return "Error: agent_id is required.";
|
|
878
|
+
if (!prompt)
|
|
879
|
+
return "Error: prompt is required.";
|
|
880
|
+
if (!ctx?.subAgentSpawner) {
|
|
881
|
+
return "Error: Agent tool is not configured (no subAgentSpawner in ctx).";
|
|
882
|
+
}
|
|
883
|
+
// Same flat-hierarchy rule as Agent: a sub-agent cannot continue (or spawn)
|
|
884
|
+
// another sub-agent. Layered with the NESTED_AGENT_TOOLS strip in engine.ts.
|
|
885
|
+
if (ctx.isSubAgent === true) {
|
|
886
|
+
return "Error: nested agents are not supported. Sub-agents cannot send input to other sub-agents. Complete the task directly using your available tools.";
|
|
887
|
+
}
|
|
888
|
+
const spawner = ctx.subAgentSpawner;
|
|
889
|
+
const parentSignal = args.__signal;
|
|
890
|
+
if (parentSignal?.aborted) {
|
|
891
|
+
return "Agent aborted before resuming.";
|
|
892
|
+
}
|
|
893
|
+
// Resolve the child session to resume. With the agent_id===childSid
|
|
894
|
+
// convention the child session is stored at sessions/<agent_id>/, so the
|
|
895
|
+
// agent_id IS the resume target. Confirm it's resumable:
|
|
896
|
+
// 1. in-memory registry (fast path, same process) — gives us the recorded
|
|
897
|
+
// childSessionId (== agentId) and the dock label/agentType;
|
|
898
|
+
// 2. on-disk probe (cross-restart) — the registry is gone but the session
|
|
899
|
+
// transcript persists.
|
|
900
|
+
// Both miss → the agent never existed or was cleaned up.
|
|
901
|
+
const entry = asyncAgentRegistry.get(agentId);
|
|
902
|
+
const resumeSessionId = entry?.childSessionId ?? agentId;
|
|
903
|
+
const onDisk = spawner.sessionExists?.(resumeSessionId) ?? false;
|
|
904
|
+
if (!entry && !onDisk) {
|
|
905
|
+
return (`Error: no resumable sub-agent with agent_id "${agentId}". ` +
|
|
906
|
+
`It may never have run or its session was cleaned up. ` +
|
|
907
|
+
`Re-spawn with Agent(...) and a complete task description.`);
|
|
908
|
+
}
|
|
909
|
+
// Concurrency guard: a sub-agent that's still running (e.g. it auto-moved to
|
|
910
|
+
// the background and hasn't finished) owns its child session. Resuming now
|
|
911
|
+
// would build a SECOND child Engine that resumes the SAME session and appends
|
|
912
|
+
// to the SAME transcript concurrently — sub-agents have no per-session
|
|
913
|
+
// `active` lock (the protocol layer's serialization only covers top-level
|
|
914
|
+
// sessions), so the two writers interleave and can corrupt the transcript.
|
|
915
|
+
// Refuse until it finishes (or is cancelled).
|
|
916
|
+
if (entry?.status === "running") {
|
|
917
|
+
return (`Error: sub-agent "${agentId}" is still running. ` +
|
|
918
|
+
`Wait for it to finish (poll with AgentStatus), or cancel it with ` +
|
|
919
|
+
`AgentCancel, before sending it more input.`);
|
|
920
|
+
}
|
|
921
|
+
const name = entry?.name;
|
|
922
|
+
const description = entry?.description ?? "sub-agent continuation";
|
|
923
|
+
// Reconstruct the role's overrides from the recorded agentType. The child
|
|
924
|
+
// Engine is built FRESH on every spawn (engine.ts spawn closure) — resuming a
|
|
925
|
+
// session replays its transcript but the new Engine's tool/skill SCOPE comes
|
|
926
|
+
// from req.toolAllowlist / req.skillAllowlist, NOT from the persisted session.
|
|
927
|
+
// So if we didn't re-resolve these, a restricted role (e.g. a read-only
|
|
928
|
+
// reviewer) would silently regain the parent's full tool set on continuation.
|
|
929
|
+
// Re-resolving from agentType keeps the resumed turn under the same
|
|
930
|
+
// tool/skill/model constraints as the original spawn. An ephemeral agent
|
|
931
|
+
// (no agentType) resolves to empty overrides → inherits parent scope, same as
|
|
932
|
+
// its first turn did.
|
|
933
|
+
let overrides;
|
|
934
|
+
try {
|
|
935
|
+
overrides = resolveAgentTypeOverrides(entry?.agentType, ctx?.agentDefinitions);
|
|
936
|
+
}
|
|
937
|
+
catch {
|
|
938
|
+
// Role no longer exists (renamed/removed since spawn). Fall back to a bare
|
|
939
|
+
// continuation rather than refusing — the transcript still carries the
|
|
940
|
+
// role's system prompt, and replaying it is more useful than an error.
|
|
941
|
+
overrides = { resolvedType: entry?.agentType };
|
|
942
|
+
}
|
|
943
|
+
const maxTurns = overrides.maxTurns ?? 15;
|
|
944
|
+
const autoBgMs = resolveAutoBgMs();
|
|
945
|
+
return runSyncSubAgent({
|
|
946
|
+
spawner,
|
|
947
|
+
agentId,
|
|
948
|
+
name,
|
|
949
|
+
description,
|
|
950
|
+
prompt,
|
|
951
|
+
maxTurns,
|
|
952
|
+
overrides,
|
|
953
|
+
autoBgMs,
|
|
954
|
+
parentSignal,
|
|
955
|
+
parentStream: spawner.parentStream,
|
|
956
|
+
ctx,
|
|
957
|
+
resumeSessionId,
|
|
958
|
+
});
|
|
959
|
+
}
|