@prestyj/cli 5.1.1 → 5.3.0
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/README.md +2 -2
- package/dist/app-sidecar.js +506 -34
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +19 -2
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +80 -27
- package/dist/cli.js.map +1 -1
- package/dist/config.js +32 -30
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-compaction.test.js +1 -1
- 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 +25 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +68 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-tools-allowlist.test.d.ts +2 -0
- package/dist/core/agent-tools-allowlist.test.d.ts.map +1 -0
- package/dist/core/agent-tools-allowlist.test.js +72 -0
- package/dist/core/agent-tools-allowlist.test.js.map +1 -0
- package/dist/core/auth-providers.d.ts +21 -0
- package/dist/core/auth-providers.d.ts.map +1 -1
- package/dist/core/auth-providers.js +15 -2
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/autopilot-cycle.d.ts +67 -0
- package/dist/core/autopilot-cycle.d.ts.map +1 -0
- package/dist/core/autopilot-cycle.js +50 -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 +179 -0
- package/dist/core/autopilot-cycle.test.js.map +1 -0
- package/dist/core/autopilot-gate.d.ts +83 -0
- package/dist/core/autopilot-gate.d.ts.map +1 -0
- package/dist/core/autopilot-gate.js +96 -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 +159 -0
- package/dist/core/autopilot-gate.test.js.map +1 -0
- package/dist/core/autopilot-verdict.d.ts +44 -0
- package/dist/core/autopilot-verdict.d.ts.map +1 -0
- package/dist/core/autopilot-verdict.js +110 -0
- package/dist/core/autopilot-verdict.js.map +1 -0
- package/dist/core/autopilot-verdict.test.d.ts +2 -0
- package/dist/core/autopilot-verdict.test.d.ts.map +1 -0
- package/dist/core/autopilot-verdict.test.js +90 -0
- package/dist/core/autopilot-verdict.test.js.map +1 -0
- package/dist/core/compaction/compactor.test.js +2 -2
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +3 -3
- package/dist/core/compaction/token-estimator.test.js.map +1 -1
- package/dist/core/event-bus.d.ts +4 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +6 -0
- package/dist/core/event-bus.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/nolan-context.d.ts +35 -0
- package/dist/core/nolan-context.d.ts.map +1 -1
- package/dist/core/nolan-context.js +63 -3
- package/dist/core/nolan-context.js.map +1 -1
- package/dist/core/nolan-context.test.js +144 -1
- 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 +10 -0
- package/dist/core/nolan-prompt.d.ts.map +1 -1
- package/dist/core/nolan-prompt.js +60 -0
- 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 +43 -0
- package/dist/core/nolan-prompt.test.js.map +1 -0
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +17 -3
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/prompt-commands.test.js +27 -1
- package/dist/core/prompt-commands.test.js.map +1 -1
- package/dist/core/session-manager.test.js +3 -1
- package/dist/core/session-manager.test.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/core/task-store.d.ts +7 -0
- package/dist/core/task-store.d.ts.map +1 -1
- package/dist/core/task-store.js +13 -0
- package/dist/core/task-store.js.map +1 -1
- package/dist/modes/json-mode.d.ts +7 -0
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +4 -0
- package/dist/modes/json-mode.js.map +1 -1
- package/dist/system-prompt.test.js +4 -4
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/tools/plan-mode.test.js +1 -1
- 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 +4 -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 -2
- package/dist/tools/subagent.js.map +1 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +21 -5
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +1 -0
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/Footer.js +1 -1
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useContextCompaction.d.ts.map +1 -1
- package/dist/ui/hooks/useContextCompaction.js +4 -2
- package/dist/ui/hooks/useContextCompaction.js.map +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +6 -2
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/thinking-level-cycle.test.js +1 -1
- package/dist/ui/thinking-level-cycle.test.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { parseAgentFile } from "./agents.js";
|
|
4
|
+
import { createTools } from "../tools/index.js";
|
|
5
|
+
// Mirror of AgentSession.isToolAllowed (private): with an allow-list, a tool
|
|
6
|
+
// passes only when its exact name is listed (MCP server whitelisting is a
|
|
7
|
+
// separate opt-in the subagent path doesn't use). Kept in lockstep with the
|
|
8
|
+
// real filter so this test tracks production behavior.
|
|
9
|
+
function filterToAllowed(toolNames, allowed) {
|
|
10
|
+
if (!allowed || allowed.length === 0)
|
|
11
|
+
return toolNames;
|
|
12
|
+
return toolNames.filter((name) => allowed.includes(name));
|
|
13
|
+
}
|
|
14
|
+
describe("agent tools frontmatter → allow-list enforcement", () => {
|
|
15
|
+
it("a `tools: read, grep` agent cannot call write, edit, or bash", async () => {
|
|
16
|
+
// The frontmatter is parsed into AgentDefinition.tools, which the subagent
|
|
17
|
+
// spawner forwards to the child as --tools and AgentSession enforces by
|
|
18
|
+
// filtering the registered tool set to those names.
|
|
19
|
+
const agent = parseAgentFile([
|
|
20
|
+
"---",
|
|
21
|
+
"name: reader",
|
|
22
|
+
"description: read-only",
|
|
23
|
+
"tools: read, grep",
|
|
24
|
+
"---",
|
|
25
|
+
"You read.",
|
|
26
|
+
].join("\n"), "project");
|
|
27
|
+
expect(agent.tools).toEqual(["read", "grep"]);
|
|
28
|
+
const { tools, processManager, lspManager } = await createTools(os.tmpdir(), {
|
|
29
|
+
lspDiagnostics: false,
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
const allNames = tools.map((t) => t.name);
|
|
33
|
+
// Sanity: the mutating tools DO exist in the unfiltered set — so the
|
|
34
|
+
// filter is what removes them, not their absence.
|
|
35
|
+
for (const mutating of ["write", "edit", "bash"]) {
|
|
36
|
+
expect(allNames).toContain(mutating);
|
|
37
|
+
}
|
|
38
|
+
const allowedNames = filterToAllowed(allNames, agent.tools);
|
|
39
|
+
// The mutating tools must NOT survive the agent's allow-list.
|
|
40
|
+
for (const banned of ["write", "edit", "bash"]) {
|
|
41
|
+
expect(allowedNames).not.toContain(banned);
|
|
42
|
+
}
|
|
43
|
+
// Exactly the declared read-only tools survive.
|
|
44
|
+
expect(allowedNames.sort()).toEqual(["grep", "read"]);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
processManager.shutdownAll();
|
|
48
|
+
lspManager?.shutdownAll();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
it("an agent with no `tools:` frontmatter keeps the full toolset (backward compatible)", async () => {
|
|
52
|
+
const agent = parseAgentFile(["---", "name: worker", "description: does anything", "---", "You do the work."].join("\n"), "project");
|
|
53
|
+
expect(agent.tools).toEqual([]);
|
|
54
|
+
const { tools, processManager, lspManager } = await createTools(os.tmpdir(), {
|
|
55
|
+
lspDiagnostics: false,
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
const allNames = tools.map((t) => t.name);
|
|
59
|
+
// Empty/unset allow-list is a pass-through: the child keeps every tool.
|
|
60
|
+
const allowedNames = filterToAllowed(allNames, agent.tools);
|
|
61
|
+
expect(allowedNames).toEqual(allNames);
|
|
62
|
+
for (const tool of ["read", "write", "edit", "bash"]) {
|
|
63
|
+
expect(allowedNames).toContain(tool);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
processManager.shutdownAll();
|
|
68
|
+
lspManager?.shutdownAll();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=agent-tools-allowlist.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools-allowlist.test.js","sourceRoot":"","sources":["../../src/core/agent-tools-allowlist.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export type AuthMethod = "oauth" | "apikey";
|
|
2
|
+
/**
|
|
3
|
+
* One API-key option for a provider that splits auth across multiple distinct
|
|
4
|
+
* endpoints/credentials (currently only Xiaomi: Token Plan vs. API Credits).
|
|
5
|
+
* Each variant stores under its own auth.json key so a user can hold both at
|
|
6
|
+
* once — the model registry picks which one a given model resolves via
|
|
7
|
+
* `getAuthStorageKeys()`.
|
|
8
|
+
*/
|
|
9
|
+
export interface ApiKeyVariant {
|
|
10
|
+
/** Storage key in auth.json (distinct from `value` when multiple variants exist). */
|
|
11
|
+
key: string;
|
|
12
|
+
/** Display label, e.g. "Token Plan" or "API Credits". */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Base URL stored alongside this variant's credential. */
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
}
|
|
2
17
|
export interface AuthProviderMeta {
|
|
3
18
|
/** Stable provider id (matches the gg-ai Provider union, plus storage keys). */
|
|
4
19
|
value: string;
|
|
@@ -12,6 +27,12 @@ export interface AuthProviderMeta {
|
|
|
12
27
|
apiKeyLabel?: string;
|
|
13
28
|
/** Fixed base URL stored alongside an API key (e.g. Xiaomi's token plan). */
|
|
14
29
|
apiKeyBaseUrl?: string;
|
|
30
|
+
/**
|
|
31
|
+
* When a provider's API-key auth splits across multiple endpoints, the
|
|
32
|
+
* choices to present (in order). The first variant is the default. Absent
|
|
33
|
+
* for every provider with a single API-key credential.
|
|
34
|
+
*/
|
|
35
|
+
apiKeyVariants?: ApiKeyVariant[];
|
|
15
36
|
}
|
|
16
37
|
export declare const AUTH_PROVIDERS: AuthProviderMeta[];
|
|
17
38
|
export declare function getAuthProvider(value: string): AuthProviderMeta | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-providers.d.ts","sourceRoot":"","sources":["../../src/core/auth-providers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth-providers.d.ts","sourceRoot":"","sources":["../../src/core/auth-providers.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,qFAAqF;IACrF,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,gFAAgF;IAChF,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED,eAAO,MAAM,cAAc,EAAE,gBAAgB,EAiF5C,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE3E"}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// authenticates. Mirrors the CLI's `ezcoder login` provider list (ui/login.tsx)
|
|
3
3
|
// so the desktop app and the terminal stay in lockstep. The app fetches this
|
|
4
4
|
// (plus live connection status) from the sidecar's /auth/status endpoint.
|
|
5
|
+
import { XIAOMI_CREDITS_KEY } from "@prestyj/core";
|
|
5
6
|
export const AUTH_PROVIDERS = [
|
|
6
7
|
{
|
|
7
8
|
value: "anthropic",
|
|
8
9
|
label: "Anthropic",
|
|
9
|
-
description: "Claude Opus 4.8, Sonnet
|
|
10
|
+
description: "Claude Opus 4.8, Fable 5, Sonnet 5, Haiku 4.5",
|
|
10
11
|
methods: ["oauth"],
|
|
11
12
|
},
|
|
12
13
|
{
|
|
@@ -45,10 +46,22 @@ export const AUTH_PROVIDERS = [
|
|
|
45
46
|
{
|
|
46
47
|
value: "xiaomi",
|
|
47
48
|
label: "Xiaomi (MiMo)",
|
|
48
|
-
description: "MiMo-V2.5-Pro, MiMo-V2.5-Pro
|
|
49
|
+
description: "MiMo-V2.5-Pro, MiMo-V2.5-Pro-UltraSpeed, MiMo-V2.5 · Token Plan or API Credits",
|
|
49
50
|
methods: ["apikey"],
|
|
50
51
|
apiKeyLabel: "Xiaomi MiMo",
|
|
51
52
|
apiKeyBaseUrl: "https://token-plan-sgp.xiaomimimo.com/v1",
|
|
53
|
+
apiKeyVariants: [
|
|
54
|
+
{
|
|
55
|
+
key: "xiaomi",
|
|
56
|
+
label: "Token Plan",
|
|
57
|
+
baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: XIAOMI_CREDITS_KEY,
|
|
61
|
+
label: "API Credits (required for UltraSpeed)",
|
|
62
|
+
baseUrl: "https://api.xiaomimimo.com/v1",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
52
65
|
},
|
|
53
66
|
{
|
|
54
67
|
value: "deepseek",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-providers.js","sourceRoot":"","sources":["../../src/core/auth-providers.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,0EAA0E;
|
|
1
|
+
{"version":3,"file":"auth-providers.js","sourceRoot":"","sources":["../../src/core/auth-providers.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,0EAA0E;AAE1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAyCnD,MAAM,CAAC,MAAM,cAAc,GAAuB;IAChD;QACE,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,+CAA+C;QAC5D,OAAO,EAAE,CAAC,OAAO,CAAC;KACnB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,8CAA8C;QAC3D,OAAO,EAAE,CAAC,OAAO,CAAC;KACnB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,+BAA+B;QAC5C,OAAO,EAAE,CAAC,OAAO,CAAC;KACnB;IACD;QACE,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,8BAA8B;QAC3C,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC;QAC5B,WAAW,EAAE,UAAU;KACxB;IACD;QACE,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,iCAAiC;QAC9C,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,MAAM;KACpB;IACD;QACE,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,YAAY;QACzB,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,SAAS;KACvB;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,gFAAgF;QAC7F,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,aAAa;QAC1B,aAAa,EAAE,0CAA0C;QACzD,cAAc,EAAE;YACd;gBACE,GAAG,EAAE,QAAQ;gBACb,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,0CAA0C;aACpD;YACD;gBACE,GAAG,EAAE,kBAAkB;gBACvB,KAAK,EAAE,uCAAuC;gBAC9C,OAAO,EAAE,+BAA+B;aACzC;SACF;KACF;IACD;QACE,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,2BAA2B;QACxC,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,UAAU;KACxB;IACD;QACE,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,sCAAsC;QACnD,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,YAAY;KAC1B;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,kBAAkB;QAC/B,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,QAAQ;KACtB;CACF,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autopilot cycle — the review→prompt→review loop as pure orchestration logic.
|
|
3
|
+
*
|
|
4
|
+
* The sidecar wires the real dependencies (nolanAuto session, runAgent, SSE
|
|
5
|
+
* broadcast); this module owns the loop's control flow so every exit path is
|
|
6
|
+
* unit-testable without booting the sidecar: verdict handling, the round cap,
|
|
7
|
+
* cancellation between steps, review failure, and the mid-cycle plan-mode hold
|
|
8
|
+
* (an injected run calling enter_plan/exit_plan must halt the loop — Nolan can
|
|
9
|
+
* never inject a prompt into a read-only plan-mode session or answer a pending
|
|
10
|
+
* plan-review modal on the user's behalf).
|
|
11
|
+
*/
|
|
12
|
+
import type { AutopilotVerdict } from "./autopilot-verdict.js";
|
|
13
|
+
/** Reason shown in the Nolan bubble when an injected run enters plan mode
|
|
14
|
+
* mid-cycle — the loop halts and hands the decision to the user. */
|
|
15
|
+
export declare const AUTOPILOT_PLAN_HOLD_REASON = "EZ Coder submitted a plan for your review. Approve or reject it yourself; autopilot won't decide that for you.";
|
|
16
|
+
/** SSE frame types the cycle can emit (matched by the webview). */
|
|
17
|
+
export type AutopilotCycleEmit = {
|
|
18
|
+
type: "autopilot_done";
|
|
19
|
+
data: Record<string, never>;
|
|
20
|
+
} | {
|
|
21
|
+
type: "autopilot_ignored";
|
|
22
|
+
data: Record<string, never>;
|
|
23
|
+
} | {
|
|
24
|
+
type: "autopilot_human";
|
|
25
|
+
data: {
|
|
26
|
+
reason: string;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
type: "autopilot_capped";
|
|
30
|
+
data: {
|
|
31
|
+
rounds: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export interface AutopilotCycleDeps {
|
|
35
|
+
/** Hard cap on review→prompt rounds per user turn (loop safety). */
|
|
36
|
+
maxRounds: number;
|
|
37
|
+
/** True once /cancel fires — checked between every step. */
|
|
38
|
+
isCancelled: () => boolean;
|
|
39
|
+
/** Live plan-mode state of the BUILD session. */
|
|
40
|
+
isPlanMode: () => boolean;
|
|
41
|
+
/** Wipe the reviewer's history so each user turn starts cheap (within one
|
|
42
|
+
* cycle the review messages persist so Nolan remembers what he asked). */
|
|
43
|
+
resetReviewer: () => Promise<void>;
|
|
44
|
+
/** Run one review; resolves to the parsed verdict or null on failure
|
|
45
|
+
* (failure is already surfaced by the sidecar as autopilot_error). */
|
|
46
|
+
review: () => Promise<AutopilotVerdict | null>;
|
|
47
|
+
/** Feed a PROMPT verdict's body to EZ Coder as an injected run. */
|
|
48
|
+
runPrompt: (body: string) => Promise<void>;
|
|
49
|
+
/** Called BEFORE runPrompt: record the injected body (digest labeling) and
|
|
50
|
+
* broadcast the autopilot_prompted marker. */
|
|
51
|
+
onInjected: (body: string, round: number) => void;
|
|
52
|
+
/** Broadcast one of the cycle's terminal SSE frames. */
|
|
53
|
+
emit: (event: AutopilotCycleEmit) => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Drive one full autopilot cycle for a finished user turn. Every exit is
|
|
57
|
+
* explicit:
|
|
58
|
+
* - cancelled → silent stop (the /cancel path already broadcast)
|
|
59
|
+
* - plan mode mid-cycle → autopilot_human with the plan-hold reason
|
|
60
|
+
* - review failed (null) → silent stop (autopilot_error already broadcast)
|
|
61
|
+
* - ALL_CLEAR → autopilot_done
|
|
62
|
+
* - IGNORE → autopilot_ignored (renders nothing)
|
|
63
|
+
* - HUMAN → autopilot_human
|
|
64
|
+
* - rounds exhausted → autopilot_capped
|
|
65
|
+
*/
|
|
66
|
+
export declare function driveAutopilotCycle(deps: AutopilotCycleDeps): Promise<void>;
|
|
67
|
+
//# sourceMappingURL=autopilot-cycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autopilot-cycle.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-cycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;qEACqE;AACrE,eAAO,MAAM,0BAA0B,mHAC2E,CAAC;AAEnH,mEAAmE;AACnE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,OAAO,CAAC;IAC3B,iDAAiD;IACjD,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B;+EAC2E;IAC3E,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC;2EACuE;IACvE,MAAM,EAAE,MAAM,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAC/C,mEAAmE;IACnE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C;mDAC+C;IAC/C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,wDAAwD;IACxD,IAAI,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BjF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Reason shown in the Nolan bubble when an injected run enters plan mode
|
|
2
|
+
* mid-cycle — the loop halts and hands the decision to the user. */
|
|
3
|
+
export const AUTOPILOT_PLAN_HOLD_REASON = "EZ Coder submitted a plan for your review. Approve or reject it yourself; autopilot won't decide that for you.";
|
|
4
|
+
/**
|
|
5
|
+
* Drive one full autopilot cycle for a finished user turn. Every exit is
|
|
6
|
+
* explicit:
|
|
7
|
+
* - cancelled → silent stop (the /cancel path already broadcast)
|
|
8
|
+
* - plan mode mid-cycle → autopilot_human with the plan-hold reason
|
|
9
|
+
* - review failed (null) → silent stop (autopilot_error already broadcast)
|
|
10
|
+
* - ALL_CLEAR → autopilot_done
|
|
11
|
+
* - IGNORE → autopilot_ignored (renders nothing)
|
|
12
|
+
* - HUMAN → autopilot_human
|
|
13
|
+
* - rounds exhausted → autopilot_capped
|
|
14
|
+
*/
|
|
15
|
+
export async function driveAutopilotCycle(deps) {
|
|
16
|
+
if (deps.isCancelled())
|
|
17
|
+
return;
|
|
18
|
+
await deps.resetReviewer();
|
|
19
|
+
for (let round = 1; round <= deps.maxRounds; round++) {
|
|
20
|
+
if (deps.isCancelled())
|
|
21
|
+
return;
|
|
22
|
+
// The gate blocks a plan-mode turn up front, so hitting this means an
|
|
23
|
+
// injected run entered plan mode mid-cycle: halt and hand it to the user.
|
|
24
|
+
if (deps.isPlanMode()) {
|
|
25
|
+
deps.emit({ type: "autopilot_human", data: { reason: AUTOPILOT_PLAN_HOLD_REASON } });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const verdict = await deps.review();
|
|
29
|
+
if (!verdict || deps.isCancelled())
|
|
30
|
+
return;
|
|
31
|
+
if (verdict.kind === "all_clear") {
|
|
32
|
+
deps.emit({ type: "autopilot_done", data: {} });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (verdict.kind === "ignore") {
|
|
36
|
+
deps.emit({ type: "autopilot_ignored", data: {} });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (verdict.kind === "human") {
|
|
40
|
+
deps.emit({ type: "autopilot_human", data: { reason: verdict.reason } });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
deps.onInjected(verdict.body, round);
|
|
44
|
+
await deps.runPrompt(verdict.body);
|
|
45
|
+
if (deps.isCancelled())
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
deps.emit({ type: "autopilot_capped", data: { rounds: deps.maxRounds } });
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=autopilot-cycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autopilot-cycle.js","sourceRoot":"","sources":["../../src/core/autopilot-cycle.ts"],"names":[],"mappings":"AAaA;qEACqE;AACrE,MAAM,CAAC,MAAM,0BAA0B,GACrC,gHAAgH,CAAC;AA+BnH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAwB;IAChE,IAAI,IAAI,CAAC,WAAW,EAAE;QAAE,OAAO;IAC/B,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,IAAI,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO;QAC/B,sEAAsE;QACtE,0EAA0E;QAC1E,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,0BAA0B,EAAE,EAAE,CAAC,CAAC;YACrF,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO;QAC3C,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACzE,OAAO;QACT,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO;IACjC,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autopilot-cycle.test.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-cycle.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { driveAutopilotCycle, AUTOPILOT_PLAN_HOLD_REASON, } from "./autopilot-cycle.js";
|
|
3
|
+
/** Build a full deps object with sane defaults; tests override what they probe.
|
|
4
|
+
* `verdicts` is consumed in order; running out returns null (review failure). */
|
|
5
|
+
function makeDeps(verdicts, overrides = {}) {
|
|
6
|
+
const emitted = [];
|
|
7
|
+
const injected = [];
|
|
8
|
+
const ran = [];
|
|
9
|
+
const queue = [...verdicts];
|
|
10
|
+
const deps = {
|
|
11
|
+
maxRounds: 3,
|
|
12
|
+
isCancelled: () => false,
|
|
13
|
+
isPlanMode: () => false,
|
|
14
|
+
resetReviewer: vi.fn(async () => { }),
|
|
15
|
+
review: vi.fn(async () => queue.shift() ?? null),
|
|
16
|
+
runPrompt: async (body) => {
|
|
17
|
+
ran.push(body);
|
|
18
|
+
},
|
|
19
|
+
onInjected: (body, round) => {
|
|
20
|
+
injected.push({ body, round });
|
|
21
|
+
},
|
|
22
|
+
emit: (event) => {
|
|
23
|
+
emitted.push(event);
|
|
24
|
+
},
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
// Overrides may swap the vi.fn defaults for plain functions; every test that
|
|
28
|
+
// asserts on mock calls passes a vi.fn itself, so the cast is safe.
|
|
29
|
+
return Object.assign(deps, { emitted, injected, ran });
|
|
30
|
+
}
|
|
31
|
+
describe("driveAutopilotCycle", () => {
|
|
32
|
+
it("ALL_CLEAR → autopilot_done, no injected run", async () => {
|
|
33
|
+
const deps = makeDeps([{ kind: "all_clear" }]);
|
|
34
|
+
await driveAutopilotCycle(deps);
|
|
35
|
+
expect(deps.emitted).toEqual([{ type: "autopilot_done", data: {} }]);
|
|
36
|
+
expect(deps.ran).toEqual([]);
|
|
37
|
+
expect(deps.resetReviewer).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
it("IGNORE → autopilot_ignored, no injected run", async () => {
|
|
40
|
+
const deps = makeDeps([{ kind: "ignore" }]);
|
|
41
|
+
await driveAutopilotCycle(deps);
|
|
42
|
+
expect(deps.emitted).toEqual([{ type: "autopilot_ignored", data: {} }]);
|
|
43
|
+
expect(deps.ran).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
it("HUMAN → autopilot_human with the verdict's reason", async () => {
|
|
46
|
+
const deps = makeDeps([{ kind: "human", reason: "ambiguous requirement" }]);
|
|
47
|
+
await driveAutopilotCycle(deps);
|
|
48
|
+
expect(deps.emitted).toEqual([
|
|
49
|
+
{ type: "autopilot_human", data: { reason: "ambiguous requirement" } },
|
|
50
|
+
]);
|
|
51
|
+
expect(deps.ran).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
it("PROMPT → records the injection BEFORE running, then re-reviews", async () => {
|
|
54
|
+
const order = [];
|
|
55
|
+
const deps = makeDeps([{ kind: "prompt", body: "fix the test" }, { kind: "all_clear" }], {
|
|
56
|
+
onInjected: (body) => order.push(`injected:${body}`),
|
|
57
|
+
runPrompt: async (body) => {
|
|
58
|
+
order.push(`ran:${body}`);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
await driveAutopilotCycle(deps);
|
|
62
|
+
// onInjected must precede runPrompt — the digest labeling depends on the
|
|
63
|
+
// body being recorded before the injected run's messages exist.
|
|
64
|
+
expect(order).toEqual(["injected:fix the test", "ran:fix the test"]);
|
|
65
|
+
expect(deps.emitted).toEqual([{ type: "autopilot_done", data: {} }]);
|
|
66
|
+
expect(deps.review).toHaveBeenCalledTimes(2);
|
|
67
|
+
});
|
|
68
|
+
it("caps at maxRounds PROMPT verdicts → autopilot_capped", async () => {
|
|
69
|
+
const deps = makeDeps([
|
|
70
|
+
{ kind: "prompt", body: "fix 1" },
|
|
71
|
+
{ kind: "prompt", body: "fix 2" },
|
|
72
|
+
{ kind: "prompt", body: "fix 3" },
|
|
73
|
+
// Would be round 4 — must never be reached.
|
|
74
|
+
{ kind: "prompt", body: "fix 4" },
|
|
75
|
+
]);
|
|
76
|
+
await driveAutopilotCycle(deps);
|
|
77
|
+
expect(deps.ran).toEqual(["fix 1", "fix 2", "fix 3"]);
|
|
78
|
+
expect(deps.emitted).toEqual([{ type: "autopilot_capped", data: { rounds: 3 } }]);
|
|
79
|
+
expect(deps.review).toHaveBeenCalledTimes(3);
|
|
80
|
+
});
|
|
81
|
+
it("review failure (null) → silent stop, nothing injected", async () => {
|
|
82
|
+
const deps = makeDeps([null]);
|
|
83
|
+
await driveAutopilotCycle(deps);
|
|
84
|
+
expect(deps.emitted).toEqual([]);
|
|
85
|
+
expect(deps.ran).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
it("cancelled before start → nothing runs, reviewer untouched", async () => {
|
|
88
|
+
const deps = makeDeps([{ kind: "all_clear" }], { isCancelled: () => true });
|
|
89
|
+
await driveAutopilotCycle(deps);
|
|
90
|
+
expect(deps.resetReviewer).not.toHaveBeenCalled();
|
|
91
|
+
expect(deps.review).not.toHaveBeenCalled();
|
|
92
|
+
expect(deps.emitted).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
it("cancel landing during the review discards the verdict", async () => {
|
|
95
|
+
let cancelled = false;
|
|
96
|
+
const deps = makeDeps([], {
|
|
97
|
+
review: vi.fn(async () => {
|
|
98
|
+
cancelled = true; // /cancel fires while Nolan is reviewing
|
|
99
|
+
return { kind: "prompt", body: "fix it" };
|
|
100
|
+
}),
|
|
101
|
+
isCancelled: () => cancelled,
|
|
102
|
+
});
|
|
103
|
+
await driveAutopilotCycle(deps);
|
|
104
|
+
expect(deps.ran).toEqual([]);
|
|
105
|
+
expect(deps.emitted).toEqual([]);
|
|
106
|
+
});
|
|
107
|
+
it("cancel landing during an injected run stops before the next review", async () => {
|
|
108
|
+
let cancelled = false;
|
|
109
|
+
const deps = makeDeps([{ kind: "prompt", body: "fix it" }, { kind: "all_clear" }], {
|
|
110
|
+
runPrompt: async () => {
|
|
111
|
+
cancelled = true; // /cancel fires mid-injected-run
|
|
112
|
+
},
|
|
113
|
+
isCancelled: () => cancelled,
|
|
114
|
+
});
|
|
115
|
+
await driveAutopilotCycle(deps);
|
|
116
|
+
expect(deps.review).toHaveBeenCalledTimes(1);
|
|
117
|
+
expect(deps.emitted).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
it("plan mode at cycle start → autopilot_human plan hold, no review", async () => {
|
|
120
|
+
const deps = makeDeps([{ kind: "all_clear" }], { isPlanMode: () => true });
|
|
121
|
+
await driveAutopilotCycle(deps);
|
|
122
|
+
expect(deps.review).not.toHaveBeenCalled();
|
|
123
|
+
expect(deps.emitted).toEqual([
|
|
124
|
+
{ type: "autopilot_human", data: { reason: AUTOPILOT_PLAN_HOLD_REASON } },
|
|
125
|
+
]);
|
|
126
|
+
});
|
|
127
|
+
it("injected run entering plan mode halts the loop before the next review", async () => {
|
|
128
|
+
let planMode = false;
|
|
129
|
+
const ran = [];
|
|
130
|
+
const deps = makeDeps([
|
|
131
|
+
{ kind: "prompt", body: "restructure the module" },
|
|
132
|
+
// Would be reviewed in round 2 — must never be reached.
|
|
133
|
+
{ kind: "prompt", body: "another fix" },
|
|
134
|
+
], {
|
|
135
|
+
runPrompt: async (body) => {
|
|
136
|
+
ran.push(body);
|
|
137
|
+
planMode = true; // EZ Coder called enter_plan/exit_plan during the run
|
|
138
|
+
},
|
|
139
|
+
isPlanMode: () => planMode,
|
|
140
|
+
});
|
|
141
|
+
await driveAutopilotCycle(deps);
|
|
142
|
+
expect(ran).toEqual(["restructure the module"]);
|
|
143
|
+
expect(deps.review).toHaveBeenCalledTimes(1);
|
|
144
|
+
expect(deps.emitted).toEqual([
|
|
145
|
+
{ type: "autopilot_human", data: { reason: AUTOPILOT_PLAN_HOLD_REASON } },
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
it("multi-round: prompt → prompt → all_clear runs both fixes then finishes", async () => {
|
|
149
|
+
const deps = makeDeps([
|
|
150
|
+
{ kind: "prompt", body: "fix 1" },
|
|
151
|
+
{ kind: "prompt", body: "fix 2" },
|
|
152
|
+
{ kind: "all_clear" },
|
|
153
|
+
]);
|
|
154
|
+
await driveAutopilotCycle(deps);
|
|
155
|
+
expect(deps.ran).toEqual(["fix 1", "fix 2"]);
|
|
156
|
+
expect(deps.injected.map((i) => i.round)).toEqual([1, 2]);
|
|
157
|
+
expect(deps.emitted).toEqual([{ type: "autopilot_done", data: {} }]);
|
|
158
|
+
});
|
|
159
|
+
it("resets the reviewer exactly once per cycle, before any review", async () => {
|
|
160
|
+
const order = [];
|
|
161
|
+
const deps = makeDeps([{ kind: "prompt", body: "fix" }, { kind: "all_clear" }], {
|
|
162
|
+
resetReviewer: vi.fn(async () => {
|
|
163
|
+
order.push("reset");
|
|
164
|
+
}),
|
|
165
|
+
runPrompt: async () => {
|
|
166
|
+
order.push("run");
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
// Wrap review to trace ordering while preserving queue behavior.
|
|
170
|
+
const innerReview = deps.review;
|
|
171
|
+
deps.review = vi.fn(async () => {
|
|
172
|
+
order.push("review");
|
|
173
|
+
return innerReview();
|
|
174
|
+
});
|
|
175
|
+
await driveAutopilotCycle(deps);
|
|
176
|
+
expect(order).toEqual(["reset", "review", "run", "review"]);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
//# sourceMappingURL=autopilot-cycle.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autopilot-cycle.test.js","sourceRoot":"","sources":["../../src/core/autopilot-cycle.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
*
|
|
19
|
+
* Kept pure + dependency-light so it's unit-testable without booting the
|
|
20
|
+
* sidecar (which runs `main()` at import time).
|
|
21
|
+
*/
|
|
22
|
+
/** A workflow (prompt-template) command: built-in PROMPT_COMMANDS or a custom
|
|
23
|
+
* `.ezcoder/commands/*.md` entry. `prompt` is the full template body the command
|
|
24
|
+
* expands to when run. */
|
|
25
|
+
export interface WorkflowCommandSpec {
|
|
26
|
+
name: string;
|
|
27
|
+
aliases?: readonly string[];
|
|
28
|
+
prompt: string;
|
|
29
|
+
}
|
|
30
|
+
/** The exact separator AgentSession.prompt() inserts between a command's
|
|
31
|
+
* template and the user's extra args (see agent-session.ts prompt expansion).
|
|
32
|
+
* Must stay byte-identical or expanded-command detection silently breaks. */
|
|
33
|
+
export declare const USER_INSTRUCTIONS_HEADER = "\n\n## User Instructions\n\n";
|
|
34
|
+
/**
|
|
35
|
+
* True when `text` invokes a known workflow command (first token, name or
|
|
36
|
+
* alias, case-insensitive). Registry/UI commands and unknown `/foo` return
|
|
37
|
+
* false — those add no assistant work and are caught by the
|
|
38
|
+
* `no-assistant-output` gate instead.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isWorkflowCommandText(text: string, commands: readonly WorkflowCommandSpec[]): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Match a transcript user-message body back to the workflow command it was
|
|
43
|
+
* expanded from. AgentSession stores the EXPANDED template as a plain user
|
|
44
|
+
* message, so without this Nolan's digest renders 400-line templates as
|
|
45
|
+
* `**User:** …` and treats them as user-authored asks.
|
|
46
|
+
*
|
|
47
|
+
* Returns the matched command plus any trailing user args, or null.
|
|
48
|
+
*/
|
|
49
|
+
export declare function matchExpandedCommand(text: string, commands: readonly WorkflowCommandSpec[]): {
|
|
50
|
+
command: WorkflowCommandSpec;
|
|
51
|
+
args: string | null;
|
|
52
|
+
} | null;
|
|
53
|
+
/** Count assistant messages — the "did this run produce reviewable work"
|
|
54
|
+
* signal. Compared before/after a run by the sidecar. */
|
|
55
|
+
export declare function countAssistantMessages(messages: ReadonlyArray<{
|
|
56
|
+
role: string;
|
|
57
|
+
}>): number;
|
|
58
|
+
export type AutopilotSkipReason = "disabled" | "cancelled" | "plan-mode" | "workflow-command" | "no-assistant-output";
|
|
59
|
+
export interface AutopilotGateInput {
|
|
60
|
+
/** The window's autopilot toggle. */
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
/** True when /cancel fired during the turn. */
|
|
63
|
+
cancelled: boolean;
|
|
64
|
+
/** True when the session ended the turn in plan mode (plan modal pending). */
|
|
65
|
+
planMode: boolean;
|
|
66
|
+
/** True when the turn was a workflow slash command (see isWorkflowCommandText). */
|
|
67
|
+
workflowCommand: boolean;
|
|
68
|
+
/** Assistant messages ADDED by this turn (after minus before). */
|
|
69
|
+
assistantMessagesAdded: number;
|
|
70
|
+
}
|
|
71
|
+
export type AutopilotGateDecision = {
|
|
72
|
+
start: true;
|
|
73
|
+
} | {
|
|
74
|
+
start: false;
|
|
75
|
+
reason: AutopilotSkipReason;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Decide whether the autopilot cycle may start for a just-finished turn.
|
|
79
|
+
* Checks are ordered cheapest/most-fundamental first; the reason is logged by
|
|
80
|
+
* the sidecar so skips are debuggable from ezcoder-app-sidecar.log.
|
|
81
|
+
*/
|
|
82
|
+
export declare function shouldStartAutopilotCycle(input: AutopilotGateInput): AutopilotGateDecision;
|
|
83
|
+
//# sourceMappingURL=autopilot-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autopilot-gate.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;2BAE2B;AAC3B,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;8EAE8E;AAC9E,eAAO,MAAM,wBAAwB,iCAAiC,CAAC;AAWvE;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,mBAAmB,EAAE,GACvC,OAAO,CAMT;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,mBAAmB,EAAE,GACvC;IAAE,OAAO,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAW9D;AAED;0DAC0D;AAC1D,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAIxF;AAED,MAAM,MAAM,mBAAmB,GAC3B,UAAU,GACV,WAAW,GACX,WAAW,GACX,kBAAkB,GAClB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,eAAe,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,qBAAqB,GAAG;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAEpG;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,kBAAkB,GAAG,qBAAqB,CAO1F"}
|