@webpresso/agent-kit 3.1.28 → 3.1.30
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/catalog/AGENTS.md.tpl +4 -4
- package/catalog/agent/rules/ci-cost-local-first.md +139 -0
- package/catalog/agent/rules/ci-test-perf.md +5 -1
- package/catalog/agent/rules/cmd-execution.md +9 -0
- package/catalog/agent/rules/pre-implementation.md +2 -1
- package/catalog/agent/rules/test-scan-perf.md +80 -0
- package/catalog/agent/rules/workflow-skills-routing.md +4 -2
- package/catalog/agent/skills/fix/SKILL.md +3 -0
- package/catalog/agent/skills/verify/SKILL.md +18 -0
- package/dist/esm/audit/blueprint-pr-coverage.js +68 -7
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +3 -0
- package/dist/esm/audit/roadmap-links.js +6 -0
- package/dist/esm/audit/test-scan-perf.d.ts +18 -0
- package/dist/esm/audit/test-scan-perf.js +275 -0
- package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
- package/dist/esm/blueprint/lifecycle/engine.js +53 -6
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +93 -12
- package/dist/esm/blueprint/markdown/helpers.js +6 -2
- package/dist/esm/ci/act-replay.js +110 -7
- package/dist/esm/ci/act-runner.d.ts +4 -0
- package/dist/esm/ci/act-runner.js +85 -26
- package/dist/esm/ci/act-worktree-git.d.ts +11 -0
- package/dist/esm/ci/act-worktree-git.js +219 -0
- package/dist/esm/cli/commands/audit.js +3 -0
- package/dist/esm/cli/commands/blueprint/mutations.js +35 -18
- package/dist/esm/cli/commands/blueprint/router.js +27 -2
- package/dist/esm/cli/commands/ci.d.ts +8 -1
- package/dist/esm/cli/commands/ci.js +80 -21
- package/dist/esm/cli/commands/dash/keymap.js +1 -0
- package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
- package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
- package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
- package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
- package/dist/esm/cli/commands/dash/plan-once.d.ts +22 -2
- package/dist/esm/cli/commands/dash/plan-once.js +41 -4
- package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
- package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
- package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
- package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
- package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
- package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
- package/dist/esm/cli/commands/dash/tui/runtime.js +239 -4
- package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/state.js +257 -4
- package/dist/esm/cli/commands/format.js +0 -2
- package/dist/esm/cli/commands/init/config.d.ts +0 -1
- package/dist/esm/cli/commands/init/config.js +1 -4
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +61 -23
- package/dist/esm/cli/commands/logs.d.ts +15 -0
- package/dist/esm/cli/commands/logs.js +192 -3
- package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
- package/dist/esm/cli/commands/quality-log-store.js +187 -51
- package/dist/esm/cli/commands/review.js +5 -8
- package/dist/esm/cli/commands/secrets.d.ts +1 -0
- package/dist/esm/cli/commands/secrets.js +4 -1
- package/dist/esm/cli/direct-provider-launch.js +25 -1
- package/dist/esm/hooks/doctor.d.ts +15 -0
- package/dist/esm/hooks/doctor.js +56 -0
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +4 -4
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/audit.d.ts +2 -1
- package/dist/esm/mcp/tools/audit.js +10 -0
- package/dist/esm/mcp/tools/audits.d.ts +1 -0
- package/dist/esm/mcp/tools/ci-act.js +9 -11
- package/dist/esm/paths/state-root.js +3 -0
- package/dist/esm/review/authority.js +17 -2
- package/dist/esm/review/availability.js +18 -5
- package/dist/esm/review/subject.d.ts +15 -0
- package/dist/esm/review/subject.js +105 -10
- package/dist/esm/runtime/executor.d.ts +1 -0
- package/dist/esm/runtime/executor.js +2 -1
- package/dist/esm/secret-gate/runner.d.ts +1 -0
- package/dist/esm/secret-gate/runner.js +4 -1
- package/dist/esm/worktrees/current.d.ts +1 -0
- package/dist/esm/worktrees/current.js +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
- package/package.json +14 -14
|
@@ -5,6 +5,7 @@ import { buildRows } from "#cli/commands/dash/menu-state.js";
|
|
|
5
5
|
import { footerHintsFor, helpEntriesFor } from "#cli/commands/dash/keymap.js";
|
|
6
6
|
import { filterPaletteCommands } from "#cli/commands/dash/tui/palette.js";
|
|
7
7
|
import { HOST_CAPABILITIES } from "#cli/commands/dash/tui/hosts.js";
|
|
8
|
+
import { DEFAULT_CONTROL_PLANE_CHAT_MODE, formatChatModePill, } from "#cli/commands/dash/tui/chat-mode.js";
|
|
8
9
|
import { sanitizeTuiText } from "#cli/commands/dash/tui/sanitize.js";
|
|
9
10
|
function padLine(text, width) {
|
|
10
11
|
return fitDashText(text, width).padEnd(Math.max(0, width)).slice(0, Math.max(0, width));
|
|
@@ -68,7 +69,8 @@ export function browserFrameChromeRows(input) {
|
|
|
68
69
|
export function renderFrame(view) {
|
|
69
70
|
const width = Math.max(24, Math.min(view.width, 120));
|
|
70
71
|
const lines = [];
|
|
71
|
-
const
|
|
72
|
+
const chatMode = view.chatMode ?? DEFAULT_CONTROL_PLANE_CHAT_MODE;
|
|
73
|
+
const title = ` wp dash · ${view.repoLabel} · ${formatModePill(view.permissionMode)} · ${formatChatModePill(chatMode)} `;
|
|
72
74
|
lines.push(padLine(title, width));
|
|
73
75
|
lines.push(hrule(width));
|
|
74
76
|
if (view.ultragoalLine)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type PaletteCommandId = "palette.open" | "host.new.claude" | "host.new.codex" | "host.new.grok" | "host.new.opencode" | "sessions.focus" | "wizard.open" | "blueprints.open" | "fleet.open" | "permission.toggle" | "help.open" | "app.quit";
|
|
1
|
+
export type PaletteCommandId = "palette.open" | "host.new.claude" | "host.new.codex" | "host.new.grok" | "host.new.opencode" | "sessions.focus" | "wizard.open" | "blueprints.open" | "fleet.open" | "merge.status" | "chat.mode.toggle" | "chat.mode.open" | "chat.mode.send" | "permission.toggle" | "help.open" | "app.quit";
|
|
2
2
|
export type PaletteCategory = "Host" | "Session" | "Navigate" | "System";
|
|
3
3
|
export type PaletteCommand = {
|
|
4
4
|
readonly id: PaletteCommandId;
|
|
@@ -37,6 +37,20 @@ const STATIC_PALETTE_COMMANDS = [
|
|
|
37
37
|
keys: "f",
|
|
38
38
|
keywords: ["fleet", "watch", "status"],
|
|
39
39
|
},
|
|
40
|
+
{
|
|
41
|
+
id: "merge.status",
|
|
42
|
+
title: "Plan-once merge status matrix",
|
|
43
|
+
category: "Navigate",
|
|
44
|
+
keys: "m",
|
|
45
|
+
keywords: ["merge", "plan-once", "multi-host", "rollup", "status", "matrix"],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "chat.mode.toggle",
|
|
49
|
+
title: "Toggle host-pane / integrated chat",
|
|
50
|
+
category: "Session",
|
|
51
|
+
keys: "c",
|
|
52
|
+
keywords: ["chat", "acp", "integrated", "host-pane", "mode", "pane"],
|
|
53
|
+
},
|
|
40
54
|
{
|
|
41
55
|
id: "permission.toggle",
|
|
42
56
|
title: "Toggle permission mode (safe / always-approve)",
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { DashPaneController } from "#cli/commands/dash/pane-controller.js";
|
|
5
5
|
import { DASH_REFRESH_INTERVALS_MS, initialAgentEntries, initialLaunchSelection, loadAgentEntriesWithDiagnostics, projectDashboardRuns, projectRunState, shouldRunInitialLaunch, } from "#cli/commands/dash/menu-helpers.js";
|
|
6
|
-
import { parseDashPermissionMode, } from "#cli/commands/dash/provider-capabilities.js";
|
|
7
|
-
import { resolveLaunchPermissionMode } from "#cli/commands/dash/launch-preferences.js";
|
|
6
|
+
import { isDashProvider, parseDashPermissionMode, } from "#cli/commands/dash/provider-capabilities.js";
|
|
7
|
+
import { readChatModePreference, resolveLaunchPermissionMode, writeChatModePreference, } from "#cli/commands/dash/launch-preferences.js";
|
|
8
8
|
import { buildDashRuntimeArgv } from "#cli/commands/dash/runtime-invocation.js";
|
|
9
9
|
import { emitDashE2eState } from "#cli/commands/dash/e2e-state.js";
|
|
10
10
|
import { loadDashUsageSnapshot } from "#cli/commands/dash/usage.js";
|
|
@@ -12,16 +12,18 @@ import { formatUltragoalSummaryLine } from "#cli/commands/dash/menu-state.js";
|
|
|
12
12
|
import { readUltragoalSummary } from "#ultragoal/summary.js";
|
|
13
13
|
import { browserFrameChromeRows, renderFrame, } from "#cli/commands/dash/tui/frame.js";
|
|
14
14
|
import { CONTROL_PLANE_HOSTS, HOST_CAPABILITIES, projectHostAvailability, } from "#cli/commands/dash/tui/hosts.js";
|
|
15
|
-
import { applyWizardNewWorktreeProbe, createInitialTuiState, reduceTuiChunk, rebindPermissionMode, seedBlueprintBrowser, seedWizardItems, withAgents, } from "#cli/commands/dash/tui/state.js";
|
|
15
|
+
import { applyChatBridgeSession, applyPreferredChatModeSeed, applyWizardNewWorktreeProbe, createInitialTuiState, reduceTuiChunk, rebindPermissionMode, seedBlueprintBrowser, seedWizardItems, withAgents, } from "#cli/commands/dash/tui/state.js";
|
|
16
|
+
import { createHermeticChatBridge } from "#cli/commands/dash/tui/chat-bridge.js";
|
|
16
17
|
import { BlueprintViewLoader } from "#cli/commands/dash/blueprints.js";
|
|
17
18
|
import { DashLaunchCoordinator } from "#cli/commands/dash/launch-coordinator.js";
|
|
18
19
|
import { sanitizeTuiText } from "#cli/commands/dash/tui/sanitize.js";
|
|
19
20
|
import { defaultLoadWorktreePickerItems, isProjectClean } from "#cli/commands/dash/menu-helpers.js";
|
|
20
|
-
import { attentionItemsFromRunStatuses, formatAttentionLine, formatResourceLine, } from "#cli/commands/dash/tui/attention-hud.js";
|
|
21
|
+
import { attentionItemsFromRunStatuses, formatAttentionLine, formatResourceLine, selectAttentionLine, } from "#cli/commands/dash/tui/attention-hud.js";
|
|
21
22
|
import { blueprintRowsFromView, browserOverlayLines } from "#cli/commands/dash/tui/browser.js";
|
|
22
23
|
import { BlueprintActionController } from "#cli/commands/dash/blueprint-actions.js";
|
|
23
24
|
import { controlBlueprintExec, executeBlueprint, readBlueprintExecutionLogs, startBlueprint, } from "#cli/commands/blueprint/router.js";
|
|
24
25
|
import { isDashRunProvider } from "#cli/commands/dash/run-types.js";
|
|
26
|
+
import { formatMergeOperatorSurface, formatPrimaryMergeAttentionLine, projectAllMergeSets, } from "#cli/commands/dash/plan-once-merge.js";
|
|
25
27
|
function whichOnPath(binary) {
|
|
26
28
|
const result = spawnSync("which", [binary], { encoding: "utf8" });
|
|
27
29
|
return result.status === 0 && Boolean(result.stdout?.trim());
|
|
@@ -117,6 +119,7 @@ function toViewModel(state, input) {
|
|
|
117
119
|
width: input.width,
|
|
118
120
|
height: input.height,
|
|
119
121
|
permissionMode: state.permissionMode,
|
|
122
|
+
chatMode: state.chatMode,
|
|
120
123
|
agents: state.agents,
|
|
121
124
|
menu: state.menu,
|
|
122
125
|
hostAvailability: state.hostAvailability,
|
|
@@ -219,11 +222,16 @@ export async function runTuiMenu(input) {
|
|
|
219
222
|
});
|
|
220
223
|
}
|
|
221
224
|
const hostAvailability = projectHostAvailability(detectHostBinaries());
|
|
225
|
+
// Preferred integrated-chat never seeds chrome here — only after agents load
|
|
226
|
+
// when host/run gates can pass (applyPreferredChatModeSeed).
|
|
227
|
+
const preferredChatMode = readChatModePreference();
|
|
222
228
|
let state = createInitialTuiState({
|
|
223
229
|
agents: initialAgentEntries(seededMode),
|
|
224
230
|
permissionMode: seededMode,
|
|
225
231
|
hostAvailability,
|
|
232
|
+
chatMode: "host-pane",
|
|
226
233
|
});
|
|
234
|
+
let preferredChatModeSeeded = false;
|
|
227
235
|
// Seed prepared-launch diagnostics (optional-tool refresh failures, etc.).
|
|
228
236
|
if (input.initialLaunch?.warnings.length) {
|
|
229
237
|
state = {
|
|
@@ -284,6 +292,32 @@ export async function runTuiMenu(input) {
|
|
|
284
292
|
}
|
|
285
293
|
dirty = false;
|
|
286
294
|
};
|
|
295
|
+
let mergeHudGeneration = 0;
|
|
296
|
+
/**
|
|
297
|
+
* Prefer plan-once merge rollup only for incomplete/partial/all-failed.
|
|
298
|
+
* all-ok is omitted so generic per-run attention stays visible.
|
|
299
|
+
*/
|
|
300
|
+
const refreshMergeAttention = async () => {
|
|
301
|
+
const gen = ++mergeHudGeneration;
|
|
302
|
+
try {
|
|
303
|
+
const projections = await projectAllMergeSets({ cwd: input.cwd });
|
|
304
|
+
if (gen !== mergeHudGeneration || shuttingDown)
|
|
305
|
+
return;
|
|
306
|
+
// null when no memberships or only all-ok — keep generic attention.
|
|
307
|
+
const mergeLine = formatPrimaryMergeAttentionLine(projections);
|
|
308
|
+
const next = selectAttentionLine({
|
|
309
|
+
mergeLine,
|
|
310
|
+
genericLine: state.attentionLine,
|
|
311
|
+
});
|
|
312
|
+
if (next === state.attentionLine)
|
|
313
|
+
return;
|
|
314
|
+
state = { ...state, attentionLine: next };
|
|
315
|
+
dirty = true;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// Merge HUD is best-effort; generic attention remains.
|
|
319
|
+
}
|
|
320
|
+
};
|
|
287
321
|
const mergeRunStatuses = (statuses) => {
|
|
288
322
|
runStatuses = statuses;
|
|
289
323
|
// Always rebind from the discovery snapshot so async status refresh cannot
|
|
@@ -298,6 +332,7 @@ export async function runTuiMenu(input) {
|
|
|
298
332
|
}, projected);
|
|
299
333
|
void projectRunState;
|
|
300
334
|
dirty = true;
|
|
335
|
+
void refreshMergeAttention();
|
|
301
336
|
};
|
|
302
337
|
const applyEffects = async (effects) => {
|
|
303
338
|
for (const effect of effects) {
|
|
@@ -305,6 +340,15 @@ export async function runTuiMenu(input) {
|
|
|
305
340
|
running = false;
|
|
306
341
|
continue;
|
|
307
342
|
}
|
|
343
|
+
if (effect.kind === "persist-chat-mode") {
|
|
344
|
+
try {
|
|
345
|
+
writeChatModePreference(effect.mode);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Preference write is best-effort; never block the control plane.
|
|
349
|
+
}
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
308
352
|
if (effect.kind === "e2e-group") {
|
|
309
353
|
emitDashE2eState({
|
|
310
354
|
kind: "group",
|
|
@@ -490,6 +534,188 @@ export async function runTuiMenu(input) {
|
|
|
490
534
|
dirty = true;
|
|
491
535
|
continue;
|
|
492
536
|
}
|
|
537
|
+
if (effect.kind === "palette-command" && effect.id === "merge.status") {
|
|
538
|
+
const loadGen = ++browserLoadGeneration;
|
|
539
|
+
try {
|
|
540
|
+
const projections = await projectAllMergeSets({ cwd: input.cwd });
|
|
541
|
+
if (loadGen !== browserLoadGeneration || shuttingDown)
|
|
542
|
+
continue;
|
|
543
|
+
if (state.overlay.kind !== "browser")
|
|
544
|
+
continue;
|
|
545
|
+
if (projections.length === 0) {
|
|
546
|
+
state = {
|
|
547
|
+
...state,
|
|
548
|
+
browser: null,
|
|
549
|
+
overlay: {
|
|
550
|
+
kind: "browser",
|
|
551
|
+
title: "Plan-once merge",
|
|
552
|
+
lines: [
|
|
553
|
+
"No plan-once membership records.",
|
|
554
|
+
"Confirm a multi-host plan first.",
|
|
555
|
+
"git/PR auto-merge out of scope — status/summary only",
|
|
556
|
+
],
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
const lines = [];
|
|
562
|
+
for (const projection of projections) {
|
|
563
|
+
const surface = formatMergeOperatorSurface(projection);
|
|
564
|
+
lines.push(surface.summary);
|
|
565
|
+
for (const row of surface.matrix)
|
|
566
|
+
lines.push(` ${row}`);
|
|
567
|
+
lines.push(surface.scopeNote);
|
|
568
|
+
lines.push("");
|
|
569
|
+
}
|
|
570
|
+
if (lines.at(-1) === "")
|
|
571
|
+
lines.pop();
|
|
572
|
+
state = {
|
|
573
|
+
...state,
|
|
574
|
+
browser: null,
|
|
575
|
+
overlay: {
|
|
576
|
+
kind: "browser",
|
|
577
|
+
title: "Plan-once merge",
|
|
578
|
+
lines: lines.map((line) => sanitizeTuiText(line)),
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
if (loadGen !== browserLoadGeneration || shuttingDown)
|
|
585
|
+
continue;
|
|
586
|
+
if (state.overlay.kind !== "browser")
|
|
587
|
+
continue;
|
|
588
|
+
state = {
|
|
589
|
+
...state,
|
|
590
|
+
browser: null,
|
|
591
|
+
overlay: {
|
|
592
|
+
kind: "browser",
|
|
593
|
+
title: "Plan-once merge",
|
|
594
|
+
lines: [
|
|
595
|
+
sanitizeTuiText(error instanceof Error ? error.message : "failed to load merge status"),
|
|
596
|
+
],
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
dirty = true;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (effect.kind === "palette-command" && effect.id === "chat.mode.open") {
|
|
604
|
+
// Hermetic bridge for CI/MVP: uses existing dash run identity only.
|
|
605
|
+
const expectedGeneration = state.chatOpenGeneration;
|
|
606
|
+
try {
|
|
607
|
+
const hostKey = state.menu.selectedKey?.startsWith("group:")
|
|
608
|
+
? state.menu.selectedKey.slice("group:".length)
|
|
609
|
+
: state.agents.find((agent) => agent.sessions.some((session) => state.menu.selectedKey === `run:${session.id}` ||
|
|
610
|
+
state.menu.selectedKey === `run:${session.runId ?? ""}`))?.key;
|
|
611
|
+
const host = hostKey && isDashProvider(hostKey)
|
|
612
|
+
? hostKey
|
|
613
|
+
: state.chatSession?.host && isDashProvider(state.chatSession.host)
|
|
614
|
+
? state.chatSession.host
|
|
615
|
+
: null;
|
|
616
|
+
let runId = state.chatSession?.runId ?? null;
|
|
617
|
+
let label = state.chatSession?.label ?? null;
|
|
618
|
+
if (!runId) {
|
|
619
|
+
const key = state.menu.selectedKey;
|
|
620
|
+
if (key?.startsWith("run:")) {
|
|
621
|
+
const id = key.slice("run:".length);
|
|
622
|
+
for (const agent of state.agents) {
|
|
623
|
+
const session = agent.sessions.find((s) => s.id === id || s.runId === id);
|
|
624
|
+
if (session) {
|
|
625
|
+
runId = session.runId ?? session.id;
|
|
626
|
+
label = session.label;
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
else if (host) {
|
|
632
|
+
const agent = state.agents.find((entry) => entry.key === host);
|
|
633
|
+
const owned = agent?.sessions.find((s) => s.runId);
|
|
634
|
+
if (owned) {
|
|
635
|
+
runId = owned.runId ?? owned.id;
|
|
636
|
+
label = owned.label;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (!host || !runId) {
|
|
641
|
+
state = applyChatBridgeSession(state, null, "Select a host session (run) before switching to integrated chat", { expectedGeneration });
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
const bridge = createHermeticChatBridge();
|
|
645
|
+
const opened = await bridge.open({ host, runId, label });
|
|
646
|
+
if (state.chatOpenGeneration !== expectedGeneration ||
|
|
647
|
+
state.chatMode !== "integrated-chat") {
|
|
648
|
+
// Operator left chat while open was in flight — discard.
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (!opened.ok) {
|
|
652
|
+
state = applyChatBridgeSession(state, null, opened.error, { expectedGeneration });
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
// Open only (no auto-send); operator composes and presses Enter.
|
|
656
|
+
state = applyChatBridgeSession(state, opened.session, undefined, {
|
|
657
|
+
expectedGeneration,
|
|
658
|
+
});
|
|
659
|
+
// Successful enter: persist preferred integrated-chat.
|
|
660
|
+
if (state.chatMode === "integrated-chat") {
|
|
661
|
+
try {
|
|
662
|
+
writeChatModePreference("integrated-chat");
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
/* best-effort */
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
state = applyChatBridgeSession(state, null, error instanceof Error ? error.message : "chat bridge failed", { expectedGeneration });
|
|
673
|
+
}
|
|
674
|
+
dirty = true;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (effect.kind === "palette-command" && effect.id === "chat.mode.send") {
|
|
678
|
+
const expectedGeneration = state.chatOpenGeneration;
|
|
679
|
+
const session = state.chatSession;
|
|
680
|
+
const text = state.chatDraft.trim();
|
|
681
|
+
if (!session || !text) {
|
|
682
|
+
state = {
|
|
683
|
+
...state,
|
|
684
|
+
status: !session ? "chat session not open yet" : "type a message before send",
|
|
685
|
+
};
|
|
686
|
+
dirty = true;
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
try {
|
|
690
|
+
const bridge = createHermeticChatBridge();
|
|
691
|
+
const sent = await bridge.send(session, text);
|
|
692
|
+
if (state.chatOpenGeneration !== expectedGeneration ||
|
|
693
|
+
state.chatMode !== "integrated-chat") {
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (!sent.ok) {
|
|
697
|
+
state = {
|
|
698
|
+
...state,
|
|
699
|
+
status: sanitizeTuiText(sent.error),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
state = applyChatBridgeSession({ ...state, chatDraft: "" }, sent.session, undefined, {
|
|
704
|
+
expectedGeneration,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
if (state.chatOpenGeneration === expectedGeneration) {
|
|
710
|
+
state = {
|
|
711
|
+
...state,
|
|
712
|
+
status: sanitizeTuiText(error instanceof Error ? error.message : "chat send failed"),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
dirty = true;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
493
719
|
if (effect.kind === "palette-command" && effect.id === "wizard.open") {
|
|
494
720
|
// Seed worktree picker items when the wizard opens (or re-emit cursor).
|
|
495
721
|
if (state.wizard && state.wizard.allItems.length === 0) {
|
|
@@ -537,6 +763,15 @@ export async function runTuiMenu(input) {
|
|
|
537
763
|
return;
|
|
538
764
|
baseAgents = [...loaded.agents];
|
|
539
765
|
state = withAgents({ ...state, warnings: [...state.warnings, ...loaded.warnings.map(sanitizeTuiText)] }, rebindPermissionMode(loaded.agents, state.permissionMode));
|
|
766
|
+
// One post-load preferred-chat seed when gates pass (no continuous retry).
|
|
767
|
+
if (!preferredChatModeSeeded && !isSmoke && !shuttingDown) {
|
|
768
|
+
preferredChatModeSeeded = true;
|
|
769
|
+
const seeded = applyPreferredChatModeSeed(state, preferredChatMode);
|
|
770
|
+
state = seeded.state;
|
|
771
|
+
if (seeded.effects.length > 0) {
|
|
772
|
+
await enqueueEffects(seeded.effects);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
540
775
|
emitDashE2eState({ kind: "providers-rendered" });
|
|
541
776
|
dirty = true;
|
|
542
777
|
paint();
|
|
@@ -8,6 +8,8 @@ import type { LifecycleAction } from "#cli/commands/dash/pane-controller.js";
|
|
|
8
8
|
import { type DashWizardProvider } from "#cli/commands/dash/session-wizard.js";
|
|
9
9
|
import { type BlueprintBrowserState, type TuiBlueprintRow } from "#cli/commands/dash/tui/browser.js";
|
|
10
10
|
import type { BlueprintAction } from "#cli/commands/dash/blueprint-actions.js";
|
|
11
|
+
import type { ChatSession } from "#cli/commands/dash/tui/chat-bridge.js";
|
|
12
|
+
import { type ControlPlaneChatMode } from "#cli/commands/dash/tui/chat-mode.js";
|
|
11
13
|
export type TuiWizardItem = {
|
|
12
14
|
readonly kind: "current" | "existing" | "new";
|
|
13
15
|
readonly path?: string;
|
|
@@ -17,6 +19,17 @@ export type TuiAppState = {
|
|
|
17
19
|
readonly menu: MenuState;
|
|
18
20
|
readonly agents: readonly AgentEntry[];
|
|
19
21
|
readonly permissionMode: DashPermissionMode;
|
|
22
|
+
/** host-pane (default escape hatch) vs integrated-chat ACP surface */
|
|
23
|
+
readonly chatMode: ControlPlaneChatMode;
|
|
24
|
+
/** Active hermetic/real chat session when in integrated-chat (uses dash run identity). */
|
|
25
|
+
readonly chatSession: ChatSession | null;
|
|
26
|
+
/** Composer draft while integrated-chat overlay is active. */
|
|
27
|
+
readonly chatDraft: string;
|
|
28
|
+
/**
|
|
29
|
+
* Monotonic open generation: runtime open/send completions only apply when
|
|
30
|
+
* this still matches the generation that issued the effect (stale-cancel).
|
|
31
|
+
*/
|
|
32
|
+
readonly chatOpenGeneration: number;
|
|
20
33
|
readonly overlay: TuiOverlay;
|
|
21
34
|
readonly status: string | null;
|
|
22
35
|
readonly warnings: readonly string[];
|
|
@@ -83,6 +96,9 @@ export type TuiEffect = {
|
|
|
83
96
|
} | {
|
|
84
97
|
readonly kind: "palette-command";
|
|
85
98
|
readonly id: PaletteCommandId;
|
|
99
|
+
} | {
|
|
100
|
+
readonly kind: "persist-chat-mode";
|
|
101
|
+
readonly mode: ControlPlaneChatMode;
|
|
86
102
|
} | {
|
|
87
103
|
readonly kind: "e2e-group";
|
|
88
104
|
readonly agent: string;
|
|
@@ -100,6 +116,11 @@ export declare function createInitialTuiState(input: {
|
|
|
100
116
|
readonly agents: readonly AgentEntry[];
|
|
101
117
|
readonly permissionMode: DashPermissionMode;
|
|
102
118
|
readonly hostAvailability?: readonly HostAvailability[];
|
|
119
|
+
/**
|
|
120
|
+
* Cold-start seed. Callers must only pass host-pane for preferred integrated-chat
|
|
121
|
+
* until host/run gates can open chat (see applyPreferredChatModeSeed).
|
|
122
|
+
*/
|
|
123
|
+
readonly chatMode?: ControlPlaneChatMode;
|
|
103
124
|
}): TuiAppState;
|
|
104
125
|
export declare function reduceTuiChunk(state: TuiAppState, chunk: string): {
|
|
105
126
|
readonly state: TuiAppState;
|
|
@@ -113,6 +134,26 @@ export declare function applyPaletteCommand(state: TuiAppState, id: PaletteComma
|
|
|
113
134
|
readonly state: TuiAppState;
|
|
114
135
|
readonly effects: readonly TuiEffect[];
|
|
115
136
|
};
|
|
137
|
+
/**
|
|
138
|
+
* Toggle host-pane ↔ integrated-chat. Integrated chat requires an ACP-capable
|
|
139
|
+
* selected host and an existing dash run identity (no second registry).
|
|
140
|
+
*/
|
|
141
|
+
export declare function toggleChatMode(state: TuiAppState): {
|
|
142
|
+
readonly state: TuiAppState;
|
|
143
|
+
readonly effects: readonly TuiEffect[];
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* After agents load: if preferred is integrated-chat and gates pass, apply the
|
|
147
|
+
* normal toggle path once. Never forces integrated-chat chrome without host/run.
|
|
148
|
+
*/
|
|
149
|
+
export declare function applyPreferredChatModeSeed(state: TuiAppState, preferred: ControlPlaneChatMode): {
|
|
150
|
+
readonly state: TuiAppState;
|
|
151
|
+
readonly effects: readonly TuiEffect[];
|
|
152
|
+
};
|
|
153
|
+
/** Runtime applies hermetic/real bridge result into state + overlay. */
|
|
154
|
+
export declare function applyChatBridgeSession(state: TuiAppState, session: ChatSession | null, error?: string, options?: {
|
|
155
|
+
readonly expectedGeneration?: number;
|
|
156
|
+
}): TuiAppState;
|
|
116
157
|
/** Open the worktree/permission wizard with a real wizard object (not overlay-only). */
|
|
117
158
|
export declare function openLaunchWizard(state: TuiAppState, provider: DashWizardProvider): {
|
|
118
159
|
readonly state: TuiAppState;
|