@capekai/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/package.json +105 -0
- package/src/adapters/ai-sdk.ts +84 -0
- package/src/compaction/contracts.ts +82 -0
- package/src/compaction/executor.ts +161 -0
- package/src/compaction/policy.ts +318 -0
- package/src/compaction/recovery.ts +139 -0
- package/src/compaction/task.ts +540 -0
- package/src/configuration/contracts.ts +58 -0
- package/src/configuration/defaults.ts +27 -0
- package/src/configuration/runtime.ts +42 -0
- package/src/configuration/single-model.ts +75 -0
- package/src/context/assembler.ts +112 -0
- package/src/context/index.ts +2 -0
- package/src/context/sources.ts +119 -0
- package/src/context/workspace.ts +63 -0
- package/src/core/agent.ts +401 -0
- package/src/core/build-tools.ts +139 -0
- package/src/core/chat-handler.ts +858 -0
- package/src/core/error-handling.ts +18 -0
- package/src/core/fork.ts +103 -0
- package/src/core/interrupt.ts +192 -0
- package/src/core/message-utils.ts +261 -0
- package/src/core/model-utils.ts +149 -0
- package/src/core/part-utils.ts +88 -0
- package/src/core/provider-utils.ts +67 -0
- package/src/core/revert.ts +46 -0
- package/src/core/step-handlers.ts +157 -0
- package/src/core/stream/finalization.ts +65 -0
- package/src/core/stream/stream-config.ts +82 -0
- package/src/core/stream-handlers.ts +242 -0
- package/src/core/structured-output.ts +68 -0
- package/src/core/tool-builders/agent-tools.ts +71 -0
- package/src/core/tool-builders/external-tools.ts +179 -0
- package/src/core/tool-builders/types.ts +16 -0
- package/src/core/tool-builders/workspace-tools.ts +293 -0
- package/src/core/tool-capabilities.ts +65 -0
- package/src/goals/evaluator.ts +171 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/loop.ts +167 -0
- package/src/goals/service.ts +39 -0
- package/src/index.ts +10 -0
- package/src/internal/ask-authority.ts +29 -0
- package/src/internal/composition.ts +44 -0
- package/src/internal/configuration.ts +22 -0
- package/src/internal/execution.ts +108 -0
- package/src/internal/hosts.ts +64 -0
- package/src/internal/plugins.ts +71 -0
- package/src/internal/providers.ts +32 -0
- package/src/internal/sandbox.ts +19 -0
- package/src/internal/tools.ts +48 -0
- package/src/internal/workspace.ts +25 -0
- package/src/kernel/diagnostics.ts +249 -0
- package/src/kernel/errors.ts +120 -0
- package/src/kernel/events.ts +82 -0
- package/src/kernel/index.ts +72 -0
- package/src/kernel/kernel.ts +62 -0
- package/src/kernel/lifecycle.ts +72 -0
- package/src/kernel/plugin.ts +218 -0
- package/src/kernel/registry.ts +493 -0
- package/src/kernel/scope.ts +776 -0
- package/src/kernel/service-key.ts +19 -0
- package/src/kernel/types.ts +317 -0
- package/src/memory/index.ts +2 -0
- package/src/memory/memory-tool.ts +75 -0
- package/src/memory/registry.ts +172 -0
- package/src/permission/ask-user-api.ts +70 -0
- package/src/permission/contracts.ts +135 -0
- package/src/permission/permission-request-manager.ts +58 -0
- package/src/permission/policy.ts +277 -0
- package/src/permission/runtime.ts +612 -0
- package/src/plugins/compaction-policy.ts +46 -0
- package/src/plugins/compose.ts +171 -0
- package/src/plugins/context-sections.ts +246 -0
- package/src/plugins/default-agent-driver.ts +14 -0
- package/src/plugins/facade-plugins.ts +129 -0
- package/src/plugins/goal-domain.ts +82 -0
- package/src/plugins/legacy-system-message.ts +152 -0
- package/src/plugins/loaded-tools.ts +23 -0
- package/src/plugins/memory-domain.ts +264 -0
- package/src/plugins/orchestrator-session.ts +29 -0
- package/src/plugins/permission-policy.ts +49 -0
- package/src/plugins/retry-policy.ts +28 -0
- package/src/plugins/scheduler-domain.ts +192 -0
- package/src/plugins/service-keys.ts +294 -0
- package/src/plugins/session-search-domain.ts +238 -0
- package/src/plugins/skills-domain.ts +272 -0
- package/src/plugins/subagent-domain.ts +287 -0
- package/src/plugins/tool-catalog.ts +78 -0
- package/src/plugins/tool-output-policy.ts +52 -0
- package/src/plugins/value-plugins.ts +150 -0
- package/src/plugins/workflow-domain.ts +198 -0
- package/src/plugins/workspace-policy.ts +37 -0
- package/src/providers/registry.ts +63 -0
- package/src/providers/types.ts +44 -0
- package/src/retry/policy.ts +282 -0
- package/src/retry/stream-chat.ts +312 -0
- package/src/runtime/agent-runtime.ts +83 -0
- package/src/runtime/default-agent-driver.ts +23 -0
- package/src/runtime/domain-tool-source.ts +156 -0
- package/src/runtime/events.ts +61 -0
- package/src/runtime/host-dependencies.ts +71 -0
- package/src/runtime/host-guidance.ts +22 -0
- package/src/runtime/host-layout.ts +23 -0
- package/src/runtime/host.ts +129 -0
- package/src/runtime/standalone-host.ts +118 -0
- package/src/sandbox/controller.ts +204 -0
- package/src/sandbox/model.ts +305 -0
- package/src/sandbox/provider.ts +53 -0
- package/src/sandbox/types.ts +110 -0
- package/src/scheduler/host.ts +22 -0
- package/src/scheduler/scheduler-tool.ts +172 -0
- package/src/session-search/host.ts +56 -0
- package/src/session-search/index.ts +23 -0
- package/src/session-search/session-search-tool.ts +151 -0
- package/src/skills/index.ts +3 -0
- package/src/skills/registry.ts +63 -0
- package/src/skills/skill-manage-tool.ts +205 -0
- package/src/skills/skill-tool.ts +42 -0
- package/src/storage/contracts.ts +159 -0
- package/src/storage/memory.ts +321 -0
- package/src/storage/options.ts +75 -0
- package/src/storage/runtime.ts +115 -0
- package/src/storage/sqlite-tool-output-artifacts.ts +106 -0
- package/src/storage/sqlite.ts +321 -0
- package/src/storage/tool-output-artifacts.ts +75 -0
- package/src/storage.ts +31 -0
- package/src/subagent/child-session.ts +282 -0
- package/src/subagent/guidance.ts +8 -0
- package/src/subagent/policy.ts +198 -0
- package/src/subagent/task-tool.ts +584 -0
- package/src/tool-output/contracts.ts +111 -0
- package/src/tool-output/policy.ts +410 -0
- package/src/tool.ts +1 -0
- package/src/tools/executor.ts +258 -0
- package/src/tools/install-manifest.ts +40 -0
- package/src/tools/llm-api.ts +77 -0
- package/src/tools/registry.ts +206 -0
- package/src/tools/tool-artifact.ts +182 -0
- package/src/tools/tool-source.ts +53 -0
- package/src/utils/errors.ts +334 -0
- package/src/utils/strip-visualization.ts +50 -0
- package/src/workflow/decomposer.ts +139 -0
- package/src/workflow/execution.ts +523 -0
- package/src/workflow/orchestrator-session.ts +161 -0
- package/src/workflow/synthesizer.ts +130 -0
- package/src/workspace/contracts.ts +135 -0
- package/src/workspace/policy.ts +327 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public workspace policy entrypoint (`@capekai/core/workspace`).
|
|
3
|
+
*
|
|
4
|
+
* Exposes exactly the workspace path policy identities the Jean2 server
|
|
5
|
+
* consumes through its workspace-paths adapter: containment and
|
|
6
|
+
* classification algorithms plus the service constructors. Every symbol
|
|
7
|
+
* resolves to the owning module's identity, identical to the compatibility
|
|
8
|
+
* barrel. S8a.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
expandPath,
|
|
13
|
+
isInsideUnselectedAdditionalRoot,
|
|
14
|
+
isPathInside,
|
|
15
|
+
isPathWithinWorkspace,
|
|
16
|
+
resolveCandidatePath,
|
|
17
|
+
resolvePath,
|
|
18
|
+
resolveRootForQuery,
|
|
19
|
+
selectEditableRoot,
|
|
20
|
+
} from '../workspace/policy';
|
|
21
|
+
export type {
|
|
22
|
+
WorkspacePolicy,
|
|
23
|
+
WorkspacePolicyOptions,
|
|
24
|
+
WorkspaceService,
|
|
25
|
+
} from '../workspace/contracts';
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only composition diagnostics. Snapshots are built fresh on every
|
|
3
|
+
* call, contain no plugin options and no service values, and can therefore
|
|
4
|
+
* be surfaced to support and tests without leaking secrets.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ContextPhase,
|
|
9
|
+
ContextSectionContribution,
|
|
10
|
+
EffectiveContextSection,
|
|
11
|
+
EffectiveTool,
|
|
12
|
+
EventListenerContribution,
|
|
13
|
+
ListenerDiagnostic,
|
|
14
|
+
PluginStatus,
|
|
15
|
+
ProvidedContextSection,
|
|
16
|
+
RunStatus,
|
|
17
|
+
RunTerminalOutcome,
|
|
18
|
+
RuntimeScope,
|
|
19
|
+
ScopeDiagnosticsSnapshot,
|
|
20
|
+
ScopeStatus,
|
|
21
|
+
ServiceDiagnostic,
|
|
22
|
+
ServiceKey,
|
|
23
|
+
ToolContribution,
|
|
24
|
+
} from './types';
|
|
25
|
+
|
|
26
|
+
export const CONTEXT_PHASES: readonly ContextPhase[] = [
|
|
27
|
+
'identity',
|
|
28
|
+
'preferences',
|
|
29
|
+
'instructions',
|
|
30
|
+
'workspace',
|
|
31
|
+
'capabilities',
|
|
32
|
+
'task',
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export interface LocalToolRegistration {
|
|
36
|
+
readonly contribution: ToolContribution;
|
|
37
|
+
readonly pluginId: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LocalContextSectionRegistration {
|
|
41
|
+
readonly contribution: ContextSectionContribution;
|
|
42
|
+
readonly pluginId: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface LocalListenerRegistration {
|
|
46
|
+
readonly contribution: EventListenerContribution;
|
|
47
|
+
readonly pluginId: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
export interface ResolvedService {
|
|
52
|
+
readonly key: ServiceKey<unknown>;
|
|
53
|
+
readonly providerPluginId: string;
|
|
54
|
+
readonly providerScope: RuntimeScope;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Structural view of a scope used by snapshot and listing helpers. */
|
|
58
|
+
export interface ScopeStateView {
|
|
59
|
+
readonly kind: RuntimeScope;
|
|
60
|
+
readonly scopeId: string;
|
|
61
|
+
readonly parent: ScopeStateView | null;
|
|
62
|
+
readonly status: ScopeStatus;
|
|
63
|
+
readonly pluginRecords: readonly {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
readonly version?: string;
|
|
66
|
+
readonly scope: RuntimeScope;
|
|
67
|
+
readonly status: PluginStatus;
|
|
68
|
+
}[];
|
|
69
|
+
readonly localServices: ReadonlyMap<string, ResolvedService>;
|
|
70
|
+
readonly localTools: ReadonlyMap<string, LocalToolRegistration>;
|
|
71
|
+
readonly localContextSections: ReadonlyMap<string, LocalContextSectionRegistration>;
|
|
72
|
+
readonly localListeners: ReadonlyMap<string, LocalListenerRegistration>;
|
|
73
|
+
readonly cleanupBarrierCount: number;
|
|
74
|
+
readonly runId?: string;
|
|
75
|
+
readonly runStatus?: RunStatus;
|
|
76
|
+
readonly runOutcome?: RunTerminalOutcome;
|
|
77
|
+
resolveServiceRecord(keyId: string): ResolvedService | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function chainOf(view: ScopeStateView): ScopeStateView[] {
|
|
81
|
+
const chain: ScopeStateView[] = [];
|
|
82
|
+
for (let current: ScopeStateView | null = view; current !== null; current = current.parent) {
|
|
83
|
+
chain.push(current);
|
|
84
|
+
}
|
|
85
|
+
return chain;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function compareIds(a: string, b: string): number {
|
|
89
|
+
if (a < b) return -1;
|
|
90
|
+
if (a > b) return 1;
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function collectEffectiveServices(view: ScopeStateView): ServiceDiagnostic[] {
|
|
95
|
+
const services: ServiceDiagnostic[] = [];
|
|
96
|
+
const seen = new Set<string>();
|
|
97
|
+
for (const scope of chainOf(view)) {
|
|
98
|
+
for (const [keyId, registration] of scope.localServices) {
|
|
99
|
+
if (seen.has(keyId)) continue;
|
|
100
|
+
seen.add(keyId);
|
|
101
|
+
services.push({
|
|
102
|
+
keyId,
|
|
103
|
+
keyScope: registration.key.scope,
|
|
104
|
+
providerPluginId: registration.providerPluginId,
|
|
105
|
+
providerScope: scope.kind,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return services;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function collectEffectiveTools(view: ScopeStateView): EffectiveTool[] {
|
|
113
|
+
const tools: EffectiveTool[] = [];
|
|
114
|
+
const seen = new Set<string>();
|
|
115
|
+
for (const scope of chainOf(view)) {
|
|
116
|
+
for (const [toolId, registration] of scope.localTools) {
|
|
117
|
+
if (seen.has(toolId)) continue;
|
|
118
|
+
seen.add(toolId);
|
|
119
|
+
const hiddenReasons: string[] = [];
|
|
120
|
+
const explicit = registration.contribution.visibility;
|
|
121
|
+
if (explicit?.visible === false) {
|
|
122
|
+
hiddenReasons.push(explicit.reason ?? 'explicitly hidden by tool visibility');
|
|
123
|
+
}
|
|
124
|
+
for (const capability of registration.contribution.requiredCapabilities ?? []) {
|
|
125
|
+
const resolved = view.resolveServiceRecord(capability.id);
|
|
126
|
+
if (resolved === null || resolved.key.scope !== capability.scope) {
|
|
127
|
+
hiddenReasons.push(`missing required capability '${capability.id}'`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
tools.push({
|
|
131
|
+
id: toolId,
|
|
132
|
+
order: registration.contribution.order ?? 0,
|
|
133
|
+
definition: registration.contribution.definition,
|
|
134
|
+
...(registration.contribution.payload !== undefined
|
|
135
|
+
? { payload: registration.contribution.payload }
|
|
136
|
+
: {}),
|
|
137
|
+
pluginId: registration.pluginId,
|
|
138
|
+
visible: hiddenReasons.length === 0,
|
|
139
|
+
hiddenReasons,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
tools.sort((a, b) => (
|
|
144
|
+
a.order - b.order
|
|
145
|
+
|| compareIds(a.pluginId, b.pluginId)
|
|
146
|
+
|| compareIds(a.id, b.id)
|
|
147
|
+
));
|
|
148
|
+
return tools;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function collectEffectiveContextSections(
|
|
152
|
+
view: ScopeStateView,
|
|
153
|
+
): EffectiveContextSection[] {
|
|
154
|
+
const sections: EffectiveContextSection[] = [];
|
|
155
|
+
const seen = new Set<string>();
|
|
156
|
+
for (const scope of chainOf(view)) {
|
|
157
|
+
for (const [sectionId, registration] of scope.localContextSections) {
|
|
158
|
+
if (seen.has(sectionId)) continue;
|
|
159
|
+
seen.add(sectionId);
|
|
160
|
+
sections.push({
|
|
161
|
+
id: sectionId,
|
|
162
|
+
phase: registration.contribution.phase,
|
|
163
|
+
order: registration.contribution.order,
|
|
164
|
+
pluginId: registration.pluginId,
|
|
165
|
+
scopeKind: scope.kind,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
sections.sort((a, b) => (
|
|
170
|
+
CONTEXT_PHASES.indexOf(a.phase) - CONTEXT_PHASES.indexOf(b.phase)
|
|
171
|
+
|| a.order - b.order
|
|
172
|
+
|| compareIds(a.pluginId, b.pluginId)
|
|
173
|
+
|| compareIds(a.id, b.id)
|
|
174
|
+
));
|
|
175
|
+
return sections;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function collectEffectiveListeners(view: ScopeStateView): ListenerDiagnostic[] {
|
|
179
|
+
const listeners: ListenerDiagnostic[] = [];
|
|
180
|
+
const seen = new Set<string>();
|
|
181
|
+
for (const scope of chainOf(view)) {
|
|
182
|
+
for (const [listenerId, registration] of scope.localListeners) {
|
|
183
|
+
if (seen.has(listenerId)) continue;
|
|
184
|
+
seen.add(listenerId);
|
|
185
|
+
listeners.push({
|
|
186
|
+
id: listenerId,
|
|
187
|
+
eventTypes: registration.contribution.eventTypes,
|
|
188
|
+
pluginId: registration.pluginId,
|
|
189
|
+
scopeKind: scope.kind,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return listeners;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
export function buildSnapshot(view: ScopeStateView): ScopeDiagnosticsSnapshot {
|
|
198
|
+
const runFields = view.runId !== undefined
|
|
199
|
+
? { runId: view.runId, runStatus: view.runStatus, runOutcome: view.runOutcome }
|
|
200
|
+
: {};
|
|
201
|
+
return {
|
|
202
|
+
scopeId: view.scopeId,
|
|
203
|
+
kind: view.kind,
|
|
204
|
+
parentKind: view.parent?.kind ?? null,
|
|
205
|
+
status: view.status,
|
|
206
|
+
plugins: view.pluginRecords.map((record) => ({
|
|
207
|
+
id: record.id,
|
|
208
|
+
version: record.version,
|
|
209
|
+
scope: record.scope,
|
|
210
|
+
status: record.status,
|
|
211
|
+
})),
|
|
212
|
+
services: collectEffectiveServices(view),
|
|
213
|
+
tools: collectEffectiveTools(view).map(({ definition: _definition, payload: _payload, ...tool }) => tool),
|
|
214
|
+
contextSections: collectEffectiveContextSections(view).map((section) => ({
|
|
215
|
+
id: section.id,
|
|
216
|
+
phase: section.phase,
|
|
217
|
+
order: section.order,
|
|
218
|
+
pluginId: section.pluginId,
|
|
219
|
+
scopeKind: section.scopeKind,
|
|
220
|
+
})),
|
|
221
|
+
listeners: collectEffectiveListeners(view),
|
|
222
|
+
cleanupBarrierCount: view.cleanupBarrierCount,
|
|
223
|
+
...runFields,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Assembles effective context sections in deterministic order. Null
|
|
228
|
+
* sections are omitted without shifting the other sections. The optional
|
|
229
|
+
* `data` is passed through to every provider as `ContextBuildContext.data`. */
|
|
230
|
+
export async function buildContextSections(
|
|
231
|
+
view: ScopeStateView,
|
|
232
|
+
data?: unknown,
|
|
233
|
+
): Promise<ProvidedContextSection[]> {
|
|
234
|
+
const chain = chainOf(view);
|
|
235
|
+
const provided: ProvidedContextSection[] = [];
|
|
236
|
+
for (const section of collectEffectiveContextSections(view)) {
|
|
237
|
+
const owningScope = chain.find((scope) => scope.kind === section.scopeKind);
|
|
238
|
+
const contribution = owningScope?.localContextSections.get(section.id)?.contribution;
|
|
239
|
+
if (contribution === undefined) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const content = await contribution.provide({ kind: section.scopeKind, data });
|
|
243
|
+
if (content === null) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
provided.push({ id: section.id, phase: section.phase, content });
|
|
247
|
+
}
|
|
248
|
+
return provided;
|
|
249
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel error types. Validation and lifecycle failures are typed so callers
|
|
3
|
+
* can distinguish malformed composition from runtime behavior.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { KernelEventType } from './types';
|
|
7
|
+
|
|
8
|
+
export function errorMessage(err: unknown): string {
|
|
9
|
+
return err instanceof Error ? err.message : String(err);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class KernelError extends Error {
|
|
13
|
+
constructor(message: string) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = new.target.name;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Composition rejected before activation: nothing ran. */
|
|
20
|
+
export class CompositionError extends KernelError {}
|
|
21
|
+
|
|
22
|
+
export class DuplicatePluginError extends CompositionError {}
|
|
23
|
+
|
|
24
|
+
export class MalformedPluginError extends CompositionError {}
|
|
25
|
+
|
|
26
|
+
export class ScopeValidationError extends CompositionError {}
|
|
27
|
+
|
|
28
|
+
/** A child scope attempted to provide a service already effective in a
|
|
29
|
+
* parent scope. Child scopes do not replace parent services. */
|
|
30
|
+
export class ServiceCollisionError extends CompositionError {}
|
|
31
|
+
|
|
32
|
+
export class DuplicateProviderError extends CompositionError {}
|
|
33
|
+
|
|
34
|
+
export class InvalidOverrideError extends CompositionError {}
|
|
35
|
+
|
|
36
|
+
export class MissingDependencyError extends CompositionError {}
|
|
37
|
+
|
|
38
|
+
export class DependencyCycleError extends CompositionError {}
|
|
39
|
+
|
|
40
|
+
/** A contribution id was registered twice within one scope. */
|
|
41
|
+
export class DuplicateContributionError extends CompositionError {}
|
|
42
|
+
|
|
43
|
+
export interface DisposalFailure {
|
|
44
|
+
readonly pluginId: string;
|
|
45
|
+
readonly error: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Aggregated errors from reverse disposal. Every resource is still
|
|
49
|
+
* attempted even when earlier disposers fail. */
|
|
50
|
+
export class DisposalError extends KernelError {
|
|
51
|
+
readonly failures: readonly DisposalFailure[];
|
|
52
|
+
|
|
53
|
+
constructor(failures: readonly DisposalFailure[]) {
|
|
54
|
+
const details = failures
|
|
55
|
+
.map((failure) => `plugin '${failure.pluginId}': ${errorMessage(failure.error)}`)
|
|
56
|
+
.join('; ');
|
|
57
|
+
super(`disposal failed with ${failures.length} error(s): ${details}`);
|
|
58
|
+
this.failures = failures;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Setup failed. Completed registrations were rolled back in reverse order.
|
|
63
|
+
* The original setup error and any disposal errors are both preserved. */
|
|
64
|
+
export class ActivationError extends KernelError {
|
|
65
|
+
readonly pluginId: string;
|
|
66
|
+
override readonly cause: unknown;
|
|
67
|
+
readonly disposalErrors: readonly DisposalFailure[];
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
pluginId: string,
|
|
71
|
+
cause: unknown,
|
|
72
|
+
disposalErrors: readonly DisposalFailure[],
|
|
73
|
+
) {
|
|
74
|
+
const rollback = disposalErrors.length > 0
|
|
75
|
+
? `; ${disposalErrors.length} disposal error(s) during rollback`
|
|
76
|
+
: '';
|
|
77
|
+
super(`plugin '${pluginId}' setup failed: ${errorMessage(cause)}${rollback}`);
|
|
78
|
+
this.pluginId = pluginId;
|
|
79
|
+
this.cause = cause;
|
|
80
|
+
this.disposalErrors = disposalErrors;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A lifecycle call arrived in the wrong state (for example start twice). */
|
|
85
|
+
export class LifecycleError extends KernelError {}
|
|
86
|
+
|
|
87
|
+
export interface ListenerFailure {
|
|
88
|
+
readonly listenerId: string;
|
|
89
|
+
readonly error: unknown;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** One or more event listeners threw during an awaited emit. Remaining
|
|
93
|
+
* listeners were still dispatched. */
|
|
94
|
+
export class EventEmitError extends KernelError {
|
|
95
|
+
readonly eventType: KernelEventType;
|
|
96
|
+
readonly failures: readonly ListenerFailure[];
|
|
97
|
+
|
|
98
|
+
constructor(eventType: KernelEventType, failures: readonly ListenerFailure[]) {
|
|
99
|
+
const details = failures
|
|
100
|
+
.map((failure) => `listener '${failure.listenerId}': ${errorMessage(failure.error)}`)
|
|
101
|
+
.join('; ');
|
|
102
|
+
super(`event '${eventType}' dispatch failed: ${details}`);
|
|
103
|
+
this.eventType = eventType;
|
|
104
|
+
this.failures = failures;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Errors collected while a run moved from terminal to disposed. Cleanup and
|
|
109
|
+
* disposal still ran to completion before this error was raised. */
|
|
110
|
+
export class RunTerminalError extends KernelError {
|
|
111
|
+
readonly runId: string;
|
|
112
|
+
readonly errors: readonly unknown[];
|
|
113
|
+
|
|
114
|
+
constructor(runId: string, errors: readonly unknown[]) {
|
|
115
|
+
const details = errors.map(errorMessage).join('; ');
|
|
116
|
+
super(`run '${runId}' terminal cleanup had ${errors.length} error(s): ${details}`);
|
|
117
|
+
this.runId = runId;
|
|
118
|
+
this.errors = errors;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple awaited emit. Listeners observe typed runtime events; the kernel
|
|
3
|
+
* dispatches each listener sequentially and awaits it. There is no serial
|
|
4
|
+
* or waterfall interception framework: listeners cannot stop or replace
|
|
5
|
+
* events.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { EventEmitError, MalformedPluginError, type ListenerFailure } from './errors';
|
|
9
|
+
import type {
|
|
10
|
+
EventListenerContribution,
|
|
11
|
+
KernelEvent,
|
|
12
|
+
KernelEventType,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
const KNOWN_EVENT_TYPES = new Set<string>(['run:started', 'run:terminal', 'run:disposed']);
|
|
16
|
+
|
|
17
|
+
export interface ListenerRegistration {
|
|
18
|
+
readonly contribution: EventListenerContribution;
|
|
19
|
+
readonly pluginId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function validateListener(contribution: EventListenerContribution): void {
|
|
23
|
+
if (typeof contribution !== 'object' || contribution === null) {
|
|
24
|
+
throw new MalformedPluginError('listener contribution must be an object');
|
|
25
|
+
}
|
|
26
|
+
if (typeof contribution.id !== 'string' || contribution.id.length === 0) {
|
|
27
|
+
throw new MalformedPluginError('listener contribution id must be a non-empty string');
|
|
28
|
+
}
|
|
29
|
+
if (!Array.isArray(contribution.eventTypes)) {
|
|
30
|
+
throw new MalformedPluginError(
|
|
31
|
+
`listener '${contribution.id}' eventTypes must be an array`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (contribution.eventTypes.length === 0) {
|
|
35
|
+
throw new MalformedPluginError(
|
|
36
|
+
`listener '${contribution.id}' must declare at least one event type`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
for (const eventType of contribution.eventTypes) {
|
|
40
|
+
if (!KNOWN_EVENT_TYPES.has(eventType)) {
|
|
41
|
+
throw new MalformedPluginError(
|
|
42
|
+
`listener '${contribution.id}' declares unknown event type '${String(eventType)}'`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (typeof contribution.handle !== 'function') {
|
|
47
|
+
throw new MalformedPluginError(
|
|
48
|
+
`listener '${contribution.id}' must provide a handle function`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ListenerScopeView {
|
|
54
|
+
readonly listeners: readonly ListenerRegistration[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Dispatches an event to every matching listener in a snapshot chain,
|
|
58
|
+
* child scope first then ancestors. All listeners run even when earlier
|
|
59
|
+
* ones throw; failures are aggregated into EventEmitError after dispatch
|
|
60
|
+
* completes. */
|
|
61
|
+
export async function dispatchEvent(
|
|
62
|
+
chain: readonly ListenerScopeView[],
|
|
63
|
+
event: KernelEvent,
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
const failures: ListenerFailure[] = [];
|
|
66
|
+
for (const scope of chain) {
|
|
67
|
+
for (const registration of scope.listeners) {
|
|
68
|
+
const { contribution } = registration;
|
|
69
|
+
if (!contribution.eventTypes.includes(event.type as KernelEventType)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
await contribution.handle(event);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
failures.push({ listenerId: contribution.id, error: err });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (failures.length > 0) {
|
|
80
|
+
throw new EventEmitError(event.type, failures);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal kernel barrel. Tests import from here; the package root index
|
|
3
|
+
* intentionally does not re-export the kernel.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { createAgentScope, createProcessScope, createRunScope } from './kernel';
|
|
7
|
+
export { serviceKey } from './service-key';
|
|
8
|
+
export { dispatchEvent, validateListener } from './events';
|
|
9
|
+
export {
|
|
10
|
+
ActivationError,
|
|
11
|
+
CompositionError,
|
|
12
|
+
DependencyCycleError,
|
|
13
|
+
DisposalError,
|
|
14
|
+
DuplicateContributionError,
|
|
15
|
+
DuplicatePluginError,
|
|
16
|
+
DuplicateProviderError,
|
|
17
|
+
EventEmitError,
|
|
18
|
+
InvalidOverrideError,
|
|
19
|
+
KernelError,
|
|
20
|
+
LifecycleError,
|
|
21
|
+
MalformedPluginError,
|
|
22
|
+
MissingDependencyError,
|
|
23
|
+
RunTerminalError,
|
|
24
|
+
ScopeValidationError,
|
|
25
|
+
ServiceCollisionError,
|
|
26
|
+
errorMessage,
|
|
27
|
+
} from './errors';
|
|
28
|
+
export type {
|
|
29
|
+
DisposalFailure,
|
|
30
|
+
ListenerFailure,
|
|
31
|
+
} from './errors';
|
|
32
|
+
export type {
|
|
33
|
+
AgentScopeHandle,
|
|
34
|
+
CapekPlugin,
|
|
35
|
+
CleanupBarrier,
|
|
36
|
+
CompositionDiagnostics,
|
|
37
|
+
ContextBuildContext,
|
|
38
|
+
ContextPhase,
|
|
39
|
+
ContextSectionContribution,
|
|
40
|
+
ContextSectionDiagnostic,
|
|
41
|
+
Disposable,
|
|
42
|
+
EffectiveContextSection,
|
|
43
|
+
EffectiveTool,
|
|
44
|
+
EventListenerContribution,
|
|
45
|
+
KernelEvent,
|
|
46
|
+
KernelEventMap,
|
|
47
|
+
KernelEventType,
|
|
48
|
+
ListenerDiagnostic,
|
|
49
|
+
PluginContext,
|
|
50
|
+
PluginDiagnostic,
|
|
51
|
+
PluginOptionsMap,
|
|
52
|
+
ProcessScopeHandle,
|
|
53
|
+
ProvidedContextSection,
|
|
54
|
+
RunCancellation,
|
|
55
|
+
RunDisposedEvent,
|
|
56
|
+
RunScopeHandle,
|
|
57
|
+
RunStartedEvent,
|
|
58
|
+
RunStatus,
|
|
59
|
+
RunTerminalEvent,
|
|
60
|
+
RunTerminalOutcome,
|
|
61
|
+
RuntimeScope,
|
|
62
|
+
ScopeDiagnosticsSnapshot,
|
|
63
|
+
ScopeHandle,
|
|
64
|
+
ScopeStatus,
|
|
65
|
+
ServiceDiagnostic,
|
|
66
|
+
ServiceKey,
|
|
67
|
+
ServiceOverride,
|
|
68
|
+
ToolContribution,
|
|
69
|
+
ToolDefinition,
|
|
70
|
+
ToolDiagnostic,
|
|
71
|
+
ToolVisibility,
|
|
72
|
+
} from './types';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel creation API. This is an internal composition entry point used by
|
|
3
|
+
* the C2 plugin layer. The kernel is not exported from the package root;
|
|
4
|
+
* `createAgent()` composes through `src/plugins`, never by importing the
|
|
5
|
+
* kernel directly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { LifecycleError } from './errors';
|
|
9
|
+
import {
|
|
10
|
+
AgentScope,
|
|
11
|
+
ProcessScope,
|
|
12
|
+
agentScopeBrand,
|
|
13
|
+
processScopeBrand,
|
|
14
|
+
} from './scope';
|
|
15
|
+
import type {
|
|
16
|
+
AgentScopeHandle,
|
|
17
|
+
CapekPlugin,
|
|
18
|
+
PluginOptionsMap,
|
|
19
|
+
ProcessScopeHandle,
|
|
20
|
+
RunScopeHandle,
|
|
21
|
+
} from './types';
|
|
22
|
+
|
|
23
|
+
/** Scope parents are validated through brands registered in the global
|
|
24
|
+
* symbol registry (Symbol.for), so a parent scope created through a
|
|
25
|
+
* duplicate module instance still carries the same brand. The check
|
|
26
|
+
* validates the parent link structurally across module instances; it is
|
|
27
|
+
* not a security boundary against code that can read the global symbol
|
|
28
|
+
* registry. */
|
|
29
|
+
function carriesBrand(target: unknown, brand: symbol): boolean {
|
|
30
|
+
return typeof target === 'object' && target !== null
|
|
31
|
+
&& (target as Record<symbol, unknown>)[brand] === true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function createProcessScope(
|
|
35
|
+
plugins: readonly CapekPlugin<unknown>[],
|
|
36
|
+
options?: PluginOptionsMap,
|
|
37
|
+
): Promise<ProcessScopeHandle> {
|
|
38
|
+
return ProcessScope.create(plugins, options);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function createAgentScope(
|
|
42
|
+
parent: ProcessScopeHandle,
|
|
43
|
+
plugins: readonly CapekPlugin<unknown>[],
|
|
44
|
+
options?: PluginOptionsMap,
|
|
45
|
+
): Promise<AgentScopeHandle> {
|
|
46
|
+
if (!carriesBrand(parent, processScopeBrand)) {
|
|
47
|
+
throw new LifecycleError('an agent scope requires a process scope parent');
|
|
48
|
+
}
|
|
49
|
+
return (parent as ProcessScope).createAgentScope(plugins, options);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function createRunScope(
|
|
53
|
+
parent: AgentScopeHandle,
|
|
54
|
+
runId: string,
|
|
55
|
+
plugins: readonly CapekPlugin<unknown>[],
|
|
56
|
+
options?: PluginOptionsMap,
|
|
57
|
+
): Promise<RunScopeHandle> {
|
|
58
|
+
if (!carriesBrand(parent, agentScopeBrand)) {
|
|
59
|
+
throw new LifecycleError('a run scope requires an agent scope parent');
|
|
60
|
+
}
|
|
61
|
+
return (parent as AgentScope).createRunScope(runId, plugins, options);
|
|
62
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activation and disposal execution. Activation follows the deterministic
|
|
3
|
+
* plan; on failure, completed registrations roll back in reverse order with
|
|
4
|
+
* both the setup error and disposal errors preserved. Normal disposal runs
|
|
5
|
+
* in reverse activation order and aggregates failures.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ActivationError,
|
|
10
|
+
DisposalError,
|
|
11
|
+
type DisposalFailure,
|
|
12
|
+
} from './errors';
|
|
13
|
+
import { enforceDeclaredProvides, isDisposable, type PluginRecord } from './plugin';
|
|
14
|
+
import type { PluginOptionsMap } from './types';
|
|
15
|
+
|
|
16
|
+
export async function activateRecords(
|
|
17
|
+
records: readonly PluginRecord[],
|
|
18
|
+
order: readonly string[],
|
|
19
|
+
options: PluginOptionsMap,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const byId = new Map(records.map((record) => [record.id, record]));
|
|
22
|
+
const completed: PluginRecord[] = [];
|
|
23
|
+
for (const id of order) {
|
|
24
|
+
const record = byId.get(id) as PluginRecord;
|
|
25
|
+
try {
|
|
26
|
+
const result = await record.plugin.setup(record.context, options[id]);
|
|
27
|
+
if (isDisposable(result)) {
|
|
28
|
+
record.returnedDisposable = result;
|
|
29
|
+
}
|
|
30
|
+
enforceDeclaredProvides(record);
|
|
31
|
+
record.status = 'active';
|
|
32
|
+
completed.push(record);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
record.status = 'failed';
|
|
35
|
+
const failures = await disposeRecords([...completed, record]);
|
|
36
|
+
throw new ActivationError(record.id, err, failures);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Disposes records in reverse order. Every disposer is attempted even when
|
|
42
|
+
* earlier disposers throw. Returns the collected failures. */
|
|
43
|
+
export async function disposeRecords(
|
|
44
|
+
records: readonly PluginRecord[],
|
|
45
|
+
): Promise<DisposalFailure[]> {
|
|
46
|
+
const failures: DisposalFailure[] = [];
|
|
47
|
+
for (const record of [...records].reverse()) {
|
|
48
|
+
if (record.status === 'disposed') continue;
|
|
49
|
+
for (const disposer of [...record.context.disposers].reverse()) {
|
|
50
|
+
try {
|
|
51
|
+
await disposer.dispose();
|
|
52
|
+
} catch (err) {
|
|
53
|
+
failures.push({ pluginId: record.id, error: err });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (record.returnedDisposable !== undefined) {
|
|
57
|
+
try {
|
|
58
|
+
await record.returnedDisposable.dispose();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
failures.push({ pluginId: record.id, error: err });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
record.status = 'disposed';
|
|
64
|
+
}
|
|
65
|
+
return failures;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function throwDisposalError(failures: readonly DisposalFailure[]): void {
|
|
69
|
+
if (failures.length > 0) {
|
|
70
|
+
throw new DisposalError(failures);
|
|
71
|
+
}
|
|
72
|
+
}
|