@otto-code/client 0.8.12 → 0.8.13
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 +34 -7
- package/dist/compat/normalize-provider-models.js +4 -1
- package/dist/daemon-client-websocket-transport.d.ts +4 -0
- package/dist/daemon-client-websocket-transport.js +14 -2
- package/dist/daemon-client.d.ts +71 -124
- package/dist/daemon-client.js +214 -206
- package/dist/index.d.ts +73 -55
- package/dist/index.js +208 -48
- package/dist/terminal-stream-router.js +1 -0
- package/package.json +7 -3
package/dist/daemon-client.js
CHANGED
|
@@ -117,6 +117,7 @@ function toTimeoutError(error, label, timeoutMs) {
|
|
|
117
117
|
const DEFAULT_RECONNECT_BASE_DELAY_MS = 1500;
|
|
118
118
|
const DEFAULT_RECONNECT_MAX_DELAY_MS = 30000;
|
|
119
119
|
const DEFAULT_SESSION_RPC_TIMEOUT_MS = 60000;
|
|
120
|
+
const PUSH_TOKEN_REVOCATION_TIMEOUT_MS = 2000;
|
|
120
121
|
const DEFAULT_CONNECT_TIMEOUT_MS = 15000;
|
|
121
122
|
const DEFAULT_LIVENESS_TIMEOUT_MS = 5000;
|
|
122
123
|
const LIVENESS_HEARTBEAT_INTERVAL_MS = 10000;
|
|
@@ -185,6 +186,21 @@ function concatByteChunks(chunks, size) {
|
|
|
185
186
|
}
|
|
186
187
|
return bytes;
|
|
187
188
|
}
|
|
189
|
+
function getTransportFrameSize(frame) {
|
|
190
|
+
if (typeof frame === "string") {
|
|
191
|
+
return frame.length;
|
|
192
|
+
}
|
|
193
|
+
return frame.byteLength;
|
|
194
|
+
}
|
|
195
|
+
function describeInboundTransportFrame(frame, rawBytes) {
|
|
196
|
+
if (typeof frame === "string") {
|
|
197
|
+
return { kind: "text", size: String(frame.length) };
|
|
198
|
+
}
|
|
199
|
+
if (rawBytes) {
|
|
200
|
+
return { kind: "binary", size: String(rawBytes.byteLength) };
|
|
201
|
+
}
|
|
202
|
+
return { kind: "unknown", size: "0" };
|
|
203
|
+
}
|
|
188
204
|
function hashForLog(value) {
|
|
189
205
|
let hash = 0;
|
|
190
206
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -595,6 +611,45 @@ export class DaemonClient {
|
|
|
595
611
|
// ============================================================================
|
|
596
612
|
// Core Send Helpers
|
|
597
613
|
// ============================================================================
|
|
614
|
+
beginTraceSection(name, args) {
|
|
615
|
+
const trace = this.config.trace;
|
|
616
|
+
if (!trace?.isEnabled()) {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
trace.beginSection(name, args);
|
|
620
|
+
return true;
|
|
621
|
+
}
|
|
622
|
+
endTraceSection(isOpen) {
|
|
623
|
+
if (isOpen) {
|
|
624
|
+
this.config.trace?.endSection();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
traceInstant(name, args) {
|
|
628
|
+
const isOpen = this.beginTraceSection(name, args);
|
|
629
|
+
this.endTraceSection(isOpen);
|
|
630
|
+
}
|
|
631
|
+
sendJsonMessage(envelopeType, messageType, message) {
|
|
632
|
+
this.traceInstant("otto.ws.message.outbound", {
|
|
633
|
+
envelopeType,
|
|
634
|
+
messageType,
|
|
635
|
+
});
|
|
636
|
+
this.sendTransportFrame(JSON.stringify(message));
|
|
637
|
+
}
|
|
638
|
+
sendTransportFrame(frame) {
|
|
639
|
+
if (!this.transport) {
|
|
640
|
+
throw new Error("Transport not connected");
|
|
641
|
+
}
|
|
642
|
+
const isOpen = this.beginTraceSection("otto.ws.frame.outbound", {
|
|
643
|
+
kind: typeof frame === "string" ? "text" : "binary",
|
|
644
|
+
size: String(getTransportFrameSize(frame)),
|
|
645
|
+
});
|
|
646
|
+
try {
|
|
647
|
+
this.transport.send(frame);
|
|
648
|
+
}
|
|
649
|
+
finally {
|
|
650
|
+
this.endTraceSection(isOpen);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
598
653
|
/**
|
|
599
654
|
* Send a session message. For fire-and-forget messages (heartbeats, etc.),
|
|
600
655
|
* failures are suppressed if `suppressSendErrors` is configured.
|
|
@@ -609,7 +664,7 @@ export class DaemonClient {
|
|
|
609
664
|
}
|
|
610
665
|
const payload = SessionInboundMessageSchema.parse(message);
|
|
611
666
|
try {
|
|
612
|
-
this.
|
|
667
|
+
this.sendJsonMessage("session", payload.type, { type: "session", message: payload });
|
|
613
668
|
}
|
|
614
669
|
catch (error) {
|
|
615
670
|
if (this.config.suppressSendErrors) {
|
|
@@ -626,7 +681,11 @@ export class DaemonClient {
|
|
|
626
681
|
throw new Error(`Transport not connected (status: ${this.connectionState.status})`);
|
|
627
682
|
}
|
|
628
683
|
try {
|
|
629
|
-
this.
|
|
684
|
+
this.traceInstant("otto.ws.message.outbound", {
|
|
685
|
+
envelopeType: "binary",
|
|
686
|
+
messageType: "binary",
|
|
687
|
+
});
|
|
688
|
+
this.sendTransportFrame(frame);
|
|
630
689
|
}
|
|
631
690
|
catch (error) {
|
|
632
691
|
if (this.config.suppressSendErrors) {
|
|
@@ -646,7 +705,7 @@ export class DaemonClient {
|
|
|
646
705
|
// If connected, send immediately
|
|
647
706
|
if (this.transport && status === "connected") {
|
|
648
707
|
const payload = SessionInboundMessageSchema.parse(message);
|
|
649
|
-
this.
|
|
708
|
+
this.sendJsonMessage("session", payload.type, { type: "session", message: payload });
|
|
650
709
|
return Promise.resolve();
|
|
651
710
|
}
|
|
652
711
|
// If connecting, queue the message to be sent once connected
|
|
@@ -677,7 +736,7 @@ export class DaemonClient {
|
|
|
677
736
|
try {
|
|
678
737
|
if (this.transport && this.connectionState.status === "connected") {
|
|
679
738
|
const payload = SessionInboundMessageSchema.parse(pending.message);
|
|
680
|
-
this.
|
|
739
|
+
this.sendJsonMessage("session", payload.type, { type: "session", message: payload });
|
|
681
740
|
pending.resolve();
|
|
682
741
|
}
|
|
683
742
|
else {
|
|
@@ -785,7 +844,7 @@ export class DaemonClient {
|
|
|
785
844
|
}
|
|
786
845
|
const payload = SessionInboundMessageSchema.parse(message);
|
|
787
846
|
try {
|
|
788
|
-
this.
|
|
847
|
+
this.sendJsonMessage("session", payload.type, { type: "session", message: payload });
|
|
789
848
|
}
|
|
790
849
|
catch (error) {
|
|
791
850
|
throw error instanceof Error ? error : new Error(String(error));
|
|
@@ -855,6 +914,15 @@ export class DaemonClient {
|
|
|
855
914
|
token,
|
|
856
915
|
});
|
|
857
916
|
}
|
|
917
|
+
async unregisterPushToken(token) {
|
|
918
|
+
const requestId = this.createRequestId();
|
|
919
|
+
await this.sendCorrelatedSessionRequest({
|
|
920
|
+
requestId,
|
|
921
|
+
message: { type: "push.unregister.request", token, requestId },
|
|
922
|
+
responseType: "push.unregister.response",
|
|
923
|
+
timeout: PUSH_TOKEN_REVOCATION_TIMEOUT_MS,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
858
926
|
async ping(params) {
|
|
859
927
|
const requestId = params?.requestId ?? `ping-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
860
928
|
const clientSentAt = Date.now();
|
|
@@ -934,7 +1002,7 @@ export class DaemonClient {
|
|
|
934
1002
|
};
|
|
935
1003
|
this.pingProbe = probe;
|
|
936
1004
|
try {
|
|
937
|
-
this.
|
|
1005
|
+
this.sendJsonMessage("ping", "ping", { type: "ping" });
|
|
938
1006
|
}
|
|
939
1007
|
catch (error) {
|
|
940
1008
|
this.clearPingProbe();
|
|
@@ -1007,6 +1075,7 @@ export class DaemonClient {
|
|
|
1007
1075
|
type: "fetch_agent_history_request",
|
|
1008
1076
|
requestId: resolvedRequestId,
|
|
1009
1077
|
...(options?.filter ? { filter: options.filter } : {}),
|
|
1078
|
+
...(options?.search ? { search: options.search } : {}),
|
|
1010
1079
|
...(options?.sort ? { sort: options.sort } : {}),
|
|
1011
1080
|
...(options?.page ? { page: options.page } : {}),
|
|
1012
1081
|
});
|
|
@@ -1778,6 +1847,14 @@ export class DaemonClient {
|
|
|
1778
1847
|
}
|
|
1779
1848
|
return { target: payload.target };
|
|
1780
1849
|
}
|
|
1850
|
+
async setProjectIcon(projectId, source, requestId) {
|
|
1851
|
+
const payload = await this.sendNamespacedCorrelatedSessionRequest({
|
|
1852
|
+
requestId,
|
|
1853
|
+
message: { type: "project.icon.set.request", projectId, source },
|
|
1854
|
+
});
|
|
1855
|
+
if (!payload.accepted)
|
|
1856
|
+
throw new Error(payload.error ?? "setProjectIcon rejected");
|
|
1857
|
+
}
|
|
1781
1858
|
async removeProject(projectId, requestId) {
|
|
1782
1859
|
const payload = await this.sendNamespacedCorrelatedSessionRequest({
|
|
1783
1860
|
requestId,
|
|
@@ -2262,6 +2339,7 @@ export class DaemonClient {
|
|
|
2262
2339
|
...(options.cursor ? { cursor: options.cursor } : {}),
|
|
2263
2340
|
...(typeof options.limit === "number" ? { limit: options.limit } : {}),
|
|
2264
2341
|
...(options.projection ? { projection: options.projection } : {}),
|
|
2342
|
+
...(options.mergeWindow === true ? { mergeWindow: true } : {}),
|
|
2265
2343
|
});
|
|
2266
2344
|
const payload = await this.sendRequest({
|
|
2267
2345
|
requestId: resolvedRequestId,
|
|
@@ -2283,6 +2361,28 @@ export class DaemonClient {
|
|
|
2283
2361
|
}
|
|
2284
2362
|
return payload;
|
|
2285
2363
|
}
|
|
2364
|
+
async listAgentTimelinePrompts(agentId, options = {}) {
|
|
2365
|
+
const requestId = this.createRequestId(options.requestId);
|
|
2366
|
+
const message = SessionInboundMessageSchema.parse({
|
|
2367
|
+
type: "agent.timeline.list_prompts.request",
|
|
2368
|
+
agentId,
|
|
2369
|
+
requestId,
|
|
2370
|
+
});
|
|
2371
|
+
const payload = await this.sendRequest({
|
|
2372
|
+
requestId,
|
|
2373
|
+
message,
|
|
2374
|
+
timeout: options.timeout,
|
|
2375
|
+
options: { skipQueue: true },
|
|
2376
|
+
select: (response) => response.type === "agent.timeline.list_prompts.response" &&
|
|
2377
|
+
response.payload.requestId === requestId
|
|
2378
|
+
? response.payload
|
|
2379
|
+
: null,
|
|
2380
|
+
});
|
|
2381
|
+
if (payload.error) {
|
|
2382
|
+
throw new Error(payload.error);
|
|
2383
|
+
}
|
|
2384
|
+
return payload;
|
|
2385
|
+
}
|
|
2286
2386
|
async buildAgentForkContext(agentId, options = {}) {
|
|
2287
2387
|
const resolvedRequestId = this.createRequestId(options.requestId);
|
|
2288
2388
|
const message = SessionInboundMessageSchema.parse({
|
|
@@ -2647,6 +2747,40 @@ export class DaemonClient {
|
|
|
2647
2747
|
}
|
|
2648
2748
|
return payload.notice ?? null;
|
|
2649
2749
|
}
|
|
2750
|
+
/**
|
|
2751
|
+
* Applies a whole agent-config bundle in one request. Use this instead of
|
|
2752
|
+
* chaining the single-field setters when the values belong together so client
|
|
2753
|
+
* interruption and other mutations cannot interleave between steps. A
|
|
2754
|
+
* provider rejection can still leave earlier steps applied.
|
|
2755
|
+
* Gated on `server_info.features.agentConfigApply`.
|
|
2756
|
+
*/
|
|
2757
|
+
async applyAgentConfig(agentId, config) {
|
|
2758
|
+
const requestId = this.createRequestId();
|
|
2759
|
+
const message = SessionInboundMessageSchema.parse({
|
|
2760
|
+
type: "agent.config.apply.request",
|
|
2761
|
+
agentId,
|
|
2762
|
+
config,
|
|
2763
|
+
requestId,
|
|
2764
|
+
});
|
|
2765
|
+
const payload = await this.sendRequest({
|
|
2766
|
+
requestId,
|
|
2767
|
+
message,
|
|
2768
|
+
options: { skipQueue: true },
|
|
2769
|
+
select: (msg) => {
|
|
2770
|
+
if (msg.type !== "agent.config.apply.response") {
|
|
2771
|
+
return null;
|
|
2772
|
+
}
|
|
2773
|
+
if (msg.payload.requestId !== requestId) {
|
|
2774
|
+
return null;
|
|
2775
|
+
}
|
|
2776
|
+
return msg.payload;
|
|
2777
|
+
},
|
|
2778
|
+
});
|
|
2779
|
+
if (!payload.accepted) {
|
|
2780
|
+
throw new Error(payload.error ?? "applyAgentConfig rejected");
|
|
2781
|
+
}
|
|
2782
|
+
return payload.notice ?? null;
|
|
2783
|
+
}
|
|
2650
2784
|
async restartServer(reason, requestId) {
|
|
2651
2785
|
const resolvedRequestId = this.createRequestId(requestId);
|
|
2652
2786
|
const message = SessionInboundMessageSchema.parse({
|
|
@@ -3712,48 +3846,6 @@ export class DaemonClient {
|
|
|
3712
3846
|
requestId: input.requestId,
|
|
3713
3847
|
}));
|
|
3714
3848
|
}
|
|
3715
|
-
/** Create an empty file or a directory. Never overwrites - see FileCreateResultSchema. */
|
|
3716
|
-
async createFileEntry(options) {
|
|
3717
|
-
const payload = await this.sendCorrelatedSessionRequest({
|
|
3718
|
-
requestId: options.requestId,
|
|
3719
|
-
message: {
|
|
3720
|
-
type: "file.create.request",
|
|
3721
|
-
cwd: options.cwd,
|
|
3722
|
-
path: options.path,
|
|
3723
|
-
kind: options.kind,
|
|
3724
|
-
},
|
|
3725
|
-
responseType: "file.create.response",
|
|
3726
|
-
});
|
|
3727
|
-
return payload.result;
|
|
3728
|
-
}
|
|
3729
|
-
/** Permanent delete - an unlink, not a move to any trash. */
|
|
3730
|
-
async deleteFileEntry(options) {
|
|
3731
|
-
const payload = await this.sendCorrelatedSessionRequest({
|
|
3732
|
-
requestId: options.requestId,
|
|
3733
|
-
message: {
|
|
3734
|
-
type: "file.delete.request",
|
|
3735
|
-
cwd: options.cwd,
|
|
3736
|
-
path: options.path,
|
|
3737
|
-
recursive: options.recursive,
|
|
3738
|
-
},
|
|
3739
|
-
responseType: "file.delete.response",
|
|
3740
|
-
});
|
|
3741
|
-
return payload.result;
|
|
3742
|
-
}
|
|
3743
|
-
/** Rename, which is also move. Never clobbers an occupied destination. */
|
|
3744
|
-
async renameFileEntry(options) {
|
|
3745
|
-
const payload = await this.sendCorrelatedSessionRequest({
|
|
3746
|
-
requestId: options.requestId,
|
|
3747
|
-
message: {
|
|
3748
|
-
type: "file.rename.request",
|
|
3749
|
-
cwd: options.cwd,
|
|
3750
|
-
path: options.path,
|
|
3751
|
-
newPath: options.newPath,
|
|
3752
|
-
},
|
|
3753
|
-
responseType: "file.rename.response",
|
|
3754
|
-
});
|
|
3755
|
-
return payload.result;
|
|
3756
|
-
}
|
|
3757
3849
|
async refineFile(options) {
|
|
3758
3850
|
const payload = await this.sendCorrelatedSessionRequest({
|
|
3759
3851
|
requestId: options.requestId,
|
|
@@ -4446,6 +4538,7 @@ export class DaemonClient {
|
|
|
4446
4538
|
message: {
|
|
4447
4539
|
type: "get_providers_snapshot_request",
|
|
4448
4540
|
cwd: options?.cwd,
|
|
4541
|
+
ifNoneMatch: options?.ifNoneMatch,
|
|
4449
4542
|
},
|
|
4450
4543
|
responseType: "get_providers_snapshot_response",
|
|
4451
4544
|
});
|
|
@@ -4978,6 +5071,31 @@ export class DaemonClient {
|
|
|
4978
5071
|
});
|
|
4979
5072
|
return unwrapBrainJob(payload);
|
|
4980
5073
|
}
|
|
5074
|
+
async createFileEntry(input) {
|
|
5075
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
5076
|
+
message: { type: "fs.entry.create.request", ...input },
|
|
5077
|
+
});
|
|
5078
|
+
}
|
|
5079
|
+
async renameFileEntry(input) {
|
|
5080
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
5081
|
+
message: { type: "fs.entry.rename.request", ...input },
|
|
5082
|
+
});
|
|
5083
|
+
}
|
|
5084
|
+
async duplicateFileEntry(input) {
|
|
5085
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
5086
|
+
message: { type: "fs.entry.duplicate.request", ...input },
|
|
5087
|
+
});
|
|
5088
|
+
}
|
|
5089
|
+
async deleteFileEntry(input) {
|
|
5090
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
5091
|
+
message: { type: "fs.entry.delete.request", ...input },
|
|
5092
|
+
});
|
|
5093
|
+
}
|
|
5094
|
+
async checkoutDiscardChanges(cwd, input) {
|
|
5095
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
5096
|
+
message: { type: "checkout.discard_changes.request", cwd, paths: input.paths },
|
|
5097
|
+
});
|
|
5098
|
+
}
|
|
4981
5099
|
async brainJobsList(requestId) {
|
|
4982
5100
|
const payload = await this.sendCorrelatedSessionRequest({
|
|
4983
5101
|
requestId,
|
|
@@ -5093,6 +5211,15 @@ export class DaemonClient {
|
|
|
5093
5211
|
}
|
|
5094
5212
|
return payload.status;
|
|
5095
5213
|
}
|
|
5214
|
+
async getProjectIcon(projectId, requestId) {
|
|
5215
|
+
return this.sendNamespacedCorrelatedSessionRequest({
|
|
5216
|
+
requestId,
|
|
5217
|
+
message: { type: "project.icon.get.request", projectId },
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5220
|
+
// ============================================================================
|
|
5221
|
+
// Provider Models / Commands
|
|
5222
|
+
// ============================================================================
|
|
5096
5223
|
/** Delete a model's files. The brain refuses while that model is loaded. */
|
|
5097
5224
|
async brainModelDelete(modelId, requestId) {
|
|
5098
5225
|
const payload = await this.sendCorrelatedSessionRequest({
|
|
@@ -5689,86 +5816,6 @@ export class DaemonClient {
|
|
|
5689
5816
|
responseType: "terminal.compatibility.diagnostic.response",
|
|
5690
5817
|
});
|
|
5691
5818
|
}
|
|
5692
|
-
async createChatRoom(options) {
|
|
5693
|
-
return this.sendCorrelatedSessionRequest({
|
|
5694
|
-
requestId: options.requestId,
|
|
5695
|
-
message: {
|
|
5696
|
-
type: "chat/create",
|
|
5697
|
-
name: options.name,
|
|
5698
|
-
...(options.purpose ? { purpose: options.purpose } : {}),
|
|
5699
|
-
},
|
|
5700
|
-
responseType: "chat/create/response",
|
|
5701
|
-
});
|
|
5702
|
-
}
|
|
5703
|
-
async listChatRooms(requestId) {
|
|
5704
|
-
return this.sendCorrelatedSessionRequest({
|
|
5705
|
-
requestId,
|
|
5706
|
-
message: {
|
|
5707
|
-
type: "chat/list",
|
|
5708
|
-
},
|
|
5709
|
-
responseType: "chat/list/response",
|
|
5710
|
-
});
|
|
5711
|
-
}
|
|
5712
|
-
async inspectChatRoom(options) {
|
|
5713
|
-
return this.sendCorrelatedSessionRequest({
|
|
5714
|
-
requestId: options.requestId,
|
|
5715
|
-
message: {
|
|
5716
|
-
type: "chat/inspect",
|
|
5717
|
-
room: options.room,
|
|
5718
|
-
},
|
|
5719
|
-
responseType: "chat/inspect/response",
|
|
5720
|
-
});
|
|
5721
|
-
}
|
|
5722
|
-
async deleteChatRoom(options) {
|
|
5723
|
-
return this.sendCorrelatedSessionRequest({
|
|
5724
|
-
requestId: options.requestId,
|
|
5725
|
-
message: {
|
|
5726
|
-
type: "chat/delete",
|
|
5727
|
-
room: options.room,
|
|
5728
|
-
},
|
|
5729
|
-
responseType: "chat/delete/response",
|
|
5730
|
-
});
|
|
5731
|
-
}
|
|
5732
|
-
async postChatMessage(options) {
|
|
5733
|
-
return this.sendCorrelatedSessionRequest({
|
|
5734
|
-
requestId: options.requestId,
|
|
5735
|
-
message: {
|
|
5736
|
-
type: "chat/post",
|
|
5737
|
-
room: options.room,
|
|
5738
|
-
body: options.body,
|
|
5739
|
-
...(options.authorAgentId ? { authorAgentId: options.authorAgentId } : {}),
|
|
5740
|
-
...(options.replyToMessageId ? { replyToMessageId: options.replyToMessageId } : {}),
|
|
5741
|
-
},
|
|
5742
|
-
responseType: "chat/post/response",
|
|
5743
|
-
});
|
|
5744
|
-
}
|
|
5745
|
-
async readChatMessages(options) {
|
|
5746
|
-
return this.sendCorrelatedSessionRequest({
|
|
5747
|
-
requestId: options.requestId,
|
|
5748
|
-
message: {
|
|
5749
|
-
type: "chat/read",
|
|
5750
|
-
room: options.room,
|
|
5751
|
-
...(typeof options.limit === "number" ? { limit: options.limit } : {}),
|
|
5752
|
-
...(options.since ? { since: options.since } : {}),
|
|
5753
|
-
...(options.authorAgentId ? { authorAgentId: options.authorAgentId } : {}),
|
|
5754
|
-
},
|
|
5755
|
-
responseType: "chat/read/response",
|
|
5756
|
-
timeout: options.timeout,
|
|
5757
|
-
});
|
|
5758
|
-
}
|
|
5759
|
-
async waitForChatMessages(options) {
|
|
5760
|
-
return this.sendCorrelatedSessionRequest({
|
|
5761
|
-
requestId: options.requestId,
|
|
5762
|
-
message: {
|
|
5763
|
-
type: "chat/wait",
|
|
5764
|
-
room: options.room,
|
|
5765
|
-
...(options.afterMessageId ? { afterMessageId: options.afterMessageId } : {}),
|
|
5766
|
-
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
5767
|
-
},
|
|
5768
|
-
responseType: "chat/wait/response",
|
|
5769
|
-
timeout: (options.timeoutMs ?? 0) + 10000,
|
|
5770
|
-
});
|
|
5771
|
-
}
|
|
5772
5819
|
async scheduleCreate(options) {
|
|
5773
5820
|
return this.sendCorrelatedSessionRequest({
|
|
5774
5821
|
requestId: options.requestId,
|
|
@@ -5968,76 +6015,6 @@ export class DaemonClient {
|
|
|
5968
6015
|
responseType: "artifact.get-content.response",
|
|
5969
6016
|
});
|
|
5970
6017
|
}
|
|
5971
|
-
async loopRun(options) {
|
|
5972
|
-
return this.sendCorrelatedSessionRequest({
|
|
5973
|
-
requestId: options.requestId,
|
|
5974
|
-
message: {
|
|
5975
|
-
type: "loop/run",
|
|
5976
|
-
prompt: options.prompt,
|
|
5977
|
-
cwd: options.cwd,
|
|
5978
|
-
...(options.provider ? { provider: options.provider } : {}),
|
|
5979
|
-
...(options.model ? { model: options.model } : {}),
|
|
5980
|
-
...(options.modeId ? { modeId: options.modeId } : {}),
|
|
5981
|
-
...(options.verifierProvider ? { verifierProvider: options.verifierProvider } : {}),
|
|
5982
|
-
...(options.verifierModel ? { verifierModel: options.verifierModel } : {}),
|
|
5983
|
-
...(options.verifierModeId ? { verifierModeId: options.verifierModeId } : {}),
|
|
5984
|
-
...(options.verifyPrompt ? { verifyPrompt: options.verifyPrompt } : {}),
|
|
5985
|
-
...(options.verifyChecks && options.verifyChecks.length > 0
|
|
5986
|
-
? { verifyChecks: options.verifyChecks }
|
|
5987
|
-
: {}),
|
|
5988
|
-
...(options.name ? { name: options.name } : {}),
|
|
5989
|
-
...(typeof options.sleepMs === "number" ? { sleepMs: options.sleepMs } : {}),
|
|
5990
|
-
...(typeof options.maxIterations === "number"
|
|
5991
|
-
? { maxIterations: options.maxIterations }
|
|
5992
|
-
: {}),
|
|
5993
|
-
...(typeof options.maxTimeMs === "number" ? { maxTimeMs: options.maxTimeMs } : {}),
|
|
5994
|
-
},
|
|
5995
|
-
responseType: "loop/run/response",
|
|
5996
|
-
});
|
|
5997
|
-
}
|
|
5998
|
-
async loopList(requestId) {
|
|
5999
|
-
return this.sendCorrelatedSessionRequest({
|
|
6000
|
-
requestId,
|
|
6001
|
-
message: {
|
|
6002
|
-
type: "loop/list",
|
|
6003
|
-
},
|
|
6004
|
-
responseType: "loop/list/response",
|
|
6005
|
-
});
|
|
6006
|
-
}
|
|
6007
|
-
async loopInspect(options) {
|
|
6008
|
-
const normalized = typeof options === "string" ? { id: options } : options;
|
|
6009
|
-
return this.sendCorrelatedSessionRequest({
|
|
6010
|
-
requestId: normalized.requestId,
|
|
6011
|
-
message: {
|
|
6012
|
-
type: "loop/inspect",
|
|
6013
|
-
id: normalized.id,
|
|
6014
|
-
},
|
|
6015
|
-
responseType: "loop/inspect/response",
|
|
6016
|
-
});
|
|
6017
|
-
}
|
|
6018
|
-
async loopLogs(options, afterSeq) {
|
|
6019
|
-
const normalized = typeof options === "string" ? { id: options, afterSeq } : options;
|
|
6020
|
-
return this.sendCorrelatedSessionRequest({
|
|
6021
|
-
requestId: normalized.requestId,
|
|
6022
|
-
message: {
|
|
6023
|
-
type: "loop/logs",
|
|
6024
|
-
id: normalized.id,
|
|
6025
|
-
...(typeof normalized.afterSeq === "number" ? { afterSeq: normalized.afterSeq } : {}),
|
|
6026
|
-
},
|
|
6027
|
-
responseType: "loop/logs/response",
|
|
6028
|
-
});
|
|
6029
|
-
}
|
|
6030
|
-
async loopStop(options) {
|
|
6031
|
-
const normalized = typeof options === "string" ? { id: options } : options;
|
|
6032
|
-
return this.sendCorrelatedSessionRequest({
|
|
6033
|
-
requestId: normalized.requestId,
|
|
6034
|
-
message: {
|
|
6035
|
-
type: "loop/stop",
|
|
6036
|
-
id: normalized.id,
|
|
6037
|
-
},
|
|
6038
|
-
responseType: "loop/stop/response",
|
|
6039
|
-
});
|
|
6040
|
-
}
|
|
6041
6018
|
onTerminalStreamEvent(handler) {
|
|
6042
6019
|
return this.terminalStreams.onEvent(handler);
|
|
6043
6020
|
}
|
|
@@ -6091,7 +6068,7 @@ export class DaemonClient {
|
|
|
6091
6068
|
return;
|
|
6092
6069
|
}
|
|
6093
6070
|
try {
|
|
6094
|
-
this.
|
|
6071
|
+
this.sendJsonMessage("hello", "hello", {
|
|
6095
6072
|
type: "hello",
|
|
6096
6073
|
clientId: this.config.clientId,
|
|
6097
6074
|
clientType: this.config.clientType ?? "cli",
|
|
@@ -6105,10 +6082,11 @@ export class DaemonClient {
|
|
|
6105
6082
|
// so dropping it silently kills cross-session project renames.
|
|
6106
6083
|
[CLIENT_CAPS.projectUpdates]: true,
|
|
6107
6084
|
[CLIENT_CAPS.communicationsPresenceUpdates]: true,
|
|
6085
|
+
[CLIENT_CAPS.compactProviderSnapshots]: true,
|
|
6108
6086
|
...this.config.capabilities,
|
|
6109
6087
|
},
|
|
6110
6088
|
...(this.config.appVersion ? { appVersion: this.config.appVersion } : {}),
|
|
6111
|
-
})
|
|
6089
|
+
});
|
|
6112
6090
|
}
|
|
6113
6091
|
catch (error) {
|
|
6114
6092
|
const message = error instanceof Error ? error.message : "Failed to send hello message";
|
|
@@ -6173,25 +6151,37 @@ export class DaemonClient {
|
|
|
6173
6151
|
return;
|
|
6174
6152
|
}
|
|
6175
6153
|
const rawBytes = asUint8Array(rawData);
|
|
6176
|
-
|
|
6177
|
-
|
|
6154
|
+
const isOpen = this.beginTraceSection("otto.ws.frame.inbound", describeInboundTransportFrame(rawData, rawBytes));
|
|
6155
|
+
try {
|
|
6156
|
+
if (rawBytes && this.tryHandleBinaryFrame(rawBytes)) {
|
|
6157
|
+
return;
|
|
6158
|
+
}
|
|
6159
|
+
const payload = decodeMessageData(rawData);
|
|
6160
|
+
if (!payload) {
|
|
6161
|
+
return;
|
|
6162
|
+
}
|
|
6163
|
+
this.handleJsonPayload(payload, rawBytes?.byteLength);
|
|
6178
6164
|
}
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
return;
|
|
6165
|
+
finally {
|
|
6166
|
+
this.endTraceSection(isOpen);
|
|
6182
6167
|
}
|
|
6183
|
-
this.handleJsonPayload(payload, rawBytes?.byteLength);
|
|
6184
6168
|
}
|
|
6185
6169
|
handleJsonPayload(payload, rawBytesLength) {
|
|
6186
6170
|
const bytes = rawBytesLength ?? payload.length;
|
|
6187
6171
|
const startMs = perfNow();
|
|
6188
6172
|
let parsedJson;
|
|
6173
|
+
const parseTraceOpen = this.beginTraceSection("otto.ws.json.parse", {
|
|
6174
|
+
size: String(bytes),
|
|
6175
|
+
});
|
|
6189
6176
|
try {
|
|
6190
6177
|
parsedJson = JSON.parse(payload);
|
|
6191
6178
|
}
|
|
6192
6179
|
catch {
|
|
6193
6180
|
return;
|
|
6194
6181
|
}
|
|
6182
|
+
finally {
|
|
6183
|
+
this.endTraceSection(parseTraceOpen);
|
|
6184
|
+
}
|
|
6195
6185
|
const parsed = validateWSOutboundMessage(parsedJson);
|
|
6196
6186
|
if (!parsed.success) {
|
|
6197
6187
|
const responseIdentity = extractCorrelatedResponseIdentity(parsedJson);
|
|
@@ -6210,10 +6200,18 @@ export class DaemonClient {
|
|
|
6210
6200
|
}
|
|
6211
6201
|
this.consecutiveLivenessFailures = 0;
|
|
6212
6202
|
if (parsed.data.type === "pong") {
|
|
6203
|
+
this.traceInstant("otto.ws.message.inbound", {
|
|
6204
|
+
envelopeType: "pong",
|
|
6205
|
+
messageType: "pong",
|
|
6206
|
+
});
|
|
6213
6207
|
this.resolvePingProbe();
|
|
6214
6208
|
this.runtimeMetrics?.recordMessage("pong", bytes, perfNow() - startMs);
|
|
6215
6209
|
return;
|
|
6216
6210
|
}
|
|
6211
|
+
this.traceInstant("otto.ws.message.inbound", {
|
|
6212
|
+
envelopeType: "session",
|
|
6213
|
+
messageType: parsed.data.message.type,
|
|
6214
|
+
});
|
|
6217
6215
|
this.handleSessionMessage(parsed.data.message);
|
|
6218
6216
|
const msgType = parsed.data.message.type;
|
|
6219
6217
|
this.runtimeMetrics?.recordMessage(msgType, bytes, perfNow() - startMs);
|
|
@@ -6224,6 +6222,11 @@ export class DaemonClient {
|
|
|
6224
6222
|
tryHandleBinaryFrame(rawBytes) {
|
|
6225
6223
|
const fileFrame = decodeFileTransferFrame(rawBytes);
|
|
6226
6224
|
if (fileFrame) {
|
|
6225
|
+
this.traceInstant("otto.ws.message.inbound", {
|
|
6226
|
+
envelopeType: "binary",
|
|
6227
|
+
messageType: "file",
|
|
6228
|
+
opcode: String(fileFrame.opcode),
|
|
6229
|
+
});
|
|
6227
6230
|
this.consecutiveLivenessFailures = 0;
|
|
6228
6231
|
this.handleFileTransferFrame(fileFrame);
|
|
6229
6232
|
this.runtimeMetrics?.recordBinaryFrame("other", rawBytes.byteLength, 0);
|
|
@@ -6233,6 +6236,11 @@ export class DaemonClient {
|
|
|
6233
6236
|
if (!frame) {
|
|
6234
6237
|
return false;
|
|
6235
6238
|
}
|
|
6239
|
+
this.traceInstant("otto.ws.message.inbound", {
|
|
6240
|
+
envelopeType: "binary",
|
|
6241
|
+
messageType: "terminal",
|
|
6242
|
+
opcode: String(frame.opcode),
|
|
6243
|
+
});
|
|
6236
6244
|
this.consecutiveLivenessFailures = 0;
|
|
6237
6245
|
const binaryStartMs = perfNow();
|
|
6238
6246
|
this.terminalStreams.handleFrame(frame);
|