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.
- package/CHANGELOG.md +28 -0
- package/README.md +42 -3
- package/package.json +3 -3
- package/schema.json +2 -1
- package/src/agents/discover-agents.ts +1 -1
- package/src/config/config.ts +81 -2
- package/src/extension/async-notifier.ts +26 -4
- package/src/extension/import-index.ts +1 -1
- package/src/extension/notification-sink.ts +1 -1
- package/src/extension/register.ts +23 -7
- package/src/extension/registration/subagent-tools.ts +11 -6
- package/src/extension/result-watcher.ts +6 -1
- package/src/extension/team-tool/api.ts +65 -8
- package/src/extension/team-tool/doctor.ts +2 -2
- package/src/observability/metric-sink.ts +1 -1
- package/src/runtime/child-pi.ts +2 -1
- package/src/runtime/crew-agent-records.ts +5 -4
- package/src/runtime/diagnostic-export.ts +2 -16
- package/src/runtime/live-session-runtime.ts +12 -6
- package/src/runtime/manifest-cache.ts +10 -1
- package/src/runtime/sidechain-output.ts +2 -1
- package/src/runtime/subagent-manager.ts +6 -1
- package/src/runtime/task-runner/live-executor.ts +3 -0
- package/src/runtime/team-runner.ts +74 -3
- package/src/schema/config-schema.ts +1 -0
- package/src/state/artifact-store.ts +4 -2
- package/src/state/event-log.ts +2 -1
- package/src/state/jsonl-writer.ts +3 -1
- package/src/state/mailbox.ts +4 -3
- package/src/state/types.ts +12 -0
- package/src/teams/discover-teams.ts +1 -1
- package/src/utils/paths.ts +3 -2
- package/src/utils/redaction.ts +41 -0
- package/src/workflows/discover-workflows.ts +1 -1
package/src/state/mailbox.ts
CHANGED
|
@@ -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 });
|
package/src/state/types.ts
CHANGED
|
@@ -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.
|
|
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));
|
package/src/utils/paths.ts
CHANGED
|
@@ -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.
|
|
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));
|