zeitlich 0.2.35 → 0.2.37
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 +146 -92
- package/dist/{activities-BVI2lTwr.d.ts → activities-Bb-nAjwQ.d.ts} +2 -2
- package/dist/{activities-hd4aNnZE.d.cts → activities-vkI4_3CC.d.cts} +2 -2
- package/dist/adapters/sandbox/bedrock/index.cjs +14 -11
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +4 -3
- package/dist/adapters/sandbox/bedrock/index.d.ts +4 -3
- package/dist/adapters/sandbox/bedrock/index.js +14 -11
- package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.cjs +2 -0
- package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.js +2 -0
- package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
- package/dist/adapters/sandbox/daytona/index.cjs +35 -6
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +3 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +3 -1
- package/dist/adapters/sandbox/daytona/index.js +35 -6
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +2 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.js +2 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/e2b/index.cjs +59 -10
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +5 -3
- package/dist/adapters/sandbox/e2b/index.d.ts +5 -3
- package/dist/adapters/sandbox/e2b/index.js +59 -10
- package/dist/adapters/sandbox/e2b/index.js.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.cjs +2 -0
- package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.js +2 -0
- package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +5 -0
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +2 -1
- package/dist/adapters/sandbox/inmemory/index.d.ts +2 -1
- package/dist/adapters/sandbox/inmemory/index.js +5 -0
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +2 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.js +2 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/thread/anthropic/index.cjs +71 -36
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +5 -5
- package/dist/adapters/thread/anthropic/index.d.ts +5 -5
- package/dist/adapters/thread/anthropic/index.js +71 -36
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +5 -1
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
- package/dist/adapters/thread/anthropic/workflow.js +5 -1
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +50 -25
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +5 -5
- package/dist/adapters/thread/google-genai/index.d.ts +5 -5
- package/dist/adapters/thread/google-genai/index.js +50 -25
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +5 -1
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
- package/dist/adapters/thread/google-genai/workflow.js +5 -1
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +34 -7
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +5 -5
- package/dist/adapters/thread/langchain/index.d.ts +5 -5
- package/dist/adapters/thread/langchain/index.js +34 -7
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +5 -1
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
- package/dist/adapters/thread/langchain/workflow.js +5 -1
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +206 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -11
- package/dist/index.d.ts +17 -11
- package/dist/index.js +207 -121
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BjdFGPTm.d.ts → proxy-0smGKvx8.d.ts} +1 -1
- package/dist/{proxy-7RnVaPdJ.d.cts → proxy-DEtowJyd.d.cts} +1 -1
- package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-3fszQih4.d.ts} +2 -2
- package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-C-C4pI2z.d.ts} +2 -2
- package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-CzYln2OC.d.cts} +2 -2
- package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-D4vgzYrh.d.cts} +2 -2
- package/dist/{types-yiXmqedU.d.ts → types-B37hKoWA.d.ts} +1 -1
- package/dist/{types-DQ1l_gXL.d.cts → types-BO7Yju20.d.cts} +63 -14
- package/dist/{types-wiGLvxWf.d.ts → types-CNuWnvy9.d.ts} +1 -1
- package/dist/{types-CADc5V_P.d.ts → types-CPKDl-y_.d.ts} +63 -14
- package/dist/{types-Mc_4BCfT.d.cts → types-D08CXPh8.d.cts} +1 -1
- package/dist/{types-CBH54cwr.d.cts → types-DWEUmYAJ.d.cts} +1 -1
- package/dist/{types-DxCpFNv_.d.cts → types-tQL9njTu.d.cts} +25 -0
- package/dist/{types-DxCpFNv_.d.ts → types-tQL9njTu.d.ts} +25 -0
- package/dist/{workflow-P2pTSfKu.d.ts → workflow-CjXHbZZc.d.ts} +2 -2
- package/dist/{workflow-DhtWRovz.d.cts → workflow-Do_lzJpT.d.cts} +2 -2
- package/dist/workflow.cjs +182 -114
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +3 -3
- package/dist/workflow.d.ts +3 -3
- package/dist/workflow.js +183 -115
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
- package/src/adapters/sandbox/bedrock/index.ts +10 -8
- package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
- package/src/adapters/sandbox/daytona/filesystem.ts +29 -6
- package/src/adapters/sandbox/daytona/index.ts +6 -0
- package/src/adapters/sandbox/daytona/proxy.ts +2 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
- package/src/adapters/sandbox/e2b/index.ts +63 -12
- package/src/adapters/sandbox/e2b/proxy.ts +2 -0
- package/src/adapters/sandbox/inmemory/index.ts +5 -0
- package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
- package/src/adapters/thread/anthropic/activities.ts +49 -26
- package/src/adapters/thread/anthropic/model-invoker.ts +15 -6
- package/src/adapters/thread/anthropic/proxy.ts +6 -2
- package/src/adapters/thread/anthropic/thread-manager.test.ts +26 -7
- package/src/adapters/thread/anthropic/thread-manager.ts +60 -46
- package/src/adapters/thread/google-genai/activities.ts +7 -2
- package/src/adapters/thread/google-genai/model-invoker.ts +26 -8
- package/src/adapters/thread/google-genai/proxy.ts +6 -2
- package/src/adapters/thread/google-genai/thread-manager.test.ts +13 -3
- package/src/adapters/thread/google-genai/thread-manager.ts +54 -33
- package/src/adapters/thread/langchain/activities.ts +46 -24
- package/src/adapters/thread/langchain/hooks.test.ts +36 -49
- package/src/adapters/thread/langchain/hooks.ts +18 -5
- package/src/adapters/thread/langchain/model-invoker.ts +3 -3
- package/src/adapters/thread/langchain/proxy.ts +6 -2
- package/src/adapters/thread/langchain/thread-manager.test.ts +5 -1
- package/src/adapters/thread/langchain/thread-manager.ts +20 -9
- package/src/index.ts +4 -1
- package/src/lib/activity.ts +16 -6
- package/src/lib/hooks/types.ts +6 -6
- package/src/lib/lifecycle.ts +9 -1
- package/src/lib/model/proxy.ts +2 -2
- package/src/lib/observability/hooks.ts +4 -5
- package/src/lib/observability/index.ts +1 -4
- package/src/lib/sandbox/manager.ts +21 -4
- package/src/lib/sandbox/node-fs.ts +3 -6
- package/src/lib/sandbox/sandbox.test.ts +36 -3
- package/src/lib/sandbox/tree.integration.test.ts +10 -3
- package/src/lib/sandbox/types.ts +35 -1
- package/src/lib/session/session-edge-cases.integration.test.ts +51 -13
- package/src/lib/session/session.integration.test.ts +139 -0
- package/src/lib/session/session.ts +50 -19
- package/src/lib/session/types.ts +13 -5
- package/src/lib/skills/fs-provider.ts +12 -8
- package/src/lib/skills/handler.ts +1 -1
- package/src/lib/skills/parse.ts +3 -1
- package/src/lib/skills/register.ts +1 -3
- package/src/lib/skills/skills.integration.test.ts +25 -15
- package/src/lib/state/manager.integration.test.ts +12 -2
- package/src/lib/subagent/define.ts +1 -1
- package/src/lib/subagent/handler.ts +186 -71
- package/src/lib/subagent/index.ts +1 -5
- package/src/lib/subagent/register.ts +3 -2
- package/src/lib/subagent/signals.ts +1 -10
- package/src/lib/subagent/subagent.integration.test.ts +438 -156
- package/src/lib/subagent/tool.ts +4 -3
- package/src/lib/subagent/types.ts +50 -20
- package/src/lib/subagent/workflow.ts +9 -49
- package/src/lib/thread/id.test.ts +1 -1
- package/src/lib/thread/id.ts +1 -2
- package/src/lib/thread/proxy.ts +3 -4
- package/src/lib/thread/types.ts +11 -3
- package/src/lib/tool-router/index.ts +1 -5
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +1 -1
- package/src/lib/tool-router/router.ts +3 -2
- package/src/lib/tool-router/types.ts +11 -3
- package/src/lib/tool-router/with-sandbox.ts +19 -5
- package/src/lib/virtual-fs/filesystem.ts +1 -1
- package/src/lib/virtual-fs/index.ts +5 -1
- package/src/lib/virtual-fs/mutations.ts +2 -4
- package/src/lib/virtual-fs/queries.ts +9 -5
- package/src/lib/virtual-fs/types.ts +4 -1
- package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
- package/src/lib/workflow.test.ts +7 -4
- package/src/lib/workflow.ts +1 -5
- package/src/tools/ask-user-question/tool.ts +1 -3
- package/src/tools/glob/handler.ts +1 -4
- package/src/tools/task-get/handler.ts +4 -5
- package/src/tools/task-list/handler.ts +1 -4
- package/src/tools/task-update/handler.ts +4 -5
- package/src/workflow.ts +20 -7
- package/tsup.config.ts +9 -6
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "@temporalio/workflow";
|
|
8
8
|
import type { SessionExitReason } from "../types";
|
|
9
9
|
import type { SessionConfig, ZeitlichSession } from "./types";
|
|
10
|
-
import type { SandboxOps } from "../sandbox/types";
|
|
10
|
+
import type { SandboxOps, SandboxSnapshot } from "../sandbox/types";
|
|
11
11
|
import type {
|
|
12
12
|
AgentState,
|
|
13
13
|
AgentStateManager,
|
|
@@ -146,12 +146,14 @@ export async function createSession<
|
|
|
146
146
|
|
|
147
147
|
const plugins: ToolMap[string][] = [];
|
|
148
148
|
let destroySubagentSandboxes: (() => Promise<void>) | undefined;
|
|
149
|
+
let cleanupSubagentSnapshots: (() => Promise<void>) | undefined;
|
|
149
150
|
|
|
150
151
|
if (subagents) {
|
|
151
152
|
const result = buildSubagentRegistration(subagents);
|
|
152
153
|
if (result) {
|
|
153
154
|
plugins.push(result.registration);
|
|
154
155
|
destroySubagentSandboxes = result.destroySubagentSandboxes;
|
|
156
|
+
cleanupSubagentSnapshots = result.cleanupSubagentSnapshots;
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
if (skills) {
|
|
@@ -210,10 +212,13 @@ export async function createSession<
|
|
|
210
212
|
}
|
|
211
213
|
);
|
|
212
214
|
|
|
213
|
-
// --- Sandbox lifecycle: create, continue, fork, or inherit
|
|
215
|
+
// --- Sandbox lifecycle: create, continue, fork, from-snapshot, or inherit ---
|
|
214
216
|
const sandboxMode = sandboxInit?.mode;
|
|
215
217
|
let sandboxId: string | undefined;
|
|
216
218
|
let sandboxOwned = false;
|
|
219
|
+
let baseSnapshot: SandboxSnapshot | undefined;
|
|
220
|
+
let exitSnapshot: SandboxSnapshot | undefined;
|
|
221
|
+
let freshlyCreated = false;
|
|
217
222
|
|
|
218
223
|
if (sandboxMode === "inherit") {
|
|
219
224
|
const inheritInit = sandboxInit as {
|
|
@@ -252,6 +257,18 @@ export async function createSession<
|
|
|
252
257
|
(sandboxInit as { mode: "fork"; sandboxId: string }).sandboxId
|
|
253
258
|
);
|
|
254
259
|
sandboxOwned = true;
|
|
260
|
+
} else if (sandboxMode === "from-snapshot") {
|
|
261
|
+
if (!sandboxOps) {
|
|
262
|
+
throw ApplicationFailure.create({
|
|
263
|
+
message: "No sandboxOps provided — cannot restore sandbox",
|
|
264
|
+
nonRetryable: true,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
const snap = (
|
|
268
|
+
sandboxInit as { mode: "from-snapshot"; snapshot: SandboxSnapshot }
|
|
269
|
+
).snapshot;
|
|
270
|
+
sandboxId = await sandboxOps.restoreSandbox(snap);
|
|
271
|
+
sandboxOwned = true;
|
|
255
272
|
} else if (sandboxOps) {
|
|
256
273
|
const skillFiles = skills ? collectSkillFiles(skills) : undefined;
|
|
257
274
|
const ctx = (sandboxInit as { mode: "new"; ctx?: unknown } | undefined)
|
|
@@ -263,10 +280,24 @@ export async function createSession<
|
|
|
263
280
|
if (result) {
|
|
264
281
|
sandboxId = result.sandboxId;
|
|
265
282
|
sandboxOwned = true;
|
|
283
|
+
freshlyCreated = true;
|
|
266
284
|
}
|
|
267
285
|
}
|
|
268
286
|
|
|
269
|
-
|
|
287
|
+
// Capture a base snapshot immediately after seeding so it can be reused
|
|
288
|
+
// as a template for future runs that want to skip the (potentially
|
|
289
|
+
// expensive) seed step.
|
|
290
|
+
if (
|
|
291
|
+
sandboxId &&
|
|
292
|
+
sandboxOwned &&
|
|
293
|
+
freshlyCreated &&
|
|
294
|
+
sandboxShutdown === "snapshot" &&
|
|
295
|
+
sandboxOps
|
|
296
|
+
) {
|
|
297
|
+
baseSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (sandboxId && sandboxOwned && onSandboxReady) {
|
|
270
301
|
onSandboxReady(sandboxId);
|
|
271
302
|
}
|
|
272
303
|
|
|
@@ -347,6 +378,7 @@ export async function createSession<
|
|
|
347
378
|
);
|
|
348
379
|
|
|
349
380
|
let exitReason: SessionExitReason = "completed";
|
|
381
|
+
let finalMessage: M | null = null;
|
|
350
382
|
|
|
351
383
|
try {
|
|
352
384
|
while (
|
|
@@ -385,21 +417,8 @@ export async function createSession<
|
|
|
385
417
|
if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
|
|
386
418
|
stateManager.complete();
|
|
387
419
|
exitReason = "completed";
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
threadId,
|
|
391
|
-
exitReason,
|
|
392
|
-
turns: currentTurn,
|
|
393
|
-
durationMs: Date.now() - sessionStartMs,
|
|
394
|
-
usage: stateManager.getTotalUsage(),
|
|
395
|
-
});
|
|
396
|
-
return {
|
|
397
|
-
threadId,
|
|
398
|
-
finalMessage: message,
|
|
399
|
-
exitReason,
|
|
400
|
-
usage: stateManager.getTotalUsage(),
|
|
401
|
-
sandboxId,
|
|
402
|
-
} as Awaited<ReturnType<ZeitlichSession<M, boolean>["runSession"]>>;
|
|
420
|
+
finalMessage = message;
|
|
421
|
+
break;
|
|
403
422
|
}
|
|
404
423
|
|
|
405
424
|
const parsedToolCalls: ParsedToolCallUnion<T>[] = [];
|
|
@@ -480,12 +499,20 @@ export async function createSession<
|
|
|
480
499
|
case "keep":
|
|
481
500
|
case "keep-until-parent-close":
|
|
482
501
|
break;
|
|
502
|
+
case "snapshot":
|
|
503
|
+
exitSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
|
|
504
|
+
await sandboxOps.destroySandbox(sandboxId);
|
|
505
|
+
break;
|
|
483
506
|
}
|
|
484
507
|
}
|
|
485
508
|
|
|
486
509
|
if (destroySubagentSandboxes) {
|
|
487
510
|
await destroySubagentSandboxes();
|
|
488
511
|
}
|
|
512
|
+
|
|
513
|
+
if (cleanupSubagentSnapshots) {
|
|
514
|
+
await cleanupSubagentSnapshots();
|
|
515
|
+
}
|
|
489
516
|
}
|
|
490
517
|
|
|
491
518
|
log.info("session ended", {
|
|
@@ -495,14 +522,18 @@ export async function createSession<
|
|
|
495
522
|
turns: stateManager.getTurns(),
|
|
496
523
|
durationMs: Date.now() - sessionStartMs,
|
|
497
524
|
usage: stateManager.getTotalUsage(),
|
|
525
|
+
...(baseSnapshot && { hasBaseSnapshot: true }),
|
|
526
|
+
...(exitSnapshot && { hasExitSnapshot: true }),
|
|
498
527
|
});
|
|
499
528
|
|
|
500
529
|
return {
|
|
501
530
|
threadId,
|
|
502
|
-
finalMessage
|
|
531
|
+
finalMessage,
|
|
503
532
|
exitReason,
|
|
504
533
|
usage: stateManager.getTotalUsage(),
|
|
505
534
|
sandboxId,
|
|
535
|
+
...(baseSnapshot && { baseSnapshot }),
|
|
536
|
+
...(exitSnapshot && { snapshot: exitSnapshot }),
|
|
506
537
|
} as Awaited<ReturnType<ZeitlichSession<M, boolean>["runSession"]>>;
|
|
507
538
|
},
|
|
508
539
|
};
|
package/src/lib/session/types.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { Duration } from "@temporalio/common";
|
|
2
|
-
import type {
|
|
3
|
-
SessionExitReason,
|
|
4
|
-
ToolResultConfig,
|
|
5
|
-
} from "../types";
|
|
2
|
+
import type { SessionExitReason, ToolResultConfig } from "../types";
|
|
6
3
|
import type {
|
|
7
4
|
ToolMap,
|
|
8
5
|
ToolCallResultUnion,
|
|
@@ -11,7 +8,7 @@ import type {
|
|
|
11
8
|
import type { Hooks } from "../hooks/types";
|
|
12
9
|
import type { SubagentConfig } from "../subagent/types";
|
|
13
10
|
import type { Skill } from "../skills/types";
|
|
14
|
-
import type { SandboxOps } from "../sandbox/types";
|
|
11
|
+
import type { SandboxOps, SandboxSnapshot } from "../sandbox/types";
|
|
15
12
|
import type { VirtualFsOps } from "../virtual-fs/types";
|
|
16
13
|
import type { RunAgentActivity } from "../model/types";
|
|
17
14
|
import type { AgentStateManager, JsonSerializable } from "../state/types";
|
|
@@ -219,6 +216,17 @@ export type SessionResult<
|
|
|
219
216
|
finalMessage: M | null;
|
|
220
217
|
exitReason: SessionExitReason;
|
|
221
218
|
usage: ReturnType<AgentStateManager<TState>["getTotalUsage"]>;
|
|
219
|
+
/**
|
|
220
|
+
* Snapshot captured on exit when `sandboxShutdown === "snapshot"`.
|
|
221
|
+
*/
|
|
222
|
+
snapshot?: SandboxSnapshot;
|
|
223
|
+
/**
|
|
224
|
+
* Snapshot captured immediately after sandbox seeding (before the agent
|
|
225
|
+
* loop starts) when `sandbox.mode === "new"` and
|
|
226
|
+
* `sandboxShutdown === "snapshot"`. Intended as a reusable "base" for new
|
|
227
|
+
* threads that want to skip re-seeding.
|
|
228
|
+
*/
|
|
229
|
+
baseSnapshot?: SandboxSnapshot;
|
|
222
230
|
} & (HasSandbox extends true
|
|
223
231
|
? { sandboxId: string }
|
|
224
232
|
: { sandboxId?: undefined });
|
|
@@ -24,7 +24,7 @@ import { parseSkillFile } from "./parse";
|
|
|
24
24
|
export class FileSystemSkillProvider implements SkillProvider {
|
|
25
25
|
constructor(
|
|
26
26
|
private readonly fs: SandboxFileSystem,
|
|
27
|
-
private readonly baseDir: string
|
|
27
|
+
private readonly baseDir: string
|
|
28
28
|
) {}
|
|
29
29
|
|
|
30
30
|
async listSkills(): Promise<SkillMetadata[]> {
|
|
@@ -45,20 +45,21 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async getSkill(name: string): Promise<Skill> {
|
|
48
|
-
const raw = await this.fs.readFile(
|
|
49
|
-
join(this.baseDir, name, "SKILL.md"),
|
|
50
|
-
);
|
|
48
|
+
const raw = await this.fs.readFile(join(this.baseDir, name, "SKILL.md"));
|
|
51
49
|
const { frontmatter, body } = parseSkillFile(raw);
|
|
52
50
|
|
|
53
51
|
if (frontmatter.name !== name) {
|
|
54
52
|
throw new Error(
|
|
55
|
-
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"
|
|
53
|
+
`Skill directory "${name}" contains SKILL.md with mismatched name "${frontmatter.name}"`
|
|
56
54
|
);
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
const location = join(this.baseDir, name);
|
|
60
58
|
const resourcePaths = await this.discoverResources(name);
|
|
61
|
-
const resourceContents = await this.readResourceContents(
|
|
59
|
+
const resourceContents = await this.readResourceContents(
|
|
60
|
+
location,
|
|
61
|
+
resourcePaths
|
|
62
|
+
);
|
|
62
63
|
return {
|
|
63
64
|
...frontmatter,
|
|
64
65
|
instructions: body,
|
|
@@ -80,7 +81,10 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
80
81
|
const { frontmatter, body } = parseSkillFile(raw);
|
|
81
82
|
const location = join(this.baseDir, dir);
|
|
82
83
|
const resourcePaths = await this.discoverResources(dir);
|
|
83
|
-
const resourceContents = await this.readResourceContents(
|
|
84
|
+
const resourceContents = await this.readResourceContents(
|
|
85
|
+
location,
|
|
86
|
+
resourcePaths
|
|
87
|
+
);
|
|
84
88
|
skills.push({
|
|
85
89
|
...frontmatter,
|
|
86
90
|
instructions: body,
|
|
@@ -119,7 +123,7 @@ export class FileSystemSkillProvider implements SkillProvider {
|
|
|
119
123
|
|
|
120
124
|
private async readResourceContents(
|
|
121
125
|
location: string,
|
|
122
|
-
resources: string[]
|
|
126
|
+
resources: string[]
|
|
123
127
|
): Promise<Record<string, string> | undefined> {
|
|
124
128
|
if (resources.length === 0) return undefined;
|
|
125
129
|
const contents: Record<string, string> = {};
|
|
@@ -18,7 +18,7 @@ function formatSkillResponse(skill: Skill): string {
|
|
|
18
18
|
if (skill.location) {
|
|
19
19
|
parts.push(`\nSkill directory: ${skill.location}`);
|
|
20
20
|
parts.push(
|
|
21
|
-
"Relative paths in this skill resolve against the skill directory above."
|
|
21
|
+
"Relative paths in this skill resolve against the skill directory above."
|
|
22
22
|
);
|
|
23
23
|
}
|
|
24
24
|
|
package/src/lib/skills/parse.ts
CHANGED
|
@@ -12,7 +12,9 @@ export function parseSkillFile(raw: string): {
|
|
|
12
12
|
body: string;
|
|
13
13
|
} {
|
|
14
14
|
const trimmed = raw.replace(/^\uFEFF/, ""); // strip BOM
|
|
15
|
-
const match = trimmed.match(
|
|
15
|
+
const match = trimmed.match(
|
|
16
|
+
/^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?([\s\S]*)$/
|
|
17
|
+
);
|
|
16
18
|
|
|
17
19
|
if (!match) {
|
|
18
20
|
throw new Error(
|
|
@@ -11,9 +11,7 @@ function validateSkillNames(skills: SkillMetadata[]): void {
|
|
|
11
11
|
const names = skills.map((s) => s.name);
|
|
12
12
|
const dupes = names.filter((n, i) => names.indexOf(n) !== i);
|
|
13
13
|
if (dupes.length > 0) {
|
|
14
|
-
throw new Error(
|
|
15
|
-
`Duplicate skill names: ${[...new Set(dupes)].join(", ")}`
|
|
16
|
-
);
|
|
14
|
+
throw new Error(`Duplicate skill names: ${[...new Set(dupes)].join(", ")}`);
|
|
17
15
|
}
|
|
18
16
|
}
|
|
19
17
|
|
|
@@ -47,7 +47,10 @@ Body content here.`;
|
|
|
47
47
|
expect(frontmatter.license).toBe("MIT");
|
|
48
48
|
expect(frontmatter.compatibility).toBe("linux-only");
|
|
49
49
|
expect(frontmatter.allowedTools).toEqual(["bash", "grep", "read-file"]);
|
|
50
|
-
expect(frontmatter.metadata).toEqual({
|
|
50
|
+
expect(frontmatter.metadata).toEqual({
|
|
51
|
+
author: "test-author",
|
|
52
|
+
version: "1.0",
|
|
53
|
+
});
|
|
51
54
|
expect(body).toBe("Body content here.");
|
|
52
55
|
});
|
|
53
56
|
|
|
@@ -76,7 +79,7 @@ Body`;
|
|
|
76
79
|
|
|
77
80
|
it("throws when frontmatter is missing", () => {
|
|
78
81
|
expect(() => parseSkillFile("No frontmatter here")).toThrow(
|
|
79
|
-
"SKILL.md must start with YAML frontmatter"
|
|
82
|
+
"SKILL.md must start with YAML frontmatter"
|
|
80
83
|
);
|
|
81
84
|
});
|
|
82
85
|
|
|
@@ -87,7 +90,7 @@ description: Missing name
|
|
|
87
90
|
Body`;
|
|
88
91
|
|
|
89
92
|
expect(() => parseSkillFile(raw)).toThrow(
|
|
90
|
-
"SKILL.md frontmatter must include a 'name' field"
|
|
93
|
+
"SKILL.md frontmatter must include a 'name' field"
|
|
91
94
|
);
|
|
92
95
|
});
|
|
93
96
|
|
|
@@ -98,7 +101,7 @@ name: no-desc
|
|
|
98
101
|
Body`;
|
|
99
102
|
|
|
100
103
|
expect(() => parseSkillFile(raw)).toThrow(
|
|
101
|
-
"SKILL.md frontmatter must include a 'description' field"
|
|
104
|
+
"SKILL.md frontmatter must include a 'description' field"
|
|
102
105
|
);
|
|
103
106
|
});
|
|
104
107
|
|
|
@@ -114,7 +117,8 @@ description: No body content
|
|
|
114
117
|
});
|
|
115
118
|
|
|
116
119
|
it("handles CRLF line endings", () => {
|
|
117
|
-
const raw =
|
|
120
|
+
const raw =
|
|
121
|
+
"---\r\nname: crlf-skill\r\ndescription: CRLF test\r\n---\r\nBody with CRLF";
|
|
118
122
|
|
|
119
123
|
const { frontmatter, body } = parseSkillFile(raw);
|
|
120
124
|
expect(frontmatter.name).toBe("crlf-skill");
|
|
@@ -204,7 +208,7 @@ describe("createReadSkillTool", () => {
|
|
|
204
208
|
|
|
205
209
|
it("throws when no skills are provided", () => {
|
|
206
210
|
expect(() => createReadSkillTool([])).toThrow(
|
|
207
|
-
"createReadSkillTool requires at least one skill"
|
|
211
|
+
"createReadSkillTool requires at least one skill"
|
|
208
212
|
);
|
|
209
213
|
});
|
|
210
214
|
});
|
|
@@ -242,7 +246,9 @@ describe("createReadSkillHandler", () => {
|
|
|
242
246
|
|
|
243
247
|
const text = result.toolResponse as string;
|
|
244
248
|
expect(text).toContain("Skill directory: /skills/skill-a");
|
|
245
|
-
expect(text).toContain(
|
|
249
|
+
expect(text).toContain(
|
|
250
|
+
"Relative paths in this skill resolve against the skill directory above."
|
|
251
|
+
);
|
|
246
252
|
});
|
|
247
253
|
|
|
248
254
|
it("lists resources derived from resourceContents keys", () => {
|
|
@@ -298,17 +304,21 @@ describe("createReadSkillHandler", () => {
|
|
|
298
304
|
const result = handler({ skill_name: "nonexistent" });
|
|
299
305
|
|
|
300
306
|
expect(typeof result.toolResponse).toBe("string");
|
|
301
|
-
expect(
|
|
307
|
+
expect(result.toolResponse as string).toContain("not found");
|
|
302
308
|
expect(result.data).toBeNull();
|
|
303
309
|
});
|
|
304
310
|
|
|
305
311
|
it("handles single skill", () => {
|
|
306
312
|
const skills: Skill[] = [
|
|
307
|
-
{
|
|
313
|
+
{
|
|
314
|
+
name: "skill-a",
|
|
315
|
+
description: "First",
|
|
316
|
+
instructions: "Instructions for A",
|
|
317
|
+
},
|
|
308
318
|
];
|
|
309
319
|
const handler = createReadSkillHandler(skills);
|
|
310
320
|
const result = handler({ skill_name: "skill-a" });
|
|
311
|
-
expect(
|
|
321
|
+
expect(result.toolResponse as string).toContain("Instructions for A");
|
|
312
322
|
});
|
|
313
323
|
});
|
|
314
324
|
|
|
@@ -350,7 +360,9 @@ describe("buildSkillRegistration", () => {
|
|
|
350
360
|
{ name: "dupe", description: "First", instructions: "A" },
|
|
351
361
|
{ name: "dupe", description: "Second", instructions: "B" },
|
|
352
362
|
];
|
|
353
|
-
expect(() => buildSkillRegistration(skills)).toThrow(
|
|
363
|
+
expect(() => buildSkillRegistration(skills)).toThrow(
|
|
364
|
+
"Duplicate skill names: dupe"
|
|
365
|
+
);
|
|
354
366
|
});
|
|
355
367
|
|
|
356
368
|
it("returns a complete tool entry with handler", () => {
|
|
@@ -389,7 +401,7 @@ describe("buildSkillRegistration", () => {
|
|
|
389
401
|
if (!registration) return;
|
|
390
402
|
const result = registration.handler(
|
|
391
403
|
{ skill_name: "test-skill" },
|
|
392
|
-
{ threadId: "t-1", toolCallId: "tc-1", toolName: "ReadSkill" }
|
|
404
|
+
{ threadId: "t-1", toolCallId: "tc-1", toolName: "ReadSkill" }
|
|
393
405
|
);
|
|
394
406
|
|
|
395
407
|
if (result instanceof Promise) {
|
|
@@ -414,9 +426,7 @@ describe("buildSkillRegistration", () => {
|
|
|
414
426
|
// FileSystemSkillProvider — resource discovery
|
|
415
427
|
// ---------------------------------------------------------------------------
|
|
416
428
|
|
|
417
|
-
function createMockFs(
|
|
418
|
-
tree: Record<string, string | "DIR">,
|
|
419
|
-
): SandboxFileSystem {
|
|
429
|
+
function createMockFs(tree: Record<string, string | "DIR">): SandboxFileSystem {
|
|
420
430
|
const dir = (entries: DirentEntry[]): DirentEntry[] => entries;
|
|
421
431
|
|
|
422
432
|
return {
|
|
@@ -11,7 +11,13 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
11
11
|
setHandler: (_def: unknown, _handler: unknown) => {},
|
|
12
12
|
uuid4: () =>
|
|
13
13
|
`00000000-0000-0000-0000-${String(++idCounter).padStart(12, "0")}`,
|
|
14
|
-
log: {
|
|
14
|
+
log: {
|
|
15
|
+
trace: () => {},
|
|
16
|
+
debug: () => {},
|
|
17
|
+
info: () => {},
|
|
18
|
+
warn: () => {},
|
|
19
|
+
error: () => {},
|
|
20
|
+
},
|
|
15
21
|
};
|
|
16
22
|
});
|
|
17
23
|
|
|
@@ -148,7 +154,11 @@ describe("createAgentStateManager integration", () => {
|
|
|
148
154
|
const sm = createAgentStateManager({});
|
|
149
155
|
|
|
150
156
|
sm.updateUsage({ inputTokens: 100, outputTokens: 50 });
|
|
151
|
-
sm.updateUsage({
|
|
157
|
+
sm.updateUsage({
|
|
158
|
+
inputTokens: 200,
|
|
159
|
+
outputTokens: 100,
|
|
160
|
+
cachedWriteTokens: 30,
|
|
161
|
+
});
|
|
152
162
|
sm.updateUsage({ cachedReadTokens: 20, reasonTokens: 10 });
|
|
153
163
|
|
|
154
164
|
expect(sm.getTotalUsage()).toEqual({
|