@skaile/workspaces 2.4.0 → 2.5.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/CHANGELOG.md +42 -0
- package/dist/app-sdk/index.js +1 -1
- package/dist/bridge/drivers/claude-sdk.js +107 -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 +33 -0
- package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
- package/dist/{chunk-RDE43ORT.js → chunk-D4VHBAJC.js} +4 -4
- package/dist/{chunk-RDE43ORT.js.map → chunk-D4VHBAJC.js.map} +1 -1
- package/dist/{chunk-6BV5NAPD.js → chunk-EUWN4EK4.js} +2 -2
- package/dist/{chunk-6BV5NAPD.js.map → chunk-EUWN4EK4.js.map} +1 -1
- package/dist/{chunk-GM2X24Q6.js → chunk-HY3MBSNC.js} +2 -2
- package/dist/{chunk-GM2X24Q6.js.map → chunk-HY3MBSNC.js.map} +1 -1
- package/dist/{chunk-G5V6YCHF.js → chunk-L3TS7ZO4.js} +3 -3
- package/dist/{chunk-G5V6YCHF.js.map → chunk-L3TS7ZO4.js.map} +1 -1
- package/dist/{chunk-CA5CWLGN.js → chunk-PKEAYYEJ.js} +7 -4
- package/dist/chunk-PKEAYYEJ.js.map +1 -0
- package/dist/{chunk-O7ROIKTF.js → chunk-VNIKF66R.js} +3 -3
- package/dist/{chunk-O7ROIKTF.js.map → chunk-VNIKF66R.js.map} +1 -1
- package/dist/{chunk-2QVPCZTL.js → chunk-YWPEDILM.js} +7 -6
- package/dist/chunk-YWPEDILM.js.map +1 -0
- package/dist/{chunk-UGKOWIRP.js → chunk-ZR2X4SIG.js} +4 -4
- package/dist/{chunk-UGKOWIRP.js.map → chunk-ZR2X4SIG.js.map} +1 -1
- package/dist/cli/index.js +6 -6
- package/dist/factory-assets/connectors/flow/run-flow.js +1 -1
- package/dist/runner/index.js +5 -5
- package/dist/runner/src/serve.d.ts.map +1 -1
- package/dist/sdk/bridge.js +2 -2
- package/dist/sdk/index.js +5 -5
- package/dist/sdk/runner.js +5 -5
- package/dist/sdk/session.js +2 -2
- package/dist/sdk/types.js +1 -1
- package/dist/session/index.js +2 -2
- package/dist/session/src/dispatcher.d.ts.map +1 -1
- package/dist/{setup-TJYLYT2A.js → setup-RHWARBBB.js} +4 -4
- package/dist/{setup-TJYLYT2A.js.map → setup-RHWARBBB.js.map} +1 -1
- package/dist/tui/index.js +5 -5
- package/dist/types/index.js +1 -1
- package/dist/types/src/events.d.ts +20 -1
- package/dist/types/src/events.d.ts.map +1 -1
- package/dist/types/src/protocol.d.ts +1 -0
- package/dist/types/src/protocol.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +12 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-2QVPCZTL.js.map +0 -1
- package/dist/chunk-CA5CWLGN.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#500](https://github.com/skaile-ai/workspaces/pull/500) [`5104b98`](https://github.com/skaile-ai/workspaces/commit/5104b980e3e7243f19142079257c5fc42be20722) Thanks [@peteralbert](https://github.com/peteralbert)! - Protocol 3.8 — acknowledge receipt of a prompt
|
|
8
|
+
|
|
9
|
+
`prompt` was the only frame in the protocol with no acknowledgement. A host sent
|
|
10
|
+
it into the socket and got nothing back, so a runner that had stopped executing
|
|
11
|
+
while its WebSocket stayed open (an in-container OOM is enough) swallowed every
|
|
12
|
+
subsequent user message with no trace anywhere: no log, no event, no failed
|
|
13
|
+
send. The host had no way to distinguish "delivered, still thinking" from "never
|
|
14
|
+
arrived".
|
|
15
|
+
|
|
16
|
+
The `prompt` command gains an optional `messageId`. When it is set the runner
|
|
17
|
+
emits exactly one new `prompt_ack` event echoing it back, as the **first act** of
|
|
18
|
+
`handlePrompt` — before `awaitSessionReadyOrFail()` (which drops prompts
|
|
19
|
+
silently), before the secrets check, and before the driver starts. When it is
|
|
20
|
+
absent nothing is emitted, so a 3.7 host sees byte-identical behaviour, and a
|
|
21
|
+
3.8 host talking to a 3.7 runner must feature-detect on
|
|
22
|
+
`major === 3 && minor >= 8` rather than read the missing ack as a lost prompt.
|
|
23
|
+
|
|
24
|
+
The ack is deliberately receipt-only: it says the frame arrived and was decoded,
|
|
25
|
+
never that the turn ran. A driver that accepts a prompt and then leaves the turn
|
|
26
|
+
promise pending is a separate failure mode and needs turn-level supervision.
|
|
27
|
+
|
|
28
|
+
`prompt_ack` is transient — system RPC, not chat. It is on the dispatcher's
|
|
29
|
+
denylist so it is never persisted (an unlisted ack would also split the
|
|
30
|
+
assistant's streaming bubble mid-turn).
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- [#501](https://github.com/skaile-ai/workspaces/pull/501) [`1076360`](https://github.com/skaile-ai/workspaces/commit/107636009ebbbfeb7d82b93a2a8633f84dfc59b6) Thanks [@peteralbert](https://github.com/peteralbert)! - Fix `ClaudeSdkDriver` so aborting a turn (Stop) actually kills an in-flight
|
|
35
|
+
tool subprocess (e.g. a running Bash command) instead of leaving it orphaned.
|
|
36
|
+
`query.close()` only tore down the SDK's connection to the CLI subprocess, not
|
|
37
|
+
whatever process that CLI in turn spawned for the tool call, and a
|
|
38
|
+
process-group signal to the CLI didn't reach it either since the CLI spawns
|
|
39
|
+
the tool's shell into its own new process group. The driver now captures the
|
|
40
|
+
CLI subprocess's own OS pid via `spawnClaudeCodeProcess`, snapshots its
|
|
41
|
+
descendant processes before sending any abort signal (reparenting on exit
|
|
42
|
+
would otherwise make a post-abort snapshot come back empty), and SIGKILLs
|
|
43
|
+
that snapshot from `abort()` and `kill()` alike.
|
|
44
|
+
|
|
3
45
|
## 2.4.0
|
|
4
46
|
|
|
5
47
|
### Minor Changes
|
package/dist/app-sdk/index.js
CHANGED
|
@@ -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-HY3MBSNC.js';
|
|
6
6
|
import '../../chunk-6E6PKKAD.js';
|
|
7
7
|
import '../../chunk-P43HBWKK.js';
|
|
8
8
|
import '../../chunk-F3HNW354.js';
|
|
@@ -17,8 +17,8 @@ import '../../chunk-GEEW6SY2.js';
|
|
|
17
17
|
import '../../chunk-U5POE4BA.js';
|
|
18
18
|
import '../../chunk-7GOCNYUV.js';
|
|
19
19
|
import '../../chunk-NSBPE2FW.js';
|
|
20
|
-
import { spawnSync } from 'child_process';
|
|
21
|
-
import { existsSync, createReadStream, createWriteStream } from 'fs';
|
|
20
|
+
import { spawnSync, spawn } from 'child_process';
|
|
21
|
+
import { readFileSync, existsSync, createReadStream, createWriteStream } from 'fs';
|
|
22
22
|
import { createRequire } from 'module';
|
|
23
23
|
import { homedir } from 'os';
|
|
24
24
|
import { join } from 'path';
|
|
@@ -393,6 +393,12 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
393
393
|
suppressingAuthStream = false;
|
|
394
394
|
/** Active query — may stay alive between turns if streamInput works. */
|
|
395
395
|
query = null;
|
|
396
|
+
/**
|
|
397
|
+
* OS pid of the CLI subprocess backing `query`, captured via
|
|
398
|
+
* `spawnClaudeCodeProcess` in {@link startQuery}. Feeds
|
|
399
|
+
* {@link snapshotDescendantPids}; see `_devlog/notes/2026-08-12-abort-kills-tool-subprocess.md`.
|
|
400
|
+
*/
|
|
401
|
+
cliProcessPid = null;
|
|
396
402
|
/** Resolves when the current turn completes. */
|
|
397
403
|
turnResolve = null;
|
|
398
404
|
/** Rejects when the current turn errors. */
|
|
@@ -629,7 +635,9 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
629
635
|
onTurnStalled(idleMs) {
|
|
630
636
|
const idle = idleMs < 6e4 ? `${Math.round(idleMs / 1e3)}s` : `${Math.round(idleMs / 6e4)}m`;
|
|
631
637
|
this.log.error("turn stalled \u2014 no SDK message received; resetting the query", { idleMs });
|
|
638
|
+
const descendants = this.snapshotDescendantPids();
|
|
632
639
|
this.hardResetQuery();
|
|
640
|
+
this.killPids(descendants);
|
|
633
641
|
this.failTurn(
|
|
634
642
|
new Error(
|
|
635
643
|
`Agent stalled: no response from the model process for ${idle}. The conversation is intact \u2014 send your message again.`
|
|
@@ -642,6 +650,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
642
650
|
* Deliberately does NOT clear `sessionId` / `hasSession` — the next
|
|
643
651
|
* `startQuery()` must resume the same SDK session so the conversation
|
|
644
652
|
* survives. That is what separates this from {@link kill}.
|
|
653
|
+
*
|
|
654
|
+
* Does NOT sweep the CLI's descendant processes — callers needing that must
|
|
655
|
+
* snapshot via {@link snapshotDescendantPids} *before* this (and any other
|
|
656
|
+
* abort signal) runs, then {@link killPids} after.
|
|
645
657
|
*/
|
|
646
658
|
hardResetQuery() {
|
|
647
659
|
if (this.query) {
|
|
@@ -653,6 +665,60 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
653
665
|
}
|
|
654
666
|
this.abortController?.abort();
|
|
655
667
|
}
|
|
668
|
+
/**
|
|
669
|
+
* Capture every current OS descendant of the CLI subprocess (SIGKILLed
|
|
670
|
+
* later via {@link killPids}). **Call BEFORE `interrupt()` /
|
|
671
|
+
* `abortController.abort()` / `query.close()` — never after**: once the CLI
|
|
672
|
+
* exits, Linux reparents its children immediately, so a later walk finds
|
|
673
|
+
* nothing. Linux-only; `[]` if there's no captured pid or no `/proc`. Full
|
|
674
|
+
* rationale: `_devlog/notes/2026-08-12-abort-kills-tool-subprocess.md`.
|
|
675
|
+
*/
|
|
676
|
+
snapshotDescendantPids() {
|
|
677
|
+
const rootPid = this.cliProcessPid;
|
|
678
|
+
if (rootPid == null) return [];
|
|
679
|
+
return this.collectDescendantPids(rootPid);
|
|
680
|
+
}
|
|
681
|
+
/** SIGKILL every pid in a snapshot from {@link snapshotDescendantPids}. */
|
|
682
|
+
killPids(pids) {
|
|
683
|
+
if (pids.length === 0) return;
|
|
684
|
+
let killed = 0;
|
|
685
|
+
for (const pid of pids) {
|
|
686
|
+
try {
|
|
687
|
+
process.kill(pid, "SIGKILL");
|
|
688
|
+
killed++;
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
this.log.info("swept CLI descendant processes on abort", {
|
|
693
|
+
attempted: pids.length,
|
|
694
|
+
killed
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
/** Direct children of `pid` via Linux procfs — no external binary required. */
|
|
698
|
+
childPids(pid) {
|
|
699
|
+
try {
|
|
700
|
+
const raw = readFileSync(`/proc/${pid}/task/${pid}/children`, "utf8");
|
|
701
|
+
return raw.trim().split(/\s+/).filter(Boolean).map(Number);
|
|
702
|
+
} catch {
|
|
703
|
+
return [];
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/** Every descendant of `rootPid` (excluding `rootPid` itself), breadth-first. */
|
|
707
|
+
collectDescendantPids(rootPid) {
|
|
708
|
+
const descendants = [];
|
|
709
|
+
let frontier = [rootPid];
|
|
710
|
+
while (frontier.length > 0) {
|
|
711
|
+
const next = [];
|
|
712
|
+
for (const pid of frontier) {
|
|
713
|
+
for (const child of this.childPids(pid)) {
|
|
714
|
+
descendants.push(child);
|
|
715
|
+
next.push(child);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
frontier = next;
|
|
719
|
+
}
|
|
720
|
+
return descendants;
|
|
721
|
+
}
|
|
656
722
|
/**
|
|
657
723
|
* Side-effects of the stale-resume recovery path: emit the resume-outcome and
|
|
658
724
|
* non-fatal error events, then reset session state so the recursive prompt()
|
|
@@ -850,6 +916,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
850
916
|
const apiKey = cloud === "default" ? this.config.apiKeys?.anthropic || this.config.env?.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY : void 0;
|
|
851
917
|
this.usingOauthCredential = cloud === "default" && !apiKey;
|
|
852
918
|
const claudePath = this.findClaudeBinary();
|
|
919
|
+
const onStderr = (data) => this.log.warn("claude stderr", { line: data.trim() });
|
|
853
920
|
const options = {
|
|
854
921
|
cwd: this.config.cwd,
|
|
855
922
|
// Load project + user settings so .claude/skills/ are discovered
|
|
@@ -872,7 +939,29 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
872
939
|
// Reasoning effort — passed directly to the SDK
|
|
873
940
|
...this.config.effort ? { effort: this.config.effort } : {},
|
|
874
941
|
hooks: {},
|
|
875
|
-
stderr:
|
|
942
|
+
stderr: onStderr,
|
|
943
|
+
// Spawn the CLI ourselves to capture its OS pid for snapshotDescendantPids()
|
|
944
|
+
// (Query never exposes it otherwise). This replaces the SDK's own default
|
|
945
|
+
// spawn, so we also have to re-wire child.stderr -> onStderr ourselves below.
|
|
946
|
+
spawnClaudeCodeProcess: (spawnOpts) => {
|
|
947
|
+
const child = spawn(spawnOpts.command, spawnOpts.args, {
|
|
948
|
+
cwd: spawnOpts.cwd,
|
|
949
|
+
env: spawnOpts.env,
|
|
950
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
951
|
+
});
|
|
952
|
+
this.cliProcessPid = child.pid ?? null;
|
|
953
|
+
child.stderr?.on("data", (chunk) => onStderr(chunk.toString()));
|
|
954
|
+
child.once("exit", () => {
|
|
955
|
+
if (this.cliProcessPid === child.pid) this.cliProcessPid = null;
|
|
956
|
+
});
|
|
957
|
+
spawnOpts.signal.addEventListener("abort", () => {
|
|
958
|
+
try {
|
|
959
|
+
child.kill();
|
|
960
|
+
} catch {
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
return child;
|
|
964
|
+
},
|
|
876
965
|
...claudePath ? { pathToClaudeCodeExecutable: claudePath } : {},
|
|
877
966
|
...this.config.systemPrompt ? {
|
|
878
967
|
systemPrompt: {
|
|
@@ -1542,6 +1631,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1542
1631
|
* Requests the current SDK query to stop processing via `query.interrupt()` and
|
|
1543
1632
|
* signals the underlying `AbortController`. The driver remains usable after abort.
|
|
1544
1633
|
*
|
|
1634
|
+
* Also SIGKILLs every OS descendant of the CLI subprocess (e.g. a running
|
|
1635
|
+
* Bash command) on either path the turn ends — see
|
|
1636
|
+
* {@link snapshotDescendantPids}'s doc for why that's needed.
|
|
1637
|
+
*
|
|
1545
1638
|
* @remarks Best-effort: if `interrupt()` throws (e.g. query already ended) the
|
|
1546
1639
|
* error is silently swallowed and the abort signal is still sent. A query
|
|
1547
1640
|
* whose generator refuses to unwind within {@link ABORT_GRACE_MS} is torn
|
|
@@ -1551,6 +1644,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1551
1644
|
this.denyPendingQuestion("turn aborted before the question was answered");
|
|
1552
1645
|
const abortedQuery = this.query;
|
|
1553
1646
|
const abortedController = this.abortController;
|
|
1647
|
+
const descendants = this.snapshotDescendantPids();
|
|
1554
1648
|
if (abortedQuery) {
|
|
1555
1649
|
try {
|
|
1556
1650
|
await abortedQuery.interrupt();
|
|
@@ -1569,6 +1663,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1569
1663
|
this.hardResetQuery();
|
|
1570
1664
|
this.completeTurn();
|
|
1571
1665
|
}
|
|
1666
|
+
this.killPids(descendants);
|
|
1572
1667
|
}
|
|
1573
1668
|
/** `true` while the driver is processing a `prompt()` call. */
|
|
1574
1669
|
get isRunning() {
|
|
@@ -1579,9 +1674,14 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1579
1674
|
*
|
|
1580
1675
|
* The driver instance must not be reused after `kill()`. Session IDs are cleared,
|
|
1581
1676
|
* so a new `ClaudeSdkDriver` will start a fresh session.
|
|
1677
|
+
*
|
|
1678
|
+
* Also SIGKILLs every OS descendant of the CLI subprocess (e.g. a running
|
|
1679
|
+
* Bash command) — see {@link snapshotDescendantPids}'s doc for why that
|
|
1680
|
+
* needs to be snapshotted before `query.close()` runs, not after.
|
|
1582
1681
|
*/
|
|
1583
1682
|
kill() {
|
|
1584
1683
|
this.denyPendingQuestion("session terminated before the question was answered");
|
|
1684
|
+
const descendants = this.snapshotDescendantPids();
|
|
1585
1685
|
if (this.query) {
|
|
1586
1686
|
try {
|
|
1587
1687
|
this.query.close();
|
|
@@ -1590,6 +1690,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1590
1690
|
this.query = null;
|
|
1591
1691
|
}
|
|
1592
1692
|
this.abortController?.abort();
|
|
1693
|
+
this.killPids(descendants);
|
|
1593
1694
|
this.running = false;
|
|
1594
1695
|
this.sessionId = "";
|
|
1595
1696
|
this.hasSession = false;
|
|
@@ -1609,6 +1710,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1609
1710
|
}
|
|
1610
1711
|
async resetSession() {
|
|
1611
1712
|
this.denyPendingQuestion("session reset before the question was answered");
|
|
1713
|
+
const descendants = this.snapshotDescendantPids();
|
|
1612
1714
|
if (this.query) {
|
|
1613
1715
|
try {
|
|
1614
1716
|
this.query.close();
|
|
@@ -1617,6 +1719,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1617
1719
|
this.query = null;
|
|
1618
1720
|
}
|
|
1619
1721
|
this.abortController?.abort();
|
|
1722
|
+
this.killPids(descendants);
|
|
1620
1723
|
this.sessionId = "";
|
|
1621
1724
|
this.hasSession = false;
|
|
1622
1725
|
this._lastTokens = null;
|