zeitlich 0.2.28 → 0.2.29

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 (93) hide show
  1. package/README.md +121 -13
  2. package/dist/{activities-3xj_fEJK.d.ts → activities-1xrWRrGJ.d.cts} +2 -3
  3. package/dist/{activities-BzYq6jf7.d.cts → activities-DOViDCTE.d.ts} +2 -3
  4. package/dist/adapters/thread/anthropic/index.d.cts +5 -6
  5. package/dist/adapters/thread/anthropic/index.d.ts +5 -6
  6. package/dist/adapters/thread/anthropic/workflow.d.cts +4 -5
  7. package/dist/adapters/thread/anthropic/workflow.d.ts +4 -5
  8. package/dist/adapters/thread/google-genai/index.d.cts +5 -6
  9. package/dist/adapters/thread/google-genai/index.d.ts +5 -6
  10. package/dist/adapters/thread/google-genai/workflow.d.cts +4 -5
  11. package/dist/adapters/thread/google-genai/workflow.d.ts +4 -5
  12. package/dist/adapters/thread/langchain/index.d.cts +5 -6
  13. package/dist/adapters/thread/langchain/index.d.ts +5 -6
  14. package/dist/adapters/thread/langchain/workflow.d.cts +4 -5
  15. package/dist/adapters/thread/langchain/workflow.d.ts +4 -5
  16. package/dist/index.cjs +499 -8
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +66 -15
  19. package/dist/index.d.ts +66 -15
  20. package/dist/index.js +495 -10
  21. package/dist/index.js.map +1 -1
  22. package/dist/{proxy-7e7v8ccg.d.ts → proxy-78nc985d.d.ts} +1 -1
  23. package/dist/{proxy-CsB8r0RR.d.cts → proxy-Bm2UTiO_.d.cts} +1 -1
  24. package/dist/{thread-manager-D8C5QvLi.d.ts → thread-manager-07BaYu_z.d.ts} +1 -1
  25. package/dist/{thread-manager-DdVFl1IY.d.cts → thread-manager-BRE5KkHB.d.cts} +1 -1
  26. package/dist/{thread-manager-DFJ3sKKU.d.cts → thread-manager-CatBkarc.d.cts} +1 -1
  27. package/dist/{thread-manager-B5qA4v7V.d.ts → thread-manager-CxbWo7q_.d.ts} +1 -1
  28. package/dist/types-BkVoEyiH.d.ts +1211 -0
  29. package/dist/{types-BZ75HpYd.d.ts → types-DAv_SLN8.d.ts} +1 -1
  30. package/dist/{types-HbjqzyJH.d.cts → types-Dpz2gXLk.d.cts} +1 -1
  31. package/dist/types-seDYom4M.d.cts +1211 -0
  32. package/dist/workflow-B4T3la0p.d.cts +750 -0
  33. package/dist/workflow-DCmaXLZ_.d.ts +750 -0
  34. package/dist/workflow.cjs +171 -5
  35. package/dist/workflow.cjs.map +1 -1
  36. package/dist/workflow.d.cts +5 -579
  37. package/dist/workflow.d.ts +5 -579
  38. package/dist/workflow.js +170 -7
  39. package/dist/workflow.js.map +1 -1
  40. package/package.json +3 -23
  41. package/src/index.ts +7 -0
  42. package/src/lib/observability/hooks.ts +117 -0
  43. package/src/lib/observability/index.ts +13 -0
  44. package/src/lib/observability/sinks.ts +88 -0
  45. package/src/lib/sandbox/manager.ts +3 -3
  46. package/src/lib/session/session-edge-cases.integration.test.ts +1 -0
  47. package/src/lib/session/session.integration.test.ts +1 -0
  48. package/src/lib/session/session.ts +63 -0
  49. package/src/lib/session/types.ts +21 -0
  50. package/src/lib/state/manager.integration.test.ts +1 -0
  51. package/src/lib/subagent/handler.ts +17 -0
  52. package/src/lib/subagent/subagent.integration.test.ts +1 -0
  53. package/src/lib/tool-router/router-edge-cases.integration.test.ts +2 -0
  54. package/src/lib/tool-router/router.integration.test.ts +2 -0
  55. package/src/lib/tool-router/router.ts +24 -2
  56. package/src/{adapters/sandbox/virtual → lib/virtual-fs}/filesystem.ts +4 -4
  57. package/src/lib/virtual-fs/index.ts +18 -0
  58. package/src/lib/virtual-fs/manager.ts +48 -0
  59. package/src/lib/virtual-fs/proxy.ts +45 -0
  60. package/src/{adapters/sandbox/virtual → lib/virtual-fs}/types.ts +43 -33
  61. package/src/{adapters/sandbox/virtual/virtual-sandbox.test.ts → lib/virtual-fs/virtual-fs.test.ts} +15 -130
  62. package/src/lib/virtual-fs/with-virtual-fs.ts +94 -0
  63. package/src/workflow.ts +25 -8
  64. package/tsup.config.ts +0 -2
  65. package/dist/adapters/sandbox/virtual/index.cjs +0 -487
  66. package/dist/adapters/sandbox/virtual/index.cjs.map +0 -1
  67. package/dist/adapters/sandbox/virtual/index.d.cts +0 -90
  68. package/dist/adapters/sandbox/virtual/index.d.ts +0 -90
  69. package/dist/adapters/sandbox/virtual/index.js +0 -479
  70. package/dist/adapters/sandbox/virtual/index.js.map +0 -1
  71. package/dist/adapters/sandbox/virtual/workflow.cjs +0 -33
  72. package/dist/adapters/sandbox/virtual/workflow.cjs.map +0 -1
  73. package/dist/adapters/sandbox/virtual/workflow.d.cts +0 -28
  74. package/dist/adapters/sandbox/virtual/workflow.d.ts +0 -28
  75. package/dist/adapters/sandbox/virtual/workflow.js +0 -31
  76. package/dist/adapters/sandbox/virtual/workflow.js.map +0 -1
  77. package/dist/queries-DVnukByF.d.cts +0 -44
  78. package/dist/queries-kjlvsUfz.d.ts +0 -44
  79. package/dist/types-BclYm5Ic.d.cts +0 -581
  80. package/dist/types-BclYm5Ic.d.ts +0 -581
  81. package/dist/types-BgsAwN3L.d.cts +0 -125
  82. package/dist/types-BtqbM1bO.d.ts +0 -490
  83. package/dist/types-BuCEZ4dF.d.cts +0 -490
  84. package/dist/types-yU5AINiP.d.ts +0 -125
  85. package/src/adapters/sandbox/virtual/index.ts +0 -92
  86. package/src/adapters/sandbox/virtual/provider.ts +0 -121
  87. package/src/adapters/sandbox/virtual/proxy.ts +0 -53
  88. package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +0 -97
  89. package/src/lib/.env +0 -1
  90. package/src/tools/bash/.env +0 -1
  91. /package/src/{adapters/sandbox/virtual → lib/virtual-fs}/mutations.ts +0 -0
  92. /package/src/{adapters/sandbox/virtual → lib/virtual-fs}/queries.ts +0 -0
  93. /package/src/{adapters/sandbox/virtual → lib/virtual-fs}/tree.ts +0 -0
