orquesta-agent 0.2.235 → 0.2.239

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 (49) hide show
  1. package/README.md +45 -0
  2. package/dist/cli-auth-detect.d.ts +17 -0
  3. package/dist/cli-auth-detect.d.ts.map +1 -0
  4. package/dist/cli-auth-detect.js +31 -0
  5. package/dist/cli-auth-detect.js.map +1 -0
  6. package/dist/executor.d.ts +12 -4
  7. package/dist/executor.d.ts.map +1 -1
  8. package/dist/executor.js +232 -70
  9. package/dist/executor.js.map +1 -1
  10. package/dist/fs-browse.d.ts +35 -0
  11. package/dist/fs-browse.d.ts.map +1 -0
  12. package/dist/fs-browse.js +106 -0
  13. package/dist/fs-browse.js.map +1 -0
  14. package/dist/hook.d.ts +26 -1
  15. package/dist/hook.d.ts.map +1 -1
  16. package/dist/hook.js +170 -21
  17. package/dist/hook.js.map +1 -1
  18. package/dist/index.js +37 -15
  19. package/dist/index.js.map +1 -1
  20. package/dist/init.d.ts +42 -0
  21. package/dist/init.d.ts.map +1 -1
  22. package/dist/init.js +100 -28
  23. package/dist/init.js.map +1 -1
  24. package/dist/sandbox.d.ts.map +1 -1
  25. package/dist/sandbox.js +4 -0
  26. package/dist/sandbox.js.map +1 -1
  27. package/dist/supabase.d.ts +35 -9
  28. package/dist/supabase.d.ts.map +1 -1
  29. package/dist/supabase.js +40 -4
  30. package/dist/supabase.js.map +1 -1
  31. package/dist/types/hook-types.d.ts +49 -1
  32. package/dist/types/hook-types.d.ts.map +1 -1
  33. package/dist/ui/onboarding.js +1 -1
  34. package/dist/ui/onboarding.js.map +1 -1
  35. package/dist/ui/public/app.js +11 -1
  36. package/dist/ui/public/index.html +1 -0
  37. package/dist/ui/public/style.css +1 -0
  38. package/dist/ui/public/welcome.html +2 -0
  39. package/dist/ui/server.d.ts +1 -1
  40. package/dist/ui/server.d.ts.map +1 -1
  41. package/dist/ui/server.js +114 -45
  42. package/dist/ui/server.js.map +1 -1
  43. package/dist/ui/types.d.ts +1 -1
  44. package/dist/ui/types.d.ts.map +1 -1
  45. package/dist/ws-client.d.ts +15 -3
  46. package/dist/ws-client.d.ts.map +1 -1
  47. package/dist/ws-client.js +42 -4
  48. package/dist/ws-client.js.map +1 -1
  49. package/package.json +1 -1
package/README.md CHANGED
@@ -86,6 +86,51 @@ This checks:
86
86
  - Claude authentication (API key or web subscription)
87
87
  - Git installation
88
88
 
89
+ ## IDE Hooks — mirror Claude Code and Cursor sessions
90
+
91
+ Everything above is *dispatch*: Orquesta sends work down to the agent. Hooks are
92
+ the opposite direction — you keep working in your own IDE and Orquesta mirrors
93
+ what happened. No daemon runs, and nothing is ever executed back into your editor.
94
+
95
+ ```bash
96
+ # Wire both tools (default)
97
+ orquesta-agent init --token oat_xxxxx
98
+
99
+ # Or pick one
100
+ orquesta-agent init --token oat_xxxxx --client cursor
101
+ orquesta-agent init --token oat_xxxxx --client claude
102
+ ```
103
+
104
+ `init` writes `.orquesta.json` (project binding — it is added to `.gitignore`
105
+ because it holds the raw token) plus `.claude/settings.json` and/or
106
+ `.cursor/hooks.json`. Hooks you already had are preserved, and re-running `init`
107
+ upgrades Orquesta's entries rather than stacking duplicates.
108
+
109
+ | Moment | Claude Code | Cursor | Recorded as |
110
+ |---|---|---|---|
111
+ | Prompt sent | `UserPromptSubmit` | `beforeSubmitPrompt` | The prompt |
112
+ | Tool ran | `PostToolUse` | `postToolUse`, `afterShellExecution`, `afterFileEdit` | Tool call + result |
113
+ | Answer written | — | `afterAgentResponse` | The assistant's reply |
114
+ | Session done | `Stop` | `stop` | Marks the prompt complete |
115
+
116
+ Cursor surfaces shell runs and file edits as their own events on top of
117
+ `postToolUse`, so all three are wired and the handler de-dupes by content — an
118
+ action reported twice is still logged once.
119
+
120
+ Two behaviours differ per tool, both deliberate:
121
+
122
+ - **stdout.** Claude Code reads `UserPromptSubmit` stdout as extra model context,
123
+ so the hook injects a short Orquesta block there. Cursor parses hook stdout as a
124
+ JSON permission decision, so on that path the hook writes **nothing**.
125
+ - **Failure is always silent.** Every hook exits 0. An unreachable Orquesta, a
126
+ revoked token, a malformed payload or a directory with no `.orquesta.json` all
127
+ leave your session untouched.
128
+
129
+ The tool is detected from the payload, so `--client` is only needed to override
130
+ the guess. Mirrored prompts arrive with `source: terminal` and a `cli_type` of
131
+ `claude-code` or `cursor-cli`, and are excluded from agent dispatch so a hooked
132
+ session is never re-executed.
133
+
89
134
  ## Architecture
