@schoolai/shipyard 3.2.0-rc.20260420.1 → 3.2.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-MEIJOKPG.js → chunk-5SJBSLGT.js} +2 -2
- package/dist/{chunk-MEIJOKPG.js.map → chunk-5SJBSLGT.js.map} +1 -1
- package/dist/{chunk-DOQSL26C.js → chunk-CANDZLMB.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/{login-PAPHAIJW.js → login-GRM7QIPC.js} +3 -3
- package/dist/{serve-FOWLAISV.js → serve-3EFFP3PN.js} +567 -124
- package/dist/{serve-FOWLAISV.js.map → serve-3EFFP3PN.js.map} +1 -1
- package/dist/{start-QKG6YRUN.js → start-24JXLIQJ.js} +4 -4
- package/package.json +1 -1
- /package/dist/{chunk-DOQSL26C.js.map → chunk-CANDZLMB.js.map} +0 -0
- /package/dist/{login-PAPHAIJW.js.map → login-GRM7QIPC.js.map} +0 -0
- /package/dist/{start-QKG6YRUN.js.map → start-24JXLIQJ.js.map} +0 -0
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
VaultKeyPutRequestSchema,
|
|
47
47
|
VaultKeyPutResponseSchema,
|
|
48
48
|
classifyClaudeCodeCompatibility
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-5SJBSLGT.js";
|
|
50
50
|
import "./chunk-EHQITHQX.js";
|
|
51
51
|
import {
|
|
52
52
|
loadAuthToken
|
|
@@ -25381,7 +25381,7 @@ var FEATURE_WORK = {
|
|
|
25381
25381
|
item(FEATURE_WORK_ID, 1, "Research the task", "Map the surface area before writing code. Identify critical files, existing patterns, edge cases, and gotchas. For larger tasks, spawn parallel Shipyard threads to explore independent sub-areas. Pin key findings on Shipyard's canvas so they stay visible during implementation.", []),
|
|
25382
25382
|
item(FEATURE_WORK_ID, 2, "Plan the approach", "Iterate on an approach on Shipyard's canvas with diagrams, state machines, or data flow so architectural decisions persist through the work. For risky or test-heavy work, write a test spec up front to drive red-green-refactor.", [1]),
|
|
25383
25383
|
item(FEATURE_WORK_ID, 3, "Sequence implementation", "Break the plan into parallelizable subtasks with explicit dependencies wired between them using TaskUpdate's addBlockedBy / addBlocks. Identify what can be done by parallel Shipyard threads versus what must happen sequentially.", [2]),
|
|
25384
|
-
item(FEATURE_WORK_ID, 4, "Implementation", "
|
|
25384
|
+
item(FEATURE_WORK_ID, 4, "Implementation \u2014 REPLACE this placeholder", "This is a placeholder, not the work itself. Before touching code, call TaskCreate to insert 2\u2013N concrete subtasks that describe what you'll actually build (file-by-file if useful). Each new task should be blocked by 'Sequence implementation' and should block 'QA'. Then mark this placeholder completed. Shipping without doing this means the work never appeared in the plan.", [3]),
|
|
25385
25385
|
item(FEATURE_WORK_ID, 5, "QA", "Multi-pass review and fix until convergence. Parallel reviewers catch different issue classes: type holes, missed edge cases, race conditions, pattern drift. Use Shipyard's add_comment on diff hunks where reviewer judgment is useful. Keep running passes until no new issues surface.", [4]),
|
|
25386
25386
|
item(FEATURE_WORK_ID, 6, "Ship", `Commit the work with a meaningful message, open a PR whose description explains the "why," push, and verify CI passes. Check pre-commit hooks and formatters run clean. Open the result in Shipyard's PR panel.`, [5]),
|
|
25387
25387
|
item(FEATURE_WORK_ID, 7, "Tend PR", "Monitor CI checks and reviewer feedback in Shipyard's PR panel; fix failures and respond to each comment even if no code change is needed. Shepherd through merge.", [6])
|
|
@@ -25397,7 +25397,7 @@ var QUICK_TASK = {
|
|
|
25397
25397
|
items: [
|
|
25398
25398
|
item(QUICK_TASK_ID, 1, "Research the task", "Lightweight investigation: read the relevant files, understand the surrounding code, check for similar prior changes. Do not go deep unless the scope warrants it.", []),
|
|
25399
25399
|
item(QUICK_TASK_ID, 2, "Sequence implementation", "Break into concrete subtasks with dependencies via TaskCreate and TaskUpdate's addBlockedBy. Even for small work, explicit steps help catch missed pieces.", [1]),
|
|
25400
|
-
item(QUICK_TASK_ID, 3, "Implementation", "
|
|
25400
|
+
item(QUICK_TASK_ID, 3, "Implementation \u2014 REPLACE this placeholder", "This is a placeholder, not the work itself. Call TaskCreate to insert the real subtasks describing what you'll actually build. Each new task should be blocked by 'Sequence implementation' and block 'QA'. Mark this placeholder completed once replaced.", [2]),
|
|
25401
25401
|
item(QUICK_TASK_ID, 4, "QA", "Quick review pass: type safety, pattern consistency, obvious issues. Use add_comment on hunks where feedback is needed. Skip multi-pass convergence if the change is trivial.", [3]),
|
|
25402
25402
|
item(QUICK_TASK_ID, 5, "Ship", "Commit, open PR, push, verify CI. Open the result in Shipyard's PR panel.", [4]),
|
|
25403
25403
|
item(QUICK_TASK_ID, 6, "Tend PR", "Monitor and shepherd to merge via Shipyard's PR panel. Respond to reviewer comments promptly.", [5])
|
|
@@ -25426,7 +25426,7 @@ var PARALLEL_REFACTOR = {
|
|
|
25426
25426
|
description: "Refactor work that can fan out into independent parallel groups. Map the scope, spawn threads, integrate, and ship as one coherent PR (or stacked PRs).",
|
|
25427
25427
|
items: [
|
|
25428
25428
|
item(PARALLEL_REFACTOR_ID, 1, "Map the scope", "Understand which files, patterns, and call sites the refactor touches. Sketch group boundaries on Shipyard's canvas so parallel workers see the same source of truth. Each group should be self-contained \u2014 no cross-group file modifications.", []),
|
|
25429
|
-
item(PARALLEL_REFACTOR_ID, 2, "Parallel implementation", "
|
|
25429
|
+
item(PARALLEL_REFACTOR_ID, 2, "Parallel implementation \u2014 REPLACE with 2\u20134 parallel groups", "Call TaskCreate 2\u20134 times to insert independent parallel groups. Each group runs on its own thread via the Agent tool. Groups blocked by 'Map the scope', all block 'Integration pass'. Delete this placeholder after inserting the real work.", [1]),
|
|
25430
25430
|
item(PARALLEL_REFACTOR_ID, 3, "Integration pass", "Resolve cross-group issues: type errors at interfaces, import ordering, pattern consistency. Rerun type checks across the whole codebase. Track any surfaced issues on Shipyard's canvas.", [2]),
|
|
25431
25431
|
item(PARALLEL_REFACTOR_ID, 4, "QA", "Multi-pass review with add_comment annotations. Refactors have high regression risk; extra scrutiny on callers of changed signatures.", [3]),
|
|
25432
25432
|
item(PARALLEL_REFACTOR_ID, 5, "Ship", "Commit per group or per theme. Stack if the refactor is large enough to warrant separate reviewable units. Open in Shipyard's PR panel.", [4]),
|
|
@@ -27288,7 +27288,7 @@ var TaskRecordSchema = external_exports.object({
|
|
|
27288
27288
|
lastActivityAt: external_exports.number().default(0),
|
|
27289
27289
|
appliedTemplateId: external_exports.string().optional()
|
|
27290
27290
|
});
|
|
27291
|
-
var TASK_STORE_VERSION =
|
|
27291
|
+
var TASK_STORE_VERSION = 10;
|
|
27292
27292
|
var TaskStoreSchema = external_exports.object({
|
|
27293
27293
|
schemaVersion: external_exports.number(),
|
|
27294
27294
|
tasks: external_exports.record(external_exports.string(), TaskRecordSchema)
|
|
@@ -27302,8 +27302,26 @@ function migrateTaskStore(raw) {
|
|
|
27302
27302
|
if (version < 5) {
|
|
27303
27303
|
return { schemaVersion: TASK_STORE_VERSION, tasks: {} };
|
|
27304
27304
|
}
|
|
27305
|
+
if (version < 10) {
|
|
27306
|
+
backfillLastActivityAt(prop(raw, "tasks"));
|
|
27307
|
+
}
|
|
27305
27308
|
return TaskStoreSchema.parse({ ...raw, schemaVersion: TASK_STORE_VERSION });
|
|
27306
27309
|
}
|
|
27310
|
+
function backfillLastActivityAt(tasksRaw) {
|
|
27311
|
+
if (typeof tasksRaw !== "object" || tasksRaw === null)
|
|
27312
|
+
return;
|
|
27313
|
+
for (const record of Object.values(tasksRaw)) {
|
|
27314
|
+
if (typeof record !== "object" || record === null)
|
|
27315
|
+
continue;
|
|
27316
|
+
const existing = prop(record, "lastActivityAt");
|
|
27317
|
+
if (typeof existing === "number" && existing !== 0)
|
|
27318
|
+
continue;
|
|
27319
|
+
const updated = prop(record, "updatedAt");
|
|
27320
|
+
const created = prop(record, "createdAt");
|
|
27321
|
+
const fallback = (typeof updated === "number" ? updated : 0) || (typeof created === "number" ? created : 0) || 0;
|
|
27322
|
+
Object.assign(record, { lastActivityAt: fallback });
|
|
27323
|
+
}
|
|
27324
|
+
}
|
|
27307
27325
|
var TemplateItemSchema = external_exports.object({
|
|
27308
27326
|
id: external_exports.string(),
|
|
27309
27327
|
content: external_exports.string(),
|
|
@@ -27656,7 +27674,8 @@ var PermissionRequestPayloadSchema = external_exports.object({
|
|
|
27656
27674
|
description: external_exports.string().optional(),
|
|
27657
27675
|
agentId: external_exports.string(),
|
|
27658
27676
|
ownerDisplayName: external_exports.string().optional(),
|
|
27659
|
-
threadId: external_exports.string().optional()
|
|
27677
|
+
threadId: external_exports.string().optional(),
|
|
27678
|
+
targetParticipantId: external_exports.string().optional()
|
|
27660
27679
|
});
|
|
27661
27680
|
var CapabilitiesPayloadSchema = external_exports.object({
|
|
27662
27681
|
models: external_exports.array(external_exports.object({
|
|
@@ -27838,7 +27857,8 @@ var BrowserToDaemonControlMessageSchema = external_exports.discriminatedUnion("t
|
|
|
27838
27857
|
channelId: external_exports.string(),
|
|
27839
27858
|
title: external_exports.string(),
|
|
27840
27859
|
cwd: external_exports.string(),
|
|
27841
|
-
mode: external_exports.enum(taskModes).default("task")
|
|
27860
|
+
mode: external_exports.enum(taskModes).default("task"),
|
|
27861
|
+
templateId: external_exports.string().optional()
|
|
27842
27862
|
}),
|
|
27843
27863
|
external_exports.object({
|
|
27844
27864
|
type: external_exports.literal("promote_task"),
|
|
@@ -28030,6 +28050,13 @@ var RateLimitInfoSchema = external_exports.object({
|
|
|
28030
28050
|
});
|
|
28031
28051
|
var DaemonToBrowserControlMessageSchema = external_exports.discriminatedUnion("type", [
|
|
28032
28052
|
external_exports.object({ type: external_exports.literal("permission_request") }).merge(PermissionRequestPayloadSchema),
|
|
28053
|
+
external_exports.object({
|
|
28054
|
+
type: external_exports.literal("permission_resolved"),
|
|
28055
|
+
taskId: external_exports.string(),
|
|
28056
|
+
toolUseId: external_exports.string(),
|
|
28057
|
+
decision: PermissionDecisionSchema,
|
|
28058
|
+
threadId: external_exports.string().optional()
|
|
28059
|
+
}),
|
|
28033
28060
|
external_exports.object({ type: external_exports.literal("capabilities"), capabilities: CapabilitiesPayloadSchema }),
|
|
28034
28061
|
external_exports.object({
|
|
28035
28062
|
type: external_exports.literal("rate_limit_info"),
|
|
@@ -31542,6 +31569,15 @@ function humanParticipantId(userId) {
|
|
|
31542
31569
|
}
|
|
31543
31570
|
function createPeerRoleRegistry() {
|
|
31544
31571
|
const peers = /* @__PURE__ */ new Map();
|
|
31572
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
31573
|
+
function notify(peerId) {
|
|
31574
|
+
for (const listener of listeners) {
|
|
31575
|
+
try {
|
|
31576
|
+
listener(peerId);
|
|
31577
|
+
} catch {
|
|
31578
|
+
}
|
|
31579
|
+
}
|
|
31580
|
+
}
|
|
31545
31581
|
return {
|
|
31546
31582
|
registerPersonalPeer(machineId, userId, displayName) {
|
|
31547
31583
|
peers.set(machineId, {
|
|
@@ -31551,6 +31587,7 @@ function createPeerRoleRegistry() {
|
|
|
31551
31587
|
displayName,
|
|
31552
31588
|
taskId: null
|
|
31553
31589
|
});
|
|
31590
|
+
notify(machineId);
|
|
31554
31591
|
},
|
|
31555
31592
|
registerCollabPeer(peerId, role, userId, displayName, taskId) {
|
|
31556
31593
|
peers.set(peerId, {
|
|
@@ -31560,6 +31597,7 @@ function createPeerRoleRegistry() {
|
|
|
31560
31597
|
displayName,
|
|
31561
31598
|
taskId
|
|
31562
31599
|
});
|
|
31600
|
+
notify(peerId);
|
|
31563
31601
|
},
|
|
31564
31602
|
unregisterPeer(peerId) {
|
|
31565
31603
|
peers.delete(peerId);
|
|
@@ -31580,6 +31618,15 @@ function createPeerRoleRegistry() {
|
|
|
31580
31618
|
const entry = peers.get(peerId);
|
|
31581
31619
|
if (entry) return entry.participantId;
|
|
31582
31620
|
return humanParticipantId("unknown");
|
|
31621
|
+
},
|
|
31622
|
+
isRegistered(peerId) {
|
|
31623
|
+
return peers.has(peerId);
|
|
31624
|
+
},
|
|
31625
|
+
onRegister(listener) {
|
|
31626
|
+
listeners.add(listener);
|
|
31627
|
+
return () => {
|
|
31628
|
+
listeners.delete(listener);
|
|
31629
|
+
};
|
|
31583
31630
|
}
|
|
31584
31631
|
};
|
|
31585
31632
|
}
|
|
@@ -46954,6 +47001,14 @@ var VisualizationFileWatcher = class {
|
|
|
46954
47001
|
content = "";
|
|
46955
47002
|
}
|
|
46956
47003
|
}
|
|
47004
|
+
this.#sendControlMessage?.({
|
|
47005
|
+
type: "viz_content",
|
|
47006
|
+
taskId: effect.taskId,
|
|
47007
|
+
slug: effect.slug,
|
|
47008
|
+
vizType: effect.vizType,
|
|
47009
|
+
title: effect.title,
|
|
47010
|
+
content
|
|
47011
|
+
});
|
|
46957
47012
|
const data = { slug: effect.slug, title: effect.title };
|
|
46958
47013
|
const nodeId = this.#deps.canvasRepo.createElement(effect.taskId, {
|
|
46959
47014
|
type: effect.vizType,
|
|
@@ -46964,14 +47019,6 @@ var VisualizationFileWatcher = class {
|
|
|
46964
47019
|
zIndex: position.zIndex,
|
|
46965
47020
|
data
|
|
46966
47021
|
});
|
|
46967
|
-
this.#sendControlMessage?.({
|
|
46968
|
-
type: "viz_content",
|
|
46969
|
-
taskId: effect.taskId,
|
|
46970
|
-
slug: effect.slug,
|
|
46971
|
-
vizType: effect.vizType,
|
|
46972
|
-
title: effect.title,
|
|
46973
|
-
content
|
|
46974
|
-
});
|
|
46975
47022
|
const followUp = this.registry.setCanvasElementId(effect.slug, `${nodeId}`);
|
|
46976
47023
|
await this.executeEffects(followUp);
|
|
46977
47024
|
}
|
|
@@ -47165,6 +47212,142 @@ var VisualizationRegistry = class {
|
|
|
47165
47212
|
}
|
|
47166
47213
|
};
|
|
47167
47214
|
|
|
47215
|
+
// src/services/loro-awareness-sampler.ts
|
|
47216
|
+
var DEFAULT_SAMPLE_INTERVAL_MS = 6e4;
|
|
47217
|
+
function planAwarenessSnapshot(inputs) {
|
|
47218
|
+
const events = [];
|
|
47219
|
+
for (const doc3 of inputs.docs) {
|
|
47220
|
+
const ourVersionBytes = safeEncodeVV(doc3.currentVersion);
|
|
47221
|
+
if (ourVersionBytes === null) continue;
|
|
47222
|
+
const ourVersion = bytesToBase64(ourVersionBytes);
|
|
47223
|
+
for (const peer of inputs.peers) {
|
|
47224
|
+
const awareness = peer.docSyncStates.get(doc3.docId);
|
|
47225
|
+
if (!awareness) continue;
|
|
47226
|
+
if (awareness.status !== "synced") continue;
|
|
47227
|
+
const theirVersion = awareness.lastKnownVersion;
|
|
47228
|
+
const theirBytes = safeEncodeVV(theirVersion);
|
|
47229
|
+
const theirCachedVersion = theirBytes === null ? null : bytesToBase64(theirBytes);
|
|
47230
|
+
const matchStatus = computeMatchStatus(doc3.currentVersion, theirVersion);
|
|
47231
|
+
events.push({
|
|
47232
|
+
event: "loro_awareness_snapshot",
|
|
47233
|
+
docId: doc3.docId,
|
|
47234
|
+
ourVersion,
|
|
47235
|
+
peerId: String(peer.peerId),
|
|
47236
|
+
theirCachedVersion,
|
|
47237
|
+
matchStatus
|
|
47238
|
+
});
|
|
47239
|
+
}
|
|
47240
|
+
}
|
|
47241
|
+
return events;
|
|
47242
|
+
}
|
|
47243
|
+
function computeMatchStatus(ours, theirs) {
|
|
47244
|
+
try {
|
|
47245
|
+
const cmp2 = ours.compare(theirs);
|
|
47246
|
+
if (cmp2 === void 0) return "concurrent";
|
|
47247
|
+
if (cmp2 === 0) return "equal";
|
|
47248
|
+
if (cmp2 === 1) return "we-ahead";
|
|
47249
|
+
if (cmp2 === -1) return "they-ahead";
|
|
47250
|
+
return "unknown";
|
|
47251
|
+
} catch {
|
|
47252
|
+
return "unknown";
|
|
47253
|
+
}
|
|
47254
|
+
}
|
|
47255
|
+
function safeEncodeVV(vv) {
|
|
47256
|
+
try {
|
|
47257
|
+
return vv.encode();
|
|
47258
|
+
} catch {
|
|
47259
|
+
return null;
|
|
47260
|
+
}
|
|
47261
|
+
}
|
|
47262
|
+
function bytesToBase64(bytes) {
|
|
47263
|
+
return Buffer.from(bytes).toString("base64");
|
|
47264
|
+
}
|
|
47265
|
+
function createLoroAwarenessSampler(deps) {
|
|
47266
|
+
let timer = null;
|
|
47267
|
+
const intervalMs = deps.intervalMs ?? DEFAULT_SAMPLE_INTERVAL_MS;
|
|
47268
|
+
function sampleOnce() {
|
|
47269
|
+
try {
|
|
47270
|
+
const synchronizer = getSynchronizer(deps.getRepo());
|
|
47271
|
+
if (!synchronizer) return;
|
|
47272
|
+
const peers = gatherPeers(synchronizer);
|
|
47273
|
+
const docs = gatherDocs(synchronizer, deps.listTaskIds(), deps.buildDocIdsForTask, deps.log);
|
|
47274
|
+
const events = planAwarenessSnapshot({ peers, docs });
|
|
47275
|
+
const counts = { equal: 0, "we-ahead": 0, "they-ahead": 0, concurrent: 0, unknown: 0 };
|
|
47276
|
+
for (const event of events) {
|
|
47277
|
+
counts[event.matchStatus] += 1;
|
|
47278
|
+
if (event.matchStatus === "equal") continue;
|
|
47279
|
+
deps.log({ ...event });
|
|
47280
|
+
}
|
|
47281
|
+
deps.log({
|
|
47282
|
+
event: "loro_awareness_summary",
|
|
47283
|
+
docCount: docs.length,
|
|
47284
|
+
peerCount: peers.length,
|
|
47285
|
+
pairsChecked: events.length,
|
|
47286
|
+
equalCount: counts.equal,
|
|
47287
|
+
weAheadCount: counts["we-ahead"],
|
|
47288
|
+
theyAheadCount: counts["they-ahead"],
|
|
47289
|
+
concurrentCount: counts.concurrent,
|
|
47290
|
+
unknownCount: counts.unknown
|
|
47291
|
+
});
|
|
47292
|
+
} catch (err) {
|
|
47293
|
+
deps.log({
|
|
47294
|
+
event: "loro_awareness_sample_failed",
|
|
47295
|
+
error: err instanceof Error ? err.message : String(err)
|
|
47296
|
+
});
|
|
47297
|
+
}
|
|
47298
|
+
}
|
|
47299
|
+
return {
|
|
47300
|
+
start() {
|
|
47301
|
+
if (timer) return;
|
|
47302
|
+
timer = setInterval(sampleOnce, intervalMs);
|
|
47303
|
+
timer.unref?.();
|
|
47304
|
+
},
|
|
47305
|
+
stop() {
|
|
47306
|
+
if (timer) {
|
|
47307
|
+
clearInterval(timer);
|
|
47308
|
+
timer = null;
|
|
47309
|
+
}
|
|
47310
|
+
},
|
|
47311
|
+
sampleOnce
|
|
47312
|
+
};
|
|
47313
|
+
}
|
|
47314
|
+
function safeOplogVersion(doc3) {
|
|
47315
|
+
try {
|
|
47316
|
+
return doc3.oplogVersion();
|
|
47317
|
+
} catch {
|
|
47318
|
+
return null;
|
|
47319
|
+
}
|
|
47320
|
+
}
|
|
47321
|
+
function getSynchronizer(repo) {
|
|
47322
|
+
const sync = repo.synchronizer;
|
|
47323
|
+
if (sync !== null && sync !== void 0 && typeof sync === "object" && "getPeers" in sync && "getDocumentState" in sync && typeof sync.getPeers === "function" && typeof sync.getDocumentState === "function") {
|
|
47324
|
+
return sync;
|
|
47325
|
+
}
|
|
47326
|
+
return null;
|
|
47327
|
+
}
|
|
47328
|
+
function gatherPeers(synchronizer) {
|
|
47329
|
+
return synchronizer.getPeers().map((p2) => ({
|
|
47330
|
+
peerId: p2.identity.peerId,
|
|
47331
|
+
docSyncStates: p2.docSyncStates
|
|
47332
|
+
}));
|
|
47333
|
+
}
|
|
47334
|
+
function gatherDocs(synchronizer, taskIds, buildDocIdsForTask, log) {
|
|
47335
|
+
const docs = [];
|
|
47336
|
+
for (const taskId of taskIds) {
|
|
47337
|
+
for (const docId of buildDocIdsForTask(taskId)) {
|
|
47338
|
+
const docState = synchronizer.getDocumentState(docId);
|
|
47339
|
+
if (!docState) continue;
|
|
47340
|
+
const currentVersion = safeOplogVersion(docState.doc);
|
|
47341
|
+
if (!currentVersion) {
|
|
47342
|
+
log({ event: "loro_awareness_doc_skipped", docId, reason: "oplog_version_unavailable" });
|
|
47343
|
+
continue;
|
|
47344
|
+
}
|
|
47345
|
+
docs.push({ docId, currentVersion });
|
|
47346
|
+
}
|
|
47347
|
+
}
|
|
47348
|
+
return docs;
|
|
47349
|
+
}
|
|
47350
|
+
|
|
47168
47351
|
// src/services/metrics/health-metrics.ts
|
|
47169
47352
|
var HealthMetrics = class {
|
|
47170
47353
|
#taskManager;
|
|
@@ -69481,7 +69664,7 @@ function classifyAbandoned(task, now, thresholdMs) {
|
|
|
69481
69664
|
if (task.abandonedAt != null) return false;
|
|
69482
69665
|
if (task.taskStartedAt == null) return false;
|
|
69483
69666
|
if (TERMINAL_STATUSES.has(task.status)) return false;
|
|
69484
|
-
const idleMs = now -
|
|
69667
|
+
const idleMs = now - task.lastActivityAt;
|
|
69485
69668
|
return idleMs >= thresholdMs;
|
|
69486
69669
|
}
|
|
69487
69670
|
async function runAbandonedSweep(deps) {
|
|
@@ -82066,6 +82249,7 @@ function createCommentEventInjector(deps) {
|
|
|
82066
82249
|
}
|
|
82067
82250
|
|
|
82068
82251
|
// src/services/channels/permission-handler.ts
|
|
82252
|
+
var RESOLVED_REPLAY_LRU_SIZE = 50;
|
|
82069
82253
|
var HIGH_RISK_TOOLS = /* @__PURE__ */ new Set(["Write", "Bash", "Edit", "NotebookEdit"]);
|
|
82070
82254
|
var LOW_RISK_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "TodoRead", "TodoWrite"]);
|
|
82071
82255
|
function classifyToolRisk(toolName, _input) {
|
|
@@ -82076,13 +82260,15 @@ function classifyToolRisk(toolName, _input) {
|
|
|
82076
82260
|
var PermissionHandler = class {
|
|
82077
82261
|
#deps;
|
|
82078
82262
|
#pendingPermissions = /* @__PURE__ */ new Map();
|
|
82263
|
+
/** LRU of recently-resolved permissions. Used for reconnect replay (#2164). */
|
|
82264
|
+
#recentlyResolved = [];
|
|
82079
82265
|
constructor(deps) {
|
|
82080
82266
|
this.#deps = deps;
|
|
82081
82267
|
}
|
|
82082
82268
|
get pendingCount() {
|
|
82083
82269
|
return this.#pendingPermissions.size;
|
|
82084
82270
|
}
|
|
82085
|
-
handlePermissionResponse(toolUseId, decision, opts) {
|
|
82271
|
+
handlePermissionResponse(toolUseId, decision, opts, senderParticipantId) {
|
|
82086
82272
|
const pending = this.#pendingPermissions.get(toolUseId);
|
|
82087
82273
|
if (!pending) {
|
|
82088
82274
|
this.#deps.log({
|
|
@@ -82092,6 +82278,20 @@ var PermissionHandler = class {
|
|
|
82092
82278
|
});
|
|
82093
82279
|
return;
|
|
82094
82280
|
}
|
|
82281
|
+
if (pending.targetParticipantId !== void 0 && senderParticipantId !== void 0 && pending.targetParticipantId !== senderParticipantId) {
|
|
82282
|
+
this.#deps.log({
|
|
82283
|
+
event: "permission_response_wrong_target",
|
|
82284
|
+
taskId: this.#deps.taskId,
|
|
82285
|
+
toolUseId,
|
|
82286
|
+
target: pending.targetParticipantId,
|
|
82287
|
+
sender: senderParticipantId
|
|
82288
|
+
});
|
|
82289
|
+
this.#deps.getSendControlMessage()?.({
|
|
82290
|
+
type: "error",
|
|
82291
|
+
error: `permission_response from "${senderParticipantId}" rejected \u2014 target is "${pending.targetParticipantId}"`
|
|
82292
|
+
});
|
|
82293
|
+
return;
|
|
82294
|
+
}
|
|
82095
82295
|
this.#pendingPermissions.delete(toolUseId);
|
|
82096
82296
|
const durationMs = Date.now() - pending.requestedAt;
|
|
82097
82297
|
this.#deps.log({
|
|
@@ -82108,6 +82308,19 @@ var PermissionHandler = class {
|
|
|
82108
82308
|
durationMs,
|
|
82109
82309
|
riskLevel: pending.riskLevel
|
|
82110
82310
|
});
|
|
82311
|
+
const resolvedMsg = {
|
|
82312
|
+
type: "permission_resolved",
|
|
82313
|
+
taskId: this.#deps.taskId,
|
|
82314
|
+
toolUseId,
|
|
82315
|
+
decision,
|
|
82316
|
+
...this.#deps.threadId !== void 0 && { threadId: this.#deps.threadId }
|
|
82317
|
+
};
|
|
82318
|
+
this.#deps.getSendControlMessage()?.(resolvedMsg);
|
|
82319
|
+
this.#rememberResolved({
|
|
82320
|
+
toolUseId,
|
|
82321
|
+
decision,
|
|
82322
|
+
...this.#deps.threadId !== void 0 && { threadId: this.#deps.threadId }
|
|
82323
|
+
});
|
|
82111
82324
|
switch (decision) {
|
|
82112
82325
|
case "approved":
|
|
82113
82326
|
pending.resolve({
|
|
@@ -82129,6 +82342,12 @@ var PermissionHandler = class {
|
|
|
82129
82342
|
}
|
|
82130
82343
|
}
|
|
82131
82344
|
}
|
|
82345
|
+
#rememberResolved(entry) {
|
|
82346
|
+
this.#recentlyResolved.push(entry);
|
|
82347
|
+
if (this.#recentlyResolved.length > RESOLVED_REPLAY_LRU_SIZE) {
|
|
82348
|
+
this.#recentlyResolved.shift();
|
|
82349
|
+
}
|
|
82350
|
+
}
|
|
82132
82351
|
/**
|
|
82133
82352
|
* Creates a permission request promise for a non-plan tool use.
|
|
82134
82353
|
* Returns a Promise<PermissionResult> that resolves when the browser
|
|
@@ -82161,6 +82380,9 @@ var PermissionHandler = class {
|
|
|
82161
82380
|
},
|
|
82162
82381
|
...this.#deps.threadId !== void 0 && {
|
|
82163
82382
|
threadId: this.#deps.threadId
|
|
82383
|
+
},
|
|
82384
|
+
...this.#deps.ownerParticipantId !== void 0 && {
|
|
82385
|
+
targetParticipantId: this.#deps.ownerParticipantId
|
|
82164
82386
|
}
|
|
82165
82387
|
};
|
|
82166
82388
|
this.#pendingPermissions.set(toolUseId, {
|
|
@@ -82169,7 +82391,10 @@ var PermissionHandler = class {
|
|
|
82169
82391
|
requestedAt,
|
|
82170
82392
|
toolName,
|
|
82171
82393
|
riskLevel,
|
|
82172
|
-
request
|
|
82394
|
+
request,
|
|
82395
|
+
...this.#deps.ownerParticipantId !== void 0 && {
|
|
82396
|
+
targetParticipantId: this.#deps.ownerParticipantId
|
|
82397
|
+
}
|
|
82173
82398
|
});
|
|
82174
82399
|
const send = this.#deps.getSendControlMessage();
|
|
82175
82400
|
if (send) {
|
|
@@ -82229,9 +82454,8 @@ var PermissionHandler = class {
|
|
|
82229
82454
|
* store-writer dedup in apps/web/src/transport/control-channel/control-channel-store-writers.ts.
|
|
82230
82455
|
*/
|
|
82231
82456
|
replayPendingToChannel(send) {
|
|
82232
|
-
if (this.#pendingPermissions.size === 0) return;
|
|
82233
|
-
let sent = 0;
|
|
82234
82457
|
const currentMainAgentId = this.#deps.getAgentParticipantId();
|
|
82458
|
+
let sent = 0;
|
|
82235
82459
|
for (const [toolUseId, pending] of this.#pendingPermissions) {
|
|
82236
82460
|
const request = pending.request.agentId === PENDING_AGENT_PARTICIPANT_ID ? { ...pending.request, agentId: currentMainAgentId } : pending.request;
|
|
82237
82461
|
try {
|
|
@@ -82246,11 +82470,33 @@ var PermissionHandler = class {
|
|
|
82246
82470
|
});
|
|
82247
82471
|
}
|
|
82248
82472
|
}
|
|
82473
|
+
let resolvedSent = 0;
|
|
82474
|
+
for (const entry of this.#recentlyResolved) {
|
|
82475
|
+
try {
|
|
82476
|
+
send({
|
|
82477
|
+
type: "permission_resolved",
|
|
82478
|
+
taskId: this.#deps.taskId,
|
|
82479
|
+
toolUseId: entry.toolUseId,
|
|
82480
|
+
decision: entry.decision,
|
|
82481
|
+
...entry.threadId !== void 0 && { threadId: entry.threadId }
|
|
82482
|
+
});
|
|
82483
|
+
resolvedSent++;
|
|
82484
|
+
} catch (err) {
|
|
82485
|
+
this.#deps.log({
|
|
82486
|
+
event: "permission_resolved_replay_send_failed",
|
|
82487
|
+
taskId: this.#deps.taskId,
|
|
82488
|
+
toolUseId: entry.toolUseId,
|
|
82489
|
+
error: err instanceof Error ? err.message : String(err)
|
|
82490
|
+
});
|
|
82491
|
+
}
|
|
82492
|
+
}
|
|
82249
82493
|
this.#deps.log({
|
|
82250
82494
|
event: "permission_replay",
|
|
82251
82495
|
taskId: this.#deps.taskId,
|
|
82252
82496
|
sent,
|
|
82253
|
-
total: this.#pendingPermissions.size
|
|
82497
|
+
total: this.#pendingPermissions.size,
|
|
82498
|
+
resolvedSent,
|
|
82499
|
+
resolvedTotal: this.#recentlyResolved.length
|
|
82254
82500
|
});
|
|
82255
82501
|
}
|
|
82256
82502
|
flushQueuedMessages(send, queue) {
|
|
@@ -83213,6 +83459,7 @@ var Thread = class {
|
|
|
83213
83459
|
taskId: config2.permissionTaskId ?? config2.threadId,
|
|
83214
83460
|
threadId: config2.permissionThreadId,
|
|
83215
83461
|
ownerDisplayName: config2.ownerDisplayName,
|
|
83462
|
+
ownerParticipantId: config2.ownerParticipantId,
|
|
83216
83463
|
getAgentParticipantId: () => this.#agentParticipantId,
|
|
83217
83464
|
getSubprocess: () => this.#subprocess,
|
|
83218
83465
|
getSendControlMessage: () => this.#sendControlMessage,
|
|
@@ -83400,8 +83647,13 @@ var Thread = class {
|
|
|
83400
83647
|
this.#streamDeltaSinks.delete(sink);
|
|
83401
83648
|
};
|
|
83402
83649
|
}
|
|
83403
|
-
handlePermissionResponse(toolUseId, decision, opts) {
|
|
83404
|
-
this.#permissionHandler.handlePermissionResponse(
|
|
83650
|
+
handlePermissionResponse(toolUseId, decision, opts, senderParticipantId) {
|
|
83651
|
+
this.#permissionHandler.handlePermissionResponse(
|
|
83652
|
+
toolUseId,
|
|
83653
|
+
decision,
|
|
83654
|
+
opts,
|
|
83655
|
+
senderParticipantId
|
|
83656
|
+
);
|
|
83405
83657
|
}
|
|
83406
83658
|
/**
|
|
83407
83659
|
* Wire the canUseTool callback on an adopted (pre-warmed) subprocess.
|
|
@@ -85169,12 +85421,15 @@ function buildPatchMessage(uri, current2, name, patch, now) {
|
|
|
85169
85421
|
resolvedAt: now,
|
|
85170
85422
|
patchContent: patch
|
|
85171
85423
|
};
|
|
85424
|
+
const patchText = uri.startsWith("shipyard://task/") ? `# Task list updated (append-only). Apply this patch to reconstruct current state:
|
|
85425
|
+
|
|
85426
|
+
${patch}` : patch;
|
|
85172
85427
|
return {
|
|
85173
85428
|
kind: "synthetic_update",
|
|
85174
85429
|
content: [
|
|
85175
85430
|
{
|
|
85176
85431
|
type: "resource",
|
|
85177
|
-
resource: { ...current2, text:
|
|
85432
|
+
resource: { ...current2, text: patchText },
|
|
85178
85433
|
_meta: toMetaRecord(meta)
|
|
85179
85434
|
}
|
|
85180
85435
|
],
|
|
@@ -85257,9 +85512,21 @@ function parseTaskResourceUri(uri) {
|
|
|
85257
85512
|
}
|
|
85258
85513
|
return taskId;
|
|
85259
85514
|
}
|
|
85260
|
-
var TASK_LIST_PREAMBLE = `This is your live task list, managed via the TaskCreate, TaskUpdate, TaskList,
|
|
85515
|
+
var TASK_LIST_PREAMBLE = `This is your live task list, managed via the TaskCreate, TaskUpdate, TaskList, TaskGet tools. Mark in_progress when you start, completed when you finish.
|
|
85516
|
+
|
|
85517
|
+
**Append-only.** Tasks once created stay in the list \u2014 status and dependencies can be updated, but items are not removed. New tasks are added via TaskCreate.
|
|
85518
|
+
|
|
85519
|
+
When you see an update diff to this resource, treat it as a state refresh: re-read and adjust your plans if a task was inserted, a status changed, or a dependency edge shifted.
|
|
85261
85520
|
|
|
85262
|
-
This is a suggested workflow, not a rigid script. Merge, split, reorder, or skip steps as the situation demands
|
|
85521
|
+
This is a suggested workflow, not a rigid script. Merge, split, reorder, or skip steps as the situation demands.`;
|
|
85522
|
+
var SKILL_EXAMPLES_BLOCK = `Some of your steps may map well to skills you have available. Examples:
|
|
85523
|
+
|
|
85524
|
+
- A "research" or "investigate" step might map to a research-style skill if you have one (e.g. /deep-research).
|
|
85525
|
+
- A "ship" or "open PR" step might map to a shipping skill (e.g. /ship).
|
|
85526
|
+
- A "review" or "QA" step might map to a review skill (e.g. /qa).
|
|
85527
|
+
|
|
85528
|
+
Use whatever's actually available. If no skill fits a step, leave it unprefixed.`;
|
|
85529
|
+
var BEFORE_YOU_START_BLOCK = `**Before you start:** Look at your available skills. For each item in this list that clearly maps to a skill you have, call TaskUpdate to prefix its title with that slash-command (e.g. a "research" step might become "/deep-research Research the task" if /deep-research is available). Items with no clear skill fit stay unprefixed \u2014 do not force a match. Placeholder items are still replaced per their description. If a step already carries a slash-command prefix, skip it \u2014 this is idempotent.`;
|
|
85263
85530
|
function buildBlocksMap(entries) {
|
|
85264
85531
|
const blocksMap = /* @__PURE__ */ new Map();
|
|
85265
85532
|
for (const task of entries) {
|
|
@@ -85306,6 +85573,12 @@ function formatForAgent(ctx) {
|
|
|
85306
85573
|
}
|
|
85307
85574
|
lines.push(TASK_LIST_PREAMBLE);
|
|
85308
85575
|
lines.push("");
|
|
85576
|
+
if (appliedTemplate) {
|
|
85577
|
+
lines.push(SKILL_EXAMPLES_BLOCK);
|
|
85578
|
+
lines.push("");
|
|
85579
|
+
lines.push(BEFORE_YOU_START_BLOCK);
|
|
85580
|
+
lines.push("");
|
|
85581
|
+
}
|
|
85309
85582
|
if (entries.length === 0) {
|
|
85310
85583
|
lines.push("No tasks yet \u2014 create some with TaskCreate as the work emerges.");
|
|
85311
85584
|
} else {
|
|
@@ -86373,6 +86646,7 @@ var SideThreadRegistry = class {
|
|
|
86373
86646
|
forkPointMessageUuid: params.forkPointMessageUuid
|
|
86374
86647
|
} : { kind: "fresh" },
|
|
86375
86648
|
humanParticipantId: params.humanParticipantId,
|
|
86649
|
+
ownerParticipantId: this.#deps.ownerParticipantId,
|
|
86376
86650
|
permissionTaskId: this.#deps.taskId,
|
|
86377
86651
|
permissionThreadId: params.threadId,
|
|
86378
86652
|
sendControlMessage: params.sendControlMessage,
|
|
@@ -88623,6 +88897,8 @@ var Task = class {
|
|
|
88623
88897
|
#broadcastToAllPeers;
|
|
88624
88898
|
#permissionQueue = [];
|
|
88625
88899
|
#disposed = false;
|
|
88900
|
+
/** One-shot: awaited before the first #handleBeforeSpawn resolves. Cleared after use. */
|
|
88901
|
+
#hydrationPromise;
|
|
88626
88902
|
/** Guard: prevents re-entry when stop() triggers further status callbacks. */
|
|
88627
88903
|
#scheduledAutoCompleted = false;
|
|
88628
88904
|
#explicitlyStopped = false;
|
|
@@ -88672,6 +88948,7 @@ var Task = class {
|
|
|
88672
88948
|
this.#costBaseline = deps.costBaseline;
|
|
88673
88949
|
this.#lastTurnStats = deps.initialTurnStats ?? null;
|
|
88674
88950
|
this.#tokenCountResult = deps.initialTokenCount ?? null;
|
|
88951
|
+
this.#hydrationPromise = deps.hydrationPromise ?? null;
|
|
88675
88952
|
this.#telemetry = new TaskTelemetry({
|
|
88676
88953
|
taskId: deps.taskId,
|
|
88677
88954
|
metricsCollector: deps.metricsCollector,
|
|
@@ -88886,6 +89163,7 @@ var Task = class {
|
|
|
88886
89163
|
spawnMode,
|
|
88887
89164
|
humanParticipantId: deps.humanParticipantId,
|
|
88888
89165
|
ownerDisplayName: deps.ownerDisplayName,
|
|
89166
|
+
ownerParticipantId: deps.ownerParticipantId,
|
|
88889
89167
|
permissionTaskId: deps.taskId,
|
|
88890
89168
|
permissionThreadId: void 0,
|
|
88891
89169
|
sendControlMessage: deps.sendControlMessage,
|
|
@@ -88944,6 +89222,7 @@ var Task = class {
|
|
|
88944
89222
|
this.#sideThreads = new SideThreadRegistry({
|
|
88945
89223
|
taskId: deps.taskId,
|
|
88946
89224
|
dataDir: deps.dataDir,
|
|
89225
|
+
ownerParticipantId: deps.ownerParticipantId,
|
|
88947
89226
|
store: deps.store,
|
|
88948
89227
|
sessionPersistence: deps.sessionPersistence,
|
|
88949
89228
|
spawnSubprocess: wrappedSpawn,
|
|
@@ -89235,8 +89514,8 @@ var Task = class {
|
|
|
89235
89514
|
addStreamDeltaSink(sink) {
|
|
89236
89515
|
return this.#mainThread.addStreamDeltaSink(sink);
|
|
89237
89516
|
}
|
|
89238
|
-
handlePermissionResponse(toolUseId, decision, opts) {
|
|
89239
|
-
this.#mainThread.handlePermissionResponse(toolUseId, decision, opts);
|
|
89517
|
+
handlePermissionResponse(toolUseId, decision, opts, senderParticipantId) {
|
|
89518
|
+
this.#mainThread.handlePermissionResponse(toolUseId, decision, opts, senderParticipantId);
|
|
89240
89519
|
}
|
|
89241
89520
|
handlePlanContinue(toolUseId, decision, feedback, opts) {
|
|
89242
89521
|
this.#planHandler.handlePlanContinue(toolUseId, decision, feedback, opts);
|
|
@@ -89594,6 +89873,10 @@ var Task = class {
|
|
|
89594
89873
|
return arr.shift();
|
|
89595
89874
|
}
|
|
89596
89875
|
async #handleBeforeSpawn(initialContent) {
|
|
89876
|
+
if (this.#hydrationPromise) {
|
|
89877
|
+
await this.#hydrationPromise;
|
|
89878
|
+
this.#hydrationPromise = null;
|
|
89879
|
+
}
|
|
89597
89880
|
await this.#ensureCompactContextFromStore();
|
|
89598
89881
|
if (this.#needsPromotionContext) {
|
|
89599
89882
|
this.#needsPromotionContext = false;
|
|
@@ -90868,7 +91151,7 @@ var TaskManager = class {
|
|
|
90868
91151
|
const task = this.#tasks.get(taskId);
|
|
90869
91152
|
return task?.orchestrator.latestSettings.permissionMode ?? null;
|
|
90870
91153
|
}
|
|
90871
|
-
handlePermissionResponse(taskId, toolUseId, decision, opts, threadId) {
|
|
91154
|
+
handlePermissionResponse(taskId, toolUseId, decision, opts, threadId, senderParticipantId) {
|
|
90872
91155
|
const task = this.#tasks.get(taskId);
|
|
90873
91156
|
if (!task) {
|
|
90874
91157
|
this.#deps.log({ event: "permission_response_no_orchestrator", taskId, toolUseId });
|
|
@@ -90877,7 +91160,7 @@ var TaskManager = class {
|
|
|
90877
91160
|
if (threadId) {
|
|
90878
91161
|
const thread = task.orchestrator.getSideThread(threadId);
|
|
90879
91162
|
if (thread) {
|
|
90880
|
-
thread.handlePermissionResponse(toolUseId, decision, opts);
|
|
91163
|
+
thread.handlePermissionResponse(toolUseId, decision, opts, senderParticipantId);
|
|
90881
91164
|
return;
|
|
90882
91165
|
}
|
|
90883
91166
|
this.#deps.log({
|
|
@@ -90887,7 +91170,7 @@ var TaskManager = class {
|
|
|
90887
91170
|
toolUseId
|
|
90888
91171
|
});
|
|
90889
91172
|
}
|
|
90890
|
-
task.orchestrator.handlePermissionResponse(toolUseId, decision, opts);
|
|
91173
|
+
task.orchestrator.handlePermissionResponse(toolUseId, decision, opts, senderParticipantId);
|
|
90891
91174
|
}
|
|
90892
91175
|
/**
|
|
90893
91176
|
* Request permission for a proxy MCP tool call.
|
|
@@ -90914,7 +91197,38 @@ var TaskManager = class {
|
|
|
90914
91197
|
if (this.#tasks.has(params.taskId)) return;
|
|
90915
91198
|
const cwd = params.cwd;
|
|
90916
91199
|
const mode = params.mode ?? "task";
|
|
90917
|
-
|
|
91200
|
+
const storeAndHydrate = async () => {
|
|
91201
|
+
const template = params.templateId ? await this.#deps.templateStore.get(params.templateId) : null;
|
|
91202
|
+
if (params.templateId && !template) {
|
|
91203
|
+
this.#deps.log({
|
|
91204
|
+
event: "task_template_not_found",
|
|
91205
|
+
taskId: params.taskId,
|
|
91206
|
+
templateId: params.templateId
|
|
91207
|
+
});
|
|
91208
|
+
}
|
|
91209
|
+
const now = Date.now();
|
|
91210
|
+
const initialOverlay = template?.items.map((item2) => ({
|
|
91211
|
+
id: item2.id,
|
|
91212
|
+
subject: item2.content,
|
|
91213
|
+
description: item2.description,
|
|
91214
|
+
status: "pending",
|
|
91215
|
+
blocks: [],
|
|
91216
|
+
blockedBy: item2.deps,
|
|
91217
|
+
createdAt: now,
|
|
91218
|
+
updatedAt: now
|
|
91219
|
+
})) ?? [];
|
|
91220
|
+
await this.#deps.taskStateStore.createTask({
|
|
91221
|
+
...params,
|
|
91222
|
+
cwd,
|
|
91223
|
+
mode,
|
|
91224
|
+
appliedTemplateId: params.templateId,
|
|
91225
|
+
initialOverlay: initialOverlay.length > 0 ? { ...DEFAULT_TASK_OVERLAY, userTasks: initialOverlay } : void 0
|
|
91226
|
+
});
|
|
91227
|
+
if (initialOverlay.length > 0) {
|
|
91228
|
+
this.#deps.notifyTaskResourceChange(params.taskId);
|
|
91229
|
+
}
|
|
91230
|
+
};
|
|
91231
|
+
const hydrationPromise = storeAndHydrate().catch((err) => {
|
|
90918
91232
|
this.#deps.log({
|
|
90919
91233
|
event: "task_state_store_create_failed",
|
|
90920
91234
|
taskId: params.taskId,
|
|
@@ -90929,7 +91243,8 @@ var TaskManager = class {
|
|
|
90929
91243
|
adoptedSubprocess: claim ?? void 0,
|
|
90930
91244
|
cwd,
|
|
90931
91245
|
mode,
|
|
90932
|
-
scheduleId: params.scheduleId
|
|
91246
|
+
scheduleId: params.scheduleId,
|
|
91247
|
+
hydrationPromise
|
|
90933
91248
|
});
|
|
90934
91249
|
this.#tasks.set(params.taskId, {
|
|
90935
91250
|
taskId: params.taskId,
|
|
@@ -91264,6 +91579,7 @@ var TaskManager = class {
|
|
|
91264
91579
|
channelId,
|
|
91265
91580
|
humanParticipantId: buildHumanParticipantId(this.#deps.userId),
|
|
91266
91581
|
ownerDisplayName: this.#deps.displayName,
|
|
91582
|
+
ownerParticipantId: buildHumanParticipantId(this.#deps.userId),
|
|
91267
91583
|
getCollabParticipants: () => this.#deps.getCollabParticipantsForTask(taskId),
|
|
91268
91584
|
epoch: this.#deps.epoch,
|
|
91269
91585
|
store: this.#deps.store,
|
|
@@ -91419,7 +91735,8 @@ var TaskManager = class {
|
|
|
91419
91735
|
adoptedSubprocess: opts?.adoptedSubprocess,
|
|
91420
91736
|
mode: opts?.mode ?? "task",
|
|
91421
91737
|
scheduleId: opts?.scheduleId,
|
|
91422
|
-
collabQueuePersistence: this.#deps.collabQueuePersistence
|
|
91738
|
+
collabQueuePersistence: this.#deps.collabQueuePersistence,
|
|
91739
|
+
hydrationPromise: opts?.hydrationPromise
|
|
91423
91740
|
});
|
|
91424
91741
|
}
|
|
91425
91742
|
};
|
|
@@ -91489,7 +91806,17 @@ function buildTaskStateStore(dataDir) {
|
|
|
91489
91806
|
get version() {
|
|
91490
91807
|
return _version;
|
|
91491
91808
|
},
|
|
91492
|
-
async createTask({
|
|
91809
|
+
async createTask({
|
|
91810
|
+
taskId,
|
|
91811
|
+
channelId,
|
|
91812
|
+
title,
|
|
91813
|
+
cwd,
|
|
91814
|
+
mode,
|
|
91815
|
+
scheduleId,
|
|
91816
|
+
scheduleName,
|
|
91817
|
+
appliedTemplateId,
|
|
91818
|
+
initialOverlay
|
|
91819
|
+
}, options) {
|
|
91493
91820
|
const now = Date.now();
|
|
91494
91821
|
pushBroadcast(options);
|
|
91495
91822
|
await store.set(taskId, {
|
|
@@ -91509,7 +91836,9 @@ function buildTaskStateStore(dataDir) {
|
|
|
91509
91836
|
totalOutputTokens: 0,
|
|
91510
91837
|
mode: mode ?? "task",
|
|
91511
91838
|
...scheduleId ? { scheduleId } : {},
|
|
91512
|
-
...scheduleName ? { scheduleName } : {}
|
|
91839
|
+
...scheduleName ? { scheduleName } : {},
|
|
91840
|
+
...appliedTemplateId ? { appliedTemplateId } : {},
|
|
91841
|
+
...initialOverlay ? { taskOverlay: initialOverlay } : {}
|
|
91513
91842
|
});
|
|
91514
91843
|
},
|
|
91515
91844
|
async updateTaskStatus(taskId, status, options) {
|
|
@@ -91524,10 +91853,20 @@ function buildTaskStateStore(dataDir) {
|
|
|
91524
91853
|
);
|
|
91525
91854
|
},
|
|
91526
91855
|
async updateCwd(taskId, cwd, options) {
|
|
91527
|
-
|
|
91856
|
+
const now = Date.now();
|
|
91857
|
+
await safeUpdate(
|
|
91858
|
+
taskId,
|
|
91859
|
+
(task) => ({ ...task, cwd, updatedAt: now, lastActivityAt: now }),
|
|
91860
|
+
options
|
|
91861
|
+
);
|
|
91528
91862
|
},
|
|
91529
91863
|
async updateMode(taskId, mode, options) {
|
|
91530
|
-
|
|
91864
|
+
const now = Date.now();
|
|
91865
|
+
await safeUpdate(
|
|
91866
|
+
taskId,
|
|
91867
|
+
(task) => ({ ...task, mode, updatedAt: now, lastActivityAt: now }),
|
|
91868
|
+
options
|
|
91869
|
+
);
|
|
91531
91870
|
},
|
|
91532
91871
|
async updateTodoProgress(taskId, progress, options) {
|
|
91533
91872
|
const now = Date.now();
|
|
@@ -91578,8 +91917,7 @@ function buildTaskStateStore(dataDir) {
|
|
|
91578
91917
|
taskId,
|
|
91579
91918
|
(task) => ({
|
|
91580
91919
|
...task,
|
|
91581
|
-
taskOverlay: overlay
|
|
91582
|
-
updatedAt: Date.now()
|
|
91920
|
+
taskOverlay: overlay
|
|
91583
91921
|
}),
|
|
91584
91922
|
options
|
|
91585
91923
|
);
|
|
@@ -91589,8 +91927,7 @@ function buildTaskStateStore(dataDir) {
|
|
|
91589
91927
|
taskId,
|
|
91590
91928
|
(task) => ({
|
|
91591
91929
|
...task,
|
|
91592
|
-
composerSettings: { ...task.composerSettings, ...settings }
|
|
91593
|
-
updatedAt: Date.now()
|
|
91930
|
+
composerSettings: { ...task.composerSettings, ...settings }
|
|
91594
91931
|
}),
|
|
91595
91932
|
options
|
|
91596
91933
|
);
|
|
@@ -91601,8 +91938,7 @@ function buildTaskStateStore(dataDir) {
|
|
|
91601
91938
|
(task) => ({
|
|
91602
91939
|
...task,
|
|
91603
91940
|
totalCostUsd: stats.totalCostUsd,
|
|
91604
|
-
totalOutputTokens: stats.totalOutputTokens
|
|
91605
|
-
updatedAt: Date.now()
|
|
91941
|
+
totalOutputTokens: stats.totalOutputTokens
|
|
91606
91942
|
}),
|
|
91607
91943
|
options
|
|
91608
91944
|
);
|
|
@@ -91612,8 +91948,7 @@ function buildTaskStateStore(dataDir) {
|
|
|
91612
91948
|
taskId,
|
|
91613
91949
|
(task) => ({
|
|
91614
91950
|
...task,
|
|
91615
|
-
lastTurnStats: snapshot
|
|
91616
|
-
updatedAt: Date.now()
|
|
91951
|
+
lastTurnStats: snapshot
|
|
91617
91952
|
}),
|
|
91618
91953
|
options
|
|
91619
91954
|
);
|
|
@@ -91623,8 +91958,7 @@ function buildTaskStateStore(dataDir) {
|
|
|
91623
91958
|
taskId,
|
|
91624
91959
|
(task) => ({
|
|
91625
91960
|
...task,
|
|
91626
|
-
lastTokenCount: snapshot
|
|
91627
|
-
updatedAt: Date.now()
|
|
91961
|
+
lastTokenCount: snapshot
|
|
91628
91962
|
}),
|
|
91629
91963
|
options
|
|
91630
91964
|
);
|
|
@@ -91634,8 +91968,7 @@ function buildTaskStateStore(dataDir) {
|
|
|
91634
91968
|
taskId,
|
|
91635
91969
|
(task) => ({
|
|
91636
91970
|
...task,
|
|
91637
|
-
lastPlanDetection: detection ?? void 0
|
|
91638
|
-
updatedAt: Date.now()
|
|
91971
|
+
lastPlanDetection: detection ?? void 0
|
|
91639
91972
|
}),
|
|
91640
91973
|
options
|
|
91641
91974
|
);
|
|
@@ -91648,8 +91981,7 @@ function buildTaskStateStore(dataDir) {
|
|
|
91648
91981
|
totalCostUsd: 0,
|
|
91649
91982
|
totalOutputTokens: 0,
|
|
91650
91983
|
lastTurnStats: void 0,
|
|
91651
|
-
lastTokenCount: void 0
|
|
91652
|
-
updatedAt: Date.now()
|
|
91984
|
+
lastTokenCount: void 0
|
|
91653
91985
|
}),
|
|
91654
91986
|
options
|
|
91655
91987
|
);
|
|
@@ -91657,21 +91989,21 @@ function buildTaskStateStore(dataDir) {
|
|
|
91657
91989
|
async setPrUrl(taskId, prUrl, options) {
|
|
91658
91990
|
await safeUpdate(
|
|
91659
91991
|
taskId,
|
|
91660
|
-
(task) => task.prUrl === prUrl ? task : { ...task, prUrl
|
|
91992
|
+
(task) => task.prUrl === prUrl ? task : { ...task, prUrl },
|
|
91661
91993
|
options
|
|
91662
91994
|
);
|
|
91663
91995
|
},
|
|
91664
91996
|
async setAppliedTemplateId(taskId, templateId, options) {
|
|
91665
91997
|
await safeUpdate(
|
|
91666
91998
|
taskId,
|
|
91667
|
-
(task) => task.appliedTemplateId === templateId ? task : { ...task, appliedTemplateId: templateId
|
|
91999
|
+
(task) => task.appliedTemplateId === templateId ? task : { ...task, appliedTemplateId: templateId },
|
|
91668
92000
|
options
|
|
91669
92001
|
);
|
|
91670
92002
|
},
|
|
91671
92003
|
async setAbandonedAt(taskId, timestamp, options) {
|
|
91672
92004
|
await safeUpdate(
|
|
91673
92005
|
taskId,
|
|
91674
|
-
(task) => task.abandonedAt != null ? task : { ...task, abandonedAt: timestamp
|
|
92006
|
+
(task) => task.abandonedAt != null ? task : { ...task, abandonedAt: timestamp },
|
|
91675
92007
|
{ broadcast: false, ...options }
|
|
91676
92008
|
);
|
|
91677
92009
|
},
|
|
@@ -93497,6 +93829,7 @@ async function createDaemon(deps) {
|
|
|
93497
93829
|
});
|
|
93498
93830
|
shipyardResolver.addSubResolver("task", taskResourceResolver);
|
|
93499
93831
|
const collabParticipantsRef = { current: () => [] };
|
|
93832
|
+
const templateStoreEarly = buildTemplateStore(deps.dataDir);
|
|
93500
93833
|
const taskManager = new TaskManager({
|
|
93501
93834
|
userId: deps.auth.userId,
|
|
93502
93835
|
displayName: deps.auth.displayName,
|
|
@@ -93524,7 +93857,8 @@ async function createDaemon(deps) {
|
|
|
93524
93857
|
gitCheckpoint,
|
|
93525
93858
|
workspaceRoot: deps.workspaceRoot,
|
|
93526
93859
|
notifyTaskResourceChange: (taskId) => taskResourceResolver.notifyChange(taskId),
|
|
93527
|
-
collabQueuePersistence
|
|
93860
|
+
collabQueuePersistence,
|
|
93861
|
+
templateStore: templateStoreEarly
|
|
93528
93862
|
});
|
|
93529
93863
|
const proxyRef = { current: null };
|
|
93530
93864
|
const preWarmManager = new PreWarmManager({
|
|
@@ -93544,7 +93878,7 @@ async function createDaemon(deps) {
|
|
|
93544
93878
|
logger.error({ err, ...ctx }, "rate-limit store write failed");
|
|
93545
93879
|
}
|
|
93546
93880
|
});
|
|
93547
|
-
const templateStore =
|
|
93881
|
+
const templateStore = templateStoreEarly;
|
|
93548
93882
|
{
|
|
93549
93883
|
const settings = await userSettingsStore.getSettings();
|
|
93550
93884
|
await seedBuiltInTemplates(
|
|
@@ -93794,7 +94128,18 @@ async function createDaemon(deps) {
|
|
|
93794
94128
|
log: (entry) => deps.log(entry)
|
|
93795
94129
|
});
|
|
93796
94130
|
abandonedSweeper.start();
|
|
94131
|
+
const awarenessSampler = createLoroAwarenessSampler({
|
|
94132
|
+
getRepo: () => repo,
|
|
94133
|
+
listTaskIds: () => taskManager.listManagedTasks().map((t) => t.taskId),
|
|
94134
|
+
buildDocIdsForTask: (taskId) => [
|
|
94135
|
+
buildPlanDocId(taskId, PLAN_EPOCH),
|
|
94136
|
+
buildCanvasDocId(taskId, CANVAS_EPOCH)
|
|
94137
|
+
],
|
|
94138
|
+
log: deps.log
|
|
94139
|
+
});
|
|
94140
|
+
awarenessSampler.start();
|
|
93797
94141
|
async function dispose() {
|
|
94142
|
+
awarenessSampler.stop();
|
|
93798
94143
|
abandonedSweeper.stop();
|
|
93799
94144
|
scheduleEvaluator.dispose();
|
|
93800
94145
|
preWarmManager.dispose();
|
|
@@ -93845,6 +94190,7 @@ async function createDaemon(deps) {
|
|
|
93845
94190
|
rateLimitStore,
|
|
93846
94191
|
templateStore,
|
|
93847
94192
|
themeStore,
|
|
94193
|
+
notifyTaskResourceChange: (taskId) => taskResourceResolver.notifyChange(taskId),
|
|
93848
94194
|
setCollabParticipantsProvider: (provider) => {
|
|
93849
94195
|
collabParticipantsRef.current = provider;
|
|
93850
94196
|
},
|
|
@@ -94186,6 +94532,7 @@ function filterOutboundForCollab(msg, collabTaskId) {
|
|
|
94186
94532
|
case "task_state_update":
|
|
94187
94533
|
case "task_removed":
|
|
94188
94534
|
case "permission_request":
|
|
94535
|
+
case "permission_resolved":
|
|
94189
94536
|
case "pr_action_result":
|
|
94190
94537
|
case "plan_detected":
|
|
94191
94538
|
case "plan_continuation_timeout":
|
|
@@ -94514,7 +94861,14 @@ function routeMessage(msg, callbacks, log) {
|
|
|
94514
94861
|
callbacks.onRemoveWorktree(msg.worktreePath);
|
|
94515
94862
|
break;
|
|
94516
94863
|
case "create_task":
|
|
94517
|
-
callbacks.onCreateTask(
|
|
94864
|
+
callbacks.onCreateTask(
|
|
94865
|
+
msg.taskId,
|
|
94866
|
+
msg.channelId,
|
|
94867
|
+
msg.title,
|
|
94868
|
+
msg.cwd,
|
|
94869
|
+
msg.mode,
|
|
94870
|
+
msg.templateId
|
|
94871
|
+
);
|
|
94518
94872
|
break;
|
|
94519
94873
|
case "promote_task":
|
|
94520
94874
|
callbacks.onPromoteTask(msg.taskId);
|
|
@@ -96157,7 +96511,15 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
96157
96511
|
guardedSend,
|
|
96158
96512
|
{
|
|
96159
96513
|
onPermissionResponse: (taskId, toolUseId, decision, opts, threadId) => {
|
|
96160
|
-
|
|
96514
|
+
const senderParticipantId = deps?.peerRoleRegistry && deps?.peerMachineId ? deps.peerRoleRegistry.getParticipantId(deps.peerMachineId) : void 0;
|
|
96515
|
+
daemon.taskManager.handlePermissionResponse(
|
|
96516
|
+
taskId,
|
|
96517
|
+
toolUseId,
|
|
96518
|
+
decision,
|
|
96519
|
+
opts,
|
|
96520
|
+
threadId,
|
|
96521
|
+
senderParticipantId
|
|
96522
|
+
);
|
|
96161
96523
|
},
|
|
96162
96524
|
onUpdateSettings: (settings) => {
|
|
96163
96525
|
if (settings.disabledMcpServers && daemon.capabilities.mcpServers) {
|
|
@@ -96189,9 +96551,26 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
96189
96551
|
logAdapter({ event: "settings_updated", settings });
|
|
96190
96552
|
},
|
|
96191
96553
|
onUpdateTaskSettings: (taskId, settings) => {
|
|
96192
|
-
|
|
96193
|
-
|
|
96194
|
-
|
|
96554
|
+
const enforcedRole = deps?.peerRoleRegistry && deps?.peerMachineId ? deps.peerRoleRegistry.getRole(deps.peerMachineId) : deps?.peerRole;
|
|
96555
|
+
let applied = settings;
|
|
96556
|
+
if (enforcedRole !== void 0 && enforcedRole !== "owner" && settings.permissionMode) {
|
|
96557
|
+
const { permissionMode: _dropped, ...rest } = settings;
|
|
96558
|
+
applied = rest;
|
|
96559
|
+
logAdapter({
|
|
96560
|
+
event: "task_settings_permission_mode_rejected",
|
|
96561
|
+
taskId,
|
|
96562
|
+
role: enforcedRole,
|
|
96563
|
+
requested: settings.permissionMode
|
|
96564
|
+
});
|
|
96565
|
+
handler.sendControl({
|
|
96566
|
+
type: "error",
|
|
96567
|
+
error: "Only the task owner can change permissionMode"
|
|
96568
|
+
});
|
|
96569
|
+
}
|
|
96570
|
+
if (Object.keys(applied).length === 0) return;
|
|
96571
|
+
daemon.taskManager.applyTaskSettings(taskId, applied);
|
|
96572
|
+
handler.sendControl({ type: "settings_ack", settings: applied, taskId });
|
|
96573
|
+
logAdapter({ event: "task_settings_updated", taskId, settings: applied });
|
|
96195
96574
|
},
|
|
96196
96575
|
onRequestCapabilities: () => {
|
|
96197
96576
|
sendCapabilities(handler);
|
|
@@ -96601,8 +96980,8 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
96601
96980
|
const threads = await daemon.taskManager.listThreads(taskId);
|
|
96602
96981
|
controlHandler.sendControl({ type: "thread_list", taskId, threads });
|
|
96603
96982
|
},
|
|
96604
|
-
onCreateTask: (taskId, channelId, title, cwd, mode) => {
|
|
96605
|
-
daemon.taskManager.createTask({ taskId, channelId, title, cwd, mode });
|
|
96983
|
+
onCreateTask: (taskId, channelId, title, cwd, mode, templateId) => {
|
|
96984
|
+
daemon.taskManager.createTask({ taskId, channelId, title, cwd, mode, templateId });
|
|
96606
96985
|
},
|
|
96607
96986
|
onPromoteTask: (taskId) => {
|
|
96608
96987
|
daemon.taskManager.promoteTask(taskId);
|
|
@@ -96736,7 +97115,7 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
|
|
|
96736
97115
|
});
|
|
96737
97116
|
},
|
|
96738
97117
|
onNotifyTemplateApplied: (taskId, templateId) => {
|
|
96739
|
-
daemon.taskStateStore.setAppliedTemplateId(taskId, templateId).catch((err) => {
|
|
97118
|
+
daemon.taskStateStore.setAppliedTemplateId(taskId, templateId).then(() => daemon.notifyTaskResourceChange(taskId)).catch((err) => {
|
|
96740
97119
|
logAdapter({
|
|
96741
97120
|
event: "notify_template_applied_failed",
|
|
96742
97121
|
taskId,
|
|
@@ -99907,73 +100286,137 @@ function buildCollabRoomManager(deps) {
|
|
|
99907
100286
|
log.info("Collab browser preview URL channel wired");
|
|
99908
100287
|
},
|
|
99909
100288
|
onFileIOChannel: (_machineId, rawChannel) => {
|
|
99910
|
-
const
|
|
99911
|
-
|
|
99912
|
-
|
|
99913
|
-
|
|
99914
|
-
|
|
99915
|
-
|
|
100289
|
+
const wireFileIo = () => {
|
|
100290
|
+
const channelState = narrow(rawChannel).readyState;
|
|
100291
|
+
if (channelState === "closed" || channelState === "closing") {
|
|
100292
|
+
log.info("Collab file I/O raw channel closed before peer registered \u2014 skipping wire");
|
|
100293
|
+
return;
|
|
100294
|
+
}
|
|
100295
|
+
const peerRole = peerRoleRegistry.getRole(_machineId);
|
|
100296
|
+
if (peerRole !== "owner" && peerRole !== "collaborator-full") {
|
|
100297
|
+
log.warn(
|
|
100298
|
+
{ role: peerRole },
|
|
100299
|
+
"Collab peer with insufficient role attempted file I/O access"
|
|
100300
|
+
);
|
|
100301
|
+
return;
|
|
100302
|
+
}
|
|
100303
|
+
const dc = narrow(rawChannel);
|
|
100304
|
+
const cwd = daemon.taskManager.getTaskCwd(collabTaskId) ?? workspaceRoot;
|
|
100305
|
+
setupFileIOChannel({
|
|
100306
|
+
dc,
|
|
100307
|
+
cwd,
|
|
100308
|
+
label: "file-io-collab",
|
|
100309
|
+
logAdapter,
|
|
100310
|
+
fileWatcherPool,
|
|
100311
|
+
fileIOHandlers,
|
|
100312
|
+
handlerDeps: {
|
|
100313
|
+
diffTurn: (taskId, turnIndex) => daemon.taskManager.diffTurn(taskId, turnIndex),
|
|
100314
|
+
diffTurnFile: (taskId, turnIndex, path2) => daemon.taskManager.diffTurnFile(taskId, turnIndex, path2),
|
|
100315
|
+
revertTurn: (taskId, turnIndex, mode, filePath) => daemon.taskManager.revertTurn(taskId, turnIndex, mode, filePath),
|
|
100316
|
+
/**
|
|
100317
|
+
* Scope the collab peer's `set_cwd` allowlist to THIS task's
|
|
100318
|
+
* cwd only — not the global set. Without this, a collaborator-full
|
|
100319
|
+
* peer for task X could redirect to task Y's cwd and cross
|
|
100320
|
+
* task-isolation boundaries.
|
|
100321
|
+
*/
|
|
100322
|
+
isAllowedCwd: (abs) => {
|
|
100323
|
+
const taskCwd = daemon.taskManager.getTaskCwd(collabTaskId);
|
|
100324
|
+
if (!taskCwd) return false;
|
|
100325
|
+
return isUnderAllowedRoot2(abs, [taskCwd]);
|
|
100326
|
+
}
|
|
100327
|
+
}
|
|
100328
|
+
});
|
|
100329
|
+
log.info("Collab file I/O channel wired");
|
|
100330
|
+
};
|
|
100331
|
+
if (peerRoleRegistry.isRegistered(_machineId)) {
|
|
100332
|
+
wireFileIo();
|
|
99916
100333
|
return;
|
|
99917
100334
|
}
|
|
99918
|
-
|
|
99919
|
-
|
|
99920
|
-
|
|
99921
|
-
|
|
99922
|
-
|
|
99923
|
-
|
|
99924
|
-
|
|
99925
|
-
|
|
99926
|
-
|
|
99927
|
-
|
|
99928
|
-
|
|
99929
|
-
|
|
99930
|
-
|
|
99931
|
-
/**
|
|
99932
|
-
* Scope the collab peer's `set_cwd` allowlist to THIS task's
|
|
99933
|
-
* cwd only — not the global set. Without this, a collaborator-full
|
|
99934
|
-
* peer for task X could redirect to task Y's cwd and cross
|
|
99935
|
-
* task-isolation boundaries.
|
|
99936
|
-
*/
|
|
99937
|
-
isAllowedCwd: (abs) => {
|
|
99938
|
-
const taskCwd = daemon.taskManager.getTaskCwd(collabTaskId);
|
|
99939
|
-
if (!taskCwd) return false;
|
|
99940
|
-
return isUnderAllowedRoot2(abs, [taskCwd]);
|
|
99941
|
-
}
|
|
99942
|
-
}
|
|
100335
|
+
log.info("Collab file I/O channel open deferred \u2014 peer not yet registered");
|
|
100336
|
+
let done = false;
|
|
100337
|
+
const finish = () => {
|
|
100338
|
+
if (done) return;
|
|
100339
|
+
done = true;
|
|
100340
|
+
clearTimeout(timer);
|
|
100341
|
+
unsubscribe();
|
|
100342
|
+
};
|
|
100343
|
+
const unsubscribe = peerRoleRegistry.onRegister((peerId) => {
|
|
100344
|
+
if (peerId !== _machineId) return;
|
|
100345
|
+
if (done) return;
|
|
100346
|
+
finish();
|
|
100347
|
+
wireFileIo();
|
|
99943
100348
|
});
|
|
99944
|
-
|
|
100349
|
+
const timer = setTimeout(() => {
|
|
100350
|
+
finish();
|
|
100351
|
+
log.warn(
|
|
100352
|
+
{ machineId: _machineId },
|
|
100353
|
+
"Collab file I/O channel buffer expired \u2014 peer never registered"
|
|
100354
|
+
);
|
|
100355
|
+
}, 5e3);
|
|
99945
100356
|
},
|
|
99946
100357
|
onTerminalChannel: (_machineId, rawChannel, taskId, terminalId) => {
|
|
99947
|
-
const
|
|
99948
|
-
|
|
99949
|
-
|
|
99950
|
-
|
|
99951
|
-
|
|
100358
|
+
const wireTerminal = () => {
|
|
100359
|
+
const channelState = narrow(rawChannel).readyState;
|
|
100360
|
+
if (channelState === "closed" || channelState === "closing") {
|
|
100361
|
+
log.info("Collab terminal raw channel closed before peer registered \u2014 skipping wire");
|
|
100362
|
+
return;
|
|
100363
|
+
}
|
|
100364
|
+
const peerRole = peerRoleRegistry.getRole(_machineId);
|
|
100365
|
+
if (peerRole !== "owner" && peerRole !== "collaborator-full") {
|
|
100366
|
+
log.warn(
|
|
100367
|
+
{ role: peerRole },
|
|
100368
|
+
"Collab peer with insufficient role attempted terminal access"
|
|
100369
|
+
);
|
|
100370
|
+
return;
|
|
100371
|
+
}
|
|
100372
|
+
const dc = narrow(rawChannel);
|
|
100373
|
+
const guarded = new GuardedChannel(toGuardedDataChannel5(dc), {
|
|
100374
|
+
label: "terminal-collab",
|
|
100375
|
+
policy: "drop",
|
|
100376
|
+
logAdapter
|
|
100377
|
+
});
|
|
100378
|
+
const handler = handleTerminalChannel(
|
|
100379
|
+
taskId,
|
|
100380
|
+
terminalId,
|
|
100381
|
+
workspaceRoot,
|
|
100382
|
+
(data) => {
|
|
100383
|
+
guarded.send(data);
|
|
100384
|
+
},
|
|
100385
|
+
logAdapter,
|
|
100386
|
+
{ ptys: terminalPtys }
|
|
99952
100387
|
);
|
|
100388
|
+
dc.onmessage = (ev) => handler.onMessage(typeof ev.data === "string" ? ev.data : String(ev.data));
|
|
100389
|
+
dc.onclose = () => {
|
|
100390
|
+
handler.dispose();
|
|
100391
|
+
guarded.dispose();
|
|
100392
|
+
};
|
|
100393
|
+
log.info({ taskId, terminalId }, "Collab terminal channel wired");
|
|
100394
|
+
};
|
|
100395
|
+
if (peerRoleRegistry.isRegistered(_machineId)) {
|
|
100396
|
+
wireTerminal();
|
|
99953
100397
|
return;
|
|
99954
100398
|
}
|
|
99955
|
-
|
|
99956
|
-
|
|
99957
|
-
|
|
99958
|
-
|
|
99959
|
-
|
|
99960
|
-
|
|
99961
|
-
|
|
99962
|
-
taskId,
|
|
99963
|
-
terminalId,
|
|
99964
|
-
workspaceRoot,
|
|
99965
|
-
(data) => {
|
|
99966
|
-
guarded.send(data);
|
|
99967
|
-
},
|
|
99968
|
-
logAdapter,
|
|
99969
|
-
{ ptys: terminalPtys }
|
|
99970
|
-
);
|
|
99971
|
-
dc.onmessage = (ev) => handler.onMessage(typeof ev.data === "string" ? ev.data : String(ev.data));
|
|
99972
|
-
dc.onclose = () => {
|
|
99973
|
-
handler.dispose();
|
|
99974
|
-
guarded.dispose();
|
|
100399
|
+
log.info("Collab terminal channel open deferred \u2014 peer not yet registered");
|
|
100400
|
+
let done = false;
|
|
100401
|
+
const finish = () => {
|
|
100402
|
+
if (done) return;
|
|
100403
|
+
done = true;
|
|
100404
|
+
clearTimeout(timer);
|
|
100405
|
+
unsubscribe();
|
|
99975
100406
|
};
|
|
99976
|
-
|
|
100407
|
+
const unsubscribe = peerRoleRegistry.onRegister((peerId) => {
|
|
100408
|
+
if (peerId !== _machineId) return;
|
|
100409
|
+
if (done) return;
|
|
100410
|
+
finish();
|
|
100411
|
+
wireTerminal();
|
|
100412
|
+
});
|
|
100413
|
+
const timer = setTimeout(() => {
|
|
100414
|
+
finish();
|
|
100415
|
+
log.warn(
|
|
100416
|
+
{ machineId: _machineId },
|
|
100417
|
+
"Collab terminal channel buffer expired \u2014 peer never registered"
|
|
100418
|
+
);
|
|
100419
|
+
}, 5e3);
|
|
99977
100420
|
},
|
|
99978
100421
|
onThreadMessageChannel: (machineId, rawChannel, taskId, threadId) => {
|
|
99979
100422
|
if (!isTaskMessageChannelAllowed(taskId, collabTaskId)) {
|
|
@@ -101605,4 +102048,4 @@ export {
|
|
|
101605
102048
|
_testing,
|
|
101606
102049
|
serve
|
|
101607
102050
|
};
|
|
101608
|
-
//# sourceMappingURL=serve-
|
|
102051
|
+
//# sourceMappingURL=serve-3EFFP3PN.js.map
|