@songsid/agend 2.3.2-beta.10 → 2.3.2-beta.12

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.
Files changed (132) hide show
  1. package/README.md +4 -0
  2. package/README.zh-TW.md +4 -0
  3. package/dist/agent-cli.js +48 -0
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.js +6 -0
  6. package/dist/agent-endpoint.js.map +1 -1
  7. package/dist/agent-whats-new.js +3 -0
  8. package/dist/agent-whats-new.js.map +1 -1
  9. package/dist/backend/kiro-classic-archive.d.ts +52 -0
  10. package/dist/backend/kiro-classic-archive.js +444 -0
  11. package/dist/backend/kiro-classic-archive.js.map +1 -0
  12. package/dist/backend/kiro-identity.d.ts +19 -0
  13. package/dist/backend/kiro-identity.js +43 -0
  14. package/dist/backend/kiro-identity.js.map +1 -1
  15. package/dist/backend/kiro-migration-verify.d.ts +10 -0
  16. package/dist/backend/kiro-migration-verify.js +52 -0
  17. package/dist/backend/kiro-migration-verify.js.map +1 -0
  18. package/dist/backend/kiro-v2-store-worker.js +21 -1
  19. package/dist/backend/kiro-v2-store-worker.js.map +1 -1
  20. package/dist/backend/kiro-v2-store.d.ts +8 -0
  21. package/dist/backend/kiro-v2-store.js.map +1 -1
  22. package/dist/backend/kiro.d.ts +99 -1
  23. package/dist/backend/kiro.js +352 -18
  24. package/dist/backend/kiro.js.map +1 -1
  25. package/dist/backend/types.d.ts +49 -0
  26. package/dist/backend/types.js +9 -0
  27. package/dist/backend/types.js.map +1 -1
  28. package/dist/channel/ipc-timeouts.js +3 -0
  29. package/dist/channel/ipc-timeouts.js.map +1 -1
  30. package/dist/channel/mcp-tools.js +12 -0
  31. package/dist/channel/mcp-tools.js.map +1 -1
  32. package/dist/cli.js +33 -0
  33. package/dist/cli.js.map +1 -1
  34. package/dist/config-validator.js +2 -0
  35. package/dist/config-validator.js.map +1 -1
  36. package/dist/config.js +2 -0
  37. package/dist/config.js.map +1 -1
  38. package/dist/daemon.d.ts +45 -0
  39. package/dist/daemon.js +342 -19
  40. package/dist/daemon.js.map +1 -1
  41. package/dist/dashboard-permissions.d.ts +15 -4
  42. package/dist/dashboard-permissions.js +49 -23
  43. package/dist/dashboard-permissions.js.map +1 -1
  44. package/dist/fleet-control-audit.d.ts +1 -1
  45. package/dist/fleet-control-audit.js +3 -0
  46. package/dist/fleet-control-audit.js.map +1 -1
  47. package/dist/fleet-manager.d.ts +61 -2
  48. package/dist/fleet-manager.js +421 -28
  49. package/dist/fleet-manager.js.map +1 -1
  50. package/dist/general-knowledge/skills/docker-install/SKILL.md +25 -0
  51. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +10 -0
  52. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +28 -0
  53. package/dist/general-knowledge/skills/web-appearance/SKILL.md +28 -0
  54. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +20 -2
  55. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +20 -2
  56. package/dist/instance-config-impact.js +2 -2
  57. package/dist/instance-config-impact.js.map +1 -1
  58. package/dist/instance-lifecycle.d.ts +4 -0
  59. package/dist/instance-lifecycle.js +7 -0
  60. package/dist/instance-lifecycle.js.map +1 -1
  61. package/dist/kiro-migrate.d.ts +11 -0
  62. package/dist/kiro-migrate.js +86 -31
  63. package/dist/kiro-migrate.js.map +1 -1
  64. package/dist/kiro-session-proof.d.ts +41 -0
  65. package/dist/kiro-session-proof.js +89 -0
  66. package/dist/kiro-session-proof.js.map +1 -0
  67. package/dist/kiro-tui-forensic.d.ts +5 -0
  68. package/dist/kiro-tui-forensic.js +3 -3
  69. package/dist/kiro-tui-forensic.js.map +1 -1
  70. package/dist/locale.js +142 -0
  71. package/dist/locale.js.map +1 -1
  72. package/dist/log-service.js +1 -1
  73. package/dist/log-service.js.map +1 -1
  74. package/dist/needs-you.d.ts +3 -1
  75. package/dist/needs-you.js +4 -2
  76. package/dist/needs-you.js.map +1 -1
  77. package/dist/office-share.d.ts +12 -2
  78. package/dist/office-share.js +27 -2
  79. package/dist/office-share.js.map +1 -1
  80. package/dist/outbound-schemas.d.ts +39 -0
  81. package/dist/outbound-schemas.js +29 -0
  82. package/dist/outbound-schemas.js.map +1 -1
  83. package/dist/remote-card-prompt.d.ts +38 -0
  84. package/dist/remote-card-prompt.js +86 -0
  85. package/dist/remote-card-prompt.js.map +1 -0
  86. package/dist/settings-change.js +8 -5
  87. package/dist/settings-change.js.map +1 -1
  88. package/dist/settings-instance-patch.js +1 -1
  89. package/dist/settings-instance-patch.js.map +1 -1
  90. package/dist/tool-permissions.js +17 -0
  91. package/dist/tool-permissions.js.map +1 -1
  92. package/dist/transcript-sources.d.ts +1 -0
  93. package/dist/transcript-sources.js +2 -1
  94. package/dist/transcript-sources.js.map +1 -1
  95. package/dist/types.d.ts +5 -0
  96. package/dist/ui/chat-store.js +3 -2
  97. package/dist/ui/chat-strings.js +2 -2
  98. package/dist/ui/fleet-office-editor.js +54 -2
  99. package/dist/ui/fleet-office-layout.js +44 -7
  100. package/dist/ui/fleet-office-pack.d.ts +9 -3
  101. package/dist/ui/fleet-office-pack.js +117 -19
  102. package/dist/ui/fleet-office-store.js +43 -0
  103. package/dist/ui/fleet-office-zones.js +92 -17
  104. package/dist/ui/fleet-office.js +102 -16
  105. package/dist/ui/panel-chat.js +14 -2
  106. package/dist/ui/panel-log.js +2 -2
  107. package/dist/ui/panel-needs.js +1 -1
  108. package/dist/ui/panel-settings.js +5 -5
  109. package/dist/ui/settings-model.js +17 -9
  110. package/dist/ui/settings-strings.js +2 -0
  111. package/dist/ui/shared/app-i18n.js +2 -2
  112. package/dist/ui/shared/app-stream.js +1 -1
  113. package/dist/ui/shared/app.css +18 -2
  114. package/dist/web-api.d.ts +5 -1
  115. package/dist/web-api.js +91 -3
  116. package/dist/web-api.js.map +1 -1
  117. package/dist/web-office-acts.js +2 -2
  118. package/dist/web-office-acts.js.map +1 -1
  119. package/dist/web-office-pack-read.d.ts +35 -0
  120. package/dist/web-office-pack-read.js +252 -0
  121. package/dist/web-office-pack-read.js.map +1 -0
  122. package/dist/web-office-pack-tools.d.ts +232 -0
  123. package/dist/web-office-pack-tools.js +754 -0
  124. package/dist/web-office-pack-tools.js.map +1 -0
  125. package/dist/web-office-skins.d.ts +68 -4
  126. package/dist/web-office-skins.js +318 -33
  127. package/dist/web-office-skins.js.map +1 -1
  128. package/dist/web-office-store.d.ts +21 -2
  129. package/dist/web-office-store.js +78 -18
  130. package/dist/web-office-store.js.map +1 -1
  131. package/package.json +1 -1
  132. package/templates/windows-shortcuts.ps1 +16 -15
