@schoolai/shipyard 3.0.0 → 3.1.0-rc.20260414.0
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-KBBJR56G.js} +1200 -360
- package/dist/serve-KBBJR56G.js.map +1 -0
- package/dist/{start-G6D7XOCQ.js → start-33NBTOH4.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-33NBTOH4.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) {
|
|
@@ -30802,7 +31121,7 @@ function handlePluginAuthRequest(pluginId, sendControl, deps, logAdapter) {
|
|
|
30802
31121
|
}
|
|
30803
31122
|
|
|
30804
31123
|
// src/services/pr-poller.ts
|
|
30805
|
-
var PR_POLL_INTERVAL_MS =
|
|
31124
|
+
var PR_POLL_INTERVAL_MS = 3e4;
|
|
30806
31125
|
async function enrichClosedPR(pr, cwd) {
|
|
30807
31126
|
if (pr.state !== "closed" || pr.mergeCommitSha) return pr;
|
|
30808
31127
|
if (!pr.headRefSha) return pr;
|
|
@@ -30813,10 +31132,9 @@ async function enrichClosedPR(pr, cwd) {
|
|
|
30813
31132
|
);
|
|
30814
31133
|
return headInBase ? { ...pr, headCommitInBase: true } : pr;
|
|
30815
31134
|
}
|
|
30816
|
-
async function
|
|
31135
|
+
async function fetchCIDataWithRemote(pr, cwd, remote) {
|
|
30817
31136
|
const referenceCommit = pr?.mergeCommitSha ?? pr?.headRefSha ?? await run("git", ["rev-parse", "HEAD"], cwd).catch(() => "");
|
|
30818
31137
|
if (!referenceCommit) return { deployments: [], workflowRuns: [], requiredChecks: [] };
|
|
30819
|
-
const remote = await run("git", ["remote", "get-url", "origin"], cwd).catch(() => "");
|
|
30820
31138
|
const parsed = parseOwnerRepo(remote);
|
|
30821
31139
|
if (!parsed) return { deployments: [], workflowRuns: [], requiredChecks: [] };
|
|
30822
31140
|
const branch = pr?.baseRef ?? "main";
|
|
@@ -30843,13 +31161,14 @@ async function fetchCIData(pr, cwd) {
|
|
|
30843
31161
|
const allDeployments = [...deployments, ...placeholderDeployments];
|
|
30844
31162
|
return { deployments: allDeployments, workflowRuns, requiredChecks };
|
|
30845
31163
|
}
|
|
30846
|
-
function createPRPoller(callbacks, log) {
|
|
30847
|
-
const
|
|
30848
|
-
|
|
31164
|
+
function createPRPoller(callbacks, log, resolveTopLevel = getGitTopLevel) {
|
|
31165
|
+
const repos = /* @__PURE__ */ new Map();
|
|
31166
|
+
const taskToRepo = /* @__PURE__ */ new Map();
|
|
31167
|
+
let disposed = false;
|
|
31168
|
+
async function fetchPRState(cwd) {
|
|
30849
31169
|
const available = await isGhAvailable();
|
|
30850
31170
|
if (!available) {
|
|
30851
31171
|
return {
|
|
30852
|
-
taskId,
|
|
30853
31172
|
prAvailable: false,
|
|
30854
31173
|
currentBranchPR: null,
|
|
30855
31174
|
assignedReviews: [],
|
|
@@ -30860,14 +31179,14 @@ function createPRPoller(callbacks, log) {
|
|
|
30860
31179
|
updatedAt: Date.now()
|
|
30861
31180
|
};
|
|
30862
31181
|
}
|
|
30863
|
-
const [
|
|
30864
|
-
getPRForCurrentBranch(cwd),
|
|
30865
|
-
|
|
30866
|
-
getUserPRs(cwd)
|
|
31182
|
+
const [prResults, remote] = await Promise.all([
|
|
31183
|
+
Promise.allSettled([getPRForCurrentBranch(cwd), getAssignedReviews(cwd), getUserPRs(cwd)]),
|
|
31184
|
+
run("git", ["remote", "get-url", "origin"], cwd).catch(() => "")
|
|
30867
31185
|
]);
|
|
31186
|
+
const [currentPR, assigned, user] = prResults;
|
|
30868
31187
|
const { allPRs: _2, currentBranchPR: rawPR } = deduplicatePRs(currentPR, assigned, user);
|
|
30869
31188
|
const currentBranchPR = rawPR ? await enrichClosedPR(rawPR, cwd) : null;
|
|
30870
|
-
const ciData = await
|
|
31189
|
+
const ciData = await fetchCIDataWithRemote(currentBranchPR, cwd, remote).catch(
|
|
30871
31190
|
() => ({
|
|
30872
31191
|
deployments: [],
|
|
30873
31192
|
workflowRuns: [],
|
|
@@ -30875,7 +31194,6 @@ function createPRPoller(callbacks, log) {
|
|
|
30875
31194
|
})
|
|
30876
31195
|
);
|
|
30877
31196
|
return {
|
|
30878
|
-
taskId,
|
|
30879
31197
|
prAvailable: true,
|
|
30880
31198
|
currentBranchPR,
|
|
30881
31199
|
assignedReviews: assigned.status === "fulfilled" ? assigned.value : [],
|
|
@@ -30886,81 +31204,135 @@ function createPRPoller(callbacks, log) {
|
|
|
30886
31204
|
updatedAt: Date.now()
|
|
30887
31205
|
};
|
|
30888
31206
|
}
|
|
30889
|
-
|
|
31207
|
+
function pushToSubscribers(entry, payload) {
|
|
31208
|
+
for (const taskId of entry.subscribers) {
|
|
31209
|
+
callbacks.onPRState({ ...payload, taskId });
|
|
31210
|
+
}
|
|
31211
|
+
}
|
|
31212
|
+
async function poll(topLevel, entry) {
|
|
31213
|
+
if (entry.isPolling) return;
|
|
31214
|
+
entry.isPolling = true;
|
|
30890
31215
|
try {
|
|
30891
|
-
const
|
|
31216
|
+
const currentBranch = await run("git", ["rev-parse", "--abbrev-ref", "HEAD"], entry.cwd).then((s2) => s2.trim()).catch(() => "");
|
|
31217
|
+
if (entry.lastBranch && currentBranch && currentBranch !== entry.lastBranch) {
|
|
31218
|
+
entry.lastContent = "";
|
|
31219
|
+
log.debug(
|
|
31220
|
+
{ topLevel, oldBranch: entry.lastBranch, newBranch: currentBranch },
|
|
31221
|
+
"Branch changed, clearing dedup cache"
|
|
31222
|
+
);
|
|
31223
|
+
}
|
|
31224
|
+
if (currentBranch) entry.lastBranch = currentBranch;
|
|
31225
|
+
const payload = await fetchPRState(entry.cwd);
|
|
30892
31226
|
if (!payload) return;
|
|
30893
31227
|
const { updatedAt: _2, ...comparable } = payload;
|
|
30894
31228
|
const contentKey = JSON.stringify(comparable);
|
|
30895
31229
|
if (entry.lastContent === contentKey) {
|
|
30896
|
-
log.debug({
|
|
31230
|
+
log.debug({ topLevel }, "PR state unchanged, skipping push");
|
|
30897
31231
|
return;
|
|
30898
31232
|
}
|
|
30899
31233
|
entry.lastContent = contentKey;
|
|
30900
|
-
|
|
30901
|
-
|
|
31234
|
+
entry.lastPayload = payload;
|
|
31235
|
+
pushToSubscribers(entry, payload);
|
|
31236
|
+
log.debug(
|
|
31237
|
+
{ topLevel, prAvailable: payload.prAvailable, subscribers: entry.subscribers.size },
|
|
31238
|
+
"PR state pushed"
|
|
31239
|
+
);
|
|
30902
31240
|
} catch (err) {
|
|
30903
|
-
log.warn({ err,
|
|
31241
|
+
log.warn({ err, topLevel }, "PR poll failed");
|
|
31242
|
+
} finally {
|
|
31243
|
+
entry.isPolling = false;
|
|
30904
31244
|
}
|
|
30905
31245
|
}
|
|
31246
|
+
function removeFromPreviousRepo(taskId) {
|
|
31247
|
+
const prev = taskToRepo.get(taskId);
|
|
31248
|
+
if (!prev) return;
|
|
31249
|
+
const prevEntry = repos.get(prev.topLevel);
|
|
31250
|
+
if (prevEntry) {
|
|
31251
|
+
prevEntry.subscribers.delete(taskId);
|
|
31252
|
+
if (prevEntry.subscribers.size === 0) {
|
|
31253
|
+
clearInterval(prevEntry.timer);
|
|
31254
|
+
for (const t of prevEntry.burstTimers) clearTimeout(t);
|
|
31255
|
+
repos.delete(prev.topLevel);
|
|
31256
|
+
log.info({ topLevel: prev.topLevel }, "PR polling stopped (no subscribers)");
|
|
31257
|
+
}
|
|
31258
|
+
}
|
|
31259
|
+
taskToRepo.delete(taskId);
|
|
31260
|
+
}
|
|
30906
31261
|
function startPolling(taskId, cwd) {
|
|
30907
|
-
|
|
30908
|
-
|
|
30909
|
-
|
|
30910
|
-
|
|
30911
|
-
|
|
30912
|
-
|
|
30913
|
-
|
|
30914
|
-
|
|
30915
|
-
|
|
30916
|
-
|
|
30917
|
-
|
|
30918
|
-
|
|
30919
|
-
|
|
31262
|
+
resolveTopLevel(cwd).then((topLevel) => {
|
|
31263
|
+
if (disposed) return;
|
|
31264
|
+
removeFromPreviousRepo(taskId);
|
|
31265
|
+
taskToRepo.set(taskId, { topLevel, cwd });
|
|
31266
|
+
const existing = repos.get(topLevel);
|
|
31267
|
+
if (existing) {
|
|
31268
|
+
existing.subscribers.add(taskId);
|
|
31269
|
+
if (existing.lastPayload) {
|
|
31270
|
+
callbacks.onPRState({ ...existing.lastPayload, taskId });
|
|
31271
|
+
}
|
|
31272
|
+
log.info({ taskId, topLevel, cwd }, "PR polling joined existing repo");
|
|
31273
|
+
return;
|
|
31274
|
+
}
|
|
31275
|
+
const entry = {
|
|
31276
|
+
timer: null,
|
|
31277
|
+
burstTimers: [],
|
|
31278
|
+
lastContent: "",
|
|
31279
|
+
lastBranch: "",
|
|
31280
|
+
lastPayload: null,
|
|
31281
|
+
cwd,
|
|
31282
|
+
subscribers: /* @__PURE__ */ new Set([taskId]),
|
|
31283
|
+
isPolling: false
|
|
31284
|
+
};
|
|
31285
|
+
poll(topLevel, entry).catch((err) => {
|
|
31286
|
+
log.warn({ err, topLevel }, "Initial PR poll failed");
|
|
30920
31287
|
});
|
|
30921
|
-
|
|
30922
|
-
|
|
30923
|
-
|
|
31288
|
+
entry.timer = setInterval(() => {
|
|
31289
|
+
poll(topLevel, entry).catch((err) => {
|
|
31290
|
+
log.warn({ err, topLevel }, "PR poll tick failed");
|
|
31291
|
+
});
|
|
31292
|
+
}, PR_POLL_INTERVAL_MS);
|
|
31293
|
+
repos.set(topLevel, entry);
|
|
31294
|
+
log.info({ taskId, topLevel, cwd }, "PR polling started");
|
|
31295
|
+
}).catch((err) => {
|
|
31296
|
+
log.warn({ err, taskId, cwd }, "Failed to resolve git toplevel for PR polling");
|
|
31297
|
+
});
|
|
30924
31298
|
}
|
|
30925
31299
|
function getCwd(taskId) {
|
|
30926
|
-
return
|
|
31300
|
+
return taskToRepo.get(taskId)?.cwd ?? null;
|
|
30927
31301
|
}
|
|
30928
31302
|
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
|
-
}
|
|
31303
|
+
removeFromPreviousRepo(taskId);
|
|
30936
31304
|
}
|
|
30937
31305
|
function forceRefresh(taskId) {
|
|
30938
|
-
const
|
|
31306
|
+
const info = taskToRepo.get(taskId);
|
|
31307
|
+
if (!info) return;
|
|
31308
|
+
const entry = repos.get(info.topLevel);
|
|
30939
31309
|
if (!entry) return;
|
|
30940
31310
|
for (const t of entry.burstTimers) clearTimeout(t);
|
|
30941
31311
|
entry.burstTimers = [];
|
|
30942
31312
|
entry.lastContent = "";
|
|
30943
|
-
poll(
|
|
30944
|
-
log.warn({ err,
|
|
31313
|
+
poll(info.topLevel, entry).catch((err) => {
|
|
31314
|
+
log.warn({ err, topLevel: info.topLevel }, "PR force refresh failed");
|
|
30945
31315
|
});
|
|
30946
31316
|
for (const delayMs of [3e3, 8e3, 15e3]) {
|
|
30947
31317
|
entry.burstTimers.push(
|
|
30948
31318
|
setTimeout(() => {
|
|
30949
31319
|
entry.lastContent = "";
|
|
30950
|
-
poll(
|
|
30951
|
-
log.warn({ err,
|
|
31320
|
+
poll(info.topLevel, entry).catch((err) => {
|
|
31321
|
+
log.warn({ err, topLevel: info.topLevel }, "PR burst refresh failed");
|
|
30952
31322
|
});
|
|
30953
31323
|
}, delayMs)
|
|
30954
31324
|
);
|
|
30955
31325
|
}
|
|
30956
31326
|
}
|
|
30957
31327
|
function dispose() {
|
|
30958
|
-
|
|
31328
|
+
disposed = true;
|
|
31329
|
+
for (const [topLevel, entry] of repos) {
|
|
30959
31330
|
clearInterval(entry.timer);
|
|
30960
31331
|
for (const t of entry.burstTimers) clearTimeout(t);
|
|
30961
|
-
log.debug({
|
|
31332
|
+
log.debug({ topLevel }, "PR poll timer cleared on dispose");
|
|
30962
31333
|
}
|
|
30963
|
-
|
|
31334
|
+
repos.clear();
|
|
31335
|
+
taskToRepo.clear();
|
|
30964
31336
|
}
|
|
30965
31337
|
return { startPolling, stopPolling, forceRefresh, getCwd, dispose };
|
|
30966
31338
|
}
|
|
@@ -31792,10 +32164,11 @@ async function rehydrateFromPersistence(persistence, taskManager, log, taskState
|
|
|
31792
32164
|
async function sweepStaleTasks(taskStateStore, taskManager, log) {
|
|
31793
32165
|
const tasks = await taskStateStore.listTasks();
|
|
31794
32166
|
const actions = planStaleSweep(tasks, (id) => taskManager.isRunning(id));
|
|
32167
|
+
const noBroadcast = { broadcast: false };
|
|
31795
32168
|
for (const action of actions) {
|
|
31796
32169
|
if (action.kind === "mark_input_required") {
|
|
31797
|
-
await taskStateStore.updateTaskStatus(action.taskId, "input_required");
|
|
31798
|
-
await taskStateStore.acknowledge(action.taskId);
|
|
32170
|
+
await taskStateStore.updateTaskStatus(action.taskId, "input_required", noBroadcast);
|
|
32171
|
+
await taskStateStore.acknowledge(action.taskId, noBroadcast);
|
|
31799
32172
|
log({
|
|
31800
32173
|
event: "stale_task_swept",
|
|
31801
32174
|
taskId: action.taskId,
|
|
@@ -33797,7 +34170,7 @@ var TOOL_DESCRIPTION2 = [
|
|
|
33797
34170
|
"The preview panel opens in the Shipyard UI next to the conversation. The user can interact with it directly."
|
|
33798
34171
|
].join("\n");
|
|
33799
34172
|
var SetPreviewTargetInput = {
|
|
33800
|
-
port: external_exports.number().int().
|
|
34173
|
+
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
34174
|
url: external_exports.string().url().optional().describe('Fully qualified URL to preview (e.g. "http://localhost:3000/dashboard").')
|
|
33802
34175
|
};
|
|
33803
34176
|
function createPreviewTools(ctx) {
|
|
@@ -33818,9 +34191,14 @@ function createPreviewTools(ctx) {
|
|
|
33818
34191
|
isError: true
|
|
33819
34192
|
};
|
|
33820
34193
|
}
|
|
33821
|
-
|
|
33822
|
-
|
|
33823
|
-
|
|
34194
|
+
if (input.url != null) {
|
|
34195
|
+
await ctx.previewProxy.stop();
|
|
34196
|
+
} else if (input.port != null) {
|
|
34197
|
+
if (ctx.previewProxy.port) {
|
|
34198
|
+
ctx.previewProxy.retarget(input.port);
|
|
34199
|
+
} else {
|
|
34200
|
+
await ctx.previewProxy.start({ port: input.port });
|
|
34201
|
+
}
|
|
33824
34202
|
}
|
|
33825
34203
|
ctx.broadcastControl({
|
|
33826
34204
|
type: "set_preview_target",
|
|
@@ -42788,6 +43166,16 @@ When you call ExitPlanMode, your plan is published to the Shipyard canvas for co
|
|
|
42788
43166
|
**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
43167
|
|
|
42790
43168
|
Do not revise the plan unless reviewers request changes. When you see \`<${XML_TAGS.PLAN_REVIEW} decision="approve">\`, call ExitPlanMode again to begin implementation.
|
|
43169
|
+
|
|
43170
|
+
## Background Execution
|
|
43171
|
+
|
|
43172
|
+
Prefer background execution to keep your main thread responsive to collaborators.
|
|
43173
|
+
|
|
43174
|
+
**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.
|
|
43175
|
+
|
|
43176
|
+
**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.
|
|
43177
|
+
|
|
43178
|
+
**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
43179
|
`.trim();
|
|
42792
43180
|
function buildCollabSystemPrompt(participants) {
|
|
42793
43181
|
const roster = participants.map((p2) => `${p2.name} (${p2.role})`).join(", ");
|
|
@@ -66428,11 +66816,13 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66428
66816
|
* Guards all SDK write operations to prevent "ProcessTransport not ready" errors.
|
|
66429
66817
|
*/
|
|
66430
66818
|
#closed = false;
|
|
66431
|
-
|
|
66819
|
+
#pidRef;
|
|
66820
|
+
constructor(query3, controller, onEvent, spawnMcpServers, pidRef) {
|
|
66432
66821
|
this.#query = query3;
|
|
66433
66822
|
this.#controller = controller;
|
|
66434
66823
|
this.#onEvent = onEvent;
|
|
66435
66824
|
this.#spawnMcpServers = spawnMcpServers;
|
|
66825
|
+
this.#pidRef = pidRef;
|
|
66436
66826
|
}
|
|
66437
66827
|
/** True once the subprocess has exited and SDK writes will throw. */
|
|
66438
66828
|
get isClosed() {
|
|
@@ -66452,27 +66842,8 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66452
66842
|
const controller = new InputController();
|
|
66453
66843
|
const { onChildSpawned, onChildExited } = options;
|
|
66454
66844
|
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;
|
|
66845
|
+
const pidRef = { current: null };
|
|
66846
|
+
const spawnClaudeCodeProcess = onChildSpawned ? createChildSpawner(stderrCallback, pidRef, onChildSpawned, onChildExited) : void 0;
|
|
66476
66847
|
const queryInstance = queryFn({
|
|
66477
66848
|
prompt: controller.iterable(),
|
|
66478
66849
|
options: {
|
|
@@ -66503,7 +66874,8 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66503
66874
|
queryInstance,
|
|
66504
66875
|
controller,
|
|
66505
66876
|
onEvent,
|
|
66506
|
-
spawnMcpServers
|
|
66877
|
+
spawnMcpServers,
|
|
66878
|
+
pidRef
|
|
66507
66879
|
);
|
|
66508
66880
|
if (initialContent.length > 0) {
|
|
66509
66881
|
controller.push(toSdkContent(initialContent));
|
|
@@ -66528,6 +66900,13 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66528
66900
|
this.#query.close();
|
|
66529
66901
|
}
|
|
66530
66902
|
forceKill() {
|
|
66903
|
+
if (this.#pidRef.current != null) {
|
|
66904
|
+
try {
|
|
66905
|
+
process.kill(this.#pidRef.current, "SIGKILL");
|
|
66906
|
+
} catch {
|
|
66907
|
+
}
|
|
66908
|
+
this.#pidRef.current = null;
|
|
66909
|
+
}
|
|
66531
66910
|
this.#closed = true;
|
|
66532
66911
|
this.#controller.end();
|
|
66533
66912
|
this.#query.close();
|
|
@@ -66584,6 +66963,14 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66584
66963
|
await this.#query.reconnectMcpServer(serverName);
|
|
66585
66964
|
}
|
|
66586
66965
|
}
|
|
66966
|
+
async stopBackgroundTask(taskId) {
|
|
66967
|
+
if (this.#closed) return;
|
|
66968
|
+
try {
|
|
66969
|
+
await this.#query.stopTask(taskId);
|
|
66970
|
+
} catch (err) {
|
|
66971
|
+
if (!this.#closed) throw err;
|
|
66972
|
+
}
|
|
66973
|
+
}
|
|
66587
66974
|
async #runMessageLoop(log) {
|
|
66588
66975
|
try {
|
|
66589
66976
|
for await (const message of this.#query) {
|
|
@@ -66592,9 +66979,11 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66592
66979
|
this.#onEvent(event);
|
|
66593
66980
|
}
|
|
66594
66981
|
}
|
|
66982
|
+
this.#pidRef.current = null;
|
|
66595
66983
|
this.#closed = true;
|
|
66596
66984
|
this.#onEvent({ type: "subprocess_died" });
|
|
66597
66985
|
} catch (error2) {
|
|
66986
|
+
this.#pidRef.current = null;
|
|
66598
66987
|
this.#closed = true;
|
|
66599
66988
|
const errorMsg = error2 instanceof Error ? error2.message : String(error2);
|
|
66600
66989
|
this.#onEvent({
|
|
@@ -66605,6 +66994,30 @@ var AnthropicAgentSubprocess = class _AnthropicAgentSubprocess {
|
|
|
66605
66994
|
}
|
|
66606
66995
|
}
|
|
66607
66996
|
};
|
|
66997
|
+
function createChildSpawner(stderrCallback, pidRef, onChildSpawned, onChildExited) {
|
|
66998
|
+
return (spawnOpts) => {
|
|
66999
|
+
const child = spawn4(spawnOpts.command, spawnOpts.args, {
|
|
67000
|
+
cwd: spawnOpts.cwd,
|
|
67001
|
+
stdio: ["pipe", "pipe", stderrCallback ? "pipe" : "ignore"],
|
|
67002
|
+
signal: spawnOpts.signal,
|
|
67003
|
+
env: { ...spawnOpts.env, CLAUDECODE: void 0 },
|
|
67004
|
+
windowsHide: true
|
|
67005
|
+
});
|
|
67006
|
+
if (!child.stdin || !child.stdout) {
|
|
67007
|
+
throw new Error("Failed to create stdio pipes for Claude subprocess");
|
|
67008
|
+
}
|
|
67009
|
+
if (stderrCallback && child.stderr) {
|
|
67010
|
+
child.stderr.on("data", (chunk) => stderrCallback(chunk.toString()));
|
|
67011
|
+
}
|
|
67012
|
+
if (child.pid) {
|
|
67013
|
+
const pid = child.pid;
|
|
67014
|
+
pidRef.current = pid;
|
|
67015
|
+
onChildSpawned(pid);
|
|
67016
|
+
child.on("exit", () => onChildExited?.(pid));
|
|
67017
|
+
}
|
|
67018
|
+
return Object.assign(child, { stdin: child.stdin, stdout: child.stdout });
|
|
67019
|
+
};
|
|
67020
|
+
}
|
|
66608
67021
|
function toSdkPermissionMode(mode) {
|
|
66609
67022
|
switch (mode) {
|
|
66610
67023
|
case "default":
|
|
@@ -72958,6 +73371,9 @@ var DirectApiSubprocess = class _DirectApiSubprocess {
|
|
|
72958
73371
|
/** No-op: no MCP in direct API mode. */
|
|
72959
73372
|
async reconnectMcpServer(_serverName) {
|
|
72960
73373
|
}
|
|
73374
|
+
/** No-op: direct API mode does not support background tasks. */
|
|
73375
|
+
async stopBackgroundTask(_taskId) {
|
|
73376
|
+
}
|
|
72961
73377
|
/** No-op in Phase 1 -- harness task context not used by direct API mode. */
|
|
72962
73378
|
setHarnessTaskId(_taskId) {
|
|
72963
73379
|
}
|
|
@@ -75062,12 +75478,20 @@ function noop3(snapshot) {
|
|
|
75062
75478
|
};
|
|
75063
75479
|
}
|
|
75064
75480
|
function handleColdIdle(snapshot, event) {
|
|
75065
|
-
if (event.type
|
|
75066
|
-
|
|
75067
|
-
|
|
75068
|
-
|
|
75069
|
-
|
|
75070
|
-
|
|
75481
|
+
if (event.type === "user_message") {
|
|
75482
|
+
const b2 = createEffectBuilder();
|
|
75483
|
+
b2.log("cold_idle", "spawning", event.type);
|
|
75484
|
+
b2.taskStatus("in_progress");
|
|
75485
|
+
b2.effects.push({ type: "spawn", reason: { kind: "fresh" } });
|
|
75486
|
+
return { state: "spawning", sessionId: null, rewindAtMessageId: null, effects: b2.effects };
|
|
75487
|
+
}
|
|
75488
|
+
if (event.type === "stop_commanded") {
|
|
75489
|
+
const b2 = createEffectBuilder();
|
|
75490
|
+
b2.effects.push({ type: "clear_queue" });
|
|
75491
|
+
b2.taskStatus("input_required");
|
|
75492
|
+
return { state: "cold_idle", sessionId: null, rewindAtMessageId: null, effects: b2.effects };
|
|
75493
|
+
}
|
|
75494
|
+
return noop3(snapshot);
|
|
75071
75495
|
}
|
|
75072
75496
|
function handleResumableIdle(snapshot, event) {
|
|
75073
75497
|
const { sessionId, rewindAtMessageId } = snapshot;
|
|
@@ -75086,6 +75510,12 @@ function handleResumableIdle(snapshot, event) {
|
|
|
75086
75510
|
b2.effects.push({ type: "spawn", reason });
|
|
75087
75511
|
return { state: "spawning", sessionId, rewindAtMessageId: null, effects: b2.effects };
|
|
75088
75512
|
}
|
|
75513
|
+
if (event.type === "stop_commanded") {
|
|
75514
|
+
const b2 = createEffectBuilder();
|
|
75515
|
+
b2.effects.push({ type: "clear_queue" });
|
|
75516
|
+
b2.taskStatus("input_required");
|
|
75517
|
+
return { state: "resumable_idle", sessionId, rewindAtMessageId, effects: b2.effects };
|
|
75518
|
+
}
|
|
75089
75519
|
if (event.type === "session_expired") {
|
|
75090
75520
|
const b2 = createEffectBuilder();
|
|
75091
75521
|
b2.log("resumable_idle", "cold_idle", event.type);
|
|
@@ -75245,12 +75675,7 @@ function exitStoppingAlive(snapshot, trigger) {
|
|
|
75245
75675
|
const { sessionId, rewindAtMessageId } = snapshot;
|
|
75246
75676
|
const b2 = createEffectBuilder();
|
|
75247
75677
|
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
|
-
}
|
|
75678
|
+
b2.effects.push({ type: "clear_queue" });
|
|
75254
75679
|
b2.log("stopping", "warm_idle", trigger);
|
|
75255
75680
|
b2.taskStatus("input_required");
|
|
75256
75681
|
return { state: "warm_idle", sessionId, rewindAtMessageId, effects: b2.effects };
|
|
@@ -75278,7 +75703,7 @@ function handleStopping(snapshot, event) {
|
|
|
75278
75703
|
b2.taskStatus("input_required");
|
|
75279
75704
|
return { state: "resumable_idle", sessionId, rewindAtMessageId, effects: b2.effects };
|
|
75280
75705
|
}
|
|
75281
|
-
if (event.type === "stop_timeout") {
|
|
75706
|
+
if (event.type === "stop_timeout" || event.type === "interrupt_failed") {
|
|
75282
75707
|
b2.log("stopping", "resumable_idle", event.type);
|
|
75283
75708
|
b2.effects.push({ type: "force_kill" });
|
|
75284
75709
|
b2.effects.push({ type: "clear_pending_inputs" });
|
|
@@ -75367,6 +75792,9 @@ var AgentSessionManager = class {
|
|
|
75367
75792
|
notifyInterruptAcknowledged() {
|
|
75368
75793
|
this.#dispatch({ type: "interrupt_acknowledged" });
|
|
75369
75794
|
}
|
|
75795
|
+
notifyInterruptFailed() {
|
|
75796
|
+
this.#dispatch({ type: "interrupt_failed" });
|
|
75797
|
+
}
|
|
75370
75798
|
notifyCloseAcknowledged() {
|
|
75371
75799
|
this.#dispatch({ type: "close_acknowledged" });
|
|
75372
75800
|
}
|
|
@@ -75868,6 +76296,10 @@ var Thread = class {
|
|
|
75868
76296
|
if (!this.#subprocess) return;
|
|
75869
76297
|
return this.#subprocess.mcpSubmitOAuthCallbackUrl(serverName, callbackUrl);
|
|
75870
76298
|
}
|
|
76299
|
+
async stopBackgroundTask(taskId) {
|
|
76300
|
+
if (!this.#subprocess) return;
|
|
76301
|
+
await this.#subprocess.stopBackgroundTask(taskId);
|
|
76302
|
+
}
|
|
75871
76303
|
async dispose() {
|
|
75872
76304
|
this.#disposed = true;
|
|
75873
76305
|
this.#permissionHandler.denyAllPending("Thread disposed");
|
|
@@ -76158,13 +76590,18 @@ ${conversationReplay}` : conversationReplay;
|
|
|
76158
76590
|
this.#subprocess?.pushMessage(content);
|
|
76159
76591
|
}
|
|
76160
76592
|
#handleInterrupt() {
|
|
76161
|
-
this.#subprocess
|
|
76593
|
+
if (!this.#subprocess || this.#subprocess.isClosed) {
|
|
76594
|
+
this.#manager.notifySubprocessDied();
|
|
76595
|
+
return;
|
|
76596
|
+
}
|
|
76597
|
+
this.#subprocess.interrupt().catch((err) => {
|
|
76162
76598
|
const msg = err instanceof Error ? err.message : String(err);
|
|
76163
76599
|
this.#config.log({
|
|
76164
76600
|
event: "thread_interrupt_failed",
|
|
76165
76601
|
threadId: this.#config.threadId,
|
|
76166
76602
|
error: msg
|
|
76167
76603
|
});
|
|
76604
|
+
this.#manager.notifyInterruptFailed();
|
|
76168
76605
|
});
|
|
76169
76606
|
}
|
|
76170
76607
|
#handleClose() {
|
|
@@ -81061,6 +81498,9 @@ var Task = class _Task {
|
|
|
81061
81498
|
this.#explicitlyStopped = true;
|
|
81062
81499
|
this.#mainThread.stop();
|
|
81063
81500
|
}
|
|
81501
|
+
async stopBackgroundTask(backgroundTaskId) {
|
|
81502
|
+
await this.#mainThread.stopBackgroundTask(backgroundTaskId);
|
|
81503
|
+
}
|
|
81064
81504
|
cancelQueued() {
|
|
81065
81505
|
const count = this.#mainThread.cancelQueued();
|
|
81066
81506
|
const collabCount = this.#collabQueue.cancelQueued();
|
|
@@ -82081,6 +82521,122 @@ Use this context to maintain continuity. You have already done this work \u2014
|
|
|
82081
82521
|
}
|
|
82082
82522
|
};
|
|
82083
82523
|
|
|
82524
|
+
// src/services/task/task-manager-mcp.ts
|
|
82525
|
+
function hasLiveSubprocess(state) {
|
|
82526
|
+
return state === "warm_idle" || state === "running" || state === "spawning";
|
|
82527
|
+
}
|
|
82528
|
+
function updateMcpServers(tasks, deps) {
|
|
82529
|
+
const resolved = deps.resolveMcpServers() ?? {};
|
|
82530
|
+
for (const task of tasks.values()) {
|
|
82531
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82532
|
+
task.orchestrator.setMcpServers(resolved).then((result) => {
|
|
82533
|
+
deps.log({
|
|
82534
|
+
event: "mcp_servers_updated",
|
|
82535
|
+
taskId: task.taskId,
|
|
82536
|
+
added: result?.added ?? [],
|
|
82537
|
+
removed: result?.removed ?? [],
|
|
82538
|
+
errors: result?.errors ?? {}
|
|
82539
|
+
});
|
|
82540
|
+
}).catch((err) => {
|
|
82541
|
+
deps.log({
|
|
82542
|
+
event: "mcp_servers_update_failed",
|
|
82543
|
+
taskId: task.taskId,
|
|
82544
|
+
error: err instanceof Error ? err.message : String(err)
|
|
82545
|
+
});
|
|
82546
|
+
});
|
|
82547
|
+
}
|
|
82548
|
+
}
|
|
82549
|
+
function triggerFastMcpPolling(tasks) {
|
|
82550
|
+
for (const task of tasks.values()) {
|
|
82551
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82552
|
+
task.orchestrator.triggerFastMcpPolling();
|
|
82553
|
+
}
|
|
82554
|
+
}
|
|
82555
|
+
function toggleMcpServer(tasks, serverName, enabled, log) {
|
|
82556
|
+
for (const task of tasks.values()) {
|
|
82557
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82558
|
+
task.orchestrator.toggleMcpServer(serverName, enabled).then(() => {
|
|
82559
|
+
log({
|
|
82560
|
+
event: "mcp_server_toggled",
|
|
82561
|
+
taskId: task.taskId,
|
|
82562
|
+
serverName,
|
|
82563
|
+
enabled
|
|
82564
|
+
});
|
|
82565
|
+
}).catch((err) => {
|
|
82566
|
+
log({
|
|
82567
|
+
event: "mcp_server_toggle_failed",
|
|
82568
|
+
taskId: task.taskId,
|
|
82569
|
+
serverName,
|
|
82570
|
+
enabled,
|
|
82571
|
+
error: err instanceof Error ? err.message : String(err)
|
|
82572
|
+
});
|
|
82573
|
+
});
|
|
82574
|
+
}
|
|
82575
|
+
}
|
|
82576
|
+
function reconnectMcpServer(tasks, serverName, deps) {
|
|
82577
|
+
const fullSet = deps.resolveMcpServers() ?? {};
|
|
82578
|
+
for (const task of tasks.values()) {
|
|
82579
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82580
|
+
const withoutTarget = { ...fullSet };
|
|
82581
|
+
delete withoutTarget[serverName];
|
|
82582
|
+
task.orchestrator.setMcpServers(withoutTarget).then(() => task.orchestrator.setMcpServers(fullSet)).then((result) => {
|
|
82583
|
+
deps.log({
|
|
82584
|
+
event: "mcp_server_reconnected",
|
|
82585
|
+
taskId: task.taskId,
|
|
82586
|
+
serverName,
|
|
82587
|
+
added: result?.added ?? [],
|
|
82588
|
+
removed: result?.removed ?? []
|
|
82589
|
+
});
|
|
82590
|
+
}).catch((err) => {
|
|
82591
|
+
deps.log({
|
|
82592
|
+
event: "mcp_server_reconnect_failed",
|
|
82593
|
+
taskId: task.taskId,
|
|
82594
|
+
serverName,
|
|
82595
|
+
error: err instanceof Error ? err.message : String(err)
|
|
82596
|
+
});
|
|
82597
|
+
});
|
|
82598
|
+
}
|
|
82599
|
+
}
|
|
82600
|
+
async function mcpAuthenticate(tasks, serverName) {
|
|
82601
|
+
for (const task of tasks.values()) {
|
|
82602
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82603
|
+
return task.orchestrator.mcpAuthenticate(serverName);
|
|
82604
|
+
}
|
|
82605
|
+
return null;
|
|
82606
|
+
}
|
|
82607
|
+
async function mcpSubmitOAuthCallbackUrl(tasks, serverName, callbackUrl) {
|
|
82608
|
+
for (const task of tasks.values()) {
|
|
82609
|
+
if (!hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82610
|
+
await task.orchestrator.mcpSubmitOAuthCallbackUrl(serverName, callbackUrl);
|
|
82611
|
+
return;
|
|
82612
|
+
}
|
|
82613
|
+
}
|
|
82614
|
+
function planGracefulShutdown(tasks, gracePeriodMs) {
|
|
82615
|
+
const immediate = [];
|
|
82616
|
+
const deferred = [];
|
|
82617
|
+
for (const task of tasks) {
|
|
82618
|
+
switch (task.state) {
|
|
82619
|
+
case "cold_idle":
|
|
82620
|
+
case "resumable_idle":
|
|
82621
|
+
case "warm_idle":
|
|
82622
|
+
immediate.push({ taskId: task.taskId, kind: "dispose_immediately" });
|
|
82623
|
+
break;
|
|
82624
|
+
case "spawning":
|
|
82625
|
+
case "running":
|
|
82626
|
+
deferred.push({ taskId: task.taskId, kind: "stop_then_wait" });
|
|
82627
|
+
break;
|
|
82628
|
+
case "stopping":
|
|
82629
|
+
deferred.push({ taskId: task.taskId, kind: "stop_then_wait" });
|
|
82630
|
+
break;
|
|
82631
|
+
default: {
|
|
82632
|
+
const _exhaustive = task.state;
|
|
82633
|
+
throw new Error(`Unhandled state: ${JSON.stringify(_exhaustive)}`);
|
|
82634
|
+
}
|
|
82635
|
+
}
|
|
82636
|
+
}
|
|
82637
|
+
return { immediate, deferred, gracePeriodMs };
|
|
82638
|
+
}
|
|
82639
|
+
|
|
82084
82640
|
// src/services/task/task-manager.ts
|
|
82085
82641
|
function cloneOverlay(overlay) {
|
|
82086
82642
|
return {
|
|
@@ -82605,111 +83161,32 @@ var TaskManager = class {
|
|
|
82605
83161
|
this.#deps.log({ event: "task_stop_requested", taskId });
|
|
82606
83162
|
this.#tasks.get(taskId)?.orchestrator.stop();
|
|
82607
83163
|
}
|
|
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
|
-
});
|
|
83164
|
+
async stopBackgroundTask(taskId, backgroundTaskId) {
|
|
83165
|
+
this.#deps.log({ event: "background_task_stop_requested", taskId, backgroundTaskId });
|
|
83166
|
+
const task = this.#tasks.get(taskId);
|
|
83167
|
+
if (!task) {
|
|
83168
|
+
this.#deps.log({ event: "background_task_stop_unknown_task", taskId, backgroundTaskId });
|
|
83169
|
+
return;
|
|
82632
83170
|
}
|
|
83171
|
+
await task.orchestrator.stopBackgroundTask(backgroundTaskId);
|
|
83172
|
+
}
|
|
83173
|
+
updateMcpServers() {
|
|
83174
|
+
updateMcpServers(this.#tasks, this.#deps);
|
|
82633
83175
|
}
|
|
82634
83176
|
triggerFastMcpPolling() {
|
|
82635
|
-
|
|
82636
|
-
if (!this.#hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82637
|
-
task.orchestrator.triggerFastMcpPolling();
|
|
82638
|
-
}
|
|
83177
|
+
triggerFastMcpPolling(this.#tasks);
|
|
82639
83178
|
}
|
|
82640
83179
|
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
|
-
}
|
|
83180
|
+
toggleMcpServer(this.#tasks, serverName, enabled, this.#deps.log);
|
|
82660
83181
|
}
|
|
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
83182
|
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
|
-
}
|
|
83183
|
+
reconnectMcpServer(this.#tasks, serverName, this.#deps);
|
|
82691
83184
|
}
|
|
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
83185
|
async mcpAuthenticate(serverName) {
|
|
82697
|
-
|
|
82698
|
-
if (!this.#hasLiveSubprocess(task.orchestrator.state)) continue;
|
|
82699
|
-
return task.orchestrator.mcpAuthenticate(serverName);
|
|
82700
|
-
}
|
|
82701
|
-
return null;
|
|
83186
|
+
return mcpAuthenticate(this.#tasks, serverName);
|
|
82702
83187
|
}
|
|
82703
83188
|
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";
|
|
83189
|
+
return mcpSubmitOAuthCallbackUrl(this.#tasks, serverName, callbackUrl);
|
|
82713
83190
|
}
|
|
82714
83191
|
cancelQueued(taskId) {
|
|
82715
83192
|
const task = this.#tasks.get(taskId);
|
|
@@ -82886,7 +83363,7 @@ var TaskManager = class {
|
|
|
82886
83363
|
/** Whether any task has a live subprocess that can accept MCP commands. */
|
|
82887
83364
|
get hasLiveSubprocesses() {
|
|
82888
83365
|
for (const task of this.#tasks.values()) {
|
|
82889
|
-
if (
|
|
83366
|
+
if (hasLiveSubprocess(task.orchestrator.state)) return true;
|
|
82890
83367
|
}
|
|
82891
83368
|
return false;
|
|
82892
83369
|
}
|
|
@@ -83016,31 +83493,6 @@ var TaskManager = class {
|
|
|
83016
83493
|
});
|
|
83017
83494
|
}
|
|
83018
83495
|
};
|
|
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
83496
|
|
|
83045
83497
|
// src/services/task/task-state-store.ts
|
|
83046
83498
|
import { join as join35 } from "path";
|
|
@@ -83072,12 +83524,43 @@ function buildTaskStateStore(dataDir) {
|
|
|
83072
83524
|
const unsubVersion = store.subscribe(() => {
|
|
83073
83525
|
_version++;
|
|
83074
83526
|
});
|
|
83527
|
+
const _broadcastQueue = [];
|
|
83528
|
+
const taskListeners = /* @__PURE__ */ new Set();
|
|
83529
|
+
const unsubBroadcast = store.subscribe((event) => {
|
|
83530
|
+
const broadcast = _broadcastQueue.shift() ?? true;
|
|
83531
|
+
const augmented = { ...event, broadcast };
|
|
83532
|
+
for (const listener of taskListeners) {
|
|
83533
|
+
try {
|
|
83534
|
+
listener(augmented);
|
|
83535
|
+
} catch {
|
|
83536
|
+
}
|
|
83537
|
+
}
|
|
83538
|
+
});
|
|
83539
|
+
function pushBroadcast(options) {
|
|
83540
|
+
_broadcastQueue.push(options?.broadcast ?? true);
|
|
83541
|
+
}
|
|
83542
|
+
async function safeUpdate(taskId, fn, options) {
|
|
83543
|
+
pushBroadcast(options);
|
|
83544
|
+
const lenBefore = _broadcastQueue.length;
|
|
83545
|
+
try {
|
|
83546
|
+
await store.update(taskId, fn);
|
|
83547
|
+
} catch (err) {
|
|
83548
|
+
if (_broadcastQueue.length === lenBefore) {
|
|
83549
|
+
_broadcastQueue.pop();
|
|
83550
|
+
}
|
|
83551
|
+
throw err;
|
|
83552
|
+
}
|
|
83553
|
+
if (_broadcastQueue.length === lenBefore) {
|
|
83554
|
+
_broadcastQueue.pop();
|
|
83555
|
+
}
|
|
83556
|
+
}
|
|
83075
83557
|
return {
|
|
83076
83558
|
get version() {
|
|
83077
83559
|
return _version;
|
|
83078
83560
|
},
|
|
83079
|
-
async createTask({ taskId, channelId, title, cwd, mode, scheduleId, scheduleName }) {
|
|
83561
|
+
async createTask({ taskId, channelId, title, cwd, mode, scheduleId, scheduleName }, options) {
|
|
83080
83562
|
const now = Date.now();
|
|
83563
|
+
pushBroadcast(options);
|
|
83081
83564
|
await store.set(taskId, {
|
|
83082
83565
|
taskId,
|
|
83083
83566
|
channelId,
|
|
@@ -83097,41 +83580,46 @@ function buildTaskStateStore(dataDir) {
|
|
|
83097
83580
|
...scheduleName ? { scheduleName } : {}
|
|
83098
83581
|
});
|
|
83099
83582
|
},
|
|
83100
|
-
async updateTaskStatus(taskId, status) {
|
|
83583
|
+
async updateTaskStatus(taskId, status, options) {
|
|
83101
83584
|
const task = await store.get(taskId);
|
|
83102
83585
|
if (!task) return;
|
|
83586
|
+
pushBroadcast(options);
|
|
83103
83587
|
await store.set(taskId, applyStatusTransition(task, status, Date.now()));
|
|
83104
83588
|
},
|
|
83105
|
-
async updateTitle(taskId, title) {
|
|
83589
|
+
async updateTitle(taskId, title, options) {
|
|
83106
83590
|
const task = await store.get(taskId);
|
|
83107
83591
|
if (!task) return;
|
|
83592
|
+
pushBroadcast(options);
|
|
83108
83593
|
await store.set(taskId, {
|
|
83109
83594
|
...task,
|
|
83110
83595
|
title,
|
|
83111
83596
|
updatedAt: Date.now()
|
|
83112
83597
|
});
|
|
83113
83598
|
},
|
|
83114
|
-
async updateCwd(taskId, cwd) {
|
|
83599
|
+
async updateCwd(taskId, cwd, options) {
|
|
83115
83600
|
const task = await store.get(taskId);
|
|
83116
83601
|
if (!task) return;
|
|
83602
|
+
pushBroadcast(options);
|
|
83117
83603
|
await store.set(taskId, {
|
|
83118
83604
|
...task,
|
|
83119
83605
|
cwd,
|
|
83120
83606
|
updatedAt: Date.now()
|
|
83121
83607
|
});
|
|
83122
83608
|
},
|
|
83123
|
-
async updateMode(taskId, mode) {
|
|
83609
|
+
async updateMode(taskId, mode, options) {
|
|
83124
83610
|
const task = await store.get(taskId);
|
|
83125
83611
|
if (!task) return;
|
|
83612
|
+
pushBroadcast(options);
|
|
83126
83613
|
await store.set(taskId, {
|
|
83127
83614
|
...task,
|
|
83128
83615
|
mode,
|
|
83129
83616
|
updatedAt: Date.now()
|
|
83130
83617
|
});
|
|
83131
83618
|
},
|
|
83132
|
-
async updateTodoProgress(taskId, progress) {
|
|
83619
|
+
async updateTodoProgress(taskId, progress, options) {
|
|
83133
83620
|
const task = await store.get(taskId);
|
|
83134
83621
|
if (!task) return;
|
|
83622
|
+
pushBroadcast(options);
|
|
83135
83623
|
await store.set(taskId, {
|
|
83136
83624
|
...task,
|
|
83137
83625
|
todoCompleted: progress.todoCompleted,
|
|
@@ -83140,15 +83628,16 @@ function buildTaskStateStore(dataDir) {
|
|
|
83140
83628
|
updatedAt: Date.now()
|
|
83141
83629
|
});
|
|
83142
83630
|
},
|
|
83143
|
-
async acknowledge(taskId) {
|
|
83144
|
-
await
|
|
83631
|
+
async acknowledge(taskId, options) {
|
|
83632
|
+
await safeUpdate(taskId, (task) => ({ ...task, acknowledgedAt: Date.now() }), options);
|
|
83145
83633
|
},
|
|
83146
|
-
async togglePin(taskId) {
|
|
83147
|
-
await
|
|
83634
|
+
async togglePin(taskId, options) {
|
|
83635
|
+
await safeUpdate(taskId, (task) => ({ ...task, pinned: !task.pinned }), options);
|
|
83148
83636
|
},
|
|
83149
|
-
async updateStructuredTasks(taskId, tasks, todoProgress) {
|
|
83637
|
+
async updateStructuredTasks(taskId, tasks, todoProgress, options) {
|
|
83150
83638
|
const task = await store.get(taskId);
|
|
83151
83639
|
if (!task) return;
|
|
83640
|
+
pushBroadcast(options);
|
|
83152
83641
|
await store.set(taskId, {
|
|
83153
83642
|
...task,
|
|
83154
83643
|
structuredTasks: tasks,
|
|
@@ -83160,39 +83649,49 @@ function buildTaskStateStore(dataDir) {
|
|
|
83160
83649
|
updatedAt: Date.now()
|
|
83161
83650
|
});
|
|
83162
83651
|
},
|
|
83163
|
-
async updateTaskOverlay(taskId, overlay) {
|
|
83652
|
+
async updateTaskOverlay(taskId, overlay, options) {
|
|
83164
83653
|
const task = await store.get(taskId);
|
|
83165
83654
|
if (!task) return;
|
|
83655
|
+
pushBroadcast(options);
|
|
83166
83656
|
await store.set(taskId, {
|
|
83167
83657
|
...task,
|
|
83168
83658
|
taskOverlay: overlay,
|
|
83169
83659
|
updatedAt: Date.now()
|
|
83170
83660
|
});
|
|
83171
83661
|
},
|
|
83172
|
-
async updateComposerSettings(taskId, settings) {
|
|
83662
|
+
async updateComposerSettings(taskId, settings, options) {
|
|
83173
83663
|
const task = await store.get(taskId);
|
|
83174
83664
|
if (!task) return;
|
|
83665
|
+
pushBroadcast(options);
|
|
83175
83666
|
await store.set(taskId, {
|
|
83176
83667
|
...task,
|
|
83177
83668
|
composerSettings: { ...task.composerSettings, ...settings },
|
|
83178
83669
|
updatedAt: Date.now()
|
|
83179
83670
|
});
|
|
83180
83671
|
},
|
|
83181
|
-
async updateCostStats(taskId, stats) {
|
|
83182
|
-
await
|
|
83183
|
-
|
|
83184
|
-
|
|
83185
|
-
|
|
83186
|
-
|
|
83187
|
-
|
|
83672
|
+
async updateCostStats(taskId, stats, options) {
|
|
83673
|
+
await safeUpdate(
|
|
83674
|
+
taskId,
|
|
83675
|
+
(task) => ({
|
|
83676
|
+
...task,
|
|
83677
|
+
totalCostUsd: stats.totalCostUsd,
|
|
83678
|
+
totalOutputTokens: stats.totalOutputTokens,
|
|
83679
|
+
updatedAt: Date.now()
|
|
83680
|
+
}),
|
|
83681
|
+
options
|
|
83682
|
+
);
|
|
83188
83683
|
},
|
|
83189
|
-
async clearCostStats(taskId) {
|
|
83190
|
-
await
|
|
83191
|
-
|
|
83192
|
-
|
|
83193
|
-
|
|
83194
|
-
|
|
83195
|
-
|
|
83684
|
+
async clearCostStats(taskId, options) {
|
|
83685
|
+
await safeUpdate(
|
|
83686
|
+
taskId,
|
|
83687
|
+
(task) => ({
|
|
83688
|
+
...task,
|
|
83689
|
+
totalCostUsd: 0,
|
|
83690
|
+
totalOutputTokens: 0,
|
|
83691
|
+
updatedAt: Date.now()
|
|
83692
|
+
}),
|
|
83693
|
+
options
|
|
83694
|
+
);
|
|
83196
83695
|
},
|
|
83197
83696
|
async getTask(taskId) {
|
|
83198
83697
|
return store.get(taskId);
|
|
@@ -83201,10 +83700,14 @@ function buildTaskStateStore(dataDir) {
|
|
|
83201
83700
|
return store.list();
|
|
83202
83701
|
},
|
|
83203
83702
|
subscribe(listener) {
|
|
83204
|
-
|
|
83703
|
+
taskListeners.add(listener);
|
|
83704
|
+
return () => {
|
|
83705
|
+
taskListeners.delete(listener);
|
|
83706
|
+
};
|
|
83205
83707
|
},
|
|
83206
83708
|
dispose() {
|
|
83207
83709
|
unsubVersion();
|
|
83710
|
+
unsubBroadcast();
|
|
83208
83711
|
}
|
|
83209
83712
|
};
|
|
83210
83713
|
}
|
|
@@ -83963,6 +84466,7 @@ async function createDaemon(deps) {
|
|
|
83963
84466
|
await rehydrateFromPersistence(sessionPersistence, taskManager, deps.log, taskStateStore);
|
|
83964
84467
|
await sweepStaleTasks(taskStateStore, taskManager, deps.log);
|
|
83965
84468
|
const taskStoreUnsub = taskStateStore.subscribe((event) => {
|
|
84469
|
+
if (!event.broadcast) return;
|
|
83966
84470
|
switch (event.kind) {
|
|
83967
84471
|
case "set": {
|
|
83968
84472
|
taskManager.broadcastControl({
|
|
@@ -84311,10 +84815,12 @@ var ROLE_PERMISSIONS = {
|
|
|
84311
84815
|
"list_schedules",
|
|
84312
84816
|
"run_schedule_now",
|
|
84313
84817
|
"stop_task",
|
|
84818
|
+
"request_preview_target",
|
|
84314
84819
|
"todo_item_added",
|
|
84315
84820
|
"todo_item_removed",
|
|
84316
84821
|
"todo_item_updated",
|
|
84317
|
-
"todo_dep_changed"
|
|
84822
|
+
"todo_dep_changed",
|
|
84823
|
+
"presence_update"
|
|
84318
84824
|
]),
|
|
84319
84825
|
"collaborator-full": /* @__PURE__ */ new Set([
|
|
84320
84826
|
"send_message",
|
|
@@ -84341,10 +84847,12 @@ var ROLE_PERMISSIONS = {
|
|
|
84341
84847
|
"request_annotation_snapshot",
|
|
84342
84848
|
"create_thread",
|
|
84343
84849
|
"list_threads",
|
|
84850
|
+
"request_preview_target",
|
|
84344
84851
|
"todo_item_added",
|
|
84345
84852
|
"todo_item_removed",
|
|
84346
84853
|
"todo_item_updated",
|
|
84347
|
-
"todo_dep_changed"
|
|
84854
|
+
"todo_dep_changed",
|
|
84855
|
+
"presence_update"
|
|
84348
84856
|
]),
|
|
84349
84857
|
"collaborator-review": /* @__PURE__ */ new Set([
|
|
84350
84858
|
"stop",
|
|
@@ -84355,7 +84863,8 @@ var ROLE_PERMISSIONS = {
|
|
|
84355
84863
|
"join_collab_room",
|
|
84356
84864
|
"leave_collab_room",
|
|
84357
84865
|
"request_annotation_snapshot",
|
|
84358
|
-
"list_threads"
|
|
84866
|
+
"list_threads",
|
|
84867
|
+
"presence_update"
|
|
84359
84868
|
]),
|
|
84360
84869
|
viewer: /* @__PURE__ */ new Set([
|
|
84361
84870
|
"subscribe",
|
|
@@ -84363,7 +84872,8 @@ var ROLE_PERMISSIONS = {
|
|
|
84363
84872
|
"request_capabilities",
|
|
84364
84873
|
"request_pr_data",
|
|
84365
84874
|
"join_collab_room",
|
|
84366
|
-
"leave_collab_room"
|
|
84875
|
+
"leave_collab_room",
|
|
84876
|
+
"presence_update"
|
|
84367
84877
|
])
|
|
84368
84878
|
};
|
|
84369
84879
|
function isCollabActionAllowed(role, action) {
|
|
@@ -84536,6 +85046,7 @@ function filterOutboundForCollab(msg, collabTaskId) {
|
|
|
84536
85046
|
case "worktree_error":
|
|
84537
85047
|
case "worktree_list":
|
|
84538
85048
|
case "worktree_removed":
|
|
85049
|
+
case "directory_list":
|
|
84539
85050
|
case "collab_room_joined":
|
|
84540
85051
|
case "collab_room_left":
|
|
84541
85052
|
case "schedule_fired":
|
|
@@ -84572,11 +85083,15 @@ function filterOutboundForCollab(msg, collabTaskId) {
|
|
|
84572
85083
|
/** Collab-scoped: always pass through (keyed by machineId/roomId, not taskId) */
|
|
84573
85084
|
case "collab_peer_role":
|
|
84574
85085
|
case "collab_participants_update":
|
|
85086
|
+
case "presence_state":
|
|
84575
85087
|
return msg;
|
|
84576
85088
|
/** Pass through: collab peers need error feedback */
|
|
84577
85089
|
case "promote_nudge":
|
|
84578
85090
|
case "error":
|
|
84579
85091
|
return msg;
|
|
85092
|
+
/** Log request is local-only, not relevant for collab peers */
|
|
85093
|
+
case "recent_logs":
|
|
85094
|
+
return null;
|
|
84580
85095
|
/** Exhaustiveness check: compile error if a new type is unhandled */
|
|
84581
85096
|
default: {
|
|
84582
85097
|
const _exhaustive = msg;
|
|
@@ -84851,6 +85366,9 @@ function routeMessage(msg, callbacks, log) {
|
|
|
84851
85366
|
case "request_user_settings":
|
|
84852
85367
|
callbacks.onRequestUserSettings();
|
|
84853
85368
|
break;
|
|
85369
|
+
case "request_preview_target":
|
|
85370
|
+
callbacks.onRequestPreviewTarget(msg.port, msg.url);
|
|
85371
|
+
break;
|
|
84854
85372
|
/** Unified annotation CRUD */
|
|
84855
85373
|
case "add_annotation":
|
|
84856
85374
|
callbacks.onAddAnnotation(msg.taskId, msg.annotation);
|
|
@@ -84931,6 +85449,9 @@ function routeMessage(msg, callbacks, log) {
|
|
|
84931
85449
|
case "stop_task":
|
|
84932
85450
|
callbacks.onStopTask(msg.taskId);
|
|
84933
85451
|
break;
|
|
85452
|
+
case "stop_background_agent":
|
|
85453
|
+
callbacks.onStopBackgroundAgent(msg.taskId, msg.backgroundTaskId);
|
|
85454
|
+
break;
|
|
84934
85455
|
case "todo_item_added":
|
|
84935
85456
|
callbacks.onTodoItemAdded(msg.taskId, msg.item);
|
|
84936
85457
|
break;
|
|
@@ -84950,6 +85471,15 @@ function routeMessage(msg, callbacks, log) {
|
|
|
84950
85471
|
msg.action
|
|
84951
85472
|
);
|
|
84952
85473
|
break;
|
|
85474
|
+
case "request_recent_logs":
|
|
85475
|
+
callbacks.onRequestRecentLogs(msg.windowMinutes);
|
|
85476
|
+
break;
|
|
85477
|
+
case "presence_update":
|
|
85478
|
+
callbacks.onPresenceUpdate(msg.state);
|
|
85479
|
+
break;
|
|
85480
|
+
case "list_directories":
|
|
85481
|
+
callbacks.onListDirectories(msg.basePath);
|
|
85482
|
+
break;
|
|
84953
85483
|
default: {
|
|
84954
85484
|
const _exhaustive = msg;
|
|
84955
85485
|
throw new Error(`Unhandled control message type: ${JSON.stringify(_exhaustive)}`);
|
|
@@ -84991,6 +85521,7 @@ function injectThreadResourceLink(store, mainChannelId, taskId, threadId, title,
|
|
|
84991
85521
|
|
|
84992
85522
|
// src/services/channels/control-channel-infra-handlers.ts
|
|
84993
85523
|
import { spawn as spawn6 } from "child_process";
|
|
85524
|
+
import { readdir as readdir7 } from "fs/promises";
|
|
84994
85525
|
|
|
84995
85526
|
// src/services/worktree-service.ts
|
|
84996
85527
|
import { execFile as execFile7, spawn as spawn5 } from "child_process";
|
|
@@ -85482,6 +86013,24 @@ function buildAgentInstallHandlers(deps) {
|
|
|
85482
86013
|
}
|
|
85483
86014
|
};
|
|
85484
86015
|
}
|
|
86016
|
+
var FILTERED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".turbo", "dist", ".next", "build"]);
|
|
86017
|
+
function buildDirectoryListHandler(deps) {
|
|
86018
|
+
return {
|
|
86019
|
+
onListDirectories: (basePath) => {
|
|
86020
|
+
readdir7(basePath, { withFileTypes: true }).then((entries) => {
|
|
86021
|
+
const directories = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".") && !FILTERED_DIRS.has(e.name)).map((e) => e.name).sort();
|
|
86022
|
+
deps.sendControlMessage({ type: "directory_list", basePath, directories });
|
|
86023
|
+
}).catch((err) => {
|
|
86024
|
+
deps.logAdapter({
|
|
86025
|
+
event: "list_directories_failed",
|
|
86026
|
+
basePath,
|
|
86027
|
+
error: err instanceof Error ? err.message : String(err)
|
|
86028
|
+
});
|
|
86029
|
+
deps.sendControlMessage({ type: "directory_list", basePath, directories: [] });
|
|
86030
|
+
});
|
|
86031
|
+
}
|
|
86032
|
+
};
|
|
86033
|
+
}
|
|
85485
86034
|
function buildEnvironmentChangedHandler(deps) {
|
|
85486
86035
|
const { daemon } = deps;
|
|
85487
86036
|
let debounceTimer = null;
|
|
@@ -85715,6 +86264,99 @@ function runPluginOp(pluginName, marketplace, action, ctx) {
|
|
|
85715
86264
|
});
|
|
85716
86265
|
}
|
|
85717
86266
|
|
|
86267
|
+
// src/services/channels/read-recent-logs.ts
|
|
86268
|
+
import { createReadStream, readdirSync as readdirSync2 } from "fs";
|
|
86269
|
+
import { join as join38 } from "path";
|
|
86270
|
+
import { createInterface } from "readline";
|
|
86271
|
+
var MAX_BYTES = 5e4;
|
|
86272
|
+
var LOG_BASE_NAME = "daemon.log";
|
|
86273
|
+
var SENSITIVE_PATTERNS = [
|
|
86274
|
+
[/eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g, "[REDACTED_JWT]"],
|
|
86275
|
+
[/"[Aa]uthorization"\s*:\s*"[^"]+"/g, '"Authorization":"[REDACTED]"'],
|
|
86276
|
+
[/"token"\s*:\s*"[^"]+"/g, '"token":"[REDACTED]"'],
|
|
86277
|
+
[/"accessToken"\s*:\s*"[^"]+"/g, '"accessToken":"[REDACTED]"'],
|
|
86278
|
+
[/"refreshToken"\s*:\s*"[^"]+"/g, '"refreshToken":"[REDACTED]"'],
|
|
86279
|
+
[/sk-ant-[A-Za-z0-9_-]{20,}/g, "[REDACTED_API_KEY]"],
|
|
86280
|
+
[/sk-[a-zA-Z0-9]{20,}/g, "[REDACTED_SECRET_KEY]"],
|
|
86281
|
+
[/device_code=[A-Za-z0-9_-]+/g, "device_code=[REDACTED]"]
|
|
86282
|
+
];
|
|
86283
|
+
function scrubSensitiveData(line) {
|
|
86284
|
+
let result = line;
|
|
86285
|
+
for (const [pattern, replacement] of SENSITIVE_PATTERNS) {
|
|
86286
|
+
result = result.replace(pattern, replacement);
|
|
86287
|
+
}
|
|
86288
|
+
return result;
|
|
86289
|
+
}
|
|
86290
|
+
async function collectFromFile(filePath, cutoff, collector, maxBytes) {
|
|
86291
|
+
const rl = createInterface({
|
|
86292
|
+
input: createReadStream(filePath, { encoding: "utf-8" }),
|
|
86293
|
+
crlfDelay: Number.POSITIVE_INFINITY
|
|
86294
|
+
});
|
|
86295
|
+
for await (const line of rl) {
|
|
86296
|
+
if (!line) continue;
|
|
86297
|
+
const timestamp = extractTimestamp(line);
|
|
86298
|
+
if (timestamp === null || timestamp < cutoff) continue;
|
|
86299
|
+
const scrubbed = scrubSensitiveData(line);
|
|
86300
|
+
const lineBytes = Buffer.byteLength(scrubbed, "utf-8");
|
|
86301
|
+
if (collector.totalBytes + lineBytes > maxBytes) {
|
|
86302
|
+
collector.truncated = true;
|
|
86303
|
+
rl.close();
|
|
86304
|
+
return true;
|
|
86305
|
+
}
|
|
86306
|
+
collector.lines.push(scrubbed);
|
|
86307
|
+
collector.totalBytes += lineBytes;
|
|
86308
|
+
}
|
|
86309
|
+
return false;
|
|
86310
|
+
}
|
|
86311
|
+
async function readRecentLogs(logDir, windowMinutes, maxBytes = MAX_BYTES) {
|
|
86312
|
+
const cutoff = Date.now() - windowMinutes * 6e4;
|
|
86313
|
+
const collector = { lines: [], totalBytes: 0, truncated: false };
|
|
86314
|
+
for (const filePath of discoverLogFiles(logDir)) {
|
|
86315
|
+
try {
|
|
86316
|
+
const exhausted = await collectFromFile(filePath, cutoff, collector, maxBytes);
|
|
86317
|
+
if (exhausted) break;
|
|
86318
|
+
} catch {
|
|
86319
|
+
}
|
|
86320
|
+
}
|
|
86321
|
+
return { lines: collector.lines, truncated: collector.truncated };
|
|
86322
|
+
}
|
|
86323
|
+
function discoverLogFiles(logDir) {
|
|
86324
|
+
let entries;
|
|
86325
|
+
try {
|
|
86326
|
+
entries = readdirSync2(logDir);
|
|
86327
|
+
} catch {
|
|
86328
|
+
return [];
|
|
86329
|
+
}
|
|
86330
|
+
const rotated = [];
|
|
86331
|
+
let currentFile = null;
|
|
86332
|
+
for (const entry of entries) {
|
|
86333
|
+
if (entry === LOG_BASE_NAME) {
|
|
86334
|
+
currentFile = join38(logDir, entry);
|
|
86335
|
+
} else if (entry.startsWith(`${LOG_BASE_NAME}.`)) {
|
|
86336
|
+
const suffix = entry.slice(LOG_BASE_NAME.length + 1);
|
|
86337
|
+
const index = Number.parseInt(suffix, 10);
|
|
86338
|
+
if (!Number.isNaN(index)) {
|
|
86339
|
+
rotated.push({ path: join38(logDir, entry), index });
|
|
86340
|
+
}
|
|
86341
|
+
}
|
|
86342
|
+
}
|
|
86343
|
+
rotated.sort((a, b2) => b2.index - a.index);
|
|
86344
|
+
const result = rotated.map((r) => r.path);
|
|
86345
|
+
if (currentFile) result.push(currentFile);
|
|
86346
|
+
return result;
|
|
86347
|
+
}
|
|
86348
|
+
function extractTimestamp(line) {
|
|
86349
|
+
const idx = line.indexOf('"time":');
|
|
86350
|
+
if (idx === -1) return null;
|
|
86351
|
+
const start = idx + 7;
|
|
86352
|
+
let end = start;
|
|
86353
|
+
while (end < line.length && line.charCodeAt(end) >= 48 && line.charCodeAt(end) <= 57) {
|
|
86354
|
+
end++;
|
|
86355
|
+
}
|
|
86356
|
+
if (end === start) return null;
|
|
86357
|
+
return Number(line.slice(start, end));
|
|
86358
|
+
}
|
|
86359
|
+
|
|
85718
86360
|
// src/services/channels/schedule-channel-callbacks.ts
|
|
85719
86361
|
function buildScheduleCallbacks(daemon, getControlHandler, log) {
|
|
85720
86362
|
return {
|
|
@@ -85883,6 +86525,33 @@ function buildTemplateCallbacks(daemon, getControlHandler, log) {
|
|
|
85883
86525
|
}
|
|
85884
86526
|
|
|
85885
86527
|
// src/services/channels/control-channel-wiring.ts
|
|
86528
|
+
function handlePreviewTargetRequest(proxy, port, url, daemon, log) {
|
|
86529
|
+
if (!proxy) return;
|
|
86530
|
+
(async () => {
|
|
86531
|
+
try {
|
|
86532
|
+
if (url) await proxy.stop();
|
|
86533
|
+
else if (port) proxy.port ? proxy.retarget(port) : await proxy.start({ port });
|
|
86534
|
+
daemon.taskManager.broadcastControl({
|
|
86535
|
+
type: "set_preview_target",
|
|
86536
|
+
port,
|
|
86537
|
+
url,
|
|
86538
|
+
proxyPort: proxy.port ?? void 0
|
|
86539
|
+
});
|
|
86540
|
+
} catch (err) {
|
|
86541
|
+
log({
|
|
86542
|
+
event: "preview_target_request_failed",
|
|
86543
|
+
error: err instanceof Error ? err.message : String(err)
|
|
86544
|
+
});
|
|
86545
|
+
}
|
|
86546
|
+
})();
|
|
86547
|
+
}
|
|
86548
|
+
function resolveIdentityFromRegistry(deps) {
|
|
86549
|
+
if (!deps.peerRoleRegistry || !deps.peerMachineId) return void 0;
|
|
86550
|
+
const entry = deps.peerRoleRegistry.getEntry(deps.peerMachineId);
|
|
86551
|
+
if (!entry) return void 0;
|
|
86552
|
+
const userId = entry.participantId.replace(/^human:/, "");
|
|
86553
|
+
return { userId, username: entry.displayName, avatarUrl: null };
|
|
86554
|
+
}
|
|
85886
86555
|
function applyMcpServerToggles(daemon, prevDisabled, nextDisabled, log) {
|
|
85887
86556
|
for (const name of nextDisabled) {
|
|
85888
86557
|
if (!prevDisabled.has(name)) {
|
|
@@ -85978,6 +86647,10 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
85978
86647
|
const worktreeHandlers = buildWorktreeHandlers(lazyInfraDeps);
|
|
85979
86648
|
const agentInstallHandlers = buildAgentInstallHandlers(lazyInfraDeps);
|
|
85980
86649
|
const envChangedHandler = buildEnvironmentChangedHandler(lazyInfraDeps);
|
|
86650
|
+
const directoryListHandler = buildDirectoryListHandler({
|
|
86651
|
+
sendControlMessage: (msg) => controlHandler.sendControl(msg),
|
|
86652
|
+
logAdapter
|
|
86653
|
+
});
|
|
85981
86654
|
const prPoller = createPRPoller(
|
|
85982
86655
|
{
|
|
85983
86656
|
onPRState: (payload) => {
|
|
@@ -86241,6 +86914,7 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86241
86914
|
},
|
|
86242
86915
|
...agentInstallHandlers,
|
|
86243
86916
|
...worktreeHandlers,
|
|
86917
|
+
...directoryListHandler,
|
|
86244
86918
|
onJoinCollabRoom: (() => {
|
|
86245
86919
|
const mgr = deps?.collabRoomManager;
|
|
86246
86920
|
if (!mgr) return void 0;
|
|
@@ -86545,6 +87219,9 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86545
87219
|
});
|
|
86546
87220
|
});
|
|
86547
87221
|
},
|
|
87222
|
+
onRequestPreviewTarget: (port, url) => {
|
|
87223
|
+
handlePreviewTargetRequest(deps?.previewProxy, port, url, daemon, logAdapter);
|
|
87224
|
+
},
|
|
86548
87225
|
onAcknowledgeTask: (taskId) => {
|
|
86549
87226
|
daemon.taskStateStore.acknowledge(taskId).catch((err) => {
|
|
86550
87227
|
logAdapter({
|
|
@@ -86574,9 +87251,44 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86574
87251
|
});
|
|
86575
87252
|
}
|
|
86576
87253
|
},
|
|
87254
|
+
onStopBackgroundAgent: (taskId, backgroundTaskId) => {
|
|
87255
|
+
daemon.taskManager.stopBackgroundTask(taskId, backgroundTaskId).catch((err) => {
|
|
87256
|
+
logAdapter({
|
|
87257
|
+
event: "stop_background_agent_failed",
|
|
87258
|
+
taskId,
|
|
87259
|
+
backgroundTaskId,
|
|
87260
|
+
error: err instanceof Error ? err.message : String(err)
|
|
87261
|
+
});
|
|
87262
|
+
});
|
|
87263
|
+
},
|
|
86577
87264
|
...buildScheduleCallbacks(daemon, () => controlHandler, logAdapter),
|
|
86578
87265
|
...buildTemplateCallbacks(daemon, () => controlHandler, logAdapter),
|
|
86579
|
-
...todoHandlers
|
|
87266
|
+
...todoHandlers,
|
|
87267
|
+
onRequestRecentLogs: (windowMinutes) => {
|
|
87268
|
+
const logDir = `${deps?.shipyardHome ?? getShipyardHome()}/logs`;
|
|
87269
|
+
readRecentLogs(logDir, windowMinutes).then((result) => {
|
|
87270
|
+
handler.sendControl({ type: "recent_logs", ...result });
|
|
87271
|
+
logAdapter({
|
|
87272
|
+
event: "recent_logs_sent",
|
|
87273
|
+
lineCount: result.lines.length,
|
|
87274
|
+
truncated: result.truncated
|
|
87275
|
+
});
|
|
87276
|
+
}).catch((err) => {
|
|
87277
|
+
handler.sendControl({ type: "recent_logs", lines: [], truncated: false });
|
|
87278
|
+
logAdapter({
|
|
87279
|
+
event: "recent_logs_failed",
|
|
87280
|
+
error: err instanceof Error ? err.message : String(err)
|
|
87281
|
+
});
|
|
87282
|
+
});
|
|
87283
|
+
},
|
|
87284
|
+
onPresenceUpdate: (state) => {
|
|
87285
|
+
if (!deps?.presencePool) return;
|
|
87286
|
+
const identity = deps.peerIdentity ?? resolveIdentityFromRegistry(deps);
|
|
87287
|
+
if (!identity) return;
|
|
87288
|
+
const ref = deps.presencePool;
|
|
87289
|
+
ref.pool = applyPresenceUpdate(ref.pool, controlPeerId, identity, state);
|
|
87290
|
+
daemon.taskManager.broadcastControl({ type: "presence_state", peers: ref.pool });
|
|
87291
|
+
}
|
|
86580
87292
|
},
|
|
86581
87293
|
logAdapter
|
|
86582
87294
|
);
|
|
@@ -86699,6 +87411,9 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86699
87411
|
const userSettingsUnsub = daemon.userSettingsStore.subscribe((settings) => {
|
|
86700
87412
|
controlHandler.sendControl({ type: "user_settings_updated", settings });
|
|
86701
87413
|
});
|
|
87414
|
+
if (deps?.presencePool) {
|
|
87415
|
+
controlHandler.sendControl({ type: "presence_state", peers: deps.presencePool.pool });
|
|
87416
|
+
}
|
|
86702
87417
|
const enforcedTaskId = deps?.collabTaskId;
|
|
86703
87418
|
dc.onmessage = (ev) => {
|
|
86704
87419
|
const raw = typeof ev.data === "string" ? ev.data : String(ev.data);
|
|
@@ -86725,6 +87440,11 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
86725
87440
|
handler.dispose();
|
|
86726
87441
|
daemon.taskManager.unregisterControlChannel(controlPeerId);
|
|
86727
87442
|
daemon.taskManager.setOnAuthNotLoggedIn(null);
|
|
87443
|
+
if (deps?.presencePool) {
|
|
87444
|
+
const ref = deps.presencePool;
|
|
87445
|
+
ref.pool = removePresence(ref.pool, controlPeerId);
|
|
87446
|
+
daemon.taskManager.broadcastControl({ type: "presence_state", peers: ref.pool });
|
|
87447
|
+
}
|
|
86728
87448
|
};
|
|
86729
87449
|
logAdapter({
|
|
86730
87450
|
event: "control_channel_wired",
|
|
@@ -86952,6 +87672,77 @@ function handleMessageChannel(opts) {
|
|
|
86952
87672
|
};
|
|
86953
87673
|
}
|
|
86954
87674
|
|
|
87675
|
+
// src/services/collab/collab-repo-manager.ts
|
|
87676
|
+
var DAEMON_IDENTITY2 = { name: "shipyard-daemon", type: "service" };
|
|
87677
|
+
var COLLAB_VISIBLE_PREFIXES = /* @__PURE__ */ new Set(["plan", "canvas"]);
|
|
87678
|
+
var COLLAB_MUTABLE_PREFIXES = {
|
|
87679
|
+
"collaborator-full": /* @__PURE__ */ new Set(["plan", "canvas"]),
|
|
87680
|
+
"collaborator-review": /* @__PURE__ */ new Set(["plan"])
|
|
87681
|
+
};
|
|
87682
|
+
function planCollabRoomResources(taskId, roomId, epochs) {
|
|
87683
|
+
return {
|
|
87684
|
+
docIds: [buildPlanDocId(taskId, epochs.plan), buildCanvasDocId(taskId, epochs.canvas)],
|
|
87685
|
+
personalBridgeType: `bridge-personal-${roomId}`,
|
|
87686
|
+
collabBridgeType: `bridge-collab-${roomId}`
|
|
87687
|
+
};
|
|
87688
|
+
}
|
|
87689
|
+
function buildCollabRepoPermissions(taskId, peerRoleRegistry) {
|
|
87690
|
+
return {
|
|
87691
|
+
visibility(doc3, peer) {
|
|
87692
|
+
if (peer.channelKind === "storage") return true;
|
|
87693
|
+
if (peer.peerType === "service") return true;
|
|
87694
|
+
const parsed = parseDocumentId(doc3.id);
|
|
87695
|
+
if (!parsed) return false;
|
|
87696
|
+
if (parsed.key !== taskId) return false;
|
|
87697
|
+
return COLLAB_VISIBLE_PREFIXES.has(parsed.prefix);
|
|
87698
|
+
},
|
|
87699
|
+
mutability(doc3, peer) {
|
|
87700
|
+
if (peer.channelKind === "storage") return true;
|
|
87701
|
+
if (peer.peerType === "service") return true;
|
|
87702
|
+
const parsed = parseDocumentId(doc3.id);
|
|
87703
|
+
if (!parsed) return false;
|
|
87704
|
+
if (parsed.key !== taskId) return false;
|
|
87705
|
+
const entry = peerRoleRegistry.getEntry(peer.peerId);
|
|
87706
|
+
if (!entry) return false;
|
|
87707
|
+
const allowed = COLLAB_MUTABLE_PREFIXES[entry.role];
|
|
87708
|
+
return allowed ? allowed.has(parsed.prefix) : false;
|
|
87709
|
+
},
|
|
87710
|
+
creation(_docId, _peer) {
|
|
87711
|
+
return false;
|
|
87712
|
+
},
|
|
87713
|
+
deletion(_doc, _peer) {
|
|
87714
|
+
return false;
|
|
87715
|
+
}
|
|
87716
|
+
};
|
|
87717
|
+
}
|
|
87718
|
+
function createCollabRepo(personalRepo, taskId, roomId, epochs, peerRoleRegistry) {
|
|
87719
|
+
const resources = planCollabRoomResources(taskId, roomId, epochs);
|
|
87720
|
+
const bridge = new Bridge();
|
|
87721
|
+
const personalBridgeAdapter = new BridgeAdapter({
|
|
87722
|
+
adapterType: resources.personalBridgeType,
|
|
87723
|
+
bridge
|
|
87724
|
+
});
|
|
87725
|
+
const collabBridgeAdapter = new BridgeAdapter({
|
|
87726
|
+
adapterType: resources.collabBridgeType,
|
|
87727
|
+
bridge
|
|
87728
|
+
});
|
|
87729
|
+
const collabWebrtcAdapter = new WebRtcDataChannelAdapter();
|
|
87730
|
+
const collabRepo = new Repo({
|
|
87731
|
+
identity: { ...DAEMON_IDENTITY2, name: `collab-room-${roomId}` },
|
|
87732
|
+
adapters: [collabBridgeAdapter, collabWebrtcAdapter],
|
|
87733
|
+
permissions: buildCollabRepoPermissions(taskId, peerRoleRegistry)
|
|
87734
|
+
});
|
|
87735
|
+
personalRepo.addAdapter(personalBridgeAdapter);
|
|
87736
|
+
return {
|
|
87737
|
+
repo: collabRepo,
|
|
87738
|
+
webrtcAdapter: collabWebrtcAdapter,
|
|
87739
|
+
async destroy() {
|
|
87740
|
+
personalRepo.removeAdapter(personalBridgeAdapter.adapterId);
|
|
87741
|
+
await collabRepo.shutdown();
|
|
87742
|
+
}
|
|
87743
|
+
};
|
|
87744
|
+
}
|
|
87745
|
+
|
|
86955
87746
|
// src/services/collab/collab-room-manager.ts
|
|
86956
87747
|
function assertNever2(x2) {
|
|
86957
87748
|
throw new Error(`Unhandled message type: ${JSON.stringify(x2)}`);
|
|
@@ -87084,6 +87875,7 @@ function createCollabRoomManager(deps) {
|
|
|
87084
87875
|
for (const userId of room.knownPeers) {
|
|
87085
87876
|
deps.peerRoleRegistry.unregisterPeer(namespacePeerId(roomId, userId));
|
|
87086
87877
|
}
|
|
87878
|
+
room.collabRepoHandle?.destroy();
|
|
87087
87879
|
room.peerManager.destroy();
|
|
87088
87880
|
room.connection.disconnect();
|
|
87089
87881
|
rooms.delete(roomId);
|
|
@@ -87121,7 +87913,19 @@ function createCollabRoomManager(deps) {
|
|
|
87121
87913
|
maxDelayMs: 3e4,
|
|
87122
87914
|
backoffMultiplier: 2
|
|
87123
87915
|
});
|
|
87124
|
-
const
|
|
87916
|
+
const collabRepoHandle = createCollabRepo(
|
|
87917
|
+
deps.personalRepo,
|
|
87918
|
+
taskId,
|
|
87919
|
+
roomId,
|
|
87920
|
+
{ plan: deps.planEpoch, canvas: deps.canvasEpoch },
|
|
87921
|
+
deps.peerRoleRegistry
|
|
87922
|
+
);
|
|
87923
|
+
const peerManager = deps.createPeerManagerForRoom(
|
|
87924
|
+
roomId,
|
|
87925
|
+
connection,
|
|
87926
|
+
taskId,
|
|
87927
|
+
collabRepoHandle.webrtcAdapter
|
|
87928
|
+
);
|
|
87125
87929
|
const handle = {
|
|
87126
87930
|
roomId,
|
|
87127
87931
|
taskId,
|
|
@@ -87132,6 +87936,7 @@ function createCollabRoomManager(deps) {
|
|
|
87132
87936
|
const delayMs = expiresAt - Date.now();
|
|
87133
87937
|
if (delayMs <= 0 || !Number.isFinite(delayMs)) {
|
|
87134
87938
|
deps.log({ event: "collab_expired", roomId, expiresAt });
|
|
87939
|
+
collabRepoHandle.destroy();
|
|
87135
87940
|
return handle;
|
|
87136
87941
|
}
|
|
87137
87942
|
const safeDelayMs = Math.min(delayMs, 2147483647);
|
|
@@ -87140,6 +87945,7 @@ function createCollabRoomManager(deps) {
|
|
|
87140
87945
|
taskId,
|
|
87141
87946
|
connection,
|
|
87142
87947
|
peerManager,
|
|
87948
|
+
collabRepoHandle,
|
|
87143
87949
|
myUserId: null,
|
|
87144
87950
|
knownPeers: /* @__PURE__ */ new Set(),
|
|
87145
87951
|
participants: [],
|
|
@@ -87163,7 +87969,12 @@ function createCollabRoomManager(deps) {
|
|
|
87163
87969
|
deps.peerRoleRegistry.unregisterPeer(namespacePeerId(roomId, userId));
|
|
87164
87970
|
}
|
|
87165
87971
|
room.peerManager.destroy();
|
|
87166
|
-
room.peerManager = deps.createPeerManagerForRoom(
|
|
87972
|
+
room.peerManager = deps.createPeerManagerForRoom(
|
|
87973
|
+
roomId,
|
|
87974
|
+
connection,
|
|
87975
|
+
taskId,
|
|
87976
|
+
room.collabRepoHandle?.webrtcAdapter ?? collabRepoHandle.webrtcAdapter
|
|
87977
|
+
);
|
|
87167
87978
|
room.myUserId = null;
|
|
87168
87979
|
room.knownPeers.clear();
|
|
87169
87980
|
}
|
|
@@ -87204,41 +88015,20 @@ function findEntry(registry, peer) {
|
|
|
87204
88015
|
}
|
|
87205
88016
|
return void 0;
|
|
87206
88017
|
}
|
|
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
88018
|
function buildCollabPermissions(registry) {
|
|
88019
|
+
function isAllowed(_doc, peer) {
|
|
88020
|
+
if (peer.channelKind === "storage") return true;
|
|
88021
|
+
if (peer.peerType === "service") return true;
|
|
88022
|
+
const entry = findEntry(registry, peer);
|
|
88023
|
+
if (!entry) return false;
|
|
88024
|
+
return isPersonalPeer(entry);
|
|
88025
|
+
}
|
|
87217
88026
|
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
|
-
},
|
|
88027
|
+
visibility: isAllowed,
|
|
88028
|
+
mutability: isAllowed,
|
|
87240
88029
|
creation(_docId, peer) {
|
|
87241
88030
|
if (peer.channelKind === "storage") return true;
|
|
88031
|
+
if (peer.peerType === "service") return true;
|
|
87242
88032
|
const entry = findEntry(registry, peer);
|
|
87243
88033
|
if (!entry) return false;
|
|
87244
88034
|
return isPersonalPeer(entry);
|
|
@@ -87298,8 +88088,8 @@ function createPeerRoleRegistry() {
|
|
|
87298
88088
|
}
|
|
87299
88089
|
|
|
87300
88090
|
// src/services/epoch-pruning.ts
|
|
87301
|
-
import { readdir as
|
|
87302
|
-
import { join as
|
|
88091
|
+
import { readdir as readdir8, rm as rm3 } from "fs/promises";
|
|
88092
|
+
import { join as join39 } from "path";
|
|
87303
88093
|
var LEGACY_PREFIXES = [
|
|
87304
88094
|
"task-meta",
|
|
87305
88095
|
"task-conv",
|
|
@@ -87316,7 +88106,7 @@ function isLegacyDocId(decoded) {
|
|
|
87316
88106
|
async function pruneOldEpochData(dataDir, currentEpoch, log) {
|
|
87317
88107
|
let entries;
|
|
87318
88108
|
try {
|
|
87319
|
-
entries = await
|
|
88109
|
+
entries = await readdir8(dataDir, { withFileTypes: true });
|
|
87320
88110
|
} catch {
|
|
87321
88111
|
return;
|
|
87322
88112
|
}
|
|
@@ -87334,7 +88124,7 @@ async function pruneOldEpochData(dataDir, currentEpoch, log) {
|
|
|
87334
88124
|
if (!parsed) {
|
|
87335
88125
|
if (isLegacyDocId(decoded)) {
|
|
87336
88126
|
removals.push(
|
|
87337
|
-
rm3(
|
|
88127
|
+
rm3(join39(dataDir, entry.name), { recursive: true }).then(() => {
|
|
87338
88128
|
pruned++;
|
|
87339
88129
|
}).catch((err) => {
|
|
87340
88130
|
log({
|
|
@@ -87349,7 +88139,7 @@ async function pruneOldEpochData(dataDir, currentEpoch, log) {
|
|
|
87349
88139
|
}
|
|
87350
88140
|
if (parsed.epoch >= currentEpoch) continue;
|
|
87351
88141
|
removals.push(
|
|
87352
|
-
rm3(
|
|
88142
|
+
rm3(join39(dataDir, entry.name), { recursive: true }).then(() => {
|
|
87353
88143
|
pruned++;
|
|
87354
88144
|
}).catch((err) => {
|
|
87355
88145
|
log({
|
|
@@ -87368,7 +88158,7 @@ async function pruneOldEpochData(dataDir, currentEpoch, log) {
|
|
|
87368
88158
|
|
|
87369
88159
|
// src/services/file-io-handler.ts
|
|
87370
88160
|
import { execFile as execFile9, spawn as spawn7 } from "child_process";
|
|
87371
|
-
import { readdir as
|
|
88161
|
+
import { readdir as readdir9, readFile as readFile25, stat as stat5, unlink as unlink6, writeFile as writeFile22 } from "fs/promises";
|
|
87372
88162
|
import { normalize as normalize5, relative as relative2, resolve } from "path";
|
|
87373
88163
|
import { promisify as promisify6 } from "util";
|
|
87374
88164
|
function handleFileIOChannel(initialCwd, send, log, deps) {
|
|
@@ -87532,7 +88322,7 @@ function handleFileIOChannel(initialCwd, send, log, deps) {
|
|
|
87532
88322
|
}
|
|
87533
88323
|
async function handleReaddir(requestId, absPath) {
|
|
87534
88324
|
try {
|
|
87535
|
-
const entries = await
|
|
88325
|
+
const entries = await readdir9(absPath, { withFileTypes: true });
|
|
87536
88326
|
const result = entries.filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => ({
|
|
87537
88327
|
name: e.name,
|
|
87538
88328
|
type: e.isDirectory() ? "directory" : "file"
|
|
@@ -88271,17 +89061,25 @@ function guardLoroChannelSend(dc, log) {
|
|
|
88271
89061
|
const originalSend = dc.send.bind(dc);
|
|
88272
89062
|
let dropCount = 0;
|
|
88273
89063
|
let lastLogAt = 0;
|
|
89064
|
+
const logDrop = (reason) => {
|
|
89065
|
+
dropCount++;
|
|
89066
|
+
const now = Date.now();
|
|
89067
|
+
if (now - lastLogAt >= 1e3) {
|
|
89068
|
+
log.warn({ droppedCount: dropCount, reason }, "Loro sync send dropped");
|
|
89069
|
+
dropCount = 0;
|
|
89070
|
+
lastLogAt = now;
|
|
89071
|
+
}
|
|
89072
|
+
};
|
|
88274
89073
|
dc.send = (data) => {
|
|
89074
|
+
if (dc.readyState !== void 0 && dc.readyState !== "open") return;
|
|
89075
|
+
if (typeof dc.bufferedAmount === "number" && dc.bufferedAmount > LORO_BACKPRESSURE_HIGH_WATER) {
|
|
89076
|
+
logDrop("backpressure");
|
|
89077
|
+
return;
|
|
89078
|
+
}
|
|
88275
89079
|
try {
|
|
88276
89080
|
originalSend(data);
|
|
88277
89081
|
} 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
|
-
}
|
|
89082
|
+
logDrop("send_error");
|
|
88285
89083
|
}
|
|
88286
89084
|
};
|
|
88287
89085
|
}
|
|
@@ -88526,8 +89324,8 @@ function createPeerManager(config2) {
|
|
|
88526
89324
|
|
|
88527
89325
|
// src/services/plugins/plugin-file-watcher.ts
|
|
88528
89326
|
import { existsSync as existsSync6, watch as watch4 } from "fs";
|
|
88529
|
-
import { readdir as
|
|
88530
|
-
import { join as
|
|
89327
|
+
import { readdir as readdir10, readFile as readFile26, stat as stat6 } from "fs/promises";
|
|
89328
|
+
import { join as join40 } from "path";
|
|
88531
89329
|
import { pathToFileURL } from "url";
|
|
88532
89330
|
var DEBOUNCE_MS2 = 500;
|
|
88533
89331
|
var PLUGIN_ID_PATTERN2 = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -88578,14 +89376,14 @@ var PluginFileWatcher = class {
|
|
|
88578
89376
|
}
|
|
88579
89377
|
let entries;
|
|
88580
89378
|
try {
|
|
88581
|
-
entries = await
|
|
89379
|
+
entries = await readdir10(dir);
|
|
88582
89380
|
} catch {
|
|
88583
89381
|
this.#reconcile([]);
|
|
88584
89382
|
return;
|
|
88585
89383
|
}
|
|
88586
89384
|
const loaded = [];
|
|
88587
89385
|
for (const entry of entries) {
|
|
88588
|
-
const pluginDir =
|
|
89386
|
+
const pluginDir = join40(dir, entry);
|
|
88589
89387
|
let stats;
|
|
88590
89388
|
try {
|
|
88591
89389
|
stats = await stat6(pluginDir);
|
|
@@ -88600,7 +89398,7 @@ var PluginFileWatcher = class {
|
|
|
88600
89398
|
this.#reconcile(loaded);
|
|
88601
89399
|
}
|
|
88602
89400
|
async #loadPlugin(id, pluginDir) {
|
|
88603
|
-
const manifestPath =
|
|
89401
|
+
const manifestPath = join40(pluginDir, "plugin.json");
|
|
88604
89402
|
let manifestRaw;
|
|
88605
89403
|
try {
|
|
88606
89404
|
manifestRaw = await readFile26(manifestPath, "utf-8");
|
|
@@ -88627,7 +89425,7 @@ var PluginFileWatcher = class {
|
|
|
88627
89425
|
}
|
|
88628
89426
|
const manifest = parsed.data;
|
|
88629
89427
|
let template = "";
|
|
88630
|
-
const templatePath =
|
|
89428
|
+
const templatePath = join40(pluginDir, "template.html");
|
|
88631
89429
|
try {
|
|
88632
89430
|
template = await readFile26(templatePath, "utf-8");
|
|
88633
89431
|
} catch {
|
|
@@ -88642,7 +89440,7 @@ var PluginFileWatcher = class {
|
|
|
88642
89440
|
};
|
|
88643
89441
|
}
|
|
88644
89442
|
async #loadAndRegisterHandler(id, pluginDir, title) {
|
|
88645
|
-
const handlerPath =
|
|
89443
|
+
const handlerPath = join40(pluginDir, "handler.mjs");
|
|
88646
89444
|
let handlerFn;
|
|
88647
89445
|
try {
|
|
88648
89446
|
const handlerStat = await stat6(handlerPath);
|
|
@@ -88903,28 +89701,14 @@ var HOP_BY_HOP_HEADERS2 = /* @__PURE__ */ new Set([
|
|
|
88903
89701
|
"proxy-authorization",
|
|
88904
89702
|
"proxy-connection",
|
|
88905
89703
|
"te",
|
|
88906
|
-
"host"
|
|
89704
|
+
"host",
|
|
89705
|
+
"accept-encoding"
|
|
88907
89706
|
]);
|
|
88908
89707
|
var IFRAME_BLOCKING_HEADERS2 = /* @__PURE__ */ new Set([
|
|
88909
89708
|
"x-frame-options",
|
|
88910
89709
|
"content-security-policy",
|
|
88911
89710
|
"content-security-policy-report-only"
|
|
88912
89711
|
]);
|
|
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
89712
|
function injectAnnotationBridge(html) {
|
|
88929
89713
|
const headIdx = html.indexOf("<head");
|
|
88930
89714
|
if (headIdx === -1) return ANNOTATION_BRIDGE_TAG + html;
|
|
@@ -88952,10 +89736,13 @@ function collectResponseHeaders(rawHeaders) {
|
|
|
88952
89736
|
}
|
|
88953
89737
|
return result;
|
|
88954
89738
|
}
|
|
89739
|
+
var BLOCKED_PORTS = /* @__PURE__ */ new Set([22, 25, 3306, 5432, 6379, 27017]);
|
|
88955
89740
|
function createPreviewProxy(config2) {
|
|
88956
89741
|
const { log } = config2;
|
|
88957
89742
|
let server = null;
|
|
88958
89743
|
let allocatedPort = null;
|
|
89744
|
+
let currentTargetPort = null;
|
|
89745
|
+
let startPromise = null;
|
|
88959
89746
|
const activeRequests = /* @__PURE__ */ new Set();
|
|
88960
89747
|
const activeSockets = /* @__PURE__ */ new Set();
|
|
88961
89748
|
function proxyRequest(clientReq, clientRes, targetPort) {
|
|
@@ -88963,7 +89750,7 @@ function createPreviewProxy(config2) {
|
|
|
88963
89750
|
headers.host = `localhost:${targetPort}`;
|
|
88964
89751
|
const proxyReq = http2.request(
|
|
88965
89752
|
{
|
|
88966
|
-
hostname: "
|
|
89753
|
+
hostname: "localhost",
|
|
88967
89754
|
port: targetPort,
|
|
88968
89755
|
path: clientReq.url ?? "/",
|
|
88969
89756
|
method: clientReq.method ?? "GET",
|
|
@@ -89041,7 +89828,7 @@ function createPreviewProxy(config2) {
|
|
|
89041
89828
|
headers.connection = "Upgrade";
|
|
89042
89829
|
headers.upgrade = clientReq.headers.upgrade ?? "websocket";
|
|
89043
89830
|
const proxyReq = http2.request({
|
|
89044
|
-
hostname: "
|
|
89831
|
+
hostname: "localhost",
|
|
89045
89832
|
port: targetPort,
|
|
89046
89833
|
path: clientReq.url ?? "/",
|
|
89047
89834
|
method: "GET",
|
|
@@ -89100,21 +89887,45 @@ function createPreviewProxy(config2) {
|
|
|
89100
89887
|
get port() {
|
|
89101
89888
|
return allocatedPort;
|
|
89102
89889
|
},
|
|
89890
|
+
get targetPort() {
|
|
89891
|
+
return currentTargetPort;
|
|
89892
|
+
},
|
|
89893
|
+
retarget(port) {
|
|
89894
|
+
if (port < 1 || port > 65535 || BLOCKED_PORTS.has(port)) {
|
|
89895
|
+
throw new Error(`Port ${port} is not allowed for preview proxy`);
|
|
89896
|
+
}
|
|
89897
|
+
currentTargetPort = port;
|
|
89898
|
+
log({ event: "preview_proxy_retargeted", proxyPort: allocatedPort, targetPort: port });
|
|
89899
|
+
},
|
|
89103
89900
|
async start(target) {
|
|
89901
|
+
if (startPromise) await startPromise;
|
|
89104
89902
|
if (server) {
|
|
89105
89903
|
throw new Error("Preview proxy already started");
|
|
89106
89904
|
}
|
|
89107
|
-
|
|
89905
|
+
if (target.port < 1 || target.port > 65535 || BLOCKED_PORTS.has(target.port)) {
|
|
89906
|
+
throw new Error(`Port ${target.port} is not allowed for preview proxy`);
|
|
89907
|
+
}
|
|
89908
|
+
currentTargetPort = target.port;
|
|
89108
89909
|
const srv = http2.createServer((req, res) => {
|
|
89109
|
-
|
|
89910
|
+
if (!currentTargetPort) {
|
|
89911
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
89912
|
+
res.end("No preview target configured");
|
|
89913
|
+
return;
|
|
89914
|
+
}
|
|
89915
|
+
proxyRequest(req, res, currentTargetPort);
|
|
89110
89916
|
});
|
|
89111
89917
|
server = srv;
|
|
89112
89918
|
srv.on("upgrade", (req, socket, head) => {
|
|
89113
|
-
|
|
89919
|
+
if (!currentTargetPort) {
|
|
89920
|
+
socket.destroy();
|
|
89921
|
+
return;
|
|
89922
|
+
}
|
|
89923
|
+
proxyWebSocketUpgrade(req, socket, head, currentTargetPort);
|
|
89114
89924
|
});
|
|
89115
|
-
|
|
89116
|
-
|
|
89117
|
-
|
|
89925
|
+
startPromise = new Promise((resolve3, reject) => {
|
|
89926
|
+
srv.listen(0, "localhost", () => {
|
|
89927
|
+
const addr = srv.address();
|
|
89928
|
+
allocatedPort = typeof addr === "object" && addr ? addr.port : null;
|
|
89118
89929
|
log({
|
|
89119
89930
|
event: "preview_proxy_started",
|
|
89120
89931
|
proxyPort: allocatedPort,
|
|
@@ -89124,8 +89935,11 @@ function createPreviewProxy(config2) {
|
|
|
89124
89935
|
});
|
|
89125
89936
|
srv.on("error", reject);
|
|
89126
89937
|
});
|
|
89938
|
+
await startPromise;
|
|
89939
|
+
startPromise = null;
|
|
89127
89940
|
},
|
|
89128
89941
|
async stop() {
|
|
89942
|
+
if (startPromise) await startPromise;
|
|
89129
89943
|
for (const req of activeRequests) {
|
|
89130
89944
|
req.destroy();
|
|
89131
89945
|
}
|
|
@@ -89138,6 +89952,7 @@ function createPreviewProxy(config2) {
|
|
|
89138
89952
|
const srv = server;
|
|
89139
89953
|
server = null;
|
|
89140
89954
|
allocatedPort = null;
|
|
89955
|
+
currentTargetPort = null;
|
|
89141
89956
|
await new Promise((resolve3, reject) => {
|
|
89142
89957
|
srv.close((err) => err ? reject(err) : resolve3());
|
|
89143
89958
|
});
|
|
@@ -89150,7 +89965,7 @@ function createPreviewProxy(config2) {
|
|
|
89150
89965
|
// src/services/storage/daemon-settings-store.ts
|
|
89151
89966
|
import { createHash as createHash5 } from "crypto";
|
|
89152
89967
|
import { mkdir as mkdir16, readFile as readFile27, rename as rename15, writeFile as writeFile23 } from "fs/promises";
|
|
89153
|
-
import { join as
|
|
89968
|
+
import { join as join41 } from "path";
|
|
89154
89969
|
var ProjectSettingsSchema = external_exports.object({
|
|
89155
89970
|
disabledMcpServers: external_exports.array(external_exports.string()).optional()
|
|
89156
89971
|
});
|
|
@@ -89158,9 +89973,9 @@ function hashProjectPath(projectPath) {
|
|
|
89158
89973
|
return createHash5("sha256").update(projectPath).digest("hex").slice(0, 16);
|
|
89159
89974
|
}
|
|
89160
89975
|
function buildDaemonSettingsStore(dataDir) {
|
|
89161
|
-
const settingsDir =
|
|
89976
|
+
const settingsDir = join41(dataDir, "settings");
|
|
89162
89977
|
function settingsPath(projectPath) {
|
|
89163
|
-
return
|
|
89978
|
+
return join41(settingsDir, `${hashProjectPath(projectPath)}.json`);
|
|
89164
89979
|
}
|
|
89165
89980
|
async function ensureDir() {
|
|
89166
89981
|
await mkdir16(settingsDir, { recursive: true });
|
|
@@ -89187,12 +90002,12 @@ function buildDaemonSettingsStore(dataDir) {
|
|
|
89187
90002
|
|
|
89188
90003
|
// src/services/storage/plugin-config-store.ts
|
|
89189
90004
|
import { mkdir as mkdir17, readFile as readFile28, rename as rename16, writeFile as writeFile24 } from "fs/promises";
|
|
89190
|
-
import { join as
|
|
90005
|
+
import { join as join42 } from "path";
|
|
89191
90006
|
function buildPluginConfigStore(pluginsDir) {
|
|
89192
90007
|
const cache2 = /* @__PURE__ */ new Map();
|
|
89193
90008
|
const writeQueues = /* @__PURE__ */ new Map();
|
|
89194
90009
|
function configPath(pluginId) {
|
|
89195
|
-
return
|
|
90010
|
+
return join42(pluginsDir, pluginId, "config.json");
|
|
89196
90011
|
}
|
|
89197
90012
|
async function ensureLoaded(pluginId) {
|
|
89198
90013
|
const cached = cache2.get(pluginId);
|
|
@@ -89223,7 +90038,7 @@ function buildPluginConfigStore(pluginsDir) {
|
|
|
89223
90038
|
const next = prev.then(async () => {
|
|
89224
90039
|
const filePath = configPath(pluginId);
|
|
89225
90040
|
const tmpPath = `${filePath}.tmp`;
|
|
89226
|
-
await mkdir17(
|
|
90041
|
+
await mkdir17(join42(pluginsDir, pluginId), { recursive: true });
|
|
89227
90042
|
await writeFile24(tmpPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
89228
90043
|
await rename16(tmpPath, filePath);
|
|
89229
90044
|
});
|
|
@@ -89631,7 +90446,7 @@ function resolveClaudeCodePath(log) {
|
|
|
89631
90446
|
try {
|
|
89632
90447
|
const req = createRequire4(import.meta.url);
|
|
89633
90448
|
const sdkMain = req.resolve("@anthropic-ai/claude-agent-sdk");
|
|
89634
|
-
const p2 =
|
|
90449
|
+
const p2 = join43(dirname14(sdkMain), "cli.js");
|
|
89635
90450
|
if (existsSync7(p2)) return ok("sdk_bundled", p2);
|
|
89636
90451
|
} catch {
|
|
89637
90452
|
}
|
|
@@ -89641,7 +90456,7 @@ function resolveClaudeCodePath(log) {
|
|
|
89641
90456
|
} catch {
|
|
89642
90457
|
}
|
|
89643
90458
|
for (const c of [
|
|
89644
|
-
|
|
90459
|
+
join43(process.env.HOME ?? "", ".local", "bin", "claude"),
|
|
89645
90460
|
"/usr/local/bin/claude"
|
|
89646
90461
|
])
|
|
89647
90462
|
if (existsSync7(c)) return ok("well_known", c);
|
|
@@ -89649,11 +90464,11 @@ function resolveClaudeCodePath(log) {
|
|
|
89649
90464
|
}
|
|
89650
90465
|
async function serve(options = {}) {
|
|
89651
90466
|
const shipyardHome = options.shipyardHome ?? getShipyardHome();
|
|
89652
|
-
const dataDir =
|
|
90467
|
+
const dataDir = join43(shipyardHome, options.isDev ? "data-dev" : "data");
|
|
89653
90468
|
const log = createChildLogger({ mode: "serve" });
|
|
89654
90469
|
const workspaceRoot = findProjectRoot(process.cwd());
|
|
89655
90470
|
registerBuiltinPlugins();
|
|
89656
|
-
const pluginConfigStore = buildPluginConfigStore(
|
|
90471
|
+
const pluginConfigStore = buildPluginConfigStore(join43(dataDir, "plugins"));
|
|
89657
90472
|
await mkdir18(dataDir, { recursive: true });
|
|
89658
90473
|
log.info({ shipyardHome, dataDir, workspaceRoot }, "Starting daemon");
|
|
89659
90474
|
function logAdapter(entry) {
|
|
@@ -89671,13 +90486,14 @@ async function serve(options = {}) {
|
|
|
89671
90486
|
await lifecycle.acquirePidFile(shipyardHome);
|
|
89672
90487
|
const pidTracker = buildPidTracker(shipyardHome);
|
|
89673
90488
|
await pidTracker.sweepOrphans(logAdapter);
|
|
89674
|
-
await pruneOldEpochData(
|
|
89675
|
-
const storage = new FileStorageAdapter(
|
|
89676
|
-
const
|
|
90489
|
+
await pruneOldEpochData(join43(dataDir, "loro"), LEGACY_EPOCH, logAdapter);
|
|
90490
|
+
const storage = new FileStorageAdapter(join43(dataDir, "loro"));
|
|
90491
|
+
const personalWebrtcAdapter = new WebRtcDataChannelAdapter();
|
|
89677
90492
|
const peerRoleRegistry = createPeerRoleRegistry();
|
|
90493
|
+
const presencePoolRef = { pool: {} };
|
|
89678
90494
|
const repo = new Repo({
|
|
89679
90495
|
identity: DAEMON_IDENTITY,
|
|
89680
|
-
adapters: [storage,
|
|
90496
|
+
adapters: [storage, personalWebrtcAdapter],
|
|
89681
90497
|
permissions: buildCollabPermissions(peerRoleRegistry)
|
|
89682
90498
|
});
|
|
89683
90499
|
const resolvedSignalingUrl = env.SHIPYARD_SIGNALING_URL ?? auth3.signalingUrl;
|
|
@@ -89726,7 +90542,7 @@ async function serve(options = {}) {
|
|
|
89726
90542
|
previewProxy
|
|
89727
90543
|
});
|
|
89728
90544
|
daemon.healthMetrics.start();
|
|
89729
|
-
const pluginsDir =
|
|
90545
|
+
const pluginsDir = join43(shipyardHome, "plugins");
|
|
89730
90546
|
await mkdir18(pluginsDir, { recursive: true });
|
|
89731
90547
|
let loadedPlugins = [];
|
|
89732
90548
|
const pluginFileWatcher = new PluginFileWatcher({
|
|
@@ -89806,8 +90622,11 @@ async function serve(options = {}) {
|
|
|
89806
90622
|
const fileIOHandlers = /* @__PURE__ */ new Set();
|
|
89807
90623
|
const collabRoomManager = signalingHandle ? createCollabRoomManager({
|
|
89808
90624
|
peerRoleRegistry,
|
|
89809
|
-
|
|
89810
|
-
|
|
90625
|
+
personalRepo: repo,
|
|
90626
|
+
planEpoch: PLAN_EPOCH,
|
|
90627
|
+
canvasEpoch: CANVAS_EPOCH,
|
|
90628
|
+
createPeerManagerForRoom: (roomId, connection, collabTaskId, collabWebrtcAdapter) => createPeerManager({
|
|
90629
|
+
webrtcAdapter: collabWebrtcAdapter,
|
|
89811
90630
|
log: createChildLogger({ mode: `collab-peer:${roomId}` }),
|
|
89812
90631
|
onAnswer(namespacedId, answer) {
|
|
89813
90632
|
const prefix = `collab:${roomId}:`;
|
|
@@ -89847,7 +90666,9 @@ async function serve(options = {}) {
|
|
|
89847
90666
|
peerRoleRegistry,
|
|
89848
90667
|
peerMachineId: machineId,
|
|
89849
90668
|
collabTaskId,
|
|
89850
|
-
configStore: pluginConfigStore
|
|
90669
|
+
configStore: pluginConfigStore,
|
|
90670
|
+
previewProxy,
|
|
90671
|
+
presencePool: presencePoolRef
|
|
89851
90672
|
});
|
|
89852
90673
|
if (loadedPlugins.length > 0) {
|
|
89853
90674
|
daemon.taskManager.broadcastControl({
|
|
@@ -90086,7 +90907,7 @@ async function serve(options = {}) {
|
|
|
90086
90907
|
);
|
|
90087
90908
|
}
|
|
90088
90909
|
const peerManager = signalingHandle ? createPeerManager({
|
|
90089
|
-
webrtcAdapter,
|
|
90910
|
+
webrtcAdapter: personalWebrtcAdapter,
|
|
90090
90911
|
onAnswer: (targetMachineId, answer) => {
|
|
90091
90912
|
signalingHandle.connection.send({
|
|
90092
90913
|
type: "webrtc-answer",
|
|
@@ -90114,7 +90935,10 @@ async function serve(options = {}) {
|
|
|
90114
90935
|
machineId: signalingHandle.machineId,
|
|
90115
90936
|
signalingBaseUrl: auth3.signalingUrl,
|
|
90116
90937
|
userToken: auth3.token,
|
|
90117
|
-
configStore: pluginConfigStore
|
|
90938
|
+
configStore: pluginConfigStore,
|
|
90939
|
+
previewProxy,
|
|
90940
|
+
presencePool: presencePoolRef,
|
|
90941
|
+
peerIdentity: { userId: auth3.userId, username: auth3.displayName, avatarUrl: null }
|
|
90118
90942
|
});
|
|
90119
90943
|
portDetector.resend();
|
|
90120
90944
|
if (loadedPlugins.length > 0) {
|
|
@@ -90347,8 +91171,24 @@ async function serve(options = {}) {
|
|
|
90347
91171
|
}
|
|
90348
91172
|
portDetector = createPortDetector({
|
|
90349
91173
|
workspaceRoot,
|
|
90350
|
-
onPorts: (ports) => {
|
|
90351
|
-
|
|
91174
|
+
onPorts: async (ports) => {
|
|
91175
|
+
const firstPort = ports[0]?.port;
|
|
91176
|
+
if (firstPort && !previewProxy.port) {
|
|
91177
|
+
try {
|
|
91178
|
+
await previewProxy.start({ port: firstPort });
|
|
91179
|
+
} catch (err) {
|
|
91180
|
+
logAdapter({
|
|
91181
|
+
event: "preview_proxy_auto_start_failed",
|
|
91182
|
+
port: firstPort,
|
|
91183
|
+
error: err instanceof Error ? err.message : String(err)
|
|
91184
|
+
});
|
|
91185
|
+
}
|
|
91186
|
+
}
|
|
91187
|
+
daemon.taskManager.broadcastControl({
|
|
91188
|
+
type: "detected_ports",
|
|
91189
|
+
ports,
|
|
91190
|
+
proxyPort: previewProxy.port ?? void 0
|
|
91191
|
+
});
|
|
90352
91192
|
},
|
|
90353
91193
|
log: logAdapter
|
|
90354
91194
|
});
|
|
@@ -90702,4 +91542,4 @@ export {
|
|
|
90702
91542
|
classifyLogLevel,
|
|
90703
91543
|
serve
|
|
90704
91544
|
};
|
|
90705
|
-
//# sourceMappingURL=serve-
|
|
91545
|
+
//# sourceMappingURL=serve-KBBJR56G.js.map
|