@skaile/workspaces 1.13.0 → 1.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/bridge/drivers/claude-sdk.js +98 -4
- package/dist/bridge/drivers/claude-sdk.js.map +1 -1
- package/dist/bridge/drivers/codex.js +1 -1
- package/dist/bridge/drivers/continue.js +2 -2
- package/dist/bridge/drivers/echo.js +1 -1
- package/dist/bridge/drivers/gemini.js +3 -3
- package/dist/bridge/drivers/goose.js +2 -2
- package/dist/bridge/drivers/omp.js +1 -1
- package/dist/bridge/drivers/opencode.js +2 -2
- package/dist/bridge/drivers/qwen.js +3 -3
- package/dist/bridge/index.js +2 -2
- package/dist/bridge/src/drivers/claude-sdk.d.ts +28 -1
- package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
- package/dist/{chunk-RQJB7KQI.js → chunk-4HKJ67FQ.js} +2 -2
- package/dist/{chunk-RQJB7KQI.js.map → chunk-4HKJ67FQ.js.map} +1 -1
- package/dist/{chunk-5D6677HY.js → chunk-BN5DZY2S.js} +4 -4
- package/dist/{chunk-5D6677HY.js.map → chunk-BN5DZY2S.js.map} +1 -1
- package/dist/{chunk-GAA5W4VX.js → chunk-DV4MCEJM.js} +3 -3
- package/dist/{chunk-GAA5W4VX.js.map → chunk-DV4MCEJM.js.map} +1 -1
- package/dist/{chunk-H5XWVDL4.js → chunk-S4AA7BEJ.js} +3 -3
- package/dist/{chunk-H5XWVDL4.js.map → chunk-S4AA7BEJ.js.map} +1 -1
- package/dist/{chunk-YM5P2P4R.js → chunk-XG3JOTAE.js} +2 -2
- package/dist/{chunk-YM5P2P4R.js.map → chunk-XG3JOTAE.js.map} +1 -1
- package/dist/{chunk-25LDSYP4.js → chunk-XLLVIG7D.js} +4 -4
- package/dist/{chunk-25LDSYP4.js.map → chunk-XLLVIG7D.js.map} +1 -1
- package/dist/cli/index.js +5 -5
- package/dist/factory-assets/connectors/flow/run-flow.js +1 -1
- package/dist/runner/index.js +3 -3
- package/dist/sdk/bridge.js +2 -2
- package/dist/sdk/index.js +3 -3
- package/dist/sdk/runner.js +3 -3
- package/dist/{setup-RN2M4VOG.js → setup-AH7TXQZF.js} +4 -4
- package/dist/{setup-RN2M4VOG.js.map → setup-AH7TXQZF.js.map} +1 -1
- package/dist/tui/index.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.13.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#431](https://github.com/skaile-ai/workspaces/pull/431) [`a9951a9`](https://github.com/skaile-ai/workspaces/commit/a9951a9ea6f108388e15a40d51a837685812ae18) Thanks [@peteralbert](https://github.com/peteralbert)! - fix(bridge): recover from a Claude SDK turn that never emits a result — a
|
|
8
|
+
per-turn inactivity watchdog (`SKAILE_TURN_STALL_MS`, default 30 min) tears the
|
|
9
|
+
wedged query down and rejects the turn, and `abort()` now forces the teardown
|
|
10
|
+
when the consumer loop refuses to unwind.
|
|
11
|
+
|
|
3
12
|
## 1.13.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
|
@@ -2,7 +2,7 @@ import { SUPPORTED_CLOUDS, buildClaudeCloudEnv } from '../../chunk-ISQWMBKD.js';
|
|
|
2
2
|
import { dispatchCapability } from '../../chunk-RRVQAE5D.js';
|
|
3
3
|
import { fetchProviderModels } from '../../chunk-BMQKMOQY.js';
|
|
4
4
|
import { AuthError, classifyClaudeSdkError, reclassifyAuthAsAccountUnavailable, AccountUnavailableError } from '../../chunk-GAH4YRCI.js';
|
|
5
|
-
import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-
|
|
5
|
+
import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-XG3JOTAE.js';
|
|
6
6
|
import '../../chunk-6E6PKKAD.js';
|
|
7
7
|
import '../../chunk-JB35MVBN.js';
|
|
8
8
|
import '../../chunk-NSBPE2FW.js';
|
|
@@ -311,6 +311,12 @@ if (typeof globalThis.require === "undefined") {
|
|
|
311
311
|
function freshRetryBudget() {
|
|
312
312
|
return { staleResume: false, poison: false, auth: false };
|
|
313
313
|
}
|
|
314
|
+
var DEFAULT_TURN_STALL_MS = 30 * 6e4;
|
|
315
|
+
function turnStallMs() {
|
|
316
|
+
const raw = Number(process.env.SKAILE_TURN_STALL_MS);
|
|
317
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_TURN_STALL_MS;
|
|
318
|
+
}
|
|
319
|
+
var ABORT_GRACE_MS = 3e3;
|
|
314
320
|
var claudeBinaryCache;
|
|
315
321
|
var claudeBinaryResolved = false;
|
|
316
322
|
var ClaudeSdkDriver = class extends AgentDriver {
|
|
@@ -343,6 +349,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
343
349
|
turnReject = null;
|
|
344
350
|
/** Guards against duplicate agent_end emissions per turn. */
|
|
345
351
|
turnCompleted = false;
|
|
352
|
+
/** `Date.now()` of the most recent SDK message — the watchdog's liveness signal. */
|
|
353
|
+
lastSdkMessageAt = 0;
|
|
354
|
+
/** Armed while a turn is in flight; trips when the SDK goes silent for too long. */
|
|
355
|
+
turnStallTimer = null;
|
|
346
356
|
/**
|
|
347
357
|
* Mirror of the in-flight {@link prompt} call's auth retry budget: `false`
|
|
348
358
|
* while the `onAuthError` self-heal is still available, `true` once it has
|
|
@@ -471,6 +481,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
471
481
|
this.running = false;
|
|
472
482
|
throw err;
|
|
473
483
|
}
|
|
484
|
+
this.armTurnStallWatchdog();
|
|
474
485
|
try {
|
|
475
486
|
await turnPromise;
|
|
476
487
|
} catch (err) {
|
|
@@ -499,12 +510,79 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
499
510
|
throw err;
|
|
500
511
|
} finally {
|
|
501
512
|
if (!retrying) {
|
|
513
|
+
this.disarmTurnStallWatchdog();
|
|
502
514
|
this.turnResolve = null;
|
|
503
515
|
this.turnReject = null;
|
|
504
516
|
this.running = false;
|
|
505
517
|
}
|
|
506
518
|
}
|
|
507
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* Arm the per-turn inactivity watchdog. Self-rescheduling rather than a single
|
|
522
|
+
* absolute timer so any mid-turn SDK message pushes the deadline out; the
|
|
523
|
+
* timer is `unref`'d so an armed watchdog never holds the process open.
|
|
524
|
+
*/
|
|
525
|
+
armTurnStallWatchdog() {
|
|
526
|
+
this.disarmTurnStallWatchdog();
|
|
527
|
+
if (this.turnCompleted || this.turnReject == null) return;
|
|
528
|
+
const budget = turnStallMs();
|
|
529
|
+
const tick = () => {
|
|
530
|
+
if (this.turnCompleted || this.turnReject == null) return;
|
|
531
|
+
if (this.hasPendingQuestion()) {
|
|
532
|
+
this.turnStallTimer = setTimeout(tick, budget);
|
|
533
|
+
this.turnStallTimer.unref?.();
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const idleMs = Date.now() - this.lastSdkMessageAt;
|
|
537
|
+
if (idleMs < budget) {
|
|
538
|
+
this.turnStallTimer = setTimeout(tick, budget - idleMs);
|
|
539
|
+
this.turnStallTimer.unref?.();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
this.onTurnStalled(idleMs);
|
|
543
|
+
};
|
|
544
|
+
this.lastSdkMessageAt = Date.now();
|
|
545
|
+
this.turnStallTimer = setTimeout(tick, budget);
|
|
546
|
+
this.turnStallTimer.unref?.();
|
|
547
|
+
}
|
|
548
|
+
disarmTurnStallWatchdog() {
|
|
549
|
+
if (this.turnStallTimer) {
|
|
550
|
+
clearTimeout(this.turnStallTimer);
|
|
551
|
+
this.turnStallTimer = null;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* The SDK went silent mid-turn. Tear the query down so the *next* prompt
|
|
556
|
+
* starts a fresh one (a live-but-deaf query would otherwise swallow every
|
|
557
|
+
* subsequent `streamInput`), then reject the turn with an actionable message.
|
|
558
|
+
*/
|
|
559
|
+
onTurnStalled(idleMs) {
|
|
560
|
+
const idle = idleMs < 6e4 ? `${Math.round(idleMs / 1e3)}s` : `${Math.round(idleMs / 6e4)}m`;
|
|
561
|
+
this.log.error("turn stalled \u2014 no SDK message received; resetting the query", { idleMs });
|
|
562
|
+
this.hardResetQuery();
|
|
563
|
+
this.failTurn(
|
|
564
|
+
new Error(
|
|
565
|
+
`Agent stalled: no response from the model process for ${idle}. The conversation is intact \u2014 send your message again.`
|
|
566
|
+
)
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Drop a query that will not unwind on its own, keeping the session intact.
|
|
571
|
+
*
|
|
572
|
+
* Deliberately does NOT clear `sessionId` / `hasSession` — the next
|
|
573
|
+
* `startQuery()` must resume the same SDK session so the conversation
|
|
574
|
+
* survives. That is what separates this from {@link kill}.
|
|
575
|
+
*/
|
|
576
|
+
hardResetQuery() {
|
|
577
|
+
if (this.query) {
|
|
578
|
+
try {
|
|
579
|
+
this.query.close();
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
this.query = null;
|
|
583
|
+
}
|
|
584
|
+
this.abortController?.abort();
|
|
585
|
+
}
|
|
508
586
|
/**
|
|
509
587
|
* Side-effects of the stale-resume recovery path: emit the resume-outcome and
|
|
510
588
|
* non-fatal error events, then reset session state so the recursive prompt()
|
|
@@ -855,6 +933,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
855
933
|
}
|
|
856
934
|
if (pending.answered >= pending.questionTexts.length) {
|
|
857
935
|
this.pendingQuestion = null;
|
|
936
|
+
this.lastSdkMessageAt = Date.now();
|
|
858
937
|
pending.resolve({
|
|
859
938
|
behavior: "allow",
|
|
860
939
|
updatedInput: { ...pending.originalInput, answers: pending.answers }
|
|
@@ -872,6 +951,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
872
951
|
const pending = this.pendingQuestion;
|
|
873
952
|
if (!pending) return;
|
|
874
953
|
this.pendingQuestion = null;
|
|
954
|
+
this.lastSdkMessageAt = Date.now();
|
|
875
955
|
pending.resolve({ behavior: "deny", message: reason });
|
|
876
956
|
}
|
|
877
957
|
/**
|
|
@@ -979,6 +1059,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
979
1059
|
try {
|
|
980
1060
|
for await (const msg of this.query) {
|
|
981
1061
|
if (this.abortController?.signal.aborted) break;
|
|
1062
|
+
this.lastSdkMessageAt = Date.now();
|
|
982
1063
|
this.captureSessionId(msg);
|
|
983
1064
|
this.handleSdkMessage(msg);
|
|
984
1065
|
if (msg.type === "result" && !msg.is_error) {
|
|
@@ -1039,6 +1120,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1039
1120
|
completeTurn() {
|
|
1040
1121
|
if (this.turnCompleted) return;
|
|
1041
1122
|
this.turnCompleted = true;
|
|
1123
|
+
this.disarmTurnStallWatchdog();
|
|
1042
1124
|
if (this.turnResolve) {
|
|
1043
1125
|
this.turnResolve();
|
|
1044
1126
|
this.turnResolve = null;
|
|
@@ -1085,6 +1167,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1085
1167
|
* exposed this hang).
|
|
1086
1168
|
*/
|
|
1087
1169
|
failTurn(err, detail) {
|
|
1170
|
+
this.disarmTurnStallWatchdog();
|
|
1088
1171
|
if (this.turnReject) {
|
|
1089
1172
|
this.turnReject(err);
|
|
1090
1173
|
this.turnResolve = null;
|
|
@@ -1390,7 +1473,9 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1390
1473
|
* signals the underlying `AbortController`. The driver remains usable after abort.
|
|
1391
1474
|
*
|
|
1392
1475
|
* @remarks Best-effort: if `interrupt()` throws (e.g. query already ended) the
|
|
1393
|
-
* error is silently swallowed and the abort signal is still sent.
|
|
1476
|
+
* error is silently swallowed and the abort signal is still sent. A query
|
|
1477
|
+
* whose generator refuses to unwind within {@link ABORT_GRACE_MS} is torn
|
|
1478
|
+
* down outright — see {@link hardResetQuery}.
|
|
1394
1479
|
*/
|
|
1395
1480
|
async abort() {
|
|
1396
1481
|
this.denyPendingQuestion("turn aborted before the question was answered");
|
|
@@ -1400,8 +1485,17 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1400
1485
|
} catch {
|
|
1401
1486
|
}
|
|
1402
1487
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1488
|
+
this.abortController?.abort();
|
|
1489
|
+
const deadline = Date.now() + ABORT_GRACE_MS;
|
|
1490
|
+
while (this.query && Date.now() < deadline) {
|
|
1491
|
+
await new Promise((resolve) => {
|
|
1492
|
+
setTimeout(resolve, 100).unref?.();
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
if (this.query) {
|
|
1496
|
+
this.log.warn("query did not unwind after abort; forcing teardown");
|
|
1497
|
+
this.hardResetQuery();
|
|
1498
|
+
this.completeTurn();
|
|
1405
1499
|
}
|
|
1406
1500
|
}
|
|
1407
1501
|
/** `true` while the driver is processing a `prompt()` call. */
|