@webpresso/agent-kit 3.1.26 → 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.
Files changed (150) hide show
  1. package/catalog/AGENTS.md.tpl +7 -11
  2. package/catalog/agent/commands/verify.md +2 -2
  3. package/catalog/agent/rules/changeset-release.md +4 -2
  4. package/catalog/agent/rules/ci-cost-local-first.md +139 -0
  5. package/catalog/agent/rules/ci-test-perf.md +5 -1
  6. package/catalog/agent/rules/cmd-execution.md +9 -0
  7. package/catalog/agent/rules/pre-implementation.md +2 -1
  8. package/catalog/agent/rules/test-scan-perf.md +80 -0
  9. package/catalog/agent/rules/workflow-skills-routing.md +9 -4
  10. package/catalog/agent/skills/autopilot/SKILL.md +48 -11
  11. package/catalog/agent/skills/claude/SKILL.md +7 -0
  12. package/catalog/agent/skills/codex/SKILL.md +7 -0
  13. package/catalog/agent/skills/fix/SKILL.md +36 -8
  14. package/catalog/agent/skills/grok/SKILL.md +7 -0
  15. package/catalog/agent/skills/investigate/SKILL.md +12 -1
  16. package/catalog/agent/skills/pll/SKILL.md +53 -48
  17. package/catalog/agent/skills/team/SKILL.md +26 -9
  18. package/catalog/agent/skills/ultragoal/SKILL.md +147 -19
  19. package/catalog/agent/skills/verify/SKILL.md +89 -51
  20. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +8 -0
  21. package/dist/esm/audit/blueprint-lifecycle-sql.js +25 -1
  22. package/dist/esm/audit/blueprint-pr-coverage.js +68 -7
  23. package/dist/esm/audit/registry.d.ts +4 -4
  24. package/dist/esm/audit/registry.js +3 -0
  25. package/dist/esm/audit/roadmap-links.js +6 -0
  26. package/dist/esm/audit/test-scan-perf.d.ts +18 -0
  27. package/dist/esm/audit/test-scan-perf.js +275 -0
  28. package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
  29. package/dist/esm/blueprint/lifecycle/engine.js +53 -6
  30. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  31. package/dist/esm/blueprint/lifecycle/review-provenance.js +93 -12
  32. package/dist/esm/blueprint/markdown/helpers.js +6 -2
  33. package/dist/esm/ci/act-replay.js +110 -7
  34. package/dist/esm/ci/act-runner.d.ts +4 -0
  35. package/dist/esm/ci/act-runner.js +85 -26
  36. package/dist/esm/ci/act-worktree-git.d.ts +11 -0
  37. package/dist/esm/ci/act-worktree-git.js +219 -0
  38. package/dist/esm/ci/native-session-memory-cache.d.ts +9 -1
  39. package/dist/esm/ci/native-session-memory-cache.js +9 -1
  40. package/dist/esm/ci/vitest-ci-shards.d.ts +6 -4
  41. package/dist/esm/ci/vitest-ci-shards.js +6 -4
  42. package/dist/esm/cli/auto-update/guard-tombstone.d.ts +25 -0
  43. package/dist/esm/cli/auto-update/guard-tombstone.js +67 -0
  44. package/dist/esm/cli/auto-update/installer.d.ts +4 -17
  45. package/dist/esm/cli/auto-update/installer.js +6 -57
  46. package/dist/esm/cli/auto-update/self-invocation.d.ts +37 -0
  47. package/dist/esm/cli/auto-update/self-invocation.js +62 -0
  48. package/dist/esm/cli/commands/audit.js +4 -0
  49. package/dist/esm/cli/commands/blueprint/mutations.js +35 -18
  50. package/dist/esm/cli/commands/blueprint/router.js +27 -2
  51. package/dist/esm/cli/commands/ci.d.ts +8 -1
  52. package/dist/esm/cli/commands/ci.js +80 -21
  53. package/dist/esm/cli/commands/dash/index.js +3 -3
  54. package/dist/esm/cli/commands/dash/keymap.js +1 -0
  55. package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
  56. package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
  57. package/dist/esm/cli/commands/dash/plan-once-attention.d.ts +32 -0
  58. package/dist/esm/cli/commands/dash/plan-once-attention.js +50 -0
  59. package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
  60. package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
  61. package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
  62. package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
  63. package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
  64. package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
  65. package/dist/esm/cli/commands/dash/plan-once-schema.d.ts +73 -0
  66. package/dist/esm/cli/commands/dash/plan-once-schema.js +119 -0
  67. package/dist/esm/cli/commands/dash/plan-once.d.ts +142 -0
  68. package/dist/esm/cli/commands/dash/plan-once.js +261 -0
  69. package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
  70. package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
  71. package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
  72. package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
  73. package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
  74. package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
  75. package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
  76. package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
  77. package/dist/esm/cli/commands/dash/tui/ipc-protocol.d.ts +87 -0
  78. package/dist/esm/cli/commands/dash/tui/ipc-protocol.js +128 -0
  79. package/dist/esm/cli/commands/dash/tui/native-host.d.ts +34 -0
  80. package/dist/esm/cli/commands/dash/tui/native-host.js +256 -0
  81. package/dist/esm/cli/commands/dash/tui/native-path.d.ts +11 -0
  82. package/dist/esm/cli/commands/dash/tui/native-path.js +41 -0
  83. package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
  84. package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
  85. package/dist/esm/cli/commands/dash/tui/runtime.js +311 -4
  86. package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
  87. package/dist/esm/cli/commands/dash/tui/state.js +257 -4
  88. package/dist/esm/cli/commands/format.js +0 -2
  89. package/dist/esm/cli/commands/init/config.d.ts +0 -1
  90. package/dist/esm/cli/commands/init/config.js +1 -4
  91. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
  92. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
  93. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
  94. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
  95. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
  96. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
  97. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +1 -1
  98. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +61 -23
  99. package/dist/esm/cli/commands/logs.d.ts +15 -0
  100. package/dist/esm/cli/commands/logs.js +192 -3
  101. package/dist/esm/cli/commands/package-manager.js +2 -1
  102. package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
  103. package/dist/esm/cli/commands/quality-log-store.js +187 -51
  104. package/dist/esm/cli/commands/review.js +5 -8
  105. package/dist/esm/cli/commands/secrets.d.ts +1 -0
  106. package/dist/esm/cli/commands/secrets.js +4 -1
  107. package/dist/esm/cli/commands/self-install-guard.d.ts +14 -32
  108. package/dist/esm/cli/commands/self-install-guard.js +17 -63
  109. package/dist/esm/cli/direct-provider-launch.js +25 -1
  110. package/dist/esm/hooks/doctor.d.ts +15 -0
  111. package/dist/esm/hooks/doctor.js +56 -0
  112. package/dist/esm/hooks/guard-switch/index.js +8 -4
  113. package/dist/esm/hooks/post-tool/lint-after-edit.js +6 -4
  114. package/dist/esm/hooks/precompact/index.js +5 -3
  115. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +4 -4
  116. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
  117. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
  118. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
  119. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
  120. package/dist/esm/hooks/sessionstart/index.d.ts +10 -0
  121. package/dist/esm/hooks/sessionstart/index.js +61 -4
  122. package/dist/esm/hooks/shared/types.d.ts +11 -0
  123. package/dist/esm/hooks/shared/types.js +21 -0
  124. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  125. package/dist/esm/mcp/tools/audit.d.ts +2 -1
  126. package/dist/esm/mcp/tools/audit.js +10 -0
  127. package/dist/esm/mcp/tools/audits.d.ts +1 -0
  128. package/dist/esm/mcp/tools/ci-act.js +9 -11
  129. package/dist/esm/paths/state-root.js +9 -0
  130. package/dist/esm/review/authority.js +17 -2
  131. package/dist/esm/review/availability.js +18 -5
  132. package/dist/esm/review/subject.d.ts +15 -0
  133. package/dist/esm/review/subject.js +105 -10
  134. package/dist/esm/runtime/executor.d.ts +1 -0
  135. package/dist/esm/runtime/executor.js +2 -1
  136. package/dist/esm/secret-gate/runner.d.ts +1 -0
  137. package/dist/esm/secret-gate/runner.js +4 -1
  138. package/dist/esm/session-memory/current-session.js +2 -0
  139. package/dist/esm/session-memory/native-runtime.js +5 -3
  140. package/dist/esm/session-memory/native-warm-markers.d.ts +18 -0
  141. package/dist/esm/session-memory/native-warm-markers.js +22 -0
  142. package/dist/esm/session-memory/sync/types.d.ts +4 -0
  143. package/dist/esm/session-memory/types.d.ts +3 -1
  144. package/dist/esm/session-memory/types.js +2 -0
  145. package/dist/esm/ultragoal/runtime.js +44 -2
  146. package/dist/esm/worktrees/current.d.ts +1 -0
  147. package/dist/esm/worktrees/current.js +1 -0
  148. package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
  149. package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
  150. package/package.json +17 -14
