pi-crew 0.1.44 → 0.1.45
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 +27 -0
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +11 -11
- package/agents/writer.md +11 -11
- package/docs/refactor-tasks-phase3.md +394 -394
- package/docs/refactor-tasks-phase4.md +564 -564
- package/docs/refactor-tasks-phase5.md +402 -402
- package/docs/refactor-tasks-phase6.md +662 -662
- package/docs/research-extension-examples.md +297 -297
- package/docs/research-extension-system.md +324 -324
- package/docs/research-optimization-plan.md +548 -548
- package/docs/research-phase10-distillation.md +198 -198
- package/docs/research-phase11-distillation.md +201 -201
- package/docs/research-pi-coding-agent.md +357 -357
- package/docs/research-source-pi-crew-reference.md +174 -174
- package/docs/runtime-flow.md +148 -148
- package/docs/source-runtime-refactor-map.md +83 -83
- package/index.ts +6 -6
- package/package.json +1 -1
- package/src/agents/agent-serializer.ts +34 -34
- package/src/extension/cross-extension-rpc.ts +82 -82
- package/src/extension/register.ts +8 -1
- package/src/extension/registration/commands.ts +18 -2
- package/src/extension/registration/compaction-guard.ts +125 -125
- package/src/extension/registration/subagent-tools.ts +148 -148
- package/src/extension/registration/team-tool.ts +26 -8
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-maintenance.ts +43 -43
- package/src/extension/team-tool/cancel.ts +105 -102
- package/src/extension/team-tool/context.ts +1 -0
- package/src/extension/team-tool/handle-settings.ts +188 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/lifecycle-actions.ts +79 -79
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/respond.ts +83 -66
- package/src/extension/team-tool/run.ts +1 -0
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +77 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-control.ts +63 -63
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/background-runner.ts +53 -53
- package/src/runtime/child-pi.ts +444 -444
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/crew-agent-records.ts +8 -0
- package/src/runtime/delivery-coordinator.ts +153 -142
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/live-agent-control.ts +87 -87
- package/src/runtime/live-agent-manager.ts +85 -85
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-session-runtime.ts +305 -305
- package/src/runtime/overflow-recovery.ts +175 -156
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/retry-executor.ts +64 -64
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stale-reconciler.ts +199 -179
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-output-context.ts +127 -127
- package/src/runtime/task-runner/live-executor.ts +101 -101
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/team-runner.ts +13 -4
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/state/state-store.ts +43 -0
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +2 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +5 -1
- package/src/ui/dashboard-panes/mailbox-pane.ts +2 -1
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-snapshot-cache.ts +56 -37
- package/src/ui/snapshot-types.ts +5 -0
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/utils/atomic-write.ts +33 -33
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +12 -12
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/sleep.ts +32 -32
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +29 -29
- package/workflows/fast-fix.workflow.md +22 -22
- package/workflows/implementation.workflow.md +38 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import { logInternalError } from "../utils/internal-error.ts";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Try to register a cleanup function with Pi's session resource cleanup API (v0.72+).
|
|
6
|
-
* Falls back to returning undefined if the API is not available.
|
|
7
|
-
*
|
|
8
|
-
* The returned function (if defined) can be called to unregister the cleanup.
|
|
9
|
-
*/
|
|
10
|
-
export function tryRegisterSessionCleanup(pi: ExtensionAPI, cleanup: () => void): (() => void) | undefined {
|
|
11
|
-
const api = pi as unknown as Record<string, unknown>;
|
|
12
|
-
const registerFn = api["registerSessionResourceCleanup"];
|
|
13
|
-
if (typeof registerFn === "function") {
|
|
14
|
-
try {
|
|
15
|
-
const unregister = (registerFn as (fn: () => void) => (() => void) | void)(cleanup);
|
|
16
|
-
if (typeof unregister === "function") return unregister;
|
|
17
|
-
// API returned void — cleanup is registered but cannot be unregistered
|
|
18
|
-
return undefined;
|
|
19
|
-
} catch (error) {
|
|
20
|
-
logInternalError("session-resources.register", error);
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
1
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
import { logInternalError } from "../utils/internal-error.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Try to register a cleanup function with Pi's session resource cleanup API (v0.72+).
|
|
6
|
+
* Falls back to returning undefined if the API is not available.
|
|
7
|
+
*
|
|
8
|
+
* The returned function (if defined) can be called to unregister the cleanup.
|
|
9
|
+
*/
|
|
10
|
+
export function tryRegisterSessionCleanup(pi: ExtensionAPI, cleanup: () => void): (() => void) | undefined {
|
|
11
|
+
const api = pi as unknown as Record<string, unknown>;
|
|
12
|
+
const registerFn = api["registerSessionResourceCleanup"];
|
|
13
|
+
if (typeof registerFn === "function") {
|
|
14
|
+
try {
|
|
15
|
+
const unregister = (registerFn as (fn: () => void) => (() => void) | void)(cleanup);
|
|
16
|
+
if (typeof unregister === "function") return unregister;
|
|
17
|
+
// API returned void — cleanup is registered but cannot be unregistered
|
|
18
|
+
return undefined;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
logInternalError("session-resources.register", error);
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates a lightweight snapshot of task state for event emission.
|
|
5
|
-
* Prevents mutation-during-callback issues by copying relevant fields.
|
|
6
|
-
*/
|
|
7
|
-
export function snapshotTaskState(task: TeamTaskState): Readonly<TeamTaskState> {
|
|
8
|
-
return {
|
|
9
|
-
...task,
|
|
10
|
-
dependsOn: [...task.dependsOn],
|
|
11
|
-
usage: task.usage ? { ...task.usage } : undefined,
|
|
12
|
-
agentProgress: task.agentProgress ? { ...task.agentProgress } : undefined,
|
|
13
|
-
heartbeat: task.heartbeat ? { ...task.heartbeat } : undefined,
|
|
14
|
-
modelAttempts: task.modelAttempts?.map((a) => ({ ...a })),
|
|
15
|
-
modelRouting: task.modelRouting ? { ...task.modelRouting } : undefined,
|
|
16
|
-
claim: task.claim ? { ...task.claim } : undefined,
|
|
17
|
-
checkpoint: task.checkpoint ? { ...task.checkpoint } : undefined,
|
|
18
|
-
attempts: task.attempts?.map((a) => ({ ...a })),
|
|
19
|
-
worktree: task.worktree ? { ...task.worktree } : undefined,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Session state snapshot for persistence before session switches.
|
|
25
|
-
* Captures the minimal set of data needed to resume operations.
|
|
26
|
-
*/
|
|
27
|
-
export interface SessionStateSnapshot {
|
|
28
|
-
/** ISO timestamp of the snapshot */
|
|
29
|
-
capturedAt: string;
|
|
30
|
-
/** Active run IDs at time of snapshot */
|
|
31
|
-
activeRunIds: string[];
|
|
32
|
-
/** Number of pending deliveries */
|
|
33
|
-
pendingDeliveryCount: number;
|
|
34
|
-
/** Session generation counter */
|
|
35
|
-
sessionGeneration: number;
|
|
36
|
-
/** Summary of active tasks by status */
|
|
37
|
-
taskSummary: Record<string, number>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Create a session state snapshot for pre-switch persistence.
|
|
42
|
-
*/
|
|
43
|
-
export function createSessionSnapshot(
|
|
44
|
-
activeRuns: TeamRunManifest[],
|
|
45
|
-
pendingDeliveryCount: number,
|
|
46
|
-
sessionGeneration: number,
|
|
47
|
-
): SessionStateSnapshot {
|
|
48
|
-
const taskSummary: Record<string, number> = {};
|
|
49
|
-
for (const run of activeRuns) {
|
|
50
|
-
taskSummary[run.status] = (taskSummary[run.status] ?? 0) + 1;
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
capturedAt: new Date().toISOString(),
|
|
54
|
-
activeRunIds: activeRuns.map((r) => r.runId),
|
|
55
|
-
pendingDeliveryCount,
|
|
56
|
-
sessionGeneration,
|
|
57
|
-
taskSummary,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
1
|
+
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a lightweight snapshot of task state for event emission.
|
|
5
|
+
* Prevents mutation-during-callback issues by copying relevant fields.
|
|
6
|
+
*/
|
|
7
|
+
export function snapshotTaskState(task: TeamTaskState): Readonly<TeamTaskState> {
|
|
8
|
+
return {
|
|
9
|
+
...task,
|
|
10
|
+
dependsOn: [...task.dependsOn],
|
|
11
|
+
usage: task.usage ? { ...task.usage } : undefined,
|
|
12
|
+
agentProgress: task.agentProgress ? { ...task.agentProgress } : undefined,
|
|
13
|
+
heartbeat: task.heartbeat ? { ...task.heartbeat } : undefined,
|
|
14
|
+
modelAttempts: task.modelAttempts?.map((a) => ({ ...a })),
|
|
15
|
+
modelRouting: task.modelRouting ? { ...task.modelRouting } : undefined,
|
|
16
|
+
claim: task.claim ? { ...task.claim } : undefined,
|
|
17
|
+
checkpoint: task.checkpoint ? { ...task.checkpoint } : undefined,
|
|
18
|
+
attempts: task.attempts?.map((a) => ({ ...a })),
|
|
19
|
+
worktree: task.worktree ? { ...task.worktree } : undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Session state snapshot for persistence before session switches.
|
|
25
|
+
* Captures the minimal set of data needed to resume operations.
|
|
26
|
+
*/
|
|
27
|
+
export interface SessionStateSnapshot {
|
|
28
|
+
/** ISO timestamp of the snapshot */
|
|
29
|
+
capturedAt: string;
|
|
30
|
+
/** Active run IDs at time of snapshot */
|
|
31
|
+
activeRunIds: string[];
|
|
32
|
+
/** Number of pending deliveries */
|
|
33
|
+
pendingDeliveryCount: number;
|
|
34
|
+
/** Session generation counter */
|
|
35
|
+
sessionGeneration: number;
|
|
36
|
+
/** Summary of active tasks by status */
|
|
37
|
+
taskSummary: Record<string, number>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a session state snapshot for pre-switch persistence.
|
|
42
|
+
*/
|
|
43
|
+
export function createSessionSnapshot(
|
|
44
|
+
activeRuns: TeamRunManifest[],
|
|
45
|
+
pendingDeliveryCount: number,
|
|
46
|
+
sessionGeneration: number,
|
|
47
|
+
): SessionStateSnapshot {
|
|
48
|
+
const taskSummary: Record<string, number> = {};
|
|
49
|
+
for (const run of activeRuns) {
|
|
50
|
+
taskSummary[run.status] = (taskSummary[run.status] ?? 0) + 1;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
capturedAt: new Date().toISOString(),
|
|
54
|
+
activeRunIds: activeRuns.map((r) => r.runId),
|
|
55
|
+
pendingDeliveryCount,
|
|
56
|
+
sessionGeneration,
|
|
57
|
+
taskSummary,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import type { UsageState } from "../state/types.ts";
|
|
3
|
-
|
|
4
|
-
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
5
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function numberField(obj: Record<string, unknown>, keys: string[]): number | undefined {
|
|
9
|
-
for (const key of keys) {
|
|
10
|
-
const value = obj[key];
|
|
11
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
12
|
-
}
|
|
13
|
-
return undefined;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function usageFromValue(value: unknown): UsageState | undefined {
|
|
17
|
-
const obj = asRecord(value);
|
|
18
|
-
if (!obj) return undefined;
|
|
19
|
-
const direct: UsageState = {
|
|
20
|
-
input: numberField(obj, ["input", "inputTokens", "input_tokens"]),
|
|
21
|
-
output: numberField(obj, ["output", "outputTokens", "output_tokens"]),
|
|
22
|
-
cacheRead: numberField(obj, ["cacheRead", "cache_read", "cacheReadTokens", "cache_read_tokens"]),
|
|
23
|
-
cacheWrite: numberField(obj, ["cacheWrite", "cache_write", "cacheWriteTokens", "cache_write_tokens"]),
|
|
24
|
-
cost: numberField(obj, ["cost", "costUsd", "cost_usd"]),
|
|
25
|
-
turns: numberField(obj, ["turns", "turnCount", "turn_count"]),
|
|
26
|
-
};
|
|
27
|
-
if (Object.values(direct).some((entry) => entry !== undefined)) return direct;
|
|
28
|
-
for (const key of ["usage", "tokenUsage", "tokens", "stats"]) {
|
|
29
|
-
const nested = usageFromValue(obj[key]);
|
|
30
|
-
if (nested) return nested;
|
|
31
|
-
}
|
|
32
|
-
const message = asRecord(obj.message);
|
|
33
|
-
return message ? usageFromValue(message.usage) : undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function addUsage(total: UsageState, usage: UsageState): UsageState {
|
|
37
|
-
return {
|
|
38
|
-
input: (total.input ?? 0) + (usage.input ?? 0),
|
|
39
|
-
output: (total.output ?? 0) + (usage.output ?? 0),
|
|
40
|
-
cacheRead: (total.cacheRead ?? 0) + (usage.cacheRead ?? 0),
|
|
41
|
-
cacheWrite: (total.cacheWrite ?? 0) + (usage.cacheWrite ?? 0),
|
|
42
|
-
cost: (total.cost ?? 0) + (usage.cost ?? 0),
|
|
43
|
-
turns: (total.turns ?? 0) + (usage.turns ?? 0),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function compactUsage(total: UsageState, foundKeys: Set<keyof UsageState>): UsageState | undefined {
|
|
48
|
-
if (foundKeys.size === 0) return undefined;
|
|
49
|
-
const compact: UsageState = {};
|
|
50
|
-
for (const key of foundKeys) compact[key] = total[key];
|
|
51
|
-
return compact;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function parseSessionUsageFromJsonlText(text: string): UsageState | undefined {
|
|
55
|
-
let total: UsageState = {};
|
|
56
|
-
const foundKeys = new Set<keyof UsageState>();
|
|
57
|
-
for (const line of text.split(/\r?\n/)) {
|
|
58
|
-
const trimmed = line.trim();
|
|
59
|
-
if (!trimmed) continue;
|
|
60
|
-
try {
|
|
61
|
-
const usage = usageFromValue(JSON.parse(trimmed) as unknown);
|
|
62
|
-
if (!usage) continue;
|
|
63
|
-
for (const key of Object.keys(usage) as Array<keyof UsageState>) foundKeys.add(key);
|
|
64
|
-
total = addUsage(total, usage);
|
|
65
|
-
} catch {
|
|
66
|
-
// Session JSONL can contain partial/corrupt lines after interrupted workers.
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return compactUsage(total, foundKeys);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function parseSessionUsage(filePath: string): UsageState | undefined {
|
|
73
|
-
try {
|
|
74
|
-
if (!fs.existsSync(filePath)) return undefined;
|
|
75
|
-
return parseSessionUsageFromJsonlText(fs.readFileSync(filePath, "utf-8"));
|
|
76
|
-
} catch {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import type { UsageState } from "../state/types.ts";
|
|
3
|
+
|
|
4
|
+
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
5
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function numberField(obj: Record<string, unknown>, keys: string[]): number | undefined {
|
|
9
|
+
for (const key of keys) {
|
|
10
|
+
const value = obj[key];
|
|
11
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function usageFromValue(value: unknown): UsageState | undefined {
|
|
17
|
+
const obj = asRecord(value);
|
|
18
|
+
if (!obj) return undefined;
|
|
19
|
+
const direct: UsageState = {
|
|
20
|
+
input: numberField(obj, ["input", "inputTokens", "input_tokens"]),
|
|
21
|
+
output: numberField(obj, ["output", "outputTokens", "output_tokens"]),
|
|
22
|
+
cacheRead: numberField(obj, ["cacheRead", "cache_read", "cacheReadTokens", "cache_read_tokens"]),
|
|
23
|
+
cacheWrite: numberField(obj, ["cacheWrite", "cache_write", "cacheWriteTokens", "cache_write_tokens"]),
|
|
24
|
+
cost: numberField(obj, ["cost", "costUsd", "cost_usd"]),
|
|
25
|
+
turns: numberField(obj, ["turns", "turnCount", "turn_count"]),
|
|
26
|
+
};
|
|
27
|
+
if (Object.values(direct).some((entry) => entry !== undefined)) return direct;
|
|
28
|
+
for (const key of ["usage", "tokenUsage", "tokens", "stats"]) {
|
|
29
|
+
const nested = usageFromValue(obj[key]);
|
|
30
|
+
if (nested) return nested;
|
|
31
|
+
}
|
|
32
|
+
const message = asRecord(obj.message);
|
|
33
|
+
return message ? usageFromValue(message.usage) : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function addUsage(total: UsageState, usage: UsageState): UsageState {
|
|
37
|
+
return {
|
|
38
|
+
input: (total.input ?? 0) + (usage.input ?? 0),
|
|
39
|
+
output: (total.output ?? 0) + (usage.output ?? 0),
|
|
40
|
+
cacheRead: (total.cacheRead ?? 0) + (usage.cacheRead ?? 0),
|
|
41
|
+
cacheWrite: (total.cacheWrite ?? 0) + (usage.cacheWrite ?? 0),
|
|
42
|
+
cost: (total.cost ?? 0) + (usage.cost ?? 0),
|
|
43
|
+
turns: (total.turns ?? 0) + (usage.turns ?? 0),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function compactUsage(total: UsageState, foundKeys: Set<keyof UsageState>): UsageState | undefined {
|
|
48
|
+
if (foundKeys.size === 0) return undefined;
|
|
49
|
+
const compact: UsageState = {};
|
|
50
|
+
for (const key of foundKeys) compact[key] = total[key];
|
|
51
|
+
return compact;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function parseSessionUsageFromJsonlText(text: string): UsageState | undefined {
|
|
55
|
+
let total: UsageState = {};
|
|
56
|
+
const foundKeys = new Set<keyof UsageState>();
|
|
57
|
+
for (const line of text.split(/\r?\n/)) {
|
|
58
|
+
const trimmed = line.trim();
|
|
59
|
+
if (!trimmed) continue;
|
|
60
|
+
try {
|
|
61
|
+
const usage = usageFromValue(JSON.parse(trimmed) as unknown);
|
|
62
|
+
if (!usage) continue;
|
|
63
|
+
for (const key of Object.keys(usage) as Array<keyof UsageState>) foundKeys.add(key);
|
|
64
|
+
total = addUsage(total, usage);
|
|
65
|
+
} catch {
|
|
66
|
+
// Session JSONL can contain partial/corrupt lines after interrupted workers.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return compactUsage(total, foundKeys);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function parseSessionUsage(filePath: string): UsageState | undefined {
|
|
73
|
+
try {
|
|
74
|
+
if (!fs.existsSync(filePath)) return undefined;
|
|
75
|
+
return parseSessionUsageFromJsonlText(fs.readFileSync(filePath, "utf-8"));
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import { redactSecrets } from "../utils/redaction.ts";
|
|
4
|
-
|
|
5
|
-
export interface SidechainEntry {
|
|
6
|
-
isSidechain: true;
|
|
7
|
-
agentId: string;
|
|
8
|
-
type: string;
|
|
9
|
-
message: unknown;
|
|
10
|
-
timestamp: string;
|
|
11
|
-
cwd: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function writeSidechainEntry(filePath: string, entry: Omit<SidechainEntry, "isSidechain" | "timestamp">): void {
|
|
15
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
16
|
-
fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ isSidechain: true, timestamp: new Date().toISOString(), ...entry }))}\n`, "utf-8");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function sidechainOutputPath(stateRoot: string, taskId: string): string {
|
|
20
|
-
return path.join(stateRoot, "agents", taskId, "sidechain.output.jsonl");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function eventToSidechainType(event: unknown): string | undefined {
|
|
24
|
-
if (!event || typeof event !== "object" || Array.isArray(event)) return undefined;
|
|
25
|
-
const type = (event as { type?: unknown }).type;
|
|
26
|
-
if (type === "message_start" || type === "message_update" || type === "message_end") return "message";
|
|
27
|
-
if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") return "tool";
|
|
28
|
-
return typeof type === "string" ? type : undefined;
|
|
29
|
-
}
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { redactSecrets } from "../utils/redaction.ts";
|
|
4
|
+
|
|
5
|
+
export interface SidechainEntry {
|
|
6
|
+
isSidechain: true;
|
|
7
|
+
agentId: string;
|
|
8
|
+
type: string;
|
|
9
|
+
message: unknown;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function writeSidechainEntry(filePath: string, entry: Omit<SidechainEntry, "isSidechain" | "timestamp">): void {
|
|
15
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
16
|
+
fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ isSidechain: true, timestamp: new Date().toISOString(), ...entry }))}\n`, "utf-8");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function sidechainOutputPath(stateRoot: string, taskId: string): string {
|
|
20
|
+
return path.join(stateRoot, "agents", taskId, "sidechain.output.jsonl");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function eventToSidechainType(event: unknown): string | undefined {
|
|
24
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) return undefined;
|
|
25
|
+
const type = (event as { type?: unknown }).type;
|
|
26
|
+
if (type === "message_start" || type === "message_update" || type === "message_end") return "message";
|
|
27
|
+
if (type === "tool_execution_start" || type === "tool_execution_update" || type === "tool_execution_end") return "tool";
|
|
28
|
+
return typeof type === "string" ? type : undefined;
|
|
29
|
+
}
|