@ekairos/story 1.21.58-beta.0 → 1.21.59-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { story, createStory, type StoryConfig, type StoryInstance, type StoryOptions, type StoryStreamOptions, Story, type RegistrableStoryBuilder, } from "./story.js";
2
2
  export type { StoryStore, ContextIdentifier, StoredContext, ContextEvent, } from "./story.store.js";
3
+ export type { WireDate, StoryMirrorContext, StoryMirrorExecution, StoryMirrorWrite, StoryMirrorRequest, } from "./mirror.js";
3
4
  export { registerStory, getStory, getStoryFactory, hasStory, listStories, type StoryKey, } from "./story.registry.js";
4
5
  export { storyDomain } from "./schema.js";
5
6
  export { didToolExecute } from "./story.toolcalls.js";
@@ -0,0 +1,41 @@
1
+ import type { ContextEvent, StoredContext } from "./story.store";
2
+ /**
3
+ * Wire-safe (JSON) mirror types shared by:
4
+ * - the workflow sender (`@ekairos/story` steps)
5
+ * - the ekairos-core receiver (`/api/story`)
6
+ *
7
+ * Note: `StoredContext` contains Date objects, but over HTTP we send ISO strings.
8
+ */
9
+ export type WireDate = string;
10
+ export type StoryMirrorContext = Omit<StoredContext<unknown>, "createdAt" | "updatedAt"> & {
11
+ createdAt: WireDate;
12
+ updatedAt?: WireDate;
13
+ };
14
+ export type StoryMirrorExecution = Record<string, unknown> & {
15
+ createdAt?: WireDate;
16
+ updatedAt?: WireDate;
17
+ };
18
+ export type StoryMirrorWrite = {
19
+ type: "context.upsert";
20
+ context: StoryMirrorContext;
21
+ } | {
22
+ type: "event.upsert";
23
+ contextId: string;
24
+ event: ContextEvent;
25
+ } | {
26
+ type: "event.update";
27
+ eventId: string;
28
+ event: ContextEvent;
29
+ } | {
30
+ type: "execution.upsert";
31
+ contextId: string;
32
+ executionId: string;
33
+ execution: StoryMirrorExecution;
34
+ triggerEventId: string;
35
+ reactionEventId: string;
36
+ setCurrentExecution?: boolean;
37
+ };
38
+ export type StoryMirrorRequest = {
39
+ orgId: string;
40
+ writes: StoryMirrorWrite[];
41
+ };
package/dist/mirror.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { StoryEnvironment } from "../story.config";
2
+ import type { StoryMirrorWrite } from "../mirror";
3
+ export declare function mirrorStoryWrites(params: {
4
+ env: StoryEnvironment;
5
+ writes: StoryMirrorWrite[];
6
+ }): Promise<void>;
@@ -0,0 +1,44 @@
1
+ function requireOrgId(env) {
2
+ const orgId = env?.orgId;
3
+ if (typeof orgId !== "string" || !orgId) {
4
+ throw new Error("[story/mirror] Missing env.orgId");
5
+ }
6
+ return orgId;
7
+ }
8
+ function requireBaseUrl() {
9
+ const baseUrl = process.env.EKAIROS_CORE_BASE_URL ||
10
+ process.env.EKAIROS_MIRROR_BASE_URL ||
11
+ process.env.EKAIROS_BASE_URL;
12
+ if (!baseUrl) {
13
+ throw new Error("[story/mirror] Missing EKAIROS_CORE_BASE_URL (or EKAIROS_MIRROR_BASE_URL)");
14
+ }
15
+ return baseUrl.replace(/\/$/, "");
16
+ }
17
+ function requireToken() {
18
+ const token = process.env.EKAIROS_STORY_MIRROR_TOKEN;
19
+ if (!token) {
20
+ throw new Error("[story/mirror] Missing EKAIROS_STORY_MIRROR_TOKEN");
21
+ }
22
+ return token;
23
+ }
24
+ export async function mirrorStoryWrites(params) {
25
+ "use step";
26
+ if (!params.writes?.length)
27
+ return;
28
+ const orgId = requireOrgId(params.env);
29
+ const baseUrl = requireBaseUrl();
30
+ const token = requireToken();
31
+ const body = { orgId, writes: params.writes };
32
+ const res = await fetch(`${baseUrl}/api/story`, {
33
+ method: "POST",
34
+ headers: {
35
+ "content-type": "application/json",
36
+ authorization: `Bearer ${token}`,
37
+ },
38
+ body: JSON.stringify(body),
39
+ });
40
+ if (!res.ok) {
41
+ const text = await res.text().catch(() => "");
42
+ throw new Error(`[story/mirror] ekairos-core write failed (${res.status}): ${text}`);
43
+ }
44
+ }
@@ -1,4 +1,4 @@
1
- import type { ModelMessage } from "ai";
1
+ import type { ModelMessage, UIMessageChunk } from "ai";
2
2
  import type { StoryEnvironment } from "../story.config";
