@agentex/agent 0.0.24 → 0.0.26

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 (164) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/LICENSE +21 -0
  3. package/README.md +52 -0
  4. package/dist/derived.d.ts +5 -3
  5. package/dist/derived.d.ts.map +1 -1
  6. package/dist/derived.js +11 -7
  7. package/dist/derived.js.map +1 -1
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +3 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/providers/acp/index.d.ts +1 -1
  13. package/dist/providers/acp/index.d.ts.map +1 -1
  14. package/dist/providers/acp/index.js +5 -97
  15. package/dist/providers/acp/index.js.map +1 -1
  16. package/dist/providers/acp/session.d.ts +8 -1
  17. package/dist/providers/acp/session.d.ts.map +1 -1
  18. package/dist/providers/acp/session.js +94 -0
  19. package/dist/providers/acp/session.js.map +1 -1
  20. package/dist/providers/claude/attach.d.ts +8 -0
  21. package/dist/providers/claude/attach.d.ts.map +1 -0
  22. package/dist/providers/claude/attach.js +113 -0
  23. package/dist/providers/claude/attach.js.map +1 -0
  24. package/dist/providers/claude/goal-capability.d.ts +15 -0
  25. package/dist/providers/claude/goal-capability.d.ts.map +1 -0
  26. package/dist/providers/claude/goal-capability.js +20 -0
  27. package/dist/providers/claude/goal-capability.js.map +1 -0
  28. package/dist/providers/claude/index.d.ts.map +1 -1
  29. package/dist/providers/claude/index.js +8 -4
  30. package/dist/providers/claude/index.js.map +1 -1
  31. package/dist/providers/claude/session.d.ts +11 -9
  32. package/dist/providers/claude/session.d.ts.map +1 -1
  33. package/dist/providers/claude/session.js +29 -14
  34. package/dist/providers/claude/session.js.map +1 -1
  35. package/dist/providers/codex/attach.d.ts +9 -0
  36. package/dist/providers/codex/attach.d.ts.map +1 -0
  37. package/dist/providers/codex/attach.js +93 -0
  38. package/dist/providers/codex/attach.js.map +1 -0
  39. package/dist/providers/codex/goal-capability.d.ts +13 -0
  40. package/dist/providers/codex/goal-capability.d.ts.map +1 -0
  41. package/dist/providers/codex/goal-capability.js +18 -0
  42. package/dist/providers/codex/goal-capability.js.map +1 -0
  43. package/dist/providers/codex/index.d.ts +1 -0
  44. package/dist/providers/codex/index.d.ts.map +1 -1
  45. package/dist/providers/codex/index.js +9 -6
  46. package/dist/providers/codex/index.js.map +1 -1
  47. package/dist/providers/codex/session.d.ts +11 -7
  48. package/dist/providers/codex/session.d.ts.map +1 -1
  49. package/dist/providers/codex/session.js +24 -12
  50. package/dist/providers/codex/session.js.map +1 -1
  51. package/dist/providers/codex/transcript-normalize.d.ts +28 -0
  52. package/dist/providers/codex/transcript-normalize.d.ts.map +1 -0
  53. package/dist/providers/codex/transcript-normalize.js +191 -0
  54. package/dist/providers/codex/transcript-normalize.js.map +1 -0
  55. package/dist/providers/cursor/index.d.ts.map +1 -1
  56. package/dist/providers/cursor/index.js +2 -2
  57. package/dist/providers/cursor/index.js.map +1 -1
  58. package/dist/providers/openclaw/index.d.ts.map +1 -1
  59. package/dist/providers/openclaw/index.js +2 -2
  60. package/dist/providers/openclaw/index.js.map +1 -1
  61. package/dist/providers/opencode/index.d.ts.map +1 -1
  62. package/dist/providers/opencode/index.js +3 -5
  63. package/dist/providers/opencode/index.js.map +1 -1
  64. package/dist/providers/pi/index.d.ts.map +1 -1
  65. package/dist/providers/pi/index.js +3 -5
  66. package/dist/providers/pi/index.js.map +1 -1
  67. package/dist/providers/process/index.d.ts.map +1 -1
  68. package/dist/providers/process/index.js +2 -2
  69. package/dist/providers/process/index.js.map +1 -1
  70. package/dist/registry.d.ts +0 -1
  71. package/dist/registry.d.ts.map +1 -1
  72. package/dist/registry.js +0 -4
  73. package/dist/registry.js.map +1 -1
  74. package/dist/sessions/index.d.ts +3 -0
  75. package/dist/sessions/index.d.ts.map +1 -0
  76. package/dist/sessions/index.js +2 -0
  77. package/dist/sessions/index.js.map +1 -0
  78. package/dist/sessions/record.d.ts +43 -0
  79. package/dist/sessions/record.d.ts.map +1 -0
  80. package/dist/sessions/record.js +85 -0
  81. package/dist/sessions/record.js.map +1 -0
  82. package/dist/types.d.ts +119 -0
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/dist/utils/uuid.d.ts +7 -1
  86. package/dist/utils/uuid.d.ts.map +1 -1
  87. package/dist/utils/uuid.js +21 -1
  88. package/dist/utils/uuid.js.map +1 -1
  89. package/package.json +64 -7
  90. package/src/derived.ts +311 -0
  91. package/src/goals/controller.ts +442 -0
  92. package/src/goals/index.ts +21 -0
  93. package/src/goals/normalize.ts +173 -0
  94. package/src/goals/sentinel.ts +90 -0
  95. package/src/index.ts +270 -0
  96. package/src/providers/_shared/http-agent.ts +304 -0
  97. package/src/providers/acp/index.ts +103 -0
  98. package/src/providers/acp/parse.ts +131 -0
  99. package/src/providers/acp/session.ts +744 -0
  100. package/src/providers/claude/attach.ts +147 -0
  101. package/src/providers/claude/codec.ts +43 -0
  102. package/src/providers/claude/execute.ts +300 -0
  103. package/src/providers/claude/goal-capability.ts +21 -0
  104. package/src/providers/claude/index.ts +72 -0
  105. package/src/providers/claude/mcp.ts +82 -0
  106. package/src/providers/claude/parse.ts +824 -0
  107. package/src/providers/claude/session.ts +1192 -0
  108. package/src/providers/claude/transcript.ts +555 -0
  109. package/src/providers/codex/attach.ts +123 -0
  110. package/src/providers/codex/codec.ts +50 -0
  111. package/src/providers/codex/execute.ts +337 -0
  112. package/src/providers/codex/goal-capability.ts +19 -0
  113. package/src/providers/codex/index.ts +57 -0
  114. package/src/providers/codex/modes.ts +159 -0
  115. package/src/providers/codex/parse.ts +691 -0
  116. package/src/providers/codex/plan-mode.ts +49 -0
  117. package/src/providers/codex/session.ts +1287 -0
  118. package/src/providers/codex/transcript-normalize.ts +197 -0
  119. package/src/providers/codex/transcript.ts +487 -0
  120. package/src/providers/codex/usage-scanner.ts +178 -0
  121. package/src/providers/copilot/index.ts +19 -0
  122. package/src/providers/cursor/codec.ts +44 -0
  123. package/src/providers/cursor/execute.ts +271 -0
  124. package/src/providers/cursor/index.ts +25 -0
  125. package/src/providers/cursor/parse.ts +288 -0
  126. package/src/providers/gemini/index.ts +21 -0
  127. package/src/providers/openclaw/codec.ts +40 -0
  128. package/src/providers/openclaw/execute.ts +19 -0
  129. package/src/providers/openclaw/index.ts +29 -0
  130. package/src/providers/opencode/codec.ts +50 -0
  131. package/src/providers/opencode/event-parse.ts +141 -0
  132. package/src/providers/opencode/execute.ts +251 -0
  133. package/src/providers/opencode/http-session.ts +427 -0
  134. package/src/providers/opencode/index.ts +30 -0
  135. package/src/providers/opencode/parse.ts +203 -0
  136. package/src/providers/opencode/server.ts +0 -0
  137. package/src/providers/pi/codec.ts +44 -0
  138. package/src/providers/pi/execute.ts +297 -0
  139. package/src/providers/pi/index.ts +30 -0
  140. package/src/providers/pi/parse.ts +231 -0
  141. package/src/providers/pi/session.ts +381 -0
  142. package/src/providers/process/execute.ts +148 -0
  143. package/src/providers/process/index.ts +52 -0
  144. package/src/registry.ts +40 -0
  145. package/src/sessions/index.ts +8 -0
  146. package/src/sessions/record.ts +108 -0
  147. package/src/types.ts +1638 -0
  148. package/src/utils/ask-user-question.ts +57 -0
  149. package/src/utils/auth.ts +661 -0
  150. package/src/utils/binary.ts +179 -0
  151. package/src/utils/endpoint.ts +172 -0
  152. package/src/utils/env.ts +63 -0
  153. package/src/utils/execute-all.ts +68 -0
  154. package/src/utils/exit-plan-mode.ts +40 -0
  155. package/src/utils/instructions.ts +427 -0
  156. package/src/utils/process.ts +223 -0
  157. package/src/utils/runtime-config.ts +100 -0
  158. package/src/utils/runtime-homes.ts +49 -0
  159. package/src/utils/skill-commands.ts +493 -0
  160. package/src/utils/skills.ts +500 -0
  161. package/src/utils/template.ts +16 -0
  162. package/src/utils/tool-names.ts +51 -0
  163. package/src/utils/uuid.ts +21 -0
  164. package/src/utils/workspace.ts +156 -0
