zeitlich 0.2.13 → 0.2.15
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 +61 -50
- package/dist/adapters/sandbox/daytona/index.cjs +205 -0
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -0
- package/dist/adapters/sandbox/daytona/index.d.cts +86 -0
- package/dist/adapters/sandbox/daytona/index.d.ts +86 -0
- package/dist/adapters/sandbox/daytona/index.js +202 -0
- package/dist/adapters/sandbox/daytona/index.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +174 -0
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.d.cts +28 -0
- package/dist/adapters/sandbox/inmemory/index.d.ts +28 -0
- package/dist/adapters/sandbox/inmemory/index.js +172 -0
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +405 -0
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -0
- package/dist/adapters/sandbox/virtual/index.d.cts +85 -0
- package/dist/adapters/sandbox/virtual/index.d.ts +85 -0
- package/dist/adapters/sandbox/virtual/index.js +400 -0
- package/dist/adapters/sandbox/virtual/index.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +306 -0
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -0
- package/dist/adapters/thread/google-genai/index.d.cts +145 -0
- package/dist/adapters/thread/google-genai/index.d.ts +145 -0
- package/dist/adapters/thread/google-genai/index.js +300 -0
- package/dist/adapters/thread/google-genai/index.js.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.cjs +29 -9
- package/dist/adapters/thread/langchain/index.cjs.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.d.cts +17 -21
- package/dist/adapters/{langchain → thread/langchain}/index.d.ts +17 -21
- package/dist/adapters/{langchain → thread/langchain}/index.js +29 -9
- package/dist/adapters/thread/langchain/index.js.map +1 -0
- package/dist/index.cjs +866 -567
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -74
- package/dist/index.d.ts +235 -74
- package/dist/index.js +854 -562
- package/dist/index.js.map +1 -1
- package/dist/{thread-manager-qc0g5Rvd.d.cts → types-35POpVfa.d.cts} +7 -6
- package/dist/{thread-manager-qc0g5Rvd.d.ts → types-35POpVfa.d.ts} +7 -6
- package/dist/types-BMXzv7TN.d.cts +476 -0
- package/dist/types-BMXzv7TN.d.ts +476 -0
- package/dist/types-BVP87m_W.d.cts +121 -0
- package/dist/types-BWvIYK28.d.ts +391 -0
- package/dist/types-CDubRtad.d.cts +115 -0
- package/dist/types-CDubRtad.d.ts +115 -0
- package/dist/types-CwwgQ_9H.d.ts +121 -0
- package/dist/types-Dje1TdH6.d.cts +391 -0
- package/dist/workflow.cjs +460 -321
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +271 -222
- package/dist/workflow.d.ts +271 -222
- package/dist/workflow.js +456 -319
- package/dist/workflow.js.map +1 -1
- package/package.json +65 -8
- package/src/adapters/sandbox/daytona/filesystem.ts +136 -0
- package/src/adapters/sandbox/daytona/index.ts +149 -0
- package/src/adapters/sandbox/daytona/types.ts +34 -0
- package/src/adapters/sandbox/inmemory/index.ts +213 -0
- package/src/adapters/sandbox/virtual/filesystem.ts +345 -0
- package/src/adapters/sandbox/virtual/index.ts +88 -0
- package/src/adapters/sandbox/virtual/mutations.ts +38 -0
- package/src/adapters/sandbox/virtual/provider.ts +101 -0
- package/src/adapters/sandbox/virtual/tree.ts +82 -0
- package/src/adapters/sandbox/virtual/types.ts +127 -0
- package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +523 -0
- package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +91 -0
- package/src/adapters/thread/google-genai/activities.ts +132 -0
- package/src/adapters/thread/google-genai/index.ts +41 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +154 -0
- package/src/adapters/thread/google-genai/thread-manager.ts +169 -0
- package/src/adapters/{langchain → thread/langchain}/activities.ts +22 -15
- package/src/adapters/{langchain → thread/langchain}/index.ts +1 -1
- package/src/adapters/{langchain → thread/langchain}/model-invoker.ts +15 -18
- package/src/adapters/{langchain → thread/langchain}/thread-manager.ts +1 -1
- package/src/index.ts +32 -24
- package/src/lib/activity.ts +87 -0
- package/src/lib/hooks/index.ts +11 -0
- package/src/lib/hooks/types.ts +98 -0
- package/src/lib/model/helpers.ts +6 -0
- package/src/lib/model/index.ts +13 -0
- package/src/lib/{model-invoker.ts → model/types.ts} +18 -1
- package/src/lib/sandbox/index.ts +19 -0
- package/src/lib/sandbox/manager.ts +76 -0
- package/src/lib/sandbox/sandbox.test.ts +158 -0
- package/src/lib/{fs.ts → sandbox/tree.ts} +6 -6
- package/src/lib/sandbox/types.ts +164 -0
- package/src/lib/session/index.ts +11 -0
- package/src/lib/{session.ts → session/session.ts} +83 -50
- package/src/lib/session/types.ts +95 -0
- package/src/lib/skills/fs-provider.ts +16 -15
- package/src/lib/skills/handler.ts +31 -0
- package/src/lib/skills/index.ts +5 -1
- package/src/lib/skills/register.ts +20 -0
- package/src/lib/skills/tool.ts +47 -0
- package/src/lib/state/index.ts +9 -0
- package/src/lib/{state-manager.ts → state/manager.ts} +10 -147
- package/src/lib/state/types.ts +134 -0
- package/src/lib/subagent/define.ts +71 -0
- package/src/lib/subagent/handler.ts +99 -0
- package/src/lib/subagent/index.ts +13 -0
- package/src/lib/subagent/register.ts +68 -0
- package/src/lib/subagent/tool.ts +80 -0
- package/src/lib/subagent/types.ts +92 -0
- package/src/lib/thread/index.ts +7 -0
- package/src/lib/{thread-manager.ts → thread/manager.ts} +20 -33
- package/src/lib/thread/types.ts +39 -0
- package/src/lib/tool-router/auto-append.ts +55 -0
- package/src/lib/tool-router/index.ts +41 -0
- package/src/lib/tool-router/router.ts +462 -0
- package/src/lib/tool-router/types.ts +478 -0
- package/src/lib/tool-router/with-sandbox.ts +70 -0
- package/src/lib/types.ts +5 -382
- package/src/tools/bash/bash.test.ts +53 -55
- package/src/tools/bash/handler.ts +23 -51
- package/src/tools/edit/handler.ts +67 -81
- package/src/tools/glob/handler.ts +60 -17
- package/src/tools/read-file/handler.ts +67 -0
- package/src/tools/read-skill/handler.ts +1 -31
- package/src/tools/read-skill/tool.ts +5 -47
- package/src/tools/subagent/handler.ts +1 -100
- package/src/tools/subagent/tool.ts +5 -93
- package/src/tools/task-create/handler.ts +1 -1
- package/src/tools/task-get/handler.ts +1 -1
- package/src/tools/task-list/handler.ts +1 -1
- package/src/tools/task-update/handler.ts +1 -1
- package/src/tools/write-file/handler.ts +47 -0
- package/src/workflow.ts +88 -47
- package/tsup.config.ts +8 -1
- package/dist/adapters/langchain/index.cjs.map +0 -1
- package/dist/adapters/langchain/index.js.map +0 -1
- package/dist/model-invoker-y_zlyMqu.d.cts +0 -892
- package/dist/model-invoker-y_zlyMqu.d.ts +0 -892
- package/src/lib/tool-router.ts +0 -977
- package/src/lib/workflow-helpers.ts +0 -50
- /package/src/lib/{thread-id.ts → thread/id.ts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SandboxFileSystem } from "./types";
|
|
2
2
|
|
|
3
3
|
const basename = (path: string, separator: string): string => {
|
|
4
4
|
if (path[path.length - 1] === separator) path = path.slice(0, -1);
|
|
@@ -25,10 +25,10 @@ const printTree = async (
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Generates a formatted file tree string from
|
|
28
|
+
* Generates a formatted file tree string from a {@link SandboxFileSystem}.
|
|
29
29
|
* Useful for including filesystem context in agent prompts.
|
|
30
30
|
*
|
|
31
|
-
* @param fs -
|
|
31
|
+
* @param fs - Sandbox filesystem implementation
|
|
32
32
|
* @param opts - Optional configuration for tree generation
|
|
33
33
|
* @param opts.dir - Root directory to start from (defaults to `/`)
|
|
34
34
|
* @param opts.separator - Path separator (`/` or `\\`, defaults to `/`)
|
|
@@ -40,7 +40,7 @@ const printTree = async (
|
|
|
40
40
|
* ```typescript
|
|
41
41
|
* import { toTree } from 'zeitlich';
|
|
42
42
|
*
|
|
43
|
-
* const fileTree = await toTree(
|
|
43
|
+
* const fileTree = await toTree(sandbox.fs);
|
|
44
44
|
* // Returns:
|
|
45
45
|
* // /
|
|
46
46
|
* // ├─ src/
|
|
@@ -50,7 +50,7 @@ const printTree = async (
|
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
52
|
export const toTree = async (
|
|
53
|
-
fs:
|
|
53
|
+
fs: SandboxFileSystem,
|
|
54
54
|
opts: {
|
|
55
55
|
dir?: string;
|
|
56
56
|
separator?: "/" | "\\";
|
|
@@ -67,7 +67,7 @@ export const toTree = async (
|
|
|
67
67
|
const sort = opts.sort ?? true;
|
|
68
68
|
let subtree = " (...)";
|
|
69
69
|
if (depth > 0) {
|
|
70
|
-
const list =
|
|
70
|
+
const list = await fs.readdirWithFileTypes(dir);
|
|
71
71
|
if (sort) {
|
|
72
72
|
list.sort((a, b) => {
|
|
73
73
|
if (a.isDirectory && b.isDirectory) {
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Sandbox Filesystem
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
export interface DirentEntry {
|
|
6
|
+
name: string;
|
|
7
|
+
isFile: boolean;
|
|
8
|
+
isDirectory: boolean;
|
|
9
|
+
isSymbolicLink: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FileStat {
|
|
13
|
+
isFile: boolean;
|
|
14
|
+
isDirectory: boolean;
|
|
15
|
+
isSymbolicLink: boolean;
|
|
16
|
+
size: number;
|
|
17
|
+
mtime: Date;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Provider-agnostic filesystem interface.
|
|
22
|
+
*
|
|
23
|
+
* Implementations that don't support a method should throw
|
|
24
|
+
* {@link SandboxNotSupportedError}.
|
|
25
|
+
*/
|
|
26
|
+
export interface SandboxFileSystem {
|
|
27
|
+
readFile(path: string): Promise<string>;
|
|
28
|
+
readFileBuffer(path: string): Promise<Uint8Array>;
|
|
29
|
+
writeFile(path: string, content: string | Uint8Array): Promise<void>;
|
|
30
|
+
appendFile(path: string, content: string | Uint8Array): Promise<void>;
|
|
31
|
+
exists(path: string): Promise<boolean>;
|
|
32
|
+
stat(path: string): Promise<FileStat>;
|
|
33
|
+
mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
|
|
34
|
+
readdir(path: string): Promise<string[]>;
|
|
35
|
+
readdirWithFileTypes(path: string): Promise<DirentEntry[]>;
|
|
36
|
+
rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
|
|
37
|
+
cp(src: string, dest: string, options?: { recursive?: boolean }): Promise<void>;
|
|
38
|
+
mv(src: string, dest: string): Promise<void>;
|
|
39
|
+
readlink(path: string): Promise<string>;
|
|
40
|
+
resolvePath(base: string, path: string): string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Execution
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
export interface ExecOptions {
|
|
48
|
+
timeout?: number;
|
|
49
|
+
cwd?: string;
|
|
50
|
+
env?: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ExecResult {
|
|
54
|
+
exitCode: number;
|
|
55
|
+
stdout: string;
|
|
56
|
+
stderr: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Capabilities
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
export interface SandboxCapabilities {
|
|
64
|
+
/** Sandbox supports filesystem operations */
|
|
65
|
+
filesystem: boolean;
|
|
66
|
+
/** Sandbox supports shell/command execution */
|
|
67
|
+
execution: boolean;
|
|
68
|
+
/** Sandbox state can be persisted and restored */
|
|
69
|
+
persistence: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Sandbox
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
export interface Sandbox {
|
|
77
|
+
readonly id: string;
|
|
78
|
+
readonly capabilities: SandboxCapabilities;
|
|
79
|
+
readonly fs: SandboxFileSystem;
|
|
80
|
+
|
|
81
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
82
|
+
destroy(): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Snapshots
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
export interface SandboxSnapshot {
|
|
90
|
+
sandboxId: string;
|
|
91
|
+
providerId: string;
|
|
92
|
+
/** Provider-specific serialised state */
|
|
93
|
+
data: unknown;
|
|
94
|
+
createdAt: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Provider
|
|
99
|
+
// ============================================================================
|
|
100
|
+
|
|
101
|
+
export interface SandboxCreateOptions {
|
|
102
|
+
/** Preferred sandbox ID (provider may ignore) */
|
|
103
|
+
id?: string;
|
|
104
|
+
/** Seed the filesystem with these files */
|
|
105
|
+
initialFiles?: Record<string, string | Uint8Array>;
|
|
106
|
+
/** Environment variables available inside the sandbox */
|
|
107
|
+
env?: Record<string, string>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface SandboxCreateResult {
|
|
111
|
+
sandbox: Sandbox;
|
|
112
|
+
/** Optional state to merge into the workflow's `AgentState` via the session. */
|
|
113
|
+
stateUpdate?: Record<string, unknown>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface SandboxProvider<
|
|
117
|
+
TOptions extends SandboxCreateOptions = SandboxCreateOptions,
|
|
118
|
+
TSandbox extends Sandbox = Sandbox,
|
|
119
|
+
> {
|
|
120
|
+
readonly id: string;
|
|
121
|
+
readonly capabilities: SandboxCapabilities;
|
|
122
|
+
|
|
123
|
+
create(options?: TOptions): Promise<SandboxCreateResult>;
|
|
124
|
+
get(sandboxId: string): Promise<TSandbox>;
|
|
125
|
+
destroy(sandboxId: string): Promise<void>;
|
|
126
|
+
snapshot(sandboxId: string): Promise<SandboxSnapshot>;
|
|
127
|
+
restore(snapshot: SandboxSnapshot): Promise<Sandbox>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// SandboxOps — workflow-side activity interface (like ThreadOps)
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
export interface SandboxOps<
|
|
135
|
+
TOptions extends SandboxCreateOptions = SandboxCreateOptions,
|
|
136
|
+
> {
|
|
137
|
+
createSandbox(
|
|
138
|
+
options?: TOptions,
|
|
139
|
+
): Promise<{ sandboxId: string; stateUpdate?: Record<string, unknown> }>;
|
|
140
|
+
destroySandbox(sandboxId: string): Promise<void>;
|
|
141
|
+
snapshotSandbox(sandboxId: string): Promise<SandboxSnapshot>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Errors
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
import { ApplicationFailure } from "@temporalio/common";
|
|
149
|
+
|
|
150
|
+
export class SandboxNotSupportedError extends ApplicationFailure {
|
|
151
|
+
constructor(operation: string) {
|
|
152
|
+
super(
|
|
153
|
+
`Sandbox does not support: ${operation}`,
|
|
154
|
+
"SandboxNotSupportedError",
|
|
155
|
+
true,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class SandboxNotFoundError extends ApplicationFailure {
|
|
161
|
+
constructor(sandboxId: string) {
|
|
162
|
+
super(`Sandbox not found: ${sandboxId}`, "SandboxNotFoundError", true);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -5,42 +5,15 @@ import {
|
|
|
5
5
|
setHandler,
|
|
6
6
|
ApplicationFailure,
|
|
7
7
|
} from "@temporalio/workflow";
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
createToolRouter,
|
|
19
|
-
type ParsedToolCallUnion,
|
|
20
|
-
type ToolMap,
|
|
21
|
-
} from "./tool-router";
|
|
22
|
-
import type { MessageContent } from "./types";
|
|
23
|
-
import { getShortId } from "./thread-id";
|
|
24
|
-
|
|
25
|
-
export interface ZeitlichSession<M = unknown> {
|
|
26
|
-
runSession<T extends JsonSerializable<T>>(args: {
|
|
27
|
-
stateManager: AgentStateManager<T>;
|
|
28
|
-
}): Promise<{
|
|
29
|
-
finalMessage: M | null;
|
|
30
|
-
exitReason: SessionExitReason;
|
|
31
|
-
usage: ReturnType<AgentStateManager<T>["getTotalUsage"]>;
|
|
32
|
-
}>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Session-level hooks for lifecycle events
|
|
37
|
-
*/
|
|
38
|
-
export interface SessionLifecycleHooks {
|
|
39
|
-
/** Called when session starts */
|
|
40
|
-
onSessionStart?: SessionStartHook;
|
|
41
|
-
/** Called when session ends */
|
|
42
|
-
onSessionEnd?: SessionEndHook;
|
|
43
|
-
}
|
|
8
|
+
import type { SessionExitReason, MessageContent } from "../types";
|
|
9
|
+
import type { ThreadOps, SessionConfig, ZeitlichSession } from "./types";
|
|
10
|
+
import type { SandboxOps } from "../sandbox/types";
|
|
11
|
+
import { type AgentStateManager, type JsonSerializable } from "../state/types";
|
|
12
|
+
import { createToolRouter } from "../tool-router/router";
|
|
13
|
+
import type { ParsedToolCallUnion, ToolMap } from "../tool-router/types";
|
|
14
|
+
import { getShortId } from "../thread/id";
|
|
15
|
+
import { buildSubagentRegistration } from "../subagent/register";
|
|
16
|
+
import { buildSkillRegistration } from "../skills/register";
|
|
44
17
|
|
|
45
18
|
/**
|
|
46
19
|
* Creates an agent session that manages the agent loop: LLM invocation,
|
|
@@ -89,27 +62,40 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
89
62
|
appendSystemPrompt = true,
|
|
90
63
|
continueThread = false,
|
|
91
64
|
waitForInputTimeout = "48h",
|
|
92
|
-
|
|
93
|
-
|
|
65
|
+
sandbox: sandboxOps,
|
|
66
|
+
sandboxId: inheritedSandboxId,
|
|
67
|
+
}: SessionConfig<T, M>): Promise<ZeitlichSession<M>> => {
|
|
68
|
+
const sourceThreadId = continueThread ? providedThreadId : undefined;
|
|
69
|
+
const threadId =
|
|
70
|
+
continueThread && providedThreadId ? getShortId() : (providedThreadId ?? getShortId());
|
|
94
71
|
|
|
95
72
|
const {
|
|
96
73
|
appendToolResult,
|
|
97
74
|
appendHumanMessage,
|
|
98
75
|
initializeThread,
|
|
99
76
|
appendSystemMessage,
|
|
77
|
+
forkThread,
|
|
100
78
|
} = threadOps ?? proxyDefaultThreadOps();
|
|
101
79
|
|
|
80
|
+
const plugins: ToolMap[string][] = [];
|
|
81
|
+
if (subagents) {
|
|
82
|
+
const reg = buildSubagentRegistration(subagents);
|
|
83
|
+
if (reg) plugins.push(reg);
|
|
84
|
+
}
|
|
85
|
+
if (skills) {
|
|
86
|
+
const reg = buildSkillRegistration(skills);
|
|
87
|
+
if (reg) plugins.push(reg);
|
|
88
|
+
}
|
|
89
|
+
|
|
102
90
|
const toolRouter = createToolRouter({
|
|
103
91
|
tools,
|
|
104
92
|
appendToolResult,
|
|
105
93
|
threadId,
|
|
106
94
|
hooks,
|
|
107
|
-
|
|
108
|
-
skills,
|
|
95
|
+
plugins,
|
|
109
96
|
parallel: processToolsInParallel,
|
|
110
97
|
});
|
|
111
98
|
|
|
112
|
-
// Helper to call session end hook
|
|
113
99
|
const callSessionEnd = async (
|
|
114
100
|
exitReason: SessionExitReason,
|
|
115
101
|
turns: number
|
|
@@ -126,12 +112,15 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
126
112
|
};
|
|
127
113
|
|
|
128
114
|
return {
|
|
129
|
-
runSession: async ({
|
|
115
|
+
runSession: async <TState extends JsonSerializable<TState>>({
|
|
130
116
|
stateManager,
|
|
117
|
+
}: {
|
|
118
|
+
stateManager: AgentStateManager<TState>;
|
|
131
119
|
}): Promise<{
|
|
120
|
+
threadId: string;
|
|
132
121
|
finalMessage: M | null;
|
|
133
122
|
exitReason: SessionExitReason;
|
|
134
|
-
usage: ReturnType<
|
|
123
|
+
usage: ReturnType<AgentStateManager<TState>["getTotalUsage"]>;
|
|
135
124
|
}> => {
|
|
136
125
|
setHandler(
|
|
137
126
|
defineUpdate<unknown, [MessageContent]>(`add${agentName}Message`),
|
|
@@ -153,6 +142,19 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
153
142
|
}
|
|
154
143
|
);
|
|
155
144
|
|
|
145
|
+
// --- Sandbox lifecycle: create or inherit ---
|
|
146
|
+
let sandboxId: string | undefined = inheritedSandboxId;
|
|
147
|
+
const ownsSandbox = !sandboxId && !!sandboxOps;
|
|
148
|
+
if (ownsSandbox) {
|
|
149
|
+
const result = await sandboxOps.createSandbox({ id: threadId });
|
|
150
|
+
sandboxId = result.sandboxId;
|
|
151
|
+
if (result.stateUpdate) {
|
|
152
|
+
stateManager.mergeUpdate(
|
|
153
|
+
result.stateUpdate as Partial<TState>,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
156
158
|
if (hooks.onSessionStart) {
|
|
157
159
|
await hooks.onSessionStart({
|
|
158
160
|
threadId,
|
|
@@ -163,7 +165,9 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
163
165
|
|
|
164
166
|
const systemPrompt = stateManager.getSystemPrompt();
|
|
165
167
|
|
|
166
|
-
if (
|
|
168
|
+
if (continueThread && sourceThreadId) {
|
|
169
|
+
await forkThread(sourceThreadId, threadId);
|
|
170
|
+
} else {
|
|
167
171
|
if (appendSystemPrompt) {
|
|
168
172
|
if (!systemPrompt || systemPrompt.trim() === "") {
|
|
169
173
|
throw ApplicationFailure.create({
|
|
@@ -201,18 +205,17 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
201
205
|
stateManager.updateUsage(usage);
|
|
202
206
|
}
|
|
203
207
|
|
|
204
|
-
// No tools configured - treat any non-end_turn as completed
|
|
205
208
|
if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
|
|
206
209
|
stateManager.complete();
|
|
207
210
|
exitReason = "completed";
|
|
208
211
|
return {
|
|
212
|
+
threadId,
|
|
209
213
|
finalMessage: message,
|
|
210
214
|
exitReason,
|
|
211
215
|
usage: stateManager.getTotalUsage(),
|
|
212
216
|
};
|
|
213
217
|
}
|
|
214
218
|
|
|
215
|
-
// Parse all tool calls uniformly through the router
|
|
216
219
|
const parsedToolCalls: ParsedToolCallUnion<T>[] = [];
|
|
217
220
|
for (const tc of rawToolCalls) {
|
|
218
221
|
try {
|
|
@@ -229,11 +232,11 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
|
|
232
|
-
// Hooks can call stateManager.waitForInput() to pause the session
|
|
233
235
|
const toolCallResults = await toolRouter.processToolCalls(
|
|
234
236
|
parsedToolCalls,
|
|
235
237
|
{
|
|
236
238
|
turn: currentTurn,
|
|
239
|
+
...(sandboxId !== undefined && { sandboxId }),
|
|
237
240
|
}
|
|
238
241
|
);
|
|
239
242
|
|
|
@@ -250,14 +253,12 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
250
253
|
);
|
|
251
254
|
if (!conditionMet) {
|
|
252
255
|
stateManager.cancel();
|
|
253
|
-
// Wait briefly to allow pending waitForStateChange handlers to complete
|
|
254
256
|
await condition(() => false, "2s");
|
|
255
257
|
break;
|
|
256
258
|
}
|
|
257
259
|
}
|
|
258
260
|
}
|
|
259
261
|
|
|
260
|
-
// Check if we hit max turns
|
|
261
262
|
if (stateManager.getTurns() >= maxTurns && stateManager.isRunning()) {
|
|
262
263
|
exitReason = "max_turns";
|
|
263
264
|
}
|
|
@@ -265,11 +266,15 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
265
266
|
exitReason = "failed";
|
|
266
267
|
throw ApplicationFailure.fromError(error);
|
|
267
268
|
} finally {
|
|
268
|
-
// SessionEnd hook - always called
|
|
269
269
|
await callSessionEnd(exitReason, stateManager.getTurns());
|
|
270
|
+
|
|
271
|
+
if (ownsSandbox && sandboxId && sandboxOps) {
|
|
272
|
+
await sandboxOps.destroySandbox(sandboxId);
|
|
273
|
+
}
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
return {
|
|
277
|
+
threadId,
|
|
273
278
|
finalMessage: null,
|
|
274
279
|
exitReason,
|
|
275
280
|
usage: stateManager.getTotalUsage(),
|
|
@@ -306,3 +311,31 @@ export function proxyDefaultThreadOps(
|
|
|
306
311
|
}
|
|
307
312
|
);
|
|
308
313
|
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Proxy sandbox lifecycle operations as Temporal activities.
|
|
317
|
+
* Call this in workflow code when the agent needs a sandbox.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```typescript
|
|
321
|
+
* const session = await createSession({
|
|
322
|
+
* sandbox: proxySandboxOps(),
|
|
323
|
+
* // ...
|
|
324
|
+
* });
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
export function proxySandboxOps(
|
|
328
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
329
|
+
): SandboxOps {
|
|
330
|
+
return proxyActivities<SandboxOps>(
|
|
331
|
+
options ?? {
|
|
332
|
+
startToCloseTimeout: "30s",
|
|
333
|
+
retry: {
|
|
334
|
+
maximumAttempts: 3,
|
|
335
|
+
initialInterval: "2s",
|
|
336
|
+
maximumInterval: "30s",
|
|
337
|
+
backoffCoefficient: 2,
|
|
338
|
+
},
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Duration } from "@temporalio/common";
|
|
2
|
+
import type {
|
|
3
|
+
MessageContent,
|
|
4
|
+
ToolResultConfig,
|
|
5
|
+
SessionExitReason,
|
|
6
|
+
} from "../types";
|
|
7
|
+
import type {
|
|
8
|
+
ToolMap,
|
|
9
|
+
ToolCallResultUnion,
|
|
10
|
+
InferToolResults,
|
|
11
|
+
} from "../tool-router/types";
|
|
12
|
+
import type { Hooks } from "../hooks/types";
|
|
13
|
+
import type { SubagentConfig } from "../subagent/types";
|
|
14
|
+
import type { Skill } from "../skills/types";
|
|
15
|
+
import type { SandboxOps } from "../sandbox/types";
|
|
16
|
+
import type { RunAgentActivity } from "../model/types";
|
|
17
|
+
import type { AgentStateManager, JsonSerializable } from "../state/types";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Thread operations required by a session.
|
|
21
|
+
* Consumers provide these — typically by wrapping Temporal activities.
|
|
22
|
+
*/
|
|
23
|
+
export interface ThreadOps {
|
|
24
|
+
/** Initialize an empty thread */
|
|
25
|
+
initializeThread(threadId: string): Promise<void>;
|
|
26
|
+
/** Append a human message to the thread */
|
|
27
|
+
appendHumanMessage(
|
|
28
|
+
threadId: string,
|
|
29
|
+
content: string | MessageContent
|
|
30
|
+
): Promise<void>;
|
|
31
|
+
/** Append a tool result to the thread */
|
|
32
|
+
appendToolResult(config: ToolResultConfig): Promise<void>;
|
|
33
|
+
/** Append a system message to the thread */
|
|
34
|
+
appendSystemMessage(threadId: string, content: string): Promise<void>;
|
|
35
|
+
/** Copy all messages from sourceThreadId into a new thread at targetThreadId */
|
|
36
|
+
forkThread(sourceThreadId: string, targetThreadId: string): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configuration for a Zeitlich agent session
|
|
41
|
+
*/
|
|
42
|
+
export interface SessionConfig<T extends ToolMap, M = unknown> {
|
|
43
|
+
/** The name of the agent, should be unique within the workflows */
|
|
44
|
+
agentName: string;
|
|
45
|
+
/** The thread ID to use for the session (defaults to a short generated ID) */
|
|
46
|
+
threadId?: string;
|
|
47
|
+
/** Metadata for the session */
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
/** Whether to append the system prompt as message to the thread */
|
|
50
|
+
appendSystemPrompt?: boolean;
|
|
51
|
+
/** How many turns to run the session for */
|
|
52
|
+
maxTurns?: number;
|
|
53
|
+
/** Workflow-specific runAgent activity (with tools pre-bound) */
|
|
54
|
+
runAgent: RunAgentActivity<M>;
|
|
55
|
+
/** Thread operations (initialize, append messages, parse tool calls) */
|
|
56
|
+
threadOps?: ThreadOps;
|
|
57
|
+
/** Tool router for processing tool calls (optional if agent has no tools) */
|
|
58
|
+
tools?: T;
|
|
59
|
+
/** Subagent configurations */
|
|
60
|
+
subagents?: SubagentConfig[];
|
|
61
|
+
/** Skills available to this agent (metadata + instructions, loaded activity-side) */
|
|
62
|
+
skills?: Skill[];
|
|
63
|
+
/** Session lifecycle hooks */
|
|
64
|
+
hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
|
|
65
|
+
/** Whether to process tools in parallel */
|
|
66
|
+
processToolsInParallel?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Build context message content from agent-specific context.
|
|
69
|
+
* Returns MessageContent array for the initial HumanMessage.
|
|
70
|
+
*/
|
|
71
|
+
buildContextMessage: () => MessageContent | Promise<MessageContent>;
|
|
72
|
+
/** When true, skip thread initialization and system prompt — append only the new human message to the existing thread. */
|
|
73
|
+
continueThread?: boolean;
|
|
74
|
+
/** How long to wait for input before cancelling the workflow */
|
|
75
|
+
waitForInputTimeout?: Duration;
|
|
76
|
+
/** Sandbox lifecycle operations (optional — omit for agents that don't need a sandbox) */
|
|
77
|
+
sandbox?: SandboxOps;
|
|
78
|
+
/**
|
|
79
|
+
* Pre-existing sandbox ID to reuse (e.g. inherited from a parent agent).
|
|
80
|
+
* When set, the session skips `createSandbox` and will not destroy the
|
|
81
|
+
* sandbox on exit (the owner is responsible for cleanup).
|
|
82
|
+
*/
|
|
83
|
+
sandboxId?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ZeitlichSession<M = unknown> {
|
|
87
|
+
runSession<T extends JsonSerializable<T>>(args: {
|
|
88
|
+
stateManager: AgentStateManager<T>;
|
|
89
|
+
}): Promise<{
|
|
90
|
+
threadId: string;
|
|
91
|
+
finalMessage: M | null;
|
|
92
|
+
exitReason: SessionExitReason;
|
|
93
|
+
usage: ReturnType<AgentStateManager<T>["getTotalUsage"]>;
|
|
94
|
+
}>;
|
|
95
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
2
1
|
import { join } from "node:path";
|
|
2
|
+
import type { SandboxFileSystem } from "../sandbox/types";
|
|
3
3
|
import type { Skill, SkillMetadata, SkillProvider } from "./types";
|
|
4
4
|
import { parseSkillFile } from "./parse";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Loads skills from a
|
|
7
|
+
* Loads skills from a directory following the agentskills.io layout:
|
|
8
8
|
*
|
|
9
9
|
* ```
|
|
10
10
|
* skills/
|
|
@@ -14,17 +14,21 @@ import { parseSkillFile } from "./parse";
|
|
|
14
14
|
* │ └── SKILL.md
|
|
15
15
|
* ```
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* Uses the sandbox filesystem abstraction — works with any backend
|
|
18
|
+
* (in-memory, host FS, Wasmer, Daytona, etc.).
|
|
18
19
|
*/
|
|
19
20
|
export class FileSystemSkillProvider implements SkillProvider {
|
|
20
|
-
constructor(
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly fs: SandboxFileSystem,
|
|
23
|
+
private readonly baseDir: string,
|
|
24
|
+
) {}
|
|
21
25
|
|
|
22
26
|
async listSkills(): Promise<SkillMetadata[]> {
|
|
23
27
|
const dirs = await this.discoverSkillDirs();
|
|
24
28
|
const skills: SkillMetadata[] = [];
|
|
25
29
|
|
|
26
30
|
for (const dir of dirs) {
|
|
27
|
-
const raw = await readFile(join(this.baseDir, dir, "SKILL.md")
|
|
31
|
+
const raw = await this.fs.readFile(join(this.baseDir, dir, "SKILL.md"));
|
|
28
32
|
const { frontmatter } = parseSkillFile(raw);
|
|
29
33
|
skills.push(frontmatter);
|
|
30
34
|
}
|
|
@@ -33,15 +37,14 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
async getSkill(name: string): Promise<Skill> {
|
|
36
|
-
const raw = await readFile(
|
|
40
|
+
const raw = await this.fs.readFile(
|
|
37
41
|
join(this.baseDir, name, "SKILL.md"),
|
|
38
|
-
"utf-8"
|
|
39
42
|
);
|
|
40
43
|
const { frontmatter, body } = parseSkillFile(raw);
|
|
41
44
|
|
|
42
45
|
if (frontmatter.name !== name) {
|
|
43
46
|
throw new Error(
|
|
44
|
-
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"
|
|
47
|
+
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"`,
|
|
45
48
|
);
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -57,7 +60,7 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
57
60
|
const skills: Skill[] = [];
|
|
58
61
|
|
|
59
62
|
for (const dir of dirs) {
|
|
60
|
-
const raw = await readFile(join(this.baseDir, dir, "SKILL.md")
|
|
63
|
+
const raw = await this.fs.readFile(join(this.baseDir, dir, "SKILL.md"));
|
|
61
64
|
const { frontmatter, body } = parseSkillFile(raw);
|
|
62
65
|
skills.push({ ...frontmatter, instructions: body });
|
|
63
66
|
}
|
|
@@ -66,16 +69,14 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
private async discoverSkillDirs(): Promise<string[]> {
|
|
69
|
-
const entries = await
|
|
72
|
+
const entries = await this.fs.readdirWithFileTypes(this.baseDir);
|
|
70
73
|
const dirs: string[] = [];
|
|
71
74
|
|
|
72
75
|
for (const entry of entries) {
|
|
73
|
-
if (!entry.isDirectory
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
if (!entry.isDirectory) continue;
|
|
77
|
+
const skillPath = join(this.baseDir, entry.name, "SKILL.md");
|
|
78
|
+
if (await this.fs.exists(skillPath)) {
|
|
76
79
|
dirs.push(entry.name);
|
|
77
|
-
} catch {
|
|
78
|
-
// No SKILL.md — skip
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Skill } from "./types";
|
|
2
|
+
import type { ToolHandlerResponse } from "../tool-router";
|
|
3
|
+
import type { ReadSkillArgs } from "./tool";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a ReadSkill handler that looks up skills from an in-memory array.
|
|
7
|
+
* Runs directly in the workflow (like task tools) — no activity needed.
|
|
8
|
+
*/
|
|
9
|
+
export function createReadSkillHandler(
|
|
10
|
+
skills: Skill[]
|
|
11
|
+
): (args: ReadSkillArgs) => ToolHandlerResponse<null> {
|
|
12
|
+
const skillMap = new Map(skills.map((s) => [s.name, s]));
|
|
13
|
+
|
|
14
|
+
return (args: ReadSkillArgs): ToolHandlerResponse<null> => {
|
|
15
|
+
const skill = skillMap.get(args.skill_name);
|
|
16
|
+
|
|
17
|
+
if (!skill) {
|
|
18
|
+
return {
|
|
19
|
+
toolResponse: JSON.stringify({
|
|
20
|
+
error: `Skill "${args.skill_name}" not found`,
|
|
21
|
+
}),
|
|
22
|
+
data: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
toolResponse: skill.instructions,
|
|
28
|
+
data: null,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/lib/skills/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export type { Skill, SkillMetadata, SkillProvider } from "./types";
|
|
2
2
|
export { parseSkillFile } from "./parse";
|
|
3
|
-
export {
|
|
3
|
+
export { createReadSkillTool, READ_SKILL_TOOL_NAME } from "./tool";
|
|
4
|
+
export type { ReadSkillArgs } from "./tool";
|
|
5
|
+
export { createReadSkillHandler } from "./handler";
|
|
6
|
+
export { buildSkillRegistration } from "./register";
|
|
7
|
+
|