pi-crew 0.1.38 → 0.1.40

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.
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { TeamRunManifest } from "./types.ts";
4
4
  import { resolveRealContainedPath } from "../utils/safe-paths.ts";
5
+ import { redactSecrets } from "../utils/redaction.ts";
5
6
 
6
7
  export type MailboxDirection = "inbox" | "outbox";
7
8
  export type MailboxMessageStatus = "queued" | "delivered" | "acknowledged";
@@ -190,7 +191,7 @@ export function readDeliveryState(manifest: TeamRunManifest): MailboxDeliverySta
190
191
 
191
192
  function writeDeliveryState(manifest: TeamRunManifest, state: MailboxDeliveryState): void {
192
193
  ensureRunMailbox(manifest);
193
- fs.writeFileSync(deliveryFile(manifest, true), `${JSON.stringify(state, null, 2)}\n`, "utf-8");
194
+ fs.writeFileSync(deliveryFile(manifest, true), `${JSON.stringify(redactSecrets(state), null, 2)}\n`, "utf-8");
194
195
  }
195
196
 
196
197
  export function appendMailboxMessage(manifest: TeamRunManifest, message: Omit<MailboxMessage, "id" | "runId" | "createdAt" | "status"> & { id?: string; status?: MailboxMessageStatus }): MailboxMessage {
@@ -208,7 +209,7 @@ export function appendMailboxMessage(manifest: TeamRunManifest, message: Omit<Ma
208
209
  status: message.status ?? "queued",
209
210
  taskId: message.taskId,
210
211
  };
211
- fs.appendFileSync(mailboxFile(manifest, complete.direction, complete.taskId), `${JSON.stringify(complete)}\n`, "utf-8");
212
+ fs.appendFileSync(mailboxFile(manifest, complete.direction, complete.taskId), `${JSON.stringify(redactSecrets(complete))}\n`, "utf-8");
212
213
  const delivery = readDeliveryState(manifest);
213
214
  delivery.messages[complete.id] = complete.status;
214
215
  delivery.updatedAt = createdAt;
@@ -249,7 +250,7 @@ export function validateMailbox(manifest: TeamRunManifest, options: { repair?: b
249
250
  const parsed = JSON.parse(line) as unknown;
250
251
  const message = parseMailboxMessage(parsed, direction);
251
252
  if (!message) throw new Error("invalid message schema");
252
- validLines.push(JSON.stringify(message));
253
+ validLines.push(JSON.stringify(redactSecrets(message)));
253
254
  } catch (error) {
254
255
  const message = error instanceof Error ? error.message : String(error);
255
256
  issues.push({ level: "error", path: filePath, message });
@@ -81,6 +81,17 @@ export interface AsyncRunState {
81
81
  spawnedAt: string;
82
82
  }
83
83
 
84
+ export interface PlanApprovalState {
85
+ required: boolean;
86
+ status: "pending" | "approved" | "cancelled";
87
+ requestedAt: string;
88
+ updatedAt: string;
89
+ approvedAt?: string;
90
+ cancelledAt?: string;
91
+ planTaskId?: string;
92
+ planArtifactPath?: string;
93
+ }
94
+
84
95
  export interface TeamRunManifest {
85
96
  schemaVersion: 1;
86
97
  runId: string;
@@ -98,6 +109,7 @@ export interface TeamRunManifest {
98
109
  eventsPath: string;
99
110
  artifacts: ArtifactDescriptor[];
100
111
  async?: AsyncRunState;
112
+ planApproval?: PlanApprovalState;
101
113
  summary?: string;
102
114
  policyDecisions?: PolicyDecision[];
103
115
  }
@@ -109,7 +109,7 @@ export function discoverTeams(cwd: string): TeamDiscoveryResult {
109
109
 
110
110
  export function allTeams(discovery: TeamDiscoveryResult): TeamConfig[] {
111
111
  const byName = new Map<string, TeamConfig>();
112
- for (const team of [...discovery.builtin, ...discovery.user, ...discovery.project]) {
112
+ for (const team of [...discovery.project, ...discovery.builtin, ...discovery.user]) {
113
113
  byName.set(team.name, team);
114
114
  }
115
115
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
@@ -29,14 +29,15 @@ export function findRepoRoot(cwd: string): string | undefined {
29
29
  let current = path.resolve(cwd);
30
30
  const root = path.parse(current).root;
31
31
  const home = path.resolve(os.homedir());
32
+ const tempRoot = path.resolve(os.tmpdir());
32
33
  while (current !== root) {
33
- if (current === home) return undefined;
34
+ if (current === home || current === tempRoot) return undefined;
34
35
  if (hasProjectMarker(current)) return current;
35
36
  const parent = path.dirname(current);
36
37
  if (parent === current) break;
37
38
  current = parent;
38
39
  }
39
- if (current === home) return undefined;
40
+ if (current === home || current === tempRoot) return undefined;
40
41
  if (hasProjectMarker(root)) return root;
41
42
  return undefined;
42
43
  }
@@ -0,0 +1,41 @@
1
+ const SECRET_KEY_PATTERN = /(?:^|[_.-])(token|api[-_]?key|password|passwd|secret|credential|authorization|private[-_]?key)(?:$|[_.-])/i;
2
+ const INLINE_SECRET_PATTERN = /(^|[\s,{])(([A-Za-z0-9_.-]*(?:api[-_]?key|token|password|passwd|secret|credential|authorization|private[-_]?key)[A-Za-z0-9_.-]*)\s*[=:]\s*)([^\s,;"'}]+)/gi;
3
+ const AUTH_HEADER_PATTERN = /\b(Authorization\s*:\s*(?:Bearer|Basic|Token)?\s*)([^\r\n]+)/gi;
4
+ const BEARER_PATTERN = /\b(Bearer\s+)([A-Za-z0-9._~+/=-]{8,})\b/g;
5
+ const PEM_PRIVATE_KEY_PATTERN = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
6
+
7
+ function isRecord(value: unknown): value is Record<string, unknown> {
8
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
9
+ }
10
+
11
+ function isSecretKey(keyName: string): boolean {
12
+ return SECRET_KEY_PATTERN.test(keyName) || /^(token|apiKey|api_key|password|secret|credential|authorization|privateKey|private_key)$/i.test(keyName);
13
+ }
14
+
15
+ export function redactSecretString(value: string): string {
16
+ return value
17
+ .replace(PEM_PRIVATE_KEY_PATTERN, "***")
18
+ .replace(AUTH_HEADER_PATTERN, "$1***")
19
+ .replace(BEARER_PATTERN, "$1***")
20
+ .replace(INLINE_SECRET_PATTERN, "$1$2***");
21
+ }
22
+
23
+ export function redactSecrets(value: unknown, keyName = ""): unknown {
24
+ if (keyName && isSecretKey(keyName)) return "***";
25
+ if (typeof value === "string") return redactSecretString(value);
26
+ if (Array.isArray(value)) return value.map((item) => redactSecrets(item));
27
+ if (isRecord(value)) {
28
+ const output: Record<string, unknown> = {};
29
+ for (const [key, entry] of Object.entries(value)) output[key] = redactSecrets(entry, key);
30
+ return output;
31
+ }
32
+ return value;
33
+ }
34
+
35
+ export function redactJsonLine(line: string): string {
36
+ try {
37
+ return JSON.stringify(redactSecrets(JSON.parse(line) as unknown));
38
+ } catch {
39
+ return redactSecretString(line);
40
+ }
41
+ }
@@ -129,7 +129,7 @@ export function discoverWorkflows(cwd: string): WorkflowDiscoveryResult {
129
129
 
130
130
  export function allWorkflows(discovery: WorkflowDiscoveryResult): WorkflowConfig[] {
131
131
  const byName = new Map<string, WorkflowConfig>();
132
- for (const workflow of [...discovery.builtin, ...discovery.user, ...discovery.project]) {
132
+ for (const workflow of [...discovery.project, ...discovery.builtin, ...discovery.user]) {
133
133
  byName.set(workflow.name, workflow);
134
134
  }
135
135
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));