pinomad 0.11.0 → 0.13.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/README.md +1 -1
- package/dist/{chunk-wb10efyq.js → chunk-445cevpm.js} +1 -1
- package/dist/{chunk-jt34g8st.js → chunk-4fferbxw.js} +247 -27
- package/dist/{chunk-67gq282t.js → chunk-53xv3fny.js} +725 -75
- package/dist/{chunk-44pnts98.js → chunk-563fnad9.js} +1 -1
- package/dist/{chunk-b457q0tm.js → chunk-6th9xv39.js} +11 -8
- package/dist/{chunk-ncbqa8jk.js → chunk-7mn38n4f.js} +1 -1
- package/dist/{chunk-as6pyf00.js → chunk-90g1cg9n.js} +1 -1
- package/dist/{chunk-9h5vawnm.js → chunk-aapsc9g0.js} +270 -66
- package/dist/{chunk-n1m1tjrk.js → chunk-d4tq62yr.js} +57 -16
- package/dist/{chunk-gtdpjqa3.js → chunk-d9mq5d46.js} +205 -49
- package/dist/{chunk-y127fe1a.js → chunk-g3ahp7b9.js} +11 -0
- package/dist/{chunk-xqycs43g.js → chunk-kmzzjv53.js} +83 -8
- package/dist/{chunk-4dcs8sbj.js → chunk-m51mq8vh.js} +43 -21
- package/dist/{chunk-tj57aqav.js → chunk-maqv34cs.js} +2 -2
- package/dist/chunk-nq57fdnq.js +106 -0
- package/dist/{chunk-h5q2y57m.js → chunk-ra86wjp5.js} +10 -4
- package/dist/{chunk-sd5fkxjj.js → chunk-s0h7c7da.js} +1 -1
- package/dist/chunk-sd640dt7.js +22 -0
- package/dist/{chunk-6d16c2fv.js → chunk-t6fw0wa9.js} +13 -6
- package/dist/{chunk-vvj9b1gc.js → chunk-wd7aracm.js} +188 -54
- package/dist/pinomad.js +15 -14
- package/package.json +13 -11
- package/web/assets/astro-DOly5024.js +1 -0
- package/web/assets/c-BIGW1oBm.js +1 -0
- package/web/assets/core-BHm2gdHL.js +4 -0
- package/web/assets/cpp-DddhVWhK.js +1 -0
- package/web/assets/csharp-B_TYUkTI.js +1 -0
- package/web/assets/css-Z72CF80r.js +1 -0
- package/web/assets/dart-CtKP3W-j.js +1 -0
- package/web/assets/diff-D97Zzqfu.js +1 -0
- package/web/assets/dockerfile-BcOcwvcX.js +1 -0
- package/web/assets/elixir-DkJLSxtU.js +1 -0
- package/web/assets/engine-javascript-BHPwbUfN.js +141 -0
- package/web/assets/engine-oniguruma-CQjQPVHy.js +1 -0
- package/web/assets/fish-BvzEVeQv.js +1 -0
- package/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/web/assets/go-C27-OAKa.js +1 -0
- package/web/assets/graphql-ChdNCCLP.js +1 -0
- package/web/assets/haskell-Df6bDoY_.js +1 -0
- package/web/assets/hcl-BWvSN4gD.js +1 -0
- package/web/assets/highlighted-diff-DrHvSLK9.js +175 -0
- package/web/assets/highlights-1aLwssz-.js +4 -0
- package/web/assets/highlights-themes-C1p4pFYS.js +1 -0
- package/web/assets/html-BlS82ilI.js +1 -0
- package/web/assets/index-A_AUJoO6.css +1 -0
- package/web/assets/index-BafYMKiN.js +396 -0
- package/web/assets/ini-BEwlwnbL.js +1 -0
- package/web/assets/java-CylS5w8V.js +1 -0
- package/web/assets/javascript-wDzz0qaB.js +1 -0
- package/web/assets/json-Cp-IABpG.js +1 -0
- package/web/assets/jsonc-Des-eS-w.js +1 -0
- package/web/assets/jsx-g9-lgVsj.js +1 -0
- package/web/assets/kotlin-BdnUsdx6.js +1 -0
- package/web/assets/less-B1dDrJ26.js +1 -0
- package/web/assets/lua-BaeVxFsk.js +1 -0
- package/web/assets/make-CHLpvVh8.js +1 -0
- package/web/assets/markdown-Cvjx9yec.js +1 -0
- package/web/assets/nix-CwoSXNpI.js +1 -0
- package/web/assets/objective-c-DXmwc3jG.js +1 -0
- package/web/assets/php-DoruYMF0.js +1 -0
- package/web/assets/pierre-dark-CyvmCCZW.js +1 -0
- package/web/assets/pierre-dark-VKo95ZgA.js +1 -0
- package/web/assets/pierre-light-480U9XYS.js +1 -0
- package/web/assets/pierre-light-BVSW-Xev.js +1 -0
- package/web/assets/postcss-DnzrXTVv.js +1 -0
- package/web/assets/powershell-Dd0kTxKP.js +1 -0
- package/web/assets/prisma-Vru482bI.js +1 -0
- package/web/assets/proto-C7zT0LnQ.js +1 -0
- package/web/assets/python-B6aJPvgy.js +1 -0
- package/web/assets/ruby-B43yHlf9.js +1 -0
- package/web/assets/rust-DPvrY3IQ.js +1 -0
- package/web/assets/scala-CqE71os6.js +1 -0
- package/web/assets/scss-Cw6XMfVs.js +1 -0
- package/web/assets/shellscript-Yzrsuije.js +1 -0
- package/web/assets/shiki-BwyCm3kZ.js +2 -0
- package/web/assets/shiki-langs-CMlALLSg.js +2 -0
- package/web/assets/shiki-wasm-CgFRq91U.js +1 -0
- package/web/assets/sql-DkvoVjww.js +1 -0
- package/web/assets/stream-tokenizer-BlgIhw0y.js +4 -0
- package/web/assets/svelte-DoIXUgJc.js +1 -0
- package/web/assets/swift-C2oV4EkX.js +1 -0
- package/web/assets/terraform-BETggiCN.js +1 -0
- package/web/assets/theming-themes-DDvoqEqT.js +1 -0
- package/web/assets/toml-vGWfd6FD.js +1 -0
- package/web/assets/tsx-COt5Ahok.js +1 -0
- package/web/assets/typescript-BPQ3VLAy.js +1 -0
- package/web/assets/vue-CwSeJimo.js +1 -0
- package/web/assets/xml-sdJ4AIDG.js +1 -0
- package/web/assets/xterm-view-6GBZ9nXN.css +32 -0
- package/web/assets/xterm-view-BnKOnF3C.js +38 -0
- package/web/assets/yaml-Buea-lGh.js +1 -0
- package/web/assets/zig-VOosw3JB.js +1 -0
- package/web/index.html +2 -2
- package/dist/chunk-etf7ef97.js +0 -55
- package/dist/chunk-greb3dp6.js +0 -22
- package/web/assets/index-BhHrKpoo.js +0 -395
- package/web/assets/index-DoyHMPwf.css +0 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ A self-hosted coding agent built on [Pi Durable](https://www.npmjs.com/package/@
|
|
|
17
17
|
## Quick start
|
|
18
18
|
|
|
19
19
|
1. **Create an account** at [app.pinomad.si](https://app.pinomad.si/) (sign-up is by invite).
|
|
20
|
-
2. **Add your host.** On the machine that should run the agent (macOS or Linux), run the two lines below. `pinomad up`
|
|
20
|
+
2. **Add your host.** On the machine that should run the agent (macOS or Linux), run the two lines below. `pinomad up` opens a page in your browser; sign in and confirm to add the host to your account. It then runs as a service. Without a browser on that machine (over SSH, say), it prints a code to enter on any signed-in device instead.
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
23
|
curl -fsSL https://pinomad.si/install.sh | sh
|
|
@@ -16,7 +16,7 @@ var NOT_BUILT = "Web client not built: run bun run build";
|
|
|
16
16
|
var SECURITY_HEADERS = {
|
|
17
17
|
"content-security-policy": [
|
|
18
18
|
"default-src 'self'",
|
|
19
|
-
"script-src 'self'",
|
|
19
|
+
"script-src 'self' 'wasm-unsafe-eval'",
|
|
20
20
|
"style-src 'self' 'unsafe-inline'",
|
|
21
21
|
"img-src 'self' data:",
|
|
22
22
|
"connect-src 'self' ws: wss:",
|
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
MAX_ATTACHMENTS_PER_MESSAGE,
|
|
4
4
|
PROTOCOL,
|
|
5
5
|
RTC_DATA_CHANNEL_LABEL,
|
|
6
|
+
TRANSCRIPT_PAGE,
|
|
6
7
|
UNAUTHORIZED_CLOSE_CODE,
|
|
8
|
+
addEarlier,
|
|
7
9
|
checkoutOf,
|
|
8
10
|
conversationStream,
|
|
9
11
|
docStream,
|
|
@@ -11,11 +13,13 @@ import {
|
|
|
11
13
|
hostStreams,
|
|
12
14
|
hostSupportsCall,
|
|
13
15
|
hostSupportsFeature,
|
|
16
|
+
isBusy,
|
|
14
17
|
isRtcHello,
|
|
15
18
|
organize,
|
|
16
19
|
parseRtcMaxMessageSize,
|
|
17
|
-
rtcSelectedPair
|
|
18
|
-
|
|
20
|
+
rtcSelectedPair,
|
|
21
|
+
withEarlier
|
|
22
|
+
} from "./chunk-kmzzjv53.js";
|
|
19
23
|
import {
|
|
20
24
|
SECURE_PROLOGUE,
|
|
21
25
|
SecureSession,
|
|
@@ -286,11 +290,12 @@ function webSocketTransport(url, token) {
|
|
|
286
290
|
};
|
|
287
291
|
}
|
|
288
292
|
|
|
289
|
-
// packages/protocol/src/
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
return live?.run !== undefined;
|
|
293
|
+
// packages/protocol/src/terminals.ts
|
|
294
|
+
function isTerminalsHello(value) {
|
|
295
|
+
return value !== null && typeof value === "object" && typeof value.limit === "number";
|
|
293
296
|
}
|
|
297
|
+
var terminalsStream = (id) => `terminals:${id}`;
|
|
298
|
+
var terminalStream = (id) => `terminal:${id}`;
|
|
294
299
|
|
|
295
300
|
// packages/protocol/src/remote-durable.ts
|
|
296
301
|
var DEFAULT_CANDIDATE_TIMEOUTS = {
|
|
@@ -389,6 +394,8 @@ class RemoteClient {
|
|
|
389
394
|
#options;
|
|
390
395
|
#listeners = new Set;
|
|
391
396
|
#values = new Map;
|
|
397
|
+
#earlier = new Map;
|
|
398
|
+
#loadingEarlier = new Map;
|
|
392
399
|
#wanted = new Set;
|
|
393
400
|
#fresh = new Set;
|
|
394
401
|
#docs = [];
|
|
@@ -458,6 +465,11 @@ class RemoteClient {
|
|
|
458
465
|
#helloModels = [];
|
|
459
466
|
#helloDefaults = {};
|
|
460
467
|
#hostVersion;
|
|
468
|
+
#hostTerminals;
|
|
469
|
+
#terminalWatchers = new Map;
|
|
470
|
+
#terminalSinks = new Map;
|
|
471
|
+
#terminalPositions = new Map;
|
|
472
|
+
#terminalSizes = new Map;
|
|
461
473
|
#attempt = 0;
|
|
462
474
|
#rtcAttemptNonce = randomHex(8);
|
|
463
475
|
#closed = false;
|
|
@@ -710,6 +722,7 @@ class RemoteClient {
|
|
|
710
722
|
this.#helloModels = frame.models;
|
|
711
723
|
this.#helloDefaults = frame.defaults ?? {};
|
|
712
724
|
this.#hostVersion = frame.hostVersion;
|
|
725
|
+
this.#hostTerminals = isTerminalsHello(frame.terminals) ? frame.terminals : undefined;
|
|
713
726
|
this.#fresh.clear();
|
|
714
727
|
const covered = new Set(hostStreams(hostMinor));
|
|
715
728
|
for (const name of HOST_STREAM_NAMES) {
|
|
@@ -727,6 +740,7 @@ class RemoteClient {
|
|
|
727
740
|
if (this.#side !== undefined)
|
|
728
741
|
for (const stream of this.#conversationStreams(this.#side))
|
|
729
742
|
this.#wanted.add(stream);
|
|
743
|
+
this.#terminalsAvailability();
|
|
730
744
|
for (const stream of this.#wanted)
|
|
731
745
|
this.#subscribe(stream);
|
|
732
746
|
if (frame.endpointSet !== undefined) {
|
|
@@ -815,6 +829,7 @@ class RemoteClient {
|
|
|
815
829
|
this.#resolveReady({
|
|
816
830
|
view: this.#viewSource(),
|
|
817
831
|
controller: this.#controller(),
|
|
832
|
+
terminals: this.#terminals(),
|
|
818
833
|
setConnectionMode: (mode) => this.setConnectionMode(mode),
|
|
819
834
|
rtcDiagnostics: () => this.rtcDiagnostics(),
|
|
820
835
|
close: () => this.#close()
|
|
@@ -828,6 +843,9 @@ class RemoteClient {
|
|
|
828
843
|
await this.#recoverSubmissions();
|
|
829
844
|
if (!this.#isActive(dial))
|
|
830
845
|
return;
|
|
846
|
+
for (const [terminalId, size] of this.#terminalSizes) {
|
|
847
|
+
this.#callNow("resizeTerminal", { terminalId, ...size }).catch(() => {});
|
|
848
|
+
}
|
|
831
849
|
this.#finishSyncGate();
|
|
832
850
|
this.#afterCarrierReady(dial);
|
|
833
851
|
}
|
|
@@ -1961,7 +1979,14 @@ class RemoteClient {
|
|
|
1961
1979
|
case "snapshot":
|
|
1962
1980
|
if (!this.#wanted.has(frame.stream))
|
|
1963
1981
|
return;
|
|
1982
|
+
if (frame.stream.startsWith("terminals:")) {
|
|
1983
|
+
this.#values.set(frame.stream, frame.value);
|
|
1984
|
+
this.#notifyTerminals(frame.stream, { kind: "ready", list: frame.value });
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1964
1987
|
this.#values.set(frame.stream, frame.value);
|
|
1988
|
+
this.#earlier.delete(frame.stream);
|
|
1989
|
+
this.#loadingEarlier.delete(frame.stream);
|
|
1965
1990
|
this.#fresh.add(frame.stream);
|
|
1966
1991
|
this.#streamVersions.set(frame.stream, (this.#streamVersions.get(frame.stream) ?? 0) + 1);
|
|
1967
1992
|
this.#refresh();
|
|
@@ -1974,9 +1999,23 @@ class RemoteClient {
|
|
|
1974
1999
|
this.#values.set(frame.stream, applyImmutable(this.#values.get(frame.stream), frame.ops));
|
|
1975
2000
|
this.#refresh();
|
|
1976
2001
|
return;
|
|
2002
|
+
case "output": {
|
|
2003
|
+
const sink = this.#terminalSinks.get(frame.stream);
|
|
2004
|
+
if (sink === undefined || !this.#wanted.has(frame.stream))
|
|
2005
|
+
return;
|
|
2006
|
+
this.#terminalPositions.set(frame.stream, frame.seq);
|
|
2007
|
+
sink.output(frame.data, frame.reset === true);
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
1977
2010
|
case "ended": {
|
|
2011
|
+
if (frame.stream.startsWith("terminals:") || frame.stream.startsWith("terminal:")) {
|
|
2012
|
+
this.#endTerminalStream(frame.stream, frame.reason);
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
1978
2015
|
this.#wanted.delete(frame.stream);
|
|
1979
2016
|
this.#values.delete(frame.stream);
|
|
2017
|
+
this.#earlier.delete(frame.stream);
|
|
2018
|
+
this.#loadingEarlier.delete(frame.stream);
|
|
1980
2019
|
this.#fresh.delete(frame.stream);
|
|
1981
2020
|
const ended = new Error(`Stream ${frame.stream} ended: ${frame.reason}`);
|
|
1982
2021
|
for (const waiter of this.#snapshotWaiters.get(frame.stream)?.splice(0) ?? []) {
|
|
@@ -2062,6 +2101,8 @@ class RemoteClient {
|
|
|
2062
2101
|
continue;
|
|
2063
2102
|
this.#wanted.delete(stream);
|
|
2064
2103
|
this.#values.delete(stream);
|
|
2104
|
+
this.#earlier.delete(stream);
|
|
2105
|
+
this.#loadingEarlier.delete(stream);
|
|
2065
2106
|
this.#fresh.delete(stream);
|
|
2066
2107
|
for (const waiter of this.#snapshotWaiters.get(stream)?.splice(0) ?? [])
|
|
2067
2108
|
waiter.resolve();
|
|
@@ -2070,7 +2111,162 @@ class RemoteClient {
|
|
|
2070
2111
|
}
|
|
2071
2112
|
#subscribe(stream) {
|
|
2072
2113
|
const refs = stream.startsWith("conversation:") && hostSupportsFeature(this.#hostMinor, "imageRefs");
|
|
2073
|
-
this.#
|
|
2114
|
+
const since = this.#terminalPositions.get(stream);
|
|
2115
|
+
const window = this.#window(stream);
|
|
2116
|
+
this.#send({
|
|
2117
|
+
type: "subscribe",
|
|
2118
|
+
stream,
|
|
2119
|
+
...refs ? { images: "ref" } : {},
|
|
2120
|
+
...since === undefined ? {} : { since },
|
|
2121
|
+
...window === undefined ? {} : { window }
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
#window(stream) {
|
|
2125
|
+
if (!stream.startsWith("conversation:") || !hostSupportsFeature(this.#hostMinor, "transcriptPaging"))
|
|
2126
|
+
return;
|
|
2127
|
+
const from = this.#shownView(stream)?.entries[0]?.id;
|
|
2128
|
+
return from === undefined ? { limit: TRANSCRIPT_PAGE } : { limit: TRANSCRIPT_PAGE, from };
|
|
2129
|
+
}
|
|
2130
|
+
#shownView(stream) {
|
|
2131
|
+
const view = this.#values.get(stream);
|
|
2132
|
+
return view === undefined ? undefined : withEarlier(view, this.#earlier.get(stream));
|
|
2133
|
+
}
|
|
2134
|
+
#loadEarlier(id) {
|
|
2135
|
+
const stream = conversationStream(id);
|
|
2136
|
+
const inFlight = this.#loadingEarlier.get(stream);
|
|
2137
|
+
if (inFlight !== undefined)
|
|
2138
|
+
return inFlight;
|
|
2139
|
+
const requested = this.#shownView(stream)?.before;
|
|
2140
|
+
if (requested === undefined)
|
|
2141
|
+
return Promise.resolve();
|
|
2142
|
+
const load = this.#call("earlierEntries", { conversationId: id, before: requested, limit: TRANSCRIPT_PAGE }).then((page) => {
|
|
2143
|
+
if (this.#loadingEarlier.get(stream) !== load)
|
|
2144
|
+
return;
|
|
2145
|
+
const shown = this.#shownView(stream);
|
|
2146
|
+
if (shown === undefined)
|
|
2147
|
+
return;
|
|
2148
|
+
const earlier = addEarlier(shown, this.#earlier.get(stream), requested, page);
|
|
2149
|
+
if (earlier === undefined)
|
|
2150
|
+
return;
|
|
2151
|
+
this.#earlier.set(stream, earlier);
|
|
2152
|
+
this.#refresh();
|
|
2153
|
+
}).finally(() => {
|
|
2154
|
+
if (this.#loadingEarlier.get(stream) === load)
|
|
2155
|
+
this.#loadingEarlier.delete(stream);
|
|
2156
|
+
});
|
|
2157
|
+
this.#loadingEarlier.set(stream, load);
|
|
2158
|
+
return load;
|
|
2159
|
+
}
|
|
2160
|
+
#terminalsUnavailable() {
|
|
2161
|
+
if (!hostSupportsFeature(this.#hostMinor, "terminals")) {
|
|
2162
|
+
return "The host is too old for terminals (needs protocol 5.27); run `pinomad upgrade` on the host";
|
|
2163
|
+
}
|
|
2164
|
+
if (this.#hostTerminals === undefined)
|
|
2165
|
+
return "The host couldn't load its terminal module; its log says which package is missing";
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
#terminalsAvailability() {
|
|
2169
|
+
const reason = this.#terminalsUnavailable();
|
|
2170
|
+
if (reason === undefined) {
|
|
2171
|
+
for (const stream of this.#terminalWatchers.keys())
|
|
2172
|
+
this.#wanted.add(stream);
|
|
2173
|
+
for (const stream of this.#terminalSinks.keys())
|
|
2174
|
+
this.#wanted.add(stream);
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
for (const stream of [...this.#terminalWatchers.keys()]) {
|
|
2178
|
+
this.#wanted.delete(stream);
|
|
2179
|
+
this.#values.delete(stream);
|
|
2180
|
+
this.#notifyTerminals(stream, { kind: "unavailable", reason });
|
|
2181
|
+
}
|
|
2182
|
+
for (const stream of [...this.#terminalSinks.keys()])
|
|
2183
|
+
this.#endTerminalStream(stream, reason);
|
|
2184
|
+
}
|
|
2185
|
+
#notifyTerminals(stream, state) {
|
|
2186
|
+
for (const listener of [...this.#terminalWatchers.get(stream) ?? []])
|
|
2187
|
+
listener(state);
|
|
2188
|
+
}
|
|
2189
|
+
#endTerminalStream(stream, reason) {
|
|
2190
|
+
this.#wanted.delete(stream);
|
|
2191
|
+
this.#values.delete(stream);
|
|
2192
|
+
if (stream.startsWith("terminals:")) {
|
|
2193
|
+
this.#notifyTerminals(stream, { kind: "unavailable", reason });
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
const sink = this.#terminalSinks.get(stream);
|
|
2197
|
+
this.#terminalSinks.delete(stream);
|
|
2198
|
+
this.#terminalPositions.delete(stream);
|
|
2199
|
+
this.#terminalSizes.delete(stream.slice("terminal:".length));
|
|
2200
|
+
sink?.ended(reason);
|
|
2201
|
+
}
|
|
2202
|
+
#terminalCall(method, args) {
|
|
2203
|
+
const reason = this.#terminalsUnavailable();
|
|
2204
|
+
return reason === undefined ? this.#call(method, args) : Promise.reject(new Error(reason));
|
|
2205
|
+
}
|
|
2206
|
+
#terminals() {
|
|
2207
|
+
return {
|
|
2208
|
+
watch: (conversationId, listener) => {
|
|
2209
|
+
const stream = terminalsStream(conversationId);
|
|
2210
|
+
let listeners = this.#terminalWatchers.get(stream);
|
|
2211
|
+
const first = listeners === undefined;
|
|
2212
|
+
if (listeners === undefined)
|
|
2213
|
+
this.#terminalWatchers.set(stream, listeners = new Set);
|
|
2214
|
+
listeners.add(listener);
|
|
2215
|
+
const reason = this.#terminalsUnavailable();
|
|
2216
|
+
const list = this.#values.get(stream);
|
|
2217
|
+
listener(reason !== undefined ? { kind: "unavailable", reason } : list !== undefined ? { kind: "ready", list } : { kind: "loading" });
|
|
2218
|
+
if (first && reason === undefined) {
|
|
2219
|
+
this.#wanted.add(stream);
|
|
2220
|
+
if (this.#helloReceived)
|
|
2221
|
+
this.#subscribe(stream);
|
|
2222
|
+
}
|
|
2223
|
+
return () => {
|
|
2224
|
+
if (!listeners.delete(listener) || listeners.size > 0)
|
|
2225
|
+
return;
|
|
2226
|
+
this.#terminalWatchers.delete(stream);
|
|
2227
|
+
if (!this.#wanted.delete(stream))
|
|
2228
|
+
return;
|
|
2229
|
+
this.#values.delete(stream);
|
|
2230
|
+
this.#send({ type: "unsubscribe", stream });
|
|
2231
|
+
};
|
|
2232
|
+
},
|
|
2233
|
+
attach: (terminalId, sink) => {
|
|
2234
|
+
const stream = terminalStream(terminalId);
|
|
2235
|
+
const reason = this.#terminalsUnavailable();
|
|
2236
|
+
if (reason !== undefined) {
|
|
2237
|
+
sink.ended(reason);
|
|
2238
|
+
return () => {};
|
|
2239
|
+
}
|
|
2240
|
+
if (this.#terminalSinks.has(stream) && this.#wanted.has(stream))
|
|
2241
|
+
this.#send({ type: "unsubscribe", stream });
|
|
2242
|
+
this.#terminalSinks.set(stream, sink);
|
|
2243
|
+
this.#terminalPositions.delete(stream);
|
|
2244
|
+
this.#wanted.add(stream);
|
|
2245
|
+
if (this.#helloReceived)
|
|
2246
|
+
this.#subscribe(stream);
|
|
2247
|
+
return () => {
|
|
2248
|
+
if (this.#terminalSinks.get(stream) !== sink)
|
|
2249
|
+
return;
|
|
2250
|
+
this.#terminalSinks.delete(stream);
|
|
2251
|
+
this.#terminalPositions.delete(stream);
|
|
2252
|
+
this.#terminalSizes.delete(terminalId);
|
|
2253
|
+
if (this.#wanted.delete(stream))
|
|
2254
|
+
this.#send({ type: "unsubscribe", stream });
|
|
2255
|
+
};
|
|
2256
|
+
},
|
|
2257
|
+
open: (conversationId, size) => this.#terminalCall("openTerminal", { conversationId, ...size }).then(({ terminalId }) => terminalId),
|
|
2258
|
+
input: (terminalId, data) => {
|
|
2259
|
+
this.#terminalCall("terminalInput", { terminalId, data }).catch(() => {});
|
|
2260
|
+
},
|
|
2261
|
+
resize: (terminalId, size) => {
|
|
2262
|
+
if (this.#terminalSinks.has(terminalStream(terminalId)))
|
|
2263
|
+
this.#terminalSizes.set(terminalId, size);
|
|
2264
|
+
this.#terminalCall("resizeTerminal", { terminalId, ...size }).catch(() => {});
|
|
2265
|
+
},
|
|
2266
|
+
rename: (terminalId, name) => this.#terminalCall("renameTerminal", { terminalId, name }).then(() => {}),
|
|
2267
|
+
restart: (terminalId, size) => this.#terminalCall("restartTerminal", { terminalId, ...this.#terminalSizes.get(terminalId) ?? size }).then(() => {}),
|
|
2268
|
+
close: (terminalId) => this.#terminalCall("closeTerminal", { terminalId }).then(() => {})
|
|
2269
|
+
};
|
|
2074
2270
|
}
|
|
2075
2271
|
#host(name) {
|
|
2076
2272
|
return this.#values.get(name);
|
|
@@ -2172,22 +2368,33 @@ class RemoteClient {
|
|
|
2172
2368
|
record.error = undefined;
|
|
2173
2369
|
this.#clearSubmissionTimer(record);
|
|
2174
2370
|
}
|
|
2175
|
-
#entriesOf(id) {
|
|
2176
|
-
if (id === undefined)
|
|
2177
|
-
return [];
|
|
2178
|
-
const view = this.#values.get(conversationStream(id));
|
|
2179
|
-
return view?.entries ?? [];
|
|
2180
|
-
}
|
|
2181
2371
|
#entryPresent(record) {
|
|
2182
|
-
if (record.entry === undefined)
|
|
2372
|
+
if (record.entry === undefined || record.conversationId === undefined)
|
|
2373
|
+
return false;
|
|
2374
|
+
const shown = this.#shownView(conversationStream(record.conversationId));
|
|
2375
|
+
if (shown === undefined)
|
|
2183
2376
|
return false;
|
|
2184
|
-
|
|
2377
|
+
if (shown.before !== undefined && Number(record.entry) <= Number(shown.before))
|
|
2378
|
+
return true;
|
|
2379
|
+
return shown.entries.some((entry) => String(entry.id) === String(record.entry));
|
|
2380
|
+
}
|
|
2381
|
+
#inboxOf(id) {
|
|
2382
|
+
if (id === undefined)
|
|
2383
|
+
return;
|
|
2384
|
+
const view = this.#values.get(conversationStream(id));
|
|
2385
|
+
return view?.docs?.["pi.inbox"];
|
|
2185
2386
|
}
|
|
2186
2387
|
#inboxHas(record) {
|
|
2187
|
-
if (record.
|
|
2388
|
+
if (record.submissionId === undefined)
|
|
2188
2389
|
return false;
|
|
2189
|
-
const
|
|
2190
|
-
return
|
|
2390
|
+
const items = this.#inboxOf(record.conversationId)?.items ?? [];
|
|
2391
|
+
return items.some((item) => String(item.id) === String(record.submissionId));
|
|
2392
|
+
}
|
|
2393
|
+
#markChecked(record) {
|
|
2394
|
+
if (record.conversationId === undefined)
|
|
2395
|
+
return;
|
|
2396
|
+
record.checkedVersion = this.#streamVersions.get(conversationStream(record.conversationId)) ?? 0;
|
|
2397
|
+
record.checkedInbox = this.#inboxOf(record.conversationId);
|
|
2191
2398
|
}
|
|
2192
2399
|
#applySubmissionRecord(record, found) {
|
|
2193
2400
|
record.admitted = true;
|
|
@@ -2218,11 +2425,10 @@ class RemoteClient {
|
|
|
2218
2425
|
this.#settleSubmission(record);
|
|
2219
2426
|
continue;
|
|
2220
2427
|
}
|
|
2221
|
-
if (record.conversationId !== undefined && record.
|
|
2428
|
+
if (record.conversationId !== undefined && !record.confirming && record.admitted && (record.entry !== undefined || !this.#inboxHas(record))) {
|
|
2222
2429
|
const stream = conversationStream(record.conversationId);
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
record.checkedVersion = version;
|
|
2430
|
+
if (this.#fresh.has(stream) && (record.checkedVersion !== (this.#streamVersions.get(stream) ?? 0) || record.checkedInbox !== this.#inboxOf(record.conversationId))) {
|
|
2431
|
+
this.#markChecked(record);
|
|
2226
2432
|
this.#confirmSubmission(record);
|
|
2227
2433
|
}
|
|
2228
2434
|
}
|
|
@@ -2256,6 +2462,8 @@ class RemoteClient {
|
|
|
2256
2462
|
} catch {} finally {
|
|
2257
2463
|
record.confirming = false;
|
|
2258
2464
|
}
|
|
2465
|
+
if (!this.#closed && !this.#submissionSettled(record))
|
|
2466
|
+
this.#reconcileSubmissions();
|
|
2259
2467
|
}
|
|
2260
2468
|
#submissionArgs(record) {
|
|
2261
2469
|
const conversationId = record.conversationId;
|
|
@@ -2299,11 +2507,11 @@ class RemoteClient {
|
|
|
2299
2507
|
record.phase = "inFlight";
|
|
2300
2508
|
if (this.#entryPresent(record))
|
|
2301
2509
|
this.#settleSubmission(record);
|
|
2510
|
+
} else if (this.#inboxHas(record)) {
|
|
2511
|
+
this.#settleSubmission(record);
|
|
2302
2512
|
} else {
|
|
2303
2513
|
record.phase = "inFlight";
|
|
2304
|
-
|
|
2305
|
-
record.checkedVersion = (this.#streamVersions.get(conversationStream(record.conversationId)) ?? 0) + (this.#streamVersions.get(docStream("pi.inbox", record.conversationId)) ?? 0);
|
|
2306
|
-
}
|
|
2514
|
+
this.#markChecked(record);
|
|
2307
2515
|
this.#confirmSubmission(record);
|
|
2308
2516
|
}
|
|
2309
2517
|
this.#touchSubmissions();
|
|
@@ -2549,7 +2757,7 @@ class RemoteClient {
|
|
|
2549
2757
|
this.#reconcileSubmissions();
|
|
2550
2758
|
const current = this.#current;
|
|
2551
2759
|
const side = this.#side;
|
|
2552
|
-
const conversation = current === undefined ? undefined : this.#
|
|
2760
|
+
const conversation = current === undefined ? undefined : this.#shownView(conversationStream(current));
|
|
2553
2761
|
const summaries = this.#host("conversations") ?? [];
|
|
2554
2762
|
const index = this.#host("index") ?? { projects: [], conversations: [] };
|
|
2555
2763
|
const devices = this.#host("devices")?.devices ?? [];
|
|
@@ -2565,7 +2773,7 @@ class RemoteClient {
|
|
|
2565
2773
|
submissions: this.#activeSubmissions(),
|
|
2566
2774
|
side: side === undefined ? undefined : {
|
|
2567
2775
|
id: side,
|
|
2568
|
-
conversation: this.#
|
|
2776
|
+
conversation: this.#shownView(conversationStream(side)),
|
|
2569
2777
|
docs: this.#docsOf(side)
|
|
2570
2778
|
},
|
|
2571
2779
|
devices,
|
|
@@ -2797,6 +3005,17 @@ class RemoteClient {
|
|
|
2797
3005
|
});
|
|
2798
3006
|
return outcome;
|
|
2799
3007
|
}),
|
|
3008
|
+
reorderQueued: (submissionId, before, target) => this.#request(async () => {
|
|
3009
|
+
if (!hostSupportsCall(this.#hostMinor, "reorderQueued")) {
|
|
3010
|
+
throw new Error("The host is too old for this (needs protocol 5.29); run `pinomad upgrade` on the host");
|
|
3011
|
+
}
|
|
3012
|
+
const { outcome } = await this.#call("reorderQueued", {
|
|
3013
|
+
conversationId: target ?? conversationId(),
|
|
3014
|
+
submissionId,
|
|
3015
|
+
...before === undefined ? {} : { before }
|
|
3016
|
+
});
|
|
3017
|
+
return outcome;
|
|
3018
|
+
}),
|
|
2800
3019
|
setThinkingLevel: (level) => this.#command(() => this.#call("setThinkingLevel", { conversationId: conversationId(), level })),
|
|
2801
3020
|
setModel: (model) => this.#command(() => this.#call("setModel", { conversationId: conversationId(), model })),
|
|
2802
3021
|
setAgent: (agent) => this.#command(() => {
|
|
@@ -2865,6 +3084,7 @@ class RemoteClient {
|
|
|
2865
3084
|
}
|
|
2866
3085
|
return this.#call("listSkills", { scope });
|
|
2867
3086
|
},
|
|
3087
|
+
loadEarlier: (id) => this.#loadEarlier(id),
|
|
2868
3088
|
listCommands: (id) => {
|
|
2869
3089
|
if (!hostSupportsCall(this.#hostMinor, "listCommands")) {
|
|
2870
3090
|
return Promise.reject(new Error("The host is too old for this (needs protocol 5.17); run `pinomad upgrade` on the host"));
|