@co0ontty/wand 4.76.0 → 4.77.0-beta.g6c4df97

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.
Files changed (71) hide show
  1. package/dist/agent-dispatch.d.ts +46 -0
  2. package/dist/agent-dispatch.js +84 -0
  3. package/dist/ai-team-availability.d.ts +69 -0
  4. package/dist/ai-team-availability.js +100 -0
  5. package/dist/ai-team-prompts.d.ts +113 -0
  6. package/dist/ai-team-prompts.js +435 -0
  7. package/dist/ai-team-runner.d.ts +241 -0
  8. package/dist/ai-team-runner.js +1370 -0
  9. package/dist/ai-team-types.d.ts +147 -0
  10. package/dist/ai-team-types.js +32 -0
  11. package/dist/attention.d.ts +40 -0
  12. package/dist/attention.js +86 -0
  13. package/dist/build-info.json +4 -4
  14. package/dist/claude-sdk-runner.d.ts +5 -0
  15. package/dist/claude-sdk-runner.js +2 -1
  16. package/dist/commit-task-archive.d.ts +3 -0
  17. package/dist/commit-task-archive.js +43 -0
  18. package/dist/config.js +0 -2
  19. package/dist/git-quick-commit.d.ts +2 -1
  20. package/dist/git-quick-commit.js +72 -60
  21. package/dist/message-truncator.d.ts +41 -5
  22. package/dist/message-truncator.js +255 -38
  23. package/dist/missions.d.ts +4 -2
  24. package/dist/missions.js +2 -2
  25. package/dist/process-manager.d.ts +1 -0
  26. package/dist/process-manager.js +17 -8
  27. package/dist/prompt-optimizer.js +13 -13
  28. package/dist/server-ai-team-routes.d.ts +11 -0
  29. package/dist/server-ai-team-routes.js +283 -0
  30. package/dist/server-attention-routes.d.ts +9 -0
  31. package/dist/server-attention-routes.js +26 -0
  32. package/dist/server-file-routes.js +1 -1
  33. package/dist/server-session-routes.js +41 -5
  34. package/dist/server-settings-routes.js +1 -1
  35. package/dist/server-task-routes.d.ts +2 -0
  36. package/dist/server-task-routes.js +6 -40
  37. package/dist/server-workspace-routes.js +13 -5
  38. package/dist/server.js +43 -0
  39. package/dist/session-topic.js +13 -13
  40. package/dist/session-transport.d.ts +3 -0
  41. package/dist/session-transport.js +1 -0
  42. package/dist/storage.d.ts +54 -1
  43. package/dist/storage.js +413 -2
  44. package/dist/structured-claude-adapter.d.ts +5 -0
  45. package/dist/structured-claude-adapter.js +13 -2
  46. package/dist/structured-claude-protocol.d.ts +2 -0
  47. package/dist/structured-claude-protocol.js +4 -0
  48. package/dist/structured-codex-adapter.js +2 -2
  49. package/dist/structured-grok-adapter.js +2 -1
  50. package/dist/structured-opencode-adapter.js +2 -2
  51. package/dist/structured-pi-adapter.js +2 -1
  52. package/dist/structured-provider-common.d.ts +17 -0
  53. package/dist/structured-provider-common.js +52 -0
  54. package/dist/structured-qoder-adapter.js +2 -1
  55. package/dist/structured-session-manager.d.ts +18 -1
  56. package/dist/structured-session-manager.js +67 -10
  57. package/dist/system-ai.d.ts +13 -2
  58. package/dist/system-ai.js +9 -5
  59. package/dist/task-title.js +10 -10
  60. package/dist/types.d.ts +25 -2
  61. package/dist/wand-task-sync.d.ts +1 -0
  62. package/dist/wand-task-sync.js +1 -0
  63. package/dist/web-ui/content/ai-teams.js +12 -0
  64. package/dist/web-ui/content/scripts.js +86 -5301
  65. package/dist/web-ui/content/styles.css +1 -1
  66. package/dist/web-ui/embedded-assets.d.ts +1 -0
  67. package/dist/web-ui/embedded-assets.js +3 -2
  68. package/dist/web-ui/scripts.d.ts +8 -0
  69. package/dist/web-ui/scripts.js +28 -2
  70. package/dist/ws-broadcast.js +52 -19
  71. package/package.json +1 -1