90
135
 
91
136
  ```
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Reading a coding CLI's own words to decide whether it is logged out.
3
+ *
4
+ * Its own module, with no imports, for one reason: executor.ts pulls in node-pty
5
+ * and the whole execution stack, so nothing there can be unit-tested. The
6
+ * classifier is the part that can actually be wrong, so it lives where a test
7
+ * can reach it.
8
+ *
9
+ * The contract is deliberately one-sided — it answers "did this CLI SAY it is
10
+ * logged out", not "is it logged in". Everything else (an older build with no
11
+ * status subcommand, a timeout, an unfamiliar phrasing) is treated as unknown by
12
+ * the caller and reported as authenticated: a false "not logged in" badge on a
13
+ * working machine sends the user chasing a problem that isn't there, while the
14
+ * badge we had before this existed was simply always green.
15
+ */
16
+ export declare function isLoggedOutMessage(output: string | null | undefined): boolean;
17
+ //# sourceMappingURL=cli-auth-detect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-auth-detect.d.ts","sourceRoot":"","sources":["../src/cli-auth-detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAaH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAG7E"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Reading a coding CLI's own words to decide whether it is logged out.
3
+ *
4
+ * Its own module, with no imports, for one reason: executor.ts pulls in node-pty
5
+ * and the whole execution stack, so nothing there can be unit-tested. The
6
+ * classifier is the part that can actually be wrong, so it lives where a test
7
+ * can reach it.
8
+ *
9
+ * The contract is deliberately one-sided — it answers "did this CLI SAY it is
10
+ * logged out", not "is it logged in". Everything else (an older build with no
11
+ * status subcommand, a timeout, an unfamiliar phrasing) is treated as unknown by
12
+ * the caller and reported as authenticated: a false "not logged in" badge on a
13
+ * working machine sends the user chasing a problem that isn't there, while the
14
+ * badge we had before this existed was simply always green.
15
+ */
16
+ const LOGGED_OUT_PATTERNS = [
17
+ // "Not logged in", "not authenticated", "You are not logged in."
18
+ /not\s+(logged\s*in|authenticated|signed\s*in)/i,
19
+ // "Logged out", "Signed out"
20
+ /(logged|signed)\s*out/i,
21
+ // "No active session", "no credentials found"
22
+ /no\s+(active\s+)?(session|credentials)/i,
23
+ // The instruction every one of them prints when unauthenticated.
24
+ /run\s+`?(cursor-agent|claude|kimi)\s+login/i,
25
+ ];
26
+ export function isLoggedOutMessage(output) {
27
+ if (!output)
28
+ return false;
29
+ return LOGGED_OUT_PATTERNS.some(pattern => pattern.test(output));
30
+ }
31
+ //# sourceMappingURL=cli-auth-detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-auth-detect.js","sourceRoot":"","sources":["../src/cli-auth-detect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,mBAAmB,GAAG;IAC1B,iEAAiE;IACjE,gDAAgD;IAChD,6BAA6B;IAC7B,wBAAwB;IACxB,8CAA8C;IAC9C,yCAAyC;IACzC,iEAAiE;IACjE,6CAA6C;CAC9C,CAAA;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAiC;IAClE,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACzB,OAAO,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;AAClE,CAAC"}
@@ -66,7 +66,7 @@ export declare function getEffectiveRuntimeConfig(): {
66
66
  sandboxMode: SandboxMode;
67
67
  sandboxStatus: 'active' | 'unavailable' | 'off';
68
68
  };
69
- export type CliPreference = 'auto' | 'orquesta' | 'claude' | 'kimi';
69
+ export type CliPreference = 'auto' | 'orquesta' | 'claude' | 'kimi' | 'cursor';
70
70
  export declare function setCliPreference(preference: CliPreference): void;
71
71
  export declare function setCliEndpoint(endpoint: string): void;
72
72
  export declare function getCliEndpoint(): string;
@@ -77,8 +77,16 @@ export declare function resolveClaudeBinary(): string | null;
77
77
  export declare function isClaudeCliAvailable(): boolean;
78
78
  export declare function resolveKimiBinary(): string | null;
79
79
  export declare function isKimiCliAvailable(): boolean;
