@p4code/cli 0.6.12 → 0.6.14
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/dist/bin.mjs +565 -373
- package/dist/client/assets/{DiffPanel-LdVJVEA7.js → DiffPanel-DPdh841e.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-OONhxVl2.js → FilePreviewPanel-BZZA-eBw.js} +2 -2
- package/dist/client/assets/{PreviewPanel-CI0So2xt.js → PreviewPanel-DUW0nki3.js} +2 -2
- package/dist/client/assets/{PullRequestCodeTab-Cw8x7iAG.js → PullRequestCodeTab-LhQpGqLu.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-CKXyKV65.js → fileCommentAnnotations-BK_HySqs.js} +2 -2
- package/dist/client/assets/{index-zIgT1Y-z.js → index-gmiM3V15.js} +16 -6
- package/dist/client/assets/{renderFileChildren-BJJohpja.js → renderFileChildren--G0qrlMR.js} +2 -2
- package/dist/client/assets/{toggle-group-CX-BX4R0.js → toggle-group-qrC3KUiP.js} +2 -2
- package/dist/client/index.html +1 -1
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import * as Effect from "effect/Effect";
|
|
|
8
8
|
import * as Layer from "effect/Layer";
|
|
9
9
|
import { Argument, Command, Flag, GlobalFlag, Prompt } from "effect/unstable/cli";
|
|
10
10
|
import * as CliError from "effect/unstable/cli/CliError";
|
|
11
|
-
import { FetchHttpClient,
|
|
11
|
+
import { FetchHttpClient, HttpBody, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, HttpEffect, HttpRouter, HttpServer, HttpServerRequest, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";
|
|
12
12
|
import * as Context from "effect/Context";
|
|
13
13
|
import * as NodeOS from "node:os";
|
|
14
14
|
import * as NodeNet from "node:net";
|
|
@@ -63,6 +63,8 @@ import * as NodeReadline from "node:readline";
|
|
|
63
63
|
import * as NodeFSP from "node:fs/promises";
|
|
64
64
|
import { access } from "node:fs/promises";
|
|
65
65
|
import * as Clock from "effect/Clock";
|
|
66
|
+
import * as Headers$1 from "effect/unstable/http/Headers";
|
|
67
|
+
import * as HttpRouter$1 from "effect/unstable/http/HttpRouter";
|
|
66
68
|
import * as NodeStream from "@effect/platform-node/NodeStream";
|
|
67
69
|
import * as NodeHttp from "node:http";
|
|
68
70
|
import * as Deferred from "effect/Deferred";
|
|
@@ -266,7 +268,7 @@ const make$124 = () => {
|
|
|
266
268
|
const layer$124 = Layer.sync(NetService, make$124);
|
|
267
269
|
//#endregion
|
|
268
270
|
//#region package.json
|
|
269
|
-
var version$1 = "0.6.
|
|
271
|
+
var version$1 = "0.6.14";
|
|
270
272
|
//#endregion
|
|
271
273
|
//#region src/config.ts
|
|
272
274
|
/**
|
|
@@ -24244,6 +24246,49 @@ function isLoopbackHost(host) {
|
|
|
24244
24246
|
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]" || normalized.startsWith("127.");
|
|
24245
24247
|
}
|
|
24246
24248
|
//#endregion
|
|
24249
|
+
//#region src/httpCompression/HttpResponseCompression.ts
|
|
24250
|
+
const GZIP_MIN_BYTES = 1024;
|
|
24251
|
+
var HttpResponseCompression = class extends Context.Service()("@p4code/cli/httpCompression/HttpResponseCompression") {};
|
|
24252
|
+
const layerNode = Layer.effect(HttpResponseCompression, Effect.gen(function* () {
|
|
24253
|
+
const NodeZlib = yield* Effect.promise(() => import("node:zlib"));
|
|
24254
|
+
return { gzip: (body, options) => {
|
|
24255
|
+
const stream = NodeZlib.createGzip();
|
|
24256
|
+
stream.end(body);
|
|
24257
|
+
return HttpServerResponse$1.raw(stream, options);
|
|
24258
|
+
} };
|
|
24259
|
+
}));
|
|
24260
|
+
const layerBun = Layer.succeed(HttpResponseCompression, { gzip: (body, options) => HttpServerResponse$1.raw(new Response(body).body.pipeThrough(new CompressionStream("gzip")), options) });
|
|
24261
|
+
function acceptsGzip(value) {
|
|
24262
|
+
if (!value) return false;
|
|
24263
|
+
const accepted = new Map(value.split(",").map((entry) => {
|
|
24264
|
+
const [coding = "", ...parameters] = entry.trim().toLowerCase().split(";");
|
|
24265
|
+
const quality = parameters.map((parameter) => parameter.trim().match(/^q=(.+)$/)?.[1]).find((parameter) => parameter !== void 0);
|
|
24266
|
+
return [coding, quality === void 0 ? 1 : Number(quality)];
|
|
24267
|
+
}));
|
|
24268
|
+
return (accepted.get("gzip") ?? accepted.get("*") ?? 0) > 0;
|
|
24269
|
+
}
|
|
24270
|
+
function varyByAcceptEncoding(value) {
|
|
24271
|
+
if (!value) return "Accept-Encoding";
|
|
24272
|
+
const values = new Set(value.split(",").map((entry) => entry.trim().toLowerCase()));
|
|
24273
|
+
return values.has("*") || values.has("accept-encoding") ? value : `${value}, Accept-Encoding`;
|
|
24274
|
+
}
|
|
24275
|
+
const compressHttpResponse = Effect.fnUntraced(function* (response, acceptEncoding) {
|
|
24276
|
+
const body = response.body;
|
|
24277
|
+
if (body._tag !== "Uint8Array" || body.contentLength < GZIP_MIN_BYTES || !body.contentType.startsWith("application/json") || response.headers["content-encoding"]) return response;
|
|
24278
|
+
const variedResponse = HttpServerResponse$1.setHeader(response, "vary", varyByAcceptEncoding(response.headers.vary));
|
|
24279
|
+
if (!acceptsGzip(acceptEncoding)) return variedResponse;
|
|
24280
|
+
const compression = yield* HttpResponseCompression;
|
|
24281
|
+
const headers = Headers$1.set(Headers$1.remove(variedResponse.headers, "content-length"), "content-encoding", "gzip");
|
|
24282
|
+
return compression.gzip(body.body, {
|
|
24283
|
+
status: response.status,
|
|
24284
|
+
statusText: response.statusText,
|
|
24285
|
+
headers,
|
|
24286
|
+
cookies: response.cookies,
|
|
24287
|
+
contentType: body.contentType
|
|
24288
|
+
});
|
|
24289
|
+
});
|
|
24290
|
+
const httpCompressionLayer = HttpRouter$1.middleware((httpEffect) => Effect.flatMap(Effect.all([httpEffect, HttpServerRequest$1.HttpServerRequest]), ([response, request]) => compressHttpResponse(response, request.headers["accept-encoding"])), { global: true });
|
|
24291
|
+
//#endregion
|
|
24247
24292
|
//#region src/persistence/Services/AgentAssets.ts
|
|
24248
24293
|
var AgentAssetRepository = class extends Context.Service()("@p4code/cli/persistence/Services/AgentAssets/AgentAssetRepository") {};
|
|
24249
24294
|
//#endregion
|
|
@@ -28795,6 +28840,321 @@ const hubTunnelClientSocketRoute = Effect.gen(function* () {
|
|
|
28795
28840
|
})));
|
|
28796
28841
|
const layer$96 = HttpRouter.add("GET", `${HUB_TUNNEL_CLIENT_SOCKET_PATH}/*`, hubTunnelClientSocketRoute);
|
|
28797
28842
|
//#endregion
|
|
28843
|
+
//#region src/sync/HubLink.ts
|
|
28844
|
+
/**
|
|
28845
|
+
* Where this server's hub is, what it authenticates with, and whether it syncs
|
|
28846
|
+
* on its own.
|
|
28847
|
+
*
|
|
28848
|
+
* Absent by default. A server with no hub configured runs exactly as it does
|
|
28849
|
+
* today — the sync is skipped rather than failing, because "no hub" is a
|
|
28850
|
+
* complete and normal configuration, not a broken one.
|
|
28851
|
+
*
|
|
28852
|
+
* The record lives in `ServerSecretStore`, which already writes with
|
|
28853
|
+
* restrictive permissions, rather than in the settings file or the database.
|
|
28854
|
+
* The sync mode rides along in that record even though it is not a secret:
|
|
28855
|
+
* connect, disconnect and pause are then one write and one read, and the
|
|
28856
|
+
* settings panel reads one thing instead of joining two. Environment variables
|
|
28857
|
+
* override the link so a container or a service unit can be configured without
|
|
28858
|
+
* a first-run interactive step — the same trade the hub process itself makes
|
|
28859
|
+
* with `P4CODE_HUB_TOKEN_HASHES` — while the mode always comes from the stored
|
|
28860
|
+
* record, so a server configured by environment can still be paused.
|
|
28861
|
+
*
|
|
28862
|
+
* Mutable, deliberately. Resolving this once at layer construction would mean
|
|
28863
|
+
* connecting to a hub did nothing until the server restarted, which is a
|
|
28864
|
+
* miserable thing to discover after typing a token into a form.
|
|
28865
|
+
*
|
|
28866
|
+
* @module sync/HubLink
|
|
28867
|
+
*/
|
|
28868
|
+
/** Secret name holding the hub record as JSON. */
|
|
28869
|
+
const HUB_LINK_SECRET_NAME = "hub-link";
|
|
28870
|
+
var HubLink = class extends Context.Service()("@p4code/cli/sync/HubLink") {};
|
|
28871
|
+
/** The submitted URL or token could not make a usable link. */
|
|
28872
|
+
var InvalidHubLink = class extends Schema$1.TaggedErrorClass()("InvalidHubLink", { detail: Schema$1.String }) {
|
|
28873
|
+
get message() {
|
|
28874
|
+
return this.detail;
|
|
28875
|
+
}
|
|
28876
|
+
};
|
|
28877
|
+
const trimTrailingSlash = (value) => value.replace(/\/+$/u, "");
|
|
28878
|
+
/**
|
|
28879
|
+
* Both halves must be present and the URL parseable. A half-configured link is
|
|
28880
|
+
* treated as no link at all, because a server that thinks it has a hub and
|
|
28881
|
+
* cannot form a request would report every sync as a failure rather than as the
|
|
28882
|
+
* "not configured" it actually is.
|
|
28883
|
+
*/
|
|
28884
|
+
const validateHubLink = (rawBaseUrl, rawToken) => {
|
|
28885
|
+
const trimmed = trimTrailingSlash(rawBaseUrl.trim());
|
|
28886
|
+
const token = rawToken.trim();
|
|
28887
|
+
if (trimmed.length === 0 || token.length === 0) return Option.none();
|
|
28888
|
+
const baseUrl = /^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
28889
|
+
if (!URL.canParse(baseUrl)) return Option.none();
|
|
28890
|
+
const parsed = new URL(baseUrl);
|
|
28891
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return Option.none();
|
|
28892
|
+
return Option.some({
|
|
28893
|
+
baseUrl,
|
|
28894
|
+
token
|
|
28895
|
+
});
|
|
28896
|
+
};
|
|
28897
|
+
const StoredHubLink = Schema$1.fromJsonString(Schema$1.Struct({
|
|
28898
|
+
baseUrl: Schema$1.String,
|
|
28899
|
+
token: Schema$1.String,
|
|
28900
|
+
syncMode: Schema$1.optional(Schema$1.Literals(["auto", "paused"])),
|
|
28901
|
+
shareMode: Schema$1.optional(Schema$1.Literals(["all", "selected"]))
|
|
28902
|
+
}));
|
|
28903
|
+
const decodeStoredHubLink = Schema$1.decodeUnknownExit(StoredHubLink);
|
|
28904
|
+
const encodeStoredHubLink = Schema$1.encodeSync(StoredHubLink);
|
|
28905
|
+
/**
|
|
28906
|
+
* Parse a stored record, tolerating anything that is not the shape we wrote.
|
|
28907
|
+
*
|
|
28908
|
+
* A corrupt secret disables the sync rather than failing the server's start.
|
|
28909
|
+
* Losing sync is recoverable by reconnecting; refusing to boot over it is not
|
|
28910
|
+
* proportionate to what has gone wrong.
|
|
28911
|
+
*/
|
|
28912
|
+
const parseHubLink = (raw) => {
|
|
28913
|
+
const decoded = decodeStoredHubLink(raw);
|
|
28914
|
+
if (decoded._tag !== "Success") return {
|
|
28915
|
+
settings: Option.none(),
|
|
28916
|
+
syncMode: "auto",
|
|
28917
|
+
shareMode: "all"
|
|
28918
|
+
};
|
|
28919
|
+
return {
|
|
28920
|
+
settings: validateHubLink(decoded.value.baseUrl, decoded.value.token),
|
|
28921
|
+
syncMode: decoded.value.syncMode ?? "auto",
|
|
28922
|
+
shareMode: decoded.value.shareMode ?? "all"
|
|
28923
|
+
};
|
|
28924
|
+
};
|
|
28925
|
+
const formatHubLink = (input) => encodeStoredHubLink({
|
|
28926
|
+
baseUrl: Option.match(input.settings, {
|
|
28927
|
+
onNone: () => "",
|
|
28928
|
+
onSome: (settings) => trimTrailingSlash(settings.baseUrl)
|
|
28929
|
+
}),
|
|
28930
|
+
token: Option.match(input.settings, {
|
|
28931
|
+
onNone: () => "",
|
|
28932
|
+
onSome: (s) => s.token
|
|
28933
|
+
}),
|
|
28934
|
+
syncMode: input.syncMode,
|
|
28935
|
+
shareMode: input.shareMode
|
|
28936
|
+
});
|
|
28937
|
+
const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
|
|
28938
|
+
const make$113 = Effect.fn("HubLink.make")(function* (environment) {
|
|
28939
|
+
const secrets = yield* ServerSecretStore;
|
|
28940
|
+
const env = environment ?? process.env;
|
|
28941
|
+
const fromEnv = fromEnvironment(env);
|
|
28942
|
+
const readStored = Effect.fn("HubLink.readStored")(function* () {
|
|
28943
|
+
const stored = yield* secrets.get(HUB_LINK_SECRET_NAME).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
28944
|
+
return Option.match(stored, {
|
|
28945
|
+
onNone: () => ({
|
|
28946
|
+
settings: Option.none(),
|
|
28947
|
+
syncMode: "auto",
|
|
28948
|
+
shareMode: "all"
|
|
28949
|
+
}),
|
|
28950
|
+
onSome: (bytes) => parseHubLink(new TextDecoder().decode(bytes))
|
|
28951
|
+
});
|
|
28952
|
+
});
|
|
28953
|
+
const toState = (record) => Option.isSome(fromEnv) ? {
|
|
28954
|
+
settings: fromEnv,
|
|
28955
|
+
syncMode: record.syncMode,
|
|
28956
|
+
shareMode: record.shareMode,
|
|
28957
|
+
source: "environment"
|
|
28958
|
+
} : {
|
|
28959
|
+
settings: record.settings,
|
|
28960
|
+
syncMode: record.syncMode,
|
|
28961
|
+
shareMode: record.shareMode,
|
|
28962
|
+
source: Option.isSome(record.settings) ? "stored" : "none"
|
|
28963
|
+
};
|
|
28964
|
+
const cache = yield* Ref.make(yield* readStored());
|
|
28965
|
+
const updates = yield* PubSub.sliding({
|
|
28966
|
+
capacity: 1,
|
|
28967
|
+
replay: 1
|
|
28968
|
+
});
|
|
28969
|
+
yield* PubSub.publish(updates, toState(yield* Ref.get(cache)));
|
|
28970
|
+
const persist = Effect.fn("HubLink.persist")(function* (record) {
|
|
28971
|
+
yield* secrets.set(HUB_LINK_SECRET_NAME, new TextEncoder().encode(formatHubLink(record)));
|
|
28972
|
+
yield* Ref.set(cache, record);
|
|
28973
|
+
yield* PubSub.publish(updates, toState(record));
|
|
28974
|
+
return toState(record);
|
|
28975
|
+
});
|
|
28976
|
+
return {
|
|
28977
|
+
current: Ref.get(cache).pipe(Effect.map(toState)),
|
|
28978
|
+
changes: Stream.fromPubSub(updates),
|
|
28979
|
+
connect: (settings) => Effect.gen(function* () {
|
|
28980
|
+
const validated = validateHubLink(settings.baseUrl, settings.token);
|
|
28981
|
+
if (Option.isNone(validated)) return yield* new InvalidHubLink({ detail: "A hub needs an http or https URL and a non-empty token." });
|
|
28982
|
+
const record = yield* Ref.get(cache);
|
|
28983
|
+
return yield* persist({
|
|
28984
|
+
...record,
|
|
28985
|
+
settings: validated
|
|
28986
|
+
});
|
|
28987
|
+
}),
|
|
28988
|
+
disconnect: Effect.gen(function* () {
|
|
28989
|
+
const record = yield* Ref.get(cache);
|
|
28990
|
+
return yield* persist({
|
|
28991
|
+
...record,
|
|
28992
|
+
settings: Option.none()
|
|
28993
|
+
});
|
|
28994
|
+
}),
|
|
28995
|
+
setSyncMode: (mode) => Effect.gen(function* () {
|
|
28996
|
+
const record = yield* Ref.get(cache);
|
|
28997
|
+
return yield* persist({
|
|
28998
|
+
...record,
|
|
28999
|
+
syncMode: mode
|
|
29000
|
+
});
|
|
29001
|
+
}),
|
|
29002
|
+
setShareMode: (mode) => Effect.gen(function* () {
|
|
29003
|
+
const record = yield* Ref.get(cache);
|
|
29004
|
+
return yield* persist({
|
|
29005
|
+
...record,
|
|
29006
|
+
shareMode: mode
|
|
29007
|
+
});
|
|
29008
|
+
})
|
|
29009
|
+
};
|
|
29010
|
+
});
|
|
29011
|
+
const layer$95 = Layer.effect(HubLink, make$113());
|
|
29012
|
+
//#endregion
|
|
29013
|
+
//#region src/persistence/Layers/RemoteTasks.ts
|
|
29014
|
+
/**
|
|
29015
|
+
* The hub's task API, as a client.
|
|
29016
|
+
*
|
|
29017
|
+
* This used to be a `TaskRepository` in its own right, and the board was
|
|
29018
|
+
* either this or the local database. It is now the transport underneath
|
|
29019
|
+
* `SyncedTaskRepository`: local SQLite is a replica of the hub, so nothing
|
|
29020
|
+
* reads from here directly and the board no longer goes dark when the hub is
|
|
29021
|
+
* unreachable. What changed with it is the failure story — an unreachable hub
|
|
29022
|
+
* used to mean no board, and now means a board that is behind.
|
|
29023
|
+
*
|
|
29024
|
+
* The four calls here are what a mirror needs and no more: pull what changed
|
|
29025
|
+
* on the hub, push a whole row, arbitrate a claim, and delete. There is no partial
|
|
29026
|
+
* patch, because a mirror always holds the full row it wants the hub to have
|
|
29027
|
+
* and sending a diff of it would only add a way for the two to disagree.
|
|
29028
|
+
*
|
|
29029
|
+
* @module persistence/Layers/RemoteTasks
|
|
29030
|
+
*/
|
|
29031
|
+
const HUB_REQUEST_TIMEOUT_MS$1 = 15e3;
|
|
29032
|
+
/**
|
|
29033
|
+
* How often the hub writes to an idle `/v1/task-changes` stream. The client
|
|
29034
|
+
* treats two missed beats as a dead connection. Under ten seconds because Bun
|
|
29035
|
+
* closes a connection idle for ten, and proxies in front of a hosted hub are
|
|
29036
|
+
* rarely more patient.
|
|
29037
|
+
*/
|
|
29038
|
+
const HUB_TASK_CHANGES_HEARTBEAT_MS = 8e3;
|
|
29039
|
+
/**
|
|
29040
|
+
* `cursor`, `deletedTaskIds` and `full` are absent from a hub that predates
|
|
29041
|
+
* cursors, which answers every pull with the whole board.
|
|
29042
|
+
*/
|
|
29043
|
+
const TaskListPage = Schema$1.Struct({
|
|
29044
|
+
tasks: Schema$1.Array(Task),
|
|
29045
|
+
deletedTaskIds: Schema$1.optional(Schema$1.Array(TaskId)),
|
|
29046
|
+
cursor: Schema$1.optional(Schema$1.String),
|
|
29047
|
+
full: Schema$1.optional(Schema$1.Boolean)
|
|
29048
|
+
});
|
|
29049
|
+
const ConflictBody$1 = Schema$1.Struct({ current: Task });
|
|
29050
|
+
const decodeTaskListPage = Schema$1.decodeUnknownEffect(TaskListPage);
|
|
29051
|
+
const decodeTask = Schema$1.decodeUnknownEffect(Task);
|
|
29052
|
+
const decodeConflictBody$1 = Schema$1.decodeUnknownEffect(ConflictBody$1);
|
|
29053
|
+
/**
|
|
29054
|
+
* Every failure here is reported as the persistence error type.
|
|
29055
|
+
*
|
|
29056
|
+
* The mirror catches these and carries on — a failed push leaves the row
|
|
29057
|
+
* `pending` and the next pass retries it — so what matters is that the detail
|
|
29058
|
+
* says the hub was the store that would not answer, which is what shows up in
|
|
29059
|
+
* the log when someone asks why their two machines disagree.
|
|
29060
|
+
*/
|
|
29061
|
+
const hubUnavailable = (operation, detail) => new PersistenceSqlError({
|
|
29062
|
+
operation: `HubTaskClient.${operation}`,
|
|
29063
|
+
detail: `The hub board is unavailable: ${detail}`,
|
|
29064
|
+
cause: null
|
|
29065
|
+
});
|
|
29066
|
+
var HubTaskClient = class extends Context.Service()("@p4code/cli/persistence/Layers/RemoteTasks/HubTaskClient") {};
|
|
29067
|
+
const makeHubTaskClient = Effect.gen(function* () {
|
|
29068
|
+
const http = yield* HttpClient.HttpClient;
|
|
29069
|
+
const link = yield* HubLink;
|
|
29070
|
+
const isLinked = link.current.pipe(Effect.map((state) => Option.isSome(state.settings)));
|
|
29071
|
+
const requireSettings = (operation) => Effect.gen(function* () {
|
|
29072
|
+
const state = yield* link.current;
|
|
29073
|
+
if (Option.isNone(state.settings)) return yield* hubUnavailable(operation, "no hub is configured on this server");
|
|
29074
|
+
return state.settings.value;
|
|
29075
|
+
});
|
|
29076
|
+
const send = (operation, settings, request) => http.execute(request.pipe(HttpClientRequest.setHeader("authorization", `Bearer ${settings.token}`), HttpClientRequest.setHeader("accept", "application/json"))).pipe(Effect.timeout(HUB_REQUEST_TIMEOUT_MS$1), Effect.mapError((cause) => hubUnavailable(operation, `${cause._tag ?? "request failed"}`)));
|
|
29077
|
+
const pull = (from) => Effect.gen(function* () {
|
|
29078
|
+
const settings = yield* requireSettings("pull");
|
|
29079
|
+
const resume = from?.baseUrl === settings.baseUrl ? from : void 0;
|
|
29080
|
+
const request = HttpClientRequest.get(`${settings.baseUrl}/tasks${resume === void 0 ? "" : `?since=${encodeURIComponent(resume.cursor)}`}`);
|
|
29081
|
+
const response = yield* send("pull", settings, resume?.etag === void 0 ? request : HttpClientRequest.setHeader(request, "if-none-match", resume.etag));
|
|
29082
|
+
if (response.status === 304) return Option.none();
|
|
29083
|
+
if (response.status !== 200) return yield* hubUnavailable("pull", `status ${response.status}`);
|
|
29084
|
+
const body = yield* response.json.pipe(Effect.flatMap(decodeTaskListPage), Effect.mapError(() => hubUnavailable("pull", "the response did not match the contract")));
|
|
29085
|
+
return Option.some({
|
|
29086
|
+
full: body.full ?? true,
|
|
29087
|
+
tasks: body.tasks,
|
|
29088
|
+
deletedTaskIds: body.deletedTaskIds ?? [],
|
|
29089
|
+
next: body.cursor === void 0 ? void 0 : {
|
|
29090
|
+
baseUrl: settings.baseUrl,
|
|
29091
|
+
cursor: body.cursor,
|
|
29092
|
+
etag: response.headers.etag
|
|
29093
|
+
}
|
|
29094
|
+
});
|
|
29095
|
+
});
|
|
29096
|
+
const watch = Stream.unwrap(Effect.gen(function* () {
|
|
29097
|
+
const settings = yield* requireSettings("watch");
|
|
29098
|
+
const response = yield* http.execute(HttpClientRequest.get(`${settings.baseUrl}/v1/task-changes`).pipe(HttpClientRequest.setHeader("authorization", `Bearer ${settings.token}`))).pipe(Effect.mapError((cause) => hubUnavailable("watch", `${cause._tag ?? "request failed"}`)));
|
|
29099
|
+
if (response.status !== 200) return yield* hubUnavailable("watch", `status ${response.status}`);
|
|
29100
|
+
const relinked = link.changes.pipe(Stream.filter((state) => Option.isNone(state.settings) || state.settings.value.baseUrl !== settings.baseUrl || state.settings.value.token !== settings.token), Stream.runHead);
|
|
29101
|
+
return response.stream.pipe(Stream.mapError(() => hubUnavailable("watch", "the change stream failed")), Stream.decodeText, Stream.splitLines, Stream.timeout(HUB_TASK_CHANGES_HEARTBEAT_MS * 2), Stream.filter((line) => line.includes("\"ready\"") || line.includes("\"changed\"")), Stream.map(() => void 0), Stream.interruptWhen(relinked));
|
|
29102
|
+
}));
|
|
29103
|
+
const put = (row, readableIdPrefix) => Effect.gen(function* () {
|
|
29104
|
+
const settings = yield* requireSettings("put");
|
|
29105
|
+
const response = yield* send("put", settings, HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.put(`${settings.baseUrl}/tasks/${encodeURIComponent(row.taskId)}`), {
|
|
29106
|
+
projectId: row.projectId,
|
|
29107
|
+
repositoryKey: row.repositoryKey,
|
|
29108
|
+
threadId: row.threadId,
|
|
29109
|
+
parentTaskId: row.parentTaskId,
|
|
29110
|
+
title: row.title,
|
|
29111
|
+
body: row.body,
|
|
29112
|
+
status: row.status,
|
|
29113
|
+
priority: row.priority,
|
|
29114
|
+
assignee: row.assignee,
|
|
29115
|
+
labels: row.labels,
|
|
29116
|
+
orderKey: row.orderKey,
|
|
29117
|
+
createdAt: row.createdAt,
|
|
29118
|
+
statusChangedAt: row.statusChangedAt ?? row.updatedAt,
|
|
29119
|
+
readableIdPrefix
|
|
29120
|
+
}));
|
|
29121
|
+
if (response.status !== 200 && response.status !== 201) return yield* hubUnavailable("put", `status ${response.status}`);
|
|
29122
|
+
return yield* response.json.pipe(Effect.flatMap(decodeTask), Effect.mapError(() => hubUnavailable("put", "the response did not match the contract")));
|
|
29123
|
+
});
|
|
29124
|
+
const patchIfStatus = (input) => Effect.gen(function* () {
|
|
29125
|
+
const settings = yield* requireSettings("patchIfStatus");
|
|
29126
|
+
const { taskId, ...fields } = input.patch;
|
|
29127
|
+
const response = yield* send("patchIfStatus", settings, HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.patch(`${settings.baseUrl}/tasks/${encodeURIComponent(taskId)}`), {
|
|
29128
|
+
...fields,
|
|
29129
|
+
expectedStatus: input.expectedStatus
|
|
29130
|
+
}));
|
|
29131
|
+
if (response.status === 404) return { outcome: "missing" };
|
|
29132
|
+
if (response.status === 409) return {
|
|
29133
|
+
outcome: "conflict",
|
|
29134
|
+
current: (yield* response.json.pipe(Effect.flatMap(decodeConflictBody$1), Effect.mapError(() => hubUnavailable("patchIfStatus", "the conflict response did not match the contract")))).current
|
|
29135
|
+
};
|
|
29136
|
+
if (response.status !== 200) return yield* hubUnavailable("patchIfStatus", `status ${response.status}`);
|
|
29137
|
+
return {
|
|
29138
|
+
outcome: "updated",
|
|
29139
|
+
task: yield* response.json.pipe(Effect.flatMap(decodeTask), Effect.mapError(() => hubUnavailable("patchIfStatus", "the response did not match the contract")))
|
|
29140
|
+
};
|
|
29141
|
+
});
|
|
29142
|
+
const remove = (taskId) => Effect.gen(function* () {
|
|
29143
|
+
const settings = yield* requireSettings("remove");
|
|
29144
|
+
const response = yield* send("remove", settings, HttpClientRequest.delete(`${settings.baseUrl}/tasks/${encodeURIComponent(taskId)}`));
|
|
29145
|
+
if (response.status !== 200 && response.status !== 204 && response.status !== 404) return yield* hubUnavailable("remove", `status ${response.status}`);
|
|
29146
|
+
});
|
|
29147
|
+
return {
|
|
29148
|
+
isLinked,
|
|
29149
|
+
pull,
|
|
29150
|
+
watch,
|
|
29151
|
+
put,
|
|
29152
|
+
patchIfStatus,
|
|
29153
|
+
remove
|
|
29154
|
+
};
|
|
29155
|
+
});
|
|
29156
|
+
Layer.effect(HubTaskClient, makeHubTaskClient);
|
|
29157
|
+
//#endregion
|
|
28798
29158
|
//#region src/hub/HubHttpServer.ts
|
|
28799
29159
|
/**
|
|
28800
29160
|
* The hub's HTTP surface.
|
|
@@ -28857,10 +29217,66 @@ const identityRoute = HttpRouter.add("GET", "/v1/identity", respondToHubFailures
|
|
|
28857
29217
|
environmentId: principal.environmentId
|
|
28858
29218
|
}, { headers: { "cache-control": "no-store" } });
|
|
28859
29219
|
})));
|
|
29220
|
+
/**
|
|
29221
|
+
* Minted per process, not per database: a database restored from a backup
|
|
29222
|
+
* carries its old sequence numbers, and a cursor past the restore point would
|
|
29223
|
+
* otherwise skip every write it lost. The cost is one full pull per server
|
|
29224
|
+
* after each hub restart.
|
|
29225
|
+
*/
|
|
29226
|
+
const TASK_CURSOR_EPOCH = NodeCrypto.randomBytes(8).toString("hex");
|
|
29227
|
+
/**
|
|
29228
|
+
* The board, or what changed on it since a cursor.
|
|
29229
|
+
*
|
|
29230
|
+
* `?since=<cursor>` returns rows changed after that cursor plus the ids
|
|
29231
|
+
* deleted since, so a server polling an idle board downloads almost nothing.
|
|
29232
|
+
* An unfiltered list also carries the cursor to start from. A cursor this
|
|
29233
|
+
* process never issued gets the whole board back with `full: true`. Cursors
|
|
29234
|
+
* are `<epoch>-<seq>`; callers treat them as opaque.
|
|
29235
|
+
*/
|
|
29236
|
+
const readTaskPage = Effect.fn("HubHttpServer.readTaskPage")(function* (filter, since) {
|
|
29237
|
+
const tasks = yield* TaskRepository;
|
|
29238
|
+
if (since === void 0 && Object.keys(filter).length > 0) return { tasks: yield* tasks.list(filter) };
|
|
29239
|
+
const sql = yield* SqlClient.SqlClient;
|
|
29240
|
+
const [head] = yield* sql`SELECT COALESCE(MAX(seq), 0) AS seq FROM task_change_log`;
|
|
29241
|
+
const seq = head?.seq ?? 0;
|
|
29242
|
+
const epoch = TASK_CURSOR_EPOCH;
|
|
29243
|
+
const cursor = `${epoch}-${seq}`;
|
|
29244
|
+
const [sinceEpoch, sinceSeqText] = since?.split("-") ?? [];
|
|
29245
|
+
const sinceSeq = Number(sinceSeqText);
|
|
29246
|
+
if (since === void 0 || sinceEpoch !== epoch || sinceSeq > seq) return {
|
|
29247
|
+
tasks: yield* tasks.list({}),
|
|
29248
|
+
deletedTaskIds: [],
|
|
29249
|
+
cursor,
|
|
29250
|
+
full: true
|
|
29251
|
+
};
|
|
29252
|
+
const changes = yield* sql`
|
|
29253
|
+
SELECT task_id AS "taskId", deleted FROM task_change_log
|
|
29254
|
+
WHERE seq > ${sinceSeq} AND seq <= ${seq}
|
|
29255
|
+
ORDER BY seq
|
|
29256
|
+
`;
|
|
29257
|
+
const changed = [];
|
|
29258
|
+
const deletedTaskIds = [];
|
|
29259
|
+
for (const change of changes) {
|
|
29260
|
+
if (change.deleted === 1) {
|
|
29261
|
+
deletedTaskIds.push(change.taskId);
|
|
29262
|
+
continue;
|
|
29263
|
+
}
|
|
29264
|
+
const task = yield* tasks.getById({ taskId: change.taskId });
|
|
29265
|
+
if (Option.isSome(task)) changed.push(task.value);
|
|
29266
|
+
}
|
|
29267
|
+
return {
|
|
29268
|
+
tasks: changed,
|
|
29269
|
+
deletedTaskIds,
|
|
29270
|
+
cursor,
|
|
29271
|
+
full: false
|
|
29272
|
+
};
|
|
29273
|
+
});
|
|
29274
|
+
/** Weak, because gzip may re-encode the same representation on the way out. */
|
|
29275
|
+
const taskPageEtag = (body) => `W/"${NodeCrypto.createHash("sha256").update(body).digest("base64url")}"`;
|
|
29276
|
+
const matchesEtag = (ifNoneMatch, etag) => ifNoneMatch !== void 0 && ifNoneMatch.split(",").map((candidate) => candidate.trim().replace(/^W\//, "")).some((candidate) => candidate === "*" || candidate === etag.replace(/^W\//, ""));
|
|
28860
29277
|
const listTasksRoute = HttpRouter.add("GET", "/tasks", respondToHubFailures(Effect.gen(function* () {
|
|
28861
29278
|
yield* authenticateHubRequest();
|
|
28862
29279
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
28863
|
-
const tasks = yield* TaskRepository;
|
|
28864
29280
|
const url = new URL(request.url, "http://hub.invalid");
|
|
28865
29281
|
const filter = yield* decodeOrInvalid(TaskListFilter, "Query string does not match TaskListFilter.")(Object.fromEntries([
|
|
28866
29282
|
"projectId",
|
|
@@ -28869,8 +29285,39 @@ const listTasksRoute = HttpRouter.add("GET", "/tasks", respondToHubFailures(Effe
|
|
|
28869
29285
|
"assignee",
|
|
28870
29286
|
"query"
|
|
28871
29287
|
].map((key) => [key, url.searchParams.get(key)]).filter(([, value]) => value !== null)));
|
|
28872
|
-
const
|
|
28873
|
-
|
|
29288
|
+
const sinceParam = url.searchParams.get("since");
|
|
29289
|
+
if (sinceParam !== null && (!/^[0-9a-f]{1,32}-\d{1,15}$/.test(sinceParam) || Object.keys(filter).length > 0)) return yield* new InvalidHubRequest({ detail: "`since` must be a cursor from an earlier page and cannot be combined with filters." });
|
|
29290
|
+
const page = yield* readTaskPage(filter, sinceParam ?? void 0);
|
|
29291
|
+
const body = JSON.stringify(page);
|
|
29292
|
+
const etag = taskPageEtag(body);
|
|
29293
|
+
if (matchesEtag(request.headers["if-none-match"], etag)) return HttpServerResponse.empty({
|
|
29294
|
+
status: 304,
|
|
29295
|
+
headers: { etag }
|
|
29296
|
+
});
|
|
29297
|
+
return HttpServerResponse.text(body, {
|
|
29298
|
+
contentType: "application/json",
|
|
29299
|
+
headers: { etag }
|
|
29300
|
+
});
|
|
29301
|
+
})));
|
|
29302
|
+
const taskChangeLine = (type) => new TextEncoder().encode(`{"type":"${type}"}\n`);
|
|
29303
|
+
/**
|
|
29304
|
+
* A nudge per board write, so linked servers pull when something changed
|
|
29305
|
+
* instead of on a timer.
|
|
29306
|
+
*
|
|
29307
|
+
* Carries no rows: the server answers a nudge with the same cursor pull it
|
|
29308
|
+
* always makes, so a nudge that is lost or duplicated costs nothing but
|
|
29309
|
+
* latency. `ready` doubles as a nudge, covering whatever changed while the
|
|
29310
|
+
* server was not listening.
|
|
29311
|
+
*/
|
|
29312
|
+
const taskChangesRoute = HttpRouter.add("GET", "/v1/task-changes", respondToHubFailures(Effect.gen(function* () {
|
|
29313
|
+
yield* authenticateHubRequest();
|
|
29314
|
+
const tasks = yield* TaskRepository;
|
|
29315
|
+
const events = Stream.merge(tasks.streamWithSnapshot.pipe(Stream.map((event) => taskChangeLine(event.type === "snapshot" ? "ready" : "changed"))), Stream.tick(HUB_TASK_CHANGES_HEARTBEAT_MS).pipe(Stream.drop(1), Stream.map(() => taskChangeLine("heartbeat"))));
|
|
29316
|
+
return HttpServerResponse.stream(events, { headers: {
|
|
29317
|
+
"content-type": "application/x-ndjson",
|
|
29318
|
+
"cache-control": "no-store",
|
|
29319
|
+
"x-accel-buffering": "no"
|
|
29320
|
+
} });
|
|
28874
29321
|
})));
|
|
28875
29322
|
const getTaskRoute = HttpRouter.add("GET", "/tasks/:taskId", respondToHubFailures(Effect.gen(function* () {
|
|
28876
29323
|
yield* authenticateHubRequest();
|
|
@@ -29292,7 +29739,7 @@ const releaseFeedLeaseRoute = HttpRouter.add("DELETE", "/feed/lease", respondToH
|
|
|
29292
29739
|
})));
|
|
29293
29740
|
/** Provide around HttpRouter.serve so request fibers share these process-owned services. */
|
|
29294
29741
|
const routingServices = Layer.mergeAll(layer$113, layer$111, layer$102, layer$101).pipe(Layer.provideMerge(layer$112));
|
|
29295
|
-
const layer$
|
|
29742
|
+
const layer$94 = Layer.mergeAll(layer$100, layer$99, layer$103, layer$97, layer$96, layer$110, layer$98, healthRoute, identityRoute, listTasksRoute, taskChangesRoute, getTaskRoute, createTaskRoute, putTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute, listFeedSourcesRoute, upsertFeedSourceRoute, deleteFeedSourceRoute, listFeedRoute, putFeedArticleRoute, putFeedEngagementRoute, markFeedReadRoute, cleanupFeedRoute, setFeedRefreshedAtRoute, acquireFeedLeaseRoute, renewFeedLeaseRoute, releaseFeedLeaseRoute).pipe(Layer.provideMerge(layer$113), Layer.provideMerge(layer$111), Layer.provideMerge(layer$102));
|
|
29296
29743
|
//#endregion
|
|
29297
29744
|
//#region src/hub/Migrations/001_Tasks.ts
|
|
29298
29745
|
/**
|
|
@@ -29789,6 +30236,57 @@ var _022_AnalyticsFacts_default = Effect.gen(function* () {
|
|
|
29789
30236
|
ON hub_analytics_facts(account_id, kind, occurred_at DESC, event_id DESC)`;
|
|
29790
30237
|
});
|
|
29791
30238
|
//#endregion
|
|
30239
|
+
//#region src/hub/Migrations/023_TaskChangeLog.ts
|
|
30240
|
+
/**
|
|
30241
|
+
* One row per task that has ever existed, stamped with the order of its last
|
|
30242
|
+
* change, so a server can ask for "everything since cursor N" instead of the
|
|
30243
|
+
* whole board every poll.
|
|
30244
|
+
*
|
|
30245
|
+
* The sequence is the hub's, not `updated_at`: that timestamp is written by
|
|
30246
|
+
* whichever machine made the edit, and a machine with a slow clock would land
|
|
30247
|
+
* behind a cursor that already moved past it. `AUTOINCREMENT` never reuses a
|
|
30248
|
+
* value, and SQLite serializes writers, so a reader never sees N+1 before N.
|
|
30249
|
+
*
|
|
30250
|
+
* Triggers rather than repository code, because the hub shares
|
|
30251
|
+
* `TaskRepository` with every environment server and only the hub needs this.
|
|
30252
|
+
* A delete keeps its row with `deleted = 1`, which is how another machine
|
|
30253
|
+
* learns the task is gone. The triggers delete then insert rather than
|
|
30254
|
+
* `INSERT OR REPLACE`, because the repository writes with an upsert and its
|
|
30255
|
+
* conflict policy overrides the trigger's, failing the whole write.
|
|
30256
|
+
*/
|
|
30257
|
+
var _023_TaskChangeLog_default = Effect.gen(function* () {
|
|
30258
|
+
const sql = yield* SqlClient.SqlClient;
|
|
30259
|
+
yield* sql`
|
|
30260
|
+
CREATE TABLE task_change_log (
|
|
30261
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
30262
|
+
task_id TEXT NOT NULL UNIQUE,
|
|
30263
|
+
deleted INTEGER NOT NULL DEFAULT 0
|
|
30264
|
+
)
|
|
30265
|
+
`;
|
|
30266
|
+
yield* sql`
|
|
30267
|
+
INSERT INTO task_change_log (task_id)
|
|
30268
|
+
SELECT task_id FROM tasks ORDER BY updated_at, task_id
|
|
30269
|
+
`;
|
|
30270
|
+
yield* sql`
|
|
30271
|
+
CREATE TRIGGER task_change_log_insert AFTER INSERT ON tasks BEGIN
|
|
30272
|
+
DELETE FROM task_change_log WHERE task_id = NEW.task_id;
|
|
30273
|
+
INSERT INTO task_change_log (task_id, deleted) VALUES (NEW.task_id, 0);
|
|
30274
|
+
END
|
|
30275
|
+
`;
|
|
30276
|
+
yield* sql`
|
|
30277
|
+
CREATE TRIGGER task_change_log_update AFTER UPDATE ON tasks BEGIN
|
|
30278
|
+
DELETE FROM task_change_log WHERE task_id = NEW.task_id;
|
|
30279
|
+
INSERT INTO task_change_log (task_id, deleted) VALUES (NEW.task_id, 0);
|
|
30280
|
+
END
|
|
30281
|
+
`;
|
|
30282
|
+
yield* sql`
|
|
30283
|
+
CREATE TRIGGER task_change_log_delete AFTER DELETE ON tasks BEGIN
|
|
30284
|
+
DELETE FROM task_change_log WHERE task_id = OLD.task_id;
|
|
30285
|
+
INSERT INTO task_change_log (task_id, deleted) VALUES (OLD.task_id, 1);
|
|
30286
|
+
END
|
|
30287
|
+
`;
|
|
30288
|
+
});
|
|
30289
|
+
//#endregion
|
|
29792
30290
|
//#region src/hub/Migrations.ts
|
|
29793
30291
|
/**
|
|
29794
30292
|
* Hub migrations.
|
|
@@ -29909,6 +30407,11 @@ const hubMigrationEntries = [
|
|
|
29909
30407
|
22,
|
|
29910
30408
|
"AnalyticsFacts",
|
|
29911
30409
|
_022_AnalyticsFacts_default
|
|
30410
|
+
],
|
|
30411
|
+
[
|
|
30412
|
+
23,
|
|
30413
|
+
"TaskChangeLog",
|
|
30414
|
+
_023_TaskChangeLog_default
|
|
29912
30415
|
]
|
|
29913
30416
|
];
|
|
29914
30417
|
const hubMigrationLoader = Migrator.fromRecord(Object.fromEntries(hubMigrationEntries.map(([id, name, migration]) => [`${id}_${name}`, migration])));
|
|
@@ -30000,7 +30503,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
30000
30503
|
});
|
|
30001
30504
|
if (isRemoteReachableHost(config.host)) yield* Effect.logWarning("Hub is reachable beyond loopback. The bearer token is the only control — repeated auth failures are not rate limited.", { host: config.host });
|
|
30002
30505
|
}));
|
|
30003
|
-
const makeHubLayer = HttpRouter.serve(Layer.mergeAll(layer$
|
|
30506
|
+
const makeHubLayer = HttpRouter.serve(Layer.mergeAll(layer$94, layer$107, layer$109).pipe(Layer.provide(httpCompressionLayer))).pipe(Layer.provideMerge(typeof Bun !== "undefined" ? layerBun : layerNode), Layer.provideMerge(routingServices), Layer.provideMerge(layer$105), Layer.provideMerge(layer$106), Layer.provideMerge(layer$104), Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$115), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
|
|
30004
30507
|
const runHub = Layer.launch(makeHubLayer);
|
|
30005
30508
|
//#endregion
|
|
30006
30509
|
//#region src/atomicWrite.ts
|
|
@@ -30157,7 +30660,7 @@ function stripDefaultServerSettings(current, defaults) {
|
|
|
30157
30660
|
}
|
|
30158
30661
|
return Object.is(current, defaults) ? void 0 : current;
|
|
30159
30662
|
}
|
|
30160
|
-
const make$
|
|
30663
|
+
const make$112 = Effect.gen(function* () {
|
|
30161
30664
|
const { settingsPath } = yield* ServerConfig$1;
|
|
30162
30665
|
const fs = yield* FileSystem.FileSystem;
|
|
30163
30666
|
const pathService = yield* Path$1.Path;
|
|
@@ -30380,7 +30883,7 @@ const make$113 = Effect.gen(function* () {
|
|
|
30380
30883
|
}
|
|
30381
30884
|
};
|
|
30382
30885
|
});
|
|
30383
|
-
const layer$
|
|
30886
|
+
const layer$93 = Layer.effect(ServerSettingsService, make$112);
|
|
30384
30887
|
//#endregion
|
|
30385
30888
|
//#region src/provider/skillWorkspace.ts
|
|
30386
30889
|
/** Ancestors inside the nearest Git checkout, ordered from root to cwd. */
|
|
@@ -31124,7 +31627,7 @@ function entriesOf(config) {
|
|
|
31124
31627
|
return found;
|
|
31125
31628
|
}
|
|
31126
31629
|
var ClaudeMcpFiles = class extends Context.Service()("@p4code/cli/mcp/ClaudeMcpFiles") {};
|
|
31127
|
-
const make$
|
|
31630
|
+
const make$111 = Effect.gen(function* () {
|
|
31128
31631
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
31129
31632
|
const path = yield* Path$1.Path;
|
|
31130
31633
|
const services = yield* Effect.context();
|
|
@@ -31195,7 +31698,7 @@ const make$112 = Effect.gen(function* () {
|
|
|
31195
31698
|
removeProject: (projectDir, name) => removeAt(Effect.succeed(projectFile(projectDir)))(name)
|
|
31196
31699
|
};
|
|
31197
31700
|
});
|
|
31198
|
-
const layer$
|
|
31701
|
+
const layer$92 = Layer.effect(ClaudeMcpFiles, make$111);
|
|
31199
31702
|
Layer.succeed(ClaudeMcpFiles, {
|
|
31200
31703
|
readUser: Effect.succeed([]),
|
|
31201
31704
|
readUserStrict: Effect.succeed([]),
|
|
@@ -31369,7 +31872,7 @@ const decodeClientRegistration = Schema$1.decodeUnknownExit(ClientRegistrationRe
|
|
|
31369
31872
|
const decodeTokenResponse = Schema$1.decodeUnknownExit(TokenResponse);
|
|
31370
31873
|
var McpOAuth = class extends Context.Service()("@p4code/cli/mcp/McpOAuth") {};
|
|
31371
31874
|
const registryError = (detail) => new McpRegistryError({ detail });
|
|
31372
|
-
const make$
|
|
31875
|
+
const make$110 = Effect.gen(function* () {
|
|
31373
31876
|
const config = yield* ServerConfig$1;
|
|
31374
31877
|
const secrets = yield* ServerSecretStore;
|
|
31375
31878
|
const http = yield* HttpClient.HttpClient;
|
|
@@ -31774,7 +32277,7 @@ const make$111 = Effect.gen(function* () {
|
|
|
31774
32277
|
grantIdentityFor
|
|
31775
32278
|
};
|
|
31776
32279
|
});
|
|
31777
|
-
const layer$
|
|
32280
|
+
const layer$91 = Layer.effect(McpOAuth, make$110);
|
|
31778
32281
|
Layer.succeed(McpOAuth, {
|
|
31779
32282
|
statusFor: () => Effect.succeed(Option.none()),
|
|
31780
32283
|
begin: () => Effect.fail(new McpRegistryError({ detail: "OAuth sign-in is not available." })),
|
|
@@ -31829,7 +32332,7 @@ const slotsOf = (registration) => registration.secrets ?? [];
|
|
|
31829
32332
|
*/
|
|
31830
32333
|
const OMITTED_FOR_SIGN_IN = "mcp server omitted: sign-in required";
|
|
31831
32334
|
const SIGN_IN_ACTION = "Connect it in Settings > MCP servers.";
|
|
31832
|
-
const make$
|
|
32335
|
+
const make$109 = Effect.gen(function* () {
|
|
31833
32336
|
const config = yield* ServerConfig$1;
|
|
31834
32337
|
const secrets = yield* ServerSecretStore;
|
|
31835
32338
|
const oauth = yield* McpOAuth;
|
|
@@ -32109,7 +32612,7 @@ const make$110 = Effect.gen(function* () {
|
|
|
32109
32612
|
resolveForSessionAtClaudeUserConfigPath
|
|
32110
32613
|
};
|
|
32111
32614
|
});
|
|
32112
|
-
const layer$
|
|
32615
|
+
const layer$90 = Layer.effect(McpRegistry, make$109);
|
|
32113
32616
|
//#endregion
|
|
32114
32617
|
//#region src/sync/skillDirectory.ts
|
|
32115
32618
|
/**
|
|
@@ -32393,176 +32896,6 @@ const removeAgentDefinition = Effect.fn("agentDefinitionFiles.remove")(function*
|
|
|
32393
32896
|
yield* fileSystem.remove(path.join(input.agentsRoot, input.name), { force: true });
|
|
32394
32897
|
});
|
|
32395
32898
|
//#endregion
|
|
32396
|
-
//#region src/sync/HubLink.ts
|
|
32397
|
-
/**
|
|
32398
|
-
* Where this server's hub is, what it authenticates with, and whether it syncs
|
|
32399
|
-
* on its own.
|
|
32400
|
-
*
|
|
32401
|
-
* Absent by default. A server with no hub configured runs exactly as it does
|
|
32402
|
-
* today — the sync is skipped rather than failing, because "no hub" is a
|
|
32403
|
-
* complete and normal configuration, not a broken one.
|
|
32404
|
-
*
|
|
32405
|
-
* The record lives in `ServerSecretStore`, which already writes with
|
|
32406
|
-
* restrictive permissions, rather than in the settings file or the database.
|
|
32407
|
-
* The sync mode rides along in that record even though it is not a secret:
|
|
32408
|
-
* connect, disconnect and pause are then one write and one read, and the
|
|
32409
|
-
* settings panel reads one thing instead of joining two. Environment variables
|
|
32410
|
-
* override the link so a container or a service unit can be configured without
|
|
32411
|
-
* a first-run interactive step — the same trade the hub process itself makes
|
|
32412
|
-
* with `P4CODE_HUB_TOKEN_HASHES` — while the mode always comes from the stored
|
|
32413
|
-
* record, so a server configured by environment can still be paused.
|
|
32414
|
-
*
|
|
32415
|
-
* Mutable, deliberately. Resolving this once at layer construction would mean
|
|
32416
|
-
* connecting to a hub did nothing until the server restarted, which is a
|
|
32417
|
-
* miserable thing to discover after typing a token into a form.
|
|
32418
|
-
*
|
|
32419
|
-
* @module sync/HubLink
|
|
32420
|
-
*/
|
|
32421
|
-
/** Secret name holding the hub record as JSON. */
|
|
32422
|
-
const HUB_LINK_SECRET_NAME = "hub-link";
|
|
32423
|
-
var HubLink = class extends Context.Service()("@p4code/cli/sync/HubLink") {};
|
|
32424
|
-
/** The submitted URL or token could not make a usable link. */
|
|
32425
|
-
var InvalidHubLink = class extends Schema$1.TaggedErrorClass()("InvalidHubLink", { detail: Schema$1.String }) {
|
|
32426
|
-
get message() {
|
|
32427
|
-
return this.detail;
|
|
32428
|
-
}
|
|
32429
|
-
};
|
|
32430
|
-
const trimTrailingSlash = (value) => value.replace(/\/+$/u, "");
|
|
32431
|
-
/**
|
|
32432
|
-
* Both halves must be present and the URL parseable. A half-configured link is
|
|
32433
|
-
* treated as no link at all, because a server that thinks it has a hub and
|
|
32434
|
-
* cannot form a request would report every sync as a failure rather than as the
|
|
32435
|
-
* "not configured" it actually is.
|
|
32436
|
-
*/
|
|
32437
|
-
const validateHubLink = (rawBaseUrl, rawToken) => {
|
|
32438
|
-
const trimmed = trimTrailingSlash(rawBaseUrl.trim());
|
|
32439
|
-
const token = rawToken.trim();
|
|
32440
|
-
if (trimmed.length === 0 || token.length === 0) return Option.none();
|
|
32441
|
-
const baseUrl = /^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
32442
|
-
if (!URL.canParse(baseUrl)) return Option.none();
|
|
32443
|
-
const parsed = new URL(baseUrl);
|
|
32444
|
-
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return Option.none();
|
|
32445
|
-
return Option.some({
|
|
32446
|
-
baseUrl,
|
|
32447
|
-
token
|
|
32448
|
-
});
|
|
32449
|
-
};
|
|
32450
|
-
const StoredHubLink = Schema$1.fromJsonString(Schema$1.Struct({
|
|
32451
|
-
baseUrl: Schema$1.String,
|
|
32452
|
-
token: Schema$1.String,
|
|
32453
|
-
syncMode: Schema$1.optional(Schema$1.Literals(["auto", "paused"])),
|
|
32454
|
-
shareMode: Schema$1.optional(Schema$1.Literals(["all", "selected"]))
|
|
32455
|
-
}));
|
|
32456
|
-
const decodeStoredHubLink = Schema$1.decodeUnknownExit(StoredHubLink);
|
|
32457
|
-
const encodeStoredHubLink = Schema$1.encodeSync(StoredHubLink);
|
|
32458
|
-
/**
|
|
32459
|
-
* Parse a stored record, tolerating anything that is not the shape we wrote.
|
|
32460
|
-
*
|
|
32461
|
-
* A corrupt secret disables the sync rather than failing the server's start.
|
|
32462
|
-
* Losing sync is recoverable by reconnecting; refusing to boot over it is not
|
|
32463
|
-
* proportionate to what has gone wrong.
|
|
32464
|
-
*/
|
|
32465
|
-
const parseHubLink = (raw) => {
|
|
32466
|
-
const decoded = decodeStoredHubLink(raw);
|
|
32467
|
-
if (decoded._tag !== "Success") return {
|
|
32468
|
-
settings: Option.none(),
|
|
32469
|
-
syncMode: "auto",
|
|
32470
|
-
shareMode: "all"
|
|
32471
|
-
};
|
|
32472
|
-
return {
|
|
32473
|
-
settings: validateHubLink(decoded.value.baseUrl, decoded.value.token),
|
|
32474
|
-
syncMode: decoded.value.syncMode ?? "auto",
|
|
32475
|
-
shareMode: decoded.value.shareMode ?? "all"
|
|
32476
|
-
};
|
|
32477
|
-
};
|
|
32478
|
-
const formatHubLink = (input) => encodeStoredHubLink({
|
|
32479
|
-
baseUrl: Option.match(input.settings, {
|
|
32480
|
-
onNone: () => "",
|
|
32481
|
-
onSome: (settings) => trimTrailingSlash(settings.baseUrl)
|
|
32482
|
-
}),
|
|
32483
|
-
token: Option.match(input.settings, {
|
|
32484
|
-
onNone: () => "",
|
|
32485
|
-
onSome: (s) => s.token
|
|
32486
|
-
}),
|
|
32487
|
-
syncMode: input.syncMode,
|
|
32488
|
-
shareMode: input.shareMode
|
|
32489
|
-
});
|
|
32490
|
-
const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
|
|
32491
|
-
const make$109 = Effect.fn("HubLink.make")(function* (environment) {
|
|
32492
|
-
const secrets = yield* ServerSecretStore;
|
|
32493
|
-
const env = environment ?? process.env;
|
|
32494
|
-
const fromEnv = fromEnvironment(env);
|
|
32495
|
-
const readStored = Effect.fn("HubLink.readStored")(function* () {
|
|
32496
|
-
const stored = yield* secrets.get(HUB_LINK_SECRET_NAME).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
32497
|
-
return Option.match(stored, {
|
|
32498
|
-
onNone: () => ({
|
|
32499
|
-
settings: Option.none(),
|
|
32500
|
-
syncMode: "auto",
|
|
32501
|
-
shareMode: "all"
|
|
32502
|
-
}),
|
|
32503
|
-
onSome: (bytes) => parseHubLink(new TextDecoder().decode(bytes))
|
|
32504
|
-
});
|
|
32505
|
-
});
|
|
32506
|
-
const toState = (record) => Option.isSome(fromEnv) ? {
|
|
32507
|
-
settings: fromEnv,
|
|
32508
|
-
syncMode: record.syncMode,
|
|
32509
|
-
shareMode: record.shareMode,
|
|
32510
|
-
source: "environment"
|
|
32511
|
-
} : {
|
|
32512
|
-
settings: record.settings,
|
|
32513
|
-
syncMode: record.syncMode,
|
|
32514
|
-
shareMode: record.shareMode,
|
|
32515
|
-
source: Option.isSome(record.settings) ? "stored" : "none"
|
|
32516
|
-
};
|
|
32517
|
-
const cache = yield* Ref.make(yield* readStored());
|
|
32518
|
-
const updates = yield* PubSub.sliding({
|
|
32519
|
-
capacity: 1,
|
|
32520
|
-
replay: 1
|
|
32521
|
-
});
|
|
32522
|
-
yield* PubSub.publish(updates, toState(yield* Ref.get(cache)));
|
|
32523
|
-
const persist = Effect.fn("HubLink.persist")(function* (record) {
|
|
32524
|
-
yield* secrets.set(HUB_LINK_SECRET_NAME, new TextEncoder().encode(formatHubLink(record)));
|
|
32525
|
-
yield* Ref.set(cache, record);
|
|
32526
|
-
yield* PubSub.publish(updates, toState(record));
|
|
32527
|
-
return toState(record);
|
|
32528
|
-
});
|
|
32529
|
-
return {
|
|
32530
|
-
current: Ref.get(cache).pipe(Effect.map(toState)),
|
|
32531
|
-
changes: Stream.fromPubSub(updates),
|
|
32532
|
-
connect: (settings) => Effect.gen(function* () {
|
|
32533
|
-
const validated = validateHubLink(settings.baseUrl, settings.token);
|
|
32534
|
-
if (Option.isNone(validated)) return yield* new InvalidHubLink({ detail: "A hub needs an http or https URL and a non-empty token." });
|
|
32535
|
-
const record = yield* Ref.get(cache);
|
|
32536
|
-
return yield* persist({
|
|
32537
|
-
...record,
|
|
32538
|
-
settings: validated
|
|
32539
|
-
});
|
|
32540
|
-
}),
|
|
32541
|
-
disconnect: Effect.gen(function* () {
|
|
32542
|
-
const record = yield* Ref.get(cache);
|
|
32543
|
-
return yield* persist({
|
|
32544
|
-
...record,
|
|
32545
|
-
settings: Option.none()
|
|
32546
|
-
});
|
|
32547
|
-
}),
|
|
32548
|
-
setSyncMode: (mode) => Effect.gen(function* () {
|
|
32549
|
-
const record = yield* Ref.get(cache);
|
|
32550
|
-
return yield* persist({
|
|
32551
|
-
...record,
|
|
32552
|
-
syncMode: mode
|
|
32553
|
-
});
|
|
32554
|
-
}),
|
|
32555
|
-
setShareMode: (mode) => Effect.gen(function* () {
|
|
32556
|
-
const record = yield* Ref.get(cache);
|
|
32557
|
-
return yield* persist({
|
|
32558
|
-
...record,
|
|
32559
|
-
shareMode: mode
|
|
32560
|
-
});
|
|
32561
|
-
})
|
|
32562
|
-
};
|
|
32563
|
-
});
|
|
32564
|
-
const layer$90 = Layer.effect(HubLink, make$109());
|
|
32565
|
-
//#endregion
|
|
32566
32899
|
//#region src/sync/HubAssetClient.ts
|
|
32567
32900
|
/**
|
|
32568
32901
|
* The server's half of the hub's asset API.
|
|
@@ -32576,7 +32909,7 @@ const layer$90 = Layer.effect(HubLink, make$109());
|
|
|
32576
32909
|
*
|
|
32577
32910
|
* @module sync/HubAssetClient
|
|
32578
32911
|
*/
|
|
32579
|
-
const HUB_REQUEST_TIMEOUT_MS
|
|
32912
|
+
const HUB_REQUEST_TIMEOUT_MS = 15e3;
|
|
32580
32913
|
/**
|
|
32581
32914
|
* The hub could not be reached, or answered something unusable.
|
|
32582
32915
|
*
|
|
@@ -32589,9 +32922,9 @@ var HubAssetConflict = class extends Data.TaggedError("HubAssetConflict") {};
|
|
|
32589
32922
|
/** No hub is configured on this server, so there is nothing to talk to. */
|
|
32590
32923
|
var HubNotConfigured = class extends Data.TaggedError("HubNotConfigured") {};
|
|
32591
32924
|
const AssetListPage = Schema$1.Struct({ assets: Schema$1.Array(AgentAsset) });
|
|
32592
|
-
const ConflictBody
|
|
32925
|
+
const ConflictBody = Schema$1.Struct({ current: Schema$1.NullOr(AgentAsset) });
|
|
32593
32926
|
const decodeAssetListPage = Schema$1.decodeUnknownEffect(AssetListPage);
|
|
32594
|
-
const decodeConflictBody
|
|
32927
|
+
const decodeConflictBody = Schema$1.decodeUnknownEffect(ConflictBody);
|
|
32595
32928
|
const decodeAsset = Schema$1.decodeUnknownEffect(AgentAsset);
|
|
32596
32929
|
var HubAssetClient = class extends Context.Service()("@p4code/cli/sync/HubAssetClient") {};
|
|
32597
32930
|
const make$108 = Effect.gen(function* () {
|
|
@@ -32603,7 +32936,7 @@ const make$108 = Effect.gen(function* () {
|
|
|
32603
32936
|
return state.settings.value;
|
|
32604
32937
|
});
|
|
32605
32938
|
const send = Effect.fn("HubAssetClient.send")(function* (operation, settings, request) {
|
|
32606
|
-
return yield* http.execute(request.pipe(HttpClientRequest.setHeader("authorization", `Bearer ${settings.token}`), HttpClientRequest.setHeader("accept", "application/json"))).pipe(Effect.timeout(HUB_REQUEST_TIMEOUT_MS
|
|
32939
|
+
return yield* http.execute(request.pipe(HttpClientRequest.setHeader("authorization", `Bearer ${settings.token}`), HttpClientRequest.setHeader("accept", "application/json"))).pipe(Effect.timeout(HUB_REQUEST_TIMEOUT_MS), Effect.mapError((cause) => new HubUnavailable({
|
|
32607
32940
|
operation,
|
|
32608
32941
|
detail: `${cause._tag ?? "error"}`
|
|
32609
32942
|
})));
|
|
@@ -32623,7 +32956,7 @@ const make$108 = Effect.gen(function* () {
|
|
|
32623
32956
|
* which is what lets a caller show both sides instead of only saying no.
|
|
32624
32957
|
*/
|
|
32625
32958
|
const conflictFrom = Effect.fn("HubAssetClient.conflictFrom")(function* (name, response) {
|
|
32626
|
-
const body = yield* decodeBody("conflict", decodeConflictBody
|
|
32959
|
+
const body = yield* decodeBody("conflict", decodeConflictBody, response).pipe(Effect.orElseSucceed(() => ({ current: null })));
|
|
32627
32960
|
return new HubAssetConflict({
|
|
32628
32961
|
name,
|
|
32629
32962
|
current: body.current === null ? Option.none() : Option.some(body.current)
|
|
@@ -34849,7 +35182,7 @@ const findAgentPreset = (requested) => {
|
|
|
34849
35182
|
};
|
|
34850
35183
|
//#endregion
|
|
34851
35184
|
//#region src/cli/assets.ts
|
|
34852
|
-
const SyncServicesLive = layer$88.pipe(Layer.provideMerge(layer$89), Layer.provideMerge(layer$
|
|
35185
|
+
const SyncServicesLive = layer$88.pipe(Layer.provideMerge(layer$89), Layer.provideMerge(layer$95), Layer.provideMerge(layer$93.pipe(Layer.provide(layer$120))), Layer.provideMerge(layer$120), Layer.provideMerge(FetchHttpClient.layer));
|
|
34853
35186
|
const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
34854
35187
|
const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
|
|
34855
35188
|
return yield* Effect.gen(function* () {
|
|
@@ -58879,18 +59212,6 @@ const acceptBunDeviceSocket = Effect.fn("DeviceHubBunSocket.accept")(function* (
|
|
|
58879
59212
|
}))), MAX_DEVICE_INPUT_BYTES, MAX_DEVICE_OUTPUT_BYTES);
|
|
58880
59213
|
});
|
|
58881
59214
|
//#endregion
|
|
58882
|
-
//#region src/httpCompression/HttpResponseCompression.ts
|
|
58883
|
-
var HttpResponseCompression = class extends Context.Service()("@p4code/cli/httpCompression/HttpResponseCompression") {};
|
|
58884
|
-
const layerNode = Layer.effect(HttpResponseCompression, Effect.gen(function* () {
|
|
58885
|
-
const NodeZlib = yield* Effect.promise(() => import("node:zlib"));
|
|
58886
|
-
return { gzip: (body, options) => {
|
|
58887
|
-
const stream = NodeZlib.createGzip();
|
|
58888
|
-
stream.end(body);
|
|
58889
|
-
return HttpServerResponse$1.raw(stream, options);
|
|
58890
|
-
} };
|
|
58891
|
-
}));
|
|
58892
|
-
const layerBun = Layer.succeed(HttpResponseCompression, { gzip: (body, options) => HttpServerResponse$1.raw(new Response(body).body.pipeThrough(new CompressionStream("gzip")), options) });
|
|
58893
|
-
//#endregion
|
|
58894
59215
|
//#region ../../packages/shared/src/devProxy.ts
|
|
58895
59216
|
/**
|
|
58896
59217
|
* Backend paths the web dev server proxies in single-origin browser dev.
|
|
@@ -60562,7 +60883,6 @@ const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
60562
60883
|
"localhost"
|
|
60563
60884
|
]);
|
|
60564
60885
|
const DESKTOP_RENDERER_ORIGINS = ["p4code://app", "p4code-dev://app"];
|
|
60565
|
-
const GZIP_MIN_BYTES = 1024;
|
|
60566
60886
|
const SVG_CONTENT_SECURITY_POLICY = "default-src 'none'; style-src 'unsafe-inline'; sandbox";
|
|
60567
60887
|
function assetResponseHeaders(filePath) {
|
|
60568
60888
|
return {
|
|
@@ -60571,36 +60891,6 @@ function assetResponseHeaders(filePath) {
|
|
|
60571
60891
|
...filePath.toLowerCase().endsWith(".svg") ? { "Content-Security-Policy": SVG_CONTENT_SECURITY_POLICY } : {}
|
|
60572
60892
|
};
|
|
60573
60893
|
}
|
|
60574
|
-
function acceptsGzip(value) {
|
|
60575
|
-
if (!value) return false;
|
|
60576
|
-
const accepted = new Map(value.split(",").map((entry) => {
|
|
60577
|
-
const [coding = "", ...parameters] = entry.trim().toLowerCase().split(";");
|
|
60578
|
-
const quality = parameters.map((parameter) => parameter.trim().match(/^q=(.+)$/)?.[1]).find((parameter) => parameter !== void 0);
|
|
60579
|
-
return [coding, quality === void 0 ? 1 : Number(quality)];
|
|
60580
|
-
}));
|
|
60581
|
-
return (accepted.get("gzip") ?? accepted.get("*") ?? 0) > 0;
|
|
60582
|
-
}
|
|
60583
|
-
function varyByAcceptEncoding(value) {
|
|
60584
|
-
if (!value) return "Accept-Encoding";
|
|
60585
|
-
const values = new Set(value.split(",").map((entry) => entry.trim().toLowerCase()));
|
|
60586
|
-
return values.has("*") || values.has("accept-encoding") ? value : `${value}, Accept-Encoding`;
|
|
60587
|
-
}
|
|
60588
|
-
const compressHttpResponse = Effect.fnUntraced(function* (response, acceptEncoding) {
|
|
60589
|
-
const body = response.body;
|
|
60590
|
-
if (body._tag !== "Uint8Array" || body.contentLength < GZIP_MIN_BYTES || !body.contentType.startsWith("application/json") || response.headers["content-encoding"]) return response;
|
|
60591
|
-
const variedResponse = HttpServerResponse.setHeader(response, "vary", varyByAcceptEncoding(response.headers.vary));
|
|
60592
|
-
if (!acceptsGzip(acceptEncoding)) return variedResponse;
|
|
60593
|
-
const compression = yield* HttpResponseCompression;
|
|
60594
|
-
const headers = Headers$1.set(Headers$1.remove(variedResponse.headers, "content-length"), "content-encoding", "gzip");
|
|
60595
|
-
return compression.gzip(body.body, {
|
|
60596
|
-
status: response.status,
|
|
60597
|
-
statusText: response.statusText,
|
|
60598
|
-
headers,
|
|
60599
|
-
cookies: response.cookies,
|
|
60600
|
-
contentType: body.contentType
|
|
60601
|
-
});
|
|
60602
|
-
});
|
|
60603
|
-
const httpCompressionLayer = HttpRouter.middleware((httpEffect) => Effect.flatMap(Effect.all([httpEffect, HttpServerRequest.HttpServerRequest]), ([response, request]) => compressHttpResponse(response, request.headers["accept-encoding"])), { global: true });
|
|
60604
60894
|
const browserApiCorsLayer = Layer.unwrap(Effect.gen(function* () {
|
|
60605
60895
|
const config = yield* ServerConfig$1;
|
|
60606
60896
|
const devOrigin = config.devUrl?.origin;
|
|
@@ -109238,7 +109528,7 @@ const COMMON_DEV_PORTS = Object.freeze([
|
|
|
109238
109528
|
8888,
|
|
109239
109529
|
9e3
|
|
109240
109530
|
]);
|
|
109241
|
-
const POLL_INTERVAL$
|
|
109531
|
+
const POLL_INTERVAL$3 = Duration.seconds(3);
|
|
109242
109532
|
const LSOF_TIMEOUT_MS = 5e3;
|
|
109243
109533
|
const WINDOWS_LISTENER_TIMEOUT_MS = 5e3;
|
|
109244
109534
|
const terminalOwnerKey = (owner) => `${owner.threadId}\u0000${owner.terminalId}`;
|
|
@@ -109413,7 +109703,7 @@ const make$61 = Effect.gen(function* PortDiscoveryMake() {
|
|
|
109413
109703
|
lastSnapshot: next
|
|
109414
109704
|
}])) yield* broadcast(next);
|
|
109415
109705
|
}, Effect.catchCause((cause) => Effect.logWarning("preview port scan failed", Cause.pretty(cause))));
|
|
109416
|
-
yield* Effect.forkScoped(pollTick().pipe(Effect.repeat(Schedule.spaced(POLL_INTERVAL$
|
|
109706
|
+
yield* Effect.forkScoped(pollTick().pipe(Effect.repeat(Schedule.spaced(POLL_INTERVAL$3))));
|
|
109417
109707
|
const acquireRetention = Effect.fn("PortDiscovery.retain")(function* () {
|
|
109418
109708
|
if (yield* Ref.modify(stateRef, (state) => [state.retainCount === 0, {
|
|
109419
109709
|
...state,
|
|
@@ -124922,7 +125212,7 @@ const LINEAR_ISSUE_FIELDS = [
|
|
|
124922
125212
|
* a Linear workspace is edited by people at human speed rather than by agents
|
|
124923
125213
|
* at machine speed.
|
|
124924
125214
|
*/
|
|
124925
|
-
const POLL_INTERVAL$
|
|
125215
|
+
const POLL_INTERVAL$2 = Duration.seconds(30);
|
|
124926
125216
|
/** Linear's cap on one page. Asking for more is an error, not a bigger page. */
|
|
124927
125217
|
const LINEAR_PAGE_LIMIT = 250;
|
|
124928
125218
|
const LINEAR_LIST_TOOL = "list_issues";
|
|
@@ -125175,7 +125465,7 @@ const makeLinearTaskRepository = Effect.gen(function* () {
|
|
|
125175
125465
|
taskId
|
|
125176
125466
|
});
|
|
125177
125467
|
}).pipe(Effect.ignoreCause({ log: true }));
|
|
125178
|
-
yield* Effect.forever(pollOnce.pipe(Effect.andThen(Effect.sleep(POLL_INTERVAL$
|
|
125468
|
+
yield* Effect.forever(pollOnce.pipe(Effect.andThen(Effect.sleep(POLL_INTERVAL$2)))).pipe(Effect.forkScoped);
|
|
125179
125469
|
return {
|
|
125180
125470
|
create,
|
|
125181
125471
|
upsert,
|
|
@@ -125199,127 +125489,6 @@ const makeLinearTaskRepository = Effect.gen(function* () {
|
|
|
125199
125489
|
};
|
|
125200
125490
|
});
|
|
125201
125491
|
//#endregion
|
|
125202
|
-
//#region src/persistence/Layers/RemoteTasks.ts
|
|
125203
|
-
/**
|
|
125204
|
-
* The hub's task API, as a client.
|
|
125205
|
-
*
|
|
125206
|
-
* This used to be a `TaskRepository` in its own right, and the board was
|
|
125207
|
-
* either this or the local database. It is now the transport underneath
|
|
125208
|
-
* `SyncedTaskRepository`: local SQLite is a replica of the hub, so nothing
|
|
125209
|
-
* reads from here directly and the board no longer goes dark when the hub is
|
|
125210
|
-
* unreachable. What changed with it is the failure story — an unreachable hub
|
|
125211
|
-
* used to mean no board, and now means a board that is behind.
|
|
125212
|
-
*
|
|
125213
|
-
* The four calls here are what a mirror needs and no more: read the hub's
|
|
125214
|
-
* rows, push a whole row, arbitrate a claim, and delete. There is no partial
|
|
125215
|
-
* patch, because a mirror always holds the full row it wants the hub to have
|
|
125216
|
-
* and sending a diff of it would only add a way for the two to disagree.
|
|
125217
|
-
*
|
|
125218
|
-
* @module persistence/Layers/RemoteTasks
|
|
125219
|
-
*/
|
|
125220
|
-
const HUB_REQUEST_TIMEOUT_MS = 15e3;
|
|
125221
|
-
const TaskListPage = Schema$1.Struct({ tasks: Schema$1.Array(Task) });
|
|
125222
|
-
const ConflictBody = Schema$1.Struct({ current: Task });
|
|
125223
|
-
const decodeTaskListPage = Schema$1.decodeUnknownEffect(TaskListPage);
|
|
125224
|
-
const decodeTask = Schema$1.decodeUnknownEffect(Task);
|
|
125225
|
-
const decodeConflictBody = Schema$1.decodeUnknownEffect(ConflictBody);
|
|
125226
|
-
/**
|
|
125227
|
-
* Every failure here is reported as the persistence error type.
|
|
125228
|
-
*
|
|
125229
|
-
* The mirror catches these and carries on — a failed push leaves the row
|
|
125230
|
-
* `pending` and the next pass retries it — so what matters is that the detail
|
|
125231
|
-
* says the hub was the store that would not answer, which is what shows up in
|
|
125232
|
-
* the log when someone asks why their two machines disagree.
|
|
125233
|
-
*/
|
|
125234
|
-
const hubUnavailable = (operation, detail) => new PersistenceSqlError({
|
|
125235
|
-
operation: `HubTaskClient.${operation}`,
|
|
125236
|
-
detail: `The hub board is unavailable: ${detail}`,
|
|
125237
|
-
cause: null
|
|
125238
|
-
});
|
|
125239
|
-
var HubTaskClient = class extends Context.Service()("@p4code/cli/persistence/Layers/RemoteTasks/HubTaskClient") {};
|
|
125240
|
-
const makeHubTaskClient = Effect.gen(function* () {
|
|
125241
|
-
const http = yield* HttpClient.HttpClient;
|
|
125242
|
-
const link = yield* HubLink;
|
|
125243
|
-
const isLinked = link.current.pipe(Effect.map((state) => Option.isSome(state.settings)));
|
|
125244
|
-
const requireSettings = (operation) => Effect.gen(function* () {
|
|
125245
|
-
const state = yield* link.current;
|
|
125246
|
-
if (Option.isNone(state.settings)) return yield* hubUnavailable(operation, "no hub is configured on this server");
|
|
125247
|
-
return state.settings.value;
|
|
125248
|
-
});
|
|
125249
|
-
const send = (operation, settings, request) => http.execute(request.pipe(HttpClientRequest.setHeader("authorization", `Bearer ${settings.token}`), HttpClientRequest.setHeader("accept", "application/json"))).pipe(Effect.timeout(HUB_REQUEST_TIMEOUT_MS), Effect.mapError((cause) => hubUnavailable(operation, `${cause._tag ?? "request failed"}`)));
|
|
125250
|
-
const list = (filter) => Effect.gen(function* () {
|
|
125251
|
-
const settings = yield* requireSettings("list");
|
|
125252
|
-
const query = new URLSearchParams();
|
|
125253
|
-
for (const key of [
|
|
125254
|
-
"projectId",
|
|
125255
|
-
"repositoryKey",
|
|
125256
|
-
"threadId",
|
|
125257
|
-
"status",
|
|
125258
|
-
"assignee",
|
|
125259
|
-
"query"
|
|
125260
|
-
]) {
|
|
125261
|
-
const value = filter[key];
|
|
125262
|
-
if (value !== void 0) query.set(key, value);
|
|
125263
|
-
}
|
|
125264
|
-
const suffix = query.size === 0 ? "" : `?${query.toString()}`;
|
|
125265
|
-
const response = yield* send("list", settings, HttpClientRequest.get(`${settings.baseUrl}/tasks${suffix}`));
|
|
125266
|
-
if (response.status !== 200) return yield* hubUnavailable("list", `status ${response.status}`);
|
|
125267
|
-
return (yield* response.json.pipe(Effect.flatMap(decodeTaskListPage), Effect.mapError(() => hubUnavailable("list", "the response did not match the contract")))).tasks;
|
|
125268
|
-
});
|
|
125269
|
-
const put = (row, readableIdPrefix) => Effect.gen(function* () {
|
|
125270
|
-
const settings = yield* requireSettings("put");
|
|
125271
|
-
const response = yield* send("put", settings, HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.put(`${settings.baseUrl}/tasks/${encodeURIComponent(row.taskId)}`), {
|
|
125272
|
-
projectId: row.projectId,
|
|
125273
|
-
repositoryKey: row.repositoryKey,
|
|
125274
|
-
threadId: row.threadId,
|
|
125275
|
-
parentTaskId: row.parentTaskId,
|
|
125276
|
-
title: row.title,
|
|
125277
|
-
body: row.body,
|
|
125278
|
-
status: row.status,
|
|
125279
|
-
priority: row.priority,
|
|
125280
|
-
assignee: row.assignee,
|
|
125281
|
-
labels: row.labels,
|
|
125282
|
-
orderKey: row.orderKey,
|
|
125283
|
-
createdAt: row.createdAt,
|
|
125284
|
-
statusChangedAt: row.statusChangedAt ?? row.updatedAt,
|
|
125285
|
-
readableIdPrefix
|
|
125286
|
-
}));
|
|
125287
|
-
if (response.status !== 200 && response.status !== 201) return yield* hubUnavailable("put", `status ${response.status}`);
|
|
125288
|
-
return yield* response.json.pipe(Effect.flatMap(decodeTask), Effect.mapError(() => hubUnavailable("put", "the response did not match the contract")));
|
|
125289
|
-
});
|
|
125290
|
-
const patchIfStatus = (input) => Effect.gen(function* () {
|
|
125291
|
-
const settings = yield* requireSettings("patchIfStatus");
|
|
125292
|
-
const { taskId, ...fields } = input.patch;
|
|
125293
|
-
const response = yield* send("patchIfStatus", settings, HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.patch(`${settings.baseUrl}/tasks/${encodeURIComponent(taskId)}`), {
|
|
125294
|
-
...fields,
|
|
125295
|
-
expectedStatus: input.expectedStatus
|
|
125296
|
-
}));
|
|
125297
|
-
if (response.status === 404) return { outcome: "missing" };
|
|
125298
|
-
if (response.status === 409) return {
|
|
125299
|
-
outcome: "conflict",
|
|
125300
|
-
current: (yield* response.json.pipe(Effect.flatMap(decodeConflictBody), Effect.mapError(() => hubUnavailable("patchIfStatus", "the conflict response did not match the contract")))).current
|
|
125301
|
-
};
|
|
125302
|
-
if (response.status !== 200) return yield* hubUnavailable("patchIfStatus", `status ${response.status}`);
|
|
125303
|
-
return {
|
|
125304
|
-
outcome: "updated",
|
|
125305
|
-
task: yield* response.json.pipe(Effect.flatMap(decodeTask), Effect.mapError(() => hubUnavailable("patchIfStatus", "the response did not match the contract")))
|
|
125306
|
-
};
|
|
125307
|
-
});
|
|
125308
|
-
const remove = (taskId) => Effect.gen(function* () {
|
|
125309
|
-
const settings = yield* requireSettings("remove");
|
|
125310
|
-
const response = yield* send("remove", settings, HttpClientRequest.delete(`${settings.baseUrl}/tasks/${encodeURIComponent(taskId)}`));
|
|
125311
|
-
if (response.status !== 200 && response.status !== 204 && response.status !== 404) return yield* hubUnavailable("remove", `status ${response.status}`);
|
|
125312
|
-
});
|
|
125313
|
-
return {
|
|
125314
|
-
isLinked,
|
|
125315
|
-
list,
|
|
125316
|
-
put,
|
|
125317
|
-
patchIfStatus,
|
|
125318
|
-
remove
|
|
125319
|
-
};
|
|
125320
|
-
});
|
|
125321
|
-
Layer.effect(HubTaskClient, makeHubTaskClient);
|
|
125322
|
-
//#endregion
|
|
125323
125492
|
//#region src/persistence/Layers/TaskSyncStore.ts
|
|
125324
125493
|
/**
|
|
125325
125494
|
* The bookkeeping a mirror needs and a board does not.
|
|
@@ -125516,14 +125685,17 @@ const makeTaskSyncStore = Effect.gen(function* () {
|
|
|
125516
125685
|
* @module persistence/Layers/SyncedTasks
|
|
125517
125686
|
*/
|
|
125518
125687
|
/**
|
|
125519
|
-
* How stale another machine's write may look here.
|
|
125688
|
+
* How stale another machine's write may look here when the hub cannot push.
|
|
125520
125689
|
*
|
|
125521
|
-
*
|
|
125522
|
-
*
|
|
125523
|
-
*
|
|
125524
|
-
*
|
|
125690
|
+
* With the hub's change stream connected, a write elsewhere triggers a pull
|
|
125691
|
+
* right away and the timer is only a safety net, so it can be slow. Without
|
|
125692
|
+
* it (an older hub, or the stream is reconnecting) the timer is the only
|
|
125693
|
+
* signal, and a board left open should still follow along.
|
|
125525
125694
|
*/
|
|
125526
|
-
const
|
|
125695
|
+
const POLL_INTERVAL$1 = Duration.seconds(10);
|
|
125696
|
+
const PUSHED_POLL_INTERVAL = Duration.minutes(5);
|
|
125697
|
+
/** Between change-stream attempts, so an older hub's 404 is not hammered. */
|
|
125698
|
+
const WATCH_RETRY_DELAY = Duration.seconds(30);
|
|
125527
125699
|
const makeSyncedTaskRepository = Effect.gen(function* () {
|
|
125528
125700
|
const local = yield* makeTaskRepository;
|
|
125529
125701
|
const sync = yield* makeTaskSyncStore;
|
|
@@ -125610,6 +125782,11 @@ const makeSyncedTaskRepository = Effect.gen(function* () {
|
|
|
125610
125782
|
* that was just removed; a pull run before either would see this machine's
|
|
125611
125783
|
* own stale absence as the hub's opinion and undo local work.
|
|
125612
125784
|
*/
|
|
125785
|
+
/**
|
|
125786
|
+
* Where the last applied pull left off. Held in memory only: a restart pulls
|
|
125787
|
+
* the whole board once, then follows changes again.
|
|
125788
|
+
*/
|
|
125789
|
+
let position;
|
|
125613
125790
|
const syncOnce = Effect.gen(function* () {
|
|
125614
125791
|
if (!(yield* hub.isLinked)) return;
|
|
125615
125792
|
for (const taskId of yield* sync.listTombstones) yield* hub.remove(taskId).pipe(Effect.andThen(sync.clearTombstone(taskId)), Effect.ignoreCause({ log: true }));
|
|
@@ -125617,21 +125794,36 @@ const makeSyncedTaskRepository = Effect.gen(function* () {
|
|
|
125617
125794
|
const { readableIdPrefix, ...task } = row;
|
|
125618
125795
|
yield* pushInBackground(task, readableIdPrefix ?? "T");
|
|
125619
125796
|
}
|
|
125620
|
-
const
|
|
125797
|
+
const pulled = yield* hub.pull(position);
|
|
125798
|
+
if (Option.isNone(pulled)) return;
|
|
125799
|
+
const page = pulled.value;
|
|
125621
125800
|
const stillPending = new Set((yield* sync.listPending).map((row) => row.taskId));
|
|
125622
125801
|
const tombstoned = new Set(yield* sync.listTombstones);
|
|
125623
125802
|
const syncedIds = yield* sync.listSyncedIds;
|
|
125624
125803
|
const held = new Map((yield* local.list({})).map((task) => [task.taskId, task]));
|
|
125625
125804
|
const seen = /* @__PURE__ */ new Set();
|
|
125626
|
-
for (const task of
|
|
125805
|
+
for (const task of page.tasks) {
|
|
125627
125806
|
seen.add(task.taskId);
|
|
125628
125807
|
if (stillPending.has(task.taskId) || tombstoned.has(task.taskId)) continue;
|
|
125629
125808
|
const current = held.get(task.taskId);
|
|
125630
125809
|
if (current === void 0 || current.updatedAt !== task.updatedAt) yield* adopt(task);
|
|
125631
125810
|
}
|
|
125632
|
-
|
|
125811
|
+
const gone = page.full ? syncedIds.filter((taskId) => !seen.has(taskId)) : page.deletedTaskIds.filter((taskId) => syncedIds.includes(taskId));
|
|
125812
|
+
for (const taskId of gone) yield* local.deleteById({ taskId });
|
|
125813
|
+
position = page.next;
|
|
125633
125814
|
}).pipe(Effect.ignoreCause({ log: true }));
|
|
125634
|
-
yield*
|
|
125815
|
+
const nudges = yield* Queue.sliding(1);
|
|
125816
|
+
let watching = false;
|
|
125817
|
+
yield* hub.watch.pipe(Stream.runForEach(() => Effect.sync(() => {
|
|
125818
|
+
watching = true;
|
|
125819
|
+
}).pipe(Effect.andThen(Queue.offer(nudges, void 0)))), Effect.ignoreCause({ log: "Debug" }), Effect.andThen(Effect.sync(() => {
|
|
125820
|
+
watching = false;
|
|
125821
|
+
}).pipe(Effect.andThen(Queue.offer(nudges, void 0)))), Effect.andThen(Effect.sleep(WATCH_RETRY_DELAY)), Effect.forever, Effect.forkScoped);
|
|
125822
|
+
const nextPass = Effect.gen(function* () {
|
|
125823
|
+
const owed = (yield* sync.listPending).length > 0 || (yield* sync.listTombstones).length > 0;
|
|
125824
|
+
return yield* Queue.take(nudges).pipe(Effect.timeoutOption(watching && !owed ? PUSHED_POLL_INTERVAL : POLL_INTERVAL$1));
|
|
125825
|
+
}).pipe(Effect.catch(() => Effect.sleep(POLL_INTERVAL$1)));
|
|
125826
|
+
yield* Effect.forever(syncOnce.pipe(Effect.andThen(nextPass))).pipe(Effect.forkScoped);
|
|
125635
125827
|
return {
|
|
125636
125828
|
create,
|
|
125637
125829
|
upsert,
|
|
@@ -133683,7 +133875,7 @@ const ProviderAdapterRegistryLive = Layer.effect(ProviderAdapterRegistry, makePr
|
|
|
133683
133875
|
//#endregion
|
|
133684
133876
|
//#region src/provider/ComputerUsePrompts.ts
|
|
133685
133877
|
/** Routing guidance is injected only when the session has P4's Computer Use endpoint. */
|
|
133686
|
-
const COMPUTER_USE_ROUTING_PROMPT = "P4 Computer Use is available for this session through the cua_repl MCP server. When you use it for a native desktop app, do not substitute AppleScript or shell automation unless the user explicitly requests that path. For a named app, start directly with const app = await cua.getApp(exactNameOrBundleId); read the automatically returned state/image and stop if it answers the request. Reuse the handle and batch safe actions; do not repeat selection or unchanged state. For Device Hub, simulator, emulator, or connected-device requests, use cua.listDevices() once, then cua.getDevice({hostId,deviceId,platform}) with exact discovered IDs. Selection already emits initial device state. Reuse its handle; do not print the handle or repeat that state before acting. Never substitute macOS getApp('Simulator'). Device hosts are scoped to this provider environment and can differ from the desktop host. If device discovery, opening, inspection, or interaction reports missing permission or unavailable control, stop and report the exact limitation. Do not retry through screenshots, shell simctl, Xcode tools, or another MCP server. Do not call getState/getApp, control the P4 desktop app, or open device settings to work around a device API failure. Missing consent, unavailable devices, or unsupported operations are limitations to report,
|
|
133878
|
+
const COMPUTER_USE_ROUTING_PROMPT = "P4 Computer Use is available for this session through the cua_repl MCP server. When you use it for a native desktop app, do not substitute AppleScript or shell automation unless the user explicitly requests that path. For a named app, start directly with const app = await cua.getApp(exactNameOrBundleId); read the automatically returned state/image and stop if it answers the request. Reuse the handle and batch safe actions; do not repeat selection or unchanged state. For Device Hub, simulator, emulator, or connected-device requests, use cua.listDevices() once, then cua.getDevice({hostId,deviceId,platform}) with exact discovered IDs, unless the user explicitly requested a different route such as simctl. Selection already emits initial device state. Reuse its handle; do not print the handle or repeat that state before acting. Never substitute macOS getApp('Simulator'). Device hosts are scoped to this provider environment and can differ from the desktop host. If device discovery, opening, inspection, or interaction reports missing permission or unavailable control, stop P4 device interaction and report the exact limitation. Do not silently retry through screenshots, shell simctl, Xcode tools, or another MCP server; an explicit user request for one of those routes is separate authorization to use it. Do not call getState/getApp, control the P4 desktop app, or open device settings to work around a device API failure. Missing consent, unavailable devices, or unsupported operations are P4 limitations to report, never permission to switch P4 hosts or bypass the tool.";
|
|
133687
133879
|
/** The routing note, naming the server the way this session's provider sees it. */
|
|
133688
133880
|
function withComputerUseRouting(input, enabled, serverName = "cua_repl") {
|
|
133689
133881
|
const prompt = serverName === "cua_repl" ? COMPUTER_USE_ROUTING_PROMPT : COMPUTER_USE_ROUTING_PROMPT.replace("the cua_repl MCP server", `the ${serverName} MCP server`);
|
|
@@ -148286,8 +148478,8 @@ const WorkspaceLayerLive = Layer.mergeAll(layer$71, WorkspaceEntriesLayerLive, W
|
|
|
148286
148478
|
const ProjectFaviconResolverLayerLive = layer$66.pipe(Layer.provide(layer$71), Layer.provide(layer$67));
|
|
148287
148479
|
const AuthLayerLive = layer$116.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$120));
|
|
148288
148480
|
const ProviderRuntimeLayerLive = Layer.mergeAll(ProviderSessionReaperLive, ProviderHealthRefresherLive).pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
|
|
148289
|
-
const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(layer$28), Layer.provideMerge(Layer.mergeAll(PullRequestServiceLive, CheckpointingLayerLive, SourceControlProviderRegistryLayerLive)), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderAuthServiceLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive, RoutedTaskRepositoryLive)), Layer.provideMerge(Layer.mergeAll(PersistenceLayerLive, layer$
|
|
148290
|
-
const UsageLayerLive = layer$25.pipe(Layer.provide(layer$
|
|
148481
|
+
const RuntimeCoreDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(layer$28), Layer.provideMerge(Layer.mergeAll(PullRequestServiceLive, CheckpointingLayerLive, SourceControlProviderRegistryLayerLive)), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderAuthServiceLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive, RoutedTaskRepositoryLive)), Layer.provideMerge(Layer.mergeAll(PersistenceLayerLive, layer$95)), Layer.provideMerge(layer$84), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(layer$61), Layer.provideMerge(Layer.mergeAll(layer$60, layer$73.pipe(Layer.provide(layer$86)), layer$75, layer$70)), Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$57), Layer.provideMerge(layer$58), Layer.provideMerge(layer$59)).pipe(Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(AntigravityInstallation.layer), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$63), Layer.provideMerge(JevWorkspaceAdvisorLive.pipe(Layer.provide(TypeSafeClientLive))), Layer.provideMerge(layer$93.pipe(Layer.provide(layer$120))), Layer.provideMerge(SupervisorSecretsLive.pipe(Layer.provide(layer$120))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$85), Layer.provideMerge(layer$69), Layer.provideMerge(remoteComputerUseBindingsLayer.pipe(Layer.provide(layer$95))), Layer.provideMerge(layer$78), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$5.pipe(Layer.provideMerge(LinearClientLive), Layer.provideMerge(Layer.mergeAll(LinearMcpClientLive, TrackerSecretsLive)), Layer.provideMerge(Layer.mergeAll(layer$90, layer$21).pipe(Layer.provideMerge(layer$91), Layer.provideMerge(layer$92))))), Layer.provideMerge(layer$120));
|
|
148482
|
+
const UsageLayerLive = layer$25.pipe(Layer.provide(layer$93.pipe(Layer.provide(layer$120))));
|
|
148291
148483
|
const RuntimeDependenciesLive = layer$24.pipe(Layer.provideMerge(RuntimeCoreDependenciesLive)).pipe(Layer.provideMerge(UsageLayerLive), Layer.provideMerge(layer$12), Layer.provideMerge(layer$11), Layer.provideMerge(layer$10), Layer.provideMerge(layer$81), Layer.provideMerge(layer$83), Layer.provideMerge(layer$82), Layer.provide(layer$124));
|
|
148292
148484
|
/**
|
|
148293
148485
|
* Hub asset sync.
|
|
@@ -148296,7 +148488,7 @@ const RuntimeDependenciesLive = layer$24.pipe(Layer.provideMerge(RuntimeCoreDepe
|
|
|
148296
148488
|
* not-configured shape, and every call reports that rather than failing — so
|
|
148297
148489
|
* this costs a server with no hub nothing but a layer.
|
|
148298
148490
|
*/
|
|
148299
|
-
const AssetSyncLive = layer$88.pipe(Layer.provideMerge(layer$77), Layer.provideMerge(layer$89), Layer.provideMerge(layer$
|
|
148491
|
+
const AssetSyncLive = layer$88.pipe(Layer.provideMerge(layer$77), Layer.provideMerge(layer$89), Layer.provideMerge(layer$95));
|
|
148300
148492
|
const RuntimeServicesLive = layer$72.pipe(Layer.provideMerge(layer$1), Layer.provideMerge(AssetSyncLive), Layer.provideMerge(layer$23), Layer.provideMerge(layer$22.pipe(Layer.provide(layer$53), Layer.provide(ProviderSessionDirectoryLayerLive), Layer.provide(ProjectionProjectRepositoryLive))), Layer.provideMerge(RuntimeDependenciesLive));
|
|
148301
148493
|
const makeRoutesLayer = Layer.mergeAll(Layer.mergeAll(HttpApiBuilder.layer(EnvironmentHttpApi).pipe(Layer.provide(authHttpApiLayer), Layer.provide(orchestrationHttpApiLayer), Layer.provide(pullRequestHttpApiLayer), Layer.provide(serverEnvironmentHttpApiLayer), Layer.provide(environmentAuthenticatedAuthLayer)), otlpTracesProxyRouteLayer, assetRouteLayer, mcpOAuthCallbackRouteLayer, staticAndDevRouteLayer, websocketRpcRouteLayer), layer$4.pipe(Layer.provide(layer$6)), layer$68, layer).pipe(Layer.provide(ProjectionProjectRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(ProjectionThreadRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(layer$67), Layer.provide(layer$28), Layer.provide(PullRequestServiceLive), Layer.provide(layer$20), Layer.provide(layer$79), Layer.provide(layer$26), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
|
|
148302
148494
|
const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
|