@@ -5,14 +5,16 @@ import { homedir } from "node:os";
5
5
  import { getAgendHome } from "../paths.js";
6
6
  import { credentialHomeSpec, credentialProfileHome, credentialProfileStoreHome, prepareCredentialProfileHome, resolveCredentialProfile, } from "./credential-profile.js";
7
7
  import { kiroAgentName, kiroFleetTag, writeKiroAgent, removeKiroAgent, writeSharedKiroMcpEntries, removeSharedKiroMcpEntries, writeTaggedKiroSteering, removeTaggedKiroSteering, } from "./kiro-agent.js";
8
- import { resolveKiroIdentity, listKiroV1Sessions, listKiroV2Sessions, confirmKiroAgentSwitch, kiroAgentConfirmed, forgetKiroIdentity, kiroIdentityNeedsStore, kiroRecordedConversation, kiroCanonicalDirectory, KiroIdentityError, kiroEngineFloor, } from "./kiro-identity.js";
8
+ import { resolveKiroIdentity, listKiroV1Sessions, listKiroV2Sessions, confirmKiroAgentSwitch, kiroAgentConfirmed, forgetKiroIdentity, kiroIdentityNeedsStore, kiroRecordedConversation, kiroCanonicalDirectory, KiroIdentityError, kiroEngineFloor, kiroCarryCandidate, } from "./kiro-identity.js";
9
9
  import { readKiroLedger, kiroHasNoLaunchRecord } from "./kiro-engine-ledger.js";
10
10
  import { execFileSync } from "node:child_process";
11
11
  import { writeFileSync, chmodSync, statSync } from "node:fs";
12
- import { UnsupportedCliError, resolveBinary, shellQuote, validateEffort, validateModel, warnIfModelMismatch } from "./types.js";
12
+ import { UnsupportedCliError, ResumeBlockedError, resolveBinary, shellQuote, validateEffort, validateModel, warnIfModelMismatch } from "./types.js";
13
+ import { KIRO_SESSION_ID_COMMAND, kiroTitleCommand } from "../kiro-session-proof.js";
13
14
  import { PIE_CLASS } from "../tui-glyphs.js";
14
15
  import { KIRO_EXPIRED_LOGIN_SCREEN } from "../login-flows.js";
15
16
  import { t } from "../locale.js";
17
+ import { sharedKiroV2StoreLane } from "./kiro-v2-store.js";
16
18
  import { decideKiroModelError, readKiroKnownModels, readKiroModelRecords, readKiroV1SavedModel, readKiroV2SavedModel, writeKiroModelRecords, KIRO_FALLBACK_MODEL } from "./kiro-retired-model.js";
17
19
  import { recordKiroLaunch } from "./kiro-engine-ledger.js";
18
20
  import { forgetKiroV3Identity, resolveKiroV3Resume } from "./kiro-v3-identity.js";
