@schoolai/shipyard 3.24.0-rc.20260813.0 → 3.24.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/electron-utility.js +1 -1
- package/dist/index.js +2 -2
- package/dist/{serve-QHLWAVVY.js → serve-RWFBSQIE.js} +117 -27
- package/dist/{serve-QHLWAVVY.js.map → serve-RWFBSQIE.js.map} +1 -1
- package/dist/{start-JJCGZ63I.js → start-ZV22UPMC.js} +2 -2
- package/package.json +1 -1
- /package/dist/{start-JJCGZ63I.js.map → start-ZV22UPMC.js.map} +0 -0
package/dist/electron-utility.js
CHANGED
|
@@ -71,7 +71,7 @@ async function main() {
|
|
|
71
71
|
await loadAuthFromConfig(env);
|
|
72
72
|
crumb("post-auth-load");
|
|
73
73
|
crumb("pre-serve-import");
|
|
74
|
-
const { serve } = await import("./serve-
|
|
74
|
+
const { serve } = await import("./serve-RWFBSQIE.js");
|
|
75
75
|
crumb("post-serve-import");
|
|
76
76
|
const portQueue = [];
|
|
77
77
|
let acceptor = null;
|
package/dist/index.js
CHANGED
|
@@ -137,7 +137,7 @@ async function handleSubcommand() {
|
|
|
137
137
|
return true;
|
|
138
138
|
}
|
|
139
139
|
if (subcommand === "start") {
|
|
140
|
-
const { startCommand } = await import("./start-
|
|
140
|
+
const { startCommand } = await import("./start-ZV22UPMC.js");
|
|
141
141
|
await startCommand();
|
|
142
142
|
return true;
|
|
143
143
|
}
|
|
@@ -159,7 +159,7 @@ async function main() {
|
|
|
159
159
|
const args = parseCliArgs();
|
|
160
160
|
if (args.serve) {
|
|
161
161
|
await loadAuthFromConfig(env);
|
|
162
|
-
const { serve } = await import("./serve-
|
|
162
|
+
const { serve } = await import("./serve-RWFBSQIE.js");
|
|
163
163
|
return serve({ isDev: env.SHIPYARD_DEV });
|
|
164
164
|
}
|
|
165
165
|
logger.error("Use `shipyard start` to run the daemon. Use --help for usage.");
|
|
@@ -78822,6 +78822,13 @@ function handleSpawning(snapshot, event) {
|
|
|
78822
78822
|
}
|
|
78823
78823
|
return noop(snapshot);
|
|
78824
78824
|
}
|
|
78825
|
+
function buildStallGiveUpMessage(recoveryAttempts) {
|
|
78826
|
+
if (recoveryAttempts <= 0) {
|
|
78827
|
+
return "The agent stopped responding mid-run. Send a message to try again.";
|
|
78828
|
+
}
|
|
78829
|
+
const plural = recoveryAttempts === 1 ? "time" : "times";
|
|
78830
|
+
return `The agent stopped responding mid-run. Automatic recovery already retried ${recoveryAttempts} ${plural} without success \u2014 send a message to resume.`;
|
|
78831
|
+
}
|
|
78825
78832
|
function runningStallTimeout(sessionId, event) {
|
|
78826
78833
|
const b2 = createEffectBuilder();
|
|
78827
78834
|
b2.log("running", "resumable_idle", event.type);
|
|
@@ -78829,11 +78836,7 @@ function runningStallTimeout(sessionId, event) {
|
|
|
78829
78836
|
b2.effects.push({ type: "clear_queue" });
|
|
78830
78837
|
if (!event.recoveryArmed) {
|
|
78831
78838
|
b2.taskStatus("input_required");
|
|
78832
|
-
b2.emitError(
|
|
78833
|
-
"The agent stopped responding mid-run. Send a message to try again.",
|
|
78834
|
-
"sdk_error",
|
|
78835
|
-
"run_stall_timeout"
|
|
78836
|
-
);
|
|
78839
|
+
b2.emitError(buildStallGiveUpMessage(event.recoveryAttempts), "sdk_error", "run_stall_timeout");
|
|
78837
78840
|
}
|
|
78838
78841
|
b2.effects.push({ type: "kill_runner" });
|
|
78839
78842
|
return {
|
|
@@ -79654,10 +79657,13 @@ var AgentSessionManager = class {
|
|
|
79654
79657
|
return;
|
|
79655
79658
|
}
|
|
79656
79659
|
let recoveryArmed = false;
|
|
79660
|
+
let recoveryAttempts = 0;
|
|
79657
79661
|
try {
|
|
79658
|
-
|
|
79662
|
+
const outcome = this.#config.onStallTimeout?.() ?? { armed: false, recoveryAttempts: 0 };
|
|
79663
|
+
recoveryArmed = outcome.armed;
|
|
79664
|
+
recoveryAttempts = outcome.recoveryAttempts;
|
|
79659
79665
|
} finally {
|
|
79660
|
-
this.#dispatch({ type: "run_stall_timeout", recoveryArmed });
|
|
79666
|
+
this.#dispatch({ type: "run_stall_timeout", recoveryArmed, recoveryAttempts });
|
|
79661
79667
|
}
|
|
79662
79668
|
}, timeoutMs);
|
|
79663
79669
|
}
|
|
@@ -79685,48 +79691,98 @@ var AgentSessionManager = class {
|
|
|
79685
79691
|
|
|
79686
79692
|
// src/services/conversation/cursor-recovery-controller.ts
|
|
79687
79693
|
var MAX_SIGNAL_DEATH_RETRIES = 1;
|
|
79688
|
-
var MAX_STALL_RESUME_RETRIES =
|
|
79694
|
+
var MAX_STALL_RESUME_RETRIES = 3;
|
|
79695
|
+
var STALL_RESUME_BASE_DELAY_MS = 3e3;
|
|
79696
|
+
var STALL_RESUME_BACKOFF_MULTIPLIER = 3;
|
|
79697
|
+
function stallResumeDelayMs(attempt) {
|
|
79698
|
+
return STALL_RESUME_BASE_DELAY_MS * STALL_RESUME_BACKOFF_MULTIPLIER ** (attempt - 1);
|
|
79699
|
+
}
|
|
79689
79700
|
var MAX_CRASH_DEATH_RETRIES = 1;
|
|
79690
79701
|
var CursorRecoveryController = class {
|
|
79691
79702
|
#signalDeathRetries = 0;
|
|
79692
79703
|
#stallResumeRetries = 0;
|
|
79693
79704
|
#stallResumeArmed = false;
|
|
79694
79705
|
#crashDeathRetries = 0;
|
|
79706
|
+
/**
|
|
79707
|
+
* Pending timer for the settle-delay before an armed stall resume fires.
|
|
79708
|
+
* Cleared at every call site that supersedes the in-flight resume (new
|
|
79709
|
+
* user turn, stop, rewind, dispose) plus its own callback on fire — the
|
|
79710
|
+
* same shape as `RateLimitFlapController#timer`. A single handle suffices;
|
|
79711
|
+
* `consumeStallResumeOnDeath` cannot run twice without an intervening
|
|
79712
|
+
* `onStallTimeout` re-arming it.
|
|
79713
|
+
*/
|
|
79714
|
+
#stallResumeTimer = null;
|
|
79695
79715
|
#deps;
|
|
79696
79716
|
constructor(deps) {
|
|
79697
79717
|
this.#deps = deps;
|
|
79698
79718
|
}
|
|
79719
|
+
/**
|
|
79720
|
+
* Cancel a pending delayed stall resume. Idempotent. Call from any path
|
|
79721
|
+
* that supersedes the armed resume (new user turn, stop, rewind, dispose)
|
|
79722
|
+
* — without this, a fresh intent would race the deferred
|
|
79723
|
+
* `respawnForResume()` fired by the settle-delay timer.
|
|
79724
|
+
*/
|
|
79725
|
+
cancelPendingResume() {
|
|
79726
|
+
if (this.#stallResumeTimer !== null) {
|
|
79727
|
+
clearTimeout(this.#stallResumeTimer);
|
|
79728
|
+
this.#stallResumeTimer = null;
|
|
79729
|
+
}
|
|
79730
|
+
}
|
|
79699
79731
|
/** Reset every budget. Call on each new user turn and on turn_complete. */
|
|
79700
79732
|
reset() {
|
|
79733
|
+
this.cancelPendingResume();
|
|
79701
79734
|
this.#signalDeathRetries = 0;
|
|
79702
79735
|
this.#stallResumeRetries = 0;
|
|
79703
79736
|
this.#stallResumeArmed = false;
|
|
79704
79737
|
this.#crashDeathRetries = 0;
|
|
79705
79738
|
}
|
|
79739
|
+
/**
|
|
79740
|
+
* Call on `turn_complete` instead of `reset()`. A successful auto-recovery
|
|
79741
|
+
* otherwise leaves no trace that the daemon self-healed rather than the
|
|
79742
|
+
* run simply never stalling — log the recovery before clearing the budget.
|
|
79743
|
+
*/
|
|
79744
|
+
noteTurnCompleteRecovery() {
|
|
79745
|
+
if (this.#stallResumeRetries > 0) {
|
|
79746
|
+
this.#deps.log({
|
|
79747
|
+
event: "thread_cursor_stall_auto_resume_recovered",
|
|
79748
|
+
threadId: this.#deps.threadId,
|
|
79749
|
+
attempts: this.#stallResumeRetries
|
|
79750
|
+
});
|
|
79751
|
+
}
|
|
79752
|
+
this.reset();
|
|
79753
|
+
}
|
|
79706
79754
|
/**
|
|
79707
79755
|
* Fired by the manager when the stall watchdog expires, BEFORE the
|
|
79708
79756
|
* `run_stall_timeout` event transitions the FSM. A mid-run Cursor stall
|
|
79709
79757
|
* otherwise lands in `resumable_idle` with a "send a message to try again"
|
|
79710
|
-
* banner and strands the orphaned turn until the user returns. Arm a
|
|
79711
|
-
* resume so the imminent `kill_runner` death is resumed instead of
|
|
79712
|
-
* On repeat
|
|
79758
|
+
* banner and strands the orphaned turn until the user returns. Arm a
|
|
79759
|
+
* resume so the imminent `kill_runner` death is resumed instead of
|
|
79760
|
+
* surfaced. On repeat, up to `MAX_STALL_RESUME_RETRIES` times, stay
|
|
79761
|
+
* armed-off and let the existing banner run.
|
|
79713
79762
|
*
|
|
79714
|
-
* Returns true when a recovery was armed — the manager passes
|
|
79715
|
-
* `recoveryArmed` on the `run_stall_timeout` FSM event so the FSM
|
|
79716
|
-
* the user-facing error banner during the
|
|
79763
|
+
* Returns `armed: true` when a recovery was armed — the manager passes
|
|
79764
|
+
* this as `recoveryArmed` on the `run_stall_timeout` FSM event so the FSM
|
|
79765
|
+
* suppresses the user-facing error banner during the self-heal.
|
|
79766
|
+
* `recoveryAttempts` is only ever positive on the cap-exceeded give-up
|
|
79767
|
+
* (see `StallTimeoutOutcome`), letting the FSM distinguish "never
|
|
79768
|
+
* attempted" from "tried N times".
|
|
79717
79769
|
*/
|
|
79718
79770
|
onStallTimeout() {
|
|
79719
|
-
if (this.#deps.getState() !== "running") return false;
|
|
79720
|
-
if (this.#deps.getAgentSystem() !== AGENT_SYSTEM_CURSOR)
|
|
79771
|
+
if (this.#deps.getState() !== "running") return { armed: false, recoveryAttempts: 0 };
|
|
79772
|
+
if (this.#deps.getAgentSystem() !== AGENT_SYSTEM_CURSOR) {
|
|
79773
|
+
return { armed: false, recoveryAttempts: 0 };
|
|
79774
|
+
}
|
|
79721
79775
|
this.#stallResumeRetries++;
|
|
79722
79776
|
if (this.#stallResumeRetries > MAX_STALL_RESUME_RETRIES) {
|
|
79777
|
+
const recoveryAttempts = this.#stallResumeRetries - 1;
|
|
79723
79778
|
this.#deps.log({
|
|
79724
79779
|
event: "thread_cursor_stall_resume_cap_exceeded",
|
|
79725
79780
|
threadId: this.#deps.threadId,
|
|
79726
|
-
retries: this.#stallResumeRetries
|
|
79781
|
+
retries: this.#stallResumeRetries,
|
|
79782
|
+
maxRetries: MAX_STALL_RESUME_RETRIES
|
|
79727
79783
|
});
|
|
79728
79784
|
this.#stallResumeRetries = 0;
|
|
79729
|
-
return false;
|
|
79785
|
+
return { armed: false, recoveryAttempts };
|
|
79730
79786
|
}
|
|
79731
79787
|
this.#deps.log({
|
|
79732
79788
|
event: "thread_cursor_stall_resume_armed",
|
|
@@ -79734,19 +79790,41 @@ var CursorRecoveryController = class {
|
|
|
79734
79790
|
retries: this.#stallResumeRetries
|
|
79735
79791
|
});
|
|
79736
79792
|
this.#stallResumeArmed = true;
|
|
79737
|
-
return true;
|
|
79793
|
+
return { armed: true, recoveryAttempts: 0 };
|
|
79738
79794
|
}
|
|
79739
79795
|
/**
|
|
79740
|
-
* Consume an armed stall resume on the `kill_runner` death. By now the FSM
|
|
79741
|
-
* in `resumable_idle` and the subprocess was already cleared by
|
|
79742
|
-
* force-kill
|
|
79743
|
-
*
|
|
79796
|
+
* Consume an armed stall resume on the `kill_runner` death. By now the FSM
|
|
79797
|
+
* is in `resumable_idle` and the subprocess was already cleared by
|
|
79798
|
+
* Thread's force-kill. Schedule the actual resume after
|
|
79799
|
+
* `stallResumeDelayMs(attempt)` rather than firing immediately — the
|
|
79800
|
+
* settle window that prevents the busy collision (see
|
|
79801
|
+
* `MAX_STALL_RESUME_RETRIES`'s doc comment). Returns true as soon as it
|
|
79802
|
+
* commits to handling the death (the timer is in-flight); the caller must
|
|
79803
|
+
* not also surface it.
|
|
79744
79804
|
*/
|
|
79745
79805
|
consumeStallResumeOnDeath() {
|
|
79746
79806
|
if (!this.#stallResumeArmed) return false;
|
|
79747
79807
|
this.#stallResumeArmed = false;
|
|
79748
|
-
this
|
|
79749
|
-
this.#
|
|
79808
|
+
this.cancelPendingResume();
|
|
79809
|
+
const attempt = this.#stallResumeRetries;
|
|
79810
|
+
const delayMs = stallResumeDelayMs(attempt);
|
|
79811
|
+
this.#deps.log({
|
|
79812
|
+
event: "thread_cursor_stall_auto_resume_scheduled",
|
|
79813
|
+
threadId: this.#deps.threadId,
|
|
79814
|
+
attempt,
|
|
79815
|
+
delayMs
|
|
79816
|
+
});
|
|
79817
|
+
this.#stallResumeTimer = setTimeout(() => {
|
|
79818
|
+
this.#stallResumeTimer = null;
|
|
79819
|
+
if (this.#deps.isDisposed()) return;
|
|
79820
|
+
this.#deps.log({
|
|
79821
|
+
event: "thread_cursor_stall_auto_resume",
|
|
79822
|
+
threadId: this.#deps.threadId,
|
|
79823
|
+
attempt,
|
|
79824
|
+
delayMs
|
|
79825
|
+
});
|
|
79826
|
+
this.#deps.respawnForResume();
|
|
79827
|
+
}, delayMs);
|
|
79750
79828
|
return true;
|
|
79751
79829
|
}
|
|
79752
79830
|
/**
|
|
@@ -80857,6 +80935,7 @@ var Thread = class {
|
|
|
80857
80935
|
threadId: config.threadId,
|
|
80858
80936
|
getState: () => this.#manager.state,
|
|
80859
80937
|
getAgentSystem: () => this.#agentSystem,
|
|
80938
|
+
isDisposed: () => this.#disposed,
|
|
80860
80939
|
log: config.log,
|
|
80861
80940
|
clearSubprocess: () => {
|
|
80862
80941
|
this.#subprocess?.setOnCwdChanged(null);
|
|
@@ -81017,8 +81096,16 @@ var Thread = class {
|
|
|
81017
81096
|
* Unlike `handleUserMessage`, this does NOT call `#recovery.noteNewUserTurn()`
|
|
81018
81097
|
* (which resets the request-aborted retry budget) because a synthetic wake is
|
|
81019
81098
|
* not a real user message.
|
|
81099
|
+
*
|
|
81100
|
+
* DOES cancel a pending flap/stall-resume timer, same reasoning as `stop()`
|
|
81101
|
+
* and `rewind()`: `sendMessage` from `resumable_idle` walks the FSM to
|
|
81102
|
+
* `spawning` on its own, so a still-armed timer firing later would call
|
|
81103
|
+
* `respawnForResume()` against whatever run this wake started — a resume
|
|
81104
|
+
* with nothing to do with the death it was originally scheduled for.
|
|
81020
81105
|
*/
|
|
81021
81106
|
pushSyntheticMessageAndWake(content) {
|
|
81107
|
+
this.#rateLimitFlap.cancelPendingRetry();
|
|
81108
|
+
this.#cursorRecovery.cancelPendingResume();
|
|
81022
81109
|
this.#manager.sendMessage(content);
|
|
81023
81110
|
}
|
|
81024
81111
|
/**
|
|
@@ -81059,6 +81146,7 @@ var Thread = class {
|
|
|
81059
81146
|
}
|
|
81060
81147
|
stop() {
|
|
81061
81148
|
this.#rateLimitFlap.cancelPendingRetry();
|
|
81149
|
+
this.#cursorRecovery.cancelPendingResume();
|
|
81062
81150
|
this.#manager.stop();
|
|
81063
81151
|
}
|
|
81064
81152
|
/**
|
|
@@ -81068,6 +81156,7 @@ var Thread = class {
|
|
|
81068
81156
|
*/
|
|
81069
81157
|
rewind(resumeAtMessageId) {
|
|
81070
81158
|
this.#rateLimitFlap.cancelPendingRetry();
|
|
81159
|
+
this.#cursorRecovery.cancelPendingResume();
|
|
81071
81160
|
this.#manager.rewind(resumeAtMessageId);
|
|
81072
81161
|
}
|
|
81073
81162
|
notifySetupReady() {
|
|
@@ -81221,6 +81310,7 @@ var Thread = class {
|
|
|
81221
81310
|
await this.#asyncQueue;
|
|
81222
81311
|
this.#disposed = true;
|
|
81223
81312
|
this.#rateLimitFlap.cancelPendingRetry();
|
|
81313
|
+
this.#cursorRecovery.cancelPendingResume();
|
|
81224
81314
|
this.#permissionHandler.denyAllPending("Thread disposed");
|
|
81225
81315
|
this.#permissionQueue.length = 0;
|
|
81226
81316
|
this.#streamDelta.dispose();
|
|
@@ -81861,7 +81951,7 @@ var Thread = class {
|
|
|
81861
81951
|
});
|
|
81862
81952
|
this.#streamDelta.resetForTurn();
|
|
81863
81953
|
this.#recovery.resetRetriesOnTurnComplete();
|
|
81864
|
-
this.#cursorRecovery.
|
|
81954
|
+
this.#cursorRecovery.noteTurnCompleteRecovery();
|
|
81865
81955
|
this.#rateLimitFlap.noteRecoveredOnTurnComplete((event.result.outputTokens ?? 0) > 0);
|
|
81866
81956
|
this.#emitThreadTurnStats(event.result);
|
|
81867
81957
|
this.#manager.notifyTurnComplete();
|
|
@@ -103390,4 +103480,4 @@ export {
|
|
|
103390
103480
|
decideWorkspaceScope,
|
|
103391
103481
|
serve
|
|
103392
103482
|
};
|
|
103393
|
-
//# sourceMappingURL=serve-
|
|
103483
|
+
//# sourceMappingURL=serve-RWFBSQIE.js.map
|