@@ -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 = {
@@ -234,6 +242,8 @@ export async function runTuiMenu(input) {
234
242
  let dirty = true;
235
243
  let lastFrame = "";
236
244
  let running = true;
245
+ /** When set, domain state paints via the native shell instead of pure-ANSI. */
246
+ let nativePushView = null;
237
247
  /** Once true, no new launch effects may call selectAgent. */
238
248
  let shuttingDown = false;
239
249
  let initialLaunchConsumed = false;
@@ -261,6 +271,14 @@ export async function runTuiMenu(input) {
261
271
  const paint = () => {
262
272
  if (shuttingDown)
263
273
  return;
274
+ // Native shell owns the terminal; never write pure-ANSI frames over it.
275
+ if (nativePushView) {
276
+ if (!dirty && !input.smokeOnce)
277
+ return;
278
+ nativePushView();
279
+ dirty = false;
280
+ return;
281
+ }
264
282
  if (!dirty && !input.smokeOnce)
265
283
  return;
266
284
  const frame = renderFrame(toViewModel(state, {
@@ -274,6 +292,32 @@ export async function runTuiMenu(input) {
274
292
  }
275
293
  dirty = false;
276
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
+ };
277
321
  const mergeRunStatuses = (statuses) => {
278
322
  runStatuses = statuses;
279
323
  // Always rebind from the discovery snapshot so async status refresh cannot
@@ -288,6 +332,7 @@ export async function runTuiMenu(input) {
288
332
  }, projected);
289
333
  void projectRunState;
290
334
  dirty = true;
335
+ void refreshMergeAttention();
291
336
  };
292
337
  const applyEffects = async (effects) => {
293
338
  for (const effect of effects) {
@@ -295,6 +340,15 @@ export async function runTuiMenu(input) {
295
340
  running = false;
296
341
  continue;
297
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
+ }
298
352
  if (effect.kind === "e2e-group") {
299
353
  emitDashE2eState({
300
354
  kind: "group",
@@ -480,6 +534,188 @@ export async function runTuiMenu(input) {
480
534
  dirty = true;
481
535
  continue;
482
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
+ }
483
719
  if (effect.kind === "palette-command" && effect.id === "wizard.open") {
484
720
  // Seed worktree picker items when the wizard opens (or re-emit cursor).
485
721
  if (state.wizard && state.wizard.allItems.length === 0) {
@@ -527,6 +763,15 @@ export async function runTuiMenu(input) {
527
763
  return;
528
764
  baseAgents = [...loaded.agents];
529
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
+ }
530
775
  emitDashE2eState({ kind: "providers-rendered" });
531
776
  dirty = true;
532
777
  paint();
@@ -621,6 +866,68 @@ export async function runTuiMenu(input) {
621
866
  stdout.write(`\n${note}\n`);
622
867
  return 0;
623
868
  }
869
+ // Primary path: native Ratatui shell when binary is available (WP_DASH_NATIVE=0 forces pure-ANSI).
870
+ const { shouldPreferNativeDashTui, resolveNativeDashTuiBinary } = await import("#cli/commands/dash/tui/native-path.js");
871
+ const nativeBinary = shouldPreferNativeDashTui() && !process.env["WP_DASH_FORCE_ANSI"]
872
+ ? resolveNativeDashTuiBinary()
873
+ : null;
874
+ if (nativeBinary) {
875
+ try {
876
+ const { runNativeTuiHost } = await import("#cli/commands/dash/tui/native-host.js");
877
+ process.stderr.write(`wp dash: native control-plane shell (${nativeBinary})\n`);
878
+ // Ensure agent list is loaded before first native frame when possible.
879
+ state = { ...state, loading: false };
880
+ const code = await runNativeTuiHost({
881
+ binaryPath: nativeBinary,
882
+ repoLabel: input.repoLabel,
883
+ getState: () => state,
884
+ setState: (next) => {
885
+ state = next;
886
+ },
887
+ onBridge: (bridge) => {
888
+ nativePushView = bridge.pushView;
889
+ },
890
+ handlers: {
891
+ onEffect: (effect) => {
892
+ // Match pure-ANSI: repaint after async effects settle so launch /
893
+ // browser / status mutations reach the native shell without waiting
894
+ // for the next key or timer tick.
895
+ void enqueueEffects([effect]).then(() => {
896
+ if (!shuttingDown)
897
+ paint();
898
+ });
899
+ },
900
+ onQuit: () => {
901
+ shuttingDown = true;
902
+ running = false;
903
+ coordinator.discardPending();
904
+ },
905
+ },
906
+ });
907
+ nativePushView = null;
908
+ if (code !== 0) {
909
+ throw new Error(`native shell exited with code ${code}`);
910
+ }
911
+ shuttingDown = true;
912
+ running = false;
913
+ coordinator.discardPending();
914
+ clearInterval(usageTimer);
915
+ clearInterval(ultragoalTimer);
916
+ clearInterval(statusTimer);
917
+ stopObservingLayout?.();
918
+ const note = await controller?.dispose();
919
+ await effectQueue.catch(() => {
920
+ /* best-effort drain after dispose */
921
+ });
922
+ if (note)
923
+ stdout.write(`\n${note}\n`);
924
+ return code;
925
+ }
926
+ catch (error) {
927
+ nativePushView = null;
928
+ process.stderr.write(`wp dash: native shell failed (${error instanceof Error ? error.message : String(error)}); falling back to pure-ANSI\n`);
929
+ }
930
+ }
624
931
  const wasRaw = stdin.isRaw;
625
932
  if (typeof stdin.setRawMode === "function")
626
933
  stdin.setRawMode(true);
@@ -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;