@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.
- package/catalog/AGENTS.md.tpl +7 -11
- package/catalog/agent/commands/verify.md +2 -2
- package/catalog/agent/rules/changeset-release.md +4 -2
- 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 +9 -4
- package/catalog/agent/skills/autopilot/SKILL.md +48 -11
- package/catalog/agent/skills/claude/SKILL.md +7 -0
- package/catalog/agent/skills/codex/SKILL.md +7 -0
- package/catalog/agent/skills/fix/SKILL.md +36 -8
- package/catalog/agent/skills/grok/SKILL.md +7 -0
- package/catalog/agent/skills/investigate/SKILL.md +12 -1
- package/catalog/agent/skills/pll/SKILL.md +53 -48
- package/catalog/agent/skills/team/SKILL.md +26 -9
- package/catalog/agent/skills/ultragoal/SKILL.md +147 -19
- package/catalog/agent/skills/verify/SKILL.md +89 -51
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +8 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +25 -1
- 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/ci/native-session-memory-cache.d.ts +9 -1
- package/dist/esm/ci/native-session-memory-cache.js +9 -1
- package/dist/esm/ci/vitest-ci-shards.d.ts +6 -4
- package/dist/esm/ci/vitest-ci-shards.js +6 -4
- package/dist/esm/cli/auto-update/guard-tombstone.d.ts +25 -0
- package/dist/esm/cli/auto-update/guard-tombstone.js +67 -0
- package/dist/esm/cli/auto-update/installer.d.ts +4 -17
- package/dist/esm/cli/auto-update/installer.js +6 -57
- package/dist/esm/cli/auto-update/self-invocation.d.ts +37 -0
- package/dist/esm/cli/auto-update/self-invocation.js +62 -0
- package/dist/esm/cli/commands/audit.js +4 -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/index.js +3 -3
- 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-attention.d.ts +32 -0
- package/dist/esm/cli/commands/dash/plan-once-attention.js +50 -0
- 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-schema.d.ts +73 -0
- package/dist/esm/cli/commands/dash/plan-once-schema.js +119 -0
- package/dist/esm/cli/commands/dash/plan-once.d.ts +142 -0
- package/dist/esm/cli/commands/dash/plan-once.js +261 -0
- 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/ipc-protocol.d.ts +87 -0
- package/dist/esm/cli/commands/dash/tui/ipc-protocol.js +128 -0
- package/dist/esm/cli/commands/dash/tui/native-host.d.ts +34 -0
- package/dist/esm/cli/commands/dash/tui/native-host.js +256 -0
- package/dist/esm/cli/commands/dash/tui/native-path.d.ts +11 -0
- package/dist/esm/cli/commands/dash/tui/native-path.js +41 -0
- 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 +311 -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/agent-kit-global/index.js +1 -1
- 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/package-manager.js +2 -1
- 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/commands/self-install-guard.d.ts +14 -32
- package/dist/esm/cli/commands/self-install-guard.js +17 -63
- 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/guard-switch/index.js +8 -4
- package/dist/esm/hooks/post-tool/lint-after-edit.js +6 -4
- package/dist/esm/hooks/precompact/index.js +5 -3
- 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/hooks/sessionstart/index.d.ts +10 -0
- package/dist/esm/hooks/sessionstart/index.js +61 -4
- package/dist/esm/hooks/shared/types.d.ts +11 -0
- package/dist/esm/hooks/shared/types.js +21 -0
- 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 +9 -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/session-memory/current-session.js +2 -0
- package/dist/esm/session-memory/native-runtime.js +5 -3
- package/dist/esm/session-memory/native-warm-markers.d.ts +18 -0
- package/dist/esm/session-memory/native-warm-markers.js +22 -0
- package/dist/esm/session-memory/sync/types.d.ts +4 -0
- package/dist/esm/session-memory/types.d.ts +3 -1
- package/dist/esm/session-memory/types.js +2 -0
- package/dist/esm/ultragoal/runtime.js +44 -2
- 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 +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;
|