pi-crew 0.9.46 → 0.9.48
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 +83 -0
- package/README.md +16 -2
- package/dist/build-meta.json +289 -164
- package/dist/index.mjs +1744 -2732
- package/dist/index.mjs.map +4 -4
- package/docs/decisions/2026-07-21-broker-phase4-default-on.md +77 -0
- package/docs/decisions/2026-07-21-broker-windows-perms.md +91 -0
- package/docs/decisions/2026-07-22-broker-phase4-gated-on.md +99 -0
- package/docs/decisions/README.md +3 -0
- package/docs/publishing.md +29 -0
- package/package.json +3 -1
- package/scripts/build-bundle.mjs +7 -0
- package/scripts/postinstall.mjs +60 -1
- package/scripts/pty_probe.py +174 -0
- package/skills/real-test-pi-crew/SKILL.md +659 -0
- package/src/config/config.ts +42 -1
- package/src/config/defaults.ts +45 -1
- package/src/config/types.ts +19 -0
- package/src/extension/register.ts +6 -1
- package/src/extension/registration/context-builder.ts +4 -0
- package/src/extension/registration/lifecycle-handlers.ts +166 -3
- package/src/extension/registration/registration-types.ts +9 -0
- package/src/prompt/prompt-runtime.ts +108 -0
- package/src/runtime/broker-issuer.ts +37 -0
- package/src/runtime/child-pi-spawn.ts +53 -0
- package/src/runtime/child-pi.ts +42 -11
- package/src/runtime/crew-broker-child.ts +88 -0
- package/src/runtime/crew-broker-client.ts +673 -0
- package/src/runtime/crew-broker-tokens.ts +84 -0
- package/src/runtime/crew-broker.ts +1276 -0
- package/src/runtime/dynamic-workflow-context.ts +7 -3
- package/src/runtime/dynamic-workflow-runner.ts +1 -1
- package/src/runtime/plan-templates.ts +8 -6
- package/src/schema/config-schema.ts +14 -0
- package/src/state/mailbox.ts +43 -0
- package/src/ui/key-utils.ts +42 -0
- package/src/ui/keybinding-map.ts +29 -3
- package/src/ui/run-dashboard.ts +28 -0
- package/src/ui/settings-overlay.ts +42 -22
- package/src/utils/ndjson.ts +115 -0
- package/src/utils/session-utils.ts +30 -0
- package/src/utils/socket-path.ts +127 -0
- package/workflows/default.workflow.md +1 -1
- package/workflows/fast-fix.workflow.md +1 -1
- package/workflows/plan-execute.workflow.md +1 -1
- package/workflows/review.workflow.md +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* socket-path.ts — Canonical broker Unix-socket / named-pipe path utilities.
|
|
3
|
+
*
|
|
4
|
+
* Moved out of the parallel-work stub `src/runtime/crew-broker-deps.ts`.
|
|
5
|
+
* The public surface (hashSessionId, getBrokerSocketPath,
|
|
6
|
+
* prepareBrokerSocketDir, removeStaleBrokerSocket) is preserved verbatim
|
|
7
|
+
* so importers can be updated with a single import-path change.
|
|
8
|
+
*
|
|
9
|
+
* No internal dependencies on other src/ modules — only Node built-ins.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import * as fsp from "node:fs/promises";
|
|
14
|
+
import * as net from "node:net";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
|
|
18
|
+
/** Default hash length for the short socket filename (8 hex chars). */
|
|
19
|
+
const DEFAULT_PATH_HASH_LEN = 8;
|
|
20
|
+
|
|
21
|
+
/** POSIX sun_path cap (108 bytes including the null terminator). Use 107 for the
|
|
22
|
+
* string portion of the path. */
|
|
23
|
+
const POSIX_SUN_PATH_BUDGET = 107;
|
|
24
|
+
|
|
25
|
+
/** SHA-256 hex prefix of `sessionId`. `length` defaults to 8; must be in [4, 32]. */
|
|
26
|
+
export function hashSessionId(sessionId: string, length: number = DEFAULT_PATH_HASH_LEN): string {
|
|
27
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
28
|
+
throw new Error("hashSessionId: sessionId must be a non-empty string");
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isInteger(length) || length < 4 || length > 32) {
|
|
31
|
+
throw new Error(`hashSessionId: length must be an integer in [4, 32] (got ${length})`);
|
|
32
|
+
}
|
|
33
|
+
const hex = createHash("sha256").update(sessionId, "utf8").digest("hex");
|
|
34
|
+
return hex.substring(0, length);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Resolve the broker endpoint for the given session.
|
|
38
|
+
*
|
|
39
|
+
* - POSIX: `${XDG_RUNTIME_DIR || os.tmpdir()}/pi-crew-<hash8>.sock` (dir
|
|
40
|
+
* 0700 enforced by `prepareBrokerSocketDir`; socket 0600 by the server).
|
|
41
|
+
* - Windows: `\\\\.\\pipe\\pi-crew-broker-<hash8>`.
|
|
42
|
+
* - Throws if the encoded POSIX path exceeds sun_path (108 bytes) — the
|
|
43
|
+
* caller cannot fix this without changing the hash length, so fail fast. */
|
|
44
|
+
export function getBrokerSocketPath(sessionId: string, platform: NodeJS.Platform = process.platform): string {
|
|
45
|
+
const hash = hashSessionId(sessionId);
|
|
46
|
+
if (platform === "win32") {
|
|
47
|
+
return `\\\\.\\pipe\\pi-crew-broker-${hash}`;
|
|
48
|
+
}
|
|
49
|
+
const base = process.env.XDG_RUNTIME_DIR || os.tmpdir();
|
|
50
|
+
const sock = path.join(base, `pi-crew-${hash}.sock`);
|
|
51
|
+
const encoded = Buffer.byteLength(sock, "utf8");
|
|
52
|
+
if (encoded > POSIX_SUN_PATH_BUDGET) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`broker socket path ${encoded} bytes exceeds sun_path budget (${POSIX_SUN_PATH_BUDGET}); check XDG_RUNTIME_DIR or use a shorter hash`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return sock;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Create the parent directory of a broker socket with mode 0700 (POSIX).
|
|
61
|
+
* Idempotent: if the directory already exists with the correct mode, leaves
|
|
62
|
+
* it alone. Refuses to operate on a symlink. Windows is a no-op (named pipes
|
|
63
|
+
* do not have an enclosing dir). */
|
|
64
|
+
export async function prepareBrokerSocketDir(sockPath: string): Promise<void> {
|
|
65
|
+
if (process.platform === "win32") return;
|
|
66
|
+
const dir = path.dirname(sockPath);
|
|
67
|
+
// mkdir with mode 0o700; recursive:true so nested paths work.
|
|
68
|
+
await fsp.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
69
|
+
// Tighten mode if it already existed (mkdir with mode ignores on existing).
|
|
70
|
+
try {
|
|
71
|
+
await fsp.chmod(dir, 0o700);
|
|
72
|
+
} catch {
|
|
73
|
+
// ENOENT or EPERM on non-POSIX — best-effort.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Connect-then-unlink stale socket (herdr pattern). If a live broker is
|
|
78
|
+
* listening, leave the endpoint intact (EADDRINUSE will surface on bind).
|
|
79
|
+
* If a stale file exists with no listener, remove it. If the path is a
|
|
80
|
+
* symlink, refuse rather than follow.
|
|
81
|
+
*
|
|
82
|
+
* Returns "removed" when the stale entry was unlinked, "kept" when a live
|
|
83
|
+
* listener was detected, "absent" when no entry existed, "refused"
|
|
84
|
+
* when the entry is a symlink. */
|
|
85
|
+
export async function removeStaleBrokerSocket(
|
|
86
|
+
sockPath: string,
|
|
87
|
+
probeTimeoutMs: number = 250,
|
|
88
|
+
): Promise<"removed" | "kept" | "absent" | "refused"> {
|
|
89
|
+
// Reject symlinks outright.
|
|
90
|
+
let st: Awaited<ReturnType<typeof fsp.lstat>>;
|
|
91
|
+
try {
|
|
92
|
+
st = await fsp.lstat(sockPath);
|
|
93
|
+
} catch (e) {
|
|
94
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
95
|
+
if (code === "ENOENT") return "absent";
|
|
96
|
+
throw e;
|
|
97
|
+
}
|
|
98
|
+
if (st.isSymbolicLink()) return "refused";
|
|
99
|
+
// Bound the probe: connect with a short timeout. If anything answers, treat as live.
|
|
100
|
+
const live = await new Promise<boolean>((resolve) => {
|
|
101
|
+
let settled = false;
|
|
102
|
+
const sock = net.createConnection(sockPath);
|
|
103
|
+
const finish = (v: boolean) => {
|
|
104
|
+
if (settled) return;
|
|
105
|
+
settled = true;
|
|
106
|
+
try {
|
|
107
|
+
sock.destroy();
|
|
108
|
+
} catch {
|
|
109
|
+
/* ignore */
|
|
110
|
+
}
|
|
111
|
+
resolve(v);
|
|
112
|
+
};
|
|
113
|
+
sock.once("connect", () => finish(true));
|
|
114
|
+
sock.once("error", () => finish(false));
|
|
115
|
+
setTimeout(() => finish(false), probeTimeoutMs);
|
|
116
|
+
});
|
|
117
|
+
if (live) return "kept";
|
|
118
|
+
// Stale: remove.
|
|
119
|
+
try {
|
|
120
|
+
await fsp.unlink(sockPath);
|
|
121
|
+
return "removed";
|
|
122
|
+
} catch (e) {
|
|
123
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
124
|
+
if (code === "ENOENT") return "absent";
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -28,4 +28,4 @@ dependsOn: execute
|
|
|
28
28
|
verify: true
|
|
29
29
|
|
|
30
30
|
Verify completion for: {goal}
|
|
31
|
-
Run
|
|
31
|
+
Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the changes. Do NOT re-run tests. Give PASS or FAIL with specific test evidence.
|
|
@@ -21,4 +21,4 @@ dependsOn: execute
|
|
|
21
21
|
verify: true
|
|
22
22
|
|
|
23
23
|
Verify the fix with available evidence.
|
|
24
|
-
Run
|
|
24
|
+
Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the fix. Do NOT re-run tests. Give PASS or FAIL with specific test evidence.
|
|
@@ -27,4 +27,4 @@ dependsOn: execute
|
|
|
27
27
|
verify: true
|
|
28
28
|
|
|
29
29
|
Verify completion for: {goal}
|
|
30
|
-
Run
|
|
30
|
+
Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with the changes. Do NOT re-run tests. Give PASS or FAIL with specific test evidence.
|
|
@@ -28,4 +28,4 @@ role: verifier
|
|
|
28
28
|
dependsOn: code-review, security-review
|
|
29
29
|
verify: true
|
|
30
30
|
|
|
31
|
-
Run
|
|
31
|
+
Run FAST checks ONCE (cache output to .crew/cache/): `npm run test:critical && npx tsc --noEmit` (completes in <60s). Do NOT run `npm run test:unit` or `npm test` — too slow (642 files, >4 min). Cross-reference cached output with reviewer and security-reviewer findings. Confirm each finding against real test output. Give PASS if findings match evidence, FAIL if critical findings are false positives or tests reveal new issues.
|