@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.
Files changed (134) hide show
  1. package/README.md +2 -2
  2. package/dist/app-sidecar.js +506 -34
  3. package/dist/app-sidecar.js.map +1 -1
  4. package/dist/cli/auth.d.ts.map +1 -1
  5. package/dist/cli/auth.js +19 -2
  6. package/dist/cli/auth.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +80 -27
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.js +32 -30
  11. package/dist/config.js.map +1 -1
  12. package/dist/core/agent-session-compaction.test.js +1 -1
  13. package/dist/core/agent-session-compaction.test.js.map +1 -1
  14. package/dist/core/agent-session-queue.test.d.ts +2 -0
  15. package/dist/core/agent-session-queue.test.d.ts.map +1 -0
  16. package/dist/core/agent-session-queue.test.js +122 -0
  17. package/dist/core/agent-session-queue.test.js.map +1 -0
  18. package/dist/core/agent-session.d.ts +25 -0
  19. package/dist/core/agent-session.d.ts.map +1 -1
  20. package/dist/core/agent-session.js +68 -12
  21. package/dist/core/agent-session.js.map +1 -1
  22. package/dist/core/agent-tools-allowlist.test.d.ts +2 -0
  23. package/dist/core/agent-tools-allowlist.test.d.ts.map +1 -0
  24. package/dist/core/agent-tools-allowlist.test.js +72 -0
  25. package/dist/core/agent-tools-allowlist.test.js.map +1 -0
  26. package/dist/core/auth-providers.d.ts +21 -0
  27. package/dist/core/auth-providers.d.ts.map +1 -1
  28. package/dist/core/auth-providers.js +15 -2
  29. package/dist/core/auth-providers.js.map +1 -1
  30. package/dist/core/autopilot-cycle.d.ts +67 -0
  31. package/dist/core/autopilot-cycle.d.ts.map +1 -0
  32. package/dist/core/autopilot-cycle.js +50 -0
  33. package/dist/core/autopilot-cycle.js.map +1 -0
  34. package/dist/core/autopilot-cycle.test.d.ts +2 -0
  35. package/dist/core/autopilot-cycle.test.d.ts.map +1 -0
  36. package/dist/core/autopilot-cycle.test.js +179 -0
  37. package/dist/core/autopilot-cycle.test.js.map +1 -0
  38. package/dist/core/autopilot-gate.d.ts +83 -0
  39. package/dist/core/autopilot-gate.d.ts.map +1 -0
  40. package/dist/core/autopilot-gate.js +96 -0
  41. package/dist/core/autopilot-gate.js.map +1 -0
  42. package/dist/core/autopilot-gate.test.d.ts +2 -0
  43. package/dist/core/autopilot-gate.test.d.ts.map +1 -0
  44. package/dist/core/autopilot-gate.test.js +159 -0
  45. package/dist/core/autopilot-gate.test.js.map +1 -0
  46. package/dist/core/autopilot-verdict.d.ts +44 -0
  47. package/dist/core/autopilot-verdict.d.ts.map +1 -0
  48. package/dist/core/autopilot-verdict.js +110 -0
  49. package/dist/core/autopilot-verdict.js.map +1 -0
  50. package/dist/core/autopilot-verdict.test.d.ts +2 -0
  51. package/dist/core/autopilot-verdict.test.d.ts.map +1 -0
  52. package/dist/core/autopilot-verdict.test.js +90 -0
  53. package/dist/core/autopilot-verdict.test.js.map +1 -0
  54. package/dist/core/compaction/compactor.test.js +2 -2
  55. package/dist/core/compaction/compactor.test.js.map +1 -1
  56. package/dist/core/compaction/token-estimator.test.js +3 -3
  57. package/dist/core/compaction/token-estimator.test.js.map +1 -1
  58. package/dist/core/event-bus.d.ts +4 -0
  59. package/dist/core/event-bus.d.ts.map +1 -1
  60. package/dist/core/event-bus.js +6 -0
  61. package/dist/core/event-bus.js.map +1 -1
  62. package/dist/core/json-mode-flag-parity.test.d.ts +2 -0
  63. package/dist/core/json-mode-flag-parity.test.d.ts.map +1 -0
  64. package/dist/core/json-mode-flag-parity.test.js +66 -0
  65. package/dist/core/json-mode-flag-parity.test.js.map +1 -0
  66. package/dist/core/nolan-context.d.ts +35 -0
  67. package/dist/core/nolan-context.d.ts.map +1 -1
  68. package/dist/core/nolan-context.js +63 -3
  69. package/dist/core/nolan-context.js.map +1 -1
  70. package/dist/core/nolan-context.test.js +144 -1
  71. package/dist/core/nolan-context.test.js.map +1 -1
  72. package/dist/core/nolan-model.d.ts +46 -0
  73. package/dist/core/nolan-model.d.ts.map +1 -0
  74. package/dist/core/nolan-model.js +30 -0
  75. package/dist/core/nolan-model.js.map +1 -0
  76. package/dist/core/nolan-model.test.d.ts +2 -0
  77. package/dist/core/nolan-model.test.d.ts.map +1 -0
  78. package/dist/core/nolan-model.test.js +51 -0
  79. package/dist/core/nolan-model.test.js.map +1 -0
  80. package/dist/core/nolan-prompt.d.ts +10 -0
  81. package/dist/core/nolan-prompt.d.ts.map +1 -1
  82. package/dist/core/nolan-prompt.js +60 -0
  83. package/dist/core/nolan-prompt.js.map +1 -1
  84. package/dist/core/nolan-prompt.test.d.ts +2 -0
  85. package/dist/core/nolan-prompt.test.d.ts.map +1 -0
  86. package/dist/core/nolan-prompt.test.js +43 -0
  87. package/dist/core/nolan-prompt.test.js.map +1 -0
  88. package/dist/core/prompt-commands.d.ts.map +1 -1
  89. package/dist/core/prompt-commands.js +17 -3
  90. package/dist/core/prompt-commands.js.map +1 -1
  91. package/dist/core/prompt-commands.test.js +27 -1
  92. package/dist/core/prompt-commands.test.js.map +1 -1
  93. package/dist/core/session-manager.test.js +3 -1
  94. package/dist/core/session-manager.test.js.map +1 -1
  95. package/dist/core/speed-benchmark.test.js +2 -3
  96. package/dist/core/speed-benchmark.test.js.map +1 -1
  97. package/dist/core/task-store.d.ts +7 -0
  98. package/dist/core/task-store.d.ts.map +1 -1
  99. package/dist/core/task-store.js +13 -0
  100. package/dist/core/task-store.js.map +1 -1
  101. package/dist/modes/json-mode.d.ts +7 -0
  102. package/dist/modes/json-mode.d.ts.map +1 -1
  103. package/dist/modes/json-mode.js +4 -0
  104. package/dist/modes/json-mode.js.map +1 -1
  105. package/dist/system-prompt.test.js +4 -4
  106. package/dist/system-prompt.test.js.map +1 -1
  107. package/dist/tools/plan-mode.test.js +1 -1
  108. package/dist/tools/plan-mode.test.js.map +1 -1
  109. package/dist/tools/prompt-hints.d.ts.map +1 -1
  110. package/dist/tools/prompt-hints.js +4 -0
  111. package/dist/tools/prompt-hints.js.map +1 -1
  112. package/dist/tools/subagent.d.ts.map +1 -1
  113. package/dist/tools/subagent.js +24 -2
  114. package/dist/tools/subagent.js.map +1 -1
  115. package/dist/ui/App.d.ts.map +1 -1
  116. package/dist/ui/App.js +21 -5
  117. package/dist/ui/App.js.map +1 -1
  118. package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
  119. package/dist/ui/components/ActivityIndicator.js +1 -0
  120. package/dist/ui/components/ActivityIndicator.js.map +1 -1
  121. package/dist/ui/components/Footer.js +1 -1
  122. package/dist/ui/components/Footer.js.map +1 -1
  123. package/dist/ui/hooks/useAgentLoop.d.ts +1 -1
  124. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  125. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  126. package/dist/ui/hooks/useContextCompaction.d.ts.map +1 -1
  127. package/dist/ui/hooks/useContextCompaction.js +4 -2
  128. package/dist/ui/hooks/useContextCompaction.js.map +1 -1
  129. package/dist/ui/login.d.ts.map +1 -1
  130. package/dist/ui/login.js +6 -2
  131. package/dist/ui/login.js.map +1 -1
  132. package/dist/ui/thinking-level-cycle.test.js +1 -1
  133. package/dist/ui/thinking-level-cycle.test.js.map +1 -1
  134. 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":"AAKA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE5C,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;CACxB;AAED,eAAO,MAAM,cAAc,EAAE,gBAAgB,EAqE5C,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE3E"}
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 4.6, Haiku 4.5",
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 UltraSpeed, MiMo-V2.5",
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;AAmB1E,MAAM,CAAC,MAAM,cAAc,GAAuB;IAChD;QACE,KAAK,EAAE,WAAW;QAClB,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,wCAAwC;QACrD,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,oDAAoD;QACjE,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,WAAW,EAAE,aAAa;QAC1B,aAAa,EAAE,0CAA0C;KAC1D;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"}
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=autopilot-cycle.test.d.ts.map
@@ -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"}