@hydra-acp/cli 0.1.8 → 0.1.10
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 +414 -82
- package/dist/index.d.ts +23 -5
- package/dist/index.js +240 -30
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1245,11 +1245,13 @@ interface JsonRpcError {
|
|
|
1245
1245
|
data?: unknown;
|
|
1246
1246
|
}
|
|
1247
1247
|
type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse;
|
|
1248
|
-
declare const HistoryPolicy: z.ZodEnum<["full", "pending_only", "none"]>;
|
|
1248
|
+
declare const HistoryPolicy: z.ZodEnum<["full", "pending_only", "none", "after_message"]>;
|
|
1249
1249
|
type HistoryPolicy = z.infer<typeof HistoryPolicy>;
|
|
1250
1250
|
declare const SessionAttachParams: z.ZodObject<{
|
|
1251
1251
|
sessionId: z.ZodString;
|
|
1252
|
-
historyPolicy: z.ZodDefault<z.ZodEnum<["full", "pending_only", "none"]>>;
|
|
1252
|
+
historyPolicy: z.ZodDefault<z.ZodEnum<["full", "pending_only", "none", "after_message"]>>;
|
|
1253
|
+
afterMessageId: z.ZodOptional<z.ZodString>;
|
|
1254
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
1253
1255
|
clientInfo: z.ZodOptional<z.ZodObject<{
|
|
1254
1256
|
name: z.ZodString;
|
|
1255
1257
|
version: z.ZodOptional<z.ZodString>;
|
|
@@ -1263,11 +1265,13 @@ declare const SessionAttachParams: z.ZodObject<{
|
|
|
1263
1265
|
_meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
1264
1266
|
}, "strip", z.ZodTypeAny, {
|
|
1265
1267
|
sessionId: string;
|
|
1266
|
-
historyPolicy: "full" | "pending_only" | "none";
|
|
1268
|
+
historyPolicy: "full" | "pending_only" | "none" | "after_message";
|
|
1267
1269
|
clientInfo?: {
|
|
1268
1270
|
name: string;
|
|
1269
1271
|
version?: string | undefined;
|
|
1270
1272
|
} | undefined;
|
|
1273
|
+
afterMessageId?: string | undefined;
|
|
1274
|
+
clientId?: string | undefined;
|
|
1271
1275
|
_meta?: Record<string, unknown> | undefined;
|
|
1272
1276
|
}, {
|
|
1273
1277
|
sessionId: string;
|
|
@@ -1275,7 +1279,9 @@ declare const SessionAttachParams: z.ZodObject<{
|
|
|
1275
1279
|
name: string;
|
|
1276
1280
|
version?: string | undefined;
|
|
1277
1281
|
} | undefined;
|
|
1278
|
-
historyPolicy?: "full" | "pending_only" | "none" | undefined;
|
|
1282
|
+
historyPolicy?: "full" | "pending_only" | "none" | "after_message" | undefined;
|
|
1283
|
+
afterMessageId?: string | undefined;
|
|
1284
|
+
clientId?: string | undefined;
|
|
1279
1285
|
_meta?: Record<string, unknown> | undefined;
|
|
1280
1286
|
}>;
|
|
1281
1287
|
type SessionAttachParams = z.infer<typeof SessionAttachParams>;
|
|
@@ -1691,12 +1697,24 @@ declare class Session {
|
|
|
1691
1697
|
upstreamSessionId: string;
|
|
1692
1698
|
}) => void): void;
|
|
1693
1699
|
get attachedCount(): number;
|
|
1700
|
+
connectedClients(excludeClientId?: string): Array<{
|
|
1701
|
+
clientId: string;
|
|
1702
|
+
name?: string;
|
|
1703
|
+
version?: string;
|
|
1704
|
+
}>;
|
|
1694
1705
|
get turnStartedAt(): number | undefined;
|
|
1695
1706
|
getHistorySnapshot(): Promise<CachedNotification[]>;
|
|
1696
1707
|
onBroadcast(handler: (entry: CachedNotification) => void): () => void;
|
|
1697
|
-
attach(client: AttachedClient, historyPolicy: HistoryPolicy
|
|
1708
|
+
attach(client: AttachedClient, historyPolicy: HistoryPolicy, opts?: {
|
|
1709
|
+
afterMessageId?: string;
|
|
1710
|
+
}): Promise<{
|
|
1711
|
+
entries: CachedNotification[];
|
|
1712
|
+
appliedPolicy: HistoryPolicy;
|
|
1713
|
+
}>;
|
|
1714
|
+
private loadReplay;
|
|
1698
1715
|
replayPendingPermissions(client: AttachedClient): void;
|
|
1699
1716
|
detach(clientId: string): void;
|
|
1717
|
+
private broadcastClientDisconnected;
|
|
1700
1718
|
prompt(clientId: string, params: unknown): Promise<unknown>;
|
|
1701
1719
|
private broadcastPromptReceived;
|
|
1702
1720
|
private broadcastTurnComplete;
|
package/dist/index.js
CHANGED
|
@@ -854,11 +854,22 @@ var JsonRpcErrorCodes = {
|
|
|
854
854
|
MethodNotFound: -32601,
|
|
855
855
|
InvalidParams: -32602,
|
|
856
856
|
InternalError: -32603,
|
|
857
|
+
// -32001…-32003 reserved for RFD #533 attach semantics:
|
|
858
|
+
// -32001 Session not found
|
|
859
|
+
// -32002 Not authorised to attach
|
|
860
|
+
// -32003 Session does not support multi-client attach
|
|
861
|
+
// We emit -32001 (matching); the other two are reserved for spec
|
|
862
|
+
// alignment even though we don't currently emit them (we bearer-auth
|
|
863
|
+
// at WS upgrade time and always support multi-client attach).
|
|
857
864
|
SessionNotFound: -32001,
|
|
858
|
-
|
|
859
|
-
|
|
865
|
+
NotAuthorisedToAttach: -32002,
|
|
866
|
+
MultiClientNotSupported: -32003,
|
|
860
867
|
AgentNotInstalled: -32005,
|
|
861
|
-
|
|
868
|
+
// Hydra-internal codes — outside the RFD's reserved range so they
|
|
869
|
+
// can't collide with future spec assignments.
|
|
870
|
+
BundleAlreadyImported: -32010,
|
|
871
|
+
PermissionDenied: -32011,
|
|
872
|
+
AlreadyAttached: -32012
|
|
862
873
|
};
|
|
863
874
|
var InitializeParams = z3.object({
|
|
864
875
|
protocolVersion: z3.number().optional(),
|
|
@@ -868,7 +879,12 @@ var InitializeParams = z3.object({
|
|
|
868
879
|
version: z3.string().optional()
|
|
869
880
|
}).optional()
|
|
870
881
|
});
|
|
871
|
-
var HistoryPolicy = z3.enum([
|
|
882
|
+
var HistoryPolicy = z3.enum([
|
|
883
|
+
"full",
|
|
884
|
+
"pending_only",
|
|
885
|
+
"none",
|
|
886
|
+
"after_message"
|
|
887
|
+
]);
|
|
872
888
|
var SessionNewParams = z3.object({
|
|
873
889
|
cwd: z3.string(),
|
|
874
890
|
agentId: z3.string().optional(),
|
|
@@ -884,6 +900,18 @@ var SessionResumeHints = z3.object({
|
|
|
884
900
|
var SessionAttachParams = z3.object({
|
|
885
901
|
sessionId: z3.string(),
|
|
886
902
|
historyPolicy: HistoryPolicy.default("full"),
|
|
903
|
+
// Required when historyPolicy is "after_message"; ignored otherwise.
|
|
904
|
+
// The proxy replays history entries strictly after the entry whose
|
|
905
|
+
// messageId matches this value. If the id isn't found in the buffer,
|
|
906
|
+
// the response.historyPolicy field surfaces "full" so the caller
|
|
907
|
+
// knows we fell back. Per RFD #533.
|
|
908
|
+
afterMessageId: z3.string().optional(),
|
|
909
|
+
// Caller-assigned opaque id (e.g. a UUID). When provided, the proxy
|
|
910
|
+
// echoes it in resolvedBy/sentBy and lifecycle events so other
|
|
911
|
+
// clients can disambiguate multiple instances of the same
|
|
912
|
+
// clientInfo.name. When omitted, the proxy assigns one and returns
|
|
913
|
+
// it in the response. Per RFD #533.
|
|
914
|
+
clientId: z3.string().optional(),
|
|
887
915
|
clientInfo: z3.object({
|
|
888
916
|
name: z3.string(),
|
|
889
917
|
version: z3.string().optional()
|
|
@@ -1385,6 +1413,9 @@ function hydraCommandsAsAdvertised() {
|
|
|
1385
1413
|
var HYDRA_ID_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
1386
1414
|
var generateHydraId = customAlphabet(HYDRA_ID_ALPHABET, 16);
|
|
1387
1415
|
var HYDRA_SESSION_PREFIX = "hydra_session_";
|
|
1416
|
+
function generateMessageId() {
|
|
1417
|
+
return `m_${generateHydraId()}`;
|
|
1418
|
+
}
|
|
1388
1419
|
var DEFAULT_HISTORY_MAX_ENTRIES = 1e3;
|
|
1389
1420
|
var Session = class {
|
|
1390
1421
|
sessionId;
|
|
@@ -1559,6 +1590,30 @@ var Session = class {
|
|
|
1559
1590
|
get attachedCount() {
|
|
1560
1591
|
return this.clients.size;
|
|
1561
1592
|
}
|
|
1593
|
+
// Roster of currently-attached clients, optionally excluding one
|
|
1594
|
+
// clientId. Used by the daemon to populate connectedClients on the
|
|
1595
|
+
// session/attach response (per RFD #533) — the freshly-attaching
|
|
1596
|
+
// client wants to see who else is on the session but not itself in
|
|
1597
|
+
// the list.
|
|
1598
|
+
connectedClients(excludeClientId) {
|
|
1599
|
+
const out = [];
|
|
1600
|
+
for (const client of this.clients.values()) {
|
|
1601
|
+
if (excludeClientId && client.clientId === excludeClientId) {
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
const entry = {
|
|
1605
|
+
clientId: client.clientId
|
|
1606
|
+
};
|
|
1607
|
+
if (client.clientInfo?.name) {
|
|
1608
|
+
entry.name = client.clientInfo.name;
|
|
1609
|
+
}
|
|
1610
|
+
if (client.clientInfo?.version) {
|
|
1611
|
+
entry.version = client.clientInfo.version;
|
|
1612
|
+
}
|
|
1613
|
+
out.push(entry);
|
|
1614
|
+
}
|
|
1615
|
+
return out;
|
|
1616
|
+
}
|
|
1562
1617
|
// Wall-clock when the in-flight agent turn began, or undefined when
|
|
1563
1618
|
// idle. Tracked in-memory by broadcastPromptReceived/broadcastTurnComplete
|
|
1564
1619
|
// so the daemon can hand a fresh attacher mid-turn the right elapsed
|
|
@@ -1590,10 +1645,12 @@ var Session = class {
|
|
|
1590
1645
|
};
|
|
1591
1646
|
}
|
|
1592
1647
|
// Register a client and (asynchronously) load the replay slice it
|
|
1593
|
-
// should receive.
|
|
1594
|
-
//
|
|
1595
|
-
//
|
|
1596
|
-
|
|
1648
|
+
// should receive. Returns both the slice to replay and the actual
|
|
1649
|
+
// historyPolicy applied (which may differ from the requested one
|
|
1650
|
+
// when after_message falls back to full). Validation errors throw
|
|
1651
|
+
// synchronously so callers can rely on either the registration being
|
|
1652
|
+
// in effect or having thrown; the disk-load is the only async work.
|
|
1653
|
+
attach(client, historyPolicy, opts = {}) {
|
|
1597
1654
|
if (this.closed) {
|
|
1598
1655
|
throw withCode(
|
|
1599
1656
|
new Error("session is closed"),
|
|
@@ -1609,9 +1666,20 @@ var Session = class {
|
|
|
1609
1666
|
this.clients.set(client.clientId, client);
|
|
1610
1667
|
this.updatedAt = Date.now();
|
|
1611
1668
|
if (historyPolicy === "none" || historyPolicy === "pending_only") {
|
|
1612
|
-
return Promise.resolve([]);
|
|
1669
|
+
return Promise.resolve({ entries: [], appliedPolicy: historyPolicy });
|
|
1670
|
+
}
|
|
1671
|
+
return this.loadReplay(historyPolicy, opts);
|
|
1672
|
+
}
|
|
1673
|
+
async loadReplay(historyPolicy, opts) {
|
|
1674
|
+
const all = await this.getHistorySnapshot();
|
|
1675
|
+
if (historyPolicy === "after_message") {
|
|
1676
|
+
const cutoff = opts.afterMessageId ? findMessageIdIndex(all, opts.afterMessageId) : -1;
|
|
1677
|
+
if (cutoff < 0) {
|
|
1678
|
+
return { entries: all, appliedPolicy: "full" };
|
|
1679
|
+
}
|
|
1680
|
+
return { entries: all.slice(cutoff + 1), appliedPolicy: "after_message" };
|
|
1613
1681
|
}
|
|
1614
|
-
return
|
|
1682
|
+
return { entries: all, appliedPolicy: "full" };
|
|
1615
1683
|
}
|
|
1616
1684
|
// Dispatch in-flight permission requests to a freshly-attached
|
|
1617
1685
|
// client. Called by the daemon's WS handler *after* it finishes
|
|
@@ -1623,8 +1691,39 @@ var Session = class {
|
|
|
1623
1691
|
}
|
|
1624
1692
|
}
|
|
1625
1693
|
detach(clientId) {
|
|
1626
|
-
|
|
1627
|
-
|
|
1694
|
+
const leaving = this.clients.get(clientId);
|
|
1695
|
+
if (!leaving) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
this.clients.delete(clientId);
|
|
1699
|
+
this.updatedAt = Date.now();
|
|
1700
|
+
this.broadcastClientDisconnected(leaving);
|
|
1701
|
+
}
|
|
1702
|
+
// Notify remaining attached clients that a peer just left, per
|
|
1703
|
+
// RFD #533. Fires for both explicit session/detach and ws-close
|
|
1704
|
+
// teardown (acp-ws calls Session.detach() in both paths). The
|
|
1705
|
+
// notification is broadcast (not recorded) — peer presence is
|
|
1706
|
+
// transient, not part of conversation history.
|
|
1707
|
+
broadcastClientDisconnected(client) {
|
|
1708
|
+
const info = {
|
|
1709
|
+
clientId: client.clientId
|
|
1710
|
+
};
|
|
1711
|
+
if (client.clientInfo?.name) {
|
|
1712
|
+
info.name = client.clientInfo.name;
|
|
1713
|
+
}
|
|
1714
|
+
if (client.clientInfo?.version) {
|
|
1715
|
+
info.version = client.clientInfo.version;
|
|
1716
|
+
}
|
|
1717
|
+
const update = {
|
|
1718
|
+
sessionUpdate: "client_disconnected",
|
|
1719
|
+
client: info,
|
|
1720
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1721
|
+
};
|
|
1722
|
+
for (const peer of this.clients.values()) {
|
|
1723
|
+
void peer.connection.notify("session/update", {
|
|
1724
|
+
sessionId: this.sessionId,
|
|
1725
|
+
update
|
|
1726
|
+
}).catch(() => void 0);
|
|
1628
1727
|
}
|
|
1629
1728
|
}
|
|
1630
1729
|
async prompt(clientId, params) {
|
|
@@ -1677,6 +1776,7 @@ var Session = class {
|
|
|
1677
1776
|
sessionId: this.sessionId,
|
|
1678
1777
|
update: {
|
|
1679
1778
|
sessionUpdate: "prompt_received",
|
|
1779
|
+
messageId: generateMessageId(),
|
|
1680
1780
|
prompt: promptParams.prompt,
|
|
1681
1781
|
sentBy
|
|
1682
1782
|
}
|
|
@@ -1702,7 +1802,8 @@ var Session = class {
|
|
|
1702
1802
|
broadcastTurnComplete(originatorClientId, response) {
|
|
1703
1803
|
const stopReason = response && typeof response === "object" && "stopReason" in response && typeof response.stopReason === "string" ? response.stopReason : void 0;
|
|
1704
1804
|
const update = {
|
|
1705
|
-
sessionUpdate: "turn_complete"
|
|
1805
|
+
sessionUpdate: "turn_complete",
|
|
1806
|
+
messageId: generateMessageId()
|
|
1706
1807
|
};
|
|
1707
1808
|
if (stopReason !== void 0) {
|
|
1708
1809
|
update.stopReason = stopReason;
|
|
@@ -2319,10 +2420,11 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
2319
2420
|
recordAndBroadcast(method, params, excludeClientId) {
|
|
2320
2421
|
const rewritten = this.rewriteForClient(params);
|
|
2321
2422
|
const recordable = !isStateUpdate(method, rewritten);
|
|
2423
|
+
const broadcast = recordable ? ensureMessageIdOnUpdate(method, rewritten) : rewritten;
|
|
2322
2424
|
if (recordable) {
|
|
2323
2425
|
const entry = {
|
|
2324
2426
|
method,
|
|
2325
|
-
params:
|
|
2427
|
+
params: broadcast,
|
|
2326
2428
|
recordedAt: Date.now()
|
|
2327
2429
|
};
|
|
2328
2430
|
this.lastRecordedAt = entry.recordedAt;
|
|
@@ -2350,7 +2452,7 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
2350
2452
|
if (excludeClientId && client.clientId === excludeClientId) {
|
|
2351
2453
|
continue;
|
|
2352
2454
|
}
|
|
2353
|
-
void client.connection.notify(method,
|
|
2455
|
+
void client.connection.notify(method, broadcast).catch(() => void 0);
|
|
2354
2456
|
}
|
|
2355
2457
|
}
|
|
2356
2458
|
async handlePermissionRequest(params) {
|
|
@@ -2362,11 +2464,13 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
2362
2464
|
);
|
|
2363
2465
|
}
|
|
2364
2466
|
const clientParams = this.rewriteForClient(params);
|
|
2467
|
+
const toolCallId = extractToolCallId(clientParams);
|
|
2365
2468
|
return new Promise((resolve3, reject) => {
|
|
2366
2469
|
let settled = false;
|
|
2367
2470
|
const outbound = [];
|
|
2368
2471
|
const entry = { addClient: sendTo };
|
|
2369
2472
|
this.inFlightPermissions.add(entry);
|
|
2473
|
+
const sessionId = this.sessionId;
|
|
2370
2474
|
const settle = (fn) => {
|
|
2371
2475
|
if (settled) {
|
|
2372
2476
|
return;
|
|
@@ -2379,22 +2483,25 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
2379
2483
|
if (settled) {
|
|
2380
2484
|
return;
|
|
2381
2485
|
}
|
|
2382
|
-
const
|
|
2486
|
+
const response = client.connection.request(
|
|
2383
2487
|
"session/request_permission",
|
|
2384
2488
|
clientParams
|
|
2385
2489
|
);
|
|
2386
|
-
outbound.push({ client
|
|
2490
|
+
outbound.push({ client });
|
|
2387
2491
|
void response.then((result) => {
|
|
2388
2492
|
settle(() => {
|
|
2493
|
+
const update = buildPermissionResolvedUpdate({
|
|
2494
|
+
toolCallId,
|
|
2495
|
+
result,
|
|
2496
|
+
resolver: client
|
|
2497
|
+
});
|
|
2389
2498
|
for (const o of outbound) {
|
|
2390
2499
|
if (o.client.clientId === client.clientId) {
|
|
2391
2500
|
continue;
|
|
2392
2501
|
}
|
|
2393
|
-
void o.client.connection.notify("session/
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
resolvedBy: client.clientId,
|
|
2397
|
-
result
|
|
2502
|
+
void o.client.connection.notify("session/update", {
|
|
2503
|
+
sessionId,
|
|
2504
|
+
update
|
|
2398
2505
|
}).catch(() => void 0);
|
|
2399
2506
|
}
|
|
2400
2507
|
resolve3(result);
|
|
@@ -2507,6 +2614,97 @@ function extractAdvertisedCommands(params) {
|
|
|
2507
2614
|
}
|
|
2508
2615
|
return out;
|
|
2509
2616
|
}
|
|
2617
|
+
function ensureMessageIdOnUpdate(method, params) {
|
|
2618
|
+
if (method !== "session/update" || !params || typeof params !== "object") {
|
|
2619
|
+
return params;
|
|
2620
|
+
}
|
|
2621
|
+
const p = params;
|
|
2622
|
+
if (!p.update || typeof p.update !== "object" || Array.isArray(p.update)) {
|
|
2623
|
+
return params;
|
|
2624
|
+
}
|
|
2625
|
+
const u = p.update;
|
|
2626
|
+
if (typeof u.messageId === "string") {
|
|
2627
|
+
return params;
|
|
2628
|
+
}
|
|
2629
|
+
return {
|
|
2630
|
+
...params,
|
|
2631
|
+
update: { ...p.update, messageId: generateMessageId() }
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
function findMessageIdIndex(history, target) {
|
|
2635
|
+
for (let i = 0; i < history.length; i++) {
|
|
2636
|
+
const entry = history[i];
|
|
2637
|
+
if (!entry || entry.method !== "session/update") {
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
const params = entry.params;
|
|
2641
|
+
if (params?.update?.messageId === target) {
|
|
2642
|
+
return i;
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
return -1;
|
|
2646
|
+
}
|
|
2647
|
+
function extractToolCallId(params) {
|
|
2648
|
+
if (!params || typeof params !== "object") {
|
|
2649
|
+
return void 0;
|
|
2650
|
+
}
|
|
2651
|
+
const toolCall = params.toolCall;
|
|
2652
|
+
if (!toolCall || typeof toolCall !== "object") {
|
|
2653
|
+
return void 0;
|
|
2654
|
+
}
|
|
2655
|
+
const id = toolCall.toolCallId;
|
|
2656
|
+
return typeof id === "string" ? id : void 0;
|
|
2657
|
+
}
|
|
2658
|
+
function buildPermissionResolvedUpdate(args) {
|
|
2659
|
+
const outcome = extractOutcome(args.result);
|
|
2660
|
+
const update = {
|
|
2661
|
+
sessionUpdate: "permission_resolved"
|
|
2662
|
+
};
|
|
2663
|
+
if (args.toolCallId !== void 0) {
|
|
2664
|
+
update.toolCallId = args.toolCallId;
|
|
2665
|
+
}
|
|
2666
|
+
if (outcome) {
|
|
2667
|
+
update.outcome = outcome;
|
|
2668
|
+
if (outcome.kind === "selected" && typeof outcome.optionId === "string") {
|
|
2669
|
+
update.chosenOptionId = outcome.optionId;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
update.resolvedBy = buildResolvedBy(args.resolver);
|
|
2673
|
+
return update;
|
|
2674
|
+
}
|
|
2675
|
+
function extractOutcome(result) {
|
|
2676
|
+
if (!result || typeof result !== "object") {
|
|
2677
|
+
return void 0;
|
|
2678
|
+
}
|
|
2679
|
+
const raw = result.outcome;
|
|
2680
|
+
if (!raw || typeof raw !== "object") {
|
|
2681
|
+
return void 0;
|
|
2682
|
+
}
|
|
2683
|
+
const kind = raw.kind;
|
|
2684
|
+
if (typeof kind !== "string") {
|
|
2685
|
+
return void 0;
|
|
2686
|
+
}
|
|
2687
|
+
const out = { kind };
|
|
2688
|
+
const optionId = raw.optionId;
|
|
2689
|
+
if (typeof optionId === "string") {
|
|
2690
|
+
out.optionId = optionId;
|
|
2691
|
+
}
|
|
2692
|
+
const reason = raw.reason;
|
|
2693
|
+
if (typeof reason === "string") {
|
|
2694
|
+
out.reason = reason;
|
|
2695
|
+
}
|
|
2696
|
+
return out;
|
|
2697
|
+
}
|
|
2698
|
+
function buildResolvedBy(client) {
|
|
2699
|
+
const out = { clientId: client.clientId };
|
|
2700
|
+
if (client.clientInfo?.name) {
|
|
2701
|
+
out.name = client.clientInfo.name;
|
|
2702
|
+
}
|
|
2703
|
+
if (client.clientInfo?.version) {
|
|
2704
|
+
out.version = client.clientInfo.version;
|
|
2705
|
+
}
|
|
2706
|
+
return out;
|
|
2707
|
+
}
|
|
2510
2708
|
function extractPromptText(prompt) {
|
|
2511
2709
|
if (typeof prompt === "string") {
|
|
2512
2710
|
return prompt;
|
|
@@ -4283,7 +4481,7 @@ function registerSessionRoutes(app, manager, defaults) {
|
|
|
4283
4481
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4284
4482
|
reply.header(
|
|
4285
4483
|
"Content-Disposition",
|
|
4286
|
-
`attachment; filename="
|
|
4484
|
+
`attachment; filename="${id}-${stamp}.hydra"`
|
|
4287
4485
|
);
|
|
4288
4486
|
reply.code(200).send(bundle);
|
|
4289
4487
|
});
|
|
@@ -4718,15 +4916,20 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
4718
4916
|
connection,
|
|
4719
4917
|
session,
|
|
4720
4918
|
state,
|
|
4721
|
-
params.clientInfo
|
|
4919
|
+
params.clientInfo,
|
|
4920
|
+
params.clientId
|
|
4921
|
+
);
|
|
4922
|
+
const { entries: replay, appliedPolicy } = await session.attach(
|
|
4923
|
+
client,
|
|
4924
|
+
params.historyPolicy,
|
|
4925
|
+
{ afterMessageId: params.afterMessageId }
|
|
4722
4926
|
);
|
|
4723
|
-
const replay = await session.attach(client, params.historyPolicy);
|
|
4724
4927
|
state.attached.set(session.sessionId, {
|
|
4725
4928
|
sessionId: session.sessionId,
|
|
4726
4929
|
clientId: client.clientId
|
|
4727
4930
|
});
|
|
4728
4931
|
app.log.info(
|
|
4729
|
-
`session/attach OK sessionId=${session.sessionId} clientId=${client.clientId} attachedCount=${state.attached.size} replayed=${replay.length}`
|
|
4932
|
+
`session/attach OK sessionId=${session.sessionId} clientId=${client.clientId} attachedCount=${state.attached.size} requestedPolicy=${params.historyPolicy} appliedPolicy=${appliedPolicy} replayed=${replay.length}`
|
|
4730
4933
|
);
|
|
4731
4934
|
for (const note of replay) {
|
|
4732
4935
|
await connection.notify(note.method, note.params);
|
|
@@ -4734,6 +4937,13 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
4734
4937
|
session.replayPendingPermissions(client);
|
|
4735
4938
|
return {
|
|
4736
4939
|
sessionId: session.sessionId,
|
|
4940
|
+
clientId: client.clientId,
|
|
4941
|
+
connectedClients: session.connectedClients(client.clientId),
|
|
4942
|
+
// appliedPolicy surfaces whether after_message fell back to full
|
|
4943
|
+
// (because afterMessageId wasn't found in history) — RFD #533
|
|
4944
|
+
// says the response.historyPolicy should reflect what actually
|
|
4945
|
+
// ran, not what was asked for.
|
|
4946
|
+
historyPolicy: appliedPolicy,
|
|
4737
4947
|
replayed: replay.length,
|
|
4738
4948
|
_meta: buildResponseMeta(session)
|
|
4739
4949
|
};
|
|
@@ -4749,7 +4959,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
4749
4959
|
const session = deps.manager.get(params.sessionId);
|
|
4750
4960
|
session?.detach(att.clientId);
|
|
4751
4961
|
state.attached.delete(params.sessionId);
|
|
4752
|
-
return {
|
|
4962
|
+
return { sessionId: params.sessionId, status: "detached" };
|
|
4753
4963
|
});
|
|
4754
4964
|
connection.onRequest("session/list", async (raw) => {
|
|
4755
4965
|
const params = SessionListParams.parse(raw ?? {});
|
|
@@ -4822,7 +5032,7 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
4822
5032
|
session = await deps.manager.resurrect(fromDisk);
|
|
4823
5033
|
}
|
|
4824
5034
|
const client = bindClientToSession(connection, session, state);
|
|
4825
|
-
const replay = await session.attach(client, "pending_only");
|
|
5035
|
+
const { entries: replay } = await session.attach(client, "pending_only");
|
|
4826
5036
|
state.attached.set(session.sessionId, {
|
|
4827
5037
|
sessionId: session.sessionId,
|
|
4828
5038
|
clientId: client.clientId
|
|
@@ -4916,11 +5126,11 @@ function buildInitializeResult() {
|
|
|
4916
5126
|
]
|
|
4917
5127
|
};
|
|
4918
5128
|
}
|
|
4919
|
-
function bindClientToSession(connection, session, state, clientInfo) {
|
|
5129
|
+
function bindClientToSession(connection, session, state, clientInfo, callerClientId) {
|
|
4920
5130
|
void state;
|
|
4921
5131
|
void session;
|
|
4922
5132
|
return {
|
|
4923
|
-
clientId: `cli_${nanoid2(8)}`,
|
|
5133
|
+
clientId: callerClientId ?? `cli_${nanoid2(8)}`,
|
|
4924
5134
|
connection,
|
|
4925
5135
|
clientInfo
|
|
4926
5136
|
};
|