zeitlich 0.2.42 → 0.2.44
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/{activities-CrN-ghLo.d.ts → activities-CPIB2v2C.d.ts} +4 -4
- package/dist/{activities-Coafq5zr.d.cts → activities-DnmNOnq4.d.cts} +4 -4
- package/dist/adapters/sandbox/daytona/index.d.cts +2 -2
- package/dist/adapters/sandbox/daytona/index.d.ts +2 -2
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/index.d.ts +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +4 -4
- package/dist/adapters/thread/anthropic/index.d.ts +4 -4
- package/dist/adapters/thread/anthropic/workflow.d.cts +4 -4
- package/dist/adapters/thread/anthropic/workflow.d.ts +4 -4
- package/dist/adapters/thread/google-genai/index.d.cts +4 -4
- package/dist/adapters/thread/google-genai/index.d.ts +4 -4
- package/dist/adapters/thread/google-genai/workflow.d.cts +4 -4
- package/dist/adapters/thread/google-genai/workflow.d.ts +4 -4
- package/dist/adapters/thread/langchain/index.cjs +11 -11
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +4 -4
- package/dist/adapters/thread/langchain/index.d.ts +4 -4
- package/dist/adapters/thread/langchain/index.js +8 -12
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +4 -4
- package/dist/adapters/thread/langchain/workflow.d.ts +4 -4
- package/dist/index.cjs +73 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +73 -51
- package/dist/index.js.map +1 -1
- package/dist/{proxy-COqA95FW.d.ts → proxy-B7Xi1znZ.d.ts} +1 -1
- package/dist/{proxy-Bf7uI-Hw.d.cts → proxy-DTnc5rqT.d.cts} +1 -1
- package/dist/{thread-manager-Bi1XlbpJ.d.ts → thread-manager-BAv340mi.d.ts} +3 -3
- package/dist/{thread-manager-wRVVBFgj.d.cts → thread-manager-BWv6ZXI3.d.cts} +4 -4
- package/dist/{thread-manager-BsLO3Fgc.d.cts → thread-manager-BlX2TwRN.d.cts} +3 -3
- package/dist/{thread-manager-BhkOyQ1I.d.ts → thread-manager-D2xorI-J.d.ts} +4 -4
- package/dist/{types-CdALEF3z.d.cts → types-4Wmk-wRq.d.cts} +1 -1
- package/dist/{types-CjY93AWZ.d.cts → types-C90VoEpt.d.cts} +1 -1
- package/dist/{types-BkX4HLzi.d.ts → types-Clhqautb.d.ts} +1 -1
- package/dist/{types-ChAy_jSP.d.ts → types-DKsCdAtQ.d.ts} +1 -1
- package/dist/{types-C66-BVBr.d.cts → types-DRJt1TMi.d.cts} +1 -1
- package/dist/{types-gVa5XCWD.d.ts → types-DpFD8ofR.d.ts} +1 -1
- package/dist/{workflow-BwT5EybR.d.ts → workflow-D32TRMr-.d.ts} +2 -2
- package/dist/{workflow-DMmiaw6w.d.cts → workflow-XVt0ww8K.d.cts} +2 -2
- package/dist/workflow.cjs +65 -39
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +2 -2
- package/dist/workflow.d.ts +2 -2
- package/dist/workflow.js +65 -39
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/.env +1 -0
- package/src/lib/session/session-edge-cases.integration.test.ts +16 -10
- package/src/lib/session/session.ts +54 -40
- package/src/lib/state/manager.integration.test.ts +27 -0
- package/src/lib/state/manager.ts +39 -1
- package/src/lib/subagent/workflow.ts +2 -2
- package/src/tools/bash/.env +1 -0
package/package.json
CHANGED
package/src/lib/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
E2B_API_KEY=e2b_39af116424059782e2aee6942fd70237cc2126c9
|
|
@@ -176,9 +176,18 @@ describe("createSession edge cases", () => {
|
|
|
176
176
|
idCounter = 0;
|
|
177
177
|
});
|
|
178
178
|
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
// NOTE: a previous test here ("cancels session when WAITING_FOR_INPUT
|
|
180
|
+
// times out") covered the condition-based wait + cancel-on-timeout flow
|
|
181
|
+
// that was removed in 7ab652b ("fix: stop halting workflow when
|
|
182
|
+
// waiting"). The session loop now exits cleanly the moment a tool puts
|
|
183
|
+
// the agent into WAITING_FOR_INPUT — there is no internal timeout to
|
|
184
|
+
// assert against — so the test was deleted rather than rewritten. The
|
|
185
|
+
// assertion below covers the surviving contract: the exit reason
|
|
186
|
+
// reflects the WAITING_FOR_INPUT state instead of the default
|
|
187
|
+
// "completed", so callers can distinguish a finished session from a
|
|
188
|
+
// parked one.
|
|
189
|
+
|
|
190
|
+
it("exits with 'waiting_for_input' when a tool parks the session", async () => {
|
|
182
191
|
const { ops } = createMockThreadOps();
|
|
183
192
|
let endReason: string | undefined;
|
|
184
193
|
const capturedRef: {
|
|
@@ -191,10 +200,7 @@ describe("createSession edge cases", () => {
|
|
|
191
200
|
schema: z.object({}),
|
|
192
201
|
handler: async (_args: Record<string, never>, _ctx: RouterContext) => {
|
|
193
202
|
capturedRef.stateManager?.waitForInput();
|
|
194
|
-
return {
|
|
195
|
-
toolResponse: "Please provide input.",
|
|
196
|
-
data: null,
|
|
197
|
-
};
|
|
203
|
+
return { toolResponse: "Please provide input.", data: null };
|
|
198
204
|
},
|
|
199
205
|
});
|
|
200
206
|
|
|
@@ -224,9 +230,9 @@ describe("createSession edge cases", () => {
|
|
|
224
230
|
|
|
225
231
|
const result = await session.runSession({ stateManager });
|
|
226
232
|
|
|
227
|
-
expect(result.exitReason).toBe("
|
|
228
|
-
expect(
|
|
229
|
-
expect(
|
|
233
|
+
expect(result.exitReason).toBe("waiting_for_input");
|
|
234
|
+
expect(endReason).toBe("waiting_for_input");
|
|
235
|
+
expect(stateManager.getStatus()).toBe("WAITING_FOR_INPUT");
|
|
230
236
|
});
|
|
231
237
|
|
|
232
238
|
// --- All tool calls are invalid ---
|
|
@@ -383,46 +383,6 @@ export async function createSession<
|
|
|
383
383
|
});
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
// --- Virtual filesystem init (independent of sandbox) ----------------
|
|
387
|
-
if (virtualFsConfig) {
|
|
388
|
-
if (!virtualFsOps) {
|
|
389
|
-
throw ApplicationFailure.create({
|
|
390
|
-
message: "No virtualFsOps provided — cannot resolve file tree",
|
|
391
|
-
nonRetryable: true,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
const result = await virtualFsOps.resolveFileTree(virtualFsConfig.ctx);
|
|
395
|
-
const skillFiles = skills ? collectSkillFiles(skills) : undefined;
|
|
396
|
-
const fileTree = skillFiles
|
|
397
|
-
? [
|
|
398
|
-
...result.fileTree,
|
|
399
|
-
...Object.entries(skillFiles).map(([path, content]) => ({
|
|
400
|
-
id: `skill:${path}`,
|
|
401
|
-
path,
|
|
402
|
-
size: content.length,
|
|
403
|
-
mtime: new Date().toISOString(),
|
|
404
|
-
metadata: {},
|
|
405
|
-
// Carry the content directly on the entry so any handler that
|
|
406
|
-
// constructs a VirtualFileSystem from `fileTree` can read it
|
|
407
|
-
// without needing to also wire up `inlineFiles` from state.
|
|
408
|
-
inlineContent: content,
|
|
409
|
-
})),
|
|
410
|
-
]
|
|
411
|
-
: result.fileTree;
|
|
412
|
-
stateManager.mergeUpdate({
|
|
413
|
-
fileTree,
|
|
414
|
-
virtualFsCtx: virtualFsConfig.ctx,
|
|
415
|
-
// `inlineFiles` is still the source of truth at read time:
|
|
416
|
-
// VirtualFileSystem checks the inlineFiles map first and only
|
|
417
|
-
// falls through to entry.inlineContent. Embedding the content on
|
|
418
|
-
// the entry is the migration target so that handlers building a
|
|
419
|
-
// VirtualFileSystem from `fileTree` alone (without forwarding
|
|
420
|
-
// `inlineFiles` from state) can read skill resources. Until a
|
|
421
|
-
// follow-up drops `inlineFiles`, both fields are populated.
|
|
422
|
-
...(skillFiles && { inlineFiles: skillFiles }),
|
|
423
|
-
} as Partial<AgentState<TState>>);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
386
|
if (hooks.onSessionStart) {
|
|
427
387
|
await hooks.onSessionStart({
|
|
428
388
|
threadId,
|
|
@@ -474,6 +434,52 @@ export async function createSession<
|
|
|
474
434
|
await initializeThread(threadId, threadKey);
|
|
475
435
|
}
|
|
476
436
|
}
|
|
437
|
+
|
|
438
|
+
// --- Virtual filesystem init (independent of sandbox) ----------------
|
|
439
|
+
// Runs AFTER thread rehydration so the freshly resolved tree is
|
|
440
|
+
// authoritative. Otherwise a stale `fileTree` carried in a persisted
|
|
441
|
+
// slice (from a run on older code that didn't strip it) could
|
|
442
|
+
// overwrite entries that no longer exist in the backing store and
|
|
443
|
+
// cause subsequent FileWrite calls to fail with "not_found".
|
|
444
|
+
if (virtualFsConfig) {
|
|
445
|
+
if (!virtualFsOps) {
|
|
446
|
+
throw ApplicationFailure.create({
|
|
447
|
+
message: "No virtualFsOps provided — cannot resolve file tree",
|
|
448
|
+
nonRetryable: true,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
const result = await virtualFsOps.resolveFileTree(virtualFsConfig.ctx);
|
|
452
|
+
const skillFiles = skills ? collectSkillFiles(skills) : undefined;
|
|
453
|
+
const fileTree = skillFiles
|
|
454
|
+
? [
|
|
455
|
+
...result.fileTree,
|
|
456
|
+
...Object.entries(skillFiles).map(([path, content]) => ({
|
|
457
|
+
id: `skill:${path}`,
|
|
458
|
+
path,
|
|
459
|
+
size: content.length,
|
|
460
|
+
mtime: new Date().toISOString(),
|
|
461
|
+
metadata: {},
|
|
462
|
+
// Carry the content directly on the entry so any handler that
|
|
463
|
+
// constructs a VirtualFileSystem from `fileTree` can read it
|
|
464
|
+
// without needing to also wire up `inlineFiles` from state.
|
|
465
|
+
inlineContent: content,
|
|
466
|
+
})),
|
|
467
|
+
]
|
|
468
|
+
: result.fileTree;
|
|
469
|
+
stateManager.mergeUpdate({
|
|
470
|
+
fileTree,
|
|
471
|
+
virtualFsCtx: virtualFsConfig.ctx,
|
|
472
|
+
// `inlineFiles` is still the source of truth at read time:
|
|
473
|
+
// VirtualFileSystem checks the inlineFiles map first and only
|
|
474
|
+
// falls through to entry.inlineContent. Embedding the content on
|
|
475
|
+
// the entry is the migration target so that handlers building a
|
|
476
|
+
// VirtualFileSystem from `fileTree` alone (without forwarding
|
|
477
|
+
// `inlineFiles` from state) can read skill resources. Until a
|
|
478
|
+
// follow-up drops `inlineFiles`, both fields are populated.
|
|
479
|
+
...(skillFiles && { inlineFiles: skillFiles }),
|
|
480
|
+
} as Partial<AgentState<TState>>);
|
|
481
|
+
}
|
|
482
|
+
|
|
477
483
|
await appendHumanMessage(
|
|
478
484
|
threadId,
|
|
479
485
|
uuid4(),
|
|
@@ -596,6 +602,14 @@ export async function createSession<
|
|
|
596
602
|
threadId,
|
|
597
603
|
maxTurns,
|
|
598
604
|
});
|
|
605
|
+
} else if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
|
|
606
|
+
// A tool put the agent into WAITING_FOR_INPUT (e.g. an AskUser
|
|
607
|
+
// tool). The loop's `isRunning()` guard then exited cleanly; we
|
|
608
|
+
// report a dedicated exit reason rather than the misleading
|
|
609
|
+
// default `"completed"`, so callers can distinguish a finished
|
|
610
|
+
// session from one parked waiting on external input.
|
|
611
|
+
exitReason = "waiting_for_input";
|
|
612
|
+
log.info("session waiting for input", { agentName, threadId });
|
|
599
613
|
}
|
|
600
614
|
} catch (error) {
|
|
601
615
|
exitReason = "failed";
|
|
@@ -385,6 +385,33 @@ describe("createAgentStateManager integration", () => {
|
|
|
385
385
|
expect("tools" in slice.custom).toBe(false);
|
|
386
386
|
});
|
|
387
387
|
|
|
388
|
+
it("getPersistedSlice strips runtime fields injected via mergeUpdate", () => {
|
|
389
|
+
const sm = createAgentStateManager<{ label: string }>({
|
|
390
|
+
initialState: { systemPrompt: "test", label: "original" },
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
sm.mergeUpdate({
|
|
394
|
+
label: "updated",
|
|
395
|
+
fileTree: [
|
|
396
|
+
{
|
|
397
|
+
id: "f1",
|
|
398
|
+
path: "/a.txt",
|
|
399
|
+
size: 1,
|
|
400
|
+
mtime: "2026-01-01T00:00:00Z",
|
|
401
|
+
metadata: {},
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
virtualFsCtx: { workspaceBase: "/tmp" },
|
|
405
|
+
inlineFiles: { "/a.txt": "hi" },
|
|
406
|
+
} as Parameters<typeof sm.mergeUpdate>[0]);
|
|
407
|
+
|
|
408
|
+
const slice = sm.getPersistedSlice();
|
|
409
|
+
expect(slice.custom).toEqual({ label: "updated" });
|
|
410
|
+
expect("fileTree" in slice.custom).toBe(false);
|
|
411
|
+
expect("virtualFsCtx" in slice.custom).toBe(false);
|
|
412
|
+
expect("inlineFiles" in slice.custom).toBe(false);
|
|
413
|
+
});
|
|
414
|
+
|
|
388
415
|
it("mergeUpdate replaces the task map when given a tasks field", () => {
|
|
389
416
|
const sm = createAgentStateManager<{ label: string; extra?: string }>({
|
|
390
417
|
initialState: {
|
package/src/lib/state/manager.ts
CHANGED
|
@@ -20,6 +20,32 @@ import type {
|
|
|
20
20
|
} from "./types";
|
|
21
21
|
import { z } from "zod";
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Fields that live on `AgentState` at runtime but must NOT be persisted into
|
|
25
|
+
* `PersistedThreadState`. They're either managed by the state manager itself
|
|
26
|
+
* (status/version/turns/tasks/tools/usage), kept in their own slot
|
|
27
|
+
* (systemPrompt), or rebuilt on each run (the virtual-fs trio).
|
|
28
|
+
*
|
|
29
|
+
* Centralizing the list keeps the constructor's destructure-omit and
|
|
30
|
+
* `getPersistedSlice` in lockstep.
|
|
31
|
+
*/
|
|
32
|
+
const RESERVED_STATE_KEYS = [
|
|
33
|
+
"status",
|
|
34
|
+
"version",
|
|
35
|
+
"turns",
|
|
36
|
+
"tasks",
|
|
37
|
+
"tools",
|
|
38
|
+
"systemPrompt",
|
|
39
|
+
"fileTree",
|
|
40
|
+
"inlineFiles",
|
|
41
|
+
"virtualFsCtx",
|
|
42
|
+
"totalInputTokens",
|
|
43
|
+
"totalOutputTokens",
|
|
44
|
+
"totalReasonTokens",
|
|
45
|
+
"cachedWriteTokens",
|
|
46
|
+
"cachedReadTokens",
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
23
49
|
/**
|
|
24
50
|
* Creates an agent state manager for tracking workflow state.
|
|
25
51
|
* Automatically registers Temporal query and update handlers for the agent.
|
|
@@ -238,9 +264,21 @@ export function createAgentStateManager<
|
|
|
238
264
|
},
|
|
239
265
|
|
|
240
266
|
getPersistedSlice(): PersistedThreadState {
|
|
267
|
+
// `customState` can pick up reserved/runtime fields via `mergeUpdate`
|
|
268
|
+
// (e.g. `fileTree`, `virtualFsCtx`, `inlineFiles` written by the
|
|
269
|
+
// virtual-fs bootstrap on every run). Those are rebuilt per run and
|
|
270
|
+
// must never round-trip through the thread store, so strip them here
|
|
271
|
+
// rather than relying on callers to remember.
|
|
272
|
+
const source = customState as unknown as Record<string, JsonValue>;
|
|
273
|
+
const custom: Record<string, JsonValue> = {};
|
|
274
|
+
const reserved = new Set<string>(RESERVED_STATE_KEYS);
|
|
275
|
+
for (const [key, value] of Object.entries(source)) {
|
|
276
|
+
if (reserved.has(key)) continue;
|
|
277
|
+
custom[key] = value;
|
|
278
|
+
}
|
|
241
279
|
return {
|
|
242
280
|
tasks: Array.from(tasks.entries()),
|
|
243
|
-
custom
|
|
281
|
+
custom,
|
|
244
282
|
};
|
|
245
283
|
},
|
|
246
284
|
|
|
@@ -168,9 +168,8 @@ export function defineSubagentWorkflow(
|
|
|
168
168
|
|
|
169
169
|
// Auto-forward sandbox outputs captured from the session so user code
|
|
170
170
|
// never has to thread them through manually. Explicit values on the fn
|
|
171
|
-
// result take precedence.
|
|
171
|
+
// result take precedence, so spread `result` LAST.
|
|
172
172
|
return {
|
|
173
|
-
...result,
|
|
174
173
|
...(capturedThreadId !== undefined && { threadId: capturedThreadId }),
|
|
175
174
|
...(capturedSandboxId !== undefined && { sandboxId: capturedSandboxId }),
|
|
176
175
|
...(capturedSnapshot !== undefined && { snapshot: capturedSnapshot }),
|
|
@@ -178,6 +177,7 @@ export function defineSubagentWorkflow(
|
|
|
178
177
|
baseSnapshot: capturedBaseSnapshot,
|
|
179
178
|
}),
|
|
180
179
|
...(capturedUsage !== undefined && { usage: capturedUsage }),
|
|
180
|
+
...result,
|
|
181
181
|
};
|
|
182
182
|
};
|
|
183
183
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
E2B_API_KEY=e2b_39af116424059782e2aee6942fd70237cc2126c9
|