zeitlich 0.2.13 → 0.2.14
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 +49 -38
- 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 +284 -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 +278 -0
- package/dist/adapters/thread/google-genai/index.js.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.cjs +7 -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 +7 -9
- package/dist/adapters/thread/langchain/index.js.map +1 -0
- package/dist/index.cjs +816 -545
- 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 +804 -540
- package/dist/index.js.map +1 -1
- package/dist/types-B4C9txdq.d.ts +389 -0
- package/dist/{thread-manager-qc0g5Rvd.d.cts → types-B9ljZewB.d.cts} +1 -6
- package/dist/{thread-manager-qc0g5Rvd.d.ts → types-B9ljZewB.d.ts} +1 -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-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-GpMU4b0w.d.cts +389 -0
- package/dist/workflow.cjs +444 -318
- 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 +440 -316
- package/dist/workflow.js.map +1 -1
- package/package.json +59 -6
- 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 +121 -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 +11 -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} +76 -48
- package/src/lib/session/types.ts +93 -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 +53 -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} +1 -33
- package/src/lib/thread/types.ts +33 -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,7 +62,9 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
89
62
|
appendSystemPrompt = true,
|
|
90
63
|
continueThread = false,
|
|
91
64
|
waitForInputTimeout = "48h",
|
|
92
|
-
|
|
65
|
+
sandbox: sandboxOps,
|
|
66
|
+
sandboxId: inheritedSandboxId,
|
|
67
|
+
}: SessionConfig<T, M>): Promise<ZeitlichSession<M>> => {
|
|
93
68
|
const threadId = providedThreadId ?? getShortId();
|
|
94
69
|
|
|
95
70
|
const {
|
|
@@ -99,17 +74,25 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
99
74
|
appendSystemMessage,
|
|
100
75
|
} = threadOps ?? proxyDefaultThreadOps();
|
|
101
76
|
|
|
77
|
+
const plugins: ToolMap[string][] = [];
|
|
78
|
+
if (subagents) {
|
|
79
|
+
const reg = buildSubagentRegistration(subagents);
|
|
80
|
+
if (reg) plugins.push(reg);
|
|
81
|
+
}
|
|
82
|
+
if (skills) {
|
|
83
|
+
const reg = buildSkillRegistration(skills);
|
|
84
|
+
if (reg) plugins.push(reg);
|
|
85
|
+
}
|
|
86
|
+
|
|
102
87
|
const toolRouter = createToolRouter({
|
|
103
88
|
tools,
|
|
104
89
|
appendToolResult,
|
|
105
90
|
threadId,
|
|
106
91
|
hooks,
|
|
107
|
-
|
|
108
|
-
skills,
|
|
92
|
+
plugins,
|
|
109
93
|
parallel: processToolsInParallel,
|
|
110
94
|
});
|
|
111
95
|
|
|
112
|
-
// Helper to call session end hook
|
|
113
96
|
const callSessionEnd = async (
|
|
114
97
|
exitReason: SessionExitReason,
|
|
115
98
|
turns: number
|
|
@@ -126,12 +109,15 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
126
109
|
};
|
|
127
110
|
|
|
128
111
|
return {
|
|
129
|
-
runSession: async ({
|
|
112
|
+
runSession: async <TState extends JsonSerializable<TState>>({
|
|
130
113
|
stateManager,
|
|
114
|
+
}: {
|
|
115
|
+
stateManager: AgentStateManager<TState>;
|
|
131
116
|
}): Promise<{
|
|
117
|
+
threadId: string;
|
|
132
118
|
finalMessage: M | null;
|
|
133
119
|
exitReason: SessionExitReason;
|
|
134
|
-
usage: ReturnType<
|
|
120
|
+
usage: ReturnType<AgentStateManager<TState>["getTotalUsage"]>;
|
|
135
121
|
}> => {
|
|
136
122
|
setHandler(
|
|
137
123
|
defineUpdate<unknown, [MessageContent]>(`add${agentName}Message`),
|
|
@@ -153,6 +139,19 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
153
139
|
}
|
|
154
140
|
);
|
|
155
141
|
|
|
142
|
+
// --- Sandbox lifecycle: create or inherit ---
|
|
143
|
+
let sandboxId: string | undefined = inheritedSandboxId;
|
|
144
|
+
const ownsSandbox = !sandboxId && !!sandboxOps;
|
|
145
|
+
if (ownsSandbox) {
|
|
146
|
+
const result = await sandboxOps.createSandbox({ id: threadId });
|
|
147
|
+
sandboxId = result.sandboxId;
|
|
148
|
+
if (result.stateUpdate) {
|
|
149
|
+
stateManager.mergeUpdate(
|
|
150
|
+
result.stateUpdate as Partial<TState>,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
156
155
|
if (hooks.onSessionStart) {
|
|
157
156
|
await hooks.onSessionStart({
|
|
158
157
|
threadId,
|
|
@@ -201,18 +200,17 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
201
200
|
stateManager.updateUsage(usage);
|
|
202
201
|
}
|
|
203
202
|
|
|
204
|
-
// No tools configured - treat any non-end_turn as completed
|
|
205
203
|
if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
|
|
206
204
|
stateManager.complete();
|
|
207
205
|
exitReason = "completed";
|
|
208
206
|
return {
|
|
207
|
+
threadId,
|
|
209
208
|
finalMessage: message,
|
|
210
209
|
exitReason,
|
|
211
210
|
usage: stateManager.getTotalUsage(),
|
|
212
211
|
};
|
|
213
212
|
}
|
|
214
213
|
|
|
215
|
-
// Parse all tool calls uniformly through the router
|
|
216
214
|
const parsedToolCalls: ParsedToolCallUnion<T>[] = [];
|
|
217
215
|
for (const tc of rawToolCalls) {
|
|
218
216
|
try {
|
|
@@ -229,11 +227,11 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
229
227
|
}
|
|
230
228
|
}
|
|
231
229
|
|
|
232
|
-
// Hooks can call stateManager.waitForInput() to pause the session
|
|
233
230
|
const toolCallResults = await toolRouter.processToolCalls(
|
|
234
231
|
parsedToolCalls,
|
|
235
232
|
{
|
|
236
233
|
turn: currentTurn,
|
|
234
|
+
...(sandboxId !== undefined && { sandboxId }),
|
|
237
235
|
}
|
|
238
236
|
);
|
|
239
237
|
|
|
@@ -250,14 +248,12 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
250
248
|
);
|
|
251
249
|
if (!conditionMet) {
|
|
252
250
|
stateManager.cancel();
|
|
253
|
-
// Wait briefly to allow pending waitForStateChange handlers to complete
|
|
254
251
|
await condition(() => false, "2s");
|
|
255
252
|
break;
|
|
256
253
|
}
|
|
257
254
|
}
|
|
258
255
|
}
|
|
259
256
|
|
|
260
|
-
// Check if we hit max turns
|
|
261
257
|
if (stateManager.getTurns() >= maxTurns && stateManager.isRunning()) {
|
|
262
258
|
exitReason = "max_turns";
|
|
263
259
|
}
|
|
@@ -265,11 +261,15 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
265
261
|
exitReason = "failed";
|
|
266
262
|
throw ApplicationFailure.fromError(error);
|
|
267
263
|
} finally {
|
|
268
|
-
// SessionEnd hook - always called
|
|
269
264
|
await callSessionEnd(exitReason, stateManager.getTurns());
|
|
265
|
+
|
|
266
|
+
if (ownsSandbox && sandboxId && sandboxOps) {
|
|
267
|
+
await sandboxOps.destroySandbox(sandboxId);
|
|
268
|
+
}
|
|
270
269
|
}
|
|
271
270
|
|
|
272
271
|
return {
|
|
272
|
+
threadId,
|
|
273
273
|
finalMessage: null,
|
|
274
274
|
exitReason,
|
|
275
275
|
usage: stateManager.getTotalUsage(),
|
|
@@ -306,3 +306,31 @@ export function proxyDefaultThreadOps(
|
|
|
306
306
|
}
|
|
307
307
|
);
|
|
308
308
|
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Proxy sandbox lifecycle operations as Temporal activities.
|
|
312
|
+
* Call this in workflow code when the agent needs a sandbox.
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* const session = await createSession({
|
|
317
|
+
* sandbox: proxySandboxOps(),
|
|
318
|
+
* // ...
|
|
319
|
+
* });
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
export function proxySandboxOps(
|
|
323
|
+
options?: Parameters<typeof proxyActivities>[0]
|
|
324
|
+
): SandboxOps {
|
|
325
|
+
return proxyActivities<SandboxOps>(
|
|
326
|
+
options ?? {
|
|
327
|
+
startToCloseTimeout: "30s",
|
|
328
|
+
retry: {
|
|
329
|
+
maximumAttempts: 3,
|
|
330
|
+
initialInterval: "2s",
|
|
331
|
+
maximumInterval: "30s",
|
|
332
|
+
backoffCoefficient: 2,
|
|
333
|
+
},
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Configuration for a Zeitlich agent session
|
|
39
|
+
*/
|
|
40
|
+
export interface SessionConfig<T extends ToolMap, M = unknown> {
|
|
41
|
+
/** The name of the agent, should be unique within the workflows */
|
|
42
|
+
agentName: string;
|
|
43
|
+
/** The thread ID to use for the session (defaults to a short generated ID) */
|
|
44
|
+
threadId?: string;
|
|
45
|
+
/** Metadata for the session */
|
|
46
|
+
metadata?: Record<string, unknown>;
|
|
47
|
+
/** Whether to append the system prompt as message to the thread */
|
|
48
|
+
appendSystemPrompt?: boolean;
|
|
49
|
+
/** How many turns to run the session for */
|
|
50
|
+
maxTurns?: number;
|
|
51
|
+
/** Workflow-specific runAgent activity (with tools pre-bound) */
|
|
52
|
+
runAgent: RunAgentActivity<M>;
|
|
53
|
+
/** Thread operations (initialize, append messages, parse tool calls) */
|
|
54
|
+
threadOps?: ThreadOps;
|
|
55
|
+
/** Tool router for processing tool calls (optional if agent has no tools) */
|
|
56
|
+
tools?: T;
|
|
57
|
+
/** Subagent configurations */
|
|
58
|
+
subagents?: SubagentConfig[];
|
|
59
|
+
/** Skills available to this agent (metadata + instructions, loaded activity-side) */
|
|
60
|
+
skills?: Skill[];
|
|
61
|
+
/** Session lifecycle hooks */
|
|
62
|
+
hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
|
|
63
|
+
/** Whether to process tools in parallel */
|
|
64
|
+
processToolsInParallel?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Build context message content from agent-specific context.
|
|
67
|
+
* Returns MessageContent array for the initial HumanMessage.
|
|
68
|
+
*/
|
|
69
|
+
buildContextMessage: () => MessageContent | Promise<MessageContent>;
|
|
70
|
+
/** When true, skip thread initialization and system prompt — append only the new human message to the existing thread. */
|
|
71
|
+
continueThread?: boolean;
|
|
72
|
+
/** How long to wait for input before cancelling the workflow */
|
|
73
|
+
waitForInputTimeout?: Duration;
|
|
74
|
+
/** Sandbox lifecycle operations (optional — omit for agents that don't need a sandbox) */
|
|
75
|
+
sandbox?: SandboxOps;
|
|
76
|
+
/**
|
|
77
|
+
* Pre-existing sandbox ID to reuse (e.g. inherited from a parent agent).
|
|
78
|
+
* When set, the session skips `createSandbox` and will not destroy the
|
|
79
|
+
* sandbox on exit (the owner is responsible for cleanup).
|
|
80
|
+
*/
|
|
81
|
+
sandboxId?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ZeitlichSession<M = unknown> {
|
|
85
|
+
runSession<T extends JsonSerializable<T>>(args: {
|
|
86
|
+
stateManager: AgentStateManager<T>;
|
|
87
|
+
}): Promise<{
|
|
88
|
+
threadId: string;
|
|
89
|
+
finalMessage: M | null;
|
|
90
|
+
exitReason: SessionExitReason;
|
|
91
|
+
usage: ReturnType<AgentStateManager<T>["getTotalUsage"]>;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
@@ -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
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ToolMap } from "../tool-router/types";
|
|
2
|
+
import type { Skill } from "./types";
|
|
3
|
+
import { createReadSkillTool } from "./tool";
|
|
4
|
+
import { createReadSkillHandler } from "./handler";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Builds a fully wired tool entry for the ReadSkill tool.
|
|
8
|
+
*
|
|
9
|
+
* Returns null if no skills are provided.
|
|
10
|
+
*/
|
|
11
|
+
export function buildSkillRegistration(
|
|
12
|
+
skills: Skill[]
|
|
13
|
+
): ToolMap[string] | null {
|
|
14
|
+
if (skills.length === 0) return null;
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
...createReadSkillTool(skills),
|
|
18
|
+
handler: createReadSkillHandler(skills),
|
|
19
|
+
};
|
|
20
|
+
}
|