@hydra-acp/cli 0.1.24 → 0.1.25
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/dist/cli.js +767 -64
- package/dist/index.d.ts +48 -0
- package/dist/index.js +344 -13
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -102,6 +102,7 @@ declare const HydraConfig: z.ZodObject<{
|
|
|
102
102
|
logMaxBytes: z.ZodDefault<z.ZodNumber>;
|
|
103
103
|
cwdColumnMaxWidth: z.ZodDefault<z.ZodNumber>;
|
|
104
104
|
progressIndicator: z.ZodDefault<z.ZodBoolean>;
|
|
105
|
+
defaultEnterAction: z.ZodDefault<z.ZodEnum<["enqueue", "amend"]>>;
|
|
105
106
|
}, "strip", z.ZodTypeAny, {
|
|
106
107
|
repaintThrottleMs: number;
|
|
107
108
|
maxScrollbackLines: number;
|
|
@@ -109,6 +110,7 @@ declare const HydraConfig: z.ZodObject<{
|
|
|
109
110
|
logMaxBytes: number;
|
|
110
111
|
cwdColumnMaxWidth: number;
|
|
111
112
|
progressIndicator: boolean;
|
|
113
|
+
defaultEnterAction: "enqueue" | "amend";
|
|
112
114
|
}, {
|
|
113
115
|
repaintThrottleMs?: number | undefined;
|
|
114
116
|
maxScrollbackLines?: number | undefined;
|
|
@@ -116,6 +118,7 @@ declare const HydraConfig: z.ZodObject<{
|
|
|
116
118
|
logMaxBytes?: number | undefined;
|
|
117
119
|
cwdColumnMaxWidth?: number | undefined;
|
|
118
120
|
progressIndicator?: boolean | undefined;
|
|
121
|
+
defaultEnterAction?: "enqueue" | "amend" | undefined;
|
|
119
122
|
}>>;
|
|
120
123
|
}, "strip", z.ZodTypeAny, {
|
|
121
124
|
daemon: {
|
|
@@ -143,6 +146,7 @@ declare const HydraConfig: z.ZodObject<{
|
|
|
143
146
|
logMaxBytes: number;
|
|
144
147
|
cwdColumnMaxWidth: number;
|
|
145
148
|
progressIndicator: boolean;
|
|
149
|
+
defaultEnterAction: "enqueue" | "amend";
|
|
146
150
|
};
|
|
147
151
|
registry: {
|
|
148
152
|
url: string;
|
|
@@ -179,6 +183,7 @@ declare const HydraConfig: z.ZodObject<{
|
|
|
179
183
|
logMaxBytes?: number | undefined;
|
|
180
184
|
cwdColumnMaxWidth?: number | undefined;
|
|
181
185
|
progressIndicator?: boolean | undefined;
|
|
186
|
+
defaultEnterAction?: "enqueue" | "amend" | undefined;
|
|
182
187
|
} | undefined;
|
|
183
188
|
registry?: {
|
|
184
189
|
url?: string | undefined;
|
|
@@ -1534,6 +1539,40 @@ declare const UpdatePromptResult: z.ZodObject<{
|
|
|
1534
1539
|
updated: boolean;
|
|
1535
1540
|
}>;
|
|
1536
1541
|
type UpdatePromptResult = z.infer<typeof UpdatePromptResult>;
|
|
1542
|
+
declare const AmendPromptParams: z.ZodObject<{
|
|
1543
|
+
sessionId: z.ZodString;
|
|
1544
|
+
targetMessageId: z.ZodString;
|
|
1545
|
+
prompt: z.ZodArray<z.ZodUnknown, "many">;
|
|
1546
|
+
replaceQueue: z.ZodOptional<z.ZodBoolean>;
|
|
1547
|
+
onTargetCompleted: z.ZodOptional<z.ZodEnum<["reject", "send_anyway"]>>;
|
|
1548
|
+
}, "strip", z.ZodTypeAny, {
|
|
1549
|
+
sessionId: string;
|
|
1550
|
+
prompt: unknown[];
|
|
1551
|
+
targetMessageId: string;
|
|
1552
|
+
replaceQueue?: boolean | undefined;
|
|
1553
|
+
onTargetCompleted?: "reject" | "send_anyway" | undefined;
|
|
1554
|
+
}, {
|
|
1555
|
+
sessionId: string;
|
|
1556
|
+
prompt: unknown[];
|
|
1557
|
+
targetMessageId: string;
|
|
1558
|
+
replaceQueue?: boolean | undefined;
|
|
1559
|
+
onTargetCompleted?: "reject" | "send_anyway" | undefined;
|
|
1560
|
+
}>;
|
|
1561
|
+
type AmendPromptParams = z.infer<typeof AmendPromptParams>;
|
|
1562
|
+
declare const AmendPromptResult: z.ZodObject<{
|
|
1563
|
+
amended: z.ZodBoolean;
|
|
1564
|
+
reason: z.ZodEnum<["ok", "target_completed", "target_cancelled", "target_not_found"]>;
|
|
1565
|
+
messageId: z.ZodOptional<z.ZodString>;
|
|
1566
|
+
}, "strip", z.ZodTypeAny, {
|
|
1567
|
+
reason: "ok" | "target_completed" | "target_cancelled" | "target_not_found";
|
|
1568
|
+
amended: boolean;
|
|
1569
|
+
messageId?: string | undefined;
|
|
1570
|
+
}, {
|
|
1571
|
+
reason: "ok" | "target_completed" | "target_cancelled" | "target_not_found";
|
|
1572
|
+
amended: boolean;
|
|
1573
|
+
messageId?: string | undefined;
|
|
1574
|
+
}>;
|
|
1575
|
+
type AmendPromptResult = z.infer<typeof AmendPromptResult>;
|
|
1537
1576
|
interface SessionCapabilities {
|
|
1538
1577
|
attach?: Record<string, never>;
|
|
1539
1578
|
list?: boolean;
|
|
@@ -1783,6 +1822,8 @@ declare class Session {
|
|
|
1783
1822
|
private modelHandlers;
|
|
1784
1823
|
private modeHandlers;
|
|
1785
1824
|
private usageHandlers;
|
|
1825
|
+
private amendInProgress;
|
|
1826
|
+
private recentlyTerminal;
|
|
1786
1827
|
constructor(init: SessionInit);
|
|
1787
1828
|
private broadcastMergedCommands;
|
|
1788
1829
|
private broadcastAvailableModes;
|
|
@@ -1814,6 +1855,9 @@ declare class Session {
|
|
|
1814
1855
|
prompt(clientId: string, params: unknown): Promise<unknown>;
|
|
1815
1856
|
private broadcastPromptReceived;
|
|
1816
1857
|
private broadcastTurnComplete;
|
|
1858
|
+
private recordTerminal;
|
|
1859
|
+
private broadcastPromptAmended;
|
|
1860
|
+
private findUserEntry;
|
|
1817
1861
|
private visibleQueueDepth;
|
|
1818
1862
|
private broadcastQueueAdded;
|
|
1819
1863
|
private broadcastQueueUpdated;
|
|
@@ -1824,6 +1868,9 @@ declare class Session {
|
|
|
1824
1868
|
replayPersistedQueue(entries: PersistedQueueEntry[]): void;
|
|
1825
1869
|
cancelQueuedPrompt(messageId: string): CancelPromptResult;
|
|
1826
1870
|
updateQueuedPrompt(messageId: string, prompt: unknown[]): UpdatePromptResult;
|
|
1871
|
+
amendPrompt(clientId: string, params: AmendPromptParams): AmendPromptResult;
|
|
1872
|
+
private amendOnHead;
|
|
1873
|
+
private enqueueAmendmentAsFollowUp;
|
|
1827
1874
|
cancel(clientId: string): Promise<void>;
|
|
1828
1875
|
forwardRequest(method: string, params: unknown): Promise<unknown>;
|
|
1829
1876
|
private rewriteForAgent;
|
|
@@ -1872,6 +1919,7 @@ declare class Session {
|
|
|
1872
1919
|
private persistedFromEntry;
|
|
1873
1920
|
private drainQueue;
|
|
1874
1921
|
private runQueueEntry;
|
|
1922
|
+
private clearAmendIfMatches;
|
|
1875
1923
|
}
|
|
1876
1924
|
|
|
1877
1925
|
declare const SessionRecord: z.ZodObject<{
|
package/dist/index.js
CHANGED
|
@@ -186,7 +186,15 @@ var TuiConfig = z.object({
|
|
|
186
186
|
// on Windows Terminal, dock badge on KDE/Konsole, etc.) while a turn is
|
|
187
187
|
// running. Set false if your terminal renders this obnoxiously or you
|
|
188
188
|
// just don't want it.
|
|
189
|
-
progressIndicator: z.boolean().default(true)
|
|
189
|
+
progressIndicator: z.boolean().default(true),
|
|
190
|
+
// What the unmodified Enter key does in the prompt composer.
|
|
191
|
+
// "enqueue" (default) — Enter enqueues the prompt (sends immediately
|
|
192
|
+
// when idle, queues behind an in-flight turn); Shift+Enter amends
|
|
193
|
+
// the in-flight turn.
|
|
194
|
+
// "amend" — flips the two: Enter amends the in-flight turn,
|
|
195
|
+
// Shift+Enter enqueues. With no turn in flight either key just
|
|
196
|
+
// enqueues, since there's nothing to amend.
|
|
197
|
+
defaultEnterAction: z.enum(["enqueue", "amend"]).default("enqueue")
|
|
190
198
|
});
|
|
191
199
|
var ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
192
200
|
var ExtensionBody = z.object({
|
|
@@ -230,7 +238,8 @@ var HydraConfig = z.object({
|
|
|
230
238
|
mouse: true,
|
|
231
239
|
logMaxBytes: 5 * 1024 * 1024,
|
|
232
240
|
cwdColumnMaxWidth: 24,
|
|
233
|
-
progressIndicator: true
|
|
241
|
+
progressIndicator: true,
|
|
242
|
+
defaultEnterAction: "enqueue"
|
|
234
243
|
})
|
|
235
244
|
});
|
|
236
245
|
function extensionList(config) {
|
|
@@ -1024,6 +1033,18 @@ function extractHydraMeta(meta) {
|
|
|
1024
1033
|
if (typeof obj.promptQueueing === "boolean") {
|
|
1025
1034
|
out.promptQueueing = obj.promptQueueing;
|
|
1026
1035
|
}
|
|
1036
|
+
if (typeof obj.promptCancelling === "boolean") {
|
|
1037
|
+
out.promptCancelling = obj.promptCancelling;
|
|
1038
|
+
}
|
|
1039
|
+
if (typeof obj.promptUpdating === "boolean") {
|
|
1040
|
+
out.promptUpdating = obj.promptUpdating;
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof obj.promptAmending === "boolean") {
|
|
1043
|
+
out.promptAmending = obj.promptAmending;
|
|
1044
|
+
}
|
|
1045
|
+
if (typeof obj.promptPipelining === "boolean") {
|
|
1046
|
+
out.promptPipelining = obj.promptPipelining;
|
|
1047
|
+
}
|
|
1027
1048
|
if (Array.isArray(obj.queue)) {
|
|
1028
1049
|
const entries = [];
|
|
1029
1050
|
for (const raw of obj.queue) {
|
|
@@ -1168,6 +1189,34 @@ var UpdatePromptResult = z3.object({
|
|
|
1168
1189
|
updated: z3.boolean(),
|
|
1169
1190
|
reason: z3.enum(["ok", "not_found", "already_running"])
|
|
1170
1191
|
});
|
|
1192
|
+
var AmendPromptParams = z3.object({
|
|
1193
|
+
sessionId: z3.string(),
|
|
1194
|
+
targetMessageId: z3.string(),
|
|
1195
|
+
prompt: z3.array(z3.unknown()),
|
|
1196
|
+
replaceQueue: z3.boolean().optional(),
|
|
1197
|
+
onTargetCompleted: z3.enum(["reject", "send_anyway"]).optional()
|
|
1198
|
+
});
|
|
1199
|
+
var AmendPromptResult = z3.object({
|
|
1200
|
+
amended: z3.boolean(),
|
|
1201
|
+
reason: z3.enum([
|
|
1202
|
+
"ok",
|
|
1203
|
+
"target_completed",
|
|
1204
|
+
"target_cancelled",
|
|
1205
|
+
"target_not_found"
|
|
1206
|
+
]),
|
|
1207
|
+
// Present when a prompt was sent or replaced: the amendment's id on
|
|
1208
|
+
// success, or the regular follow-up's id when onTargetCompleted is
|
|
1209
|
+
// "send_anyway" and the daemon forwarded the prompt anyway.
|
|
1210
|
+
messageId: z3.string().optional()
|
|
1211
|
+
});
|
|
1212
|
+
var PromptAmendedParams = z3.object({
|
|
1213
|
+
sessionId: z3.string(),
|
|
1214
|
+
cancelledMessageId: z3.string(),
|
|
1215
|
+
newMessageId: z3.string(),
|
|
1216
|
+
prompt: z3.array(z3.unknown()),
|
|
1217
|
+
originator: PromptOriginatorSchema,
|
|
1218
|
+
amendedAt: z3.number()
|
|
1219
|
+
});
|
|
1171
1220
|
var ProxyInitializeParams = z3.object({
|
|
1172
1221
|
protocolVersion: z3.number().optional(),
|
|
1173
1222
|
proxyInfo: z3.object({
|
|
@@ -1646,6 +1695,7 @@ function stripHydraSessionPrefix(id) {
|
|
|
1646
1695
|
return id.startsWith(HYDRA_SESSION_PREFIX) ? id.slice(HYDRA_SESSION_PREFIX.length) : id;
|
|
1647
1696
|
}
|
|
1648
1697
|
var DEFAULT_HISTORY_MAX_ENTRIES = 1e3;
|
|
1698
|
+
var RECENTLY_TERMINAL_LIMIT = 64;
|
|
1649
1699
|
var Session = class {
|
|
1650
1700
|
sessionId;
|
|
1651
1701
|
cwd;
|
|
@@ -1745,6 +1795,20 @@ var Session = class {
|
|
|
1745
1795
|
modelHandlers = [];
|
|
1746
1796
|
modeHandlers = [];
|
|
1747
1797
|
usageHandlers = [];
|
|
1798
|
+
// Set by amendPrompt at the start of a cancel-and-resubmit dance.
|
|
1799
|
+
// broadcastTurnComplete reads it to attach the _meta.amended marker
|
|
1800
|
+
// to the cancelled turn's turn_complete notification, and to fire the
|
|
1801
|
+
// dedicated prompt_amended notification. Cleared when the cancelled
|
|
1802
|
+
// turn's task completes (runQueueEntry) OR if the amendment is
|
|
1803
|
+
// cancelled mid-window via cancel_prompt(M2) before drainQueue picks
|
|
1804
|
+
// it up.
|
|
1805
|
+
amendInProgress;
|
|
1806
|
+
// LRU of recently-terminal messageIds → stopReason. Used by
|
|
1807
|
+
// amendPrompt to resolve targets that completed/cancelled before
|
|
1808
|
+
// the amend arrived. Capped at RECENTLY_TERMINAL_LIMIT entries;
|
|
1809
|
+
// older entries fall out and resolve to target_not_found, which is
|
|
1810
|
+
// the correct behavior.
|
|
1811
|
+
recentlyTerminal = /* @__PURE__ */ new Map();
|
|
1748
1812
|
constructor(init) {
|
|
1749
1813
|
this.sessionId = init.sessionId ?? `${HYDRA_SESSION_PREFIX}${generateHydraId()}`;
|
|
1750
1814
|
this.cwd = init.cwd;
|
|
@@ -2174,7 +2238,7 @@ var Session = class {
|
|
|
2174
2238
|
);
|
|
2175
2239
|
}
|
|
2176
2240
|
}
|
|
2177
|
-
broadcastTurnComplete(originatorClientId, response) {
|
|
2241
|
+
broadcastTurnComplete(originatorClientId, response, promptMessageId, wasAmend) {
|
|
2178
2242
|
const stopReason = response && typeof response === "object" && "stopReason" in response && typeof response.stopReason === "string" ? response.stopReason : void 0;
|
|
2179
2243
|
const update = {
|
|
2180
2244
|
sessionUpdate: "turn_complete",
|
|
@@ -2183,15 +2247,83 @@ var Session = class {
|
|
|
2183
2247
|
if (stopReason !== void 0) {
|
|
2184
2248
|
update.stopReason = stopReason;
|
|
2185
2249
|
}
|
|
2250
|
+
const amend = this.amendInProgress;
|
|
2251
|
+
if (amend && promptMessageId !== void 0 && amend.cancelledMessageId === promptMessageId) {
|
|
2252
|
+
update._meta = {
|
|
2253
|
+
"hydra-acp": {
|
|
2254
|
+
amended: {
|
|
2255
|
+
cancelledMessageId: amend.cancelledMessageId,
|
|
2256
|
+
newMessageId: amend.newMessageId
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2186
2261
|
this.promptStartedAt = void 0;
|
|
2262
|
+
if (promptMessageId !== void 0 && stopReason !== void 0) {
|
|
2263
|
+
this.recordTerminal(promptMessageId, stopReason);
|
|
2264
|
+
}
|
|
2187
2265
|
this.recordAndBroadcast(
|
|
2188
2266
|
"session/update",
|
|
2189
2267
|
{
|
|
2190
2268
|
sessionId: this.sessionId,
|
|
2191
2269
|
update
|
|
2192
2270
|
},
|
|
2193
|
-
originatorClientId
|
|
2271
|
+
wasAmend ? void 0 : originatorClientId
|
|
2194
2272
|
);
|
|
2273
|
+
if (amend && promptMessageId !== void 0 && amend.cancelledMessageId === promptMessageId) {
|
|
2274
|
+
this.broadcastPromptAmended(amend);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
// Record that a prompt's turn has ended, with its terminal stopReason.
|
|
2278
|
+
// Used by amendPrompt to resolve targetMessageIds that completed/cancelled
|
|
2279
|
+
// before the amend arrived. LRU-trimmed at RECENTLY_TERMINAL_LIMIT.
|
|
2280
|
+
recordTerminal(messageId, stopReason) {
|
|
2281
|
+
this.recentlyTerminal.set(messageId, {
|
|
2282
|
+
stopReason,
|
|
2283
|
+
terminatedAt: Date.now()
|
|
2284
|
+
});
|
|
2285
|
+
while (this.recentlyTerminal.size > RECENTLY_TERMINAL_LIMIT) {
|
|
2286
|
+
const oldest = this.recentlyTerminal.keys().next().value;
|
|
2287
|
+
if (oldest === void 0) {
|
|
2288
|
+
break;
|
|
2289
|
+
}
|
|
2290
|
+
this.recentlyTerminal.delete(oldest);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
// Fire hydra-acp/prompt_amended for the M1→M2 linkage. The amendment's
|
|
2294
|
+
// current content is read live from the queue entry so any update_prompt
|
|
2295
|
+
// calls during the amend window are reflected. Best-effort: if M2 has
|
|
2296
|
+
// already been cancelled out of the queue by the time we get here, we
|
|
2297
|
+
// skip — the amendInProgress clearing in cancelQueuedPrompt should have
|
|
2298
|
+
// prevented this code path from running in that case.
|
|
2299
|
+
broadcastPromptAmended(amend) {
|
|
2300
|
+
const entry = this.findUserEntry(amend.newMessageId);
|
|
2301
|
+
if (!entry) {
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
const params = {
|
|
2305
|
+
sessionId: this.sessionId,
|
|
2306
|
+
cancelledMessageId: amend.cancelledMessageId,
|
|
2307
|
+
newMessageId: amend.newMessageId,
|
|
2308
|
+
prompt: entry.prompt,
|
|
2309
|
+
originator: entry.originator,
|
|
2310
|
+
amendedAt: Date.now()
|
|
2311
|
+
};
|
|
2312
|
+
this.broadcastQueueNotification(
|
|
2313
|
+
"hydra-acp/prompt_amended",
|
|
2314
|
+
params
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
// Look up a user-prompt queue entry by messageId, searching both the
|
|
2318
|
+
// currentEntry slot and the waiting queue.
|
|
2319
|
+
findUserEntry(messageId) {
|
|
2320
|
+
if (this.currentEntry?.messageId === messageId && this.currentEntry.kind === "user") {
|
|
2321
|
+
return this.currentEntry;
|
|
2322
|
+
}
|
|
2323
|
+
const queued = this.promptQueue.find(
|
|
2324
|
+
(e) => e.messageId === messageId && e.kind === "user"
|
|
2325
|
+
);
|
|
2326
|
+
return queued?.kind === "user" ? queued : void 0;
|
|
2195
2327
|
}
|
|
2196
2328
|
// Total visible-or-running entries: the in-flight head (if any) plus
|
|
2197
2329
|
// the queue's user-visible waiting entries. Internal entries don't
|
|
@@ -2204,9 +2336,9 @@ var Session = class {
|
|
|
2204
2336
|
}
|
|
2205
2337
|
return count;
|
|
2206
2338
|
}
|
|
2207
|
-
broadcastQueueAdded(entry) {
|
|
2339
|
+
broadcastQueueAdded(entry, options) {
|
|
2208
2340
|
const depth = this.visibleQueueDepth();
|
|
2209
|
-
const position = Math.max(0, depth - 1);
|
|
2341
|
+
const position = options?.position ?? Math.max(0, depth - 1);
|
|
2210
2342
|
const params = {
|
|
2211
2343
|
sessionId: this.sessionId,
|
|
2212
2344
|
messageId: entry.messageId,
|
|
@@ -2216,6 +2348,11 @@ var Session = class {
|
|
|
2216
2348
|
queueDepth: depth,
|
|
2217
2349
|
enqueuedAt: entry.enqueuedAt
|
|
2218
2350
|
};
|
|
2351
|
+
if (options?.amending !== void 0) {
|
|
2352
|
+
params._meta = {
|
|
2353
|
+
"hydra-acp": { amending: options.amending }
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2219
2356
|
this.broadcastQueueNotification("hydra-acp/prompt_queue_added", params);
|
|
2220
2357
|
}
|
|
2221
2358
|
broadcastQueueUpdated(messageId, prompt) {
|
|
@@ -2338,6 +2475,9 @@ var Session = class {
|
|
|
2338
2475
|
this.broadcastQueueRemoved(messageId, "cancelled");
|
|
2339
2476
|
this.persistRewrite();
|
|
2340
2477
|
}
|
|
2478
|
+
if (this.amendInProgress?.newMessageId === messageId) {
|
|
2479
|
+
this.amendInProgress = void 0;
|
|
2480
|
+
}
|
|
2341
2481
|
entry.resolve({ stopReason: "cancelled" });
|
|
2342
2482
|
return { cancelled: true, reason: "ok" };
|
|
2343
2483
|
}
|
|
@@ -2359,6 +2499,143 @@ var Session = class {
|
|
|
2359
2499
|
this.persistRewrite();
|
|
2360
2500
|
return { updated: true, reason: "ok" };
|
|
2361
2501
|
}
|
|
2502
|
+
// Amend the head prompt: cancel the in-flight turn and submit a
|
|
2503
|
+
// replacement that sits at the head of the queue. Resolves the
|
|
2504
|
+
// request immediately (the caller doesn't wait on cancel-settle).
|
|
2505
|
+
// Honours race outcomes — if the target finished or was cancelled
|
|
2506
|
+
// before this arrived, the request resolves with an outcome explaining
|
|
2507
|
+
// why and (depending on onTargetCompleted) optionally forwards as a
|
|
2508
|
+
// plain prompt. Queued targets are edited in place (same machinery
|
|
2509
|
+
// as updateQueuedPrompt).
|
|
2510
|
+
amendPrompt(clientId, params) {
|
|
2511
|
+
const client = this.clients.get(clientId);
|
|
2512
|
+
if (!client) {
|
|
2513
|
+
throw withCode(
|
|
2514
|
+
new Error("client not attached"),
|
|
2515
|
+
JsonRpcErrorCodes.SessionNotFound
|
|
2516
|
+
);
|
|
2517
|
+
}
|
|
2518
|
+
const { targetMessageId, prompt, replaceQueue, onTargetCompleted } = params;
|
|
2519
|
+
if (this.currentEntry?.messageId === targetMessageId && this.currentEntry.kind === "user" && !this.currentEntry.cancelled && this.amendInProgress === void 0) {
|
|
2520
|
+
return this.amendOnHead(client, prompt, targetMessageId, replaceQueue);
|
|
2521
|
+
}
|
|
2522
|
+
const queuedEntry = this.promptQueue.find(
|
|
2523
|
+
(e) => e.messageId === targetMessageId && e.kind === "user"
|
|
2524
|
+
);
|
|
2525
|
+
if (queuedEntry && queuedEntry.kind === "user" && !queuedEntry.cancelled) {
|
|
2526
|
+
queuedEntry.prompt = prompt;
|
|
2527
|
+
this.broadcastQueueUpdated(targetMessageId, prompt);
|
|
2528
|
+
this.persistRewrite();
|
|
2529
|
+
return { amended: true, reason: "ok", messageId: targetMessageId };
|
|
2530
|
+
}
|
|
2531
|
+
const terminal = this.recentlyTerminal.get(targetMessageId);
|
|
2532
|
+
if (terminal) {
|
|
2533
|
+
if (terminal.stopReason === "cancelled") {
|
|
2534
|
+
return { amended: false, reason: "target_cancelled" };
|
|
2535
|
+
}
|
|
2536
|
+
if (onTargetCompleted === "send_anyway") {
|
|
2537
|
+
const newMessageId = this.enqueueAmendmentAsFollowUp(client, prompt);
|
|
2538
|
+
return {
|
|
2539
|
+
amended: false,
|
|
2540
|
+
reason: "target_completed",
|
|
2541
|
+
messageId: newMessageId
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
return { amended: false, reason: "target_completed" };
|
|
2545
|
+
}
|
|
2546
|
+
return { amended: false, reason: "target_not_found" };
|
|
2547
|
+
}
|
|
2548
|
+
// Head-of-queue amendment: splice M2 in front of any waiting entries,
|
|
2549
|
+
// broadcast the amend window's queue_added with the amending hint,
|
|
2550
|
+
// mark amendInProgress so the cancelled turn's broadcastTurnComplete
|
|
2551
|
+
// attaches the _meta marker and fires prompt_amended, then fire the
|
|
2552
|
+
// upstream session/cancel without awaiting it. drainQueue is already
|
|
2553
|
+
// running on the head; when its session/prompt returns, it advances
|
|
2554
|
+
// to M2 in the normal way.
|
|
2555
|
+
amendOnHead(client, prompt, targetMessageId, replaceQueue) {
|
|
2556
|
+
const newMessageId = generateMessageId();
|
|
2557
|
+
const originator = { clientId: client.clientId };
|
|
2558
|
+
if (client.clientInfo?.name) {
|
|
2559
|
+
originator.name = client.clientInfo.name;
|
|
2560
|
+
}
|
|
2561
|
+
if (client.clientInfo?.version) {
|
|
2562
|
+
originator.version = client.clientInfo.version;
|
|
2563
|
+
}
|
|
2564
|
+
if (replaceQueue) {
|
|
2565
|
+
const survivors = [];
|
|
2566
|
+
for (const entry2 of this.promptQueue) {
|
|
2567
|
+
if (entry2.kind === "user" && !entry2.cancelled) {
|
|
2568
|
+
entry2.cancelled = true;
|
|
2569
|
+
this.broadcastQueueRemoved(entry2.messageId, "cancelled");
|
|
2570
|
+
entry2.resolve({ stopReason: "cancelled" });
|
|
2571
|
+
continue;
|
|
2572
|
+
}
|
|
2573
|
+
survivors.push(entry2);
|
|
2574
|
+
}
|
|
2575
|
+
this.promptQueue = survivors;
|
|
2576
|
+
}
|
|
2577
|
+
const entry = {
|
|
2578
|
+
kind: "user",
|
|
2579
|
+
messageId: newMessageId,
|
|
2580
|
+
originator,
|
|
2581
|
+
clientId: client.clientId,
|
|
2582
|
+
prompt,
|
|
2583
|
+
enqueuedAt: Date.now(),
|
|
2584
|
+
cancelled: false,
|
|
2585
|
+
wasAmend: true,
|
|
2586
|
+
// No-op resolve/reject: there's no client request awaiting M2's
|
|
2587
|
+
// session/prompt response. The amend_prompt request has already
|
|
2588
|
+
// returned by this point. drainQueue calls these unconditionally
|
|
2589
|
+
// when runQueueEntry settles; making them no-ops is safe.
|
|
2590
|
+
resolve: () => void 0,
|
|
2591
|
+
reject: () => void 0
|
|
2592
|
+
};
|
|
2593
|
+
this.promptQueue.unshift(entry);
|
|
2594
|
+
this.persistRewrite();
|
|
2595
|
+
this.broadcastQueueAdded(entry, {
|
|
2596
|
+
amending: targetMessageId,
|
|
2597
|
+
position: 1
|
|
2598
|
+
});
|
|
2599
|
+
this.amendInProgress = {
|
|
2600
|
+
cancelledMessageId: targetMessageId,
|
|
2601
|
+
newMessageId
|
|
2602
|
+
};
|
|
2603
|
+
void this.agent.connection.notify("session/cancel", { sessionId: this.upstreamSessionId }).catch(() => void 0);
|
|
2604
|
+
return {
|
|
2605
|
+
amended: true,
|
|
2606
|
+
reason: "ok",
|
|
2607
|
+
messageId: newMessageId
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
// Send the amendment as a plain follow-up prompt — used when the
|
|
2611
|
+
// target already completed and the caller opted in to send_anyway.
|
|
2612
|
+
// Returns the new prompt's messageId so the result can surface it.
|
|
2613
|
+
enqueueAmendmentAsFollowUp(client, prompt) {
|
|
2614
|
+
const messageId = generateMessageId();
|
|
2615
|
+
const originator = { clientId: client.clientId };
|
|
2616
|
+
if (client.clientInfo?.name) {
|
|
2617
|
+
originator.name = client.clientInfo.name;
|
|
2618
|
+
}
|
|
2619
|
+
if (client.clientInfo?.version) {
|
|
2620
|
+
originator.version = client.clientInfo.version;
|
|
2621
|
+
}
|
|
2622
|
+
const entry = {
|
|
2623
|
+
kind: "user",
|
|
2624
|
+
messageId,
|
|
2625
|
+
originator,
|
|
2626
|
+
clientId: client.clientId,
|
|
2627
|
+
prompt,
|
|
2628
|
+
enqueuedAt: Date.now(),
|
|
2629
|
+
cancelled: false,
|
|
2630
|
+
resolve: () => void 0,
|
|
2631
|
+
reject: () => void 0
|
|
2632
|
+
};
|
|
2633
|
+
this.promptQueue.push(entry);
|
|
2634
|
+
this.persistRewrite();
|
|
2635
|
+
this.broadcastQueueAdded(entry);
|
|
2636
|
+
void this.drainQueue();
|
|
2637
|
+
return messageId;
|
|
2638
|
+
}
|
|
2362
2639
|
async cancel(clientId) {
|
|
2363
2640
|
const client = this.clients.get(clientId);
|
|
2364
2641
|
if (!client) {
|
|
@@ -3217,6 +3494,7 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
3217
3494
|
try {
|
|
3218
3495
|
const result = await this.runQueueEntry(next);
|
|
3219
3496
|
next.resolve(result);
|
|
3497
|
+
await Promise.resolve();
|
|
3220
3498
|
} catch (err) {
|
|
3221
3499
|
next.reject(err);
|
|
3222
3500
|
} finally {
|
|
@@ -3253,12 +3531,33 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
3253
3531
|
}
|
|
3254
3532
|
);
|
|
3255
3533
|
} catch (err) {
|
|
3256
|
-
this.broadcastTurnComplete(
|
|
3534
|
+
this.broadcastTurnComplete(
|
|
3535
|
+
entry.clientId,
|
|
3536
|
+
{ stopReason: "error" },
|
|
3537
|
+
entry.messageId,
|
|
3538
|
+
entry.wasAmend
|
|
3539
|
+
);
|
|
3540
|
+
this.clearAmendIfMatches(entry.messageId);
|
|
3257
3541
|
throw err;
|
|
3258
3542
|
}
|
|
3259
|
-
this.broadcastTurnComplete(
|
|
3543
|
+
this.broadcastTurnComplete(
|
|
3544
|
+
entry.clientId,
|
|
3545
|
+
response,
|
|
3546
|
+
entry.messageId,
|
|
3547
|
+
entry.wasAmend
|
|
3548
|
+
);
|
|
3549
|
+
this.clearAmendIfMatches(entry.messageId);
|
|
3260
3550
|
return response;
|
|
3261
3551
|
}
|
|
3552
|
+
// Clear amendInProgress once the cancelled turn's task has fully
|
|
3553
|
+
// settled. broadcastTurnComplete needs the marker still set when it
|
|
3554
|
+
// fires, so the clear must happen *after*. Called from runQueueEntry's
|
|
3555
|
+
// settle path for both success and error.
|
|
3556
|
+
clearAmendIfMatches(messageId) {
|
|
3557
|
+
if (this.amendInProgress?.cancelledMessageId === messageId) {
|
|
3558
|
+
this.amendInProgress = void 0;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3262
3561
|
};
|
|
3263
3562
|
function withCode(err, code) {
|
|
3264
3563
|
err.code = code;
|
|
@@ -5992,7 +6291,16 @@ function mapModel(u) {
|
|
|
5992
6291
|
}
|
|
5993
6292
|
function mapTurnComplete(u) {
|
|
5994
6293
|
const stopReason = readString(u, "stopReason");
|
|
5995
|
-
|
|
6294
|
+
const meta = u._meta;
|
|
6295
|
+
const amended = meta?.["hydra-acp"]?.amended !== void 0 && meta["hydra-acp"].amended !== null;
|
|
6296
|
+
const out = { kind: "turn-complete" };
|
|
6297
|
+
if (stopReason !== void 0) {
|
|
6298
|
+
out.stopReason = stopReason;
|
|
6299
|
+
}
|
|
6300
|
+
if (amended) {
|
|
6301
|
+
out.amended = true;
|
|
6302
|
+
}
|
|
6303
|
+
return out;
|
|
5996
6304
|
}
|
|
5997
6305
|
function extractContentText(content) {
|
|
5998
6306
|
if (typeof content === "string") {
|
|
@@ -7111,6 +7419,22 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
7111
7419
|
}
|
|
7112
7420
|
return session.updateQueuedPrompt(params.messageId, params.prompt);
|
|
7113
7421
|
});
|
|
7422
|
+
connection.onRequest("hydra-acp/amend_prompt", async (raw) => {
|
|
7423
|
+
const params = AmendPromptParams.parse(raw);
|
|
7424
|
+
const att = state.attached.get(params.sessionId);
|
|
7425
|
+
if (!att) {
|
|
7426
|
+
const err = new Error("not attached to session");
|
|
7427
|
+
err.code = JsonRpcErrorCodes.SessionNotFound;
|
|
7428
|
+
throw err;
|
|
7429
|
+
}
|
|
7430
|
+
const session = deps.manager.get(params.sessionId);
|
|
7431
|
+
if (!session) {
|
|
7432
|
+
const err = new Error(`session ${params.sessionId} not found`);
|
|
7433
|
+
err.code = JsonRpcErrorCodes.SessionNotFound;
|
|
7434
|
+
throw err;
|
|
7435
|
+
}
|
|
7436
|
+
return session.amendPrompt(att.clientId, params);
|
|
7437
|
+
});
|
|
7114
7438
|
connection.onRequest("session/load", async (raw) => {
|
|
7115
7439
|
const rawObj = raw ?? {};
|
|
7116
7440
|
const rawSessionId = typeof rawObj.sessionId === "string" ? rawObj.sessionId : void 0;
|
|
@@ -7263,10 +7587,17 @@ function buildInitializeResult() {
|
|
|
7263
7587
|
],
|
|
7264
7588
|
// Advertise hydra-only capabilities via _meta["hydra-acp"]. Generic
|
|
7265
7589
|
// ACP clients ignore the field; capability-aware clients learn here
|
|
7266
|
-
//
|
|
7267
|
-
//
|
|
7268
|
-
//
|
|
7269
|
-
|
|
7590
|
+
// which hydra-acp extensions the daemon supports so they can gate
|
|
7591
|
+
// UI surface accordingly. promptPipelining is false until the
|
|
7592
|
+
// streaming-input probe lands (Option A in the steering brief);
|
|
7593
|
+
// the others are unconditional method-availability flags.
|
|
7594
|
+
_meta: mergeMeta(void 0, {
|
|
7595
|
+
promptQueueing: true,
|
|
7596
|
+
promptCancelling: true,
|
|
7597
|
+
promptUpdating: true,
|
|
7598
|
+
promptAmending: true,
|
|
7599
|
+
promptPipelining: false
|
|
7600
|
+
})
|
|
7270
7601
|
};
|
|
7271
7602
|
}
|
|
7272
7603
|
function bindClientToSession(connection, session, state, clientInfo, callerClientId) {
|