pi-orchestrator-config 4.1.5

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 (175) hide show
  1. package/README.md +808 -0
  2. package/agents/api-documenter.md +32 -0
  3. package/agents/bash-expert.md +53 -0
  4. package/agents/code-reviewer-docs.md +145 -0
  5. package/agents/code-reviewer-guidelines.md +97 -0
  6. package/agents/code-reviewer-quality.md +142 -0
  7. package/agents/code-reviewer-security.md +107 -0
  8. package/agents/code-reviewer-spec.md +164 -0
  9. package/agents/debugger.md +63 -0
  10. package/agents/docker-expert.md +40 -0
  11. package/agents/docs-fetcher.md +48 -0
  12. package/agents/git-expert.md +63 -0
  13. package/agents/github-expert.md +83 -0
  14. package/agents/go-expert.md +62 -0
  15. package/agents/issue-reviewer-feasibility.md +60 -0
  16. package/agents/issue-reviewer-scope.md +63 -0
  17. package/agents/issue-reviewer-spec.md +77 -0
  18. package/agents/java-expert.md +31 -0
  19. package/agents/jenkins-expert.md +38 -0
  20. package/agents/kubernetes-expert.md +40 -0
  21. package/agents/planner.md +42 -0
  22. package/agents/python-expert.md +61 -0
  23. package/agents/reviewer.md +26 -0
  24. package/agents/scout.md +40 -0
  25. package/agents/security-auditor.md +254 -0
  26. package/agents/technical-documentation-writer.md +37 -0
  27. package/agents/test-automator.md +55 -0
  28. package/agents/test-runner.md +47 -0
  29. package/agents/ts-expert.md +23 -0
  30. package/agents/worker.md +30 -0
  31. package/extensions/acpx-provider/index.ts +104 -0
  32. package/extensions/acpx-provider/load-runtime.ts +99 -0
  33. package/extensions/acpx-provider/runtime-models.ts +40 -0
  34. package/extensions/cli-provider/agents/claude.ts +128 -0
  35. package/extensions/cli-provider/agents/cursor.ts +84 -0
  36. package/extensions/cli-provider/agents/gemini.ts +100 -0
  37. package/extensions/cli-provider/discover.ts +77 -0
  38. package/extensions/cli-provider/index.ts +57 -0
  39. package/extensions/cli-provider/parsers.ts +304 -0
  40. package/extensions/cli-provider/providers.ts +58 -0
  41. package/extensions/cli-provider/runner.ts +225 -0
  42. package/extensions/cli-provider/runtime-models.ts +32 -0
  43. package/extensions/cli-provider/session-reaper.ts +122 -0
  44. package/extensions/cli-provider/sessions.ts +533 -0
  45. package/extensions/cli-provider/shared/discover-cache.ts +235 -0
  46. package/extensions/cli-provider/types.ts +48 -0
  47. package/extensions/coms/coms-net-server.ts +1642 -0
  48. package/extensions/coms/coms-net-wrapper.ts +521 -0
  49. package/extensions/coms/coms-net.ts +1589 -0
  50. package/extensions/coms/coms-p2p.ts +1879 -0
  51. package/extensions/coms/coms-shared.ts +510 -0
  52. package/extensions/coms/coms-wrapper.ts +138 -0
  53. package/extensions/coms/index.ts +20 -0
  54. package/extensions/coms/themeMap.ts +147 -0
  55. package/extensions/image-gen/image-gen.ts +332 -0
  56. package/extensions/image-gen/index.ts +14 -0
  57. package/extensions/orchestrator/agents.ts +134 -0
  58. package/extensions/orchestrator/ask-user.ts +293 -0
  59. package/extensions/orchestrator/async-agents.ts +1037 -0
  60. package/extensions/orchestrator/async-capability.ts +129 -0
  61. package/extensions/orchestrator/async-runner.ts +324 -0
  62. package/extensions/orchestrator/async-status-parse.ts +31 -0
  63. package/extensions/orchestrator/async-status-ui.ts +201 -0
  64. package/extensions/orchestrator/async-wait.ts +36 -0
  65. package/extensions/orchestrator/btw.ts +210 -0
  66. package/extensions/orchestrator/cron-status-format.ts +126 -0
  67. package/extensions/orchestrator/cron-status-ui.ts +132 -0
  68. package/extensions/orchestrator/cron.ts +730 -0
  69. package/extensions/orchestrator/dreaming.ts +355 -0
  70. package/extensions/orchestrator/enforcement-helpers.ts +530 -0
  71. package/extensions/orchestrator/enforcement-rules.ts +329 -0
  72. package/extensions/orchestrator/enforcement.ts +828 -0
  73. package/extensions/orchestrator/extended-autocomplete.ts +593 -0
  74. package/extensions/orchestrator/git-helpers.ts +420 -0
  75. package/extensions/orchestrator/github-autocomplete.ts +250 -0
  76. package/extensions/orchestrator/icons.ts +17 -0
  77. package/extensions/orchestrator/index.ts +100 -0
  78. package/extensions/orchestrator/memory-embeddings.ts +281 -0
  79. package/extensions/orchestrator/memory-promotion.ts +449 -0
  80. package/extensions/orchestrator/memory-provenance.ts +80 -0
  81. package/extensions/orchestrator/memory-query-class.ts +90 -0
  82. package/extensions/orchestrator/memory-scoring.ts +434 -0
  83. package/extensions/orchestrator/memory-tools.ts +883 -0
  84. package/extensions/orchestrator/memory-tree.ts +212 -0
  85. package/extensions/orchestrator/nvim.ts +169 -0
  86. package/extensions/orchestrator/overlay-dashboard-utils.ts +25 -0
  87. package/extensions/orchestrator/overlay-dashboard.ts +512 -0
  88. package/extensions/orchestrator/pi-config-review-state.ts +303 -0
  89. package/extensions/orchestrator/preference-extractor.ts +106 -0
  90. package/extensions/orchestrator/project-settings.ts +468 -0
  91. package/extensions/orchestrator/promotion-queue.ts +311 -0
  92. package/extensions/orchestrator/resolve-agent-model.ts +32 -0
  93. package/extensions/orchestrator/review-ui.ts +204 -0
  94. package/extensions/orchestrator/rule-placeholders.ts +12 -0
  95. package/extensions/orchestrator/rules.ts +481 -0
  96. package/extensions/orchestrator/session-search.ts +243 -0
  97. package/extensions/orchestrator/session-validation.ts +374 -0
  98. package/extensions/orchestrator/situation-report.ts +370 -0
  99. package/extensions/orchestrator/status-bar.ts +31 -0
  100. package/extensions/orchestrator/status-line.ts +192 -0
  101. package/extensions/orchestrator/status.ts +110 -0
  102. package/extensions/orchestrator/subagent-tool.ts +1748 -0
  103. package/extensions/orchestrator/task-lifecycle.ts +88 -0
  104. package/extensions/orchestrator/utils.ts +227 -0
  105. package/extensions/pidash/index.ts +15 -0
  106. package/extensions/pidash/pidash.ts +941 -0
  107. package/extensions/pidiff/index.ts +13 -0
  108. package/extensions/pidiff/pidiff.ts +369 -0
  109. package/extensions/providers/acpx-driver.ts +479 -0
  110. package/extensions/providers/built-in-drivers.ts +52 -0
  111. package/extensions/providers/claude-driver.ts +319 -0
  112. package/extensions/providers/cursor-acpx-driver.ts +398 -0
  113. package/extensions/providers/cursor-cli-driver.ts +484 -0
  114. package/extensions/providers/gemini-driver.ts +302 -0
  115. package/extensions/providers/index.ts +513 -0
  116. package/extensions/shared/build-system-prompt.ts +57 -0
  117. package/extensions/shared/create-runtime-provider.ts +190 -0
  118. package/extensions/shared/daemon-manager.ts +248 -0
  119. package/extensions/shared/file-logger.ts +115 -0
  120. package/extensions/shared/managed-refresh.ts +211 -0
  121. package/extensions/shared/provider-driver.ts +295 -0
  122. package/extensions/shared/provider-errors.ts +305 -0
  123. package/extensions/shared/provider-registry.ts +388 -0
  124. package/extensions/shared/stream-builder.ts +275 -0
  125. package/extensions/shared/types.ts +20 -0
  126. package/extensions/shared/ui/README.md +10 -0
  127. package/extensions/shared/ui/badge.tsx +52 -0
  128. package/extensions/shared/ui/button.tsx +58 -0
  129. package/extensions/shared/ui/collapsible.tsx +21 -0
  130. package/extensions/shared/ui/dropdown-menu.tsx +266 -0
  131. package/extensions/shared/ui/input.tsx +20 -0
  132. package/extensions/shared/ui/popover.tsx +114 -0
  133. package/extensions/shared/ui/resizable.tsx +48 -0
  134. package/extensions/shared/ui/scroll-area.tsx +53 -0
  135. package/extensions/shared/ui/select.tsx +201 -0
  136. package/extensions/shared/ui/separator.tsx +25 -0
  137. package/extensions/shared/ui/tooltip.tsx +64 -0
  138. package/extensions/shared/ws-client.ts +74 -0
  139. package/package.json +59 -0
  140. package/prompts/coderabbit-rate-limit.md +95 -0
  141. package/prompts/create-coms-feature-manager.md +186 -0
  142. package/prompts/create-skill.md +128 -0
  143. package/prompts/domain-model.md +109 -0
  144. package/prompts/external-ai.md +665 -0
  145. package/prompts/implement-and-review.md +25 -0
  146. package/prompts/implement.md +26 -0
  147. package/prompts/issue-review.md +256 -0
  148. package/prompts/pr-review.md +753 -0
  149. package/prompts/query-db.md +154 -0
  150. package/prompts/refine-review.md +208 -0
  151. package/prompts/release.md +235 -0
  152. package/prompts/remember.md +33 -0
  153. package/prompts/review-handler-status.md +27 -0
  154. package/prompts/review-handler.md +677 -0
  155. package/prompts/review-local.md +159 -0
  156. package/prompts/scout-and-plan.md +21 -0
  157. package/rules/00-orchestrator-core.md +42 -0
  158. package/rules/05-issue-first-workflow.md +119 -0
  159. package/rules/10-agent-routing.md +58 -0
  160. package/rules/15-mcp-launchpad.md +46 -0
  161. package/rules/20-code-review-loop.md +234 -0
  162. package/rules/25-documentation-updates.md +15 -0
  163. package/rules/30-prompt-templates.md +19 -0
  164. package/rules/35-memory.md +240 -0
  165. package/rules/40-critical-rules.md +124 -0
  166. package/rules/45-file-preview.md +29 -0
  167. package/rules/50-agent-bug-reporting.md +90 -0
  168. package/rules/55-coms-protocol.md +63 -0
  169. package/rules/60-task-tracking.md +66 -0
  170. package/skills/conflict-resolver/SKILL.md +213 -0
  171. package/skills/skill-creator/SKILL.md +85 -0
  172. package/skills/systematic-debugging/SKILL.md +353 -0
  173. package/skills/test-driven-development/SKILL.md +335 -0
  174. package/skills/update-agents-md/SKILL.md +282 -0
  175. package/templates/coms-feature-manager-prompt.md +313 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Gemini CLI agent — flags + installed-bundle discovery (isVisible models).
