pi-crew 0.9.1 → 0.9.2
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 +37 -0
- package/package.json +1 -1
- package/test-bugs-all.mjs +0 -89
- package/test-lastActivityAt.mjs +0 -167
- package/test-tp.mjs +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v0.9.2] — package cleanup: remove scratch scripts leaked into npm tarball (2026-06-22)
|
|
4
|
+
|
|
5
|
+
Patch release. **No code changes.** Purely a published-package hygiene fix.
|
|
6
|
+
|
|
7
|
+
### What was wrong
|
|
8
|
+
|
|
9
|
+
The `package.json` `files` field includes a root-level `*.mjs` glob, intended
|
|
10
|
+
for the legit `install.mjs` (postinstall script). It accidentally also picked
|
|
11
|
+
up three ad-hoc scratch scripts that were committed at the repo root during
|
|
12
|
+
earlier development:
|
|
13
|
+
|
|
14
|
+
- `test-tp.mjs` — a 12-line `tool-progress` format smoke print
|
|
15
|
+
- `test-bugs-all.mjs` — an 89-line string-matching bug-fix verifier
|
|
16
|
+
- `test-lastActivityAt.mjs` — a manual `node:test` for a heartbeat fallback
|
|
17
|
+
|
|
18
|
+
All three shipped in the published npm tarball (v0.9.0 and v0.9.1) even though
|
|
19
|
+
they are not part of `npm test` and have no runtime value for consumers.
|
|
20
|
+
|
|
21
|
+
### Fix
|
|
22
|
+
|
|
23
|
+
- `git rm`'d the three scratch scripts. The behavior they checked is covered by
|
|
24
|
+
the real unit suite (`test/unit/`), which does NOT ship to npm.
|
|
25
|
+
- After the fix, the only `.mjs` in the tarball is the intended `install.mjs`.
|
|
26
|
+
|
|
27
|
+
### Verification
|
|
28
|
+
|
|
29
|
+
- tsc: 0
|
|
30
|
+
- `npm pack --dry-run` confirms `install.mjs` is the only `.mjs` shipped
|
|
31
|
+
- regression suite (env-allowlist, goal-p1d-schema): 11/11 pass
|
|
32
|
+
- No behavior change — consumers see no functional difference
|
|
33
|
+
|
|
34
|
+
### Breaking changes
|
|
35
|
+
|
|
36
|
+
None.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
3
40
|
## [v0.9.1] — Windows essentials fix + cross-platform CI green (2026-06-22)
|
|
4
41
|
|
|
5
42
|
Patch release. No new features. Fixes a real Windows bug reported by a user,
|
package/package.json
CHANGED
package/test-bugs-all.mjs
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
console.log("=== PI-CREW BUG FIXES VERIFICATION ===\n");
|
|
5
|
-
|
|
6
|
-
let allPassed = true;
|
|
7
|
-
|
|
8
|
-
// Bug #17: Check killAsync is commented out
|
|
9
|
-
console.log("Bug #17: Background runner session shutdown fix");
|
|
10
|
-
const registerContent = fs.readFileSync("src/extension/register.ts", "utf-8");
|
|
11
|
-
const killAsyncMatch = registerContent.match(/\/\/\s*for\s*\(\s*const\s+manifest\s+of\s+manifestCache\.list\(50\)/);
|
|
12
|
-
if (killAsyncMatch) {
|
|
13
|
-
console.log(" ✅ killAsync loop is commented out");
|
|
14
|
-
} else if (registerContent.includes("for (const manifest of manifestCache.list(50))") && !registerContent.includes("// for (const manifest")) {
|
|
15
|
-
console.log(" ❌ killAsync loop is NOT commented out - BUG NOT FIXED");
|
|
16
|
-
allPassed = false;
|
|
17
|
-
} else {
|
|
18
|
-
console.log(" ✅ killAsync pattern not found (may have been refactored)");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Bug #18: Check stdio is ["ignore", "pipe", "pipe"]
|
|
22
|
-
console.log("\nBug #18: Child-pi stdin fix");
|
|
23
|
-
const childPiContent = fs.readFileSync("src/runtime/child-pi.ts", "utf-8");
|
|
24
|
-
const stdioMatch = childPiContent.match(/stdio:\s*\[\s*"ignore"\s*,\s*"pipe"\s*,\s*"pipe"\s*\]/);
|
|
25
|
-
if (stdioMatch) {
|
|
26
|
-
console.log(" ✅ stdio is ['ignore', 'pipe', 'pipe']");
|
|
27
|
-
} else if (childPiContent.includes('stdio: ["pipe", "pipe", "pipe"]')) {
|
|
28
|
-
console.log(" ❌ stdio is still ['pipe', 'pipe', 'pipe'] - BUG NOT FIXED");
|
|
29
|
-
allPassed = false;
|
|
30
|
-
} else {
|
|
31
|
-
console.log(" ⚠️ stdio pattern not found in expected format");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Bug #19: Check temp workspace cleanup
|
|
35
|
-
console.log("\nBug #19: Phantom runs temp workspace fix");
|
|
36
|
-
const runIndexContent = fs.readFileSync("src/extension/run-index.ts", "utf-8");
|
|
37
|
-
const tempDirCheck = runIndexContent.includes("isTempRoot") || runIndexContent.includes("tmpdir") || runIndexContent.includes("tmpDir");
|
|
38
|
-
const activeRunContent = fs.readFileSync("src/state/active-run-registry.ts", "utf-8");
|
|
39
|
-
const timeoutCheck = activeRunContent.includes("30 * 60 * 1000") || activeRunContent.includes("30*60*1000");
|
|
40
|
-
if (tempDirCheck && timeoutCheck) {
|
|
41
|
-
console.log(" ✅ Temp workspace detection and 30-min timeout present");
|
|
42
|
-
} else if (!tempDirCheck) {
|
|
43
|
-
console.log(" ❌ Temp workspace detection NOT found - BUG NOT FIXED");
|
|
44
|
-
allPassed = false;
|
|
45
|
-
} else if (!timeoutCheck) {
|
|
46
|
-
console.log(" ❌ 30-min timeout NOT found - BUG NOT FIXED");
|
|
47
|
-
allPassed = false;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Bug #20: Check needs_attention in completedIds
|
|
51
|
-
console.log("\nBug #20: Infinite retry loop fix");
|
|
52
|
-
const teamRunnerContent = fs.readFileSync("src/runtime/team-runner.ts", "utf-8");
|
|
53
|
-
const needsAttentionMatch = teamRunnerContent.match(/status\s*===\s*"needs_attention"/g);
|
|
54
|
-
if (needsAttentionMatch && needsAttentionMatch.length >= 3) {
|
|
55
|
-
console.log(" ✅ needs_attention status checks found (" + needsAttentionMatch.length + " places)");
|
|
56
|
-
} else {
|
|
57
|
-
console.log(" ❌ needs_attention status check NOT found or insufficient - BUG NOT FIXED");
|
|
58
|
-
allPassed = false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Check the specific completedIds fix
|
|
62
|
-
const completedIdsFix = teamRunnerContent.includes('status === "completed" || t.status === "needs_attention"');
|
|
63
|
-
if (completedIdsFix) {
|
|
64
|
-
console.log(" ✅ completedIds includes needs_attention");
|
|
65
|
-
} else {
|
|
66
|
-
console.log(" ❌ completedIds does NOT include needs_attention - BUG NOT FIXED");
|
|
67
|
-
allPassed = false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Check dist file
|
|
71
|
-
console.log("\n=== Checking dist/index.mjs ===");
|
|
72
|
-
if (fs.existsSync("dist/index.mjs")) {
|
|
73
|
-
const distContent = fs.readFileSync("dist/index.mjs", "utf-8");
|
|
74
|
-
const distNeedsAttention = distContent.includes('t2.status === "completed" || t2.status === "needs_attention"');
|
|
75
|
-
if (distNeedsAttention) {
|
|
76
|
-
console.log(" ✅ Bug #20 fix is in dist/index.mjs");
|
|
77
|
-
} else {
|
|
78
|
-
console.log(" ❌ Bug #20 fix NOT in dist/index.mjs - rebuild needed");
|
|
79
|
-
allPassed = false;
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
console.log(" ⚠️ dist/index.mjs not found - run npm run build first");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
console.log("\n" + "=".repeat(40));
|
|
86
|
-
console.log(allPassed ? "✅ ALL BUGS ARE FIXED" : "❌ SOME BUGS ARE NOT FIXED");
|
|
87
|
-
console.log("=".repeat(40));
|
|
88
|
-
|
|
89
|
-
process.exit(allPassed ? 0 : 1);
|
package/test-lastActivityAt.mjs
DELETED
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test for lastActivityAt fallback in heartbeat-watcher
|
|
3
|
-
* Verifies that tasks with stale heartbeat but recent lastActivityAt are not marked dead
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import test from "node:test";
|
|
7
|
-
import assert from "node:assert/strict";
|
|
8
|
-
import * as fs from "node:fs";
|
|
9
|
-
import * as os from "node:os";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import { createMetricRegistry } from "./src/observability/metric-registry.ts";
|
|
12
|
-
import { HeartbeatWatcher } from "./src/runtime/heartbeat-watcher.ts";
|
|
13
|
-
import { createRunManifest, saveRunTasks, updateRunStatus } from "./src/state/state-store.ts";
|
|
14
|
-
import { createManifestCache } from "./src/runtime/manifest-cache.ts";
|
|
15
|
-
|
|
16
|
-
const team = { name: "t", description: "", source: "test", filePath: "t", roles: [{ name: "r", agent: "a" }] };
|
|
17
|
-
const workflow = { name: "w", description: "", source: "test", filePath: "w", steps: [{ id: "s", role: "r", task: "x" }] };
|
|
18
|
-
|
|
19
|
-
test("HeartbeatWatcher uses lastActivityAt fallback - task NOT dead when heartbeat stale but activity recent", () => {
|
|
20
|
-
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-lastactivity-at-"));
|
|
21
|
-
try {
|
|
22
|
-
fs.writeFileSync(path.join(cwd, "package.json"), "{}", "utf-8");
|
|
23
|
-
const created = createRunManifest({ cwd, team, workflow, goal: "hb" });
|
|
24
|
-
const manifest = updateRunStatus(created.manifest, "running", "running");
|
|
25
|
-
|
|
26
|
-
// Create task with STALE heartbeat (old lastSeenAt) but RECENT lastActivityAt
|
|
27
|
-
// Heartbeat is from Jan 1, 2026 (stale - 10 minutes old)
|
|
28
|
-
// lastActivityAt is from Jan 1, 2026 00:08:00 (2 minutes old - within dead threshold of 5 minutes)
|
|
29
|
-
const tasksWithHeartbeat = created.tasks.map((task) => ({
|
|
30
|
-
...task,
|
|
31
|
-
status: "running",
|
|
32
|
-
heartbeat: { workerId: task.id, lastSeenAt: "2026-01-01T00:00:00.000Z", alive: true },
|
|
33
|
-
// Agent is still active - lastActivityAt is recent (within dead threshold)
|
|
34
|
-
agentProgress: {
|
|
35
|
-
lastActivityAt: "2026-01-01T00:08:00.000Z", // 2 minutes ago
|
|
36
|
-
currentTool: "working",
|
|
37
|
-
toolCount: 5,
|
|
38
|
-
tokens: 1000,
|
|
39
|
-
turns: 2
|
|
40
|
-
}
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
saveRunTasks(manifest, tasksWithHeartbeat);
|
|
44
|
-
const cache = createManifestCache(cwd, { watch: false, debounceMs: 0 });
|
|
45
|
-
const notifications = [];
|
|
46
|
-
let deadletters = 0;
|
|
47
|
-
const watcher = new HeartbeatWatcher({
|
|
48
|
-
cwd,
|
|
49
|
-
manifestCache: cache,
|
|
50
|
-
registry: createMetricRegistry(),
|
|
51
|
-
router: { enqueue: (n) => { notifications.push(n.id ?? ""); return true; } },
|
|
52
|
-
deadletterTickThreshold: 3,
|
|
53
|
-
onDeadletterTrigger: () => { deadletters += 1; }
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Simulate time at 00:10:00 - 10 minutes after heartbeat, 2 minutes after activity
|
|
57
|
-
// With fallback: activity age = 2 minutes < dead threshold (5 minutes) -> should be warn/stale, not dead
|
|
58
|
-
watcher.tick(Date.parse("2026-01-01T00:10:00.000Z"));
|
|
59
|
-
watcher.tick(Date.parse("2026-01-01T00:10:05.000Z"));
|
|
60
|
-
watcher.tick(Date.parse("2026-01-01T00:10:10.000Z"));
|
|
61
|
-
|
|
62
|
-
// Should NOT have any dead notifications because lastActivityAt is recent
|
|
63
|
-
assert.equal(notifications.length, 0, "Should NOT mark task dead when lastActivityAt is recent (within dead threshold)");
|
|
64
|
-
assert.equal(deadletters, 0, "Should NOT trigger deadletter when lastActivityAt is recent");
|
|
65
|
-
|
|
66
|
-
watcher.dispose();
|
|
67
|
-
cache.dispose();
|
|
68
|
-
} finally {
|
|
69
|
-
fs.rmSync(cwd, { recursive: true, force: true });
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("HeartbeatWatcher marks task dead when BOTH heartbeat and lastActivityAt are stale", () => {
|
|
74
|
-
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-both-stale-"));
|
|
75
|
-
try {
|
|
76
|
-
fs.writeFileSync(path.join(cwd, "package.json"), "{}", "utf-8");
|
|
77
|
-
const created = createRunManifest({ cwd, team, workflow, goal: "hb" });
|
|
78
|
-
const manifest = updateRunStatus(created.manifest, "running", "running");
|
|
79
|
-
|
|
80
|
-
// Create task with BOTH stale heartbeat AND stale lastActivityAt
|
|
81
|
-
// Heartbeat is from Jan 1, 2026 00:00:00 (10 minutes old)
|
|
82
|
-
// lastActivityAt is also from Jan 1, 2026 00:00:00 (also 10 minutes old - beyond dead threshold)
|
|
83
|
-
const tasksWithHeartbeat = created.tasks.map((task) => ({
|
|
84
|
-
...task,
|
|
85
|
-
status: "running",
|
|
86
|
-
heartbeat: { workerId: task.id, lastSeenAt: "2026-01-01T00:00:00.000Z", alive: true },
|
|
87
|
-
agentProgress: {
|
|
88
|
-
lastActivityAt: "2026-01-01T00:00:00.000Z", // 10 minutes old - beyond dead threshold
|
|
89
|
-
currentTool: "done",
|
|
90
|
-
toolCount: 5,
|
|
91
|
-
tokens: 1000,
|
|
92
|
-
turns: 2
|
|
93
|
-
}
|
|
94
|
-
}));
|
|
95
|
-
|
|
96
|
-
saveRunTasks(manifest, tasksWithHeartbeat);
|
|
97
|
-
const cache = createManifestCache(cwd, { watch: false, debounceMs: 0 });
|
|
98
|
-
const notifications = [];
|
|
99
|
-
let deadletters = 0;
|
|
100
|
-
const watcher = new HeartbeatWatcher({
|
|
101
|
-
cwd,
|
|
102
|
-
manifestCache: cache,
|
|
103
|
-
registry: createMetricRegistry(),
|
|
104
|
-
router: { enqueue: (n) => { notifications.push(n.id ?? ""); return true; } },
|
|
105
|
-
deadletterTickThreshold: 3,
|
|
106
|
-
onDeadletterTrigger: () => { deadletters += 1; }
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Simulate time at 00:10:00 - both heartbeat and activity are 10 minutes old
|
|
110
|
-
watcher.tick(Date.parse("2026-01-01T00:10:00.000Z"));
|
|
111
|
-
watcher.tick(Date.parse("2026-01-01T00:10:05.000Z"));
|
|
112
|
-
watcher.tick(Date.parse("2026-01-01T00:10:10.000Z"));
|
|
113
|
-
|
|
114
|
-
// SHOULD have dead notifications because BOTH are stale (> 5 minutes)
|
|
115
|
-
assert.ok(notifications.length > 0, "Should mark task dead when BOTH heartbeat and lastActivityAt are stale");
|
|
116
|
-
assert.ok(deadletters > 0, "Should trigger deadletter when BOTH are stale");
|
|
117
|
-
|
|
118
|
-
watcher.dispose();
|
|
119
|
-
cache.dispose();
|
|
120
|
-
} finally {
|
|
121
|
-
fs.rmSync(cwd, { recursive: true, force: true });
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("HeartbeatWatcher without lastActivityAt still marks stale heartbeat as dead", () => {
|
|
126
|
-
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-no-activity-"));
|
|
127
|
-
try {
|
|
128
|
-
fs.writeFileSync(path.join(cwd, "package.json"), "{}", "utf-8");
|
|
129
|
-
const created = createRunManifest({ cwd, team, workflow, goal: "hb" });
|
|
130
|
-
const manifest = updateRunStatus(created.manifest, "running", "running");
|
|
131
|
-
|
|
132
|
-
// Create task with stale heartbeat but NO lastActivityAt
|
|
133
|
-
const tasksWithHeartbeat = created.tasks.map((task) => ({
|
|
134
|
-
...task,
|
|
135
|
-
status: "running",
|
|
136
|
-
heartbeat: { workerId: task.id, lastSeenAt: "2026-01-01T00:00:00.000Z", alive: true },
|
|
137
|
-
// No agentProgress at all
|
|
138
|
-
}));
|
|
139
|
-
|
|
140
|
-
saveRunTasks(manifest, tasksWithHeartbeat);
|
|
141
|
-
const cache = createManifestCache(cwd, { watch: false, debounceMs: 0 });
|
|
142
|
-
const notifications = [];
|
|
143
|
-
let deadletters = 0;
|
|
144
|
-
const watcher = new HeartbeatWatcher({
|
|
145
|
-
cwd,
|
|
146
|
-
manifestCache: cache,
|
|
147
|
-
registry: createMetricRegistry(),
|
|
148
|
-
router: { enqueue: (n) => { notifications.push(n.id ?? ""); return true; } },
|
|
149
|
-
deadletterTickThreshold: 3,
|
|
150
|
-
onDeadletterTrigger: () => { deadletters += 1; }
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// Simulate time at 00:10:00 - heartbeat is 10 minutes old
|
|
154
|
-
watcher.tick(Date.parse("2026-01-01T00:10:00.000Z"));
|
|
155
|
-
watcher.tick(Date.parse("2026-01-01T00:10:05.000Z"));
|
|
156
|
-
watcher.tick(Date.parse("2026-01-01T00:10:10.000Z"));
|
|
157
|
-
|
|
158
|
-
// SHOULD have dead notifications because heartbeat is stale and no fallback
|
|
159
|
-
assert.ok(notifications.length > 0, "Should mark task dead when heartbeat stale and no lastActivityAt");
|
|
160
|
-
assert.ok(deadletters > 0, "Should trigger deadletter when no fallback available");
|
|
161
|
-
|
|
162
|
-
watcher.dispose();
|
|
163
|
-
cache.dispose();
|
|
164
|
-
} finally {
|
|
165
|
-
fs.rmSync(cwd, { recursive: true, force: true });
|
|
166
|
-
}
|
|
167
|
-
});
|
package/test-tp.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { formatToolProgress, formatCurrentToolLine } from "./src/runtime/tool-progress.ts";
|
|
2
|
-
|
|
3
|
-
const progress = {
|
|
4
|
-
recentTools: [{ tool: "bash", args: "ls", endedAt: "2024-01-01T00:00:00.000Z" }],
|
|
5
|
-
toolCount: 1,
|
|
6
|
-
activityState: "active"
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const display = formatToolProgress(progress);
|
|
10
|
-
console.log("currentTool:", display.currentTool);
|
|
11
|
-
console.log("toolCount:", display.toolCount);
|
|
12
|
-
console.log("TEST PASSED if no errors above");
|