@rigkit/engine 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rigkit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@rigkit/engine",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
+ "license": "MIT",
4
5
  "type": "module",
5
6
  "repository": {
6
7
  "type": "git",
@@ -11,7 +11,7 @@ import type {
11
11
  ProviderRuntimeContext,
12
12
  WorkflowProviderController,
13
13
  } from "./provider/types.ts";
14
- import type { DevMachineEvent, ExecResult, JsonValue } from "./types.ts";
14
+ import type { DevMachineEvent, ExecResult, JsonValue, WorkflowProviderCheckResult } from "./types.ts";
15
15
 
16
16
  describe("DevMachineEngine workflow runtime", () => {
17
17
  test("plans, applies graph nodes, reuses graph cache, and forks workspaces", async () => {
@@ -791,6 +791,91 @@ describe("DevMachineEngine workflow runtime", () => {
791
791
  expect(leakedHostRow).toBeNull();
792
792
  });
793
793
 
794
+ test("includes provider checks in workflow plans", async () => {
795
+ const projectDir = mkdtempSync(join(tmpdir(), "rigkit-provider-status-"));
796
+ writeFileSync(
797
+ join(projectDir, "rig.config.ts"),
798
+ `
799
+ import { defineProvider, workflow } from "${import.meta.dir}/index.ts";
800
+
801
+ const app = workflow("provider-status", {
802
+ providers: {
803
+ test: defineProvider("test", {}),
804
+ },
805
+ });
806
+
807
+ export default app.task("noop", async () => {});
808
+ `,
809
+ );
810
+
811
+ const engine = await createDevMachineEngine({
812
+ projectDir,
813
+ providerFactory: () => new FakeWorkflowProvider({
814
+ check: {
815
+ id: "account",
816
+ label: "Test account",
817
+ status: "ok",
818
+ value: "acct-1",
819
+ fingerprint: "acct-1",
820
+ metadata: { accountId: "acct-1" },
821
+ },
822
+ }),
823
+ });
824
+ await engine.load();
825
+
826
+ expect((await engine.plan()).providerChecks).toEqual([{
827
+ providerId: "test",
828
+ providerName: "test",
829
+ id: "account",
830
+ label: "Test account",
831
+ status: "ok",
832
+ value: "acct-1",
833
+ fingerprint: "acct-1",
834
+ metadata: { accountId: "acct-1" },
835
+ }]);
836
+ });
837
+
838
+ test("requires provider checks before applying workflow tasks", async () => {
839
+ const projectDir = mkdtempSync(join(tmpdir(), "rigkit-provider-check-required-"));
840
+ writeFileSync(
841
+ join(projectDir, "rig.config.ts"),
842
+ `
843
+ import { defineProvider, workflow } from "${import.meta.dir}/index.ts";
844
+
845
+ const app = workflow("provider-check-required", {
846
+ providers: {
847
+ test: defineProvider("test", {}),
848
+ },
849
+ });
850
+
851
+ export default app.task("noop", async () => {});
852
+ `,
853
+ );
854
+
855
+ const engine = await createDevMachineEngine({
856
+ projectDir,
857
+ providerFactory: () => new FakeWorkflowProvider({
858
+ check: {
859
+ id: "auth",
860
+ label: "Test auth",
861
+ status: "required",
862
+ value: "login required",
863
+ message: "Run the provider auth flow.",
864
+ fingerprint: "missing",
865
+ },
866
+ }),
867
+ });
868
+ await engine.load();
869
+
870
+ expect((await engine.plan()).providerChecks?.[0]).toMatchObject({
871
+ label: "Test auth",
872
+ status: "required",
873
+ });
874
+ await expect(engine.apply()).rejects.toThrow(
875
+ "Provider check required: Test auth. Run the provider auth flow.",
876
+ );
877
+ });
878
+
794
879
  test("rejects task outputs that are not JSON serializable", async () => {
795
880
  const projectDir = mkdtempSync(join(tmpdir(), "rigkit-"));
796
881
  writeFileSync(
@@ -1146,9 +1231,14 @@ class FakeWorkflowProvider implements WorkflowProviderController<FakeRuntime> {
1146
1231
  constructor(
1147
1232
  private readonly options: {
1148
1233
  terminalCompleted?: Promise<{ finished: true }>;
1234
+ check?: WorkflowProviderCheckResult | WorkflowProviderCheckResult[];
1149
1235
  } = {},
1150
1236
  ) {}
1151
1237
 
1238
+ checks(): WorkflowProviderCheckResult | WorkflowProviderCheckResult[] | undefined {
1239
+ return this.options.check;
1240
+ }
1241
+
1152
1242
  runtime(context: ProviderRuntimeContext): FakeRuntime {
1153
1243
  return {
1154
1244
  createVm: async () => this.createVm(context),
package/src/engine.ts CHANGED
@@ -46,6 +46,8 @@ import type {
46
46
  WorkflowPlan,
47
47
  WorkflowPlanNode,
48
48
  WorkflowProviderMap,
49
+ WorkflowProviderCheck,
50
+ WorkflowProviderCheckResult,
49
51
  WorkflowStepInvalidation,
50
52
  WorkflowTaskCacheTTL,
51
53
  WorkflowTaskNode,
@@ -68,7 +70,7 @@ export type CreateDevMachineEngineOptions = {
68
70
  interaction?: {
69
71
  present?: InteractionPresenter;
70
72
  };
71
- local?: Partial<LocalWorkspaceRuntime>;
73
+ local?: LocalWorkspaceRuntimeOptions;
72
74
  };
73
75
 
74
76
  export type { InteractionPresenter, InteractionPresentationRequest };
@@ -101,6 +103,12 @@ export type EngineProjectInfo = {
101
103
  workflow?: WorkflowSummary;
102
104
  };
103
105
 
106
+ export type LocalWorkspaceRuntimeOptions =
107
+ & Partial<Omit<LocalWorkspaceRuntime, "prompt">>
108
+ & {
109
+ prompt?: Partial<NonNullable<LocalWorkspaceRuntime["prompt"]>>;
110
+ };
111
+
104
112
  export type EngineCacheScope = WorkflowCacheScope;
105
113
 
106
114
  export type EngineCacheEntry = {
@@ -313,6 +321,12 @@ export class DevMachineEngine {
313
321
  this.interactionPresenter = options.interaction?.present ?? defaultInteractionPresenter;
314
322
  this.local = {
315
323
  open: options.local?.open ?? openLocalTarget,
324
+ prompt: {
325
+ message: options.local?.prompt?.message ?? showLocalMessage,
326
+ text: options.local?.prompt?.text ?? promptLocalText,
327
+ confirm: options.local?.prompt?.confirm ?? confirmLocalPrompt,
328
+ select: options.local?.prompt?.select ?? selectLocalOption,
329
+ },
316
330
  command: options.local?.command ?? runLocalCommand,
317
331
  requestCapability: options.local?.requestCapability ?? requestUnsupportedHostCapability,
318
332
  requestCapabilitySession: options.local?.requestCapabilitySession ?? requestUnsupportedHostCapabilitySession,
@@ -329,7 +343,7 @@ export class DevMachineEngine {
329
343
 
330
344
  if (!existsSync(this.configPath)) {
331
345
  throw new Error(
332
- `No Rigkit config found at ${this.configPath}. Create one with "rig init" or pass -config=<file>.`,
346
+ `No Rigkit config found at ${this.configPath}. Create one with "rig init" or pass --config=<file>.`,
333
347
  );
334
348
  }
335
349
 
@@ -794,6 +808,7 @@ export class DevMachineEngine {
794
808
  async runOperation(input: { operation: string; workflow?: string; input?: unknown }): Promise<unknown> {
795
809
  const { workflow, operation } = this.getWorkflowOperation(input.operation, input.workflow);
796
810
  const providers = await this.createProviders(workflow);
811
+ await this.requireProviderChecks(workflow, providers);
797
812
  const metadata: JsonObject = {};
798
813
  const runtime = await this.createTaskRuntime({
799
814
  workflow,
@@ -928,6 +943,7 @@ export class DevMachineEngine {
928
943
  const applied = await this.apply({ workflow: input.workflow ?? input.machine });
929
944
  const workflow = this.getWorkflow(input.workflow ?? input.machine);
930
945
  const providers = await this.createProviders(workflow);
946
+ await this.requireProviderChecks(workflow, providers);
931
947
  if (!workflow.workspace) {
932
948
  throw new Error(`Workflow ${workflow.name} does not define a workspace`);
933
949
  }
@@ -986,6 +1002,7 @@ export class DevMachineEngine {
986
1002
  throw new Error(`Workflow ${workflow.name} does not define a workspace`);
987
1003
  }
988
1004
  const providers = await this.createProviders(workflow);
1005
+ await this.requireProviderChecks(workflow, providers);
989
1006
  const metadata: JsonObject = {};
990
1007
  const runtime = await this.createTaskRuntime({
991
1008
  workflow,
@@ -1021,7 +1038,10 @@ export class DevMachineEngine {
1021
1038
  providers: ProviderControllers;
1022
1039
  mode: EvaluationMode;
1023
1040
  }): Promise<{ context: Record<string, JsonValue>; plan: WorkflowPlan; fragments: Set<string> }> {
1024
- const providerFingerprint = providerFingerprintFor(input.workflow);
1041
+ const providerChecks = await this.collectProviderChecks(input.workflow, input.providers, {
1042
+ mode: input.mode === "plan" ? "plan" : "require",
1043
+ });
1044
+ const providerFingerprint = providerFingerprintFor(input.workflow, providerChecks);
1025
1045
  const planNodes: WorkflowPlanNode[] = [];
1026
1046
  const previousEvaluationFragmentHashes = this.evaluationFragmentHashes;
1027
1047
  const fragments = new Set<string>();
@@ -1060,6 +1080,7 @@ export class DevMachineEngine {
1060
1080
  const plan: WorkflowPlan = {
1061
1081
  workflow: input.workflow.name,
1062
1082
  providerFingerprint,
1083
+ ...(providerChecks.length > 0 ? { providerChecks } : {}),
1063
1084
  cachedNodeCount,
1064
1085
  nodeCount: planNodes.length,
1065
1086
  nodes: planNodes,
@@ -1547,6 +1568,7 @@ export class DevMachineEngine {
1547
1568
  operation: WorkflowWorkspaceOperationDefinition<any, any, any, any>;
1548
1569
  rawInput: unknown;
1549
1570
  }): Promise<unknown> {
1571
+ await this.requireProviderChecks(input.workflow, input.providers);
1550
1572
  const metadata: JsonObject = {};
1551
1573
  const providers = await this.createTaskRuntime({
1552
1574
  workflow: input.workflow,
@@ -1829,6 +1851,48 @@ export class DevMachineEngine {
1829
1851
  return Object.fromEntries(entries);
1830
1852
  }
1831
1853
 
1854
+ private async collectProviderChecks(
1855
+ workflow: LoadedWorkflow,
1856
+ providers: ProviderControllers,
1857
+ input: { mode: "plan" | "require" },
1858
+ ): Promise<WorkflowProviderCheck[]> {
1859
+ const checks = await Promise.all(
1860
+ Object.entries(providers).map(async ([providerName, controller]) => {
1861
+ const result = await controller.checks?.({
1862
+ mode: input.mode,
1863
+ workflow: workflow.name,
1864
+ local: this.local,
1865
+ });
1866
+ return normalizeProviderChecks(result).map((check) => ({
1867
+ providerId: check.providerId ?? controller.providerId,
1868
+ providerName: check.providerName ?? providerName,
1869
+ id: check.id,
1870
+ label: check.label,
1871
+ status: check.status,
1872
+ value: check.value,
1873
+ ...(check.message ? { message: check.message } : {}),
1874
+ ...(check.detail ? { detail: check.detail } : {}),
1875
+ ...(check.fingerprint ? { fingerprint: check.fingerprint } : {}),
1876
+ ...(check.metadata ? { metadata: check.metadata } : {}),
1877
+ }));
1878
+ }),
1879
+ );
1880
+ const flatChecks = checks.flat();
1881
+ if (input.mode === "require") {
1882
+ const required = flatChecks.find((check) => check.status !== "ok");
1883
+ if (required) {
1884
+ throw new Error(
1885
+ `Provider check required: ${required.label}${required.message ? `. ${required.message}` : ""}`,
1886
+ );
1887
+ }
1888
+ }
1889
+ return flatChecks;
1890
+ }
1891
+
1892
+ private async requireProviderChecks(workflow: LoadedWorkflow, providers: ProviderControllers): Promise<void> {
1893
+ await this.collectProviderChecks(workflow, providers, { mode: "require" });
1894
+ }
1895
+
1832
1896
  private async createProviderFromPlugin(input: Parameters<ProviderFactory>[0]): Promise<WorkflowProviderController> {
1833
1897
  const plugin = this.providers.find((provider) => provider.providerId === input.provider.providerId);
1834
1898
  if (!plugin) {
@@ -2034,9 +2098,9 @@ function collectNodePaths(root: WorkflowNodeDefinition<any, any, any>): string[]
2034
2098
  }
2035
2099
  }
2036
2100
 
2037
- function providerFingerprintFor(workflow: LoadedWorkflow): string {
2101
+ function providerFingerprintFor(workflow: LoadedWorkflow, providerChecks: WorkflowProviderCheck[]): string {
2038
2102
  return hash({
2039
- cache: "provider-v2",
2103
+ cache: "provider-v3",
2040
2104
  providers: Object.fromEntries(
2041
2105
  Object.entries(workflow.providers).map(([name, provider]) => [
2042
2106
  name,
@@ -2047,6 +2111,13 @@ function providerFingerprintFor(workflow: LoadedWorkflow): string {
2047
2111
  },
2048
2112
  ]),
2049
2113
  ),
2114
+ checks: providerChecks.map((check) => ({
2115
+ providerName: check.providerName,
2116
+ providerId: check.providerId,
2117
+ id: check.id,
2118
+ status: check.status,
2119
+ fingerprint: check.fingerprint ?? check.value,
2120
+ })),
2050
2121
  });
2051
2122
  }
2052
2123
 
@@ -2484,6 +2555,13 @@ function optionalBooleanInput(
2484
2555
  return value;
2485
2556
  }
2486
2557
 
2558
+ function normalizeProviderChecks(
2559
+ result: WorkflowProviderCheckResult | WorkflowProviderCheckResult[] | undefined,
2560
+ ): WorkflowProviderCheckResult[] {
2561
+ if (result === undefined) return [];
2562
+ return Array.isArray(result) ? result : [result];
2563
+ }
2564
+
2487
2565
  function isPlainObject(value: unknown): value is Record<string, unknown> {
2488
2566
  return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype);
2489
2567
  }
@@ -2507,6 +2585,30 @@ async function openLocalTarget(target: string): Promise<void> {
2507
2585
  }
2508
2586
  }
2509
2587
 
2588
+ async function showLocalMessage(input: { message: string; level?: "info" | "warn" | "error" }): Promise<void> {
2589
+ const writer = input.level === "error" || input.level === "warn" ? console.error : console.log;
2590
+ writer(input.message);
2591
+ }
2592
+
2593
+ async function promptLocalText(input: { message: string; defaultValue?: string }): Promise<string> {
2594
+ if (input.defaultValue !== undefined) return input.defaultValue;
2595
+ throw new Error(`Host text prompt requires an interactive runtime host: ${input.message}`);
2596
+ }
2597
+
2598
+ async function confirmLocalPrompt(input: { message: string; defaultValue?: boolean }): Promise<boolean> {
2599
+ if (input.defaultValue !== undefined) return input.defaultValue;
2600
+ throw new Error(`Host confirm prompt requires an interactive runtime host: ${input.message}`);
2601
+ }
2602
+
2603
+ async function selectLocalOption(input: {
2604
+ message: string;
2605
+ options: Array<{ value: string; label?: string; description?: string }>;
2606
+ defaultValue?: string;
2607
+ }): Promise<string> {
2608
+ if (input.defaultValue) return input.defaultValue;
2609
+ throw new Error(`Host select prompt requires an interactive runtime host: ${input.message}`);
2610
+ }
2611
+
2510
2612
  async function requestUnsupportedHostCapability<Result = unknown>(capability: string): Promise<Result> {
2511
2613
  throw new Error(
2512
2614
  `Host capability ${capability} is unavailable outside a runtime host. ` +
@@ -7,6 +7,7 @@ import type {
7
7
  LoadedProviderDefinition,
8
8
  LocalWorkspaceRuntime,
9
9
  MaybePromise,
10
+ WorkflowProviderCheckResult,
10
11
  } from "../types.ts";
11
12
 
12
13
  export type VmHandle = {
@@ -73,9 +74,16 @@ export type ProviderRuntimeContext = {
73
74
  metadata(metadata: JsonObject): void;
74
75
  };
75
76
 
77
+ export type ProviderCheckContext = {
78
+ mode: "plan" | "require";
79
+ workflow: string;
80
+ local: LocalWorkspaceRuntime;
81
+ };
82
+
76
83
  export interface WorkflowProviderController<Runtime = unknown> {
77
84
  readonly providerId: string;
78
85
  runtime(context: ProviderRuntimeContext): MaybePromise<Runtime>;
86
+ checks?(context: ProviderCheckContext): MaybePromise<WorkflowProviderCheckResult | WorkflowProviderCheckResult[] | undefined>;
79
87
  validateArtifact?(ref: JsonValue): MaybePromise<boolean>;
80
88
  }
81
89
 
package/src/types.ts CHANGED
@@ -54,6 +54,7 @@ export type CommandOptions = ExecOptions & {
54
54
 
55
55
  export type LocalWorkspaceRuntime = {
56
56
  open(target: string): MaybePromise<void>;
57
+ prompt?: LocalPromptRuntime;
57
58
  command?(input: LocalCommandRequest): MaybePromise<LocalCommandResult>;
58
59
  requestCapability?<Result = unknown>(
59
60
  capability: string,
@@ -67,6 +68,38 @@ export type LocalWorkspaceRuntime = {
67
68
  ): MaybePromise<HostCapabilitySession<Result>>;
68
69
  };
69
70
 
71
+ export type LocalPromptRuntime = {
72
+ message(input: LocalMessageRequest): MaybePromise<void>;
73
+ text(input: LocalTextRequest): MaybePromise<string>;
74
+ confirm(input: LocalConfirmRequest): MaybePromise<boolean>;
75
+ select(input: LocalSelectRequest): MaybePromise<string>;
76
+ };
77
+
78
+ export type LocalMessageRequest = {
79
+ message: string;
80
+ level?: "info" | "warn" | "error";
81
+ };
82
+
83
+ export type LocalTextRequest = {
84
+ message: string;
85
+ defaultValue?: string;
86
+ };
87
+
88
+ export type LocalConfirmRequest = {
89
+ message: string;
90
+ defaultValue?: boolean;
91
+ };
92
+
93
+ export type LocalSelectRequest = {
94
+ message: string;
95
+ options: Array<{
96
+ value: string;
97
+ label?: string;
98
+ description?: string;
99
+ }>;
100
+ defaultValue?: string;
101
+ };
102
+
70
103
  export type LocalHostCapabilityRequestOptions = {
71
104
  nodePath?: string;
72
105
  };
@@ -810,9 +843,29 @@ export type WorkflowPlanNode = {
810
843
  upstreamRunIds: string[];
811
844
  };
812
845
 
846
+ export type WorkflowProviderCheckStatus = "ok" | "required";
847
+
848
+ export type WorkflowProviderCheck = {
849
+ providerId: string;
850
+ providerName: string;
851
+ id: string;
852
+ label: string;
853
+ status: WorkflowProviderCheckStatus;
854
+ value: string;
855
+ message?: string;
856
+ detail?: string;
857
+ fingerprint?: string;
858
+ metadata?: JsonObject;
859
+ };
860
+
861
+ export type WorkflowProviderCheckResult =
862
+ & Omit<WorkflowProviderCheck, "providerId" | "providerName">
863
+ & Partial<Pick<WorkflowProviderCheck, "providerId" | "providerName">>;
864
+
813
865
  export type WorkflowPlan = {
814
866
  workflow: string;
815
867
  providerFingerprint: string;
868
+ providerChecks?: WorkflowProviderCheck[];
816
869
  cachedNodeCount: number;
817
870
  nodeCount: number;
818
871
  nodes: WorkflowPlanNode[];
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const RIGKIT_ENGINE_VERSION = "0.2.10";
1
+ export const RIGKIT_ENGINE_VERSION = "0.2.11";