@narumitw/pi-subagents 0.49.3 → 0.52.0
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/README.md +362 -53
- package/package.json +10 -7
- package/src/adaptive-scheduler.ts +224 -0
- package/src/admission-benchmark.ts +95 -0
- package/src/admission-policy.ts +78 -0
- package/src/agent-projection.ts +53 -0
- package/src/agents.ts +58 -1
- package/src/auto-transport.ts +114 -0
- package/src/blocking-status.ts +63 -0
- package/src/capabilities.ts +145 -0
- package/src/capability-grant.ts +115 -0
- package/src/capability-router.ts +107 -0
- package/src/completion-delivery.ts +257 -0
- package/src/config-status.ts +221 -0
- package/src/config-ui.ts +215 -236
- package/src/consult-resources.ts +4 -27
- package/src/consult.ts +9 -1
- package/src/create-stateful-transport.ts +55 -0
- package/src/delegation-contract.ts +417 -0
- package/src/execution-plan.ts +322 -0
- package/src/execution-profiles.ts +95 -0
- package/src/execution-ui.ts +320 -0
- package/src/execution.ts +1098 -158
- package/src/in-process-transport.ts +269 -25
- package/src/inspect-render.ts +101 -1
- package/src/inspect.ts +321 -3
- package/src/integration-controller.ts +98 -0
- package/src/limits.ts +3 -0
- package/src/orchestration-metrics.ts +109 -0
- package/src/outcome.ts +61 -0
- package/src/panel-child-group.ts +35 -0
- package/src/panel-contract.ts +343 -0
- package/src/panel-evidence.ts +59 -0
- package/src/panel-execution.ts +770 -0
- package/src/panel-failure.ts +56 -0
- package/src/panel-planning.ts +175 -0
- package/src/panel-prompts.ts +132 -0
- package/src/panel-reconciliation.ts +57 -0
- package/src/panel-render.ts +103 -0
- package/src/parallel-limit-ui.ts +112 -0
- package/src/params.ts +179 -3
- package/src/persistence.ts +182 -32
- package/src/prompt-resources.ts +38 -0
- package/src/registry-types.ts +175 -0
- package/src/registry.ts +466 -143
- package/src/render.ts +72 -6
- package/src/result-contract.ts +416 -0
- package/src/retained-semantic-state.ts +100 -0
- package/src/rpc-timeout-finalization.ts +207 -0
- package/src/rpc-transport-metadata.ts +65 -0
- package/src/rpc-transport.ts +990 -0
- package/src/rpc-turn-capture.ts +142 -0
- package/src/runner-result.ts +55 -0
- package/src/runner-usage.ts +48 -0
- package/src/runner.ts +325 -73
- package/src/semantic-snapshot.ts +214 -0
- package/src/settings.ts +254 -35
- package/src/spawn-idempotency.ts +61 -0
- package/src/stateful-config.ts +13 -0
- package/src/stateful-guidance.ts +1 -0
- package/src/stateful-lifecycle.ts +45 -2
- package/src/stateful-limit-ui.ts +246 -0
- package/src/stateful-limits.ts +96 -0
- package/src/stateful-prompt.ts +11 -2
- package/src/stateful-render.ts +48 -3
- package/src/stateful.ts +467 -357
- package/src/subagents.ts +114 -46
- package/src/subprocess-transport.ts +64 -5
- package/src/supervision.ts +103 -0
- package/src/timeout-checkpoint.ts +305 -0
- package/src/timeout-finalization.ts +75 -0
- package/src/transport-types.ts +68 -0
- package/src/transport-ui.ts +169 -0
- package/src/transport.ts +16 -4
- package/src/turn-budget.ts +109 -0
- package/src/verification-policy.ts +67 -0
- package/src/work-item-ledger.ts +931 -0
- package/src/work-item-persistence.ts +223 -0
- package/src/workflow-planning.ts +162 -0
- package/src/workflow-tree-identity.ts +289 -0
- package/src/workflow-ui.ts +61 -0
- package/src/workflow-verification.ts +296 -0
- package/src/workspace.ts +69 -12
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { DEFAULT_MAX_OUTPUT_BYTES, truncateUtf8 } from "./limits.js";
|
|
2
|
+
import { boundedPrivateText } from "./safe-text.js";
|
|
3
|
+
import { journalMessages, TimeoutProgressJournal } from "./timeout-checkpoint.js";
|
|
4
|
+
import { emptyTransportUsage, type TransportUsage } from "./transport-types.js";
|
|
5
|
+
import type { TurnBudgetMonitor } from "./turn-budget.js";
|
|
6
|
+
|
|
7
|
+
export interface RpcTurnCapture {
|
|
8
|
+
output: string;
|
|
9
|
+
partial: string;
|
|
10
|
+
stopReason?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
provider?: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
usage: TransportUsage;
|
|
15
|
+
firstActivityAt?: number;
|
|
16
|
+
journal: TimeoutProgressJournal;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createRpcTurnCapture(): RpcTurnCapture {
|
|
20
|
+
return {
|
|
21
|
+
output: "",
|
|
22
|
+
partial: "",
|
|
23
|
+
usage: emptyTransportUsage(),
|
|
24
|
+
journal: new TimeoutProgressJournal(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function captureRpcEvent(event: unknown, capture: RpcTurnCapture): void {
|
|
29
|
+
if (!isRecord(event)) return;
|
|
30
|
+
if (event.type === "tool_execution_start") {
|
|
31
|
+
capture.journal.recordToolCall(
|
|
32
|
+
typeof event.toolCallId === "string" ? event.toolCallId : "",
|
|
33
|
+
typeof event.toolName === "string" ? event.toolName : "tool",
|
|
34
|
+
isRecord(event.args) ? event.args : {},
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (event.type === "tool_execution_end") {
|
|
38
|
+
const result = isRecord(event.result) ? event.result : {};
|
|
39
|
+
capture.journal.recordToolResult(
|
|
40
|
+
typeof event.toolCallId === "string" ? event.toolCallId : "",
|
|
41
|
+
typeof event.toolName === "string" ? event.toolName : "tool",
|
|
42
|
+
{ content: result.content, isError: event.isError },
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (event.type === "message_update") {
|
|
46
|
+
const delta = event.assistantMessageEvent;
|
|
47
|
+
if (isRecord(delta) && delta.type === "text_delta" && typeof delta.delta === "string") {
|
|
48
|
+
capture.partial = truncateUtf8(
|
|
49
|
+
`${capture.partial}${delta.delta}`,
|
|
50
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
51
|
+
).text;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (event.type !== "message_end" || !isRecord(event.message)) return;
|
|
55
|
+
const candidate = event.message;
|
|
56
|
+
if (candidate.role !== "toolResult") journalMessages(capture.journal, [candidate]);
|
|
57
|
+
if (candidate.role !== "assistant") return;
|
|
58
|
+
capture.output = truncateUtf8(
|
|
59
|
+
assistantText(candidate.content) || capture.partial,
|
|
60
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
61
|
+
).text;
|
|
62
|
+
capture.partial = capture.output;
|
|
63
|
+
capture.stopReason = typeof candidate.stopReason === "string" ? candidate.stopReason : undefined;
|
|
64
|
+
capture.error =
|
|
65
|
+
typeof candidate.errorMessage === "string"
|
|
66
|
+
? boundedPrivateText(candidate.errorMessage, 4 * 1024)
|
|
67
|
+
: undefined;
|
|
68
|
+
capture.provider =
|
|
69
|
+
typeof candidate.provider === "string"
|
|
70
|
+
? boundedPrivateText(candidate.provider, 256)
|
|
71
|
+
: undefined;
|
|
72
|
+
const responseModel =
|
|
73
|
+
typeof candidate.responseModel === "string"
|
|
74
|
+
? candidate.responseModel
|
|
75
|
+
: typeof candidate.model === "string"
|
|
76
|
+
? candidate.model
|
|
77
|
+
: undefined;
|
|
78
|
+
capture.model = responseModel ? boundedPrivateText(responseModel, 256) : undefined;
|
|
79
|
+
capture.usage.turns++;
|
|
80
|
+
addUsage(capture.usage, candidate.usage);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function observeRpcBudgetEvent(event: unknown, monitor: TurnBudgetMonitor): void {
|
|
84
|
+
if (!isRecord(event)) return;
|
|
85
|
+
if (event.type === "tool_execution_end") monitor.recordActivity();
|
|
86
|
+
if (event.type !== "message_end" || !isRecord(event.message)) return;
|
|
87
|
+
const message = event.message;
|
|
88
|
+
if (message.role === "toolResult") {
|
|
89
|
+
monitor.recordActivity();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (message.role !== "assistant") return;
|
|
93
|
+
monitor.recordToolCalls(assistantToolCallCount(message.content));
|
|
94
|
+
monitor.recordAssistantTurn(
|
|
95
|
+
typeof message.stopReason === "string" ? message.stopReason : undefined,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function assistantToolCallCount(content: unknown): number {
|
|
100
|
+
if (!Array.isArray(content)) return 0;
|
|
101
|
+
return content.filter((part) => isRecord(part) && part.type === "toolCall").length;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function addUsage(target: TransportUsage, value: unknown): void {
|
|
105
|
+
if (!isRecord(value)) return;
|
|
106
|
+
target.input += safeNonNegative(value.input);
|
|
107
|
+
target.output += safeNonNegative(value.output);
|
|
108
|
+
target.cacheRead += safeNonNegative(value.cacheRead);
|
|
109
|
+
target.cacheWrite += safeNonNegative(value.cacheWrite);
|
|
110
|
+
const reportedTotal = safeNonNegative(value.totalTokens);
|
|
111
|
+
target.totalTokens +=
|
|
112
|
+
reportedTotal ||
|
|
113
|
+
safeNonNegative(value.input) +
|
|
114
|
+
safeNonNegative(value.output) +
|
|
115
|
+
safeNonNegative(value.cacheRead) +
|
|
116
|
+
safeNonNegative(value.cacheWrite);
|
|
117
|
+
if (isRecord(value.cost)) target.cost += safeNonNegative(value.cost.total);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function safeNonNegative(value: unknown): number {
|
|
121
|
+
return typeof value === "number" &&
|
|
122
|
+
Number.isFinite(value) &&
|
|
123
|
+
value >= 0 &&
|
|
124
|
+
value <= Number.MAX_SAFE_INTEGER
|
|
125
|
+
? value
|
|
126
|
+
: 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assistantText(value: unknown): string {
|
|
130
|
+
if (typeof value === "string") return value;
|
|
131
|
+
if (!Array.isArray(value)) return "";
|
|
132
|
+
return value
|
|
133
|
+
.flatMap((part) => {
|
|
134
|
+
if (!isRecord(part)) return [];
|
|
135
|
+
return part.type === "text" && typeof part.text === "string" ? [part.text] : [];
|
|
136
|
+
})
|
|
137
|
+
.join("\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
141
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
142
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Message } from "@earendil-works/pi-ai";
|
|
2
|
+
import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
|
|
3
|
+
import type { SingleResult } from "./runner.js";
|
|
4
|
+
import { formatTimeoutCheckpoint } from "./timeout-checkpoint.js";
|
|
5
|
+
|
|
6
|
+
export function getFinalOutput(messages: Message[]): string {
|
|
7
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
8
|
+
const msg = messages[i];
|
|
9
|
+
if (msg?.role === "assistant") {
|
|
10
|
+
const text = msg.content
|
|
11
|
+
.filter((part) => part.type === "text")
|
|
12
|
+
.map((part) => part.text)
|
|
13
|
+
.join("\n");
|
|
14
|
+
if (text) return text;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getResultFinalOutput(result: SingleResult): string {
|
|
21
|
+
return result.finalOutput ?? getFinalOutput(result.messages);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isResultError(result: SingleResult): boolean {
|
|
25
|
+
return (
|
|
26
|
+
(result.exitCode !== 0 && result.exitCode !== -1) ||
|
|
27
|
+
result.timedOut === true ||
|
|
28
|
+
result.stopReason === "timeout" ||
|
|
29
|
+
result.stopReason === "error" ||
|
|
30
|
+
result.stopReason === "aborted"
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatResultFailure(result: SingleResult): string {
|
|
35
|
+
const error = result.errorMessage || result.stderr.trim();
|
|
36
|
+
const output = getResultFinalOutput(result);
|
|
37
|
+
const sections = [error];
|
|
38
|
+
if (result.timeoutSummary) sections.push(`Timed-out work summary:\n${result.timeoutSummary}`);
|
|
39
|
+
else if (output) sections.push(`Partial output:\n${output}`);
|
|
40
|
+
if (result.partialOutput && result.partialOutput !== output) {
|
|
41
|
+
sections.push(`Partial output before finalization:\n${result.partialOutput}`);
|
|
42
|
+
}
|
|
43
|
+
if (result.termination && !result.timeoutSummary) {
|
|
44
|
+
sections.push(
|
|
45
|
+
`Termination checkpoint:\n${formatTimeoutCheckpoint(result.termination.checkpoint)}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
if (result.timeoutSummaryError) {
|
|
49
|
+
sections.push(`Summary finalization failed: ${result.timeoutSummaryError}`);
|
|
50
|
+
}
|
|
51
|
+
return truncateUtf8(
|
|
52
|
+
sections.filter(Boolean).join("\n\n") || "(no output)",
|
|
53
|
+
DEFAULT_MAX_CONTEXT_BYTES,
|
|
54
|
+
).text;
|
|
55
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface UsageStats {
|
|
2
|
+
input: number;
|
|
3
|
+
output: number;
|
|
4
|
+
cacheRead: number;
|
|
5
|
+
cacheWrite: number;
|
|
6
|
+
cost: number;
|
|
7
|
+
costInput?: number;
|
|
8
|
+
costOutput?: number;
|
|
9
|
+
costCacheRead?: number;
|
|
10
|
+
costCacheWrite?: number;
|
|
11
|
+
totalTokens?: number;
|
|
12
|
+
contextTokens: number;
|
|
13
|
+
turns: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const MAX_USAGE_VALUE = Number.MAX_SAFE_INTEGER;
|
|
17
|
+
|
|
18
|
+
export function protocolUsageCount(value: unknown): number {
|
|
19
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function protocolUsageCost(value: unknown): number {
|
|
23
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
24
|
+
? Math.min(value, MAX_USAGE_VALUE)
|
|
25
|
+
: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function addUsageValue(current: number, addition: number): number {
|
|
29
|
+
return Math.min(MAX_USAGE_VALUE, current + addition);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function mergeUsageStats(target: UsageStats, addition: UsageStats): void {
|
|
33
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite", "cost", "turns"] as const) {
|
|
34
|
+
target[key] = addUsageValue(target[key], addition[key]);
|
|
35
|
+
}
|
|
36
|
+
for (const key of [
|
|
37
|
+
"costInput",
|
|
38
|
+
"costOutput",
|
|
39
|
+
"costCacheRead",
|
|
40
|
+
"costCacheWrite",
|
|
41
|
+
"totalTokens",
|
|
42
|
+
] as const) {
|
|
43
|
+
if (addition[key] !== undefined) {
|
|
44
|
+
target[key] = addUsageValue(target[key] ?? 0, addition[key]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
target.contextTokens = addition.contextTokens || target.contextTokens;
|
|
48
|
+
}
|