@opengeni/runtime 2.5.2 → 2.5.3-canary.1
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/{agent-Q2VNH5FV.js → agent-RJQLKFIW.js} +2 -2
- package/dist/{chunk-LGLJ5PVD.js → chunk-46BFUGFL.js} +2622 -1368
- package/dist/chunk-46BFUGFL.js.map +1 -0
- package/dist/{chunk-CD22I76N.js → chunk-FHGLYLBL.js} +2 -2
- package/dist/{chunk-25B3EQRW.js → chunk-UNICCRW5.js} +4 -4
- package/dist/{chunk-GJC7H2FC.js → chunk-VUEWS4GI.js} +494 -95
- package/dist/chunk-VUEWS4GI.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +22 -4
- package/dist/{localSources-Z5IJIIRV.js → localSources-PVFCAPN2.js} +3 -3
- package/dist/mcp-operation-dispatch.d.ts +45 -0
- package/dist/mcp-operation-observation.d.ts +137 -0
- package/dist/operational-instructions.d.ts +1 -1
- package/dist/sandbox/archive-spool.d.ts +14 -0
- package/dist/sandbox/host-archive-spool.d.ts +19 -0
- package/dist/sandbox/index.d.ts +5 -1
- package/dist/sandbox/index.js +7 -3
- package/dist/sandbox/routing/backend-resolver.d.ts +1 -0
- package/dist/sandbox/selfhosted/file-transfer.d.ts +20 -0
- package/dist/sandbox/selfhosted/session.d.ts +7 -0
- package/dist/sandbox/workspace-archive.d.ts +7 -0
- package/dist/workspace-skills.d.ts +4 -0
- package/dist/workspace-tool-gateway.js +4 -4
- package/package.json +11 -11
- package/src/bundled_default_skills/document-parsing/SKILL.md +36 -0
- package/src/bundled_site_skills/opengeni-sites/SKILL.md +1 -1
- package/src/index.ts +231 -45
- package/src/lazy-tool-transport.ts +2 -0
- package/src/mcp-operation-dispatch.ts +144 -0
- package/src/mcp-operation-observation.ts +148 -0
- package/src/operational-instructions.ts +1 -1
- package/src/runtime-skills.ts +7 -2
- package/src/sandbox/archive-spool.ts +16 -0
- package/src/sandbox/host-archive-spool.ts +1040 -0
- package/src/sandbox/index.ts +292 -203
- package/src/sandbox/routing/backend-resolver.ts +2 -0
- package/src/sandbox/selfhosted/control-rpc.ts +22 -0
- package/src/sandbox/selfhosted/fault-rendering.ts +9 -0
- package/src/sandbox/selfhosted/file-transfer.ts +160 -0
- package/src/sandbox/selfhosted/op-testing.ts +3 -0
- package/src/sandbox/selfhosted/session.ts +194 -5
- package/src/sandbox/workspace-archive.ts +54 -1
- package/src/workspace-skills.ts +171 -31
- package/dist/chunk-GJC7H2FC.js.map +0 -1
- package/dist/chunk-LGLJ5PVD.js.map +0 -1
- /package/dist/{agent-Q2VNH5FV.js.map → agent-RJQLKFIW.js.map} +0 -0
- /package/dist/{chunk-CD22I76N.js.map → chunk-FHGLYLBL.js.map} +0 -0
- /package/dist/{chunk-25B3EQRW.js.map → chunk-UNICCRW5.js.map} +0 -0
- /package/dist/{localSources-Z5IJIIRV.js.map → localSources-PVFCAPN2.js.map} +0 -0
package/src/workspace-skills.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { tool, type Tool } from "@openai/agents";
|
|
3
|
+
import { parseSkillFrontmatter } from "@opengeni/contracts";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import {
|
|
6
|
+
assertSkillRelativePath,
|
|
7
|
+
listSkillPaths,
|
|
8
|
+
readSkillFiles,
|
|
9
|
+
SkillFileError,
|
|
10
|
+
SKILL_READ_MAX_PATHS,
|
|
11
|
+
SKILL_READ_MAX_OUTPUT_BYTES,
|
|
12
|
+
type SkillTextFile,
|
|
13
|
+
} from "./skill-files";
|
|
2
14
|
|
|
3
15
|
import {
|
|
4
16
|
Capability,
|
|
@@ -64,24 +76,94 @@ export class WorkspaceSkillsCapability extends Capability {
|
|
|
64
76
|
super();
|
|
65
77
|
}
|
|
66
78
|
|
|
67
|
-
override
|
|
79
|
+
override bind(session: SandboxSessionLike): this {
|
|
80
|
+
if (this._session !== session) delete this.discovery;
|
|
81
|
+
return super.bind(session);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private discover(): Promise<readonly WorkspaceSkill[]> {
|
|
68
85
|
const session = requireWorkspaceSkillSession(this._session);
|
|
69
|
-
this.discovery ??= discoverWorkspaceSkills(
|
|
86
|
+
return (this.discovery ??= discoverWorkspaceSkills(
|
|
70
87
|
session,
|
|
71
88
|
this.searchPaths,
|
|
72
89
|
this.reservedNames,
|
|
73
90
|
this._runAs,
|
|
74
91
|
this.shadowedNames,
|
|
75
|
-
);
|
|
76
|
-
|
|
92
|
+
));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override tools(): Tool<unknown>[] {
|
|
96
|
+
return [
|
|
97
|
+
tool({
|
|
98
|
+
name: "repository_skill_read",
|
|
99
|
+
description:
|
|
100
|
+
"Read current repository Skill files from the bound workspace. Use the exact repository: identifier from the Repository skills index. Omit paths for SKILL.md; explicit relative paths read only those files. Set listFiles:true without paths for a bounded file inventory. This reads live files through the sandbox, including Connected Machines.",
|
|
101
|
+
parameters: z.object({
|
|
102
|
+
skill: z.string().min(1),
|
|
103
|
+
paths: z.array(z.string().min(1).max(1024)).min(1).max(SKILL_READ_MAX_PATHS).optional(),
|
|
104
|
+
listFiles: z.boolean().optional(),
|
|
105
|
+
}),
|
|
106
|
+
execute: async (request) => {
|
|
107
|
+
if (request.listFiles && request.paths !== undefined)
|
|
108
|
+
throw new SkillFileError(
|
|
109
|
+
"invalid_request",
|
|
110
|
+
"listFiles:true cannot be combined with paths.",
|
|
111
|
+
);
|
|
112
|
+
const skill = (await this.discover()).find(
|
|
113
|
+
(entry) => repositorySkillId(entry) === request.skill,
|
|
114
|
+
);
|
|
115
|
+
if (!skill)
|
|
116
|
+
throw new SkillFileError(
|
|
117
|
+
"missing_file",
|
|
118
|
+
"Repository Skill is not in this index. Use its exact repository: identifier.",
|
|
119
|
+
);
|
|
120
|
+
const session = requireWorkspaceSkillSession(this._session);
|
|
121
|
+
const root = skill.path.slice(0, -SKILL_FILE.length - 1);
|
|
122
|
+
const result = request.listFiles
|
|
123
|
+
? listSkillPaths(
|
|
124
|
+
await repositorySkillInventory(session, root, this._runAs),
|
|
125
|
+
MAX_SKILL_ENTRIES,
|
|
126
|
+
)
|
|
127
|
+
: readSkillFiles(
|
|
128
|
+
await repositorySkillFiles(
|
|
129
|
+
session,
|
|
130
|
+
root,
|
|
131
|
+
request.paths ?? [SKILL_FILE],
|
|
132
|
+
this._runAs,
|
|
133
|
+
),
|
|
134
|
+
request.paths,
|
|
135
|
+
);
|
|
136
|
+
const output = JSON.stringify({
|
|
137
|
+
skillId: request.skill,
|
|
138
|
+
source: "repository",
|
|
139
|
+
root,
|
|
140
|
+
...result,
|
|
141
|
+
});
|
|
142
|
+
if (new TextEncoder().encode(output).byteLength > SKILL_READ_MAX_OUTPUT_BYTES) {
|
|
143
|
+
throw new SkillFileError(
|
|
144
|
+
"output_too_large",
|
|
145
|
+
"Repository Skill response exceeds the read output limit. Request fewer paths.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return output;
|
|
149
|
+
},
|
|
150
|
+
}),
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
override async instructions(): Promise<string | null> {
|
|
155
|
+
const skills = await this.discover();
|
|
77
156
|
if (skills.length === 0) return null;
|
|
78
157
|
|
|
79
158
|
const available = skills
|
|
80
|
-
.map(
|
|
159
|
+
.map(
|
|
160
|
+
(skill) =>
|
|
161
|
+
`- ${JSON.stringify({ id: repositorySkillId(skill), name: skill.name, description: skill.description, reader: "repository_skill_read", path: skill.path })}`,
|
|
162
|
+
)
|
|
81
163
|
.join("\n");
|
|
82
164
|
return `## Repository skills
|
|
83
165
|
|
|
84
|
-
Repository skills are instructions already present in the live workspace.
|
|
166
|
+
Repository skills are instructions already present in the live workspace. Read them with repository_skill_read using the exact repository: id below. The reader returns current live files; ordinary filesystem tools execute referenced scripts and read binary assets. Configured Skills use their separate skill_read index.
|
|
85
167
|
|
|
86
168
|
### Available repository skills
|
|
87
169
|
${available}
|
|
@@ -165,6 +247,18 @@ async function discoverWorkspaceSkillsUnmeasured(
|
|
|
165
247
|
const skillMarkdownPath = joinWorkspacePath(entry.path, SKILL_FILE);
|
|
166
248
|
let markdown: string;
|
|
167
249
|
try {
|
|
250
|
+
// Match reader/inventory eligibility: symlink entrypoints are not
|
|
251
|
+
// advertised because the portable filesystem API cannot prove containment.
|
|
252
|
+
const skillEntries = await session.listDir({
|
|
253
|
+
path: entry.path,
|
|
254
|
+
...(runAs ? { runAs } : {}),
|
|
255
|
+
});
|
|
256
|
+
if (
|
|
257
|
+
!skillEntries.some(
|
|
258
|
+
(candidate) => candidate.name === SKILL_FILE && candidate.type === "file",
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
continue;
|
|
168
262
|
const content = await session.readFile({
|
|
169
263
|
path: skillMarkdownPath,
|
|
170
264
|
...(runAs ? { runAs } : {}),
|
|
@@ -182,7 +276,7 @@ async function discoverWorkspaceSkillsUnmeasured(
|
|
|
182
276
|
const key = name.toLowerCase();
|
|
183
277
|
if (shadowedNames.has(key)) continue;
|
|
184
278
|
const description = frontmatter.description?.trim() || "No description provided.";
|
|
185
|
-
if (description.length > 2_048
|
|
279
|
+
if (description.length > 2_048) {
|
|
186
280
|
throw new Error(`Repository skill "${name}" has an invalid description`);
|
|
187
281
|
}
|
|
188
282
|
if (reservedNames.has(key)) {
|
|
@@ -267,34 +361,80 @@ async function fingerprintWorkspaceDirectory(
|
|
|
267
361
|
return hash.digest("hex");
|
|
268
362
|
}
|
|
269
363
|
|
|
270
|
-
function
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
364
|
+
function repositorySkillId(skill: WorkspaceSkill): string {
|
|
365
|
+
return `repository:${skill.path}`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** Resolve only ordinary directory entries under the discovered Skill root.
|
|
369
|
+
* Never treat a caller identifier as a path or follow a listed symlink. The
|
|
370
|
+
* bound sandbox remains the filesystem authority, including concurrent edits.
|
|
371
|
+
*/
|
|
372
|
+
async function repositorySkillFiles(
|
|
373
|
+
session: SandboxSessionLike,
|
|
374
|
+
root: string,
|
|
375
|
+
paths: readonly string[],
|
|
376
|
+
runAs?: string,
|
|
377
|
+
): Promise<SkillTextFile[]> {
|
|
378
|
+
// Validate the entire request before touching the filesystem.
|
|
379
|
+
readSkillFiles(
|
|
380
|
+
paths.map((path) => ({ path, content: "" })),
|
|
381
|
+
paths,
|
|
382
|
+
);
|
|
383
|
+
const files: SkillTextFile[] = [];
|
|
384
|
+
for (const path of paths) {
|
|
385
|
+
let directory = root;
|
|
386
|
+
const segments = path.split("/");
|
|
387
|
+
for (const [index, name] of segments.entries()) {
|
|
388
|
+
const entries = await session.listDir!({ path: directory, ...(runAs ? { runAs } : {}) });
|
|
389
|
+
const entry = entries.find((candidate) => candidate.name === name);
|
|
390
|
+
const expected = index === segments.length - 1 ? "file" : "dir";
|
|
391
|
+
if (entry?.type !== expected)
|
|
392
|
+
throw new SkillFileError("missing_file", `Repository Skill file is unavailable: ${path}`);
|
|
393
|
+
directory = joinWorkspacePath(directory, name);
|
|
394
|
+
}
|
|
395
|
+
const content = await session.readFile!({ path: directory, ...(runAs ? { runAs } : {}) });
|
|
396
|
+
files.push({
|
|
397
|
+
path,
|
|
398
|
+
content:
|
|
399
|
+
typeof content === "string"
|
|
400
|
+
? content
|
|
401
|
+
: new TextDecoder("utf-8", { fatal: true }).decode(content),
|
|
402
|
+
});
|
|
403
|
+
// Bound aggregate output as each file arrives; do not truncate accepted text.
|
|
404
|
+
readSkillFiles(
|
|
405
|
+
files,
|
|
406
|
+
files.map((file) => file.path),
|
|
407
|
+
);
|
|
285
408
|
}
|
|
286
|
-
return
|
|
409
|
+
return files;
|
|
287
410
|
}
|
|
288
411
|
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
412
|
+
async function repositorySkillInventory(
|
|
413
|
+
session: SandboxSessionLike,
|
|
414
|
+
root: string,
|
|
415
|
+
runAs?: string,
|
|
416
|
+
): Promise<SkillTextFile[]> {
|
|
417
|
+
const files: SkillTextFile[] = [];
|
|
418
|
+
let count = 0;
|
|
419
|
+
async function visit(relative: string): Promise<void> {
|
|
420
|
+
const entries = await session.listDir!({
|
|
421
|
+
path: relative ? joinWorkspacePath(root, relative) : root,
|
|
422
|
+
...(runAs ? { runAs } : {}),
|
|
423
|
+
});
|
|
424
|
+
for (const entry of entries) {
|
|
425
|
+
if (++count > MAX_SKILL_ENTRIES)
|
|
426
|
+
throw new SkillFileError("output_too_large", "Repository Skill inventory is too large.");
|
|
427
|
+
// Paths derive from validated names, never provider-returned absolute paths.
|
|
428
|
+
assertSkillRelativePath(entry.name);
|
|
429
|
+
if (entry.name.includes("/"))
|
|
430
|
+
throw new SkillFileError("invalid_path", "Invalid repository directory entry.");
|
|
431
|
+
const path = relative ? `${relative}/${entry.name}` : entry.name;
|
|
432
|
+
if (entry.type === "dir") await visit(path);
|
|
433
|
+
else if (entry.type === "file") files.push({ path, content: "" });
|
|
434
|
+
}
|
|
296
435
|
}
|
|
297
|
-
|
|
436
|
+
await visit("");
|
|
437
|
+
return files;
|
|
298
438
|
}
|
|
299
439
|
|
|
300
440
|
function joinWorkspacePath(parent: string, child: string): string {
|