@hydra-acp/cli 0.1.8 → 0.1.9
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 +3 -3
- package/dist/cli.js +413 -81
- package/dist/index.d.ts +23 -5
- package/dist/index.js +240 -30
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -392,11 +392,22 @@ var init_types = __esm({
|
|
|
392
392
|
MethodNotFound: -32601,
|
|
393
393
|
InvalidParams: -32602,
|
|
394
394
|
InternalError: -32603,
|
|
395
|
+
// -32001…-32003 reserved for RFD #533 attach semantics:
|
|
396
|
+
// -32001 Session not found
|
|
397
|
+
// -32002 Not authorised to attach
|
|
398
|
+
// -32003 Session does not support multi-client attach
|
|
399
|
+
// We emit -32001 (matching); the other two are reserved for spec
|
|
400
|
+
// alignment even though we don't currently emit them (we bearer-auth
|
|
401
|
+
// at WS upgrade time and always support multi-client attach).
|
|
395
402
|
SessionNotFound: -32001,
|
|
396
|
-
|
|
397
|
-
|
|
403
|
+
NotAuthorisedToAttach: -32002,
|
|
404
|
+
MultiClientNotSupported: -32003,
|
|
398
405
|
AgentNotInstalled: -32005,
|
|
399
|
-
|
|
406
|
+
// Hydra-internal codes — outside the RFD's reserved range so they
|
|
407
|
+
// can't collide with future spec assignments.
|
|
408
|
+
BundleAlreadyImported: -32010,
|
|
409
|
+
PermissionDenied: -32011,
|
|
410
|
+
AlreadyAttached: -32012
|
|
400
411
|
};
|
|
401
412
|
InitializeParams = z3.object({
|
|
402
413
|
protocolVersion: z3.number().optional(),
|
|
@@ -406,7 +417,12 @@ var init_types = __esm({
|
|
|
406
417
|
version: z3.string().optional()
|
|
407
418
|
}).optional()
|
|
408
419
|
});
|
|
409
|
-
HistoryPolicy = z3.enum([
|
|
420
|
+
HistoryPolicy = z3.enum([
|
|
421
|
+
"full",
|
|
422
|
+
"pending_only",
|
|
423
|
+
"none",
|
|
424
|
+
"after_message"
|
|
425
|
+
]);
|
|
410
426
|
SessionNewParams = z3.object({
|
|
411
427
|
cwd: z3.string(),
|
|
412
428
|
agentId: z3.string().optional(),
|
|
@@ -422,6 +438,18 @@ var init_types = __esm({
|
|
|
422
438
|
SessionAttachParams = z3.object({
|
|
423
439
|
sessionId: z3.string(),
|
|
424
440
|
historyPolicy: HistoryPolicy.default("full"),
|
|
441
|
+
// Required when historyPolicy is "after_message"; ignored otherwise.
|
|
442
|
+
// The proxy replays history entries strictly after the entry whose
|
|
443
|
+
// messageId matches this value. If the id isn't found in the buffer,
|
|
444
|
+
// the response.historyPolicy field surfaces "full" so the caller
|
|
445
|
+
// knows we fell back. Per RFD #533.
|
|
446
|
+
afterMessageId: z3.string().optional(),
|
|
447
|
+
// Caller-assigned opaque id (e.g. a UUID). When provided, the proxy
|
|
448
|
+
// echoes it in resolvedBy/sentBy and lifecycle events so other
|
|
449
|
+
// clients can disambiguate multiple instances of the same
|
|
450
|
+
// clientInfo.name. When omitted, the proxy assigns one and returns
|
|
451
|
+
// it in the response. Per RFD #533.
|
|
452
|
+
clientId: z3.string().optional(),
|
|
425
453
|
clientInfo: z3.object({
|
|
426
454
|
name: z3.string(),
|
|
427
455
|
version: z3.string().optional()
|
|
@@ -708,6 +736,9 @@ var init_hydra_commands = __esm({
|
|
|
708
736
|
|
|
709
737
|
// src/core/session.ts
|
|
710
738
|
import { customAlphabet } from "nanoid";
|
|
739
|
+
function generateMessageId() {
|
|
740
|
+
return `m_${generateHydraId()}`;
|
|
741
|
+
}
|
|
711
742
|
function stripHydraSessionPrefix(id) {
|
|
712
743
|
return id.startsWith(HYDRA_SESSION_PREFIX) ? id.slice(HYDRA_SESSION_PREFIX.length) : id;
|
|
713
744
|
}
|
|
@@ -772,6 +803,97 @@ function extractAdvertisedCommands(params) {
|
|
|
772
803
|
}
|
|
773
804
|
return out;
|
|
774
805
|
}
|
|
806
|
+
function ensureMessageIdOnUpdate(method, params) {
|
|
807
|
+
if (method !== "session/update" || !params || typeof params !== "object") {
|
|
808
|
+
return params;
|
|
809
|
+
}
|
|
810
|
+
const p = params;
|
|
811
|
+
if (!p.update || typeof p.update !== "object" || Array.isArray(p.update)) {
|
|
812
|
+
return params;
|
|
813
|
+
}
|
|
814
|
+
const u = p.update;
|
|
815
|
+
if (typeof u.messageId === "string") {
|
|
816
|
+
return params;
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
...params,
|
|
820
|
+
update: { ...p.update, messageId: generateMessageId() }
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function findMessageIdIndex(history, target) {
|
|
824
|
+
for (let i = 0; i < history.length; i++) {
|
|
825
|
+
const entry = history[i];
|
|
826
|
+
if (!entry || entry.method !== "session/update") {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
const params = entry.params;
|
|
830
|
+
if (params?.update?.messageId === target) {
|
|
831
|
+
return i;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return -1;
|
|
835
|
+
}
|
|
836
|
+
function extractToolCallId(params) {
|
|
837
|
+
if (!params || typeof params !== "object") {
|
|
838
|
+
return void 0;
|
|
839
|
+
}
|
|
840
|
+
const toolCall = params.toolCall;
|
|
841
|
+
if (!toolCall || typeof toolCall !== "object") {
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
const id = toolCall.toolCallId;
|
|
845
|
+
return typeof id === "string" ? id : void 0;
|
|
846
|
+
}
|
|
847
|
+
function buildPermissionResolvedUpdate(args) {
|
|
848
|
+
const outcome = extractOutcome(args.result);
|
|
849
|
+
const update = {
|
|
850
|
+
sessionUpdate: "permission_resolved"
|
|
851
|
+
};
|
|
852
|
+
if (args.toolCallId !== void 0) {
|
|
853
|
+
update.toolCallId = args.toolCallId;
|
|
854
|
+
}
|
|
855
|
+
if (outcome) {
|
|
856
|
+
update.outcome = outcome;
|
|
857
|
+
if (outcome.kind === "selected" && typeof outcome.optionId === "string") {
|
|
858
|
+
update.chosenOptionId = outcome.optionId;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
update.resolvedBy = buildResolvedBy(args.resolver);
|
|
862
|
+
return update;
|
|
863
|
+
}
|
|
864
|
+
function extractOutcome(result) {
|
|
865
|
+
if (!result || typeof result !== "object") {
|
|
866
|
+
return void 0;
|
|
867
|
+
}
|
|
868
|
+
const raw = result.outcome;
|
|
869
|
+
if (!raw || typeof raw !== "object") {
|
|
870
|
+
return void 0;
|
|
871
|
+
}
|
|
872
|
+
const kind = raw.kind;
|
|
873
|
+
if (typeof kind !== "string") {
|
|
874
|
+
return void 0;
|
|
875
|
+
}
|
|
876
|
+
const out = { kind };
|
|
877
|
+
const optionId = raw.optionId;
|
|
878
|
+
if (typeof optionId === "string") {
|
|
879
|
+
out.optionId = optionId;
|
|
880
|
+
}
|
|
881
|
+
const reason = raw.reason;
|
|
882
|
+
if (typeof reason === "string") {
|
|
883
|
+
out.reason = reason;
|
|
884
|
+
}
|
|
885
|
+
return out;
|
|
886
|
+
}
|
|
887
|
+
function buildResolvedBy(client) {
|
|
888
|
+
const out = { clientId: client.clientId };
|
|
889
|
+
if (client.clientInfo?.name) {
|
|
890
|
+
out.name = client.clientInfo.name;
|
|
891
|
+
}
|
|
892
|
+
if (client.clientInfo?.version) {
|
|
893
|
+
out.version = client.clientInfo.version;
|
|
894
|
+
}
|
|
895
|
+
return out;
|
|
896
|
+
}
|
|
775
897
|
function extractPromptText(prompt) {
|
|
776
898
|
if (typeof prompt === "string") {
|
|
777
899
|
return prompt;
|
|
@@ -979,6 +1101,30 @@ var init_session = __esm({
|
|
|
979
1101
|
get attachedCount() {
|
|
980
1102
|
return this.clients.size;
|
|
981
1103
|
}
|
|
1104
|
+
// Roster of currently-attached clients, optionally excluding one
|
|
1105
|
+
// clientId. Used by the daemon to populate connectedClients on the
|
|
1106
|
+
// session/attach response (per RFD #533) — the freshly-attaching
|
|
1107
|
+
// client wants to see who else is on the session but not itself in
|
|
1108
|
+
// the list.
|
|
1109
|
+
connectedClients(excludeClientId) {
|
|
1110
|
+
const out = [];
|
|
1111
|
+
for (const client of this.clients.values()) {
|
|
1112
|
+
if (excludeClientId && client.clientId === excludeClientId) {
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
const entry = {
|
|
1116
|
+
clientId: client.clientId
|
|
1117
|
+
};
|
|
1118
|
+
if (client.clientInfo?.name) {
|
|
1119
|
+
entry.name = client.clientInfo.name;
|
|
1120
|
+
}
|
|
1121
|
+
if (client.clientInfo?.version) {
|
|
1122
|
+
entry.version = client.clientInfo.version;
|
|
1123
|
+
}
|
|
1124
|
+
out.push(entry);
|
|
1125
|
+
}
|
|
1126
|
+
return out;
|
|
1127
|
+
}
|
|
982
1128
|
// Wall-clock when the in-flight agent turn began, or undefined when
|
|
983
1129
|
// idle. Tracked in-memory by broadcastPromptReceived/broadcastTurnComplete
|
|
984
1130
|
// so the daemon can hand a fresh attacher mid-turn the right elapsed
|
|
@@ -1010,10 +1156,12 @@ var init_session = __esm({
|
|
|
1010
1156
|
};
|
|
1011
1157
|
}
|
|
1012
1158
|
// Register a client and (asynchronously) load the replay slice it
|
|
1013
|
-
// should receive.
|
|
1014
|
-
//
|
|
1015
|
-
//
|
|
1016
|
-
|
|
1159
|
+
// should receive. Returns both the slice to replay and the actual
|
|
1160
|
+
// historyPolicy applied (which may differ from the requested one
|
|
1161
|
+
// when after_message falls back to full). Validation errors throw
|
|
1162
|
+
// synchronously so callers can rely on either the registration being
|
|
1163
|
+
// in effect or having thrown; the disk-load is the only async work.
|
|
1164
|
+
attach(client, historyPolicy, opts = {}) {
|
|
1017
1165
|
if (this.closed) {
|
|
1018
1166
|
throw withCode(
|
|
1019
1167
|
new Error("session is closed"),
|
|
@@ -1029,9 +1177,20 @@ var init_session = __esm({
|
|
|
1029
1177
|
this.clients.set(client.clientId, client);
|
|
1030
1178
|
this.updatedAt = Date.now();
|
|
1031
1179
|
if (historyPolicy === "none" || historyPolicy === "pending_only") {
|
|
1032
|
-
return Promise.resolve([]);
|
|
1180
|
+
return Promise.resolve({ entries: [], appliedPolicy: historyPolicy });
|
|
1181
|
+
}
|
|
1182
|
+
return this.loadReplay(historyPolicy, opts);
|
|
1183
|
+
}
|
|
1184
|
+
async loadReplay(historyPolicy, opts) {
|
|
1185
|
+
const all = await this.getHistorySnapshot();
|
|
1186
|
+
if (historyPolicy === "after_message") {
|
|
1187
|
+
const cutoff = opts.afterMessageId ? findMessageIdIndex(all, opts.afterMessageId) : -1;
|
|
1188
|
+
if (cutoff < 0) {
|
|
1189
|
+
return { entries: all, appliedPolicy: "full" };
|
|
1190
|
+
}
|
|
1191
|
+
return { entries: all.slice(cutoff + 1), appliedPolicy: "after_message" };
|
|
1033
1192
|
}
|
|
1034
|
-
return
|
|
1193
|
+
return { entries: all, appliedPolicy: "full" };
|
|
1035
1194
|
}
|
|
1036
1195
|
// Dispatch in-flight permission requests to a freshly-attached
|
|
1037
1196
|
// client. Called by the daemon's WS handler *after* it finishes
|
|
@@ -1043,8 +1202,39 @@ var init_session = __esm({
|
|
|
1043
1202
|
}
|
|
1044
1203
|
}
|
|
1045
1204
|
detach(clientId) {
|
|
1046
|
-
|
|
1047
|
-
|
|
1205
|
+
const leaving = this.clients.get(clientId);
|
|
1206
|
+
if (!leaving) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
this.clients.delete(clientId);
|
|
1210
|
+
this.updatedAt = Date.now();
|
|
1211
|
+
this.broadcastClientDisconnected(leaving);
|
|
1212
|
+
}
|
|
1213
|
+
// Notify remaining attached clients that a peer just left, per
|
|
1214
|
+
// RFD #533. Fires for both explicit session/detach and ws-close
|
|
1215
|
+
// teardown (acp-ws calls Session.detach() in both paths). The
|
|
1216
|
+
// notification is broadcast (not recorded) — peer presence is
|
|
1217
|
+
// transient, not part of conversation history.
|
|
1218
|
+
broadcastClientDisconnected(client) {
|
|
1219
|
+
const info = {
|
|
1220
|
+
clientId: client.clientId
|
|
1221
|
+
};
|
|
1222
|
+
if (client.clientInfo?.name) {
|
|
1223
|
+
info.name = client.clientInfo.name;
|
|
1224
|
+
}
|
|
1225
|
+
if (client.clientInfo?.version) {
|
|
1226
|
+
info.version = client.clientInfo.version;
|
|
1227
|
+
}
|
|
1228
|
+
const update = {
|
|
1229
|
+
sessionUpdate: "client_disconnected",
|
|
1230
|
+
client: info,
|
|
1231
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1232
|
+
};
|
|
1233
|
+
for (const peer of this.clients.values()) {
|
|
1234
|
+
void peer.connection.notify("session/update", {
|
|
1235
|
+
sessionId: this.sessionId,
|
|
1236
|
+
update
|
|
1237
|
+
}).catch(() => void 0);
|
|
1048
1238
|
}
|
|
1049
1239
|
}
|
|
1050
1240
|
async prompt(clientId, params) {
|
|
@@ -1097,6 +1287,7 @@ var init_session = __esm({
|
|
|
1097
1287
|
sessionId: this.sessionId,
|
|
1098
1288
|
update: {
|
|
1099
1289
|
sessionUpdate: "prompt_received",
|
|
1290
|
+
messageId: generateMessageId(),
|
|
1100
1291
|
prompt: promptParams.prompt,
|
|
1101
1292
|
sentBy
|
|
1102
1293
|
}
|
|
@@ -1122,7 +1313,8 @@ var init_session = __esm({
|
|
|
1122
1313
|
broadcastTurnComplete(originatorClientId, response) {
|
|
1123
1314
|
const stopReason = response && typeof response === "object" && "stopReason" in response && typeof response.stopReason === "string" ? response.stopReason : void 0;
|
|
1124
1315
|
const update = {
|
|
1125
|
-
sessionUpdate: "turn_complete"
|
|
1316
|
+
sessionUpdate: "turn_complete",
|
|
1317
|
+
messageId: generateMessageId()
|
|
1126
1318
|
};
|
|
1127
1319
|
if (stopReason !== void 0) {
|
|
1128
1320
|
update.stopReason = stopReason;
|
|
@@ -1739,10 +1931,11 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
1739
1931
|
recordAndBroadcast(method, params, excludeClientId) {
|
|
1740
1932
|
const rewritten = this.rewriteForClient(params);
|
|
1741
1933
|
const recordable = !isStateUpdate(method, rewritten);
|
|
1934
|
+
const broadcast = recordable ? ensureMessageIdOnUpdate(method, rewritten) : rewritten;
|
|
1742
1935
|
if (recordable) {
|
|
1743
1936
|
const entry = {
|
|
1744
1937
|
method,
|
|
1745
|
-
params:
|
|
1938
|
+
params: broadcast,
|
|
1746
1939
|
recordedAt: Date.now()
|
|
1747
1940
|
};
|
|
1748
1941
|
this.lastRecordedAt = entry.recordedAt;
|
|
@@ -1770,7 +1963,7 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
1770
1963
|
if (excludeClientId && client.clientId === excludeClientId) {
|
|
1771
1964
|
continue;
|
|
1772
1965
|
}
|
|
1773
|
-
void client.connection.notify(method,
|
|
1966
|
+
void client.connection.notify(method, broadcast).catch(() => void 0);
|
|
1774
1967
|
}
|
|
1775
1968
|
}
|
|
1776
1969
|
async handlePermissionRequest(params) {
|
|
@@ -1782,11 +1975,13 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
1782
1975
|
);
|
|
1783
1976
|
}
|
|
1784
1977
|
const clientParams = this.rewriteForClient(params);
|
|
1978
|
+
const toolCallId = extractToolCallId(clientParams);
|
|
1785
1979
|
return new Promise((resolve5, reject) => {
|
|
1786
1980
|
let settled = false;
|
|
1787
1981
|
const outbound = [];
|
|
1788
1982
|
const entry = { addClient: sendTo };
|
|
1789
1983
|
this.inFlightPermissions.add(entry);
|
|
1984
|
+
const sessionId = this.sessionId;
|
|
1790
1985
|
const settle = (fn) => {
|
|
1791
1986
|
if (settled) {
|
|
1792
1987
|
return;
|
|
@@ -1799,22 +1994,25 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
1799
1994
|
if (settled) {
|
|
1800
1995
|
return;
|
|
1801
1996
|
}
|
|
1802
|
-
const
|
|
1997
|
+
const response = client.connection.request(
|
|
1803
1998
|
"session/request_permission",
|
|
1804
1999
|
clientParams
|
|
1805
2000
|
);
|
|
1806
|
-
outbound.push({ client
|
|
2001
|
+
outbound.push({ client });
|
|
1807
2002
|
void response.then((result) => {
|
|
1808
2003
|
settle(() => {
|
|
2004
|
+
const update = buildPermissionResolvedUpdate({
|
|
2005
|
+
toolCallId,
|
|
2006
|
+
result,
|
|
2007
|
+
resolver: client
|
|
2008
|
+
});
|
|
1809
2009
|
for (const o of outbound) {
|
|
1810
2010
|
if (o.client.clientId === client.clientId) {
|
|
1811
2011
|
continue;
|
|
1812
2012
|
}
|
|
1813
|
-
void o.client.connection.notify("session/
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
resolvedBy: client.clientId,
|
|
1817
|
-
result
|
|
2013
|
+
void o.client.connection.notify("session/update", {
|
|
2014
|
+
sessionId,
|
|
2015
|
+
update
|
|
1818
2016
|
}).catch(() => void 0);
|
|
1819
2017
|
}
|
|
1820
2018
|
resolve5(result);
|
|
@@ -4355,7 +4553,7 @@ var init_screen = __esm({
|
|
|
4355
4553
|
this.streamingActive = false;
|
|
4356
4554
|
this.lines.push(...lines);
|
|
4357
4555
|
this.trackLines(lines);
|
|
4358
|
-
this.
|
|
4556
|
+
this.adjustScrollForRowChange(this.wrappedRowsOfMany(lines));
|
|
4359
4557
|
this.trimScrollback();
|
|
4360
4558
|
this.scheduleRepaint();
|
|
4361
4559
|
}
|
|
@@ -4363,19 +4561,40 @@ var init_screen = __esm({
|
|
|
4363
4561
|
this.streamingActive = false;
|
|
4364
4562
|
this.lines.push(line);
|
|
4365
4563
|
this.trackLine(line);
|
|
4366
|
-
this.
|
|
4564
|
+
this.adjustScrollForRowChange(this.wrappedRowsOf(line));
|
|
4367
4565
|
this.trimScrollback();
|
|
4368
4566
|
this.scheduleRepaint();
|
|
4369
4567
|
}
|
|
4370
4568
|
// When scrolled away from the bottom, shift scrollOffset to keep the
|
|
4371
4569
|
// user's visible window anchored on the same content as the lines
|
|
4372
|
-
// array grows.
|
|
4373
|
-
//
|
|
4374
|
-
|
|
4570
|
+
// array grows. `delta` is measured in WRAPPED ROWS — the same unit
|
|
4571
|
+
// scrollOffset uses — so a single logical line that wraps to N rows
|
|
4572
|
+
// contributes N, not 1. Counting logical lines here was the original
|
|
4573
|
+
// bug: any wrapped append would slide the view up by N−1 rows.
|
|
4574
|
+
adjustScrollForRowChange(delta) {
|
|
4375
4575
|
if (this.scrollOffset > 0 && delta !== 0) {
|
|
4376
4576
|
this.scrollOffset = Math.max(0, this.scrollOffset + delta);
|
|
4377
4577
|
}
|
|
4378
4578
|
}
|
|
4579
|
+
// Wrapped-row count for a single line at the current terminal width.
|
|
4580
|
+
// Reuses the wrap cache, and synchronises the cache's width with the
|
|
4581
|
+
// current width so a resize that hasn't yet been picked up by
|
|
4582
|
+
// drawScrollback can't return stale counts during an insert.
|
|
4583
|
+
wrappedRowsOf(line) {
|
|
4584
|
+
const w = this.term.width;
|
|
4585
|
+
if (this.wrapCacheWidth !== w) {
|
|
4586
|
+
this.wrapCache.clear();
|
|
4587
|
+
this.wrapCacheWidth = w;
|
|
4588
|
+
}
|
|
4589
|
+
return this.wrapOne(line, w).length;
|
|
4590
|
+
}
|
|
4591
|
+
wrappedRowsOfMany(lines) {
|
|
4592
|
+
let n = 0;
|
|
4593
|
+
for (const line of lines) {
|
|
4594
|
+
n += this.wrappedRowsOf(line);
|
|
4595
|
+
}
|
|
4596
|
+
return n;
|
|
4597
|
+
}
|
|
4379
4598
|
trackLine(line) {
|
|
4380
4599
|
this.lineIds.set(line, this.nextLineId++);
|
|
4381
4600
|
}
|
|
@@ -4425,12 +4644,14 @@ var init_screen = __esm({
|
|
|
4425
4644
|
}
|
|
4426
4645
|
const existing = this.keyedBlocks.get(key);
|
|
4427
4646
|
let touchesEnd = false;
|
|
4428
|
-
let
|
|
4647
|
+
let rowDelta = 0;
|
|
4429
4648
|
if (existing) {
|
|
4430
4649
|
const oldEnd = existing.start + existing.count;
|
|
4431
4650
|
touchesEnd = oldEnd >= this.lines.length;
|
|
4651
|
+
const oldRows = this.wrappedRowsOfMany(
|
|
4652
|
+
this.lines.slice(existing.start, oldEnd)
|
|
4653
|
+
);
|
|
4432
4654
|
const delta = newLines.length - existing.count;
|
|
4433
|
-
scrollDelta = delta;
|
|
4434
4655
|
const removed = this.lines.splice(
|
|
4435
4656
|
existing.start,
|
|
4436
4657
|
existing.count,
|
|
@@ -4448,20 +4669,21 @@ var init_screen = __esm({
|
|
|
4448
4669
|
}
|
|
4449
4670
|
}
|
|
4450
4671
|
}
|
|
4672
|
+
rowDelta = this.wrappedRowsOfMany(newLines) - oldRows;
|
|
4451
4673
|
} else {
|
|
4452
4674
|
touchesEnd = true;
|
|
4453
|
-
scrollDelta = newLines.length;
|
|
4454
4675
|
this.keyedBlocks.set(key, {
|
|
4455
4676
|
start: this.lines.length,
|
|
4456
4677
|
count: newLines.length
|
|
4457
4678
|
});
|
|
4458
4679
|
this.lines.push(...newLines);
|
|
4459
4680
|
this.trackLines(newLines);
|
|
4681
|
+
rowDelta = this.wrappedRowsOfMany(newLines);
|
|
4460
4682
|
}
|
|
4461
4683
|
if (touchesEnd) {
|
|
4462
4684
|
this.streamingActive = false;
|
|
4463
4685
|
}
|
|
4464
|
-
this.
|
|
4686
|
+
this.adjustScrollForRowChange(rowDelta);
|
|
4465
4687
|
this.trimScrollback();
|
|
4466
4688
|
this.scheduleRepaint();
|
|
4467
4689
|
}
|
|
@@ -4475,12 +4697,14 @@ var init_screen = __esm({
|
|
|
4475
4697
|
}
|
|
4476
4698
|
const fragments = text.split("\n");
|
|
4477
4699
|
const [first, ...rest] = fragments;
|
|
4478
|
-
let
|
|
4700
|
+
let rowDelta = 0;
|
|
4479
4701
|
if (this.streamingActive && this.lines.length > 0) {
|
|
4480
4702
|
const last = this.lines[this.lines.length - 1];
|
|
4481
4703
|
if (last) {
|
|
4704
|
+
const before = this.wrappedRowsOf(last);
|
|
4482
4705
|
this.forgetLine(last);
|
|
4483
4706
|
last.body += first ?? "";
|
|
4707
|
+
rowDelta += this.wrappedRowsOf(last) - before;
|
|
4484
4708
|
}
|
|
4485
4709
|
} else {
|
|
4486
4710
|
if (this.lines.length > 0) {
|
|
@@ -4490,7 +4714,7 @@ var init_screen = __esm({
|
|
|
4490
4714
|
const sep = { body: "" };
|
|
4491
4715
|
this.lines.push(sep);
|
|
4492
4716
|
this.trackLine(sep);
|
|
4493
|
-
|
|
4717
|
+
rowDelta += this.wrappedRowsOf(sep);
|
|
4494
4718
|
}
|
|
4495
4719
|
}
|
|
4496
4720
|
const initial = {
|
|
@@ -4503,7 +4727,7 @@ var init_screen = __esm({
|
|
|
4503
4727
|
}
|
|
4504
4728
|
this.lines.push(initial);
|
|
4505
4729
|
this.trackLine(initial);
|
|
4506
|
-
|
|
4730
|
+
rowDelta += this.wrappedRowsOf(initial);
|
|
4507
4731
|
}
|
|
4508
4732
|
const continuationPrefix = " ".repeat(prefix.length);
|
|
4509
4733
|
for (const piece of rest) {
|
|
@@ -4514,10 +4738,10 @@ var init_screen = __esm({
|
|
|
4514
4738
|
};
|
|
4515
4739
|
this.lines.push(cont);
|
|
4516
4740
|
this.trackLine(cont);
|
|
4517
|
-
|
|
4741
|
+
rowDelta += this.wrappedRowsOf(cont);
|
|
4518
4742
|
}
|
|
4519
4743
|
this.streamingActive = true;
|
|
4520
|
-
this.
|
|
4744
|
+
this.adjustScrollForRowChange(rowDelta);
|
|
4521
4745
|
this.trimScrollback();
|
|
4522
4746
|
this.scheduleRepaint();
|
|
4523
4747
|
}
|
|
@@ -4628,6 +4852,9 @@ var init_screen = __esm({
|
|
|
4628
4852
|
return;
|
|
4629
4853
|
}
|
|
4630
4854
|
const touchesEnd = existing.start + existing.count >= this.lines.length;
|
|
4855
|
+
const removedRows = this.wrappedRowsOfMany(
|
|
4856
|
+
this.lines.slice(existing.start, existing.start + existing.count)
|
|
4857
|
+
);
|
|
4631
4858
|
const removed = this.lines.splice(existing.start, existing.count);
|
|
4632
4859
|
for (const line of removed) {
|
|
4633
4860
|
this.forgetLine(line);
|
|
@@ -4641,7 +4868,7 @@ var init_screen = __esm({
|
|
|
4641
4868
|
if (touchesEnd) {
|
|
4642
4869
|
this.streamingActive = false;
|
|
4643
4870
|
}
|
|
4644
|
-
this.
|
|
4871
|
+
this.adjustScrollForRowChange(-removedRows);
|
|
4645
4872
|
this.scheduleRepaint();
|
|
4646
4873
|
}
|
|
4647
4874
|
redraw() {
|
|
@@ -4726,7 +4953,7 @@ var init_screen = __esm({
|
|
|
4726
4953
|
this.lines.push(sep);
|
|
4727
4954
|
this.trackLine(sep);
|
|
4728
4955
|
this.streamingActive = false;
|
|
4729
|
-
this.
|
|
4956
|
+
this.adjustScrollForRowChange(this.wrappedRowsOf(sep));
|
|
4730
4957
|
this.trimScrollback();
|
|
4731
4958
|
this.scheduleRepaint();
|
|
4732
4959
|
}
|
|
@@ -7072,13 +7299,25 @@ async function runSession(term, config, opts, exitHint) {
|
|
|
7072
7299
|
} else if (event?.kind === "turn-complete") {
|
|
7073
7300
|
adjustPendingTurns(-1);
|
|
7074
7301
|
}
|
|
7302
|
+
if (rawTag === "permission_resolved") {
|
|
7303
|
+
handlePermissionResolved(update);
|
|
7304
|
+
return;
|
|
7305
|
+
}
|
|
7075
7306
|
appendRender(event);
|
|
7076
7307
|
maybeDismissPermissionByToolUpdate(update);
|
|
7077
7308
|
});
|
|
7078
|
-
|
|
7079
|
-
const
|
|
7080
|
-
|
|
7081
|
-
|
|
7309
|
+
const handlePermissionResolved = (update) => {
|
|
7310
|
+
const u = update ?? {};
|
|
7311
|
+
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : void 0;
|
|
7312
|
+
let outcome;
|
|
7313
|
+
if (u.outcome && typeof u.outcome === "object") {
|
|
7314
|
+
outcome = u.outcome;
|
|
7315
|
+
} else if (typeof u.chosenOptionId === "string") {
|
|
7316
|
+
outcome = { kind: "selected", optionId: u.chosenOptionId };
|
|
7317
|
+
}
|
|
7318
|
+
const result = outcome ? { outcome } : void 0;
|
|
7319
|
+
dismissPermissionExternally(toolCallId, result);
|
|
7320
|
+
};
|
|
7082
7321
|
let pendingPermission = null;
|
|
7083
7322
|
const dismissPermissionExternally = (toolCallId, result) => {
|
|
7084
7323
|
if (!pendingPermission) {
|
|
@@ -10705,7 +10944,7 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
10705
10944
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
10706
10945
|
reply.header(
|
|
10707
10946
|
"Content-Disposition",
|
|
10708
|
-
`attachment; filename="
|
|
10947
|
+
`attachment; filename="${id}-${stamp}.hydra"`
|
|
10709
10948
|
);
|
|
10710
10949
|
reply.code(200).send(bundle);
|
|
10711
10950
|
});
|
|
@@ -11071,15 +11310,20 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
11071
11310
|
connection,
|
|
11072
11311
|
session,
|
|
11073
11312
|
state,
|
|
11074
|
-
params.clientInfo
|
|
11313
|
+
params.clientInfo,
|
|
11314
|
+
params.clientId
|
|
11315
|
+
);
|
|
11316
|
+
const { entries: replay, appliedPolicy } = await session.attach(
|
|
11317
|
+
client,
|
|
11318
|
+
params.historyPolicy,
|
|
11319
|
+
{ afterMessageId: params.afterMessageId }
|
|
11075
11320
|
);
|
|
11076
|
-
const replay = await session.attach(client, params.historyPolicy);
|
|
11077
11321
|
state.attached.set(session.sessionId, {
|
|
11078
11322
|
sessionId: session.sessionId,
|
|
11079
11323
|
clientId: client.clientId
|
|
11080
11324
|
});
|
|
11081
11325
|
app.log.info(
|
|
11082
|
-
`session/attach OK sessionId=${session.sessionId} clientId=${client.clientId} attachedCount=${state.attached.size} replayed=${replay.length}`
|
|
11326
|
+
`session/attach OK sessionId=${session.sessionId} clientId=${client.clientId} attachedCount=${state.attached.size} requestedPolicy=${params.historyPolicy} appliedPolicy=${appliedPolicy} replayed=${replay.length}`
|
|
11083
11327
|
);
|
|
11084
11328
|
for (const note of replay) {
|
|
11085
11329
|
await connection.notify(note.method, note.params);
|
|
@@ -11087,6 +11331,13 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
11087
11331
|
session.replayPendingPermissions(client);
|
|
11088
11332
|
return {
|
|
11089
11333
|
sessionId: session.sessionId,
|
|
11334
|
+
clientId: client.clientId,
|
|
11335
|
+
connectedClients: session.connectedClients(client.clientId),
|
|
11336
|
+
// appliedPolicy surfaces whether after_message fell back to full
|
|
11337
|
+
// (because afterMessageId wasn't found in history) — RFD #533
|
|
11338
|
+
// says the response.historyPolicy should reflect what actually
|
|
11339
|
+
// ran, not what was asked for.
|
|
11340
|
+
historyPolicy: appliedPolicy,
|
|
11090
11341
|
replayed: replay.length,
|
|
11091
11342
|
_meta: buildResponseMeta(session)
|
|
11092
11343
|
};
|
|
@@ -11102,7 +11353,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
11102
11353
|
const session = deps.manager.get(params.sessionId);
|
|
11103
11354
|
session?.detach(att.clientId);
|
|
11104
11355
|
state.attached.delete(params.sessionId);
|
|
11105
|
-
return {
|
|
11356
|
+
return { sessionId: params.sessionId, status: "detached" };
|
|
11106
11357
|
});
|
|
11107
11358
|
connection.onRequest("session/list", async (raw) => {
|
|
11108
11359
|
const params = SessionListParams.parse(raw ?? {});
|
|
@@ -11175,7 +11426,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
11175
11426
|
session = await deps.manager.resurrect(fromDisk);
|
|
11176
11427
|
}
|
|
11177
11428
|
const client = bindClientToSession(connection, session, state);
|
|
11178
|
-
const replay = await session.attach(client, "pending_only");
|
|
11429
|
+
const { entries: replay } = await session.attach(client, "pending_only");
|
|
11179
11430
|
state.attached.set(session.sessionId, {
|
|
11180
11431
|
sessionId: session.sessionId,
|
|
11181
11432
|
clientId: client.clientId
|
|
@@ -11269,11 +11520,11 @@ function buildInitializeResult() {
|
|
|
11269
11520
|
]
|
|
11270
11521
|
};
|
|
11271
11522
|
}
|
|
11272
|
-
function bindClientToSession(connection, session, state, clientInfo) {
|
|
11523
|
+
function bindClientToSession(connection, session, state, clientInfo, callerClientId) {
|
|
11273
11524
|
void state;
|
|
11274
11525
|
void session;
|
|
11275
11526
|
return {
|
|
11276
|
-
clientId: `cli_${nanoid2(8)}`,
|
|
11527
|
+
clientId: callerClientId ?? `cli_${nanoid2(8)}`,
|
|
11277
11528
|
connection,
|
|
11278
11529
|
clientInfo
|
|
11279
11530
|
};
|
|
@@ -12216,10 +12467,22 @@ var SessionTracker = class {
|
|
|
12216
12467
|
contexts = /* @__PURE__ */ new Map();
|
|
12217
12468
|
pending = /* @__PURE__ */ new Map();
|
|
12218
12469
|
pendingPermissions = /* @__PURE__ */ new Map();
|
|
12470
|
+
// Secondary index — same entries as `pendingPermissions`, keyed by the
|
|
12471
|
+
// tool call id from the request_permission params. Used to correlate
|
|
12472
|
+
// the daemon's `session/update`/`permission_resolved` events back to the
|
|
12473
|
+
// pending downstream request, since per-recipient JSON-RPC ids are no
|
|
12474
|
+
// longer carried on the wire.
|
|
12475
|
+
pendingPermissionsByToolCall = /* @__PURE__ */ new Map();
|
|
12476
|
+
// Most recent messageId observed on a session/update from the daemon
|
|
12477
|
+
// (prompt_received / turn_complete), keyed by sessionId. Used by the
|
|
12478
|
+
// reconnect-replay path to send historyPolicy:"after_message" with
|
|
12479
|
+
// afterMessageId so the daemon only replays the delta we missed.
|
|
12480
|
+
lastMessageIds = /* @__PURE__ */ new Map();
|
|
12219
12481
|
observeFromClient(msg) {
|
|
12220
12482
|
if (isResponse2(msg)) {
|
|
12221
|
-
|
|
12222
|
-
|
|
12483
|
+
const existing = this.pendingPermissions.get(msg.id);
|
|
12484
|
+
if (existing) {
|
|
12485
|
+
this.deletePendingPermission(existing);
|
|
12223
12486
|
}
|
|
12224
12487
|
return;
|
|
12225
12488
|
}
|
|
@@ -12246,16 +12509,34 @@ var SessionTracker = class {
|
|
|
12246
12509
|
}
|
|
12247
12510
|
}
|
|
12248
12511
|
observeFromServer(msg) {
|
|
12512
|
+
if (!isRequest(msg) && !isResponse2(msg) && "method" in msg) {
|
|
12513
|
+
if (msg.method === "session/update") {
|
|
12514
|
+
const params = msg.params ?? {};
|
|
12515
|
+
const sessionId2 = typeof params.sessionId === "string" ? params.sessionId : void 0;
|
|
12516
|
+
const messageId = typeof params.update?.messageId === "string" ? params.update.messageId : void 0;
|
|
12517
|
+
if (sessionId2 && messageId) {
|
|
12518
|
+
this.lastMessageIds.set(sessionId2, messageId);
|
|
12519
|
+
}
|
|
12520
|
+
}
|
|
12521
|
+
return;
|
|
12522
|
+
}
|
|
12249
12523
|
if (isRequest(msg)) {
|
|
12250
12524
|
if (msg.method === "session/request_permission") {
|
|
12251
12525
|
const params = msg.params ?? {};
|
|
12252
12526
|
const sessionId2 = typeof params.sessionId === "string" ? params.sessionId : void 0;
|
|
12253
12527
|
if (sessionId2) {
|
|
12254
|
-
|
|
12528
|
+
const toolCall = params.toolCall;
|
|
12529
|
+
const toolCallId = toolCall && typeof toolCall.toolCallId === "string" ? toolCall.toolCallId : void 0;
|
|
12530
|
+
const entry = {
|
|
12255
12531
|
requestId: msg.id,
|
|
12256
12532
|
sessionId: sessionId2,
|
|
12533
|
+
toolCallId,
|
|
12257
12534
|
params
|
|
12258
|
-
}
|
|
12535
|
+
};
|
|
12536
|
+
this.pendingPermissions.set(msg.id, entry);
|
|
12537
|
+
if (toolCallId) {
|
|
12538
|
+
this.pendingPermissionsByToolCall.set(toolCallId, entry);
|
|
12539
|
+
}
|
|
12259
12540
|
}
|
|
12260
12541
|
}
|
|
12261
12542
|
return;
|
|
@@ -12303,6 +12584,14 @@ var SessionTracker = class {
|
|
|
12303
12584
|
}
|
|
12304
12585
|
forget(sessionId) {
|
|
12305
12586
|
this.contexts.delete(sessionId);
|
|
12587
|
+
this.lastMessageIds.delete(sessionId);
|
|
12588
|
+
}
|
|
12589
|
+
// Latest messageId observed for `sessionId`, or undefined if we
|
|
12590
|
+
// haven't seen one (no prompt_received/turn_complete has flowed
|
|
12591
|
+
// through yet). Used by reconnect-replay to issue
|
|
12592
|
+
// historyPolicy:"after_message" with afterMessageId.
|
|
12593
|
+
lastMessageId(sessionId) {
|
|
12594
|
+
return this.lastMessageIds.get(sessionId);
|
|
12306
12595
|
}
|
|
12307
12596
|
clearPending() {
|
|
12308
12597
|
this.pending.clear();
|
|
@@ -12310,15 +12599,29 @@ var SessionTracker = class {
|
|
|
12310
12599
|
takePendingPermissions() {
|
|
12311
12600
|
const out = [...this.pendingPermissions.values()];
|
|
12312
12601
|
this.pendingPermissions.clear();
|
|
12602
|
+
this.pendingPermissionsByToolCall.clear();
|
|
12313
12603
|
return out;
|
|
12314
12604
|
}
|
|
12315
12605
|
takePendingPermission(requestId) {
|
|
12316
12606
|
const found = this.pendingPermissions.get(requestId);
|
|
12317
12607
|
if (found) {
|
|
12318
|
-
this.
|
|
12608
|
+
this.deletePendingPermission(found);
|
|
12609
|
+
}
|
|
12610
|
+
return found;
|
|
12611
|
+
}
|
|
12612
|
+
takePendingPermissionByToolCall(toolCallId) {
|
|
12613
|
+
const found = this.pendingPermissionsByToolCall.get(toolCallId);
|
|
12614
|
+
if (found) {
|
|
12615
|
+
this.deletePendingPermission(found);
|
|
12319
12616
|
}
|
|
12320
12617
|
return found;
|
|
12321
12618
|
}
|
|
12619
|
+
deletePendingPermission(entry) {
|
|
12620
|
+
this.pendingPermissions.delete(entry.requestId);
|
|
12621
|
+
if (entry.toolCallId) {
|
|
12622
|
+
this.pendingPermissionsByToolCall.delete(entry.toolCallId);
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12322
12625
|
};
|
|
12323
12626
|
function isRequest(msg) {
|
|
12324
12627
|
return "method" in msg && "id" in msg && msg.id !== void 0;
|
|
@@ -12354,7 +12657,7 @@ async function runShim(opts) {
|
|
|
12354
12657
|
`
|
|
12355
12658
|
);
|
|
12356
12659
|
for (const ctx of contexts) {
|
|
12357
|
-
await replayAttach(upstream, ctx);
|
|
12660
|
+
await replayAttach(upstream, ctx, tracker.lastMessageId(ctx.sessionId));
|
|
12358
12661
|
}
|
|
12359
12662
|
}
|
|
12360
12663
|
});
|
|
@@ -12413,25 +12716,47 @@ function wireShim({
|
|
|
12413
12716
|
});
|
|
12414
12717
|
}
|
|
12415
12718
|
function maybeReplyToResolvedPermission(msg, tracker, downstream) {
|
|
12416
|
-
|
|
12719
|
+
const update = extractPermissionResolvedUpdate(msg);
|
|
12720
|
+
if (!update) {
|
|
12417
12721
|
return;
|
|
12418
12722
|
}
|
|
12419
|
-
const
|
|
12420
|
-
if (
|
|
12723
|
+
const toolCallId = typeof update.toolCallId === "string" ? update.toolCallId : void 0;
|
|
12724
|
+
if (!toolCallId) {
|
|
12421
12725
|
return;
|
|
12422
12726
|
}
|
|
12423
|
-
const pending = tracker.
|
|
12727
|
+
const pending = tracker.takePendingPermissionByToolCall(toolCallId);
|
|
12424
12728
|
if (!pending) {
|
|
12425
12729
|
return;
|
|
12426
12730
|
}
|
|
12731
|
+
const outcome = reconstructOutcome(update);
|
|
12427
12732
|
void downstream.send({
|
|
12428
12733
|
jsonrpc: "2.0",
|
|
12429
12734
|
id: pending.requestId,
|
|
12430
|
-
result:
|
|
12735
|
+
result: outcome ? { outcome } : null
|
|
12431
12736
|
}).catch(() => void 0);
|
|
12432
12737
|
}
|
|
12433
|
-
function
|
|
12434
|
-
|
|
12738
|
+
function extractPermissionResolvedUpdate(msg) {
|
|
12739
|
+
if (!isSessionUpdateNotification(msg)) {
|
|
12740
|
+
return void 0;
|
|
12741
|
+
}
|
|
12742
|
+
const params = msg.params ?? {};
|
|
12743
|
+
const update = params.update;
|
|
12744
|
+
if (!update || typeof update !== "object" || update.sessionUpdate !== "permission_resolved") {
|
|
12745
|
+
return void 0;
|
|
12746
|
+
}
|
|
12747
|
+
return update;
|
|
12748
|
+
}
|
|
12749
|
+
function isSessionUpdateNotification(msg) {
|
|
12750
|
+
return "method" in msg && msg.method === "session/update" && !("id" in msg && msg.id !== void 0);
|
|
12751
|
+
}
|
|
12752
|
+
function reconstructOutcome(update) {
|
|
12753
|
+
if (update.outcome && typeof update.outcome === "object") {
|
|
12754
|
+
return update.outcome;
|
|
12755
|
+
}
|
|
12756
|
+
if (typeof update.chosenOptionId === "string") {
|
|
12757
|
+
return { kind: "selected", optionId: update.chosenOptionId };
|
|
12758
|
+
}
|
|
12759
|
+
return void 0;
|
|
12435
12760
|
}
|
|
12436
12761
|
async function cancelPendingPermissions(tracker, downstream) {
|
|
12437
12762
|
const pendings = tracker.takePendingPermissions();
|
|
@@ -12443,21 +12768,26 @@ async function cancelPendingPermissions(tracker, downstream) {
|
|
|
12443
12768
|
`
|
|
12444
12769
|
);
|
|
12445
12770
|
for (const pending of pendings) {
|
|
12446
|
-
const
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12771
|
+
const sessionId = typeof pending.params.sessionId === "string" ? pending.params.sessionId : void 0;
|
|
12772
|
+
if (!sessionId) {
|
|
12773
|
+
continue;
|
|
12774
|
+
}
|
|
12775
|
+
const update = {
|
|
12776
|
+
sessionUpdate: "permission_resolved",
|
|
12777
|
+
outcome: { kind: "cancelled", reason: "daemon-disconnected" },
|
|
12778
|
+
resolvedBy: { clientId: "hydra-acp" }
|
|
12452
12779
|
};
|
|
12780
|
+
if (pending.toolCallId) {
|
|
12781
|
+
update.toolCallId = pending.toolCallId;
|
|
12782
|
+
}
|
|
12453
12783
|
await downstream.send({
|
|
12454
12784
|
jsonrpc: "2.0",
|
|
12455
|
-
method: "session/
|
|
12456
|
-
params
|
|
12785
|
+
method: "session/update",
|
|
12786
|
+
params: { sessionId, update }
|
|
12457
12787
|
}).catch(() => void 0);
|
|
12458
12788
|
}
|
|
12459
12789
|
}
|
|
12460
|
-
async function replayAttach(stream, ctx) {
|
|
12790
|
+
async function replayAttach(stream, ctx, afterMessageId) {
|
|
12461
12791
|
const resumeHints = {
|
|
12462
12792
|
upstreamSessionId: ctx.upstreamSessionId,
|
|
12463
12793
|
agentId: ctx.agentId,
|
|
@@ -12469,19 +12799,21 @@ async function replayAttach(stream, ctx) {
|
|
|
12469
12799
|
if (ctx.agentArgs && ctx.agentArgs.length > 0) {
|
|
12470
12800
|
resumeHints.agentArgs = ctx.agentArgs;
|
|
12471
12801
|
}
|
|
12802
|
+
const params = {
|
|
12803
|
+
sessionId: ctx.sessionId,
|
|
12804
|
+
_meta: { "hydra-acp": { resume: resumeHints } }
|
|
12805
|
+
};
|
|
12806
|
+
if (afterMessageId) {
|
|
12807
|
+
params.historyPolicy = "after_message";
|
|
12808
|
+
params.afterMessageId = afterMessageId;
|
|
12809
|
+
} else {
|
|
12810
|
+
params.historyPolicy = "pending_only";
|
|
12811
|
+
}
|
|
12472
12812
|
const request = {
|
|
12473
12813
|
jsonrpc: "2.0",
|
|
12474
12814
|
id: `resume-${ctx.sessionId}-${Date.now()}`,
|
|
12475
12815
|
method: "session/attach",
|
|
12476
|
-
params
|
|
12477
|
-
sessionId: ctx.sessionId,
|
|
12478
|
-
historyPolicy: "pending_only",
|
|
12479
|
-
_meta: {
|
|
12480
|
-
"hydra-acp": {
|
|
12481
|
-
resume: resumeHints
|
|
12482
|
-
}
|
|
12483
|
-
}
|
|
12484
|
-
}
|
|
12816
|
+
params
|
|
12485
12817
|
};
|
|
12486
12818
|
try {
|
|
12487
12819
|
const resp = await stream.request(request);
|