@mirasoth/soothe-client 0.4.0 → 0.4.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/README.md +39 -21
- package/dist/{chunk-U6RMINYV.js → chunk-YYUVHZ3W.js} +120 -28
- package/dist/chunk-YYUVHZ3W.js.map +1 -0
- package/dist/client-SOJZSF7C.js +7 -0
- package/dist/index.cjs +168 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -41
- package/dist/index.d.ts +91 -41
- package/dist/index.js +52 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-U6RMINYV.js.map +0 -1
- package/dist/client-UNPC32NQ.js +0 -7
- /package/dist/{client-UNPC32NQ.js.map → client-SOJZSF7C.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
# @mirasoth/soothe-client
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Provides a typed protocol-1 message stack, session bootstrap, reconnect/reattach,
|
|
6
|
-
appkit (connection pool, turn runner, event classifier), and a dual-socket
|
|
7
|
-
`DaemonSession` for streamed turns — matching the production Go and Python clients.
|
|
8
|
-
|
|
9
|
-
## Install
|
|
3
|
+
Talk to a running **soothe-daemon** over WebSocket — send prompts, stream agent
|
|
4
|
+
turns, run jobs.
|
|
10
5
|
|
|
11
6
|
```bash
|
|
12
7
|
npm install @mirasoth/soothe-client
|
|
@@ -14,7 +9,7 @@ npm install @mirasoth/soothe-client
|
|
|
14
9
|
npm install sharp
|
|
15
10
|
```
|
|
16
11
|
|
|
17
|
-
Requires Node.js `>=19
|
|
12
|
+
Requires Node.js `>=19` and a local daemon (default `ws://127.0.0.1:8765`).
|
|
18
13
|
|
|
19
14
|
## Quick start
|
|
20
15
|
|
|
@@ -23,15 +18,17 @@ import { DaemonSession } from '@mirasoth/soothe-client';
|
|
|
23
18
|
|
|
24
19
|
const session = new DaemonSession('ws://127.0.0.1:8765');
|
|
25
20
|
await session.connect();
|
|
26
|
-
await session.sendTurn('
|
|
21
|
+
await session.sendTurn('Summarize this in one sentence: agents need tools.');
|
|
27
22
|
|
|
28
|
-
for await (const [
|
|
23
|
+
for await (const [_namespace, mode, data] of session.iterTurnChunks()) {
|
|
29
24
|
console.log(mode, data);
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
await session.close();
|
|
33
28
|
```
|
|
34
29
|
|
|
30
|
+
More patterns: [`examples/`](examples/) (hello → streaming → multi-turn → pool → jobs).
|
|
31
|
+
|
|
35
32
|
## What you get
|
|
36
33
|
|
|
37
34
|
| Need | Use |
|
|
@@ -42,16 +39,23 @@ await session.close();
|
|
|
42
39
|
| Many users / HTTP backend | `ConnectionPool` + `TurnRunner` |
|
|
43
40
|
|
|
44
41
|
`iterTurnChunks` peels leftover prior-goal terminals at turn start, ignores
|
|
45
|
-
premature `soothe.stream.end` until the turn has real progress,
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
premature `soothe.stream.end` until the turn has real progress, drains a short
|
|
43
|
+
post-idle window, and sends `delivery_ack` on terminal frames for daemon drain
|
|
44
|
+
gating.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { CommandClient } from '@mirasoth/soothe-client';
|
|
48
|
+
|
|
49
|
+
const cc = new CommandClient('ws://127.0.0.1:8765', { timeoutMs: 30_000 });
|
|
50
|
+
const created = await cc.jobCreate('Echo: smoke job', '/tmp/workspace');
|
|
51
|
+
await cc.jobStatus(String(created.job_id));
|
|
52
|
+
await cc.jobCancel(String(created.job_id));
|
|
53
|
+
```
|
|
48
54
|
|
|
49
55
|
## Appkit TurnRunner
|
|
50
56
|
|
|
51
57
|
Product backends that pool connections per chat session use `ConnectionPool` +
|
|
52
|
-
`QueryGate` + `TurnRunner` + `EventClassifier
|
|
53
|
-
|
|
54
|
-
Lifecycle knobs (all opt-in; defaults match historical fail-on-timeout behaviour):
|
|
58
|
+
`QueryGate` + `TurnRunner` + `EventClassifier`.
|
|
55
59
|
|
|
56
60
|
| Knob | Default | Notes |
|
|
57
61
|
|------|---------|--------|
|
|
@@ -76,15 +80,23 @@ Lifecycle knobs (all opt-in; defaults match historical fail-on-timeout behaviour
|
|
|
76
80
|
|
|
77
81
|
## API surface
|
|
78
82
|
|
|
79
|
-
- **`
|
|
80
|
-
- **`
|
|
81
|
-
- **`
|
|
83
|
+
- **`DaemonSession`** — dual-socket loop session + `iterTurnChunks` (preferred for chat)
|
|
84
|
+
- **`CommandClient`** — ephemeral connect → one RPC → close (jobs / cron)
|
|
85
|
+
- **`Client`** — long-lived WebSocket, RPC, reconnect/reattach, peel-stale helpers
|
|
86
|
+
- **`ConnectionPool` / `TurnRunner` / `QueryGate` / `EventClassifier` / `SSEBroadcaster`** — multi-user appkit
|
|
82
87
|
- **`connectedWebsocket` / `protocol1Rpc`** — oneshot CLI-style helpers
|
|
83
|
-
- **`bootstrapLoopSession
|
|
88
|
+
- **`bootstrapLoopSession` / `connectWithRetries`** — session helpers
|
|
84
89
|
|
|
85
90
|
See `dist/index.d.ts` or `src/index.ts` for the full export list.
|
|
86
91
|
|
|
87
|
-
##
|
|
92
|
+
## Limitations
|
|
93
|
+
|
|
94
|
+
Autopilot control is WebSocket-only (protocol-1 `autopilot_*` / `job_*`
|
|
95
|
+
request RPCs). Prefer `CommandClient` for job/cron/autopilot one-shots so they
|
|
96
|
+
do not share a streaming socket. Worker event streams use
|
|
97
|
+
`client.autopilotSubscribe()` on a long-lived `Client`.
|
|
98
|
+
|
|
99
|
+
## Develop
|
|
88
100
|
|
|
89
101
|
```bash
|
|
90
102
|
make help # list targets
|
|
@@ -92,8 +104,14 @@ make install # install dependencies
|
|
|
92
104
|
make build # compile to dist/
|
|
93
105
|
make test # unit tests
|
|
94
106
|
make verify # full pre-publish verification
|
|
107
|
+
npm test -- examples/progressive # 01–06 ladder (offline)
|
|
95
108
|
```
|
|
96
109
|
|
|
110
|
+
## Compatibility
|
|
111
|
+
|
|
112
|
+
Same protocol-1 WebSocket contract as `soothe-client-python` and
|
|
113
|
+
`soothe-client-go`.
|
|
114
|
+
|
|
97
115
|
## License
|
|
98
116
|
|
|
99
117
|
MIT — see [LICENSE](./LICENSE).
|
|
@@ -63,7 +63,7 @@ var ConnectionError = class extends Error {
|
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
65
|
var DaemonError = class extends Error {
|
|
66
|
-
/** Numeric error code from the
|
|
66
|
+
/** Numeric error code from the daemon error registry. */
|
|
67
67
|
code;
|
|
68
68
|
/** The daemon's error message text. */
|
|
69
69
|
daemonMessage;
|
|
@@ -621,7 +621,7 @@ function messagesWireTerminal(data) {
|
|
|
621
621
|
import { randomUUID } from "crypto";
|
|
622
622
|
var PROTO_VERSION = "1";
|
|
623
623
|
var DEFAULT_CLIENT_CAPABILITIES = ["streaming", "batch", "heartbeat", "receipts"];
|
|
624
|
-
var CLIENT_VERSION = "0.4.
|
|
624
|
+
var CLIENT_VERSION = "0.4.1";
|
|
625
625
|
function encodeMessage(msg) {
|
|
626
626
|
return JSON.stringify(msg) + "\n";
|
|
627
627
|
}
|
|
@@ -793,7 +793,7 @@ var Client = class extends EventEmitter {
|
|
|
793
793
|
inboundDroppedCount = 0;
|
|
794
794
|
onStreamDegraded = null;
|
|
795
795
|
resolvers = [];
|
|
796
|
-
// Protocol-1 handshake state
|
|
796
|
+
// Protocol-1 handshake state
|
|
797
797
|
handshakeComplete = false;
|
|
798
798
|
negotiatedCapabilities = /* @__PURE__ */ new Set();
|
|
799
799
|
protocolVersion = null;
|
|
@@ -801,12 +801,12 @@ var Client = class extends EventEmitter {
|
|
|
801
801
|
heartbeatIntervalMs = 0;
|
|
802
802
|
heartbeatTimer = null;
|
|
803
803
|
lastPongMonotonic = 0;
|
|
804
|
-
// Mid-session drop signal
|
|
804
|
+
// Mid-session drop signal. The 'disconnected' event is
|
|
805
805
|
// emitted exactly once when the connection drops, carrying a DisconnectCause
|
|
806
806
|
// that distinguishes clean (peer `disconnect`) from unclean (read/write
|
|
807
807
|
// error or missed pong). `disconnFired` guards the once-only delivery.
|
|
808
808
|
disconnFired = false;
|
|
809
|
-
// Pending-request/subscription multiplexer
|
|
809
|
+
// Pending-request/subscription multiplexer. Routes
|
|
810
810
|
// inbound frames by (type, id) instead of discarding non-matching events.
|
|
811
811
|
mux = new Multiplexer();
|
|
812
812
|
deliveryRecvSeq = /* @__PURE__ */ new Map();
|
|
@@ -923,7 +923,7 @@ var Client = class extends EventEmitter {
|
|
|
923
923
|
return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.handshakeComplete;
|
|
924
924
|
}
|
|
925
925
|
// ---------------------------------------------------------------------------
|
|
926
|
-
// Mid-session drop signal + reconnect/reattach
|
|
926
|
+
// Mid-session drop signal + reconnect/reattach
|
|
927
927
|
// ---------------------------------------------------------------------------
|
|
928
928
|
/**
|
|
929
929
|
* Returns whether the connection has dropped (the `'disconnected'` event has
|
|
@@ -958,8 +958,8 @@ var Client = class extends EventEmitter {
|
|
|
958
958
|
}
|
|
959
959
|
}
|
|
960
960
|
/**
|
|
961
|
-
* Re-dials the daemon and re-handshakes after a connection drop
|
|
962
|
-
*
|
|
961
|
+
* Re-dials the daemon and re-handshakes after a connection drop.
|
|
962
|
+
* Does not re-establish loop subscriptions; follow with
|
|
963
963
|
* `reattachAndProbe()` to resume a loop session. The caller should invoke
|
|
964
964
|
* this after the `'disconnected'` event fires. Reuses the same Client,
|
|
965
965
|
* resetting the drop signal and multiplexer.
|
|
@@ -993,7 +993,7 @@ var Client = class extends EventEmitter {
|
|
|
993
993
|
* Returns a `StaleLoopError` when the probe fails; callers should fall back
|
|
994
994
|
* to a fresh `loop_new` bootstrap.
|
|
995
995
|
*
|
|
996
|
-
*
|
|
996
|
+
* Note: connection-level readiness is the handshake's readiness_state
|
|
997
997
|
* (+ daemon_status); loop_get is a loop-scoped probe only, not a readiness
|
|
998
998
|
* probe.
|
|
999
999
|
*/
|
|
@@ -1034,7 +1034,7 @@ var Client = class extends EventEmitter {
|
|
|
1034
1034
|
}
|
|
1035
1035
|
}
|
|
1036
1036
|
// ---------------------------------------------------------------------------
|
|
1037
|
-
// Protocol-1 handshake
|
|
1037
|
+
// Protocol-1 handshake
|
|
1038
1038
|
// ---------------------------------------------------------------------------
|
|
1039
1039
|
/** Send connection_init and wait for connection_ack with readiness "ready". */
|
|
1040
1040
|
async _performHandshake() {
|
|
@@ -1083,7 +1083,7 @@ var Client = class extends EventEmitter {
|
|
|
1083
1083
|
throw new Error(`timeout after ${this.config.daemonReadyTimeout}ms waiting for connection_ack`);
|
|
1084
1084
|
}
|
|
1085
1085
|
// ---------------------------------------------------------------------------
|
|
1086
|
-
// Heartbeat
|
|
1086
|
+
// Heartbeat
|
|
1087
1087
|
// ---------------------------------------------------------------------------
|
|
1088
1088
|
_startHeartbeat() {
|
|
1089
1089
|
if (!this.negotiatedCapabilities.has("heartbeat")) return;
|
|
@@ -1278,7 +1278,7 @@ var Client = class extends EventEmitter {
|
|
|
1278
1278
|
}
|
|
1279
1279
|
}
|
|
1280
1280
|
// ---------------------------------------------------------------------------
|
|
1281
|
-
// Protocol-1 RPC primitives
|
|
1281
|
+
// Protocol-1 RPC primitives
|
|
1282
1282
|
// ---------------------------------------------------------------------------
|
|
1283
1283
|
/**
|
|
1284
1284
|
* Reads the next frame directly from the live socket (via a resolver),
|
|
@@ -1304,16 +1304,16 @@ var Client = class extends EventEmitter {
|
|
|
1304
1304
|
});
|
|
1305
1305
|
}
|
|
1306
1306
|
/**
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1307
|
+
* Sends a `request` envelope and waits for the matching `response` (or
|
|
1308
|
+
* `error`) correlated by `id`. Returns the `result` object.
|
|
1309
|
+
*
|
|
1310
|
+
* Multiplexer-aware: registers a pending RPC wait
|
|
1311
|
+
* keyed by the request id so that, even when a `receiveMessages()` reader
|
|
1312
|
+
* is concurrently active, the matching `response`/`error` is routed to
|
|
1313
|
+
* this caller instead of being discarded or buffered behind a stream.
|
|
1314
|
+
* Non-matching frames are routed to their own waiters by the multiplexer
|
|
1315
|
+
* or flow on to the resolver queue for stream readers.
|
|
1316
|
+
*/
|
|
1317
1317
|
async requestResponse(method, params, responseType, timeout = 15e3) {
|
|
1318
1318
|
const req = requestEnvelope(method, params);
|
|
1319
1319
|
const rid = req.id;
|
|
@@ -1464,7 +1464,7 @@ var Client = class extends EventEmitter {
|
|
|
1464
1464
|
return ev;
|
|
1465
1465
|
}
|
|
1466
1466
|
// ---------------------------------------------------------------------------
|
|
1467
|
-
// High-level API methods
|
|
1467
|
+
// High-level API methods
|
|
1468
1468
|
// ---------------------------------------------------------------------------
|
|
1469
1469
|
/** Sends user input to the daemon (loop_input notification; requires loopID). */
|
|
1470
1470
|
sendInput(text, options) {
|
|
@@ -1503,7 +1503,7 @@ var Client = class extends EventEmitter {
|
|
|
1503
1503
|
return this.notify("slash_command", { cmd });
|
|
1504
1504
|
}
|
|
1505
1505
|
// ---------------------------------------------------------------------------
|
|
1506
|
-
// Loop lifecycle methods
|
|
1506
|
+
// Loop lifecycle methods
|
|
1507
1507
|
// ---------------------------------------------------------------------------
|
|
1508
1508
|
/** Requests the daemon to create a new StrangeLoop and waits for the response. */
|
|
1509
1509
|
sendLoopNew(opts) {
|
|
@@ -1598,7 +1598,7 @@ var Client = class extends EventEmitter {
|
|
|
1598
1598
|
sendLoopCardsFetch(loopID) {
|
|
1599
1599
|
return this.sendMessage(requestEnvelope("loop_cards_fetch", { loop_id: loopID }));
|
|
1600
1600
|
}
|
|
1601
|
-
/** Requests the full loop history
|
|
1601
|
+
/** Requests the full loop history. */
|
|
1602
1602
|
sendLoopHistoryFetch(loopID) {
|
|
1603
1603
|
return this.sendMessage(requestEnvelope("loop_history_fetch", { loop_id: loopID }));
|
|
1604
1604
|
}
|
|
@@ -1693,7 +1693,7 @@ var Client = class extends EventEmitter {
|
|
|
1693
1693
|
);
|
|
1694
1694
|
}
|
|
1695
1695
|
// ---------------------------------------------------------------------------
|
|
1696
|
-
//
|
|
1696
|
+
// Job IPC methods
|
|
1697
1697
|
// ---------------------------------------------------------------------------
|
|
1698
1698
|
/** Creates an autopilot job and waits for the response. */
|
|
1699
1699
|
createJob(goal, verificationRules, workspace, timeout) {
|
|
@@ -1728,6 +1728,98 @@ var Client = class extends EventEmitter {
|
|
|
1728
1728
|
if (goalId) params.goal_id = goalId;
|
|
1729
1729
|
return this.requestResponse("job_guidance", params, "job_guidance", timeout ?? 3e4);
|
|
1730
1730
|
}
|
|
1731
|
+
// ---------------------------------------------------------------------------
|
|
1732
|
+
// Autopilot goal RPCs (protocol-1 request methods)
|
|
1733
|
+
// ---------------------------------------------------------------------------
|
|
1734
|
+
/** Return autopilot scheduler status (running / dreaming / pool). */
|
|
1735
|
+
autopilotStatus(timeout) {
|
|
1736
|
+
return this.requestResponse("autopilot_status", {}, "autopilot_status", timeout ?? 15e3);
|
|
1737
|
+
}
|
|
1738
|
+
/** Submit a new autopilot goal (returns goal_id). */
|
|
1739
|
+
autopilotSubmit(description, opts) {
|
|
1740
|
+
const params = {
|
|
1741
|
+
description,
|
|
1742
|
+
priority: opts?.priority ?? 50
|
|
1743
|
+
};
|
|
1744
|
+
if (opts?.workspace) params.workspace = opts.workspace;
|
|
1745
|
+
return this.requestResponse(
|
|
1746
|
+
"autopilot_submit",
|
|
1747
|
+
params,
|
|
1748
|
+
"autopilot_submit",
|
|
1749
|
+
opts?.timeout ?? 15e3
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
/** List all goals (including non-root children). */
|
|
1753
|
+
autopilotListGoals(timeout) {
|
|
1754
|
+
return this.requestResponse(
|
|
1755
|
+
"autopilot_list_goals",
|
|
1756
|
+
{},
|
|
1757
|
+
"autopilot_list_goals",
|
|
1758
|
+
timeout ?? 15e3
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
/** Fetch one goal by id. */
|
|
1762
|
+
autopilotGetGoal(goalId, timeout) {
|
|
1763
|
+
return this.requestResponse(
|
|
1764
|
+
"autopilot_get_goal",
|
|
1765
|
+
{ goal_id: goalId },
|
|
1766
|
+
"autopilot_get_goal",
|
|
1767
|
+
timeout ?? 15e3
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
/** Cancel a goal and its non-terminal descendants. */
|
|
1771
|
+
autopilotCancelGoal(goalId, timeout) {
|
|
1772
|
+
return this.requestResponse(
|
|
1773
|
+
"autopilot_cancel_goal",
|
|
1774
|
+
{ goal_id: goalId },
|
|
1775
|
+
"autopilot_cancel_goal",
|
|
1776
|
+
timeout ?? 15e3
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
/** Cancel every open (non-terminal) goal. */
|
|
1780
|
+
autopilotCancelAll(timeout) {
|
|
1781
|
+
return this.requestResponse(
|
|
1782
|
+
"autopilot_cancel_all",
|
|
1783
|
+
{},
|
|
1784
|
+
"autopilot_cancel_all",
|
|
1785
|
+
timeout ?? 15e3
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
/** Exit dreaming mode and resume scheduling. */
|
|
1789
|
+
autopilotWake(timeout) {
|
|
1790
|
+
return this.requestResponse("autopilot_wake", {}, "autopilot_wake", timeout ?? 15e3);
|
|
1791
|
+
}
|
|
1792
|
+
/** Force dreaming mode. */
|
|
1793
|
+
autopilotDream(timeout) {
|
|
1794
|
+
return this.requestResponse("autopilot_dream", {}, "autopilot_dream", timeout ?? 15e3);
|
|
1795
|
+
}
|
|
1796
|
+
/** Resume a suspended or blocked goal. */
|
|
1797
|
+
autopilotResume(goalId, timeout) {
|
|
1798
|
+
return this.requestResponse(
|
|
1799
|
+
"autopilot_resume",
|
|
1800
|
+
{ goal_id: goalId },
|
|
1801
|
+
"autopilot_resume",
|
|
1802
|
+
timeout ?? 15e3
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
/** List root goals only (jobs). Prefer createJob / getJobStatus for job control. */
|
|
1806
|
+
autopilotListJobs(timeout) {
|
|
1807
|
+
return this.requestResponse(
|
|
1808
|
+
"autopilot_list_jobs",
|
|
1809
|
+
{},
|
|
1810
|
+
"autopilot_list_jobs",
|
|
1811
|
+
timeout ?? 15e3
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
/** Get a root job with DAG snapshot. Prefer getJobStatus / getJobDag. */
|
|
1815
|
+
autopilotGetJob(jobId, timeout) {
|
|
1816
|
+
return this.requestResponse(
|
|
1817
|
+
"autopilot_get_job",
|
|
1818
|
+
{ job_id: jobId },
|
|
1819
|
+
"autopilot_get_job",
|
|
1820
|
+
timeout ?? 15e3
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1731
1823
|
/** Subscribes to autopilot worker events. */
|
|
1732
1824
|
autopilotSubscribe(timeout) {
|
|
1733
1825
|
return this.subscribe("autopilot_events", {}, timeout ?? 15e3);
|
|
@@ -1738,7 +1830,7 @@ var Client = class extends EventEmitter {
|
|
|
1738
1830
|
return this._requestResponseForEnvelope(req, "autopilot_unsubscribe", timeout ?? 15e3);
|
|
1739
1831
|
}
|
|
1740
1832
|
// ---------------------------------------------------------------------------
|
|
1741
|
-
//
|
|
1833
|
+
// Cron IPC methods
|
|
1742
1834
|
// ---------------------------------------------------------------------------
|
|
1743
1835
|
/** Creates a scheduled job from natural language. */
|
|
1744
1836
|
cronAdd(text, priority, timeout) {
|
|
@@ -1880,4 +1972,4 @@ export {
|
|
|
1880
1972
|
inboundNeedsDeliveryAck,
|
|
1881
1973
|
Client
|
|
1882
1974
|
};
|
|
1883
|
-
//# sourceMappingURL=chunk-
|
|
1975
|
+
//# sourceMappingURL=chunk-YYUVHZ3W.js.map
|