indusagi-coding-agent 0.2.8 → 0.2.9
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/package.json +2 -1
- package/src/_decls/entry.ts +18 -0
- package/src/_decls/guardrails.ts +35 -0
- package/src/_decls/index.ts +26 -0
- package/src/addons/contract.ts +236 -0
- package/src/addons/dispatch/event-dispatcher.ts +164 -0
- package/src/addons/dispatch/index.ts +25 -0
- package/src/addons/dispatch/tool-interceptor.ts +208 -0
- package/src/addons/host.ts +225 -0
- package/src/addons/index.ts +112 -0
- package/src/addons/manifest.ts +158 -0
- package/src/addons/sandbox.ts +170 -0
- package/src/addons/surface.ts +78 -0
- package/src/boot/auth-vault.ts +195 -0
- package/src/boot/boot.ts +138 -0
- package/src/boot/contract.ts +238 -0
- package/src/boot/heap.ts +59 -0
- package/src/boot/index.ts +28 -0
- package/src/boot/invocation.ts +93 -0
- package/src/boot/runners/addon-wiring.ts +153 -0
- package/src/boot/runners/checkpoint.ts +169 -0
- package/src/boot/runners/delegate-runner.ts +294 -0
- package/src/boot/runners/index.ts +13 -0
- package/src/boot/runners/link-runner.ts +45 -0
- package/src/boot/runners/memdir.ts +168 -0
- package/src/boot/runners/oneshot-runner.ts +58 -0
- package/src/boot/runners/read-state.ts +90 -0
- package/src/boot/runners/registry.ts +42 -0
- package/src/boot/runners/repl-runner.ts +143 -0
- package/src/boot/runners/server-mode.ts +121 -0
- package/src/boot/runners/session.ts +641 -0
- package/src/boot/server-token.ts +148 -0
- package/src/boot/stages.ts +167 -0
- package/src/boot/upgrade/apply.ts +94 -0
- package/src/boot/upgrade/index.ts +13 -0
- package/src/boot/upgrade/upgrades.ts +289 -0
- package/src/briefing/compose.ts +150 -0
- package/src/briefing/context-docs.ts +19 -0
- package/src/briefing/contract.ts +717 -0
- package/src/briefing/index.ts +31 -0
- package/src/briefing/macros.ts +97 -0
- package/src/briefing/skills.ts +47 -0
- package/src/capability-deck/bridge-ledger/index.ts +27 -0
- package/src/capability-deck/bridge-ledger/key.ts +67 -0
- package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
- package/src/capability-deck/bridge-ledger/network.ts +117 -0
- package/src/capability-deck/builtin-bridge.ts +312 -0
- package/src/capability-deck/cards/bg-process-card.ts +335 -0
- package/src/capability-deck/cards/index.ts +115 -0
- package/src/capability-deck/cards/memory-card.ts +146 -0
- package/src/capability-deck/cards/plan-file.ts +97 -0
- package/src/capability-deck/cards/plan-tools.ts +185 -0
- package/src/capability-deck/cards/saas-card.ts +183 -0
- package/src/capability-deck/cards/task-card.ts +207 -0
- package/src/capability-deck/cards/todo-card.ts +168 -0
- package/src/capability-deck/cards/workflow-card.ts +247 -0
- package/src/capability-deck/contract.ts +388 -0
- package/src/capability-deck/index.ts +48 -0
- package/src/capability-deck/manifest.ts +109 -0
- package/src/capability-deck/provision.ts +169 -0
- package/src/channels/contract.ts +191 -0
- package/src/channels/framer.ts +50 -0
- package/src/channels/index.ts +101 -0
- package/src/channels/link/dialog.ts +129 -0
- package/src/channels/link/driver.ts +190 -0
- package/src/channels/link/index.ts +34 -0
- package/src/channels/link/server.ts +134 -0
- package/src/channels/oneshot.ts +90 -0
- package/src/channels/ops.ts +65 -0
- package/src/channels/session-ops.ts +81 -0
- package/src/conductor/bash-guard.ts +599 -0
- package/src/conductor/catalog/catalog.ts +116 -0
- package/src/conductor/catalog/index.ts +8 -0
- package/src/conductor/catalog/matcher.ts +134 -0
- package/src/conductor/conductor.ts +234 -0
- package/src/conductor/contract.ts +842 -0
- package/src/conductor/diagnostics.ts +227 -0
- package/src/conductor/index.ts +33 -0
- package/src/conductor/permissions.ts +588 -0
- package/src/conductor/quota-error.ts +49 -0
- package/src/conductor/signal-hub/hub.ts +46 -0
- package/src/conductor/signal-hub/index.ts +2 -0
- package/src/conductor/signal-hub/translate.test.ts +81 -0
- package/src/conductor/signal-hub/translate.ts +74 -0
- package/src/conductor/skill-parse/index.ts +2 -0
- package/src/conductor/skill-parse/parse.ts +108 -0
- package/src/conductor/transcript-store/index.ts +22 -0
- package/src/conductor/transcript-store/serialize.ts +116 -0
- package/src/conductor/transcript-store/store.ts +205 -0
- package/src/console/auth-status.ts +56 -0
- package/src/console/components/AgentsView.ts +165 -0
- package/src/console/components/BackgroundAgents.ts +155 -0
- package/src/console/components/Banner.ts +334 -0
- package/src/console/components/Composer.ts +94 -0
- package/src/console/components/StatusBar.ts +49 -0
- package/src/console/components/TerminalConsole.ts +1090 -0
- package/src/console/components/WorkingIndicator.ts +98 -0
- package/src/console/components/banner-sweep.ts +24 -0
- package/src/console/components/welcome.ts +74 -0
- package/src/console/contract.ts +630 -0
- package/src/console/index.ts +34 -0
- package/src/console/input/complete.ts +127 -0
- package/src/console/input/dir-reader.ts +34 -0
- package/src/console/input/index.ts +23 -0
- package/src/console/input/keymap.ts +159 -0
- package/src/console/input/paste.ts +104 -0
- package/src/console/mount.ts +56 -0
- package/src/console/overlays/approval-queue.ts +57 -0
- package/src/console/overlays/approval.ts +130 -0
- package/src/console/overlays/auth.ts +342 -0
- package/src/console/overlays/boards.ts +308 -0
- package/src/console/overlays/host.ts +36 -0
- package/src/console/overlays/index.ts +26 -0
- package/src/console/overlays/pickers.ts +258 -0
- package/src/console/overlays/sessions.ts +190 -0
- package/src/console/reducer.ts +182 -0
- package/src/console/slash/builtins.ts +81 -0
- package/src/console/slash/commands/dynamic.ts +83 -0
- package/src/console/slash/commands/integrations.ts +695 -0
- package/src/console/slash/commands/shared.ts +75 -0
- package/src/console/slash/commands/transcript.ts +263 -0
- package/src/console/slash/commands/workbench.ts +246 -0
- package/src/console/slash/index.ts +15 -0
- package/src/console/slash/registry.ts +70 -0
- package/src/console/slash/resolve.ts +63 -0
- package/src/console/startup.ts +209 -0
- package/src/console/theme/adapter.ts +45 -0
- package/src/console/theme/index.ts +7 -0
- package/src/console/theme/palette.ts +68 -0
- package/src/console/theme/resolve.ts +39 -0
- package/src/console/theme/tokens.ts +71 -0
- package/src/entry.ts +55 -0
- package/src/guardrails.ts +37 -0
- package/src/index.ts +18 -0
- package/src/insight/channel.ts +88 -0
- package/src/insight/contract.ts +185 -0
- package/src/insight/index.ts +110 -0
- package/src/insight/recorder.ts +213 -0
- package/src/insight/redaction.ts +157 -0
- package/src/insight/replay.ts +158 -0
- package/src/insight/sampling.ts +70 -0
- package/src/insight/serialize.ts +50 -0
- package/src/insight/sinks/console.ts +64 -0
- package/src/insight/sinks/file.ts +40 -0
- package/src/insight/sinks/index.ts +24 -0
- package/src/insight/sinks/stream.ts +54 -0
- package/src/integrations/sarvam/attach.ts +239 -0
- package/src/integrations/sarvam/config.ts +156 -0
- package/src/integrations/sarvam/index.ts +25 -0
- package/src/integrations/sarvam/sarvam.test.ts +60 -0
- package/src/integrations/sarvam/types.ts +27 -0
- package/src/integrations/zoho/attach.ts +342 -0
- package/src/integrations/zoho/config.ts +125 -0
- package/src/integrations/zoho/index.ts +27 -0
- package/src/integrations/zoho/types.ts +21 -0
- package/src/integrations/zoho/zoho.test.ts +50 -0
- package/src/kit/clipboard-image.ts +107 -0
- package/src/kit/external-editor.ts +48 -0
- package/src/kit/image.ts +59 -0
- package/src/kit/index.ts +51 -0
- package/src/kit/shell.ts +19 -0
- package/src/kit/tool-fetch.ts +85 -0
- package/src/launch/catalog.ts +148 -0
- package/src/launch/contract.ts +187 -0
- package/src/launch/credentials.ts +625 -0
- package/src/launch/index.ts +98 -0
- package/src/launch/invocation/attachments.ts +179 -0
- package/src/launch/invocation/flags.ts +196 -0
- package/src/launch/invocation/index.ts +25 -0
- package/src/launch/invocation/read.ts +260 -0
- package/src/launch/invocation/usage.ts +67 -0
- package/src/launch/login.ts +324 -0
- package/src/launch/oauth.test.ts +18 -0
- package/src/launch/oauth.ts +203 -0
- package/src/launch/packages.ts +194 -0
- package/src/launch/pickers.ts +189 -0
- package/src/runtime-bridge/bridges/_drive.ts +96 -0
- package/src/runtime-bridge/bridges/builtins.ts +68 -0
- package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
- package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
- package/src/runtime-bridge/bridges/index.ts +33 -0
- package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
- package/src/runtime-bridge/broker.ts +227 -0
- package/src/runtime-bridge/contract.ts +122 -0
- package/src/runtime-bridge/index.ts +79 -0
- package/src/runtime-bridge/sink.ts +180 -0
- package/src/sessions/contract.ts +81 -0
- package/src/sessions/index.ts +13 -0
- package/src/sessions/library.ts +229 -0
- package/src/settings/contract.ts +114 -0
- package/src/settings/index.ts +32 -0
- package/src/settings/manager.ts +117 -0
- package/src/transcript-export/index.ts +45 -0
- package/src/transcript-export/publish.ts +260 -0
- package/src/transcript-export/sgr.ts +315 -0
- package/src/transcript-export/template.ts +272 -0
- package/src/transcript-export/theme-bridge.ts +150 -0
- package/src/window-budget/budget/estimate.ts +135 -0
- package/src/window-budget/budget/gate.ts +33 -0
- package/src/window-budget/budget/index.ts +16 -0
- package/src/window-budget/budget/slice.ts +56 -0
- package/src/window-budget/condenser.ts +58 -0
- package/src/window-budget/contract.ts +184 -0
- package/src/window-budget/index.ts +19 -0
- package/src/window-budget/microcompact.ts +95 -0
- package/src/window-budget/rehydrate.ts +136 -0
- package/src/window-budget/summarize/condense.ts +103 -0
- package/src/window-budget/summarize/index.ts +14 -0
- package/src/window-budget/summarize/prompt.ts +149 -0
- package/src/workflow-engine/agent-runner.ts +181 -0
- package/src/workflow-engine/display.ts +224 -0
- package/src/workflow-engine/engine.ts +294 -0
- package/src/workflow-engine/index.ts +23 -0
- package/src/workflow-engine/parse.ts +172 -0
- package/src/workflow-engine/structured-output.ts +35 -0
- package/src/workspace/brand.ts +29 -0
- package/src/workspace/index.ts +18 -0
- package/src/workspace/locator.ts +103 -0
- package/src/workspace/runtime-detect.ts +64 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Type declarations recovered from dist/types (no runtime body)
|
|
3
|
+
/**
|
|
4
|
+
* Boot-layer contract — the FROZEN type surface of Phase 1.
|
|
5
|
+
*
|
|
6
|
+
* This module is the single typed seam between the operating-system entry point
|
|
7
|
+
* (`entry.ts`) and everything that turns a command line into a running coding
|
|
8
|
+
* agent. It declares *only* shapes — no behavior, no I/O, no literals beyond the
|
|
9
|
+
* narrow string unions that pin the public modes. Every later boot module
|
|
10
|
+
* (workspace locator, upgrade registry, stage pipeline, runner registry) is
|
|
11
|
+
* written against the names declared here, so this file is intentionally small,
|
|
12
|
+
* append-mostly, and stable.
|
|
13
|
+
*
|
|
14
|
+
* Design stance:
|
|
15
|
+
* - One immutable {@link BootContext} is threaded through an ordered list of
|
|
16
|
+
* {@link Stage} transforms; a stage returns the next context (or the same
|
|
17
|
+
* one) and never mutates in place.
|
|
18
|
+
* - All branding lives in a single {@link Brand} record and all on-disk paths
|
|
19
|
+
* in a single {@link Workspace} object — there are no scattered free getters
|
|
20
|
+
* and no string literals duplicated across the codebase.
|
|
21
|
+
* - Where the rebuilt framework already owns a concept (the model catalog, the
|
|
22
|
+
* user-settings shape), the resolved-resource graph is typed against the
|
|
23
|
+
* framework's published types rather than re-declaring them here.
|
|
24
|
+
*
|
|
25
|
+
* Framework anchors (all from the `indusagi` package):
|
|
26
|
+
* - `ModelRegistry` ← `indusagi/ai` — the resolved model catalog.
|
|
27
|
+
* - `Settings` ← `indusagi/shell-app` — the user-tunable config shape.
|
|
28
|
+
* - `ThinkingLevel` ← `indusagi/agent` — reasoning-effort vocabulary (re-exported
|
|
29
|
+
* for convenience by boot consumers).
|
|
30
|
+
*/
|
|
31
|
+
import type { ModelRegistry } from "indusagi/ai";
|
|
32
|
+
import type { Settings } from "indusagi/shell-app";
|
|
33
|
+
import type { ThinkingLevel } from "indusagi/agent";
|
|
34
|
+
/** Re-exported framework reasoning vocabulary, surfaced through the boot contract. */
|
|
35
|
+
export type { ThinkingLevel };
|
|
36
|
+
/**
|
|
37
|
+
* The single source of truth for every branding / identity literal.
|
|
38
|
+
*
|
|
39
|
+
* Nothing else in the app hard-codes the product name, the profile directory
|
|
40
|
+
* name, the bin names, the environment-variable namespace, or the share-viewer
|
|
41
|
+
* origin. Resolving these once into a frozen record means a rebrand touches one
|
|
42
|
+
* value, never a grep across the tree.
|
|
43
|
+
*/
|
|
44
|
+
export interface Brand {
|
|
45
|
+
/** Product name used in banners and `process.title` (e.g. `"indusagi"`). */
|
|
46
|
+
readonly name: string;
|
|
47
|
+
/** Display label for human-facing surfaces; defaults to {@link name}. */
|
|
48
|
+
readonly label: string;
|
|
49
|
+
/** Profile directory leaf under the user's home (e.g. `".indusagi"`). */
|
|
50
|
+
readonly profileDirName: string;
|
|
51
|
+
/** Sub-directory of the profile holding the agent's own state (e.g. `"agent"`). */
|
|
52
|
+
readonly stateDirName: string;
|
|
53
|
+
/** The executable names this app installs as (both point at one entry). */
|
|
54
|
+
readonly binNames: readonly [primary: string, alias: string];
|
|
55
|
+
/** Environment-variable name prefix shared by all app-scoped vars (e.g. `"INDUSAGI"`). */
|
|
56
|
+
readonly envPrefix: string;
|
|
57
|
+
/** Env var overriding the resolved profile directory (e.g. `"INDUSAGI_CODING_AGENT_DIR"`). */
|
|
58
|
+
readonly envProfileDir: string;
|
|
59
|
+
/** Env var that, when set, disables transport-noise log filtering (e.g. `"INDUSAGI_DEBUG"`). */
|
|
60
|
+
readonly envDebug: string;
|
|
61
|
+
/** Env var overriding the share-viewer origin (e.g. `"INDUSAGI_SHARE_VIEWER_URL"`). */
|
|
62
|
+
readonly envShareViewer: string;
|
|
63
|
+
/** Default origin a published transcript links to. */
|
|
64
|
+
readonly shareViewerUrl: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Every resolved on-disk location the app reads or writes, as one immutable
|
|
68
|
+
* record rather than a family of free `get*Path()` getters.
|
|
69
|
+
*
|
|
70
|
+
* All members are absolute paths already expanded against the user's home and
|
|
71
|
+
* the active {@link Brand}; consumers join nothing further. A single object also
|
|
72
|
+
* makes the layout trivially inspectable and swappable in tests.
|
|
73
|
+
*/
|
|
74
|
+
export interface Workspace {
|
|
75
|
+
/** Root profile directory, e.g. `~/.indusagi/agent`. */
|
|
76
|
+
readonly profileDir: string;
|
|
77
|
+
/** Merged-settings file (`settings.json`). */
|
|
78
|
+
readonly settingsPath: string;
|
|
79
|
+
/** Consolidated credential store (`auth.json`). */
|
|
80
|
+
readonly authPath: string;
|
|
81
|
+
/** Per-cwd transcript directory root (`sessions/`). */
|
|
82
|
+
readonly sessionsDir: string;
|
|
83
|
+
/** Custom model-catalog overrides (`models.json`). */
|
|
84
|
+
readonly modelsPath: string;
|
|
85
|
+
/** Provisioned native helper binaries (`bin/`, holds fd / rg). */
|
|
86
|
+
readonly toolsDir: string;
|
|
87
|
+
/** Alias for the managed-binary directory; equals {@link toolsDir}. */
|
|
88
|
+
readonly binDir: string;
|
|
89
|
+
/** User-authored prompt/command templates (`prompts/`). */
|
|
90
|
+
readonly promptsDir: string;
|
|
91
|
+
/** User-installed color themes (`themes/`). */
|
|
92
|
+
readonly themesDir: string;
|
|
93
|
+
/** Bundled HTML transcript-export template directory. */
|
|
94
|
+
readonly exportTemplateDir: string;
|
|
95
|
+
/** Verbose diagnostic log file path. */
|
|
96
|
+
readonly debugLogPath: string;
|
|
97
|
+
/** External MCP server configuration (`mcp-servers.json`). */
|
|
98
|
+
readonly mcpConfigPath: string;
|
|
99
|
+
/** Memory-feature configuration (`memory.json`). */
|
|
100
|
+
readonly memoryConfigPath: string;
|
|
101
|
+
/** Composio-integration configuration (`composio.json`). */
|
|
102
|
+
readonly composioConfigPath: string;
|
|
103
|
+
/** On-disk memory database (`memory.db`). */
|
|
104
|
+
readonly memoryDbPath: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The three top-level execution modes the boot layer can dispatch to.
|
|
108
|
+
*
|
|
109
|
+
* - `repl` — interactive terminal session.
|
|
110
|
+
* - `oneshot` — single non-interactive request to stdout (text or JSON).
|
|
111
|
+
* - `link` — headless JSON-RPC link for a driving parent process.
|
|
112
|
+
*/
|
|
113
|
+
export type RunnerId = "repl" | "oneshot" | "link";
|
|
114
|
+
/**
|
|
115
|
+
* The parsed command line, reduced to what the boot layer needs to choose and
|
|
116
|
+
* configure a {@link Runner}. This is the minimal Phase-1 shape; Phase 10 enriches
|
|
117
|
+
* it with the full declarative flag surface. Raw, un-consumed tokens survive in
|
|
118
|
+
* {@link rest} and loosely-typed switches in {@link flags} so nothing is lost
|
|
119
|
+
* before the richer parser lands.
|
|
120
|
+
*/
|
|
121
|
+
export interface Invocation {
|
|
122
|
+
/** Resolved execution mode. */
|
|
123
|
+
readonly mode: RunnerId;
|
|
124
|
+
/** First user message / request text, when supplied positionally or via stdin. */
|
|
125
|
+
readonly prompt?: string;
|
|
126
|
+
/** Explicit model selector from the command line, if any (`--model` / `-m`). */
|
|
127
|
+
readonly modelId?: string;
|
|
128
|
+
/** Model to fall back to when the bound model is overloaded mid-turn (`--fallback-model`). */
|
|
129
|
+
readonly fallbackModelId?: string;
|
|
130
|
+
/** Working directory the run is scoped to; absent means the process cwd (`--cwd`). */
|
|
131
|
+
readonly cwd?: string;
|
|
132
|
+
/** Named credential account to authenticate with (`--account`). */
|
|
133
|
+
readonly account?: string;
|
|
134
|
+
/** Reasoning effort (`--thinking`): off / minimal / low / medium / high / xhigh. */
|
|
135
|
+
readonly thinking?: string;
|
|
136
|
+
/** Replacement system prompt (`--system`); a path is read as a file, else literal text. */
|
|
137
|
+
readonly system?: string;
|
|
138
|
+
/** Extra text appended after the system prompt (`--append-system`); path-or-literal. */
|
|
139
|
+
readonly appendSystem?: string;
|
|
140
|
+
/** Allow-list of built-in tool names (`--tools`); absent means the full deck. */
|
|
141
|
+
readonly tools?: readonly string[];
|
|
142
|
+
/** Disable every built-in tool (`--no-tools`). */
|
|
143
|
+
readonly noTools?: boolean;
|
|
144
|
+
/** External MCP endpoint config paths to attach (`--mcp`). */
|
|
145
|
+
readonly mcp?: readonly string[];
|
|
146
|
+
/** Open the resume picker before the session starts (`--resume` / `-r`). */
|
|
147
|
+
readonly resume?: boolean;
|
|
148
|
+
/** Auto-resume the most recent session in the cwd (`--continue` / `-c`). */
|
|
149
|
+
readonly continueLatest?: boolean;
|
|
150
|
+
/** Print the model catalog and exit (`--list-models`). */
|
|
151
|
+
readonly listModels?: boolean;
|
|
152
|
+
/** Optional substring filter for `--list-models`. */
|
|
153
|
+
readonly listModelsFilter?: string;
|
|
154
|
+
/** Parsed switches, keyed by canonical flag name (values intentionally loose). */
|
|
155
|
+
readonly flags: Record<string, unknown>;
|
|
156
|
+
/** Positional / pass-through tokens not consumed as flags. */
|
|
157
|
+
readonly rest: string[];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Placeholder for the resolved per-account credential graph.
|
|
161
|
+
*
|
|
162
|
+
* The rebuilt framework publishes no credential type, so the boot contract owns
|
|
163
|
+
* an opaque, forward-declared shape here; Phase 2 replaces it with the concrete
|
|
164
|
+
* multi-account credential vault. Kept deliberately structural (an indexable
|
|
165
|
+
* bag) so a stage may attach it without the vault module existing yet.
|
|
166
|
+
*/
|
|
167
|
+
export interface CredentialGraph {
|
|
168
|
+
readonly [account: string]: unknown;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The resolved settings / auth / model-registry graph assembled during startup.
|
|
172
|
+
*
|
|
173
|
+
* This is a Phase-1 placeholder: the fields are typed against the framework's
|
|
174
|
+
* own published types where one exists ({@link Settings}, {@link ModelRegistry}),
|
|
175
|
+
* and against an app-local placeholder ({@link CredentialGraph}) where the
|
|
176
|
+
* framework does not yet own the concept. Later phases attach the loaded
|
|
177
|
+
* extension/MCP graph onto the same object without changing these anchors.
|
|
178
|
+
*/
|
|
179
|
+
export interface StartupResources {
|
|
180
|
+
/** Merged user settings (framework-owned shape). */
|
|
181
|
+
readonly settings: Settings;
|
|
182
|
+
/** Resolved credentials per account (app-owned placeholder until Phase 2). */
|
|
183
|
+
readonly auth: CredentialGraph;
|
|
184
|
+
/** The resolved model catalog (framework-owned registry). */
|
|
185
|
+
readonly models: ModelRegistry;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The immutable value threaded through the {@link Stage} pipeline.
|
|
189
|
+
*
|
|
190
|
+
* Each stage receives a context and returns the next one; treat every field as
|
|
191
|
+
* read-only and produce successors by spreading rather than mutating. The
|
|
192
|
+
* resolved-resource graph is optional because early stages run before it exists.
|
|
193
|
+
* {@link closables} accumulates teardown callbacks (open files, servers, MCP
|
|
194
|
+
* clients) that the entry point drains in reverse on exit.
|
|
195
|
+
*/
|
|
196
|
+
export interface BootContext {
|
|
197
|
+
/** The process arguments the launch was invoked with (already sliced). */
|
|
198
|
+
readonly argv: string[];
|
|
199
|
+
/** Resolved on-disk layout. */
|
|
200
|
+
readonly workspace: Workspace;
|
|
201
|
+
/** Resolved identity literals. */
|
|
202
|
+
readonly brand: Brand;
|
|
203
|
+
/** Parsed command line. */
|
|
204
|
+
readonly invocation: Invocation;
|
|
205
|
+
/** Resolved settings/auth/model graph; absent until the resource stage runs. */
|
|
206
|
+
readonly resources?: StartupResources;
|
|
207
|
+
/** Teardown callbacks to drain on shutdown (latest-registered first). */
|
|
208
|
+
readonly closables: Array<() => Promise<void>>;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* One step of the launch pipeline: a named, pure-ish transform over a context
|
|
212
|
+
* value of type `C` (defaulting to {@link BootContext}).
|
|
213
|
+
*
|
|
214
|
+
* A stage may return its result synchronously or asynchronously. It must not
|
|
215
|
+
* mutate its input; it returns the next context. The `name` is used for tracing
|
|
216
|
+
* and error attribution when a stage throws.
|
|
217
|
+
*/
|
|
218
|
+
export interface Stage<C = BootContext> {
|
|
219
|
+
/** Stable identifier for tracing and error messages. */
|
|
220
|
+
readonly name: string;
|
|
221
|
+
/** Transform the context, yielding its successor. */
|
|
222
|
+
apply(ctx: C): Promise<C> | C;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* A terminal execution strategy for one {@link RunnerId} mode.
|
|
226
|
+
*
|
|
227
|
+
* The runner registry asks each runner whether it {@link accepts} a parsed
|
|
228
|
+
* {@link Invocation}; the first match runs. {@link run} drives the selected mode
|
|
229
|
+
* to completion and resolves to the process exit code.
|
|
230
|
+
*/
|
|
231
|
+
export interface Runner {
|
|
232
|
+
/** The mode this runner serves. */
|
|
233
|
+
readonly id: RunnerId;
|
|
234
|
+
/** Whether this runner handles the given invocation. */
|
|
235
|
+
accepts(inv: Invocation): boolean;
|
|
236
|
+
/** Execute the mode; resolves to the process exit code. */
|
|
237
|
+
run(ctx: BootContext): Promise<number>;
|
|
238
|
+
}
|
package/src/boot/heap.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heap headroom guard.
|
|
3
|
+
*
|
|
4
|
+
* Node's default old-space cap (~2 GB on 64-bit builds) is too small for heavy
|
|
5
|
+
* runs — most acutely a burst of parallel sub-agents whose combined transcripts
|
|
6
|
+
* push the resident heap past the default and abort the whole process with a V8
|
|
7
|
+
* "JavaScript heap out of memory" fatal (the exact crash this guards against).
|
|
8
|
+
*
|
|
9
|
+
* `--max-old-space-size` can only be set at process launch, so before any real
|
|
10
|
+
* work begins we re-exec the *same* program once with a raised cap, inherit its
|
|
11
|
+
* stdio (so the interactive TUI runs unchanged in the child), and adopt its exit
|
|
12
|
+
* code. The re-exec is one level deep: the child carries `INDUS_HEAP_BOOSTED=1`
|
|
13
|
+
* so it skips the guard and runs the agent directly.
|
|
14
|
+
*
|
|
15
|
+
* Controlled by env:
|
|
16
|
+
* INDUS_MAX_HEAP_MB target old-space size in MB (default 4096; 0 disables
|
|
17
|
+
* the guard entirely and runs in-process).
|
|
18
|
+
* INDUS_HEAP_BOOSTED set internally on the child; never set this yourself.
|
|
19
|
+
*
|
|
20
|
+
* If the re-exec cannot spawn (a locked-down sandbox, a missing argv[1]) the
|
|
21
|
+
* guard silently falls back to running in the current, un-boosted process rather
|
|
22
|
+
* than failing to start.
|
|
23
|
+
*/
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
25
|
+
import { argv, env, execArgv, execPath, exit } from "node:process";
|
|
26
|
+
|
|
27
|
+
function targetHeapMb(): number {
|
|
28
|
+
const raw = parseInt(env.INDUS_MAX_HEAP_MB ?? "", 10);
|
|
29
|
+
if (Number.isFinite(raw)) return Math.max(0, raw);
|
|
30
|
+
return 4096;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function alreadyRaised(): boolean {
|
|
34
|
+
return env.INDUS_HEAP_BOOSTED === "1" || (env.NODE_OPTIONS ?? "").includes("--max-old-space-size") || execArgv.some(
|
|
35
|
+
(a) => a.startsWith("--max-old-space-size") || a.startsWith("--max_old_space_size"),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ensure the process has heap headroom, re-execing once if it does not.
|
|
41
|
+
*
|
|
42
|
+
* Returns normally in three cases: the guard is disabled, the cap is already
|
|
43
|
+
* raised, or the re-exec could not be spawned. Otherwise it does NOT return —
|
|
44
|
+
* it runs the boosted child to completion and exits with the child's status.
|
|
45
|
+
*/
|
|
46
|
+
export function ensureHeapHeadroom(): void {
|
|
47
|
+
const mb = targetHeapMb();
|
|
48
|
+
if (mb === 0 || alreadyRaised()) return;
|
|
49
|
+
const entry = argv[1];
|
|
50
|
+
if (!entry) return;
|
|
51
|
+
const result = spawnSync(
|
|
52
|
+
execPath,
|
|
53
|
+
[`--max-old-space-size=${mb}`, ...execArgv, entry, ...argv.slice(2)],
|
|
54
|
+
{ stdio: "inherit", env: { ...env, INDUS_HEAP_BOOSTED: "1" } },
|
|
55
|
+
);
|
|
56
|
+
if (result.error) return;
|
|
57
|
+
if (typeof result.status === "number") exit(result.status);
|
|
58
|
+
exit(result.signal ? 1 : 0);
|
|
59
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot subsystem — public barrel.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the frozen Phase-1 contract type surface plus the assembled boot
|
|
5
|
+
* layer: the orchestrator ({@link boot}), the stage pipeline
|
|
6
|
+
* ({@link runStages} / {@link STAGES}), the invocation parser
|
|
7
|
+
* ({@link tokenizeInvocation}), the runner registry ({@link selectRunner} /
|
|
8
|
+
* {@link RUNNERS}), and the upgrade driver ({@link applyUpgrades}). Consumers
|
|
9
|
+
* import the boot type surface and behavior from `src/boot` rather than reaching
|
|
10
|
+
* into individual modules.
|
|
11
|
+
*/
|
|
12
|
+
export type { Brand, Workspace, Invocation, RunnerId, BootContext, Stage, Runner, StartupResources, CredentialGraph, ThinkingLevel, } from "./contract.js";
|
|
13
|
+
export { boot } from "./boot.js";
|
|
14
|
+
export { runStages, STAGES, locateWorkspace, upgrade, buildInvocation, resolveResources, selectRunnerStage, } from "./stages.js";
|
|
15
|
+
export { tokenizeInvocation, wantsHelp, wantsVersion, } from "./invocation.js";
|
|
16
|
+
export { replRunner, oneshotRunner, linkRunner, RUNNERS, selectRunner } from "./runners/registry.js";
|
|
17
|
+
export { createAuthVault } from "./auth-vault.js";
|
|
18
|
+
export { ensureHeapHeadroom } from "./heap.js";
|
|
19
|
+
export { serverMode } from "./runners/server-mode.js";
|
|
20
|
+
export { delegateRunner } from "./runners/delegate-runner.js";
|
|
21
|
+
export { buildOverlayServices, applyResume } from "./runners/repl-runner.js";
|
|
22
|
+
export { buildAddonHost, wrapToolsWithAddons, concatAddonTools } from "./runners/addon-wiring.js";
|
|
23
|
+
export { memoryDirFor, DiskMemoryStore } from "./runners/memdir.js";
|
|
24
|
+
export type { Upgrade, UpgradeReport } from "./upgrade/index.js";
|
|
25
|
+
export { applyUpgrades, UPGRADES } from "./upgrade/index.js";
|
|
26
|
+
export { BRAND, VERSION } from "../workspace/brand.js";
|
|
27
|
+
export { createWorkspace, ensureDirs } from "../workspace/locator.js";
|
|
28
|
+
export type { WorkspaceOverrides } from "../workspace/locator.js";
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invocation reader for the boot layer — the adapter that drives the full
|
|
3
|
+
* Phase-10 declarative flag grammar and projects its rich result down onto the
|
|
4
|
+
* thin boot {@link Invocation} the runner pipeline routes on.
|
|
5
|
+
*
|
|
6
|
+
* The parsing itself is no longer owned here. {@link readInvocation} (from the
|
|
7
|
+
* `launch/` subsystem) walks the single declarative flag table and produces the
|
|
8
|
+
* full {@link LaunchInvocation}; this module's job is purely the *projection*:
|
|
9
|
+
* map the launch {@link OutputMode} onto the boot {@link RunnerId}, rename the
|
|
10
|
+
* resolved fields the boot layer reads (`model` → `modelId`, `positionals` →
|
|
11
|
+
* `rest`), and widen the typed flag bag back to the loose
|
|
12
|
+
* `Record<string, unknown>` the boot {@link Invocation} carries. The result is
|
|
13
|
+
* type-compatible with the rest of the boot pipeline, so the runner registry and
|
|
14
|
+
* the session helpers consume it unchanged.
|
|
15
|
+
*
|
|
16
|
+
* - mode: `text` → `repl`, `json` → `oneshot`, `rpc` → `link`.
|
|
17
|
+
* - the full flag bag survives on {@link Invocation.flags} so the meta checks
|
|
18
|
+
* (`--help` / `--version`) and the oneshot output-shape probe (`json`) keep
|
|
19
|
+
* reading the same canonical keys they always did.
|
|
20
|
+
*
|
|
21
|
+
* The parse is total and never throws: the launch reader tolerates unknown
|
|
22
|
+
* `--flags` as boolean switches in the flag bag rather than rejecting them, so
|
|
23
|
+
* nothing is silently dropped before a later phase can reinterpret it.
|
|
24
|
+
*/
|
|
25
|
+
import { readInvocation } from "../launch/index.js";
|
|
26
|
+
import type { Invocation } from "./contract.js";
|
|
27
|
+
|
|
28
|
+
function toRunnerId(mode: string): Invocation["mode"] {
|
|
29
|
+
switch (mode) {
|
|
30
|
+
case "rpc":
|
|
31
|
+
return "link";
|
|
32
|
+
case "json":
|
|
33
|
+
return "oneshot";
|
|
34
|
+
case "text":
|
|
35
|
+
default:
|
|
36
|
+
return "repl";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function projectInvocation(launch: ReturnType<typeof readInvocation>): Invocation {
|
|
41
|
+
const listModelsRaw = launch.flags["list-models"];
|
|
42
|
+
const listModels = listModelsRaw !== undefined && listModelsRaw !== false;
|
|
43
|
+
const listModelsFilter = typeof listModelsRaw === "string" && listModelsRaw.length > 0 ? listModelsRaw : undefined;
|
|
44
|
+
return {
|
|
45
|
+
mode: toRunnerId(launch.mode),
|
|
46
|
+
...launch.prompt !== undefined ? { prompt: launch.prompt } : {},
|
|
47
|
+
...launch.model !== undefined ? { modelId: launch.model } : {},
|
|
48
|
+
...launch.fallbackModel !== undefined ? { fallbackModelId: launch.fallbackModel } : {},
|
|
49
|
+
...launch.cwd !== undefined ? { cwd: launch.cwd } : {},
|
|
50
|
+
...launch.account !== undefined ? { account: launch.account } : {},
|
|
51
|
+
...launch.thinking !== undefined ? { thinking: launch.thinking } : {},
|
|
52
|
+
...launch.system !== undefined ? { system: launch.system } : {},
|
|
53
|
+
...launch.appendSystem !== undefined ? { appendSystem: launch.appendSystem } : {},
|
|
54
|
+
...launch.tools !== undefined ? { tools: launch.tools } : {},
|
|
55
|
+
...launch.noTools ? { noTools: true } : {},
|
|
56
|
+
...launch.mcp.length > 0 ? { mcp: launch.mcp } : {},
|
|
57
|
+
...launch.zohoUrl !== undefined && launch.zohoUrl !== "" ? { zohoUrl: launch.zohoUrl } : {},
|
|
58
|
+
...launch.noZoho ? { noZoho: true } : {},
|
|
59
|
+
...launch.sarvamKey !== undefined && launch.sarvamKey !== "" ? { sarvamKey: launch.sarvamKey } : {},
|
|
60
|
+
...launch.noSarvam ? { noSarvam: true } : {},
|
|
61
|
+
...launch.sarvamBasePath !== undefined && launch.sarvamBasePath !== "" ? { sarvamBasePath: launch.sarvamBasePath } : {},
|
|
62
|
+
...launch.flags["resume"] === true ? { resume: true } : {},
|
|
63
|
+
...launch.flags["continue"] === true ? { continueLatest: true } : {},
|
|
64
|
+
...listModels ? { listModels: true } : {},
|
|
65
|
+
...listModelsFilter !== undefined ? { listModelsFilter } : {},
|
|
66
|
+
flags: launch.flags,
|
|
67
|
+
rest: launch.positionals,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read a sliced `process.argv` into the boot {@link Invocation}.
|
|
73
|
+
*
|
|
74
|
+
* Delegates the actual grammar to the launch {@link readInvocation} (the one
|
|
75
|
+
* declarative flag table), then projects its result onto the boot shape. The
|
|
76
|
+
* boot layer thus shares one parser with the launch subsystem — help, parsing,
|
|
77
|
+
* and routing can no longer drift.
|
|
78
|
+
*
|
|
79
|
+
* @param argv The already-sliced argument vector (no node/exec path).
|
|
80
|
+
*/
|
|
81
|
+
export function tokenizeInvocation(argv: readonly string[]): Invocation {
|
|
82
|
+
return projectInvocation(readInvocation(argv));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Whether the parsed invocation is asking for the help banner. */
|
|
86
|
+
export function wantsHelp(inv: Invocation): boolean {
|
|
87
|
+
return inv.flags.help === true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Whether the parsed invocation is asking for the version string. */
|
|
91
|
+
export function wantsVersion(inv: Invocation): boolean {
|
|
92
|
+
return inv.flags.version === true;
|
|
93
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot helper: activate the addon host for a session and fold its tool
|
|
3
|
+
* interceptors around the deck.
|
|
4
|
+
*
|
|
5
|
+
* The {@link createAddonHost addon host} is the product's extension mechanism —
|
|
6
|
+
* locally-authored modules under `<cwd>/.indus/addons` that graft tools, slash
|
|
7
|
+
* commands, lifecycle observers, and tool-boundary interceptors onto a running
|
|
8
|
+
* session. The host itself is fully built and tested, but until this module it
|
|
9
|
+
* was never *instantiated* outside its own unit tests: nothing in the boot path
|
|
10
|
+
* discovered, loaded, or wired addons.
|
|
11
|
+
*
|
|
12
|
+
* This module closes that gap with two narrow seams the runners call once at
|
|
13
|
+
* session-assembly time:
|
|
14
|
+
*
|
|
15
|
+
* 1. {@link buildAddonHost} — construct a host, swallow its fault stream (a
|
|
16
|
+
* broken addon must never sink the session), and {@link AddonHost.loadAll
|
|
17
|
+
* load} every addon discovered under the run's `.indus/addons` directory.
|
|
18
|
+
* With no such directory the returned {@link AddonSurfaceBundle} is empty —
|
|
19
|
+
* no interceptors, no contributed tools — so wiring it is a perfect no-op.
|
|
20
|
+
* 2. {@link wrapToolsWithAddons} — fold the bundle's
|
|
21
|
+
* {@link InterceptorChain} around every tool whose name a stage matches,
|
|
22
|
+
* leaving the rest identity-equal. A wrapped tool runs the chain's
|
|
23
|
+
* `enter` → real `execute` → `exit` reduce; an `enter` block short-circuits
|
|
24
|
+
* to an `isError` result without ever invoking the real tool.
|
|
25
|
+
*
|
|
26
|
+
* Scope (v1): only the per-tool interceptor boundary is wired. The richer
|
|
27
|
+
* lifecycle-event fan-out (`session:start`, `turn:end`, …) needs a conductor
|
|
28
|
+
* seam that does not exist yet and is deferred. The tool interceptor boundary is
|
|
29
|
+
* the load-bearing one — it is where the Wave 4 permission gate later registers
|
|
30
|
+
* as a built-in interceptor rather than a parallel mechanism.
|
|
31
|
+
*/
|
|
32
|
+
import {
|
|
33
|
+
type AddonSurfaceBundle,
|
|
34
|
+
type AgentTool,
|
|
35
|
+
type InterceptorChain,
|
|
36
|
+
createAddonHost,
|
|
37
|
+
} from "../../addons/index.js";
|
|
38
|
+
import type { BootContext } from "../contract.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build and populate the addon host for a run, returning its wired
|
|
42
|
+
* {@link AddonSurfaceBundle}.
|
|
43
|
+
*
|
|
44
|
+
* Constructs a host with an empty {@link FrameworkHandles} bag (v1 supplies no
|
|
45
|
+
* `exec` handle — the runner has no shell-exec callback to hand an addon — and
|
|
46
|
+
* the rest are interactive-only), installs a swallow-everything fault sink so a
|
|
47
|
+
* broken addon degrades silently instead of crashing the boot, then discovers
|
|
48
|
+
* and loads every addon under `<cwd>/.indus/addons` (the contract's default
|
|
49
|
+
* {@link ADDONS_DIR}). With no addons directory the bundle is empty and wiring it
|
|
50
|
+
* downstream is a no-op.
|
|
51
|
+
*
|
|
52
|
+
* @param ctx the boot context whose invocation carries the run cwd
|
|
53
|
+
* @returns the wired bundle (dispatch, interceptors, contributed commands/tools)
|
|
54
|
+
*/
|
|
55
|
+
export async function buildAddonHost(ctx: BootContext): Promise<AddonSurfaceBundle> {
|
|
56
|
+
const host = createAddonHost({ handles: {} });
|
|
57
|
+
host.onFault(() => {
|
|
58
|
+
// swallow addon load faults so a broken addon cannot sink the session
|
|
59
|
+
});
|
|
60
|
+
return host.loadAll({ workspace: ctx.invocation.cwd ?? process.cwd() });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Fold a bundle's {@link InterceptorChain} around every tool a stage matches.
|
|
65
|
+
*
|
|
66
|
+
* Each tool whose `name` the chain matches is replaced with a wrapper that runs
|
|
67
|
+
* the chain around its real `execute`; every other tool is returned *identical*
|
|
68
|
+
* (same object reference) so an empty bundle leaves the deck untouched. The
|
|
69
|
+
* contributed `bundle.tools` are NOT appended here — the caller concatenates
|
|
70
|
+
* them (de-duped against the existing deck) before wrapping, so addon tools are
|
|
71
|
+
* themselves subject to interception.
|
|
72
|
+
*
|
|
73
|
+
* @param tools the run's tool deck (deck + MCP + already-concatenated addon tools)
|
|
74
|
+
* @param bundle the loaded addon bundle whose interceptor chain wraps the deck
|
|
75
|
+
* @returns a new array: matched tools wrapped, unmatched tools identity-equal
|
|
76
|
+
*/
|
|
77
|
+
export function wrapToolsWithAddons(tools: AgentTool[], bundle: AddonSurfaceBundle): AgentTool[] {
|
|
78
|
+
const chain = bundle.interceptors;
|
|
79
|
+
return tools.map(
|
|
80
|
+
(tool) => chain.matches(tool.name) ? wrapOne(tool, chain) : tool,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Wrap one tool so its `execute` runs through the interceptor chain.
|
|
86
|
+
*
|
|
87
|
+
* The wrapper spreads the original tool (preserving `name` / `description` /
|
|
88
|
+
* `parameters` / `label` and any extra fields) and overrides only `execute`. The
|
|
89
|
+
* override:
|
|
90
|
+
*
|
|
91
|
+
* - closes over the live `toolCallId`, `signal`, and `onUpdate` so streaming
|
|
92
|
+
* updates and cancellation still reach the real tool (the chain only threads
|
|
93
|
+
* the decoded `args`, so these must be captured here);
|
|
94
|
+
* - hands the chain a `(args) => realExecute(args)` closure as the inner
|
|
95
|
+
* execution and runs `chain.run({ tool, callId, args }, …)`;
|
|
96
|
+
* - maps the resulting {@link InterceptResult} back to an
|
|
97
|
+
* {@link AgentToolResult}: a `blocked` enter short-circuits to an `isError`
|
|
98
|
+
* result carrying the gate's `reason` (the real tool never ran); otherwise
|
|
99
|
+
* the chain's (possibly exit-rewritten) `result` is returned.
|
|
100
|
+
*
|
|
101
|
+
* The chain — not this wrapper — owns fault isolation and the throw-on-no-recover
|
|
102
|
+
* semantics, so a tool error that no exit stage recovers propagates exactly as it
|
|
103
|
+
* would unwrapped.
|
|
104
|
+
*
|
|
105
|
+
* @param tool the real tool to fold the chain around
|
|
106
|
+
* @param chain the interceptor chain matching this tool's name
|
|
107
|
+
*/
|
|
108
|
+
export function wrapOne(tool: AgentTool, chain: InterceptorChain): AgentTool {
|
|
109
|
+
const realExecute = tool.execute.bind(tool);
|
|
110
|
+
return {
|
|
111
|
+
...tool,
|
|
112
|
+
async execute(toolCallId: string, params: unknown, signal?: AbortSignal, onUpdate?: unknown) {
|
|
113
|
+
const args = params ?? {};
|
|
114
|
+
const outcome = await chain.run(
|
|
115
|
+
{ tool: tool.name, callId: toolCallId, args: args as Record<string, unknown> },
|
|
116
|
+
(next) => realExecute(toolCallId, next, signal as never, onUpdate as never),
|
|
117
|
+
);
|
|
118
|
+
if (outcome.blocked !== undefined) {
|
|
119
|
+
return {
|
|
120
|
+
content: [
|
|
121
|
+
{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: outcome.blocked.reason ?? "Blocked by an addon.",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
details: undefined,
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return outcome.result ?? { content: [], details: undefined };
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Concatenate an addon bundle's contributed tools onto an existing deck, dropping
|
|
137
|
+
* any whose name a deck tool already claims.
|
|
138
|
+
*
|
|
139
|
+
* The host de-dupes its OWN tools against each other, but not against the product
|
|
140
|
+
* deck or MCP tools — so an addon tool named `read` would otherwise shadow (or
|
|
141
|
+
* be appended alongside) the core read tool. This filter keeps the first
|
|
142
|
+
* claimant (the existing deck) and admits only addon tools with a fresh name.
|
|
143
|
+
*
|
|
144
|
+
* @param deck the existing tool deck (deck + MCP)
|
|
145
|
+
* @param bundle the loaded addon bundle whose `tools` are appended
|
|
146
|
+
* @returns the deck followed by the non-conflicting addon tools
|
|
147
|
+
*/
|
|
148
|
+
export function concatAddonTools(deck: AgentTool[], bundle: AddonSurfaceBundle): AgentTool[] {
|
|
149
|
+
if (bundle.tools.length === 0) return deck;
|
|
150
|
+
const claimed = new Set(deck.map((t) => t.name));
|
|
151
|
+
const fresh = bundle.tools.filter((t) => !claimed.has(t.name));
|
|
152
|
+
return fresh.length === 0 ? deck : [...deck, ...fresh];
|
|
153
|
+
}
|