pi-crew 0.9.20 → 0.9.22
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 +64 -0
- package/dist/build-meta.json +72 -24
- package/dist/index.mjs +698 -307
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
- package/src/extension/cross-extension-rpc.ts +16 -3
- package/src/extension/team-tool/run.ts +33 -4
- package/src/runtime/background-runner.ts +7 -0
- package/src/runtime/dynamic-workflow-context.ts +3 -3
- package/src/runtime/task-runner.ts +4 -4
- package/src/runtime/team-runner.ts +130 -1
- package/src/runtime/tool-output-pruner.ts +5 -3
- package/src/state/event-log.ts +168 -12
- package/src/state/types.ts +5 -0
- package/src/utils/token-counter.ts +67 -0
- package/src/worktree/worktree-manager.ts +363 -1
package/dist/index.mjs
CHANGED
|
@@ -11703,6 +11703,9 @@ function resetEventLogMode() {
|
|
|
11703
11703
|
asyncQueues.clear();
|
|
11704
11704
|
}
|
|
11705
11705
|
async function appendEventAsync(eventsPath, event) {
|
|
11706
|
+
if (!TERMINAL_EVENT_TYPES.has(event.type)) {
|
|
11707
|
+
return appendEventBuffered(eventsPath, event);
|
|
11708
|
+
}
|
|
11706
11709
|
const queueKey = eventsPath;
|
|
11707
11710
|
const prev = asyncQueues.get(queueKey) ?? Promise.resolve();
|
|
11708
11711
|
const next = prev.then(async () => {
|
|
@@ -11853,6 +11856,101 @@ async function appendEventAsync(eventsPath, event) {
|
|
|
11853
11856
|
);
|
|
11854
11857
|
return next;
|
|
11855
11858
|
}
|
|
11859
|
+
async function appendEventBatchInsideLock(eventsPath, queue) {
|
|
11860
|
+
if (queue.length === 0) return;
|
|
11861
|
+
fs8.mkdirSync(path6.dirname(eventsPath), { recursive: true });
|
|
11862
|
+
try {
|
|
11863
|
+
if (fs8.existsSync(eventsPath)) {
|
|
11864
|
+
const stat2 = fs8.statSync(eventsPath);
|
|
11865
|
+
if (stat2.size > MAX_EVENTS_BYTES) {
|
|
11866
|
+
try {
|
|
11867
|
+
const prepared = prepareCompaction(eventsPath);
|
|
11868
|
+
if (prepared) applyCompactionUnlocked(eventsPath, prepared);
|
|
11869
|
+
} catch (error) {
|
|
11870
|
+
logInternalError("event-log.batch-immediate-compact", error, `eventsPath=${eventsPath}`);
|
|
11871
|
+
}
|
|
11872
|
+
if (fs8.existsSync(eventsPath) && fs8.statSync(eventsPath).size > MAX_EVENTS_BYTES) {
|
|
11873
|
+
rotateEventLogUnlocked(eventsPath);
|
|
11874
|
+
}
|
|
11875
|
+
}
|
|
11876
|
+
}
|
|
11877
|
+
} catch (error) {
|
|
11878
|
+
logInternalError("event-log.batch-size-check", error, `eventsPath=${eventsPath}`);
|
|
11879
|
+
}
|
|
11880
|
+
const startingSeq = queue[0]?.event.metadata?.seq ?? nextSequence(eventsPath);
|
|
11881
|
+
let nextSeq = startingSeq;
|
|
11882
|
+
const finalized = [];
|
|
11883
|
+
let lastSeq = 0;
|
|
11884
|
+
for (const item of queue) {
|
|
11885
|
+
const baseMetadata = item.event.metadata;
|
|
11886
|
+
const seq = baseMetadata?.seq ?? nextSeq++;
|
|
11887
|
+
let metadata = {
|
|
11888
|
+
seq,
|
|
11889
|
+
provenance: baseMetadata?.provenance ?? "team_runner",
|
|
11890
|
+
...baseMetadata?.parentEventId ? { parentEventId: baseMetadata.parentEventId } : {},
|
|
11891
|
+
...baseMetadata?.attemptId ? { attemptId: baseMetadata.attemptId } : {},
|
|
11892
|
+
...baseMetadata?.branchId ? { branchId: baseMetadata.branchId } : {},
|
|
11893
|
+
...baseMetadata?.causationId ? { causationId: baseMetadata.causationId } : {},
|
|
11894
|
+
...baseMetadata?.correlationId ? { correlationId: baseMetadata.correlationId } : {},
|
|
11895
|
+
...baseMetadata?.sessionIdentity ? { sessionIdentity: baseMetadata.sessionIdentity } : {},
|
|
11896
|
+
...baseMetadata?.ownership ? { ownership: baseMetadata.ownership } : {},
|
|
11897
|
+
...baseMetadata?.nudgeId ? { nudgeId: baseMetadata.nudgeId } : {},
|
|
11898
|
+
...baseMetadata?.confidence ? { confidence: baseMetadata.confidence } : {}
|
|
11899
|
+
};
|
|
11900
|
+
const fullEvent = {
|
|
11901
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11902
|
+
...item.event,
|
|
11903
|
+
metadata
|
|
11904
|
+
};
|
|
11905
|
+
if (baseMetadata?.fingerprint || TERMINAL_EVENT_TYPES.has(fullEvent.type)) {
|
|
11906
|
+
metadata = {
|
|
11907
|
+
...metadata,
|
|
11908
|
+
fingerprint: baseMetadata?.fingerprint ?? computeEventFingerprint(fullEvent)
|
|
11909
|
+
};
|
|
11910
|
+
fullEvent.metadata = metadata;
|
|
11911
|
+
}
|
|
11912
|
+
finalized.push({ item, line: `${JSON.stringify(redactSecrets(fullEvent))}
|
|
11913
|
+
`, fullEvent });
|
|
11914
|
+
lastSeq = seq;
|
|
11915
|
+
}
|
|
11916
|
+
try {
|
|
11917
|
+
if (fs8.existsSync(eventsPath) && fs8.statSync(eventsPath).size > MAX_EVENTS_BYTES) {
|
|
11918
|
+
logInternalError(
|
|
11919
|
+
"event-log.size-limit",
|
|
11920
|
+
new Error(`events file ${eventsPath} exceeds ${MAX_EVENTS_BYTES} bytes after compaction`),
|
|
11921
|
+
`eventsPath=${eventsPath}`
|
|
11922
|
+
);
|
|
11923
|
+
for (const { item } of finalized) item.reject(new Error("event log size limit exceeded"));
|
|
11924
|
+
return;
|
|
11925
|
+
}
|
|
11926
|
+
} catch (error) {
|
|
11927
|
+
logInternalError("event-log.batch-size-check-post", error, `eventsPath=${eventsPath}`);
|
|
11928
|
+
}
|
|
11929
|
+
fs8.appendFileSync(eventsPath, finalized.map((f) => f.line).join(""), "utf-8");
|
|
11930
|
+
const fd = fs8.openSync(eventsPath, "r+");
|
|
11931
|
+
try {
|
|
11932
|
+
fs8.fsyncSync(fd);
|
|
11933
|
+
} catch {
|
|
11934
|
+
} finally {
|
|
11935
|
+
fs8.closeSync(fd);
|
|
11936
|
+
}
|
|
11937
|
+
persistSequence(eventsPath, lastSeq);
|
|
11938
|
+
try {
|
|
11939
|
+
const stat2 = fs8.statSync(eventsPath);
|
|
11940
|
+
if (sequenceCache.size >= MAX_SEQUENCE_CACHE_ENTRIES) {
|
|
11941
|
+
evictOldestSequenceCacheEntries();
|
|
11942
|
+
}
|
|
11943
|
+
sequenceCache.set(eventsPath, {
|
|
11944
|
+
size: stat2.size,
|
|
11945
|
+
mtimeMs: stat2.mtimeMs,
|
|
11946
|
+
seq: lastSeq,
|
|
11947
|
+
lastAccessMs: Date.now()
|
|
11948
|
+
});
|
|
11949
|
+
} catch (error) {
|
|
11950
|
+
logInternalError("event-log.batch-cache-update", error, `eventsPath=${eventsPath}`);
|
|
11951
|
+
}
|
|
11952
|
+
for (const { item, fullEvent } of finalized) item.resolve(fullEvent);
|
|
11953
|
+
}
|
|
11856
11954
|
function appendEventInsideLock(eventsPath, event) {
|
|
11857
11955
|
fs8.mkdirSync(path6.dirname(eventsPath), { recursive: true });
|
|
11858
11956
|
const baseMetadata = event.metadata;
|
|
@@ -11967,8 +12065,11 @@ function appendEventBuffered(eventsPath, event, bufferMs = DEFAULT_BUFFER_MS) {
|
|
|
11967
12065
|
queue.push({ event, resolve: resolve21, reject });
|
|
11968
12066
|
bufferedQueues.set(eventsPath, queue);
|
|
11969
12067
|
if (!bufferedTimers.has(eventsPath)) {
|
|
11970
|
-
const timer = setTimeout(() =>
|
|
11971
|
-
|
|
12068
|
+
const timer = setTimeout(() => {
|
|
12069
|
+
flushOneEventLogBuffer(eventsPath).catch((error) => {
|
|
12070
|
+
logInternalError("event-log.buffered-flush", error, `eventsPath=${eventsPath}`);
|
|
12071
|
+
});
|
|
12072
|
+
}, bufferMs);
|
|
11972
12073
|
bufferedTimers.set(eventsPath, timer);
|
|
11973
12074
|
}
|
|
11974
12075
|
});
|
|
@@ -12000,14 +12101,7 @@ async function flushOneEventLogBuffer(eventsPath) {
|
|
|
12000
12101
|
}
|
|
12001
12102
|
}
|
|
12002
12103
|
await withEventLogLockAsync(eventsPath, async () => {
|
|
12003
|
-
|
|
12004
|
-
try {
|
|
12005
|
-
const ev = appendEventInsideLock(eventsPath, item.event);
|
|
12006
|
-
item.resolve(ev);
|
|
12007
|
-
} catch (error) {
|
|
12008
|
-
item.reject(error);
|
|
12009
|
-
}
|
|
12010
|
-
}
|
|
12104
|
+
await appendEventBatchInsideLock(eventsPath, queue);
|
|
12011
12105
|
});
|
|
12012
12106
|
} catch (error) {
|
|
12013
12107
|
if (queue) for (const item of queue) item.reject(error);
|
|
@@ -12108,8 +12202,14 @@ var init_event_log = __esm({
|
|
|
12108
12202
|
bufferedTimers = /* @__PURE__ */ new Map();
|
|
12109
12203
|
DEFAULT_BUFFER_MS = 20;
|
|
12110
12204
|
process.on("exit", () => {
|
|
12111
|
-
|
|
12112
|
-
|
|
12205
|
+
if (bufferedQueues.size > 0) {
|
|
12206
|
+
flushEventLogBuffer().catch(() => {
|
|
12207
|
+
});
|
|
12208
|
+
}
|
|
12209
|
+
if (asyncQueues.size > 0) {
|
|
12210
|
+
drainAsyncQueues().catch(() => {
|
|
12211
|
+
});
|
|
12212
|
+
}
|
|
12113
12213
|
asyncQueues.clear();
|
|
12114
12214
|
});
|
|
12115
12215
|
process.on("SIGTERM", () => setImmediate(() => flushEventLogBuffer()));
|
|
@@ -17462,6 +17562,22 @@ var init_env_allowlist = __esm({
|
|
|
17462
17562
|
function isKnownProviderKey(key) {
|
|
17463
17563
|
return KNOWN_PROVIDER_KEYS.has(key);
|
|
17464
17564
|
}
|
|
17565
|
+
function providerEnvKeys(modelId) {
|
|
17566
|
+
if (!modelId) return [];
|
|
17567
|
+
const separatorIndex = modelId.indexOf("/");
|
|
17568
|
+
if (separatorIndex <= 0) return [];
|
|
17569
|
+
const provider = modelId.substring(0, separatorIndex).toLowerCase();
|
|
17570
|
+
return PROVIDER_ENV_KEY_MAP[provider] ?? [];
|
|
17571
|
+
}
|
|
17572
|
+
function buildScopedAllowList(baseAllowList, models) {
|
|
17573
|
+
const providerKeys = /* @__PURE__ */ new Set();
|
|
17574
|
+
for (const model of models) {
|
|
17575
|
+
for (const key of providerEnvKeys(model)) {
|
|
17576
|
+
providerKeys.add(key);
|
|
17577
|
+
}
|
|
17578
|
+
}
|
|
17579
|
+
return [...baseAllowList, ...providerKeys];
|
|
17580
|
+
}
|
|
17465
17581
|
function isDangerousGlob(pattern) {
|
|
17466
17582
|
if (!pattern.endsWith("*")) return false;
|
|
17467
17583
|
const prefix = pattern.slice(0, -1);
|
|
@@ -17502,7 +17618,7 @@ function sanitizeEnvSecrets(env, options) {
|
|
|
17502
17618
|
}
|
|
17503
17619
|
return filtered;
|
|
17504
17620
|
}
|
|
17505
|
-
var KNOWN_PROVIDER_KEYS, SECRET_SUFFIXES;
|
|
17621
|
+
var KNOWN_PROVIDER_KEYS, PROVIDER_ENV_KEY_MAP, SECRET_SUFFIXES;
|
|
17506
17622
|
var init_env_filter = __esm({
|
|
17507
17623
|
"src/utils/env-filter.ts"() {
|
|
17508
17624
|
"use strict";
|
|
@@ -17523,6 +17639,19 @@ var init_env_filter = __esm({
|
|
|
17523
17639
|
"ZEU_API_KEY",
|
|
17524
17640
|
"ZERODEV_API_KEY"
|
|
17525
17641
|
]);
|
|
17642
|
+
PROVIDER_ENV_KEY_MAP = {
|
|
17643
|
+
minimax: ["MINIMAX_API_KEY", "MINIMAX_GROUP_ID"],
|
|
17644
|
+
openai: ["OPENAI_API_KEY", "OPENAI_ORG_ID"],
|
|
17645
|
+
anthropic: ["ANTHROPIC_API_KEY"],
|
|
17646
|
+
google: ["GOOGLE_API_KEY", "GOOGLE_GENERATIVE_LANGUAGE_API_KEY"],
|
|
17647
|
+
gemini: ["GOOGLE_API_KEY", "GOOGLE_GENERATIVE_LANGUAGE_API_KEY"],
|
|
17648
|
+
azure: ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"],
|
|
17649
|
+
"azure-openai": ["AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT"],
|
|
17650
|
+
aws: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"],
|
|
17651
|
+
bedrock: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"],
|
|
17652
|
+
zai: ["ZEU_API_KEY"],
|
|
17653
|
+
zerodev: ["ZERODEV_API_KEY"]
|
|
17654
|
+
};
|
|
17526
17655
|
SECRET_SUFFIXES = ["token", "api", "key", "password", "passwd", "secret", "credential", "authorization", "private"];
|
|
17527
17656
|
}
|
|
17528
17657
|
});
|
|
@@ -18194,7 +18323,7 @@ function terminateActiveChildPiProcesses() {
|
|
|
18194
18323
|
for (const [pid, child] of entries) killProcessTree(pid, child);
|
|
18195
18324
|
return entries.length;
|
|
18196
18325
|
}
|
|
18197
|
-
function buildChildPiSpawnOptions(cwd, env) {
|
|
18326
|
+
function buildChildPiSpawnOptions(cwd, env, model) {
|
|
18198
18327
|
let validatedCwd;
|
|
18199
18328
|
try {
|
|
18200
18329
|
validatedCwd = fs27.realpathSync(cwd);
|
|
@@ -18209,81 +18338,8 @@ function buildChildPiSpawnOptions(cwd, env) {
|
|
|
18209
18338
|
throw new Error(`Invalid cwd: ${cwd} \u2014 ${error instanceof Error ? error.message : String(error)}`);
|
|
18210
18339
|
}
|
|
18211
18340
|
}
|
|
18212
|
-
const
|
|
18213
|
-
|
|
18214
|
-
/*
|
|
18215
|
-
* SECURITY WARNING: All model provider API keys below are passed to EVERY child worker.
|
|
18216
|
-
* If any child is compromised (e.g. via prompt injection), all listed keys are exposed.
|
|
18217
|
-
* This is a deliberate trade-off: multi-provider setups require the child Pi process to
|
|
18218
|
-
* authenticate with whichever provider the model routes to. Reducing keys per-child
|
|
18219
|
-
* would break multi-provider functionality. Mitigations:
|
|
18220
|
-
* - sanitizeEnvSecrets strips all env vars NOT on this list.
|
|
18221
|
-
* - Do NOT add wildcards ("*_API_KEY") — only explicit, intended provider keys.
|
|
18222
|
-
* - Consider per-task key scoping if the architecture allows it in the future.
|
|
18223
|
-
*
|
|
18224
|
-
* MAINTENANCE REQUIREMENT: When new secret env vars are added to the Pi ecosystem,
|
|
18225
|
-
* they MUST be explicitly added to this allowlist to be passed to child processes.
|
|
18226
|
-
* A CI check should fail if a secret-like env var (matching patterns like *_API_KEY,
|
|
18227
|
-
* *_TOKEN, *_SECRET) is detected in the codebase but not present in this list.
|
|
18228
|
-
*/
|
|
18229
|
-
// NOTE: Model provider API keys are NOT needed here — child Pi uses the same
|
|
18230
|
-
// config file as parent Pi. Passing keys via env is a security risk.
|
|
18231
|
-
"PATH",
|
|
18232
|
-
"HOME",
|
|
18233
|
-
"USER",
|
|
18234
|
-
"SHELL",
|
|
18235
|
-
"TERM",
|
|
18236
|
-
"LANG",
|
|
18237
|
-
// FIX: Replaced broad wildcards (LC_*, XDG_*, NVM_*, NODE_*, npm_*) with
|
|
18238
|
-
// specific names. Previously NPM_TOKEN, NODE_ENV=production, NVM_RC_VERSION
|
|
18239
|
-
// all leaked through wildcards.
|
|
18240
|
-
"LC_ALL",
|
|
18241
|
-
"LC_COLLATE",
|
|
18242
|
-
"LC_CTYPE",
|
|
18243
|
-
"LC_MESSAGES",
|
|
18244
|
-
"LC_MONETARY",
|
|
18245
|
-
"LC_NUMERIC",
|
|
18246
|
-
"LC_TIME",
|
|
18247
|
-
"XDG_CONFIG_HOME",
|
|
18248
|
-
"XDG_DATA_HOME",
|
|
18249
|
-
"XDG_CACHE_HOME",
|
|
18250
|
-
"XDG_RUNTIME_DIR",
|
|
18251
|
-
// Windows essentials — see WINDOWS_ESSENTIAL_ENV_VARS (src/utils/env-allowlist.ts).
|
|
18252
|
-
...WINDOWS_ESSENTIAL_ENV_VARS,
|
|
18253
|
-
"NVM_BIN",
|
|
18254
|
-
"NVM_DIR",
|
|
18255
|
-
"NVM_INC",
|
|
18256
|
-
// NODE_PATH is intentionally omitted from the allowlist.
|
|
18257
|
-
// NODE_PATH can reveal user environment information (e.g., NVM paths under $HOME)
|
|
18258
|
-
// and the validation at lines 286-298 only filters to standard system prefixes.
|
|
18259
|
-
// Removing it entirely is cleaner than best-effort filtering.
|
|
18260
|
-
"NODE_DISABLE_COLORS",
|
|
18261
|
-
"NODE_EXTRA_CA_CERTS",
|
|
18262
|
-
"NPM_CONFIG_REGISTRY",
|
|
18263
|
-
"NPM_CONFIG_USERCONFIG",
|
|
18264
|
-
"NPM_CONFIG_GLOBALCONFIG",
|
|
18265
|
-
// FIX: Replace PI_CREW_*/PI_TEAMS_* wildcards with explicit list of
|
|
18266
|
-
// safe vars. Wildcards are fragile — any new secret var would leak.
|
|
18267
|
-
// Only non-secret execution-control vars that children legitimately need.
|
|
18268
|
-
"PI_CREW_DEPTH",
|
|
18269
|
-
"PI_CREW_MAX_DEPTH",
|
|
18270
|
-
"PI_CREW_INHERIT_PROJECT_CONTEXT",
|
|
18271
|
-
"PI_CREW_INHERIT_SKILLS",
|
|
18272
|
-
// PI_CREW_KIND marks this process as a crew sub-agent (vs the user's main session).
|
|
18273
|
-
// doctor --zombies matches it to safely list orphaned sub-agents only.
|
|
18274
|
-
"PI_CREW_KIND",
|
|
18275
|
-
// PI_CREW_PARENT_PID is needed by child-pi's parent-guard (uses
|
|
18276
|
-
// process.kill(pid, 0) liveness check). The PID is not a secret.
|
|
18277
|
-
"PI_CREW_PARENT_PID",
|
|
18278
|
-
"PI_TEAMS_DEPTH",
|
|
18279
|
-
"PI_TEAMS_MAX_DEPTH",
|
|
18280
|
-
"PI_TEAMS_INHERIT_PROJECT_CONTEXT",
|
|
18281
|
-
"PI_TEAMS_INHERIT_SKILLS",
|
|
18282
|
-
"PI_TEAMS_PI_BIN",
|
|
18283
|
-
"PI_TEAMS_MOCK_CHILD_PI",
|
|
18284
|
-
"PI_CREW_ALLOW_MOCK"
|
|
18285
|
-
]
|
|
18286
|
-
});
|
|
18341
|
+
const allowList = model ? buildScopedAllowList(BASE_ALLOWLIST, [model]) : BASE_ALLOWLIST;
|
|
18342
|
+
const filteredEnv = sanitizeEnvSecrets(env, { allowList });
|
|
18287
18343
|
if (filteredEnv.NODE_PATH) {
|
|
18288
18344
|
const validPrefixes = ["/opt/", "/lib/", "/usr/local/", "/usr/", "/home/"];
|
|
18289
18345
|
const validPaths = filteredEnv.NODE_PATH.split(":").filter((p) => {
|
|
@@ -18620,10 +18676,14 @@ ${JSON.stringify({ type: "message_end", usage: { input: 10, output: 5, cost: 1e-
|
|
|
18620
18676
|
const child = spawn(
|
|
18621
18677
|
spawnSpec.command,
|
|
18622
18678
|
spawnSpec.args,
|
|
18623
|
-
buildChildPiSpawnOptions(
|
|
18624
|
-
|
|
18625
|
-
|
|
18626
|
-
|
|
18679
|
+
buildChildPiSpawnOptions(
|
|
18680
|
+
input.cwd,
|
|
18681
|
+
{
|
|
18682
|
+
...process.env,
|
|
18683
|
+
...built.env
|
|
18684
|
+
},
|
|
18685
|
+
input.model
|
|
18686
|
+
)
|
|
18627
18687
|
);
|
|
18628
18688
|
if (child.pid) {
|
|
18629
18689
|
activeChildProcesses.set(child.pid, child);
|
|
@@ -19110,7 +19170,7 @@ ${JSON.stringify({ type: "message_end", usage: { input: 10, output: 5, cost: 1e-
|
|
|
19110
19170
|
}
|
|
19111
19171
|
}
|
|
19112
19172
|
}
|
|
19113
|
-
var POST_EXIT_STDIO_GUARD_MS, FINAL_DRAIN_MS, HARD_KILL_MS, RESPONSE_TIMEOUT_MS, MAX_CAPTURE_BYTES, MAX_ASSISTANT_TEXT_CHARS, MAX_TOOL_RESULT_CHARS, MAX_TOOL_INPUT_CHARS, MAX_COMPACT_CONTENT_CHARS, activeChildProcesses, childHardKillTimers, ChildPiLineObserver;
|
|
19173
|
+
var POST_EXIT_STDIO_GUARD_MS, FINAL_DRAIN_MS, HARD_KILL_MS, RESPONSE_TIMEOUT_MS, MAX_CAPTURE_BYTES, MAX_ASSISTANT_TEXT_CHARS, MAX_TOOL_RESULT_CHARS, MAX_TOOL_INPUT_CHARS, MAX_COMPACT_CONTENT_CHARS, activeChildProcesses, childHardKillTimers, BASE_ALLOWLIST, ChildPiLineObserver;
|
|
19114
19174
|
var init_child_pi = __esm({
|
|
19115
19175
|
"src/runtime/child-pi.ts"() {
|
|
19116
19176
|
"use strict";
|
|
@@ -19139,6 +19199,48 @@ var init_child_pi = __esm({
|
|
|
19139
19199
|
MAX_COMPACT_CONTENT_CHARS = DEFAULT_CHILD_PI.maxCompactContentChars;
|
|
19140
19200
|
activeChildProcesses = /* @__PURE__ */ new Map();
|
|
19141
19201
|
childHardKillTimers = /* @__PURE__ */ new Map();
|
|
19202
|
+
BASE_ALLOWLIST = [
|
|
19203
|
+
"PATH",
|
|
19204
|
+
"HOME",
|
|
19205
|
+
"USER",
|
|
19206
|
+
"SHELL",
|
|
19207
|
+
"TERM",
|
|
19208
|
+
"LANG",
|
|
19209
|
+
"LC_ALL",
|
|
19210
|
+
"LC_COLLATE",
|
|
19211
|
+
"LC_CTYPE",
|
|
19212
|
+
"LC_MESSAGES",
|
|
19213
|
+
"LC_MONETARY",
|
|
19214
|
+
"LC_NUMERIC",
|
|
19215
|
+
"LC_TIME",
|
|
19216
|
+
"XDG_CONFIG_HOME",
|
|
19217
|
+
"XDG_DATA_HOME",
|
|
19218
|
+
"XDG_CACHE_HOME",
|
|
19219
|
+
"XDG_RUNTIME_DIR",
|
|
19220
|
+
// Windows essentials — see WINDOWS_ESSENTIAL_ENV_VARS (src/utils/env-allowlist.ts).
|
|
19221
|
+
...WINDOWS_ESSENTIAL_ENV_VARS,
|
|
19222
|
+
"NVM_BIN",
|
|
19223
|
+
"NVM_DIR",
|
|
19224
|
+
"NVM_INC",
|
|
19225
|
+
"NODE_DISABLE_COLORS",
|
|
19226
|
+
"NODE_EXTRA_CA_CERTS",
|
|
19227
|
+
"NPM_CONFIG_REGISTRY",
|
|
19228
|
+
"NPM_CONFIG_USERCONFIG",
|
|
19229
|
+
"NPM_CONFIG_GLOBALCONFIG",
|
|
19230
|
+
"PI_CREW_DEPTH",
|
|
19231
|
+
"PI_CREW_MAX_DEPTH",
|
|
19232
|
+
"PI_CREW_INHERIT_PROJECT_CONTEXT",
|
|
19233
|
+
"PI_CREW_INHERIT_SKILLS",
|
|
19234
|
+
"PI_CREW_KIND",
|
|
19235
|
+
"PI_CREW_PARENT_PID",
|
|
19236
|
+
"PI_TEAMS_DEPTH",
|
|
19237
|
+
"PI_TEAMS_MAX_DEPTH",
|
|
19238
|
+
"PI_TEAMS_INHERIT_PROJECT_CONTEXT",
|
|
19239
|
+
"PI_TEAMS_INHERIT_SKILLS",
|
|
19240
|
+
"PI_TEAMS_PI_BIN",
|
|
19241
|
+
"PI_TEAMS_MOCK_CHILD_PI",
|
|
19242
|
+
"PI_CREW_ALLOW_MOCK"
|
|
19243
|
+
];
|
|
19142
19244
|
ChildPiLineObserver = class {
|
|
19143
19245
|
buffer = "";
|
|
19144
19246
|
input;
|
|
@@ -46743,7 +46845,7 @@ function buildTeamDoctorReport(input) {
|
|
|
46743
46845
|
);
|
|
46744
46846
|
const sections = [
|
|
46745
46847
|
section("Runtime", () => {
|
|
46746
|
-
const
|
|
46848
|
+
const git3 = commandExists("git", ["--version"]);
|
|
46747
46849
|
const pi = piCommandExists();
|
|
46748
46850
|
return [
|
|
46749
46851
|
{ label: "cwd", ok: true, detail: input.cwd },
|
|
@@ -46753,7 +46855,7 @@ function buildTeamDoctorReport(input) {
|
|
|
46753
46855
|
detail: `${process.platform}/${process.arch} node=${process.version}`
|
|
46754
46856
|
},
|
|
46755
46857
|
{ label: "pi command", ok: pi.ok, detail: pi.detail },
|
|
46756
|
-
{ label: "git command", ok:
|
|
46858
|
+
{ label: "git command", ok: git3.ok, detail: git3.detail },
|
|
46757
46859
|
{
|
|
46758
46860
|
label: "config",
|
|
46759
46861
|
ok: input.configErrors.length === 0,
|
|
@@ -53491,9 +53593,41 @@ var init_goal_achievement = __esm({
|
|
|
53491
53593
|
}
|
|
53492
53594
|
});
|
|
53493
53595
|
|
|
53596
|
+
// src/utils/token-counter.ts
|
|
53597
|
+
function isWhitespace(c) {
|
|
53598
|
+
return c === 32 || c === 9 || c === 10 || c === 13;
|
|
53599
|
+
}
|
|
53600
|
+
function isAlphanumeric(c) {
|
|
53601
|
+
return c >= 48 && c <= 57 || // 0-9
|
|
53602
|
+
c >= 65 && c <= 90 || // A-Z
|
|
53603
|
+
c >= 97 && c <= 122 || // a-z
|
|
53604
|
+
c === 95;
|
|
53605
|
+
}
|
|
53606
|
+
function countTokens(text) {
|
|
53607
|
+
if (!text || text.length === 0) return 0;
|
|
53608
|
+
let alpha = 0;
|
|
53609
|
+
let punct = 0;
|
|
53610
|
+
const len = text.length;
|
|
53611
|
+
for (let i2 = 0; i2 < len; i2++) {
|
|
53612
|
+
const c = text.charCodeAt(i2);
|
|
53613
|
+
if (isWhitespace(c)) continue;
|
|
53614
|
+
if (isAlphanumeric(c)) {
|
|
53615
|
+
alpha++;
|
|
53616
|
+
} else {
|
|
53617
|
+
punct++;
|
|
53618
|
+
}
|
|
53619
|
+
}
|
|
53620
|
+
return Math.ceil(alpha / 4) + punct;
|
|
53621
|
+
}
|
|
53622
|
+
var init_token_counter = __esm({
|
|
53623
|
+
"src/utils/token-counter.ts"() {
|
|
53624
|
+
"use strict";
|
|
53625
|
+
}
|
|
53626
|
+
});
|
|
53627
|
+
|
|
53494
53628
|
// src/runtime/tool-output-pruner.ts
|
|
53495
53629
|
function estimateTokens(text) {
|
|
53496
|
-
return
|
|
53630
|
+
return countTokens(text);
|
|
53497
53631
|
}
|
|
53498
53632
|
function firstErrorLine(text) {
|
|
53499
53633
|
return text.split(/\r?\n/).find((line4) => /error|failed|exception|panic/i.test(line4))?.trim();
|
|
@@ -53524,7 +53658,7 @@ function createPrunedNotice(tokens, entry) {
|
|
|
53524
53658
|
const generic = `[Output pruned \u2014 ${tokens} tokens]`;
|
|
53525
53659
|
const digest = resultDigest(entry.toolName, entry.content, entry.isError);
|
|
53526
53660
|
if (!digest) return generic;
|
|
53527
|
-
const genericTokens =
|
|
53661
|
+
const genericTokens = countTokens(generic);
|
|
53528
53662
|
const maxTokens = Math.max(genericTokens, Math.floor(genericTokens * DIGEST_NOTICE_TOKEN_CAP_MULTIPLIER));
|
|
53529
53663
|
const prefix = `[Output pruned \u2014 ${tokens} tokens; `;
|
|
53530
53664
|
const suffix = "]";
|
|
@@ -53599,7 +53733,7 @@ function pruneToolOutputs(results, config = DEFAULT_PRUNE_CONFIG) {
|
|
|
53599
53733
|
entry,
|
|
53600
53734
|
tokens,
|
|
53601
53735
|
notice,
|
|
53602
|
-
savings: Math.max(0, tokens -
|
|
53736
|
+
savings: Math.max(0, tokens - countTokens(notice))
|
|
53603
53737
|
});
|
|
53604
53738
|
accumulatedTokens += tokens;
|
|
53605
53739
|
}
|
|
@@ -53624,6 +53758,7 @@ var DEFAULT_PRUNE_CONFIG, DIGEST_NOTICE_TOKEN_CAP_MULTIPLIER, READ_SELECTOR_SUFF
|
|
|
53624
53758
|
var init_tool_output_pruner = __esm({
|
|
53625
53759
|
"src/runtime/tool-output-pruner.ts"() {
|
|
53626
53760
|
"use strict";
|
|
53761
|
+
init_token_counter();
|
|
53627
53762
|
DEFAULT_PRUNE_CONFIG = {
|
|
53628
53763
|
protectTokens: 4e4,
|
|
53629
53764
|
minimumSavings: 2e4,
|
|
@@ -55008,60 +55143,70 @@ var init_task_graph_scheduler = __esm({
|
|
|
55008
55143
|
});
|
|
55009
55144
|
|
|
55010
55145
|
// src/worktree/worktree-manager.ts
|
|
55011
|
-
import { execFileSync as execFileSync6, spawnSync as spawnSync3 } from "node:child_process";
|
|
55146
|
+
import { execFile, execFileSync as execFileSync6, spawnSync as spawnSync3 } from "node:child_process";
|
|
55012
55147
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
55013
55148
|
import * as fs75 from "node:fs";
|
|
55014
55149
|
import * as path63 from "node:path";
|
|
55015
|
-
|
|
55016
|
-
|
|
55150
|
+
import { promisify } from "node:util";
|
|
55151
|
+
function gitEnv() {
|
|
55152
|
+
return {
|
|
55153
|
+
...sanitizeEnvSecrets(process.env, {
|
|
55154
|
+
allowList: [
|
|
55155
|
+
"PATH",
|
|
55156
|
+
"HOME",
|
|
55157
|
+
"USER",
|
|
55158
|
+
...WINDOWS_ESSENTIAL_ENV_VARS,
|
|
55159
|
+
"SHELL",
|
|
55160
|
+
"TERM",
|
|
55161
|
+
"LANG",
|
|
55162
|
+
"LC_ALL",
|
|
55163
|
+
"LC_COLLATE",
|
|
55164
|
+
"LC_CTYPE",
|
|
55165
|
+
"LC_MESSAGES",
|
|
55166
|
+
"XDG_CONFIG_HOME",
|
|
55167
|
+
"XDG_DATA_HOME",
|
|
55168
|
+
"XDG_CACHE_HOME",
|
|
55169
|
+
"NVM_BIN",
|
|
55170
|
+
"NVM_DIR",
|
|
55171
|
+
"NODE_PATH",
|
|
55172
|
+
"GIT_CONFIG_GLOBAL",
|
|
55173
|
+
"GIT_CONFIG_SYSTEM",
|
|
55174
|
+
"GIT_AUTHOR_NAME",
|
|
55175
|
+
"GIT_AUTHOR_EMAIL",
|
|
55176
|
+
"GIT_COMMITTER_NAME",
|
|
55177
|
+
"GIT_COMMITTER_EMAIL"
|
|
55178
|
+
]
|
|
55179
|
+
}),
|
|
55180
|
+
LANG: "en_US.UTF-8",
|
|
55181
|
+
LC_ALL: "en_US.UTF-8"
|
|
55182
|
+
};
|
|
55183
|
+
}
|
|
55184
|
+
async function gitAsync(cwd, args) {
|
|
55185
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
55017
55186
|
cwd,
|
|
55018
55187
|
encoding: "utf-8",
|
|
55019
|
-
|
|
55020
|
-
env: {
|
|
55021
|
-
...sanitizeEnvSecrets(process.env, {
|
|
55022
|
-
allowList: [
|
|
55023
|
-
"PATH",
|
|
55024
|
-
"HOME",
|
|
55025
|
-
"USER",
|
|
55026
|
-
...WINDOWS_ESSENTIAL_ENV_VARS,
|
|
55027
|
-
"SHELL",
|
|
55028
|
-
"TERM",
|
|
55029
|
-
"LANG",
|
|
55030
|
-
"LC_ALL",
|
|
55031
|
-
"LC_COLLATE",
|
|
55032
|
-
"LC_CTYPE",
|
|
55033
|
-
"LC_MESSAGES",
|
|
55034
|
-
"XDG_CONFIG_HOME",
|
|
55035
|
-
"XDG_DATA_HOME",
|
|
55036
|
-
"XDG_CACHE_HOME",
|
|
55037
|
-
"NVM_BIN",
|
|
55038
|
-
"NVM_DIR",
|
|
55039
|
-
"NODE_PATH",
|
|
55040
|
-
"GIT_CONFIG_GLOBAL",
|
|
55041
|
-
"GIT_CONFIG_SYSTEM",
|
|
55042
|
-
"GIT_AUTHOR_NAME",
|
|
55043
|
-
"GIT_AUTHOR_EMAIL",
|
|
55044
|
-
"GIT_COMMITTER_NAME",
|
|
55045
|
-
"GIT_COMMITTER_EMAIL"
|
|
55046
|
-
]
|
|
55047
|
-
}),
|
|
55048
|
-
LANG: "en_US.UTF-8",
|
|
55049
|
-
LC_ALL: "en_US.UTF-8"
|
|
55050
|
-
},
|
|
55188
|
+
env: gitEnv(),
|
|
55051
55189
|
windowsHide: true
|
|
55052
|
-
})
|
|
55190
|
+
});
|
|
55191
|
+
return stdout.trim();
|
|
55053
55192
|
}
|
|
55054
55193
|
function sanitizeBranchPart2(value) {
|
|
55055
55194
|
return value.toLowerCase().replace(/[^a-z0-9_/-]+/g, "-").replace(/^-+|-+$/g, "") || "task";
|
|
55056
55195
|
}
|
|
55057
|
-
function
|
|
55058
|
-
|
|
55196
|
+
async function findGitRootAsync(cwd) {
|
|
55197
|
+
const cached = _gitRootCache.get(cwd);
|
|
55198
|
+
if (cached) return cached;
|
|
55199
|
+
const root = await gitAsync(cwd, ["rev-parse", "--show-toplevel"]);
|
|
55200
|
+
_gitRootCache.set(cwd, root);
|
|
55201
|
+
return root;
|
|
55059
55202
|
}
|
|
55060
|
-
function
|
|
55061
|
-
|
|
55203
|
+
async function assertCleanLeaderAsync(repoRoot) {
|
|
55204
|
+
if (_cleanLeaderCache.has(repoRoot)) return;
|
|
55205
|
+
const status = await gitAsync(repoRoot, ["status", "--porcelain"]);
|
|
55062
55206
|
if (status.trim()) {
|
|
55063
55207
|
throw new Error("Worktree mode requires a clean leader repository. Commit/stash changes or use workspaceMode: 'single'.");
|
|
55064
55208
|
}
|
|
55209
|
+
_cleanLeaderCache.add(repoRoot);
|
|
55065
55210
|
}
|
|
55066
55211
|
function linkNodeModulesIfPresent(repoRoot, worktreePath) {
|
|
55067
55212
|
const source = path63.join(repoRoot, "node_modules");
|
|
@@ -55233,31 +55378,30 @@ function runSetupHook(manifest, task, repoRoot, worktreePath, branch) {
|
|
|
55233
55378
|
return [];
|
|
55234
55379
|
}
|
|
55235
55380
|
}
|
|
55236
|
-
function
|
|
55381
|
+
async function branchExistsAsync(repoRoot, branch) {
|
|
55237
55382
|
let local = false;
|
|
55238
55383
|
try {
|
|
55239
|
-
|
|
55384
|
+
await gitAsync(repoRoot, ["rev-parse", "--verify", `refs/heads/${branch}`]);
|
|
55240
55385
|
local = true;
|
|
55241
55386
|
} catch {
|
|
55242
55387
|
}
|
|
55243
55388
|
if (local) return { local: true, remoteOnly: false };
|
|
55244
55389
|
try {
|
|
55245
|
-
const out =
|
|
55390
|
+
const out = (await execFileAsync("git", ["for-each-ref", "--format=%(refname)", `refs/remotes/*/${branch}`], {
|
|
55246
55391
|
cwd: repoRoot,
|
|
55247
55392
|
encoding: "utf-8",
|
|
55248
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
55249
55393
|
windowsHide: true
|
|
55250
|
-
}).trim();
|
|
55394
|
+
})).stdout.trim();
|
|
55251
55395
|
return { local: false, remoteOnly: out.length > 0 };
|
|
55252
55396
|
} catch {
|
|
55253
55397
|
return { local: false, remoteOnly: false };
|
|
55254
55398
|
}
|
|
55255
55399
|
}
|
|
55256
|
-
function
|
|
55400
|
+
async function pruneStaleWorktreesAsync(repoRoot) {
|
|
55257
55401
|
try {
|
|
55258
|
-
|
|
55402
|
+
await execFileAsync("git", ["worktree", "prune"], {
|
|
55259
55403
|
cwd: repoRoot,
|
|
55260
|
-
|
|
55404
|
+
windowsHide: true
|
|
55261
55405
|
});
|
|
55262
55406
|
} catch {
|
|
55263
55407
|
}
|
|
@@ -55322,11 +55466,11 @@ function overlaySeedPaths(repoRoot, worktreePath, seedPaths) {
|
|
|
55322
55466
|
});
|
|
55323
55467
|
}
|
|
55324
55468
|
}
|
|
55325
|
-
function
|
|
55469
|
+
async function prepareTaskWorkspaceAsync(manifest, task, stepSeedPaths) {
|
|
55326
55470
|
if (manifest.workspaceMode !== "worktree") return { cwd: task.cwd };
|
|
55327
|
-
const repoRoot =
|
|
55471
|
+
const repoRoot = await findGitRootAsync(manifest.cwd);
|
|
55328
55472
|
const loadedConfig = loadConfig(manifest.cwd);
|
|
55329
|
-
if (loadedConfig.config.requireCleanWorktreeLeader !== false)
|
|
55473
|
+
if (loadedConfig.config.requireCleanWorktreeLeader !== false) await assertCleanLeaderAsync(repoRoot);
|
|
55330
55474
|
const sanitizedRunId = manifest.runId.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
55331
55475
|
const worktreeRoot = path63.join(projectCrewRoot(manifest.cwd), DEFAULT_PATHS.state.worktreesSubdir, sanitizedRunId);
|
|
55332
55476
|
fs75.mkdirSync(worktreeRoot, { recursive: true });
|
|
@@ -55345,7 +55489,7 @@ function prepareTaskWorkspace(manifest, task, stepSeedPaths) {
|
|
|
55345
55489
|
const branch = `pi-crew/${sanitizeBranchPart2(manifest.runId)}/${sanitizeBranchPart2(task.id)}`;
|
|
55346
55490
|
let worktreeExists = false;
|
|
55347
55491
|
try {
|
|
55348
|
-
const worktreeList =
|
|
55492
|
+
const worktreeList = await gitAsync(repoRoot, ["worktree", "list", "--porcelain"]);
|
|
55349
55493
|
const normalizedWtPath = process.platform === "win32" ? (() => {
|
|
55350
55494
|
try {
|
|
55351
55495
|
const r = fs75.realpathSync.native(worktreePath);
|
|
@@ -55368,7 +55512,7 @@ function prepareTaskWorkspace(manifest, task, stepSeedPaths) {
|
|
|
55368
55512
|
if (worktreeExists) {
|
|
55369
55513
|
let currentBranch;
|
|
55370
55514
|
try {
|
|
55371
|
-
currentBranch =
|
|
55515
|
+
currentBranch = await gitAsync(worktreePath, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
55372
55516
|
} catch (gitError) {
|
|
55373
55517
|
throw new Error(
|
|
55374
55518
|
`Existing worktree at ${worktreePath} is not a valid git repository; cannot verify branch: ${gitError instanceof Error ? gitError.message : String(gitError)}`
|
|
@@ -55377,30 +55521,31 @@ function prepareTaskWorkspace(manifest, task, stepSeedPaths) {
|
|
|
55377
55521
|
if (currentBranch !== branch) {
|
|
55378
55522
|
throw new Error(`Existing worktree branch mismatch at ${worktreePath}: expected '${branch}', got '${currentBranch}'.`);
|
|
55379
55523
|
}
|
|
55380
|
-
const dirtyStatus =
|
|
55524
|
+
const dirtyStatus = await gitAsync(worktreePath, ["status", "--porcelain"]);
|
|
55381
55525
|
if (dirtyStatus.trim()) {
|
|
55382
55526
|
logInternalError(
|
|
55383
55527
|
"worktree.reused.dirty",
|
|
55384
55528
|
new Error(`Discarding uncommitted changes in reused worktree at ${worktreePath}`),
|
|
55385
55529
|
`runId=${manifest.runId}, taskId=${task.id}, dirtyStatus=${dirtyStatus.trim()}`
|
|
55386
55530
|
);
|
|
55387
|
-
|
|
55388
|
-
|
|
55531
|
+
await gitAsync(worktreePath, ["checkout", "--", "."]);
|
|
55532
|
+
await gitAsync(worktreePath, ["clean", "-fd"]);
|
|
55389
55533
|
}
|
|
55390
55534
|
const globalSeedPaths2 = loadedConfig.config.worktree?.seedPaths ?? [];
|
|
55391
55535
|
const mergedReused = normalizeSeedPaths([...globalSeedPaths2, ...stepSeedPaths ?? []], repoRoot);
|
|
55392
55536
|
if (mergedReused.length > 0) {
|
|
55393
55537
|
overlaySeedPaths(repoRoot, worktreePath, mergedReused);
|
|
55394
55538
|
}
|
|
55395
|
-
|
|
55539
|
+
_cleanLeaderCache.delete(repoRoot);
|
|
55540
|
+
await assertCleanLeaderAsync(repoRoot);
|
|
55396
55541
|
return { cwd: worktreePath, worktreePath, branch, reused: true };
|
|
55397
55542
|
}
|
|
55398
|
-
|
|
55399
|
-
const exists =
|
|
55543
|
+
await pruneStaleWorktreesAsync(repoRoot);
|
|
55544
|
+
const exists = await branchExistsAsync(repoRoot, branch);
|
|
55400
55545
|
let worktreeCreated = false;
|
|
55401
55546
|
try {
|
|
55402
55547
|
if (exists.local) {
|
|
55403
|
-
|
|
55548
|
+
await gitAsync(repoRoot, ["worktree", "add", worktreePath, branch]);
|
|
55404
55549
|
} else {
|
|
55405
55550
|
if (exists.remoteOnly) {
|
|
55406
55551
|
logInternalError(
|
|
@@ -55409,7 +55554,7 @@ function prepareTaskWorkspace(manifest, task, stepSeedPaths) {
|
|
|
55409
55554
|
`branch=${branch}`
|
|
55410
55555
|
);
|
|
55411
55556
|
}
|
|
55412
|
-
|
|
55557
|
+
await gitAsync(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
55413
55558
|
}
|
|
55414
55559
|
worktreeCreated = true;
|
|
55415
55560
|
} catch (error) {
|
|
@@ -55443,10 +55588,10 @@ function prepareTaskWorkspace(manifest, task, stepSeedPaths) {
|
|
|
55443
55588
|
syntheticPaths
|
|
55444
55589
|
};
|
|
55445
55590
|
}
|
|
55446
|
-
function
|
|
55591
|
+
async function captureWorktreeDiffStatAsync(worktreePath) {
|
|
55447
55592
|
try {
|
|
55448
|
-
const diffStat =
|
|
55449
|
-
const numstat =
|
|
55593
|
+
const diffStat = await gitAsync(worktreePath, ["diff", "--stat"]);
|
|
55594
|
+
const numstat = await gitAsync(worktreePath, ["diff", "--numstat"]);
|
|
55450
55595
|
let filesChanged = 0;
|
|
55451
55596
|
let insertions = 0;
|
|
55452
55597
|
let deletions = 0;
|
|
@@ -55461,36 +55606,37 @@ function captureWorktreeDiffStat(worktreePath) {
|
|
|
55461
55606
|
return { filesChanged: 0, insertions: 0, deletions: 0, diffStat: "" };
|
|
55462
55607
|
}
|
|
55463
55608
|
}
|
|
55464
|
-
function
|
|
55609
|
+
async function captureWorktreeDiffAsync(worktreePath) {
|
|
55465
55610
|
try {
|
|
55466
|
-
|
|
55611
|
+
const [stat2, full] = await Promise.all([gitAsync(worktreePath, ["diff", "--stat"]), gitAsync(worktreePath, ["diff"])]);
|
|
55612
|
+
return stat2 + "\n\n" + full;
|
|
55467
55613
|
} catch (error) {
|
|
55468
55614
|
const message = error instanceof Error ? error.message : String(error);
|
|
55469
55615
|
return `Failed to capture worktree diff: ${message}`;
|
|
55470
55616
|
}
|
|
55471
55617
|
}
|
|
55472
|
-
function
|
|
55618
|
+
async function prepareAgentWorktreeAsync(manifest, agentId) {
|
|
55473
55619
|
try {
|
|
55474
|
-
const repoRoot =
|
|
55620
|
+
const repoRoot = await findGitRootAsync(manifest.cwd);
|
|
55475
55621
|
const loadedConfig = loadConfig(manifest.cwd);
|
|
55476
|
-
if (loadedConfig.config.requireCleanWorktreeLeader !== false)
|
|
55622
|
+
if (loadedConfig.config.requireCleanWorktreeLeader !== false) await assertCleanLeaderAsync(repoRoot);
|
|
55477
55623
|
const sanitizedRunId = manifest.runId.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^-+|-+$/g, "") || "run";
|
|
55478
55624
|
const worktreeRoot = path63.join(projectCrewRoot(manifest.cwd), DEFAULT_PATHS.state.worktreesSubdir, sanitizedRunId);
|
|
55479
55625
|
fs75.mkdirSync(worktreeRoot, { recursive: true });
|
|
55480
55626
|
const sanitizedAgentId = sanitizeBranchPart2(agentId);
|
|
55481
55627
|
const worktreePath = path63.join(worktreeRoot, sanitizedAgentId);
|
|
55482
55628
|
const branch = `pi-crew/${sanitizedRunId}/${sanitizedAgentId}`;
|
|
55483
|
-
|
|
55484
|
-
|
|
55629
|
+
await pruneStaleWorktreesAsync(repoRoot);
|
|
55630
|
+
await gitAsync(repoRoot, ["worktree", "add", "-b", branch, worktreePath, "HEAD"]);
|
|
55485
55631
|
const nodeModulesLinked = loadedConfig.config.worktree?.linkNodeModules === true ? linkNodeModulesIfPresent(repoRoot, worktreePath) : false;
|
|
55486
55632
|
return { cwd: worktreePath, worktreePath, branch, nodeModulesLinked };
|
|
55487
55633
|
} catch {
|
|
55488
55634
|
return void 0;
|
|
55489
55635
|
}
|
|
55490
55636
|
}
|
|
55491
|
-
function
|
|
55637
|
+
async function cleanupAgentWorktreeAsync(manifest, worktreePath, branch) {
|
|
55492
55638
|
try {
|
|
55493
|
-
const diff =
|
|
55639
|
+
const diff = await captureWorktreeDiffAsync(worktreePath);
|
|
55494
55640
|
if (diff.trim() && !diff.startsWith("Failed to capture worktree diff")) {
|
|
55495
55641
|
writeArtifact(manifest.artifactsRoot, {
|
|
55496
55642
|
kind: "diff",
|
|
@@ -55502,9 +55648,19 @@ function cleanupAgentWorktree(manifest, worktreePath, branch) {
|
|
|
55502
55648
|
} catch (error) {
|
|
55503
55649
|
logInternalError("worktree.agent-cleanup.diff", error, `worktreePath=${worktreePath}`);
|
|
55504
55650
|
}
|
|
55651
|
+
let repoRoot;
|
|
55652
|
+
try {
|
|
55653
|
+
repoRoot = await findGitRootAsync(manifest.cwd);
|
|
55654
|
+
} catch {
|
|
55655
|
+
try {
|
|
55656
|
+
fs75.rmSync(worktreePath, { recursive: true, force: true });
|
|
55657
|
+
} catch (rmError) {
|
|
55658
|
+
logInternalError("worktree.agent-cleanup.rm", rmError, `worktreePath=${worktreePath}`);
|
|
55659
|
+
}
|
|
55660
|
+
return;
|
|
55661
|
+
}
|
|
55505
55662
|
try {
|
|
55506
|
-
|
|
55507
|
-
git3(repoRoot, ["worktree", "remove", "--force", worktreePath]);
|
|
55663
|
+
await gitAsync(repoRoot, ["worktree", "remove", "--force", worktreePath]);
|
|
55508
55664
|
} catch (error) {
|
|
55509
55665
|
logInternalError("worktree.agent-cleanup.remove", error, `worktreePath=${worktreePath}`);
|
|
55510
55666
|
try {
|
|
@@ -55515,19 +55671,18 @@ function cleanupAgentWorktree(manifest, worktreePath, branch) {
|
|
|
55515
55671
|
}
|
|
55516
55672
|
if (branch) {
|
|
55517
55673
|
try {
|
|
55518
|
-
|
|
55519
|
-
git3(repoRoot, ["branch", "-D", branch]);
|
|
55674
|
+
await gitAsync(repoRoot, ["branch", "-D", branch]);
|
|
55520
55675
|
} catch (error) {
|
|
55521
55676
|
logInternalError("worktree.agent-cleanup.branch", error, `branch=${branch}`);
|
|
55522
55677
|
}
|
|
55523
55678
|
}
|
|
55524
55679
|
try {
|
|
55525
|
-
|
|
55526
|
-
git3(repoRoot, ["worktree", "prune"]);
|
|
55680
|
+
await gitAsync(repoRoot, ["worktree", "prune"]);
|
|
55527
55681
|
} catch (error) {
|
|
55528
55682
|
logInternalError("worktree.agent-cleanup.prune", error, `worktreePath=${worktreePath}`);
|
|
55529
55683
|
}
|
|
55530
55684
|
}
|
|
55685
|
+
var execFileAsync, _gitRootCache, _cleanLeaderCache;
|
|
55531
55686
|
var init_worktree_manager = __esm({
|
|
55532
55687
|
"src/worktree/worktree-manager.ts"() {
|
|
55533
55688
|
"use strict";
|
|
@@ -55538,6 +55693,9 @@ var init_worktree_manager = __esm({
|
|
|
55538
55693
|
init_env_filter();
|
|
55539
55694
|
init_internal_error();
|
|
55540
55695
|
init_paths();
|
|
55696
|
+
execFileAsync = promisify(execFile);
|
|
55697
|
+
_gitRootCache = /* @__PURE__ */ new Map();
|
|
55698
|
+
_cleanLeaderCache = /* @__PURE__ */ new Set();
|
|
55541
55699
|
}
|
|
55542
55700
|
});
|
|
55543
55701
|
|
|
@@ -57496,7 +57654,7 @@ async function runTeamTask(input) {
|
|
|
57496
57654
|
let streamBridge;
|
|
57497
57655
|
try {
|
|
57498
57656
|
streamBridge = registerStreamBridge(manifest.runId);
|
|
57499
|
-
const workspace =
|
|
57657
|
+
const workspace = await prepareTaskWorkspaceAsync(manifest, input.task, input.step.seedPaths);
|
|
57500
57658
|
const worktree = workspace.worktreePath && workspace.branch ? {
|
|
57501
57659
|
path: workspace.worktreePath,
|
|
57502
57660
|
branch: workspace.branch,
|
|
@@ -58154,13 +58312,13 @@ async function runTeamTask(input) {
|
|
|
58154
58312
|
const diffArtifact = workspace.worktreePath ? writeArtifact(manifest.artifactsRoot, {
|
|
58155
58313
|
kind: "diff",
|
|
58156
58314
|
relativePath: `diffs/${task.id}.diff`,
|
|
58157
|
-
content:
|
|
58315
|
+
content: await captureWorktreeDiffAsync(workspace.worktreePath),
|
|
58158
58316
|
producer: task.id
|
|
58159
58317
|
}) : void 0;
|
|
58160
58318
|
const diffStatArtifact = workspace.worktreePath ? writeArtifact(manifest.artifactsRoot, {
|
|
58161
58319
|
kind: "metadata",
|
|
58162
58320
|
relativePath: `metadata/${task.id}.diff-stat.json`,
|
|
58163
|
-
content: `${JSON.stringify({ ...
|
|
58321
|
+
content: `${JSON.stringify({ ...await captureWorktreeDiffStatAsync(workspace.worktreePath), syntheticPaths: workspace.syntheticPaths ?? [], nodeModulesLinked: workspace.nodeModulesLinked ?? false }, null, 2)}
|
|
58164
58322
|
`,
|
|
58165
58323
|
producer: task.id
|
|
58166
58324
|
}) : void 0;
|
|
@@ -59399,6 +59557,7 @@ __export(team_runner_exports, {
|
|
|
59399
59557
|
__test__mergeTaskUpdates: () => __test__mergeTaskUpdates,
|
|
59400
59558
|
__test__parseAdaptivePlan: () => __test__parseAdaptivePlan,
|
|
59401
59559
|
__test__repairAdaptivePlan: () => __test__repairAdaptivePlan,
|
|
59560
|
+
checkPerTaskBudget: () => checkPerTaskBudget,
|
|
59402
59561
|
executeTeamRun: () => executeTeamRun,
|
|
59403
59562
|
hasPendingMutatingTaskAtBoundary: () => hasPendingMutatingTaskAtBoundary,
|
|
59404
59563
|
mergeTaskUpdatesPreservingTerminal: () => mergeTaskUpdatesPreservingTerminal,
|
|
@@ -59429,6 +59588,23 @@ function startTeamRunHeartbeat(stateRoot, runId) {
|
|
|
59429
59588
|
const interval = setInterval(writeHeartbeat, 3e4);
|
|
59430
59589
|
return () => clearInterval(interval);
|
|
59431
59590
|
}
|
|
59591
|
+
function checkPerTaskBudget(tasks, budgetTotal, budgetWarning, budgetAbort, fairShareFraction = 0.5) {
|
|
59592
|
+
const usage = aggregateUsage(tasks);
|
|
59593
|
+
const totalUsed = (usage?.input ?? 0) + (usage?.output ?? 0) + (usage?.cacheWrite ?? 0);
|
|
59594
|
+
const abort = totalUsed >= budgetAbort * budgetTotal;
|
|
59595
|
+
const warning = !abort && totalUsed >= budgetWarning * budgetTotal;
|
|
59596
|
+
const remainingBudget = Math.max(0, budgetTotal - totalUsed);
|
|
59597
|
+
const fairShareThreshold = remainingBudget * fairShareFraction;
|
|
59598
|
+
const fairShareViolators = [];
|
|
59599
|
+
for (const task of tasks) {
|
|
59600
|
+
if (!task.usage) continue;
|
|
59601
|
+
const taskTotal = (task.usage.input ?? 0) + (task.usage.output ?? 0) + (task.usage.cacheWrite ?? 0);
|
|
59602
|
+
if (fairShareThreshold > 0 && taskTotal > fairShareThreshold && taskTotal > budgetTotal * 0.1) {
|
|
59603
|
+
fairShareViolators.push(task.id);
|
|
59604
|
+
}
|
|
59605
|
+
}
|
|
59606
|
+
return { abort, warning, fairShareViolators, totalUsed };
|
|
59607
|
+
}
|
|
59432
59608
|
function findStep(workflow, task) {
|
|
59433
59609
|
const step = workflow.steps.find((candidate) => candidate.id === task.stepId);
|
|
59434
59610
|
if (!step) throw new Error(`Workflow step '${task.stepId}' not found for task '${task.id}'.`);
|
|
@@ -59737,6 +59913,13 @@ async function executeTeamRun(input) {
|
|
|
59737
59913
|
"running",
|
|
59738
59914
|
input.executeWorkers ? "Executing team workflow." : "Creating workflow prompts and placeholder results."
|
|
59739
59915
|
);
|
|
59916
|
+
if (input.budgetTotal !== void 0) manifest.budgetTotal = input.budgetTotal;
|
|
59917
|
+
if (input.budgetWarning !== void 0) manifest.budgetWarning = input.budgetWarning;
|
|
59918
|
+
if (input.budgetAbort !== void 0) manifest.budgetAbort = input.budgetAbort;
|
|
59919
|
+
if (input.budgetUnlimited !== void 0) manifest.budgetUnlimited = input.budgetUnlimited;
|
|
59920
|
+
if (manifest.budgetTotal !== void 0 && manifest.budgetTotal > 0 && !manifest.budgetUnlimited) {
|
|
59921
|
+
saveRunManifest(manifest);
|
|
59922
|
+
}
|
|
59740
59923
|
void registerRunPromise(manifest.runId);
|
|
59741
59924
|
const stopTeamHeartbeat = startTeamRunHeartbeat(manifest.stateRoot, manifest.runId);
|
|
59742
59925
|
const cleanupUsage = () => {
|
|
@@ -60434,6 +60617,60 @@ async function executeTeamRunCore(input, manifest, workflow) {
|
|
|
60434
60617
|
}
|
|
60435
60618
|
wfMachine = { ...wfMachine, currentPhaseIndex: pi + 1 };
|
|
60436
60619
|
}
|
|
60620
|
+
if (input.budgetTotal !== void 0 && input.budgetTotal > 0 && input.budgetUnlimited !== true) {
|
|
60621
|
+
const warnThreshold = input.budgetWarning ?? 0.8;
|
|
60622
|
+
const abortThreshold = input.budgetAbort ?? 0.95;
|
|
60623
|
+
const budgetCheck = checkPerTaskBudget(tasks, input.budgetTotal, warnThreshold, abortThreshold);
|
|
60624
|
+
if (budgetCheck.abort) {
|
|
60625
|
+
const message = `Per-task budget abort threshold exceeded: ${formatTokens(budgetCheck.totalUsed)}/${formatTokens(input.budgetTotal)} (${Math.round(budgetCheck.totalUsed / input.budgetTotal * 100)}%)`;
|
|
60626
|
+
console.warn(`[team-runner] ${message}`);
|
|
60627
|
+
await appendEventAsync(manifest.eventsPath, {
|
|
60628
|
+
type: "run.budget_abort",
|
|
60629
|
+
runId: manifest.runId,
|
|
60630
|
+
message,
|
|
60631
|
+
data: {
|
|
60632
|
+
budgetTotal: input.budgetTotal,
|
|
60633
|
+
budgetUsed: budgetCheck.totalUsed,
|
|
60634
|
+
threshold: "abort"
|
|
60635
|
+
}
|
|
60636
|
+
});
|
|
60637
|
+
tasks = markBlocked(tasks, `Budget abort threshold exceeded: ${message}`);
|
|
60638
|
+
await saveRunTasksAsync(manifest, tasks);
|
|
60639
|
+
manifest = updateRunStatus(manifest, "failed", message);
|
|
60640
|
+
return { manifest, tasks };
|
|
60641
|
+
}
|
|
60642
|
+
if (budgetCheck.warning) {
|
|
60643
|
+
const message = `Per-task budget warning threshold crossed: ${formatTokens(budgetCheck.totalUsed)}/${formatTokens(input.budgetTotal)} (${Math.round(budgetCheck.totalUsed / input.budgetTotal * 100)}%)`;
|
|
60644
|
+
console.warn(`[team-runner] ${message}`);
|
|
60645
|
+
await appendEventAsync(manifest.eventsPath, {
|
|
60646
|
+
type: "run.budget_warning",
|
|
60647
|
+
runId: manifest.runId,
|
|
60648
|
+
message,
|
|
60649
|
+
data: {
|
|
60650
|
+
budgetTotal: input.budgetTotal,
|
|
60651
|
+
budgetUsed: budgetCheck.totalUsed,
|
|
60652
|
+
threshold: "warning"
|
|
60653
|
+
}
|
|
60654
|
+
});
|
|
60655
|
+
}
|
|
60656
|
+
for (const violatorId of budgetCheck.fairShareViolators) {
|
|
60657
|
+
const violator = tasks.find((t2) => t2.id === violatorId);
|
|
60658
|
+
if (!violator) continue;
|
|
60659
|
+
const taskTotal = (violator.usage?.input ?? 0) + (violator.usage?.output ?? 0) + (violator.usage?.cacheWrite ?? 0);
|
|
60660
|
+
const message = `Task '${violatorId}' consumed ${formatTokens(taskTotal)} (${Math.round(taskTotal / input.budgetTotal * 100)}% of total budget) \u2014 exceeds fair share`;
|
|
60661
|
+
console.warn(`[team-runner.fair-share] ${message}`);
|
|
60662
|
+
await appendEventAsync(manifest.eventsPath, {
|
|
60663
|
+
type: "task.budget_fair_share",
|
|
60664
|
+
runId: manifest.runId,
|
|
60665
|
+
taskId: violatorId,
|
|
60666
|
+
message,
|
|
60667
|
+
data: {
|
|
60668
|
+
budgetTotal: input.budgetTotal,
|
|
60669
|
+
taskUsage: taskTotal
|
|
60670
|
+
}
|
|
60671
|
+
});
|
|
60672
|
+
}
|
|
60673
|
+
}
|
|
60437
60674
|
const cancelledResult = results.find((item) => item.manifest.status === "cancelled");
|
|
60438
60675
|
if (cancelledResult || input.signal?.aborted) {
|
|
60439
60676
|
const reason = input.signal?.aborted ? cancellationReasonFromSignal(input.signal) : void 0;
|
|
@@ -68838,7 +69075,7 @@ function makeWorkflowCtx(manifest, opts) {
|
|
|
68838
69075
|
const task = composeAgentTask(call);
|
|
68839
69076
|
let agentCwd = manifest.cwd;
|
|
68840
69077
|
if (call.worktree === true) {
|
|
68841
|
-
const wt =
|
|
69078
|
+
const wt = await prepareAgentWorktreeAsync(manifest, `dwf-agent-${Date.now()}-${randomBytes3(4).toString("hex")}`);
|
|
68842
69079
|
if (wt?.worktreePath) {
|
|
68843
69080
|
agentCwd = wt.cwd;
|
|
68844
69081
|
worktreePath = wt.worktreePath;
|
|
@@ -68913,7 +69150,7 @@ function makeWorkflowCtx(manifest, opts) {
|
|
|
68913
69150
|
} finally {
|
|
68914
69151
|
if (worktreePath) {
|
|
68915
69152
|
try {
|
|
68916
|
-
|
|
69153
|
+
await cleanupAgentWorktreeAsync(manifest, worktreePath, worktreeBranch);
|
|
68917
69154
|
} catch (cleanupError) {
|
|
68918
69155
|
logInternalError("dynamic-workflow-context.worktree-cleanup", cleanupError, `worktreePath=${worktreePath}`);
|
|
68919
69156
|
}
|
|
@@ -69609,7 +69846,7 @@ async function handleRun(params, ctx) {
|
|
|
69609
69846
|
let resolvedCtx = ctx;
|
|
69610
69847
|
if (workingDir) {
|
|
69611
69848
|
try {
|
|
69612
|
-
const gitRoot =
|
|
69849
|
+
const gitRoot = await findGitRootAsync(workingDir);
|
|
69613
69850
|
if (gitRoot && gitRoot !== workingDir) {
|
|
69614
69851
|
resolvedCtx = { ...ctx, cwd: gitRoot };
|
|
69615
69852
|
}
|
|
@@ -69619,7 +69856,7 @@ async function handleRun(params, ctx) {
|
|
|
69619
69856
|
if (params.workspaceMode === "worktree") {
|
|
69620
69857
|
let gitRoot;
|
|
69621
69858
|
try {
|
|
69622
|
-
gitRoot =
|
|
69859
|
+
gitRoot = await findGitRootAsync(resolvedCtx.cwd);
|
|
69623
69860
|
} catch {
|
|
69624
69861
|
}
|
|
69625
69862
|
if (!gitRoot) {
|
|
@@ -69633,7 +69870,7 @@ Use workspaceMode: 'single' or run from a git repository.`,
|
|
|
69633
69870
|
const preCheckConfig = loadConfig(resolvedCtx.cwd);
|
|
69634
69871
|
if (preCheckConfig.config.requireCleanWorktreeLeader !== false) {
|
|
69635
69872
|
try {
|
|
69636
|
-
|
|
69873
|
+
await assertCleanLeaderAsync(gitRoot);
|
|
69637
69874
|
} catch (err2) {
|
|
69638
69875
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
69639
69876
|
return result(
|
|
@@ -69885,10 +70122,30 @@ ${dwfResult.manifest.summary ?? ""}`,
|
|
|
69885
70122
|
const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
|
|
69886
70123
|
const runtime = await resolveCrewRuntime(executedConfig);
|
|
69887
70124
|
const runtimeResolution = runtimeResolutionState(runtime);
|
|
70125
|
+
if (process.env.PI_CREW_DEBUG_BUDGET === "1") {
|
|
70126
|
+
console.log(
|
|
70127
|
+
"[DEBUG budget] params keys:",
|
|
70128
|
+
Object.keys(params),
|
|
70129
|
+
"budgetTotal:",
|
|
70130
|
+
params.budgetTotal,
|
|
70131
|
+
"budgetWarning:",
|
|
70132
|
+
params.budgetWarning,
|
|
70133
|
+
"budgetAbort:",
|
|
70134
|
+
params.budgetAbort
|
|
70135
|
+
);
|
|
70136
|
+
}
|
|
69888
70137
|
const executionManifest = {
|
|
69889
70138
|
...updatedManifest,
|
|
69890
70139
|
runtimeResolution,
|
|
69891
70140
|
runConfig: executedConfig,
|
|
70141
|
+
// Persist budget config on the manifest so it's observable post-run
|
|
70142
|
+
// (events.jsonl, status reads, audits). The team-runner reads these
|
|
70143
|
+
// from the input, but persisting them means consumers can verify
|
|
70144
|
+
// enforcement was armed without re-parsing the original call params.
|
|
70145
|
+
...params.budgetTotal !== void 0 ? { budgetTotal: params.budgetTotal } : {},
|
|
70146
|
+
...params.budgetWarning !== void 0 ? { budgetWarning: params.budgetWarning } : {},
|
|
70147
|
+
...params.budgetAbort !== void 0 ? { budgetAbort: params.budgetAbort } : {},
|
|
70148
|
+
...params.budgetUnlimited !== void 0 ? { budgetUnlimited: params.budgetUnlimited } : {},
|
|
69892
70149
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
69893
70150
|
};
|
|
69894
70151
|
atomicWriteJson(paths.manifestPath, executionManifest);
|
|
@@ -70153,7 +70410,11 @@ ${dwfResult.manifest.summary ?? ""}`,
|
|
|
70153
70410
|
reliability: executedConfig.reliability,
|
|
70154
70411
|
metricRegistry: ctx.metricRegistry,
|
|
70155
70412
|
onJsonEvent: ctx.onJsonEvent,
|
|
70156
|
-
workspaceId: ctx.sessionId ?? ctx.cwd
|
|
70413
|
+
workspaceId: ctx.sessionId ?? ctx.cwd,
|
|
70414
|
+
budgetTotal: params.budgetTotal,
|
|
70415
|
+
budgetWarning: params.budgetWarning,
|
|
70416
|
+
budgetAbort: params.budgetAbort,
|
|
70417
|
+
budgetUnlimited: params.budgetUnlimited
|
|
70157
70418
|
});
|
|
70158
70419
|
} finally {
|
|
70159
70420
|
unregisterActiveRun(updatedManifest.runId);
|
|
@@ -70280,7 +70541,11 @@ ${dwfResult.manifest.summary ?? ""}`,
|
|
|
70280
70541
|
reliability: executedConfig.reliability,
|
|
70281
70542
|
metricRegistry: ctx.metricRegistry,
|
|
70282
70543
|
onJsonEvent: ctx.onJsonEvent,
|
|
70283
|
-
workspaceId: ctx.cwd
|
|
70544
|
+
workspaceId: ctx.cwd,
|
|
70545
|
+
budgetTotal: params.budgetTotal,
|
|
70546
|
+
budgetWarning: params.budgetWarning,
|
|
70547
|
+
budgetAbort: params.budgetAbort,
|
|
70548
|
+
budgetUnlimited: params.budgetUnlimited
|
|
70284
70549
|
});
|
|
70285
70550
|
} finally {
|
|
70286
70551
|
unregisterActiveRun(updatedManifest.runId);
|
|
@@ -77264,7 +77529,7 @@ __export(otlp_exporter_exports, {
|
|
|
77264
77529
|
convertToOTLP: () => convertToOTLP,
|
|
77265
77530
|
validateEndpoint: () => validateEndpoint
|
|
77266
77531
|
});
|
|
77267
|
-
import { promisify } from "node:util";
|
|
77532
|
+
import { promisify as promisify2 } from "node:util";
|
|
77268
77533
|
import { gzip } from "node:zlib";
|
|
77269
77534
|
function validateEndpoint(endpoint) {
|
|
77270
77535
|
let url;
|
|
@@ -77382,7 +77647,7 @@ var init_otlp_exporter = __esm({
|
|
|
77382
77647
|
"use strict";
|
|
77383
77648
|
init_internal_error();
|
|
77384
77649
|
init_redaction();
|
|
77385
|
-
gzipAsync =
|
|
77650
|
+
gzipAsync = promisify2(gzip);
|
|
77386
77651
|
MAX_SNAPSHOTS_PER_PUSH = 5e3;
|
|
77387
77652
|
OTLPExporter = class {
|
|
77388
77653
|
name = "otlp";
|
|
@@ -81215,6 +81480,104 @@ var CREW_SHORTCUT_KEYS = CREW_SHORTCUTS.map((s) => s.key);
|
|
|
81215
81480
|
|
|
81216
81481
|
// src/extension/cross-extension-rpc.ts
|
|
81217
81482
|
init_safe_paths();
|
|
81483
|
+
|
|
81484
|
+
// src/extension/rpc-hmac.ts
|
|
81485
|
+
import { createHmac, randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
|
|
81486
|
+
var RPC_HMAC_VERSION = 1;
|
|
81487
|
+
var SECRET_ENV_VAR = "PI_CREW_RPC_SECRET";
|
|
81488
|
+
var CLOCK_SKEW_TOLERANCE_MS = 5 * 60 * 1e3;
|
|
81489
|
+
var SIGNATURE_VALIDITY_MS = 10 * 60 * 1e3;
|
|
81490
|
+
function getRpcSecret() {
|
|
81491
|
+
return process.env[SECRET_ENV_VAR];
|
|
81492
|
+
}
|
|
81493
|
+
function isHmacEnabled() {
|
|
81494
|
+
return typeof process.env[SECRET_ENV_VAR] === "string" && process.env[SECRET_ENV_VAR].length > 0;
|
|
81495
|
+
}
|
|
81496
|
+
function verifyRpcSignature(payload, body) {
|
|
81497
|
+
const secret = getRpcSecret();
|
|
81498
|
+
if (!secret) {
|
|
81499
|
+
return { valid: false, reason: `[pi-crew HMAC] ${SECRET_ENV_VAR} not configured.` };
|
|
81500
|
+
}
|
|
81501
|
+
if (payload.version !== RPC_HMAC_VERSION) {
|
|
81502
|
+
return { valid: false, reason: `HMAC version mismatch: expected ${RPC_HMAC_VERSION}, got ${payload.version}` };
|
|
81503
|
+
}
|
|
81504
|
+
const now = Date.now();
|
|
81505
|
+
const age = now - payload.timestamp;
|
|
81506
|
+
if (age < -CLOCK_SKEW_TOLERANCE_MS) {
|
|
81507
|
+
return { valid: false, reason: `HMAC timestamp in the future by ${Math.abs(age)}ms` };
|
|
81508
|
+
}
|
|
81509
|
+
if (age > SIGNATURE_VALIDITY_MS) {
|
|
81510
|
+
return { valid: false, reason: `HMAC signature expired (${age}ms old)` };
|
|
81511
|
+
}
|
|
81512
|
+
const expected = computeHmac(secret, payload, body);
|
|
81513
|
+
return timingSafeCompare(payload.signature, expected) ? { valid: true } : { valid: false, reason: "HMAC signature mismatch" };
|
|
81514
|
+
}
|
|
81515
|
+
function withHmacVerification(handler, _channel) {
|
|
81516
|
+
return (params) => {
|
|
81517
|
+
if (!isHmacEnabled()) {
|
|
81518
|
+
return handler(params);
|
|
81519
|
+
}
|
|
81520
|
+
const sigPayload = extractSignaturePayload(params);
|
|
81521
|
+
if (!sigPayload) {
|
|
81522
|
+
throw new Error("[pi-crew HMAC] Missing HMAC signature in RPC request.");
|
|
81523
|
+
}
|
|
81524
|
+
const originalBody = stripHmacFields(params);
|
|
81525
|
+
const verification = verifyRpcSignature(sigPayload, originalBody);
|
|
81526
|
+
if (!verification.valid) {
|
|
81527
|
+
throw new Error(`[pi-crew HMAC] ${verification.reason}`);
|
|
81528
|
+
}
|
|
81529
|
+
return handler(originalBody);
|
|
81530
|
+
};
|
|
81531
|
+
}
|
|
81532
|
+
function stripHmacFields(params) {
|
|
81533
|
+
const result4 = { ...params };
|
|
81534
|
+
delete result4.hmacVersion;
|
|
81535
|
+
delete result4.hmacOrigin;
|
|
81536
|
+
delete result4.hmacTimestamp;
|
|
81537
|
+
delete result4.hmacChannel;
|
|
81538
|
+
delete result4.hmacNonce;
|
|
81539
|
+
delete result4.hmacSignature;
|
|
81540
|
+
return result4;
|
|
81541
|
+
}
|
|
81542
|
+
function computeHmac(secret, payload, body) {
|
|
81543
|
+
const payloadForHmac = {
|
|
81544
|
+
version: payload.version,
|
|
81545
|
+
origin: payload.origin,
|
|
81546
|
+
timestamp: payload.timestamp,
|
|
81547
|
+
channel: payload.channel,
|
|
81548
|
+
nonce: payload.nonce
|
|
81549
|
+
};
|
|
81550
|
+
const message = `${JSON.stringify(payloadForHmac)}:${JSON.stringify(body)}`;
|
|
81551
|
+
return createHmac("sha256", secret).update(message).digest("hex");
|
|
81552
|
+
}
|
|
81553
|
+
function timingSafeCompare(a, b) {
|
|
81554
|
+
const bufA = Buffer.from(a, "hex");
|
|
81555
|
+
const bufB = Buffer.from(b, "hex");
|
|
81556
|
+
const maxLen = Math.max(bufA.length, bufB.length);
|
|
81557
|
+
const safeA = Buffer.alloc(maxLen);
|
|
81558
|
+
const safeB = Buffer.alloc(maxLen);
|
|
81559
|
+
bufA.copy(safeA);
|
|
81560
|
+
bufB.copy(safeB);
|
|
81561
|
+
const equal = timingSafeEqual3(safeA, safeB);
|
|
81562
|
+
return equal && bufA.length === bufB.length;
|
|
81563
|
+
}
|
|
81564
|
+
function extractSignaturePayload(params) {
|
|
81565
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) return null;
|
|
81566
|
+
const obj = params;
|
|
81567
|
+
if (typeof obj.hmacVersion !== "number" || typeof obj.hmacOrigin !== "string" || typeof obj.hmacTimestamp !== "number" || typeof obj.hmacChannel !== "string" || typeof obj.hmacNonce !== "string" || typeof obj.hmacSignature !== "string") {
|
|
81568
|
+
return null;
|
|
81569
|
+
}
|
|
81570
|
+
return {
|
|
81571
|
+
version: obj.hmacVersion,
|
|
81572
|
+
origin: obj.hmacOrigin,
|
|
81573
|
+
timestamp: obj.hmacTimestamp,
|
|
81574
|
+
channel: obj.hmacChannel,
|
|
81575
|
+
nonce: obj.hmacNonce,
|
|
81576
|
+
signature: obj.hmacSignature
|
|
81577
|
+
};
|
|
81578
|
+
}
|
|
81579
|
+
|
|
81580
|
+
// src/extension/cross-extension-rpc.ts
|
|
81218
81581
|
init_live_control_realtime();
|
|
81219
81582
|
var _cachedHandleTeamTool5;
|
|
81220
81583
|
async function handleTeamTool6(params, ctx) {
|
|
@@ -81312,139 +81675,167 @@ function registerPiCrewRpc(events, getCtx) {
|
|
|
81312
81675
|
on(
|
|
81313
81676
|
events,
|
|
81314
81677
|
"pi-crew:rpc:ping",
|
|
81315
|
-
(
|
|
81316
|
-
|
|
81317
|
-
|
|
81318
|
-
|
|
81678
|
+
withHmacVerification(
|
|
81679
|
+
(raw) => reply(events, "pi-crew:rpc:ping", requestId2(raw), {
|
|
81680
|
+
success: true,
|
|
81681
|
+
data: { version: PI_CREW_RPC_VERSION }
|
|
81682
|
+
}),
|
|
81683
|
+
"pi-crew:rpc:ping"
|
|
81684
|
+
)
|
|
81319
81685
|
),
|
|
81320
|
-
on(
|
|
81321
|
-
|
|
81322
|
-
|
|
81323
|
-
|
|
81324
|
-
|
|
81686
|
+
on(
|
|
81687
|
+
events,
|
|
81688
|
+
"pi-crew:rpc:run",
|
|
81689
|
+
withHmacVerification(async (raw) => {
|
|
81690
|
+
const id = requestId2(raw);
|
|
81691
|
+
try {
|
|
81692
|
+
const rateLimit = checkRpcRateLimit();
|
|
81693
|
+
if (!rateLimit.allowed) {
|
|
81694
|
+
reply(events, "pi-crew:rpc:run", id, {
|
|
81695
|
+
success: false,
|
|
81696
|
+
error: `RPC run rate limit exceeded. Max ${RPC_RATE_LIMIT_MAX} requests per ${RPC_RATE_LIMIT_WINDOW_MS / 1e3}s. Retry after ${Math.ceil((rateLimit.retryAfterMs ?? 6e4) / 1e3)}s.`
|
|
81697
|
+
});
|
|
81698
|
+
return;
|
|
81699
|
+
}
|
|
81700
|
+
recordRpcRun();
|
|
81701
|
+
const ctx = getCtx();
|
|
81702
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
81703
|
+
const ALLOWED_RPC_RUN_KEYS = /* @__PURE__ */ new Set([
|
|
81704
|
+
"goal",
|
|
81705
|
+
"team",
|
|
81706
|
+
"workflow",
|
|
81707
|
+
"async",
|
|
81708
|
+
"cwd",
|
|
81709
|
+
"config",
|
|
81710
|
+
"skill",
|
|
81711
|
+
"model",
|
|
81712
|
+
"budgetTotal",
|
|
81713
|
+
"budgetWarning",
|
|
81714
|
+
"budgetAbort",
|
|
81715
|
+
"budgetUnlimited"
|
|
81716
|
+
]);
|
|
81717
|
+
let params;
|
|
81718
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
81719
|
+
const filtered = {
|
|
81720
|
+
...raw
|
|
81721
|
+
};
|
|
81722
|
+
for (const key of Object.keys(filtered)) {
|
|
81723
|
+
if (!ALLOWED_RPC_RUN_KEYS.has(key)) delete filtered[key];
|
|
81724
|
+
}
|
|
81725
|
+
params = {
|
|
81726
|
+
...filtered,
|
|
81727
|
+
action: "run"
|
|
81728
|
+
};
|
|
81729
|
+
} else {
|
|
81730
|
+
params = { action: "run" };
|
|
81731
|
+
}
|
|
81732
|
+
const permission = isAllowedRpcRunParams(params);
|
|
81733
|
+
if (!permission.ok) {
|
|
81734
|
+
reply(events, "pi-crew:rpc:run", id, {
|
|
81735
|
+
success: false,
|
|
81736
|
+
error: permission.error ?? "permission denied"
|
|
81737
|
+
});
|
|
81738
|
+
return;
|
|
81739
|
+
}
|
|
81740
|
+
const result4 = await handleTeamTool6(params, ctx);
|
|
81741
|
+
reply(
|
|
81742
|
+
events,
|
|
81743
|
+
"pi-crew:rpc:run",
|
|
81744
|
+
id,
|
|
81745
|
+
result4.isError ? { success: false, error: textOf(result4) } : { success: true, data: result4.details }
|
|
81746
|
+
);
|
|
81747
|
+
} catch (error) {
|
|
81325
81748
|
reply(events, "pi-crew:rpc:run", id, {
|
|
81326
81749
|
success: false,
|
|
81327
|
-
error:
|
|
81750
|
+
error: error instanceof Error ? error.message : String(error)
|
|
81328
81751
|
});
|
|
81329
|
-
return;
|
|
81330
|
-
}
|
|
81331
|
-
recordRpcRun();
|
|
81332
|
-
const ctx = getCtx();
|
|
81333
|
-
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
81334
|
-
const ALLOWED_RPC_RUN_KEYS = /* @__PURE__ */ new Set(["goal", "team", "workflow", "async", "cwd", "config", "skill", "model"]);
|
|
81335
|
-
let params;
|
|
81336
|
-
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
81337
|
-
const filtered = {
|
|
81338
|
-
...raw
|
|
81339
|
-
};
|
|
81340
|
-
for (const key of Object.keys(filtered)) {
|
|
81341
|
-
if (!ALLOWED_RPC_RUN_KEYS.has(key)) delete filtered[key];
|
|
81342
|
-
}
|
|
81343
|
-
params = {
|
|
81344
|
-
...filtered,
|
|
81345
|
-
action: "run"
|
|
81346
|
-
};
|
|
81347
|
-
} else {
|
|
81348
|
-
params = { action: "run" };
|
|
81349
81752
|
}
|
|
81350
|
-
|
|
81351
|
-
|
|
81352
|
-
|
|
81753
|
+
}, "pi-crew:rpc:run")
|
|
81754
|
+
),
|
|
81755
|
+
on(
|
|
81756
|
+
events,
|
|
81757
|
+
"pi-crew:rpc:status",
|
|
81758
|
+
withHmacVerification(async (raw) => {
|
|
81759
|
+
const id = requestId2(raw);
|
|
81760
|
+
try {
|
|
81761
|
+
const ctx = getCtx();
|
|
81762
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
81763
|
+
const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? raw.runId : void 0;
|
|
81764
|
+
const result4 = await handleTeamTool6({ action: "status", runId }, ctx);
|
|
81765
|
+
reply(
|
|
81766
|
+
events,
|
|
81767
|
+
"pi-crew:rpc:status",
|
|
81768
|
+
id,
|
|
81769
|
+
result4.isError ? { success: false, error: textOf(result4) } : {
|
|
81770
|
+
success: true,
|
|
81771
|
+
data: {
|
|
81772
|
+
text: textOf(result4),
|
|
81773
|
+
details: result4.details
|
|
81774
|
+
}
|
|
81775
|
+
}
|
|
81776
|
+
);
|
|
81777
|
+
} catch (error) {
|
|
81778
|
+
reply(events, "pi-crew:rpc:status", id, {
|
|
81353
81779
|
success: false,
|
|
81354
|
-
error:
|
|
81780
|
+
error: error instanceof Error ? error.message : String(error)
|
|
81355
81781
|
});
|
|
81356
|
-
return;
|
|
81357
81782
|
}
|
|
81358
|
-
|
|
81359
|
-
|
|
81360
|
-
events,
|
|
81361
|
-
"pi-crew:rpc:run",
|
|
81362
|
-
id,
|
|
81363
|
-
result4.isError ? { success: false, error: textOf(result4) } : { success: true, data: result4.details }
|
|
81364
|
-
);
|
|
81365
|
-
} catch (error) {
|
|
81366
|
-
reply(events, "pi-crew:rpc:run", id, {
|
|
81367
|
-
success: false,
|
|
81368
|
-
error: error instanceof Error ? error.message : String(error)
|
|
81369
|
-
});
|
|
81370
|
-
}
|
|
81371
|
-
}),
|
|
81372
|
-
on(events, "pi-crew:rpc:status", async (raw) => {
|
|
81373
|
-
const id = requestId2(raw);
|
|
81374
|
-
try {
|
|
81375
|
-
const ctx = getCtx();
|
|
81376
|
-
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
81377
|
-
const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? raw.runId : void 0;
|
|
81378
|
-
const result4 = await handleTeamTool6({ action: "status", runId }, ctx);
|
|
81379
|
-
reply(
|
|
81380
|
-
events,
|
|
81381
|
-
"pi-crew:rpc:status",
|
|
81382
|
-
id,
|
|
81383
|
-
result4.isError ? { success: false, error: textOf(result4) } : {
|
|
81384
|
-
success: true,
|
|
81385
|
-
data: {
|
|
81386
|
-
text: textOf(result4),
|
|
81387
|
-
details: result4.details
|
|
81388
|
-
}
|
|
81389
|
-
}
|
|
81390
|
-
);
|
|
81391
|
-
} catch (error) {
|
|
81392
|
-
reply(events, "pi-crew:rpc:status", id, {
|
|
81393
|
-
success: false,
|
|
81394
|
-
error: error instanceof Error ? error.message : String(error)
|
|
81395
|
-
});
|
|
81396
|
-
}
|
|
81397
|
-
}),
|
|
81783
|
+
}, "pi-crew:rpc:status")
|
|
81784
|
+
),
|
|
81398
81785
|
on(events, "pi-crew:live-control", (raw) => {
|
|
81399
81786
|
const request = parseLiveControlRealtimeMessage(raw);
|
|
81400
81787
|
if (request) publishLiveControlRealtime(request);
|
|
81401
81788
|
}),
|
|
81402
|
-
on(
|
|
81403
|
-
|
|
81404
|
-
|
|
81405
|
-
|
|
81406
|
-
|
|
81407
|
-
|
|
81408
|
-
|
|
81409
|
-
|
|
81789
|
+
on(
|
|
81790
|
+
events,
|
|
81791
|
+
"pi-crew:rpc:live-control",
|
|
81792
|
+
withHmacVerification(async (raw) => {
|
|
81793
|
+
const id = requestId2(raw);
|
|
81794
|
+
try {
|
|
81795
|
+
const ctx = getCtx();
|
|
81796
|
+
if (!ctx) throw new Error("No active pi-crew session context.");
|
|
81797
|
+
const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
81798
|
+
const rawOp = typeof obj.operation === "string" ? obj.operation : "steer-agent";
|
|
81799
|
+
if (!isAllowedRpcOperation(rawOp)) {
|
|
81800
|
+
reply(events, "pi-crew:rpc:live-control", id, {
|
|
81801
|
+
success: false,
|
|
81802
|
+
error: `RPC operation '${rawOp}' is not allowed. Allowed: ${[...RPC_ALLOWED_OPERATIONS].join(", ")}`
|
|
81803
|
+
});
|
|
81804
|
+
return;
|
|
81805
|
+
}
|
|
81806
|
+
const result4 = await handleTeamTool6(
|
|
81807
|
+
{
|
|
81808
|
+
action: "api",
|
|
81809
|
+
runId: typeof obj.runId === "string" ? obj.runId : void 0,
|
|
81810
|
+
config: {
|
|
81811
|
+
operation: rawOp,
|
|
81812
|
+
agentId: obj.agentId,
|
|
81813
|
+
message: obj.message,
|
|
81814
|
+
prompt: obj.prompt
|
|
81815
|
+
}
|
|
81816
|
+
},
|
|
81817
|
+
ctx
|
|
81818
|
+
);
|
|
81819
|
+
reply(
|
|
81820
|
+
events,
|
|
81821
|
+
"pi-crew:rpc:live-control",
|
|
81822
|
+
id,
|
|
81823
|
+
result4.isError ? { success: false, error: textOf(result4) } : {
|
|
81824
|
+
success: true,
|
|
81825
|
+
data: {
|
|
81826
|
+
text: textOf(result4),
|
|
81827
|
+
details: result4.details
|
|
81828
|
+
}
|
|
81829
|
+
}
|
|
81830
|
+
);
|
|
81831
|
+
} catch (error) {
|
|
81410
81832
|
reply(events, "pi-crew:rpc:live-control", id, {
|
|
81411
81833
|
success: false,
|
|
81412
|
-
error:
|
|
81834
|
+
error: error instanceof Error ? error.message : String(error)
|
|
81413
81835
|
});
|
|
81414
|
-
return;
|
|
81415
81836
|
}
|
|
81416
|
-
|
|
81417
|
-
|
|
81418
|
-
action: "api",
|
|
81419
|
-
runId: typeof obj.runId === "string" ? obj.runId : void 0,
|
|
81420
|
-
config: {
|
|
81421
|
-
operation: rawOp,
|
|
81422
|
-
agentId: obj.agentId,
|
|
81423
|
-
message: obj.message,
|
|
81424
|
-
prompt: obj.prompt
|
|
81425
|
-
}
|
|
81426
|
-
},
|
|
81427
|
-
ctx
|
|
81428
|
-
);
|
|
81429
|
-
reply(
|
|
81430
|
-
events,
|
|
81431
|
-
"pi-crew:rpc:live-control",
|
|
81432
|
-
id,
|
|
81433
|
-
result4.isError ? { success: false, error: textOf(result4) } : {
|
|
81434
|
-
success: true,
|
|
81435
|
-
data: {
|
|
81436
|
-
text: textOf(result4),
|
|
81437
|
-
details: result4.details
|
|
81438
|
-
}
|
|
81439
|
-
}
|
|
81440
|
-
);
|
|
81441
|
-
} catch (error) {
|
|
81442
|
-
reply(events, "pi-crew:rpc:live-control", id, {
|
|
81443
|
-
success: false,
|
|
81444
|
-
error: error instanceof Error ? error.message : String(error)
|
|
81445
|
-
});
|
|
81446
|
-
}
|
|
81447
|
-
})
|
|
81837
|
+
}, "pi-crew:rpc:live-control")
|
|
81838
|
+
)
|
|
81448
81839
|
];
|
|
81449
81840
|
return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
|
|
81450
81841
|
}
|