3
+ */
4
+
5
+ import {
6
+ existsSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ statSync,
10
+ } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { cliProviderLog } from "../../shared/file-logger.js";
13
+ import type { CliProviderDef, DiscoveredCliModel } from "../types.js";
14
+ import {
15
+ cacheKeyForFile,
16
+ modelIdToDisplayName,
17
+ readModelCache,
18
+ resolveBinary,
19
+ writeModelCache,
20
+ } from "../shared/discover-cache.js";
21
+
22
+ /**
23
+ * Parse user-facing models from gemini-cli bundle source.
24
+ * Matches entries like: "gemini-2.5-pro": { ... isVisible: true ... }
25
+ */
26
+ export function parseGeminiCliVisibleModels(bundleText: string): DiscoveredCliModel[] {
27
+ const re =
28
+ /"((?:gemini|gemma|auto)[^"]+)"\s*:\s*\{[^}]{0,500}?isVisible:\s*true/g;
29
+ const models: DiscoveredCliModel[] = [];
30
+ const seen = new Set<string>();
31
+ let m: RegExpExecArray | null;
32
+ while ((m = re.exec(bundleText))) {
33
+ const id = m[1];
34
+ if (id.endsWith("-base") || seen.has(id)) continue;
35
+ seen.add(id);
36
+ models.push({ id, name: modelIdToDisplayName(id) });
37
+ }
38
+ return models;
39
+ }
40
+
41
+ function discoverGeminiModels(): DiscoveredCliModel[] {
42
+ const binary = resolveBinary("gemini");
43
+ if (!binary) return [];
44
+
45
+ try {
46
+ const key = cacheKeyForFile("gemini", binary);
47
+ const cached = readModelCache(key);
48
+ if (cached && cached.length > 0) return cached;
49
+
50
+ const bundleDir = dirname(binary);
51
+ const candidates = readdirSync(bundleDir)
52
+ .filter((f) => f.endsWith(".js") && (f.startsWith("chunk-") || f === "gemini.js"))
53
+ .map((f) => join(bundleDir, f))
54
+ .filter((p) => existsSync(p))
55
+ .map((p) => ({ path: p, size: statSync(p).size }))
56
+ .filter((f) => f.size > 0 && f.size <= 80 * 1024 * 1024)
57
+ .sort((a, b) => b.size - a.size);
58
+
59
+ const models: DiscoveredCliModel[] = [];
60
+ const seen = new Set<string>();
61
+ for (const { path } of candidates) {
62
+ const text = readFileSync(path, "utf-8");
63
+ for (const m of parseGeminiCliVisibleModels(text)) {
64
+ if (seen.has(m.id)) continue;
65
+ seen.add(m.id);
66
+ models.push(m);
67
+ }
68
+ if (models.length > 0) break;
69
+ }
70
+
71
+ if (models.length > 0) writeModelCache(key, models);
72
+ return models;
73
+ } catch (err) {
74
+ cliProviderLog("error", "gemini: CLI bundle parse failed", err);
75
+ return [];
76
+ }
77
+ }
78
+
79
+ export const geminiProvider: CliProviderDef = {
80
+ name: "gemini",
81
+ binary: "gemini",
82
+ buildBaseArgs: (model) => {
83
+ // --skip-trust: workspace; --yolo: auto-approve all tool actions
84
+ const args = [
85
+ "--skip-trust",
86
+ "--yolo",
87
+ "--output-format",
88
+ "stream-json",
89
+ ];
90
+ if (model && model !== "default") {
91
+ args.unshift("--model", model);
92
+ }
93
+ return args;
94
+ },
95
+ resumeFlag: "--resume",
96
+ continueFlags: ["--resume"],
97
+ outputFormat: "stream-json",
98
+ promptOnStdin: true,
99
+ discoverModels: discoverGeminiModels,
100
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Dynamic model discovery facade — dispatches to per-agent drivers.
3
+ * CLI only, no API keys / cloud APIs.
4
+ *
5
+ * IMPORTANT: These IDs are CLI `--model` values, NOT acpx model ids.
6
+ */
7
+
8
+ import { cliProviderLog } from "../shared/file-logger.js";
9
+ import { CLI_PROVIDERS, isCliAgentName, type CliAgentName } from "./providers.js";
10
+ import type { DiscoveredCliModel } from "./types.js";
11
+ import { modelIdToDisplayName, resolveBinary } from "./shared/discover-cache.js";
12
+
13
+ export type { DiscoveredCliModel } from "./types.js";
14
+ export { modelIdToDisplayName } from "./shared/discover-cache.js";
15
+ export { parseAgentListModels } from "./agents/cursor.js";
16
+ export {
17
+ parseClaudeBinaryCatalog,
18
+ scanClaudeBinaryCatalog,
19
+ } from "./agents/claude.js";
20
+ export { parseGeminiCliVisibleModels } from "./agents/gemini.js";
21
+
22
+ /** True if the CLI binary is on PATH. */
23
+ export function isCliBinaryAvailable(agent: CliAgentName): boolean {
24
+ return !!resolveBinary(CLI_PROVIDERS[agent].binary);
25
+ }
26
+
27
+ /**
28
+ * Discover models for a CLI agent (id + display name).
29
+ * CLI-only — never uses API keys or cloud list endpoints.
30
+ */
31
+ export async function discoverCliModelsDetailed(
32
+ agent: string,
33
+ ): Promise<DiscoveredCliModel[]> {
34
+ if (!isCliAgentName(agent)) return [];
35
+ if (!isCliBinaryAvailable(agent)) {
36
+ cliProviderLog(
37
+ "warn",
38
+ `${agent}: binary not found (${CLI_PROVIDERS[agent].binary})`,
39
+ );
40
+ return [];
41
+ }
42
+
43
+ let discovered: DiscoveredCliModel[] = [];
44
+ try {
45
+ discovered = CLI_PROVIDERS[agent].discoverModels();
46
+ } catch (err) {
47
+ cliProviderLog("error", `${agent}: discovery error`, err);
48
+ }
49
+
50
+ if (discovered.length > 0) {
51
+ cliProviderLog("info", `${agent}: discovered ${discovered.length} model(s)`);
52
+ } else {
53
+ cliProviderLog("warn", `${agent}: discovery returned no models`);
54
+ }
55
+ return discovered;
56
+ }
57
+
58
+ /** Discover model ids for a CLI agent. */
59
+ export async function discoverCliModelIds(agent: string): Promise<string[]> {
60
+ const models = await discoverCliModelsDetailed(agent);
61
+ return models.map((m) => m.id);
62
+ }
63
+
64
+ /** Discover models ready for registries (sidecar / external tools). */
65
+ export async function discoverCliModels(
66
+ agent: string,
67
+ ): Promise<Array<{ id: string; name: string; provider: string }>> {
68
+ if (!/^[a-z0-9_-]+$/i.test(agent)) {
69
+ throw new Error(`Invalid agent name: ${agent}`);
70
+ }
71
+ const models = await discoverCliModelsDetailed(agent);
72
+ return models.map((m) => ({
73
+ id: `${agent}:${m.id}`,
74
+ name: `${m.name} (${agent})`,
75
+ provider: `cli-${agent}`,
76
+ }));
77
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * CLI Provider Extension — Backward-Compatible Shim
3
+ *
4
+ * This file exists solely for backward compatibility with consumers that
5
+ * resolve `extensions/cli-provider/index.ts` from the pi-orchestrator-config
6
+ * package (e.g. pi-sidecar).
7
+ *
8
+ * Delegates provider registration to the unified driver extension at
9
+ * `extensions/providers/index.ts`. Re-exports all public APIs so existing
10
+ * import paths continue to work.
11
+ *
12
+ * DO NOT add new logic here — add it to the driver or shared modules.
13
+ *
14
+ * @module cli-provider (shim)
15
+ */
16
+
17
+ // Re-export discovery APIs (consumed by pi-sidecar via jiti)
18
+ export {
19
+ discoverCliModels,
20
+ discoverCliModelIds,
21
+ discoverCliModelsDetailed,
22
+ modelIdToDisplayName,
23
+ isCliBinaryAvailable,
24
+ } from "./discover.js";
25
+
26
+ // Re-export parsers (consumed by tests)
27
+ export {
28
+ parseAgentListModels,
29
+ } from "./agents/cursor.js";
30
+ export {
31
+ parseClaudeBinaryCatalog,
32
+ scanClaudeBinaryCatalog,
33
+ } from "./agents/claude.js";
34
+ export {
35
+ parseGeminiCliVisibleModels,
36
+ } from "./agents/gemini.js";
37
+
38
+ // Re-export session management (consumed by drivers and tests)
39
+ export {
40
+ resolveCliHistorySeed,
41
+ } from "./sessions.js";
42
+
43
+ // Re-export model mapping (consumed by tests and unified extension)
44
+ export {
45
+ mapCliDiscoveredModels,
46
+ } from "./runtime-models.js";
47
+
48
+ /**
49
+ * No-op extension entry point.
50
+ *
51
+ * Registration is now handled by the unified provider extension at
52
+ * `extensions/providers/index.ts`. This default export exists only so
53
+ * pi's extension loader does not throw when loading this directory.
54
+ */
55
+ export default async function (_pi: unknown) {
56
+ // Intentional no-op — registration moved to extensions/providers/index.ts
57
+ }
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Parse CLI JSON / stream-json stdout into text + session id.
3
+ * Also supports incremental NDJSON event parsing for live streaming.
4
+ */
5
+
6
+ /** Per-turn usage extracted from CLI result events. */
7
+ export interface CliUsage {
8
+ inputTokens?: number;
9
+ outputTokens?: number;
10
+ cachedReadTokens?: number;
11
+ cachedWriteTokens?: number;
12
+ totalTokens?: number;
13
+ costUsd?: number;
14
+ }
15
+
16
+ export interface CliParseResult {
17
+ text: string;
18
+ sessionId?: string;
19
+ thinking?: string;
20
+ usage?: CliUsage;
21
+ }
22
+
23
+ export type CliStreamEvent =
24
+ | { kind: "session"; sessionId: string }
25
+ | { kind: "thinking_delta"; text: string }
26
+ | { kind: "text_delta"; text: string }
27
+ | { kind: "done"; text?: string; sessionId?: string };
28
+
29
+ /** Parse Claude-style single JSON object. */
30
+ export function parseClaudeJson(stdout: string): CliParseResult {
31
+ const data = JSON.parse(stdout.trim());
32
+ const text =
33
+ typeof data.result === "string"
34
+ ? data.result
35
+ : typeof data.content === "string"
36
+ ? data.content
37
+ : Array.isArray(data.content)
38
+ ? data.content
39
+ .filter((b: any) => b?.type === "text" && typeof b.text === "string")
40
+ .map((b: any) => b.text)
41
+ .join("")
42
+ : "";
43
+ const sessionId =
44
+ typeof data.session_id === "string"
45
+ ? data.session_id
46
+ : typeof data.sessionId === "string"
47
+ ? data.sessionId
48
+ : undefined;
49
+ return { text: text || "", sessionId };
50
+ }
51
+
52
+ /** Parse Gemini-style JSON. */
53
+ export function parseGeminiJson(stdout: string): CliParseResult {
54
+ const data = JSON.parse(stdout.trim());
55
+ let text = "";
56
+ if (typeof data.response === "string") text = data.response;
57
+ else if (typeof data.text === "string") text = data.text;
58
+ else if (typeof data.result === "string") text = data.result;
59
+ const sessionId =
60
+ typeof data.session_id === "string"
61
+ ? data.session_id
62
+ : typeof data.sessionId === "string"
63
+ ? data.sessionId
64
+ : undefined;
65
+ return { text, sessionId };
66
+ }
67
+
68
+ function assistantTextFromEvent(ev: any): string {
69
+ if (ev?.type === "assistant" && Array.isArray(ev.message?.content)) {
70
+ return ev.message.content
71
+ .filter((b: any) => b?.type === "text" && typeof b.text === "string")
72
+ .map((b: any) => b.text)
73
+ .join("");
74
+ }
75
+ if (ev?.type === "assistant" && typeof ev.message?.content === "string") {
76
+ return ev.message.content;
77
+ }
78
+ if (ev?.type === "text" && typeof ev.text === "string") return ev.text;
79
+ if (ev?.type === "content_block_delta" && typeof ev.delta?.text === "string") {
80
+ return ev.delta.text;
81
+ }
82
+ return "";
83
+ }
84
+
85
+ /**
86
+ * Incremental parser state for stream-json NDJSON (cursor / claude / gemini).
87
+ * Handles both pure deltas and cumulative snapshots (cursor emits both).
88
+ */
89
+ export class StreamJsonAccumulator {
90
+ text = "";
91
+ thinking = "";
92
+ sessionId?: string;
93
+ usage?: CliUsage;
94
+
95
+ /** Feed one NDJSON object; returns events to emit to pi. */
96
+ push(ev: any): CliStreamEvent[] {
97
+ const out: CliStreamEvent[] = [];
98
+ if (!ev || typeof ev !== "object") return out;
99
+
100
+ if (typeof ev.session_id === "string") {
101
+ this.sessionId = ev.session_id;
102
+ out.push({ kind: "session", sessionId: ev.session_id });
103
+ } else if (typeof ev.sessionId === "string") {
104
+ this.sessionId = ev.sessionId;
105
+ out.push({ kind: "session", sessionId: ev.sessionId });
106
+ }
107
+
108
+ if (ev.type === "thinking" && ev.subtype === "delta" && typeof ev.text === "string") {
109
+ this.thinking += ev.text;
110
+ out.push({ kind: "thinking_delta", text: ev.text });
111
+ }
112
+
113
+ if (ev.type === "content_block_delta" && typeof ev.delta?.text === "string") {
114
+ const delta = ev.delta.text;
115
+ this.text += delta;
116
+ out.push({ kind: "text_delta", text: delta });
117
+ return out;
118
+ }
119
+
120
+ // Gemini CLI: {"type":"message","role":"assistant","content":"...","delta":true}
121
+ if (
122
+ ev.type === "message" &&
123
+ ev.role === "assistant" &&
124
+ typeof ev.content === "string" &&
125
+ ev.content.length > 0
126
+ ) {
127
+ if (ev.delta === true) {
128
+ this.text += ev.content;
129
+ out.push({ kind: "text_delta", text: ev.content });
130
+ } else if (ev.content.startsWith(this.text)) {
131
+ const delta = ev.content.slice(this.text.length);
132
+ this.text = ev.content;
133
+ if (delta) out.push({ kind: "text_delta", text: delta });
134
+ } else if (!this.text) {
135
+ this.text = ev.content;
136
+ out.push({ kind: "text_delta", text: ev.content });
137
+ }
138
+ return out;
139
+ }
140
+
141
+ const piece = assistantTextFromEvent(ev);
142
+ if (piece) {
143
+ if (piece.startsWith(this.text)) {
144
+ const delta = piece.slice(this.text.length);
145
+ this.text = piece;
146
+ if (delta) out.push({ kind: "text_delta", text: delta });
147
+ } else if (!this.text.endsWith(piece)) {
148
+ this.text += piece;
149
+ out.push({ kind: "text_delta", text: piece });
150
+ }
151
+ }
152
+
153
+ if (ev.type === "result") {
154
+ const finalText =
155
+ typeof ev.result === "string"
156
+ ? ev.result
157
+ : typeof ev.text === "string"
158
+ ? ev.text
159
+ : undefined;
160
+ if (finalText !== undefined) {
161
+ if (finalText.startsWith(this.text)) {
162
+ const delta = finalText.slice(this.text.length);
163
+ this.text = finalText;
164
+ if (delta) out.push({ kind: "text_delta", text: delta });
165
+ } else if (this.text !== finalText) {
166
+ // Prefer authoritative result if stream was wrong/empty
167
+ if (!this.text) {
168
+ this.text = finalText;
169
+ out.push({ kind: "text_delta", text: finalText });
170
+ } else {
171
+ this.text = finalText;
172
+ }
173
+ }
174
+ }
175
+ // Extract usage from result event (format varies by CLI agent)
176
+ this.usage = extractCliUsage(ev);
177
+
178
+ out.push({
179
+ kind: "done",
180
+ text: this.text,
181
+ sessionId: this.sessionId,
182
+ });
183
+ }
184
+
185
+ return out;
186
+ }
187
+
188
+ /** Feed a chunk of stdout (may contain partial lines). */
189
+ feedChunk(chunk: string, lineBuffer: { value: string }): CliStreamEvent[] {
190
+ lineBuffer.value += chunk;
191
+ const events: CliStreamEvent[] = [];
192
+ const lines = lineBuffer.value.split("\n");
193
+ lineBuffer.value = lines.pop() ?? "";
194
+ for (const line of lines) {
195
+ const trimmed = line.trim();
196
+ if (!trimmed) continue;
197
+ try {
198
+ events.push(...this.push(JSON.parse(trimmed)));
199
+ } catch {
200
+ /* incomplete / non-json line */
201
+ }
202
+ }
203
+ return events;
204
+ }
205
+
206
+ flush(lineBuffer: { value: string }): CliStreamEvent[] {
207
+ const trimmed = lineBuffer.value.trim();
208
+ lineBuffer.value = "";
209
+ if (!trimmed) return [];
210
+ try {
211
+ return this.push(JSON.parse(trimmed));
212
+ } catch {
213
+ return [];
214
+ }
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Parse Cursor agent stream-json (NDJSON). Accumulate assistant text deltas
220
+ * and last session id.
221
+ */
222
+ export function parseCursorStreamJson(stdout: string): CliParseResult {
223
+ const acc = new StreamJsonAccumulator();
224
+ for (const line of stdout.split("\n")) {
225
+ const trimmed = line.trim();
226
+ if (!trimmed) continue;
227
+ try {
228
+ acc.push(JSON.parse(trimmed));
229
+ } catch {
230
+ /* skip */
231
+ }
232
+ }
233
+ return {
234
+ text: acc.text,
235
+ sessionId: acc.sessionId,
236
+ thinking: acc.thinking || undefined,
237
+ usage: acc.usage,
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Extract usage from a CLI result event. Handles three formats:
243
+ * - Cursor: result.usage.{inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens}
244
+ * - Claude: result.usage.{input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens} + result.total_cost_usd
245
+ * - Gemini: result.stats.{input_tokens, output_tokens, total_tokens, cached}
246
+ */
247
+ export function extractCliUsage(ev: any): CliUsage | undefined {
248
+ if (!ev || typeof ev !== "object") return undefined;
249
+
250
+ // Cursor format: result.usage.{inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens}
251
+ if (ev.usage && typeof ev.usage === "object" && typeof ev.usage.inputTokens === "number") {
252
+ const u = ev.usage;
253
+ return {
254
+ inputTokens: u.inputTokens ?? undefined,
255
+ outputTokens: u.outputTokens ?? undefined,
256
+ cachedReadTokens: u.cacheReadTokens ?? undefined,
257
+ cachedWriteTokens: u.cacheWriteTokens ?? undefined,
258
+ totalTokens: (u.inputTokens ?? 0) + (u.outputTokens ?? 0),
259
+ };
260
+ }
261
+
262
+ // Claude format: result.usage.{input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens}
263
+ if (ev.usage && typeof ev.usage === "object" && typeof ev.usage.input_tokens === "number") {
264
+ const u = ev.usage;
265
+ const cost = typeof ev.total_cost_usd === "number" ? ev.total_cost_usd : undefined;
266
+ return {
267
+ inputTokens: u.input_tokens ?? undefined,
268
+ outputTokens: u.output_tokens ?? undefined,
269
+ cachedReadTokens: u.cache_read_input_tokens ?? undefined,
270
+ cachedWriteTokens: u.cache_creation_input_tokens ?? undefined,
271
+ totalTokens: (u.input_tokens ?? 0) + (u.output_tokens ?? 0),
272
+ costUsd: cost,
273
+ };
274
+ }
275
+
276
+ // Gemini format: result.stats.{input_tokens, output_tokens, total_tokens, cached}
277
+ if (ev.stats && typeof ev.stats === "object" && typeof ev.stats.input_tokens === "number") {
278
+ const s = ev.stats;
279
+ return {
280
+ inputTokens: s.input_tokens ?? undefined,
281
+ outputTokens: s.output_tokens ?? undefined,
282
+ cachedReadTokens: s.cached ?? undefined,
283
+ totalTokens: s.total_tokens ?? undefined,
284
+ };
285
+ }
286
+
287
+ return undefined;
288
+ }
289
+
290
+ export function parseCliOutput(agent: string, stdout: string): CliParseResult {
291
+ if (agent === "cursor") return parseCursorStreamJson(stdout);
292
+ // Prefer stream-json NDJSON when present; fall back to single JSON object
293
+ if (stdout.includes("\n") && stdout.trim().startsWith("{")) {
294
+ const first = stdout.trim().split("\n")[0];
295
+ try {
296
+ const ev = JSON.parse(first);
297
+ if (ev?.type) return parseCursorStreamJson(stdout);
298
+ } catch {
299
+ /* fall through */
300
+ }
301
+ }
302
+ if (agent === "gemini") return parseGeminiJson(stdout);
303
+ return parseClaudeJson(stdout);
304
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * CLI provider registry — assembles per-agent drivers (t3-style Drivers/*).
3
+ * Add a new CLI: create agents/<name>.ts, register here.
4
+ */
5
+
6
+ import type { CliAgentName, CliProviderDef } from "./types.js";
7
+ import { claudeProvider } from "./agents/claude.js";
8
+ import { cursorProvider } from "./agents/cursor.js";
9
+ import { geminiProvider } from "./agents/gemini.js";
10
+
11
+ /**
12
+ * Headless flags (cli-* is a backend LLM — no TTY for prompts):
13
+ *
14
+ * | Concern | cursor | claude | gemini |
15
+ * |---------|--------|--------|--------|
16
+ * | Workspace trust | `--trust` | skipped by `-p` | `--skip-trust` |
17
+ * | Tool/command approve | `--force` (`--yolo` alias) | `--dangerously-skip-permissions` | `--yolo` |
18
+ *
19
+ * Trust alone is NOT enough: without auto-approve, tool calls that need
20
+ * confirmation hang or fail (no interactive user).
21
+ */
22
+ export const CLI_PROVIDERS: Record<CliAgentName, CliProviderDef> = {
23
+ claude: claudeProvider,
24
+ gemini: geminiProvider,
25
+ cursor: cursorProvider,
26
+ };
27
+
28
+ export type { CliAgentName, CliProviderDef } from "./types.js";
29
+
30
+ export const VALID_CLI_AGENTS = new Set<string>(Object.keys(CLI_PROVIDERS));
31
+
32
+ export function isCliAgentName(name: string): name is CliAgentName {
33
+ return VALID_CLI_AGENTS.has(name);
34
+ }
35
+
36
+ /** Build full command argv for a turn. */
37
+ export function buildCliCommand(opts: {
38
+ agent: CliAgentName;
39
+ model: string;
40
+ cwd: string;
41
+ sessionId?: string | null;
42
+ continueSession?: boolean;
43
+ /** Override the default agent binary (from driver config). */
44
+ binary?: string;
45
+ }): { binary: string; args: string[]; promptOnStdin: boolean } {
46
+ const def = CLI_PROVIDERS[opts.agent];
47
+ const args = [...def.buildBaseArgs(opts.model, opts.cwd)];
48
+ if (opts.sessionId) {
49
+ args.push(def.resumeFlag, opts.sessionId);
50
+ } else if (opts.continueSession) {
51
+ args.push(...def.continueFlags);
52
+ }
53
+ return {
54
+ binary: opts.binary || def.binary,
55
+ args,
56
+ promptOnStdin: def.promptOnStdin,
57
+ };
58
+ }