@co0ontty/wand 4.89.0-beta.gde0b3cd → 4.89.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/dist/agent-dispatch.d.ts +0 -5
- package/dist/agent-dispatch.js +0 -8
- package/dist/ai-team-runner.d.ts +1 -1
- package/dist/ai-team-runner.js +5 -13
- package/dist/ai-team-types.js +2 -3
- package/dist/build-info.json +4 -4
- package/dist/commit-task-archive.d.ts +0 -3
- package/dist/commit-task-archive.js +0 -15
- package/dist/config.js +0 -1
- package/dist/decision-expert-employee.d.ts +1 -1
- package/dist/decision-expert-employee.js +3 -3
- package/dist/decision-expert-service.d.ts +5 -6
- package/dist/decision-expert-service.js +49 -75
- package/dist/decision-types.d.ts +1 -2
- package/dist/git-quick-commit.d.ts +4 -13
- package/dist/git-quick-commit.js +33 -112
- package/dist/local-model-setup.d.ts +1 -7
- package/dist/local-model-setup.js +7 -54
- package/dist/local-model-types.d.ts +1 -2
- package/dist/process-manager.d.ts +1 -4
- package/dist/process-manager.js +6 -5
- package/dist/server-employee-routes.d.ts +0 -2
- package/dist/server-employee-routes.js +45 -19
- package/dist/server-file-routes.js +25 -61
- package/dist/server-local-model-routes.js +0 -12
- package/dist/server-session-routes.d.ts +1 -2
- package/dist/server-session-routes.js +10 -13
- package/dist/server-speech-routes.d.ts +0 -2
- package/dist/server-speech-routes.js +1 -7
- package/dist/server-task-routes.d.ts +0 -3
- package/dist/server-task-routes.js +10 -13
- package/dist/server.js +8 -20
- package/dist/session-ai-context.d.ts +4 -6
- package/dist/session-ai-context.js +9 -10
- package/dist/silicon-employee-dispatch.d.ts +6 -4
- package/dist/silicon-employee-dispatch.js +6 -9
- package/dist/speech-service.d.ts +1 -3
- package/dist/speech-service.js +3 -14
- package/dist/speech-types.d.ts +0 -5
- package/dist/storage.d.ts +0 -2
- package/dist/storage.js +1 -18
- package/dist/structured-session-manager.d.ts +2 -3
- package/dist/structured-session-manager.js +7 -11
- package/dist/system-employee.d.ts +4 -6
- package/dist/system-employee.js +5 -8
- package/dist/types.d.ts +1 -3
- package/dist/user-memory.d.ts +0 -2
- package/dist/user-memory.js +1 -1
- package/dist/web-ui/content/ai-teams.js +10 -10
- package/dist/web-ui/content/scripts.js +204 -316
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +0 -1
- package/dist/web-ui/embedded-assets.js +3 -4
- package/dist/web-ui/index.js +3 -3
- package/dist/web-ui/provider-identity.d.ts +0 -2
- package/dist/web-ui/provider-identity.js +1 -7
- package/dist/web-ui/scripts.d.ts +0 -2
- package/dist/web-ui/scripts.js +0 -5
- package/package.json +1 -1
- package/dist/employee-text.d.ts +0 -28
- package/dist/employee-text.js +0 -124
- package/dist/speech-polisher-employee.d.ts +0 -6
- package/dist/speech-polisher-employee.js +0 -23
- package/dist/speech-polisher-identity.d.ts +0 -4
- package/dist/speech-polisher-identity.js +0 -4
- package/dist/speech-polisher-service.d.ts +0 -31
- package/dist/speech-polisher-service.js +0 -88
- package/dist/speech-support.d.ts +0 -6
- package/dist/speech-support.js +0 -26
package/dist/agent-dispatch.d.ts
CHANGED
|
@@ -5,11 +5,6 @@ import type { StructuredSessionManager } from "./structured-session-manager.js";
|
|
|
5
5
|
import type { WandTask, WandTaskAgent } from "./task-types.js";
|
|
6
6
|
import { type SiliconEmployee } from "./ai-team-types.js";
|
|
7
7
|
import type { SessionSnapshot, WandConfig } from "./types.js";
|
|
8
|
-
import type { StructuredFailure } from "./structured-failure.js";
|
|
9
|
-
/** A local capability check rejected the input before a session or provider request existed. */
|
|
10
|
-
export declare class AgentDispatchPreflightError extends Error {
|
|
11
|
-
readonly failure: StructuredFailure;
|
|
12
|
-
}
|
|
13
8
|
export interface AgentDispatchDeps {
|
|
14
9
|
storage: WandStorage;
|
|
15
10
|
config: WandConfig;
|
package/dist/agent-dispatch.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { getDefaultModelForProvider } from "./config.js";
|
|
2
|
-
import { isLocalDecisionModel } from "./decision-expert-identity.js";
|
|
3
2
|
import { defaultModelGroupSelector } from "./model-groups.js";
|
|
4
3
|
import { defaultRoleForCli } from "./default-employee.js";
|
|
5
4
|
import { providerCliCommand } from "./session-provider.js";
|
|
6
5
|
import { agentKey } from "./ai-team-types.js";
|
|
7
|
-
/** A local capability check rejected the input before a session or provider request existed. */
|
|
8
|
-
export class AgentDispatchPreflightError extends Error {
|
|
9
|
-
failure = { kind: "preflight", delivery: "rejected", retryable: true };
|
|
10
|
-
}
|
|
11
6
|
/** 任务卡的执行目录:工作任务的 worktree / 目录 → 所属项目目录 → 全局默认目录。 */
|
|
12
7
|
export function resolveTaskDispatchTarget(deps, task) {
|
|
13
8
|
const workspace = task.workspaceId ? deps.storage.getWorkspace(task.workspaceId) : null;
|
|
@@ -47,9 +42,6 @@ export async function dispatchAgentForTask(deps, input) {
|
|
|
47
42
|
const provider = agent.provider;
|
|
48
43
|
const model = agent.model === "default" ? "" : agent.model;
|
|
49
44
|
const resolvedModel = model || defaultModelGroupSelector(config.modelGroups, provider, getDefaultModelForProvider(config, provider)) || getDefaultModelForProvider(config, provider) || undefined;
|
|
50
|
-
if (isLocalDecisionModel(resolvedModel)) {
|
|
51
|
-
throw new AgentDispatchPreflightError("LAYA 仅支持有界决策,不能执行聊天或任务;该候选尚未接受输入。");
|
|
52
|
-
}
|
|
53
45
|
const session = agent.kind === "pty"
|
|
54
46
|
? await processes.start(providerCliCommand(provider), cwd, agent.mode, input.prompt, {
|
|
55
47
|
provider,
|
package/dist/ai-team-runner.d.ts
CHANGED
|
@@ -362,7 +362,7 @@ export declare class AiTeamRunner {
|
|
|
362
362
|
/** host-disabled 是进程级信号:本次运行该 kind 的候选全部不再尝试。 */
|
|
363
363
|
private disableHostKind;
|
|
364
364
|
/**
|
|
365
|
-
* 失败候选的黑名单记账(§3.4):
|
|
365
|
+
* 失败候选的黑名单记账(§3.4):spawn-missing 封 provider、model-unknown 直接封五元组、
|
|
366
366
|
* startup-timeout 累计到 STARTUP_TIMEOUT_STRIKES 才封。runtime-failure 这类不计入,
|
|
367
367
|
* 一次写完一次落库。
|
|
368
368
|
*/
|
package/dist/ai-team-runner.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { freezeTeamEmployees, projectTeamEmployees, teamMemberEmployee } from "./ai-team-employee-binding.js";
|
|
7
7
|
import { buildAiTeamDeliverySummary } from "./ai-team-delivery.js";
|
|
8
|
-
import { dispatchAgentForTask,
|
|
8
|
+
import { dispatchAgentForTask, resolveTaskDispatchTarget, sendToAgentSession, stopAgentSession, } from "./agent-dispatch.js";
|
|
9
9
|
import { AI_TEAM_REPORT_DIR, aiTeamChatHistoryPath, aiTeamHandoffPath, aiTeamReportPath, buildLeaderFollowupPrompt, buildLeaderFormatRetryPrompt, buildAiTeamObjective, buildLeaderKickoffPrompt, buildMemberPrompt, leaderOf, parseLeaderDecision, renderChatHistoryFile, renderHandoffFile, } from "./ai-team-prompts.js";
|
|
10
10
|
import { AI_TEAM_STARTUP_WINDOW_MS, classifyCandidateFailure, classifyStartupFailure, isDegradableWorkFailure, isModelUnknownBeforeDispatch, } from "./ai-team-availability.js";
|
|
11
11
|
import { AI_TEAM_ACTIVE_RUN_STATUSES, AI_TEAM_DETAIL_CHAT_TURNS, AI_TEAM_SILENCE_HEARTBEAT_MS, AI_TEAM_TERMINAL_RUN_STATUSES, agentKey, aiTeamChatTitle, memberAgents, } from "./ai-team-types.js";
|
|
@@ -1551,8 +1551,7 @@ export class AiTeamRunner {
|
|
|
1551
1551
|
}
|
|
1552
1552
|
catch (error) {
|
|
1553
1553
|
const message = getErrorMessage(error);
|
|
1554
|
-
const
|
|
1555
|
-
const kind = rejected ? "input-rejected" : classifyCandidateFailure(message);
|
|
1554
|
+
const kind = classifyCandidateFailure(message);
|
|
1556
1555
|
if (agent && kind === "host-disabled") {
|
|
1557
1556
|
// 进程级信号(agent-dispatch 的「当前服务未启用 … 会话」):本次运行该 kind 的候选全部不再
|
|
1558
1557
|
// 尝试,也不能当成「不可降级」直接吞掉——记进黑名单后交给既有失败出口。
|
|
@@ -1567,7 +1566,7 @@ export class AiTeamRunner {
|
|
|
1567
1566
|
await this.degradeStep(run, running, { kind, reason: message }, proceed, buildPrompt);
|
|
1568
1567
|
return;
|
|
1569
1568
|
}
|
|
1570
|
-
if (agent && step.kind === "work" &&
|
|
1569
|
+
if (agent && step.kind === "work" && kind === "spawn-missing"
|
|
1571
1570
|
&& (!member?.employeeId || reusable === null)
|
|
1572
1571
|
&& !existsSync(path.join(run.cwd, step.reportPath)) && !this.noDegrade.has(step.id)) {
|
|
1573
1572
|
// §3.4 触发点 1:会话根本没起来,ENOENT 对 structured / pty 同样可靠(修正 B4 排除的是
|
|
@@ -1765,8 +1764,7 @@ export class AiTeamRunner {
|
|
|
1765
1764
|
reason: `本服务未启用${agent.kind === "pty" ? "终端" : "结构化"}会话,候选 ${agent.provider} 不再尝试`,
|
|
1766
1765
|
};
|
|
1767
1766
|
}
|
|
1768
|
-
|
|
1769
|
-
if (agent.engine !== "sdk" && state.providers.includes(agent.provider)) {
|
|
1767
|
+
if (state.providers.includes(agent.provider)) {
|
|
1770
1768
|
return { kind: "spawn-missing", reason: `${agent.provider} CLI 不可用` };
|
|
1771
1769
|
}
|
|
1772
1770
|
const blocked = state.agents.find((item) => item.key === agentKey(agent));
|
|
@@ -1790,7 +1788,7 @@ export class AiTeamRunner {
|
|
|
1790
1788
|
this.saveState(runId, { ...state, hostDisabled: [...state.hostDisabled, kind] });
|
|
1791
1789
|
}
|
|
1792
1790
|
/**
|
|
1793
|
-
* 失败候选的黑名单记账(§3.4):
|
|
1791
|
+
* 失败候选的黑名单记账(§3.4):spawn-missing 封 provider、model-unknown 直接封五元组、
|
|
1794
1792
|
* startup-timeout 累计到 STARTUP_TIMEOUT_STRIKES 才封。runtime-failure 这类不计入,
|
|
1795
1793
|
* 一次写完一次落库。
|
|
1796
1794
|
*/
|
|
@@ -1798,12 +1796,6 @@ export class AiTeamRunner {
|
|
|
1798
1796
|
const state = this.stateOf(runId);
|
|
1799
1797
|
const key = agentKey(agent);
|
|
1800
1798
|
if (kind === "spawn-missing") {
|
|
1801
|
-
if (agent.engine === "sdk") {
|
|
1802
|
-
if (state.agents.some((item) => item.key === key))
|
|
1803
|
-
return;
|
|
1804
|
-
this.saveState(runId, { ...state, agents: [...state.agents, { key, kind }] });
|
|
1805
|
-
return;
|
|
1806
|
-
}
|
|
1807
1799
|
if (state.providers.includes(agent.provider))
|
|
1808
1800
|
return;
|
|
1809
1801
|
this.saveState(runId, { ...state, providers: [...state.providers, agent.provider] });
|
package/dist/ai-team-types.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { DECISION_EXPERT_KEY } from "./decision-expert-identity.js";
|
|
2
|
-
import { SPEECH_POLISHER_KEY } from "./speech-polisher-identity.js";
|
|
3
2
|
export const AI_TEAM_MIN_MEMBERS = 2;
|
|
4
3
|
export const AI_TEAM_MAX_MEMBERS = 8;
|
|
5
4
|
export const AI_TEAM_DEFAULT_MAX_STEPS = 30;
|
|
@@ -53,13 +52,13 @@ export function isDecisionExpertSiliconEmployee(employee) {
|
|
|
53
52
|
return employee?.systemKey === DECISION_EXPERT_KEY;
|
|
54
53
|
}
|
|
55
54
|
export function isBuiltinSiliconEmployee(employee) {
|
|
56
|
-
return isSystemSiliconEmployee(employee) || isDefaultSiliconEmployee(employee) || isDecisionExpertSiliconEmployee(employee)
|
|
55
|
+
return isSystemSiliconEmployee(employee) || isDefaultSiliconEmployee(employee) || isDecisionExpertSiliconEmployee(employee);
|
|
57
56
|
}
|
|
58
57
|
export const SILICON_EMPLOYEE_MAX_TAGS = 8;
|
|
59
58
|
export const SILICON_EMPLOYEE_TAG_MAX_CHARS = 20;
|
|
60
59
|
/** 内置标识只由真实员工身份投影,不能用自定义标签冒充。 */
|
|
61
60
|
export function siliconEmployeeTags(employee) {
|
|
62
|
-
if (isSystemSiliconEmployee(employee) || isDecisionExpertSiliconEmployee(employee)
|
|
61
|
+
if (isSystemSiliconEmployee(employee) || isDecisionExpertSiliconEmployee(employee))
|
|
63
62
|
return [SYSTEM_EMPLOYEE_TAG];
|
|
64
63
|
if (isDefaultSiliconEmployee(employee))
|
|
65
64
|
return [DEFAULT_EMPLOYEE_TAG];
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-10-
|
|
4
|
-
"version": "4.89.0
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "b60280807f605ce884aea7ec30f202157dbb3047",
|
|
3
|
+
"builtAt": "2026-10-09T11:37:44.403Z",
|
|
4
|
+
"version": "4.89.0",
|
|
5
|
+
"channel": "stable"
|
|
6
6
|
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { WandStorage } from "./storage.js";
|
|
2
2
|
import type { SessionSnapshot } from "./types.js";
|
|
3
|
-
import type { SessionRegistry } from "./session-registry.js";
|
|
4
3
|
export declare function archiveCommitTasks(storage: WandStorage, session: SessionSnapshot, entryIds: readonly string[], options?: {
|
|
5
4
|
workspaceTaskId?: string | null;
|
|
6
5
|
}): string[];
|
|
7
|
-
/** Sweep standalone chats in this project, including those absent from commit prompts. */
|
|
8
|
-
export declare function archiveCommitStandaloneSessions(storage: WandStorage, sessions: Pick<SessionRegistry, "listSlim" | "setArchived">, session: SessionSnapshot, archivedSessionIds: string[]): void;
|
|
@@ -48,18 +48,3 @@ export function archiveCommitTasks(storage, session, entryIds, options = {}) {
|
|
|
48
48
|
}
|
|
49
49
|
return storage.archiveWandTasks(eligible);
|
|
50
50
|
}
|
|
51
|
-
/** Sweep standalone chats in this project, including those absent from commit prompts. */
|
|
52
|
-
export function archiveCommitStandaloneSessions(storage, sessions, session, archivedSessionIds) {
|
|
53
|
-
const directory = projectCwdForSession(session);
|
|
54
|
-
if (!directory)
|
|
55
|
-
return;
|
|
56
|
-
for (const candidate of sessions.listSlim()) {
|
|
57
|
-
if (candidate.archived || projectCwdForSession(candidate) !== directory)
|
|
58
|
-
continue;
|
|
59
|
-
// A workspace task remains a task even if it has no corresponding board card.
|
|
60
|
-
if (candidate.workspaceTaskId || cardIdForSession(storage, candidate.id))
|
|
61
|
-
continue;
|
|
62
|
-
if (sessions.setArchived(candidate.id, true))
|
|
63
|
-
archivedSessionIds.push(candidate.id);
|
|
64
|
-
}
|
|
65
|
-
}
|
package/dist/config.js
CHANGED
|
@@ -255,7 +255,6 @@ export async function loadConfigWithStorage(configPath, storage) {
|
|
|
255
255
|
// 默认任务伙伴独立于系统运维;重启保留短期偏好与用户维护的候选。
|
|
256
256
|
storage.ensureDefaultSiliconEmployee(config.defaultProvider);
|
|
257
257
|
storage.ensureDecisionExpertEmployee();
|
|
258
|
-
storage.ensureSpeechPolisherEmployee();
|
|
259
258
|
// 如果 JSON 里有偏好字段(说明是老版本配置或刚迁移),重写一次干净版本
|
|
260
259
|
const hasLegacyPrefs = PREFERENCE_KEYS.some((key) => key in rawInput);
|
|
261
260
|
const hasLegacySecrets = "password" in rawInput || "appSecret" in rawInput;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SiliconEmployee } from "./ai-team-types.js";
|
|
2
2
|
import type { WandTaskAgent } from "./task-types.js";
|
|
3
|
-
export declare const DECISION_EXPERT_DUTY = "Wand \u51B3\u7B56\u4E13\u5BB6\uFF1A\u6839\u636E\u6700\u5C11\u5FC5\u8981\u8BC1\u636E\u505A\u6709\u754C\u9009\u62E9\u3001\u8BC4\u5206\u4E0E\u662F\u975E\u5224\u65AD\uFF1B\
|
|
3
|
+
export declare const DECISION_EXPERT_DUTY = "Wand \u51B3\u7B56\u4E13\u5BB6\uFF1A\u6839\u636E\u6700\u5C11\u5FC5\u8981\u8BC1\u636E\u505A\u6709\u754C\u9009\u62E9\u3001\u8BC4\u5206\u4E0E\u662F\u975E\u5224\u65AD\uFF1B\u5148\u6838\u5BF9\u672C\u673A\u80FD\u529B\uFF0C\u4F18\u5148\u672C\u5730\u51B3\u7B56\uFF0C\u5907\u7528\u514D\u8D39\u6A21\u578B\u3002";
|
|
4
4
|
export declare const DECISION_EXPERT_PROMPT: string;
|
|
5
5
|
export declare function decisionExpertSeedAgents(): WandTaskAgent[];
|
|
6
6
|
export declare function decisionExpertDefinition(now: string, existing?: SiliconEmployee | null): SiliconEmployee;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { SYSTEM_EMPLOYEE_TAG } from "./ai-team-types.js";
|
|
2
2
|
import { DECISION_EXPERT_ID, DECISION_EXPERT_KEY, DECISION_EXPERT_NAME, WAND_LOCAL_DECISION_MODEL } from "./decision-expert-identity.js";
|
|
3
3
|
import { OPENROUTER_FREE_SELECTOR } from "./openrouter-free-selection.js";
|
|
4
|
-
export const DECISION_EXPERT_DUTY = "Wand
|
|
4
|
+
export const DECISION_EXPERT_DUTY = "Wand 决策专家:根据最少必要证据做有界选择、评分与是非判断;先核对本机能力,优先本地决策,备用免费模型。";
|
|
5
5
|
export const DECISION_EXPERT_PROMPT = [
|
|
6
6
|
"你是 Wand 内置系统员工「决策专家」。你的职责是基于用户提供的证据,做有界候选选择、评分与是非判断。",
|
|
7
|
-
"
|
|
7
|
+
"首选为 Wand 的 LAYA 本地决策模型,备用默认 Wand 免费分组。LAYA 不生成开放式文本,不伪装成聊天模型。",
|
|
8
8
|
"先核对机器的 CPU、内存、Apple Silicon / Metal 和运行时健康。性能/平台不足时明确说明原因,建议配置你的调用链,不能捏造可以运行。",
|
|
9
9
|
"只使用输入中已授权的最少证据,不读取别人的知识/私聊。概率不是正确性保证,更不是权限、删除、发布或付款批准。",
|
|
10
10
|
"严格输出任务要求的格式;不编造选项、解释、执行结果或事实。失败有明确原因,不盲目重复已执行/未知送达的工作。",
|
|
11
|
-
"
|
|
11
|
+
"免费分组在每次请求前重新核对价格;没有免费候选时停止并提示配置,不静默换付费模型。",
|
|
12
12
|
].join("\n");
|
|
13
13
|
export function decisionExpertSeedAgents() {
|
|
14
14
|
return [WAND_LOCAL_DECISION_MODEL, OPENROUTER_FREE_SELECTOR].map(model => ({ provider: "pi", engine: "sdk", model,
|
|
@@ -3,18 +3,16 @@ import { type DecisionRequest, type DecisionResult } from "./decision-types.js";
|
|
|
3
3
|
import type { DecisionService } from "./decision-service.js";
|
|
4
4
|
import type { SiliconEmployee } from "./ai-team-types.js";
|
|
5
5
|
import { type OpenRouterFreeModelsService } from "./openrouter-free-models.js";
|
|
6
|
-
import type {
|
|
6
|
+
import type { WandConfig } from "./types.js";
|
|
7
7
|
import type { WandTaskAgent } from "./task-types.js";
|
|
8
8
|
interface DecisionExpertDeps {
|
|
9
9
|
employee(): SiliconEmployee | null;
|
|
10
10
|
local: Pick<DecisionService, "status" | "evaluate">;
|
|
11
11
|
free: Pick<OpenRouterFreeModelsService, "status" | "resolveForCall">;
|
|
12
|
-
config: WandConfig
|
|
12
|
+
config: Pick<WandConfig, "modelGroups">;
|
|
13
13
|
hardware?: () => DecisionHardwareAssessment;
|
|
14
14
|
timeoutMs?: number;
|
|
15
|
-
/**
|
|
16
|
-
prepare?: (agent: WandTaskAgent, request: AiTextRequest, signal: AbortSignal) => Promise<() => Promise<string>>;
|
|
17
|
-
/** Compatibility seam for existing bounded evaluator fixtures. */
|
|
15
|
+
/** Tests may inject a free, no-tool structured generator; production never silently swaps engine. */
|
|
18
16
|
generate?: (agent: WandTaskAgent, request: DecisionRequest, employee: SiliconEmployee, signal?: AbortSignal) => Promise<{
|
|
19
17
|
answers: unknown;
|
|
20
18
|
inputTokens: number;
|
|
@@ -22,7 +20,7 @@ interface DecisionExpertDeps {
|
|
|
22
20
|
model: string;
|
|
23
21
|
}>;
|
|
24
22
|
}
|
|
25
|
-
/**
|
|
23
|
+
/** Dedicated bounded employee evaluator. LAYA is not sent to a chat provider as a fake model ID. */
|
|
26
24
|
export declare class DecisionExpertService {
|
|
27
25
|
private readonly deps;
|
|
28
26
|
private active;
|
|
@@ -42,5 +40,6 @@ export declare class DecisionExpertService {
|
|
|
42
40
|
notice: string | null;
|
|
43
41
|
};
|
|
44
42
|
evaluate(value: unknown, caller: string, signal?: AbortSignal): Promise<DecisionResult>;
|
|
43
|
+
private generate;
|
|
45
44
|
}
|
|
46
45
|
export {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { DECISION_EXPERT_ID, DECISION_EXPERT_KEY, WAND_LOCAL_DECISION_MODEL } from "./decision-expert-identity.js";
|
|
2
2
|
import { decisionHardware } from "./decision-hardware.js";
|
|
3
3
|
import { DecisionError, parseDecisionRequest, parseDecisionResult } from "./decision-types.js";
|
|
4
|
-
import { isOpenRouterFreeSelector } from "./openrouter-free-models.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import { isOpenRouterFreeSelector, OPENROUTER_FREE_ROUTING } from "./openrouter-free-models.js";
|
|
5
|
+
import { freeModelOrder } from "./model-groups.js";
|
|
6
|
+
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
7
|
+
/** Dedicated bounded employee evaluator. LAYA is not sent to a chat provider as a fake model ID. */
|
|
7
8
|
export class DecisionExpertService {
|
|
8
9
|
deps;
|
|
9
10
|
active = 0;
|
|
@@ -13,19 +14,12 @@ export class DecisionExpertService {
|
|
|
13
14
|
status() {
|
|
14
15
|
const local = this.deps.local.status(), employee = this.deps.employee(), free = this.deps.free.status();
|
|
15
16
|
const hardware = (this.deps.hardware ?? decisionHardware)();
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (isOpenRouterFreeSelector(agent.model))
|
|
21
|
-
return agent.provider === "pi" && agent.engine === "sdk" && free.configured && free.modelCount > 0;
|
|
22
|
-
return agent.kind === "structured";
|
|
23
|
-
});
|
|
24
|
-
return { enabled: !!employee, supported: true, configured, state: local.state, queued: this.active,
|
|
25
|
-
completed: local.completed, failed: local.failed, experimental: true,
|
|
17
|
+
const backup = employee?.agents.some(agent => agent.provider === "pi" && agent.engine === "sdk" && agent.kind === "structured" && isOpenRouterFreeSelector(agent.model)) && free.configured && free.modelCount > 0;
|
|
18
|
+
const primary = employee?.agents.some(agent => agent.provider === "pi" && agent.engine === "sdk" && agent.kind === "structured" && agent.model === WAND_LOCAL_DECISION_MODEL);
|
|
19
|
+
return { enabled: !!employee, supported: true, configured: !!employee && ((!!primary && hardware.suitable && local.supported && local.enabled && local.configured) || !!backup),
|
|
20
|
+
state: local.state, queued: this.active, completed: local.completed, failed: local.failed, experimental: true,
|
|
26
21
|
hardware, employeeId: employee?.id ?? DECISION_EXPERT_ID, employeeName: employee?.name ?? "决策专家",
|
|
27
|
-
notice: !hardware.suitable ? hardware.message : !local.enabled || !local.configured
|
|
28
|
-
? "本地决策未就绪,可初始化 LAYA 或配置「决策专家」的工具与模型调用链。" : null };
|
|
22
|
+
notice: !hardware.suitable ? hardware.message : !local.enabled || !local.configured ? "本地决策未就绪,可初始化 LAYA 或配置「决策专家」员工调用链使用备用免费分组。" : null };
|
|
29
23
|
}
|
|
30
24
|
async evaluate(value, caller, signal) {
|
|
31
25
|
const request = parseDecisionRequest(value), employee = this.deps.employee();
|
|
@@ -40,92 +34,72 @@ export class DecisionExpertService {
|
|
|
40
34
|
const timeout = new DecisionError("TIMEOUT", "决策专家请求超时,请稍后重试或调整该员工调用链。", 504);
|
|
41
35
|
const timer = setTimeout(() => deadline.abort(timeout), this.deps.timeoutMs ?? 45_000);
|
|
42
36
|
signal = signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal;
|
|
43
|
-
const prompt = {
|
|
44
|
-
system: `${employee.prompt}\n只输出JSON对象 {"answers":{...}}。对每题保留type;choice给choice和所有criteria的probabilities(总和1),score给score和以0开始索引的probabilities(总和1),noul给0到1的noul。不得执行操作或增加候选。概率是实验性辅助而非授权。`,
|
|
45
|
-
prompt: JSON.stringify(request),
|
|
46
|
-
};
|
|
47
37
|
try {
|
|
48
38
|
const hardware = (this.deps.hardware ?? decisionHardware)();
|
|
49
|
-
for (
|
|
50
|
-
|
|
39
|
+
for (let candidate = 0; candidate < employee.agents.length; candidate += 1) {
|
|
40
|
+
const agent = employee.agents[candidate];
|
|
51
41
|
if (agent.model === WAND_LOCAL_DECISION_MODEL) {
|
|
52
42
|
if (agent.provider !== "pi" || agent.engine !== "sdk" || agent.kind !== "structured")
|
|
53
|
-
|
|
43
|
+
throw new DecisionError("CONFIGURATION_REQUIRED", "本地 LAYA 候选需使用 Wand 决策执行入口;请核对「决策专家」调用链。", 503);
|
|
54
44
|
const local = this.deps.local.status();
|
|
55
45
|
if (!hardware.suitable || !local.enabled || !local.configured || !local.supported)
|
|
56
46
|
continue;
|
|
57
47
|
try {
|
|
58
|
-
const result = await
|
|
48
|
+
const result = await this.deps.local.evaluate(request, caller, signal);
|
|
59
49
|
return { ...result, executor: { employeeId: employee.id, candidate, source: "local" } };
|
|
60
50
|
}
|
|
61
51
|
catch (error) {
|
|
62
|
-
signal.
|
|
63
|
-
|
|
52
|
+
if (deadline.signal.aborted)
|
|
53
|
+
throw timeout;
|
|
54
|
+
if (signal?.aborted || (error instanceof DecisionError && ["CANCELLED", "INVALID_REQUEST", "RATE_LIMIT", "BUSY"].includes(error.code)))
|
|
64
55
|
throw error;
|
|
65
|
-
//
|
|
56
|
+
// This is read-only inference, never a replay of a tool/task side effect.
|
|
66
57
|
continue;
|
|
67
58
|
}
|
|
68
59
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
const availability = new AbortController();
|
|
75
|
-
const checkTimer = setTimeout(() => availability.abort(new Error("候选可用性检查超时。")), 8000);
|
|
76
|
-
const check = AbortSignal.any([signal, availability.signal]);
|
|
77
|
-
let generate;
|
|
78
|
-
let usage;
|
|
79
|
-
try {
|
|
80
|
-
generate = await abortableEmployeeText((this.deps.prepare ?? ((next, text, checkedSignal) => prepareEmployeeTextCandidate({
|
|
81
|
-
config: this.deps.config, free: this.deps.free, onUsage: value => { usage = value; },
|
|
82
|
-
}, next, text, checkedSignal)))(agent, prompt, check), check);
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
signal.throwIfAborted();
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
clearTimeout(checkTimer);
|
|
90
|
-
}
|
|
91
|
-
run = async () => {
|
|
92
|
-
const raw = await abortableEmployeeText(generate(), signal);
|
|
93
|
-
if (raw.length > 32768)
|
|
94
|
-
throw new Error("决策结果超过32KiB。");
|
|
95
|
-
const body = JSON.parse(raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, ""));
|
|
96
|
-
return { answers: body?.answers, inputTokens: usage?.inputTokens ?? 0, outputTokens: usage?.outputTokens ?? 0,
|
|
97
|
-
model: usage?.model ?? agent.model, available: !!usage };
|
|
98
|
-
};
|
|
60
|
+
if (!isOpenRouterFreeSelector(agent.model)) {
|
|
61
|
+
// A missing free pool never authorizes using an arbitrary/paid provider behind the user's back.
|
|
62
|
+
throw new DecisionError("CONFIGURATION_REQUIRED", "此建议仅允许已配置的本地决策或 Wand 免费分组;请核对「决策专家」调用链与费用边界。", 503);
|
|
99
63
|
}
|
|
64
|
+
if (agent.provider !== "pi" || agent.engine !== "sdk")
|
|
65
|
+
throw new DecisionError("CONFIGURATION_REQUIRED", "建议的免费分组候选需使用 Wand Agent;不会用另一引擎冒充,请配置「决策专家」调用链。", 503);
|
|
100
66
|
try {
|
|
101
|
-
const generated = await
|
|
102
|
-
signal.throwIfAborted();
|
|
67
|
+
const generated = await (this.deps.generate ?? this.generate.bind(this))(agent, request, employee, signal);
|
|
103
68
|
const result = parseDecisionResult({ answers: generated.answers, usage: { input_tokens: generated.inputTokens, output_tokens: generated.outputTokens, truncated: false } }, request, { model: generated.model, runtime: "decision-expert" });
|
|
104
|
-
|
|
105
|
-
result.usage.available = false;
|
|
106
|
-
return { ...result, executor: { employeeId: employee.id, candidate,
|
|
107
|
-
source: isOpenRouterFreeSelector(agent.model) ? "free-group" : agent.engine === "sdk" ? "sdk" : "cli" } };
|
|
69
|
+
return { ...result, executor: { employeeId: employee.id, candidate, source: "free-group" } };
|
|
108
70
|
}
|
|
109
71
|
catch (error) {
|
|
110
|
-
signal.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
72
|
+
if (deadline.signal.aborted)
|
|
73
|
+
throw timeout;
|
|
74
|
+
if (signal?.aborted)
|
|
75
|
+
throw new DecisionError("CANCELLED", "决策专家请求已取消。", 499);
|
|
76
|
+
// Once a free model request was sent, don't silently repeat it via another candidate.
|
|
77
|
+
throw new DecisionError("CONFIGURATION_REQUIRED", "决策专家免费分组调用失败或结果无效,请同步免费分组并配置该员工调用链。", 503);
|
|
115
78
|
}
|
|
116
79
|
}
|
|
117
|
-
throw new DecisionError("CONFIGURATION_REQUIRED", "
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
if (deadline.signal.aborted)
|
|
121
|
-
throw timeout;
|
|
122
|
-
if (signal.aborted)
|
|
123
|
-
throw new DecisionError("CANCELLED", "决策专家请求已取消。", 499);
|
|
124
|
-
throw error;
|
|
80
|
+
throw new DecisionError("CONFIGURATION_REQUIRED", !hardware.suitable ? hardware.message : "决策专家没有当前可用的候选;请初始化 LAYA 或配置该员工备用调用链。", 503);
|
|
125
81
|
}
|
|
126
82
|
finally {
|
|
127
83
|
clearTimeout(timer);
|
|
128
84
|
this.active -= 1;
|
|
129
85
|
}
|
|
130
86
|
}
|
|
87
|
+
async generate(agent, request, employee, signal) {
|
|
88
|
+
const order = freeModelOrder(this.deps.config.modelGroups);
|
|
89
|
+
const checked = await this.deps.free.resolveForCall(agent.model, signal, order.length ? { allowedSelectors: order } : {});
|
|
90
|
+
const { streamSimple } = await import("@earendil-works/pi-ai/api/openai-completions");
|
|
91
|
+
const model = checked.model;
|
|
92
|
+
const message = await streamSimple(model, normalizeContext({
|
|
93
|
+
systemPrompt: `${employee.prompt}\n只输出JSON对象 {"answers":{...}}。对每题保留type;choice给choice和所有criteria的probabilities(总和1),score给score和以0开始索引的probabilities(总和1),noul给0到1的noul。不得输出工具调用、执行操作或增加候选。概率是实验性辅助而非授权。`,
|
|
94
|
+
messages: [{ role: "user", content: JSON.stringify(request), timestamp: Date.now() }], tools: [],
|
|
95
|
+
}), { apiKey: checked.apiKey, signal, maxTokens: Math.min(4096, model.maxTokens), toolChoice: "none",
|
|
96
|
+
onPayload: payload => ({ ...payload, provider: OPENROUTER_FREE_ROUTING }) }).result();
|
|
97
|
+
if (message.stopReason === "error" || message.stopReason === "aborted" || message.content.some(part => part.type === "toolCall"))
|
|
98
|
+
throw new Error("Decision expert failed");
|
|
99
|
+
const text = message.content.filter(part => part.type === "text").map(part => part.text).join("").trim();
|
|
100
|
+
if (text.length > 32768)
|
|
101
|
+
throw new Error("Decision expert output too large");
|
|
102
|
+
const body = JSON.parse(text);
|
|
103
|
+
return { answers: body.answers, inputTokens: message.usage.input, outputTokens: message.usage.output, model: `${model.provider}/${model.id}` };
|
|
104
|
+
}
|
|
131
105
|
}
|
package/dist/decision-types.d.ts
CHANGED
|
@@ -24,14 +24,13 @@ export interface DecisionResult {
|
|
|
24
24
|
input_tokens: number;
|
|
25
25
|
output_tokens: number;
|
|
26
26
|
truncated?: boolean;
|
|
27
|
-
available?: boolean;
|
|
28
27
|
};
|
|
29
28
|
experimental: true;
|
|
30
29
|
runtime: "laya-mlx" | "decision-expert";
|
|
31
30
|
executor?: {
|
|
32
31
|
employeeId: string;
|
|
33
32
|
candidate: number;
|
|
34
|
-
source: "local" | "free-group"
|
|
33
|
+
source: "local" | "free-group";
|
|
35
34
|
};
|
|
36
35
|
}
|
|
37
36
|
export declare class DecisionError extends Error {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type EmployeeTextDeps } from "./employee-text.js";
|
|
2
1
|
import type { AiTextRequest } from "./types.js";
|
|
3
2
|
import { GitStatusResult, PushResult, QuickCommitResult, SessionProvider, SessionSnapshot, TagHeadResult } from "./types.js";
|
|
4
3
|
export type QuickCommitErrorCode = "CWD_MISSING" | "NO_CWD" | "NOT_A_GIT_REPO" | "NO_COMMIT" | "NOTHING_TO_COMMIT" | "NOTHING_TO_PUSH" | "EMPTY_MESSAGE" | "EMPTY_TAG" | "EMPTY_AI_MESSAGE" | "AI_FALLBACK_FAILED" | "INVALID_AI_TAG" | "TAG_EXISTS" | "GIT_ADD_FAILED" | "GIT_DIFF_FAILED" | "GIT_COMMIT_FAILED" | "GIT_TAG_FAILED" | "CLAUDE_CLI_MISSING" | "CLAUDE_CLI_FAILED" | "CLAUDE_TIMEOUT";
|
|
@@ -27,15 +26,12 @@ export interface QuickCommitAiOptions {
|
|
|
27
26
|
inheritEnv?: boolean;
|
|
28
27
|
/** 内置「系统运维」员工的角色设定:作为系统提示前缀注入。 */
|
|
29
28
|
opsPersona?: string;
|
|
30
|
-
/**
|
|
29
|
+
/** CLI 降级链(按顺序);未设置时只用 provider/model 这一次调用。 */
|
|
31
30
|
cliCandidates?: import("./types.js").AiCliCandidate[];
|
|
32
|
-
/**
|
|
33
|
-
employeeText?: EmployeeTextDeps;
|
|
34
|
-
/** 系统应用必须经员工渠道;没有可用候选时直接失败,不退回默认 provider。 */
|
|
31
|
+
/** 系统应用必须经员工渠道;没有可用 CLI 候选时直接失败,不退回默认 provider。 */
|
|
35
32
|
employeeChannelOnly?: boolean;
|
|
36
33
|
/** 整条候选链的时间上限;默认 CLI_CHAIN_BUDGET_MS。第一条消息里的调用要短得多。 */
|
|
37
34
|
budgetMs?: number;
|
|
38
|
-
signal?: AbortSignal;
|
|
39
35
|
}
|
|
40
36
|
export declare class QuickCommitError extends Error {
|
|
41
37
|
readonly code: QuickCommitErrorCode;
|
|
@@ -51,16 +47,11 @@ export interface CommitInputOptions {
|
|
|
51
47
|
iterationDigest?: string;
|
|
52
48
|
includeDiff?: boolean;
|
|
53
49
|
}
|
|
54
|
-
type CliAiTextOptions = QuickCommitAiOptions & {
|
|
55
|
-
deadline?: number;
|
|
56
|
-
};
|
|
57
|
-
export declare function assertGeminiTextCapability(): void;
|
|
58
|
-
export declare function callCliAiText(request: AiTextRequest, cwd: string, language: string, opts: CliAiTextOptions): Promise<string>;
|
|
59
50
|
/** 角色设定走系统提示前缀;任务自己的输出格式与约束必须排在后面。 */
|
|
60
51
|
export declare function withOpsPersona(request: AiTextRequest, persona?: string): AiTextRequest;
|
|
61
52
|
/**
|
|
62
|
-
* Run a lightweight AI request through the system employee's
|
|
63
|
-
*
|
|
53
|
+
* Run a lightweight AI request through the system employee's CLI chain.
|
|
54
|
+
* 所有 Wand 自有文本调用都走本机已安装的 CLI,没有任何直连 API 分支。
|
|
64
55
|
* parseOutput 在候选链内解析/校验结果;失败后继续下一条,而非提前宣布成功。
|
|
65
56
|
*/
|
|
66
57
|
export declare function callConfiguredAiText(request: AiTextRequest, cwd: string, language: string, opts: QuickCommitAiOptions): Promise<string>;
|