@smooai/smooth-operator-temporal 1.13.5

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 Smoo AI, Inc.
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/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @smooai/smooth-operator-temporal
2
+
3
+ Optional **Temporal-backed durable execution** for the TypeScript
4
+ [`@smooai/smooth-operator-core`](../core) agent engine (ADR-030). The TypeScript
5
+ sibling of the Rust [`smooth-operator-temporal`](../../rust/smooth-operator-temporal)
6
+ crate.
7
+
8
+ An agent turn runs as a Temporal **workflow** whose side-effects — the model call
9
+ and each tool invocation — are Temporal **activities**. The workflow drives the
10
+ engine's deterministic `driveTurn` orchestration **unchanged**, so the durable
11
+ path and the in-process path are the *same loop*. What durability buys: crash-safe
12
+ resume, durable human-in-the-loop via signals, and durable timers (an agent that
13
+ pauses itself for minutes or days and resumes).
14
+
15
+ ## Why a separate package
16
+
17
+ Mirrors the Rust crate's off-by-default `temporal` cargo feature: the published
18
+ `@smooai/smooth-operator-core` stays **zero-infra** and never pulls a Temporal SDK
19
+ into a consumer's dependency tree. Only a deployment that wants durable execution
20
+ installs this package. The activity **DTO boundary** (`./dto`) carries no Temporal
21
+ dependency and is always unit-tested.
22
+
23
+ ## Shape
24
+
25
+ | Export | Side | Role |
26
+ | ------------------------- | -------- | ---------------------------------------------------------------------- |
27
+ | `TemporalAgentExecutor` | client | The durable `AgentExecutor`; a drop-in for the engine's `InProcessExecutor`. |
28
+ | `createActivities(h)` | worker | Build the worker's activity object from engine handles (model + tools). |
29
+ | `agentTurnWorkflow` | workflow | The turn workflow (registered via the `./workflows` subpath). |
30
+ | `approveToolSignal` / `denyToolSignal` | both | Durable HITL signals. |
31
+
32
+ ## Testing
33
+
34
+ ```sh
35
+ # Zero-infra DTO boundary unit test (always runs, no Temporal):
36
+ pnpm --filter @smooai/smooth-operator-temporal test
37
+
38
+ # Full e2e against an ephemeral Temporal dev server (health, agent turn,
39
+ # durable timer, HITL) — self-skips offline:
40
+ pnpm --filter @smooai/smooth-operator-core build
41
+ pnpm --filter @smooai/smooth-operator-temporal build
42
+ SMOOTH_TEMPORAL_E2E=1 pnpm --filter @smooai/smooth-operator-temporal test
43
+ ```
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The Temporal **activities** for the agent-turn workflow: the side-effecting
3
+ * half of a turn (the model call, each tool invocation) plus a liveness probe.
4
+ *
5
+ * The TS sibling of the Rust crate's `AgentTurnActivities`. Where the Rust
6
+ * activities read their engine handles from a process-global installed by
7
+ * `init_engine`, the TypeScript Temporal SDK registers activities as an object
8
+ * passed to `Worker.create({ activities })` — so the handles are simply closed
9
+ * over here via {@link createActivities}, no global needed.
10
+ *
11
+ * Each activity runs in the **normal Node context** (not the deterministic
12
+ * workflow sandbox), so it may import the full engine freely: the model call and
13
+ * tool dispatch are a verbatim reuse of the engine's own
14
+ * {@link InProcessActivities} — the same code the in-process executor runs — with
15
+ * the DTO projection applied on the way out. One implementation, two backends.
16
+ */
17
+ import type { LlmProvider, Tool, ToolResult } from '@smooai/smooth-operator-core';
18
+ import { type ModelCallInput, type ModelCallOutput, type ToolInvokeInput } from './dto.js';
19
+ /**
20
+ * The engine handles the activities run against — the durable-path analog of the
21
+ * arguments the in-process executor is constructed with.
22
+ */
23
+ export interface EngineHandles {
24
+ /** The OpenAI-compatible chat client backing the `modelCall` activity. */
25
+ llm: LlmProvider;
26
+ /** The tools dispatchable by the `toolInvoke` activity (default none). */
27
+ tools?: Tool[];
28
+ /** Model id for the request body; defaults to the engine's own default. */
29
+ model?: string;
30
+ }
31
+ /** The activity surface registered with a Temporal worker. */
32
+ export interface AgentTurnActivities {
33
+ /** Liveness probe backing the scaffold health workflow. */
34
+ healthEcho(message: string): Promise<string>;
35
+ /** The model call (`Think`), run as a durable, retried activity. */
36
+ modelCall(input: ModelCallInput): Promise<ModelCallOutput>;
37
+ /** A single tool invocation (`Act`), run as a durable activity. */
38
+ toolInvoke(input: ToolInvokeInput): Promise<ToolResult>;
39
+ }
40
+ /**
41
+ * Build the activity object a Temporal worker registers, bound to `handles`.
42
+ *
43
+ * Tool *business* failures come back inside {@link ToolResult.isError} (not as a
44
+ * thrown activity error), exactly as {@link InProcessActivities.toolInvoke}
45
+ * reports them — matching the engine's own dispatch so the model reacts to a
46
+ * failed tool instead of the turn failing.
47
+ */
48
+ export declare function createActivities(handles: EngineHandles): AgentTurnActivities;
49
+ //# sourceMappingURL=activities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activities.d.ts","sourceRoot":"","sources":["../src/activities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAElF,OAAO,EAAyB,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAElH;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B,0EAA0E;IAC1E,GAAG,EAAE,WAAW,CAAC;IACjB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IAChC,2DAA2D;IAC3D,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,oEAAoE;IACpE,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3D,mEAAmE;IACnE,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC3D;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,mBAAmB,CAc5E"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The Temporal **activities** for the agent-turn workflow: the side-effecting
3
+ * half of a turn (the model call, each tool invocation) plus a liveness probe.
4
+ *
5
+ * The TS sibling of the Rust crate's `AgentTurnActivities`. Where the Rust
6
+ * activities read their engine handles from a process-global installed by
7
+ * `init_engine`, the TypeScript Temporal SDK registers activities as an object
8
+ * passed to `Worker.create({ activities })` — so the handles are simply closed
9
+ * over here via {@link createActivities}, no global needed.
10
+ *
11
+ * Each activity runs in the **normal Node context** (not the deterministic
12
+ * workflow sandbox), so it may import the full engine freely: the model call and
13
+ * tool dispatch are a verbatim reuse of the engine's own
14
+ * {@link InProcessActivities} — the same code the in-process executor runs — with
15
+ * the DTO projection applied on the way out. One implementation, two backends.
16
+ */
17
+ import { InProcessActivities } from '@smooai/smooth-operator-core/executor';
18
+ import { modelResponseToOutput } from './dto.js';
19
+ /**
20
+ * Build the activity object a Temporal worker registers, bound to `handles`.
21
+ *
22
+ * Tool *business* failures come back inside {@link ToolResult.isError} (not as a
23
+ * thrown activity error), exactly as {@link InProcessActivities.toolInvoke}
24
+ * reports them — matching the engine's own dispatch so the model reacts to a
25
+ * failed tool instead of the turn failing.
26
+ */
27
+ export function createActivities(handles) {
28
+ const inproc = new InProcessActivities(handles.llm, handles.tools ?? [], handles.model);
29
+ return {
30
+ async healthEcho(message) {
31
+ return `smooth-operator-temporal ok: ${message}`;
32
+ },
33
+ async modelCall(input) {
34
+ const response = await inproc.modelCall(input.messages, input.tools);
35
+ return modelResponseToOutput(response);
36
+ },
37
+ async toolInvoke(input) {
38
+ return inproc.toolInvoke(input.call);
39
+ },
40
+ };
41
+ }
42
+ //# sourceMappingURL=activities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activities.js","sourceRoot":"","sources":["../src/activities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAG5E,OAAO,EAAE,qBAAqB,EAAmE,MAAM,UAAU,CAAC;AAyBlH;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACnD,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,IAAI,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACxF,OAAO;QACH,KAAK,CAAC,UAAU,CAAC,OAAe;YAC5B,OAAO,gCAAgC,OAAO,EAAE,CAAC;QACrD,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,KAAqB;YACjC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YACrE,OAAO,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC3C,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,KAAsB;YACnC,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC;KACJ,CAAC;AACN,CAAC"}
package/dist/dto.d.ts ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Data-transfer objects for the Temporal **activity boundary**.
3
+ *
4
+ * The TypeScript port of the Rust crate's `dto.rs`. A Temporal activity
5
+ * serializes its input and output as JSON, so the workflow-side
6
+ * {@link import('@smooai/smooth-operator-core').AgentActivities} surface cannot
7
+ * traffic the raw {@link ModelResponse} (the full OpenAI object) across the
8
+ * boundary directly — it traffics {@link ModelCallOutput}, a projection of the
9
+ * fields the deterministic `driveTurn` orchestration actually reads (`content`,
10
+ * `tool_calls`) plus the accounting fields (`usage`), and the workflow-side
11
+ * adapter reconstructs a minimal {@link ModelResponse} from it via
12
+ * {@link outputToModelResponse}.
13
+ *
14
+ * Everything here is plain data over `@smooai/smooth-operator-core` types and
15
+ * imports **no** `@temporalio/*` package, so it is always built and unit-tested
16
+ * regardless of whether a Temporal runtime is present — exactly like the Rust
17
+ * `dto.rs` compiling without the `temporal` cargo feature.
18
+ */
19
+ import type { ModelResponse } from '@smooai/smooth-operator-core/executor';
20
+ import type { ToolCall } from '@smooai/smooth-operator-core';
21
+ /** Token usage carried across the activity boundary (OpenAI wire spelling). */
22
+ export interface DtoUsage {
23
+ prompt_tokens: number;
24
+ completion_tokens: number;
25
+ }
26
+ /** Input to the `modelCall` activity: the context window + the tool specs to offer. */
27
+ export interface ModelCallInput {
28
+ /** The conversation context window, as OpenAI-shaped messages. */
29
+ messages: Array<Record<string, unknown>>;
30
+ /** Tool schemas (the OpenAI `tools` array) the model may call; empty means none. */
31
+ tools: Array<Record<string, unknown>>;
32
+ }
33
+ /**
34
+ * Output of the `modelCall` activity: a serde projection of {@link ModelResponse}.
35
+ *
36
+ * Carries the fields the orchestration reads (`content`, `toolCalls`) plus the
37
+ * accounting `usage` so the durable path preserves cost/audit data. Anything the
38
+ * live OpenAI response carries beyond these is transient and dropped — it does
39
+ * not survive (nor need to survive) the activity boundary.
40
+ */
41
+ export interface ModelCallOutput {
42
+ /** Assistant text content ('' when the model only asked for tools). */
43
+ content: string;
44
+ /** Tool calls the model requested, in OpenAI wire shape. */
45
+ toolCalls: Array<{
46
+ id: string;
47
+ function: {
48
+ name: string;
49
+ arguments: string;
50
+ };
51
+ }>;
52
+ /** Token usage reported by the gateway, if any. */
53
+ usage?: DtoUsage;
54
+ }
55
+ /** Input to the `toolInvoke` activity: the single tool call to execute. */
56
+ export interface ToolInvokeInput {
57
+ /** The tool call (id + name + arguments) to dispatch. */
58
+ call: ToolCall;
59
+ }
60
+ /**
61
+ * Input to the agent-turn workflow — everything needed to seed the conversation
62
+ * and bound the loop. Serializable so it crosses the workflow-start boundary. The
63
+ * TS sibling of Rust's `AgentTurnInput`.
64
+ */
65
+ export interface AgentTurnInput {
66
+ /** System prompt for the turn. */
67
+ systemPrompt: string;
68
+ /** The user message that opens the turn. */
69
+ userMessage: string;
70
+ /** Prior OpenAI-shaped messages replayed as memory before the user message. */
71
+ history?: Array<Record<string, unknown>>;
72
+ /** Tool schemas (the OpenAI `tools` array) available to the model. */
73
+ tools?: Array<Record<string, unknown>>;
74
+ /** Iteration bound. Omitted / `0` falls back to the loop's default. */
75
+ maxIterations?: number;
76
+ /**
77
+ * Names of tools that require human approval before they run. When the model
78
+ * calls one, the workflow blocks durably until an `approveTool` / `denyTool`
79
+ * signal names that tool call.
80
+ */
81
+ approvalRequiredTools?: string[];
82
+ /**
83
+ * Name of the built-in **durable wait** tool, if any. A model call to this tool
84
+ * with an integer `seconds` argument sleeps the workflow on a Temporal timer
85
+ * (a durable pause that can span days) instead of dispatching an activity.
86
+ */
87
+ waitTool?: string;
88
+ }
89
+ /**
90
+ * Result of the agent-turn workflow: the full conversation plus the turn
91
+ * accounting the durable path preserved. Serializable (it is the workflow's
92
+ * return value).
93
+ */
94
+ export interface AgentTurnResult {
95
+ /** The full conversation (system + user + assistant/tool messages). */
96
+ messages: Array<Record<string, unknown>>;
97
+ /** Number of model-call iterations the loop ran. */
98
+ iterations: number;
99
+ /** Number of tools actually invoked (approval-denied / wait tools excluded). */
100
+ toolCalls: number;
101
+ /** Accumulated token usage across every model call. */
102
+ usage: DtoUsage;
103
+ }
104
+ /**
105
+ * Project a live {@link ModelResponse} down to the serde {@link ModelCallOutput}
106
+ * the activity returns. The TS sibling of Rust's `impl From<&LlmResponse> for
107
+ * ModelCallOutput`.
108
+ */
109
+ export declare function modelResponseToOutput(response: ModelResponse): ModelCallOutput;
110
+ /**
111
+ * Reconstruct the minimal {@link ModelResponse} the deterministic `driveTurn`
112
+ * loop consumes (`choices[0].message.{content,tool_calls}` + `usage`) from the
113
+ * activity's {@link ModelCallOutput}. The TS sibling of Rust's
114
+ * `ModelCallOutput::into_llm_response`.
115
+ */
116
+ export declare function outputToModelResponse(output: ModelCallOutput): ModelResponse;
117
+ //# sourceMappingURL=dto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dto.d.ts","sourceRoot":"","sources":["../src/dto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAE7D,+EAA+E;AAC/E,MAAM,WAAW,QAAQ;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;CAC7B;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC3B,kEAAkE;IAClE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACzC,oFAAoF;IACpF,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC5B,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,SAAS,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;IAChF,mDAAmD;IACnD,KAAK,CAAC,EAAE,QAAQ,CAAC;CACpB;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC5B,yDAAyD;IACzD,IAAI,EAAE,QAAQ,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC3B,kCAAkC;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACzC,sEAAsE;IACtE,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvC,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC5B,uEAAuE;IACvE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACzC,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,KAAK,EAAE,QAAQ,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,eAAe,CAiB9E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,aAAa,CAY5E"}
package/dist/dto.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Data-transfer objects for the Temporal **activity boundary**.
3
+ *
4
+ * The TypeScript port of the Rust crate's `dto.rs`. A Temporal activity
5
+ * serializes its input and output as JSON, so the workflow-side
6
+ * {@link import('@smooai/smooth-operator-core').AgentActivities} surface cannot
7
+ * traffic the raw {@link ModelResponse} (the full OpenAI object) across the
8
+ * boundary directly — it traffics {@link ModelCallOutput}, a projection of the
9
+ * fields the deterministic `driveTurn` orchestration actually reads (`content`,
10
+ * `tool_calls`) plus the accounting fields (`usage`), and the workflow-side
11
+ * adapter reconstructs a minimal {@link ModelResponse} from it via
12
+ * {@link outputToModelResponse}.
13
+ *
14
+ * Everything here is plain data over `@smooai/smooth-operator-core` types and
15
+ * imports **no** `@temporalio/*` package, so it is always built and unit-tested
16
+ * regardless of whether a Temporal runtime is present — exactly like the Rust
17
+ * `dto.rs` compiling without the `temporal` cargo feature.
18
+ */
19
+ /**
20
+ * Project a live {@link ModelResponse} down to the serde {@link ModelCallOutput}
21
+ * the activity returns. The TS sibling of Rust's `impl From<&LlmResponse> for
22
+ * ModelCallOutput`.
23
+ */
24
+ export function modelResponseToOutput(response) {
25
+ const message = response.choices[0].message;
26
+ const usage = response.usage ?? undefined;
27
+ const out = {
28
+ content: message.content ?? '',
29
+ toolCalls: (message.tool_calls ?? []).map((tc) => ({
30
+ id: tc.id,
31
+ function: { name: tc.function.name, arguments: tc.function.arguments },
32
+ })),
33
+ };
34
+ if (usage) {
35
+ out.usage = {
36
+ prompt_tokens: usage.prompt_tokens ?? 0,
37
+ completion_tokens: usage.completion_tokens ?? 0,
38
+ };
39
+ }
40
+ return out;
41
+ }
42
+ /**
43
+ * Reconstruct the minimal {@link ModelResponse} the deterministic `driveTurn`
44
+ * loop consumes (`choices[0].message.{content,tool_calls}` + `usage`) from the
45
+ * activity's {@link ModelCallOutput}. The TS sibling of Rust's
46
+ * `ModelCallOutput::into_llm_response`.
47
+ */
48
+ export function outputToModelResponse(output) {
49
+ return {
50
+ choices: [
51
+ {
52
+ message: {
53
+ content: output.content,
54
+ tool_calls: output.toolCalls.length > 0 ? output.toolCalls : null,
55
+ },
56
+ },
57
+ ],
58
+ usage: output.usage ?? null,
59
+ };
60
+ }
61
+ //# sourceMappingURL=dto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dto.js","sourceRoot":"","sources":["../src/dto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAwFH;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAuB;IACzD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,SAAS,CAAC;IAC1C,MAAM,GAAG,GAAoB;QACzB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;QAC9B,SAAS,EAAE,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC/C,EAAE,EAAE,EAAE,CAAC,EAAE;YACT,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,EAAE;SACzE,CAAC,CAAC;KACN,CAAC;IACF,IAAI,KAAK,EAAE,CAAC;QACR,GAAG,CAAC,KAAK,GAAG;YACR,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,CAAC;YACvC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,IAAI,CAAC;SAClD,CAAC;IACN,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAuB;IACzD,OAAO;QACH,OAAO,EAAE;YACL;gBACI,OAAO,EAAE;oBACL,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;iBACpE;aACJ;SACJ;QACD,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;KACb,CAAC;AACvB,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The Temporal-backed {@link AgentExecutor} — the durable sibling of the engine's
3
+ * zero-infra `InProcessExecutor`.
4
+ *
5
+ * It runs a turn by starting the {@link agentTurnWorkflow} on a Temporal cluster
6
+ * and awaiting its result, then shaping the returned conversation into the same
7
+ * {@link AgentRunResponse} `SmoothAgent.run` would produce. A consumer swaps a
8
+ * direct `agent.run(...)` for `executor.execute(agent, ...)` with no other change
9
+ * — the turn now survives a crash, gets durable HITL via signals, and can pause
10
+ * on a durable timer.
11
+ *
12
+ * ## The engine-handle split (mirrors Rust's `init_engine`)
13
+ *
14
+ * A durable turn has two halves in two processes: the **worker** holds the model
15
+ * client + tool *implementations* (via {@link createActivities}); this **executor**
16
+ * (client side) holds the turn *configuration* — system prompt, tool *schemas*,
17
+ * approval/wait policy — supplied at construction. That is why {@link execute}
18
+ * reads its prompt/tools from {@link TemporalAgentExecutorOptions} rather than off
19
+ * the passed `agent` (whose config is private and lives in the worker process).
20
+ * `message` and `history` ARE honored per call.
21
+ *
22
+ * The workflow is started by its **type name** (never by importing the workflow
23
+ * module), so this client-side file pulls in no `@temporalio/workflow` sandbox
24
+ * code — importing that module in a normal Node context throws.
25
+ */
26
+ import type { Client } from '@temporalio/client';
27
+ import type { AgentExecutor, AgentRunResponse, SmoothAgent, StreamEvent } from '@smooai/smooth-operator-core';
28
+ import type { SmoothAgentThread } from '@smooai/smooth-operator-core';
29
+ /** The Temporal workflow type name the worker registers {@link agentTurnWorkflow} under. */
30
+ export declare const AGENT_TURN_WORKFLOW_TYPE = "agentTurnWorkflow";
31
+ /** Configuration for a {@link TemporalAgentExecutor}. */
32
+ export interface TemporalAgentExecutorOptions {
33
+ /** A connected `@temporalio/client` {@link Client}. */
34
+ client: Client;
35
+ /** The task queue the worker polls (must match the worker's). */
36
+ taskQueue: string;
37
+ /** System prompt for every turn this executor runs. */
38
+ systemPrompt?: string;
39
+ /** OpenAI tool schemas (the `tools` array) offered to the model. */
40
+ tools?: Array<Record<string, unknown>>;
41
+ /** Iteration bound; omitted / `0` uses the engine default. */
42
+ maxIterations?: number;
43
+ /** Tool names gated behind durable human approval (`approveTool` / `denyTool` signals). */
44
+ approvalRequiredTools?: string[];
45
+ /** Name of the durable wait tool, if any. */
46
+ waitTool?: string;
47
+ /** Prefix for generated workflow ids (default `agent-turn`). */
48
+ workflowIdPrefix?: string;
49
+ }
50
+ export declare class TemporalAgentExecutor implements AgentExecutor {
51
+ private readonly options;
52
+ constructor(options: TemporalAgentExecutorOptions);
53
+ execute(_agent: SmoothAgent, message: string, history?: Array<Record<string, unknown>>, _thread?: SmoothAgentThread): Promise<AgentRunResponse>;
54
+ /**
55
+ * A workflow-backed turn has no token-delta stream (its history is the
56
+ * checkpoint, not a live channel), so streaming resolves the durable turn and
57
+ * yields a single terminal `done`.
58
+ *
59
+ * ponytail: no incremental `text` / `tool_call` events on the durable path —
60
+ * a durable turn's progress is observed via workflow history/queries, not a
61
+ * token stream. Upgrade path (per ADR-030's open questions): a workflow-side
62
+ * signal/update channel feeding deltas back to the client.
63
+ */
64
+ executeStreaming(agent: SmoothAgent, message: string, history?: Array<Record<string, unknown>>, thread?: SmoothAgentThread): AsyncGenerator<StreamEvent>;
65
+ }
66
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC9G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAItE,4FAA4F;AAC5F,eAAO,MAAM,wBAAwB,sBAAsB,CAAC;AAE5D,yDAAyD;AACzD,MAAM,WAAW,4BAA4B;IACzC,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2FAA2F;IAC3F,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,qBAAa,qBAAsB,YAAW,aAAa;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,4BAA4B;IAE5D,OAAO,CACT,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,OAAO,CAAC,EAAE,iBAAiB,GAC5B,OAAO,CAAC,gBAAgB,CAAC;IAmB5B;;;;;;;;;OASG;IACI,gBAAgB,CACnB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACxC,MAAM,CAAC,EAAE,iBAAiB,GAC3B,cAAc,CAAC,WAAW,CAAC;CAIjC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The Temporal-backed {@link AgentExecutor} — the durable sibling of the engine's
3
+ * zero-infra `InProcessExecutor`.
4
+ *
5
+ * It runs a turn by starting the {@link agentTurnWorkflow} on a Temporal cluster
6
+ * and awaiting its result, then shaping the returned conversation into the same
7
+ * {@link AgentRunResponse} `SmoothAgent.run` would produce. A consumer swaps a
8
+ * direct `agent.run(...)` for `executor.execute(agent, ...)` with no other change
9
+ * — the turn now survives a crash, gets durable HITL via signals, and can pause
10
+ * on a durable timer.
11
+ *
12
+ * ## The engine-handle split (mirrors Rust's `init_engine`)
13
+ *
14
+ * A durable turn has two halves in two processes: the **worker** holds the model
15
+ * client + tool *implementations* (via {@link createActivities}); this **executor**
16
+ * (client side) holds the turn *configuration* — system prompt, tool *schemas*,
17
+ * approval/wait policy — supplied at construction. That is why {@link execute}
18
+ * reads its prompt/tools from {@link TemporalAgentExecutorOptions} rather than off
19
+ * the passed `agent` (whose config is private and lives in the worker process).
20
+ * `message` and `history` ARE honored per call.
21
+ *
22
+ * The workflow is started by its **type name** (never by importing the workflow
23
+ * module), so this client-side file pulls in no `@temporalio/workflow` sandbox
24
+ * code — importing that module in a normal Node context throws.
25
+ */
26
+ import { randomUUID } from 'node:crypto';
27
+ /** The Temporal workflow type name the worker registers {@link agentTurnWorkflow} under. */
28
+ export const AGENT_TURN_WORKFLOW_TYPE = 'agentTurnWorkflow';
29
+ export class TemporalAgentExecutor {
30
+ options;
31
+ constructor(options) {
32
+ this.options = options;
33
+ }
34
+ async execute(_agent, message, history, _thread) {
35
+ const input = {
36
+ systemPrompt: this.options.systemPrompt ?? '',
37
+ userMessage: message,
38
+ history,
39
+ tools: this.options.tools,
40
+ maxIterations: this.options.maxIterations,
41
+ approvalRequiredTools: this.options.approvalRequiredTools,
42
+ waitTool: this.options.waitTool,
43
+ };
44
+ const workflowId = `${this.options.workflowIdPrefix ?? 'agent-turn'}-${randomUUID()}`;
45
+ const result = await this.options.client.workflow.execute(AGENT_TURN_WORKFLOW_TYPE, {
46
+ taskQueue: this.options.taskQueue,
47
+ workflowId,
48
+ args: [input],
49
+ });
50
+ return toAgentRunResponse(result);
51
+ }
52
+ /**
53
+ * A workflow-backed turn has no token-delta stream (its history is the
54
+ * checkpoint, not a live channel), so streaming resolves the durable turn and
55
+ * yields a single terminal `done`.
56
+ *
57
+ * ponytail: no incremental `text` / `tool_call` events on the durable path —
58
+ * a durable turn's progress is observed via workflow history/queries, not a
59
+ * token stream. Upgrade path (per ADR-030's open questions): a workflow-side
60
+ * signal/update channel feeding deltas back to the client.
61
+ */
62
+ async *executeStreaming(agent, message, history, thread) {
63
+ const response = await this.execute(agent, message, history, thread);
64
+ yield { type: 'done', response };
65
+ }
66
+ }
67
+ /** Shape a durable-turn {@link AgentTurnResult} into an engine {@link AgentRunResponse}. */
68
+ function toAgentRunResponse(result) {
69
+ let text = '';
70
+ for (let i = result.messages.length - 1; i >= 0; i--) {
71
+ const msg = result.messages[i];
72
+ if (msg.role === 'assistant' && typeof msg.content === 'string' && msg.content.length > 0) {
73
+ text = msg.content;
74
+ break;
75
+ }
76
+ }
77
+ return {
78
+ text,
79
+ iterations: result.iterations,
80
+ toolCalls: result.toolCalls,
81
+ usage: { promptTokens: result.usage.prompt_tokens, completionTokens: result.usage.completion_tokens },
82
+ // ponytail: authoritative per-request cost lives in the gateway's spend
83
+ // logs, not carried back through the workflow boundary. Wire it via the
84
+ // model_call DTO's gateway cost when durable-turn cost attribution lands.
85
+ costUsd: 0,
86
+ budgetExceeded: false,
87
+ };
88
+ }
89
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AASzC,4FAA4F;AAC5F,MAAM,CAAC,MAAM,wBAAwB,GAAG,mBAAmB,CAAC;AAsB5D,MAAM,OAAO,qBAAqB;IACD;IAA7B,YAA6B,OAAqC;QAArC,YAAO,GAAP,OAAO,CAA8B;IAAG,CAAC;IAEtE,KAAK,CAAC,OAAO,CACT,MAAmB,EACnB,OAAe,EACf,OAAwC,EACxC,OAA2B;QAE3B,MAAM,KAAK,GAAmB;YAC1B,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE;YAC7C,WAAW,EAAE,OAAO;YACpB,OAAO;YACP,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK;YACzB,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;YACzC,qBAAqB,EAAE,IAAI,CAAC,OAAO,CAAC,qBAAqB;YACzD,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;SAClC,CAAC;QACF,MAAM,UAAU,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,IAAI,YAAY,IAAI,UAAU,EAAE,EAAE,CAAC;QACtF,MAAM,MAAM,GAAoB,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,wBAAwB,EAAE;YACjG,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;YACjC,UAAU;YACV,IAAI,EAAE,CAAC,KAAK,CAAC;SAChB,CAAC,CAAC;QACH,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,CAAC,gBAAgB,CACnB,KAAkB,EAClB,OAAe,EACf,OAAwC,EACxC,MAA0B;QAE1B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACrE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;CACJ;AAED,4FAA4F;AAC5F,SAAS,kBAAkB,CAAC,MAAuB;IAC/C,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnD,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxF,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC;YACnB,MAAM;QACV,CAAC;IACL,CAAC;IACD,OAAO;QACH,IAAI;QACJ,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,KAAK,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE;QACrG,wEAAwE;QACxE,wEAAwE;QACxE,0EAA0E;QAC1E,OAAO,EAAE,CAAC;QACV,cAAc,EAAE,KAAK;KACxB,CAAC;AACN,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @smooai/smooth-operator-temporal — optional Temporal-backed durable execution
3
+ * for the TypeScript smooth-operator engine (ADR-030).
4
+ *
5
+ * The TypeScript sibling of the Rust `smooth-operator-temporal` crate. An agent
6
+ * turn runs as a Temporal workflow ({@link agentTurnWorkflow}) whose model call
7
+ * and tool invocations are activities, driving the engine's deterministic
8
+ * `driveTurn` loop unchanged — the same loop the in-process executor runs.
9
+ *
10
+ * - {@link TemporalAgentExecutor} — the durable {@link AgentExecutor}; a
11
+ * drop-in for the engine's `InProcessExecutor`.
12
+ * - {@link createActivities} — build the worker's activity object from engine
13
+ * handles (model client + tools).
14
+ * - Workflows are also published at the `./workflows` subpath (the path a
15
+ * Temporal worker registers) and activities at `./activities`.
16
+ * - The `./dto` boundary types (re-exported here) carry no Temporal dependency.
17
+ */
18
+ export type { AgentTurnActivities, EngineHandles } from './activities.js';
19
+ export { createActivities } from './activities.js';
20
+ export type { AgentTurnInput, AgentTurnResult, DtoUsage, ModelCallInput, ModelCallOutput, ToolInvokeInput } from './dto.js';
21
+ export { modelResponseToOutput, outputToModelResponse } from './dto.js';
22
+ export { AGENT_TURN_WORKFLOW_TYPE, TemporalAgentExecutor } from './executor.js';
23
+ export type { TemporalAgentExecutorOptions } from './executor.js';
24
+ export { approveToolSignal, denyToolSignal } from './signals.js';
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC5H,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAChF,YAAY,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAKlE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @smooai/smooth-operator-temporal — optional Temporal-backed durable execution
3
+ * for the TypeScript smooth-operator engine (ADR-030).
4
+ *
5
+ * The TypeScript sibling of the Rust `smooth-operator-temporal` crate. An agent
6
+ * turn runs as a Temporal workflow ({@link agentTurnWorkflow}) whose model call
7
+ * and tool invocations are activities, driving the engine's deterministic
8
+ * `driveTurn` loop unchanged — the same loop the in-process executor runs.
9
+ *
10
+ * - {@link TemporalAgentExecutor} — the durable {@link AgentExecutor}; a
11
+ * drop-in for the engine's `InProcessExecutor`.
12
+ * - {@link createActivities} — build the worker's activity object from engine
13
+ * handles (model client + tools).
14
+ * - Workflows are also published at the `./workflows` subpath (the path a
15
+ * Temporal worker registers) and activities at `./activities`.
16
+ * - The `./dto` boundary types (re-exported here) carry no Temporal dependency.
17
+ */
18
+ export { createActivities } from './activities.js';
19
+ export { modelResponseToOutput, outputToModelResponse } from './dto.js';
20
+ export { AGENT_TURN_WORKFLOW_TYPE, TemporalAgentExecutor } from './executor.js';
21
+ // Signals are exported from their own context-free module so a client can import
22
+ // them; the workflow functions themselves live at the `./workflows` subpath (the
23
+ // path a Temporal worker registers) and are NOT re-exported here — importing that
24
+ // module in a normal Node process throws (its top-level `proxyActivities`).
25
+ export { approveToolSignal, denyToolSignal } from './signals.js';
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEhF,iFAAiF;AACjF,iFAAiF;AACjF,kFAAkF;AAClF,4EAA4E;AAC5E,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Signal definitions for the agent-turn workflow, in their own module so they are
3
+ * safe to import from BOTH sides: the workflow (which handles them) and a client
4
+ * (which sends them).
5
+ *
6
+ * {@link defineSignal} only builds a signal descriptor — it needs no workflow
7
+ * context — so importing this file in a normal Node/client process is safe, unlike
8
+ * importing `workflows.ts` (whose top-level `proxyActivities` throws outside a
9
+ * workflow).
10
+ */
11
+ /** A human approves the tool call with this id, unblocking the durable HITL gate. */
12
+ export declare const approveToolSignal: import("@temporalio/workflow").SignalDefinition<[string], string>;
13
+ /** A human denies the tool call with this id; the gate skips it with an error result. */
14
+ export declare const denyToolSignal: import("@temporalio/workflow").SignalDefinition<[string], string>;
15
+ //# sourceMappingURL=signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,mEAAwC,CAAC;AACvE,yFAAyF;AACzF,eAAO,MAAM,cAAc,mEAAqC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Signal definitions for the agent-turn workflow, in their own module so they are
3
+ * safe to import from BOTH sides: the workflow (which handles them) and a client
4
+ * (which sends them).
5
+ *
6
+ * {@link defineSignal} only builds a signal descriptor — it needs no workflow
7
+ * context — so importing this file in a normal Node/client process is safe, unlike
8
+ * importing `workflows.ts` (whose top-level `proxyActivities` throws outside a
9
+ * workflow).
10
+ */
11
+ import { defineSignal } from '@temporalio/workflow';
12
+ /** A human approves the tool call with this id, unblocking the durable HITL gate. */
13
+ export const approveToolSignal = defineSignal('approveTool');
14
+ /** A human denies the tool call with this id; the gate skips it with an error result. */
15
+ export const denyToolSignal = defineSignal('denyTool');
16
+ //# sourceMappingURL=signals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.js","sourceRoot":"","sources":["../src/signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,qFAAqF;AACrF,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAW,aAAa,CAAC,CAAC;AACvE,yFAAyF;AACzF,MAAM,CAAC,MAAM,cAAc,GAAG,YAAY,CAAW,UAAU,CAAC,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The Temporal **workflows** (the deterministic half of durable execution).
3
+ *
4
+ * The TS sibling of the Rust crate's `AgentTurnWorkflow` + `HealthWorkflow`. An
5
+ * agent turn runs as {@link agentTurnWorkflow}, which drives the engine's
6
+ * deterministic {@link driveTurn} orchestration **unchanged** over an adapter
7
+ * that schedules the model call and each tool invocation as Temporal activities.
8
+ * The in-process executor runs the same `driveTurn` inline — one loop, two
9
+ * backends.
10
+ *
11
+ * This module runs in Temporal's **deterministic workflow sandbox**, so it may
12
+ * import only replay-safe code: `@temporalio/workflow` primitives, the pure
13
+ * `driveTurn` + its types from `@smooai/smooth-operator-core/executor` (that
14
+ * entry point has zero runtime imports — it is type-only), and the plain DTO
15
+ * helpers. It performs no I/O, clock reads, or RNG of its own — every
16
+ * side-effect is a scheduled activity, every wait a durable `sleep`/`condition`.
17
+ */
18
+ import { type AgentTurnInput, type AgentTurnResult } from './dto.js';
19
+ /**
20
+ * Run one agent turn as a durable workflow. Seeds the conversation (system +
21
+ * history + user), then drives {@link driveTurn} over an activity-scheduling
22
+ * adapter, and returns the full conversation plus turn accounting.
23
+ *
24
+ * Durable human-in-the-loop and durable timers live in the adapter, not in
25
+ * `driveTurn`: an approval-gated tool blocks on {@link condition} until an
26
+ * `approveTool` / `denyTool` signal names its call id, and the configured wait
27
+ * tool sleeps on a durable {@link sleep} timer — both recorded in workflow
28
+ * history, so they survive worker restarts and can resolve arbitrarily later.
29
+ */
30
+ export declare function agentTurnWorkflow(input: AgentTurnInput): Promise<AgentTurnResult>;
31
+ /**
32
+ * Scaffold liveness workflow — proves the SDK integrates end to end and backs the
33
+ * health e2e. The TS sibling of the Rust crate's `HealthWorkflow`.
34
+ */
35
+ export declare function healthWorkflow(message: string): Promise<string>;
36
+ //# sourceMappingURL=workflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAUH,OAAO,EAAyB,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAY5F;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAmEvF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAErE"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The Temporal **workflows** (the deterministic half of durable execution).
3
+ *
4
+ * The TS sibling of the Rust crate's `AgentTurnWorkflow` + `HealthWorkflow`. An
5
+ * agent turn runs as {@link agentTurnWorkflow}, which drives the engine's
6
+ * deterministic {@link driveTurn} orchestration **unchanged** over an adapter
7
+ * that schedules the model call and each tool invocation as Temporal activities.
8
+ * The in-process executor runs the same `driveTurn` inline — one loop, two
9
+ * backends.
10
+ *
11
+ * This module runs in Temporal's **deterministic workflow sandbox**, so it may
12
+ * import only replay-safe code: `@temporalio/workflow` primitives, the pure
13
+ * `driveTurn` + its types from `@smooai/smooth-operator-core/executor` (that
14
+ * entry point has zero runtime imports — it is type-only), and the plain DTO
15
+ * helpers. It performs no I/O, clock reads, or RNG of its own — every
16
+ * side-effect is a scheduled activity, every wait a durable `sleep`/`condition`.
17
+ */
18
+ import { condition, proxyActivities, setHandler, sleep } from '@temporalio/workflow';
19
+ import { driveTurn } from '@smooai/smooth-operator-core/executor';
20
+ import { outputToModelResponse } from './dto.js';
21
+ import { approveToolSignal, denyToolSignal } from './signals.js';
22
+ /**
23
+ * Activity stubs. 60s start-to-close mirrors the Rust crate's
24
+ * `agent_activity_opts`; the SDK's default retry policy makes transient model /
25
+ * tool failures durable retries rather than turn failures.
26
+ */
27
+ const { modelCall, toolInvoke, healthEcho } = proxyActivities({
28
+ startToCloseTimeout: '60s',
29
+ });
30
+ /**
31
+ * Run one agent turn as a durable workflow. Seeds the conversation (system +
32
+ * history + user), then drives {@link driveTurn} over an activity-scheduling
33
+ * adapter, and returns the full conversation plus turn accounting.
34
+ *
35
+ * Durable human-in-the-loop and durable timers live in the adapter, not in
36
+ * `driveTurn`: an approval-gated tool blocks on {@link condition} until an
37
+ * `approveTool` / `denyTool` signal names its call id, and the configured wait
38
+ * tool sleeps on a durable {@link sleep} timer — both recorded in workflow
39
+ * history, so they survive worker restarts and can resolve arbitrarily later.
40
+ */
41
+ export async function agentTurnWorkflow(input) {
42
+ const approved = new Set();
43
+ const denied = new Set();
44
+ setHandler(approveToolSignal, (callId) => {
45
+ approved.add(callId);
46
+ });
47
+ setHandler(denyToolSignal, (callId) => {
48
+ denied.add(callId);
49
+ });
50
+ const messages = [];
51
+ if (input.systemPrompt)
52
+ messages.push({ role: 'system', content: input.systemPrompt });
53
+ if (input.history)
54
+ messages.push(...input.history);
55
+ messages.push({ role: 'user', content: input.userMessage });
56
+ let iterations = 0;
57
+ let toolCalls = 0;
58
+ const usage = { prompt_tokens: 0, completion_tokens: 0 };
59
+ const approvalRequired = input.approvalRequiredTools ?? [];
60
+ const activities = {
61
+ async modelCall(msgs, tools) {
62
+ iterations += 1;
63
+ const output = await modelCall({ messages: msgs, tools });
64
+ if (output.usage) {
65
+ usage.prompt_tokens += output.usage.prompt_tokens;
66
+ usage.completion_tokens += output.usage.completion_tokens;
67
+ }
68
+ return outputToModelResponse(output);
69
+ },
70
+ async toolInvoke(call) {
71
+ // Durable wait: the configured wait tool sleeps on a Temporal timer
72
+ // instead of dispatching an activity. Recorded in history, so the
73
+ // pause survives restarts and can span days.
74
+ if (input.waitTool !== undefined && call.name === input.waitTool) {
75
+ const raw = call.arguments.seconds;
76
+ const seconds = typeof raw === 'number' ? raw : Number(raw);
77
+ if (!Number.isInteger(seconds) || seconds < 0) {
78
+ return { toolCallId: call.id, content: `wait tool '${call.name}' requires an integer 'seconds' argument`, isError: true };
79
+ }
80
+ await sleep(seconds * 1000);
81
+ return { toolCallId: call.id, content: `Waited ${seconds}s (durable timer).`, isError: false };
82
+ }
83
+ // Durable human-in-the-loop gate: block until a signal names this call
84
+ // id. `driveTurn` is unchanged — the gate lives here.
85
+ if (approvalRequired.includes(call.name)) {
86
+ await condition(() => approved.has(call.id) || denied.has(call.id));
87
+ if (denied.has(call.id)) {
88
+ return { toolCallId: call.id, content: `Tool call '${call.name}' was denied by human approval.`, isError: true };
89
+ }
90
+ }
91
+ toolCalls += 1;
92
+ return toolInvoke({ call });
93
+ },
94
+ };
95
+ if (input.maxIterations !== undefined && input.maxIterations > 0) {
96
+ await driveTurn(activities, messages, input.tools ?? [], { maxIterations: input.maxIterations });
97
+ }
98
+ else {
99
+ // Omit the policy so the engine's own default iteration bound applies —
100
+ // never a magic number duplicated here.
101
+ await driveTurn(activities, messages, input.tools ?? []);
102
+ }
103
+ return { messages, iterations, toolCalls, usage };
104
+ }
105
+ /**
106
+ * Scaffold liveness workflow — proves the SDK integrates end to end and backs the
107
+ * health e2e. The TS sibling of the Rust crate's `HealthWorkflow`.
108
+ */
109
+ export async function healthWorkflow(message) {
110
+ return healthEcho(message);
111
+ }
112
+ //# sourceMappingURL=workflows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.js","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@smooai/smooth-operator-temporal",
3
+ "version": "1.13.5",
4
+ "description": "Optional Temporal-backed durable execution backend for the smooth-operator agent engine (ADR-030). The TypeScript sibling of the Rust `smooth-operator-temporal` crate — an agent turn runs as a Temporal workflow whose model call and tool invocations are activities, driving the same `driveTurn` loop as the in-process executor.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/SmooAI/smooth-operator-core.git",
10
+ "directory": "typescript/temporal"
11
+ },
12
+ "homepage": "https://github.com/SmooAI/smooth-operator-core/tree/main/typescript/temporal",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js"
19
+ },
20
+ "./workflows": {
21
+ "types": "./dist/workflows.d.ts",
22
+ "import": "./dist/workflows.js"
23
+ },
24
+ "./activities": {
25
+ "types": "./dist/activities.d.ts",
26
+ "import": "./dist/activities.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "dependencies": {
33
+ "@temporalio/activity": "^1.11.0",
34
+ "@temporalio/client": "^1.11.0",
35
+ "@temporalio/worker": "^1.11.0",
36
+ "@temporalio/workflow": "^1.11.0",
37
+ "@smooai/smooth-operator-core": "1.13.5"
38
+ },
39
+ "devDependencies": {
40
+ "@temporalio/testing": "^1.11.0",
41
+ "@types/node": "^25.9.2",
42
+ "typescript": "^5.7.3",
43
+ "vitest": "^2.1.8"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc -p tsconfig.build.json",
47
+ "typecheck": "tsc -p tsconfig.json --noEmit",
48
+ "test": "vitest run"
49
+ }
50
+ }