@pi-harness/pi-harness 0.1.85 → 0.1.87
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/apps/web/dist/assets/de-DhpdIJOo.js +1 -0
- package/apps/web/dist/assets/en-Dxf8FQYK.js +1 -0
- package/apps/web/dist/assets/es-CltpT46t.js +1 -0
- package/apps/web/dist/assets/fr-BeFnZlsQ.js +1 -0
- package/apps/web/dist/assets/{index--BtqJS5O.css → index-CYoyyJnL.css} +1 -1
- package/apps/web/dist/assets/index-DdCC9J2I.js +29 -0
- package/apps/web/dist/assets/ja-Dl0FYuCd.js +1 -0
- package/apps/web/dist/assets/ko-C3paKqYe.js +1 -0
- package/apps/web/dist/assets/pt-BR-CuPyhsku.js +1 -0
- package/apps/web/dist/assets/ru-UnUgv8E7.js +1 -0
- package/apps/web/dist/assets/zh-TW-CG6IjP89.js +1 -0
- package/apps/web/dist/index.html +2 -2
- package/apps/web/package.json +1 -1
- package/apps/web/src/style.css +3 -0
- package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.js +144 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
- package/node_modules/@pi-harness/api-gateway/package.json +1 -1
- package/node_modules/@pi-harness/cli/package.json +1 -1
- package/node_modules/@pi-harness/host-webserver/package.json +1 -1
- package/node_modules/@pi-harness/web-app/package.json +1 -1
- package/package.json +6 -6
- package/packages/api-gateway/dist/index.d.ts.map +1 -1
- package/packages/api-gateway/dist/index.js +144 -1
- package/packages/api-gateway/dist/index.js.map +1 -1
- package/packages/api-gateway/package.json +1 -1
- package/packages/api-gateway/src/index.ts +138 -1
- package/packages/api-gateway/test/index.test.ts +104 -3
- package/packages/bundle-web-app/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/host-webserver/package.json +1 -1
- package/apps/web/dist/assets/de-D-l2rv3V.js +0 -1
- package/apps/web/dist/assets/en-Dr2zqUBB.js +0 -1
- package/apps/web/dist/assets/es-DE_jy231.js +0 -1
- package/apps/web/dist/assets/fr-BY56gaJw.js +0 -1
- package/apps/web/dist/assets/index-Qupnc3-4.js +0 -29
- package/apps/web/dist/assets/ja-CVgWx-2k.js +0 -1
- package/apps/web/dist/assets/ko-BpKji4q-.js +0 -1
- package/apps/web/dist/assets/pt-BR-Cpdxbmiq.js +0 -1
- package/apps/web/dist/assets/ru-D66z4xXo.js +0 -1
- package/apps/web/dist/assets/zh-TW-CX8lrXBT.js +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { existsSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { mkdir, mkdtemp, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { mkdir, mkdtemp, opendir, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
@@ -23,6 +23,12 @@ const PROCESS_FAILURE_TEXT_LIMIT = 400;
|
|
|
23
23
|
const PROCESS_TIMEOUT_MS = 120_000;
|
|
24
24
|
// Mutating commands run user hooks (pre-commit, lint-staged, test suites) and may stage large trees; killing them part-way leaves index.lock and a half-finished operation behind, so they get a far more generous bound than read-only queries.
|
|
25
25
|
const GIT_MUTATION_TIMEOUT_MS = 120_000;
|
|
26
|
+
const MAX_WORKSPACE_FILES = 5_000;
|
|
27
|
+
const MAX_WORKSPACE_FILE_CANDIDATES = 20_000;
|
|
28
|
+
const MAX_WORKSPACE_DIRECTORIES = 2_000;
|
|
29
|
+
const MAX_WORKSPACE_DEPTH = 16;
|
|
30
|
+
const WORKSPACE_FILE_CACHE_MS = 1_000;
|
|
31
|
+
const IGNORED_WORKSPACE_DIRECTORIES = new Set([".git", "node_modules", ".pi", "dist", "build"]);
|
|
26
32
|
class PayloadTooLargeError extends Error {
|
|
27
33
|
constructor() {
|
|
28
34
|
super("Request body is too large");
|
|
@@ -124,6 +130,7 @@ async function createSessionSnapshot(services, events, logger, messages = servic
|
|
|
124
130
|
sessionId: session.sessionId,
|
|
125
131
|
sessionFile: session.sessionFile,
|
|
126
132
|
name: typeof manager?.getSessionName === "function" ? (manager.getSessionName() ?? undefined) : undefined,
|
|
133
|
+
forked: typeof manager?.getHeader === "function" && typeof manager.getHeader()?.parentSession === "string",
|
|
127
134
|
messages,
|
|
128
135
|
entries: typeof manager?.getEntries === "function" ? manager.getEntries() : [],
|
|
129
136
|
events,
|
|
@@ -673,6 +680,106 @@ function gitCommand(cwd, args, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
673
680
|
});
|
|
674
681
|
});
|
|
675
682
|
}
|
|
683
|
+
function ignoredWorkspaceDirectory(name) {
|
|
684
|
+
return IGNORED_WORKSPACE_DIRECTORIES.has(process.platform === "win32" || process.platform === "darwin" ? name.toLowerCase() : name);
|
|
685
|
+
}
|
|
686
|
+
async function existingWorkspaceFiles(root, candidates, truncated) {
|
|
687
|
+
const canonicalRoot = await realpath(resolve(root));
|
|
688
|
+
const paths = [];
|
|
689
|
+
for (let start = 0; start < candidates.length && paths.length < MAX_WORKSPACE_FILES; start += 64) {
|
|
690
|
+
const batch = candidates.slice(start, start + 64);
|
|
691
|
+
const existing = await Promise.all(batch.map(async (path) => {
|
|
692
|
+
const target = resolve(canonicalRoot, path);
|
|
693
|
+
if (escapesRoot(relative(canonicalRoot, target)))
|
|
694
|
+
return undefined;
|
|
695
|
+
try {
|
|
696
|
+
const canonical = await realpath(target);
|
|
697
|
+
if (escapesRoot(relative(canonicalRoot, canonical)) || !(await stat(canonical)).isFile())
|
|
698
|
+
return undefined;
|
|
699
|
+
return path;
|
|
700
|
+
}
|
|
701
|
+
catch {
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
704
|
+
}));
|
|
705
|
+
for (const path of existing) {
|
|
706
|
+
if (path !== undefined)
|
|
707
|
+
paths.push(path);
|
|
708
|
+
if (paths.length >= MAX_WORKSPACE_FILES)
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return { paths: paths.sort(), truncated: truncated || paths.length >= MAX_WORKSPACE_FILES };
|
|
713
|
+
}
|
|
714
|
+
async function gitWorkspaceFiles(root) {
|
|
715
|
+
const result = await gitCommand(root, ["ls-files", "--cached", "--others", "--exclude-standard", "--deduplicate", "-z"]);
|
|
716
|
+
if (result.code !== 0 && result.terminated !== "output-limit") {
|
|
717
|
+
if (/not a git repository/iu.test(result.stderr))
|
|
718
|
+
return undefined;
|
|
719
|
+
throw new Error(result.terminated ? gitTerminationMessage("ls-files", result.terminated) : result.stderr.trim() || "Unable to list workspace files");
|
|
720
|
+
}
|
|
721
|
+
const lastBoundary = result.stdout.lastIndexOf("\0");
|
|
722
|
+
const completeOutput = result.terminated === "output-limit" ? result.stdout.slice(0, lastBoundary + 1) : result.stdout;
|
|
723
|
+
const allCandidates = [...new Set(completeOutput.split("\0").filter((path) => path !== "" && !path.includes("\uFFFD")))];
|
|
724
|
+
const candidates = allCandidates.slice(0, MAX_WORKSPACE_FILE_CANDIDATES);
|
|
725
|
+
return existingWorkspaceFiles(root, candidates, result.terminated === "output-limit" || allCandidates.length > candidates.length);
|
|
726
|
+
}
|
|
727
|
+
async function walkedWorkspaceFiles(root) {
|
|
728
|
+
const canonicalRoot = await realpath(resolve(root));
|
|
729
|
+
const pending = [{ directory: canonicalRoot, depth: 0 }];
|
|
730
|
+
const paths = [];
|
|
731
|
+
let directories = 0;
|
|
732
|
+
let scanned = 0;
|
|
733
|
+
let truncated = false;
|
|
734
|
+
while (pending.length > 0 && paths.length < MAX_WORKSPACE_FILES && directories < MAX_WORKSPACE_DIRECTORIES && scanned < MAX_WORKSPACE_FILE_CANDIDATES) {
|
|
735
|
+
const current = pending.shift();
|
|
736
|
+
if (current === undefined)
|
|
737
|
+
break;
|
|
738
|
+
directories += 1;
|
|
739
|
+
try {
|
|
740
|
+
const handle = await opendir(current.directory);
|
|
741
|
+
for await (const entry of handle) {
|
|
742
|
+
scanned += 1;
|
|
743
|
+
if (scanned > MAX_WORKSPACE_FILE_CANDIDATES) {
|
|
744
|
+
truncated = true;
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
const name = entry.name;
|
|
748
|
+
if (name.includes("\uFFFD")) {
|
|
749
|
+
truncated = true;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
const target = join(current.directory, name);
|
|
753
|
+
if (entry.isSymbolicLink())
|
|
754
|
+
continue;
|
|
755
|
+
if (entry.isFile()) {
|
|
756
|
+
paths.push(relative(canonicalRoot, target).split(sep).join("/"));
|
|
757
|
+
if (paths.length >= MAX_WORKSPACE_FILES) {
|
|
758
|
+
truncated = true;
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
else if (entry.isDirectory() && !ignoredWorkspaceDirectory(name)) {
|
|
763
|
+
if (current.depth >= MAX_WORKSPACE_DEPTH)
|
|
764
|
+
truncated = true;
|
|
765
|
+
else
|
|
766
|
+
pending.push({ directory: target, depth: current.depth + 1 });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
catch (error) {
|
|
771
|
+
if (current.directory === canonicalRoot)
|
|
772
|
+
throw error;
|
|
773
|
+
truncated = true;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (pending.length > 0 || directories >= MAX_WORKSPACE_DIRECTORIES || scanned >= MAX_WORKSPACE_FILE_CANDIDATES)
|
|
777
|
+
truncated = true;
|
|
778
|
+
return { paths: paths.sort(), truncated };
|
|
779
|
+
}
|
|
780
|
+
async function workspaceFiles(root) {
|
|
781
|
+
return (await gitWorkspaceFiles(root)) ?? walkedWorkspaceFiles(root);
|
|
782
|
+
}
|
|
676
783
|
function probeEveryApiCliAuth() {
|
|
677
784
|
const executable = process.env.EVERYAPI_CLI_PATH?.trim() || "everyapi";
|
|
678
785
|
return new Promise((resolveStatus) => {
|
|
@@ -774,6 +881,25 @@ export default {
|
|
|
774
881
|
};
|
|
775
882
|
const disposePluginPanels = registerPluginPanels(context, services);
|
|
776
883
|
let busy = false;
|
|
884
|
+
let workspaceFileCache;
|
|
885
|
+
let workspaceFileRequest;
|
|
886
|
+
const readWorkspaceFiles = (cwd) => {
|
|
887
|
+
if (workspaceFileCache?.cwd === cwd && workspaceFileCache.expiresAt > Date.now())
|
|
888
|
+
return Promise.resolve(workspaceFileCache.catalogue);
|
|
889
|
+
if (workspaceFileRequest?.cwd === cwd)
|
|
890
|
+
return workspaceFileRequest.promise;
|
|
891
|
+
const promise = workspaceFiles(cwd)
|
|
892
|
+
.then((catalogue) => {
|
|
893
|
+
workspaceFileCache = { cwd, expiresAt: Date.now() + WORKSPACE_FILE_CACHE_MS, catalogue };
|
|
894
|
+
return catalogue;
|
|
895
|
+
})
|
|
896
|
+
.finally(() => {
|
|
897
|
+
if (workspaceFileRequest?.promise === promise)
|
|
898
|
+
workspaceFileRequest = undefined;
|
|
899
|
+
});
|
|
900
|
+
workspaceFileRequest = { cwd, promise };
|
|
901
|
+
return promise;
|
|
902
|
+
};
|
|
777
903
|
const events = [];
|
|
778
904
|
const eventClients = new Set();
|
|
779
905
|
const initialRunAt = Date.now();
|
|
@@ -1537,6 +1663,21 @@ export default {
|
|
|
1537
1663
|
sendJson(response, 200, { items, ...(status.truncated ? { truncated: true } : {}) });
|
|
1538
1664
|
},
|
|
1539
1665
|
});
|
|
1666
|
+
const disposeWorkspaceFiles = services.webServer.register({
|
|
1667
|
+
path: "/api/workspace/files",
|
|
1668
|
+
async handler(_request, response) {
|
|
1669
|
+
try {
|
|
1670
|
+
const catalogue = await readWorkspaceFiles(activeCwd(services));
|
|
1671
|
+
sendJson(response, 200, {
|
|
1672
|
+
items: catalogue.paths.map((path) => ({ path, status: "", label: "workspace" })),
|
|
1673
|
+
truncated: catalogue.truncated,
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
catch (error) {
|
|
1677
|
+
sendJson(response, 500, { error: errorText(error) });
|
|
1678
|
+
}
|
|
1679
|
+
},
|
|
1680
|
+
});
|
|
1540
1681
|
const disposeFileDiff = services.webServer.register({
|
|
1541
1682
|
path: "/api/files/diff",
|
|
1542
1683
|
async handler(request, response) {
|
|
@@ -2233,6 +2374,7 @@ export default {
|
|
|
2233
2374
|
modified: item.modified,
|
|
2234
2375
|
messageCount: item.messageCount,
|
|
2235
2376
|
firstMessage: item.firstMessage,
|
|
2377
|
+
forked: typeof item.parentSessionPath === "string" && item.parentSessionPath !== "",
|
|
2236
2378
|
archived: metadata[item.path]?.archived === true,
|
|
2237
2379
|
pinned: metadata[item.path]?.pinned === true,
|
|
2238
2380
|
})),
|
|
@@ -2270,6 +2412,7 @@ export default {
|
|
|
2270
2412
|
disposeWorkspaces();
|
|
2271
2413
|
disposePickDirectory();
|
|
2272
2414
|
disposeFiles();
|
|
2415
|
+
disposeWorkspaceFiles();
|
|
2273
2416
|
disposeFileDiff();
|
|
2274
2417
|
disposeFileCommit();
|
|
2275
2418
|
disposeFileRevert();
|