@co0ontty/wand 4.79.0 → 4.80.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 +18 -6
- package/dist/agent-dispatch.d.ts +3 -0
- package/dist/agent-dispatch.js +5 -0
- package/dist/ai-team-runner.js +4 -2
- package/dist/ai-team-types.d.ts +34 -0
- package/dist/ai-team-types.js +12 -1
- package/dist/build-info.json +3 -3
- package/dist/config.d.ts +4 -3
- package/dist/config.js +25 -0
- package/dist/git-quick-commit.d.ts +9 -8
- package/dist/git-quick-commit.js +123 -8
- package/dist/models.d.ts +3 -0
- package/dist/models.js +24 -2
- package/dist/process-manager.js +16 -3
- package/dist/provider-cli-updater.d.ts +1 -1
- package/dist/provider-cli-updater.js +12 -1
- package/dist/server-ai-team-routes.js +4 -1
- package/dist/server-employee-routes.d.ts +22 -0
- package/dist/server-employee-routes.js +277 -0
- package/dist/server-session-routes.js +38 -13
- package/dist/server-settings-routes.js +5 -0
- package/dist/server-task-routes.d.ts +2 -0
- package/dist/server-task-routes.js +174 -22
- package/dist/server-update-routes.js +1 -1
- package/dist/server-workspace-routes.js +3 -0
- package/dist/server.js +16 -3
- package/dist/session-ai-context.d.ts +7 -2
- package/dist/session-ai-context.js +36 -9
- package/dist/session-provider.d.ts +6 -1
- package/dist/session-provider.js +27 -1
- package/dist/session-transport.js +4 -0
- package/dist/silicon-employee-dispatch.d.ts +9 -0
- package/dist/silicon-employee-dispatch.js +13 -0
- package/dist/silicon-employee-draft.d.ts +36 -0
- package/dist/silicon-employee-draft.js +146 -0
- package/dist/storage.d.ts +19 -2
- package/dist/storage.js +158 -7
- package/dist/structured-gemini-adapter.d.ts +40 -0
- package/dist/structured-gemini-adapter.js +198 -0
- package/dist/structured-provider-common.d.ts +2 -1
- package/dist/structured-provider-common.js +7 -2
- package/dist/structured-session-manager.d.ts +10 -0
- package/dist/structured-session-manager.js +106 -14
- package/dist/system-ai.js +1 -1
- package/dist/system-employee.d.ts +35 -0
- package/dist/system-employee.js +81 -0
- package/dist/task-types.d.ts +8 -1
- package/dist/task-types.js +7 -0
- package/dist/types.d.ts +23 -4
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/scripts.js +64 -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 +2 -1
- package/dist/web-ui/provider-identity.js +10 -1
- package/dist/web-ui/session-activity.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { callConfiguredAiText } from "./git-quick-commit.js";
|
|
2
|
+
import { employeeCliAvailable } from "./silicon-employee-dispatch.js";
|
|
3
|
+
import { SESSION_PROVIDERS, isSessionProvider } from "./session-provider.js";
|
|
4
|
+
import { DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_AGENT_MODE, normalizeWandTaskAgentMode, } from "./task-types.js";
|
|
5
|
+
import { clipAtWordBoundary } from "./text-utils.js";
|
|
6
|
+
export class SiliconEmployeeDraftError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(message, code) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.name = "SiliconEmployeeDraftError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const NAME_MAX = 40;
|
|
15
|
+
const DUTY_MAX = 200;
|
|
16
|
+
const PROMPT_MAX = 20_000;
|
|
17
|
+
const EXPECTATION_MAX = 4_000;
|
|
18
|
+
/** 只用于提示词的 CLI 人话名;执行候选本身仍用 provider id。 */
|
|
19
|
+
const PROVIDER_LABELS = {
|
|
20
|
+
claude: "Claude Code",
|
|
21
|
+
codex: "OpenAI Codex CLI",
|
|
22
|
+
opencode: "OpenCode CLI",
|
|
23
|
+
grok: "Grok Build CLI",
|
|
24
|
+
qoder: "Qoder CLI",
|
|
25
|
+
pi: "Pi coding agent",
|
|
26
|
+
gemini: "Gemini CLI",
|
|
27
|
+
};
|
|
28
|
+
/** 硅基员工只能跑结构化会话;候选沿用看板的「跟随服务端默认」。 */
|
|
29
|
+
export function employeeAgentFor(provider) {
|
|
30
|
+
return {
|
|
31
|
+
provider,
|
|
32
|
+
model: "default",
|
|
33
|
+
thinkingEffort: "off",
|
|
34
|
+
mode: normalizeWandTaskAgentMode(provider, DEFAULT_WAND_TASK_AGENT_MODE),
|
|
35
|
+
kind: DEFAULT_WAND_TASK_AGENT_KIND,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 已安装的 CLI:默认 provider 优先,其余按全局顺序;
|
|
40
|
+
* 都没装时返回空数组,由调用方决定退回到哪一个首选。
|
|
41
|
+
*/
|
|
42
|
+
export function availableEmployeeProviders(preferred = "claude", isAvailable = employeeCliAvailable) {
|
|
43
|
+
const order = [preferred, ...SESSION_PROVIDERS];
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
const available = [];
|
|
46
|
+
for (const provider of order) {
|
|
47
|
+
if (seen.has(provider))
|
|
48
|
+
continue;
|
|
49
|
+
seen.add(provider);
|
|
50
|
+
if (isAvailable(employeeAgentFor(provider)))
|
|
51
|
+
available.push(provider);
|
|
52
|
+
}
|
|
53
|
+
return available;
|
|
54
|
+
}
|
|
55
|
+
function stripFences(raw) {
|
|
56
|
+
return raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
57
|
+
}
|
|
58
|
+
/** 从模型输出里取第一个 JSON 对象,容忍前后夹带解释文字。 */
|
|
59
|
+
export function extractDraftJson(raw) {
|
|
60
|
+
const text = stripFences(raw);
|
|
61
|
+
const start = text.indexOf("{");
|
|
62
|
+
const end = text.lastIndexOf("}");
|
|
63
|
+
if (start < 0 || end <= start) {
|
|
64
|
+
throw new SiliconEmployeeDraftError("AI 没有返回可用的员工配置,请换个说法再试。", "DRAFT_INVALID_JSON");
|
|
65
|
+
}
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(text.slice(start, end + 1));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new SiliconEmployeeDraftError("AI 返回的员工配置无法解析,请重试。", "DRAFT_INVALID_JSON");
|
|
72
|
+
}
|
|
73
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
74
|
+
throw new SiliconEmployeeDraftError("AI 返回的员工配置无法解析,请重试。", "DRAFT_INVALID_JSON");
|
|
75
|
+
}
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
function oneLine(record, key) {
|
|
79
|
+
const value = record[key];
|
|
80
|
+
return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 校验并收敛模型输出。名字 / 角色设定缺失即视为不可用;
|
|
84
|
+
* provider 不在已安装清单里时按候选顺序兜底,不让员工带着跑不起来的 CLI 落库。
|
|
85
|
+
*/
|
|
86
|
+
export function parseSiliconEmployeeDraft(raw, options = {}) {
|
|
87
|
+
const record = extractDraftJson(raw);
|
|
88
|
+
const name = clipAtWordBoundary(oneLine(record, "name"), NAME_MAX);
|
|
89
|
+
if (!name)
|
|
90
|
+
throw new SiliconEmployeeDraftError("AI 没有给出员工名字,请重试。", "DRAFT_MISSING_NAME");
|
|
91
|
+
const duty = clipAtWordBoundary(oneLine(record, "duty"), DUTY_MAX);
|
|
92
|
+
const prompt = typeof record.prompt === "string" ? record.prompt.trim().slice(0, PROMPT_MAX) : "";
|
|
93
|
+
if (!prompt)
|
|
94
|
+
throw new SiliconEmployeeDraftError("AI 没有给出员工角色设定,请重试。", "DRAFT_MISSING_PROMPT");
|
|
95
|
+
const allowed = options.allowedProviders?.length ? options.allowedProviders : [...SESSION_PROVIDERS];
|
|
96
|
+
const requested = oneLine(record, "provider").toLowerCase();
|
|
97
|
+
const provider = isSessionProvider(requested) && allowed.includes(requested)
|
|
98
|
+
? requested
|
|
99
|
+
: options.fallbackProvider && allowed.includes(options.fallbackProvider)
|
|
100
|
+
? options.fallbackProvider
|
|
101
|
+
: allowed[0];
|
|
102
|
+
return { name, duty, prompt, agent: employeeAgentFor(provider) };
|
|
103
|
+
}
|
|
104
|
+
/** 规则走系统提示,用户的期望走用户消息;一次性调用沿用系统 AI 通道。 */
|
|
105
|
+
export function buildEmployeeDraftPrompt(expectation, providers, existingNames = []) {
|
|
106
|
+
const list = providers.length ? providers : [...SESSION_PROVIDERS];
|
|
107
|
+
const lines = [
|
|
108
|
+
"你正在帮用户创建一位「硅基员工」:一个长期负责某类工作的 AI 角色,之后会以结构化会话执行任务。",
|
|
109
|
+
"根据用户对这位员工的期望,设计一份配置,只输出一个 JSON 对象(不要 Markdown 代码块,不要任何解释):",
|
|
110
|
+
'{"name":"员工名字","duty":"一句话职责","prompt":"角色设定","provider":"执行工具"}',
|
|
111
|
+
"",
|
|
112
|
+
"字段要求:",
|
|
113
|
+
"- name:2-8 个中文,具体、好记、体现职能(如「前端小匠」「接口守夜人」);不要「助手」「AI」这类空泛词,不要带标点。",
|
|
114
|
+
"- duty:一句话职责,15-40 个字,说清它负责什么、交付什么,会显示在侧栏和消息署名里。",
|
|
115
|
+
"- prompt:角色设定(系统提示),用第二人称「你」写给这位员工,分点写清专业能力、工作要求、交付标准与协作习惯,300-600 字;不要复述用户原话,不要写具体项目或文件名。",
|
|
116
|
+
`- provider:执行工具,只能取 ${list.map((provider) => `"${provider}"(${PROVIDER_LABELS[provider]})`).join("、")} 之一,挑最适合这项工作的。`,
|
|
117
|
+
];
|
|
118
|
+
if (existingNames.length) {
|
|
119
|
+
lines.push(`- 不要与已有员工重名:${existingNames.slice(0, 20).join("、")}。`);
|
|
120
|
+
}
|
|
121
|
+
return { system: lines.join("\n"), prompt: `用户对这位员工的期望:\n${expectation}` };
|
|
122
|
+
}
|
|
123
|
+
/** 按自然语言期望生成员工配置;调用方负责随后落库。 */
|
|
124
|
+
export async function generateSiliconEmployeeDraft(expectation, ai = {}, options = {}) {
|
|
125
|
+
const trimmed = expectation.trim();
|
|
126
|
+
if (!trimmed) {
|
|
127
|
+
throw new SiliconEmployeeDraftError("先说说你对这位员工的期望吧。", "EMPTY_EXPECTATION");
|
|
128
|
+
}
|
|
129
|
+
if (trimmed.length > EXPECTATION_MAX) {
|
|
130
|
+
throw new SiliconEmployeeDraftError(`期望描述太长了(${trimmed.length} 字),请精简到 ${EXPECTATION_MAX} 字以内。`, "EXPECTATION_TOO_LONG");
|
|
131
|
+
}
|
|
132
|
+
const available = availableEmployeeProviders(options.preferredProvider, options.isProviderAvailable);
|
|
133
|
+
const providers = available.length ? available : [options.preferredProvider ?? "claude", ...SESSION_PROVIDERS];
|
|
134
|
+
const request = buildEmployeeDraftPrompt(trimmed, providers, options.existingNames);
|
|
135
|
+
let raw;
|
|
136
|
+
try {
|
|
137
|
+
raw = await callConfiguredAiText(request, options.cwd || process.cwd(), options.language ?? "", ai);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error instanceof SiliconEmployeeDraftError)
|
|
141
|
+
throw error;
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
throw new SiliconEmployeeDraftError(message || "AI 生成员工配置失败。", "DRAFT_AI_FAILED");
|
|
144
|
+
}
|
|
145
|
+
return parseSiliconEmployeeDraft(raw, { allowedProviders: providers, fallbackProvider: providers[0] });
|
|
146
|
+
}
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SessionSnapshot, ConversationTurn, SessionProvider, StructuredSessionState, Workspace, LayoutNode, TaskWindowLayout, WorkspaceDefaultProvider, WorkspaceKind, WorkspaceTask, WorkspaceTaskWorktree, WorkspaceTaskStatus } from "./types.js";
|
|
2
|
-
import type { AiTeam, AiTeamRun, AiTeamRunChatMarker, AiTeamRunStatus, AiTeamStep, AiTeamStepSessionMarker, CandidateFailureKind, StepDispatchInfo } from "./ai-team-types.js";
|
|
2
|
+
import type { AiTeam, AiTeamRun, AiTeamRunChatMarker, AiTeamRunStatus, AiTeamStep, AiTeamStepSessionMarker, CandidateFailureKind, SiliconEmployee, StepDispatchInfo } from "./ai-team-types.js";
|
|
3
3
|
import type { WandTask, WandTaskAgent, WandTaskAgentKind, WandTaskTitleSource } from "./task-types.js";
|
|
4
|
+
import { type SystemEmployeeSeed } from "./system-employee.js";
|
|
4
5
|
import type { AgentActivityItem, Mission, MissionAttempt, MissionReviewComment, MissionReviewStatus, MissionStatus } from "./mission-types.js";
|
|
5
6
|
import { type PasswordVault, type PasswordVaultItem, type PasswordVaultItemFilter, type PasswordVaultItemInput } from "./password-manager.js";
|
|
6
7
|
/** `ai_team_runs.run_state_json`:本次运行的候选黑名单(§3.4 修正 B12 两层键值 + host-disabled)。 */
|
|
@@ -55,8 +56,9 @@ interface CreateWandTaskInput {
|
|
|
55
56
|
dueDate?: string | null;
|
|
56
57
|
milestoneId?: string | null;
|
|
57
58
|
agent?: WandTaskAgent | null;
|
|
59
|
+
executionSubject?: WandTask["executionSubject"];
|
|
58
60
|
}
|
|
59
|
-
type WandTaskPatch = Partial<Pick<WandTask, "workspaceId" | "workspaceTaskId" | "parentTaskId" | "title" | "titleSource" | "autoTitleSignature" | "description" | "status" | "priority" | "labels" | "dueDate" | "milestoneId" | "sortOrder" | "agent">>;
|
|
61
|
+
type WandTaskPatch = Partial<Pick<WandTask, "workspaceId" | "workspaceTaskId" | "parentTaskId" | "title" | "titleSource" | "autoTitleSignature" | "description" | "status" | "priority" | "labels" | "dueDate" | "milestoneId" | "sortOrder" | "agent" | "executionSubject">>;
|
|
60
62
|
export declare class WandStorage {
|
|
61
63
|
private readonly db;
|
|
62
64
|
private readonly dbPath;
|
|
@@ -310,6 +312,21 @@ export declare class WandStorage {
|
|
|
310
312
|
deleteAuthSession(token: string): void;
|
|
311
313
|
deleteAllAuthSessions(): void;
|
|
312
314
|
deleteExpiredAuthSessions(now: number): void;
|
|
315
|
+
listSiliconEmployees(options?: {
|
|
316
|
+
includeArchived?: boolean;
|
|
317
|
+
}): SiliconEmployee[];
|
|
318
|
+
getSiliconEmployee(id: string): SiliconEmployee | null;
|
|
319
|
+
/** 内置「系统运维」员工;未创建(或被删除过)时返回 null。 */
|
|
320
|
+
getSystemSiliconEmployee(key?: string): SiliconEmployee | null;
|
|
321
|
+
/**
|
|
322
|
+
* 幂等地保证内置员工存在。已存在时只补齐锁定字段(防止旧的同名定义漂移),
|
|
323
|
+
* 候选保留用户当下的设置;不存在时按 seed 建首条候选。
|
|
324
|
+
*/
|
|
325
|
+
ensureSystemSiliconEmployee(seed?: SystemEmployeeSeed): SiliconEmployee;
|
|
326
|
+
saveSiliconEmployee(employee: SiliconEmployee): void;
|
|
327
|
+
archiveSiliconEmployee(id: string, archivedAt?: string): void;
|
|
328
|
+
unarchiveSiliconEmployee(id: string): void;
|
|
329
|
+
deleteSiliconEmployee(id: string): void;
|
|
313
330
|
listAiTeams(): AiTeam[];
|
|
314
331
|
getAiTeam(id: string): AiTeam | null;
|
|
315
332
|
saveAiTeam(team: AiTeam): void;
|
package/dist/storage.js
CHANGED
|
@@ -9,6 +9,8 @@ import { DEFAULT_ITERATION_NAME, DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK
|
|
|
9
9
|
import { firstLayoutTabId } from "./layout-tree.js";
|
|
10
10
|
import { isUnnamedWorkspaceTaskName } from "./wand-task-sync.js";
|
|
11
11
|
import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, isTeamMemberRole, memberAgents } from "./ai-team-types.js";
|
|
12
|
+
import { isTaskExecutionSubject } from "./task-types.js";
|
|
13
|
+
import { SYSTEM_EMPLOYEE_KEY, systemEmployeeDefinition, systemEmployeeSeedAgents, } from "./system-employee.js";
|
|
12
14
|
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
13
15
|
import { DEFAULT_PASSWORD_VAULT_ID, DEFAULT_PASSWORD_VAULT_NAME, decryptVaultSecret, encryptVaultSecret, itemMatchesFilter, normalizePasswordItemInput, normalizeVaultName, nowIso, } from "./password-manager.js";
|
|
14
16
|
function safeJsonParse(raw) {
|
|
@@ -40,7 +42,7 @@ export function parseWandTaskAgent(raw) {
|
|
|
40
42
|
return null;
|
|
41
43
|
if (typeof model !== "string" || !model.trim() || model.trim().length > 128)
|
|
42
44
|
return null;
|
|
43
|
-
if (thinkingEffort
|
|
45
|
+
if (!isThinkingEffort(thinkingEffort))
|
|
44
46
|
return null;
|
|
45
47
|
// mode 是后加列:历史行没有该字段时按标准模式读取,不因此整条配置退化成 null。
|
|
46
48
|
const mode = normalizeWandTaskAgentMode(provider, value.mode);
|
|
@@ -177,6 +179,11 @@ function serializeSessionOptions(snapshot) {
|
|
|
177
179
|
currentTaskTitle: snapshot.currentTaskTitle,
|
|
178
180
|
summary: snapshot.summary,
|
|
179
181
|
systemPrompt: snapshot.systemPrompt,
|
|
182
|
+
employeeId: snapshot.employeeId,
|
|
183
|
+
employeeName: snapshot.employeeName,
|
|
184
|
+
employeeAvatar: snapshot.employeeAvatar,
|
|
185
|
+
employeeCandidates: snapshot.employeeCandidates,
|
|
186
|
+
employeeCandidateIndex: snapshot.employeeCandidateIndex,
|
|
180
187
|
};
|
|
181
188
|
return JSON.stringify(options);
|
|
182
189
|
}
|
|
@@ -240,6 +247,20 @@ function parseSessionOptions(raw) {
|
|
|
240
247
|
options.summary = parsed.summary;
|
|
241
248
|
if (typeof parsed.systemPrompt === "string")
|
|
242
249
|
options.systemPrompt = parsed.systemPrompt;
|
|
250
|
+
if (typeof parsed.employeeId === "string")
|
|
251
|
+
options.employeeId = parsed.employeeId;
|
|
252
|
+
if (typeof parsed.employeeName === "string")
|
|
253
|
+
options.employeeName = parsed.employeeName;
|
|
254
|
+
if (typeof parsed.employeeAvatar === "string")
|
|
255
|
+
options.employeeAvatar = parsed.employeeAvatar;
|
|
256
|
+
if (Array.isArray(parsed.employeeCandidates)) {
|
|
257
|
+
options.employeeCandidates = parsed.employeeCandidates
|
|
258
|
+
.map(parseWandTaskAgent)
|
|
259
|
+
.filter((agent) => agent?.kind === "structured");
|
|
260
|
+
}
|
|
261
|
+
if (Number.isSafeInteger(parsed.employeeCandidateIndex) && parsed.employeeCandidateIndex >= 0) {
|
|
262
|
+
options.employeeCandidateIndex = parsed.employeeCandidateIndex;
|
|
263
|
+
}
|
|
243
264
|
return options;
|
|
244
265
|
}
|
|
245
266
|
function parseQueuedMessages(raw) {
|
|
@@ -763,6 +784,7 @@ const INIT_SQL = `
|
|
|
763
784
|
milestone_id TEXT,
|
|
764
785
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
765
786
|
agent_json TEXT,
|
|
787
|
+
execution_subject_json TEXT,
|
|
766
788
|
workspace_task_id TEXT,
|
|
767
789
|
parent_task_id TEXT,
|
|
768
790
|
title_source TEXT,
|
|
@@ -794,6 +816,21 @@ const INIT_SQL = `
|
|
|
794
816
|
updated_at TEXT NOT NULL
|
|
795
817
|
);
|
|
796
818
|
|
|
819
|
+
CREATE TABLE IF NOT EXISTS silicon_employees (
|
|
820
|
+
id TEXT PRIMARY KEY,
|
|
821
|
+
name TEXT NOT NULL,
|
|
822
|
+
duty TEXT NOT NULL DEFAULT '',
|
|
823
|
+
prompt TEXT NOT NULL DEFAULT '',
|
|
824
|
+
avatar TEXT NOT NULL DEFAULT '',
|
|
825
|
+
agents_json TEXT NOT NULL,
|
|
826
|
+
system_key TEXT,
|
|
827
|
+
archived_at TEXT,
|
|
828
|
+
created_at TEXT NOT NULL,
|
|
829
|
+
updated_at TEXT NOT NULL
|
|
830
|
+
);
|
|
831
|
+
CREATE INDEX IF NOT EXISTS idx_silicon_employees_archived ON silicon_employees(archived_at);
|
|
832
|
+
|
|
833
|
+
|
|
797
834
|
CREATE TABLE IF NOT EXISTS ai_team_runs (
|
|
798
835
|
id TEXT PRIMARY KEY,
|
|
799
836
|
team_id TEXT NOT NULL,
|
|
@@ -850,6 +887,27 @@ function ensureAiTeamSchema(db) {
|
|
|
850
887
|
db.exec("ALTER TABLE ai_team_steps ADD COLUMN dispatch_info_json TEXT NOT NULL DEFAULT '{}'");
|
|
851
888
|
if (!runColumns.has("run_state_json"))
|
|
852
889
|
db.exec("ALTER TABLE ai_team_runs ADD COLUMN run_state_json TEXT NOT NULL DEFAULT '{}'");
|
|
890
|
+
// 硅基员工表结构保护(遵循只加不删)
|
|
891
|
+
db.exec(`
|
|
892
|
+
CREATE TABLE IF NOT EXISTS silicon_employees (
|
|
893
|
+
id TEXT PRIMARY KEY,
|
|
894
|
+
name TEXT NOT NULL,
|
|
895
|
+
duty TEXT NOT NULL DEFAULT '',
|
|
896
|
+
prompt TEXT NOT NULL DEFAULT '',
|
|
897
|
+
avatar TEXT NOT NULL DEFAULT '',
|
|
898
|
+
agents_json TEXT NOT NULL,
|
|
899
|
+
system_key TEXT,
|
|
900
|
+
archived_at TEXT,
|
|
901
|
+
created_at TEXT NOT NULL,
|
|
902
|
+
updated_at TEXT NOT NULL
|
|
903
|
+
);
|
|
904
|
+
CREATE INDEX IF NOT EXISTS idx_silicon_employees_archived ON silicon_employees(archived_at);
|
|
905
|
+
`);
|
|
906
|
+
// 内置员工标记(如 "wand-ops"):只加列,用户员工保持 NULL。
|
|
907
|
+
const employeeColumns = new Set(db.prepare("PRAGMA table_info(silicon_employees)").all().map((column) => column.name));
|
|
908
|
+
if (employeeColumns.size > 0 && !employeeColumns.has("system_key")) {
|
|
909
|
+
db.exec("ALTER TABLE silicon_employees ADD COLUMN system_key TEXT");
|
|
910
|
+
}
|
|
853
911
|
}
|
|
854
912
|
function ensureWandTaskSchema(db) {
|
|
855
913
|
const columns = db.prepare("PRAGMA table_info(wand_tasks)").all();
|
|
@@ -861,6 +919,8 @@ function ensureWandTaskSchema(db) {
|
|
|
861
919
|
// 任务的默认派发配置(CLI 工具 / 模型 / 思考深度)。只加列,历史行保持 NULL。
|
|
862
920
|
if (columns.length > 0 && !names.has("agent_json"))
|
|
863
921
|
db.exec("ALTER TABLE wand_tasks ADD COLUMN agent_json TEXT");
|
|
922
|
+
if (columns.length > 0 && !names.has("execution_subject_json"))
|
|
923
|
+
db.exec("ALTER TABLE wand_tasks ADD COLUMN execution_subject_json TEXT");
|
|
864
924
|
if (columns.length > 0 && !names.has("workspace_task_id"))
|
|
865
925
|
db.exec("ALTER TABLE wand_tasks ADD COLUMN workspace_task_id TEXT");
|
|
866
926
|
// 父任务归属只加列;历史任务仍然是顶层任务。
|
|
@@ -999,7 +1059,7 @@ export function ensureDatabaseFile(dbPath) {
|
|
|
999
1059
|
}
|
|
1000
1060
|
const WAND_TASK_FIELDS = `id, identifier, workspace_id, workspace_task_id, parent_task_id,
|
|
1001
1061
|
title, title_source, auto_title_signature, description, status, priority, labels_json,
|
|
1002
|
-
due_date, milestone_id, sort_order, agent_json, created_at, updated_at`;
|
|
1062
|
+
due_date, milestone_id, sort_order, agent_json, execution_subject_json, created_at, updated_at`;
|
|
1003
1063
|
// Shared metadata comes from the latest explicitly linked card, including meaningful NULLs.
|
|
1004
1064
|
// The old container columns remain readable for legacy rows until the startup migration.
|
|
1005
1065
|
const WORKSPACE_TASK_PROJECTION = `SELECT wt.id,
|
|
@@ -1530,6 +1590,10 @@ export class WandStorage {
|
|
|
1530
1590
|
milestoneId: typeof row.milestone_id === "string" && row.milestone_id ? row.milestone_id : null,
|
|
1531
1591
|
sortOrder: Number(row.sort_order) || 0,
|
|
1532
1592
|
agent: parseWandTaskAgent(row.agent_json),
|
|
1593
|
+
executionSubject: (() => {
|
|
1594
|
+
const parsed = safeJsonParse(typeof row.execution_subject_json === "string" ? row.execution_subject_json : null);
|
|
1595
|
+
return isTaskExecutionSubject(parsed) ? parsed : null;
|
|
1596
|
+
})(),
|
|
1533
1597
|
createdAt: String(row.created_at),
|
|
1534
1598
|
updatedAt: String(row.updated_at),
|
|
1535
1599
|
};
|
|
@@ -1553,8 +1617,8 @@ export class WandStorage {
|
|
|
1553
1617
|
const number = this.db.prepare(`SELECT COALESCE(MAX(CAST(substr(identifier, 6) AS INTEGER)), 0) AS value
|
|
1554
1618
|
FROM wand_tasks WHERE identifier GLOB 'TASK-[0-9]*'`).get();
|
|
1555
1619
|
this.db.prepare(`INSERT INTO wand_tasks (${WAND_TASK_FIELDS})
|
|
1556
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1557
|
-
.run(id, `TASK-${number.value + 1}`, input.workspaceId ?? null, input.workspaceTaskId ?? null, input.parentTaskId ?? null, input.title, input.titleSource ?? "user", null, input.description ?? "", status, input.priority ?? DEFAULT_WAND_TASK_PRIORITY, JSON.stringify(input.labels ?? []), input.dueDate ?? null, input.milestoneId ?? null, max.value + 1, input.agent ? JSON.stringify(input.agent) : null, createdAt, createdAt);
|
|
1620
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1621
|
+
.run(id, `TASK-${number.value + 1}`, input.workspaceId ?? null, input.workspaceTaskId ?? null, input.parentTaskId ?? null, input.title, input.titleSource ?? "user", null, input.description ?? "", status, input.priority ?? DEFAULT_WAND_TASK_PRIORITY, JSON.stringify(input.labels ?? []), input.dueDate ?? null, input.milestoneId ?? null, max.value + 1, input.agent ? JSON.stringify(input.agent) : null, input.executionSubject ? JSON.stringify(input.executionSubject) : null, createdAt, createdAt);
|
|
1558
1622
|
return this.getWandTask(id);
|
|
1559
1623
|
}
|
|
1560
1624
|
createWandTask(input) {
|
|
@@ -1601,9 +1665,9 @@ export class WandStorage {
|
|
|
1601
1665
|
}
|
|
1602
1666
|
this.db.prepare(`UPDATE wand_tasks SET workspace_id = ?, workspace_task_id = ?, parent_task_id = ?,
|
|
1603
1667
|
title = ?, title_source = ?, auto_title_signature = ?, description = ?, status = ?, priority = ?,
|
|
1604
|
-
labels_json = ?, due_date = ?, milestone_id = ?, sort_order = ?, agent_json = ?, updated_at = ?
|
|
1668
|
+
labels_json = ?, due_date = ?, milestone_id = ?, sort_order = ?, agent_json = ?, execution_subject_json = ?, updated_at = ?
|
|
1605
1669
|
WHERE id = ?`)
|
|
1606
|
-
.run(next.workspaceId, next.workspaceTaskId, next.parentTaskId, next.title, next.titleSource, next.autoTitleSignature ?? null, next.description, next.status, next.priority, JSON.stringify(next.labels), next.dueDate, next.milestoneId ?? null, next.sortOrder, next.agent ? JSON.stringify(next.agent) : null, next.updatedAt, next.id);
|
|
1670
|
+
.run(next.workspaceId, next.workspaceTaskId, next.parentTaskId, next.title, next.titleSource, next.autoTitleSignature ?? null, next.description, next.status, next.priority, JSON.stringify(next.labels), next.dueDate, next.milestoneId ?? null, next.sortOrder, next.agent ? JSON.stringify(next.agent) : null, next.executionSubject ? JSON.stringify(next.executionSubject) : null, next.updatedAt, next.id);
|
|
1607
1671
|
if (patch.workspaceId !== undefined && next.workspaceTaskId) {
|
|
1608
1672
|
const workspaceId = next.workspaceId ?? this.ensureGlobalWorkspace().id;
|
|
1609
1673
|
// The container FK/index and standalone session workspace bucket follow the canonical project.
|
|
@@ -2252,6 +2316,71 @@ export class WandStorage {
|
|
|
2252
2316
|
deleteExpiredAuthSessions(now) {
|
|
2253
2317
|
this.db.prepare("DELETE FROM auth_sessions WHERE expires_at < ?").run(now);
|
|
2254
2318
|
}
|
|
2319
|
+
// ============ Silicon Employees ============
|
|
2320
|
+
listSiliconEmployees(options) {
|
|
2321
|
+
const includeArchived = options?.includeArchived ?? false;
|
|
2322
|
+
// 内置员工(system_key 非空)始终排在最前,其余按创建时间。
|
|
2323
|
+
const sql = includeArchived
|
|
2324
|
+
? "SELECT * FROM silicon_employees ORDER BY (system_key IS NULL), created_at ASC"
|
|
2325
|
+
: "SELECT * FROM silicon_employees WHERE archived_at IS NULL ORDER BY (system_key IS NULL), created_at ASC";
|
|
2326
|
+
const rows = this.db.prepare(sql).all();
|
|
2327
|
+
return rows.map(mapSiliconEmployeeRow);
|
|
2328
|
+
}
|
|
2329
|
+
getSiliconEmployee(id) {
|
|
2330
|
+
const row = this.db.prepare("SELECT * FROM silicon_employees WHERE id = ?").get(id);
|
|
2331
|
+
return row ? mapSiliconEmployeeRow(row) : null;
|
|
2332
|
+
}
|
|
2333
|
+
/** 内置「系统运维」员工;未创建(或被删除过)时返回 null。 */
|
|
2334
|
+
getSystemSiliconEmployee(key = SYSTEM_EMPLOYEE_KEY) {
|
|
2335
|
+
const row = this.db.prepare("SELECT * FROM silicon_employees WHERE system_key = ?").get(key);
|
|
2336
|
+
return row ? mapSiliconEmployeeRow(row) : null;
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* 幂等地保证内置员工存在。已存在时只补齐锁定字段(防止旧的同名定义漂移),
|
|
2340
|
+
* 候选保留用户当下的设置;不存在时按 seed 建首条候选。
|
|
2341
|
+
*/
|
|
2342
|
+
ensureSystemSiliconEmployee(seed = {}) {
|
|
2343
|
+
const existing = this.getSystemSiliconEmployee();
|
|
2344
|
+
if (!existing) {
|
|
2345
|
+
const employee = systemEmployeeDefinition(systemEmployeeSeedAgents(seed), new Date().toISOString());
|
|
2346
|
+
this.saveSiliconEmployee(employee);
|
|
2347
|
+
return employee;
|
|
2348
|
+
}
|
|
2349
|
+
const locked = systemEmployeeDefinition(existing.agents, new Date().toISOString(), existing);
|
|
2350
|
+
if (existing.name !== locked.name
|
|
2351
|
+
|| existing.duty !== locked.duty
|
|
2352
|
+
|| existing.prompt !== locked.prompt
|
|
2353
|
+
|| existing.avatar !== locked.avatar
|
|
2354
|
+
|| existing.archivedAt !== undefined
|
|
2355
|
+
|| existing.agents.length === 0) {
|
|
2356
|
+
this.saveSiliconEmployee(locked);
|
|
2357
|
+
return locked;
|
|
2358
|
+
}
|
|
2359
|
+
return existing;
|
|
2360
|
+
}
|
|
2361
|
+
saveSiliconEmployee(employee) {
|
|
2362
|
+
this.db.prepare(`INSERT INTO silicon_employees (
|
|
2363
|
+
id, name, duty, prompt, avatar, agents_json, system_key, archived_at, created_at, updated_at
|
|
2364
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2365
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
2366
|
+
name = excluded.name,
|
|
2367
|
+
duty = excluded.duty,
|
|
2368
|
+
prompt = excluded.prompt,
|
|
2369
|
+
avatar = excluded.avatar,
|
|
2370
|
+
agents_json = excluded.agents_json,
|
|
2371
|
+
system_key = excluded.system_key,
|
|
2372
|
+
archived_at = excluded.archived_at,
|
|
2373
|
+
updated_at = excluded.updated_at`).run(employee.id, employee.name, employee.duty, employee.prompt, employee.avatar, JSON.stringify(employee.agents), employee.systemKey ?? null, employee.archivedAt ?? null, employee.createdAt, employee.updatedAt);
|
|
2374
|
+
}
|
|
2375
|
+
archiveSiliconEmployee(id, archivedAt = new Date().toISOString()) {
|
|
2376
|
+
this.db.prepare("UPDATE silicon_employees SET archived_at = ?, updated_at = ? WHERE id = ?").run(archivedAt, new Date().toISOString(), id);
|
|
2377
|
+
}
|
|
2378
|
+
unarchiveSiliconEmployee(id) {
|
|
2379
|
+
this.db.prepare("UPDATE silicon_employees SET archived_at = NULL, updated_at = ? WHERE id = ?").run(new Date().toISOString(), id);
|
|
2380
|
+
}
|
|
2381
|
+
deleteSiliconEmployee(id) {
|
|
2382
|
+
this.db.prepare("DELETE FROM silicon_employees WHERE id = ?").run(id);
|
|
2383
|
+
}
|
|
2255
2384
|
// ============ AI Teams ============
|
|
2256
2385
|
listAiTeams() {
|
|
2257
2386
|
const rows = this.db.prepare("SELECT * FROM ai_teams ORDER BY created_at ASC").all();
|
|
@@ -2301,7 +2430,7 @@ export class WandStorage {
|
|
|
2301
2430
|
* 一次查询建整张表,供 /api/tasks 每请求一次构建;不逐会话查询。
|
|
2302
2431
|
*/
|
|
2303
2432
|
listAiTeamRunChatMarkers() {
|
|
2304
|
-
const rows = this.db.prepare(`SELECT r.id, r.team_json, r.chat_session_id, t.name AS current_team_name
|
|
2433
|
+
const rows = this.db.prepare(`SELECT r.id, r.team_id, r.team_json, r.chat_session_id, t.name AS current_team_name
|
|
2305
2434
|
FROM ai_team_runs r LEFT JOIN ai_teams t ON t.id = r.team_id
|
|
2306
2435
|
WHERE r.chat_session_id IS NOT NULL AND r.chat_session_id <> '' ORDER BY r.created_at DESC`).all();
|
|
2307
2436
|
const markers = new Map();
|
|
@@ -2312,6 +2441,7 @@ export class WandStorage {
|
|
|
2312
2441
|
const rawTeam = safeJsonParse(typeof row.team_json === "string" ? row.team_json : null);
|
|
2313
2442
|
markers.set(chatSessionId, {
|
|
2314
2443
|
runId: String(row.id),
|
|
2444
|
+
teamId: String(row.team_id),
|
|
2315
2445
|
teamName: typeof row.current_team_name === "string" ? row.current_team_name : String(rawTeam?.name ?? ""),
|
|
2316
2446
|
memberCount: Array.isArray(rawTeam?.members) ? rawTeam.members.length : 0,
|
|
2317
2447
|
});
|
|
@@ -2624,6 +2754,27 @@ export class WandStorage {
|
|
|
2624
2754
|
this.db.prepare("DELETE FROM command_sessions WHERE id = ?").run(id);
|
|
2625
2755
|
}
|
|
2626
2756
|
}
|
|
2757
|
+
function mapSiliconEmployeeRow(row) {
|
|
2758
|
+
const rawAgents = safeJsonParse(typeof row.agents_json === "string" ? row.agents_json : null);
|
|
2759
|
+
const agents = Array.isArray(rawAgents)
|
|
2760
|
+
? rawAgents
|
|
2761
|
+
.map((candidate) => parseWandTaskAgent(candidate))
|
|
2762
|
+
.filter((candidate) => candidate !== null)
|
|
2763
|
+
: [];
|
|
2764
|
+
const systemKey = typeof row.system_key === "string" && row.system_key ? row.system_key : undefined;
|
|
2765
|
+
return {
|
|
2766
|
+
id: String(row.id),
|
|
2767
|
+
name: String(row.name),
|
|
2768
|
+
duty: String(row.duty ?? ""),
|
|
2769
|
+
prompt: String(row.prompt ?? ""),
|
|
2770
|
+
avatar: String(row.avatar ?? ""),
|
|
2771
|
+
agents,
|
|
2772
|
+
systemKey,
|
|
2773
|
+
archivedAt: typeof row.archived_at === "string" && row.archived_at ? row.archived_at : undefined,
|
|
2774
|
+
createdAt: String(row.created_at),
|
|
2775
|
+
updatedAt: String(row.updated_at),
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2627
2778
|
function mapAiTeamRow(row) {
|
|
2628
2779
|
return {
|
|
2629
2780
|
id: String(row.id),
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import type { StructuredExecHost } from "./structured-exec-host.js";
|
|
3
|
+
import type { StructuredRunnerAdapter, StructuredRunnerContext, StructuredRunnerExecution, StructuredRunnerObserver, StructuredRunnerTurnState } from "./structured-runner.js";
|
|
4
|
+
import type { SessionSnapshot } from "./types.js";
|
|
5
|
+
export type GeminiTurnState = StructuredRunnerTurnState;
|
|
6
|
+
/**
|
|
7
|
+
* Gemini CLI 的会话写在 `~/.gemini/tmp/<project_hash>/chats/`。ID 找不回来时 headless
|
|
8
|
+
* 会以 42 退出并打印下面这句,识别出来才能丢掉失效的 resume ID 重开一轮
|
|
9
|
+
* (与 Claude / Pi 的同类兜底一致)。
|
|
10
|
+
*/
|
|
11
|
+
export declare function isMissingGeminiSession(error: string | null | undefined): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* approval mode 映射。结构化 runner 没有权限桥,`default` 下需要确认的工具调用会被
|
|
14
|
+
* 直接拒绝;`yolo` 才是能连续干活的那一档。
|
|
15
|
+
*/
|
|
16
|
+
export declare function geminiApprovalMode(session: SessionSnapshot): "default" | "auto_edit" | "yolo";
|
|
17
|
+
/**
|
|
18
|
+
* `-p ""` 只把 CLI 钉在 headless 模式,真正的 prompt 走 stdin(Gemini CLI 把 stdin
|
|
19
|
+
* 内容与 `-p` 内容用空行拼接,`-p` 为空时只剩 stdin)。这样长消息不撞 argv 上限,
|
|
20
|
+
* 也不会出现在 `ps` 里。
|
|
21
|
+
*
|
|
22
|
+
* `--skip-trust` 必须带:headless 模式下 Gemini CLI 会检查工作区信任状态,未信任的
|
|
23
|
+
* 目录会直接以 55 退出,并把 `--approval-mode` 降级成 `default`。Wand 是在用户自己
|
|
24
|
+
* 选定的工作目录里代表用户启动这个 CLI,与其它 provider 的 auto-approve 语义一致。
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildGeminiArgs(session: SessionSnapshot): string[];
|
|
27
|
+
export declare function geminiToolName(name: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Apply one Gemini CLI `--output-format stream-json` event.
|
|
30
|
+
*
|
|
31
|
+
* assistant `message` 事件带 `delta: true`,每个 chunk 是增量而不是快照,所以文本是
|
|
32
|
+
* 追加而不是替换。tool_use / tool_result 由 CLI 直接按 tool_id 配对,不需要 upsert。
|
|
33
|
+
*/
|
|
34
|
+
export declare function applyGeminiEvent(state: GeminiTurnState, event: Record<string, unknown>): string | null;
|
|
35
|
+
export declare class GeminiRunner implements StructuredRunnerAdapter {
|
|
36
|
+
private readonly spawnProcess;
|
|
37
|
+
private readonly execHost?;
|
|
38
|
+
constructor(spawnProcess?: typeof spawn, execHost?: StructuredExecHost | undefined);
|
|
39
|
+
start(context: StructuredRunnerContext, observer: StructuredRunnerObserver): StructuredRunnerExecution;
|
|
40
|
+
}
|