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.
- package/README.md +45 -0
- package/dist/cli-auth-detect.d.ts +17 -0
- package/dist/cli-auth-detect.d.ts.map +1 -0
- package/dist/cli-auth-detect.js +31 -0
- package/dist/cli-auth-detect.js.map +1 -0
- package/dist/executor.d.ts +12 -4
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +232 -70
- package/dist/executor.js.map +1 -1
- package/dist/fs-browse.d.ts +35 -0
- package/dist/fs-browse.d.ts.map +1 -0
- package/dist/fs-browse.js +106 -0
- package/dist/fs-browse.js.map +1 -0
- package/dist/hook.d.ts +26 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +170 -21
- package/dist/hook.js.map +1 -1
- package/dist/index.js +37 -15
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +42 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +100 -28
- package/dist/init.js.map +1 -1
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +4 -0
- package/dist/sandbox.js.map +1 -1
- package/dist/supabase.d.ts +35 -9
- package/dist/supabase.d.ts.map +1 -1
- package/dist/supabase.js +40 -4
- package/dist/supabase.js.map +1 -1
- package/dist/types/hook-types.d.ts +49 -1
- package/dist/types/hook-types.d.ts.map +1 -1
- package/dist/ui/onboarding.js +1 -1
- package/dist/ui/onboarding.js.map +1 -1
- package/dist/ui/public/app.js +11 -1
- package/dist/ui/public/index.html +1 -0
- package/dist/ui/public/style.css +1 -0
- package/dist/ui/public/welcome.html +2 -0
- package/dist/ui/server.d.ts +1 -1
- package/dist/ui/server.d.ts.map +1 -1
- package/dist/ui/server.js +114 -45
- package/dist/ui/server.js.map +1 -1
- package/dist/ui/types.d.ts +1 -1
- package/dist/ui/types.d.ts.map +1 -1
- package/dist/ws-client.d.ts +15 -3
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +42 -4
- package/dist/ws-client.js.map +1 -1
- 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"}
|
package/dist/executor.d.ts
CHANGED
|
@@ -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:
|
|
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
|
/**
|
package/dist/executor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAkItD,OAAO,EAAgG,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;
|
|
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
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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 (
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
if (
|
|
907
|
-
return { cli:
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (
|
|
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:
|
|
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
|
-
|
|
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 === '
|
|
1993
|
-
? (
|
|
1994
|
-
:
|
|
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
|
-
|
|
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 === '
|
|
2041
|
-
? '--output-format text' //
|
|
2042
|
-
//
|
|
2043
|
-
//
|
|
2044
|
-
//
|
|
2045
|
-
//
|
|
2046
|
-
:
|
|
2047
|
-
|
|
2048
|
-
|
|
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
|
-
|
|
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'
|
|
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 === '
|
|
3548
|
-
? (
|
|
3549
|
-
: cliCommand
|
|
3550
|
-
|
|
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 === '
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
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 === '
|
|
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
|
}
|