@armadra/agent 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +426 -2
- package/dist/agent/agent.d.ts +56 -0
- package/dist/agent/agent.js +129 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +158 -0
- package/dist/agent/queue.d.ts +25 -0
- package/dist/agent/queue.js +55 -0
- package/dist/agent/retry.d.ts +22 -0
- package/dist/agent/retry.js +92 -0
- package/dist/agent/schema.d.ts +27 -0
- package/dist/agent/schema.js +186 -0
- package/dist/agent/session-cache.d.ts +100 -0
- package/dist/agent/session-cache.js +511 -0
- package/dist/agent/session-compaction.d.ts +49 -0
- package/dist/agent/session-compaction.js +251 -0
- package/dist/agent/session-core.d.ts +103 -0
- package/dist/agent/session-core.js +7 -0
- package/dist/agent/session-run.d.ts +55 -0
- package/dist/agent/session-run.js +246 -0
- package/dist/agent/session-state.d.ts +39 -0
- package/dist/agent/session-state.js +118 -0
- package/dist/agent/session-subagent.d.ts +54 -0
- package/dist/agent/session-subagent.js +157 -0
- package/dist/agent/session-sync.d.ts +20 -0
- package/dist/agent/session-sync.js +104 -0
- package/dist/agent/session-tools.d.ts +24 -0
- package/dist/agent/session-tools.js +277 -0
- package/dist/agent/session.d.ts +117 -0
- package/dist/agent/session.js +484 -0
- package/dist/agent/system-prompt.d.ts +49 -0
- package/dist/agent/system-prompt.js +121 -0
- package/dist/agent/tool-runner.d.ts +64 -0
- package/dist/agent/tool-runner.js +309 -0
- package/dist/agent/transform.d.ts +26 -0
- package/dist/agent/transform.js +171 -0
- package/dist/agent/types.d.ts +378 -0
- package/dist/agent/types.js +21 -0
- package/dist/ai/apis/anthropic-messages.d.ts +26 -0
- package/dist/ai/apis/anthropic-messages.js +214 -0
- package/dist/ai/apis/anthropic-request.d.ts +42 -0
- package/dist/ai/apis/anthropic-request.js +282 -0
- package/dist/ai/apis/api.d.ts +28 -0
- package/dist/ai/apis/api.js +102 -0
- package/dist/ai/apis/cache-params.d.ts +48 -0
- package/dist/ai/apis/cache-params.js +163 -0
- package/dist/ai/apis/google-generative-ai.d.ts +32 -0
- package/dist/ai/apis/google-generative-ai.js +249 -0
- package/dist/ai/apis/google-request.d.ts +40 -0
- package/dist/ai/apis/google-request.js +255 -0
- package/dist/ai/apis/openai-compat.d.ts +35 -0
- package/dist/ai/apis/openai-compat.js +142 -0
- package/dist/ai/apis/openai-completions.d.ts +28 -0
- package/dist/ai/apis/openai-completions.js +246 -0
- package/dist/ai/apis/openai-request.d.ts +35 -0
- package/dist/ai/apis/openai-request.js +303 -0
- package/dist/ai/apis/openai-responses-request.d.ts +52 -0
- package/dist/ai/apis/openai-responses-request.js +266 -0
- package/dist/ai/apis/openai-responses.d.ts +28 -0
- package/dist/ai/apis/openai-responses.js +303 -0
- package/dist/ai/apis/shared.d.ts +56 -0
- package/dist/ai/apis/shared.js +179 -0
- package/dist/ai/cache/economics.d.ts +24 -0
- package/dist/ai/cache/economics.js +69 -0
- package/dist/ai/cache/fingerprint.d.ts +17 -0
- package/dist/ai/cache/fingerprint.js +39 -0
- package/dist/ai/cache/miss.d.ts +49 -0
- package/dist/ai/cache/miss.js +96 -0
- package/dist/ai/cache/reporting.d.ts +39 -0
- package/dist/ai/cache/reporting.js +102 -0
- package/dist/ai/cache/types.d.ts +94 -0
- package/dist/ai/cache/types.js +19 -0
- package/dist/ai/cache/warmer.d.ts +94 -0
- package/dist/ai/cache/warmer.js +224 -0
- package/dist/ai/context.d.ts +35 -0
- package/dist/ai/context.js +114 -0
- package/dist/ai/cost.d.ts +21 -0
- package/dist/ai/cost.js +60 -0
- package/dist/ai/event-stream.d.ts +33 -0
- package/dist/ai/event-stream.js +101 -0
- package/dist/ai/fake/fake-provider.d.ts +68 -0
- package/dist/ai/fake/fake-provider.js +278 -0
- package/dist/ai/fake/fake-script.d.ts +69 -0
- package/dist/ai/fake/fake-script.js +142 -0
- package/dist/ai/http.d.ts +54 -0
- package/dist/ai/http.js +212 -0
- package/dist/ai/json-partial.d.ts +17 -0
- package/dist/ai/json-partial.js +243 -0
- package/dist/ai/overflow.d.ts +22 -0
- package/dist/ai/overflow.js +61 -0
- package/dist/ai/providers/auth.d.ts +71 -0
- package/dist/ai/providers/auth.js +202 -0
- package/dist/ai/providers/builtin.d.ts +23 -0
- package/dist/ai/providers/builtin.js +148 -0
- package/dist/ai/providers/catalog-data.d.ts +5 -0
- package/dist/ai/providers/catalog-data.js +19 -0
- package/dist/ai/providers/catalog.d.ts +28 -0
- package/dist/ai/providers/catalog.js +166 -0
- package/dist/ai/providers/registry.d.ts +83 -0
- package/dist/ai/providers/registry.js +305 -0
- package/dist/ai/sse.d.ts +32 -0
- package/dist/ai/sse.js +140 -0
- package/dist/ai/thinking.d.ts +42 -0
- package/dist/ai/thinking.js +101 -0
- package/dist/ai/types.d.ts +428 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +25770 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +82 -0
- package/dist/cli/args.js +381 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +398 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +104 -0
- package/dist/cli/compose-session.d.ts +69 -0
- package/dist/cli/compose-session.js +370 -0
- package/dist/cli/compose-store.d.ts +24 -0
- package/dist/cli/compose-store.js +125 -0
- package/dist/cli/compose.d.ts +106 -0
- package/dist/cli/compose.js +206 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +205 -0
- package/dist/cli/deps.js +12 -0
- package/dist/cli/exit-codes.d.ts +29 -0
- package/dist/cli/exit-codes.js +40 -0
- package/dist/cli/main.d.ts +26 -0
- package/dist/cli/main.js +165 -0
- package/dist/cli/runtime.d.ts +90 -0
- package/dist/cli/runtime.js +8 -0
- package/dist/cli/startup-screen.d.ts +15 -0
- package/dist/cli/startup-screen.js +56 -0
- package/dist/cli/startup-steps.d.ts +35 -0
- package/dist/cli/startup-steps.js +197 -0
- package/dist/cli/subcommands/auth.d.ts +12 -0
- package/dist/cli/subcommands/auth.js +91 -0
- package/dist/cli/subcommands/config.d.ts +44 -0
- package/dist/cli/subcommands/config.js +201 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +41 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -0
- package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
- package/dist/cli/subcommands/models-cache-probe.js +190 -0
- package/dist/cli/subcommands/models-discover.d.ts +40 -0
- package/dist/cli/subcommands/models-discover.js +204 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +144 -0
- package/dist/cli/subcommands/sessions.d.ts +11 -0
- package/dist/cli/subcommands/sessions.js +100 -0
- package/dist/codemode/capability.d.ts +37 -0
- package/dist/codemode/capability.js +55 -0
- package/dist/codemode/declarations.d.ts +44 -0
- package/dist/codemode/declarations.js +111 -0
- package/dist/codemode/host-side.d.ts +59 -0
- package/dist/codemode/host-side.js +253 -0
- package/dist/codemode/modes.d.ts +23 -0
- package/dist/codemode/modes.js +40 -0
- package/dist/codemode/protocol.d.ts +91 -0
- package/dist/codemode/protocol.js +114 -0
- package/dist/codemode/sandbox-entry.d.ts +25 -0
- package/dist/codemode/sandbox-entry.js +323 -0
- package/dist/codemode/store.d.ts +26 -0
- package/dist/codemode/store.js +53 -0
- package/dist/codemode/tool.d.ts +105 -0
- package/dist/codemode/tool.js +283 -0
- package/dist/compaction/branch-summary.d.ts +30 -0
- package/dist/compaction/branch-summary.js +79 -0
- package/dist/compaction/breaker.d.ts +32 -0
- package/dist/compaction/breaker.js +65 -0
- package/dist/compaction/cut-point.d.ts +23 -0
- package/dist/compaction/cut-point.js +70 -0
- package/dist/compaction/estimate.d.ts +30 -0
- package/dist/compaction/estimate.js +114 -0
- package/dist/compaction/prune-tier.d.ts +31 -0
- package/dist/compaction/prune-tier.js +83 -0
- package/dist/compaction/serialize.d.ts +20 -0
- package/dist/compaction/serialize.js +115 -0
- package/dist/compaction/summarize-tier.d.ts +111 -0
- package/dist/compaction/summarize-tier.js +305 -0
- package/dist/config/auth-file.d.ts +39 -0
- package/dist/config/auth-file.js +92 -0
- package/dist/config/context-files.d.ts +30 -0
- package/dist/config/context-files.js +77 -0
- package/dist/config/load.d.ts +45 -0
- package/dist/config/load.js +97 -0
- package/dist/config/merge.d.ts +73 -0
- package/dist/config/merge.js +248 -0
- package/dist/config/paths.d.ts +38 -0
- package/dist/config/paths.js +108 -0
- package/dist/config/profile.d.ts +27 -0
- package/dist/config/profile.js +65 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.js +413 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +172 -0
- package/dist/config/types.js +27 -0
- package/dist/config/write.d.ts +10 -0
- package/dist/config/write.js +25 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +30 -0
- package/dist/hooks/config.d.ts +49 -0
- package/dist/hooks/config.js +102 -0
- package/dist/hooks/dispatcher.d.ts +41 -0
- package/dist/hooks/dispatcher.js +104 -0
- package/dist/hooks/matcher.d.ts +22 -0
- package/dist/hooks/matcher.js +108 -0
- package/dist/hooks/protocol.d.ts +33 -0
- package/dist/hooks/protocol.js +240 -0
- package/dist/hooks/runner.d.ts +30 -0
- package/dist/hooks/runner.js +143 -0
- package/dist/hooks/types.d.ts +147 -0
- package/dist/hooks/types.js +20 -0
- package/dist/host/api-impl.d.ts +75 -0
- package/dist/host/api-impl.js +173 -0
- package/dist/host/loader.d.ts +31 -0
- package/dist/host/loader.js +147 -0
- package/dist/host/types.d.ts +169 -0
- package/dist/host/types.js +14 -0
- package/dist/host.d.ts +5 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +28 -0
- package/dist/modes/commands-core.d.ts +44 -0
- package/dist/modes/commands-core.js +171 -0
- package/dist/modes/interactive/approval-dialog.d.ts +48 -0
- package/dist/modes/interactive/approval-dialog.js +202 -0
- package/dist/modes/interactive/commands.d.ts +41 -0
- package/dist/modes/interactive/commands.js +202 -0
- package/dist/modes/interactive/completion.d.ts +53 -0
- package/dist/modes/interactive/completion.js +157 -0
- package/dist/modes/interactive/interactive-mode.d.ts +46 -0
- package/dist/modes/interactive/interactive-mode.js +487 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -0
- package/dist/modes/interactive/key-dispatch.js +107 -0
- package/dist/modes/interactive/line/line-editor.d.ts +50 -0
- package/dist/modes/interactive/line/line-editor.js +232 -0
- package/dist/modes/interactive/line/line-mode.d.ts +20 -0
- package/dist/modes/interactive/line/line-mode.js +216 -0
- package/dist/modes/interactive/line/line-render.d.ts +35 -0
- package/dist/modes/interactive/line/line-render.js +171 -0
- package/dist/modes/interactive/line/paste-state.d.ts +26 -0
- package/dist/modes/interactive/line/paste-state.js +72 -0
- package/dist/modes/interactive/message-view.d.ts +77 -0
- package/dist/modes/interactive/message-view.js +269 -0
- package/dist/modes/interactive/pickers.d.ts +42 -0
- package/dist/modes/interactive/pickers.js +115 -0
- package/dist/modes/interactive/startup-ui.d.ts +28 -0
- package/dist/modes/interactive/startup-ui.js +238 -0
- package/dist/modes/interactive/status-bar.d.ts +48 -0
- package/dist/modes/interactive/status-bar.js +139 -0
- package/dist/modes/interactive/tool-view.d.ts +95 -0
- package/dist/modes/interactive/tool-view.js +344 -0
- package/dist/modes/print/json-event.d.ts +15 -0
- package/dist/modes/print/json-event.js +39 -0
- package/dist/modes/print/print-mode.d.ts +15 -0
- package/dist/modes/print/print-mode.js +96 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +258 -0
- package/dist/modes/rpc/jsonl.d.ts +16 -0
- package/dist/modes/rpc/jsonl.js +55 -0
- package/dist/modes/rpc/rpc-mode.d.ts +22 -0
- package/dist/modes/rpc/rpc-mode.js +153 -0
- package/dist/modes/session-report.d.ts +55 -0
- package/dist/modes/session-report.js +278 -0
- package/dist/modes/shared.d.ts +13 -0
- package/dist/modes/shared.js +52 -0
- package/dist/modes/startup-ui-text.d.ts +16 -0
- package/dist/modes/startup-ui-text.js +103 -0
- package/dist/permissions/broker.d.ts +47 -0
- package/dist/permissions/broker.js +107 -0
- package/dist/permissions/dangerous.d.ts +47 -0
- package/dist/permissions/dangerous.js +370 -0
- package/dist/permissions/pipeline.d.ts +51 -0
- package/dist/permissions/pipeline.js +181 -0
- package/dist/permissions/preview.d.ts +43 -0
- package/dist/permissions/preview.js +463 -0
- package/dist/permissions/rules.d.ts +58 -0
- package/dist/permissions/rules.js +249 -0
- package/dist/permissions/types.d.ts +100 -0
- package/dist/permissions/types.js +18 -0
- package/dist/rpc.d.ts +171 -0
- package/dist/rpc.js +13 -0
- package/dist/sdk.d.ts +98 -0
- package/dist/sdk.js +195 -0
- package/dist/session/manager.d.ts +70 -0
- package/dist/session/manager.js +278 -0
- package/dist/session/migrate.d.ts +19 -0
- package/dist/session/migrate.js +53 -0
- package/dist/session/projection.d.ts +50 -0
- package/dist/session/projection.js +180 -0
- package/dist/session/store.d.ts +47 -0
- package/dist/session/store.js +210 -0
- package/dist/session/tree.d.ts +21 -0
- package/dist/session/tree.js +88 -0
- package/dist/session/types.d.ts +199 -0
- package/dist/session/types.js +18 -0
- package/dist/skills/discover.d.ts +60 -0
- package/dist/skills/discover.js +160 -0
- package/dist/skills/expand.d.ts +35 -0
- package/dist/skills/expand.js +46 -0
- package/dist/skills/frontmatter.d.ts +27 -0
- package/dist/skills/frontmatter.js +157 -0
- package/dist/skills/index-prompt.d.ts +13 -0
- package/dist/skills/index-prompt.js +42 -0
- package/dist/skills/templates.d.ts +52 -0
- package/dist/skills/templates.js +177 -0
- package/dist/tools/bash.d.ts +49 -0
- package/dist/tools/bash.js +222 -0
- package/dist/tools/edit-fuzzy.d.ts +36 -0
- package/dist/tools/edit-fuzzy.js +129 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +241 -0
- package/dist/tools/file-mutex.d.ts +8 -0
- package/dist/tools/file-mutex.js +29 -0
- package/dist/tools/glob.d.ts +44 -0
- package/dist/tools/glob.js +217 -0
- package/dist/tools/grep.d.ts +39 -0
- package/dist/tools/grep.js +179 -0
- package/dist/tools/ignore.d.ts +40 -0
- package/dist/tools/ignore.js +174 -0
- package/dist/tools/ls.d.ts +21 -0
- package/dist/tools/ls.js +96 -0
- package/dist/tools/output-accumulator.d.ts +48 -0
- package/dist/tools/output-accumulator.js +106 -0
- package/dist/tools/paths.d.ts +20 -0
- package/dist/tools/paths.js +58 -0
- package/dist/tools/presets.d.ts +71 -0
- package/dist/tools/presets.js +128 -0
- package/dist/tools/process-tree.d.ts +35 -0
- package/dist/tools/process-tree.js +125 -0
- package/dist/tools/read.d.ts +28 -0
- package/dist/tools/read.js +181 -0
- package/dist/tools/registry.d.ts +43 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/shell.d.ts +38 -0
- package/dist/tools/shell.js +91 -0
- package/dist/tools/task.d.ts +29 -0
- package/dist/tools/task.js +107 -0
- package/dist/tools/todo.d.ts +28 -0
- package/dist/tools/todo.js +107 -0
- package/dist/tools/truncate.d.ts +44 -0
- package/dist/tools/truncate.js +175 -0
- package/dist/tools/types.d.ts +135 -0
- package/dist/tools/types.js +19 -0
- package/dist/tools/write.d.ts +20 -0
- package/dist/tools/write.js +84 -0
- package/dist/tui/ansi.d.ts +29 -0
- package/dist/tui/ansi.js +420 -0
- package/dist/tui/component.d.ts +38 -0
- package/dist/tui/component.js +13 -0
- package/dist/tui/components/box.d.ts +26 -0
- package/dist/tui/components/box.js +65 -0
- package/dist/tui/components/container.d.ts +14 -0
- package/dist/tui/components/container.js +39 -0
- package/dist/tui/components/editor-buffer.d.ts +67 -0
- package/dist/tui/components/editor-buffer.js +300 -0
- package/dist/tui/components/editor-paste.d.ts +29 -0
- package/dist/tui/components/editor-paste.js +64 -0
- package/dist/tui/components/editor.d.ts +114 -0
- package/dist/tui/components/editor.js +530 -0
- package/dist/tui/components/key-value.d.ts +29 -0
- package/dist/tui/components/key-value.js +48 -0
- package/dist/tui/components/loader.d.ts +35 -0
- package/dist/tui/components/loader.js +71 -0
- package/dist/tui/components/markdown.d.ts +90 -0
- package/dist/tui/components/markdown.js +462 -0
- package/dist/tui/components/meter.d.ts +29 -0
- package/dist/tui/components/meter.js +47 -0
- package/dist/tui/components/overlay.d.ts +24 -0
- package/dist/tui/components/overlay.js +52 -0
- package/dist/tui/components/select-list.d.ts +57 -0
- package/dist/tui/components/select-list.js +179 -0
- package/dist/tui/components/spacer.d.ts +11 -0
- package/dist/tui/components/spacer.js +16 -0
- package/dist/tui/components/text.d.ts +30 -0
- package/dist/tui/components/text.js +65 -0
- package/dist/tui/keybindings.d.ts +66 -0
- package/dist/tui/keybindings.js +120 -0
- package/dist/tui/keys.d.ts +38 -0
- package/dist/tui/keys.js +275 -0
- package/dist/tui/stdin-buffer.d.ts +41 -0
- package/dist/tui/stdin-buffer.js +206 -0
- package/dist/tui/terminal.d.ts +79 -0
- package/dist/tui/terminal.js +153 -0
- package/dist/tui/theme.d.ts +31 -0
- package/dist/tui/theme.js +199 -0
- package/dist/tui/tui.d.ts +103 -0
- package/dist/tui/tui.js +363 -0
- package/dist/tui/vt-screen.d.ts +46 -0
- package/dist/tui/vt-screen.js +251 -0
- package/dist/tui.d.ts +30 -0
- package/dist/tui.js +31 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +24 -0
- package/docs/codemode.md +102 -0
- package/docs/hooks.md +196 -0
- package/docs/host-api.md +161 -0
- package/docs/providers.md +316 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +168 -0
- package/package.json +101 -4
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 命令式 Hook 契约(设计 §6.1)。[B0] 契约文件,实现归 B5。
|
|
3
|
+
*
|
|
4
|
+
* 补全与偏差:
|
|
5
|
+
* - 补全 `HookCommand`、`HookMatcherGroup`、`HookConfig`(hooks.json 文件形状)、
|
|
6
|
+
* `HookRunResult`(单条命令结果)、`HookOutcome`(同一事件全部 Hook 合并后的结论)与
|
|
7
|
+
* Runtime / AgentSession 需要的 `HookDispatcherApi`。
|
|
8
|
+
* - `HookDecision` = stdout JSON 里 `decision` 的取值;合并规则 deny > block > ask > allow。
|
|
9
|
+
*/
|
|
10
|
+
import type { PermissionMode } from "../permissions/types.js";
|
|
11
|
+
export type HookEvent = "SessionStart" | "UserPromptSubmit" | "PreToolUse" | "PostToolUse" | "Stop" | "SubagentStop" | "PreCompact" | "Notification" | "SessionEnd";
|
|
12
|
+
export declare const HOOK_EVENTS: readonly HookEvent[];
|
|
13
|
+
export type HookDecision = "allow" | "deny" | "ask" | "block";
|
|
14
|
+
export interface HookCommand {
|
|
15
|
+
type: "command";
|
|
16
|
+
command: string;
|
|
17
|
+
/** 缺省 60 000,上限 600 000。 */
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface HookMatcherGroup {
|
|
21
|
+
/** 缺省匹配全部;只在 Pre/PostToolUse 生效(语法见 §6.1 matcher)。 */
|
|
22
|
+
matcher?: string;
|
|
23
|
+
hooks: HookCommand[];
|
|
24
|
+
}
|
|
25
|
+
/** hooks.json 文件形状。 */
|
|
26
|
+
export interface HookConfig {
|
|
27
|
+
version: 1;
|
|
28
|
+
hooks: Partial<Record<HookEvent, HookMatcherGroup[]>>;
|
|
29
|
+
}
|
|
30
|
+
export type HookSource = "user" | "profile" | "project" | "sdk";
|
|
31
|
+
export interface HookNotification {
|
|
32
|
+
kind: "approval" | "settled" | "error" | "retry";
|
|
33
|
+
message: string;
|
|
34
|
+
}
|
|
35
|
+
/** stdin 输入:所有事件共有字段 + 事件特有字段。 */
|
|
36
|
+
export interface HookInput {
|
|
37
|
+
hookEventName: HookEvent;
|
|
38
|
+
sessionId: string;
|
|
39
|
+
sessionFile?: string;
|
|
40
|
+
cwd: string;
|
|
41
|
+
transcriptPath?: string;
|
|
42
|
+
model: {
|
|
43
|
+
provider: string;
|
|
44
|
+
id: string;
|
|
45
|
+
};
|
|
46
|
+
permissionMode: PermissionMode;
|
|
47
|
+
depth: number;
|
|
48
|
+
/** 宿主适配器 id(未激活时缺省)。 */
|
|
49
|
+
host?: string;
|
|
50
|
+
/** SessionStart */
|
|
51
|
+
source?: "startup" | "resume" | "new" | "fork";
|
|
52
|
+
/** SessionEnd */
|
|
53
|
+
reason?: "exit" | "new" | "switch";
|
|
54
|
+
toolCallId?: string;
|
|
55
|
+
toolName?: string;
|
|
56
|
+
toolInput?: unknown;
|
|
57
|
+
toolResult?: {
|
|
58
|
+
content: string;
|
|
59
|
+
isError: boolean;
|
|
60
|
+
};
|
|
61
|
+
permissionDecision?: "allow" | "ask" | "deny";
|
|
62
|
+
prompt?: string;
|
|
63
|
+
lastAssistantText?: string;
|
|
64
|
+
/** 已由 Stop Hook 续跑过 → 处理器应避免再 block。 */
|
|
65
|
+
stopHookActive?: boolean;
|
|
66
|
+
tokensBefore?: number;
|
|
67
|
+
trigger?: "auto" | "manual";
|
|
68
|
+
notification?: HookNotification;
|
|
69
|
+
/** Pre/PostToolUse:本次调用来自 codemode 脚本里的 `tools.*`(设计 §5.5)。 */
|
|
70
|
+
viaCodemode?: boolean;
|
|
71
|
+
/** Pre/PostToolUse:codemode 内层调用时为外层 `codemode` 调用的 toolCallId。 */
|
|
72
|
+
parentToolCallId?: string;
|
|
73
|
+
}
|
|
74
|
+
/** stdout JSON 形状;空 / 非 JSON → 无决策。 */
|
|
75
|
+
export interface HookOutput {
|
|
76
|
+
decision?: HookDecision;
|
|
77
|
+
reason?: string;
|
|
78
|
+
updatedInput?: unknown;
|
|
79
|
+
updatedPrompt?: string;
|
|
80
|
+
additionalContext?: string;
|
|
81
|
+
customInstructions?: string;
|
|
82
|
+
/** 任何事件:请求结束当前 run。 */
|
|
83
|
+
continue?: false;
|
|
84
|
+
/** TUI 不显示该 Hook 的输出。 */
|
|
85
|
+
suppressOutput?: true;
|
|
86
|
+
}
|
|
87
|
+
/** 单条 Hook 命令的执行结果。 */
|
|
88
|
+
export interface HookRunResult {
|
|
89
|
+
event: HookEvent;
|
|
90
|
+
command: string;
|
|
91
|
+
source: HookSource;
|
|
92
|
+
/** null = 超时或被信号杀死。 */
|
|
93
|
+
exitCode: number | null;
|
|
94
|
+
timedOut: boolean;
|
|
95
|
+
durationMs: number;
|
|
96
|
+
stdout: string;
|
|
97
|
+
stderr: string;
|
|
98
|
+
/** stdout 解析成功时存在。 */
|
|
99
|
+
output?: HookOutput;
|
|
100
|
+
}
|
|
101
|
+
/** 同一事件下全部匹配 Hook 并行结束后的合并结论。 */
|
|
102
|
+
export interface HookOutcome {
|
|
103
|
+
/** 最严:deny > block > ask > allow;无决策为 undefined。 */
|
|
104
|
+
decision?: HookDecision;
|
|
105
|
+
/** 决定性 Hook 的 reason,或退出码 2 时的 stderr。 */
|
|
106
|
+
reason?: string;
|
|
107
|
+
/** 只在恰好一个 Hook 返回时采用。 */
|
|
108
|
+
updatedInput?: unknown;
|
|
109
|
+
hasUpdatedInput: boolean;
|
|
110
|
+
updatedPrompt?: string;
|
|
111
|
+
/** 按配置顺序拼接。 */
|
|
112
|
+
additionalContext?: string;
|
|
113
|
+
customInstructions?: string;
|
|
114
|
+
/** 有 Hook 返回 `continue: false`。 */
|
|
115
|
+
stop: boolean;
|
|
116
|
+
suppressOutput: boolean;
|
|
117
|
+
results: HookRunResult[];
|
|
118
|
+
warnings: string[];
|
|
119
|
+
}
|
|
120
|
+
/** 所有事件共有的公共输入字段;dispatcher 每次运行时现取,调用方可按次覆盖(子 Agent)。 */
|
|
121
|
+
export type HookCommonContext = Pick<HookInput, "sessionId" | "sessionFile" | "cwd" | "transcriptPath" | "model" | "permissionMode" | "depth" | "host">;
|
|
122
|
+
/**
|
|
123
|
+
* `HookDispatcherApi.run` 第 4 参数:按次覆盖的公共字段。即 `Partial<HookCommonContext>`,
|
|
124
|
+
* 但在 `exactOptionalPropertyTypes` 下也接受显式 `undefined`(视为未给),方便子 Agent 直接传
|
|
125
|
+
* 可能为空的 `sessionFile`。
|
|
126
|
+
*/
|
|
127
|
+
export type HookContextOverrides = {
|
|
128
|
+
[K in keyof HookCommonContext]?: HookCommonContext[K] | undefined;
|
|
129
|
+
};
|
|
130
|
+
/** 事件对应的「事件特有」输入(公共字段由 dispatcher 填)。 */
|
|
131
|
+
export type HookEventPayload = Omit<HookInput, "hookEventName" | "sessionId" | "sessionFile" | "cwd" | "transcriptPath" | "model" | "permissionMode" | "depth" | "host">;
|
|
132
|
+
export interface HookDispatcherApi {
|
|
133
|
+
/** 该事件是否有任何已加载的 Hook(无则调用方可跳过构造输入)。 */
|
|
134
|
+
has(event: HookEvent, toolName?: string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* `context`:按次覆盖公共字段——子 Agent 传自己的 `depth / sessionId / sessionFile`,
|
|
137
|
+
* 未给的字段仍取 dispatcher 的缺省值;值为 `undefined` 的键视为未给。
|
|
138
|
+
*/
|
|
139
|
+
run(event: HookEvent, payload: HookEventPayload, signal?: AbortSignal, context?: HookContextOverrides): Promise<HookOutcome>;
|
|
140
|
+
/** doctor / `/hooks` 列表。 */
|
|
141
|
+
list(): readonly {
|
|
142
|
+
event: HookEvent;
|
|
143
|
+
matcher?: string;
|
|
144
|
+
command: string;
|
|
145
|
+
source: HookSource;
|
|
146
|
+
}[];
|
|
147
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 命令式 Hook 契约(设计 §6.1)。[B0] 契约文件,实现归 B5。
|
|
3
|
+
*
|
|
4
|
+
* 补全与偏差:
|
|
5
|
+
* - 补全 `HookCommand`、`HookMatcherGroup`、`HookConfig`(hooks.json 文件形状)、
|
|
6
|
+
* `HookRunResult`(单条命令结果)、`HookOutcome`(同一事件全部 Hook 合并后的结论)与
|
|
7
|
+
* Runtime / AgentSession 需要的 `HookDispatcherApi`。
|
|
8
|
+
* - `HookDecision` = stdout JSON 里 `decision` 的取值;合并规则 deny > block > ask > allow。
|
|
9
|
+
*/
|
|
10
|
+
export const HOOK_EVENTS = [
|
|
11
|
+
"SessionStart",
|
|
12
|
+
"UserPromptSubmit",
|
|
13
|
+
"PreToolUse",
|
|
14
|
+
"PostToolUse",
|
|
15
|
+
"Stop",
|
|
16
|
+
"SubagentStop",
|
|
17
|
+
"PreCompact",
|
|
18
|
+
"Notification",
|
|
19
|
+
"SessionEnd",
|
|
20
|
+
];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostApi 实现(设计 §6.2)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 核心不知道宿主:这里把 HostApi 的各个面接到注入的依赖上——工具注册表(B3)、会话访问器
|
|
5
|
+
* (B2 组装后才可用,所以全部是惰性函数)、指令收集、broker 槽、sendUser、UI 通知。
|
|
6
|
+
* 事件总线 `AgentEventBus` 由 bootstrap 创建并交给 AgentSession 发事件;处理器只观察,
|
|
7
|
+
* 抛错记日志;`emit` 等待全部处理器(`session_shutdown` 需要被 await)。
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentEventName, AgentEvents, ApprovalBroker, HostAdapter, HostAdapterHandle, HostApi, HostMode, InstructionSource } from "./types.js";
|
|
10
|
+
import type { ToolRegistryApi } from "../tools/types.js";
|
|
11
|
+
import type { WarmingDecisionHandler } from "../ai/cache/types.js";
|
|
12
|
+
export type HostNotify = (message: string, level: "info" | "warn" | "error") => void;
|
|
13
|
+
export type HostLogLevel = "debug" | "info" | "warn" | "error";
|
|
14
|
+
export type HostLogger = (level: HostLogLevel, message: string, detail?: unknown) => void;
|
|
15
|
+
type Handler<E extends AgentEventName> = (event: AgentEvents[E]) => void | Promise<void>;
|
|
16
|
+
/** 进程内事件总线(宿主观察 AgentEvents)。 */
|
|
17
|
+
export declare class AgentEventBus {
|
|
18
|
+
private readonly handlers;
|
|
19
|
+
private readonly log;
|
|
20
|
+
constructor(log?: HostLogger);
|
|
21
|
+
on<E extends AgentEventName>(name: E, handler: Handler<E>): () => void;
|
|
22
|
+
listenerCount(name: AgentEventName): number;
|
|
23
|
+
/** 依次调用处理器并等待;处理器异常记日志后继续。 */
|
|
24
|
+
emit<E extends AgentEventName>(name: E, event: AgentEvents[E]): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
/** 惰性会话访问器(AgentSession 组装前调用返回占位值)。 */
|
|
27
|
+
export interface HostSessionAccess {
|
|
28
|
+
id(): string;
|
|
29
|
+
file(): string | undefined;
|
|
30
|
+
cwd(): string;
|
|
31
|
+
model(): {
|
|
32
|
+
provider: string;
|
|
33
|
+
id: string;
|
|
34
|
+
} | undefined;
|
|
35
|
+
}
|
|
36
|
+
export interface HostApiDeps {
|
|
37
|
+
mode: HostMode;
|
|
38
|
+
env?: Readonly<NodeJS.ProcessEnv>;
|
|
39
|
+
session: HostSessionAccess;
|
|
40
|
+
tools: ToolRegistryApi;
|
|
41
|
+
bus: AgentEventBus;
|
|
42
|
+
/** 运行中按 steer 入队;会话未组装时抛错。 */
|
|
43
|
+
sendUser(text: string, origin: string): Promise<"started" | "queued">;
|
|
44
|
+
/** 交互 / line 模式的 UI 通知;未提供时写 stderr。模式层也可晚绑定:`binding.setNotify()`。 */
|
|
45
|
+
notify?: HostNotify;
|
|
46
|
+
/** 状态变化回调(TUI 状态栏刷新)。 */
|
|
47
|
+
onStatus?: (key: string, text: string | undefined) => void;
|
|
48
|
+
log?: HostLogger;
|
|
49
|
+
/** stderr 写入(测试注入)。 */
|
|
50
|
+
stderr?: (text: string) => void;
|
|
51
|
+
}
|
|
52
|
+
/** createHostApi 的产物:api 交给适配器,其余给 bootstrap / AgentSession。 */
|
|
53
|
+
export interface HostApiBinding {
|
|
54
|
+
readonly api: HostApi;
|
|
55
|
+
/** 适配器追加的指令(系统提示 host 节,按追加顺序)。 */
|
|
56
|
+
readonly instructions: readonly InstructionSource[];
|
|
57
|
+
/** 适配器设置的 broker(最后一次 setBroker 生效)。 */
|
|
58
|
+
broker(): ApprovalBroker | undefined;
|
|
59
|
+
/** 适配器注册的工具名。 */
|
|
60
|
+
readonly registeredTools: readonly string[];
|
|
61
|
+
/** 适配器禁用的工具名。 */
|
|
62
|
+
readonly disabledTools: readonly string[];
|
|
63
|
+
status(): ReadonlyMap<string, string>;
|
|
64
|
+
/** 绑定成 Runtime 持有的 handle。 */
|
|
65
|
+
handle(adapter: HostAdapter, source: string): HostAdapterHandle;
|
|
66
|
+
/**
|
|
67
|
+
* 晚绑定 UI 通知(模式层在 TUI / RPC 就绪后调用);覆盖构造时的 `deps.notify`,
|
|
68
|
+
* 传 undefined 恢复为构造时的值(未提供则写 stderr)。
|
|
69
|
+
*/
|
|
70
|
+
setNotify(fn?: HostNotify): void;
|
|
71
|
+
/** [W3-C0] 宿主经 `cache.onWarmingDecision` 注册的否决钩子(最后注册且未注销的那个)。 */
|
|
72
|
+
warmingDecider(): WarmingDecisionHandler | undefined;
|
|
73
|
+
}
|
|
74
|
+
export declare function createHostApi(deps: HostApiDeps): HostApiBinding;
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HostApi 实现(设计 §6.2)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 核心不知道宿主:这里把 HostApi 的各个面接到注入的依赖上——工具注册表(B3)、会话访问器
|
|
5
|
+
* (B2 组装后才可用,所以全部是惰性函数)、指令收集、broker 槽、sendUser、UI 通知。
|
|
6
|
+
* 事件总线 `AgentEventBus` 由 bootstrap 创建并交给 AgentSession 发事件;处理器只观察,
|
|
7
|
+
* 抛错记日志;`emit` 等待全部处理器(`session_shutdown` 需要被 await)。
|
|
8
|
+
*/
|
|
9
|
+
import { AmaError } from "../errors.js";
|
|
10
|
+
import { AMA_VERSION } from "../version.js";
|
|
11
|
+
import { HOST_API_VERSION } from "./types.js";
|
|
12
|
+
/** 进程内事件总线(宿主观察 AgentEvents)。 */
|
|
13
|
+
export class AgentEventBus {
|
|
14
|
+
handlers = new Map();
|
|
15
|
+
log;
|
|
16
|
+
constructor(log = () => undefined) {
|
|
17
|
+
this.log = log;
|
|
18
|
+
}
|
|
19
|
+
on(name, handler) {
|
|
20
|
+
const set = this.handlers.get(name) ?? new Set();
|
|
21
|
+
set.add(handler);
|
|
22
|
+
this.handlers.set(name, set);
|
|
23
|
+
return () => {
|
|
24
|
+
set.delete(handler);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
listenerCount(name) {
|
|
28
|
+
return this.handlers.get(name)?.size ?? 0;
|
|
29
|
+
}
|
|
30
|
+
/** 依次调用处理器并等待;处理器异常记日志后继续。 */
|
|
31
|
+
async emit(name, event) {
|
|
32
|
+
const set = this.handlers.get(name);
|
|
33
|
+
if (set === undefined || set.size === 0)
|
|
34
|
+
return;
|
|
35
|
+
for (const handler of [...set]) {
|
|
36
|
+
try {
|
|
37
|
+
await handler(event);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
this.log("error", `宿主事件处理器异常(${name})`, error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const TOOL_NAME = /^[a-z][a-z0-9_]{1,63}$/;
|
|
46
|
+
function validateTool(tool) {
|
|
47
|
+
if (typeof tool !== "object" || tool === null) {
|
|
48
|
+
throw new AmaError("invalid_arguments", "tools.register:工具定义应为对象");
|
|
49
|
+
}
|
|
50
|
+
if (typeof tool.name !== "string" || !TOOL_NAME.test(tool.name)) {
|
|
51
|
+
throw new AmaError("invalid_arguments", `tools.register:工具名不合法:${String(tool.name)}`);
|
|
52
|
+
}
|
|
53
|
+
if (typeof tool.execute !== "function") {
|
|
54
|
+
throw new AmaError("invalid_arguments", `tools.register:${tool.name} 缺少 execute()`);
|
|
55
|
+
}
|
|
56
|
+
if (!["read", "write", "execute"].includes(tool.permission)) {
|
|
57
|
+
throw new AmaError("invalid_arguments", `tools.register:${tool.name} 的 permission 不合法`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function createHostApi(deps) {
|
|
61
|
+
const instructions = [];
|
|
62
|
+
const registeredTools = [];
|
|
63
|
+
const disabledTools = [];
|
|
64
|
+
const status = new Map();
|
|
65
|
+
let broker;
|
|
66
|
+
const warmingHandlers = [];
|
|
67
|
+
let notify = deps.notify;
|
|
68
|
+
const writeErr = deps.stderr ?? ((text) => void process.stderr.write(text));
|
|
69
|
+
const log = deps.log ??
|
|
70
|
+
((level, message) => {
|
|
71
|
+
if (level === "warn" || level === "error")
|
|
72
|
+
writeErr(`ama: [host] ${message}\n`);
|
|
73
|
+
});
|
|
74
|
+
const api = Object.freeze({
|
|
75
|
+
version: HOST_API_VERSION,
|
|
76
|
+
agent: Object.freeze({ name: "ama", version: AMA_VERSION }),
|
|
77
|
+
env: Object.freeze({ ...(deps.env ?? process.env) }),
|
|
78
|
+
mode: deps.mode,
|
|
79
|
+
session: Object.freeze({
|
|
80
|
+
id: () => deps.session.id(),
|
|
81
|
+
file: () => deps.session.file(),
|
|
82
|
+
cwd: () => deps.session.cwd(),
|
|
83
|
+
model: () => deps.session.model(),
|
|
84
|
+
}),
|
|
85
|
+
tools: Object.freeze({
|
|
86
|
+
register(tool) {
|
|
87
|
+
validateTool(tool);
|
|
88
|
+
if (deps.tools.get(tool.name) !== undefined) {
|
|
89
|
+
throw new AmaError("tool_exists", `tools.register:工具 ${tool.name} 已存在`);
|
|
90
|
+
}
|
|
91
|
+
deps.tools.register(tool, "host");
|
|
92
|
+
registeredTools.push(tool.name);
|
|
93
|
+
},
|
|
94
|
+
disable(name) {
|
|
95
|
+
deps.tools.disable(name);
|
|
96
|
+
if (!disabledTools.includes(name))
|
|
97
|
+
disabledTools.push(name);
|
|
98
|
+
},
|
|
99
|
+
list: () => deps.tools.list(),
|
|
100
|
+
}),
|
|
101
|
+
instructions: Object.freeze({
|
|
102
|
+
add(source) {
|
|
103
|
+
if (source.kind === "file" && typeof source.path !== "string") {
|
|
104
|
+
throw new AmaError("invalid_arguments", "instructions.add:file 需要 path");
|
|
105
|
+
}
|
|
106
|
+
if (source.kind === "text" && typeof source.text !== "string") {
|
|
107
|
+
throw new AmaError("invalid_arguments", "instructions.add:text 需要 text");
|
|
108
|
+
}
|
|
109
|
+
instructions.push({ ...source });
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
events: Object.freeze({
|
|
113
|
+
on: (name, handler) => deps.bus.on(name, handler),
|
|
114
|
+
}),
|
|
115
|
+
approvals: Object.freeze({
|
|
116
|
+
setBroker(next) {
|
|
117
|
+
if (typeof next?.ask !== "function") {
|
|
118
|
+
throw new AmaError("invalid_arguments", "approvals.setBroker:broker 需要 ask()");
|
|
119
|
+
}
|
|
120
|
+
broker = next;
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
messages: Object.freeze({
|
|
124
|
+
sendUser: (text, origin) => deps.sendUser(text, origin ?? "host"),
|
|
125
|
+
}),
|
|
126
|
+
ui: Object.freeze({
|
|
127
|
+
notify(message, level = "info") {
|
|
128
|
+
// 交互 / line 由 UI 显示;rpc 由模式转成事件;print(或未注入)写 stderr。
|
|
129
|
+
if (notify !== undefined && deps.mode !== "print") {
|
|
130
|
+
notify(message, level);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
writeErr(`ama: [host${level === "info" ? "" : ` ${level}`}] ${message}\n`);
|
|
134
|
+
},
|
|
135
|
+
setStatus(key, text) {
|
|
136
|
+
if (text === undefined || text === "")
|
|
137
|
+
status.delete(key);
|
|
138
|
+
else
|
|
139
|
+
status.set(key, text);
|
|
140
|
+
deps.onStatus?.(key, text);
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
log,
|
|
144
|
+
cache: Object.freeze({
|
|
145
|
+
onWarmingDecision(handler) {
|
|
146
|
+
if (typeof handler !== "function") {
|
|
147
|
+
throw new AmaError("invalid_arguments", "cache.onWarmingDecision:需要函数");
|
|
148
|
+
}
|
|
149
|
+
warmingHandlers.push(handler);
|
|
150
|
+
return () => {
|
|
151
|
+
const index = warmingHandlers.lastIndexOf(handler);
|
|
152
|
+
if (index >= 0)
|
|
153
|
+
warmingHandlers.splice(index, 1);
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
api,
|
|
160
|
+
instructions,
|
|
161
|
+
broker: () => broker,
|
|
162
|
+
registeredTools,
|
|
163
|
+
disabledTools,
|
|
164
|
+
status: () => status,
|
|
165
|
+
handle(adapter, source) {
|
|
166
|
+
return { adapter, api, source, status: () => status };
|
|
167
|
+
},
|
|
168
|
+
setNotify(fn) {
|
|
169
|
+
notify = fn ?? deps.notify;
|
|
170
|
+
},
|
|
171
|
+
warmingDecider: () => warmingHandlers.at(-1),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--host` 宿主适配器加载(设计 §6.2、§11.1 第 13 步)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - 路径相对 cwd 解析;`.cjs` 用 createRequire,`.mjs` 用动态 import,`.js` 先 require、
|
|
5
|
+
* 遇到 ESM 再动态 import。模块导出可以是 `default`(ESM 默认导出 / CJS `module.exports`)
|
|
6
|
+
* 或具名的 `hostApi` + `create`。
|
|
7
|
+
* - 动态 import 的说明符是变量:bundle 是 CJS,esbuild 只改写字面量说明符的 `import()`,
|
|
8
|
+
* 变量说明符原样保留,所以 bundle 里也能加载 ESM 适配器。
|
|
9
|
+
* - 版本:`hostApi !== HOST_API_VERSION` → StartupError 78;加载失败 / 形状不对 → 6。
|
|
10
|
+
* - `create(api)` 超时 10 s 或抛错 → 6;返回 undefined → 不激活。
|
|
11
|
+
*/
|
|
12
|
+
import type { HostAdapter, HostAdapterHandle, HostApi, HostModule } from "./types.js";
|
|
13
|
+
import type { HostApiBinding } from "./api-impl.js";
|
|
14
|
+
export declare const HOST_CREATE_TIMEOUT_MS = 10000;
|
|
15
|
+
/** 从模块命名空间 / exports 取出 HostModule 形状。 */
|
|
16
|
+
export declare function extractHostModule(namespace: unknown, label: string): HostModule;
|
|
17
|
+
/** 加载并校验适配器模块。 */
|
|
18
|
+
export declare function loadHostModule(spec: string, cwd?: string): Promise<HostModule>;
|
|
19
|
+
/** 调 create(api);返回 undefined 表示不激活。 */
|
|
20
|
+
export declare function createAdapter(module: HostModule, api: HostApi, label: string, timeoutMs?: number): Promise<HostAdapter | undefined>;
|
|
21
|
+
export interface ActivateHostInput {
|
|
22
|
+
/** 模块路径或已加载的 HostModule(SDK)。 */
|
|
23
|
+
module: string | HostModule;
|
|
24
|
+
binding: HostApiBinding;
|
|
25
|
+
cwd?: string;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}
|
|
28
|
+
/** 第 13 步:加载 → 版本校验 → create;返回 handle 或 undefined(未激活)。 */
|
|
29
|
+
export declare function activateHost(input: ActivateHostInput): Promise<HostAdapterHandle | undefined>;
|
|
30
|
+
/** 调 dispose(幂等;异常交给 onError)。 */
|
|
31
|
+
export declare function disposeHost(handle: HostAdapterHandle | undefined, onError?: (error: unknown) => void): Promise<void>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--host` 宿主适配器加载(设计 §6.2、§11.1 第 13 步)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - 路径相对 cwd 解析;`.cjs` 用 createRequire,`.mjs` 用动态 import,`.js` 先 require、
|
|
5
|
+
* 遇到 ESM 再动态 import。模块导出可以是 `default`(ESM 默认导出 / CJS `module.exports`)
|
|
6
|
+
* 或具名的 `hostApi` + `create`。
|
|
7
|
+
* - 动态 import 的说明符是变量:bundle 是 CJS,esbuild 只改写字面量说明符的 `import()`,
|
|
8
|
+
* 变量说明符原样保留,所以 bundle 里也能加载 ESM 适配器。
|
|
9
|
+
* - 版本:`hostApi !== HOST_API_VERSION` → StartupError 78;加载失败 / 形状不对 → 6。
|
|
10
|
+
* - `create(api)` 超时 10 s 或抛错 → 6;返回 undefined → 不激活。
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { isAbsolute, resolve } from "node:path";
|
|
15
|
+
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { StartupError } from "../errors.js";
|
|
17
|
+
import { HOST_API_VERSION } from "./types.js";
|
|
18
|
+
const EXIT_HOST = 6;
|
|
19
|
+
const EXIT_HOST_VERSION = 78;
|
|
20
|
+
export const HOST_CREATE_TIMEOUT_MS = 10_000;
|
|
21
|
+
/** 变量作说明符的 `import()`:esbuild 打 CJS 时原样保留(字面量说明符才会被改写成 require)。 */
|
|
22
|
+
function dynamicImport(specifier) {
|
|
23
|
+
return import(specifier);
|
|
24
|
+
}
|
|
25
|
+
function loadFailure(message, cause) {
|
|
26
|
+
return new StartupError("host_load_failed", message, EXIT_HOST, cause === undefined ? {} : { cause });
|
|
27
|
+
}
|
|
28
|
+
function errorText(error) {
|
|
29
|
+
return error instanceof Error ? error.message : String(error);
|
|
30
|
+
}
|
|
31
|
+
async function importModule(path) {
|
|
32
|
+
const lower = path.toLowerCase();
|
|
33
|
+
if (lower.endsWith(".mjs"))
|
|
34
|
+
return dynamicImport(pathToFileURL(path).href);
|
|
35
|
+
const require = createRequire(pathToFileURL(path).href);
|
|
36
|
+
if (lower.endsWith(".cjs"))
|
|
37
|
+
return require(path);
|
|
38
|
+
try {
|
|
39
|
+
return require(path);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const code = error.code;
|
|
43
|
+
if (code === "ERR_REQUIRE_ESM" ||
|
|
44
|
+
code === "ERR_REQUIRE_ASYNC_MODULE" ||
|
|
45
|
+
error instanceof SyntaxError) {
|
|
46
|
+
return dynamicImport(pathToFileURL(path).href);
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function isRecord(value) {
|
|
52
|
+
return (typeof value === "object" || typeof value === "function") && value !== null;
|
|
53
|
+
}
|
|
54
|
+
/** 从模块命名空间 / exports 取出 HostModule 形状。 */
|
|
55
|
+
export function extractHostModule(namespace, label) {
|
|
56
|
+
const candidates = [];
|
|
57
|
+
if (isRecord(namespace)) {
|
|
58
|
+
if (isRecord(namespace["default"])) {
|
|
59
|
+
const inner = namespace["default"];
|
|
60
|
+
// CJS 经 import 加载时 default 再套一层 default。
|
|
61
|
+
if (isRecord(inner["default"]) && !("hostApi" in inner))
|
|
62
|
+
candidates.push(inner["default"]);
|
|
63
|
+
candidates.push(inner);
|
|
64
|
+
}
|
|
65
|
+
candidates.push(namespace);
|
|
66
|
+
}
|
|
67
|
+
const found = candidates.find((c) => isRecord(c) && ("hostApi" in c || "create" in c));
|
|
68
|
+
if (!isRecord(found))
|
|
69
|
+
throw loadFailure(`${label}: 不是宿主适配器模块(缺少 hostApi / create 导出)`);
|
|
70
|
+
if (found["hostApi"] !== HOST_API_VERSION) {
|
|
71
|
+
throw new StartupError("host_version_mismatch", `${label}: 宿主适配器 hostApi=${String(found["hostApi"])},ama 需要 ${HOST_API_VERSION}`, EXIT_HOST_VERSION);
|
|
72
|
+
}
|
|
73
|
+
if (typeof found["create"] !== "function")
|
|
74
|
+
throw loadFailure(`${label}: 缺少 create(api) 函数`);
|
|
75
|
+
return found;
|
|
76
|
+
}
|
|
77
|
+
/** 加载并校验适配器模块。 */
|
|
78
|
+
export async function loadHostModule(spec, cwd = process.cwd()) {
|
|
79
|
+
const path = isAbsolute(spec) ? spec : resolve(cwd, spec);
|
|
80
|
+
if (!existsSync(path))
|
|
81
|
+
throw loadFailure(`宿主适配器不存在:${path}`);
|
|
82
|
+
let namespace;
|
|
83
|
+
try {
|
|
84
|
+
namespace = await importModule(path);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw loadFailure(`宿主适配器加载失败:${path}:${errorText(error)}`, error);
|
|
88
|
+
}
|
|
89
|
+
return extractHostModule(namespace, path);
|
|
90
|
+
}
|
|
91
|
+
/** 调 create(api);返回 undefined 表示不激活。 */
|
|
92
|
+
export async function createAdapter(module, api, label, timeoutMs = HOST_CREATE_TIMEOUT_MS) {
|
|
93
|
+
let timer;
|
|
94
|
+
const timeout = new Promise((_, reject) => {
|
|
95
|
+
timer = setTimeout(() => reject(loadFailure(`${label}: create() 超过 ${timeoutMs} ms 未返回`)), timeoutMs);
|
|
96
|
+
});
|
|
97
|
+
let adapter;
|
|
98
|
+
try {
|
|
99
|
+
adapter = await Promise.race([Promise.resolve().then(() => module.create(api)), timeout]);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof StartupError)
|
|
103
|
+
throw error;
|
|
104
|
+
throw loadFailure(`${label}: create() 失败:${errorText(error)}`, error);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
if (adapter === undefined || adapter === null)
|
|
110
|
+
return undefined;
|
|
111
|
+
if (typeof adapter !== "object" || typeof adapter.id !== "string" || adapter.id === "") {
|
|
112
|
+
throw loadFailure(`${label}: create() 返回的适配器缺少 id`);
|
|
113
|
+
}
|
|
114
|
+
return adapter;
|
|
115
|
+
}
|
|
116
|
+
/** 第 13 步:加载 → 版本校验 → create;返回 handle 或 undefined(未激活)。 */
|
|
117
|
+
export async function activateHost(input) {
|
|
118
|
+
let module;
|
|
119
|
+
let source;
|
|
120
|
+
if (typeof input.module === "string") {
|
|
121
|
+
source = isAbsolute(input.module)
|
|
122
|
+
? input.module
|
|
123
|
+
: resolve(input.cwd ?? process.cwd(), input.module);
|
|
124
|
+
module = await loadHostModule(source);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
source = "sdk";
|
|
128
|
+
module = extractHostModule(input.module, "sdk");
|
|
129
|
+
}
|
|
130
|
+
const adapter = await createAdapter(module, input.binding.api, source, input.timeoutMs);
|
|
131
|
+
return adapter === undefined ? undefined : input.binding.handle(adapter, source);
|
|
132
|
+
}
|
|
133
|
+
const disposed = new WeakSet();
|
|
134
|
+
/** 调 dispose(幂等;异常交给 onError)。 */
|
|
135
|
+
export async function disposeHost(handle, onError = () => undefined) {
|
|
136
|
+
if (handle === undefined)
|
|
137
|
+
return;
|
|
138
|
+
if (disposed.has(handle))
|
|
139
|
+
return;
|
|
140
|
+
disposed.add(handle);
|
|
141
|
+
try {
|
|
142
|
+
await handle.adapter.dispose?.();
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
onError(error);
|
|
146
|
+
}
|
|
147
|
+
}
|