@co0ontty/wand 4.82.0 → 4.83.1-beta.g97d7ae9
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 +47 -9
- package/dist/agent-dispatch.d.ts +3 -2
- package/dist/agent-dispatch.js +19 -3
- package/dist/ai-team-delivery-types.d.ts +40 -0
- package/dist/ai-team-delivery-types.js +1 -0
- package/dist/ai-team-delivery.d.ts +13 -0
- package/dist/ai-team-delivery.js +89 -0
- package/dist/ai-team-employee-binding.d.ts +16 -0
- package/dist/ai-team-employee-binding.js +77 -0
- package/dist/ai-team-prompts.js +5 -0
- package/dist/ai-team-runner.d.ts +9 -2
- package/dist/ai-team-runner.js +52 -7
- package/dist/ai-team-types.d.ts +17 -3
- package/dist/ai-team-types.js +43 -3
- package/dist/build-info.json +4 -4
- package/dist/cli.js +36 -0
- package/dist/config.d.ts +18 -10
- package/dist/config.js +63 -105
- package/dist/decision-client.d.ts +4 -0
- package/dist/decision-client.js +91 -0
- package/dist/decision-runner.d.ts +9 -0
- package/dist/decision-runner.js +36 -0
- package/dist/decision-service.d.ts +46 -0
- package/dist/decision-service.js +230 -0
- package/dist/decision-skill.d.ts +6 -0
- package/dist/decision-skill.js +70 -0
- package/dist/decision-tool.d.ts +21 -0
- package/dist/decision-tool.js +346 -0
- package/dist/decision-types.d.ts +36 -0
- package/dist/decision-types.js +123 -0
- package/dist/decision-worker.py +86 -0
- package/dist/default-employee.d.ts +4 -1
- package/dist/default-employee.js +11 -1
- package/dist/env-utils.js +3 -0
- package/dist/git-quick-commit.js +7 -1
- package/dist/message-truncator.d.ts +1 -1
- package/dist/message-truncator.js +29 -22
- package/dist/models.d.ts +1 -0
- package/dist/models.js +31 -4
- package/dist/process-manager.d.ts +39 -1
- package/dist/process-manager.js +112 -8
- package/dist/provider-catalog.d.ts +47 -0
- package/dist/provider-catalog.js +138 -0
- package/dist/provider-cli-updater.d.ts +1 -1
- package/dist/pty-foreground.d.ts +37 -0
- package/dist/pty-foreground.js +56 -0
- package/dist/pty-turn-activity.d.ts +6 -5
- package/dist/pty-turn-activity.js +6 -5
- package/dist/retention.d.ts +29 -0
- package/dist/retention.js +142 -0
- package/dist/server-ai-team-routes.d.ts +2 -1
- package/dist/server-ai-team-routes.js +27 -9
- package/dist/server-decision-routes.d.ts +10 -0
- package/dist/server-decision-routes.js +52 -0
- package/dist/server-employee-routes.js +9 -1
- package/dist/server-session-routes.d.ts +2 -2
- package/dist/server-session-routes.js +97 -1
- package/dist/server-settings-routes.js +10 -36
- package/dist/server-update-routes.js +2 -1
- package/dist/server-workspace-routes.js +8 -0
- package/dist/server.js +37 -5
- package/dist/session-completion-state.d.ts +26 -0
- package/dist/session-completion-state.js +30 -0
- package/dist/session-completion.d.ts +12 -0
- package/dist/session-completion.js +62 -0
- package/dist/session-provider.d.ts +1 -15
- package/dist/session-provider.js +4 -54
- package/dist/session-registry.d.ts +5 -0
- package/dist/session-registry.js +19 -1
- package/dist/session-transport.js +2 -0
- package/dist/storage.d.ts +12 -0
- package/dist/storage.js +119 -16
- package/dist/structured-client-protocol.js +36 -1
- package/dist/structured-provider-common.js +3 -3
- package/dist/structured-session-manager.d.ts +13 -1
- package/dist/structured-session-manager.js +196 -27
- package/dist/system-employee.js +1 -0
- package/dist/task-types.d.ts +4 -2
- package/dist/tool-preview.d.ts +8 -0
- package/dist/tool-preview.js +158 -0
- package/dist/types.d.ts +35 -2
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/scripts.js +66 -64
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +3 -0
- package/dist/web-ui/embedded-assets.js +3 -3
- package/dist/web-ui/provider-identity.d.ts +7 -13
- package/dist/web-ui/provider-identity.js +8 -71
- package/package.json +3 -2
- package/skills/wand-decision/SKILL.md +61 -0
- package/skills/wand-decision/scripts/decide.mjs +16 -0
package/dist/ai-team-runner.js
CHANGED
|
@@ -3,6 +3,8 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { freezeTeamEmployees, projectTeamEmployees, teamMemberEmployee } from "./ai-team-employee-binding.js";
|
|
7
|
+
import { buildAiTeamDeliverySummary } from "./ai-team-delivery.js";
|
|
6
8
|
import { dispatchAgentForTask, resolveTaskDispatchTarget, sendToAgentSession, stopAgentSession, } from "./agent-dispatch.js";
|
|
7
9
|
import { AI_TEAM_REPORT_DIR, aiTeamChatHistoryPath, aiTeamHandoffPath, aiTeamReportPath, buildLeaderFollowupPrompt, buildLeaderFormatRetryPrompt, buildAiTeamObjective, buildLeaderKickoffPrompt, buildMemberPrompt, leaderOf, parseLeaderDecision, renderChatHistoryFile, renderHandoffFile, } from "./ai-team-prompts.js";
|
|
8
10
|
import { AI_TEAM_STARTUP_WINDOW_MS, classifyCandidateFailure, classifyStartupFailure, isDegradableWorkFailure, isModelUnknownBeforeDispatch, } from "./ai-team-availability.js";
|
|
@@ -39,10 +41,11 @@ export function createAiTeamSessionOps(deps) {
|
|
|
39
41
|
const { session } = await dispatchAgentForTask(deps, input);
|
|
40
42
|
return session.id;
|
|
41
43
|
},
|
|
42
|
-
send: (sessionId, text) => sendToAgentSession(deps, sessionId, text),
|
|
44
|
+
send: (sessionId, text, teamRequestStarted) => sendToAgentSession(deps, sessionId, text, teamRequestStarted),
|
|
43
45
|
stop: (sessionId) => stopAgentSession(deps, sessionId),
|
|
44
46
|
snapshot: (sessionId) => deps.sessions.getLatest(sessionId),
|
|
45
47
|
ownerOf: (sessionId) => deps.sessions.ownerOf(sessionId),
|
|
48
|
+
consumeUnacceptedStartup: (sessionId, requestId) => deps.structured?.consumeUnacceptedTeamStartup(sessionId, requestId) ?? false,
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
51
|
export function createAiTeamChatOps(deps) {
|
|
@@ -101,7 +104,8 @@ export function displayAiTeam(snapshot, current) {
|
|
|
101
104
|
updatedAt: current.updatedAt,
|
|
102
105
|
members: snapshot.members.map((member) => {
|
|
103
106
|
const live = byId.get(member.id);
|
|
104
|
-
return live
|
|
107
|
+
return live && !member.employeeId && !live.employeeId
|
|
108
|
+
? { ...member, name: live.name, avatar: live.avatar } : member;
|
|
105
109
|
}),
|
|
106
110
|
};
|
|
107
111
|
}
|
|
@@ -346,6 +350,7 @@ export class AiTeamRunner {
|
|
|
346
350
|
recheckTimers = new Map();
|
|
347
351
|
/** 已经确定「不降级、按既有路径记 failed」的步骤,防止 finishStep 再判一次形成回环。 */
|
|
348
352
|
noDegrade = new Set();
|
|
353
|
+
teamRequestIds = new Map();
|
|
349
354
|
sweepTimer = null;
|
|
350
355
|
/** 脱敏参照目录只算一次:os.homedir() 读 env,configDir 随 storage 定,运行期不变。 */
|
|
351
356
|
redactionDirsValue = null;
|
|
@@ -386,9 +391,10 @@ export class AiTeamRunner {
|
|
|
386
391
|
}
|
|
387
392
|
// ── 公开操作 ──
|
|
388
393
|
async start(input) {
|
|
389
|
-
const
|
|
390
|
-
if (!
|
|
394
|
+
const definition = this.storage.getAiTeam(input.teamId);
|
|
395
|
+
if (!definition)
|
|
391
396
|
throw new Error("团队不存在。");
|
|
397
|
+
const team = freezeTeamEmployees(definition, this.storage);
|
|
392
398
|
const task = this.storage.getWandTask(input.taskId);
|
|
393
399
|
if (!task)
|
|
394
400
|
throw new Error("任务不存在。");
|
|
@@ -594,14 +600,16 @@ export class AiTeamRunner {
|
|
|
594
600
|
const task = (binding?.workspaceTaskId
|
|
595
601
|
? this.storage.getWandTaskByWorkspaceTaskId(binding.workspaceTaskId) : null)
|
|
596
602
|
?? this.storage.getWandTask(run.taskId);
|
|
603
|
+
const displayTeam = projectTeamEmployees(displayAiTeam(run.team, currentTeam), this.storage);
|
|
597
604
|
return {
|
|
598
605
|
run,
|
|
606
|
+
delivery: buildAiTeamDeliverySummary({ run, steps, displayTeam, relayTurns: messages, memberStates }),
|
|
599
607
|
chatTitle: aiTeamChatTitle(task?.title),
|
|
600
608
|
chatTitleUpdatedAt: task?.updatedAt ?? "",
|
|
601
609
|
steps,
|
|
602
610
|
memberStates,
|
|
603
611
|
// 别把展示字段写回 run.team:运行快照里的执行候选/职责由 runner 独立管理。
|
|
604
|
-
displayTeam
|
|
612
|
+
displayTeam,
|
|
605
613
|
chatTurns: messages.slice(-AI_TEAM_DETAIL_CHAT_TURNS),
|
|
606
614
|
};
|
|
607
615
|
}
|
|
@@ -788,6 +796,7 @@ export class AiTeamRunner {
|
|
|
788
796
|
this.liveTimers.clear();
|
|
789
797
|
this.lastLiveKey.clear();
|
|
790
798
|
this.noDegrade.clear();
|
|
799
|
+
this.teamRequestIds.clear();
|
|
791
800
|
}
|
|
792
801
|
/** 等当前排队的所有操作跑完(测试用)。 */
|
|
793
802
|
async idle() {
|
|
@@ -952,6 +961,10 @@ export class AiTeamRunner {
|
|
|
952
961
|
// 按真实角色身份发出正常对话气泡并展示完整报错,而不是次级 notice 小字。
|
|
953
962
|
const leader = leaderOf(run.team);
|
|
954
963
|
if (outcome.kind === "failed" && outcome.sessionError) {
|
|
964
|
+
// Leaders keep their existing wait-for-user rule, not worker candidate fallback.
|
|
965
|
+
const requestId = this.teamRequestIds.get(step.id);
|
|
966
|
+
if (step.sessionId && requestId)
|
|
967
|
+
this.ops.consumeUnacceptedStartup?.(step.sessionId, requestId);
|
|
955
968
|
this.saveStep({ ...step, status: "failed", report: outcome.text, endedAt });
|
|
956
969
|
this.postTurn(run, {
|
|
957
970
|
role: "assistant",
|
|
@@ -1288,8 +1301,12 @@ export class AiTeamRunner {
|
|
|
1288
1301
|
throw new Error("成员不存在");
|
|
1289
1302
|
if (!agent)
|
|
1290
1303
|
throw new Error("成员没有配置可用的 CLI 候选");
|
|
1304
|
+
if (member.employeeId && !teamMemberEmployee(member))
|
|
1305
|
+
throw new Error("员工执行快照缺失,不能重绑定旧运行。");
|
|
1291
1306
|
if (reusable) {
|
|
1292
|
-
await this.ops.send(reusable, prompt.message)
|
|
1307
|
+
await this.ops.send(reusable, prompt.message, (_sessionId, requestId) => {
|
|
1308
|
+
this.teamRequestIds.set(step.id, requestId);
|
|
1309
|
+
});
|
|
1293
1310
|
}
|
|
1294
1311
|
else {
|
|
1295
1312
|
const task = this.storage.getWandTask(run.taskId);
|
|
@@ -1301,9 +1318,15 @@ export class AiTeamRunner {
|
|
|
1301
1318
|
prompt: prompt.message,
|
|
1302
1319
|
automationId: `ai-team:${run.id}`,
|
|
1303
1320
|
systemPrompt: prompt.system,
|
|
1321
|
+
employee: teamMemberEmployee(member),
|
|
1322
|
+
employeeCandidateIndex: member.employeeId ? info.usedCandidate : undefined,
|
|
1323
|
+
teamRequestStarted: (_sessionId, requestId) => { this.teamRequestIds.set(step.id, requestId); },
|
|
1304
1324
|
});
|
|
1305
1325
|
running = { ...running, sessionId };
|
|
1306
1326
|
this.saveStep(running);
|
|
1327
|
+
// Immediate async spawn failure can precede open() returning and the session→step link.
|
|
1328
|
+
if (this.ops.snapshot(sessionId)?.status === "failed")
|
|
1329
|
+
this.ingest({ type: "status", sessionId });
|
|
1307
1330
|
}
|
|
1308
1331
|
}
|
|
1309
1332
|
catch (error) {
|
|
@@ -1315,6 +1338,7 @@ export class AiTeamRunner {
|
|
|
1315
1338
|
this.disableHostKind(run.id, agent.kind);
|
|
1316
1339
|
}
|
|
1317
1340
|
else if (agent && step.kind === "work" && kind === "spawn-missing"
|
|
1341
|
+
&& (!member?.employeeId || reusable === null)
|
|
1318
1342
|
&& !existsSync(path.join(run.cwd, step.reportPath)) && !this.noDegrade.has(step.id)) {
|
|
1319
1343
|
// §3.4 触发点 1:会话根本没起来,ENOENT 对 structured / pty 同样可靠(修正 B4 排除的是
|
|
1320
1344
|
// PTY 的异步分类)。传 `running` 而不是入参 `step`:复用会话时 sessionId 只在 running 上。
|
|
@@ -1432,6 +1456,15 @@ export class AiTeamRunner {
|
|
|
1432
1456
|
const member = this.member(run.team, step.memberId);
|
|
1433
1457
|
if (!member)
|
|
1434
1458
|
return null;
|
|
1459
|
+
const requestId = this.teamRequestIds.get(step.id);
|
|
1460
|
+
const unaccepted = !!(step.sessionId && requestId
|
|
1461
|
+
&& this.ops.consumeUnacceptedStartup?.(step.sessionId, requestId));
|
|
1462
|
+
// Bound employees may retry only this request's explicit unaccepted fact, never a text/time guess.
|
|
1463
|
+
if (member.employeeId) {
|
|
1464
|
+
if (!unaccepted || existsSync(path.join(run.cwd, step.reportPath)))
|
|
1465
|
+
return null;
|
|
1466
|
+
return { kind: "spawn-missing", reason: outcomeText };
|
|
1467
|
+
}
|
|
1435
1468
|
const info = this.infoOf(step);
|
|
1436
1469
|
const candidates = memberAgents(member);
|
|
1437
1470
|
const agent = candidates[info.usedCandidate];
|
|
@@ -1539,15 +1572,19 @@ export class AiTeamRunner {
|
|
|
1539
1572
|
}
|
|
1540
1573
|
/**
|
|
1541
1574
|
* 运行停下来等用户时,用户可能刚在团队页换了成员的 CLI / 模型(比如额度用尽换一个)。
|
|
1542
|
-
*
|
|
1575
|
+
* 独立 CLI 团队沿用旧刷新规则;带员工链接的运行全程保留新开工时的执行快照。
|
|
1543
1576
|
*/
|
|
1544
1577
|
refreshTeam(run) {
|
|
1578
|
+
if (run.team.members.some((member) => member.employeeId))
|
|
1579
|
+
return;
|
|
1545
1580
|
const latest = this.storage.getAiTeam(run.teamId);
|
|
1546
1581
|
if (!latest || !latest.members.some((member) => member.isLeader))
|
|
1547
1582
|
return;
|
|
1548
1583
|
const used = new Set(this.storage.listAiTeamSteps(run.id).map((step) => step.memberId));
|
|
1549
1584
|
if ([...used].some((id) => !latest.members.some((member) => member.id === id)))
|
|
1550
1585
|
return;
|
|
1586
|
+
if (latest.members.some((member) => member.employeeId))
|
|
1587
|
+
return;
|
|
1551
1588
|
run.team = latest;
|
|
1552
1589
|
}
|
|
1553
1590
|
/**
|
|
@@ -1563,6 +1600,8 @@ export class AiTeamRunner {
|
|
|
1563
1600
|
const snapshot = this.ops.snapshot(step.sessionId);
|
|
1564
1601
|
if (!snapshot || isSessionGone(snapshot))
|
|
1565
1602
|
return null;
|
|
1603
|
+
if ((snapshot.employeeId ?? undefined) !== member.employeeId)
|
|
1604
|
+
return null;
|
|
1566
1605
|
const candidates = memberAgents(member);
|
|
1567
1606
|
const previous = candidates[this.infoOf(step).usedCandidate] ?? candidates[0];
|
|
1568
1607
|
if (!previous || (agent && agentKey(previous) !== agentKey(agent)))
|
|
@@ -1722,6 +1761,12 @@ export class AiTeamRunner {
|
|
|
1722
1761
|
this.notify(run);
|
|
1723
1762
|
}
|
|
1724
1763
|
saveStep(step) {
|
|
1764
|
+
if (step.status !== "running" && step.status !== "queued") {
|
|
1765
|
+
const requestId = this.teamRequestIds.get(step.id);
|
|
1766
|
+
if (step.sessionId && requestId)
|
|
1767
|
+
this.ops.consumeUnacceptedStartup?.(step.sessionId, requestId);
|
|
1768
|
+
this.teamRequestIds.delete(step.id);
|
|
1769
|
+
}
|
|
1725
1770
|
this.storage.saveAiTeamStep(step);
|
|
1726
1771
|
}
|
|
1727
1772
|
notify(run) {
|
package/dist/ai-team-types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentActivityState } from "./mission-types.js";
|
|
2
|
+
import type { AiTeamDeliverySummary } from "./ai-team-delivery-types.js";
|
|
2
3
|
import type { WandTaskAgent } from "./task-types.js";
|
|
3
4
|
import type { ConversationTurn } from "./types.js";
|
|
4
5
|
export declare const AI_TEAM_MIN_MEMBERS = 2;
|
|
@@ -15,6 +16,8 @@ export declare function isTeamMemberRole(value: unknown): value is TeamMemberRol
|
|
|
15
16
|
export interface AiTeamMember {
|
|
16
17
|
/** 团队内唯一,形如 "m_xxxxxxxx";Leader 派工时原样引用。 */
|
|
17
18
|
id: string;
|
|
19
|
+
/** 通讯录身份链接;缺省是独立 CLI 成员,不按名字猜测。 */
|
|
20
|
+
employeeId?: string;
|
|
18
21
|
name: string;
|
|
19
22
|
/** 职责说明,原样写进提示词。 */
|
|
20
23
|
duty: string;
|
|
@@ -62,6 +65,8 @@ export interface SiliconEmployee {
|
|
|
62
65
|
prompt: string;
|
|
63
66
|
avatar: string;
|
|
64
67
|
agents: WandTaskAgent[];
|
|
68
|
+
/** 内置标签按 systemKey 固定;普通员工的标签由用户维护。旧客户端可不传。 */
|
|
69
|
+
tags?: string[];
|
|
65
70
|
/**
|
|
66
71
|
* 非空 = Wand 内置员工(如 "wand-ops" 系统运维):名字/职责/人设/头像锁定,
|
|
67
72
|
* 不可归档、不可删除,只有执行候选由用户维护。
|
|
@@ -87,14 +92,21 @@ export declare const SYSTEM_EMPLOYEE_KEY = "wand-ops";
|
|
|
87
92
|
export declare const SYSTEM_EMPLOYEE_ID = "e_wand_ops";
|
|
88
93
|
export declare const SYSTEM_EMPLOYEE_NAME = "\u52E4\u52B3\u7684\u521D\u4E8C";
|
|
89
94
|
/** 员工列表 / 设置页上的标识。 */
|
|
90
|
-
export declare const SYSTEM_EMPLOYEE_TAG = "\u7CFB\u7EDF\
|
|
95
|
+
export declare const SYSTEM_EMPLOYEE_TAG = "\u7CFB\u7EDF\u7528\u6237";
|
|
91
96
|
export declare function isSystemSiliconEmployee(employee: Pick<SiliconEmployee, "systemKey"> | null | undefined): boolean;
|
|
92
97
|
export declare const DEFAULT_EMPLOYEE_KEY = "wand-default";
|
|
93
98
|
export declare const DEFAULT_EMPLOYEE_ID = "e_wand_default";
|
|
94
|
-
export declare const DEFAULT_EMPLOYEE_NAME = "\
|
|
95
|
-
export declare const DEFAULT_EMPLOYEE_TAG = "\u9ED8\u8BA4\
|
|
99
|
+
export declare const DEFAULT_EMPLOYEE_NAME = "\u8D5B\u535A\u864E\u599E";
|
|
100
|
+
export declare const DEFAULT_EMPLOYEE_TAG = "\u9ED8\u8BA4\u7528\u6237";
|
|
96
101
|
export declare function isDefaultSiliconEmployee(employee: Pick<SiliconEmployee, "systemKey"> | null | undefined): boolean;
|
|
97
102
|
export declare function isBuiltinSiliconEmployee(employee: Pick<SiliconEmployee, "systemKey"> | null | undefined): boolean;
|
|
103
|
+
export declare const SILICON_EMPLOYEE_MAX_TAGS = 8;
|
|
104
|
+
export declare const SILICON_EMPLOYEE_TAG_MAX_CHARS = 20;
|
|
105
|
+
/** 内置标识只由真实员工身份投影,不能用自定义标签冒充。 */
|
|
106
|
+
export declare function siliconEmployeeTags(employee: Pick<SiliconEmployee, "systemKey" | "tags">): string[];
|
|
107
|
+
/** API / storage / Web 共用:去空白、去重,保留用户顺序,不静默截断。 */
|
|
108
|
+
export declare function parseSiliconEmployeeTags(value: unknown): string[];
|
|
109
|
+
export declare function parseSiliconEmployeeTagInput(value: string): string[];
|
|
98
110
|
export interface AiTeam {
|
|
99
111
|
id: string;
|
|
100
112
|
name: string;
|
|
@@ -236,6 +248,8 @@ export interface AiTeamLiveUpdate {
|
|
|
236
248
|
}
|
|
237
249
|
export interface AiTeamRunDetail {
|
|
238
250
|
run: AiTeamRun;
|
|
251
|
+
/** One run's result/files/current handoffs; absent on older servers, no side effects. */
|
|
252
|
+
delivery?: AiTeamDeliverySummary;
|
|
239
253
|
/** 仅供展示的群名,读取当前任务标题,不以团队名代替。 */
|
|
240
254
|
chatTitle?: string;
|
|
241
255
|
/** 任务标题的版本;任务改名不改变 run.updatedAt。 */
|
package/dist/ai-team-types.js
CHANGED
|
@@ -34,21 +34,61 @@ export const SYSTEM_EMPLOYEE_KEY = "wand-ops";
|
|
|
34
34
|
export const SYSTEM_EMPLOYEE_ID = "e_wand_ops";
|
|
35
35
|
export const SYSTEM_EMPLOYEE_NAME = "勤劳的初二";
|
|
36
36
|
/** 员工列表 / 设置页上的标识。 */
|
|
37
|
-
export const SYSTEM_EMPLOYEE_TAG = "
|
|
37
|
+
export const SYSTEM_EMPLOYEE_TAG = "系统用户";
|
|
38
38
|
export function isSystemSiliconEmployee(employee) {
|
|
39
39
|
return employee?.systemKey === SYSTEM_EMPLOYEE_KEY;
|
|
40
40
|
}
|
|
41
41
|
// 默认任务角色与系统运维分开;CLI 是执行器,角色不替换用户选定的工具/权限。
|
|
42
42
|
export const DEFAULT_EMPLOYEE_KEY = "wand-default";
|
|
43
43
|
export const DEFAULT_EMPLOYEE_ID = "e_wand_default";
|
|
44
|
-
export const DEFAULT_EMPLOYEE_NAME = "
|
|
45
|
-
export const DEFAULT_EMPLOYEE_TAG = "
|
|
44
|
+
export const DEFAULT_EMPLOYEE_NAME = "赛博虎妞";
|
|
45
|
+
export const DEFAULT_EMPLOYEE_TAG = "默认用户";
|
|
46
46
|
export function isDefaultSiliconEmployee(employee) {
|
|
47
47
|
return employee?.systemKey === DEFAULT_EMPLOYEE_KEY;
|
|
48
48
|
}
|
|
49
49
|
export function isBuiltinSiliconEmployee(employee) {
|
|
50
50
|
return isSystemSiliconEmployee(employee) || isDefaultSiliconEmployee(employee);
|
|
51
51
|
}
|
|
52
|
+
export const SILICON_EMPLOYEE_MAX_TAGS = 8;
|
|
53
|
+
export const SILICON_EMPLOYEE_TAG_MAX_CHARS = 20;
|
|
54
|
+
/** 内置标识只由真实员工身份投影,不能用自定义标签冒充。 */
|
|
55
|
+
export function siliconEmployeeTags(employee) {
|
|
56
|
+
if (isSystemSiliconEmployee(employee))
|
|
57
|
+
return [SYSTEM_EMPLOYEE_TAG];
|
|
58
|
+
if (isDefaultSiliconEmployee(employee))
|
|
59
|
+
return [DEFAULT_EMPLOYEE_TAG];
|
|
60
|
+
return employee.tags ?? [];
|
|
61
|
+
}
|
|
62
|
+
/** API / storage / Web 共用:去空白、去重,保留用户顺序,不静默截断。 */
|
|
63
|
+
export function parseSiliconEmployeeTags(value) {
|
|
64
|
+
if (!Array.isArray(value))
|
|
65
|
+
throw new Error("员工标签必须是数组。");
|
|
66
|
+
const tags = [];
|
|
67
|
+
for (const raw of value) {
|
|
68
|
+
if (typeof raw !== "string")
|
|
69
|
+
throw new Error("员工标签必须是文字。");
|
|
70
|
+
const tag = raw.trim();
|
|
71
|
+
if (!tag)
|
|
72
|
+
continue;
|
|
73
|
+
if (tag.length > SILICON_EMPLOYEE_TAG_MAX_CHARS) {
|
|
74
|
+
throw new Error(`每个员工标签不能超过 ${SILICON_EMPLOYEE_TAG_MAX_CHARS} 个字符。`);
|
|
75
|
+
}
|
|
76
|
+
if (/[,,、\x00-\x1f\x7f]/.test(tag))
|
|
77
|
+
throw new Error("员工标签不能包含分隔符或控制字符。");
|
|
78
|
+
if (tag === SYSTEM_EMPLOYEE_TAG || tag === DEFAULT_EMPLOYEE_TAG) {
|
|
79
|
+
throw new Error(`「${tag}」是内置标签,不可自定义。`);
|
|
80
|
+
}
|
|
81
|
+
if (!tags.includes(tag))
|
|
82
|
+
tags.push(tag);
|
|
83
|
+
}
|
|
84
|
+
if (tags.length > SILICON_EMPLOYEE_MAX_TAGS) {
|
|
85
|
+
throw new Error(`最多 ${SILICON_EMPLOYEE_MAX_TAGS} 个员工标签。`);
|
|
86
|
+
}
|
|
87
|
+
return tags;
|
|
88
|
+
}
|
|
89
|
+
export function parseSiliconEmployeeTagInput(value) {
|
|
90
|
+
return parseSiliconEmployeeTags(value.split(/[,,、\n]+/));
|
|
91
|
+
}
|
|
52
92
|
export const AI_TEAM_ACTIVE_RUN_STATUSES = ["running", "awaiting_approval", "waiting_user"];
|
|
53
93
|
/** 终态:不会再有 live 变化,runner 据此清掉该 run 的推送指纹,避免长跑服务按 run 数攒字符串。 */
|
|
54
94
|
export const AI_TEAM_TERMINAL_RUN_STATUSES = ["done", "failed", "stopped"];
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-10-
|
|
4
|
-
"version": "4.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "97d7ae95626425a23420a4f9bfabbf733d5ae73a",
|
|
3
|
+
"builtAt": "2026-10-03T02:40:52.838Z",
|
|
4
|
+
"version": "4.83.1-beta.g97d7ae9",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
package/dist/cli.js
CHANGED
|
@@ -179,6 +179,39 @@ async function main() {
|
|
|
179
179
|
await runAgentCliCommand(command, args, configPath);
|
|
180
180
|
break;
|
|
181
181
|
}
|
|
182
|
+
case "decision:configure": {
|
|
183
|
+
const { isAbsolute, join } = await import("node:path");
|
|
184
|
+
const disabling = args.includes("--disable");
|
|
185
|
+
const pythonPath = readFlagValue(args, "--python") ?? "";
|
|
186
|
+
const modelPath = readFlagValue(args, "--model") ?? "";
|
|
187
|
+
if (!disabling && (!isAbsolute(pythonPath) || !isAbsolute(modelPath)
|
|
188
|
+
|| !existsSync(pythonPath) || !existsSync(join(modelPath, "model.safetensors")))) {
|
|
189
|
+
throw new Error("用法:wand decision:configure --python <Python绝对路径> --model <已下载模型目录>;或 --disable。");
|
|
190
|
+
}
|
|
191
|
+
const { config } = await loadConfigForCli(configPath);
|
|
192
|
+
config.localDecision = disabling
|
|
193
|
+
? { enabled: false, pythonPath: config.localDecision?.pythonPath ?? "", modelPath: config.localDecision?.modelPath ?? "" }
|
|
194
|
+
: { enabled: true, pythonPath, modelPath };
|
|
195
|
+
await saveConfig(configPath, config);
|
|
196
|
+
process.stdout.write("本地决策配置已保存,重启 Wand 服务后生效。未修改系统 Python,也未下载模型。\n");
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "decision:skills": {
|
|
200
|
+
const { dirname } = await import("node:path");
|
|
201
|
+
const { installDecisionSkill } = await import("./decision-skill.js");
|
|
202
|
+
const { SESSION_PROVIDERS } = await import("./provider-catalog.js");
|
|
203
|
+
const selected = readFlagValue(args, "--providers")?.split(",") ?? [...SESSION_PROVIDERS];
|
|
204
|
+
if (selected.some((provider) => !SESSION_PROVIDERS.includes(provider)))
|
|
205
|
+
throw new Error("未知CLI技能目标。");
|
|
206
|
+
const result = await installDecisionSkill(dirname(configPath), selected);
|
|
207
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "decide": {
|
|
211
|
+
const { runDecisionCli } = await import("./decision-client.js");
|
|
212
|
+
await runDecisionCli(args.slice(1));
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
182
215
|
case "knowledge:remember":
|
|
183
216
|
case "knowledge:search":
|
|
184
217
|
case "knowledge:forget": {
|
|
@@ -234,6 +267,9 @@ Agent runtime:
|
|
|
234
267
|
wand mission:diff <mission-id> <attempt-id>
|
|
235
268
|
wand mission:review <mission-id> <attempt-id> --file <path> --body <text> [--line N]
|
|
236
269
|
wand mission:review:send <mission-id> <attempt-id>
|
|
270
|
+
wand decide --stdin | --status Use the session-bound local decision service
|
|
271
|
+
wand decision:configure --python <path> --model <dir> | --disable
|
|
272
|
+
wand decision:skills [--providers pi,claude,...] Install managed user-level skills
|
|
237
273
|
wand knowledge:remember <text> | --stdin
|
|
238
274
|
wand knowledge:search [query]
|
|
239
275
|
wand knowledge:forget <entry-id>
|
package/dist/config.d.ts
CHANGED
|
@@ -9,6 +9,20 @@ import type { WandStorage } from "./storage.js";
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "defaultGeminiModel", "commitCli", "commitModel", "systemAiCli", "systemAiModel", "defaultThinkingEffort", "language", "cardDefaults", "inheritEnv"];
|
|
11
11
|
export type PreferenceKey = (typeof PREFERENCE_KEYS)[number];
|
|
12
|
+
/**
|
|
13
|
+
* 每个 provider 的默认模型偏好字段。新增 provider 时只改这一处:
|
|
14
|
+
* 偏好读写、环境归一、默认模型投影都从这张表派生。
|
|
15
|
+
*/
|
|
16
|
+
export declare const PROVIDER_MODEL_PREFERENCE_FIELDS: {
|
|
17
|
+
readonly claude: "defaultModel";
|
|
18
|
+
readonly codex: "defaultCodexModel";
|
|
19
|
+
readonly opencode: "defaultOpenCodeModel";
|
|
20
|
+
readonly grok: "defaultGrokModel";
|
|
21
|
+
readonly qoder: "defaultQoderModel";
|
|
22
|
+
readonly pi: "defaultPiModel";
|
|
23
|
+
readonly gemini: "defaultGeminiModel";
|
|
24
|
+
};
|
|
25
|
+
export type ProviderModelField = (typeof PROVIDER_MODEL_PREFERENCE_FIELDS)[SessionProvider];
|
|
12
26
|
export declare function isPreferenceKey(key: string): key is PreferenceKey;
|
|
13
27
|
/**
|
|
14
28
|
* The user's login shell from passwd, then $SHELL, then a platform default.
|
|
@@ -39,13 +53,7 @@ export declare function applyStoragePreferences(config: WandConfig, storage: Wan
|
|
|
39
53
|
/** Write a single preference value to DB and (in-place) update the live config object. */
|
|
40
54
|
export declare function writePreferenceToStorage(config: WandConfig, storage: WandStorage, key: PreferenceKey, value: unknown): void;
|
|
41
55
|
export declare function isExecutionMode(value: unknown): value is ExecutionMode;
|
|
42
|
-
export declare function getProviderDefaultModels(config: Pick<WandConfig,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
grok: string;
|
|
47
|
-
qoder: string;
|
|
48
|
-
pi: string;
|
|
49
|
-
gemini: string;
|
|
50
|
-
};
|
|
51
|
-
export declare function getDefaultModelForProvider(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultGeminiModel">, provider: SessionProvider | undefined): string;
|
|
56
|
+
export declare function getProviderDefaultModels(config: Pick<WandConfig, ProviderModelField>): Record<SessionProvider, string>;
|
|
57
|
+
export declare function getDefaultModelForProvider(config: Pick<WandConfig, ProviderModelField>, provider: SessionProvider | undefined): string;
|
|
58
|
+
/** 把按 provider 归档的默认模型投影回各自的 `default*Model` 字段(客户端兼容字段)。 */
|
|
59
|
+
export declare function providerModelFields(models: Record<SessionProvider, string>): Pick<WandConfig, ProviderModelField>;
|
package/dist/config.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { isRunningAsRoot } from "./env-utils.js";
|
|
8
|
-
import { isSessionProvider } from "./session-provider.js";
|
|
8
|
+
import { isSessionProvider, SESSION_PROVIDERS } from "./session-provider.js";
|
|
9
9
|
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
10
10
|
const DEFAULT_CONFIG_DIR = ".wand";
|
|
11
11
|
const DEFAULT_CONFIG_FILE = "config.json";
|
|
@@ -38,6 +38,24 @@ export const PREFERENCE_KEYS = [
|
|
|
38
38
|
"cardDefaults",
|
|
39
39
|
"inheritEnv",
|
|
40
40
|
];
|
|
41
|
+
/**
|
|
42
|
+
* 每个 provider 的默认模型偏好字段。新增 provider 时只改这一处:
|
|
43
|
+
* 偏好读写、环境归一、默认模型投影都从这张表派生。
|
|
44
|
+
*/
|
|
45
|
+
export const PROVIDER_MODEL_PREFERENCE_FIELDS = {
|
|
46
|
+
claude: "defaultModel",
|
|
47
|
+
codex: "defaultCodexModel",
|
|
48
|
+
opencode: "defaultOpenCodeModel",
|
|
49
|
+
grok: "defaultGrokModel",
|
|
50
|
+
qoder: "defaultQoderModel",
|
|
51
|
+
pi: "defaultPiModel",
|
|
52
|
+
gemini: "defaultGeminiModel",
|
|
53
|
+
};
|
|
54
|
+
const PROVIDER_MODEL_FIELDS = Object.values(PROVIDER_MODEL_PREFERENCE_FIELDS);
|
|
55
|
+
const PROVIDER_MODEL_FIELD_SET = new Set(PROVIDER_MODEL_FIELDS);
|
|
56
|
+
function isProviderModelField(key) {
|
|
57
|
+
return PROVIDER_MODEL_FIELD_SET.has(key);
|
|
58
|
+
}
|
|
41
59
|
const PREFERENCE_KEY_SET = new Set(PREFERENCE_KEYS);
|
|
42
60
|
function preferenceStorageKey(key) {
|
|
43
61
|
return `pref:${key}`;
|
|
@@ -92,6 +110,7 @@ export const defaultConfig = () => ({
|
|
|
92
110
|
ios: defaultIosIpaConfig(),
|
|
93
111
|
render: defaultRenderConfig(),
|
|
94
112
|
structured: { processHost: "legacy" },
|
|
113
|
+
localDecision: { enabled: false, pythonPath: "", modelPath: "" },
|
|
95
114
|
cardDefaults: defaultCardExpandDefaults(),
|
|
96
115
|
defaultModel: "",
|
|
97
116
|
defaultCodexModel: "",
|
|
@@ -332,40 +351,13 @@ export function applyStoragePreferences(config, storage) {
|
|
|
332
351
|
if (typeof v === "string" && v.trim())
|
|
333
352
|
config.defaultCwd = v;
|
|
334
353
|
}
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
if (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (storage.hasPreference(preferenceStorageKey("defaultCodexModel"))) {
|
|
341
|
-
const v = storage.getPreference(preferenceStorageKey("defaultCodexModel"), defaults.defaultCodexModel ?? "");
|
|
342
|
-
if (typeof v === "string")
|
|
343
|
-
config.defaultCodexModel = v.trim();
|
|
344
|
-
}
|
|
345
|
-
if (storage.hasPreference(preferenceStorageKey("defaultOpenCodeModel"))) {
|
|
346
|
-
const v = storage.getPreference(preferenceStorageKey("defaultOpenCodeModel"), defaults.defaultOpenCodeModel ?? "");
|
|
347
|
-
if (typeof v === "string")
|
|
348
|
-
config.defaultOpenCodeModel = v.trim();
|
|
349
|
-
}
|
|
350
|
-
if (storage.hasPreference(preferenceStorageKey("defaultGrokModel"))) {
|
|
351
|
-
const v = storage.getPreference(preferenceStorageKey("defaultGrokModel"), defaults.defaultGrokModel ?? "");
|
|
352
|
-
if (typeof v === "string")
|
|
353
|
-
config.defaultGrokModel = v.trim();
|
|
354
|
-
}
|
|
355
|
-
if (storage.hasPreference(preferenceStorageKey("defaultQoderModel"))) {
|
|
356
|
-
const v = storage.getPreference(preferenceStorageKey("defaultQoderModel"), defaults.defaultQoderModel ?? "");
|
|
357
|
-
if (typeof v === "string")
|
|
358
|
-
config.defaultQoderModel = v.trim();
|
|
359
|
-
}
|
|
360
|
-
if (storage.hasPreference(preferenceStorageKey("defaultPiModel"))) {
|
|
361
|
-
const v = storage.getPreference(preferenceStorageKey("defaultPiModel"), defaults.defaultPiModel ?? "");
|
|
362
|
-
if (typeof v === "string")
|
|
363
|
-
config.defaultPiModel = v.trim();
|
|
364
|
-
}
|
|
365
|
-
if (storage.hasPreference(preferenceStorageKey("defaultGeminiModel"))) {
|
|
366
|
-
const v = storage.getPreference(preferenceStorageKey("defaultGeminiModel"), defaults.defaultGeminiModel ?? "");
|
|
354
|
+
for (const field of PROVIDER_MODEL_FIELDS) {
|
|
355
|
+
const dbKey = preferenceStorageKey(field);
|
|
356
|
+
if (!storage.hasPreference(dbKey))
|
|
357
|
+
continue;
|
|
358
|
+
const v = storage.getPreference(dbKey, defaults[field] ?? "");
|
|
367
359
|
if (typeof v === "string")
|
|
368
|
-
config
|
|
360
|
+
config[field] = v.trim();
|
|
369
361
|
}
|
|
370
362
|
if (storage.hasPreference(preferenceStorageKey("commitCli"))) {
|
|
371
363
|
const v = storage.getPreference(preferenceStorageKey("commitCli"), defaults.commitCli ?? "claude");
|
|
@@ -410,6 +402,14 @@ export function applyStoragePreferences(config, storage) {
|
|
|
410
402
|
/** Write a single preference value to DB and (in-place) update the live config object. */
|
|
411
403
|
export function writePreferenceToStorage(config, storage, key, value) {
|
|
412
404
|
const dbKey = preferenceStorageKey(key);
|
|
405
|
+
// Default model fields share one shape (trimmed string) across every provider;
|
|
406
|
+
// handling them before the switch keeps the switch free of seven copies.
|
|
407
|
+
if (isProviderModelField(key)) {
|
|
408
|
+
const v = typeof value === "string" ? value.trim() : "";
|
|
409
|
+
storage.setPreference(dbKey, v);
|
|
410
|
+
config[key] = v;
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
413
|
switch (key) {
|
|
414
414
|
case "defaultProvider": {
|
|
415
415
|
if (!isSessionProvider(value))
|
|
@@ -445,48 +445,6 @@ export function writePreferenceToStorage(config, storage, key, value) {
|
|
|
445
445
|
config.defaultCwd = v || defaultConfig().defaultCwd;
|
|
446
446
|
break;
|
|
447
447
|
}
|
|
448
|
-
case "defaultModel": {
|
|
449
|
-
const v = typeof value === "string" ? value.trim() : "";
|
|
450
|
-
storage.setPreference(dbKey, v);
|
|
451
|
-
config.defaultModel = v;
|
|
452
|
-
break;
|
|
453
|
-
}
|
|
454
|
-
case "defaultCodexModel": {
|
|
455
|
-
const v = typeof value === "string" ? value.trim() : "";
|
|
456
|
-
storage.setPreference(dbKey, v);
|
|
457
|
-
config.defaultCodexModel = v;
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
case "defaultOpenCodeModel": {
|
|
461
|
-
const v = typeof value === "string" ? value.trim() : "";
|
|
462
|
-
storage.setPreference(dbKey, v);
|
|
463
|
-
config.defaultOpenCodeModel = v;
|
|
464
|
-
break;
|
|
465
|
-
}
|
|
466
|
-
case "defaultGrokModel": {
|
|
467
|
-
const v = typeof value === "string" ? value.trim() : "";
|
|
468
|
-
storage.setPreference(dbKey, v);
|
|
469
|
-
config.defaultGrokModel = v;
|
|
470
|
-
break;
|
|
471
|
-
}
|
|
472
|
-
case "defaultQoderModel": {
|
|
473
|
-
const v = typeof value === "string" ? value.trim() : "";
|
|
474
|
-
storage.setPreference(dbKey, v);
|
|
475
|
-
config.defaultQoderModel = v;
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
case "defaultPiModel": {
|
|
479
|
-
const v = typeof value === "string" ? value.trim() : "";
|
|
480
|
-
storage.setPreference(dbKey, v);
|
|
481
|
-
config.defaultPiModel = v;
|
|
482
|
-
break;
|
|
483
|
-
}
|
|
484
|
-
case "defaultGeminiModel": {
|
|
485
|
-
const v = typeof value === "string" ? value.trim() : "";
|
|
486
|
-
storage.setPreference(dbKey, v);
|
|
487
|
-
config.defaultGeminiModel = v;
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
490
448
|
case "commitCli": {
|
|
491
449
|
if (value !== "claude" && value !== "codex" && value !== "opencode")
|
|
492
450
|
throw new Error(`无效 commit CLI: ${value}`);
|
|
@@ -734,17 +692,16 @@ function mergeWithDefaults(input) {
|
|
|
734
692
|
ios: normalizeIosIpaConfig(input.ios) ?? defaults.ios,
|
|
735
693
|
render: normalizeRenderConfig(input.render) ?? defaults.render,
|
|
736
694
|
structured: { processHost: input.structured?.processHost === "rust" ? "rust" : "legacy" },
|
|
695
|
+
localDecision: {
|
|
696
|
+
enabled: input.localDecision?.enabled === true,
|
|
697
|
+
pythonPath: typeof input.localDecision?.pythonPath === "string" ? input.localDecision.pythonPath.trim() : "",
|
|
698
|
+
modelPath: typeof input.localDecision?.modelPath === "string" ? input.localDecision.modelPath.trim() : "",
|
|
699
|
+
},
|
|
737
700
|
cardDefaults: normalizeCardDefaults(input.cardDefaults),
|
|
738
701
|
defaultProvider: isSessionProvider(input.defaultProvider) ? input.defaultProvider : "claude",
|
|
739
702
|
defaultSessionKind: input.defaultSessionKind === "pty" ? "pty" : "structured",
|
|
740
703
|
defaultTaskWorktree: typeof input.defaultTaskWorktree === "boolean" ? input.defaultTaskWorktree : defaults.defaultTaskWorktree,
|
|
741
|
-
|
|
742
|
-
defaultCodexModel: typeof input.defaultCodexModel === "string" ? input.defaultCodexModel.trim() : defaults.defaultCodexModel,
|
|
743
|
-
defaultOpenCodeModel: typeof input.defaultOpenCodeModel === "string" ? input.defaultOpenCodeModel.trim() : defaults.defaultOpenCodeModel,
|
|
744
|
-
defaultGrokModel: typeof input.defaultGrokModel === "string" ? input.defaultGrokModel.trim() : defaults.defaultGrokModel,
|
|
745
|
-
defaultQoderModel: typeof input.defaultQoderModel === "string" ? input.defaultQoderModel.trim() : defaults.defaultQoderModel,
|
|
746
|
-
defaultPiModel: typeof input.defaultPiModel === "string" ? input.defaultPiModel.trim() : defaults.defaultPiModel,
|
|
747
|
-
defaultGeminiModel: typeof input.defaultGeminiModel === "string" ? input.defaultGeminiModel.trim() : defaults.defaultGeminiModel,
|
|
704
|
+
...providerModelOverrides(input, defaults),
|
|
748
705
|
commitCli: input.commitCli === "codex" || input.commitCli === "opencode" ? input.commitCli : "claude",
|
|
749
706
|
commitModel: typeof input.commitModel === "string" ? input.commitModel.trim() : defaults.commitModel,
|
|
750
707
|
systemAiCli: isSessionProvider(input.systemAiCli) ? input.systemAiCli : undefined,
|
|
@@ -764,32 +721,33 @@ function configuredModelId(value) {
|
|
|
764
721
|
const trimmed = (value ?? "").trim();
|
|
765
722
|
return trimmed === "default" ? "" : trimmed;
|
|
766
723
|
}
|
|
724
|
+
/** 归一每个 provider 的默认模型:字符串则 trim,否则回落到默认值。 */
|
|
725
|
+
function providerModelOverrides(input, defaults) {
|
|
726
|
+
const result = {};
|
|
727
|
+
for (const field of PROVIDER_MODEL_FIELDS) {
|
|
728
|
+
const value = input[field];
|
|
729
|
+
result[field] = typeof value === "string" ? value.trim() : defaults[field];
|
|
730
|
+
}
|
|
731
|
+
return result;
|
|
732
|
+
}
|
|
767
733
|
export function getProviderDefaultModels(config) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
qoder: configuredModelId(config.defaultQoderModel),
|
|
774
|
-
pi: configuredModelId(config.defaultPiModel),
|
|
775
|
-
gemini: configuredModelId(config.defaultGeminiModel),
|
|
776
|
-
};
|
|
734
|
+
const models = {};
|
|
735
|
+
for (const provider of SESSION_PROVIDERS) {
|
|
736
|
+
models[provider] = configuredModelId(config[PROVIDER_MODEL_PREFERENCE_FIELDS[provider]]);
|
|
737
|
+
}
|
|
738
|
+
return models;
|
|
777
739
|
}
|
|
778
740
|
export function getDefaultModelForProvider(config, provider) {
|
|
779
741
|
const defaults = getProviderDefaultModels(config);
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
return defaults.pi;
|
|
790
|
-
if (provider === "gemini")
|
|
791
|
-
return defaults.gemini;
|
|
792
|
-
return defaults.claude;
|
|
742
|
+
return provider ? defaults[provider] : defaults.claude;
|
|
743
|
+
}
|
|
744
|
+
/** 把按 provider 归档的默认模型投影回各自的 `default*Model` 字段(客户端兼容字段)。 */
|
|
745
|
+
export function providerModelFields(models) {
|
|
746
|
+
const result = {};
|
|
747
|
+
for (const provider of SESSION_PROVIDERS) {
|
|
748
|
+
result[PROVIDER_MODEL_PREFERENCE_FIELDS[provider]] = models[provider];
|
|
749
|
+
}
|
|
750
|
+
return result;
|
|
793
751
|
}
|
|
794
752
|
function normalizePresetCommand(command) {
|
|
795
753
|
const trimmed = command.trim();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type DecisionRequest } from "./decision-types.js";
|
|
2
|
+
/** No admin/password lookup, redirects, or implicit remote fallback. Credentials stay in headers. */
|
|
3
|
+
export declare function callDecisionService(request: DecisionRequest | null, env?: NodeJS.ProcessEnv): Promise<unknown>;
|
|
4
|
+
export declare function runDecisionCli(args: string[]): Promise<void>;
|