@co0ontty/wand 4.82.0 → 4.83.1
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 +3 -3
- 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/session-provider.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { constants, accessSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export function isSessionProvider(value) {
|
|
8
|
-
return typeof value === "string" && SESSION_PROVIDER_SET.has(value);
|
|
9
|
-
}
|
|
10
|
-
/** provider 对应的 CLI 可执行文件名(Qoder 的 CLI 叫 qodercli)。 */
|
|
11
|
-
export function providerCliCommand(provider) {
|
|
12
|
-
return provider === "qoder" ? "qodercli" : provider;
|
|
13
|
-
}
|
|
3
|
+
import { providerCliCommand } from "./provider-catalog.js";
|
|
4
|
+
// Provider 列表、别名、展示名与推断规则全部收敛在 src/provider-catalog.ts(浏览器层同用)。
|
|
5
|
+
// 这里只保留服务端特有的、需要 node:fs 的能力。
|
|
6
|
+
export { SESSION_PROVIDERS, isSessionProvider, providerCliCommand, providerDisplayName, inferProviderFromRunner, inferProviderFromCommand, normalizeProviderId, } from "./provider-catalog.js";
|
|
14
7
|
/**
|
|
15
8
|
* 不启动 shell 也能判断某个 provider 的 CLI 是否在 PATH 上(与子进程 PATH 一致)。
|
|
16
9
|
* 用于多候选降级时跳过没安装的工具,避免白等一次 spawn 失败。
|
|
@@ -30,46 +23,3 @@ export function providerCliInstalled(provider, pathValue = process.env.PATH ?? "
|
|
|
30
23
|
}
|
|
31
24
|
return false;
|
|
32
25
|
}
|
|
33
|
-
/** 从 runner 名推断 provider;`pty` 等与 provider 无关的 runner 返回 undefined。 */
|
|
34
|
-
export function inferProviderFromRunner(runner) {
|
|
35
|
-
if (runner === "claude-cli" || runner === "claude-cli-print" || runner === "claude-sdk")
|
|
36
|
-
return "claude";
|
|
37
|
-
if (runner === "codex-cli-exec")
|
|
38
|
-
return "codex";
|
|
39
|
-
if (runner === "opencode-cli-run")
|
|
40
|
-
return "opencode";
|
|
41
|
-
if (runner === "grok-cli-headless")
|
|
42
|
-
return "grok";
|
|
43
|
-
if (runner === "qoder-cli-print")
|
|
44
|
-
return "qoder";
|
|
45
|
-
if (runner === "pi-cli-json")
|
|
46
|
-
return "pi";
|
|
47
|
-
if (runner === "gemini-cli-json")
|
|
48
|
-
return "gemini";
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* 从启动命令推断 provider;无法识别时返回 undefined,由调用方决定默认值。
|
|
53
|
-
*
|
|
54
|
-
* 识别的是命令行的第一个 token:`claude`、`npx claude`、任意路径下的 `claude`
|
|
55
|
-
* 都算 Claude,其余 provider 要求命令以其 CLI 名开头。
|
|
56
|
-
*/
|
|
57
|
-
export function inferProviderFromCommand(command) {
|
|
58
|
-
const value = command.trim();
|
|
59
|
-
if (/^(?:claude|npx\s+claude|[^\s]+\/claude)(?:\s|$)/i.test(value))
|
|
60
|
-
return "claude";
|
|
61
|
-
if (/^codex\b/i.test(value))
|
|
62
|
-
return "codex";
|
|
63
|
-
if (/^opencode\b/i.test(value))
|
|
64
|
-
return "opencode";
|
|
65
|
-
if (/^grok\b/i.test(value))
|
|
66
|
-
return "grok";
|
|
67
|
-
if (/^qodercli\b/i.test(value))
|
|
68
|
-
return "qoder";
|
|
69
|
-
if (/^pi\b/i.test(value))
|
|
70
|
-
return "pi";
|
|
71
|
-
// Gemini CLI 的可执行文件就叫 gemini;-p / --prompt 等参数不影响首 token 识别。
|
|
72
|
-
if (/^gemini\b/i.test(value))
|
|
73
|
-
return "gemini";
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
@@ -31,6 +31,11 @@ export declare class SessionRegistry {
|
|
|
31
31
|
setSessionThinkingEffort(id: string, effort: SessionSnapshot["thinkingEffort"]): SessionSnapshot | null;
|
|
32
32
|
setSessionMode(id: string, mode: ExecutionMode): SessionSnapshot | null;
|
|
33
33
|
setSessionTopic(id: string, title: string, description: string): SessionSnapshot | null;
|
|
34
|
+
/**
|
|
35
|
+
* 归档 / 取消归档一个会话:只写标记,不杀进程、不删历史;真正的清理留给保留期扫描。
|
|
36
|
+
* 会话可能只存在于 storage(重启后未加载),因此 storage-only 也要能改。
|
|
37
|
+
*/
|
|
38
|
+
setArchived(id: string, archived: boolean): SessionSnapshot | null;
|
|
34
39
|
updateWorktreeState(id: string, status: SessionSnapshot["worktreeMergeStatus"], info: SessionSnapshot["worktreeMergeInfo"]): SessionSnapshot | null;
|
|
35
40
|
delete(id: string): SessionSnapshot | null;
|
|
36
41
|
deleteWithProviderHistory(id: string): SessionSnapshot | null;
|
package/dist/session-registry.js
CHANGED
|
@@ -67,7 +67,7 @@ export class SessionRegistry {
|
|
|
67
67
|
get(id) {
|
|
68
68
|
const snapshot = this.structured.get(id) ?? this.processes.getOwned(id) ?? this.storage.getSession(id);
|
|
69
69
|
const membership = this.storage.getSessionWorkspace(id);
|
|
70
|
-
return snapshot
|
|
70
|
+
return snapshot ? { ...snapshot, ...membership, ...this.storage.getSessionCompletion(id) } : null;
|
|
71
71
|
}
|
|
72
72
|
getLatest(id) {
|
|
73
73
|
return this.get(id);
|
|
@@ -84,6 +84,8 @@ export class SessionRegistry {
|
|
|
84
84
|
const live = byId.get(snapshot.id);
|
|
85
85
|
byId.set(snapshot.id, live ? {
|
|
86
86
|
...live, workspaceId: snapshot.workspaceId, workspaceTaskId: snapshot.workspaceTaskId,
|
|
87
|
+
completionRevision: snapshot.completionRevision,
|
|
88
|
+
viewedCompletionRevision: snapshot.viewedCompletionRevision,
|
|
87
89
|
} : snapshot);
|
|
88
90
|
}
|
|
89
91
|
return Array.from(byId.values()).sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
@@ -137,6 +139,22 @@ export class SessionRegistry {
|
|
|
137
139
|
return this.processes.setSessionTopic(id, title, description);
|
|
138
140
|
return this.updateStored(id, (snapshot) => ({ ...snapshot, title, description, summary: description }));
|
|
139
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* 归档 / 取消归档一个会话:只写标记,不杀进程、不删历史;真正的清理留给保留期扫描。
|
|
144
|
+
* 会话可能只存在于 storage(重启后未加载),因此 storage-only 也要能改。
|
|
145
|
+
*/
|
|
146
|
+
setArchived(id, archived) {
|
|
147
|
+
const owner = this.ownerOf(id);
|
|
148
|
+
if (owner === "structured")
|
|
149
|
+
return this.structured.setSessionArchived(id, archived);
|
|
150
|
+
if (owner === "pty")
|
|
151
|
+
return this.processes.setSessionArchived(id, archived);
|
|
152
|
+
return this.updateStored(id, (snapshot) => ({
|
|
153
|
+
...snapshot,
|
|
154
|
+
archived,
|
|
155
|
+
archivedAt: archived ? new Date().toISOString() : null,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
140
158
|
updateWorktreeState(id, status, info) {
|
|
141
159
|
const owner = this.ownerOf(id);
|
|
142
160
|
if (owner === "structured")
|
|
@@ -50,6 +50,8 @@ function sessionBase(snapshot) {
|
|
|
50
50
|
exitCode: snapshot.exitCode,
|
|
51
51
|
startedAt: snapshot.startedAt,
|
|
52
52
|
endedAt: snapshot.endedAt,
|
|
53
|
+
completionRevision: snapshot.completionRevision ?? 0,
|
|
54
|
+
viewedCompletionRevision: snapshot.viewedCompletionRevision ?? 0,
|
|
53
55
|
archived: snapshot.archived,
|
|
54
56
|
archivedAt: snapshot.archivedAt,
|
|
55
57
|
permissionBlocked: snapshot.permissionBlocked,
|
package/dist/storage.d.ts
CHANGED
|
@@ -345,6 +345,10 @@ export declare class WandStorage {
|
|
|
345
345
|
issueEmployeeKnowledgeAccess(sessionId: string, employeeId: string, now?: number): string;
|
|
346
346
|
resolveEmployeeKnowledgeAccess(token: string, now?: number): string | null;
|
|
347
347
|
revokeEmployeeKnowledgeAccess(token: string): void;
|
|
348
|
+
/** Inference-only capability; never accepted by general HTTP authentication. */
|
|
349
|
+
issueDecisionAccess(sessionId: string, now?: number): string;
|
|
350
|
+
resolveDecisionAccess(token: string, now?: number): string | null;
|
|
351
|
+
revokeDecisionAccess(token: string): void;
|
|
348
352
|
/** No IO on input hot paths; queued observations still CAS against the database before writing. */
|
|
349
353
|
userMemoryCaptureState(): Readonly<{
|
|
350
354
|
enabled: boolean;
|
|
@@ -412,6 +416,14 @@ export declare class WandStorage {
|
|
|
412
416
|
upsertAgentActivity(item: AgentActivityItem): void;
|
|
413
417
|
listAgentActivity(): AgentActivityItem[];
|
|
414
418
|
markAgentActivityRead(sessionId?: string): void;
|
|
419
|
+
/** Completion/read state is independent of runner checkpoints and survives restarts. */
|
|
420
|
+
getSessionCompletion(id: string): {
|
|
421
|
+
completionRevision: number;
|
|
422
|
+
viewedCompletionRevision: number;
|
|
423
|
+
} | null;
|
|
424
|
+
recordSessionCompletion(id: string): ReturnType<WandStorage["getSessionCompletion"]>;
|
|
425
|
+
/** A delayed view of generation N must never consume a newer completion N+1. */
|
|
426
|
+
markSessionCompletionViewed(id: string, revision: number): boolean;
|
|
415
427
|
saveSession(snapshot: SessionSnapshot): void;
|
|
416
428
|
/** Update runtime/scalar fields without serializing or rewriting messages/output. */
|
|
417
429
|
updateSessionRuntimeMetadata(snapshot: SessionSnapshot): void;
|
package/dist/storage.js
CHANGED
|
@@ -8,12 +8,13 @@ import { inferProviderFromCommand, inferProviderFromRunner, isSessionProvider, S
|
|
|
8
8
|
import { DEFAULT_ITERATION_NAME, DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentKind, normalizeWandTaskAgentMode } from "./task-types.js";
|
|
9
9
|
import { firstLayoutTabId } from "./layout-tree.js";
|
|
10
10
|
import { isUnnamedWorkspaceTaskName } from "./wand-task-sync.js";
|
|
11
|
-
import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, aiTeamChatTitle, isTeamMemberRole, memberAgents } from "./ai-team-types.js";
|
|
11
|
+
import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, aiTeamChatTitle, isBuiltinSiliconEmployee, isTeamMemberRole, memberAgents, parseSiliconEmployeeTags, siliconEmployeeTags } from "./ai-team-types.js";
|
|
12
|
+
import { freezeTeamEmployees, projectTeamDefinition, restoreTeamMemberEmployee, serializeExecutionTeam } from "./ai-team-employee-binding.js";
|
|
12
13
|
import { isTaskExecutionSubject } from "./task-types.js";
|
|
13
14
|
import { SYSTEM_EMPLOYEE_KEY, systemEmployeeDefinition, systemEmployeeSeedAgents, } from "./system-employee.js";
|
|
14
15
|
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
15
16
|
import { DEFAULT_EMPLOYEE_KEY } from "./ai-team-types.js";
|
|
16
|
-
import { defaultEmployeeDefinition } from "./default-employee.js";
|
|
17
|
+
import { defaultEmployeeDefinition, legacyPtyRoleIdentity } from "./default-employee.js";
|
|
17
18
|
import { normalizeEmployeeKnowledge } from "./employee-knowledge-content.js";
|
|
18
19
|
import { EMPLOYEE_KNOWLEDGE_MAX_ENTRIES } from "./employee-knowledge-types.js";
|
|
19
20
|
import { USER_MEMORY_MAX_EVENTS, USER_MEMORY_RETENTION_MS, } from "./user-memory-types.js";
|
|
@@ -302,7 +303,7 @@ function mapWorktreeMergeFields(row) {
|
|
|
302
303
|
}
|
|
303
304
|
function sessionSelectFields(slim = false) {
|
|
304
305
|
return `id, session_source, automation_id, provider, session_kind, runner, command, cwd, mode, status, exit_code, started_at, ended_at, ${slim ? "'' AS output" : "output"}, pty_output_seq, archived, archived_at, claude_session_id, ${slim ? "NULL AS messages" : "messages"}, queued_messages, queued_message_skills, structured_state
|
|
305
|
-
, resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description, session_options, workspace_id, workspace_task_id`;
|
|
306
|
+
, resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description, session_options, workspace_id, workspace_task_id, completion_revision, viewed_completion_revision`;
|
|
306
307
|
}
|
|
307
308
|
function mapWorkspaceKind(raw) {
|
|
308
309
|
return raw === "global" ? "global" : "project";
|
|
@@ -497,6 +498,8 @@ function mapSessionCore(row) {
|
|
|
497
498
|
exitCode: row.exit_code,
|
|
498
499
|
startedAt: row.started_at,
|
|
499
500
|
endedAt: row.ended_at,
|
|
501
|
+
completionRevision: row.completion_revision ?? 0,
|
|
502
|
+
viewedCompletionRevision: row.viewed_completion_revision ?? 0,
|
|
500
503
|
output: row.output,
|
|
501
504
|
ptyOutputSeq: row.pty_output_seq,
|
|
502
505
|
archived: Boolean(row.archived),
|
|
@@ -515,6 +518,7 @@ function mapSessionCore(row) {
|
|
|
515
518
|
workspaceTaskId: row.workspace_task_id ?? undefined,
|
|
516
519
|
...mapWorktreeMergeFields(row),
|
|
517
520
|
...sessionOptions,
|
|
521
|
+
...legacyPtyRoleIdentity({ ...sessionOptions, sessionKind: row.session_kind ?? "pty", provider }),
|
|
518
522
|
...(Object.prototype.hasOwnProperty.call(sessionOptions, "pendingEscalation")
|
|
519
523
|
? { permissionBlocked: Boolean(sessionOptions.pendingEscalation) }
|
|
520
524
|
: {}),
|
|
@@ -781,6 +785,7 @@ const INIT_SQL = `
|
|
|
781
785
|
parent_task_id TEXT,
|
|
782
786
|
title_source TEXT,
|
|
783
787
|
auto_title_signature TEXT,
|
|
788
|
+
archived_at TEXT,
|
|
784
789
|
created_at TEXT NOT NULL,
|
|
785
790
|
updated_at TEXT NOT NULL,
|
|
786
791
|
FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE SET NULL
|
|
@@ -839,6 +844,13 @@ const INIT_SQL = `
|
|
|
839
844
|
);
|
|
840
845
|
CREATE INDEX IF NOT EXISTS idx_employee_knowledge_access_expiry ON employee_knowledge_access(expires_at);
|
|
841
846
|
|
|
847
|
+
CREATE TABLE IF NOT EXISTS decision_access (
|
|
848
|
+
token_hash TEXT PRIMARY KEY,
|
|
849
|
+
session_id TEXT NOT NULL REFERENCES command_sessions(id) ON DELETE CASCADE,
|
|
850
|
+
expires_at INTEGER NOT NULL
|
|
851
|
+
);
|
|
852
|
+
CREATE INDEX IF NOT EXISTS idx_decision_access_expiry ON decision_access(expires_at);
|
|
853
|
+
|
|
842
854
|
CREATE TABLE IF NOT EXISTS user_memory_events (
|
|
843
855
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
844
856
|
feature TEXT NOT NULL,
|
|
@@ -935,6 +947,9 @@ function ensureAiTeamSchema(db) {
|
|
|
935
947
|
if (employeeColumns.size > 0 && !employeeColumns.has("system_key")) {
|
|
936
948
|
db.exec("ALTER TABLE silicon_employees ADD COLUMN system_key TEXT");
|
|
937
949
|
}
|
|
950
|
+
if (employeeColumns.size > 0 && !employeeColumns.has("tags_json")) {
|
|
951
|
+
db.exec("ALTER TABLE silicon_employees ADD COLUMN tags_json TEXT NOT NULL DEFAULT '[]'");
|
|
952
|
+
}
|
|
938
953
|
}
|
|
939
954
|
function ensureWandTaskSchema(db) {
|
|
940
955
|
const columns = db.prepare("PRAGMA table_info(wand_tasks)").all();
|
|
@@ -962,6 +977,9 @@ function ensureWandTaskSchema(db) {
|
|
|
962
977
|
// 里程碑:只加列,历史行保持 NULL;里程碑本体在 wand_milestones(INIT_SQL 建表)。
|
|
963
978
|
if (columns.length > 0 && !names.has("milestone_id"))
|
|
964
979
|
db.exec("ALTER TABLE wand_tasks ADD COLUMN milestone_id TEXT");
|
|
980
|
+
// 归档时间(自动/手动归档都写它):保留期清理按它起算 7 天,历史归档行保持 NULL。
|
|
981
|
+
if (columns.length > 0 && !names.has("archived_at"))
|
|
982
|
+
db.exec("ALTER TABLE wand_tasks ADD COLUMN archived_at TEXT");
|
|
965
983
|
if (columns.length > 0) {
|
|
966
984
|
// Index creation must wait until the column exists on legacy databases.
|
|
967
985
|
// INIT_SQL cannot create it: CREATE TABLE IF NOT EXISTS is a no-op on old
|
|
@@ -1086,7 +1104,7 @@ export function ensureDatabaseFile(dbPath) {
|
|
|
1086
1104
|
}
|
|
1087
1105
|
const WAND_TASK_FIELDS = `id, identifier, workspace_id, workspace_task_id, parent_task_id,
|
|
1088
1106
|
title, title_source, auto_title_signature, description, status, priority, labels_json,
|
|
1089
|
-
due_date, milestone_id, sort_order, agent_json, execution_subject_json, created_at, updated_at`;
|
|
1107
|
+
due_date, milestone_id, sort_order, agent_json, execution_subject_json, archived_at, created_at, updated_at`;
|
|
1090
1108
|
// Shared metadata comes from the latest explicitly linked card, including meaningful NULLs.
|
|
1091
1109
|
// The old container columns remain readable for legacy rows until the startup migration.
|
|
1092
1110
|
const WORKSPACE_TASK_PROJECTION = `SELECT wt.id,
|
|
@@ -1559,9 +1577,12 @@ export class WandStorage {
|
|
|
1559
1577
|
}
|
|
1560
1578
|
tasksAggregateFingerprint() {
|
|
1561
1579
|
const sessions = this.db.prepare(`SELECT COUNT(*) AS count,
|
|
1580
|
+
COALESCE(SUM(archived), 0) AS archivedCount,
|
|
1562
1581
|
COALESCE(MAX(started_at), '') AS started,
|
|
1563
1582
|
COALESCE(MAX(ended_at), '') AS ended,
|
|
1564
|
-
COALESCE(MAX(title), '') AS title
|
|
1583
|
+
COALESCE(MAX(title), '') AS title,
|
|
1584
|
+
COALESCE(SUM(completion_revision), 0) AS completionRevision,
|
|
1585
|
+
COALESCE(SUM(viewed_completion_revision), 0) AS viewedCompletionRevision
|
|
1565
1586
|
FROM command_sessions`).get();
|
|
1566
1587
|
// Per-task metadata catches edits even when another task owns the maximum
|
|
1567
1588
|
// timestamp/revision. Layout payloads are represented by their own revision.
|
|
@@ -1630,6 +1651,7 @@ export class WandStorage {
|
|
|
1630
1651
|
})(),
|
|
1631
1652
|
createdAt: String(row.created_at),
|
|
1632
1653
|
updatedAt: String(row.updated_at),
|
|
1654
|
+
archivedAt: typeof row.archived_at === "string" && row.archived_at ? row.archived_at : null,
|
|
1633
1655
|
};
|
|
1634
1656
|
}
|
|
1635
1657
|
getWandTask(id) {
|
|
@@ -1646,13 +1668,15 @@ export class WandStorage {
|
|
|
1646
1668
|
insertWandTask(input, createdAt = nowIso()) {
|
|
1647
1669
|
const id = crypto.randomUUID();
|
|
1648
1670
|
const status = input.status ?? "todo";
|
|
1671
|
+
const createdAtValue = createdAt;
|
|
1672
|
+
const archivedAt = status === "archived" ? createdAtValue : null;
|
|
1649
1673
|
const max = this.db.prepare(`SELECT COALESCE(MAX(sort_order), -1) AS value FROM wand_tasks
|
|
1650
1674
|
WHERE workspace_id IS ? AND status = ?`).get(input.workspaceId ?? null, status);
|
|
1651
1675
|
const number = this.db.prepare(`SELECT COALESCE(MAX(CAST(substr(identifier, 6) AS INTEGER)), 0) AS value
|
|
1652
1676
|
FROM wand_tasks WHERE identifier GLOB 'TASK-[0-9]*'`).get();
|
|
1653
1677
|
this.db.prepare(`INSERT INTO wand_tasks (${WAND_TASK_FIELDS})
|
|
1654
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1655
|
-
.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,
|
|
1678
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
1679
|
+
.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, archivedAt, createdAtValue, createdAtValue);
|
|
1656
1680
|
return this.getWandTask(id);
|
|
1657
1681
|
}
|
|
1658
1682
|
createWandTask(input) {
|
|
@@ -1685,6 +1709,14 @@ export class WandStorage {
|
|
|
1685
1709
|
/** Private canonical writer; callers own the transaction, never a reverse writer. */
|
|
1686
1710
|
writeWandTask(current, patch) {
|
|
1687
1711
|
const next = { ...current, ...patch, updatedAt: nowIso() };
|
|
1712
|
+
// 归档时间只在进入归档时写一次;取消归档/重新打开时清空,恢复“未动多久”重新计时。
|
|
1713
|
+
if (next.status === "archived") {
|
|
1714
|
+
if (!next.archivedAt)
|
|
1715
|
+
next.archivedAt = next.updatedAt;
|
|
1716
|
+
}
|
|
1717
|
+
else {
|
|
1718
|
+
next.archivedAt = null;
|
|
1719
|
+
}
|
|
1688
1720
|
if (patch.milestoneId !== undefined || patch.workspaceId !== undefined) {
|
|
1689
1721
|
next.milestoneId = this.taskMilestoneForWrite(next.milestoneId, next.workspaceId);
|
|
1690
1722
|
}
|
|
@@ -1699,9 +1731,10 @@ export class WandStorage {
|
|
|
1699
1731
|
}
|
|
1700
1732
|
this.db.prepare(`UPDATE wand_tasks SET workspace_id = ?, workspace_task_id = ?, parent_task_id = ?,
|
|
1701
1733
|
title = ?, title_source = ?, auto_title_signature = ?, description = ?, status = ?, priority = ?,
|
|
1702
|
-
labels_json = ?, due_date = ?, milestone_id = ?, sort_order = ?, agent_json = ?, execution_subject_json = ?,
|
|
1734
|
+
labels_json = ?, due_date = ?, milestone_id = ?, sort_order = ?, agent_json = ?, execution_subject_json = ?,
|
|
1735
|
+
archived_at = ?, updated_at = ?
|
|
1703
1736
|
WHERE id = ?`)
|
|
1704
|
-
.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);
|
|
1737
|
+
.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.archivedAt, next.updatedAt, next.id);
|
|
1705
1738
|
if (patch.workspaceId !== undefined && next.workspaceTaskId) {
|
|
1706
1739
|
const workspaceId = next.workspaceId ?? this.ensureGlobalWorkspace().id;
|
|
1707
1740
|
// The container FK/index and standalone session workspace bucket follow the canonical project.
|
|
@@ -2416,18 +2449,22 @@ export class WandStorage {
|
|
|
2416
2449
|
return existing;
|
|
2417
2450
|
}
|
|
2418
2451
|
saveSiliconEmployee(employee) {
|
|
2452
|
+
const tags = isBuiltinSiliconEmployee(employee)
|
|
2453
|
+
? siliconEmployeeTags(employee)
|
|
2454
|
+
: parseSiliconEmployeeTags(employee.tags ?? []);
|
|
2419
2455
|
this.db.prepare(`INSERT INTO silicon_employees (
|
|
2420
|
-
id, name, duty, prompt, avatar, agents_json, system_key, archived_at, created_at, updated_at
|
|
2421
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2456
|
+
id, name, duty, prompt, avatar, agents_json, system_key, archived_at, created_at, updated_at, tags_json
|
|
2457
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2422
2458
|
ON CONFLICT(id) DO UPDATE SET
|
|
2423
2459
|
name = excluded.name,
|
|
2424
2460
|
duty = excluded.duty,
|
|
2425
2461
|
prompt = excluded.prompt,
|
|
2426
2462
|
avatar = excluded.avatar,
|
|
2427
2463
|
agents_json = excluded.agents_json,
|
|
2464
|
+
tags_json = excluded.tags_json,
|
|
2428
2465
|
system_key = excluded.system_key,
|
|
2429
2466
|
archived_at = excluded.archived_at,
|
|
2430
|
-
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);
|
|
2467
|
+
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, JSON.stringify(tags));
|
|
2431
2468
|
}
|
|
2432
2469
|
archiveSiliconEmployee(id, archivedAt = new Date().toISOString()) {
|
|
2433
2470
|
this.db.prepare("UPDATE silicon_employees SET archived_at = ?, updated_at = ? WHERE id = ?").run(archivedAt, new Date().toISOString(), id);
|
|
@@ -2530,6 +2567,27 @@ export class WandStorage {
|
|
|
2530
2567
|
this.db.prepare("DELETE FROM employee_knowledge_access WHERE token_hash = ?")
|
|
2531
2568
|
.run(crypto.createHash("sha256").update(token).digest("hex"));
|
|
2532
2569
|
}
|
|
2570
|
+
/** Inference-only capability; never accepted by general HTTP authentication. */
|
|
2571
|
+
issueDecisionAccess(sessionId, now = Date.now()) {
|
|
2572
|
+
if (!this.getSessionSlim(sessionId))
|
|
2573
|
+
throw new Error("决策调用所属会话不存在。");
|
|
2574
|
+
this.db.prepare("DELETE FROM decision_access WHERE expires_at <= ? OR session_id = ?").run(now, sessionId);
|
|
2575
|
+
const token = `wd_${crypto.randomBytes(32).toString("base64url")}`;
|
|
2576
|
+
this.db.prepare("INSERT INTO decision_access (token_hash, session_id, expires_at) VALUES (?, ?, ?)")
|
|
2577
|
+
.run(crypto.createHash("sha256").update(token).digest("hex"), sessionId, now + 6 * 60 * 60 * 1000);
|
|
2578
|
+
return token;
|
|
2579
|
+
}
|
|
2580
|
+
resolveDecisionAccess(token, now = Date.now()) {
|
|
2581
|
+
if (!/^wd_[A-Za-z0-9_-]{43}$/.test(token))
|
|
2582
|
+
return null;
|
|
2583
|
+
const row = this.db.prepare("SELECT session_id FROM decision_access WHERE token_hash = ? AND expires_at > ?")
|
|
2584
|
+
.get(crypto.createHash("sha256").update(token).digest("hex"), now);
|
|
2585
|
+
return typeof row?.session_id === "string" ? row.session_id : null;
|
|
2586
|
+
}
|
|
2587
|
+
revokeDecisionAccess(token) {
|
|
2588
|
+
this.db.prepare("DELETE FROM decision_access WHERE token_hash = ?")
|
|
2589
|
+
.run(crypto.createHash("sha256").update(token).digest("hex"));
|
|
2590
|
+
}
|
|
2533
2591
|
// ============ User short-term memory ============
|
|
2534
2592
|
/** No IO on input hot paths; queued observations still CAS against the database before writing. */
|
|
2535
2593
|
userMemoryCaptureState() {
|
|
@@ -2644,13 +2702,14 @@ export class WandStorage {
|
|
|
2644
2702
|
// ============ AI Teams ============
|
|
2645
2703
|
listAiTeams() {
|
|
2646
2704
|
const rows = this.db.prepare("SELECT * FROM ai_teams ORDER BY created_at ASC").all();
|
|
2647
|
-
return rows.map(mapAiTeamRow);
|
|
2705
|
+
return rows.map((row) => projectTeamDefinition(mapAiTeamRow(row), this));
|
|
2648
2706
|
}
|
|
2649
2707
|
getAiTeam(id) {
|
|
2650
2708
|
const row = this.db.prepare("SELECT * FROM ai_teams WHERE id = ?").get(id);
|
|
2651
|
-
return row ? mapAiTeamRow(row) : null;
|
|
2709
|
+
return row ? projectTeamDefinition(mapAiTeamRow(row), this) : null;
|
|
2652
2710
|
}
|
|
2653
2711
|
saveAiTeam(team) {
|
|
2712
|
+
team = freezeTeamEmployees(team, this);
|
|
2654
2713
|
this.db.prepare(`INSERT INTO ai_teams (
|
|
2655
2714
|
id, name, description, instructions, members_json, require_plan_approval, max_steps,
|
|
2656
2715
|
created_at, updated_at
|
|
@@ -2678,7 +2737,7 @@ export class WandStorage {
|
|
|
2678
2737
|
steps_used = excluded.steps_used, step_limit = excluded.step_limit,
|
|
2679
2738
|
format_retries = excluded.format_retries, plan_approved = excluded.plan_approved,
|
|
2680
2739
|
chat_session_id = excluded.chat_session_id, pending_notes_json = excluded.pending_notes_json,
|
|
2681
|
-
updated_at = excluded.updated_at`).run(run.id, run.teamId, run.taskId,
|
|
2740
|
+
updated_at = excluded.updated_at`).run(run.id, run.teamId, run.taskId, serializeExecutionTeam(run.team), run.objective, run.cwd, run.status, run.statusDetail, run.stepsUsed, run.stepLimit, run.formatRetries, run.planApproved ? 1 : 0, run.chatSessionId, JSON.stringify(run.pendingNotes), run.createdAt, run.updatedAt);
|
|
2682
2741
|
}
|
|
2683
2742
|
/** 群聊会话对应的最近一次运行(同一个群聊可以接着开新运行)。 */
|
|
2684
2743
|
getLatestAiTeamRunByChat(sessionId) {
|
|
@@ -2741,7 +2800,12 @@ export class WandStorage {
|
|
|
2741
2800
|
// ai_teams.members_json 存的是裸成员数组;team_json 存的是整份团队对象。
|
|
2742
2801
|
currentTeams.set(runId, safeJsonParse(typeof row.current_members_json === "string" ? row.current_members_json : null));
|
|
2743
2802
|
}
|
|
2744
|
-
const
|
|
2803
|
+
const frozenMember = normalizeAiTeamMembers(runTeam?.members).find((member) => member.id === memberId);
|
|
2804
|
+
const currentMember = normalizeAiTeamMembers(currentTeams.get(runId)).find((member) => member.id === memberId);
|
|
2805
|
+
const employee = frozenMember?.employeeId ? this.getSiliconEmployee(frozenMember.employeeId) : null;
|
|
2806
|
+
const memberName = (frozenMember?.employeeId
|
|
2807
|
+
? employee?.name ?? frozenMember.name
|
|
2808
|
+
: currentMember?.employeeId ? undefined : teamMemberName(currentTeams.get(runId), memberId))
|
|
2745
2809
|
?? teamMemberName(runTeam?.members, memberId)
|
|
2746
2810
|
?? memberId;
|
|
2747
2811
|
const runStatus = String(row.run_status ?? "");
|
|
@@ -2909,6 +2973,26 @@ export class WandStorage {
|
|
|
2909
2973
|
else
|
|
2910
2974
|
this.db.prepare("UPDATE agent_activity SET read_at = ? WHERE read_at IS NULL").run(at);
|
|
2911
2975
|
}
|
|
2976
|
+
/** Completion/read state is independent of runner checkpoints and survives restarts. */
|
|
2977
|
+
getSessionCompletion(id) {
|
|
2978
|
+
const row = this.db.prepare(`SELECT completion_revision AS completionRevision,
|
|
2979
|
+
viewed_completion_revision AS viewedCompletionRevision FROM command_sessions WHERE id = ?`)
|
|
2980
|
+
.get(id);
|
|
2981
|
+
return row ? { ...row } : null;
|
|
2982
|
+
}
|
|
2983
|
+
recordSessionCompletion(id) {
|
|
2984
|
+
const row = this.db.prepare(`UPDATE command_sessions SET completion_revision = completion_revision + 1
|
|
2985
|
+
WHERE id = ? RETURNING completion_revision AS completionRevision,
|
|
2986
|
+
viewed_completion_revision AS viewedCompletionRevision`)
|
|
2987
|
+
.get(id);
|
|
2988
|
+
return row ? { ...row } : null;
|
|
2989
|
+
}
|
|
2990
|
+
/** A delayed view of generation N must never consume a newer completion N+1. */
|
|
2991
|
+
markSessionCompletionViewed(id, revision) {
|
|
2992
|
+
return this.db.prepare(`UPDATE command_sessions SET viewed_completion_revision = ?
|
|
2993
|
+
WHERE id = ? AND completion_revision = ? AND viewed_completion_revision < ?`)
|
|
2994
|
+
.run(revision, id, revision, revision).changes > 0;
|
|
2995
|
+
}
|
|
2912
2996
|
saveSession(snapshot) {
|
|
2913
2997
|
const isNew = !this.getSessionWorkspace(snapshot.id);
|
|
2914
2998
|
// A single SQLite statement is already atomic. Avoid BEGIN IMMEDIATE in
|
|
@@ -3029,6 +3113,8 @@ function mapSiliconEmployeeRow(row) {
|
|
|
3029
3113
|
.filter((candidate) => candidate !== null)
|
|
3030
3114
|
: [];
|
|
3031
3115
|
const systemKey = typeof row.system_key === "string" && row.system_key ? row.system_key : undefined;
|
|
3116
|
+
const rawTags = safeJsonParse(typeof row.tags_json === "string" ? row.tags_json : null);
|
|
3117
|
+
const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [];
|
|
3032
3118
|
return {
|
|
3033
3119
|
id: String(row.id),
|
|
3034
3120
|
name: String(row.name),
|
|
@@ -3037,6 +3123,7 @@ function mapSiliconEmployeeRow(row) {
|
|
|
3037
3123
|
avatar: String(row.avatar ?? ""),
|
|
3038
3124
|
agents,
|
|
3039
3125
|
systemKey,
|
|
3126
|
+
tags: siliconEmployeeTags({ systemKey, tags }),
|
|
3040
3127
|
archivedAt: typeof row.archived_at === "string" && row.archived_at ? row.archived_at : undefined,
|
|
3041
3128
|
createdAt: String(row.created_at),
|
|
3042
3129
|
updatedAt: String(row.updated_at),
|
|
@@ -3085,6 +3172,20 @@ function normalizeAiTeamMember(raw) {
|
|
|
3085
3172
|
member.avatar = value.avatar;
|
|
3086
3173
|
if (isTeamMemberRole(value.role))
|
|
3087
3174
|
member.role = value.role;
|
|
3175
|
+
if (typeof value.employeeId === "string" && value.employeeId.trim()) {
|
|
3176
|
+
member.employeeId = value.employeeId.trim();
|
|
3177
|
+
const raw = value._employeeSnapshot;
|
|
3178
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
3179
|
+
const snapshot = raw;
|
|
3180
|
+
if (snapshot.id === member.employeeId && typeof snapshot.prompt === "string") {
|
|
3181
|
+
restoreTeamMemberEmployee(member, {
|
|
3182
|
+
id: member.employeeId, name: member.name, avatar: member.avatar ?? "",
|
|
3183
|
+
prompt: snapshot.prompt, duty: String(snapshot.duty ?? ""), agents,
|
|
3184
|
+
createdAt: String(snapshot.createdAt ?? ""), updatedAt: String(snapshot.updatedAt ?? ""),
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3088
3189
|
return member;
|
|
3089
3190
|
}
|
|
3090
3191
|
function normalizeAiTeamMembers(raw) {
|
|
@@ -3386,6 +3487,8 @@ const SCHEMA_MIGRATIONS = [
|
|
|
3386
3487
|
["session_options", `ALTER TABLE command_sessions ADD COLUMN session_options TEXT NOT NULL DEFAULT '{"schemaVersion":1}'`],
|
|
3387
3488
|
["workspace_id", "ALTER TABLE command_sessions ADD COLUMN workspace_id TEXT"],
|
|
3388
3489
|
["workspace_task_id", "ALTER TABLE command_sessions ADD COLUMN workspace_task_id TEXT"],
|
|
3490
|
+
["completion_revision", "ALTER TABLE command_sessions ADD COLUMN completion_revision INTEGER NOT NULL DEFAULT 0"],
|
|
3491
|
+
["viewed_completion_revision", "ALTER TABLE command_sessions ADD COLUMN viewed_completion_revision INTEGER NOT NULL DEFAULT 0"],
|
|
3389
3492
|
];
|
|
3390
3493
|
/** 首页目录组顺序的偏好键(app_config 表,与其它 UI 偏好同一套读写)。 */
|
|
3391
3494
|
const WORKSPACE_GROUP_ORDER_KEY = "workspaceGroupOrder";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { withToolPreview } from "./tool-preview.js";
|
|
1
2
|
import { asRecord, isStructuredImagePart } from "./structured-content.js";
|
|
3
|
+
import { isDecisionToolCall, decisionCardSummary } from "./decision-tool.js";
|
|
2
4
|
export const WAND_PROTOCOL_VERSION = 2;
|
|
3
5
|
function text(value) {
|
|
4
6
|
return typeof value === "string" && value ? value : undefined;
|
|
@@ -255,9 +257,42 @@ function rewriteInlineToolImageUrls(messages, sessionId) {
|
|
|
255
257
|
* 传 sessionId 时额外把内联图片改写成取图 URL(见 rewriteInlineToolImageUrls)。
|
|
256
258
|
*/
|
|
257
259
|
export function enrichStructuredMessages(messages, sessionId) {
|
|
260
|
+
// 决策卡缩略投影:入参与结果可能分属不同 turn(流式/分页),先按调用 id 配对,
|
|
261
|
+
// 让两个块拿到同一份摘要;只有窗口里存在的那一侧才参与计算。
|
|
262
|
+
const decisionCalls = new Map();
|
|
263
|
+
for (const turn of messages) {
|
|
264
|
+
for (const block of turn.content) {
|
|
265
|
+
if (block.type === "tool_use" && isDecisionToolCall(block)) {
|
|
266
|
+
const call = decisionCalls.get(block.id) ?? {};
|
|
267
|
+
call.use = block;
|
|
268
|
+
decisionCalls.set(block.id, call);
|
|
269
|
+
}
|
|
270
|
+
else if (block.type === "tool_result") {
|
|
271
|
+
const call = decisionCalls.get(block.tool_use_id);
|
|
272
|
+
if (call)
|
|
273
|
+
call.result = block;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const decisionIds = new Set(decisionCalls.keys());
|
|
278
|
+
const decisionSummaries = new Map([...decisionCalls].map(([id, call]) => [id, decisionCardSummary(call.use?.input, call.result)]));
|
|
279
|
+
const decisionSemantic = (id) => {
|
|
280
|
+
const summary = decisionSummaries.get(id);
|
|
281
|
+
return summary ? { kind: "decision", summary } : { kind: "decision" };
|
|
282
|
+
};
|
|
258
283
|
const enriched = stampDerivedSubagents(messages).map((turn) => ({
|
|
259
284
|
...turn,
|
|
260
|
-
content: turn.content.map((block) => {
|
|
285
|
+
content: turn.content.map(withToolPreview).map((block) => {
|
|
286
|
+
if (block.type === "tool_use" && decisionIds.has(block.id)) {
|
|
287
|
+
return { ...block, semantic: decisionSemantic(block.id), activity: undefined };
|
|
288
|
+
}
|
|
289
|
+
if (block.type === "tool_result" && decisionIds.has(block.tool_use_id)) {
|
|
290
|
+
return { ...block, semantic: decisionSemantic(block.tool_use_id) };
|
|
291
|
+
}
|
|
292
|
+
if (block.type === "tool_result" && block.semantic?.kind === "decision") {
|
|
293
|
+
const summary = decisionSummaries.get(block.tool_use_id) ?? block.semantic.summary;
|
|
294
|
+
return { ...block, semantic: { kind: "decision", ...(summary ? { summary } : {}) } };
|
|
295
|
+
}
|
|
261
296
|
if (block.type !== "tool_use" || block.name !== "AskUserQuestion")
|
|
262
297
|
return block;
|
|
263
298
|
const questions = questionsFromInput(block.input);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isNativeThinkingEffort } from "./provider-catalog.js";
|
|
1
2
|
import { shellQuote } from "./shell-quote.js";
|
|
2
|
-
const NATIVE_THINKING_EFFORT = /^(claude|codex|opencode|grok|qoder|pi):[a-z0-9][a-z0-9_-]{0,31}$/;
|
|
3
3
|
/**
|
|
4
4
|
* 把会话级系统提示交给 CLI 的开关。Claude / Qoder / Pi 用 `--append-system-prompt`,
|
|
5
5
|
* Grok 用 `--rules`;Codex / OpenCode / Gemini 没有这个入口,只能退回并入本轮消息
|
|
@@ -58,7 +58,7 @@ export function isThinkingEffort(value) {
|
|
|
58
58
|
|| value === "standard"
|
|
59
59
|
|| value === "deep"
|
|
60
60
|
|| value === "max"
|
|
61
|
-
|| (
|
|
61
|
+
|| isNativeThinkingEffort(value);
|
|
62
62
|
}
|
|
63
63
|
/** 只取本 provider 的原生档位。别的 CLI 的前缀不能串过去。 */
|
|
64
64
|
export function prefixedThinkingEffort(provider, effort) {
|
|
@@ -127,7 +127,7 @@ export function normalizeThinkingEffort(value) {
|
|
|
127
127
|
const normalized = value.trim().toLowerCase();
|
|
128
128
|
if (normalized === "off" || normalized === "standard" || normalized === "deep" || normalized === "max")
|
|
129
129
|
return normalized;
|
|
130
|
-
if (
|
|
130
|
+
if (isNativeThinkingEffort(normalized))
|
|
131
131
|
return normalized;
|
|
132
132
|
return null;
|
|
133
133
|
}
|
|
@@ -3,6 +3,7 @@ import { SessionLogger } from "./session-logger.js";
|
|
|
3
3
|
import { WandStorage } from "./storage.js";
|
|
4
4
|
import { ConversationTurn, ExecutionMode, ProcessEvent, SessionProvider, SessionRunner, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
|
|
5
5
|
import type { WandTaskAgent } from "./task-types.js";
|
|
6
|
+
import { type DecisionRuntimeAccess } from "./decision-runner.js";
|
|
6
7
|
import { type StructuredExecHost } from "./structured-exec-host.js";
|
|
7
8
|
import type { StructuredRunnerAdapter } from "./structured-runner.js";
|
|
8
9
|
export interface StructuredSessionManagerRunners {
|
|
@@ -69,6 +70,7 @@ export declare class StructuredSessionManager {
|
|
|
69
70
|
private readonly execHost?;
|
|
70
71
|
private readonly sessions;
|
|
71
72
|
private readonly pendingRunnerExecutions;
|
|
73
|
+
private readonly unacceptedTeamStarts;
|
|
72
74
|
private readonly interruptedWith;
|
|
73
75
|
private readonly preserveQueueOnInterrupt;
|
|
74
76
|
/** Last wall-clock time (ms) a streaming checkpoint reached SQLite. */
|
|
@@ -102,7 +104,7 @@ export declare class StructuredSessionManager {
|
|
|
102
104
|
private detachedRecoveryRetryTimer;
|
|
103
105
|
private detachedRecoveryFailureLogged;
|
|
104
106
|
private disposed;
|
|
105
|
-
constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, runners?: StructuredSessionManagerRunners, execHost?: StructuredExecHost | undefined);
|
|
107
|
+
constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, runners?: StructuredSessionManagerRunners, execHost?: StructuredExecHost | undefined, decisionRuntime?: () => DecisionRuntimeAccess | null);
|
|
106
108
|
private archiveExpiredSessions;
|
|
107
109
|
setEventEmitter(emitEvent: (event: ProcessEvent) => void): void;
|
|
108
110
|
/** Stop every runner and flush terminal state before storage is closed. */
|
|
@@ -128,6 +130,8 @@ export declare class StructuredSessionManager {
|
|
|
128
130
|
get(id: string): SessionSnapshot | null;
|
|
129
131
|
setSessionWorkspace(id: string, membership: Pick<SessionSnapshot, "workspaceId" | "workspaceTaskId">): void;
|
|
130
132
|
setSessionTopic(id: string, title: string, description: string): SessionSnapshot;
|
|
133
|
+
/** 归档 / 取消归档:只写标记并广播,不中断运行中的 run,也不删历史。 */
|
|
134
|
+
setSessionArchived(id: string, archived: boolean): SessionSnapshot;
|
|
131
135
|
private setSessionTopicGenerating;
|
|
132
136
|
/**
|
|
133
137
|
* Update worktree merge progress on the canonical in-memory snapshot before
|
|
@@ -144,6 +148,10 @@ export declare class StructuredSessionManager {
|
|
|
144
148
|
automationId: string;
|
|
145
149
|
title: string;
|
|
146
150
|
}): SessionSnapshot;
|
|
151
|
+
/** Server-only generation anchor, including a failed unaccepted turn whose activeRequestId is cleared. */
|
|
152
|
+
teamRequestId(id: string): string | null;
|
|
153
|
+
/** Consume only the exact failed request observed by the team's dispatch, never error-text inference. */
|
|
154
|
+
consumeUnacceptedTeamStartup(id: string, requestId: string): boolean;
|
|
147
155
|
/** Only an unstarted employee conversation may change CLI after a failed first turn. */
|
|
148
156
|
private retryEmployeeCandidate;
|
|
149
157
|
/** 往转发会话里追加别的参与者的发言。 */
|
|
@@ -153,6 +161,7 @@ export declare class StructuredSessionManager {
|
|
|
153
161
|
idempotencyKey?: string;
|
|
154
162
|
preserveQueue?: boolean;
|
|
155
163
|
queueAlreadyRemoved?: boolean;
|
|
164
|
+
teamRequestStarted?: (sessionId: string, requestId: string) => void;
|
|
156
165
|
}): Promise<SessionSnapshot>;
|
|
157
166
|
/**
|
|
158
167
|
* Reorder the pending queued messages. `order` is a permutation of the current
|
|
@@ -175,6 +184,8 @@ export declare class StructuredSessionManager {
|
|
|
175
184
|
promoteQueuedMessage(sessionId: string, index: number, expectedText?: string, idempotencyKey?: string): Promise<SessionSnapshot>;
|
|
176
185
|
/** Clear all queued messages. No-op when queue is already empty. */
|
|
177
186
|
clearQueuedMessages(sessionId: string): SessionSnapshot;
|
|
187
|
+
/** 新建空白对话可原位换 CLI;接受过输入、恢复会话和自动化不跨 provider 搬历史。 */
|
|
188
|
+
setSessionProvider(sessionId: string, provider: SessionProvider): SessionSnapshot;
|
|
178
189
|
/** Update the selected model for a structured session. Takes effect on the next spawn. */
|
|
179
190
|
setSessionModel(sessionId: string, model: string | null): SessionSnapshot;
|
|
180
191
|
/**
|
|
@@ -196,6 +207,7 @@ export declare class StructuredSessionManager {
|
|
|
196
207
|
denyPermission(sessionId: string): SessionSnapshot;
|
|
197
208
|
/** Resolve a specific escalation by requestId. */
|
|
198
209
|
resolveEscalation(sessionId: string, requestId: string, resolution: unknown): SessionSnapshot;
|
|
210
|
+
private requireRecoveryControl;
|
|
199
211
|
stop(id: string): SessionSnapshot;
|
|
200
212
|
delete(id: string): void;
|
|
201
213
|
private requireSession;
|