@kenkaiiii/ggcoder 5.6.0 → 5.6.2

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 (62) hide show
  1. package/dist/app-sidecar.js +353 -67
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/agent-session-queue.test.d.ts +2 -0
  4. package/dist/core/agent-session-queue.test.d.ts.map +1 -0
  5. package/dist/core/agent-session-queue.test.js +122 -0
  6. package/dist/core/agent-session-queue.test.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +42 -2
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +76 -3
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/autopilot-cycle.d.ts +67 -0
  12. package/dist/core/autopilot-cycle.d.ts.map +1 -0
  13. package/dist/core/autopilot-cycle.js +50 -0
  14. package/dist/core/autopilot-cycle.js.map +1 -0
  15. package/dist/core/autopilot-cycle.test.d.ts +2 -0
  16. package/dist/core/autopilot-cycle.test.d.ts.map +1 -0
  17. package/dist/core/autopilot-cycle.test.js +179 -0
  18. package/dist/core/autopilot-cycle.test.js.map +1 -0
  19. package/dist/core/autopilot-gate.d.ts +97 -0
  20. package/dist/core/autopilot-gate.d.ts.map +1 -0
  21. package/dist/core/autopilot-gate.js +196 -0
  22. package/dist/core/autopilot-gate.js.map +1 -0
  23. package/dist/core/autopilot-gate.test.d.ts +2 -0
  24. package/dist/core/autopilot-gate.test.d.ts.map +1 -0
  25. package/dist/core/autopilot-gate.test.js +270 -0
  26. package/dist/core/autopilot-gate.test.js.map +1 -0
  27. package/dist/core/autopilot-verdict.d.ts.map +1 -1
  28. package/dist/core/autopilot-verdict.js +41 -1
  29. package/dist/core/autopilot-verdict.js.map +1 -1
  30. package/dist/core/autopilot-verdict.test.js +16 -0
  31. package/dist/core/autopilot-verdict.test.js.map +1 -1
  32. package/dist/core/ken-context.d.ts +23 -2
  33. package/dist/core/ken-context.d.ts.map +1 -1
  34. package/dist/core/ken-context.js +47 -8
  35. package/dist/core/ken-context.js.map +1 -1
  36. package/dist/core/ken-context.test.js +120 -5
  37. package/dist/core/ken-context.test.js.map +1 -1
  38. package/dist/core/ken-model.d.ts +46 -0
  39. package/dist/core/ken-model.d.ts.map +1 -0
  40. package/dist/core/ken-model.js +26 -0
  41. package/dist/core/ken-model.js.map +1 -0
  42. package/dist/core/ken-model.test.d.ts +2 -0
  43. package/dist/core/ken-model.test.d.ts.map +1 -0
  44. package/dist/core/ken-model.test.js +51 -0
  45. package/dist/core/ken-model.test.js.map +1 -0
  46. package/dist/core/ken-prompt.d.ts +2 -15
  47. package/dist/core/ken-prompt.d.ts.map +1 -1
  48. package/dist/core/ken-prompt.js +47 -14
  49. package/dist/core/ken-prompt.js.map +1 -1
  50. package/dist/core/ken-prompt.test.d.ts +2 -0
  51. package/dist/core/ken-prompt.test.d.ts.map +1 -0
  52. package/dist/core/ken-prompt.test.js +76 -0
  53. package/dist/core/ken-prompt.test.js.map +1 -0
  54. package/dist/core/session-manager.d.ts +18 -0
  55. package/dist/core/session-manager.d.ts.map +1 -1
  56. package/dist/core/session-manager.js +31 -0
  57. package/dist/core/session-manager.js.map +1 -1
  58. package/dist/core/session-manager.test.js +71 -1
  59. package/dist/core/session-manager.test.js.map +1 -1
  60. package/dist/core/speed-benchmark.test.js +2 -3
  61. package/dist/core/speed-benchmark.test.js.map +1 -1
  62. package/package.json +4 -4
