@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
@@ -1,58 +1,28 @@
1
1
  import type { AgentPreset } from "../preset/index.js";
2
- import type { BuiltinTool } from "../tool-system/builtin/index.js";
3
2
  import type { SandboxBackend } from "../tool-system/sandbox/index.js";
4
3
  import type { SessionManager } from "../session/session-manager.js";
5
4
  import type { ArtifactKind, ArtifactRole } from "../run/types.js";
6
5
  import type { HookEventName } from "../hooks/events.js";
7
6
  import type { HookHandler } from "../hooks/registry.js";
8
7
  /**
9
- * A host-installable slice of agent behavior.
10
- *
11
- * Core owns the lifecycle and execution contracts; product packages contribute
12
- * tools, presets, and prompt text through this boundary. Modules are plain data
13
- * so a host can compose them per Engine without mutating process-global state.
8
+ * Contribution types shared by AgentModule engine contributions
9
+ * (src/composition/types.ts). The former CapabilityModule interface and its
10
+ * process-global registry were removed in the composition cutover product
11
+ * packages now ship AgentModule factories compiled at the host root.
14
12
  */
15
- export interface CapabilityModule {
16
- id: string;
17
- tools?: readonly BuiltinTool[];
18
- presets?: readonly AgentPreset[];
19
- /** Preset selected when this capability is installed and the host did not choose one. */
20
- defaultPreset?: string;
21
- promptSections?: Readonly<Record<string, string>>;
22
- /** Volatile, capability-owned context appended after the cacheable prompt prefix. */
23
- dynamicContextProviders?: readonly CapabilityDynamicContextProvider[];
24
- /** Optional project boundary for layered instruction discovery. */
25
- instructionBoundary?: CapabilityInstructionBoundaryFinder;
26
- /** Runtime services exposed only to this capability's tools. */
27
- createToolService?(host: CapabilityToolServiceHost): unknown;
28
- /** Capability-specific run artifact recognition. */
29
- artifactDetectors?: readonly CapabilityArtifactDetector[];
30
- /** Pre-mutation snapshot targets for capability-owned compound file tools. */
31
- fileHistory?: readonly CapabilityFileHistoryContribution[];
32
- /** Optional validation for persisted product-specific workspace pointers. */
33
- sessionWorkspace?: SessionWorkspaceCapability;
34
- /** Trusted in-process handlers joined to this Engine's normal hook chain. */
35
- engineHooks?: readonly CapabilityEngineHookContribution[];
36
- adjustToolSelection?(names: Set<string>, context: CapabilityToolSelectionContext): void;
37
- }
38
13
  export interface CapabilityEngineHookContribution {
39
14
  event: HookEventName;
40
15
  handler: HookHandler;
41
16
  priority?: number;
42
17
  name?: string;
43
18
  }
44
- export interface ResolvedCapabilityEngineHook extends CapabilityEngineHookContribution {
45
- capabilityId: string;
46
- priority: number;
47
- name: string;
48
- }
49
19
  export interface CapabilityDynamicContext {
50
20
  cwd: string;
51
21
  preset: AgentPreset;
52
22
  }
53
23
  export type CapabilityDynamicContextProvider = (context: CapabilityDynamicContext) => string | undefined | Promise<string | undefined>;
54
24
  export type CapabilityInstructionBoundaryFinder = (cwd: string) => string | null;
