orquesta-agent 0.2.238 → 0.2.240
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/dist/executor.d.ts +42 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +120 -18
- 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/index.js +90 -7
- package/dist/index.js.map +1 -1
- package/dist/supabase.d.ts +31 -1
- package/dist/supabase.d.ts.map +1 -1
- package/dist/supabase.js +10 -2
- package/dist/supabase.js.map +1 -1
- package/dist/ui/server.d.ts.map +1 -1
- package/dist/ui/server.js +112 -43
- package/dist/ui/server.js.map +1 -1
- package/dist/ws-client.d.ts +14 -2
- 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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only file browsing for the dashboard's Code Browser.
|
|
3
|
+
*
|
|
4
|
+
* A project bought as "Solo Orquesta" has no GitHub repository and no SSH
|
|
5
|
+
* credentials — the two sources the browser knew about — so the panel was
|
|
6
|
+
* permanently blocked even though the agent is connected and the files are
|
|
7
|
+
* right there on the machine. This answers `fs:list` / `fs:read` from the
|
|
8
|
+
* agent's own working directory. QA-68.
|
|
9
|
+
*
|
|
10
|
+
* Read-only on purpose, and confined to the working directory: it resolves
|
|
11
|
+
* every requested path and refuses anything that lands outside, symlinks
|
|
12
|
+
* included, so a crafted path cannot be used to read the rest of the disk.
|
|
13
|
+
*/
|
|
14
|
+
export interface BrowseItem {
|
|
15
|
+
name: string;
|
|
16
|
+
path: string;
|
|
17
|
+
type: 'file' | 'dir';
|
|
18
|
+
size: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function listDirectory(root: string, requestedPath?: string): {
|
|
21
|
+
path: string;
|
|
22
|
+
items: BrowseItem[];
|
|
23
|
+
truncated: boolean;
|
|
24
|
+
} | {
|
|
25
|
+
error: string;
|
|
26
|
+
};
|
|
27
|
+
export declare function readFileForBrowser(root: string, requestedPath?: string): {
|
|
28
|
+
path: string;
|
|
29
|
+
name: string;
|
|
30
|
+
content: string;
|
|
31
|
+
size: number;
|
|
32
|
+
} | {
|
|
33
|
+
error: string;
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=fs-browse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-browse.d.ts","sourceRoot":"","sources":["../src/fs-browse.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,KAAK,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb;AAsCD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG;IACnE,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;CACnB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAyCpB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG;IACxE,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CA2BpB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import * as logger from './logger.js';
|
|
4
|
+
/** A listing is a directory pane — long enough to be useful, short enough to send. */
|
|
5
|
+
const MAX_ENTRIES = 1000;
|
|
6
|
+
/** Above this a file stops being something you read in a browser pane. */
|
|
7
|
+
const MAX_FILE_BYTES = 1024 * 1024;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve `rel` beneath `root`, or null when it escapes.
|
|
10
|
+
*
|
|
11
|
+
* Both the lexical path and its realpath are checked: `..` is caught by the
|
|
12
|
+
* first, a symlink pointing out of the tree only by the second.
|
|
13
|
+
*/
|
|
14
|
+
function resolveInside(root, rel) {
|
|
15
|
+
const base = path.resolve(root);
|
|
16
|
+
const cleaned = (rel || '').replace(/^[/\\]+/, '');
|
|
17
|
+
const target = path.resolve(base, cleaned === '.' ? '' : cleaned);
|
|
18
|
+
const contains = (p) => p === base || p.startsWith(base + path.sep);
|
|
19
|
+
if (!contains(target))
|
|
20
|
+
return null;
|
|
21
|
+
try {
|
|
22
|
+
const real = fs.realpathSync(target);
|
|
23
|
+
const realBase = fs.realpathSync(base);
|
|
24
|
+
if (real !== realBase && !real.startsWith(realBase + path.sep))
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Does not exist yet / cannot be resolved — the caller's stat will report it.
|
|
29
|
+
}
|
|
30
|
+
return target;
|
|
31
|
+
}
|
|
32
|
+
/** Path as the dashboard addresses it: relative to the root, forward slashes. */
|
|
33
|
+
function toRelative(root, absolute) {
|
|
34
|
+
const rel = path.relative(path.resolve(root), absolute);
|
|
35
|
+
return rel.split(path.sep).join('/');
|
|
36
|
+
}
|
|
37
|
+
export function listDirectory(root, requestedPath) {
|
|
38
|
+
const target = resolveInside(root, requestedPath);
|
|
39
|
+
if (!target)
|
|
40
|
+
return { error: 'Path is outside the working directory' };
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
const stat = fs.statSync(target);
|
|
44
|
+
if (!stat.isDirectory())
|
|
45
|
+
return { error: 'Not a directory' };
|
|
46
|
+
entries = fs.readdirSync(target, { withFileTypes: true });
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return { error: err instanceof Error ? err.message : 'Could not read directory' };
|
|
50
|
+
}
|
|
51
|
+
// `.git` is megabytes of objects nobody browses, and the GitHub source does
|
|
52
|
+
// not show it either — keeping it out keeps the two sources looking alike.
|
|
53
|
+
const visible = entries.filter(e => e.name !== '.git');
|
|
54
|
+
const truncated = visible.length > MAX_ENTRIES;
|
|
55
|
+
const items = [];
|
|
56
|
+
for (const entry of visible.slice(0, MAX_ENTRIES)) {
|
|
57
|
+
const absolute = path.join(target, entry.name);
|
|
58
|
+
let size = 0;
|
|
59
|
+
let isDir = entry.isDirectory();
|
|
60
|
+
try {
|
|
61
|
+
const stat = fs.statSync(absolute); // follows symlinks, so a linked dir reads as a dir
|
|
62
|
+
size = stat.isFile() ? stat.size : 0;
|
|
63
|
+
isDir = stat.isDirectory();
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Broken symlink or a race with a delete — list it as an empty file.
|
|
67
|
+
}
|
|
68
|
+
items.push({
|
|
69
|
+
name: entry.name,
|
|
70
|
+
path: toRelative(root, absolute),
|
|
71
|
+
type: isDir ? 'dir' : 'file',
|
|
72
|
+
size,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
items.sort((a, b) => (a.type !== b.type ? (a.type === 'dir' ? -1 : 1) : a.name.localeCompare(b.name)));
|
|
76
|
+
return { path: toRelative(root, target), items, truncated };
|
|
77
|
+
}
|
|
78
|
+
export function readFileForBrowser(root, requestedPath) {
|
|
79
|
+
const target = resolveInside(root, requestedPath);
|
|
80
|
+
if (!target)
|
|
81
|
+
return { error: 'Path is outside the working directory' };
|
|
82
|
+
try {
|
|
83
|
+
const stat = fs.statSync(target);
|
|
84
|
+
if (!stat.isFile())
|
|
85
|
+
return { error: 'Not a file' };
|
|
86
|
+
if (stat.size > MAX_FILE_BYTES) {
|
|
87
|
+
return { error: `File is too large to open here (${Math.round(stat.size / 1024)} KB)` };
|
|
88
|
+
}
|
|
89
|
+
const buffer = fs.readFileSync(target);
|
|
90
|
+
// A NUL byte early on is the usual, cheap "this is not text" signal.
|
|
91
|
+
if (buffer.subarray(0, 8000).includes(0)) {
|
|
92
|
+
return { error: 'Binary file' };
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
path: toRelative(root, target),
|
|
96
|
+
name: path.basename(target),
|
|
97
|
+
content: buffer.toString('utf-8'),
|
|
98
|
+
size: stat.size,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
logger.debug(`[fs-browse] read failed: ${err instanceof Error ? err.message : err}`);
|
|
103
|
+
return { error: err instanceof Error ? err.message : 'Could not read file' };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=fs-browse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-browse.js","sourceRoot":"","sources":["../src/fs-browse.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -29,12 +29,13 @@ import * as path from 'path';
|
|
|
29
29
|
import * as os from 'os';
|
|
30
30
|
import { fileURLToPath } from 'url';
|
|
31
31
|
import * as logger from './logger.js';
|
|
32
|
+
import { listDirectory, readFileForBrowser } from './fs-browse.js';
|
|
32
33
|
import { startDaemon, isDaemonChild, acquireSingleInstanceLock } from './daemon.js';
|
|
33
34
|
import { getCliTokenHash } from './cli-config.js';
|
|
34
35
|
import { validateToken, markDisconnected, subscribeToChannel, setAgentToken, reportAgentError, reportAgentProblem, persistAgentSettings, startHeartbeat, stopHeartbeat, setPermissionModeUpdater, setCliPreferenceUpdater, setCliEndpointUpdater, setBrowserEnabledUpdater, setSandboxUpdater, setSandboxAccessUpdater, setCurrentProjectId, getCurrentProjectId, fetchCredentials, fetchSkills, syncSkillsToDirectory, uploadClaudeMd, setClaudeAuthChecker, setOrquestaCliChecker, setKimiCliChecker, setCursorCliChecker, setActiveCliResolver, setEffectiveConfigResolver, setAgentVersion, setAgentName, } from './supabase.js';
|
|
35
36
|
import { createSocketIOConnection, createExecutionChannel, setupSocketIOListeners, } from './socketio-transport.js';
|
|
36
37
|
import { execSync, spawn } from 'child_process';
|
|
37
|
-
import { execute, evaluate, cancel, cancelAll, hasRunningProcesses, checkClaudeAuth, configureAuth, gitClone, gitPull, setPermissionMode, setSandboxConfig, setSandboxAccess, setAgentInstructions, setProjectSettings, handleSupervisionResponse, setInjectedCredentials, generatePlanItems, isOrquestaCliAvailable, isClaudeCliAvailable, setCliPreference, setCliEndpoint, setBrowserEnabled, selectCli, isKimiCliAvailable, isCursorCliAvailable, checkCursorAuth,
|
|
38
|
+
import { execute, evaluate, cancel, cancelAll, hasRunningProcesses, checkClaudeAuth, configureAuth, gitClone, gitPull, gitCommitAndPush, gitNumstatSince, getCurrentGitBranch, setPermissionMode, setSandboxConfig, setSandboxAccess, setAgentInstructions, setProjectSettings, handleSupervisionResponse, setInjectedCredentials, generatePlanItems, isOrquestaCliAvailable, isClaudeCliAvailable, setCliPreference, setCliEndpoint, setBrowserEnabled, selectCli, isKimiCliAvailable, isCursorCliAvailable, checkCursorAuth,
|
|
38
39
|
// Interactive session functions
|
|
39
40
|
startSession, sendSessionInput, endSession, terminateSession, hasActiveSession, hasActiveSessionFor, getEffectiveRuntimeConfig, getSessionStatus, resizeSession, setOnSessionEnded, listResumableSessions, } from './executor.js';
|
|
40
41
|
import { startBrowserSession, endBrowserSession, tunnelInbound, openBrowserTunnel, browserRpc, runBrowserGoal, pauseBrowserGoal, resumeBrowserGoal, cancelBrowserGoal, shutdownAllBrowserSessions, } from './browser.js';
|
|
@@ -690,6 +691,48 @@ async function connect(options) {
|
|
|
690
691
|
return promptQueue.some(q => q.promptId === promptId) ||
|
|
691
692
|
[...runningExecutions.values()].some(e => e.promptId === promptId);
|
|
692
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Commit the assisted item's work and hand the server the raw facts.
|
|
696
|
+
*
|
|
697
|
+
* Facts only: the commit sha, the branch, and `git diff --numstat` text.
|
|
698
|
+
* The server evaluates the gate. That split is the point — a gate computed
|
|
699
|
+
* on the machine being gated protects nobody, and this machine belongs to
|
|
700
|
+
* the customer.
|
|
701
|
+
*
|
|
702
|
+
* Best-effort throughout: a failure here must not fail the prompt, which
|
|
703
|
+
* has already produced real work. The reviewer sees the item stuck
|
|
704
|
+
* in_progress and can nudge the run instead.
|
|
705
|
+
*/
|
|
706
|
+
async function reportAssistedResult(payload, execDir, baseBranch) {
|
|
707
|
+
const assisted = payload.assisted;
|
|
708
|
+
try {
|
|
709
|
+
const commit = await gitCommitAndPush(execDir, `assisted: ${payload.content.split('\n')[0].slice(0, 120)}`);
|
|
710
|
+
// Diff against the branch we started from when we know it; otherwise
|
|
711
|
+
// the last commit only, which is what the item just produced anyway.
|
|
712
|
+
const base = baseBranch && baseBranch !== assisted.branch ? baseBranch : 'HEAD~1';
|
|
713
|
+
const numstat = commit.sha ? gitNumstatSince(execDir, base) : '';
|
|
714
|
+
const apiUrl = process.env.ORQUESTA_API_URL || 'https://getorquesta.com';
|
|
715
|
+
const res = await fetch(`${apiUrl}/api/agent/assisted-report`, {
|
|
716
|
+
method: 'POST',
|
|
717
|
+
headers: { 'Content-Type': 'application/json' },
|
|
718
|
+
body: JSON.stringify({
|
|
719
|
+
token,
|
|
720
|
+
promptId: payload.promptId,
|
|
721
|
+
commitSha: commit.sha,
|
|
722
|
+
branch: assisted.branch,
|
|
723
|
+
numstat,
|
|
724
|
+
}),
|
|
725
|
+
});
|
|
726
|
+
if (!res.ok) {
|
|
727
|
+
logger.warn(`[assisted] report rejected (${res.status}) for item ${assisted.itemId}`);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
logger.success(`[assisted] reported ${commit.sha ? commit.sha.slice(0, 8) : 'no commit'} on ${assisted.branch}`);
|
|
731
|
+
}
|
|
732
|
+
catch (err) {
|
|
733
|
+
logger.warn(`[assisted] report failed: ${err}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
693
736
|
async function executePrompt(payload) {
|
|
694
737
|
const execId = payload.id;
|
|
695
738
|
runningExecutions.set(execId, { promptId: payload.promptId, commandId: execId, subAgentId: payload.subAgentId });
|
|
@@ -717,6 +760,14 @@ async function connect(options) {
|
|
|
717
760
|
catch {
|
|
718
761
|
// Non-fatal: continue with existing credentials
|
|
719
762
|
}
|
|
763
|
+
// The branch we came FROM, captured before any checkout below moves
|
|
764
|
+
// HEAD. Auto-merge used to read it afterwards, from `workingDirectory`
|
|
765
|
+
// — which for the common case (execDir === workingDirectory) is the
|
|
766
|
+
// sub-agent branch itself, so `subCfg.branch !== mainBranch` was false
|
|
767
|
+
// and the merge silently never ran. Capture first, merge into this.
|
|
768
|
+
const baseBranch = fs.existsSync(path.join(execDir, '.git'))
|
|
769
|
+
? getCurrentGitBranch(execDir)
|
|
770
|
+
: null;
|
|
720
771
|
// Branch checkout if sub-agent has a configured branch
|
|
721
772
|
if (subCfg?.branch && fs.existsSync(path.join(execDir, '.git'))) {
|
|
722
773
|
try {
|
|
@@ -742,15 +793,28 @@ async function connect(options) {
|
|
|
742
793
|
channel: channel,
|
|
743
794
|
permissionMode: currentPermissionMode,
|
|
744
795
|
attachments: payload.attachments,
|
|
796
|
+
assisted: payload.assisted,
|
|
745
797
|
});
|
|
798
|
+
// Assisted run: commit what the CLI produced and report the FACTS to
|
|
799
|
+
// the server, which computes the gate. The agent deliberately makes no
|
|
800
|
+
// judgement about the diff — it does not decide whether the change is
|
|
801
|
+
// too big, and it never merges or deploys. autoMerge is false on these
|
|
802
|
+
// payloads for the same reason.
|
|
803
|
+
if (payload.assisted && fs.existsSync(path.join(execDir, '.git'))) {
|
|
804
|
+
await reportAssistedResult(payload, execDir, baseBranch);
|
|
805
|
+
}
|
|
746
806
|
// Auto-merge if configured
|
|
747
807
|
if (subCfg?.autoMerge && subCfg.branch && fs.existsSync(path.join(execDir, '.git'))) {
|
|
748
808
|
try {
|
|
749
|
-
const mainBranch =
|
|
750
|
-
if (subCfg.branch !== mainBranch) {
|
|
751
|
-
execSync(`git checkout ${mainBranch}
|
|
809
|
+
const mainBranch = baseBranch;
|
|
810
|
+
if (mainBranch && subCfg.branch !== mainBranch) {
|
|
811
|
+
execSync(`git checkout ${mainBranch}`, { cwd: execDir, stdio: 'pipe' });
|
|
812
|
+
execSync(`git merge ${subCfg.branch} --no-edit`, { cwd: execDir, stdio: 'pipe' });
|
|
752
813
|
logger.success(`${label} Auto-merged ${subCfg.branch} into ${mainBranch}`);
|
|
753
814
|
}
|
|
815
|
+
else if (!mainBranch) {
|
|
816
|
+
logger.warn(`${label} Auto-merge skipped: could not determine the base branch`);
|
|
817
|
+
}
|
|
754
818
|
}
|
|
755
819
|
catch (err) {
|
|
756
820
|
logger.warn(`${label} Auto-merge failed (resolve manually): ${err}`);
|
|
@@ -820,12 +884,24 @@ async function connect(options) {
|
|
|
820
884
|
}
|
|
821
885
|
else {
|
|
822
886
|
logger.info(`[Heartbeat] Dispatching missed execute for prompt ${cmd.promptId}`);
|
|
887
|
+
// Carry the branch envelope through the fallback too. Without it an
|
|
888
|
+
// assisted item delivered by heartbeat runs on whatever branch is
|
|
889
|
+
// currently checked out, which is exactly the isolation the run
|
|
890
|
+
// depends on.
|
|
891
|
+
const queued = {
|
|
892
|
+
id: cmd.commandId,
|
|
893
|
+
promptId: cmd.promptId,
|
|
894
|
+
content: cmd.content,
|
|
895
|
+
workingDirectory,
|
|
896
|
+
subAgentConfig: cmd.subAgentConfig,
|
|
897
|
+
assisted: cmd.assisted,
|
|
898
|
+
};
|
|
823
899
|
if (isDefaultSlotBusy()) {
|
|
824
900
|
logger.warn(`[Heartbeat] Default slot busy - queuing missed prompt: ${cmd.promptId}`);
|
|
825
|
-
promptQueue.push(
|
|
901
|
+
promptQueue.push(queued);
|
|
826
902
|
}
|
|
827
903
|
else {
|
|
828
|
-
executePrompt(
|
|
904
|
+
executePrompt(queued);
|
|
829
905
|
}
|
|
830
906
|
}
|
|
831
907
|
});
|
|
@@ -1288,7 +1364,14 @@ async function connect(options) {
|
|
|
1288
1364
|
},
|
|
1289
1365
|
// Resumable past conversations for the dashboard Resume menu. Defaults to
|
|
1290
1366
|
// the project working dir when the request omits one.
|
|
1291
|
-
(payload) => listResumableSessions(payload?.workingDirectory || workingDirectory)
|
|
1367
|
+
(payload) => listResumableSessions(payload?.workingDirectory || workingDirectory),
|
|
1368
|
+
// Read-only file browsing, rooted at the working directory. This is the
|
|
1369
|
+
// only file source a project with no GitHub repo and no SSH host has, and
|
|
1370
|
+
// the browser panel is blocked without it. QA-68.
|
|
1371
|
+
{
|
|
1372
|
+
onFsList: (payload) => listDirectory(workingDirectory, payload?.path),
|
|
1373
|
+
onFsRead: (payload) => readFileForBrowser(workingDirectory, payload?.path),
|
|
1374
|
+
});
|
|
1292
1375
|
}
|
|
1293
1376
|
catch (err) {
|
|
1294
1377
|
const message = err instanceof Error ? err.message : 'Unknown error';
|