@@ -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 Ken 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; // GG 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,97 @@
1
+ /** A workflow (prompt-template) command: built-in PROMPT_COMMANDS or a custom
2
+ * `.gg/commands/*.md` entry. `prompt` is the full template body the command
3
+ * expands to when run. */
4
+ export interface WorkflowCommandSpec {
5
+ name: string;
6
+ aliases?: readonly string[];
7
+ prompt: string;
8
+ }
9
+ /** The exact separator AgentSession.prompt() inserts between a command's
10
+ * template and the user's extra args (see agent-session.ts prompt expansion).
11
+ * Must stay byte-identical or expanded-command detection silently breaks. */
12
+ export declare const USER_INSTRUCTIONS_HEADER = "\n\n## User Instructions\n\n";
13
+ /**
14
+ * True when `text` invokes a known workflow command (first token, name or
15
+ * alias, case-insensitive). Registry/UI commands and unknown `/foo` return
16
+ * false — those add no assistant work and are caught by the
17
+ * `no-assistant-output` gate instead.
18
+ */
19
+ export declare function isWorkflowCommandText(text: string, commands: readonly WorkflowCommandSpec[]): boolean;
20
+ /**
21
+ * Match a transcript user-message body back to the workflow command it was
22
+ * expanded from. AgentSession stores the EXPANDED template as a plain user
23
+ * message, so without this Ken's digest renders 400-line templates as
24
+ * `**User:** …` and treats them as user-authored asks.
25
+ *
26
+ * Returns the matched command plus any trailing user args, or null.
27
+ */
28
+ export declare function matchExpandedCommand(text: string, commands: readonly WorkflowCommandSpec[]): {
29
+ command: WorkflowCommandSpec;
30
+ args: string | null;
31
+ } | null;
32
+ /** Count assistant messages — the "did this run produce reviewable work"
33
+ * signal. Compared before/after a run by the sidecar. */
34
+ export declare function countAssistantMessages(messages: ReadonlyArray<{
35
+ role: string;
36
+ }>): number;
37
+ /** Extract every tool call made by assistant messages from `startIndex`
38
+ * onward — i.e. the tool calls added during one turn, given the message
39
+ * array length captured before that turn ran. Used to feed
40
+ * isMechanicalOnlyTurn without the gate module depending on gg-ai's full
41
+ * Message type (kept structurally typed so it's easy to unit test). */
42
+ export declare function extractTurnToolCalls(messages: ReadonlyArray<{
43
+ role: string;
44
+ content: string | ReadonlyArray<{
45
+ type: string;
46
+ name?: string;
47
+ args?: Record<string, unknown>;
48
+ }>;
49
+ }>, startIndex: number): TurnToolCall[];
50
+ export type AutopilotSkipReason = "disabled" | "cancelled" | "plan-mode" | "workflow-command" | "mechanical-only" | "no-assistant-output";
51
+ export interface AutopilotGateInput {
52
+ /** The window's autopilot toggle. */
53
+ enabled: boolean;
54
+ /** True when /cancel fired during the turn. */
55
+ cancelled: boolean;
56
+ /** True when the session ended the turn in plan mode (plan modal pending). */
57
+ planMode: boolean;
58
+ /** True when the turn was a workflow slash command (see isWorkflowCommandText). */
59
+ workflowCommand: boolean;
60
+ /** Assistant messages ADDED by this turn (after minus before). */
61
+ assistantMessagesAdded: number;
62
+ /** True when every tool call this turn was mechanical bash usage (see
63
+ * isMechanicalOnlyTurn) — nothing for Ken to review. Optional so existing
64
+ * callers/tests that don't set it default to false (reviewable). */
65
+ mechanicalOnly?: boolean;
66
+ }
67
+ export type AutopilotGateDecision = {
68
+ start: true;
69
+ } | {
70
+ start: false;
71
+ reason: AutopilotSkipReason;
72
+ };
73
+ /**
74
+ * Decide whether the autopilot cycle may start for a just-finished turn.
75
+ * Checks are ordered cheapest/most-fundamental first; the reason is logged by
76
+ * the sidecar so skips are debuggable from gg-app-sidecar.log.
77
+ */
78
+ export declare function shouldStartAutopilotCycle(input: AutopilotGateInput): AutopilotGateDecision;
79
+ /** A tool call made during a turn, as recorded on an assistant message's
80
+ * content parts. Mirrors gg-ai's `ToolCall` shape minus the fields the gate
81
+ * doesn't need (id). */
82
+ export interface TurnToolCall {
83
+ name: string;
84
+ args?: Record<string, unknown>;
85
+ }
86
+ /**
87
+ * True when the turn has nothing for Ken to review: either it made NO tool
88
+ * calls at all (a plain-text answer — "a plain question that got answered
89
+ * with no code touched" is IGNORE per the autopilot contract), or EVERY tool
90
+ * call was mechanical (a dedicated read-only tool, or bash usage that starts
91
+ * a background process / is read-only / is a plain git commit-push chain).
92
+ * ANY non-mechanical call (edit, write, subagent, generate_image, or bash
93
+ * outside these safe shapes) makes the whole turn reviewable — conservative
94
+ * default: keep reviewing whenever it's unclear.
95
+ */
96
+ export declare function isMechanicalOnlyTurn(toolCalls: readonly TurnToolCall[]): boolean;
97
+ //# 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":"AAiCA;;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;;;;wEAIwE;AACxE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EACH,MAAM,GACN,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;CACpF,CAAC,EACF,UAAU,EAAE,MAAM,GACjB,YAAY,EAAE,CAWhB;AAED,MAAM,MAAM,mBAAmB,GAC3B,UAAU,GACV,WAAW,GACX,WAAW,GACX,kBAAkB,GAClB,iBAAiB,GACjB,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;IAC/B;;yEAEqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;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,CAQ1F;AAED;;yBAEyB;AACzB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAsDD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAOhF"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Autopilot gate — pure decision logic for whether Ken's auto-review cycle may
3
+ * start after a finished GG 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
+ * `.gg/commands/*.md`) end with reports or A/B/C choices that are reserved
10
+ * for the USER. Ken 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 (Ken'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; Ken 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 Ken'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 Ken'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 gg-app-sidecar.log.
111
+ */
112
+ export function shouldStartAutopilotCycle(input) {
113
+ if (!input.enabled)
114
+ return { start: false, reason: "disabled" };
115
+ if (input.cancelled)
116
+ return { start: false, reason: "cancelled" };
117
+ if (input.planMode)
118
+ return { start: false, reason: "plan-mode" };
119
+ if (input.workflowCommand)
120
+ return { start: false, reason: "workflow-command" };
121
+ if (input.mechanicalOnly)
122
+ return { start: false, reason: "mechanical-only" };
123
+ if (input.assistantMessagesAdded <= 0)
124
+ return { start: false, reason: "no-assistant-output" };
125
+ return { start: true };
126
+ }
127
+ /** Dedicated tools that can never touch the repo/filesystem in a way worth
128
+ * reviewing: pure lookups (read/grep/find/ls/source_path/code_search),
129
+ * network research (web_fetch/web_search), reading a background process's
130
+ * output (task_output), a UI screenshot, or loading a skill definition. Any
131
+ * call to one of these is mechanical regardless of its arguments — unlike
132
+ * bash, which needs isMechanicalBashCall to tell a read-only invocation from
133
+ * a mutating one. Deliberately excludes generate_image (produces a real
134
+ * artifact), subagent (can do anything internally), tasks/task_send/
135
+ * task_stop (mutate task/process state), and enter_plan/exit_plan (mode
136
+ * transitions already handled by the separate plan-mode gate check). */
137
+ const READ_ONLY_TOOL_NAMES = new Set([
138
+ "read",
139
+ "grep",
140
+ "find",
141
+ "ls",
142
+ "web_fetch",
143
+ "web_search",
144
+ "source_path",
145
+ "code_search",
146
+ "task_output",
147
+ "screenshot",
148
+ "skill",
149
+ ]);
150
+ /** Leading git subcommands that are a plain add/commit/push workflow —
151
+ * mechanical for review purposes even though they mutate the repo (distinct
152
+ * from plan-mode's stricter read-only bar in read-only-bash.ts). */
153
+ const GIT_COMMIT_WORKFLOW = /^git\s+(add|commit|push)\b/i;
154
+ /**
155
+ * True when a bash call is mechanical: starting a background process (a dev
156
+ * server or watcher — nothing to review until it's stopped and its actual
157
+ * code changes land in a later turn), a read-only command (reuses the same
158
+ * conservative classifier plan mode trusts), or a plain git add/commit/push
159
+ * chain. Conservative by construction: anything it can't prove mechanical
160
+ * (including a chain mixing git with other commands) returns false, so the
161
+ * turn still gets reviewed — false negatives are safe here, false positives
162
+ * are not.
163
+ */
164
+ function isMechanicalBashCall(args) {
165
+ if (!args)
166
+ return false;
167
+ if (args.run_in_background === true)
168
+ return true;
169
+ const command = typeof args.command === "string" ? args.command : "";
170
+ if (!command.trim())
171
+ return false;
172
+ if (isReadOnlyCommand(command))
173
+ return true;
174
+ const segments = command
175
+ .split(/&&|;|\|/)
176
+ .map((s) => s.trim())
177
+ .filter((s) => s.length > 0);
178
+ return segments.length > 0 && segments.every((s) => GIT_COMMIT_WORKFLOW.test(s));
179
+ }
180
+ /**
181
+ * True when the turn has nothing for Ken to review: either it made NO tool
182
+ * calls at all (a plain-text answer — "a plain question that got answered
183
+ * with no code touched" is IGNORE per the autopilot contract), or EVERY tool
184
+ * call was mechanical (a dedicated read-only tool, or bash usage that starts
185
+ * a background process / is read-only / is a plain git commit-push chain).
186
+ * ANY non-mechanical call (edit, write, subagent, generate_image, or bash
187
+ * outside these safe shapes) makes the whole turn reviewable — conservative
188
+ * default: keep reviewing whenever it's unclear.
189
+ */
190
+ export function isMechanicalOnlyTurn(toolCalls) {
191
+ if (toolCalls.length === 0)
192
+ return true;
193
+ return toolCalls.every((call) => READ_ONLY_TOOL_NAMES.has(call.name) ||
194
+ (call.name === "bash" && isMechanicalBashCall(call.args)));
195
+ }
196
+ //# 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=autopilot-gate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-gate.test.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-gate.test.ts"],"names":[],"mappings":""}