@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
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConversationSessionPreview, SessionSnapshot } from "./types.js";
|
|
2
|
+
export declare const CONVERSATION_SESSION_PREFIX = "conversation-message:";
|
|
3
|
+
export declare const CONVERSATION_PREVIEW_LIMIT = 4000;
|
|
4
|
+
export declare const conversationSessionPreviewLabels: Record<ConversationSessionPreview["status"], string>;
|
|
5
|
+
/** The session owns the transcript. IM only reads a bounded tail; never copies it into message history. */
|
|
6
|
+
export declare function conversationSessionPreview(session: SessionSnapshot | null, startup?: {
|
|
7
|
+
startup?: "pending" | "started" | "failed";
|
|
8
|
+
error?: string;
|
|
9
|
+
}): ConversationSessionPreview;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const CONVERSATION_SESSION_PREFIX = "conversation-message:";
|
|
2
|
+
export const CONVERSATION_PREVIEW_LIMIT = 4000;
|
|
3
|
+
export const conversationSessionPreviewLabels = {
|
|
4
|
+
starting: "正在启动", running: "正在回复", waiting_user: "等待你确认", done: "已完成",
|
|
5
|
+
failed: "执行失败", stopped: "已停止", unavailable: "会话不可用",
|
|
6
|
+
};
|
|
7
|
+
/** The session owns the transcript. IM only reads a bounded tail; never copies it into message history. */
|
|
8
|
+
export function conversationSessionPreview(session, startup) {
|
|
9
|
+
if (!session)
|
|
10
|
+
return { status: "unavailable", text: "会话已不可用,原消息保留。" };
|
|
11
|
+
const state = session.structuredState;
|
|
12
|
+
const turns = session.messages ?? [];
|
|
13
|
+
const last = turns.at(-1);
|
|
14
|
+
const question = last?.role === "assistant" && last.content.some(block => block.type === "tool_use"
|
|
15
|
+
&& (block.semantic?.kind === "question_request" || block.name === "AskUserQuestion")
|
|
16
|
+
&& !last.content.some(result => result.type === "tool_result" && result.tool_use_id === block.id));
|
|
17
|
+
const active = state?.inFlight || session.status === "running";
|
|
18
|
+
let userInputs = 0;
|
|
19
|
+
const continued = startup?.startup === "failed" && turns.some(turn => turn.role === "user" && turn.content.some(block => block.type === "text" && block.text.trim()) && ++userInputs > 1);
|
|
20
|
+
// A failed initial delivery receipt remains immutable evidence, but must not mask an explicit later turn.
|
|
21
|
+
const startupFailed = startup?.startup === "failed" && !active && !continued && !last?.completedAt;
|
|
22
|
+
const status = session.status === "stopped" ? "stopped"
|
|
23
|
+
: session.status === "failed" || !!state?.lastError || startupFailed ? "failed"
|
|
24
|
+
: session.pendingEscalation || question ? "waiting_user"
|
|
25
|
+
: active ? "running" : startup?.startup === "pending" ? "starting" : "done";
|
|
26
|
+
// Walk backwards and stop as soon as the viewport budget is filled. Large sessions stay bounded.
|
|
27
|
+
let text = "";
|
|
28
|
+
for (let index = turns.length - 1; index >= 0 && text.length < CONVERSATION_PREVIEW_LIMIT; index--) {
|
|
29
|
+
const turn = turns[index];
|
|
30
|
+
if (turn.role === "user")
|
|
31
|
+
break; // Explicit follow-ups belong to the same session, not the next IM message.
|
|
32
|
+
for (let blockIndex = turn.content.length - 1; blockIndex >= 0 && text.length < CONVERSATION_PREVIEW_LIMIT; blockIndex--) {
|
|
33
|
+
const block = turn.content[blockIndex];
|
|
34
|
+
if (block.type === "text" && block.text.trim())
|
|
35
|
+
text = `${block.text.slice(-CONVERSATION_PREVIEW_LIMIT)}${text ? `\n\n${text}` : ""}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (status === "failed")
|
|
39
|
+
text = [text, state?.lastError || (startupFailed ? startup?.error : undefined) || "执行失败,请打开会话核对;不会自动重发。"].filter(Boolean).join("\n\n");
|
|
40
|
+
if (!text) {
|
|
41
|
+
const tool = last?.content.slice().reverse().find(block => block.type === "tool_use");
|
|
42
|
+
text = session.pendingEscalation?.reason || (active && tool?.type === "tool_use" ? `正在调用 ${tool.name}` : "")
|
|
43
|
+
|| (status === "starting" ? "消息已接收,正在启动独立会话。" : status === "running" ? "正在思考…"
|
|
44
|
+
: status === "stopped" ? "本轮已停止。" : "本轮已结束,打开会话查看完整过程。");
|
|
45
|
+
}
|
|
46
|
+
return { status, text: text.slice(-CONVERSATION_PREVIEW_LIMIT) };
|
|
47
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AiTeamLiveStep } from "./ai-team-types.js";
|
|
2
|
+
import type { ConversationTurn } from "./types.js";
|
|
3
|
+
export declare const conversationTaskPreviewLabels: Record<NonNullable<ConversationTurn["taskPreview"]>["status"], string>;
|
|
4
|
+
/** Shared by the HTTP snapshot and WS projection; only this run’s real output, bounded at the tail. */
|
|
5
|
+
export declare function conversationTaskLiveText(steps: readonly AiTeamLiveStep[]): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const conversationTaskPreviewLabels = {
|
|
2
|
+
starting: "正在启动", running: "处理中", awaiting_approval: "等你批准计划", waiting_user: "等你回复",
|
|
3
|
+
done: "已完成", failed: "执行失败", stopped: "已停止", unavailable: "任务不可用",
|
|
4
|
+
};
|
|
5
|
+
/** Shared by the HTTP snapshot and WS projection; only this run’s real output, bounded at the tail. */
|
|
6
|
+
export function conversationTaskLiveText(steps) {
|
|
7
|
+
return steps.filter(step => step.text.trim()).map(step => `${step.memberName}:\n${step.text.slice(-4000)}`).join("\n\n").slice(-4000);
|
|
8
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { AiTeam, AiTeamRun, AiTeamRunDetail } from "./ai-team-types.js";
|
|
2
|
+
import type { WandTask } from "./task-types.js";
|
|
3
|
+
import type { ConversationSessionPreview, ConversationTurn } from "./types.js";
|
|
4
|
+
/** This Wand service has one authenticated owner; connection principals are capabilities, not new users. */
|
|
5
|
+
export declare const CONVERSATION_OWNER = "local-owner";
|
|
6
|
+
export declare const CONVERSATION_RELAY_PREFIX = "conversation:";
|
|
7
|
+
export declare const CONVERSATION_MIN_MEMBERS = 1;
|
|
8
|
+
export type ConversationTarget = {
|
|
9
|
+
taskId: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
} | null;
|
|
12
|
+
/** Instance configuration is independent of both presets and frozen run.team. */
|
|
13
|
+
export interface ConversationInstance {
|
|
14
|
+
id: string;
|
|
15
|
+
owner: string;
|
|
16
|
+
kind: "dm" | "group";
|
|
17
|
+
peerEmployeeId: string | null;
|
|
18
|
+
name: string;
|
|
19
|
+
nameSource: "auto" | "custom";
|
|
20
|
+
sourceTemplateId: string | null;
|
|
21
|
+
team: AiTeam | null;
|
|
22
|
+
memberVersion: number;
|
|
23
|
+
joinedVersions: Record<string, number>;
|
|
24
|
+
/** Group relay or preserved legacy DM channel, never the most recent direct-message execution. */
|
|
25
|
+
sessionId: string | null;
|
|
26
|
+
/** Group talk/legacy DM channel; new DM executions belong to each reply's sessionLink. */
|
|
27
|
+
communicationSessionId: string | null;
|
|
28
|
+
historySessionIds?: string[];
|
|
29
|
+
createdAt: string;
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ConversationTaskSummary {
|
|
33
|
+
task: WandTask;
|
|
34
|
+
linkedAt: string;
|
|
35
|
+
runs: AiTeamRun[];
|
|
36
|
+
startup?: {
|
|
37
|
+
state: "pending" | "started" | "failed";
|
|
38
|
+
error?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface ConversationSummary extends ConversationInstance {
|
|
42
|
+
pinnedAt?: string | null;
|
|
43
|
+
dissolvedAt?: string | null;
|
|
44
|
+
dissolvedBy?: string | null;
|
|
45
|
+
deleting?: boolean;
|
|
46
|
+
title: string;
|
|
47
|
+
unavailableReason: string | null;
|
|
48
|
+
memberUnavailableReasons: Record<string, string>;
|
|
49
|
+
tasks: ConversationTaskSummary[];
|
|
50
|
+
preview: string;
|
|
51
|
+
messageAt: string;
|
|
52
|
+
}
|
|
53
|
+
export interface ConversationDetail extends ConversationSummary {
|
|
54
|
+
messages: ConversationTurn[];
|
|
55
|
+
runDetails: AiTeamRunDetail[];
|
|
56
|
+
}
|
|
57
|
+
export interface ConversationReceipt {
|
|
58
|
+
requestId: string;
|
|
59
|
+
state: "pending" | "accepted" | "rejected";
|
|
60
|
+
conversationId: string;
|
|
61
|
+
taskId?: string;
|
|
62
|
+
runId?: string;
|
|
63
|
+
sessionId?: string;
|
|
64
|
+
messageId?: string;
|
|
65
|
+
/** Session or task/group acceptance is durable before execution startup. Not a delivery rejection. */
|
|
66
|
+
startup?: "pending" | "started" | "failed";
|
|
67
|
+
error?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface ConversationSessionUpdate {
|
|
70
|
+
conversationId: string;
|
|
71
|
+
sessionId: string;
|
|
72
|
+
preview: ConversationSessionPreview;
|
|
73
|
+
}
|
|
74
|
+
export interface ConversationRequest {
|
|
75
|
+
id: string;
|
|
76
|
+
fingerprint: string;
|
|
77
|
+
receipt: ConversationReceipt;
|
|
78
|
+
updatedAt: string;
|
|
79
|
+
}
|
|
80
|
+
export declare function employeeConversationId(employeeId: string): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** This Wand service has one authenticated owner; connection principals are capabilities, not new users. */
|
|
2
|
+
export const CONVERSATION_OWNER = "local-owner";
|
|
3
|
+
export const CONVERSATION_RELAY_PREFIX = "conversation:";
|
|
4
|
+
export const CONVERSATION_MIN_MEMBERS = 1;
|
|
5
|
+
export function employeeConversationId(employeeId) {
|
|
6
|
+
return `dm_${employeeId}`;
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type PiSessionSettings } from "./pi-session-settings.js";
|
|
2
|
+
import type { HarnessExtensionState, SessionSnapshot, WandConfig } from "./types.js";
|
|
3
|
+
import type { loadPiAi, loadPiCodingAgent } from "./harness-engine.js";
|
|
4
|
+
import type { Agent, AgentMessage, AgentTool } from "@earendil-works/pi-agent-core";
|
|
5
|
+
import type { AgentSession, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
export declare function parseHarnessExtensionState(raw: unknown): HarnessExtensionState | undefined;
|
|
7
|
+
export interface CoreExtensionHost {
|
|
8
|
+
session: AgentSession;
|
|
9
|
+
capture(): HarnessExtensionState;
|
|
10
|
+
interrupt(): void;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare function createCoreExtensionHost(options: {
|
|
14
|
+
pi: Awaited<ReturnType<typeof loadPiCodingAgent>>;
|
|
15
|
+
ai: Awaited<ReturnType<typeof loadPiAi>>;
|
|
16
|
+
config: WandConfig;
|
|
17
|
+
target: SessionSnapshot;
|
|
18
|
+
settings: PiSessionSettings;
|
|
19
|
+
systemPrompt: string;
|
|
20
|
+
agent: Agent;
|
|
21
|
+
messages: AgentMessage[];
|
|
22
|
+
tools: AgentTool[];
|
|
23
|
+
runtime: ModelRuntime | null;
|
|
24
|
+
/** Stream authentication is already owned by the injected stream / zero-price model allocator. */
|
|
25
|
+
externalStreamAuth: boolean;
|
|
26
|
+
onNotice(text: string): void;
|
|
27
|
+
}): Promise<CoreExtensionHost>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createPiExtensionResources } from "./pi-extension-resources.js";
|
|
2
|
+
import { PI_BUILTIN_TOOLS } from "./pi-session-settings.js";
|
|
3
|
+
const MAX_EXTENSION_STATE_BYTES = 128 * 1024;
|
|
4
|
+
const MAX_EXTENSION_ENTRIES = 128;
|
|
5
|
+
export function parseHarnessExtensionState(raw) {
|
|
6
|
+
if (!raw || typeof raw !== "object" || !Array.isArray(raw.entries))
|
|
7
|
+
return undefined;
|
|
8
|
+
const entries = raw.entries;
|
|
9
|
+
if (entries.length > MAX_EXTENSION_ENTRIES || Buffer.byteLength(JSON.stringify(entries)) > MAX_EXTENSION_STATE_BYTES)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (!entries.every((entry) => entry && typeof entry.customType === "string" && entry.customType.length <= 200
|
|
12
|
+
&& (entry.type === "custom" || (entry.type === "custom_message" && typeof entry.content === "string" && typeof entry.display === "boolean"))))
|
|
13
|
+
return undefined;
|
|
14
|
+
return { entries };
|
|
15
|
+
}
|
|
16
|
+
/** Keep native snapshots/store data bounded, without another copy of Wand's ordinary messages. */
|
|
17
|
+
function captureExtensionState(manager, prior) {
|
|
18
|
+
const entries = [];
|
|
19
|
+
const store = Object.create(null);
|
|
20
|
+
for (const entry of manager.getBranch()) {
|
|
21
|
+
if (entry.type === "custom") {
|
|
22
|
+
if (entry.customType === "codemode-store") {
|
|
23
|
+
const data = entry.data;
|
|
24
|
+
Object.assign(store, data?.set ?? {});
|
|
25
|
+
for (const key of data?.delete ?? [])
|
|
26
|
+
delete store[key];
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (entry.customType === "pi-goal") {
|
|
30
|
+
const index = entries.findIndex((item) => item.type === "custom" && item.customType === entry.customType);
|
|
31
|
+
if (index >= 0)
|
|
32
|
+
entries.splice(index, 1);
|
|
33
|
+
}
|
|
34
|
+
entries.push({ type: "custom", customType: entry.customType, data: entry.data });
|
|
35
|
+
}
|
|
36
|
+
else if (entry.type === "custom_message") {
|
|
37
|
+
// Goal events are snapshots: only the newest directive should survive into the next turn.
|
|
38
|
+
if (entry.customType === "pi-goal-event") {
|
|
39
|
+
const index = entries.findIndex((item) => item.type === "custom_message" && item.customType === entry.customType);
|
|
40
|
+
if (index >= 0)
|
|
41
|
+
entries.splice(index, 1);
|
|
42
|
+
}
|
|
43
|
+
const content = typeof entry.content === "string" ? entry.content : entry.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
44
|
+
entries.push({ type: "custom_message", customType: entry.customType, content, display: entry.display, details: entry.details });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (Object.keys(store).length)
|
|
48
|
+
entries.push({ type: "custom", customType: "codemode-store", data: { set: store, delete: [] } });
|
|
49
|
+
// Disabled custom messages were intentionally not exposed to the agent, but are not deleted.
|
|
50
|
+
for (const entry of prior?.entries ?? []) {
|
|
51
|
+
if (entry.type === "custom_message" && !entries.some((item) => item.customType === entry.customType))
|
|
52
|
+
entries.push(entry);
|
|
53
|
+
}
|
|
54
|
+
const state = parseHarnessExtensionState({ entries });
|
|
55
|
+
if (!state)
|
|
56
|
+
throw new Error("Pi 扩展状态超出保存上限(128 条 / 128 KiB);没有截断或伪造已保存状态。");
|
|
57
|
+
return state;
|
|
58
|
+
}
|
|
59
|
+
export async function createCoreExtensionHost(options) {
|
|
60
|
+
const { pi, target, settings, agent } = options;
|
|
61
|
+
const resources = await createPiExtensionResources(pi, options.config, target.cwd, settings, options.systemPrompt);
|
|
62
|
+
const manager = pi.SessionManager.inMemory(target.cwd);
|
|
63
|
+
for (const entry of target.harnessExtensionState?.entries ?? []) {
|
|
64
|
+
if (entry.type === "custom")
|
|
65
|
+
manager.appendCustomEntry(entry.customType, entry.data);
|
|
66
|
+
else if (entry.customType.startsWith("pi-goal") ? settings.goalMode : settings.globalTools) {
|
|
67
|
+
manager.appendCustomMessageEntry(entry.customType, entry.content, entry.display, entry.details);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const message of options.messages)
|
|
71
|
+
manager.appendMessage(message);
|
|
72
|
+
let runtime = options.runtime ?? await pi.ModelRuntime.create({
|
|
73
|
+
credentials: new options.ai.InMemoryCredentialStore(), modelsStore: new options.ai.InMemoryModelsStore(),
|
|
74
|
+
modelsPath: null, refreshOnCreate: false,
|
|
75
|
+
});
|
|
76
|
+
if (options.externalStreamAuth) {
|
|
77
|
+
// Per-session facade, never mutate the shared auth runtime or write synthetic credentials.
|
|
78
|
+
const provider = agent.state.model.provider;
|
|
79
|
+
runtime = new Proxy(runtime, { get(base, key) {
|
|
80
|
+
if (key === "hasConfiguredAuth")
|
|
81
|
+
return (id) => id === provider || base.hasConfiguredAuth(id);
|
|
82
|
+
const value = Reflect.get(base, key);
|
|
83
|
+
return typeof value === "function" ? value.bind(base) : value;
|
|
84
|
+
} });
|
|
85
|
+
}
|
|
86
|
+
const runnerRef = {};
|
|
87
|
+
const session = new pi.AgentSession({
|
|
88
|
+
agent, cwd: target.cwd, sessionManager: manager, modelRuntime: runtime, resourceLoader: resources,
|
|
89
|
+
// Wand owns compaction, queues, model choice and history; the SDK owns only this round's extensions.
|
|
90
|
+
settingsManager: pi.SettingsManager.inMemory({ compaction: { enabled: false }, retry: { enabled: false }, cacheWarming: "off" }),
|
|
91
|
+
baseToolsOverride: Object.fromEntries(options.tools.map((tool) => [tool.name, tool])),
|
|
92
|
+
initialActiveToolNames: [...options.tools.map((tool) => tool.name), ...(settings.codemode !== "off" ? ["codemode"] : [])],
|
|
93
|
+
excludedToolNames: [...PI_BUILTIN_TOOLS.filter((name) => !settings.tools.includes(name)),
|
|
94
|
+
...(!settings.localDecision ? ["decision_evaluate"] : []),
|
|
95
|
+
...(settings.codemode === "off" ? ["codemode"] : []),
|
|
96
|
+
...(!settings.goalMode ? ["create_goal", "get_goal", "update_goal"] : [])],
|
|
97
|
+
extensionRunnerRef: runnerRef,
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
// Keep headless dialogs conservative. Notifications still reach the caller instead of disappearing.
|
|
101
|
+
const ui = runnerRef.current.getUIContext();
|
|
102
|
+
await session.bindExtensions({ mode: "print", uiContext: { ...ui, notify: options.onNotice },
|
|
103
|
+
onError: (error) => { options.onNotice(`Pi 扩展错误:${error.error}`); },
|
|
104
|
+
abortHandler: () => agent.abort(), shutdownHandler: () => agent.abort() });
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
session.dispose();
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
return { session,
|
|
111
|
+
capture: () => captureExtensionState(manager, target.harnessExtensionState),
|
|
112
|
+
interrupt: () => {
|
|
113
|
+
// pi-goal continues on agent_end, even after abort. Pause its native state BEFORE aborting,
|
|
114
|
+
// through the real command handler (never dispatch an unknown command to the model).
|
|
115
|
+
if (settings.goalMode) {
|
|
116
|
+
const runner = runnerRef.current;
|
|
117
|
+
const goal = runner?.getCommand("goal");
|
|
118
|
+
if (goal && runner) {
|
|
119
|
+
void Promise.resolve(goal.handler("pause", runner.createCommandContext())).catch((error) => {
|
|
120
|
+
options.onNotice(error instanceof Error ? error.message : "暂停目标失败。");
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
session.clearQueue();
|
|
125
|
+
// The SDK's abort flag also prevents agent_end/before_settle hooks from restarting the loop.
|
|
126
|
+
void session.abort().catch((error) => {
|
|
127
|
+
options.onNotice(error instanceof Error ? error.message : "停止 Pi 扩展失败。");
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
close: async () => {
|
|
131
|
+
try {
|
|
132
|
+
await runnerRef.current?.emit({ type: "session_shutdown", reason: "quit" });
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
session.dispose();
|
|
136
|
+
}
|
|
137
|
+
}, };
|
|
138
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type OpenRouterFreeModelsService } from "./openrouter-free-models.js";
|
|
2
|
+
import type { ResolvedCoreModel } from "./harness-engine.js";
|
|
3
|
+
import type { DecisionRuntimeAccess } from "./decision-runner.js";
|
|
4
|
+
import type { ConversationTurn, SessionSnapshot, WandConfig } from "./types.js";
|
|
5
|
+
import type { StructuredRunnerAdapter, StructuredRunnerContext, StructuredRunnerExecution, StructuredRunnerObserver, StructuredRunnerTurnState } from "./structured-runner.js";
|
|
6
|
+
import type { AgentMessage, AgentTool, StreamFn } from "@earendil-works/pi-agent-core";
|
|
7
|
+
import type { Model, Api, ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
8
|
+
/** 压缩摘要作为上下文导入时的头部:明确它不是新的用户要求。 */
|
|
9
|
+
export declare const CORE_SUMMARY_HEADER = "[\u66F4\u65E9\u7684\u5BF9\u8BDD\u5DF2\u88AB\u538B\u7F29\u4E3A\u4E0B\u9762\u7684\u6458\u8981\u3002\u5B83\u662F\u672C\u4F1A\u8BDD\u7684\u5DF2\u6709\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u662F\u65B0\u7684\u7528\u6237\u8981\u6C42\u3002]\n\n";
|
|
10
|
+
/**
|
|
11
|
+
* Wand 历史 → core transcript。
|
|
12
|
+
* Wand 库是唯一事实源:每个回合都从存储的历史重建,不依赖上游 session 文件。
|
|
13
|
+
* Wand 允许 tool_result 落在 tool_use 之后的另一个 turn(服务端把结果并进下一个 user turn),
|
|
14
|
+
* 这里按 toolCallId 重新配对成标准的 assistant / toolResult 消息。
|
|
15
|
+
*
|
|
16
|
+
* `from` 之后的 turn 才进入 transcript;更早的历史由 `summary` 代表(上下文压缩后)。
|
|
17
|
+
*/
|
|
18
|
+
export declare function coreMessagesFromTurns(session: SessionSnapshot, options?: {
|
|
19
|
+
from?: number;
|
|
20
|
+
until?: number;
|
|
21
|
+
summary?: string;
|
|
22
|
+
}): AgentMessage[];
|
|
23
|
+
/**
|
|
24
|
+
* core agent 事件 → Wand 回合状态。与 pi CLI 适配器共用同一套 blocks 语义,
|
|
25
|
+
* 但参数与结果是上游真值(不再从 provider 文本里反推)。返回本轮的 primaryError。
|
|
26
|
+
*/
|
|
27
|
+
export declare function applyCoreAgentEvent(state: StructuredRunnerTurnState, event: Record<string, unknown>): string | null;
|
|
28
|
+
export interface CoreRunnerOptions {
|
|
29
|
+
openRouter?: OpenRouterFreeModelsService;
|
|
30
|
+
config: WandConfig;
|
|
31
|
+
/** 进程内决策能力(与 CLI 的 env token 路径二选一,core 用这个)。 */
|
|
32
|
+
decisionAccess?: () => DecisionRuntimeAccess | null;
|
|
33
|
+
/** 测试/宿主注入:替换模型流式实现(生产用 ModelRuntime.streamSimple)。 */
|
|
34
|
+
streamFn?: StreamFn;
|
|
35
|
+
/** 测试/宿主注入:替换模型解析(生产走 Pi 认证目录)。 */
|
|
36
|
+
modelResolver?: (session: SessionSnapshot) => Promise<ResolvedCoreModel | null>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 进程内 core runner:用 Pi 的 agent loop 跑结构化会话。
|
|
40
|
+
* 与 CLI runner 的契约完全相同(同一个 StructuredRunnerAdapter),
|
|
41
|
+
* 差别是 loop 与工具都在 Wand 进程内,系统提示由 Wand 拥有、上下文由 Wand 拥有。
|
|
42
|
+
*/
|
|
43
|
+
export declare class CoreRunner implements StructuredRunnerAdapter {
|
|
44
|
+
private readonly options;
|
|
45
|
+
constructor(options: CoreRunnerOptions);
|
|
46
|
+
start(context: StructuredRunnerContext, observer: StructuredRunnerObserver): StructuredRunnerExecution;
|
|
47
|
+
/** 返回本轮的 primaryError;null 表示成功。 */
|
|
48
|
+
private runTurn;
|
|
49
|
+
private buildTools;
|
|
50
|
+
}
|
|
51
|
+
type ClampThinkingLevel = (model: Model<Api>, level: ModelThinkingLevel) => ModelThinkingLevel;
|
|
52
|
+
/**
|
|
53
|
+
* Wand 自有工具:本地决策(进程内)。
|
|
54
|
+
* 同进程直接调决策服务,不经过 env token / HTTP 回环;
|
|
55
|
+
* `questions` 用 JSON 字符串传递,避开各家 provider 对嵌套 schema 的差异,
|
|
56
|
+
* 也把校验交给服务端的有界校验器(1–8 题、每题 2–8 项、总请求≤32KiB)。
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildDecisionTool(decision: DecisionRuntimeAccess, type: typeof import("@earendil-works/pi-ai").Type, sessionId: string): AgentTool; /**
|
|
59
|
+
* Wand 的思考档 → 当前模型真正支持的档位。
|
|
60
|
+
* Wand 的 off/standard/deep/max 是历史四档,`provider:level` 是原生档;
|
|
61
|
+
* 最终一律交给上游 clamp,避免把不支持的档位发给 provider。
|
|
62
|
+
*/
|
|
63
|
+
export declare function thinkingLevelFor(clamp: ClampThinkingLevel, model: Model<Api>, effort: SessionSnapshot["thinkingEffort"]): ModelThinkingLevel;
|
|
64
|
+
/** 压缩预算:config.harness.compaction 覆盖上游默认值。 */
|
|
65
|
+
export declare function compactionSettings(config: WandConfig, defaults: {
|
|
66
|
+
enabled: boolean;
|
|
67
|
+
reserveTokens: number;
|
|
68
|
+
keepRecentTokens: number;
|
|
69
|
+
}): {
|
|
70
|
+
enabled: boolean;
|
|
71
|
+
reserveTokens: number;
|
|
72
|
+
keepRecentTokens: number;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* 在 turn 空间里找切点:保留预算内最最早的真实用户输入作为切点。
|
|
76
|
+
* 预算内找不到时至少向前推一格(否则超限会话会永远压不下去);再没办法就返回原切点。
|
|
77
|
+
*/
|
|
78
|
+
export declare function findCompactionCut(turns: readonly ConversationTurn[], from: number, keepRecentTokens: number, turnTokens: (index: number) => number, isUserText?: (turn: ConversationTurn) => boolean): number;
|
|
79
|
+
/**
|
|
80
|
+
* core 会话的基础系统提示。 * Wand 拥有它:不再依赖外部 CLI 的默认提示词,也不再往别人的提示词后面追加。
|
|
81
|
+
* 会话级 systemPrompt / 员工角色 / 本轮知识由 buildCoreSystemPrompt 接在后面。
|
|
82
|
+
*/
|
|
83
|
+
export declare const CORE_BASE_SYSTEM_PROMPT: string;
|
|
84
|
+
/** 基础提示 + 会话级系统提示(角色/规则/本轮知识)。 */
|
|
85
|
+
export declare function buildCoreSystemPrompt(session: Pick<SessionSnapshot, "systemPrompt" | "runtimeSystemPrompt">): string;
|
|
86
|
+
export {};
|