experimental-ash 0.22.2 → 0.24.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.
Files changed (172) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/docs/internals/hooks.md +13 -16
  3. package/dist/docs/internals/message-runtime.md +1 -1
  4. package/dist/docs/public/auth-and-route-protection.md +3 -3
  5. package/dist/docs/public/faqs.md +67 -0
  6. package/dist/docs/public/meta.json +1 -0
  7. package/dist/docs/public/schedules.md +11 -0
  8. package/dist/docs/public/session-context.md +46 -89
  9. package/dist/docs/public/skills.md +13 -0
  10. package/dist/docs/public/subagents.md +12 -6
  11. package/dist/docs/public/tools.md +9 -13
  12. package/dist/docs/public/typescript-api.md +4 -4
  13. package/dist/src/channel/types.d.ts +10 -12
  14. package/dist/src/chunks/{client-CKsU8Li3.js → client-nshDsWNF.js} +1 -1
  15. package/dist/src/chunks/{dev-authored-source-watcher-BLzYWh05.js → dev-authored-source-watcher-B4PaZGUr.js} +1 -1
  16. package/dist/src/chunks/host-DsW72Q-w.js +65 -0
  17. package/dist/src/chunks/paths-OknjaYR8.js +89 -0
  18. package/dist/src/chunks/prewarm-B4YblQ5m.js +6 -0
  19. package/dist/src/cli/commands/info.js +1 -1
  20. package/dist/src/cli/dev/repl.js +2 -2
  21. package/dist/src/cli/run.js +1 -1
  22. package/dist/src/client/session.js +8 -0
  23. package/dist/src/client/types.d.ts +12 -1
  24. package/dist/src/compiled/.vendor-stamp.json +3 -3
  25. package/dist/src/compiled/@workflow/core/_ms.d.ts +4 -0
  26. package/dist/src/compiled/@workflow/core/_workflow-serde.d.ts +5 -0
  27. package/dist/src/compiled/@workflow/core/_workflow-utils.d.ts +8 -0
  28. package/dist/src/compiled/@workflow/core/_workflow-world.d.ts +59 -0
  29. package/dist/src/compiled/@workflow/core/capabilities.d.ts +45 -0
  30. package/dist/src/compiled/@workflow/core/capture-stack.d.ts +16 -0
  31. package/dist/src/compiled/@workflow/core/class-serialization.d.ts +31 -0
  32. package/dist/src/compiled/@workflow/core/classify-error.d.ts +20 -0
  33. package/dist/src/compiled/@workflow/core/context-errors.d.ts +27 -0
  34. package/dist/src/compiled/@workflow/core/context-violation-error.d.ts +97 -0
  35. package/dist/src/compiled/@workflow/core/create-hook.d.ts +179 -0
  36. package/dist/src/compiled/@workflow/core/define-hook.d.ts +68 -0
  37. package/dist/src/compiled/@workflow/core/describe-error.d.ts +70 -0
  38. package/dist/src/compiled/@workflow/core/encryption.d.ts +51 -0
  39. package/dist/src/compiled/@workflow/core/events-consumer.d.ts +64 -0
  40. package/dist/src/compiled/@workflow/core/flushable-stream.d.ts +82 -0
  41. package/dist/src/compiled/@workflow/core/global.d.ts +48 -0
  42. package/dist/src/compiled/@workflow/core/index.d.ts +19 -38
  43. package/dist/src/compiled/@workflow/core/index.js +2 -2
  44. package/dist/src/compiled/@workflow/core/log-format.d.ts +25 -0
  45. package/dist/src/compiled/@workflow/core/logger.d.ts +29 -0
  46. package/dist/src/compiled/@workflow/core/package.json +1 -1
  47. package/dist/src/compiled/@workflow/core/private.d.ts +59 -10
  48. package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +51 -0
  49. package/dist/src/compiled/@workflow/core/runtime/get-port-lazy.d.ts +10 -0
  50. package/dist/src/compiled/@workflow/core/runtime/get-world-lazy.d.ts +32 -0
  51. package/dist/src/compiled/@workflow/core/runtime/helpers.d.ts +97 -0
  52. package/dist/src/compiled/@workflow/core/runtime/replay-budget.d.ts +98 -0
  53. package/dist/src/compiled/@workflow/core/runtime/resume-hook.d.ts +77 -0
  54. package/dist/src/compiled/@workflow/core/runtime/run.d.ts +134 -0
  55. package/dist/src/compiled/@workflow/core/runtime/runs.d.ts +50 -0
  56. package/dist/src/compiled/@workflow/core/runtime/start.d.ts +59 -0
  57. package/dist/src/compiled/@workflow/core/runtime/step-executor.d.ts +40 -0
  58. package/dist/src/compiled/@workflow/core/runtime/step-handler.d.ts +2 -0
  59. package/dist/src/compiled/@workflow/core/runtime/suspension-handler.d.ts +42 -0
  60. package/dist/src/compiled/@workflow/core/runtime/world-init.d.ts +75 -0
  61. package/dist/src/compiled/@workflow/core/runtime/world.d.ts +32 -0
  62. package/dist/src/compiled/@workflow/core/runtime.d.ts +22 -67
  63. package/dist/src/compiled/@workflow/core/runtime.js +27 -27
  64. package/dist/src/compiled/@workflow/core/schemas.d.ts +15 -0
  65. package/dist/src/compiled/@workflow/core/serialization/client.d.ts +17 -0
  66. package/dist/src/compiled/@workflow/core/serialization/codec-devalue.d.ts +14 -0
  67. package/dist/src/compiled/@workflow/core/serialization/codec.d.ts +90 -0
  68. package/dist/src/compiled/@workflow/core/serialization/encryption.d.ts +32 -0
  69. package/dist/src/compiled/@workflow/core/serialization/errors.d.ts +21 -0
  70. package/dist/src/compiled/@workflow/core/serialization/format.d.ts +60 -0
  71. package/dist/src/compiled/@workflow/core/serialization/index.d.ts +18 -0
  72. package/dist/src/compiled/@workflow/core/serialization/reducers/class.d.ts +11 -0
  73. package/dist/src/compiled/@workflow/core/serialization/reducers/common.d.ts +16 -0
  74. package/dist/src/compiled/@workflow/core/serialization/reducers/step-function.d.ts +35 -0
  75. package/dist/src/compiled/@workflow/core/serialization/step.d.ts +17 -0
  76. package/dist/src/compiled/@workflow/core/serialization/types.d.ts +215 -0
  77. package/dist/src/compiled/@workflow/core/serialization/workflow.d.ts +29 -0
  78. package/dist/src/compiled/@workflow/core/serialization-format.d.ts +171 -0
  79. package/dist/src/compiled/@workflow/core/serialization.d.ts +337 -0
  80. package/dist/src/compiled/@workflow/core/sleep.d.ts +33 -0
  81. package/dist/src/compiled/@workflow/core/source-map.d.ts +10 -0
  82. package/dist/src/compiled/@workflow/core/step/context-storage.d.ts +13 -0
  83. package/dist/src/compiled/@workflow/core/step/get-closure-vars.d.ts +9 -0
  84. package/dist/src/compiled/@workflow/core/step/get-step-metadata.d.ts +42 -0
  85. package/dist/src/compiled/@workflow/core/step/get-workflow-metadata.d.ts +7 -0
  86. package/dist/src/compiled/@workflow/core/step/writable-stream.d.ts +22 -0
  87. package/dist/src/compiled/@workflow/core/step.d.ts +4 -0
  88. package/dist/src/compiled/@workflow/core/symbols.d.ts +36 -0
  89. package/dist/src/compiled/@workflow/core/telemetry/semantic-conventions.d.ts +283 -0
  90. package/dist/src/compiled/@workflow/core/telemetry.d.ts +53 -0
  91. package/dist/src/compiled/@workflow/core/types.d.ts +14 -0
  92. package/dist/src/compiled/@workflow/core/util.d.ts +40 -0
  93. package/dist/src/compiled/@workflow/core/version.d.ts +2 -0
  94. package/dist/src/compiled/@workflow/core/vm/index.d.ts +17 -0
  95. package/dist/src/compiled/@workflow/core/vm/uint8array-base64.d.ts +21 -0
  96. package/dist/src/compiled/@workflow/core/vm/uuid.d.ts +10 -0
  97. package/dist/src/compiled/@workflow/core/workflow/abort-controller.d.ts +65 -0
  98. package/dist/src/compiled/@workflow/core/workflow/create-hook.d.ts +7 -0
  99. package/dist/src/compiled/@workflow/core/workflow/define-hook.d.ts +10 -0
  100. package/dist/src/compiled/@workflow/core/workflow/get-workflow-metadata.d.ts +32 -0
  101. package/dist/src/compiled/@workflow/core/workflow/hook.d.ts +4 -0
  102. package/dist/src/compiled/@workflow/core/workflow/index.d.ts +11 -0
  103. package/dist/src/compiled/@workflow/core/workflow/sleep.d.ts +4 -0
  104. package/dist/src/compiled/@workflow/core/workflow/world-init-stub.d.ts +15 -0
  105. package/dist/src/compiled/@workflow/core/workflow/writable-stream.d.ts +3 -0
  106. package/dist/src/compiled/@workflow/core/workflow.d.ts +1 -38
  107. package/dist/src/compiled/@workflow/core/workflow.js +1 -1
  108. package/dist/src/compiled/@workflow/errors/error-codes.d.ts +5 -1
  109. package/dist/src/compiled/@workflow/errors/index.d.ts +15 -1
  110. package/dist/src/compiled/@workflow/errors/index.js +1 -1
  111. package/dist/src/compiled/@workflow/errors/package.json +1 -1
  112. package/dist/src/compiled/_chunks/workflow/{context-errors-zbKocOyk.js → context-errors-Bbvvp-li.js} +2 -2
  113. package/dist/src/compiled/_chunks/workflow/{dist-0iNBqPYp.js → dist-C7wPwOI9.js} +2 -2
  114. package/dist/src/compiled/_chunks/workflow/{dist-D774SUM4.js → dist-C_oiE-l7.js} +1 -1
  115. package/dist/src/compiled/_chunks/workflow/resume-hook-C3VWUPii.js +12 -0
  116. package/dist/src/compiled/_chunks/workflow/sleep-QTkC1VFe.js +1 -0
  117. package/dist/src/compiled/_chunks/workflow/{symbols-D-4tVV8x.js → symbols-QezhMuLg.js} +1 -1
  118. package/dist/src/evals/cli/eval.js +1 -1
  119. package/dist/src/execution/await-authorization-orchestrator.d.ts +2 -1
  120. package/dist/src/execution/await-authorization-orchestrator.js +4 -0
  121. package/dist/src/execution/connection-auth-steps.d.ts +4 -0
  122. package/dist/src/execution/connection-auth-steps.js +9 -11
  123. package/dist/src/execution/node-step.d.ts +4 -5
  124. package/dist/src/execution/subagent-adapter.d.ts +0 -27
  125. package/dist/src/execution/subagent-adapter.js +2 -66
  126. package/dist/src/execution/subagent-hitl-proxy.d.ts +2 -2
  127. package/dist/src/execution/subagent-hitl-proxy.js +2 -2
  128. package/dist/src/execution/task-mode.d.ts +3 -3
  129. package/dist/src/execution/task-mode.js +3 -3
  130. package/dist/src/execution/turn-workflow.d.ts +41 -0
  131. package/dist/src/execution/turn-workflow.js +96 -0
  132. package/dist/src/execution/workflow-entry.js +77 -87
  133. package/dist/src/execution/workflow-errors.d.ts +14 -0
  134. package/dist/src/execution/workflow-errors.js +54 -0
  135. package/dist/src/execution/workflow-runtime.d.ts +34 -3
  136. package/dist/src/execution/workflow-runtime.js +52 -10
  137. package/dist/src/execution/workflow-steps.d.ts +27 -2
  138. package/dist/src/execution/workflow-steps.js +31 -26
  139. package/dist/src/harness/instrumentation-config.js +14 -7
  140. package/dist/src/harness/messages.d.ts +7 -7
  141. package/dist/src/harness/messages.js +4 -4
  142. package/dist/src/harness/runtime-actions.d.ts +4 -4
  143. package/dist/src/internal/application/package.js +1 -1
  144. package/dist/src/internal/workflow-bundle/workflow-builders.d.ts +1 -1
  145. package/dist/src/internal/workflow-bundle/workflow-builders.js +20 -8
  146. package/dist/src/internal/workflow-bundle/workflow-transformer.d.ts +13 -0
  147. package/dist/src/internal/workflow-bundle/workflow-transformer.js +10 -4
  148. package/dist/src/protocol/message.d.ts +6 -1
  149. package/dist/src/public/channels/ash.js +50 -3
  150. package/dist/src/public/context/index.d.ts +4 -7
  151. package/dist/src/public/context/index.js +4 -5
  152. package/dist/src/public/definitions/state.d.ts +33 -0
  153. package/dist/src/public/definitions/state.js +34 -0
  154. package/dist/src/public/next/index.d.ts +7 -0
  155. package/dist/src/public/next/index.js +2 -0
  156. package/dist/src/public/next/vercel-json.d.ts +1 -0
  157. package/dist/src/public/next/vercel-json.js +1 -0
  158. package/dist/src/react/index.d.ts +1 -1
  159. package/dist/src/react/use-ash-agent.d.ts +8 -0
  160. package/dist/src/react/use-ash-agent.js +26 -4
  161. package/dist/src/services/dev-client.d.ts +4 -1
  162. package/dist/src/services/dev-client.js +1 -0
  163. package/package.json +4 -4
  164. package/dist/src/chunks/host-DREC8e8Z.js +0 -65
  165. package/dist/src/chunks/paths-C6sp4T2U.js +0 -88
  166. package/dist/src/chunks/prewarm-hz8p2jlZ.js +0 -6
  167. package/dist/src/compiled/_chunks/workflow/resume-hook-CL8Ed91K.js +0 -12
  168. package/dist/src/compiled/_chunks/workflow/sleep-Dn3i9nxI.js +0 -1
  169. package/dist/src/execution/continuous-entry.d.ts +0 -59
  170. package/dist/src/execution/continuous-entry.js +0 -487
  171. package/dist/src/execution/continuous-runtime.d.ts +0 -17
  172. package/dist/src/execution/continuous-runtime.js +0 -123
