@codeworksh/harness 0.3.0 → 0.4.0-canary.52.1.ge7df22a4
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 +1275 -147
- package/effect.mjs +692 -97
- 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";
|
|
@@ -42,6 +44,7 @@ var schema_exports$8 = /* @__PURE__ */ __exportAll({
|
|
|
42
44
|
CompactionData: () => CompactionData,
|
|
43
45
|
Config: () => Config$1,
|
|
44
46
|
DEFAULT_TITLE: () => DEFAULT_TITLE,
|
|
47
|
+
Execution: () => Execution,
|
|
45
48
|
ID: () => ID$1,
|
|
46
49
|
IDFromDb: () => IDFromDb,
|
|
47
50
|
InterruptReason: () => InterruptReason,
|
|
@@ -86,6 +89,12 @@ const Config$1 = Schema.Struct({
|
|
|
86
89
|
* different facts, and inspecting the cause cannot tell them apart.
|
|
87
90
|
*/
|
|
88
91
|
const InterruptReason = Schema.Literals(["user", "shutdown"]);
|
|
92
|
+
/**
|
|
93
|
+
* A run left unfinished: `running` while one is in flight, and still after the process died during
|
|
94
|
+
* it; `closed` once a shutdown stopped it. Either resumes when the server starts again; a run that
|
|
95
|
+
* ended any other way leaves none.
|
|
96
|
+
*/
|
|
97
|
+
const Execution = Schema.Literals(["running", "closed"]);
|
|
89
98
|
const JsonObject = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown));
|
|
90
99
|
const MessageEnvelopeIdentity = Schema.fromJsonString(Schema.Struct({ messageId: Schema.String }));
|
|
91
100
|
const CompactionData = Schema.fromJsonString(Schema.Struct({
|
|
@@ -134,10 +143,10 @@ var global_exports = /* @__PURE__ */ __exportAll({
|
|
|
134
143
|
app: () => app,
|
|
135
144
|
appConfigDir: () => appConfigDir,
|
|
136
145
|
authFile: () => authFile,
|
|
137
|
-
defaultLayer: () => defaultLayer$
|
|
146
|
+
defaultLayer: () => defaultLayer$4,
|
|
138
147
|
homeConfig: () => homeConfig,
|
|
139
148
|
layer: () => layer$27,
|
|
140
|
-
layerWith: () => layerWith,
|
|
149
|
+
layerWith: () => layerWith$1,
|
|
141
150
|
make: () => make$18,
|
|
142
151
|
resolve: () => resolve$4
|
|
143
152
|
});
|
|
@@ -184,8 +193,8 @@ const build = (input) => Effect.gen(function* () {
|
|
|
184
193
|
return Service$17.of(paths);
|
|
185
194
|
});
|
|
186
195
|
const layer$27 = Layer.effect(Service$17, build({}));
|
|
187
|
-
const defaultLayer$
|
|
188
|
-
const layerWith = (input) => Layer.effect(Service$17, build(input));
|
|
196
|
+
const defaultLayer$4 = layer$27;
|
|
197
|
+
const layerWith$1 = (input) => Layer.effect(Service$17, build(input));
|
|
189
198
|
//#endregion
|
|
190
199
|
//#region src/db/migrations.ts
|
|
191
200
|
const migrations = {
|
|
@@ -366,6 +375,14 @@ const migrations = {
|
|
|
366
375
|
}),
|
|
367
376
|
"202609290001_session_config": Effect.gen(function* () {
|
|
368
377
|
yield* (yield* SqlClient.SqlClient)`ALTER TABLE session ADD COLUMN config TEXT`;
|
|
378
|
+
}),
|
|
379
|
+
"202610230001_session_input_cancel": Effect.gen(function* () {
|
|
380
|
+
yield* (yield* SqlClient.SqlClient)`ALTER TABLE session_input ADD COLUMN cancelled_seq INTEGER`;
|
|
381
|
+
}),
|
|
382
|
+
"202610240001_session_execution": Effect.gen(function* () {
|
|
383
|
+
const sql = yield* SqlClient.SqlClient;
|
|
384
|
+
yield* sql`ALTER TABLE session ADD COLUMN execution TEXT`;
|
|
385
|
+
yield* sql`ALTER TABLE session ADD COLUMN restarts INTEGER NOT NULL DEFAULT 0`;
|
|
369
386
|
})
|
|
370
387
|
};
|
|
371
388
|
//#endregion
|
|
@@ -374,7 +391,7 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
374
391
|
Database: () => db_exports,
|
|
375
392
|
SqlClient: () => SqlClient$1,
|
|
376
393
|
SqlSchema: () => SqlSchema$1,
|
|
377
|
-
defaultLayer: () => defaultLayer$
|
|
394
|
+
defaultLayer: () => defaultLayer$3,
|
|
378
395
|
layer: () => layer$26,
|
|
379
396
|
locationConfig: () => locationConfig,
|
|
380
397
|
path: () => path$1,
|
|
@@ -408,7 +425,7 @@ const path$1 = Effect.fn("Database.path")(function* (data) {
|
|
|
408
425
|
const directory = data ?? (yield* resolve$4()).data;
|
|
409
426
|
return resolveDatabaseLocation(yield* locationConfig, directory);
|
|
410
427
|
});
|
|
411
|
-
const defaultLayer$
|
|
428
|
+
const defaultLayer$3 = Layer.unwrap(path$1().pipe(Effect.map(layer$26)));
|
|
412
429
|
//#endregion
|
|
413
430
|
//#region src/event/schema.ts
|
|
414
431
|
var schema_exports$7 = /* @__PURE__ */ __exportAll({});
|
|
@@ -589,6 +606,8 @@ var SessionRow = class extends Model$1.Class("SessionRow")({
|
|
|
589
606
|
tokensOutput: Model$1.GeneratedByDb(Schema.Int),
|
|
590
607
|
tokensCacheRead: Model$1.GeneratedByDb(Schema.Int),
|
|
591
608
|
tokensCacheWrite: Model$1.GeneratedByDb(Schema.Int),
|
|
609
|
+
execution: Model$1.FieldOption(Execution),
|
|
610
|
+
restarts: Model$1.GeneratedByDb(Schema.Int),
|
|
592
611
|
...Timestamps
|
|
593
612
|
}) {};
|
|
594
613
|
var SessionInputRow = class extends Model$1.Class("SessionInputRow")({
|
|
@@ -598,6 +617,7 @@ var SessionInputRow = class extends Model$1.Class("SessionInputRow")({
|
|
|
598
617
|
delivery: Schema.Literals(inputDeliveries),
|
|
599
618
|
admittedSeq: Schema.Int,
|
|
600
619
|
promotedSeq: Model$1.FieldOption(Schema.Int),
|
|
620
|
+
cancelledSeq: Model$1.FieldOption(Schema.Int),
|
|
601
621
|
createdAt: Model$1.DateTimeInsertFromNumber
|
|
602
622
|
}) {};
|
|
603
623
|
var SessionEntryRow = class extends Model$1.Class("SessionEntryRow")({
|
|
@@ -729,6 +749,8 @@ var list_exports = /* @__PURE__ */ __exportAll({
|
|
|
729
749
|
LLMThinkingStart: () => LLMThinkingStart,
|
|
730
750
|
PluginUpdated: () => PluginUpdated,
|
|
731
751
|
PromptAdmitted: () => PromptAdmitted,
|
|
752
|
+
PromptCancelled: () => PromptCancelled,
|
|
753
|
+
PromptUpdated: () => PromptUpdated,
|
|
732
754
|
Prompted: () => Prompted,
|
|
733
755
|
PublicDefinitions: () => PublicDefinitions,
|
|
734
756
|
RetryFinished: () => RetryFinished,
|
|
@@ -766,6 +788,29 @@ const Prompted = schema_exports$7.EventSchema.define({
|
|
|
766
788
|
...durableOptions,
|
|
767
789
|
schema: PromptFields
|
|
768
790
|
});
|
|
791
|
+
/** A pending prompt withdrawn before promotion; it never enters the conversation. */
|
|
792
|
+
const PromptCancelled = schema_exports$7.EventSchema.define({
|
|
793
|
+
type: "session.prompt.cancelled",
|
|
794
|
+
...durableOptions,
|
|
795
|
+
schema: {
|
|
796
|
+
...baseOptions,
|
|
797
|
+
messageId: ID
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
/**
|
|
801
|
+
* A pending prompt moved to the other lane or rewritten before promotion; it keeps its place. Only
|
|
802
|
+
* what changed is carried, so two updates of different fields both stand.
|
|
803
|
+
*/
|
|
804
|
+
const PromptUpdated = schema_exports$7.EventSchema.define({
|
|
805
|
+
type: "session.prompt.updated",
|
|
806
|
+
...durableOptions,
|
|
807
|
+
schema: {
|
|
808
|
+
...baseOptions,
|
|
809
|
+
messageId: ID,
|
|
810
|
+
prompt: Prompt.pipe(schema_exports$9.optional),
|
|
811
|
+
delivery: Delivery.pipe(schema_exports$9.optional)
|
|
812
|
+
}
|
|
813
|
+
});
|
|
769
814
|
const ConfigChanged = schema_exports$7.EventSchema.define({
|
|
770
815
|
type: "session.config.changed",
|
|
771
816
|
...durableOptions,
|
|
@@ -1000,14 +1045,14 @@ const PluginUpdated = schema_exports$7.EventSchema.define({
|
|
|
1000
1045
|
* Everything the kernel can publish. `PublicDefinitions` is the narrower set
|
|
1001
1046
|
* that reaches clients -- adding an event here does not publish it.
|
|
1002
1047
|
*/
|
|
1003
|
-
const Definitions = schema_exports$7.EventSchema.inventory(ExecutionStarted, ExecutionSucceeded, ExecutionFailed, ExecutionInterrupted, PromptAdmitted, Prompted, ConfigChanged, SessionForked, TurnStarted, TurnEnded, TurnAborted, RetryScheduled, RetryFinished, LLMStarted, LLMTextStart, LLMTextDelta, LLMTextEnd, LLMThinkingStart, LLMThinkingDelta, LLMThinkingEnd, LLMEnded, LLMFailed, ToolStarted, ToolProgress$2, ToolSettled, PluginUpdated);
|
|
1048
|
+
const Definitions = schema_exports$7.EventSchema.inventory(ExecutionStarted, ExecutionSucceeded, ExecutionFailed, ExecutionInterrupted, PromptAdmitted, Prompted, PromptCancelled, PromptUpdated, ConfigChanged, SessionForked, TurnStarted, TurnEnded, TurnAborted, RetryScheduled, RetryFinished, LLMStarted, LLMTextStart, LLMTextDelta, LLMTextEnd, LLMThinkingStart, LLMThinkingDelta, LLMThinkingEnd, LLMEnded, LLMFailed, ToolStarted, ToolProgress$2, ToolSettled, PluginUpdated);
|
|
1004
1049
|
/**
|
|
1005
1050
|
* The wire contract: what a client is allowed to receive. Identical to
|
|
1006
1051
|
* `Definitions` today, and separate so that adding a kernel event is not by
|
|
1007
1052
|
* itself a decision to publish it.
|
|
1008
1053
|
*/
|
|
1009
1054
|
const PublicDefinitions = Definitions;
|
|
1010
|
-
const DurableDefinitions = schema_exports$7.EventSchema.inventory(PromptAdmitted, Prompted, ConfigChanged, SessionForked, TurnEnded, LLMStarted, LLMEnded, LLMFailed, ToolSettled);
|
|
1055
|
+
const DurableDefinitions = schema_exports$7.EventSchema.inventory(PromptAdmitted, Prompted, PromptCancelled, PromptUpdated, ConfigChanged, SessionForked, TurnEnded, LLMStarted, LLMEnded, LLMFailed, ToolSettled);
|
|
1011
1056
|
const Manifest$2 = {
|
|
1012
1057
|
definitions: schema_exports$7.EventSchema.durable([...DurableDefinitions]),
|
|
1013
1058
|
schema: Schema.Union([...DurableDefinitions], { mode: "oneOf" }).pipe(Schema.toTaggedUnion("type"))
|
|
@@ -1579,7 +1624,7 @@ var session_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
1579
1624
|
SessionNotLinkedError: () => SessionNotLinkedError,
|
|
1580
1625
|
SessionRow: () => SessionRow,
|
|
1581
1626
|
ToolCallNotFoundError: () => ToolCallNotFoundError,
|
|
1582
|
-
defaultLayer: () => defaultLayer,
|
|
1627
|
+
defaultLayer: () => defaultLayer$2,
|
|
1583
1628
|
entryStates: () => entryStates,
|
|
1584
1629
|
entryTypes: () => entryTypes,
|
|
1585
1630
|
layer: () => layer$23,
|
|
@@ -1663,6 +1708,16 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
1663
1708
|
SELECT p.* FROM space p JOIN session s ON s.space_id = p.id WHERE s.id = ${sessionId}
|
|
1664
1709
|
`
|
|
1665
1710
|
});
|
|
1711
|
+
const findProject = SqlSchema.findOneOption({
|
|
1712
|
+
Request: Schema.String,
|
|
1713
|
+
Result: ProjectRow,
|
|
1714
|
+
execute: (sessionId) => sql`
|
|
1715
|
+
SELECT p.* FROM project p
|
|
1716
|
+
JOIN space sp ON sp.project_id = p.id
|
|
1717
|
+
JOIN session s ON s.space_id = sp.id
|
|
1718
|
+
WHERE s.id = ${sessionId}
|
|
1719
|
+
`
|
|
1720
|
+
});
|
|
1666
1721
|
const findSpaceById = SqlSchema.findOneOption({
|
|
1667
1722
|
Request: Schema.String,
|
|
1668
1723
|
Result: SpaceRow,
|
|
@@ -1795,6 +1850,7 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
1795
1850
|
directory: input.directory,
|
|
1796
1851
|
hostDir: Option.fromUndefinedOr(input.hostDir),
|
|
1797
1852
|
config: Option.none(),
|
|
1853
|
+
execution: Option.none(),
|
|
1798
1854
|
title: input.title,
|
|
1799
1855
|
tag: Option.fromUndefinedOr(input.tag),
|
|
1800
1856
|
metadata: Option.fromUndefinedOr(input.metadata),
|
|
@@ -1811,6 +1867,13 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
1811
1867
|
const space = Effect.fn("Session.space")(function* (sessionId) {
|
|
1812
1868
|
return Option.map(yield* findSpace(sessionId).pipe(Effect.orDie), fromRow$1);
|
|
1813
1869
|
});
|
|
1870
|
+
const project = Effect.fn("Session.project")(function* (sessionId) {
|
|
1871
|
+
return Option.map(yield* findProject(sessionId).pipe(Effect.orDie), (row) => new schema_exports$6.ProjectSchema.Info({
|
|
1872
|
+
id: schema_exports$6.ProjectSchema.ID.make(row.id),
|
|
1873
|
+
name: row.name,
|
|
1874
|
+
status: row.status
|
|
1875
|
+
}));
|
|
1876
|
+
});
|
|
1814
1877
|
const list = Effect.fn("Session.list")(function* (input) {
|
|
1815
1878
|
return yield* (input === void 0 ? selectAll() : "spaceId" in input ? selectBySpace(input.spaceId) : selectByProject(input.projectId)).pipe(Effect.orDie);
|
|
1816
1879
|
});
|
|
@@ -2141,6 +2204,7 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2141
2204
|
directory: source.value.directory,
|
|
2142
2205
|
hostDir: source.value.hostDir,
|
|
2143
2206
|
config: source.value.config,
|
|
2207
|
+
execution: Option.none(),
|
|
2144
2208
|
title: input.title ?? source.value.title,
|
|
2145
2209
|
tag: input.tag === void 0 ? source.value.tag : Option.some(input.tag),
|
|
2146
2210
|
metadata: source.value.metadata,
|
|
@@ -2250,6 +2314,41 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2250
2314
|
WHERE id = ${sessionId}
|
|
2251
2315
|
`;
|
|
2252
2316
|
}, Effect.orDie);
|
|
2317
|
+
const markRunning = Effect.fn("Session.markRunning")(function* (sessionId) {
|
|
2318
|
+
yield* sql`UPDATE session SET execution = 'running' WHERE id = ${sessionId}`;
|
|
2319
|
+
}, Effect.orDie);
|
|
2320
|
+
const endExecution = Effect.fn("Session.endExecution")(function* (sessionId, end, stoppedAt) {
|
|
2321
|
+
if (end === "closed") {
|
|
2322
|
+
yield* sql`UPDATE session SET execution = 'closed' WHERE id = ${sessionId}`;
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
const after = end === "stopped" ? stoppedAt ?? Number.MAX_SAFE_INTEGER : -1;
|
|
2326
|
+
yield* sql`
|
|
2327
|
+
UPDATE session SET execution = NULL, restarts = 0
|
|
2328
|
+
WHERE id = ${sessionId} AND NOT EXISTS (
|
|
2329
|
+
SELECT 1 FROM session_input
|
|
2330
|
+
WHERE session_id = ${sessionId} AND promoted_seq IS NULL AND cancelled_seq IS NULL
|
|
2331
|
+
AND admitted_seq > ${after}
|
|
2332
|
+
)
|
|
2333
|
+
`;
|
|
2334
|
+
}, Effect.orDie);
|
|
2335
|
+
const claimRestart = Effect.fn("Session.claimRestart")(function* (sessionId) {
|
|
2336
|
+
const rows = yield* sql`
|
|
2337
|
+
UPDATE session
|
|
2338
|
+
SET restarts = restarts + (CASE WHEN execution = 'running' THEN 1 ELSE 0 END), execution = 'running'
|
|
2339
|
+
WHERE id = ${sessionId} AND execution IS NOT NULL
|
|
2340
|
+
RETURNING restarts
|
|
2341
|
+
`;
|
|
2342
|
+
return Option.fromUndefinedOr(rows[0]?.restarts);
|
|
2343
|
+
}, Effect.orDie);
|
|
2344
|
+
const resetRestarts = Effect.fn("Session.resetRestarts")(function* (sessionId) {
|
|
2345
|
+
yield* sql`UPDATE session SET restarts = 0 WHERE id = ${sessionId} AND restarts > 0`;
|
|
2346
|
+
}, Effect.orDie);
|
|
2347
|
+
const unfinished = SqlSchema.findAll({
|
|
2348
|
+
Request: Schema.Void,
|
|
2349
|
+
Result: SessionRow,
|
|
2350
|
+
execute: () => sql`SELECT * FROM session WHERE execution IS NOT NULL ORDER BY updated_at`
|
|
2351
|
+
})(void 0).pipe(Effect.orDie, Effect.withSpan("Session.unfinished"));
|
|
2253
2352
|
const relink = Effect.fn("Session.relink")(function* (input) {
|
|
2254
2353
|
const reject = (reason) => new RelinkError({
|
|
2255
2354
|
sessionId: input.sessionId,
|
|
@@ -2311,8 +2410,14 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2311
2410
|
link,
|
|
2312
2411
|
retitle,
|
|
2313
2412
|
configure,
|
|
2413
|
+
markRunning,
|
|
2414
|
+
endExecution,
|
|
2415
|
+
claimRestart,
|
|
2416
|
+
resetRestarts,
|
|
2417
|
+
unfinished,
|
|
2314
2418
|
get,
|
|
2315
2419
|
space,
|
|
2420
|
+
project,
|
|
2316
2421
|
list,
|
|
2317
2422
|
entry,
|
|
2318
2423
|
path,
|
|
@@ -2329,7 +2434,7 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2329
2434
|
setLabel
|
|
2330
2435
|
});
|
|
2331
2436
|
}));
|
|
2332
|
-
const defaultLayer = layer$23.pipe(Layer.provide(defaultLayer$
|
|
2437
|
+
const defaultLayer$2 = layer$23.pipe(Layer.provide(defaultLayer$3));
|
|
2333
2438
|
//#endregion
|
|
2334
2439
|
//#region src/context/errors.ts
|
|
2335
2440
|
var ContextDecodeError = class extends Schema.TaggedError()("ContextDecodeError", {
|
|
@@ -4274,6 +4379,17 @@ const make$9 = () => {
|
|
|
4274
4379
|
}) };
|
|
4275
4380
|
};
|
|
4276
4381
|
//#endregion
|
|
4382
|
+
//#region src/sandbox/sandbox.ts
|
|
4383
|
+
/**
|
|
4384
|
+
* Assemble a local backend into a sandbox with a **real host shell** — the
|
|
4385
|
+
* right choice whenever commands must invoke actual binaries (`git`, package
|
|
4386
|
+
* managers). For a sandbox whose shell should stay inside the VFS, use
|
|
4387
|
+
* `EnvBash.services` instead, which wires just-bash over the same tree.
|
|
4388
|
+
*/
|
|
4389
|
+
const services = (backend, identity) => Layer.provideMerge(io_exports.SandboxIO.mount(identity), Layer.provideMerge(Layer.merge(layer$21, layer$18()), backend));
|
|
4390
|
+
/** Default sandbox: the real OS filesystem and processes, mounted at `cwd`. */
|
|
4391
|
+
const defaultLayer$1 = (cwd) => services(layer$20(), io_exports.SandboxIO.host(cwd));
|
|
4392
|
+
//#endregion
|
|
4277
4393
|
//#region src/git/git.ts
|
|
4278
4394
|
var AppProcessError = class extends Schema.TaggedError()("AppProcessError", {
|
|
4279
4395
|
command: Schema.String,
|
|
@@ -4464,7 +4580,8 @@ const up = Effect.fn("SandboxFileSystem.up")(function* (fs, options) {
|
|
|
4464
4580
|
const isDirectory$1 = Effect.fn("SandboxFileSystem.isDirectory")(function* (fs, path) {
|
|
4465
4581
|
return yield* fs.stat(path).pipe(Effect.map((stat) => stat.isDirectory), Effect.orElseSucceed(() => false));
|
|
4466
4582
|
});
|
|
4467
|
-
|
|
4583
|
+
/** True when `path` exists and is a regular file; false when it is absent or unreadable. */
|
|
4584
|
+
const isFile = Effect.fn("SandboxFileSystem.isFile")(function* (fs, path) {
|
|
4468
4585
|
return yield* fs.stat(path).pipe(Effect.map((stat) => stat.isFile), Effect.orElseSucceed(() => false));
|
|
4469
4586
|
});
|
|
4470
4587
|
/** Read a file, or `undefined` when it is missing or unreadable. */
|
|
@@ -4611,6 +4728,16 @@ const layer$13 = Layer.effect(Service$10, Effect.gen(function* () {
|
|
|
4611
4728
|
}));
|
|
4612
4729
|
//#endregion
|
|
4613
4730
|
//#region src/project/project.ts
|
|
4731
|
+
var project_exports = /* @__PURE__ */ __exportAll({
|
|
4732
|
+
Project: () => project_exports,
|
|
4733
|
+
ProjectNotFoundError: () => ProjectNotFoundError,
|
|
4734
|
+
Service: () => Service$9,
|
|
4735
|
+
defaultLayer: () => defaultLayer,
|
|
4736
|
+
layer: () => layer$12,
|
|
4737
|
+
layerWith: () => layerWith,
|
|
4738
|
+
provisional: () => provisional,
|
|
4739
|
+
rename: () => rename
|
|
4740
|
+
});
|
|
4614
4741
|
/** Deterministic, env-local id for a directory without a portable identity. Never written to a marker. */
|
|
4615
4742
|
const provisional = (env, location) => schema_exports$6.ProjectSchema.ID.make(fast(JSON.stringify([
|
|
4616
4743
|
"dir",
|
|
@@ -4664,7 +4791,7 @@ const layer$12 = Layer.effect(Service$9, Effect.gen(function* () {
|
|
|
4664
4791
|
main: repo.gitDir === repo.store
|
|
4665
4792
|
}]).toSorted((a, b) => Number(b.main) - Number(a.main));
|
|
4666
4793
|
});
|
|
4667
|
-
const resolveOrCreate = Effect.fn("Project.resolveOrCreate")(function* (cwd) {
|
|
4794
|
+
const resolveOrCreate = Effect.fn("Project.resolveOrCreate")(function* (cwd, options) {
|
|
4668
4795
|
if (!(yield* fs.exists(cwd).pipe(Effect.orDie))) return yield* new DirectoryNotFoundError$1({
|
|
4669
4796
|
directory: cwd,
|
|
4670
4797
|
sandboxInstanceId: env
|
|
@@ -4678,7 +4805,7 @@ const layer$12 = Layer.effect(Service$9, Effect.gen(function* () {
|
|
|
4678
4805
|
const ident = repo === void 0 ? void 0 : yield* repos.identity(repo);
|
|
4679
4806
|
const worktree = repo?.directory ?? location;
|
|
4680
4807
|
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);
|
|
4808
|
+
const name = options?.name ?? ident?.name ?? posix.basename(worktree);
|
|
4682
4809
|
const vcs = repo === void 0 ? void 0 : {
|
|
4683
4810
|
type: "git",
|
|
4684
4811
|
store: repo.store
|
|
@@ -4750,6 +4877,32 @@ const layer$12 = Layer.effect(Service$9, Effect.gen(function* () {
|
|
|
4750
4877
|
list
|
|
4751
4878
|
});
|
|
4752
4879
|
}));
|
|
4880
|
+
var ProjectNotFoundError = class extends Schema.TaggedError()("ProjectNotFoundError", { projectId: Schema.String }) {};
|
|
4881
|
+
/** Renames a project. Needs only the database, not a mount: a name is the same in every env. */
|
|
4882
|
+
const rename = Effect.fn("Project.rename")(function* (input) {
|
|
4883
|
+
const sql = yield* SqlClient.SqlClient;
|
|
4884
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
4885
|
+
const row = yield* SqlSchema.findOneOption({
|
|
4886
|
+
Request: Schema.Struct({
|
|
4887
|
+
id: Schema.String,
|
|
4888
|
+
name: Schema.String
|
|
4889
|
+
}),
|
|
4890
|
+
Result: ProjectRow,
|
|
4891
|
+
execute: ({ id, name }) => sql`UPDATE project SET name = ${name}, updated_at = ${now} WHERE id = ${id} RETURNING *`
|
|
4892
|
+
})({
|
|
4893
|
+
id: input.projectId,
|
|
4894
|
+
name: input.name
|
|
4895
|
+
}).pipe(Effect.orDie);
|
|
4896
|
+
if (Option.isNone(row)) return yield* new ProjectNotFoundError({ projectId: input.projectId });
|
|
4897
|
+
return new schema_exports$6.ProjectSchema.Info({
|
|
4898
|
+
id: schema_exports$6.ProjectSchema.ID.make(row.value.id),
|
|
4899
|
+
name: row.value.name,
|
|
4900
|
+
status: row.value.status
|
|
4901
|
+
});
|
|
4902
|
+
});
|
|
4903
|
+
/** Takes an assembled sandbox — local or remote — not a local backend. */
|
|
4904
|
+
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));
|
|
4905
|
+
const defaultLayer = (path) => layerWith(defaultLayer$1(path));
|
|
4753
4906
|
//#endregion
|
|
4754
4907
|
//#region src/location/location.ts
|
|
4755
4908
|
var location_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -4761,10 +4914,10 @@ var location_exports = /* @__PURE__ */ __exportAll({
|
|
|
4761
4914
|
layer: () => layer$11,
|
|
4762
4915
|
layerMounted: () => layerMounted
|
|
4763
4916
|
});
|
|
4764
|
-
const layer$11 = () => Layer.effect(Service, Effect.gen(function* () {
|
|
4917
|
+
const layer$11 = (options) => Layer.effect(Service, Effect.gen(function* () {
|
|
4765
4918
|
const instance = yield* io_exports.SandboxIO.Current;
|
|
4766
4919
|
const directory = schema_exports$9.AbsolutePath.make(instance.cwd);
|
|
4767
|
-
const resolved = yield* (yield* Service$9).resolveOrCreate(directory);
|
|
4920
|
+
const resolved = yield* (yield* Service$9).resolveOrCreate(directory, options);
|
|
4768
4921
|
return Service.of(new Info({
|
|
4769
4922
|
directory: resolved.directory,
|
|
4770
4923
|
space: resolved.space,
|
|
@@ -4776,7 +4929,7 @@ const layer$11 = () => Layer.effect(Service, Effect.gen(function* () {
|
|
|
4776
4929
|
* folded in. What remains is exactly the mount and the database — for callers
|
|
4777
4930
|
* that already hold both, such as the runner and `Session.create`.
|
|
4778
4931
|
*/
|
|
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))));
|
|
4932
|
+
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
4933
|
//#endregion
|
|
4781
4934
|
//#region src/runner/run.ts
|
|
4782
4935
|
var run_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -4871,7 +5024,6 @@ var ProviderError = class extends Schema.TaggedError()("Runner.ProviderError", {
|
|
|
4871
5024
|
var ModelCatalogError = class extends Schema.TaggedError()("Runner.ModelCatalogError", {
|
|
4872
5025
|
path: Schema.String,
|
|
4873
5026
|
reason: Schema.Literals([
|
|
4874
|
-
"missing",
|
|
4875
5027
|
"unreadable",
|
|
4876
5028
|
"empty",
|
|
4877
5029
|
"invalid"
|
|
@@ -4905,6 +5057,7 @@ var Service$6 = class extends Context.Service()("@codeworksh/harness/runner/exec
|
|
|
4905
5057
|
Layer.succeed(Service$6, Service$6.of({
|
|
4906
5058
|
active: Effect.succeed(/* @__PURE__ */ new Set()),
|
|
4907
5059
|
resume: () => Effect.void,
|
|
5060
|
+
recover: () => Effect.void,
|
|
4908
5061
|
wake: () => Effect.void,
|
|
4909
5062
|
interrupt: () => Effect.succeed(false),
|
|
4910
5063
|
awaitIdle: () => Effect.void
|
|
@@ -4918,7 +5071,8 @@ const Admitted = Schema.Struct({
|
|
|
4918
5071
|
prompt: Prompt,
|
|
4919
5072
|
delivery: Delivery,
|
|
4920
5073
|
timeCreated: schema_exports$9.DateTimeUtcFromMillis,
|
|
4921
|
-
promotedSeq: schema_exports$9.NonNegativeInt.pipe(schema_exports$9.optional)
|
|
5074
|
+
promotedSeq: schema_exports$9.NonNegativeInt.pipe(schema_exports$9.optional),
|
|
5075
|
+
cancelledSeq: schema_exports$9.NonNegativeInt.pipe(schema_exports$9.optional)
|
|
4922
5076
|
}).annotate({ identifier: "SessionInput.Admitted" });
|
|
4923
5077
|
//#endregion
|
|
4924
5078
|
//#region src/session/input/input.ts
|
|
@@ -4939,7 +5093,8 @@ const fromRow = (row) => Admitted.make({
|
|
|
4939
5093
|
prompt: row.prompt,
|
|
4940
5094
|
delivery: row.delivery,
|
|
4941
5095
|
timeCreated: row.createdAt,
|
|
4942
|
-
...Option.isNone(row.promotedSeq) ? {} : { promotedSeq: row.promotedSeq.value }
|
|
5096
|
+
...Option.isNone(row.promotedSeq) ? {} : { promotedSeq: row.promotedSeq.value },
|
|
5097
|
+
...Option.isNone(row.cancelledSeq) ? {} : { cancelledSeq: row.cancelledSeq.value }
|
|
4943
5098
|
});
|
|
4944
5099
|
/**
|
|
4945
5100
|
* Whether a stored input carries the prompt the caller believes it admitted.
|
|
@@ -4963,6 +5118,38 @@ const make$8 = Effect.gen(function* () {
|
|
|
4963
5118
|
INSERT INTO session_input ${sql.insert(row)}
|
|
4964
5119
|
ON CONFLICT(id) DO NOTHING
|
|
4965
5120
|
RETURNING id
|
|
5121
|
+
`
|
|
5122
|
+
});
|
|
5123
|
+
const updateRow = SqlSchema.findAll({
|
|
5124
|
+
Request: Schema.Struct({
|
|
5125
|
+
id: Schema.String,
|
|
5126
|
+
sessionId: Schema.String,
|
|
5127
|
+
prompt: Schema.NullOr(Schema.fromJsonString(Prompt)),
|
|
5128
|
+
delivery: Schema.NullOr(Delivery)
|
|
5129
|
+
}),
|
|
5130
|
+
Result: Schema.Struct({ id: Schema.String }),
|
|
5131
|
+
execute: ({ id, sessionId, prompt, delivery }) => sql`
|
|
5132
|
+
UPDATE session_input SET prompt = COALESCE(${prompt}, prompt), delivery = COALESCE(${delivery}, delivery)
|
|
5133
|
+
WHERE id = ${id} AND session_id = ${sessionId} AND promoted_seq IS NULL AND cancelled_seq IS NULL
|
|
5134
|
+
RETURNING id
|
|
5135
|
+
`
|
|
5136
|
+
});
|
|
5137
|
+
const reviveRow = SqlSchema.findAll({
|
|
5138
|
+
Request: Schema.Struct({
|
|
5139
|
+
id: Schema.String,
|
|
5140
|
+
sessionId: Schema.String,
|
|
5141
|
+
prompt: Schema.fromJsonString(Prompt),
|
|
5142
|
+
delivery: Delivery,
|
|
5143
|
+
admittedSeq: Schema.Int,
|
|
5144
|
+
createdAt: schema_exports$9.DateTimeUtcFromMillis
|
|
5145
|
+
}),
|
|
5146
|
+
Result: Schema.Struct({ id: Schema.String }),
|
|
5147
|
+
execute: ({ id, sessionId, prompt, delivery, admittedSeq, createdAt }) => sql`
|
|
5148
|
+
UPDATE session_input
|
|
5149
|
+
SET prompt = ${prompt}, delivery = ${delivery}, admitted_seq = ${admittedSeq}, created_at = ${createdAt},
|
|
5150
|
+
cancelled_seq = NULL
|
|
5151
|
+
WHERE id = ${id} AND session_id = ${sessionId} AND promoted_seq IS NULL AND cancelled_seq IS NOT NULL
|
|
5152
|
+
RETURNING id
|
|
4966
5153
|
`
|
|
4967
5154
|
});
|
|
4968
5155
|
const promoteRow = SqlSchema.findAll({
|
|
@@ -4974,8 +5161,21 @@ const make$8 = Effect.gen(function* () {
|
|
|
4974
5161
|
Result: SessionInputRow,
|
|
4975
5162
|
execute: ({ id, sessionId, promotedSeq }) => sql`
|
|
4976
5163
|
UPDATE session_input SET promoted_seq = ${promotedSeq}
|
|
4977
|
-
WHERE id = ${id} AND session_id = ${sessionId} AND promoted_seq IS NULL
|
|
5164
|
+
WHERE id = ${id} AND session_id = ${sessionId} AND promoted_seq IS NULL AND cancelled_seq IS NULL
|
|
4978
5165
|
RETURNING *
|
|
5166
|
+
`
|
|
5167
|
+
});
|
|
5168
|
+
const cancelRow = SqlSchema.findAll({
|
|
5169
|
+
Request: Schema.Struct({
|
|
5170
|
+
id: Schema.String,
|
|
5171
|
+
sessionId: Schema.String,
|
|
5172
|
+
cancelledSeq: Schema.Int
|
|
5173
|
+
}),
|
|
5174
|
+
Result: Schema.Struct({ id: Schema.String }),
|
|
5175
|
+
execute: ({ id, sessionId, cancelledSeq }) => sql`
|
|
5176
|
+
UPDATE session_input SET cancelled_seq = ${cancelledSeq}
|
|
5177
|
+
WHERE id = ${id} AND session_id = ${sessionId} AND promoted_seq IS NULL AND cancelled_seq IS NULL
|
|
5178
|
+
RETURNING id
|
|
4979
5179
|
`
|
|
4980
5180
|
});
|
|
4981
5181
|
const selectEntry = SqlSchema.findAll({
|
|
@@ -4991,8 +5191,17 @@ const make$8 = Effect.gen(function* () {
|
|
|
4991
5191
|
Result: Schema.Struct({ id: Schema.String }),
|
|
4992
5192
|
execute: ({ sessionId, delivery }) => sql`
|
|
4993
5193
|
SELECT id FROM session_input
|
|
4994
|
-
WHERE session_id = ${sessionId} AND promoted_seq IS NULL AND delivery = ${delivery}
|
|
5194
|
+
WHERE session_id = ${sessionId} AND promoted_seq IS NULL AND cancelled_seq IS NULL AND delivery = ${delivery}
|
|
4995
5195
|
LIMIT 1
|
|
5196
|
+
`
|
|
5197
|
+
});
|
|
5198
|
+
const selectPending = SqlSchema.findAll({
|
|
5199
|
+
Request: Schema.String,
|
|
5200
|
+
Result: SessionInputRow,
|
|
5201
|
+
execute: (sessionId) => sql`
|
|
5202
|
+
SELECT * FROM session_input
|
|
5203
|
+
WHERE session_id = ${sessionId} AND promoted_seq IS NULL AND cancelled_seq IS NULL
|
|
5204
|
+
ORDER BY admitted_seq
|
|
4996
5205
|
`
|
|
4997
5206
|
});
|
|
4998
5207
|
const selectPendingSteers = SqlSchema.findAll({
|
|
@@ -5005,6 +5214,7 @@ const make$8 = Effect.gen(function* () {
|
|
|
5005
5214
|
SELECT * FROM session_input
|
|
5006
5215
|
WHERE session_id = ${sessionId}
|
|
5007
5216
|
AND promoted_seq IS NULL
|
|
5217
|
+
AND cancelled_seq IS NULL
|
|
5008
5218
|
AND delivery = 'steer'
|
|
5009
5219
|
AND admitted_seq <= ${cutoff}
|
|
5010
5220
|
ORDER BY admitted_seq
|
|
@@ -5015,7 +5225,7 @@ const make$8 = Effect.gen(function* () {
|
|
|
5015
5225
|
Result: SessionInputRow,
|
|
5016
5226
|
execute: (sessionId) => sql`
|
|
5017
5227
|
SELECT * FROM session_input
|
|
5018
|
-
WHERE session_id = ${sessionId} AND promoted_seq IS NULL AND delivery = 'followUp'
|
|
5228
|
+
WHERE session_id = ${sessionId} AND promoted_seq IS NULL AND cancelled_seq IS NULL AND delivery = 'followUp'
|
|
5019
5229
|
ORDER BY admitted_seq
|
|
5020
5230
|
LIMIT 1
|
|
5021
5231
|
`
|
|
@@ -5025,7 +5235,7 @@ const make$8 = Effect.gen(function* () {
|
|
|
5025
5235
|
}, Effect.orDie);
|
|
5026
5236
|
const admit = Effect.fn("SessionInput.admit")(function* (input) {
|
|
5027
5237
|
const existing = yield* find(input.id);
|
|
5028
|
-
if (Option.isSome(existing)) return existing.value;
|
|
5238
|
+
if (Option.isSome(existing) && existing.value.cancelledSeq === void 0) return existing.value;
|
|
5029
5239
|
const timestamp = yield* DateTime.now;
|
|
5030
5240
|
return yield* events.publish(PromptAdmitted, {
|
|
5031
5241
|
messageId: input.id,
|
|
@@ -5040,10 +5250,7 @@ const make$8 = Effect.gen(function* () {
|
|
|
5040
5250
|
prompt: input.prompt,
|
|
5041
5251
|
delivery: input.delivery,
|
|
5042
5252
|
timeCreated: timestamp
|
|
5043
|
-
}))), Effect.catchDefect((defect) => find(input.id).pipe(Effect.flatMap(Option.
|
|
5044
|
-
onNone: () => Effect.die(defect),
|
|
5045
|
-
onSome: (stored) => Effect.succeed(stored)
|
|
5046
|
-
})))));
|
|
5253
|
+
}))), Effect.catchDefect((defect) => find(input.id).pipe(Effect.flatMap((stored) => Option.isSome(stored) && stored.value.cancelledSeq === void 0 ? Effect.succeed(stored.value) : Effect.die(defect)))));
|
|
5047
5254
|
});
|
|
5048
5255
|
const projectAdmitted = Effect.fn("SessionInput.projectAdmitted")(function* (input) {
|
|
5049
5256
|
if ((yield* selectEntry(input.id)).length > 0) return yield* Effect.die(new LifecycleConflict({ id: input.id }));
|
|
@@ -5054,7 +5261,16 @@ const make$8 = Effect.gen(function* () {
|
|
|
5054
5261
|
delivery: input.delivery,
|
|
5055
5262
|
admittedSeq: input.admittedSeq,
|
|
5056
5263
|
promotedSeq: Option.none(),
|
|
5264
|
+
cancelledSeq: Option.none(),
|
|
5057
5265
|
createdAt: VariantSchema.Override(input.timeCreated)
|
|
5266
|
+
})).length > 0) return;
|
|
5267
|
+
if ((yield* reviveRow({
|
|
5268
|
+
id: input.id,
|
|
5269
|
+
sessionId: input.sessionId,
|
|
5270
|
+
prompt: input.prompt,
|
|
5271
|
+
delivery: input.delivery,
|
|
5272
|
+
admittedSeq: input.admittedSeq,
|
|
5273
|
+
createdAt: input.timeCreated
|
|
5058
5274
|
})).length === 0) return yield* Effect.die(new LifecycleConflict({ id: input.id }));
|
|
5059
5275
|
}, Effect.orDie);
|
|
5060
5276
|
const projectPrompted = Effect.fn("SessionInput.projectPrompted")(function* (input) {
|
|
@@ -5069,7 +5285,7 @@ const make$8 = Effect.gen(function* () {
|
|
|
5069
5285
|
}
|
|
5070
5286
|
const stored = yield* find(input.id);
|
|
5071
5287
|
if (Option.isSome(stored)) {
|
|
5072
|
-
if (!matchesProjection(stored.value, input) || stored.value.promotedSeq !== input.promotedSeq) return yield* Effect.die(new LifecycleConflict({ id: input.id }));
|
|
5288
|
+
if (!matchesProjection(stored.value, input) || stored.value.promotedSeq !== input.promotedSeq || stored.value.cancelledSeq !== void 0) return yield* Effect.die(new LifecycleConflict({ id: input.id }));
|
|
5073
5289
|
return;
|
|
5074
5290
|
}
|
|
5075
5291
|
yield* insertRow({
|
|
@@ -5079,9 +5295,50 @@ const make$8 = Effect.gen(function* () {
|
|
|
5079
5295
|
delivery: input.delivery,
|
|
5080
5296
|
admittedSeq: input.promotedSeq,
|
|
5081
5297
|
promotedSeq: Option.some(input.promotedSeq),
|
|
5298
|
+
cancelledSeq: Option.none(),
|
|
5082
5299
|
createdAt: VariantSchema.Override(input.timeCreated)
|
|
5083
5300
|
});
|
|
5084
5301
|
}, Effect.orDie);
|
|
5302
|
+
const projectCancelled = Effect.fn("SessionInput.projectCancelled")(function* (input) {
|
|
5303
|
+
if ((yield* cancelRow(input)).length > 0) return;
|
|
5304
|
+
const stored = yield* find(input.id);
|
|
5305
|
+
if (Option.isSome(stored) && stored.value.cancelledSeq === input.cancelledSeq) return;
|
|
5306
|
+
return yield* Effect.die(new LifecycleConflict({ id: input.id }));
|
|
5307
|
+
}, Effect.orDie);
|
|
5308
|
+
const projectUpdated = Effect.fn("SessionInput.projectUpdated")(function* (input) {
|
|
5309
|
+
if ((yield* updateRow({
|
|
5310
|
+
id: input.id,
|
|
5311
|
+
sessionId: input.sessionId,
|
|
5312
|
+
prompt: input.prompt ?? null,
|
|
5313
|
+
delivery: input.delivery ?? null
|
|
5314
|
+
})).length > 0) return;
|
|
5315
|
+
return yield* Effect.die(new LifecycleConflict({ id: input.id }));
|
|
5316
|
+
}, Effect.orDie);
|
|
5317
|
+
const update = Effect.fn("SessionInput.update")(function* (sessionId, id, patch) {
|
|
5318
|
+
const stored = yield* find(id);
|
|
5319
|
+
if (Option.isNone(stored) || stored.value.sessionId !== sessionId || stored.value.promotedSeq !== void 0 || stored.value.cancelledSeq !== void 0) return Option.none();
|
|
5320
|
+
const prompt = patch.prompt === void 0 || Prompt.equivalence(patch.prompt, stored.value.prompt) ? void 0 : patch.prompt;
|
|
5321
|
+
const delivery = patch.delivery === void 0 || patch.delivery === stored.value.delivery ? void 0 : patch.delivery;
|
|
5322
|
+
if (prompt === void 0 && delivery === void 0) return stored;
|
|
5323
|
+
const timestamp = yield* DateTime.now;
|
|
5324
|
+
return yield* events.publish(PromptUpdated, {
|
|
5325
|
+
sessionId,
|
|
5326
|
+
messageId: id,
|
|
5327
|
+
timestamp,
|
|
5328
|
+
...prompt === void 0 ? {} : { prompt },
|
|
5329
|
+
...delivery === void 0 ? {} : { delivery }
|
|
5330
|
+
}).pipe(Effect.andThen(find(id)), Effect.catchDefect((defect) => isLifecycleConflict(defect) ? Effect.succeed(Option.none()) : Effect.die(defect)));
|
|
5331
|
+
});
|
|
5332
|
+
const cancel = Effect.fn("SessionInput.cancel")(function* (sessionId, id) {
|
|
5333
|
+
const stored = yield* find(id);
|
|
5334
|
+
if (Option.isNone(stored) || stored.value.sessionId !== sessionId || stored.value.promotedSeq !== void 0 || stored.value.cancelledSeq !== void 0) return false;
|
|
5335
|
+
const timestamp = yield* DateTime.now;
|
|
5336
|
+
return yield* events.publish(PromptCancelled, {
|
|
5337
|
+
sessionId,
|
|
5338
|
+
messageId: id,
|
|
5339
|
+
timestamp
|
|
5340
|
+
}).pipe(Effect.as(true), Effect.catchDefect((defect) => isLifecycleConflict(defect) ? Effect.succeed(false) : Effect.die(defect)));
|
|
5341
|
+
});
|
|
5085
5342
|
const publishPrompted = Effect.fn("SessionInput.publishPrompted")(function* (sessionId, rows) {
|
|
5086
5343
|
let promoted = 0;
|
|
5087
5344
|
for (const row of rows) {
|
|
@@ -5092,7 +5349,7 @@ const make$8 = Effect.gen(function* () {
|
|
|
5092
5349
|
messageId: id,
|
|
5093
5350
|
prompt: row.prompt,
|
|
5094
5351
|
delivery: row.delivery
|
|
5095
|
-
}).pipe(Effect.as(true), Effect.catchDefect((defect) => isLifecycleConflict(defect) ? find(id).pipe(Effect.flatMap((stored) => Option.isSome(stored) && stored.value.promotedSeq !== void 0 ? Effect.succeed(false) : Effect.die(defect))) : Effect.die(defect)))) promoted += 1;
|
|
5352
|
+
}).pipe(Effect.as(true), Effect.catchDefect((defect) => isLifecycleConflict(defect) ? find(id).pipe(Effect.flatMap((stored) => Option.isSome(stored) && (stored.value.promotedSeq !== void 0 || stored.value.cancelledSeq !== void 0 || stored.value.admittedSeq !== row.admittedSeq || stored.value.delivery !== row.delivery || !Prompt.equivalence(stored.value.prompt, row.prompt)) ? Effect.succeed(false) : Effect.die(defect))) : Effect.die(defect)))) promoted += 1;
|
|
5096
5353
|
}
|
|
5097
5354
|
return promoted;
|
|
5098
5355
|
});
|
|
@@ -5101,6 +5358,13 @@ const make$8 = Effect.gen(function* () {
|
|
|
5101
5358
|
admit,
|
|
5102
5359
|
projectAdmitted,
|
|
5103
5360
|
projectPrompted,
|
|
5361
|
+
projectCancelled,
|
|
5362
|
+
projectUpdated,
|
|
5363
|
+
update,
|
|
5364
|
+
cancel,
|
|
5365
|
+
pending: Effect.fn("SessionInput.pending")(function* (sessionId) {
|
|
5366
|
+
return (yield* selectPending(sessionId)).map(fromRow);
|
|
5367
|
+
}, Effect.orDie),
|
|
5104
5368
|
hasPending: Effect.fn("SessionInput.hasPending")(function* (sessionId, delivery) {
|
|
5105
5369
|
return (yield* selectPendingOne({
|
|
5106
5370
|
sessionId,
|
|
@@ -5126,6 +5390,7 @@ const make$8 = Effect.gen(function* () {
|
|
|
5126
5390
|
var control_exports = /* @__PURE__ */ __exportAll({
|
|
5127
5391
|
Control: () => control_exports,
|
|
5128
5392
|
PromptConflictError: () => PromptConflictError,
|
|
5393
|
+
RESTART_LIMIT: () => 5,
|
|
5129
5394
|
Service: () => Service$5,
|
|
5130
5395
|
layer: () => layer$10
|
|
5131
5396
|
});
|
|
@@ -5144,6 +5409,7 @@ const layer$10 = Layer.effect(Service$5, Effect.gen(function* () {
|
|
|
5144
5409
|
const inputs = yield* make$8;
|
|
5145
5410
|
const execution = yield* Service$6;
|
|
5146
5411
|
const isLifecycleConflict = Schema.is(LifecycleConflict);
|
|
5412
|
+
const wake = (sessionId) => sessions.markRunning(sessionId).pipe(Effect.andThen(execution.wake(sessionId)));
|
|
5147
5413
|
/**
|
|
5148
5414
|
* Uninterruptible end to end: a client that disconnects mid-call must not
|
|
5149
5415
|
* leave a prompt half-admitted. Everything durable happens inside `admit`,
|
|
@@ -5174,10 +5440,10 @@ const layer$10 = Layer.effect(Service$5, Effect.gen(function* () {
|
|
|
5174
5440
|
});
|
|
5175
5441
|
return admitted;
|
|
5176
5442
|
});
|
|
5177
|
-
const prompt = Effect.fn("Control.prompt")((input) => Effect.uninterruptible(admit(input).pipe(Effect.tap((admitted) =>
|
|
5443
|
+
const prompt = Effect.fn("Control.prompt")((input) => Effect.uninterruptible(admit(input).pipe(Effect.tap((admitted) => wake(admitted.sessionId)))));
|
|
5178
5444
|
const run = Effect.fn("Control.run")((input) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
5179
5445
|
const admitted = yield* admit(input);
|
|
5180
|
-
yield*
|
|
5446
|
+
yield* wake(admitted.sessionId);
|
|
5181
5447
|
yield* restore(execution.awaitIdle(admitted.sessionId));
|
|
5182
5448
|
return admitted;
|
|
5183
5449
|
})));
|
|
@@ -5189,16 +5455,40 @@ const layer$10 = Layer.effect(Service$5, Effect.gen(function* () {
|
|
|
5189
5455
|
const resume = Effect.fn("Control.resume")(function* (sessionId) {
|
|
5190
5456
|
const session = yield* sessions.get(sessionId);
|
|
5191
5457
|
if (Option.isNone(session)) return yield* new SessionNotFoundError({ sessionId });
|
|
5458
|
+
yield* sessions.markRunning(sessionId);
|
|
5192
5459
|
yield* execution.resume(sessionId);
|
|
5193
5460
|
});
|
|
5194
5461
|
const interrupt = Effect.fn("Control.interrupt")((sessionId, options) => Effect.uninterruptible(execution.interrupt(sessionId, options?.reason ?? "user", options)));
|
|
5462
|
+
const update = Effect.fn("Control.update")((sessionId, id, patch) => Effect.uninterruptible(Effect.gen(function* () {
|
|
5463
|
+
const updated = yield* inputs.update(sessionId, id, patch);
|
|
5464
|
+
if (Option.isNone(updated)) return false;
|
|
5465
|
+
if (patch.delivery === "steer") yield* wake(sessionId);
|
|
5466
|
+
return true;
|
|
5467
|
+
})));
|
|
5468
|
+
const resumeUnfinished = Effect.gen(function* () {
|
|
5469
|
+
const running = yield* execution.active;
|
|
5470
|
+
const unfinished = (yield* sessions.unfinished).filter((row) => !running.has(row.id));
|
|
5471
|
+
yield* Effect.forEach(unfinished, Effect.fnUntraced(function* (row) {
|
|
5472
|
+
if ((yield* execution.active).has(row.id)) return;
|
|
5473
|
+
const restarts = yield* sessions.claimRestart(row.id);
|
|
5474
|
+
if (Option.isNone(restarts) || restarts.value >= 5) return;
|
|
5475
|
+
yield* execution.recover(row.id).pipe(Effect.tapCause((cause) => Effect.logWarning("Resuming an unfinished session failed", cause).pipe(Effect.annotateLogs({ sessionId: row.id }))), Effect.ignore);
|
|
5476
|
+
}), {
|
|
5477
|
+
concurrency: "unbounded",
|
|
5478
|
+
discard: true
|
|
5479
|
+
});
|
|
5480
|
+
}).pipe(Effect.withSpan("Control.resumeUnfinished"));
|
|
5195
5481
|
return Service$5.of({
|
|
5196
5482
|
prompt,
|
|
5197
5483
|
run,
|
|
5198
5484
|
wait,
|
|
5199
5485
|
resume,
|
|
5200
5486
|
interrupt,
|
|
5201
|
-
active: execution.active
|
|
5487
|
+
active: execution.active,
|
|
5488
|
+
resumeUnfinished,
|
|
5489
|
+
pending: inputs.pending,
|
|
5490
|
+
cancel: inputs.cancel,
|
|
5491
|
+
update
|
|
5202
5492
|
});
|
|
5203
5493
|
}));
|
|
5204
5494
|
//#endregion
|
|
@@ -5312,7 +5602,7 @@ var settings_exports = /* @__PURE__ */ __exportAll({
|
|
|
5312
5602
|
layer: () => layer$8,
|
|
5313
5603
|
load: () => load$3,
|
|
5314
5604
|
modules: () => modules,
|
|
5315
|
-
parse: () => parse$
|
|
5605
|
+
parse: () => parse$5,
|
|
5316
5606
|
paths: () => paths,
|
|
5317
5607
|
projectRoot: () => projectRoot,
|
|
5318
5608
|
userConfigDir: () => userConfigDir
|
|
@@ -5401,7 +5691,7 @@ const decodeFormatter = SchemaIssue.makeFormatterStandardSchemaV1({ leafHook: (i
|
|
|
5401
5691
|
const message = "annotations" in issue ? issue.annotations?.message : void 0;
|
|
5402
5692
|
return typeof message === "string" ? message.replaceAll(/\s*\n\s*/g, " ") : "invalid value";
|
|
5403
5693
|
} });
|
|
5404
|
-
const parse$
|
|
5694
|
+
const parse$5 = Effect.fn("Settings.parse")(function* (path, source) {
|
|
5405
5695
|
const errors = [];
|
|
5406
5696
|
const json = parse(source, errors, {
|
|
5407
5697
|
allowTrailingComma: true,
|
|
@@ -5449,7 +5739,7 @@ const attempt = (path) => fileSystem.readFileString(path).pipe(Effect.mapError((
|
|
|
5449
5739
|
path,
|
|
5450
5740
|
reason: "read",
|
|
5451
5741
|
detail: error.reason._tag
|
|
5452
|
-
})), Effect.flatMap((source) => parse$
|
|
5742
|
+
})), Effect.flatMap((source) => parse$5(path, source)));
|
|
5453
5743
|
/**
|
|
5454
5744
|
* One read of every layer. Exported for the harness constructor, which needs the plugin
|
|
5455
5745
|
* selection before any layer is built; everything else goes through `Service`.
|
|
@@ -5563,8 +5853,13 @@ const stale = Effect.fn("ModelCatalog.stale")(function* (path) {
|
|
|
5563
5853
|
const ttl = yield* ttlConfig;
|
|
5564
5854
|
return (yield* Clock.currentTimeMillis) - mtime.value >= Duration.toMillis(ttl);
|
|
5565
5855
|
});
|
|
5856
|
+
/** OpenRouter's catalog is narrowed to what the host's key may use, when one is set. */
|
|
5857
|
+
const openRouterKey = () => getEnvApiKey({ env: ["OPENROUTER_API_KEY"] });
|
|
5566
5858
|
const download$1 = (path) => Effect.tryPromise({
|
|
5567
|
-
try: () =>
|
|
5859
|
+
try: () => downloadCatalog({
|
|
5860
|
+
path,
|
|
5861
|
+
openRouterKey: openRouterKey()
|
|
5862
|
+
}),
|
|
5568
5863
|
catch: (cause) => new RefreshError({
|
|
5569
5864
|
path,
|
|
5570
5865
|
detail: cause instanceof Error ? cause.message : String(cause)
|
|
@@ -5585,8 +5880,7 @@ const refresh$1 = Effect.fn("ModelCatalog.refresh")(function* (home, options = {
|
|
|
5585
5880
|
});
|
|
5586
5881
|
/**
|
|
5587
5882
|
* 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.
|
|
5883
|
+
* download leaves the old file in place; without one, aikit's bundled baseline is read.
|
|
5590
5884
|
*/
|
|
5591
5885
|
const check$1 = Effect.fn("ModelCatalog.check")(function* ({ path, pinned }) {
|
|
5592
5886
|
if (pinned || !(yield* stale(path))) return;
|
|
@@ -5622,7 +5916,7 @@ const loadError = (cause) => {
|
|
|
5622
5916
|
if (typeof cause !== "object" || cause === null || !("name" in cause) || cause.name !== "ModelCatalogLoadError") return;
|
|
5623
5917
|
if (!("data" in cause) || typeof cause.data !== "object" || cause.data === null) return void 0;
|
|
5624
5918
|
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 !== "
|
|
5919
|
+
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
5920
|
return new ModelCatalogError({
|
|
5627
5921
|
path: data.path,
|
|
5628
5922
|
reason: data.reason,
|
|
@@ -5755,6 +6049,22 @@ const defaultPromptPlugin = (0, plugin_exports$1.define)({
|
|
|
5755
6049
|
})
|
|
5756
6050
|
});
|
|
5757
6051
|
//#endregion
|
|
6052
|
+
//#region src/plugin/builtin/prompt/walk.ts
|
|
6053
|
+
const stripBom$1 = (raw) => raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
6054
|
+
/** `directory` and every ancestor up to the sandbox root, closest first. */
|
|
6055
|
+
const ancestors = (directory) => {
|
|
6056
|
+
const chain = [];
|
|
6057
|
+
for (let current = directory;; current = posix.dirname(current)) {
|
|
6058
|
+
chain.push(current);
|
|
6059
|
+
if (posix.dirname(current) === current) return chain;
|
|
6060
|
+
}
|
|
6061
|
+
};
|
|
6062
|
+
/**
|
|
6063
|
+
* Which of `paths` exist as the requested kind (`entry` takes either). Absence stays silent; any other failure
|
|
6064
|
+
* (permissions, a remote blip) is logged so a dropped entry is not a mystery, then counts as absent.
|
|
6065
|
+
*/
|
|
6066
|
+
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]))));
|
|
6067
|
+
//#endregion
|
|
5758
6068
|
//#region src/plugin/builtin/prompt/instruction.ts
|
|
5759
6069
|
/** First readable match per directory wins. */
|
|
5760
6070
|
const names = [
|
|
@@ -5766,22 +6076,9 @@ const names = [
|
|
|
5766
6076
|
"CLAUDE.MD"
|
|
5767
6077
|
];
|
|
5768
6078
|
const content = (raw) => {
|
|
5769
|
-
const text =
|
|
6079
|
+
const text = stripBom$1(raw);
|
|
5770
6080
|
return text.trim().length === 0 ? void 0 : text;
|
|
5771
6081
|
};
|
|
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
6082
|
/** A file that stats but does not read falls through to the next candidate. */
|
|
5786
6083
|
const first = Effect.fnUntraced(function* (fs, directory, files) {
|
|
5787
6084
|
for (const name of names) {
|
|
@@ -5809,9 +6106,9 @@ const shadowing = (location) => {
|
|
|
5809
6106
|
return location.space.location.startsWith(`${main}/`) ? main : void 0;
|
|
5810
6107
|
};
|
|
5811
6108
|
/** 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) {
|
|
6109
|
+
const discover$1 = Effect.fnUntraced(function* (fs, location) {
|
|
5813
6110
|
const chain = ancestors(location.directory);
|
|
5814
|
-
const files = yield* present$1(fs, chain.flatMap((directory) => names.map((name) => posix.join(directory, name))));
|
|
6111
|
+
const files = yield* present$1(fs, chain.flatMap((directory) => names.map((name) => posix.join(directory, name))), "file", "instruction file");
|
|
5815
6112
|
const found = yield* Effect.forEach(chain, (directory) => first(fs, directory, files), { concurrency: "unbounded" });
|
|
5816
6113
|
const main = shadowing(location);
|
|
5817
6114
|
const worktree = main === void 0 ? void 0 : found[chain.indexOf(location.space.location)];
|
|
@@ -5827,7 +6124,7 @@ const instructionPlugin = (0, plugin_exports$1.define)({
|
|
|
5827
6124
|
kind: "prompt",
|
|
5828
6125
|
setup: Effect.fn("InstructionPlugin.setup")(function* (ctx) {
|
|
5829
6126
|
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
5830
|
-
const files = (yield* discover(fs, ctx.location)).filter((file) => file.content !== void 0);
|
|
6127
|
+
const files = (yield* discover$1(fs, ctx.location)).filter((file) => file.content !== void 0);
|
|
5831
6128
|
if (files.length === 0) return;
|
|
5832
6129
|
const prompt = ctx.plugin.prompt;
|
|
5833
6130
|
prompt.sections.append(Section.ProjectContext, "Project-Specific Instructions & Guidelines:");
|
|
@@ -5835,6 +6132,236 @@ const instructionPlugin = (0, plugin_exports$1.define)({
|
|
|
5835
6132
|
})
|
|
5836
6133
|
});
|
|
5837
6134
|
//#endregion
|
|
6135
|
+
//#region src/plugin/builtin/prompt/skill.ts
|
|
6136
|
+
/** Max name length per Agent Skills spec. */
|
|
6137
|
+
const MAX_NAME_LENGTH = 64;
|
|
6138
|
+
/** Max description length per Agent Skills spec. */
|
|
6139
|
+
const MAX_DESCRIPTION_LENGTH = 1024;
|
|
6140
|
+
const IGNORE_FILE_NAMES = [
|
|
6141
|
+
".gitignore",
|
|
6142
|
+
".ignore",
|
|
6143
|
+
".fdignore"
|
|
6144
|
+
];
|
|
6145
|
+
const normalizeNewlines = (value) => value.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
|
|
6146
|
+
const extractFrontmatter = (content) => {
|
|
6147
|
+
const normalized = normalizeNewlines(stripBom$1(content));
|
|
6148
|
+
if (!normalized.startsWith("---")) return void 0;
|
|
6149
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
6150
|
+
if (endIndex === -1) return void 0;
|
|
6151
|
+
return normalized.slice(4, endIndex);
|
|
6152
|
+
};
|
|
6153
|
+
const parseFrontmatter = (raw) => {
|
|
6154
|
+
const yamlString = extractFrontmatter(raw);
|
|
6155
|
+
if (yamlString === void 0) return { frontmatter: {} };
|
|
6156
|
+
try {
|
|
6157
|
+
const parsed = parse$1(yamlString);
|
|
6158
|
+
return { frontmatter: typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {} };
|
|
6159
|
+
} catch (error) {
|
|
6160
|
+
return {
|
|
6161
|
+
frontmatter: {},
|
|
6162
|
+
error
|
|
6163
|
+
};
|
|
6164
|
+
}
|
|
6165
|
+
};
|
|
6166
|
+
const validateName = (name) => {
|
|
6167
|
+
const errors = [];
|
|
6168
|
+
if (name.length > MAX_NAME_LENGTH) errors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);
|
|
6169
|
+
if (!/^[a-z0-9-]+$/.test(name)) errors.push("name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)");
|
|
6170
|
+
if (name.startsWith("-") || name.endsWith("-")) errors.push("name must not start or end with a hyphen");
|
|
6171
|
+
if (name.includes("--")) errors.push("name must not contain consecutive hyphens");
|
|
6172
|
+
return errors;
|
|
6173
|
+
};
|
|
6174
|
+
const validateDescription = (description) => {
|
|
6175
|
+
const errors = [];
|
|
6176
|
+
if (typeof description !== "string" || description.trim() === "") errors.push("description is required");
|
|
6177
|
+
else if (description.length > MAX_DESCRIPTION_LENGTH) errors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);
|
|
6178
|
+
return errors;
|
|
6179
|
+
};
|
|
6180
|
+
const prefixIgnorePattern = (line, prefix) => {
|
|
6181
|
+
const trimmed = line.trim();
|
|
6182
|
+
if (!trimmed) return void 0;
|
|
6183
|
+
if (trimmed.startsWith("#") && !trimmed.startsWith("\\#")) return void 0;
|
|
6184
|
+
let pattern = line;
|
|
6185
|
+
let negated = false;
|
|
6186
|
+
if (pattern.startsWith("!")) {
|
|
6187
|
+
negated = true;
|
|
6188
|
+
pattern = pattern.slice(1);
|
|
6189
|
+
} else if (pattern.startsWith("\\!")) pattern = pattern.slice(1);
|
|
6190
|
+
if (pattern.startsWith("/")) pattern = pattern.slice(1);
|
|
6191
|
+
const prefixed = prefix ? `${prefix}${pattern}` : pattern;
|
|
6192
|
+
return negated ? `!${prefixed}` : prefixed;
|
|
6193
|
+
};
|
|
6194
|
+
const addIgnoreRules = Effect.fnUntraced(function* (fs, ig, dir, root, entryNames) {
|
|
6195
|
+
const relativeDir = posix.relative(root, dir);
|
|
6196
|
+
const prefix = relativeDir && relativeDir !== "." ? `${relativeDir}/` : "";
|
|
6197
|
+
const presentIgnoreFiles = IGNORE_FILE_NAMES.filter((name) => entryNames.has(name));
|
|
6198
|
+
if (presentIgnoreFiles.length === 0) return;
|
|
6199
|
+
const contents = yield* Effect.forEach(presentIgnoreFiles, (filename) => fs.readFile(posix.join(dir, filename)).pipe(Effect.option), { concurrency: "unbounded" });
|
|
6200
|
+
for (const content of contents) {
|
|
6201
|
+
if (Option.isNone(content)) continue;
|
|
6202
|
+
const patterns = content.value.split(/\r?\n/).map((line) => prefixIgnorePattern(line, prefix)).filter((line) => line !== void 0);
|
|
6203
|
+
if (patterns.length > 0) ig.add(patterns);
|
|
6204
|
+
}
|
|
6205
|
+
});
|
|
6206
|
+
const loadSkillFromFile = Effect.fnUntraced(function* (fs, filePath) {
|
|
6207
|
+
const isDeclaredSkill = posix.basename(filePath) === "SKILL.md";
|
|
6208
|
+
const raw = yield* fs.readFile(filePath).pipe(Effect.tapError((error) => Effect.logWarning(`could not read skill file ${filePath}`, error.cause)), Effect.option);
|
|
6209
|
+
if (Option.isNone(raw)) return void 0;
|
|
6210
|
+
const { frontmatter, error } = parseFrontmatter(raw.value);
|
|
6211
|
+
if (error !== void 0) {
|
|
6212
|
+
if (isDeclaredSkill) yield* Effect.logWarning(`could not parse skill file ${filePath}`, error);
|
|
6213
|
+
return;
|
|
6214
|
+
}
|
|
6215
|
+
const description = frontmatter.description;
|
|
6216
|
+
const hasDescription = typeof description === "string" && description.trim() !== "";
|
|
6217
|
+
if (!isDeclaredSkill && !hasDescription) return void 0;
|
|
6218
|
+
const skillDir = posix.dirname(filePath);
|
|
6219
|
+
const parentDirName = posix.basename(skillDir);
|
|
6220
|
+
for (const error of validateDescription(description)) yield* Effect.logWarning(`skill ${filePath}: ${error}`);
|
|
6221
|
+
const name = (typeof frontmatter.name === "string" ? frontmatter.name : void 0) || parentDirName;
|
|
6222
|
+
for (const error of validateName(name)) yield* Effect.logWarning(`skill ${filePath}: ${error}`);
|
|
6223
|
+
if (!hasDescription) return void 0;
|
|
6224
|
+
return {
|
|
6225
|
+
name,
|
|
6226
|
+
description,
|
|
6227
|
+
filePath,
|
|
6228
|
+
baseDir: skillDir,
|
|
6229
|
+
disableModelInvocation: frontmatter["disable-model-invocation"] === true
|
|
6230
|
+
};
|
|
6231
|
+
});
|
|
6232
|
+
const scanDir = (fs, dir, mode, root, ig, visited) => Effect.gen(function* () {
|
|
6233
|
+
const realDir = yield* realpath(fs, dir);
|
|
6234
|
+
if (visited.has(realDir)) return [];
|
|
6235
|
+
const nextVisited = /* @__PURE__ */ new Set([...visited, realDir]);
|
|
6236
|
+
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);
|
|
6237
|
+
if (Option.isNone(listed)) return [];
|
|
6238
|
+
const entries = [...listed.value].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
6239
|
+
const entrySet = new Set(entries);
|
|
6240
|
+
yield* addIgnoreRules(fs, ig, dir, root, entrySet);
|
|
6241
|
+
if (entrySet.has("SKILL.md")) {
|
|
6242
|
+
const fullPath = posix.join(dir, "SKILL.md");
|
|
6243
|
+
const relPath = posix.relative(root, fullPath);
|
|
6244
|
+
if ((yield* isFile(fs, fullPath)) && !ig.ignores(relPath)) {
|
|
6245
|
+
const skill = yield* loadSkillFromFile(fs, fullPath);
|
|
6246
|
+
return skill === void 0 ? [] : [skill];
|
|
6247
|
+
}
|
|
6248
|
+
}
|
|
6249
|
+
const visible = entries.filter((name) => !name.startsWith(".") && name !== "node_modules");
|
|
6250
|
+
const statted = yield* Effect.forEach(visible, (name) => {
|
|
6251
|
+
const fullPath = posix.join(dir, name);
|
|
6252
|
+
return fs.stat(fullPath).pipe(Effect.map((stat) => ({
|
|
6253
|
+
name,
|
|
6254
|
+
fullPath,
|
|
6255
|
+
stat
|
|
6256
|
+
})), Effect.option);
|
|
6257
|
+
}, { concurrency: "unbounded" });
|
|
6258
|
+
const skills = [];
|
|
6259
|
+
for (const item of statted) {
|
|
6260
|
+
if (Option.isNone(item)) continue;
|
|
6261
|
+
const { name, fullPath, stat } = item.value;
|
|
6262
|
+
const relPath = posix.relative(root, fullPath);
|
|
6263
|
+
if (ig.ignores(stat.isDirectory ? `${relPath}/` : relPath)) continue;
|
|
6264
|
+
if (stat.isDirectory) {
|
|
6265
|
+
const nested = yield* scanDir(fs, fullPath, mode, root, ig, nextVisited);
|
|
6266
|
+
skills.push(...nested);
|
|
6267
|
+
continue;
|
|
6268
|
+
}
|
|
6269
|
+
if (stat.isFile && name.endsWith(".md") && (mode === "codework" && dir === root || mode === "agents" && dir !== root)) {
|
|
6270
|
+
const skill = yield* loadSkillFromFile(fs, fullPath);
|
|
6271
|
+
if (skill !== void 0) skills.push(skill);
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
return skills;
|
|
6275
|
+
});
|
|
6276
|
+
/**
|
|
6277
|
+
* Load skills from a single directory in the sandbox.
|
|
6278
|
+
*
|
|
6279
|
+
* Discovery rules:
|
|
6280
|
+
* - If a directory contains `SKILL.md`, treat it as a skill root and do not recurse further.
|
|
6281
|
+
* - In `"codework"` mode, direct `.md` children in the root are also loaded; in `"agents"` mode,
|
|
6282
|
+
* `.md` files in subdirectories are loaded while root `.md` files are ignored.
|
|
6283
|
+
* - Recurse into subdirectories (skipping dotfiles, `node_modules`, and ignored paths).
|
|
6284
|
+
*/
|
|
6285
|
+
const loadSkillsFromDir = (fs, dir, mode) => scanDir(fs, dir, mode, dir, ignore(), /* @__PURE__ */ new Set());
|
|
6286
|
+
/**
|
|
6287
|
+
* Discover skills inside the sandbox: `.codework/skills` at the cwd, then
|
|
6288
|
+
* `.agents/skills` from the cwd up to the git root (the closest ancestor holding `.git`), or up to
|
|
6289
|
+
* the sandbox root outside a repository. Earlier skills win name collisions.
|
|
6290
|
+
*/
|
|
6291
|
+
const discover = Effect.fnUntraced(function* (fs, location) {
|
|
6292
|
+
const chain = ancestors(location.directory);
|
|
6293
|
+
const own = posix.join(location.directory, appConfigDir, "skills");
|
|
6294
|
+
const agents = chain.map((directory) => posix.join(directory, ".agents", "skills"));
|
|
6295
|
+
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" });
|
|
6296
|
+
const root = chain.findIndex((directory) => gits.has(posix.join(directory, ".git")));
|
|
6297
|
+
const walked = root === -1 ? agents : agents.slice(0, root + 1);
|
|
6298
|
+
const sources = [{
|
|
6299
|
+
dir: own,
|
|
6300
|
+
mode: "codework"
|
|
6301
|
+
}, ...walked.map((dir) => ({
|
|
6302
|
+
dir,
|
|
6303
|
+
mode: "agents"
|
|
6304
|
+
}))].filter(({ dir }) => dirs.has(dir));
|
|
6305
|
+
const found = yield* Effect.forEach(sources, ({ dir, mode }) => loadSkillsFromDir(fs, dir, mode), { concurrency: "unbounded" });
|
|
6306
|
+
const skillMap = /* @__PURE__ */ new Map();
|
|
6307
|
+
const realPathSet = /* @__PURE__ */ new Set();
|
|
6308
|
+
for (const skill of found.flat()) {
|
|
6309
|
+
const realPath = yield* realpath(fs, skill.filePath);
|
|
6310
|
+
if (realPathSet.has(realPath)) continue;
|
|
6311
|
+
const existing = skillMap.get(skill.name);
|
|
6312
|
+
if (existing !== void 0) yield* Effect.logWarning(`skill ${skill.filePath}: name "${skill.name}" collision with ${existing.filePath}`);
|
|
6313
|
+
else {
|
|
6314
|
+
skillMap.set(skill.name, skill);
|
|
6315
|
+
realPathSet.add(realPath);
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
return Array.from(skillMap.values());
|
|
6319
|
+
});
|
|
6320
|
+
const escapeXml = (str) => str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
6321
|
+
/**
|
|
6322
|
+
* Format skills for inclusion in `Section.Skills` per the Agent Skills specification.
|
|
6323
|
+
* Skills with `disableModelInvocation: true` are excluded from the prompt.
|
|
6324
|
+
*/
|
|
6325
|
+
const formatSkillsForPrompt = (skills, fileReadTool = "read") => {
|
|
6326
|
+
const visibleSkills = skills.filter((s) => !s.disableModelInvocation);
|
|
6327
|
+
if (visibleSkills.length === 0) return "";
|
|
6328
|
+
const lines = [
|
|
6329
|
+
"The following skills provide specialized instructions for specific tasks.",
|
|
6330
|
+
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.",
|
|
6331
|
+
"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.",
|
|
6332
|
+
"",
|
|
6333
|
+
"<available_skills>"
|
|
6334
|
+
];
|
|
6335
|
+
for (const skill of visibleSkills) {
|
|
6336
|
+
lines.push(" <skill>");
|
|
6337
|
+
lines.push(` <name>${escapeXml(skill.name)}</name>`);
|
|
6338
|
+
lines.push(` <description>${escapeXml(skill.description)}</description>`);
|
|
6339
|
+
lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
|
|
6340
|
+
lines.push(" </skill>");
|
|
6341
|
+
}
|
|
6342
|
+
lines.push("</available_skills>");
|
|
6343
|
+
return lines.join("\n");
|
|
6344
|
+
};
|
|
6345
|
+
const readers = ["read", "bash"];
|
|
6346
|
+
const skillPlugin = (0, plugin_exports$1.define)({
|
|
6347
|
+
id: "codework.prompt.skill",
|
|
6348
|
+
kind: "prompt",
|
|
6349
|
+
setup: Effect.fn("SkillPlugin.setup")(function* (ctx) {
|
|
6350
|
+
const tools = ctx.plugin.tools;
|
|
6351
|
+
const visible = (name) => {
|
|
6352
|
+
const tool = tools.get(name);
|
|
6353
|
+
return tool !== void 0 && tool.promptSnippet !== void 0 && tool.promptSnippet.length > 0;
|
|
6354
|
+
};
|
|
6355
|
+
const fileReadTool = readers.find(visible) ?? (readers.some((name) => tools.has(name)) ? "indirect" : void 0);
|
|
6356
|
+
if (fileReadTool === void 0) return;
|
|
6357
|
+
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
6358
|
+
const skills = yield* discover(fs, ctx.location);
|
|
6359
|
+
const formatted = formatSkillsForPrompt(skills, fileReadTool);
|
|
6360
|
+
if (formatted.length === 0) return;
|
|
6361
|
+
ctx.plugin.prompt.sections.append(Section.Skills, formatted);
|
|
6362
|
+
})
|
|
6363
|
+
});
|
|
6364
|
+
//#endregion
|
|
5838
6365
|
//#region src/tool/truncate.ts
|
|
5839
6366
|
/**
|
|
5840
6367
|
* Shared truncation utilities for tool outputs.
|
|
@@ -6146,8 +6673,10 @@ const windowBytes = (limits) => limits.maxBytes * 2;
|
|
|
6146
6673
|
*/
|
|
6147
6674
|
const script = (command, file, limits, pipeline, report) => [
|
|
6148
6675
|
`f=${(0, shell_exports.quote)(file)}`,
|
|
6676
|
+
`c=${(0, shell_exports.quote)(command)}`,
|
|
6677
|
+
"if command -v bash >/dev/null 2>&1; then s=bash; else s=sh; fi",
|
|
6149
6678
|
"run() (",
|
|
6150
|
-
|
|
6679
|
+
" if [ \"$s\" = bash ]; then bash -c \"$c\"; else eval \"$c\"; fi",
|
|
6151
6680
|
")",
|
|
6152
6681
|
`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
6682
|
" echo \"$$ $(ps -o pgid= -p $$ 2>/dev/null)\" > \"$f.pid\"",
|
|
@@ -6164,18 +6693,19 @@ const script = (command, file, limits, pipeline, report) => [
|
|
|
6164
6693
|
`printf '%s\\n' ${(0, shell_exports.quote)(unsaved(file))}`,
|
|
6165
6694
|
"run 2>&1"
|
|
6166
6695
|
].join("\n");
|
|
6167
|
-
const header = new RegExp(`^${HEADER} (\\d+) (\\d+) ([01])(?: (\\d+))?$`, "m");
|
|
6168
|
-
const parse$
|
|
6696
|
+
const header = new RegExp(`^${HEADER} (\\d+) (\\d+) ([01]) (bash|sh)(?: (\\d+))?$`, "m");
|
|
6697
|
+
const parse$4 = (match) => ({
|
|
6169
6698
|
bytes: Number(match[1]),
|
|
6170
6699
|
newlines: Number(match[2]),
|
|
6171
|
-
kept: match[3] === "1"
|
|
6700
|
+
kept: match[3] === "1",
|
|
6701
|
+
shell: match[4] === "sh" ? "sh" : "bash"
|
|
6172
6702
|
});
|
|
6173
6703
|
/**
|
|
6174
6704
|
* For a backend that streams: all output still flows back as it is produced, and
|
|
6175
6705
|
* a copy lands in the file. The report rides on stderr, the wrapper's own channel
|
|
6176
6706
|
* since the command's is folded into stdout.
|
|
6177
6707
|
*/
|
|
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`]);
|
|
6708
|
+
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
6709
|
/** Read {@link streaming}'s stderr: the report, if it got that far, and anything else the wrapper said. */
|
|
6180
6710
|
const report = (stderr) => {
|
|
6181
6711
|
const match = header.exec(stderr);
|
|
@@ -6184,7 +6714,7 @@ const report = (stderr) => {
|
|
|
6184
6714
|
rest: stderr
|
|
6185
6715
|
};
|
|
6186
6716
|
return {
|
|
6187
|
-
report: parse$
|
|
6717
|
+
report: parse$4(match),
|
|
6188
6718
|
rest: stderr.slice(0, match.index) + stderr.slice(match.index + match[0].length).replace(/^\n/, "")
|
|
6189
6719
|
};
|
|
6190
6720
|
};
|
|
@@ -6197,7 +6727,7 @@ const buffered = (command, file, limits) => {
|
|
|
6197
6727
|
const tail = `tail -n ${limits.maxLines} "$f" | tail -c ${windowBytes(limits)}`;
|
|
6198
6728
|
return script(command, file, limits, (source) => `{ ${source} | cat; } >> "$f"`, [
|
|
6199
6729
|
`w=$(${tail} | wc -c | awk '{ print $1 }')`,
|
|
6200
|
-
`printf '${HEADER} %s %s %s %s\\n' "$b" "$n" "$keep" "$w"`,
|
|
6730
|
+
`printf '${HEADER} %s %s %s %s %s\\n' "$b" "$n" "$keep" "$s" "$w"`,
|
|
6201
6731
|
tail
|
|
6202
6732
|
]);
|
|
6203
6733
|
};
|
|
@@ -6205,11 +6735,11 @@ const buffered = (command, file, limits) => {
|
|
|
6205
6735
|
const window = (stdout) => {
|
|
6206
6736
|
const newline = stdout.indexOf("\n");
|
|
6207
6737
|
const match = header.exec(newline === -1 ? stdout : stdout.slice(0, newline));
|
|
6208
|
-
if (match === null || match[
|
|
6738
|
+
if (match === null || match[5] === void 0) return void 0;
|
|
6209
6739
|
return {
|
|
6210
|
-
...parse$
|
|
6740
|
+
...parse$4(match),
|
|
6211
6741
|
text: stdout.slice(newline + 1),
|
|
6212
|
-
textBytes: Number(match[
|
|
6742
|
+
textBytes: Number(match[5])
|
|
6213
6743
|
};
|
|
6214
6744
|
};
|
|
6215
6745
|
/**
|
|
@@ -6357,7 +6887,9 @@ const BashSuccess = Schema.Struct({
|
|
|
6357
6887
|
/** Non-zero exit — expected, model-visible. Carries the same shape as success. */
|
|
6358
6888
|
var BashFailed = class extends Schema.TaggedError()("BashFailed", {
|
|
6359
6889
|
...outputFields,
|
|
6360
|
-
exitCode: Schema.Finite
|
|
6890
|
+
exitCode: Schema.Finite,
|
|
6891
|
+
/** The shell that ran the command, when the sandbox reported it. */
|
|
6892
|
+
shell: Schema.optional(Schema.Literals(["bash", "sh"]))
|
|
6361
6893
|
}) {};
|
|
6362
6894
|
/** Deadline exceeded — expected, model-visible. Carries the partial output produced so far. */
|
|
6363
6895
|
var BashTimedOut = class extends Schema.TaggedError()("BashTimedOut", {
|
|
@@ -6367,7 +6899,7 @@ var BashTimedOut = class extends Schema.TaggedError()("BashTimedOut", {
|
|
|
6367
6899
|
const BashFailure = Schema.Union([BashFailed, BashTimedOut]);
|
|
6368
6900
|
/** The reason for a failure, after any output: the model sees nothing else of `details`. */
|
|
6369
6901
|
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}`;
|
|
6902
|
+
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
6903
|
return failure.output ? `${failure.output.replace(/\n$/, "")}\n\n${status}` : status;
|
|
6372
6904
|
};
|
|
6373
6905
|
const bashDef = tool_exports.define({
|
|
@@ -6421,6 +6953,7 @@ const present = (snap, lastLineBytes, saved) => {
|
|
|
6421
6953
|
};
|
|
6422
6954
|
};
|
|
6423
6955
|
const utf8 = new TextEncoder();
|
|
6956
|
+
const shellOf = (report) => report !== void 0 ? { shell: report.shell } : {};
|
|
6424
6957
|
/**
|
|
6425
6958
|
* A command cut off by its deadline never reached the wrapper's own cleanup: kill
|
|
6426
6959
|
* what is left of it and remove its side files, keeping the output file only when
|
|
@@ -6454,7 +6987,8 @@ const runBuffered = (shell, params, file) => Effect.gen(function* () {
|
|
|
6454
6987
|
const presented = present(acc.snapshot(), acc.getLastLineBytes(), window$1?.kept === true ? file : void 0);
|
|
6455
6988
|
if (result.exitCode !== 0) return yield* new BashFailed({
|
|
6456
6989
|
exitCode: result.exitCode,
|
|
6457
|
-
...presented
|
|
6990
|
+
...presented,
|
|
6991
|
+
...shellOf(window$1)
|
|
6458
6992
|
});
|
|
6459
6993
|
return {
|
|
6460
6994
|
exitCode: result.exitCode,
|
|
@@ -6512,7 +7046,8 @@ const runStreaming = (shell, stream, params, file) => Effect.gen(function* () {
|
|
|
6512
7046
|
const code = yield* Ref.get(exitCode);
|
|
6513
7047
|
if (code === null || code !== 0) return yield* new BashFailed({
|
|
6514
7048
|
exitCode: code ?? -1,
|
|
6515
|
-
...presented
|
|
7049
|
+
...presented,
|
|
7050
|
+
...shellOf(report$1)
|
|
6516
7051
|
});
|
|
6517
7052
|
return {
|
|
6518
7053
|
exitCode: code,
|
|
@@ -6895,7 +7430,7 @@ const editDef = tool_exports.define({
|
|
|
6895
7430
|
})
|
|
6896
7431
|
});
|
|
6897
7432
|
const fail$4 = (message) => Effect.fail(new EditFailed({ message }));
|
|
6898
|
-
/** Filesystem failures name the backend's error code
|
|
7433
|
+
/** Filesystem failures name the backend's error code. */
|
|
6899
7434
|
const accessFailure = (path) => (error) => {
|
|
6900
7435
|
const code = error.cause?.code;
|
|
6901
7436
|
return fail$4(`Could not edit file: ${path}. Error code: ${typeof code === "string" ? code : "unknown"}.`);
|
|
@@ -7282,7 +7817,7 @@ const pipeline = (argv, limit, errors) => [
|
|
|
7282
7817
|
].join("; ");
|
|
7283
7818
|
const STATUS = /^\{"type":"rc","code":(\d+)\}$/;
|
|
7284
7819
|
/** The pipeline's output: match events, ripgrep's status, and anything else, which is its stderr. */
|
|
7285
|
-
const parse$
|
|
7820
|
+
const parse$3 = (stdout, stderr) => {
|
|
7286
7821
|
const matches = [];
|
|
7287
7822
|
const errors = [];
|
|
7288
7823
|
let status;
|
|
@@ -7366,7 +7901,7 @@ const grepHandler = (params) => Effect.gen(function* () {
|
|
|
7366
7901
|
if (params.glob) argv.push("--glob", params.glob);
|
|
7367
7902
|
argv.push("-e", params.pattern, searchPath);
|
|
7368
7903
|
const result = yield* shell.exec(pipeline(argv, limit, errors));
|
|
7369
|
-
const parsed = parse$
|
|
7904
|
+
const parsed = parse$3(result.stdout, result.stderr);
|
|
7370
7905
|
return {
|
|
7371
7906
|
...parsed,
|
|
7372
7907
|
exitCode: parsed.status ?? result.exitCode
|
|
@@ -7931,7 +8466,8 @@ const builtins = Object.freeze([
|
|
|
7931
8466
|
findPlugin,
|
|
7932
8467
|
lsPlugin,
|
|
7933
8468
|
defaultPromptPlugin,
|
|
7934
|
-
instructionPlugin
|
|
8469
|
+
instructionPlugin,
|
|
8470
|
+
skillPlugin
|
|
7935
8471
|
]);
|
|
7936
8472
|
//#endregion
|
|
7937
8473
|
//#region src/plugin/error.ts
|
|
@@ -8371,7 +8907,7 @@ const isPath = (reference) => reference.startsWith("file:") || reference.startsW
|
|
|
8371
8907
|
* - a **CLI argument** anchors to the process's startup directory, because that is where the
|
|
8372
8908
|
* person typed it.
|
|
8373
8909
|
*/
|
|
8374
|
-
const parse$
|
|
8910
|
+
const parse$2 = (reference, from) => Effect.gen(function* () {
|
|
8375
8911
|
const fail = (reason, message) => new SourceError({
|
|
8376
8912
|
reason,
|
|
8377
8913
|
reference,
|
|
@@ -9047,7 +9583,7 @@ const inspect = Effect.fn("PluginLoader.inspect")(function* (reference, options)
|
|
|
9047
9583
|
reference,
|
|
9048
9584
|
file: options.file
|
|
9049
9585
|
};
|
|
9050
|
-
const target = yield* parse$
|
|
9586
|
+
const target = yield* parse$2(reference, options.hostDir);
|
|
9051
9587
|
const loaded = yield* load$2(target, origin, options);
|
|
9052
9588
|
return {
|
|
9053
9589
|
id: loaded.plugin.id,
|
|
@@ -9073,7 +9609,7 @@ const load$2 = Effect.fn("PluginLoader.load")(function* (target, origin, options
|
|
|
9073
9609
|
* are one package, and `./plugins/x.ts` is the file it resolves to from the directory that
|
|
9074
9610
|
* declared it. Pure -- nothing is installed, imported or read.
|
|
9075
9611
|
*/
|
|
9076
|
-
const canonical = (reference, hostDir) => Effect.map(parse$
|
|
9612
|
+
const canonical = (reference, hostDir) => Effect.map(parse$2(reference, hostDir), canonical$1);
|
|
9077
9613
|
//#endregion
|
|
9078
9614
|
//#region src/plugin/catalog.ts
|
|
9079
9615
|
/**
|
|
@@ -9135,7 +9671,7 @@ const load$1 = Effect.fn("PluginCatalog.load")(function* (references, options) {
|
|
|
9135
9671
|
remember(plugin, []);
|
|
9136
9672
|
continue;
|
|
9137
9673
|
}
|
|
9138
|
-
const target = yield* parse$
|
|
9674
|
+
const target = yield* parse$2(reference, options.hostDir);
|
|
9139
9675
|
const key = target.kind === "local" ? target.path : target.spec;
|
|
9140
9676
|
const already = seen.get(key);
|
|
9141
9677
|
if (already !== void 0) {
|
|
@@ -9391,12 +9927,18 @@ const layer$6 = Layer.effect(Service$6, Effect.gen(function* () {
|
|
|
9391
9927
|
const runner = yield* Service$7;
|
|
9392
9928
|
const events = yield* Service$16;
|
|
9393
9929
|
const reportLifecycle = (sessionId, publish) => publish.pipe(Effect.tapCause((cause) => Cause.hasInterruptsOnly(cause) ? Effect.void : Effect.logError("Failed to publish session execution lifecycle", cause).pipe(Effect.annotateLogs({ sessionId }))), Effect.ignore);
|
|
9930
|
+
const recovering = /* @__PURE__ */ new Set();
|
|
9931
|
+
const stoppedAt = /* @__PURE__ */ new Map();
|
|
9394
9932
|
const coordinator = yield* make$7({
|
|
9395
|
-
started: (sessionId) => reportLifecycle(sessionId,
|
|
9933
|
+
started: (sessionId) => reportLifecycle(sessionId, (recovering.has(sessionId) ? Effect.void : store.markRunning(sessionId)).pipe(Effect.andThen(DateTime.now), Effect.andThen((timestamp) => events.publish(ExecutionStarted, {
|
|
9396
9934
|
sessionId,
|
|
9397
9935
|
timestamp
|
|
9398
9936
|
})))),
|
|
9399
9937
|
settled: (sessionId, exit, reason) => reportLifecycle(sessionId, Effect.gen(function* () {
|
|
9938
|
+
const interrupted = Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause);
|
|
9939
|
+
const stop = stoppedAt.get(sessionId);
|
|
9940
|
+
stoppedAt.delete(sessionId);
|
|
9941
|
+
yield* store.endExecution(sessionId, !interrupted ? "finished" : reason === "user" ? "stopped" : "closed", stop);
|
|
9400
9942
|
const timestamp = yield* DateTime.now;
|
|
9401
9943
|
if (Exit.isSuccess(exit)) return yield* events.publish(ExecutionSucceeded, {
|
|
9402
9944
|
sessionId,
|
|
@@ -9416,6 +9958,8 @@ const layer$6 = Layer.effect(Service$6, Effect.gen(function* () {
|
|
|
9416
9958
|
drain: Effect.fnUntraced(function* (sessionId, force) {
|
|
9417
9959
|
const session = yield* store.get(sessionId);
|
|
9418
9960
|
if (Option.isNone(session)) return yield* new SessionNotFoundError({ sessionId });
|
|
9961
|
+
const recovered = recovering.delete(sessionId);
|
|
9962
|
+
if (recovered && Option.isNone(session.value.execution)) return;
|
|
9419
9963
|
const space = yield* store.space(sessionId);
|
|
9420
9964
|
if (Option.isNone(space)) return yield* new SessionLinkedSpaceNotFoundError({ sessionId });
|
|
9421
9965
|
const instanceId = space.value.env;
|
|
@@ -9436,15 +9980,30 @@ const layer$6 = Layer.effect(Service$6, Effect.gen(function* () {
|
|
|
9436
9980
|
})));
|
|
9437
9981
|
return yield* runner.run({
|
|
9438
9982
|
sessionId,
|
|
9439
|
-
force
|
|
9983
|
+
force,
|
|
9984
|
+
recovered
|
|
9440
9985
|
}).pipe(Effect.provideContext(Context.merge(locationContext, mountContext)));
|
|
9441
9986
|
}).pipe(Effect.scoped, Effect.tapCause((cause) => Cause.hasDies(cause) ? Effect.logError("defect", cause).pipe(Effect.annotateLogs({ sessionId })) : Effect.void));
|
|
9442
9987
|
})
|
|
9443
9988
|
});
|
|
9989
|
+
const interrupt = (sessionId, reason, options) => Effect.gen(function* () {
|
|
9990
|
+
const recorded = reason === "user" && !stoppedAt.has(sessionId);
|
|
9991
|
+
if (recorded) stoppedAt.set(sessionId, yield* events.latestSequence(sessionId));
|
|
9992
|
+
const accepted = yield* coordinator.interrupt(sessionId, reason, options);
|
|
9993
|
+
if (recorded && !accepted) stoppedAt.delete(sessionId);
|
|
9994
|
+
return accepted;
|
|
9995
|
+
});
|
|
9996
|
+
const recover = (sessionId) => Effect.suspend(() => {
|
|
9997
|
+
recovering.add(sessionId);
|
|
9998
|
+
return coordinator.run(sessionId);
|
|
9999
|
+
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
10000
|
+
recovering.delete(sessionId);
|
|
10001
|
+
})));
|
|
9444
10002
|
return Service$6.of({
|
|
9445
10003
|
active: coordinator.active,
|
|
9446
|
-
interrupt
|
|
10004
|
+
interrupt,
|
|
9447
10005
|
resume: coordinator.run,
|
|
10006
|
+
recover,
|
|
9448
10007
|
wake: coordinator.wake,
|
|
9449
10008
|
awaitIdle: coordinator.awaitIdle
|
|
9450
10009
|
});
|
|
@@ -10970,12 +11529,13 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
10970
11529
|
}));
|
|
10971
11530
|
return interruptedCause;
|
|
10972
11531
|
});
|
|
10973
|
-
const runTurnAttempt = Effect.fn("Loop.runTurnAttempt", { level: "Info" })(function* (promotion, snapshot) {
|
|
11532
|
+
const runTurnAttempt = Effect.fn("Loop.runTurnAttempt", { level: "Info" })(function* (promotion, snapshot, continuing) {
|
|
10974
11533
|
const sessionId = snapshot.sessionId;
|
|
10975
11534
|
if (promotion !== void 0) {
|
|
10976
11535
|
const cutoff = yield* events.latestSequence(sessionId);
|
|
10977
|
-
|
|
10978
|
-
yield* inputs.promoteSteers(sessionId, cutoff);
|
|
11536
|
+
const followUp = promotion === "followUp" && (yield* inputs.promoteFollowUp(sessionId));
|
|
11537
|
+
const steers = yield* inputs.promoteSteers(sessionId, cutoff);
|
|
11538
|
+
if (!continuing && !followUp && steers === 0) return { needsContinuation: false };
|
|
10979
11539
|
}
|
|
10980
11540
|
const assembled = yield* context.assemble(sessionId);
|
|
10981
11541
|
const leaf = assembled.lastAssistant?.entryId === assembled.leafEntryId ? assembled.lastAssistant.message : void 0;
|
|
@@ -11074,8 +11634,9 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
11074
11634
|
});
|
|
11075
11635
|
}));
|
|
11076
11636
|
});
|
|
11077
|
-
|
|
11078
|
-
|
|
11637
|
+
/** `continuing`: earlier work (tool results, a forced resume) needs a reply even when nothing is promoted. */
|
|
11638
|
+
const runTurn = Effect.fn("Loop.runTurn", { level: "Info" })(function* (promotion, snapshot, continuing) {
|
|
11639
|
+
return yield* runTurnAttempt(promotion, snapshot, continuing);
|
|
11079
11640
|
});
|
|
11080
11641
|
const run = Effect.fn("Loop.run", { level: "Info" })(function* (input) {
|
|
11081
11642
|
yield* healDanglingDraft(input.sessionId);
|
|
@@ -11087,17 +11648,24 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
11087
11648
|
"gen_ai.agent.name": "codework",
|
|
11088
11649
|
"gen_ai.conversation.id": input.sessionId
|
|
11089
11650
|
});
|
|
11090
|
-
|
|
11651
|
+
const cutOff = input.recovered === true && !hasSteer && hasFollowUp && (yield* context.assemble(input.sessionId).pipe(Effect.map((assembled) => {
|
|
11652
|
+
const leaf = assembled.lastAssistant?.entryId === assembled.leafEntryId ? assembled.lastAssistant.message : void 0;
|
|
11653
|
+
return assembled.messages.length > 0 && leaf?.stopReason !== "stop" && leaf?.stopReason !== "length";
|
|
11654
|
+
})));
|
|
11655
|
+
let promotion = hasSteer ? "steer" : hasFollowUp && !cutOff ? "followUp" : void 0;
|
|
11091
11656
|
const drain = Effect.gen(function* () {
|
|
11092
11657
|
let shouldRun = true;
|
|
11658
|
+
let continuing = input.force;
|
|
11093
11659
|
while (shouldRun) {
|
|
11094
11660
|
const snapshot = yield* state.snapshot(input.sessionId);
|
|
11095
11661
|
let needsContinuation = true;
|
|
11096
11662
|
while (needsContinuation) {
|
|
11097
|
-
const result = yield* runTurn(promotion, snapshot);
|
|
11663
|
+
const result = yield* runTurn(promotion, snapshot, continuing);
|
|
11098
11664
|
promotion = "steer";
|
|
11665
|
+
continuing = result.needsContinuation;
|
|
11099
11666
|
needsContinuation = result.needsContinuation || (yield* inputs.hasPending(input.sessionId, "steer"));
|
|
11100
11667
|
}
|
|
11668
|
+
continuing = false;
|
|
11101
11669
|
shouldRun = yield* inputs.hasPending(input.sessionId, "followUp");
|
|
11102
11670
|
promotion = shouldRun ? "followUp" : void 0;
|
|
11103
11671
|
}
|
|
@@ -11219,6 +11787,20 @@ const layer$2 = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
11219
11787
|
timeCreated: event.data.timestamp
|
|
11220
11788
|
});
|
|
11221
11789
|
}));
|
|
11790
|
+
yield* events.project(PromptCancelled, (event) => Effect.gen(function* () {
|
|
11791
|
+
if (event.durable === void 0) return yield* Effect.die("PromptCancelled is missing its aggregate sequence");
|
|
11792
|
+
yield* input.projectCancelled({
|
|
11793
|
+
cancelledSeq: event.durable.seq,
|
|
11794
|
+
id: event.data.messageId,
|
|
11795
|
+
sessionId: event.data.sessionId
|
|
11796
|
+
});
|
|
11797
|
+
}));
|
|
11798
|
+
yield* events.project(PromptUpdated, (event) => input.projectUpdated({
|
|
11799
|
+
id: event.data.messageId,
|
|
11800
|
+
sessionId: event.data.sessionId,
|
|
11801
|
+
...event.data.prompt === void 0 ? {} : { prompt: event.data.prompt },
|
|
11802
|
+
...event.data.delivery === void 0 ? {} : { delivery: event.data.delivery }
|
|
11803
|
+
}));
|
|
11222
11804
|
yield* events.project(Prompted, (event) => Effect.gen(function* () {
|
|
11223
11805
|
if (event.durable === void 0) return yield* Effect.die("Prompted is missing its aggregate sequence");
|
|
11224
11806
|
yield* input.projectPrompted({
|
|
@@ -11304,7 +11886,7 @@ const layer$2 = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
11304
11886
|
id: event.data.messageId,
|
|
11305
11887
|
sessionId: event.data.sessionId,
|
|
11306
11888
|
state: "committed"
|
|
11307
|
-
}));
|
|
11889
|
+
}).pipe(Effect.andThen(sessions.resetRestarts(event.data.sessionId))));
|
|
11308
11890
|
}));
|
|
11309
11891
|
//#endregion
|
|
11310
11892
|
//#region src/session/live.ts
|
|
@@ -11318,7 +11900,7 @@ var harness_exports = /* @__PURE__ */ __exportAll({
|
|
|
11318
11900
|
const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
11319
11901
|
const hostCwd = options.hostCwd ?? process.cwd();
|
|
11320
11902
|
const paths = yield* resolve$4(options.home === void 0 ? {} : { home: hostPath.resolve(hostCwd, expandTilde(options.home, hostPath)) });
|
|
11321
|
-
const global = layerWith(paths);
|
|
11903
|
+
const global = layerWith$1(paths);
|
|
11322
11904
|
const settingsOptions = options.userConfigDir === void 0 ? {} : { userConfigDir: userConfigDir(options.userConfigDir, hostCwd) };
|
|
11323
11905
|
const config = yield* load$3({
|
|
11324
11906
|
...settingsOptions,
|
|
@@ -11337,7 +11919,7 @@ const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
11337
11919
|
* `hostDir` led to; only the process view falls back to `hostCwd`.
|
|
11338
11920
|
*/
|
|
11339
11921
|
const hostDirOf = (root) => root ?? hostCwd;
|
|
11340
|
-
const filed = (reference, from) => parse$
|
|
11922
|
+
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
11923
|
const resolveOnly = {
|
|
11342
11924
|
...catalogOptions,
|
|
11343
11925
|
install: required
|
|
@@ -11579,13 +12161,26 @@ const makeHandle = Effect.fn("Session.makeHandle")(function* (id) {
|
|
|
11579
12161
|
const space = Option.getOrUndefined(yield* sessions.space(id));
|
|
11580
12162
|
const sandbox = space === void 0 || space.env === instance_exports.SandboxInstance.ID.local ? void 0 : Option.getOrUndefined(yield* sandboxes.get(space.env));
|
|
11581
12163
|
const hostDir = Option.getOrUndefined(row.hostDir);
|
|
12164
|
+
const project = Option.getOrUndefined(yield* sessions.project(id));
|
|
12165
|
+
const config = Option.getOrUndefined(row.config);
|
|
12166
|
+
const model = config?.model;
|
|
12167
|
+
const thinkingLevel = config?.thinkingLevel;
|
|
12168
|
+
const parked = Option.isSome(row.execution) && row.restarts >= 5 && !(yield* control.active).has(id);
|
|
11582
12169
|
return {
|
|
11583
12170
|
id,
|
|
11584
12171
|
title: row.title,
|
|
11585
12172
|
directory: row.directory,
|
|
11586
12173
|
...hostDir === void 0 ? {} : { hostDir },
|
|
11587
12174
|
hasHostLink: hostDir !== void 0,
|
|
11588
|
-
...sandbox === void 0 ? {} : { sandbox }
|
|
12175
|
+
...sandbox === void 0 ? {} : { sandbox },
|
|
12176
|
+
...project === void 0 ? {} : { project: {
|
|
12177
|
+
id: project.id,
|
|
12178
|
+
name: project.name
|
|
12179
|
+
} },
|
|
12180
|
+
updatedAt: DateTime.toEpochMillis(row.updatedAt),
|
|
12181
|
+
...model === void 0 ? {} : { model },
|
|
12182
|
+
...thinkingLevel === void 0 ? {} : { thinkingLevel },
|
|
12183
|
+
parked
|
|
11589
12184
|
};
|
|
11590
12185
|
}).pipe(Effect.withSpan("Session.info"));
|
|
11591
12186
|
return {
|
|
@@ -11613,7 +12208,7 @@ const create$1 = Effect.fn("Session.create")(function* (input = {}) {
|
|
|
11613
12208
|
const sandboxes = yield* Controller;
|
|
11614
12209
|
const id = ID$1.create();
|
|
11615
12210
|
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 });
|
|
12211
|
+
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
12212
|
yield* sessions.create({
|
|
11618
12213
|
id,
|
|
11619
12214
|
spaceId: location.space.id,
|
|
@@ -11727,7 +12322,7 @@ var plugin_exports = /* @__PURE__ */ __exportAll({
|
|
|
11727
12322
|
inspect: () => inspect,
|
|
11728
12323
|
lendResolve: () => lendResolve,
|
|
11729
12324
|
localName: () => localName,
|
|
11730
|
-
parse: () => parse$
|
|
12325
|
+
parse: () => parse$2,
|
|
11731
12326
|
probe: () => probe,
|
|
11732
12327
|
resolve: () => resolve$2,
|
|
11733
12328
|
resolveCached: () => required,
|
|
@@ -11740,6 +12335,6 @@ var DateTimeUtcFromMillis = schema_exports$9.DateTimeUtcFromMillis;
|
|
|
11740
12335
|
var EventSchema = schema_exports$7.EventSchema;
|
|
11741
12336
|
var SandboxError = errors_exports.SandboxError;
|
|
11742
12337
|
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 };
|
|
12338
|
+
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
12339
|
|
|
11745
12340
|
//# sourceMappingURL=effect.mjs.map
|