pi-crew 0.1.22 → 0.1.24

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -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) this.input.onJsonEvent?.(compact.event);
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()) this.input.onStdoutLine?.(compact.displayLine);
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 6;
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
- saveRunTasks(manifest, tasks);
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
- saveRunTasks(manifest, tasks);
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
- fs.writeFileSync(tempPath, content, "utf-8");
8
- fs.renameSync(tempPath, filePath);
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
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