3
3
  import type { ContextEvent, ContextIdentifier } from "../story.store";
4
4
  import type { SerializableToolForModel } from "../tools-to-model-tools";
@@ -19,6 +19,7 @@ export declare function executeReaction(params: {
19
19
  maxSteps: number;
20
20
  sendStart?: boolean;
21
21
  silent?: boolean;
22
+ writable?: WritableStream<UIMessageChunk>;
22
23
  }): Promise<{
23
24
  assistantEvent: ContextEvent;
24
25
  toolCalls: any[];
@@ -60,9 +60,9 @@ export async function executeReaction(params) {
60
60
  console.error("[ekairos/story] reaction.step store.eventsToModelMessages failed", safeErrorJson(error));
61
61
  throw error;
62
62
  }
63
- const writable = params.silent
63
+ const writable = params.silent || !params.writable
64
64
  ? new WritableStream({ write() { } })
65
- : (await import("workflow")).getWritable();
65
+ : params.writable;
66
66
  const { jsonSchema, gateway, smoothStream, stepCountIs, streamText } = await import("ai");
67
67
  const { extractToolCallsFromParts } = await import("../story.toolcalls");
68
68
  // Match DurableAgent-style model init behavior:
@@ -1,3 +1,4 @@
1
+ import type { UIMessageChunk } from "ai";
1
2
  import type { StoryEnvironment } from "../story.config";
2
3
  import type { ContextEvent, ContextIdentifier, StoredContext } from "../story.store";
3
4
  /**
@@ -7,6 +8,7 @@ import type { ContextEvent, ContextIdentifier, StoredContext } from "../story.st
7
8
  */
8
9
  export declare function initializeContext<C>(env: StoryEnvironment, contextIdentifier: ContextIdentifier | null, opts?: {
9
10
  silent?: boolean;
11
+ writable?: WritableStream<UIMessageChunk>;
10
12
  }): Promise<{
11
13
  context: StoredContext<C>;
12
14
  isNew: boolean;
@@ -30,10 +30,8 @@ export async function initializeContext(env, contextIdentifier, opts) {
30
30
  console.log("[ekairos/story] story.engine initializeContext isNew", result.isNew);
31
31
  // If we're running in a non-streaming context (e.g. tests or headless usage),
32
32
  // we skip writing stream chunks entirely.
33
- if (!opts?.silent) {
34
- const { getWritable } = await import("workflow");
35
- const writable = getWritable();
36
- const writer = writable.getWriter();
33
+ if (!opts?.silent && opts?.writable) {
34
+ const writer = opts.writable.getWriter();
37
35
  try {
38
36
  await writer.write({
39
37
  type: "data-context-id",
@@ -1,3 +1,4 @@
1
+ import type { UIMessageChunk } from "ai";
1
2
  export declare function writeContextSubstate(params: {
2
3
  /**
3
4
  * Ephemeral substate key for the UI (story engine internal state).
@@ -7,9 +8,11 @@ export declare function writeContextSubstate(params: {
7
8
  */
8
9
  key: string | null;
9
10
  transient?: boolean;
11
+ writable?: WritableStream<UIMessageChunk>;
10
12
  }): Promise<void>;
11
13
  export declare function writeContextIdChunk(params: {
12
14
  contextId: string;
15
+ writable?: WritableStream<UIMessageChunk>;
13
16
  }): Promise<void>;
14
17
  export declare function writeStoryPing(params: {
15
18
  /**
@@ -17,6 +20,7 @@ export declare function writeStoryPing(params: {
17
20
  * This is intentionally generic so clients can ignore it safely.
18
21
  */
19
22
  label?: string;
23
+ writable?: WritableStream<UIMessageChunk>;
20
24
  }): Promise<void>;
21
25
  export declare function writeToolOutputs(params: {
22
26
  results: Array<{
@@ -28,8 +32,10 @@ export declare function writeToolOutputs(params: {
28
32
  success: false;
29
33
  errorText: string;
30
34
  }>;
35
+ writable?: WritableStream<UIMessageChunk>;
31
36
  }): Promise<void>;
32
37
  export declare function closeStoryStream(params: {
33
38
  preventClose?: boolean;
34
39
  sendFinish?: boolean;
40
+ writable?: WritableStream<UIMessageChunk>;
35
41
  }): Promise<void>;
@@ -1,7 +1,8 @@
1
1
  export async function writeContextSubstate(params) {
2
2
  "use step";
3
- const { getWritable } = await import("workflow");
4
- const writable = getWritable();
3
+ const writable = params.writable;
4
+ if (!writable)
5
+ return;
5
6
  const writer = writable.getWriter();
6
7
  try {
7
8
  await writer.write({
@@ -16,8 +17,9 @@ export async function writeContextSubstate(params) {
16
17
  }
17
18
  export async function writeContextIdChunk(params) {
18
19
  "use step";
19
- const { getWritable } = await import("workflow");
20
- const writable = getWritable();
20
+ const writable = params.writable;
21
+ if (!writable)
22
+ return;
21
23
  const writer = writable.getWriter();
22
24
  try {
23
25
  await writer.write({
@@ -32,8 +34,9 @@ export async function writeContextIdChunk(params) {
32
34
  }
33
35
  export async function writeStoryPing(params) {
34
36
  "use step";
35
- const { getWritable } = await import("workflow");
36
- const writable = getWritable();
37
+ const writable = params.writable;
38
+ if (!writable)
39
+ return;
37
40
  const writer = writable.getWriter();
38
41
  try {
39
42
  await writer.write({
@@ -48,8 +51,9 @@ export async function writeStoryPing(params) {
48
51
  }
49
52
  export async function writeToolOutputs(params) {
50
53
  "use step";
51
- const { getWritable } = await import("workflow");
52
- const writable = getWritable();
54
+ const writable = params.writable;
55
+ if (!writable)
56
+ return;
53
57
  const writer = writable.getWriter();
54
58
  try {
55
59
  for (const r of params.results) {
@@ -77,8 +81,9 @@ export async function closeStoryStream(params) {
77
81
  "use step";
78
82
  const sendFinish = params.sendFinish ?? true;
79
83
  const preventClose = params.preventClose ?? false;
80
- const { getWritable } = await import("workflow");
81
- const writable = getWritable();
84
+ const writable = params.writable;
85
+ if (!writable)
86
+ return;
82
87
  if (sendFinish) {
83
88
  const writer = writable.getWriter();
84
89
  try {
@@ -1,6 +1,5 @@
1
- import type { Tool } from "ai";
2
1
  import type { StoryEnvironment } from "./story.config.js";
3
- import { Story, type StoryModelInit, type StoryOptions, type ShouldContinue, type StoryShouldContinueArgs, type StoryReactParams } from "./story.engine.js";
2
+ import { Story, type StoryModelInit, type StoryOptions, type StoryTool, type ShouldContinue, type StoryShouldContinueArgs, type StoryReactParams } from "./story.engine.js";
4
3
  import type { ContextEvent, StoredContext } from "./story.store.js";
5
4
  import { type StoryKey } from "./story.registry.js";
6
5
  export interface StoryConfig<Context, Env extends StoryEnvironment = StoryEnvironment> {
@@ -27,11 +26,11 @@ export interface StoryConfig<Context, Env extends StoryEnvironment = StoryEnviro
27
26
  /**
28
27
  * Actions available to the model (aka "tools" in AI SDK terminology).
29
28
  */
30
- actions: (context: StoredContext<Context>, env: Env) => Promise<Record<string, Tool>> | Record<string, Tool>;
29
+ actions: (context: StoredContext<Context>, env: Env) => Promise<Record<string, StoryTool>> | Record<string, StoryTool>;
31
30
  /**
32
31
  * @deprecated Use `actions()` instead.
33
32
  */
34
- tools?: (context: StoredContext<Context>, env: Env) => Promise<Record<string, Tool>> | Record<string, Tool>;
33
+ tools?: (context: StoredContext<Context>, env: Env) => Promise<Record<string, StoryTool>> | Record<string, StoryTool>;
35
34
  /**
36
35
  * Model configuration (DurableAgent-style).
37
36
  *
@@ -59,7 +58,7 @@ export declare function story<Context, Env extends StoryEnvironment = StoryEnvir
59
58
  type AnyContextInitializer<Env extends StoryEnvironment> = (context: StoredContext<any>, env: Env) => Promise<any> | any;
60
59
  type InferContextFromInitializer<I extends AnyContextInitializer<any>> = Awaited<ReturnType<I>>;
61
60
  type BuilderSystemPrompt<Context, Env extends StoryEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<string> | string;
62
- type BuilderTools<Context, Env extends StoryEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<Record<string, Tool>> | Record<string, Tool>;
61
+ type BuilderTools<Context, Env extends StoryEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<Record<string, StoryTool>> | Record<string, StoryTool>;
63
62
  type BuilderExpandEvents<Context, Env extends StoryEnvironment> = (events: ContextEvent[], context: StoredContext<Context>, env: Env) => Promise<ContextEvent[]> | ContextEvent[];
64
63
  type BuilderShouldContinue<Context, Env extends StoryEnvironment> = (args: StoryShouldContinueArgs<Context, Env>) => Promise<ShouldContinue> | ShouldContinue;
65
64
  type BuilderModel<Context, Env extends StoryEnvironment> = StoryModelInit | ((context: StoredContext<Context>, env: Env) => StoryModelInit);
@@ -1,4 +1,4 @@
1
- import type { Tool } from "ai";
1
+ import type { Tool, UIMessageChunk } from "ai";
2
2
  import type { StoryEnvironment } from "./story.config.js";
3
3
  import type { ContextEvent, ContextIdentifier, StoredContext } from "./story.store.js";
4
4
  export interface StoryOptions<Context = any, Env extends StoryEnvironment = StoryEnvironment> {
@@ -47,6 +47,16 @@ export interface StoryStreamOptions {
47
47
  * Default: false.
48
48
  */
49
49
  silent?: boolean;
50
+ /**
51
+ * Optional workflow writable stream to emit UIMessageChunks into.
52
+ *
53
+ * When omitted, the story will run without emitting stream chunks (equivalent to "headless"
54
+ * execution for output purposes).
55
+ *
56
+ * IMPORTANT: We intentionally avoid calling `getWritable()` inside steps; callers can pass a
57
+ * writable from a `"use workflow"` function if they need streaming.
58
+ */
59
+ writable?: WritableStream<UIMessageChunk>;
50
60
  }
51
61
  /**
52
62
  * Model initializer (DurableAgent-style).
@@ -65,6 +75,58 @@ export type StoryReactParams<Env extends StoryEnvironment = StoryEnvironment> =
65
75
  context?: ContextIdentifier | null;
66
76
  options?: StoryStreamOptions;
67
77
  };
78
+ /**
79
+ * Payload expected to resume an auto=false tool execution.
80
+ *
81
+ * This must be serializable because it crosses the workflow hook boundary.
82
+ *
83
+ * See: https://useworkflow.dev/docs/foundations/hooks
84
+ */
85
+ export type StoryToolApprovalPayload = {
86
+ approved: true;
87
+ comment?: string;
88
+ args?: Record<string, unknown>;
89
+ } | {
90
+ approved: false;
91
+ comment?: string;
92
+ };
93
+ /**
94
+ * Deterministic hook token for approving an `auto: false` tool call.
95
+ *
96
+ * External systems can resume the hook with:
97
+ * `resumeHook(toolApprovalHookToken({ executionId, toolCallId }), { approved: true })`
98
+ */
99
+ export declare function toolApprovalHookToken(params: {
100
+ executionId: string;
101
+ toolCallId: string;
102
+ }): string;
103
+ /**
104
+ * Deterministic webhook token for approving an `auto: false` tool call.
105
+ *
106
+ * When using Workflow DevKit, the webhook is available at:
107
+ * `/.well-known/workflow/v1/webhook/:token`
108
+ *
109
+ * See: https://useworkflow.dev/docs/foundations/hooks
110
+ */
111
+ export declare function toolApprovalWebhookToken(params: {
112
+ executionId: string;
113
+ toolCallId: string;
114
+ }): string;
115
+ /**
116
+ * Story-level tool type.
117
+ *
118
+ * Allows stories to attach metadata to actions/tools (e.g. `{ auto: false }`)
119
+ * while remaining compatible with the AI SDK `Tool` runtime shape.
120
+ *
121
+ * Default behavior when omitted: `auto === true`.
122
+ */
123
+ export type StoryTool = Tool & {
124
+ /**
125
+ * If `false`, this action is not intended for automatic execution by the engine.
126
+ * (Validation/enforcement can be added by callers; default is `true`.)
127
+ */
128
+ auto?: boolean;
129
+ };
68
130
  /**
69
131
  * ## Story loop continuation signal
70
132
  *
@@ -103,7 +165,7 @@ export declare abstract class Story<Context, Env extends StoryEnvironment = Stor
103
165
  constructor(opts?: StoryOptions<Context, Env>);
104
166
  protected abstract initialize(context: StoredContext<Context>, env: Env): Promise<Context> | Context;
105
167
  protected abstract buildSystemPrompt(context: StoredContext<Context>, env: Env): Promise<string> | string;
106
- protected abstract buildTools(context: StoredContext<Context>, env: Env): Promise<Record<string, Tool>> | Record<string, Tool>;
168
+ protected abstract buildTools(context: StoredContext<Context>, env: Env): Promise<Record<string, StoryTool>> | Record<string, StoryTool>;
107
169
  /**
108
170
  * First-class event expansion stage (runs on every iteration of the durable loop).
109
171
  *
@@ -3,6 +3,26 @@ import { executeReaction } from "./steps/reaction.steps.js";
3
3
  import { toolsToModelTools } from "./tools-to-model-tools.js";
4
4
  import { closeStoryStream, writeContextSubstate, writeStoryPing, writeToolOutputs } from "./steps/stream.steps.js";
5
5
  import { completeExecution, createReactionEvent, initializeContext, saveReactionEvent, saveTriggerEvent, updateContextContent, updateContextStatus, updateEvent, } from "./steps/store.steps.js";
6
+ /**
7
+ * Deterministic hook token for approving an `auto: false` tool call.
8
+ *
9
+ * External systems can resume the hook with:
10
+ * `resumeHook(toolApprovalHookToken({ executionId, toolCallId }), { approved: true })`
11
+ */
12
+ export function toolApprovalHookToken(params) {
13
+ return `ekairos_story:tool-approval:${params.executionId}:${params.toolCallId}`;
14
+ }
15
+ /**
16
+ * Deterministic webhook token for approving an `auto: false` tool call.
17
+ *
18
+ * When using Workflow DevKit, the webhook is available at:
19
+ * `/.well-known/workflow/v1/webhook/:token`
20
+ *
21
+ * See: https://useworkflow.dev/docs/foundations/hooks
22
+ */
23
+ export function toolApprovalWebhookToken(params) {
24
+ return `ekairos_story:tool-approval-webhook:${params.executionId}:${params.toolCallId}`;
25
+ }
6
26
  export class Story {
7
27
  constructor(opts = {}) {
8
28
  this.opts = opts;
@@ -59,8 +79,9 @@ export class Story {
59
79
  const preventClose = params.options?.preventClose ?? false;
60
80
  const sendFinish = params.options?.sendFinish ?? true;
61
81
  const silent = params.options?.silent ?? false;
82
+ const writable = params.options?.writable;
62
83
  // 1) Ensure context exists (step)
63
- const ctxResult = await initializeContext(params.env, params.contextIdentifier, { silent });
84
+ const ctxResult = await initializeContext(params.env, params.contextIdentifier, { silent, writable });
64
85
  const currentContext = ctxResult.context;
65
86
  const contextSelector = params.contextIdentifier?.id
66
87
  ? { id: String(params.contextIdentifier.id) }
@@ -81,7 +102,7 @@ export class Story {
81
102
  // Emit a simple ping chunk early so clients can validate that streaming works end-to-end.
82
103
  // This should be ignored safely by clients that don't care about it.
83
104
  if (!silent) {
84
- await writeStoryPing({ label: "story-start" });
105
+ await writeStoryPing({ label: "story-start", writable });
85
106
  }
86
107
  let reactionEvent = null;
87
108
  // Latest persisted context state for this run (we keep it in memory; store is updated via steps).
@@ -95,7 +116,7 @@ export class Story {
95
116
  }
96
117
  try {
97
118
  if (!silent) {
98
- await closeStoryStream({ preventClose, sendFinish });
119
+ await closeStoryStream({ preventClose, sendFinish, writable });
99
120
  }
100
121
  }
101
122
  catch {
@@ -126,6 +147,7 @@ export class Story {
126
147
  // Only emit a `start` chunk once per story turn.
127
148
  sendStart: !silent && iter === 0 && reactionEvent === null,
128
149
  silent,
150
+ writable,
129
151
  });
130
152
  // Persist/append the assistant event for this iteration
131
153
  if (!reactionEvent) {
@@ -159,7 +181,7 @@ export class Story {
159
181
  await updateContextStatus(params.env, contextSelector, "open");
160
182
  await completeExecution(params.env, contextSelector, executionId, "completed");
161
183
  if (!silent) {
162
- await closeStoryStream({ preventClose, sendFinish });
184
+ await closeStoryStream({ preventClose, sendFinish, writable });
163
185
  }
164
186
  return {
165
187
  contextId: currentContext.id,
@@ -172,7 +194,7 @@ export class Story {
172
194
  }
173
195
  // Execute tool calls (workflow context; tool implementations decide step vs workflow)
174
196
  if (!silent && toolCalls.length) {
175
- await writeContextSubstate({ key: "actions", transient: true });
197
+ await writeContextSubstate({ key: "actions", transient: true, writable });
176
198
  }
177
199
  const executionResults = await Promise.all(toolCalls.map(async (tc) => {
178
200
  const toolDef = toolsAll[tc.toolName];
@@ -185,7 +207,41 @@ export class Story {
185
207
  };
186
208
  }
187
209
  try {
188
- const output = await toolDef.execute(tc.args, {
210
+ let toolArgs = tc.args;
211
+ if (toolDef?.auto === false) {
212
+ const { createHook, createWebhook } = await import("workflow");
213
+ const hookToken = toolApprovalHookToken({
214
+ executionId,
215
+ toolCallId: String(tc.toolCallId),
216
+ });
217
+ const webhookToken = toolApprovalWebhookToken({
218
+ executionId,
219
+ toolCallId: String(tc.toolCallId),
220
+ });
221
+ const hook = createHook({ token: hookToken });
222
+ const webhook = createWebhook({ token: webhookToken });
223
+ const approvalOrRequest = await Promise.race([
224
+ hook.then((approval) => ({ source: "hook", approval })),
225
+ webhook.then((request) => ({ source: "webhook", request })),
226
+ ]);
227
+ const approval = approvalOrRequest.source === "hook"
228
+ ? approvalOrRequest.approval
229
+ : await approvalOrRequest.request.json().catch(() => null);
230
+ if (!approval || approval.approved !== true) {
231
+ return {
232
+ tc,
233
+ success: false,
234
+ output: null,
235
+ errorText: approval && "comment" in approval && approval.comment
236
+ ? `Tool execution not approved: ${approval.comment}`
237
+ : "Tool execution not approved",
238
+ };
239
+ }
240
+ if ("args" in approval && approval.args !== undefined) {
241
+ toolArgs = approval.args;
242
+ }
243
+ }
244
+ const output = await toolDef.execute(toolArgs, {
189
245
  toolCallId: tc.toolCallId,
190
246
  messages: messagesForModel,
191
247
  eventId: reactionEventId,
@@ -214,11 +270,12 @@ export class Story {
214
270
  success: false,
215
271
  errorText: r.errorText,
216
272
  }),
273
+ writable,
217
274
  });
218
275
  }
219
276
  // Clear action status once tool execution results have been emitted.
220
277
  if (!silent && toolCalls.length) {
221
- await writeContextSubstate({ key: null, transient: true });
278
+ await writeContextSubstate({ key: null, transient: true, writable });
222
279
  }
223
280
  // Merge tool results into persisted parts (so next LLM call can see them)
224
281
  if (reactionEvent) {
@@ -266,7 +323,7 @@ export class Story {
266
323
  await updateContextStatus(params.env, contextSelector, "open");
267
324
  await completeExecution(params.env, contextSelector, executionId, "completed");
268
325
  if (!silent) {
269
- await closeStoryStream({ preventClose, sendFinish });
326
+ await closeStoryStream({ preventClose, sendFinish, writable });
270
327
  }
271
328
  return {
272
329
  contextId: currentContext.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekairos/story",
3
- "version": "1.21.58-beta.0",
3
+ "version": "1.21.59-beta.0",
4
4
  "description": "Pulzar Story - Workflow-based AI Stories",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "dependencies": {
72
72
  "@ai-sdk/openai": "^2.0.52",
73
- "@ekairos/domain": "^1.21.58-beta.0",
73
+ "@ekairos/domain": "^1.21.59-beta.0",
74
74
  "@instantdb/admin": "^0.22.13",
75
75
  "@instantdb/core": "^0.22.13",
76
76
  "@vercel/sandbox": "^0.0.23",