@codeworksh/harness 0.0.1-dev.20260925163524 → 0.1.0-dev.20261007133219
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -11
- package/effect.d.mts +2213 -826
- package/effect.mjs +1104 -550
- package/effect.mjs.map +1 -1
- package/package.json +5 -5
package/effect.mjs
CHANGED
|
@@ -3,32 +3,34 @@ import { a as io_exports, i as errors_exports, n as shell_exports, o as instance
|
|
|
3
3
|
import "./daytona-BAurx6Gp.mjs";
|
|
4
4
|
import "./vercel-C0v5nh20.mjs";
|
|
5
5
|
import { createRequire, registerHooks } from "node:module";
|
|
6
|
-
import { Cause, Clock, Config, Context, Crypto, DateTime, Deferred, Duration, Effect,
|
|
6
|
+
import { Cause, Clock, Config, Context, Crypto, DateTime, Deferred, Duration, Effect, Exit, Fiber, FiberSet, FileSystem, Layer, LayerMap, Option, Path, Predicate, PubSub, Queue, Random, Ref, Result, Schedule, Schema, SchemaIssue, Scope, Semaphore, Stream, String as String$1 } from "effect";
|
|
7
|
+
import { Message, Model, getEnvApiKey, llm, stream } from "@codeworksh/aikit";
|
|
7
8
|
import { MessageID as ID, SessionID as ID$1 } from "@codeworksh/plugin/ids";
|
|
8
|
-
import { Migrator, SqlClient, SqlClient as SqlClient$1, SqlError, SqlSchema, SqlSchema as SqlSchema$1 } from "effect/
|
|
9
|
+
import { Migrator, SqlClient, SqlClient as SqlClient$1, SqlError, SqlSchema, SqlSchema as SqlSchema$1 } from "effect/sql";
|
|
9
10
|
import { uuidv7 } from "uuidv7";
|
|
10
11
|
import { SqliteClient, SqliteMigrator } from "@effect/sql-sqlite-node";
|
|
11
12
|
import * as os from "node:os";
|
|
12
13
|
import { homedir, tmpdir } from "node:os";
|
|
13
14
|
import { NodeChildProcessSpawner, NodeCrypto, NodeFileSystem, NodePath } from "@effect/platform-node";
|
|
14
|
-
import { Model, VariantSchema } from "effect/
|
|
15
|
-
import {
|
|
15
|
+
import { Model as Model$1, VariantSchema } from "effect/schema";
|
|
16
|
+
import { Hex } from "effect/encoding";
|
|
16
17
|
import { MemoryProvider, RealFSProvider, SqliteProvider, create } from "@platformatic/vfs";
|
|
17
18
|
import fs, { statSync } from "node:fs";
|
|
18
|
-
import { ChildProcess, ChildProcessSpawner } from "effect/
|
|
19
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
19
20
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
20
21
|
import { Bash } from "just-bash";
|
|
21
|
-
import { DirectoryNotFoundError, DirectoryNotFoundError as DirectoryNotFoundError$1, Info, Info as Info$1, NotDirectoryError, NotDirectoryError as NotDirectoryError$1,
|
|
22
|
+
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";
|
|
22
23
|
import { generateModels } from "@codeworksh/aikit/modelgen";
|
|
24
|
+
import { JsonGitHubCopilotAuthStorage, getGitHubCopilotApiKey } from "@codeworksh/aikit/oauth/github/copilot";
|
|
25
|
+
import { JsonOpenAICodexAuthStorage, getOpenAICodexApiKey } from "@codeworksh/aikit/oauth/openai/codex";
|
|
26
|
+
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
27
|
+
import * as Section from "@codeworksh/plugin/plugin/section";
|
|
23
28
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
24
29
|
import path from "node:path";
|
|
25
30
|
import { Script, constants } from "node:vm";
|
|
26
31
|
import npa from "npm-package-arg";
|
|
27
32
|
import * as AikitFailure from "@codeworksh/aikit/failure";
|
|
28
|
-
import { JsonGitHubCopilotAuthStorage, getGitHubCopilotApiKey } from "@codeworksh/aikit/oauth/github/copilot";
|
|
29
|
-
import { JsonOpenAICodexAuthStorage, getOpenAICodexApiKey } from "@codeworksh/aikit/oauth/openai/codex";
|
|
30
33
|
import "@codeworksh/plugin/tool/error";
|
|
31
|
-
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
32
34
|
//#region src/schema.ts
|
|
33
35
|
var schema_exports$9 = /* @__PURE__ */ __exportAll({});
|
|
34
36
|
import * as import__codeworksh_plugin_schema from "@codeworksh/plugin/schema";
|
|
@@ -38,15 +40,46 @@ __reExport(schema_exports$9, import__codeworksh_plugin_schema);
|
|
|
38
40
|
var schema_exports$8 = /* @__PURE__ */ __exportAll({
|
|
39
41
|
AssistantEnvelopeUsage: () => AssistantEnvelopeUsage,
|
|
40
42
|
CompactionData: () => CompactionData,
|
|
43
|
+
Config: () => Config$1,
|
|
44
|
+
DEFAULT_TITLE: () => DEFAULT_TITLE,
|
|
41
45
|
ID: () => ID$1,
|
|
42
46
|
IDFromDb: () => IDFromDb,
|
|
43
47
|
InterruptReason: () => InterruptReason,
|
|
44
48
|
JsonObject: () => JsonObject,
|
|
45
49
|
MessageEnvelopeIdentity: () => MessageEnvelopeIdentity,
|
|
46
50
|
SessionSchema: () => schema_exports$8,
|
|
47
|
-
|
|
51
|
+
TITLE_MAX_LENGTH: () => 48,
|
|
52
|
+
ThinkingLevel: () => ThinkingLevel,
|
|
53
|
+
Usage: () => Usage,
|
|
54
|
+
titleFrom: () => titleFrom
|
|
48
55
|
});
|
|
49
56
|
const IDFromDb = Schema.String.pipe(Schema.brand("Session.ID"));
|
|
57
|
+
/** The title of a session created without one; its first prompt replaces it. */
|
|
58
|
+
const DEFAULT_TITLE = "Session";
|
|
59
|
+
/**
|
|
60
|
+
* A title from a prompt: its first non-empty line, whitespace collapsed, cut on a word boundary
|
|
61
|
+
* to {@link TITLE_MAX_LENGTH} characters including the ellipsis. `undefined` for a blank prompt.
|
|
62
|
+
*/
|
|
63
|
+
const titleFrom = (text) => {
|
|
64
|
+
const line = text.split("\n").map((one) => one.replace(/\s+/g, " ").trim()).find((one) => one.length > 0);
|
|
65
|
+
if (line === void 0 || line.length <= 48) return line;
|
|
66
|
+
const cut = line.slice(0, 47);
|
|
67
|
+
const space = cut.lastIndexOf(" ");
|
|
68
|
+
return `${(space > 0 ? cut.slice(0, space) : cut).trimEnd()}…`;
|
|
69
|
+
};
|
|
70
|
+
const ThinkingLevel = Schema.Literals(Object.values(Model.ThinkingLevelEnum));
|
|
71
|
+
/**
|
|
72
|
+
* The model and thinking level chosen for a session, over its settings. Every key is optional:
|
|
73
|
+
* an absent one means the session follows its settings. `session.config.changed` carries only the
|
|
74
|
+
* keys it changes, and the `session.config` column holds them merged.
|
|
75
|
+
*/
|
|
76
|
+
const Config$1 = Schema.Struct({
|
|
77
|
+
model: (0, schema_exports$9.optional)(Schema.Struct({
|
|
78
|
+
provider: Schema.String,
|
|
79
|
+
id: Schema.String
|
|
80
|
+
})),
|
|
81
|
+
thinkingLevel: (0, schema_exports$9.optional)(ThinkingLevel)
|
|
82
|
+
});
|
|
50
83
|
/**
|
|
51
84
|
* Why an execution stopped. Supplied by whoever asked for the interruption --
|
|
52
85
|
* a stop the user asked for and a stop the process imposed on shutdown are
|
|
@@ -101,10 +134,10 @@ var global_exports = /* @__PURE__ */ __exportAll({
|
|
|
101
134
|
app: () => app,
|
|
102
135
|
appConfigDir: () => appConfigDir,
|
|
103
136
|
authFile: () => authFile,
|
|
104
|
-
defaultLayer: () => defaultLayer$
|
|
137
|
+
defaultLayer: () => defaultLayer$2,
|
|
105
138
|
homeConfig: () => homeConfig,
|
|
106
139
|
layer: () => layer$27,
|
|
107
|
-
layerWith: () => layerWith
|
|
140
|
+
layerWith: () => layerWith,
|
|
108
141
|
make: () => make$18,
|
|
109
142
|
resolve: () => resolve$3
|
|
110
143
|
});
|
|
@@ -150,8 +183,8 @@ const build = (input) => Effect.gen(function* () {
|
|
|
150
183
|
return Service$17.of(paths);
|
|
151
184
|
});
|
|
152
185
|
const layer$27 = Layer.effect(Service$17, build({}));
|
|
153
|
-
const defaultLayer$
|
|
154
|
-
const layerWith
|
|
186
|
+
const defaultLayer$2 = layer$27;
|
|
187
|
+
const layerWith = (input) => Layer.effect(Service$17, build(input));
|
|
155
188
|
//#endregion
|
|
156
189
|
//#region src/db/migrations.ts
|
|
157
190
|
const migrations = {
|
|
@@ -329,6 +362,9 @@ const migrations = {
|
|
|
329
362
|
}),
|
|
330
363
|
"202609190001_session_host_dir": Effect.gen(function* () {
|
|
331
364
|
yield* (yield* SqlClient.SqlClient)`ALTER TABLE session ADD COLUMN host_dir TEXT`;
|
|
365
|
+
}),
|
|
366
|
+
"202609290001_session_config": Effect.gen(function* () {
|
|
367
|
+
yield* (yield* SqlClient.SqlClient)`ALTER TABLE session ADD COLUMN config TEXT`;
|
|
332
368
|
})
|
|
333
369
|
};
|
|
334
370
|
//#endregion
|
|
@@ -337,7 +373,7 @@ var db_exports = /* @__PURE__ */ __exportAll({
|
|
|
337
373
|
Database: () => db_exports,
|
|
338
374
|
SqlClient: () => SqlClient$1,
|
|
339
375
|
SqlSchema: () => SqlSchema$1,
|
|
340
|
-
defaultLayer: () => defaultLayer$
|
|
376
|
+
defaultLayer: () => defaultLayer$1,
|
|
341
377
|
layer: () => layer$26,
|
|
342
378
|
locationConfig: () => locationConfig,
|
|
343
379
|
path: () => path$1,
|
|
@@ -371,7 +407,7 @@ const path$1 = Effect.fn("Database.path")(function* (data) {
|
|
|
371
407
|
const directory = data ?? (yield* resolve$3()).data;
|
|
372
408
|
return resolveDatabaseLocation(yield* locationConfig, directory);
|
|
373
409
|
});
|
|
374
|
-
const defaultLayer$
|
|
410
|
+
const defaultLayer$1 = Layer.unwrap(path$1().pipe(Effect.map(layer$26)));
|
|
375
411
|
//#endregion
|
|
376
412
|
//#region src/event/schema.ts
|
|
377
413
|
var schema_exports$7 = /* @__PURE__ */ __exportAll({});
|
|
@@ -386,13 +422,54 @@ __reExport(schema_exports$6, import__codeworksh_plugin_project);
|
|
|
386
422
|
//#region src/session/prompt/schema.ts
|
|
387
423
|
var schema_exports = /* @__PURE__ */ __exportAll({
|
|
388
424
|
Delivery: () => Delivery,
|
|
425
|
+
IMAGE_MAX_BASE64: () => IMAGE_MAX_BASE64,
|
|
426
|
+
IMAGE_TYPES: () => IMAGE_TYPES,
|
|
427
|
+
ImagePart: () => ImagePart,
|
|
428
|
+
InvalidPromptError: () => InvalidPromptError,
|
|
429
|
+
Part: () => Part,
|
|
389
430
|
Prompt: () => Prompt,
|
|
390
|
-
PromptSchema: () => schema_exports
|
|
431
|
+
PromptSchema: () => schema_exports,
|
|
432
|
+
TextPart: () => TextPart,
|
|
433
|
+
imageProblem: () => imageProblem
|
|
434
|
+
});
|
|
435
|
+
const IMAGE_TYPES = [
|
|
436
|
+
"image/png",
|
|
437
|
+
"image/jpeg",
|
|
438
|
+
"image/gif",
|
|
439
|
+
"image/webp"
|
|
440
|
+
];
|
|
441
|
+
const IMAGE_MAX_BASE64 = 4718592;
|
|
442
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
443
|
+
const imageProblem = (image) => {
|
|
444
|
+
if (!IMAGE_TYPES.includes(image.mimeType)) return `Unsupported image type ${image.mimeType}; use ${IMAGE_TYPES.join(", ")}`;
|
|
445
|
+
if (image.data.length > 4718592) return `Image exceeds the ${IMAGE_MAX_BASE64 / 1024 / 1024} MB limit of base64`;
|
|
446
|
+
if (image.data.length === 0 || image.data.length % 4 !== 0 || !BASE64.test(image.data)) return "Image data is not base64";
|
|
447
|
+
};
|
|
448
|
+
const TextPart = Schema.Struct({
|
|
449
|
+
type: Schema.Literal("text"),
|
|
450
|
+
text: Schema.String
|
|
391
451
|
});
|
|
392
|
-
|
|
452
|
+
/** aikit's image content: base64 `data` and its `mimeType`. */
|
|
453
|
+
const ImagePart = Schema.Struct({
|
|
454
|
+
type: Schema.Literal("image"),
|
|
455
|
+
data: Schema.String,
|
|
456
|
+
mimeType: Schema.String
|
|
457
|
+
}).check(Schema.makeFilter(imageProblem));
|
|
458
|
+
const Part = Schema.Union([TextPart, ImagePart]);
|
|
459
|
+
/** A user prompt: aikit user-message parts, in the order they were written. */
|
|
460
|
+
const Prompt = Schema.Struct({ parts: Schema.Array(Part).check(Schema.isMinLength(1)) }).annotate({ identifier: "Prompt" }).pipe((0, schema_exports$9.withStatics)((schema) => ({
|
|
393
461
|
equivalence: Schema.toEquivalence(schema),
|
|
394
|
-
|
|
462
|
+
fromText: (text) => schema.make({ parts: [{
|
|
463
|
+
type: "text",
|
|
464
|
+
text
|
|
465
|
+
}] }),
|
|
466
|
+
text: (prompt) => prompt.parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("\n\n")
|
|
395
467
|
})));
|
|
468
|
+
var InvalidPromptError = class extends Schema.TaggedError()("InvalidPromptError", { reason: Schema.String }) {
|
|
469
|
+
get message() {
|
|
470
|
+
return this.reason;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
396
473
|
const Delivery = Schema.Literals(["steer", "followUp"]);
|
|
397
474
|
//#endregion
|
|
398
475
|
//#region src/space/schema.ts
|
|
@@ -402,8 +479,8 @@ __reExport(schema_exports$5, import__codeworksh_plugin_space);
|
|
|
402
479
|
//#endregion
|
|
403
480
|
//#region src/db/schema.sql.ts
|
|
404
481
|
const Timestamps = {
|
|
405
|
-
createdAt: Model.DateTimeInsertFromNumber,
|
|
406
|
-
updatedAt: Model.DateTimeUpdateFromNumber
|
|
482
|
+
createdAt: Model$1.DateTimeInsertFromNumber,
|
|
483
|
+
updatedAt: Model$1.DateTimeUpdateFromNumber
|
|
407
484
|
};
|
|
408
485
|
const Metadata = Schema.Record(Schema.String, Schema.String);
|
|
409
486
|
const entryTypes = [
|
|
@@ -441,19 +518,19 @@ const toolStatuses = [
|
|
|
441
518
|
];
|
|
442
519
|
const inputDeliveries = ["steer", "followUp"];
|
|
443
520
|
const EventData = Schema.Record(Schema.String, Schema.Unknown);
|
|
444
|
-
Model.Class("EventSequenceRow")({
|
|
521
|
+
Model$1.Class("EventSequenceRow")({
|
|
445
522
|
aggregateId: Schema.String,
|
|
446
523
|
seq: Schema.Natural,
|
|
447
|
-
ownerId: Model.FieldOption(Schema.String)
|
|
524
|
+
ownerId: Model$1.FieldOption(Schema.String)
|
|
448
525
|
});
|
|
449
|
-
var EventRow = class extends Model.Class("EventRow")({
|
|
526
|
+
var EventRow = class extends Model$1.Class("EventRow")({
|
|
450
527
|
id: schema_exports$7.EventSchema.ID,
|
|
451
528
|
aggregateId: Schema.String,
|
|
452
529
|
seq: Schema.Natural,
|
|
453
530
|
type: Schema.String,
|
|
454
|
-
data: Model.JsonFromString(EventData)
|
|
531
|
+
data: Model$1.JsonFromString(EventData)
|
|
455
532
|
}) {};
|
|
456
|
-
var ProjectRow = class extends Model.Class("ProjectRow")({
|
|
533
|
+
var ProjectRow = class extends Model$1.Class("ProjectRow")({
|
|
457
534
|
id: Schema.String,
|
|
458
535
|
name: Schema.String,
|
|
459
536
|
status: schema_exports$6.ProjectSchema.Status,
|
|
@@ -467,81 +544,82 @@ var ProjectRow = class extends Model.Class("ProjectRow")({
|
|
|
467
544
|
* The host has no row: `local` is reserved and `NULL` is its storage form, so
|
|
468
545
|
* this table only ever holds namespaces something had to provision.
|
|
469
546
|
*/
|
|
470
|
-
var SandboxInstanceRow = class extends Model.Class("SandboxInstanceRow")({
|
|
547
|
+
var SandboxInstanceRow = class extends Model$1.Class("SandboxInstanceRow")({
|
|
471
548
|
id: instance_exports.SandboxInstance.ID,
|
|
472
549
|
driver: Schema.String,
|
|
473
550
|
kind: instance_exports.SandboxInstance.Kind,
|
|
474
|
-
providerResourceId: Model.FieldOption(Schema.String),
|
|
475
|
-
runtimeConfig: Model.FieldOption(Schema.String),
|
|
551
|
+
providerResourceId: Model$1.FieldOption(Schema.String),
|
|
552
|
+
runtimeConfig: Model$1.FieldOption(Schema.String),
|
|
476
553
|
ownership: instance_exports.SandboxInstance.Ownership,
|
|
477
554
|
status: instance_exports.SandboxInstance.Status,
|
|
478
|
-
providerStatus: Model.FieldOption(Schema.String),
|
|
479
|
-
stateObservedAt: Model.DateTimeFromNumberWithNow,
|
|
480
|
-
metadata: Model.FieldOption(Model.JsonFromString(Metadata)),
|
|
481
|
-
lastError: Model.FieldOption(Model.JsonFromString(instance_exports.SandboxInstance.PersistedError)),
|
|
482
|
-
lastMountedAt: Model.FieldOption(Schema.DateTimeUtcFromMillis),
|
|
483
|
-
lastUnmountedAt: Model.FieldOption(Schema.DateTimeUtcFromMillis),
|
|
484
|
-
lastUsedAt: Model.FieldOption(Schema.DateTimeUtcFromMillis),
|
|
485
|
-
removedAt: Model.FieldOption(Schema.DateTimeUtcFromMillis),
|
|
555
|
+
providerStatus: Model$1.FieldOption(Schema.String),
|
|
556
|
+
stateObservedAt: Model$1.DateTimeFromNumberWithNow,
|
|
557
|
+
metadata: Model$1.FieldOption(Model$1.JsonFromString(Metadata)),
|
|
558
|
+
lastError: Model$1.FieldOption(Model$1.JsonFromString(instance_exports.SandboxInstance.PersistedError)),
|
|
559
|
+
lastMountedAt: Model$1.FieldOption(Schema.DateTimeUtcFromMillis),
|
|
560
|
+
lastUnmountedAt: Model$1.FieldOption(Schema.DateTimeUtcFromMillis),
|
|
561
|
+
lastUsedAt: Model$1.FieldOption(Schema.DateTimeUtcFromMillis),
|
|
562
|
+
removedAt: Model$1.FieldOption(Schema.DateTimeUtcFromMillis),
|
|
486
563
|
...Timestamps
|
|
487
564
|
}) {};
|
|
488
|
-
var SpaceRow = class extends Model.Class("SpaceRow")({
|
|
565
|
+
var SpaceRow = class extends Model$1.Class("SpaceRow")({
|
|
489
566
|
id: Schema.String,
|
|
490
567
|
projectId: Schema.String,
|
|
491
568
|
location: schema_exports$9.AbsolutePath,
|
|
492
569
|
kind: schema_exports$5.SpaceSchema.Kind,
|
|
493
|
-
env: Model.FieldOption(instance_exports.SandboxInstance.ID),
|
|
570
|
+
env: Model$1.FieldOption(instance_exports.SandboxInstance.ID),
|
|
494
571
|
status: schema_exports$5.SpaceSchema.Status,
|
|
495
572
|
...Timestamps
|
|
496
573
|
}) {};
|
|
497
|
-
var SessionRow = class extends Model.Class("SessionRow")({
|
|
574
|
+
var SessionRow = class extends Model$1.Class("SessionRow")({
|
|
498
575
|
id: IDFromDb,
|
|
499
576
|
spaceId: Schema.String,
|
|
500
|
-
parentId: Model.FieldOption(IDFromDb),
|
|
577
|
+
parentId: Model$1.FieldOption(IDFromDb),
|
|
501
578
|
slug: Schema.String,
|
|
502
579
|
directory: schema_exports$9.AbsolutePath,
|
|
503
|
-
hostDir: Model.FieldOption(schema_exports$9.AbsolutePath),
|
|
580
|
+
hostDir: Model$1.FieldOption(schema_exports$9.AbsolutePath),
|
|
581
|
+
config: Model$1.FieldOption(Model$1.JsonFromString(Config$1)),
|
|
504
582
|
title: Schema.String,
|
|
505
|
-
tag: Model.FieldOption(Schema.String),
|
|
506
|
-
metadata: Model.FieldOption(Model.JsonFromString(Metadata)),
|
|
507
|
-
leafEntryId: Model.FieldOption(Schema.String),
|
|
508
|
-
cost: Model.GeneratedByDb(Schema.Finite),
|
|
509
|
-
tokensInput: Model.GeneratedByDb(Schema.Int),
|
|
510
|
-
tokensOutput: Model.GeneratedByDb(Schema.Int),
|
|
511
|
-
tokensCacheRead: Model.GeneratedByDb(Schema.Int),
|
|
512
|
-
tokensCacheWrite: Model.GeneratedByDb(Schema.Int),
|
|
583
|
+
tag: Model$1.FieldOption(Schema.String),
|
|
584
|
+
metadata: Model$1.FieldOption(Model$1.JsonFromString(Metadata)),
|
|
585
|
+
leafEntryId: Model$1.FieldOption(Schema.String),
|
|
586
|
+
cost: Model$1.GeneratedByDb(Schema.Finite),
|
|
587
|
+
tokensInput: Model$1.GeneratedByDb(Schema.Int),
|
|
588
|
+
tokensOutput: Model$1.GeneratedByDb(Schema.Int),
|
|
589
|
+
tokensCacheRead: Model$1.GeneratedByDb(Schema.Int),
|
|
590
|
+
tokensCacheWrite: Model$1.GeneratedByDb(Schema.Int),
|
|
513
591
|
...Timestamps
|
|
514
592
|
}) {};
|
|
515
|
-
var SessionInputRow = class extends Model.Class("SessionInputRow")({
|
|
593
|
+
var SessionInputRow = class extends Model$1.Class("SessionInputRow")({
|
|
516
594
|
id: Schema.String,
|
|
517
595
|
sessionId: IDFromDb,
|
|
518
|
-
prompt: Model.JsonFromString(Prompt),
|
|
596
|
+
prompt: Model$1.JsonFromString(Prompt),
|
|
519
597
|
delivery: Schema.Literals(inputDeliveries),
|
|
520
598
|
admittedSeq: Schema.Int,
|
|
521
|
-
promotedSeq: Model.FieldOption(Schema.Int),
|
|
522
|
-
createdAt: Model.DateTimeInsertFromNumber
|
|
599
|
+
promotedSeq: Model$1.FieldOption(Schema.Int),
|
|
600
|
+
createdAt: Model$1.DateTimeInsertFromNumber
|
|
523
601
|
}) {};
|
|
524
|
-
var SessionEntryRow = class extends Model.Class("SessionEntryRow")({
|
|
602
|
+
var SessionEntryRow = class extends Model$1.Class("SessionEntryRow")({
|
|
525
603
|
id: Schema.String,
|
|
526
604
|
sessionId: IDFromDb,
|
|
527
|
-
parentId: Model.FieldOption(Schema.String),
|
|
605
|
+
parentId: Model$1.FieldOption(Schema.String),
|
|
528
606
|
seq: schema_exports$9.NonNegativeInt,
|
|
529
607
|
type: Schema.Literals(entryTypes),
|
|
530
608
|
state: Schema.Literals(entryStates),
|
|
531
609
|
data: Schema.String,
|
|
532
|
-
label: Model.FieldOption(Schema.String),
|
|
533
|
-
metadata: Model.FieldOption(Model.JsonFromString(Metadata)),
|
|
610
|
+
label: Model$1.FieldOption(Schema.String),
|
|
611
|
+
metadata: Model$1.FieldOption(Model$1.JsonFromString(Metadata)),
|
|
534
612
|
...Timestamps
|
|
535
613
|
}) {};
|
|
536
|
-
var SessionEntryPartRow = class extends Model.Class("SessionEntryPartRow")({
|
|
614
|
+
var SessionEntryPartRow = class extends Model$1.Class("SessionEntryPartRow")({
|
|
537
615
|
id: Schema.String,
|
|
538
616
|
entryId: Schema.String,
|
|
539
617
|
sessionId: IDFromDb,
|
|
540
618
|
partIndex: Schema.Int,
|
|
541
619
|
type: Schema.Literals(partTypes),
|
|
542
|
-
status: Model.FieldOption(Schema.Literals(toolStatuses)),
|
|
543
|
-
callId: Model.FieldOption(Schema.String),
|
|
544
|
-
toolName: Model.FieldOption(Schema.String),
|
|
620
|
+
status: Model$1.FieldOption(Schema.Literals(toolStatuses)),
|
|
621
|
+
callId: Model$1.FieldOption(Schema.String),
|
|
622
|
+
toolName: Model$1.FieldOption(Schema.String),
|
|
545
623
|
data: Schema.String,
|
|
546
624
|
...Timestamps
|
|
547
625
|
}) {};
|
|
@@ -631,6 +709,7 @@ var schema_exports$4 = /* @__PURE__ */ __exportAll({
|
|
|
631
709
|
//#endregion
|
|
632
710
|
//#region src/event/list.ts
|
|
633
711
|
var list_exports = /* @__PURE__ */ __exportAll({
|
|
712
|
+
ConfigChanged: () => ConfigChanged,
|
|
634
713
|
Definitions: () => Definitions,
|
|
635
714
|
DurableDefinitions: () => DurableDefinitions,
|
|
636
715
|
EventList: () => list_exports,
|
|
@@ -651,6 +730,8 @@ var list_exports = /* @__PURE__ */ __exportAll({
|
|
|
651
730
|
PromptAdmitted: () => PromptAdmitted,
|
|
652
731
|
Prompted: () => Prompted,
|
|
653
732
|
PublicDefinitions: () => PublicDefinitions,
|
|
733
|
+
RetryFinished: () => RetryFinished,
|
|
734
|
+
RetryScheduled: () => RetryScheduled,
|
|
654
735
|
SessionForked: () => SessionForked,
|
|
655
736
|
ToolProgress: () => ToolProgress$2,
|
|
656
737
|
ToolSettled: () => ToolSettled,
|
|
@@ -684,6 +765,14 @@ const Prompted = schema_exports$7.EventSchema.define({
|
|
|
684
765
|
...durableOptions,
|
|
685
766
|
schema: PromptFields
|
|
686
767
|
});
|
|
768
|
+
const ConfigChanged = schema_exports$7.EventSchema.define({
|
|
769
|
+
type: "session.config.changed",
|
|
770
|
+
...durableOptions,
|
|
771
|
+
schema: {
|
|
772
|
+
...baseOptions,
|
|
773
|
+
...Config$1.fields
|
|
774
|
+
}
|
|
775
|
+
});
|
|
687
776
|
const LLMFields = {
|
|
688
777
|
...baseOptions,
|
|
689
778
|
messageId: ID
|
|
@@ -713,6 +802,30 @@ const TurnAborted = schema_exports$7.EventSchema.define({
|
|
|
713
802
|
cause: TurnAbortCause
|
|
714
803
|
}
|
|
715
804
|
});
|
|
805
|
+
/**
|
|
806
|
+
* A failed provider request will be repeated after `delayMs`. `attempt` counts retries from 1, so
|
|
807
|
+
* the request that failed was attempt `attempt - 1`; the failed draft is already settled.
|
|
808
|
+
*/
|
|
809
|
+
const RetryScheduled = schema_exports$7.EventSchema.define({
|
|
810
|
+
type: "session.retry.scheduled",
|
|
811
|
+
schema: {
|
|
812
|
+
...baseOptions,
|
|
813
|
+
attempt: schema_exports$9.NonNegativeInt,
|
|
814
|
+
maxRetries: schema_exports$9.NonNegativeInt,
|
|
815
|
+
delayMs: schema_exports$9.NonNegativeInt,
|
|
816
|
+
message: Schema.String
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
/** Once per turn that retried: whether a retry succeeded, after how many. */
|
|
820
|
+
const RetryFinished = schema_exports$7.EventSchema.define({
|
|
821
|
+
type: "session.retry.finished",
|
|
822
|
+
schema: {
|
|
823
|
+
...baseOptions,
|
|
824
|
+
attempt: schema_exports$9.NonNegativeInt,
|
|
825
|
+
success: Schema.Boolean,
|
|
826
|
+
message: (0, schema_exports$9.optional)(Schema.String)
|
|
827
|
+
}
|
|
828
|
+
});
|
|
716
829
|
/** Durable insertion of the request's draft assistant placeholder. */
|
|
717
830
|
const LLMStarted = schema_exports$7.EventSchema.define({
|
|
718
831
|
type: "session.llm.started",
|
|
@@ -787,7 +900,10 @@ const ToolStarted = schema_exports$7.EventSchema.define({
|
|
|
787
900
|
schema: {
|
|
788
901
|
...LLMFields,
|
|
789
902
|
callID: Schema.String,
|
|
790
|
-
name: Schema.String
|
|
903
|
+
name: Schema.String,
|
|
904
|
+
/** The tool's display label, when its definition declares one. */
|
|
905
|
+
label: (0, schema_exports$9.optional)(Schema.String),
|
|
906
|
+
arguments: Schema.Record(Schema.String, Schema.Json)
|
|
791
907
|
}
|
|
792
908
|
});
|
|
793
909
|
const ToolProgress$2 = schema_exports$7.EventSchema.define({
|
|
@@ -883,14 +999,14 @@ const PluginUpdated = schema_exports$7.EventSchema.define({
|
|
|
883
999
|
* Everything the kernel can publish. `PublicDefinitions` is the narrower set
|
|
884
1000
|
* that reaches clients -- adding an event here does not publish it.
|
|
885
1001
|
*/
|
|
886
|
-
const Definitions = schema_exports$7.EventSchema.inventory(ExecutionStarted, ExecutionSucceeded, ExecutionFailed, ExecutionInterrupted, PromptAdmitted, Prompted, SessionForked, TurnStarted, TurnEnded, TurnAborted, LLMStarted, LLMTextStart, LLMTextDelta, LLMTextEnd, LLMThinkingStart, LLMThinkingDelta, LLMThinkingEnd, LLMEnded, LLMFailed, ToolStarted, ToolProgress$2, ToolSettled, PluginUpdated);
|
|
1002
|
+
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);
|
|
887
1003
|
/**
|
|
888
1004
|
* The wire contract: what a client is allowed to receive. Identical to
|
|
889
1005
|
* `Definitions` today, and separate so that adding a kernel event is not by
|
|
890
1006
|
* itself a decision to publish it.
|
|
891
1007
|
*/
|
|
892
1008
|
const PublicDefinitions = Definitions;
|
|
893
|
-
const DurableDefinitions = schema_exports$7.EventSchema.inventory(PromptAdmitted, Prompted, SessionForked, TurnEnded, LLMStarted, LLMEnded, LLMFailed, ToolSettled);
|
|
1009
|
+
const DurableDefinitions = schema_exports$7.EventSchema.inventory(PromptAdmitted, Prompted, ConfigChanged, SessionForked, TurnEnded, LLMStarted, LLMEnded, LLMFailed, ToolSettled);
|
|
894
1010
|
const Manifest$2 = {
|
|
895
1011
|
definitions: schema_exports$7.EventSchema.durable([...DurableDefinitions]),
|
|
896
1012
|
schema: Schema.Union([...DurableDefinitions], { mode: "oneOf" }).pipe(Schema.toTaggedUnion("type"))
|
|
@@ -1235,7 +1351,7 @@ const layer$25 = Layer.effect(Service$16, Effect.gen(function* () {
|
|
|
1235
1351
|
const encoder = new TextEncoder();
|
|
1236
1352
|
function fast(input) {
|
|
1237
1353
|
const data = typeof input === "string" ? encoder.encode(input) : input;
|
|
1238
|
-
return
|
|
1354
|
+
return Hex.encode(Effect.runSync(crypto.digest("SHA-1", data)));
|
|
1239
1355
|
}
|
|
1240
1356
|
//#endregion
|
|
1241
1357
|
//#region src/space/space.ts
|
|
@@ -1377,6 +1493,77 @@ const layer$24 = Layer.effect(Service$15, Effect.gen(function* () {
|
|
|
1377
1493
|
});
|
|
1378
1494
|
}));
|
|
1379
1495
|
//#endregion
|
|
1496
|
+
//#region src/util/slug.ts
|
|
1497
|
+
const PREFIXES = [
|
|
1498
|
+
"binary",
|
|
1499
|
+
"protocol",
|
|
1500
|
+
"astromech",
|
|
1501
|
+
"thermal",
|
|
1502
|
+
"ionic",
|
|
1503
|
+
"encrypted",
|
|
1504
|
+
"holonet",
|
|
1505
|
+
"hyperspace",
|
|
1506
|
+
"magnetic",
|
|
1507
|
+
"logic",
|
|
1508
|
+
"plasma",
|
|
1509
|
+
"vector",
|
|
1510
|
+
"optical",
|
|
1511
|
+
"quantum",
|
|
1512
|
+
"mechanical",
|
|
1513
|
+
"servo",
|
|
1514
|
+
"fusion",
|
|
1515
|
+
"flux",
|
|
1516
|
+
"sychro",
|
|
1517
|
+
"static"
|
|
1518
|
+
];
|
|
1519
|
+
const HARDWARE = [
|
|
1520
|
+
"core",
|
|
1521
|
+
"unit",
|
|
1522
|
+
"node",
|
|
1523
|
+
"array",
|
|
1524
|
+
"link",
|
|
1525
|
+
"processor",
|
|
1526
|
+
"module",
|
|
1527
|
+
"uplink",
|
|
1528
|
+
"interface",
|
|
1529
|
+
"splicer",
|
|
1530
|
+
"matrix",
|
|
1531
|
+
"buffer",
|
|
1532
|
+
"relay",
|
|
1533
|
+
"circuit",
|
|
1534
|
+
"sensor",
|
|
1535
|
+
"driver",
|
|
1536
|
+
"bridge",
|
|
1537
|
+
"manifold",
|
|
1538
|
+
"oscillator",
|
|
1539
|
+
"terminal"
|
|
1540
|
+
];
|
|
1541
|
+
const SECTORS = [
|
|
1542
|
+
"kuat",
|
|
1543
|
+
"corellia",
|
|
1544
|
+
"coruscant",
|
|
1545
|
+
"bespin",
|
|
1546
|
+
"kamino",
|
|
1547
|
+
"mustafar",
|
|
1548
|
+
"fondor",
|
|
1549
|
+
"hosnian",
|
|
1550
|
+
"lothal",
|
|
1551
|
+
"scarif",
|
|
1552
|
+
"sub-level",
|
|
1553
|
+
"deep-space",
|
|
1554
|
+
"outer-rim",
|
|
1555
|
+
"mid-rim",
|
|
1556
|
+
"sector-7"
|
|
1557
|
+
];
|
|
1558
|
+
const CONNECTORS = [
|
|
1559
|
+
"at",
|
|
1560
|
+
"of",
|
|
1561
|
+
"in"
|
|
1562
|
+
];
|
|
1563
|
+
const create$3 = Effect.fnUntraced(function* (id) {
|
|
1564
|
+
return `${yield* Random.choice(PREFIXES)}-${yield* Random.choice(HARDWARE)}-${yield* Random.choice(CONNECTORS)}-${yield* Random.choice(SECTORS)}-${id.slice(-8)}`;
|
|
1565
|
+
});
|
|
1566
|
+
//#endregion
|
|
1380
1567
|
//#region src/session/session.ts
|
|
1381
1568
|
var session_exports$1 = /* @__PURE__ */ __exportAll({
|
|
1382
1569
|
EntryNotFoundError: () => EntryNotFoundError,
|
|
@@ -1391,7 +1578,7 @@ var session_exports$1 = /* @__PURE__ */ __exportAll({
|
|
|
1391
1578
|
SessionNotLinkedError: () => SessionNotLinkedError,
|
|
1392
1579
|
SessionRow: () => SessionRow,
|
|
1393
1580
|
ToolCallNotFoundError: () => ToolCallNotFoundError,
|
|
1394
|
-
defaultLayer: () => defaultLayer
|
|
1581
|
+
defaultLayer: () => defaultLayer,
|
|
1395
1582
|
entryStates: () => entryStates,
|
|
1396
1583
|
entryTypes: () => entryTypes,
|
|
1397
1584
|
layer: () => layer$23,
|
|
@@ -1603,9 +1790,10 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
1603
1790
|
id,
|
|
1604
1791
|
spaceId: input.spaceId,
|
|
1605
1792
|
parentId: Option.fromUndefinedOr(input.parentId),
|
|
1606
|
-
slug:
|
|
1793
|
+
slug: yield* create$3(id),
|
|
1607
1794
|
directory: input.directory,
|
|
1608
1795
|
hostDir: Option.fromUndefinedOr(input.hostDir),
|
|
1796
|
+
config: Option.none(),
|
|
1609
1797
|
title: input.title,
|
|
1610
1798
|
tag: Option.fromUndefinedOr(input.tag),
|
|
1611
1799
|
metadata: Option.fromUndefinedOr(input.metadata),
|
|
@@ -1948,9 +2136,10 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
1948
2136
|
id: newSessionId,
|
|
1949
2137
|
spaceId: source.value.spaceId,
|
|
1950
2138
|
parentId: Option.some(source.value.id),
|
|
1951
|
-
slug:
|
|
2139
|
+
slug: yield* create$3(newSessionId),
|
|
1952
2140
|
directory: source.value.directory,
|
|
1953
2141
|
hostDir: source.value.hostDir,
|
|
2142
|
+
config: source.value.config,
|
|
1954
2143
|
title: input.title ?? source.value.title,
|
|
1955
2144
|
tag: input.tag === void 0 ? source.value.tag : Option.some(input.tag),
|
|
1956
2145
|
metadata: source.value.metadata,
|
|
@@ -2045,6 +2234,21 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2045
2234
|
if (Option.isNone(updated)) return yield* Effect.die(/* @__PURE__ */ new Error("session link did not persist"));
|
|
2046
2235
|
return updated.value;
|
|
2047
2236
|
});
|
|
2237
|
+
const retitle = Effect.fn("Session.retitle")(function* (sessionId, title) {
|
|
2238
|
+
return (yield* sql`
|
|
2239
|
+
UPDATE session SET title = ${title}
|
|
2240
|
+
WHERE id = ${sessionId} AND title = ${DEFAULT_TITLE}
|
|
2241
|
+
RETURNING id
|
|
2242
|
+
`.pipe(Effect.orDie)).length > 0;
|
|
2243
|
+
});
|
|
2244
|
+
const encodeConfig = Schema.encodeEffect(Schema.fromJsonString(Config$1));
|
|
2245
|
+
const configure = Effect.fn("Session.configure")(function* (sessionId, config) {
|
|
2246
|
+
const patch = yield* encodeConfig(config);
|
|
2247
|
+
yield* sql`
|
|
2248
|
+
UPDATE session SET config = json_patch(COALESCE(config, '{}'), ${patch})
|
|
2249
|
+
WHERE id = ${sessionId}
|
|
2250
|
+
`;
|
|
2251
|
+
}, Effect.orDie);
|
|
2048
2252
|
const relink = Effect.fn("Session.relink")(function* (input) {
|
|
2049
2253
|
const reject = (reason) => new RelinkError({
|
|
2050
2254
|
sessionId: input.sessionId,
|
|
@@ -2104,6 +2308,8 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2104
2308
|
return Service$14.of({
|
|
2105
2309
|
create,
|
|
2106
2310
|
link,
|
|
2311
|
+
retitle,
|
|
2312
|
+
configure,
|
|
2107
2313
|
get,
|
|
2108
2314
|
space,
|
|
2109
2315
|
list,
|
|
@@ -2122,7 +2328,7 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2122
2328
|
setLabel
|
|
2123
2329
|
});
|
|
2124
2330
|
}));
|
|
2125
|
-
const defaultLayer
|
|
2331
|
+
const defaultLayer = layer$23.pipe(Layer.provide(defaultLayer$1));
|
|
2126
2332
|
//#endregion
|
|
2127
2333
|
//#region src/context/errors.ts
|
|
2128
2334
|
var ContextDecodeError = class extends Schema.TaggedError()("ContextDecodeError", {
|
|
@@ -3857,17 +4063,6 @@ const make$9 = () => {
|
|
|
3857
4063
|
}) };
|
|
3858
4064
|
};
|
|
3859
4065
|
//#endregion
|
|
3860
|
-
//#region src/sandbox/sandbox.ts
|
|
3861
|
-
/**
|
|
3862
|
-
* Assemble a local backend into a sandbox with a **real host shell** — the
|
|
3863
|
-
* right choice whenever commands must invoke actual binaries (`git`, package
|
|
3864
|
-
* managers). For a sandbox whose shell should stay inside the VFS, use
|
|
3865
|
-
* `EnvBash.services` instead, which wires just-bash over the same tree.
|
|
3866
|
-
*/
|
|
3867
|
-
const services = (backend, identity) => Layer.provideMerge(io_exports.SandboxIO.mount(identity), Layer.provideMerge(Layer.merge(layer$21, layer$18()), backend));
|
|
3868
|
-
/** Default sandbox: the real OS filesystem and processes, mounted at `cwd`. */
|
|
3869
|
-
const defaultLayer$1 = (cwd) => services(layer$20(), io_exports.SandboxIO.host(cwd));
|
|
3870
|
-
//#endregion
|
|
3871
4066
|
//#region src/git/git.ts
|
|
3872
4067
|
var AppProcessError = class extends Schema.TaggedError()("AppProcessError", {
|
|
3873
4068
|
command: Schema.String,
|
|
@@ -4351,16 +4546,13 @@ var location_exports = /* @__PURE__ */ __exportAll({
|
|
|
4351
4546
|
Info: () => Info$1,
|
|
4352
4547
|
Location: () => location_exports,
|
|
4353
4548
|
NotDirectoryError: () => NotDirectoryError,
|
|
4354
|
-
Ref: () => Ref$2,
|
|
4355
4549
|
Service: () => Service$8,
|
|
4356
|
-
defaultLayer: () => defaultLayer,
|
|
4357
4550
|
layer: () => layer$11,
|
|
4358
|
-
layerMounted: () => layerMounted
|
|
4359
|
-
layerWith: () => layerWith
|
|
4551
|
+
layerMounted: () => layerMounted
|
|
4360
4552
|
});
|
|
4361
|
-
const layer$11 = (
|
|
4553
|
+
const layer$11 = () => Layer.effect(Service, Effect.gen(function* () {
|
|
4362
4554
|
const instance = yield* io_exports.SandboxIO.Current;
|
|
4363
|
-
const directory = schema_exports$9.AbsolutePath.make(
|
|
4555
|
+
const directory = schema_exports$9.AbsolutePath.make(instance.cwd);
|
|
4364
4556
|
const resolved = yield* (yield* Service$9).resolveOrCreate(directory);
|
|
4365
4557
|
return Service.of(new Info({
|
|
4366
4558
|
directory: resolved.directory,
|
|
@@ -4373,9 +4565,7 @@ const layer$11 = (ref = {}) => Layer.effect(Service, Effect.gen(function* () {
|
|
|
4373
4565
|
* folded in. What remains is exactly the mount and the database — for callers
|
|
4374
4566
|
* that already hold both, such as the runner and `Session.create`.
|
|
4375
4567
|
*/
|
|
4376
|
-
const layerMounted = (
|
|
4377
|
-
const layerWith = (ref, sandbox) => layerMounted(ref).pipe(Layer.provide(sandbox), Layer.provide(defaultLayer$3));
|
|
4378
|
-
const defaultLayer = (ref, path) => layerWith(ref, defaultLayer$1(path));
|
|
4568
|
+
const layerMounted = () => layer$11().pipe(Layer.provide(layer$12.pipe(Layer.provide(Layer.mergeAll(layer$14, layer$13, layer$24)), Layer.provide(layer$15))));
|
|
4379
4569
|
//#endregion
|
|
4380
4570
|
//#region src/runner/run.ts
|
|
4381
4571
|
var run_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -4870,91 +5060,340 @@ const layer$9 = (definitions = Definitions) => Layer.sync(Service$4, () => {
|
|
|
4870
5060
|
});
|
|
4871
5061
|
});
|
|
4872
5062
|
//#endregion
|
|
4873
|
-
//#region src/
|
|
4874
|
-
var
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
5063
|
+
//#region src/settings/schema.ts
|
|
5064
|
+
var schema_exports$3 = /* @__PURE__ */ __exportAll({});
|
|
5065
|
+
import * as import__codeworksh_plugin_settings from "@codeworksh/plugin/settings";
|
|
5066
|
+
__reExport(schema_exports$3, import__codeworksh_plugin_settings);
|
|
5067
|
+
//#endregion
|
|
5068
|
+
//#region src/settings/merge.ts
|
|
5069
|
+
/**
|
|
5070
|
+
* A plain string-keyed record: what a settings patch merges, and what a plugin's `options` block
|
|
5071
|
+
* has to be. A Map, a typed array or a class instance is an object to `typeof` and not one of
|
|
5072
|
+
* these — `Object.freeze` alone does not tell them apart, and it throws on some of them.
|
|
5073
|
+
*/
|
|
5074
|
+
const isRecord$1 = (value) => Predicate.isObject(value) && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
5075
|
+
const copy = (value) => {
|
|
5076
|
+
if (Array.isArray(value)) return value.map(copy);
|
|
5077
|
+
return isRecord$1(value) ? merge({}, value) : value;
|
|
5078
|
+
};
|
|
5079
|
+
/** Null/undefined properties are absent, objects merge, and arrays replace. Never mutates inputs. */
|
|
5080
|
+
function merge(base, ...patches) {
|
|
5081
|
+
const result = /* @__PURE__ */ new Map();
|
|
5082
|
+
for (const patch of [base, ...patches]) {
|
|
5083
|
+
if (patch === void 0) continue;
|
|
5084
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
5085
|
+
if (value === null || value === void 0) continue;
|
|
5086
|
+
const previous = result.get(key);
|
|
5087
|
+
result.set(key, isRecord$1(previous) && isRecord$1(value) ? merge(previous, value) : copy(value));
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
return Object.fromEntries(result);
|
|
5091
|
+
}
|
|
5092
|
+
/** Normalize nullable properties before schema validation, without changing array positions. */
|
|
5093
|
+
const normalize$1 = copy;
|
|
5094
|
+
//#endregion
|
|
5095
|
+
//#region src/settings/settings.ts
|
|
5096
|
+
var settings_exports = /* @__PURE__ */ __exportAll({
|
|
5097
|
+
Service: () => Service$3,
|
|
5098
|
+
Settings: () => settings_exports,
|
|
5099
|
+
SettingsError: () => SettingsError,
|
|
5100
|
+
defaults: () => schema_exports$3.defaults,
|
|
4878
5101
|
layer: () => layer$8,
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
5102
|
+
load: () => load$3,
|
|
5103
|
+
modules: () => modules,
|
|
5104
|
+
parse: () => parse$2,
|
|
5105
|
+
paths: () => paths,
|
|
5106
|
+
projectRoot: () => projectRoot,
|
|
5107
|
+
userConfigDir: () => userConfigDir
|
|
4885
5108
|
});
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
5109
|
+
/**
|
|
5110
|
+
* An app-level `--user-config-dir` as the absolute directory it names: `~` is the user's home, and
|
|
5111
|
+
* a relative spelling is relative to `hostCwd` -- never to a session's `hostDir`, which is not
|
|
5112
|
+
* where the flag was typed.
|
|
5113
|
+
*/
|
|
5114
|
+
const userConfigDir = (raw, hostCwd) => hostPath.resolve(hostCwd, expandTilde(raw, hostPath));
|
|
5115
|
+
/** The settings files a directory may hold, in the order it is searched. */
|
|
5116
|
+
const settingsIn = (directory) => [hostPath.join(directory, "settings.jsonc"), hostPath.join(directory, "settings.json")];
|
|
5117
|
+
/** A directory and every ancestor above it, nearest first. */
|
|
5118
|
+
const ancestors = (from) => {
|
|
5119
|
+
const chain = [];
|
|
5120
|
+
for (let directory = from;; directory = hostPath.dirname(directory)) {
|
|
5121
|
+
chain.push(directory);
|
|
5122
|
+
if (hostPath.dirname(directory) === directory) return chain;
|
|
5123
|
+
}
|
|
5124
|
+
};
|
|
5125
|
+
const isDirectory = (path) => fileSystem.stat(path).pipe(Effect.map((info) => info.type === "Directory"), Effect.orElseSucceed(() => false));
|
|
5126
|
+
/**
|
|
5127
|
+
* The project a directory is in: the nearest ancestor holding a `.codework` **directory**.
|
|
5128
|
+
*
|
|
5129
|
+
* This is git's rule. `~/workspace/app/.codework` makes `~/workspace/app` a project, and
|
|
5130
|
+
* `~/workspace/app/packages/fubar` is inside it. Nearest wins outright -- the walk stops at the
|
|
5131
|
+
* first marker, so an inner project is never merged with an outer one.
|
|
5132
|
+
*
|
|
5133
|
+
* The marker is the directory, not a file in it. An empty `.codework/` is still a project root,
|
|
5134
|
+
* which is the point: creating the directory is how a person says "a project begins here", and it
|
|
5135
|
+
* has to mean that before any settings exist. It also makes the question one `stat` per ancestor
|
|
5136
|
+
* whose answer does not change as files come and go inside.
|
|
5137
|
+
*
|
|
5138
|
+
* `<home>` is skipped. `~/.codework` is the user layer, not a project; without this, running
|
|
5139
|
+
* anywhere under `$HOME` would find it as one and read the user layer twice -- and since plugin
|
|
5140
|
+
* entries accumulate across layers, every user plugin would load twice.
|
|
5141
|
+
*/
|
|
5142
|
+
const projectRoot = Effect.fn("Settings.projectRoot")(function* (from, home) {
|
|
5143
|
+
const userConfig = hostPath.resolve(home);
|
|
5144
|
+
for (const directory of ancestors(hostPath.resolve(from))) {
|
|
5145
|
+
const marker = hostPath.join(directory, appConfigDir);
|
|
5146
|
+
if (hostPath.resolve(marker) === userConfig) continue;
|
|
5147
|
+
if (yield* isDirectory(marker)) return directory;
|
|
5148
|
+
}
|
|
5149
|
+
});
|
|
5150
|
+
/**
|
|
5151
|
+
* Ordered layers, each a group of candidates where the first file that exists is selected -- a
|
|
5152
|
+
* group never contributes more than one file.
|
|
5153
|
+
*
|
|
5154
|
+
* `.jsonc` is the canonical spelling: these files are written by hand and read as JSONC, so the
|
|
5155
|
+
* extension says what the file is and an editor validates it as such. `.json` remains a fallback
|
|
5156
|
+
* everywhere, tried second, because the parser reads either and a file already named that must
|
|
5157
|
+
* keep working.
|
|
5158
|
+
*
|
|
5159
|
+
* `root` is an already-discovered project root ({@link projectRoot}), not a directory to search
|
|
5160
|
+
* from: the walk is a `stat` per ancestor and belongs with the other I/O. `undefined` contributes
|
|
5161
|
+
* no project layer at all, which is what a session with no host directory gets.
|
|
5162
|
+
*/
|
|
5163
|
+
function paths(input) {
|
|
5164
|
+
return [settingsIn(input.userConfigDir ?? input.home), input.root === void 0 ? [] : settingsIn(hostPath.join(input.root, appConfigDir))];
|
|
5165
|
+
}
|
|
5166
|
+
var SettingsError = class extends Schema.TaggedError()("SettingsError", {
|
|
4892
5167
|
path: Schema.String,
|
|
5168
|
+
reason: Schema.Literals([
|
|
5169
|
+
"read",
|
|
5170
|
+
"parse",
|
|
5171
|
+
"decode"
|
|
5172
|
+
]),
|
|
4893
5173
|
detail: Schema.String
|
|
4894
5174
|
}) {
|
|
4895
5175
|
get message() {
|
|
4896
|
-
return
|
|
5176
|
+
return `${this.path}: ${this.reason}: ${this.detail}`;
|
|
4897
5177
|
}
|
|
4898
5178
|
};
|
|
4899
|
-
/**
|
|
4900
|
-
const
|
|
4901
|
-
const
|
|
4902
|
-
|
|
4903
|
-
const path = Model$1.catalogPath();
|
|
4904
|
-
return {
|
|
4905
|
-
path,
|
|
4906
|
-
pinned: path !== own
|
|
4907
|
-
};
|
|
5179
|
+
/** `line:column`, 1-based, for a parser offset into the source. */
|
|
5180
|
+
const at = (source, offset) => {
|
|
5181
|
+
const before = source.slice(0, offset).split("\n");
|
|
5182
|
+
return `${before.length}:${(before.at(-1)?.length ?? 0) + 1}`;
|
|
4908
5183
|
};
|
|
4909
|
-
const modified = (path) => fileSystem.stat(path).pipe(Effect.map((info) => Option.map(info.mtime, (date) => date.getTime())), Effect.orElseSucceed(() => Option.none()));
|
|
4910
|
-
const stale = Effect.fn("ModelCatalog.stale")(function* (path) {
|
|
4911
|
-
const mtime = yield* modified(path);
|
|
4912
|
-
if (Option.isNone(mtime)) return true;
|
|
4913
|
-
const ttl = yield* ttlConfig;
|
|
4914
|
-
return (yield* Clock.currentTimeMillis) - mtime.value >= Duration.toMillis(ttl);
|
|
4915
|
-
});
|
|
4916
|
-
const download$1 = (path) => Effect.tryPromise({
|
|
4917
|
-
try: () => generateModels({ path }),
|
|
4918
|
-
catch: (cause) => new RefreshError({
|
|
4919
|
-
path,
|
|
4920
|
-
detail: cause instanceof Error ? cause.message : String(cause)
|
|
4921
|
-
})
|
|
4922
|
-
});
|
|
4923
|
-
/** Download a new catalog for the home when it is missing, older than the TTL, or `force` asks. */
|
|
4924
|
-
const refresh$1 = Effect.fn("ModelCatalog.refresh")(function* (home, options = {}) {
|
|
4925
|
-
const { path } = use(home);
|
|
4926
|
-
if (options.force !== true && !(yield* stale(path))) return {
|
|
4927
|
-
path,
|
|
4928
|
-
refreshed: false
|
|
4929
|
-
};
|
|
4930
|
-
yield* download$1(path);
|
|
4931
|
-
return {
|
|
4932
|
-
path,
|
|
4933
|
-
refreshed: true
|
|
4934
|
-
};
|
|
4935
|
-
});
|
|
4936
5184
|
/**
|
|
4937
|
-
*
|
|
4938
|
-
*
|
|
4939
|
-
*
|
|
5185
|
+
* Decode failures name the field, and explain it only with a message a schema check wrote. Effect's
|
|
5186
|
+
* defaults quote the rejected value, and in a settings file that value can be a secret, so any
|
|
5187
|
+
* other failure reads "invalid value".
|
|
4940
5188
|
*/
|
|
4941
|
-
const
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
});
|
|
4945
|
-
const
|
|
4946
|
-
|
|
4947
|
-
const
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
5189
|
+
const decodeFormatter = SchemaIssue.makeFormatterStandardSchemaV1({ leafHook: (issue) => {
|
|
5190
|
+
const message = "annotations" in issue ? issue.annotations?.message : void 0;
|
|
5191
|
+
return typeof message === "string" ? message.replaceAll(/\s*\n\s*/g, " ") : "invalid value";
|
|
5192
|
+
} });
|
|
5193
|
+
const parse$2 = Effect.fn("Settings.parse")(function* (path, source) {
|
|
5194
|
+
const errors = [];
|
|
5195
|
+
const json = parse(source, errors, {
|
|
5196
|
+
allowTrailingComma: true,
|
|
5197
|
+
disallowComments: false
|
|
5198
|
+
});
|
|
5199
|
+
const first = errors[0];
|
|
5200
|
+
if (first !== void 0) return yield* new SettingsError({
|
|
5201
|
+
path,
|
|
5202
|
+
reason: "parse",
|
|
5203
|
+
detail: `${printParseErrorCode(first.error)} at ${at(source, first.offset)}`
|
|
5204
|
+
});
|
|
5205
|
+
const document = normalize$1(json);
|
|
5206
|
+
if (Predicate.isObject(document) && Predicate.isObject(json) && "plugins" in json) Object.assign(document, { plugins: json.plugins });
|
|
5207
|
+
return yield* Schema.decodeUnknownEffect(schema_exports$3.Patch)(document).pipe(Effect.mapError((error) => new SettingsError({
|
|
5208
|
+
path,
|
|
5209
|
+
reason: "decode",
|
|
5210
|
+
detail: decodeFormatter(error.issue).issues.map((issue) => `${issue.path?.map((part) => typeof part === "object" ? String(part.key) : part).join(".") ?? "settings"}: ${issue.message}`).join("; ")
|
|
5211
|
+
})));
|
|
4951
5212
|
});
|
|
5213
|
+
var Service$3 = class extends Context.Service()("@codeworksh/harness/settings/settings/Service") {};
|
|
4952
5214
|
/**
|
|
4953
|
-
*
|
|
4954
|
-
*
|
|
4955
|
-
*
|
|
5215
|
+
* Anchor relative plugin entries to the file that declared them.
|
|
5216
|
+
*
|
|
5217
|
+
* A reference is otherwise resolved against the host startup directory, which is right for
|
|
5218
|
+
* a project file sitting in it and meaningless for `~/.codework/settings.json`, where
|
|
5219
|
+
* `./plugins/x.ts` would name a different file in every project the process is started in.
|
|
5220
|
+
* `file:` URLs, package specs and package names are left exactly as written, and a `plugin`
|
|
5221
|
+
* key is an ID rather than a location. A configuration entry's `package` is anchored like a
|
|
5222
|
+
* module entry, so a relative path names the same module in both spellings.
|
|
4956
5223
|
*/
|
|
4957
|
-
const
|
|
5224
|
+
const anchored = (entry, file) => {
|
|
5225
|
+
const directory = hostPath.dirname(file);
|
|
5226
|
+
const resolve = (reference) => reference.startsWith("./") || reference.startsWith("../") ? hostPath.resolve(directory, reference) : reference;
|
|
5227
|
+
if (typeof entry === "string") return resolve(entry);
|
|
5228
|
+
return "package" in entry ? {
|
|
5229
|
+
...entry,
|
|
5230
|
+
package: resolve(entry.package)
|
|
5231
|
+
} : entry;
|
|
5232
|
+
};
|
|
5233
|
+
const anchor$1 = (patch, file) => patch.plugins === void 0 ? patch : {
|
|
5234
|
+
...patch,
|
|
5235
|
+
plugins: patch.plugins.map((entry) => anchored(entry, file))
|
|
5236
|
+
};
|
|
5237
|
+
const attempt = (path) => fileSystem.readFileString(path).pipe(Effect.mapError((error) => new SettingsError({
|
|
5238
|
+
path,
|
|
5239
|
+
reason: "read",
|
|
5240
|
+
detail: error.reason._tag
|
|
5241
|
+
})), Effect.flatMap((source) => parse$2(path, source)));
|
|
5242
|
+
/**
|
|
5243
|
+
* One read of every layer. Exported for the harness constructor, which needs the plugin
|
|
5244
|
+
* selection before any layer is built; everything else goes through `Service`.
|
|
5245
|
+
*/
|
|
5246
|
+
const load$3 = Effect.fn("Settings.load")(function* (options) {
|
|
5247
|
+
const root = options.hostDir === void 0 ? void 0 : yield* projectRoot(options.hostDir, options.home);
|
|
5248
|
+
const files = paths({
|
|
5249
|
+
home: options.home,
|
|
5250
|
+
root,
|
|
5251
|
+
...options.userConfigDir === void 0 ? {} : { userConfigDir: options.userConfigDir }
|
|
5252
|
+
});
|
|
5253
|
+
let settings = merge(schema_exports$3.defaults);
|
|
5254
|
+
let declared = schema_exports$3.defaults.declared;
|
|
5255
|
+
let models = schema_exports$3.defaults.models;
|
|
5256
|
+
for (const group of files) for (const path of group) {
|
|
5257
|
+
const result = yield* Effect.result(attempt(path));
|
|
5258
|
+
if (Result.isSuccess(result)) {
|
|
5259
|
+
const { models: entries, ...patch } = anchor$1(result.success, path);
|
|
5260
|
+
settings = merge(settings, patch);
|
|
5261
|
+
if (entries !== void 0) models = [...models, ...entries.map((entry) => ({
|
|
5262
|
+
entry,
|
|
5263
|
+
file: path
|
|
5264
|
+
}))];
|
|
5265
|
+
if (result.success.plugins !== void 0) declared = [...declared, ...result.success.plugins.map((written) => ({
|
|
5266
|
+
written,
|
|
5267
|
+
entry: anchored(written, path),
|
|
5268
|
+
file: path
|
|
5269
|
+
}))];
|
|
5270
|
+
break;
|
|
5271
|
+
}
|
|
5272
|
+
const error = result.failure;
|
|
5273
|
+
if (error.reason === "read" && error.detail === "NotFound") continue;
|
|
5274
|
+
return yield* error;
|
|
5275
|
+
}
|
|
5276
|
+
return {
|
|
5277
|
+
...settings,
|
|
5278
|
+
plugins: declared.map((one) => one.entry),
|
|
5279
|
+
declared,
|
|
5280
|
+
models
|
|
5281
|
+
};
|
|
5282
|
+
});
|
|
5283
|
+
/**
|
|
5284
|
+
* Each module reference once, owned by the **last** file to declare it (D6).
|
|
5285
|
+
*
|
|
5286
|
+
* The owner is not bookkeeping: its `.npmrc` chain keys the store artifact. The CLI that installs
|
|
5287
|
+
* a reference and the runtime that loads it must pick the same owner, or `plugin update` refreshes
|
|
5288
|
+
* an artifact the runtime never reads. Only a string entry loads a module, so only a string entry
|
|
5289
|
+
* owns one -- a `{ package }` patch in another file configures it and must not move its registry.
|
|
5290
|
+
* A reference keeps the position of its first declaration.
|
|
5291
|
+
*/
|
|
5292
|
+
const modules = (declared) => {
|
|
5293
|
+
const owners = /* @__PURE__ */ new Map();
|
|
5294
|
+
for (const one of declared) if (typeof one.entry === "string") owners.set(one.entry, one);
|
|
5295
|
+
return owners;
|
|
5296
|
+
};
|
|
5297
|
+
const layer$8 = (options = {}) => Layer.effect(Service$3, Effect.gen(function* () {
|
|
5298
|
+
const global = yield* Service$17;
|
|
5299
|
+
return Service$3.of({ load: (hostDir) => load$3({
|
|
5300
|
+
...options,
|
|
5301
|
+
home: global.home,
|
|
5302
|
+
hostDir
|
|
5303
|
+
}) });
|
|
5304
|
+
}));
|
|
5305
|
+
//#endregion
|
|
5306
|
+
//#region src/model/catalog.ts
|
|
5307
|
+
var catalog_exports = /* @__PURE__ */ __exportAll({
|
|
5308
|
+
Credentials: () => Credentials,
|
|
5309
|
+
ModelCatalog: () => catalog_exports,
|
|
5310
|
+
RefreshError: () => RefreshError,
|
|
5311
|
+
available: () => available,
|
|
5312
|
+
effective: () => effective,
|
|
5313
|
+
entry: () => entry,
|
|
5314
|
+
filename: () => filename,
|
|
5315
|
+
fromSettings: () => fromSettings,
|
|
5316
|
+
layer: () => layer$7,
|
|
5317
|
+
loadError: () => loadError,
|
|
5318
|
+
models: () => models,
|
|
5319
|
+
oauthStorage: () => oauthStorage,
|
|
5320
|
+
providers: () => providers,
|
|
5321
|
+
refresh: () => refresh$1,
|
|
5322
|
+
sync: () => sync,
|
|
5323
|
+
ttlConfig: () => ttlConfig
|
|
5324
|
+
});
|
|
5325
|
+
const filename = "models.gen.json";
|
|
5326
|
+
/** How old the catalog may get before a check downloads a new one. */
|
|
5327
|
+
const ttlConfig = Config.Duration("CODEWORK_MODELS_TTL").pipe(Config.withDefault(Duration.minutes(15)));
|
|
5328
|
+
/** How often a server looks at the file. A look is a `stat`; only a stale file costs a download. */
|
|
5329
|
+
const checkEvery = Duration.minutes(1);
|
|
5330
|
+
var RefreshError = class extends Schema.TaggedError()("ModelCatalog.RefreshError", {
|
|
5331
|
+
path: Schema.String,
|
|
5332
|
+
detail: Schema.String
|
|
5333
|
+
}) {
|
|
5334
|
+
get message() {
|
|
5335
|
+
return `failed to refresh the model catalog at ${this.path}: ${this.detail}`;
|
|
5336
|
+
}
|
|
5337
|
+
};
|
|
5338
|
+
/** Point aikit at `<home>/models.gen.json`, which `CODEWORK_MODELS_FILE` overrides. */
|
|
5339
|
+
const use = (home) => {
|
|
5340
|
+
const own = hostPath.resolve(home, filename);
|
|
5341
|
+
Model.configureCatalog(own);
|
|
5342
|
+
const path = Model.catalogPath();
|
|
5343
|
+
return {
|
|
5344
|
+
path,
|
|
5345
|
+
pinned: path !== own
|
|
5346
|
+
};
|
|
5347
|
+
};
|
|
5348
|
+
const modified = (path) => fileSystem.stat(path).pipe(Effect.map((info) => Option.map(info.mtime, (date) => date.getTime())), Effect.orElseSucceed(() => Option.none()));
|
|
5349
|
+
const stale = Effect.fn("ModelCatalog.stale")(function* (path) {
|
|
5350
|
+
const mtime = yield* modified(path);
|
|
5351
|
+
if (Option.isNone(mtime)) return true;
|
|
5352
|
+
const ttl = yield* ttlConfig;
|
|
5353
|
+
return (yield* Clock.currentTimeMillis) - mtime.value >= Duration.toMillis(ttl);
|
|
5354
|
+
});
|
|
5355
|
+
const download$1 = (path) => Effect.tryPromise({
|
|
5356
|
+
try: () => generateModels({ path }),
|
|
5357
|
+
catch: (cause) => new RefreshError({
|
|
5358
|
+
path,
|
|
5359
|
+
detail: cause instanceof Error ? cause.message : String(cause)
|
|
5360
|
+
})
|
|
5361
|
+
});
|
|
5362
|
+
/** Download a new catalog for the home when it is missing, older than the TTL, or `force` asks. */
|
|
5363
|
+
const refresh$1 = Effect.fn("ModelCatalog.refresh")(function* (home, options = {}) {
|
|
5364
|
+
const { path } = use(home);
|
|
5365
|
+
if (options.force !== true && !(yield* stale(path))) return {
|
|
5366
|
+
path,
|
|
5367
|
+
refreshed: false
|
|
5368
|
+
};
|
|
5369
|
+
yield* download$1(path);
|
|
5370
|
+
return {
|
|
5371
|
+
path,
|
|
5372
|
+
refreshed: true
|
|
5373
|
+
};
|
|
5374
|
+
});
|
|
5375
|
+
/**
|
|
5376
|
+
* The automatic check: refresh a stale catalog unless it is pinned, and never fail. A failed
|
|
5377
|
+
* download leaves the old file in place; a missing one surfaces later as a
|
|
5378
|
+
* `Runner.ModelCatalogError` from whatever needed it.
|
|
5379
|
+
*/
|
|
5380
|
+
const check$1 = Effect.fn("ModelCatalog.check")(function* ({ path, pinned }) {
|
|
5381
|
+
if (pinned || !(yield* stale(path))) return;
|
|
5382
|
+
yield* download$1(path);
|
|
5383
|
+
});
|
|
5384
|
+
const quietly = (effect) => effect.pipe(Effect.asVoid, Effect.catch((error) => Effect.logWarning(`model catalog: ${error.message}`)));
|
|
5385
|
+
/** Check the home's catalog once, for a command that runs and exits; returns the file aikit reads. */
|
|
5386
|
+
const sync = Effect.fn("ModelCatalog.sync")(function* (home) {
|
|
5387
|
+
const target = use(home);
|
|
5388
|
+
yield* quietly(check$1(target));
|
|
5389
|
+
return target.path;
|
|
5390
|
+
});
|
|
5391
|
+
/**
|
|
5392
|
+
* Check at boot, and with `watch`, keep checking for the life of the layer. A file whose mtime
|
|
5393
|
+
* moved -- refreshed here, by `codework models refresh`, or by hand -- is reloaded, so the
|
|
5394
|
+
* process never serves a catalog older than what is on disk.
|
|
5395
|
+
*/
|
|
5396
|
+
const layer$7 = (options) => Layer.effectDiscard(Effect.gen(function* () {
|
|
4958
5397
|
const target = use(options.home);
|
|
4959
5398
|
yield* quietly(check$1(target));
|
|
4960
5399
|
if (!options.watch) return;
|
|
@@ -4963,7 +5402,7 @@ const layer$8 = (options) => Layer.effectDiscard(Effect.gen(function* () {
|
|
|
4963
5402
|
const tick = Effect.gen(function* () {
|
|
4964
5403
|
yield* quietly(check$1(target));
|
|
4965
5404
|
const mtime = yield* current;
|
|
4966
|
-
if (mtime !== (yield* Ref.getAndSet(seen, mtime))) Model
|
|
5405
|
+
if (mtime !== (yield* Ref.getAndSet(seen, mtime))) Model.reloadCatalog();
|
|
4967
5406
|
});
|
|
4968
5407
|
yield* Effect.forkScoped(tick.pipe(Effect.repeat(Schedule.spaced(checkEvery))));
|
|
4969
5408
|
}));
|
|
@@ -4980,79 +5419,112 @@ const loadError = (cause) => {
|
|
|
4980
5419
|
});
|
|
4981
5420
|
};
|
|
4982
5421
|
const catalogError = (cause) => loadError(cause) ?? new ModelCatalogError({
|
|
4983
|
-
path: Model
|
|
5422
|
+
path: Model.catalogPath(),
|
|
4984
5423
|
reason: "unreadable",
|
|
4985
5424
|
detail: cause instanceof Error ? cause.message : "failed to load the model catalog"
|
|
4986
5425
|
});
|
|
4987
5426
|
const models = Effect.tryPromise({
|
|
4988
|
-
try: () => Model
|
|
5427
|
+
try: () => Model.getBuiltInModels(),
|
|
4989
5428
|
catch: catalogError
|
|
4990
5429
|
});
|
|
4991
5430
|
const providers = Effect.tryPromise({
|
|
4992
|
-
try: () => Model
|
|
5431
|
+
try: () => Model.getProviders(),
|
|
4993
5432
|
catch: catalogError
|
|
4994
5433
|
});
|
|
4995
|
-
//#endregion
|
|
4996
|
-
//#region src/plugin/plugin.ts
|
|
4997
|
-
var plugin_exports$1 = /* @__PURE__ */ __exportAll({});
|
|
4998
|
-
import * as import__codeworksh_plugin_plugin from "@codeworksh/plugin/plugin";
|
|
4999
|
-
__reExport(plugin_exports$1, import__codeworksh_plugin_plugin);
|
|
5000
|
-
//#endregion
|
|
5001
|
-
//#region src/plugin/builtin/prompt/default.ts
|
|
5002
|
-
/** The default coding-agent foundation, used unless `promptCustom` replaces it. */
|
|
5003
|
-
const foundation = `You are an expert coding assistant operating inside codework, a coding agent harness.`;
|
|
5004
5434
|
/**
|
|
5005
|
-
*
|
|
5006
|
-
*
|
|
5007
|
-
*
|
|
5008
|
-
* earns its place only if a model measurably behaves worse without it.
|
|
5435
|
+
* Where this process reads OAuth logins: `<home>/aikit/auth.json` when a home was chosen,
|
|
5436
|
+
* otherwise `undefined`, which lets aikit resolve it (`CODEWORK_CREDENTIALS`, then the default home).
|
|
5437
|
+
* One value, so the model calls and {@link available} can never read different files.
|
|
5009
5438
|
*/
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
const
|
|
5439
|
+
var Credentials = class extends Context.Service()("@codeworksh/harness/model/catalog/Credentials") {};
|
|
5440
|
+
/** The storage options an aikit OAuth store takes for {@link Credentials.authFile}. */
|
|
5441
|
+
const oauthStorage = (authFile) => authFile === void 0 ? {} : { path: authFile };
|
|
5442
|
+
/** Whether an OAuth login is stored for the protocol. Reads the file; never refreshes a token. */
|
|
5443
|
+
const loggedIn = (protocol, authFile) => {
|
|
5444
|
+
const storage = oauthStorage(authFile);
|
|
5445
|
+
const store = protocol === "openai-codex" ? new JsonOpenAICodexAuthStorage(storage) : protocol === "github-copilot" ? new JsonGitHubCopilotAuthStorage(storage) : void 0;
|
|
5446
|
+
if (store === void 0) return Effect.succeed(false);
|
|
5447
|
+
return Effect.tryPromise(() => store.get()).pipe(Effect.map((stored) => stored !== void 0), Effect.orElseSucceed(() => false));
|
|
5448
|
+
};
|
|
5017
5449
|
/**
|
|
5018
|
-
*
|
|
5019
|
-
*
|
|
5020
|
-
*
|
|
5021
|
-
* Tool order is bucket order, which is the same order the index renders in and the
|
|
5022
|
-
* same order the provider receives definitions in -- one ordering, not three.
|
|
5450
|
+
* A settings entry as the catalog reads it. Settings entries are read exactly like generated ones;
|
|
5451
|
+
* the one difference is `provider.key`, written `"${NAME}"`, which becomes the variable name aikit
|
|
5452
|
+
* looks up when it calls the model.
|
|
5023
5453
|
*/
|
|
5024
|
-
const
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
if (normalized.length === 0 || seen.has(normalized)) continue;
|
|
5030
|
-
seen.add(normalized);
|
|
5031
|
-
collected.push(normalized);
|
|
5454
|
+
const fromSettings = (entry) => entry.provider.key === void 0 ? entry : {
|
|
5455
|
+
...entry,
|
|
5456
|
+
provider: {
|
|
5457
|
+
...entry.provider,
|
|
5458
|
+
key: (0, schema_exports$3.keyName)(entry.provider.key)
|
|
5032
5459
|
}
|
|
5033
|
-
return collected;
|
|
5034
5460
|
};
|
|
5461
|
+
/** The settings entry for a model, if one declares it: the last declaration wins. */
|
|
5462
|
+
const entry = (declared, provider, id) => {
|
|
5463
|
+
const found = declared.findLast((one) => one.entry.provider.id === provider && one.entry.id === id);
|
|
5464
|
+
return found === void 0 ? void 0 : fromSettings(found.entry);
|
|
5465
|
+
};
|
|
5466
|
+
/**
|
|
5467
|
+
* The generated catalog with settings entries appended in layer order: a new provider or model is
|
|
5468
|
+
* added, and an entry for an existing one replaces it, as a later entry in a generated file would.
|
|
5469
|
+
*/
|
|
5470
|
+
const effective = Effect.fn("ModelCatalog.effective")(function* (declared) {
|
|
5471
|
+
const catalog = { ...yield* models };
|
|
5472
|
+
for (const { entry: one } of declared) catalog[one.provider.id] = {
|
|
5473
|
+
...catalog[one.provider.id],
|
|
5474
|
+
[one.id]: fromSettings(one)
|
|
5475
|
+
};
|
|
5476
|
+
return catalog;
|
|
5477
|
+
});
|
|
5478
|
+
const oauthProtocols = /* @__PURE__ */ new Set(["openai-codex", "github-copilot"]);
|
|
5035
5479
|
/**
|
|
5036
|
-
* The
|
|
5480
|
+
* The effective catalog's providers this process has credentials for, optionally only those
|
|
5481
|
+
* credentialed one way. `hostDir` selects the project whose settings entries apply; without it,
|
|
5482
|
+
* only the user's.
|
|
5037
5483
|
*
|
|
5038
|
-
* `
|
|
5039
|
-
*
|
|
5484
|
+
* `apiKey`: the provider's `key` or `env` names are set in the host environment (they include the
|
|
5485
|
+
* OAuth providers' env tokens). `oauth`: a login is stored in the {@link Credentials} file. `none`:
|
|
5486
|
+
* the provider names no key and isn't an OAuth provider. A provider with a key and a login reports
|
|
5487
|
+
* `apiKey`, the one a model call uses first.
|
|
5488
|
+
*
|
|
5489
|
+
* Presence, not validity: nothing is sent over the network, so a revoked key still counts. A
|
|
5490
|
+
* client showing a model picker should still add the session's current model.
|
|
5040
5491
|
*/
|
|
5041
|
-
const
|
|
5042
|
-
const
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5492
|
+
const available = Effect.fn("ModelCatalog.available")(function* (filter = {}) {
|
|
5493
|
+
const { authFile } = yield* Credentials;
|
|
5494
|
+
const settings = yield* Service$3;
|
|
5495
|
+
const catalog = yield* effective((yield* settings.load(filter.hostDir)).models);
|
|
5496
|
+
const usable = [];
|
|
5497
|
+
for (const [id, entries] of Object.entries(catalog)) {
|
|
5498
|
+
const first = entries === void 0 ? void 0 : Object.values(entries)[0];
|
|
5499
|
+
if (entries === void 0 || first === void 0) continue;
|
|
5500
|
+
const keyless = first.provider.key === void 0 && first.provider.env.length === 0 && !oauthProtocols.has(first.protocol);
|
|
5501
|
+
const credential = getEnvApiKey(first.provider) !== void 0 ? "apiKey" : (yield* loggedIn(first.protocol, authFile)) ? "oauth" : keyless ? "none" : void 0;
|
|
5502
|
+
if (credential === void 0 || filter.credential !== void 0 && filter.credential !== credential) continue;
|
|
5503
|
+
usable.push({
|
|
5504
|
+
id,
|
|
5505
|
+
credential,
|
|
5506
|
+
models: entries
|
|
5507
|
+
});
|
|
5508
|
+
}
|
|
5509
|
+
return usable;
|
|
5510
|
+
});
|
|
5511
|
+
//#endregion
|
|
5512
|
+
//#region src/plugin/plugin.ts
|
|
5513
|
+
var plugin_exports$1 = /* @__PURE__ */ __exportAll({});
|
|
5514
|
+
import * as import__codeworksh_plugin_plugin from "@codeworksh/plugin/plugin";
|
|
5515
|
+
__reExport(plugin_exports$1, import__codeworksh_plugin_plugin);
|
|
5516
|
+
//#endregion
|
|
5517
|
+
//#region src/plugin/builtin/prompt/default.ts
|
|
5518
|
+
/** The default prompt foundation, used unless `promptCustom` replaces it. */
|
|
5519
|
+
const foundation = "You are an expert coding assistant operating inside codework, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.";
|
|
5520
|
+
/** Every line here is spent on every request; keep it short. */
|
|
5521
|
+
const standingRules = ["Be concise in your responses", "Show file paths clearly when working with files"];
|
|
5522
|
+
/** Bash stands in for file tools only until dedicated ones are registered. */
|
|
5523
|
+
const toolRules = (names) => names.has("bash") && ![
|
|
5524
|
+
"grep",
|
|
5525
|
+
"find",
|
|
5526
|
+
"ls"
|
|
5527
|
+
].some((name) => names.has(name)) ? ["Use bash for file operations like ls, rg, find"] : [];
|
|
5056
5528
|
/**
|
|
5057
5529
|
* A caller slot, awaited lazily. A throw or rejection fails the snapshot, which the
|
|
5058
5530
|
* host attributes to this plugin's id.
|
|
@@ -5063,13 +5535,81 @@ const defaultPromptPlugin = (0, plugin_exports$1.define)({
|
|
|
5063
5535
|
kind: "prompt",
|
|
5064
5536
|
setup: Effect.fn("DefaultPromptPlugin.setup")(function* (ctx) {
|
|
5065
5537
|
const custom = yield* slot(ctx, ctx.config.promptCustom);
|
|
5066
|
-
const append = yield* slot(ctx, ctx.config.promptSystemAppend);
|
|
5067
|
-
ctx.plugin.prompt
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5538
|
+
const append = (yield* slot(ctx, ctx.config.promptSystemAppend))?.trim();
|
|
5539
|
+
const prompt = ctx.plugin.prompt;
|
|
5540
|
+
prompt.foundation.set(custom ?? foundation);
|
|
5541
|
+
const names = new Set(ctx.plugin.tools.list().map((tool) => tool.name));
|
|
5542
|
+
for (const rule of [...toolRules(names), ...standingRules]) prompt.sections.append(Section.Rules, rule);
|
|
5543
|
+
if (append !== void 0 && append.length > 0) prompt.sections.append(Section.Addendum, append);
|
|
5544
|
+
})
|
|
5545
|
+
});
|
|
5546
|
+
//#endregion
|
|
5547
|
+
//#region src/plugin/builtin/prompt/instruction.ts
|
|
5548
|
+
/** First readable match per directory wins. */
|
|
5549
|
+
const names = [
|
|
5550
|
+
"AGENTS.override.md",
|
|
5551
|
+
"CLAUDE.override.md",
|
|
5552
|
+
"AGENTS.md",
|
|
5553
|
+
"AGENTS.MD",
|
|
5554
|
+
"CLAUDE.md",
|
|
5555
|
+
"CLAUDE.MD"
|
|
5556
|
+
];
|
|
5557
|
+
const content = (raw) => {
|
|
5558
|
+
const text = raw.charCodeAt(0) === 65279 ? raw.slice(1) : raw;
|
|
5559
|
+
return text.trim().length === 0 ? void 0 : text;
|
|
5560
|
+
};
|
|
5561
|
+
const first = Effect.fnUntraced(function* (fs, directory) {
|
|
5562
|
+
for (const name of names) {
|
|
5563
|
+
const path = posix.join(directory, name);
|
|
5564
|
+
if (!(yield* fs.stat(path).pipe(Effect.map((stat) => stat.isFile), Effect.orElseSucceed(() => false)))) continue;
|
|
5565
|
+
const raw = yield* fs.readFile(path).pipe(Effect.tapError((cause) => Effect.logWarning(`could not read instruction file ${path}`, cause)), Effect.option);
|
|
5566
|
+
if (Option.isSome(raw)) return {
|
|
5567
|
+
name,
|
|
5568
|
+
path,
|
|
5569
|
+
content: content(raw.value)
|
|
5570
|
+
};
|
|
5571
|
+
}
|
|
5572
|
+
});
|
|
5573
|
+
/** Only the path is escaped; file content goes in verbatim. */
|
|
5574
|
+
const attribute = (value) => value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<");
|
|
5575
|
+
/**
|
|
5576
|
+
* The main checkout's directory when the cwd is in a linked worktree nested inside it (e.g.
|
|
5577
|
+
* `repo/.claude/worktrees/x`), so its same-named file is not applied twice. Sibling worktrees,
|
|
5578
|
+
* bare layouts and submodules are never shadowed.
|
|
5579
|
+
*/
|
|
5580
|
+
const shadowing = (location) => {
|
|
5581
|
+
const store = location.project.vcs?.store;
|
|
5582
|
+
if (location.space.kind !== "linked" || store === void 0 || posix.basename(store) !== ".git") return void 0;
|
|
5583
|
+
const main = posix.dirname(store);
|
|
5584
|
+
return location.space.location.startsWith(`${main}/`) ? main : void 0;
|
|
5585
|
+
};
|
|
5586
|
+
const instructionPlugin = (0, plugin_exports$1.define)({
|
|
5587
|
+
id: "codework.prompt.instruction",
|
|
5588
|
+
kind: "prompt",
|
|
5589
|
+
setup: Effect.fn("InstructionPlugin.setup")(function* (ctx) {
|
|
5590
|
+
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
5591
|
+
const probed = /* @__PURE__ */ new Map();
|
|
5592
|
+
const at = Effect.fnUntraced(function* (directory) {
|
|
5593
|
+
if (!probed.has(directory)) probed.set(directory, yield* first(fs, directory));
|
|
5594
|
+
return probed.get(directory);
|
|
5595
|
+
});
|
|
5596
|
+
const walked = [];
|
|
5597
|
+
const main = shadowing(ctx.location);
|
|
5598
|
+
const worktree = main === void 0 ? void 0 : yield* at(ctx.location.space.location);
|
|
5599
|
+
let current = ctx.location.directory;
|
|
5600
|
+
while (true) {
|
|
5601
|
+
const here = yield* at(current);
|
|
5602
|
+
const shadowed = current === main && worktree !== void 0 && here?.name === worktree.name;
|
|
5603
|
+
if (here !== void 0 && !shadowed) walked.push(here);
|
|
5604
|
+
const parent = posix.dirname(current);
|
|
5605
|
+
if (parent === current) break;
|
|
5606
|
+
current = parent;
|
|
5607
|
+
}
|
|
5608
|
+
const files = walked.reverse().filter((file) => file.content !== void 0);
|
|
5609
|
+
if (files.length === 0) return;
|
|
5610
|
+
const prompt = ctx.plugin.prompt;
|
|
5611
|
+
prompt.sections.append(Section.ProjectContext, "Project-Specific Instructions & Guidelines:");
|
|
5612
|
+
for (const file of files) prompt.sections.append(Section.ProjectContext, `<project_instructions path="${attribute(file.path)}">\n${file.content}\n</project_instructions>`);
|
|
5073
5613
|
})
|
|
5074
5614
|
});
|
|
5075
5615
|
//#endregion
|
|
@@ -5301,7 +5841,7 @@ var Accumulator = class {
|
|
|
5301
5841
|
ensureTempFile() {
|
|
5302
5842
|
if (this.tempFile !== void 0) return Effect.void;
|
|
5303
5843
|
return Effect.gen({ self: this }, function* () {
|
|
5304
|
-
const suffix =
|
|
5844
|
+
const suffix = Hex.encode(yield* crypto.randomBytes(8));
|
|
5305
5845
|
this.tempFilePath = defaultTempFilePath(this.tempFilePrefix, suffix);
|
|
5306
5846
|
this.tempFile = yield* fileSystem.open(this.tempFilePath, { flag: "w" });
|
|
5307
5847
|
for (const chunk of this.rawChunks) yield* this.tempFile.writeAll(chunk);
|
|
@@ -5480,7 +6020,7 @@ const footer = (t, fullOutputPath, lastLineBytes) => {
|
|
|
5480
6020
|
};
|
|
5481
6021
|
/** Write full output to a host temp file (best-effort; undefined on failure). */
|
|
5482
6022
|
const spillToTempFile = (content) => Effect.gen(function* () {
|
|
5483
|
-
const suffix =
|
|
6023
|
+
const suffix = Hex.encode(yield* crypto.randomBytes(6));
|
|
5484
6024
|
const path = posix.join(tmpdir(), `codework-bash-${suffix}.log`);
|
|
5485
6025
|
yield* fileSystem.writeFileString(path, content);
|
|
5486
6026
|
return path;
|
|
@@ -5583,36 +6123,13 @@ const bashPlugin = (0, plugin_exports$1.define)({
|
|
|
5583
6123
|
//#region src/plugin/builtin.ts
|
|
5584
6124
|
/**
|
|
5585
6125
|
* Every plugin the harness ships, and the selection a caller who passes no `plugins` gets. Tool
|
|
5586
|
-
* contributors come first
|
|
5587
|
-
*/
|
|
5588
|
-
const builtins = Object.freeze([bashPlugin, defaultPromptPlugin]);
|
|
5589
|
-
//#endregion
|
|
5590
|
-
//#region src/settings/merge.ts
|
|
5591
|
-
/**
|
|
5592
|
-
* A plain string-keyed record: what a settings patch merges, and what a plugin's `options` block
|
|
5593
|
-
* has to be. A Map, a typed array or a class instance is an object to `typeof` and not one of
|
|
5594
|
-
* these — `Object.freeze` alone does not tell them apart, and it throws on some of them.
|
|
6126
|
+
* contributors come first; within the prompt domain, declaration order is run order.
|
|
5595
6127
|
*/
|
|
5596
|
-
const
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
/** Null/undefined properties are absent, objects merge, and arrays replace. Never mutates inputs. */
|
|
5602
|
-
function merge(base, ...patches) {
|
|
5603
|
-
const result = /* @__PURE__ */ new Map();
|
|
5604
|
-
for (const patch of [base, ...patches]) {
|
|
5605
|
-
if (patch === void 0) continue;
|
|
5606
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
5607
|
-
if (value === null || value === void 0) continue;
|
|
5608
|
-
const previous = result.get(key);
|
|
5609
|
-
result.set(key, isRecord$1(previous) && isRecord$1(value) ? merge(previous, value) : copy(value));
|
|
5610
|
-
}
|
|
5611
|
-
}
|
|
5612
|
-
return Object.fromEntries(result);
|
|
5613
|
-
}
|
|
5614
|
-
/** Normalize nullable properties before schema validation, without changing array positions. */
|
|
5615
|
-
const normalize = copy;
|
|
6128
|
+
const builtins = Object.freeze([
|
|
6129
|
+
bashPlugin,
|
|
6130
|
+
defaultPromptPlugin,
|
|
6131
|
+
instructionPlugin
|
|
6132
|
+
]);
|
|
5616
6133
|
//#endregion
|
|
5617
6134
|
//#region src/util/module.ts
|
|
5618
6135
|
/*!
|
|
@@ -6169,7 +6686,7 @@ const isPath = (reference) => reference.startsWith("file:") || reference.startsW
|
|
|
6169
6686
|
* - a **CLI argument** anchors to the process's startup directory, because that is where the
|
|
6170
6687
|
* person typed it.
|
|
6171
6688
|
*/
|
|
6172
|
-
const parse$
|
|
6689
|
+
const parse$1 = (reference, from) => Effect.gen(function* () {
|
|
6173
6690
|
const fail = (reason, message) => new SourceError({
|
|
6174
6691
|
reason,
|
|
6175
6692
|
reference,
|
|
@@ -6386,7 +6903,7 @@ const slugOf = (target) => target.kind === "git" ? target.slug : canonical$1(tar
|
|
|
6386
6903
|
*/
|
|
6387
6904
|
const digest = Effect.fn("PluginStore.digest")(function* (target, cache, from) {
|
|
6388
6905
|
const source = target.kind === "registry" ? `${yield* registry(target, cache, from)}\0${target.spec}` : target.spec;
|
|
6389
|
-
return
|
|
6906
|
+
return Hex.encode(yield* crypto.digest("SHA-256", new TextEncoder().encode(source)));
|
|
6390
6907
|
});
|
|
6391
6908
|
const entryDir = Effect.fn("PluginStore.entryDir")(function* (target, cache, from) {
|
|
6392
6909
|
const key = yield* digest(target, cache, from);
|
|
@@ -6790,7 +7307,7 @@ const localUrl = Effect.fn("PluginLoader.localUrl")(function* (location, origin)
|
|
|
6790
7307
|
*/
|
|
6791
7308
|
const install = Effect.fn("PluginLoader.install")(function* (target, cache, origin, options) {
|
|
6792
7309
|
const added = yield* add(target, cache, {
|
|
6793
|
-
from: anchor
|
|
7310
|
+
from: anchor(origin.file, options.hostDir),
|
|
6794
7311
|
validate: (fetched) => imported(url(fetched.entrypoint), origin, options.import)
|
|
6795
7312
|
});
|
|
6796
7313
|
return {
|
|
@@ -6806,13 +7323,13 @@ const install = Effect.fn("PluginLoader.install")(function* (target, cache, orig
|
|
|
6806
7323
|
* package boundary discovered upward from the chain's start -- the root itself -- so anchoring at
|
|
6807
7324
|
* the file's own directory would miss it. Absent a declaring file the host context stands in.
|
|
6808
7325
|
*/
|
|
6809
|
-
const anchor
|
|
7326
|
+
const anchor = (file, fallback) => {
|
|
6810
7327
|
if (file === void 0) return fallback;
|
|
6811
7328
|
const directory = hostPath.dirname(file);
|
|
6812
7329
|
return hostPath.basename(directory) === ".codework" ? hostPath.dirname(directory) : directory;
|
|
6813
7330
|
};
|
|
6814
7331
|
/** The default installer, or the one the caller injected. */
|
|
6815
|
-
const installer = (target, origin, options) => options.install === void 0 ? install(target, options.cache, origin, options) : options.install(target, options.cache, anchor
|
|
7332
|
+
const installer = (target, origin, options) => options.install === void 0 ? install(target, options.cache, origin, options) : options.install(target, options.cache, anchor(origin.file, options.hostDir));
|
|
6816
7333
|
/** Import a module and confirm it default-exports a plugin. */
|
|
6817
7334
|
const imported = Effect.fn("PluginLoader.imported")(function* (url, origin, load) {
|
|
6818
7335
|
const module = yield* Effect.tryPromise({
|
|
@@ -6844,15 +7361,15 @@ const inspect = Effect.fn("PluginLoader.inspect")(function* (reference, options)
|
|
|
6844
7361
|
reference,
|
|
6845
7362
|
file: options.file
|
|
6846
7363
|
};
|
|
6847
|
-
const target = yield* parse$
|
|
6848
|
-
const loaded = yield* load$
|
|
7364
|
+
const target = yield* parse$1(reference, options.hostDir);
|
|
7365
|
+
const loaded = yield* load$2(target, origin, options);
|
|
6849
7366
|
return {
|
|
6850
7367
|
id: loaded.plugin.id,
|
|
6851
7368
|
...loaded.version === void 0 ? {} : { version: loaded.version },
|
|
6852
7369
|
...loaded.name === void 0 ? {} : { name: loaded.name }
|
|
6853
7370
|
};
|
|
6854
7371
|
});
|
|
6855
|
-
const load$
|
|
7372
|
+
const load$2 = Effect.fn("PluginLoader.load")(function* (target, origin, options) {
|
|
6856
7373
|
const installed = yield* target.kind === "local" ? localUrl(target.path, origin).pipe(Effect.mapError((cause) => sourceFailure(origin, cause))) : installer(target, origin, options).pipe(Effect.mapError((cause) => origin.file === void 0 ? cause : declaredIn(cause, origin.file)));
|
|
6857
7374
|
const address = target.kind === "local" && options.reload !== void 0 && options.reload > 0 ? `${installed.url}?reload=${options.reload}` : installed.url;
|
|
6858
7375
|
return {
|
|
@@ -6870,7 +7387,7 @@ const load$3 = Effect.fn("PluginLoader.load")(function* (target, origin, options
|
|
|
6870
7387
|
* are one package, and `./plugins/x.ts` is the file it resolves to from the directory that
|
|
6871
7388
|
* declared it. Pure -- nothing is installed, imported or read.
|
|
6872
7389
|
*/
|
|
6873
|
-
const canonical = (reference, hostDir) => Effect.map(parse$
|
|
7390
|
+
const canonical = (reference, hostDir) => Effect.map(parse$1(reference, hostDir), canonical$1);
|
|
6874
7391
|
//#endregion
|
|
6875
7392
|
//#region src/plugin/catalog.ts
|
|
6876
7393
|
/**
|
|
@@ -6897,7 +7414,7 @@ const isPatch = (reference) => Predicate.isObject(reference) && !Predicate.isFun
|
|
|
6897
7414
|
*
|
|
6898
7415
|
* Loading is keyed by what a reference resolves to, so naming one module twice imports it once.
|
|
6899
7416
|
*/
|
|
6900
|
-
const load$
|
|
7417
|
+
const load$1 = Effect.fn("PluginCatalog.load")(function* (references, options) {
|
|
6901
7418
|
const plugins = /* @__PURE__ */ new Map();
|
|
6902
7419
|
const aliases = /* @__PURE__ */ new Map();
|
|
6903
7420
|
const versions = /* @__PURE__ */ new Map();
|
|
@@ -6932,14 +7449,14 @@ const load$2 = Effect.fn("PluginCatalog.load")(function* (references, options) {
|
|
|
6932
7449
|
remember(plugin, []);
|
|
6933
7450
|
continue;
|
|
6934
7451
|
}
|
|
6935
|
-
const target = yield* parse$
|
|
7452
|
+
const target = yield* parse$1(reference, options.hostDir);
|
|
6936
7453
|
const key = target.kind === "local" ? target.path : target.spec;
|
|
6937
7454
|
const already = seen.get(key);
|
|
6938
7455
|
if (already !== void 0) {
|
|
6939
7456
|
aliases.set(reference, already);
|
|
6940
7457
|
continue;
|
|
6941
7458
|
}
|
|
6942
|
-
const definition = yield* load$
|
|
7459
|
+
const definition = yield* load$2(target, origin, options);
|
|
6943
7460
|
yield* note(definition.plugin, definition.version === void 0 ? key : `${key}@${definition.version}`);
|
|
6944
7461
|
const names = target.kind === "local" ? [target.path, ...definition.name === void 0 ? [] : [definition.name]] : [canonical$1(target), target.spec];
|
|
6945
7462
|
remember(definition.plugin, [reference, ...names], {
|
|
@@ -7061,12 +7578,12 @@ const follow = Effect.fn("PluginCatalog.follow")(function* (references, pool, op
|
|
|
7061
7578
|
for (const reference of references) {
|
|
7062
7579
|
if (typeof reference !== "string") continue;
|
|
7063
7580
|
const owner = options.declared?.get(reference);
|
|
7064
|
-
const from = anchor
|
|
7581
|
+
const from = anchor(owner, options.hostDir);
|
|
7065
7582
|
const id = pool.aliases.get(reference);
|
|
7066
7583
|
if (id !== void 0) {
|
|
7067
7584
|
const origin = pool.origins.get(id);
|
|
7068
7585
|
if (owner === void 0 || origin?.reference !== reference) continue;
|
|
7069
|
-
if (anchor
|
|
7586
|
+
if (anchor(origin.file, options.hostDir) === from) continue;
|
|
7070
7587
|
}
|
|
7071
7588
|
if (Option.isSome(yield* filed(reference, from))) {
|
|
7072
7589
|
moved = true;
|
|
@@ -7075,7 +7592,7 @@ const follow = Effect.fn("PluginCatalog.follow")(function* (references, pool, op
|
|
|
7075
7592
|
}
|
|
7076
7593
|
if (!moved) for (const origin of pool.origins.values()) {
|
|
7077
7594
|
if (origin.generation === void 0) continue;
|
|
7078
|
-
const current = yield* filed(origin.reference, anchor
|
|
7595
|
+
const current = yield* filed(origin.reference, anchor(origin.file, options.hostDir));
|
|
7079
7596
|
if (Option.isSome(current) && current.value.generation !== void 0 && current.value.generation > origin.generation) {
|
|
7080
7597
|
moved = true;
|
|
7081
7598
|
break;
|
|
@@ -7084,7 +7601,7 @@ const follow = Effect.fn("PluginCatalog.follow")(function* (references, pool, op
|
|
|
7084
7601
|
if (!moved) return Option.none();
|
|
7085
7602
|
const retained = [];
|
|
7086
7603
|
for (const origin of pool.origins.values()) {
|
|
7087
|
-
if (references.includes(origin.reference) || Option.isSome(yield* filed(origin.reference, anchor
|
|
7604
|
+
if (references.includes(origin.reference) || Option.isSome(yield* filed(origin.reference, anchor(origin.file, options.hostDir)))) {
|
|
7088
7605
|
retained.push(origin);
|
|
7089
7606
|
continue;
|
|
7090
7607
|
}
|
|
@@ -7094,13 +7611,13 @@ const follow = Effect.fn("PluginCatalog.follow")(function* (references, pool, op
|
|
|
7094
7611
|
const known = /* @__PURE__ */ new Map();
|
|
7095
7612
|
for (const origin of retained) if (origin.file !== void 0) known.set(origin.reference, origin.file);
|
|
7096
7613
|
for (const [reference, file] of options.declared ?? /* @__PURE__ */ new Map()) known.set(reference, file);
|
|
7097
|
-
return Option.some(yield* load$
|
|
7614
|
+
return Option.some(yield* load$1(accumulated, {
|
|
7098
7615
|
...options,
|
|
7099
7616
|
declared: known
|
|
7100
7617
|
}));
|
|
7101
7618
|
});
|
|
7102
7619
|
Effect.fn("PluginCatalog.prepare")(function* (references, options) {
|
|
7103
|
-
const pool = yield* load$
|
|
7620
|
+
const pool = yield* load$1(references, options);
|
|
7104
7621
|
return (yield* select(references, pool)).selection;
|
|
7105
7622
|
});
|
|
7106
7623
|
//#endregion
|
|
@@ -7178,7 +7695,7 @@ const make$7 = (options) => Effect.gen(function* () {
|
|
|
7178
7695
|
});
|
|
7179
7696
|
//#endregion
|
|
7180
7697
|
//#region src/runner/execute.ts
|
|
7181
|
-
const layer$
|
|
7698
|
+
const layer$6 = Layer.effect(Service$6, Effect.gen(function* () {
|
|
7182
7699
|
const store = yield* Service$14;
|
|
7183
7700
|
const sandbox = yield* Controller;
|
|
7184
7701
|
const sql = yield* SqlClient.SqlClient;
|
|
@@ -7244,11 +7761,6 @@ const layer$7 = Layer.effect(Service$6, Effect.gen(function* () {
|
|
|
7244
7761
|
});
|
|
7245
7762
|
}));
|
|
7246
7763
|
//#endregion
|
|
7247
|
-
//#region src/settings/schema.ts
|
|
7248
|
-
var schema_exports$3 = /* @__PURE__ */ __exportAll({});
|
|
7249
|
-
import * as import__codeworksh_plugin_settings from "@codeworksh/plugin/settings";
|
|
7250
|
-
__reExport(schema_exports$3, import__codeworksh_plugin_settings);
|
|
7251
|
-
//#endregion
|
|
7252
7764
|
//#region src/settings/resolve.ts
|
|
7253
7765
|
const patternMatches = (pattern, id) => new RegExp(`^${pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`).test(id);
|
|
7254
7766
|
function collect(settings, provider, id) {
|
|
@@ -7287,11 +7799,12 @@ function resolveRequest(block, model) {
|
|
|
7287
7799
|
"toolExecution",
|
|
7288
7800
|
"contextWindow",
|
|
7289
7801
|
"protocol",
|
|
7802
|
+
"apiKey",
|
|
7290
7803
|
"extras"
|
|
7291
7804
|
].includes(key)));
|
|
7292
7805
|
return {
|
|
7293
7806
|
...resolveOptions(block),
|
|
7294
|
-
...Object.keys(bag).length === 0 ? {} : { providerOptions: { [Model
|
|
7807
|
+
...Object.keys(bag).length === 0 ? {} : { providerOptions: { [Model.optionsKey(model)]: bag } }
|
|
7295
7808
|
};
|
|
7296
7809
|
}
|
|
7297
7810
|
/**
|
|
@@ -7525,15 +8038,27 @@ const messageFailure = (message) => {
|
|
|
7525
8038
|
};
|
|
7526
8039
|
/** Resolve once before exchange setup; execution reuses this exact instance. */
|
|
7527
8040
|
const resolve$1 = Effect.fn("LLM.resolve")(function* (input) {
|
|
7528
|
-
const
|
|
7529
|
-
|
|
8041
|
+
const overrides = resolveOverrides(input.settings ?? {});
|
|
8042
|
+
const declared = entry(input.models ?? [], input.provider, input.model);
|
|
8043
|
+
const model = declared !== void 0 ? {
|
|
8044
|
+
...declared,
|
|
8045
|
+
...overrides
|
|
8046
|
+
} : yield* Effect.tryPromise({
|
|
8047
|
+
try: () => llm(input.provider, input.model, overrides),
|
|
7530
8048
|
catch: (cause) => loadError(cause) ?? providerErrorFromUnknown(input, cause)
|
|
7531
8049
|
});
|
|
7532
8050
|
if (model === void 0) return yield* new ModelNotFoundError({
|
|
7533
8051
|
provider: input.provider,
|
|
7534
8052
|
model: input.model
|
|
7535
8053
|
});
|
|
7536
|
-
|
|
8054
|
+
const apiKey = input.settings?.apiKey;
|
|
8055
|
+
return apiKey === void 0 ? model : {
|
|
8056
|
+
...model,
|
|
8057
|
+
provider: {
|
|
8058
|
+
...model.provider,
|
|
8059
|
+
key: (0, schema_exports$3.keyName)(apiKey)
|
|
8060
|
+
}
|
|
8061
|
+
};
|
|
7537
8062
|
});
|
|
7538
8063
|
/**
|
|
7539
8064
|
* Fill in what an OAuth provider needs and the model catalog cannot carry: the
|
|
@@ -7542,7 +8067,7 @@ const resolve$1 = Effect.fn("LLM.resolve")(function* (input) {
|
|
|
7542
8067
|
* with no stored login resolves to `{}` so aikit reports the missing key.
|
|
7543
8068
|
*/
|
|
7544
8069
|
const oauthCredentials = async (protocol, options) => {
|
|
7545
|
-
const storage = options.authFile
|
|
8070
|
+
const storage = oauthStorage(options.authFile);
|
|
7546
8071
|
if (protocol === "openai-codex") {
|
|
7547
8072
|
const apiKey = await getOpenAICodexApiKey({ storage: new JsonOpenAICodexAuthStorage(storage) });
|
|
7548
8073
|
return apiKey === void 0 ? {} : { apiKey };
|
|
@@ -7915,20 +8440,109 @@ const make$3 = (tools) => {
|
|
|
7915
8440
|
Context.Service()("@codeworksh/harness/tool/registry/ToolRegistry");
|
|
7916
8441
|
const make$2 = () => {
|
|
7917
8442
|
let open = true;
|
|
7918
|
-
let
|
|
8443
|
+
let foundation;
|
|
8444
|
+
const sections = /* @__PURE__ */ new Map();
|
|
8445
|
+
const writable = () => {
|
|
8446
|
+
if (!open) throw new Error("prompt registry is closed");
|
|
8447
|
+
};
|
|
8448
|
+
const slot = (section) => {
|
|
8449
|
+
writable();
|
|
8450
|
+
const reason = Section.invalid(section.name);
|
|
8451
|
+
if (reason !== void 0) throw new Error(reason);
|
|
8452
|
+
if (!Section.isFormat(section.format)) throw new Error(`prompt section ${section.name} has an unknown format: ${JSON.stringify(section.format)}`);
|
|
8453
|
+
const builtin = Section.builtins.find((candidate) => candidate.name === section.name);
|
|
8454
|
+
if (builtin !== void 0 && builtin.format !== section.format) throw new Error(`prompt section ${section.name} is built in as "${builtin.format}"`);
|
|
8455
|
+
const existing = sections.get(section.name);
|
|
8456
|
+
if (existing === void 0) {
|
|
8457
|
+
const opened = {
|
|
8458
|
+
format: section.format,
|
|
8459
|
+
entries: []
|
|
8460
|
+
};
|
|
8461
|
+
sections.set(section.name, opened);
|
|
8462
|
+
return opened;
|
|
8463
|
+
}
|
|
8464
|
+
if (existing.format !== section.format) throw new Error(`prompt section ${section.name} is "${existing.format}"; a write as "${section.format}" cannot merge into it`);
|
|
8465
|
+
return existing;
|
|
8466
|
+
};
|
|
7919
8467
|
return {
|
|
7920
8468
|
registry: Object.freeze({
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
8469
|
+
foundation: Object.freeze({
|
|
8470
|
+
set: (text) => {
|
|
8471
|
+
writable();
|
|
8472
|
+
foundation = text;
|
|
8473
|
+
},
|
|
8474
|
+
get: () => foundation
|
|
8475
|
+
}),
|
|
8476
|
+
sections: Object.freeze({
|
|
8477
|
+
append: (section, entry) => {
|
|
8478
|
+
slot(section).entries.push(entry);
|
|
8479
|
+
},
|
|
8480
|
+
set: (section, entries) => {
|
|
8481
|
+
slot(section).entries = [...entries];
|
|
8482
|
+
},
|
|
8483
|
+
remove: (section) => {
|
|
8484
|
+
writable();
|
|
8485
|
+
sections.delete(section.name);
|
|
8486
|
+
},
|
|
8487
|
+
get: (section) => {
|
|
8488
|
+
const found = sections.get(section.name);
|
|
8489
|
+
return found === void 0 ? void 0 : Object.freeze([...found.entries]);
|
|
8490
|
+
}
|
|
8491
|
+
})
|
|
7926
8492
|
}),
|
|
7927
8493
|
close: () => {
|
|
7928
8494
|
open = false;
|
|
7929
8495
|
},
|
|
7930
|
-
|
|
8496
|
+
snapshot: () => ({
|
|
8497
|
+
foundation,
|
|
8498
|
+
sections: new Map(Array.from(sections, ([name, { format, entries }]) => [name, {
|
|
8499
|
+
format,
|
|
8500
|
+
entries: [...entries]
|
|
8501
|
+
}]))
|
|
8502
|
+
})
|
|
8503
|
+
};
|
|
8504
|
+
};
|
|
8505
|
+
//#endregion
|
|
8506
|
+
//#region src/plugin/prompt/render.ts
|
|
8507
|
+
const tag = (name, body) => `<${name}>\n${body}\n</${name}>`;
|
|
8508
|
+
/** Two spellings of one list entry dedupe against each other. */
|
|
8509
|
+
const normalize = (value) => value.trim().replace(/\s+/g, " ");
|
|
8510
|
+
const body = ({ format, entries }) => {
|
|
8511
|
+
if (format === "text") {
|
|
8512
|
+
const kept = entries.filter((entry) => entry.trim().length > 0);
|
|
8513
|
+
return kept.length === 0 ? void 0 : kept.join("\n\n");
|
|
8514
|
+
}
|
|
8515
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8516
|
+
for (const entry of entries) {
|
|
8517
|
+
const normalized = normalize(entry);
|
|
8518
|
+
if (normalized.length > 0) seen.add(normalized);
|
|
8519
|
+
}
|
|
8520
|
+
return seen.size === 0 ? void 0 : Array.from(seen, (line) => `- ${line}`).join("\n");
|
|
8521
|
+
};
|
|
8522
|
+
/** `(none)` rather than no section: a model told it has no tools behaves better than one left guessing. */
|
|
8523
|
+
const toolIndex = (tools) => {
|
|
8524
|
+
const listed = tools.filter((tool) => tool.promptSnippet !== void 0 && tool.promptSnippet.length > 0);
|
|
8525
|
+
if (listed.length === 0) return "(none)";
|
|
8526
|
+
return listed.map((tool) => `- ${tool.name}: ${tool.promptSnippet}`).join("\n");
|
|
8527
|
+
};
|
|
8528
|
+
const render = (input) => {
|
|
8529
|
+
const written = new Map(input.prompt.sections);
|
|
8530
|
+
const guidelines = input.tools.flatMap((tool) => tool.promptGuidelines ?? []);
|
|
8531
|
+
written.set(Section.Rules.name, {
|
|
8532
|
+
format: Section.Rules.format,
|
|
8533
|
+
entries: [...guidelines, ...written.get(Section.Rules.name)?.entries ?? []]
|
|
8534
|
+
});
|
|
8535
|
+
const parts = [input.prompt.foundation ?? "You are an expert coding assistant operating inside codework, a coding agent harness.", tag("tools", toolIndex(input.tools))];
|
|
8536
|
+
const push = (name) => {
|
|
8537
|
+
const section = written.get(name);
|
|
8538
|
+
const rendered = section === void 0 ? void 0 : body(section);
|
|
8539
|
+
if (rendered !== void 0) parts.push(tag(name, rendered));
|
|
8540
|
+
written.delete(name);
|
|
7931
8541
|
};
|
|
8542
|
+
for (const section of Section.builtins) push(section.name);
|
|
8543
|
+
parts.push(tag("cwd", input.directory));
|
|
8544
|
+
for (const name of Array.from(written.keys())) push(name);
|
|
8545
|
+
return parts.join("\n\n");
|
|
7932
8546
|
};
|
|
7933
8547
|
//#endregion
|
|
7934
8548
|
//#region src/plugin/tool/registry.ts
|
|
@@ -8020,11 +8634,16 @@ const make = () => {
|
|
|
8020
8634
|
prompt: prompt.registry
|
|
8021
8635
|
}),
|
|
8022
8636
|
close,
|
|
8023
|
-
freeze: () => {
|
|
8637
|
+
freeze: (directory) => {
|
|
8024
8638
|
close();
|
|
8639
|
+
const resolved = make$3(tools.entries()).resolve();
|
|
8025
8640
|
return {
|
|
8026
|
-
tools:
|
|
8027
|
-
systemPrompt:
|
|
8641
|
+
tools: resolved,
|
|
8642
|
+
systemPrompt: render({
|
|
8643
|
+
prompt: prompt.snapshot(),
|
|
8644
|
+
tools: resolved.defs,
|
|
8645
|
+
directory
|
|
8646
|
+
})
|
|
8028
8647
|
};
|
|
8029
8648
|
}
|
|
8030
8649
|
};
|
|
@@ -8069,7 +8688,7 @@ const run = Effect.fn("PluginHost.run")(function* (selection, input) {
|
|
|
8069
8688
|
}));
|
|
8070
8689
|
}));
|
|
8071
8690
|
return yield* Effect.try({
|
|
8072
|
-
try: buckets.freeze,
|
|
8691
|
+
try: () => buckets.freeze(input.location.directory),
|
|
8073
8692
|
catch: (cause) => new SetupError({
|
|
8074
8693
|
message: `plugin snapshot freeze failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
8075
8694
|
cause
|
|
@@ -8079,8 +8698,8 @@ const run = Effect.fn("PluginHost.run")(function* (selection, input) {
|
|
|
8079
8698
|
});
|
|
8080
8699
|
//#endregion
|
|
8081
8700
|
//#region src/session/runtime.ts
|
|
8082
|
-
var Service$
|
|
8083
|
-
const layer$
|
|
8701
|
+
var Service$2 = class extends Context.Service()("@codeworksh/harness/session/runtime/Service") {};
|
|
8702
|
+
const layer$5 = Layer.effect(Service$2, Effect.gen(function* () {
|
|
8084
8703
|
const bindings = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
8085
8704
|
const get = (sessionId) => Ref.get(bindings).pipe(Effect.map((entries) => Option.fromUndefinedOr(entries.get(sessionId))));
|
|
8086
8705
|
const set = (sessionId, value) => Ref.update(bindings, (entries) => {
|
|
@@ -8101,7 +8720,7 @@ const layer$6 = Layer.effect(Service$3, Effect.gen(function* () {
|
|
|
8101
8720
|
next.delete(sessionId);
|
|
8102
8721
|
return next;
|
|
8103
8722
|
});
|
|
8104
|
-
return Service$
|
|
8723
|
+
return Service$2.of({
|
|
8105
8724
|
get,
|
|
8106
8725
|
set,
|
|
8107
8726
|
update,
|
|
@@ -8109,202 +8728,6 @@ const layer$6 = Layer.effect(Service$3, Effect.gen(function* () {
|
|
|
8109
8728
|
});
|
|
8110
8729
|
}));
|
|
8111
8730
|
//#endregion
|
|
8112
|
-
//#region src/settings/settings.ts
|
|
8113
|
-
var settings_exports = /* @__PURE__ */ __exportAll({
|
|
8114
|
-
Service: () => Service$2,
|
|
8115
|
-
Settings: () => settings_exports,
|
|
8116
|
-
SettingsError: () => SettingsError,
|
|
8117
|
-
defaults: () => schema_exports$3.defaults,
|
|
8118
|
-
layer: () => layer$5,
|
|
8119
|
-
load: () => load$1,
|
|
8120
|
-
modules: () => modules,
|
|
8121
|
-
parse: () => parse$1,
|
|
8122
|
-
paths: () => paths,
|
|
8123
|
-
projectRoot: () => projectRoot,
|
|
8124
|
-
userConfigDir: () => userConfigDir
|
|
8125
|
-
});
|
|
8126
|
-
/**
|
|
8127
|
-
* An app-level `--user-config-dir` as the absolute directory it names: `~` is the user's home, and
|
|
8128
|
-
* a relative spelling is relative to `hostCwd` -- never to a session's `hostDir`, which is not
|
|
8129
|
-
* where the flag was typed.
|
|
8130
|
-
*/
|
|
8131
|
-
const userConfigDir = (raw, hostCwd) => hostPath.resolve(hostCwd, expandTilde(raw, hostPath));
|
|
8132
|
-
/** The settings files a directory may hold, in the order it is searched. */
|
|
8133
|
-
const settingsIn = (directory) => [hostPath.join(directory, "settings.jsonc"), hostPath.join(directory, "settings.json")];
|
|
8134
|
-
/** A directory and every ancestor above it, nearest first. */
|
|
8135
|
-
const ancestors = (from) => {
|
|
8136
|
-
const chain = [];
|
|
8137
|
-
for (let directory = from;; directory = hostPath.dirname(directory)) {
|
|
8138
|
-
chain.push(directory);
|
|
8139
|
-
if (hostPath.dirname(directory) === directory) return chain;
|
|
8140
|
-
}
|
|
8141
|
-
};
|
|
8142
|
-
const isDirectory = (path) => fileSystem.stat(path).pipe(Effect.map((info) => info.type === "Directory"), Effect.orElseSucceed(() => false));
|
|
8143
|
-
/**
|
|
8144
|
-
* The project a directory is in: the nearest ancestor holding a `.codework` **directory**.
|
|
8145
|
-
*
|
|
8146
|
-
* This is git's rule. `~/workspace/app/.codework` makes `~/workspace/app` a project, and
|
|
8147
|
-
* `~/workspace/app/packages/fubar` is inside it. Nearest wins outright -- the walk stops at the
|
|
8148
|
-
* first marker, so an inner project is never merged with an outer one.
|
|
8149
|
-
*
|
|
8150
|
-
* The marker is the directory, not a file in it. An empty `.codework/` is still a project root,
|
|
8151
|
-
* which is the point: creating the directory is how a person says "a project begins here", and it
|
|
8152
|
-
* has to mean that before any settings exist. It also makes the question one `stat` per ancestor
|
|
8153
|
-
* whose answer does not change as files come and go inside.
|
|
8154
|
-
*
|
|
8155
|
-
* `<home>` is skipped. `~/.codework` is the user layer, not a project; without this, running
|
|
8156
|
-
* anywhere under `$HOME` would find it as one and read the user layer twice -- and since plugin
|
|
8157
|
-
* entries accumulate across layers, every user plugin would load twice.
|
|
8158
|
-
*/
|
|
8159
|
-
const projectRoot = Effect.fn("Settings.projectRoot")(function* (from, home) {
|
|
8160
|
-
const userConfig = hostPath.resolve(home);
|
|
8161
|
-
for (const directory of ancestors(hostPath.resolve(from))) {
|
|
8162
|
-
const marker = hostPath.join(directory, appConfigDir);
|
|
8163
|
-
if (hostPath.resolve(marker) === userConfig) continue;
|
|
8164
|
-
if (yield* isDirectory(marker)) return directory;
|
|
8165
|
-
}
|
|
8166
|
-
});
|
|
8167
|
-
/**
|
|
8168
|
-
* Ordered layers, each a group of candidates where the first file that exists is selected -- a
|
|
8169
|
-
* group never contributes more than one file.
|
|
8170
|
-
*
|
|
8171
|
-
* `.jsonc` is the canonical spelling: these files are written by hand and read as JSONC, so the
|
|
8172
|
-
* extension says what the file is and an editor validates it as such. `.json` remains a fallback
|
|
8173
|
-
* everywhere, tried second, because the parser reads either and a file already named that must
|
|
8174
|
-
* keep working.
|
|
8175
|
-
*
|
|
8176
|
-
* `root` is an already-discovered project root ({@link projectRoot}), not a directory to search
|
|
8177
|
-
* from: the walk is a `stat` per ancestor and belongs with the other I/O. `undefined` contributes
|
|
8178
|
-
* no project layer at all, which is what a session with no host directory gets.
|
|
8179
|
-
*/
|
|
8180
|
-
function paths(input) {
|
|
8181
|
-
return [settingsIn(input.userConfigDir ?? input.home), input.root === void 0 ? [] : settingsIn(hostPath.join(input.root, appConfigDir))];
|
|
8182
|
-
}
|
|
8183
|
-
var SettingsError = class extends Schema.TaggedError()("SettingsError", {
|
|
8184
|
-
path: Schema.String,
|
|
8185
|
-
reason: Schema.Literals([
|
|
8186
|
-
"read",
|
|
8187
|
-
"parse",
|
|
8188
|
-
"decode"
|
|
8189
|
-
]),
|
|
8190
|
-
detail: Schema.String
|
|
8191
|
-
}) {
|
|
8192
|
-
get message() {
|
|
8193
|
-
return `${this.path}: ${this.reason}: ${this.detail}`;
|
|
8194
|
-
}
|
|
8195
|
-
};
|
|
8196
|
-
/** `line:column`, 1-based, for a parser offset into the source. */
|
|
8197
|
-
const at = (source, offset) => {
|
|
8198
|
-
const before = source.slice(0, offset).split("\n");
|
|
8199
|
-
return `${before.length}:${(before.at(-1)?.length ?? 0) + 1}`;
|
|
8200
|
-
};
|
|
8201
|
-
const parse$1 = Effect.fn("Settings.parse")(function* (path, source) {
|
|
8202
|
-
const errors = [];
|
|
8203
|
-
const json = parse(source, errors, {
|
|
8204
|
-
allowTrailingComma: true,
|
|
8205
|
-
disallowComments: false
|
|
8206
|
-
});
|
|
8207
|
-
const first = errors[0];
|
|
8208
|
-
if (first !== void 0) return yield* new SettingsError({
|
|
8209
|
-
path,
|
|
8210
|
-
reason: "parse",
|
|
8211
|
-
detail: `${printParseErrorCode(first.error)} at ${at(source, first.offset)}`
|
|
8212
|
-
});
|
|
8213
|
-
const document = normalize(json);
|
|
8214
|
-
if (Predicate.isObject(document) && Predicate.isObject(json) && "plugins" in json) Object.assign(document, { plugins: json.plugins });
|
|
8215
|
-
return yield* Schema.decodeUnknownEffect(schema_exports$3.Patch)(document).pipe(Effect.mapError((error) => new SettingsError({
|
|
8216
|
-
path,
|
|
8217
|
-
reason: "decode",
|
|
8218
|
-
detail: SchemaIssue.makeFormatterStandardSchemaV1()(error.issue).issues.map((issue) => `${issue.path?.map((part) => typeof part === "object" ? String(part.key) : part).join(".") ?? "settings"}: invalid value`).join("; ")
|
|
8219
|
-
})));
|
|
8220
|
-
});
|
|
8221
|
-
var Service$2 = class extends Context.Service()("@codeworksh/harness/settings/settings/Service") {};
|
|
8222
|
-
/**
|
|
8223
|
-
* Anchor relative plugin entries to the file that declared them.
|
|
8224
|
-
*
|
|
8225
|
-
* A reference is otherwise resolved against the host startup directory, which is right for
|
|
8226
|
-
* a project file sitting in it and meaningless for `~/.codework/settings.json`, where
|
|
8227
|
-
* `./plugins/x.ts` would name a different file in every project the process is started in.
|
|
8228
|
-
* `file:` URLs, package specs and package names are left exactly as written, and a `plugin`
|
|
8229
|
-
* key is an ID rather than a location. A configuration entry's `package` is anchored like a
|
|
8230
|
-
* module entry, so a relative path names the same module in both spellings.
|
|
8231
|
-
*/
|
|
8232
|
-
const anchored = (entry, file) => {
|
|
8233
|
-
const directory = hostPath.dirname(file);
|
|
8234
|
-
const resolve = (reference) => reference.startsWith("./") || reference.startsWith("../") ? hostPath.resolve(directory, reference) : reference;
|
|
8235
|
-
if (typeof entry === "string") return resolve(entry);
|
|
8236
|
-
return "package" in entry ? {
|
|
8237
|
-
...entry,
|
|
8238
|
-
package: resolve(entry.package)
|
|
8239
|
-
} : entry;
|
|
8240
|
-
};
|
|
8241
|
-
const anchor = (patch, file) => patch.plugins === void 0 ? patch : {
|
|
8242
|
-
...patch,
|
|
8243
|
-
plugins: patch.plugins.map((entry) => anchored(entry, file))
|
|
8244
|
-
};
|
|
8245
|
-
const attempt = (path) => fileSystem.readFileString(path).pipe(Effect.mapError((error) => new SettingsError({
|
|
8246
|
-
path,
|
|
8247
|
-
reason: "read",
|
|
8248
|
-
detail: error.reason._tag
|
|
8249
|
-
})), Effect.flatMap((source) => parse$1(path, source)));
|
|
8250
|
-
/**
|
|
8251
|
-
* One read of every layer. Exported for the harness constructor, which needs the plugin
|
|
8252
|
-
* selection before any layer is built; everything else goes through `Service`.
|
|
8253
|
-
*/
|
|
8254
|
-
const load$1 = Effect.fn("Settings.load")(function* (options) {
|
|
8255
|
-
const root = options.hostDir === void 0 ? void 0 : yield* projectRoot(options.hostDir, options.home);
|
|
8256
|
-
const files = paths({
|
|
8257
|
-
home: options.home,
|
|
8258
|
-
root,
|
|
8259
|
-
...options.userConfigDir === void 0 ? {} : { userConfigDir: options.userConfigDir }
|
|
8260
|
-
});
|
|
8261
|
-
let settings = merge(schema_exports$3.defaults);
|
|
8262
|
-
let declared = schema_exports$3.defaults.declared;
|
|
8263
|
-
for (const group of files) for (const path of group) {
|
|
8264
|
-
const result = yield* Effect.result(attempt(path));
|
|
8265
|
-
if (Result.isSuccess(result)) {
|
|
8266
|
-
const patch = anchor(result.success, path);
|
|
8267
|
-
settings = merge(settings, patch);
|
|
8268
|
-
if (result.success.plugins !== void 0) declared = [...declared, ...result.success.plugins.map((written) => ({
|
|
8269
|
-
written,
|
|
8270
|
-
entry: anchored(written, path),
|
|
8271
|
-
file: path
|
|
8272
|
-
}))];
|
|
8273
|
-
break;
|
|
8274
|
-
}
|
|
8275
|
-
const error = result.failure;
|
|
8276
|
-
if (error.reason === "read" && error.detail === "NotFound") continue;
|
|
8277
|
-
return yield* error;
|
|
8278
|
-
}
|
|
8279
|
-
return {
|
|
8280
|
-
...settings,
|
|
8281
|
-
plugins: declared.map((one) => one.entry),
|
|
8282
|
-
declared
|
|
8283
|
-
};
|
|
8284
|
-
});
|
|
8285
|
-
/**
|
|
8286
|
-
* Each module reference once, owned by the **last** file to declare it (D6).
|
|
8287
|
-
*
|
|
8288
|
-
* The owner is not bookkeeping: its `.npmrc` chain keys the store artifact. The CLI that installs
|
|
8289
|
-
* a reference and the runtime that loads it must pick the same owner, or `plugin update` refreshes
|
|
8290
|
-
* an artifact the runtime never reads. Only a string entry loads a module, so only a string entry
|
|
8291
|
-
* owns one -- a `{ package }` patch in another file configures it and must not move its registry.
|
|
8292
|
-
* A reference keeps the position of its first declaration.
|
|
8293
|
-
*/
|
|
8294
|
-
const modules = (declared) => {
|
|
8295
|
-
const owners = /* @__PURE__ */ new Map();
|
|
8296
|
-
for (const one of declared) if (typeof one.entry === "string") owners.set(one.entry, one);
|
|
8297
|
-
return owners;
|
|
8298
|
-
};
|
|
8299
|
-
const layer$5 = (options = {}) => Layer.effect(Service$2, Effect.gen(function* () {
|
|
8300
|
-
const global = yield* Service$17;
|
|
8301
|
-
return Service$2.of({ load: (hostDir) => load$1({
|
|
8302
|
-
...options,
|
|
8303
|
-
home: global.home,
|
|
8304
|
-
hostDir
|
|
8305
|
-
}) });
|
|
8306
|
-
}));
|
|
8307
|
-
//#endregion
|
|
8308
8731
|
//#region src/state/state.ts
|
|
8309
8732
|
var state_exports = /* @__PURE__ */ __exportAll({
|
|
8310
8733
|
Service: () => Service$1,
|
|
@@ -8354,9 +8777,9 @@ const resolver = (input) => typeof input === "string" ? () => input : input;
|
|
|
8354
8777
|
*/
|
|
8355
8778
|
const layer$4 = (options, pool, references, follow, rebuild, viewOf = () => Effect.undefined) => {
|
|
8356
8779
|
return Layer.effect(Service$1, Effect.gen(function* () {
|
|
8357
|
-
const runtime = yield* Service$
|
|
8780
|
+
const runtime = yield* Service$2;
|
|
8358
8781
|
const sessions = yield* Service$14;
|
|
8359
|
-
const settings = yield* Service$
|
|
8782
|
+
const settings = yield* Service$3;
|
|
8360
8783
|
const eventRegistry = yield* Service$4;
|
|
8361
8784
|
const eventService = yield* Service$16;
|
|
8362
8785
|
const events = makeEvents(eventService);
|
|
@@ -8409,6 +8832,26 @@ const layer$4 = (options, pool, references, follow, rebuild, viewOf = () => Effe
|
|
|
8409
8832
|
..."reference" in cause && typeof cause.reference === "string" ? { reference: cause.reference } : {},
|
|
8410
8833
|
...sessionId === void 0 ? {} : { sessionId }
|
|
8411
8834
|
});
|
|
8835
|
+
/** Settings and composed configuration for a session's next exchange. */
|
|
8836
|
+
const configure = Effect.fnUntraced(function* (sessionId) {
|
|
8837
|
+
const session = Option.getOrUndefined(yield* sessions.get(sessionId));
|
|
8838
|
+
const hostDir = session === void 0 ? void 0 : Option.getOrUndefined(session.hostDir);
|
|
8839
|
+
const chosen = session === void 0 ? {} : Option.getOrElse(session.config, () => ({}));
|
|
8840
|
+
const sessionOptions = {
|
|
8841
|
+
...chosen.model === void 0 ? {} : {
|
|
8842
|
+
provider: chosen.model.provider,
|
|
8843
|
+
model: chosen.model.id
|
|
8844
|
+
},
|
|
8845
|
+
...chosen.thinkingLevel === void 0 ? {} : { thinkingLevel: chosen.thinkingLevel },
|
|
8846
|
+
...Option.getOrElse(yield* runtime.get(sessionId), () => ({}))
|
|
8847
|
+
};
|
|
8848
|
+
const loadedSettings = yield* settings.load(hostDir);
|
|
8849
|
+
return {
|
|
8850
|
+
hostDir,
|
|
8851
|
+
loadedSettings,
|
|
8852
|
+
configured: compose(loadedSettings, options, sessionOptions)
|
|
8853
|
+
};
|
|
8854
|
+
});
|
|
8412
8855
|
return Service$1.of({
|
|
8413
8856
|
reload: Effect.gen(function* () {
|
|
8414
8857
|
const failures = /* @__PURE__ */ new Map();
|
|
@@ -8427,12 +8870,16 @@ const layer$4 = (options, pool, references, follow, rebuild, viewOf = () => Effe
|
|
|
8427
8870
|
failure: first
|
|
8428
8871
|
};
|
|
8429
8872
|
}),
|
|
8873
|
+
configuration: Effect.fn("State.configuration")(function* (sessionId) {
|
|
8874
|
+
const { configured } = yield* configure(sessionId);
|
|
8875
|
+
return {
|
|
8876
|
+
provider: configured.provider,
|
|
8877
|
+
model: configured.model,
|
|
8878
|
+
thinkingLevel: configured.thinkingLevel
|
|
8879
|
+
};
|
|
8880
|
+
}),
|
|
8430
8881
|
snapshot: Effect.fn("State.snapshot")(function* (sessionId) {
|
|
8431
|
-
const
|
|
8432
|
-
const session = yield* sessions.get(sessionId);
|
|
8433
|
-
const hostDir = Option.isNone(session) ? void 0 : Option.getOrUndefined(session.value.hostDir);
|
|
8434
|
-
const loadedSettings = yield* settings.load(hostDir);
|
|
8435
|
-
const configured = compose(loadedSettings, options, sessionOptions);
|
|
8882
|
+
const { hostDir, loadedSettings, configured } = yield* configure(sessionId);
|
|
8436
8883
|
const { promptCustom, promptSystemAppend, provider: _provider, model: _model, thinkingLevel: _thinkingLevel, toolExecution: _toolExecution, ...rest } = configured.runtime;
|
|
8437
8884
|
const { provider, model, thinkingLevel, toolExecution } = configured;
|
|
8438
8885
|
const request = merge(resolveOptions(configured.block), rest);
|
|
@@ -8441,7 +8888,8 @@ const layer$4 = (options, pool, references, follow, rebuild, viewOf = () => Effe
|
|
|
8441
8888
|
const resolvedModel = yield* resolve$1({
|
|
8442
8889
|
provider,
|
|
8443
8890
|
model,
|
|
8444
|
-
settings: configured.block
|
|
8891
|
+
settings: configured.block,
|
|
8892
|
+
models: loadedSettings.models
|
|
8445
8893
|
});
|
|
8446
8894
|
const refs = references(loadedSettings);
|
|
8447
8895
|
const failed = (cause) => new SnapshotError({
|
|
@@ -8460,7 +8908,15 @@ const layer$4 = (options, pool, references, follow, rebuild, viewOf = () => Effe
|
|
|
8460
8908
|
const chosen = yield* select(refs, loaded).pipe(Effect.mapError(failed));
|
|
8461
8909
|
for (const reference of chosen.missing) {
|
|
8462
8910
|
const where = loadedSettings.declared.find((one) => typeof one.entry === "string" ? one.entry === reference : "package" in one.entry && one.entry.package === reference);
|
|
8463
|
-
|
|
8911
|
+
const error = `plugin ${reference} is configured but not loaded — run \`codework plugin install\`${where === void 0 ? "" : ` (declared in ${where.file})`}`;
|
|
8912
|
+
yield* Effect.logWarning(error);
|
|
8913
|
+
yield* eventService.publish(PluginUpdated, {
|
|
8914
|
+
status: "failed",
|
|
8915
|
+
reference,
|
|
8916
|
+
error,
|
|
8917
|
+
...where === void 0 ? {} : { file: where.file },
|
|
8918
|
+
sessionId
|
|
8919
|
+
});
|
|
8464
8920
|
}
|
|
8465
8921
|
return {
|
|
8466
8922
|
sessionId,
|
|
@@ -8488,13 +8944,34 @@ const layer$4 = (options, pool, references, follow, rebuild, viewOf = () => Effe
|
|
|
8488
8944
|
thinkingLevel,
|
|
8489
8945
|
request,
|
|
8490
8946
|
settings: configured.block,
|
|
8491
|
-
toolExecution
|
|
8947
|
+
toolExecution,
|
|
8948
|
+
retry: loadedSettings.retry
|
|
8492
8949
|
};
|
|
8493
8950
|
})
|
|
8494
8951
|
});
|
|
8495
8952
|
}));
|
|
8496
8953
|
};
|
|
8497
8954
|
//#endregion
|
|
8955
|
+
//#region src/runner/retry.ts
|
|
8956
|
+
/**
|
|
8957
|
+
* Milliseconds to wait before retry `attempt` (1-based), or `undefined` to give up.
|
|
8958
|
+
*
|
|
8959
|
+
* Only a provider failure the provider itself calls transient is retried. A quota error is
|
|
8960
|
+
* excluded even though it often arrives as a retryable 429: waiting does not refill a quota.
|
|
8961
|
+
* Context overflow is a non-retryable 400 and is left to compaction.
|
|
8962
|
+
*
|
|
8963
|
+
* A server-sent `retry-after` raises the delay, never lowers it. One longer than `maxDelayMs`
|
|
8964
|
+
* gives up rather than retrying early into the same rejection.
|
|
8965
|
+
*/
|
|
8966
|
+
const delay = (policy, attempt, error) => {
|
|
8967
|
+
if (attempt > policy.maxRetries) return void 0;
|
|
8968
|
+
if (!Schema.is(ProviderError)(error) || !error.isRetryable) return void 0;
|
|
8969
|
+
if (error.reason._tag === "Runner.ProviderQuotaError") return void 0;
|
|
8970
|
+
const backoff = Math.min(policy.baseDelayMs * 2 ** (attempt - 1), policy.maxDelayMs);
|
|
8971
|
+
const requested = error.reason.retryAfter === void 0 ? 0 : Duration.toMillis(error.reason.retryAfter);
|
|
8972
|
+
return requested > policy.maxDelayMs ? void 0 : Math.max(backoff, Math.ceil(requested));
|
|
8973
|
+
};
|
|
8974
|
+
//#endregion
|
|
8498
8975
|
//#region src/runner/loop.ts
|
|
8499
8976
|
const terminalResult = (text) => ({
|
|
8500
8977
|
content: [{
|
|
@@ -8614,7 +9091,9 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
8614
9091
|
sessionId: snapshot.sessionId,
|
|
8615
9092
|
messageId,
|
|
8616
9093
|
callID: call.callID,
|
|
8617
|
-
name: call.name
|
|
9094
|
+
name: call.name,
|
|
9095
|
+
label: snapshot.tools.defs.find((def) => def.name === call.name)?.label,
|
|
9096
|
+
arguments: call.arguments
|
|
8618
9097
|
});
|
|
8619
9098
|
const handled = yield* snapshot.tools.handle(call, {
|
|
8620
9099
|
sessionId: snapshot.sessionId,
|
|
@@ -8661,11 +9140,14 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
8661
9140
|
if (leaf !== void 0 && (leaf.stopReason === "stop" || leaf.stopReason === "length")) return { needsContinuation: false };
|
|
8662
9141
|
let publisher;
|
|
8663
9142
|
let committed = false;
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
9143
|
+
let retries = 0;
|
|
9144
|
+
/** Abort the latest attempt's draft, unless its own terminal event already did. */
|
|
9145
|
+
const settleDraft = (cause) => Effect.gen(function* () {
|
|
9146
|
+
if (publisher?.startedMessageId === void 0) return;
|
|
9147
|
+
const stored = yield* sessions.entry(publisher.startedMessageId);
|
|
9148
|
+
if (Option.isSome(stored) && stored.value.entry.state === "draft") yield* publishFailure(stored.value, cause);
|
|
9149
|
+
});
|
|
9150
|
+
const attempt = Effect.gen(function* () {
|
|
8669
9151
|
publisher = yield* make$6({ sessionId }).pipe(Effect.provideService(Service$16, events));
|
|
8670
9152
|
const terminal = yield* requestLLM({
|
|
8671
9153
|
sessionId,
|
|
@@ -8689,6 +9171,43 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
8689
9171
|
model: snapshot.model
|
|
8690
9172
|
}, messageFailure(terminal.message));
|
|
8691
9173
|
}
|
|
9174
|
+
return terminal;
|
|
9175
|
+
});
|
|
9176
|
+
const request = () => attempt.pipe(Effect.catch((error) => {
|
|
9177
|
+
const delayMs = delay(snapshot.retry, retries + 1, error);
|
|
9178
|
+
if (delayMs === void 0) return Effect.fail(error);
|
|
9179
|
+
return Effect.gen(function* () {
|
|
9180
|
+
retries += 1;
|
|
9181
|
+
yield* Effect.uninterruptible(settleDraft({
|
|
9182
|
+
_tag: "error",
|
|
9183
|
+
message: error.message
|
|
9184
|
+
}));
|
|
9185
|
+
yield* events.publish(RetryScheduled, {
|
|
9186
|
+
timestamp: yield* DateTime.now,
|
|
9187
|
+
sessionId,
|
|
9188
|
+
attempt: retries,
|
|
9189
|
+
maxRetries: snapshot.retry.maxRetries,
|
|
9190
|
+
delayMs,
|
|
9191
|
+
message: error.message
|
|
9192
|
+
});
|
|
9193
|
+
yield* Effect.sleep(Duration.millis(delayMs));
|
|
9194
|
+
return yield* request();
|
|
9195
|
+
});
|
|
9196
|
+
}));
|
|
9197
|
+
return yield* Effect.gen(function* () {
|
|
9198
|
+
yield* events.publish(TurnStarted, {
|
|
9199
|
+
timestamp: yield* DateTime.now,
|
|
9200
|
+
sessionId
|
|
9201
|
+
});
|
|
9202
|
+
const terminal = yield* request().pipe(Effect.onExit((exit) => retries === 0 ? Effect.void : Effect.gen(function* () {
|
|
9203
|
+
yield* events.publish(RetryFinished, {
|
|
9204
|
+
timestamp: yield* DateTime.now,
|
|
9205
|
+
sessionId,
|
|
9206
|
+
attempt: retries,
|
|
9207
|
+
success: Exit.isSuccess(exit),
|
|
9208
|
+
...Exit.isSuccess(exit) ? {} : { message: Cause.hasInterrupts(exit.cause) ? "interrupted" : errorMessage(exit.cause) }
|
|
9209
|
+
});
|
|
9210
|
+
})));
|
|
8692
9211
|
const interrupted = yield* settleTools(snapshot, terminal.message, terminal.reason, () => {
|
|
8693
9212
|
committed = true;
|
|
8694
9213
|
});
|
|
@@ -8697,24 +9216,20 @@ const layer$3 = (options = {}) => Layer.effect(Service$7, Effect.gen(function* (
|
|
|
8697
9216
|
needsContinuation: terminal.reason === "toolUse",
|
|
8698
9217
|
messageId: terminal.message.messageId
|
|
8699
9218
|
};
|
|
8700
|
-
}).pipe(Effect.
|
|
8701
|
-
if (committed) return Effect.
|
|
9219
|
+
}).pipe(Effect.onError((cause) => {
|
|
9220
|
+
if (committed) return Effect.void;
|
|
8702
9221
|
const turnCause = Cause.hasInterrupts(cause) ? { _tag: "interrupted" } : {
|
|
8703
9222
|
_tag: "error",
|
|
8704
9223
|
message: errorMessage(cause)
|
|
8705
9224
|
};
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
const stored = yield* sessions.entry(publisher.startedMessageId);
|
|
8709
|
-
if (Option.isSome(stored) && stored.value.entry.state === "draft") yield* publishFailure(stored.value, turnCause);
|
|
8710
|
-
}
|
|
9225
|
+
return Effect.gen(function* () {
|
|
9226
|
+
yield* settleDraft(turnCause);
|
|
8711
9227
|
yield* events.publish(TurnAborted, {
|
|
8712
9228
|
timestamp: yield* DateTime.now,
|
|
8713
9229
|
sessionId,
|
|
8714
9230
|
cause: turnCause
|
|
8715
9231
|
});
|
|
8716
9232
|
});
|
|
8717
|
-
return Effect.uninterruptible(record).pipe(Effect.andThen(Effect.failCause(cause)));
|
|
8718
9233
|
}));
|
|
8719
9234
|
});
|
|
8720
9235
|
const runTurn = Effect.fn("Loop.runTurn")(function* (promotion, snapshot) {
|
|
@@ -8868,10 +9383,7 @@ const layer$2 = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
8868
9383
|
messageId: event.data.messageId,
|
|
8869
9384
|
role: "user",
|
|
8870
9385
|
time: { created: DateTime.toEpochMillis(event.data.timestamp) },
|
|
8871
|
-
parts: [
|
|
8872
|
-
type: "text",
|
|
8873
|
-
text: event.data.prompt.text
|
|
8874
|
-
}]
|
|
9386
|
+
parts: [...event.data.prompt.parts]
|
|
8875
9387
|
})).pipe(Effect.orDie);
|
|
8876
9388
|
yield* sessions.append({
|
|
8877
9389
|
id: event.data.messageId,
|
|
@@ -8881,7 +9393,13 @@ const layer$2 = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
8881
9393
|
...encoded,
|
|
8882
9394
|
...event.metadata === void 0 ? {} : { metadata: event.metadata }
|
|
8883
9395
|
}).pipe(Effect.orDie);
|
|
9396
|
+
const title = titleFrom(Prompt.text(event.data.prompt));
|
|
9397
|
+
if (title !== void 0) yield* sessions.retitle(event.data.sessionId, title);
|
|
8884
9398
|
}));
|
|
9399
|
+
yield* events.project(ConfigChanged, (event) => {
|
|
9400
|
+
const { sessionId, timestamp: _timestamp, ...config } = event.data;
|
|
9401
|
+
return sessions.configure(sessionId, config);
|
|
9402
|
+
});
|
|
8885
9403
|
yield* events.project(LLMStarted, (event) => Effect.gen(function* () {
|
|
8886
9404
|
if (event.durable === void 0) return yield* Effect.die("LLMStarted is missing its aggregate sequence");
|
|
8887
9405
|
const encoded = yield* encodeAssistant({
|
|
@@ -8950,9 +9468,9 @@ var harness_exports = /* @__PURE__ */ __exportAll({
|
|
|
8950
9468
|
const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
8951
9469
|
const hostCwd = options.hostCwd ?? process.cwd();
|
|
8952
9470
|
const paths = yield* resolve$3(options.home === void 0 ? {} : { home: hostPath.resolve(hostCwd, expandTilde(options.home, hostPath)) });
|
|
8953
|
-
const global = layerWith
|
|
9471
|
+
const global = layerWith(paths);
|
|
8954
9472
|
const settingsOptions = options.userConfigDir === void 0 ? {} : { userConfigDir: userConfigDir(options.userConfigDir, hostCwd) };
|
|
8955
|
-
const config = yield* load$
|
|
9473
|
+
const config = yield* load$3({
|
|
8956
9474
|
...settingsOptions,
|
|
8957
9475
|
home: paths.home
|
|
8958
9476
|
});
|
|
@@ -8969,12 +9487,12 @@ const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
8969
9487
|
* `hostDir` led to; only the process view falls back to `hostCwd`.
|
|
8970
9488
|
*/
|
|
8971
9489
|
const hostDirOf = (root) => root ?? hostCwd;
|
|
8972
|
-
const filed = (reference, from) => parse$
|
|
9490
|
+
const filed = (reference, from) => parse$1(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()));
|
|
8973
9491
|
const resolveOnly = {
|
|
8974
9492
|
...catalogOptions,
|
|
8975
9493
|
install: required
|
|
8976
9494
|
};
|
|
8977
|
-
const pool = yield* Ref.make(yield* load$
|
|
9495
|
+
const pool = yield* Ref.make(yield* load$1(references(config), {
|
|
8978
9496
|
...resolveOnly,
|
|
8979
9497
|
declared: declaredIn(config)
|
|
8980
9498
|
}));
|
|
@@ -8988,7 +9506,7 @@ const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
8988
9506
|
let reloads = 0;
|
|
8989
9507
|
const rebuild = (root, loaded) => Effect.gen(function* () {
|
|
8990
9508
|
reloads += 1;
|
|
8991
|
-
const current = yield* load$
|
|
9509
|
+
const current = yield* load$3({
|
|
8992
9510
|
...settingsOptions,
|
|
8993
9511
|
home: paths.home,
|
|
8994
9512
|
...root === void 0 ? {} : { hostDir: root }
|
|
@@ -8996,7 +9514,7 @@ const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
8996
9514
|
const retained = [];
|
|
8997
9515
|
const configured = references(current);
|
|
8998
9516
|
for (const origin of loaded.origins.values()) {
|
|
8999
|
-
if (configured.includes(origin.reference) || Option.isSome(yield* filed(origin.reference, anchor
|
|
9517
|
+
if (configured.includes(origin.reference) || Option.isSome(yield* filed(origin.reference, anchor(origin.file, hostDirOf(root))))) {
|
|
9000
9518
|
retained.push(origin);
|
|
9001
9519
|
continue;
|
|
9002
9520
|
}
|
|
@@ -9006,7 +9524,7 @@ const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
9006
9524
|
const known = /* @__PURE__ */ new Map();
|
|
9007
9525
|
for (const origin of retained) if (origin.file !== void 0) known.set(origin.reference, origin.file);
|
|
9008
9526
|
for (const [reference, file] of declaredIn(current)) known.set(reference, file);
|
|
9009
|
-
return yield* load$
|
|
9527
|
+
return yield* load$1(accumulated, {
|
|
9010
9528
|
...resolveOnly,
|
|
9011
9529
|
hostDir: hostDirOf(root),
|
|
9012
9530
|
declared: known,
|
|
@@ -9021,10 +9539,10 @@ const layer = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
9021
9539
|
const authFile$1 = authFile(options.home === void 0 ? void 0 : paths.home);
|
|
9022
9540
|
const open = options.llm ?? openWith(authFile$1 === void 0 ? {} : { authFile: authFile$1 });
|
|
9023
9541
|
const loop = layer$3({ request: make$5(open) });
|
|
9024
|
-
return layer$10.pipe(Layer.provideMerge(layer$
|
|
9542
|
+
return layer$10.pipe(Layer.provideMerge(layer$6.pipe(Layer.provide(loop))), Layer.provideMerge(layer$4({}, pool, references, followStore, rebuild, viewOf)), Layer.provideMerge(layer$8(settingsOptions)), Layer.provideMerge(layer$5), Layer.provideMerge(sandboxes), Layer.provideMerge(layer$22), Layer.provideMerge(layer$1), Layer.provideMerge(layer$25), Layer.provideMerge(layer$9(definitions)), Layer.provideMerge(database), Layer.provideMerge(global), Layer.provideMerge(layer$7({
|
|
9025
9543
|
home: paths.home,
|
|
9026
9544
|
watch: options.watchModels ?? false
|
|
9027
|
-
})));
|
|
9545
|
+
})), Layer.provideMerge(Layer.succeed(Credentials, { authFile: authFile$1 })));
|
|
9028
9546
|
}));
|
|
9029
9547
|
//#endregion
|
|
9030
9548
|
//#region src/effect/sandbox.ts
|
|
@@ -9151,30 +9669,54 @@ var session_exports = /* @__PURE__ */ __exportAll({
|
|
|
9151
9669
|
SessionMessageSchema: () => schema_exports$4,
|
|
9152
9670
|
SessionSchema: () => schema_exports$8,
|
|
9153
9671
|
attach: () => attach,
|
|
9672
|
+
configuration: () => configuration,
|
|
9154
9673
|
create: () => create$1,
|
|
9155
9674
|
get: () => get,
|
|
9156
9675
|
link: () => link,
|
|
9157
9676
|
relink: () => relink
|
|
9158
9677
|
});
|
|
9678
|
+
/** What stays in this process. The model and thinking level are the session's {@link chosen} config. */
|
|
9159
9679
|
const runtimeBindings = (input) => ({
|
|
9160
9680
|
...input.model?.options,
|
|
9161
|
-
...input.model === void 0 ? {} : {
|
|
9162
|
-
provider: input.model.provider,
|
|
9163
|
-
model: input.model.id
|
|
9164
|
-
},
|
|
9165
|
-
...input.thinkingLevel === void 0 ? {} : { thinkingLevel: input.thinkingLevel },
|
|
9166
9681
|
...input.tools?.execution === void 0 ? {} : { toolExecution: input.tools.execution },
|
|
9167
9682
|
...input.systemPrompt?.custom === void 0 ? {} : { promptCustom: input.systemPrompt.custom },
|
|
9168
9683
|
...input.systemPrompt?.append === void 0 ? {} : { promptSystemAppend: input.systemPrompt.append }
|
|
9169
9684
|
});
|
|
9170
|
-
|
|
9685
|
+
/** The keys of the session's durable config this input names. */
|
|
9686
|
+
const chosen = (input) => ({
|
|
9687
|
+
...input.model === void 0 ? {} : { model: {
|
|
9688
|
+
provider: input.model.provider,
|
|
9689
|
+
id: input.model.id
|
|
9690
|
+
} },
|
|
9691
|
+
...input.thinkingLevel === void 0 ? {} : { thinkingLevel: input.thinkingLevel }
|
|
9692
|
+
});
|
|
9693
|
+
/** Records a config choice; the projector merges it into the session row in the same commit. */
|
|
9694
|
+
const choose = Effect.fn("Session.choose")(function* (sessionId, input) {
|
|
9695
|
+
const config = chosen(input);
|
|
9696
|
+
if (Object.keys(config).length === 0) return;
|
|
9697
|
+
yield* (yield* Service$16).publish(ConfigChanged, {
|
|
9698
|
+
sessionId,
|
|
9699
|
+
timestamp: yield* DateTime.now,
|
|
9700
|
+
...config
|
|
9701
|
+
});
|
|
9702
|
+
});
|
|
9703
|
+
const promptInput = Effect.fnUntraced(function* (input) {
|
|
9171
9704
|
const value = typeof input === "string" ? { text: input } : input;
|
|
9705
|
+
const parts = "parts" in value ? value.parts : [{
|
|
9706
|
+
type: "text",
|
|
9707
|
+
text: value.text
|
|
9708
|
+
}];
|
|
9709
|
+
if (parts.length === 0) return yield* new InvalidPromptError({ reason: "A prompt needs at least one part" });
|
|
9710
|
+
for (const part of parts) {
|
|
9711
|
+
const problem = part.type === "image" ? imageProblem(part) : void 0;
|
|
9712
|
+
if (problem !== void 0) return yield* new InvalidPromptError({ reason: problem });
|
|
9713
|
+
}
|
|
9172
9714
|
return {
|
|
9173
|
-
prompt: Prompt.make({
|
|
9715
|
+
prompt: Prompt.make({ parts }),
|
|
9174
9716
|
...value.delivery === void 0 ? {} : { delivery: value.delivery },
|
|
9175
9717
|
...value.id === void 0 ? {} : { id: value.id }
|
|
9176
9718
|
};
|
|
9177
|
-
};
|
|
9719
|
+
});
|
|
9178
9720
|
const makeHandle = Effect.fn("Session.makeHandle")(function* (id) {
|
|
9179
9721
|
const sessions = yield* Service$14;
|
|
9180
9722
|
const control = yield* Service$5;
|
|
@@ -9200,14 +9742,14 @@ const makeHandle = Effect.fn("Session.makeHandle")(function* (id) {
|
|
|
9200
9742
|
id,
|
|
9201
9743
|
active: control.active.pipe(Effect.map((active) => active.has(id))),
|
|
9202
9744
|
info,
|
|
9203
|
-
prompt: (input) => control.prompt({
|
|
9745
|
+
prompt: (input) => promptInput(input).pipe(Effect.flatMap((prompt) => control.prompt({
|
|
9204
9746
|
sessionId: id,
|
|
9205
|
-
...
|
|
9206
|
-
}),
|
|
9207
|
-
run: (input) => control.run({
|
|
9747
|
+
...prompt
|
|
9748
|
+
}))),
|
|
9749
|
+
run: (input) => promptInput(input).pipe(Effect.flatMap((prompt) => control.run({
|
|
9208
9750
|
sessionId: id,
|
|
9209
|
-
...
|
|
9210
|
-
}),
|
|
9751
|
+
...prompt
|
|
9752
|
+
}))),
|
|
9211
9753
|
wait: () => control.wait(id),
|
|
9212
9754
|
resume: () => control.resume(id),
|
|
9213
9755
|
interrupt: () => control.interrupt(id, { awaitSettlement: true }),
|
|
@@ -9217,7 +9759,7 @@ const makeHandle = Effect.fn("Session.makeHandle")(function* (id) {
|
|
|
9217
9759
|
});
|
|
9218
9760
|
const create$1 = Effect.fn("Session.create")(function* (input = {}) {
|
|
9219
9761
|
const sessions = yield* Service$14;
|
|
9220
|
-
const runtime = yield* Service$
|
|
9762
|
+
const runtime = yield* Service$2;
|
|
9221
9763
|
const sandboxes = yield* Controller;
|
|
9222
9764
|
const id = ID$1.create();
|
|
9223
9765
|
const sandboxId = input.sandbox?.id ?? instance_exports.SandboxInstance.ID.local;
|
|
@@ -9226,11 +9768,11 @@ const create$1 = Effect.fn("Session.create")(function* (input = {}) {
|
|
|
9226
9768
|
id,
|
|
9227
9769
|
spaceId: location.space.id,
|
|
9228
9770
|
directory: location.directory,
|
|
9229
|
-
slug: id,
|
|
9230
9771
|
title: input.title ?? "Session",
|
|
9231
9772
|
...input.hostDir === void 0 ? {} : { hostDir: declaredHostDir(input.hostDir) }
|
|
9232
9773
|
});
|
|
9233
9774
|
yield* runtime.set(id, runtimeBindings(input));
|
|
9775
|
+
yield* choose(id, input);
|
|
9234
9776
|
return yield* makeHandle(id);
|
|
9235
9777
|
});
|
|
9236
9778
|
/**
|
|
@@ -9296,9 +9838,20 @@ const get = Effect.fn("Session.get")(function* (sessionId) {
|
|
|
9296
9838
|
const attach = Effect.fn("Session.attach")(function* (input) {
|
|
9297
9839
|
const found = yield* get(input.sessionId);
|
|
9298
9840
|
if (Option.isNone(found)) return yield* new SessionNotFoundError({ sessionId: input.sessionId });
|
|
9299
|
-
yield* (yield* Service$
|
|
9841
|
+
yield* (yield* Service$2).update(input.sessionId, runtimeBindings(input));
|
|
9842
|
+
yield* choose(input.sessionId, input);
|
|
9300
9843
|
return found.value;
|
|
9301
9844
|
});
|
|
9845
|
+
/**
|
|
9846
|
+
* The model and thinking level the session's next exchange runs with. A choice made through
|
|
9847
|
+
* {@link create} or {@link attach} is saved with the session and outlives the process; without
|
|
9848
|
+
* one, the session follows its settings.
|
|
9849
|
+
*/
|
|
9850
|
+
const configuration = Effect.fn("Session.configuration")(function* (sessionId) {
|
|
9851
|
+
const sessions = yield* Service$14;
|
|
9852
|
+
if (Option.isNone(yield* sessions.get(sessionId))) return yield* new SessionNotFoundError({ sessionId });
|
|
9853
|
+
return yield* Service$1.use((state) => state.configuration(sessionId));
|
|
9854
|
+
});
|
|
9302
9855
|
//#endregion
|
|
9303
9856
|
//#region src/plugin/prompt/schema.ts
|
|
9304
9857
|
var schema_exports$1 = /* @__PURE__ */ __exportAll({});
|
|
@@ -9310,11 +9863,12 @@ var plugin_exports = /* @__PURE__ */ __exportAll({
|
|
|
9310
9863
|
InstallError: () => InstallError,
|
|
9311
9864
|
LoadError: () => LoadError,
|
|
9312
9865
|
Prompt: () => schema_exports$1,
|
|
9866
|
+
Section: () => Section,
|
|
9313
9867
|
SetupError: () => SetupError,
|
|
9314
9868
|
SourceError: () => SourceError,
|
|
9315
9869
|
StoreError: () => StoreError,
|
|
9316
9870
|
Tool: () => schema_exports$2,
|
|
9317
|
-
anchor: () => anchor
|
|
9871
|
+
anchor: () => anchor,
|
|
9318
9872
|
canonical: () => canonical,
|
|
9319
9873
|
check: () => check,
|
|
9320
9874
|
define: () => plugin_exports$1.define,
|
|
@@ -9322,7 +9876,7 @@ var plugin_exports = /* @__PURE__ */ __exportAll({
|
|
|
9322
9876
|
identity: () => digest,
|
|
9323
9877
|
inspect: () => inspect,
|
|
9324
9878
|
localName: () => localName,
|
|
9325
|
-
parse: () => parse$
|
|
9879
|
+
parse: () => parse$1,
|
|
9326
9880
|
probe: () => probe,
|
|
9327
9881
|
resolve: () => resolve$2,
|
|
9328
9882
|
resolveCached: () => required,
|