@pi-harness/pi-harness 0.1.89 → 0.1.91
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/{index-9WCj0TsV.js → index-Dg7qqI2I.js} +1 -1
- package/apps/web/dist/index.html +1 -1
- package/apps/web/package.json +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.js +16 -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 +16 -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 +13 -1
- package/packages/api-gateway/test/index.test.ts +76 -0
- package/packages/bundle-web-app/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/host-webserver/package.json +1 -1
|
@@ -2167,7 +2167,10 @@ export default {
|
|
|
2167
2167
|
}
|
|
2168
2168
|
events.length = 0;
|
|
2169
2169
|
}
|
|
2170
|
-
|
|
2170
|
+
// A newly created active session may not have a JSONL file yet: Pi defers persistence until the first entry. Treat that missing file as already deleted, while preserving failures for unexpected paths such as directories.
|
|
2171
|
+
await unlink(path).catch((error: unknown) => {
|
|
2172
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
2173
|
+
});
|
|
2171
2174
|
await mutateSessionMetadata(manager, context.logger, (metadata) => {
|
|
2172
2175
|
delete metadata[path];
|
|
2173
2176
|
});
|
|
@@ -2394,6 +2397,15 @@ export default {
|
|
|
2394
2397
|
sendJson(response, 400, { error: "Invalid session path" });
|
|
2395
2398
|
return;
|
|
2396
2399
|
}
|
|
2400
|
+
// Pi defers creating the JSONL file for a new empty session until its first entry. Exporting that active session is still a durable user action, so materialize its in-memory header and entries before reading it; missing non-active paths remain a 404.
|
|
2401
|
+
if (path === services.runtime.session.sessionFile && !existsSync(path)) {
|
|
2402
|
+
try {
|
|
2403
|
+
persistSessionBeforeFirstAssistant(manager);
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
// Another tab may win the first-persistence race between existsSync and writeFileSync; its file is the same export target, so continue with the read.
|
|
2406
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2397
2409
|
const content = await readFile(path, "utf8");
|
|
2398
2410
|
response.writeHead(200, {
|
|
2399
2411
|
"content-type": "application/x-ndjson; charset=utf-8",
|
|
@@ -3762,6 +3762,82 @@ describe("API gateway plugin", () => {
|
|
|
3762
3762
|
expect(JSON.parse(await readFile(join(sessionDir, ".pi-harness-session-meta.json"), "utf8"))).toEqual({});
|
|
3763
3763
|
});
|
|
3764
3764
|
|
|
3765
|
+
test("deletes an empty active session whose JSONL file has not been persisted yet", async () => {
|
|
3766
|
+
const context = new Context();
|
|
3767
|
+
contexts.push(context);
|
|
3768
|
+
const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-empty-session-delete-"));
|
|
3769
|
+
temporaryDirectories.push(directory);
|
|
3770
|
+
const sessionDir = join(directory, "sessions");
|
|
3771
|
+
await mkdir(sessionDir);
|
|
3772
|
+
await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
|
|
3773
|
+
const target = join(sessionDir, "2026-08-30T00-00-00-000Z_empty.jsonl");
|
|
3774
|
+
const manager = SessionManager.create("/tmp", sessionDir);
|
|
3775
|
+
let activePath: string | undefined = target;
|
|
3776
|
+
const session = {
|
|
3777
|
+
sessionId: "empty-session",
|
|
3778
|
+
get sessionFile() {
|
|
3779
|
+
return activePath;
|
|
3780
|
+
},
|
|
3781
|
+
messages: [],
|
|
3782
|
+
isStreaming: false,
|
|
3783
|
+
sessionManager: manager,
|
|
3784
|
+
extensionRunner: { setUIContext() {} },
|
|
3785
|
+
subscribe: () => () => {},
|
|
3786
|
+
};
|
|
3787
|
+
const sessionRuntime = {
|
|
3788
|
+
newSession() {
|
|
3789
|
+
activePath = undefined;
|
|
3790
|
+
return Promise.resolve({ cancelled: false });
|
|
3791
|
+
},
|
|
3792
|
+
};
|
|
3793
|
+
context.provide("piRuntime", { session, sessionRuntime, prompt: () => Promise.resolve() } as never);
|
|
3794
|
+
context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
|
|
3795
|
+
context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
|
|
3796
|
+
await context.plugin(apiPlugin);
|
|
3797
|
+
|
|
3798
|
+
const response = await fetch(context.webServer.url + "/api/session/delete", {
|
|
3799
|
+
method: "POST",
|
|
3800
|
+
headers: { "content-type": "application/json" },
|
|
3801
|
+
body: JSON.stringify({ path: target, confirm: true }),
|
|
3802
|
+
});
|
|
3803
|
+
expect(response.status).toBe(200);
|
|
3804
|
+
await expect(response.json()).resolves.toMatchObject({ deleted: true, path: target });
|
|
3805
|
+
await expect(stat(target)).rejects.toMatchObject({ code: "ENOENT" });
|
|
3806
|
+
});
|
|
3807
|
+
|
|
3808
|
+
test("exports an empty active session by persisting its in-memory header", async () => {
|
|
3809
|
+
const context = new Context();
|
|
3810
|
+
contexts.push(context);
|
|
3811
|
+
const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-empty-session-export-"));
|
|
3812
|
+
temporaryDirectories.push(directory);
|
|
3813
|
+
const sessionDir = join(directory, "sessions");
|
|
3814
|
+
await mkdir(sessionDir);
|
|
3815
|
+
await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
|
|
3816
|
+
const manager = SessionManager.create("/tmp", sessionDir);
|
|
3817
|
+
const target = manager.getSessionFile();
|
|
3818
|
+
if (!target) throw new Error("Expected a new session path");
|
|
3819
|
+
const session = {
|
|
3820
|
+
sessionId: manager.getSessionId(),
|
|
3821
|
+
get sessionFile() {
|
|
3822
|
+
return manager.getSessionFile();
|
|
3823
|
+
},
|
|
3824
|
+
messages: [],
|
|
3825
|
+
isStreaming: false,
|
|
3826
|
+
sessionManager: manager,
|
|
3827
|
+
subscribe: () => () => {},
|
|
3828
|
+
};
|
|
3829
|
+
context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
|
|
3830
|
+
context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
|
|
3831
|
+
context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
|
|
3832
|
+
await context.plugin(apiPlugin);
|
|
3833
|
+
|
|
3834
|
+
const response = await fetch(context.webServer.url + "/api/session/export");
|
|
3835
|
+
expect(response.status).toBe(200);
|
|
3836
|
+
expect(response.headers.get("content-type")).toContain("application/x-ndjson");
|
|
3837
|
+
expect(await response.text()).toContain('"type":"session"');
|
|
3838
|
+
await expect(stat(target)).resolves.toBeDefined();
|
|
3839
|
+
});
|
|
3840
|
+
|
|
3765
3841
|
test("keeps batch deletion going past a failing session and persists the metadata it did remove", async () => {
|
|
3766
3842
|
const context = new Context();
|
|
3767
3843
|
contexts.push(context);
|