@@ -1,4 +1,56 @@
1
+ import { shellQuote } from "./shell-quote.js";
1
2
  const NATIVE_THINKING_EFFORT = /^(claude|codex|opencode|grok|qoder|pi):[a-z0-9][a-z0-9_-]{0,31}$/;
3
+ /**
4
+ * 把会话级系统提示交给 CLI 的开关。Claude / Qoder / Pi 用 `--append-system-prompt`,
5
+ * Grok 用 `--rules`;Codex / OpenCode 没有这个入口,只能退回并入本轮消息。
6
+ * 各 provider 的支持情况由 `--help` 实测得出,新增 provider 前先验。
7
+ */
8
+ export function systemPromptFlag(provider) {
9
+ switch (provider) {
10
+ case "claude":
11
+ case "qoder":
12
+ case "pi":
13
+ return "--append-system-prompt";
14
+ case "grok":
15
+ return "--rules";
16
+ default:
17
+ return null;
18
+ }
19
+ }
20
+ /** 结构化 runner 的 args:provider 支持时把系统提示当成独立参数传。 */
21
+ export function systemPromptArgs(session) {
22
+ const text = session.systemPrompt?.trim();
23
+ if (!text)
24
+ return [];
25
+ const flag = systemPromptFlag(session.provider);
26
+ return flag ? [flag, text] : [];
27
+ }
28
+ /**
29
+ * provider 没有系统提示通道时的兜底:把它放在本会话第一条消息最前面,并写明它不是用户输入。
30
+ * 只在首轮拼一次:后续轮次的消息已经接在有它的对话历史后面。有通道的 provider 不会走这里。
31
+ */
32
+ export function promptWithSystemFallback(session, prompt) {
33
+ const text = session.systemPrompt?.trim();
34
+ if (!text || systemPromptFlag(session.provider))
35
+ return prompt;
36
+ if ((session.messages?.length ?? 0) > 1)
37
+ return prompt;
38
+ return composeSystemFallback(text, prompt);
39
+ }
40
+ /** provider 没有系统提示通道时的并接格式:明确标出这不是用户输入。 */
41
+ export function composeSystemFallback(systemPrompt, prompt) {
42
+ return `以下是本会话的固定要求(来自系统,不是用户输入,优先级高于后面的内容):\n\n${systemPrompt.trim()}\n\n---\n\n${prompt}`;
43
+ }
44
+ /** 把系统提示接到 CLI 命令上(provider 支持时);不支持时原样返回,由调用方并进首条输入。 */
45
+ export function commandWithSystemPrompt(command, provider, systemPrompt) {
46
+ const text = systemPrompt?.trim();
47
+ if (!text)
48
+ return command;
49
+ const flag = systemPromptFlag(provider);
50
+ if (!flag || command.includes(flag))
51
+ return command;
52
+ return `${command} ${flag} ${shellQuote(text)}`;
53
+ }
2
54
  /** 判断任意值是否为合法的 thinking effort(旧四档,或 `provider:level`)。 */