80
+ export declare function resolveCursorBinary(): string | null;
81
+ export declare function isCursorCliAvailable(): boolean;
82
+ export declare function checkCursorAuth(): {
83
+ available: boolean;
84
+ authenticated: boolean;
85
+ method: string | null;
86
+ };
87
+ export type CliName = 'orquesta' | 'claude' | 'kimi' | 'cursor';
80
88
  export declare function selectCli(): {
81
- cli: 'orquesta' | 'claude' | 'kimi' | null;
89
+ cli: CliName | null;
82
90
  reason: string;
83
91
  };
84
92
  export declare function checkClaudeAuth(): {
@@ -237,7 +245,7 @@ export interface SessionInfo {
237
245
  subAgentName: string | null;
238
246
  workingDirectory: string;
239
247
  uptime: number;
240
- cliType?: 'claude' | 'orquesta' | 'kimi';
248
+ cliType?: 'claude' | 'orquesta' | 'kimi' | 'cursor';
241
249
  }
242
250
  /**
243
251
  * List every active interactive session — the dashboard renders these so each
@@ -254,7 +262,7 @@ export declare function getSessionStatus(): {
254
262
  sessionId: string | null;
255
263
  workingDirectory: string | null;
256
264
  uptime: number | null;
257
- cliType?: 'claude' | 'orquesta' | 'kimi';
265
+ cliType?: 'claude' | 'orquesta' | 'kimi' | 'cursor';
258
266
  sessions: SessionInfo[];
259
267
  };
260
268
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"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"}
package/dist/executor.js CHANGED
@@ -122,6 +122,7 @@ import * as logger from './logger.js';
122
122
  // interactive-session spawn — node-pty can't launch the `.cmd` shim).
123
123
  const nodeRequire = createRequire(import.meta.url);
124
124
  import { isSandboxAvailable, buildBwrapArgs, shQuote, ensureStrictProjectDirs, encodeClaudeProjectDir } from './sandbox.js';
125
+ import { isLoggedOutMessage } from './cli-auth-detect.js';
125
126
  import { parseCoordSpec, runCoordination } from './coordination.js';
126
127
  import { parseSudosudoInstallSpec, runSudosudoInstall } from './sudosudo.js';
127
128
  import { sendOutput, sendComplete, sendError, sendSupervisionRequest, sendExecutionResumed, updatePromptStatus, persistOutputLogs, clearOutputBuffer, sendRequirement, persistRequirement, sendQAInstructions, persistQAInstructions, sendPlanItemsGenerated, sendSessionOutput, sendSessionStarted, sendSessionEnded, sendSessionError, sendSessionLog, reportAgentError } from './supabase.js';
@@ -874,6 +875,101 @@ export function resolveKimiBinary() {
874
875
  export function isKimiCliAvailable() {
875
876
  return resolveKimiBinary() !== null;
876
877
  }
878
+ // Resolve Cursor's headless CLI (`cursor-agent`). Its installer
879
+ // (`curl https://cursor.com/install -fsS | bash`) drops the binary in
880
+ // ~/.local/bin and only PRINTS a reminder to add that to PATH — so on a machine
881
+ // where the user never edited their shell profile, a bare `cursor-agent` lookup
882
+ // fails while the CLI is in fact installed. Same trap as kimi.
883
+ // Order: CURSOR_AGENT_PATH → on PATH → ~/.local/bin/cursor-agent.
884
+ // On Windows the PowerShell installer does put it on PATH, so the PATH probe is
885
+ // the working path there.
886
+ let cursorBinaryCache = null;
887
+ export function resolveCursorBinary() {
888
+ const now = Date.now();
889
+ if (cursorBinaryCache && now - cursorBinaryCache.at < CLI_DETECTION_TTL_MS)
890
+ return cursorBinaryCache.path;
891
+ let resolved = null;
892
+ const envPath = process.env.CURSOR_AGENT_PATH;
893
+ if (envPath) {
894
+ try {
895
+ fs.accessSync(envPath, fs.constants.X_OK);
896
+ resolved = envPath;
897
+ }
898
+ catch { /* bad override */ }
899
+ }
900
+ if (!resolved) {
901
+ try {
902
+ execSync('cursor-agent --version', { stdio: 'pipe', timeout: 15000 });
903
+ resolved = 'cursor-agent';
904
+ }
905
+ catch { /* not on PATH */ }
906
+ }
907
+ if (!resolved) {
908
+ const candidate = path.join(os.homedir(), '.local', 'bin', 'cursor-agent');
909
+ try {
910
+ fs.accessSync(candidate, fs.constants.X_OK);
911
+ resolved = candidate;
912
+ }
913
+ catch { /* not installed there */ }
914
+ }
915
+ cursorBinaryCache = { path: resolved, at: now };
916
+ return resolved;
917
+ }
918
+ // Cursor CLI (the `cursor-agent` binary). Authenticated separately with
919
+ // `cursor-agent login` against the user's own Cursor subscription — Orquesta
920
+ // never sees that credential.
921
+ export function isCursorCliAvailable() {
922
+ return resolveCursorBinary() !== null;
923
+ }
924
+ // Is cursor-agent actually LOGGED IN, not just installed?
925
+ //
926
+ // Installed-but-logged-out is a real state and used to be invisible: the agent
927
+ // reported `authenticated: available`, so the dashboard showed a green tick, the
928
+ // user pinned Cursor, and every prompt failed at execution with cursor's own
929
+ // auth error. `cursor-agent status` is the documented way to ask.
930
+ //
931
+ // Deliberately conservative: it reports NOT authenticated only when cursor says
932
+ // so in words. Any other outcome (an older build with no `status` subcommand, a
933
+ // timeout, an unparseable answer) reports authenticated with a null method —
934
+ // crying wolf on a working machine is worse than the tick we had before, and
935
+ // nothing about EXECUTION depends on this flag; selectCli() still keys purely on
936
+ // the binary existing.
937
+ let cursorAuthCache = null;
938
+ export function checkCursorAuth() {
939
+ const now = Date.now();
940
+ if (cursorAuthCache && now - cursorAuthCache.at < CLI_DETECTION_TTL_MS)
941
+ return cursorAuthCache.result;
942
+ const cache = (result) => {
943
+ cursorAuthCache = { result, at: now };
944
+ return result;
945
+ };
946
+ const binary = resolveCursorBinary();
947
+ if (!binary)
948
+ return cache({ available: false, authenticated: false, method: null });
949
+ // An API key in the environment authenticates cursor-agent on its own, and is
950
+ // what CI machines use — no point spawning a process to confirm it.
951
+ if (process.env.CURSOR_API_KEY)
952
+ return cache({ available: true, authenticated: true, method: 'api_key' });
953
+ try {
954
+ const out = execSync(`"${binary}" status`, {
955
+ stdio: ['ignore', 'pipe', 'pipe'],
956
+ timeout: 15000,
957
+ encoding: 'utf-8',
958
+ });
959
+ if (isLoggedOutMessage(out))
960
+ return cache({ available: true, authenticated: false, method: null });
961
+ return cache({ available: true, authenticated: true, method: 'subscription' });
962
+ }
963
+ catch (error) {
964
+ // Non-zero exit covers both "logged out" and "this build has no `status`",
965
+ // so the OUTPUT decides, not the exit code.
966
+ const err = error;
967
+ const combined = `${err.stdout ?? ''}${err.stderr ?? ''}`;
968
+ if (isLoggedOutMessage(combined))
969
+ return cache({ available: true, authenticated: false, method: null });
970
+ return cache({ available: true, authenticated: true, method: null });
971
+ }
972
+ }
877
973
  // Dedupe the fallback warning: selectCli() runs on every heartbeat (the agent
