-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathcontext.ts
More file actions
207 lines (190 loc) · 6.79 KB
/
Copy pathcontext.ts
File metadata and controls
207 lines (190 loc) · 6.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import type {
ArchitectPlanContext,
ArchitectPlanWriteContext,
ArchitectStoredState,
ArchitectTabTypeDefinition,
ArchetypeProviderDefinition,
MarkdownFenceDefinition,
PluginManifest,
PolicyProposal,
ResourceDefinition,
ToolDefinition,
UiElementDefinition,
} from "@dreamgraph/sdk";
/**
* Runtime surface passed to every plugin handler. Mirrors the contract
* specified in `plans/DREAMGRAPH_SDK_ROADMAP.md` §5.3.
*
* The deny list lives in `docs/sdk/plugin-context.md`. In M3 this is an
* API boundary, not a sandbox: `ctx` does not expose raw `fs`, `process`,
* dynamic `import()`, internal-tier writers, tension/graph mutation,
* `setInterval` / `setTimeout` helpers, or arbitrary network primitives.
* Plugin code that reaches around `ctx` to Node globals is treated as a
* trust violation and grounds for quarantine, not as a sandbox escape.
*
* Stronger runtime isolation is deferred to M8 (worker-thread runtime).
*/
export interface PluginIdentitySurface {
readonly id: string;
readonly version: string;
}
export interface PluginInstanceSurface {
readonly uuid: string;
}
export interface PluginLogger {
debug(message: string, meta?: object): void;
info(message: string, meta?: object): void;
warn(message: string, meta?: object): void;
error(message: string, meta?: object): void;
}
export interface PluginEventEnvelope {
readonly kind: string;
readonly payload: unknown;
}
export type PluginEventHandler = (
event: PluginEventEnvelope,
) => void | Promise<void>;
export interface PluginEventEmitSurface {
emit(kind: string, payload: unknown): void;
/**
* Subscribe to a stable event kind. Returns an `unsubscribe` function.
* Hosts that do not yet wire the subscription seam may return a no-op
* unsubscribe and log a warning; callers should not rely on this for
* critical-path behavior until the seam is documented as live.
*/
subscribe(kind: string, handler: PluginEventHandler): () => void;
}
/**
* Tools surface — register MCP tools contributed by the plugin (M4).
*
* Registration is stateful in the host's contribution registry. The
* returned `unregister` function removes the entry so subsequent MCP
* sessions no longer see the tool. Existing sessions retain the tool
* binding but invocations after unregister return a `tool_unavailable`
* error from the wrapper handler.
*/
export interface PluginToolsSurface {
register(definition: ToolDefinition): () => void;
}
/**
* Resources surface — register MCP resources contributed by the plugin (M4).
* Mirrors {@link PluginToolsSurface} semantics for the `plugin://<id>/...`
* URI namespace.
*/
export interface PluginResourcesSurface {
register(definition: ResourceDefinition): () => void;
}
/**
* UI surface — register semantic UI elements contributed by the plugin (M6).
*
* Metadata-only: the seam writes into `ui_registry.json` via the host's
* locked write path and emits `plugin.output.accepted` /
* `plugin.output.rejected` telemetry. The element id MUST be prefixed
* with `<plugin-id>.` and the manifest MUST declare both the
* `ui:register` capability and the `mutate_ui_registry` effect.
*
* The executable / iframe UI surface (§4.7 ui executable) is not
* exposed here and remains post-1.0.
*/
export interface PluginUiSurface {
register(definition: UiElementDefinition): () => void;
}
/**
* Policies surface — propose discipline rules tagged `source: "plugin:<id>"`
* (M6 closure, §4.6). The host journals proposals; merging into the live
* manifest is deferred until a monotonicity model is specified.
*/
export interface PluginPoliciesSurface {
propose(proposal: PolicyProposal): () => void;
}
/**
* Archetypes surface — register a provider whose payload feeds federation
* (M6 closure, §4.8). The host stores the provider record and may invoke
* `definition.fetch()` on demand.
*/
export interface PluginArchetypesSurface {
registerProvider(definition: ArchetypeProviderDefinition): () => void;
}
/**
* Markdown fences surface — register a custom code-fence language the
* webview SDK can introspect (M6 closure, §4.9). Manifest-only stub: no
* rendering boundary is exposed in-process.
*/
export interface PluginMarkdownFencesSurface {
register(definition: MarkdownFenceDefinition): () => void;
}
/** Declarative Architect tabs with daemon-owned plan state. */
export interface PluginArchitectSurface {
tabs: { register(definition: ArchitectTabTypeDefinition): () => void };
planState: {
read<T>(key: string, context: ArchitectPlanContext): Promise<ArchitectStoredState<T> | null>;
write<T>(key: string, value: T, context: ArchitectPlanWriteContext): Promise<ArchitectStoredState<T>>;
};
}
export interface PluginContext {
readonly plugin: PluginIdentitySurface;
readonly instance: PluginInstanceSurface;
readonly logger: PluginLogger;
readonly events: PluginEventEmitSurface;
readonly tools: PluginToolsSurface;
readonly resources: PluginResourcesSurface;
readonly ui: PluginUiSurface;
readonly policies: PluginPoliciesSurface;
readonly archetypes: PluginArchetypesSurface;
readonly markdownFences: PluginMarkdownFencesSurface;
readonly architect: PluginArchitectSurface;
readonly signal: AbortSignal;
}
export interface PluginContextDependencies {
manifest: PluginManifest;
instanceUuid: string;
logger: PluginLogger;
events: PluginEventEmitSurface;
tools: PluginToolsSurface;
resources: PluginResourcesSurface;
ui: PluginUiSurface;
policies: PluginPoliciesSurface;
archetypes: PluginArchetypesSurface;
markdownFences: PluginMarkdownFencesSurface;
architect: PluginArchitectSurface;
signal: AbortSignal;
}
export function createPluginContext(
deps: PluginContextDependencies,
): PluginContext {
return {
plugin: { id: deps.manifest.id, version: deps.manifest.version },
instance: { uuid: deps.instanceUuid },
logger: deps.logger,
events: deps.events,
tools: deps.tools,
resources: deps.resources,
ui: deps.ui,
policies: deps.policies,
archetypes: deps.archetypes,
markdownFences: deps.markdownFences,
architect: deps.architect,
signal: deps.signal,
};
}
/**
* Minimal logger implementation that prefixes every message with the plugin
* id, matching the M3 trust-banner aesthetic and avoiding cross-plugin
* logger leakage. Hosts may swap this out for a structured logger.
*/
export function createNamespacedLogger(
pluginId: string,
sink: (level: string, line: string, meta?: object) => void = (level, line) =>
// eslint-disable-next-line no-console
console.log(`[${level}] ${line}`),
): PluginLogger {
const wrap = (level: "debug" | "info" | "warn" | "error") =>
(message: string, meta?: object) =>
sink(level, `plugin:${pluginId} ${message}`, meta);
return {
debug: wrap("debug"),
info: wrap("info"),
warn: wrap("warn"),
error: wrap("error"),
};
}