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.
- package/README.md +121 -13
- package/dist/{activities-3xj_fEJK.d.ts → activities-1xrWRrGJ.d.cts} +2 -3
- package/dist/{activities-BzYq6jf7.d.cts → activities-DOViDCTE.d.ts} +2 -3
- package/dist/adapters/thread/anthropic/index.d.cts +5 -6
- package/dist/adapters/thread/anthropic/index.d.ts +5 -6
- package/dist/adapters/thread/anthropic/workflow.d.cts +4 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +4 -5
- package/dist/adapters/thread/google-genai/index.d.cts +5 -6
- package/dist/adapters/thread/google-genai/index.d.ts +5 -6
- package/dist/adapters/thread/google-genai/workflow.d.cts +4 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +4 -5
- package/dist/adapters/thread/langchain/index.d.cts +5 -6
- package/dist/adapters/thread/langchain/index.d.ts +5 -6
- package/dist/adapters/thread/langchain/workflow.d.cts +4 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +4 -5
- package/dist/index.cjs +499 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -15
- package/dist/index.d.ts +66 -15
- package/dist/index.js +495 -10
- package/dist/index.js.map +1 -1
- package/dist/{proxy-7e7v8ccg.d.ts → proxy-78nc985d.d.ts} +1 -1
- package/dist/{proxy-CsB8r0RR.d.cts → proxy-Bm2UTiO_.d.cts} +1 -1
- package/dist/{thread-manager-D8C5QvLi.d.ts → thread-manager-07BaYu_z.d.ts} +1 -1
- package/dist/{thread-manager-DdVFl1IY.d.cts → thread-manager-BRE5KkHB.d.cts} +1 -1
- package/dist/{thread-manager-DFJ3sKKU.d.cts → thread-manager-CatBkarc.d.cts} +1 -1
- package/dist/{thread-manager-B5qA4v7V.d.ts → thread-manager-CxbWo7q_.d.ts} +1 -1
- package/dist/types-BkVoEyiH.d.ts +1211 -0
- package/dist/{types-BZ75HpYd.d.ts → types-DAv_SLN8.d.ts} +1 -1
- package/dist/{types-HbjqzyJH.d.cts → types-Dpz2gXLk.d.cts} +1 -1
- package/dist/types-seDYom4M.d.cts +1211 -0
- package/dist/workflow-B4T3la0p.d.cts +750 -0
- package/dist/workflow-DCmaXLZ_.d.ts +750 -0
- package/dist/workflow.cjs +171 -5
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +5 -579
- package/dist/workflow.d.ts +5 -579
- package/dist/workflow.js +170 -7
- package/dist/workflow.js.map +1 -1
- package/package.json +3 -23
- package/src/index.ts +7 -0
- package/src/lib/observability/hooks.ts +117 -0
- package/src/lib/observability/index.ts +13 -0
- package/src/lib/observability/sinks.ts +88 -0
- package/src/lib/sandbox/manager.ts +3 -3
- package/src/lib/session/session-edge-cases.integration.test.ts +1 -0
- package/src/lib/session/session.integration.test.ts +1 -0
- package/src/lib/session/session.ts +63 -0
- package/src/lib/session/types.ts +21 -0
- package/src/lib/state/manager.integration.test.ts +1 -0
- package/src/lib/subagent/handler.ts +17 -0
- package/src/lib/subagent/subagent.integration.test.ts +1 -0
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +2 -0
- package/src/lib/tool-router/router.integration.test.ts +2 -0
- package/src/lib/tool-router/router.ts +24 -2
- package/src/{adapters/sandbox/virtual → lib/virtual-fs}/filesystem.ts +4 -4
- package/src/lib/virtual-fs/index.ts +18 -0
- package/src/lib/virtual-fs/manager.ts +48 -0
- package/src/lib/virtual-fs/proxy.ts +45 -0
- package/src/{adapters/sandbox/virtual → lib/virtual-fs}/types.ts +43 -33
- package/src/{adapters/sandbox/virtual/virtual-sandbox.test.ts → lib/virtual-fs/virtual-fs.test.ts} +15 -130
- package/src/lib/virtual-fs/with-virtual-fs.ts +94 -0
- package/src/workflow.ts +25 -8
- package/tsup.config.ts +0 -2
- package/dist/adapters/sandbox/virtual/index.cjs +0 -487
- package/dist/adapters/sandbox/virtual/index.cjs.map +0 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +0 -90
- package/dist/adapters/sandbox/virtual/index.d.ts +0 -90
- package/dist/adapters/sandbox/virtual/index.js +0 -479
- package/dist/adapters/sandbox/virtual/index.js.map +0 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +0 -33
- package/dist/adapters/sandbox/virtual/workflow.cjs.map +0 -1
- package/dist/adapters/sandbox/virtual/workflow.d.cts +0 -28
- package/dist/adapters/sandbox/virtual/workflow.d.ts +0 -28
- package/dist/adapters/sandbox/virtual/workflow.js +0 -31
- package/dist/adapters/sandbox/virtual/workflow.js.map +0 -1
- package/dist/queries-DVnukByF.d.cts +0 -44
- package/dist/queries-kjlvsUfz.d.ts +0 -44
- package/dist/types-BclYm5Ic.d.cts +0 -581
- package/dist/types-BclYm5Ic.d.ts +0 -581
- package/dist/types-BgsAwN3L.d.cts +0 -125
- package/dist/types-BtqbM1bO.d.ts +0 -490
- package/dist/types-BuCEZ4dF.d.cts +0 -490
- package/dist/types-yU5AINiP.d.ts +0 -125
- package/src/adapters/sandbox/virtual/index.ts +0 -92
- package/src/adapters/sandbox/virtual/provider.ts +0 -121
- package/src/adapters/sandbox/virtual/proxy.ts +0 -53
- package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +0 -97
- package/src/lib/.env +0 -1
- package/src/tools/bash/.env +0 -1
- /package/src/{adapters/sandbox/virtual → lib/virtual-fs}/mutations.ts +0 -0
- /package/src/{adapters/sandbox/virtual → lib/virtual-fs}/queries.ts +0 -0
- /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 {
|
|
2
|
-
import type {
|
|
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
|
|
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
|
-
//
|
|
75
|
+
// VirtualFsOps — workflow-side activity interface
|
|
77
76
|
// ============================================================================
|
|
78
77
|
|
|
79
78
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
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
|
|
84
|
-
TCtx,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
118
|
+
* The portion of workflow `AgentState` that the virtual filesystem reads via
|
|
97
119
|
* {@link queryParentWorkflowState}. Populated automatically by the session
|
|
98
|
-
*
|
|
120
|
+
* when `virtualFs` config is provided.
|
|
99
121
|
*/
|
|
100
|
-
export interface
|
|
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
|
|
128
|
-
* Guarantees a live (ephemeral)
|
|
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
|
|
140
|
+
export interface VirtualFsContext<
|
|
131
141
|
TCtx = unknown,
|
|
132
142
|
TMeta = FileEntryMetadata,
|
|
133
143
|
> extends RouterContext {
|
|
134
|
-
|
|
144
|
+
virtualFs: VirtualFileSystem<TCtx, TMeta>;
|
|
135
145
|
}
|
package/src/{adapters/sandbox/virtual/virtual-sandbox.test.ts → lib/virtual-fs/virtual-fs.test.ts}
RENAMED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it, beforeEach } from "vitest";
|
|
2
2
|
import type { FileEntry, FileResolver } from "./types";
|
|
3
|
-
import {
|
|
4
|
-
import { createVirtualSandbox } from "./index";
|
|
3
|
+
import { VirtualFileSystem } from "./filesystem";
|
|
5
4
|
import { applyVirtualTreeMutations } from "./mutations";
|
|
6
|
-
import {
|
|
7
|
-
import { SandboxNotSupportedError } from "
|
|
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
|
-
//
|
|
114
|
+
// VirtualFileSystem
|
|
116
115
|
// ============================================================================
|
|
117
116
|
|
|
118
|
-
describe("
|
|
119
|
-
let fs:
|
|
117
|
+
describe("VirtualFileSystem", () => {
|
|
118
|
+
let fs: VirtualFileSystem<TestCtx>;
|
|
120
119
|
|
|
121
120
|
beforeEach(() => {
|
|
122
121
|
const { resolver } = createMockResolver();
|
|
123
|
-
fs = new
|
|
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
|
-
//
|
|
329
|
+
// createVirtualFsActivities
|
|
331
330
|
// ============================================================================
|
|
332
331
|
|
|
333
|
-
describe("
|
|
334
|
-
it("creates
|
|
332
|
+
describe("createVirtualFsActivities", () => {
|
|
333
|
+
it("creates prefixed activity with resolveFileTree", async () => {
|
|
335
334
|
const { resolver } = createMockResolver();
|
|
336
|
-
const
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
expect(
|
|
455
|
-
expect(
|
|
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 {
|
|
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
|
|
156
|
-
// pulling activity-side code like
|
|
157
|
-
export { applyVirtualTreeMutations } from "./
|
|
158
|
-
export { formatVirtualFileTree } from "./
|
|
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 "./
|
|
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
|
-
|
|
185
|
+
VirtualFsOps,
|
|
186
|
+
PrefixedVirtualFsOps,
|
|
187
|
+
VirtualFsState,
|
|
171
188
|
TreeMutation,
|
|
172
|
-
} from "./
|
|
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",
|