@songsid/agend 2.3.2-beta.10 → 2.3.2-beta.11
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/agent-cli.js +48 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +6 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +3 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/backend/kiro-classic-archive.d.ts +52 -0
- package/dist/backend/kiro-classic-archive.js +444 -0
- package/dist/backend/kiro-classic-archive.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +19 -0
- package/dist/backend/kiro-identity.js +43 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro-migration-verify.d.ts +10 -0
- package/dist/backend/kiro-migration-verify.js +52 -0
- package/dist/backend/kiro-migration-verify.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.js +21 -1
- package/dist/backend/kiro-v2-store-worker.js.map +1 -1
- package/dist/backend/kiro-v2-store.d.ts +8 -0
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +99 -1
- package/dist/backend/kiro.js +352 -18
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +49 -0
- package/dist/backend/types.js +9 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/ipc-timeouts.js +3 -0
- package/dist/channel/ipc-timeouts.js.map +1 -1
- package/dist/channel/mcp-tools.js +12 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +33 -0
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +2 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +45 -0
- package/dist/daemon.js +342 -19
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +15 -4
- package/dist/dashboard-permissions.js +49 -23
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/fleet-control-audit.d.ts +1 -1
- package/dist/fleet-control-audit.js +3 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-manager.d.ts +61 -2
- package/dist/fleet-manager.js +421 -28
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +10 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +25 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +25 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +12 -2
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +12 -2
- package/dist/instance-config-impact.js +2 -2
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +4 -0
- package/dist/instance-lifecycle.js +7 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-migrate.d.ts +11 -0
- package/dist/kiro-migrate.js +86 -31
- package/dist/kiro-migrate.js.map +1 -1
- package/dist/kiro-session-proof.d.ts +41 -0
- package/dist/kiro-session-proof.js +89 -0
- package/dist/kiro-session-proof.js.map +1 -0
- package/dist/kiro-tui-forensic.d.ts +5 -0
- package/dist/kiro-tui-forensic.js +3 -3
- package/dist/kiro-tui-forensic.js.map +1 -1
- package/dist/locale.js +142 -0
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/needs-you.d.ts +3 -1
- package/dist/needs-you.js +4 -2
- package/dist/needs-you.js.map +1 -1
- package/dist/office-share.d.ts +12 -2
- package/dist/office-share.js +27 -2
- package/dist/office-share.js.map +1 -1
- package/dist/outbound-schemas.d.ts +39 -0
- package/dist/outbound-schemas.js +29 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/remote-card-prompt.d.ts +38 -0
- package/dist/remote-card-prompt.js +86 -0
- package/dist/remote-card-prompt.js.map +1 -0
- package/dist/settings-change.js +8 -5
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-instance-patch.js +1 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/tool-permissions.js +17 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/transcript-sources.d.ts +1 -0
- package/dist/transcript-sources.js +2 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/ui/chat-store.js +3 -2
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/fleet-office-editor.js +54 -2
- package/dist/ui/fleet-office-layout.js +44 -7
- package/dist/ui/fleet-office-pack.d.ts +6 -1
- package/dist/ui/fleet-office-pack.js +101 -15
- package/dist/ui/fleet-office-store.js +43 -0
- package/dist/ui/fleet-office-zones.js +64 -13
- package/dist/ui/fleet-office.js +5 -3
- package/dist/ui/panel-chat.js +14 -2
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +1 -1
- package/dist/ui/panel-settings.js +5 -5
- package/dist/ui/settings-model.js +17 -9
- package/dist/ui/settings-strings.js +2 -0
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-stream.js +1 -1
- package/dist/ui/shared/app.css +18 -2
- package/dist/web-api.d.ts +5 -1
- package/dist/web-api.js +91 -3
- package/dist/web-api.js.map +1 -1
- package/dist/web-office-acts.js +2 -2
- package/dist/web-office-acts.js.map +1 -1
- package/dist/web-office-pack-read.d.ts +35 -0
- package/dist/web-office-pack-read.js +252 -0
- package/dist/web-office-pack-read.js.map +1 -0
- package/dist/web-office-pack-tools.d.ts +232 -0
- package/dist/web-office-pack-tools.js +754 -0
- package/dist/web-office-pack-tools.js.map +1 -0
- package/dist/web-office-skins.d.ts +68 -4
- package/dist/web-office-skins.js +318 -33
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-office-store.d.ts +5 -2
- package/dist/web-office-store.js +41 -15
- package/dist/web-office-store.js.map +1 -1
- package/package.json +1 -1
package/dist/backend/kiro.js
CHANGED
|
@@ -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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
125
|
+
return "steer";
|
|
102
126
|
if (new RegExp(`${working}Type to queue(?:[ \\t]+·[ \\t]+[^·]+?[ \\t]+to steer)?$`).test(row))
|
|
103
|
-
return
|
|
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
|
|
109
|
-
|
|
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.
|