878
974
  // reports activeCli), so logging unconditionally spammed thousands of identical
879
975
  // WARN lines. Only log when the message actually changes.
@@ -884,53 +980,48 @@ function warnCliOnce(msg) {
884
980
  lastCliWarn = msg;
885
981
  logger.warn(msg);
886
982
  }
983
+ // Preference order when nothing is pinned, and the order a fallback walks when
984
+ // the pinned CLI turns out not to be installed. Table-driven rather than a nest
985
+ // of pairwise ifs: every CLI added here otherwise costs another N branches, and
986
+ // the pair that got missed is exactly how a preference silently no-ops.
987
+ const CLI_AUTO_ORDER = ['orquesta', 'claude', 'kimi', 'cursor'];
988
+ const CLI_AUTO_REASON = {
989
+ orquesta: 'Auto: orquesta available (local LLM)',
990
+ claude: 'Auto: claude available (Anthropic API)',
991
+ kimi: 'Auto: kimi available',
992
+ cursor: 'Auto: cursor-agent available',
993
+ };
887
994
  // Select which CLI to use based on availability and preference
888
995
  export function selectCli() {
889
- const hasOrquesta = isOrquestaCliAvailable();
890
- const hasClaude = isClaudeCliAvailable();
891
- const hasKimi = isKimiCliAvailable();
892
- // If none available, return null
893
- if (!hasOrquesta && !hasClaude && !hasKimi) {
894
- return { cli: null, reason: 'No CLI found (orquesta, claude, kimi)' };
996
+ const available = {
997
+ orquesta: isOrquestaCliAvailable(),
998
+ claude: isClaudeCliAvailable(),
999
+ kimi: isKimiCliAvailable(),
1000
+ cursor: isCursorCliAvailable(),
1001
+ };
1002
+ if (!CLI_AUTO_ORDER.some(cli => available[cli])) {
1003
+ return { cli: null, reason: `No CLI found (${CLI_AUTO_ORDER.join(', ')})` };
895
1004
  }
1005
+ const preferred = globalCliPreference;
896
1006
  // Explicit preference, available → use it.
897
- if (globalCliPreference === 'orquesta' && hasOrquesta)
898
- return { cli: 'orquesta', reason: 'User preference: orquesta' };
899
- if (globalCliPreference === 'claude' && hasClaude)
900
- return { cli: 'claude', reason: 'User preference: claude' };
901
- if (globalCliPreference === 'kimi' && hasKimi)
902
- return { cli: 'kimi', reason: 'User preference: kimi' };
903
- // Explicit preference, NOT available → warn once + fall back (orquesta claude → kimi).
904
- if (globalCliPreference === 'kimi' && !hasKimi) {
905
- warnCliOnce('Kimi CLI preferred but not available, falling back');
906
- if (hasOrquesta)
907
- return { cli: 'orquesta', reason: 'Fallback: kimi not available' };
908
- if (hasClaude)
909
- return { cli: 'claude', reason: 'Fallback: kimi not available' };
910
- }
911
- if (globalCliPreference === 'orquesta' && !hasOrquesta) {
912
- warnCliOnce('Orquesta CLI preferred but not available, falling back');
913
- if (hasClaude)
914
- return { cli: 'claude', reason: 'Fallback: orquesta not available' };
915
- if (hasKimi)
916
- return { cli: 'kimi', reason: 'Fallback: orquesta not available' };
917
- }
918
- if (globalCliPreference === 'claude' && !hasClaude) {
919
- warnCliOnce('Claude CLI preferred but not available, falling back');
920
- if (hasOrquesta)
921
- return { cli: 'orquesta', reason: 'Fallback: claude not available' };
922
- if (hasKimi)
923
- return { cli: 'kimi', reason: 'Fallback: claude not available' };
924
- }
925
- // Auto mode: prefer orquesta → claude → kimi.
926
- if (hasOrquesta) {
1007
+ if (preferred !== 'auto' && available[preferred]) {
1008
+ lastCliWarn = null;
1009
+ return { cli: preferred, reason: `User preference: ${preferred}` };
1010
+ }
1011
+ // Explicit preference, NOT available → warn once, then fall through to the
1012
+ // auto order skipping the one that isn't there.
1013
+ if (preferred !== 'auto') {
1014
+ warnCliOnce(`${preferred} CLI preferred but not available, falling back`);
1015
+ const fallback = CLI_AUTO_ORDER.find(cli => cli !== preferred && available[cli]);
1016
+ if (fallback)
1017
+ return { cli: fallback, reason: `Fallback: ${preferred} not available` };
1018
+ return { cli: null, reason: 'No CLI found' };
1019
+ }
1020
+ const auto = CLI_AUTO_ORDER.find(cli => available[cli]);
1021
+ if (auto) {
927
1022
  lastCliWarn = null;
928
- return { cli: 'orquesta', reason: 'Auto: orquesta available (local LLM)' };
1023
+ return { cli: auto, reason: CLI_AUTO_REASON[auto] };
929
1024
  }
930
- if (hasClaude)
931
- return { cli: 'claude', reason: 'Auto: claude available (Anthropic API)' };
932
- if (hasKimi)
933
- return { cli: 'kimi', reason: 'Auto: kimi available' };
934
1025
  return { cli: null, reason: 'No CLI found' };
935
1026
  }
936
1027
  // Check if Claude CLI is authenticated
@@ -1507,6 +1598,20 @@ function detectQAInstructions(text, commandId) {
1507
1598
  const buf = (qaBufferMap.get(commandId) || '') + text;
1508
1599
  qaBufferMap.set(commandId, buf.length > 20000 ? buf.slice(-15000) : buf);
1509
1600
  const currentBuf = qaBufferMap.get(commandId);
1601
+ /*
1602
+ * Last-resort unescape for a block that reached us still JSON-encoded (a CLI
1603
+ * whose output format we do not parse, or a tool that echoes a JSON payload).
1604
+ * Only when the whole capture is a single line carrying literal \n escapes —
1605
+ * a genuine multi-line QA block is left exactly as written, so a step that
1606
+ * legitimately mentions \n in a regex or a code span is not rewritten.
1607
+ */
1608
+ const unescapeIfEncoded = (value) => !value.includes('\n') && /\\n/.test(value)
1609
+ ? value
1610
+ .replace(/\\r\\n|\\n/g, '\n')
1611
+ .replace(/\\t/g, '\t')
1612
+ .replace(/\\"/g, '"')
1613
+ .replace(/\\\\/g, '\\')
1614
+ : value;
1510
1615
  // Look for QA Instructions header (## or ###)
1511
1616
  const qaMatch = currentBuf.match(/##\s*QA\s+Instructions\s*([\s\S]*?)(?=\n##[^#]|$)/i);
1512
1617
  if (!qaMatch || qaDetectedMap.get(commandId))
@@ -1515,7 +1620,7 @@ function detectQAInstructions(text, commandId) {
1515
1620
  // to end-of-buffer and swallows whatever the CLI prints next (observed: the
1516
1621
  // whole appended system prompt — Orquesta Capabilities, browser tools —
1517
1622
  // persisted as "QA instructions" in the DB). Real QA blocks are short.
1518
- const qaContent = qaMatch[1].length > 4000 ? qaMatch[1].slice(0, 4000) : qaMatch[1];
1623
+ const qaContent = unescapeIfEncoded(qaMatch[1].length > 4000 ? qaMatch[1].slice(0, 4000) : qaMatch[1]);
1519
1624
  // Ignore the TEMPLATE echo: if the CLI echoes the QA-instructions prompt
1520
1625
  // template itself ("[Brief description of the feature/change]" etc.), the
1521
1626
  // placeholders are still in brackets — that's not a real QA report.
@@ -1525,7 +1630,11 @@ function detectQAInstructions(text, commandId) {
1525
1630
  // Matches: **Feature**: or **Feature:** or ### Feature or **Summary**: etc
1526
1631
  // Note: Claude sometimes puts colon inside bold (**Feature:**) or outside (**Feature**:)
1527
1632
  const featureMatch = qaContent.match(/(?:\*\*(?:Feature|Description|Summary)(?::\*\*|\*\*:)|###?\s*(?:Feature|Description|Summary):?\s*\n?)\s*(.+?)(?:\n|$)/i);
1528
- const feature = featureMatch ? featureMatch[1].trim() : 'Feature verification';
1633
+ // Clamp it: the feature is a card heading in the dashboard, so whatever the
1634
+ // regex captures has to stay heading-sized even if the output was shaped in
1635
+ // some way these patterns did not anticipate. QA-70.
1636
+ const rawFeature = featureMatch ? featureMatch[1].trim() : 'Feature verification';
1637
+ const feature = rawFeature.length > 160 ? rawFeature.slice(0, 157).trimEnd() + '…' : rawFeature;
1529
1638
  // Extract steps to test (accept multiple formats)
1530
1639
  // Matches: "**Steps to Test**:", "**Steps to verify:**", "### Test Steps", "### Steps", etc
1531
1640
  const stepsMatch = qaContent.match(/(?:\*\*(?:Steps\s+to\s+(?:Test|[Vv]erify)|Verification\s+Steps|Testing\s+Steps|Test\s+Steps|Steps)(?::\*\*|\*\*:)|###?\s*(?:Steps\s+to\s+(?:Test|[Vv]erify)|Verification\s+Steps|Testing\s+Steps|Test\s+Steps|Steps):?\s*\n?)\s*([\s\S]*?)(?=\*\*Expected|###?\s*Expected|###?\s*Rollback|\*\*Test\s+URL|\*\*Production\s+URL|$)/i);
@@ -1989,9 +2098,11 @@ ${userRequestBody}`;
1989
2098
  // (cliCommand stays the TYPE for flag decisions.)
1990
2099
  const cliBinary = cliCommand === 'kimi'
1991
2100
  ? (resolveKimiBinary() || 'kimi')
1992
- : cliCommand === 'claude'
1993
- ? (resolveClaudeBinary() || 'claude')
1994
- : (cliCommand || 'claude');
2101
+ : cliCommand === 'cursor'
2102
+ ? (resolveCursorBinary() || 'cursor-agent')
2103
+ : cliCommand === 'claude'
2104
+ ? (resolveClaudeBinary() || 'claude')
2105
+ : (cliCommand || 'claude');
1995
2106
  const cwd = workingDirectory || process.cwd();
1996
2107
  // Which branch this prompt runs on — captured up front so it's persisted even
1997
2108
  // if the run fails, and broadcast live below once sendSpecialEvent exists.
@@ -2010,7 +2121,9 @@ ${userRequestBody}`;
2010
2121
  // can never regress to a worse state than before.
2011
2122
  let promptContent = fullContent;
2012
2123
  let sysPromptFilePath = null;
2013
- if (cliCommand !== 'kimi') {
2124
+ // kimi and cursor-agent have no --append-system-prompt-file, so their static
2125
+ // context stays inline in the prompt.
2126
+ if (cliCommand !== 'kimi' && cliCommand !== 'cursor') {
2014
2127
  try {
2015
2128
  const promptDir = process.platform === 'win32'
2016
2129
  ? os.tmpdir()
@@ -2037,15 +2150,28 @@ ${userRequestBody}`;
2037
2150
  // Orquesta CLI doesn't need special format flags - it outputs directly
2038
2151
  const baseFlags = cliCommand === 'orquesta'
2039
2152
  ? '--verbose' // Orquesta: simple verbose output
2040
- : cliCommand === 'kimi'
2041
- ? '--output-format text' // Kimi: plain text. NOTE: -p (one-shot) CANNOT be
2042
- // combined with -y ("Cannot combine --prompt with
2043
- // --yolo"), so dispatched kimi prompts respond as
2044
- // chat (no autonomous tool use). Interactive kimi
2045
- // sessions DO get -y (see ptyArgs below).
2046
- : '--verbose --output-format stream-json'; // Claude: stream-json format
2047
- // Kimi has no --dangerously-skip-permissions, and -p forbids -y.
2048
- const permFlags = cliCommand === 'kimi' ? '' : (mode === 'auto' ? '--dangerously-skip-permissions' : '');
2153
+ : cliCommand === 'cursor'
2154
+ ? '--output-format text' // Cursor: plain text. Its stream-json events are a
2155
+ // different shape from Claude's, and parseStreamJson
2156
+ // below only understands Claude's and orquesta-cli's,
2157
+ // so asking for it would give us an unparseable
2158
+ // stream instead of an answer.
2159
+ : cliCommand === 'kimi'
2160
+ ? '--output-format text' // Kimi: plain text. NOTE: -p (one-shot) CANNOT be
2161
+ // combined with -y ("Cannot combine --prompt with
2162
+ // --yolo"), so dispatched kimi prompts respond as
2163
+ // chat (no autonomous tool use). Interactive kimi
2164
+ // sessions DO get -y (see ptyArgs below).
2165
+ : '--verbose --output-format stream-json'; // Claude: stream-json format
2166
+ // Kimi has no --dangerously-skip-permissions, and -p forbids -y. Cursor spells
2167
+ // the same thing --force (alias --yolo).
2168
+ const permFlags = cliCommand === 'kimi'
2169
+ ? ''
2170
+ : mode !== 'auto'
2171
+ ? ''
2172
+ : cliCommand === 'cursor'
2173
+ ? '--force'
2174
+ : '--dangerously-skip-permissions';
2049
2175
  // Per-project endpoint override (orquesta CLI only). Single-quoted for the
2050
2176
  // shell-wrapped (Linux) path; pushed as a separate arg on Win/macOS below.
2051
2177
  const useEndpoint = cliCommand === 'orquesta' && !!globalCliEndpoint;
@@ -2534,8 +2660,17 @@ ${userRequestBody}`;
2534
2660
  logger.warn(`Failed to persist requirement: ${err}`);
2535
2661
  });
2536
2662
  }
2537
- // Detect QA instructions
2538
- const qaInstructions = detectQAInstructions(text, commandId);
2663
+ // Detect QA instructions.
2664
+ //
2665
+ // On the PARSED output, not the raw chunk. With --output-format
2666
+ // stream-json every assistant message arrives as one JSON line, so the
2667
+ // newlines inside it are the two characters \n and the markdown
2668
+ // structure the patterns below look for does not exist yet: the
2669
+ // "**Feature**:" capture, terminated by a newline, ran to the end of the
2670
+ // line and stored the entire escaped message as the feature name — the
2671
+ // wall of text QA saw in the panel. `parsed` is the same text the user
2672
+ // reads in the output pane, with real line breaks. QA-70.
2673
+ const qaInstructions = detectQAInstructions(outputToSend, commandId);
2539
2674
  if (qaInstructions) {
2540
2675
  logger.success(`QA Instructions detected: ${qaInstructions.feature}`);
2541
2676
  // Send via realtime channel
@@ -2682,10 +2817,20 @@ ${userRequestBody}`;
2682
2817
  // Record WHICH CLI + model executed it (timeline/history). Model is
2683
2818
  // best-effort — the CLI/proxy pick the true upstream model internally
2684
2819
  // (batuta-auto fans out per prompt), so we report the CLI's MODEL CONFIG:
2685
- const cliTypeForDb = cliCommand === 'orquesta' ? 'orquesta-cli' : cliCommand === 'kimi' ? 'kimi-cli' : 'claude-cli';
2820
+ const cliTypeForDb = cliCommand === 'orquesta'
2821
+ ? 'orquesta-cli'
2822
+ : cliCommand === 'kimi'
2823
+ ? 'kimi-cli'
2824
+ : cliCommand === 'cursor'
2825
+ ? 'cursor-cli'
2826
+ : 'claude-cli';
2686
2827
  let modelForDb;
2687
2828
  if (cliCommand === 'kimi')
2688
2829
  modelForDb = 'kimi-for-coding';
2830
+ // cursor-agent picks the model from the user's own Cursor settings and does
2831
+ // not report it in text mode, so leave it unset rather than guess.
2832
+ else if (cliCommand === 'cursor')
2833
+ modelForDb = undefined;
2689
2834
  else if (cliCommand === 'orquesta')
2690
2835
  modelForDb = (globalCliEndpoint === 'batuta' || !globalCliEndpoint) ? 'batuta-auto' : globalCliEndpoint;
2691
2836
  else if (cliCommand === 'claude')
@@ -3544,20 +3689,30 @@ export async function startSession(options) {
3544
3689
  // inside bwrap (/ is ro-bound). cliCommand stays the TYPE.
3545
3690
  const cliBinary = cliCommand === 'kimi'
3546
3691
  ? (resolveKimiBinary() || 'kimi')
3547
- : cliCommand === 'claude'
3548
- ? (resolveClaudeBinary() || 'claude')
3549
- : cliCommand;
3550
- logger.info(`Interactive session CLI: ${cliCommand}${cliCommand === 'kimi' ? ` (${cliBinary})` : ''}`);
3692
+ : cliCommand === 'cursor'
3693
+ ? (resolveCursorBinary() || 'cursor-agent')
3694
+ : cliCommand === 'claude'
3695
+ ? (resolveClaudeBinary() || 'claude')
3696
+ : cliCommand;
3697
+ const resolvedByPath = cliCommand === 'kimi' || cliCommand === 'cursor';
3698
+ logger.info(`Interactive session CLI: ${cliCommand}${resolvedByPath ? ` (${cliBinary})` : ''}`);
3551
3699
  // Best-effort model + cli label reported in session:started so the dashboard
3552
3700
  // can tag the interactive prompt (same mapping dispatched prompts use on
3553
3701
  // completion). claude's exact model isn't knowable from a raw PTY, so it falls
3554
3702
  // back to ANTHROPIC_MODEL when set, else undefined.
3555
3703
  const sessionModel = cliCommand === 'kimi'
3556
3704
  ? 'kimi-for-coding'
3557
- : cliCommand === 'orquesta'
3558
- ? ((globalCliEndpoint === 'batuta' || !globalCliEndpoint) ? 'batuta-auto' : globalCliEndpoint)
3559
- : (process.env.ANTHROPIC_MODEL || undefined);
3560
- const sessionCliType = cliCommand === 'orquesta' ? 'orquesta' : cliCommand === 'kimi' ? 'kimi' : 'claude';
3705
+ : cliCommand === 'cursor'
3706
+ // cursor-agent uses whatever model the user selected in their own Cursor
3707
+ // account; it isn't knowable from the PTY, so report nothing rather than lie.
3708
+ ? undefined
3709
+ : cliCommand === 'orquesta'
3710
+ ? ((globalCliEndpoint === 'batuta' || !globalCliEndpoint) ? 'batuta-auto' : globalCliEndpoint)
3711
+ : (process.env.ANTHROPIC_MODEL || undefined);
3712
+ const sessionCliType = cliCommand === 'orquesta' ? 'orquesta'
3713
+ : cliCommand === 'kimi' ? 'kimi'
3714
+ : cliCommand === 'cursor' ? 'cursor'
3715
+ : 'claude';
3561
3716
  // Filter undefined env values — node-pty requires Record<string, string>.
3562
3717
  // In strict sandbox mode, sandboxEnv() first reduces to the allowlist so the
3563
3718
  // interactive CLI can't read unrelated host secrets from its own env.
@@ -3637,7 +3792,7 @@ export async function startSession(options) {
3637
3792
  // `ps aux`. Default-off because a file unreadable inside bwrap would silently
3638
3793
  // strip the system prompt (degraded session, not a crash) — flip the default
3639
3794
  // only after E2E confirms the sandbox reads it. Rollback = unset the var.
3640
- const wantSysPromptFile = cliCommand !== 'kimi' && ((process.platform === 'win32' && cliCommand === 'orquesta') ||
3795
+ const wantSysPromptFile = cliCommand !== 'kimi' && cliCommand !== 'cursor' && ((process.platform === 'win32' && cliCommand === 'orquesta') ||
3641
3796
  /^(1|true|yes|on)$/i.test(process.env.ORQUESTA_SYSPROMPT_FILE || ''));
3642
3797
  if (wantSysPromptFile) {
3643
3798
  try {
@@ -3656,7 +3811,13 @@ export async function startSession(options) {
3656
3811
  }
3657
3812
  }
3658
3813
  let ptyArgs;
3659
- if (cliCommand === 'kimi') {
3814
+ if (cliCommand === 'cursor') {
3815
+ // cursor-agent has no --append-system-prompt; it reads AGENTS.md / CLAUDE.md
3816
+ // from the working dir (which the agent already syncs), so the capabilities
3817
+ // block isn't injected. `--force` is its --dangerously-skip-permissions.
3818
+ ptyArgs = ['--force'];
3819
+ }
3820
+ else if (cliCommand === 'kimi') {
3660
3821
  // Kimi has neither --dangerously-skip-permissions nor --append-system-prompt.
3661
3822
  // `-y` auto-approves the interactive session; it reads project conventions from
3662
3823
  // CLAUDE.md / AGENTS.md in the working dir (which the agent already syncs), so
@@ -3674,10 +3835,11 @@ export async function startSession(options) {
3674
3835
  // resume flag, so the option is silently ignored there. Prepended so it's an
3675
3836
  // unambiguous standalone flag, separate from --append-system-prompt's value.
3676
3837
  if ((resume || resumeSessionId) && cliCommand !== 'kimi') {
3838
+ // cursor-agent spells these the same way claude does (--continue / --resume <id>).
3677
3839
  // claude can target a SPECIFIC past conversation by id (--resume <id>);
3678
3840
  // orquesta-cli only reattaches the latest (--continue). So when a specific
3679
3841
  // id is requested and we're on claude, use --resume; otherwise --continue.
3680
- if (resumeSessionId && cliCommand === 'claude') {
3842
+ if (resumeSessionId && (cliCommand === 'claude' || cliCommand === 'cursor')) {
3681
3843
  ptyArgs.unshift('--resume', resumeSessionId);
3682
3844
  logger.info(`[Session] Resuming conversation ${resumeSessionId} (--resume) for ${cliCommand}`);
3683
3845
  }