55
- /** Generic host services from which a capability may build its private tool service. */
25
+ /** Generic host services from which a module may build its private tool service. */
56
26
  export interface CapabilityToolServiceHost {
57
27
  readonly isSubAgent: boolean;
58
28
  readonly settings: {
@@ -88,22 +58,3 @@ export interface CapabilityFileHistoryContribution {
88
58
  toolName: string;
89
59
  resolveTargets(args: Record<string, unknown>, cwd: string): readonly string[];
90
60
  }
91
- /**
92
- * Install a capability at a process composition root (CLI/desktop worker).
93
- * Library consumers should prefer EngineConfig.capabilities for isolation.
94
- */
95
- export declare function registerCapability(capability: CapabilityModule): void;
96
- /** Primarily useful for isolated hosts and tests that own their process. */
97
- export declare function unregisterCapability(id: string): void;
98
- export declare function listRegisteredCapabilities(): CapabilityModule[];
99
- /** Merge process-installed and per-Engine modules, rejecting ambiguous IDs. */
100
- export declare function resolveCapabilities(local?: readonly CapabilityModule[]): CapabilityModule[];
101
- export declare function composeToolCatalog(coreTools: readonly BuiltinTool[], capabilities: readonly CapabilityModule[], extensionModules?: readonly {
102
- catalogTools?: readonly BuiltinTool[];
103
- }[]): BuiltinTool[];
104
- export declare function composePromptSections(capabilities: readonly CapabilityModule[]): Record<string, string>;
105
- export declare function composeDynamicContextProviders(capabilities: readonly CapabilityModule[]): CapabilityDynamicContextProvider[];
106
- export declare function composeArtifactDetectors(capabilities: readonly CapabilityModule[]): CapabilityArtifactDetector[];
107
- /** Normalize code hooks with deterministic names and a product-module priority. */
108
- export declare function composeCapabilityEngineHooks(capabilities: readonly CapabilityModule[]): ResolvedCapabilityEngineHook[];
109
- export declare function resolveInstructionBoundary(cwd: string, capabilities: readonly CapabilityModule[]): string | null;
@@ -1,80 +1 @@
1
- const installedCapabilities = new Map();
2
- /**
3
- * Install a capability at a process composition root (CLI/desktop worker).
4
- * Library consumers should prefer EngineConfig.capabilities for isolation.
5
- */
6
- export function registerCapability(capability) {
7
- const existing = installedCapabilities.get(capability.id);
8
- if (existing === capability)
9
- return;
10
- if (existing)
11
- throw new Error(`Capability '${capability.id}' is already registered`);
12
- installedCapabilities.set(capability.id, capability);
13
- }
14
- /** Primarily useful for isolated hosts and tests that own their process. */
15
- export function unregisterCapability(id) {
16
- installedCapabilities.delete(id);
17
- }
18
- export function listRegisteredCapabilities() {
19
- return [...installedCapabilities.values()];
20
- }
21
- /** Merge process-installed and per-Engine modules, rejecting ambiguous IDs. */
22
- export function resolveCapabilities(local = []) {
23
- const resolved = new Map(installedCapabilities);
24
- for (const capability of local) {
25
- const existing = resolved.get(capability.id);
26
- if (existing && existing !== capability) {
27
- throw new Error(`Capability '${capability.id}' was provided more than once`);
28
- }
29
- resolved.set(capability.id, capability);
30
- }
31
- return [...resolved.values()];
32
- }
33
- export function composeToolCatalog(coreTools, capabilities, extensionModules = []) {
34
- const catalog = new Map();
35
- for (const tool of [
36
- ...coreTools,
37
- ...capabilities.flatMap((capability) => [...(capability.tools ?? [])]),
38
- ...extensionModules.flatMap((module) => [...(module.catalogTools ?? [])]),
39
- ]) {
40
- const name = tool.definition.name;
41
- if (catalog.has(name))
42
- throw new Error(`Tool '${name}' is contributed more than once`);
43
- catalog.set(name, tool);
44
- }
45
- return [...catalog.values()];
46
- }
47
- export function composePromptSections(capabilities) {
48
- const sections = {};
49
- for (const capability of capabilities) {
50
- for (const [name, content] of Object.entries(capability.promptSections ?? {})) {
51
- if (name in sections)
52
- throw new Error(`Prompt section '${name}' is contributed more than once`);
53
- sections[name] = content;
54
- }
55
- }
56
- return sections;
57
- }
58
- export function composeDynamicContextProviders(capabilities) {
59
- return capabilities.flatMap((capability) => [...(capability.dynamicContextProviders ?? [])]);
60
- }
61
- export function composeArtifactDetectors(capabilities) {
62
- return capabilities.flatMap((capability) => [...(capability.artifactDetectors ?? [])]);
63
- }
64
- /** Normalize code hooks with deterministic names and a product-module priority. */
65
- export function composeCapabilityEngineHooks(capabilities) {
66
- return capabilities.flatMap((capability) => (capability.engineHooks ?? []).map((hook, index) => ({
67
- ...hook,
68
- capabilityId: capability.id,
69
- priority: hook.priority ?? 20,
70
- name: `capability:${capability.id}:${hook.name ?? `${hook.event}:${index}`}`,
71
- })));
72
- }
73
- export function resolveInstructionBoundary(cwd, capabilities) {
74
- for (const capability of capabilities) {
75
- const boundary = capability.instructionBoundary?.(cwd);
76
- if (boundary)
77
- return boundary;
78
- }
79
- return null;
80
- }
1
+ export {};
@@ -54,31 +54,29 @@ import { cronScheduler } from "../automation/scheduler.js";
54
54
  import { CronStore, defaultCronStorePath } from "../automation/store.js";
55
55
  import { resolveLLMConfigForTag } from "../engine/resolve-llm-config.js";
56
56
  import { createIpcCredentialAccess, setDefaultCredentialAccess } from "../credentials/access.js";
57
- async function loadConfiguredExtensionModules() {
57
+ import { compileComposition } from "../composition/compiler.js";
58
+ /**
59
+ * Load AgentModules from CODE_SHELL_CAPABILITY_MODULES: comma-separated
60
+ * "specifier#exportName" entries (exportName defaults to createModule).
61
+ * Fail loud — a silently skipped module used to surface much later as an
62
+ * unexplained missing tool or "unknown behavior profile: pet".
63
+ */
64
+ async function loadConfiguredAgentModules() {
58
65
  const specs = (process.env.CODE_SHELL_CAPABILITY_MODULES ?? "")
59
66
  .split(",")
60
67
  .map((entry) => entry.trim())
61
68
  .filter(Boolean);
62
69
  const modules = [];
63
70
  for (const spec of specs) {
64
- const [moduleId, exportName = "createCapability"] = spec.split("#", 2);
71
+ const [moduleId, exportName = "createModule"] = spec.split("#", 2);
65
72
  if (!moduleId)
66
73
  continue;
67
- try {
68
- const loaded = (await import(moduleId));
69
- const factory = loaded[exportName];
70
- if (typeof factory !== "function") {
71
- throw new Error(`export ${exportName} is not a capability factory`);
72
- }
73
- modules.push(factory());
74
- }
75
- catch (err) {
76
- logger.warn("capability.module_unavailable", {
77
- moduleId,
78
- exportName,
79
- error: err instanceof Error ? err.message : String(err),
80
- });
74
+ const loaded = (await import(moduleId));
75
+ const factory = loaded[exportName];
76
+ if (typeof factory !== "function") {
77
+ throw new Error(`CODE_SHELL_CAPABILITY_MODULES: export ${exportName} of ${moduleId} is not an AgentModule factory`);
81
78
  }
79
+ modules.push(factory());
82
80
  }
83
81
  return modules;
84
82
  }
@@ -112,7 +110,9 @@ export function resolveSessionCwd(slice) {
112
110
  }
113
111
  // ─── Read base config from environment / settings ─────────────────
114
112
  const cwd = process.env.AGENT_CWD ?? process.cwd();
115
- const extensionModules = await loadConfiguredExtensionModules();
113
+ // Compile ONCE at the host root; seed engine, per-session engines and the
114
+ // AgentServer all consume this same composition (design §9.3).
115
+ const composition = compileComposition({ modules: await loadConfiguredAgentModules() });
116
116
  // Injectable data root (identity dimension foundations, Task 3). Server
117
117
  // Phase 2 per-user workers spawn this entry with CODE_SHELL_DATA_ROOT set to
118
118
  // the user's isolated data root; session persistence (engine session store,
@@ -159,7 +159,7 @@ const seedEngine = new Engine({
159
159
  enabledBuiltinTools: settings.agent.enabledBuiltinTools,
160
160
  disabledBuiltinTools: settings.agent.disabledBuiltinTools,
161
161
  builtinToolHost: "desktop",
162
- extensionModules,
162
+ composition,
163
163
  settingsScope: "full",
164
164
  // No runtime — Engine.populateModelPoolFromSettings() runs in ctor.
165
165
  });
@@ -265,7 +265,7 @@ const chatManager = new ChatSessionManager({
265
265
  // session it creates is a desktop-origin session.
266
266
  origin: "desktop",
267
267
  builtinToolHost: "desktop",
268
- extensionModules,
268
+ composition,
269
269
  // Inherit full scope so spawned subagents read user config too.
270
270
  settingsScope: "full",
271
271
  // MCP servers from settings — the worker reads the full disk
@@ -350,11 +350,11 @@ setCapabilityChangedSink(() => {
350
350
  // ChatSession (that only happens on a send), so chatManager.get() misses. This
351
351
  // reads the same ~/.code-shell/sessions/<id>/state.json every Engine writes, so
352
352
  // the goal block re-surfaces on load ("goal 还在但页面不显示" fix).
353
- const goalDiskReader = new SessionManager(dataSessionsDir);
353
+ const goalDiskReader = new SessionManager(dataSessionsDir, composition.engine.sessionWorkspaces[0]?.value);
354
354
  const agentServer = new AgentServer({
355
355
  chatManager,
356
356
  transport: stdioTransport,
357
- extensionModules,
357
+ composition,
358
358
  workspaceBridge: true,
359
359
  panelBridge: true,
360
360
  // Cold background-wakeup rehydrate must read the same sessions store the
@@ -0,0 +1,7 @@
1
+ import type { CompileCompositionOptions, ResolvedComposition } from "./types.js";
2
+ /**
3
+ * Pure composition compiler (design §7): same input produces the same
4
+ * frozen ResolvedComposition, identical order and digest. Conflicts fail
5
+ * loud with both owning module ids; no I/O, no resource creation.
6
+ */
7
+ export declare function compileComposition(options?: CompileCompositionOptions): ResolvedComposition;
@@ -0,0 +1,247 @@
1
+ import { CompositionError } from "../exceptions.js";
2
+ import { DEFAULT_AGENT_PRESET } from "../preset/index.js";
3
+ import { CORE_AGENT_MODULE } from "./core-module.js";
4
+ import { computeCompositionDigest, toCompositionSnapshot } from "./snapshot.js";
5
+ const MODULE_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
6
+ function registerModules(options) {
7
+ const core = options.core ?? CORE_AGENT_MODULE;
8
+ const registered = [];
9
+ const seen = new Set();
10
+ const push = (module, source) => {
11
+ if (!MODULE_ID_PATTERN.test(module.id)) {
12
+ throw new CompositionError(`Invalid module id: "${module.id}"`, {
13
+ code: "invalid_module_id",
14
+ key: module.id,
15
+ });
16
+ }
17
+ if (seen.has(module.id)) {
18
+ throw new CompositionError(`Duplicate module id: "${module.id}"`, {
19
+ code: "duplicate_module",
20
+ key: module.id,
21
+ });
22
+ }
23
+ seen.add(module.id);
24
+ registered.push({
25
+ module,
26
+ resolved: { id: module.id, order: registered.length, source },
27
+ });
28
+ };
29
+ push(core, "core");
30
+ for (const module of options.modules ?? [])
31
+ push(module, "host");
32
+ for (const id of options.expectedModules ?? []) {
33
+ if (!seen.has(id)) {
34
+ throw new CompositionError(`Expected module "${id}" is missing`, {
35
+ code: "missing_expected_module",
36
+ key: id,
37
+ });
38
+ }
39
+ }
40
+ return registered;
41
+ }
42
+ function moduleDiagnostics(registered) {
43
+ const diagnostics = [];
44
+ for (const { module, resolved } of registered) {
45
+ if (resolved.source === "core")
46
+ continue;
47
+ const hasEngine = module.engine !== undefined;
48
+ const hasProtocol = module.protocol !== undefined;
49
+ if (!hasEngine && !hasProtocol) {
50
+ diagnostics.push({
51
+ code: "empty_module",
52
+ moduleId: module.id,
53
+ message: `Module "${module.id}" declares no contributions`,
54
+ });
55
+ }
56
+ else if (!hasProtocol) {
57
+ diagnostics.push({
58
+ code: "engine_only_module",
59
+ moduleId: module.id,
60
+ message: `Module "${module.id}" contributes engine surface only`,
61
+ });
62
+ }
63
+ else if (!hasEngine) {
64
+ diagnostics.push({
65
+ code: "protocol_only_module",
66
+ moduleId: module.id,
67
+ message: `Module "${module.id}" contributes protocol surface only`,
68
+ });
69
+ }
70
+ }
71
+ return diagnostics;
72
+ }
73
+ function duplicate(kind, key, firstModuleId, secondModuleId) {
74
+ return new CompositionError(`Duplicate ${kind} "${key}" contributed by "${firstModuleId}" and "${secondModuleId}"`, { code: `duplicate_${kind.replaceAll(" ", "_")}`, key, firstModuleId, secondModuleId });
75
+ }
76
+ /** Collect keyed contributions across modules, failing loud on duplicates. */
77
+ function collectKeyed(registered, kind, pick) {
78
+ const owners = new Map();
79
+ const collected = [];
80
+ for (const { module } of registered) {
81
+ for (const [key, value] of pick(module)) {
82
+ const owner = owners.get(key);
83
+ if (owner !== undefined)
84
+ throw duplicate(kind, key, owner, module.id);
85
+ owners.set(key, module.id);
86
+ collected.push({ key, moduleId: module.id, value });
87
+ }
88
+ }
89
+ return collected;
90
+ }
91
+ /** One optional contribution per module (e.g. instructionBoundary). */
92
+ function collectSingle(registered, pick) {
93
+ return registered.flatMap(({ module }) => {
94
+ const value = pick(module);
95
+ return value === undefined ? [] : [{ key: module.id, moduleId: module.id, value }];
96
+ });
97
+ }
98
+ /** Ordered, non-keyed contributions (providers, detectors, file-history). */
99
+ function collectMany(registered, pick) {
100
+ return registered.flatMap(({ module }) => (pick(module) ?? []).map((value, index) => ({
101
+ key: `${module.id}:${index}`,
102
+ moduleId: module.id,
103
+ value,
104
+ })));
105
+ }
106
+ function collectEngine(registered, diagnostics) {
107
+ // Tools: preset-tags join the composed catalog (module order), always tools
108
+ // are appended afterwards — mirroring composeToolCatalog() followed by
109
+ // registerExtensionModules() in the current engine constructor.
110
+ const toolOwners = new Map();
111
+ const presetTagTools = [];
112
+ const alwaysTools = [];
113
+ for (const { module } of registered) {
114
+ for (const contribution of module.engine?.tools ?? []) {
115
+ const name = contribution.tool.definition.name;
116
+ const owner = toolOwners.get(name);
117
+ if (owner !== undefined)
118
+ throw duplicate("tool", name, owner, module.id);
119
+ toolOwners.set(name, module.id);
120
+ if (contribution.kind === "preset-tags") {
121
+ presetTagTools.push({ kind: "preset-tags", moduleId: module.id, tool: contribution.tool });
122
+ }
123
+ else {
124
+ alwaysTools.push({ kind: "always", moduleId: module.id, tool: contribution.tool });
125
+ }
126
+ }
127
+ }
128
+ const tools = [...presetTagTools, ...alwaysTools];
129
+ // Presets: a host module may shadow a CORE preset of the same name — this
130
+ // mirrors resolveAgentPreset(), which resolves capability-contributed
131
+ // presets before builtins (e.g. coding's extended "general"). The shadow is
132
+ // surfaced as a diagnostic; host-vs-host duplicates still fail loud.
133
+ const presets = [];
134
+ const presetIndex = new Map();
135
+ const coreModuleId = registered[0]?.resolved.id ?? "core";
136
+ for (const { module } of registered) {
137
+ for (const preset of module.engine?.presets ?? []) {
138
+ const existing = presetIndex.get(preset.name);
139
+ if (existing === undefined) {
140
+ presetIndex.set(preset.name, presets.length);
141
+ presets.push({ key: preset.name, moduleId: module.id, value: preset });
142
+ continue;
143
+ }
144
+ const owner = presets[existing];
145
+ if (!owner || owner.moduleId !== coreModuleId) {
146
+ throw duplicate("preset", preset.name, owner?.moduleId ?? coreModuleId, module.id);
147
+ }
148
+ presets[existing] = { key: preset.name, moduleId: module.id, value: preset };
149
+ diagnostics.push({
150
+ code: "core_preset_shadowed",
151
+ moduleId: module.id,
152
+ message: `Module "${module.id}" shadows core preset "${preset.name}"`,
153
+ });
154
+ }
155
+ }
156
+ // Default preset: at most one distinct declaration wins; none → core default.
157
+ let defaultPreset;
158
+ for (const { module } of registered) {
159
+ const declared = module.engine?.defaultPreset;
160
+ if (!declared)
161
+ continue;
162
+ if (defaultPreset && defaultPreset.name !== declared) {
163
+ throw new CompositionError(`Conflicting default presets: "${defaultPreset.name}" (${defaultPreset.moduleId}) vs "${declared}" (${module.id})`, {
164
+ code: "conflicting_default_preset",
165
+ key: declared,
166
+ firstModuleId: defaultPreset.moduleId,
167
+ secondModuleId: module.id,
168
+ });
169
+ }
170
+ defaultPreset ??= { name: declared, moduleId: module.id };
171
+ }
172
+ const defaultPresetName = defaultPreset?.name ?? DEFAULT_AGENT_PRESET;
173
+ if (!presets.some((p) => p.key === defaultPresetName)) {
174
+ throw new CompositionError(`Default preset "${defaultPresetName}" is not contributed`, {
175
+ code: "unknown_default_preset",
176
+ key: defaultPresetName,
177
+ firstModuleId: defaultPreset?.moduleId ?? "core",
178
+ });
179
+ }
180
+ // Preset tool references must resolve to preset-tags tools.
181
+ const presetTagToolNames = new Set(presetTagTools.map((t) => t.tool.definition.name));
182
+ for (const { key, moduleId, value } of presets) {
183
+ for (const toolName of value.builtinTools) {
184
+ if (!presetTagToolNames.has(toolName)) {
185
+ throw new CompositionError(`Preset "${key}" references unknown tool "${toolName}"`, {
186
+ code: "unknown_preset_tool",
187
+ key: toolName,
188
+ firstModuleId: moduleId,
189
+ });
190
+ }
191
+ }
192
+ }
193
+ const promptSections = collectKeyed(registered, "prompt section", (m) => Object.entries(m.engine?.promptSections ?? {}));
194
+ const behaviorProfiles = collectKeyed(registered, "behavior profile", (m) => (m.engine?.behaviorProfiles ?? []).map((p) => [p.id, p]));
195
+ const hooks = registered.flatMap(({ module }) => (module.engine?.hooks ?? []).map((hook, index) => ({
196
+ moduleId: module.id,
197
+ event: hook.event,
198
+ handler: hook.handler,
199
+ priority: hook.priority ?? 20,
200
+ name: `capability:${module.id}:${hook.name ?? `${hook.event}:${index}`}`,
201
+ })));
202
+ return {
203
+ tools,
204
+ presets,
205
+ defaultPreset: defaultPresetName,
206
+ promptSections,
207
+ dynamicContextProviders: collectMany(registered, (m) => m.engine?.dynamicContextProviders),
208
+ instructionBoundaries: collectSingle(registered, (m) => m.engine?.instructionBoundary),
209
+ artifactDetectors: collectMany(registered, (m) => m.engine?.artifactDetectors),
210
+ fileHistory: collectMany(registered, (m) => m.engine?.fileHistory),
211
+ sessionWorkspaces: collectSingle(registered, (m) => m.engine?.sessionWorkspace),
212
+ hooks,
213
+ behaviorProfiles,
214
+ toolSelectionAdjusters: collectSingle(registered, (m) => m.engine?.adjustToolSelection),
215
+ toolServices: collectSingle(registered, (m) => m.engine?.createToolService),
216
+ };
217
+ }
218
+ function collectProtocol(registered) {
219
+ const queries = collectKeyed(registered, "query", (m) => Object.entries(m.protocol?.queries ?? {}));
220
+ const hiddenSessionKinds = collectKeyed(registered, "hidden session kind", (m) => (m.protocol?.hiddenSessionKinds ?? []).map((k) => [k, k]));
221
+ return {
222
+ queries,
223
+ observerFactories: collectSingle(registered, (m) => m.protocol?.createObserver),
224
+ runValidators: collectSingle(registered, (m) => m.protocol?.validateRunParams),
225
+ hiddenSessionKinds,
226
+ };
227
+ }
228
+ /**
229
+ * Pure composition compiler (design §7): same input produces the same
230
+ * frozen ResolvedComposition, identical order and digest. Conflicts fail
231
+ * loud with both owning module ids; no I/O, no resource creation.
232
+ */
233
+ export function compileComposition(options = {}) {
234
+ const registered = registerModules(options);
235
+ const diagnostics = moduleDiagnostics(registered);
236
+ const engine = collectEngine(registered, diagnostics);
237
+ const protocol = collectProtocol(registered);
238
+ const draft = {
239
+ version: 1,
240
+ modules: Object.freeze(registered.map((r) => r.resolved)),
241
+ engine,
242
+ protocol,
243
+ diagnostics: Object.freeze(diagnostics),
244
+ };
245
+ const digest = computeCompositionDigest(toCompositionSnapshot(draft));
246
+ return Object.freeze({ ...draft, digest });
247
+ }
@@ -0,0 +1,10 @@
1
+ import type { AgentModule } from "./types.js";
2
+ /**
3
+ * Core's own declarations expressed as a module so one compiler handles a
4
+ * single data path. This does NOT make core unloadable or overridable —
5
+ * it is always module order 0 and duplicate keys against it fail loud.
6
+ *
7
+ * No defaultPreset here: the compiler falls back to DEFAULT_AGENT_PRESET
8
+ * only when no product module declares one (mirrors resolveAgentPreset).
9
+ */
10
+ export declare const CORE_AGENT_MODULE: AgentModule;
@@ -0,0 +1,19 @@
1
+ import { BUILTIN_TOOLS } from "../tool-system/builtin/index.js";
2
+ import { BUILTIN_AGENT_PRESETS } from "../preset/index.js";
3
+ import { ISOLATED_TASK_PROFILE, QUICK_CHAT_RESTRICTED_PROFILE } from "../engine/run-types.js";
4
+ /**
5
+ * Core's own declarations expressed as a module so one compiler handles a
6
+ * single data path. This does NOT make core unloadable or overridable —
7
+ * it is always module order 0 and duplicate keys against it fail loud.
8
+ *
9
+ * No defaultPreset here: the compiler falls back to DEFAULT_AGENT_PRESET
10
+ * only when no product module declares one (mirrors resolveAgentPreset).
11
+ */
12
+ export const CORE_AGENT_MODULE = {
13
+ id: "core",
14
+ engine: {
15
+ tools: BUILTIN_TOOLS.map((tool) => ({ kind: "preset-tags", tool })),
16
+ presets: Object.values(BUILTIN_AGENT_PRESETS),
17
+ behaviorProfiles: [QUICK_CHAT_RESTRICTED_PROFILE, ISOLATED_TASK_PROFILE],
18
+ },
19
+ };
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export { CORE_AGENT_MODULE } from "./core-module.js";
3
+ export { compileComposition } from "./compiler.js";
4
+ export { toCompositionSnapshot, computeCompositionDigest } from "./snapshot.js";
5
+ export { compositionPromptSections, compositionToolCatalog, presetInjectedTools, registerAlwaysTools, resolveCompositionInstructionBoundary, resolvePresetFromComposition, } from "./resolve-preset.js";
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export { CORE_AGENT_MODULE } from "./core-module.js";
3
+ export { compileComposition } from "./compiler.js";
4
+ export { toCompositionSnapshot, computeCompositionDigest } from "./snapshot.js";
5
+ export { compositionPromptSections, compositionToolCatalog, presetInjectedTools, registerAlwaysTools, resolveCompositionInstructionBoundary, resolvePresetFromComposition, } from "./resolve-preset.js";
@@ -0,0 +1,15 @@
1
+ import type { ExtensionQueryHandler, ProtocolObserver, ProtocolObserverHost } from "../tool-system/capability-module.js";
2
+ import type { ResolvedComposition } from "./types.js";
3
+ /**
4
+ * Wire a resolved protocol composition into an AgentServer's observer list
5
+ * and query dispatch table. Observer factories run first (isolated per
6
+ * module — a throwing factory only loses its own observer); declared
7
+ * queries never clobber a handler an observer registered at create time.
8
+ */
9
+ export declare function attachProtocolContributions(opts: {
10
+ protocol: ResolvedComposition["protocol"];
11
+ host: ProtocolObserverHost;
12
+ observers: ProtocolObserver[];
13
+ queryHandlers: Map<string, ExtensionQueryHandler>;
14
+ warn: (message: string) => void;
15
+ }): void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Wire a resolved protocol composition into an AgentServer's observer list
3
+ * and query dispatch table. Observer factories run first (isolated per
4
+ * module — a throwing factory only loses its own observer); declared
5
+ * queries never clobber a handler an observer registered at create time.
6
+ */
7
+ export function attachProtocolContributions(opts) {
8
+ for (const factory of opts.protocol.observerFactories) {
9
+ try {
10
+ opts.observers.push(factory.value(opts.host));
11
+ }
12
+ catch (err) {
13
+ opts.warn(`protocol observer init failed for module ${factory.moduleId}: ${err.message}`);
14
+ }
15
+ }
16
+ for (const query of opts.protocol.queries) {
17
+ if (!opts.queryHandlers.has(query.key)) {
18
+ opts.queryHandlers.set(query.key, query.value);
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Runtime helpers over a ResolvedComposition. Preset selection and
3
+ * validation happen HERE, at use time — not at compile time — because the
4
+ * preset can change per session (config slice) and per settings hot reload.
5
+ */
6
+ import type { AgentPreset } from "../preset/index.js";
7
+ import type { BuiltinTool } from "../tool-system/builtin/index.js";
8
+ import type { ExtensionTool } from "../tool-system/capability-module.js";
9
+ import type { ResolvedComposition } from "./types.js";
10
+ /** Resolve a preset by name (or the composition default), failing loud. */
11
+ export declare function resolvePresetFromComposition(composition: ResolvedComposition, name?: string): AgentPreset;
12
+ /** All preset-tags tools — the composed catalog in effective order. */
13
+ export declare function compositionToolCatalog(composition: ResolvedComposition): BuiltinTool[];
14
+ /**
15
+ * Preset-tags tools force-joined to the ACTIVE preset regardless of its name.
16
+ * Rule: tools owned by modules that contribute no presets (pet-style catalog
17
+ * tools) — presets snapshot their tool lists from catalogs known at module
18
+ * authoring time, which can never include such packages. Modules that DO
19
+ * contribute presets (core, coding) reference their tools via preset tags
20
+ * already. Visibility stays gated by each tool's exposure.availability.
21
+ */
22
+ export declare function presetInjectedTools(composition: ResolvedComposition): BuiltinTool[];
23
+ /** Module-contributed named prompt sections as a plain record. */
24
+ export declare function compositionPromptSections(composition: ResolvedComposition): Record<string, string>;
25
+ /**
26
+ * Register always-exposure tools on the engine-local registry fork.
27
+ * Cross-module uniqueness is compiler-enforced; this guards collisions with
28
+ * runtime-registered tools.
29
+ */
30
+ export declare function registerAlwaysTools(composition: ResolvedComposition, registry: {
31
+ hasTool(name: string): boolean;
32
+ registerTool(definition: ExtensionTool["definition"], execute: ExtensionTool["execute"]): void;
33
+ }): void;
34
+ /** First non-null module instruction boundary, in module order. */
35
+ export declare function resolveCompositionInstructionBoundary(composition: ResolvedComposition, cwd: string): string | null;