@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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-plane interaction mode + per-host ACP capability matrix.
|
|
3
|
+
* Pure — no I/O. Missing ACP never silently omits a host.
|
|
4
|
+
*/
|
|
5
|
+
import { type DashProvider } from "#cli/commands/dash/provider-capabilities.js";
|
|
6
|
+
export declare const CONTROL_PLANE_CHAT_MODES: readonly ["host-pane", "integrated-chat"];
|
|
7
|
+
export type ControlPlaneChatMode = (typeof CONTROL_PLANE_CHAT_MODES)[number];
|
|
8
|
+
export declare const DEFAULT_CONTROL_PLANE_CHAT_MODE: ControlPlaneChatMode;
|
|
9
|
+
export type HostAcpCapability = {
|
|
10
|
+
readonly host: DashProvider;
|
|
11
|
+
/** True when integrated-chat transport can open for this host (hermetic or real). */
|
|
12
|
+
readonly acpAvailable: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* When acpAvailable is false, honest degrade reason (required).
|
|
15
|
+
* When true, optional note about transport class.
|
|
16
|
+
*/
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
/** Transport class for docs/tests — hermetic fake, headless, or acp. */
|
|
19
|
+
readonly transport: "hermetic" | "headless" | "acp" | "unavailable";
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Explicit matrix for every control-plane host. Never omit a host.
|
|
23
|
+
* MVP: Codex is hermetic-chat capable for CI; others degrade with reasons.
|
|
24
|
+
*/
|
|
25
|
+
export declare const HOST_ACP_CAPABILITIES: readonly HostAcpCapability[];
|
|
26
|
+
export declare function hostAcpCapability(host: DashProvider): HostAcpCapability;
|
|
27
|
+
/** Every host has an entry — used by tests to lock non-silent-omit. */
|
|
28
|
+
export declare function projectHostAcpMatrix(): readonly HostAcpCapability[];
|
|
29
|
+
export declare function isControlPlaneChatMode(value: string): value is ControlPlaneChatMode;
|
|
30
|
+
export declare function formatChatModePill(mode: ControlPlaneChatMode): string;
|
|
31
|
+
export declare function toggleControlPlaneChatMode(mode: ControlPlaneChatMode): ControlPlaneChatMode;
|
|
32
|
+
/**
|
|
33
|
+
* Pure mode transition. Integrated-chat requires an explicit host that is ACP-capable
|
|
34
|
+
* when opening chat; switching back to host-pane always succeeds.
|
|
35
|
+
*/
|
|
36
|
+
export declare function reduceChatModeSwitch(input: {
|
|
37
|
+
readonly mode: ControlPlaneChatMode;
|
|
38
|
+
readonly target: ControlPlaneChatMode;
|
|
39
|
+
readonly host?: DashProvider | null;
|
|
40
|
+
}): {
|
|
41
|
+
readonly ok: true;
|
|
42
|
+
readonly mode: ControlPlaneChatMode;
|
|
43
|
+
} | {
|
|
44
|
+
readonly ok: false;
|
|
45
|
+
readonly error: string;
|
|
46
|
+
readonly mode: ControlPlaneChatMode;
|
|
47
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control-plane interaction mode + per-host ACP capability matrix.
|
|
3
|
+
* Pure — no I/O. Missing ACP never silently omits a host.
|
|
4
|
+
*/
|
|
5
|
+
import { DASH_PROVIDERS } from "#cli/commands/dash/provider-capabilities.js";
|
|
6
|
+
export const CONTROL_PLANE_CHAT_MODES = ["host-pane", "integrated-chat"];
|
|
7
|
+
export const DEFAULT_CONTROL_PLANE_CHAT_MODE = "host-pane";
|
|
8
|
+
/**
|
|
9
|
+
* Explicit matrix for every control-plane host. Never omit a host.
|
|
10
|
+
* MVP: Codex is hermetic-chat capable for CI; others degrade with reasons.
|
|
11
|
+
*/
|
|
12
|
+
export const HOST_ACP_CAPABILITIES = DASH_PROVIDERS.map((host) => {
|
|
13
|
+
if (host === "codex") {
|
|
14
|
+
return {
|
|
15
|
+
host,
|
|
16
|
+
acpAvailable: true,
|
|
17
|
+
reason: "Hermetic integrated-chat adapter (MVP); real ACP optional later",
|
|
18
|
+
transport: "hermetic",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (host === "claude") {
|
|
22
|
+
return {
|
|
23
|
+
host,
|
|
24
|
+
acpAvailable: false,
|
|
25
|
+
reason: "Claude ACP not wired in dash yet — use host-pane",
|
|
26
|
+
transport: "unavailable",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (host === "grok") {
|
|
30
|
+
return {
|
|
31
|
+
host,
|
|
32
|
+
acpAvailable: false,
|
|
33
|
+
reason: "Grok ACP/headless chat not wired in dash yet — use host-pane",
|
|
34
|
+
transport: "unavailable",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
host,
|
|
39
|
+
acpAvailable: false,
|
|
40
|
+
reason: "OpenCode ACP not wired in dash yet — use host-pane",
|
|
41
|
+
transport: "unavailable",
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
export function hostAcpCapability(host) {
|
|
45
|
+
const row = HOST_ACP_CAPABILITIES.find((item) => item.host === host);
|
|
46
|
+
if (!row) {
|
|
47
|
+
// Should be unreachable — matrix covers DASH_PROVIDERS SSOT.
|
|
48
|
+
return {
|
|
49
|
+
host,
|
|
50
|
+
acpAvailable: false,
|
|
51
|
+
reason: `No ACP capability entry for ${host}`,
|
|
52
|
+
transport: "unavailable",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return row;
|
|
56
|
+
}
|
|
57
|
+
/** Every host has an entry — used by tests to lock non-silent-omit. */
|
|
58
|
+
export function projectHostAcpMatrix() {
|
|
59
|
+
return HOST_ACP_CAPABILITIES;
|
|
60
|
+
}
|
|
61
|
+
export function isControlPlaneChatMode(value) {
|
|
62
|
+
return CONTROL_PLANE_CHAT_MODES.includes(value);
|
|
63
|
+
}
|
|
64
|
+
export function formatChatModePill(mode) {
|
|
65
|
+
return mode === "integrated-chat" ? "[chat]" : "[pane]";
|
|
66
|
+
}
|
|
67
|
+
export function toggleControlPlaneChatMode(mode) {
|
|
68
|
+
return mode === "host-pane" ? "integrated-chat" : "host-pane";
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Pure mode transition. Integrated-chat requires an explicit host that is ACP-capable
|
|
72
|
+
* when opening chat; switching back to host-pane always succeeds.
|
|
73
|
+
*/
|
|
74
|
+
export function reduceChatModeSwitch(input) {
|
|
75
|
+
if (input.target === input.mode) {
|
|
76
|
+
return { ok: true, mode: input.mode };
|
|
77
|
+
}
|
|
78
|
+
if (input.target === "host-pane") {
|
|
79
|
+
return { ok: true, mode: "host-pane" };
|
|
80
|
+
}
|
|
81
|
+
// target integrated-chat
|
|
82
|
+
if (!input.host) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
error: "Select a host session before switching to integrated chat",
|
|
86
|
+
mode: input.mode,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const cap = hostAcpCapability(input.host);
|
|
90
|
+
if (!cap.acpAvailable) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
error: cap.reason,
|
|
94
|
+
mode: input.mode,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { ok: true, mode: "integrated-chat" };
|
|
98
|
+
}
|
|
@@ -3,6 +3,7 @@ import { type MenuState } from "#cli/commands/dash/menu-state.js";
|
|
|
3
3
|
import { type PaletteCommand } from "#cli/commands/dash/tui/palette.js";
|
|
4
4
|
import { type HostAvailability } from "#cli/commands/dash/tui/hosts.js";
|
|
5
5
|
import type { DashPermissionMode } from "#cli/commands/dash/provider-capabilities.js";
|
|
6
|
+
import { type ControlPlaneChatMode } from "#cli/commands/dash/tui/chat-mode.js";
|
|
6
7
|
export type TuiOverlay = {
|
|
7
8
|
readonly kind: "none";
|
|
8
9
|
} | {
|
|
@@ -28,6 +29,8 @@ export type AppViewModel = {
|
|
|
28
29
|
readonly width: number;
|
|
29
30
|
readonly height: number;
|
|
30
31
|
readonly permissionMode: DashPermissionMode;
|
|
32
|
+
/** host-pane (default) vs integrated-chat ACP surface */
|
|
33
|
+
readonly chatMode: ControlPlaneChatMode;
|
|
31
34
|
readonly agents: readonly AgentEntry[];
|
|
32
35
|
readonly menu: MenuState;
|
|
33
36
|
readonly hostAvailability: readonly HostAvailability[];
|
|
@@ -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)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const DASH_TUI_IPC_SCHEMA_VERSION: 1;
|
|
3
|
+
/** Keys that must never appear in IPC payloads (secret / env leakage). */
|
|
4
|
+
export declare const FORBIDDEN_IPC_KEYS: readonly ["env", "argv", "secret", "secrets", "password", "token", "authorization", "apiKey", "api_key"];
|
|
5
|
+
export declare const dashTuiViewModelSchema: z.ZodObject<{
|
|
6
|
+
repoLabel: z.ZodString;
|
|
7
|
+
width: z.ZodNumber;
|
|
8
|
+
height: z.ZodNumber;
|
|
9
|
+
permissionMode: z.ZodEnum<{
|
|
10
|
+
dangerous: "dangerous";
|
|
11
|
+
safe: "safe";
|
|
12
|
+
}>;
|
|
13
|
+
hostAvailability: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
14
|
+
id: z.ZodString;
|
|
15
|
+
available: z.ZodBoolean;
|
|
16
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
17
|
+
}, z.core.$strict>>>;
|
|
18
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
19
|
+
warnings: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
20
|
+
menuLines: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
21
|
+
overlayKind: z.ZodDefault<z.ZodString>;
|
|
22
|
+
overlayLines: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
23
|
+
paletteQuery: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
24
|
+
}, z.core.$strict>;
|
|
25
|
+
export type DashTuiViewModel = z.infer<typeof dashTuiViewModelSchema>;
|
|
26
|
+
export declare const dashTuiClientKeySchema: z.ZodObject<{
|
|
27
|
+
type: z.ZodLiteral<"key">;
|
|
28
|
+
kind: z.ZodString;
|
|
29
|
+
value: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strict>;
|
|
31
|
+
export declare const dashTuiClientMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
32
|
+
type: z.ZodLiteral<"hello">;
|
|
33
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
34
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
35
|
+
type: z.ZodLiteral<"ready">;
|
|
36
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
37
|
+
type: z.ZodLiteral<"key">;
|
|
38
|
+
kind: z.ZodString;
|
|
39
|
+
value: z.ZodOptional<z.ZodString>;
|
|
40
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
41
|
+
type: z.ZodLiteral<"resize">;
|
|
42
|
+
width: z.ZodNumber;
|
|
43
|
+
height: z.ZodNumber;
|
|
44
|
+
}, z.core.$strict>], "type">;
|
|
45
|
+
export type DashTuiClientMessage = z.infer<typeof dashTuiClientMessageSchema>;
|
|
46
|
+
export declare const dashTuiServerMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
47
|
+
type: z.ZodLiteral<"hello">;
|
|
48
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
49
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
50
|
+
type: z.ZodLiteral<"view">;
|
|
51
|
+
view: z.ZodObject<{
|
|
52
|
+
repoLabel: z.ZodString;
|
|
53
|
+
width: z.ZodNumber;
|
|
54
|
+
height: z.ZodNumber;
|
|
55
|
+
permissionMode: z.ZodEnum<{
|
|
56
|
+
dangerous: "dangerous";
|
|
57
|
+
safe: "safe";
|
|
58
|
+
}>;
|
|
59
|
+
hostAvailability: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
60
|
+
id: z.ZodString;
|
|
61
|
+
available: z.ZodBoolean;
|
|
62
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
63
|
+
}, z.core.$strict>>>;
|
|
64
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
65
|
+
warnings: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
66
|
+
menuLines: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
67
|
+
overlayKind: z.ZodDefault<z.ZodString>;
|
|
68
|
+
overlayLines: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
69
|
+
paletteQuery: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
70
|
+
}, z.core.$strict>;
|
|
71
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
72
|
+
type: z.ZodLiteral<"quit">;
|
|
73
|
+
}, z.core.$strict>], "type">;
|
|
74
|
+
export type DashTuiServerMessage = z.infer<typeof dashTuiServerMessageSchema>;
|
|
75
|
+
export declare function assertNoForbiddenIpcKeys(value: unknown, path?: string): void;
|
|
76
|
+
/** Encode one length-prefixed JSON frame (u32 little-endian length + UTF-8 body). */
|
|
77
|
+
export declare function encodeIpcFrame(message: unknown): Buffer;
|
|
78
|
+
/**
|
|
79
|
+
* Decode complete frames from a byte buffer. Returns messages and any incomplete tail.
|
|
80
|
+
*/
|
|
81
|
+
export declare function decodeIpcFrames(buffer: Buffer): {
|
|
82
|
+
readonly messages: readonly unknown[];
|
|
83
|
+
readonly rest: Buffer;
|
|
84
|
+
};
|
|
85
|
+
export declare function parseClientMessage(raw: unknown): DashTuiClientMessage;
|
|
86
|
+
export declare function parseServerMessage(raw: unknown): DashTuiServerMessage;
|
|
87
|
+
export declare function encodeServerView(view: DashTuiViewModel): Buffer;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Length-prefixed JSON-RPC-over-stdio framing for the native dash shell.
|
|
2
|
+
// schemaVersion 1 — keep in sync with native/dash-tui.
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
export const DASH_TUI_IPC_SCHEMA_VERSION = 1;
|
|
5
|
+
/** Keys that must never appear in IPC payloads (secret / env leakage). */
|
|
6
|
+
export const FORBIDDEN_IPC_KEYS = [
|
|
7
|
+
"env",
|
|
8
|
+
"argv",
|
|
9
|
+
"secret",
|
|
10
|
+
"secrets",
|
|
11
|
+
"password",
|
|
12
|
+
"token",
|
|
13
|
+
"authorization",
|
|
14
|
+
"apiKey",
|
|
15
|
+
"api_key",
|
|
16
|
+
];
|
|
17
|
+
const hostAvailabilitySchema = z
|
|
18
|
+
.object({
|
|
19
|
+
id: z.string().min(1),
|
|
20
|
+
available: z.boolean(),
|
|
21
|
+
reason: z.string().optional(),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export const dashTuiViewModelSchema = z
|
|
25
|
+
.object({
|
|
26
|
+
repoLabel: z.string(),
|
|
27
|
+
width: z.number().int().positive(),
|
|
28
|
+
height: z.number().int().positive(),
|
|
29
|
+
permissionMode: z.enum(["safe", "dangerous"]),
|
|
30
|
+
hostAvailability: z.array(hostAvailabilitySchema).default([]),
|
|
31
|
+
status: z.string().nullable().optional(),
|
|
32
|
+
warnings: z.array(z.string()).default([]),
|
|
33
|
+
menuLines: z.array(z.string()).default([]),
|
|
34
|
+
overlayKind: z.string().default("none"),
|
|
35
|
+
overlayLines: z.array(z.string()).default([]),
|
|
36
|
+
paletteQuery: z.string().nullable().optional(),
|
|
37
|
+
})
|
|
38
|
+
.strict();
|
|
39
|
+
export const dashTuiClientKeySchema = z
|
|
40
|
+
.object({
|
|
41
|
+
type: z.literal("key"),
|
|
42
|
+
kind: z.string().min(1),
|
|
43
|
+
value: z.string().optional(),
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
export const dashTuiClientMessageSchema = z.discriminatedUnion("type", [
|
|
47
|
+
z.object({ type: z.literal("hello"), schemaVersion: z.literal(1) }).strict(),
|
|
48
|
+
z.object({ type: z.literal("ready") }).strict(),
|
|
49
|
+
dashTuiClientKeySchema,
|
|
50
|
+
z
|
|
51
|
+
.object({
|
|
52
|
+
type: z.literal("resize"),
|
|
53
|
+
width: z.number().int().positive(),
|
|
54
|
+
height: z.number().int().positive(),
|
|
55
|
+
})
|
|
56
|
+
.strict(),
|
|
57
|
+
]);
|
|
58
|
+
export const dashTuiServerMessageSchema = z.discriminatedUnion("type", [
|
|
59
|
+
z.object({ type: z.literal("hello"), schemaVersion: z.literal(1) }).strict(),
|
|
60
|
+
z.object({ type: z.literal("view"), view: dashTuiViewModelSchema }).strict(),
|
|
61
|
+
z.object({ type: z.literal("quit") }).strict(),
|
|
62
|
+
]);
|
|
63
|
+
export function assertNoForbiddenIpcKeys(value, path = "$") {
|
|
64
|
+
if (value === null || value === undefined)
|
|
65
|
+
return;
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
value.forEach((item, index) => assertNoForbiddenIpcKeys(item, `${path}[${index}]`));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (typeof value !== "object")
|
|
71
|
+
return;
|
|
72
|
+
for (const [key, child] of Object.entries(value)) {
|
|
73
|
+
const lower = key.toLowerCase();
|
|
74
|
+
if (FORBIDDEN_IPC_KEYS.some((forbidden) => lower === forbidden.toLowerCase() || lower.includes(forbidden.toLowerCase()))) {
|
|
75
|
+
throw new Error(`Forbidden IPC key at ${path}.${key}`);
|
|
76
|
+
}
|
|
77
|
+
assertNoForbiddenIpcKeys(child, `${path}.${key}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Encode one length-prefixed JSON frame (u32 little-endian length + UTF-8 body). */
|
|
81
|
+
export function encodeIpcFrame(message) {
|
|
82
|
+
assertNoForbiddenIpcKeys(message);
|
|
83
|
+
const body = Buffer.from(JSON.stringify(message), "utf8");
|
|
84
|
+
const header = Buffer.alloc(4);
|
|
85
|
+
header.writeUInt32LE(body.length, 0);
|
|
86
|
+
return Buffer.concat([header, body]);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Decode complete frames from a byte buffer. Returns messages and any incomplete tail.
|
|
90
|
+
*/
|
|
91
|
+
export function decodeIpcFrames(buffer) {
|
|
92
|
+
const messages = [];
|
|
93
|
+
let offset = 0;
|
|
94
|
+
while (offset + 4 <= buffer.length) {
|
|
95
|
+
const len = buffer.readUInt32LE(offset);
|
|
96
|
+
if (len > 8 * 1024 * 1024) {
|
|
97
|
+
// Unrecoverable length claim — caller should drop/resync the stream.
|
|
98
|
+
throw new Error(`IPC frame too large: ${len}`);
|
|
99
|
+
}
|
|
100
|
+
if (offset + 4 + len > buffer.length)
|
|
101
|
+
break;
|
|
102
|
+
const body = buffer.subarray(offset + 4, offset + 4 + len);
|
|
103
|
+
offset += 4 + len;
|
|
104
|
+
try {
|
|
105
|
+
messages.push(JSON.parse(body.toString("utf8")));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Skip one corrupt payload; continue scanning subsequent frames.
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { messages, rest: buffer.subarray(offset) };
|
|
113
|
+
}
|
|
114
|
+
export function parseClientMessage(raw) {
|
|
115
|
+
assertNoForbiddenIpcKeys(raw);
|
|
116
|
+
return dashTuiClientMessageSchema.parse(raw);
|
|
117
|
+
}
|
|
118
|
+
export function parseServerMessage(raw) {
|
|
119
|
+
assertNoForbiddenIpcKeys(raw);
|
|
120
|
+
return dashTuiServerMessageSchema.parse(raw);
|
|
121
|
+
}
|
|
122
|
+
export function encodeServerView(view) {
|
|
123
|
+
const message = {
|
|
124
|
+
type: "view",
|
|
125
|
+
view: dashTuiViewModelSchema.parse(view),
|
|
126
|
+
};
|
|
127
|
+
return encodeIpcFrame(message);
|
|
128
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { type TuiAppState, type TuiEffect } from "#cli/commands/dash/tui/state.js";
|
|
3
|
+
import { type DashTuiViewModel } from "#cli/commands/dash/tui/ipc-protocol.js";
|
|
4
|
+
import type { ParsedKey } from "#cli/commands/dash/tui/keys.js";
|
|
5
|
+
export declare function projectNativeViewModel(state: TuiAppState, input: {
|
|
6
|
+
readonly repoLabel: string;
|
|
7
|
+
readonly width: number;
|
|
8
|
+
readonly height: number;
|
|
9
|
+
}): DashTuiViewModel;
|
|
10
|
+
export declare function clientMessageToParsedKey(message: {
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
readonly value?: string;
|
|
13
|
+
}): ParsedKey | null;
|
|
14
|
+
export type NativeHostHandlers = {
|
|
15
|
+
readonly onEffect: (effect: TuiEffect) => void | Promise<void>;
|
|
16
|
+
readonly onQuit: () => void;
|
|
17
|
+
};
|
|
18
|
+
/** Bridge so the domain runtime can push async state into the native shell. */
|
|
19
|
+
export type NativeHostBridge = {
|
|
20
|
+
readonly pushView: () => void;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Drive pure TUI state from a native binary over length-prefixed JSON frames.
|
|
24
|
+
* State is owned by the caller (runtime) so launch/HUD/browser updates paint.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runNativeTuiHost(input: {
|
|
27
|
+
readonly binaryPath: string;
|
|
28
|
+
readonly repoLabel: string;
|
|
29
|
+
readonly getState: () => TuiAppState;
|
|
30
|
+
readonly setState: (next: TuiAppState) => void;
|
|
31
|
+
readonly handlers: NativeHostHandlers;
|
|
32
|
+
readonly onBridge?: (bridge: NativeHostBridge) => void;
|
|
33
|
+
readonly spawnImpl?: typeof spawn;
|
|
34
|
+
}): Promise<number>;
|