@cjhyy/code-shell-core 0.8.11 → 0.8.12

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.
Files changed (47) hide show
  1. package/dist/capabilities/index.d.ts +5 -54
  2. package/dist/capabilities/index.js +1 -80
  3. package/dist/cli/agent-server-stdio.js +21 -21
  4. package/dist/composition/compiler.d.ts +7 -0
  5. package/dist/composition/compiler.js +247 -0
  6. package/dist/composition/core-module.d.ts +10 -0
  7. package/dist/composition/core-module.js +19 -0
  8. package/dist/composition/index.d.ts +5 -0
  9. package/dist/composition/index.js +5 -0
  10. package/dist/composition/protocol-attach.d.ts +15 -0
  11. package/dist/composition/protocol-attach.js +21 -0
  12. package/dist/composition/resolve-preset.d.ts +35 -0
  13. package/dist/composition/resolve-preset.js +58 -0
  14. package/dist/composition/snapshot.d.ts +9 -0
  15. package/dist/composition/snapshot.js +48 -0
  16. package/dist/composition/types.d.ts +169 -0
  17. package/dist/composition/types.js +1 -0
  18. package/dist/engine/engine.d.ts +2 -1
  19. package/dist/engine/engine.js +54 -56
  20. package/dist/engine/subagent-spawner.js +2 -2
  21. package/dist/engine/types.d.ts +9 -6
  22. package/dist/exceptions.d.ts +8 -0
  23. package/dist/exceptions.js +11 -0
  24. package/dist/index.d.ts +8 -8
  25. package/dist/index.extension.d.ts +3 -3
  26. package/dist/index.extension.js +0 -1
  27. package/dist/index.js +6 -6
  28. package/dist/preset/index.d.ts +19 -26
  29. package/dist/preset/index.js +26 -66
  30. package/dist/product/define.js +8 -2
  31. package/dist/prompt/section-loader.d.ts +2 -8
  32. package/dist/prompt/section-loader.js +4 -17
  33. package/dist/protocol/client.d.ts +1 -0
  34. package/dist/protocol/server.d.ts +10 -11
  35. package/dist/protocol/server.js +33 -28
  36. package/dist/protocol/types.d.ts +5 -0
  37. package/dist/run/EngineRunner.d.ts +3 -2
  38. package/dist/run/EngineRunner.js +1 -1
  39. package/dist/run/RunManager.d.ts +3 -3
  40. package/dist/run/RunManager.js +6 -8
  41. package/dist/run/factory.d.ts +3 -3
  42. package/dist/run/factory.js +2 -2
  43. package/dist/session/session-manager.d.ts +1 -1
  44. package/dist/session/session-manager.js +3 -6
  45. package/dist/tool-system/capability-module.d.ts +0 -34
  46. package/dist/tool-system/capability-module.js +1 -53
  47. package/package.json +1 -1