@@ -0,0 +1,48 @@
1
+ import type {
2
+ FileEntryMetadata,
3
+ FileResolver,
4
+ PrefixedVirtualFsOps,
5
+ VirtualFsOps,
6
+ } from "./types";
7
+
8
+ /**
9
+ * Creates prefixed Temporal activity functions for a {@link FileResolver}.
10
+ *
11
+ * Pair with {@link proxyVirtualFsOps} on the workflow side using the same
12
+ * scope string.
13
+ *
14
+ * @param resolver - Consumer-provided bridge to DB / S3 / CRUD layer
15
+ * @param scope - Workflow name used to namespace the activities
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { createVirtualFsActivities } from 'zeitlich';
20
+ *
21
+ * const activities = {
22
+ * ...createVirtualFsActivities(resolver, "CodingAgent"),
23
+ * };
24
+ * // registers: codingAgentResolveFileTree
25
+ * ```
26
+ */
27
+ export function createVirtualFsActivities<
28
+ S extends string,
29
+ TCtx = unknown,
30
+ TMeta = FileEntryMetadata,
31
+ >(
32
+ resolver: FileResolver<TCtx, TMeta>,
33
+ scope: S,
34
+ ): PrefixedVirtualFsOps<S, TCtx, TMeta> {
35
+ const ops: VirtualFsOps<TCtx, TMeta> = {
36
+ resolveFileTree: async (ctx: TCtx) => {
37
+ const fileTree = await resolver.resolveEntries(ctx);
38
+ return { fileTree };
39
+ },
40
+ };
41
+
42
+ const prefix = scope;
43
+ const cap = (s: string): string => s.charAt(0).toUpperCase() + s.slice(1);
44
+
45
+ return Object.fromEntries(
46
+ Object.entries(ops).map(([k, v]) => [`${prefix}${cap(k)}`, v]),
47
+ ) as PrefixedVirtualFsOps<S, TCtx, TMeta>;
48
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Workflow-safe proxy for virtual filesystem operations.
3
+ *
4
+ * Import this from `zeitlich/workflow` in your Temporal workflow files.
5
+ *
6
+ * By default the scope is derived from `workflowInfo().workflowType`,
7
+ * so activities are automatically namespaced per workflow.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { proxyVirtualFsOps } from 'zeitlich/workflow';
12
+ *
13
+ * const virtualFsOps = proxyVirtualFsOps();
14
+ * ```
15
+ */
16
+ import { proxyActivities, workflowInfo } from "@temporalio/workflow";
17
+ import type { VirtualFsOps } from "./types";
18
+
19
+ export function proxyVirtualFsOps<TCtx = unknown>(
20
+ scope?: string,
21
+ options?: Parameters<typeof proxyActivities>[0],
22
+ ): VirtualFsOps<TCtx> {
23
+ const resolvedScope = scope ?? workflowInfo().workflowType;
24
+
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ const acts = proxyActivities<Record<string, (...args: any[]) => any>>(
27
+ options ?? {
28
+ startToCloseTimeout: "30s",
29
+ retry: {
30
+ maximumAttempts: 3,
31
+ initialInterval: "2s",
32
+ maximumInterval: "30s",
33
+ backoffCoefficient: 2,
34
+ },
35
+ },
36
+ );
37
+
38
+ const prefix = resolvedScope;
39
+ const p = (key: string): string =>
40
+ `${prefix}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
41
+
42
+ return {
43
+ resolveFileTree: acts[p("resolveFileTree")],
44
+ } as VirtualFsOps<TCtx>;
45
+ }
@@ -1,6 +1,5 @@
1
- import type { Sandbox, SandboxCreateOptions } from "../../../lib/sandbox/types";
2
- import type { RouterContext } from "../../../lib/tool-router/types";
3
- import type { VirtualSandboxFileSystem } from "./filesystem";
1
+ import type { RouterContext } from "../tool-router/types";
2
+ import type { VirtualFileSystem } from "./filesystem";
4
3
 
