@co0ontty/wand 4.83.1 → 4.84.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 +4 -0
- package/dist/agent-dispatch.js +5 -1
- package/dist/ai-team-availability.d.ts +1 -1
- package/dist/ai-team-prompts.js +13 -4
- package/dist/ai-team-runner.d.ts +80 -5
- package/dist/ai-team-runner.js +332 -55
- package/dist/ai-team-types.d.ts +92 -5
- package/dist/ai-team-types.js +9 -2
- package/dist/build-info.json +3 -3
- package/dist/cli-api.d.ts +1 -0
- package/dist/cli-api.js +3 -0
- package/dist/cli.js +18 -1
- package/dist/commit-task-archive.d.ts +3 -1
- package/dist/commit-task-archive.js +21 -10
- package/dist/config.d.ts +1 -1
- package/dist/config.js +73 -0
- package/dist/conversation-mentions.d.ts +16 -0
- package/dist/conversation-mentions.js +32 -0
- package/dist/conversation-service.d.ts +90 -0
- package/dist/conversation-service.js +759 -0
- package/dist/conversation-session-preview.d.ts +9 -0
- package/dist/conversation-session-preview.js +47 -0
- package/dist/conversation-task-preview.d.ts +5 -0
- package/dist/conversation-task-preview.js +8 -0
- package/dist/conversation-types.d.ts +80 -0
- package/dist/conversation-types.js +7 -0
- package/dist/core-extension-host.d.ts +27 -0
- package/dist/core-extension-host.js +138 -0
- package/dist/core-runner.d.ts +86 -0
- package/dist/core-runner.js +757 -0
- package/dist/core-status-cli.d.ts +9 -0
- package/dist/core-status-cli.js +280 -0
- package/dist/core-turn-tracker.d.ts +12 -0
- package/dist/core-turn-tracker.js +36 -0
- package/dist/decision-runner.d.ts +6 -0
- package/dist/decision-runner.js +3 -1
- package/dist/decision-tool.d.ts +2 -0
- package/dist/decision-tool.js +49 -22
- package/dist/env-utils.d.ts +7 -3
- package/dist/env-utils.js +30 -5
- package/dist/git-quick-commit.d.ts +5 -0
- package/dist/git-quick-commit.js +9 -3
- package/dist/git-utils.js +2 -1
- package/dist/harness-engine.d.ts +110 -0
- package/dist/harness-engine.js +331 -0
- package/dist/mission-diff.js +2 -1
- package/dist/model-auto-assign.d.ts +50 -0
- package/dist/model-auto-assign.js +163 -0
- package/dist/model-group-runner.d.ts +4 -0
- package/dist/model-group-runner.js +72 -0
- package/dist/model-groups.d.ts +38 -0
- package/dist/model-groups.js +138 -0
- package/dist/models.d.ts +12 -0
- package/dist/models.js +102 -8
- package/dist/npm-update-utils.js +2 -1
- package/dist/openrouter-free-cli.d.ts +25 -0
- package/dist/openrouter-free-cli.js +109 -0
- package/dist/openrouter-free-models.d.ts +85 -0
- package/dist/openrouter-free-models.js +458 -0
- package/dist/openrouter-free-selection.d.ts +5 -0
- package/dist/openrouter-free-selection.js +7 -0
- package/dist/path-repair.d.ts +10 -0
- package/dist/path-repair.js +50 -5
- package/dist/pi-auto-resources.d.ts +12 -0
- package/dist/pi-auto-resources.js +151 -0
- package/dist/pi-execution-types.d.ts +48 -0
- package/dist/pi-execution-types.js +1 -0
- package/dist/pi-execution.d.ts +9 -0
- package/dist/pi-execution.js +187 -0
- package/dist/pi-extension-resources.d.ts +13 -0
- package/dist/pi-extension-resources.js +71 -0
- package/dist/pi-openrouter-free-bridge.d.ts +2 -0
- package/dist/pi-openrouter-free-bridge.js +80 -0
- package/dist/pi-resource-bridge.d.ts +2 -0
- package/dist/pi-resource-bridge.js +73 -0
- package/dist/pi-resource-catalog.d.ts +18 -0
- package/dist/pi-resource-catalog.js +82 -0
- package/dist/pi-resource-recommendation.d.ts +29 -0
- package/dist/pi-resource-recommendation.js +169 -0
- package/dist/pi-resource-run.d.ts +8 -0
- package/dist/pi-resource-run.js +28 -0
- package/dist/pi-session-settings.d.ts +157 -0
- package/dist/pi-session-settings.js +166 -0
- package/dist/platform-action-catalog.d.ts +24 -0
- package/dist/platform-action-catalog.js +141 -0
- package/dist/platform-action-service.d.ts +37 -0
- package/dist/platform-action-service.js +138 -0
- package/dist/platform-action-types.d.ts +125 -0
- package/dist/platform-action-types.js +7 -0
- package/dist/process-manager.d.ts +21 -1
- package/dist/process-manager.js +168 -15
- package/dist/provider-catalog.js +1 -1
- package/dist/provider-cli-updater.js +29 -8
- package/dist/provider-history-scanner.d.ts +9 -0
- package/dist/provider-history-scanner.js +107 -1
- package/dist/retention.d.ts +19 -7
- package/dist/retention.js +171 -28
- package/dist/server-ai-team-routes.js +6 -2
- package/dist/server-conversation-routes.d.ts +5 -0
- package/dist/server-conversation-routes.js +91 -0
- package/dist/server-employee-routes.d.ts +2 -0
- package/dist/server-employee-routes.js +85 -11
- package/dist/server-pi-recommendation-routes.d.ts +13 -0
- package/dist/server-pi-recommendation-routes.js +67 -0
- package/dist/server-resume-routes.js +1 -1
- package/dist/server-session-routes.d.ts +4 -3
- package/dist/server-session-routes.js +182 -37
- package/dist/server-settings-routes.d.ts +5 -0
- package/dist/server-settings-routes.js +46 -0
- package/dist/server-task-routes.js +49 -4
- package/dist/server-team-dispatch-routes.d.ts +19 -0
- package/dist/server-team-dispatch-routes.js +71 -0
- package/dist/server-update-routes.d.ts +1 -1
- package/dist/server-update-routes.js +10 -5
- package/dist/server-workspace-routes.js +44 -24
- package/dist/server.d.ts +1 -0
- package/dist/server.js +148 -30
- package/dist/session-ai-context.d.ts +5 -1
- package/dist/session-ai-context.js +9 -4
- package/dist/session-registry.d.ts +4 -0
- package/dist/session-registry.js +47 -13
- package/dist/session-transport.js +8 -1
- package/dist/settings-web-access.d.ts +15 -0
- package/dist/settings-web-access.js +37 -0
- package/dist/silicon-employee-defaults.d.ts +29 -0
- package/dist/silicon-employee-defaults.js +41 -0
- package/dist/silicon-employee-dispatch.d.ts +5 -1
- package/dist/silicon-employee-dispatch.js +10 -4
- package/dist/silicon-employee-draft.d.ts +21 -4
- package/dist/silicon-employee-draft.js +103 -16
- package/dist/storage.d.ts +73 -2
- package/dist/storage.js +362 -26
- package/dist/structured-claude-protocol.js +1 -1
- package/dist/structured-client-protocol.js +11 -33
- package/dist/structured-content.d.ts +23 -0
- package/dist/structured-content.js +72 -0
- package/dist/structured-failure.d.ts +16 -0
- package/dist/structured-failure.js +36 -0
- package/dist/structured-pi-adapter.d.ts +8 -3
- package/dist/structured-pi-adapter.js +131 -30
- package/dist/structured-provider-common.d.ts +6 -0
- package/dist/structured-provider-common.js +23 -1
- package/dist/structured-runner.d.ts +45 -0
- package/dist/structured-session-manager.d.ts +78 -4
- package/dist/structured-session-manager.js +513 -129
- package/dist/structured-thinking.d.ts +15 -0
- package/dist/structured-thinking.js +46 -0
- package/dist/subagent-dispatch.d.ts +56 -0
- package/dist/subagent-dispatch.js +102 -0
- package/dist/system-employee.d.ts +4 -3
- package/dist/system-employee.js +7 -4
- package/dist/task-retention.d.ts +23 -0
- package/dist/task-retention.js +69 -0
- package/dist/task-types.d.ts +4 -0
- package/dist/task-types.js +3 -0
- package/dist/team-dispatch.d.ts +101 -0
- package/dist/team-dispatch.js +269 -0
- package/dist/tool-preview.js +7 -1
- package/dist/tui/commands.js +50 -1
- package/dist/tui/ipc-protocol.d.ts +1 -1
- package/dist/tui/ipc-server.d.ts +4 -0
- package/dist/tui/ipc-server.js +23 -0
- package/dist/turn-heartbeat.d.ts +35 -0
- package/dist/turn-heartbeat.js +64 -0
- package/dist/types.d.ts +147 -1
- package/dist/user-profile.d.ts +33 -0
- package/dist/user-profile.js +70 -0
- package/dist/web-ui/chat-message-meta.d.ts +11 -0
- package/dist/web-ui/chat-message-meta.js +47 -0
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/scripts.js +795 -95
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -6808
- package/dist/web-ui/content/vendor/qrcode/qrcode.bundle.js +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +5 -5
- package/dist/web-ui/index.d.ts +1 -1
- package/dist/web-ui/index.js +3 -3
- package/dist/web-ui/markdown.d.ts +2 -0
- package/dist/web-ui/markdown.js +111 -0
- package/dist/web-ui/page.d.ts +4 -0
- package/dist/web-ui/page.js +8 -0
- package/dist/web-ui/react/local-preview/controller.d.ts +30 -0
- package/dist/web-ui/react/local-preview/controller.js +159 -0
- package/dist/web-ui/running-activity.d.ts +63 -0
- package/dist/web-ui/running-activity.js +134 -0
- package/dist/web-ui/styles.js +2 -11
- package/package.json +7 -2
|
@@ -22,3 +22,26 @@ export declare function canonicalizeStructuredImagePart(value: unknown): Structu
|
|
|
22
22
|
export declare function contentHasStructuredImage(content: unknown): boolean;
|
|
23
23
|
/** Preserve both Responses content parts and arbitrary structured tool output. */
|
|
24
24
|
export declare function normalizeStructuredToolResultContent(content: unknown): string | StructuredContentPart[];
|
|
25
|
+
/**
|
|
26
|
+
* 将工具执行产生的原始结果(无论是纯文本、JSON、还是带图片的多模态数组)统一归一化为 Wand 内容块格式。
|
|
27
|
+
* - 纯文本结果(或仅包含 text part 的数组):统一合并为格式规整的纯文本字符串,避免无谓的数组包装;
|
|
28
|
+
* - 包含图片/富媒体的结果:归一化为 `StructuredContentPart[]`,并将图片收敛为规范的 image part;
|
|
29
|
+
* - 针对 `{ content: [...] }` / `{ result: ... }` / `{ output: ... }` 等常见包装层自动解包。
|
|
30
|
+
*/
|
|
31
|
+
export declare function canonicalizeToolResultContent(value: unknown): string | StructuredContentPart[];
|
|
32
|
+
export type AgentContentPart = {
|
|
33
|
+
type: "text";
|
|
34
|
+
text: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: "image";
|
|
37
|
+
data: string;
|
|
38
|
+
mimeType: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* 将 Wand 历史存储或传输层中的 tool_result content 还原为 Agent/LLM SDK(如 @earendil-works/pi-ai)
|
|
42
|
+
* 可识别的 content parts 数组。
|
|
43
|
+
* - 文本转换为 `{ type: "text", text }`;
|
|
44
|
+
* - 包含 Base64 的图片转换为 `{ type: "image", data, mimeType }`;
|
|
45
|
+
* - 保证在多轮历史回放或断点恢复时,模型能够感知之前工具返回的多模态图片。
|
|
46
|
+
*/
|
|
47
|
+
export declare function toolResultContentToAgentParts(content: unknown): AgentContentPart[];
|
|
@@ -100,3 +100,75 @@ export function normalizeStructuredToolResultContent(content) {
|
|
|
100
100
|
}
|
|
101
101
|
return typeof content === "undefined" || content === null ? "" : String(content);
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* 将工具执行产生的原始结果(无论是纯文本、JSON、还是带图片的多模态数组)统一归一化为 Wand 内容块格式。
|
|
105
|
+
* - 纯文本结果(或仅包含 text part 的数组):统一合并为格式规整的纯文本字符串,避免无谓的数组包装;
|
|
106
|
+
* - 包含图片/富媒体的结果:归一化为 `StructuredContentPart[]`,并将图片收敛为规范的 image part;
|
|
107
|
+
* - 针对 `{ content: [...] }` / `{ result: ... }` / `{ output: ... }` 等常见包装层自动解包。
|
|
108
|
+
*/
|
|
109
|
+
export function canonicalizeToolResultContent(value) {
|
|
110
|
+
const record = asRecord(value);
|
|
111
|
+
const raw = record && "content" in record
|
|
112
|
+
? record.content
|
|
113
|
+
: record && "output" in record && typeof record.output === "string"
|
|
114
|
+
? record.output
|
|
115
|
+
: value;
|
|
116
|
+
const normalized = normalizeStructuredToolResultContent(raw);
|
|
117
|
+
if (typeof normalized === "string")
|
|
118
|
+
return normalized;
|
|
119
|
+
if (normalized.length > 0 && normalized.every((part) => part.type === "text" && typeof part.text === "string")) {
|
|
120
|
+
return normalized
|
|
121
|
+
.map((part) => part.text)
|
|
122
|
+
.filter((text) => text.length > 0)
|
|
123
|
+
.join("\n");
|
|
124
|
+
}
|
|
125
|
+
return normalized;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 将 Wand 历史存储或传输层中的 tool_result content 还原为 Agent/LLM SDK(如 @earendil-works/pi-ai)
|
|
129
|
+
* 可识别的 content parts 数组。
|
|
130
|
+
* - 文本转换为 `{ type: "text", text }`;
|
|
131
|
+
* - 包含 Base64 的图片转换为 `{ type: "image", data, mimeType }`;
|
|
132
|
+
* - 保证在多轮历史回放或断点恢复时,模型能够感知之前工具返回的多模态图片。
|
|
133
|
+
*/
|
|
134
|
+
export function toolResultContentToAgentParts(content) {
|
|
135
|
+
if (typeof content === "string") {
|
|
136
|
+
return [{ type: "text", text: content }];
|
|
137
|
+
}
|
|
138
|
+
if (!Array.isArray(content)) {
|
|
139
|
+
const text = typeof content === "undefined" || content === null ? "" : String(content);
|
|
140
|
+
return [{ type: "text", text }];
|
|
141
|
+
}
|
|
142
|
+
const parts = [];
|
|
143
|
+
for (const part of content) {
|
|
144
|
+
if (!part || typeof part !== "object")
|
|
145
|
+
continue;
|
|
146
|
+
const record = part;
|
|
147
|
+
if (record.type === "text" && typeof record.text === "string") {
|
|
148
|
+
parts.push({ type: "text", text: record.text });
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (isStructuredImagePart(record)) {
|
|
152
|
+
const canonical = canonicalizeStructuredImagePart(record);
|
|
153
|
+
const source = asRecord(canonical?.source);
|
|
154
|
+
if (source && source.type === "base64" && typeof source.data === "string" && source.data) {
|
|
155
|
+
parts.push({
|
|
156
|
+
type: "image",
|
|
157
|
+
data: source.data,
|
|
158
|
+
mimeType: typeof source.media_type === "string" && source.media_type ? source.media_type : "image/png",
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
parts.push({ type: "text", text: JSON.stringify(record) });
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
parts.push({ type: "text", text: String(record) });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (parts.length === 0) {
|
|
171
|
+
return [{ type: "text", text: "" }];
|
|
172
|
+
}
|
|
173
|
+
return parts;
|
|
174
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StructuredRunnerResult, StructuredRunnerTurnState } from "./structured-runner.js";
|
|
2
|
+
export type ProviderRejectionKind = "quota" | "authentication" | "rate-limit" | "model-unavailable";
|
|
3
|
+
export interface StructuredFailure {
|
|
4
|
+
kind: ProviderRejectionKind | "spawn" | "preflight" | "runtime" | "cancelled";
|
|
5
|
+
delivery: "rejected" | "accepted" | "unknown";
|
|
6
|
+
retryable: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Only call for a provider protocol error, never assistant text, stderr or a caught transport error. */
|
|
9
|
+
export declare function classifyProviderRejection(message: string | null | undefined): ProviderRejectionKind | null;
|
|
10
|
+
/** Monotonic observations matter: a final error can replace earlier usage or content. */
|
|
11
|
+
export declare function hasStructuredExecutionProgress(state: StructuredRunnerTurnState): boolean;
|
|
12
|
+
/** Shared by model groups, employee fallback and team startup facts. Missing facts never authorize replay. */
|
|
13
|
+
export declare function classifyStructuredFailure(result: StructuredRunnerResult, observed?: {
|
|
14
|
+
output?: boolean;
|
|
15
|
+
progress?: boolean;
|
|
16
|
+
}): StructuredFailure | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Only call for a provider protocol error, never assistant text, stderr or a caught transport error. */
|
|
2
|
+
export function classifyProviderRejection(message) {
|
|
3
|
+
const text = message?.trim() ?? "";
|
|
4
|
+
if (!text || /\b(?:408|409|5\d\d)\b|fetch failed|timed?\s*out|ECONN\w*|socket|network error/i.test(text))
|
|
5
|
+
return null;
|
|
6
|
+
if (/insufficient[_ -](?:quota|credits|balance)|quota[_ -](?:exceeded|exhausted)|credit balance is too low|(?:usage|spending) limit (?:has been )?(?:reached|exceeded)|you have hit your usage limit/i.test(text)
|
|
7
|
+
|| /^(?:错误[::]\s*)?(?:积分|额度|余额)(?:已)?(?:耗尽|用尽|不足)(?:[,,::。.!!\s].*)?$/.test(text))
|
|
8
|
+
return "quota";
|
|
9
|
+
if (/\b(?:invalid_api_key|authentication_error|invalid api key|incorrect api key|unauthorized)\b/i.test(text))
|
|
10
|
+
return "authentication";
|
|
11
|
+
if (/\b(?:rate_limit_exceeded|rate_limit_error|rate limit exceeded|too many requests)\b/i.test(text))
|
|
12
|
+
return "rate-limit";
|
|
13
|
+
if (/\b(?:model_not_found|model not found|unknown model)\b/i.test(text))
|
|
14
|
+
return "model-unavailable";
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/** Monotonic observations matter: a final error can replace earlier usage or content. */
|
|
18
|
+
export function hasStructuredExecutionProgress(state) {
|
|
19
|
+
return !!state.result.trim() || state.blocks.some((block) => block.type === "text" ? !!block.text.trim()
|
|
20
|
+
: block.type === "thinking" ? !!block.thinking.trim() : true)
|
|
21
|
+
|| Object.values(state.usage ?? {}).some((value) => typeof value === "number" && value > 0);
|
|
22
|
+
}
|
|
23
|
+
/** Shared by model groups, employee fallback and team startup facts. Missing facts never authorize replay. */
|
|
24
|
+
export function classifyStructuredFailure(result, observed = {}) {
|
|
25
|
+
if (!result.spawnError && !result.primaryError && !result.signal && (result.exitCode === null || result.exitCode === 0))
|
|
26
|
+
return null;
|
|
27
|
+
const progressed = observed.progress || hasStructuredExecutionProgress(result.state) || result.inputAccepted === true;
|
|
28
|
+
// A verified provider rejection may include a native session ID and protocol/selection metadata.
|
|
29
|
+
// Legacy adapters without this fact retain their stricter no-output/no-session retry boundary.
|
|
30
|
+
const uncertainOutput = !result.rejection && (observed.output || (!result.spawnError && !!result.state.sessionId));
|
|
31
|
+
const rejected = !progressed && !uncertainOutput && (result.inputAccepted === false || !!result.spawnError);
|
|
32
|
+
const kind = result.signal || result.stopReason ? "cancelled" : result.rejection
|
|
33
|
+
?? (result.spawnError ? "spawn" : result.inputAccepted === false ? "preflight" : "runtime");
|
|
34
|
+
return { kind, delivery: progressed ? "accepted" : rejected ? "rejected" : "unknown",
|
|
35
|
+
retryable: rejected && kind !== "cancelled" && !result.retryForbidden };
|
|
36
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { type OpenRouterFreeCliDependencies } from "./openrouter-free-cli.js";
|
|
2
3
|
import type { StructuredExecHost } from "./structured-exec-host.js";
|
|
3
4
|
import type { StructuredRunnerAdapter, StructuredRunnerContext, StructuredRunnerExecution, StructuredRunnerObserver, StructuredRunnerTurnState } from "./structured-runner.js";
|
|
4
5
|
import type { SessionSnapshot } from "./types.js";
|
|
@@ -6,13 +7,17 @@ type PiTurnState = StructuredRunnerTurnState & {
|
|
|
6
7
|
pendingSessionId?: string;
|
|
7
8
|
};
|
|
8
9
|
export declare function isMissingPiSession(error: string | null | undefined, sessionId: string | null | undefined): boolean;
|
|
9
|
-
export declare function
|
|
10
|
+
export declare function piToolsArgs(session: Pick<SessionSnapshot, "piSettings">): string[];
|
|
11
|
+
export declare function buildPiArgs(session: SessionSnapshot, prompt: string, resourceArgs?: string[]): string[];
|
|
10
12
|
export declare function piToolName(name: string): string;
|
|
11
|
-
export declare function applyPiEvent(state: PiTurnState, event: Record<string, unknown
|
|
13
|
+
export declare function applyPiEvent(state: PiTurnState, event: Record<string, unknown>, observedAt?: string): string | null;
|
|
12
14
|
export declare class PiRunner implements StructuredRunnerAdapter {
|
|
13
15
|
private readonly spawnProcess;
|
|
14
16
|
private readonly execHost?;
|
|
15
|
-
|
|
17
|
+
private readonly agentDir?;
|
|
18
|
+
private readonly freeModels?;
|
|
19
|
+
constructor(spawnProcess?: typeof spawn, execHost?: StructuredExecHost | undefined, agentDir?: string | undefined, freeModels?: OpenRouterFreeCliDependencies | undefined);
|
|
16
20
|
start(context: StructuredRunnerContext, observer: StructuredRunnerObserver): StructuredRunnerExecution;
|
|
21
|
+
private startCli;
|
|
17
22
|
}
|
|
18
23
|
export {};
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { capturePiExecutionEvent } from "./pi-execution.js";
|
|
3
|
+
import { defaultCoreHarnessAgentDir } from "./harness-engine.js";
|
|
4
|
+
import { preparePiResources } from "./pi-resource-run.js";
|
|
5
|
+
import { prepareOpenRouterFreeCli } from "./openrouter-free-cli.js";
|
|
6
|
+
import { isOpenRouterFreeSelector } from "./openrouter-free-selection.js";
|
|
7
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
8
|
+
import { classifyProviderRejection, hasStructuredExecutionProgress } from "./structured-failure.js";
|
|
9
|
+
import { settleThinkingRound, updateThinkingActivity } from "./structured-thinking.js";
|
|
2
10
|
import { startStructuredCli } from "./structured-exec-pump.js";
|
|
3
|
-
import { asRecord,
|
|
11
|
+
import { asRecord, canonicalizeToolResultContent } from "./structured-content.js";
|
|
4
12
|
import { systemPromptArgs, thinkingEffortToPiLevel } from "./structured-provider-common.js";
|
|
13
|
+
import { defaultPiCliSessionSettings, PI_BUILTIN_TOOLS, piToolSelection } from "./pi-session-settings.js";
|
|
5
14
|
export function isMissingPiSession(error, sessionId) {
|
|
6
15
|
return !!sessionId && !!error?.includes(`No session found matching '${sessionId}'`);
|
|
7
16
|
}
|
|
@@ -17,24 +26,22 @@ function textContent(value) {
|
|
|
17
26
|
return item.text;
|
|
18
27
|
return textContent(item.content);
|
|
19
28
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
return normalized.map((part) => part.text).filter(Boolean).join("\n");
|
|
33
|
-
}
|
|
34
|
-
return normalized;
|
|
29
|
+
export function piToolsArgs(session) {
|
|
30
|
+
// 没有会话设置(旧快照)时按 CLI 出厂默认:跟随 Pi 自身配置,不传任何工具参数。
|
|
31
|
+
const settings = session.piSettings ?? defaultPiCliSessionSettings();
|
|
32
|
+
// 旧快照可能带未校验的名字:只保留合法的基础工具,再交给策略附加 CodeMode / 目标工具。
|
|
33
|
+
const selection = piToolSelection({
|
|
34
|
+
...settings,
|
|
35
|
+
tools: settings.tools.filter((tool) => PI_BUILTIN_TOOLS.includes(tool)),
|
|
36
|
+
});
|
|
37
|
+
// `globalTools` 打开:既不传 --tools 也不传 --exclude-tools,完全跟随 Pi 自身配置。
|
|
38
|
+
if (selection.mode === "config")
|
|
39
|
+
return [];
|
|
40
|
+
return selection.names.length ? ["--tools", selection.names.join(",")] : ["--no-tools"];
|
|
35
41
|
}
|
|
36
|
-
export function buildPiArgs(session, prompt) {
|
|
42
|
+
export function buildPiArgs(session, prompt, resourceArgs = []) {
|
|
37
43
|
const args = ["--mode", "json", "--print"];
|
|
44
|
+
args.push(...piToolsArgs(session), ...resourceArgs);
|
|
38
45
|
const model = session.selectedModel?.trim();
|
|
39
46
|
if (model && model !== "default")
|
|
40
47
|
args.push("--model", model);
|
|
@@ -86,8 +93,21 @@ function applyPiAssistantMessage(state, message) {
|
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
const thinking = thinkings.join("");
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
// 流式事件只给出边界时,最后一轮会停在空占位上。这里用完整正文补齐,
|
|
97
|
+
// 而不是因为「已经有思考块」就把真实内容整段丢掉;单轮时更长的正文为准。
|
|
98
|
+
const thinkingBlocks = state.blocks.filter((block) => block.type === "thinking");
|
|
99
|
+
const lastThinking = thinkingBlocks.at(-1);
|
|
100
|
+
if (thinking) {
|
|
101
|
+
if (!lastThinking)
|
|
102
|
+
state.blocks.push({ type: "thinking", thinking });
|
|
103
|
+
else if (!lastThinking.thinking.trim())
|
|
104
|
+
lastThinking.thinking = thinking;
|
|
105
|
+
else if (thinkingBlocks.length === 1 && thinking.length > lastThinking.thinking.length) {
|
|
106
|
+
lastThinking.thinking = thinking;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
settleThinkingRound(state.blocks, thinking);
|
|
91
111
|
}
|
|
92
112
|
const text = texts.join("");
|
|
93
113
|
if (text) {
|
|
@@ -107,7 +127,8 @@ function applyPiAssistantMessage(state, message) {
|
|
|
107
127
|
}
|
|
108
128
|
return null;
|
|
109
129
|
}
|
|
110
|
-
export function applyPiEvent(state, event) {
|
|
130
|
+
export function applyPiEvent(state, event, observedAt) {
|
|
131
|
+
capturePiExecutionEvent(state.blocks, event);
|
|
111
132
|
// Pi announces the ID before it writes a session file. The file is only
|
|
112
133
|
// created when an assistant message is saved, so this ID is not resumable yet.
|
|
113
134
|
if (event.type === "session" && typeof event.id === "string")
|
|
@@ -119,6 +140,7 @@ export function applyPiEvent(state, event) {
|
|
|
119
140
|
state.phase = "responding";
|
|
120
141
|
const update = asRecord(event.assistantMessageEvent);
|
|
121
142
|
const delta = typeof update?.delta === "string" ? update.delta : "";
|
|
143
|
+
updateThinkingActivity(state.blocks, update, observedAt);
|
|
122
144
|
if (update?.type === "text_delta" && delta) {
|
|
123
145
|
const last = state.blocks.at(-1);
|
|
124
146
|
if (last?.type === "text")
|
|
@@ -158,7 +180,7 @@ export function applyPiEvent(state, event) {
|
|
|
158
180
|
}
|
|
159
181
|
if (event.type === "tool_execution_end") {
|
|
160
182
|
const id = typeof event.toolCallId === "string" ? event.toolCallId : "unknown";
|
|
161
|
-
state.blocks.push({ type: "tool_result", tool_use_id: id, content:
|
|
183
|
+
state.blocks.push({ type: "tool_result", tool_use_id: id, content: canonicalizeToolResultContent(event.result), is_error: event.isError === true });
|
|
162
184
|
}
|
|
163
185
|
if (event.type === "message_end" || event.type === "turn_end") {
|
|
164
186
|
const message = asRecord(event.message);
|
|
@@ -176,28 +198,85 @@ export function applyPiEvent(state, event) {
|
|
|
176
198
|
}
|
|
177
199
|
}
|
|
178
200
|
if (event.type === "agent_end" && Array.isArray(event.messages)) {
|
|
201
|
+
let error = null;
|
|
179
202
|
for (const raw of event.messages) {
|
|
180
203
|
const message = asRecord(raw);
|
|
181
204
|
if (message?.role === "assistant") {
|
|
182
205
|
if (state.pendingSessionId)
|
|
183
206
|
state.sessionId = state.pendingSessionId;
|
|
184
|
-
|
|
185
|
-
if (error)
|
|
186
|
-
return error;
|
|
207
|
+
error = applyPiAssistantMessage(state, message);
|
|
187
208
|
}
|
|
188
209
|
}
|
|
210
|
+
return error;
|
|
189
211
|
}
|
|
190
212
|
return null;
|
|
191
213
|
}
|
|
192
214
|
export class PiRunner {
|
|
193
215
|
spawnProcess;
|
|
194
216
|
execHost;
|
|
195
|
-
|
|
217
|
+
agentDir;
|
|
218
|
+
freeModels;
|
|
219
|
+
constructor(spawnProcess = spawn, execHost, agentDir, freeModels) {
|
|
196
220
|
this.spawnProcess = spawnProcess;
|
|
197
221
|
this.execHost = execHost;
|
|
222
|
+
this.agentDir = agentDir;
|
|
223
|
+
this.freeModels = freeModels;
|
|
198
224
|
}
|
|
199
225
|
start(context, observer) {
|
|
200
|
-
const
|
|
226
|
+
const selection = context.session.piSettings?.resources;
|
|
227
|
+
const codemodeOverride = context.session.piSettings?.codemodeOverride;
|
|
228
|
+
const freeSelection = isOpenRouterFreeSelector(context.session.selectedModel);
|
|
229
|
+
const env = { ...context.env };
|
|
230
|
+
delete env.WAND_PI_RESOURCE_POLICY;
|
|
231
|
+
delete env.WAND_PI_FREE_POLICY;
|
|
232
|
+
delete env.WAND_PI_FREE_TOKEN;
|
|
233
|
+
context = { ...context, env };
|
|
234
|
+
if (!selection && !codemodeOverride && !freeSelection)
|
|
235
|
+
return this.startCli(context, observer);
|
|
236
|
+
// Return synchronously so the manager registers ownership in this tick. Preparation belongs to
|
|
237
|
+
// this execution; stop/delete during preparation prevents the process from being started.
|
|
238
|
+
let execution = null;
|
|
239
|
+
let interrupted = false;
|
|
240
|
+
const spawnedAt = new Date().toISOString();
|
|
241
|
+
const completion = (async () => {
|
|
242
|
+
const prepared = [];
|
|
243
|
+
try {
|
|
244
|
+
let preparedEnv = context.env;
|
|
245
|
+
if (selection || codemodeOverride) {
|
|
246
|
+
const resources = await preparePiResources(this.agentDir ?? defaultCoreHarnessAgentDir(), context.session.cwd, selection, preparedEnv, codemodeOverride);
|
|
247
|
+
prepared.push(resources);
|
|
248
|
+
preparedEnv = resources.env;
|
|
249
|
+
}
|
|
250
|
+
if (interrupted || !observer.isActive())
|
|
251
|
+
throw new Error("资源准备已取消,尚未启动 Pi。");
|
|
252
|
+
if (freeSelection) {
|
|
253
|
+
if (!this.freeModels)
|
|
254
|
+
throw new Error("免费模型 CLI 接入未就绪,请更新 Wand 服务后重试。");
|
|
255
|
+
const free = prepareOpenRouterFreeCli(this.freeModels, { ...context, env: preparedEnv });
|
|
256
|
+
prepared.push(free);
|
|
257
|
+
preparedEnv = free.env;
|
|
258
|
+
}
|
|
259
|
+
execution = this.startCli({ ...context, env: preparedEnv }, observer, prepared.flatMap((item) => item.args));
|
|
260
|
+
const result = await execution.completion;
|
|
261
|
+
// A configured fallback tool uses its own capabilities. Only the free-price boundary
|
|
262
|
+
// forbids switching after execution; resource preparation failures still fail closed below.
|
|
263
|
+
return freeSelection ? { ...result, retryForbidden: true } : result;
|
|
264
|
+
}
|
|
265
|
+
finally {
|
|
266
|
+
for (const item of prepared.reverse())
|
|
267
|
+
item.close();
|
|
268
|
+
}
|
|
269
|
+
})().catch((error) => ({
|
|
270
|
+
state: { blocks: [], result: "", sessionId: context.session.claudeSessionId },
|
|
271
|
+
exitCode: 1, signal: null, stderr: "", primaryError: getErrorMessage(error),
|
|
272
|
+
inputAccepted: false, retryForbidden: true,
|
|
273
|
+
}));
|
|
274
|
+
return { get args() { return execution?.args ?? buildPiArgs(context.session, context.prompt, ["--no-skills"]); },
|
|
275
|
+
get pid() { return execution?.pid ?? null; }, spawnedAt, completion,
|
|
276
|
+
interrupt: () => { interrupted = true; execution?.interrupt(); } };
|
|
277
|
+
}
|
|
278
|
+
startCli(context, observer, resourceArgs = []) {
|
|
279
|
+
const args = buildPiArgs(context.session, context.prompt, resourceArgs);
|
|
201
280
|
const state = {
|
|
202
281
|
blocks: [],
|
|
203
282
|
result: "",
|
|
@@ -206,12 +285,14 @@ export class PiRunner {
|
|
|
206
285
|
phase: "responding",
|
|
207
286
|
};
|
|
208
287
|
let primaryError = null;
|
|
288
|
+
let progressed = false;
|
|
289
|
+
let unparsedOutput = false;
|
|
209
290
|
return startStructuredCli({
|
|
210
291
|
sessionId: context.session.id,
|
|
211
292
|
file: "pi",
|
|
212
293
|
args,
|
|
213
294
|
cwd: context.session.cwd,
|
|
214
|
-
env: context.env,
|
|
295
|
+
env: this.agentDir ? { ...context.env, PI_CODING_AGENT_DIR: this.agentDir } : context.env,
|
|
215
296
|
observer,
|
|
216
297
|
execHost: this.execHost,
|
|
217
298
|
spawnProcess: this.spawnProcess,
|
|
@@ -222,12 +303,32 @@ export class PiRunner {
|
|
|
222
303
|
try {
|
|
223
304
|
const event = JSON.parse(line);
|
|
224
305
|
observer.onEvent?.(event);
|
|
225
|
-
primaryError = applyPiEvent(state, event) ?? primaryError;
|
|
306
|
+
primaryError = applyPiEvent(state, event, new Date().toISOString()) ?? primaryError;
|
|
307
|
+
progressed ||= hasStructuredExecutionProgress(state);
|
|
308
|
+
const messages = event.type === "agent_end" && Array.isArray(event.messages) ? event.messages
|
|
309
|
+
: event.type === "message_end" || event.type === "turn_end" ? [event.message] : [];
|
|
310
|
+
progressed ||= messages.some((raw) => {
|
|
311
|
+
const message = asRecord(raw);
|
|
312
|
+
return message?.role === "assistant" && (message.stopReason !== "error"
|
|
313
|
+
|| (Array.isArray(message.content) && message.content.length > 0));
|
|
314
|
+
});
|
|
315
|
+
const lastAssistant = asRecord([...messages].reverse().find((raw) => asRecord(raw)?.role === "assistant"));
|
|
316
|
+
// Pi can recover internally. A later completed answer supersedes an earlier error;
|
|
317
|
+
// the opposite order remains a failure and the monotonic progress fact forbids replay.
|
|
318
|
+
if (lastAssistant && lastAssistant.stopReason !== "error")
|
|
319
|
+
primaryError = null;
|
|
226
320
|
observer.onUpdate(state);
|
|
227
321
|
}
|
|
228
|
-
catch {
|
|
322
|
+
catch {
|
|
323
|
+
unparsedOutput = true; /* Unknown stdout is not evidence of a safe refusal. */
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
finalize: (ctx, exitCode, signal, spawnError) => {
|
|
327
|
+
const rejection = !progressed && !unparsedOutput && !signal && !spawnError
|
|
328
|
+
? classifyProviderRejection(primaryError) : null;
|
|
329
|
+
return { state, exitCode, signal, stderr: ctx.stderr, primaryError, ...(spawnError ? { spawnError } : {}),
|
|
330
|
+
...(rejection ? { rejection, inputAccepted: false } : progressed ? { inputAccepted: true } : {}) };
|
|
229
331
|
},
|
|
230
|
-
finalize: (ctx, exitCode, signal, spawnError) => ({ state, exitCode, signal, stderr: ctx.stderr, primaryError, ...(spawnError ? { spawnError } : {}) }),
|
|
231
332
|
});
|
|
232
333
|
}
|
|
233
334
|
}
|
|
@@ -30,6 +30,12 @@ export declare function defaultStructuredRunner(provider: SessionProvider): Sess
|
|
|
30
30
|
export declare function normalizeStructuredRunner(runner: unknown): unknown;
|
|
31
31
|
export declare function resolveStructuredRunner(provider: SessionProvider, requestedRunner: unknown): SessionRunner;
|
|
32
32
|
export declare function defaultStructuredState(provider: SessionProvider, runner?: SessionRunner): StructuredSessionState;
|
|
33
|
+
/**
|
|
34
|
+
* 回合锚点的收敛兜底:`inFlight !== true` 时 turnStartedAt / lastActivityAt 一律读成
|
|
35
|
+
* null。写入点已经逐条走 settleTurn(),这里再挡一层历史库残值和重启降级窗口——
|
|
36
|
+
* 否则客户端会在一条早就结束的会话上算出「已运行 N 分钟」。
|
|
37
|
+
*/
|
|
38
|
+
export declare function normalizeTurnAnchors(state: StructuredSessionState | undefined): StructuredSessionState | undefined;
|
|
33
39
|
export declare function normalizeThinkingEffort(value: unknown): SessionSnapshot["thinkingEffort"];
|
|
34
40
|
export declare function thinkingEffortToClaudeCliEffort(effort: SessionSnapshot["thinkingEffort"]): string | null;
|
|
35
41
|
export declare function thinkingEffortToClaudeSlashEffort(effort: SessionSnapshot["thinkingEffort"]): string;
|
|
@@ -119,7 +119,29 @@ export function resolveStructuredRunner(provider, requestedRunner) {
|
|
|
119
119
|
return runner === "claude-sdk" ? "claude-cli-print" : runner;
|
|
120
120
|
}
|
|
121
121
|
export function defaultStructuredState(provider, runner = defaultStructuredRunner(provider)) {
|
|
122
|
-
return {
|
|
122
|
+
return {
|
|
123
|
+
provider,
|
|
124
|
+
runner,
|
|
125
|
+
lastError: null,
|
|
126
|
+
inFlight: false,
|
|
127
|
+
activeRequestId: null,
|
|
128
|
+
// 锚点是 inFlight 的派生读数:不在飞的默认态必须显式为 null,而不是 undefined,
|
|
129
|
+
// 这样任何 {...defaultStructuredState(...)} 展开都不会把上一轮的旧锚点带出来。
|
|
130
|
+
turnStartedAt: null,
|
|
131
|
+
lastActivityAt: null,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 回合锚点的收敛兜底:`inFlight !== true` 时 turnStartedAt / lastActivityAt 一律读成
|
|
136
|
+
* null。写入点已经逐条走 settleTurn(),这里再挡一层历史库残值和重启降级窗口——
|
|
137
|
+
* 否则客户端会在一条早就结束的会话上算出「已运行 N 分钟」。
|
|
138
|
+
*/
|
|
139
|
+
export function normalizeTurnAnchors(state) {
|
|
140
|
+
if (!state || state.inFlight)
|
|
141
|
+
return state;
|
|
142
|
+
if (state.turnStartedAt === null && state.lastActivityAt === null)
|
|
143
|
+
return state;
|
|
144
|
+
return { ...state, turnStartedAt: null, lastActivityAt: null };
|
|
123
145
|
}
|
|
124
146
|
export function normalizeThinkingEffort(value) {
|
|
125
147
|
if (typeof value !== "string")
|
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import type { ContentBlock, ConversationTurn, SessionSnapshot } from "./types.js";
|
|
2
|
+
/** core 引擎回报的上下文占用;CLI runner 无此能力时省略。 */
|
|
3
|
+
export interface StructuredContextUsage {
|
|
4
|
+
/** 本轮请求实际占用的上下文 token。 */
|
|
5
|
+
usedTokens: number;
|
|
6
|
+
/** 当前模型上下文窗口 token。 */
|
|
7
|
+
windowTokens: number;
|
|
8
|
+
/** used / window,0-100,保留 1 位。 */
|
|
9
|
+
percent: number;
|
|
10
|
+
/** 本会话累计压缩次数。 */
|
|
11
|
+
compactions?: number;
|
|
12
|
+
}
|
|
13
|
+
/** core 引擎本轮发生的一次上下文压缩,由会话管理器落成压缩状态。 */
|
|
14
|
+
export interface StructuredCompactionNotice {
|
|
15
|
+
reason: "threshold" | "manual" | "overflow";
|
|
16
|
+
tokensBefore: number;
|
|
17
|
+
/** 压缩后重估的 token;估算值,不作为计费依据。 */
|
|
18
|
+
tokensAfter?: number;
|
|
19
|
+
summary: string;
|
|
20
|
+
/** 摘要覆盖到第几条会话消息之前(即从这一条开始保留原样)。 */
|
|
21
|
+
fromTurnIndex: number;
|
|
22
|
+
/** 被摘要覆盖的消息条数。 */
|
|
23
|
+
droppedMessages: number;
|
|
24
|
+
/** 本会话累计压缩次数。 */
|
|
25
|
+
compactions: number;
|
|
26
|
+
/** 摘要调用本身的用量。 */
|
|
27
|
+
usage?: ConversationTurn["usage"];
|
|
28
|
+
}
|
|
2
29
|
export interface StructuredRunnerTurnState {
|
|
3
30
|
blocks: ContentBlock[];
|
|
4
31
|
result: string;
|
|
@@ -7,11 +34,21 @@ export interface StructuredRunnerTurnState {
|
|
|
7
34
|
usage?: ConversationTurn["usage"];
|
|
8
35
|
/** Provider turn phase when the CLI stays alive after its visible reply. */
|
|
9
36
|
phase?: "responding" | "background";
|
|
37
|
+
/** Actual server-owned resource choice for this submitted round; presentation metadata only. */
|
|
38
|
+
resourceSelection?: import("./pi-session-settings.js").PiResourceSelectionNotice;
|
|
39
|
+
/** 本轮结束后的上下文占用(core 引擎)。 */
|
|
40
|
+
contextUsage?: StructuredContextUsage;
|
|
41
|
+
/** 本轮发生的上下文压缩(core 引擎);每个回合最多一条。 */
|
|
42
|
+
compaction?: StructuredCompactionNotice;
|
|
43
|
+
/** Native Pi extension metadata to checkpoint, never part of the wire DTO. */
|
|
44
|
+
harnessExtensionState?: SessionSnapshot["harnessExtensionState"];
|
|
10
45
|
}
|
|
11
46
|
export interface StructuredRunnerContext {
|
|
12
47
|
session: SessionSnapshot;
|
|
13
48
|
prompt: string;
|
|
14
49
|
env: NodeJS.ProcessEnv;
|
|
50
|
+
/** Execution-only group boundary; never persisted/projected to session DTOs. */
|
|
51
|
+
modelGroupModels?: readonly string[];
|
|
15
52
|
}
|
|
16
53
|
export interface StructuredRunnerObserver {
|
|
17
54
|
isActive(): boolean;
|
|
@@ -30,6 +67,14 @@ export interface StructuredRunnerResult {
|
|
|
30
67
|
stdoutTail?: string;
|
|
31
68
|
stopReason?: "ask-user-question";
|
|
32
69
|
spawnError?: NodeJS.ErrnoException;
|
|
70
|
+
/** Explicit pre-execution fact only; absent means unknown, not permission to replay. */
|
|
71
|
+
inputAccepted?: boolean;
|
|
72
|
+
/** A resource preparation failure or explicit price boundary must not be bypassed by fallback. */
|
|
73
|
+
retryForbidden?: boolean;
|
|
74
|
+
/** Adapter-verified initial provider refusal, with no prior model output or tool execution. */
|
|
75
|
+
rejection?: import("./structured-failure.js").ProviderRejectionKind;
|
|
76
|
+
/** Normalized once at the runner boundary; consumers do not re-guess delivery from error text. */
|
|
77
|
+
failure?: import("./structured-failure.js").StructuredFailure | null;
|
|
33
78
|
}
|
|
34
79
|
export interface StructuredRunnerExecution {
|
|
35
80
|
args: string[];
|