@codeworksh/harness 0.3.0 → 0.4.0
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-D4sNwzSr.mjs → daytona-BqRoRhe7.mjs} +5 -8
- package/daytona-BqRoRhe7.mjs.map +1 -0
- package/effect.d.mts +75 -6
- package/effect.mjs +392 -68
- package/effect.mjs.map +1 -1
- package/package.json +11 -7
- package/sandboxes/daytona/index.mjs +1 -1
- package/daytona-D4sNwzSr.mjs.map +0 -1
package/effect.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
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-
|
|
4
|
+
import "./daytona-BqRoRhe7.mjs";
|
|
5
5
|
import "./vercel-3GouQSJI.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
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";
|
|
@@ -21,11 +21,13 @@ import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
|
21
21
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
22
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
|
-
import {
|
|
24
|
+
import { downloadCatalog } 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
33
|
import npa from "npm-package-arg";
|
|
@@ -134,10 +136,10 @@ var global_exports = /* @__PURE__ */ __exportAll({
|
|
|
134
136
|
app: () => app,
|
|
135
137
|
appConfigDir: () => appConfigDir,
|
|
136
138
|
authFile: () => authFile,
|
|
137
|
-
defaultLayer: () => defaultLayer$
|
|
139
|
+
defaultLayer: () => defaultLayer$4,
|
|
138
140
|
homeConfig: () => homeConfig,
|
|
139
141
|
layer: () => layer$27,
|
|
140
|
-
layerWith: () => layerWith,
|
|
142
|
+
layerWith: () => layerWith$1,
|
|
141
143
|
make: () => make$18,
|
|
142
144
|
resolve: () => resolve$4
|
|
143
145
|
});
|
|
@@ -184,8 +186,8 @@ const build = (input) => Effect.gen(function* () {
|
|
|
184
186
|
return Service$17.of(paths);
|
|
185
187
|
});
|
|
186
188
|
const layer$27 = Layer.effect(Service$17, build({}));
|
|
187
|
-
const defaultLayer$
|
|
188
|
-
const layerWith = (input) => Layer.effect(Service$17, build(input));
|
|
189
|
+
const defaultLayer$4 = layer$27;
|
|
190
|
+
const layerWith$1 = (input) => Layer.effect(Service$17, build(input));
|
|
189
191
|
//#endregion
|
|
190
192
|
//#region src/db/migrations.ts
|
|
191
193
|
const migrations = {
|
|
@@ -374,7 +376,7 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
374
376
|
Database: () => db_exports,
|
|
375
377
|
SqlClient: () => SqlClient$1,
|
|
376
378
|
SqlSchema: () => SqlSchema$1,
|
|
377
|
-
defaultLayer: () => defaultLayer$
|
|
379
|
+
defaultLayer: () => defaultLayer$3,
|
|
378
380
|
layer: () => layer$26,
|
|
379
381
|
locationConfig: () => locationConfig,
|
|
380
382
|
path: () => path$1,
|
|
@@ -408,7 +410,7 @@ const path$1 = Effect.fn("Database.path")(function* (data) {
|
|
|
408
410
|
const directory = data ?? (yield* resolve$4()).data;
|
|
409
411
|
return resolveDatabaseLocation(yield* locationConfig, directory);
|
|
410
412
|
});
|
|
411
|
-
const defaultLayer$
|
|
413
|
+
const defaultLayer$3 = Layer.unwrap(path$1().pipe(Effect.map(layer$26)));
|
|
412
414
|
//#endregion
|
|
413
415
|
//#region src/event/schema.ts
|
|
414
416
|
var schema_exports$7 = /* @__PURE__ */ __exportAll({});
|
|
@@ -1579,7 +1581,7 @@ var session_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
1579
1581
|
SessionNotLinkedError: () => SessionNotLinkedError,
|
|
1580
1582
|
SessionRow: () => SessionRow,
|
|
1581
1583
|
ToolCallNotFoundError: () => ToolCallNotFoundError,
|
|
1582
|
-
defaultLayer: () => defaultLayer,
|
|
1584
|
+
defaultLayer: () => defaultLayer$2,
|
|
1583
1585
|
entryStates: () => entryStates,
|
|
1584
1586
|
entryTypes: () => entryTypes,
|
|
1585
1587
|
layer: () => layer$23,
|
|
@@ -1663,6 +1665,16 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
1663
1665
|
SELECT p.* FROM space p JOIN session s ON s.space_id = p.id WHERE s.id = ${sessionId}
|
|
1664
1666
|
`
|
|
1665
1667
|
});
|
|
1668
|
+
const findProject = SqlSchema.findOneOption({
|
|
1669
|
+
Request: Schema.String,
|
|
1670
|
+
Result: ProjectRow,
|
|
1671
|
+
execute: (sessionId) => sql`
|
|
1672
|
+
SELECT p.* FROM project p
|
|
1673
|
+
JOIN space sp ON sp.project_id = p.id
|
|
1674
|
+
JOIN session s ON s.space_id = sp.id
|
|
1675
|
+
WHERE s.id = ${sessionId}
|
|
1676
|
+
`
|
|
1677
|
+
});
|
|
1666
1678
|
const findSpaceById = SqlSchema.findOneOption({
|
|
1667
1679
|
Request: Schema.String,
|
|
1668
1680
|
Result: SpaceRow,
|
|
@@ -1811,6 +1823,13 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
1811
1823
|
const space = Effect.fn("Session.space")(function* (sessionId) {
|
|
1812
1824
|
return Option.map(yield* findSpace(sessionId).pipe(Effect.orDie), fromRow$1);
|
|
1813
1825
|
});
|
|
1826
|
+
const project = Effect.fn("Session.project")(function* (sessionId) {
|
|
1827
|
+
return Option.map(yield* findProject(sessionId).pipe(Effect.orDie), (row) => new schema_exports$6.ProjectSchema.Info({
|
|
1828
|
+
id: schema_exports$6.ProjectSchema.ID.make(row.id),
|
|
1829
|
+
name: row.name,
|
|
1830
|
+
status: row.status
|
|
1831
|
+
}));
|
|
1832
|
+
});
|
|
1814
1833
|
const list = Effect.fn("Session.list")(function* (input) {
|
|
1815
1834
|
return yield* (input === void 0 ? selectAll() : "spaceId" in input ? selectBySpace(input.spaceId) : selectByProject(input.projectId)).pipe(Effect.orDie);
|
|
1816
1835
|
});
|
|
@@ -2313,6 +2332,7 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2313
2332
|
configure,
|
|
2314
2333
|
get,
|
|
2315
2334
|
space,
|
|
2335
|
+
project,
|
|
2316
2336
|
list,
|
|
2317
2337
|
entry,
|
|
2318
2338
|
path,
|
|
@@ -2329,7 +2349,7 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2329
2349
|
setLabel
|
|
2330
2350
|
});
|
|
2331
2351
|
}));
|
|
2332
|
-
const defaultLayer = layer$23.pipe(Layer.provide(defaultLayer$
|
|
2352
|
+
const defaultLayer$2 = layer$23.pipe(Layer.provide(defaultLayer$3));
|
|
2333
2353
|
//#endregion
|
|
2334
2354
|
//#region src/context/errors.ts
|
|
2335
2355
|
var ContextDecodeError = class extends Schema.TaggedError()("ContextDecodeError", {
|
|
@@ -4274,6 +4294,17 @@ const make$9 = () => {
|
|
|
4274
4294
|
}) };
|
|
4275
4295
|
};
|
|
4276
4296
|
//#endregion
|
|
4297
|
+
//#region src/sandbox/sandbox.ts
|
|
4298
|
+
/**
|
|
4299
|
+
* Assemble a local backend into a sandbox with a **real host shell** — the
|
|
4300
|
+
* right choice whenever commands must invoke actual binaries (`git`, package
|
|
4301
|
+
* managers). For a sandbox whose shell should stay inside the VFS, use
|
|
4302
|
+
* `EnvBash.services` instead, which wires just-bash over the same tree.
|
|
4303
|
+
*/
|
|
4304
|
+
const services = (backend, identity) => Layer.provideMerge(io_exports.SandboxIO.mount(identity), Layer.provideMerge(Layer.merge(layer$21, layer$18()), backend));
|
|
4305
|
+
/** Default sandbox: the real OS filesystem and processes, mounted at `cwd`. */
|
|
4306
|
+
const defaultLayer$1 = (cwd) => services(layer$20(), io_exports.SandboxIO.host(cwd));
|
|
4307
|
+
//#endregion
|
|
4277
4308
|
//#region src/git/git.ts
|
|
4278
4309
|
var AppProcessError = class extends Schema.TaggedError()("AppProcessError", {
|
|
4279
4310
|
command: Schema.String,
|
|
@@ -4464,7 +4495,8 @@ const up = Effect.fn("SandboxFileSystem.up")(function* (fs, options) {
|
|
|
4464
4495
|
const isDirectory$1 = Effect.fn("SandboxFileSystem.isDirectory")(function* (fs, path) {
|
|
4465
4496
|
return yield* fs.stat(path).pipe(Effect.map((stat) => stat.isDirectory), Effect.orElseSucceed(() => false));
|
|
4466
4497
|
});
|
|
4467
|
-
|
|
4498
|
+
/** True when `path` exists and is a regular file; false when it is absent or unreadable. */
|
|
4499
|
+
const isFile = Effect.fn("SandboxFileSystem.isFile")(function* (fs, path) {
|
|
4468
4500
|
return yield* fs.stat(path).pipe(Effect.map((stat) => stat.isFile), Effect.orElseSucceed(() => false));
|
|
4469
4501
|
});
|
|
4470
4502
|
/** Read a file, or `undefined` when it is missing or unreadable. */
|
|
@@ -4611,6 +4643,16 @@ const layer$13 = Layer.effect(Service$10, Effect.gen(function* () {
|
|
|
4611
4643
|
}));
|
|
4612
4644
|
//#endregion
|
|
4613
4645
|
//#region src/project/project.ts
|
|
4646
|
+
var project_exports = /* @__PURE__ */ __exportAll({
|
|
4647
|
+
Project: () => project_exports,
|
|
4648
|
+
ProjectNotFoundError: () => ProjectNotFoundError,
|
|
4649
|
+
Service: () => Service$9,
|
|
4650
|
+
defaultLayer: () => defaultLayer,
|
|
4651
|
+
layer: () => layer$12,
|
|
4652
|
+
layerWith: () => layerWith,
|
|
4653
|
+
provisional: () => provisional,
|
|
4654
|
+
rename: () => rename
|
|
4655
|
+
});
|
|
4614
4656
|
/** Deterministic, env-local id for a directory without a portable identity. Never written to a marker. */
|
|
4615
4657
|
const provisional = (env, location) => schema_exports$6.ProjectSchema.ID.make(fast(JSON.stringify([
|
|
4616
4658
|
"dir",
|
|
@@ -4664,7 +4706,7 @@ const layer$12 = Layer.effect(Service$9, Effect.gen(function* () {
|
|
|
4664
4706
|
main: repo.gitDir === repo.store
|
|
4665
4707
|
}]).toSorted((a, b) => Number(b.main) - Number(a.main));
|
|
4666
4708
|
});
|
|
4667
|
-
const resolveOrCreate = Effect.fn("Project.resolveOrCreate")(function* (cwd) {
|
|
4709
|
+
const resolveOrCreate = Effect.fn("Project.resolveOrCreate")(function* (cwd, options) {
|
|
4668
4710
|
if (!(yield* fs.exists(cwd).pipe(Effect.orDie))) return yield* new DirectoryNotFoundError$1({
|
|
4669
4711
|
directory: cwd,
|
|
4670
4712
|
sandboxInstanceId: env
|
|
@@ -4678,7 +4720,7 @@ const layer$12 = Layer.effect(Service$9, Effect.gen(function* () {
|
|
|
4678
4720
|
const ident = repo === void 0 ? void 0 : yield* repos.identity(repo);
|
|
4679
4721
|
const worktree = repo?.directory ?? location;
|
|
4680
4722
|
const projectId = ident?.id === void 0 ? provisional(env, worktree) : schema_exports$6.ProjectSchema.ID.make(ident.id);
|
|
4681
|
-
const name = ident?.name ?? posix.basename(worktree);
|
|
4723
|
+
const name = options?.name ?? ident?.name ?? posix.basename(worktree);
|
|
4682
4724
|
const vcs = repo === void 0 ? void 0 : {
|
|
4683
4725
|
type: "git",
|
|
4684
4726
|
store: repo.store
|
|
@@ -4750,6 +4792,32 @@ const layer$12 = Layer.effect(Service$9, Effect.gen(function* () {
|
|
|
4750
4792
|
list
|
|
4751
4793
|
});
|
|
4752
4794
|
}));
|
|
4795
|
+
var ProjectNotFoundError = class extends Schema.TaggedError()("ProjectNotFoundError", { projectId: Schema.String }) {};
|
|
4796
|
+
/** Renames a project. Needs only the database, not a mount: a name is the same in every env. */
|
|
4797
|
+
const rename = Effect.fn("Project.rename")(function* (input) {
|
|
4798
|
+
const sql = yield* SqlClient.SqlClient;
|
|
4799
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
4800
|
+
const row = yield* SqlSchema.findOneOption({
|
|
4801
|
+
Request: Schema.Struct({
|
|
4802
|
+
id: Schema.String,
|
|
4803
|
+
name: Schema.String
|
|
4804
|
+
}),
|
|
4805
|
+
Result: ProjectRow,
|
|
4806
|
+
execute: ({ id, name }) => sql`UPDATE project SET name = ${name}, updated_at = ${now} WHERE id = ${id} RETURNING *`
|
|
4807
|
+
})({
|
|
4808
|
+
id: input.projectId,
|
|
4809
|
+
name: input.name
|
|
4810
|
+
}).pipe(Effect.orDie);
|
|
4811
|
+
if (Option.isNone(row)) return yield* new ProjectNotFoundError({ projectId: input.projectId });
|
|
4812
|
+
return new schema_exports$6.ProjectSchema.Info({
|
|
4813
|
+
id: schema_exports$6.ProjectSchema.ID.make(row.value.id),
|
|
4814
|
+
name: row.value.name,
|
|
4815
|
+
status: row.value.status
|
|
4816
|
+
});
|
|
4817
|
+
});
|
|
4818
|
+
/** Takes an assembled sandbox — local or remote — not a local backend. */
|
|
4819
|
+
const layerWith = (sandbox) => layer$12.pipe(Layer.provide(Layer.mergeAll(layer$14, layer$13, layer$24)), Layer.provide(layer$15), Layer.provide(sandbox), Layer.provide(defaultLayer$3));
|
|
4820
|
+
const defaultLayer = (path) => layerWith(defaultLayer$1(path));
|
|
4753
4821
|
//#endregion
|
|
4754
4822
|
//#region src/location/location.ts
|
|
4755
4823
|
var location_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -4761,10 +4829,10 @@ var location_exports = /* @__PURE__ */ __exportAll({
|
|
|
4761
4829
|
layer: () => layer$11,
|
|
4762
4830
|
layerMounted: () => layerMounted
|
|
4763
4831
|
});
|
|
4764
|
-
const layer$11 = () => Layer.effect(Service, Effect.gen(function* () {
|
|
4832
|
+
const layer$11 = (options) => Layer.effect(Service, Effect.gen(function* () {
|
|
4765
4833
|
const instance = yield* io_exports.SandboxIO.Current;
|
|
4766
4834
|
const directory = schema_exports$9.AbsolutePath.make(instance.cwd);
|
|
4767
|
-
const resolved = yield* (yield* Service$9).resolveOrCreate(directory);
|
|
4835
|
+
const resolved = yield* (yield* Service$9).resolveOrCreate(directory, options);
|
|
4768
4836
|
return Service.of(new Info({
|
|
4769
4837
|
directory: resolved.directory,
|
|
4770
4838
|
space: resolved.space,
|
|
@@ -4776,7 +4844,7 @@ const layer$11 = () => Layer.effect(Service, Effect.gen(function* () {
|
|
|
4776
4844
|
* folded in. What remains is exactly the mount and the database — for callers
|
|
4777
4845
|
* that already hold both, such as the runner and `Session.create`.
|
|
4778
4846
|
*/
|
|
4779
|
-
const layerMounted = () => layer$11().pipe(Layer.provide(layer$12.pipe(Layer.provide(Layer.mergeAll(layer$14, layer$13, layer$24)), Layer.provide(layer$15))));
|
|
4847
|
+
const layerMounted = (options) => layer$11(options).pipe(Layer.provide(layer$12.pipe(Layer.provide(Layer.mergeAll(layer$14, layer$13, layer$24)), Layer.provide(layer$15))));
|
|
4780
4848
|
//#endregion
|
|
4781
4849
|
//#region src/runner/run.ts
|
|
4782
4850
|
var run_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -4871,7 +4939,6 @@ var ProviderError = class extends Schema.TaggedError()("Runner.ProviderError", {
|
|
|
4871
4939
|
var ModelCatalogError = class extends Schema.TaggedError()("Runner.ModelCatalogError", {
|
|
4872
4940
|
path: Schema.String,
|
|
4873
4941
|
reason: Schema.Literals([
|
|
4874
|
-
"missing",
|
|
4875
4942
|
"unreadable",
|
|
4876
4943
|
"empty",
|
|
4877
4944
|
"invalid"
|
|
@@ -5312,7 +5379,7 @@ var settings_exports = /* @__PURE__ */ __exportAll({
|
|
|
5312
5379
|
layer: () => layer$8,
|
|
5313
5380
|
load: () => load$3,
|
|
5314
5381
|
modules: () => modules,
|
|
5315
|
-
parse: () => parse$
|
|
5382
|
+
parse: () => parse$5,
|
|
5316
5383
|
paths: () => paths,
|
|
5317
5384
|
projectRoot: () => projectRoot,
|
|
5318
5385
|
userConfigDir: () => userConfigDir
|
|
@@ -5401,7 +5468,7 @@ const decodeFormatter = SchemaIssue.makeFormatterStandardSchemaV1({ leafHook: (i
|
|
|
5401
5468
|
const message = "annotations" in issue ? issue.annotations?.message : void 0;
|
|
5402
5469
|
return typeof message === "string" ? message.replaceAll(/\s*\n\s*/g, " ") : "invalid value";
|
|
5403
5470
|
} });
|
|
5404
|
-
const parse$
|
|
5471
|
+
const parse$5 = Effect.fn("Settings.parse")(function* (path, source) {
|
|
5405
5472
|
const errors = [];
|
|
5406
5473
|
const json = parse(source, errors, {
|
|
5407
5474
|
allowTrailingComma: true,
|
|
@@ -5449,7 +5516,7 @@ const attempt = (path) => fileSystem.readFileString(path).pipe(Effect.mapError((
|
|
|
5449
5516
|
path,
|
|
5450
5517
|
reason: "read",
|
|
5451
5518
|
detail: error.reason._tag
|
|
5452
|
-
})), Effect.flatMap((source) => parse$
|
|
5519
|
+
})), Effect.flatMap((source) => parse$5(path, source)));
|
|
5453
5520
|
/**
|
|
5454
5521
|
* One read of every layer. Exported for the harness constructor, which needs the plugin
|
|
5455
5522
|
* selection before any layer is built; everything else goes through `Service`.
|
|
@@ -5563,8 +5630,13 @@ const stale = Effect.fn("ModelCatalog.stale")(function* (path) {
|
|
|
5563
5630
|
const ttl = yield* ttlConfig;
|
|
5564
5631
|
return (yield* Clock.currentTimeMillis) - mtime.value >= Duration.toMillis(ttl);
|
|
5565
5632
|
});
|
|
5633
|
+
/** OpenRouter's catalog is narrowed to what the host's key may use, when one is set. */
|
|
5634
|
+
const openRouterKey = () => getEnvApiKey({ env: ["OPENROUTER_API_KEY"] });
|
|
5566
5635
|
const download$1 = (path) => Effect.tryPromise({
|
|
5567
|
-
try: () =>
|
|
5636
|
+
try: () => downloadCatalog({
|
|
5637
|
+
path,
|
|
5638
|
+
openRouterKey: openRouterKey()
|
|
5639
|
+
}),
|
|
5568
5640
|
catch: (cause) => new RefreshError({
|
|
5569
5641
|
path,
|
|
5570
5642
|
detail: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -5585,8 +5657,7 @@ const refresh$1 = Effect.fn("ModelCatalog.refresh")(function* (home, options = {
|
|
|
5585
5657
|
});
|
|
5586
5658
|
/**
|
|
5587
5659
|
* The automatic check: refresh a stale catalog unless it is pinned, and never fail. A failed
|
|
5588
|
-
* download leaves the old file in place;
|
|
5589
|
-
* `Runner.ModelCatalogError` from whatever needed it.
|
|
5660
|
+
* download leaves the old file in place; without one, aikit's bundled baseline is read.
|
|
5590
5661
|
*/
|
|
5591
5662
|
const check$1 = Effect.fn("ModelCatalog.check")(function* ({ path, pinned }) {
|
|
5592
5663
|
if (pinned || !(yield* stale(path))) return;
|
|
@@ -5622,7 +5693,7 @@ const loadError = (cause) => {
|
|
|
5622
5693
|
if (typeof cause !== "object" || cause === null || !("name" in cause) || cause.name !== "ModelCatalogLoadError") return;
|
|
5623
5694
|
if (!("data" in cause) || typeof cause.data !== "object" || cause.data === null) return void 0;
|
|
5624
5695
|
const data = cause.data;
|
|
5625
|
-
if (!("path" in data) || typeof data.path !== "string" || !("message" in data) || typeof data.message !== "string" || !("reason" in data) || data.reason !== "
|
|
5696
|
+
if (!("path" in data) || typeof data.path !== "string" || !("message" in data) || typeof data.message !== "string" || !("reason" in data) || data.reason !== "unreadable" && data.reason !== "empty" && data.reason !== "invalid") return;
|
|
5626
5697
|
return new ModelCatalogError({
|
|
5627
5698
|
path: data.path,
|
|
5628
5699
|
reason: data.reason,
|
|
@@ -5755,6 +5826,22 @@ const defaultPromptPlugin = (0, plugin_exports$1.define)({
|
|
|
5755
5826
|
})
|
|
5756
5827
|
});
|
|
5757
5828
|
//#endregion
|
|
5829
|
+
//#region src/plugin/builtin/prompt/walk.ts
|
|
5830
|
+
const stripBom$1 = (raw) => raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
5831
|
+
/** `directory` and every ancestor up to the sandbox root, closest first. */
|
|
5832
|
+
const ancestors = (directory) => {
|
|
5833
|
+
const chain = [];
|
|
5834
|
+
for (let current = directory;; current = posix.dirname(current)) {
|
|
5835
|
+
chain.push(current);
|
|
5836
|
+
if (posix.dirname(current) === current) return chain;
|
|
5837
|
+
}
|
|
5838
|
+
};
|
|
5839
|
+
/**
|
|
5840
|
+
* Which of `paths` exist as the requested kind (`entry` takes either). Absence stays silent; any other failure
|
|
5841
|
+
* (permissions, a remote blip) is logged so a dropped entry is not a mystery, then counts as absent.
|
|
5842
|
+
*/
|
|
5843
|
+
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]))));
|
|
5844
|
+
//#endregion
|
|
5758
5845
|
//#region src/plugin/builtin/prompt/instruction.ts
|
|
5759
5846
|
/** First readable match per directory wins. */
|
|
5760
5847
|
const names = [
|
|
@@ -5766,22 +5853,9 @@ const names = [
|
|
|
5766
5853
|
"CLAUDE.MD"
|
|
5767
5854
|
];
|
|
5768
5855
|
const content = (raw) => {
|
|
5769
|
-
const text =
|
|
5856
|
+
const text = stripBom$1(raw);
|
|
5770
5857
|
return text.trim().length === 0 ? void 0 : text;
|
|
5771
5858
|
};
|
|
5772
|
-
/** `directory` and every ancestor up to the sandbox root, closest first. */
|
|
5773
|
-
const ancestors = (directory) => {
|
|
5774
|
-
const chain = [];
|
|
5775
|
-
for (let current = directory;; current = posix.dirname(current)) {
|
|
5776
|
-
chain.push(current);
|
|
5777
|
-
if (posix.dirname(current) === current) return chain;
|
|
5778
|
-
}
|
|
5779
|
-
};
|
|
5780
|
-
/**
|
|
5781
|
-
* Which of `paths` are files. Absence is the expected answer and stays silent; any other failure
|
|
5782
|
-
* (permissions, a remote blip) is logged so a dropped file is not a mystery, then counts as absent.
|
|
5783
|
-
*/
|
|
5784
|
-
const present$1 = (fs, paths) => Effect.forEach(paths, (path) => fs.exists(path).pipe(Effect.flatMap((exists) => exists ? fs.stat(path).pipe(Effect.map((stat) => stat.isFile)) : Effect.succeed(false)), 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]))));
|
|
5785
5859
|
/** A file that stats but does not read falls through to the next candidate. */
|
|
5786
5860
|
const first = Effect.fnUntraced(function* (fs, directory, files) {
|
|
5787
5861
|
for (const name of names) {
|
|
@@ -5809,9 +5883,9 @@ const shadowing = (location) => {
|
|
|
5809
5883
|
return location.space.location.startsWith(`${main}/`) ? main : void 0;
|
|
5810
5884
|
};
|
|
5811
5885
|
/** One file per directory from the sandbox root down to the cwd, closest last so it has the final word. */
|
|
5812
|
-
const discover = Effect.fnUntraced(function* (fs, location) {
|
|
5886
|
+
const discover$1 = Effect.fnUntraced(function* (fs, location) {
|
|
5813
5887
|
const chain = ancestors(location.directory);
|
|
5814
|
-
const files = yield* present$1(fs, chain.flatMap((directory) => names.map((name) => posix.join(directory, name))));
|
|
5888
|
+
const files = yield* present$1(fs, chain.flatMap((directory) => names.map((name) => posix.join(directory, name))), "file", "instruction file");
|
|
5815
5889
|
const found = yield* Effect.forEach(chain, (directory) => first(fs, directory, files), { concurrency: "unbounded" });
|
|
5816
5890
|
const main = shadowing(location);
|
|
5817
5891
|
const worktree = main === void 0 ? void 0 : found[chain.indexOf(location.space.location)];
|
|
@@ -5827,7 +5901,7 @@ const instructionPlugin = (0, plugin_exports$1.define)({
|
|
|
5827
5901
|
kind: "prompt",
|
|
5828
5902
|
setup: Effect.fn("InstructionPlugin.setup")(function* (ctx) {
|
|
5829
5903
|
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
5830
|
-
const files = (yield* discover(fs, ctx.location)).filter((file) => file.content !== void 0);
|
|
5904
|
+
const files = (yield* discover$1(fs, ctx.location)).filter((file) => file.content !== void 0);
|
|
5831
5905
|
if (files.length === 0) return;
|
|
5832
5906
|
const prompt = ctx.plugin.prompt;
|
|
5833
5907
|
prompt.sections.append(Section.ProjectContext, "Project-Specific Instructions & Guidelines:");
|
|
@@ -5835,6 +5909,236 @@ const instructionPlugin = (0, plugin_exports$1.define)({
|
|
|
5835
5909
|
})
|
|
5836
5910
|
});
|
|
5837
5911
|
//#endregion
|
|
5912
|
+
//#region src/plugin/builtin/prompt/skill.ts
|
|
5913
|
+
/** Max name length per Agent Skills spec. */
|
|
5914
|
+
const MAX_NAME_LENGTH = 64;
|
|
5915
|
+
/** Max description length per Agent Skills spec. */
|
|
5916
|
+
const MAX_DESCRIPTION_LENGTH = 1024;
|
|
5917
|
+
const IGNORE_FILE_NAMES = [
|
|
5918
|
+
".gitignore",
|
|
5919
|
+
".ignore",
|
|
5920
|
+
".fdignore"
|
|
5921
|
+
];
|
|
5922
|
+
const normalizeNewlines = (value) => value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
5923
|
+
const extractFrontmatter = (content) => {
|
|
5924
|
+
const normalized = normalizeNewlines(stripBom$1(content));
|
|
5925
|
+
if (!normalized.startsWith("---")) return void 0;
|
|
5926
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
5927
|
+
if (endIndex === -1) return void 0;
|
|
5928
|
+
return normalized.slice(4, endIndex);
|
|
5929
|
+
};
|
|
5930
|
+
const parseFrontmatter = (raw) => {
|
|
5931
|
+
const yamlString = extractFrontmatter(raw);
|
|
5932
|
+
if (yamlString === void 0) return { frontmatter: {} };
|
|
5933
|
+
try {
|
|
5934
|
+
const parsed = parse$1(yamlString);
|
|
5935
|
+
return { frontmatter: typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {} };
|
|
5936
|
+
} catch (error) {
|
|
5937
|
+
return {
|
|
5938
|
+
frontmatter: {},
|
|
5939
|
+
error
|
|
5940
|
+
};
|
|
5941
|
+
}
|
|
5942
|
+
};
|
|
5943
|
+
const validateName = (name) => {
|
|
5944
|
+
const errors = [];
|
|
5945
|
+
if (name.length > MAX_NAME_LENGTH) errors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);
|
|
5946
|
+
if (!/^[a-z0-9-]+$/.test(name)) errors.push("name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)");
|
|
5947
|
+
if (name.startsWith("-") || name.endsWith("-")) errors.push("name must not start or end with a hyphen");
|
|
5948
|
+
if (name.includes("--")) errors.push("name must not contain consecutive hyphens");
|
|
5949
|
+
return errors;
|
|
5950
|
+
};
|
|
5951
|
+
const validateDescription = (description) => {
|
|
5952
|
+
const errors = [];
|
|
5953
|
+
if (typeof description !== "string" || description.trim() === "") errors.push("description is required");
|
|
5954
|
+
else if (description.length > MAX_DESCRIPTION_LENGTH) errors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);
|
|
5955
|
+
return errors;
|
|
5956
|
+
};
|
|
5957
|
+
const prefixIgnorePattern = (line, prefix) => {
|
|
5958
|
+
const trimmed = line.trim();
|
|
5959
|
+
if (!trimmed) return void 0;
|
|
5960
|
+
if (trimmed.startsWith("#") && !trimmed.startsWith("\\#")) return void 0;
|
|
5961
|
+
let pattern = line;
|
|
5962
|
+
let negated = false;
|
|
5963
|
+
if (pattern.startsWith("!")) {
|
|
5964
|
+
negated = true;
|
|
5965
|
+
pattern = pattern.slice(1);
|
|
5966
|
+
} else if (pattern.startsWith("\\!")) pattern = pattern.slice(1);
|
|
5967
|
+
if (pattern.startsWith("/")) pattern = pattern.slice(1);
|
|
5968
|
+
const prefixed = prefix ? `${prefix}${pattern}` : pattern;
|
|
5969
|
+
return negated ? `!${prefixed}` : prefixed;
|
|
5970
|
+
};
|
|
5971
|
+
const addIgnoreRules = Effect.fnUntraced(function* (fs, ig, dir, root, entryNames) {
|
|
5972
|
+
const relativeDir = posix.relative(root, dir);
|
|
5973
|
+
const prefix = relativeDir && relativeDir !== "." ? `${relativeDir}/` : "";
|
|
5974
|
+
const presentIgnoreFiles = IGNORE_FILE_NAMES.filter((name) => entryNames.has(name));
|
|
5975
|
+
if (presentIgnoreFiles.length === 0) return;
|
|
5976
|
+
const contents = yield* Effect.forEach(presentIgnoreFiles, (filename) => fs.readFile(posix.join(dir, filename)).pipe(Effect.option), { concurrency: "unbounded" });
|
|
5977
|
+
for (const content of contents) {
|
|
5978
|
+
if (Option.isNone(content)) continue;
|
|
5979
|
+
const patterns = content.value.split(/\r?\n/).map((line) => prefixIgnorePattern(line, prefix)).filter((line) => line !== void 0);
|
|
5980
|
+
if (patterns.length > 0) ig.add(patterns);
|
|
5981
|
+
}
|
|
5982
|
+
});
|
|
5983
|
+
const loadSkillFromFile = Effect.fnUntraced(function* (fs, filePath) {
|
|
5984
|
+
const isDeclaredSkill = posix.basename(filePath) === "SKILL.md";
|
|
5985
|
+
const raw = yield* fs.readFile(filePath).pipe(Effect.tapError((error) => Effect.logWarning(`could not read skill file ${filePath}`, error.cause)), Effect.option);
|
|
5986
|
+
if (Option.isNone(raw)) return void 0;
|
|
5987
|
+
const { frontmatter, error } = parseFrontmatter(raw.value);
|
|
5988
|
+
if (error !== void 0) {
|
|
5989
|
+
if (isDeclaredSkill) yield* Effect.logWarning(`could not parse skill file ${filePath}`, error);
|
|
5990
|
+
return;
|
|
5991
|
+
}
|
|
5992
|
+
const description = frontmatter.description;
|
|
5993
|
+
const hasDescription = typeof description === "string" && description.trim() !== "";
|
|
5994
|
+
if (!isDeclaredSkill && !hasDescription) return void 0;
|
|
5995
|
+
const skillDir = posix.dirname(filePath);
|
|
5996
|
+
const parentDirName = posix.basename(skillDir);
|
|
5997
|
+
for (const error of validateDescription(description)) yield* Effect.logWarning(`skill ${filePath}: ${error}`);
|
|
5998
|
+
const name = (typeof frontmatter.name === "string" ? frontmatter.name : void 0) || parentDirName;
|
|
5999
|
+
for (const error of validateName(name)) yield* Effect.logWarning(`skill ${filePath}: ${error}`);
|
|
6000
|
+
if (!hasDescription) return void 0;
|
|
6001
|
+
return {
|
|
6002
|
+
name,
|
|
6003
|
+
description,
|
|
6004
|
+
filePath,
|
|
6005
|
+
baseDir: skillDir,
|
|
6006
|
+
disableModelInvocation: frontmatter["disable-model-invocation"] === true
|
|
6007
|
+
};
|
|
6008
|
+
});
|
|
6009
|
+
const scanDir = (fs, dir, mode, root, ig, visited) => Effect.gen(function* () {
|
|
6010
|
+
const realDir = yield* realpath(fs, dir);
|
|
6011
|
+
if (visited.has(realDir)) return [];
|
|
6012
|
+
const nextVisited = /* @__PURE__ */ new Set([...visited, realDir]);
|
|
6013
|
+
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);
|
|
6014
|
+
if (Option.isNone(listed)) return [];
|
|
6015
|
+
const entries = [...listed.value].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
6016
|
+
const entrySet = new Set(entries);
|
|
6017
|
+
yield* addIgnoreRules(fs, ig, dir, root, entrySet);
|
|
6018
|
+
if (entrySet.has("SKILL.md")) {
|
|
6019
|
+
const fullPath = posix.join(dir, "SKILL.md");
|
|
6020
|
+
const relPath = posix.relative(root, fullPath);
|
|
6021
|
+
if ((yield* isFile(fs, fullPath)) && !ig.ignores(relPath)) {
|
|
6022
|
+
const skill = yield* loadSkillFromFile(fs, fullPath);
|
|
6023
|
+
return skill === void 0 ? [] : [skill];
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
const visible = entries.filter((name) => !name.startsWith(".") && name !== "node_modules");
|
|
6027
|
+
const statted = yield* Effect.forEach(visible, (name) => {
|
|
6028
|
+
const fullPath = posix.join(dir, name);
|
|
6029
|
+
return fs.stat(fullPath).pipe(Effect.map((stat) => ({
|
|
6030
|
+
name,
|
|
6031
|
+
fullPath,
|
|
6032
|
+
stat
|
|
6033
|
+
})), Effect.option);
|
|
6034
|
+
}, { concurrency: "unbounded" });
|
|
6035
|
+
const skills = [];
|
|
6036
|
+
for (const item of statted) {
|
|
6037
|
+
if (Option.isNone(item)) continue;
|
|
6038
|
+
const { name, fullPath, stat } = item.value;
|
|
6039
|
+
const relPath = posix.relative(root, fullPath);
|
|
6040
|
+
if (ig.ignores(stat.isDirectory ? `${relPath}/` : relPath)) continue;
|
|
6041
|
+
if (stat.isDirectory) {
|
|
6042
|
+
const nested = yield* scanDir(fs, fullPath, mode, root, ig, nextVisited);
|
|
6043
|
+
skills.push(...nested);
|
|
6044
|
+
continue;
|
|
6045
|
+
}
|
|
6046
|
+
if (stat.isFile && name.endsWith(".md") && (mode === "codework" && dir === root || mode === "agents" && dir !== root)) {
|
|
6047
|
+
const skill = yield* loadSkillFromFile(fs, fullPath);
|
|
6048
|
+
if (skill !== void 0) skills.push(skill);
|
|
6049
|
+
}
|
|
6050
|
+
}
|
|
6051
|
+
return skills;
|
|
6052
|
+
});
|
|
6053
|
+
/**
|
|
6054
|
+
* Load skills from a single directory in the sandbox.
|
|
6055
|
+
*
|
|
6056
|
+
* Discovery rules:
|
|
6057
|
+
* - If a directory contains `SKILL.md`, treat it as a skill root and do not recurse further.
|
|
6058
|
+
* - In `"codework"` mode, direct `.md` children in the root are also loaded; in `"agents"` mode,
|
|
6059
|
+
* `.md` files in subdirectories are loaded while root `.md` files are ignored.
|
|
6060
|
+
* - Recurse into subdirectories (skipping dotfiles, `node_modules`, and ignored paths).
|
|
6061
|
+
*/
|
|
6062
|
+
const loadSkillsFromDir = (fs, dir, mode) => scanDir(fs, dir, mode, dir, ignore(), /* @__PURE__ */ new Set());
|
|
6063
|
+
/**
|
|
6064
|
+
* Discover skills inside the sandbox: `.codework/skills` at the cwd, then
|
|
6065
|
+
* `.agents/skills` from the cwd up to the git root (the closest ancestor holding `.git`), or up to
|
|
6066
|
+
* the sandbox root outside a repository. Earlier skills win name collisions.
|
|
6067
|
+
*/
|
|
6068
|
+
const discover = Effect.fnUntraced(function* (fs, location) {
|
|
6069
|
+
const chain = ancestors(location.directory);
|
|
6070
|
+
const own = posix.join(location.directory, appConfigDir, "skills");
|
|
6071
|
+
const agents = chain.map((directory) => posix.join(directory, ".agents", "skills"));
|
|
6072
|
+
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" });
|
|
6073
|
+
const root = chain.findIndex((directory) => gits.has(posix.join(directory, ".git")));
|
|
6074
|
+
const walked = root === -1 ? agents : agents.slice(0, root + 1);
|
|
6075
|
+
const sources = [{
|
|
6076
|
+
dir: own,
|
|
6077
|
+
mode: "codework"
|
|
6078
|
+
}, ...walked.map((dir) => ({
|
|
6079
|
+
dir,
|
|
6080
|
+
mode: "agents"
|
|
6081
|
+
}))].filter(({ dir }) => dirs.has(dir));
|
|
6082
|
+
const found = yield* Effect.forEach(sources, ({ dir, mode }) => loadSkillsFromDir(fs, dir, mode), { concurrency: "unbounded" });
|
|
6083
|
+
const skillMap = /* @__PURE__ */ new Map();
|
|
6084
|
+
const realPathSet = /* @__PURE__ */ new Set();
|
|
6085
|
+
for (const skill of found.flat()) {
|
|
6086
|
+
const realPath = yield* realpath(fs, skill.filePath);
|
|
6087
|
+
if (realPathSet.has(realPath)) continue;
|
|
6088
|
+
const existing = skillMap.get(skill.name);
|
|
6089
|
+
if (existing !== void 0) yield* Effect.logWarning(`skill ${skill.filePath}: name "${skill.name}" collision with ${existing.filePath}`);
|
|
6090
|
+
else {
|
|
6091
|
+
skillMap.set(skill.name, skill);
|
|
6092
|
+
realPathSet.add(realPath);
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
return Array.from(skillMap.values());
|
|
6096
|
+
});
|
|
6097
|
+
const escapeXml = (str) => str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
6098
|
+
/**
|
|
6099
|
+
* Format skills for inclusion in `Section.Skills` per the Agent Skills specification.
|
|
6100
|
+
* Skills with `disableModelInvocation: true` are excluded from the prompt.
|
|
6101
|
+
*/
|
|
6102
|
+
const formatSkillsForPrompt = (skills, fileReadTool = "read") => {
|
|
6103
|
+
const visibleSkills = skills.filter((s) => !s.disableModelInvocation);
|
|
6104
|
+
if (visibleSkills.length === 0) return "";
|
|
6105
|
+
const lines = [
|
|
6106
|
+
"The following skills provide specialized instructions for specific tasks.",
|
|
6107
|
+
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.",
|
|
6108
|
+
"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.",
|
|
6109
|
+
"",
|
|
6110
|
+
"<available_skills>"
|
|
6111
|
+
];
|
|
6112
|
+
for (const skill of visibleSkills) {
|
|
6113
|
+
lines.push(" <skill>");
|
|
6114
|
+
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
6115
|
+
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
6116
|
+
lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
|
|
6117
|
+
lines.push(" </skill>");
|
|
6118
|
+
}
|
|
6119
|
+
lines.push("</available_skills>");
|
|
6120
|
+
return lines.join("\n");
|
|
6121
|
+
};
|
|
6122
|
+
const readers = ["read", "bash"];
|
|
6123
|
+
const skillPlugin = (0, plugin_exports$1.define)({
|
|
6124
|
+
id: "codework.prompt.skill",
|
|
6125
|
+
kind: "prompt",
|
|
6126
|
+
setup: Effect.fn("SkillPlugin.setup")(function* (ctx) {
|
|
6127
|
+
const tools = ctx.plugin.tools;
|
|
6128
|
+
const visible = (name) => {
|
|
6129
|
+
const tool = tools.get(name);
|
|
6130
|
+
return tool !== void 0 && tool.promptSnippet !== void 0 && tool.promptSnippet.length > 0;
|
|
6131
|
+
};
|
|
6132
|
+
const fileReadTool = readers.find(visible) ?? (readers.some((name) => tools.has(name)) ? "indirect" : void 0);
|
|
6133
|
+
if (fileReadTool === void 0) return;
|
|
6134
|
+
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
6135
|
+
const skills = yield* discover(fs, ctx.location);
|
|
6136
|
+
const formatted = formatSkillsForPrompt(skills, fileReadTool);
|
|
6137
|
+
if (formatted.length === 0) return;
|
|
6138
|
+
ctx.plugin.prompt.sections.append(Section.Skills, formatted);
|
|
6139
|
+
})
|
|
6140
|
+
});
|
|
6141
|
+
//#endregion
|
|
5838
6142
|
//#region src/tool/truncate.ts
|
|
5839
6143
|
/**
|
|
5840
6144
|
* Shared truncation utilities for tool outputs.
|
|
@@ -6146,8 +6450,10 @@ const windowBytes = (limits) => limits.maxBytes * 2;
|
|
|
6146
6450
|
*/
|
|
6147
6451
|
const script = (command, file, limits, pipeline, report) => [
|
|
6148
6452
|
`f=${(0, shell_exports.quote)(file)}`,
|
|
6453
|
+
`c=${(0, shell_exports.quote)(command)}`,
|
|
6454
|
+
"if command -v bash >/dev/null 2>&1; then s=bash; else s=sh; fi",
|
|
6149
6455
|
"run() (",
|
|
6150
|
-
|
|
6456
|
+
" if [ \"$s\" = bash ]; then bash -c \"$c\"; else eval \"$c\"; fi",
|
|
6151
6457
|
")",
|
|
6152
6458
|
`if mkdir -p ${(0, shell_exports.quote)(posix.dirname(file))} 2>/dev/null && (umask 077 2>/dev/null; set -C && : > "$f" && : > "$f.rc" && : > "$f.pid") 2>/dev/null; then`,
|
|
6153
6459
|
" echo \"$$ $(ps -o pgid= -p $$ 2>/dev/null)\" > \"$f.pid\"",
|
|
@@ -6164,18 +6470,19 @@ const script = (command, file, limits, pipeline, report) => [
|
|
|
6164
6470
|
`printf '%s\\n' ${(0, shell_exports.quote)(unsaved(file))}`,
|
|
6165
6471
|
"run 2>&1"
|
|
6166
6472
|
].join("\n");
|
|
6167
|
-
const header = new RegExp(`^${HEADER} (\\d+) (\\d+) ([01])(?: (\\d+))?$`, "m");
|
|
6168
|
-
const parse$
|
|
6473
|
+
const header = new RegExp(`^${HEADER} (\\d+) (\\d+) ([01]) (bash|sh)(?: (\\d+))?$`, "m");
|
|
6474
|
+
const parse$4 = (match) => ({
|
|
6169
6475
|
bytes: Number(match[1]),
|
|
6170
6476
|
newlines: Number(match[2]),
|
|
6171
|
-
kept: match[3] === "1"
|
|
6477
|
+
kept: match[3] === "1",
|
|
6478
|
+
shell: match[4] === "sh" ? "sh" : "bash"
|
|
6172
6479
|
});
|
|
6173
6480
|
/**
|
|
6174
6481
|
* For a backend that streams: all output still flows back as it is produced, and
|
|
6175
6482
|
* a copy lands in the file. The report rides on stderr, the wrapper's own channel
|
|
6176
6483
|
* since the command's is folded into stdout.
|
|
6177
6484
|
*/
|
|
6178
|
-
const streaming = (command, file, limits) => script(command, file, limits, (source) => `${source} | tee -a "$f"`, [`printf '${HEADER} %s %s %s\\n' "$b" "$n" "$keep" >&2`]);
|
|
6485
|
+
const streaming = (command, file, limits) => script(command, file, limits, (source) => `${source} | tee -a "$f"`, [`printf '${HEADER} %s %s %s %s\\n' "$b" "$n" "$keep" "$s" >&2`]);
|
|
6179
6486
|
/** Read {@link streaming}'s stderr: the report, if it got that far, and anything else the wrapper said. */
|
|
6180
6487
|
const report = (stderr) => {
|
|
6181
6488
|
const match = header.exec(stderr);
|
|
@@ -6184,7 +6491,7 @@ const report = (stderr) => {
|
|
|
6184
6491
|
rest: stderr
|
|
6185
6492
|
};
|
|
6186
6493
|
return {
|
|
6187
|
-
report: parse$
|
|
6494
|
+
report: parse$4(match),
|
|
6188
6495
|
rest: stderr.slice(0, match.index) + stderr.slice(match.index + match[0].length).replace(/^\n/, "")
|
|
6189
6496
|
};
|
|
6190
6497
|
};
|
|
@@ -6197,7 +6504,7 @@ const buffered = (command, file, limits) => {
|
|
|
6197
6504
|
const tail = `tail -n ${limits.maxLines} "$f" | tail -c ${windowBytes(limits)}`;
|
|
6198
6505
|
return script(command, file, limits, (source) => `{ ${source} | cat; } >> "$f"`, [
|
|
6199
6506
|
`w=$(${tail} | wc -c | awk '{ print $1 }')`,
|
|
6200
|
-
`printf '${HEADER} %s %s %s %s\\n' "$b" "$n" "$keep" "$w"`,
|
|
6507
|
+
`printf '${HEADER} %s %s %s %s %s\\n' "$b" "$n" "$keep" "$s" "$w"`,
|
|
6201
6508
|
tail
|
|
6202
6509
|
]);
|
|
6203
6510
|
};
|
|
@@ -6205,11 +6512,11 @@ const buffered = (command, file, limits) => {
|
|
|
6205
6512
|
const window = (stdout) => {
|
|
6206
6513
|
const newline = stdout.indexOf("\n");
|
|
6207
6514
|
const match = header.exec(newline === -1 ? stdout : stdout.slice(0, newline));
|
|
6208
|
-
if (match === null || match[
|
|
6515
|
+
if (match === null || match[5] === void 0) return void 0;
|
|
6209
6516
|
return {
|
|
6210
|
-
...parse$
|
|
6517
|
+
...parse$4(match),
|
|
6211
6518
|
text: stdout.slice(newline + 1),
|
|
6212
|
-
textBytes: Number(match[
|
|
6519
|
+
textBytes: Number(match[5])
|
|
6213
6520
|
};
|
|
6214
6521
|
};
|
|
6215
6522
|
/**
|
|
@@ -6357,7 +6664,9 @@ const BashSuccess = Schema.Struct({
|
|
|
6357
6664
|
/** Non-zero exit — expected, model-visible. Carries the same shape as success. */
|
|
6358
6665
|
var BashFailed = class extends Schema.TaggedError()("BashFailed", {
|
|
6359
6666
|
...outputFields,
|
|
6360
|
-
exitCode: Schema.Finite
|
|
6667
|
+
exitCode: Schema.Finite,
|
|
6668
|
+
/** The shell that ran the command, when the sandbox reported it. */
|
|
6669
|
+
shell: Schema.optional(Schema.Literals(["bash", "sh"]))
|
|
6361
6670
|
}) {};
|
|
6362
6671
|
/** Deadline exceeded — expected, model-visible. Carries the partial output produced so far. */
|
|
6363
6672
|
var BashTimedOut = class extends Schema.TaggedError()("BashTimedOut", {
|
|
@@ -6367,7 +6676,7 @@ var BashTimedOut = class extends Schema.TaggedError()("BashTimedOut", {
|
|
|
6367
6676
|
const BashFailure = Schema.Union([BashFailed, BashTimedOut]);
|
|
6368
6677
|
/** The reason for a failure, after any output: the model sees nothing else of `details`. */
|
|
6369
6678
|
const withStatus = (failure) => {
|
|
6370
|
-
const status = failure._tag === "BashTimedOut" ? `Command timed out after ${failure.timeoutSeconds} seconds` : failure.exitCode === -1 ? "Command terminated without an exit code" : `Command exited with code ${failure.exitCode}`;
|
|
6679
|
+
const status = failure._tag === "BashTimedOut" ? `Command timed out after ${failure.timeoutSeconds} seconds` : failure.exitCode === -1 ? "Command terminated without an exit code" : `Command exited with code ${failure.exitCode}${failure.shell === "sh" ? " (ran under sh: bash is not on PATH in this sandbox)" : ""}`;
|
|
6371
6680
|
return failure.output ? `${failure.output.replace(/\n$/, "")}\n\n${status}` : status;
|
|
6372
6681
|
};
|
|
6373
6682
|
const bashDef = tool_exports.define({
|
|
@@ -6421,6 +6730,7 @@ const present = (snap, lastLineBytes, saved) => {
|
|
|
6421
6730
|
};
|
|
6422
6731
|
};
|
|
6423
6732
|
const utf8 = new TextEncoder();
|
|
6733
|
+
const shellOf = (report) => report !== void 0 ? { shell: report.shell } : {};
|
|
6424
6734
|
/**
|
|
6425
6735
|
* A command cut off by its deadline never reached the wrapper's own cleanup: kill
|
|
6426
6736
|
* what is left of it and remove its side files, keeping the output file only when
|
|
@@ -6454,7 +6764,8 @@ const runBuffered = (shell, params, file) => Effect.gen(function* () {
|
|
|
6454
6764
|
const presented = present(acc.snapshot(), acc.getLastLineBytes(), window$1?.kept === true ? file : void 0);
|
|
6455
6765
|
if (result.exitCode !== 0) return yield* new BashFailed({
|
|
6456
6766
|
exitCode: result.exitCode,
|
|
6457
|
-
...presented
|
|
6767
|
+
...presented,
|
|
6768
|
+
...shellOf(window$1)
|
|
6458
6769
|
});
|
|
6459
6770
|
return {
|
|
6460
6771
|
exitCode: result.exitCode,
|
|
@@ -6512,7 +6823,8 @@ const runStreaming = (shell, stream, params, file) => Effect.gen(function* () {
|
|
|
6512
6823
|
const code = yield* Ref.get(exitCode);
|
|
6513
6824
|
if (code === null || code !== 0) return yield* new BashFailed({
|
|
6514
6825
|
exitCode: code ?? -1,
|
|
6515
|
-
...presented
|
|
6826
|
+
...presented,
|
|
6827
|
+
...shellOf(report$1)
|
|
6516
6828
|
});
|
|
6517
6829
|
return {
|
|
6518
6830
|
exitCode: code,
|
|
@@ -7282,7 +7594,7 @@ const pipeline = (argv, limit, errors) => [
|
|
|
7282
7594
|
].join("; ");
|
|
7283
7595
|
const STATUS = /^\{"type":"rc","code":(\d+)\}$/;
|
|
7284
7596
|
/** The pipeline's output: match events, ripgrep's status, and anything else, which is its stderr. */
|
|
7285
|
-
const parse$
|
|
7597
|
+
const parse$3 = (stdout, stderr) => {
|
|
7286
7598
|
const matches = [];
|
|
7287
7599
|
const errors = [];
|
|
7288
7600
|
let status;
|
|
@@ -7366,7 +7678,7 @@ const grepHandler = (params) => Effect.gen(function* () {
|
|
|
7366
7678
|
if (params.glob) argv.push("--glob", params.glob);
|
|
7367
7679
|
argv.push("-e", params.pattern, searchPath);
|
|
7368
7680
|
const result = yield* shell.exec(pipeline(argv, limit, errors));
|
|
7369
|
-
const parsed = parse$
|
|
7681
|
+
const parsed = parse$3(result.stdout, result.stderr);
|
|
7370
7682
|
return {
|
|
7371
7683
|
...parsed,
|
|
7372
7684
|
exitCode: parsed.status ?? result.exitCode
|
|
@@ -7931,7 +8243,8 @@ const builtins = Object.freeze([
|
|
|
7931
8243
|
findPlugin,
|
|
7932
8244
|
lsPlugin,
|
|
7933
8245
|
defaultPromptPlugin,
|
|
7934
|
-
instructionPlugin
|
|
8246
|
+
instructionPlugin,
|
|
8247
|
+
skillPlugin
|
|
7935
8248
|
]);
|
|
7936
8249
|
//#endregion
|
|
7937
8250
|
//#region src/plugin/error.ts
|
|
@@ -8371,7 +8684,7 @@ const isPath = (reference) => reference.startsWith("file:") || reference.startsW
|
|
|
8371
8684
|
* - a **CLI argument** anchors to the process's startup directory, because that is where the
|
|
8372
8685
|
* person typed it.
|
|
8373
8686
|
*/
|
|
8374
|
-
const parse$
|
|
8687
|
+
const parse$2 = (reference, from) => Effect.gen(function* () {
|
|
8375
8688
|
const fail = (reason, message) => new SourceError({
|
|
8376
8689
|
reason,
|
|
8377
8690
|
reference,
|
|
@@ -9047,7 +9360,7 @@ const inspect = Effect.fn("PluginLoader.inspect")(function* (reference, options)
|
|
|
9047
9360
|
reference,
|
|
9048
9361
|
file: options.file
|
|
9049
9362
|
};
|
|
9050
|
-
const target = yield* parse$
|
|
9363
|
+
const target = yield* parse$2(reference, options.hostDir);
|
|
9051
9364
|
const loaded = yield* load$2(target, origin, options);
|
|
9052
9365
|
return {
|
|
9053
9366
|
id: loaded.plugin.id,
|
|
@@ -9073,7 +9386,7 @@ const load$2 = Effect.fn("PluginLoader.load")(function* (target, origin, options
|
|
|
9073
9386
|
* are one package, and `./plugins/x.ts` is the file it resolves to from the directory that
|
|
9074
9387
|
* declared it. Pure -- nothing is installed, imported or read.
|
|
9075
9388
|
*/
|
|
9076
|
-
const canonical = (reference, hostDir) => Effect.map(parse$
|
|
9389
|
+
const canonical = (reference, hostDir) => Effect.map(parse$2(reference, hostDir), canonical$1);
|
|
9077
9390
|
//#endregion
|
|
9078
9391
|
//#region src/plugin/catalog.ts
|
|
9079
9392
|
/**
|
|
@@ -9135,7 +9448,7 @@ const load$1 = Effect.fn("PluginCatalog.load")(function* (references, options) {
|
|
|
9135
9448
|
remember(plugin, []);
|
|
9136
9449
|
continue;
|
|
9137
9450
|
}
|
|
9138
|
-
const target = yield* parse$
|
|
9451
|
+
const target = yield* parse$2(reference, options.hostDir);
|
|
9139
9452
|
const key = target.kind === "local" ? target.path : target.spec;
|
|
9140
9453
|
const already = seen.get(key);
|
|
9141
9454
|
if (already !== void 0) {
|
|
@@ -11318,7 +11631,7 @@ var harness_exports = /* @__PURE__ */ __exportAll({
|
|
|
11318
11631
|
const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
11319
11632
|
const hostCwd = options.hostCwd ?? process.cwd();
|
|
11320
11633
|
const paths = yield* resolve$4(options.home === void 0 ? {} : { home: hostPath.resolve(hostCwd, expandTilde(options.home, hostPath)) });
|
|
11321
|
-
const global = layerWith(paths);
|
|
11634
|
+
const global = layerWith$1(paths);
|
|
11322
11635
|
const settingsOptions = options.userConfigDir === void 0 ? {} : { userConfigDir: userConfigDir(options.userConfigDir, hostCwd) };
|
|
11323
11636
|
const config = yield* load$3({
|
|
11324
11637
|
...settingsOptions,
|
|
@@ -11337,7 +11650,7 @@ const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
11337
11650
|
* `hostDir` led to; only the process view falls back to `hostCwd`.
|
|
11338
11651
|
*/
|
|
11339
11652
|
const hostDirOf = (root) => root ?? hostCwd;
|
|
11340
|
-
const filed = (reference, from) => parse$
|
|
11653
|
+
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()));
|
|
11341
11654
|
const resolveOnly = {
|
|
11342
11655
|
...catalogOptions,
|
|
11343
11656
|
install: required
|
|
@@ -11579,13 +11892,24 @@ const makeHandle = Effect.fn("Session.makeHandle")(function* (id) {
|
|
|
11579
11892
|
const space = Option.getOrUndefined(yield* sessions.space(id));
|
|
11580
11893
|
const sandbox = space === void 0 || space.env === instance_exports.SandboxInstance.ID.local ? void 0 : Option.getOrUndefined(yield* sandboxes.get(space.env));
|
|
11581
11894
|
const hostDir = Option.getOrUndefined(row.hostDir);
|
|
11895
|
+
const project = Option.getOrUndefined(yield* sessions.project(id));
|
|
11896
|
+
const config = Option.getOrUndefined(row.config);
|
|
11897
|
+
const model = config?.model;
|
|
11898
|
+
const thinkingLevel = config?.thinkingLevel;
|
|
11582
11899
|
return {
|
|
11583
11900
|
id,
|
|
11584
11901
|
title: row.title,
|
|
11585
11902
|
directory: row.directory,
|
|
11586
11903
|
...hostDir === void 0 ? {} : { hostDir },
|
|
11587
11904
|
hasHostLink: hostDir !== void 0,
|
|
11588
|
-
...sandbox === void 0 ? {} : { sandbox }
|
|
11905
|
+
...sandbox === void 0 ? {} : { sandbox },
|
|
11906
|
+
...project === void 0 ? {} : { project: {
|
|
11907
|
+
id: project.id,
|
|
11908
|
+
name: project.name
|
|
11909
|
+
} },
|
|
11910
|
+
updatedAt: DateTime.toEpochMillis(row.updatedAt),
|
|
11911
|
+
...model === void 0 ? {} : { model },
|
|
11912
|
+
...thinkingLevel === void 0 ? {} : { thinkingLevel }
|
|
11589
11913
|
};
|
|
11590
11914
|
}).pipe(Effect.withSpan("Session.info"));
|
|
11591
11915
|
return {
|
|
@@ -11613,7 +11937,7 @@ const create$1 = Effect.fn("Session.create")(function* (input = {}) {
|
|
|
11613
11937
|
const sandboxes = yield* Controller;
|
|
11614
11938
|
const id = ID$1.create();
|
|
11615
11939
|
const sandboxId = input.sandbox?.id ?? instance_exports.SandboxInstance.ID.local;
|
|
11616
|
-
const location = yield* sandboxes.withMount(sandboxId, Effect.provide(Service$8.use(Effect.succeed), layerMounted()), input.directory === void 0 ? void 0 : { cwd: input.directory });
|
|
11940
|
+
const location = yield* sandboxes.withMount(sandboxId, Effect.provide(Service$8.use(Effect.succeed), layerMounted(input.project === void 0 ? void 0 : { name: input.project.name })), input.directory === void 0 ? void 0 : { cwd: input.directory });
|
|
11617
11941
|
yield* sessions.create({
|
|
11618
11942
|
id,
|
|
11619
11943
|
spaceId: location.space.id,
|
|
@@ -11727,7 +12051,7 @@ var plugin_exports = /* @__PURE__ */ __exportAll({
|
|
|
11727
12051
|
inspect: () => inspect,
|
|
11728
12052
|
lendResolve: () => lendResolve,
|
|
11729
12053
|
localName: () => localName,
|
|
11730
|
-
parse: () => parse$
|
|
12054
|
+
parse: () => parse$2,
|
|
11731
12055
|
probe: () => probe,
|
|
11732
12056
|
resolve: () => resolve$2,
|
|
11733
12057
|
resolveCached: () => required,
|
|
@@ -11740,6 +12064,6 @@ var DateTimeUtcFromMillis = schema_exports$9.DateTimeUtcFromMillis;
|
|
|
11740
12064
|
var EventSchema = schema_exports$7.EventSchema;
|
|
11741
12065
|
var SandboxError = errors_exports.SandboxError;
|
|
11742
12066
|
var optional = schema_exports$9.optional;
|
|
11743
|
-
export { control_exports as Control, db_exports as Database, DateTimeUtcFromMillis, event_exports as Event, list_exports as EventList, registry_exports as EventRegistry, EventSchema, global_exports as Global, harness_exports as Harness, location_exports as Location, catalog_exports as ModelCatalog, plugin_exports as Plugin, schema_exports as PromptSchema, run_exports as Runner, sandbox_exports as Sandbox, SandboxError, session_exports as Session, session_exports$1 as SessionStore, settings_exports as Settings, state_exports as State, tool_exports as Tool, optional };
|
|
12067
|
+
export { control_exports as Control, db_exports as Database, DateTimeUtcFromMillis, event_exports as Event, list_exports as EventList, registry_exports as EventRegistry, EventSchema, global_exports as Global, harness_exports as Harness, location_exports as Location, catalog_exports as ModelCatalog, plugin_exports as Plugin, project_exports as Project, schema_exports as PromptSchema, run_exports as Runner, sandbox_exports as Sandbox, SandboxError, session_exports as Session, session_exports$1 as SessionStore, settings_exports as Settings, state_exports as State, tool_exports as Tool, optional };
|
|
11744
12068
|
|
|
11745
12069
|
//# sourceMappingURL=effect.mjs.map
|