@codeworksh/cli 0.0.1-dev.20260922184805 → 0.0.1-dev.20260923102823
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 +16 -18
- package/{add--i7mPR73.mjs → add-D1yPPRz2.mjs} +4 -4
- package/{add--i7mPR73.mjs.map → add-D1yPPRz2.mjs.map} +1 -1
- package/catalog-BtH_l7f2.mjs +17 -0
- package/catalog-BtH_l7f2.mjs.map +1 -0
- package/{check-Dq2WOgap.mjs → check-B7Uh_G-l.mjs} +4 -4
- package/{check-Dq2WOgap.mjs.map → check-B7Uh_G-l.mjs.map} +1 -1
- package/{commonjs-BDiLMfFF.mjs → commonjs-bcl-ngMU.mjs} +3 -3
- package/{commonjs-BDiLMfFF.mjs.map → commonjs-bcl-ngMU.mjs.map} +1 -1
- package/{definitions-Dfwyyv_K.mjs → definitions-XbglYT7Q.mjs} +3 -3
- package/{definitions-Dfwyyv_K.mjs.map → definitions-XbglYT7Q.mjs.map} +1 -1
- package/{entries-Gfsq7U4P.mjs → entries-B4tUkrxs.mjs} +2 -2
- package/{entries-Gfsq7U4P.mjs.map → entries-B4tUkrxs.mjs.map} +1 -1
- package/{error-BrbT9_4g.mjs → error-DG5INUkQ.mjs} +2185 -2090
- package/error-DG5INUkQ.mjs.map +1 -0
- package/{harness-Bgvrr0ZB.mjs → harness-LSywfXzo.mjs} +2 -2
- package/{harness-Bgvrr0ZB.mjs.map → harness-LSywfXzo.mjs.map} +1 -1
- package/index.d.mts +1 -5
- package/index.mjs +18 -18
- package/index.mjs.map +1 -1
- package/{install-DoO4D3fk.mjs → install-zXEeTQ8w.mjs} +4 -4
- package/{install-DoO4D3fk.mjs.map → install-zXEeTQ8w.mjs.map} +1 -1
- package/{lib-CyynPpjH.mjs → lib-BCUUrctP.mjs} +5 -5
- package/{lib-CyynPpjH.mjs.map → lib-BCUUrctP.mjs.map} +1 -1
- package/{lib-Dw7R2suc.mjs → lib-CNtGmPS7.mjs} +5 -5
- package/{lib-Dw7R2suc.mjs.map → lib-CNtGmPS7.mjs.map} +1 -1
- package/{lib-JQygPqgO.mjs → lib-CZnS894S.mjs} +3 -3
- package/{lib-JQygPqgO.mjs.map → lib-CZnS894S.mjs.map} +1 -1
- package/{lib-BkWR53Bz.mjs → lib-DHoZNIet.mjs} +6 -6
- package/{lib-BkWR53Bz.mjs.map → lib-DHoZNIet.mjs.map} +1 -1
- package/{link-B1zfETsS.mjs → link-DquUj9D6.mjs} +3 -3
- package/{link-B1zfETsS.mjs.map → link-DquUj9D6.mjs.map} +1 -1
- package/{list-CfPfmfe0.mjs → list-C1ZWDpwI.mjs} +6 -5
- package/list-C1ZWDpwI.mjs.map +1 -0
- package/{list-3OR68HpY.mjs → list-eorMHI-g.mjs} +4 -4
- package/{list-3OR68HpY.mjs.map → list-eorMHI-g.mjs.map} +1 -1
- package/{login-GFV3Z9f2.mjs → login-DEjauWkd.mjs} +4 -4
- package/{login-GFV3Z9f2.mjs.map → login-DEjauWkd.mjs.map} +1 -1
- package/{logout-D17TQUfo.mjs → logout-BUY-ESj_.mjs} +4 -4
- package/{logout-D17TQUfo.mjs.map → logout-BUY-ESj_.mjs.map} +1 -1
- package/{p-map-C2WSUhyP.mjs → p-map-CMI1i54p.mjs} +2 -2
- package/{p-map-C2WSUhyP.mjs.map → p-map-CMI1i54p.mjs.map} +1 -1
- package/package.json +3 -3
- package/{providers-DLXs9aWZ.mjs → providers-wTGawU9e.mjs} +6 -5
- package/providers-wTGawU9e.mjs.map +1 -0
- package/refresh-B2jxJmk1.mjs +16 -0
- package/refresh-B2jxJmk1.mjs.map +1 -0
- package/{refresh-BTNuP1zL.mjs → refresh-C7e_qZit.mjs} +4 -4
- package/{refresh-BTNuP1zL.mjs.map → refresh-C7e_qZit.mjs.map} +1 -1
- package/{reload-BzwsCBQq.mjs → reload-B5LO2rk9.mjs} +3 -3
- package/{reload-BzwsCBQq.mjs.map → reload-B5LO2rk9.mjs.map} +1 -1
- package/{remove-DgB3l5JL.mjs → remove-CTVgJKXM.mjs} +4 -4
- package/{remove-DgB3l5JL.mjs.map → remove-CTVgJKXM.mjs.map} +1 -1
- package/{run-BVo4XsL6.mjs → run-CORFA6op.mjs} +4 -4
- package/{run-BVo4XsL6.mjs.map → run-CORFA6op.mjs.map} +1 -1
- package/{runtime-Dl621FOp.mjs → runtime-Bp4kILYn.mjs} +12 -19
- package/{runtime-Dl621FOp.mjs.map → runtime-Bp4kILYn.mjs.map} +1 -1
- package/{serve-Dpep18-A.mjs → serve-DzaOcgKC.mjs} +8 -5
- package/{serve-Dpep18-A.mjs.map → serve-DzaOcgKC.mjs.map} +1 -1
- package/{settings-C52IrA72.mjs → settings-tMD7Y0ev.mjs} +2 -2
- package/{settings-C52IrA72.mjs.map → settings-tMD7Y0ev.mjs.map} +1 -1
- package/{shared-Uwjcofff.mjs → shared-D8hbPMPF.mjs} +3 -3
- package/{shared-Uwjcofff.mjs.map → shared-D8hbPMPF.mjs.map} +1 -1
- package/{status-DPYq2Sx4.mjs → status-CrAKnTHQ.mjs} +4 -4
- package/{status-DPYq2Sx4.mjs.map → status-CrAKnTHQ.mjs.map} +1 -1
- package/{type-defs-BZxrtlVD.mjs → type-defs-JBxRyl-4.mjs} +3 -3
- package/{type-defs-BZxrtlVD.mjs.map → type-defs-JBxRyl-4.mjs.map} +1 -1
- package/{update-CJrBbHwq.mjs → update-DK7kAVc3.mjs} +4 -4
- package/{update-CJrBbHwq.mjs.map → update-DK7kAVc3.mjs.map} +1 -1
- package/{valid-BWgW_TUl.mjs → valid-Bc7tec52.mjs} +2 -2
- package/{valid-BWgW_TUl.mjs.map → valid-Bc7tec52.mjs.map} +1 -1
- package/catalog-DJxy_lne.mjs +0 -31
- package/catalog-DJxy_lne.mjs.map +0 -1
- package/error-BrbT9_4g.mjs.map +0 -1
- package/generate-DrGYpD3l.mjs +0 -35
- package/generate-DrGYpD3l.mjs.map +0 -1
- package/list-CfPfmfe0.mjs.map +0 -1
- package/providers-DLXs9aWZ.mjs.map +0 -1
|
@@ -20,6 +20,7 @@ import { Buffer as Buffer$1 } from "node:buffer";
|
|
|
20
20
|
import { Bash } from "just-bash";
|
|
21
21
|
import { SandboxDriver as SandboxDriver$3, SandboxProvider } from "@codeworksh/plugin/sandbox";
|
|
22
22
|
import { DirectoryNotFoundError, DirectoryNotFoundError as DirectoryNotFoundError$1, Info, NotDirectoryError, NotDirectoryError as NotDirectoryError$1, Service, Service as Service$8 } from "@codeworksh/plugin/location";
|
|
23
|
+
import { generateModels } from "@codeworksh/aikit/modelgen";
|
|
23
24
|
import { Script, constants } from "node:vm";
|
|
24
25
|
import * as AikitFailure from "@codeworksh/aikit/failure";
|
|
25
26
|
import { JsonGitHubCopilotAuthStorage, getGitHubCopilotApiKey } from "@codeworksh/aikit/oauth/github/copilot";
|
|
@@ -357,7 +358,7 @@ const setup = Effect.gen(function* () {
|
|
|
357
358
|
yield* sql`PRAGMA foreign_keys = ON`;
|
|
358
359
|
yield* SqliteMigrator.run({ loader: Migrator.fromRecord(migrations) });
|
|
359
360
|
});
|
|
360
|
-
function layer$
|
|
361
|
+
function layer$27(location) {
|
|
361
362
|
return Layer.unwrap(Effect.gen(function* () {
|
|
362
363
|
if (location !== ":memory:") yield* fileSystem.makeDirectory(posix.dirname(location), { recursive: true }).pipe(Effect.orDie);
|
|
363
364
|
return SqliteClient.layer({
|
|
@@ -376,7 +377,7 @@ const path$1 = Effect.fn("Database.path")(function* (data) {
|
|
|
376
377
|
const directory = data ?? (yield* resolve$3()).data;
|
|
377
378
|
return resolveDatabaseLocation(yield* locationConfig, directory);
|
|
378
379
|
});
|
|
379
|
-
const defaultLayer$1 = Layer.unwrap(path$1().pipe(Effect.map(layer$
|
|
380
|
+
const defaultLayer$1 = Layer.unwrap(path$1().pipe(Effect.map(layer$27)));
|
|
380
381
|
//#endregion
|
|
381
382
|
//#region ../harness/src/event/schema.ts
|
|
382
383
|
var schema_exports$6 = /* @__PURE__ */ __exportAll({});
|
|
@@ -555,7 +556,7 @@ var SessionEntryPartRow = class extends Model$1.Class("SessionEntryPartRow")({
|
|
|
555
556
|
/**
|
|
556
557
|
* The public description of a failed execution.
|
|
557
558
|
*
|
|
558
|
-
* Open by design: `type` is a
|
|
559
|
+
* Open by design: `type` is a kebab-case category ending in `-error`, not a closed union.
|
|
559
560
|
* A client older than the server degrades to the message instead of failing to
|
|
560
561
|
* decode, and a new category is an additive change. The typed cause stays
|
|
561
562
|
* server-side -- this is what crosses the wire.
|
|
@@ -568,40 +569,40 @@ const Error$1 = Schema.Struct({
|
|
|
568
569
|
});
|
|
569
570
|
const providerCategory$1 = (tag) => {
|
|
570
571
|
switch (tag) {
|
|
571
|
-
case "Runner.ProviderAuthenticationError": return "provider
|
|
572
|
-
case "Runner.ProviderAuthorizationError": return "provider
|
|
573
|
-
case "Runner.ProviderConfigurationError": return "provider
|
|
574
|
-
case "Runner.ProviderModelUnavailableError": return "provider
|
|
575
|
-
case "Runner.ProviderRateLimitError": return "provider
|
|
576
|
-
case "Runner.ProviderQuotaError": return "provider
|
|
577
|
-
case "Runner.ProviderInvalidRequestError": return "provider
|
|
578
|
-
case "Runner.ProviderContentPolicyError": return "provider
|
|
579
|
-
case "Runner.ProviderTimeoutError": return "provider
|
|
580
|
-
case "Runner.ProviderTransportError": return "provider
|
|
581
|
-
case "Runner.ProviderUnavailableError": return "provider
|
|
582
|
-
case "Runner.ProviderInvalidResponseError": return "provider
|
|
583
|
-
default: return "provider
|
|
572
|
+
case "Runner.ProviderAuthenticationError": return "provider-authentication-error";
|
|
573
|
+
case "Runner.ProviderAuthorizationError": return "provider-authorization-error";
|
|
574
|
+
case "Runner.ProviderConfigurationError": return "provider-configuration-error";
|
|
575
|
+
case "Runner.ProviderModelUnavailableError": return "provider-model-unavailable-error";
|
|
576
|
+
case "Runner.ProviderRateLimitError": return "provider-rate-limit-error";
|
|
577
|
+
case "Runner.ProviderQuotaError": return "provider-quota-error";
|
|
578
|
+
case "Runner.ProviderInvalidRequestError": return "provider-invalid-request-error";
|
|
579
|
+
case "Runner.ProviderContentPolicyError": return "provider-content-filter-error";
|
|
580
|
+
case "Runner.ProviderTimeoutError": return "provider-timeout-error";
|
|
581
|
+
case "Runner.ProviderTransportError": return "provider-transport-error";
|
|
582
|
+
case "Runner.ProviderUnavailableError": return "provider-unavailable-error";
|
|
583
|
+
case "Runner.ProviderInvalidResponseError": return "provider-invalid-response-error";
|
|
584
|
+
default: return "provider-unknown-error";
|
|
584
585
|
}
|
|
585
586
|
};
|
|
586
587
|
const category = (tag) => {
|
|
587
588
|
switch (tag) {
|
|
588
|
-
case "Runner.ModelCatalogError": return "model
|
|
589
|
-
case "Runner.ModelNotFoundError": return "model
|
|
590
|
-
case "Runner.LLMStreamError": return "llm
|
|
591
|
-
case "ContextDecodeError": return "context
|
|
592
|
-
case "ContextEncodeError": return "context
|
|
593
|
-
case "SessionNotFoundError": return "session
|
|
594
|
-
case "SessionLinkedSpaceNotFoundError": return "session
|
|
595
|
-
case "SettingsError": return "settings";
|
|
596
|
-
case "State.SnapshotError": return "state
|
|
597
|
-
case "Runner.SandboxDirectoryNotFoundError": return "sandbox
|
|
598
|
-
case "SandboxFileSystemError": return "sandbox
|
|
589
|
+
case "Runner.ModelCatalogError": return "model-catalog-error";
|
|
590
|
+
case "Runner.ModelNotFoundError": return "model-not-found-error";
|
|
591
|
+
case "Runner.LLMStreamError": return "llm-stream-error";
|
|
592
|
+
case "ContextDecodeError": return "context-decode-error";
|
|
593
|
+
case "ContextEncodeError": return "context-encode-error";
|
|
594
|
+
case "SessionNotFoundError": return "session-not-found-error";
|
|
595
|
+
case "SessionLinkedSpaceNotFoundError": return "session-space-not-found-error";
|
|
596
|
+
case "SettingsError": return "settings-error";
|
|
597
|
+
case "State.SnapshotError": return "state-snapshot-error";
|
|
598
|
+
case "Runner.SandboxDirectoryNotFoundError": return "sandbox-directory-not-found-error";
|
|
599
|
+
case "SandboxFileSystemError": return "sandbox-filesystem-error";
|
|
599
600
|
case "SandboxNotFoundError":
|
|
600
601
|
case "SandboxDriverNotRegisteredError":
|
|
601
602
|
case "SandboxUnavailError":
|
|
602
603
|
case "SandboxRemovedError":
|
|
603
|
-
case "SandboxProviderError": return "sandbox
|
|
604
|
-
default: return "unknown";
|
|
604
|
+
case "SandboxProviderError": return "sandbox-mount-error";
|
|
605
|
+
default: return "unknown-error";
|
|
605
606
|
}
|
|
606
607
|
};
|
|
607
608
|
const message = (cause) => cause instanceof globalThis.Error ? cause.message : typeof cause === "string" ? cause : "Session execution failed";
|
|
@@ -609,7 +610,7 @@ const message = (cause) => cause instanceof globalThis.Error ? cause.message : t
|
|
|
609
610
|
const fromCause = (cause) => {
|
|
610
611
|
const tag = Predicate.hasProperty(cause, "_tag") && typeof cause._tag === "string" ? cause._tag : void 0;
|
|
611
612
|
if (tag === void 0) return {
|
|
612
|
-
type: "unknown",
|
|
613
|
+
type: "unknown-error",
|
|
613
614
|
message: message(cause)
|
|
614
615
|
};
|
|
615
616
|
if (tag === "Runner.ProviderError") {
|
|
@@ -872,7 +873,7 @@ var InvalidDurableEventError = class extends Schema.TaggedError()("InvalidDurabl
|
|
|
872
873
|
}) {};
|
|
873
874
|
var SubscriptionOverflowError = class extends Schema.TaggedError()("SubscriptionOverflowError", { capacity: Schema.Int }) {};
|
|
874
875
|
var Service$16 = class extends Context.Service()("@codeworksh/harness/event/event/Service") {};
|
|
875
|
-
const layer$
|
|
876
|
+
const layer$26 = Layer.effect(Service$16, Effect.gen(function* () {
|
|
876
877
|
const sql = yield* SqlClient.SqlClient;
|
|
877
878
|
const pubsub = { durable: /* @__PURE__ */ new Map() };
|
|
878
879
|
const subscriptions = /* @__PURE__ */ new Set();
|
|
@@ -1235,7 +1236,7 @@ const archiveEnv = Effect.fn("Space.archiveEnv")(function* (env) {
|
|
|
1235
1236
|
`;
|
|
1236
1237
|
})).pipe(Effect.orDie);
|
|
1237
1238
|
});
|
|
1238
|
-
const layer$
|
|
1239
|
+
const layer$25 = Layer.effect(Service$15, Effect.gen(function* () {
|
|
1239
1240
|
const sql = yield* SqlClient.SqlClient;
|
|
1240
1241
|
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
1241
1242
|
const { id: env } = yield* io_exports.SandboxIO.Current;
|
|
@@ -1403,7 +1404,7 @@ const decodeMessageEnvelopeIdentity = Schema.decodeUnknownEffect(MessageEnvelope
|
|
|
1403
1404
|
const decodeCompactionData$1 = Schema.decodeUnknownEffect(CompactionData);
|
|
1404
1405
|
const decodeJsonObject$1 = Schema.decodeUnknownEffect(JsonObject);
|
|
1405
1406
|
const encodeJsonObject$1 = Schema.encodeEffect(JsonObject);
|
|
1406
|
-
const layer$
|
|
1407
|
+
const layer$24 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
1407
1408
|
const sql = yield* SqlClient.SqlClient;
|
|
1408
1409
|
const events = yield* Service$16;
|
|
1409
1410
|
const findSession = SqlSchema.findOneOption({
|
|
@@ -2065,7 +2066,7 @@ const layer$23 = Layer.effect(Service$14, Effect.gen(function* () {
|
|
|
2065
2066
|
setLabel
|
|
2066
2067
|
});
|
|
2067
2068
|
}));
|
|
2068
|
-
layer$
|
|
2069
|
+
layer$24.pipe(Layer.provide(defaultLayer$1));
|
|
2069
2070
|
//#endregion
|
|
2070
2071
|
//#region ../harness/src/context/errors.ts
|
|
2071
2072
|
var ContextDecodeError = class extends Schema.TaggedError()("ContextDecodeError", {
|
|
@@ -2366,7 +2367,7 @@ const assemblePath = Effect.fn("Context.assemblePath")(function* (sessionId, pat
|
|
|
2366
2367
|
//#endregion
|
|
2367
2368
|
//#region ../harness/src/context/context.ts
|
|
2368
2369
|
var Service$13 = class extends Context.Service()("@codeworksh/harness/context/context/Service") {};
|
|
2369
|
-
const layer$
|
|
2370
|
+
const layer$23 = Layer.effect(Service$13, Effect.gen(function* () {
|
|
2370
2371
|
const sessions = yield* Service$14;
|
|
2371
2372
|
const assemble = Effect.fn("Context.assemble")(function* (sessionId) {
|
|
2372
2373
|
const found = yield* sessions.get(sessionId);
|
|
@@ -2466,7 +2467,7 @@ const make$22 = (vfs) => {
|
|
|
2466
2467
|
};
|
|
2467
2468
|
};
|
|
2468
2469
|
/** The `SandboxFileSystem.Service` backed by the local {@link Vfs}. */
|
|
2469
|
-
const layer$
|
|
2470
|
+
const layer$22 = Layer.effect(filesystem_exports.SandboxFileSystem.Service, Effect.gen(function* () {
|
|
2470
2471
|
return filesystem_exports.SandboxFileSystem.fromProvider(make$22(yield* Vfs));
|
|
2471
2472
|
}));
|
|
2472
2473
|
//#endregion
|
|
@@ -2493,7 +2494,7 @@ var HostProvider = class extends RealFSProvider {
|
|
|
2493
2494
|
* reason to keep relative paths above the mount, not below it — nothing here
|
|
2494
2495
|
* resolves them, and nothing here should.
|
|
2495
2496
|
*/
|
|
2496
|
-
const layer$
|
|
2497
|
+
const layer$21 = () => {
|
|
2497
2498
|
const vfs = create(new HostProvider("/"), {
|
|
2498
2499
|
moduleHooks: false,
|
|
2499
2500
|
virtualCwd: true
|
|
@@ -2586,7 +2587,7 @@ const make$21 = (drivers) => Effect.gen(function* () {
|
|
|
2586
2587
|
encodeRuntimeConfig
|
|
2587
2588
|
});
|
|
2588
2589
|
});
|
|
2589
|
-
const layer$
|
|
2590
|
+
const layer$20 = (...drivers) => Layer.effect(Registry, make$21(drivers));
|
|
2590
2591
|
//#endregion
|
|
2591
2592
|
//#region ../harness/src/sandbox/shell/shell.ts
|
|
2592
2593
|
var shell_exports = /* @__PURE__ */ __exportAll({});
|
|
@@ -2595,7 +2596,7 @@ __reExport(shell_exports, import__codeworksh_plugin_sandbox_shell);
|
|
|
2595
2596
|
//#endregion
|
|
2596
2597
|
//#region ../harness/src/sandbox/shell/host.ts
|
|
2597
2598
|
const DEFAULT_SHELL = "sh";
|
|
2598
|
-
const layer$
|
|
2599
|
+
const layer$19 = (options = {}) => Layer.effect(shell_exports.Shell, Effect.gen(function* () {
|
|
2599
2600
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
2600
2601
|
const run = (label, file, args, opts) => Effect.scoped(Effect.gen(function* () {
|
|
2601
2602
|
const handle = yield* spawner.spawn(ChildProcess.make(file, [...args], {
|
|
@@ -2686,7 +2687,7 @@ const make$20 = Effect.gen(function* () {
|
|
|
2686
2687
|
//#endregion
|
|
2687
2688
|
//#region ../harness/src/sandbox/control.ts
|
|
2688
2689
|
var Controller = class extends Context.Service()("@codeworksh/harness/sandbox/control/Controller") {};
|
|
2689
|
-
const hostTransport = Layer.provide(Layer.merge(Layer.fresh(layer$
|
|
2690
|
+
const hostTransport = Layer.provide(Layer.merge(Layer.fresh(layer$22), layer$19()), layer$21());
|
|
2690
2691
|
const asDate = DateTime.toDateUtc;
|
|
2691
2692
|
const optionalDate = Option.map(DateTime.toDateUtc);
|
|
2692
2693
|
const MetadataJson = Schema.fromJsonString(Schema.Record(Schema.String, Schema.String));
|
|
@@ -3306,7 +3307,7 @@ const make$19 = Effect.fn("Sandbox.Controller.make")(function* (options) {
|
|
|
3306
3307
|
destroy
|
|
3307
3308
|
});
|
|
3308
3309
|
});
|
|
3309
|
-
const layer$
|
|
3310
|
+
const layer$18 = (options) => Layer.effect(Controller, make$19(options));
|
|
3310
3311
|
//#endregion
|
|
3311
3312
|
//#region ../harness/src/sandbox/utils/seed.ts
|
|
3312
3313
|
const copyDirectory = async (vfs, sourceDirectory, targetDirectory) => {
|
|
@@ -3562,7 +3563,7 @@ const transport$3 = (inner) => Layer.provideMerge(Layer.effect(shell_exports.She
|
|
|
3562
3563
|
})), inner);
|
|
3563
3564
|
//#endregion
|
|
3564
3565
|
//#region ../harness/src/sandbox/virtual.ts
|
|
3565
|
-
const transportLayer = (primitives) => Layer.provideMerge(Layer.merge(Layer.fresh(layer$
|
|
3566
|
+
const transportLayer = (primitives) => Layer.provideMerge(Layer.merge(Layer.fresh(layer$22), transport$3(primitives)), primitives);
|
|
3566
3567
|
/** A cwd-neutral FileSystem + just-bash transport over one retained VFS. */
|
|
3567
3568
|
const transport$2 = (vfs) => {
|
|
3568
3569
|
const primitives = Layer.merge(Layer.succeed(Vfs, vfs), unsupported);
|
|
@@ -3669,7 +3670,7 @@ const make$15 = (location, options) => Effect.tryPromise({
|
|
|
3669
3670
|
})
|
|
3670
3671
|
});
|
|
3671
3672
|
const vfsLayer = (location, options) => Layer.effect(Vfs, Effect.acquireRelease(make$15(location, options), ({ provider }) => Effect.sync(() => provider.close())).pipe(Effect.map(({ vfs }) => vfs)));
|
|
3672
|
-
const layer$
|
|
3673
|
+
const layer$17 = (opts) => Layer.merge(vfsLayer(opts?.location, opts?.options), opts?.options?.hostProcess ? host : unsupported);
|
|
3673
3674
|
//#endregion
|
|
3674
3675
|
//#region ../harness/src/sandbox/drivers/sqldb.ts
|
|
3675
3676
|
const CreateConfig$2 = Schema.Struct({
|
|
@@ -3687,7 +3688,7 @@ const make$14 = () => {
|
|
|
3687
3688
|
const memory = /* @__PURE__ */ new Map();
|
|
3688
3689
|
const runtimeLocation = (input) => input.runtimeConfig.location ?? Option.getOrUndefined(input.providerResourceId);
|
|
3689
3690
|
const persistentTransport = (location) => {
|
|
3690
|
-
const backend = layer$
|
|
3691
|
+
const backend = layer$17({ location });
|
|
3691
3692
|
return transportLayer(Layer.effectContext(Layer.build(backend).pipe(Effect.mapError((cause) => (0, errors_exports.providerError)({
|
|
3692
3693
|
driver: name,
|
|
3693
3694
|
operation: "attach",
|
|
@@ -4473,7 +4474,7 @@ var AppProcessError = class extends Schema.TaggedError()("AppProcessError", {
|
|
|
4473
4474
|
cause: Schema.optional(Schema.Defect())
|
|
4474
4475
|
}) {};
|
|
4475
4476
|
var Service$12 = class extends Context.Service()("@codeworksh/harness/git/git/Service") {};
|
|
4476
|
-
const layer$
|
|
4477
|
+
const layer$16 = Layer.effect(Service$12, Effect.gen(function* () {
|
|
4477
4478
|
const shell = yield* io_exports.SandboxIO.Shell;
|
|
4478
4479
|
const execute = (cwd) => (args, options) => shell.execArgv(["git", ...args], {
|
|
4479
4480
|
cwd,
|
|
@@ -4697,7 +4698,7 @@ function parts(host, name) {
|
|
|
4697
4698
|
if (!host || !pathname) return void 0;
|
|
4698
4699
|
return `${host}/${pathname}`.toLowerCase();
|
|
4699
4700
|
}
|
|
4700
|
-
const layer$
|
|
4701
|
+
const layer$15 = Layer.effect(Service$11, Effect.gen(function* () {
|
|
4701
4702
|
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
4702
4703
|
const git = yield* Service$12;
|
|
4703
4704
|
const find = Effect.fn("Repo.find")(function* (input) {
|
|
@@ -4754,7 +4755,7 @@ var WorktreeError = class extends Schema.TaggedError()("Worktree.WorktreeError",
|
|
|
4754
4755
|
cause: Schema.optional(Schema.Defect())
|
|
4755
4756
|
}) {};
|
|
4756
4757
|
var Service$10 = class extends Context.Service()("@codeworksh/harness/worktree/worktree/Service") {};
|
|
4757
|
-
const layer$
|
|
4758
|
+
const layer$14 = Layer.effect(Service$10, Effect.gen(function* () {
|
|
4758
4759
|
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
4759
4760
|
const git = yield* Service$12;
|
|
4760
4761
|
const worktree = Effect.fnUntraced(function* (operation, cwd, args, directory) {
|
|
@@ -4809,7 +4810,7 @@ const provisional = (env, location) => schema_exports$5.ProjectSchema.ID.make(fa
|
|
|
4809
4810
|
location
|
|
4810
4811
|
])));
|
|
4811
4812
|
var Service$9 = class extends Context.Service()("@codeworksh/harness/project/project/Service") {};
|
|
4812
|
-
const layer$
|
|
4813
|
+
const layer$13 = Layer.effect(Service$9, Effect.gen(function* () {
|
|
4813
4814
|
const sql = yield* SqlClient.SqlClient;
|
|
4814
4815
|
const fs = yield* io_exports.SandboxIO.FileSystem;
|
|
4815
4816
|
const repos = yield* Service$11;
|
|
@@ -4943,7 +4944,7 @@ const layer$12 = Layer.effect(Service$9, Effect.gen(function* () {
|
|
|
4943
4944
|
}));
|
|
4944
4945
|
//#endregion
|
|
4945
4946
|
//#region ../harness/src/location/location.ts
|
|
4946
|
-
const layer$
|
|
4947
|
+
const layer$12 = (ref = {}) => Layer.effect(Service, Effect.gen(function* () {
|
|
4947
4948
|
const instance = yield* io_exports.SandboxIO.Current;
|
|
4948
4949
|
const directory = schema_exports$8.AbsolutePath.make(ref.directory ?? instance.cwd);
|
|
4949
4950
|
const resolved = yield* (yield* Service$9).resolveOrCreate(directory);
|
|
@@ -4958,7 +4959,7 @@ const layer$11 = (ref = {}) => Layer.effect(Service, Effect.gen(function* () {
|
|
|
4958
4959
|
* folded in. What remains is exactly the mount and the database — for callers
|
|
4959
4960
|
* that already hold both, such as the runner and `Session.create`.
|
|
4960
4961
|
*/
|
|
4961
|
-
const layerMounted = (ref = {}) => layer$
|
|
4962
|
+
const layerMounted = (ref = {}) => layer$12(ref).pipe(Layer.provide(layer$13.pipe(Layer.provide(Layer.mergeAll(layer$15, layer$14, layer$25)), Layer.provide(layer$16))));
|
|
4962
4963
|
//#endregion
|
|
4963
4964
|
//#region ../harness/src/runner/run.ts
|
|
4964
4965
|
var SandboxDirectoryNotFoundError = class extends Schema.TaggedError()("Runner.SandboxDirectoryNotFoundError", {
|
|
@@ -5292,7 +5293,7 @@ var PromptConflictError = class extends Schema.TaggedError()("PromptConflictErro
|
|
|
5292
5293
|
messageId: Schema.String
|
|
5293
5294
|
}) {};
|
|
5294
5295
|
var Service$5 = class extends Context.Service()("@codeworksh/harness/control/Service") {};
|
|
5295
|
-
const layer$
|
|
5296
|
+
const layer$11 = Layer.effect(Service$5, Effect.gen(function* () {
|
|
5296
5297
|
const sessions = yield* Service$14;
|
|
5297
5298
|
const inputs = yield* make$9;
|
|
5298
5299
|
const execution = yield* Service$6;
|
|
@@ -5400,7 +5401,7 @@ const flatten = Effect.fn("EventRegistry.flatten")(function* (plugins) {
|
|
|
5400
5401
|
}
|
|
5401
5402
|
return [...Definitions, ...definitions];
|
|
5402
5403
|
});
|
|
5403
|
-
const layer$
|
|
5404
|
+
const layer$10 = (definitions = Definitions) => Layer.sync(Service$4, () => {
|
|
5404
5405
|
let current = definitions;
|
|
5405
5406
|
let latest = schema_exports$6.EventSchema.latest(current);
|
|
5406
5407
|
const replace = Effect.fn("EventRegistry.replace")(function* (plugins) {
|
|
@@ -5417,2100 +5418,2211 @@ const layer$9 = (definitions = Definitions) => Layer.sync(Service$4, () => {
|
|
|
5417
5418
|
});
|
|
5418
5419
|
});
|
|
5419
5420
|
//#endregion
|
|
5420
|
-
//#region ../harness/src/
|
|
5421
|
+
//#region ../harness/src/model/catalog.ts
|
|
5422
|
+
const filename = "models.gen.json";
|
|
5423
|
+
/** How old the catalog may get before a check downloads a new one. */
|
|
5424
|
+
const ttlConfig = Config.Duration("CODEWORK_MODELS_TTL").pipe(Config.withDefault(Duration.minutes(15)));
|
|
5425
|
+
/** How often a server looks at the file. A look is a `stat`; only a stale file costs a download. */
|
|
5426
|
+
const checkEvery = Duration.minutes(1);
|
|
5427
|
+
var RefreshError = class extends Schema.TaggedError()("ModelCatalog.RefreshError", {
|
|
5428
|
+
path: Schema.String,
|
|
5429
|
+
detail: Schema.String
|
|
5430
|
+
}) {
|
|
5431
|
+
get message() {
|
|
5432
|
+
return `failed to refresh the model catalog at ${this.path}: ${this.detail}`;
|
|
5433
|
+
}
|
|
5434
|
+
};
|
|
5435
|
+
/** Point aikit at `<home>/models.gen.json`, which `CODEWORK_MODELS_FILE` overrides. */
|
|
5436
|
+
const use = (home) => {
|
|
5437
|
+
const own = hostPath.resolve(home, filename);
|
|
5438
|
+
Model.configureCatalog(own);
|
|
5439
|
+
const path = Model.catalogPath();
|
|
5440
|
+
return {
|
|
5441
|
+
path,
|
|
5442
|
+
pinned: path !== own
|
|
5443
|
+
};
|
|
5444
|
+
};
|
|
5445
|
+
const modified = (path) => fileSystem.stat(path).pipe(Effect.map((info) => Option.map(info.mtime, (date) => date.getTime())), Effect.orElseSucceed(() => Option.none()));
|
|
5446
|
+
const stale = Effect.fn("ModelCatalog.stale")(function* (path) {
|
|
5447
|
+
const mtime = yield* modified(path);
|
|
5448
|
+
if (Option.isNone(mtime)) return true;
|
|
5449
|
+
const ttl = yield* ttlConfig;
|
|
5450
|
+
return (yield* Clock.currentTimeMillis) - mtime.value >= Duration.toMillis(ttl);
|
|
5451
|
+
});
|
|
5452
|
+
const download$1 = (path) => Effect.tryPromise({
|
|
5453
|
+
try: () => generateModels({ path }),
|
|
5454
|
+
catch: (cause) => new RefreshError({
|
|
5455
|
+
path,
|
|
5456
|
+
detail: cause instanceof Error ? cause.message : String(cause)
|
|
5457
|
+
})
|
|
5458
|
+
});
|
|
5459
|
+
/** Download a new catalog for the home when it is missing, older than the TTL, or `force` asks. */
|
|
5460
|
+
const refresh$1 = Effect.fn("ModelCatalog.refresh")(function* (home, options = {}) {
|
|
5461
|
+
const { path } = use(home);
|
|
5462
|
+
if (options.force !== true && !(yield* stale(path))) return {
|
|
5463
|
+
path,
|
|
5464
|
+
refreshed: false
|
|
5465
|
+
};
|
|
5466
|
+
yield* download$1(path);
|
|
5467
|
+
return {
|
|
5468
|
+
path,
|
|
5469
|
+
refreshed: true
|
|
5470
|
+
};
|
|
5471
|
+
});
|
|
5421
5472
|
/**
|
|
5422
|
-
*
|
|
5423
|
-
*
|
|
5424
|
-
*
|
|
5473
|
+
* The automatic check: refresh a stale catalog unless it is pinned, and never fail. A failed
|
|
5474
|
+
* download leaves the old file in place; a missing one surfaces later as a
|
|
5475
|
+
* `Runner.ModelCatalogError` from whatever needed it.
|
|
5425
5476
|
*/
|
|
5426
|
-
const
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5477
|
+
const check$1 = Effect.fn("ModelCatalog.check")(function* ({ path, pinned }) {
|
|
5478
|
+
if (pinned || !(yield* stale(path))) return;
|
|
5479
|
+
yield* download$1(path);
|
|
5480
|
+
});
|
|
5481
|
+
const quietly = (effect) => effect.pipe(Effect.asVoid, Effect.catch((error) => Effect.logWarning(`model catalog: ${error.message}`)));
|
|
5482
|
+
/** Check the home's catalog once, for a command that runs and exits; returns the file aikit reads. */
|
|
5483
|
+
const sync = Effect.fn("ModelCatalog.sync")(function* (home) {
|
|
5484
|
+
const target = use(home);
|
|
5485
|
+
yield* quietly(check$1(target));
|
|
5486
|
+
return target.path;
|
|
5487
|
+
});
|
|
5488
|
+
/**
|
|
5489
|
+
* Check at boot, and with `watch`, keep checking for the life of the layer. A file whose mtime
|
|
5490
|
+
* moved -- refreshed here, by `codework models refresh`, or by hand -- is reloaded, so the
|
|
5491
|
+
* process never serves a catalog older than what is on disk.
|
|
5492
|
+
*/
|
|
5493
|
+
const layer$9 = (options) => Layer.effectDiscard(Effect.gen(function* () {
|
|
5494
|
+
const target = use(options.home);
|
|
5495
|
+
yield* quietly(check$1(target));
|
|
5496
|
+
if (!options.watch) return;
|
|
5497
|
+
const current = modified(target.path).pipe(Effect.map(Option.getOrUndefined));
|
|
5498
|
+
const seen = yield* Ref.make(yield* current);
|
|
5499
|
+
const tick = Effect.gen(function* () {
|
|
5500
|
+
yield* quietly(check$1(target));
|
|
5501
|
+
const mtime = yield* current;
|
|
5502
|
+
if (mtime !== (yield* Ref.getAndSet(seen, mtime))) Model.reloadCatalog();
|
|
5503
|
+
});
|
|
5504
|
+
yield* Effect.forkScoped(tick.pipe(Effect.repeat(Schedule.spaced(checkEvery))));
|
|
5505
|
+
}));
|
|
5506
|
+
/** Translate aikit's catalog failure, or undefined for anything else. */
|
|
5507
|
+
const loadError = (cause) => {
|
|
5508
|
+
if (typeof cause !== "object" || cause === null || !("name" in cause) || cause.name !== "ModelCatalogLoadError") return;
|
|
5509
|
+
if (!("data" in cause) || typeof cause.data !== "object" || cause.data === null) return void 0;
|
|
5510
|
+
const data = cause.data;
|
|
5511
|
+
if (!("path" in data) || typeof data.path !== "string" || !("message" in data) || typeof data.message !== "string" || !("reason" in data) || data.reason !== "missing" && data.reason !== "unreadable" && data.reason !== "empty" && data.reason !== "invalid") return;
|
|
5512
|
+
return new ModelCatalogError({
|
|
5513
|
+
path: data.path,
|
|
5514
|
+
reason: data.reason,
|
|
5515
|
+
detail: data.message
|
|
5516
|
+
});
|
|
5430
5517
|
};
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
}
|
|
5444
|
-
/** Normalize nullable properties before schema validation, without changing array positions. */
|
|
5445
|
-
const normalize$1 = copy;
|
|
5518
|
+
const catalogError = (cause) => loadError(cause) ?? new ModelCatalogError({
|
|
5519
|
+
path: Model.catalogPath(),
|
|
5520
|
+
reason: "unreadable",
|
|
5521
|
+
detail: cause instanceof Error ? cause.message : "failed to load the model catalog"
|
|
5522
|
+
});
|
|
5523
|
+
const models = Effect.tryPromise({
|
|
5524
|
+
try: () => Model.getBuiltInModels(),
|
|
5525
|
+
catch: catalogError
|
|
5526
|
+
});
|
|
5527
|
+
const providers = Effect.tryPromise({
|
|
5528
|
+
try: () => Model.getProviders(),
|
|
5529
|
+
catch: catalogError
|
|
5530
|
+
});
|
|
5446
5531
|
//#endregion
|
|
5447
|
-
//#region ../harness/src/
|
|
5448
|
-
|
|
5449
|
-
*
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5456
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
5457
|
-
* in the Software without restriction, including without limitation the rights
|
|
5458
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
5459
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
5460
|
-
* furnished to do so, subject to the following conditions:
|
|
5461
|
-
*
|
|
5462
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
5463
|
-
* copies or substantial portions of the Software.
|
|
5464
|
-
*
|
|
5465
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
5466
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
5467
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
5468
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
5469
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
5470
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
5471
|
-
* SOFTWARE.
|
|
5472
|
-
*/
|
|
5532
|
+
//#region ../harness/src/plugin/plugin.ts
|
|
5533
|
+
var plugin_exports = /* @__PURE__ */ __exportAll({});
|
|
5534
|
+
import * as import__codeworksh_plugin_plugin from "@codeworksh/plugin/plugin";
|
|
5535
|
+
__reExport(plugin_exports, import__codeworksh_plugin_plugin);
|
|
5536
|
+
//#endregion
|
|
5537
|
+
//#region ../harness/src/plugin/builtin/prompt/default.ts
|
|
5538
|
+
/** The default coding-agent foundation, used unless `promptCustom` replaces it. */
|
|
5539
|
+
const foundation = `You are an expert coding assistant operating inside codework, a coding agent harness.`;
|
|
5473
5540
|
/**
|
|
5474
|
-
*
|
|
5475
|
-
*
|
|
5476
|
-
* A plugin is installed into the store as its own npm tree, so `effect` and the plugin SDK land
|
|
5477
|
-
* there a second time. Two copies of the *same version* are still two module instances, and
|
|
5478
|
-
* Effect Schema's checks do not survive the crossing: a schema built by one copy and evaluated by
|
|
5479
|
-
* the other reports every check as failed, so a tool whose success type is `Schema.Finite` dies
|
|
5480
|
-
* on "Expected a finite number" for `22800`. Nothing about that is visible at registration --
|
|
5481
|
-
* `Schema.isSchema` passes, the JSON Schema is derived correctly, and the tool reaches the model
|
|
5482
|
-
* -- so it surfaces on the first call rather than at boot.
|
|
5541
|
+
* Guidelines that hold regardless of which tools are registered.
|
|
5483
5542
|
*
|
|
5484
|
-
*
|
|
5485
|
-
*
|
|
5486
|
-
* whichever one is running now, and two applications embedding the harness need not agree on
|
|
5487
|
-
* where their `effect` lives.
|
|
5543
|
+
* Kept short on purpose. Every line here is spent on every request, so a line
|
|
5544
|
+
* earns its place only if a model measurably behaves worse without it.
|
|
5488
5545
|
*/
|
|
5489
|
-
const
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5546
|
+
const standingGuidelines = [
|
|
5547
|
+
"Be concise. Report what you did and what you found, not what you are about to do.",
|
|
5548
|
+
"Quote exact paths and command output rather than paraphrasing them.",
|
|
5549
|
+
"If a command fails, read the error before retrying."
|
|
5550
|
+
];
|
|
5551
|
+
/** Collapse whitespace so two spellings of one guideline dedupe against each other. */
|
|
5552
|
+
const normalize$1 = (value) => value.trim().replace(/\s+/g, " ");
|
|
5496
5553
|
/**
|
|
5497
|
-
*
|
|
5498
|
-
*
|
|
5554
|
+
* Tool-contributed guidelines followed by the standing ones, normalized and
|
|
5555
|
+
* deduplicated with first occurrence winning.
|
|
5556
|
+
*
|
|
5557
|
+
* Tool order is bucket order, which is the same order the index renders in and the
|
|
5558
|
+
* same order the provider receives definitions in -- one ordering, not three.
|
|
5499
5559
|
*/
|
|
5500
|
-
|
|
5501
|
-
const
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
}
|
|
5509
|
-
/** Use Node's main loader even when the caller runs inside a bundler or test VM. */
|
|
5510
|
-
async function importModule(specifier) {
|
|
5511
|
-
const imported = await withSharedModules(() => new Script(`import(${JSON.stringify(specifier)})`, { importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER }).runInThisContext());
|
|
5512
|
-
if (typeof imported !== "object" || imported === null) return imported;
|
|
5513
|
-
const module = imported;
|
|
5514
|
-
const exports = module["module.exports"];
|
|
5515
|
-
if (exports !== module.default || typeof exports !== "object" && typeof exports !== "function" || exports === null) return imported;
|
|
5516
|
-
return Object.assign({}, module, exports);
|
|
5517
|
-
}
|
|
5518
|
-
/** Node resolution scoped synchronously to the caller. */
|
|
5519
|
-
function resolveModule(specifier, directory, conditions) {
|
|
5520
|
-
const hook = registerHooks({ resolve(target, context, nextResolve) {
|
|
5521
|
-
return nextResolve(target, {
|
|
5522
|
-
...context,
|
|
5523
|
-
parentURL: pathToFileURL(path.join(directory, "package.json")).href,
|
|
5524
|
-
...conditions === void 0 ? {} : { conditions: [.../* @__PURE__ */ new Set([...context.conditions, ...conditions])] }
|
|
5525
|
-
});
|
|
5526
|
-
} });
|
|
5527
|
-
try {
|
|
5528
|
-
const resolve = (target) => {
|
|
5529
|
-
const resolved = import.meta.resolve(path.isAbsolute(target) ? pathToFileURL(target).href : target);
|
|
5530
|
-
if (resolved.startsWith("file:")) statSync(new URL(resolved));
|
|
5531
|
-
return resolved;
|
|
5532
|
-
};
|
|
5533
|
-
try {
|
|
5534
|
-
return resolve(specifier);
|
|
5535
|
-
} catch (error) {
|
|
5536
|
-
if (path.extname(specifier) || !missing(error)) throw error;
|
|
5537
|
-
for (const extension of [
|
|
5538
|
-
".ts",
|
|
5539
|
-
".tsx",
|
|
5540
|
-
".js",
|
|
5541
|
-
".jsx",
|
|
5542
|
-
".mts",
|
|
5543
|
-
".mjs",
|
|
5544
|
-
".cts",
|
|
5545
|
-
".cjs"
|
|
5546
|
-
]) try {
|
|
5547
|
-
return resolve(specifier + extension);
|
|
5548
|
-
} catch (cause) {
|
|
5549
|
-
if (!missing(cause)) throw cause;
|
|
5550
|
-
}
|
|
5551
|
-
throw error;
|
|
5552
|
-
}
|
|
5553
|
-
} finally {
|
|
5554
|
-
hook.deregister();
|
|
5560
|
+
const guidelines = (tools) => {
|
|
5561
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5562
|
+
const collected = [];
|
|
5563
|
+
for (const line of [...tools.flatMap((tool) => tool.promptGuidelines ?? []), ...standingGuidelines]) {
|
|
5564
|
+
const normalized = normalize$1(line);
|
|
5565
|
+
if (normalized.length === 0 || seen.has(normalized)) continue;
|
|
5566
|
+
seen.add(normalized);
|
|
5567
|
+
collected.push(normalized);
|
|
5555
5568
|
}
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
return error instanceof Error && "code" in error && [
|
|
5559
|
-
"ENOENT",
|
|
5560
|
-
"ENOTDIR",
|
|
5561
|
-
"ERR_MODULE_NOT_FOUND"
|
|
5562
|
-
].includes(String(error.code));
|
|
5563
|
-
}
|
|
5564
|
-
//#endregion
|
|
5565
|
-
//#region ../harness/src/plugin/error.ts
|
|
5569
|
+
return collected;
|
|
5570
|
+
};
|
|
5566
5571
|
/**
|
|
5567
|
-
* The
|
|
5568
|
-
*
|
|
5572
|
+
* The rendered tool index.
|
|
5573
|
+
*
|
|
5574
|
+
* `(none)` rather than an omitted section: a model told it has no tools behaves
|
|
5575
|
+
* better than one left to infer it from silence.
|
|
5569
5576
|
*/
|
|
5570
|
-
const
|
|
5577
|
+
const toolIndex = (tools) => {
|
|
5578
|
+
const listed = tools.filter((tool) => tool.promptSnippet !== void 0 && tool.promptSnippet.length > 0);
|
|
5579
|
+
if (listed.length === 0) return "(none)";
|
|
5580
|
+
return listed.map((tool) => `- ${tool.name}: ${tool.promptSnippet}`).join("\n");
|
|
5581
|
+
};
|
|
5582
|
+
const assemble = (input) => {
|
|
5583
|
+
const sections = [input.promptCustom ?? foundation];
|
|
5584
|
+
sections.push(`Available tools:\n${toolIndex(input.tools)}`);
|
|
5585
|
+
const lines = guidelines(input.tools);
|
|
5586
|
+
if (lines.length > 0) sections.push(`Guidelines:\n${lines.map((line) => `- ${line}`).join("\n")}`);
|
|
5587
|
+
const append = input.promptSystemAppend?.trim();
|
|
5588
|
+
if (append !== void 0 && append.length > 0) sections.push(append);
|
|
5589
|
+
sections.push(`Current working directory: ${input.directory}`);
|
|
5590
|
+
return sections.join("\n\n");
|
|
5591
|
+
};
|
|
5571
5592
|
/**
|
|
5572
|
-
*
|
|
5573
|
-
*
|
|
5574
|
-
* Absent for an embedder's own list and for anything the CLI was handed directly -- in both cases
|
|
5575
|
-
* the caller already knows where the string came from. It is the settings path that needs saying,
|
|
5576
|
-
* because several layers accumulate entries and "which file says this" is the question.
|
|
5593
|
+
* A caller slot, awaited lazily. A throw or rejection fails the snapshot, which the
|
|
5594
|
+
* host attributes to this plugin's id.
|
|
5577
5595
|
*/
|
|
5578
|
-
const
|
|
5596
|
+
const slot = (ctx, resolver) => Effect.tryPromise(() => Promise.resolve(resolver?.(ctx)));
|
|
5597
|
+
const defaultPromptPlugin = (0, plugin_exports.define)({
|
|
5598
|
+
id: "codework.prompt.default",
|
|
5599
|
+
kind: "prompt",
|
|
5600
|
+
setup: Effect.fn("DefaultPromptPlugin.setup")(function* (ctx) {
|
|
5601
|
+
const custom = yield* slot(ctx, ctx.config.promptCustom);
|
|
5602
|
+
const append = yield* slot(ctx, ctx.config.promptSystemAppend);
|
|
5603
|
+
ctx.plugin.prompt.set(assemble({
|
|
5604
|
+
tools: ctx.plugin.tools.list(),
|
|
5605
|
+
directory: ctx.location.directory,
|
|
5606
|
+
...custom === void 0 ? {} : { promptCustom: custom },
|
|
5607
|
+
...append === void 0 ? {} : { promptSystemAppend: append }
|
|
5608
|
+
}));
|
|
5609
|
+
})
|
|
5610
|
+
});
|
|
5611
|
+
//#endregion
|
|
5612
|
+
//#region ../harness/src/tool/truncate.ts
|
|
5579
5613
|
/**
|
|
5580
|
-
*
|
|
5614
|
+
* Shared truncation utilities for tool outputs.
|
|
5581
5615
|
*
|
|
5582
|
-
*
|
|
5583
|
-
*
|
|
5584
|
-
*
|
|
5585
|
-
*/
|
|
5586
|
-
var SourceError = class extends Schema.TaggedError()("PluginSourceError", {
|
|
5587
|
-
reason: Schema.Literals([
|
|
5588
|
-
"plugin-unsupported-source",
|
|
5589
|
-
"plugin-not-found",
|
|
5590
|
-
"plugin-escapes-root"
|
|
5591
|
-
]),
|
|
5592
|
-
reference: Reference,
|
|
5593
|
-
file: File$1,
|
|
5594
|
-
message: Schema.String
|
|
5595
|
-
}) {};
|
|
5596
|
-
/** A spec that is installable, and whose install did not produce a usable package. */
|
|
5597
|
-
var InstallError = class extends Schema.TaggedError()("PluginInstallError", {
|
|
5598
|
-
reason: Schema.Literals([
|
|
5599
|
-
"plugin-resolve-failed",
|
|
5600
|
-
"plugin-fetch-failed",
|
|
5601
|
-
"plugin-no-commit",
|
|
5602
|
-
"plugin-no-entrypoint"
|
|
5603
|
-
]),
|
|
5604
|
-
reference: Reference,
|
|
5605
|
-
file: File$1,
|
|
5606
|
-
message: Schema.String,
|
|
5607
|
-
cause: Schema.optional(Schema.Defect())
|
|
5608
|
-
}) {};
|
|
5609
|
-
/**
|
|
5610
|
-
* A module that reached the import and did not come back as a plugin.
|
|
5616
|
+
* Truncation is based on two independent limits — whichever is hit first
|
|
5617
|
+
* wins: a line limit and a byte limit. Lines are never split (except the bash
|
|
5618
|
+
* tail edge case where a single line exceeds the byte limit).
|
|
5611
5619
|
*
|
|
5612
|
-
*
|
|
5613
|
-
*
|
|
5614
|
-
* because one is fixed by installing and the other by editing code.
|
|
5620
|
+
* The bash tool uses {@link truncateTail} (keep the end — errors / final
|
|
5621
|
+
* results); file readers use {@link truncateHead} (keep the beginning).
|
|
5615
5622
|
*/
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
}
|
|
5630
|
-
/** The store's own failures: what is filed, what is not, and what could not be claimed. */
|
|
5631
|
-
var StoreError = class extends Schema.TaggedError()("PluginStoreError", {
|
|
5632
|
-
reason: Schema.Literals([
|
|
5633
|
-
"plugin-not-installed",
|
|
5634
|
-
"plugin-lock-timeout",
|
|
5635
|
-
"plugin-marker-invalid",
|
|
5636
|
-
"plugin-index-invalid",
|
|
5637
|
-
"plugin-collect-failed"
|
|
5638
|
-
]),
|
|
5639
|
-
reference: Reference,
|
|
5640
|
-
file: File$1,
|
|
5641
|
-
message: Schema.String,
|
|
5642
|
-
cause: Schema.optional(Schema.Defect())
|
|
5643
|
-
}) {};
|
|
5623
|
+
const DEFAULT_MAX_LINES = 2e3;
|
|
5624
|
+
const DEFAULT_MAX_BYTES = 51200;
|
|
5625
|
+
function splitLinesForCounting(content) {
|
|
5626
|
+
if (content.length === 0) return [];
|
|
5627
|
+
const lines = content.split("\n");
|
|
5628
|
+
if (content.endsWith("\n")) lines.pop();
|
|
5629
|
+
return lines;
|
|
5630
|
+
}
|
|
5631
|
+
/** Format bytes as a human-readable size. */
|
|
5632
|
+
function formatSize(bytes) {
|
|
5633
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
5634
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
5635
|
+
return `${(bytes / 1048576).toFixed(1)}MB`;
|
|
5636
|
+
}
|
|
5644
5637
|
/**
|
|
5645
|
-
*
|
|
5646
|
-
*
|
|
5647
|
-
*
|
|
5648
|
-
*
|
|
5649
|
-
* so -- leaving an existing answer alone, since a more specific one is already better.
|
|
5638
|
+
* Truncate content from the tail (keep the last N lines/bytes). Suitable for
|
|
5639
|
+
* bash output where the end matters (errors, final results). May return a
|
|
5640
|
+
* partial first line if the last line of the original content exceeds the byte
|
|
5641
|
+
* limit.
|
|
5650
5642
|
*/
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
const
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5643
|
+
function truncateTail(content, options = {}) {
|
|
5644
|
+
const maxLines = options.maxLines ?? 2e3;
|
|
5645
|
+
const maxBytes = options.maxBytes ?? 51200;
|
|
5646
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
5647
|
+
const lines = splitLinesForCounting(content);
|
|
5648
|
+
const totalLines = lines.length;
|
|
5649
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) return {
|
|
5650
|
+
content,
|
|
5651
|
+
truncated: false,
|
|
5652
|
+
truncatedBy: null,
|
|
5653
|
+
totalLines,
|
|
5654
|
+
totalBytes,
|
|
5655
|
+
outputLines: totalLines,
|
|
5656
|
+
outputBytes: totalBytes,
|
|
5657
|
+
lastLinePartial: false,
|
|
5658
|
+
firstLineExceedsLimit: false,
|
|
5659
|
+
maxLines,
|
|
5660
|
+
maxBytes
|
|
5657
5661
|
};
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
}
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5662
|
+
const outputLinesArr = [];
|
|
5663
|
+
let outputBytesCount = 0;
|
|
5664
|
+
let truncatedBy = "lines";
|
|
5665
|
+
let lastLinePartial = false;
|
|
5666
|
+
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
5667
|
+
const line = lines[i] ?? "";
|
|
5668
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0);
|
|
5669
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
5670
|
+
truncatedBy = "bytes";
|
|
5671
|
+
if (outputLinesArr.length === 0) {
|
|
5672
|
+
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
5673
|
+
outputLinesArr.unshift(truncatedLine);
|
|
5674
|
+
outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
|
|
5675
|
+
lastLinePartial = true;
|
|
5676
|
+
}
|
|
5677
|
+
break;
|
|
5678
|
+
}
|
|
5679
|
+
outputLinesArr.unshift(line);
|
|
5680
|
+
outputBytesCount += lineBytes;
|
|
5681
|
+
}
|
|
5682
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) truncatedBy = "lines";
|
|
5683
|
+
const outputContent = outputLinesArr.join("\n");
|
|
5684
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
5685
|
+
return {
|
|
5686
|
+
content: outputContent,
|
|
5687
|
+
truncated: true,
|
|
5688
|
+
truncatedBy,
|
|
5689
|
+
totalLines,
|
|
5690
|
+
totalBytes,
|
|
5691
|
+
outputLines: outputLinesArr.length,
|
|
5692
|
+
outputBytes: finalOutputBytes,
|
|
5693
|
+
lastLinePartial,
|
|
5694
|
+
firstLineExceedsLimit: false,
|
|
5695
|
+
maxLines,
|
|
5696
|
+
maxBytes
|
|
5697
|
+
};
|
|
5698
|
+
}
|
|
5682
5699
|
/**
|
|
5683
|
-
*
|
|
5684
|
-
*
|
|
5685
|
-
*
|
|
5686
|
-
* A relative path is a disguised `process.cwd()` call: and every consumer
|
|
5687
|
-
* resolves it silently. `path.resolve("x")` anchors to the process directory, npm answers a
|
|
5688
|
-
* relative `cwd` with a `localPrefix` of whatever repository the process is inside (measured, not
|
|
5689
|
-
* assumed), and `Session.link` would write the *server's* directory into a field whose whole
|
|
5690
|
-
* purpose is to be a host path the client declared.
|
|
5691
|
-
*
|
|
5692
|
-
* For a long-running server the process directory is nobody's project, so the result is a
|
|
5693
|
-
* plausible answer to a question no one asked.
|
|
5694
|
-
* An absolute path is the one spelling none of them can quietly reinterpret.
|
|
5695
|
-
*
|
|
5696
|
-
* A defect rather than a typed error because nothing a user types reaches here. Every reference
|
|
5697
|
-
* goes through `parse`, and every directory comes from `Global.resolve`, a resolved `cwd` or a
|
|
5698
|
-
* session's `hostDir`, all of them already absolute. A relative one means a new call site got it
|
|
5699
|
-
* wrong, and a stack trace names that call site; a typed error would ask every caller to handle a
|
|
5700
|
-
* case none of them can cause.
|
|
5700
|
+
* Truncate a string to fit within a byte limit (from the end). Handles
|
|
5701
|
+
* multi-byte UTF-8 characters correctly (never splits a codepoint).
|
|
5701
5702
|
*/
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5703
|
+
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
5704
|
+
const buf = Buffer.from(str, "utf-8");
|
|
5705
|
+
if (buf.length <= maxBytes) return str;
|
|
5706
|
+
let start = buf.length - maxBytes;
|
|
5707
|
+
while (start < buf.length && (buf[start] & 192) === 128) start++;
|
|
5708
|
+
return buf.subarray(start).toString("utf-8");
|
|
5709
|
+
}
|
|
5706
5710
|
//#endregion
|
|
5707
|
-
//#region ../harness/src/
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
const created = new Config({
|
|
5751
|
-
npmPath: fileURLToPath(new URL("..", import.meta.url)),
|
|
5752
|
-
cwd: dir,
|
|
5753
|
-
env: { ...process.env },
|
|
5754
|
-
argv: [
|
|
5755
|
-
process.execPath,
|
|
5756
|
-
process.execPath,
|
|
5757
|
-
"--cache",
|
|
5758
|
-
hostPath.join(cache, "npm")
|
|
5759
|
-
],
|
|
5760
|
-
execPath: process.execPath,
|
|
5761
|
-
platform: process.platform,
|
|
5762
|
-
definitions,
|
|
5763
|
-
flatten,
|
|
5764
|
-
nerfDarts,
|
|
5765
|
-
shorthands,
|
|
5766
|
-
warn: false
|
|
5711
|
+
//#region ../harness/src/tool/accumulator.ts
|
|
5712
|
+
function defaultTempFilePath(prefix, suffix) {
|
|
5713
|
+
return posix.join(tmpdir(), `${prefix}-${suffix}.log`);
|
|
5714
|
+
}
|
|
5715
|
+
function byteLength(text) {
|
|
5716
|
+
return Buffer.byteLength(text, "utf-8");
|
|
5717
|
+
}
|
|
5718
|
+
var Accumulator = class {
|
|
5719
|
+
maxLines;
|
|
5720
|
+
maxBytes;
|
|
5721
|
+
maxRollingBytes;
|
|
5722
|
+
tempFilePrefix;
|
|
5723
|
+
decoder = new TextDecoder();
|
|
5724
|
+
rawChunks = [];
|
|
5725
|
+
tailText = "";
|
|
5726
|
+
tailBytes = 0;
|
|
5727
|
+
tailStartsAtLineBoundary = true;
|
|
5728
|
+
totalRawBytes = 0;
|
|
5729
|
+
totalDecodedBytes = 0;
|
|
5730
|
+
completedLines = 0;
|
|
5731
|
+
totalLines = 0;
|
|
5732
|
+
currentLineBytes = 0;
|
|
5733
|
+
hasOpenLine = false;
|
|
5734
|
+
finished = false;
|
|
5735
|
+
tempFilePath;
|
|
5736
|
+
tempFile;
|
|
5737
|
+
constructor(options = {}) {
|
|
5738
|
+
this.maxLines = options.maxLines ?? 2e3;
|
|
5739
|
+
this.maxBytes = options.maxBytes ?? 51200;
|
|
5740
|
+
this.maxRollingBytes = Math.max(this.maxBytes * 2, 1);
|
|
5741
|
+
this.tempFilePrefix = options.tempFilePrefix ?? "codework-output";
|
|
5742
|
+
}
|
|
5743
|
+
append(data) {
|
|
5744
|
+
return Effect.suspend(() => {
|
|
5745
|
+
if (this.finished) return Effect.die(/* @__PURE__ */ new Error("cannot append to a finished output accumulator"));
|
|
5746
|
+
this.totalRawBytes += data.length;
|
|
5747
|
+
this.appendDecodedText(this.decoder.decode(data, { stream: true }));
|
|
5748
|
+
if (this.tempFile !== void 0 || this.shouldUseTempFile()) return Effect.gen({ self: this }, function* () {
|
|
5749
|
+
yield* this.ensureTempFile();
|
|
5750
|
+
yield* this.tempFile.writeAll(data).pipe(Effect.orDie);
|
|
5751
|
+
});
|
|
5752
|
+
if (data.length > 0) this.rawChunks.push(data);
|
|
5753
|
+
return Effect.void;
|
|
5767
5754
|
});
|
|
5768
|
-
await created.load();
|
|
5769
|
-
return created;
|
|
5770
|
-
}).pipe(Effect.catch((cause) => Effect.logWarning(`cannot load npm configuration from ${dir}; npm defaults apply`, cause).pipe(Effect.as(void 0))));
|
|
5771
|
-
if (config === void 0) return { allowGit: "root" };
|
|
5772
|
-
for (const kind of [
|
|
5773
|
-
"project",
|
|
5774
|
-
"user",
|
|
5775
|
-
"global"
|
|
5776
|
-
]) {
|
|
5777
|
-
const loaded = config.data.get(kind);
|
|
5778
|
-
if (loaded?.loadError !== void 0 && loaded.loadError.code !== "ENOENT") yield* Effect.logWarning(`cannot read npm configuration ${loaded.source ?? `for ${kind}`}; npm defaults apply`, loaded.loadError);
|
|
5779
5755
|
}
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
}
|
|
5812
|
-
|
|
5813
|
-
|
|
5756
|
+
finish() {
|
|
5757
|
+
return Effect.suspend(() => {
|
|
5758
|
+
if (this.finished) return Effect.void;
|
|
5759
|
+
this.finished = true;
|
|
5760
|
+
this.appendDecodedText(this.decoder.decode());
|
|
5761
|
+
return Effect.gen({ self: this }, function* () {
|
|
5762
|
+
if (this.shouldUseTempFile()) yield* this.ensureTempFile();
|
|
5763
|
+
if (this.tempFile !== void 0) yield* this.tempFile.sync.pipe(Effect.orDie);
|
|
5764
|
+
});
|
|
5765
|
+
});
|
|
5766
|
+
}
|
|
5767
|
+
snapshot() {
|
|
5768
|
+
const tailTruncation = truncateTail(this.getSnapshotText(), {
|
|
5769
|
+
maxLines: this.maxLines,
|
|
5770
|
+
maxBytes: this.maxBytes
|
|
5771
|
+
});
|
|
5772
|
+
const truncated = this.totalLines > this.maxLines || this.totalDecodedBytes > this.maxBytes;
|
|
5773
|
+
const truncatedBy = truncated ? tailTruncation.truncatedBy ?? (this.totalDecodedBytes > this.maxBytes ? "bytes" : "lines") : null;
|
|
5774
|
+
const truncation = {
|
|
5775
|
+
...tailTruncation,
|
|
5776
|
+
truncated,
|
|
5777
|
+
truncatedBy,
|
|
5778
|
+
totalLines: this.totalLines,
|
|
5779
|
+
totalBytes: this.totalDecodedBytes,
|
|
5780
|
+
maxLines: this.maxLines,
|
|
5781
|
+
maxBytes: this.maxBytes
|
|
5782
|
+
};
|
|
5783
|
+
return {
|
|
5784
|
+
content: truncation.content,
|
|
5785
|
+
truncation,
|
|
5786
|
+
...this.tempFilePath === void 0 ? {} : { fullOutputPath: this.tempFilePath }
|
|
5787
|
+
};
|
|
5788
|
+
}
|
|
5789
|
+
/** Bytes in the final (possibly unterminated) line — for the partial-line footer. */
|
|
5790
|
+
getLastLineBytes() {
|
|
5791
|
+
return this.currentLineBytes;
|
|
5792
|
+
}
|
|
5793
|
+
appendDecodedText(text) {
|
|
5794
|
+
if (text.length === 0) return;
|
|
5795
|
+
const bytes = byteLength(text);
|
|
5796
|
+
this.totalDecodedBytes += bytes;
|
|
5797
|
+
this.tailText += text;
|
|
5798
|
+
this.tailBytes += bytes;
|
|
5799
|
+
if (this.tailBytes > this.maxRollingBytes * 2) this.trimTail();
|
|
5800
|
+
let newlines = 0;
|
|
5801
|
+
let lastNewline = -1;
|
|
5802
|
+
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1)) {
|
|
5803
|
+
newlines++;
|
|
5804
|
+
lastNewline = i;
|
|
5805
|
+
}
|
|
5806
|
+
if (newlines === 0) {
|
|
5807
|
+
this.currentLineBytes += bytes;
|
|
5808
|
+
this.hasOpenLine = true;
|
|
5809
|
+
} else {
|
|
5810
|
+
this.completedLines += newlines;
|
|
5811
|
+
const tail = text.slice(lastNewline + 1);
|
|
5812
|
+
this.currentLineBytes = byteLength(tail);
|
|
5813
|
+
this.hasOpenLine = tail.length > 0;
|
|
5814
|
+
}
|
|
5815
|
+
this.totalLines = this.completedLines + (this.hasOpenLine ? 1 : 0);
|
|
5816
|
+
}
|
|
5817
|
+
trimTail() {
|
|
5818
|
+
const buffer = Buffer.from(this.tailText, "utf-8");
|
|
5819
|
+
if (buffer.length <= this.maxRollingBytes) {
|
|
5820
|
+
this.tailBytes = buffer.length;
|
|
5821
|
+
return;
|
|
5822
|
+
}
|
|
5823
|
+
let start = buffer.length - this.maxRollingBytes;
|
|
5824
|
+
while (start < buffer.length && (buffer[start] & 192) === 128) start++;
|
|
5825
|
+
this.tailStartsAtLineBoundary = start === 0 ? this.tailStartsAtLineBoundary : buffer[start - 1] === 10;
|
|
5826
|
+
this.tailText = buffer.subarray(start).toString("utf-8");
|
|
5827
|
+
this.tailBytes = byteLength(this.tailText);
|
|
5828
|
+
}
|
|
5829
|
+
getSnapshotText() {
|
|
5830
|
+
if (this.tailStartsAtLineBoundary) return this.tailText;
|
|
5831
|
+
const firstNewline = this.tailText.indexOf("\n");
|
|
5832
|
+
return firstNewline === -1 ? this.tailText : this.tailText.slice(firstNewline + 1);
|
|
5833
|
+
}
|
|
5834
|
+
shouldUseTempFile() {
|
|
5835
|
+
return this.totalRawBytes > this.maxBytes || this.totalDecodedBytes > this.maxBytes || this.totalLines > this.maxLines;
|
|
5836
|
+
}
|
|
5837
|
+
ensureTempFile() {
|
|
5838
|
+
if (this.tempFile !== void 0) return Effect.void;
|
|
5839
|
+
return Effect.gen({ self: this }, function* () {
|
|
5840
|
+
const suffix = Encoding.encodeHex(yield* crypto.randomBytes(8));
|
|
5841
|
+
this.tempFilePath = defaultTempFilePath(this.tempFilePrefix, suffix);
|
|
5842
|
+
this.tempFile = yield* fileSystem.open(this.tempFilePath, { flag: "w" });
|
|
5843
|
+
for (const chunk of this.rawChunks) yield* this.tempFile.writeAll(chunk);
|
|
5844
|
+
this.rawChunks = [];
|
|
5845
|
+
}).pipe(Effect.orDie);
|
|
5846
|
+
}
|
|
5847
|
+
};
|
|
5848
|
+
//#endregion
|
|
5849
|
+
//#region ../harness/src/tool/progress.ts
|
|
5850
|
+
var progress_exports = /* @__PURE__ */ __exportAll({});
|
|
5851
|
+
import * as import__codeworksh_plugin_tool_progress from "@codeworksh/plugin/tool/progress";
|
|
5852
|
+
__reExport(progress_exports, import__codeworksh_plugin_tool_progress);
|
|
5853
|
+
//#endregion
|
|
5854
|
+
//#region ../harness/src/tool/shell.ts
|
|
5814
5855
|
/**
|
|
5815
|
-
*
|
|
5856
|
+
* `ToolShell` — the tool-owned, cancellable command-execution capability the bash tool depends on.
|
|
5816
5857
|
*
|
|
5817
|
-
*
|
|
5858
|
+
* It deliberately does **not** reuse `sandbox/Shell` directly: the local OS path
|
|
5859
|
+
* provides no `Shell` at all, and `sandbox/Shell` models neither cancellation
|
|
5860
|
+
* nor a timeout. `ToolShell` makes both part of the contract. Adapters honor the
|
|
5861
|
+
* mid-flight process kill where the SDK allows it (local, Vercel) and fall back
|
|
5862
|
+
* to the timeout where it does not (Daytona) — "kill where it's easy".
|
|
5863
|
+
*
|
|
5864
|
+
* Two modes, and Local has two variants:
|
|
5865
|
+
* - Local / full OS shell → {@link local} (default)
|
|
5866
|
+
* - Local / just-bash → {@link fromSandboxShell} over `EnvBash`
|
|
5867
|
+
* - Remote / provider → {@link fromSandboxShell} over `EnvDaytona` / `EnvVercel`
|
|
5818
5868
|
*/
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
"MODULE_NOT_FOUND",
|
|
5832
|
-
"ERR_MODULE_NOT_FOUND",
|
|
5833
|
-
"ERR_PACKAGE_PATH_NOT_EXPORTED",
|
|
5834
|
-
"ERR_UNSUPPORTED_DIR_IMPORT"
|
|
5835
|
-
]);
|
|
5869
|
+
/** Infra/spawn failure — not model-actionable; handlers should treat it as a defect. */
|
|
5870
|
+
var ToolShellError = class extends Schema.TaggedError()("ToolShellError", {
|
|
5871
|
+
command: Schema.String,
|
|
5872
|
+
cause: Schema.optional(Schema.Defect())
|
|
5873
|
+
}) {};
|
|
5874
|
+
/** The command exceeded its deadline. Distinct so handlers can map it to a domain timeout. */
|
|
5875
|
+
var ToolShellTimeout = class extends Schema.TaggedError()("ToolShellTimeout", {
|
|
5876
|
+
command: Schema.String,
|
|
5877
|
+
timeoutMillis: Schema.Finite
|
|
5878
|
+
}) {};
|
|
5879
|
+
const widenExecError = (effect) => effect.pipe(Effect.mapError((error) => error));
|
|
5880
|
+
var ToolShell = class extends Context.Service()("@codeworksh/harness/tool/shell/ToolShell") {};
|
|
5836
5881
|
/**
|
|
5837
|
-
*
|
|
5838
|
-
*
|
|
5839
|
-
* Ordered subpaths through the package's own `exports`, so a package that publishes a dedicated
|
|
5840
|
-
* `./plugin` entry is honoured and one that does not falls back to its main.
|
|
5882
|
+
* Bridge the existing `sandbox/Shell` into a `ToolShell`. Used for just-bash
|
|
5883
|
+
* (in-process) and remote providers.
|
|
5841
5884
|
*
|
|
5842
|
-
*
|
|
5843
|
-
* a
|
|
5844
|
-
*
|
|
5885
|
+
* `cwd` and `env` both pass through to the backing `sandbox/Shell`, which
|
|
5886
|
+
* resolves a relative `cwd` against its mount. The buffered `exec`
|
|
5887
|
+
* timeout is enforced here with `Effect.timeoutOption` (interruption always
|
|
5888
|
+
* returns control; the underlying command is only truly killed mid-flight on
|
|
5889
|
+
* signal-aware backends, handled in the provider layer). When the backend exposes
|
|
5890
|
+
* `stream` (e.g. Vercel `Command.logs`), it is bridged to {@link IToolShell.stream}
|
|
5891
|
+
* so the bash tool streams over it too.
|
|
5845
5892
|
*/
|
|
5846
|
-
const
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
const
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5893
|
+
const fromSandboxShell = Layer.effect(ToolShell, Effect.gen(function* () {
|
|
5894
|
+
const shell = yield* shell_exports.Shell;
|
|
5895
|
+
const exec = (command, options) => {
|
|
5896
|
+
const run = shell.exec(command, {
|
|
5897
|
+
...options?.env ? { env: options.env } : {},
|
|
5898
|
+
...options?.cwd ? { cwd: options.cwd } : {}
|
|
5899
|
+
}).pipe(Effect.mapError((cause) => new ToolShellError({
|
|
5900
|
+
command,
|
|
5901
|
+
cause
|
|
5902
|
+
})));
|
|
5903
|
+
if (options?.timeout === void 0) return widenExecError(run);
|
|
5904
|
+
const timeout = options.timeout;
|
|
5905
|
+
return run.pipe(Effect.timeoutOption(timeout), Effect.flatMap(Option.match({
|
|
5906
|
+
onNone: () => Effect.fail(new ToolShellTimeout({
|
|
5907
|
+
command,
|
|
5908
|
+
timeoutMillis: Duration.toMillis(timeout)
|
|
5909
|
+
})),
|
|
5910
|
+
onSome: Effect.succeed
|
|
5854
5911
|
})));
|
|
5855
|
-
if (Option.isSome(resolved)) return fileURLToPath(resolved.value);
|
|
5856
|
-
}
|
|
5857
|
-
return yield* new InstallError({
|
|
5858
|
-
reason: "plugin-no-entrypoint",
|
|
5859
|
-
reference: name ?? directory,
|
|
5860
|
-
message: `no plugin entrypoint in ${directory}`
|
|
5861
|
-
});
|
|
5862
|
-
});
|
|
5863
|
-
const reify = Effect.fn("PluginNpm.reify")(function* ({ target, into, cache, from }) {
|
|
5864
|
-
const flat = yield* options(from, cache);
|
|
5865
|
-
const { Arborist } = yield* Effect.promise(() => import("./lib-Dw7R2suc.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1)));
|
|
5866
|
-
const settings = {
|
|
5867
|
-
...flat,
|
|
5868
|
-
...target.mutable ? {
|
|
5869
|
-
preferOnline: true,
|
|
5870
|
-
noGitRevCache: true
|
|
5871
|
-
} : {},
|
|
5872
|
-
audit: false
|
|
5873
5912
|
};
|
|
5874
|
-
const
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
}
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5913
|
+
const sandboxStream = shell.stream;
|
|
5914
|
+
const stream = sandboxStream ? (command, options) => sandboxStream(command, {
|
|
5915
|
+
...options?.env ? { env: options.env } : {},
|
|
5916
|
+
...options?.cwd ? { cwd: options.cwd } : {}
|
|
5917
|
+
}).pipe(Stream.map((chunk) => chunk._tag === "exit" ? {
|
|
5918
|
+
_tag: "Exit",
|
|
5919
|
+
exitCode: chunk.exitCode
|
|
5920
|
+
} : {
|
|
5921
|
+
_tag: "Output",
|
|
5922
|
+
bytes: chunk.bytes
|
|
5923
|
+
}), Stream.mapError((cause) => new ToolShellError({
|
|
5924
|
+
command,
|
|
5925
|
+
cause
|
|
5926
|
+
}))) : void 0;
|
|
5927
|
+
return ToolShell.of(stream ? {
|
|
5928
|
+
exec,
|
|
5929
|
+
stream
|
|
5930
|
+
} : { exec });
|
|
5931
|
+
}));
|
|
5932
|
+
Duration.seconds(1);
|
|
5933
|
+
//#endregion
|
|
5934
|
+
//#region ../harness/src/tool/tool.ts
|
|
5935
|
+
var tool_exports = /* @__PURE__ */ __exportAll({});
|
|
5936
|
+
import * as import__codeworksh_plugin_tool from "@codeworksh/plugin/tool";
|
|
5937
|
+
__reExport(tool_exports, import__codeworksh_plugin_tool);
|
|
5938
|
+
//#endregion
|
|
5939
|
+
//#region ../harness/src/plugin/builtin/tool/bash.ts
|
|
5898
5940
|
/**
|
|
5899
|
-
*
|
|
5941
|
+
* The self-contained bash plugin — a worked example for tool plugins. The definition is pure data;
|
|
5942
|
+
* the handler depends only on {@link ToolShell} / {@link ToolProgress}, never
|
|
5943
|
+
* `sandbox/Shell`, so the backend (local OS / just-bash / remote provider) is
|
|
5944
|
+
* swapped by changing the provided Layer with no change to the tool.
|
|
5900
5945
|
*
|
|
5901
|
-
*
|
|
5902
|
-
*
|
|
5946
|
+
* Two execution paths, picked by capability:
|
|
5947
|
+
* - **variant A (buffered)** otherwise: `ToolShell.exec` returns the full output,
|
|
5948
|
+
* truncated once after completion.
|
|
5949
|
+
* - **variant B (streaming)** when the backend offers `ToolShell.stream`: output
|
|
5950
|
+
* flows through an {@link Accumulator} (bounded memory + temp-file spill)
|
|
5951
|
+
* and is reported live via {@link ToolProgress}; on timeout the partial output
|
|
5952
|
+
* produced so far is preserved.
|
|
5953
|
+
*
|
|
5954
|
+
* Success and failure carry the *same* structured shape
|
|
5955
|
+
* (`output`, `exitCode`, `truncated`, `fullOutputPath`)
|
|
5956
|
+
* — a non-zero exit is just `exitCode !== 0`, not a different kind of result.
|
|
5903
5957
|
*/
|
|
5904
|
-
const
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5958
|
+
const BashParams = Schema.Struct({
|
|
5959
|
+
command: Schema.String.annotate({ description: "The bash command to execute." }),
|
|
5960
|
+
timeout: Schema.optional(Schema.Finite.check(Schema.isGreaterThan(0)).annotate({ description: "Optional timeout in seconds (must be greater than 0)." }))
|
|
5961
|
+
});
|
|
5962
|
+
const outputFields = {
|
|
5963
|
+
/** Combined stdout + stderr, truncated for display (full output is on disk when `truncated`). */
|
|
5964
|
+
output: Schema.String,
|
|
5965
|
+
truncated: Schema.Boolean,
|
|
5966
|
+
fullOutputPath: Schema.optional(Schema.String)
|
|
5967
|
+
};
|
|
5968
|
+
const BashSuccess = Schema.Struct({
|
|
5969
|
+
...outputFields,
|
|
5970
|
+
exitCode: Schema.Finite
|
|
5971
|
+
});
|
|
5972
|
+
/** Non-zero exit — expected, model-visible. Carries the same shape as success. */
|
|
5973
|
+
var BashFailed = class extends Schema.TaggedError()("BashFailed", {
|
|
5974
|
+
...outputFields,
|
|
5975
|
+
exitCode: Schema.Finite
|
|
5976
|
+
}) {};
|
|
5977
|
+
/** Deadline exceeded — expected, model-visible. Carries the partial output produced so far. */
|
|
5978
|
+
var BashTimedOut = class extends Schema.TaggedError()("BashTimedOut", {
|
|
5979
|
+
...outputFields,
|
|
5980
|
+
timeoutSeconds: Schema.Finite
|
|
5981
|
+
}) {};
|
|
5982
|
+
const BashFailure = Schema.Union([BashFailed, BashTimedOut]);
|
|
5983
|
+
const bashDef = tool_exports.define({
|
|
5984
|
+
name: "bash",
|
|
5985
|
+
label: "bash",
|
|
5986
|
+
promptSnippet: "Execute bash commands (ls, grep, find, etc.).",
|
|
5987
|
+
description: `Execute a bash command in the working directory and return its combined stdout/stderr output. Output is truncated to the last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES}KB (whichever is hit first); when truncated, the full output is saved to a temp file. A non-zero exit code is reported as an error carrying the captured output.`,
|
|
5988
|
+
parameters: BashParams,
|
|
5989
|
+
success: BashSuccess,
|
|
5990
|
+
failure: BashFailure,
|
|
5991
|
+
encodeContent: (success) => [{
|
|
5992
|
+
type: "text",
|
|
5993
|
+
text: success.output
|
|
5994
|
+
}],
|
|
5995
|
+
encodeFailureContent: (failure) => [{
|
|
5996
|
+
type: "text",
|
|
5997
|
+
text: failure.output
|
|
5998
|
+
}]
|
|
5999
|
+
});
|
|
6000
|
+
/** Combine stdout and stderr into one stream of text, stderr after stdout. */
|
|
6001
|
+
const combineOutput = (stdout, stderr) => {
|
|
6002
|
+
if (stderr.length === 0) return stdout;
|
|
6003
|
+
if (stdout.length === 0) return stderr;
|
|
6004
|
+
return `${stdout}\n${stderr}`;
|
|
6005
|
+
};
|
|
6006
|
+
/** One-line footer appended to truncated, model-facing output. */
|
|
6007
|
+
const footer = (t, fullOutputPath, lastLineBytes) => {
|
|
6008
|
+
const where = fullOutputPath ? ` Full output: ${fullOutputPath}` : "";
|
|
6009
|
+
const startLine = t.totalLines - t.outputLines + 1;
|
|
6010
|
+
if (t.lastLinePartial) {
|
|
6011
|
+
const lineSize = lastLineBytes !== void 0 ? ` (line is ${formatSize(lastLineBytes)})` : "";
|
|
6012
|
+
return `\n\n[showing last ${formatSize(t.outputBytes)} of line ${t.totalLines}${lineSize}.${where}]`;
|
|
6013
|
+
}
|
|
6014
|
+
if (t.truncatedBy === "lines") return `\n\n[showing lines ${startLine}-${t.totalLines} of ${t.totalLines}.${where}]`;
|
|
6015
|
+
return `\n\n[showing lines ${startLine}-${t.totalLines} of ${t.totalLines} (${formatSize(t.maxBytes)} limit).${where}]`;
|
|
6016
|
+
};
|
|
6017
|
+
/** Write full output to a host temp file (best-effort; undefined on failure). */
|
|
6018
|
+
const spillToTempFile = (content) => Effect.gen(function* () {
|
|
6019
|
+
const suffix = Encoding.encodeHex(yield* crypto.randomBytes(6));
|
|
6020
|
+
const path = posix.join(tmpdir(), `codework-bash-${suffix}.log`);
|
|
6021
|
+
yield* fileSystem.writeFileString(path, content);
|
|
6022
|
+
return path;
|
|
6023
|
+
}).pipe(Effect.orElseSucceed(() => void 0));
|
|
6024
|
+
/** Variant A: truncate the buffered output once, spilling the full output if truncated. */
|
|
6025
|
+
const presentBuffered = (combined) => Effect.gen(function* () {
|
|
6026
|
+
const t = truncateTail(combined);
|
|
6027
|
+
if (!t.truncated) return {
|
|
6028
|
+
output: t.content,
|
|
6029
|
+
truncated: false
|
|
5910
6030
|
};
|
|
5911
|
-
const
|
|
5912
|
-
reason: "plugin-fetch-failed",
|
|
5913
|
-
reference: target.spec,
|
|
5914
|
-
message: `cannot resolve the staging directory ${into}`,
|
|
5915
|
-
cause
|
|
5916
|
-
})));
|
|
5917
|
-
const node = (yield* runner({
|
|
5918
|
-
target,
|
|
5919
|
-
into: root,
|
|
5920
|
-
cache,
|
|
5921
|
-
from
|
|
5922
|
-
})).edgesOut.values().next().value?.to;
|
|
5923
|
-
if (node === void 0) return yield* new InstallError({
|
|
5924
|
-
reason: "plugin-fetch-failed",
|
|
5925
|
-
reference: target.spec,
|
|
5926
|
-
message: `${target.spec} installed nothing`
|
|
5927
|
-
});
|
|
5928
|
-
const manifest = yield* fileSystem.readFileString(hostPath.join(node.path, "package.json")).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Manifest$1))), Effect.option);
|
|
5929
|
-
const version = Option.isNone(manifest) ? void 0 : manifest.value.version;
|
|
5930
|
-
const revision = target.kind === "registry" ? version : (yield* commit(root, node.name)) ?? (yield* noCommit(target.spec));
|
|
6031
|
+
const fullOutputPath = yield* spillToTempFile(combined);
|
|
5931
6032
|
return {
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
...
|
|
5935
|
-
...revision === void 0 ? {} : { revision },
|
|
5936
|
-
entrypoint: yield* entrypoint(node.path, node.name)
|
|
6033
|
+
output: t.content + footer(t, fullOutputPath),
|
|
6034
|
+
truncated: true,
|
|
6035
|
+
...fullOutputPath !== void 0 ? { fullOutputPath } : {}
|
|
5937
6036
|
};
|
|
5938
6037
|
});
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
* target skips the call entirely -- a version and a SHA cannot move.
|
|
5950
|
-
*/
|
|
5951
|
-
const probe = Effect.fn("PluginNpm.probe")(function* (target, cache, from) {
|
|
5952
|
-
if (target.kind === "local" || !target.mutable) return void 0;
|
|
5953
|
-
const opts = {
|
|
5954
|
-
...yield* options(from, cache),
|
|
5955
|
-
preferOnline: true,
|
|
5956
|
-
noGitRevCache: true,
|
|
5957
|
-
_isRoot: true
|
|
6038
|
+
/** Variant B: present an accumulator snapshot (the temp file is spilled incrementally). */
|
|
6039
|
+
const presentSnapshot = (snap, lastLineBytes) => {
|
|
6040
|
+
if (!snap.truncation.truncated) return {
|
|
6041
|
+
output: snap.content,
|
|
6042
|
+
truncated: false
|
|
6043
|
+
};
|
|
6044
|
+
return {
|
|
6045
|
+
output: snap.content + footer(snap.truncation, snap.fullOutputPath, lastLineBytes),
|
|
6046
|
+
truncated: true,
|
|
6047
|
+
...snap.fullOutputPath !== void 0 ? { fullOutputPath: snap.fullOutputPath } : {}
|
|
5958
6048
|
};
|
|
5959
|
-
const { default: pacote } = yield* Effect.promise(() => import("./lib-BkWR53Bz.mjs").then((n) => /* @__PURE__ */ __toESM(n.t(), 1)));
|
|
5960
|
-
return yield* Effect.tryPromise({
|
|
5961
|
-
try: async () => target.kind === "registry" ? (await pacote.manifest(target.spec, opts)).version : commitIn(await pacote.resolve(target.spec, opts)),
|
|
5962
|
-
catch: (cause) => new InstallError({
|
|
5963
|
-
reason: "plugin-resolve-failed",
|
|
5964
|
-
reference: target.spec,
|
|
5965
|
-
message: `cannot reach ${target.spec}`,
|
|
5966
|
-
cause
|
|
5967
|
-
})
|
|
5968
|
-
});
|
|
5969
|
-
});
|
|
5970
|
-
/** A `file:` URL for an entrypoint, which is what the module loader takes. */
|
|
5971
|
-
const url = (entrypoint) => pathToFileURL(entrypoint).href;
|
|
5972
|
-
//#endregion
|
|
5973
|
-
//#region ../harness/src/plugin/plugin.ts
|
|
5974
|
-
var plugin_exports = /* @__PURE__ */ __exportAll({});
|
|
5975
|
-
import * as import__codeworksh_plugin_plugin from "@codeworksh/plugin/plugin";
|
|
5976
|
-
__reExport(plugin_exports, import__codeworksh_plugin_plugin);
|
|
5977
|
-
//#endregion
|
|
5978
|
-
//#region ../harness/src/plugin/source.ts
|
|
5979
|
-
/** A committish that names one artifact rather than a moving branch: an unabbreviated SHA. */
|
|
5980
|
-
const isCommit = (committish) => typeof committish === "string" && /^[a-f0-9]{40}$|^[a-f0-9]{64}$/i.test(committish);
|
|
5981
|
-
/**
|
|
5982
|
-
* A short, readable name for a git source, used to make a store path recognisable to a human
|
|
5983
|
-
* reading `ls`. It is never an identity: two remotes can slug alike, and the digest is what
|
|
5984
|
-
* separates them.
|
|
5985
|
-
*/
|
|
5986
|
-
const slug = (spec) => {
|
|
5987
|
-
const withoutCommittish = spec.split("#")[0] ?? spec;
|
|
5988
|
-
return (() => {
|
|
5989
|
-
try {
|
|
5990
|
-
return decodeURIComponent(withoutCommittish);
|
|
5991
|
-
} catch {
|
|
5992
|
-
return withoutCommittish;
|
|
5993
|
-
}
|
|
5994
|
-
})().replace(/\.git$/i, "").split(/[/:\\]/).at(-1)?.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repository";
|
|
5995
6049
|
};
|
|
5996
|
-
/**
|
|
5997
|
-
const
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
reference,
|
|
6011
|
-
message
|
|
6050
|
+
/** Variant A — buffered: one `exec`, truncate after completion. No live progress. */
|
|
6051
|
+
const runBuffered = (shell, params) => Effect.gen(function* () {
|
|
6052
|
+
const result = yield* shell.exec(params.command, params.timeout !== void 0 ? { timeout: Duration.seconds(params.timeout) } : void 0).pipe(Effect.catchTags({
|
|
6053
|
+
ToolShellTimeout: (timeout) => Effect.fail(new BashTimedOut({
|
|
6054
|
+
timeoutSeconds: timeout.timeoutMillis / 1e3,
|
|
6055
|
+
output: "",
|
|
6056
|
+
truncated: false
|
|
6057
|
+
})),
|
|
6058
|
+
ToolShellError: (cause) => Effect.die(cause)
|
|
6059
|
+
}));
|
|
6060
|
+
const present = yield* presentBuffered(combineOutput(result.stdout, result.stderr));
|
|
6061
|
+
if (result.exitCode !== 0) return yield* new BashFailed({
|
|
6062
|
+
exitCode: result.exitCode,
|
|
6063
|
+
...present
|
|
6012
6064
|
});
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
return {
|
|
6017
|
-
kind: "local",
|
|
6018
|
-
path: fileURLToPath(reference)
|
|
6019
|
-
};
|
|
6020
|
-
}
|
|
6021
|
-
const expanded = expandTilde(reference, hostPath);
|
|
6022
|
-
if (isPath(expanded)) return {
|
|
6023
|
-
kind: "local",
|
|
6024
|
-
path: hostPath.resolve(from, expanded)
|
|
6065
|
+
return {
|
|
6066
|
+
exitCode: result.exitCode,
|
|
6067
|
+
...present
|
|
6025
6068
|
};
|
|
6026
|
-
const parsed = yield* Effect.try({
|
|
6027
|
-
try: () => npa(reference),
|
|
6028
|
-
catch: () => fail("plugin-unsupported-source", `cannot parse plugin source: ${reference}`)
|
|
6029
|
-
});
|
|
6030
|
-
if (parsed.type === "git") {
|
|
6031
|
-
const { gitSubdir } = parsed;
|
|
6032
|
-
return {
|
|
6033
|
-
kind: "git",
|
|
6034
|
-
slug: slug(reference),
|
|
6035
|
-
spec: reference,
|
|
6036
|
-
...parsed.gitCommittish == null ? {} : { committish: parsed.gitCommittish },
|
|
6037
|
-
...gitSubdir == null ? {} : { subdir: gitSubdir },
|
|
6038
|
-
mutable: !isCommit(parsed.gitCommittish)
|
|
6039
|
-
};
|
|
6040
|
-
}
|
|
6041
|
-
if (parsed.name !== null && [
|
|
6042
|
-
"version",
|
|
6043
|
-
"range",
|
|
6044
|
-
"tag"
|
|
6045
|
-
].includes(parsed.type)) {
|
|
6046
|
-
const omitted = parsed.raw === parsed.name || parsed.raw === `${parsed.name}@`;
|
|
6047
|
-
return {
|
|
6048
|
-
kind: "registry",
|
|
6049
|
-
name: parsed.name,
|
|
6050
|
-
spec: `${parsed.name}@${omitted ? "latest" : parsed.rawSpec}`,
|
|
6051
|
-
mutable: parsed.type !== "version"
|
|
6052
|
-
};
|
|
6053
|
-
}
|
|
6054
|
-
return yield* fail("plugin-unsupported-source", `unsupported plugin source: ${reference}`);
|
|
6055
|
-
});
|
|
6056
|
-
/**
|
|
6057
|
-
* What two references must share to be the same plugin, without installing either.
|
|
6058
|
-
*
|
|
6059
|
-
* A local path is the file it resolves to; a package is its name, so `@acme/x@1` and `@acme/x@2`
|
|
6060
|
-
* are one package at two versions. Pure -- nothing is installed, imported or read.
|
|
6061
|
-
*/
|
|
6062
|
-
const canonical$1 = (target) => {
|
|
6063
|
-
switch (target.kind) {
|
|
6064
|
-
case "local": return target.path;
|
|
6065
|
-
case "registry": return target.name;
|
|
6066
|
-
case "git": return target.spec;
|
|
6067
|
-
}
|
|
6068
|
-
};
|
|
6069
|
-
//#endregion
|
|
6070
|
-
//#region ../harness/src/plugin/index.store.ts
|
|
6071
|
-
const Record_ = Schema.Struct({
|
|
6072
|
-
spec: Schema.String,
|
|
6073
|
-
source: Schema.Literals(["registry", "git"]),
|
|
6074
|
-
name: Schema.String,
|
|
6075
|
-
slug: Schema.String,
|
|
6076
|
-
mutable: Schema.Boolean,
|
|
6077
|
-
generation: Schema.Finite,
|
|
6078
|
-
version: Schema.optional(Schema.String),
|
|
6079
|
-
revision: Schema.optional(Schema.String),
|
|
6080
|
-
entrypoint: Schema.String,
|
|
6081
|
-
installedAt: Schema.Finite,
|
|
6082
|
-
checkedAt: Schema.optional(Schema.Finite),
|
|
6083
|
-
outdated: Schema.optional(Schema.Boolean),
|
|
6084
|
-
/** The remote revision observed at `checkedAt`; distinct from the installed `revision`. */
|
|
6085
|
-
available: Schema.optional(Schema.String)
|
|
6086
|
-
});
|
|
6087
|
-
const File = Schema.Struct({
|
|
6088
|
-
version: Schema.Finite,
|
|
6089
|
-
entries: Schema.Record(Schema.String, Record_)
|
|
6090
|
-
});
|
|
6091
|
-
const VERSION = 1;
|
|
6092
|
-
const NAME = "index.json";
|
|
6093
|
-
/**
|
|
6094
|
-
* Read the whole file, or start empty.
|
|
6095
|
-
*
|
|
6096
|
-
* An unreadable or unparseable index is not an error: it is a cold cache. Failing here would make
|
|
6097
|
-
* a corrupt accelerator break installs that do not need it.
|
|
6098
|
-
*/
|
|
6099
|
-
const read = Effect.fn("PluginIndex.read")(function* (root) {
|
|
6100
|
-
const parsed = yield* fileSystem.readFileString(hostPath.join(root, NAME)).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(File))), Effect.option);
|
|
6101
|
-
if (Option.isNone(parsed) || parsed.value.version !== VERSION) return /* @__PURE__ */ new Map();
|
|
6102
|
-
return new Map(Object.entries(parsed.value.entries));
|
|
6103
6069
|
});
|
|
6104
|
-
/**
|
|
6105
|
-
const
|
|
6106
|
-
const
|
|
6107
|
-
|
|
6108
|
-
|
|
6070
|
+
/** Variant B — streaming: accumulate output, report progress, keep partial output on timeout. */
|
|
6071
|
+
const runStreaming = (stream, params) => Effect.scoped(Effect.gen(function* () {
|
|
6072
|
+
const acc = new Accumulator({ tempFilePrefix: "codework-bash" });
|
|
6073
|
+
const progress = yield* progress_exports.ToolProgress;
|
|
6074
|
+
const exitCode = yield* Ref.make(null);
|
|
6075
|
+
const consume = stream(params.command).pipe(Stream.runForEach((event) => event._tag === "Exit" ? Ref.set(exitCode, event.exitCode) : Effect.gen(function* () {
|
|
6076
|
+
yield* acc.append(Buffer.from(event.bytes));
|
|
6077
|
+
yield* progress.report({ content: [{
|
|
6078
|
+
type: "text",
|
|
6079
|
+
text: acc.snapshot().content
|
|
6080
|
+
}] });
|
|
6081
|
+
})), Effect.catchTag("ToolShellError", (cause) => Effect.die(cause)));
|
|
6082
|
+
let timedOutAfter;
|
|
6083
|
+
if (params.timeout !== void 0) {
|
|
6084
|
+
const finished = yield* consume.pipe(Effect.timeoutOption(Duration.seconds(params.timeout)));
|
|
6085
|
+
if (Option.isNone(finished)) timedOutAfter = params.timeout;
|
|
6086
|
+
} else yield* consume;
|
|
6087
|
+
yield* acc.finish();
|
|
6088
|
+
const present = presentSnapshot(acc.snapshot(), acc.getLastLineBytes());
|
|
6089
|
+
if (timedOutAfter !== void 0) return yield* new BashTimedOut({
|
|
6090
|
+
timeoutSeconds: timedOutAfter,
|
|
6091
|
+
...present
|
|
6109
6092
|
});
|
|
6110
|
-
yield*
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
suffix: ".tmp"
|
|
6093
|
+
const code = yield* Ref.get(exitCode);
|
|
6094
|
+
if (code === null || code !== 0) return yield* new BashFailed({
|
|
6095
|
+
exitCode: code ?? -1,
|
|
6096
|
+
...present
|
|
6115
6097
|
});
|
|
6116
|
-
|
|
6098
|
+
return {
|
|
6099
|
+
exitCode: code,
|
|
6100
|
+
...present
|
|
6101
|
+
};
|
|
6102
|
+
}));
|
|
6103
|
+
const bashHandler = (params) => Effect.gen(function* () {
|
|
6104
|
+
const shell = yield* ToolShell;
|
|
6105
|
+
return shell.stream !== void 0 ? yield* runStreaming(shell.stream, params) : yield* runBuffered(shell, params);
|
|
6117
6106
|
});
|
|
6118
|
-
/**
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
const
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6107
|
+
/** The bash tool: definition + handler, wired the testable (def/exec split) way. */
|
|
6108
|
+
const bashTool = tool_exports.implement(bashDef, bashHandler);
|
|
6109
|
+
const bashPlugin = (0, plugin_exports.define)({
|
|
6110
|
+
id: "codework.tool.bash",
|
|
6111
|
+
kind: "tool",
|
|
6112
|
+
setup: Effect.fn("BashPlugin.setup")(function* (ctx) {
|
|
6113
|
+
const shell = yield* io_exports.SandboxIO.Shell;
|
|
6114
|
+
const mounted = fromSandboxShell.pipe(Layer.provide(Layer.succeed(io_exports.SandboxIO.Shell, shell)));
|
|
6115
|
+
ctx.plugin.tools.add(tool_exports.provide(bashTool, mounted));
|
|
6116
|
+
})
|
|
6128
6117
|
});
|
|
6118
|
+
//#endregion
|
|
6119
|
+
//#region ../harness/src/plugin/builtin.ts
|
|
6129
6120
|
/**
|
|
6130
|
-
*
|
|
6131
|
-
*
|
|
6132
|
-
* Unlocked, like every write here but the one that publishes a generation. Two processes checking
|
|
6133
|
-
* at once both read the whole file and both rewrite it, so one update is lost and a record added
|
|
6134
|
-
* in between can vanish -- and neither matters, because a lost `checkedAt` costs one network call
|
|
6135
|
-
* and a vanished record is a miss a scan repairs.
|
|
6121
|
+
* Every plugin the harness ships, and the selection a caller who passes no `plugins` gets. Tool
|
|
6122
|
+
* contributors come first so the Prompt plugin that indexes them runs after they registered.
|
|
6136
6123
|
*/
|
|
6137
|
-
const
|
|
6138
|
-
const entries = yield* read(root);
|
|
6139
|
-
const existing = entries.get(digest);
|
|
6140
|
-
if (existing === void 0) return;
|
|
6141
|
-
entries.set(digest, {
|
|
6142
|
-
...existing,
|
|
6143
|
-
...fields
|
|
6144
|
-
});
|
|
6145
|
-
yield* write(root, entries).pipe(Effect.ignore);
|
|
6146
|
-
});
|
|
6147
|
-
/** Forget one entry, for a store entry that has been removed. */
|
|
6148
|
-
const drop = Effect.fn("PluginIndex.drop")(function* (root, digest) {
|
|
6149
|
-
const entries = yield* read(root);
|
|
6150
|
-
if (!entries.delete(digest)) return;
|
|
6151
|
-
yield* write(root, entries).pipe(Effect.ignore);
|
|
6152
|
-
});
|
|
6124
|
+
const builtins = Object.freeze([bashPlugin, defaultPromptPlugin]);
|
|
6153
6125
|
//#endregion
|
|
6154
|
-
//#region ../harness/src/
|
|
6155
|
-
/** How long to wait for another holder before giving up. */
|
|
6156
|
-
const TIMEOUT = Duration.minutes(2);
|
|
6157
|
-
/** The holder refreshes the lock's mtime this often; one not refreshed for the timeout is stale. */
|
|
6158
|
-
const HEARTBEAT = Duration.seconds(15);
|
|
6159
|
-
const abandoned = (directory) => Effect.gen(function* () {
|
|
6160
|
-
const info = yield* fileSystem.stat(directory);
|
|
6161
|
-
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
6162
|
-
return Option.exists(info.mtime, (mtime) => now - mtime.getTime() > Duration.toMillis(TIMEOUT));
|
|
6163
|
-
}).pipe(Effect.orElseSucceed(() => false));
|
|
6164
|
-
const heartbeat = (directory) => Effect.clockWith((clock) => clock.currentTimeMillis).pipe(Effect.flatMap((now) => fileSystem.utimes(directory, now, now)), Effect.ignore, Effect.repeat(Schedule.spaced(HEARTBEAT)));
|
|
6126
|
+
//#region ../harness/src/settings/merge.ts
|
|
6165
6127
|
/**
|
|
6166
|
-
*
|
|
6167
|
-
*
|
|
6128
|
+
* A plain string-keyed record: what a settings patch merges, and what a plugin's `options` block
|
|
6129
|
+
* has to be. A Map, a typed array or a class instance is an object to `typeof` and not one of
|
|
6130
|
+
* these — `Object.freeze` alone does not tell them apart, and it throws on some of them.
|
|
6131
|
+
*/
|
|
6132
|
+
const isRecord$1 = (value) => Predicate.isObject(value) && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
6133
|
+
const copy = (value) => {
|
|
6134
|
+
if (Array.isArray(value)) return value.map(copy);
|
|
6135
|
+
return isRecord$1(value) ? merge({}, value) : value;
|
|
6136
|
+
};
|
|
6137
|
+
/** Null/undefined properties are absent, objects merge, and arrays replace. Never mutates inputs. */
|
|
6138
|
+
function merge(base, ...patches) {
|
|
6139
|
+
const result = /* @__PURE__ */ new Map();
|
|
6140
|
+
for (const patch of [base, ...patches]) {
|
|
6141
|
+
if (patch === void 0) continue;
|
|
6142
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
6143
|
+
if (value === null || value === void 0) continue;
|
|
6144
|
+
const previous = result.get(key);
|
|
6145
|
+
result.set(key, isRecord$1(previous) && isRecord$1(value) ? merge(previous, value) : copy(value));
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
return Object.fromEntries(result);
|
|
6149
|
+
}
|
|
6150
|
+
/** Normalize nullable properties before schema validation, without changing array positions. */
|
|
6151
|
+
const normalize = copy;
|
|
6152
|
+
//#endregion
|
|
6153
|
+
//#region ../harness/src/util/module.ts
|
|
6154
|
+
/*!
|
|
6155
|
+
* Adapted from OpenCode packages/util/src/runtime/import.node.ts.
|
|
6168
6156
|
*
|
|
6169
|
-
*
|
|
6170
|
-
* attempt is `uninterruptible` so `mkdir` and recording ownership cannot be split -- an interrupt
|
|
6171
|
-
* between them would leave behind a lock the finalizer does not know to remove.
|
|
6157
|
+
* MIT License
|
|
6172
6158
|
*
|
|
6173
|
-
*
|
|
6159
|
+
* Copyright (c) 2025 opencode
|
|
6160
|
+
*
|
|
6161
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6162
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
6163
|
+
* in the Software without restriction, including without limitation the rights
|
|
6164
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
6165
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
6166
|
+
* furnished to do so, subject to the following conditions:
|
|
6167
|
+
*
|
|
6168
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
6169
|
+
* copies or substantial portions of the Software.
|
|
6170
|
+
*
|
|
6171
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
6172
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
6173
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
6174
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
6175
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
6176
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
6177
|
+
* SOFTWARE.
|
|
6174
6178
|
*/
|
|
6175
|
-
const lock = Effect.fn("PluginLock.acquire")(function* (directory, onTimeout) {
|
|
6176
|
-
const held = yield* Ref.make(false);
|
|
6177
|
-
yield* Effect.acquireRelease(Effect.void, () => Ref.get(held).pipe(Effect.flatMap((owned) => owned ? fileSystem.remove(directory, {
|
|
6178
|
-
recursive: true,
|
|
6179
|
-
force: true
|
|
6180
|
-
}) : Effect.void), Effect.orDie));
|
|
6181
|
-
if (!(yield* fileSystem.makeDirectory(directory).pipe(Effect.andThen(Ref.set(held, true)), Effect.as(true), Effect.catchIf((error) => error.reason._tag === "AlreadyExists", () => abandoned(directory).pipe(Effect.flatMap((stale) => stale ? fileSystem.remove(directory, {
|
|
6182
|
-
recursive: true,
|
|
6183
|
-
force: true
|
|
6184
|
-
}).pipe(Effect.as(false)) : Effect.succeed(false)))), Effect.uninterruptible).pipe(Effect.repeat({
|
|
6185
|
-
schedule: Schedule.spaced("50 millis"),
|
|
6186
|
-
until: (owned) => owned
|
|
6187
|
-
}), Effect.timeoutOrElse({
|
|
6188
|
-
duration: TIMEOUT,
|
|
6189
|
-
orElse: () => Effect.succeed(false)
|
|
6190
|
-
})))) return yield* Effect.fail(onTimeout());
|
|
6191
|
-
yield* Effect.forkScoped(heartbeat(directory));
|
|
6192
|
-
});
|
|
6193
|
-
//#endregion
|
|
6194
|
-
//#region ../harness/src/plugin/store.ts
|
|
6195
|
-
/** Facts about the *package*, and nothing about the plugin inside it. */
|
|
6196
|
-
const Marker = Schema.Struct({
|
|
6197
|
-
spec: Schema.String,
|
|
6198
|
-
name: Schema.String,
|
|
6199
|
-
version: Schema.optional(Schema.String),
|
|
6200
|
-
revision: Schema.optional(Schema.String),
|
|
6201
|
-
/** Relative to the generation directory, so the marker survives the publishing rename. */
|
|
6202
|
-
entrypoint: Schema.String,
|
|
6203
|
-
createdAt: Schema.Finite
|
|
6204
|
-
});
|
|
6205
|
-
const MARKER = ".complete.json";
|
|
6206
|
-
const STAGING = ".staging-";
|
|
6207
|
-
const root = (cache) => hostPath.join(cache, "plugins", "v2");
|
|
6208
6179
|
/**
|
|
6209
|
-
*
|
|
6210
|
-
*
|
|
6211
|
-
*
|
|
6180
|
+
* Packages a plugin must share with the harness instead of using the copy installed beside it.
|
|
6181
|
+
*
|
|
6182
|
+
* A plugin is installed into the store as its own npm tree, so `effect` and the plugin SDK land
|
|
6183
|
+
* there a second time. Two copies of the *same version* are still two module instances, and
|
|
6184
|
+
* Effect Schema's checks do not survive the crossing: a schema built by one copy and evaluated by
|
|
6185
|
+
* the other reports every check as failed, so a tool whose success type is `Schema.Finite` dies
|
|
6186
|
+
* on "Expected a finite number" for `22800`. Nothing about that is visible at registration --
|
|
6187
|
+
* `Schema.isSchema` passes, the JSON Schema is derived correctly, and the tool reaches the model
|
|
6188
|
+
* -- so it surfaces on the first call rather than at boot.
|
|
6189
|
+
*
|
|
6190
|
+
* Resolution is the right place to fix it. A symlink into the store cannot be: a generation is
|
|
6191
|
+
* immutable and shared by every project on the machine, while the harness doing the loading is
|
|
6192
|
+
* whichever one is running now, and two applications embedding the harness need not agree on
|
|
6193
|
+
* where their `effect` lives.
|
|
6212
6194
|
*/
|
|
6213
|
-
const
|
|
6195
|
+
const sharedPackages = /* @__PURE__ */ new Set(["effect", "@codeworksh/plugin"]);
|
|
6196
|
+
/** The package a bare specifier names: `effect/Schema` -> `effect`, `@a/b/c` -> `@a/b`. */
|
|
6197
|
+
function packageOf(specifier) {
|
|
6198
|
+
if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.includes(":")) return "";
|
|
6199
|
+
const segments = specifier.split("/");
|
|
6200
|
+
return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
|
|
6201
|
+
}
|
|
6214
6202
|
/**
|
|
6215
|
-
*
|
|
6216
|
-
*
|
|
6217
|
-
*
|
|
6218
|
-
* Not truncated. The fast path proves a hit by checking that a marker *exists* rather than by
|
|
6219
|
-
* decoding it, and the index is keyed by digest alone, so a truncated digest would let a collision
|
|
6220
|
-
* return the wrong package silently.
|
|
6203
|
+
* Resolve the shared packages as though this module had imported them, for as long as `run`
|
|
6204
|
+
* takes. Anything else resolves exactly as it would have.
|
|
6221
6205
|
*/
|
|
6222
|
-
|
|
6223
|
-
const
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
const
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
});
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
generation: input.generation,
|
|
6248
|
-
directory: input.directory,
|
|
6249
|
-
url: url(hostPath.resolve(input.directory, marker.entrypoint))
|
|
6250
|
-
});
|
|
6251
|
-
/** The newest generation that carries a marker. Unmarked ones are invisible: they never happened. */
|
|
6252
|
-
const active = Effect.fn("PluginStore.active")(function* (directory, key) {
|
|
6253
|
-
for (const generation of (yield* generations(directory)).reverse()) {
|
|
6254
|
-
const where = hostPath.join(directory, String(generation));
|
|
6255
|
-
const marker = yield* readMarker(where);
|
|
6256
|
-
if (Option.isSome(marker)) return entryOf(marker.value, {
|
|
6257
|
-
digest: key,
|
|
6258
|
-
generation,
|
|
6259
|
-
directory: where
|
|
6206
|
+
function withSharedModules(run) {
|
|
6207
|
+
const hook = registerHooks({ resolve(specifier, context, nextResolve) {
|
|
6208
|
+
return sharedPackages.has(packageOf(specifier)) ? nextResolve(specifier, {
|
|
6209
|
+
...context,
|
|
6210
|
+
parentURL: import.meta.url
|
|
6211
|
+
}) : nextResolve(specifier, context);
|
|
6212
|
+
} });
|
|
6213
|
+
return run().finally(() => hook.deregister());
|
|
6214
|
+
}
|
|
6215
|
+
/** Use Node's main loader even when the caller runs inside a bundler or test VM. */
|
|
6216
|
+
async function importModule(specifier) {
|
|
6217
|
+
const imported = await withSharedModules(() => new Script(`import(${JSON.stringify(specifier)})`, { importModuleDynamically: constants.USE_MAIN_CONTEXT_DEFAULT_LOADER }).runInThisContext());
|
|
6218
|
+
if (typeof imported !== "object" || imported === null) return imported;
|
|
6219
|
+
const module = imported;
|
|
6220
|
+
const exports = module["module.exports"];
|
|
6221
|
+
if (exports !== module.default || typeof exports !== "object" && typeof exports !== "function" || exports === null) return imported;
|
|
6222
|
+
return Object.assign({}, module, exports);
|
|
6223
|
+
}
|
|
6224
|
+
/** Node resolution scoped synchronously to the caller. */
|
|
6225
|
+
function resolveModule(specifier, directory, conditions) {
|
|
6226
|
+
const hook = registerHooks({ resolve(target, context, nextResolve) {
|
|
6227
|
+
return nextResolve(target, {
|
|
6228
|
+
...context,
|
|
6229
|
+
parentURL: pathToFileURL(path.join(directory, "package.json")).href,
|
|
6230
|
+
...conditions === void 0 ? {} : { conditions: [.../* @__PURE__ */ new Set([...context.conditions, ...conditions])] }
|
|
6260
6231
|
});
|
|
6232
|
+
} });
|
|
6233
|
+
try {
|
|
6234
|
+
const resolve = (target) => {
|
|
6235
|
+
const resolved = import.meta.resolve(path.isAbsolute(target) ? pathToFileURL(target).href : target);
|
|
6236
|
+
if (resolved.startsWith("file:")) statSync(new URL(resolved));
|
|
6237
|
+
return resolved;
|
|
6238
|
+
};
|
|
6239
|
+
try {
|
|
6240
|
+
return resolve(specifier);
|
|
6241
|
+
} catch (error) {
|
|
6242
|
+
if (path.extname(specifier) || !missing(error)) throw error;
|
|
6243
|
+
for (const extension of [
|
|
6244
|
+
".ts",
|
|
6245
|
+
".tsx",
|
|
6246
|
+
".js",
|
|
6247
|
+
".jsx",
|
|
6248
|
+
".mts",
|
|
6249
|
+
".mjs",
|
|
6250
|
+
".cts",
|
|
6251
|
+
".cjs"
|
|
6252
|
+
]) try {
|
|
6253
|
+
return resolve(specifier + extension);
|
|
6254
|
+
} catch (cause) {
|
|
6255
|
+
if (!missing(cause)) throw cause;
|
|
6256
|
+
}
|
|
6257
|
+
throw error;
|
|
6258
|
+
}
|
|
6259
|
+
} finally {
|
|
6260
|
+
hook.deregister();
|
|
6261
6261
|
}
|
|
6262
|
-
}
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
...entry.revision === void 0 ? {} : { revision: entry.revision },
|
|
6273
|
-
entrypoint: entry.entrypoint,
|
|
6274
|
-
installedAt
|
|
6275
|
-
});
|
|
6262
|
+
}
|
|
6263
|
+
function missing(error) {
|
|
6264
|
+
return error instanceof Error && "code" in error && [
|
|
6265
|
+
"ENOENT",
|
|
6266
|
+
"ENOTDIR",
|
|
6267
|
+
"ERR_MODULE_NOT_FOUND"
|
|
6268
|
+
].includes(String(error.code));
|
|
6269
|
+
}
|
|
6270
|
+
//#endregion
|
|
6271
|
+
//#region ../harness/src/plugin/error.ts
|
|
6276
6272
|
/**
|
|
6277
|
-
*
|
|
6278
|
-
*
|
|
6273
|
+
* The reference as the person wrote it -- the string they will recognise in their settings file,
|
|
6274
|
+
* never the resolved path or the canonical spec. Every plugin failure carries one.
|
|
6275
|
+
*/
|
|
6276
|
+
const Reference = Schema.String;
|
|
6277
|
+
/**
|
|
6278
|
+
* The settings file that declared the entry, when one did.
|
|
6279
6279
|
*
|
|
6280
|
-
*
|
|
6281
|
-
*
|
|
6280
|
+
* Absent for an embedder's own list and for anything the CLI was handed directly -- in both cases
|
|
6281
|
+
* the caller already knows where the string came from. It is the settings path that needs saying,
|
|
6282
|
+
* because several layers accumulate entries and "which file says this" is the question.
|
|
6282
6283
|
*/
|
|
6283
|
-
const
|
|
6284
|
-
const { key, directory } = yield* entryDir(target, cache, from);
|
|
6285
|
-
const base = root(cache);
|
|
6286
|
-
const hinted = (yield* read(base)).get(key);
|
|
6287
|
-
if (hinted !== void 0) {
|
|
6288
|
-
const where = hostPath.join(directory, String(hinted.generation));
|
|
6289
|
-
const marker = yield* readMarker(where);
|
|
6290
|
-
if (Option.isSome(marker)) return entryOf(marker.value, {
|
|
6291
|
-
digest: key,
|
|
6292
|
-
generation: hinted.generation,
|
|
6293
|
-
directory: where
|
|
6294
|
-
});
|
|
6295
|
-
}
|
|
6296
|
-
const scanned = yield* active(directory, key);
|
|
6297
|
-
if (scanned === void 0) return void 0;
|
|
6298
|
-
yield* put(base, key, record(target, scanned, scanned.createdAt));
|
|
6299
|
-
return scanned;
|
|
6300
|
-
}, Effect.mapError((cause) => Schema.is(StoreError)(cause) || Schema.is(InstallError)(cause) ? cause : new StoreError({
|
|
6301
|
-
reason: "plugin-marker-invalid",
|
|
6302
|
-
reference: "",
|
|
6303
|
-
message: "cannot read the plugin store",
|
|
6304
|
-
cause
|
|
6305
|
-
})));
|
|
6284
|
+
const File$1 = Schema.optional(Schema.String);
|
|
6306
6285
|
/**
|
|
6307
|
-
*
|
|
6286
|
+
* A spec that cannot be installed at all.
|
|
6308
6287
|
*
|
|
6309
|
-
* `plugin
|
|
6310
|
-
*
|
|
6311
|
-
*
|
|
6288
|
+
* `plugin-not-found` is a local path with nothing at it; `plugin-not-installed` (the store's) is a
|
|
6289
|
+
* package spec with no entry. The first is a mistake in a settings file, the second is fixed by
|
|
6290
|
+
* `plugin install`, and keeping them apart is most of the value of having slugs.
|
|
6312
6291
|
*/
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6292
|
+
var SourceError = class extends Schema.TaggedError()("PluginSourceError", {
|
|
6293
|
+
reason: Schema.Literals([
|
|
6294
|
+
"plugin-unsupported-source",
|
|
6295
|
+
"plugin-not-found",
|
|
6296
|
+
"plugin-escapes-root"
|
|
6297
|
+
]),
|
|
6298
|
+
reference: Reference,
|
|
6299
|
+
file: File$1,
|
|
6300
|
+
message: Schema.String
|
|
6301
|
+
}) {};
|
|
6302
|
+
/** A spec that is installable, and whose install did not produce a usable package. */
|
|
6303
|
+
var InstallError = class extends Schema.TaggedError()("PluginInstallError", {
|
|
6304
|
+
reason: Schema.Literals([
|
|
6305
|
+
"plugin-resolve-failed",
|
|
6306
|
+
"plugin-fetch-failed",
|
|
6307
|
+
"plugin-no-commit",
|
|
6308
|
+
"plugin-no-entrypoint"
|
|
6309
|
+
]),
|
|
6310
|
+
reference: Reference,
|
|
6311
|
+
file: File$1,
|
|
6312
|
+
message: Schema.String,
|
|
6313
|
+
cause: Schema.optional(Schema.Defect())
|
|
6314
|
+
}) {};
|
|
6318
6315
|
/**
|
|
6319
|
-
*
|
|
6316
|
+
* A module that reached the import and did not come back as a plugin.
|
|
6320
6317
|
*
|
|
6321
|
-
*
|
|
6322
|
-
*
|
|
6318
|
+
* Distinct from {@link InstallError}: the bytes are here and are fine as *bytes*. What failed is
|
|
6319
|
+
* evaluating them, or what they turned out to export -- which is the distinction a caller acts on,
|
|
6320
|
+
* because one is fixed by installing and the other by editing code.
|
|
6323
6321
|
*/
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
const validated = yield* options.validate(fetched);
|
|
6353
|
-
const entrypoint = hostPath.relative(yield* fileSystem.realPath(staging), fetched.entrypoint);
|
|
6354
|
-
if (entrypoint.startsWith("..") || hostPath.isAbsolute(entrypoint)) return yield* new InstallError({
|
|
6355
|
-
reason: "plugin-no-entrypoint",
|
|
6356
|
-
reference: target.spec,
|
|
6357
|
-
message: `entrypoint escapes its installation: ${entrypoint}`
|
|
6358
|
-
});
|
|
6359
|
-
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
6360
|
-
const marker = {
|
|
6361
|
-
spec: target.spec,
|
|
6362
|
-
name: fetched.name,
|
|
6363
|
-
...fetched.version === void 0 ? {} : { version: fetched.version },
|
|
6364
|
-
...fetched.revision === void 0 ? {} : { revision: fetched.revision },
|
|
6365
|
-
entrypoint,
|
|
6366
|
-
createdAt: now
|
|
6367
|
-
};
|
|
6368
|
-
yield* fileSystem.writeFileString(hostPath.join(staging, MARKER), yield* Schema.encodeEffect(Schema.fromJsonString(Marker))(marker));
|
|
6369
|
-
const previous = (yield* generations(directory)).at(-1) ?? 0;
|
|
6370
|
-
const generation = Math.max(now, previous + 1);
|
|
6371
|
-
const published = hostPath.join(directory, String(generation));
|
|
6372
|
-
yield* fileSystem.rename(staging, published);
|
|
6373
|
-
const entry = entryOf(marker, {
|
|
6374
|
-
digest: key,
|
|
6375
|
-
generation,
|
|
6376
|
-
directory: published
|
|
6377
|
-
});
|
|
6378
|
-
yield* put(root(cache), key, record(target, entry, now));
|
|
6379
|
-
yield* collect$1(directory);
|
|
6380
|
-
return {
|
|
6381
|
-
entry,
|
|
6382
|
-
validated
|
|
6383
|
-
};
|
|
6384
|
-
}).pipe(Effect.scoped, Effect.mapError((cause) => Schema.is(InstallError)(cause) || Schema.is(StoreError)(cause) || Schema.is(LoadError)(cause) ? cause : failure$1(target, "plugin-marker-invalid", `cannot write the store entry for ${target.spec}`, cause)));
|
|
6385
|
-
});
|
|
6322
|
+
var LoadError = class extends Schema.TaggedError()("PluginLoadError", {
|
|
6323
|
+
reason: Schema.Literals([
|
|
6324
|
+
"plugin-import-failed",
|
|
6325
|
+
"plugin-missing-dependency",
|
|
6326
|
+
"plugin-not-compiled",
|
|
6327
|
+
"plugin-invalid-definition"
|
|
6328
|
+
]),
|
|
6329
|
+
reference: Reference,
|
|
6330
|
+
file: File$1,
|
|
6331
|
+
message: Schema.String,
|
|
6332
|
+
/** The ID the module declared, when it got far enough to declare one. */
|
|
6333
|
+
id: Schema.optional(Schema.String),
|
|
6334
|
+
cause: Schema.optional(Schema.Defect())
|
|
6335
|
+
}) {};
|
|
6336
|
+
/** The store's own failures: what is filed, what is not, and what could not be claimed. */
|
|
6337
|
+
var StoreError = class extends Schema.TaggedError()("PluginStoreError", {
|
|
6338
|
+
reason: Schema.Literals([
|
|
6339
|
+
"plugin-not-installed",
|
|
6340
|
+
"plugin-lock-timeout",
|
|
6341
|
+
"plugin-marker-invalid",
|
|
6342
|
+
"plugin-index-invalid",
|
|
6343
|
+
"plugin-collect-failed"
|
|
6344
|
+
]),
|
|
6345
|
+
reference: Reference,
|
|
6346
|
+
file: File$1,
|
|
6347
|
+
message: Schema.String,
|
|
6348
|
+
cause: Schema.optional(Schema.Defect())
|
|
6349
|
+
}) {};
|
|
6386
6350
|
/**
|
|
6387
|
-
*
|
|
6351
|
+
* Note which settings file declared the entry a failure came from.
|
|
6388
6352
|
*
|
|
6389
|
-
* The
|
|
6390
|
-
*
|
|
6391
|
-
*
|
|
6353
|
+
* The store and the installer raise their own failures without knowing that, because neither is
|
|
6354
|
+
* told: they are handed a spec, not a settings layer. The loader knows, and this is where it says
|
|
6355
|
+
* so -- leaving an existing answer alone, since a more specific one is already better.
|
|
6392
6356
|
*/
|
|
6393
|
-
const
|
|
6394
|
-
|
|
6395
|
-
const
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6357
|
+
const declaredIn = (error, file) => {
|
|
6358
|
+
if (error.file !== void 0) return error;
|
|
6359
|
+
const shared = {
|
|
6360
|
+
reference: error.reference,
|
|
6361
|
+
message: error.message,
|
|
6362
|
+
file
|
|
6363
|
+
};
|
|
6364
|
+
if (Schema.is(SourceError)(error)) return new SourceError({
|
|
6365
|
+
...shared,
|
|
6366
|
+
reason: error.reason
|
|
6367
|
+
});
|
|
6368
|
+
if (Schema.is(InstallError)(error)) return new InstallError({
|
|
6369
|
+
...shared,
|
|
6370
|
+
reason: error.reason,
|
|
6371
|
+
cause: error.cause
|
|
6372
|
+
});
|
|
6373
|
+
if (Schema.is(LoadError)(error)) return new LoadError({
|
|
6374
|
+
...shared,
|
|
6375
|
+
reason: error.reason,
|
|
6376
|
+
id: error.id,
|
|
6377
|
+
cause: error.cause
|
|
6378
|
+
});
|
|
6379
|
+
if (Schema.is(StoreError)(error)) return new StoreError({
|
|
6380
|
+
...shared,
|
|
6381
|
+
reason: error.reason,
|
|
6382
|
+
cause: error.cause
|
|
6383
|
+
});
|
|
6384
|
+
return error;
|
|
6385
|
+
};
|
|
6386
|
+
//#endregion
|
|
6387
|
+
//#region ../harness/src/util/path.ts
|
|
6419
6388
|
/**
|
|
6420
|
-
*
|
|
6389
|
+
* A directory that crosses into the harness is absolute, and a relative one is a **defect** rather
|
|
6390
|
+
* than an error.
|
|
6421
6391
|
*
|
|
6422
|
-
*
|
|
6423
|
-
*
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
* Is a newer revision available?
|
|
6392
|
+
* A relative path is a disguised `process.cwd()` call: and every consumer
|
|
6393
|
+
* resolves it silently. `path.resolve("x")` anchors to the process directory, npm answers a
|
|
6394
|
+
* relative `cwd` with a `localPrefix` of whatever repository the process is inside (measured, not
|
|
6395
|
+
* assumed), and `Session.link` would write the *server's* directory into a field whose whole
|
|
6396
|
+
* purpose is to be a host path the client declared.
|
|
6428
6397
|
*
|
|
6429
|
-
*
|
|
6430
|
-
*
|
|
6431
|
-
*
|
|
6398
|
+
* For a long-running server the process directory is nobody's project, so the result is a
|
|
6399
|
+
* plausible answer to a question no one asked.
|
|
6400
|
+
* An absolute path is the one spelling none of them can quietly reinterpret.
|
|
6401
|
+
*
|
|
6402
|
+
* A defect rather than a typed error because nothing a user types reaches here. Every reference
|
|
6403
|
+
* goes through `parse`, and every directory comes from `Global.resolve`, a resolved `cwd` or a
|
|
6404
|
+
* session's `hostDir`, all of them already absolute. A relative one means a new call site got it
|
|
6405
|
+
* wrong, and a stack trace names that call site; a typed error would ask every caller to handle a
|
|
6406
|
+
* case none of them can cause.
|
|
6432
6407
|
*/
|
|
6433
|
-
const
|
|
6434
|
-
if (
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
if (options.refresh !== true && record?.checkedAt !== void 0 && now - record.checkedAt < CHECK_TTL && (record.outdated !== true || record.available !== void 0)) return record.outdated === true ? {
|
|
6442
|
-
_tag: "outdated",
|
|
6443
|
-
available: record.available,
|
|
6444
|
-
...entry.revision === void 0 ? {} : { filed: entry.revision }
|
|
6445
|
-
} : {
|
|
6446
|
-
_tag: "current",
|
|
6447
|
-
...entry.revision === void 0 ? {} : { revision: entry.revision }
|
|
6448
|
-
};
|
|
6449
|
-
const available = yield* options.probe(target);
|
|
6450
|
-
if (available === void 0) return {
|
|
6451
|
-
_tag: "current",
|
|
6452
|
-
...entry.revision === void 0 ? {} : { revision: entry.revision }
|
|
6453
|
-
};
|
|
6454
|
-
const outdated = available !== entry.revision;
|
|
6455
|
-
yield* patch(base, key, {
|
|
6456
|
-
checkedAt: now,
|
|
6457
|
-
outdated,
|
|
6458
|
-
available
|
|
6459
|
-
});
|
|
6460
|
-
return outdated ? {
|
|
6461
|
-
_tag: "outdated",
|
|
6462
|
-
available,
|
|
6463
|
-
...entry.revision === void 0 ? {} : { filed: entry.revision }
|
|
6464
|
-
} : {
|
|
6465
|
-
_tag: "current",
|
|
6466
|
-
...entry.revision === void 0 ? {} : { revision: entry.revision }
|
|
6467
|
-
};
|
|
6468
|
-
});
|
|
6408
|
+
const rooted = (directory, role) => {
|
|
6409
|
+
if (hostPath.isAbsolute(directory)) return directory;
|
|
6410
|
+
throw new Error(`${role} must be an absolute path, got ${JSON.stringify(directory)}`);
|
|
6411
|
+
};
|
|
6412
|
+
//#endregion
|
|
6413
|
+
//#region ../harness/src/plugin/npm.ts
|
|
6414
|
+
const Manifest$1 = Schema.Struct({ version: Schema.optional(Schema.String) });
|
|
6415
|
+
const Lockfile = Schema.Struct({ packages: Schema.optional(Schema.Record(Schema.String, Schema.Struct({ resolved: Schema.optional(Schema.String) }))) });
|
|
6469
6416
|
/**
|
|
6470
|
-
*
|
|
6417
|
+
* npm's flattened config: registry, scopes, auth, and cache paths.
|
|
6471
6418
|
*
|
|
6472
|
-
*
|
|
6473
|
-
*
|
|
6419
|
+
* Built in one place, because two call sites deriving it separately is how the cache location
|
|
6420
|
+
* drifts between an install and the staleness check that is supposed to describe it.
|
|
6421
|
+
*
|
|
6422
|
+
* `dir` is where the `.npmrc` chain is read from, and it is the **host directory the caller
|
|
6423
|
+
* belongs to** -- never the staging directory and never the cache. A company repo keeps its
|
|
6424
|
+
* private registry and its scoped token in its own `.npmrc`, so a chain read anywhere else
|
|
6425
|
+
* resolves the plugin against the public registry and fails 404, or worse, installs a public
|
|
6426
|
+
* package of the same name. This is the registry half of auth; the git half is below.
|
|
6427
|
+
*
|
|
6428
|
+
* No `--prefix`. npm pins its local prefix to a `--prefix` on `argv` and stops walking, so
|
|
6429
|
+
* passing one would look up exactly `<dir>/.npmrc` and nothing above it -- wrong for a person
|
|
6430
|
+
* standing in `packages/x` whose token lives at the repository root. Without it npm walks up from
|
|
6431
|
+
* `cwd` to the nearest `package.json` or `node_modules` exactly as `npm` itself would if it were
|
|
6432
|
+
* run there, which is the only rule a user can predict.
|
|
6433
|
+
*
|
|
6434
|
+
* `--cache` is ours and is passed on `argv`, which outranks every `.npmrc`. npm's `flatten`
|
|
6435
|
+
* derives `_cacache`, `_npx` and `_tuf` from that one value, so naming the parent is enough --
|
|
6436
|
+
* and overriding `flat.cache` afterwards would redirect one of the three and leave `_tuf` pointing
|
|
6437
|
+
* at the user's home. `--home` exists to make a run self-contained; anything reached by an ambient
|
|
6438
|
+
* default is a test reading the developer's real machine.
|
|
6439
|
+
*
|
|
6440
|
+
* **Nothing here names an `env`.** npm hands git `opts.env` when one is present and falls back to
|
|
6441
|
+
* `{ ...gitDefaults, ...process.env }` when it is not, so leaving it out is what lets a git source
|
|
6442
|
+
* reach a private remote at all: `SSH_AUTH_SOCK` finds the agent, `HOME` finds the `~/.gitconfig`
|
|
6443
|
+
* that names the credential helper, and `PATH` finds `git`. Setting `env` to anything -- even to
|
|
6444
|
+
* a copy of `process.env` -- would be a standing invitation to prune it later and break private
|
|
6445
|
+
* git silently, months from the change. §15 Q2.
|
|
6474
6446
|
*/
|
|
6475
|
-
const
|
|
6476
|
-
const
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
refresh: true,
|
|
6480
|
-
probe: options.probe,
|
|
6481
|
-
from: options.from
|
|
6482
|
-
}))._tag !== "outdated") return {
|
|
6483
|
-
_tag: "unchanged",
|
|
6484
|
-
entry: current
|
|
6485
|
-
};
|
|
6486
|
-
const added = yield* add(target, cache, {
|
|
6487
|
-
refresh: true,
|
|
6488
|
-
validate: options.validate,
|
|
6489
|
-
from: options.from,
|
|
6490
|
-
...options.runner === void 0 ? {} : { runner: options.runner }
|
|
6491
|
-
});
|
|
6492
|
-
return added.entry.generation === current.generation ? {
|
|
6493
|
-
_tag: "unchanged",
|
|
6494
|
-
entry: current
|
|
6495
|
-
} : {
|
|
6496
|
-
_tag: "updated",
|
|
6497
|
-
entry: added.entry,
|
|
6498
|
-
...added.validated === void 0 ? {} : { validated: added.validated }
|
|
6499
|
-
};
|
|
6500
|
-
});
|
|
6501
|
-
Effect.fn("PluginStore.remove")(function* (target, cache, from) {
|
|
6502
|
-
const { key, directory } = yield* entryDir(target, cache, from);
|
|
6503
|
-
const existed = yield* fileSystem.exists(directory).pipe(Effect.orElseSucceed(() => false));
|
|
6504
|
-
yield* fileSystem.remove(directory, {
|
|
6505
|
-
recursive: true,
|
|
6506
|
-
force: true
|
|
6507
|
-
}).pipe(Effect.ignore);
|
|
6508
|
-
yield* drop(root(cache), key);
|
|
6509
|
-
return existed;
|
|
6447
|
+
const options = (dir, cache) => Effect.suspend(() => {
|
|
6448
|
+
const root = rooted(dir, "the directory a plugin install reads its .npmrc chain from");
|
|
6449
|
+
const store = rooted(cache, "the npm cache");
|
|
6450
|
+
return npmOptions(root, store);
|
|
6510
6451
|
});
|
|
6511
|
-
Effect.
|
|
6512
|
-
const
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6452
|
+
const npmOptions = (dir, cache) => Effect.gen(function* () {
|
|
6453
|
+
const config = yield* Effect.tryPromise(async () => {
|
|
6454
|
+
const { default: Config } = await import("./lib-BCUUrctP.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
6455
|
+
const { definitions, flatten, nerfDarts, shorthands } = (await import("./definitions-XbglYT7Q.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1))).default;
|
|
6456
|
+
const created = new Config({
|
|
6457
|
+
npmPath: fileURLToPath(new URL("..", import.meta.url)),
|
|
6458
|
+
cwd: dir,
|
|
6459
|
+
env: { ...process.env },
|
|
6460
|
+
argv: [
|
|
6461
|
+
process.execPath,
|
|
6462
|
+
process.execPath,
|
|
6463
|
+
"--cache",
|
|
6464
|
+
hostPath.join(cache, "npm")
|
|
6465
|
+
],
|
|
6466
|
+
execPath: process.execPath,
|
|
6467
|
+
platform: process.platform,
|
|
6468
|
+
definitions,
|
|
6469
|
+
flatten,
|
|
6470
|
+
nerfDarts,
|
|
6471
|
+
shorthands,
|
|
6472
|
+
warn: false
|
|
6473
|
+
});
|
|
6474
|
+
await created.load();
|
|
6475
|
+
return created;
|
|
6476
|
+
}).pipe(Effect.catch((cause) => Effect.logWarning(`cannot load npm configuration from ${dir}; npm defaults apply`, cause).pipe(Effect.as(void 0))));
|
|
6477
|
+
if (config === void 0) return { allowGit: "root" };
|
|
6478
|
+
for (const kind of [
|
|
6479
|
+
"project",
|
|
6480
|
+
"user",
|
|
6481
|
+
"global"
|
|
6482
|
+
]) {
|
|
6483
|
+
const loaded = config.data.get(kind);
|
|
6484
|
+
if (loaded?.loadError !== void 0 && loaded.loadError.code !== "ENOENT") yield* Effect.logWarning(`cannot read npm configuration ${loaded.source ?? `for ${kind}`}; npm defaults apply`, loaded.loadError);
|
|
6521
6485
|
}
|
|
6522
|
-
return
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
id: Id,
|
|
6527
|
-
kind: Schema.Literals(Object.keys(plugin_exports.domains)),
|
|
6528
|
-
events: Schema.optional(Schema.Array(Schema.declare((value) => Predicate.hasProperty(value, "type") && Predicate.isString(value.type)))),
|
|
6529
|
-
setup: Schema.declare((value) => Predicate.isFunction(value))
|
|
6530
|
-
});
|
|
6531
|
-
/** Every plugin failure says which file to go and edit, when there is one. */
|
|
6532
|
-
const where = (origin) => origin.file === void 0 ? {} : { file: origin.file };
|
|
6533
|
-
const detail = (cause) => cause instanceof Error ? cause.message : typeof cause === "string" ? cause : String(cause);
|
|
6534
|
-
/** A reference that does not name anything loadable. */
|
|
6535
|
-
const sourceFailure = (origin, cause) => Schema.is(SourceError)(cause) ? cause : new SourceError({
|
|
6536
|
-
reason: "plugin-not-found",
|
|
6537
|
-
reference: origin.reference,
|
|
6538
|
-
...where(origin),
|
|
6539
|
-
message: detail(cause)
|
|
6486
|
+
return {
|
|
6487
|
+
...config.flat,
|
|
6488
|
+
allowGit: "root"
|
|
6489
|
+
};
|
|
6540
6490
|
});
|
|
6541
|
-
|
|
6542
|
-
|
|
6491
|
+
const PUBLIC_REGISTRY = "https://registry.npmjs.org/";
|
|
6492
|
+
/** Stable, credential-free registry identity used to address registry artifacts in the store. */
|
|
6493
|
+
const registry = Effect.fn("PluginNpm.registry")(function* (target, cache, from) {
|
|
6494
|
+
const flat = yield* options(from, cache);
|
|
6495
|
+
const scope = target.name.startsWith("@") ? target.name.slice(0, target.name.indexOf("/")) : void 0;
|
|
6496
|
+
const configured = (scope === void 0 ? void 0 : flat[`${scope}:registry`]) ?? flat.registry ?? PUBLIC_REGISTRY;
|
|
6497
|
+
if (typeof configured !== "string") return yield* new InstallError({
|
|
6498
|
+
reason: "plugin-resolve-failed",
|
|
6499
|
+
reference: target.spec,
|
|
6500
|
+
message: `npm registry for ${target.name} is not a URL`
|
|
6501
|
+
});
|
|
6502
|
+
return yield* Effect.try({
|
|
6503
|
+
try: () => {
|
|
6504
|
+
const value = new URL(configured);
|
|
6505
|
+
value.username = "";
|
|
6506
|
+
value.password = "";
|
|
6507
|
+
value.hash = "";
|
|
6508
|
+
return value.href;
|
|
6509
|
+
},
|
|
6510
|
+
catch: (cause) => new InstallError({
|
|
6511
|
+
reason: "plugin-resolve-failed",
|
|
6512
|
+
reference: target.spec,
|
|
6513
|
+
message: `npm registry for ${target.name} is not a valid URL: ${configured}`,
|
|
6514
|
+
cause
|
|
6515
|
+
})
|
|
6516
|
+
});
|
|
6517
|
+
});
|
|
6518
|
+
/** A 40- or 64-hex commit out of the `resolved` URL arborist writes into the lockfile. */
|
|
6519
|
+
const commitIn = (resolved) => resolved?.match(/#([a-f0-9]{40}|[a-f0-9]{64})(?=::|$)/i)?.[1];
|
|
6543
6520
|
/**
|
|
6544
|
-
*
|
|
6545
|
-
*
|
|
6546
|
-
* Two of these get their own reason, because the remedy differs and that is the whole value of a
|
|
6547
|
-
* slug. A missing dependency means the plugin is fine and its `node_modules` is not, which a
|
|
6548
|
-
* person fixes in the package rather than in their settings. An uncompiled one means the plugin
|
|
6549
|
-
* shipped its sources: Node refuses to strip types under `node_modules`, and installing a plugin
|
|
6550
|
-
* runs no lifecycle scripts, so a `prepare` build never happens either -- the author has to ship
|
|
6551
|
-
* the build output, and nothing the user can do to their settings will help.
|
|
6521
|
+
* The commit a git install actually landed on, read back from the lockfile arborist wrote.
|
|
6552
6522
|
*
|
|
6553
|
-
*
|
|
6554
|
-
* Node's documented identifier and exact, where the wording is free to change under us. Only an
|
|
6555
|
-
* installed plugin can reach it -- a local path lives outside `node_modules`, where Node strips
|
|
6556
|
-
* types happily, which is what keeps the single-file dev workflow working.
|
|
6523
|
+
* Both spellings are tried because which one exists depends on whether the tree was saved.
|
|
6557
6524
|
*/
|
|
6558
|
-
const
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
});
|
|
6565
|
-
/** A module that imported and is not a plugin. */
|
|
6566
|
-
const definitionFailure = (origin, cause, id) => new LoadError({
|
|
6567
|
-
reason: "plugin-invalid-definition",
|
|
6568
|
-
reference: origin.reference,
|
|
6569
|
-
...where(origin),
|
|
6570
|
-
message: detail(cause),
|
|
6571
|
-
...id === void 0 ? {} : { id },
|
|
6572
|
-
cause
|
|
6573
|
-
});
|
|
6574
|
-
const validate = Effect.fn("PluginLoader.validate")(function* (input, origin, builtin = false) {
|
|
6575
|
-
yield* Schema.decodeUnknownEffect(Definition)(input).pipe(Effect.mapError((cause) => definitionFailure(origin, cause)));
|
|
6576
|
-
const plugin = input;
|
|
6577
|
-
if (!builtin && plugin.id.startsWith("codework.")) return yield* definitionFailure(origin, /* @__PURE__ */ new Error("codework namespace is reserved for builtins"), plugin.id);
|
|
6578
|
-
return plugin;
|
|
6579
|
-
});
|
|
6580
|
-
const Manifest = Schema.Struct({
|
|
6581
|
-
name: Schema.optional(Schema.String),
|
|
6582
|
-
exports: Schema.optional(Schema.Unknown)
|
|
6583
|
-
});
|
|
6584
|
-
const localUrl = Effect.fn("PluginLoader.localUrl")(function* (location, origin) {
|
|
6585
|
-
const escapes = (message) => new SourceError({
|
|
6586
|
-
reason: "plugin-escapes-root",
|
|
6587
|
-
reference: origin.reference,
|
|
6588
|
-
...where(origin),
|
|
6589
|
-
message
|
|
6590
|
-
});
|
|
6591
|
-
if ((yield* fileSystem.stat(location)).type !== "Directory") return { url: yield* Effect.try(() => resolveModule(location, hostPath.dirname(location))) };
|
|
6592
|
-
const manifestPath = hostPath.join(location, "package.json");
|
|
6593
|
-
if (!(yield* fileSystem.exists(manifestPath))) return { url: yield* Effect.try(() => resolveModule("./index", location)) };
|
|
6594
|
-
const manifest = yield* fileSystem.readFileString(manifestPath).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Manifest))));
|
|
6595
|
-
if (manifest.exports !== void 0) {
|
|
6596
|
-
const name = manifest.name;
|
|
6597
|
-
if (!name) return yield* escapes("a local package with exports must declare its name");
|
|
6598
|
-
const url = yield* Effect.try(() => resolveModule(name, location));
|
|
6599
|
-
const resolved = yield* fileSystem.realPath(fileURLToPath(url));
|
|
6600
|
-
if (hostPath.relative(yield* fileSystem.realPath(location), resolved).startsWith("..")) return yield* escapes("package export escapes its root");
|
|
6601
|
-
return {
|
|
6602
|
-
url: pathToFileURL(resolved).href,
|
|
6603
|
-
name
|
|
6604
|
-
};
|
|
6525
|
+
const commit = Effect.fn("PluginNpm.commit")(function* (root, name) {
|
|
6526
|
+
for (const file of [hostPath.join(root, "package-lock.json"), hostPath.join(root, "node_modules", ".package-lock.json")]) {
|
|
6527
|
+
const parsed = yield* fileSystem.readFileString(file).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Lockfile))), Effect.option);
|
|
6528
|
+
if (Option.isNone(parsed)) continue;
|
|
6529
|
+
const found = commitIn(parsed.value.packages?.[`node_modules/${name}`]?.resolved);
|
|
6530
|
+
if (found !== void 0) return found;
|
|
6605
6531
|
}
|
|
6606
|
-
const url = yield* Effect.try(() => resolveModule(createRequire(pathToFileURL(manifestPath)).resolve(location), location));
|
|
6607
|
-
return manifest.name === void 0 ? { url } : {
|
|
6608
|
-
url,
|
|
6609
|
-
name: manifest.name
|
|
6610
|
-
};
|
|
6611
6532
|
});
|
|
6533
|
+
/** Node error codes that mean "nothing here", as opposed to "this package is broken". */
|
|
6534
|
+
const notFound = /* @__PURE__ */ new Set([
|
|
6535
|
+
"ENOENT",
|
|
6536
|
+
"ENOTDIR",
|
|
6537
|
+
"MODULE_NOT_FOUND",
|
|
6538
|
+
"ERR_MODULE_NOT_FOUND",
|
|
6539
|
+
"ERR_PACKAGE_PATH_NOT_EXPORTED",
|
|
6540
|
+
"ERR_UNSUPPORTED_DIR_IMPORT"
|
|
6541
|
+
]);
|
|
6612
6542
|
/**
|
|
6613
|
-
*
|
|
6543
|
+
* The module to import out of an installed package.
|
|
6614
6544
|
*
|
|
6615
|
-
*
|
|
6616
|
-
*
|
|
6617
|
-
*
|
|
6618
|
-
*
|
|
6545
|
+
* Ordered subpaths through the package's own `exports`, so a package that publishes a dedicated
|
|
6546
|
+
* `./plugin` entry is honoured and one that does not falls back to its main.
|
|
6547
|
+
*
|
|
6548
|
+
* Only "not found" is tolerated. Swallowing every error would turn a package that is broken --
|
|
6549
|
+
* a syntax error in its manifest, a permission problem -- into "no entrypoint", which is the
|
|
6550
|
+
* wrong diagnosis in the one place a person needs the right one.
|
|
6619
6551
|
*/
|
|
6620
|
-
const
|
|
6621
|
-
const
|
|
6622
|
-
|
|
6623
|
-
|
|
6552
|
+
const entrypoint = (directory, name) => Effect.gen(function* () {
|
|
6553
|
+
for (const subpath of ["", "plugin"]) {
|
|
6554
|
+
const specifier = name === void 0 ? hostPath.join(directory, subpath === "" ? "index" : subpath) : hostPath.join(name, subpath);
|
|
6555
|
+
const resolved = yield* Effect.try(() => resolveModule(specifier, directory)).pipe(Effect.asSome, Effect.catchIf((error) => notFound.has(String(error.cause?.code)), () => Effect.succeed(Option.none())), Effect.mapError((error) => new InstallError({
|
|
6556
|
+
reason: "plugin-no-entrypoint",
|
|
6557
|
+
reference: name ?? directory,
|
|
6558
|
+
message: `cannot resolve an entrypoint in ${directory}`,
|
|
6559
|
+
cause: error
|
|
6560
|
+
})));
|
|
6561
|
+
if (Option.isSome(resolved)) return fileURLToPath(resolved.value);
|
|
6562
|
+
}
|
|
6563
|
+
return yield* new InstallError({
|
|
6564
|
+
reason: "plugin-no-entrypoint",
|
|
6565
|
+
reference: name ?? directory,
|
|
6566
|
+
message: `no plugin entrypoint in ${directory}`
|
|
6624
6567
|
});
|
|
6625
|
-
return {
|
|
6626
|
-
url: added.entry.url,
|
|
6627
|
-
...added.entry.version === void 0 ? {} : { version: added.entry.version },
|
|
6628
|
-
generation: added.entry.generation,
|
|
6629
|
-
...added.validated === void 0 ? {} : { plugin: added.validated }
|
|
6630
|
-
};
|
|
6631
6568
|
});
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
};
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6569
|
+
const reify = Effect.fn("PluginNpm.reify")(function* ({ target, into, cache, from }) {
|
|
6570
|
+
const flat = yield* options(from, cache);
|
|
6571
|
+
const { Arborist } = yield* Effect.promise(() => import("./lib-CNtGmPS7.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1)));
|
|
6572
|
+
const settings = {
|
|
6573
|
+
...flat,
|
|
6574
|
+
...target.mutable ? {
|
|
6575
|
+
preferOnline: true,
|
|
6576
|
+
noGitRevCache: true
|
|
6577
|
+
} : {},
|
|
6578
|
+
audit: false
|
|
6579
|
+
};
|
|
6580
|
+
const arborist = new Arborist({
|
|
6581
|
+
...settings,
|
|
6582
|
+
path: into,
|
|
6583
|
+
binLinks: true,
|
|
6584
|
+
progress: false,
|
|
6585
|
+
savePrefix: "",
|
|
6586
|
+
ignoreScripts: true
|
|
6587
|
+
});
|
|
6588
|
+
return yield* Effect.tryPromise({
|
|
6589
|
+
try: () => arborist.reify({
|
|
6590
|
+
...settings,
|
|
6591
|
+
add: [target.spec],
|
|
6592
|
+
update: target.mutable,
|
|
6593
|
+
save: true,
|
|
6594
|
+
saveType: "prod"
|
|
6595
|
+
}),
|
|
6596
|
+
catch: (cause) => new InstallError({
|
|
6597
|
+
reason: "plugin-fetch-failed",
|
|
6598
|
+
reference: target.spec,
|
|
6599
|
+
message: `cannot install ${target.spec}`,
|
|
6600
|
+
cause
|
|
6601
|
+
})
|
|
6650
6602
|
});
|
|
6651
|
-
return yield* validate(Predicate.isObject(module) && "default" in module ? module.default : void 0, origin);
|
|
6652
6603
|
});
|
|
6653
6604
|
/**
|
|
6654
|
-
*
|
|
6605
|
+
* Fill `into` with the package `target` names, and report what landed there.
|
|
6655
6606
|
*
|
|
6656
|
-
*
|
|
6657
|
-
*
|
|
6658
|
-
*/
|
|
6659
|
-
const definition$1 = (entrypoint, reference) => imported(url(entrypoint), {
|
|
6660
|
-
index: 0,
|
|
6661
|
-
reference
|
|
6662
|
-
});
|
|
6663
|
-
/**
|
|
6664
|
-
* Install and import one reference, and report what it turned out to be, without selecting it.
|
|
6665
|
-
* `codework plugin add` uses this to prove a package really is a plugin before it writes the
|
|
6666
|
-
* reference into a settings file: the failure a user gets is the one they can act on — a bad
|
|
6667
|
-
* spec, an install that did not resolve, a module that exports no plugin — rather than a broken
|
|
6668
|
-
* configuration that only fails at the next run.
|
|
6607
|
+
* A local target downloads nothing: the files on disk are already the answer, and a local plugin
|
|
6608
|
+
* is never copied into the store.
|
|
6669
6609
|
*/
|
|
6670
|
-
const
|
|
6671
|
-
const
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6610
|
+
const download = Effect.fn("PluginNpm.download")(function* (input) {
|
|
6611
|
+
const { target, into, cache, from, runner = reify } = input;
|
|
6612
|
+
if (target.kind === "local") return {
|
|
6613
|
+
directory: target.path,
|
|
6614
|
+
name: hostPath.basename(target.path),
|
|
6615
|
+
entrypoint: yield* entrypoint(target.path)
|
|
6675
6616
|
};
|
|
6676
|
-
const
|
|
6677
|
-
|
|
6617
|
+
const root = yield* fileSystem.realPath(into).pipe(Effect.mapError((cause) => new InstallError({
|
|
6618
|
+
reason: "plugin-fetch-failed",
|
|
6619
|
+
reference: target.spec,
|
|
6620
|
+
message: `cannot resolve the staging directory ${into}`,
|
|
6621
|
+
cause
|
|
6622
|
+
})));
|
|
6623
|
+
const node = (yield* runner({
|
|
6624
|
+
target,
|
|
6625
|
+
into: root,
|
|
6626
|
+
cache,
|
|
6627
|
+
from
|
|
6628
|
+
})).edgesOut.values().next().value?.to;
|
|
6629
|
+
if (node === void 0) return yield* new InstallError({
|
|
6630
|
+
reason: "plugin-fetch-failed",
|
|
6631
|
+
reference: target.spec,
|
|
6632
|
+
message: `${target.spec} installed nothing`
|
|
6633
|
+
});
|
|
6634
|
+
const manifest = yield* fileSystem.readFileString(hostPath.join(node.path, "package.json")).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Manifest$1))), Effect.option);
|
|
6635
|
+
const version = Option.isNone(manifest) ? void 0 : manifest.value.version;
|
|
6636
|
+
const revision = target.kind === "registry" ? version : (yield* commit(root, node.name)) ?? (yield* noCommit(target.spec));
|
|
6678
6637
|
return {
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
...
|
|
6638
|
+
directory: node.path,
|
|
6639
|
+
name: node.name,
|
|
6640
|
+
...version === void 0 ? {} : { version },
|
|
6641
|
+
...revision === void 0 ? {} : { revision },
|
|
6642
|
+
entrypoint: yield* entrypoint(node.path, node.name)
|
|
6682
6643
|
};
|
|
6683
6644
|
});
|
|
6684
|
-
const
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6645
|
+
const noCommit = (spec) => new InstallError({
|
|
6646
|
+
reason: "plugin-no-commit",
|
|
6647
|
+
reference: spec,
|
|
6648
|
+
message: `cannot resolve a commit for ${spec}`
|
|
6649
|
+
});
|
|
6650
|
+
/**
|
|
6651
|
+
* What a mutable target points at right now, without installing it.
|
|
6652
|
+
*
|
|
6653
|
+
* `preferOnline` and `noGitRevCache` are the whole point: without them pacote answers from its own
|
|
6654
|
+
* metadata cache, and a staleness check that reads a cache is not a staleness check. An immutable
|
|
6655
|
+
* target skips the call entirely -- a version and a SHA cannot move.
|
|
6656
|
+
*/
|
|
6657
|
+
const probe = Effect.fn("PluginNpm.probe")(function* (target, cache, from) {
|
|
6658
|
+
if (target.kind === "local" || !target.mutable) return void 0;
|
|
6659
|
+
const opts = {
|
|
6660
|
+
...yield* options(from, cache),
|
|
6661
|
+
preferOnline: true,
|
|
6662
|
+
noGitRevCache: true,
|
|
6663
|
+
_isRoot: true
|
|
6693
6664
|
};
|
|
6665
|
+
const { default: pacote } = yield* Effect.promise(() => import("./lib-DHoZNIet.mjs").then((n) => /* @__PURE__ */ __toESM(n.t(), 1)));
|
|
6666
|
+
return yield* Effect.tryPromise({
|
|
6667
|
+
try: async () => target.kind === "registry" ? (await pacote.manifest(target.spec, opts)).version : commitIn(await pacote.resolve(target.spec, opts)),
|
|
6668
|
+
catch: (cause) => new InstallError({
|
|
6669
|
+
reason: "plugin-resolve-failed",
|
|
6670
|
+
reference: target.spec,
|
|
6671
|
+
message: `cannot reach ${target.spec}`,
|
|
6672
|
+
cause
|
|
6673
|
+
})
|
|
6674
|
+
});
|
|
6694
6675
|
});
|
|
6695
|
-
/**
|
|
6696
|
-
|
|
6697
|
-
*
|
|
6698
|
-
* Two references naming the same module compare equal through it: `@acme/x@1.2.0` and `@acme/x`
|
|
6699
|
-
* are one package, and `./plugins/x.ts` is the file it resolves to from the directory that
|
|
6700
|
-
* declared it. Pure -- nothing is installed, imported or read.
|
|
6701
|
-
*/
|
|
6702
|
-
const canonical = (reference, hostDir) => Effect.map(parse$2(reference, hostDir), canonical$1);
|
|
6676
|
+
/** A `file:` URL for an entrypoint, which is what the module loader takes. */
|
|
6677
|
+
const url = (entrypoint) => pathToFileURL(entrypoint).href;
|
|
6703
6678
|
//#endregion
|
|
6704
|
-
//#region ../harness/src/plugin/
|
|
6679
|
+
//#region ../harness/src/plugin/source.ts
|
|
6680
|
+
/** A committish that names one artifact rather than a moving branch: an unabbreviated SHA. */
|
|
6681
|
+
const isCommit = (committish) => typeof committish === "string" && /^[a-f0-9]{40}$|^[a-f0-9]{64}$/i.test(committish);
|
|
6705
6682
|
/**
|
|
6706
|
-
* A
|
|
6707
|
-
* `
|
|
6708
|
-
*
|
|
6709
|
-
*
|
|
6710
|
-
* e.g
|
|
6711
|
-
* "acme/tool@1.2.0" → module (load me)
|
|
6712
|
-
* define({id, kind, setup, ...}) → module (I AM the definition — embedder code only)
|
|
6713
|
-
* { plugin: "x", enabled: false } → config (don't load — adjust something already loaded)
|
|
6714
|
-
*
|
|
6715
|
-
* setup present → definition, always (even with a stray plugin: key)
|
|
6716
|
-
* setup absent + plugin:/package: present → config patch
|
|
6717
|
-
* setup absent + neither key → malformed
|
|
6683
|
+
* A short, readable name for a git source, used to make a store path recognisable to a human
|
|
6684
|
+
* reading `ls`. It is never an identity: two remotes can slug alike, and the digest is what
|
|
6685
|
+
* separates them.
|
|
6718
6686
|
*/
|
|
6719
|
-
const
|
|
6687
|
+
const slug = (spec) => {
|
|
6688
|
+
const withoutCommittish = spec.split("#")[0] ?? spec;
|
|
6689
|
+
return (() => {
|
|
6690
|
+
try {
|
|
6691
|
+
return decodeURIComponent(withoutCommittish);
|
|
6692
|
+
} catch {
|
|
6693
|
+
return withoutCommittish;
|
|
6694
|
+
}
|
|
6695
|
+
})().replace(/\.git$/i, "").split(/[/:\\]/).at(-1)?.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "repository";
|
|
6696
|
+
};
|
|
6697
|
+
/** Whether a reference names a place on disk rather than something to fetch. */
|
|
6698
|
+
const isPath = (reference) => reference.startsWith("file:") || reference.startsWith("./") || reference.startsWith("../") || hostPath.isAbsolute(reference);
|
|
6720
6699
|
/**
|
|
6721
|
-
*
|
|
6722
|
-
*
|
|
6723
|
-
* Runs at boot, at `reload`, and at any exchange that finds a reference it cannot satisfy. It
|
|
6724
|
-
* deliberately knows nothing about `enabled`, options or order -- those are data over modules that
|
|
6725
|
-
* are already loaded, they cost a walk of a short array, and they belong to every exchange.
|
|
6700
|
+
* `from` is the directory a relative spec resolves against, and the caller picks it:
|
|
6726
6701
|
*
|
|
6727
|
-
*
|
|
6702
|
+
* - a **plugin entry** anchors to the directory holding the file that declared it --
|
|
6703
|
+
* `<root>/.codework/` for a project entry, `<home>/` for a user one, the named directory for
|
|
6704
|
+
* `--user-config-dir`. The rule is about the file, not the project.
|
|
6705
|
+
* - a **CLI argument** anchors to the process's startup directory, because that is where the
|
|
6706
|
+
* person typed it.
|
|
6728
6707
|
*/
|
|
6729
|
-
const
|
|
6730
|
-
const
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
};
|
|
6742
|
-
/** Source metadata exists for diagnostics; a silent ID replacement is where it earns that. */
|
|
6743
|
-
const note = (plugin, source) => plugins.has(plugin.id) ? Effect.logDebug(`plugin ${plugin.id} redefined by ${source}; the earlier definition is discarded`) : Effect.void;
|
|
6744
|
-
for (const builtin of options.builtins) remember(yield* validate(builtin, {
|
|
6745
|
-
index: -1,
|
|
6746
|
-
reference: builtin.id
|
|
6747
|
-
}, true), []);
|
|
6748
|
-
for (const [index, reference] of references.entries()) {
|
|
6749
|
-
if (isPatch(reference)) continue;
|
|
6750
|
-
const declared = typeof reference === "string" ? reference : Predicate.hasProperty(reference, "id") ? reference.id : void 0;
|
|
6751
|
-
const named = typeof declared === "string" ? declared : `<plugin object #${index}>`;
|
|
6752
|
-
const origin = {
|
|
6753
|
-
index,
|
|
6754
|
-
reference: named,
|
|
6755
|
-
file: options.declared?.get(named)
|
|
6708
|
+
const parse$2 = (reference, from) => Effect.gen(function* () {
|
|
6709
|
+
const fail = (reason, message) => new SourceError({
|
|
6710
|
+
reason,
|
|
6711
|
+
reference,
|
|
6712
|
+
message
|
|
6713
|
+
});
|
|
6714
|
+
rooted(from, "the directory a plugin reference anchors to");
|
|
6715
|
+
if (reference.startsWith("file:")) {
|
|
6716
|
+
if (!reference.startsWith("file:///")) return yield* fail("plugin-unsupported-source", `not an absolute file URL: ${reference}`);
|
|
6717
|
+
return {
|
|
6718
|
+
kind: "local",
|
|
6719
|
+
path: fileURLToPath(reference)
|
|
6756
6720
|
};
|
|
6757
|
-
if (typeof reference !== "string") {
|
|
6758
|
-
if (typeof declared === "string" && plugins.get(declared) === reference) continue;
|
|
6759
|
-
const plugin = yield* validate(reference, origin);
|
|
6760
|
-
yield* note(plugin, "a supplied object");
|
|
6761
|
-
remember(plugin, []);
|
|
6762
|
-
continue;
|
|
6763
|
-
}
|
|
6764
|
-
const target = yield* parse$2(reference, options.hostDir);
|
|
6765
|
-
const key = target.kind === "local" ? target.path : target.spec;
|
|
6766
|
-
const already = seen.get(key);
|
|
6767
|
-
if (already !== void 0) {
|
|
6768
|
-
aliases.set(reference, already);
|
|
6769
|
-
continue;
|
|
6770
|
-
}
|
|
6771
|
-
const definition = yield* load$3(target, origin, options);
|
|
6772
|
-
yield* note(definition.plugin, definition.version === void 0 ? key : `${key}@${definition.version}`);
|
|
6773
|
-
const names = target.kind === "local" ? [target.path, ...definition.name === void 0 ? [] : [definition.name]] : [canonical$1(target), target.spec];
|
|
6774
|
-
remember(definition.plugin, [reference, ...names], {
|
|
6775
|
-
...definition.version === void 0 ? {} : { version: definition.version },
|
|
6776
|
-
origin: {
|
|
6777
|
-
reference,
|
|
6778
|
-
...definition.generation === void 0 ? {} : { generation: definition.generation },
|
|
6779
|
-
...origin.file === void 0 ? {} : { file: origin.file }
|
|
6780
|
-
}
|
|
6781
|
-
});
|
|
6782
|
-
seen.set(key, definition.plugin.id);
|
|
6783
6721
|
}
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
origins
|
|
6722
|
+
const expanded = expandTilde(reference, hostPath);
|
|
6723
|
+
if (isPath(expanded)) return {
|
|
6724
|
+
kind: "local",
|
|
6725
|
+
path: hostPath.resolve(from, expanded)
|
|
6789
6726
|
};
|
|
6727
|
+
const parsed = yield* Effect.try({
|
|
6728
|
+
try: () => npa(reference),
|
|
6729
|
+
catch: () => fail("plugin-unsupported-source", `cannot parse plugin source: ${reference}`)
|
|
6730
|
+
});
|
|
6731
|
+
if (parsed.type === "git") {
|
|
6732
|
+
const { gitSubdir } = parsed;
|
|
6733
|
+
return {
|
|
6734
|
+
kind: "git",
|
|
6735
|
+
slug: slug(reference),
|
|
6736
|
+
spec: reference,
|
|
6737
|
+
...parsed.gitCommittish == null ? {} : { committish: parsed.gitCommittish },
|
|
6738
|
+
...gitSubdir == null ? {} : { subdir: gitSubdir },
|
|
6739
|
+
mutable: !isCommit(parsed.gitCommittish)
|
|
6740
|
+
};
|
|
6741
|
+
}
|
|
6742
|
+
if (parsed.name !== null && [
|
|
6743
|
+
"version",
|
|
6744
|
+
"range",
|
|
6745
|
+
"tag"
|
|
6746
|
+
].includes(parsed.type)) {
|
|
6747
|
+
const omitted = parsed.raw === parsed.name || parsed.raw === `${parsed.name}@`;
|
|
6748
|
+
return {
|
|
6749
|
+
kind: "registry",
|
|
6750
|
+
name: parsed.name,
|
|
6751
|
+
spec: `${parsed.name}@${omitted ? "latest" : parsed.rawSpec}`,
|
|
6752
|
+
mutable: parsed.type !== "version"
|
|
6753
|
+
};
|
|
6754
|
+
}
|
|
6755
|
+
return yield* fail("plugin-unsupported-source", `unsupported plugin source: ${reference}`);
|
|
6790
6756
|
});
|
|
6791
6757
|
/**
|
|
6792
|
-
*
|
|
6793
|
-
*
|
|
6794
|
-
* An array entry is a **module** — a definition object, a path, a `file:` URL, or a package
|
|
6795
|
-
* spec. It takes the position it is written at, so naming a module again moves it.
|
|
6796
|
-
*
|
|
6797
|
-
* An object entry is **configuration** for a plugin an earlier entry (or the built-in list)
|
|
6798
|
-
* already selected, addressed by its ID (`plugin`) or the module string (`package`) that selected
|
|
6799
|
-
* it. It never loads, installs or reorders anything, and a name matching nothing in the selection
|
|
6800
|
-
* is ignored rather than failing the boot — a typo costs a debug line, never a fetch.
|
|
6758
|
+
* What two references must share to be the same plugin, without installing either.
|
|
6801
6759
|
*
|
|
6802
|
-
*
|
|
6803
|
-
*
|
|
6760
|
+
* A local path is the file it resolves to; a package is its name, so `@acme/x@1` and `@acme/x@2`
|
|
6761
|
+
* are one package at two versions. Pure -- nothing is installed, imported or read.
|
|
6804
6762
|
*/
|
|
6805
|
-
const
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
index,
|
|
6811
|
-
options: operations.get(id)?.options ?? {}
|
|
6812
|
-
});
|
|
6813
|
-
for (const [index, reference] of references.entries()) {
|
|
6814
|
-
if (isPatch(reference)) {
|
|
6815
|
-
const named = reference.plugin ?? reference.package;
|
|
6816
|
-
const origin = {
|
|
6817
|
-
index,
|
|
6818
|
-
reference: typeof named === "string" ? named : `<plugin entry #${index}>`
|
|
6819
|
-
};
|
|
6820
|
-
const invalid = reference.plugin !== void 0 && reference.package !== void 0 ? "names both `plugin` and `package`" : typeof named !== "string" || named.length === 0 ? "names neither `plugin` nor `package`" : reference.enabled !== void 0 && typeof reference.enabled !== "boolean" ? "`enabled` is not a boolean" : reference.options !== void 0 && !isRecord$1(reference.options) ? "`options` is not a plain object" : void 0;
|
|
6821
|
-
if (invalid !== void 0) return yield* definitionFailure(origin, /* @__PURE__ */ new Error(`plugin entry ${invalid}`));
|
|
6822
|
-
const id = reference.plugin === void 0 ? pool.aliases.get(reference.package) : reference.plugin;
|
|
6823
|
-
const operation = id === void 0 ? void 0 : operations.get(id);
|
|
6824
|
-
if (id === void 0 || operation === void 0) {
|
|
6825
|
-
yield* Effect.logDebug(`plugin configuration ignored: nothing selected under ${named}`);
|
|
6826
|
-
continue;
|
|
6827
|
-
}
|
|
6828
|
-
operations.set(id, {
|
|
6829
|
-
enabled: reference.enabled ?? operation.enabled,
|
|
6830
|
-
index: operation.index,
|
|
6831
|
-
options: reference.options ?? operation.options
|
|
6832
|
-
});
|
|
6833
|
-
continue;
|
|
6834
|
-
}
|
|
6835
|
-
if (typeof reference !== "string") {
|
|
6836
|
-
const declared = Predicate.hasProperty(reference, "id") ? reference.id : void 0;
|
|
6837
|
-
if (typeof declared === "string" && pool.plugins.has(declared)) choose(declared, index);
|
|
6838
|
-
continue;
|
|
6839
|
-
}
|
|
6840
|
-
const id = pool.aliases.get(reference);
|
|
6841
|
-
if (id === void 0) missing.push(reference);
|
|
6842
|
-
else choose(id, index);
|
|
6843
|
-
}
|
|
6844
|
-
const selection = [];
|
|
6845
|
-
for (const [id, operation] of operations) {
|
|
6846
|
-
if (!operation.enabled) continue;
|
|
6847
|
-
const plugin = pool.plugins.get(id);
|
|
6848
|
-
if (plugin !== void 0) selection.push({
|
|
6849
|
-
plugin,
|
|
6850
|
-
options: Object.freeze(operation.options),
|
|
6851
|
-
index: operation.index
|
|
6852
|
-
});
|
|
6763
|
+
const canonical$1 = (target) => {
|
|
6764
|
+
switch (target.kind) {
|
|
6765
|
+
case "local": return target.path;
|
|
6766
|
+
case "registry": return target.name;
|
|
6767
|
+
case "git": return target.spec;
|
|
6853
6768
|
}
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6769
|
+
};
|
|
6770
|
+
//#endregion
|
|
6771
|
+
//#region ../harness/src/plugin/index.store.ts
|
|
6772
|
+
const Record_ = Schema.Struct({
|
|
6773
|
+
spec: Schema.String,
|
|
6774
|
+
source: Schema.Literals(["registry", "git"]),
|
|
6775
|
+
name: Schema.String,
|
|
6776
|
+
slug: Schema.String,
|
|
6777
|
+
mutable: Schema.Boolean,
|
|
6778
|
+
generation: Schema.Finite,
|
|
6779
|
+
version: Schema.optional(Schema.String),
|
|
6780
|
+
revision: Schema.optional(Schema.String),
|
|
6781
|
+
entrypoint: Schema.String,
|
|
6782
|
+
installedAt: Schema.Finite,
|
|
6783
|
+
checkedAt: Schema.optional(Schema.Finite),
|
|
6784
|
+
outdated: Schema.optional(Schema.Boolean),
|
|
6785
|
+
/** The remote revision observed at `checkedAt`; distinct from the installed `revision`. */
|
|
6786
|
+
available: Schema.optional(Schema.String)
|
|
6787
|
+
});
|
|
6788
|
+
const File = Schema.Struct({
|
|
6789
|
+
version: Schema.Finite,
|
|
6790
|
+
entries: Schema.Record(Schema.String, Record_)
|
|
6862
6791
|
});
|
|
6792
|
+
const VERSION = 1;
|
|
6793
|
+
const NAME = "index.json";
|
|
6863
6794
|
/**
|
|
6864
|
-
*
|
|
6865
|
-
*
|
|
6866
|
-
* Every exchange already re-reads every settings layer from disk, and the config pass already
|
|
6867
|
-
* walks those entries against the loaded set. Two things can be settled right there, and they are
|
|
6868
|
-
* the same question asked twice:
|
|
6869
|
-
*
|
|
6870
|
-
* 1. a settings entry names a module that is not loaded, and the store holds it;
|
|
6871
|
-
* 2. a loaded module is not the newest generation the store has.
|
|
6795
|
+
* Read the whole file, or start empty.
|
|
6872
6796
|
*
|
|
6873
|
-
*
|
|
6874
|
-
*
|
|
6875
|
-
|
|
6797
|
+
* An unreadable or unparseable index is not an error: it is a cold cache. Failing here would make
|
|
6798
|
+
* a corrupt accelerator break installs that do not need it.
|
|
6799
|
+
*/
|
|
6800
|
+
const read = Effect.fn("PluginIndex.read")(function* (root) {
|
|
6801
|
+
const parsed = yield* fileSystem.readFileString(hostPath.join(root, NAME)).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(File))), Effect.option);
|
|
6802
|
+
if (Option.isNone(parsed) || parsed.value.version !== VERSION) return /* @__PURE__ */ new Map();
|
|
6803
|
+
return new Map(Object.entries(parsed.value.entries));
|
|
6804
|
+
});
|
|
6805
|
+
/** Temp file plus rename, so a reader never sees a half-written index. */
|
|
6806
|
+
const write = Effect.fn("PluginIndex.write")(function* (root, entries) {
|
|
6807
|
+
const encoded = yield* Schema.encodeEffect(Schema.fromJsonString(File))({
|
|
6808
|
+
version: VERSION,
|
|
6809
|
+
entries: Object.fromEntries(entries)
|
|
6810
|
+
});
|
|
6811
|
+
yield* fileSystem.makeDirectory(root, { recursive: true });
|
|
6812
|
+
const temporary = yield* fileSystem.makeTempFile({
|
|
6813
|
+
directory: root,
|
|
6814
|
+
prefix: `.${NAME}.`,
|
|
6815
|
+
suffix: ".tmp"
|
|
6816
|
+
});
|
|
6817
|
+
yield* fileSystem.writeFileString(temporary, encoded).pipe(Effect.andThen(fileSystem.rename(temporary, hostPath.join(root, NAME))), Effect.ensuring(fileSystem.remove(temporary, { force: true }).pipe(Effect.ignore)));
|
|
6818
|
+
});
|
|
6819
|
+
/**
|
|
6820
|
+
* Record one entry, keeping whatever else the file holds.
|
|
6876
6821
|
*
|
|
6877
|
-
*
|
|
6878
|
-
*
|
|
6822
|
+
* Ignores its own failures. A store that cannot write its accelerator still works; refusing the
|
|
6823
|
+
* install would be the accelerator deciding whether the install counted.
|
|
6824
|
+
*/
|
|
6825
|
+
const put = Effect.fn("PluginIndex.put")(function* (root, digest, record) {
|
|
6826
|
+
const entries = yield* read(root);
|
|
6827
|
+
entries.set(digest, record);
|
|
6828
|
+
yield* write(root, entries).pipe(Effect.ignore);
|
|
6829
|
+
});
|
|
6830
|
+
/**
|
|
6831
|
+
* Merge fields into one record, if it is there.
|
|
6879
6832
|
*
|
|
6880
|
-
*
|
|
6881
|
-
*
|
|
6882
|
-
*
|
|
6883
|
-
*
|
|
6884
|
-
* running is unaffected.
|
|
6833
|
+
* Unlocked, like every write here but the one that publishes a generation. Two processes checking
|
|
6834
|
+
* at once both read the whole file and both rewrite it, so one update is lost and a record added
|
|
6835
|
+
* in between can vanish -- and neither matters, because a lost `checkedAt` costs one network call
|
|
6836
|
+
* and a vanished record is a miss a scan repairs.
|
|
6885
6837
|
*/
|
|
6886
|
-
const
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
if (!moved) for (const origin of pool.origins.values()) {
|
|
6896
|
-
if (origin.generation === void 0) continue;
|
|
6897
|
-
const current = yield* filed(origin.reference, anchor$1(origin.file, options.hostDir));
|
|
6898
|
-
if (Option.isSome(current) && current.value.generation !== void 0 && current.value.generation > origin.generation) {
|
|
6899
|
-
moved = true;
|
|
6900
|
-
break;
|
|
6901
|
-
}
|
|
6902
|
-
}
|
|
6903
|
-
if (!moved) return Option.none();
|
|
6904
|
-
const retained = [];
|
|
6905
|
-
for (const origin of pool.origins.values()) {
|
|
6906
|
-
if (references.includes(origin.reference) || Option.isSome(yield* filed(origin.reference, anchor$1(origin.file, options.hostDir)))) {
|
|
6907
|
-
retained.push(origin);
|
|
6908
|
-
continue;
|
|
6909
|
-
}
|
|
6910
|
-
yield* Effect.logWarning(`plugin ${origin.reference} no longer resolves; it is dropped from the loaded set`);
|
|
6911
|
-
}
|
|
6912
|
-
const accumulated = [...Array.from(retained, (origin) => origin.reference), ...references];
|
|
6913
|
-
const known = /* @__PURE__ */ new Map();
|
|
6914
|
-
for (const origin of retained) if (origin.file !== void 0) known.set(origin.reference, origin.file);
|
|
6915
|
-
for (const [reference, file] of options.declared ?? /* @__PURE__ */ new Map()) known.set(reference, file);
|
|
6916
|
-
return Option.some(yield* load$2(accumulated, {
|
|
6917
|
-
...options,
|
|
6918
|
-
declared: known
|
|
6919
|
-
}));
|
|
6838
|
+
const patch = Effect.fn("PluginIndex.patch")(function* (root, digest, fields) {
|
|
6839
|
+
const entries = yield* read(root);
|
|
6840
|
+
const existing = entries.get(digest);
|
|
6841
|
+
if (existing === void 0) return;
|
|
6842
|
+
entries.set(digest, {
|
|
6843
|
+
...existing,
|
|
6844
|
+
...fields
|
|
6845
|
+
});
|
|
6846
|
+
yield* write(root, entries).pipe(Effect.ignore);
|
|
6920
6847
|
});
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6848
|
+
/** Forget one entry, for a store entry that has been removed. */
|
|
6849
|
+
const drop = Effect.fn("PluginIndex.drop")(function* (root, digest) {
|
|
6850
|
+
const entries = yield* read(root);
|
|
6851
|
+
if (!entries.delete(digest)) return;
|
|
6852
|
+
yield* write(root, entries).pipe(Effect.ignore);
|
|
6924
6853
|
});
|
|
6925
6854
|
//#endregion
|
|
6926
|
-
//#region ../harness/src/plugin/
|
|
6927
|
-
/**
|
|
6928
|
-
const
|
|
6855
|
+
//#region ../harness/src/plugin/lock.ts
|
|
6856
|
+
/** How long to wait for another holder before giving up. */
|
|
6857
|
+
const TIMEOUT = Duration.minutes(2);
|
|
6858
|
+
/** The holder refreshes the lock's mtime this often; one not refreshed for the timeout is stale. */
|
|
6859
|
+
const HEARTBEAT = Duration.seconds(15);
|
|
6860
|
+
const abandoned = (directory) => Effect.gen(function* () {
|
|
6861
|
+
const info = yield* fileSystem.stat(directory);
|
|
6862
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
6863
|
+
return Option.exists(info.mtime, (mtime) => now - mtime.getTime() > Duration.toMillis(TIMEOUT));
|
|
6864
|
+
}).pipe(Effect.orElseSucceed(() => false));
|
|
6865
|
+
const heartbeat = (directory) => Effect.clockWith((clock) => clock.currentTimeMillis).pipe(Effect.flatMap((now) => fileSystem.utimes(directory, now, now)), Effect.ignore, Effect.repeat(Schedule.spaced(HEARTBEAT)));
|
|
6929
6866
|
/**
|
|
6930
|
-
*
|
|
6867
|
+
* Claims `directory` by creating it, polling while another process holds it, and releasing it when
|
|
6868
|
+
* the surrounding scope closes.
|
|
6931
6869
|
*
|
|
6932
|
-
*
|
|
6933
|
-
*
|
|
6870
|
+
* One finalizer is registered for the whole wait rather than one per 50ms poll, and a single
|
|
6871
|
+
* attempt is `uninterruptible` so `mkdir` and recording ownership cannot be split -- an interrupt
|
|
6872
|
+
* between them would leave behind a lock the finalizer does not know to remove.
|
|
6873
|
+
*
|
|
6874
|
+
* `onTimeout` names the failure, because what a caller is waiting for is the caller's to describe.
|
|
6934
6875
|
*/
|
|
6935
|
-
const
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6876
|
+
const lock = Effect.fn("PluginLock.acquire")(function* (directory, onTimeout) {
|
|
6877
|
+
const held = yield* Ref.make(false);
|
|
6878
|
+
yield* Effect.acquireRelease(Effect.void, () => Ref.get(held).pipe(Effect.flatMap((owned) => owned ? fileSystem.remove(directory, {
|
|
6879
|
+
recursive: true,
|
|
6880
|
+
force: true
|
|
6881
|
+
}) : Effect.void), Effect.orDie));
|
|
6882
|
+
if (!(yield* fileSystem.makeDirectory(directory).pipe(Effect.andThen(Ref.set(held, true)), Effect.as(true), Effect.catchIf((error) => error.reason._tag === "AlreadyExists", () => abandoned(directory).pipe(Effect.flatMap((stale) => stale ? fileSystem.remove(directory, {
|
|
6883
|
+
recursive: true,
|
|
6884
|
+
force: true
|
|
6885
|
+
}).pipe(Effect.as(false)) : Effect.succeed(false)))), Effect.uninterruptible).pipe(Effect.repeat({
|
|
6886
|
+
schedule: Schedule.spaced("50 millis"),
|
|
6887
|
+
until: (owned) => owned
|
|
6888
|
+
}), Effect.timeoutOrElse({
|
|
6889
|
+
duration: TIMEOUT,
|
|
6890
|
+
orElse: () => Effect.succeed(false)
|
|
6891
|
+
})))) return yield* Effect.fail(onTimeout());
|
|
6892
|
+
yield* Effect.forkScoped(heartbeat(directory));
|
|
6893
|
+
});
|
|
6894
|
+
//#endregion
|
|
6895
|
+
//#region ../harness/src/plugin/store.ts
|
|
6896
|
+
/** Facts about the *package*, and nothing about the plugin inside it. */
|
|
6897
|
+
const Marker = Schema.Struct({
|
|
6898
|
+
spec: Schema.String,
|
|
6899
|
+
name: Schema.String,
|
|
6900
|
+
version: Schema.optional(Schema.String),
|
|
6901
|
+
revision: Schema.optional(Schema.String),
|
|
6902
|
+
/** Relative to the generation directory, so the marker survives the publishing rename. */
|
|
6903
|
+
entrypoint: Schema.String,
|
|
6904
|
+
createdAt: Schema.Finite
|
|
6905
|
+
});
|
|
6906
|
+
const MARKER = ".complete.json";
|
|
6907
|
+
const STAGING = ".staging-";
|
|
6908
|
+
const root = (cache) => hostPath.join(cache, "plugins", "v2");
|
|
6942
6909
|
/**
|
|
6943
|
-
*
|
|
6944
|
-
*
|
|
6910
|
+
* A readable label, never an identity: two specs that slug alike share a parent directory and
|
|
6911
|
+
* nothing more. Worth having because a greppable path is worth real money when debugging, and a
|
|
6912
|
+
* bare hash throws away every clue.
|
|
6913
|
+
*/
|
|
6914
|
+
const slugOf = (target) => target.kind === "git" ? target.slug : canonical$1(target).replace(/^@/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "package";
|
|
6915
|
+
/**
|
|
6916
|
+
* The **full** SHA-256 of the source identity: canonical spec plus effective registry for a
|
|
6917
|
+
* registry package, and the canonical spec alone for git.
|
|
6945
6918
|
*
|
|
6946
|
-
*
|
|
6947
|
-
*
|
|
6919
|
+
* Not truncated. The fast path proves a hit by checking that a marker *exists* rather than by
|
|
6920
|
+
* decoding it, and the index is keyed by digest alone, so a truncated digest would let a collision
|
|
6921
|
+
* return the wrong package silently.
|
|
6948
6922
|
*/
|
|
6949
|
-
const
|
|
6950
|
-
const
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6923
|
+
const digest = Effect.fn("PluginStore.digest")(function* (target, cache, from) {
|
|
6924
|
+
const source = target.kind === "registry" ? `${yield* registry(target, cache, from)}\0${target.spec}` : target.spec;
|
|
6925
|
+
return Encoding.encodeHex(yield* crypto.digest("SHA-256", new TextEncoder().encode(source)));
|
|
6926
|
+
});
|
|
6927
|
+
const entryDir = Effect.fn("PluginStore.entryDir")(function* (target, cache, from) {
|
|
6928
|
+
const key = yield* digest(target, cache, from);
|
|
6929
|
+
return {
|
|
6930
|
+
key,
|
|
6931
|
+
directory: hostPath.join(root(cache), slugOf(target), key)
|
|
6932
|
+
};
|
|
6933
|
+
});
|
|
6934
|
+
const failure$1 = (target, reason, message, cause) => new StoreError({
|
|
6935
|
+
reason,
|
|
6936
|
+
reference: target.spec,
|
|
6937
|
+
message,
|
|
6938
|
+
...cause === void 0 ? {} : { cause }
|
|
6939
|
+
});
|
|
6940
|
+
/** Generation directories, oldest first. A name that is not a number is not a generation. */
|
|
6941
|
+
const generations = Effect.fn("PluginStore.generations")(function* (directory) {
|
|
6942
|
+
return (yield* fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed(() => []))).filter((name) => /^\d+$/.test(name)).map((name) => Number(name)).sort((left, right) => left - right);
|
|
6943
|
+
});
|
|
6944
|
+
const readMarker = (directory) => fileSystem.readFileString(hostPath.join(directory, MARKER)).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Marker))), Effect.option);
|
|
6945
|
+
const entryOf = (marker, input) => ({
|
|
6946
|
+
...marker,
|
|
6947
|
+
digest: input.digest,
|
|
6948
|
+
generation: input.generation,
|
|
6949
|
+
directory: input.directory,
|
|
6950
|
+
url: url(hostPath.resolve(input.directory, marker.entrypoint))
|
|
6951
|
+
});
|
|
6952
|
+
/** The newest generation that carries a marker. Unmarked ones are invisible: they never happened. */
|
|
6953
|
+
const active = Effect.fn("PluginStore.active")(function* (directory, key) {
|
|
6954
|
+
for (const generation of (yield* generations(directory)).reverse()) {
|
|
6955
|
+
const where = hostPath.join(directory, String(generation));
|
|
6956
|
+
const marker = yield* readMarker(where);
|
|
6957
|
+
if (Option.isSome(marker)) return entryOf(marker.value, {
|
|
6958
|
+
digest: key,
|
|
6959
|
+
generation,
|
|
6960
|
+
directory: where
|
|
6961
|
+
});
|
|
6957
6962
|
}
|
|
6958
|
-
|
|
6959
|
-
|
|
6963
|
+
});
|
|
6964
|
+
/** What the index files about an entry, all of it derivable from the spec and the marker. */
|
|
6965
|
+
const record = (target, entry, installedAt) => ({
|
|
6966
|
+
spec: target.spec,
|
|
6967
|
+
source: target.kind,
|
|
6968
|
+
name: entry.name,
|
|
6969
|
+
slug: slugOf(target),
|
|
6970
|
+
mutable: target.mutable,
|
|
6971
|
+
generation: entry.generation,
|
|
6972
|
+
...entry.version === void 0 ? {} : { version: entry.version },
|
|
6973
|
+
...entry.revision === void 0 ? {} : { revision: entry.revision },
|
|
6974
|
+
entrypoint: entry.entrypoint,
|
|
6975
|
+
installedAt
|
|
6976
|
+
});
|
|
6960
6977
|
/**
|
|
6961
|
-
*
|
|
6978
|
+
* Disk-only lookup. No network, no install -- this is what boot uses, and the reason booting
|
|
6979
|
+
* offline works.
|
|
6962
6980
|
*
|
|
6963
|
-
*
|
|
6964
|
-
*
|
|
6981
|
+
* The index is consulted first and is only ever a hint: a hit is proved by the marker still
|
|
6982
|
+
* existing, and anything else falls through to a scan that repairs the index on the way past.
|
|
6965
6983
|
*/
|
|
6966
|
-
const
|
|
6967
|
-
const
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
const
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6984
|
+
const resolve$2 = Effect.fn("PluginStore.resolve")(function* (target, cache, from) {
|
|
6985
|
+
const { key, directory } = yield* entryDir(target, cache, from);
|
|
6986
|
+
const base = root(cache);
|
|
6987
|
+
const hinted = (yield* read(base)).get(key);
|
|
6988
|
+
if (hinted !== void 0) {
|
|
6989
|
+
const where = hostPath.join(directory, String(hinted.generation));
|
|
6990
|
+
const marker = yield* readMarker(where);
|
|
6991
|
+
if (Option.isSome(marker)) return entryOf(marker.value, {
|
|
6992
|
+
digest: key,
|
|
6993
|
+
generation: hinted.generation,
|
|
6994
|
+
directory: where
|
|
6995
|
+
});
|
|
6996
|
+
}
|
|
6997
|
+
const scanned = yield* active(directory, key);
|
|
6998
|
+
if (scanned === void 0) return void 0;
|
|
6999
|
+
yield* put(base, key, record(target, scanned, scanned.createdAt));
|
|
7000
|
+
return scanned;
|
|
7001
|
+
}, Effect.mapError((cause) => Schema.is(StoreError)(cause) || Schema.is(InstallError)(cause) ? cause : new StoreError({
|
|
7002
|
+
reason: "plugin-marker-invalid",
|
|
7003
|
+
reference: "",
|
|
7004
|
+
message: "cannot read the plugin store",
|
|
7005
|
+
cause
|
|
7006
|
+
})));
|
|
6981
7007
|
/**
|
|
6982
|
-
*
|
|
6983
|
-
*
|
|
7008
|
+
* `resolve`, for a caller that needs the entry rather than an answer about it.
|
|
7009
|
+
*
|
|
7010
|
+
* `plugin remove` uses this as its installer: it has to identify what an entry names without
|
|
7011
|
+
* fetching anything, and a spec with nothing filed is a real answer there -- the command reports
|
|
7012
|
+
* it and moves on.
|
|
6984
7013
|
*/
|
|
6985
|
-
const
|
|
6986
|
-
const
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
setup: Effect.fn("DefaultPromptPlugin.setup")(function* (ctx) {
|
|
6990
|
-
const custom = yield* slot(ctx, ctx.config.promptCustom);
|
|
6991
|
-
const append = yield* slot(ctx, ctx.config.promptSystemAppend);
|
|
6992
|
-
ctx.plugin.prompt.set(assemble({
|
|
6993
|
-
tools: ctx.plugin.tools.list(),
|
|
6994
|
-
directory: ctx.location.directory,
|
|
6995
|
-
...custom === void 0 ? {} : { promptCustom: custom },
|
|
6996
|
-
...append === void 0 ? {} : { promptSystemAppend: append }
|
|
6997
|
-
}));
|
|
6998
|
-
})
|
|
7014
|
+
const required = Effect.fn("PluginStore.required")(function* (target, cache, from) {
|
|
7015
|
+
const found = yield* resolve$2(target, cache, from);
|
|
7016
|
+
if (found === void 0) return yield* failure$1(target, "plugin-not-installed", `${target.spec} is not installed`);
|
|
7017
|
+
return found;
|
|
6999
7018
|
});
|
|
7000
|
-
//#endregion
|
|
7001
|
-
//#region ../harness/src/tool/truncate.ts
|
|
7002
7019
|
/**
|
|
7003
|
-
*
|
|
7004
|
-
*
|
|
7005
|
-
* Truncation is based on two independent limits — whichever is hit first
|
|
7006
|
-
* wins: a line limit and a byte limit. Lines are never split (except the bash
|
|
7007
|
-
* tail edge case where a single line exceeds the byte limit).
|
|
7020
|
+
* Install if absent; hand back what is filed if present.
|
|
7008
7021
|
*
|
|
7009
|
-
*
|
|
7010
|
-
*
|
|
7011
|
-
*/
|
|
7012
|
-
const DEFAULT_MAX_LINES = 2e3;
|
|
7013
|
-
const DEFAULT_MAX_BYTES = 51200;
|
|
7014
|
-
function splitLinesForCounting(content) {
|
|
7015
|
-
if (content.length === 0) return [];
|
|
7016
|
-
const lines = content.split("\n");
|
|
7017
|
-
if (content.endsWith("\n")) lines.pop();
|
|
7018
|
-
return lines;
|
|
7019
|
-
}
|
|
7020
|
-
/** Format bytes as a human-readable size. */
|
|
7021
|
-
function formatSize(bytes) {
|
|
7022
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
7023
|
-
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
7024
|
-
return `${(bytes / 1048576).toFixed(1)}MB`;
|
|
7025
|
-
}
|
|
7026
|
-
/**
|
|
7027
|
-
* Truncate content from the tail (keep the last N lines/bytes). Suitable for
|
|
7028
|
-
* bash output where the end matters (errors, final results). May return a
|
|
7029
|
-
* partial first line if the last line of the original content exceeds the byte
|
|
7030
|
-
* limit.
|
|
7022
|
+
* `refresh` forces past both the fast path and the double-check, which is what `update` needs and
|
|
7023
|
+
* nothing else should use.
|
|
7031
7024
|
*/
|
|
7032
|
-
function
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
if (totalLines <= maxLines && totalBytes <= maxBytes) return {
|
|
7039
|
-
content,
|
|
7040
|
-
truncated: false,
|
|
7041
|
-
truncatedBy: null,
|
|
7042
|
-
totalLines,
|
|
7043
|
-
totalBytes,
|
|
7044
|
-
outputLines: totalLines,
|
|
7045
|
-
outputBytes: totalBytes,
|
|
7046
|
-
lastLinePartial: false,
|
|
7047
|
-
firstLineExceedsLimit: false,
|
|
7048
|
-
maxLines,
|
|
7049
|
-
maxBytes
|
|
7050
|
-
};
|
|
7051
|
-
const outputLinesArr = [];
|
|
7052
|
-
let outputBytesCount = 0;
|
|
7053
|
-
let truncatedBy = "lines";
|
|
7054
|
-
let lastLinePartial = false;
|
|
7055
|
-
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
7056
|
-
const line = lines[i] ?? "";
|
|
7057
|
-
const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0);
|
|
7058
|
-
if (outputBytesCount + lineBytes > maxBytes) {
|
|
7059
|
-
truncatedBy = "bytes";
|
|
7060
|
-
if (outputLinesArr.length === 0) {
|
|
7061
|
-
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
7062
|
-
outputLinesArr.unshift(truncatedLine);
|
|
7063
|
-
outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
|
|
7064
|
-
lastLinePartial = true;
|
|
7065
|
-
}
|
|
7066
|
-
break;
|
|
7025
|
+
const add = Effect.fn("PluginStore.add")(function* (target, cache, options) {
|
|
7026
|
+
return yield* Effect.gen(function* () {
|
|
7027
|
+
const { key, directory } = yield* entryDir(target, cache, options.from);
|
|
7028
|
+
if (options.refresh !== true) {
|
|
7029
|
+
const found = yield* resolve$2(target, cache, options.from);
|
|
7030
|
+
if (found !== void 0) return { entry: found };
|
|
7067
7031
|
}
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
}
|
|
7088
|
-
/**
|
|
7089
|
-
* Truncate a string to fit within a byte limit (from the end). Handles
|
|
7090
|
-
* multi-byte UTF-8 characters correctly (never splits a codepoint).
|
|
7091
|
-
*/
|
|
7092
|
-
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
7093
|
-
const buf = Buffer.from(str, "utf-8");
|
|
7094
|
-
if (buf.length <= maxBytes) return str;
|
|
7095
|
-
let start = buf.length - maxBytes;
|
|
7096
|
-
while (start < buf.length && (buf[start] & 192) === 128) start++;
|
|
7097
|
-
return buf.subarray(start).toString("utf-8");
|
|
7098
|
-
}
|
|
7099
|
-
//#endregion
|
|
7100
|
-
//#region ../harness/src/tool/accumulator.ts
|
|
7101
|
-
function defaultTempFilePath(prefix, suffix) {
|
|
7102
|
-
return posix.join(tmpdir(), `${prefix}-${suffix}.log`);
|
|
7103
|
-
}
|
|
7104
|
-
function byteLength(text) {
|
|
7105
|
-
return Buffer.byteLength(text, "utf-8");
|
|
7106
|
-
}
|
|
7107
|
-
var Accumulator = class {
|
|
7108
|
-
maxLines;
|
|
7109
|
-
maxBytes;
|
|
7110
|
-
maxRollingBytes;
|
|
7111
|
-
tempFilePrefix;
|
|
7112
|
-
decoder = new TextDecoder();
|
|
7113
|
-
rawChunks = [];
|
|
7114
|
-
tailText = "";
|
|
7115
|
-
tailBytes = 0;
|
|
7116
|
-
tailStartsAtLineBoundary = true;
|
|
7117
|
-
totalRawBytes = 0;
|
|
7118
|
-
totalDecodedBytes = 0;
|
|
7119
|
-
completedLines = 0;
|
|
7120
|
-
totalLines = 0;
|
|
7121
|
-
currentLineBytes = 0;
|
|
7122
|
-
hasOpenLine = false;
|
|
7123
|
-
finished = false;
|
|
7124
|
-
tempFilePath;
|
|
7125
|
-
tempFile;
|
|
7126
|
-
constructor(options = {}) {
|
|
7127
|
-
this.maxLines = options.maxLines ?? 2e3;
|
|
7128
|
-
this.maxBytes = options.maxBytes ?? 51200;
|
|
7129
|
-
this.maxRollingBytes = Math.max(this.maxBytes * 2, 1);
|
|
7130
|
-
this.tempFilePrefix = options.tempFilePrefix ?? "codework-output";
|
|
7131
|
-
}
|
|
7132
|
-
append(data) {
|
|
7133
|
-
return Effect.suspend(() => {
|
|
7134
|
-
if (this.finished) return Effect.die(/* @__PURE__ */ new Error("cannot append to a finished output accumulator"));
|
|
7135
|
-
this.totalRawBytes += data.length;
|
|
7136
|
-
this.appendDecodedText(this.decoder.decode(data, { stream: true }));
|
|
7137
|
-
if (this.tempFile !== void 0 || this.shouldUseTempFile()) return Effect.gen({ self: this }, function* () {
|
|
7138
|
-
yield* this.ensureTempFile();
|
|
7139
|
-
yield* this.tempFile.writeAll(data).pipe(Effect.orDie);
|
|
7140
|
-
});
|
|
7141
|
-
if (data.length > 0) this.rawChunks.push(data);
|
|
7142
|
-
return Effect.void;
|
|
7143
|
-
});
|
|
7144
|
-
}
|
|
7145
|
-
finish() {
|
|
7146
|
-
return Effect.suspend(() => {
|
|
7147
|
-
if (this.finished) return Effect.void;
|
|
7148
|
-
this.finished = true;
|
|
7149
|
-
this.appendDecodedText(this.decoder.decode());
|
|
7150
|
-
return Effect.gen({ self: this }, function* () {
|
|
7151
|
-
if (this.shouldUseTempFile()) yield* this.ensureTempFile();
|
|
7152
|
-
if (this.tempFile !== void 0) yield* this.tempFile.sync.pipe(Effect.orDie);
|
|
7153
|
-
});
|
|
7032
|
+
yield* fileSystem.makeDirectory(directory, { recursive: true });
|
|
7033
|
+
yield* lock(hostPath.join(directory, ".lock"), () => failure$1(target, "plugin-lock-timeout", `timed out waiting for another installation of ${target.spec}`));
|
|
7034
|
+
const current = yield* active(directory, key);
|
|
7035
|
+
if (current !== void 0 && options.refresh !== true) return { entry: current };
|
|
7036
|
+
const real = yield* fileSystem.realPath(directory).pipe(Effect.orElseSucceed(() => directory));
|
|
7037
|
+
const staging = yield* Effect.acquireRelease(fileSystem.makeTempDirectory({
|
|
7038
|
+
directory: real,
|
|
7039
|
+
prefix: STAGING
|
|
7040
|
+
}), (staged) => fileSystem.remove(staged, {
|
|
7041
|
+
recursive: true,
|
|
7042
|
+
force: true
|
|
7043
|
+
}).pipe(Effect.orDie));
|
|
7044
|
+
yield* fileSystem.writeFileString(hostPath.join(staging, "package.json"), "{\"private\":true,\"type\":\"module\"}");
|
|
7045
|
+
const fetched = yield* download({
|
|
7046
|
+
target,
|
|
7047
|
+
into: staging,
|
|
7048
|
+
cache,
|
|
7049
|
+
from: options.from,
|
|
7050
|
+
...options.runner === void 0 ? {} : { runner: options.runner }
|
|
7154
7051
|
});
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
const
|
|
7158
|
-
|
|
7159
|
-
|
|
7052
|
+
if (current !== void 0 && current.revision === fetched.revision) return { entry: current };
|
|
7053
|
+
const validated = yield* options.validate(fetched);
|
|
7054
|
+
const entrypoint = hostPath.relative(yield* fileSystem.realPath(staging), fetched.entrypoint);
|
|
7055
|
+
if (entrypoint.startsWith("..") || hostPath.isAbsolute(entrypoint)) return yield* new InstallError({
|
|
7056
|
+
reason: "plugin-no-entrypoint",
|
|
7057
|
+
reference: target.spec,
|
|
7058
|
+
message: `entrypoint escapes its installation: ${entrypoint}`
|
|
7160
7059
|
});
|
|
7161
|
-
const
|
|
7162
|
-
const
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
maxLines: this.maxLines,
|
|
7170
|
-
maxBytes: this.maxBytes
|
|
7060
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
7061
|
+
const marker = {
|
|
7062
|
+
spec: target.spec,
|
|
7063
|
+
name: fetched.name,
|
|
7064
|
+
...fetched.version === void 0 ? {} : { version: fetched.version },
|
|
7065
|
+
...fetched.revision === void 0 ? {} : { revision: fetched.revision },
|
|
7066
|
+
entrypoint,
|
|
7067
|
+
createdAt: now
|
|
7171
7068
|
};
|
|
7069
|
+
yield* fileSystem.writeFileString(hostPath.join(staging, MARKER), yield* Schema.encodeEffect(Schema.fromJsonString(Marker))(marker));
|
|
7070
|
+
const previous = (yield* generations(directory)).at(-1) ?? 0;
|
|
7071
|
+
const generation = Math.max(now, previous + 1);
|
|
7072
|
+
const published = hostPath.join(directory, String(generation));
|
|
7073
|
+
yield* fileSystem.rename(staging, published);
|
|
7074
|
+
const entry = entryOf(marker, {
|
|
7075
|
+
digest: key,
|
|
7076
|
+
generation,
|
|
7077
|
+
directory: published
|
|
7078
|
+
});
|
|
7079
|
+
yield* put(root(cache), key, record(target, entry, now));
|
|
7080
|
+
yield* collect$1(directory);
|
|
7172
7081
|
return {
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
...this.tempFilePath === void 0 ? {} : { fullOutputPath: this.tempFilePath }
|
|
7082
|
+
entry,
|
|
7083
|
+
validated
|
|
7176
7084
|
};
|
|
7177
|
-
}
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
this.hasOpenLine = tail.length > 0;
|
|
7203
|
-
}
|
|
7204
|
-
this.totalLines = this.completedLines + (this.hasOpenLine ? 1 : 0);
|
|
7205
|
-
}
|
|
7206
|
-
trimTail() {
|
|
7207
|
-
const buffer = Buffer.from(this.tailText, "utf-8");
|
|
7208
|
-
if (buffer.length <= this.maxRollingBytes) {
|
|
7209
|
-
this.tailBytes = buffer.length;
|
|
7210
|
-
return;
|
|
7085
|
+
}).pipe(Effect.scoped, Effect.mapError((cause) => Schema.is(InstallError)(cause) || Schema.is(StoreError)(cause) || Schema.is(LoadError)(cause) ? cause : failure$1(target, "plugin-marker-invalid", `cannot write the store entry for ${target.spec}`, cause)));
|
|
7086
|
+
});
|
|
7087
|
+
/**
|
|
7088
|
+
* Expire what is superseded.
|
|
7089
|
+
*
|
|
7090
|
+
* The two newest generations are kept regardless of age: the newest is live, and the one before it
|
|
7091
|
+
* may still be imported by an exchange in flight. Runs at the end of an `add` that published
|
|
7092
|
+
* something -- no timer and no sweeper, because installs are the only thing that creates garbage.
|
|
7093
|
+
*/
|
|
7094
|
+
const collect$1 = Effect.fn("PluginStore.collect")(function* (directory) {
|
|
7095
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
7096
|
+
const keep = new Set((yield* generations(directory)).slice(-2).map(String));
|
|
7097
|
+
const names = yield* fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed(() => []));
|
|
7098
|
+
for (const name of names) {
|
|
7099
|
+
if (keep.has(name) || name === ".lock") continue;
|
|
7100
|
+
if (name.startsWith(STAGING)) {
|
|
7101
|
+
const started = yield* fileSystem.stat(hostPath.join(directory, name)).pipe(Effect.option);
|
|
7102
|
+
if ((Option.isNone(started) ? 0 : Option.match(started.value.mtime, {
|
|
7103
|
+
onNone: () => 0,
|
|
7104
|
+
onSome: (at) => now - at.getTime()
|
|
7105
|
+
})) > HOUR) yield* fileSystem.remove(hostPath.join(directory, name), {
|
|
7106
|
+
recursive: true,
|
|
7107
|
+
force: true
|
|
7108
|
+
}).pipe(Effect.ignore);
|
|
7109
|
+
continue;
|
|
7211
7110
|
}
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
}
|
|
7218
|
-
getSnapshotText() {
|
|
7219
|
-
if (this.tailStartsAtLineBoundary) return this.tailText;
|
|
7220
|
-
const firstNewline = this.tailText.indexOf("\n");
|
|
7221
|
-
return firstNewline === -1 ? this.tailText : this.tailText.slice(firstNewline + 1);
|
|
7222
|
-
}
|
|
7223
|
-
shouldUseTempFile() {
|
|
7224
|
-
return this.totalRawBytes > this.maxBytes || this.totalDecodedBytes > this.maxBytes || this.totalLines > this.maxLines;
|
|
7225
|
-
}
|
|
7226
|
-
ensureTempFile() {
|
|
7227
|
-
if (this.tempFile !== void 0) return Effect.void;
|
|
7228
|
-
return Effect.gen({ self: this }, function* () {
|
|
7229
|
-
const suffix = Encoding.encodeHex(yield* crypto.randomBytes(8));
|
|
7230
|
-
this.tempFilePath = defaultTempFilePath(this.tempFilePrefix, suffix);
|
|
7231
|
-
this.tempFile = yield* fileSystem.open(this.tempFilePath, { flag: "w" });
|
|
7232
|
-
for (const chunk of this.rawChunks) yield* this.tempFile.writeAll(chunk);
|
|
7233
|
-
this.rawChunks = [];
|
|
7234
|
-
}).pipe(Effect.orDie);
|
|
7111
|
+
if (!/^\d+$/.test(name)) continue;
|
|
7112
|
+
if (now - Number(name) > WEEK) yield* fileSystem.remove(hostPath.join(directory, name), {
|
|
7113
|
+
recursive: true,
|
|
7114
|
+
force: true
|
|
7115
|
+
}).pipe(Effect.ignore);
|
|
7235
7116
|
}
|
|
7236
|
-
};
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
var progress_exports = /* @__PURE__ */ __exportAll({});
|
|
7240
|
-
import * as import__codeworksh_plugin_tool_progress from "@codeworksh/plugin/tool/progress";
|
|
7241
|
-
__reExport(progress_exports, import__codeworksh_plugin_tool_progress);
|
|
7242
|
-
//#endregion
|
|
7243
|
-
//#region ../harness/src/tool/shell.ts
|
|
7117
|
+
});
|
|
7118
|
+
const HOUR = 36e5;
|
|
7119
|
+
const WEEK = 168 * HOUR;
|
|
7244
7120
|
/**
|
|
7245
|
-
*
|
|
7246
|
-
*
|
|
7247
|
-
* It deliberately does **not** reuse `sandbox/Shell` directly: the local OS path
|
|
7248
|
-
* provides no `Shell` at all, and `sandbox/Shell` models neither cancellation
|
|
7249
|
-
* nor a timeout. `ToolShell` makes both part of the contract. Adapters honor the
|
|
7250
|
-
* mid-flight process kill where the SDK allows it (local, Vercel) and fall back
|
|
7251
|
-
* to the timeout where it does not (Daytona) — "kill where it's easy".
|
|
7121
|
+
* How long a network answer is reused before `check` asks again.
|
|
7252
7122
|
*
|
|
7253
|
-
*
|
|
7254
|
-
*
|
|
7255
|
-
* - Local / just-bash → {@link fromSandboxShell} over `EnvBash`
|
|
7256
|
-
* - Remote / provider → {@link fromSandboxShell} over `EnvDaytona` / `EnvVercel`
|
|
7123
|
+
* Persisted in the index rather than held in memory, because the thing that benefits is a command
|
|
7124
|
+
* that exits: an in-memory staleness cache is worthless to `codework plugin check`.
|
|
7257
7125
|
*/
|
|
7258
|
-
|
|
7259
|
-
var ToolShellError = class extends Schema.TaggedError()("ToolShellError", {
|
|
7260
|
-
command: Schema.String,
|
|
7261
|
-
cause: Schema.optional(Schema.Defect())
|
|
7262
|
-
}) {};
|
|
7263
|
-
/** The command exceeded its deadline. Distinct so handlers can map it to a domain timeout. */
|
|
7264
|
-
var ToolShellTimeout = class extends Schema.TaggedError()("ToolShellTimeout", {
|
|
7265
|
-
command: Schema.String,
|
|
7266
|
-
timeoutMillis: Schema.Finite
|
|
7267
|
-
}) {};
|
|
7268
|
-
const widenExecError = (effect) => effect.pipe(Effect.mapError((error) => error));
|
|
7269
|
-
var ToolShell = class extends Context.Service()("@codeworksh/harness/tool/shell/ToolShell") {};
|
|
7126
|
+
const CHECK_TTL = 36e5;
|
|
7270
7127
|
/**
|
|
7271
|
-
*
|
|
7272
|
-
* (in-process) and remote providers.
|
|
7128
|
+
* Is a newer revision available?
|
|
7273
7129
|
*
|
|
7274
|
-
*
|
|
7275
|
-
*
|
|
7276
|
-
*
|
|
7277
|
-
* returns control; the underlying command is only truly killed mid-flight on
|
|
7278
|
-
* signal-aware backends, handled in the provider layer). When the backend exposes
|
|
7279
|
-
* `stream` (e.g. Vercel `Command.logs`), it is bridged to {@link IToolShell.stream}
|
|
7280
|
-
* so the bash tool streams over it too.
|
|
7130
|
+
* The only network call in the store's vocabulary, and it is the caller's to make. A `probe` that
|
|
7131
|
+
* fails -- offline, registry down, a remote that refuses -- propagates rather than answering
|
|
7132
|
+
* `current`: an outage must not read as "you are up to date".
|
|
7281
7133
|
*/
|
|
7282
|
-
const
|
|
7283
|
-
|
|
7284
|
-
const
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
timeoutMillis: Duration.toMillis(timeout)
|
|
7298
|
-
})),
|
|
7299
|
-
onSome: Effect.succeed
|
|
7300
|
-
})));
|
|
7134
|
+
const check = Effect.fn("PluginStore.check")(function* (target, cache, options) {
|
|
7135
|
+
if (!target.mutable) return { _tag: "immutable" };
|
|
7136
|
+
const entry = yield* resolve$2(target, cache, options.from);
|
|
7137
|
+
if (entry === void 0) return { _tag: "not-installed" };
|
|
7138
|
+
const key = yield* digest(target, cache, options.from).pipe(Effect.mapError((cause) => failure$1(target, "plugin-marker-invalid", `cannot address ${target.spec}`, cause)));
|
|
7139
|
+
const base = root(cache);
|
|
7140
|
+
const record = (yield* read(base)).get(key);
|
|
7141
|
+
const now = yield* Effect.clockWith((clock) => clock.currentTimeMillis);
|
|
7142
|
+
if (options.refresh !== true && record?.checkedAt !== void 0 && now - record.checkedAt < CHECK_TTL && (record.outdated !== true || record.available !== void 0)) return record.outdated === true ? {
|
|
7143
|
+
_tag: "outdated",
|
|
7144
|
+
available: record.available,
|
|
7145
|
+
...entry.revision === void 0 ? {} : { filed: entry.revision }
|
|
7146
|
+
} : {
|
|
7147
|
+
_tag: "current",
|
|
7148
|
+
...entry.revision === void 0 ? {} : { revision: entry.revision }
|
|
7301
7149
|
};
|
|
7302
|
-
const
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
...
|
|
7306
|
-
}
|
|
7307
|
-
|
|
7308
|
-
|
|
7150
|
+
const available = yield* options.probe(target);
|
|
7151
|
+
if (available === void 0) return {
|
|
7152
|
+
_tag: "current",
|
|
7153
|
+
...entry.revision === void 0 ? {} : { revision: entry.revision }
|
|
7154
|
+
};
|
|
7155
|
+
const outdated = available !== entry.revision;
|
|
7156
|
+
yield* patch(base, key, {
|
|
7157
|
+
checkedAt: now,
|
|
7158
|
+
outdated,
|
|
7159
|
+
available
|
|
7160
|
+
});
|
|
7161
|
+
return outdated ? {
|
|
7162
|
+
_tag: "outdated",
|
|
7163
|
+
available,
|
|
7164
|
+
...entry.revision === void 0 ? {} : { filed: entry.revision }
|
|
7165
|
+
} : {
|
|
7166
|
+
_tag: "current",
|
|
7167
|
+
...entry.revision === void 0 ? {} : { revision: entry.revision }
|
|
7168
|
+
};
|
|
7169
|
+
});
|
|
7170
|
+
/**
|
|
7171
|
+
* Refresh a mutable target, if the network says there is anything to refresh.
|
|
7172
|
+
*
|
|
7173
|
+
* `add` with `refresh` is what does the work; this is the guard in front of it, so an `update`
|
|
7174
|
+
* that finds nothing new neither stages nor publishes.
|
|
7175
|
+
*/
|
|
7176
|
+
const update = Effect.fn("PluginStore.update")(function* (target, cache, options) {
|
|
7177
|
+
const current = yield* resolve$2(target, cache, options.from);
|
|
7178
|
+
if (current === void 0) return { _tag: "not-installed" };
|
|
7179
|
+
if ((yield* check(target, cache, {
|
|
7180
|
+
refresh: true,
|
|
7181
|
+
probe: options.probe,
|
|
7182
|
+
from: options.from
|
|
7183
|
+
}))._tag !== "outdated") return {
|
|
7184
|
+
_tag: "unchanged",
|
|
7185
|
+
entry: current
|
|
7186
|
+
};
|
|
7187
|
+
const added = yield* add(target, cache, {
|
|
7188
|
+
refresh: true,
|
|
7189
|
+
validate: options.validate,
|
|
7190
|
+
from: options.from,
|
|
7191
|
+
...options.runner === void 0 ? {} : { runner: options.runner }
|
|
7192
|
+
});
|
|
7193
|
+
return added.entry.generation === current.generation ? {
|
|
7194
|
+
_tag: "unchanged",
|
|
7195
|
+
entry: current
|
|
7309
7196
|
} : {
|
|
7310
|
-
_tag: "
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7197
|
+
_tag: "updated",
|
|
7198
|
+
entry: added.entry,
|
|
7199
|
+
...added.validated === void 0 ? {} : { validated: added.validated }
|
|
7200
|
+
};
|
|
7201
|
+
});
|
|
7202
|
+
Effect.fn("PluginStore.remove")(function* (target, cache, from) {
|
|
7203
|
+
const { key, directory } = yield* entryDir(target, cache, from);
|
|
7204
|
+
const existed = yield* fileSystem.exists(directory).pipe(Effect.orElseSucceed(() => false));
|
|
7205
|
+
yield* fileSystem.remove(directory, {
|
|
7206
|
+
recursive: true,
|
|
7207
|
+
force: true
|
|
7208
|
+
}).pipe(Effect.ignore);
|
|
7209
|
+
yield* drop(root(cache), key);
|
|
7210
|
+
return existed;
|
|
7211
|
+
});
|
|
7212
|
+
Effect.fn("PluginStore.all")(function* (cache) {
|
|
7213
|
+
const base = root(cache);
|
|
7214
|
+
const slugs = yield* fileSystem.readDirectory(base).pipe(Effect.orElseSucceed(() => []));
|
|
7215
|
+
const found = [];
|
|
7216
|
+
for (const slug of slugs) {
|
|
7217
|
+
const digests = yield* fileSystem.readDirectory(hostPath.join(base, slug)).pipe(Effect.orElseSucceed(() => []));
|
|
7218
|
+
for (const key of digests) {
|
|
7219
|
+
const entry = yield* active(hostPath.join(base, slug, key), key);
|
|
7220
|
+
if (entry !== void 0) found.push(entry);
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
return found;
|
|
7224
|
+
});
|
|
7225
|
+
const Id = Schema.String.check(Schema.isPattern(/^[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*){2}$/));
|
|
7226
|
+
const Definition = Schema.Struct({
|
|
7227
|
+
id: Id,
|
|
7228
|
+
kind: Schema.Literals(Object.keys(plugin_exports.domains)),
|
|
7229
|
+
events: Schema.optional(Schema.Array(Schema.declare((value) => Predicate.hasProperty(value, "type") && Predicate.isString(value.type)))),
|
|
7230
|
+
setup: Schema.declare((value) => Predicate.isFunction(value))
|
|
7231
|
+
});
|
|
7232
|
+
/** Every plugin failure says which file to go and edit, when there is one. */
|
|
7233
|
+
const where = (origin) => origin.file === void 0 ? {} : { file: origin.file };
|
|
7234
|
+
const detail = (cause) => cause instanceof Error ? cause.message : typeof cause === "string" ? cause : String(cause);
|
|
7235
|
+
/** A reference that does not name anything loadable. */
|
|
7236
|
+
const sourceFailure = (origin, cause) => Schema.is(SourceError)(cause) ? cause : new SourceError({
|
|
7237
|
+
reason: "plugin-not-found",
|
|
7238
|
+
reference: origin.reference,
|
|
7239
|
+
...where(origin),
|
|
7240
|
+
message: detail(cause)
|
|
7241
|
+
});
|
|
7242
|
+
/** Node's own code for a failure, when it set one. */
|
|
7243
|
+
const codeOf = (cause) => Predicate.hasProperty(cause, "code") && Predicate.isString(cause.code) ? cause.code : void 0;
|
|
7329
7244
|
/**
|
|
7330
|
-
*
|
|
7331
|
-
* the handler depends only on {@link ToolShell} / {@link ToolProgress}, never
|
|
7332
|
-
* `sandbox/Shell`, so the backend (local OS / just-bash / remote provider) is
|
|
7333
|
-
* swapped by changing the provided Layer with no change to the tool.
|
|
7245
|
+
* A module that would not import.
|
|
7334
7246
|
*
|
|
7335
|
-
* Two
|
|
7336
|
-
*
|
|
7337
|
-
*
|
|
7338
|
-
*
|
|
7339
|
-
*
|
|
7340
|
-
*
|
|
7341
|
-
* produced so far is preserved.
|
|
7247
|
+
* Two of these get their own reason, because the remedy differs and that is the whole value of a
|
|
7248
|
+
* slug. A missing dependency means the plugin is fine and its `node_modules` is not, which a
|
|
7249
|
+
* person fixes in the package rather than in their settings. An uncompiled one means the plugin
|
|
7250
|
+
* shipped its sources: Node refuses to strip types under `node_modules`, and installing a plugin
|
|
7251
|
+
* runs no lifecycle scripts, so a `prepare` build never happens either -- the author has to ship
|
|
7252
|
+
* the build output, and nothing the user can do to their settings will help.
|
|
7342
7253
|
*
|
|
7343
|
-
*
|
|
7344
|
-
*
|
|
7345
|
-
*
|
|
7254
|
+
* `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING` is matched by code rather than by message: it is
|
|
7255
|
+
* Node's documented identifier and exact, where the wording is free to change under us. Only an
|
|
7256
|
+
* installed plugin can reach it -- a local path lives outside `node_modules`, where Node strips
|
|
7257
|
+
* types happily, which is what keeps the single-file dev workflow working.
|
|
7346
7258
|
*/
|
|
7347
|
-
const
|
|
7348
|
-
|
|
7349
|
-
|
|
7259
|
+
const importFailure = (origin, cause) => new LoadError({
|
|
7260
|
+
reason: codeOf(cause) === "ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING" ? "plugin-not-compiled" : /cannot find (package|module)/i.test(detail(cause)) ? "plugin-missing-dependency" : "plugin-import-failed",
|
|
7261
|
+
reference: origin.reference,
|
|
7262
|
+
...where(origin),
|
|
7263
|
+
message: detail(cause),
|
|
7264
|
+
cause
|
|
7350
7265
|
});
|
|
7351
|
-
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
exitCode: Schema.Finite
|
|
7266
|
+
/** A module that imported and is not a plugin. */
|
|
7267
|
+
const definitionFailure = (origin, cause, id) => new LoadError({
|
|
7268
|
+
reason: "plugin-invalid-definition",
|
|
7269
|
+
reference: origin.reference,
|
|
7270
|
+
...where(origin),
|
|
7271
|
+
message: detail(cause),
|
|
7272
|
+
...id === void 0 ? {} : { id },
|
|
7273
|
+
cause
|
|
7360
7274
|
});
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
/** Deadline exceeded — expected, model-visible. Carries the partial output produced so far. */
|
|
7367
|
-
var BashTimedOut = class extends Schema.TaggedError()("BashTimedOut", {
|
|
7368
|
-
...outputFields,
|
|
7369
|
-
timeoutSeconds: Schema.Finite
|
|
7370
|
-
}) {};
|
|
7371
|
-
const BashFailure = Schema.Union([BashFailed, BashTimedOut]);
|
|
7372
|
-
const bashDef = tool_exports.define({
|
|
7373
|
-
name: "bash",
|
|
7374
|
-
label: "bash",
|
|
7375
|
-
promptSnippet: "Execute bash commands (ls, grep, find, etc.).",
|
|
7376
|
-
description: `Execute a bash command in the working directory and return its combined stdout/stderr output. Output is truncated to the last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES}KB (whichever is hit first); when truncated, the full output is saved to a temp file. A non-zero exit code is reported as an error carrying the captured output.`,
|
|
7377
|
-
parameters: BashParams,
|
|
7378
|
-
success: BashSuccess,
|
|
7379
|
-
failure: BashFailure,
|
|
7380
|
-
encodeContent: (success) => [{
|
|
7381
|
-
type: "text",
|
|
7382
|
-
text: success.output
|
|
7383
|
-
}],
|
|
7384
|
-
encodeFailureContent: (failure) => [{
|
|
7385
|
-
type: "text",
|
|
7386
|
-
text: failure.output
|
|
7387
|
-
}]
|
|
7275
|
+
const validate = Effect.fn("PluginLoader.validate")(function* (input, origin, builtin = false) {
|
|
7276
|
+
yield* Schema.decodeUnknownEffect(Definition)(input).pipe(Effect.mapError((cause) => definitionFailure(origin, cause)));
|
|
7277
|
+
const plugin = input;
|
|
7278
|
+
if (!builtin && plugin.id.startsWith("codework.")) return yield* definitionFailure(origin, /* @__PURE__ */ new Error("codework namespace is reserved for builtins"), plugin.id);
|
|
7279
|
+
return plugin;
|
|
7388
7280
|
});
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7281
|
+
const Manifest = Schema.Struct({
|
|
7282
|
+
name: Schema.optional(Schema.String),
|
|
7283
|
+
exports: Schema.optional(Schema.Unknown)
|
|
7284
|
+
});
|
|
7285
|
+
const localUrl = Effect.fn("PluginLoader.localUrl")(function* (location, origin) {
|
|
7286
|
+
const escapes = (message) => new SourceError({
|
|
7287
|
+
reason: "plugin-escapes-root",
|
|
7288
|
+
reference: origin.reference,
|
|
7289
|
+
...where(origin),
|
|
7290
|
+
message
|
|
7291
|
+
});
|
|
7292
|
+
if ((yield* fileSystem.stat(location)).type !== "Directory") return { url: yield* Effect.try(() => resolveModule(location, hostPath.dirname(location))) };
|
|
7293
|
+
const manifestPath = hostPath.join(location, "package.json");
|
|
7294
|
+
if (!(yield* fileSystem.exists(manifestPath))) return { url: yield* Effect.try(() => resolveModule("./index", location)) };
|
|
7295
|
+
const manifest = yield* fileSystem.readFileString(manifestPath).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.fromJsonString(Manifest))));
|
|
7296
|
+
if (manifest.exports !== void 0) {
|
|
7297
|
+
const name = manifest.name;
|
|
7298
|
+
if (!name) return yield* escapes("a local package with exports must declare its name");
|
|
7299
|
+
const url = yield* Effect.try(() => resolveModule(name, location));
|
|
7300
|
+
const resolved = yield* fileSystem.realPath(fileURLToPath(url));
|
|
7301
|
+
if (hostPath.relative(yield* fileSystem.realPath(location), resolved).startsWith("..")) return yield* escapes("package export escapes its root");
|
|
7302
|
+
return {
|
|
7303
|
+
url: pathToFileURL(resolved).href,
|
|
7304
|
+
name
|
|
7305
|
+
};
|
|
7402
7306
|
}
|
|
7403
|
-
|
|
7404
|
-
return
|
|
7307
|
+
const url = yield* Effect.try(() => resolveModule(createRequire(pathToFileURL(manifestPath)).resolve(location), location));
|
|
7308
|
+
return manifest.name === void 0 ? { url } : {
|
|
7309
|
+
url,
|
|
7310
|
+
name: manifest.name
|
|
7311
|
+
};
|
|
7312
|
+
});
|
|
7313
|
+
/**
|
|
7314
|
+
* Install a package into the store, importing and validating it **while it is still staged**.
|
|
7315
|
+
*
|
|
7316
|
+
* The validation is passed down rather than done afterwards for one reason: the store publishes by
|
|
7317
|
+
* rename, so a package that turns out not to be a plugin must be rejected before it is published.
|
|
7318
|
+
* Publishing first would make the broken generation the newest marked one, which `resolve` returns
|
|
7319
|
+
* forever after -- and re-running `add` would hit the fast path and return it again.
|
|
7320
|
+
*/
|
|
7321
|
+
const install = Effect.fn("PluginLoader.install")(function* (target, cache, origin, options) {
|
|
7322
|
+
const added = yield* add(target, cache, {
|
|
7323
|
+
from: anchor$1(origin.file, options.hostDir),
|
|
7324
|
+
validate: (fetched) => imported(url(fetched.entrypoint), origin, options.import)
|
|
7325
|
+
});
|
|
7326
|
+
return {
|
|
7327
|
+
url: added.entry.url,
|
|
7328
|
+
...added.entry.version === void 0 ? {} : { version: added.entry.version },
|
|
7329
|
+
generation: added.entry.generation,
|
|
7330
|
+
...added.validated === void 0 ? {} : { plugin: added.validated }
|
|
7331
|
+
};
|
|
7332
|
+
});
|
|
7333
|
+
/**
|
|
7334
|
+
* The directory whose `.npmrc` chain names the artifact a reference resolves to: the project that
|
|
7335
|
+
* declared it. A settings file sits in `<root>/.codework/`, but npm reads a project `.npmrc` at the
|
|
7336
|
+
* package boundary discovered upward from the chain's start -- the root itself -- so anchoring at
|
|
7337
|
+
* the file's own directory would miss it. Absent a declaring file the host context stands in.
|
|
7338
|
+
*/
|
|
7339
|
+
const anchor$1 = (file, fallback) => {
|
|
7340
|
+
if (file === void 0) return fallback;
|
|
7341
|
+
const directory = hostPath.dirname(file);
|
|
7342
|
+
return hostPath.basename(directory) === ".codework" ? hostPath.dirname(directory) : directory;
|
|
7405
7343
|
};
|
|
7406
|
-
/**
|
|
7407
|
-
const
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
yield*
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7344
|
+
/** The default installer, or the one the caller injected. */
|
|
7345
|
+
const installer = (target, origin, options) => options.install === void 0 ? install(target, options.cache, origin, options) : options.install(target, options.cache, anchor$1(origin.file, options.hostDir));
|
|
7346
|
+
/** Import a module and confirm it default-exports a plugin. */
|
|
7347
|
+
const imported = Effect.fn("PluginLoader.imported")(function* (url, origin, load) {
|
|
7348
|
+
const module = yield* Effect.tryPromise({
|
|
7349
|
+
try: () => (load ?? importModule)(url),
|
|
7350
|
+
catch: (cause) => importFailure(origin, cause)
|
|
7351
|
+
});
|
|
7352
|
+
return yield* validate(Predicate.isObject(module) && "default" in module ? module.default : void 0, origin);
|
|
7353
|
+
});
|
|
7354
|
+
/**
|
|
7355
|
+
* Import one entrypoint and confirm it is a plugin, without touching the store.
|
|
7356
|
+
*
|
|
7357
|
+
* This is what a caller hands the store as its `validate`: the artifact is checked while it is
|
|
7358
|
+
* still staged, so a package that turns out not to be a plugin is never published.
|
|
7359
|
+
*/
|
|
7360
|
+
const definition$1 = (entrypoint, reference) => imported(url(entrypoint), {
|
|
7361
|
+
index: 0,
|
|
7362
|
+
reference
|
|
7363
|
+
});
|
|
7364
|
+
/**
|
|
7365
|
+
* Install and import one reference, and report what it turned out to be, without selecting it.
|
|
7366
|
+
* `codework plugin add` uses this to prove a package really is a plugin before it writes the
|
|
7367
|
+
* reference into a settings file: the failure a user gets is the one they can act on — a bad
|
|
7368
|
+
* spec, an install that did not resolve, a module that exports no plugin — rather than a broken
|
|
7369
|
+
* configuration that only fails at the next run.
|
|
7370
|
+
*/
|
|
7371
|
+
const inspect = Effect.fn("PluginLoader.inspect")(function* (reference, options) {
|
|
7372
|
+
const origin = {
|
|
7373
|
+
index: 0,
|
|
7374
|
+
reference,
|
|
7375
|
+
file: options.file
|
|
7419
7376
|
};
|
|
7420
|
-
const
|
|
7377
|
+
const target = yield* parse$2(reference, options.hostDir);
|
|
7378
|
+
const loaded = yield* load$3(target, origin, options);
|
|
7421
7379
|
return {
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
...
|
|
7380
|
+
id: loaded.plugin.id,
|
|
7381
|
+
...loaded.version === void 0 ? {} : { version: loaded.version },
|
|
7382
|
+
...loaded.name === void 0 ? {} : { name: loaded.name }
|
|
7425
7383
|
};
|
|
7426
7384
|
});
|
|
7427
|
-
|
|
7428
|
-
const
|
|
7429
|
-
|
|
7430
|
-
output: snap.content,
|
|
7431
|
-
truncated: false
|
|
7432
|
-
};
|
|
7385
|
+
const load$3 = Effect.fn("PluginLoader.load")(function* (target, origin, options) {
|
|
7386
|
+
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)));
|
|
7387
|
+
const address = target.kind === "local" && options.reload !== void 0 && options.reload > 0 ? `${installed.url}?reload=${options.reload}` : installed.url;
|
|
7433
7388
|
return {
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
...
|
|
7389
|
+
plugin: installed.plugin ?? (yield* imported(address, origin, options.import)),
|
|
7390
|
+
source: origin.reference,
|
|
7391
|
+
..."version" in installed && installed.version !== void 0 ? { version: installed.version } : {},
|
|
7392
|
+
..."generation" in installed && installed.generation !== void 0 ? { generation: installed.generation } : {},
|
|
7393
|
+
...installed.name === void 0 ? {} : { name: installed.name }
|
|
7437
7394
|
};
|
|
7438
|
-
};
|
|
7439
|
-
/**
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7395
|
+
});
|
|
7396
|
+
/**
|
|
7397
|
+
* The version-free, location-anchored spelling of a reference.
|
|
7398
|
+
*
|
|
7399
|
+
* Two references naming the same module compare equal through it: `@acme/x@1.2.0` and `@acme/x`
|
|
7400
|
+
* are one package, and `./plugins/x.ts` is the file it resolves to from the directory that
|
|
7401
|
+
* declared it. Pure -- nothing is installed, imported or read.
|
|
7402
|
+
*/
|
|
7403
|
+
const canonical = (reference, hostDir) => Effect.map(parse$2(reference, hostDir), canonical$1);
|
|
7404
|
+
//#endregion
|
|
7405
|
+
//#region ../harness/src/plugin/catalog.ts
|
|
7406
|
+
/**
|
|
7407
|
+
* A definition carries `setup`; a patch carries `plugin` or `package` and no `setup`. Checking
|
|
7408
|
+
* `setup` first matters because `Plugin` permits extra properties, so a definition holding its
|
|
7409
|
+
* own `plugin` property must not be read as configuration.
|
|
7410
|
+
*
|
|
7411
|
+
* e.g
|
|
7412
|
+
* "acme/tool@1.2.0" → module (load me)
|
|
7413
|
+
* define({id, kind, setup, ...}) → module (I AM the definition — embedder code only)
|
|
7414
|
+
* { plugin: "x", enabled: false } → config (don't load — adjust something already loaded)
|
|
7415
|
+
*
|
|
7416
|
+
* setup present → definition, always (even with a stray plugin: key)
|
|
7417
|
+
* setup absent + plugin:/package: present → config patch
|
|
7418
|
+
* setup absent + neither key → malformed
|
|
7419
|
+
*/
|
|
7420
|
+
const isPatch = (reference) => Predicate.isObject(reference) && !Predicate.isFunction(reference) && !("setup" in reference) && ("plugin" in reference || "package" in reference);
|
|
7421
|
+
/**
|
|
7422
|
+
* The **load pass**: store lookup and ESM import, never the network.
|
|
7423
|
+
*
|
|
7424
|
+
* Runs at boot, at `reload`, and at any exchange that finds a reference it cannot satisfy. It
|
|
7425
|
+
* deliberately knows nothing about `enabled`, options or order -- those are data over modules that
|
|
7426
|
+
* are already loaded, they cost a walk of a short array, and they belong to every exchange.
|
|
7427
|
+
*
|
|
7428
|
+
* Loading is keyed by what a reference resolves to, so naming one module twice imports it once.
|
|
7429
|
+
*/
|
|
7430
|
+
const load$2 = Effect.fn("PluginCatalog.load")(function* (references, options) {
|
|
7431
|
+
const plugins = /* @__PURE__ */ new Map();
|
|
7432
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
7433
|
+
const versions = /* @__PURE__ */ new Map();
|
|
7434
|
+
const origins = /* @__PURE__ */ new Map();
|
|
7435
|
+
const seen = /* @__PURE__ */ new Map();
|
|
7436
|
+
const remember = (plugin, names, from) => {
|
|
7437
|
+
plugins.set(plugin.id, plugin);
|
|
7438
|
+
aliases.set(plugin.id, plugin.id);
|
|
7439
|
+
for (const name of names) aliases.set(name, plugin.id);
|
|
7440
|
+
if (from?.version !== void 0) versions.set(plugin.id, from.version);
|
|
7441
|
+
if (from !== void 0) origins.set(plugin.id, from.origin);
|
|
7442
|
+
};
|
|
7443
|
+
/** Source metadata exists for diagnostics; a silent ID replacement is where it earns that. */
|
|
7444
|
+
const note = (plugin, source) => plugins.has(plugin.id) ? Effect.logDebug(`plugin ${plugin.id} redefined by ${source}; the earlier definition is discarded`) : Effect.void;
|
|
7445
|
+
for (const builtin of options.builtins) remember(yield* validate(builtin, {
|
|
7446
|
+
index: -1,
|
|
7447
|
+
reference: builtin.id
|
|
7448
|
+
}, true), []);
|
|
7449
|
+
for (const [index, reference] of references.entries()) {
|
|
7450
|
+
if (isPatch(reference)) continue;
|
|
7451
|
+
const declared = typeof reference === "string" ? reference : Predicate.hasProperty(reference, "id") ? reference.id : void 0;
|
|
7452
|
+
const named = typeof declared === "string" ? declared : `<plugin object #${index}>`;
|
|
7453
|
+
const origin = {
|
|
7454
|
+
index,
|
|
7455
|
+
reference: named,
|
|
7456
|
+
file: options.declared?.get(named)
|
|
7457
|
+
};
|
|
7458
|
+
if (typeof reference !== "string") {
|
|
7459
|
+
if (typeof declared === "string" && plugins.get(declared) === reference) continue;
|
|
7460
|
+
const plugin = yield* validate(reference, origin);
|
|
7461
|
+
yield* note(plugin, "a supplied object");
|
|
7462
|
+
remember(plugin, []);
|
|
7463
|
+
continue;
|
|
7464
|
+
}
|
|
7465
|
+
const target = yield* parse$2(reference, options.hostDir);
|
|
7466
|
+
const key = target.kind === "local" ? target.path : target.spec;
|
|
7467
|
+
const already = seen.get(key);
|
|
7468
|
+
if (already !== void 0) {
|
|
7469
|
+
aliases.set(reference, already);
|
|
7470
|
+
continue;
|
|
7471
|
+
}
|
|
7472
|
+
const definition = yield* load$3(target, origin, options);
|
|
7473
|
+
yield* note(definition.plugin, definition.version === void 0 ? key : `${key}@${definition.version}`);
|
|
7474
|
+
const names = target.kind === "local" ? [target.path, ...definition.name === void 0 ? [] : [definition.name]] : [canonical$1(target), target.spec];
|
|
7475
|
+
remember(definition.plugin, [reference, ...names], {
|
|
7476
|
+
...definition.version === void 0 ? {} : { version: definition.version },
|
|
7477
|
+
origin: {
|
|
7478
|
+
reference,
|
|
7479
|
+
...definition.generation === void 0 ? {} : { generation: definition.generation },
|
|
7480
|
+
...origin.file === void 0 ? {} : { file: origin.file }
|
|
7481
|
+
}
|
|
7482
|
+
});
|
|
7483
|
+
seen.set(key, definition.plugin.id);
|
|
7484
|
+
}
|
|
7454
7485
|
return {
|
|
7455
|
-
|
|
7456
|
-
|
|
7486
|
+
plugins,
|
|
7487
|
+
aliases,
|
|
7488
|
+
versions,
|
|
7489
|
+
origins
|
|
7457
7490
|
};
|
|
7458
7491
|
});
|
|
7459
|
-
/**
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
...present
|
|
7481
|
-
});
|
|
7482
|
-
const code = yield* Ref.get(exitCode);
|
|
7483
|
-
if (code === null || code !== 0) return yield* new BashFailed({
|
|
7484
|
-
exitCode: code ?? -1,
|
|
7485
|
-
...present
|
|
7492
|
+
/**
|
|
7493
|
+
* The **config pass**: pure data over modules that are already loaded, run at every exchange.
|
|
7494
|
+
*
|
|
7495
|
+
* An array entry is a **module** — a definition object, a path, a `file:` URL, or a package
|
|
7496
|
+
* spec. It takes the position it is written at, so naming a module again moves it.
|
|
7497
|
+
*
|
|
7498
|
+
* An object entry is **configuration** for a plugin an earlier entry (or the built-in list)
|
|
7499
|
+
* already selected, addressed by its ID (`plugin`) or the module string (`package`) that selected
|
|
7500
|
+
* it. It never loads, installs or reorders anything, and a name matching nothing in the selection
|
|
7501
|
+
* is ignored rather than failing the boot — a typo costs a debug line, never a fetch.
|
|
7502
|
+
*
|
|
7503
|
+
* Nothing here touches the disk, which is the whole point of the split: an `options` edit, an
|
|
7504
|
+
* `enabled` flip or a reordering applies at the next exchange without importing anything.
|
|
7505
|
+
*/
|
|
7506
|
+
const select = Effect.fn("PluginCatalog.select")(function* (references, pool) {
|
|
7507
|
+
const operations = /* @__PURE__ */ new Map();
|
|
7508
|
+
const missing = [];
|
|
7509
|
+
const choose = (id, index) => operations.set(id, {
|
|
7510
|
+
enabled: true,
|
|
7511
|
+
index,
|
|
7512
|
+
options: operations.get(id)?.options ?? {}
|
|
7486
7513
|
});
|
|
7514
|
+
for (const [index, reference] of references.entries()) {
|
|
7515
|
+
if (isPatch(reference)) {
|
|
7516
|
+
const named = reference.plugin ?? reference.package;
|
|
7517
|
+
const origin = {
|
|
7518
|
+
index,
|
|
7519
|
+
reference: typeof named === "string" ? named : `<plugin entry #${index}>`
|
|
7520
|
+
};
|
|
7521
|
+
const invalid = reference.plugin !== void 0 && reference.package !== void 0 ? "names both `plugin` and `package`" : typeof named !== "string" || named.length === 0 ? "names neither `plugin` nor `package`" : reference.enabled !== void 0 && typeof reference.enabled !== "boolean" ? "`enabled` is not a boolean" : reference.options !== void 0 && !isRecord$1(reference.options) ? "`options` is not a plain object" : void 0;
|
|
7522
|
+
if (invalid !== void 0) return yield* definitionFailure(origin, /* @__PURE__ */ new Error(`plugin entry ${invalid}`));
|
|
7523
|
+
const id = reference.plugin === void 0 ? pool.aliases.get(reference.package) : reference.plugin;
|
|
7524
|
+
const operation = id === void 0 ? void 0 : operations.get(id);
|
|
7525
|
+
if (id === void 0 || operation === void 0) {
|
|
7526
|
+
yield* Effect.logDebug(`plugin configuration ignored: nothing selected under ${named}`);
|
|
7527
|
+
continue;
|
|
7528
|
+
}
|
|
7529
|
+
operations.set(id, {
|
|
7530
|
+
enabled: reference.enabled ?? operation.enabled,
|
|
7531
|
+
index: operation.index,
|
|
7532
|
+
options: reference.options ?? operation.options
|
|
7533
|
+
});
|
|
7534
|
+
continue;
|
|
7535
|
+
}
|
|
7536
|
+
if (typeof reference !== "string") {
|
|
7537
|
+
const declared = Predicate.hasProperty(reference, "id") ? reference.id : void 0;
|
|
7538
|
+
if (typeof declared === "string" && pool.plugins.has(declared)) choose(declared, index);
|
|
7539
|
+
continue;
|
|
7540
|
+
}
|
|
7541
|
+
const id = pool.aliases.get(reference);
|
|
7542
|
+
if (id === void 0) missing.push(reference);
|
|
7543
|
+
else choose(id, index);
|
|
7544
|
+
}
|
|
7545
|
+
const selection = [];
|
|
7546
|
+
for (const [id, operation] of operations) {
|
|
7547
|
+
if (!operation.enabled) continue;
|
|
7548
|
+
const plugin = pool.plugins.get(id);
|
|
7549
|
+
if (plugin !== void 0) selection.push({
|
|
7550
|
+
plugin,
|
|
7551
|
+
options: Object.freeze(operation.options),
|
|
7552
|
+
index: operation.index
|
|
7553
|
+
});
|
|
7554
|
+
}
|
|
7555
|
+
selection.sort((a, b) => (0, plugin_exports.rank)(a.plugin.kind) - (0, plugin_exports.rank)(b.plugin.kind) || a.index - b.index);
|
|
7487
7556
|
return {
|
|
7488
|
-
|
|
7489
|
-
|
|
7557
|
+
selection: Object.freeze(selection.map(({ plugin, options }) => ({
|
|
7558
|
+
plugin,
|
|
7559
|
+
options
|
|
7560
|
+
}))),
|
|
7561
|
+
missing
|
|
7490
7562
|
};
|
|
7491
|
-
}));
|
|
7492
|
-
const bashHandler = (params) => Effect.gen(function* () {
|
|
7493
|
-
const shell = yield* ToolShell;
|
|
7494
|
-
return shell.stream !== void 0 ? yield* runStreaming(shell.stream, params) : yield* runBuffered(shell, params);
|
|
7495
|
-
});
|
|
7496
|
-
/** The bash tool: definition + handler, wired the testable (def/exec split) way. */
|
|
7497
|
-
const bashTool = tool_exports.implement(bashDef, bashHandler);
|
|
7498
|
-
const bashPlugin = (0, plugin_exports.define)({
|
|
7499
|
-
id: "codework.tool.bash",
|
|
7500
|
-
kind: "tool",
|
|
7501
|
-
setup: Effect.fn("BashPlugin.setup")(function* (ctx) {
|
|
7502
|
-
const shell = yield* io_exports.SandboxIO.Shell;
|
|
7503
|
-
const mounted = fromSandboxShell.pipe(Layer.provide(Layer.succeed(io_exports.SandboxIO.Shell, shell)));
|
|
7504
|
-
ctx.plugin.tools.add(tool_exports.provide(bashTool, mounted));
|
|
7505
|
-
})
|
|
7506
7563
|
});
|
|
7507
|
-
//#endregion
|
|
7508
|
-
//#region ../harness/src/plugin/builtin.ts
|
|
7509
7564
|
/**
|
|
7510
|
-
*
|
|
7511
|
-
*
|
|
7565
|
+
* The exchange-boundary check: has the store moved under this pool?
|
|
7566
|
+
*
|
|
7567
|
+
* Every exchange already re-reads every settings layer from disk, and the config pass already
|
|
7568
|
+
* walks those entries against the loaded set. Two things can be settled right there, and they are
|
|
7569
|
+
* the same question asked twice:
|
|
7570
|
+
*
|
|
7571
|
+
* 1. a settings entry names a module that is not loaded, and the store holds it;
|
|
7572
|
+
* 2. a loaded module is not the newest generation the store has.
|
|
7573
|
+
*
|
|
7574
|
+
* The second is what makes `plugin update` finish. Without it the question is "is this entry
|
|
7575
|
+
* loaded?", the answer after an update is still yes, and the bytes just fetched sit on disk until
|
|
7576
|
+
* something else forces an import.
|
|
7577
|
+
*
|
|
7578
|
+
* Returns the new pool, or nothing when nothing moved -- which is the common case, and costs one
|
|
7579
|
+
* store lookup per filed module.
|
|
7580
|
+
*
|
|
7581
|
+
* **This is not a watcher.** Nothing polls and no fiber exists; it is the observation that we were
|
|
7582
|
+
* already paying for the I/O that would tell us, and discarding the answer. Three bounds keep it
|
|
7583
|
+
* safe: the store only and never the network, so an exchange cannot block on a registry;
|
|
7584
|
+
* resolve-only, so it cannot install; and once, at the top of a snapshot, so an exchange already
|
|
7585
|
+
* running is unaffected.
|
|
7512
7586
|
*/
|
|
7513
|
-
const
|
|
7587
|
+
const follow = Effect.fn("PluginCatalog.follow")(function* (references, pool, options, filed) {
|
|
7588
|
+
let moved = false;
|
|
7589
|
+
for (const reference of references) {
|
|
7590
|
+
if (typeof reference !== "string" || pool.aliases.has(reference)) continue;
|
|
7591
|
+
if (Option.isSome(yield* filed(reference, anchor$1(options.declared?.get(reference), options.hostDir)))) {
|
|
7592
|
+
moved = true;
|
|
7593
|
+
break;
|
|
7594
|
+
}
|
|
7595
|
+
}
|
|
7596
|
+
if (!moved) for (const origin of pool.origins.values()) {
|
|
7597
|
+
if (origin.generation === void 0) continue;
|
|
7598
|
+
const current = yield* filed(origin.reference, anchor$1(origin.file, options.hostDir));
|
|
7599
|
+
if (Option.isSome(current) && current.value.generation !== void 0 && current.value.generation > origin.generation) {
|
|
7600
|
+
moved = true;
|
|
7601
|
+
break;
|
|
7602
|
+
}
|
|
7603
|
+
}
|
|
7604
|
+
if (!moved) return Option.none();
|
|
7605
|
+
const retained = [];
|
|
7606
|
+
for (const origin of pool.origins.values()) {
|
|
7607
|
+
if (references.includes(origin.reference) || Option.isSome(yield* filed(origin.reference, anchor$1(origin.file, options.hostDir)))) {
|
|
7608
|
+
retained.push(origin);
|
|
7609
|
+
continue;
|
|
7610
|
+
}
|
|
7611
|
+
yield* Effect.logWarning(`plugin ${origin.reference} no longer resolves; it is dropped from the loaded set`);
|
|
7612
|
+
}
|
|
7613
|
+
const accumulated = [...Array.from(retained, (origin) => origin.reference), ...references];
|
|
7614
|
+
const known = /* @__PURE__ */ new Map();
|
|
7615
|
+
for (const origin of retained) if (origin.file !== void 0) known.set(origin.reference, origin.file);
|
|
7616
|
+
for (const [reference, file] of options.declared ?? /* @__PURE__ */ new Map()) known.set(reference, file);
|
|
7617
|
+
return Option.some(yield* load$2(accumulated, {
|
|
7618
|
+
...options,
|
|
7619
|
+
declared: known
|
|
7620
|
+
}));
|
|
7621
|
+
});
|
|
7622
|
+
Effect.fn("PluginCatalog.prepare")(function* (references, options) {
|
|
7623
|
+
const pool = yield* load$2(references, options);
|
|
7624
|
+
return (yield* select(references, pool)).selection;
|
|
7625
|
+
});
|
|
7514
7626
|
//#endregion
|
|
7515
7627
|
//#region ../harness/src/runner/coordinator.ts
|
|
7516
7628
|
const make$8 = (options) => Effect.gen(function* () {
|
|
@@ -7652,6 +7764,82 @@ const layer$8 = Layer.effect(Service$6, Effect.gen(function* () {
|
|
|
7652
7764
|
});
|
|
7653
7765
|
}));
|
|
7654
7766
|
//#endregion
|
|
7767
|
+
//#region ../harness/src/settings/schema.ts
|
|
7768
|
+
var schema_exports$1 = /* @__PURE__ */ __exportAll({});
|
|
7769
|
+
import * as import__codeworksh_plugin_settings from "@codeworksh/plugin/settings";
|
|
7770
|
+
__reExport(schema_exports$1, import__codeworksh_plugin_settings);
|
|
7771
|
+
//#endregion
|
|
7772
|
+
//#region ../harness/src/settings/resolve.ts
|
|
7773
|
+
const patternMatches = (pattern, id) => new RegExp(`^${pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`).test(id);
|
|
7774
|
+
function collect(settings, provider, id) {
|
|
7775
|
+
const config = settings.model;
|
|
7776
|
+
const blocks = config.providerOptions?.[provider] ?? {};
|
|
7777
|
+
const patterns = Object.keys(blocks).filter((key) => key !== "*" && key !== id && key.includes("*") && patternMatches(key, id)).sort((a, b) => a.indexOf("*") - b.indexOf("*"));
|
|
7778
|
+
return merge({
|
|
7779
|
+
thinkingLevel: config.thinkingLevel,
|
|
7780
|
+
toolExecution: config.toolExecution,
|
|
7781
|
+
...config.thinkingBudgets === void 0 ? {} : { thinkingBudgets: config.thinkingBudgets }
|
|
7782
|
+
}, config.options, blocks["*"], ...patterns.map((key) => blocks[key]), blocks[id]);
|
|
7783
|
+
}
|
|
7784
|
+
/**
|
|
7785
|
+
* Corrections to the catalog entry itself, applied before lookup.
|
|
7786
|
+
*
|
|
7787
|
+
* `protocol` is here rather than in the request bag because it is model shape: it decides which
|
|
7788
|
+
* wire protocol aikit speaks, and through `Model.optionsKey` which namespace the provider bag is
|
|
7789
|
+
* keyed by. A catalog cannot know it for a local OpenAI-compatible server, and the person
|
|
7790
|
+
* pointing at one does.
|
|
7791
|
+
*/
|
|
7792
|
+
const resolveOverrides = (block) => ({
|
|
7793
|
+
...block.contextWindow === void 0 ? {} : { contextWindow: block.contextWindow },
|
|
7794
|
+
...block.protocol === void 0 ? {} : { protocol: block.protocol }
|
|
7795
|
+
});
|
|
7796
|
+
/** Protocol-independent request attributes can already be captured by State. */
|
|
7797
|
+
function resolveOptions(block) {
|
|
7798
|
+
return {
|
|
7799
|
+
...Object.fromEntries(Object.entries(block).filter(([key]) => Object.hasOwn(schema_exports$1.requestFields, key))),
|
|
7800
|
+
...block.extras === void 0 ? {} : { factoryOptions: merge({}, block.extras) }
|
|
7801
|
+
};
|
|
7802
|
+
}
|
|
7803
|
+
/** The bag key becomes knowable only after lookup at the LLM boundary. */
|
|
7804
|
+
function resolveRequest(block, model) {
|
|
7805
|
+
const bag = Object.fromEntries(Object.entries(block).filter(([key]) => !Object.hasOwn(schema_exports$1.requestFields, key) && ![
|
|
7806
|
+
"thinkingLevel",
|
|
7807
|
+
"toolExecution",
|
|
7808
|
+
"contextWindow",
|
|
7809
|
+
"protocol",
|
|
7810
|
+
"extras"
|
|
7811
|
+
].includes(key)));
|
|
7812
|
+
return {
|
|
7813
|
+
...resolveOptions(block),
|
|
7814
|
+
...Object.keys(bag).length === 0 ? {} : { providerOptions: { [Model.optionsKey(model)]: bag } }
|
|
7815
|
+
};
|
|
7816
|
+
}
|
|
7817
|
+
/**
|
|
7818
|
+
* Harness-owned controls, which never reach a provider.
|
|
7819
|
+
*
|
|
7820
|
+
* Separate from {@link resolveRequest} because these steer the loop itself -- how hard to
|
|
7821
|
+
* think, how to schedule tool calls -- rather than the request body. Runtime bindings
|
|
7822
|
+
* outrank a matched block, which outranks the file's top level.
|
|
7823
|
+
*/
|
|
7824
|
+
const resolveControls = (settings, block, runtime) => ({
|
|
7825
|
+
thinkingLevel: runtime.thinkingLevel ?? block.thinkingLevel ?? settings.model.thinkingLevel,
|
|
7826
|
+
toolExecution: runtime.toolExecution ?? block.toolExecution ?? settings.model.toolExecution
|
|
7827
|
+
});
|
|
7828
|
+
/** Pure configuration composition. Domain selections need not exist in the catalog. */
|
|
7829
|
+
function compose(settings, options = {}, session = {}) {
|
|
7830
|
+
const runtime = merge(options, session);
|
|
7831
|
+
const provider = runtime.provider ?? settings.model.provider;
|
|
7832
|
+
const model = runtime.model ?? settings.model.id;
|
|
7833
|
+
const block = collect(settings, provider, model);
|
|
7834
|
+
return {
|
|
7835
|
+
provider,
|
|
7836
|
+
model,
|
|
7837
|
+
...resolveControls(settings, block, runtime),
|
|
7838
|
+
block,
|
|
7839
|
+
runtime
|
|
7840
|
+
};
|
|
7841
|
+
}
|
|
7842
|
+
//#endregion
|
|
7655
7843
|
//#region ../harness/src/runner/event.ts
|
|
7656
7844
|
const make$7 = Effect.fn("LLMEventPublisher.make")(function* (input) {
|
|
7657
7845
|
const events = yield* Service$16;
|
|
@@ -7801,82 +7989,6 @@ const make$7 = Effect.fn("LLMEventPublisher.make")(function* (input) {
|
|
|
7801
7989
|
};
|
|
7802
7990
|
});
|
|
7803
7991
|
//#endregion
|
|
7804
|
-
//#region ../harness/src/settings/schema.ts
|
|
7805
|
-
var schema_exports$1 = /* @__PURE__ */ __exportAll({});
|
|
7806
|
-
import * as import__codeworksh_plugin_settings from "@codeworksh/plugin/settings";
|
|
7807
|
-
__reExport(schema_exports$1, import__codeworksh_plugin_settings);
|
|
7808
|
-
//#endregion
|
|
7809
|
-
//#region ../harness/src/settings/resolve.ts
|
|
7810
|
-
const patternMatches = (pattern, id) => new RegExp(`^${pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`).test(id);
|
|
7811
|
-
function collect(settings, provider, id) {
|
|
7812
|
-
const config = settings.model;
|
|
7813
|
-
const blocks = config.providerOptions?.[provider] ?? {};
|
|
7814
|
-
const patterns = Object.keys(blocks).filter((key) => key !== "*" && key !== id && key.includes("*") && patternMatches(key, id)).sort((a, b) => a.indexOf("*") - b.indexOf("*"));
|
|
7815
|
-
return merge({
|
|
7816
|
-
thinkingLevel: config.thinkingLevel,
|
|
7817
|
-
toolExecution: config.toolExecution,
|
|
7818
|
-
...config.thinkingBudgets === void 0 ? {} : { thinkingBudgets: config.thinkingBudgets }
|
|
7819
|
-
}, config.options, blocks["*"], ...patterns.map((key) => blocks[key]), blocks[id]);
|
|
7820
|
-
}
|
|
7821
|
-
/**
|
|
7822
|
-
* Corrections to the catalog entry itself, applied before lookup.
|
|
7823
|
-
*
|
|
7824
|
-
* `protocol` is here rather than in the request bag because it is model shape: it decides which
|
|
7825
|
-
* wire protocol aikit speaks, and through `Model.optionsKey` which namespace the provider bag is
|
|
7826
|
-
* keyed by. A catalog cannot know it for a local OpenAI-compatible server, and the person
|
|
7827
|
-
* pointing at one does.
|
|
7828
|
-
*/
|
|
7829
|
-
const resolveOverrides = (block) => ({
|
|
7830
|
-
...block.contextWindow === void 0 ? {} : { contextWindow: block.contextWindow },
|
|
7831
|
-
...block.protocol === void 0 ? {} : { protocol: block.protocol }
|
|
7832
|
-
});
|
|
7833
|
-
/** Protocol-independent request attributes can already be captured by State. */
|
|
7834
|
-
function resolveOptions(block) {
|
|
7835
|
-
return {
|
|
7836
|
-
...Object.fromEntries(Object.entries(block).filter(([key]) => Object.hasOwn(schema_exports$1.requestFields, key))),
|
|
7837
|
-
...block.extras === void 0 ? {} : { factoryOptions: merge({}, block.extras) }
|
|
7838
|
-
};
|
|
7839
|
-
}
|
|
7840
|
-
/** The bag key becomes knowable only after lookup at the LLM boundary. */
|
|
7841
|
-
function resolveRequest(block, model) {
|
|
7842
|
-
const bag = Object.fromEntries(Object.entries(block).filter(([key]) => !Object.hasOwn(schema_exports$1.requestFields, key) && ![
|
|
7843
|
-
"thinkingLevel",
|
|
7844
|
-
"toolExecution",
|
|
7845
|
-
"contextWindow",
|
|
7846
|
-
"protocol",
|
|
7847
|
-
"extras"
|
|
7848
|
-
].includes(key)));
|
|
7849
|
-
return {
|
|
7850
|
-
...resolveOptions(block),
|
|
7851
|
-
...Object.keys(bag).length === 0 ? {} : { providerOptions: { [Model.optionsKey(model)]: bag } }
|
|
7852
|
-
};
|
|
7853
|
-
}
|
|
7854
|
-
/**
|
|
7855
|
-
* Harness-owned controls, which never reach a provider.
|
|
7856
|
-
*
|
|
7857
|
-
* Separate from {@link resolveRequest} because these steer the loop itself -- how hard to
|
|
7858
|
-
* think, how to schedule tool calls -- rather than the request body. Runtime bindings
|
|
7859
|
-
* outrank a matched block, which outranks the file's top level.
|
|
7860
|
-
*/
|
|
7861
|
-
const resolveControls = (settings, block, runtime) => ({
|
|
7862
|
-
thinkingLevel: runtime.thinkingLevel ?? block.thinkingLevel ?? settings.model.thinkingLevel,
|
|
7863
|
-
toolExecution: runtime.toolExecution ?? block.toolExecution ?? settings.model.toolExecution
|
|
7864
|
-
});
|
|
7865
|
-
/** Pure configuration composition. Domain selections need not exist in the catalog. */
|
|
7866
|
-
function compose(settings, options = {}, session = {}) {
|
|
7867
|
-
const runtime = merge(options, session);
|
|
7868
|
-
const provider = runtime.provider ?? settings.model.provider;
|
|
7869
|
-
const model = runtime.model ?? settings.model.id;
|
|
7870
|
-
const block = collect(settings, provider, model);
|
|
7871
|
-
return {
|
|
7872
|
-
provider,
|
|
7873
|
-
model,
|
|
7874
|
-
...resolveControls(settings, block, runtime),
|
|
7875
|
-
block,
|
|
7876
|
-
runtime
|
|
7877
|
-
};
|
|
7878
|
-
}
|
|
7879
|
-
//#endregion
|
|
7880
7992
|
//#region ../harness/src/runner/llm.ts
|
|
7881
7993
|
const runtimeOptions = (input, model, signal) => {
|
|
7882
7994
|
const { activeTools, ...options } = merge(resolveRequest(input.settings ?? {}, model), input.options);
|
|
@@ -7926,17 +8038,6 @@ const providerError = (input, failure) => {
|
|
|
7926
8038
|
});
|
|
7927
8039
|
};
|
|
7928
8040
|
const providerErrorFromUnknown = (input, cause) => providerError(input, AikitFailure.normalize(cause));
|
|
7929
|
-
const modelCatalogFailure = (cause) => {
|
|
7930
|
-
if (typeof cause !== "object" || cause === null || !("name" in cause) || cause.name !== "ModelCatalogLoadError") return;
|
|
7931
|
-
if (!("data" in cause) || typeof cause.data !== "object" || cause.data === null) return void 0;
|
|
7932
|
-
const data = cause.data;
|
|
7933
|
-
if (!("path" in data) || typeof data.path !== "string" || !("message" in data) || typeof data.message !== "string" || !("reason" in data) || data.reason !== "missing" && data.reason !== "unreadable" && data.reason !== "empty" && data.reason !== "invalid") return;
|
|
7934
|
-
return {
|
|
7935
|
-
path: data.path,
|
|
7936
|
-
reason: data.reason,
|
|
7937
|
-
message: data.message
|
|
7938
|
-
};
|
|
7939
|
-
};
|
|
7940
8041
|
/** Read structured terminal data while remaining compatible with older Aikit messages. */
|
|
7941
8042
|
const messageFailure = (message) => {
|
|
7942
8043
|
const candidate = message.failure;
|
|
@@ -7946,14 +8047,7 @@ const messageFailure = (message) => {
|
|
|
7946
8047
|
const resolve$1 = Effect.fn("LLM.resolve")(function* (input) {
|
|
7947
8048
|
const model = yield* Effect.tryPromise({
|
|
7948
8049
|
try: () => llm(input.provider, input.model, resolveOverrides(input.settings ?? {})),
|
|
7949
|
-
catch: (cause) =>
|
|
7950
|
-
const catalog = modelCatalogFailure(cause);
|
|
7951
|
-
return catalog === void 0 ? providerErrorFromUnknown(input, cause) : new ModelCatalogError({
|
|
7952
|
-
path: catalog.path,
|
|
7953
|
-
reason: catalog.reason,
|
|
7954
|
-
detail: catalog.message
|
|
7955
|
-
});
|
|
7956
|
-
}
|
|
8050
|
+
catch: (cause) => loadError(cause) ?? providerErrorFromUnknown(input, cause)
|
|
7957
8051
|
});
|
|
7958
8052
|
if (model === void 0) return yield* new ModelNotFoundError({
|
|
7959
8053
|
provider: input.provider,
|
|
@@ -8623,7 +8717,7 @@ const parse$1 = Effect.fn("Settings.parse")(function* (path, source) {
|
|
|
8623
8717
|
reason: "parse",
|
|
8624
8718
|
detail: `${printParseErrorCode(first.error)} at ${at(source, first.offset)}`
|
|
8625
8719
|
});
|
|
8626
|
-
const document = normalize
|
|
8720
|
+
const document = normalize(json);
|
|
8627
8721
|
if (Predicate.isObject(document) && Predicate.isObject(json) && "plugins" in json) Object.assign(document, { plugins: json.plugins });
|
|
8628
8722
|
return yield* Schema.decodeUnknownEffect(schema_exports$1.Patch)(document).pipe(Effect.mapError((error) => new SettingsError({
|
|
8629
8723
|
path,
|
|
@@ -9300,7 +9394,7 @@ const layer$3 = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
9300
9394
|
}));
|
|
9301
9395
|
//#endregion
|
|
9302
9396
|
//#region ../harness/src/session/live.ts
|
|
9303
|
-
const layer$2 = Layer.provideMerge(layer$3, layer$
|
|
9397
|
+
const layer$2 = Layer.provideMerge(layer$3, layer$24);
|
|
9304
9398
|
//#endregion
|
|
9305
9399
|
//#region ../harness/src/effect/harness.ts
|
|
9306
9400
|
const layer$1 = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
@@ -9361,14 +9455,17 @@ const layer$1 = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
|
9361
9455
|
});
|
|
9362
9456
|
});
|
|
9363
9457
|
const definitions = yield* flatten([...(yield* Ref.get(pool)).plugins.values()]);
|
|
9364
|
-
const database = layer$
|
|
9458
|
+
const database = layer$27(resolveDatabaseLocation(options.database ?? (yield* locationConfig), paths.data));
|
|
9365
9459
|
const configured = yield* loadAll(options.sandboxes ?? [], { hostCwd });
|
|
9366
|
-
const drivers = layer$
|
|
9367
|
-
const sandboxes = layer$
|
|
9460
|
+
const drivers = layer$20(driver_exports.SandboxDriver.withSource(make$16().driver, "core"), driver_exports.SandboxDriver.withSource(make$14().driver, "core"), ...configured);
|
|
9461
|
+
const sandboxes = layer$18({ hostCwd }).pipe(Layer.provideMerge(drivers), Layer.provideMerge(database));
|
|
9368
9462
|
const authFile$1 = authFile(options.home === void 0 ? void 0 : paths.home);
|
|
9369
9463
|
const open = options.llm ?? openWith(authFile$1 === void 0 ? {} : { authFile: authFile$1 });
|
|
9370
9464
|
const loop = layer$4({ request: make$6(open) });
|
|
9371
|
-
return layer$
|
|
9465
|
+
return layer$11.pipe(Layer.provideMerge(layer$8.pipe(Layer.provide(loop))), Layer.provideMerge(layer$5({}, pool, references, followStore, rebuild)), Layer.provideMerge(layer$6(settingsOptions)), Layer.provideMerge(layer$7), Layer.provideMerge(sandboxes), Layer.provideMerge(layer$23), Layer.provideMerge(layer$2), Layer.provideMerge(layer$26), Layer.provideMerge(layer$10(definitions)), Layer.provideMerge(database), Layer.provideMerge(global), Layer.provideMerge(layer$9({
|
|
9466
|
+
home: paths.home,
|
|
9467
|
+
watch: options.watchModels ?? false
|
|
9468
|
+
})));
|
|
9372
9469
|
}));
|
|
9373
9470
|
//#endregion
|
|
9374
9471
|
//#region ../harness/src/effect/sandbox.ts
|
|
@@ -10067,16 +10164,15 @@ const terminalColumns = () => process.stderr.isTTY ? process.stderr.columns ?? 7
|
|
|
10067
10164
|
//#region src/cli/error.ts
|
|
10068
10165
|
/** Bad flag or argument combinations the parser cannot express on its own. */
|
|
10069
10166
|
var InvalidInputError = class extends Schema.TaggedError()("CLI.InvalidInputError", { message: Schema.String }) {};
|
|
10070
|
-
var ModelgenError = class extends Schema.TaggedError()("CLI.ModelgenError", { cause: Schema.Defect() }) {};
|
|
10071
10167
|
var OAuthError = class extends Schema.TaggedError()("CLI.OAuthError", {
|
|
10072
10168
|
message: Schema.String,
|
|
10073
10169
|
cause: Schema.optional(Schema.Defect())
|
|
10074
10170
|
}) {};
|
|
10075
10171
|
const isInvalidInputError = Schema.is(InvalidInputError);
|
|
10076
|
-
const isModelgenError = Schema.is(ModelgenError);
|
|
10077
10172
|
const isOAuthError = Schema.is(OAuthError);
|
|
10078
10173
|
const isProviderError = Schema.is(ProviderError);
|
|
10079
10174
|
const isModelCatalogError = Schema.is(ModelCatalogError);
|
|
10175
|
+
const isModelCatalogRefreshError = Schema.is(RefreshError);
|
|
10080
10176
|
const isModelNotFoundError = Schema.is(ModelNotFoundError);
|
|
10081
10177
|
const isLLMStreamError = Schema.is(LLMStreamError);
|
|
10082
10178
|
const isSandboxProviderError = Schema.is(SandboxProvider.SandboxProviderError);
|
|
@@ -10089,23 +10185,6 @@ const isSettingsError = Schema.is(SettingsError);
|
|
|
10089
10185
|
const isSandboxDriverNotRegisteredError = Schema.is(errors_exports.SandboxError.SandboxDriverNotRegisteredError);
|
|
10090
10186
|
const isSandboxDriverRegistrationError = Schema.is(errors_exports.SandboxError.SandboxDriverRegistrationError);
|
|
10091
10187
|
const isExecutionError = Schema.is(ExecutionError);
|
|
10092
|
-
const providerCategory = (reason) => {
|
|
10093
|
-
switch (reason._tag) {
|
|
10094
|
-
case "Runner.ProviderAuthenticationError": return "authentication";
|
|
10095
|
-
case "Runner.ProviderConfigurationError": return "configuration";
|
|
10096
|
-
case "Runner.ProviderAuthorizationError": return "authorization";
|
|
10097
|
-
case "Runner.ProviderModelUnavailableError": return "model_unavailable";
|
|
10098
|
-
case "Runner.ProviderRateLimitError": return "rate_limited";
|
|
10099
|
-
case "Runner.ProviderQuotaError": return "quota";
|
|
10100
|
-
case "Runner.ProviderInvalidRequestError": return "invalid_request";
|
|
10101
|
-
case "Runner.ProviderContentPolicyError": return "content_policy";
|
|
10102
|
-
case "Runner.ProviderTimeoutError": return "timeout";
|
|
10103
|
-
case "Runner.ProviderTransportError": return "transport";
|
|
10104
|
-
case "Runner.ProviderUnavailableError": return "provider_unavailable";
|
|
10105
|
-
case "Runner.ProviderInvalidResponseError": return "invalid_response";
|
|
10106
|
-
case "Runner.ProviderUnknownError": return "provider";
|
|
10107
|
-
}
|
|
10108
|
-
};
|
|
10109
10188
|
const credentialHint = (provider) => {
|
|
10110
10189
|
switch (provider) {
|
|
10111
10190
|
case "openrouter": return "set OPENROUTER_API_KEY and retry";
|
|
@@ -10142,6 +10221,23 @@ const unknownMessage = (error) => {
|
|
|
10142
10221
|
* usually hand-written in a settings file, so the phase is worth translating.
|
|
10143
10222
|
*/
|
|
10144
10223
|
/** One hint per reason. Lowercase, like everything else a plugin failure prints. */
|
|
10224
|
+
const providerCategory = (reason) => {
|
|
10225
|
+
switch (reason._tag) {
|
|
10226
|
+
case "Runner.ProviderAuthenticationError": return "provider-authentication-error";
|
|
10227
|
+
case "Runner.ProviderConfigurationError": return "provider-configuration-error";
|
|
10228
|
+
case "Runner.ProviderAuthorizationError": return "provider-authorization-error";
|
|
10229
|
+
case "Runner.ProviderModelUnavailableError": return "provider-model-unavailable-error";
|
|
10230
|
+
case "Runner.ProviderRateLimitError": return "provider-rate-limit-error";
|
|
10231
|
+
case "Runner.ProviderQuotaError": return "provider-quota-error";
|
|
10232
|
+
case "Runner.ProviderInvalidRequestError": return "provider-invalid-request-error";
|
|
10233
|
+
case "Runner.ProviderContentPolicyError": return "provider-content-filter-error";
|
|
10234
|
+
case "Runner.ProviderTimeoutError": return "provider-timeout-error";
|
|
10235
|
+
case "Runner.ProviderTransportError": return "provider-transport-error";
|
|
10236
|
+
case "Runner.ProviderUnavailableError": return "provider-unavailable-error";
|
|
10237
|
+
case "Runner.ProviderInvalidResponseError": return "provider-invalid-response-error";
|
|
10238
|
+
case "Runner.ProviderUnknownError": return "provider-unknown-error";
|
|
10239
|
+
}
|
|
10240
|
+
};
|
|
10145
10241
|
const pluginReasonHint = (reason) => {
|
|
10146
10242
|
switch (reason) {
|
|
10147
10243
|
case "plugin-unsupported-source": return "a path entry starts with `./`, `../`, `~/` or `/`; anything else is a package or a git spec, and a remote tarball is not supported";
|
|
@@ -10174,8 +10270,7 @@ const renderError = (error) => {
|
|
|
10174
10270
|
if (isSandboxDriverNotRegisteredError(error)) return `error: sandbox driver "${error.driver}" is not registered (available: ${error.registered?.join(", ") ?? "none"})\n`;
|
|
10175
10271
|
if (isSandboxDriverRegistrationError(error)) return `error: ${error.reason}\n`;
|
|
10176
10272
|
if (isInvalidInputError(error)) return `error: ${error.message}\n`;
|
|
10177
|
-
if (
|
|
10178
|
-
if (isOAuthError(error)) return [`error[oauth]: ${error.message}`, ...error.cause === void 0 ? [] : [`detail: ${unknownMessage(error.cause)}`]].join("\n") + "\n";
|
|
10273
|
+
if (isOAuthError(error)) return [`error[oauth-credential-error]: ${error.message}`, ...error.cause === void 0 ? [] : [`detail: ${unknownMessage(error.cause)}`]].join("\n") + "\n";
|
|
10179
10274
|
if (isSandboxProviderError(error)) return [
|
|
10180
10275
|
`error: SandboxProviderError - ${error.sanitized.message}`,
|
|
10181
10276
|
`driver: ${error.driver}`,
|
|
@@ -10189,8 +10284,7 @@ const renderError = (error) => {
|
|
|
10189
10284
|
"hint: run `codework session link <session-id>` from the directory to anchor it to"
|
|
10190
10285
|
].join("\n") + "\n";
|
|
10191
10286
|
if (isSettingsError(error)) return [
|
|
10192
|
-
`error[settings]: ${error.path}`,
|
|
10193
|
-
`reason: ${error.reason}`,
|
|
10287
|
+
`error[settings-${error.reason}-failed]: ${error.path}`,
|
|
10194
10288
|
`detail: ${error.detail}`,
|
|
10195
10289
|
`hint: ${settingsHint(error.reason)}`
|
|
10196
10290
|
].join("\n") + "\n";
|
|
@@ -10211,9 +10305,10 @@ const renderError = (error) => {
|
|
|
10211
10305
|
...hint === void 0 ? [] : [`hint: ${hint}`]
|
|
10212
10306
|
].join("\n") + "\n";
|
|
10213
10307
|
}
|
|
10214
|
-
if (isModelCatalogError(error)) return [`error[model-catalog]: ${error.message}`, "hint: run `codework models
|
|
10215
|
-
if (
|
|
10216
|
-
if (
|
|
10308
|
+
if (isModelCatalogError(error)) return [`error[model-catalog-${error.reason}]: ${error.message}`, "hint: run `codework models refresh`"].join("\n") + "\n";
|
|
10309
|
+
if (isModelCatalogRefreshError(error)) return [`error[catalog-refresh-failed]: ${error.message}`, "hint: check the network connection and retry"].join("\n") + "\n";
|
|
10310
|
+
if (isModelNotFoundError(error)) return [`error[model-not-found-error]: ${error.message}`, "hint: check the provider/model IDs in models.gen.json or regenerate the catalog"].join("\n") + "\n";
|
|
10311
|
+
if (isLLMStreamError(error)) return `error[llm-stream-error]: ${error.message}\n`;
|
|
10217
10312
|
if (isExecutionError(error)) return [error.type === "aborted" ? `error: ${error.message}` : `error[${error.type}]: ${error.message}`, ...error.status === void 0 ? [] : [`status: ${error.status}`]].join("\n") + "\n";
|
|
10218
10313
|
return `error: ${unknownMessage(error)}\n`;
|
|
10219
10314
|
};
|
|
@@ -10221,6 +10316,6 @@ const reportFailure = (error) => writeError(renderError(error)).pipe(Effect.andT
|
|
|
10221
10316
|
process.exitCode = 1;
|
|
10222
10317
|
})));
|
|
10223
10318
|
//#endregion
|
|
10224
|
-
export {
|
|
10319
|
+
export { refresh$1 as $, stop as A, __toESM as At, check as B, link as C, ID$1 as Ct, list as D, __exportAll as Dt, drivers as E, __esmMin as Et, projectRoot as F, parse$2 as G, resolve$2 as H, anchor$1 as I, LoadError as J, probe as K, canonical as L, Service$1 as M, load$1 as N, register as O, __require as Ot, paths as P, providers as Q, definition$1 as R, get as S, resolve$3 as St, create$2 as T, __commonJSMin as Tt, update as U, required as V, lock as W, StoreError as X, SourceError as Y, models as Z, Connected as _, schema_exports$6 as _t, addUsage as a, Service$14 as at, attach as b, Service$17 as bt, terminalColumns as c, layer$24 as ct, writeOut as d, layer$26 as dt, sync as et, layer as f, LLMEnded as ft, OAuthError$1 as g, instance_exports as gt, Api as h, Prompt as ht, reportFailure as i, sandbox$1 as it, layer$1 as j, resolve as k, __toCommonJS as kt, usage as l, Service$16 as lt, run as m, TurnEnded as mt, OAuthError as n, Service$5 as nt, emptyUsage as o, SessionNotFoundError as ot, make as p, LLMTextDelta as pt, InstallError as q, renderError as r, sandbox as rt, header as s, SessionNotLinkedError as st, InvalidInputError as t, Service$4 as tt, writeError as u, SubscriptionOverflowError as ut, Core as v, layer$27 as vt, relink as w, schema_exports$8 as wt, create$1 as x, authFile as xt, encode as y, path$1 as yt, inspect as z };
|
|
10225
10320
|
|
|
10226
|
-
//# sourceMappingURL=error-
|
|
10321
|
+
//# sourceMappingURL=error-DG5INUkQ.mjs.map
|