@rigkit/sdk 0.2.1 → 0.2.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/package.json +3 -3
- package/src/cli.ts +5 -0
- package/src/index.test.ts +1 -1
- package/src/index.ts +0 -2
- package/src/runtime/api.ts +0 -2
- package/src/runtime/app.test.ts +88 -78
- package/src/runtime/cli.ts +5 -0
- package/src/runtime/control.ts +40 -8
- package/src/runtime/index.ts +0 -2
- package/src/runtime/operations.ts +8 -57
- package/src/runtime/protocol.ts +2 -21
- package/src/runtime/server.ts +5 -0
- package/src/runtime/sessions.ts +0 -56
- package/src/runtime/types.ts +2 -0
- package/src/runtime/version.ts +1 -1
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigkit/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"@effect/platform": "0.96.1",
|
|
24
24
|
"@effect/platform-bun": "0.89.0",
|
|
25
25
|
"effect": "^3.21.2",
|
|
26
|
-
"@rigkit/engine": "0.2.
|
|
27
|
-
"@rigkit/runtime-client": "0.2.
|
|
26
|
+
"@rigkit/engine": "0.2.3",
|
|
27
|
+
"@rigkit/runtime-client": "0.2.3"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/bun": "latest",
|
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { serveRuntime } from "./runtime/server.ts";
|
|
|
4
4
|
|
|
5
5
|
type ServeArgs = {
|
|
6
6
|
projectId?: string;
|
|
7
|
+
runtimeFingerprint?: string;
|
|
7
8
|
projectDir?: string;
|
|
8
9
|
configPath?: string;
|
|
9
10
|
statePath?: string;
|
|
@@ -37,6 +38,7 @@ if (missing.length > 0) {
|
|
|
37
38
|
|
|
38
39
|
const runtime = await serveRuntime({
|
|
39
40
|
projectId: options.projectId!,
|
|
41
|
+
runtimeFingerprint: options.runtimeFingerprint,
|
|
40
42
|
projectDir: resolve(options.projectDir!),
|
|
41
43
|
configPath: resolve(options.configPath!),
|
|
42
44
|
statePath: options.statePath ? resolve(options.statePath) : undefined,
|
|
@@ -79,6 +81,9 @@ function parseServeArgs(args: string[]): ServeArgs {
|
|
|
79
81
|
case "--project-id":
|
|
80
82
|
parsed.projectId = readValue();
|
|
81
83
|
break;
|
|
84
|
+
case "--runtime-fingerprint":
|
|
85
|
+
parsed.runtimeFingerprint = readValue();
|
|
86
|
+
break;
|
|
82
87
|
case "--project-dir":
|
|
83
88
|
case "--project":
|
|
84
89
|
parsed.projectDir = readValue();
|
package/src/index.test.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { defineHostCapabilities, defineHostCapability } from "./host.ts";
|
|
|
16
16
|
|
|
17
17
|
describe("@rigkit/sdk package boundary", () => {
|
|
18
18
|
test("exports authoring API and project runtime entrypoints", () => {
|
|
19
|
-
expect(RIGKIT_SDK_VERSION).toBe("0.2.
|
|
19
|
+
expect(RIGKIT_SDK_VERSION).toBe("0.2.3");
|
|
20
20
|
expect(env).toBeTypeOf("function");
|
|
21
21
|
expect(env.secret).toBeTypeOf("function");
|
|
22
22
|
expect(defineConfig).toBeTypeOf("function");
|
package/src/index.ts
CHANGED
|
@@ -19,12 +19,10 @@ export {
|
|
|
19
19
|
DEFAULT_IDLE_MS,
|
|
20
20
|
RIGKIT_RUNTIME_VERSION,
|
|
21
21
|
createRuntimeStateService,
|
|
22
|
-
HostCapabilityRequirementEffectSchema,
|
|
23
22
|
HostCommandRequestEffectSchema,
|
|
24
23
|
HostCommandRequestSchema,
|
|
25
24
|
HostCommandResultEffectSchema,
|
|
26
25
|
HostCommandResultSchema,
|
|
27
|
-
HostMethodRequirementEffectSchema,
|
|
28
26
|
HostResponseEffectSchema,
|
|
29
27
|
HostResponseSchema,
|
|
30
28
|
OkResponseEffectSchema,
|
package/src/runtime/api.ts
CHANGED
|
@@ -3,8 +3,6 @@ import { runtimeControlApi } from "@rigkit/runtime-client";
|
|
|
3
3
|
|
|
4
4
|
export {
|
|
5
5
|
RuntimeControlHealthEffectSchema as RuntimeHealthEffectSchema,
|
|
6
|
-
RuntimeControlHostCapabilityRequirementEffectSchema as HostCapabilityRequirementEffectSchema,
|
|
7
|
-
RuntimeControlHostMethodRequirementEffectSchema as HostMethodRequirementEffectSchema,
|
|
8
6
|
RuntimeControlMetadataEffectSchema as RuntimeMetadataEffectSchema,
|
|
9
7
|
RuntimeControlOkResponseEffectSchema as OkResponseEffectSchema,
|
|
10
8
|
RuntimeControlOperationCliEffectSchema as RuntimeOperationCliEffectSchema,
|
package/src/runtime/app.test.ts
CHANGED
|
@@ -111,6 +111,7 @@ describe("runtime HTTP app", () => {
|
|
|
111
111
|
let closed: Promise<void> | undefined;
|
|
112
112
|
|
|
113
113
|
try {
|
|
114
|
+
writeNoopConfig(root);
|
|
114
115
|
await Effect.runPromise(Effect.scoped(
|
|
115
116
|
Effect.flatMap(
|
|
116
117
|
serveRuntimeEffect({
|
|
@@ -146,6 +147,7 @@ describe("runtime HTTP app", () => {
|
|
|
146
147
|
const root = mkdtempSync(join(tmpdir(), "rigkit-runtime-shutdown-"));
|
|
147
148
|
|
|
148
149
|
try {
|
|
150
|
+
writeNoopConfig(root);
|
|
149
151
|
const server = await serveRuntime({
|
|
150
152
|
projectId: "test-project",
|
|
151
153
|
projectDir: root,
|
|
@@ -167,6 +169,26 @@ describe("runtime HTTP app", () => {
|
|
|
167
169
|
}
|
|
168
170
|
});
|
|
169
171
|
|
|
172
|
+
test("loads config before reporting runtime readiness", async () => {
|
|
173
|
+
const root = mkdtempSync(join(tmpdir(), "rigkit-runtime-startup-config-"));
|
|
174
|
+
const configPath = join(root, "rig.config.ts");
|
|
175
|
+
writeFileSync(configPath, "throw new Error('startup config failed');\n");
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await expect(serveRuntime({
|
|
179
|
+
projectId: "test-project",
|
|
180
|
+
projectDir: root,
|
|
181
|
+
configPath,
|
|
182
|
+
handlePath: join(root, "runtime.json"),
|
|
183
|
+
tokenPath: join(root, "runtime.token"),
|
|
184
|
+
token: "test-token",
|
|
185
|
+
idleMs: 60_000,
|
|
186
|
+
})).rejects.toThrow("startup config failed");
|
|
187
|
+
} finally {
|
|
188
|
+
rmSync(root, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
170
192
|
test("returns structured validation errors", async () => {
|
|
171
193
|
const app = createRuntimeApp(testContext(), createRunStore());
|
|
172
194
|
|
|
@@ -215,11 +237,10 @@ describe("runtime HTTP app", () => {
|
|
|
215
237
|
scheme: "bearer",
|
|
216
238
|
});
|
|
217
239
|
expect(body.components.schemas.RuntimeOperation.required).toContain("inputSchema");
|
|
218
|
-
expect(body.components.schemas.Workspace.required).toContain("
|
|
240
|
+
expect(body.components.schemas.Workspace.required).toContain("ctx");
|
|
219
241
|
expect(body.components.schemas.OperationsManifest.required).toEqual([
|
|
220
|
-
"hostMethods",
|
|
221
|
-
"hostCapabilities",
|
|
222
242
|
"operations",
|
|
243
|
+
"workspaceOperations",
|
|
223
244
|
]);
|
|
224
245
|
});
|
|
225
246
|
|
|
@@ -255,7 +276,6 @@ describe("runtime HTTP app", () => {
|
|
|
255
276
|
kind: "command",
|
|
256
277
|
title: "SSH",
|
|
257
278
|
description: "Get an SSH command",
|
|
258
|
-
requiredHostMethods: [{ id: "host.command.run", modes: ["interactive"] }],
|
|
259
279
|
inputFields: [
|
|
260
280
|
{ kind: "string", name: "workflow", required: false },
|
|
261
281
|
{ kind: "string", name: "workspaceOrVmId", position: 0, required: true },
|
|
@@ -277,9 +297,6 @@ describe("runtime HTTP app", () => {
|
|
|
277
297
|
source: "config",
|
|
278
298
|
title: "Open",
|
|
279
299
|
description: "Open a workspace",
|
|
280
|
-
createsWorkspace: false,
|
|
281
|
-
requiredHostMethods: [{ id: "host.command.run", modes: ["interactive"] }],
|
|
282
|
-
requiredHostCapabilities: [{ id: "cmux.open", schemaHash: "sha256:cmux-open-schema" }],
|
|
283
300
|
inputFields: [
|
|
284
301
|
{
|
|
285
302
|
kind: "workspace",
|
|
@@ -297,33 +314,47 @@ describe("runtime HTTP app", () => {
|
|
|
297
314
|
},
|
|
298
315
|
],
|
|
299
316
|
},
|
|
317
|
+
{
|
|
318
|
+
workflow: "",
|
|
319
|
+
id: "create",
|
|
320
|
+
source: "core",
|
|
321
|
+
kind: "command",
|
|
322
|
+
createsWorkspace: true,
|
|
323
|
+
inputFields: [{ kind: "string", name: "name", required: true }],
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
listRuntimeWorkspaceOperations: () => [
|
|
300
327
|
{
|
|
301
328
|
workflow: "test",
|
|
302
|
-
id: "
|
|
329
|
+
id: "remove",
|
|
330
|
+
source: "core",
|
|
331
|
+
kind: "workspace-action",
|
|
332
|
+
title: "Remove",
|
|
333
|
+
description: "Remove a workspace",
|
|
334
|
+
inputFields: [],
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
workflow: "test",
|
|
338
|
+
id: "open-cmux",
|
|
303
339
|
source: "config",
|
|
304
|
-
|
|
340
|
+
kind: "workspace-action",
|
|
341
|
+
title: "Open cmux",
|
|
342
|
+
description: "Open a workspace in cmux",
|
|
305
343
|
inputFields: [],
|
|
306
344
|
},
|
|
307
345
|
],
|
|
308
346
|
} as any);
|
|
309
347
|
|
|
310
348
|
const operation = manifest.operations.find((item) => item.id === "open");
|
|
311
|
-
const
|
|
349
|
+
const createOperation = manifest.operations.find((item) => item.id === "create");
|
|
312
350
|
const sshOperation = manifest.operations.find((item) => item.id === "ssh");
|
|
351
|
+
const cmuxOperation = manifest.workspaceOperations.find((item) => item.id === "open-cmux");
|
|
313
352
|
const inputSchema = operation?.inputSchema as any;
|
|
314
353
|
const sshInputSchema = sshOperation?.inputSchema as any;
|
|
315
354
|
|
|
316
355
|
expect(operation?.source).toBe("config");
|
|
317
356
|
expect(operation?.kind).toBe("workspace-action");
|
|
318
|
-
expect(
|
|
319
|
-
{ id: "host.command.run", modes: ["interactive"] },
|
|
320
|
-
]);
|
|
321
|
-
expect(operation?.requiredHostCapabilities).toEqual([
|
|
322
|
-
{ id: "cmux.open", schemaHash: "sha256:cmux-open-schema" },
|
|
323
|
-
]);
|
|
324
|
-
expect(manifest.hostCapabilities.optional).toEqual([
|
|
325
|
-
{ id: "cmux.open", schemaHash: "sha256:cmux-open-schema" },
|
|
326
|
-
]);
|
|
357
|
+
expect(cmuxOperation?.id).toBe("open-cmux");
|
|
327
358
|
expect(operation?.cli?.positionals).toEqual([{ name: "workspace", index: 0 }]);
|
|
328
359
|
expect(operation?.cli?.options).toEqual([
|
|
329
360
|
{ name: "rebuild", flag: "--rebuild", required: false, type: "boolean" },
|
|
@@ -339,9 +370,9 @@ describe("runtime HTTP app", () => {
|
|
|
339
370
|
default: false,
|
|
340
371
|
description: "Rebuild before opening",
|
|
341
372
|
});
|
|
342
|
-
expect(
|
|
343
|
-
expect(
|
|
344
|
-
expect(manifest.
|
|
373
|
+
expect(createOperation?.source).toBe("core");
|
|
374
|
+
expect(createOperation?.createsWorkspace).toBe(true);
|
|
375
|
+
expect(manifest.workspaceOperations.map((item) => item.id)).toEqual(["remove", "open-cmux"]);
|
|
345
376
|
expect(sshOperation?.cli?.options?.find((item) => item.name === "print")).toEqual({
|
|
346
377
|
name: "print",
|
|
347
378
|
flag: "--print",
|
|
@@ -425,8 +456,6 @@ describe("runtime HTTP app", () => {
|
|
|
425
456
|
|
|
426
457
|
expect(ack?.operation).toEqual({
|
|
427
458
|
id: "plan",
|
|
428
|
-
requiredHostCapabilities: [],
|
|
429
|
-
requiredHostMethods: [],
|
|
430
459
|
});
|
|
431
460
|
expect(messages.some((message) => message.type === "heartbeat.ack")).toBe(true);
|
|
432
461
|
expect(messages.some((message) => message.type === "run.completed")).toBe(true);
|
|
@@ -435,21 +464,24 @@ describe("runtime HTTP app", () => {
|
|
|
435
464
|
}
|
|
436
465
|
});
|
|
437
466
|
|
|
438
|
-
test("exposes persisted workspace payload as workspace
|
|
439
|
-
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-runtime-workspace-
|
|
467
|
+
test("exposes persisted workspace payload as workspace context", async () => {
|
|
468
|
+
const projectDir = mkdtempSync(join(tmpdir(), "rigkit-runtime-workspace-ctx-"));
|
|
440
469
|
const configPath = join(projectDir, "rig.config.ts");
|
|
441
470
|
writeFileSync(
|
|
442
471
|
configPath,
|
|
443
472
|
`
|
|
444
473
|
import { defineConfig, sequence } from "${import.meta.dir}/../../../engine/src/index.ts";
|
|
445
474
|
|
|
446
|
-
const root = sequence("workspace-
|
|
475
|
+
const root = sequence("workspace-ctx")
|
|
447
476
|
.step("prepare", async () => ({ repoPath: "/workspace/repo" }))
|
|
448
|
-
.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
477
|
+
.workspace({
|
|
478
|
+
create: async ({ workflow, workspace }) => ({
|
|
479
|
+
name: workspace.name,
|
|
480
|
+
vmId: "vm-" + workspace.name,
|
|
481
|
+
repoPath: workflow.ctx.repoPath,
|
|
482
|
+
}),
|
|
483
|
+
remove: async () => {},
|
|
484
|
+
});
|
|
453
485
|
|
|
454
486
|
export default defineConfig({
|
|
455
487
|
providers: {},
|
|
@@ -459,7 +491,7 @@ describe("runtime HTTP app", () => {
|
|
|
459
491
|
);
|
|
460
492
|
|
|
461
493
|
const server = await serveRuntime({
|
|
462
|
-
projectId: "project-workspace-
|
|
494
|
+
projectId: "project-workspace-ctx-test",
|
|
463
495
|
projectDir,
|
|
464
496
|
configPath,
|
|
465
497
|
statePath: join(projectDir, "state.sqlite"),
|
|
@@ -483,14 +515,13 @@ describe("runtime HTTP app", () => {
|
|
|
483
515
|
|
|
484
516
|
const { workspaces } = await fetch(new URL("/workspaces", server.url), {
|
|
485
517
|
headers: { authorization: `Bearer ${server.token}` },
|
|
486
|
-
}).then((response) => response.json() as Promise<{ workspaces: Array<{
|
|
518
|
+
}).then((response) => response.json() as Promise<{ workspaces: Array<{ ctx: Record<string, unknown> }> }>);
|
|
487
519
|
|
|
488
|
-
expect(workspaces[0]?.
|
|
520
|
+
expect(workspaces[0]?.ctx).toEqual({
|
|
489
521
|
name: "demo",
|
|
490
|
-
|
|
522
|
+
vmId: "vm-demo",
|
|
491
523
|
repoPath: "/workspace/repo",
|
|
492
524
|
});
|
|
493
|
-
expect(workspaces[0]?.metadata).toEqual(workspaces[0]?.data);
|
|
494
525
|
} finally {
|
|
495
526
|
server.stop();
|
|
496
527
|
}
|
|
@@ -506,7 +537,10 @@ describe("runtime HTTP app", () => {
|
|
|
506
537
|
|
|
507
538
|
const root = sequence("validation")
|
|
508
539
|
.step("prepare", async () => ({ ok: true }))
|
|
509
|
-
.
|
|
540
|
+
.workspace({
|
|
541
|
+
create: async ({ workspace }) => ({ name: workspace.name, vmId: "vm-" + workspace.name }),
|
|
542
|
+
remove: async () => {},
|
|
543
|
+
});
|
|
510
544
|
|
|
511
545
|
export default defineConfig({
|
|
512
546
|
providers: {},
|
|
@@ -552,46 +586,9 @@ describe("runtime HTTP app", () => {
|
|
|
552
586
|
}
|
|
553
587
|
});
|
|
554
588
|
|
|
555
|
-
test("fails run sessions when host hello lacks required methods or capabilities", async () => {
|
|
556
|
-
const { server, projectDir } = await serveRuntimeFixture("rigkit-runtime-required-host-", `
|
|
557
|
-
const root = sequence("required-host").operation("needs-host", {
|
|
558
|
-
requiredHostMethods: [{ id: "host.command.run", modes: ["capture"] }],
|
|
559
|
-
requiredHostCapabilities: [{ id: "cmux.open", schemaHash: "sha256:cmux-open-schema" }],
|
|
560
|
-
run: async () => await new Promise(() => {}),
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
export default defineConfig({
|
|
564
|
-
providers: {},
|
|
565
|
-
workflows: { root },
|
|
566
|
-
});
|
|
567
|
-
`);
|
|
568
|
-
|
|
569
|
-
try {
|
|
570
|
-
const started = await startRun(server, "needs-host");
|
|
571
|
-
const messages = await collectSessionMessages(
|
|
572
|
-
new URL(started.sessionUrl, server.url),
|
|
573
|
-
server.token,
|
|
574
|
-
{
|
|
575
|
-
done: (items) => items.some((item) => item.type === "run.failed"),
|
|
576
|
-
},
|
|
577
|
-
);
|
|
578
|
-
const failed = messages.find((message) => message.type === "run.failed");
|
|
579
|
-
const message = String(failed?.error?.message ?? "");
|
|
580
|
-
|
|
581
|
-
expect(failed?.error?.code).toBe("HOST_REQUEST_FAILED");
|
|
582
|
-
expect(message).toContain("host method host.command.run:capture");
|
|
583
|
-
expect(message).toContain("host capability cmux.open@sha256:cmux-open-schema");
|
|
584
|
-
expect(messages.some((item) => item.type === "hello.ack")).toBe(false);
|
|
585
|
-
} finally {
|
|
586
|
-
server.stop();
|
|
587
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
|
|
591
589
|
test("bridges typed host capability requests over run sessions", async () => {
|
|
592
590
|
const { server, projectDir } = await serveRuntimeFixture("rigkit-runtime-capability-", `
|
|
593
591
|
const root = sequence("capability-test").operation("open", {
|
|
594
|
-
requiredHostCapabilities: [{ id: "cmux.open", schemaHash: "sha256:cmux-open-schema" }],
|
|
595
592
|
run: async ({ local }) => await local.requestCapability("cmux.open", { name: "demo" }),
|
|
596
593
|
});
|
|
597
594
|
|
|
@@ -637,7 +634,6 @@ describe("runtime HTTP app", () => {
|
|
|
637
634
|
test("resolves host capability resource lifetimes from session close reports", async () => {
|
|
638
635
|
const { server, projectDir } = await serveRuntimeFixture("rigkit-runtime-capability-close-", `
|
|
639
636
|
const root = sequence("capability-close-test").operation("open", {
|
|
640
|
-
requiredHostCapabilities: [{ id: "cmux.open", schemaHash: "sha256:cmux-open-schema" }],
|
|
641
637
|
run: async ({ local }) => {
|
|
642
638
|
if (!local.requestCapabilitySession) throw new Error("requestCapabilitySession unavailable");
|
|
643
639
|
const session = await local.requestCapabilitySession("cmux.open", { name: "demo" });
|
|
@@ -686,7 +682,6 @@ describe("runtime HTTP app", () => {
|
|
|
686
682
|
test("keeps host-owned capability runs attached until the host cancels", async () => {
|
|
687
683
|
const { server, projectDir } = await serveRuntimeFixture("rigkit-runtime-capability-attached-", `
|
|
688
684
|
const root = sequence("capability-attached-test").operation("open", {
|
|
689
|
-
requiredHostCapabilities: [{ id: "cmux.open", schemaHash: "sha256:cmux-open-schema" }],
|
|
690
685
|
run: async ({ local }) => {
|
|
691
686
|
await local.requestCapability("cmux.open", { name: "demo" });
|
|
692
687
|
await new Promise(() => {});
|
|
@@ -733,7 +728,6 @@ describe("runtime HTTP app", () => {
|
|
|
733
728
|
test("turns host response errors into typed host request failures", async () => {
|
|
734
729
|
const { server, projectDir } = await serveRuntimeFixture("rigkit-runtime-capability-error-", `
|
|
735
730
|
const root = sequence("capability-error-test").operation("cmux-open", {
|
|
736
|
-
requiredHostCapabilities: [{ id: "cmux.open", schemaHash: "sha256:cmux-open-schema" }],
|
|
737
731
|
run: async ({ local }) => await local.requestCapability("cmux.open", { name: "demo" }),
|
|
738
732
|
});
|
|
739
733
|
|
|
@@ -806,6 +800,22 @@ describe("runtime HTTP app", () => {
|
|
|
806
800
|
});
|
|
807
801
|
});
|
|
808
802
|
|
|
803
|
+
function writeNoopConfig(projectDir: string): void {
|
|
804
|
+
writeFileSync(
|
|
805
|
+
join(projectDir, "rig.config.ts"),
|
|
806
|
+
`
|
|
807
|
+
import { defineConfig, sequence } from "${import.meta.dir}/../../../engine/src/index.ts";
|
|
808
|
+
|
|
809
|
+
const root = sequence("noop").step("ready", async () => ({ ready: true }));
|
|
810
|
+
|
|
811
|
+
export default defineConfig({
|
|
812
|
+
providers: {},
|
|
813
|
+
workflows: { root },
|
|
814
|
+
});
|
|
815
|
+
`,
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
809
819
|
async function serveRuntimeFixture(prefix: string, configBody: string) {
|
|
810
820
|
const projectDir = mkdtempSync(join(tmpdir(), prefix));
|
|
811
821
|
const configPath = join(projectDir, "rig.config.ts");
|
package/src/runtime/cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { serveRuntime } from "./server.ts";
|
|
|
4
4
|
|
|
5
5
|
type ServeArgs = {
|
|
6
6
|
projectId?: string;
|
|
7
|
+
runtimeFingerprint?: string;
|
|
7
8
|
projectDir?: string;
|
|
8
9
|
configPath?: string;
|
|
9
10
|
statePath?: string;
|
|
@@ -37,6 +38,7 @@ if (missing.length > 0) {
|
|
|
37
38
|
|
|
38
39
|
const runtime = await serveRuntime({
|
|
39
40
|
projectId: options.projectId!,
|
|
41
|
+
runtimeFingerprint: options.runtimeFingerprint,
|
|
40
42
|
projectDir: resolve(options.projectDir!),
|
|
41
43
|
configPath: resolve(options.configPath!),
|
|
42
44
|
statePath: options.statePath ? resolve(options.statePath) : undefined,
|
|
@@ -78,6 +80,9 @@ function parseServeArgs(args: string[]): ServeArgs {
|
|
|
78
80
|
case "--project-id":
|
|
79
81
|
parsed.projectId = readValue();
|
|
80
82
|
break;
|
|
83
|
+
case "--runtime-fingerprint":
|
|
84
|
+
parsed.runtimeFingerprint = readValue();
|
|
85
|
+
break;
|
|
81
86
|
case "--project-dir":
|
|
82
87
|
case "--project":
|
|
83
88
|
parsed.projectDir = readValue();
|
package/src/runtime/control.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type HostResponse,
|
|
10
10
|
type RunOperationRequest,
|
|
11
11
|
type RuntimeOperation,
|
|
12
|
+
type RuntimeOperationsManifest,
|
|
12
13
|
} from "./protocol.ts";
|
|
13
14
|
import {
|
|
14
15
|
createRun,
|
|
@@ -34,6 +35,7 @@ export function runtimeHealth(context: RuntimeContext) {
|
|
|
34
35
|
return {
|
|
35
36
|
ok: true,
|
|
36
37
|
projectId: context.projectId,
|
|
38
|
+
runtimeFingerprint: context.runtimeFingerprint,
|
|
37
39
|
projectDir: context.projectDir,
|
|
38
40
|
configPath: context.configPath,
|
|
39
41
|
statePath: context.statePath,
|
|
@@ -86,10 +88,10 @@ export function runtimeRuns(store: RunStore) {
|
|
|
86
88
|
export async function startRuntimeRun(state: RuntimeAppState, body: RunOperationRequest) {
|
|
87
89
|
const engine = await loadEngine(state.context);
|
|
88
90
|
const manifest = operationManifestFor(engine);
|
|
89
|
-
const
|
|
90
|
-
if (!
|
|
91
|
+
const resolved = resolveRuntimeOperation(manifest, body.operation);
|
|
92
|
+
if (!resolved) throw new RuntimeControlHttpError(400, `Unknown operation ${body.operation}`);
|
|
91
93
|
|
|
92
|
-
const run = createRun(
|
|
94
|
+
const run = createRun(resolved.runOperation, body.input ?? {}, resolved.operation);
|
|
93
95
|
state.store.runs.set(run.id, run);
|
|
94
96
|
runOperation(run, state.store, state.context);
|
|
95
97
|
|
|
@@ -143,16 +145,46 @@ export function runtimeControlErrorStatus(error: unknown): number {
|
|
|
143
145
|
return 500;
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
+
function resolveRuntimeOperation(
|
|
149
|
+
manifest: RuntimeOperationsManifest,
|
|
150
|
+
requestedOperation: string,
|
|
151
|
+
): { operation: RuntimeOperation; runOperation: string } | undefined {
|
|
152
|
+
const workspaceOperation = parseWorkspaceOperationId(requestedOperation);
|
|
153
|
+
if (workspaceOperation) {
|
|
154
|
+
const operation = manifest.workspaceOperations.find((item) => item.id === workspaceOperation.operation);
|
|
155
|
+
return operation ? { operation, runOperation: requestedOperation } : undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const operation = manifest.operations.find((operation) =>
|
|
148
159
|
operation.id === requestedOperation || operation.aliases?.includes(requestedOperation)
|
|
149
160
|
);
|
|
161
|
+
return operation ? { operation, runOperation: operation.id } : undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function parseWorkspaceOperationId(value: string): { workspace: string; operation: string } | undefined {
|
|
165
|
+
const slash = value.indexOf("/");
|
|
166
|
+
if (slash <= 0 || slash === value.length - 1) return undefined;
|
|
167
|
+
return {
|
|
168
|
+
workspace: value.slice(0, slash),
|
|
169
|
+
operation: value.slice(slash + 1),
|
|
170
|
+
};
|
|
150
171
|
}
|
|
151
172
|
|
|
152
|
-
function runtimeWorkspace(workspace: WorkspaceRecord):
|
|
173
|
+
function runtimeWorkspace(workspace: WorkspaceRecord): {
|
|
174
|
+
id: string;
|
|
175
|
+
name: string;
|
|
176
|
+
workflow: string;
|
|
177
|
+
ctx: WorkspaceRecord["ctx"];
|
|
178
|
+
createdAt: string;
|
|
179
|
+
updatedAt: string;
|
|
180
|
+
} {
|
|
153
181
|
return {
|
|
154
|
-
|
|
155
|
-
|
|
182
|
+
id: workspace.id,
|
|
183
|
+
name: workspace.name,
|
|
184
|
+
workflow: workspace.workflow,
|
|
185
|
+
ctx: workspace.ctx,
|
|
186
|
+
createdAt: workspace.createdAt,
|
|
187
|
+
updatedAt: workspace.updatedAt,
|
|
156
188
|
};
|
|
157
189
|
}
|
|
158
190
|
|
package/src/runtime/index.ts
CHANGED
|
@@ -100,6 +100,12 @@ export function operationsFor(engine: DevMachineEngine): RuntimeOperation[] {
|
|
|
100
100
|
return engine.listRuntimeOperations().map((operation) => runtimeOperationForEngineOperation(engine, operation));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
export function workspaceOperationsFor(engine: DevMachineEngine): RuntimeOperation[] {
|
|
104
|
+
return engine.listRuntimeWorkspaceOperations().map((operation) =>
|
|
105
|
+
runtimeOperationForEngineOperation(engine, operation)
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
103
109
|
function runtimeOperationForEngineOperation(engine: DevMachineEngine, operation: EngineOperationSummary): RuntimeOperation {
|
|
104
110
|
const required = operation.inputFields
|
|
105
111
|
.filter((field) => field.required ?? true)
|
|
@@ -116,14 +122,6 @@ function runtimeOperationForEngineOperation(engine: DevMachineEngine, operation:
|
|
|
116
122
|
title: operation.title ?? titleize(operation.id),
|
|
117
123
|
description: operation.description ?? "",
|
|
118
124
|
createsWorkspace: operation.createsWorkspace,
|
|
119
|
-
requiredHostMethods: operation.requiredHostMethods?.map((method) => ({
|
|
120
|
-
id: method.id,
|
|
121
|
-
...(method.modes?.length ? { modes: [...method.modes] } : {}),
|
|
122
|
-
})),
|
|
123
|
-
requiredHostCapabilities: operation.requiredHostCapabilities?.map((capability) => ({
|
|
124
|
-
id: capability.id,
|
|
125
|
-
...(capability.schemaHash ? { schemaHash: capability.schemaHash } : {}),
|
|
126
|
-
})),
|
|
127
125
|
cli: operation.cli ? cloneOperationCli(operation.cli) : cliForFields(operation.inputFields),
|
|
128
126
|
inputSchema: objectSchema(properties, required),
|
|
129
127
|
};
|
|
@@ -188,60 +186,13 @@ function cliForFields(fields: EngineOperationSummary["inputFields"]): NonNullabl
|
|
|
188
186
|
|
|
189
187
|
export function operationManifestFor(engine: DevMachineEngine): RuntimeOperationsManifest {
|
|
190
188
|
const operations = operationsFor(engine);
|
|
189
|
+
const workspaceOperations = workspaceOperationsFor(engine);
|
|
191
190
|
return {
|
|
192
|
-
hostMethods: {
|
|
193
|
-
known: [
|
|
194
|
-
{ id: "message.show" },
|
|
195
|
-
{ id: "prompt.text" },
|
|
196
|
-
{ id: "prompt.confirm" },
|
|
197
|
-
{ id: "prompt.select" },
|
|
198
|
-
{ id: "open.external" },
|
|
199
|
-
{ id: "host.command.run", modes: ["capture", "interactive"] },
|
|
200
|
-
],
|
|
201
|
-
requiredByOperations: Object.fromEntries(
|
|
202
|
-
operations
|
|
203
|
-
.filter((operation) => operation.requiredHostMethods?.length)
|
|
204
|
-
.map((operation) => [
|
|
205
|
-
operation.id,
|
|
206
|
-
operation.requiredHostMethods!.flatMap((method) =>
|
|
207
|
-
method.modes?.length
|
|
208
|
-
? method.modes.map((mode) => `${method.id}:${mode}`)
|
|
209
|
-
: [method.id]
|
|
210
|
-
),
|
|
211
|
-
]),
|
|
212
|
-
),
|
|
213
|
-
},
|
|
214
|
-
hostCapabilities: {
|
|
215
|
-
optional: dedupeHostCapabilities(
|
|
216
|
-
operations.flatMap((operation) => operation.requiredHostCapabilities ?? []),
|
|
217
|
-
),
|
|
218
|
-
requiredByOperations: Object.fromEntries(
|
|
219
|
-
operations
|
|
220
|
-
.filter((operation) => operation.requiredHostCapabilities?.length)
|
|
221
|
-
.map((operation) => [operation.id, operation.requiredHostCapabilities!.map((capability) => capability.id)]),
|
|
222
|
-
),
|
|
223
|
-
},
|
|
224
191
|
operations,
|
|
192
|
+
workspaceOperations,
|
|
225
193
|
};
|
|
226
194
|
}
|
|
227
195
|
|
|
228
|
-
function dedupeHostCapabilities(
|
|
229
|
-
capabilities: Array<{ id: string; schemaHash?: string }>,
|
|
230
|
-
): Array<{ id: string; schemaHash?: string }> {
|
|
231
|
-
const seen = new Set<string>();
|
|
232
|
-
const deduped: Array<{ id: string; schemaHash?: string }> = [];
|
|
233
|
-
for (const capability of capabilities) {
|
|
234
|
-
const key = capability.schemaHash ? `${capability.id}\0${capability.schemaHash}` : capability.id;
|
|
235
|
-
if (seen.has(key)) continue;
|
|
236
|
-
seen.add(key);
|
|
237
|
-
deduped.push({
|
|
238
|
-
id: capability.id,
|
|
239
|
-
...(capability.schemaHash ? { schemaHash: capability.schemaHash } : {}),
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
return deduped;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
196
|
function workflowJsonSchema(workflows: string[]): JsonSchema {
|
|
246
197
|
return workflows.length > 0
|
|
247
198
|
? { type: "string", enum: workflows }
|
package/src/runtime/protocol.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Schema } from "effect";
|
|
|
2
2
|
import type { WorkflowEvent } from "@rigkit/engine";
|
|
3
3
|
|
|
4
4
|
export const RUNTIME_API_VERSION = 1;
|
|
5
|
-
export const RUNTIME_PROTOCOL_HASH = "sha256:
|
|
5
|
+
export const RUNTIME_PROTOCOL_HASH = "sha256:ac8d4a503b56c15b333ea51f57ab1f6fca776bea93f498120b10ab601cc0960a";
|
|
6
6
|
export const DEFAULT_IDLE_MS = 30 * 60 * 1000;
|
|
7
7
|
|
|
8
8
|
export class RuntimeProtocolSchemaError extends Error {
|
|
@@ -147,16 +147,6 @@ export type RuntimeOperationCli = {
|
|
|
147
147
|
options?: RuntimeOperationCliOption[];
|
|
148
148
|
};
|
|
149
149
|
|
|
150
|
-
export type RuntimeHostMethodRequirement = {
|
|
151
|
-
id: string;
|
|
152
|
-
modes?: string[];
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
export type RuntimeHostCapabilityRequirement = {
|
|
156
|
-
id: string;
|
|
157
|
-
schemaHash?: string;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
150
|
export type RuntimeOperation = {
|
|
161
151
|
id: string;
|
|
162
152
|
aliases?: string[];
|
|
@@ -165,22 +155,13 @@ export type RuntimeOperation = {
|
|
|
165
155
|
title: string;
|
|
166
156
|
description: string;
|
|
167
157
|
createsWorkspace?: boolean;
|
|
168
|
-
requiredHostMethods?: RuntimeHostMethodRequirement[];
|
|
169
|
-
requiredHostCapabilities?: RuntimeHostCapabilityRequirement[];
|
|
170
158
|
cli?: RuntimeOperationCli;
|
|
171
159
|
inputSchema: JsonSchema;
|
|
172
160
|
};
|
|
173
161
|
|
|
174
162
|
export type RuntimeOperationsManifest = {
|
|
175
|
-
hostMethods: {
|
|
176
|
-
known: RuntimeHostMethodRequirement[];
|
|
177
|
-
requiredByOperations: Record<string, string[]>;
|
|
178
|
-
};
|
|
179
|
-
hostCapabilities: {
|
|
180
|
-
optional: RuntimeHostCapabilityRequirement[];
|
|
181
|
-
requiredByOperations: Record<string, string[]>;
|
|
182
|
-
};
|
|
183
163
|
operations: RuntimeOperation[];
|
|
164
|
+
workspaceOperations: RuntimeOperation[];
|
|
184
165
|
};
|
|
185
166
|
|
|
186
167
|
export function objectSchema(properties: Record<string, unknown>, required: string[] = []): JsonSchema {
|
package/src/runtime/server.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { RIGKIT_RUNTIME_VERSION } from "./version.ts";
|
|
|
13
13
|
import { runtimeJsonError, sessionRunIdFor } from "./app.ts";
|
|
14
14
|
import { createRuntimeControlApiHandler } from "./api-handlers.ts";
|
|
15
15
|
import type { RuntimeAppState } from "./control.ts";
|
|
16
|
+
import { loadEngine } from "./operations.ts";
|
|
16
17
|
import { runSessionSocketEffect } from "./sessions.ts";
|
|
17
18
|
import { DEFAULT_IDLE_MS } from "./protocol.ts";
|
|
18
19
|
import { createRunStore } from "./runs.ts";
|
|
@@ -60,6 +61,7 @@ export async function serveRuntime(options: ServeRuntimeOptions): Promise<Runtim
|
|
|
60
61
|
options.handlePath,
|
|
61
62
|
`${JSON.stringify({
|
|
62
63
|
projectId: options.projectId,
|
|
64
|
+
runtimeFingerprint: options.runtimeFingerprint,
|
|
63
65
|
projectDir,
|
|
64
66
|
configPath,
|
|
65
67
|
statePath,
|
|
@@ -76,6 +78,7 @@ export async function serveRuntime(options: ServeRuntimeOptions): Promise<Runtim
|
|
|
76
78
|
|
|
77
79
|
const context: RuntimeContext = {
|
|
78
80
|
projectId: options.projectId,
|
|
81
|
+
runtimeFingerprint: options.runtimeFingerprint,
|
|
79
82
|
projectDir,
|
|
80
83
|
configPath,
|
|
81
84
|
statePath,
|
|
@@ -90,6 +93,8 @@ export async function serveRuntime(options: ServeRuntimeOptions): Promise<Runtim
|
|
|
90
93
|
stop: () => stopServer(),
|
|
91
94
|
};
|
|
92
95
|
const state: RuntimeAppState = { context, store };
|
|
96
|
+
await loadEngine(context);
|
|
97
|
+
|
|
93
98
|
const controlApi = createRuntimeControlApiHandler(context, store);
|
|
94
99
|
const app = createRuntimeHttpApp(state, controlApi);
|
|
95
100
|
const scope = Effect.runSync(Scope.make());
|
package/src/runtime/sessions.ts
CHANGED
|
@@ -81,19 +81,6 @@ function openRunSession(
|
|
|
81
81
|
const message = parseSessionMessage(value);
|
|
82
82
|
if (!message) return;
|
|
83
83
|
if (message.type === "hello") {
|
|
84
|
-
const unsupported = unsupportedSessionRequirements(message, run.operationDefinition);
|
|
85
|
-
if (unsupported.length > 0) {
|
|
86
|
-
failRun(
|
|
87
|
-
run,
|
|
88
|
-
new RuntimeHostRequestError({
|
|
89
|
-
message: `Host does not support required ${unsupported.join(", ")}`,
|
|
90
|
-
}),
|
|
91
|
-
state.store,
|
|
92
|
-
);
|
|
93
|
-
acknowledged = true;
|
|
94
|
-
sendBacklog();
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
84
|
acknowledged = true;
|
|
98
85
|
sendHelloAck(socket, run);
|
|
99
86
|
sendBacklog();
|
|
@@ -160,53 +147,10 @@ function sendHelloAck(ws: RuntimeSessionTransport, run: { operation: string; ope
|
|
|
160
147
|
},
|
|
161
148
|
operation: {
|
|
162
149
|
id: run.operation,
|
|
163
|
-
requiredHostCapabilities: run.operationDefinition?.requiredHostCapabilities ?? [],
|
|
164
|
-
requiredHostMethods: run.operationDefinition?.requiredHostMethods ?? [],
|
|
165
150
|
},
|
|
166
151
|
}));
|
|
167
152
|
}
|
|
168
153
|
|
|
169
|
-
function unsupportedSessionRequirements(message: SessionMessage, operation: RuntimeOperation | undefined): string[] {
|
|
170
|
-
const unsupported: string[] = [];
|
|
171
|
-
for (const requirement of operation?.requiredHostMethods ?? []) {
|
|
172
|
-
const hostMethod = sessionItems(message.hostMethods).find((item) => item.id === requirement.id);
|
|
173
|
-
if (!hostMethod || !supportsModes(hostMethod.modes, requirement.modes)) {
|
|
174
|
-
unsupported.push(`host method ${formatRequirement(requirement.id, requirement.modes)}`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
for (const requirement of operation?.requiredHostCapabilities ?? []) {
|
|
178
|
-
const capability = sessionItems(message.hostCapabilities).find((item) => item.id === requirement.id);
|
|
179
|
-
if (!capability || (requirement.schemaHash && capability.schemaHash !== requirement.schemaHash)) {
|
|
180
|
-
unsupported.push(requirement.schemaHash
|
|
181
|
-
? `host capability ${requirement.id}@${requirement.schemaHash}`
|
|
182
|
-
: `host capability ${requirement.id}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return unsupported;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function sessionItems(value: unknown): Array<{ id: string; modes?: string[]; schemaHash?: string }> {
|
|
189
|
-
if (!Array.isArray(value)) return [];
|
|
190
|
-
return value.flatMap((item) => {
|
|
191
|
-
if (!isRecord(item) || typeof item.id !== "string") return [];
|
|
192
|
-
return [{
|
|
193
|
-
id: item.id,
|
|
194
|
-
modes: Array.isArray(item.modes) ? item.modes.filter((mode): mode is string => typeof mode === "string") : undefined,
|
|
195
|
-
schemaHash: typeof item.schemaHash === "string" ? item.schemaHash : undefined,
|
|
196
|
-
}];
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function supportsModes(hostModes: string[] | undefined, requiredModes: string[] | undefined): boolean {
|
|
201
|
-
if (!requiredModes?.length) return true;
|
|
202
|
-
const supported = new Set(hostModes ?? []);
|
|
203
|
-
return requiredModes.every((mode) => supported.has(mode));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function formatRequirement(id: string, modes: string[] | undefined): string {
|
|
207
|
-
return modes?.length ? `${id}:${modes.join("|")}` : id;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
154
|
function sessionEvent(event: unknown): Record<string, unknown> {
|
|
211
155
|
if (isRecord(event) && event.type === "host.request") {
|
|
212
156
|
return {
|
package/src/runtime/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { JsonValue } from "@rigkit/engine";
|
|
|
2
2
|
|
|
3
3
|
export type ServeRuntimeOptions = {
|
|
4
4
|
projectId: string;
|
|
5
|
+
runtimeFingerprint?: string;
|
|
5
6
|
projectDir: string;
|
|
6
7
|
configPath: string;
|
|
7
8
|
statePath?: string;
|
|
@@ -23,6 +24,7 @@ export type RuntimeServer = {
|
|
|
23
24
|
|
|
24
25
|
export type RuntimeContext = {
|
|
25
26
|
readonly projectId: string;
|
|
27
|
+
readonly runtimeFingerprint?: string;
|
|
26
28
|
readonly projectDir: string;
|
|
27
29
|
readonly configPath: string;
|
|
28
30
|
readonly statePath?: string;
|
package/src/runtime/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RIGKIT_RUNTIME_VERSION = "0.2.
|
|
1
|
+
export const RIGKIT_RUNTIME_VERSION = "0.2.3";
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RIGKIT_SDK_VERSION = "0.2.
|
|
1
|
+
export const RIGKIT_SDK_VERSION = "0.2.3";
|