@codeworksh/harness 0.2.0 → 0.3.0-canary.29.1.g334eb9ee
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/{daytona-ByTHZelc.mjs → daytona-D4sNwzSr.mjs} +13 -7
- package/daytona-D4sNwzSr.mjs.map +1 -0
- package/effect.d.mts +848 -825
- package/effect.mjs +573 -211
- package/effect.mjs.map +1 -1
- package/package.json +6 -4
- package/resize-D34zcVQl.mjs +461 -0
- package/resize-D34zcVQl.mjs.map +1 -0
- package/sandboxes/daytona/index.mjs +1 -1
- package/tool/image/worker.mjs +1 -1
- package/daytona-ByTHZelc.mjs.map +0 -1
- package/resize-Dwv5dCgY.mjs +0 -263
- package/resize-Dwv5dCgY.mjs.map +0 -1
package/effect.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { n as __reExport, t as __exportAll } from "./rolldown-runtime-B4FMCO8f.mjs";
|
|
2
2
|
import { a as io_exports, i as errors_exports, n as shell_exports, o as instance_exports, r as filesystem_exports, s as posix } from "./resource-DFZh0e-s.mjs";
|
|
3
|
-
import "./
|
|
3
|
+
import { a as decode, c as importModule, d as share, l as lendResolve, n as dimensionNote, o as load$5, r as resize, s as load$4, u as resolveModule } from "./resize-D34zcVQl.mjs";
|
|
4
|
+
import "./daytona-D4sNwzSr.mjs";
|
|
4
5
|
import "./vercel-3GouQSJI.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { Cause, Clock, Config, Context, Crypto, DateTime, Deferred, Duration, Effect, Exit, Fiber, FiberSet, FileSystem, Layer, LayerMap, Option, Path, Predicate, PubSub, Queue, Random, Ref, Result, Schedule, Schema, SchemaIssue, Scope, Semaphore, Stream, String as String$1 } from "effect";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { Cause, Clock, Config, Context, Crypto, Data, DateTime, Deferred, Duration, Effect, Exit, Fiber, FiberSet, FileSystem, Layer, LayerMap, Metric, Option, Path, Predicate, PubSub, Queue, Random, Ref, Result, Schedule, Schema, SchemaIssue, Scope, Semaphore, Stream, String as String$1 } from "effect";
|
|
8
8
|
import { Message, Model, getEnvApiKey, llm, normalizeOptionalNulls, stream } from "@codeworksh/aikit";
|
|
9
9
|
import { MessageID as ID, SessionID as ID$1 } from "@codeworksh/plugin/ids";
|
|
10
10
|
import { Migrator, SqlClient, SqlClient as SqlClient$1, SqlError, SqlSchema, SqlSchema as SqlSchema$1 } from "effect/sql";
|
|
@@ -16,21 +16,20 @@ import { NodeChildProcessSpawner, NodeCrypto, NodeFileSystem, NodePath } from "@
|
|
|
16
16
|
import { Model as Model$1, VariantSchema } from "effect/schema";
|
|
17
17
|
import { Hex } from "effect/encoding";
|
|
18
18
|
import { MemoryProvider, RealFSProvider, SqliteProvider, create } from "@platformatic/vfs";
|
|
19
|
-
import fs
|
|
19
|
+
import fs from "node:fs";
|
|
20
20
|
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
21
21
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
22
|
-
import {
|
|
22
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
23
23
|
import { DirectoryNotFoundError, DirectoryNotFoundError as DirectoryNotFoundError$1, Info, Info as Info$1, NotDirectoryError, NotDirectoryError as NotDirectoryError$1, Service, Service as Service$8 } from "@codeworksh/plugin/location";
|
|
24
24
|
import { generateModels } from "@codeworksh/aikit/modelgen";
|
|
25
25
|
import { JsonGitHubCopilotAuthStorage, getGitHubCopilotApiKey } from "@codeworksh/aikit/oauth/github/copilot";
|
|
26
26
|
import { JsonOpenAICodexAuthStorage, getOpenAICodexApiKey } from "@codeworksh/aikit/oauth/openai/codex";
|
|
27
27
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
28
28
|
import * as Section from "@codeworksh/plugin/plugin/section";
|
|
29
|
+
import ignore from "ignore";
|
|
30
|
+
import { parse as parse$1 } from "yaml";
|
|
29
31
|
import * as Diff from "diff";
|
|
30
32
|
import { FetchHttpClient, HttpClient } from "effect/http";
|
|
31
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
32
|
-
import path from "node:path";
|
|
33
|
-
import { Script, constants } from "node:vm";
|
|
34
33
|
import npa from "npm-package-arg";
|
|
35
34
|
import * as AikitFailure from "@codeworksh/aikit/failure";
|
|
36
35
|
import "@codeworksh/plugin/tool/error";
|
|
@@ -380,7 +379,7 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
380
379
|
defaultLayer: () => defaultLayer$1,
|
|
381
380
|
layer: () => layer$26,
|
|
382
381
|
locationConfig: () => locationConfig,
|
|
383
|
-
path: () => path$
|
|
382
|
+
path: () => path$1,
|
|
384
383
|
resolveDatabaseLocation: () => resolveDatabaseLocation
|
|
385
384
|
});
|
|
386
385
|
const setup = Effect.gen(function* () {
|
|
@@ -407,11 +406,11 @@ function resolveDatabaseLocation(configured, data) {
|
|
|
407
406
|
if (configured === ":memory:" || posix.isAbsolute(configured)) return configured;
|
|
408
407
|
return posix.join(data, configured);
|
|
409
408
|
}
|
|
410
|
-
const path$
|
|
409
|
+
const path$1 = Effect.fn("Database.path")(function* (data) {
|
|
411
410
|
const directory = data ?? (yield* resolve$4()).data;
|
|
412
411
|
return resolveDatabaseLocation(yield* locationConfig, directory);
|
|
413
412
|
});
|
|
414
|
-
const defaultLayer$1 = Layer.unwrap(path$
|
|
413
|
+
const defaultLayer$1 = Layer.unwrap(path$1().pipe(Effect.map(layer$26)));
|
|
415
414
|
//#endregion
|
|
416
415
|
//#region src/event/schema.ts
|
|
417
416
|
var schema_exports$7 = /* @__PURE__ */ __exportAll({});
|
|
@@ -2643,6 +2642,114 @@ const layer$22 = Layer.effect(Service$13, Effect.gen(function* () {
|
|
|
2643
2642
|
return Service$13.of({ assemble });
|
|
2644
2643
|
}));
|
|
2645
2644
|
//#endregion
|
|
2645
|
+
//#region src/telemetry/telemetry.ts
|
|
2646
|
+
const durationBuckets = Metric.boundariesFromIterable([
|
|
2647
|
+
.01,
|
|
2648
|
+
.02,
|
|
2649
|
+
.04,
|
|
2650
|
+
.08,
|
|
2651
|
+
.16,
|
|
2652
|
+
.32,
|
|
2653
|
+
.64,
|
|
2654
|
+
1.28,
|
|
2655
|
+
2.56,
|
|
2656
|
+
5.12,
|
|
2657
|
+
10.24,
|
|
2658
|
+
20.48,
|
|
2659
|
+
40.96,
|
|
2660
|
+
81.92
|
|
2661
|
+
]);
|
|
2662
|
+
const llmDuration = Metric.histogram("codework.llm.duration", {
|
|
2663
|
+
description: "Provider request duration",
|
|
2664
|
+
attributes: { unit: "s" },
|
|
2665
|
+
boundaries: durationBuckets
|
|
2666
|
+
});
|
|
2667
|
+
const llmTtft = Metric.histogram("codework.llm.ttft", {
|
|
2668
|
+
description: "Time to first token: from request start to the first streamed content",
|
|
2669
|
+
attributes: { unit: "s" },
|
|
2670
|
+
boundaries: Metric.boundariesFromIterable([
|
|
2671
|
+
.001,
|
|
2672
|
+
.005,
|
|
2673
|
+
.01,
|
|
2674
|
+
.02,
|
|
2675
|
+
.04,
|
|
2676
|
+
.06,
|
|
2677
|
+
.08,
|
|
2678
|
+
.1,
|
|
2679
|
+
.25,
|
|
2680
|
+
.5,
|
|
2681
|
+
.75,
|
|
2682
|
+
1,
|
|
2683
|
+
2.5,
|
|
2684
|
+
5,
|
|
2685
|
+
7.5,
|
|
2686
|
+
10
|
|
2687
|
+
])
|
|
2688
|
+
});
|
|
2689
|
+
const llmTokens = Metric.counter("codework.llm.tokens", {
|
|
2690
|
+
description: "Tokens used, by type; cache and reasoning tokens are part of input and output",
|
|
2691
|
+
attributes: { unit: "{token}" },
|
|
2692
|
+
incremental: true
|
|
2693
|
+
});
|
|
2694
|
+
const llmCost = Metric.counter("codework.llm.cost", {
|
|
2695
|
+
description: "Provider request cost from the model catalog's prices",
|
|
2696
|
+
attributes: { unit: "USD" },
|
|
2697
|
+
incremental: true
|
|
2698
|
+
});
|
|
2699
|
+
const agentDuration = Metric.histogram("codework.agent.duration", {
|
|
2700
|
+
description: "Session run duration, across every turn it drains",
|
|
2701
|
+
attributes: { unit: "s" },
|
|
2702
|
+
boundaries: Metric.boundariesFromIterable([
|
|
2703
|
+
.1,
|
|
2704
|
+
.2,
|
|
2705
|
+
.4,
|
|
2706
|
+
.8,
|
|
2707
|
+
1.6,
|
|
2708
|
+
3.2,
|
|
2709
|
+
6.4,
|
|
2710
|
+
12.8,
|
|
2711
|
+
25.6,
|
|
2712
|
+
51.2,
|
|
2713
|
+
102.4,
|
|
2714
|
+
204.8,
|
|
2715
|
+
409.6
|
|
2716
|
+
])
|
|
2717
|
+
});
|
|
2718
|
+
const toolDuration = Metric.histogram("codework.tool.duration", {
|
|
2719
|
+
description: "Tool execution duration",
|
|
2720
|
+
attributes: { unit: "s" },
|
|
2721
|
+
boundaries: durationBuckets
|
|
2722
|
+
});
|
|
2723
|
+
const sandboxProvisionDuration = Metric.histogram("codework.sandbox.provision.duration", {
|
|
2724
|
+
description: "Sandbox driver create and wake duration",
|
|
2725
|
+
attributes: { unit: "s" },
|
|
2726
|
+
boundaries: durationBuckets
|
|
2727
|
+
});
|
|
2728
|
+
const sandboxExecDuration = Metric.histogram("codework.sandbox.exec.duration", {
|
|
2729
|
+
description: "Sandbox shell command duration",
|
|
2730
|
+
attributes: { unit: "s" },
|
|
2731
|
+
boundaries: durationBuckets
|
|
2732
|
+
});
|
|
2733
|
+
const sandboxActive = Metric.gauge("codework.sandbox.active", {
|
|
2734
|
+
description: "Managed and remote sandboxes with at least one mount",
|
|
2735
|
+
attributes: { unit: "{sandbox}" }
|
|
2736
|
+
});
|
|
2737
|
+
const record$1 = (metric, value, attributes) => Metric.update(Metric.withAttributes(metric, attributes), value);
|
|
2738
|
+
const adjust = (metric, delta, attributes) => Metric.modify(Metric.withAttributes(metric, attributes), delta);
|
|
2739
|
+
const errorType = (cause) => {
|
|
2740
|
+
if (Cause.hasInterruptsOnly(cause)) return "interrupted";
|
|
2741
|
+
const error = Cause.squash(cause);
|
|
2742
|
+
return typeof error === "object" && error !== null && "_tag" in error && typeof error._tag === "string" ? error._tag : "_OTHER";
|
|
2743
|
+
};
|
|
2744
|
+
const withError = (attributes, exit) => Exit.isSuccess(exit) ? attributes : {
|
|
2745
|
+
...attributes,
|
|
2746
|
+
error: errorType(exit.cause)
|
|
2747
|
+
};
|
|
2748
|
+
const timed = (self, observe) => Effect.clockWith((clock) => {
|
|
2749
|
+
const start = clock.currentTimeMillisUnsafe();
|
|
2750
|
+
return self.pipe(Effect.onExit((exit) => observe((clock.currentTimeMillisUnsafe() - start) / 1e3, exit)));
|
|
2751
|
+
});
|
|
2752
|
+
//#endregion
|
|
2646
2753
|
//#region src/sandbox/driver.ts
|
|
2647
2754
|
var driver_exports = /* @__PURE__ */ __exportAll({});
|
|
2648
2755
|
import * as import__codeworksh_plugin_sandbox_driver from "@codeworksh/plugin/sandbox/driver";
|
|
@@ -3029,6 +3136,22 @@ const parseJson = (driver, operation, value) => decodeUnknownJson(value).pipe(Ef
|
|
|
3029
3136
|
operation,
|
|
3030
3137
|
cause
|
|
3031
3138
|
})));
|
|
3139
|
+
const provision = (driver, operation, self) => timed(self, (seconds, exit) => record$1(sandboxProvisionDuration, seconds, withError({
|
|
3140
|
+
driver,
|
|
3141
|
+
operation
|
|
3142
|
+
}, exit)));
|
|
3143
|
+
const timedShell = (shell, driver) => {
|
|
3144
|
+
const time = (self) => timed(self, (seconds, exit) => record$1(sandboxExecDuration, seconds, withError({ driver }, exit)));
|
|
3145
|
+
const stream = shell.stream;
|
|
3146
|
+
return {
|
|
3147
|
+
exec: (command, options) => time(shell.exec(command, options)),
|
|
3148
|
+
execArgv: (argv, options) => time(shell.execArgv(argv, options)),
|
|
3149
|
+
...stream === void 0 ? {} : { stream: (command, options) => Stream.unwrap(Effect.clockWith((clock) => {
|
|
3150
|
+
const start = clock.currentTimeMillisUnsafe();
|
|
3151
|
+
return Effect.succeed(stream(command, options).pipe(Stream.onExit((exit) => record$1(sandboxExecDuration, (clock.currentTimeMillisUnsafe() - start) / 1e3, withError({ driver }, exit)))));
|
|
3152
|
+
})) }
|
|
3153
|
+
};
|
|
3154
|
+
};
|
|
3032
3155
|
const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
3033
3156
|
const sql = yield* SqlClient.SqlClient;
|
|
3034
3157
|
const registry = yield* Registry;
|
|
@@ -3039,20 +3162,22 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3039
3162
|
const hostCreatedAt = asDate(yield* DateTime.now);
|
|
3040
3163
|
const refCount = (id) => refs.get(id) ?? 0;
|
|
3041
3164
|
const usage = (id, count = refCount(id)) => id === instance_exports.SandboxInstance.ID.local ? "pinned" : count > 0 ? "busy" : "idle";
|
|
3165
|
+
const activeChanged = (lease, delta) => lease.driver === void 0 ? Effect.void : adjust(sandboxActive, delta, { driver: lease.driver });
|
|
3042
3166
|
const acquireLease = (id, lease) => gate.withPermits(1)(Effect.sync(() => {
|
|
3043
3167
|
const wasIdle = refCount(id) === 0;
|
|
3044
3168
|
refs.set(id, refCount(id) + 1);
|
|
3045
3169
|
lease.held = true;
|
|
3046
3170
|
return wasIdle;
|
|
3047
|
-
}));
|
|
3171
|
+
})).pipe(Effect.tap((wasIdle) => wasIdle ? activeChanged(lease, 1) : Effect.void));
|
|
3048
3172
|
const releaseLease = (id, lease) => Effect.gen(function* () {
|
|
3049
3173
|
if (!lease.held) return;
|
|
3050
|
-
yield* gate.withPermits(1)(Effect.sync(() => {
|
|
3174
|
+
if (yield* gate.withPermits(1)(Effect.sync(() => {
|
|
3051
3175
|
const next = Math.max(0, refCount(id) - 1);
|
|
3052
3176
|
if (next === 0) refs.delete(id);
|
|
3053
3177
|
else refs.set(id, next);
|
|
3054
3178
|
lease.held = false;
|
|
3055
|
-
|
|
3179
|
+
return next === 0;
|
|
3180
|
+
}))) yield* activeChanged(lease, -1);
|
|
3056
3181
|
if (!lease.mounted) return;
|
|
3057
3182
|
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
3058
3183
|
yield* sql`
|
|
@@ -3222,7 +3347,7 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3222
3347
|
const attached = yield* runtime(row, "resolveCwd");
|
|
3223
3348
|
return driver_exports.SandboxDriver.AbsolutePath.make(io_exports.SandboxIO.resolveMountCwd(attached.input.runtimeConfig.defaultCwd, override));
|
|
3224
3349
|
});
|
|
3225
|
-
const create = Effect.fn("Sandbox.Controller.create")(function* (input) {
|
|
3350
|
+
const create = Effect.fn("Sandbox.Controller.create", { level: "Info" })(function* (input) {
|
|
3226
3351
|
const driver = yield* registry.get(input.driver.name);
|
|
3227
3352
|
const config = yield* registry.decodeCreateConfig(input.driver.name, input.config);
|
|
3228
3353
|
const id = input.instanceId ?? instance_exports.SandboxInstance.ID.create();
|
|
@@ -3238,10 +3363,10 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3238
3363
|
status: "provisioning",
|
|
3239
3364
|
metadata: input.metadata
|
|
3240
3365
|
});
|
|
3241
|
-
const provisioned = yield* driver.create({
|
|
3366
|
+
const provisioned = yield* provision(driver.name, "create", driver.create({
|
|
3242
3367
|
instanceId: id,
|
|
3243
3368
|
config
|
|
3244
|
-
}).pipe(Effect.catch((error) => persistProviderFailure(id, "faulted", error)));
|
|
3369
|
+
})).pipe(Effect.catch((error) => persistProviderFailure(id, "faulted", error)));
|
|
3245
3370
|
return yield* Effect.gen(function* () {
|
|
3246
3371
|
const encoded = yield* registry.encodeRuntimeConfig(driver.name, provisioned.runtimeConfig);
|
|
3247
3372
|
const runtimeConfig = yield* encodeUnknownJson$1(encoded).pipe(Effect.mapError((cause) => (0, errors_exports.providerError)({
|
|
@@ -3330,7 +3455,8 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3330
3455
|
let invalidateBeforeAttach = false;
|
|
3331
3456
|
const lease = reserved ?? {
|
|
3332
3457
|
held: false,
|
|
3333
|
-
mounted: false
|
|
3458
|
+
mounted: false,
|
|
3459
|
+
driver: void 0
|
|
3334
3460
|
};
|
|
3335
3461
|
if (id === instance_exports.SandboxInstance.ID.local) identity = {
|
|
3336
3462
|
id,
|
|
@@ -3355,6 +3481,7 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3355
3481
|
});
|
|
3356
3482
|
invalidateBeforeAttach = row.status !== "online";
|
|
3357
3483
|
const attached = yield* runtime(row, "mount");
|
|
3484
|
+
lease.driver = attached.driver.name;
|
|
3358
3485
|
identity = {
|
|
3359
3486
|
id,
|
|
3360
3487
|
driver: attached.driver.name,
|
|
@@ -3377,7 +3504,10 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3377
3504
|
lease.held = true;
|
|
3378
3505
|
return idle;
|
|
3379
3506
|
}));
|
|
3380
|
-
if (reserved === void 0)
|
|
3507
|
+
if (reserved === void 0) {
|
|
3508
|
+
yield* Effect.addFinalizer(() => releaseLease(id, lease));
|
|
3509
|
+
if (wasIdle) yield* activeChanged(lease, 1);
|
|
3510
|
+
}
|
|
3381
3511
|
if (wasIdle && invalidateBeforeAttach) yield* transports.invalidate(id);
|
|
3382
3512
|
}
|
|
3383
3513
|
const handleTransportError = (error) => {
|
|
@@ -3407,7 +3537,7 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3407
3537
|
WHERE id = ${id}
|
|
3408
3538
|
`.pipe(Effect.orDie);
|
|
3409
3539
|
}
|
|
3410
|
-
return Layer.mergeAll(Layer.succeed(io_exports.SandboxIO.Current, identity), Layer.succeed(io_exports.SandboxIO.FileSystem, filesystem_exports.SandboxFileSystem.withCwd(fs, identity.cwd)), Layer.succeed(io_exports.SandboxIO.Shell, (0, shell_exports.withCwd)(shell, identity.cwd))).pipe(io_exports.SandboxIO.withMutation);
|
|
3540
|
+
return Layer.mergeAll(Layer.succeed(io_exports.SandboxIO.Current, identity), Layer.succeed(io_exports.SandboxIO.FileSystem, filesystem_exports.SandboxFileSystem.withCwd(fs, identity.cwd)), Layer.succeed(io_exports.SandboxIO.Shell, timedShell((0, shell_exports.withCwd)(shell, identity.cwd), identity.driver))).pipe(io_exports.SandboxIO.withMutation);
|
|
3411
3541
|
}));
|
|
3412
3542
|
const mount = (id, mountOptions) => mountLayer(id, mountOptions);
|
|
3413
3543
|
const withMount = (id, use, mountOptions) => use.pipe(Effect.provide(mount(id, mountOptions)), Effect.scoped);
|
|
@@ -3434,7 +3564,7 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3434
3564
|
});
|
|
3435
3565
|
return yield* reload(id);
|
|
3436
3566
|
});
|
|
3437
|
-
const wake = Effect.fn("Sandbox.Controller.wake")(function* (id) {
|
|
3567
|
+
const wake = Effect.fn("Sandbox.Controller.wake", { level: "Info" })(function* (id) {
|
|
3438
3568
|
if (id === instance_exports.SandboxInstance.ID.local) return hostInfo();
|
|
3439
3569
|
const row = yield* requireRow(id);
|
|
3440
3570
|
if (row.status === "removed") return yield* new errors_exports.SandboxRemovedError({
|
|
@@ -3448,7 +3578,7 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3448
3578
|
operation: "wake"
|
|
3449
3579
|
});
|
|
3450
3580
|
if (refCount(id) === 0) yield* transports.invalidate(id);
|
|
3451
|
-
const observed = yield* attached.driver.wake(attached.input).pipe(Effect.catch((error) => (0, errors_exports.providerErrorIsNotFound)(error) ? markUnavailable(id, error) : persistProviderFailure(id, "faulted", error)));
|
|
3581
|
+
const observed = yield* provision(attached.driver.name, "wake", attached.driver.wake(attached.input)).pipe(Effect.catch((error) => (0, errors_exports.providerErrorIsNotFound)(error) ? markUnavailable(id, error) : persistProviderFailure(id, "faulted", error)));
|
|
3452
3582
|
yield* updateObservation({
|
|
3453
3583
|
id,
|
|
3454
3584
|
status: observed.status,
|
|
@@ -3607,7 +3737,8 @@ const make$14 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3607
3737
|
const id = input.instanceId ?? instance_exports.SandboxInstance.ID.create();
|
|
3608
3738
|
const lease = {
|
|
3609
3739
|
held: false,
|
|
3610
|
-
mounted: false
|
|
3740
|
+
mounted: false,
|
|
3741
|
+
driver: input.driver.name
|
|
3611
3742
|
};
|
|
3612
3743
|
return Layer.unwrap(Effect.gen(function* () {
|
|
3613
3744
|
yield* acquireLease(id, lease);
|
|
@@ -3875,7 +4006,8 @@ const fromBash = (bash) => {
|
|
|
3875
4006
|
});
|
|
3876
4007
|
return shell_exports.Shell.of((0, shell_exports.fromExec)({ exec }));
|
|
3877
4008
|
};
|
|
3878
|
-
const
|
|
4009
|
+
const JustBash = load$4("just-bash").pipe(Effect.map((module) => module), Effect.orDie);
|
|
4010
|
+
const make$12 = (module, vfs, cwd) => fromBash(new module.Bash({
|
|
3879
4011
|
fs: bridge(vfs, cwd),
|
|
3880
4012
|
cwd
|
|
3881
4013
|
}));
|
|
@@ -3884,9 +4016,10 @@ const make$12 = (vfs, cwd) => fromBash(new Bash({
|
|
|
3884
4016
|
* mount binds its cwd. This matches Flue's BashFactory ownership: mounts share
|
|
3885
4017
|
* the VFS, but shell state (functions, options, command hashes) is mount-local.
|
|
3886
4018
|
*/
|
|
3887
|
-
const transport$1 = (inner) => Layer.provideMerge(Layer.effect(shell_exports.Shell, Effect.
|
|
3888
|
-
const
|
|
3889
|
-
|
|
4019
|
+
const transport$1 = (inner) => Layer.provideMerge(Layer.effect(shell_exports.Shell, Effect.gen(function* () {
|
|
4020
|
+
const module = yield* JustBash;
|
|
4021
|
+
const vfs = yield* Vfs;
|
|
4022
|
+
return (0, shell_exports.perMount)(make$12(module, vfs, "/"), (cwd) => make$12(module, vfs, cwd));
|
|
3890
4023
|
})), inner);
|
|
3891
4024
|
//#endregion
|
|
3892
4025
|
//#region src/sandbox/virtual.ts
|
|
@@ -4333,7 +4466,8 @@ const up = Effect.fn("SandboxFileSystem.up")(function* (fs, options) {
|
|
|
4333
4466
|
const isDirectory$1 = Effect.fn("SandboxFileSystem.isDirectory")(function* (fs, path) {
|
|
4334
4467
|
return yield* fs.stat(path).pipe(Effect.map((stat) => stat.isDirectory), Effect.orElseSucceed(() => false));
|
|
4335
4468
|
});
|
|
4336
|
-
|
|
4469
|
+
/** True when `path` exists and is a regular file; false when it is absent or unreadable. */
|
|
4470
|
+
const isFile = Effect.fn("SandboxFileSystem.isFile")(function* (fs, path) {
|
|
4337
4471
|
return yield* fs.stat(path).pipe(Effect.map((stat) => stat.isFile), Effect.orElseSucceed(() => false));
|
|
4338
4472
|
});
|
|
4339
4473
|
/** Read a file, or `undefined` when it is missing or unreadable. */
|
|
@@ -5181,7 +5315,7 @@ var settings_exports = /* @__PURE__ */ __exportAll({
|
|
|
5181
5315
|
layer: () => layer$8,
|
|
5182
5316
|
load: () => load$3,
|
|
5183
5317
|
modules: () => modules,
|
|
5184
|
-
parse: () => parse$
|
|
5318
|
+
parse: () => parse$5,
|
|
5185
5319
|
paths: () => paths,
|
|
5186
5320
|
projectRoot: () => projectRoot,
|
|
5187
5321
|
userConfigDir: () => userConfigDir
|
|
@@ -5270,7 +5404,7 @@ const decodeFormatter = SchemaIssue.makeFormatterStandardSchemaV1({ leafHook: (i
|
|
|
5270
5404
|
const message = "annotations" in issue ? issue.annotations?.message : void 0;
|
|
5271
5405
|
return typeof message === "string" ? message.replaceAll(/\s*\n\s*/g, " ") : "invalid value";
|
|
5272
5406
|
} });
|
|
5273
|
-
const parse$
|
|
5407
|
+
const parse$5 = Effect.fn("Settings.parse")(function* (path, source) {
|
|
5274
5408
|
const errors = [];
|
|
5275
5409
|
const json = parse(source, errors, {
|
|
5276
5410
|
allowTrailingComma: true,
|
|
@@ -5318,7 +5452,7 @@ const attempt = (path) => fileSystem.readFileString(path).pipe(Effect.mapError((
|
|
|
5318
5452
|
path,
|
|
5319
5453
|
reason: "read",
|
|
5320
5454
|
detail: error.reason._tag
|
|
5321
|
-
})), Effect.flatMap((source) => parse$
|
|
5455
|
+
})), Effect.flatMap((source) => parse$5(path, source)));
|
|
5322
5456
|
/**
|
|
5323
5457
|
* One read of every layer. Exported for the harness constructor, which needs the plugin
|
|
5324
5458
|
* selection before any layer is built; everything else goes through `Service`.
|
|
@@ -5624,6 +5758,22 @@ const defaultPromptPlugin = (0, plugin_exports$1.define)({
|
|
|
5624
5758
|
})
|
|
5625
5759
|
});
|
|
5626
5760
|
//#endregion
|
|
5761
|
+
//#region src/plugin/builtin/prompt/walk.ts
|
|
5762
|
+
const stripBom$1 = (raw) => raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
5763
|
+
/** `directory` and every ancestor up to the sandbox root, closest first. */
|
|
5764
|
+
const ancestors = (directory) => {
|
|
5765
|
+
const chain = [];
|
|
5766
|
+
for (let current = directory;; current = posix.dirname(current)) {
|
|
5767
|
+
chain.push(current);
|
|
5768
|
+
if (posix.dirname(current) === current) return chain;
|
|
5769
|
+
}
|
|
5770
|
+
};
|
|
5771
|
+
/**
|
|
5772
|
+
* Which of `paths` exist as the requested kind (`entry` takes either). Absence stays silent; any other failure
|
|
5773
|
+
* (permissions, a remote blip) is logged so a dropped entry is not a mystery, then counts as absent.
|
|
5774
|
+
*/
|
|
5775
|
+
const present$1 = (fs, paths, kind, label) => Effect.forEach(paths, (path) => fs.exists(path).pipe(Effect.filterOrElse((exists) => !exists || kind === "entry", () => fs.stat(path).pipe(Effect.map((stat) => kind === "file" ? stat.isFile : stat.isDirectory))), Effect.catch((error) => filesystem_exports.SandboxFileSystem.isNotFoundError(error.cause) ? Effect.succeed(false) : Effect.logWarning(`could not stat ${label} ${path}`, error.cause).pipe(Effect.as(false)))), { concurrency: "unbounded" }).pipe(Effect.map((hits) => new Set(paths.filter((_, index) => hits[index]))));
|
|
5776
|
+
//#endregion
|
|
5627
5777
|
//#region src/plugin/builtin/prompt/instruction.ts
|
|
5628
5778
|
/** First readable match per directory wins. */
|
|
5629
5779
|
const names = [
|
|
@@ -5635,22 +5785,9 @@ const names = [
|
|
|
5635
5785
|
"CLAUDE.MD"
|
|
5636
5786
|
];
|
|
5637
5787
|
const content = (raw) => {
|
|
5638
|
-
const text =
|
|
5788
|
+
const text = stripBom$1(raw);
|
|
5639
5789
|
return text.trim().length === 0 ? void 0 : text;
|
|
5640
5790
|
};
|
|
5641
|
-
/** `directory` and every ancestor up to the sandbox root, closest first. */
|
|
5642
|
-
const ancestors = (directory) => {
|
|
5643
|
-
const chain = [];
|
|
5644
|
-
for (let current = directory;; current = posix.dirname(current)) {
|
|
5645
|
-
chain.push(current);
|
|
5646
|
-
if (posix.dirname(current) === current) return chain;
|
|
5647
|
-
}
|
|
5648
|
-
};
|
|
5649
|
-
/**
|
|
5650
|
-
* Which of `paths` are files. Absence is the expected answer and stays silent; any other failure
|
|
5651
|
-
* (permissions, a remote blip) is logged so a dropped file is not a mystery, then counts as absent.
|
|
5652
|
-
*/
|
|
5653
|
-
const present$1 = (fs, paths) => Effect.forEach(paths, (path) => fs.stat(path).pipe(Effect.map((stat) => stat.isFile), Effect.catch((error) => filesystem_exports.SandboxFileSystem.isNotFoundError(error.cause) ? Effect.succeed(false) : Effect.logWarning(`could not stat instruction file ${path}`, error.cause).pipe(Effect.as(false)))), { concurrency: "unbounded" }).pipe(Effect.map((hits) => new Set(paths.filter((_, index) => hits[index]))));
|
|
5654
5791
|
/** A file that stats but does not read falls through to the next candidate. */
|
|
5655
5792
|
const first = Effect.fnUntraced(function* (fs, directory, files) {
|
|
5656
5793
|
for (const name of names) {
|
|
@@ -5678,9 +5815,9 @@ const shadowing = (location) => {
|
|
|
5678
5815
|
return location.space.location.startsWith(`${main}/`) ? main : void 0;
|
|
5679
5816
|
};
|
|
5680
5817
|
/** One file per directory from the sandbox root down to the cwd, closest last so it has the final word. */
|
|
5681
|
-
const discover = Effect.fnUntraced(function* (fs, location) {
|
|
5818
|
+
const discover$1 = Effect.fnUntraced(function* (fs, location) {
|
|
5682
5819
|
const chain = ancestors(location.directory);
|
|
5683
|
-
const files = yield* present$1(fs, chain.flatMap((directory) => names.map((name) => posix.join(directory, name))));
|
|
5820
|
+
const files = yield* present$1(fs, chain.flatMap((directory) => names.map((name) => posix.join(directory, name))), "file", "instruction file");
|
|
5684
5821
|
const found = yield* Effect.forEach(chain, (directory) => first(fs, directory, files), { concurrency: "unbounded" });
|
|
5685
5822
|
const main = shadowing(location);
|
|
5686
5823
|
const worktree = main === void 0 ? void 0 : found[chain.indexOf(location.space.location)];
|
|
@@ -5696,7 +5833,7 @@ const instructionPlugin = (0, plugin_exports$1.define)({
|
|
|
5696
5833
|
kind: "prompt",
|
|
5697
5834
|
setup: Effect.fn("InstructionPlugin.setup")(function* (ctx) {
|
|
5698
5835
|
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
5699
|
-
const files = (yield* discover(fs, ctx.location)).filter((file) => file.content !== void 0);
|
|
5836
|
+
const files = (yield* discover$1(fs, ctx.location)).filter((file) => file.content !== void 0);
|
|
5700
5837
|
if (files.length === 0) return;
|
|
5701
5838
|
const prompt = ctx.plugin.prompt;
|
|
5702
5839
|
prompt.sections.append(Section.ProjectContext, "Project-Specific Instructions & Guidelines:");
|
|
@@ -5704,6 +5841,236 @@ const instructionPlugin = (0, plugin_exports$1.define)({
|
|
|
5704
5841
|
})
|
|
5705
5842
|
});
|
|
5706
5843
|
//#endregion
|
|
5844
|
+
//#region src/plugin/builtin/prompt/skill.ts
|
|
5845
|
+
/** Max name length per Agent Skills spec. */
|
|
5846
|
+
const MAX_NAME_LENGTH = 64;
|
|
5847
|
+
/** Max description length per Agent Skills spec. */
|
|
5848
|
+
const MAX_DESCRIPTION_LENGTH = 1024;
|
|
5849
|
+
const IGNORE_FILE_NAMES = [
|
|
5850
|
+
".gitignore",
|
|
5851
|
+
".ignore",
|
|
5852
|
+
".fdignore"
|
|
5853
|
+
];
|
|
5854
|
+
const normalizeNewlines = (value) => value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
5855
|
+
const extractFrontmatter = (content) => {
|
|
5856
|
+
const normalized = normalizeNewlines(stripBom$1(content));
|
|
5857
|
+
if (!normalized.startsWith("---")) return void 0;
|
|
5858
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
5859
|
+
if (endIndex === -1) return void 0;
|
|
5860
|
+
return normalized.slice(4, endIndex);
|
|
5861
|
+
};
|
|
5862
|
+
const parseFrontmatter = (raw) => {
|
|
5863
|
+
const yamlString = extractFrontmatter(raw);
|
|
5864
|
+
if (yamlString === void 0) return { frontmatter: {} };
|
|
5865
|
+
try {
|
|
5866
|
+
const parsed = parse$1(yamlString);
|
|
5867
|
+
return { frontmatter: typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {} };
|
|
5868
|
+
} catch (error) {
|
|
5869
|
+
return {
|
|
5870
|
+
frontmatter: {},
|
|
5871
|
+
error
|
|
5872
|
+
};
|
|
5873
|
+
}
|
|
5874
|
+
};
|
|
5875
|
+
const validateName = (name) => {
|
|
5876
|
+
const errors = [];
|
|
5877
|
+
if (name.length > MAX_NAME_LENGTH) errors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);
|
|
5878
|
+
if (!/^[a-z0-9-]+$/.test(name)) errors.push("name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)");
|
|
5879
|
+
if (name.startsWith("-") || name.endsWith("-")) errors.push("name must not start or end with a hyphen");
|
|
5880
|
+
if (name.includes("--")) errors.push("name must not contain consecutive hyphens");
|
|
5881
|
+
return errors;
|
|
5882
|
+
};
|
|
5883
|
+
const validateDescription = (description) => {
|
|
5884
|
+
const errors = [];
|
|
5885
|
+
if (typeof description !== "string" || description.trim() === "") errors.push("description is required");
|
|
5886
|
+
else if (description.length > MAX_DESCRIPTION_LENGTH) errors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);
|
|
5887
|
+
return errors;
|
|
5888
|
+
};
|
|
5889
|
+
const prefixIgnorePattern = (line, prefix) => {
|
|
5890
|
+
const trimmed = line.trim();
|
|
5891
|
+
if (!trimmed) return void 0;
|
|
5892
|
+
if (trimmed.startsWith("#") && !trimmed.startsWith("\\#")) return void 0;
|
|
5893
|
+
let pattern = line;
|
|
5894
|
+
let negated = false;
|
|
5895
|
+
if (pattern.startsWith("!")) {
|
|
5896
|
+
negated = true;
|
|
5897
|
+
pattern = pattern.slice(1);
|
|
5898
|
+
} else if (pattern.startsWith("\\!")) pattern = pattern.slice(1);
|
|
5899
|
+
if (pattern.startsWith("/")) pattern = pattern.slice(1);
|
|
5900
|
+
const prefixed = prefix ? `${prefix}${pattern}` : pattern;
|
|
5901
|
+
return negated ? `!${prefixed}` : prefixed;
|
|
5902
|
+
};
|
|
5903
|
+
const addIgnoreRules = Effect.fnUntraced(function* (fs, ig, dir, root, entryNames) {
|
|
5904
|
+
const relativeDir = posix.relative(root, dir);
|
|
5905
|
+
const prefix = relativeDir && relativeDir !== "." ? `${relativeDir}/` : "";
|
|
5906
|
+
const presentIgnoreFiles = IGNORE_FILE_NAMES.filter((name) => entryNames.has(name));
|
|
5907
|
+
if (presentIgnoreFiles.length === 0) return;
|
|
5908
|
+
const contents = yield* Effect.forEach(presentIgnoreFiles, (filename) => fs.readFile(posix.join(dir, filename)).pipe(Effect.option), { concurrency: "unbounded" });
|
|
5909
|
+
for (const content of contents) {
|
|
5910
|
+
if (Option.isNone(content)) continue;
|
|
5911
|
+
const patterns = content.value.split(/\r?\n/).map((line) => prefixIgnorePattern(line, prefix)).filter((line) => line !== void 0);
|
|
5912
|
+
if (patterns.length > 0) ig.add(patterns);
|
|
5913
|
+
}
|
|
5914
|
+
});
|
|
5915
|
+
const loadSkillFromFile = Effect.fnUntraced(function* (fs, filePath) {
|
|
5916
|
+
const isDeclaredSkill = posix.basename(filePath) === "SKILL.md";
|
|
5917
|
+
const raw = yield* fs.readFile(filePath).pipe(Effect.tapError((error) => Effect.logWarning(`could not read skill file ${filePath}`, error.cause)), Effect.option);
|
|
5918
|
+
if (Option.isNone(raw)) return void 0;
|
|
5919
|
+
const { frontmatter, error } = parseFrontmatter(raw.value);
|
|
5920
|
+
if (error !== void 0) {
|
|
5921
|
+
if (isDeclaredSkill) yield* Effect.logWarning(`could not parse skill file ${filePath}`, error);
|
|
5922
|
+
return;
|
|
5923
|
+
}
|
|
5924
|
+
const description = frontmatter.description;
|
|
5925
|
+
const hasDescription = typeof description === "string" && description.trim() !== "";
|
|
5926
|
+
if (!isDeclaredSkill && !hasDescription) return void 0;
|
|
5927
|
+
const skillDir = posix.dirname(filePath);
|
|
5928
|
+
const parentDirName = posix.basename(skillDir);
|
|
5929
|
+
for (const error of validateDescription(description)) yield* Effect.logWarning(`skill ${filePath}: ${error}`);
|
|
5930
|
+
const name = (typeof frontmatter.name === "string" ? frontmatter.name : void 0) || parentDirName;
|
|
5931
|
+
for (const error of validateName(name)) yield* Effect.logWarning(`skill ${filePath}: ${error}`);
|
|
5932
|
+
if (!hasDescription) return void 0;
|
|
5933
|
+
return {
|
|
5934
|
+
name,
|
|
5935
|
+
description,
|
|
5936
|
+
filePath,
|
|
5937
|
+
baseDir: skillDir,
|
|
5938
|
+
disableModelInvocation: frontmatter["disable-model-invocation"] === true
|
|
5939
|
+
};
|
|
5940
|
+
});
|
|
5941
|
+
const scanDir = (fs, dir, mode, root, ig, visited) => Effect.gen(function* () {
|
|
5942
|
+
const realDir = yield* realpath(fs, dir);
|
|
5943
|
+
if (visited.has(realDir)) return [];
|
|
5944
|
+
const nextVisited = /* @__PURE__ */ new Set([...visited, realDir]);
|
|
5945
|
+
const listed = yield* fs.readdir(dir).pipe(Effect.tapError((error) => filesystem_exports.SandboxFileSystem.isNotFoundError(error.cause) ? Effect.void : Effect.logWarning(`could not read skill directory ${dir}`, error.cause)), Effect.option);
|
|
5946
|
+
if (Option.isNone(listed)) return [];
|
|
5947
|
+
const entries = [...listed.value].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
5948
|
+
const entrySet = new Set(entries);
|
|
5949
|
+
yield* addIgnoreRules(fs, ig, dir, root, entrySet);
|
|
5950
|
+
if (entrySet.has("SKILL.md")) {
|
|
5951
|
+
const fullPath = posix.join(dir, "SKILL.md");
|
|
5952
|
+
const relPath = posix.relative(root, fullPath);
|
|
5953
|
+
if ((yield* isFile(fs, fullPath)) && !ig.ignores(relPath)) {
|
|
5954
|
+
const skill = yield* loadSkillFromFile(fs, fullPath);
|
|
5955
|
+
return skill === void 0 ? [] : [skill];
|
|
5956
|
+
}
|
|
5957
|
+
}
|
|
5958
|
+
const visible = entries.filter((name) => !name.startsWith(".") && name !== "node_modules");
|
|
5959
|
+
const statted = yield* Effect.forEach(visible, (name) => {
|
|
5960
|
+
const fullPath = posix.join(dir, name);
|
|
5961
|
+
return fs.stat(fullPath).pipe(Effect.map((stat) => ({
|
|
5962
|
+
name,
|
|
5963
|
+
fullPath,
|
|
5964
|
+
stat
|
|
5965
|
+
})), Effect.option);
|
|
5966
|
+
}, { concurrency: "unbounded" });
|
|
5967
|
+
const skills = [];
|
|
5968
|
+
for (const item of statted) {
|
|
5969
|
+
if (Option.isNone(item)) continue;
|
|
5970
|
+
const { name, fullPath, stat } = item.value;
|
|
5971
|
+
const relPath = posix.relative(root, fullPath);
|
|
5972
|
+
if (ig.ignores(stat.isDirectory ? `${relPath}/` : relPath)) continue;
|
|
5973
|
+
if (stat.isDirectory) {
|
|
5974
|
+
const nested = yield* scanDir(fs, fullPath, mode, root, ig, nextVisited);
|
|
5975
|
+
skills.push(...nested);
|
|
5976
|
+
continue;
|
|
5977
|
+
}
|
|
5978
|
+
if (stat.isFile && name.endsWith(".md") && (mode === "codework" && dir === root || mode === "agents" && dir !== root)) {
|
|
5979
|
+
const skill = yield* loadSkillFromFile(fs, fullPath);
|
|
5980
|
+
if (skill !== void 0) skills.push(skill);
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
return skills;
|
|
5984
|
+
});
|
|
5985
|
+
/**
|
|
5986
|
+
* Load skills from a single directory in the sandbox.
|
|
5987
|
+
*
|
|
5988
|
+
* Discovery rules:
|
|
5989
|
+
* - If a directory contains `SKILL.md`, treat it as a skill root and do not recurse further.
|
|
5990
|
+
* - In `"codework"` mode, direct `.md` children in the root are also loaded; in `"agents"` mode,
|
|
5991
|
+
* `.md` files in subdirectories are loaded while root `.md` files are ignored.
|
|
5992
|
+
* - Recurse into subdirectories (skipping dotfiles, `node_modules`, and ignored paths).
|
|
5993
|
+
*/
|
|
5994
|
+
const loadSkillsFromDir = (fs, dir, mode) => scanDir(fs, dir, mode, dir, ignore(), /* @__PURE__ */ new Set());
|
|
5995
|
+
/**
|
|
5996
|
+
* Discover skills inside the sandbox: `.codework/skills` at the cwd, then
|
|
5997
|
+
* `.agents/skills` from the cwd up to the git root (the closest ancestor holding `.git`), or up to
|
|
5998
|
+
* the sandbox root outside a repository. Earlier skills win name collisions.
|
|
5999
|
+
*/
|
|
6000
|
+
const discover = Effect.fnUntraced(function* (fs, location) {
|
|
6001
|
+
const chain = ancestors(location.directory);
|
|
6002
|
+
const own = posix.join(location.directory, appConfigDir, "skills");
|
|
6003
|
+
const agents = chain.map((directory) => posix.join(directory, ".agents", "skills"));
|
|
6004
|
+
const [dirs, gits] = yield* Effect.all([present$1(fs, [own, ...agents], "directory", "skill directory"), present$1(fs, chain.map((directory) => posix.join(directory, ".git")), "entry", "git marker")], { concurrency: "unbounded" });
|
|
6005
|
+
const root = chain.findIndex((directory) => gits.has(posix.join(directory, ".git")));
|
|
6006
|
+
const walked = root === -1 ? agents : agents.slice(0, root + 1);
|
|
6007
|
+
const sources = [{
|
|
6008
|
+
dir: own,
|
|
6009
|
+
mode: "codework"
|
|
6010
|
+
}, ...walked.map((dir) => ({
|
|
6011
|
+
dir,
|
|
6012
|
+
mode: "agents"
|
|
6013
|
+
}))].filter(({ dir }) => dirs.has(dir));
|
|
6014
|
+
const found = yield* Effect.forEach(sources, ({ dir, mode }) => loadSkillsFromDir(fs, dir, mode), { concurrency: "unbounded" });
|
|
6015
|
+
const skillMap = /* @__PURE__ */ new Map();
|
|
6016
|
+
const realPathSet = /* @__PURE__ */ new Set();
|
|
6017
|
+
for (const skill of found.flat()) {
|
|
6018
|
+
const realPath = yield* realpath(fs, skill.filePath);
|
|
6019
|
+
if (realPathSet.has(realPath)) continue;
|
|
6020
|
+
const existing = skillMap.get(skill.name);
|
|
6021
|
+
if (existing !== void 0) yield* Effect.logWarning(`skill ${skill.filePath}: name "${skill.name}" collision with ${existing.filePath}`);
|
|
6022
|
+
else {
|
|
6023
|
+
skillMap.set(skill.name, skill);
|
|
6024
|
+
realPathSet.add(realPath);
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
return Array.from(skillMap.values());
|
|
6028
|
+
});
|
|
6029
|
+
const escapeXml = (str) => str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
6030
|
+
/**
|
|
6031
|
+
* Format skills for inclusion in `Section.Skills` per the Agent Skills specification.
|
|
6032
|
+
* Skills with `disableModelInvocation: true` are excluded from the prompt.
|
|
6033
|
+
*/
|
|
6034
|
+
const formatSkillsForPrompt = (skills, fileReadTool = "read") => {
|
|
6035
|
+
const visibleSkills = skills.filter((s) => !s.disableModelInvocation);
|
|
6036
|
+
if (visibleSkills.length === 0) return "";
|
|
6037
|
+
const lines = [
|
|
6038
|
+
"The following skills provide specialized instructions for specific tasks.",
|
|
6039
|
+
fileReadTool === "read" ? "Use the read tool to load a skill's file when the task matches its description." : fileReadTool === "bash" ? "Use bash to load a skill's file when the task matches its description." : "Load a skill's file when the task matches its description.",
|
|
6040
|
+
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
|
|
6041
|
+
"",
|
|
6042
|
+
"<available_skills>"
|
|
6043
|
+
];
|
|
6044
|
+
for (const skill of visibleSkills) {
|
|
6045
|
+
lines.push(" <skill>");
|
|
6046
|
+
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
6047
|
+
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
6048
|
+
lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
|
|
6049
|
+
lines.push(" </skill>");
|
|
6050
|
+
}
|
|
6051
|
+
lines.push("</available_skills>");
|
|
6052
|
+
return lines.join("\n");
|
|
6053
|
+
};
|
|
6054
|
+
const readers = ["read", "bash"];
|
|
6055
|
+
const skillPlugin = (0, plugin_exports$1.define)({
|
|
6056
|
+
id: "codework.prompt.skill",
|
|
6057
|
+
kind: "prompt",
|
|
6058
|
+
setup: Effect.fn("SkillPlugin.setup")(function* (ctx) {
|
|
6059
|
+
const tools = ctx.plugin.tools;
|
|
6060
|
+
const visible = (name) => {
|
|
6061
|
+
const tool = tools.get(name);
|
|
6062
|
+
return tool !== void 0 && tool.promptSnippet !== void 0 && tool.promptSnippet.length > 0;
|
|
6063
|
+
};
|
|
6064
|
+
const fileReadTool = readers.find(visible) ?? (readers.some((name) => tools.has(name)) ? "indirect" : void 0);
|
|
6065
|
+
if (fileReadTool === void 0) return;
|
|
6066
|
+
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
6067
|
+
const skills = yield* discover(fs, ctx.location);
|
|
6068
|
+
const formatted = formatSkillsForPrompt(skills, fileReadTool);
|
|
6069
|
+
if (formatted.length === 0) return;
|
|
6070
|
+
ctx.plugin.prompt.sections.append(Section.Skills, formatted);
|
|
6071
|
+
})
|
|
6072
|
+
});
|
|
6073
|
+
//#endregion
|
|
5707
6074
|
//#region src/tool/truncate.ts
|
|
5708
6075
|
/**
|
|
5709
6076
|
* Shared truncation utilities for tool outputs.
|
|
@@ -6002,7 +6369,7 @@ var Accumulator = class {
|
|
|
6002
6369
|
//#endregion
|
|
6003
6370
|
//#region src/tool/output.ts
|
|
6004
6371
|
/** A new, unused output file path under `spillPath`. */
|
|
6005
|
-
const path
|
|
6372
|
+
const path = (spillPath, prefix) => crypto.randomBytes(8).pipe(Effect.map((bytes) => posix.join(spillPath, `${prefix}-${Hex.encode(bytes)}.log`)), Effect.orDie);
|
|
6006
6373
|
/** First line printed when the output file cannot be created; the command then runs unsaved. */
|
|
6007
6374
|
const unsaved = (file) => `codework: output not saved, cannot create ${file}`;
|
|
6008
6375
|
const HEADER = "codework-output";
|
|
@@ -6034,7 +6401,7 @@ const script = (command, file, limits, pipeline, report) => [
|
|
|
6034
6401
|
"run 2>&1"
|
|
6035
6402
|
].join("\n");
|
|
6036
6403
|
const header = new RegExp(`^${HEADER} (\\d+) (\\d+) ([01])(?: (\\d+))?$`, "m");
|
|
6037
|
-
const parse$
|
|
6404
|
+
const parse$4 = (match) => ({
|
|
6038
6405
|
bytes: Number(match[1]),
|
|
6039
6406
|
newlines: Number(match[2]),
|
|
6040
6407
|
kept: match[3] === "1"
|
|
@@ -6053,7 +6420,7 @@ const report = (stderr) => {
|
|
|
6053
6420
|
rest: stderr
|
|
6054
6421
|
};
|
|
6055
6422
|
return {
|
|
6056
|
-
report: parse$
|
|
6423
|
+
report: parse$4(match),
|
|
6057
6424
|
rest: stderr.slice(0, match.index) + stderr.slice(match.index + match[0].length).replace(/^\n/, "")
|
|
6058
6425
|
};
|
|
6059
6426
|
};
|
|
@@ -6076,7 +6443,7 @@ const window = (stdout) => {
|
|
|
6076
6443
|
const match = header.exec(newline === -1 ? stdout : stdout.slice(0, newline));
|
|
6077
6444
|
if (match === null || match[4] === void 0) return void 0;
|
|
6078
6445
|
return {
|
|
6079
|
-
...parse$
|
|
6446
|
+
...parse$4(match),
|
|
6080
6447
|
text: stdout.slice(newline + 1),
|
|
6081
6448
|
textBytes: Number(match[4])
|
|
6082
6449
|
};
|
|
@@ -6390,7 +6757,7 @@ const runStreaming = (shell, stream, params, file) => Effect.gen(function* () {
|
|
|
6390
6757
|
});
|
|
6391
6758
|
const bashHandler = (params) => Effect.gen(function* () {
|
|
6392
6759
|
const shell = yield* ToolShell;
|
|
6393
|
-
const file = yield* path
|
|
6760
|
+
const file = yield* path((yield* io_exports.SandboxIO.Current).spillPath, "codework-bash");
|
|
6394
6761
|
return yield* (shell.stream !== void 0 ? runStreaming(shell, shell.stream, params, file) : runBuffered(shell, params, file)).pipe(Effect.onInterrupt(() => cleanUp(shell, file, {
|
|
6395
6762
|
output: "",
|
|
6396
6763
|
truncated: false
|
|
@@ -7151,7 +7518,7 @@ const pipeline = (argv, limit, errors) => [
|
|
|
7151
7518
|
].join("; ");
|
|
7152
7519
|
const STATUS = /^\{"type":"rc","code":(\d+)\}$/;
|
|
7153
7520
|
/** The pipeline's output: match events, ripgrep's status, and anything else, which is its stderr. */
|
|
7154
|
-
const parse$
|
|
7521
|
+
const parse$3 = (stdout, stderr) => {
|
|
7155
7522
|
const matches = [];
|
|
7156
7523
|
const errors = [];
|
|
7157
7524
|
let status;
|
|
@@ -7220,7 +7587,7 @@ const grepHandler = (params) => Effect.gen(function* () {
|
|
|
7220
7587
|
const isDirectory = stat.value.isDirectory;
|
|
7221
7588
|
const context = params.context && params.context > 0 ? Math.trunc(params.context) : 0;
|
|
7222
7589
|
const limit = Math.max(1, Math.trunc(params.limit ?? DEFAULT_LIMIT$1));
|
|
7223
|
-
const errors = yield* path
|
|
7590
|
+
const errors = yield* path(sandbox.spillPath, "codework-grep");
|
|
7224
7591
|
const run = yield* withCommand("rg", sandbox, shell, fs, (command) => Effect.gen(function* () {
|
|
7225
7592
|
const argv = [
|
|
7226
7593
|
command,
|
|
@@ -7235,7 +7602,7 @@ const grepHandler = (params) => Effect.gen(function* () {
|
|
|
7235
7602
|
if (params.glob) argv.push("--glob", params.glob);
|
|
7236
7603
|
argv.push("-e", params.pattern, searchPath);
|
|
7237
7604
|
const result = yield* shell.exec(pipeline(argv, limit, errors));
|
|
7238
|
-
const parsed = parse$
|
|
7605
|
+
const parsed = parse$3(result.stdout, result.stderr);
|
|
7239
7606
|
return {
|
|
7240
7607
|
...parsed,
|
|
7241
7608
|
exitCode: parsed.status ?? result.exitCode
|
|
@@ -7495,7 +7862,7 @@ const INLINE = /* @__PURE__ */ new Set([
|
|
|
7495
7862
|
"image/gif",
|
|
7496
7863
|
"image/webp"
|
|
7497
7864
|
]);
|
|
7498
|
-
const toPng = (bytes) => load$
|
|
7865
|
+
const toPng = (bytes) => load$5.pipe(Effect.map((photon) => {
|
|
7499
7866
|
if (photon === void 0) return void 0;
|
|
7500
7867
|
try {
|
|
7501
7868
|
const image = decode(photon, bytes);
|
|
@@ -7800,127 +8167,10 @@ const builtins = Object.freeze([
|
|
|
7800
8167
|
findPlugin,
|
|
7801
8168
|
lsPlugin,
|
|
7802
8169
|
defaultPromptPlugin,
|
|
7803
|
-
instructionPlugin
|
|
8170
|
+
instructionPlugin,
|
|
8171
|
+
skillPlugin
|
|
7804
8172
|
]);
|
|
7805
8173
|
//#endregion
|
|
7806
|
-
//#region src/util/module.ts
|
|
7807
|
-
/*!
|
|
7808
|
-
* Adapted from OpenCode packages/util/src/runtime/import.node.ts.
|
|
7809
|
-
*
|
|
7810
|
-
* MIT License
|
|
7811
|
-
*
|
|
7812
|
-
* Copyright (c) 2025 opencode
|
|
7813
|
-
*
|
|
7814
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7815
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
7816
|
-
* in the Software without restriction, including without limitation the rights
|
|
7817
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7818
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
7819
|
-
* furnished to do so, subject to the following conditions:
|
|
7820
|
-
*
|
|
7821
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
7822
|
-
* copies or substantial portions of the Software.
|
|
7823
|
-
*
|
|
7824
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
7825
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
7826
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
7827
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
7828
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
7829
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
7830
|
-
* SOFTWARE.
|
|
7831
|
-
*/
|
|
7832
|
-
/**
|
|
7833
|
-
* Packages a plugin must share with the harness instead of using the copy installed beside it.
|
|
7834
|
-
*
|
|
7835
|
-
* A plugin is installed into the store as its own npm tree, so `effect` and the plugin SDK land
|
|
7836
|
-
* there a second time. Two copies of the *same version* are still two module instances, and
|
|
7837
|
-
* Effect Schema's checks do not survive the crossing: a schema built by one copy and evaluated by
|
|
7838
|
-
* the other reports every check as failed, so a tool whose success type is `Schema.Finite` dies
|
|
7839
|
-
* on "Expected a finite number" for `22800`. Nothing about that is visible at registration --
|
|
7840
|
-
* `Schema.isSchema` passes, the JSON Schema is derived correctly, and the tool reaches the model
|
|
7841
|
-
* -- so it surfaces on the first call rather than at boot.
|
|
7842
|
-
*
|
|
7843
|
-
* Resolution is the right place to fix it. A symlink into the store cannot be: a generation is
|
|
7844
|
-
* immutable and shared by every project on the machine, while the harness doing the loading is
|
|
7845
|
-
* whichever one is running now, and two applications embedding the harness need not agree on
|
|
7846
|
-
* where their `effect` lives.
|
|
7847
|
-
*/
|
|
7848
|
-
const sharedPackages = /* @__PURE__ */ new Set(["effect", "@codeworksh/plugin"]);
|
|
7849
|
-
/** The package a bare specifier names: `effect/Schema` -> `effect`, `@a/b/c` -> `@a/b`. */
|
|
7850
|
-
function packageOf(specifier) {
|
|
7851
|
-
if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.includes(":")) return "";
|
|
7852
|
-
const segments = specifier.split("/");
|
|
7853
|
-
return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
|
|
7854
|
-
}
|
|
7855
|
-
/**
|
|
7856
|
-
* Resolve the shared packages as though this module had imported them, for as long as `run`
|
|
7857
|
-
* takes. Anything else resolves exactly as it would have.
|
|
7858
|
-
*/
|
|
7859
|
-
function withSharedModules(run) {
|
|
7860
|
-
const hook = registerHooks({ resolve(specifier, context, nextResolve) {
|
|
7861
|
-
return sharedPackages.has(packageOf(specifier)) ? nextResolve(specifier, {
|
|
7862
|
-
...context,
|
|
7863
|
-
parentURL: import.meta.url
|
|
7864
|
-
}) : nextResolve(specifier, context);
|
|
7865
|
-
} });
|
|
7866
|
-
return run().finally(() => hook.deregister());
|
|
7867
|
-
}
|
|
7868
|
-
/** Use Node's main loader even when the caller runs inside a bundler or test VM. */
|
|
7869
|
-
async function importModule(specifier) {
|
|
7870
|
-
const imported = await withSharedModules(() => new Script(`import(${JSON.stringify(specifier)})`, { importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER }).runInThisContext());
|
|
7871
|
-
if (typeof imported !== "object" || imported === null) return imported;
|
|
7872
|
-
const module = imported;
|
|
7873
|
-
const exports = module["module.exports"];
|
|
7874
|
-
if (exports !== module.default || typeof exports !== "object" && typeof exports !== "function" || exports === null) return imported;
|
|
7875
|
-
return Object.assign({}, module, exports);
|
|
7876
|
-
}
|
|
7877
|
-
/** Node resolution scoped synchronously to the caller. */
|
|
7878
|
-
function resolveModule(specifier, directory, conditions) {
|
|
7879
|
-
const hook = registerHooks({ resolve(target, context, nextResolve) {
|
|
7880
|
-
return nextResolve(target, {
|
|
7881
|
-
...context,
|
|
7882
|
-
parentURL: pathToFileURL(path.join(directory, "package.json")).href,
|
|
7883
|
-
...conditions === void 0 ? {} : { conditions: [.../* @__PURE__ */ new Set([...context.conditions, ...conditions])] }
|
|
7884
|
-
});
|
|
7885
|
-
} });
|
|
7886
|
-
try {
|
|
7887
|
-
const resolve = (target) => {
|
|
7888
|
-
const resolved = import.meta.resolve(path.isAbsolute(target) ? pathToFileURL(target).href : target);
|
|
7889
|
-
if (resolved.startsWith("file:")) statSync(new URL(resolved));
|
|
7890
|
-
return resolved;
|
|
7891
|
-
};
|
|
7892
|
-
try {
|
|
7893
|
-
return resolve(specifier);
|
|
7894
|
-
} catch (error) {
|
|
7895
|
-
if (path.extname(specifier) || !missing(error)) throw error;
|
|
7896
|
-
for (const extension of [
|
|
7897
|
-
".ts",
|
|
7898
|
-
".tsx",
|
|
7899
|
-
".js",
|
|
7900
|
-
".jsx",
|
|
7901
|
-
".mts",
|
|
7902
|
-
".mjs",
|
|
7903
|
-
".cts",
|
|
7904
|
-
".cjs"
|
|
7905
|
-
]) try {
|
|
7906
|
-
return resolve(specifier + extension);
|
|
7907
|
-
} catch (cause) {
|
|
7908
|
-
if (!missing(cause)) throw cause;
|
|
7909
|
-
}
|
|
7910
|
-
throw error;
|
|
7911
|
-
}
|
|
7912
|
-
} finally {
|
|
7913
|
-
hook.deregister();
|
|
7914
|
-
}
|
|
7915
|
-
}
|
|
7916
|
-
function missing(error) {
|
|
7917
|
-
return error instanceof Error && "code" in error && [
|
|
7918
|
-
"ENOENT",
|
|
7919
|
-
"ENOTDIR",
|
|
7920
|
-
"ERR_MODULE_NOT_FOUND"
|
|
7921
|
-
].includes(String(error.code));
|
|
7922
|
-
}
|
|
7923
|
-
//#endregion
|
|
7924
8174
|
//#region src/plugin/error.ts
|
|
7925
8175
|
/**
|
|
7926
8176
|
* The reference as the person wrote it -- the string they will recognise in their settings file,
|
|
@@ -8358,7 +8608,7 @@ const isPath = (reference) => reference.startsWith("file:") || reference.startsW
|
|
|
8358
8608
|
* - a **CLI argument** anchors to the process's startup directory, because that is where the
|
|
8359
8609
|
* person typed it.
|
|
8360
8610
|
*/
|
|
8361
|
-
const parse$
|
|
8611
|
+
const parse$2 = (reference, from) => Effect.gen(function* () {
|
|
8362
8612
|
const fail = (reason, message) => new SourceError({
|
|
8363
8613
|
reason,
|
|
8364
8614
|
reference,
|
|
@@ -9034,7 +9284,7 @@ const inspect = Effect.fn("PluginLoader.inspect")(function* (reference, options)
|
|
|
9034
9284
|
reference,
|
|
9035
9285
|
file: options.file
|
|
9036
9286
|
};
|
|
9037
|
-
const target = yield* parse$
|
|
9287
|
+
const target = yield* parse$2(reference, options.hostDir);
|
|
9038
9288
|
const loaded = yield* load$2(target, origin, options);
|
|
9039
9289
|
return {
|
|
9040
9290
|
id: loaded.plugin.id,
|
|
@@ -9060,7 +9310,7 @@ const load$2 = Effect.fn("PluginLoader.load")(function* (target, origin, options
|
|
|
9060
9310
|
* are one package, and `./plugins/x.ts` is the file it resolves to from the directory that
|
|
9061
9311
|
* declared it. Pure -- nothing is installed, imported or read.
|
|
9062
9312
|
*/
|
|
9063
|
-
const canonical = (reference, hostDir) => Effect.map(parse$
|
|
9313
|
+
const canonical = (reference, hostDir) => Effect.map(parse$2(reference, hostDir), canonical$1);
|
|
9064
9314
|
//#endregion
|
|
9065
9315
|
//#region src/plugin/catalog.ts
|
|
9066
9316
|
/**
|
|
@@ -9122,7 +9372,7 @@ const load$1 = Effect.fn("PluginCatalog.load")(function* (references, options) {
|
|
|
9122
9372
|
remember(plugin, []);
|
|
9123
9373
|
continue;
|
|
9124
9374
|
}
|
|
9125
|
-
const target = yield* parse$
|
|
9375
|
+
const target = yield* parse$2(reference, options.hostDir);
|
|
9126
9376
|
const key = target.kind === "local" ? target.path : target.spec;
|
|
9127
9377
|
const already = seen.get(key);
|
|
9128
9378
|
if (already !== void 0) {
|
|
@@ -9800,31 +10050,116 @@ const openWith = (options = {}) => Effect.fn("LLM.open")(function* (input, signa
|
|
|
9800
10050
|
});
|
|
9801
10051
|
});
|
|
9802
10052
|
const open = openWith();
|
|
10053
|
+
const isProviderError = Schema.is(ProviderError);
|
|
10054
|
+
/** `error.type` for a request: the provider's failure reason, so a terminal and a thrown failure agree. */
|
|
10055
|
+
const requestErrorType = (input, exit) => {
|
|
10056
|
+
if (Exit.isFailure(exit)) {
|
|
10057
|
+
const error = Cause.squash(exit.cause);
|
|
10058
|
+
return isProviderError(error) ? error.reason._tag : errorType(exit.cause);
|
|
10059
|
+
}
|
|
10060
|
+
if (exit.value.outcome === "ended") return void 0;
|
|
10061
|
+
if (exit.value.reason === "aborted") return "aborted";
|
|
10062
|
+
return providerError(input, messageFailure(exit.value.message)).reason._tag;
|
|
10063
|
+
};
|
|
10064
|
+
const observe = Effect.fnUntraced(function* (input, timing, exit, drained) {
|
|
10065
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
10066
|
+
const labels = {
|
|
10067
|
+
provider: input.provider,
|
|
10068
|
+
model: input.model
|
|
10069
|
+
};
|
|
10070
|
+
const errorType = requestErrorType(input, exit);
|
|
10071
|
+
yield* record$1(llmDuration, (now - timing.start) / 1e3, errorType === void 0 ? labels : {
|
|
10072
|
+
...labels,
|
|
10073
|
+
error: errorType
|
|
10074
|
+
});
|
|
10075
|
+
if (timing.firstToken !== void 0) {
|
|
10076
|
+
const ttft = (timing.firstToken - timing.start) / 1e3;
|
|
10077
|
+
yield* record$1(llmTtft, ttft, labels);
|
|
10078
|
+
yield* Effect.annotateCurrentSpan("gen_ai.response.time_to_first_chunk", ttft);
|
|
10079
|
+
}
|
|
10080
|
+
if (errorType !== void 0) yield* Effect.annotateCurrentSpan("error.type", errorType);
|
|
10081
|
+
const terminal = Exit.isSuccess(exit) ? exit.value : drained;
|
|
10082
|
+
if (terminal === void 0) return;
|
|
10083
|
+
const { message } = terminal;
|
|
10084
|
+
const { usage } = message;
|
|
10085
|
+
const inputTokens = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
10086
|
+
const tokens = {
|
|
10087
|
+
input: inputTokens,
|
|
10088
|
+
output: usage.output,
|
|
10089
|
+
cache_read: usage.cacheRead,
|
|
10090
|
+
cache_write: usage.cacheWrite,
|
|
10091
|
+
reasoning: usage.reasoning ?? 0
|
|
10092
|
+
};
|
|
10093
|
+
for (const [type, count] of Object.entries(tokens)) if (count > 0) yield* record$1(llmTokens, count, {
|
|
10094
|
+
...labels,
|
|
10095
|
+
type
|
|
10096
|
+
});
|
|
10097
|
+
if (usage.cost.total > 0) yield* record$1(llmCost, usage.cost.total, labels);
|
|
10098
|
+
yield* Effect.annotateCurrentSpan({
|
|
10099
|
+
"gen_ai.response.model": message.responseModel ?? message.model,
|
|
10100
|
+
...message.responseId === void 0 ? {} : { "gen_ai.response.id": message.responseId },
|
|
10101
|
+
"gen_ai.response.finish_reasons": [terminal.reason],
|
|
10102
|
+
"gen_ai.usage.input_tokens": inputTokens,
|
|
10103
|
+
"gen_ai.usage.output_tokens": usage.output,
|
|
10104
|
+
"gen_ai.usage.cache_read.input_tokens": usage.cacheRead,
|
|
10105
|
+
"gen_ai.usage.cache_write.input_tokens": usage.cacheWrite,
|
|
10106
|
+
...usage.reasoning === void 0 ? {} : { "gen_ai.usage.reasoning.output_tokens": usage.reasoning }
|
|
10107
|
+
});
|
|
10108
|
+
});
|
|
10109
|
+
const isToken = (event) => event.type === "text.delta" || event.type === "thinking.delta" || event.type === "toolcall.delta";
|
|
10110
|
+
/**
|
|
10111
|
+
* A failed terminal is the request's result, not an error. It leaves the
|
|
10112
|
+
* traced request as a failure so the span records an error status, and is
|
|
10113
|
+
* recovered just outside it.
|
|
10114
|
+
*/
|
|
10115
|
+
var FailedTerminal = class extends Data.TaggedError("LLM.FailedTerminal") {};
|
|
9803
10116
|
/**
|
|
9804
10117
|
* Build a provider request from an opener. Tests use this with deterministic
|
|
9805
10118
|
* aikit event streams; production uses `open` above.
|
|
9806
10119
|
*/
|
|
9807
10120
|
const make$5 = (openStream) => {
|
|
9808
|
-
const request = Effect.fn("LLM.run"
|
|
10121
|
+
const request = Effect.fn("LLM.run", {
|
|
10122
|
+
kind: "client",
|
|
10123
|
+
level: "Info"
|
|
10124
|
+
})(function* (input) {
|
|
10125
|
+
yield* Effect.annotateCurrentSpan({
|
|
10126
|
+
"gen_ai.operation.name": "chat",
|
|
10127
|
+
"gen_ai.provider.name": input.provider,
|
|
10128
|
+
"gen_ai.request.model": input.model,
|
|
10129
|
+
"gen_ai.conversation.id": input.sessionId,
|
|
10130
|
+
"gen_ai.request.stream": true
|
|
10131
|
+
});
|
|
10132
|
+
const start = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
10133
|
+
let firstToken;
|
|
10134
|
+
let drained;
|
|
10135
|
+
const markToken = (event) => firstToken !== void 0 || !isToken(event) ? Effect.void : Effect.clockWith((clock) => Effect.sync(() => {
|
|
10136
|
+
firstToken = clock.currentTimeMillisUnsafe();
|
|
10137
|
+
}));
|
|
9809
10138
|
const signalScope = yield* Scope.make();
|
|
9810
10139
|
yield* Effect.addFinalizer((exit) => Scope.close(signalScope, exit));
|
|
9811
10140
|
const signal = yield* Effect.abortSignal.pipe(Scope.provide(signalScope));
|
|
9812
10141
|
const abort = Scope.close(signalScope, Exit.void);
|
|
9813
|
-
|
|
10142
|
+
const terminal = yield* Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
9814
10143
|
const iterable = yield* restore(openStream(input, signal)).pipe(Effect.onInterrupt(() => abort));
|
|
9815
|
-
const consumer = yield* Stream.fromAsyncIterable(iterable, (cause) => providerErrorFromUnknown(input, cause)).pipe(Stream.runForEach(input.publisher.publish), Effect.andThen(input.publisher.terminal), Effect.interruptible).pipe(Effect.forkScoped({ startImmediately: true }));
|
|
10144
|
+
const consumer = yield* Stream.fromAsyncIterable(iterable, (cause) => providerErrorFromUnknown(input, cause)).pipe(Stream.runForEach((event) => Effect.andThen(markToken(event), input.publisher.publish(event))), Effect.andThen(input.publisher.terminal), Effect.interruptible).pipe(Effect.forkScoped({ startImmediately: true }));
|
|
9816
10145
|
const awaited = yield* restore(Fiber.await(consumer)).pipe(Effect.onInterrupt(() => abort), Effect.exit);
|
|
9817
10146
|
if (Exit.isFailure(awaited)) {
|
|
9818
|
-
const
|
|
9819
|
-
if (Exit.isFailure(
|
|
10147
|
+
const consumed = yield* Fiber.await(consumer);
|
|
10148
|
+
if (Exit.isFailure(consumed)) return yield* Effect.failCause(consumed.cause);
|
|
10149
|
+
drained = consumed.value;
|
|
9820
10150
|
return yield* Effect.failCause(awaited.cause);
|
|
9821
10151
|
}
|
|
9822
10152
|
const completed = awaited.value;
|
|
9823
10153
|
if (Exit.isFailure(completed)) return yield* Effect.failCause(completed.cause);
|
|
9824
10154
|
return completed.value;
|
|
9825
|
-
}))
|
|
9826
|
-
|
|
9827
|
-
|
|
10155
|
+
})).pipe(Effect.onExit((exit) => observe(input, {
|
|
10156
|
+
start,
|
|
10157
|
+
firstToken
|
|
10158
|
+
}, exit, drained)));
|
|
10159
|
+
if (terminal.outcome === "failed") return yield* new FailedTerminal({ terminal });
|
|
10160
|
+
return terminal;
|
|
10161
|
+
});
|
|
10162
|
+
return (input) => request(input).pipe(Effect.catchTag("LLM.FailedTerminal", (failed) => Effect.succeed(failed.terminal)), Effect.scoped);
|
|
9828
10163
|
};
|
|
9829
10164
|
const run$1 = make$5(open);
|
|
9830
10165
|
//#endregion
|
|
@@ -10735,6 +11070,17 @@ const errorPart = (call, cause, now) => ({
|
|
|
10735
11070
|
end: endTime(call, now)
|
|
10736
11071
|
}
|
|
10737
11072
|
});
|
|
11073
|
+
const observeTool = (snapshot, call, seconds, exit) => {
|
|
11074
|
+
const tool = snapshot.tools.defs.some((def) => def.name === call.name) ? call.name : "_OTHER";
|
|
11075
|
+
const labels = Exit.isFailure(exit) ? {
|
|
11076
|
+
tool,
|
|
11077
|
+
error: errorType(exit.cause)
|
|
11078
|
+
} : exit.value.status === "completed" ? { tool } : {
|
|
11079
|
+
tool,
|
|
11080
|
+
error: exit.value.status
|
|
11081
|
+
};
|
|
11082
|
+
return record$1(toolDuration, seconds, labels);
|
|
11083
|
+
};
|
|
10738
11084
|
const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* () {
|
|
10739
11085
|
const context = yield* Service$13;
|
|
10740
11086
|
const events = yield* Service$16;
|
|
@@ -10773,7 +11119,7 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
10773
11119
|
if (draft === void 0) return;
|
|
10774
11120
|
yield* publishFailure(draft, { _tag: "interrupted" });
|
|
10775
11121
|
});
|
|
10776
|
-
const settleTools = Effect.fn("Loop.settleTools")(function* (snapshot, message, reason, markCommitted) {
|
|
11122
|
+
const settleTools = Effect.fn("Loop.settleTools", { level: "Info" })(function* (snapshot, message, reason, markCommitted) {
|
|
10777
11123
|
const pending = message.parts.filter((part) => part.type === "toolCall" && part.status === "pending");
|
|
10778
11124
|
let interruptedCause;
|
|
10779
11125
|
const messageId = ID.from(message.messageId);
|
|
@@ -10812,7 +11158,12 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
10812
11158
|
return;
|
|
10813
11159
|
}
|
|
10814
11160
|
const settled = /* @__PURE__ */ new Set();
|
|
10815
|
-
const handle = Effect.fn("Loop.handleTool")(function* (call) {
|
|
11161
|
+
const handle = Effect.fn("Loop.handleTool", { level: "Info" })(function* (call) {
|
|
11162
|
+
yield* Effect.annotateCurrentSpan({
|
|
11163
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11164
|
+
"gen_ai.tool.name": call.name,
|
|
11165
|
+
"gen_ai.tool.call.id": call.callID
|
|
11166
|
+
});
|
|
10816
11167
|
yield* events.publish(ToolStarted, {
|
|
10817
11168
|
timestamp: yield* DateTime.now,
|
|
10818
11169
|
sessionId: snapshot.sessionId,
|
|
@@ -10822,7 +11173,7 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
10822
11173
|
label: snapshot.tools.defs.find((def) => def.name === call.name)?.label,
|
|
10823
11174
|
arguments: call.arguments
|
|
10824
11175
|
});
|
|
10825
|
-
const
|
|
11176
|
+
const execute = snapshot.tools.handle(call, {
|
|
10826
11177
|
sessionId: snapshot.sessionId,
|
|
10827
11178
|
messageId,
|
|
10828
11179
|
onProgress: (progress) => events.publish(ToolProgress$2, {
|
|
@@ -10833,6 +11184,7 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
10833
11184
|
partial: progress.partial
|
|
10834
11185
|
}).pipe(Effect.asVoid)
|
|
10835
11186
|
}).pipe(Effect.catchCauseIf((cause) => !Cause.hasInterrupts(cause), (cause) => Effect.map(Effect.clockWith((clock) => clock.currentTimeMillis), (now) => errorPart(call, cause, now))));
|
|
11187
|
+
const handled = yield* timed(execute, (seconds, exit) => observeTool(snapshot, call, seconds, exit));
|
|
10836
11188
|
yield* Effect.uninterruptible(Effect.gen(function* () {
|
|
10837
11189
|
yield* settle(call, handled);
|
|
10838
11190
|
yield* Effect.sync(() => settled.add(call.callID));
|
|
@@ -10855,7 +11207,7 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
10855
11207
|
}));
|
|
10856
11208
|
return interruptedCause;
|
|
10857
11209
|
});
|
|
10858
|
-
const runTurnAttempt = Effect.fn("Loop.runTurnAttempt")(function* (promotion, snapshot) {
|
|
11210
|
+
const runTurnAttempt = Effect.fn("Loop.runTurnAttempt", { level: "Info" })(function* (promotion, snapshot) {
|
|
10859
11211
|
const sessionId = snapshot.sessionId;
|
|
10860
11212
|
if (promotion !== void 0) {
|
|
10861
11213
|
const cutoff = yield* events.latestSequence(sessionId);
|
|
@@ -10959,27 +11311,35 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
10959
11311
|
});
|
|
10960
11312
|
}));
|
|
10961
11313
|
});
|
|
10962
|
-
const runTurn = Effect.fn("Loop.runTurn")(function* (promotion, snapshot) {
|
|
11314
|
+
const runTurn = Effect.fn("Loop.runTurn", { level: "Info" })(function* (promotion, snapshot) {
|
|
10963
11315
|
return yield* runTurnAttempt(promotion, snapshot);
|
|
10964
11316
|
});
|
|
10965
|
-
const run = Effect.fn("Loop.run")(function* (input) {
|
|
11317
|
+
const run = Effect.fn("Loop.run", { level: "Info" })(function* (input) {
|
|
10966
11318
|
yield* healDanglingDraft(input.sessionId);
|
|
10967
11319
|
const hasSteer = yield* inputs.hasPending(input.sessionId, "steer");
|
|
10968
11320
|
const hasFollowUp = hasSteer ? false : yield* inputs.hasPending(input.sessionId, "followUp");
|
|
10969
11321
|
if (!input.force && !hasSteer && !hasFollowUp) return;
|
|
11322
|
+
yield* Effect.annotateCurrentSpan({
|
|
11323
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
11324
|
+
"gen_ai.agent.name": "codework",
|
|
11325
|
+
"gen_ai.conversation.id": input.sessionId
|
|
11326
|
+
});
|
|
10970
11327
|
let promotion = hasSteer ? "steer" : hasFollowUp ? "followUp" : void 0;
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
11328
|
+
const drain = Effect.gen(function* () {
|
|
11329
|
+
let shouldRun = true;
|
|
11330
|
+
while (shouldRun) {
|
|
11331
|
+
const snapshot = yield* state.snapshot(input.sessionId);
|
|
11332
|
+
let needsContinuation = true;
|
|
11333
|
+
while (needsContinuation) {
|
|
11334
|
+
const result = yield* runTurn(promotion, snapshot);
|
|
11335
|
+
promotion = "steer";
|
|
11336
|
+
needsContinuation = result.needsContinuation || (yield* inputs.hasPending(input.sessionId, "steer"));
|
|
11337
|
+
}
|
|
11338
|
+
shouldRun = yield* inputs.hasPending(input.sessionId, "followUp");
|
|
11339
|
+
promotion = shouldRun ? "followUp" : void 0;
|
|
10979
11340
|
}
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
}
|
|
11341
|
+
});
|
|
11342
|
+
yield* timed(drain, (seconds, exit) => record$1(agentDuration, seconds, withError({}, exit)));
|
|
10983
11343
|
});
|
|
10984
11344
|
return Service$7.of({ run });
|
|
10985
11345
|
}));
|
|
@@ -11214,7 +11574,7 @@ const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
11214
11574
|
* `hostDir` led to; only the process view falls back to `hostCwd`.
|
|
11215
11575
|
*/
|
|
11216
11576
|
const hostDirOf = (root) => root ?? hostCwd;
|
|
11217
|
-
const filed = (reference, from) => parse$
|
|
11577
|
+
const filed = (reference, from) => parse$2(reference, from).pipe(Effect.flatMap((target) => target.kind === "local" ? fileSystem.exists(target.path).pipe(Effect.map((there) => there ? Option.some({}) : Option.none())) : resolve$2(target, paths.cache, from).pipe(Effect.map((entry) => Option.fromUndefinedOr(entry)))), Effect.orElseSucceed(() => Option.none()));
|
|
11218
11578
|
const resolveOnly = {
|
|
11219
11579
|
...catalogOptions,
|
|
11220
11580
|
install: required
|
|
@@ -11300,7 +11660,7 @@ const runtimeOverrides = (driver, value) => {
|
|
|
11300
11660
|
}));
|
|
11301
11661
|
return Effect.succeed(value);
|
|
11302
11662
|
};
|
|
11303
|
-
const create$2 = Effect.fn("Sandbox.create")(function* (input) {
|
|
11663
|
+
const create$2 = Effect.fn("Sandbox.create", { level: "Info" })(function* (input) {
|
|
11304
11664
|
const controller = yield* Controller;
|
|
11305
11665
|
const registry = yield* Registry;
|
|
11306
11666
|
const driverName = name(input.driver);
|
|
@@ -11326,7 +11686,7 @@ const Selection = Schema.Union([Schema.Struct({ id: instance_exports.SandboxInst
|
|
|
11326
11686
|
driver: Schema.String,
|
|
11327
11687
|
providerResourceId: (0, schema_exports$9.optional)(Schema.String)
|
|
11328
11688
|
})]);
|
|
11329
|
-
const resolve = Effect.fn("Sandbox.resolve")(function* (input) {
|
|
11689
|
+
const resolve = Effect.fn("Sandbox.resolve", { level: "Info" })(function* (input) {
|
|
11330
11690
|
if ("id" in input) {
|
|
11331
11691
|
const found = yield* get$1(input.id);
|
|
11332
11692
|
if (Option.isNone(found)) return yield* new errors_exports.SandboxNotFoundError({ id: input.id });
|
|
@@ -11379,7 +11739,7 @@ const list = Effect.fn("Sandbox.list")(function* (input) {
|
|
|
11379
11739
|
const refresh = Effect.fn("Sandbox.refresh")(function* (id) {
|
|
11380
11740
|
return yield* (yield* Controller).refresh(id);
|
|
11381
11741
|
});
|
|
11382
|
-
const wake = Effect.fn("Sandbox.wake")(function* (id) {
|
|
11742
|
+
const wake = Effect.fn("Sandbox.wake", { level: "Info" })(function* (id) {
|
|
11383
11743
|
return yield* (yield* Controller).wake(id);
|
|
11384
11744
|
});
|
|
11385
11745
|
const stop = Effect.fn("Sandbox.stop")(function* (id) {
|
|
@@ -11602,12 +11962,14 @@ var plugin_exports = /* @__PURE__ */ __exportAll({
|
|
|
11602
11962
|
definition: () => definition$1,
|
|
11603
11963
|
identity: () => digest,
|
|
11604
11964
|
inspect: () => inspect,
|
|
11965
|
+
lendResolve: () => lendResolve,
|
|
11605
11966
|
localName: () => localName,
|
|
11606
|
-
parse: () => parse$
|
|
11967
|
+
parse: () => parse$2,
|
|
11607
11968
|
probe: () => probe,
|
|
11608
11969
|
resolve: () => resolve$2,
|
|
11609
11970
|
resolveCached: () => required,
|
|
11610
11971
|
settingsLock: () => lock,
|
|
11972
|
+
share: () => share,
|
|
11611
11973
|
update: () => update
|
|
11612
11974
|
});
|
|
11613
11975
|
//#endregion
|