@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigkit/sdk",
3
- "version": "0.2.1",
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.1",
27
- "@rigkit/runtime-client": "0.2.1"
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.1");
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,
@@ -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,
@@ -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("data");
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: "fork",
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
- createsWorkspace: true,
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 forkOperation = manifest.operations.find((item) => item.id === "fork");
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(operation?.requiredHostMethods).toEqual([
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(forkOperation?.source).toBe("config");
343
- expect(forkOperation?.createsWorkspace).toBe(true);
344
- expect(manifest.operations.some((item) => item.id === "create")).toBe(false);
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 data", async () => {
439
- const projectDir = mkdtempSync(join(tmpdir(), "rigkit-runtime-workspace-data-"));
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-data")
475
+ const root = sequence("workspace-ctx")
447
476
  .step("prepare", async () => ({ repoPath: "/workspace/repo" }))
448
- .create(async ({ ctx, name }) => ({
449
- name,
450
- resourceId: "resource-" + name,
451
- repoPath: ctx.repoPath,
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-data-test",
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<{ data: Record<string, unknown>; metadata: Record<string, unknown> }> }>);
518
+ }).then((response) => response.json() as Promise<{ workspaces: Array<{ ctx: Record<string, unknown> }> }>);
487
519
 
488
- expect(workspaces[0]?.data).toEqual({
520
+ expect(workspaces[0]?.ctx).toEqual({
489
521
  name: "demo",
490
- resourceId: "resource-demo",
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
- .create(async ({ name }) => ({ name, resourceId: "resource-" + name }));
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");
@@ -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();
@@ -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 operation = findRuntimeOperation(manifest.operations, body.operation);
90
- if (!operation) throw new RuntimeControlHttpError(400, `Unknown operation ${body.operation}`);
91
+ const resolved = resolveRuntimeOperation(manifest, body.operation);
92
+ if (!resolved) throw new RuntimeControlHttpError(400, `Unknown operation ${body.operation}`);
91
93
 
92
- const run = createRun(operation.id, body.input ?? {}, operation);
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 findRuntimeOperation(operations: RuntimeOperation[], requestedOperation: string): RuntimeOperation | undefined {
147
- return operations.find((operation) =>
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): WorkspaceRecord & { data: WorkspaceRecord["metadata"] } {
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
- ...workspace,
155
- data: workspace.metadata,
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
 
@@ -35,8 +35,6 @@ export {
35
35
  type RuntimeStateServiceOptions,
36
36
  } from "./state.ts";
37
37
  export {
38
- HostCapabilityRequirementEffectSchema,
39
- HostMethodRequirementEffectSchema,
40
38
  OkResponseEffectSchema,
41
39
  OperationsManifestEffectSchema,
42
40
  ProjectInfoEffectSchema,
@@ -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 }
@@ -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:6dc28b06c95c98d310db445f1b19aef478cd828f11287467fe261483dfde4f81";
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 {
@@ -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());
@@ -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 {
@@ -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;
@@ -1 +1 @@
1
- export const RIGKIT_RUNTIME_VERSION = "0.2.1";
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";
1
+ export const RIGKIT_SDK_VERSION = "0.2.3";