@staix/agent-hub 0.12.26 → 0.12.28
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/CHANGELOG.md +13 -0
- package/README.md +3 -3
- package/docs/agent-notes/bus.md +1 -0
- package/docs/agent-notes/tests.md +1 -0
- package/docs/operations.md +119 -27
- package/docs/quickstart.md +1 -1
- package/docs/security.md +34 -6
- package/docs/specs/2026-09-19-agent-hub-design.md +111 -27
- package/docs/specs/2026-10-12-office-view-design.md +55 -11
- package/docs/verification/2026-10-15-0.12.27.md +31 -0
- package/docs/verification/assets/391-pet-window-spike.png +0 -0
- package/docs/verified.json +11 -11
- package/package.json +3 -1
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +26 -5
- package/src/adapters/claude-channel.ts +26 -4
- package/src/adapters/pi.ts +12 -10
- package/src/cli/help.ts +2 -1
- package/src/cli/identity.ts +2 -0
- package/src/cli/main.ts +21 -6
- package/src/cli/pet-config.ts +1 -1
- package/src/cli/preview.ts +7 -1
- package/src/hub/bus.ts +9 -0
- package/src/hub/daemon.ts +30 -7
- package/src/hub/ui.ts +1 -3
- package/src/pet/config.ts +42 -5
- package/src/pet/window.html +114 -0
- package/src/pet/window.jxa.js +122 -0
- package/src/pet/window.ts +154 -0
- package/src/pi/session.ts +28 -0
- package/src/pi/tool-receipts.ts +1 -0
- package/src/ui/index.html +318 -129
- package/src/ui/office-model.ts +12 -12
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFileSync, openSync, closeSync, fsyncSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join, relative, resolve, dirname } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { realPath } from "../hub/project.ts";
|
|
5
|
+
import { atomicPrivateJSON } from "../hub/recovery-store.ts";
|
|
6
|
+
|
|
7
|
+
/** One validation boundary for ordinary starts and explicit preserved-history selection. */
|
|
8
|
+
export function validatePiSession(stateDir: string, cwd: string, sessionFile: string, sessionId?: string): { sessionFile: string; sessionId: string } {
|
|
9
|
+
const file = realPath(sessionFile), sessions = realPath(join(stateDir, "pi-sessions"));
|
|
10
|
+
const rel = relative(sessions, file);
|
|
11
|
+
if (!rel || rel.startsWith("..") || resolve(sessions, rel) !== file) throw new Error("Pi session file is outside the managed project session directory");
|
|
12
|
+
const header = JSON.parse(readFileSync(file, "utf8").split("\n", 1)[0]!);
|
|
13
|
+
if (header.type !== "session" || typeof header.id !== "string" || !header.id || typeof header.cwd !== "string" || realPath(header.cwd) !== realPath(cwd) || (sessionId && header.id !== sessionId)) throw new Error("Pi session header does not match the requested project/session");
|
|
14
|
+
return { sessionFile: file, sessionId: header.id };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The private selection record must reach storage before any prior owner is revoked. No transcript bodies. */
|
|
18
|
+
export function recordPiReplacement(stateDir: string, instanceId: string, previous: Record<string, unknown>, selected: { sessionFile: string; sessionId: string }): string {
|
|
19
|
+
const directory = join(stateDir, "pi-session-replacements");
|
|
20
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
21
|
+
const file = join(directory, `${randomUUID()}.json`);
|
|
22
|
+
atomicPrivateJSON(file, { schema: 1, at: Date.now(), instanceId, previous, selected });
|
|
23
|
+
for (const path of [file, directory, dirname(directory)]) {
|
|
24
|
+
const fd = openSync(path, "r");
|
|
25
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
26
|
+
}
|
|
27
|
+
return file;
|
|
28
|
+
}
|
package/src/pi/tool-receipts.ts
CHANGED
|
@@ -64,6 +64,7 @@ export class PiToolReceipts {
|
|
|
64
64
|
return work;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
get unsettled(): boolean { return this.pending.size > 0 || !!this.db.query("SELECT 1 FROM pi_tool_receipts WHERE state != 'done' LIMIT 1").get(); }
|
|
67
68
|
get inFlight(): number { return this.pending.size; }
|
|
68
69
|
async drain(): Promise<void> { await Promise.allSettled(this.pending.values()); }
|
|
69
70
|
|