@songsid/agend 2.3.2-beta.9 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/README.zh-TW.md +6 -0
- 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/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/claude-code.js +23 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-session-lookup.d.ts +15 -0
- package/dist/backend/codex-session-lookup.js +58 -0
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.js +15 -4
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro-classic-archive.d.ts +57 -0
- package/dist/backend/kiro-classic-archive.js +622 -0
- package/dist/backend/kiro-classic-archive.js.map +1 -0
- package/dist/backend/kiro-classic-links.d.ts +24 -0
- package/dist/backend/kiro-classic-links.js +135 -0
- package/dist/backend/kiro-classic-links.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +100 -1
- package/dist/backend/kiro-identity.js +262 -31
- 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-reconcile-store.d.ts +27 -0
- package/dist/backend/kiro-reconcile-store.js +344 -0
- package/dist/backend/kiro-reconcile-store.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.js +35 -1
- package/dist/backend/kiro-v2-store-worker.js.map +1 -1
- package/dist/backend/kiro-v2-store.d.ts +31 -1
- package/dist/backend/kiro-v2-store.js +22 -12
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +124 -1
- package/dist/backend/kiro.js +553 -29
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +102 -0
- package/dist/backend/types.js +9 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-service.d.ts +13 -1
- package/dist/cache-service.js +45 -2
- package/dist/cache-service.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 +40 -5
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +2 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +6 -0
- package/dist/classic-channel-manager.js +25 -0
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +50 -4
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +7 -1
- package/dist/config-validator.js +17 -8
- 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 +66 -0
- package/dist/daemon.js +421 -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/doctor.js +2 -1
- package/dist/doctor.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 +90 -3
- package/dist/fleet-manager.js +569 -41
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +9 -0
- package/dist/forged-envelope-scan.js +30 -5
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-instance.d.ts +24 -0
- package/dist/general-instance.js +53 -0
- package/dist/general-instance.js.map +1 -1
- package/dist/general-knowledge/skills/docker-install/SKILL.md +25 -0
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -3
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +34 -0
- package/dist/general-knowledge/skills/session-management/SKILL.md +1 -1
- package/dist/general-knowledge/skills/tui-effort/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +32 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +32 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +57 -17
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +57 -17
- package/dist/instance-config-impact.js +3 -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 +19 -2
- package/dist/kiro-migrate.js +133 -35
- package/dist/kiro-migrate.js.map +1 -1
- package/dist/kiro-reconcile.d.ts +21 -0
- package/dist/kiro-reconcile.js +88 -0
- package/dist/kiro-reconcile.js.map +1 -0
- 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 +19 -0
- package/dist/kiro-tui-forensic.js +124 -0
- package/dist/kiro-tui-forensic.js.map +1 -0
- package/dist/locale.js +198 -0
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +2 -2
- 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-levels.d.ts +64 -0
- package/dist/office-levels.js +66 -0
- package/dist/office-levels.js.map +1 -0
- package/dist/office-share-page.d.ts +3 -1
- package/dist/office-share-page.js +8 -3
- package/dist/office-share-page.js.map +1 -1
- package/dist/office-share-service.d.ts +6 -3
- package/dist/office-share-service.js +12 -8
- package/dist/office-share-service.js.map +1 -1
- package/dist/office-share.d.ts +45 -8
- package/dist/office-share.js +75 -15
- package/dist/office-share.js.map +1 -1
- package/dist/outbound-handlers.js +20 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +47 -2
- package/dist/outbound-schemas.js +41 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +1 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.d.ts +39 -1
- package/dist/quickstart-api.js +115 -17
- package/dist/quickstart-api.js.map +1 -1
- package/dist/remote-card-answer.d.ts +83 -0
- package/dist/remote-card-answer.js +208 -0
- package/dist/remote-card-answer.js.map +1 -0
- 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/scheduler/task-board.d.ts +28 -6
- package/dist/scheduler/task-board.js +66 -11
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/service-installer.d.ts +13 -0
- package/dist/service-installer.js +15 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +25 -0
- package/dist/service-owner.js +43 -2
- package/dist/service-owner.js.map +1 -1
- package/dist/settings-api.d.ts +2 -0
- package/dist/settings-api.js +18 -12
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +10 -5
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +7 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-instance-patch.js +1 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/setup-form.d.ts +2 -2
- package/dist/setup-form.js +139 -93
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-i18n.d.ts +25 -0
- package/dist/setup-i18n.js +226 -0
- package/dist/setup-i18n.js.map +1 -0
- package/dist/task-board-waker.js +7 -2
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tool-permissions.js +18 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/transcript-sources.d.ts +1 -0
- package/dist/transcript-sources.js +20 -3
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/ui/backend-signin.js +4 -3
- package/dist/ui/chat-store.js +3 -2
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/fleet-office-card.js +114 -0
- package/dist/ui/fleet-office-editor.js +81 -9
- package/dist/ui/fleet-office-layout.js +56 -11
- package/dist/ui/fleet-office-levels.js +105 -0
- package/dist/ui/fleet-office-model.js +9 -1
- package/dist/ui/fleet-office-pack.d.ts +15 -3
- package/dist/ui/fleet-office-pack.js +177 -23
- package/dist/ui/fleet-office-skin.js +39 -3
- package/dist/ui/fleet-office-store.js +74 -3
- package/dist/ui/fleet-office-view.js +8 -0
- package/dist/ui/fleet-office-zones.js +102 -19
- package/dist/ui/fleet-office.js +308 -44
- package/dist/ui/fleet-org.js +2 -0
- package/dist/ui/fleet-task-card.js +2 -2
- package/dist/ui/fleet-task-common.js +30 -7
- package/dist/ui/fleet-task-work.js +2 -2
- package/dist/ui/fleet-tasks.js +10 -11
- package/dist/ui/office-share-dialog.js +31 -7
- package/dist/ui/office-share-page.js +1 -0
- package/dist/ui/panel-chat.js +14 -2
- package/dist/ui/panel-log.js +4 -4
- package/dist/ui/panel-needs.js +1 -1
- package/dist/ui/panel-settings.js +16 -8
- package/dist/ui/settings-discord.js +3 -2
- package/dist/ui/settings-guests.js +17 -7
- package/dist/ui/settings-model.js +17 -9
- package/dist/ui/settings-registry.js +1 -0
- package/dist/ui/settings-strings.js +16 -4
- package/dist/ui/settings-token.js +3 -2
- package/dist/ui/settings-wizard.js +25 -9
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-stream.js +1 -1
- package/dist/ui/shared/app.css +70 -2
- package/dist/ui/shared/setup-host-text.js +107 -0
- package/dist/ui/structured-signin.js +89 -38
- package/dist/usage-rollup.d.ts +34 -3
- package/dist/usage-rollup.js +56 -3
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +10 -1
- package/dist/web-api.js +207 -17
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-office-acts.d.ts +5 -2
- package/dist/web-office-acts.js +9 -4
- 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 +241 -0
- package/dist/web-office-pack-tools.js +785 -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 +324 -33
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-office-store.d.ts +47 -4
- package/dist/web-office-store.js +124 -23
- package/dist/web-office-store.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +2 -2
- package/templates/windows-shortcuts.ps1 +18 -17
package/dist/backend/kiro.js
CHANGED
|
@@ -5,14 +5,17 @@ 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, readKiroClassicLinks, 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";
|
|
18
|
+
import { KIRO_CLASSIC_LINKS_NOT_PREPARED } from "./kiro-classic-links.js";
|
|
16
19
|
import { decideKiroModelError, readKiroKnownModels, readKiroModelRecords, readKiroV1SavedModel, readKiroV2SavedModel, writeKiroModelRecords, KIRO_FALLBACK_MODEL } from "./kiro-retired-model.js";
|
|
17
20
|
import { recordKiroLaunch } from "./kiro-engine-ledger.js";
|
|
18
21
|
import { forgetKiroV3Identity, resolveKiroV3Resume } from "./kiro-v3-identity.js";
|
|
@@ -49,10 +52,13 @@ export const KIRO_TESTED_MAX = "2.34.0";
|
|
|
49
52
|
* queued message mid-tool — has been captured (tests/fixtures/kiro-steer-1405, 2026-10-08: 2.27.1 `--tui
|
|
50
53
|
* --agent-engine=v2` and `--v3`, 2.28.0 `--tui --agent-engine=v2`; identical rows on all three). Any other version,
|
|
51
54
|
* 2.28.0's v3, the legacy UI and an undetected version all deliver a steer as an ordinary message after the turn.
|
|
55
|
+
* 2.34.0 TUI was checked on the user-authorized #2598 D5 run (tests/fixtures/kiro-2.34.0-tui-d5):
|
|
56
|
+
* native steer during a read-only tool, a fleet message queued mid-tool, and a native queue entry in the busy turn.
|
|
52
57
|
*/
|
|
53
58
|
export const KIRO_STEER_VERIFIED = {
|
|
54
59
|
"2.27.1": ["tui", "v3"],
|
|
55
60
|
"2.28.0": ["tui"],
|
|
61
|
+
"2.34.0": ["tui"],
|
|
56
62
|
};
|
|
57
63
|
/**
|
|
58
64
|
* The TUI composer's interrupt mode, read off its own row: the LAST row starting at column 0 with `›` (transcript user
|
|
@@ -70,8 +76,10 @@ export const KIRO_STEER_VERIFIED = {
|
|
|
70
76
|
* evidence of either an empty or a full box. The toggle key's label is whatever the user bound; it is read past, never
|
|
71
77
|
* pressed.
|
|
72
78
|
*/
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
/** #2682: the takeover hold's key (RuntimeDialog.pattern); kiroTakeoverActive decides, this never matches a pane. */
|
|
80
|
+
const KIRO_TAKEOVER_KEY = /(?!)kiro-takeover/;
|
|
81
|
+
/** The composer's `›` row in `rows` and how many typed-text rows continue below it; null when there is none. */
|
|
82
|
+
function findKiroComposerRow(rows) {
|
|
75
83
|
let i = rows.length - 1;
|
|
76
84
|
while (i >= 0 && (rows[i] === "" || /^[ \t]{20,}\S/.test(rows[i])))
|
|
77
85
|
i--;
|
|
@@ -83,30 +91,47 @@ function readKiroComposer(pane) {
|
|
|
83
91
|
continued++;
|
|
84
92
|
}
|
|
85
93
|
const row = rows[i];
|
|
86
|
-
|
|
94
|
+
return row !== undefined && /^›[ \t]+\S/.test(row) ? { index: i, continued } : null;
|
|
95
|
+
}
|
|
96
|
+
function readKiroComposer(pane) {
|
|
97
|
+
const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
|
|
98
|
+
const at = findKiroComposerRow(rows);
|
|
99
|
+
if (!at)
|
|
87
100
|
return null;
|
|
101
|
+
let row = rows[at.index];
|
|
88
102
|
// A placeholder is one row: with continuation rows below it, this is typed text — unless its first row is another
|
|
89
|
-
// mode's placeholder
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
// mode's placeholder. #2682: a narrow pane word-wraps a placeholder onto indented rows; joined, a COMPLETE known form
|
|
104
|
+
// reads as itself (an idle placeholder is then idle, not unreadable); anything else stays unreadable.
|
|
105
|
+
if (at.continued > 0) {
|
|
106
|
+
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)) {
|
|
107
|
+
return { mode: "text", text: row.replace(/^›[ \t]+/, "") };
|
|
108
|
+
}
|
|
109
|
+
row = [row, ...rows.slice(at.index + 1, at.index + 1 + at.continued).map(r => r.trim())].join(" ");
|
|
110
|
+
const mode = kiroPlaceholderMode(row);
|
|
111
|
+
return mode ? { mode, text: row.replace(/^›[ \t]+/, "") } : null;
|
|
94
112
|
}
|
|
95
|
-
const working = String.raw `^›[ \t]+Kiro is working(?:[ \t]+·[ \t]+[^·]+?)?[ \t]+·[ \t]+`;
|
|
96
113
|
const text = row.replace(/^›[ \t]+/, "");
|
|
114
|
+
const mode = kiroPlaceholderMode(row);
|
|
115
|
+
if (mode)
|
|
116
|
+
return { mode, text };
|
|
117
|
+
// Other placeholders (and anything the ASCII glyph set paints) are not typed text.
|
|
118
|
+
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))
|
|
119
|
+
return null;
|
|
120
|
+
return { mode: "text", text };
|
|
121
|
+
}
|
|
122
|
+
/** The empty composer's mode from its whole placeholder (`› …`, one row or wrapped rows joined); null when it is none. */
|
|
123
|
+
function kiroPlaceholderMode(row) {
|
|
124
|
+
const working = String.raw `^›[ \t]+Kiro is working(?:[ \t]+·[ \t]+[^·]+?)?[ \t]+·[ \t]+`;
|
|
97
125
|
if (new RegExp(`${working}Type to steer[ \\t]+·[ \\t]+[^·]+?[ \\t]+to queue$`).test(row))
|
|
98
|
-
return
|
|
126
|
+
return "steer";
|
|
99
127
|
if (new RegExp(`${working}Type to queue(?:[ \\t]+·[ \\t]+[^·]+?[ \\t]+to steer)?$`).test(row))
|
|
100
|
-
return
|
|
128
|
+
return "queue";
|
|
101
129
|
const idle = String.raw `^›[ \t]+ask a question or describe a task`;
|
|
102
130
|
if (new RegExp(`${idle}[ \\t]+↵$`).test(row)
|
|
103
131
|
|| new RegExp(`${idle}[ \\t]+↵[ \\t]+·[ \\t]+exit plan mode: shift\\+tab$`).test(row)
|
|
104
132
|
|| new RegExp(`${idle}[ \\t]+·[ \\t]+/tangent to go back[ \\t]+·[ \\t]+/tangent ls to view$`).test(row))
|
|
105
|
-
return
|
|
106
|
-
|
|
107
|
-
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))
|
|
108
|
-
return null;
|
|
109
|
-
return { mode: "text", text };
|
|
133
|
+
return "idle";
|
|
134
|
+
return null;
|
|
110
135
|
}
|
|
111
136
|
export function readKiroSteerComposer(pane) {
|
|
112
137
|
return readKiroComposer(pane)?.mode ?? null;
|
|
@@ -116,6 +141,178 @@ export function readKiroComposerText(pane) {
|
|
|
116
141
|
const c = readKiroComposer(pane);
|
|
117
142
|
return c?.mode === "text" ? c.text : null;
|
|
118
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* #2683: the composer as a one-line housekeeping command (`/session-id`, `/title …`) sees it: `""` when it is
|
|
146
|
+
* positively empty (the idle placeholder, nothing else), the typed text of a single-row draft, or null for anything
|
|
147
|
+
* else (busy, another mode, wrapped text, no composer). A typed slash command opens kiro's command menu below the box
|
|
148
|
+
* (live 2.34.0, tui and V3; tests/fixtures/kiro-session-id-2681):
|
|
149
|
+
*
|
|
150
|
+
* `› /session-id`
|
|
151
|
+
* `/session-id Print the current session ID`
|
|
152
|
+
* `──────…`
|
|
153
|
+
* ` esc to cancel · ↵ to select`
|
|
154
|
+
*
|
|
155
|
+
* and its `›` row is read past that menu. Enter there runs the highlighted entry, the command itself.
|
|
156
|
+
*/
|
|
157
|
+
export function readKiroCommandComposer(pane) {
|
|
158
|
+
const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
|
|
159
|
+
let i = rows.length - 1;
|
|
160
|
+
while (i >= 0 && (rows[i] === "" || /^[ \t]{20,}\S/.test(rows[i])))
|
|
161
|
+
i--;
|
|
162
|
+
if (i >= 2 && /^ esc to cancel · .*↵ to select$/.test(rows[i]) && /^─{20,}$/.test(rows[i - 1])) {
|
|
163
|
+
let r = i - 2, entries = 0;
|
|
164
|
+
while (r >= 0 && entries < 12 && /^\/[\w-]+ {2,}\S/.test(rows[r])) {
|
|
165
|
+
r--;
|
|
166
|
+
entries++;
|
|
167
|
+
}
|
|
168
|
+
const row = rows[r];
|
|
169
|
+
return entries > 0 && row !== undefined && /^›[ \t]+\/\S/.test(row) ? row.replace(/^›[ \t]+/, "") : null;
|
|
170
|
+
}
|
|
171
|
+
const at = findKiroComposerRow(rows);
|
|
172
|
+
if (!at || at.continued > 0)
|
|
173
|
+
return null;
|
|
174
|
+
const c = readKiroComposer(pane);
|
|
175
|
+
return c?.mode === "idle" ? "" : c?.mode === "text" ? c.text : null;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* #2683: kiro's own answer to `/session-id`, where only that answer is painted: the alert at the bottom of the chat,
|
|
179
|
+
* on the rows right above the footer (the divider within 8 rows above the composer, and the trust banner's divider
|
|
180
|
+
* above that one when it is shown). Live 2.34.0, tui and V3 (tests/fixtures/kiro-session-id-2681):
|
|
181
|
+
*
|
|
182
|
+
* `• Session ID: <id>`
|
|
183
|
+
* ` Resume with: kiro-cli --resume-id <id>` (the same id)
|
|
184
|
+
*
|
|
185
|
+
* or `• Session ID: none` alone. It is a transient alert (10 s), not a turn. The same lines anywhere else (a quoted
|
|
186
|
+
* user row is ` › …`, an answer scrolls up) are not read; an agent answer that ends with them would sit in the same
|
|
187
|
+
* place, which is why the daemon also requires that the alert was not there before it typed the command. Null when no
|
|
188
|
+
* such alert is shown.
|
|
189
|
+
*/
|
|
190
|
+
export function readKiroSessionIdAlert(pane) {
|
|
191
|
+
const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
|
|
192
|
+
const at = findKiroComposerRow(rows);
|
|
193
|
+
if (!at)
|
|
194
|
+
return null;
|
|
195
|
+
let d = at.index - 1;
|
|
196
|
+
while (d >= 0 && at.index - d <= 8 && !/^─{20,}$/.test(rows[d]))
|
|
197
|
+
d--;
|
|
198
|
+
if (d < 0 || !/^─{20,}$/.test(rows[d]))
|
|
199
|
+
return null;
|
|
200
|
+
if (d >= 2 && /^ \S/.test(rows[d - 1]) && /^─{20,}$/.test(rows[d - 2]))
|
|
201
|
+
d -= 2; // ` Trust All Tools active …`
|
|
202
|
+
if (rows[d - 1] === "• Session ID: none")
|
|
203
|
+
return "none";
|
|
204
|
+
const id = /^ {2}Resume with: kiro-cli --resume-id ([A-Za-z0-9][A-Za-z0-9_.-]{0,199})$/.exec(rows[d - 1] ?? "")?.[1];
|
|
205
|
+
return id !== undefined && id !== "none" && rows[d - 2] === `• Session ID: ${id}` ? id : null;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* #2676: a collapsed paste on the composer's row. The TUI shows a paste of more than 10 lines or 500 characters as one
|
|
209
|
+
* chip (2.34.0 tui.js: `n>1 ? "${n} lines" : "${r} chars"`, then `▸`, then the `Press Tab to expand` hint):
|
|
210
|
+
*
|
|
211
|
+
* `› 42 lines ▸ Press Tab to expand` → 42
|
|
212
|
+
* `› 657 chars ▸ Press Tab to expand` → 1 (one line, shown only by its length)
|
|
213
|
+
*
|
|
214
|
+
* `text` is the row's text (readKiroComposerText). Null when it is not such a chip.
|
|
215
|
+
*/
|
|
216
|
+
export function readKiroCollapsedPasteLines(text) {
|
|
217
|
+
const m = /^(\d+) (lines|chars) ▸(?:[ \t]+Press Tab to expand)?$/.exec(text.trim());
|
|
218
|
+
if (!m)
|
|
219
|
+
return null;
|
|
220
|
+
return m[2] === "lines" ? Number(m[1]) : 1;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* #2682: another program owns kiro's pane, so a paste and an Enter would go into it instead of the chat (captured on
|
|
224
|
+
* 2.34.0, tests/fixtures/kiro-2.34.0-takeover-2682). Read from kiro's own rows first:
|
|
225
|
+
* - a focused `/kvim` panel: `── Neovim · focused · Ctrl+\ to chat · :q to close ──`, or `· the agent is waiting on you:`
|
|
226
|
+
* (unfocused, `Neovim · Ctrl+\ to edit`, the composer has the keys again);
|
|
227
|
+
* - a running `!` command (tui, V3): its PTY takes every keystroke, while the composer still reads busy
|
|
228
|
+
* (`Kiro is working · Type to steer`) — the nearest turn above kiro's footer is ` › !…`, with only its indented
|
|
229
|
+
* output and `• Running...` below (kiroBusyTurn; a turn it cannot see is not called a takeover here, but no busy
|
|
230
|
+
* input is typed into it either: KiroBackend.busyTurnTakesInput);
|
|
231
|
+
* - an editor or pager kiro started (`/editor`, `/transcript`, a full-screen `!vim`/`!less`): no composer row, kiro's
|
|
232
|
+
* temp file `kiro-editor-XXXXXX/prompt.md` or `kiro-raw-XXXXXX/conversation.md` on screen;
|
|
233
|
+
* - legacy: a `!` command running from its prompt row (the bottom row is the prompt with `!…` after the marker: the
|
|
234
|
+
* command took the terminal before kiro painted anything else), and its `/editor` and `/reply` temp file
|
|
235
|
+
* `q_prompt_<uuid>.md` with no prompt row at the bottom;
|
|
236
|
+
* and, only where kiro's input row is not on screen (every engine; legacy's `/editor` and `/reply` run `$EDITOR`), the
|
|
237
|
+
* program's own bottom rows: vi/vim/nvim (`~` rows, its ruler, `-- INSERT --`), less (`:`, `(END)`), more
|
|
238
|
+
* (`--More--`), nano (`GNU nano` and `^X Exit`). `/fullscreen` is not a takeover: the same composer takes input.
|
|
239
|
+
*/
|
|
240
|
+
export function kiroTakeoverActive(pane, ui) {
|
|
241
|
+
const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
|
|
242
|
+
if (ui === "legacy") {
|
|
243
|
+
const prompt = kiroLegacyPromptAtBottom(rows);
|
|
244
|
+
if (prompt !== null)
|
|
245
|
+
return prompt.startsWith("!");
|
|
246
|
+
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)))
|
|
247
|
+
return true;
|
|
248
|
+
return fullScreenProgramAtBottom(rows);
|
|
249
|
+
}
|
|
250
|
+
if (rows.some(row => /^─+ Neovim · (?:focused · |the agent is waiting on you: )/.test(row)))
|
|
251
|
+
return true;
|
|
252
|
+
const at = findKiroComposerRow(rows);
|
|
253
|
+
if (at) {
|
|
254
|
+
const mode = readKiroComposer(pane)?.mode;
|
|
255
|
+
return (mode === "steer" || mode === "queue") && kiroBusyTurn(rows, at.index) === "shell";
|
|
256
|
+
}
|
|
257
|
+
if (rows.some(row => /\/kiro-(?:editor|raw)-[A-Za-z0-9]{6}\/(?:prompt|conversation)\.md\b/.test(row)))
|
|
258
|
+
return true;
|
|
259
|
+
return fullScreenProgramAtBottom(rows);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* #2682: the legacy UI's prompt row as the last non-blank row — `4% !>` (trust-all) or `4% >`, with an optional
|
|
263
|
+
* `[agent]` prefix and mode glyph, as getBottomReadyPattern — and the text after its marker (a placeholder hint, a
|
|
264
|
+
* draft, or a `!` command it is running); null when the bottom row is not the prompt.
|
|
265
|
+
*/
|
|
266
|
+
export function kiroLegacyPromptAtBottom(rows) {
|
|
267
|
+
const last = [...rows].reverse().find(row => row.trim() !== "");
|
|
268
|
+
const m = last === undefined ? null : /^\s*(?:\[[^\]]*\]\s*)?\d+%\s*(?:[^\s\d%!❯>]{1,2}\s+)?(?:!\s?[❯>]|❯|>)(.*)$/.exec(last);
|
|
269
|
+
return m ? m[1].trim() : null;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* #2682: what the busy turn nearest above kiro's footer is: "shell" — a `!` command still running (its ` › !` row with
|
|
273
|
+
* only its output and `• Running...` below), "agent" — positive evidence of the agent's turn first (another user row,
|
|
274
|
+
* one of kiro's bullets, its spinner), "unknown" — no footer, or nothing that says either up to the top of the pane.
|
|
275
|
+
* Everything else is passed over, never taken as evidence: the footer and its notes however they wrap, dividers, the
|
|
276
|
+
* tray and tips, indented output.
|
|
277
|
+
*/
|
|
278
|
+
function kiroBusyTurn(rows, composer) {
|
|
279
|
+
let d = composer - 1;
|
|
280
|
+
while (d >= 0 && composer - d <= 8 && !/^─{20,}$/.test(rows[d]))
|
|
281
|
+
d--;
|
|
282
|
+
if (d < 0 || !/^─{20,}$/.test(rows[d]))
|
|
283
|
+
return "unknown"; // no footer divider within reach
|
|
284
|
+
for (let r = d - 1; r >= 0; r--) {
|
|
285
|
+
const row = rows[r];
|
|
286
|
+
if (/^ {2}› !/.test(row))
|
|
287
|
+
return "shell";
|
|
288
|
+
if (/^ {2}› /.test(row) || /^• (?!Running\.\.\.$)\S/.test(row) || /^\S[ \t]+(?:Thinking|Working)(?:\.{3}|…)/.test(row))
|
|
289
|
+
return "agent";
|
|
290
|
+
}
|
|
291
|
+
return "unknown";
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* #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.
|
|
295
|
+
* A `!` command's PTY would take it, and a turn it cannot see might be one.
|
|
296
|
+
*/
|
|
297
|
+
export function kiroBusyTurnTakesInput(pane) {
|
|
298
|
+
const rows = pane.split("\n").map(row => row.replace(/\s+$/, ""));
|
|
299
|
+
const at = findKiroComposerRow(rows);
|
|
300
|
+
return at !== null && kiroBusyTurn(rows, at.index) === "agent";
|
|
301
|
+
}
|
|
302
|
+
/** #2682: a full-screen editor or pager's own bottom rows (vi/vim/nvim, less, more, nano), whoever started it. */
|
|
303
|
+
function fullScreenProgramAtBottom(rows) {
|
|
304
|
+
const shown = rows.filter(row => row.trim() !== "");
|
|
305
|
+
const last = shown[shown.length - 1]?.trim() ?? "";
|
|
306
|
+
if (last === ":" || /\(END\)$/.test(last) || /^--More--/.test(last))
|
|
307
|
+
return true; // less, more
|
|
308
|
+
if (/^-- (?:INSERT|REPLACE|VISUAL(?: LINE| BLOCK)?) --/.test(last))
|
|
309
|
+
return true; // vi modes
|
|
310
|
+
if (/\s\d+,\d+(?:-\d+)?\s+(?:All|Top|Bot|\d+%)$/.test(last))
|
|
311
|
+
return true; // vi ruler
|
|
312
|
+
if (shown.slice(-7, -1).filter(row => row === "~").length >= 3)
|
|
313
|
+
return true; // vi's empty lines
|
|
314
|
+
return shown.slice(-2).some(row => /\^X Exit/.test(row)) && rows.some(row => /^\s*GNU nano\b/.test(row)); // nano
|
|
315
|
+
}
|
|
119
316
|
const UNKNOWN_KIRO_COMPATIBILITY = {
|
|
120
317
|
supportsLegacyUi: false,
|
|
121
318
|
supportsTui: false,
|
|
@@ -134,11 +331,12 @@ const warnedVersionGateCacheKeys = new Set();
|
|
|
134
331
|
* This broad test is only a safety hold: it must never select an option or
|
|
135
332
|
* claim a service outage. A ready prompt below the picker makes it history.
|
|
136
333
|
*/
|
|
334
|
+
const KIRO_MODEL_PICKER_HEADER = /^\s*Select model(?: \(type to search\):|:\s+type to search)\s*$/m;
|
|
137
335
|
function kiroModelPickerAtTail(pane) {
|
|
138
336
|
const lines = pane.replace(/\r/g, "").split("\n");
|
|
139
337
|
let pickerIndex = -1;
|
|
140
338
|
for (let index = lines.length - 1; index >= 0; index--) {
|
|
141
|
-
if (
|
|
339
|
+
if (KIRO_MODEL_PICKER_HEADER.test(lines[index])) {
|
|
142
340
|
pickerIndex = index;
|
|
143
341
|
break;
|
|
144
342
|
}
|
|
@@ -146,7 +344,8 @@ function kiroModelPickerAtTail(pane) {
|
|
|
146
344
|
if (pickerIndex < 0)
|
|
147
345
|
return false;
|
|
148
346
|
const tail = lines.slice(pickerIndex + 1);
|
|
149
|
-
|
|
347
|
+
const composer = readKiroSteerComposer(tail.join("\n"));
|
|
348
|
+
if (tail.some(line => /^\s*\d+%[^\n]*[!❯>]\s+\S/.test(line)) || (composer !== null && composer !== "text"))
|
|
150
349
|
return false;
|
|
151
350
|
return tail.some(line => /^\s*[>❯›]\s*\*?\s*\S/.test(line));
|
|
152
351
|
}
|
|
@@ -399,6 +598,137 @@ export function kiroQuestionCardActive(pane) {
|
|
|
399
598
|
return false;
|
|
400
599
|
return body.filter(row => /^\s*❯\s+\S/.test(row)).length === 1;
|
|
401
600
|
}
|
|
601
|
+
/** #2675: the live card's rows — the header row, the body between it and the key hints' divider, and the key hints. */
|
|
602
|
+
function kiroCardRows(pane, headerAt) {
|
|
603
|
+
const rows = pane.replace(/\r/g, "").split("\n").map(row => row.trimEnd());
|
|
604
|
+
let last = rows.length - 1;
|
|
605
|
+
while (last >= 0 && !rows[last].trim())
|
|
606
|
+
last--;
|
|
607
|
+
const header = headerAt(rows, last);
|
|
608
|
+
return header < 0 ? null : { header: rows[header].trim(), body: rows.slice(header + 1, last - 1), footer: rows[last].trim(), width: rows[last - 1].trim().length };
|
|
609
|
+
}
|
|
610
|
+
const KIRO_QUESTION_OPTION_ROW = /^ (❯| ) (\d+)\. (.*?)( \(recommended\))?$/;
|
|
611
|
+
const KIRO_QUESTION_SUBOPTION_ROW = /^ (❯| ) \[(✓| )\] (.*)$/;
|
|
612
|
+
const KIRO_QUESTION_FREE_ROW = /^ (❯| ) (Type a different answer…|or type feedback to request changes)$/;
|
|
613
|
+
/**
|
|
614
|
+
* #2675: the live Question card (see kiroQuestionCardActive), read for a person to answer from the chat: the question,
|
|
615
|
+
* the numbered options (`(recommended)` and the description row under one), the typed-answer row, and where the `❯`
|
|
616
|
+
* is (tui.js `A0`; tests/fixtures/kiro-question-2592). On a sub-options page the rows are the `[✓]` boxes; while an
|
|
617
|
+
* answer is being typed the last row is `❯ <text>`. A row the shapes above do not account for is a wrapped title (the
|
|
618
|
+
* card's own indent) or description (deeper), joined to the row it continues; one before any option belongs to the
|
|
619
|
+
* question. Null when the card is not live, or when it is in a shape this reader does not know.
|
|
620
|
+
*/
|
|
621
|
+
export function readKiroQuestionCard(pane) {
|
|
622
|
+
if (!kiroQuestionCardActive(pane))
|
|
623
|
+
return null;
|
|
624
|
+
const card = kiroCardRows(pane, (rows, last) => {
|
|
625
|
+
let top = last - 2;
|
|
626
|
+
while (top >= 0 && !/^─{3,}$/.test(rows[top].trim()))
|
|
627
|
+
top--;
|
|
628
|
+
return top + 1;
|
|
629
|
+
});
|
|
630
|
+
if (!card)
|
|
631
|
+
return null;
|
|
632
|
+
const page = /space to toggle/.test(card.footer) ? "suboptions" : "options";
|
|
633
|
+
const question = [];
|
|
634
|
+
const options = [];
|
|
635
|
+
let cursor = null, freeText = false, typed = null, freeRow = false;
|
|
636
|
+
for (const row of card.body) {
|
|
637
|
+
const option = page === "options" ? KIRO_QUESTION_OPTION_ROW.exec(row) : null;
|
|
638
|
+
const sub = page === "suboptions" ? KIRO_QUESTION_SUBOPTION_ROW.exec(row) : null;
|
|
639
|
+
const free = page === "options" ? KIRO_QUESTION_FREE_ROW.exec(row) : null;
|
|
640
|
+
if (freeRow)
|
|
641
|
+
return null; // nothing follows the typed-answer row
|
|
642
|
+
if (option && Number(option[2]) === options.length + 1) {
|
|
643
|
+
if (option[1] === "❯")
|
|
644
|
+
cursor = options.length;
|
|
645
|
+
options.push({ n: options.length + 1, title: option[3], recommended: option[4] !== undefined, description: null });
|
|
646
|
+
}
|
|
647
|
+
else if (sub) {
|
|
648
|
+
if (sub[1] === "❯")
|
|
649
|
+
cursor = options.length;
|
|
650
|
+
options.push({ n: options.length + 1, title: sub[3], recommended: false, description: null, checked: sub[2] === "✓" });
|
|
651
|
+
}
|
|
652
|
+
else if (free) {
|
|
653
|
+
freeText = true;
|
|
654
|
+
freeRow = true;
|
|
655
|
+
if (free[1] === "❯")
|
|
656
|
+
cursor = options.length;
|
|
657
|
+
}
|
|
658
|
+
else if (page === "options" && options.length > 0 && /^ ❯ /.test(row)) {
|
|
659
|
+
freeText = true;
|
|
660
|
+
freeRow = true;
|
|
661
|
+
typed = row.slice(3);
|
|
662
|
+
}
|
|
663
|
+
else if (!row.trim()) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
else if (options.length === 0) {
|
|
667
|
+
question.push(row.replace(/^ /, ""));
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
const lastOption = options[options.length - 1];
|
|
671
|
+
if (/^ {5,}\S/.test(row))
|
|
672
|
+
lastOption.description = lastOption.description === null ? row.trim() : `${lastOption.description} ${row.trim()}`;
|
|
673
|
+
else if (lastOption.description === null)
|
|
674
|
+
lastOption.title = `${lastOption.title} ${row.trim()}`;
|
|
675
|
+
else
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (options.length === 0 || question.length === 0)
|
|
680
|
+
return null;
|
|
681
|
+
const typing = typed !== null;
|
|
682
|
+
if (typing ? cursor !== null : cursor === null)
|
|
683
|
+
return null;
|
|
684
|
+
const titles = options.map(option => `${option.n}. ${option.title}`).join("\n");
|
|
685
|
+
return {
|
|
686
|
+
kind: "question", identity: `${card.header}\n${question.join("\n")}\n${page === "suboptions" ? "sub" : "options"}\n${titles}`,
|
|
687
|
+
title: card.header, question: question.join("\n").trim(), page: typing ? "freetext" : page, options, freeText, cursor, typed, rowWidth: card.width,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
const KIRO_MCP_INPUT_ACTIONS = new Set(["Submit", "Decline", "Open in browser", "Copy URL", "I'll open it myself", "Cancel"]);
|
|
691
|
+
/**
|
|
692
|
+
* #2675: the live MCP `Input requested` card (see kiroMcpInputRequestActive), read for the chat: the server, the body
|
|
693
|
+
* as painted, with the `URL:` row masked (a sign-in link stays in the pane), its action rows, and the `❯` row. Its
|
|
694
|
+
* identity is the server, the mode, the message's first row and the actions: moving or filling in a field keeps it.
|
|
695
|
+
*/
|
|
696
|
+
export function readKiroMcpInputCard(pane) {
|
|
697
|
+
if (!kiroMcpInputRequestActive(pane))
|
|
698
|
+
return null;
|
|
699
|
+
const card = kiroCardRows(pane, (rows, last) => {
|
|
700
|
+
for (let index = last - 2; index >= 1; index--)
|
|
701
|
+
if (KIRO_MCP_INPUT_HEADER.test(rows[index]))
|
|
702
|
+
return index;
|
|
703
|
+
return -1;
|
|
704
|
+
});
|
|
705
|
+
if (!card)
|
|
706
|
+
return null;
|
|
707
|
+
// The URL row is masked, and so is every row after it up to the next action row: a long URL wraps (#2677 review).
|
|
708
|
+
let inUrl = false;
|
|
709
|
+
const body = card.body.slice(1).flatMap(row => {
|
|
710
|
+
if (/^\s*URL: /.test(row)) {
|
|
711
|
+
inUrl = true;
|
|
712
|
+
return [row.replace(/URL: .*$/, "URL: (shown in the instance pane)")];
|
|
713
|
+
}
|
|
714
|
+
if (inUrl && KIRO_MCP_INPUT_ACTIONS.has(row.replace(/^ ❯ /, " ").trim()))
|
|
715
|
+
inUrl = false;
|
|
716
|
+
return inUrl ? [] : [row];
|
|
717
|
+
});
|
|
718
|
+
const strip = (row) => row.replace(/^ ❯ /, " ").trim();
|
|
719
|
+
const cursorRow = body.find(row => /^\s*❯\s+\S/.test(row));
|
|
720
|
+
if (!cursorRow)
|
|
721
|
+
return null;
|
|
722
|
+
return {
|
|
723
|
+
kind: "input", server: card.header.replace(/^Input requested · /, ""),
|
|
724
|
+
mode: /ctrl\+s to submit/.test(card.footer) ? "form" : "url",
|
|
725
|
+
rows: body.map(row => row.replace(/^ ❯ /, " ").replace(/^ {3}/, "")).filter(row => row.trim()),
|
|
726
|
+
actions: body.map(strip).filter(row => KIRO_MCP_INPUT_ACTIONS.has(row)),
|
|
727
|
+
cursorRow: strip(cursorRow),
|
|
728
|
+
// The focused field paints extra rows (its value, its caret) and a typed value changes its row: neither is another request.
|
|
729
|
+
identity: `${card.header}\n${/ctrl\+s to submit/.test(card.footer) ? "form" : "url"}\n${strip(body[0] ?? "")}\n${body.map(strip).filter(row => KIRO_MCP_INPUT_ACTIONS.has(row)).join("|")}`,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
402
732
|
/**
|
|
403
733
|
* Kiro's service-unavailable path opens the same picker as `/model`, but only
|
|
404
734
|
* the former needs incident escalation. The runtime scanner and delivery gate
|
|
@@ -411,7 +741,7 @@ export function kiroUnavailableModelPickerActive(pane) {
|
|
|
411
741
|
const lines = pane.replace(/\r/g, "").split("\n");
|
|
412
742
|
let pickerIndex = -1;
|
|
413
743
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
414
|
-
if (
|
|
744
|
+
if (KIRO_MODEL_PICKER_HEADER.test(lines[i])) {
|
|
415
745
|
pickerIndex = i;
|
|
416
746
|
break;
|
|
417
747
|
}
|
|
@@ -627,6 +957,13 @@ function versionAtLeast(version, minimum) {
|
|
|
627
957
|
}
|
|
628
958
|
return true;
|
|
629
959
|
}
|
|
960
|
+
/** Verified carry/reconcile stays on supported 2.x and requires the same capabilities as launch. */
|
|
961
|
+
export function supportsVerifiedKiroMigration(compat) {
|
|
962
|
+
// `version` retains the complete --version line (e.g. "kiro-cli 2.34.0").
|
|
963
|
+
const version = parseSemver(compat.version);
|
|
964
|
+
return !!version && version[0] === 2 && versionAtLeast(version, "2.34.0")
|
|
965
|
+
&& compat.supportsTui && compat.supportsInstanceAgent === true && compat.agentEngines?.includes("v2") === true;
|
|
966
|
+
}
|
|
630
967
|
function helpAdvertisesFlag(help, flag) {
|
|
631
968
|
// clap prints a short alias first when there is one: ` -r, --resume`.
|
|
632
969
|
return new RegExp(`^\\s*(?:-[A-Za-z0-9],\\s*)?${flag}(?:[ =<,]|$)`, "m").test(help);
|
|
@@ -754,6 +1091,22 @@ function nextPatch(version) {
|
|
|
754
1091
|
const [major, minor, patch] = parseSemver(version);
|
|
755
1092
|
return `${major}.${minor}.${patch + 1}`;
|
|
756
1093
|
}
|
|
1094
|
+
/** #2714: what a resume decision says about a compaction, for the log and the Log tab. */
|
|
1095
|
+
function kiroCompactionNotes(decision) {
|
|
1096
|
+
const notes = [];
|
|
1097
|
+
if (decision.followed?.length)
|
|
1098
|
+
notes.push(t("kiro.compaction_followed", decision.followed[0], decision.id));
|
|
1099
|
+
const kept = decision.kept;
|
|
1100
|
+
if (kept?.reason === "unreadable")
|
|
1101
|
+
notes.push(t("kiro.compaction_kept_unreadable", decision.id));
|
|
1102
|
+
else if (kept?.reason === "over-budget")
|
|
1103
|
+
notes.push(t("kiro.compaction_kept_over_budget", decision.id));
|
|
1104
|
+
else if (kept?.reason === "ambiguous")
|
|
1105
|
+
notes.push(t("kiro.compaction_kept_ambiguous", decision.id, kept.from, kept.candidates.join(", ")));
|
|
1106
|
+
else if (kept?.reason === "not-ours")
|
|
1107
|
+
notes.push(t("kiro.compaction_kept_not_ours", decision.id, kept.from, kept.candidate));
|
|
1108
|
+
return notes;
|
|
1109
|
+
}
|
|
757
1110
|
/** #2598 B3: engines in the order a conversation can move between them (never back). */
|
|
758
1111
|
const KIRO_ENGINE_RANK = { legacy: 1, tui: 2, v3: 3 };
|
|
759
1112
|
/** Binary generations already told they cannot isolate (once each). */
|
|
@@ -838,6 +1191,34 @@ export function readActiveKiroAgent(pane, ui) {
|
|
|
838
1191
|
}
|
|
839
1192
|
return null;
|
|
840
1193
|
}
|
|
1194
|
+
/** Model on Kiro 2.34's live footer (status, cwd, optional branch, blank, idle composer).
|
|
1195
|
+
* Separate from readActiveKiroAgent: do not widen the disabled agent-switch action's reader.
|
|
1196
|
+
*/
|
|
1197
|
+
export function readActiveKiroModel(pane, expectedAgent) {
|
|
1198
|
+
if (readKiroSteerComposer(pane) !== "idle")
|
|
1199
|
+
return null;
|
|
1200
|
+
const rows = pane.split("\n").map(row => row.replace(/\s+$/, "")), at = findKiroComposerRow(rows);
|
|
1201
|
+
if (!at)
|
|
1202
|
+
return null;
|
|
1203
|
+
let cwd = false, branch = false;
|
|
1204
|
+
// Footer only. Never search backwards through arbitrary output or a model picker for a matching model.
|
|
1205
|
+
for (let i = at.index - 1; i >= Math.max(0, at.index - 8); i--) {
|
|
1206
|
+
const row = rows[i];
|
|
1207
|
+
if (!row.trim() || RULE_ROW.test(row))
|
|
1208
|
+
continue;
|
|
1209
|
+
if (!branch && /^\s*\([^()\r\n]{1,128}\)\s*$/.test(row)) {
|
|
1210
|
+
branch = true;
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
if (!cwd && /^\s*(?:\/[^\x00-\x1f]*|~(?:\/[^\x00-\x1f]*)?)\s*$/.test(row)) {
|
|
1214
|
+
cwd = true;
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
const status = /^\s*(\S+) · ([A-Za-z0-9_.:/-]{1,128}) · .+$/.exec(row);
|
|
1218
|
+
return cwd && status && (expectedAgent === undefined || status[1] === expectedAgent) ? status[2] : null;
|
|
1219
|
+
}
|
|
1220
|
+
return null;
|
|
1221
|
+
}
|
|
841
1222
|
export function planKiroLaunch(ui, compat) {
|
|
842
1223
|
const version = compat.version ?? "of unknown version";
|
|
843
1224
|
if (ui === "v3")
|
|
@@ -968,6 +1349,9 @@ export const KIRO_RATE_LIMIT_SCREEN = new RegExp(KIRO_MESSAGE_LEAD + wrapToleran
|
|
|
968
1349
|
/** #1636: kiro's monthly cap (MonthlyLimitReached branch); see getErrorPatterns. */
|
|
969
1350
|
export const KIRO_REQUEST_LIMIT_SCREEN = new RegExp(KIRO_MESSAGE_LEAD + "(?:" + ["Hourly", "Daily", "Weekly", "Monthly"].map(wrapTolerant).join("|") + ")"
|
|
970
1351
|
+ wrapTolerant(" request limit reached") + "(?:[ \\t]*$|" + wrapTolerant(" - The limits reset on") + "\\b)", "m");
|
|
1352
|
+
/** #2598: Kiro 2.34 lists a carried UUID in both V2 and classic; this proves presence, not a missing session. */
|
|
1353
|
+
export const KIRO_AMBIGUOUS_RESUME_SCREEN = new RegExp(wrapTolerant('Error: Session id "') + '[A-Za-z0-9_.\\n-]{1,256}' + wrapTolerant('" is ambiguous (')
|
|
1354
|
+
+ "\\d+" + wrapTolerant(" matches)."), "m");
|
|
971
1355
|
/** Startup budget for a `--resume` launch (60% first output, 40% ready). */
|
|
972
1356
|
export const KIRO_RESUME_STARTUP_BUDGET_MS = 60_000;
|
|
973
1357
|
export class KiroBackend {
|
|
@@ -1008,9 +1392,12 @@ export class KiroBackend {
|
|
|
1008
1392
|
/** #1218: the last launch built in full (cleared when a build starts, set only when it returns). */
|
|
1009
1393
|
builtLaunch = null;
|
|
1010
1394
|
preparedV2Store = null;
|
|
1395
|
+
/** #2738: a classic launch's compaction links, worked out in prepareLaunch on their worker lane (off the loop). */
|
|
1396
|
+
preparedClassicLinks = null;
|
|
1011
1397
|
/** #2648: the saved model of the v2 conversation this instance has recorded, read in prepareLaunch (off the loop). */
|
|
1012
1398
|
preparedV2Model = null;
|
|
1013
1399
|
storePreparation = 0;
|
|
1400
|
+
preparedCarry = null;
|
|
1014
1401
|
storeKey(config) {
|
|
1015
1402
|
return JSON.stringify([config.instanceName, config.workingDirectory, kiroCanonicalDirectory(config.workingDirectory), config.kiroUi,
|
|
1016
1403
|
resolveCredentialProfile(config.backendOptions), !!config.skipResume, getAgendHome(), kiroV2SessionsDir(), kiroCanonicalDirectory(kiroV2SessionsDir())]);
|
|
@@ -1021,22 +1408,63 @@ export class KiroBackend {
|
|
|
1021
1408
|
}
|
|
1022
1409
|
return this.preparedV2Store.store;
|
|
1023
1410
|
}
|
|
1411
|
+
/** Not prepared for this launch: unproven, so the launch stays on the recorded conversation (never read here). */
|
|
1412
|
+
readPreparedClassicLinks(config) {
|
|
1413
|
+
return this.preparedClassicLinks?.key === this.storeKey(config) ? this.preparedClassicLinks.links
|
|
1414
|
+
: KIRO_CLASSIC_LINKS_NOT_PREPARED;
|
|
1415
|
+
}
|
|
1024
1416
|
/** Metadata only: identity claims/config writes remain synchronous, after
|
|
1025
1417
|
* the daemon rechecks its launch admission following this await. */
|
|
1026
1418
|
async prepareLaunch(config) {
|
|
1027
1419
|
const preparation = ++this.storePreparation;
|
|
1028
1420
|
this.preparedV2Store = null;
|
|
1029
1421
|
this.preparedV2Model = null;
|
|
1030
|
-
|
|
1422
|
+
this.preparedCarry = null;
|
|
1423
|
+
this.preparedClassicLinks = null;
|
|
1424
|
+
if (!config)
|
|
1031
1425
|
return;
|
|
1032
1426
|
if (!this.fixedCompatibility)
|
|
1033
1427
|
this.compatibility = cachedKiroCliCompatibility(this.binaryPath).compatibility;
|
|
1034
1428
|
const key = this.storeKey(config);
|
|
1429
|
+
const classic = planKiroLaunch(config.kiroUi ?? "legacy", this.compatibility);
|
|
1430
|
+
if (classic.kind === "launch" && classic.flags.includes("--agent-engine=v1") && this.compatibility.supportsInstanceAgent && !config.skipResume) {
|
|
1431
|
+
// #2738: whether the recorded conversation was compacted reads every transcript of the key — a GB on a
|
|
1432
|
+
// long-lived instance. Worked out here on the worker lane; writeConfig only looks the answer up.
|
|
1433
|
+
const links = await readKiroClassicLinks(config.workingDirectory, kiroV1DbPath(resolveCredentialProfile(config.backendOptions)));
|
|
1434
|
+
if (preparation === this.storePreparation && key === this.storeKey(config))
|
|
1435
|
+
this.preparedClassicLinks = { key, links };
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
if (config.kiroUi !== "tui")
|
|
1439
|
+
return;
|
|
1035
1440
|
const plan = planKiroLaunch("tui", this.compatibility);
|
|
1036
1441
|
if (plan.kind !== "launch" || !plan.flags.includes("--agent-engine=v2") || !this.compatibility.supportsInstanceAgent)
|
|
1037
1442
|
return;
|
|
1038
1443
|
const opts = { instance: config.instanceName, engine: "v2", workingDirectory: config.workingDirectory,
|
|
1039
1444
|
credentialProfile: resolveCredentialProfile(config.backendOptions), skipResume: config.skipResume, agendHome: getAgendHome() };
|
|
1445
|
+
if (!config.skipResume && this.needsCarryPreparation()) {
|
|
1446
|
+
try {
|
|
1447
|
+
this.preparedCarry = { key, error: null };
|
|
1448
|
+
if (kiroCarryCandidate(opts)) {
|
|
1449
|
+
const reply = await sharedKiroV2StoreLane.read({ keys: [], sessionsDir: kiroV2SessionsDir(), carry: {
|
|
1450
|
+
...opts, sessionsDir: kiroV2SessionsDir(), dbPath: kiroV1DbPath(opts.credentialProfile),
|
|
1451
|
+
} });
|
|
1452
|
+
if (preparation !== this.storePreparation || key !== this.storeKey(config))
|
|
1453
|
+
return;
|
|
1454
|
+
this.preparedCarry = { key, error: reply.kind === "unreadable" ? reply.detail : null };
|
|
1455
|
+
if (reply.kind === "unreadable")
|
|
1456
|
+
return;
|
|
1457
|
+
if (reply.carry?.archivePath)
|
|
1458
|
+
this.addLaunchWarning(t("kiro.archive_done", reply.carry.archivePath));
|
|
1459
|
+
if (reply.carry?.repaired)
|
|
1460
|
+
this.addLaunchWarning(t("kiro.carry_pin_repaired"));
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
catch (err) {
|
|
1464
|
+
this.preparedCarry = { key, error: err.message };
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1040
1468
|
if (kiroIdentityNeedsStore(opts)) {
|
|
1041
1469
|
const store = await listKiroV2Sessions(config.workingDirectory, kiroV2SessionsDir());
|
|
1042
1470
|
if (preparation === this.storePreparation && key === this.storeKey(config))
|
|
@@ -1051,6 +1479,27 @@ export class KiroBackend {
|
|
|
1051
1479
|
this.preparedV2Model = { key, id: recorded, model };
|
|
1052
1480
|
}
|
|
1053
1481
|
}
|
|
1482
|
+
needsCarryPreparation() {
|
|
1483
|
+
const v = /(?:^|\s)(\d+)\.(\d+)\.(\d+)/.exec(this.compatibility.version ?? "");
|
|
1484
|
+
return !!v && (Number(v[1]) > 2 || (Number(v[1]) === 2 && Number(v[2]) >= 34));
|
|
1485
|
+
}
|
|
1486
|
+
assertCarryPrepared(config) {
|
|
1487
|
+
if (config.kiroUi !== "tui" || config.skipResume || !this.needsCarryPreparation())
|
|
1488
|
+
return;
|
|
1489
|
+
const opts = { agendHome: getAgendHome(), instance: config.instanceName, workingDirectory: config.workingDirectory,
|
|
1490
|
+
credentialProfile: resolveCredentialProfile(config.backendOptions) };
|
|
1491
|
+
let required;
|
|
1492
|
+
try {
|
|
1493
|
+
required = !!kiroCarryCandidate(opts);
|
|
1494
|
+
}
|
|
1495
|
+
catch (err) {
|
|
1496
|
+
throw new ResumeBlockedError(t("kiro.archive_failed", err.message), t("kiro.archive_remedy"));
|
|
1497
|
+
}
|
|
1498
|
+
if (this.preparedCarry?.key === this.storeKey(config) && this.preparedCarry.error)
|
|
1499
|
+
throw new ResumeBlockedError(t("kiro.archive_failed", this.preparedCarry.error), t("kiro.archive_remedy"));
|
|
1500
|
+
if (required && this.preparedCarry?.key !== this.storeKey(config))
|
|
1501
|
+
throw new ResumeBlockedError(t("kiro.archive_failed", "carry preparation is missing"), t("kiro.archive_remedy"));
|
|
1502
|
+
}
|
|
1054
1503
|
constructor(instanceDir, compatibility) {
|
|
1055
1504
|
this.instanceDir = instanceDir;
|
|
1056
1505
|
this.binaryPath = resolveBinary("kiro-cli");
|
|
@@ -1105,6 +1554,10 @@ export class KiroBackend {
|
|
|
1105
1554
|
readSteerComposerText(pane) {
|
|
1106
1555
|
return this.activeUi === "legacy" ? null : readKiroComposerText(pane);
|
|
1107
1556
|
}
|
|
1557
|
+
/** #2676: the line count a collapsed paste chip on the composer's row names (readKiroCollapsedPasteLines). */
|
|
1558
|
+
readSteerComposerPasteLines(text) {
|
|
1559
|
+
return this.activeUi === "legacy" ? null : readKiroCollapsedPasteLines(text);
|
|
1560
|
+
}
|
|
1108
1561
|
/**
|
|
1109
1562
|
* Live evidence (kiro-cli 2.21.0, 2026-09-03): `--resume` prints nothing
|
|
1110
1563
|
* until the conversation comes back from the backend ("Picking up where we
|
|
@@ -1116,6 +1569,18 @@ export class KiroBackend {
|
|
|
1116
1569
|
getStartupBudgetMs(ctx) {
|
|
1117
1570
|
return ctx.resume ? KIRO_RESUME_STARTUP_BUDGET_MS : undefined;
|
|
1118
1571
|
}
|
|
1572
|
+
resumeBlockedPattern() { return KIRO_AMBIGUOUS_RESUME_SCREEN; }
|
|
1573
|
+
retainedResumeBlock() {
|
|
1574
|
+
const plan = this.activePlan;
|
|
1575
|
+
if (!plan || plan.engine !== "v2" || plan.mode !== "resume" || !plan.id || !this.activeInstance)
|
|
1576
|
+
return null;
|
|
1577
|
+
let carried = true;
|
|
1578
|
+
try {
|
|
1579
|
+
carried = kiroCarryCandidate({ agendHome: getAgendHome(), instance: this.activeInstance, workingDirectory: plan.cwd, credentialProfile: plan.credentialProfile })?.id === plan.id;
|
|
1580
|
+
}
|
|
1581
|
+
catch { /* lost ownership proof is also a hold, never authority to discard history */ }
|
|
1582
|
+
return carried ? { reason: t("kiro.carry_resume_failed"), remedy: t("kiro.archive_remedy") } : null;
|
|
1583
|
+
}
|
|
1119
1584
|
retriesResumeOnStartupFailure() {
|
|
1120
1585
|
// A resume miss is far more often "the backend was slow" than "the session
|
|
1121
1586
|
// is broken"; one more attempt before abandoning the conversation is cheap.
|
|
@@ -1150,6 +1615,7 @@ export class KiroBackend {
|
|
|
1150
1615
|
// V3 has a different engine and no verified completion signal yet (#1144).
|
|
1151
1616
|
this.replyGuardOptedIn = false;
|
|
1152
1617
|
this.builtLaunch = null;
|
|
1618
|
+
this.assertCarryPrepared(config);
|
|
1153
1619
|
const ui = config.kiroUi ?? "legacy";
|
|
1154
1620
|
// #2598 B3: never back onto an older engine than this instance has run on — that resumes the frozen copy and loses
|
|
1155
1621
|
// everything done since. Before any probe: no kiro-cli flag or version changes the answer.
|
|
@@ -1188,6 +1654,8 @@ export class KiroBackend {
|
|
|
1188
1654
|
const lp = prepared ? this.launchPlan : this.planInstanceLaunch(config);
|
|
1189
1655
|
if (!prepared && lp.reason)
|
|
1190
1656
|
this.addLaunchWarning(lp.reason);
|
|
1657
|
+
if (!prepared)
|
|
1658
|
+
this.quietLaunchNotes.push(...lp.notes ?? []);
|
|
1191
1659
|
this.launchPlan = null;
|
|
1192
1660
|
this.activePlan = lp;
|
|
1193
1661
|
this.activeSpec = lp.mode === "legacy" ? null : this.agentSpec(config);
|
|
@@ -1324,6 +1792,8 @@ export class KiroBackend {
|
|
|
1324
1792
|
this.launchWarning = null;
|
|
1325
1793
|
return warning;
|
|
1326
1794
|
}
|
|
1795
|
+
quietLaunchNotes = [];
|
|
1796
|
+
consumeQuietLaunchNotes() { const notes = this.quietLaunchNotes; this.quietLaunchNotes = []; return notes; }
|
|
1327
1797
|
shouldWarnUnsupportedEffort() {
|
|
1328
1798
|
if (!this.compatibilityCacheKey) {
|
|
1329
1799
|
if (this.warnedUnsupportedEffort)
|
|
@@ -1337,6 +1807,7 @@ export class KiroBackend {
|
|
|
1337
1807
|
return true;
|
|
1338
1808
|
}
|
|
1339
1809
|
writeConfig(config) {
|
|
1810
|
+
this.assertCarryPrepared(config);
|
|
1340
1811
|
// WORKAROUND: kiro-cli ignores the "env" block of an MCP server entry — the server subprocess inherits the fleet
|
|
1341
1812
|
// manager's env, with a stale AGEND_SOCKET_PATH from whichever daemon wrote last. Each server therefore runs
|
|
1342
1813
|
// through a wrapper script that exports this instance's env and execs the real server.
|
|
@@ -1360,6 +1831,7 @@ export class KiroBackend {
|
|
|
1360
1831
|
this.launchPlan = plan;
|
|
1361
1832
|
if (plan.reason)
|
|
1362
1833
|
this.addLaunchWarning(plan.reason);
|
|
1834
|
+
this.quietLaunchNotes.push(...plan.notes ?? []);
|
|
1363
1835
|
const spec = this.agentSpec(config);
|
|
1364
1836
|
const root = dirname(this.instanceDir);
|
|
1365
1837
|
if (plan.mode === "legacy") {
|
|
@@ -1511,7 +1983,7 @@ export class KiroBackend {
|
|
|
1511
1983
|
skipResume: config.skipResume,
|
|
1512
1984
|
agendHome,
|
|
1513
1985
|
readStore: () => engine === "v1"
|
|
1514
|
-
? listKiroV1Sessions(config.workingDirectory, kiroV1DbPath(credentialProfile))
|
|
1986
|
+
? listKiroV1Sessions(config.workingDirectory, kiroV1DbPath(credentialProfile), this.readPreparedClassicLinks(config))
|
|
1515
1987
|
: this.readPreparedV2Store(config),
|
|
1516
1988
|
launchedBefore: () => kiroLaunchedHereBefore(config.instanceName, config.workingDirectory, credentialProfile, engine),
|
|
1517
1989
|
upgradeAdoption: () => {
|
|
@@ -1532,10 +2004,12 @@ export class KiroBackend {
|
|
|
1532
2004
|
}
|
|
1533
2005
|
if (decision.mode === "legacy")
|
|
1534
2006
|
return legacy(t("kiro.identity_legacy", decision.reason));
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
2007
|
+
if (decision.mode === "resume") {
|
|
2008
|
+
return { ...base, mode: "resume", id: decision.id, agent, agentConfirmed: decision.agentConfirmed, reason: null, notes: kiroCompactionNotes(decision) };
|
|
2009
|
+
}
|
|
2010
|
+
const reasons = [decision.notice ? t(`kiro.upgrade_${decision.notice.replaceAll("-", "_")}`) : null,
|
|
2011
|
+
decision.lost ? t("kiro.claim_lost", decision.lost.id, t(`kiro.claim_lost_${decision.lost.claim.replaceAll("-", "_")}`)) : null];
|
|
2012
|
+
return { ...base, mode: "fresh", agent, agentConfirmed: true, reason: reasons.filter(r => r !== null).join("\n") || null };
|
|
1539
2013
|
}
|
|
1540
2014
|
/**
|
|
1541
2015
|
* After a resume as the instance's agent (#906 §3): what the daemon needs to read the active agent off the live
|
|
@@ -1827,7 +2301,7 @@ export class KiroBackend {
|
|
|
1827
2301
|
// picker. Its choices range across credit multipliers and capability
|
|
1828
2302
|
// tiers, so this must be a human decision, never an automatic Enter or
|
|
1829
2303
|
// Escape. The ordinary /model menu alone is intentionally not matched.
|
|
1830
|
-
pattern:
|
|
2304
|
+
pattern: KIRO_MODEL_PICKER_HEADER,
|
|
1831
2305
|
keys: [],
|
|
1832
2306
|
description: "Kiro model unavailable — choose a replacement model in the instance pane",
|
|
1833
2307
|
blocksDelivery: true,
|
|
@@ -1839,7 +2313,7 @@ export class KiroBackend {
|
|
|
1839
2313
|
// Any active /model picker owns stdin, even if a future Kiro release
|
|
1840
2314
|
// changes the exact warning, credit wording or row layout. Keep this
|
|
1841
2315
|
// after the verified-outage case: it never labels /model as an outage.
|
|
1842
|
-
pattern:
|
|
2316
|
+
pattern: KIRO_MODEL_PICKER_HEADER,
|
|
1843
2317
|
keys: [],
|
|
1844
2318
|
description: "Kiro model picker — manual selection required",
|
|
1845
2319
|
blocksDelivery: true,
|
|
@@ -1856,6 +2330,8 @@ export class KiroBackend {
|
|
|
1856
2330
|
holdOnly: true,
|
|
1857
2331
|
inputBlocked: true,
|
|
1858
2332
|
isActive: kiroMcpInputRequestActive,
|
|
2333
|
+
requestIdentity: pane => readKiroMcpInputCard(pane)?.identity ?? null,
|
|
2334
|
+
remoteCard: readKiroMcpInputCard,
|
|
1859
2335
|
},
|
|
1860
2336
|
{
|
|
1861
2337
|
// #2592: the agent's question to the user owns stdin until the person answers or cancels it.
|
|
@@ -1866,11 +2342,42 @@ export class KiroBackend {
|
|
|
1866
2342
|
holdOnly: true,
|
|
1867
2343
|
inputBlocked: true,
|
|
1868
2344
|
isActive: kiroQuestionCardActive,
|
|
2345
|
+
requestIdentity: pane => readKiroQuestionCard(pane)?.identity ?? null,
|
|
2346
|
+
remoteCard: readKiroQuestionCard,
|
|
1869
2347
|
},
|
|
1870
2348
|
...kiroTrustPromptDialogs(this.activeTrustAll),
|
|
1871
2349
|
...kiroWorkspaceTrustDialogs(this.activeTrustAll),
|
|
2350
|
+
{
|
|
2351
|
+
// #2682: an editor, a pager, a `!` command or a focused /kvim owns the pane: a paste and an Enter would go into
|
|
2352
|
+
// it. Last, so every dialog above keeps its own words; held for the person, never keyed.
|
|
2353
|
+
pattern: KIRO_TAKEOVER_KEY,
|
|
2354
|
+
keys: [],
|
|
2355
|
+
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",
|
|
2356
|
+
blocksDelivery: true,
|
|
2357
|
+
holdOnly: true,
|
|
2358
|
+
inputBlocked: true,
|
|
2359
|
+
isActive: pane => kiroTakeoverActive(pane, this.activeUi),
|
|
2360
|
+
},
|
|
1872
2361
|
];
|
|
1873
2362
|
}
|
|
2363
|
+
/**
|
|
2364
|
+
* #2682: kiro's own input row, read positively, before an idle delivery. TUI and V3: the composer reads "idle", or
|
|
2365
|
+
* "text" (a draft, as before); busy, another mode (a goal, the tray being edited) or no composer at all — an unknown
|
|
2366
|
+
* full-screen program, a blank frame — is not a screen to type into. Legacy: its prompt row is the bottom row (`4% !>`,
|
|
2367
|
+
* or `4% >` without trust-all, live 2.34.0), not running a `!` command. Anything else: the delivery waits.
|
|
2368
|
+
*/
|
|
2369
|
+
/** #2682: the busy composer's input reaches kiro's own turn (kiroBusyTurnTakesInput), not a `!` command's PTY. */
|
|
2370
|
+
busyTurnTakesInput(pane) {
|
|
2371
|
+
return this.activeUi !== "legacy" && kiroBusyTurnTakesInput(pane);
|
|
2372
|
+
}
|
|
2373
|
+
idleComposerReady(pane) {
|
|
2374
|
+
if (this.activeUi === "legacy") {
|
|
2375
|
+
const prompt = kiroLegacyPromptAtBottom(pane.split("\n").map(row => row.replace(/\s+$/, "")));
|
|
2376
|
+
return prompt !== null && !prompt.startsWith("!");
|
|
2377
|
+
}
|
|
2378
|
+
const mode = readKiroSteerComposer(pane);
|
|
2379
|
+
return mode === "idle" || mode === "text";
|
|
2380
|
+
}
|
|
1874
2381
|
getContextUsage() {
|
|
1875
2382
|
return null;
|
|
1876
2383
|
}
|
|
@@ -1885,6 +2392,23 @@ export class KiroBackend {
|
|
|
1885
2392
|
return null;
|
|
1886
2393
|
return { engine: built.engine, sessionId: plan?.mode === "resume" && plan.id ? plan.id : null };
|
|
1887
2394
|
}
|
|
2395
|
+
/**
|
|
2396
|
+
* M-2025: `/session-id` and `/title` exist on tui and V3 only. Legacy keeps
|
|
2397
|
+
* the tmux window name and the sqlite lookup.
|
|
2398
|
+
*/
|
|
2399
|
+
sessionProof() {
|
|
2400
|
+
const ui = this.activeUi;
|
|
2401
|
+
if (ui !== "tui" && ui !== "v3")
|
|
2402
|
+
return null;
|
|
2403
|
+
const pinned = this.transcriptLaunch()?.sessionId ?? null;
|
|
2404
|
+
return {
|
|
2405
|
+
pinned,
|
|
2406
|
+
sessionIdCommand: KIRO_SESSION_ID_COMMAND,
|
|
2407
|
+
titleCommand: this.activeInstance ? kiroTitleCommand(this.activeInstance) : null,
|
|
2408
|
+
readComposer: readKiroCommandComposer,
|
|
2409
|
+
readSessionIdAlert: readKiroSessionIdAlert,
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
1888
2412
|
getSessionId() {
|
|
1889
2413
|
// Kiro manages sessions internally via SQLite keyed by working directory.
|
|
1890
2414
|
// No external session ID needed — --resume handles it automatically.
|