openworkgraph 0.2.12 → 0.3.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/dist/api.d.ts +1 -0
- package/dist/api.js +6 -0
- package/dist/backend/adapter.d.ts +1 -0
- package/dist/backend/adapter.js +161 -8
- package/dist/backend/stdio.d.ts +1 -1
- package/dist/backend/stdio.js +2 -2
- package/dist/backend/types.d.ts +12 -0
- package/dist/builtin-skills.d.ts +56 -0
- package/dist/builtin-skills.js +514 -0
- package/dist/cli.js +0 -0
- package/dist/graph-transfer.js +7 -2
- package/dist/inputs.d.ts +1 -0
- package/dist/inputs.js +1 -1
- package/dist/plugins.d.ts +4 -0
- package/dist/plugins.js +33 -1
- package/dist/runtime-backend.d.ts +3 -1
- package/dist/runtime-backend.js +11 -2
- package/dist/runtime.d.ts +7 -0
- package/dist/runtime.js +17 -2
- package/dist/service.js +7 -5
- package/dist/skill-configs.d.ts +43 -0
- package/dist/skill-configs.js +554 -0
- package/dist/skill-schema.d.ts +10 -0
- package/dist/skill-schema.js +144 -0
- package/dist/skill-sessions.d.ts +24 -0
- package/dist/skill-sessions.js +165 -0
- package/dist/skill-source.d.ts +37 -0
- package/dist/skill-source.js +104 -0
- package/dist/skills-api.d.ts +8 -4
- package/dist/skills-api.js +48 -27
- package/dist/skills-management-api.d.ts +12 -0
- package/dist/skills-management-api.js +139 -0
- package/dist/vendor/protocol/index.d.ts +3 -1
- package/dist/vendor/protocol/index.js +2 -1
- package/dist/vendor/protocol/skills.d.ts +107 -0
- package/dist/vendor/protocol/skills.js +33 -0
- package/package.json +2 -2
package/dist/runtime-backend.js
CHANGED
|
@@ -29,13 +29,15 @@ export class RuntimeBackend {
|
|
|
29
29
|
dirs;
|
|
30
30
|
adapter;
|
|
31
31
|
probe;
|
|
32
|
+
skillSessions;
|
|
32
33
|
contexts = new Map();
|
|
33
34
|
runContexts;
|
|
34
|
-
constructor(runs, dirs, adapter, probe = new ProjectProbe()) {
|
|
35
|
+
constructor(runs, dirs, adapter, probe = new ProjectProbe(), skillSessions) {
|
|
35
36
|
this.runs = runs;
|
|
36
37
|
this.dirs = dirs;
|
|
37
38
|
this.adapter = adapter;
|
|
38
39
|
this.probe = probe;
|
|
40
|
+
this.skillSessions = skillSessions;
|
|
39
41
|
this.runContexts = new RunContexts(runs, dirs);
|
|
40
42
|
}
|
|
41
43
|
snapshot(snapshot, context, expectedRunId) {
|
|
@@ -83,6 +85,11 @@ export class RuntimeBackend {
|
|
|
83
85
|
let inputFiles;
|
|
84
86
|
let upstreamContext;
|
|
85
87
|
try {
|
|
88
|
+
if (snapshot.skills?.length) {
|
|
89
|
+
if (!this.skillSessions)
|
|
90
|
+
throw new ServiceError('INPUT_BLOCKED', '冻结技能上下文不可用。');
|
|
91
|
+
await this.skillSessions.environment(snapshot.skills, true);
|
|
92
|
+
}
|
|
86
93
|
if (runtime.details.kind === 'execution')
|
|
87
94
|
upstreamContext = this.runContexts.lineage(run.id);
|
|
88
95
|
const inspect = await this.probe.inspect([path]);
|
|
@@ -144,7 +151,7 @@ export class RuntimeBackend {
|
|
|
144
151
|
context.emit({ type: 'stopped' });
|
|
145
152
|
return;
|
|
146
153
|
}
|
|
147
|
-
const result = await this.adapter.start({ runId: run.id, kind: runtime.details.kind, projectPath: path, inputPath: directories.input, outputPath: directories.output, serviceRoot: this.dirs.root, prompt: snapshot.prompt, resources: snapshot.resources, projectFiles: snapshot.projectFiles ?? [], files: inputFiles, model: snapshot.model, ...(upstreamContext ? { upstreamContext } : {}), ...(snapshot.imageRoute?.type === 'codex' && snapshot.imageRoute.options ? { imageOptions: snapshot.imageRoute.options } : {}), sandboxMode: sandboxMode(runtime.details.sandboxMode ?? 'read-only') }, {
|
|
154
|
+
const result = await this.adapter.start({ runId: run.id, kind: runtime.details.kind, projectPath: path, inputPath: directories.input, outputPath: directories.output, serviceRoot: this.dirs.root, prompt: snapshot.prompt, resources: snapshot.resources, projectFiles: snapshot.projectFiles ?? [], files: inputFiles, model: snapshot.model, skills: snapshot.skills ?? [], ...(upstreamContext ? { upstreamContext } : {}), ...(snapshot.imageRoute?.type === 'codex' && snapshot.imageRoute.options ? { imageOptions: snapshot.imageRoute.options } : {}), sandboxMode: sandboxMode(runtime.details.sandboxMode ?? 'read-only') }, {
|
|
148
155
|
onSnapshot: value => this.snapshot(value, context, run.id),
|
|
149
156
|
onInteraction: value => {
|
|
150
157
|
if (!this.runs.db.prepare('SELECT 1 FROM runs WHERE id=?').get(run.id))
|
|
@@ -160,6 +167,8 @@ export class RuntimeBackend {
|
|
|
160
167
|
},
|
|
161
168
|
queryHistory: async (query) => this.queryHistory(run.id, query),
|
|
162
169
|
readRunContext: async (request) => this.runContexts.read(run.id, request),
|
|
170
|
+
...(this.skillSessions ? { skillEnvironment: async (skillId) => this.skillSessions.forSkill(snapshot.skills ?? [], skillId) } : {}),
|
|
171
|
+
...(this.skillSessions ? { withSkillEnvironment: (skillId, dispatch) => this.skillSessions.withEnvironment(snapshot.skills ?? [], skillId, dispatch) } : {}),
|
|
163
172
|
});
|
|
164
173
|
this.snapshot(result, context, run.id);
|
|
165
174
|
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -17,12 +17,16 @@ import type { DataDirectories } from './directories.js';
|
|
|
17
17
|
import { ExecutionSettingsStore } from './execution-settings.js';
|
|
18
18
|
import { ImageProviders } from './image-providers.js';
|
|
19
19
|
import { ImageCredentials } from './image-credentials.js';
|
|
20
|
+
import { BuiltinSkills, type BuiltinSkillsOptions } from './builtin-skills.js';
|
|
21
|
+
import { SkillConfigs } from './skill-configs.js';
|
|
22
|
+
import { SkillSessions } from './skill-sessions.js';
|
|
20
23
|
export interface WorkflowRuntimeOptions {
|
|
21
24
|
backend?: SchedulerBackend;
|
|
22
25
|
models?: BackendModel[];
|
|
23
26
|
/** Default: discover on model reads/submission. True also probes at startup; false disables execution and discovery. */
|
|
24
27
|
enableBackend?: boolean;
|
|
25
28
|
backendOptions?: AdapterOptions;
|
|
29
|
+
skillsOptions?: BuiltinSkillsOptions;
|
|
26
30
|
}
|
|
27
31
|
export declare class WorkflowRuntime {
|
|
28
32
|
readonly db: DatabaseSync;
|
|
@@ -44,6 +48,9 @@ export declare class WorkflowRuntime {
|
|
|
44
48
|
readonly executionSettings: ExecutionSettingsStore;
|
|
45
49
|
readonly imageProviders: ImageProviders;
|
|
46
50
|
readonly imageCredentials: ImageCredentials;
|
|
51
|
+
readonly skills: BuiltinSkills;
|
|
52
|
+
readonly skillConfigs: SkillConfigs;
|
|
53
|
+
readonly skillSessions: SkillSessions;
|
|
47
54
|
private readonly probe;
|
|
48
55
|
private readonly adapter;
|
|
49
56
|
private availableModels;
|
package/dist/runtime.js
CHANGED
|
@@ -29,6 +29,9 @@ import { ImageProviders } from './image-providers.js';
|
|
|
29
29
|
import { ImageCredentials } from './image-credentials.js';
|
|
30
30
|
import { ApiImageAgent, RoutedImageBackend } from './api-image-agent.js';
|
|
31
31
|
import { planOpenAiImage, discoverOpenAiModels } from './openai-image-driver.js';
|
|
32
|
+
import { BuiltinSkills } from './builtin-skills.js';
|
|
33
|
+
import { SkillConfigs } from './skill-configs.js';
|
|
34
|
+
import { SkillSessions } from './skill-sessions.js';
|
|
32
35
|
class RuntimeRuns extends Runs {
|
|
33
36
|
available;
|
|
34
37
|
constructor(db, serviceId, dependencies, available) {
|
|
@@ -62,6 +65,9 @@ export class WorkflowRuntime {
|
|
|
62
65
|
executionSettings;
|
|
63
66
|
imageProviders;
|
|
64
67
|
imageCredentials;
|
|
68
|
+
skills;
|
|
69
|
+
skillConfigs;
|
|
70
|
+
skillSessions;
|
|
65
71
|
probe = new ProjectProbe();
|
|
66
72
|
adapter;
|
|
67
73
|
availableModels;
|
|
@@ -88,6 +94,9 @@ export class WorkflowRuntime {
|
|
|
88
94
|
this.executionSettings = new ExecutionSettingsStore(db);
|
|
89
95
|
this.imageCredentials = new ImageCredentials(directories, serviceId);
|
|
90
96
|
this.imageProviders = new ImageProviders(db, this.imageCredentials);
|
|
97
|
+
this.skills = new BuiltinSkills(directories, serviceId, options.skillsOptions);
|
|
98
|
+
this.skillConfigs = new SkillConfigs(directories, serviceId);
|
|
99
|
+
this.skillSessions = new SkillSessions(this.skills, this.skillConfigs, options.backendOptions);
|
|
91
100
|
this.inputs = new InputPreparation(db, { plugins: this.plugins, readRepresentation: resource => this.resources.readPreparedRepresentation(resource) });
|
|
92
101
|
this.models = new ModelDefaultsStore(db);
|
|
93
102
|
this.runs = new RuntimeRuns(db, serviceId, { freeze: () => { throw new ServiceError('INPUT_BLOCKED', 'Use prepareSubmission outside the auth transaction'); }, ...(!options.backend ? { validateReply: (interaction, answer) => { backendReply(interaction, answer); } } : {}) }, (_run, snapshot) => snapshot.imageRoute?.type === 'api'
|
|
@@ -97,7 +106,7 @@ export class WorkflowRuntime {
|
|
|
97
106
|
this.publication = new Publication(db, directories, this.resources, this.graphs, this.runs);
|
|
98
107
|
this.executionChains = new ExecutionChains(this.runs, this.graphs, this.resources);
|
|
99
108
|
this.adapter = options.backend ? undefined : new CodexBackendAdapter(options.backendOptions);
|
|
100
|
-
const codexBackend = options.backend ?? new RuntimeBackend(this.runs, directories, this.adapter, this.probe);
|
|
109
|
+
const codexBackend = options.backend ?? new RuntimeBackend(this.runs, directories, this.adapter, this.probe, this.skillSessions);
|
|
101
110
|
const backend = new RoutedImageBackend(codexBackend, new ApiImageAgent(this.runs, this.resources, this.imageProviders, this.imageCredentials, directories), this.runs);
|
|
102
111
|
this.scheduler = new Scheduler(this.runs, backend, { publish: (run, token) => this.publication.publish(run, token), acquireProjectLock: (_run, path) => acquireProjectLock(path) });
|
|
103
112
|
}
|
|
@@ -320,6 +329,11 @@ export class WorkflowRuntime {
|
|
|
320
329
|
}
|
|
321
330
|
const scopedInputs = new InputPreparation(this.db, { plugins: this.plugins, readRepresentation: resource => prepared.get(resourceKey(resource)) ?? null });
|
|
322
331
|
const scopedPreview = scopedInputs.inputPreview(request.graphId, request.nodeId, !!apiRoute);
|
|
332
|
+
const targetContent = this.db.prepare('SELECT v.content FROM nodes n JOIN node_versions v ON v.node_id=n.id AND v.version=n.current_version WHERE n.id=? AND n.graph_id=?').get(request.nodeId, request.graphId);
|
|
333
|
+
const skillReferences = targetContent ? JSON.parse(String(targetContent.content)).skillReferences : undefined;
|
|
334
|
+
if (apiRoute && Array.isArray(skillReferences) && skillReferences.length)
|
|
335
|
+
throw new ServiceError('INPUT_BLOCKED', '独立图片 API 不支持技能引用,请切换到 Codex 或移除技能引用。');
|
|
336
|
+
const frozenSkills = apiRoute ? [] : await this.skillSessions.freeze(scopedPreview.prompt, skillReferences, path);
|
|
323
337
|
if (apiRoute) {
|
|
324
338
|
const references = [];
|
|
325
339
|
for (const envelope of scopedPreview.resources) {
|
|
@@ -357,7 +371,7 @@ export class WorkflowRuntime {
|
|
|
357
371
|
},
|
|
358
372
|
freeze: () => scopedInputs.build(request.graphId, request.nodeId, apiRoute
|
|
359
373
|
? { expectedExecutionRevision: request.expectedExecutionRevision, imageRoute: apiRoute }
|
|
360
|
-
: { expectedExecutionRevision: request.expectedExecutionRevision, model: this.models.freeze(request.modelOverride, this.availableModels), ...(request.kind === 'image_generation' ? { imageRoute: request.imageRoute?.type === 'codex' ? request.imageRoute : { type: 'codex' } } : {}) }),
|
|
374
|
+
: { expectedExecutionRevision: request.expectedExecutionRevision, model: this.models.freeze(request.modelOverride, this.availableModels), skills: frozenSkills, ...(request.kind === 'image_generation' ? { imageRoute: request.imageRoute?.type === 'codex' ? request.imageRoute : { type: 'codex' } } : {}) }),
|
|
361
375
|
});
|
|
362
376
|
return principal => scoped.submit(request, principal, request.kind === 'execution' && holdExecution);
|
|
363
377
|
}
|
|
@@ -380,6 +394,7 @@ export class WorkflowRuntime {
|
|
|
380
394
|
else {
|
|
381
395
|
this.requireAvailable({ kind: token.details.kind });
|
|
382
396
|
await this.refreshModels();
|
|
397
|
+
await this.skillSessions.environment(snapshot.skills ?? [], true);
|
|
383
398
|
}
|
|
384
399
|
const project = this.db.prepare('SELECT state,canonical_path FROM projects WHERE id=?').get(run.projectId);
|
|
385
400
|
if (!project || project.state !== 'active')
|
package/dist/service.js
CHANGED
|
@@ -136,14 +136,15 @@ export function createService(dataDir, options = {}) {
|
|
|
136
136
|
const rawPath = request.url ?? "";
|
|
137
137
|
const fileRoute = /^[/]v1[/]projects[/][a-zA-Z0-9_-]+[/]files(?:[/](?:search|content|link-content|media|thumbnail|stat))?(?:[?]|$)/.test(rawPath);
|
|
138
138
|
const skillsRoute = /^[/]v1[/]projects[/][a-zA-Z0-9_-]+[/]skills[/]search(?:[?]|$)/.test(rawPath);
|
|
139
|
+
const skillsManagementRoute = /^[/]v1[/]skills(?:[/][^/?#]+(?:[/](?:install|update|uninstall|config|files))?)?(?:[?]|$)/.test(rawPath);
|
|
139
140
|
const mediaRoute = /^[/]v1[/]projects[/][a-zA-Z0-9_-]+[/](?:assets[/][a-zA-Z0-9_-]+[/]versions[/][1-9][0-9]*|graphs[/][a-zA-Z0-9_-]+[/]resources[/][a-zA-Z0-9_-]+[/]versions[/][1-9][0-9]*)[/](?:thumbnail|preview|content)(?:[?]|$)/.test(rawPath);
|
|
140
|
-
const queryRoute = fileRoute || skillsRoute || mediaRoute;
|
|
141
|
+
const queryRoute = fileRoute || skillsRoute || skillsManagementRoute || mediaRoute;
|
|
141
142
|
const path = queryRoute ? rawPath.split('?')[0] : rawPath;
|
|
142
143
|
if (!path.startsWith("/") ||
|
|
143
144
|
path.startsWith("//") ||
|
|
144
145
|
(!queryRoute && path.includes("?")) ||
|
|
145
146
|
path.includes("#") ||
|
|
146
|
-
path.includes("%") ||
|
|
147
|
+
(!skillsManagementRoute && path.includes("%")) ||
|
|
147
148
|
path.includes("\\"))
|
|
148
149
|
throw new ServiceError("INVALID_REQUEST", "请使用标准 API 路径;凭据只能放在 Authorization 请求头,不能放入 URL。");
|
|
149
150
|
if (queryRoute && (rawPath.includes('#') || rawPath.length > 8192 || rawPath.split('?').length > 2))
|
|
@@ -170,11 +171,12 @@ export function createService(dataDir, options = {}) {
|
|
|
170
171
|
.split(",")
|
|
171
172
|
.map((value) => value.trim())
|
|
172
173
|
.filter(Boolean);
|
|
174
|
+
const allowedMethods = /^[/]v1[/]skills[/][^/]+[/]config$/.test(path) ? ["GET", "POST", "PUT", "DELETE"] : ["GET", "POST", "DELETE"];
|
|
173
175
|
if (typeof method !== "string" ||
|
|
174
|
-
!
|
|
176
|
+
!allowedMethods.includes(method) ||
|
|
175
177
|
headers.some((header) => !CORS_HEADERS.includes(header)))
|
|
176
178
|
throw new ServiceError("ORIGIN_DENIED", "不允许的跨来源方法或请求头。");
|
|
177
|
-
response.setHeader("Access-Control-Allow-Methods", "
|
|
179
|
+
response.setHeader("Access-Control-Allow-Methods", allowedMethods.join(", "));
|
|
178
180
|
response.setHeader("Access-Control-Allow-Headers", CORS_HEADERS.join(", "));
|
|
179
181
|
response.setHeader("Access-Control-Max-Age", "300");
|
|
180
182
|
response.writeHead(204);
|
|
@@ -288,7 +290,7 @@ export function createService(dataDir, options = {}) {
|
|
|
288
290
|
events.stream(request, response, token, origin);
|
|
289
291
|
return;
|
|
290
292
|
}
|
|
291
|
-
const business = await api.handle(request, fileRoute || skillsRoute ? rawPath : path, token, origin);
|
|
293
|
+
const business = await api.handle(request, fileRoute || skillsRoute || skillsManagementRoute ? rawPath : path, token, origin);
|
|
292
294
|
if (business.handled) {
|
|
293
295
|
const body = auth.withSession(token, origin, (current) => business.binary ?? (business.binaryParts ? undefined : events.decorate(business.body, current.id)));
|
|
294
296
|
if (business.status)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SkillConfigSchema, SkillConfiguration, SkillConfigurationWrite } from "./vendor/protocol/index.js";
|
|
2
|
+
import type { DataDirectories } from './directories.js';
|
|
3
|
+
/** Omit schema to validate an automatic skill without loading its env/defaults. */
|
|
4
|
+
export interface SkillConfigRevisionRequest {
|
|
5
|
+
skillId: string;
|
|
6
|
+
revision: number;
|
|
7
|
+
schema?: SkillConfigSchema;
|
|
8
|
+
}
|
|
9
|
+
/** Workspace-private, immutable revisions. Uninstall deliberately does not call clear. */
|
|
10
|
+
export declare class SkillConfigs {
|
|
11
|
+
private readonly directories;
|
|
12
|
+
private readonly serviceId;
|
|
13
|
+
private sid;
|
|
14
|
+
constructor(directories: DataDirectories, serviceId: string);
|
|
15
|
+
private windowsSid;
|
|
16
|
+
private windowsAcl;
|
|
17
|
+
private directory;
|
|
18
|
+
private locked;
|
|
19
|
+
private claim;
|
|
20
|
+
private owner;
|
|
21
|
+
private dead;
|
|
22
|
+
private recover;
|
|
23
|
+
private load;
|
|
24
|
+
private manifest;
|
|
25
|
+
private removeRevoked;
|
|
26
|
+
private snapshot;
|
|
27
|
+
private atomic;
|
|
28
|
+
private effective;
|
|
29
|
+
private view;
|
|
30
|
+
read(skillId: string, schema: SkillConfigSchema): Promise<SkillConfiguration>;
|
|
31
|
+
write(skillId: string, schema: SkillConfigSchema, body: SkillConfigurationWrite): Promise<SkillConfiguration>;
|
|
32
|
+
clear(skillId: string, expectedRevision: number): Promise<SkillConfiguration>;
|
|
33
|
+
revision(skillId: string): Promise<number>;
|
|
34
|
+
hasRevision(skillId: string, revision: number): Promise<boolean>;
|
|
35
|
+
environment(skillId: string, schema: SkillConfigSchema, revision: number): Promise<Record<string, string>>;
|
|
36
|
+
private snapshotEnvironment;
|
|
37
|
+
/** Deterministic multi-skill locking prevents clear between the final revision
|
|
38
|
+
* check, secret read, and synchronous spawn/request. No nested public store
|
|
39
|
+
* methods may be awaited inside dispatch. Never await a returned command. */
|
|
40
|
+
withRevisions<T>(requests: readonly SkillConfigRevisionRequest[], dispatch: (environments: ReadonlyMap<string, Record<string, string>>) => T): Promise<{
|
|
41
|
+
value: T;
|
|
42
|
+
}>;
|
|
43
|
+
}
|