@songsid/agend 2.3.2-beta.1 → 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/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent-cli.js +69 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +11 -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/antigravity-usage-scan.d.ts +58 -0
- package/dist/antigravity-usage-scan.js +257 -0
- package/dist/antigravity-usage-scan.js.map +1 -0
- package/dist/antigravity-usage-worker.d.ts +1 -0
- package/dist/antigravity-usage-worker.js +10 -0
- package/dist/antigravity-usage-worker.js.map +1 -0
- package/dist/auth-api.js +4 -0
- package/dist/auth-api.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 +83 -0
- package/dist/backend/kiro-identity.js +144 -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-retired-model.d.ts +62 -0
- package/dist/backend/kiro-retired-model.js +160 -0
- package/dist/backend/kiro-retired-model.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 +169 -2
- package/dist/backend/kiro.js +703 -28
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +108 -0
- package/dist/backend/types.js +9 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-signin-guide.d.ts +1 -0
- package/dist/backend-signin-guide.js +9 -0
- package/dist/backend-signin-guide.js.map +1 -0
- package/dist/backend-signin-pending.d.ts +10 -0
- package/dist/backend-signin-pending.js +44 -0
- package/dist/backend-signin-pending.js.map +1 -0
- package/dist/browser-backend-signins.d.ts +56 -0
- package/dist/browser-backend-signins.js +189 -0
- package/dist/browser-backend-signins.js.map +1 -0
- package/dist/cache-ledger.d.ts +38 -2
- package/dist/cache-ledger.js +133 -8
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-prices.d.ts +2 -1
- package/dist/cache-prices.js +5 -2
- package/dist/cache-prices.js.map +1 -1
- package/dist/cache-service.d.ts +68 -0
- package/dist/cache-service.js +355 -13
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +11 -0
- package/dist/channel/adapters/discord.js +71 -14
- package/dist/channel/adapters/discord.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 +41 -2
- 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 +11 -0
- package/dist/classic-channel-manager.js +39 -1
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +54 -2
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +7 -1
- package/dist/config-validator.js +55 -10
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +33 -1
- package/dist/config.js.map +1 -1
- package/dist/cpu-quota.d.ts +23 -0
- package/dist/cpu-quota.js +86 -0
- package/dist/cpu-quota.js.map +1 -0
- package/dist/daemon.d.ts +68 -0
- package/dist/daemon.js +448 -26
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +31 -6
- package/dist/dashboard-permissions.js +84 -9
- 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 +133 -2
- package/dist/fleet-manager.js +905 -94
- 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-knowledge/skills/credential-profiles/SKILL.md +5 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +24 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +49 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +49 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +47 -9
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +54 -11
- package/dist/instance-config-impact.js +10 -5
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +23 -0
- package/dist/instance-lifecycle.js +62 -12
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-db-reader.d.ts +5 -0
- package/dist/kiro-db-reader.js +18 -0
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +2 -0
- package/dist/kiro-engine-status.js +9 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-migrate.d.ts +60 -0
- package/dist/kiro-migrate.js +160 -0
- package/dist/kiro-migrate.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-transcript-lane.d.ts +2 -0
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/kiro-transcript-worker.js +1 -0
- package/dist/kiro-transcript-worker.js.map +1 -1
- package/dist/kiro-tui-forensic.d.ts +12 -0
- package/dist/kiro-tui-forensic.js +110 -0
- package/dist/kiro-tui-forensic.js.map +1 -0
- package/dist/kiro-usage-run.d.ts +7 -0
- package/dist/kiro-usage-run.js +39 -0
- package/dist/kiro-usage-run.js.map +1 -0
- package/dist/kiro-usage-scan.d.ts +49 -0
- package/dist/kiro-usage-scan.js +223 -0
- package/dist/kiro-usage-scan.js.map +1 -0
- package/dist/kiro-usage-worker.d.ts +1 -0
- package/dist/kiro-usage-worker.js +10 -0
- package/dist/kiro-usage-worker.js.map +1 -0
- package/dist/kiro-usage.d.ts +58 -0
- package/dist/kiro-usage.js +310 -0
- package/dist/kiro-usage.js.map +1 -0
- package/dist/locale.js +230 -8
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/login-url.d.ts +4 -0
- package/dist/login-url.js +22 -0
- package/dist/login-url.js.map +1 -0
- package/dist/needs-you-hub.d.ts +2 -1
- package/dist/needs-you-hub.js +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +10 -2
- package/dist/needs-you.js +10 -2
- package/dist/needs-you.js.map +1 -1
- package/dist/notice-delivery.d.ts +16 -0
- package/dist/notice-delivery.js +35 -0
- package/dist/notice-delivery.js.map +1 -0
- package/dist/office-share-page.d.ts +16 -0
- package/dist/office-share-page.js +85 -0
- package/dist/office-share-page.js.map +1 -0
- package/dist/office-share-service.d.ts +91 -0
- package/dist/office-share-service.js +176 -0
- package/dist/office-share-service.js.map +1 -0
- package/dist/office-share.d.ts +268 -0
- package/dist/office-share.js +574 -0
- package/dist/office-share.js.map +1 -0
- package/dist/opencode-usage-scan.d.ts +28 -0
- package/dist/opencode-usage-scan.js +92 -0
- package/dist/opencode-usage-scan.js.map +1 -0
- package/dist/opencode-usage-worker.d.ts +1 -0
- package/dist/opencode-usage-worker.js +10 -0
- package/dist/opencode-usage-worker.js.map +1 -0
- package/dist/outbound-handlers.js +22 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +65 -2
- package/dist/outbound-schemas.js +56 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +7 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.js +13 -2
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.js +2 -1
- package/dist/quickstart.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/reply-triggers.d.ts +2 -0
- package/dist/reply-triggers.js +3 -0
- package/dist/reply-triggers.js.map +1 -0
- package/dist/settings-api.d.ts +2 -0
- package/dist/settings-api.js +41 -16
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +20 -14
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +7 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-instance-patch.js +4 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +55 -6
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.d.ts +9 -0
- package/dist/setup-host.js +127 -6
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-signin-assets.d.ts +2 -0
- package/dist/setup-signin-assets.js +6 -0
- package/dist/setup-signin-assets.js.map +1 -0
- package/dist/setup-wizard.js +2 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/structured-signin-transport.d.ts +8 -0
- package/dist/structured-signin-transport.js +79 -0
- package/dist/structured-signin-transport.js.map +1 -0
- package/dist/structured-signin.d.ts +131 -0
- package/dist/structured-signin.js +388 -0
- package/dist/structured-signin.js.map +1 -0
- package/dist/tool-permissions.js +29 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.js +6 -2
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +27 -2
- package/dist/transcript-sources.js +104 -40
- package/dist/transcript-sources.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +1 -1
- package/dist/turn-reply-guard.js +1 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +33 -8
- package/dist/ui/backend-signin.js +63 -10
- package/dist/ui/chat-store.js +3 -2
- package/dist/ui/chat-strings.js +4 -4
- package/dist/ui/fleet-office-editor.js +68 -11
- package/dist/ui/fleet-office-layout.js +110 -35
- package/dist/ui/fleet-office-model.js +6 -3
- package/dist/ui/fleet-office-overview.js +167 -0
- package/dist/ui/fleet-office-pack.d.ts +13 -3
- package/dist/ui/fleet-office-pack.js +214 -24
- package/dist/ui/fleet-office-skin.js +98 -23
- package/dist/ui/fleet-office-store.js +82 -5
- package/dist/ui/fleet-office-view.js +5 -4
- package/dist/ui/fleet-office-zones.js +232 -21
- package/dist/ui/fleet-office.js +549 -186
- package/dist/ui/fleet-usage.js +19 -15
- package/dist/ui/office-share-dialog.js +145 -0
- package/dist/ui/office-share-page.js +164 -0
- package/dist/ui/panel-chat.js +22 -3
- package/dist/ui/panel-log.js +4 -4
- package/dist/ui/panel-needs.js +5 -5
- package/dist/ui/panel-settings.js +38 -12
- package/dist/ui/settings-dialogs.js +8 -1
- package/dist/ui/settings-guests.js +17 -7
- package/dist/ui/settings-model.js +22 -6
- package/dist/ui/settings-registry.js +4 -0
- package/dist/ui/settings-strings.js +18 -2
- package/dist/ui/shared/app-i18n.js +4 -2
- package/dist/ui/shared/app-shell.js +14 -6
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +85 -4
- package/dist/ui/shared/office/cozy.png +0 -0
- package/dist/ui/shared/side-panel.js +95 -0
- package/dist/ui/structured-signin.js +115 -0
- package/dist/usage-report.d.ts +20 -0
- package/dist/usage-report.js +54 -17
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +9 -1
- package/dist/usage-rollup.js +20 -4
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +23 -1
- package/dist/web-api.js +292 -3
- 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-backend-login.d.ts +1 -7
- package/dist/web-backend-login.js +2 -24
- package/dist/web-backend-login.js.map +1 -1
- package/dist/web-office-acts.d.ts +173 -0
- package/dist/web-office-acts.js +285 -0
- package/dist/web-office-acts.js.map +1 -0
- 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 +71 -6
- package/dist/web-office-skins.js +321 -35
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-office-store.d.ts +5 -2
- package/dist/web-office-store.js +69 -10
- package/dist/web-office-store.js.map +1 -1
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/working-recovery.d.ts +2 -2
- package/dist/working-recovery.js +43 -8
- package/dist/working-recovery.js.map +1 -1
- package/dist/working-state-journal.d.ts +2 -0
- package/dist/working-state-journal.js +16 -1
- package/dist/working-state-journal.js.map +1 -1
- package/package.json +3 -2
- package/templates/windows-shortcuts.ps1 +155 -140
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, kiroCanonicalDirectory, KiroIdentityError, } 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";
|
|
18
|
+
import { decideKiroModelError, readKiroKnownModels, readKiroModelRecords, readKiroV1SavedModel, readKiroV2SavedModel, writeKiroModelRecords, KIRO_FALLBACK_MODEL } from "./kiro-retired-model.js";
|
|
16
19
|
import { recordKiroLaunch } from "./kiro-engine-ledger.js";
|
|
17
20
|
import { forgetKiroV3Identity, resolveKiroV3Resume } from "./kiro-v3-identity.js";
|
|
18
21
|
// Kiro CLI feature gates. These are deliberately separate: the flags shipped
|
|
@@ -40,7 +43,7 @@ export const KIRO_INSTANCE_AGENT_MIN = "2.21.0";
|
|
|
40
43
|
* the binary's own --help instead of the version table, and the operator is
|
|
41
44
|
* told the version is unverified.
|
|
42
45
|
*/
|
|
43
|
-
export const KIRO_TESTED_MAX = "2.
|
|
46
|
+
export const KIRO_TESTED_MAX = "2.34.0";
|
|
44
47
|
/**
|
|
45
48
|
* kiro-cli versions, per TUI front-end, whose mid-turn steering AgEnD drives (#1405). Only kiro's TUI steers — typed
|
|
46
49
|
* input while a turn runs is injected into it ("steer", the default) or held for its end ("queue", Ctrl+S toggles); the
|
|
@@ -48,10 +51,13 @@ export const KIRO_TESTED_MAX = "2.33.0";
|
|
|
48
51
|
* queued message mid-tool — has been captured (tests/fixtures/kiro-steer-1405, 2026-10-08: 2.27.1 `--tui
|
|
49
52
|
* --agent-engine=v2` and `--v3`, 2.28.0 `--tui --agent-engine=v2`; identical rows on all three). Any other version,
|
|
50
53
|
* 2.28.0's v3, the legacy UI and an undetected version all deliver a steer as an ordinary message after the turn.
|
|
54
|
+
* 2.34.0 TUI was checked on the user-authorized #2598 D5 run (tests/fixtures/kiro-2.34.0-tui-d5):
|
|
55
|
+
* native steer during a read-only tool, a fleet message queued mid-tool, and a native queue entry in the busy turn.
|
|
51
56
|
*/
|
|
52
57
|
export const KIRO_STEER_VERIFIED = {
|
|
53
58
|
"2.27.1": ["tui", "v3"],
|
|
54
59
|
"2.28.0": ["tui"],
|
|
60
|
+
"2.34.0": ["tui"],
|
|
55
61
|
};
|
|
56
62
|
/**
|
|
57
63
|
* The TUI composer's interrupt mode, read off its own row: the LAST row starting at column 0 with `›` (transcript user
|
|
@@ -69,8 +75,10 @@ export const KIRO_STEER_VERIFIED = {
|
|
|
69
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
|
|
70
76
|
* pressed.
|
|
71
77
|
*/
|
|
72
|
-
|
|
73
|
-
|
|
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) {
|
|
74
82
|
let i = rows.length - 1;
|
|
75
83
|
while (i >= 0 && (rows[i] === "" || /^[ \t]{20,}\S/.test(rows[i])))
|
|
76
84
|
i--;
|
|
@@ -82,30 +90,47 @@ function readKiroComposer(pane) {
|
|
|
82
90
|
continued++;
|
|
83
91
|
}
|
|
84
92
|
const row = rows[i];
|
|
85
|
-
|
|
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)
|
|
86
99
|
return null;
|
|
100
|
+
let row = rows[at.index];
|
|
87
101
|
// A placeholder is one row: with continuation rows below it, this is typed text — unless its first row is another
|
|
88
|
-
// mode's placeholder
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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;
|
|
93
111
|
}
|
|
94
|
-
const working = String.raw `^›[ \t]+Kiro is working(?:[ \t]+·[ \t]+[^·]+?)?[ \t]+·[ \t]+`;
|
|
95
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]+`;
|
|
96
124
|
if (new RegExp(`${working}Type to steer[ \\t]+·[ \\t]+[^·]+?[ \\t]+to queue$`).test(row))
|
|
97
|
-
return
|
|
125
|
+
return "steer";
|
|
98
126
|
if (new RegExp(`${working}Type to queue(?:[ \\t]+·[ \\t]+[^·]+?[ \\t]+to steer)?$`).test(row))
|
|
99
|
-
return
|
|
127
|
+
return "queue";
|
|
100
128
|
const idle = String.raw `^›[ \t]+ask a question or describe a task`;
|
|
101
129
|
if (new RegExp(`${idle}[ \\t]+↵$`).test(row)
|
|
102
130
|
|| new RegExp(`${idle}[ \\t]+↵[ \\t]+·[ \\t]+exit plan mode: shift\\+tab$`).test(row)
|
|
103
131
|
|| new RegExp(`${idle}[ \\t]+·[ \\t]+/tangent to go back[ \\t]+·[ \\t]+/tangent ls to view$`).test(row))
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
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))
|
|
107
|
-
return null;
|
|
108
|
-
return { mode: "text", text };
|
|
132
|
+
return "idle";
|
|
133
|
+
return null;
|
|
109
134
|
}
|
|
110
135
|
export function readKiroSteerComposer(pane) {
|
|
111
136
|
return readKiroComposer(pane)?.mode ?? null;
|
|
@@ -115,6 +140,178 @@ export function readKiroComposerText(pane) {
|
|
|
115
140
|
const c = readKiroComposer(pane);
|
|
116
141
|
return c?.mode === "text" ? c.text : null;
|
|
117
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
|
+
}
|
|
118
315
|
const UNKNOWN_KIRO_COMPATIBILITY = {
|
|
119
316
|
supportsLegacyUi: false,
|
|
120
317
|
supportsTui: false,
|
|
@@ -133,11 +330,12 @@ const warnedVersionGateCacheKeys = new Set();
|
|
|
133
330
|
* This broad test is only a safety hold: it must never select an option or
|
|
134
331
|
* claim a service outage. A ready prompt below the picker makes it history.
|
|
135
332
|
*/
|
|
333
|
+
const KIRO_MODEL_PICKER_HEADER = /^\s*Select model(?: \(type to search\):|:\s+type to search)\s*$/m;
|
|
136
334
|
function kiroModelPickerAtTail(pane) {
|
|
137
335
|
const lines = pane.replace(/\r/g, "").split("\n");
|
|
138
336
|
let pickerIndex = -1;
|
|
139
337
|
for (let index = lines.length - 1; index >= 0; index--) {
|
|
140
|
-
if (
|
|
338
|
+
if (KIRO_MODEL_PICKER_HEADER.test(lines[index])) {
|
|
141
339
|
pickerIndex = index;
|
|
142
340
|
break;
|
|
143
341
|
}
|
|
@@ -145,7 +343,8 @@ function kiroModelPickerAtTail(pane) {
|
|
|
145
343
|
if (pickerIndex < 0)
|
|
146
344
|
return false;
|
|
147
345
|
const tail = lines.slice(pickerIndex + 1);
|
|
148
|
-
|
|
346
|
+
const composer = readKiroSteerComposer(tail.join("\n"));
|
|
347
|
+
if (tail.some(line => /^\s*\d+%[^\n]*[!❯>]\s+\S/.test(line)) || (composer !== null && composer !== "text"))
|
|
149
348
|
return false;
|
|
150
349
|
return tail.some(line => /^\s*[>❯›]\s*\*?\s*\S/.test(line));
|
|
151
350
|
}
|
|
@@ -330,6 +529,205 @@ function kiroWorkspaceTrustDialogs(trustAll) {
|
|
|
330
529
|
},
|
|
331
530
|
];
|
|
332
531
|
}
|
|
532
|
+
const KIRO_MCP_INPUT_HEADER = /^\s*Input requested · \S.*$/;
|
|
533
|
+
const KIRO_MCP_INPUT_FOOTER = /^\s*esc to cancel · ↑↓ to navigate · enter to select(?: · ctrl\+s to submit)?$/;
|
|
534
|
+
/**
|
|
535
|
+
* #2587: kiro-cli 2.34.0's V3 engine answers an MCP server's `elicitation/create` (a tool asking the user for input)
|
|
536
|
+
* with a card in place of the composer: a divider, `Input requested · <server>`, a divider, the server's message and
|
|
537
|
+
* one row per form field then Submit / Decline (form mode) — or Host, URL and Open in browser / Copy URL / I'll open it
|
|
538
|
+
* myself / Decline / Cancel (url mode) — with exactly one `❯` cursor row, a divider, and the key hints as the last row
|
|
539
|
+
* (tests/fixtures/kiro-2.34.0-audit). Bottom-anchored: the card is live only while it is what the pane ends with. The
|
|
540
|
+
* answer can carry credentials or a sign-in, so it is the person's: never a key, and no delivery's Enter may land in it.
|
|
541
|
+
*/
|
|
542
|
+
export function kiroMcpInputRequestActive(pane) {
|
|
543
|
+
const rows = pane.replace(/\r/g, "").split("\n").map(row => row.trimEnd());
|
|
544
|
+
let last = rows.length - 1;
|
|
545
|
+
while (last >= 0 && !rows[last].trim())
|
|
546
|
+
last--;
|
|
547
|
+
if (last < 0 || !KIRO_MCP_INPUT_FOOTER.test(rows[last]))
|
|
548
|
+
return false;
|
|
549
|
+
const divider = (row) => row !== undefined && /^─{3,}$/.test(row.trim());
|
|
550
|
+
if (!divider(rows[last - 1]))
|
|
551
|
+
return false;
|
|
552
|
+
let header = -1;
|
|
553
|
+
for (let index = last - 2; index >= 1; index--) {
|
|
554
|
+
if (KIRO_MCP_INPUT_HEADER.test(rows[index])) {
|
|
555
|
+
header = index;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (header < 0 || !divider(rows[header - 1]) || !divider(rows[header + 1]))
|
|
560
|
+
return false;
|
|
561
|
+
const body = rows.slice(header + 2, last - 1);
|
|
562
|
+
// A V3 composer (`›` at column 0) inside means the card is history. A lone `>` is a form field's own input caret.
|
|
563
|
+
if (body.some(row => divider(row) || /^›(?:[ \t]|$)/.test(row)))
|
|
564
|
+
return false;
|
|
565
|
+
return body.filter(row => /^\s*❯\s+\S/.test(row)).length === 1;
|
|
566
|
+
}
|
|
567
|
+
const KIRO_QUESTION_HEADER = /^\s*(?:\S.* > )?Question$/;
|
|
568
|
+
const KIRO_QUESTION_FOOTER = /^\s*esc to (?:cancel|go back) · ↑↓ to navigate(?: · space to toggle)? · ↵ to submit$/;
|
|
569
|
+
/**
|
|
570
|
+
* #2592: kiro's V3 engine shows the agent's question to the user (`question_request`) as a card in place of the
|
|
571
|
+
* composer: a divider, `Question` (or `<name> > Question`), the question, numbered options (a description row under
|
|
572
|
+
* one, `(recommended)` after one) and `Type a different answer…`, a divider, and the key hints as the last row — or, in
|
|
573
|
+
* an option's sub-options, `[✓] <title>` rows and `esc to go back · … · space to toggle · ↵ to submit`
|
|
574
|
+
* (tests/fixtures/kiro-question-2592; unchanged from 2.33.1 to 2.34.0). Any typed character turns the card into a free
|
|
575
|
+
* answer and Enter submits it, so a delivered message would become the answer to a question nobody read. The person
|
|
576
|
+
* answers it in the pane (the user's decision on #2592); it is never keyed. Bottom-anchored like
|
|
577
|
+
* kiroMcpInputRequestActive: the header is the row right under the nearest divider above the key hints' divider.
|
|
578
|
+
*/
|
|
579
|
+
export function kiroQuestionCardActive(pane) {
|
|
580
|
+
const rows = pane.replace(/\r/g, "").split("\n").map(row => row.trimEnd());
|
|
581
|
+
let last = rows.length - 1;
|
|
582
|
+
while (last >= 0 && !rows[last].trim())
|
|
583
|
+
last--;
|
|
584
|
+
if (last < 0 || !KIRO_QUESTION_FOOTER.test(rows[last]))
|
|
585
|
+
return false;
|
|
586
|
+
const divider = (row) => row !== undefined && /^─{3,}$/.test(row.trim());
|
|
587
|
+
if (!divider(rows[last - 1]))
|
|
588
|
+
return false;
|
|
589
|
+
let top = last - 2;
|
|
590
|
+
while (top >= 0 && !divider(rows[top]))
|
|
591
|
+
top--;
|
|
592
|
+
if (top < 0 || !KIRO_QUESTION_HEADER.test(rows[top + 1] ?? ""))
|
|
593
|
+
return false;
|
|
594
|
+
const body = rows.slice(top + 2, last - 1);
|
|
595
|
+
// The question, then the options: a V3 composer (`›` at column 0) among them means the card is history.
|
|
596
|
+
if (body.length < 2 || body.some(row => /^›(?:[ \t]|$)/.test(row)))
|
|
597
|
+
return false;
|
|
598
|
+
return body.filter(row => /^\s*❯\s+\S/.test(row)).length === 1;
|
|
599
|
+
}
|
|
600
|
+
/** #2675: the live card's rows — the header row, the body between it and the key hints' divider, and the key hints. */
|
|
601
|
+
function kiroCardRows(pane, headerAt) {
|
|
602
|
+
const rows = pane.replace(/\r/g, "").split("\n").map(row => row.trimEnd());
|
|
603
|
+
let last = rows.length - 1;
|
|
604
|
+
while (last >= 0 && !rows[last].trim())
|
|
605
|
+
last--;
|
|
606
|
+
const header = headerAt(rows, last);
|
|
607
|
+
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 };
|
|
608
|
+
}
|
|
609
|
+
const KIRO_QUESTION_OPTION_ROW = /^ (❯| ) (\d+)\. (.*?)( \(recommended\))?$/;
|
|
610
|
+
const KIRO_QUESTION_SUBOPTION_ROW = /^ (❯| ) \[(✓| )\] (.*)$/;
|
|
611
|
+
const KIRO_QUESTION_FREE_ROW = /^ (❯| ) (Type a different answer…|or type feedback to request changes)$/;
|
|
612
|
+
/**
|
|
613
|
+
* #2675: the live Question card (see kiroQuestionCardActive), read for a person to answer from the chat: the question,
|
|
614
|
+
* the numbered options (`(recommended)` and the description row under one), the typed-answer row, and where the `❯`
|
|
615
|
+
* is (tui.js `A0`; tests/fixtures/kiro-question-2592). On a sub-options page the rows are the `[✓]` boxes; while an
|
|
616
|
+
* answer is being typed the last row is `❯ <text>`. A row the shapes above do not account for is a wrapped title (the
|
|
617
|
+
* card's own indent) or description (deeper), joined to the row it continues; one before any option belongs to the
|
|
618
|
+
* question. Null when the card is not live, or when it is in a shape this reader does not know.
|
|
619
|
+
*/
|
|
620
|
+
export function readKiroQuestionCard(pane) {
|
|
621
|
+
if (!kiroQuestionCardActive(pane))
|
|
622
|
+
return null;
|
|
623
|
+
const card = kiroCardRows(pane, (rows, last) => {
|
|
624
|
+
let top = last - 2;
|
|
625
|
+
while (top >= 0 && !/^─{3,}$/.test(rows[top].trim()))
|
|
626
|
+
top--;
|
|
627
|
+
return top + 1;
|
|
628
|
+
});
|
|
629
|
+
if (!card)
|
|
630
|
+
return null;
|
|
631
|
+
const page = /space to toggle/.test(card.footer) ? "suboptions" : "options";
|
|
632
|
+
const question = [];
|
|
633
|
+
const options = [];
|
|
634
|
+
let cursor = null, freeText = false, typed = null, freeRow = false;
|
|
635
|
+
for (const row of card.body) {
|
|
636
|
+
const option = page === "options" ? KIRO_QUESTION_OPTION_ROW.exec(row) : null;
|
|
637
|
+
const sub = page === "suboptions" ? KIRO_QUESTION_SUBOPTION_ROW.exec(row) : null;
|
|
638
|
+
const free = page === "options" ? KIRO_QUESTION_FREE_ROW.exec(row) : null;
|
|
639
|
+
if (freeRow)
|
|
640
|
+
return null; // nothing follows the typed-answer row
|
|
641
|
+
if (option && Number(option[2]) === options.length + 1) {
|
|
642
|
+
if (option[1] === "❯")
|
|
643
|
+
cursor = options.length;
|
|
644
|
+
options.push({ n: options.length + 1, title: option[3], recommended: option[4] !== undefined, description: null });
|
|
645
|
+
}
|
|
646
|
+
else if (sub) {
|
|
647
|
+
if (sub[1] === "❯")
|
|
648
|
+
cursor = options.length;
|
|
649
|
+
options.push({ n: options.length + 1, title: sub[3], recommended: false, description: null, checked: sub[2] === "✓" });
|
|
650
|
+
}
|
|
651
|
+
else if (free) {
|
|
652
|
+
freeText = true;
|
|
653
|
+
freeRow = true;
|
|
654
|
+
if (free[1] === "❯")
|
|
655
|
+
cursor = options.length;
|
|
656
|
+
}
|
|
657
|
+
else if (page === "options" && options.length > 0 && /^ ❯ /.test(row)) {
|
|
658
|
+
freeText = true;
|
|
659
|
+
freeRow = true;
|
|
660
|
+
typed = row.slice(3);
|
|
661
|
+
}
|
|
662
|
+
else if (!row.trim()) {
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
else if (options.length === 0) {
|
|
666
|
+
question.push(row.replace(/^ /, ""));
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
const lastOption = options[options.length - 1];
|
|
670
|
+
if (/^ {5,}\S/.test(row))
|
|
671
|
+
lastOption.description = lastOption.description === null ? row.trim() : `${lastOption.description} ${row.trim()}`;
|
|
672
|
+
else if (lastOption.description === null)
|
|
673
|
+
lastOption.title = `${lastOption.title} ${row.trim()}`;
|
|
674
|
+
else
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (options.length === 0 || question.length === 0)
|
|
679
|
+
return null;
|
|
680
|
+
const typing = typed !== null;
|
|
681
|
+
if (typing ? cursor !== null : cursor === null)
|
|
682
|
+
return null;
|
|
683
|
+
const titles = options.map(option => `${option.n}. ${option.title}`).join("\n");
|
|
684
|
+
return {
|
|
685
|
+
kind: "question", identity: `${card.header}\n${question.join("\n")}\n${page === "suboptions" ? "sub" : "options"}\n${titles}`,
|
|
686
|
+
title: card.header, question: question.join("\n").trim(), page: typing ? "freetext" : page, options, freeText, cursor, typed, rowWidth: card.width,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
const KIRO_MCP_INPUT_ACTIONS = new Set(["Submit", "Decline", "Open in browser", "Copy URL", "I'll open it myself", "Cancel"]);
|
|
690
|
+
/**
|
|
691
|
+
* #2675: the live MCP `Input requested` card (see kiroMcpInputRequestActive), read for the chat: the server, the body
|
|
692
|
+
* as painted, with the `URL:` row masked (a sign-in link stays in the pane), its action rows, and the `❯` row. Its
|
|
693
|
+
* identity is the server, the mode, the message's first row and the actions: moving or filling in a field keeps it.
|
|
694
|
+
*/
|
|
695
|
+
export function readKiroMcpInputCard(pane) {
|
|
696
|
+
if (!kiroMcpInputRequestActive(pane))
|
|
697
|
+
return null;
|
|
698
|
+
const card = kiroCardRows(pane, (rows, last) => {
|
|
699
|
+
for (let index = last - 2; index >= 1; index--)
|
|
700
|
+
if (KIRO_MCP_INPUT_HEADER.test(rows[index]))
|
|
701
|
+
return index;
|
|
702
|
+
return -1;
|
|
703
|
+
});
|
|
704
|
+
if (!card)
|
|
705
|
+
return null;
|
|
706
|
+
// The URL row is masked, and so is every row after it up to the next action row: a long URL wraps (#2677 review).
|
|
707
|
+
let inUrl = false;
|
|
708
|
+
const body = card.body.slice(1).flatMap(row => {
|
|
709
|
+
if (/^\s*URL: /.test(row)) {
|
|
710
|
+
inUrl = true;
|
|
711
|
+
return [row.replace(/URL: .*$/, "URL: (shown in the instance pane)")];
|
|
712
|
+
}
|
|
713
|
+
if (inUrl && KIRO_MCP_INPUT_ACTIONS.has(row.replace(/^ ❯ /, " ").trim()))
|
|
714
|
+
inUrl = false;
|
|
715
|
+
return inUrl ? [] : [row];
|
|
716
|
+
});
|
|
717
|
+
const strip = (row) => row.replace(/^ ❯ /, " ").trim();
|
|
718
|
+
const cursorRow = body.find(row => /^\s*❯\s+\S/.test(row));
|
|
719
|
+
if (!cursorRow)
|
|
720
|
+
return null;
|
|
721
|
+
return {
|
|
722
|
+
kind: "input", server: card.header.replace(/^Input requested · /, ""),
|
|
723
|
+
mode: /ctrl\+s to submit/.test(card.footer) ? "form" : "url",
|
|
724
|
+
rows: body.map(row => row.replace(/^ ❯ /, " ").replace(/^ {3}/, "")).filter(row => row.trim()),
|
|
725
|
+
actions: body.map(strip).filter(row => KIRO_MCP_INPUT_ACTIONS.has(row)),
|
|
726
|
+
cursorRow: strip(cursorRow),
|
|
727
|
+
// The focused field paints extra rows (its value, its caret) and a typed value changes its row: neither is another request.
|
|
728
|
+
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("|")}`,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
333
731
|
/**
|
|
334
732
|
* Kiro's service-unavailable path opens the same picker as `/model`, but only
|
|
335
733
|
* the former needs incident escalation. The runtime scanner and delivery gate
|
|
@@ -342,7 +740,7 @@ export function kiroUnavailableModelPickerActive(pane) {
|
|
|
342
740
|
const lines = pane.replace(/\r/g, "").split("\n");
|
|
343
741
|
let pickerIndex = -1;
|
|
344
742
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
345
|
-
if (
|
|
743
|
+
if (KIRO_MODEL_PICKER_HEADER.test(lines[i])) {
|
|
346
744
|
pickerIndex = i;
|
|
347
745
|
break;
|
|
348
746
|
}
|
|
@@ -634,6 +1032,17 @@ function compatibilityFromHelp(version, help) {
|
|
|
634
1032
|
source: "help",
|
|
635
1033
|
};
|
|
636
1034
|
}
|
|
1035
|
+
/**
|
|
1036
|
+
* #2598 (D1): kiro-cli-chat skips its self-update when this is set (2.30.0 and 2.34.0 both read it). AgEnD pins the
|
|
1037
|
+
* kiro version it has audited; an in-place update can swap the engine a running fleet depends on (3.0 drops legacy).
|
|
1038
|
+
* Every chat AgEnD starts carries it: the instance launch (a shell prefix) and the probes (their child env). The
|
|
1039
|
+
* host-wide `app.disableAutoupdates` setting is the operator's half; this holds even where that was never set.
|
|
1040
|
+
*/
|
|
1041
|
+
export const KIRO_NO_AUTO_UPDATE = "KIRO_NO_AUTO_UPDATE";
|
|
1042
|
+
/** A kiro child's environment: the given one (default: ours) with self-update off. */
|
|
1043
|
+
export function kiroChildEnv(base = process.env) {
|
|
1044
|
+
return { ...base, [KIRO_NO_AUTO_UPDATE]: "1" };
|
|
1045
|
+
}
|
|
637
1046
|
/**
|
|
638
1047
|
* Probe once per binary generation. A version AgEnD has run (<= TESTED_MAX)
|
|
639
1048
|
* is answered from the table without a second CLI call; anything newer, or a
|
|
@@ -645,6 +1054,7 @@ export function probeKiroCliCompatibility(binaryPath, run = (binary, args) => me
|
|
|
645
1054
|
encoding: "utf-8",
|
|
646
1055
|
timeout: 5000,
|
|
647
1056
|
stdio: ["ignore", "pipe", "ignore"],
|
|
1057
|
+
env: kiroChildEnv(),
|
|
648
1058
|
}))) {
|
|
649
1059
|
return measureSyncWork("kiro.compatibilitySync", () => probeKiroCliCompatibilitySync(binaryPath, run));
|
|
650
1060
|
}
|
|
@@ -673,6 +1083,8 @@ function nextPatch(version) {
|
|
|
673
1083
|
const [major, minor, patch] = parseSemver(version);
|
|
674
1084
|
return `${major}.${minor}.${patch + 1}`;
|
|
675
1085
|
}
|
|
1086
|
+
/** #2598 B3: engines in the order a conversation can move between them (never back). */
|
|
1087
|
+
const KIRO_ENGINE_RANK = { legacy: 1, tui: 2, v3: 3 };
|
|
676
1088
|
/** Binary generations already told they cannot isolate (once each). */
|
|
677
1089
|
const warnedNoInstanceAgentKeys = new Set();
|
|
678
1090
|
/** Instances already told about an untagged steering file (once each). */
|
|
@@ -755,6 +1167,34 @@ export function readActiveKiroAgent(pane, ui) {
|
|
|
755
1167
|
}
|
|
756
1168
|
return null;
|
|
757
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
|
+
}
|
|
758
1198
|
export function planKiroLaunch(ui, compat) {
|
|
759
1199
|
const version = compat.version ?? "of unknown version";
|
|
760
1200
|
if (ui === "v3")
|
|
@@ -885,6 +1325,9 @@ export const KIRO_RATE_LIMIT_SCREEN = new RegExp(KIRO_MESSAGE_LEAD + wrapToleran
|
|
|
885
1325
|
/** #1636: kiro's monthly cap (MonthlyLimitReached branch); see getErrorPatterns. */
|
|
886
1326
|
export const KIRO_REQUEST_LIMIT_SCREEN = new RegExp(KIRO_MESSAGE_LEAD + "(?:" + ["Hourly", "Daily", "Weekly", "Monthly"].map(wrapTolerant).join("|") + ")"
|
|
887
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");
|
|
888
1331
|
/** Startup budget for a `--resume` launch (60% first output, 40% ready). */
|
|
889
1332
|
export const KIRO_RESUME_STARTUP_BUDGET_MS = 60_000;
|
|
890
1333
|
export class KiroBackend {
|
|
@@ -913,6 +1356,8 @@ export class KiroBackend {
|
|
|
913
1356
|
activeTrustAll = true;
|
|
914
1357
|
/** Version-gate notice for the launch just built (consumeLaunchWarning). */
|
|
915
1358
|
launchWarning = null;
|
|
1359
|
+
/** #2648: what this launch's model came from, for a model error to be judged by (decideKiroModelError). */
|
|
1360
|
+
launchModels = null;
|
|
916
1361
|
/**
|
|
917
1362
|
* What this launch attempt resumes and as which agent (#906), resolved once per attempt in writeConfig — it claims
|
|
918
1363
|
* conversations and records fresh starts — and read by the buildCommand that follows it.
|
|
@@ -923,7 +1368,10 @@ export class KiroBackend {
|
|
|
923
1368
|
/** #1218: the last launch built in full (cleared when a build starts, set only when it returns). */
|
|
924
1369
|
builtLaunch = null;
|
|
925
1370
|
preparedV2Store = null;
|
|
1371
|
+
/** #2648: the saved model of the v2 conversation this instance has recorded, read in prepareLaunch (off the loop). */
|
|
1372
|
+
preparedV2Model = null;
|
|
926
1373
|
storePreparation = 0;
|
|
1374
|
+
preparedCarry = null;
|
|
927
1375
|
storeKey(config) {
|
|
928
1376
|
return JSON.stringify([config.instanceName, config.workingDirectory, kiroCanonicalDirectory(config.workingDirectory), config.kiroUi,
|
|
929
1377
|
resolveCredentialProfile(config.backendOptions), !!config.skipResume, getAgendHome(), kiroV2SessionsDir(), kiroCanonicalDirectory(kiroV2SessionsDir())]);
|
|
@@ -939,6 +1387,8 @@ export class KiroBackend {
|
|
|
939
1387
|
async prepareLaunch(config) {
|
|
940
1388
|
const preparation = ++this.storePreparation;
|
|
941
1389
|
this.preparedV2Store = null;
|
|
1390
|
+
this.preparedV2Model = null;
|
|
1391
|
+
this.preparedCarry = null;
|
|
942
1392
|
if (!config || config.kiroUi !== "tui")
|
|
943
1393
|
return;
|
|
944
1394
|
if (!this.fixedCompatibility)
|
|
@@ -949,11 +1399,63 @@ export class KiroBackend {
|
|
|
949
1399
|
return;
|
|
950
1400
|
const opts = { instance: config.instanceName, engine: "v2", workingDirectory: config.workingDirectory,
|
|
951
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
|
+
}
|
|
952
1425
|
if (kiroIdentityNeedsStore(opts)) {
|
|
953
1426
|
const store = await listKiroV2Sessions(config.workingDirectory, kiroV2SessionsDir());
|
|
954
1427
|
if (preparation === this.storePreparation && key === this.storeKey(config))
|
|
955
1428
|
this.preparedV2Store = { key, store };
|
|
956
1429
|
}
|
|
1430
|
+
// #2648: only for an instance with no model of its own, and only the conversation it has recorded (the resolver
|
|
1431
|
+
// in writeConfig may pick another — then nothing is foreseen, and a model error is handled after the launch).
|
|
1432
|
+
const recorded = !config.model && !config.skipResume ? kiroRecordedConversation(opts) : null;
|
|
1433
|
+
if (recorded) {
|
|
1434
|
+
const model = await readKiroV2SavedModel(kiroV2SessionsDir(), recorded);
|
|
1435
|
+
if (preparation === this.storePreparation && key === this.storeKey(config))
|
|
1436
|
+
this.preparedV2Model = { key, id: recorded, model };
|
|
1437
|
+
}
|
|
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"));
|
|
957
1459
|
}
|
|
958
1460
|
constructor(instanceDir, compatibility) {
|
|
959
1461
|
this.instanceDir = instanceDir;
|
|
@@ -1009,6 +1511,10 @@ export class KiroBackend {
|
|
|
1009
1511
|
readSteerComposerText(pane) {
|
|
1010
1512
|
return this.activeUi === "legacy" ? null : readKiroComposerText(pane);
|
|
1011
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
|
+
}
|
|
1012
1518
|
/**
|
|
1013
1519
|
* Live evidence (kiro-cli 2.21.0, 2026-09-03): `--resume` prints nothing
|
|
1014
1520
|
* until the conversation comes back from the backend ("Picking up where we
|
|
@@ -1020,6 +1526,18 @@ export class KiroBackend {
|
|
|
1020
1526
|
getStartupBudgetMs(ctx) {
|
|
1021
1527
|
return ctx.resume ? KIRO_RESUME_STARTUP_BUDGET_MS : undefined;
|
|
1022
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
|
+
}
|
|
1023
1541
|
retriesResumeOnStartupFailure() {
|
|
1024
1542
|
// A resume miss is far more often "the backend was slow" than "the session
|
|
1025
1543
|
// is broken"; one more attempt before abandoning the conversation is cheap.
|
|
@@ -1054,7 +1572,14 @@ export class KiroBackend {
|
|
|
1054
1572
|
// V3 has a different engine and no verified completion signal yet (#1144).
|
|
1055
1573
|
this.replyGuardOptedIn = false;
|
|
1056
1574
|
this.builtLaunch = null;
|
|
1575
|
+
this.assertCarryPrepared(config);
|
|
1057
1576
|
const ui = config.kiroUi ?? "legacy";
|
|
1577
|
+
// #2598 B3: never back onto an older engine than this instance has run on — that resumes the frozen copy and loses
|
|
1578
|
+
// everything done since. Before any probe: no kiro-cli flag or version changes the answer.
|
|
1579
|
+
const floor = kiroEngineFloor(getAgendHome(), config.instanceName);
|
|
1580
|
+
if (floor && KIRO_ENGINE_RANK[ui] < KIRO_ENGINE_RANK[floor === "v2" ? "tui" : "v3"]) {
|
|
1581
|
+
throw new UnsupportedCliError(t("kiro.engine_floor", config.instanceName, ui, floor === "v2" ? "tui" : "v3"));
|
|
1582
|
+
}
|
|
1058
1583
|
// Every launch re-reads the binary generation: a crash-respawn reuses this
|
|
1059
1584
|
// backend, and kiro-cli may have replaced itself in place since the last
|
|
1060
1585
|
// launch (an auto-update to a release without this instance's engine).
|
|
@@ -1075,7 +1600,8 @@ export class KiroBackend {
|
|
|
1075
1600
|
const plan = planKiroLaunch(ui, this.compatibility);
|
|
1076
1601
|
if (plan.kind === "refuse")
|
|
1077
1602
|
throw new UnsupportedCliError(plan.reason);
|
|
1078
|
-
|
|
1603
|
+
// #2598: a shell assignment, so it reaches kiro-cli-chat through `kiro-cli chat` and any credential-home prefix.
|
|
1604
|
+
let cmd = `${KIRO_NO_AUTO_UPDATE}=1 ${this.binaryPath} chat`;
|
|
1079
1605
|
for (const flag of plan.flags)
|
|
1080
1606
|
cmd += ` ${flag}`;
|
|
1081
1607
|
this.noteVersionGate();
|
|
@@ -1119,17 +1645,32 @@ export class KiroBackend {
|
|
|
1119
1645
|
if (id)
|
|
1120
1646
|
cmd += ` --resume-id ${shellQuote(id)}`;
|
|
1121
1647
|
}
|
|
1122
|
-
else if (!config.skipResume) {
|
|
1123
|
-
// Legacy mode, classic
|
|
1648
|
+
else if (!config.skipResume && ui === "legacy") {
|
|
1649
|
+
// Legacy mode, classic: the boolean form — kiro resumes the directory's newest conversation. Not isolated
|
|
1124
1650
|
// between instances sharing the directory (#1410); an instance launches like this only without the
|
|
1125
1651
|
// per-instance plan (an old kiro-cli, or a conversation store AgEnD could not read on adoption).
|
|
1126
1652
|
cmd += " --resume";
|
|
1127
1653
|
}
|
|
1654
|
+
else if (!config.skipResume && ui === "tui") {
|
|
1655
|
+
// #2598 B3: never the boolean form under the TUI — it takes the directory's newest conversation from EITHER
|
|
1656
|
+
// engine, and on a classic one makes a TUI fork of it (the wrong conversation, possibly another instance's). A
|
|
1657
|
+
// TUI launch without its per-instance plan starts fresh instead, and says so.
|
|
1658
|
+
this.addLaunchWarning(t("kiro.tui_no_plan_fresh"));
|
|
1659
|
+
}
|
|
1660
|
+
const resumed = lp.mode === "resume" && lp.id && (lp.engine === "v1" || lp.engine === "v2") ? { engine: lp.engine, id: lp.id } : null;
|
|
1661
|
+
const fallbackModel = !config.model && resumed && config.instanceDir
|
|
1662
|
+
? this.retiredModelFallback(config, resumed, credentialProfile) : null;
|
|
1663
|
+
this.launchModels = config.instanceDir
|
|
1664
|
+
? { instanceDir: config.instanceDir, ownModel: config.model ?? null, launchModel: config.model ?? fallbackModel, conversation: resumed?.id ?? null }
|
|
1665
|
+
: null;
|
|
1128
1666
|
if (config.model) {
|
|
1129
1667
|
const model = validateModel(config.model);
|
|
1130
1668
|
warnIfModelMismatch("kiro-cli", model);
|
|
1131
1669
|
cmd += ` --model ${shellQuote(model)}`;
|
|
1132
1670
|
}
|
|
1671
|
+
else if (fallbackModel) {
|
|
1672
|
+
cmd += ` --model ${shellQuote(validateModel(fallbackModel))}`;
|
|
1673
|
+
}
|
|
1133
1674
|
if (config.effort) {
|
|
1134
1675
|
const effort = validateEffort(config.effort);
|
|
1135
1676
|
if (this.compatibility.supportsEffortFlag) {
|
|
@@ -1219,6 +1760,7 @@ export class KiroBackend {
|
|
|
1219
1760
|
return true;
|
|
1220
1761
|
}
|
|
1221
1762
|
writeConfig(config) {
|
|
1763
|
+
this.assertCarryPrepared(config);
|
|
1222
1764
|
// WORKAROUND: kiro-cli ignores the "env" block of an MCP server entry — the server subprocess inherits the fleet
|
|
1223
1765
|
// manager's env, with a stale AGEND_SOCKET_PATH from whichever daemon wrote last. Each server therefore runs
|
|
1224
1766
|
// through a wrapper script that exports this instance's env and execs the real server.
|
|
@@ -1290,6 +1832,59 @@ export class KiroBackend {
|
|
|
1290
1832
|
this.addLaunchWarning(t("kiro.untagged_steering"));
|
|
1291
1833
|
}
|
|
1292
1834
|
}
|
|
1835
|
+
/**
|
|
1836
|
+
* #2648 steps 1 and 3: the model a resumed conversation is launched on instead of its saved one, or null to launch as
|
|
1837
|
+
* before. Only for an instance with no model of its own. The conversation's current saved model decides, every
|
|
1838
|
+
* launch: one kiro's cached list no longer has is switched to kiro's default; one it has — or, without a list, one
|
|
1839
|
+
* other than the model that failed (a later /model choice, or the default kiro saved after a switch) — is launched
|
|
1840
|
+
* as saved, even after an earlier switch (#2649 review). Only when the saved model cannot be read does a recorded
|
|
1841
|
+
* switch (step 2's restart) still apply. Announced once per conversation; the record also keeps that dedupe.
|
|
1842
|
+
*/
|
|
1843
|
+
retiredModelFallback(config, resumed, credentialProfile) {
|
|
1844
|
+
const records = readKiroModelRecords(config.instanceDir);
|
|
1845
|
+
let entry = records.switched[resumed.id];
|
|
1846
|
+
const known = readKiroKnownModels(getAgendHome());
|
|
1847
|
+
const prepared = this.preparedV2Model;
|
|
1848
|
+
const saved = resumed.engine === "v1"
|
|
1849
|
+
? readKiroV1SavedModel({ id: resumed.id, workingDirectory: config.workingDirectory, dbPath: kiroV1DbPath(credentialProfile) })
|
|
1850
|
+
: prepared && prepared.key === this.storeKey(config) && prepared.id === resumed.id ? prepared.model : null;
|
|
1851
|
+
if (saved) {
|
|
1852
|
+
// Step 2's restart recorded no model: the one saved now is the one that failed — unless it is the default the
|
|
1853
|
+
// switch put there (kiro saves the model a launch ran on).
|
|
1854
|
+
if (entry && entry.from === null && saved !== entry.to)
|
|
1855
|
+
entry.from = saved;
|
|
1856
|
+
const supported = known ? known.models.has(saved) : !!entry && (saved === entry.to || (entry.from !== null && saved !== entry.from));
|
|
1857
|
+
if (supported || (!known && !entry)) {
|
|
1858
|
+
if (entry && entry.from !== saved)
|
|
1859
|
+
this.saveModelRecords(config.instanceDir, records);
|
|
1860
|
+
return null;
|
|
1861
|
+
}
|
|
1862
|
+
if (!entry || entry.from !== saved)
|
|
1863
|
+
entry = records.switched[resumed.id] = { from: saved, to: known?.defaultModel ?? entry?.to ?? KIRO_FALLBACK_MODEL, announced: entry?.announced ?? false };
|
|
1864
|
+
}
|
|
1865
|
+
else if (!entry) {
|
|
1866
|
+
return null;
|
|
1867
|
+
}
|
|
1868
|
+
if (!entry.announced) {
|
|
1869
|
+
this.addLaunchWarning(t("kiro.retired_model_switched", config.instanceName, entry.from ?? t("kiro.retired_model_unknown"), entry.to));
|
|
1870
|
+
entry.announced = true;
|
|
1871
|
+
}
|
|
1872
|
+
this.saveModelRecords(config.instanceDir, records);
|
|
1873
|
+
return entry.to;
|
|
1874
|
+
}
|
|
1875
|
+
saveModelRecords(instanceDir, records) {
|
|
1876
|
+
try {
|
|
1877
|
+
writeKiroModelRecords(instanceDir, records);
|
|
1878
|
+
}
|
|
1879
|
+
catch { /* the next launch decides again; at worst it says so again */ }
|
|
1880
|
+
}
|
|
1881
|
+
/** #2648 steps 2 and 4: what a model error on this launch should do (records it); null before any launch. */
|
|
1882
|
+
onModelError() {
|
|
1883
|
+
const m = this.launchModels;
|
|
1884
|
+
if (!m)
|
|
1885
|
+
return null;
|
|
1886
|
+
return decideKiroModelError(m.instanceDir, m, readKiroKnownModels(getAgendHome())?.defaultModel ?? KIRO_FALLBACK_MODEL);
|
|
1887
|
+
}
|
|
1293
1888
|
addLaunchWarning(message) {
|
|
1294
1889
|
this.launchWarning = this.launchWarning ? `${this.launchWarning}\n${message}` : message;
|
|
1295
1890
|
}
|
|
@@ -1656,7 +2251,7 @@ export class KiroBackend {
|
|
|
1656
2251
|
// picker. Its choices range across credit multipliers and capability
|
|
1657
2252
|
// tiers, so this must be a human decision, never an automatic Enter or
|
|
1658
2253
|
// Escape. The ordinary /model menu alone is intentionally not matched.
|
|
1659
|
-
pattern:
|
|
2254
|
+
pattern: KIRO_MODEL_PICKER_HEADER,
|
|
1660
2255
|
keys: [],
|
|
1661
2256
|
description: "Kiro model unavailable — choose a replacement model in the instance pane",
|
|
1662
2257
|
blocksDelivery: true,
|
|
@@ -1668,7 +2263,7 @@ export class KiroBackend {
|
|
|
1668
2263
|
// Any active /model picker owns stdin, even if a future Kiro release
|
|
1669
2264
|
// changes the exact warning, credit wording or row layout. Keep this
|
|
1670
2265
|
// after the verified-outage case: it never labels /model as an outage.
|
|
1671
|
-
pattern:
|
|
2266
|
+
pattern: KIRO_MODEL_PICKER_HEADER,
|
|
1672
2267
|
keys: [],
|
|
1673
2268
|
description: "Kiro model picker — manual selection required",
|
|
1674
2269
|
blocksDelivery: true,
|
|
@@ -1676,14 +2271,94 @@ export class KiroBackend {
|
|
|
1676
2271
|
inputBlocked: true,
|
|
1677
2272
|
isActive: kiroModelPickerAtTail,
|
|
1678
2273
|
},
|
|
2274
|
+
{
|
|
2275
|
+
// #2587: an MCP server's input request (kiro-cli 2.34.0 V3) owns stdin until the person answers or cancels it.
|
|
2276
|
+
pattern: KIRO_MCP_INPUT_HEADER,
|
|
2277
|
+
keys: [],
|
|
2278
|
+
description: "Kiro MCP server is asking for input — answer it in the instance pane",
|
|
2279
|
+
blocksDelivery: true,
|
|
2280
|
+
holdOnly: true,
|
|
2281
|
+
inputBlocked: true,
|
|
2282
|
+
isActive: kiroMcpInputRequestActive,
|
|
2283
|
+
requestIdentity: pane => readKiroMcpInputCard(pane)?.identity ?? null,
|
|
2284
|
+
remoteCard: readKiroMcpInputCard,
|
|
2285
|
+
},
|
|
2286
|
+
{
|
|
2287
|
+
// #2592: the agent's question to the user owns stdin until the person answers or cancels it.
|
|
2288
|
+
pattern: KIRO_QUESTION_HEADER,
|
|
2289
|
+
keys: [],
|
|
2290
|
+
description: "Kiro agent is asking a question — answer it in the instance pane",
|
|
2291
|
+
blocksDelivery: true,
|
|
2292
|
+
holdOnly: true,
|
|
2293
|
+
inputBlocked: true,
|
|
2294
|
+
isActive: kiroQuestionCardActive,
|
|
2295
|
+
requestIdentity: pane => readKiroQuestionCard(pane)?.identity ?? null,
|
|
2296
|
+
remoteCard: readKiroQuestionCard,
|
|
2297
|
+
},
|
|
1679
2298
|
...kiroTrustPromptDialogs(this.activeTrustAll),
|
|
1680
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
|
+
},
|
|
1681
2311
|
];
|
|
1682
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
|
+
}
|
|
1683
2331
|
getContextUsage() {
|
|
1684
2332
|
return null;
|
|
1685
2333
|
}
|
|
1686
2334
|
logLaunch() { return this.builtLaunch ? { ...this.builtLaunch } : null; }
|
|
2335
|
+
/**
|
|
2336
|
+
* #2598 B1: the store is the launch's engine's (v1 = the classic sqlite store, v2 = the TUI's session files), and the
|
|
2337
|
+
* conversation is the one the launch resumed by id. A fresh or legacy-mode launch owns no id yet.
|
|
2338
|
+
*/
|
|
2339
|
+
transcriptLaunch() {
|
|
2340
|
+
const built = this.builtLaunch, plan = this.activePlan;
|
|
2341
|
+
if (!built)
|
|
2342
|
+
return null;
|
|
2343
|
+
return { engine: built.engine, sessionId: plan?.mode === "resume" && plan.id ? plan.id : null };
|
|
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
|
+
}
|
|
1687
2362
|
getSessionId() {
|
|
1688
2363
|
// Kiro manages sessions internally via SQLite keyed by working directory.
|
|
1689
2364
|
// No external session ID needed — --resume handles it automatically.
|
|
@@ -1709,7 +2384,7 @@ export class KiroBackend {
|
|
|
1709
2384
|
*/
|
|
1710
2385
|
readModelsPayload() {
|
|
1711
2386
|
try {
|
|
1712
|
-
const out = execFileSync(this.binaryPath, ["chat", "--list-models", "--format", "json"], { encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
2387
|
+
const out = execFileSync(this.binaryPath, ["chat", "--list-models", "--format", "json"], { encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"], env: kiroChildEnv() });
|
|
1713
2388
|
const parsed = JSON.parse(out);
|
|
1714
2389
|
const arr = Array.isArray(parsed) ? parsed : (parsed?.models ?? []);
|
|
1715
2390
|
const models = arr.map((m) => {
|