@qilin-agent/kylin-host-runner 0.0.0-stage → 3.1.3
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/LICENSE +21 -0
- package/README.i18n.yaml +70 -0
- package/README.md +150 -2
- package/README.zh.md +151 -0
- package/lib/index.js +2619 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +2483 -0
- package/lib/typert.remote-client.d.ts +56 -0
- package/lib/typert.remote-client.js +819 -0
- package/lib/types/guard.d.ts +71 -0
- package/lib/types/guard.js +766 -0
- package/lib/types/index.d.ts +280 -0
- package/lib/types/index.js +1150 -0
- package/lib/types/inspect-registry.d.ts +78 -0
- package/lib/types/inspect-registry.js +237 -0
- package/lib/types/lifecycle.d.ts +29 -0
- package/lib/types/lifecycle.js +49 -0
- package/lib/types/registry.d.ts +225 -0
- package/lib/types/registry.js +131 -0
- package/lib/types/sandbox.d.ts +99 -0
- package/lib/types/sandbox.js +251 -0
- package/lib/types/types.d.ts +363 -0
- package/lib/types/types.js +6 -0
- package/package.json +73 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Host registry for model-visible, read-only Cordis capability queries. */
|
|
2
|
+
import { Service } from '@qilin-agent/kylin';
|
|
3
|
+
import type { Context } from '@qilin-agent/kylin';
|
|
4
|
+
import type { Agent } from '@qilin-agent/agent';
|
|
5
|
+
import { type JsonValue } from '@qilin-agent/util-values';
|
|
6
|
+
import type { CordisInspectPlatform, CordisInspectProviderManifest, CordisInspectProviderView, CordisInspectQueryResolution, CordisInspectRequestId, CordisInspectResolveAck } from './types.ts';
|
|
7
|
+
/** Context supplied to a Host inspect query. */
|
|
8
|
+
export interface HostCordisInspectQueryContext {
|
|
9
|
+
/** Tool-call cancellation. */
|
|
10
|
+
signal: AbortSignal;
|
|
11
|
+
/** Agent whose scoped runtime is being inspected. */
|
|
12
|
+
agent: Agent;
|
|
13
|
+
}
|
|
14
|
+
/** Local registration paired with its serializable manifest. */
|
|
15
|
+
export interface HostCordisInspectProviderRegistration {
|
|
16
|
+
/** Provider and explicit method directory. */
|
|
17
|
+
manifest: CordisInspectProviderManifest;
|
|
18
|
+
/** Execute one declared method. */
|
|
19
|
+
query(method: string, input: JsonValue | undefined, context: HostCordisInspectQueryContext): Promise<JsonValue>;
|
|
20
|
+
}
|
|
21
|
+
declare module '@qilin-agent/kylin' {
|
|
22
|
+
interface Context {
|
|
23
|
+
/** Host registry for Cordis inspect providers and Client manifest/query routing. */
|
|
24
|
+
cordisInspect: CordisInspectRegistryService;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Registry and cross-page router behind the two model-facing inspect tools. */
|
|
28
|
+
export declare class CordisInspectRegistryService extends Service {
|
|
29
|
+
private readonly clientQueryTimeoutMs;
|
|
30
|
+
private readonly providers;
|
|
31
|
+
private readonly pending;
|
|
32
|
+
private clientManifest;
|
|
33
|
+
private nextRequest;
|
|
34
|
+
/**
|
|
35
|
+
* Register the process-global Host registry.
|
|
36
|
+
* @param ctx - owning Host context.
|
|
37
|
+
* @param clientQueryTimeoutMs - maximum wait for a valid Client response, in milliseconds.
|
|
38
|
+
*/
|
|
39
|
+
constructor(ctx: Context, clientQueryTimeoutMs: number);
|
|
40
|
+
/**
|
|
41
|
+
* Register one Host provider.
|
|
42
|
+
* @param registration - manifest and local query handler.
|
|
43
|
+
* @returns idempotent disposer.
|
|
44
|
+
*/
|
|
45
|
+
register(registration: HostCordisInspectProviderRegistration): () => void;
|
|
46
|
+
/**
|
|
47
|
+
* Replace the mirrored Client provider directory.
|
|
48
|
+
* @param providers - complete Client manifest snapshot.
|
|
49
|
+
*/
|
|
50
|
+
syncClientManifest(providers: readonly CordisInspectProviderManifest[]): void;
|
|
51
|
+
/**
|
|
52
|
+
* Return the complete known Host and Client provider directory.
|
|
53
|
+
* @returns Host providers followed by the Client providers.
|
|
54
|
+
*/
|
|
55
|
+
list(): CordisInspectProviderView[];
|
|
56
|
+
/**
|
|
57
|
+
* Execute one provider query on its owning platform.
|
|
58
|
+
* @param platform - Host or Client runtime.
|
|
59
|
+
* @param providerId - provider selected from {@link list}.
|
|
60
|
+
* @param methodName - declared method name.
|
|
61
|
+
* @param input - optional lossless JSON input.
|
|
62
|
+
* @param agent - requesting Agent and scope.
|
|
63
|
+
* @param signal - tool-call cancellation.
|
|
64
|
+
* @returns provider JSON data; Client queries fail fast when Gateway has no live Client
|
|
65
|
+
* and retain only the first observed failure diagnostic for timeout reporting.
|
|
66
|
+
*/
|
|
67
|
+
query(platform: CordisInspectPlatform, providerId: string, methodName: string, input: JsonValue | undefined, agent: Agent, signal: AbortSignal): Promise<JsonValue>;
|
|
68
|
+
/**
|
|
69
|
+
* Accept the first valid Client response for a pending query.
|
|
70
|
+
* @param agent - Agent whose Session owns the query.
|
|
71
|
+
* @param requestId - Pending Client query identity.
|
|
72
|
+
* @param resolution - Client provider result or failure.
|
|
73
|
+
* @returns acknowledgement with accepted true only for a success that settles the query; only the first failure diagnostic is retained.
|
|
74
|
+
*/
|
|
75
|
+
resolveClientQuery(agent: Agent, requestId: CordisInspectRequestId, resolution: CordisInspectQueryResolution): CordisInspectResolveAck;
|
|
76
|
+
private queryClient;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=inspect-registry.d.ts.map
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/** Host registry for model-visible, read-only Cordis capability queries. */
|
|
2
|
+
import { Service } from '@qilin-agent/kylin';
|
|
3
|
+
import { snapshotJsonValue } from '@qilin-agent/util-values';
|
|
4
|
+
import { assertSupportedJsonSchema, validateJsonSchemaValue } from '@qilin-agent/tools';
|
|
5
|
+
/** Registry and cross-page router behind the two model-facing inspect tools. */
|
|
6
|
+
export class CordisInspectRegistryService extends Service {
|
|
7
|
+
clientQueryTimeoutMs;
|
|
8
|
+
providers = new Map();
|
|
9
|
+
pending = new Map();
|
|
10
|
+
clientManifest;
|
|
11
|
+
nextRequest = 1;
|
|
12
|
+
/**
|
|
13
|
+
* Register the process-global Host registry.
|
|
14
|
+
* @param ctx - owning Host context.
|
|
15
|
+
* @param clientQueryTimeoutMs - maximum wait for a valid Client response, in milliseconds.
|
|
16
|
+
*/
|
|
17
|
+
constructor(ctx, clientQueryTimeoutMs) {
|
|
18
|
+
super(ctx, 'cordisInspect');
|
|
19
|
+
this.clientQueryTimeoutMs = clientQueryTimeoutMs;
|
|
20
|
+
ctx.effect(() => () => {
|
|
21
|
+
for (const pending of this.pending.values()) {
|
|
22
|
+
pending.settle({ ok: false, reason: 'cancelled', message: 'Client inspect registry was disposed' });
|
|
23
|
+
}
|
|
24
|
+
}, 'cordis-inspect: pending queries');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Register one Host provider.
|
|
28
|
+
* @param registration - manifest and local query handler.
|
|
29
|
+
* @returns idempotent disposer.
|
|
30
|
+
*/
|
|
31
|
+
register(registration) {
|
|
32
|
+
const manifest = validateManifest(registration.manifest);
|
|
33
|
+
if (this.providers.has(manifest.id))
|
|
34
|
+
throw new Error(`Host Cordis inspect provider "${manifest.id}" is already registered`);
|
|
35
|
+
const stored = { ...registration, manifest };
|
|
36
|
+
this.providers.set(manifest.id, stored);
|
|
37
|
+
return () => {
|
|
38
|
+
if (this.providers.get(manifest.id) === stored)
|
|
39
|
+
this.providers.delete(manifest.id);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Replace the mirrored Client provider directory.
|
|
44
|
+
* @param providers - complete Client manifest snapshot.
|
|
45
|
+
*/
|
|
46
|
+
syncClientManifest(providers) {
|
|
47
|
+
const ids = new Set();
|
|
48
|
+
const validated = providers.map((provider) => {
|
|
49
|
+
const manifest = validateManifest(provider);
|
|
50
|
+
if (ids.has(manifest.id))
|
|
51
|
+
throw new Error(`Client Cordis inspect manifest repeats provider "${manifest.id}"`);
|
|
52
|
+
ids.add(manifest.id);
|
|
53
|
+
return manifest;
|
|
54
|
+
});
|
|
55
|
+
this.clientManifest = Object.freeze(validated);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Return the complete known Host and Client provider directory.
|
|
59
|
+
* @returns Host providers followed by the Client providers.
|
|
60
|
+
*/
|
|
61
|
+
list() {
|
|
62
|
+
return [
|
|
63
|
+
...[...this.providers.values()].map(provider => view('host', provider.manifest)),
|
|
64
|
+
...(this.clientManifest ?? []).map(provider => view('client', provider)),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Execute one provider query on its owning platform.
|
|
69
|
+
* @param platform - Host or Client runtime.
|
|
70
|
+
* @param providerId - provider selected from {@link list}.
|
|
71
|
+
* @param methodName - declared method name.
|
|
72
|
+
* @param input - optional lossless JSON input.
|
|
73
|
+
* @param agent - requesting Agent and scope.
|
|
74
|
+
* @param signal - tool-call cancellation.
|
|
75
|
+
* @returns provider JSON data; Client queries fail fast when Gateway has no live Client
|
|
76
|
+
* and retain only the first observed failure diagnostic for timeout reporting.
|
|
77
|
+
*/
|
|
78
|
+
async query(platform, providerId, methodName, input, agent, signal) {
|
|
79
|
+
if (platform === 'host') {
|
|
80
|
+
const registration = this.providers.get(providerId);
|
|
81
|
+
if (registration === undefined)
|
|
82
|
+
throw new Error(`Host Cordis inspect provider "${providerId}" is not registered`);
|
|
83
|
+
const method = findMethod(registration.manifest, methodName);
|
|
84
|
+
validateInput('Host', providerId, method, input);
|
|
85
|
+
signal.throwIfAborted();
|
|
86
|
+
const data = await registration.query(methodName, input, { agent, signal });
|
|
87
|
+
signal.throwIfAborted();
|
|
88
|
+
return validateOutput('Host', providerId, method, data);
|
|
89
|
+
}
|
|
90
|
+
return await this.queryClient(providerId, methodName, input, agent, signal);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Accept the first valid Client response for a pending query.
|
|
94
|
+
* @param agent - Agent whose Session owns the query.
|
|
95
|
+
* @param requestId - Pending Client query identity.
|
|
96
|
+
* @param resolution - Client provider result or failure.
|
|
97
|
+
* @returns acknowledgement with accepted true only for a success that settles the query; only the first failure diagnostic is retained.
|
|
98
|
+
*/
|
|
99
|
+
resolveClientQuery(agent, requestId, resolution) {
|
|
100
|
+
const pending = this.pending.get(requestId);
|
|
101
|
+
if (pending === undefined || pending.request.agentId !== agent.id)
|
|
102
|
+
return { accepted: false };
|
|
103
|
+
// A failed page must not prevent another page from supplying a valid result.
|
|
104
|
+
if (!resolution.ok) {
|
|
105
|
+
pending.failure ??= `${resolution.reason}: ${resolution.message}`;
|
|
106
|
+
return { accepted: false };
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
resolution = {
|
|
110
|
+
ok: true,
|
|
111
|
+
data: validateOutput('Client', pending.request.provider, pending.method, resolution.data),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
pending.failure ??= error instanceof Error ? error.message : String(error);
|
|
116
|
+
return { accepted: false };
|
|
117
|
+
}
|
|
118
|
+
pending.settle(resolution);
|
|
119
|
+
return { accepted: true };
|
|
120
|
+
}
|
|
121
|
+
async queryClient(providerId, methodName, input, agent, signal) {
|
|
122
|
+
const provider = this.clientManifest?.find(candidate => candidate.id === providerId);
|
|
123
|
+
if (provider === undefined)
|
|
124
|
+
throw new Error(`Client Cordis inspect provider "${providerId}" is not registered`);
|
|
125
|
+
const method = findMethod(provider, methodName);
|
|
126
|
+
validateInput('Client', providerId, method, input);
|
|
127
|
+
signal.throwIfAborted();
|
|
128
|
+
const gateway = this.ctx.get('typertGateway');
|
|
129
|
+
if (gateway !== undefined && !gateway.hasLiveClient()) {
|
|
130
|
+
throw new Error(`Client inspect query ${providerId}.${methodName} has no connected Harness page. Open or reconnect the Harness page, then retry.`);
|
|
131
|
+
}
|
|
132
|
+
const requestId = `inspect-${this.nextRequest++}`;
|
|
133
|
+
const request = {
|
|
134
|
+
requestId,
|
|
135
|
+
agentId: agent.id,
|
|
136
|
+
provider: providerId,
|
|
137
|
+
method: methodName,
|
|
138
|
+
...input === undefined ? {} : { input },
|
|
139
|
+
};
|
|
140
|
+
const result = new Promise((resolve) => {
|
|
141
|
+
this.pending.set(requestId, {
|
|
142
|
+
request,
|
|
143
|
+
method,
|
|
144
|
+
settle: (resolution) => {
|
|
145
|
+
this.pending.delete(requestId);
|
|
146
|
+
resolve(resolution);
|
|
147
|
+
try {
|
|
148
|
+
this.ctx.emit('cordis/inspect-query-resolved', { requestId });
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error('[kylin-host-runner] notifying Client inspect completion failed:', error);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
const onAbort = () => {
|
|
157
|
+
this.pending.get(requestId)?.settle({
|
|
158
|
+
ok: false,
|
|
159
|
+
reason: 'cancelled',
|
|
160
|
+
message: `Client inspect query ${providerId}.${methodName} was cancelled`,
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
const timer = setTimeout(() => {
|
|
164
|
+
const pending = this.pending.get(requestId);
|
|
165
|
+
if (pending === undefined)
|
|
166
|
+
return;
|
|
167
|
+
const detail = pending.failure === undefined
|
|
168
|
+
? 'Open or reconnect the Harness page, then retry.'
|
|
169
|
+
: `Client failure: ${pending.failure}`;
|
|
170
|
+
pending.settle({
|
|
171
|
+
ok: false,
|
|
172
|
+
reason: 'provider-error',
|
|
173
|
+
message: `Client inspect query ${providerId}.${methodName} timed out after ${this.clientQueryTimeoutMs}ms. ${detail}`,
|
|
174
|
+
});
|
|
175
|
+
}, this.clientQueryTimeoutMs);
|
|
176
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
177
|
+
try {
|
|
178
|
+
if (signal.aborted)
|
|
179
|
+
onAbort();
|
|
180
|
+
else
|
|
181
|
+
this.ctx.emit('cordis/inspect-query', request);
|
|
182
|
+
const resolution = await result;
|
|
183
|
+
if (!resolution.ok)
|
|
184
|
+
throw new Error(`${providerId}.${methodName}: ${resolution.message}`);
|
|
185
|
+
return resolution.data;
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
signal.removeEventListener('abort', onAbort);
|
|
190
|
+
this.pending.delete(requestId);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function view(platform, manifest) {
|
|
195
|
+
return { platform, ...manifest, methods: [...manifest.methods] };
|
|
196
|
+
}
|
|
197
|
+
function validateManifest(manifest) {
|
|
198
|
+
if (manifest.id.trim() === '')
|
|
199
|
+
throw new Error('Kylin inspect provider id must not be empty');
|
|
200
|
+
if (manifest.description.trim() === '')
|
|
201
|
+
throw new Error(`Cordis inspect provider "${manifest.id}" needs a description`);
|
|
202
|
+
const names = new Set();
|
|
203
|
+
const methods = manifest.methods.map((method) => {
|
|
204
|
+
if (method.name.trim() === '')
|
|
205
|
+
throw new Error(`Cordis inspect provider "${manifest.id}" has an empty method name`);
|
|
206
|
+
if (names.has(method.name))
|
|
207
|
+
throw new Error(`Cordis inspect provider "${manifest.id}" repeats method "${method.name}"`);
|
|
208
|
+
if (method.description.trim() === '')
|
|
209
|
+
throw new Error(`Cordis inspect method ${manifest.id}.${method.name} needs a description`);
|
|
210
|
+
assertSupportedJsonSchema(method.inputSchema);
|
|
211
|
+
assertSupportedJsonSchema(method.outputSchema);
|
|
212
|
+
names.add(method.name);
|
|
213
|
+
return Object.freeze({ ...method });
|
|
214
|
+
});
|
|
215
|
+
return Object.freeze({ ...manifest, methods: Object.freeze(methods) });
|
|
216
|
+
}
|
|
217
|
+
function findMethod(manifest, name) {
|
|
218
|
+
const method = manifest.methods.find(candidate => candidate.name === name);
|
|
219
|
+
if (method === undefined)
|
|
220
|
+
throw new Error(`Cordis inspect provider "${manifest.id}" has no method "${name}"`);
|
|
221
|
+
return method;
|
|
222
|
+
}
|
|
223
|
+
function validateInput(platform, provider, method, input) {
|
|
224
|
+
const violations = validateJsonSchemaValue(method.inputSchema, input ?? {}, 'input');
|
|
225
|
+
if (violations.length > 0)
|
|
226
|
+
throw new Error(`${platform} Cordis inspect ${provider}.${method.name} rejected input: ${violations.join('; ')}`);
|
|
227
|
+
}
|
|
228
|
+
function validateOutput(platform, provider, method, data) {
|
|
229
|
+
const snapshot = snapshotJsonValue(data);
|
|
230
|
+
if (snapshot === undefined)
|
|
231
|
+
throw new Error(`${platform} Cordis inspect ${provider}.${method.name} returned a non-JSON value`);
|
|
232
|
+
const violations = validateJsonSchemaValue(method.outputSchema, snapshot, 'output');
|
|
233
|
+
if (violations.length > 0)
|
|
234
|
+
throw new Error(`${platform} Cordis inspect ${provider}.${method.name} returned invalid output: ${violations.join('; ')}`);
|
|
235
|
+
return snapshot;
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=inspect-registry.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-half fiber lifecycle over the `cordis-dynamic` group: settle a
|
|
3
|
+
* sandbox-produced plugin as a child fiber (never leaving a failed fiber
|
|
4
|
+
* mounted), and report the services a settled-but-pending fiber still waits
|
|
5
|
+
* for. Stopping needs no helper — a host half unwinds through an ordinary
|
|
6
|
+
* awaited `fiber.dispose()`, because everything the plugin registered is an
|
|
7
|
+
* effect on its fiber.
|
|
8
|
+
* @module @qilin-agent/kylin-host-runner/lifecycle
|
|
9
|
+
*/
|
|
10
|
+
import type { Context, Fiber, Plugin } from '@qilin-agent/kylin';
|
|
11
|
+
/**
|
|
12
|
+
* Await the group, start and settle one guarded child, and dispose it before rethrowing any
|
|
13
|
+
* startup failure so a failed run never lingers. A valid unresolved inject may remain pending.
|
|
14
|
+
* @param group - the `cordis-dynamic` group fiber every host half hangs under.
|
|
15
|
+
* @param plugin - the plugin the sandbox returned; wrapped with the registration guard before starting.
|
|
16
|
+
* @param reportGuardFailure - reports post-activation Host guard rejections to the owning Agent.
|
|
17
|
+
* @returns the settled child fiber (possibly pending on unsatisfied `inject`).
|
|
18
|
+
*/
|
|
19
|
+
export declare function startHostHalf(group: Fiber, plugin: Plugin, reportGuardFailure: (error: Error) => void): Promise<Fiber>;
|
|
20
|
+
/**
|
|
21
|
+
* The services a fiber declared in `inject` that do not exist yet — a settled
|
|
22
|
+
* fiber that is not active is waiting on exactly these (legal cordis
|
|
23
|
+
* semantics: it activates when the service appears).
|
|
24
|
+
* @param ctx - the context to resolve service existence against.
|
|
25
|
+
* @param fiber - the host-half fiber whose `inject` declarations are checked.
|
|
26
|
+
* @returns the missing service names, in declaration order.
|
|
27
|
+
*/
|
|
28
|
+
export declare function missingServices(ctx: Context, fiber: Fiber): string[];
|
|
29
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-half fiber lifecycle over the `cordis-dynamic` group: settle a
|
|
3
|
+
* sandbox-produced plugin as a child fiber (never leaving a failed fiber
|
|
4
|
+
* mounted), and report the services a settled-but-pending fiber still waits
|
|
5
|
+
* for. Stopping needs no helper — a host half unwinds through an ordinary
|
|
6
|
+
* awaited `fiber.dispose()`, because everything the plugin registered is an
|
|
7
|
+
* effect on its fiber.
|
|
8
|
+
* @module @qilin-agent/kylin-host-runner/lifecycle
|
|
9
|
+
*/
|
|
10
|
+
import { guardedPlugin } from "./guard.js";
|
|
11
|
+
/**
|
|
12
|
+
* Await the group, start and settle one guarded child, and dispose it before rethrowing any
|
|
13
|
+
* startup failure so a failed run never lingers. A valid unresolved inject may remain pending.
|
|
14
|
+
* @param group - the `cordis-dynamic` group fiber every host half hangs under.
|
|
15
|
+
* @param plugin - the plugin the sandbox returned; wrapped with the registration guard before starting.
|
|
16
|
+
* @param reportGuardFailure - reports post-activation Host guard rejections to the owning Agent.
|
|
17
|
+
* @returns the settled child fiber (possibly pending on unsatisfied `inject`).
|
|
18
|
+
*/
|
|
19
|
+
export async function startHostHalf(group, plugin, reportGuardFailure) {
|
|
20
|
+
await group.await();
|
|
21
|
+
const fiber = group.ctx.plugin(guardedPlugin(plugin, reportGuardFailure));
|
|
22
|
+
try {
|
|
23
|
+
await fiber.await();
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
await fiber.dispose();
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
// The commonest startup collision is running a NEW version of a package
|
|
29
|
+
// while the old run still holds the name — teach the replace recipe.
|
|
30
|
+
if (message.includes('already registered')) {
|
|
31
|
+
throw new Error(`${message} — to REPLACE something an earlier dynamic package registered, first stop that package through its runner or the Cordis panel `
|
|
32
|
+
+ 'before running the new version.');
|
|
33
|
+
}
|
|
34
|
+
throw error instanceof Error ? error : new Error(message);
|
|
35
|
+
}
|
|
36
|
+
return fiber;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The services a fiber declared in `inject` that do not exist yet — a settled
|
|
40
|
+
* fiber that is not active is waiting on exactly these (legal cordis
|
|
41
|
+
* semantics: it activates when the service appears).
|
|
42
|
+
* @param ctx - the context to resolve service existence against.
|
|
43
|
+
* @param fiber - the host-half fiber whose `inject` declarations are checked.
|
|
44
|
+
* @returns the missing service names, in declaration order.
|
|
45
|
+
*/
|
|
46
|
+
export function missingServices(ctx, fiber) {
|
|
47
|
+
return Object.keys(fiber.inject).filter(service => ctx.get(service) === undefined);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-local dynamic Plugin registry and its opaque identity mints.
|
|
3
|
+
* @module @qilin-agent/kylin-host-runner/registry
|
|
4
|
+
*/
|
|
5
|
+
import type { Fiber } from '@qilin-agent/kylin';
|
|
6
|
+
import type { SessionId } from '@qilin-agent/session/types';
|
|
7
|
+
import type { ApprovalRequestId, CordisDynamicPackageId, CordisDynamicPluginId, CordisDynamicPluginRunId, CordisDynamicRunMode, DynamicCordisRenderFailure, DynamicCordisRunAttempt } from './types.ts';
|
|
8
|
+
/** One Host method exposed to this package's Client half. */
|
|
9
|
+
export type DynamicCordisHandler = (args: unknown) => Promise<unknown>;
|
|
10
|
+
/** One live activation and everything its teardown owns. */
|
|
11
|
+
export interface DynamicCordisRun {
|
|
12
|
+
/** Exact activation identity. */
|
|
13
|
+
pluginRunId: CordisDynamicPluginRunId;
|
|
14
|
+
/** Immutable package version being run. */
|
|
15
|
+
packageId: CordisDynamicPackageId;
|
|
16
|
+
/** Host-half Fiber, absent for Client-only packages. */
|
|
17
|
+
fiber?: Fiber;
|
|
18
|
+
/** Active Host methods. */
|
|
19
|
+
handlers: Map<string, DynamicCordisHandler>;
|
|
20
|
+
/** Method registration cleanup. */
|
|
21
|
+
handlerDisposers: (() => void)[];
|
|
22
|
+
/** Runtime failures already sent to the owning Agent during this activation. */
|
|
23
|
+
reportedRuntimeErrors: Set<string>;
|
|
24
|
+
/** Last render failure observed for this version's current run. */
|
|
25
|
+
renderFailure?: DynamicCordisRenderFailure;
|
|
26
|
+
/** Approval whose transition started this run, when model-driven. */
|
|
27
|
+
startedForRequest?: ApprovalRequestId;
|
|
28
|
+
}
|
|
29
|
+
/** One immutable package version. */
|
|
30
|
+
export interface DynamicCordisDefinition {
|
|
31
|
+
/** Package identity. */
|
|
32
|
+
packageId: CordisDynamicPackageId;
|
|
33
|
+
/** Package label. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** User-facing purpose. */
|
|
36
|
+
purpose: string;
|
|
37
|
+
/** Host source. */
|
|
38
|
+
hostCode?: string;
|
|
39
|
+
/** Client source. */
|
|
40
|
+
clientCode?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Stable plugin instance containing immutable package versions. */
|
|
43
|
+
export interface DynamicCordisPlugin {
|
|
44
|
+
/** Stable identity. */
|
|
45
|
+
pluginId: CordisDynamicPluginId;
|
|
46
|
+
/** Owning session. */
|
|
47
|
+
sessionId: SessionId;
|
|
48
|
+
/** Versions in define order. */
|
|
49
|
+
packages: Map<CordisDynamicPackageId, DynamicCordisDefinition>;
|
|
50
|
+
/** Client-bearing Packages individually authorized by the user. */
|
|
51
|
+
approvedClientPackages: Set<CordisDynamicPackageId>;
|
|
52
|
+
/** Whether one user decision authorized future Package versions of this Plugin. */
|
|
53
|
+
clientVersionUpdatesApproved: boolean;
|
|
54
|
+
/** Last successfully activated version. */
|
|
55
|
+
currentPackageId?: CordisDynamicPackageId;
|
|
56
|
+
/** Failed or in-progress target version. */
|
|
57
|
+
nextPackageId?: CordisDynamicPackageId;
|
|
58
|
+
/** Current activation. */
|
|
59
|
+
run?: DynamicCordisRun;
|
|
60
|
+
/** Latest activation attempt, including approval and asynchronous failure state. */
|
|
61
|
+
latestRun?: DynamicCordisRunAttempt;
|
|
62
|
+
}
|
|
63
|
+
/** One suspended model-driven activation. */
|
|
64
|
+
export interface DynamicCordisPendingRequest {
|
|
65
|
+
/** Session whose model requested this activation. */
|
|
66
|
+
agentId: SessionId;
|
|
67
|
+
pluginId: CordisDynamicPluginId;
|
|
68
|
+
packageId: CordisDynamicPackageId;
|
|
69
|
+
pluginRunId: CordisDynamicPluginRunId;
|
|
70
|
+
mode: CordisDynamicRunMode;
|
|
71
|
+
/** Whether this request must wait for an explicit user decision. */
|
|
72
|
+
requiresApproval: boolean;
|
|
73
|
+
}
|
|
74
|
+
/** Request accepted by `define`; it never crosses the Remote transport. */
|
|
75
|
+
export interface DynamicCordisDefineRequest {
|
|
76
|
+
/** Session that owns the plugin. */
|
|
77
|
+
sessionId: SessionId;
|
|
78
|
+
/** Create a plugin or append to an existing one. */
|
|
79
|
+
plugin: {
|
|
80
|
+
kind: 'new';
|
|
81
|
+
idPrefix: string;
|
|
82
|
+
} | {
|
|
83
|
+
kind: 'existing';
|
|
84
|
+
pluginId: CordisDynamicPluginId;
|
|
85
|
+
};
|
|
86
|
+
/** Package label. */
|
|
87
|
+
name: string;
|
|
88
|
+
/** User-facing purpose. */
|
|
89
|
+
purpose: string;
|
|
90
|
+
/** At least one source half. */
|
|
91
|
+
code: {
|
|
92
|
+
host?: string;
|
|
93
|
+
client?: string;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Successful `define` result. */
|
|
97
|
+
export interface DynamicCordisDefineReceipt {
|
|
98
|
+
pluginId: CordisDynamicPluginId;
|
|
99
|
+
packageId: CordisDynamicPackageId;
|
|
100
|
+
name: string;
|
|
101
|
+
purpose: string;
|
|
102
|
+
hasHostHalf: boolean;
|
|
103
|
+
hasClientHalf: boolean;
|
|
104
|
+
}
|
|
105
|
+
/** Source-free modification context for an explicit `@pluginId` reference. */
|
|
106
|
+
export interface DynamicCordisReference {
|
|
107
|
+
pluginId: CordisDynamicPluginId;
|
|
108
|
+
packageId: CordisDynamicPackageId;
|
|
109
|
+
name: string;
|
|
110
|
+
purpose: string;
|
|
111
|
+
currentPackageId?: CordisDynamicPackageId;
|
|
112
|
+
nextPackageId?: CordisDynamicPackageId;
|
|
113
|
+
activeRun?: {
|
|
114
|
+
pluginRunId: CordisDynamicPluginRunId;
|
|
115
|
+
packageId: CordisDynamicPackageId;
|
|
116
|
+
};
|
|
117
|
+
latestRun?: DynamicCordisRunAttempt;
|
|
118
|
+
}
|
|
119
|
+
/** Source-free Plugin summary returned by layered self inspection. */
|
|
120
|
+
export interface DynamicCordisPluginInspection extends DynamicCordisReference {
|
|
121
|
+
/** Immutable Package summaries in define order. */
|
|
122
|
+
packages: Array<{
|
|
123
|
+
packageId: CordisDynamicPackageId;
|
|
124
|
+
name: string;
|
|
125
|
+
purpose: string;
|
|
126
|
+
hasHostHalf: boolean;
|
|
127
|
+
hasClientHalf: boolean;
|
|
128
|
+
}>;
|
|
129
|
+
}
|
|
130
|
+
/** Exact immutable Package metadata and source returned by explicit inspection. */
|
|
131
|
+
export interface DynamicCordisPackageInspection extends DynamicCordisReference {
|
|
132
|
+
/** Host and Client function bodies stored for this Package. */
|
|
133
|
+
code: {
|
|
134
|
+
host?: string;
|
|
135
|
+
client?: string;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** Registry, identity mints, and pending approval index. */
|
|
139
|
+
export declare class DynamicCordisRegistry {
|
|
140
|
+
private readonly plugins;
|
|
141
|
+
private readonly pendingRequests;
|
|
142
|
+
private nextPlugin;
|
|
143
|
+
private nextPackage;
|
|
144
|
+
private nextRun;
|
|
145
|
+
private nextApproval;
|
|
146
|
+
/**
|
|
147
|
+
* Mint a semantic plugin ID without reusing a prior suffix.
|
|
148
|
+
* @param prefix - validated lowercase semantic prefix proposed by the model.
|
|
149
|
+
* @returns a process-unique Plugin ID.
|
|
150
|
+
*/
|
|
151
|
+
mintPluginId(prefix: string): string;
|
|
152
|
+
/**
|
|
153
|
+
* Mint an immutable package ID.
|
|
154
|
+
* @returns a process-unique Package ID.
|
|
155
|
+
*/
|
|
156
|
+
mintPackageId(): string;
|
|
157
|
+
/**
|
|
158
|
+
* Mint an activation ID.
|
|
159
|
+
* @returns a process-unique Plugin Run ID.
|
|
160
|
+
*/
|
|
161
|
+
mintPluginRunId(): string;
|
|
162
|
+
/**
|
|
163
|
+
* Mint an approval ID.
|
|
164
|
+
* @returns a process-unique approval request ID.
|
|
165
|
+
*/
|
|
166
|
+
mintApprovalRequestId(): string;
|
|
167
|
+
/**
|
|
168
|
+
* Add one stable plugin.
|
|
169
|
+
* @param plugin - Plugin record to retain under its stable ID.
|
|
170
|
+
*/
|
|
171
|
+
add(plugin: DynamicCordisPlugin): void;
|
|
172
|
+
/**
|
|
173
|
+
* Read one plugin.
|
|
174
|
+
* @param id - stable Plugin ID.
|
|
175
|
+
* @returns the Plugin record, or `undefined` when absent.
|
|
176
|
+
*/
|
|
177
|
+
get(id: CordisDynamicPluginId): DynamicCordisPlugin | undefined;
|
|
178
|
+
/**
|
|
179
|
+
* Delete one plugin and all package versions.
|
|
180
|
+
* @param id - stable Plugin ID to remove.
|
|
181
|
+
* @returns whether a Plugin record was removed.
|
|
182
|
+
*/
|
|
183
|
+
delete(id: CordisDynamicPluginId): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* Read all plugins in creation order.
|
|
186
|
+
* @returns a snapshot of every Plugin record.
|
|
187
|
+
*/
|
|
188
|
+
all(): DynamicCordisPlugin[];
|
|
189
|
+
/**
|
|
190
|
+
* Read one session's plugins in creation order.
|
|
191
|
+
* @param sessionId - owning session to filter by.
|
|
192
|
+
* @returns a snapshot of matching Plugin records.
|
|
193
|
+
*/
|
|
194
|
+
ofSession(sessionId: SessionId): DynamicCordisPlugin[];
|
|
195
|
+
/**
|
|
196
|
+
* Publish one pending approval.
|
|
197
|
+
* @param id - approval request ID.
|
|
198
|
+
* @param pending - resolver and Plugin metadata retained until settlement.
|
|
199
|
+
*/
|
|
200
|
+
armRequest(id: ApprovalRequestId, pending: DynamicCordisPendingRequest): void;
|
|
201
|
+
/**
|
|
202
|
+
* Read one pending approval without claiming it.
|
|
203
|
+
* @param id - approval request ID.
|
|
204
|
+
* @returns the pending request, or `undefined` when absent.
|
|
205
|
+
*/
|
|
206
|
+
peekRequest(id: ApprovalRequestId): DynamicCordisPendingRequest | undefined;
|
|
207
|
+
/**
|
|
208
|
+
* Claim one pending approval; first answer wins.
|
|
209
|
+
* @param id - approval request ID.
|
|
210
|
+
* @returns the claimed request, or `undefined` when already settled.
|
|
211
|
+
*/
|
|
212
|
+
claimRequest(id: ApprovalRequestId): DynamicCordisPendingRequest | undefined;
|
|
213
|
+
/**
|
|
214
|
+
* Cancel one pending approval.
|
|
215
|
+
* @param id - approval request ID to remove.
|
|
216
|
+
*/
|
|
217
|
+
disarmRequest(id: ApprovalRequestId): void;
|
|
218
|
+
/**
|
|
219
|
+
* Find a pending approval for one Plugin.
|
|
220
|
+
* @param pluginId - stable Plugin ID.
|
|
221
|
+
* @returns its approval request ID, or `undefined` when none is pending.
|
|
222
|
+
*/
|
|
223
|
+
pendingRequestFor(pluginId: CordisDynamicPluginId): ApprovalRequestId | undefined;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=registry.d.ts.map
|