3
55
  export function isThinkingEffort(value) {
4
56
  return value === "off"
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { ClaudeCliProtocolReducer } from "./structured-claude-protocol.js";
3
3
  import { startStructuredCli } from "./structured-exec-pump.js";
4
- import { thinkingEffortToQoderEffort } from "./structured-provider-common.js";
4
+ import { systemPromptArgs, thinkingEffortToQoderEffort } from "./structured-provider-common.js";
5
5
  export function buildQoderArgs(session, prompt) {
6
6
  const args = ["-p", prompt, "--output-format", "stream-json"];
7
7
  const model = session.selectedModel?.trim();
@@ -13,6 +13,7 @@ export function buildQoderArgs(session, prompt) {
13
13
  // 与 PTY runner 一致:Qoder 一律以 yolo(bypass_permissions)启动。结构化 runner 没有
14
14
  // 权限桥,--print 下未批准的调用只会被拒绝,因此不做任何模式降级。
15
15
  args.push("--permission-mode", "bypass_permissions");
16
+ args.push(...systemPromptArgs(session));
16
17
  if (session.claudeSessionId)
17
18
  args.push("-r", session.claudeSessionId);
18
19
  return args;
@@ -2,7 +2,7 @@ import { query as sdkQuery } from "@anthropic-ai/claude-agent-sdk";
2
2
  import { type WorktreeSetupSpec } from "./git-worktree.js";
3
3
  import { SessionLogger } from "./session-logger.js";
4
4
  import { WandStorage } from "./storage.js";
5
- import { ExecutionMode, ProcessEvent, SessionProvider, SessionRunner, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
5
+ import { ConversationTurn, ExecutionMode, ProcessEvent, SessionProvider, SessionRunner, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
6
6
  import { type StructuredExecHost } from "./structured-exec-host.js";
7
7
  import type { StructuredRunnerAdapter } from "./structured-runner.js";
8
8
  export interface StructuredSessionManagerRunners {
@@ -26,6 +26,8 @@ interface CreateStructuredSessionOptions {
26
26
  thinkingEffort?: SessionSnapshot["thinkingEffort"];
27
27
  sessionSource?: SessionSource;
28
28
  automationId?: string;
29
+ /** 会话级系统提示(团队 / 自动化的角色与规则);走 provider 的系统提示通道。 */
30
+ systemPrompt?: string;
29
31
  /** 所属工作空间 ID(多标签 / 分屏项目)。 */
30
32
  workspaceId?: string;
31
33
  /** 所属工作空间任务 ID(任务 = 独立 worktree + 一组标签)。 */
@@ -38,6 +40,12 @@ interface CreateStructuredSessionOptions {
38
40
  */
39
41
  claudeSessionId?: string;
40
42
  }
43
+ /**
44
+ * 转发会话(AI 团队群聊):外观是普通结构化会话,但不起 CLI。
45
+ * 用户发的话交给 handler,别的参与者的发言由 appendRelayTurns 写进来。
46
+ * 按 automationId 前缀识别,重启后照样生效。
47
+ */
48
+ export type StructuredRelayHandler = (sessionId: string, text: string) => void | Promise<void>;
41
49
  /**
42
50
  * 返回最近一次真正提交给结构化会话的用户输入。
43
51
  *
@@ -89,6 +97,7 @@ export declare class StructuredSessionManager {
89
97
  * map 大小溢出时按时间裁剪。
90
98
  */
91
99
  private readonly seenIdempotencyKeys;
100
+ private readonly relayHandlers;
92
101
  private emitEvent;
93
102
  private archiveTimer;
94
103
  private readonly topicCoordinator;
@@ -139,6 +148,14 @@ export declare class StructuredSessionManager {
139
148
  setWorktreeMergeState(id: string, status: SessionSnapshot["worktreeMergeStatus"], info: SessionSnapshot["worktreeMergeInfo"]): SessionSnapshot | null;
140
149
  private maybeGenerateSessionTopic;
141
150
  createSession(options: CreateStructuredSessionOptions): SessionSnapshot;
151
+ registerRelay(automationIdPrefix: string, handler: StructuredRelayHandler): void;
152
+ private relayHandlerFor;
153
+ createRelaySession(options: CreateStructuredSessionOptions & {
154
+ automationId: string;
155
+ title: string;
156
+ }): SessionSnapshot;
157
+ /** 往转发会话里追加别的参与者的发言。 */
158
+ appendRelayTurns(id: string, turns: ConversationTurn[]): SessionSnapshot | null;
142
159
  sendMessage(id: string, input: string, opts?: {
143
160
  interrupt?: boolean;
144
161
  idempotencyKey?: string;
@@ -14,7 +14,7 @@ import { resolveSystemAiContext } from "./session-ai-context.js";
14
14
  import { CodexRunner } from "./structured-codex-adapter.js";
15
15
  import { CodexProtocolReducer } from "./structured-codex-protocol.js";
16
16
  import { normalizeStructuredToolResultContent } from "./structured-content.js";
17
- import { buildAppendSystemPromptParts, buildClaudeSdkThinking, ClaudeCliRunner, derivePermissionPolicy, } from "./structured-claude-adapter.js";
17
+ import { buildClaudeSdkThinking, buildSessionSystemPromptParts, ClaudeCliRunner, derivePermissionPolicy, } from "./structured-claude-adapter.js";
18
18
  import { inferStructuredEscalation, structuredPermissionDenied } from "./structured-permission.js";
19
19
  import { captureTaskMeta, extractClaudeAssistantMessage, extractClaudeModelName, normalizeClaudeToolInput, stampParentTaskResults, stampSelfTask, tagSubagentBlocks, ClaudeCliProtocolReducer, } from "./structured-claude-protocol.js";
20
20
  import { OpenCodeRunner, applyOpenCodeEvent } from "./structured-opencode-adapter.js";
@@ -24,7 +24,6 @@ import { PiRunner, applyPiEvent, isMissingPiSession } from "./structured-pi-adap
24
24
  import { structuredRunId, } from "./structured-exec-host.js";
25
25
  import { defaultStructuredRunner, defaultStructuredState, isStructuredRunnerForProvider, normalizeThinkingEffort, resolveStructuredRunner, } from "./structured-provider-common.js";
26
26
  import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
27
- /** The runner already persisted/emitted its detailed terminal snapshot. */
28
27
  class PersistedStructuredRunnerError extends Error {
29
28
  constructor(message) {
30
29
  super(message);
@@ -416,6 +415,7 @@ export class StructuredSessionManager {
416
415
  * map 大小溢出时按时间裁剪。
417
416
  */
418
417
  seenIdempotencyKeys = new Map();
418
+ relayHandlers = new Map();
419
419
  emitEvent = null;
420
420
  archiveTimer = null;
421
421
  topicCoordinator = new SessionTopicCoordinator();
@@ -1168,6 +1168,7 @@ export class StructuredSessionManager {
1168
1168
  sessionKind: "structured",
1169
1169
  sessionSource: options.sessionSource ?? "interactive",
1170
1170
  automationId: options.automationId,
1171
+ systemPrompt: options.systemPrompt?.trim() || null,
1171
1172
  workspaceId: options.workspaceId,
1172
1173
  workspaceTaskId: options.workspaceTaskId,
1173
1174
  provider,
@@ -1218,6 +1219,43 @@ export class StructuredSessionManager {
1218
1219
  this.emit({ type: "started", sessionId: id, data: { sessionKind: "structured" } });
1219
1220
  return snapshot;
1220
1221
  }
1222
+ registerRelay(automationIdPrefix, handler) {
1223
+ this.relayHandlers.set(automationIdPrefix, handler);
1224
+ }
1225
+ relayHandlerFor(session) {
1226
+ const automationId = session.automationId ?? "";
1227
+ for (const [prefix, handler] of this.relayHandlers) {
1228
+ if (automationId.startsWith(prefix))
1229
+ return handler;
1230
+ }
1231
+ return null;
1232
+ }
1233
+ createRelaySession(options) {
1234
+ const created = this.createSession(options);
1235
+ const titled = { ...created, title: options.title };
1236
+ this.sessions.set(titled.id, titled);
1237
+ this.storage.saveSession(titled);
1238
+ this.emitStructuredSnapshot(titled);
1239
+ return titled;
1240
+ }
1241
+ /** 往转发会话里追加别的参与者的发言。 */
1242
+ appendRelayTurns(id, turns) {
1243
+ const session = this.sessions.get(id);
1244
+ if (!session || turns.length === 0)
1245
+ return session ?? null;
1246
+ const createdAt = new Date().toISOString();
1247
+ const updated = {
1248
+ ...session,
1249
+ messages: [
1250
+ ...(session.messages ?? []),
1251
+ ...turns.map((turn) => ({ ...turn, createdAt: turn.createdAt ?? createdAt })),
1252
+ ],
1253
+ };
1254
+ this.sessions.set(id, updated);
1255
+ this.storage.saveSession(updated);
1256
+ this.emitStructuredSnapshot(updated);
1257
+ return updated;
1258
+ }
1221
1259
  async sendMessage(id, input, opts) {
1222
1260
  if (this.disposed)
1223
1261
  throw new Error("StructuredSessionManager has been disposed.");
@@ -1225,6 +1263,20 @@ export class StructuredSessionManager {
1225
1263
  const prompt = input.trim();
1226
1264
  if (!prompt)
1227
1265
  return session;
1266
+ const relay = this.relayHandlerFor(session);
1267
+ if (relay) {
1268
+ // 群聊不起 CLI:先落下用户这句话,再交给转发方;转发失败以提示行告诉用户。
1269
+ const posted = this.appendRelayTurns(id, [{ role: "user", content: [{ type: "text", text: prompt }] }]);
1270
+ try {
1271
+ await relay(id, prompt);
1272
+ }
1273
+ catch (error) {
1274
+ this.appendRelayTurns(id, [{
1275
+ role: "assistant", notice: true, content: [{ type: "text", text: `没能转达:${getErrorMessage(error)}` }],
1276
+ }]);
1277
+ }
1278
+ return this.sessions.get(id) ?? posted;
1279
+ }
1228
1280
  const skills = opts?.skills ?? [];
1229
1281
  if (opts?.idempotencyKey) {
1230
1282
  const mapKey = `${id}:${opts.idempotencyKey}`;
@@ -2639,13 +2691,18 @@ export class StructuredSessionManager {
2639
2691
  const interruptedByUser = this.interruptedWith.has(sessionId);
2640
2692
  const interruptedForQuestion = result.stopReason === "ask-user-question";
2641
2693
  const failedExit = (result.exitCode !== null && result.exitCode !== 0) || result.signal !== null;
2642
- if (failedExit && !interruptedByUser && !interruptedForQuestion) {
2643
- const errorText = this.formatStructuredExitError(commandLabel, result.exitCode, result.signal, {
2644
- stderr: result.stderr,
2645
- stdoutTail: result.stdoutTail,
2646
- primary: result.primaryError,
2647
- });
2648
- const failed = this.finishStructuredFailure(current, typeof result.exitCode === "number" ? result.exitCode : 1, errorText, result.state);
2694
+ // Pi 等 CLI 在 provider 报错(额度用尽、鉴权失败)时仍以 0 退出;这一轮没有任何产出时
2695
+ // 必须按失败落盘,否则会被当成一次空回复,用户和团队调度都看不到真正的错误。
2696
+ const erroredEmptyTurn = !failedExit && !!result.primaryError && !result.state.blocks.some((block) => block.type === "tool_use" || (block.type === "text" && block.text.trim() !== ""));
2697
+ if ((failedExit || erroredEmptyTurn) && !interruptedByUser && !interruptedForQuestion) {
2698
+ const errorText = erroredEmptyTurn
2699
+ ? result.primaryError.trim()
2700
+ : this.formatStructuredExitError(commandLabel, result.exitCode, result.signal, {
2701
+ stderr: result.stderr,
2702
+ stdoutTail: result.stdoutTail,
2703
+ primary: result.primaryError,
2704
+ });
2705
+ const failed = this.finishStructuredFailure(current, failedExit && typeof result.exitCode === "number" ? result.exitCode : 1, errorText, result.state);
2649
2706
  if (provider === "pi" && isMissingPiSession(result.stderr, current.claudeSessionId)) {
2650
2707
  failed.claudeSessionId = null;
2651
2708
  }
@@ -2734,7 +2791,7 @@ export class StructuredSessionManager {
2734
2791
  let killedForAskUserQuestion = false;
2735
2792
  // 权限策略 + 系统提示词都通过共享 helper 派生,与 CLI runner 一字不差。
2736
2793
  const permPolicy = derivePermissionPolicy(session.mode, session.autoApprovePermissions ?? false, session.cwd);
2737
- const systemPromptParts = buildAppendSystemPromptParts(this.config.language, session.mode);
2794
+ const systemPromptParts = buildSessionSystemPromptParts(session, this.config.language);
2738
2795
  const sdkClaudeBinary = resolveSdkClaudeBinary();
2739
2796
  // SDK 默认会把整个 process.env 透传给 claude 子进程;这里显式按 inheritEnv 配置组装,
2740
2797
  // 否则关闭"继承环境变量"开关时 SDK 路径会被静默忽略。
@@ -11,6 +11,17 @@ export declare function systemAiProfiles(config: SystemAiConfig | undefined, for
11
11
  * Later groups are appended after earlier groups and duplicate routes are skipped.
12
12
  */
13
13
  export declare function mergeSystemAiConfigs(...groups: Array<SystemAiConfig | SystemAiConfig[] | undefined>): SystemAiConfig | undefined;
14
- export declare function callSystemAiText(prompt: string, config: SystemAiConfig, timeoutMs?: number): Promise<string>;
14
+ /**
15
+ * 一次性 AI 请求:规则 / 角色走系统提示,内容走用户消息。
16
+ * 直连 API 用各协议的 system 通道;CLI 用各 provider 自己的系统提示开关
17
+ * (`systemPromptFlag`),没有开关的 provider 退回把规则并在内容前面。
18
+ */
19
+ export interface AiTextRequest {
20
+ /** 规则、角色、输出格式。空字符串表示这次调用不需要系统提示。 */
21
+ system: string;
22
+ /** 本次要处理的内容(diff、用户原话、对话摘要…)。 */
23
+ prompt: string;
24
+ }
25
+ export declare function callSystemAiText(request: AiTextRequest, config: SystemAiConfig, timeoutMs?: number): Promise<string>;
15
26
  /** Try every configured API in order. Empty responses are treated as unavailable. */
16
- export declare function callSystemAiTextWithFallback(prompt: string, config: SystemAiConfig, timeoutMs?: number): Promise<string>;
27
+ export declare function callSystemAiTextWithFallback(request: AiTextRequest, config: SystemAiConfig, timeoutMs?: number): Promise<string>;
package/dist/system-ai.js CHANGED
@@ -344,11 +344,15 @@ function endpoint(baseUrl, protocol) {
344
344
  }
345
345
  return url.toString();
346
346
  }
347
- export async function callSystemAiText(prompt, config, timeoutMs = SYSTEM_AI_TIMEOUT_MS) {
347
+ function requestParts(request) {
348
+ return { system: request.system?.trim() ?? "", prompt: request.prompt ?? "" };
349
+ }
350
+ export async function callSystemAiText(request, config, timeoutMs = SYSTEM_AI_TIMEOUT_MS) {
348
351
  const normalized = normalizeSystemAiConfig(config);
349
352
  if (!normalized.enabled || !normalized.baseUrl || !normalized.apiKey || !normalized.model) {
350
353
  throw new SystemAiError("系统 AI API 配置不完整。", "SYSTEM_AI_CONFIG_INVALID");
351
354
  }
355
+ const { system, prompt } = requestParts(request);
352
356
  const headers = { "content-type": "application/json" };
353
357
  if (normalized.protocol === "anthropic") {
354
358
  headers["anthropic-version"] = "2023-06-01";
@@ -364,14 +368,14 @@ export async function callSystemAiText(prompt, config, timeoutMs = SYSTEM_AI_TIM
364
368
  headers.authorization = `Bearer ${normalized.apiKey}`;
365
369
  }
366
370
  const body = normalized.protocol === "anthropic"
367
- ? { model: normalized.model, max_tokens: 2048, messages: [{ role: "user", content: prompt }] }
371
+ ? { model: normalized.model, max_tokens: 2048, ...(system ? { system } : {}), messages: [{ role: "user", content: prompt }] }
368
372
  : {
369
373
  model: normalized.model,
370
374
  // Quick system tasks should not silently inherit a model's expensive
371
375
  // default reasoning level. Settings probes a route with this same
372
376
  // payload, so an incompatible endpoint fails visibly before it is used.
373
377
  reasoning_effort: "low",
374
- messages: [{ role: "user", content: prompt }],
378
+ messages: [...(system ? [{ role: "system", content: system }] : []), { role: "user", content: prompt }],
375
379
  stream: false,
376
380
  };
377
381
  let response;
@@ -399,7 +403,7 @@ export async function callSystemAiText(prompt, config, timeoutMs = SYSTEM_AI_TIM
399
403
  }
400
404
  }
401
405
  /** Try every configured API in order. Empty responses are treated as unavailable. */
402
- export async function callSystemAiTextWithFallback(prompt, config, timeoutMs = SYSTEM_AI_TIMEOUT_MS) {
406
+ export async function callSystemAiTextWithFallback(request, config, timeoutMs = SYSTEM_AI_TIMEOUT_MS) {
403
407
  const profiles = systemAiProfiles(config, true);
404
408
  if (!profiles.length) {
405
409
  throw new SystemAiError("系统 AI API 配置不完整。", "SYSTEM_AI_CONFIG_INVALID");
@@ -407,7 +411,7 @@ export async function callSystemAiTextWithFallback(prompt, config, timeoutMs = S
407
411
  const errors = [];
408
412
  for (const profile of profiles) {
409
413
  try {
410
- const text = await callSystemAiText(prompt, profile, timeoutMs);
414
+ const text = await callSystemAiText(request, profile, timeoutMs);
411
415
  if (text.trim())
412
416
  return text;
413
417
  errors.push(`${profile.source ?? "custom"}: 返回空结果`);
@@ -69,16 +69,16 @@ export async function generateWandTaskTitle(description, cwd, language = "", ai
69
69
  if (!input)
70
70
  throw new Error("没有可总结的任务描述。");
71
71
  const outputLanguage = language.trim() || "与描述相同的语言";
72
- const prompt = [
73
- "根据下面的任务描述,写一个用于任务列表的具体标题。",
74
- `使用${outputLanguage}输出。`,
75
- "只输出标题本身:一行纯文本,不要引号、不要 Markdown、不要编号、不要「标题:」这类前缀、不要任何解释。",
76
- "不超过 20 个字,必须点出描述里的具体对象或动作,不要使用「任务」「待办」「关于」「请求」等空泛词。",
77
- "",
78
- "任务描述:",
79
- input,
80
- ].join("\n");
81
- const raw = await callConfiguredAiText(prompt, cwd || process.cwd(), language, ai);
72
+ const request = {
73
+ system: [
74
+ "根据用户消息里的任务描述,写一个用于任务列表的具体标题。",
75
+ `使用${outputLanguage}输出。`,
76
+ "只输出标题本身:一行纯文本,不要引号、不要 Markdown、不要编号、不要「标题:」这类前缀、不要任何解释。",
77
+ "不超过 20 个字,必须点出描述里的具体对象或动作,不要使用「任务」「待办」「关于」「请求」等空泛词。",
78
+ ].join("\n"),
79
+ prompt: `任务描述:\n${input}`,
80
+ };
81
+ const raw = await callConfiguredAiText(request, cwd || process.cwd(), language, ai);
82
82
  const stripped = stripGeneratedTitle(raw);
83
83
  // 拿到报错文案 / 整段解释时宁可保留占位标题,也不能把垃圾写进看板。
84
84
  // 先判可信度再裁剪:截断过的长句看起来就像标题,会绕过长度检查。
package/dist/types.d.ts CHANGED
@@ -57,8 +57,6 @@ export interface CardExpandDefaults {
57
57
  terminal?: boolean;
58
58
  /** Thinking blocks (default: false) */
59
59
  thinking?: boolean;
60
- /** Tool groups (default: false) */
61
- toolGroup?: boolean;
62
60
  }
63
61
  export interface AndroidApkConfig {
64
62
  enabled?: boolean;
@@ -363,6 +361,11 @@ export interface CommandRequest {
363
361
  cwd?: string;
364
362
  mode?: ExecutionMode;
365
363
  initialInput?: string;
364
+ /**
365
+ * 会话级系统提示(角色、规则):走 provider 自己的系统提示开关,
366
+ * 不要拼进 initialInput。见 AGENTS.md「Session 输入契约」。
367
+ */
368
+ systemPrompt?: string;
366
369
  worktreeEnabled?: boolean;
367
370
  /** 模型(别名或完整 ID)。留空则按 provider 回落到服务端默认模型。 */
368
371
  model?: string;
@@ -514,9 +517,24 @@ export interface ToolResultBlock {
514
517
  __subagent?: SubagentMeta;
515
518
  }
516
519
  export type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock;
520
+ /** 群聊(AI 团队)里的发言人;普通会话没有,按 role 用默认人设。 */
521
+ export interface ConversationAuthor {
522
+ /** 团队成员 id;用户发言为 "user"。 */
523
+ id: string;
524
+ name: string;
525
+ /** 成员头像:上传的 data URL、"cat:<n>" 指定毛色,或空(按 id 哈希毛色)。 */
526
+ avatar?: string;
527
+ leader?: boolean;
528
+ provider?: SessionProvider;
529
+ /** 这条发言对应的成员会话,可点开看完整过程。 */
530
+ sessionId?: string;
531
+ }
517
532
  export interface ConversationTurn {
518
533
  role: "user" | "assistant";
519
534
  content: ContentBlock[];
535
+ author?: ConversationAuthor;
536
+ /** 群聊里的进度提示(派工、开始、出错):渲染成居中的一行,不是气泡。 */
537
+ notice?: boolean;
520
538
  /** ISO time when this turn was first recorded (user send / assistant start). */
521
539
  createdAt?: string;
522
540
  /** ISO time when an assistant turn finished streaming. */
@@ -550,6 +568,11 @@ export interface SessionSnapshot {
550
568
  sessionSource?: SessionSource;
551
569
  /** 自动化创建会话时关联的自动化任务 ID。 */
552
570
  automationId?: string;
571
+ /**
572
+ * 会话级系统提示:自动化 / 团队会话的角色与规则放这里,走 provider 的系统提示通道,
573
+ * 不要拼进首条用户消息。provider 没有该通道时(codex / opencode)由调用方退回并入消息。
574
+ */
575
+ systemPrompt?: string | null;
553
576
  /** 所属工作空间 ID(多标签 / 分屏项目)。会话在该工作空间窗口内作为一个标签。 */
554
577
  workspaceId?: string;
555
578
  /** 所属工作空间任务 ID;任务独占一个 worktree,其下所有会话共享该 worktree 目录。 */
@@ -22,6 +22,7 @@ export declare function taskAutoNameSignature(source: string): string;
22
22
  export declare function ensureBoardTaskForWorkspaceTask(storage: WandStorage, task: WorkspaceTask, workspace: Workspace, options?: {
23
23
  titleSource?: WandTaskTitleSource;
24
24
  description?: string;
25
+ parentTaskId?: string | null;
25
26
  }): WandTask;
26
27
  /** A board task always has a sidebar container, even before its first session. */
27
28
  export declare function ensureWorkspaceTaskForBoardTask(storage: WandStorage, card: WandTask): WorkspaceTask;
@@ -112,6 +112,7 @@ export function ensureBoardTaskForWorkspaceTask(storage, task, workspace, option
112
112
  return storage.createWandTask({
113
113
  workspaceId: workspace.kind === "global" ? null : workspace.id,
114
114
  workspaceTaskId: task.id,
115
+ parentTaskId: options.parentTaskId ?? null,
115
116
  title: task.name,
116
117
  // 占位名(未命名任务 / 新任务 / 空)走自动命名;用户填过名就是 user,永不被模型覆盖。
117
118
  titleSource: options.titleSource ?? (isUnnamedWorkspaceTaskName(task.name) ? "auto" : "user"),