@@ -73,8 +75,10 @@ export const KIRO_STEER_VERIFIED = {
73
75
  * evidence of either an empty or a full box. The toggle key's label is whatever the user bound; it is read past, never
74
76
  * pressed.
75
77
  */
76
- function readKiroComposer(pane) {
77
- const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
78
+ /** #2682: the takeover hold's key (RuntimeDialog.pattern); kiroTakeoverActive decides, this never matches a pane. */
79
+ const KIRO_TAKEOVER_KEY = /(?!)kiro-takeover/;
80
+ /** The composer's `›` row in `rows` and how many typed-text rows continue below it; null when there is none. */
81
+ function findKiroComposerRow(rows) {
78
82
  let i = rows.length - 1;
79
83
  while (i >= 0 && (rows[i] === "" || /^[ \t]{20,}\S/.test(rows[i])))
80
84
  i--;
@@ -86,30 +90,47 @@ function readKiroComposer(pane) {
86
90
  continued++;
87
91
  }
88
92
  const row = rows[i];
89
- if (row === undefined || !/^›[ \t]+\S/.test(row))
93
+ return row !== undefined && /^›[ \t]+\S/.test(row) ? { index: i, continued } : null;
94
+ }
95
+ function readKiroComposer(pane) {
96
+ const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
97
+ const at = findKiroComposerRow(rows);
98
+ if (!at)
90
99
  return null;
100
+ let row = rows[at.index];
91
101
  // A placeholder is one row: with continuation rows below it, this is typed text — unless its first row is another
92
- // mode's placeholder, which is then not ours to read.
93
- if (continued > 0) {
94
- if (/^›[ \t]+(?:Kiro is working\b|ask a question or describe a task\b|Goal (?:Active|Paused):|Editing queued message \d|Initializing\b|describe what "|running shell command\b)/.test(row))
95
- return null;
96
- return { mode: "text", text: row.replace(/^›[ \t]+/, "") };
102
+ // mode's placeholder. #2682: a narrow pane word-wraps a placeholder onto indented rows; joined, a COMPLETE known form
103
+ // reads as itself (an idle placeholder is then idle, not unreadable); anything else stays unreadable.
104
+ if (at.continued > 0) {
105
+ if (!/^›[ \t]+(?:Kiro is working\b|ask a question or describe a task\b|Goal (?:Active|Paused):|Editing queued message \d|Initializing\b|describe what "|running shell command\b)/.test(row)) {
106
+ return { mode: "text", text: row.replace(/^›[ \t]+/, "") };
107
+ }
108
+ row = [row, ...rows.slice(at.index + 1, at.index + 1 + at.continued).map(r => r.trim())].join(" ");
109
+ const mode = kiroPlaceholderMode(row);
110
+ return mode ? { mode, text: row.replace(/^›[ \t]+/, "") } : null;
97
111
  }
98
- const working = String.raw `^›[ \t]+Kiro is working(?:[ \t]+·[ \t]+[^·]+?)?[ \t]+·[ \t]+`;
99
112
  const text = row.replace(/^›[ \t]+/, "");
113
+ const mode = kiroPlaceholderMode(row);
114
+ if (mode)
115
+ return { mode, text };
116
+ // Other placeholders (and anything the ASCII glyph set paints) are not typed text.
117
+ if (/^›[ \t]+(?:Kiro is working\b|Goal (?:Active|Paused):|Editing queued message \d|Initializing\b|describe what "|running shell command\b|ask a question or describe a task (?:\.|enter\b))/.test(row))
118
+ return null;
119
+ return { mode: "text", text };
120
+ }
121
+ /** The empty composer's mode from its whole placeholder (`› …`, one row or wrapped rows joined); null when it is none. */
122
+ function kiroPlaceholderMode(row) {
123
+ const working = String.raw `^›[ \t]+Kiro is working(?:[ \t]+·[ \t]+[^·]+?)?[ \t]+·[ \t]+`;
100
124
  if (new RegExp(`${working}Type to steer[ \\t]+·[ \\t]+[^·]+?[ \\t]+to queue$`).test(row))
101
- return { mode: "steer", text };
125
+ return "steer";
102
126
  if (new RegExp(`${working}Type to queue(?:[ \\t]+·[ \\t]+[^·]+?[ \\t]+to steer)?$`).test(row))
103
- return { mode: "queue", text };
127
+ return "queue";
104
128
  const idle = String.raw `^›[ \t]+ask a question or describe a task`;
105
129
  if (new RegExp(`${idle}[ \\t]+↵$`).test(row)
106
130
  || new RegExp(`${idle}[ \\t]+↵[ \\t]+·[ \\t]+exit plan mode: shift\\+tab$`).test(row)
107
131
  || new RegExp(`${idle}[ \\t]+·[ \\t]+/tangent to go back[ \\t]+·[ \\t]+/tangent ls to view$`).test(row))
108
- return { mode: "idle", text };
109
- // Other placeholders (and anything the ASCII glyph set paints) are not typed text.
110
- if (/^›[ \t]+(?:Kiro is working\b|Goal (?:Active|Paused):|Editing queued message \d|Initializing\b|describe what "|running shell command\b|ask a question or describe a task (?:\.|enter\b))/.test(row))
111
- return null;
112
- return { mode: "text", text };
132
+ return "idle";
133
+ return null;
113
134
  }
114
135
  export function readKiroSteerComposer(pane) {
115
136
  return readKiroComposer(pane)?.mode ?? null;
@@ -119,6 +140,178 @@ export function readKiroComposerText(pane) {
119
140
  const c = readKiroComposer(pane);
120
141
  return c?.mode === "text" ? c.text : null;
121
142
  }
143
+ /**
144
+ * #2683: the composer as a one-line housekeeping command (`/session-id`, `/title …`) sees it: `""` when it is
145
+ * positively empty (the idle placeholder, nothing else), the typed text of a single-row draft, or null for anything
146
+ * else (busy, another mode, wrapped text, no composer). A typed slash command opens kiro's command menu below the box
147
+ * (live 2.34.0, tui and V3; tests/fixtures/kiro-session-id-2681):
148
+ *
149
+ * `› /session-id`
150
+ * `/session-id Print the current session ID`
151
+ * `──────…`
152
+ * ` esc to cancel · ↵ to select`
153
+ *
154
+ * and its `›` row is read past that menu. Enter there runs the highlighted entry, the command itself.
155
+ */
156
+ export function readKiroCommandComposer(pane) {
157
+ const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
158
+ let i = rows.length - 1;
159
+ while (i >= 0 && (rows[i] === "" || /^[ \t]{20,}\S/.test(rows[i])))
160
+ i--;
161
+ if (i >= 2 && /^ esc to cancel · .*↵ to select$/.test(rows[i]) && /^─{20,}$/.test(rows[i - 1])) {
162
+ let r = i - 2, entries = 0;
163
+ while (r >= 0 && entries < 12 && /^\/[\w-]+ {2,}\S/.test(rows[r])) {
164
+ r--;
165
+ entries++;
166
+ }
167
+ const row = rows[r];
168
+ return entries > 0 && row !== undefined && /^›[ \t]+\/\S/.test(row) ? row.replace(/^›[ \t]+/, "") : null;
169
+ }
170
+ const at = findKiroComposerRow(rows);
171
+ if (!at || at.continued > 0)
172
+ return null;
173
+ const c = readKiroComposer(pane);
174
+ return c?.mode === "idle" ? "" : c?.mode === "text" ? c.text : null;
175
+ }
176
+ /**
177
+ * #2683: kiro's own answer to `/session-id`, where only that answer is painted: the alert at the bottom of the chat,
178
+ * on the rows right above the footer (the divider within 8 rows above the composer, and the trust banner's divider
179
+ * above that one when it is shown). Live 2.34.0, tui and V3 (tests/fixtures/kiro-session-id-2681):
180
+ *
181
+ * `• Session ID: <id>`
182
+ * ` Resume with: kiro-cli --resume-id <id>` (the same id)
183
+ *
184
+ * or `• Session ID: none` alone. It is a transient alert (10 s), not a turn. The same lines anywhere else (a quoted
185
+ * user row is ` › …`, an answer scrolls up) are not read; an agent answer that ends with them would sit in the same
186
+ * place, which is why the daemon also requires that the alert was not there before it typed the command. Null when no
187
+ * such alert is shown.
188
+ */
189
+ export function readKiroSessionIdAlert(pane) {
190
+ const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
191
+ const at = findKiroComposerRow(rows);
192
+ if (!at)
193
+ return null;
194
+ let d = at.index - 1;
195
+ while (d >= 0 && at.index - d <= 8 && !/^─{20,}$/.test(rows[d]))
196
+ d--;
197
+ if (d < 0 || !/^─{20,}$/.test(rows[d]))
198
+ return null;
199
+ if (d >= 2 && /^ \S/.test(rows[d - 1]) && /^─{20,}$/.test(rows[d - 2]))
200
+ d -= 2; // ` Trust All Tools active …`
201
+ if (rows[d - 1] === "• Session ID: none")
202
+ return "none";
203
+ const id = /^ {2}Resume with: kiro-cli --resume-id ([A-Za-z0-9][A-Za-z0-9_.-]{0,199})$/.exec(rows[d - 1] ?? "")?.[1];
204
+ return id !== undefined && id !== "none" && rows[d - 2] === `• Session ID: ${id}` ? id : null;
205
+ }
206
+ /**
207
+ * #2676: a collapsed paste on the composer's row. The TUI shows a paste of more than 10 lines or 500 characters as one
208
+ * chip (2.34.0 tui.js: `n>1 ? "${n} lines" : "${r} chars"`, then `▸`, then the `Press Tab to expand` hint):
209
+ *
210
+ * `› 42 lines ▸ Press Tab to expand` → 42
211
+ * `› 657 chars ▸ Press Tab to expand` → 1 (one line, shown only by its length)
212
+ *
213
+ * `text` is the row's text (readKiroComposerText). Null when it is not such a chip.
214
+ */
215
+ export function readKiroCollapsedPasteLines(text) {
216
+ const m = /^(\d+) (lines|chars) ▸(?:[ \t]+Press Tab to expand)?$/.exec(text.trim());
217
+ if (!m)
218
+ return null;
219
+ return m[2] === "lines" ? Number(m[1]) : 1;
220
+ }
221
+ /**
222
+ * #2682: another program owns kiro's pane, so a paste and an Enter would go into it instead of the chat (captured on
223
+ * 2.34.0, tests/fixtures/kiro-2.34.0-takeover-2682). Read from kiro's own rows first:
224
+ * - a focused `/kvim` panel: `── Neovim · focused · Ctrl+\ to chat · :q to close ──`, or `· the agent is waiting on you:`
225
+ * (unfocused, `Neovim · Ctrl+\ to edit`, the composer has the keys again);
226
+ * - a running `!` command (tui, V3): its PTY takes every keystroke, while the composer still reads busy
227
+ * (`Kiro is working · Type to steer`) — the nearest turn above kiro's footer is ` › !…`, with only its indented
228
+ * output and `• Running...` below (kiroBusyTurn; a turn it cannot see is not called a takeover here, but no busy
229
+ * input is typed into it either: KiroBackend.busyTurnTakesInput);
230
+ * - an editor or pager kiro started (`/editor`, `/transcript`, a full-screen `!vim`/`!less`): no composer row, kiro's
231
+ * temp file `kiro-editor-XXXXXX/prompt.md` or `kiro-raw-XXXXXX/conversation.md` on screen;
232
+ * - legacy: a `!` command running from its prompt row (the bottom row is the prompt with `!…` after the marker: the
233
+ * command took the terminal before kiro painted anything else), and its `/editor` and `/reply` temp file
234
+ * `q_prompt_<uuid>.md` with no prompt row at the bottom;
235
+ * and, only where kiro's input row is not on screen (every engine; legacy's `/editor` and `/reply` run `$EDITOR`), the
236
+ * program's own bottom rows: vi/vim/nvim (`~` rows, its ruler, `-- INSERT --`), less (`:`, `(END)`), more
237
+ * (`--More--`), nano (`GNU nano` and `^X Exit`). `/fullscreen` is not a takeover: the same composer takes input.
238
+ */
239
+ export function kiroTakeoverActive(pane, ui) {
240
+ const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
241
+ if (ui === "legacy") {
242
+ const prompt = kiroLegacyPromptAtBottom(rows);
243
+ if (prompt !== null)
244
+ return prompt.startsWith("!");
245
+ if (rows.some(row => /\/q_prompt_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.md\b/.test(row)))
246
+ return true;
247
+ return fullScreenProgramAtBottom(rows);
248
+ }
249
+ if (rows.some(row => /^─+ Neovim · (?:focused · |the agent is waiting on you: )/.test(row)))
250
+ return true;
251
+ const at = findKiroComposerRow(rows);
252
+ if (at) {
253
+ const mode = readKiroComposer(pane)?.mode;
254
+ return (mode === "steer" || mode === "queue") && kiroBusyTurn(rows, at.index) === "shell";
255
+ }
256
+ if (rows.some(row => /\/kiro-(?:editor|raw)-[A-Za-z0-9]{6}\/(?:prompt|conversation)\.md\b/.test(row)))
257
+ return true;
258
+ return fullScreenProgramAtBottom(rows);
259
+ }
260
+ /**
261
+ * #2682: the legacy UI's prompt row as the last non-blank row — `4% !>` (trust-all) or `4% >`, with an optional
262
+ * `[agent]` prefix and mode glyph, as getBottomReadyPattern — and the text after its marker (a placeholder hint, a
263
+ * draft, or a `!` command it is running); null when the bottom row is not the prompt.
264
+ */
265
+ export function kiroLegacyPromptAtBottom(rows) {
266
+ const last = [...rows].reverse().find(row => row.trim() !== "");
267
+ const m = last === undefined ? null : /^\s*(?:\[[^\]]*\]\s*)?\d+%\s*(?:[^\s\d%!❯>]{1,2}\s+)?(?:!\s?[❯>]|❯|>)(.*)$/.exec(last);
268
+ return m ? m[1].trim() : null;
269
+ }
270
+ /**
271
+ * #2682: what the busy turn nearest above kiro's footer is: "shell" — a `!` command still running (its ` › !` row with
272
+ * only its output and `• Running...` below), "agent" — positive evidence of the agent's turn first (another user row,
273
+ * one of kiro's bullets, its spinner), "unknown" — no footer, or nothing that says either up to the top of the pane.
274
+ * Everything else is passed over, never taken as evidence: the footer and its notes however they wrap, dividers, the
275
+ * tray and tips, indented output.
276
+ */
277
+ function kiroBusyTurn(rows, composer) {
278
+ let d = composer - 1;
279
+ while (d >= 0 && composer - d <= 8 && !/^─{20,}$/.test(rows[d]))
280
+ d--;
281
+ if (d < 0 || !/^─{20,}$/.test(rows[d]))
282
+ return "unknown"; // no footer divider within reach
283
+ for (let r = d - 1; r >= 0; r--) {
284
+ const row = rows[r];
285
+ if (/^ {2}› !/.test(row))
286
+ return "shell";
287
+ if (/^ {2}› /.test(row) || /^• (?!Running\.\.\.$)\S/.test(row) || /^\S[ \t]+(?:Thinking|Working)(?:\.{3}|…)/.test(row))
288
+ return "agent";
289
+ }
290
+ return "unknown";
291
+ }
292
+ /**
293
+ * #2682: whether a busy composer's typed input goes to kiro's own turn: only when that turn is seen to be the agent's.
294
+ * A `!` command's PTY would take it, and a turn it cannot see might be one.
295
+ */
296
+ export function kiroBusyTurnTakesInput(pane) {
297
+ const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
298
+ const at = findKiroComposerRow(rows);
299
+ return at !== null && kiroBusyTurn(rows, at.index) === "agent";
300
+ }
301
+ /** #2682: a full-screen editor or pager's own bottom rows (vi/vim/nvim, less, more, nano), whoever started it. */
302
+ function fullScreenProgramAtBottom(rows) {
303
+ const shown = rows.filter(row => row.trim() !== "");
304
+ const last = shown[shown.length - 1]?.trim() ?? "";
305
+ if (last === ":" || /\(END\)$/.test(last) || /^--More--/.test(last))
306
+ return true; // less, more
307
+ if (/^-- (?:INSERT|REPLACE|VISUAL(?: LINE| BLOCK)?) --/.test(last))
308
+ return true; // vi modes
309
+ if (/\s\d+,\d+(?:-\d+)?\s+(?:All|Top|Bot|\d+%)$/.test(last))
310
+ return true; // vi ruler
311
+ if (shown.slice(-7, -1).filter(row => row === "~").length >= 3)
312
+ return true; // vi's empty lines
313
+ return shown.slice(-2).some(row => /\^X Exit/.test(row)) && rows.some(row => /^\s*GNU nano\b/.test(row)); // nano
314
+ }
122
315
  const UNKNOWN_KIRO_COMPATIBILITY = {
123
316
  supportsLegacyUi: false,
124
317
  supportsTui: false,
@@ -974,6 +1167,34 @@ export function readActiveKiroAgent(pane, ui) {
974
1167
  }
975
1168
  return null;
976
1169
  }
1170
+ /** Model on Kiro 2.34's live footer (status, cwd, optional branch, blank, idle composer).
1171
+ * Separate from readActiveKiroAgent: do not widen the disabled agent-switch action's reader.
1172
+ */
1173
+ export function readActiveKiroModel(pane, expectedAgent) {
1174
+ if (readKiroSteerComposer(pane) !== "idle")
1175
+ return null;
1176
+ const rows = pane.split("\n").map(row => row.replace(/\s+$/, "")), at = findKiroComposerRow(rows);
1177
+ if (!at)
1178
+ return null;
1179
+ let cwd = false, branch = false;
1180
+ // Footer only. Never search backwards through arbitrary output or a model picker for a matching model.
1181
+ for (let i = at.index - 1; i >= Math.max(0, at.index - 8); i--) {
1182
+ const row = rows[i];
1183
+ if (!row.trim() || RULE_ROW.test(row))
1184
+ continue;
1185
+ if (!branch && /^\s*\([^()\r\n]{1,128}\)\s*$/.test(row)) {
1186
+ branch = true;
1187
+ continue;
1188
+ }
1189
+ if (!cwd && /^\s*(?:\/[^\x00-\x1f]*|~(?:\/[^\x00-\x1f]*)?)\s*$/.test(row)) {
1190
+ cwd = true;
1191
+ continue;
1192
+ }
1193
+ const status = /^\s*(\S+) · ([A-Za-z0-9_.:/-]{1,128}) · .+$/.exec(row);
1194
+ return cwd && status && (expectedAgent === undefined || status[1] === expectedAgent) ? status[2] : null;
1195
+ }
1196
+ return null;
1197
+ }
977
1198
  export function planKiroLaunch(ui, compat) {
978
1199
  const version = compat.version ?? "of unknown version";
979
1200
  if (ui === "v3")
@@ -1104,6 +1325,9 @@ export const KIRO_RATE_LIMIT_SCREEN = new RegExp(KIRO_MESSAGE_LEAD + wrapToleran
1104
1325
  /** #1636: kiro's monthly cap (MonthlyLimitReached branch); see getErrorPatterns. */
1105
1326
  export const KIRO_REQUEST_LIMIT_SCREEN = new RegExp(KIRO_MESSAGE_LEAD + "(?:" + ["Hourly", "Daily", "Weekly", "Monthly"].map(wrapTolerant).join("|") + ")"
1106
1327
  + wrapTolerant(" request limit reached") + "(?:[ \\t]*$|" + wrapTolerant(" - The limits reset on") + "\\b)", "m");
1328
+ /** #2598: Kiro 2.34 lists a carried UUID in both V2 and classic; this proves presence, not a missing session. */
1329
+ export const KIRO_AMBIGUOUS_RESUME_SCREEN = new RegExp(wrapTolerant('Error: Session id "') + '[A-Za-z0-9_.\\n-]{1,256}' + wrapTolerant('" is ambiguous (')
1330
+ + "\\d+" + wrapTolerant(" matches)."), "m");
1107
1331
  /** Startup budget for a `--resume` launch (60% first output, 40% ready). */
1108
1332
  export const KIRO_RESUME_STARTUP_BUDGET_MS = 60_000;
1109
1333
  export class KiroBackend {
@@ -1147,6 +1371,7 @@ export class KiroBackend {
1147
1371
  /** #2648: the saved model of the v2 conversation this instance has recorded, read in prepareLaunch (off the loop). */
1148
1372
  preparedV2Model = null;
1149
1373
  storePreparation = 0;
1374
+ preparedCarry = null;
1150
1375
  storeKey(config) {
1151
1376
  return JSON.stringify([config.instanceName, config.workingDirectory, kiroCanonicalDirectory(config.workingDirectory), config.kiroUi,
1152
1377
  resolveCredentialProfile(config.backendOptions), !!config.skipResume, getAgendHome(), kiroV2SessionsDir(), kiroCanonicalDirectory(kiroV2SessionsDir())]);
@@ -1163,6 +1388,7 @@ export class KiroBackend {
1163
1388
  const preparation = ++this.storePreparation;
1164
1389
  this.preparedV2Store = null;
1165
1390
  this.preparedV2Model = null;
1391
+ this.preparedCarry = null;
1166
1392
  if (!config || config.kiroUi !== "tui")
1167
1393
  return;
1168
1394
  if (!this.fixedCompatibility)
@@ -1173,6 +1399,29 @@ export class KiroBackend {
1173
1399
  return;
1174
1400
  const opts = { instance: config.instanceName, engine: "v2", workingDirectory: config.workingDirectory,
1175
1401
  credentialProfile: resolveCredentialProfile(config.backendOptions), skipResume: config.skipResume, agendHome: getAgendHome() };
1402
+ if (!config.skipResume && this.needsCarryPreparation()) {
1403
+ try {
1404
+ this.preparedCarry = { key, error: null };
1405
+ if (kiroCarryCandidate(opts)) {
1406
+ const reply = await sharedKiroV2StoreLane.read({ keys: [], sessionsDir: kiroV2SessionsDir(), carry: {
1407
+ ...opts, sessionsDir: kiroV2SessionsDir(), dbPath: kiroV1DbPath(opts.credentialProfile),
1408
+ } });
1409
+ if (preparation !== this.storePreparation || key !== this.storeKey(config))
1410
+ return;
1411
+ this.preparedCarry = { key, error: reply.kind === "unreadable" ? reply.detail : null };
1412
+ if (reply.kind === "unreadable")
1413
+ return;
1414
+ if (reply.carry?.archivePath)
1415
+ this.addLaunchWarning(t("kiro.archive_done", reply.carry.archivePath));
1416
+ if (reply.carry?.repaired)
1417
+ this.addLaunchWarning(t("kiro.carry_pin_repaired"));
1418
+ }
1419
+ }
1420
+ catch (err) {
1421
+ this.preparedCarry = { key, error: err.message };
1422
+ return;
1423
+ }
1424
+ }
1176
1425
  if (kiroIdentityNeedsStore(opts)) {
1177
1426
  const store = await listKiroV2Sessions(config.workingDirectory, kiroV2SessionsDir());
1178
1427
  if (preparation === this.storePreparation && key === this.storeKey(config))
@@ -1187,6 +1436,27 @@ export class KiroBackend {
1187
1436
  this.preparedV2Model = { key, id: recorded, model };
1188
1437
  }
1189
1438
  }
1439
+ needsCarryPreparation() {
1440
+ const v = /(?:^|\s)(\d+)\.(\d+)\.(\d+)/.exec(this.compatibility.version ?? "");
1441
+ return !!v && (Number(v[1]) > 2 || (Number(v[1]) === 2 && Number(v[2]) >= 34));
1442
+ }
1443
+ assertCarryPrepared(config) {
1444
+ if (config.kiroUi !== "tui" || config.skipResume || !this.needsCarryPreparation())
1445
+ return;
1446
+ const opts = { agendHome: getAgendHome(), instance: config.instanceName, workingDirectory: config.workingDirectory,
1447
+ credentialProfile: resolveCredentialProfile(config.backendOptions) };
1448
+ let required;
1449
+ try {
1450
+ required = !!kiroCarryCandidate(opts);
1451
+ }
1452
+ catch (err) {
1453
+ throw new ResumeBlockedError(t("kiro.archive_failed", err.message), t("kiro.archive_remedy"));
1454
+ }
1455
+ if (this.preparedCarry?.key === this.storeKey(config) && this.preparedCarry.error)
1456
+ throw new ResumeBlockedError(t("kiro.archive_failed", this.preparedCarry.error), t("kiro.archive_remedy"));
1457
+ if (required && this.preparedCarry?.key !== this.storeKey(config))
1458
+ throw new ResumeBlockedError(t("kiro.archive_failed", "carry preparation is missing"), t("kiro.archive_remedy"));
1459
+ }
1190
1460
  constructor(instanceDir, compatibility) {
1191
1461
  this.instanceDir = instanceDir;
1192
1462
  this.binaryPath = resolveBinary("kiro-cli");
@@ -1241,6 +1511,10 @@ export class KiroBackend {
1241
1511
  readSteerComposerText(pane) {
1242
1512
  return this.activeUi === "legacy" ? null : readKiroComposerText(pane);
1243
1513
  }
1514
+ /** #2676: the line count a collapsed paste chip on the composer's row names (readKiroCollapsedPasteLines). */
1515
+ readSteerComposerPasteLines(text) {
1516
+ return this.activeUi === "legacy" ? null : readKiroCollapsedPasteLines(text);
1517
+ }
1244
1518
  /**
1245
1519
  * Live evidence (kiro-cli 2.21.0, 2026-09-03): `--resume` prints nothing
1246
1520
  * until the conversation comes back from the backend ("Picking up where we
@@ -1252,6 +1526,18 @@ export class KiroBackend {
1252
1526
  getStartupBudgetMs(ctx) {
1253
1527
  return ctx.resume ? KIRO_RESUME_STARTUP_BUDGET_MS : undefined;
1254
1528
  }
1529
+ resumeBlockedPattern() { return KIRO_AMBIGUOUS_RESUME_SCREEN; }
1530
+ retainedResumeBlock() {
1531
+ const plan = this.activePlan;
1532
+ if (!plan || plan.engine !== "v2" || plan.mode !== "resume" || !plan.id || !this.activeInstance)
1533
+ return null;
1534
+ let carried = true;
1535
+ try {
1536
+ carried = kiroCarryCandidate({ agendHome: getAgendHome(), instance: this.activeInstance, workingDirectory: plan.cwd, credentialProfile: plan.credentialProfile })?.id === plan.id;
1537
+ }
1538
+ catch { /* lost ownership proof is also a hold, never authority to discard history */ }
1539
+ return carried ? { reason: t("kiro.carry_resume_failed"), remedy: t("kiro.archive_remedy") } : null;
1540
+ }
1255
1541
  retriesResumeOnStartupFailure() {
1256
1542
  // A resume miss is far more often "the backend was slow" than "the session
1257
1543
  // is broken"; one more attempt before abandoning the conversation is cheap.
@@ -1286,6 +1572,7 @@ export class KiroBackend {
1286
1572
  // V3 has a different engine and no verified completion signal yet (#1144).
1287
1573
  this.replyGuardOptedIn = false;
1288
1574
  this.builtLaunch = null;
1575
+ this.assertCarryPrepared(config);
1289
1576
  const ui = config.kiroUi ?? "legacy";
1290
1577
  // #2598 B3: never back onto an older engine than this instance has run on — that resumes the frozen copy and loses
1291
1578
  // everything done since. Before any probe: no kiro-cli flag or version changes the answer.
@@ -1473,6 +1760,7 @@ export class KiroBackend {
1473
1760
  return true;
1474
1761
  }
1475
1762
  writeConfig(config) {
1763
+ this.assertCarryPrepared(config);
1476
1764
  // WORKAROUND: kiro-cli ignores the "env" block of an MCP server entry — the server subprocess inherits the fleet
1477
1765
  // manager's env, with a stale AGEND_SOCKET_PATH from whichever daemon wrote last. Each server therefore runs
1478
1766
  // through a wrapper script that exports this instance's env and execs the real server.
@@ -2009,8 +2297,37 @@ export class KiroBackend {
2009
2297
  },
2010
2298
  ...kiroTrustPromptDialogs(this.activeTrustAll),
2011
2299
  ...kiroWorkspaceTrustDialogs(this.activeTrustAll),
2300
+ {
2301
+ // #2682: an editor, a pager, a `!` command or a focused /kvim owns the pane: a paste and an Enter would go into
2302
+ // it. Last, so every dialog above keeps its own words; held for the person, never keyed.
2303
+ pattern: KIRO_TAKEOVER_KEY,
2304
+ keys: [],
2305
+ description: "Kiro's terminal is in use by another program (an editor, a pager or a ! command) — finish or close it in the instance pane",
2306
+ blocksDelivery: true,
2307
+ holdOnly: true,
2308
+ inputBlocked: true,
2309
+ isActive: pane => kiroTakeoverActive(pane, this.activeUi),
2310
+ },
2012
2311
  ];
2013
2312
  }
2313
+ /**
2314
+ * #2682: kiro's own input row, read positively, before an idle delivery. TUI and V3: the composer reads "idle", or
2315
+ * "text" (a draft, as before); busy, another mode (a goal, the tray being edited) or no composer at all — an unknown
2316
+ * full-screen program, a blank frame — is not a screen to type into. Legacy: its prompt row is the bottom row (`4% !>`,
2317
+ * or `4% >` without trust-all, live 2.34.0), not running a `!` command. Anything else: the delivery waits.
2318
+ */
2319
+ /** #2682: the busy composer's input reaches kiro's own turn (kiroBusyTurnTakesInput), not a `!` command's PTY. */
2320
+ busyTurnTakesInput(pane) {
2321
+ return this.activeUi !== "legacy" && kiroBusyTurnTakesInput(pane);
2322
+ }
2323
+ idleComposerReady(pane) {
2324
+ if (this.activeUi === "legacy") {
2325
+ const prompt = kiroLegacyPromptAtBottom(pane.split("\n").map(row => row.replace(/\s+$/, "")));
2326
+ return prompt !== null && !prompt.startsWith("!");
2327
+ }
2328
+ const mode = readKiroSteerComposer(pane);
2329
+ return mode === "idle" || mode === "text";
2330
+ }
2014
2331
  getContextUsage() {
2015
2332
  return null;
2016
2333
  }
@@ -2025,6 +2342,23 @@ export class KiroBackend {
2025
2342
  return null;
2026
2343
  return { engine: built.engine, sessionId: plan?.mode === "resume" && plan.id ? plan.id : null };
2027
2344
  }
2345
+ /**
2346
+ * M-2025: `/session-id` and `/title` exist on tui and V3 only. Legacy keeps
2347
+ * the tmux window name and the sqlite lookup.
2348
+ */
2349
+ sessionProof() {
2350
+ const ui = this.activeUi;
2351
+ if (ui !== "tui" && ui !== "v3")
2352
+ return null;
2353
+ const pinned = this.transcriptLaunch()?.sessionId ?? null;
2354
+ return {
2355
+ pinned,
2356
+ sessionIdCommand: KIRO_SESSION_ID_COMMAND,
2357
+ titleCommand: this.activeInstance ? kiroTitleCommand(this.activeInstance) : null,
2358
+ readComposer: readKiroCommandComposer,
2359
+ readSessionIdAlert: readKiroSessionIdAlert,
2360
+ };
2361
+ }
2028
2362
  getSessionId() {
2029
2363
  // Kiro manages sessions internally via SQLite keyed by working directory.
2030
2364
  // No external session ID needed — --resume handles it automatically.