pi-crew 0.1.22 → 0.1.23
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/package.json
CHANGED
package/src/runtime/child-pi.ts
CHANGED
|
@@ -182,9 +182,13 @@ export class ChildPiLineObserver {
|
|
|
182
182
|
private emitLine(line: string): void {
|
|
183
183
|
if (!line.trim()) return;
|
|
184
184
|
const compact = compactChildPiLine(line);
|
|
185
|
-
if (compact.event !== undefined)
|
|
185
|
+
if (compact.event !== undefined) {
|
|
186
|
+
try { this.input.onJsonEvent?.(compact.event); } catch {}
|
|
187
|
+
}
|
|
186
188
|
if (compact.persistedLine) appendTranscript(this.input, compact.persistedLine);
|
|
187
|
-
if (compact.displayLine?.trim())
|
|
189
|
+
if (compact.displayLine?.trim()) {
|
|
190
|
+
try { this.input.onStdoutLine?.(compact.displayLine); } catch {}
|
|
191
|
+
}
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
|
|
@@ -15,7 +15,7 @@ export interface BatchConcurrencyDecision {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function defaultWorkflowConcurrency(workflowName: string): number {
|
|
18
|
-
if (workflowName === "parallel-research") return
|
|
18
|
+
if (workflowName === "parallel-research") return 4;
|
|
19
19
|
if (workflowName === "research") return 2;
|
|
20
20
|
if (workflowName === "implementation" || workflowName === "review" || workflowName === "default") return 2;
|
|
21
21
|
return 1;
|
|
@@ -4,7 +4,7 @@ import type { CrewLimitsConfig, CrewRuntimeConfig } from "../config/config.ts";
|
|
|
4
4
|
import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState, UsageState } from "../state/types.ts";
|
|
5
5
|
import { writeArtifact } from "../state/artifact-store.ts";
|
|
6
6
|
import { appendEvent } from "../state/event-log.ts";
|
|
7
|
-
import { saveRunManifest, saveRunTasks } from "../state/state-store.ts";
|
|
7
|
+
import { loadRunManifestById, saveRunManifest, saveRunTasks } from "../state/state-store.ts";
|
|
8
8
|
import { createTaskClaim } from "../state/task-claims.ts";
|
|
9
9
|
import { createWorkerHeartbeat, touchWorkerHeartbeat } from "./worker-heartbeat.ts";
|
|
10
10
|
import type { WorkflowStep } from "../workflows/workflow-config.ts";
|
|
@@ -112,6 +112,13 @@ function updateTask(tasks: TeamTaskState[], updated: TeamTaskState): TeamTaskSta
|
|
|
112
112
|
return tasks.map((task) => task.id === updated.id ? updated : task);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks: TeamTaskState[], updated: TeamTaskState): TeamTaskState[] {
|
|
116
|
+
const latest = loadRunManifestById(manifest.cwd, manifest.runId)?.tasks ?? fallbackTasks;
|
|
117
|
+
const merged = updateTask(latest, updated);
|
|
118
|
+
saveRunTasks(manifest, merged);
|
|
119
|
+
return merged;
|
|
120
|
+
}
|
|
121
|
+
|
|
115
122
|
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
116
123
|
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
117
124
|
}
|
|
@@ -268,7 +275,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
268
275
|
} as TeamTaskState;
|
|
269
276
|
let tasks = updateTask(input.tasks, task);
|
|
270
277
|
const runtimeKind = input.runtimeKind ?? (input.executeWorkers ? "child-process" : "scaffold");
|
|
271
|
-
|
|
278
|
+
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
272
279
|
upsertCrewAgent(manifest, recordFromTask(manifest, task, runtimeKind));
|
|
273
280
|
appendEvent(manifest.eventsPath, { type: "task.started", runId: manifest.runId, taskId: task.id, data: { role: task.role, agent: task.agent, runtime: runtimeKind, cwd: task.cwd, worktreePath: workspace.worktreePath, worktreeBranch: workspace.branch, worktreeReused: workspace.reused } });
|
|
274
281
|
const permissionMode = permissionForRole(task.role);
|
|
@@ -529,7 +536,7 @@ export async function runTeamTask(input: TaskRunnerInput): Promise<{ manifest: T
|
|
|
529
536
|
});
|
|
530
537
|
manifest = { ...manifest, updatedAt: new Date().toISOString(), artifacts: [...manifest.artifacts, promptArtifact, resultArtifact, inputsArtifact, coordinationArtifact, packetArtifact, verificationArtifact, startupArtifact, permissionArtifact, ...(sharedOutputArtifact ? [sharedOutputArtifact] : []), ...(logArtifact ? [logArtifact] : []), ...(transcriptArtifact ? [transcriptArtifact] : []), ...(diffArtifact ? [diffArtifact] : []), ...(diffStatArtifact ? [diffStatArtifact] : [])] };
|
|
531
538
|
saveRunManifest(manifest);
|
|
532
|
-
|
|
539
|
+
tasks = persistSingleTaskUpdate(manifest, tasks, task);
|
|
533
540
|
upsertCrewAgent(manifest, recordFromTask(manifest, task, runtimeKind));
|
|
534
541
|
appendEvent(manifest.eventsPath, { type: error ? "task.failed" : "task.completed", runId: manifest.runId, taskId: task.id, message: error });
|
|
535
542
|
return { manifest, tasks };
|
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
|
+
const RETRYABLE_RENAME_CODES = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
5
|
+
|
|
6
|
+
function sleepSync(ms: number): void {
|
|
7
|
+
const buffer = new SharedArrayBuffer(4);
|
|
8
|
+
Atomics.wait(new Int32Array(buffer), 0, 0, ms);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isRetryableRenameError(error: unknown): boolean {
|
|
12
|
+
return Boolean(error && typeof error === "object" && "code" in error && RETRYABLE_RENAME_CODES.has(String((error as NodeJS.ErrnoException).code)));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function __test__renameWithRetry(tempPath: string, filePath: string, retries = 20, rename: (oldPath: string, newPath: string) => void = fs.renameSync): void {
|
|
16
|
+
let lastError: unknown;
|
|
17
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
rename(tempPath, filePath);
|
|
20
|
+
return;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
lastError = error;
|
|
23
|
+
if (!isRetryableRenameError(error) || attempt === retries) break;
|
|
24
|
+
sleepSync(Math.min(250, 10 * 2 ** attempt));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
throw lastError;
|
|
28
|
+
}
|
|
29
|
+
|
|
4
30
|
export function atomicWriteFile(filePath: string, content: string): void {
|
|
5
31
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
6
|
-
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
7
|
-
|
|
8
|
-
|
|
32
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
33
|
+
try {
|
|
34
|
+
fs.writeFileSync(tempPath, content, "utf-8");
|
|
35
|
+
__test__renameWithRetry(tempPath, filePath);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
try { fs.rmSync(tempPath, { force: true }); } catch {}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
9
40
|
}
|
|
10
41
|
|
|
11
42
|
export function atomicWriteJson<T>(filePath: string, value: T): void {
|
|
@@ -3,7 +3,7 @@ name: parallel-research
|
|
|
3
3
|
description: Parallel research team for multi-project/source audits
|
|
4
4
|
workspaceMode: single
|
|
5
5
|
defaultWorkflow: parallel-research
|
|
6
|
-
maxConcurrency:
|
|
6
|
+
maxConcurrency: 4
|
|
7
7
|
triggers: đọc sâu, deep read, deep research, source audit, multiple projects, parallel research, pi-*
|
|
8
8
|
category: research
|
|
9
9
|
cost: cheap
|