@stigmer/runner 3.29.0 → 3.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/.build-fingerprint +1 -1
- package/dist/activities/call-eval.d.ts +16 -0
- package/dist/activities/call-eval.js +54 -1
- package/dist/activities/call-eval.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +1 -0
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -1
- package/dist/activities/hydrate-workflow-execution.d.ts +1 -1
- package/dist/activities/workflow-event-activities.d.ts +2 -2
- package/dist/activities/workflow-event-activities.js +2 -2
- package/dist/config.d.ts +8 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/harness/transcript/builder.d.ts +3 -2
- package/dist/harness/transcript/builder.js +28 -8
- package/dist/harness/transcript/builder.js.map +1 -1
- package/dist/runner-manager.d.ts +9 -0
- package/dist/runner-manager.js +3 -0
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.d.ts +9 -0
- package/dist/runner.js +3 -1
- package/dist/runner.js.map +1 -1
- package/dist/shared/approval-policy.d.ts +5 -0
- package/dist/shared/approval-policy.js +8 -0
- package/dist/shared/approval-policy.js.map +1 -1
- package/dist/shared/args-preview.d.ts +12 -5
- package/dist/shared/args-preview.js +12 -5
- package/dist/shared/args-preview.js.map +1 -1
- package/dist/shared/runner-credential-keys.d.ts +0 -29
- package/dist/shared/runner-credential-keys.js +8 -0
- package/dist/shared/runner-credential-keys.js.map +1 -1
- package/dist/worker.d.ts +2 -4
- package/dist/worker.js +3 -4
- package/dist/worker.js.map +1 -1
- package/dist/workflow-engine/tasks/run.d.ts +15 -1
- package/dist/workflow-engine/tasks/run.js +18 -1
- package/dist/workflow-engine/tasks/run.js.map +1 -1
- package/dist/workflows/call-agent-orchestrator.d.ts +1 -1
- package/dist/workflows/call-agent-orchestrator.js +1 -1
- package/dist/workflows/engine-core.d.ts +28 -8
- package/dist/workflows/engine-core.js +9 -7
- package/dist/workflows/engine-core.js.map +1 -1
- package/dist/workflows/index.d.ts +22 -7
- package/dist/workflows/index.js +22 -7
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/workflow-signals.d.ts +3 -3
- package/dist/workflows/workflow-signals.js +3 -3
- package/package.json +5 -5
- package/src/__test-utils__/config-fixture.ts +1 -0
- package/src/__test-utils__/workflows/index.ts +15 -0
- package/src/__test-utils__/workflows/inline-model.ts +34 -0
- package/src/__tests__/dispatch-surface-e2e.test.ts +212 -0
- package/src/__tests__/golden-e2e.test.ts +4 -3
- package/src/__tests__/history-encryption-e2e.test.ts +1 -1
- package/src/__tests__/runner.test.ts +20 -0
- package/src/activities/__tests__/call-eval.test.ts +115 -0
- package/src/activities/__tests__/classify-tool-approvals.test.ts +1 -0
- package/src/activities/__tests__/discover-mcp-server.test.ts +1 -0
- package/src/activities/call-eval.ts +59 -1
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +1 -0
- package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +1 -0
- package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +1 -0
- package/src/activities/hydrate-workflow-execution.ts +1 -1
- package/src/activities/workflow-event-activities.ts +2 -2
- package/src/config.ts +10 -0
- package/src/harness/transcript/__tests__/builder.test.ts +56 -1
- package/src/harness/transcript/builder.ts +29 -8
- package/src/runner-manager.ts +14 -0
- package/src/runner.ts +14 -0
- package/src/shared/__tests__/approval-policy.test.ts +10 -0
- package/src/shared/__tests__/args-preview.test.ts +50 -1
- package/src/shared/__tests__/artifact-storage.test.ts +1 -0
- package/src/shared/__tests__/runner-credential-store.test.ts +20 -0
- package/src/shared/approval-policy.ts +7 -0
- package/src/shared/args-preview.ts +12 -5
- package/src/shared/runner-credential-keys.ts +12 -0
- package/src/worker.ts +3 -4
- package/src/workflow-engine/__tests__/tasks/run.test.ts +25 -0
- package/src/workflow-engine/tasks/run.ts +21 -1
- package/src/workflows/__tests__/barrel.test.ts +52 -0
- package/src/workflows/__tests__/{execute-serverless-workflow.test.ts → engine-inline-model.test.ts} +16 -4
- package/src/workflows/call-agent-orchestrator.ts +1 -1
- package/src/workflows/engine-core.ts +34 -9
- package/src/workflows/index.ts +22 -10
- package/src/workflows/workflow-signals.ts +3 -3
- package/dist/workflows/execute-serverless-workflow.d.ts +0 -32
- package/dist/workflows/execute-serverless-workflow.js +0 -36
- package/dist/workflows/execute-serverless-workflow.js.map +0 -1
- package/src/workflows/execute-serverless-workflow.ts +0 -60
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { loadTemporalConnectionConfig } from "@stigmer/temporal-codecs";
|
|
2
3
|
import {
|
|
3
4
|
captureRunnerSecrets,
|
|
4
5
|
getRunnerSecret,
|
|
@@ -87,6 +88,25 @@ describe("captureRunnerSecrets", () => {
|
|
|
87
88
|
expect(RUNNER_SECRET_ENV_KEYS, `'${key}' must be in the scrub list`).toContain(key);
|
|
88
89
|
}
|
|
89
90
|
});
|
|
91
|
+
|
|
92
|
+
it("takes the Temporal API key and client key into custody, and the connection settings still read them", () => {
|
|
93
|
+
// Pinned by name, like the encryption keys: a runner that left either
|
|
94
|
+
// in process.env would hand every agent shell a credential to Temporal.
|
|
95
|
+
// Temporal's own TEMPORAL_API_KEY is the user's and stays untouched.
|
|
96
|
+
for (const key of ["STIGMER_TEMPORAL_API_KEY", "STIGMER_TEMPORAL_TLS_CLIENT_KEY_DATA"]) {
|
|
97
|
+
expect(RUNNER_CREDENTIAL_ENV_KEYS, `'${key}' must be a captured credential`).toContain(key);
|
|
98
|
+
}
|
|
99
|
+
expect(RUNNER_SECRET_ENV_KEYS).not.toContain("TEMPORAL_API_KEY");
|
|
100
|
+
|
|
101
|
+
process.env.STIGMER_TEMPORAL_API_KEY = "temporal-key";
|
|
102
|
+
captureRunnerSecrets();
|
|
103
|
+
|
|
104
|
+
expect(process.env.STIGMER_TEMPORAL_API_KEY).toBeUndefined();
|
|
105
|
+
expect(loadTemporalConnectionConfig(getRunnerSecret)).toEqual({
|
|
106
|
+
tls: {},
|
|
107
|
+
apiKey: "temporal-key",
|
|
108
|
+
});
|
|
109
|
+
});
|
|
90
110
|
});
|
|
91
111
|
|
|
92
112
|
describe("getRunnerSecret", () => {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type { ToolApprovalPolicy } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/spec_pb";
|
|
19
19
|
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
20
20
|
import { ApprovalAction, ApprovalMode, ApprovalPolicySource } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
21
|
+
import { SENSITIVE_ARG_KEYS } from "./args-preview.js";
|
|
21
22
|
import { toolApprovalCategory, type ToolApprovalCategory } from "./tool-kind.js";
|
|
22
23
|
import type { ResolvedMcpServer } from "./mcp-resolver.js";
|
|
23
24
|
|
|
@@ -451,6 +452,11 @@ export function lookupMcpToolPolicy(
|
|
|
451
452
|
* - {{args.field_name}} — replaced with the argument value
|
|
452
453
|
* - {{tool_name}} — replaced with the tool name
|
|
453
454
|
* - Missing fields are replaced with "<unknown>"
|
|
455
|
+
* - A secret-keyed field ({@link SENSITIVE_ARG_KEYS}) is replaced with
|
|
456
|
+
* "[REDACTED]" (stigmer#1119): the message is stored on the row and shown on
|
|
457
|
+
* the approval card, which must carry no more than the redacted preview.
|
|
458
|
+
* Every approval message in the runner, native and Cursor alike, resolves
|
|
459
|
+
* here, so this is the one place the rule applies to it.
|
|
454
460
|
*/
|
|
455
461
|
export function resolveApprovalMessage(
|
|
456
462
|
template: string,
|
|
@@ -460,6 +466,7 @@ export function resolveApprovalMessage(
|
|
|
460
466
|
return template
|
|
461
467
|
.replace(/\{\{tool_name\}\}/g, toolName)
|
|
462
468
|
.replace(/\{\{args\.(\w+)\}\}/g, (_match, field: string) => {
|
|
469
|
+
if (SENSITIVE_ARG_KEYS.has(field.toLowerCase())) return "[REDACTED]";
|
|
463
470
|
const value = args[field];
|
|
464
471
|
if (value === undefined || value === null) return "<unknown>";
|
|
465
472
|
if (typeof value === "string") return value;
|
|
@@ -34,8 +34,13 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
* Argument keys whose values are secrets and must never appear in a preview
|
|
38
|
-
*
|
|
37
|
+
* Argument keys whose values are secrets and must never appear in a preview,
|
|
38
|
+
* on a row's `args`, or in an approval message (stigmer#1119). Matched
|
|
39
|
+
* case-insensitively against the top-level key name. Clients mirror this set
|
|
40
|
+
* to hide the values on rows persisted before the runner redacted `args`
|
|
41
|
+
* (`@stigmer/sdk`'s `isSecretArgKey`); the shared, machine-checked contract is
|
|
42
|
+
* `test/fixtures/tool-view/secret-args.json` — keep this set and that fixture
|
|
43
|
+
* in lockstep.
|
|
39
44
|
*/
|
|
40
45
|
export const SENSITIVE_ARG_KEYS: ReadonlySet<string> = new Set([
|
|
41
46
|
"password", "token", "secret", "api_key", "apikey",
|
|
@@ -60,9 +65,11 @@ export const SALIENT_ARG_FIELDS = ["file_path", "path", "target_notebook", "comm
|
|
|
60
65
|
/**
|
|
61
66
|
* Redact secret-keyed values (see {@link SENSITIVE_ARG_KEYS}), preserving every
|
|
62
67
|
* other entry verbatim. The first step of the preview builder, and the shape
|
|
63
|
-
* stamped as `args` on
|
|
64
|
-
*
|
|
65
|
-
*
|
|
68
|
+
* stamped as `args` on every row: by the transcript builder on every row with
|
|
69
|
+
* args (since stigmer#1119), and on a proposed row the harness recovered from
|
|
70
|
+
* its engine's state (issue #754's header fix). Full enough for the UI's
|
|
71
|
+
* path/primary-arg extraction and for exact-apply, never carrying a secret
|
|
72
|
+
* value.
|
|
66
73
|
*/
|
|
67
74
|
export function redactSensitiveArgs(
|
|
68
75
|
args: Record<string, unknown>,
|
|
@@ -27,6 +27,11 @@
|
|
|
27
27
|
* ambient aws/gcloud auth), and on a local backend the shell can read the
|
|
28
28
|
* credential files off disk regardless (owner ruling on #385).
|
|
29
29
|
*/
|
|
30
|
+
import {
|
|
31
|
+
TEMPORAL_API_KEY_ENV,
|
|
32
|
+
TEMPORAL_TLS_CLIENT_KEY_DATA_ENV,
|
|
33
|
+
} from "@stigmer/temporal-codecs";
|
|
34
|
+
|
|
30
35
|
export const RUNNER_CREDENTIAL_ENV_KEYS: readonly string[] = [
|
|
31
36
|
// Derives HITL approval fingerprints (fingerprint-secret.ts) — an agent
|
|
32
37
|
// that reads this could forge approval receipts.
|
|
@@ -49,6 +54,13 @@ export const RUNNER_CREDENTIAL_ENV_KEYS: readonly string[] = [
|
|
|
49
54
|
// Bedrock backend bearer credential, read by the AWS SDK's chain
|
|
50
55
|
// (llm-backend.ts documents it as a supported auth path).
|
|
51
56
|
"AWS_BEARER_TOKEN_BEDROCK",
|
|
57
|
+
// The Temporal frontend's API key and the mutual-TLS client key, read by
|
|
58
|
+
// the Temporal connection settings (@stigmer/temporal-codecs'
|
|
59
|
+
// connection config). Whoever holds either can reach Temporal as this
|
|
60
|
+
// runner. Stigmer's own names, never Temporal's TEMPORAL_API_KEY, which
|
|
61
|
+
// is the user's (that module's header).
|
|
62
|
+
TEMPORAL_API_KEY_ENV,
|
|
63
|
+
TEMPORAL_TLS_CLIENT_KEY_DATA_ENV,
|
|
52
64
|
];
|
|
53
65
|
|
|
54
66
|
/**
|
package/src/worker.ts
CHANGED
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Registers both activities and workflows on a single queue.
|
|
5
5
|
*
|
|
6
|
-
* Workflows:
|
|
7
|
-
*
|
|
8
|
-
* - "stigmer/mcp-server/discover" — Legacy discover-only workflow
|
|
9
|
-
* - "stigmer/workflow/execute" — CNCF Serverless Workflow execution
|
|
6
|
+
* Workflows: exactly the types in `workflows/index.ts`, each started by
|
|
7
|
+
* the server with ids (that file's header is the list and the rule).
|
|
10
8
|
*
|
|
11
9
|
* Architecture:
|
|
12
10
|
* - Java backend starts connect workflows on the runner's task queue
|
|
@@ -41,6 +39,7 @@ export async function startWorker(opts: StartWorkerOptions): Promise<Worker> {
|
|
|
41
39
|
|
|
42
40
|
const connection = await NativeConnection.connect({
|
|
43
41
|
address: config.temporalAddress,
|
|
42
|
+
...config.temporalConnection,
|
|
44
43
|
});
|
|
45
44
|
|
|
46
45
|
const { createWorkflowMetricsSinks } = await import("./interceptors/workflow-metrics-sink.js");
|
|
@@ -79,6 +79,31 @@ describe("RunTaskBuilder", () => {
|
|
|
79
79
|
const builder = new RunTaskBuilder("task", taskDef);
|
|
80
80
|
expect(() => builder.build()).toThrow("workflow.name is required");
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
it.each([
|
|
84
|
+
"stigmer/workflow/execute",
|
|
85
|
+
"stigmer/workflow/execute-from-execution",
|
|
86
|
+
"stigmer/mcp-server/connect",
|
|
87
|
+
"stigmer/mcp-server/discover",
|
|
88
|
+
"stigmer/agent-execution/invoke",
|
|
89
|
+
])("refuses the platform workflow type %s", (name) => {
|
|
90
|
+
const taskDef: RunTaskDef = {
|
|
91
|
+
kind: "run",
|
|
92
|
+
run: { workflow: { name, namespace: "default", version: "1.0.0" } },
|
|
93
|
+
};
|
|
94
|
+
const builder = new RunTaskBuilder("reachIn", taskDef);
|
|
95
|
+
expect(() => builder.build()).toThrow(
|
|
96
|
+
`Run task 'reachIn': '${name}' is a platform workflow type, and a workflow cannot start one`,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("admits a name that only mentions the platform outside the prefix", () => {
|
|
101
|
+
const taskDef: RunTaskDef = {
|
|
102
|
+
kind: "run",
|
|
103
|
+
run: { workflow: { name: "my-stigmer/child", namespace: "default", version: "1.0.0" } },
|
|
104
|
+
};
|
|
105
|
+
expect(() => new RunTaskBuilder("child", taskDef).build()).not.toThrow();
|
|
106
|
+
});
|
|
82
107
|
});
|
|
83
108
|
|
|
84
109
|
describe("script execution", () => {
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* - `run.workflow`: Child Temporal workflow (await or fire-and-forget)
|
|
8
8
|
*
|
|
9
9
|
* The kernel validates configuration and delegates to `ctx.runCommand`
|
|
10
|
-
* (for script/shell) or `ctx.runWorkflow` (for child workflows).
|
|
10
|
+
* (for script/shell) or `ctx.runWorkflow` (for child workflows). A child
|
|
11
|
+
* workflow's name may not be a platform workflow type
|
|
12
|
+
* ({@link PLATFORM_WORKFLOW_TYPE_PREFIX} says why).
|
|
11
13
|
*
|
|
12
14
|
* YAML shapes:
|
|
13
15
|
* - runScript:
|
|
@@ -42,6 +44,19 @@ import type {
|
|
|
42
44
|
|
|
43
45
|
const SUPPORTED_LANGUAGES = ["js", "python"];
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The namespace of the platform's own Temporal workflow types. A child
|
|
49
|
+
* started by `run.workflow` runs on the parent's task queue, which is a
|
|
50
|
+
* runner's queue, so the types a name can reach are exactly the ones a
|
|
51
|
+
* runner registers (`workflows/index.ts`): work the server never
|
|
52
|
+
* dispatched, outside the run it belongs to. A workflow therefore cannot
|
|
53
|
+
* name one. `workflows/__tests__/barrel.test.ts` pins that every registered
|
|
54
|
+
* type carries this prefix, so the refusal covers each. (Server types need
|
|
55
|
+
* not carry it, `schedule/tick` does not: they run on the server's own
|
|
56
|
+
* queues, which a child here never reaches.)
|
|
57
|
+
*/
|
|
58
|
+
export const PLATFORM_WORKFLOW_TYPE_PREFIX = "stigmer/";
|
|
59
|
+
|
|
45
60
|
export class RunTaskBuilder implements TaskBuilder {
|
|
46
61
|
readonly taskName: string;
|
|
47
62
|
readonly taskDef: TaskDef;
|
|
@@ -138,5 +153,10 @@ function validateRunConfig(config: RunConfig, taskName: string): void {
|
|
|
138
153
|
if (!config.workflow.name) {
|
|
139
154
|
throw new Error(`Run task '${taskName}': workflow.name is required`);
|
|
140
155
|
}
|
|
156
|
+
if (config.workflow.name.startsWith(PLATFORM_WORKFLOW_TYPE_PREFIX)) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Run task '${taskName}': '${config.workflow.name}' is a platform workflow type, and a workflow cannot start one`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
141
161
|
}
|
|
142
162
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fence on what a runner registers. Every export of `workflows/index.ts`
|
|
3
|
+
* is a Temporal workflow type on every worker root, and anyone who can
|
|
4
|
+
* reach Temporal can start any of them on a runner's queue. So the set is
|
|
5
|
+
* pinned exactly: the three types the server starts, each by id, none of
|
|
6
|
+
* which takes a model to run. A new export fails here until this list, and
|
|
7
|
+
* the review that comes with changing it, admits it.
|
|
8
|
+
*
|
|
9
|
+
* It also pins the link to the `run.workflow` refusal
|
|
10
|
+
* (`workflow-engine/tasks/run.ts`): every registered type carries
|
|
11
|
+
* `PLATFORM_WORKFLOW_TYPE_PREFIX`, so a workflow can never name one as its
|
|
12
|
+
* child. And it pins the test barrel (`src/__test-utils__/workflows/`) to
|
|
13
|
+
* the production set plus its one inline entry, under the name the tests
|
|
14
|
+
* start it by, since an export alias cannot import that constant.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, expect, it } from "vitest";
|
|
18
|
+
|
|
19
|
+
import * as barrel from "../index.js";
|
|
20
|
+
import * as testBarrel from "../../__test-utils__/workflows/index.js";
|
|
21
|
+
import { INLINE_MODEL_WORKFLOW_TYPE } from "../../__test-utils__/workflows/inline-model.js";
|
|
22
|
+
import { EXECUTE_FROM_EXECUTION_WORKFLOW_TYPE } from "../execute-from-execution.js";
|
|
23
|
+
import { PLATFORM_WORKFLOW_TYPE_PREFIX } from "../../workflow-engine/tasks/run.js";
|
|
24
|
+
|
|
25
|
+
describe("the registered workflow types", () => {
|
|
26
|
+
it("are exactly the types the server starts", () => {
|
|
27
|
+
expect(Object.keys(barrel).sort()).toEqual(
|
|
28
|
+
[
|
|
29
|
+
"stigmer/mcp-server/connect",
|
|
30
|
+
"stigmer/mcp-server/discover",
|
|
31
|
+
EXECUTE_FROM_EXECUTION_WORKFLOW_TYPE,
|
|
32
|
+
].sort(),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("include no type that runs a model handed to it", () => {
|
|
37
|
+
expect(Object.keys(barrel)).not.toContain("stigmer/workflow/execute");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("all carry the platform prefix the run.workflow refusal checks", () => {
|
|
41
|
+
for (const type of Object.keys(barrel)) {
|
|
42
|
+
expect(type.startsWith(PLATFORM_WORKFLOW_TYPE_PREFIX), type).toBe(true);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("are what the test barrel adds its inline entry to, outside the platform prefix", () => {
|
|
47
|
+
expect(Object.keys(testBarrel).sort()).toEqual(
|
|
48
|
+
[...Object.keys(barrel), INLINE_MODEL_WORKFLOW_TYPE].sort(),
|
|
49
|
+
);
|
|
50
|
+
expect(INLINE_MODEL_WORKFLOW_TYPE.startsWith(PLATFORM_WORKFLOW_TYPE_PREFIX)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/src/workflows/__tests__/{execute-serverless-workflow.test.ts → engine-inline-model.test.ts}
RENAMED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workflow engine (`engine-core.ts`) driven with a materialized model,
|
|
3
|
+
* through the test harness's inline entry
|
|
4
|
+
* (`src/__test-utils__/workflows/inline-model.ts`), with the Temporal
|
|
5
|
+
* workflow API mocked. It pins the engine's behaviour: task output,
|
|
6
|
+
* workflow-level `input.from` and `output.as`, expression evaluation
|
|
7
|
+
* through the local activity, error handling, event sequences and flow
|
|
8
|
+
* directives. Production reaches the same engine only through the
|
|
9
|
+
* hydration workflow; `barrel.test.ts` pins that no production type takes
|
|
10
|
+
* a model from its caller.
|
|
11
|
+
*/
|
|
12
|
+
|
|
1
13
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
14
|
import { evaluateExpressionBatch } from "../../workflow-engine/expression.js";
|
|
3
15
|
import type { WorkflowModel } from "../../workflow-engine/types.js";
|
|
4
|
-
import type { ExecuteServerlessWorkflowInput } from "../
|
|
16
|
+
import type { ExecuteServerlessWorkflowInput } from "../engine-core.js";
|
|
5
17
|
|
|
6
18
|
const mockEvaluateExpressions = vi.fn();
|
|
7
19
|
const mockResetEventSequence = vi.fn();
|
|
@@ -51,7 +63,7 @@ vi.mock("@temporalio/workflow", () => ({
|
|
|
51
63
|
isCancellation: vi.fn(() => false),
|
|
52
64
|
}));
|
|
53
65
|
|
|
54
|
-
describe("
|
|
66
|
+
describe("the engine, on a materialized model", () => {
|
|
55
67
|
beforeEach(() => {
|
|
56
68
|
vi.clearAllMocks();
|
|
57
69
|
mockEvaluateExpressions.mockImplementation(
|
|
@@ -60,8 +72,8 @@ describe("executeServerlessWorkflow", () => {
|
|
|
60
72
|
});
|
|
61
73
|
|
|
62
74
|
async function runWorkflow(input: ExecuteServerlessWorkflowInput) {
|
|
63
|
-
const {
|
|
64
|
-
return
|
|
75
|
+
const { executeInlineModel } = await import("../../__test-utils__/workflows/inline-model.js");
|
|
76
|
+
return executeInlineModel(input);
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
describe("basic workflow execution", () => {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The workflow engine's CallAgentTaskBuilder (workflow-engine/tasks/
|
|
12
12
|
* call-agent.ts) calls `ctx.callAgent()`, which is wired to
|
|
13
|
-
* `orchestrateAgentCall()` in
|
|
13
|
+
* `orchestrateAgentCall()` in engine-core.ts.
|
|
14
14
|
*
|
|
15
15
|
* SANDBOX RULES: Only @temporalio/workflow imports, type-only imports,
|
|
16
16
|
* and pure logic. No Node.js built-ins.
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* Shared workflow engine core — runs CNCF Serverless Workflow DSL
|
|
3
3
|
* tasks inside the Temporal deterministic sandbox.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* The engine runs a fully materialized {@link ExecuteServerlessWorkflowInput}
|
|
6
|
+
* and owns that type. Production reaches it only through the hydration
|
|
7
|
+
* workflow ("stigmer/workflow/execute-from-execution"), which reads the
|
|
8
|
+
* model, env and input back from the server by id; no registered workflow
|
|
9
|
+
* accepts a materialized model from its caller (see `index.ts`). The test
|
|
10
|
+
* harness runs goldens through an inline entry of its own
|
|
11
|
+
* (`src/__test-utils__/workflows/`).
|
|
9
12
|
*
|
|
10
13
|
* SANDBOX RULES: This file runs inside the Temporal deterministic V8
|
|
11
14
|
* isolate. No Node.js built-ins (crypto, fs, net), no non-deterministic
|
|
@@ -46,10 +49,9 @@ import type {
|
|
|
46
49
|
HumanInputExecutionConfig,
|
|
47
50
|
TaskExecutionContext,
|
|
48
51
|
WorkflowEventDescriptor,
|
|
52
|
+
WorkflowModel,
|
|
49
53
|
} from "../workflow-engine/types.js";
|
|
50
54
|
|
|
51
|
-
import type { ExecuteServerlessWorkflowInput } from "./execute-serverless-workflow.js";
|
|
52
|
-
|
|
53
55
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
56
|
// Activity Proxies (module-level singletons — shared across all workflow
|
|
55
57
|
// invocations within the same worker)
|
|
@@ -132,6 +134,30 @@ const promoteProxy = proxyLocalActivities<PromoteActivities>({
|
|
|
132
134
|
},
|
|
133
135
|
});
|
|
134
136
|
|
|
137
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
// Engine Input
|
|
139
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* What the engine runs: a parsed model, the merged env and the workflow
|
|
143
|
+
* input. Built by the hydration activity from what the server holds
|
|
144
|
+
* (`activities/hydrate-workflow-execution.ts`), never taken from a caller.
|
|
145
|
+
*/
|
|
146
|
+
export interface ExecuteServerlessWorkflowInput {
|
|
147
|
+
model: WorkflowModel;
|
|
148
|
+
workflow_input: unknown;
|
|
149
|
+
env: Record<string, unknown>;
|
|
150
|
+
metadata?: ExecutionMetadata;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface ExecutionMetadata {
|
|
154
|
+
execution_id?: string;
|
|
155
|
+
workflow_id?: string;
|
|
156
|
+
workflow_instance_id?: string;
|
|
157
|
+
org_id?: string;
|
|
158
|
+
execution_target?: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
135
161
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
136
162
|
// Engine Core
|
|
137
163
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -149,9 +175,8 @@ export interface RunWorkflowEngineOptions {
|
|
|
149
175
|
* executes all tasks via {@link executeDoTasks}, and returns the final
|
|
150
176
|
* output.
|
|
151
177
|
*
|
|
152
|
-
* Called by
|
|
153
|
-
*
|
|
154
|
-
* - `executeFromExecution` (wrapper that hydrates from slim IDs first)
|
|
178
|
+
* Called by `executeFromExecution` once it has hydrated the input from
|
|
179
|
+
* slim ids, and by the test harness's inline entry.
|
|
155
180
|
*
|
|
156
181
|
* @param options.checkPause Optional pause yield point callback. When
|
|
157
182
|
* provided, the engine calls this between tasks and allows the
|
package/src/workflows/index.ts
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workflow barrel file — the entry point for Temporal's workflow bundler
|
|
2
|
+
* Workflow barrel file — the entry point for Temporal's workflow bundler,
|
|
3
|
+
* and the complete list of workflow types a runner registers.
|
|
3
4
|
*
|
|
4
5
|
* Re-exports workflow functions with the exact Temporal workflow type
|
|
5
|
-
* names that the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* names that the server uses to start them. The Temporal TS SDK derives
|
|
7
|
+
* the workflow type from the export name of the module passed to
|
|
8
|
+
* `workflowsPath`, so every export here is a registered type on every
|
|
9
|
+
* worker root: the static worker, the manager and pool workers, and the
|
|
10
|
+
* pre-built bundle (`scripts/bundle-slim.mjs`).
|
|
8
11
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
12
|
+
* THE RULE: every export is a type the server starts, and each takes ids
|
|
13
|
+
* that the runner reads back from the server, never a model to run.
|
|
14
|
+
* Anyone who can reach Temporal can start any type registered here on a
|
|
15
|
+
* runner's queue, so a new export is a new door and is reviewed as a
|
|
16
|
+
* wire change. `__tests__/barrel.test.ts` pins the exact set. Tests that
|
|
17
|
+
* hand the engine a model directly use the test barrel
|
|
18
|
+
* (`src/__test-utils__/workflows/index.ts`).
|
|
19
|
+
*
|
|
20
|
+
* The types and their starters:
|
|
21
|
+
* - "stigmer/mcp-server/connect" and "stigmer/mcp-server/discover": the
|
|
22
|
+
* server's MCP connect lane (`temporal/mcpserver/names.ts` in the
|
|
23
|
+
* server), by MCP server id.
|
|
24
|
+
* - "stigmer/workflow/execute-from-execution": the server's workflow
|
|
25
|
+
* execution orchestrator (`temporal/workflowexecution/names.ts`), by
|
|
26
|
+
* execution id; it hydrates the model from the server.
|
|
11
27
|
*
|
|
12
28
|
* ES2022 arbitrary module export names (`export { fn as "..." }`) let
|
|
13
29
|
* us map TypeScript function names to the slash-delimited Temporal
|
|
@@ -26,10 +42,6 @@ export {
|
|
|
26
42
|
discoverMcpServerLegacy as "stigmer/mcp-server/discover",
|
|
27
43
|
} from "./connect-mcp-server.js";
|
|
28
44
|
|
|
29
|
-
export {
|
|
30
|
-
executeServerlessWorkflow as "stigmer/workflow/execute",
|
|
31
|
-
} from "./execute-serverless-workflow.js";
|
|
32
|
-
|
|
33
45
|
export {
|
|
34
46
|
executeFromExecution as "stigmer/workflow/execute-from-execution",
|
|
35
47
|
} from "./execute-from-execution.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared pause/resume signal definitions and handler setup for
|
|
3
|
-
* workflow execution. Used by
|
|
4
|
-
* ("stigmer/workflow/execute-from-execution") and the
|
|
5
|
-
*
|
|
3
|
+
* workflow execution. Used by the hydration wrapper
|
|
4
|
+
* ("stigmer/workflow/execute-from-execution") and by the test harness's
|
|
5
|
+
* inline entry (`src/__test-utils__/workflows/inline-model.ts`).
|
|
6
6
|
*
|
|
7
7
|
* The Java/Go orchestrators forward pause and resume signals to
|
|
8
8
|
* the child workflow. The engine checks a `checkPause` callback
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Temporal workflow for executing CNCF Serverless Workflow definitions.
|
|
3
|
-
*
|
|
4
|
-
* Workflow type: "stigmer/workflow/execute"
|
|
5
|
-
*
|
|
6
|
-
* Accepts a fully materialized {@link ExecuteServerlessWorkflowInput}
|
|
7
|
-
* (parsed WorkflowModel, merged env, workflow_input) and runs the
|
|
8
|
-
* engine. Callers that already have the materialized input use this
|
|
9
|
-
* workflow directly. The hydration wrapper workflow
|
|
10
|
-
* ("stigmer/workflow/execute-from-execution") handles the case where
|
|
11
|
-
* only slim IDs are available.
|
|
12
|
-
*
|
|
13
|
-
* SANDBOX RULES: This file runs inside the Temporal deterministic V8
|
|
14
|
-
* isolate. No Node.js built-ins (crypto, fs, net), no non-deterministic
|
|
15
|
-
* operations, no side-effecting imports. Only @temporalio/workflow APIs,
|
|
16
|
-
* type-only imports, and pure JS/TS logic.
|
|
17
|
-
*/
|
|
18
|
-
import type { WorkflowModel } from "../workflow-engine/types.js";
|
|
19
|
-
export interface ExecuteServerlessWorkflowInput {
|
|
20
|
-
model: WorkflowModel;
|
|
21
|
-
workflow_input: unknown;
|
|
22
|
-
env: Record<string, unknown>;
|
|
23
|
-
metadata?: ExecutionMetadata;
|
|
24
|
-
}
|
|
25
|
-
export interface ExecutionMetadata {
|
|
26
|
-
execution_id?: string;
|
|
27
|
-
workflow_id?: string;
|
|
28
|
-
workflow_instance_id?: string;
|
|
29
|
-
org_id?: string;
|
|
30
|
-
execution_target?: number;
|
|
31
|
-
}
|
|
32
|
-
export declare function executeServerlessWorkflow(input: ExecuteServerlessWorkflowInput): Promise<unknown>;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Temporal workflow for executing CNCF Serverless Workflow definitions.
|
|
3
|
-
*
|
|
4
|
-
* Workflow type: "stigmer/workflow/execute"
|
|
5
|
-
*
|
|
6
|
-
* Accepts a fully materialized {@link ExecuteServerlessWorkflowInput}
|
|
7
|
-
* (parsed WorkflowModel, merged env, workflow_input) and runs the
|
|
8
|
-
* engine. Callers that already have the materialized input use this
|
|
9
|
-
* workflow directly. The hydration wrapper workflow
|
|
10
|
-
* ("stigmer/workflow/execute-from-execution") handles the case where
|
|
11
|
-
* only slim IDs are available.
|
|
12
|
-
*
|
|
13
|
-
* SANDBOX RULES: This file runs inside the Temporal deterministic V8
|
|
14
|
-
* isolate. No Node.js built-ins (crypto, fs, net), no non-deterministic
|
|
15
|
-
* operations, no side-effecting imports. Only @temporalio/workflow APIs,
|
|
16
|
-
* type-only imports, and pure JS/TS logic.
|
|
17
|
-
*/
|
|
18
|
-
import { isCancellation } from "@temporalio/workflow";
|
|
19
|
-
import { runWorkflowEngine } from "./engine-core.js";
|
|
20
|
-
import { setupPauseResumeHandlers } from "./workflow-signals.js";
|
|
21
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
// Workflow Function
|
|
23
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
-
export async function executeServerlessWorkflow(input) {
|
|
25
|
-
const { checkPause } = setupPauseResumeHandlers();
|
|
26
|
-
try {
|
|
27
|
-
return await runWorkflowEngine(input, { checkPause });
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
if (isCancellation(err)) {
|
|
31
|
-
// Let CancelledFailure propagate — parent handles cleanup
|
|
32
|
-
}
|
|
33
|
-
throw err;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
//# sourceMappingURL=execute-serverless-workflow.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"execute-serverless-workflow.js","sourceRoot":"","sources":["../../src/workflows/execute-serverless-workflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAqBjE,gFAAgF;AAChF,oBAAoB;AACpB,gFAAgF;AAEhF,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,KAAqC;IAErC,MAAM,EAAE,UAAU,EAAE,GAAG,wBAAwB,EAAE,CAAC;IAElD,IAAI,CAAC;QACH,OAAO,MAAM,iBAAiB,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,0DAA0D;QAC5D,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Temporal workflow for executing CNCF Serverless Workflow definitions.
|
|
3
|
-
*
|
|
4
|
-
* Workflow type: "stigmer/workflow/execute"
|
|
5
|
-
*
|
|
6
|
-
* Accepts a fully materialized {@link ExecuteServerlessWorkflowInput}
|
|
7
|
-
* (parsed WorkflowModel, merged env, workflow_input) and runs the
|
|
8
|
-
* engine. Callers that already have the materialized input use this
|
|
9
|
-
* workflow directly. The hydration wrapper workflow
|
|
10
|
-
* ("stigmer/workflow/execute-from-execution") handles the case where
|
|
11
|
-
* only slim IDs are available.
|
|
12
|
-
*
|
|
13
|
-
* SANDBOX RULES: This file runs inside the Temporal deterministic V8
|
|
14
|
-
* isolate. No Node.js built-ins (crypto, fs, net), no non-deterministic
|
|
15
|
-
* operations, no side-effecting imports. Only @temporalio/workflow APIs,
|
|
16
|
-
* type-only imports, and pure JS/TS logic.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { isCancellation } from "@temporalio/workflow";
|
|
20
|
-
import type { WorkflowModel } from "../workflow-engine/types.js";
|
|
21
|
-
import { runWorkflowEngine } from "./engine-core.js";
|
|
22
|
-
import { setupPauseResumeHandlers } from "./workflow-signals.js";
|
|
23
|
-
|
|
24
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
-
// Workflow Input/Output Types
|
|
26
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
export interface ExecuteServerlessWorkflowInput {
|
|
29
|
-
model: WorkflowModel;
|
|
30
|
-
workflow_input: unknown;
|
|
31
|
-
env: Record<string, unknown>;
|
|
32
|
-
metadata?: ExecutionMetadata;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ExecutionMetadata {
|
|
36
|
-
execution_id?: string;
|
|
37
|
-
workflow_id?: string;
|
|
38
|
-
workflow_instance_id?: string;
|
|
39
|
-
org_id?: string;
|
|
40
|
-
execution_target?: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
-
// Workflow Function
|
|
45
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
-
|
|
47
|
-
export async function executeServerlessWorkflow(
|
|
48
|
-
input: ExecuteServerlessWorkflowInput,
|
|
49
|
-
): Promise<unknown> {
|
|
50
|
-
const { checkPause } = setupPauseResumeHandlers();
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
return await runWorkflowEngine(input, { checkPause });
|
|
54
|
-
} catch (err) {
|
|
55
|
-
if (isCancellation(err)) {
|
|
56
|
-
// Let CancelledFailure propagate — parent handles cleanup
|
|
57
|
-
}
|
|
58
|
-
throw err;
|
|
59
|
-
}
|
|
60
|
-
}
|