@schoolai/shipyard 3.0.0 → 3.1.0-rc.20260414.1
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/{chunk-MBTQ4BO5.js → chunk-5B74AXTS.js} +2 -2
- package/dist/{chunk-QMLEP5EE.js → chunk-E747QA24.js} +3 -2
- package/dist/{chunk-QMLEP5EE.js.map → chunk-E747QA24.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/{login-DX7RFKIX.js → login-ZGDBOWV3.js} +3 -3
- package/dist/{serve-LL6LUMZI.js → serve-XSXFIZDE.js} +1210 -364
- package/dist/serve-XSXFIZDE.js.map +1 -0
- package/dist/{start-G6D7XOCQ.js → start-GDBTWN5J.js} +4 -4
- package/package.json +1 -1
- package/dist/serve-LL6LUMZI.js.map +0 -1
- /package/dist/{chunk-MBTQ4BO5.js.map → chunk-5B74AXTS.js.map} +0 -0
- /package/dist/{login-DX7RFKIX.js.map → login-ZGDBOWV3.js.map} +0 -0
- /package/dist/{start-G6D7XOCQ.js.map → start-GDBTWN5J.js.map} +0 -0
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
VaultKeyPutRequestSchema,
|
|
38
38
|
VaultKeyPutResponseSchema,
|
|
39
39
|
classifyClaudeCodeCompatibility
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-E747QA24.js";
|
|
41
41
|
import {
|
|
42
42
|
loadAuthToken
|
|
43
43
|
} from "./chunk-IHSXN66C.js";
|
|
@@ -76,7 +76,7 @@ import { execSync } from "child_process";
|
|
|
76
76
|
import { existsSync as existsSync7 } from "fs";
|
|
77
77
|
import { mkdir as mkdir18 } from "fs/promises";
|
|
78
78
|
import { createRequire as createRequire4 } from "module";
|
|
79
|
-
import { dirname as dirname14, join as
|
|
79
|
+
import { dirname as dirname14, join as join43 } from "path";
|
|
80
80
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
81
81
|
|
|
82
82
|
// ../../node_modules/.pnpm/@loro-extended+change@6.0.0-beta.0_loro-crdt@1.10.6/node_modules/@loro-extended/change/dist/index.js
|
|
@@ -7262,6 +7262,243 @@ var Adapter = class {
|
|
|
7262
7262
|
this.#sendInterceptors = [];
|
|
7263
7263
|
}
|
|
7264
7264
|
};
|
|
7265
|
+
var Bridge = class {
|
|
7266
|
+
adapters = /* @__PURE__ */ new Map();
|
|
7267
|
+
logger;
|
|
7268
|
+
constructor({ logger: logger2 } = {}) {
|
|
7269
|
+
this.logger = logger2 ?? getLogger(["@loro-extended", "repo"]);
|
|
7270
|
+
}
|
|
7271
|
+
/**
|
|
7272
|
+
* Register an adapter with this bridge
|
|
7273
|
+
*/
|
|
7274
|
+
addAdapter(adapter) {
|
|
7275
|
+
if (!adapter.adapterType)
|
|
7276
|
+
throw new Error("can't add adapter without adapter id");
|
|
7277
|
+
this.adapters.set(adapter.adapterType, adapter);
|
|
7278
|
+
}
|
|
7279
|
+
/**
|
|
7280
|
+
* Remove an adapter from this bridge
|
|
7281
|
+
*/
|
|
7282
|
+
removeAdapter(adapterType) {
|
|
7283
|
+
this.adapters.delete(adapterType);
|
|
7284
|
+
}
|
|
7285
|
+
/**
|
|
7286
|
+
* Route a message from one adapter to another
|
|
7287
|
+
*/
|
|
7288
|
+
routeMessage(fromAdapterType, toAdapterType, message) {
|
|
7289
|
+
this.logger.trace("routeMessage: {messageType} from {from} to {to}", {
|
|
7290
|
+
from: fromAdapterType,
|
|
7291
|
+
to: toAdapterType,
|
|
7292
|
+
messageType: message.type
|
|
7293
|
+
});
|
|
7294
|
+
const toAdapter = this.adapters.get(toAdapterType);
|
|
7295
|
+
if (toAdapter) {
|
|
7296
|
+
toAdapter.deliverMessage(fromAdapterType, message);
|
|
7297
|
+
} else {
|
|
7298
|
+
this.logger.warn(
|
|
7299
|
+
"routeMessage: target adapter {toAdapterType} not found",
|
|
7300
|
+
{ toAdapterType }
|
|
7301
|
+
);
|
|
7302
|
+
}
|
|
7303
|
+
}
|
|
7304
|
+
/**
|
|
7305
|
+
* Get all adapter IDs currently in the bridge
|
|
7306
|
+
*/
|
|
7307
|
+
get adapterTypes() {
|
|
7308
|
+
return new Set(this.adapters.keys());
|
|
7309
|
+
}
|
|
7310
|
+
};
|
|
7311
|
+
var BridgeAdapter = class extends Adapter {
|
|
7312
|
+
bridge;
|
|
7313
|
+
logger;
|
|
7314
|
+
// Track which remote adapter each channel connects to
|
|
7315
|
+
channelToAdapter = /* @__PURE__ */ new Map();
|
|
7316
|
+
adapterToChannel = /* @__PURE__ */ new Map();
|
|
7317
|
+
constructor({ adapterType, adapterId, bridge, logger: logger2 }) {
|
|
7318
|
+
super({ adapterType, adapterId: adapterId ?? adapterType });
|
|
7319
|
+
this.bridge = bridge;
|
|
7320
|
+
this.logger = (logger2 ?? getLogger(["@loro-extended", "repo"])).with({
|
|
7321
|
+
adapterType
|
|
7322
|
+
});
|
|
7323
|
+
this.logger.trace(`new BridgeAdapter`);
|
|
7324
|
+
}
|
|
7325
|
+
generate(context) {
|
|
7326
|
+
this.logger.debug("generate channel to {targetAdapterType}", {
|
|
7327
|
+
targetAdapterType: context.targetAdapterType
|
|
7328
|
+
});
|
|
7329
|
+
return {
|
|
7330
|
+
adapterType: this.adapterType,
|
|
7331
|
+
kind: "network",
|
|
7332
|
+
send: (msg) => {
|
|
7333
|
+
this.logger.debug("channel.send: {messageType} from {from} to {to}", {
|
|
7334
|
+
from: this.adapterType,
|
|
7335
|
+
to: context.targetAdapterType,
|
|
7336
|
+
messageType: msg.type
|
|
7337
|
+
});
|
|
7338
|
+
this.bridge.routeMessage(
|
|
7339
|
+
this.adapterType,
|
|
7340
|
+
context.targetAdapterType,
|
|
7341
|
+
msg
|
|
7342
|
+
);
|
|
7343
|
+
},
|
|
7344
|
+
stop: () => {
|
|
7345
|
+
}
|
|
7346
|
+
};
|
|
7347
|
+
}
|
|
7348
|
+
/**
|
|
7349
|
+
* Start participating in the in-process network.
|
|
7350
|
+
* Uses two-phase initialization:
|
|
7351
|
+
* 1. Create all channels (no messages sent)
|
|
7352
|
+
* 2. Establish channels (only the "newer" adapter initiates to avoid double-establishment)
|
|
7353
|
+
*/
|
|
7354
|
+
async onStart() {
|
|
7355
|
+
this.logger.trace(`onStart - registering with bridge`);
|
|
7356
|
+
this.bridge.addAdapter(this);
|
|
7357
|
+
for (const [adapterType, adapter] of this.bridge.adapters) {
|
|
7358
|
+
if (adapterType !== this.adapterType) {
|
|
7359
|
+
this.logger.trace("telling {adapterType} to create channel to us", {
|
|
7360
|
+
adapterType
|
|
7361
|
+
});
|
|
7362
|
+
adapter.createChannelTo(this.adapterType);
|
|
7363
|
+
}
|
|
7364
|
+
}
|
|
7365
|
+
for (const adapterType of this.bridge.adapters.keys()) {
|
|
7366
|
+
if (adapterType !== this.adapterType) {
|
|
7367
|
+
this.logger.trace("creating our channel to {adapterType}", {
|
|
7368
|
+
adapterType
|
|
7369
|
+
});
|
|
7370
|
+
this.createChannelTo(adapterType);
|
|
7371
|
+
}
|
|
7372
|
+
}
|
|
7373
|
+
for (const channelId of this.adapterToChannel.values()) {
|
|
7374
|
+
this.logger.trace("establishing our channel {channelId}", { channelId });
|
|
7375
|
+
this.establishChannel(channelId);
|
|
7376
|
+
}
|
|
7377
|
+
this.logger.trace(`onStart complete`);
|
|
7378
|
+
}
|
|
7379
|
+
/**
|
|
7380
|
+
* Stop participating in the in-process network.
|
|
7381
|
+
* Cleans up all channels and removes from bridge.
|
|
7382
|
+
*/
|
|
7383
|
+
async onStop() {
|
|
7384
|
+
this.logger.trace(`stop`);
|
|
7385
|
+
for (const [adapterType, adapter] of this.bridge.adapters) {
|
|
7386
|
+
if (adapterType !== this.adapterType) {
|
|
7387
|
+
adapter.removeChannelTo(this.adapterType);
|
|
7388
|
+
}
|
|
7389
|
+
}
|
|
7390
|
+
this.bridge.removeAdapter(this.adapterType);
|
|
7391
|
+
for (const channelId of this.channelToAdapter.keys()) {
|
|
7392
|
+
this.removeChannel(channelId);
|
|
7393
|
+
}
|
|
7394
|
+
this.channelToAdapter.clear();
|
|
7395
|
+
this.adapterToChannel.clear();
|
|
7396
|
+
}
|
|
7397
|
+
/**
|
|
7398
|
+
* Create a channel to a target adapter (Phase 1).
|
|
7399
|
+
* Does NOT trigger establishment - that happens in Phase 2.
|
|
7400
|
+
* Called by our own onStart() or by other adapters when they start.
|
|
7401
|
+
*/
|
|
7402
|
+
createChannelTo(targetAdapterType) {
|
|
7403
|
+
if (this.adapterToChannel.has(targetAdapterType)) {
|
|
7404
|
+
this.logger.trace("channel already exists to {targetAdapterType}", {
|
|
7405
|
+
targetAdapterType
|
|
7406
|
+
});
|
|
7407
|
+
return;
|
|
7408
|
+
}
|
|
7409
|
+
const channel = this.addChannel({ targetAdapterType });
|
|
7410
|
+
this.channelToAdapter.set(channel.channelId, targetAdapterType);
|
|
7411
|
+
this.adapterToChannel.set(targetAdapterType, channel.channelId);
|
|
7412
|
+
this.logger.trace(
|
|
7413
|
+
"channel {channelId} created (not yet established) to {targetAdapterType}",
|
|
7414
|
+
{
|
|
7415
|
+
targetAdapterType,
|
|
7416
|
+
channelId: channel.channelId
|
|
7417
|
+
}
|
|
7418
|
+
);
|
|
7419
|
+
}
|
|
7420
|
+
/**
|
|
7421
|
+
* Establish a channel to a target adapter (Phase 2).
|
|
7422
|
+
* Triggers the establishment handshake.
|
|
7423
|
+
* Called by our own onStart() or by other adapters when they start.
|
|
7424
|
+
*/
|
|
7425
|
+
establishChannelTo(targetAdapterType) {
|
|
7426
|
+
const channelId = this.adapterToChannel.get(targetAdapterType);
|
|
7427
|
+
if (!channelId) {
|
|
7428
|
+
this.logger.warn("no channel found to establish to {targetAdapterType}", {
|
|
7429
|
+
targetAdapterType
|
|
7430
|
+
});
|
|
7431
|
+
return;
|
|
7432
|
+
}
|
|
7433
|
+
this.logger.trace("establishing channel {channelId}", { channelId });
|
|
7434
|
+
this.establishChannel(channelId);
|
|
7435
|
+
}
|
|
7436
|
+
/**
|
|
7437
|
+
* Remove a channel to a target adapter.
|
|
7438
|
+
* Called by other adapters when they stop.
|
|
7439
|
+
*/
|
|
7440
|
+
removeChannelTo(targetAdapterType) {
|
|
7441
|
+
const channelId = this.adapterToChannel.get(targetAdapterType);
|
|
7442
|
+
if (channelId) {
|
|
7443
|
+
this.logger.trace("removing channel to adapter {targetAdapterType}", {
|
|
7444
|
+
targetAdapterType
|
|
7445
|
+
});
|
|
7446
|
+
this.removeChannel(channelId);
|
|
7447
|
+
this.channelToAdapter.delete(channelId);
|
|
7448
|
+
this.adapterToChannel.delete(targetAdapterType);
|
|
7449
|
+
}
|
|
7450
|
+
}
|
|
7451
|
+
/**
|
|
7452
|
+
* Deliver a message from another adapter to the appropriate channel.
|
|
7453
|
+
* Called by Bridge.routeMessage().
|
|
7454
|
+
*
|
|
7455
|
+
* Delivers messages asynchronously via queueMicrotask() to simulate real
|
|
7456
|
+
* network adapter behavior. This ensures tests using BridgeAdapter exercise
|
|
7457
|
+
* the same async codepaths as production adapters (WebSocket, SSE, etc.).
|
|
7458
|
+
*
|
|
7459
|
+
* Tests should use `waitForSync()` or `waitUntilReady()` to await sync completion.
|
|
7460
|
+
*/
|
|
7461
|
+
deliverMessage(fromAdapterType, message) {
|
|
7462
|
+
const channelId = this.adapterToChannel.get(fromAdapterType);
|
|
7463
|
+
if (channelId) {
|
|
7464
|
+
const channel = this.channels.get(channelId);
|
|
7465
|
+
if (channel) {
|
|
7466
|
+
this.logger.trace(
|
|
7467
|
+
"queueing message {messageType} to channel {channelId} from {from}",
|
|
7468
|
+
{
|
|
7469
|
+
from: fromAdapterType,
|
|
7470
|
+
messageType: message.type,
|
|
7471
|
+
channelId
|
|
7472
|
+
}
|
|
7473
|
+
);
|
|
7474
|
+
queueMicrotask(() => {
|
|
7475
|
+
this.logger.trace(
|
|
7476
|
+
"delivering message {messageType} to channel {channelId} from {from}",
|
|
7477
|
+
{
|
|
7478
|
+
from: fromAdapterType,
|
|
7479
|
+
messageType: message.type,
|
|
7480
|
+
channelId
|
|
7481
|
+
}
|
|
7482
|
+
);
|
|
7483
|
+
channel.onReceive(message);
|
|
7484
|
+
});
|
|
7485
|
+
} else {
|
|
7486
|
+
this.logger.warn(
|
|
7487
|
+
"channel {channelId} not found for message delivery from {fromAdapterType}",
|
|
7488
|
+
{
|
|
7489
|
+
fromAdapterType,
|
|
7490
|
+
channelId
|
|
7491
|
+
}
|
|
7492
|
+
);
|
|
7493
|
+
}
|
|
7494
|
+
} else {
|
|
7495
|
+
this.logger.warn("no channel found for adapter {fromAdapterType}", {
|
|
7496
|
+
fromAdapterType,
|
|
7497
|
+
availableChannels: Array.from(this.adapterToChannel.keys())
|
|
7498
|
+
});
|
|
7499
|
+
}
|
|
7500
|
+
}
|
|
7501
|
+
};
|
|
7265
7502
|
function isEstablished(channel) {
|
|
7266
7503
|
return channel.type === "established";
|
|
7267
7504
|
}
|
|
@@ -24374,6 +24611,7 @@ function parseDocumentId(id) {
|
|
|
24374
24611
|
return null;
|
|
24375
24612
|
return { prefix, key, epoch };
|
|
24376
24613
|
}
|
|
24614
|
+
var LORO_BACKPRESSURE_HIGH_WATER = 1024 * 1024;
|
|
24377
24615
|
var HARNESS_SERVER_NAME = "shipyard-harness";
|
|
24378
24616
|
var VISUALIZE_READ_ME_TOOL = "read_me";
|
|
24379
24617
|
var VISUALIZE_TOOL = "visualize";
|
|
@@ -24828,6 +25066,47 @@ var PRStatePayloadSchema = external_exports.object({
|
|
|
24828
25066
|
requiredChecks: external_exports.array(external_exports.string()).default([]),
|
|
24829
25067
|
updatedAt: external_exports.number()
|
|
24830
25068
|
});
|
|
25069
|
+
function applyPresenceUpdate(pool2, peerId, identity, state) {
|
|
25070
|
+
const existing = pool2[peerId];
|
|
25071
|
+
const mergedState = {
|
|
25072
|
+
...existing?.state,
|
|
25073
|
+
...state
|
|
25074
|
+
};
|
|
25075
|
+
if (state.typing === void 0 && "typing" in state) {
|
|
25076
|
+
delete mergedState.typing;
|
|
25077
|
+
}
|
|
25078
|
+
if (state.viewing === void 0 && "viewing" in state) {
|
|
25079
|
+
delete mergedState.viewing;
|
|
25080
|
+
}
|
|
25081
|
+
return {
|
|
25082
|
+
...pool2,
|
|
25083
|
+
[peerId]: {
|
|
25084
|
+
userId: identity.userId,
|
|
25085
|
+
username: identity.username,
|
|
25086
|
+
avatarUrl: identity.avatarUrl,
|
|
25087
|
+
state: mergedState
|
|
25088
|
+
}
|
|
25089
|
+
};
|
|
25090
|
+
}
|
|
25091
|
+
function removePresence(pool2, peerId) {
|
|
25092
|
+
const { [peerId]: _2, ...rest } = pool2;
|
|
25093
|
+
return rest;
|
|
25094
|
+
}
|
|
25095
|
+
var PeerPresenceStateSchema = external_exports.object({
|
|
25096
|
+
typing: external_exports.object({
|
|
25097
|
+
taskId: external_exports.string(),
|
|
25098
|
+
context: external_exports.string()
|
|
25099
|
+
}).optional(),
|
|
25100
|
+
viewing: external_exports.object({
|
|
25101
|
+
taskId: external_exports.string()
|
|
25102
|
+
}).optional()
|
|
25103
|
+
});
|
|
25104
|
+
var PeerPresenceSlotSchema = external_exports.object({
|
|
25105
|
+
userId: external_exports.string(),
|
|
25106
|
+
username: external_exports.string(),
|
|
25107
|
+
avatarUrl: external_exports.string().nullable(),
|
|
25108
|
+
state: PeerPresenceStateSchema
|
|
25109
|
+
});
|
|
24831
25110
|
var ASSET_CHUNK_SIZE = 16384;
|
|
24832
25111
|
var ASSET_REQUEST_ID_BYTES = 36;
|
|
24833
25112
|
var ASSET_TRANSFER_LABEL = "asset-transfer";
|
|
@@ -25768,6 +26047,10 @@ var BrowserToDaemonControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
25768
26047
|
type: external_exports.literal("remove_worktree"),
|
|
25769
26048
|
worktreePath: external_exports.string()
|
|
25770
26049
|
}),
|
|
26050
|
+
external_exports.object({
|
|
26051
|
+
type: external_exports.literal("list_directories"),
|
|
26052
|
+
basePath: external_exports.string()
|
|
26053
|
+
}),
|
|
25771
26054
|
external_exports.object({
|
|
25772
26055
|
type: external_exports.literal("create_task"),
|
|
25773
26056
|
taskId: external_exports.string(),
|
|
@@ -25792,6 +26075,11 @@ var BrowserToDaemonControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
25792
26075
|
value: external_exports.unknown()
|
|
25793
26076
|
}),
|
|
25794
26077
|
external_exports.object({ type: external_exports.literal("request_user_settings") }),
|
|
26078
|
+
external_exports.object({
|
|
26079
|
+
type: external_exports.literal("request_preview_target"),
|
|
26080
|
+
port: external_exports.number().int().positive().optional(),
|
|
26081
|
+
url: external_exports.string().url().optional()
|
|
26082
|
+
}),
|
|
25795
26083
|
external_exports.object({
|
|
25796
26084
|
type: external_exports.literal("add_annotation"),
|
|
25797
26085
|
taskId: external_exports.string(),
|
|
@@ -25847,6 +26135,11 @@ var BrowserToDaemonControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
25847
26135
|
external_exports.object({ type: external_exports.literal("delete_template"), templateId: external_exports.string() }),
|
|
25848
26136
|
external_exports.object({ type: external_exports.literal("list_templates") }),
|
|
25849
26137
|
external_exports.object({ type: external_exports.literal("stop_task"), taskId: external_exports.string() }),
|
|
26138
|
+
external_exports.object({
|
|
26139
|
+
type: external_exports.literal("stop_background_agent"),
|
|
26140
|
+
taskId: external_exports.string(),
|
|
26141
|
+
backgroundTaskId: external_exports.string()
|
|
26142
|
+
}),
|
|
25850
26143
|
external_exports.object({
|
|
25851
26144
|
type: external_exports.literal("todo_item_added"),
|
|
25852
26145
|
taskId: external_exports.string(),
|
|
@@ -25881,6 +26174,14 @@ var BrowserToDaemonControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
25881
26174
|
depId: external_exports.string(),
|
|
25882
26175
|
depContent: external_exports.string(),
|
|
25883
26176
|
action: external_exports.enum(["connected", "disconnected"])
|
|
26177
|
+
}),
|
|
26178
|
+
external_exports.object({
|
|
26179
|
+
type: external_exports.literal("request_recent_logs"),
|
|
26180
|
+
windowMinutes: external_exports.number().int().positive().max(120).default(30)
|
|
26181
|
+
}),
|
|
26182
|
+
external_exports.object({
|
|
26183
|
+
type: external_exports.literal("presence_update"),
|
|
26184
|
+
state: PeerPresenceStateSchema
|
|
25884
26185
|
})
|
|
25885
26186
|
]);
|
|
25886
26187
|
var DaemonToBrowserControlMessageSchema = external_exports.discriminatedUnion("type", [
|
|
@@ -25974,7 +26275,8 @@ var DaemonToBrowserControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
25974
26275
|
}),
|
|
25975
26276
|
external_exports.object({
|
|
25976
26277
|
type: external_exports.literal("detected_ports"),
|
|
25977
|
-
ports: external_exports.array(DetectedPortSchema)
|
|
26278
|
+
ports: external_exports.array(DetectedPortSchema),
|
|
26279
|
+
proxyPort: external_exports.number().optional()
|
|
25978
26280
|
}),
|
|
25979
26281
|
external_exports.object({
|
|
25980
26282
|
type: external_exports.literal("set_preview_target"),
|
|
@@ -26056,6 +26358,11 @@ var DaemonToBrowserControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
26056
26358
|
type: external_exports.literal("worktree_removed"),
|
|
26057
26359
|
worktreePath: external_exports.string()
|
|
26058
26360
|
}),
|
|
26361
|
+
external_exports.object({
|
|
26362
|
+
type: external_exports.literal("directory_list"),
|
|
26363
|
+
basePath: external_exports.string(),
|
|
26364
|
+
directories: external_exports.array(external_exports.string())
|
|
26365
|
+
}),
|
|
26059
26366
|
external_exports.object({
|
|
26060
26367
|
type: external_exports.literal("task_index_snapshot"),
|
|
26061
26368
|
tasks: external_exports.record(external_exports.string(), TaskRecordSchema),
|
|
@@ -26247,6 +26554,15 @@ var DaemonToBrowserControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
26247
26554
|
taskId: external_exports.string(),
|
|
26248
26555
|
error: external_exports.string(),
|
|
26249
26556
|
errorKind: TaskErrorKindSchema
|
|
26557
|
+
}),
|
|
26558
|
+
external_exports.object({
|
|
26559
|
+
type: external_exports.literal("recent_logs"),
|
|
26560
|
+
lines: external_exports.array(external_exports.string()),
|
|
26561
|
+
truncated: external_exports.boolean()
|
|
26562
|
+
}),
|
|
26563
|
+
external_exports.object({
|
|
26564
|
+
type: external_exports.literal("presence_state"),
|
|
26565
|
+
peers: external_exports.record(external_exports.string(), PeerPresenceSlotSchema)
|
|
26250
26566
|
})
|
|
26251
26567
|
]);
|
|
26252
26568
|
var TASK_MESSAGES_PREFIX = "task-messages:";
|
|
@@ -26645,14 +26961,6 @@ function structuredTaskToCCFile(task) {
|
|
|
26645
26961
|
updatedAt: task.updatedAt
|
|
26646
26962
|
};
|
|
26647
26963
|
}
|
|
26648
|
-
var TaskViewPresenceShape = Shape.plain.struct({
|
|
26649
|
-
userId: Shape.plain.string(),
|
|
26650
|
-
username: Shape.plain.string(),
|
|
26651
|
-
avatarUrl: Shape.plain.string(),
|
|
26652
|
-
taskId: Shape.plain.string(),
|
|
26653
|
-
timestamp: Shape.plain.number()
|
|
26654
|
-
});
|
|
26655
|
-
var TaskViewDocSchema = Shape.doc({}, { mergeable: true });
|
|
26656
26964
|
var TODO_ITEM_STATUSES = ["pending", "in_progress", "completed"];
|
|
26657
26965
|
function generateTodoId(content) {
|
|
26658
26966
|
let hash = 5381;
|
|
@@ -27504,14 +27812,25 @@ function runWithTimeout(command2, args, cwd, timeoutMs) {
|
|
|
27504
27812
|
}
|
|
27505
27813
|
|
|
27506
27814
|
// src/shared/capabilities/git-repo.ts
|
|
27815
|
+
var ghAvailableCache = false;
|
|
27507
27816
|
async function isGhAvailable() {
|
|
27817
|
+
if (ghAvailableCache) return true;
|
|
27508
27818
|
try {
|
|
27509
27819
|
await run("which", ["gh"]);
|
|
27820
|
+
ghAvailableCache = true;
|
|
27510
27821
|
return true;
|
|
27511
27822
|
} catch {
|
|
27512
27823
|
return false;
|
|
27513
27824
|
}
|
|
27514
27825
|
}
|
|
27826
|
+
var topLevelCache = /* @__PURE__ */ new Map();
|
|
27827
|
+
async function getGitTopLevel(cwd) {
|
|
27828
|
+
const cached = topLevelCache.get(cwd);
|
|
27829
|
+
if (cached !== void 0) return cached;
|
|
27830
|
+
const topLevel = (await runWithTimeout("git", ["rev-parse", "--show-toplevel"], cwd, TIMEOUT_MS)).trim();
|
|
27831
|
+
topLevelCache.set(cwd, topLevel);
|
|
27832
|
+
return topLevel;
|
|
27833
|
+
}
|
|
27515
27834
|
function parseOwnerRepo(remoteUrl) {
|
|
27516
27835
|
const match2 = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
|
27517
27836
|
if (!match2?.[1] || !match2[2]) return null;
|
|
@@ -29186,7 +29505,7 @@ function nanoid(size2 = 21) {
|
|
|
29186
29505
|
}
|
|
29187
29506
|
|
|
29188
29507
|
// src/services/bootstrap/signaling.ts
|
|
29189
|
-
var DAEMON_NPM_VERSION = true ? "3.
|
|
29508
|
+
var DAEMON_NPM_VERSION = true ? "3.1.0" : "unknown";
|
|
29190
29509
|
function createDaemonSignaling(config2) {
|
|
29191
29510
|
const agentId = config2.agentId ?? nanoid();
|
|
29192
29511
|
function send(msg) {
|
|
@@ -29486,7 +29805,9 @@ function handleBrowserPreviewChannel(port, send, sendBinary, log, options) {
|
|
|
29486
29805
|
function buildRequestHeaders(msgHeaders) {
|
|
29487
29806
|
const headers = { host: `localhost:${port}` };
|
|
29488
29807
|
for (const [k2, v2] of Object.entries(msgHeaders)) {
|
|
29489
|
-
|
|
29808
|
+
const lower = k2.toLowerCase();
|
|
29809
|
+
if (HOP_BY_HOP_HEADERS.has(lower) || lower === "accept-encoding") continue;
|
|
29810
|
+
headers[k2] = v2;
|
|
29490
29811
|
}
|
|
29491
29812
|
const cookieHeader = buildCookieHeader();
|
|
29492
29813
|
if (cookieHeader) {
|
|
@@ -29571,9 +29892,11 @@ var IFRAME_BLOCKING_HEADERS = /* @__PURE__ */ new Set([
|
|
|
29571
29892
|
"content-security-policy-report-only"
|
|
29572
29893
|
]);
|
|
29573
29894
|
var HISTORY_GUARD = `<script>(function(){var p=history.pushState,r=history.replaceState;history.pushState=function(){try{p.apply(this,arguments)}catch(e){if(!(e instanceof DOMException&&e.name==="SecurityError"))throw e}};history.replaceState=function(){try{r.apply(this,arguments)}catch(e){if(!(e instanceof DOMException&&e.name==="SecurityError"))throw e}};var la=Location.prototype.assign,lr=Location.prototype.replace;function patchLoc(orig,hFn){return function(url){if(typeof url==="string"&&url.charAt(0)==="#"){try{hFn.call(history,null,"",location.origin+location.pathname+location.search+url)}catch(e){location.hash=url}return}try{return orig.call(this,url)}catch(e){if(!(e instanceof DOMException&&e.name==="SecurityError"))throw e}}}try{Location.prototype.assign=patchLoc(la,p);Location.prototype.replace=patchLoc(lr,r)}catch(e){}})();</script>`;
|
|
29895
|
+
var COLLAB_REFRESH = `<script>(function(){if(!location.pathname.startsWith("/__preview/"))return;var R=WebSocket;WebSocket=function(u,p){var s={readyState:3,send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},onopen:null,onclose:null,onerror:null,onmessage:null,url:u,protocol:"",extensions:"",bufferedAmount:0,binaryType:"blob",CONNECTING:0,OPEN:1,CLOSING:2,CLOSED:3};setTimeout(function(){if(s.onclose)s.onclose({code:1006,reason:"",wasClean:false})},0);return s};WebSocket.CONNECTING=0;WebSocket.OPEN=1;WebSocket.CLOSING=2;WebSocket.CLOSED=3;var h="";function poll(){fetch(location.href,{cache:"no-store"}).then(function(r){return r.text()}).then(function(t){var n=0;for(var i=0;i<t.length;i++){n=((n<<5)-n+t.charCodeAt(i))|0}var s=String(n);if(h&&h!==s)location.reload();h=s}).catch(function(){});setTimeout(poll,3000)}setTimeout(poll,3000)})();</script>`;
|
|
29574
29896
|
function injectBaseHref(html, origin) {
|
|
29575
|
-
const
|
|
29576
|
-
const
|
|
29897
|
+
const encodedOrigin = encodeURIComponent(origin);
|
|
29898
|
+
const baseTag = `<base href="/__preview/url/${encodedOrigin}/">`;
|
|
29899
|
+
const injection = HISTORY_GUARD + baseTag + COLLAB_REFRESH + ANNOTATION_BRIDGE_TAG;
|
|
29577
29900
|
const headIdx = html.indexOf("<head");
|
|
29578
29901
|
if (headIdx === -1) return injection + html;
|
|
29579
29902
|
const closeIdx = html.indexOf(">", headIdx);
|
|
@@ -29630,7 +29953,9 @@ function handleBrowserPreviewUrlChannel(send, sendBinary, log, options) {
|
|
|
29630
29953
|
function buildFetchHeaders(msgHeaders, host) {
|
|
29631
29954
|
const headers = {};
|
|
29632
29955
|
for (const [k2, v2] of Object.entries(msgHeaders)) {
|
|
29633
|
-
|
|
29956
|
+
const lower = k2.toLowerCase();
|
|
29957
|
+
if (HOP_BY_HOP_HEADERS.has(lower) || lower === "accept-encoding") continue;
|
|
29958
|
+
headers[k2] = v2;
|
|
29634
29959
|
}
|
|
29635
29960
|
headers.host = host;
|
|
29636
29961
|
return headers;
|
|
@@ -30802,7 +31127,7 @@ function handlePluginAuthRequest(pluginId, sendControl, deps, logAdapter) {
|
|
|
30802
31127
|
}
|
|
30803
31128
|
|
|
30804
31129
|
// src/services/pr-poller.ts
|
|
30805
|
-
var PR_POLL_INTERVAL_MS =
|
|
31130
|
+
var PR_POLL_INTERVAL_MS = 3e4;
|
|
30806
31131
|
async function enrichClosedPR(pr, cwd) {
|
|
30807
31132
|
if (pr.state !== "closed" || pr.mergeCommitSha) return pr;
|
|
30808
31133
|
if (!pr.headRefSha) return pr;
|
|
@@ -30813,10 +31138,9 @@ async function enrichClosedPR(pr, cwd) {
|
|
|
30813
31138
|
);
|
|
30814
31139
|
return headInBase ? { ...pr, headCommitInBase: true } : pr;
|
|
30815
31140
|
}
|
|
30816
|
-
async function
|
|
31141
|
+
async function fetchCIDataWithRemote(pr, cwd, remote) {
|
|
30817
31142
|
const referenceCommit = pr?.mergeCommitSha ?? pr?.headRefSha ?? await run("git", ["rev-parse", "HEAD"], cwd).catch(() => "");
|
|
30818
31143
|
if (!referenceCommit) return { deployments: [], workflowRuns: [], requiredChecks: [] };
|
|
30819
|
-
const remote = await run("git", ["remote", "get-url", "origin"], cwd).catch(() => "");
|
|
30820
31144
|
const parsed = parseOwnerRepo(remote);
|
|
30821
31145
|
if (!parsed) return { deployments: [], workflowRuns: [], requiredChecks: [] };
|
|
30822
31146
|
const branch = pr?.baseRef ?? "main";
|
|
@@ -30843,13 +31167,14 @@ async function fetchCIData(pr, cwd) {
|
|
|
30843
31167
|
const allDeployments = [...deployments, ...placeholderDeployments];
|
|
30844
31168
|
return { deployments: allDeployments, workflowRuns, requiredChecks };
|
|
30845
31169
|
}
|
|
30846
|
-
function createPRPoller(callbacks, log) {
|
|
30847
|
-
const
|
|
30848
|
-
|
|
31170
|
+
function createPRPoller(callbacks, log, resolveTopLevel = getGitTopLevel) {
|
|
31171
|
+
const repos = /* @__PURE__ */ new Map();
|
|
31172
|
+
const taskToRepo = /* @__PURE__ */ new Map();
|
|
31173
|
+
let disposed = false;
|
|
31174
|
+
async function fetchPRState(cwd) {
|
|
30849
31175
|
const available = await isGhAvailable();
|
|
30850
31176
|
if (!available) {
|
|
30851
31177
|
return {
|
|
30852
|
-
taskId,
|
|
30853
31178
|
prAvailable: false,
|
|
30854
31179
|
currentBranchPR: null,
|
|
30855
31180
|
assignedReviews: [],
|
|
@@ -30860,14 +31185,14 @@ function createPRPoller(callbacks, log) {
|
|
|
30860
31185
|
updatedAt: Date.now()
|
|
30861
31186
|
};
|
|
30862
31187
|
}
|
|
30863
|
-
const [
|
|
30864
|
-
getPRForCurrentBranch(cwd),
|
|
30865
|
-
|
|
30866
|
-
getUserPRs(cwd)
|
|
31188
|
+
const [prResults, remote] = await Promise.all([
|
|
31189
|
+
Promise.allSettled([getPRForCurrentBranch(cwd), getAssignedReviews(cwd), getUserPRs(cwd)]),
|
|
31190
|
+
run("git", ["remote", "get-url", "origin"], cwd).catch(() => "")
|
|
30867
31191
|
]);
|
|
31192
|
+
const [currentPR, assigned, user] = prResults;
|
|
30868
31193
|
const { allPRs: _2, currentBranchPR: rawPR } = deduplicatePRs(currentPR, assigned, user);
|
|
30869
31194
|
const currentBranchPR = rawPR ? await enrichClosedPR(rawPR, cwd) : null;
|
|
30870
|
-
const ciData = await
|
|
31195
|
+
const ciData = await fetchCIDataWithRemote(currentBranchPR, cwd, remote).catch(
|
|
30871
31196
|
() => ({
|
|
30872
31197
|
deployments: [],
|
|
30873
31198
|
workflowRuns: [],
|
|
@@ -30875,7 +31200,6 @@ function createPRPoller(callbacks, log) {
|
|
|
30875
31200
|
})
|
|
30876
31201
|
);
|
|
30877
31202
|
return {
|
|
30878
|
-
taskId,
|
|
30879
31203
|
prAvailable: true,
|
|
30880
31204
|
currentBranchPR,
|
|
30881
31205
|
assignedReviews: assigned.status === "fulfilled" ? assigned.value : [],
|
|
@@ -30886,81 +31210,135 @@ function createPRPoller(callbacks, log) {
|
|
|
30886
31210
|
updatedAt: Date.now()
|
|
30887
31211
|
};
|
|
30888
31212
|
}
|
|
30889
|
-
|
|
31213
|
+
function pushToSubscribers(entry, payload) {
|
|
31214
|
+
for (const taskId of entry.subscribers) {
|
|
31215
|
+
callbacks.onPRState({ ...payload, taskId });
|
|
31216
|
+
}
|
|
31217
|
+
}
|
|
31218
|
+
async function poll(topLevel, entry) {
|
|
31219
|
+
if (entry.isPolling) return;
|
|
31220
|
+
entry.isPolling = true;
|
|
30890
31221
|
try {
|
|
30891
|
-
const
|
|
31222
|
+
const currentBranch = await run("git", ["rev-parse", "--abbrev-ref", "HEAD"], entry.cwd).then((s2) => s2.trim()).catch(() => "");
|
|
31223
|
+
if (entry.lastBranch && currentBranch && currentBranch !== entry.lastBranch) {
|
|
31224
|
+
entry.lastContent = "";
|
|
31225
|
+
log.debug(
|
|
31226
|
+
{ topLevel, oldBranch: entry.lastBranch, newBranch: currentBranch },
|
|
31227
|
+
"Branch changed, clearing dedup cache"
|
|
31228
|
+
);
|
|
31229
|
+
}
|
|
31230
|
+
if (currentBranch) entry.lastBranch = currentBranch;
|
|
31231
|
+
const payload = await fetchPRState(entry.cwd);
|
|
30892
31232
|
if (!payload) return;
|
|
30893
31233
|
const { updatedAt: _2, ...comparable } = payload;
|
|
30894
31234
|
const contentKey = JSON.stringify(comparable);
|
|
30895
31235
|
if (entry.lastContent === contentKey) {
|
|
30896
|
-
log.debug({
|
|
31236
|
+
log.debug({ topLevel }, "PR state unchanged, skipping push");
|
|
30897
31237
|
return;
|
|
30898
31238
|
}
|
|
30899
31239
|
entry.lastContent = contentKey;
|
|
30900
|
-
|
|
30901
|
-
|
|
31240
|
+
entry.lastPayload = payload;
|
|
31241
|
+
pushToSubscribers(entry, payload);
|
|
31242
|
+
log.debug(
|
|
31243
|
+
{ topLevel, prAvailable: payload.prAvailable, subscribers: entry.subscribers.size },
|
|
31244
|
+
"PR state pushed"
|
|
31245
|
+
);
|
|
30902
31246
|
} catch (err) {
|
|
30903
|
-
log.warn({ err,
|
|
31247
|
+
log.warn({ err, topLevel }, "PR poll failed");
|
|
31248
|
+
} finally {
|
|
31249
|
+
entry.isPolling = false;
|
|
30904
31250
|
}
|
|
30905
31251
|
}
|
|
31252
|
+
function removeFromPreviousRepo(taskId) {
|
|
31253
|
+
const prev = taskToRepo.get(taskId);
|
|
31254
|
+
if (!prev) return;
|
|
31255
|
+
const prevEntry = repos.get(prev.topLevel);
|
|
31256
|
+
if (prevEntry) {
|
|
31257
|
+
prevEntry.subscribers.delete(taskId);
|
|
31258
|
+
if (prevEntry.subscribers.size === 0) {
|
|
31259
|
+
clearInterval(prevEntry.timer);
|
|
31260
|
+
for (const t of prevEntry.burstTimers) clearTimeout(t);
|
|
31261
|
+
repos.delete(prev.topLevel);
|
|
31262
|
+
log.info({ topLevel: prev.topLevel }, "PR polling stopped (no subscribers)");
|
|
31263
|
+
}
|
|
31264
|
+
}
|
|
31265
|
+
taskToRepo.delete(taskId);
|
|
31266
|
+
}
|
|
30906
31267
|
function startPolling(taskId, cwd) {
|
|
30907
|
-
|
|
30908
|
-
|
|
30909
|
-
|
|
30910
|
-
|
|
30911
|
-
|
|
30912
|
-
|
|
30913
|
-
|
|
30914
|
-
|
|
30915
|
-
|
|
30916
|
-
|
|
30917
|
-
|
|
30918
|
-
|
|
30919
|
-
|
|
31268
|
+
resolveTopLevel(cwd).then((topLevel) => {
|
|
31269
|
+
if (disposed) return;
|
|
31270
|
+
removeFromPreviousRepo(taskId);
|
|
31271
|
+
taskToRepo.set(taskId, { topLevel, cwd });
|
|
31272
|
+
const existing = repos.get(topLevel);
|
|
31273
|
+
if (existing) {
|
|
31274
|
+
existing.subscribers.add(taskId);
|
|
31275
|
+
if (existing.lastPayload) {
|
|
31276
|
+
callbacks.onPRState({ ...existing.lastPayload, taskId });
|
|
31277
|
+
}
|
|
31278
|
+
log.info({ taskId, topLevel, cwd }, "PR polling joined existing repo");
|
|
31279
|
+
return;
|
|
31280
|
+
}
|
|
31281
|
+
const entry = {
|
|
31282
|
+
timer: null,
|
|
31283
|
+
burstTimers: [],
|
|
31284
|
+
lastContent: "",
|
|
31285
|
+
lastBranch: "",
|
|
31286
|
+
lastPayload: null,
|
|
31287
|
+
cwd,
|
|
31288
|
+
subscribers: /* @__PURE__ */ new Set([taskId]),
|
|
31289
|
+
isPolling: false
|
|
31290
|
+
};
|
|
31291
|
+
poll(topLevel, entry).catch((err) => {
|
|
31292
|
+
log.warn({ err, topLevel }, "Initial PR poll failed");
|
|
30920
31293
|
});
|
|
30921
|
-
|
|
30922
|
-
|
|
30923
|
-
|
|
31294
|
+
entry.timer = setInterval(() => {
|
|
31295
|
+
poll(topLevel, entry).catch((err) => {
|
|
31296
|
+
log.warn({ err, topLevel }, "PR poll tick failed");
|
|
31297
|
+
});
|
|
31298
|
+
}, PR_POLL_INTERVAL_MS);
|
|
31299
|
+
repos.set(topLevel, entry);
|
|
31300
|
+
log.info({ taskId, topLevel, cwd }, "PR polling started");
|
|
31301
|
+
}).catch((err) => {
|
|
31302
|
+
log.warn({ err, taskId, cwd }, "Failed to resolve git toplevel for PR polling");
|
|
31303
|
+
});
|
|
30924
31304
|
}
|
|
30925
31305
|
function getCwd(taskId) {
|
|
30926
|
-
return
|
|
31306
|
+
return taskToRepo.get(taskId)?.cwd ?? null;
|
|
30927
31307
|
}
|
|
30928
31308
|
function stopPolling(taskId) {
|
|
30929
|
-
|
|
30930
|
-
if (existing) {
|
|
30931
|
-
clearInterval(existing.timer);
|
|
30932
|
-
for (const t of existing.burstTimers) clearTimeout(t);
|
|
30933
|
-
polls.delete(taskId);
|
|
30934
|
-
log.info({ taskId }, "PR polling stopped");
|
|
30935
|
-
}
|
|
31309
|
+
removeFromPreviousRepo(taskId);
|
|
30936
31310
|
}
|
|
30937
31311
|
function forceRefresh(taskId) {
|
|
30938
|
-
const
|
|
31312
|
+
const info = taskToRepo.get(taskId);
|
|
31313
|
+
if (!info) return;
|
|
31314
|
+
const entry = repos.get(info.topLevel);
|
|
30939
31315
|
if (!entry) return;
|
|
30940
31316
|
for (const t of entry.burstTimers) clearTimeout(t);
|
|
30941
31317
|
entry.burstTimers = [];
|
|
30942
31318
|
entry.lastContent = "";
|
|
30943
|
-
poll(
|
|
30944
|
-
log.warn({ err,
|
|
31319
|
+
poll(info.topLevel, entry).catch((err) => {
|
|
31320
|
+
log.warn({ err, topLevel: info.topLevel }, "PR force refresh failed");
|
|
30945
31321
|
});
|
|
30946
31322
|
for (const delayMs of [3e3, 8e3, 15e3]) {
|
|
30947
31323
|
entry.burstTimers.push(
|
|
30948
31324
|
setTimeout(() => {
|
|
30949
31325
|
entry.lastContent = "";
|
|
30950
|
-
poll(
|
|
30951
|
-
log.warn({ err,
|
|
31326
|
+
poll(info.topLevel, entry).catch((err) => {
|
|
31327
|
+
log.warn({ err, topLevel: info.topLevel }, "PR burst refresh failed");
|
|
30952
31328
|
});
|
|
30953
31329
|
}, delayMs)
|
|
30954
31330
|
);
|
|
30955
31331
|
}
|
|
30956
31332
|
}
|
|
30957
31333
|
function dispose() {
|
|
30958
|
-
|
|
31334
|
+
disposed = true;
|
|
31335
|
+
for (const [topLevel, entry] of repos) {
|
|
30959
31336
|
clearInterval(entry.timer);
|
|
30960
31337
|
for (const t of entry.burstTimers) clearTimeout(t);
|
|
30961
|
-
log.debug({
|
|
31338
|
+
log.debug({ topLevel }, "PR poll timer cleared on dispose");
|
|
30962
31339
|
}
|
|
30963
|
-
|
|
31340
|
+
repos.clear();
|
|
31341
|
+
taskToRepo.clear();
|
|
30964
31342
|
}
|
|
30965
31343
|
return { startPolling, stopPolling, forceRefresh, getCwd, dispose };
|
|
30966
31344
|
}
|
|
@@ -31792,10 +32170,11 @@ async function rehydrateFromPersistence(persistence, taskManager, log, taskState
|
|
|
31792
32170
|
async function sweepStaleTasks(taskStateStore, taskManager, log) {
|
|
31793
32171
|
const tasks = await taskStateStore.listTasks();
|
|
31794
32172
|
const actions = planStaleSweep(tasks, (id) => taskManager.isRunning(id));
|
|
32173
|
+
const noBroadcast = { broadcast: false };
|
|
31795
32174
|
for (const action of actions) {
|
|
31796
32175
|
if (action.kind === "mark_input_required") {
|
|
31797
|
-
await taskStateStore.updateTaskStatus(action.taskId, "input_required");
|
|
31798
|
-
await taskStateStore.acknowledge(action.taskId);
|
|
32176
|
+
await taskStateStore.updateTaskStatus(action.taskId, "input_required", noBroadcast);
|
|
32177
|
+
await taskStateStore.acknowledge(action.taskId, noBroadcast);
|
|
31799
32178
|
log({
|
|
31800
32179
|
event: "stale_task_swept",
|
|
31801
32180
|
taskId: action.taskId,
|
|
@@ -33797,7 +34176,7 @@ var TOOL_DESCRIPTION2 = [
|
|
|
33797
34176
|
"The preview panel opens in the Shipyard UI next to the conversation. The user can interact with it directly."
|
|
33798
34177
|
].join("\n");
|
|
33799
34178
|
var SetPreviewTargetInput = {
|
|
33800
|
-
port: external_exports.number().int().
|
|
34179
|
+
port: external_exports.number().int().min(1).max(65535).optional().describe("Local port number of the running dev server (e.g. 3000, 5173, 8080)."),
|
|
33801
34180
|
url: external_exports.string().url().optional().describe('Fully qualified URL to preview (e.g. "http://localhost:3000/dashboard").')
|
|
33802
34181
|
};
|
|
33803
34182
|
function createPreviewTools(ctx) {
|
|
@@ -33818,9 +34197,14 @@ function createPreviewTools(ctx) {
|
|
|
33818
34197
|
isError: true
|
|
33819
34198
|
};
|
|
33820
34199
|
}
|
|
33821
|
-
|
|
33822
|
-
|
|
33823
|
-
|
|
34200
|
+
if (input.url != null) {
|
|
34201
|
+
await ctx.previewProxy.stop();
|
|
34202
|
+
} else if (input.port != null) {
|
|
34203
|
+
if (ctx.previewProxy.port) {
|
|
34204
|
+
ctx.previewProxy.retarget(input.port);
|
|
34205
|
+
} else {
|
|
34206
|
+
await ctx.previewProxy.start({ port: input.port });
|
|
34207
|
+
}
|
|
33824
34208
|
}
|
|
33825
34209
|
ctx.broadcastControl({
|
|
33826
34210
|
type: "set_preview_target",
|
|
@@ -42788,6 +43172,16 @@ When you call ExitPlanMode, your plan is published to the Shipyard canvas for co
|
|
|
42788
43172
|
**While in plan mode** \u2014 place supporting visualizations on the canvas using \`present(slug, "canvas")\`. Architecture diagrams, data models, and flow charts alongside the plan text help reviewers understand and evaluate your design.
|
|
42789
43173
|
|
|
42790
43174
|
Do not revise the plan unless reviewers request changes. When you see \`<${XML_TAGS.PLAN_REVIEW} decision="approve">\`, call ExitPlanMode again to begin implementation.
|
|
43175
|
+
|
|
43176
|
+
## Background Execution
|
|
43177
|
+
|
|
43178
|
+
Prefer background execution to keep your main thread responsive to collaborators.
|
|
43179
|
+
|
|
43180
|
+
**Agent tool** \u2014 Always use \`run_in_background: true\`. Foreground agents block your main thread \u2014 collaborators cannot interact with you, and there is no way to move a foreground operation to background after it starts. Background agents run in parallel and notify on completion.
|
|
43181
|
+
|
|
43182
|
+
**Bash tool** \u2014 Use \`run_in_background: true\` for commands that may take more than 10 seconds: builds, installs, test suites, large git operations, network requests. Use \`TaskOutput\` to read results when notified. Keep short commands (git status, ls, cat) in the foreground.
|
|
43183
|
+
|
|
43184
|
+
**Why this matters:** Foreground operations block everything. The only recovery from a stuck foreground operation is stopping the entire agent. Background-first execution avoids this and keeps you available for collaborator feedback.
|
|
42791
43185
|
`.trim();
|
|
42792
43186
|
function buildCollabSystemPrompt(participants) {
|
|
42793
43187
|
const roster = participants.map((p2) => `${p2.name} (${p2.role})`).join(", ");
|
|
@@ -66428,11 +66822,13 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66428
66822
|
* Guards all SDK write operations to prevent "ProcessTransport not ready" errors.
|
|
66429
66823
|
*/
|
|
66430
66824
|
#closed = false;
|
|
66431
|
-
|
|
66825
|
+
#pidRef;
|
|
66826
|
+
constructor(query3, controller, onEvent, spawnMcpServers, pidRef) {
|
|
66432
66827
|
this.#query = query3;
|
|
66433
66828
|
this.#controller = controller;
|
|
66434
66829
|
this.#onEvent = onEvent;
|
|
66435
66830
|
this.#spawnMcpServers = spawnMcpServers;
|
|
66831
|
+
this.#pidRef = pidRef;
|
|
66436
66832
|
}
|
|
66437
66833
|
/** True once the subprocess has exited and SDK writes will throw. */
|
|
66438
66834
|
get isClosed() {
|
|
@@ -66452,27 +66848,8 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66452
66848
|
const controller = new InputController();
|
|
66453
66849
|
const { onChildSpawned, onChildExited } = options;
|
|
66454
66850
|
const stderrCallback = options.stderr;
|
|
66455
|
-
const
|
|
66456
|
-
|
|
66457
|
-
cwd: spawnOpts.cwd,
|
|
66458
|
-
stdio: ["pipe", "pipe", stderrCallback ? "pipe" : "ignore"],
|
|
66459
|
-
signal: spawnOpts.signal,
|
|
66460
|
-
env: { ...spawnOpts.env, CLAUDECODE: void 0 },
|
|
66461
|
-
windowsHide: true
|
|
66462
|
-
});
|
|
66463
|
-
if (!child.stdin || !child.stdout) {
|
|
66464
|
-
throw new Error("Failed to create stdio pipes for Claude subprocess");
|
|
66465
|
-
}
|
|
66466
|
-
if (stderrCallback && child.stderr) {
|
|
66467
|
-
child.stderr.on("data", (chunk) => stderrCallback(chunk.toString()));
|
|
66468
|
-
}
|
|
66469
|
-
if (child.pid) {
|
|
66470
|
-
const pid = child.pid;
|
|
66471
|
-
onChildSpawned(pid);
|
|
66472
|
-
child.on("exit", () => onChildExited?.(pid));
|
|
66473
|
-
}
|
|
66474
|
-
return Object.assign(child, { stdin: child.stdin, stdout: child.stdout });
|
|
66475
|
-
} : void 0;
|
|
66851
|
+
const pidRef = { current: null };
|
|
66852
|
+
const spawnClaudeCodeProcess = onChildSpawned ? createChildSpawner(stderrCallback, pidRef, onChildSpawned, onChildExited) : void 0;
|
|
66476
66853
|
const queryInstance = queryFn({
|
|
66477
66854
|
prompt: controller.iterable(),
|
|
66478
66855
|
options: {
|
|
@@ -66503,7 +66880,8 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66503
66880
|
queryInstance,
|
|
66504
66881
|
controller,
|
|
66505
66882
|
onEvent,
|
|
66506
|
-
spawnMcpServers
|
|
66883
|
+
spawnMcpServers,
|
|
66884
|
+
pidRef
|
|
66507
66885
|
);
|
|
66508
66886
|
if (initialContent.length > 0) {
|
|
66509
66887
|
controller.push(toSdkContent(initialContent));
|
|
@@ -66528,6 +66906,13 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66528
66906
|
this.#query.close();
|
|
66529
66907
|
}
|
|
66530
66908
|
forceKill() {
|
|
66909
|
+
if (this.#pidRef.current != null) {
|
|
66910
|
+
try {
|
|
66911
|
+
process.kill(this.#pidRef.current, "SIGKILL");
|
|
66912
|
+
} catch {
|
|
66913
|
+
}
|
|
66914
|
+
this.#pidRef.current = null;
|
|
66915
|
+
}
|
|
66531
66916
|
this.#closed = true;
|
|
66532
66917
|
this.#controller.end();
|
|
66533
66918
|
this.#query.close();
|
|
@@ -66584,6 +66969,14 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66584
66969
|
await this.#query.reconnectMcpServer(serverName);
|
|
66585
66970
|
}
|
|
66586
66971
|
}
|
|
66972
|
+
async stopBackgroundTask(taskId) {
|
|
66973
|
+
if (this.#closed) return;
|
|
66974
|
+
try {
|
|
66975
|
+
await this.#query.stopTask(taskId);
|
|
66976
|
+
} catch (err) {
|
|
66977
|
+
if (!this.#closed) throw err;
|
|
66978
|
+
}
|
|
66979
|
+
}
|
|
66587
66980
|
async #runMessageLoop(log) {
|
|
66588
66981
|
try {
|
|
66589
66982
|
for await (const message of this.#query) {
|
|
@@ -66592,9 +66985,11 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66592
66985
|
this.#onEvent(event);
|
|
66593
66986
|
}
|
|
66594
66987
|
}
|
|
66988
|
+
this.#pidRef.current = null;
|
|
66595
66989
|
this.#closed = true;
|
|
66596
66990
|
this.#onEvent({ type: "subprocess_died" });
|
|
66597
66991
|
} catch (error2) {
|
|
66992
|
+
this.#pidRef.current = null;
|
|
66598
66993
|
this.#closed = true;
|
|
66599
66994
|
const errorMsg = error2 instanceof Error ? error2.message : String(error2);
|
|
66600
66995
|
this.#onEvent({
|
|
@@ -66605,6 +67000,30 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66605
67000
|
}
|
|
66606
67001
|
}
|
|
66607
67002
|
};
|
|
67003
|
+
function createChildSpawner(stderrCallback, pidRef, onChildSpawned, onChildExited) {
|
|
67004
|
+
return (spawnOpts) => {
|
|
67005
|
+
const child = spawn4(spawnOpts.command, spawnOpts.args, {
|
|
67006
|
+
cwd: spawnOpts.cwd,
|
|
67007
|
+
stdio: ["pipe", "pipe", stderrCallback ? "pipe" : "ignore"],
|
|
67008
|
+
signal: spawnOpts.signal,
|
|
67009
|
+
env: { ...spawnOpts.env, CLAUDECODE: void 0 },
|
|
67010
|
+
windowsHide: true
|
|
67011
|
+
});
|
|
67012
|
+
if (!child.stdin || !child.stdout) {
|
|
67013
|
+
throw new Error("Failed to create stdio pipes for Claude subprocess");
|
|
67014
|
+
}
|
|
67015
|
+
if (stderrCallback && child.stderr) {
|
|
67016
|
+
child.stderr.on("data", (chunk) => stderrCallback(chunk.toString()));
|
|
67017
|
+
}
|
|
67018
|
+
if (child.pid) {
|
|
67019
|
+
const pid = child.pid;
|
|
67020
|
+
pidRef.current = pid;
|
|
67021
|
+
onChildSpawned(pid);
|
|
67022
|
+
child.on("exit", () => onChildExited?.(pid));
|
|
67023
|
+
}
|
|
67024
|
+
return Object.assign(child, { stdin: child.stdin, stdout: child.stdout });
|
|
67025
|
+
};
|
|
67026
|
+
}
|
|
66608
67027
|
function toSdkPermissionMode(mode) {
|
|
66609
67028
|
switch (mode) {
|
|
66610
67029
|
case "default":
|
|
@@ -72958,6 +73377,9 @@ var DirectApiSubprocess = class _DirectApiSubprocess {
|
|
|
72958
73377
|
/** No-op: no MCP in direct API mode. */
|
|
72959
73378
|
async reconnectMcpServer(_serverName) {
|
|
72960
73379
|
}
|
|
73380
|
+
/** No-op: direct API mode does not support background tasks. */
|
|
73381
|
+
async stopBackgroundTask(_taskId) {
|
|
73382
|
+
}
|
|
72961
73383
|
/** No-op in Phase 1 -- harness task context not used by direct API mode. */
|
|
72962
73384
|
setHarnessTaskId(_taskId) {
|
|
72963
73385
|
}
|
|
@@ -75062,12 +75484,20 @@ function noop3(snapshot) {
|
|
|
75062
75484
|
};
|
|
75063
75485
|
}
|
|
75064
75486
|
function handleColdIdle(snapshot, event) {
|
|
75065
|
-
if (event.type
|
|
75066
|
-
|
|
75067
|
-
|
|
75068
|
-
|
|
75069
|
-
|
|
75070
|
-
|
|
75487
|
+
if (event.type === "user_message") {
|
|
75488
|
+
const b2 = createEffectBuilder();
|
|
75489
|
+
b2.log("cold_idle", "spawning", event.type);
|
|
75490
|
+
b2.taskStatus("in_progress");
|
|
75491
|
+
b2.effects.push({ type: "spawn", reason: { kind: "fresh" } });
|
|
75492
|
+
return { state: "spawning", sessionId: null, rewindAtMessageId: null, effects: b2.effects };
|
|
75493
|
+
}
|
|
75494
|
+
if (event.type === "stop_commanded") {
|
|
75495
|
+
const b2 = createEffectBuilder();
|
|
75496
|
+
b2.effects.push({ type: "clear_queue" });
|
|
75497
|
+
b2.taskStatus("input_required");
|
|
75498
|
+
return { state: "cold_idle", sessionId: null, rewindAtMessageId: null, effects: b2.effects };
|
|
75499
|
+
}
|
|
75500
|
+
return noop3(snapshot);
|
|
75071
75501
|
}
|
|
75072
75502
|
function handleResumableIdle(snapshot, event) {
|
|
75073
75503
|
const { sessionId, rewindAtMessageId } = snapshot;
|
|
@@ -75086,6 +75516,12 @@ function handleResumableIdle(snapshot, event) {
|
|
|
75086
75516
|
b2.effects.push({ type: "spawn", reason });
|
|
75087
75517
|
return { state: "spawning", sessionId, rewindAtMessageId: null, effects: b2.effects };
|
|
75088
75518
|
}
|
|
75519
|
+
if (event.type === "stop_commanded") {
|
|
75520
|
+
const b2 = createEffectBuilder();
|
|
75521
|
+
b2.effects.push({ type: "clear_queue" });
|
|
75522
|
+
b2.taskStatus("input_required");
|
|
75523
|
+
return { state: "resumable_idle", sessionId, rewindAtMessageId, effects: b2.effects };
|
|
75524
|
+
}
|
|
75089
75525
|
if (event.type === "session_expired") {
|
|
75090
75526
|
const b2 = createEffectBuilder();
|
|
75091
75527
|
b2.log("resumable_idle", "cold_idle", event.type);
|
|
@@ -75245,12 +75681,7 @@ function exitStoppingAlive(snapshot, trigger) {
|
|
|
75245
75681
|
const { sessionId, rewindAtMessageId } = snapshot;
|
|
75246
75682
|
const b2 = createEffectBuilder();
|
|
75247
75683
|
b2.effects.push({ type: "clear_pending_inputs" });
|
|
75248
|
-
|
|
75249
|
-
b2.log("stopping", "running", trigger);
|
|
75250
|
-
b2.effects.push({ type: "push_message" });
|
|
75251
|
-
b2.taskStatus("in_progress");
|
|
75252
|
-
return { state: "running", sessionId, rewindAtMessageId, effects: b2.effects };
|
|
75253
|
-
}
|
|
75684
|
+
b2.effects.push({ type: "clear_queue" });
|
|
75254
75685
|
b2.log("stopping", "warm_idle", trigger);
|
|
75255
75686
|
b2.taskStatus("input_required");
|
|
75256
75687
|
return { state: "warm_idle", sessionId, rewindAtMessageId, effects: b2.effects };
|
|
@@ -75278,7 +75709,7 @@ function handleStopping(snapshot, event) {
|
|
|
75278
75709
|
b2.taskStatus("input_required");
|
|
75279
75710
|
return { state: "resumable_idle", sessionId, rewindAtMessageId, effects: b2.effects };
|
|
75280
75711
|
}
|
|
75281
|
-
if (event.type === "stop_timeout") {
|
|
75712
|
+
if (event.type === "stop_timeout" || event.type === "interrupt_failed") {
|
|
75282
75713
|
b2.log("stopping", "resumable_idle", event.type);
|
|
75283
75714
|
b2.effects.push({ type: "force_kill" });
|
|
75284
75715
|
b2.effects.push({ type: "clear_pending_inputs" });
|
|
@@ -75367,6 +75798,9 @@ var AgentSessionManager = class {
|
|
|
75367
75798
|
notifyInterruptAcknowledged() {
|
|
75368
75799
|
this.#dispatch({ type: "interrupt_acknowledged" });
|
|
75369
75800
|
}
|
|
75801
|
+
notifyInterruptFailed() {
|
|
75802
|
+
this.#dispatch({ type: "interrupt_failed" });
|
|
75803
|
+
}
|
|
75370
75804
|
notifyCloseAcknowledged() {
|
|
75371
75805
|
this.#dispatch({ type: "close_acknowledged" });
|
|
75372
75806
|
}
|
|
@@ -75868,6 +76302,10 @@ var Thread = class {
|
|
|
75868
76302
|
if (!this.#subprocess) return;
|
|
75869
76303
|
return this.#subprocess.mcpSubmitOAuthCallbackUrl(serverName, callbackUrl);
|
|
75870
76304
|
}
|
|
76305
|
+
async stopBackgroundTask(taskId) {
|
|
76306
|
+
if (!this.#subprocess) return;
|
|
76307
|
+
await this.#subprocess.stopBackgroundTask(taskId);
|
|
76308
|
+
}
|
|
75871
76309
|
async dispose() {
|
|
75872
76310
|
this.#disposed = true;
|
|
75873
76311
|
this.#permissionHandler.denyAllPending("Thread disposed");
|
|
@@ -76158,13 +76596,18 @@ ${conversationReplay}` : conversationReplay;
|
|
|
76158
76596
|
this.#subprocess?.pushMessage(content);
|
|
76159
76597
|
}
|
|
76160
76598
|
#handleInterrupt() {
|
|
76161
|
-
this.#subprocess
|
|
76599
|
+
if (!this.#subprocess || this.#subprocess.isClosed) {
|
|
76600
|
+
this.#manager.notifySubprocessDied();
|
|
76601
|
+
return;
|
|
76602
|
+
}
|
|
76603
|
+
this.#subprocess.interrupt().catch((err) => {
|
|
76162
76604
|
const msg = err instanceof Error ? err.message : String(err);
|
|
76163
76605
|
this.#config.log({
|
|
76164
76606
|
event: "thread_interrupt_failed",
|
|
76165
76607
|
threadId: this.#config.threadId,
|
|
76166
76608
|
error: msg
|
|
76167
76609
|
});
|
|
76610
|
+
this.#manager.notifyInterruptFailed();
|
|
76168
76611
|
});
|
|
76169
76612
|
}
|
|
76170
76613
|
#handleClose() {
|
|
@@ -81061,6 +81504,9 @@ var Task = class _Task {
|
|
|
81061
81504
|
this.#explicitlyStopped = true;
|
|
81062
81505
|
this.#mainThread.stop();
|
|
81063
81506
|
}
|
|
81507
|
+
async stopBackgroundTask(backgroundTaskId) {
|
|
81508
|
+
await this.#mainThread.stopBackgroundTask(backgroundTaskId);
|
|
81509
|
+
}
|
|
81064
81510
|
cancelQueued() {
|
|
81065
81511
|
const count = this.#mainThread.cancelQueued();
|
|
81066
81512
|
const collabCount = this.#collabQueue.cancelQueued();
|
|
@@ -82081,6 +82527,122 @@ Use this context to maintain continuity. You have already done this work \u2014
|
|
|
82081
82527
|
}
|
|
82082
82528
|
};
|
|
82083
82529
|
|
|
82530
|
+
// src/services/task/task-manager-mcp.ts
|
|
82531
|
+
function hasLiveSubprocess(state) {
|
|
82532
|
+
return state === "warm_idle" || state === "running" || state === "spawning";
|
|
82533
|
+
}
|
|
82534
|
+
function updateMcpServers(tasks, deps) {
|
|
82535
|
+
const resolved = deps.resolveMcpServers() ?? {};
|
|
82536
|
+
for (const task of tasks.values()) {
|
|
82537
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82538
|
+
task.orchestrator.setMcpServers(resolved).then((result) => {
|
|
82539
|
+
deps.log({
|
|
82540
|
+
event: "mcp_servers_updated",
|
|
82541
|
+
taskId: task.taskId,
|
|
82542
|
+
added: result?.added ?? [],
|
|
82543
|
+
removed: result?.removed ?? [],
|
|
82544
|
+
errors: result?.errors ?? {}
|
|
82545
|
+
});
|
|
82546
|
+
}).catch((err) => {
|
|
82547
|
+
deps.log({
|
|
82548
|
+
event: "mcp_servers_update_failed",
|
|
82549
|
+
taskId: task.taskId,
|
|
82550
|
+
error: err instanceof Error ? err.message : String(err)
|
|
82551
|
+
});
|
|
82552
|
+
});
|
|
82553
|
+
}
|
|
82554
|
+
}
|
|
82555
|
+
function triggerFastMcpPolling(tasks) {
|
|
82556
|
+
for (const task of tasks.values()) {
|
|
82557
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82558
|
+
task.orchestrator.triggerFastMcpPolling();
|
|
82559
|
+
}
|
|
82560
|
+
}
|
|
82561
|
+
function toggleMcpServer(tasks, serverName, enabled, log) {
|
|
82562
|
+
for (const task of tasks.values()) {
|
|
82563
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82564
|
+
task.orchestrator.toggleMcpServer(serverName, enabled).then(() => {
|
|
82565
|
+
log({
|
|
82566
|
+
event: "mcp_server_toggled",
|
|
82567
|
+
taskId: task.taskId,
|
|
82568
|
+
serverName,
|
|
82569
|
+
enabled
|
|
82570
|
+
});
|
|
82571
|
+
}).catch((err) => {
|
|
82572
|
+
log({
|
|
82573
|
+
event: "mcp_server_toggle_failed",
|
|
82574
|
+
taskId: task.taskId,
|
|
82575
|
+
serverName,
|
|
82576
|
+
enabled,
|
|
82577
|
+
error: err instanceof Error ? err.message : String(err)
|
|
82578
|
+
});
|
|
82579
|
+
});
|
|
82580
|
+
}
|
|
82581
|
+
}
|
|
82582
|
+
function reconnectMcpServer(tasks, serverName, deps) {
|
|
82583
|
+
const fullSet = deps.resolveMcpServers() ?? {};
|
|
82584
|
+
for (const task of tasks.values()) {
|
|
82585
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82586
|
+
const withoutTarget = { ...fullSet };
|
|
82587
|
+
delete withoutTarget[serverName];
|
|
82588
|
+
task.orchestrator.setMcpServers(withoutTarget).then(() => task.orchestrator.setMcpServers(fullSet)).then((result) => {
|
|
82589
|
+
deps.log({
|
|
82590
|
+
event: "mcp_server_reconnected",
|
|
82591
|
+
taskId: task.taskId,
|
|
82592
|
+
serverName,
|
|
82593
|
+
added: result?.added ?? [],
|
|
82594
|
+
removed: result?.removed ?? []
|
|
82595
|
+
});
|
|
82596
|
+
}).catch((err) => {
|
|
82597
|
+
deps.log({
|
|
82598
|
+
event: "mcp_server_reconnect_failed",
|
|
82599
|
+
taskId: task.taskId,
|
|
82600
|
+
serverName,
|
|
82601
|
+
error: err instanceof Error ? err.message : String(err)
|
|
82602
|
+
});
|
|
82603
|
+
});
|
|
82604
|
+
}
|
|
82605
|
+
}
|
|
82606
|
+
async function mcpAuthenticate(tasks, serverName) {
|
|
82607
|
+
for (const task of tasks.values()) {
|
|
82608
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82609
|
+
return task.orchestrator.mcpAuthenticate(serverName);
|
|
82610
|
+
}
|
|
82611
|
+
return null;
|
|
82612
|
+
}
|
|
82613
|
+
async function mcpSubmitOAuthCallbackUrl(tasks, serverName, callbackUrl) {
|
|
82614
|
+
for (const task of tasks.values()) {
|
|
82615
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82616
|
+
await task.orchestrator.mcpSubmitOAuthCallbackUrl(serverName, callbackUrl);
|
|
82617
|
+
return;
|
|
82618
|
+
}
|
|
82619
|
+
}
|
|
82620
|
+
function planGracefulShutdown(tasks, gracePeriodMs) {
|
|
82621
|
+
const immediate = [];
|
|
82622
|
+
const deferred = [];
|
|
82623
|
+
for (const task of tasks) {
|
|
82624
|
+
switch (task.state) {
|
|
82625
|
+
case "cold_idle":
|
|
82626
|
+
case "resumable_idle":
|
|
82627
|
+
case "warm_idle":
|
|
82628
|
+
immediate.push({ taskId: task.taskId, kind: "dispose_immediately" });
|
|
82629
|
+
break;
|
|
82630
|
+
case "spawning":
|
|
82631
|
+
case "running":
|
|
82632
|
+
deferred.push({ taskId: task.taskId, kind: "stop_then_wait" });
|
|
82633
|
+
break;
|
|
82634
|
+
case "stopping":
|
|
82635
|
+
deferred.push({ taskId: task.taskId, kind: "stop_then_wait" });
|
|
82636
|
+
break;
|
|
82637
|
+
default: {
|
|
82638
|
+
const _exhaustive = task.state;
|
|
82639
|
+
throw new Error(`Unhandled state: ${JSON.stringify(_exhaustive)}`);
|
|
82640
|
+
}
|
|
82641
|
+
}
|
|
82642
|
+
}
|
|
82643
|
+
return { immediate, deferred, gracePeriodMs };
|
|
82644
|
+
}
|
|
82645
|
+
|
|
82084
82646
|
// src/services/task/task-manager.ts
|
|
82085
82647
|
function cloneOverlay(overlay) {
|
|
82086
82648
|
return {
|
|
@@ -82605,111 +83167,32 @@ var TaskManager = class {
|
|
|
82605
83167
|
this.#deps.log({ event: "task_stop_requested", taskId });
|
|
82606
83168
|
this.#tasks.get(taskId)?.orchestrator.stop();
|
|
82607
83169
|
}
|
|
82608
|
-
|
|
82609
|
-
|
|
82610
|
-
|
|
82611
|
-
|
|
82612
|
-
|
|
82613
|
-
|
|
82614
|
-
const resolved = this.#deps.resolveMcpServers() ?? {};
|
|
82615
|
-
for (const task of this.#tasks.values()) {
|
|
82616
|
-
if (!this.#hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82617
|
-
task.orchestrator.setMcpServers(resolved).then((result) => {
|
|
82618
|
-
this.#deps.log({
|
|
82619
|
-
event: "mcp_servers_updated",
|
|
82620
|
-
taskId: task.taskId,
|
|
82621
|
-
added: result?.added ?? [],
|
|
82622
|
-
removed: result?.removed ?? [],
|
|
82623
|
-
errors: result?.errors ?? {}
|
|
82624
|
-
});
|
|
82625
|
-
}).catch((err) => {
|
|
82626
|
-
this.#deps.log({
|
|
82627
|
-
event: "mcp_servers_update_failed",
|
|
82628
|
-
taskId: task.taskId,
|
|
82629
|
-
error: err instanceof Error ? err.message : String(err)
|
|
82630
|
-
});
|
|
82631
|
-
});
|
|
83170
|
+
async stopBackgroundTask(taskId, backgroundTaskId) {
|
|
83171
|
+
this.#deps.log({ event: "background_task_stop_requested", taskId, backgroundTaskId });
|
|
83172
|
+
const task = this.#tasks.get(taskId);
|
|
83173
|
+
if (!task) {
|
|
83174
|
+
this.#deps.log({ event: "background_task_stop_unknown_task", taskId, backgroundTaskId });
|
|
83175
|
+
return;
|
|
82632
83176
|
}
|
|
83177
|
+
await task.orchestrator.stopBackgroundTask(backgroundTaskId);
|
|
83178
|
+
}
|
|
83179
|
+
updateMcpServers() {
|
|
83180
|
+
updateMcpServers(this.#tasks, this.#deps);
|
|
82633
83181
|
}
|
|
82634
83182
|
triggerFastMcpPolling() {
|
|
82635
|
-
|
|
82636
|
-
if (!this.#hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82637
|
-
task.orchestrator.triggerFastMcpPolling();
|
|
82638
|
-
}
|
|
83183
|
+
triggerFastMcpPolling(this.#tasks);
|
|
82639
83184
|
}
|
|
82640
83185
|
toggleMcpServer(serverName, enabled) {
|
|
82641
|
-
|
|
82642
|
-
if (!this.#hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82643
|
-
task.orchestrator.toggleMcpServer(serverName, enabled).then(() => {
|
|
82644
|
-
this.#deps.log({
|
|
82645
|
-
event: "mcp_server_toggled",
|
|
82646
|
-
taskId: task.taskId,
|
|
82647
|
-
serverName,
|
|
82648
|
-
enabled
|
|
82649
|
-
});
|
|
82650
|
-
}).catch((err) => {
|
|
82651
|
-
this.#deps.log({
|
|
82652
|
-
event: "mcp_server_toggle_failed",
|
|
82653
|
-
taskId: task.taskId,
|
|
82654
|
-
serverName,
|
|
82655
|
-
enabled,
|
|
82656
|
-
error: err instanceof Error ? err.message : String(err)
|
|
82657
|
-
});
|
|
82658
|
-
});
|
|
82659
|
-
}
|
|
83186
|
+
toggleMcpServer(this.#tasks, serverName, enabled, this.#deps.log);
|
|
82660
83187
|
}
|
|
82661
|
-
/**
|
|
82662
|
-
* Force reconnect a specific MCP server after token refresh.
|
|
82663
|
-
*
|
|
82664
|
-
* The SDK subprocess diffs setMcpServers by name: if the server name
|
|
82665
|
-
* already exists it is NOT reconnected even when headers change. To
|
|
82666
|
-
* pick up fresh credentials we remove the server first, then re-add it.
|
|
82667
|
-
*/
|
|
82668
83188
|
reconnectMcpServer(serverName) {
|
|
82669
|
-
|
|
82670
|
-
for (const task of this.#tasks.values()) {
|
|
82671
|
-
if (!this.#hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82672
|
-
const withoutTarget = { ...fullSet };
|
|
82673
|
-
delete withoutTarget[serverName];
|
|
82674
|
-
task.orchestrator.setMcpServers(withoutTarget).then(() => task.orchestrator.setMcpServers(fullSet)).then((result) => {
|
|
82675
|
-
this.#deps.log({
|
|
82676
|
-
event: "mcp_server_reconnected",
|
|
82677
|
-
taskId: task.taskId,
|
|
82678
|
-
serverName,
|
|
82679
|
-
added: result?.added ?? [],
|
|
82680
|
-
removed: result?.removed ?? []
|
|
82681
|
-
});
|
|
82682
|
-
}).catch((err) => {
|
|
82683
|
-
this.#deps.log({
|
|
82684
|
-
event: "mcp_server_reconnect_failed",
|
|
82685
|
-
taskId: task.taskId,
|
|
82686
|
-
serverName,
|
|
82687
|
-
error: err instanceof Error ? err.message : String(err)
|
|
82688
|
-
});
|
|
82689
|
-
});
|
|
82690
|
-
}
|
|
83189
|
+
reconnectMcpServer(this.#tasks, serverName, this.#deps);
|
|
82691
83190
|
}
|
|
82692
|
-
/**
|
|
82693
|
-
* Initiate SDK-level OAuth for an MCP server on any active task.
|
|
82694
|
-
* Returns the authorize URL if the SDK supports it, null otherwise.
|
|
82695
|
-
*/
|
|
82696
83191
|
async mcpAuthenticate(serverName) {
|
|
82697
|
-
|
|
82698
|
-
if (!this.#hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82699
|
-
return task.orchestrator.mcpAuthenticate(serverName);
|
|
82700
|
-
}
|
|
82701
|
-
return null;
|
|
83192
|
+
return mcpAuthenticate(this.#tasks, serverName);
|
|
82702
83193
|
}
|
|
82703
83194
|
async mcpSubmitOAuthCallbackUrl(serverName, callbackUrl) {
|
|
82704
|
-
|
|
82705
|
-
if (!this.#hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82706
|
-
await task.orchestrator.mcpSubmitOAuthCallbackUrl(serverName, callbackUrl);
|
|
82707
|
-
return;
|
|
82708
|
-
}
|
|
82709
|
-
}
|
|
82710
|
-
/** True when the state implies a subprocess is alive and can accept commands. */
|
|
82711
|
-
#hasLiveSubprocess(state) {
|
|
82712
|
-
return state === "warm_idle" || state === "running" || state === "spawning";
|
|
83195
|
+
return mcpSubmitOAuthCallbackUrl(this.#tasks, serverName, callbackUrl);
|
|
82713
83196
|
}
|
|
82714
83197
|
cancelQueued(taskId) {
|
|
82715
83198
|
const task = this.#tasks.get(taskId);
|
|
@@ -82886,7 +83369,7 @@ var TaskManager = class {
|
|
|
82886
83369
|
/** Whether any task has a live subprocess that can accept MCP commands. */
|
|
82887
83370
|
get hasLiveSubprocesses() {
|
|
82888
83371
|
for (const task of this.#tasks.values()) {
|
|
82889
|
-
if (
|
|
83372
|
+
if (hasLiveSubprocess(task.orchestrator.state)) return true;
|
|
82890
83373
|
}
|
|
82891
83374
|
return false;
|
|
82892
83375
|
}
|
|
@@ -83016,31 +83499,6 @@ var TaskManager = class {
|
|
|
83016
83499
|
});
|
|
83017
83500
|
}
|
|
83018
83501
|
};
|
|
83019
|
-
function planGracefulShutdown(tasks, gracePeriodMs) {
|
|
83020
|
-
const immediate = [];
|
|
83021
|
-
const deferred = [];
|
|
83022
|
-
for (const task of tasks) {
|
|
83023
|
-
switch (task.state) {
|
|
83024
|
-
case "cold_idle":
|
|
83025
|
-
case "resumable_idle":
|
|
83026
|
-
case "warm_idle":
|
|
83027
|
-
immediate.push({ taskId: task.taskId, kind: "dispose_immediately" });
|
|
83028
|
-
break;
|
|
83029
|
-
case "spawning":
|
|
83030
|
-
case "running":
|
|
83031
|
-
deferred.push({ taskId: task.taskId, kind: "stop_then_wait" });
|
|
83032
|
-
break;
|
|
83033
|
-
case "stopping":
|
|
83034
|
-
deferred.push({ taskId: task.taskId, kind: "stop_then_wait" });
|
|
83035
|
-
break;
|
|
83036
|
-
default: {
|
|
83037
|
-
const _exhaustive = task.state;
|
|
83038
|
-
throw new Error(`Unhandled state: ${JSON.stringify(_exhaustive)}`);
|
|
83039
|
-
}
|
|
83040
|
-
}
|
|
83041
|
-
}
|
|
83042
|
-
return { immediate, deferred, gracePeriodMs };
|
|
83043
|
-
}
|
|
83044
83502
|
|
|
83045
83503
|
// src/services/task/task-state-store.ts
|
|
83046
83504
|
import { join as join35 } from "path";
|
|
@@ -83072,12 +83530,43 @@ function buildTaskStateStore(dataDir) {
|
|
|
83072
83530
|
const unsubVersion = store.subscribe(() => {
|
|
83073
83531
|
_version++;
|
|
83074
83532
|
});
|
|
83533
|
+
const _broadcastQueue = [];
|
|
83534
|
+
const taskListeners = /* @__PURE__ */ new Set();
|
|
83535
|
+
const unsubBroadcast = store.subscribe((event) => {
|
|
83536
|
+
const broadcast = _broadcastQueue.shift() ?? true;
|
|
83537
|
+
const augmented = { ...event, broadcast };
|
|
83538
|
+
for (const listener of taskListeners) {
|
|
83539
|
+
try {
|
|
83540
|
+
listener(augmented);
|
|
83541
|
+
} catch {
|
|
83542
|
+
}
|
|
83543
|
+
}
|
|
83544
|
+
});
|
|
83545
|
+
function pushBroadcast(options) {
|
|
83546
|
+
_broadcastQueue.push(options?.broadcast ?? true);
|
|
83547
|
+
}
|
|
83548
|
+
async function safeUpdate(taskId, fn, options) {
|
|
83549
|
+
pushBroadcast(options);
|
|
83550
|
+
const lenBefore = _broadcastQueue.length;
|
|
83551
|
+
try {
|
|
83552
|
+
await store.update(taskId, fn);
|
|
83553
|
+
} catch (err) {
|
|
83554
|
+
if (_broadcastQueue.length === lenBefore) {
|
|
83555
|
+
_broadcastQueue.pop();
|
|
83556
|
+
}
|
|
83557
|
+
throw err;
|
|
83558
|
+
}
|
|
83559
|
+
if (_broadcastQueue.length === lenBefore) {
|
|
83560
|
+
_broadcastQueue.pop();
|
|
83561
|
+
}
|
|
83562
|
+
}
|
|
83075
83563
|
return {
|
|
83076
83564
|
get version() {
|
|
83077
83565
|
return _version;
|
|
83078
83566
|
},
|
|
83079
|
-
async createTask({ taskId, channelId, title, cwd, mode, scheduleId, scheduleName }) {
|
|
83567
|
+
async createTask({ taskId, channelId, title, cwd, mode, scheduleId, scheduleName }, options) {
|
|
83080
83568
|
const now = Date.now();
|
|
83569
|
+
pushBroadcast(options);
|
|
83081
83570
|
await store.set(taskId, {
|
|
83082
83571
|
taskId,
|
|
83083
83572
|
channelId,
|
|
@@ -83097,41 +83586,46 @@ function buildTaskStateStore(dataDir) {
|
|
|
83097
83586
|
...scheduleName ? { scheduleName } : {}
|
|
83098
83587
|
});
|
|
83099
83588
|
},
|
|
83100
|
-
async updateTaskStatus(taskId, status) {
|
|
83589
|
+
async updateTaskStatus(taskId, status, options) {
|
|
83101
83590
|
const task = await store.get(taskId);
|
|
83102
83591
|
if (!task) return;
|
|
83592
|
+
pushBroadcast(options);
|
|
83103
83593
|
await store.set(taskId, applyStatusTransition(task, status, Date.now()));
|
|
83104
83594
|
},
|
|
83105
|
-
async updateTitle(taskId, title) {
|
|
83595
|
+
async updateTitle(taskId, title, options) {
|
|
83106
83596
|
const task = await store.get(taskId);
|
|
83107
83597
|
if (!task) return;
|
|
83598
|
+
pushBroadcast(options);
|
|
83108
83599
|
await store.set(taskId, {
|
|
83109
83600
|
...task,
|
|
83110
83601
|
title,
|
|
83111
83602
|
updatedAt: Date.now()
|
|
83112
83603
|
});
|
|
83113
83604
|
},
|
|
83114
|
-
async updateCwd(taskId, cwd) {
|
|
83605
|
+
async updateCwd(taskId, cwd, options) {
|
|
83115
83606
|
const task = await store.get(taskId);
|
|
83116
83607
|
if (!task) return;
|
|
83608
|
+
pushBroadcast(options);
|
|
83117
83609
|
await store.set(taskId, {
|
|
83118
83610
|
...task,
|
|
83119
83611
|
cwd,
|
|
83120
83612
|
updatedAt: Date.now()
|
|
83121
83613
|
});
|
|
83122
83614
|
},
|
|
83123
|
-
async updateMode(taskId, mode) {
|
|
83615
|
+
async updateMode(taskId, mode, options) {
|
|
83124
83616
|
const task = await store.get(taskId);
|
|
83125
83617
|
if (!task) return;
|
|
83618
|
+
pushBroadcast(options);
|
|
83126
83619
|
await store.set(taskId, {
|
|
83127
83620
|
...task,
|
|
83128
83621
|
mode,
|
|
83129
83622
|
updatedAt: Date.now()
|
|
83130
83623
|
});
|
|
83131
83624
|
},
|
|
83132
|
-
async updateTodoProgress(taskId, progress) {
|
|
83625
|
+
async updateTodoProgress(taskId, progress, options) {
|
|
83133
83626
|
const task = await store.get(taskId);
|
|
83134
83627
|
if (!task) return;
|
|
83628
|
+
pushBroadcast(options);
|
|
83135
83629
|
await store.set(taskId, {
|
|
83136
83630
|
...task,
|
|
83137
83631
|
todoCompleted: progress.todoCompleted,
|
|
@@ -83140,15 +83634,16 @@ function buildTaskStateStore(dataDir) {
|
|
|
83140
83634
|
updatedAt: Date.now()
|
|
83141
83635
|
});
|
|
83142
83636
|
},
|
|
83143
|
-
async acknowledge(taskId) {
|
|
83144
|
-
await
|
|
83637
|
+
async acknowledge(taskId, options) {
|
|
83638
|
+
await safeUpdate(taskId, (task) => ({ ...task, acknowledgedAt: Date.now() }), options);
|
|
83145
83639
|
},
|
|
83146
|
-
async togglePin(taskId) {
|
|
83147
|
-
await
|
|
83640
|
+
async togglePin(taskId, options) {
|
|
83641
|
+
await safeUpdate(taskId, (task) => ({ ...task, pinned: !task.pinned }), options);
|
|
83148
83642
|
},
|
|
83149
|
-
async updateStructuredTasks(taskId, tasks, todoProgress) {
|
|
83643
|
+
async updateStructuredTasks(taskId, tasks, todoProgress, options) {
|
|
83150
83644
|
const task = await store.get(taskId);
|
|
83151
83645
|
if (!task) return;
|
|
83646
|
+
pushBroadcast(options);
|
|
83152
83647
|
await store.set(taskId, {
|
|
83153
83648
|
...task,
|
|
83154
83649
|
structuredTasks: tasks,
|
|
@@ -83160,39 +83655,49 @@ function buildTaskStateStore(dataDir) {
|
|
|
83160
83655
|
updatedAt: Date.now()
|
|
83161
83656
|
});
|
|
83162
83657
|
},
|
|
83163
|
-
async updateTaskOverlay(taskId, overlay) {
|
|
83658
|
+
async updateTaskOverlay(taskId, overlay, options) {
|
|
83164
83659
|
const task = await store.get(taskId);
|
|
83165
83660
|
if (!task) return;
|
|
83661
|
+
pushBroadcast(options);
|
|
83166
83662
|
await store.set(taskId, {
|
|
83167
83663
|
...task,
|
|
83168
83664
|
taskOverlay: overlay,
|
|
83169
83665
|
updatedAt: Date.now()
|
|
83170
83666
|
});
|
|
83171
83667
|
},
|
|
83172
|
-
async updateComposerSettings(taskId, settings) {
|
|
83668
|
+
async updateComposerSettings(taskId, settings, options) {
|
|
83173
83669
|
const task = await store.get(taskId);
|
|
83174
83670
|
if (!task) return;
|
|
83671
|
+
pushBroadcast(options);
|
|
83175
83672
|
await store.set(taskId, {
|
|
83176
83673
|
...task,
|
|
83177
83674
|
composerSettings: { ...task.composerSettings, ...settings },
|
|
83178
83675
|
updatedAt: Date.now()
|
|
83179
83676
|
});
|
|
83180
83677
|
},
|
|
83181
|
-
async updateCostStats(taskId, stats) {
|
|
83182
|
-
await
|
|
83183
|
-
|
|
83184
|
-
|
|
83185
|
-
|
|
83186
|
-
|
|
83187
|
-
|
|
83678
|
+
async updateCostStats(taskId, stats, options) {
|
|
83679
|
+
await safeUpdate(
|
|
83680
|
+
taskId,
|
|
83681
|
+
(task) => ({
|
|
83682
|
+
...task,
|
|
83683
|
+
totalCostUsd: stats.totalCostUsd,
|
|
83684
|
+
totalOutputTokens: stats.totalOutputTokens,
|
|
83685
|
+
updatedAt: Date.now()
|
|
83686
|
+
}),
|
|
83687
|
+
options
|
|
83688
|
+
);
|
|
83188
83689
|
},
|
|
83189
|
-
async clearCostStats(taskId) {
|
|
83190
|
-
await
|
|
83191
|
-
|
|
83192
|
-
|
|
83193
|
-
|
|
83194
|
-
|
|
83195
|
-
|
|
83690
|
+
async clearCostStats(taskId, options) {
|
|
83691
|
+
await safeUpdate(
|
|
83692
|
+
taskId,
|
|
83693
|
+
(task) => ({
|
|
83694
|
+
...task,
|
|
83695
|
+
totalCostUsd: 0,
|
|
83696
|
+
totalOutputTokens: 0,
|
|
83697
|
+
updatedAt: Date.now()
|
|
83698
|
+
}),
|
|
83699
|
+
options
|
|
83700
|
+
);
|
|
83196
83701
|
},
|
|
83197
83702
|
async getTask(taskId) {
|
|
83198
83703
|
return store.get(taskId);
|
|
@@ -83201,10 +83706,14 @@ function buildTaskStateStore(dataDir) {
|
|
|
83201
83706
|
return store.list();
|
|
83202
83707
|
},
|
|
83203
83708
|
subscribe(listener) {
|
|
83204
|
-
|
|
83709
|
+
taskListeners.add(listener);
|
|
83710
|
+
return () => {
|
|
83711
|
+
taskListeners.delete(listener);
|
|
83712
|
+
};
|
|
83205
83713
|
},
|
|
83206
83714
|
dispose() {
|
|
83207
83715
|
unsubVersion();
|
|
83716
|
+
unsubBroadcast();
|
|
83208
83717
|
}
|
|
83209
83718
|
};
|
|
83210
83719
|
}
|
|
@@ -83963,6 +84472,7 @@ async function createDaemon(deps) {
|
|
|
83963
84472
|
await rehydrateFromPersistence(sessionPersistence, taskManager, deps.log, taskStateStore);
|
|
83964
84473
|
await sweepStaleTasks(taskStateStore, taskManager, deps.log);
|
|
83965
84474
|
const taskStoreUnsub = taskStateStore.subscribe((event) => {
|
|
84475
|
+
if (!event.broadcast) return;
|
|
83966
84476
|
switch (event.kind) {
|
|
83967
84477
|
case "set": {
|
|
83968
84478
|
taskManager.broadcastControl({
|
|
@@ -84311,10 +84821,12 @@ var ROLE_PERMISSIONS = {
|
|
|
84311
84821
|
"list_schedules",
|
|
84312
84822
|
"run_schedule_now",
|
|
84313
84823
|
"stop_task",
|
|
84824
|
+
"request_preview_target",
|
|
84314
84825
|
"todo_item_added",
|
|
84315
84826
|
"todo_item_removed",
|
|
84316
84827
|
"todo_item_updated",
|
|
84317
|
-
"todo_dep_changed"
|
|
84828
|
+
"todo_dep_changed",
|
|
84829
|
+
"presence_update"
|
|
84318
84830
|
]),
|
|
84319
84831
|
"collaborator-full": /* @__PURE__ */ new Set([
|
|
84320
84832
|
"send_message",
|
|
@@ -84341,10 +84853,12 @@ var ROLE_PERMISSIONS = {
|
|
|
84341
84853
|
"request_annotation_snapshot",
|
|
84342
84854
|
"create_thread",
|
|
84343
84855
|
"list_threads",
|
|
84856
|
+
"request_preview_target",
|
|
84344
84857
|
"todo_item_added",
|
|
84345
84858
|
"todo_item_removed",
|
|
84346
84859
|
"todo_item_updated",
|
|
84347
|
-
"todo_dep_changed"
|
|
84860
|
+
"todo_dep_changed",
|
|
84861
|
+
"presence_update"
|
|
84348
84862
|
]),
|
|
84349
84863
|
"collaborator-review": /* @__PURE__ */ new Set([
|
|
84350
84864
|
"stop",
|
|
@@ -84355,7 +84869,8 @@ var ROLE_PERMISSIONS = {
|
|
|
84355
84869
|
"join_collab_room",
|
|
84356
84870
|
"leave_collab_room",
|
|
84357
84871
|
"request_annotation_snapshot",
|
|
84358
|
-
"list_threads"
|
|
84872
|
+
"list_threads",
|
|
84873
|
+
"presence_update"
|
|
84359
84874
|
]),
|
|
84360
84875
|
viewer: /* @__PURE__ */ new Set([
|
|
84361
84876
|
"subscribe",
|
|
@@ -84363,7 +84878,8 @@ var ROLE_PERMISSIONS = {
|
|
|
84363
84878
|
"request_capabilities",
|
|
84364
84879
|
"request_pr_data",
|
|
84365
84880
|
"join_collab_room",
|
|
84366
|
-
"leave_collab_room"
|
|
84881
|
+
"leave_collab_room",
|
|
84882
|
+
"presence_update"
|
|
84367
84883
|
])
|
|
84368
84884
|
};
|
|
84369
84885
|
function isCollabActionAllowed(role, action) {
|
|
@@ -84536,6 +85052,7 @@ function filterOutboundForCollab(msg, collabTaskId) {
|
|
|
84536
85052
|
case "worktree_error":
|
|
84537
85053
|
case "worktree_list":
|
|
84538
85054
|
case "worktree_removed":
|
|
85055
|
+
case "directory_list":
|
|
84539
85056
|
case "collab_room_joined":
|
|
84540
85057
|
case "collab_room_left":
|
|
84541
85058
|
case "schedule_fired":
|
|
@@ -84572,11 +85089,15 @@ function filterOutboundForCollab(msg, collabTaskId) {
|
|
|
84572
85089
|
/** Collab-scoped: always pass through (keyed by machineId/roomId, not taskId) */
|
|
84573
85090
|
case "collab_peer_role":
|
|
84574
85091
|
case "collab_participants_update":
|
|
85092
|
+
case "presence_state":
|
|
84575
85093
|
return msg;
|
|
84576
85094
|
/** Pass through: collab peers need error feedback */
|
|
84577
85095
|
case "promote_nudge":
|
|
84578
85096
|
case "error":
|
|
84579
85097
|
return msg;
|
|
85098
|
+
/** Log request is local-only, not relevant for collab peers */
|
|
85099
|
+
case "recent_logs":
|
|
85100
|
+
return null;
|
|
84580
85101
|
/** Exhaustiveness check: compile error if a new type is unhandled */
|
|
84581
85102
|
default: {
|
|
84582
85103
|
const _exhaustive = msg;
|
|
@@ -84851,6 +85372,9 @@ function routeMessage(msg, callbacks, log) {
|
|
|
84851
85372
|
case "request_user_settings":
|
|
84852
85373
|
callbacks.onRequestUserSettings();
|
|
84853
85374
|
break;
|
|
85375
|
+
case "request_preview_target":
|
|
85376
|
+
callbacks.onRequestPreviewTarget(msg.port, msg.url);
|
|
85377
|
+
break;
|
|
84854
85378
|
/** Unified annotation CRUD */
|
|
84855
85379
|
case "add_annotation":
|
|
84856
85380
|
callbacks.onAddAnnotation(msg.taskId, msg.annotation);
|
|
@@ -84931,6 +85455,9 @@ function routeMessage(msg, callbacks, log) {
|
|
|
84931
85455
|
case "stop_task":
|
|
84932
85456
|
callbacks.onStopTask(msg.taskId);
|
|
84933
85457
|
break;
|
|
85458
|
+
case "stop_background_agent":
|
|
85459
|
+
callbacks.onStopBackgroundAgent(msg.taskId, msg.backgroundTaskId);
|
|
85460
|
+
break;
|
|
84934
85461
|
case "todo_item_added":
|
|
84935
85462
|
callbacks.onTodoItemAdded(msg.taskId, msg.item);
|
|
84936
85463
|
break;
|
|
@@ -84950,6 +85477,15 @@ function routeMessage(msg, callbacks, log) {
|
|
|
84950
85477
|
msg.action
|
|
84951
85478
|
);
|
|
84952
85479
|
break;
|
|
85480
|
+
case "request_recent_logs":
|
|
85481
|
+
callbacks.onRequestRecentLogs(msg.windowMinutes);
|
|
85482
|
+
break;
|
|
85483
|
+
case "presence_update":
|
|
85484
|
+
callbacks.onPresenceUpdate(msg.state);
|
|
85485
|
+
break;
|
|
85486
|
+
case "list_directories":
|
|
85487
|
+
callbacks.onListDirectories(msg.basePath);
|
|
85488
|
+
break;
|
|
84953
85489
|
default: {
|
|
84954
85490
|
const _exhaustive = msg;
|
|
84955
85491
|
throw new Error(`Unhandled control message type: ${JSON.stringify(_exhaustive)}`);
|
|
@@ -84991,6 +85527,7 @@ function injectThreadResourceLink(store, mainChannelId, taskId, threadId, title,
|
|
|
84991
85527
|
|
|
84992
85528
|
// src/services/channels/control-channel-infra-handlers.ts
|
|
84993
85529
|
import { spawn as spawn6 } from "child_process";
|
|
85530
|
+
import { readdir as readdir7 } from "fs/promises";
|
|
84994
85531
|
|
|
84995
85532
|
// src/services/worktree-service.ts
|
|
84996
85533
|
import { execFile as execFile7, spawn as spawn5 } from "child_process";
|
|
@@ -85482,6 +86019,24 @@ function buildAgentInstallHandlers(deps) {
|
|
|
85482
86019
|
}
|
|
85483
86020
|
};
|
|
85484
86021
|
}
|
|
86022
|
+
var FILTERED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".turbo", "dist", ".next", "build"]);
|
|
86023
|
+
function buildDirectoryListHandler(deps) {
|
|
86024
|
+
return {
|
|
86025
|
+
onListDirectories: (basePath) => {
|
|
86026
|
+
readdir7(basePath, { withFileTypes: true }).then((entries) => {
|
|
86027
|
+
const directories = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !FILTERED_DIRS.has(e.name)).map((e) => e.name).sort();
|
|
86028
|
+
deps.sendControlMessage({ type: "directory_list", basePath, directories });
|
|
86029
|
+
}).catch((err) => {
|
|
86030
|
+
deps.logAdapter({
|
|
86031
|
+
event: "list_directories_failed",
|
|
86032
|
+
basePath,
|
|
86033
|
+
error: err instanceof Error ? err.message : String(err)
|
|
86034
|
+
});
|
|
86035
|
+
deps.sendControlMessage({ type: "directory_list", basePath, directories: [] });
|
|
86036
|
+
});
|
|
86037
|
+
}
|
|
86038
|
+
};
|
|
86039
|
+
}
|
|
85485
86040
|
function buildEnvironmentChangedHandler(deps) {
|
|
85486
86041
|
const { daemon } = deps;
|
|
85487
86042
|
let debounceTimer = null;
|
|
@@ -85715,6 +86270,99 @@ function runPluginOp(pluginName, marketplace, action, ctx) {
|
|
|
85715
86270
|
});
|
|
85716
86271
|
}
|
|
85717
86272
|
|
|
86273
|
+
// src/services/channels/read-recent-logs.ts
|
|
86274
|
+
import { createReadStream, readdirSync as readdirSync2 } from "fs";
|
|
86275
|
+
import { join as join38 } from "path";
|
|
86276
|
+
import { createInterface } from "readline";
|
|
86277
|
+
var MAX_BYTES = 5e4;
|
|
86278
|
+
var LOG_BASE_NAME = "daemon.log";
|
|
86279
|
+
var SENSITIVE_PATTERNS = [
|
|
86280
|
+
[/eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g, "[REDACTED_JWT]"],
|
|
86281
|
+
[/"[Aa]uthorization"\s*:\s*"[^"]+"/g, '"Authorization":"[REDACTED]"'],
|
|
86282
|
+
[/"token"\s*:\s*"[^"]+"/g, '"token":"[REDACTED]"'],
|
|
86283
|
+
[/"accessToken"\s*:\s*"[^"]+"/g, '"accessToken":"[REDACTED]"'],
|
|
86284
|
+
[/"refreshToken"\s*:\s*"[^"]+"/g, '"refreshToken":"[REDACTED]"'],
|
|
86285
|
+
[/sk-ant-[A-Za-z0-9_-]{20,}/g, "[REDACTED_API_KEY]"],
|
|
86286
|
+
[/sk-[a-zA-Z0-9]{20,}/g, "[REDACTED_SECRET_KEY]"],
|
|
86287
|
+
[/device_code=[A-Za-z0-9_-]+/g, "device_code=[REDACTED]"]
|
|
86288
|
+
];
|
|
86289
|
+
function scrubSensitiveData(line) {
|
|
86290
|
+
let result = line;
|
|
86291
|
+
for (const [pattern, replacement] of SENSITIVE_PATTERNS) {
|
|
86292
|
+
result = result.replace(pattern, replacement);
|
|
86293
|
+
}
|
|
86294
|
+
return result;
|
|
86295
|
+
}
|
|
86296
|
+
async function collectFromFile(filePath, cutoff, collector, maxBytes) {
|
|
86297
|
+
const rl = createInterface({
|
|
86298
|
+
input: createReadStream(filePath, { encoding: "utf-8" }),
|
|
86299
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
86300
|
+
});
|
|
86301
|
+
for await (const line of rl) {
|
|
86302
|
+
if (!line) continue;
|
|
86303
|
+
const timestamp = extractTimestamp(line);
|
|
86304
|
+
if (timestamp === null || timestamp < cutoff) continue;
|
|
86305
|
+
const scrubbed = scrubSensitiveData(line);
|
|
86306
|
+
const lineBytes = Buffer.byteLength(scrubbed, "utf-8");
|
|
86307
|
+
if (collector.totalBytes + lineBytes > maxBytes) {
|
|
86308
|
+
collector.truncated = true;
|
|
86309
|
+
rl.close();
|
|
86310
|
+
return true;
|
|
86311
|
+
}
|
|
86312
|
+
collector.lines.push(scrubbed);
|
|
86313
|
+
collector.totalBytes += lineBytes;
|
|
86314
|
+
}
|
|
86315
|
+
return false;
|
|
86316
|
+
}
|
|
86317
|
+
async function readRecentLogs(logDir, windowMinutes, maxBytes = MAX_BYTES) {
|
|
86318
|
+
const cutoff = Date.now() - windowMinutes * 6e4;
|
|
86319
|
+
const collector = { lines: [], totalBytes: 0, truncated: false };
|
|
86320
|
+
for (const filePath of discoverLogFiles(logDir)) {
|
|
86321
|
+
try {
|
|
86322
|
+
const exhausted = await collectFromFile(filePath, cutoff, collector, maxBytes);
|
|
86323
|
+
if (exhausted) break;
|
|
86324
|
+
} catch {
|
|
86325
|
+
}
|
|
86326
|
+
}
|
|
86327
|
+
return { lines: collector.lines, truncated: collector.truncated };
|
|
86328
|
+
}
|
|
86329
|
+
function discoverLogFiles(logDir) {
|
|
86330
|
+
let entries;
|
|
86331
|
+
try {
|
|
86332
|
+
entries = readdirSync2(logDir);
|
|
86333
|
+
} catch {
|
|
86334
|
+
return [];
|
|
86335
|
+
}
|
|
86336
|
+
const rotated = [];
|
|
86337
|
+
let currentFile = null;
|
|
86338
|
+
for (const entry of entries) {
|
|
86339
|
+
if (entry === LOG_BASE_NAME) {
|
|
86340
|
+
currentFile = join38(logDir, entry);
|
|
86341
|
+
} else if (entry.startsWith(`${LOG_BASE_NAME}.`)) {
|
|
86342
|
+
const suffix = entry.slice(LOG_BASE_NAME.length + 1);
|
|
86343
|
+
const index = Number.parseInt(suffix, 10);
|
|
86344
|
+
if (!Number.isNaN(index)) {
|
|
86345
|
+
rotated.push({ path: join38(logDir, entry), index });
|
|
86346
|
+
}
|
|
86347
|
+
}
|
|
86348
|
+
}
|
|
86349
|
+
rotated.sort((a, b2) => b2.index - a.index);
|
|
86350
|
+
const result = rotated.map((r) => r.path);
|
|
86351
|
+
if (currentFile) result.push(currentFile);
|
|
86352
|
+
return result;
|
|
86353
|
+
}
|
|
86354
|
+
function extractTimestamp(line) {
|
|
86355
|
+
const idx = line.indexOf('"time":');
|
|
86356
|
+
if (idx === -1) return null;
|
|
86357
|
+
const start = idx + 7;
|
|
86358
|
+
let end = start;
|
|
86359
|
+
while (end < line.length && line.charCodeAt(end) >= 48 && line.charCodeAt(end) <= 57) {
|
|
86360
|
+
end++;
|
|
86361
|
+
}
|
|
86362
|
+
if (end === start) return null;
|
|
86363
|
+
return Number(line.slice(start, end));
|
|
86364
|
+
}
|
|
86365
|
+
|
|
85718
86366
|
// src/services/channels/schedule-channel-callbacks.ts
|
|
85719
86367
|
function buildScheduleCallbacks(daemon, getControlHandler, log) {
|
|
85720
86368
|
return {
|
|
@@ -85883,6 +86531,33 @@ function buildTemplateCallbacks(daemon, getControlHandler, log) {
|
|
|
85883
86531
|
}
|
|
85884
86532
|
|
|
85885
86533
|
// src/services/channels/control-channel-wiring.ts
|
|
86534
|
+
function handlePreviewTargetRequest(proxy, port, url, daemon, log) {
|
|
86535
|
+
if (!proxy) return;
|
|
86536
|
+
(async () => {
|
|
86537
|
+
try {
|
|
86538
|
+
if (url) await proxy.stop();
|
|
86539
|
+
else if (port) proxy.port ? proxy.retarget(port) : await proxy.start({ port });
|
|
86540
|
+
daemon.taskManager.broadcastControl({
|
|
86541
|
+
type: "set_preview_target",
|
|
86542
|
+
port,
|
|
86543
|
+
url,
|
|
86544
|
+
proxyPort: proxy.port ?? void 0
|
|
86545
|
+
});
|
|
86546
|
+
} catch (err) {
|
|
86547
|
+
log({
|
|
86548
|
+
event: "preview_target_request_failed",
|
|
86549
|
+
error: err instanceof Error ? err.message : String(err)
|
|
86550
|
+
});
|
|
86551
|
+
}
|
|
86552
|
+
})();
|
|
86553
|
+
}
|
|
86554
|
+
function resolveIdentityFromRegistry(deps) {
|
|
86555
|
+
if (!deps.peerRoleRegistry || !deps.peerMachineId) return void 0;
|
|
86556
|
+
const entry = deps.peerRoleRegistry.getEntry(deps.peerMachineId);
|
|
86557
|
+
if (!entry) return void 0;
|
|
86558
|
+
const userId = entry.participantId.replace(/^human:/, "");
|
|
86559
|
+
return { userId, username: entry.displayName, avatarUrl: null };
|
|
86560
|
+
}
|
|
85886
86561
|
function applyMcpServerToggles(daemon, prevDisabled, nextDisabled, log) {
|
|
85887
86562
|
for (const name of nextDisabled) {
|
|
85888
86563
|
if (!prevDisabled.has(name)) {
|
|
@@ -85978,6 +86653,10 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
85978
86653
|
const worktreeHandlers = buildWorktreeHandlers(lazyInfraDeps);
|
|
85979
86654
|
const agentInstallHandlers = buildAgentInstallHandlers(lazyInfraDeps);
|
|
85980
86655
|
const envChangedHandler = buildEnvironmentChangedHandler(lazyInfraDeps);
|
|
86656
|
+
const directoryListHandler = buildDirectoryListHandler({
|
|
86657
|
+
sendControlMessage: (msg) => controlHandler.sendControl(msg),
|
|
86658
|
+
logAdapter
|
|
86659
|
+
});
|
|
85981
86660
|
const prPoller = createPRPoller(
|
|
85982
86661
|
{
|
|
85983
86662
|
onPRState: (payload) => {
|
|
@@ -86241,6 +86920,7 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86241
86920
|
},
|
|
86242
86921
|
...agentInstallHandlers,
|
|
86243
86922
|
...worktreeHandlers,
|
|
86923
|
+
...directoryListHandler,
|
|
86244
86924
|
onJoinCollabRoom: (() => {
|
|
86245
86925
|
const mgr = deps?.collabRoomManager;
|
|
86246
86926
|
if (!mgr) return void 0;
|
|
@@ -86545,6 +87225,9 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86545
87225
|
});
|
|
86546
87226
|
});
|
|
86547
87227
|
},
|
|
87228
|
+
onRequestPreviewTarget: (port, url) => {
|
|
87229
|
+
handlePreviewTargetRequest(deps?.previewProxy, port, url, daemon, logAdapter);
|
|
87230
|
+
},
|
|
86548
87231
|
onAcknowledgeTask: (taskId) => {
|
|
86549
87232
|
daemon.taskStateStore.acknowledge(taskId).catch((err) => {
|
|
86550
87233
|
logAdapter({
|
|
@@ -86574,9 +87257,44 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86574
87257
|
});
|
|
86575
87258
|
}
|
|
86576
87259
|
},
|
|
87260
|
+
onStopBackgroundAgent: (taskId, backgroundTaskId) => {
|
|
87261
|
+
daemon.taskManager.stopBackgroundTask(taskId, backgroundTaskId).catch((err) => {
|
|
87262
|
+
logAdapter({
|
|
87263
|
+
event: "stop_background_agent_failed",
|
|
87264
|
+
taskId,
|
|
87265
|
+
backgroundTaskId,
|
|
87266
|
+
error: err instanceof Error ? err.message : String(err)
|
|
87267
|
+
});
|
|
87268
|
+
});
|
|
87269
|
+
},
|
|
86577
87270
|
...buildScheduleCallbacks(daemon, () => controlHandler, logAdapter),
|
|
86578
87271
|
...buildTemplateCallbacks(daemon, () => controlHandler, logAdapter),
|
|
86579
|
-
...todoHandlers
|
|
87272
|
+
...todoHandlers,
|
|
87273
|
+
onRequestRecentLogs: (windowMinutes) => {
|
|
87274
|
+
const logDir = `${deps?.shipyardHome ?? getShipyardHome()}/logs`;
|
|
87275
|
+
readRecentLogs(logDir, windowMinutes).then((result) => {
|
|
87276
|
+
handler.sendControl({ type: "recent_logs", ...result });
|
|
87277
|
+
logAdapter({
|
|
87278
|
+
event: "recent_logs_sent",
|
|
87279
|
+
lineCount: result.lines.length,
|
|
87280
|
+
truncated: result.truncated
|
|
87281
|
+
});
|
|
87282
|
+
}).catch((err) => {
|
|
87283
|
+
handler.sendControl({ type: "recent_logs", lines: [], truncated: false });
|
|
87284
|
+
logAdapter({
|
|
87285
|
+
event: "recent_logs_failed",
|
|
87286
|
+
error: err instanceof Error ? err.message : String(err)
|
|
87287
|
+
});
|
|
87288
|
+
});
|
|
87289
|
+
},
|
|
87290
|
+
onPresenceUpdate: (state) => {
|
|
87291
|
+
if (!deps?.presencePool) return;
|
|
87292
|
+
const identity = deps.peerIdentity ?? resolveIdentityFromRegistry(deps);
|
|
87293
|
+
if (!identity) return;
|
|
87294
|
+
const ref = deps.presencePool;
|
|
87295
|
+
ref.pool = applyPresenceUpdate(ref.pool, controlPeerId, identity, state);
|
|
87296
|
+
daemon.taskManager.broadcastControl({ type: "presence_state", peers: ref.pool });
|
|
87297
|
+
}
|
|
86580
87298
|
},
|
|
86581
87299
|
logAdapter
|
|
86582
87300
|
);
|
|
@@ -86699,6 +87417,9 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86699
87417
|
const userSettingsUnsub = daemon.userSettingsStore.subscribe((settings) => {
|
|
86700
87418
|
controlHandler.sendControl({ type: "user_settings_updated", settings });
|
|
86701
87419
|
});
|
|
87420
|
+
if (deps?.presencePool) {
|
|
87421
|
+
controlHandler.sendControl({ type: "presence_state", peers: deps.presencePool.pool });
|
|
87422
|
+
}
|
|
86702
87423
|
const enforcedTaskId = deps?.collabTaskId;
|
|
86703
87424
|
dc.onmessage = (ev) => {
|
|
86704
87425
|
const raw = typeof ev.data === "string" ? ev.data : String(ev.data);
|
|
@@ -86725,6 +87446,11 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86725
87446
|
handler.dispose();
|
|
86726
87447
|
daemon.taskManager.unregisterControlChannel(controlPeerId);
|
|
86727
87448
|
daemon.taskManager.setOnAuthNotLoggedIn(null);
|
|
87449
|
+
if (deps?.presencePool) {
|
|
87450
|
+
const ref = deps.presencePool;
|
|
87451
|
+
ref.pool = removePresence(ref.pool, controlPeerId);
|
|
87452
|
+
daemon.taskManager.broadcastControl({ type: "presence_state", peers: ref.pool });
|
|
87453
|
+
}
|
|
86728
87454
|
};
|
|
86729
87455
|
logAdapter({
|
|
86730
87456
|
event: "control_channel_wired",
|
|
@@ -86952,6 +87678,77 @@ function handleMessageChannel(opts) {
|
|
|
86952
87678
|
};
|
|
86953
87679
|
}
|
|
86954
87680
|
|
|
87681
|
+
// src/services/collab/collab-repo-manager.ts
|
|
87682
|
+
var DAEMON_IDENTITY2 = { name: "shipyard-daemon", type: "service" };
|
|
87683
|
+
var COLLAB_VISIBLE_PREFIXES = /* @__PURE__ */ new Set(["plan", "canvas"]);
|
|
87684
|
+
var COLLAB_MUTABLE_PREFIXES = {
|
|
87685
|
+
"collaborator-full": /* @__PURE__ */ new Set(["plan", "canvas"]),
|
|
87686
|
+
"collaborator-review": /* @__PURE__ */ new Set(["plan"])
|
|
87687
|
+
};
|
|
87688
|
+
function planCollabRoomResources(taskId, roomId, epochs) {
|
|
87689
|
+
return {
|
|
87690
|
+
docIds: [buildPlanDocId(taskId, epochs.plan), buildCanvasDocId(taskId, epochs.canvas)],
|
|
87691
|
+
personalBridgeType: `bridge-personal-${roomId}`,
|
|
87692
|
+
collabBridgeType: `bridge-collab-${roomId}`
|
|
87693
|
+
};
|
|
87694
|
+
}
|
|
87695
|
+
function buildCollabRepoPermissions(taskId, peerRoleRegistry) {
|
|
87696
|
+
return {
|
|
87697
|
+
visibility(doc3, peer) {
|
|
87698
|
+
if (peer.channelKind === "storage") return true;
|
|
87699
|
+
if (peer.peerType === "service") return true;
|
|
87700
|
+
const parsed = parseDocumentId(doc3.id);
|
|
87701
|
+
if (!parsed) return false;
|
|
87702
|
+
if (parsed.key !== taskId) return false;
|
|
87703
|
+
return COLLAB_VISIBLE_PREFIXES.has(parsed.prefix);
|
|
87704
|
+
},
|
|
87705
|
+
mutability(doc3, peer) {
|
|
87706
|
+
if (peer.channelKind === "storage") return true;
|
|
87707
|
+
if (peer.peerType === "service") return true;
|
|
87708
|
+
const parsed = parseDocumentId(doc3.id);
|
|
87709
|
+
if (!parsed) return false;
|
|
87710
|
+
if (parsed.key !== taskId) return false;
|
|
87711
|
+
const entry = peerRoleRegistry.getEntry(peer.peerId);
|
|
87712
|
+
if (!entry) return false;
|
|
87713
|
+
const allowed = COLLAB_MUTABLE_PREFIXES[entry.role];
|
|
87714
|
+
return allowed ? allowed.has(parsed.prefix) : false;
|
|
87715
|
+
},
|
|
87716
|
+
creation(_docId, _peer) {
|
|
87717
|
+
return false;
|
|
87718
|
+
},
|
|
87719
|
+
deletion(_doc, _peer) {
|
|
87720
|
+
return false;
|
|
87721
|
+
}
|
|
87722
|
+
};
|
|
87723
|
+
}
|
|
87724
|
+
function createCollabRepo(personalRepo, taskId, roomId, epochs, peerRoleRegistry) {
|
|
87725
|
+
const resources = planCollabRoomResources(taskId, roomId, epochs);
|
|
87726
|
+
const bridge = new Bridge();
|
|
87727
|
+
const personalBridgeAdapter = new BridgeAdapter({
|
|
87728
|
+
adapterType: resources.personalBridgeType,
|
|
87729
|
+
bridge
|
|
87730
|
+
});
|
|
87731
|
+
const collabBridgeAdapter = new BridgeAdapter({
|
|
87732
|
+
adapterType: resources.collabBridgeType,
|
|
87733
|
+
bridge
|
|
87734
|
+
});
|
|
87735
|
+
const collabWebrtcAdapter = new WebRtcDataChannelAdapter();
|
|
87736
|
+
const collabRepo = new Repo({
|
|
87737
|
+
identity: { ...DAEMON_IDENTITY2, name: `collab-room-${roomId}` },
|
|
87738
|
+
adapters: [collabBridgeAdapter, collabWebrtcAdapter],
|
|
87739
|
+
permissions: buildCollabRepoPermissions(taskId, peerRoleRegistry)
|
|
87740
|
+
});
|
|
87741
|
+
personalRepo.addAdapter(personalBridgeAdapter);
|
|
87742
|
+
return {
|
|
87743
|
+
repo: collabRepo,
|
|
87744
|
+
webrtcAdapter: collabWebrtcAdapter,
|
|
87745
|
+
async destroy() {
|
|
87746
|
+
personalRepo.removeAdapter(personalBridgeAdapter.adapterId);
|
|
87747
|
+
await collabRepo.shutdown();
|
|
87748
|
+
}
|
|
87749
|
+
};
|
|
87750
|
+
}
|
|
87751
|
+
|
|
86955
87752
|
// src/services/collab/collab-room-manager.ts
|
|
86956
87753
|
function assertNever2(x2) {
|
|
86957
87754
|
throw new Error(`Unhandled message type: ${JSON.stringify(x2)}`);
|
|
@@ -87084,6 +87881,7 @@ function createCollabRoomManager(deps) {
|
|
|
87084
87881
|
for (const userId of room.knownPeers) {
|
|
87085
87882
|
deps.peerRoleRegistry.unregisterPeer(namespacePeerId(roomId, userId));
|
|
87086
87883
|
}
|
|
87884
|
+
room.collabRepoHandle?.destroy();
|
|
87087
87885
|
room.peerManager.destroy();
|
|
87088
87886
|
room.connection.disconnect();
|
|
87089
87887
|
rooms.delete(roomId);
|
|
@@ -87121,7 +87919,19 @@ function createCollabRoomManager(deps) {
|
|
|
87121
87919
|
maxDelayMs: 3e4,
|
|
87122
87920
|
backoffMultiplier: 2
|
|
87123
87921
|
});
|
|
87124
|
-
const
|
|
87922
|
+
const collabRepoHandle = createCollabRepo(
|
|
87923
|
+
deps.personalRepo,
|
|
87924
|
+
taskId,
|
|
87925
|
+
roomId,
|
|
87926
|
+
{ plan: deps.planEpoch, canvas: deps.canvasEpoch },
|
|
87927
|
+
deps.peerRoleRegistry
|
|
87928
|
+
);
|
|
87929
|
+
const peerManager = deps.createPeerManagerForRoom(
|
|
87930
|
+
roomId,
|
|
87931
|
+
connection,
|
|
87932
|
+
taskId,
|
|
87933
|
+
collabRepoHandle.webrtcAdapter
|
|
87934
|
+
);
|
|
87125
87935
|
const handle = {
|
|
87126
87936
|
roomId,
|
|
87127
87937
|
taskId,
|
|
@@ -87132,6 +87942,7 @@ function createCollabRoomManager(deps) {
|
|
|
87132
87942
|
const delayMs = expiresAt - Date.now();
|
|
87133
87943
|
if (delayMs <= 0 || !Number.isFinite(delayMs)) {
|
|
87134
87944
|
deps.log({ event: "collab_expired", roomId, expiresAt });
|
|
87945
|
+
collabRepoHandle.destroy();
|
|
87135
87946
|
return handle;
|
|
87136
87947
|
}
|
|
87137
87948
|
const safeDelayMs = Math.min(delayMs, 2147483647);
|
|
@@ -87140,6 +87951,7 @@ function createCollabRoomManager(deps) {
|
|
|
87140
87951
|
taskId,
|
|
87141
87952
|
connection,
|
|
87142
87953
|
peerManager,
|
|
87954
|
+
collabRepoHandle,
|
|
87143
87955
|
myUserId: null,
|
|
87144
87956
|
knownPeers: /* @__PURE__ */ new Set(),
|
|
87145
87957
|
participants: [],
|
|
@@ -87163,7 +87975,12 @@ function createCollabRoomManager(deps) {
|
|
|
87163
87975
|
deps.peerRoleRegistry.unregisterPeer(namespacePeerId(roomId, userId));
|
|
87164
87976
|
}
|
|
87165
87977
|
room.peerManager.destroy();
|
|
87166
|
-
room.peerManager = deps.createPeerManagerForRoom(
|
|
87978
|
+
room.peerManager = deps.createPeerManagerForRoom(
|
|
87979
|
+
roomId,
|
|
87980
|
+
connection,
|
|
87981
|
+
taskId,
|
|
87982
|
+
room.collabRepoHandle?.webrtcAdapter ?? collabRepoHandle.webrtcAdapter
|
|
87983
|
+
);
|
|
87167
87984
|
room.myUserId = null;
|
|
87168
87985
|
room.knownPeers.clear();
|
|
87169
87986
|
}
|
|
@@ -87204,41 +88021,20 @@ function findEntry(registry, peer) {
|
|
|
87204
88021
|
}
|
|
87205
88022
|
return void 0;
|
|
87206
88023
|
}
|
|
87207
|
-
var COLLAB_VISIBLE_PREFIXES = {
|
|
87208
|
-
"collaborator-full": /* @__PURE__ */ new Set(["plan", "canvas"]),
|
|
87209
|
-
"collaborator-review": /* @__PURE__ */ new Set(["plan", "canvas"]),
|
|
87210
|
-
viewer: /* @__PURE__ */ new Set([])
|
|
87211
|
-
};
|
|
87212
|
-
var COLLAB_MUTABLE_PREFIXES = {
|
|
87213
|
-
"collaborator-full": /* @__PURE__ */ new Set(["plan", "canvas"]),
|
|
87214
|
-
"collaborator-review": /* @__PURE__ */ new Set(["plan"])
|
|
87215
|
-
};
|
|
87216
88024
|
function buildCollabPermissions(registry) {
|
|
88025
|
+
function isAllowed(_doc, peer) {
|
|
88026
|
+
if (peer.channelKind === "storage") return true;
|
|
88027
|
+
if (peer.peerType === "service") return true;
|
|
88028
|
+
const entry = findEntry(registry, peer);
|
|
88029
|
+
if (!entry) return false;
|
|
88030
|
+
return isPersonalPeer(entry);
|
|
88031
|
+
}
|
|
87217
88032
|
return {
|
|
87218
|
-
visibility
|
|
87219
|
-
|
|
87220
|
-
const entry = findEntry(registry, peer);
|
|
87221
|
-
if (!entry) return false;
|
|
87222
|
-
if (isPersonalPeer(entry)) return true;
|
|
87223
|
-
const parsed = parseDocumentId(doc3.id);
|
|
87224
|
-
if (!parsed) return false;
|
|
87225
|
-
if (parsed.key !== entry.taskId) return false;
|
|
87226
|
-
const allowed = COLLAB_VISIBLE_PREFIXES[entry.role];
|
|
87227
|
-
return allowed ? allowed.has(parsed.prefix) : false;
|
|
87228
|
-
},
|
|
87229
|
-
mutability(doc3, peer) {
|
|
87230
|
-
if (peer.channelKind === "storage") return true;
|
|
87231
|
-
const entry = findEntry(registry, peer);
|
|
87232
|
-
if (!entry) return false;
|
|
87233
|
-
if (isPersonalPeer(entry)) return true;
|
|
87234
|
-
const parsed = parseDocumentId(doc3.id);
|
|
87235
|
-
if (!parsed) return false;
|
|
87236
|
-
if (parsed.key !== entry.taskId) return false;
|
|
87237
|
-
const allowed = COLLAB_MUTABLE_PREFIXES[entry.role];
|
|
87238
|
-
return allowed ? allowed.has(parsed.prefix) : false;
|
|
87239
|
-
},
|
|
88033
|
+
visibility: isAllowed,
|
|
88034
|
+
mutability: isAllowed,
|
|
87240
88035
|
creation(_docId, peer) {
|
|
87241
88036
|
if (peer.channelKind === "storage") return true;
|
|
88037
|
+
if (peer.peerType === "service") return true;
|
|
87242
88038
|
const entry = findEntry(registry, peer);
|
|
87243
88039
|
if (!entry) return false;
|
|
87244
88040
|
return isPersonalPeer(entry);
|
|
@@ -87298,8 +88094,8 @@ function createPeerRoleRegistry() {
|
|
|
87298
88094
|
}
|
|
87299
88095
|
|
|
87300
88096
|
// src/services/epoch-pruning.ts
|
|
87301
|
-
import { readdir as
|
|
87302
|
-
import { join as
|
|
88097
|
+
import { readdir as readdir8, rm as rm3 } from "fs/promises";
|
|
88098
|
+
import { join as join39 } from "path";
|
|
87303
88099
|
var LEGACY_PREFIXES = [
|
|
87304
88100
|
"task-meta",
|
|
87305
88101
|
"task-conv",
|
|
@@ -87316,7 +88112,7 @@ function isLegacyDocId(decoded) {
|
|
|
87316
88112
|
async function pruneOldEpochData(dataDir, currentEpoch, log) {
|
|
87317
88113
|
let entries;
|
|
87318
88114
|
try {
|
|
87319
|
-
entries = await
|
|
88115
|
+
entries = await readdir8(dataDir, { withFileTypes: true });
|
|
87320
88116
|
} catch {
|
|
87321
88117
|
return;
|
|
87322
88118
|
}
|
|
@@ -87334,7 +88130,7 @@ async function pruneOldEpochData(dataDir, currentEpoch, log) {
|
|
|
87334
88130
|
if (!parsed) {
|
|
87335
88131
|
if (isLegacyDocId(decoded)) {
|
|
87336
88132
|
removals.push(
|
|
87337
|
-
rm3(
|
|
88133
|
+
rm3(join39(dataDir, entry.name), { recursive: true }).then(() => {
|
|
87338
88134
|
pruned++;
|
|
87339
88135
|
}).catch((err) => {
|
|
87340
88136
|
log({
|
|
@@ -87349,7 +88145,7 @@ async function pruneOldEpochData(dataDir, currentEpoch, log) {
|
|
|
87349
88145
|
}
|
|
87350
88146
|
if (parsed.epoch >= currentEpoch) continue;
|
|
87351
88147
|
removals.push(
|
|
87352
|
-
rm3(
|
|
88148
|
+
rm3(join39(dataDir, entry.name), { recursive: true }).then(() => {
|
|
87353
88149
|
pruned++;
|
|
87354
88150
|
}).catch((err) => {
|
|
87355
88151
|
log({
|
|
@@ -87368,7 +88164,7 @@ async function pruneOldEpochData(dataDir, currentEpoch, log) {
|
|
|
87368
88164
|
|
|
87369
88165
|
// src/services/file-io-handler.ts
|
|
87370
88166
|
import { execFile as execFile9, spawn as spawn7 } from "child_process";
|
|
87371
|
-
import { readdir as
|
|
88167
|
+
import { readdir as readdir9, readFile as readFile25, stat as stat5, unlink as unlink6, writeFile as writeFile22 } from "fs/promises";
|
|
87372
88168
|
import { normalize as normalize5, relative as relative2, resolve } from "path";
|
|
87373
88169
|
import { promisify as promisify6 } from "util";
|
|
87374
88170
|
function handleFileIOChannel(initialCwd, send, log, deps) {
|
|
@@ -87532,7 +88328,7 @@ function handleFileIOChannel(initialCwd, send, log, deps) {
|
|
|
87532
88328
|
}
|
|
87533
88329
|
async function handleReaddir(requestId, absPath) {
|
|
87534
88330
|
try {
|
|
87535
|
-
const entries = await
|
|
88331
|
+
const entries = await readdir9(absPath, { withFileTypes: true });
|
|
87536
88332
|
const result = entries.filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => ({
|
|
87537
88333
|
name: e.name,
|
|
87538
88334
|
type: e.isDirectory() ? "directory" : "file"
|
|
@@ -88271,17 +89067,25 @@ function guardLoroChannelSend(dc, log) {
|
|
|
88271
89067
|
const originalSend = dc.send.bind(dc);
|
|
88272
89068
|
let dropCount = 0;
|
|
88273
89069
|
let lastLogAt = 0;
|
|
89070
|
+
const logDrop = (reason) => {
|
|
89071
|
+
dropCount++;
|
|
89072
|
+
const now = Date.now();
|
|
89073
|
+
if (now - lastLogAt >= 1e3) {
|
|
89074
|
+
log.warn({ droppedCount: dropCount, reason }, "Loro sync send dropped");
|
|
89075
|
+
dropCount = 0;
|
|
89076
|
+
lastLogAt = now;
|
|
89077
|
+
}
|
|
89078
|
+
};
|
|
88274
89079
|
dc.send = (data) => {
|
|
89080
|
+
if (dc.readyState !== void 0 && dc.readyState !== "open") return;
|
|
89081
|
+
if (typeof dc.bufferedAmount === "number" && dc.bufferedAmount > LORO_BACKPRESSURE_HIGH_WATER) {
|
|
89082
|
+
logDrop("backpressure");
|
|
89083
|
+
return;
|
|
89084
|
+
}
|
|
88275
89085
|
try {
|
|
88276
89086
|
originalSend(data);
|
|
88277
89087
|
} catch {
|
|
88278
|
-
|
|
88279
|
-
const now = Date.now();
|
|
88280
|
-
if (now - lastLogAt >= 1e3) {
|
|
88281
|
-
log.warn({ droppedCount: dropCount }, "Loro sync send failed (SCTP overflow)");
|
|
88282
|
-
dropCount = 0;
|
|
88283
|
-
lastLogAt = now;
|
|
88284
|
-
}
|
|
89088
|
+
logDrop("send_error");
|
|
88285
89089
|
}
|
|
88286
89090
|
};
|
|
88287
89091
|
}
|
|
@@ -88526,8 +89330,8 @@ function createPeerManager(config2) {
|
|
|
88526
89330
|
|
|
88527
89331
|
// src/services/plugins/plugin-file-watcher.ts
|
|
88528
89332
|
import { existsSync as existsSync6, watch as watch4 } from "fs";
|
|
88529
|
-
import { readdir as
|
|
88530
|
-
import { join as
|
|
89333
|
+
import { readdir as readdir10, readFile as readFile26, stat as stat6 } from "fs/promises";
|
|
89334
|
+
import { join as join40 } from "path";
|
|
88531
89335
|
import { pathToFileURL } from "url";
|
|
88532
89336
|
var DEBOUNCE_MS2 = 500;
|
|
88533
89337
|
var PLUGIN_ID_PATTERN2 = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -88578,14 +89382,14 @@ var PluginFileWatcher = class {
|
|
|
88578
89382
|
}
|
|
88579
89383
|
let entries;
|
|
88580
89384
|
try {
|
|
88581
|
-
entries = await
|
|
89385
|
+
entries = await readdir10(dir);
|
|
88582
89386
|
} catch {
|
|
88583
89387
|
this.#reconcile([]);
|
|
88584
89388
|
return;
|
|
88585
89389
|
}
|
|
88586
89390
|
const loaded = [];
|
|
88587
89391
|
for (const entry of entries) {
|
|
88588
|
-
const pluginDir =
|
|
89392
|
+
const pluginDir = join40(dir, entry);
|
|
88589
89393
|
let stats;
|
|
88590
89394
|
try {
|
|
88591
89395
|
stats = await stat6(pluginDir);
|
|
@@ -88600,7 +89404,7 @@ var PluginFileWatcher = class {
|
|
|
88600
89404
|
this.#reconcile(loaded);
|
|
88601
89405
|
}
|
|
88602
89406
|
async #loadPlugin(id, pluginDir) {
|
|
88603
|
-
const manifestPath =
|
|
89407
|
+
const manifestPath = join40(pluginDir, "plugin.json");
|
|
88604
89408
|
let manifestRaw;
|
|
88605
89409
|
try {
|
|
88606
89410
|
manifestRaw = await readFile26(manifestPath, "utf-8");
|
|
@@ -88627,7 +89431,7 @@ var PluginFileWatcher = class {
|
|
|
88627
89431
|
}
|
|
88628
89432
|
const manifest = parsed.data;
|
|
88629
89433
|
let template = "";
|
|
88630
|
-
const templatePath =
|
|
89434
|
+
const templatePath = join40(pluginDir, "template.html");
|
|
88631
89435
|
try {
|
|
88632
89436
|
template = await readFile26(templatePath, "utf-8");
|
|
88633
89437
|
} catch {
|
|
@@ -88642,7 +89446,7 @@ var PluginFileWatcher = class {
|
|
|
88642
89446
|
};
|
|
88643
89447
|
}
|
|
88644
89448
|
async #loadAndRegisterHandler(id, pluginDir, title) {
|
|
88645
|
-
const handlerPath =
|
|
89449
|
+
const handlerPath = join40(pluginDir, "handler.mjs");
|
|
88646
89450
|
let handlerFn;
|
|
88647
89451
|
try {
|
|
88648
89452
|
const handlerStat = await stat6(handlerPath);
|
|
@@ -88903,28 +89707,14 @@ var HOP_BY_HOP_HEADERS2 = /* @__PURE__ */ new Set([
|
|
|
88903
89707
|
"proxy-authorization",
|
|
88904
89708
|
"proxy-connection",
|
|
88905
89709
|
"te",
|
|
88906
|
-
"host"
|
|
89710
|
+
"host",
|
|
89711
|
+
"accept-encoding"
|
|
88907
89712
|
]);
|
|
88908
89713
|
var IFRAME_BLOCKING_HEADERS2 = /* @__PURE__ */ new Set([
|
|
88909
89714
|
"x-frame-options",
|
|
88910
89715
|
"content-security-policy",
|
|
88911
89716
|
"content-security-policy-report-only"
|
|
88912
89717
|
]);
|
|
88913
|
-
function findAvailablePort2() {
|
|
88914
|
-
return new Promise((resolve3, reject) => {
|
|
88915
|
-
const srv = http2.createServer();
|
|
88916
|
-
srv.listen(0, "127.0.0.1", () => {
|
|
88917
|
-
const addr = srv.address();
|
|
88918
|
-
if (typeof addr === "object" && addr) {
|
|
88919
|
-
const port = addr.port;
|
|
88920
|
-
srv.close(() => resolve3(port));
|
|
88921
|
-
} else {
|
|
88922
|
-
reject(new Error("Failed to allocate port"));
|
|
88923
|
-
}
|
|
88924
|
-
});
|
|
88925
|
-
srv.on("error", reject);
|
|
88926
|
-
});
|
|
88927
|
-
}
|
|
88928
89718
|
function injectAnnotationBridge(html) {
|
|
88929
89719
|
const headIdx = html.indexOf("<head");
|
|
88930
89720
|
if (headIdx === -1) return ANNOTATION_BRIDGE_TAG + html;
|
|
@@ -88952,10 +89742,13 @@ function collectResponseHeaders(rawHeaders) {
|
|
|
88952
89742
|
}
|
|
88953
89743
|
return result;
|
|
88954
89744
|
}
|
|
89745
|
+
var BLOCKED_PORTS = /* @__PURE__ */ new Set([22, 25, 3306, 5432, 6379, 27017]);
|
|
88955
89746
|
function createPreviewProxy(config2) {
|
|
88956
89747
|
const { log } = config2;
|
|
88957
89748
|
let server = null;
|
|
88958
89749
|
let allocatedPort = null;
|
|
89750
|
+
let currentTargetPort = null;
|
|
89751
|
+
let startPromise = null;
|
|
88959
89752
|
const activeRequests = /* @__PURE__ */ new Set();
|
|
88960
89753
|
const activeSockets = /* @__PURE__ */ new Set();
|
|
88961
89754
|
function proxyRequest(clientReq, clientRes, targetPort) {
|
|
@@ -88963,7 +89756,7 @@ function createPreviewProxy(config2) {
|
|
|
88963
89756
|
headers.host = `localhost:${targetPort}`;
|
|
88964
89757
|
const proxyReq = http2.request(
|
|
88965
89758
|
{
|
|
88966
|
-
hostname: "
|
|
89759
|
+
hostname: "localhost",
|
|
88967
89760
|
port: targetPort,
|
|
88968
89761
|
path: clientReq.url ?? "/",
|
|
88969
89762
|
method: clientReq.method ?? "GET",
|
|
@@ -89041,7 +89834,7 @@ function createPreviewProxy(config2) {
|
|
|
89041
89834
|
headers.connection = "Upgrade";
|
|
89042
89835
|
headers.upgrade = clientReq.headers.upgrade ?? "websocket";
|
|
89043
89836
|
const proxyReq = http2.request({
|
|
89044
|
-
hostname: "
|
|
89837
|
+
hostname: "localhost",
|
|
89045
89838
|
port: targetPort,
|
|
89046
89839
|
path: clientReq.url ?? "/",
|
|
89047
89840
|
method: "GET",
|
|
@@ -89100,21 +89893,45 @@ function createPreviewProxy(config2) {
|
|
|
89100
89893
|
get port() {
|
|
89101
89894
|
return allocatedPort;
|
|
89102
89895
|
},
|
|
89896
|
+
get targetPort() {
|
|
89897
|
+
return currentTargetPort;
|
|
89898
|
+
},
|
|
89899
|
+
retarget(port) {
|
|
89900
|
+
if (port < 1 || port > 65535 || BLOCKED_PORTS.has(port)) {
|
|
89901
|
+
throw new Error(`Port ${port} is not allowed for preview proxy`);
|
|
89902
|
+
}
|
|
89903
|
+
currentTargetPort = port;
|
|
89904
|
+
log({ event: "preview_proxy_retargeted", proxyPort: allocatedPort, targetPort: port });
|
|
89905
|
+
},
|
|
89103
89906
|
async start(target) {
|
|
89907
|
+
if (startPromise) await startPromise;
|
|
89104
89908
|
if (server) {
|
|
89105
89909
|
throw new Error("Preview proxy already started");
|
|
89106
89910
|
}
|
|
89107
|
-
|
|
89911
|
+
if (target.port < 1 || target.port > 65535 || BLOCKED_PORTS.has(target.port)) {
|
|
89912
|
+
throw new Error(`Port ${target.port} is not allowed for preview proxy`);
|
|
89913
|
+
}
|
|
89914
|
+
currentTargetPort = target.port;
|
|
89108
89915
|
const srv = http2.createServer((req, res) => {
|
|
89109
|
-
|
|
89916
|
+
if (!currentTargetPort) {
|
|
89917
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
89918
|
+
res.end("No preview target configured");
|
|
89919
|
+
return;
|
|
89920
|
+
}
|
|
89921
|
+
proxyRequest(req, res, currentTargetPort);
|
|
89110
89922
|
});
|
|
89111
89923
|
server = srv;
|
|
89112
89924
|
srv.on("upgrade", (req, socket, head) => {
|
|
89113
|
-
|
|
89925
|
+
if (!currentTargetPort) {
|
|
89926
|
+
socket.destroy();
|
|
89927
|
+
return;
|
|
89928
|
+
}
|
|
89929
|
+
proxyWebSocketUpgrade(req, socket, head, currentTargetPort);
|
|
89114
89930
|
});
|
|
89115
|
-
|
|
89116
|
-
|
|
89117
|
-
|
|
89931
|
+
startPromise = new Promise((resolve3, reject) => {
|
|
89932
|
+
srv.listen(0, "localhost", () => {
|
|
89933
|
+
const addr = srv.address();
|
|
89934
|
+
allocatedPort = typeof addr === "object" && addr ? addr.port : null;
|
|
89118
89935
|
log({
|
|
89119
89936
|
event: "preview_proxy_started",
|
|
89120
89937
|
proxyPort: allocatedPort,
|
|
@@ -89124,8 +89941,11 @@ function createPreviewProxy(config2) {
|
|
|
89124
89941
|
});
|
|
89125
89942
|
srv.on("error", reject);
|
|
89126
89943
|
});
|
|
89944
|
+
await startPromise;
|
|
89945
|
+
startPromise = null;
|
|
89127
89946
|
},
|
|
89128
89947
|
async stop() {
|
|
89948
|
+
if (startPromise) await startPromise;
|
|
89129
89949
|
for (const req of activeRequests) {
|
|
89130
89950
|
req.destroy();
|
|
89131
89951
|
}
|
|
@@ -89138,6 +89958,7 @@ function createPreviewProxy(config2) {
|
|
|
89138
89958
|
const srv = server;
|
|
89139
89959
|
server = null;
|
|
89140
89960
|
allocatedPort = null;
|
|
89961
|
+
currentTargetPort = null;
|
|
89141
89962
|
await new Promise((resolve3, reject) => {
|
|
89142
89963
|
srv.close((err) => err ? reject(err) : resolve3());
|
|
89143
89964
|
});
|
|
@@ -89150,7 +89971,7 @@ function createPreviewProxy(config2) {
|
|
|
89150
89971
|
// src/services/storage/daemon-settings-store.ts
|
|
89151
89972
|
import { createHash as createHash5 } from "crypto";
|
|
89152
89973
|
import { mkdir as mkdir16, readFile as readFile27, rename as rename15, writeFile as writeFile23 } from "fs/promises";
|
|
89153
|
-
import { join as
|
|
89974
|
+
import { join as join41 } from "path";
|
|
89154
89975
|
var ProjectSettingsSchema = external_exports.object({
|
|
89155
89976
|
disabledMcpServers: external_exports.array(external_exports.string()).optional()
|
|
89156
89977
|
});
|
|
@@ -89158,9 +89979,9 @@ function hashProjectPath(projectPath) {
|
|
|
89158
89979
|
return createHash5("sha256").update(projectPath).digest("hex").slice(0, 16);
|
|
89159
89980
|
}
|
|
89160
89981
|
function buildDaemonSettingsStore(dataDir) {
|
|
89161
|
-
const settingsDir =
|
|
89982
|
+
const settingsDir = join41(dataDir, "settings");
|
|
89162
89983
|
function settingsPath(projectPath) {
|
|
89163
|
-
return
|
|
89984
|
+
return join41(settingsDir, `${hashProjectPath(projectPath)}.json`);
|
|
89164
89985
|
}
|
|
89165
89986
|
async function ensureDir() {
|
|
89166
89987
|
await mkdir16(settingsDir, { recursive: true });
|
|
@@ -89187,12 +90008,12 @@ function buildDaemonSettingsStore(dataDir) {
|
|
|
89187
90008
|
|
|
89188
90009
|
// src/services/storage/plugin-config-store.ts
|
|
89189
90010
|
import { mkdir as mkdir17, readFile as readFile28, rename as rename16, writeFile as writeFile24 } from "fs/promises";
|
|
89190
|
-
import { join as
|
|
90011
|
+
import { join as join42 } from "path";
|
|
89191
90012
|
function buildPluginConfigStore(pluginsDir) {
|
|
89192
90013
|
const cache2 = /* @__PURE__ */ new Map();
|
|
89193
90014
|
const writeQueues = /* @__PURE__ */ new Map();
|
|
89194
90015
|
function configPath(pluginId) {
|
|
89195
|
-
return
|
|
90016
|
+
return join42(pluginsDir, pluginId, "config.json");
|
|
89196
90017
|
}
|
|
89197
90018
|
async function ensureLoaded(pluginId) {
|
|
89198
90019
|
const cached = cache2.get(pluginId);
|
|
@@ -89223,7 +90044,7 @@ function buildPluginConfigStore(pluginsDir) {
|
|
|
89223
90044
|
const next = prev.then(async () => {
|
|
89224
90045
|
const filePath = configPath(pluginId);
|
|
89225
90046
|
const tmpPath = `${filePath}.tmp`;
|
|
89226
|
-
await mkdir17(
|
|
90047
|
+
await mkdir17(join42(pluginsDir, pluginId), { recursive: true });
|
|
89227
90048
|
await writeFile24(tmpPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
89228
90049
|
await rename16(tmpPath, filePath);
|
|
89229
90050
|
});
|
|
@@ -89631,7 +90452,7 @@ function resolveClaudeCodePath(log) {
|
|
|
89631
90452
|
try {
|
|
89632
90453
|
const req = createRequire4(import.meta.url);
|
|
89633
90454
|
const sdkMain = req.resolve("@anthropic-ai/claude-agent-sdk");
|
|
89634
|
-
const p2 =
|
|
90455
|
+
const p2 = join43(dirname14(sdkMain), "cli.js");
|
|
89635
90456
|
if (existsSync7(p2)) return ok("sdk_bundled", p2);
|
|
89636
90457
|
} catch {
|
|
89637
90458
|
}
|
|
@@ -89641,7 +90462,7 @@ function resolveClaudeCodePath(log) {
|
|
|
89641
90462
|
} catch {
|
|
89642
90463
|
}
|
|
89643
90464
|
for (const c of [
|
|
89644
|
-
|
|
90465
|
+
join43(process.env.HOME ?? "", ".local", "bin", "claude"),
|
|
89645
90466
|
"/usr/local/bin/claude"
|
|
89646
90467
|
])
|
|
89647
90468
|
if (existsSync7(c)) return ok("well_known", c);
|
|
@@ -89649,11 +90470,11 @@ function resolveClaudeCodePath(log) {
|
|
|
89649
90470
|
}
|
|
89650
90471
|
async function serve(options = {}) {
|
|
89651
90472
|
const shipyardHome = options.shipyardHome ?? getShipyardHome();
|
|
89652
|
-
const dataDir =
|
|
90473
|
+
const dataDir = join43(shipyardHome, options.isDev ? "data-dev" : "data");
|
|
89653
90474
|
const log = createChildLogger({ mode: "serve" });
|
|
89654
90475
|
const workspaceRoot = findProjectRoot(process.cwd());
|
|
89655
90476
|
registerBuiltinPlugins();
|
|
89656
|
-
const pluginConfigStore = buildPluginConfigStore(
|
|
90477
|
+
const pluginConfigStore = buildPluginConfigStore(join43(dataDir, "plugins"));
|
|
89657
90478
|
await mkdir18(dataDir, { recursive: true });
|
|
89658
90479
|
log.info({ shipyardHome, dataDir, workspaceRoot }, "Starting daemon");
|
|
89659
90480
|
function logAdapter(entry) {
|
|
@@ -89671,13 +90492,14 @@ async function serve(options = {}) {
|
|
|
89671
90492
|
await lifecycle.acquirePidFile(shipyardHome);
|
|
89672
90493
|
const pidTracker = buildPidTracker(shipyardHome);
|
|
89673
90494
|
await pidTracker.sweepOrphans(logAdapter);
|
|
89674
|
-
await pruneOldEpochData(
|
|
89675
|
-
const storage = new FileStorageAdapter(
|
|
89676
|
-
const
|
|
90495
|
+
await pruneOldEpochData(join43(dataDir, "loro"), LEGACY_EPOCH, logAdapter);
|
|
90496
|
+
const storage = new FileStorageAdapter(join43(dataDir, "loro"));
|
|
90497
|
+
const personalWebrtcAdapter = new WebRtcDataChannelAdapter();
|
|
89677
90498
|
const peerRoleRegistry = createPeerRoleRegistry();
|
|
90499
|
+
const presencePoolRef = { pool: {} };
|
|
89678
90500
|
const repo = new Repo({
|
|
89679
90501
|
identity: DAEMON_IDENTITY,
|
|
89680
|
-
adapters: [storage,
|
|
90502
|
+
adapters: [storage, personalWebrtcAdapter],
|
|
89681
90503
|
permissions: buildCollabPermissions(peerRoleRegistry)
|
|
89682
90504
|
});
|
|
89683
90505
|
const resolvedSignalingUrl = env.SHIPYARD_SIGNALING_URL ?? auth3.signalingUrl;
|
|
@@ -89726,7 +90548,7 @@ async function serve(options = {}) {
|
|
|
89726
90548
|
previewProxy
|
|
89727
90549
|
});
|
|
89728
90550
|
daemon.healthMetrics.start();
|
|
89729
|
-
const pluginsDir =
|
|
90551
|
+
const pluginsDir = join43(shipyardHome, "plugins");
|
|
89730
90552
|
await mkdir18(pluginsDir, { recursive: true });
|
|
89731
90553
|
let loadedPlugins = [];
|
|
89732
90554
|
const pluginFileWatcher = new PluginFileWatcher({
|
|
@@ -89806,8 +90628,11 @@ async function serve(options = {}) {
|
|
|
89806
90628
|
const fileIOHandlers = /* @__PURE__ */ new Set();
|
|
89807
90629
|
const collabRoomManager = signalingHandle ? createCollabRoomManager({
|
|
89808
90630
|
peerRoleRegistry,
|
|
89809
|
-
|
|
89810
|
-
|
|
90631
|
+
personalRepo: repo,
|
|
90632
|
+
planEpoch: PLAN_EPOCH,
|
|
90633
|
+
canvasEpoch: CANVAS_EPOCH,
|
|
90634
|
+
createPeerManagerForRoom: (roomId, connection, collabTaskId, collabWebrtcAdapter) => createPeerManager({
|
|
90635
|
+
webrtcAdapter: collabWebrtcAdapter,
|
|
89811
90636
|
log: createChildLogger({ mode: `collab-peer:${roomId}` }),
|
|
89812
90637
|
onAnswer(namespacedId, answer) {
|
|
89813
90638
|
const prefix = `collab:${roomId}:`;
|
|
@@ -89847,7 +90672,9 @@ async function serve(options = {}) {
|
|
|
89847
90672
|
peerRoleRegistry,
|
|
89848
90673
|
peerMachineId: machineId,
|
|
89849
90674
|
collabTaskId,
|
|
89850
|
-
configStore: pluginConfigStore
|
|
90675
|
+
configStore: pluginConfigStore,
|
|
90676
|
+
previewProxy,
|
|
90677
|
+
presencePool: presencePoolRef
|
|
89851
90678
|
});
|
|
89852
90679
|
if (loadedPlugins.length > 0) {
|
|
89853
90680
|
daemon.taskManager.broadcastControl({
|
|
@@ -90086,7 +90913,7 @@ async function serve(options = {}) {
|
|
|
90086
90913
|
);
|
|
90087
90914
|
}
|
|
90088
90915
|
const peerManager = signalingHandle ? createPeerManager({
|
|
90089
|
-
webrtcAdapter,
|
|
90916
|
+
webrtcAdapter: personalWebrtcAdapter,
|
|
90090
90917
|
onAnswer: (targetMachineId, answer) => {
|
|
90091
90918
|
signalingHandle.connection.send({
|
|
90092
90919
|
type: "webrtc-answer",
|
|
@@ -90114,7 +90941,10 @@ async function serve(options = {}) {
|
|
|
90114
90941
|
machineId: signalingHandle.machineId,
|
|
90115
90942
|
signalingBaseUrl: auth3.signalingUrl,
|
|
90116
90943
|
userToken: auth3.token,
|
|
90117
|
-
configStore: pluginConfigStore
|
|
90944
|
+
configStore: pluginConfigStore,
|
|
90945
|
+
previewProxy,
|
|
90946
|
+
presencePool: presencePoolRef,
|
|
90947
|
+
peerIdentity: { userId: auth3.userId, username: auth3.displayName, avatarUrl: null }
|
|
90118
90948
|
});
|
|
90119
90949
|
portDetector.resend();
|
|
90120
90950
|
if (loadedPlugins.length > 0) {
|
|
@@ -90347,8 +91177,24 @@ async function serve(options = {}) {
|
|
|
90347
91177
|
}
|
|
90348
91178
|
portDetector = createPortDetector({
|
|
90349
91179
|
workspaceRoot,
|
|
90350
|
-
onPorts: (ports) => {
|
|
90351
|
-
|
|
91180
|
+
onPorts: async (ports) => {
|
|
91181
|
+
const firstPort = ports[0]?.port;
|
|
91182
|
+
if (firstPort && !previewProxy.port) {
|
|
91183
|
+
try {
|
|
91184
|
+
await previewProxy.start({ port: firstPort });
|
|
91185
|
+
} catch (err) {
|
|
91186
|
+
logAdapter({
|
|
91187
|
+
event: "preview_proxy_auto_start_failed",
|
|
91188
|
+
port: firstPort,
|
|
91189
|
+
error: err instanceof Error ? err.message : String(err)
|
|
91190
|
+
});
|
|
91191
|
+
}
|
|
91192
|
+
}
|
|
91193
|
+
daemon.taskManager.broadcastControl({
|
|
91194
|
+
type: "detected_ports",
|
|
91195
|
+
ports,
|
|
91196
|
+
proxyPort: previewProxy.port ?? void 0
|
|
91197
|
+
});
|
|
90352
91198
|
},
|
|
90353
91199
|
log: logAdapter
|
|
90354
91200
|
});
|
|
@@ -90702,4 +91548,4 @@ export {
|
|
|
90702
91548
|
classifyLogLevel,
|
|
90703
91549
|
serve
|
|
90704
91550
|
};
|
|
90705
|
-
//# sourceMappingURL=serve-
|
|
91551
|
+
//# sourceMappingURL=serve-XSXFIZDE.js.map
|