@@ -2,11 +2,22 @@ import type { DeliverPayload, HookPayload, SessionAuthContext, SubagentInputRequ
2
2
  import { deserializeContext } from "#context/serialize.js";
3
3
  import type { HarnessSession } from "#harness/types.js";
4
4
  import type { RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
5
+ import { type TurnWorkflowInput } from "#execution/turn-workflow.js";
5
6
  import type { DurableStepResult } from "#execution/types.js";
7
+ /**
8
+ * Input for one atomic harness step inside a durable `"use step"`
9
+ * boundary.
10
+ */
11
+ export interface TurnStepInput {
12
+ readonly input: HookPayload | undefined;
13
+ readonly parentWritable: WritableStream<Uint8Array>;
14
+ readonly serializedContext: Record<string, unknown>;
15
+ readonly session: HarnessSession;
16
+ }
6
17
  /**
7
18
  * Runs one atomic harness step inside a durable `"use step"` boundary.
8
19
  */
9
- export declare function durableRunStep(serializedContext: Record<string, unknown>, session: HarnessSession, input: HookPayload | undefined): Promise<DurableStepResult>;
20
+ export declare function turnStep(input: TurnStepInput): Promise<DurableStepResult>;
10
21
  /**
11
22
  * Reconciles `session.continuationToken` with the live
12
23
  * `ContinuationTokenKey` value in context.
@@ -30,6 +41,7 @@ export declare function reconcileSessionContinuationToken(ctx: Awaited<ReturnTyp
30
41
  * runs independently on its own public child stream.
31
42
  */
32
43
  export declare function dispatchPendingRuntimeActionsStep(input: {
44
+ readonly parentWritable: WritableStream<Uint8Array>;
33
45
  readonly serializedContext: Record<string, unknown>;
34
46
  readonly session: HarnessSession;
35
47
  }): Promise<HarnessSession>;
@@ -38,8 +50,9 @@ export declare function dispatchPendingRuntimeActionsStep(input: {
38
50
  * adapter before the workflow run tears down.
39
51
  */
40
52
  export declare function emitTerminalSessionFailureStep(input: {
41
- readonly serializedContext: Record<string, unknown>;
42
53
  readonly error: unknown;
54
+ readonly parentWritable: WritableStream<Uint8Array>;
55
+ readonly serializedContext: Record<string, unknown>;
43
56
  }): Promise<void>;
44
57
  /**
45
58
  * Outcome of {@link runProxyInputRequestStep}, including the session
@@ -55,6 +68,7 @@ export interface RunProxyInputRequestResult {
55
68
  */
56
69
  export declare function runProxyInputRequestStep(input: {
57
70
  readonly hookPayload: SubagentInputRequestHookPayload;
71
+ readonly parentWritable: WritableStream<Uint8Array>;
58
72
  readonly serializedContext: Record<string, unknown>;
59
73
  readonly session: HarnessSession;
60
74
  }): Promise<RunProxyInputRequestResult>;
@@ -81,9 +95,20 @@ export interface RoutedProxiedDeliverResult {
81
95
  */
82
96
  export declare function routeProxiedDeliverStep(input: {
83
97
  readonly auth?: SessionAuthContext | null;
98
+ readonly parentWritable: WritableStream<Uint8Array>;
84
99
  readonly payload: DeliverPayload;
85
100
  readonly session: HarnessSession;
86
101
  }): Promise<RoutedProxiedDeliverResult>;
102
+ /**
103
+ * Starts a per-turn child workflow for the current driver session.
104
+ *
105
+ * `parentWritable` is forwarded to the child in the workflow input so
106
+ * the child's step writes land directly on the driver run's stream —
107
+ * no copier, no double-store.
108
+ */
109
+ export declare function dispatchTurnStep(input: TurnWorkflowInput): Promise<{
110
+ readonly runId: string;
111
+ }>;
87
112
  /**
88
113
  * Creates the durable session state inside a step boundary before the
89
114
  * workflow enters its long-lived turn loop.
@@ -1,4 +1,3 @@
1
- import { getWritable } from "#compiled/@workflow/core/index.js";
2
1
  import { buildAdapterContext } from "#channel/adapter-context.js";
3
2
  import { callAdapterEventHandler, defaultDeliverResult } from "#channel/adapter.js";
4
3
  import { toContextAccessor } from "#context/container.js";
@@ -19,20 +18,21 @@ import { createExecutionNodeStep } from "#execution/node-step.js";
19
18
  import { emitProxiedInputRequest, routeDeliverPayload } from "#execution/subagent-hitl-proxy.js";
20
19
  import { createSession, refreshSessionFromTurnAgent } from "#execution/session.js";
21
20
  import { buildSubagentRunInput } from "#execution/subagent-tool.js";
22
- import { createWorkflowRuntime, workflowEntryReference } from "#execution/workflow-runtime.js";
21
+ import { turnWorkflow } from "#execution/turn-workflow.js";
22
+ import { createWorkflowRuntime, startWorkflowPreferLatest, workflowEntryReference, } from "#execution/workflow-runtime.js";
23
23
  /**
24
24
  * Runs one atomic harness step inside a durable `"use step"` boundary.
25
25
  */
26
- export async function durableRunStep(serializedContext, session, input) {
26
+ export async function turnStep(input) {
27
27
  "use step";
28
- const ctx = await deserializeContext(serializedContext);
28
+ const ctx = await deserializeContext(input.serializedContext);
29
29
  const adapter = ctx.require(ChannelKey);
30
30
  // Apply deliver-time auth if present. The auth traveled through
31
31
  // resumeHook as JSON on the HookPayload. On the first turn (from
32
32
  // run()), input carries no auth because it was already seeded by
33
33
  // buildRunContext.
34
- if (input?.kind === "deliver" && input.auth !== undefined) {
35
- ctx.set(AuthKey, input.auth ?? null);
34
+ if (input.input?.kind === "deliver" && input.input.auth !== undefined) {
35
+ ctx.set(AuthKey, input.input.auth ?? null);
36
36
  }
37
37
  // Build the adapter context for deliver and event handlers.
38
38
  // Slack adapters override this to inject ctx.thread and ctx.slack.
@@ -41,9 +41,9 @@ export async function durableRunStep(serializedContext, session, input) {
41
41
  // custom context keys and optionally transforms the message.
42
42
  // Coalesces the resulting StepInput values.
43
43
  let resolved;
44
- if (input?.kind === "deliver") {
44
+ if (input.input?.kind === "deliver") {
45
45
  const results = [];
46
- for (const payload of input.payloads) {
46
+ for (const payload of input.input.payloads) {
47
47
  const result = adapter.deliver
48
48
  ? await adapter.deliver(payload, adapterCtx)
49
49
  : defaultDeliverResult(payload);
@@ -53,31 +53,27 @@ export async function durableRunStep(serializedContext, session, input) {
53
53
  }
54
54
  resolved = results.length === 0 ? undefined : results.reduce(coalesceTurnInputs);
55
55
  }
56
- else if (input?.kind === "runtime-action-result") {
57
- resolved = { runtimeActionResults: input.results };
56
+ else if (input.input?.kind === "runtime-action-result") {
57
+ resolved = { runtimeActionResults: input.input.results };
58
58
  }
59
59
  // Update adapter state on context after deliver mutations.
60
- if (input?.kind === "deliver") {
60
+ if (input.input?.kind === "deliver") {
61
61
  const updatedAdapter = { ...adapter, state: { ...adapterCtx.state } };
62
62
  ctx.set(ChannelKey, updatedAdapter);
63
63
  }
64
64
  // Adapter handled the delivery inline (e.g. a Slack interaction that
65
65
  // only edits a message). Persist any context mutations and re-park
66
66
  // without running a model turn.
67
- if (input?.kind === "deliver" && resolved === undefined) {
68
- const rekeyed = reconcileSessionContinuationToken(ctx, session);
67
+ if (input.input?.kind === "deliver" && resolved === undefined) {
68
+ const rekeyed = reconcileSessionContinuationToken(ctx, input.session);
69
69
  const nextSerializedContext = serializeContext(ctx);
70
- const writable = getWritable();
71
- const writer = writable.getWriter();
72
- writer.releaseLock();
73
70
  return {
74
71
  action: "park",
75
72
  serializedContext: nextSerializedContext,
76
73
  session: rekeyed,
77
74
  };
78
75
  }
79
- const writable = getWritable();
80
- const writer = writable.getWriter();
76
+ const writer = input.parentWritable.getWriter();
81
77
  const hookRegistry = ctx.require(BundleKey).hookRegistry;
82
78
  const emit = async (event) => {
83
79
  const toEmit = await callAdapterEventHandler(adapter, event, adapterCtx);
@@ -85,7 +81,7 @@ export async function durableRunStep(serializedContext, session, input) {
85
81
  await writer.write(encodeMessageStreamEvent(timestampHandleMessageStreamEvent(toEmit)));
86
82
  await dispatchStreamEventHooks({ ctx, registry: hookRegistry, event: toEmit });
87
83
  };
88
- let stepResult = await runStep(ctx, session, async (enrichedSession) => {
84
+ let stepResult = await runStep(ctx, input.session, async (enrichedSession) => {
89
85
  const bundle = ctx.require(BundleKey);
90
86
  const capabilities = ctx.get(CapabilitiesKey);
91
87
  const mode = ctx.require(ModeKey);
@@ -223,8 +219,7 @@ export async function dispatchPendingRuntimeActionsStep(input) {
223
219
  const auth = ctx.get(AuthKey) ?? null;
224
220
  const capabilities = ctx.get(CapabilitiesKey);
225
221
  const initiatorAuth = ctx.get(InitiatorAuthKey) ?? null;
226
- const writable = getWritable();
227
- const writer = writable.getWriter();
222
+ const writer = input.parentWritable.getWriter();
228
223
  const adapterCtx = buildAdapterContext(adapter, toContextAccessor(ctx));
229
224
  let nextSession = input.session;
230
225
  try {
@@ -308,8 +303,7 @@ export async function emitTerminalSessionFailureStep(input) {
308
303
  // consumers (client SDK readers, event-stream replays, dashboards)
309
304
  // see a canonical terminal event instead of an abrupt stream close.
310
305
  try {
311
- const writable = getWritable();
312
- const writer = writable.getWriter();
306
+ const writer = input.parentWritable.getWriter();
313
307
  try {
314
308
  await writer.write(encodeMessageStreamEvent(timestampHandleMessageStreamEvent(event)));
315
309
  }
@@ -335,8 +329,7 @@ export async function runProxyInputRequestStep(input) {
335
329
  const adapter = ctx.require(ChannelKey);
336
330
  const adapterCtx = buildAdapterContext(adapter, toContextAccessor(ctx));
337
331
  const mode = ctx.require(ModeKey);
338
- const writable = getWritable();
339
- const writer = writable.getWriter();
332
+ const writer = input.parentWritable.getWriter();
340
333
  let proxyResult;
341
334
  try {
342
335
  const emit = async (event) => {
@@ -355,7 +348,7 @@ export async function runProxyInputRequestStep(input) {
355
348
  }
356
349
  // Persist adapter-state mutations (e.g. Slack's `pendingRequests`
357
350
  // cache populated by the `input.requested` handler) so the next
358
- // `durableRunStep` observes them across the serialized context
351
+ // `turnStep` observes them across the serialized context
359
352
  // boundary. Without this the workflow runtime rehydrates a stale
360
353
  // adapter and later text-reply deliveries miss the cached batch.
361
354
  ctx.set(ChannelKey, { ...adapter, state: { ...adapterCtx.state } });
@@ -396,6 +389,18 @@ export async function routeProxiedDeliverStep(input) {
396
389
  }
397
390
  return { remainder: routed.forSelf };
398
391
  }
392
+ /**
393
+ * Starts a per-turn child workflow for the current driver session.
394
+ *
395
+ * `parentWritable` is forwarded to the child in the workflow input so
396
+ * the child's step writes land directly on the driver run's stream —
397
+ * no copier, no double-store.
398
+ */
399
+ export async function dispatchTurnStep(input) {
400
+ "use step";
401
+ const run = await startWorkflowPreferLatest(turnWorkflow, [input]);
402
+ return { runId: run.runId };
403
+ }
399
404
  /**
400
405
  * Creates the durable session state inside a step boundary before the
401
406
  * workflow enters its long-lived turn loop.
@@ -1,14 +1,21 @@
1
1
  /**
2
- * Module-level store for the authored instrumentation config.
2
+ * Process-global store for the authored instrumentation config.
3
3
  *
4
4
  * Populated at server startup by the generated Nitro instrumentation plugin
5
5
  * when the user's `agent/instrumentation.ts` has a default export produced
6
- * by `defineInstrumentation()`.
6
+ * by `defineInstrumentation()`. The harness reads from this at turn time
7
+ * to decide whether telemetry is enabled and which settings to pass to the
8
+ * AI SDK.
7
9
  *
8
- * The harness reads from this at turn time to decide whether telemetry is
9
- * enabled and which settings to pass to the AI SDK.
10
+ * Rooted on `globalThis` so the generated Nitro instrumentation plugin
11
+ * (which Nitro keeps external by `file://` URL) and the bundled harness
12
+ * chunk (which Nitro inlines via the package's `#harness/*` import alias)
13
+ * share one source of truth, even though they resolve to two distinct ESM
14
+ * module instances. See `context/key.ts` and
15
+ * `runtime/sessions/runtime-session.ts` for the established pattern.
10
16
  */
11
- let registeredConfig;
17
+ const INSTRUMENTATION_CONFIG_GLOBAL_KEY = Symbol.for("experimental-ash.harness-instrumentation-config");
18
+ const globalContainer = globalThis;
12
19
  /**
13
20
  * Registers the authored instrumentation config and invokes its `setup`
14
21
  * callback with the resolved agent name.
@@ -22,7 +29,7 @@ export function registerInstrumentationConfig(config, context) {
22
29
  if (config.setup !== undefined) {
23
30
  config.setup(context);
24
31
  }
25
- registeredConfig = config;
32
+ globalContainer[INSTRUMENTATION_CONFIG_GLOBAL_KEY] = config;
26
33
  }
27
34
  /**
28
35
  * Returns the registered instrumentation config, or `undefined` when no
@@ -31,5 +38,5 @@ export function registerInstrumentationConfig(config, context) {
31
38
  * @internal — not part of the public API.
32
39
  */
33
40
  export function getInstrumentationConfig() {
34
- return registeredConfig;
41
+ return globalContainer[INSTRUMENTATION_CONFIG_GLOBAL_KEY];
35
42
  }
@@ -19,9 +19,9 @@ export declare function coalesceTurnInputs(a: StepInput, b: StepInput): StepInpu
19
19
  */
20
20
  export declare function resolveAssistantStepText(messages: readonly ModelMessage[], fallback: string | undefined): string | null;
21
21
  /**
22
- * Structural shape shared by both the workflow `DeliverHookPayload` and the
23
- * continuous `DeliverQueueItem`. Using a structural type avoids coupling
24
- * this helper to either runtime's concrete type.
22
+ * Structural shape of the workflow `DeliverHookPayload`. Using a
23
+ * structural type keeps this helper decoupled from the concrete
24
+ * runtime type.
25
25
  */
26
26
  interface DeliverLike {
27
27
  readonly auth?: SessionAuthContext | null;
@@ -32,10 +32,10 @@ interface DeliverLike {
32
32
  * Coalesces an array of deliver-like items into a single item by
33
33
  * collecting all payloads and keeping the most recent auth value.
34
34
  *
35
- * Used by both the workflow and continuous runtimes to batch follow-up
36
- * deliveries that arrived while a turn or subagent delegation was in
37
- * progress. Each payload is later passed to `onDeliver` individually so
38
- * channel-specific fields are never lost.
35
+ * Used by the workflow runtime to batch follow-up deliveries that
36
+ * arrived while a turn or subagent delegation was in progress. Each
37
+ * payload is later passed to `onDeliver` individually so channel-
38
+ * specific fields are never lost.
39
39
  */
40
40
  export declare function coalesceDeliveries<T extends DeliverLike>(items: readonly T[]): T;
41
41
  export {};
@@ -122,10 +122,10 @@ function toUserContentArray(value) {
122
122
  * Coalesces an array of deliver-like items into a single item by
123
123
  * collecting all payloads and keeping the most recent auth value.
124
124
  *
125
- * Used by both the workflow and continuous runtimes to batch follow-up
126
- * deliveries that arrived while a turn or subagent delegation was in
127
- * progress. Each payload is later passed to `onDeliver` individually so
128
- * channel-specific fields are never lost.
125
+ * Used by the workflow runtime to batch follow-up deliveries that
126
+ * arrived while a turn or subagent delegation was in progress. Each
127
+ * payload is later passed to `onDeliver` individually so channel-
128
+ * specific fields are never lost.
129
129
  */
130
130
  export function coalesceDeliveries(items) {
131
131
  const [first, ...rest] = items;
@@ -67,10 +67,10 @@ export declare function recordPendingSubagentChildToken(input: {
67
67
  /**
68
68
  * Discriminated item consumed by {@link accumulateRuntimeActionResults}.
69
69
  *
70
- * Both the workflow and continuous runtimes receive interleaved deliveries
71
- * and runtime-action results while waiting for pending subagent calls.
72
- * This union abstracts over `HookPayload` and `QueueItem` so the shared
73
- * accumulation loop is runtime-agnostic.
70
+ * The workflow runtime receives interleaved deliveries and runtime-action
71
+ * results while waiting for pending subagent calls; this union lets the
72
+ * accumulation loop process both kinds without coupling to the concrete
73
+ * `HookPayload` shape.
74
74
  */
75
75
  export type RuntimeActionAccumulatorItem<TDeliver> = {
76
76
  readonly kind: "deliver";
@@ -6,7 +6,7 @@ import { ASH_PACKAGE_NAME } from "#package-name.js";
6
6
  let cachedPackageInfo;
7
7
  // The package build stamps the published version into `dist` so bundled
8
8
  // deployments can still report package metadata without resolving package.json.
9
- const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.22.2";
9
+ const BUNDLED_FALLBACK_PACKAGE_VERSION = "0.24.0";
10
10
  const BUNDLED_FALLBACK_PACKAGE_VERSION_PLACEHOLDER = "__ASH_PACKAGE_VERSION_PLACEHOLDER__";
11
11
  const WORKFLOW_MODULE_ALIASES = {
12
12
  "workflow/api": "src/compiled/@workflow/core/runtime.js",
@@ -28,7 +28,7 @@ export declare const WORKFLOW_QUEUE_TRIGGER: {
28
28
  readonly retryAfterSeconds: 5;
29
29
  readonly initialDelaySeconds: 0;
30
30
  };
31
- export declare function applyWorkflowTransform(filename: string, source: string, mode: "workflow" | "step" | "client" | false, absolutePath?: string, projectRoot?: string): Promise<{
31
+ export declare function applyWorkflowTransform(filename: string, source: string, mode: "workflow" | "step" | "client" | false, absolutePath?: string, projectRoot?: string, stableWorkflowNames?: ReadonlySet<string>): Promise<{
32
32
  code: string;
33
33
  workflowManifest: WorkflowManifest;
34
34
  }>;
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { STABLE_WORKFLOW_NAMES } from "#execution/workflow-runtime.js";
4
5
  import { transformWorkflowDirectives } from "./workflow-transformer.js";
5
6
  export const WORKFLOW_QUEUE_TRIGGER = {
6
7
  type: "queue/v2beta",
@@ -11,15 +12,22 @@ export const WORKFLOW_QUEUE_TRIGGER = {
11
12
  };
12
13
  const packageJsonCache = new Map();
13
14
  const projectDepsCache = new Map();
14
- export async function applyWorkflowTransform(filename, source, mode, absolutePath, projectRoot) {
15
+ export async function applyWorkflowTransform(filename, source, mode, absolutePath, projectRoot, stableWorkflowNames = STABLE_WORKFLOW_NAMES) {
15
16
  const resolvedProjectRoot = projectRoot ?? process.cwd();
16
17
  const absoluteFilename = absolutePath === undefined
17
18
  ? isAbsolute(filename)
18
19
  ? filename
19
20
  : join(resolvedProjectRoot, filename)
20
21
  : absolutePath;
21
- const { moduleSpecifier } = resolveModuleSpecifier(absoluteFilename, resolvedProjectRoot);
22
- return transformWorkflowDirectives({ filename, mode, moduleSpecifier, source });
22
+ const { moduleSpecifier, stableModuleSpecifier } = resolveModuleSpecifier(absoluteFilename, resolvedProjectRoot);
23
+ return transformWorkflowDirectives({
24
+ filename,
25
+ mode,
26
+ moduleSpecifier,
27
+ source,
28
+ stableModuleSpecifier,
29
+ stableWorkflowNames,
30
+ });
23
31
  }
24
32
  export function detectWorkflowPatterns(source) {
25
33
  return {
@@ -58,17 +66,21 @@ function resolveModuleSpecifier(filePath, projectRoot) {
58
66
  const inNodeModules = isInNodeModules(filePath);
59
67
  const inWorkspace = !inNodeModules && isWorkspacePackage(filePath, projectRoot);
60
68
  if (!inNodeModules && !inWorkspace) {
61
- return { moduleSpecifier: undefined };
69
+ return { moduleSpecifier: undefined, stableModuleSpecifier: undefined };
62
70
  }
63
71
  const pkg = findPackageJson(filePath);
64
72
  if (pkg === null) {
65
- return { moduleSpecifier: undefined };
73
+ return { moduleSpecifier: undefined, stableModuleSpecifier: undefined };
66
74
  }
67
75
  const subpath = resolveExportSubpath(filePath, pkg);
76
+ // The default specifier is version-stamped for normal workflow/step
77
+ // ids; the stable variant drops the `@<pkg.version>` suffix so
78
+ // {@link STABLE_WORKFLOW_NAMES} functions emit cross-deployment
79
+ // routable ids.
80
+ const base = subpath ? `${pkg.name}${subpath}` : pkg.name;
68
81
  return {
69
- moduleSpecifier: subpath
70
- ? `${pkg.name}${subpath}@${pkg.version}`
71
- : `${pkg.name}@${pkg.version}`,
82
+ moduleSpecifier: `${base}@${pkg.version}`,
83
+ stableModuleSpecifier: base,
72
84
  };
73
85
  }
74
86
  function findPackageJson(filePath) {
@@ -5,6 +5,19 @@ export declare function transformWorkflowDirectives(input: {
5
5
  mode: WorkflowDirectiveMode;
6
6
  moduleSpecifier: string | undefined;
7
7
  source: string;
8
+ /**
9
+ * Package-qualified module specifier without the `@<pkg.version>`
10
+ * stamp. Used to mint workflow ids for functions named in
11
+ * {@link transformWorkflowDirectives.input.stableWorkflowNames} so
12
+ * the bundled id matches the runtime reference on every deployment.
13
+ */
14
+ stableModuleSpecifier?: string | undefined;
15
+ /**
16
+ * Workflow function names whose bundled id should be emitted without
17
+ * the package version stamp. See `STABLE_WORKFLOW_NAMES` in
18
+ * `workflow-runtime.ts` for the canonical set ash itself uses.
19
+ */
20
+ stableWorkflowNames?: ReadonlySet<string>;
8
21
  }): Promise<{
9
22
  code: string;
10
23
  workflowManifest: WorkflowManifest;
@@ -13,14 +13,19 @@ export async function transformWorkflowDirectives(input) {
13
13
  if (functions.length === 0) {
14
14
  return { code: input.source, workflowManifest: {} };
15
15
  }
16
- const idBase = input.moduleSpecifier ?? `./${stripJavaScriptExtension(input.filename)}`;
16
+ // Step ids stay version-stamped — they are per-deployment internal
17
+ // identifiers, not cross-deployment routing keys. Only workflow
18
+ // functions whose name appears in `stableWorkflowNames` opt out of
19
+ // the version stamp.
20
+ const defaultIdBase = input.moduleSpecifier ?? `./${stripJavaScriptExtension(input.filename)}`;
21
+ const stableIdBase = input.stableModuleSpecifier ?? defaultIdBase;
17
22
  const manifest = {};
18
23
  const replacements = [];
19
24
  const suffixes = [];
20
25
  let hasStepRegistration = false;
21
26
  for (const fn of functions) {
22
27
  if (fn.directive === "use step") {
23
- const stepId = createStepId(idBase, fn.name);
28
+ const stepId = createStepId(defaultIdBase, fn.name);
24
29
  manifest.steps ??= {};
25
30
  const stepsForFile = (manifest.steps[input.filename] ??= {});
26
31
  stepsForFile[fn.name] = { stepId };
@@ -44,7 +49,8 @@ export async function transformWorkflowDirectives(input) {
44
49
  }
45
50
  continue;
46
51
  }
47
- const workflowId = `workflow//${idBase}//${fn.name}`;
52
+ const isStable = input.stableWorkflowNames?.has(fn.name) === true;
53
+ const workflowId = `workflow//${isStable ? stableIdBase : defaultIdBase}//${fn.name}`;
48
54
  manifest.workflows ??= {};
49
55
  const workflowsForFile = (manifest.workflows[input.filename] ??= {});
50
56
  workflowsForFile[fn.name] = { workflowId };
@@ -66,7 +72,7 @@ export async function transformWorkflowDirectives(input) {
66
72
  const hasWorkflowDirective = functions.some((fn) => fn.directive === "use workflow");
67
73
  if (input.mode === "workflow" && !hasWorkflowDirective) {
68
74
  return {
69
- code: `${manifestComment}\n${createWorkflowStepProxySource(input.source, ast, functions, idBase)}`,
75
+ code: `${manifestComment}\n${createWorkflowStepProxySource(input.source, ast, functions, defaultIdBase)}`,
70
76
  workflowManifest: manifest,
71
77
  };
72
78
  }
@@ -76,20 +76,25 @@ export interface RuntimeIdentity {
76
76
  * `message` is either a plain text string or an AI SDK `UserContent`
77
77
  * array (mixing `text`, `image`, and `file` parts). Clients pass
78
78
  * multimodal attachments with the same shape AI SDK's `useChat`
79
- * `sendMessage({ files })` produces.
79
+ * `sendMessage({ files })` produces. `clientContext` is one-turn
80
+ * client/page context; the channel converts it into internal model context.
80
81
  */
81
82
  export type HandleMessageRequestBody = {
82
83
  readonly message: string | UserContent;
84
+ readonly clientContext?: string | readonly string[] | JsonObject;
83
85
  } | {
84
86
  readonly continuationToken: string;
85
87
  readonly message: string | UserContent;
88
+ readonly clientContext?: string | readonly string[] | JsonObject;
86
89
  } | {
87
90
  readonly continuationToken: string;
88
91
  readonly inputResponses: readonly InputResponse[];
92
+ readonly clientContext?: string | readonly string[] | JsonObject;
89
93
  } | {
90
94
  readonly continuationToken: string;
91
95
  readonly inputResponses: readonly InputResponse[];
92
96
  readonly message: string | UserContent;
97
+ readonly clientContext?: string | readonly string[] | JsonObject;
93
98
  };
94
99
  /**
95
100
  * JSON response returned when the message route accepts a start or resume
@@ -1,8 +1,10 @@
1
+ import {} from "ai";
1
2
  import { ASH_MESSAGE_STREAM_CONTENT_TYPE, ASH_MESSAGE_STREAM_FORMAT, ASH_MESSAGE_STREAM_VERSION, ASH_SESSION_ID_HEADER, ASH_STREAM_FORMAT_HEADER, ASH_STREAM_VERSION_HEADER, } from "#protocol/message.js";
2
3
  import { isInputResponse } from "#runtime/input/types.js";
3
4
  import { createUnauthorizedResponse } from "#public/channels/auth.js";
4
5
  import { collectUploadPolicyViolations, formatUploadPolicyViolation, mergeUploadPolicy, } from "#public/channels/upload-policy.js";
5
6
  import { defineChannel, POST, GET } from "#public/definitions/defineChannel.js";
7
+ import { parseJsonObject } from "#shared/json.js";
6
8
  export function ashChannel(input) {
7
9
  const uploadPolicy = mergeUploadPolicy(input.uploadPolicy);
8
10
  return defineChannel({
@@ -29,7 +31,7 @@ export function ashChannel(input) {
29
31
  if (policyRejection !== null)
30
32
  return policyRejection;
31
33
  const token = `ash:${crypto.randomUUID()}`;
32
- const session = await send(body.message, {
34
+ const session = await send(createSendPayload(body), {
33
35
  auth: sessionAuth,
34
36
  continuationToken: token,
35
37
  });
@@ -79,6 +81,7 @@ export function ashChannel(input) {
79
81
  const session = await send({
80
82
  inputResponses: body.inputResponses,
81
83
  message: body.message,
84
+ modelContext: body.modelContext,
82
85
  }, {
83
86
  auth: sessionAuth,
84
87
  continuationToken: body.continuationToken,
@@ -137,10 +140,13 @@ function parseCreateBody(payload) {
137
140
  const message = parseMessageField(payload.message);
138
141
  if (message instanceof Response)
139
142
  return message;
143
+ const modelContext = parseClientContextField(payload.clientContext);
144
+ if (modelContext instanceof Response)
145
+ return modelContext;
140
146
  if (message === undefined) {
141
147
  return Response.json({ error: "Missing or empty 'message' field.", ok: false }, { status: 400 });
142
148
  }
143
- return { message };
149
+ return { message, modelContext };
144
150
  }
145
151
  function parseContinueBody(payload) {
146
152
  const continuationToken = typeof payload.continuationToken === "string" && payload.continuationToken.length > 0
@@ -155,13 +161,22 @@ function parseContinueBody(payload) {
155
161
  const inputResponses = parseInputResponses(payload.inputResponses);
156
162
  if (inputResponses instanceof Response)
157
163
  return inputResponses;
164
+ const modelContext = parseClientContextField(payload.clientContext);
165
+ if (modelContext instanceof Response)
166
+ return modelContext;
158
167
  if (message === undefined && inputResponses === undefined) {
159
168
  return Response.json({
160
169
  error: "Expected a non-empty 'message', a non-empty 'inputResponses' array, or both.",
161
170
  ok: false,
162
171
  }, { status: 400 });
163
172
  }
164
- return { message, continuationToken, inputResponses };
173
+ return { message, continuationToken, inputResponses, modelContext };
174
+ }
175
+ function createSendPayload(body) {
176
+ if (body.modelContext === undefined) {
177
+ return body.message;
178
+ }
179
+ return { message: body.message, modelContext: body.modelContext };
165
180
  }
166
181
  function parseMessageField(value) {
167
182
  if (value === undefined)
@@ -255,6 +270,38 @@ function parseInputResponses(value) {
255
270
  }
256
271
  return inputResponses;
257
272
  }
273
+ const CLIENT_CONTEXT_PREFIX = "Ephemeral client context:\n";
274
+ function parseClientContextField(value) {
275
+ if (value === undefined)
276
+ return undefined;
277
+ if (typeof value === "string") {
278
+ return value.length > 0 ? [toClientContextMessage(value)] : undefined;
279
+ }
280
+ if (Array.isArray(value)) {
281
+ if (value.length === 0)
282
+ return undefined;
283
+ if (!value.every((entry) => typeof entry === "string" && entry.length > 0)) {
284
+ return Response.json({ error: "Expected 'clientContext' array entries to be non-empty strings.", ok: false }, { status: 400 });
285
+ }
286
+ return value.map((entry) => toClientContextMessage(entry));
287
+ }
288
+ if (value === null || typeof value !== "object") {
289
+ return Response.json({
290
+ error: "Expected 'clientContext' to be a string, string array, or JSON object.",
291
+ ok: false,
292
+ }, { status: 400 });
293
+ }
294
+ try {
295
+ const json = parseJsonObject(value);
296
+ return [toClientContextMessage(JSON.stringify(json))];
297
+ }
298
+ catch {
299
+ return Response.json({ error: "Expected 'clientContext' to be a JSON-serializable object.", ok: false }, { status: 400 });
300
+ }
301
+ }
302
+ function toClientContextMessage(content) {
303
+ return { role: "user", content: `${CLIENT_CONTEXT_PREFIX}${content}` };
304
+ }
258
305
  function parseStartIndex(request) {
259
306
  const raw = new URL(request.url).searchParams.get("startIndex");
260
307
  if (raw === null)
@@ -2,15 +2,12 @@
2
2
  * Runtime context helpers for authored code.
3
3
  *
4
4
  * These APIs work only inside active authored runtime execution such as
5
- * tools and other Ash-invoked callbacks. They read from a single
6
- * `AshContext` container bound via `AsyncLocalStorage`.
5
+ * tools and other Ash-invoked callbacks.
7
6
  *
8
7
  * @example
9
8
  * ```ts
10
- * import { ContextKey, getContext, setContext } from "experimental-ash/context";
9
+ * import { defineState, getSession } from "experimental-ash/context";
11
10
  * ```
12
11
  */
13
- export { ensureContext, getContext, getSession, hasContext, requireContext, type Session, type SessionAuth, type SessionAuthContext, type SessionParent, type SessionTurn, setContext, } from "#context/accessors.js";
14
- export type { AshContext } from "#context/container.js";
15
- export { type ContextAccessor, ContextKey, type ContextKeyOptions } from "#context/key.js";
16
- export type { ContextProvider, ContextReader } from "#context/provider.js";
12
+ export { getSession, type Session, type SessionAuth, type SessionAuthContext, type SessionParent, type SessionTurn, } from "#context/accessors.js";
13
+ export { defineState, type StateHandle } from "#public/definitions/state.js";
@@ -2,13 +2,12 @@
2
2
  * Runtime context helpers for authored code.
3
3
  *
4
4
  * These APIs work only inside active authored runtime execution such as
5
- * tools and other Ash-invoked callbacks. They read from a single
6
- * `AshContext` container bound via `AsyncLocalStorage`.
5
+ * tools and other Ash-invoked callbacks.
7
6
  *
8
7
  * @example
9
8
  * ```ts
10
- * import { ContextKey, getContext, setContext } from "experimental-ash/context";
9
+ * import { defineState, getSession } from "experimental-ash/context";
11
10
  * ```
12
11
  */
13
- export { ensureContext, getContext, getSession, hasContext, requireContext, setContext, } from "#context/accessors.js";
14
- export { ContextKey } from "#context/key.js";
12
+ export { getSession, } from "#context/accessors.js";
13
+ export { defineState } from "#public/definitions/state.js";