@prestyj/cli 5.2.0 → 5.3.1
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/dist/app-sidecar.js +856 -91
- package/dist/app-sidecar.js.map +1 -1
- package/dist/core/agent-session-compaction.test.js +89 -0
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session-queue.test.d.ts +2 -0
- package/dist/core/agent-session-queue.test.d.ts.map +1 -0
- package/dist/core/agent-session-queue.test.js +122 -0
- package/dist/core/agent-session-queue.test.js.map +1 -0
- package/dist/core/agent-session.d.ts +79 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +268 -47
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autopilot-cycle.d.ts +119 -0
- package/dist/core/autopilot-cycle.d.ts.map +1 -0
- package/dist/core/autopilot-cycle.js +119 -0
- package/dist/core/autopilot-cycle.js.map +1 -0
- package/dist/core/autopilot-cycle.test.d.ts +2 -0
- package/dist/core/autopilot-cycle.test.d.ts.map +1 -0
- package/dist/core/autopilot-cycle.test.js +375 -0
- package/dist/core/autopilot-cycle.test.js.map +1 -0
- package/dist/core/autopilot-gate.d.ts +109 -0
- package/dist/core/autopilot-gate.d.ts.map +1 -0
- package/dist/core/autopilot-gate.js +205 -0
- package/dist/core/autopilot-gate.js.map +1 -0
- package/dist/core/autopilot-gate.test.d.ts +2 -0
- package/dist/core/autopilot-gate.test.d.ts.map +1 -0
- package/dist/core/autopilot-gate.test.js +295 -0
- package/dist/core/autopilot-gate.test.js.map +1 -0
- package/dist/core/autopilot-verdict.d.ts +20 -2
- package/dist/core/autopilot-verdict.d.ts.map +1 -1
- package/dist/core/autopilot-verdict.js +94 -3
- package/dist/core/autopilot-verdict.js.map +1 -1
- package/dist/core/autopilot-verdict.test.js +73 -0
- package/dist/core/autopilot-verdict.test.js.map +1 -1
- package/dist/core/bash-spawn-benchmark.d.ts +2 -0
- package/dist/core/bash-spawn-benchmark.d.ts.map +1 -0
- package/dist/core/bash-spawn-benchmark.js +139 -0
- package/dist/core/bash-spawn-benchmark.js.map +1 -0
- package/dist/core/hashline-edit-benchmark.d.ts +33 -1
- package/dist/core/hashline-edit-benchmark.d.ts.map +1 -1
- package/dist/core/hashline-edit-benchmark.js +193 -21
- package/dist/core/hashline-edit-benchmark.js.map +1 -1
- package/dist/core/hashline-edit-benchmark.test.js +51 -2
- package/dist/core/hashline-edit-benchmark.test.js.map +1 -1
- package/dist/core/json-mode-flag-parity.test.d.ts +2 -0
- package/dist/core/json-mode-flag-parity.test.d.ts.map +1 -0
- package/dist/core/json-mode-flag-parity.test.js +66 -0
- package/dist/core/json-mode-flag-parity.test.js.map +1 -0
- package/dist/core/mcp/deferred-catalog.d.ts +28 -0
- package/dist/core/mcp/deferred-catalog.d.ts.map +1 -0
- package/dist/core/mcp/deferred-catalog.js +72 -0
- package/dist/core/mcp/deferred-catalog.js.map +1 -0
- package/dist/core/mcp/resolve-stdio.d.ts +19 -3
- package/dist/core/mcp/resolve-stdio.d.ts.map +1 -1
- package/dist/core/mcp/resolve-stdio.js +136 -5
- package/dist/core/mcp/resolve-stdio.js.map +1 -1
- package/dist/core/mcp/resolve-stdio.test.js +82 -1
- package/dist/core/mcp/resolve-stdio.test.js.map +1 -1
- package/dist/core/nolan-context.d.ts +43 -2
- package/dist/core/nolan-context.d.ts.map +1 -1
- package/dist/core/nolan-context.js +91 -8
- package/dist/core/nolan-context.js.map +1 -1
- package/dist/core/nolan-context.test.js +167 -5
- package/dist/core/nolan-context.test.js.map +1 -1
- package/dist/core/nolan-model.d.ts +46 -0
- package/dist/core/nolan-model.d.ts.map +1 -0
- package/dist/core/nolan-model.js +30 -0
- package/dist/core/nolan-model.js.map +1 -0
- package/dist/core/nolan-model.test.d.ts +2 -0
- package/dist/core/nolan-model.test.d.ts.map +1 -0
- package/dist/core/nolan-model.test.js +51 -0
- package/dist/core/nolan-model.test.js.map +1 -0
- package/dist/core/nolan-prompt.d.ts +5 -18
- package/dist/core/nolan-prompt.d.ts.map +1 -1
- package/dist/core/nolan-prompt.js +112 -19
- package/dist/core/nolan-prompt.js.map +1 -1
- package/dist/core/nolan-prompt.test.d.ts +2 -0
- package/dist/core/nolan-prompt.test.d.ts.map +1 -0
- package/dist/core/nolan-prompt.test.js +138 -0
- package/dist/core/nolan-prompt.test.js.map +1 -0
- package/dist/core/persistent-shell.d.ts +27 -0
- package/dist/core/persistent-shell.d.ts.map +1 -0
- package/dist/core/persistent-shell.js +150 -0
- package/dist/core/persistent-shell.js.map +1 -0
- package/dist/core/persistent-shell.test.d.ts +2 -0
- package/dist/core/persistent-shell.test.d.ts.map +1 -0
- package/dist/core/persistent-shell.test.js +84 -0
- package/dist/core/persistent-shell.test.js.map +1 -0
- package/dist/core/process-manager-dev-server-repro.test.js +34 -6
- package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
- package/dist/core/progress/engine.d.ts +23 -0
- package/dist/core/progress/engine.d.ts.map +1 -0
- package/dist/core/progress/engine.js +131 -0
- package/dist/core/progress/engine.js.map +1 -0
- package/dist/core/progress/engine.test.d.ts +2 -0
- package/dist/core/progress/engine.test.d.ts.map +1 -0
- package/dist/core/progress/engine.test.js +136 -0
- package/dist/core/progress/engine.test.js.map +1 -0
- package/dist/core/progress/git-xp.d.ts +16 -0
- package/dist/core/progress/git-xp.d.ts.map +1 -0
- package/dist/core/progress/git-xp.js +106 -0
- package/dist/core/progress/git-xp.js.map +1 -0
- package/dist/core/progress/git-xp.test.d.ts +2 -0
- package/dist/core/progress/git-xp.test.d.ts.map +1 -0
- package/dist/core/progress/git-xp.test.js +88 -0
- package/dist/core/progress/git-xp.test.js.map +1 -0
- package/dist/core/progress/ranks.d.ts +21 -0
- package/dist/core/progress/ranks.d.ts.map +1 -0
- package/dist/core/progress/ranks.js +141 -0
- package/dist/core/progress/ranks.js.map +1 -0
- package/dist/core/progress/ranks.test.d.ts +2 -0
- package/dist/core/progress/ranks.test.d.ts.map +1 -0
- package/dist/core/progress/ranks.test.js +59 -0
- package/dist/core/progress/ranks.test.js.map +1 -0
- package/dist/core/progress/rebuild.d.ts +7 -0
- package/dist/core/progress/rebuild.d.ts.map +1 -0
- package/dist/core/progress/rebuild.js +106 -0
- package/dist/core/progress/rebuild.js.map +1 -0
- package/dist/core/progress/rebuild.test.d.ts +2 -0
- package/dist/core/progress/rebuild.test.d.ts.map +1 -0
- package/dist/core/progress/rebuild.test.js +72 -0
- package/dist/core/progress/rebuild.test.js.map +1 -0
- package/dist/core/progress/store.d.ts +35 -0
- package/dist/core/progress/store.d.ts.map +1 -0
- package/dist/core/progress/store.js +200 -0
- package/dist/core/progress/store.js.map +1 -0
- package/dist/core/progress/store.test.d.ts +2 -0
- package/dist/core/progress/store.test.d.ts.map +1 -0
- package/dist/core/progress/store.test.js +108 -0
- package/dist/core/progress/store.test.js.map +1 -0
- package/dist/core/progress/types.d.ts +108 -0
- package/dist/core/progress/types.d.ts.map +1 -0
- package/dist/core/progress/types.js +3 -0
- package/dist/core/progress/types.js.map +1 -0
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +40 -9
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.d.ts +2 -0
- package/dist/core/project-discovery.test.d.ts.map +1 -0
- package/dist/core/project-discovery.test.js +104 -0
- package/dist/core/project-discovery.test.js.map +1 -0
- package/dist/core/session-history.d.ts +51 -0
- package/dist/core/session-history.d.ts.map +1 -0
- package/dist/core/session-history.js +156 -0
- package/dist/core/session-history.js.map +1 -0
- package/dist/core/session-history.test.d.ts +2 -0
- package/dist/core/session-history.test.d.ts.map +1 -0
- package/dist/core/session-history.test.js +101 -0
- package/dist/core/session-history.test.js.map +1 -0
- package/dist/core/session-manager.d.ts +34 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +67 -0
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +140 -1
- package/dist/core/session-manager.test.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/speed-benchmark.test.js +2 -3
- package/dist/core/speed-benchmark.test.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +12 -8
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +15 -20
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/bash.d.ts +1 -0
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +27 -2
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +9 -2
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +91 -10
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/edit.test.js +97 -0
- package/dist/tools/edit.test.js.map +1 -1
- package/dist/tools/plan-mode.test.js +25 -0
- package/dist/tools/plan-mode.test.js.map +1 -1
- package/dist/tools/prompt-hints.d.ts.map +1 -1
- package/dist/tools/prompt-hints.js +3 -0
- package/dist/tools/prompt-hints.js.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +24 -1
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/tool-search.d.ts +15 -0
- package/dist/tools/tool-search.d.ts.map +1 -0
- package/dist/tools/tool-search.js +35 -0
- package/dist/tools/tool-search.js.map +1 -0
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +22 -9
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autopilot gate — pure decision logic for whether Nolan's auto-review cycle may
|
|
3
|
+
* start after a finished EZ Coder turn.
|
|
4
|
+
*
|
|
5
|
+
* Autopilot must NOT review every turn. The concrete leak cases this gate
|
|
6
|
+
* closes (each has a matching unit test in autopilot-gate.test.ts):
|
|
7
|
+
*
|
|
8
|
+
* - Workflow slash commands (`/compare`, `/bullet-proof`, `/expand`, custom
|
|
9
|
+
* `.ezcoder/commands/*.md`) end with reports or A/B/C choices that are reserved
|
|
10
|
+
* for the USER. Nolan reviewing them reads "findings" as "something real is
|
|
11
|
+
* wrong" and injects fix prompts the user never approved.
|
|
12
|
+
* - Registry commands (`/help`, `/session`, unknown `/foo`) and failed runs
|
|
13
|
+
* add no assistant work at all — a review would judge the PREVIOUS turn
|
|
14
|
+
* again (Nolan's cycle memory is wiped per turn) and can flip a settled
|
|
15
|
+
* ALL_CLEAR into a fresh PROMPT.
|
|
16
|
+
* - A turn that ended in plan mode has a pending Accept/Reject modal; Nolan must
|
|
17
|
+
* never inject a prompt into a read-only plan-mode session.
|
|
18
|
+
* - A turn whose ONLY tool calls were mechanical usage — a bash command that
|
|
19
|
+
* starts a background process (dev server, watcher), is read-only, or is a
|
|
20
|
+
* plain `git add`/`commit`/`push` workflow; a dedicated read-only tool
|
|
21
|
+
* (read/grep/find/ls/web_fetch/web_search/source_path/code_search/
|
|
22
|
+
* task_output/screenshot/skill); or NO tool calls at all (a plain-text
|
|
23
|
+
* answer with nothing built) — was always going to come back as Nolan's
|
|
24
|
+
* IGNORE verdict (see the autopilot system prompt's "mechanical operation"
|
|
25
|
+
* / "plain question answered with no code touched" rules). Pre-filtering
|
|
26
|
+
* these skips the review API call entirely instead of paying for one whose
|
|
27
|
+
* answer is already known.
|
|
28
|
+
*
|
|
29
|
+
* Kept pure + dependency-light so it's unit-testable without booting the
|
|
30
|
+
* sidecar (which runs `main()` at import time).
|
|
31
|
+
*/
|
|
32
|
+
import { isReadOnlyCommand } from "../tools/read-only-bash.js";
|
|
33
|
+
/** The exact separator AgentSession.prompt() inserts between a command's
|
|
34
|
+
* template and the user's extra args (see agent-session.ts prompt expansion).
|
|
35
|
+
* Must stay byte-identical or expanded-command detection silently breaks. */
|
|
36
|
+
export const USER_INSTRUCTIONS_HEADER = "\n\n## User Instructions\n\n";
|
|
37
|
+
/** Extract the `/name` token from raw input, or null when it isn't a slash
|
|
38
|
+
* invocation. */
|
|
39
|
+
function parseSlashName(text) {
|
|
40
|
+
const trimmed = text.trim();
|
|
41
|
+
if (!trimmed.startsWith("/"))
|
|
42
|
+
return null;
|
|
43
|
+
const name = trimmed.slice(1).split(/\s/, 1)[0]?.toLowerCase() ?? "";
|
|
44
|
+
return name.length > 0 ? name : null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* True when `text` invokes a known workflow command (first token, name or
|
|
48
|
+
* alias, case-insensitive). Registry/UI commands and unknown `/foo` return
|
|
49
|
+
* false — those add no assistant work and are caught by the
|
|
50
|
+
* `no-assistant-output` gate instead.
|
|
51
|
+
*/
|
|
52
|
+
export function isWorkflowCommandText(text, commands) {
|
|
53
|
+
const name = parseSlashName(text);
|
|
54
|
+
if (!name)
|
|
55
|
+
return false;
|
|
56
|
+
return commands.some((c) => c.name.toLowerCase() === name || (c.aliases ?? []).some((a) => a.toLowerCase() === name));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Match a transcript user-message body back to the workflow command it was
|
|
60
|
+
* expanded from. AgentSession stores the EXPANDED template as a plain user
|
|
61
|
+
* message, so without this Nolan's digest renders 400-line templates as
|
|
62
|
+
* `**User:** …` and treats them as user-authored asks.
|
|
63
|
+
*
|
|
64
|
+
* Returns the matched command plus any trailing user args, or null.
|
|
65
|
+
*/
|
|
66
|
+
export function matchExpandedCommand(text, commands) {
|
|
67
|
+
for (const command of commands) {
|
|
68
|
+
if (!command.prompt)
|
|
69
|
+
continue;
|
|
70
|
+
if (text === command.prompt)
|
|
71
|
+
return { command, args: null };
|
|
72
|
+
const prefix = command.prompt + USER_INSTRUCTIONS_HEADER;
|
|
73
|
+
if (text.startsWith(prefix)) {
|
|
74
|
+
const args = text.slice(prefix.length).trim();
|
|
75
|
+
return { command, args: args.length > 0 ? args : null };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/** Count assistant messages — the "did this run produce reviewable work"
|
|
81
|
+
* signal. Compared before/after a run by the sidecar. */
|
|
82
|
+
export function countAssistantMessages(messages) {
|
|
83
|
+
let count = 0;
|
|
84
|
+
for (const m of messages)
|
|
85
|
+
if (m.role === "assistant")
|
|
86
|
+
count++;
|
|
87
|
+
return count;
|
|
88
|
+
}
|
|
89
|
+
/** Extract every tool call made by assistant messages from `startIndex`
|
|
90
|
+
* onward — i.e. the tool calls added during one turn, given the message
|
|
91
|
+
* array length captured before that turn ran. Used to feed
|
|
92
|
+
* isMechanicalOnlyTurn without the gate module depending on gg-ai's full
|
|
93
|
+
* Message type (kept structurally typed so it's easy to unit test). */
|
|
94
|
+
export function extractTurnToolCalls(messages, startIndex) {
|
|
95
|
+
const calls = [];
|
|
96
|
+
for (const m of messages.slice(startIndex)) {
|
|
97
|
+
if (m.role !== "assistant" || typeof m.content === "string")
|
|
98
|
+
continue;
|
|
99
|
+
for (const part of m.content) {
|
|
100
|
+
if (part.type === "tool_call" && typeof part.name === "string") {
|
|
101
|
+
calls.push({ name: part.name, args: part.args });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return calls;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Decide whether the autopilot cycle may start for a just-finished turn.
|
|
109
|
+
* Checks are ordered cheapest/most-fundamental first; the reason is logged by
|
|
110
|
+
* the sidecar so skips are debuggable from ezcoder-app-sidecar.log.
|
|
111
|
+
*
|
|
112
|
+
* `planPending` beats the workflow/mechanical/no-output checks: a plan
|
|
113
|
+
* submission via exit_plan flips plan mode OFF and often has only mechanical
|
|
114
|
+
* tool calls, but it IS reviewable work — Nolan reviews the plan itself
|
|
115
|
+
* (kind: "plan") instead of skipping. `planMode` still wins over it: a turn
|
|
116
|
+
* that ended INSIDE plan mode (enter without exit) has no submitted plan and
|
|
117
|
+
* Nolan must never prompt into a read-only session.
|
|
118
|
+
*/
|
|
119
|
+
export function shouldStartAutopilotCycle(input) {
|
|
120
|
+
if (!input.enabled)
|
|
121
|
+
return { start: false, reason: "disabled" };
|
|
122
|
+
if (input.cancelled)
|
|
123
|
+
return { start: false, reason: "cancelled" };
|
|
124
|
+
if (input.planMode)
|
|
125
|
+
return { start: false, reason: "plan-mode" };
|
|
126
|
+
if (input.planPending)
|
|
127
|
+
return { start: true, kind: "plan" };
|
|
128
|
+
if (input.workflowCommand)
|
|
129
|
+
return { start: false, reason: "workflow-command" };
|
|
130
|
+
if (input.mechanicalOnly)
|
|
131
|
+
return { start: false, reason: "mechanical-only" };
|
|
132
|
+
if (input.assistantMessagesAdded <= 0)
|
|
133
|
+
return { start: false, reason: "no-assistant-output" };
|
|
134
|
+
return { start: true, kind: "work" };
|
|
135
|
+
}
|
|
136
|
+
/** Dedicated tools that can never touch the repo/filesystem in a way worth
|
|
137
|
+
* reviewing: pure lookups (read/grep/find/ls/source_path/code_search),
|
|
138
|
+
* network research (web_fetch/web_search), reading a background process's
|
|
139
|
+
* output (task_output), a UI screenshot, or loading a skill definition. Any
|
|
140
|
+
* call to one of these is mechanical regardless of its arguments — unlike
|
|
141
|
+
* bash, which needs isMechanicalBashCall to tell a read-only invocation from
|
|
142
|
+
* a mutating one. Deliberately excludes generate_image (produces a real
|
|
143
|
+
* artifact), subagent (can do anything internally), tasks/task_send/
|
|
144
|
+
* task_stop (mutate task/process state), and enter_plan/exit_plan (mode
|
|
145
|
+
* transitions already handled by the separate plan-mode gate check). */
|
|
146
|
+
const READ_ONLY_TOOL_NAMES = new Set([
|
|
147
|
+
"read",
|
|
148
|
+
"grep",
|
|
149
|
+
"find",
|
|
150
|
+
"ls",
|
|
151
|
+
"web_fetch",
|
|
152
|
+
"web_search",
|
|
153
|
+
"source_path",
|
|
154
|
+
"code_search",
|
|
155
|
+
"task_output",
|
|
156
|
+
"screenshot",
|
|
157
|
+
"skill",
|
|
158
|
+
]);
|
|
159
|
+
/** Leading git subcommands that are a plain add/commit/push workflow —
|
|
160
|
+
* mechanical for review purposes even though they mutate the repo (distinct
|
|
161
|
+
* from plan-mode's stricter read-only bar in read-only-bash.ts). */
|
|
162
|
+
const GIT_COMMIT_WORKFLOW = /^git\s+(add|commit|push)\b/i;
|
|
163
|
+
/**
|
|
164
|
+
* True when a bash call is mechanical: starting a background process (a dev
|
|
165
|
+
* server or watcher — nothing to review until it's stopped and its actual
|
|
166
|
+
* code changes land in a later turn), a read-only command (reuses the same
|
|
167
|
+
* conservative classifier plan mode trusts), or a plain git add/commit/push
|
|
168
|
+
* chain. Conservative by construction: anything it can't prove mechanical
|
|
169
|
+
* (including a chain mixing git with other commands) returns false, so the
|
|
170
|
+
* turn still gets reviewed — false negatives are safe here, false positives
|
|
171
|
+
* are not.
|
|
172
|
+
*/
|
|
173
|
+
function isMechanicalBashCall(args) {
|
|
174
|
+
if (!args)
|
|
175
|
+
return false;
|
|
176
|
+
if (args.run_in_background === true)
|
|
177
|
+
return true;
|
|
178
|
+
const command = typeof args.command === "string" ? args.command : "";
|
|
179
|
+
if (!command.trim())
|
|
180
|
+
return false;
|
|
181
|
+
if (isReadOnlyCommand(command))
|
|
182
|
+
return true;
|
|
183
|
+
const segments = command
|
|
184
|
+
.split(/&&|;|\|/)
|
|
185
|
+
.map((s) => s.trim())
|
|
186
|
+
.filter((s) => s.length > 0);
|
|
187
|
+
return segments.length > 0 && segments.every((s) => GIT_COMMIT_WORKFLOW.test(s));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* True when the turn has nothing for Nolan to review: either it made NO tool
|
|
191
|
+
* calls at all (a plain-text answer — "a plain question that got answered
|
|
192
|
+
* with no code touched" is IGNORE per the autopilot contract), or EVERY tool
|
|
193
|
+
* call was mechanical (a dedicated read-only tool, or bash usage that starts
|
|
194
|
+
* a background process / is read-only / is a plain git commit-push chain).
|
|
195
|
+
* ANY non-mechanical call (edit, write, subagent, generate_image, or bash
|
|
196
|
+
* outside these safe shapes) makes the whole turn reviewable — conservative
|
|
197
|
+
* default: keep reviewing whenever it's unclear.
|
|
198
|
+
*/
|
|
199
|
+
export function isMechanicalOnlyTurn(toolCalls) {
|
|
200
|
+
if (toolCalls.length === 0)
|
|
201
|
+
return true;
|
|
202
|
+
return toolCalls.every((call) => READ_ONLY_TOOL_NAMES.has(call.name) ||
|
|
203
|
+
(call.name === "bash" && isMechanicalBashCall(call.args)));
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=autopilot-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autopilot-gate.js","sourceRoot":"","sources":["../../src/core/autopilot-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autopilot-gate.test.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-gate.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { isWorkflowCommandText, matchExpandedCommand, countAssistantMessages, shouldStartAutopilotCycle, isMechanicalOnlyTurn, extractTurnToolCalls, USER_INSTRUCTIONS_HEADER, } from "./autopilot-gate.js";
|
|
3
|
+
import { PROMPT_COMMANDS } from "./prompt-commands.js";
|
|
4
|
+
const COMMANDS = [
|
|
5
|
+
{ name: "compare", aliases: [], prompt: "Compare the code you just created…" },
|
|
6
|
+
{ name: "bullet-proof", aliases: ["bp"], prompt: "Audit the project…" },
|
|
7
|
+
];
|
|
8
|
+
describe("isWorkflowCommandText", () => {
|
|
9
|
+
it("matches a bare command", () => {
|
|
10
|
+
expect(isWorkflowCommandText("/compare", COMMANDS)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
it("matches a command with trailing args", () => {
|
|
13
|
+
expect(isWorkflowCommandText("/compare src/foo.ts please", COMMANDS)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it("matches aliases and is case-insensitive", () => {
|
|
16
|
+
expect(isWorkflowCommandText("/bp", COMMANDS)).toBe(true);
|
|
17
|
+
expect(isWorkflowCommandText("/COMPARE", COMMANDS)).toBe(true);
|
|
18
|
+
expect(isWorkflowCommandText("/Bullet-Proof", COMMANDS)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it("tolerates leading whitespace (matches trim semantics of the prompt path)", () => {
|
|
21
|
+
expect(isWorkflowCommandText(" /compare", COMMANDS)).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
it("rejects unknown commands, non-slash text, and prefix collisions", () => {
|
|
24
|
+
expect(isWorkflowCommandText("/help", COMMANDS)).toBe(false);
|
|
25
|
+
expect(isWorkflowCommandText("/comparex", COMMANDS)).toBe(false);
|
|
26
|
+
expect(isWorkflowCommandText("compare the files", COMMANDS)).toBe(false);
|
|
27
|
+
expect(isWorkflowCommandText("run /compare for me", COMMANDS)).toBe(false);
|
|
28
|
+
expect(isWorkflowCommandText("/", COMMANDS)).toBe(false);
|
|
29
|
+
expect(isWorkflowCommandText("", COMMANDS)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
it("recognizes EVERY built-in workflow command (the real leak set)", () => {
|
|
32
|
+
// The reported bug: /compare fired an autopilot review of a report-only
|
|
33
|
+
// command. Prove the gate recognizes every shipped PROMPT_COMMAND (and its
|
|
34
|
+
// aliases) so none of them can leak into a review.
|
|
35
|
+
for (const cmd of PROMPT_COMMANDS) {
|
|
36
|
+
expect(isWorkflowCommandText(`/${cmd.name}`, PROMPT_COMMANDS)).toBe(true);
|
|
37
|
+
expect(isWorkflowCommandText(`/${cmd.name} with args`, PROMPT_COMMANDS)).toBe(true);
|
|
38
|
+
for (const alias of cmd.aliases) {
|
|
39
|
+
expect(isWorkflowCommandText(`/${alias}`, PROMPT_COMMANDS)).toBe(true);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("matchExpandedCommand", () => {
|
|
45
|
+
it("matches an exact template body", () => {
|
|
46
|
+
const m = matchExpandedCommand("Compare the code you just created…", COMMANDS);
|
|
47
|
+
expect(m?.command.name).toBe("compare");
|
|
48
|
+
expect(m?.args).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
it("matches template + user-instructions suffix and extracts the args", () => {
|
|
51
|
+
const text = `Audit the project…${USER_INSTRUCTIONS_HEADER}only the auth module`;
|
|
52
|
+
const m = matchExpandedCommand(text, COMMANDS);
|
|
53
|
+
expect(m?.command.name).toBe("bullet-proof");
|
|
54
|
+
expect(m?.args).toBe("only the auth module");
|
|
55
|
+
});
|
|
56
|
+
it("uses the exact separator AgentSession.prompt() inserts", () => {
|
|
57
|
+
// Lockstep guard: if agent-session.ts ever changes its expansion format,
|
|
58
|
+
// this literal must change with it or digest labeling silently breaks.
|
|
59
|
+
expect(USER_INSTRUCTIONS_HEADER).toBe("\n\n## User Instructions\n\n");
|
|
60
|
+
});
|
|
61
|
+
it("returns null for ordinary user text and near-misses", () => {
|
|
62
|
+
expect(matchExpandedCommand("compare my code to the repo", COMMANDS)).toBeNull();
|
|
63
|
+
// Template body with arbitrary extra text but no separator is NOT an expansion.
|
|
64
|
+
expect(matchExpandedCommand("Compare the code you just created… and also do X", COMMANDS)).toBeNull();
|
|
65
|
+
expect(matchExpandedCommand("", COMMANDS)).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
it("matches every real built-in template round-tripped through expansion", () => {
|
|
68
|
+
for (const cmd of PROMPT_COMMANDS) {
|
|
69
|
+
// Mirrors AgentSession.prompt(): bare command → template verbatim.
|
|
70
|
+
expect(matchExpandedCommand(cmd.prompt, PROMPT_COMMANDS)?.command.name).toBe(cmd.name);
|
|
71
|
+
// Command with args → template + header + args.
|
|
72
|
+
const withArgs = `${cmd.prompt}${USER_INSTRUCTIONS_HEADER}focus on src/`;
|
|
73
|
+
const m = matchExpandedCommand(withArgs, PROMPT_COMMANDS);
|
|
74
|
+
expect(m?.command.name).toBe(cmd.name);
|
|
75
|
+
expect(m?.args).toBe("focus on src/");
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe("countAssistantMessages", () => {
|
|
80
|
+
it("counts only assistant-role messages", () => {
|
|
81
|
+
expect(countAssistantMessages([
|
|
82
|
+
{ role: "system" },
|
|
83
|
+
{ role: "user" },
|
|
84
|
+
{ role: "assistant" },
|
|
85
|
+
{ role: "tool" },
|
|
86
|
+
{ role: "assistant" },
|
|
87
|
+
])).toBe(2);
|
|
88
|
+
expect(countAssistantMessages([])).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe("shouldStartAutopilotCycle", () => {
|
|
92
|
+
const reviewable = {
|
|
93
|
+
enabled: true,
|
|
94
|
+
cancelled: false,
|
|
95
|
+
planMode: false,
|
|
96
|
+
workflowCommand: false,
|
|
97
|
+
assistantMessagesAdded: 1,
|
|
98
|
+
};
|
|
99
|
+
it("starts a work review for a normal reviewable turn", () => {
|
|
100
|
+
expect(shouldStartAutopilotCycle(reviewable)).toEqual({ start: true, kind: "work" });
|
|
101
|
+
});
|
|
102
|
+
it("starts a plan review when the turn submitted a plan (exit_plan)", () => {
|
|
103
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, planPending: true })).toEqual({
|
|
104
|
+
start: true,
|
|
105
|
+
kind: "plan",
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it("plan review beats workflow-command / mechanical-only / no-assistant-output", () => {
|
|
109
|
+
// A plan-submission turn often looks skippable (mostly reads + exit_plan,
|
|
110
|
+
// maybe zero assistant text added by the gate's count) — planPending must
|
|
111
|
+
// override all the cheap-skip checks so the plan actually gets reviewed.
|
|
112
|
+
expect(shouldStartAutopilotCycle({
|
|
113
|
+
...reviewable,
|
|
114
|
+
planPending: true,
|
|
115
|
+
workflowCommand: true,
|
|
116
|
+
mechanicalOnly: true,
|
|
117
|
+
assistantMessagesAdded: 0,
|
|
118
|
+
})).toEqual({ start: true, kind: "plan" });
|
|
119
|
+
});
|
|
120
|
+
it("disabled / cancelled / plan-mode still beat a pending plan", () => {
|
|
121
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, planPending: true, enabled: false })).toEqual({ start: false, reason: "disabled" });
|
|
122
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, planPending: true, cancelled: true })).toEqual({ start: false, reason: "cancelled" });
|
|
123
|
+
// Still INSIDE plan mode (entered, never exited) → no submitted plan to
|
|
124
|
+
// review; Nolan must not touch a read-only session.
|
|
125
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, planPending: true, planMode: true })).toEqual({ start: false, reason: "plan-mode" });
|
|
126
|
+
});
|
|
127
|
+
it("skips when autopilot is off", () => {
|
|
128
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, enabled: false })).toEqual({
|
|
129
|
+
start: false,
|
|
130
|
+
reason: "disabled",
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
it("skips when the turn was cancelled", () => {
|
|
134
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, cancelled: true })).toEqual({
|
|
135
|
+
start: false,
|
|
136
|
+
reason: "cancelled",
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
it("skips when the turn ended in plan mode (pending Accept/Reject modal)", () => {
|
|
140
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, planMode: true })).toEqual({
|
|
141
|
+
start: false,
|
|
142
|
+
reason: "plan-mode",
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
it("skips workflow-command turns (/compare — the reported bug)", () => {
|
|
146
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, workflowCommand: true })).toEqual({
|
|
147
|
+
start: false,
|
|
148
|
+
reason: "workflow-command",
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
it("skips turns that added no assistant output (/help, unknown /foo, failed runs)", () => {
|
|
152
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, assistantMessagesAdded: 0 })).toEqual({
|
|
153
|
+
start: false,
|
|
154
|
+
reason: "no-assistant-output",
|
|
155
|
+
});
|
|
156
|
+
// A run that somehow REMOVED messages (defensive) also never reviews.
|
|
157
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, assistantMessagesAdded: -1 })).toEqual({
|
|
158
|
+
start: false,
|
|
159
|
+
reason: "no-assistant-output",
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
it("skips mechanical-only turns (dev server start, read-only lookup, git commit/push)", () => {
|
|
163
|
+
expect(shouldStartAutopilotCycle({ ...reviewable, mechanicalOnly: true })).toEqual({
|
|
164
|
+
start: false,
|
|
165
|
+
reason: "mechanical-only",
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
it("resolves multiple skip conditions in fixed priority order", () => {
|
|
169
|
+
// disabled beats everything; plan-mode beats workflow-command beats no-output.
|
|
170
|
+
expect(shouldStartAutopilotCycle({
|
|
171
|
+
enabled: false,
|
|
172
|
+
cancelled: true,
|
|
173
|
+
planMode: true,
|
|
174
|
+
workflowCommand: true,
|
|
175
|
+
assistantMessagesAdded: 0,
|
|
176
|
+
})).toEqual({ start: false, reason: "disabled" });
|
|
177
|
+
expect(shouldStartAutopilotCycle({
|
|
178
|
+
...reviewable,
|
|
179
|
+
planMode: true,
|
|
180
|
+
workflowCommand: true,
|
|
181
|
+
assistantMessagesAdded: 0,
|
|
182
|
+
})).toEqual({ start: false, reason: "plan-mode" });
|
|
183
|
+
expect(shouldStartAutopilotCycle({
|
|
184
|
+
...reviewable,
|
|
185
|
+
workflowCommand: true,
|
|
186
|
+
assistantMessagesAdded: 0,
|
|
187
|
+
})).toEqual({ start: false, reason: "workflow-command" });
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe("isMechanicalOnlyTurn", () => {
|
|
191
|
+
it("is TRUE for a turn with no tool calls (plain-text answer, nothing built)", () => {
|
|
192
|
+
// Matches the autopilot contract's own IGNORE example: "a plain question
|
|
193
|
+
// that got answered with no code touched".
|
|
194
|
+
expect(isMechanicalOnlyTurn([])).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
it("treats dedicated read-only tools as mechanical regardless of args", () => {
|
|
197
|
+
const calls = [
|
|
198
|
+
{ name: "read", args: { file_path: "a.ts" } },
|
|
199
|
+
{ name: "grep", args: { pattern: "TODO" } },
|
|
200
|
+
{ name: "find", args: { pattern: "*.ts" } },
|
|
201
|
+
{ name: "ls", args: { path: "." } },
|
|
202
|
+
{ name: "web_fetch", args: { url: "https://example.com" } },
|
|
203
|
+
{ name: "web_search", args: { query: "x" } },
|
|
204
|
+
{ name: "source_path", args: { package: "zod" } },
|
|
205
|
+
{ name: "code_search", args: { query: "x" } },
|
|
206
|
+
{ name: "task_output", args: { id: "abc" } },
|
|
207
|
+
{ name: "screenshot", args: { url: "http://localhost:3000" } },
|
|
208
|
+
{ name: "skill", args: { skill: "find-skills" } },
|
|
209
|
+
];
|
|
210
|
+
expect(isMechanicalOnlyTurn(calls)).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
it("is NOT mechanical when a research turn also calls write/edit/subagent", () => {
|
|
213
|
+
expect(isMechanicalOnlyTurn([
|
|
214
|
+
{ name: "read", args: {} },
|
|
215
|
+
{ name: "write", args: {} },
|
|
216
|
+
])).toBe(false);
|
|
217
|
+
expect(isMechanicalOnlyTurn([
|
|
218
|
+
{ name: "grep", args: {} },
|
|
219
|
+
{ name: "subagent", args: {} },
|
|
220
|
+
])).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
it("is NOT mechanical for tools that mutate task/process state or produce artifacts", () => {
|
|
223
|
+
expect(isMechanicalOnlyTurn([{ name: "generate_image", args: {} }])).toBe(false);
|
|
224
|
+
expect(isMechanicalOnlyTurn([{ name: "tasks", args: {} }])).toBe(false);
|
|
225
|
+
expect(isMechanicalOnlyTurn([{ name: "task_send", args: {} }])).toBe(false);
|
|
226
|
+
expect(isMechanicalOnlyTurn([{ name: "task_stop", args: {} }])).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
it("treats a background bash start (dev server / watcher) as mechanical", () => {
|
|
229
|
+
const calls = [
|
|
230
|
+
{ name: "bash", args: { command: "npm run dev", run_in_background: true } },
|
|
231
|
+
];
|
|
232
|
+
expect(isMechanicalOnlyTurn(calls)).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
it("treats a read-only bash command as mechanical", () => {
|
|
235
|
+
const calls = [{ name: "bash", args: { command: "git status" } }];
|
|
236
|
+
expect(isMechanicalOnlyTurn(calls)).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
it("treats a plain git add/commit/push workflow as mechanical", () => {
|
|
239
|
+
const calls = [
|
|
240
|
+
{ name: "bash", args: { command: 'git add -A && git commit -m "fix" && git push' } },
|
|
241
|
+
];
|
|
242
|
+
expect(isMechanicalOnlyTurn(calls)).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
it("is NOT mechanical when a git chain mixes in a non-git command", () => {
|
|
245
|
+
const calls = [
|
|
246
|
+
{ name: "bash", args: { command: "git commit -m x && rm -rf dist" } },
|
|
247
|
+
];
|
|
248
|
+
expect(isMechanicalOnlyTurn(calls)).toBe(false);
|
|
249
|
+
});
|
|
250
|
+
it("is NOT mechanical when any call is edit/write", () => {
|
|
251
|
+
const calls = [
|
|
252
|
+
{ name: "bash", args: { command: "git status" } },
|
|
253
|
+
{ name: "edit", args: { file_path: "a.ts" } },
|
|
254
|
+
];
|
|
255
|
+
expect(isMechanicalOnlyTurn(calls)).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
it("is NOT mechanical for an ordinary mutating bash command", () => {
|
|
258
|
+
const calls = [{ name: "bash", args: { command: "rm -rf node_modules" } }];
|
|
259
|
+
expect(isMechanicalOnlyTurn(calls)).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
it("is NOT mechanical when args are missing (defensive)", () => {
|
|
262
|
+
expect(isMechanicalOnlyTurn([{ name: "bash" }])).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
describe("extractTurnToolCalls", () => {
|
|
266
|
+
it("collects tool calls only from assistant messages at/after startIndex", () => {
|
|
267
|
+
const messages = [
|
|
268
|
+
{ role: "user", content: "hi" },
|
|
269
|
+
{
|
|
270
|
+
role: "assistant",
|
|
271
|
+
content: [{ type: "tool_call", name: "read", args: { file_path: "old.ts" } }],
|
|
272
|
+
},
|
|
273
|
+
{ role: "tool", content: "ok" },
|
|
274
|
+
{
|
|
275
|
+
role: "assistant",
|
|
276
|
+
content: [
|
|
277
|
+
{ type: "text" },
|
|
278
|
+
{ type: "tool_call", name: "bash", args: { command: "git status" } },
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
expect(extractTurnToolCalls(messages, 2)).toEqual([
|
|
283
|
+
{ name: "bash", args: { command: "git status" } },
|
|
284
|
+
]);
|
|
285
|
+
});
|
|
286
|
+
it("returns an empty array for a string-content assistant message (no tool calls)", () => {
|
|
287
|
+
const messages = [{ role: "assistant", content: "just text, no tools" }];
|
|
288
|
+
expect(extractTurnToolCalls(messages, 0)).toEqual([]);
|
|
289
|
+
});
|
|
290
|
+
it("returns an empty array when startIndex is at the end", () => {
|
|
291
|
+
const messages = [{ role: "assistant", content: [{ type: "tool_call", name: "bash" }] }];
|
|
292
|
+
expect(extractTurnToolCalls(messages, 1)).toEqual([]);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
//# sourceMappingURL=autopilot-gate.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autopilot-gate.test.js","sourceRoot":"","sources":["../../src/core/autopilot-gate.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Autopilot Nolan's verdict contract.
|
|
3
3
|
*
|
|
4
4
|
* In autopilot mode Nolan never talks to the user — he auto-reviews EZ Coder's
|
|
5
|
-
* work and replies with exactly one of
|
|
5
|
+
* work and replies with exactly one of four machine-parseable verdicts. The
|
|
6
6
|
* first non-empty line carries the keyword; anything after is the payload.
|
|
7
7
|
*
|
|
8
8
|
* PROMPT
|
|
@@ -10,17 +10,35 @@
|
|
|
10
10
|
*
|
|
11
11
|
* ALL_CLEAR
|
|
12
12
|
*
|
|
13
|
+
* IGNORE
|
|
14
|
+
*
|
|
13
15
|
* HUMAN
|
|
14
16
|
* <one short reason the human is needed>
|
|
15
17
|
*
|
|
18
|
+
* ALL_CLEAR and IGNORE both stop the cycle with nothing left to do, but they
|
|
19
|
+
* mean different things to the UI: ALL_CLEAR is a verdict on real work ("GG
|
|
20
|
+
* Coder built/changed something and it checks out") and renders a one-line Nolan
|
|
21
|
+
* marker. IGNORE means the turn was never worth reviewing in the first place
|
|
22
|
+
* (small talk, a question, an ack, a mechanical operation with no code
|
|
23
|
+
* changes) — it renders NOTHING, not even a marker, so trivial turns don't
|
|
24
|
+
* spam the transcript with a Nolan bubble that adds no information.
|
|
25
|
+
*
|
|
16
26
|
* Parsing is forgiving and safe-by-default: any reply we can't confidently map
|
|
17
|
-
* to PROMPT or
|
|
27
|
+
* to PROMPT, ALL_CLEAR, or IGNORE becomes a HUMAN stop, never a blind loop.
|
|
28
|
+
*
|
|
29
|
+
* Recovery order for prose-then-keyword drift (Nolan writing commentary before
|
|
30
|
+
* the verdict despite the contract): bare trailing ALL_CLEAR/IGNORE → buried
|
|
31
|
+
* HUMAN → buried line-start PROMPT (uppercase, line-start only, so prose like
|
|
32
|
+
* "prompt the user" can never match). HUMAN wins over PROMPT when both appear
|
|
33
|
+
* — recovering a stop is always safe, recovering an action must lose ties.
|
|
18
34
|
*/
|
|
19
35
|
export type AutopilotVerdict = {
|
|
20
36
|
kind: "prompt";
|
|
21
37
|
body: string;
|
|
22
38
|
} | {
|
|
23
39
|
kind: "all_clear";
|
|
40
|
+
} | {
|
|
41
|
+
kind: "ignore";
|
|
24
42
|
} | {
|
|
25
43
|
kind: "human";
|
|
26
44
|
reason: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autopilot-verdict.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-verdict.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"autopilot-verdict.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-verdict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAyDtC;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAuGrE"}
|