@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/cli.js
CHANGED
|
@@ -289,7 +289,15 @@ var init_config = __esm({
|
|
|
289
289
|
// on Windows Terminal, dock badge on KDE/Konsole, etc.) while a turn is
|
|
290
290
|
// running. Set false if your terminal renders this obnoxiously or you
|
|
291
291
|
// just don't want it.
|
|
292
|
-
progressIndicator: z.boolean().default(true)
|
|
292
|
+
progressIndicator: z.boolean().default(true),
|
|
293
|
+
// What the unmodified Enter key does in the prompt composer.
|
|
294
|
+
// "enqueue" (default) — Enter enqueues the prompt (sends immediately
|
|
295
|
+
// when idle, queues behind an in-flight turn); Shift+Enter amends
|
|
296
|
+
// the in-flight turn.
|
|
297
|
+
// "amend" — flips the two: Enter amends the in-flight turn,
|
|
298
|
+
// Shift+Enter enqueues. With no turn in flight either key just
|
|
299
|
+
// enqueues, since there's nothing to amend.
|
|
300
|
+
defaultEnterAction: z.enum(["enqueue", "amend"]).default("enqueue")
|
|
293
301
|
});
|
|
294
302
|
ExtensionName = z.string().min(1).regex(/^[A-Za-z0-9._-]+$/, "extension name must be filename-safe");
|
|
295
303
|
ExtensionBody = z.object({
|
|
@@ -333,7 +341,8 @@ var init_config = __esm({
|
|
|
333
341
|
mouse: true,
|
|
334
342
|
logMaxBytes: 5 * 1024 * 1024,
|
|
335
343
|
cwdColumnMaxWidth: 24,
|
|
336
|
-
progressIndicator: true
|
|
344
|
+
progressIndicator: true,
|
|
345
|
+
defaultEnterAction: "enqueue"
|
|
337
346
|
})
|
|
338
347
|
});
|
|
339
348
|
}
|
|
@@ -413,6 +422,18 @@ function extractHydraMeta(meta) {
|
|
|
413
422
|
if (typeof obj.promptQueueing === "boolean") {
|
|
414
423
|
out.promptQueueing = obj.promptQueueing;
|
|
415
424
|
}
|
|
425
|
+
if (typeof obj.promptCancelling === "boolean") {
|
|
426
|
+
out.promptCancelling = obj.promptCancelling;
|
|
427
|
+
}
|
|
428
|
+
if (typeof obj.promptUpdating === "boolean") {
|
|
429
|
+
out.promptUpdating = obj.promptUpdating;
|
|
430
|
+
}
|
|
431
|
+
if (typeof obj.promptAmending === "boolean") {
|
|
432
|
+
out.promptAmending = obj.promptAmending;
|
|
433
|
+
}
|
|
434
|
+
if (typeof obj.promptPipelining === "boolean") {
|
|
435
|
+
out.promptPipelining = obj.promptPipelining;
|
|
436
|
+
}
|
|
416
437
|
if (Array.isArray(obj.queue)) {
|
|
417
438
|
const entries = [];
|
|
418
439
|
for (const raw of obj.queue) {
|
|
@@ -467,7 +488,7 @@ function extractHydraMeta(meta) {
|
|
|
467
488
|
function mergeMeta(passthrough, ours) {
|
|
468
489
|
return { ...passthrough ?? {}, [HYDRA_META_KEY]: ours };
|
|
469
490
|
}
|
|
470
|
-
var ACP_PROTOCOL_VERSION, JsonRpcErrorCodes, InitializeParams, HistoryPolicy, SessionNewParams, SessionResumeHints, SessionAttachParams, HYDRA_META_KEY, SessionDetachParams, SessionListParams, SessionListUsage, SessionListEntry, SessionListResult, SessionPromptParams, SessionCancelParams, PromptOriginatorSchema, PromptQueueAddedParams, PromptQueueUpdatedParams, PromptQueueRemovedParams, CancelPromptParams, CancelPromptResult, UpdatePromptParams, UpdatePromptResult, ProxyInitializeParams;
|
|
491
|
+
var ACP_PROTOCOL_VERSION, JsonRpcErrorCodes, InitializeParams, HistoryPolicy, SessionNewParams, SessionResumeHints, SessionAttachParams, HYDRA_META_KEY, SessionDetachParams, SessionListParams, SessionListUsage, SessionListEntry, SessionListResult, SessionPromptParams, SessionCancelParams, PromptOriginatorSchema, PromptQueueAddedParams, PromptQueueUpdatedParams, PromptQueueRemovedParams, CancelPromptParams, CancelPromptResult, UpdatePromptParams, UpdatePromptResult, AmendPromptParams, AmendPromptResult, PromptAmendedParams, ProxyInitializeParams;
|
|
471
492
|
var init_types = __esm({
|
|
472
493
|
"src/acp/types.ts"() {
|
|
473
494
|
"use strict";
|
|
@@ -633,6 +654,34 @@ var init_types = __esm({
|
|
|
633
654
|
updated: z3.boolean(),
|
|
634
655
|
reason: z3.enum(["ok", "not_found", "already_running"])
|
|
635
656
|
});
|
|
657
|
+
AmendPromptParams = z3.object({
|
|
658
|
+
sessionId: z3.string(),
|
|
659
|
+
targetMessageId: z3.string(),
|
|
660
|
+
prompt: z3.array(z3.unknown()),
|
|
661
|
+
replaceQueue: z3.boolean().optional(),
|
|
662
|
+
onTargetCompleted: z3.enum(["reject", "send_anyway"]).optional()
|
|
663
|
+
});
|
|
664
|
+
AmendPromptResult = z3.object({
|
|
665
|
+
amended: z3.boolean(),
|
|
666
|
+
reason: z3.enum([
|
|
667
|
+
"ok",
|
|
668
|
+
"target_completed",
|
|
669
|
+
"target_cancelled",
|
|
670
|
+
"target_not_found"
|
|
671
|
+
]),
|
|
672
|
+
// Present when a prompt was sent or replaced: the amendment's id on
|
|
673
|
+
// success, or the regular follow-up's id when onTargetCompleted is
|
|
674
|
+
// "send_anyway" and the daemon forwarded the prompt anyway.
|
|
675
|
+
messageId: z3.string().optional()
|
|
676
|
+
});
|
|
677
|
+
PromptAmendedParams = z3.object({
|
|
678
|
+
sessionId: z3.string(),
|
|
679
|
+
cancelledMessageId: z3.string(),
|
|
680
|
+
newMessageId: z3.string(),
|
|
681
|
+
prompt: z3.array(z3.unknown()),
|
|
682
|
+
originator: PromptOriginatorSchema,
|
|
683
|
+
amendedAt: z3.number()
|
|
684
|
+
});
|
|
636
685
|
ProxyInitializeParams = z3.object({
|
|
637
686
|
protocolVersion: z3.number().optional(),
|
|
638
687
|
proxyInfo: z3.object({
|
|
@@ -1154,7 +1203,7 @@ function firstLine(text, max) {
|
|
|
1154
1203
|
}
|
|
1155
1204
|
return void 0;
|
|
1156
1205
|
}
|
|
1157
|
-
var HYDRA_ID_ALPHABET, generateHydraId, HYDRA_SESSION_PREFIX, DEFAULT_HISTORY_MAX_ENTRIES, Session, STATE_UPDATE_KINDS;
|
|
1206
|
+
var HYDRA_ID_ALPHABET, generateHydraId, HYDRA_SESSION_PREFIX, DEFAULT_HISTORY_MAX_ENTRIES, RECENTLY_TERMINAL_LIMIT, Session, STATE_UPDATE_KINDS;
|
|
1158
1207
|
var init_session = __esm({
|
|
1159
1208
|
"src/core/session.ts"() {
|
|
1160
1209
|
"use strict";
|
|
@@ -1165,6 +1214,7 @@ var init_session = __esm({
|
|
|
1165
1214
|
generateHydraId = customAlphabet(HYDRA_ID_ALPHABET, 16);
|
|
1166
1215
|
HYDRA_SESSION_PREFIX = "hydra_session_";
|
|
1167
1216
|
DEFAULT_HISTORY_MAX_ENTRIES = 1e3;
|
|
1217
|
+
RECENTLY_TERMINAL_LIMIT = 64;
|
|
1168
1218
|
Session = class {
|
|
1169
1219
|
sessionId;
|
|
1170
1220
|
cwd;
|
|
@@ -1264,6 +1314,20 @@ var init_session = __esm({
|
|
|
1264
1314
|
modelHandlers = [];
|
|
1265
1315
|
modeHandlers = [];
|
|
1266
1316
|
usageHandlers = [];
|
|
1317
|
+
// Set by amendPrompt at the start of a cancel-and-resubmit dance.
|
|
1318
|
+
// broadcastTurnComplete reads it to attach the _meta.amended marker
|
|
1319
|
+
// to the cancelled turn's turn_complete notification, and to fire the
|
|
1320
|
+
// dedicated prompt_amended notification. Cleared when the cancelled
|
|
1321
|
+
// turn's task completes (runQueueEntry) OR if the amendment is
|
|
1322
|
+
// cancelled mid-window via cancel_prompt(M2) before drainQueue picks
|
|
1323
|
+
// it up.
|
|
1324
|
+
amendInProgress;
|
|
1325
|
+
// LRU of recently-terminal messageIds → stopReason. Used by
|
|
1326
|
+
// amendPrompt to resolve targets that completed/cancelled before
|
|
1327
|
+
// the amend arrived. Capped at RECENTLY_TERMINAL_LIMIT entries;
|
|
1328
|
+
// older entries fall out and resolve to target_not_found, which is
|
|
1329
|
+
// the correct behavior.
|
|
1330
|
+
recentlyTerminal = /* @__PURE__ */ new Map();
|
|
1267
1331
|
constructor(init) {
|
|
1268
1332
|
this.sessionId = init.sessionId ?? `${HYDRA_SESSION_PREFIX}${generateHydraId()}`;
|
|
1269
1333
|
this.cwd = init.cwd;
|
|
@@ -1693,7 +1757,7 @@ var init_session = __esm({
|
|
|
1693
1757
|
);
|
|
1694
1758
|
}
|
|
1695
1759
|
}
|
|
1696
|
-
broadcastTurnComplete(originatorClientId, response) {
|
|
1760
|
+
broadcastTurnComplete(originatorClientId, response, promptMessageId, wasAmend) {
|
|
1697
1761
|
const stopReason = response && typeof response === "object" && "stopReason" in response && typeof response.stopReason === "string" ? response.stopReason : void 0;
|
|
1698
1762
|
const update = {
|
|
1699
1763
|
sessionUpdate: "turn_complete",
|
|
@@ -1702,15 +1766,83 @@ var init_session = __esm({
|
|
|
1702
1766
|
if (stopReason !== void 0) {
|
|
1703
1767
|
update.stopReason = stopReason;
|
|
1704
1768
|
}
|
|
1769
|
+
const amend = this.amendInProgress;
|
|
1770
|
+
if (amend && promptMessageId !== void 0 && amend.cancelledMessageId === promptMessageId) {
|
|
1771
|
+
update._meta = {
|
|
1772
|
+
"hydra-acp": {
|
|
1773
|
+
amended: {
|
|
1774
|
+
cancelledMessageId: amend.cancelledMessageId,
|
|
1775
|
+
newMessageId: amend.newMessageId
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1705
1780
|
this.promptStartedAt = void 0;
|
|
1781
|
+
if (promptMessageId !== void 0 && stopReason !== void 0) {
|
|
1782
|
+
this.recordTerminal(promptMessageId, stopReason);
|
|
1783
|
+
}
|
|
1706
1784
|
this.recordAndBroadcast(
|
|
1707
1785
|
"session/update",
|
|
1708
1786
|
{
|
|
1709
1787
|
sessionId: this.sessionId,
|
|
1710
1788
|
update
|
|
1711
1789
|
},
|
|
1712
|
-
originatorClientId
|
|
1790
|
+
wasAmend ? void 0 : originatorClientId
|
|
1791
|
+
);
|
|
1792
|
+
if (amend && promptMessageId !== void 0 && amend.cancelledMessageId === promptMessageId) {
|
|
1793
|
+
this.broadcastPromptAmended(amend);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
// Record that a prompt's turn has ended, with its terminal stopReason.
|
|
1797
|
+
// Used by amendPrompt to resolve targetMessageIds that completed/cancelled
|
|
1798
|
+
// before the amend arrived. LRU-trimmed at RECENTLY_TERMINAL_LIMIT.
|
|
1799
|
+
recordTerminal(messageId, stopReason) {
|
|
1800
|
+
this.recentlyTerminal.set(messageId, {
|
|
1801
|
+
stopReason,
|
|
1802
|
+
terminatedAt: Date.now()
|
|
1803
|
+
});
|
|
1804
|
+
while (this.recentlyTerminal.size > RECENTLY_TERMINAL_LIMIT) {
|
|
1805
|
+
const oldest = this.recentlyTerminal.keys().next().value;
|
|
1806
|
+
if (oldest === void 0) {
|
|
1807
|
+
break;
|
|
1808
|
+
}
|
|
1809
|
+
this.recentlyTerminal.delete(oldest);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
// Fire hydra-acp/prompt_amended for the M1→M2 linkage. The amendment's
|
|
1813
|
+
// current content is read live from the queue entry so any update_prompt
|
|
1814
|
+
// calls during the amend window are reflected. Best-effort: if M2 has
|
|
1815
|
+
// already been cancelled out of the queue by the time we get here, we
|
|
1816
|
+
// skip — the amendInProgress clearing in cancelQueuedPrompt should have
|
|
1817
|
+
// prevented this code path from running in that case.
|
|
1818
|
+
broadcastPromptAmended(amend) {
|
|
1819
|
+
const entry = this.findUserEntry(amend.newMessageId);
|
|
1820
|
+
if (!entry) {
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
const params = {
|
|
1824
|
+
sessionId: this.sessionId,
|
|
1825
|
+
cancelledMessageId: amend.cancelledMessageId,
|
|
1826
|
+
newMessageId: amend.newMessageId,
|
|
1827
|
+
prompt: entry.prompt,
|
|
1828
|
+
originator: entry.originator,
|
|
1829
|
+
amendedAt: Date.now()
|
|
1830
|
+
};
|
|
1831
|
+
this.broadcastQueueNotification(
|
|
1832
|
+
"hydra-acp/prompt_amended",
|
|
1833
|
+
params
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
// Look up a user-prompt queue entry by messageId, searching both the
|
|
1837
|
+
// currentEntry slot and the waiting queue.
|
|
1838
|
+
findUserEntry(messageId) {
|
|
1839
|
+
if (this.currentEntry?.messageId === messageId && this.currentEntry.kind === "user") {
|
|
1840
|
+
return this.currentEntry;
|
|
1841
|
+
}
|
|
1842
|
+
const queued = this.promptQueue.find(
|
|
1843
|
+
(e) => e.messageId === messageId && e.kind === "user"
|
|
1713
1844
|
);
|
|
1845
|
+
return queued?.kind === "user" ? queued : void 0;
|
|
1714
1846
|
}
|
|
1715
1847
|
// Total visible-or-running entries: the in-flight head (if any) plus
|
|
1716
1848
|
// the queue's user-visible waiting entries. Internal entries don't
|
|
@@ -1723,9 +1855,9 @@ var init_session = __esm({
|
|
|
1723
1855
|
}
|
|
1724
1856
|
return count;
|
|
1725
1857
|
}
|
|
1726
|
-
broadcastQueueAdded(entry) {
|
|
1858
|
+
broadcastQueueAdded(entry, options) {
|
|
1727
1859
|
const depth = this.visibleQueueDepth();
|
|
1728
|
-
const position = Math.max(0, depth - 1);
|
|
1860
|
+
const position = options?.position ?? Math.max(0, depth - 1);
|
|
1729
1861
|
const params = {
|
|
1730
1862
|
sessionId: this.sessionId,
|
|
1731
1863
|
messageId: entry.messageId,
|
|
@@ -1735,6 +1867,11 @@ var init_session = __esm({
|
|
|
1735
1867
|
queueDepth: depth,
|
|
1736
1868
|
enqueuedAt: entry.enqueuedAt
|
|
1737
1869
|
};
|
|
1870
|
+
if (options?.amending !== void 0) {
|
|
1871
|
+
params._meta = {
|
|
1872
|
+
"hydra-acp": { amending: options.amending }
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1738
1875
|
this.broadcastQueueNotification("hydra-acp/prompt_queue_added", params);
|
|
1739
1876
|
}
|
|
1740
1877
|
broadcastQueueUpdated(messageId, prompt) {
|
|
@@ -1857,6 +1994,9 @@ var init_session = __esm({
|
|
|
1857
1994
|
this.broadcastQueueRemoved(messageId, "cancelled");
|
|
1858
1995
|
this.persistRewrite();
|
|
1859
1996
|
}
|
|
1997
|
+
if (this.amendInProgress?.newMessageId === messageId) {
|
|
1998
|
+
this.amendInProgress = void 0;
|
|
1999
|
+
}
|
|
1860
2000
|
entry.resolve({ stopReason: "cancelled" });
|
|
1861
2001
|
return { cancelled: true, reason: "ok" };
|
|
1862
2002
|
}
|
|
@@ -1878,6 +2018,143 @@ var init_session = __esm({
|
|
|
1878
2018
|
this.persistRewrite();
|
|
1879
2019
|
return { updated: true, reason: "ok" };
|
|
1880
2020
|
}
|
|
2021
|
+
// Amend the head prompt: cancel the in-flight turn and submit a
|
|
2022
|
+
// replacement that sits at the head of the queue. Resolves the
|
|
2023
|
+
// request immediately (the caller doesn't wait on cancel-settle).
|
|
2024
|
+
// Honours race outcomes — if the target finished or was cancelled
|
|
2025
|
+
// before this arrived, the request resolves with an outcome explaining
|
|
2026
|
+
// why and (depending on onTargetCompleted) optionally forwards as a
|
|
2027
|
+
// plain prompt. Queued targets are edited in place (same machinery
|
|
2028
|
+
// as updateQueuedPrompt).
|
|
2029
|
+
amendPrompt(clientId, params) {
|
|
2030
|
+
const client = this.clients.get(clientId);
|
|
2031
|
+
if (!client) {
|
|
2032
|
+
throw withCode(
|
|
2033
|
+
new Error("client not attached"),
|
|
2034
|
+
JsonRpcErrorCodes.SessionNotFound
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
const { targetMessageId, prompt, replaceQueue, onTargetCompleted } = params;
|
|
2038
|
+
if (this.currentEntry?.messageId === targetMessageId && this.currentEntry.kind === "user" && !this.currentEntry.cancelled && this.amendInProgress === void 0) {
|
|
2039
|
+
return this.amendOnHead(client, prompt, targetMessageId, replaceQueue);
|
|
2040
|
+
}
|
|
2041
|
+
const queuedEntry = this.promptQueue.find(
|
|
2042
|
+
(e) => e.messageId === targetMessageId && e.kind === "user"
|
|
2043
|
+
);
|
|
2044
|
+
if (queuedEntry && queuedEntry.kind === "user" && !queuedEntry.cancelled) {
|
|
2045
|
+
queuedEntry.prompt = prompt;
|
|
2046
|
+
this.broadcastQueueUpdated(targetMessageId, prompt);
|
|
2047
|
+
this.persistRewrite();
|
|
2048
|
+
return { amended: true, reason: "ok", messageId: targetMessageId };
|
|
2049
|
+
}
|
|
2050
|
+
const terminal = this.recentlyTerminal.get(targetMessageId);
|
|
2051
|
+
if (terminal) {
|
|
2052
|
+
if (terminal.stopReason === "cancelled") {
|
|
2053
|
+
return { amended: false, reason: "target_cancelled" };
|
|
2054
|
+
}
|
|
2055
|
+
if (onTargetCompleted === "send_anyway") {
|
|
2056
|
+
const newMessageId = this.enqueueAmendmentAsFollowUp(client, prompt);
|
|
2057
|
+
return {
|
|
2058
|
+
amended: false,
|
|
2059
|
+
reason: "target_completed",
|
|
2060
|
+
messageId: newMessageId
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
return { amended: false, reason: "target_completed" };
|
|
2064
|
+
}
|
|
2065
|
+
return { amended: false, reason: "target_not_found" };
|
|
2066
|
+
}
|
|
2067
|
+
// Head-of-queue amendment: splice M2 in front of any waiting entries,
|
|
2068
|
+
// broadcast the amend window's queue_added with the amending hint,
|
|
2069
|
+
// mark amendInProgress so the cancelled turn's broadcastTurnComplete
|
|
2070
|
+
// attaches the _meta marker and fires prompt_amended, then fire the
|
|
2071
|
+
// upstream session/cancel without awaiting it. drainQueue is already
|
|
2072
|
+
// running on the head; when its session/prompt returns, it advances
|
|
2073
|
+
// to M2 in the normal way.
|
|
2074
|
+
amendOnHead(client, prompt, targetMessageId, replaceQueue) {
|
|
2075
|
+
const newMessageId = generateMessageId();
|
|
2076
|
+
const originator = { clientId: client.clientId };
|
|
2077
|
+
if (client.clientInfo?.name) {
|
|
2078
|
+
originator.name = client.clientInfo.name;
|
|
2079
|
+
}
|
|
2080
|
+
if (client.clientInfo?.version) {
|
|
2081
|
+
originator.version = client.clientInfo.version;
|
|
2082
|
+
}
|
|
2083
|
+
if (replaceQueue) {
|
|
2084
|
+
const survivors = [];
|
|
2085
|
+
for (const entry2 of this.promptQueue) {
|
|
2086
|
+
if (entry2.kind === "user" && !entry2.cancelled) {
|
|
2087
|
+
entry2.cancelled = true;
|
|
2088
|
+
this.broadcastQueueRemoved(entry2.messageId, "cancelled");
|
|
2089
|
+
entry2.resolve({ stopReason: "cancelled" });
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
survivors.push(entry2);
|
|
2093
|
+
}
|
|
2094
|
+
this.promptQueue = survivors;
|
|
2095
|
+
}
|
|
2096
|
+
const entry = {
|
|
2097
|
+
kind: "user",
|
|
2098
|
+
messageId: newMessageId,
|
|
2099
|
+
originator,
|
|
2100
|
+
clientId: client.clientId,
|
|
2101
|
+
prompt,
|
|
2102
|
+
enqueuedAt: Date.now(),
|
|
2103
|
+
cancelled: false,
|
|
2104
|
+
wasAmend: true,
|
|
2105
|
+
// No-op resolve/reject: there's no client request awaiting M2's
|
|
2106
|
+
// session/prompt response. The amend_prompt request has already
|
|
2107
|
+
// returned by this point. drainQueue calls these unconditionally
|
|
2108
|
+
// when runQueueEntry settles; making them no-ops is safe.
|
|
2109
|
+
resolve: () => void 0,
|
|
2110
|
+
reject: () => void 0
|
|
2111
|
+
};
|
|
2112
|
+
this.promptQueue.unshift(entry);
|
|
2113
|
+
this.persistRewrite();
|
|
2114
|
+
this.broadcastQueueAdded(entry, {
|
|
2115
|
+
amending: targetMessageId,
|
|
2116
|
+
position: 1
|
|
2117
|
+
});
|
|
2118
|
+
this.amendInProgress = {
|
|
2119
|
+
cancelledMessageId: targetMessageId,
|
|
2120
|
+
newMessageId
|
|
2121
|
+
};
|
|
2122
|
+
void this.agent.connection.notify("session/cancel", { sessionId: this.upstreamSessionId }).catch(() => void 0);
|
|
2123
|
+
return {
|
|
2124
|
+
amended: true,
|
|
2125
|
+
reason: "ok",
|
|
2126
|
+
messageId: newMessageId
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
// Send the amendment as a plain follow-up prompt — used when the
|
|
2130
|
+
// target already completed and the caller opted in to send_anyway.
|
|
2131
|
+
// Returns the new prompt's messageId so the result can surface it.
|
|
2132
|
+
enqueueAmendmentAsFollowUp(client, prompt) {
|
|
2133
|
+
const messageId = generateMessageId();
|
|
2134
|
+
const originator = { clientId: client.clientId };
|
|
2135
|
+
if (client.clientInfo?.name) {
|
|
2136
|
+
originator.name = client.clientInfo.name;
|
|
2137
|
+
}
|
|
2138
|
+
if (client.clientInfo?.version) {
|
|
2139
|
+
originator.version = client.clientInfo.version;
|
|
2140
|
+
}
|
|
2141
|
+
const entry = {
|
|
2142
|
+
kind: "user",
|
|
2143
|
+
messageId,
|
|
2144
|
+
originator,
|
|
2145
|
+
clientId: client.clientId,
|
|
2146
|
+
prompt,
|
|
2147
|
+
enqueuedAt: Date.now(),
|
|
2148
|
+
cancelled: false,
|
|
2149
|
+
resolve: () => void 0,
|
|
2150
|
+
reject: () => void 0
|
|
2151
|
+
};
|
|
2152
|
+
this.promptQueue.push(entry);
|
|
2153
|
+
this.persistRewrite();
|
|
2154
|
+
this.broadcastQueueAdded(entry);
|
|
2155
|
+
void this.drainQueue();
|
|
2156
|
+
return messageId;
|
|
2157
|
+
}
|
|
1881
2158
|
async cancel(clientId) {
|
|
1882
2159
|
const client = this.clients.get(clientId);
|
|
1883
2160
|
if (!client) {
|
|
@@ -2736,6 +3013,7 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
2736
3013
|
try {
|
|
2737
3014
|
const result = await this.runQueueEntry(next);
|
|
2738
3015
|
next.resolve(result);
|
|
3016
|
+
await Promise.resolve();
|
|
2739
3017
|
} catch (err) {
|
|
2740
3018
|
next.reject(err);
|
|
2741
3019
|
} finally {
|
|
@@ -2772,12 +3050,33 @@ _(switched from \`${oldAgentId}\` to \`${newAgentId}\`)_
|
|
|
2772
3050
|
}
|
|
2773
3051
|
);
|
|
2774
3052
|
} catch (err) {
|
|
2775
|
-
this.broadcastTurnComplete(
|
|
3053
|
+
this.broadcastTurnComplete(
|
|
3054
|
+
entry.clientId,
|
|
3055
|
+
{ stopReason: "error" },
|
|
3056
|
+
entry.messageId,
|
|
3057
|
+
entry.wasAmend
|
|
3058
|
+
);
|
|
3059
|
+
this.clearAmendIfMatches(entry.messageId);
|
|
2776
3060
|
throw err;
|
|
2777
3061
|
}
|
|
2778
|
-
this.broadcastTurnComplete(
|
|
3062
|
+
this.broadcastTurnComplete(
|
|
3063
|
+
entry.clientId,
|
|
3064
|
+
response,
|
|
3065
|
+
entry.messageId,
|
|
3066
|
+
entry.wasAmend
|
|
3067
|
+
);
|
|
3068
|
+
this.clearAmendIfMatches(entry.messageId);
|
|
2779
3069
|
return response;
|
|
2780
3070
|
}
|
|
3071
|
+
// Clear amendInProgress once the cancelled turn's task has fully
|
|
3072
|
+
// settled. broadcastTurnComplete needs the marker still set when it
|
|
3073
|
+
// fires, so the clear must happen *after*. Called from runQueueEntry's
|
|
3074
|
+
// settle path for both success and error.
|
|
3075
|
+
clearAmendIfMatches(messageId) {
|
|
3076
|
+
if (this.amendInProgress?.cancelledMessageId === messageId) {
|
|
3077
|
+
this.amendInProgress = void 0;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
2781
3080
|
};
|
|
2782
3081
|
STATE_UPDATE_KINDS = /* @__PURE__ */ new Set([
|
|
2783
3082
|
"session_info_update",
|
|
@@ -3427,7 +3726,16 @@ function mapModel(u) {
|
|
|
3427
3726
|
}
|
|
3428
3727
|
function mapTurnComplete(u) {
|
|
3429
3728
|
const stopReason = readString(u, "stopReason");
|
|
3430
|
-
|
|
3729
|
+
const meta = u._meta;
|
|
3730
|
+
const amended = meta?.["hydra-acp"]?.amended !== void 0 && meta["hydra-acp"].amended !== null;
|
|
3731
|
+
const out = { kind: "turn-complete" };
|
|
3732
|
+
if (stopReason !== void 0) {
|
|
3733
|
+
out.stopReason = stopReason;
|
|
3734
|
+
}
|
|
3735
|
+
if (amended) {
|
|
3736
|
+
out.amended = true;
|
|
3737
|
+
}
|
|
3738
|
+
return out;
|
|
3431
3739
|
}
|
|
3432
3740
|
function extractContentText(content) {
|
|
3433
3741
|
if (typeof content === "string") {
|
|
@@ -5947,6 +6255,12 @@ function mapKeyName(name) {
|
|
|
5947
6255
|
case "ALT_ENTER":
|
|
5948
6256
|
case "META_ENTER":
|
|
5949
6257
|
return "alt-enter";
|
|
6258
|
+
case "SHIFT_ENTER":
|
|
6259
|
+
return "shift-enter";
|
|
6260
|
+
case "CTRL_ENTER":
|
|
6261
|
+
return "ctrl-enter";
|
|
6262
|
+
case "CTRL_J":
|
|
6263
|
+
return "ctrl-enter";
|
|
5950
6264
|
case "ALT_B":
|
|
5951
6265
|
case "META_B":
|
|
5952
6266
|
return "alt-b";
|
|
@@ -6216,12 +6530,15 @@ var init_screen = __esm({
|
|
|
6216
6530
|
this.term.fullscreen(false);
|
|
6217
6531
|
this.term("\n");
|
|
6218
6532
|
}
|
|
6219
|
-
// Enables bracketed paste mode on the terminal and
|
|
6220
|
-
// see the \x1b[200~/\x1b[201~ markers
|
|
6221
|
-
//
|
|
6222
|
-
//
|
|
6533
|
+
// Enables bracketed paste mode + modifyOtherKeys on the terminal and
|
|
6534
|
+
// rewires stdin so we see the \x1b[200~/\x1b[201~ paste markers and
|
|
6535
|
+
// CSI-u modified-key sequences (Shift+Enter etc.) BEFORE terminal-kit's
|
|
6536
|
+
// key parser. Non-special data is forwarded to terminal-kit unchanged.
|
|
6223
6537
|
installBracketedPaste() {
|
|
6224
6538
|
process.stdout.write("\x1B[?2004h");
|
|
6539
|
+
process.stdout.write("\x1B[>4;2m");
|
|
6540
|
+
process.stdout.write("\x1B[>5;1m");
|
|
6541
|
+
process.stdout.write("\x1B[>1u");
|
|
6225
6542
|
const t = this.term;
|
|
6226
6543
|
if (!t.stdin || typeof t.onStdin !== "function") {
|
|
6227
6544
|
return;
|
|
@@ -6232,6 +6549,9 @@ var init_screen = __esm({
|
|
|
6232
6549
|
}
|
|
6233
6550
|
uninstallBracketedPaste() {
|
|
6234
6551
|
process.stdout.write("\x1B[?2004l");
|
|
6552
|
+
process.stdout.write("\x1B[>4;0m");
|
|
6553
|
+
process.stdout.write("\x1B[>5;0m");
|
|
6554
|
+
process.stdout.write("\x1B[<u");
|
|
6235
6555
|
const t = this.term;
|
|
6236
6556
|
if (!t.stdin || this.terminalKitStdinHandler === null) {
|
|
6237
6557
|
return;
|
|
@@ -6244,6 +6564,38 @@ var init_screen = __esm({
|
|
|
6244
6564
|
}
|
|
6245
6565
|
handleRawStdin(chunk) {
|
|
6246
6566
|
let text = chunk.toString("binary");
|
|
6567
|
+
if (!this.pasteActive) {
|
|
6568
|
+
const markers = [
|
|
6569
|
+
{ seq: "\x1B[13;2u", name: "shift-enter" },
|
|
6570
|
+
{ seq: "\x1B[27;2;13~", name: "shift-enter" },
|
|
6571
|
+
{ seq: "\x1B[13;5u", name: "ctrl-enter" },
|
|
6572
|
+
{ seq: "\x1B[27;5;13~", name: "ctrl-enter" },
|
|
6573
|
+
// Bare LF — universal fallback for terminals without
|
|
6574
|
+
// modifyOtherKeys / kitty protocol. Last so the longer escape
|
|
6575
|
+
// sequences match first and we don't double-fire.
|
|
6576
|
+
{ seq: "\n", name: "ctrl-enter" }
|
|
6577
|
+
];
|
|
6578
|
+
for (const { seq, name } of markers) {
|
|
6579
|
+
if (text.includes(seq)) {
|
|
6580
|
+
const parts = text.split(seq);
|
|
6581
|
+
for (let i = 0; i < parts.length; i++) {
|
|
6582
|
+
if (parts[i].length > 0) {
|
|
6583
|
+
this.handleRawStdin(Buffer.from(parts[i], "binary"));
|
|
6584
|
+
}
|
|
6585
|
+
if (i < parts.length - 1) {
|
|
6586
|
+
this.onKey([{ type: "key", name }]);
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
return;
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
6593
|
+
this.handleRawStdinSegment(text);
|
|
6594
|
+
}
|
|
6595
|
+
// Inner stdin-segment handler — paste-marker detection and forwarding
|
|
6596
|
+
// to terminal-kit. Split out so shift-enter interception can call it
|
|
6597
|
+
// for the non-shift-enter portions of a mixed chunk.
|
|
6598
|
+
handleRawStdinSegment(text) {
|
|
6247
6599
|
const startMarker = "\x1B[200~";
|
|
6248
6600
|
const endMarker = "\x1B[201~";
|
|
6249
6601
|
while (text.length > 0) {
|
|
@@ -7944,6 +8296,9 @@ var init_input = __esm({
|
|
|
7944
8296
|
switch (name) {
|
|
7945
8297
|
case "enter":
|
|
7946
8298
|
return this.send();
|
|
8299
|
+
case "shift-enter":
|
|
8300
|
+
case "ctrl-enter":
|
|
8301
|
+
return this.amend();
|
|
7947
8302
|
case "alt-enter":
|
|
7948
8303
|
this.insertNewline();
|
|
7949
8304
|
return [];
|
|
@@ -8130,22 +8485,64 @@ var init_input = __esm({
|
|
|
8130
8485
|
this.setCurrentLine(line + next);
|
|
8131
8486
|
}
|
|
8132
8487
|
}
|
|
8488
|
+
// ^U: kill from cursor to start of current line. At col 0 with a line
|
|
8489
|
+
// above:
|
|
8490
|
+
// - If the current line is empty, collapse it (kill just the
|
|
8491
|
+
// newline) so the cursor lands at the end of the previous line.
|
|
8492
|
+
// Don't slurp that line's contents.
|
|
8493
|
+
// - Otherwise, kill the previous line entirely + the joining
|
|
8494
|
+
// newline, so ^U from the start of a non-empty line walks up
|
|
8495
|
+
// line-by-line.
|
|
8496
|
+
// Single-line behavior is unchanged.
|
|
8133
8497
|
killLine() {
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
this.
|
|
8498
|
+
if (this.col > 0) {
|
|
8499
|
+
const line = this.currentLine();
|
|
8500
|
+
this.killBuffer = line.slice(0, this.col);
|
|
8501
|
+
this.setCurrentLine(line.slice(this.col));
|
|
8502
|
+
this.col = 0;
|
|
8503
|
+
return;
|
|
8138
8504
|
}
|
|
8139
|
-
|
|
8140
|
-
|
|
8505
|
+
if (this.row === 0) {
|
|
8506
|
+
return;
|
|
8507
|
+
}
|
|
8508
|
+
if (this.currentLine().length === 0) {
|
|
8509
|
+
this.killBuffer = "\n";
|
|
8510
|
+
this.buffer.splice(this.row, 1);
|
|
8511
|
+
this.row -= 1;
|
|
8512
|
+
this.col = this.currentLine().length;
|
|
8513
|
+
return;
|
|
8514
|
+
}
|
|
8515
|
+
const prev = this.buffer[this.row - 1] ?? "";
|
|
8516
|
+
this.killBuffer = prev + "\n";
|
|
8517
|
+
this.buffer.splice(this.row - 1, 1);
|
|
8518
|
+
this.row -= 1;
|
|
8141
8519
|
}
|
|
8520
|
+
// ^K: kill from cursor to end of current line. At end-of-line with a
|
|
8521
|
+
// line below:
|
|
8522
|
+
// - If the current line is empty, collapse it (kill just the
|
|
8523
|
+
// newline) so what was the next line takes its place. Don't slurp
|
|
8524
|
+
// that line's contents.
|
|
8525
|
+
// - Otherwise, kill the joining newline + the entire next line, so
|
|
8526
|
+
// ^K from the end of a non-empty line walks down line-by-line.
|
|
8527
|
+
// Single-line behavior is unchanged.
|
|
8142
8528
|
killToEnd() {
|
|
8143
8529
|
const line = this.currentLine();
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
this.
|
|
8530
|
+
if (this.col < line.length) {
|
|
8531
|
+
this.killBuffer = line.slice(this.col);
|
|
8532
|
+
this.setCurrentLine(line.slice(0, this.col));
|
|
8533
|
+
return;
|
|
8147
8534
|
}
|
|
8148
|
-
this.
|
|
8535
|
+
if (this.row >= this.buffer.length - 1) {
|
|
8536
|
+
return;
|
|
8537
|
+
}
|
|
8538
|
+
if (line.length === 0) {
|
|
8539
|
+
this.killBuffer = "\n";
|
|
8540
|
+
this.buffer.splice(this.row, 1);
|
|
8541
|
+
return;
|
|
8542
|
+
}
|
|
8543
|
+
const next = this.buffer[this.row + 1] ?? "";
|
|
8544
|
+
this.killBuffer = "\n" + next;
|
|
8545
|
+
this.buffer.splice(this.row + 1, 1);
|
|
8149
8546
|
}
|
|
8150
8547
|
killWord() {
|
|
8151
8548
|
const line = this.currentLine();
|
|
@@ -8494,6 +8891,31 @@ var init_input = __esm({
|
|
|
8494
8891
|
this.clearBuffer();
|
|
8495
8892
|
return [{ type: "send", text, planMode, attachments }];
|
|
8496
8893
|
}
|
|
8894
|
+
// Shift+Enter: amend the in-flight turn. Editing a queued slot
|
|
8895
|
+
// delegates to the existing queue-edit / queue-remove path — Shift+Enter
|
|
8896
|
+
// there has no special meaning since the entry is already queued (not
|
|
8897
|
+
// running). With an empty draft and no attachments we emit nothing
|
|
8898
|
+
// (no-op). Otherwise emit an "amend" effect; the app decides whether
|
|
8899
|
+
// to route through amend_prompt or fall through to a regular send.
|
|
8900
|
+
amend() {
|
|
8901
|
+
const text = this.bufferText();
|
|
8902
|
+
if (this.queueIndex >= 0 && this.queueIndex < this.queue.length) {
|
|
8903
|
+
const index = this.queueIndex;
|
|
8904
|
+
const attachments2 = [...this.attachments];
|
|
8905
|
+
this.clearBuffer();
|
|
8906
|
+
if (text.trim().length === 0) {
|
|
8907
|
+
return [{ type: "queue-remove", index }];
|
|
8908
|
+
}
|
|
8909
|
+
return [{ type: "queue-edit", index, text, attachments: attachments2 }];
|
|
8910
|
+
}
|
|
8911
|
+
if (text.trim().length === 0 && this.attachments.length === 0) {
|
|
8912
|
+
return [];
|
|
8913
|
+
}
|
|
8914
|
+
const planMode = this.planMode;
|
|
8915
|
+
const attachments = [...this.attachments];
|
|
8916
|
+
this.clearBuffer();
|
|
8917
|
+
return [{ type: "amend", text, planMode, attachments }];
|
|
8918
|
+
}
|
|
8497
8919
|
// Home: jump to the very start of the prompt buffer. If we're already
|
|
8498
8920
|
// there, fall through to scrolling the scrollback to its top.
|
|
8499
8921
|
handleHome() {
|
|
@@ -8605,26 +9027,34 @@ async function readLinux(env) {
|
|
|
8605
9027
|
reason: "install wl-clipboard (Wayland) or xclip (X11) to paste from the clipboard"
|
|
8606
9028
|
};
|
|
8607
9029
|
}
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
9030
|
+
const targets = await listTargets(env, tool);
|
|
9031
|
+
const imageMime = pickImageTarget(targets);
|
|
9032
|
+
if (imageMime) {
|
|
9033
|
+
try {
|
|
9034
|
+
const buf = await runCapture(
|
|
9035
|
+
env.spawn,
|
|
9036
|
+
tool.cmd,
|
|
9037
|
+
tool.imageArgs(imageMime)
|
|
9038
|
+
);
|
|
9039
|
+
if (buf.length > 0) {
|
|
9040
|
+
if (buf.length > MAX_ATTACHMENT_BYTES) {
|
|
9041
|
+
return {
|
|
9042
|
+
ok: false,
|
|
9043
|
+
reason: `clipboard image is ${formatSize(buf.length)}, max ${formatSize(MAX_ATTACHMENT_BYTES)}`
|
|
9044
|
+
};
|
|
9045
|
+
}
|
|
8612
9046
|
return {
|
|
8613
|
-
ok:
|
|
8614
|
-
|
|
9047
|
+
ok: true,
|
|
9048
|
+
kind: "image",
|
|
9049
|
+
attachment: {
|
|
9050
|
+
mimeType: imageMime,
|
|
9051
|
+
data: buf.toString("base64"),
|
|
9052
|
+
sizeBytes: buf.length
|
|
9053
|
+
}
|
|
8615
9054
|
};
|
|
8616
9055
|
}
|
|
8617
|
-
|
|
8618
|
-
ok: true,
|
|
8619
|
-
kind: "image",
|
|
8620
|
-
attachment: {
|
|
8621
|
-
mimeType: "image/png",
|
|
8622
|
-
data: buf.toString("base64"),
|
|
8623
|
-
sizeBytes: buf.length
|
|
8624
|
-
}
|
|
8625
|
-
};
|
|
9056
|
+
} catch {
|
|
8626
9057
|
}
|
|
8627
|
-
} catch {
|
|
8628
9058
|
}
|
|
8629
9059
|
try {
|
|
8630
9060
|
const buf = await runCapture(env.spawn, tool.cmd, tool.textArgs);
|
|
@@ -8644,7 +9074,8 @@ async function detectLinuxTool(env) {
|
|
|
8644
9074
|
if (env.env.WAYLAND_DISPLAY && await which(env, "wl-paste")) {
|
|
8645
9075
|
return {
|
|
8646
9076
|
cmd: "wl-paste",
|
|
8647
|
-
|
|
9077
|
+
listTargetsArgs: ["--list-types"],
|
|
9078
|
+
imageArgs: (mime) => ["-t", mime],
|
|
8648
9079
|
// -n: drop trailing newline wl-paste adds by default. We further
|
|
8649
9080
|
// normalize line endings below, but this avoids a spurious
|
|
8650
9081
|
// empty trailing row from a single-line clipboard text.
|
|
@@ -8654,12 +9085,30 @@ async function detectLinuxTool(env) {
|
|
|
8654
9085
|
if (env.env.DISPLAY && await which(env, "xclip")) {
|
|
8655
9086
|
return {
|
|
8656
9087
|
cmd: "xclip",
|
|
8657
|
-
|
|
9088
|
+
listTargetsArgs: ["-selection", "clipboard", "-t", "TARGETS", "-o"],
|
|
9089
|
+
imageArgs: (mime) => ["-selection", "clipboard", "-t", mime, "-o"],
|
|
8658
9090
|
textArgs: ["-selection", "clipboard", "-o"]
|
|
8659
9091
|
};
|
|
8660
9092
|
}
|
|
8661
9093
|
return null;
|
|
8662
9094
|
}
|
|
9095
|
+
function pickImageTarget(targets) {
|
|
9096
|
+
const offered = new Set(targets.map((t) => t.toLowerCase()));
|
|
9097
|
+
for (const mime of SUPPORTED_IMAGE_MIMES) {
|
|
9098
|
+
if (offered.has(mime)) {
|
|
9099
|
+
return mime;
|
|
9100
|
+
}
|
|
9101
|
+
}
|
|
9102
|
+
return null;
|
|
9103
|
+
}
|
|
9104
|
+
async function listTargets(env, tool) {
|
|
9105
|
+
try {
|
|
9106
|
+
const buf = await runCapture(env.spawn, tool.cmd, tool.listTargetsArgs);
|
|
9107
|
+
return buf.toString("utf-8").split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
9108
|
+
} catch {
|
|
9109
|
+
return [];
|
|
9110
|
+
}
|
|
9111
|
+
}
|
|
8663
9112
|
function normalizeText(text) {
|
|
8664
9113
|
return text.replace(/\r\n?/g, "\n");
|
|
8665
9114
|
}
|
|
@@ -8754,7 +9203,7 @@ function runCapture(spawn6, cmd, args) {
|
|
|
8754
9203
|
});
|
|
8755
9204
|
});
|
|
8756
9205
|
}
|
|
8757
|
-
var defaultEnv;
|
|
9206
|
+
var defaultEnv, SUPPORTED_IMAGE_MIMES;
|
|
8758
9207
|
var init_clipboard = __esm({
|
|
8759
9208
|
"src/tui/clipboard.ts"() {
|
|
8760
9209
|
"use strict";
|
|
@@ -8765,6 +9214,12 @@ var init_clipboard = __esm({
|
|
|
8765
9214
|
spawn: nodeSpawn,
|
|
8766
9215
|
tmpdir: os4.tmpdir
|
|
8767
9216
|
};
|
|
9217
|
+
SUPPORTED_IMAGE_MIMES = [
|
|
9218
|
+
"image/png",
|
|
9219
|
+
"image/jpeg",
|
|
9220
|
+
"image/gif",
|
|
9221
|
+
"image/webp"
|
|
9222
|
+
];
|
|
8768
9223
|
}
|
|
8769
9224
|
});
|
|
8770
9225
|
|
|
@@ -8905,7 +9360,8 @@ function parseAgentMarkdown(text) {
|
|
|
8905
9360
|
codeBuffer = [];
|
|
8906
9361
|
codeLang = "";
|
|
8907
9362
|
};
|
|
8908
|
-
for (
|
|
9363
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9364
|
+
const line = lines[i];
|
|
8909
9365
|
const fence = line.match(/^\s*```\s*(\w*)\s*$/);
|
|
8910
9366
|
if (fence) {
|
|
8911
9367
|
if (!inCode) {
|
|
@@ -8933,6 +9389,19 @@ function parseAgentMarkdown(text) {
|
|
|
8933
9389
|
});
|
|
8934
9390
|
continue;
|
|
8935
9391
|
}
|
|
9392
|
+
const next = lines[i + 1];
|
|
9393
|
+
if (line.includes("|") && next !== void 0 && isTableSeparatorLine(next) && parseTableRow(line).length === parseTableRow(next).length) {
|
|
9394
|
+
const header = parseTableRow(line);
|
|
9395
|
+
const body = [];
|
|
9396
|
+
let j = i + 2;
|
|
9397
|
+
while (j < lines.length && lines[j].includes("|")) {
|
|
9398
|
+
body.push(parseTableRow(lines[j]));
|
|
9399
|
+
j++;
|
|
9400
|
+
}
|
|
9401
|
+
out.push(...formatTable(header, body));
|
|
9402
|
+
i = j - 1;
|
|
9403
|
+
continue;
|
|
9404
|
+
}
|
|
8936
9405
|
const bullet = line.match(/^(\s*)[-*+]\s+(.*)$/);
|
|
8937
9406
|
if (bullet) {
|
|
8938
9407
|
const indent = bullet[1] ?? "";
|
|
@@ -8967,6 +9436,70 @@ function parseAgentMarkdown(text) {
|
|
|
8967
9436
|
}
|
|
8968
9437
|
return out;
|
|
8969
9438
|
}
|
|
9439
|
+
function parseTableRow(line) {
|
|
9440
|
+
let s = line.trim();
|
|
9441
|
+
if (s.startsWith("|")) {
|
|
9442
|
+
s = s.slice(1);
|
|
9443
|
+
}
|
|
9444
|
+
if (s.endsWith("|")) {
|
|
9445
|
+
s = s.slice(0, -1);
|
|
9446
|
+
}
|
|
9447
|
+
return s.split("|").map((c) => c.trim());
|
|
9448
|
+
}
|
|
9449
|
+
function isTableSeparatorLine(line) {
|
|
9450
|
+
if (!line.includes("|")) {
|
|
9451
|
+
return false;
|
|
9452
|
+
}
|
|
9453
|
+
const cells = parseTableRow(line);
|
|
9454
|
+
if (cells.length === 0) {
|
|
9455
|
+
return false;
|
|
9456
|
+
}
|
|
9457
|
+
return cells.every((c) => /^:?-+:?$/.test(c));
|
|
9458
|
+
}
|
|
9459
|
+
function formatTable(header, body) {
|
|
9460
|
+
const cols = header.length;
|
|
9461
|
+
const widths = new Array(cols).fill(0);
|
|
9462
|
+
for (let c = 0; c < cols; c++) {
|
|
9463
|
+
widths[c] = header[c]?.length ?? 0;
|
|
9464
|
+
}
|
|
9465
|
+
for (const row of body) {
|
|
9466
|
+
for (let c = 0; c < cols; c++) {
|
|
9467
|
+
const cell = row[c] ?? "";
|
|
9468
|
+
if (cell.length > widths[c]) {
|
|
9469
|
+
widths[c] = cell.length;
|
|
9470
|
+
}
|
|
9471
|
+
}
|
|
9472
|
+
}
|
|
9473
|
+
const renderRow = (cells, style) => {
|
|
9474
|
+
const padded = [];
|
|
9475
|
+
for (let c = 0; c < cols; c++) {
|
|
9476
|
+
const cell = cells[c] ?? "";
|
|
9477
|
+
const w = widths[c];
|
|
9478
|
+
const marked = applyInlineMarkup(cell);
|
|
9479
|
+
padded.push(marked + " ".repeat(Math.max(0, w - cell.length)));
|
|
9480
|
+
}
|
|
9481
|
+
return {
|
|
9482
|
+
prefix: " ",
|
|
9483
|
+
body: padded.join(" \u2502 "),
|
|
9484
|
+
bodyStyle: style
|
|
9485
|
+
};
|
|
9486
|
+
};
|
|
9487
|
+
const out = [];
|
|
9488
|
+
out.push(renderRow(header, "heading-3"));
|
|
9489
|
+
const rules = [];
|
|
9490
|
+
for (let c = 0; c < cols; c++) {
|
|
9491
|
+
rules.push("\u2500".repeat(widths[c]));
|
|
9492
|
+
}
|
|
9493
|
+
out.push({
|
|
9494
|
+
prefix: " ",
|
|
9495
|
+
body: rules.join("\u2500\u253C\u2500"),
|
|
9496
|
+
bodyStyle: "dim"
|
|
9497
|
+
});
|
|
9498
|
+
for (const row of body) {
|
|
9499
|
+
out.push(renderRow(row, "agent"));
|
|
9500
|
+
}
|
|
9501
|
+
return out;
|
|
9502
|
+
}
|
|
8970
9503
|
function highlightFencedBlock(lang, lines) {
|
|
8971
9504
|
if (lang.length === 0 || !supportsLanguage(lang)) {
|
|
8972
9505
|
return lines.map((body) => ({ body, ansi: false }));
|
|
@@ -9235,6 +9768,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9235
9768
|
}
|
|
9236
9769
|
};
|
|
9237
9770
|
let pendingTurns = 0;
|
|
9771
|
+
let currentHeadMessageId;
|
|
9238
9772
|
let sessionBusySince = null;
|
|
9239
9773
|
let sessionElapsedTimer = null;
|
|
9240
9774
|
const adjustPendingTurns = (delta) => {
|
|
@@ -9326,13 +9860,29 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9326
9860
|
screenRef.setBanner({ status: "cold", elapsedMs: void 0 });
|
|
9327
9861
|
}
|
|
9328
9862
|
});
|
|
9863
|
+
const amendPendingPaintTimers = /* @__PURE__ */ new Map();
|
|
9864
|
+
const AMEND_CHIP_DISPLAY_DELAY_MS = 200;
|
|
9329
9865
|
conn.onNotification("hydra-acp/prompt_queue_added", (params) => {
|
|
9330
9866
|
if (teardownStarted) return;
|
|
9331
9867
|
const p = params ?? {};
|
|
9332
9868
|
if (typeof p.messageId !== "string") return;
|
|
9333
|
-
|
|
9334
|
-
if (
|
|
9335
|
-
|
|
9869
|
+
const isAmendPending = typeof p._meta?.["hydra-acp"]?.amending === "string";
|
|
9870
|
+
if (isAmendPending) {
|
|
9871
|
+
const mid = p.messageId;
|
|
9872
|
+
const prompt = p.prompt;
|
|
9873
|
+
const timer = setTimeout(() => {
|
|
9874
|
+
amendPendingPaintTimers.delete(mid);
|
|
9875
|
+
queueCache.set(mid, chipFromPrompt(mid, prompt));
|
|
9876
|
+
if (screenRef && dispatcherRef) {
|
|
9877
|
+
refreshQueueDisplay();
|
|
9878
|
+
}
|
|
9879
|
+
}, AMEND_CHIP_DISPLAY_DELAY_MS);
|
|
9880
|
+
amendPendingPaintTimers.set(mid, timer);
|
|
9881
|
+
} else {
|
|
9882
|
+
queueCache.set(p.messageId, chipFromPrompt(p.messageId, p.prompt));
|
|
9883
|
+
if (screenRef && dispatcherRef) {
|
|
9884
|
+
refreshQueueDisplay();
|
|
9885
|
+
}
|
|
9336
9886
|
}
|
|
9337
9887
|
if (ownClientId !== void 0 && p.originator?.clientId === ownClientId) {
|
|
9338
9888
|
const echo = pendingEchoes.shift();
|
|
@@ -9377,6 +9927,14 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9377
9927
|
if (teardownStarted) return;
|
|
9378
9928
|
const p = params ?? {};
|
|
9379
9929
|
if (typeof p.messageId !== "string") return;
|
|
9930
|
+
if (p.reason === "started") {
|
|
9931
|
+
currentHeadMessageId = p.messageId;
|
|
9932
|
+
}
|
|
9933
|
+
const pendingTimer = amendPendingPaintTimers.get(p.messageId);
|
|
9934
|
+
if (pendingTimer !== void 0) {
|
|
9935
|
+
clearTimeout(pendingTimer);
|
|
9936
|
+
amendPendingPaintTimers.delete(p.messageId);
|
|
9937
|
+
}
|
|
9380
9938
|
const hadChip = queueCache.delete(p.messageId);
|
|
9381
9939
|
if (hadChip && screenRef && dispatcherRef) {
|
|
9382
9940
|
refreshQueueDisplay();
|
|
@@ -9395,6 +9953,22 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9395
9953
|
}
|
|
9396
9954
|
}
|
|
9397
9955
|
});
|
|
9956
|
+
conn.onNotification("hydra-acp/prompt_amended", (params) => {
|
|
9957
|
+
if (teardownStarted) return;
|
|
9958
|
+
const p = params ?? {};
|
|
9959
|
+
if (typeof p.cancelledMessageId !== "string") return;
|
|
9960
|
+
const cancelledId = p.cancelledMessageId;
|
|
9961
|
+
amendedMessageIds.add(cancelledId);
|
|
9962
|
+
if (currentTurnEcho !== null && currentTurnEcho.messageId !== void 0 && currentTurnEcho.messageId === cancelledId) {
|
|
9963
|
+
appendRender({
|
|
9964
|
+
kind: "turn-complete",
|
|
9965
|
+
stopReason: "cancelled",
|
|
9966
|
+
amended: true
|
|
9967
|
+
});
|
|
9968
|
+
currentTurnEcho = null;
|
|
9969
|
+
amendedMessageIds.delete(cancelledId);
|
|
9970
|
+
}
|
|
9971
|
+
});
|
|
9398
9972
|
const handlePermissionResolved = (update) => {
|
|
9399
9973
|
const u = update ?? {};
|
|
9400
9974
|
const toolCallId = typeof u.toolCallId === "string" ? u.toolCallId : void 0;
|
|
@@ -9502,6 +10076,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9502
10076
|
let upstreamSessionId;
|
|
9503
10077
|
let agentInfoName;
|
|
9504
10078
|
let agentAcceptsImages = true;
|
|
10079
|
+
let daemonSupportsAmend = false;
|
|
9505
10080
|
try {
|
|
9506
10081
|
const initResult = await conn.request("initialize", {
|
|
9507
10082
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
@@ -9516,6 +10091,8 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9516
10091
|
if (imageCap === false) {
|
|
9517
10092
|
agentAcceptsImages = false;
|
|
9518
10093
|
}
|
|
10094
|
+
const hydraMeta = extractHydraMeta(initResult?._meta ?? void 0);
|
|
10095
|
+
daemonSupportsAmend = hydraMeta.promptAmending === true;
|
|
9519
10096
|
} catch {
|
|
9520
10097
|
}
|
|
9521
10098
|
let resolvedSessionId = ctx.sessionId;
|
|
@@ -9916,6 +10493,18 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9916
10493
|
}
|
|
9917
10494
|
return true;
|
|
9918
10495
|
};
|
|
10496
|
+
const buildHelpEntries = () => {
|
|
10497
|
+
const enqueueDesc = "enqueue prompt (sends now, or queues during a turn)";
|
|
10498
|
+
const amendDesc = "amend the in-flight turn (cancel + replace)";
|
|
10499
|
+
const head = config.tui.defaultEnterAction === "amend" ? [
|
|
10500
|
+
["Enter", amendDesc],
|
|
10501
|
+
["Ctrl+Enter / Shift+Enter", enqueueDesc]
|
|
10502
|
+
] : [
|
|
10503
|
+
["Enter", enqueueDesc],
|
|
10504
|
+
["Ctrl+Enter / Shift+Enter", amendDesc]
|
|
10505
|
+
];
|
|
10506
|
+
return [...head, ...HELP_ENTRIES_TAIL];
|
|
10507
|
+
};
|
|
9919
10508
|
const toggleHelpModal = () => {
|
|
9920
10509
|
if (screen.isHelpPromptActive()) {
|
|
9921
10510
|
screen.setHelpPrompt(null);
|
|
@@ -9923,7 +10512,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
9923
10512
|
}
|
|
9924
10513
|
screen.setHelpPrompt({
|
|
9925
10514
|
title: "Hotkeys",
|
|
9926
|
-
entries:
|
|
10515
|
+
entries: buildHelpEntries(),
|
|
9927
10516
|
hint: "any key dismisses \xB7 /help lists commands"
|
|
9928
10517
|
});
|
|
9929
10518
|
};
|
|
@@ -10026,7 +10615,18 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10026
10615
|
const handleEffect = (effect) => {
|
|
10027
10616
|
switch (effect.type) {
|
|
10028
10617
|
case "send":
|
|
10029
|
-
|
|
10618
|
+
if (config.tui.defaultEnterAction === "amend") {
|
|
10619
|
+
amendPrompt(effect.text, effect.attachments);
|
|
10620
|
+
} else {
|
|
10621
|
+
enqueuePrompt(effect.text, effect.attachments);
|
|
10622
|
+
}
|
|
10623
|
+
return;
|
|
10624
|
+
case "amend":
|
|
10625
|
+
if (config.tui.defaultEnterAction === "amend") {
|
|
10626
|
+
enqueuePrompt(effect.text, effect.attachments);
|
|
10627
|
+
} else {
|
|
10628
|
+
amendPrompt(effect.text, effect.attachments);
|
|
10629
|
+
}
|
|
10030
10630
|
return;
|
|
10031
10631
|
case "queue-edit": {
|
|
10032
10632
|
const mid = queueMessageIdAt(effect.index);
|
|
@@ -10234,6 +10834,7 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10234
10834
|
const queueCache = /* @__PURE__ */ new Map();
|
|
10235
10835
|
const pendingEchoes = [];
|
|
10236
10836
|
const ownPendingByMid = /* @__PURE__ */ new Map();
|
|
10837
|
+
const amendedMessageIds = /* @__PURE__ */ new Set();
|
|
10237
10838
|
let currentTurnEcho = null;
|
|
10238
10839
|
const refreshQueueDisplay = () => {
|
|
10239
10840
|
const entries = [...queueCache.values()];
|
|
@@ -10268,6 +10869,75 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10268
10869
|
saveHistory(historyFile, history).catch(() => void 0);
|
|
10269
10870
|
void runPrompt(text, attachments);
|
|
10270
10871
|
};
|
|
10872
|
+
const amendPrompt = (text, attachments) => {
|
|
10873
|
+
screen.scrollToBottom();
|
|
10874
|
+
if (handleBuiltinCommand(text)) {
|
|
10875
|
+
return;
|
|
10876
|
+
}
|
|
10877
|
+
history = appendEntry(history, text);
|
|
10878
|
+
dispatcher.setHistory(history);
|
|
10879
|
+
saveHistory(historyFile, history).catch(() => void 0);
|
|
10880
|
+
if (!daemonSupportsAmend || currentHeadMessageId === void 0) {
|
|
10881
|
+
void runPrompt(text, attachments);
|
|
10882
|
+
return;
|
|
10883
|
+
}
|
|
10884
|
+
const target = currentHeadMessageId;
|
|
10885
|
+
const blocks = [];
|
|
10886
|
+
if (text.length > 0) {
|
|
10887
|
+
blocks.push({ type: "text", text });
|
|
10888
|
+
}
|
|
10889
|
+
for (const a of attachments) {
|
|
10890
|
+
blocks.push({ type: "image", data: a.data, mimeType: a.mimeType });
|
|
10891
|
+
}
|
|
10892
|
+
const echo = { text, attachments, flushed: false };
|
|
10893
|
+
pendingEchoes.push(echo);
|
|
10894
|
+
const popEcho = () => {
|
|
10895
|
+
const idx = pendingEchoes.indexOf(echo);
|
|
10896
|
+
if (idx >= 0) {
|
|
10897
|
+
pendingEchoes.splice(idx, 1);
|
|
10898
|
+
}
|
|
10899
|
+
if (echo.messageId !== void 0) {
|
|
10900
|
+
ownPendingByMid.delete(echo.messageId);
|
|
10901
|
+
}
|
|
10902
|
+
};
|
|
10903
|
+
conn.request("hydra-acp/amend_prompt", {
|
|
10904
|
+
sessionId: resolvedSessionId,
|
|
10905
|
+
targetMessageId: target,
|
|
10906
|
+
prompt: blocks
|
|
10907
|
+
}).then((raw) => {
|
|
10908
|
+
const res = raw;
|
|
10909
|
+
if (res.amended && res.reason === "ok") {
|
|
10910
|
+
adjustPendingTurns(1);
|
|
10911
|
+
return;
|
|
10912
|
+
}
|
|
10913
|
+
popEcho();
|
|
10914
|
+
if (res.reason === "target_completed") {
|
|
10915
|
+
screen.notify(
|
|
10916
|
+
"previous response finished \u2014 press Enter to send as a new turn"
|
|
10917
|
+
);
|
|
10918
|
+
dispatcher.setBuffer(text, attachments);
|
|
10919
|
+
screen.refreshPrompt();
|
|
10920
|
+
return;
|
|
10921
|
+
}
|
|
10922
|
+
if (res.reason === "target_cancelled") {
|
|
10923
|
+
screen.notify("amend skipped \u2014 previous turn was cancelled");
|
|
10924
|
+
dispatcher.setBuffer(text, attachments);
|
|
10925
|
+
screen.refreshPrompt();
|
|
10926
|
+
return;
|
|
10927
|
+
}
|
|
10928
|
+
if (res.reason === "target_not_found") {
|
|
10929
|
+
screen.notify("amend skipped \u2014 no matching prompt");
|
|
10930
|
+
dispatcher.setBuffer(text, attachments);
|
|
10931
|
+
screen.refreshPrompt();
|
|
10932
|
+
return;
|
|
10933
|
+
}
|
|
10934
|
+
}).catch((err) => {
|
|
10935
|
+
popEcho();
|
|
10936
|
+
screen.notify(`amend failed: ${err.message}`);
|
|
10937
|
+
dispatcher.setBuffer(text, attachments);
|
|
10938
|
+
screen.refreshPrompt();
|
|
10939
|
+
});
|
|
10940
|
+
};
|
|
10271
10941
|
const handleModeToggle = async (_on) => {
|
|
10272
10942
|
if (agentModes.length === 0) {
|
|
10273
10943
|
screen.notify("no modes advertised by agent");
|
|
@@ -10492,9 +11162,18 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10492
11162
|
turnInFlight = null;
|
|
10493
11163
|
adjustPendingTurns(-1);
|
|
10494
11164
|
if (echo.flushed && currentTurnEcho === echo) {
|
|
10495
|
-
|
|
10496
|
-
|
|
10497
|
-
|
|
11165
|
+
const wasAmended = echo.messageId !== void 0 && amendedMessageIds.has(echo.messageId);
|
|
11166
|
+
if (wasAmended && echo.messageId !== void 0) {
|
|
11167
|
+
amendedMessageIds.delete(echo.messageId);
|
|
11168
|
+
}
|
|
11169
|
+
const tc = { kind: "turn-complete" };
|
|
11170
|
+
if (stopReason !== void 0) {
|
|
11171
|
+
tc.stopReason = stopReason;
|
|
11172
|
+
}
|
|
11173
|
+
if (wasAmended) {
|
|
11174
|
+
tc.amended = true;
|
|
11175
|
+
}
|
|
11176
|
+
appendRender(tc);
|
|
10498
11177
|
currentTurnEcho = null;
|
|
10499
11178
|
}
|
|
10500
11179
|
if (pendingPrefill !== null) {
|
|
@@ -10737,8 +11416,10 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10737
11416
|
screen.appendLines(formatted);
|
|
10738
11417
|
}
|
|
10739
11418
|
if (event.kind === "turn-complete") {
|
|
11419
|
+
currentHeadMessageId = void 0;
|
|
10740
11420
|
closeAgentText();
|
|
10741
|
-
|
|
11421
|
+
const effectiveStopReason = event.amended ? "amended" : event.stopReason;
|
|
11422
|
+
if (lastPlanEvent !== null && effectiveStopReason !== void 0 && effectiveStopReason !== "end_turn") {
|
|
10742
11423
|
const lines = formatEvent({ ...lastPlanEvent, stopped: true });
|
|
10743
11424
|
if (lines.length > 0) {
|
|
10744
11425
|
screen.upsertLines("plan", lines);
|
|
@@ -10748,15 +11429,15 @@ async function runSession(term, config, serviceToken, opts, exitHint) {
|
|
|
10748
11429
|
screen.clearKey("plan");
|
|
10749
11430
|
if (toolsBlockStartedAt !== null) {
|
|
10750
11431
|
toolsBlockEndedAt = Date.now();
|
|
10751
|
-
toolsBlockStopReason =
|
|
11432
|
+
toolsBlockStopReason = effectiveStopReason ?? null;
|
|
10752
11433
|
renderToolsBlock();
|
|
10753
11434
|
screen.clearKey("tools");
|
|
10754
|
-
} else if (
|
|
11435
|
+
} else if (effectiveStopReason !== void 0 && effectiveStopReason !== "end_turn") {
|
|
10755
11436
|
screen.appendLines([
|
|
10756
11437
|
{
|
|
10757
11438
|
prefix: "\u26A0 ",
|
|
10758
11439
|
prefixStyle: "tool-status-fail",
|
|
10759
|
-
body: `turn ended: ${
|
|
11440
|
+
body: `turn ended: ${effectiveStopReason}`,
|
|
10760
11441
|
bodyStyle: "tool-status-fail"
|
|
10761
11442
|
}
|
|
10762
11443
|
]);
|
|
@@ -11014,7 +11695,7 @@ function rotateIfBig(target) {
|
|
|
11014
11695
|
} catch {
|
|
11015
11696
|
}
|
|
11016
11697
|
}
|
|
11017
|
-
var
|
|
11698
|
+
var HELP_ENTRIES_TAIL, logMaxBytes;
|
|
11018
11699
|
var init_app = __esm({
|
|
11019
11700
|
"src/tui/app.ts"() {
|
|
11020
11701
|
"use strict";
|
|
@@ -11038,8 +11719,7 @@ var init_app = __esm({
|
|
|
11038
11719
|
init_completion();
|
|
11039
11720
|
init_render_update();
|
|
11040
11721
|
init_format();
|
|
11041
|
-
|
|
11042
|
-
["Enter", "send prompt (or queue while a turn is running)"],
|
|
11722
|
+
HELP_ENTRIES_TAIL = [
|
|
11043
11723
|
["Alt+Enter", "newline in prompt"],
|
|
11044
11724
|
["Shift+Tab", "cycle agent modes (plan / accept-edits / etc.)"],
|
|
11045
11725
|
["Tab", "indent \xB7 slash-command completion"],
|
|
@@ -14711,6 +15391,22 @@ function registerAcpWsEndpoint(app, deps) {
|
|
|
14711
15391
|
}
|
|
14712
15392
|
return session.updateQueuedPrompt(params.messageId, params.prompt);
|
|
14713
15393
|
});
|
|
15394
|
+
connection.onRequest("hydra-acp/amend_prompt", async (raw) => {
|
|
15395
|
+
const params = AmendPromptParams.parse(raw);
|
|
15396
|
+
const att = state.attached.get(params.sessionId);
|
|
15397
|
+
if (!att) {
|
|
15398
|
+
const err = new Error("not attached to session");
|
|
15399
|
+
err.code = JsonRpcErrorCodes.SessionNotFound;
|
|
15400
|
+
throw err;
|
|
15401
|
+
}
|
|
15402
|
+
const session = deps.manager.get(params.sessionId);
|
|
15403
|
+
if (!session) {
|
|
15404
|
+
const err = new Error(`session ${params.sessionId} not found`);
|
|
15405
|
+
err.code = JsonRpcErrorCodes.SessionNotFound;
|
|
15406
|
+
throw err;
|
|
15407
|
+
}
|
|
15408
|
+
return session.amendPrompt(att.clientId, params);
|
|
15409
|
+
});
|
|
14714
15410
|
connection.onRequest("session/load", async (raw) => {
|
|
14715
15411
|
const rawObj = raw ?? {};
|
|
14716
15412
|
const rawSessionId = typeof rawObj.sessionId === "string" ? rawObj.sessionId : void 0;
|
|
@@ -14863,10 +15559,17 @@ function buildInitializeResult() {
|
|
|
14863
15559
|
],
|
|
14864
15560
|
// Advertise hydra-only capabilities via _meta["hydra-acp"]. Generic
|
|
14865
15561
|
// ACP clients ignore the field; capability-aware clients learn here
|
|
14866
|
-
//
|
|
14867
|
-
//
|
|
14868
|
-
//
|
|
14869
|
-
|
|
15562
|
+
// which hydra-acp extensions the daemon supports so they can gate
|
|
15563
|
+
// UI surface accordingly. promptPipelining is false until the
|
|
15564
|
+
// streaming-input probe lands (Option A in the steering brief);
|
|
15565
|
+
// the others are unconditional method-availability flags.
|
|
15566
|
+
_meta: mergeMeta(void 0, {
|
|
15567
|
+
promptQueueing: true,
|
|
15568
|
+
promptCancelling: true,
|
|
15569
|
+
promptUpdating: true,
|
|
15570
|
+
promptAmending: true,
|
|
15571
|
+
promptPipelining: false
|
|
15572
|
+
})
|
|
14870
15573
|
};
|
|
14871
15574
|
}
|
|
14872
15575
|
function bindClientToSession(connection, session, state, clientInfo, callerClientId) {
|