@schoolai/shipyard 3.2.3-rc.20260422.1 → 3.2.3-rc.20260422.2
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/index.js +2 -2
- package/dist/{serve-KNOYKFPQ.js → serve-3FTGY4YL.js} +174 -58
- package/dist/{serve-KNOYKFPQ.js.map → serve-3FTGY4YL.js.map} +1 -1
- package/dist/{start-FONQGWYX.js → start-4KHICMP2.js} +2 -2
- package/package.json +1 -1
- /package/dist/{start-FONQGWYX.js.map → start-4KHICMP2.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -106,7 +106,7 @@ async function handleSubcommand() {
|
|
|
106
106
|
return true;
|
|
107
107
|
}
|
|
108
108
|
if (subcommand === "start") {
|
|
109
|
-
const { startCommand } = await import("./start-
|
|
109
|
+
const { startCommand } = await import("./start-4KHICMP2.js");
|
|
110
110
|
await startCommand();
|
|
111
111
|
return true;
|
|
112
112
|
}
|
|
@@ -123,7 +123,7 @@ async function main() {
|
|
|
123
123
|
const args = parseCliArgs();
|
|
124
124
|
if (args.serve) {
|
|
125
125
|
await loadAuthFromConfig(env);
|
|
126
|
-
const { serve } = await import("./serve-
|
|
126
|
+
const { serve } = await import("./serve-3FTGY4YL.js");
|
|
127
127
|
return serve({ isDev: env.SHIPYARD_DEV });
|
|
128
128
|
}
|
|
129
129
|
logger.error("Use `shipyard start` to run the daemon. Use --help for usage.");
|
|
@@ -87601,6 +87601,47 @@ ${conversationReplay}` : conversationReplay;
|
|
|
87601
87601
|
}
|
|
87602
87602
|
});
|
|
87603
87603
|
}
|
|
87604
|
+
/**
|
|
87605
|
+
* Deduped append for echoed user messages. Browser-originated writes carry
|
|
87606
|
+
* a correlationId so dedup catches the twin row via `corr:<id>`. Synthetic
|
|
87607
|
+
* pushes (plan injection, side threads, model-set) have no correlationId —
|
|
87608
|
+
* we pass `event.sdkUuid` so the `sdk:<uuid>` pass catches SDK replays
|
|
87609
|
+
* instead of falling back to the 60s fingerprint window, which would
|
|
87610
|
+
* wrongly collapse two distinct synthetic pushes with identical content.
|
|
87611
|
+
*/
|
|
87612
|
+
async #handleUserMessageEcho(event) {
|
|
87613
|
+
const echoMsgId = crypto.randomUUID();
|
|
87614
|
+
const meta = this.#callbacks.onBeforeStoreUserMessage?.(event);
|
|
87615
|
+
const result = await this.#config.store.appendMessageDeduped(
|
|
87616
|
+
{
|
|
87617
|
+
channelId: this.#config.channelId,
|
|
87618
|
+
messageId: echoMsgId,
|
|
87619
|
+
participantId: meta?.participantId ?? this.#config.humanParticipantId ?? PENDING_AGENT_PARTICIPANT_ID,
|
|
87620
|
+
senderKind: "human",
|
|
87621
|
+
content: meta?.content ?? event.content,
|
|
87622
|
+
timestamp: Date.now(),
|
|
87623
|
+
model: meta?.model ?? null,
|
|
87624
|
+
reasoningEffort: meta?.reasoningEffort ?? null,
|
|
87625
|
+
permissionMode: meta?.permissionMode ?? null,
|
|
87626
|
+
...meta?.isSynthetic && { isSynthetic: true },
|
|
87627
|
+
...meta?.correlationId && { correlationId: meta.correlationId },
|
|
87628
|
+
...event.sdkUuid && { sdkUuid: event.sdkUuid }
|
|
87629
|
+
},
|
|
87630
|
+
{}
|
|
87631
|
+
);
|
|
87632
|
+
if (result.isDuplicate) {
|
|
87633
|
+
this.#config.log({
|
|
87634
|
+
event: "thread_echo_dedup_hit",
|
|
87635
|
+
threadId: this.#config.threadId,
|
|
87636
|
+
dedupKey: result.dedupKey,
|
|
87637
|
+
seqNo: result.seqNo
|
|
87638
|
+
});
|
|
87639
|
+
}
|
|
87640
|
+
this.#callbacks.onMessageStored?.(result.seqNo, echoMsgId, "human", event.sdkUuid);
|
|
87641
|
+
if (meta?.correlationId) {
|
|
87642
|
+
this.#callbacks.onUserMessageConfirmed?.(meta.correlationId, event.sdkUuid);
|
|
87643
|
+
}
|
|
87644
|
+
}
|
|
87604
87645
|
async #handleSubprocessEvent(event) {
|
|
87605
87646
|
switch (event.type) {
|
|
87606
87647
|
case "init_received":
|
|
@@ -87655,25 +87696,7 @@ ${conversationReplay}` : conversationReplay;
|
|
|
87655
87696
|
case "rate_limit_error":
|
|
87656
87697
|
break;
|
|
87657
87698
|
case "user_message_echo": {
|
|
87658
|
-
|
|
87659
|
-
const meta = this.#callbacks.onBeforeStoreUserMessage?.(event);
|
|
87660
|
-
const echoSeqNo = await this.#config.store.appendMessage({
|
|
87661
|
-
channelId: this.#config.channelId,
|
|
87662
|
-
messageId: echoMsgId,
|
|
87663
|
-
participantId: meta?.participantId ?? this.#config.humanParticipantId ?? PENDING_AGENT_PARTICIPANT_ID,
|
|
87664
|
-
senderKind: "human",
|
|
87665
|
-
content: meta?.content ?? event.content,
|
|
87666
|
-
timestamp: Date.now(),
|
|
87667
|
-
model: meta?.model ?? null,
|
|
87668
|
-
reasoningEffort: meta?.reasoningEffort ?? null,
|
|
87669
|
-
permissionMode: meta?.permissionMode ?? null,
|
|
87670
|
-
...meta?.isSynthetic && { isSynthetic: true },
|
|
87671
|
-
...meta?.correlationId && { correlationId: meta.correlationId }
|
|
87672
|
-
});
|
|
87673
|
-
this.#callbacks.onMessageStored?.(echoSeqNo, echoMsgId, "human", event.sdkUuid);
|
|
87674
|
-
if (meta?.correlationId) {
|
|
87675
|
-
this.#callbacks.onUserMessageConfirmed?.(meta.correlationId, event.sdkUuid);
|
|
87676
|
-
}
|
|
87699
|
+
await this.#handleUserMessageEcho(event);
|
|
87677
87700
|
break;
|
|
87678
87701
|
}
|
|
87679
87702
|
case "assistant_message": {
|
|
@@ -89250,6 +89273,16 @@ var ResourcePushManager = class {
|
|
|
89250
89273
|
#batchTimer;
|
|
89251
89274
|
#pushCountThisTurn = 0;
|
|
89252
89275
|
#pushedThisTurn = /* @__PURE__ */ new Set();
|
|
89276
|
+
/**
|
|
89277
|
+
* Ref-counted session-lifetime claims. Each `markPushed` increments;
|
|
89278
|
+
* each `unmarkPushed` decrements. Entries are deleted when the count
|
|
89279
|
+
* hits zero. Used for "inject exactly once per subprocess session"
|
|
89280
|
+
* callers (the task-list prepend) where two concurrent paths (sync
|
|
89281
|
+
* prepend + async push pipeline) may both legitimately claim the URI.
|
|
89282
|
+
* A plain Set would let an optimistic claim's rollback erase another
|
|
89283
|
+
* caller's successful mark; the ref count prevents that.
|
|
89284
|
+
*/
|
|
89285
|
+
#pushedEverCount = /* @__PURE__ */ new Map();
|
|
89253
89286
|
#disposed = false;
|
|
89254
89287
|
#epoch = 0;
|
|
89255
89288
|
#flushInProgress = Promise.resolve();
|
|
@@ -89302,12 +89335,46 @@ var ResourcePushManager = class {
|
|
|
89302
89335
|
return this.#pushedThisTurn;
|
|
89303
89336
|
}
|
|
89304
89337
|
/**
|
|
89305
|
-
*
|
|
89306
|
-
*
|
|
89307
|
-
|
|
89338
|
+
* Whether a URI has been pushed at least once this session. For one-shot
|
|
89339
|
+
* callers (task-list prepend) to decide whether to inject again.
|
|
89340
|
+
*/
|
|
89341
|
+
isSessionPushed(uri) {
|
|
89342
|
+
return this.#pushedEverCount.has(uri);
|
|
89343
|
+
}
|
|
89344
|
+
/**
|
|
89345
|
+
* Seed the session-lifetime claim for URIs already pushed in a prior
|
|
89346
|
+
* daemon run (resumable_idle tasks whose JSONL already contains a
|
|
89347
|
+
* prepend synthetic). Without this, restart re-injects the task-list
|
|
89348
|
+
* because the fresh push manager has an empty count map.
|
|
89349
|
+
*/
|
|
89350
|
+
seedSessionPushed(uri) {
|
|
89351
|
+
if (!this.#pushedEverCount.has(uri)) this.#pushedEverCount.set(uri, 1);
|
|
89352
|
+
}
|
|
89353
|
+
/**
|
|
89354
|
+
* Mark a URI as pushed this turn AND record a session-lifetime claim.
|
|
89355
|
+
* Call from both the sync prepend (before awaiting resolve) and the
|
|
89356
|
+
* async push pipeline (after a successful write). Pair each call with
|
|
89357
|
+
* `unmarkPushed` only if the operation did NOT ultimately write.
|
|
89308
89358
|
*/
|
|
89309
89359
|
markPushed(uri) {
|
|
89310
89360
|
this.#pushedThisTurn.add(uri);
|
|
89361
|
+
this.#pushedEverCount.set(uri, (this.#pushedEverCount.get(uri) ?? 0) + 1);
|
|
89362
|
+
}
|
|
89363
|
+
/**
|
|
89364
|
+
* Release a `markPushed` claim. Decrements the session-lifetime count —
|
|
89365
|
+
* the entry is only removed when the count hits zero, so a concurrent
|
|
89366
|
+
* caller's successful write stays recorded even if this caller rolls
|
|
89367
|
+
* back. Also clears the per-turn set unconditionally (a successful
|
|
89368
|
+
* parallel caller re-adds it as needed before its own write).
|
|
89369
|
+
*/
|
|
89370
|
+
unmarkPushed(uri) {
|
|
89371
|
+
this.#pushedThisTurn.delete(uri);
|
|
89372
|
+
const current2 = this.#pushedEverCount.get(uri) ?? 0;
|
|
89373
|
+
if (current2 <= 1) {
|
|
89374
|
+
this.#pushedEverCount.delete(uri);
|
|
89375
|
+
} else {
|
|
89376
|
+
this.#pushedEverCount.set(uri, current2 - 1);
|
|
89377
|
+
}
|
|
89311
89378
|
}
|
|
89312
89379
|
/** Permanently shut down. Use reset() for clearSession. */
|
|
89313
89380
|
dispose() {
|
|
@@ -89329,6 +89396,7 @@ var ResourcePushManager = class {
|
|
|
89329
89396
|
this.#subscriptions.clear();
|
|
89330
89397
|
this.#dirtyUris.clear();
|
|
89331
89398
|
this.#pushedThisTurn.clear();
|
|
89399
|
+
this.#pushedEverCount.clear();
|
|
89332
89400
|
this.#pushCountThisTurn = 0;
|
|
89333
89401
|
}
|
|
89334
89402
|
#markDirty(uri) {
|
|
@@ -89439,35 +89507,11 @@ var ResourcePushManager = class {
|
|
|
89439
89507
|
this.#batchTimer.reset();
|
|
89440
89508
|
const batch = this.#drainDirtyBatch();
|
|
89441
89509
|
if (!batch) return;
|
|
89510
|
+
const markedUris = /* @__PURE__ */ new Set();
|
|
89442
89511
|
try {
|
|
89443
|
-
|
|
89444
|
-
if (this.#disposed || this.#epoch !== startEpoch) return;
|
|
89445
|
-
if (resolved.size === 0) {
|
|
89446
|
-
if (this.#dirtyUris.size > 0) this.#batchTimer.schedule();
|
|
89447
|
-
return;
|
|
89448
|
-
}
|
|
89449
|
-
const plan = await this.#planPush(resolved);
|
|
89450
|
-
if (this.#disposed || this.#epoch !== startEpoch || plan.syntheticMessages.length === 0)
|
|
89451
|
-
return;
|
|
89452
|
-
const allContent = await this.#writeSynthetics(plan.syntheticMessages);
|
|
89453
|
-
if (this.#disposed || this.#epoch !== startEpoch) return;
|
|
89454
|
-
this.#deliverToSubprocess(allContent, resolved.size);
|
|
89455
|
-
for (const uri of resolved.keys()) {
|
|
89456
|
-
this.#pushedThisTurn.add(uri);
|
|
89457
|
-
}
|
|
89458
|
-
this.#deps.log({
|
|
89459
|
-
event: "resource_push_delivered",
|
|
89460
|
-
taskId: this.#deps.taskId,
|
|
89461
|
-
uriCount: resolved.size
|
|
89462
|
-
});
|
|
89463
|
-
this.#deps.log({
|
|
89464
|
-
event: "resource_push_flushed",
|
|
89465
|
-
taskId: this.#deps.taskId,
|
|
89466
|
-
uriCount: batch.size,
|
|
89467
|
-
syntheticCount: plan.syntheticMessages.length,
|
|
89468
|
-
pushCountThisTurn: this.#pushCountThisTurn
|
|
89469
|
-
});
|
|
89512
|
+
await this.#executeFlush(batch, startEpoch, markedUris);
|
|
89470
89513
|
} catch (err) {
|
|
89514
|
+
for (const uri of markedUris) this.unmarkPushed(uri);
|
|
89471
89515
|
this.#requeueBatch(batch);
|
|
89472
89516
|
this.#deps.log({
|
|
89473
89517
|
event: "resource_push_flush_error",
|
|
@@ -89477,6 +89521,30 @@ var ResourcePushManager = class {
|
|
|
89477
89521
|
});
|
|
89478
89522
|
}
|
|
89479
89523
|
}
|
|
89524
|
+
async #executeFlush(batch, startEpoch, markedUris) {
|
|
89525
|
+
const resolved = await this.#resolveBatch(batch);
|
|
89526
|
+
if (this.#disposed || this.#epoch !== startEpoch) return;
|
|
89527
|
+
if (resolved.size === 0) {
|
|
89528
|
+
if (this.#dirtyUris.size > 0) this.#batchTimer.schedule();
|
|
89529
|
+
return;
|
|
89530
|
+
}
|
|
89531
|
+
const plan = await this.#planPush(resolved);
|
|
89532
|
+
if (this.#disposed || this.#epoch !== startEpoch || plan.syntheticMessages.length === 0) return;
|
|
89533
|
+
for (const uri of resolved.keys()) {
|
|
89534
|
+
this.markPushed(uri);
|
|
89535
|
+
markedUris.add(uri);
|
|
89536
|
+
}
|
|
89537
|
+
const allContent = await this.#writeSynthetics(plan.syntheticMessages);
|
|
89538
|
+
if (this.#disposed || this.#epoch !== startEpoch) return;
|
|
89539
|
+
this.#deliverToSubprocess(allContent, resolved.size);
|
|
89540
|
+
this.#deps.log({
|
|
89541
|
+
event: "resource_push_flushed",
|
|
89542
|
+
taskId: this.#deps.taskId,
|
|
89543
|
+
uriCount: batch.size,
|
|
89544
|
+
syntheticCount: plan.syntheticMessages.length,
|
|
89545
|
+
pushCountThisTurn: this.#pushCountThisTurn
|
|
89546
|
+
});
|
|
89547
|
+
}
|
|
89480
89548
|
};
|
|
89481
89549
|
|
|
89482
89550
|
// src/services/rewind.ts
|
|
@@ -89625,10 +89693,10 @@ var RewindCheckpointHandler = class {
|
|
|
89625
89693
|
this.#allCheckpointTurnNos = [...allTurnNos];
|
|
89626
89694
|
}
|
|
89627
89695
|
recordSeqNo(seqNo) {
|
|
89628
|
-
this.#latestSeqNo = seqNo;
|
|
89696
|
+
if (seqNo > this.#latestSeqNo) this.#latestSeqNo = seqNo;
|
|
89629
89697
|
}
|
|
89630
89698
|
recordSdkUuid(seqNo, sdkUuid) {
|
|
89631
|
-
this.#seqNoToSdkUuid.set(seqNo, sdkUuid);
|
|
89699
|
+
if (!this.#seqNoToSdkUuid.has(seqNo)) this.#seqNoToSdkUuid.set(seqNo, sdkUuid);
|
|
89632
89700
|
}
|
|
89633
89701
|
findSdkUuidForSeqNo(targetSeqNo) {
|
|
89634
89702
|
let bestUuid = null;
|
|
@@ -92459,6 +92527,12 @@ function classifyRoiTransition(nextStatus, roiEverRan) {
|
|
|
92459
92527
|
if (nextStatus === "in_progress") return "reset-cycle";
|
|
92460
92528
|
return "nothing";
|
|
92461
92529
|
}
|
|
92530
|
+
function isTaskListSynthetic(msg, taskUri) {
|
|
92531
|
+
if (!msg.isSynthetic || msg.content.length !== 1) return false;
|
|
92532
|
+
const block2 = msg.content[0];
|
|
92533
|
+
if (!block2 || block2.type !== "resource") return false;
|
|
92534
|
+
return "uri" in block2.resource && block2.resource.uri === taskUri;
|
|
92535
|
+
}
|
|
92462
92536
|
var Task = class {
|
|
92463
92537
|
#deps;
|
|
92464
92538
|
#mainThread;
|
|
@@ -92594,6 +92668,8 @@ var Task = class {
|
|
|
92594
92668
|
error: err instanceof Error ? err.message : String(err)
|
|
92595
92669
|
});
|
|
92596
92670
|
});
|
|
92671
|
+
const seedPromise = this.#seedPushManagerFromHistory();
|
|
92672
|
+
this.#hydrationPromise = this.#hydrationPromise ? this.#hydrationPromise.then(() => seedPromise) : seedPromise;
|
|
92597
92673
|
}
|
|
92598
92674
|
this.#subagentManager = new SubagentManager({
|
|
92599
92675
|
taskId: deps.taskId,
|
|
@@ -93610,6 +93686,33 @@ var Task = class {
|
|
|
93610
93686
|
if (!arr || arr.length === 0) return void 0;
|
|
93611
93687
|
return arr.shift();
|
|
93612
93688
|
}
|
|
93689
|
+
/**
|
|
93690
|
+
* Resume path: if a prior daemon run already injected the task-list for
|
|
93691
|
+
* this channel, its synthetic row is in the JSONL. Seed the push manager
|
|
93692
|
+
* so `#resolveTaskListPrepend`'s `isSessionPushed` guard matches on the
|
|
93693
|
+
* first spawn — otherwise restart re-prepends, duplicating the chip and
|
|
93694
|
+
* dumping the full resource text back into the agent's context.
|
|
93695
|
+
*
|
|
93696
|
+
* Chained into `#hydrationPromise` (which `#awaitHydration` blocks on at
|
|
93697
|
+
* every spawn/flush entry point) so the seed completes before any
|
|
93698
|
+
* caller reaches `#resolveTaskListPrepend`. A fire-and-forget scan would
|
|
93699
|
+
* race against a fast-arriving first user message.
|
|
93700
|
+
*/
|
|
93701
|
+
async #seedPushManagerFromHistory() {
|
|
93702
|
+
const taskUri = buildTaskResourceUri(this.#deps.taskId);
|
|
93703
|
+
try {
|
|
93704
|
+
const msgs = await this.#deps.store.getMessages(this.#deps.channelId);
|
|
93705
|
+
if (msgs.some((msg) => isTaskListSynthetic(msg, taskUri))) {
|
|
93706
|
+
this.#pushManager.seedSessionPushed(taskUri);
|
|
93707
|
+
}
|
|
93708
|
+
} catch (err) {
|
|
93709
|
+
this.#deps.log({
|
|
93710
|
+
event: "push_manager_seed_failed",
|
|
93711
|
+
taskId: this.#deps.taskId,
|
|
93712
|
+
error: err instanceof Error ? err.message : String(err)
|
|
93713
|
+
});
|
|
93714
|
+
}
|
|
93715
|
+
}
|
|
93613
93716
|
async #awaitHydration() {
|
|
93614
93717
|
if (this.#hydrationPromise) {
|
|
93615
93718
|
await this.#hydrationPromise;
|
|
@@ -93730,10 +93833,12 @@ Use this context to maintain continuity. You have already done this work \u2014
|
|
|
93730
93833
|
const registry = this.#deps.resourceRegistry;
|
|
93731
93834
|
if (!registry) return null;
|
|
93732
93835
|
const taskUri = buildTaskResourceUri(this.#deps.taskId);
|
|
93733
|
-
if (this.#pushManager.
|
|
93836
|
+
if (this.#pushManager.isSessionPushed(taskUri)) return null;
|
|
93837
|
+
this.#pushManager.markPushed(taskUri);
|
|
93838
|
+
let injected = false;
|
|
93734
93839
|
try {
|
|
93735
93840
|
const taskResource = await registry.resolve(taskUri);
|
|
93736
|
-
if ("text" in taskResource && !taskResource.text.includes('task-count="0"')) {
|
|
93841
|
+
if ("text" in taskResource && !taskResource.text.includes('<task-list task-count="0"')) {
|
|
93737
93842
|
const rootMsg = buildRootMessage(
|
|
93738
93843
|
taskUri,
|
|
93739
93844
|
taskResource,
|
|
@@ -93748,10 +93853,12 @@ Use this context to maintain continuity. You have already done this work \u2014
|
|
|
93748
93853
|
},
|
|
93749
93854
|
[rootMsg]
|
|
93750
93855
|
);
|
|
93751
|
-
|
|
93856
|
+
injected = true;
|
|
93752
93857
|
return rootMsg.content;
|
|
93753
93858
|
}
|
|
93754
93859
|
} catch {
|
|
93860
|
+
} finally {
|
|
93861
|
+
if (!injected) this.#pushManager.unmarkPushed(taskUri);
|
|
93755
93862
|
}
|
|
93756
93863
|
return null;
|
|
93757
93864
|
}
|
|
@@ -94724,14 +94831,23 @@ function findSdkUuidForSeqNoInTask(tasks, taskId, seqNo) {
|
|
|
94724
94831
|
// src/services/task/manager/task-manager-template.ts
|
|
94725
94832
|
function buildInitialOverlayFromTemplate(template, now) {
|
|
94726
94833
|
if (!template || template.items.length === 0) return void 0;
|
|
94727
|
-
const
|
|
94728
|
-
|
|
94834
|
+
const idRemap = new Map(template.items.map((item2, index) => [item2.id, String(index + 1)]));
|
|
94835
|
+
const userTasks = template.items.map((item2, index) => ({
|
|
94836
|
+
id: String(index + 1),
|
|
94729
94837
|
subject: item2.content,
|
|
94730
94838
|
description: item2.description,
|
|
94731
94839
|
status: "pending",
|
|
94732
94840
|
owner: "user",
|
|
94733
94841
|
blocks: [],
|
|
94734
|
-
|
|
94842
|
+
/**
|
|
94843
|
+
* Drop deps pointing outside the template. Falling through the
|
|
94844
|
+
* original colon-bearing ID would leave a dangling `blockedBy` that
|
|
94845
|
+
* CC's TaskUpdate can't resolve — silently stuck in the UI.
|
|
94846
|
+
*/
|
|
94847
|
+
blockedBy: item2.deps.flatMap((dep) => {
|
|
94848
|
+
const mapped = idRemap.get(dep);
|
|
94849
|
+
return mapped ? [mapped] : [];
|
|
94850
|
+
}),
|
|
94735
94851
|
createdAt: now,
|
|
94736
94852
|
updatedAt: now
|
|
94737
94853
|
}));
|
|
@@ -105833,4 +105949,4 @@ export {
|
|
|
105833
105949
|
_testing,
|
|
105834
105950
|
serve
|
|
105835
105951
|
};
|
|
105836
|
-
//# sourceMappingURL=serve-
|
|
105952
|
+
//# sourceMappingURL=serve-3FTGY4YL.js.map
|