5
4
  // ============================================================================
6
5
  // File Entry
@@ -15,7 +14,7 @@ export type FileEntryMetadata = Record<
15
14
  /** JSON-serializable metadata for a single file in the virtual tree. */
16
15
  export interface FileEntry<TMeta = FileEntryMetadata> {
17
16
  id: string;
18
- /** Virtual path inside the sandbox, e.g. "/src/index.ts" */
17
+ /** Virtual path, e.g. "/src/index.ts" */
19
18
  path: string;
20
19
  size: number;
21
20
  /** ISO-8601 date string (JSON-safe) */
@@ -73,63 +72,74 @@ export interface FileResolver<TCtx = unknown, TMeta = FileEntryMetadata> {
73
72
  }
74
73
 
75
74
  // ============================================================================
76
- // Create Options
75
+ // VirtualFsOps — workflow-side activity interface
77
76
  // ============================================================================
78
77
 
79
78
  /**
80
- * Options for {@link VirtualSandboxProvider.create}.
81
- * Extends base options with resolver context.
79
+ * Workflow-side operations for the virtual filesystem.
80
+ *
81
+ * Unlike {@link SandboxOps}, this only exposes what is actually needed:
82
+ * resolving the initial file tree from the consumer's data layer.
82
83
  */
83
- export interface VirtualSandboxCreateOptions<
84
- TCtx,
85
- > extends SandboxCreateOptions {
86
- resolverContext: TCtx;
87
- /** Base path for resolving relative filesystem paths (default "/"). */
88
- workspaceBase?: string;
84
+ export interface VirtualFsOps<
85
+ TCtx = unknown,
86
+ TMeta = FileEntryMetadata,
87
+ > {
88
+ resolveFileTree(
89
+ ctx: TCtx,
90
+ ): Promise<{
91
+ fileTree: FileEntry<TMeta>[];
92
+ stateUpdate?: Record<string, unknown>;
93
+ }>;
89
94
  }
90
95
 
96
+ /**
97
+ * Maps generic {@link VirtualFsOps} method names to scope-prefixed names.
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * type Ops = PrefixedVirtualFsOps<"codingAgent">;
102
+ * // → { codingAgentResolveFileTree: ... }
103
+ * ```
104
+ */
105
+ export type PrefixedVirtualFsOps<
106
+ TPrefix extends string,
107
+ TCtx = unknown,
108
+ TMeta = FileEntryMetadata,
109
+ > = {
110
+ [K in keyof VirtualFsOps<TCtx, TMeta> as `${TPrefix}${Capitalize<K & string>}`]: VirtualFsOps<TCtx, TMeta>[K];
111
+ };
112
+
91
113
  // ============================================================================
92
114
  // Workflow State Shape
93
115
  // ============================================================================
94
116
 
95
117
  /**
96
- * The portion of workflow `AgentState` that the virtual sandbox reads via
118
+ * The portion of workflow `AgentState` that the virtual filesystem reads via
97
119
  * {@link queryParentWorkflowState}. Populated automatically by the session
98
- * from the provider's `stateUpdate` after `createSandbox`.
120
+ * when `virtualFs` config is provided.
99
121
  */
100
- export interface VirtualSandboxState<
122
+ export interface VirtualFsState<
101
123
  TCtx = unknown,
102
124
  TMeta = FileEntryMetadata,
103
125
  > {
104
- sandboxId: string;
105
126
  fileTree: FileEntry<TMeta>[];
106
127
  resolverContext: TCtx;
107
128
  workspaceBase?: string;
108
129
  }
109
130
 
110
- // ============================================================================
111
- // VirtualSandbox instance type
112
- // ============================================================================
113
-
114
- /**
115
- * A {@link Sandbox} whose filesystem is backed by a {@link VirtualSandboxFileSystem}.
116
- */
117
- export type VirtualSandbox<
118
- TCtx = unknown,
119
- TMeta = FileEntryMetadata,
120
- > = Sandbox & { fs: VirtualSandboxFileSystem<TCtx, TMeta> };
121
-
122
131
  // ============================================================================
123
132
  // Handler Context
124
133
  // ============================================================================
125
134
 
126
135
  /**
127
- * Extended router context injected by {@link withVirtualSandbox}.
128
- * Guarantees a live (ephemeral) sandbox built from the workflow file tree.
136
+ * Extended router context injected by {@link withVirtualFs}.
137
+ * Guarantees a live (ephemeral) virtual filesystem built from the workflow
138
+ * file tree.
129
139
  */
130
- export interface VirtualSandboxContext<
140
+ export interface VirtualFsContext<
131
141
  TCtx = unknown,
132
142
  TMeta = FileEntryMetadata,
133
143
  > extends RouterContext {
134
- sandbox: VirtualSandbox<TCtx, TMeta>;
144
+ virtualFs: VirtualFileSystem<TCtx, TMeta>;
135
145
  }
@@ -1,10 +1,9 @@
1
1
  import { describe, expect, it, beforeEach } from "vitest";
2
2
  import type { FileEntry, FileResolver } from "./types";
3
- import { VirtualSandboxFileSystem } from "./filesystem";
4
- import { createVirtualSandbox } from "./index";
3
+ import { VirtualFileSystem } from "./filesystem";
5
4
  import { applyVirtualTreeMutations } from "./mutations";
6
- import { VirtualSandboxProvider } from "./provider";
7
- import { SandboxNotSupportedError } from "../../../lib/sandbox/types";
5
+ import { createVirtualFsActivities } from "./manager";
6
+ import { SandboxNotSupportedError } from "../sandbox/types";
8
7
 
9
8
  // ============================================================================
10
9
  // Mock resolver
@@ -112,15 +111,15 @@ const sampleTree: FileEntry[] = [
112
111
  const ctx: TestCtx = { projectId: "proj-42" };
113
112
 
114
113
  // ============================================================================
115
- // VirtualSandboxFileSystem
114
+ // VirtualFileSystem
116
115
  // ============================================================================
117
116
 
118
- describe("VirtualSandboxFileSystem", () => {
119
- let fs: VirtualSandboxFileSystem<TestCtx>;
117
+ describe("VirtualFileSystem", () => {
118
+ let fs: VirtualFileSystem<TestCtx>;
120
119
 
121
120
  beforeEach(() => {
122
121
  const { resolver } = createMockResolver();
123
- fs = new VirtualSandboxFileSystem(sampleTree, resolver, ctx);
122
+ fs = new VirtualFileSystem(sampleTree, resolver, ctx);
124
123
  });
125
124
 
126
125
  // --- exists / stat ---
@@ -327,132 +326,18 @@ describe("VirtualSandboxFileSystem", () => {
327
326
  });
328
327
 
329
328
  // ============================================================================
330
- // createVirtualSandbox
329
+ // createVirtualFsActivities
331
330
  // ============================================================================
332
331
 
333
- describe("createVirtualSandbox", () => {
334
- it("creates a sandbox with correct capabilities", () => {
332
+ describe("createVirtualFsActivities", () => {
333
+ it("creates prefixed activity with resolveFileTree", async () => {
335
334
  const { resolver } = createMockResolver();
336
- const sandbox = createVirtualSandbox("test-id", sampleTree, resolver, ctx);
337
- expect(sandbox.capabilities.filesystem).toBe(true);
338
- expect(sandbox.capabilities.execution).toBe(false);
339
- expect(sandbox.capabilities.persistence).toBe(true);
340
- });
341
-
342
- it("exec throws SandboxNotSupportedError", async () => {
343
- const { resolver } = createMockResolver();
344
- const sandbox = createVirtualSandbox("test-id", sampleTree, resolver, ctx);
345
- await expect(sandbox.exec("ls")).rejects.toThrow(SandboxNotSupportedError);
346
- });
347
-
348
- it("fs operations work through the sandbox", async () => {
349
- const { resolver } = createMockResolver();
350
- const sandbox = createVirtualSandbox("test-id", sampleTree, resolver, ctx);
351
- const content = await sandbox.fs.readFile("/README.md");
352
- expect(content).toBe("# README\nThis is a readme.");
353
- });
354
-
355
- it("getMutations is accessible", async () => {
356
- const { resolver } = createMockResolver();
357
- const sandbox = createVirtualSandbox("test-id", sampleTree, resolver, ctx);
358
- await sandbox.fs.writeFile("/new.txt", "hi");
359
- expect(sandbox.fs.getMutations()).toHaveLength(1);
360
- });
361
- });
362
-
363
- // ============================================================================
364
- // VirtualSandboxProvider
365
- // ============================================================================
366
-
367
- describe("VirtualSandboxProvider", () => {
368
- it("create resolves entries and returns sandbox + stateUpdate", async () => {
369
- const { resolver } = createMockResolver();
370
- const provider = new VirtualSandboxProvider(resolver);
371
- const { sandbox, stateUpdate } = await provider.create({
372
- resolverContext: ctx,
373
- });
374
- expect(sandbox.capabilities.filesystem).toBe(true);
375
- expect(sandbox.capabilities.execution).toBe(false);
376
- const content = await sandbox.fs.readFile("/resolved/file-1.txt");
377
- expect(content).toBe('console.log("hello");');
378
-
379
- expect(stateUpdate).toBeDefined();
380
- expect(stateUpdate?.resolverContext).toEqual(ctx);
381
- expect(Array.isArray(stateUpdate?.fileTree)).toBe(true);
382
- expect((stateUpdate?.fileTree as FileEntry[]).length).toBe(3);
383
- });
384
-
385
- it("create uses provided id as sandbox id", async () => {
386
- const { resolver } = createMockResolver();
387
- const provider = new VirtualSandboxProvider(resolver);
388
- const { sandbox, stateUpdate } = await provider.create({
389
- id: "my-sandbox",
390
- resolverContext: ctx,
391
- });
392
- expect(sandbox.id).toBe("my-sandbox");
393
- expect(stateUpdate?.sandboxId).toBe("my-sandbox");
394
- });
395
-
396
- it("get throws (state lives in workflow)", async () => {
397
- const { resolver } = createMockResolver();
398
- const provider = new VirtualSandboxProvider(resolver);
399
- await expect(provider.get()).rejects.toThrow("Sandbox does not support");
400
- });
401
-
402
- it("snapshot throws (state lives in workflow)", async () => {
403
- const { resolver } = createMockResolver();
404
- const provider = new VirtualSandboxProvider(resolver);
405
- await expect(provider.snapshot()).rejects.toThrow(
406
- "Sandbox does not support",
407
- );
408
- });
409
-
410
- it("restore throws (state lives in workflow)", async () => {
411
- const { resolver } = createMockResolver();
412
- const provider = new VirtualSandboxProvider(resolver);
413
- await expect(provider.restore()).rejects.toThrow(
414
- "Sandbox does not support",
415
- );
416
- });
417
-
418
- it("destroy is a no-op", async () => {
419
- const { resolver } = createMockResolver();
420
- const provider = new VirtualSandboxProvider(resolver);
421
- await expect(provider.destroy()).resolves.not.toThrow();
422
- });
423
-
424
- it("create throws without required options", async () => {
425
- const { resolver } = createMockResolver();
426
- const provider = new VirtualSandboxProvider(resolver);
427
- await expect(provider.create()).rejects.toThrow(
428
- "requires resolverContext",
429
- );
430
- });
431
-
432
- it("create seeds initialFiles via writeFile into sandbox and fileTree", async () => {
433
- const { resolver } = createMockResolver();
434
- const provider = new VirtualSandboxProvider(resolver);
435
- const { sandbox, stateUpdate } = await provider.create({
436
- resolverContext: ctx,
437
- initialFiles: {
438
- "/skills/my-skill/SKILL.md": "---\nname: my-skill\n---\nDo things.",
439
- "/skills/my-skill/references/guide.md": "# Guide\nStep 1...",
440
- },
441
- });
442
-
443
- expect(await sandbox.fs.readFile("/skills/my-skill/SKILL.md")).toBe(
444
- "---\nname: my-skill\n---\nDo things.",
445
- );
446
- expect(await sandbox.fs.readFile("/skills/my-skill/references/guide.md")).toBe(
447
- "# Guide\nStep 1...",
448
- );
449
- expect(await sandbox.fs.exists("/skills/my-skill")).toBe(true);
450
- expect(await sandbox.fs.exists("/skills/my-skill/references")).toBe(true);
335
+ const activities = createVirtualFsActivities(resolver, "codingAgent");
451
336
 
452
- const tree = stateUpdate?.fileTree as FileEntry[];
453
- expect(tree.find((e) => e.path === "/skills/my-skill/SKILL.md")).toBeDefined();
454
- expect(tree.find((e) => e.path === "/skills/my-skill/references/guide.md")).toBeDefined();
455
- expect(stateUpdate).not.toHaveProperty("localFiles");
337
+ expect(activities).toHaveProperty("codingAgentResolveFileTree");
338
+ const result = await activities.codingAgentResolveFileTree(ctx);
339
+ expect(result.fileTree).toHaveLength(3);
340
+ expect(result.fileTree[0]?.path).toMatch(/^\/resolved\//);
456
341
  });
457
342
  });
458
343
 
@@ -0,0 +1,94 @@
1
+ import type { WorkflowClient } from "@temporalio/client";
2
+ import { queryParentWorkflowState } from "../activity";
3
+ import type { JsonValue } from "../state/types";
4
+ import type { ActivityToolHandler, RouterContext } from "../tool-router/types";
5
+ import type {
6
+ FileEntryMetadata,
7
+ FileResolver,
8
+ TreeMutation,
9
+ VirtualFsContext,
10
+ VirtualFsState,
11
+ } from "./types";
12
+ import { VirtualFileSystem } from "./filesystem";
13
+
14
+ /**
15
+ * Wraps a tool handler that needs a virtual filesystem, automatically querying
16
+ * the parent workflow for the current file tree and resolver context.
17
+ *
18
+ * On each invocation the wrapper:
19
+ * 1. Queries the workflow's `AgentState` for `fileTree`, `resolverContext`, and `workspaceBase`
20
+ * 2. Creates an ephemeral {@link VirtualFileSystem} from tree + resolver
21
+ * 3. Runs the inner handler
22
+ * 4. Returns the handler's result together with any {@link TreeMutation}s
23
+ *
24
+ * The consumer applies mutations back to workflow state via a post-tool hook.
25
+ *
26
+ * @param client - Temporal `WorkflowClient` for querying the parent workflow
27
+ * @param resolver - {@link FileResolver} bridging to the consumer's data layer
28
+ * @param handler - Inner handler expecting a {@link VirtualFsContext}
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * import { withVirtualFs, type VirtualFsContext } from 'zeitlich';
33
+ *
34
+ * const readHandler: ActivityToolHandler<FileReadArgs, ReadResult, VirtualFsContext> =
35
+ * async (args, { virtualFs }) => {
36
+ * const content = await virtualFs.readFile(args.path);
37
+ * return { toolResponse: content, data: { path: args.path, content } };
38
+ * };
39
+ *
40
+ * // At activity registration:
41
+ * const handler = withVirtualFs(client, resolver, readHandler);
42
+ * ```
43
+ */
44
+ export function withVirtualFs<
45
+ TArgs,
46
+ TResult,
47
+ TCtx,
48
+ TMeta = FileEntryMetadata,
49
+ TToolResponse = JsonValue,
50
+ >(
51
+ client: WorkflowClient,
52
+ resolver: FileResolver<TCtx, TMeta>,
53
+ handler: ActivityToolHandler<
54
+ TArgs,
55
+ TResult,
56
+ VirtualFsContext<TCtx, TMeta>,
57
+ TToolResponse
58
+ >
59
+ ): ActivityToolHandler<
60
+ TArgs,
61
+ (TResult & { treeMutations: TreeMutation<TMeta>[] }) | null,
62
+ RouterContext,
63
+ TToolResponse | string
64
+ > {
65
+ return async (args, context) => {
66
+ const state =
67
+ await queryParentWorkflowState<VirtualFsState<TCtx, TMeta>>(client);
68
+
69
+ const { fileTree, resolverContext, workspaceBase } = state;
70
+ if (!fileTree) {
71
+ return {
72
+ toolResponse: `Error: No fileTree in agent state. The ${context.toolName} tool requires a virtual filesystem.`,
73
+ data: null,
74
+ };
75
+ }
76
+
77
+ const virtualFs = new VirtualFileSystem(
78
+ fileTree,
79
+ resolver,
80
+ resolverContext,
81
+ workspaceBase ?? "/",
82
+ );
83
+ const response = await handler(args, { ...context, virtualFs });
84
+ const mutations = virtualFs.getMutations();
85
+
86
+ return {
87
+ toolResponse: response.toolResponse,
88
+ data: {
89
+ ...(response.data ?? {}),
90
+ treeMutations: mutations,
91
+ } as TResult & { treeMutations: TreeMutation<TMeta>[] },
92
+ };
93
+ };
94
+ }
package/src/workflow.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * // In your workflow file
10
10
  * import { createSession, defineWorkflow, defineTool, bashTool } from 'zeitlich/workflow';
11
11
  * import { proxyLangChainThreadOps } from 'zeitlich/adapters/thread/langchain/workflow';
12
- * import { proxyVirtualSandboxOps } from 'zeitlich/adapters/sandbox/virtual/workflow';
12
+ * import { proxyVirtualFsOps } from 'zeitlich/workflow';
13
13
  * ```
14
14
  */
15
15
 
@@ -93,6 +93,20 @@ export type {
93
93
  PostHumanMessageAppendHookContext,
94
94
  } from "./lib/hooks";
95
95
 
96
+ // Observability
97
+ export {
98
+ createObservabilityHooks,
99
+ composeHooks,
100
+ } from "./lib/observability";
101
+ export type {
102
+ ObservabilityHooks,
103
+ ZeitlichObservabilitySinks,
104
+ SessionStartedEvent,
105
+ SessionEndedEvent,
106
+ TurnCompletedEvent,
107
+ ToolExecutedEvent,
108
+ } from "./lib/observability";
109
+
96
110
  // Core types
97
111
  export type {
98
112
  TokenUsage,
@@ -152,24 +166,27 @@ export {
152
166
  SandboxNotSupportedError,
153
167
  } from "./lib/sandbox/types";
154
168
 
155
- // Virtual sandbox (workflow-safe — imported from leaf modules to avoid
156
- // pulling activity-side code like VirtualSandboxFileSystem / Provider).
157
- export { applyVirtualTreeMutations } from "./adapters/sandbox/virtual/mutations";
158
- export { formatVirtualFileTree } from "./adapters/sandbox/virtual/tree";
169
+ // Virtual filesystem (workflow-safe — imported from leaf modules to avoid
170
+ // pulling activity-side code like VirtualFileSystem).
171
+ export { applyVirtualTreeMutations } from "./lib/virtual-fs/mutations";
172
+ export { formatVirtualFileTree } from "./lib/virtual-fs/tree";
159
173
  export {
160
174
  hasFileWithMimeType,
161
175
  filesWithMimeType,
162
176
  hasDirectory,
163
- } from "./adapters/sandbox/virtual/queries";
177
+ } from "./lib/virtual-fs/queries";
178
+ export { proxyVirtualFsOps } from "./lib/virtual-fs/proxy";
164
179
 
165
180
  export type {
166
181
  FileEntry,
167
182
  FileEntryMetadata,
168
183
  FileResolver,
169
184
  VirtualFileTree,
170
- VirtualSandboxState,
185
+ VirtualFsOps,
186
+ PrefixedVirtualFsOps,
187
+ VirtualFsState,
171
188
  TreeMutation,
172
- } from "./adapters/sandbox/virtual/types";
189
+ } from "./lib/virtual-fs/types";
173
190
 
174
191
  // Subagent support
175
192
  export type { SubagentArgs } from "./lib/subagent";
package/tsup.config.ts CHANGED
@@ -18,8 +18,6 @@ export default defineConfig({
18
18
  "adapters/sandbox/inmemory/workflow": "src/adapters/sandbox/inmemory/proxy.ts",
19
19
  "adapters/sandbox/daytona/index": "src/adapters/sandbox/daytona/index.ts",
20
20
  "adapters/sandbox/daytona/workflow": "src/adapters/sandbox/daytona/proxy.ts",
21
- "adapters/sandbox/virtual/index": "src/adapters/sandbox/virtual/index.ts",
22
- "adapters/sandbox/virtual/workflow": "src/adapters/sandbox/virtual/proxy.ts",
23
21
  "adapters/sandbox/e2b/index": "src/adapters/sandbox/e2b/index.ts",
24
22
  "adapters/sandbox/e2b/workflow": "src/adapters/sandbox/e2b/proxy.ts",
25
23
  "adapters/sandbox/bedrock/index": "src/adapters/sandbox/bedrock/index.ts",