@@ -0,0 +1,58 @@
1
+ import { ConfigError } from "../exceptions.js";
2
+ /** Resolve a preset by name (or the composition default), failing loud. */
3
+ export function resolvePresetFromComposition(composition, name) {
4
+ const resolvedName = name || composition.engine.defaultPreset;
5
+ const found = composition.engine.presets.find((p) => p.key === resolvedName);
6
+ if (found)
7
+ return found.value;
8
+ const allowed = composition.engine.presets.map((p) => p.key).join(", ");
9
+ throw new Error(`Unknown agent preset "${resolvedName}". Available presets: ${allowed}`);
10
+ }
11
+ /** All preset-tags tools — the composed catalog in effective order. */
12
+ export function compositionToolCatalog(composition) {
13
+ return composition.engine.tools.flatMap((t) => (t.kind === "preset-tags" ? [t.tool] : []));
14
+ }
15
+ /**
16
+ * Preset-tags tools force-joined to the ACTIVE preset regardless of its name.
17
+ * Rule: tools owned by modules that contribute no presets (pet-style catalog
18
+ * tools) — presets snapshot their tool lists from catalogs known at module
19
+ * authoring time, which can never include such packages. Modules that DO
20
+ * contribute presets (core, coding) reference their tools via preset tags
21
+ * already. Visibility stays gated by each tool's exposure.availability.
22
+ */
23
+ export function presetInjectedTools(composition) {
24
+ const presetOwners = new Set(composition.engine.presets.map((p) => p.moduleId));
25
+ return composition.engine.tools.flatMap((t) => t.kind === "preset-tags" && !presetOwners.has(t.moduleId) ? [t.tool] : []);
26
+ }
27
+ /** Module-contributed named prompt sections as a plain record. */
28
+ export function compositionPromptSections(composition) {
29
+ return Object.fromEntries(composition.engine.promptSections.map((s) => [s.key, s.value]));
30
+ }
31
+ /**
32
+ * Register always-exposure tools on the engine-local registry fork.
33
+ * Cross-module uniqueness is compiler-enforced; this guards collisions with
34
+ * runtime-registered tools.
35
+ */
36
+ export function registerAlwaysTools(composition, registry) {
37
+ for (const contribution of composition.engine.tools) {
38
+ if (contribution.kind !== "always")
39
+ continue;
40
+ const name = contribution.tool.definition.name;
41
+ if (registry.hasTool(name)) {
42
+ throw new ConfigError(`Capability tool conflicts with registered tool: ${name}`, {
43
+ duplicateCapabilityTool: name,
44
+ capabilityId: contribution.moduleId,
45
+ });
46
+ }
47
+ registry.registerTool(contribution.tool.definition, contribution.tool.execute);
48
+ }
49
+ }
50
+ /** First non-null module instruction boundary, in module order. */
51
+ export function resolveCompositionInstructionBoundary(composition, cwd) {
52
+ for (const boundary of composition.engine.instructionBoundaries) {
53
+ const found = boundary.value(cwd);
54
+ if (found)
55
+ return found;
56
+ }
57
+ return null;
58
+ }
@@ -0,0 +1,9 @@
1
+ import type { CompositionSnapshot, ResolvedComposition } from "./types.js";
2
+ /**
3
+ * Pure-data projection of a composition. Field construction order is the
4
+ * canonical serialization order — computeCompositionDigest() hashes the
5
+ * JSON directly, so never reorder fields without updating golden fixtures.
6
+ * Unkeyed by design: the snapshot carries no secrets (design §11.2).
7
+ */
8
+ export declare function toCompositionSnapshot(composition: Pick<ResolvedComposition, "modules" | "engine" | "protocol">): CompositionSnapshot;
9
+ export declare function computeCompositionDigest(snapshot: CompositionSnapshot): string;
@@ -0,0 +1,48 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Pure-data projection of a composition. Field construction order is the
4
+ * canonical serialization order — computeCompositionDigest() hashes the
5
+ * JSON directly, so never reorder fields without updating golden fixtures.
6
+ * Unkeyed by design: the snapshot carries no secrets (design §11.2).
7
+ */
8
+ export function toCompositionSnapshot(composition) {
9
+ return {
10
+ version: 1,
11
+ modules: composition.modules.map((m) => ({ id: m.id, order: m.order, source: m.source })),
12
+ tools: composition.engine.tools.map((t) => ({
13
+ name: t.tool.definition.name,
14
+ moduleId: t.moduleId,
15
+ exposure: t.kind,
16
+ presetTags: t.kind === "preset-tags" ? [...t.tool.exposure.presetTags] : [],
17
+ })),
18
+ presets: composition.engine.presets.map((p) => ({
19
+ name: p.key,
20
+ moduleId: p.moduleId,
21
+ isDefault: p.key === composition.engine.defaultPreset,
22
+ })),
23
+ promptSections: composition.engine.promptSections.map((s) => ({
24
+ name: s.key,
25
+ moduleId: s.moduleId,
26
+ })),
27
+ hooks: composition.engine.hooks.map((h) => ({
28
+ event: h.event,
29
+ name: h.name,
30
+ priority: h.priority,
31
+ moduleId: h.moduleId,
32
+ })),
33
+ behaviorProfiles: composition.engine.behaviorProfiles.map((p) => ({
34
+ id: p.key,
35
+ moduleId: p.moduleId,
36
+ })),
37
+ queries: composition.protocol.queries.map((q) => ({ type: q.key, moduleId: q.moduleId })),
38
+ observers: composition.protocol.observerFactories.map((o) => o.moduleId),
39
+ runValidators: composition.protocol.runValidators.map((v) => v.moduleId),
40
+ hiddenSessionKinds: composition.protocol.hiddenSessionKinds.map((k) => ({
41
+ kind: k.key,
42
+ moduleId: k.moduleId,
43
+ })),
44
+ };
45
+ }
46
+ export function computeCompositionDigest(snapshot) {
47
+ return createHash("sha256").update(JSON.stringify(snapshot)).digest("hex");
48
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * AgentModule — the single trusted product-module interface that unifies
3
+ * CapabilityModule and ExtensionModule (design:
4
+ * docs/todo/agent-module-resolved-composition-design.md). Phase A only adds
5
+ * the types and the pure compiler; no production path consumes them yet.
6
+ */
7
+ import type { CapabilityArtifactDetector, CapabilityDynamicContextProvider, CapabilityEngineHookContribution, CapabilityFileHistoryContribution, CapabilityInstructionBoundaryFinder, CapabilityToolSelectionContext, CapabilityToolServiceHost, SessionWorkspaceCapability } from "../capabilities/index.js";
8
+ import type { AgentPreset } from "../preset/index.js";
9
+ import type { BuiltinTool } from "../tool-system/builtin/index.js";
10
+ import type { ExtensionQueryHandler, ExtensionTool, ProtocolObserver, ProtocolObserverHost } from "../tool-system/capability-module.js";
11
+ import type { RunBehaviorProfile } from "../engine/run-types.js";
12
+ import type { HookEventName } from "../hooks/events.js";
13
+ /**
14
+ * One tool contribution with explicit exposure:
15
+ * - "preset-tags": full BuiltinTool metadata; joins the composed catalog.
16
+ * - "always": plain ExtensionTool; registered directly, always visible.
17
+ */
18
+ export type AgentModuleToolContribution = {
19
+ readonly kind: "preset-tags";
20
+ readonly tool: BuiltinTool;
21
+ } | {
22
+ readonly kind: "always";
23
+ readonly tool: ExtensionTool;
24
+ };
25
+ export interface AgentEngineContributions {
26
+ readonly tools?: readonly AgentModuleToolContribution[];
27
+ readonly presets?: readonly AgentPreset[];
28
+ /** Preset used when the host does not choose one. At most one module may declare it. */
29
+ readonly defaultPreset?: string;
30
+ readonly promptSections?: Readonly<Record<string, string>>;
31
+ readonly dynamicContextProviders?: readonly CapabilityDynamicContextProvider[];
32
+ readonly instructionBoundary?: CapabilityInstructionBoundaryFinder;
33
+ readonly artifactDetectors?: readonly CapabilityArtifactDetector[];
34
+ readonly fileHistory?: readonly CapabilityFileHistoryContribution[];
35
+ readonly sessionWorkspace?: SessionWorkspaceCapability;
36
+ readonly hooks?: readonly CapabilityEngineHookContribution[];
37
+ readonly behaviorProfiles?: readonly RunBehaviorProfile[];
38
+ readonly adjustToolSelection?: (names: Set<string>, context: CapabilityToolSelectionContext) => void;
39
+ /** Phase C renames this to privateService with owned lifetime. */
40
+ readonly createToolService?: (host: CapabilityToolServiceHost) => unknown;
41
+ }
42
+ export interface AgentProtocolContributions {
43
+ readonly queries?: Readonly<Record<string, ExtensionQueryHandler>>;
44
+ /** Existing name createProtocolObserver; renamed here by design. */
45
+ readonly createObserver?: (host: ProtocolObserverHost) => ProtocolObserver;
46
+ readonly validateRunParams?: (params: Record<string, unknown>) => string | null;
47
+ readonly hiddenSessionKinds?: readonly string[];
48
+ }
49
+ export interface AgentModule {
50
+ readonly id: string;
51
+ readonly engine?: AgentEngineContributions;
52
+ readonly protocol?: AgentProtocolContributions;
53
+ }
54
+ export interface ResolvedModule {
55
+ readonly id: string;
56
+ readonly order: number;
57
+ readonly source: "core" | "host";
58
+ }
59
+ export interface ResolvedContribution<T> {
60
+ readonly key: string;
61
+ readonly moduleId: string;
62
+ readonly value: T;
63
+ }
64
+ export type ResolvedToolContribution = {
65
+ readonly kind: "preset-tags";
66
+ readonly moduleId: string;
67
+ readonly tool: BuiltinTool;
68
+ } | {
69
+ readonly kind: "always";
70
+ readonly moduleId: string;
71
+ readonly tool: ExtensionTool;
72
+ };
73
+ export interface ResolvedEngineHook {
74
+ readonly moduleId: string;
75
+ readonly event: HookEventName;
76
+ readonly handler: CapabilityEngineHookContribution["handler"];
77
+ readonly priority: number;
78
+ readonly name: string;
79
+ }
80
+ export interface ResolvedEngineComposition {
81
+ /**
82
+ * Effective registry order: every preset-tags tool (module order) first,
83
+ * then every always tool (module order) — mirrors the current engine's
84
+ * composeToolCatalog() + registerExtensionModules() sequence.
85
+ */
86
+ readonly tools: readonly ResolvedToolContribution[];
87
+ readonly presets: readonly ResolvedContribution<AgentPreset>[];
88
+ readonly defaultPreset: string;
89
+ readonly promptSections: readonly ResolvedContribution<string>[];
90
+ readonly dynamicContextProviders: readonly ResolvedContribution<CapabilityDynamicContextProvider>[];
91
+ readonly instructionBoundaries: readonly ResolvedContribution<CapabilityInstructionBoundaryFinder>[];
92
+ readonly artifactDetectors: readonly ResolvedContribution<CapabilityArtifactDetector>[];
93
+ readonly fileHistory: readonly ResolvedContribution<CapabilityFileHistoryContribution>[];
94
+ readonly sessionWorkspaces: readonly ResolvedContribution<SessionWorkspaceCapability>[];
95
+ readonly hooks: readonly ResolvedEngineHook[];
96
+ readonly behaviorProfiles: readonly ResolvedContribution<RunBehaviorProfile>[];
97
+ readonly toolSelectionAdjusters: readonly ResolvedContribution<NonNullable<AgentEngineContributions["adjustToolSelection"]>>[];
98
+ readonly toolServices: readonly ResolvedContribution<NonNullable<AgentEngineContributions["createToolService"]>>[];
99
+ }
100
+ export interface ResolvedProtocolComposition {
101
+ readonly queries: readonly ResolvedContribution<ExtensionQueryHandler>[];
102
+ readonly observerFactories: readonly ResolvedContribution<(host: ProtocolObserverHost) => ProtocolObserver>[];
103
+ readonly runValidators: readonly ResolvedContribution<(params: Record<string, unknown>) => string | null>[];
104
+ readonly hiddenSessionKinds: readonly ResolvedContribution<string>[];
105
+ }
106
+ export interface CompositionDiagnostic {
107
+ readonly code: "empty_module" | "engine_only_module" | "protocol_only_module" | "core_preset_shadowed";
108
+ readonly moduleId: string;
109
+ readonly message: string;
110
+ }
111
+ export interface ResolvedComposition {
112
+ readonly version: 1;
113
+ readonly digest: string;
114
+ readonly modules: readonly ResolvedModule[];
115
+ readonly engine: ResolvedEngineComposition;
116
+ readonly protocol: ResolvedProtocolComposition;
117
+ readonly diagnostics: readonly CompositionDiagnostic[];
118
+ }
119
+ export interface CompileCompositionOptions {
120
+ /** Defaults to CORE_AGENT_MODULE. Overridable only for unit tests. */
121
+ readonly core?: AgentModule;
122
+ readonly modules?: readonly AgentModule[];
123
+ /** Module ids the host requires; missing ids are a compile error. */
124
+ readonly expectedModules?: readonly string[];
125
+ }
126
+ /** Pure-data projection; never contains functions, prompt bodies or paths. */
127
+ export interface CompositionSnapshot {
128
+ version: 1;
129
+ modules: Array<{
130
+ id: string;
131
+ order: number;
132
+ source: string;
133
+ }>;
134
+ tools: Array<{
135
+ name: string;
136
+ moduleId: string;
137
+ exposure: "preset-tags" | "always";
138
+ presetTags: string[];
139
+ }>;
140
+ presets: Array<{
141
+ name: string;
142
+ moduleId: string;
143
+ isDefault: boolean;
144
+ }>;
145
+ promptSections: Array<{
146
+ name: string;
147
+ moduleId: string;
148
+ }>;
149
+ hooks: Array<{
150
+ event: string;
151
+ name: string;
152
+ priority: number;
153
+ moduleId: string;
154
+ }>;
155
+ behaviorProfiles: Array<{
156
+ id: string;
157
+ moduleId: string;
158
+ }>;
159
+ queries: Array<{
160
+ type: string;
161
+ moduleId: string;
162
+ }>;
163
+ observers: string[];
164
+ runValidators: string[];
165
+ hiddenSessionKinds: Array<{
166
+ kind: string;
167
+ moduleId: string;
168
+ }>;
169
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -83,7 +83,8 @@ export declare class Engine {
83
83
  private runtimeToolRegistry;
84
84
  /** Engine-local view containing only this Engine's capability modules. */
85
85
  private toolRegistry;
86
- private readonly capabilities;
86
+ /** Compiled module contributions — the single composition fact source. */
87
+ private readonly composition;
87
88
  private readonly toolCatalog;
88
89
  private readonly toolGuards;
89
90
  /** Per-turn dynamic definition rewriters contributed by builtin exposures. */
@@ -3,14 +3,12 @@
3
3
  */
4
4
  import { createLLMClient } from "../llm/client-factory.js";
5
5
  import { ToolRegistry } from "../tool-system/registry.js";
6
- import { queryExtensionModules, registerExtensionModules, } from "../tool-system/capability-module.js";
7
6
  import { readLastTodoSnapshot } from "../tool-system/builtin/task.js";
8
7
  import { getMergedCatalog } from "../model-catalog/index.js";
9
8
  import { modelEntriesFromConnections } from "./model-connections-pool.js";
10
9
  import { cumulativeCacheHitRate, foldRunUsage, normalizeCumulativeUsageCounters, } from "../session/usage.js";
11
10
  import { enqueueSteerItem, consumeSteerItems, removeSteerItem, } from "./steer-queue.js";
12
11
  import { RunEnvironmentResolver } from "./run-environment.js";
13
- import { BUILTIN_TOOLS, } from "../tool-system/builtin/index.js";
14
12
  import { asyncAgentRegistry } from "../tool-system/builtin/agent-registry.js";
15
13
  import { skillToolDef } from "../tool-system/builtin/skill.js";
16
14
  import { backgroundShellManager } from "../runtime/background-shell.js";
@@ -35,8 +33,10 @@ import { resolveFeatureFlags, } from "../settings/feature-flags.js";
35
33
  import { effectiveBuiltinLists, effectiveDisabledList, effectiveProjectOverrides, } from "../capability-control/overlay.js";
36
34
  import { computeEffectiveDisabledLists } from "../capability-control/disabled-lists.js";
37
35
  import { registerFileHistoryHook } from "./file-history-hook.js";
38
- import { resolveAgentPreset, resolveBuiltinToolNames } from "../preset/index.js";
39
- import { composeDynamicContextProviders, composeCapabilityEngineHooks, composePromptSections, composeToolCatalog, resolveCapabilities, resolveInstructionBoundary, } from "../capabilities/index.js";
36
+ import { resolveToolNamesForPreset } from "../preset/index.js";
37
+ import { compileComposition } from "../composition/compiler.js";
38
+ import { ConfigError } from "../exceptions.js";
39
+ import { compositionPromptSections, compositionToolCatalog, presetInjectedTools, registerAlwaysTools, resolveCompositionInstructionBoundary, resolvePresetFromComposition, } from "../composition/resolve-preset.js";
40
40
  import { ModelPool } from "../llm/model-pool.js";
41
41
  import { AgentDefinitionRegistry } from "../agent/agent-definition-registry.js";
42
42
  import { defaultCacheDir } from "../llm/model-cache.js";
@@ -44,7 +44,6 @@ import { detectProviderFromApiKey, buildModelPool } from "../onboarding.js";
44
44
  import { detectPastedNoise } from "../utils/task-sanitizer.js";
45
45
  import { PromptCacheDiagnosticRecorder, promptCacheDropHint, } from "./prompt-cache-diagnostics.js";
46
46
  import { buildRunUserMessageContent, prepareRunImageInput } from "./run-image-input.js";
47
- import { ISOLATED_TASK_PROFILE, QUICK_CHAT_RESTRICTED_PROFILE, } from "./run-types.js";
48
47
  import { createSubAgentSpawner } from "./subagent-spawner.js";
49
48
  import { AuxiliaryPipeline, sameLlmIdentity } from "./auxiliary-pipeline.js";
50
49
  import { PermissionController } from "./permission-controller.js";
@@ -152,7 +151,8 @@ export class Engine {
152
151
  runtimeToolRegistry;
153
152
  /** Engine-local view containing only this Engine's capability modules. */
154
153
  toolRegistry;
155
- capabilities;
154
+ /** Compiled module contributions — the single composition fact source. */
155
+ composition;
156
156
  toolCatalog;
157
157
  toolGuards;
158
158
  /** Per-turn dynamic definition rewriters contributed by builtin exposures. */
@@ -400,9 +400,13 @@ export class Engine {
400
400
  },
401
401
  ...(this.runtime ? { runtime: this.runtime } : {}),
402
402
  });
403
- this.capabilities = resolveCapabilities(config.capabilities);
404
- this.config = { ...config, capabilities: this.capabilities };
405
- this.toolCatalog = composeToolCatalog(BUILTIN_TOOLS, this.capabilities, config.extensionModules ?? []);
403
+ if (config.composition && config.modules) {
404
+ throw new ConfigError("EngineConfig.composition is mutually exclusive with modules");
405
+ }
406
+ // Single composition fact source for every module contribution.
407
+ this.composition =
408
+ config.composition ?? compileComposition({ modules: config.modules ?? [] });
409
+ this.toolCatalog = compositionToolCatalog(this.composition);
406
410
  this.toolGuards = new Map(this.toolCatalog.flatMap((tool) => tool.exposure.availability
407
411
  ? [[tool.definition.name, tool.exposure.availability]]
408
412
  : []));
@@ -410,35 +414,31 @@ export class Engine {
410
414
  ? [[tool.definition.name, tool.exposure.rewriteDefinition]]
411
415
  : []));
412
416
  // Behavior profile registry: core defaults first, then host config, then
413
- // extension modules — later registrations override earlier ones by id.
417
+ // module contributions — later registrations override earlier ones by id.
418
+ const moduleProfiles = this.composition.engine.behaviorProfiles;
414
419
  this.behaviorProfiles = new Map([
415
- QUICK_CHAT_RESTRICTED_PROFILE,
416
- ISOLATED_TASK_PROFILE,
420
+ ...moduleProfiles.filter((p) => p.moduleId === "core").map((p) => p.value),
417
421
  ...(config.behaviorProfiles ?? []),
418
- ...(config.extensionModules ?? []).flatMap((module) => module.behaviorProfiles ?? []),
422
+ ...moduleProfiles.filter((p) => p.moduleId !== "core").map((p) => p.value),
419
423
  ].map((profile) => [profile.id, profile]));
420
- this.capabilityPromptSections = composePromptSections(this.capabilities);
421
- this.capabilityDynamicContextProviders = composeDynamicContextProviders(this.capabilities);
422
- this.preset = resolveAgentPreset(config.preset, this.capabilities);
423
- // Extension catalogTools join the active preset regardless of its name:
424
- // presets snapshot their tool lists from the catalogs known at module
425
- // load, which can never include extension packages. Visibility stays
426
- // gated by each tool's exposure.availability guard.
427
- const extensionCatalogTools = (config.extensionModules ?? []).flatMap((module) => [
428
- ...(module.catalogTools ?? []),
429
- ]);
430
- if (extensionCatalogTools.length > 0) {
424
+ this.capabilityPromptSections = compositionPromptSections(this.composition);
425
+ this.capabilityDynamicContextProviders = this.composition.engine.dynamicContextProviders.map((c) => c.value);
426
+ this.preset = resolvePresetFromComposition(this.composition, config.preset);
427
+ // Catalog tools owned by modules that contribute no presets join the
428
+ // active preset regardless of its name: presets snapshot their tool lists
429
+ // from catalogs known at module authoring time, which can never include
430
+ // such packages. Visibility stays gated by exposure.availability.
431
+ const injectedTools = presetInjectedTools(this.composition);
432
+ if (injectedTools.length > 0) {
431
433
  this.preset = {
432
434
  ...this.preset,
433
435
  builtinTools: [
434
436
  ...this.preset.builtinTools,
435
- ...extensionCatalogTools.map((tool) => tool.definition.name),
437
+ ...injectedTools.map((tool) => tool.definition.name),
436
438
  ],
437
439
  defaultPermissionRules: [
438
440
  ...this.preset.defaultPermissionRules,
439
- ...extensionCatalogTools.flatMap((tool) => [
440
- ...(tool.exposure.defaultPermissionRules ?? []),
441
- ]),
441
+ ...injectedTools.flatMap((tool) => [...(tool.exposure.defaultPermissionRules ?? [])]),
442
442
  ],
443
443
  };
444
444
  }
@@ -468,22 +468,22 @@ export class Engine {
468
468
  this.runtimeToolRegistry =
469
469
  config.runtime?.toolRegistry ??
470
470
  new ToolRegistry({
471
- builtinTools: resolveBuiltinToolNames({
472
- preset: this.preset.name,
471
+ builtinTools: resolveToolNamesForPreset({
472
+ preset: this.preset,
473
473
  host: config.builtinToolHost,
474
474
  enabledBuiltinTools: [
475
475
  ...builtinLists.enabledBuiltinTools,
476
- // Extension catalogTools are preset-agnostic (see preset merge
476
+ // Injected catalog tools are preset-agnostic (see preset merge
477
477
  // above); their availability guards gate actual visibility.
478
- ...extensionCatalogTools.map((tool) => tool.definition.name),
478
+ ...injectedTools.map((tool) => tool.definition.name),
479
479
  ],
480
480
  disabledBuiltinTools: builtinLists.disabledBuiltinTools,
481
- capabilities: this.capabilities,
481
+ adjusters: this.composition.engine.toolSelectionAdjusters.map((a) => a.value),
482
482
  }),
483
483
  toolCatalog: this.toolCatalog,
484
484
  });
485
485
  this.toolRegistry = this.runtimeToolRegistry.fork();
486
- registerExtensionModules(this.toolRegistry, config.extensionModules ?? []);
486
+ registerAlwaysTools(this.composition, this.toolRegistry);
487
487
  this.hooks = new HookRegistry();
488
488
  // Installed-plugin hooks — declared in each plugin's hooks/hooks.json.
489
489
  // Registered first (priority 80) so user-authored hooks at lower
@@ -506,15 +506,13 @@ export class Engine {
506
506
  // settings.hooks → shell-command wrappers. Chain order:
507
507
  // plugin (80) → shell (50) → capability (20) → SDK code (default 0).
508
508
  this.registerSettingsHooks();
509
- for (const hook of composeCapabilityEngineHooks(this.capabilities)) {
509
+ for (const hook of this.composition.engine.hooks) {
510
510
  this.hooks.register(hook.event, hook.handler, hook.priority, hook.name);
511
511
  }
512
512
  for (const hook of config.hooks ?? []) {
513
513
  this.hooks.register(hook.event, hook.handler, hook.priority, hook.name);
514
514
  }
515
- this.sessionManager = new SessionManager(config.sessionStorageDir, this.capabilities
516
- .map((capability) => capability.sessionWorkspace)
517
- .find((candidate) => candidate !== undefined));
515
+ this.sessionManager = new SessionManager(config.sessionStorageDir, this.composition.engine.sessionWorkspaces[0]?.value);
518
516
  // Initialize model pool — prefer runtime's shared pool, fall back to self-constructed.
519
517
  this.modelPool = config.runtime?.modelPool ?? new ModelPool();
520
518
  this.auxiliaryPipeline = new AuxiliaryPipeline({
@@ -666,8 +664,11 @@ export class Engine {
666
664
  this.toolRegistry.registerTool(definition, executor);
667
665
  }
668
666
  /** Dispatch a host-installed capability query without teaching core its name. */
669
- queryCapability(type, params = {}) {
670
- return queryExtensionModules(this.config.extensionModules ?? [], type, params);
667
+ async queryCapability(type, params = {}) {
668
+ const handler = this.composition.protocol.queries.find((q) => q.key === type)?.value;
669
+ if (!handler)
670
+ return { handled: false };
671
+ return { handled: true, data: await handler(params) };
671
672
  }
672
673
  /**
673
674
  * Inject the askUser handler after construction. Used by AgentServer
@@ -1756,9 +1757,7 @@ export class Engine {
1756
1757
  sessionDir: join(this.config.sessionStorageDir ?? sessionsRoot(), session.state.sessionId),
1757
1758
  cwd,
1758
1759
  getTurnSeq: () => session.state.turnSeq,
1759
- contributions: this.capabilities.flatMap((capability) => [
1760
- ...(capability.fileHistory ?? []),
1761
- ]),
1760
+ contributions: this.composition.engine.fileHistory.map((c) => c.value),
1762
1761
  });
1763
1762
  // Hook: agent start
1764
1763
  await this.emitHook("on_agent_start", {
@@ -2004,7 +2003,7 @@ export class Engine {
2004
2003
  sessionBrief: session.state.sessionBrief,
2005
2004
  profileMemoryDir,
2006
2005
  instructionCompatFileNames: compatFileNamesFrom(this.config.instructions),
2007
- instructionBoundaryFinder: (scanCwd) => resolveInstructionBoundary(scanCwd, this.capabilities),
2006
+ instructionBoundaryFinder: (scanCwd) => resolveCompositionInstructionBoundary(this.composition, scanCwd),
2008
2007
  disabledSkills,
2009
2008
  disabledPlugins,
2010
2009
  skillAllowlist: profileCanUseSkills ? toolCtx.skillAllowlist : [],
@@ -2558,22 +2557,23 @@ export class Engine {
2558
2557
  // (rebuilt per turn from this.preset) reflects the new preset's system
2559
2558
  // prompt / behavior. Only when the preset actually changed.
2560
2559
  if (patch.preset !== undefined && patch.preset !== prevPresetName) {
2561
- const nextPreset = resolveAgentPreset(this.config.preset, this.capabilities);
2560
+ const nextPreset = resolvePresetFromComposition(this.composition, this.config.preset);
2562
2561
  // The builtin tool SET is ctor-frozen and may be shared via runtime — we
2563
2562
  // do NOT rebuild it here. If the new preset implies a different builtin
2564
2563
  // tool set, that part of the change only lands on session restart.
2565
- const prevTools = resolveBuiltinToolNames({
2566
- preset: prevPresetName,
2564
+ const adjusters = this.composition.engine.toolSelectionAdjusters.map((a) => a.value);
2565
+ const prevTools = resolveToolNamesForPreset({
2566
+ preset: resolvePresetFromComposition(this.composition, prevPresetName),
2567
2567
  host: this.config.builtinToolHost,
2568
- capabilities: this.capabilities,
2568
+ adjusters,
2569
2569
  })
2570
2570
  .slice()
2571
2571
  .sort()
2572
2572
  .join(",");
2573
- const nextTools = resolveBuiltinToolNames({
2574
- preset: nextPreset.name,
2573
+ const nextTools = resolveToolNamesForPreset({
2574
+ preset: nextPreset,
2575
2575
  host: this.config.builtinToolHost,
2576
- capabilities: this.capabilities,
2576
+ adjusters,
2577
2577
  })
2578
2578
  .slice()
2579
2579
  .sort()
@@ -3278,17 +3278,15 @@ export class Engine {
3278
3278
  */
3279
3279
  buildToolContext(cwd = this.config.cwd ?? process.cwd(), explicitProfileOverrides, profileMemoryDir) {
3280
3280
  const { disabledSkills, disabledPlugins } = this.readDisabledLists(cwd, explicitProfileOverrides);
3281
- const capabilityServices = Object.fromEntries(this.capabilities.flatMap((capability) => {
3282
- if (!capability.createToolService)
3283
- return [];
3284
- const service = capability.createToolService({
3281
+ const capabilityServices = Object.fromEntries(this.composition.engine.toolServices.map(({ moduleId, value }) => {
3282
+ const service = value({
3285
3283
  isSubAgent: this.config.isSubAgent === true,
3286
3284
  settings: this.getSettingsManager(),
3287
3285
  resolveSandbox: (cwd) => this.runEnvironmentResolver.resolveSandbox(cwd),
3288
3286
  readShellEnv: (cwd) => this.runEnvironmentResolver.readShellEnv(cwd),
3289
3287
  getSessionManager: () => this.sessionManager,
3290
3288
  });
3291
- return [[capability.id, service]];
3289
+ return [moduleId, service];
3292
3290
  }));
3293
3291
  const ctx = {
3294
3292
  shellEnv: this.runEnvironmentResolver.readShellEnv(cwd),
@@ -237,8 +237,8 @@ export function createSubAgentSpawner(deps) {
237
237
  preset: deps.presetName,
238
238
  enabledBuiltinTools: scope.enabled,
239
239
  disabledBuiltinTools: scope.disabled,
240
- capabilities: deps.parentConfig.capabilities,
241
- extensionModules: deps.parentConfig.extensionModules,
240
+ composition: deps.parentConfig.composition,
241
+ modules: deps.parentConfig.modules,
242
242
  builtinToolHost: deps.parentConfig.builtinToolHost,
243
243
  customSystemPrompt: deps.parentConfig.customSystemPrompt,
244
244
  appendSystemPrompt: [deps.parentConfig.appendSystemPrompt, request.appendSystemPrompt]
@@ -21,8 +21,7 @@ import type { SettingsScope } from "../settings/manager.js";
21
21
  import type { EngineRuntime } from "./runtime.js";
22
22
  import type { HookEventName } from "../hooks/events.js";
23
23
  import type { HookHandler } from "../hooks/registry.js";
24
- import type { CapabilityModule } from "../capabilities/index.js";
25
- import type { ExtensionModule } from "../tool-system/capability-module.js";
24
+ import type { AgentModule, ResolvedComposition } from "../composition/types.js";
26
25
  import type { RunBehaviorProfile } from "./run-types.js";
27
26
  import type { LegacyPetWorkDelegation } from "../types.js";
28
27
  export interface EngineConfig {
@@ -45,10 +44,14 @@ export interface EngineConfig {
45
44
  preset?: AgentPresetName;
46
45
  enabledBuiltinTools?: string[];
47
46
  disabledBuiltinTools?: string[];
48
- /** Trusted optional product capabilities installed by the owning host. */
49
- capabilities?: readonly CapabilityModule[];
50
- /** Lightweight runtime tools and queries loaded by the host process. */
51
- extensionModules?: readonly ExtensionModule[];
47
+ /**
48
+ * Precompiled composition from the host root — the single source of truth
49
+ * for module contributions. Mutually exclusive with modules / capabilities
50
+ * / extensionModules.
51
+ */
52
+ composition?: ResolvedComposition;
53
+ /** AgentModules for library consumers; Engine compiles them once. */
54
+ modules?: readonly AgentModule[];
52
55
  /**
53
56
  * Named per-run behavior profiles selectable via EngineRunOptions.behaviorMode
54
57
  * or a profile's activateForSessionKinds. Merged (by id, later wins) over the
@@ -42,6 +42,14 @@ export declare class TranscriptError extends FrameworkError {
42
42
  export declare class ConfigError extends FrameworkError {
43
43
  constructor(message: string, details?: Record<string, unknown>);
44
44
  }
45
+ /**
46
+ * Thrown by compileComposition() on any conflicting or invalid module
47
+ * contribution. Structured details carry at least { code, key } plus the
48
+ * owning module ids so hosts can render actionable errors.
49
+ */
50
+ export declare class CompositionError extends FrameworkError {
51
+ constructor(message: string, details?: Record<string, unknown>);
52
+ }
45
53
  /**
46
54
  * Thrown by `resolveSandboxBackend` when an explicit sandbox mode is
47
55
  * requested but the corresponding backend is unavailable on this host
@@ -91,6 +91,17 @@ export class ConfigError extends FrameworkError {
91
91
  this.name = "ConfigError";
92
92
  }
93
93
  }
94
+ /**
95
+ * Thrown by compileComposition() on any conflicting or invalid module
96
+ * contribution. Structured details carry at least { code, key } plus the
97
+ * owning module ids so hosts can render actionable errors.
98
+ */
99
+ export class CompositionError extends FrameworkError {
100
+ constructor(message, details) {
101
+ super(message, details);
102
+ this.name = "CompositionError";
103
+ }
104
+ }
94
105
  // ─── Sandbox Errors ───────────────────────────────────────────────
95
106
  /**
96
107
  * Thrown by `resolveSandboxBackend` when an explicit sandbox mode is