@@ -0,0 +1,178 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import * as os from "node:os";
4
+ import * as readline from "node:readline";
5
+ import { createReadStream } from "node:fs";
6
+ import type { TokenUsage } from "../../types.js";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Log path resolution
10
+ // ---------------------------------------------------------------------------
11
+
12
+ function getCodexHome(): string {
13
+ return process.env["CODEX_HOME"] || path.join(os.homedir(), ".codex");
14
+ }
15
+
16
+ /**
17
+ * Find session log files that may contain usage data for a given time range.
18
+ * Codex stores logs at: `CODEX_HOME/sessions/YYYY/MM/DD/*.jsonl`
19
+ */
20
+ async function findLogFiles(startedAfter: Date): Promise<string[]> {
21
+ const sessionsDir = path.join(getCodexHome(), "sessions");
22
+ const files: string[] = [];
23
+
24
+ // Walk date-partitioned directories starting from the startedAfter date
25
+ const startDate = new Date(startedAfter);
26
+ const now = new Date();
27
+
28
+ for (let d = new Date(startDate); d <= now; d.setDate(d.getDate() + 1)) {
29
+ const year = String(d.getFullYear());
30
+ const month = String(d.getMonth() + 1).padStart(2, "0");
31
+ const day = String(d.getDate()).padStart(2, "0");
32
+ const dayDir = path.join(sessionsDir, year, month, day);
33
+
34
+ try {
35
+ const entries = await fs.readdir(dayDir);
36
+ for (const entry of entries) {
37
+ if (entry.endsWith(".jsonl")) {
38
+ files.push(path.join(dayDir, entry));
39
+ }
40
+ }
41
+ } catch {
42
+ // Directory doesn't exist — skip
43
+ }
44
+ }
45
+
46
+ return files;
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // JSONL scanning
51
+ // ---------------------------------------------------------------------------
52
+
53
+ interface ScanOptions {
54
+ /** Only include events after this timestamp */
55
+ startedAfter: Date;
56
+ /** Optional thread ID to filter by */
57
+ threadId?: string;
58
+ }
59
+
60
+ /**
61
+ * Scan Codex session log files for token usage events and aggregate them.
62
+ *
63
+ * Codex logs `token_count` events in session JSONL files with fields like
64
+ * `InputTokens`, `OutputTokens`, `CachedInputTokens`, `CacheReadInputTokens`.
65
+ *
66
+ * Returns aggregated usage keyed by model name, or null if no usage found.
67
+ */
68
+ export async function scanCodexSessionUsage(
69
+ options: ScanOptions,
70
+ ): Promise<Record<string, TokenUsage> | undefined> {
71
+ const logFiles = await findLogFiles(options.startedAfter);
72
+ if (logFiles.length === 0) return undefined;
73
+
74
+ const usageByModel = new Map<string, {
75
+ inputTokens: number;
76
+ outputTokens: number;
77
+ cachedInputTokens: number;
78
+ cacheCreationInputTokens: number;
79
+ }>();
80
+
81
+ for (const file of logFiles) {
82
+ await scanFile(file, options, usageByModel);
83
+ }
84
+
85
+ if (usageByModel.size === 0) return undefined;
86
+
87
+ const result: Record<string, TokenUsage> = {};
88
+ for (const [model, usage] of usageByModel) {
89
+ const entry: TokenUsage = {
90
+ inputTokens: usage.inputTokens,
91
+ outputTokens: usage.outputTokens,
92
+ };
93
+ if (usage.cachedInputTokens > 0) entry.cachedInputTokens = usage.cachedInputTokens;
94
+ if (usage.cacheCreationInputTokens > 0) entry.cacheCreationInputTokens = usage.cacheCreationInputTokens;
95
+ result[model] = entry;
96
+ }
97
+ return result;
98
+ }
99
+
100
+ async function scanFile(
101
+ filePath: string,
102
+ options: ScanOptions,
103
+ usageByModel: Map<string, { inputTokens: number; outputTokens: number; cachedInputTokens: number; cacheCreationInputTokens: number }>,
104
+ ): Promise<void> {
105
+ let stream: ReturnType<typeof createReadStream>;
106
+ try {
107
+ stream = createReadStream(filePath, { encoding: "utf-8" });
108
+ } catch {
109
+ return;
110
+ }
111
+
112
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
113
+
114
+ try {
115
+ for await (const line of rl) {
116
+ const trimmed = line.trim();
117
+ if (!trimmed) continue;
118
+
119
+ let parsed: Record<string, unknown>;
120
+ try {
121
+ parsed = JSON.parse(trimmed);
122
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) continue;
123
+ } catch {
124
+ continue;
125
+ }
126
+
127
+ const type = typeof parsed["type"] === "string" ? parsed["type"] : "";
128
+ if (type !== "token_count") continue;
129
+
130
+ // Check timestamp
131
+ const ts = typeof parsed["timestamp"] === "string" ? parsed["timestamp"]
132
+ : typeof parsed["created_at"] === "string" ? parsed["created_at"]
133
+ : null;
134
+ if (ts) {
135
+ const eventTime = new Date(ts);
136
+ if (eventTime < options.startedAfter) continue;
137
+ }
138
+
139
+ // Check thread ID filter
140
+ if (options.threadId) {
141
+ const eventThreadId = typeof parsed["thread_id"] === "string" ? parsed["thread_id"]
142
+ : typeof parsed["threadId"] === "string" ? parsed["threadId"]
143
+ : null;
144
+ if (eventThreadId && eventThreadId !== options.threadId) continue;
145
+ }
146
+
147
+ // Extract usage
148
+ const model = typeof parsed["model"] === "string" ? parsed["model"] : "unknown";
149
+ const inputTokens = typeof parsed["InputTokens"] === "number" ? parsed["InputTokens"]
150
+ : typeof parsed["input_tokens"] === "number" ? parsed["input_tokens"]
151
+ : 0;
152
+ const outputTokens = typeof parsed["OutputTokens"] === "number" ? parsed["OutputTokens"]
153
+ : typeof parsed["output_tokens"] === "number" ? parsed["output_tokens"]
154
+ : 0;
155
+ const cachedInputTokens = typeof parsed["CacheReadInputTokens"] === "number" ? parsed["CacheReadInputTokens"]
156
+ : typeof parsed["cached_input_tokens"] === "number" ? parsed["cached_input_tokens"]
157
+ : 0;
158
+ const cacheCreationInputTokens = typeof parsed["CachedInputTokens"] === "number" ? parsed["CachedInputTokens"]
159
+ : typeof parsed["cache_creation_input_tokens"] === "number" ? parsed["cache_creation_input_tokens"]
160
+ : 0;
161
+
162
+ const existing = usageByModel.get(model) ?? {
163
+ inputTokens: 0,
164
+ outputTokens: 0,
165
+ cachedInputTokens: 0,
166
+ cacheCreationInputTokens: 0,
167
+ };
168
+ existing.inputTokens += inputTokens;
169
+ existing.outputTokens += outputTokens;
170
+ existing.cachedInputTokens += cachedInputTokens;
171
+ existing.cacheCreationInputTokens += cacheCreationInputTokens;
172
+ usageByModel.set(model, existing);
173
+ }
174
+ } finally {
175
+ rl.close();
176
+ stream.destroy();
177
+ }
178
+ }
@@ -0,0 +1,19 @@
1
+ import { acpProvider } from "../acp/index.js";
2
+
3
+ /**
4
+ * GitHub Copilot CLI via the Agent Client Protocol (`copilot --acp`). Copilot
5
+ * handles its own GitHub auth.
6
+ *
7
+ * Adding Copilot is the whole point of the ACP tier — it's a handful of lines
8
+ * because the ACP base does the work (sessions, streaming, tool-call
9
+ * correlation, permission bridging, mode discovery).
10
+ *
11
+ * NOTE: Copilot also exposes an "allow-all" approval mode through an ACP
12
+ * `setSessionConfigOption` (distinct from `setSessionMode`). Fully surfacing it
13
+ * would need a config-option transformer hook (future work); the standard
14
+ * mode + permission flow works today.
15
+ */
16
+ export const copilotProvider = acpProvider({
17
+ id: "copilot",
18
+ command: ["copilot", "--acp"],
19
+ });
@@ -0,0 +1,44 @@
1
+ import type { SessionCodec } from "../../types.js";
2
+
3
+ function readNonEmptyString(value: unknown): string | null {
4
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
5
+ }
6
+
7
+ function parseAsObject(raw: unknown): Record<string, unknown> | null {
8
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
9
+ return raw as Record<string, unknown>;
10
+ }
11
+
12
+ export const cursorSessionCodec: SessionCodec = {
13
+ deserialize(raw: unknown): Record<string, unknown> | null {
14
+ const obj = parseAsObject(raw);
15
+ if (!obj) return null;
16
+ const sessionId =
17
+ readNonEmptyString(obj["sessionId"]) ??
18
+ readNonEmptyString(obj["session_id"]);
19
+ if (!sessionId) return null;
20
+ const cwd = readNonEmptyString(obj["cwd"]);
21
+ return {
22
+ sessionId,
23
+ ...(cwd ? { cwd } : {}),
24
+ };
25
+ },
26
+
27
+ serialize(params: Record<string, unknown> | null): Record<string, unknown> | null {
28
+ if (!params) return null;
29
+ const sessionId =
30
+ readNonEmptyString(params["sessionId"]) ??
31
+ readNonEmptyString(params["session_id"]);
32
+ if (!sessionId) return null;
33
+ const cwd = readNonEmptyString(params["cwd"]);
34
+ return {
35
+ sessionId,
36
+ ...(cwd ? { cwd } : {}),
37
+ };
38
+ },
39
+
40
+ getDisplayId(params: Record<string, unknown> | null): string | null {
41
+ if (!params) return null;
42
+ return readNonEmptyString(params["sessionId"]) ?? readNonEmptyString(params["session_id"]);
43
+ },
44
+ };
@@ -0,0 +1,271 @@
1
+ import * as path from "node:path";
2
+ import type { ExecutionContext, ExecutionResult } from "../../types.js";
3
+ import { findBinary } from "../../utils/binary.js";
4
+ import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
5
+ import { runChildProcess, deriveErrorCode } from "../../utils/process.js";
6
+ import { detectAuth } from "../../utils/auth.js";
7
+ import { injectHomeSkills } from "../../utils/skills.js";
8
+ import { resolveInstructions } from "../../utils/instructions.js";
9
+ import { prepareWorkspace } from "../../utils/workspace.js";
10
+ import type { PreparedWorkspace } from "../../utils/workspace.js";
11
+ import { uuidv7 } from "../../utils/uuid.js";
12
+ import {
13
+ parseCursorJsonl,
14
+ parseCursorStreamLine,
15
+ normalizeCursorStreamLine,
16
+ isCursorUnknownSessionError,
17
+ isCursorAuthRequired,
18
+ } from "./parse.js";
19
+
20
+ export async function executeCursorProvider(ctx: ExecutionContext): Promise<ExecutionResult> {
21
+ const runId = ctx.runId ?? uuidv7();
22
+ let cwd = ctx.cwd ?? process.cwd();
23
+ const model = ctx.model ?? ctx.config?.model ?? "";
24
+ const config = ctx.config ?? {};
25
+ const startedAt = new Date().toISOString();
26
+
27
+ // 1. Resolve binary
28
+ ctx.onLifecycle?.({ phase: "preparing", step: "binary" });
29
+ let resolvedBinary;
30
+ try {
31
+ resolvedBinary = await findBinary("agent", config.command);
32
+ } catch (err) {
33
+ const errorMessage = err instanceof Error ? err.message : "Binary not found";
34
+ ctx.onLifecycle?.({ phase: "error", message: errorMessage });
35
+ return {
36
+ runId,
37
+ exitCode: null,
38
+ signal: null,
39
+ status: "failed" as const,
40
+ startedAt,
41
+ completedAt: new Date().toISOString(),
42
+ durationMs: Date.now() - new Date(startedAt).getTime(),
43
+ errorMessage,
44
+ errorCode: "binary_not_found",
45
+ costUsd: null,
46
+ model: null,
47
+ summary: null,
48
+ sessionParams: null,
49
+ sessionDisplayId: null,
50
+ clearSession: false,
51
+ billingType: null,
52
+ };
53
+ }
54
+
55
+ // 2. Workspace isolation
56
+ let workspace: PreparedWorkspace | undefined;
57
+ if (config.workspace) {
58
+ ctx.onLifecycle?.({ phase: "preparing", step: "workspace" });
59
+ workspace = await prepareWorkspace(cwd, config.workspace);
60
+ cwd = workspace.cwd;
61
+ }
62
+
63
+ // 3. Build env & detect auth/billing
64
+ ctx.onLifecycle?.({ phase: "preparing", step: "auth" });
65
+ const env = buildEnv(ctx.env);
66
+ ensurePathInEnv(env);
67
+ const billingType = detectAuth("cursor", env).billingType;
68
+
69
+ // 3. Resolve instructions
70
+ ctx.onLifecycle?.({ phase: "preparing", step: "instructions" });
71
+ const instructions = await resolveInstructions(config.instructionsFile);
72
+ const fullPrompt = instructions ? `${instructions}\n\n${ctx.prompt}` : ctx.prompt;
73
+
74
+ // 4. Inject skills into ~/.cursor/skills/
75
+ ctx.onLifecycle?.({ phase: "preparing", step: "skills" });
76
+ if (config.skillDirs && config.skillDirs.length > 0) {
77
+ try {
78
+ await injectHomeSkills(config.skillDirs, "cursor");
79
+ } catch {
80
+ // Non-fatal
81
+ }
82
+ }
83
+
84
+ // 4. Determine session resume
85
+ const sessionParams = ctx.sessionParams ?? null;
86
+ const sessionId = (() => {
87
+ if (!sessionParams) return null;
88
+ const id =
89
+ (sessionParams["sessionId"] as string | undefined) ??
90
+ (sessionParams["session_id"] as string | undefined);
91
+ if (!id || typeof id !== "string") return null;
92
+ const sessionCwd = sessionParams["cwd"] as string | undefined;
93
+ if (sessionCwd && path.resolve(sessionCwd) !== path.resolve(cwd)) return null;
94
+ return id;
95
+ })();
96
+
97
+ // 5. Build args
98
+ const buildArgs = (resumeSessionId: string | null): string[] => {
99
+ const args = [...resolvedBinary.prefixArgs, "-p", "--output-format", "stream-json", "--workspace", cwd];
100
+ if (resumeSessionId) args.push("--resume", resumeSessionId);
101
+ if (model) args.push("--model", model);
102
+ if (config.mode) args.push("--mode", config.mode);
103
+ if (config.skipPermissions) args.push("--yolo");
104
+ if (config.extraArgs) args.push(...config.extraArgs);
105
+ return args;
106
+ };
107
+
108
+ // 6. Run attempt
109
+ const runAttempt = async (resumeSessionId: string | null) => {
110
+ const args = buildArgs(resumeSessionId);
111
+ let stdoutLineBuffer = "";
112
+
113
+ ctx.onLifecycle?.({ phase: "spawning" });
114
+ const proc = await runChildProcess({
115
+ runId,
116
+ command: resolvedBinary.bin,
117
+ args,
118
+ cwd,
119
+ env,
120
+ stdin: fullPrompt,
121
+ timeoutSec: config.timeoutSec,
122
+ graceSec: config.graceSec,
123
+ onStart: (pid) => {
124
+ ctx.onLifecycle?.({ phase: "running", pid });
125
+ ctx.onStart?.(pid);
126
+ },
127
+ signal: ctx.signal,
128
+ onOutput: async (stream, chunk) => {
129
+ if (stream !== "stdout") {
130
+ if (ctx.onOutput) {
131
+ try { await ctx.onOutput(stream, chunk); } catch { /* swallow */ }
132
+ }
133
+ return;
134
+ }
135
+
136
+ // Buffer stdout and normalize cursor stream lines
137
+ stdoutLineBuffer += chunk;
138
+ const lines = stdoutLineBuffer.split("\n");
139
+ stdoutLineBuffer = lines.pop() ?? "";
140
+
141
+ for (const rawLine of lines) {
142
+ const normalized = normalizeCursorStreamLine(rawLine);
143
+ if (!normalized.line) continue;
144
+
145
+ if (ctx.onOutput) {
146
+ try { await ctx.onOutput(normalized.stream ?? "stdout", `${normalized.line}\n`); } catch { /* swallow */ }
147
+ }
148
+
149
+ if (ctx.onEvent) {
150
+ const event = parseCursorStreamLine(rawLine);
151
+ if (event) {
152
+ try { await ctx.onEvent(event); } catch { /* swallow */ }
153
+ }
154
+ }
155
+ }
156
+ },
157
+ });
158
+
159
+ // Flush remaining buffer
160
+ if (stdoutLineBuffer.trim()) {
161
+ const normalized = normalizeCursorStreamLine(stdoutLineBuffer);
162
+ if (normalized.line && ctx.onOutput) {
163
+ try { await ctx.onOutput(normalized.stream ?? "stdout", `${normalized.line}\n`); } catch { /* swallow */ }
164
+ }
165
+ if (ctx.onEvent) {
166
+ const event = parseCursorStreamLine(stdoutLineBuffer);
167
+ if (event) {
168
+ try { await ctx.onEvent(event); } catch { /* swallow */ }
169
+ }
170
+ }
171
+ }
172
+
173
+ return proc;
174
+ };
175
+
176
+ // 7. Initial attempt
177
+ let proc = await runAttempt(sessionId);
178
+ let clearSession = false;
179
+
180
+ // 8. Check for unknown session — retry once
181
+ if (
182
+ sessionId &&
183
+ !proc.timedOut &&
184
+ (proc.exitCode ?? 0) !== 0 &&
185
+ isCursorUnknownSessionError(proc.stdout, proc.stderr)
186
+ ) {
187
+ proc = await runAttempt(null);
188
+ clearSession = true;
189
+ }
190
+
191
+ // 9. Parse result
192
+ const parsed = parseCursorJsonl(proc.stdout);
193
+ const processErrorCode = deriveErrorCode(proc);
194
+
195
+ let errorCode = processErrorCode;
196
+ if (!errorCode && isCursorAuthRequired(proc.stdout, proc.stderr)) {
197
+ errorCode = "auth_required";
198
+ if (ctx.onEvent) {
199
+ try {
200
+ await ctx.onEvent({
201
+ type: "auth_required",
202
+ httpStatus: null,
203
+ reason: "missing",
204
+ loginCommand: "cursor-agent login",
205
+ message: parsed.errorMessage,
206
+ timestamp: new Date().toISOString(),
207
+ providerType: "cursor",
208
+ sessionId: parsed.sessionId,
209
+ messageId: null,
210
+ eventId: null,
211
+ turnId: null,
212
+ parentToolCallId: null,
213
+ raw: {},
214
+ });
215
+ } catch { /* swallow */ }
216
+ }
217
+ }
218
+
219
+ const errorMessage = (() => {
220
+ if (proc.timedOut) return `Timed out after ${config.timeoutSec ?? 0}s`;
221
+ if (errorCode === "auth_required") return "Cursor requires authentication.";
222
+ if (parsed.errorMessage) return parsed.errorMessage;
223
+ if ((proc.exitCode ?? 0) !== 0 && !parsed.summary) {
224
+ const stderrLine = proc.stderr.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
225
+ return stderrLine ?? `Cursor exited with code ${proc.exitCode ?? -1}`;
226
+ }
227
+ return null;
228
+ })();
229
+
230
+ const resolvedSessionId = parsed.sessionId;
231
+ const resultSessionParams = resolvedSessionId ? { sessionId: resolvedSessionId, cwd } : null;
232
+
233
+ const completedAt = new Date().toISOString();
234
+ const resolvedModel = parsed.model ?? model ?? null;
235
+ const status = proc.aborted ? "aborted" as const
236
+ : proc.timedOut ? "timeout" as const
237
+ : (errorCode || errorMessage) ? "failed" as const
238
+ : "completed" as const;
239
+
240
+ if (status === "completed") {
241
+ ctx.onLifecycle?.({ phase: "completed" });
242
+ } else if (status === "aborted") {
243
+ ctx.onLifecycle?.({ phase: "cancelled" });
244
+ } else {
245
+ ctx.onLifecycle?.({ phase: "error", message: errorMessage ?? "Unknown error" });
246
+ }
247
+
248
+ return {
249
+ runId,
250
+ exitCode: proc.exitCode,
251
+ signal: proc.signal ?? null,
252
+ status,
253
+ startedAt,
254
+ completedAt,
255
+ durationMs: new Date(completedAt).getTime() - new Date(startedAt).getTime(),
256
+ errorMessage,
257
+ errorCode,
258
+ usage: parsed.usage && resolvedModel
259
+ ? { [resolvedModel]: { inputTokens: parsed.usage.inputTokens, outputTokens: parsed.usage.outputTokens, cachedInputTokens: parsed.usage.cachedInputTokens } }
260
+ : undefined,
261
+ costUsd: parsed.costUsd,
262
+ model: resolvedModel,
263
+ summary: parsed.summary,
264
+ sessionParams: resultSessionParams,
265
+ sessionDisplayId: resolvedSessionId,
266
+ clearSession,
267
+ billingType,
268
+ raw: null,
269
+ workspace,
270
+ };
271
+ }
@@ -0,0 +1,25 @@
1
+ import type { ProviderModule } from "../../types.js";
2
+ import { cursorSessionCodec } from "./codec.js";
3
+ import { resolveAuthForProvider } from "../../utils/auth.js";
4
+
5
+ export const cursorProvider: ProviderModule = {
6
+ type: "cursor",
7
+ capabilities: {
8
+ sessions: false,
9
+ modelDiscovery: false,
10
+ quotaProbing: false,
11
+ mcp: false,
12
+ skills: true,
13
+ instructions: true,
14
+ workspace: true,
15
+ planMode: false,
16
+ concurrentSend: false,
17
+ cancelQueuedMessage: false,
18
+ stopTask: false,
19
+ modes: false,
20
+ },
21
+ // execute.ts loads lazily on first use.
22
+ execute: async (ctx) => (await import("./execute.js")).executeCursorProvider(ctx),
23
+ resolveAuth: (ctx) => resolveAuthForProvider("cursor", ctx),
24
+ sessionCodec: cursorSessionCodec,
25
+ };