@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.
- package/dist/agent-dispatch.d.ts +46 -0
- package/dist/agent-dispatch.js +84 -0
- package/dist/ai-team-availability.d.ts +69 -0
- package/dist/ai-team-availability.js +100 -0
- package/dist/ai-team-prompts.d.ts +113 -0
- package/dist/ai-team-prompts.js +435 -0
- package/dist/ai-team-runner.d.ts +241 -0
- package/dist/ai-team-runner.js +1370 -0
- package/dist/ai-team-types.d.ts +147 -0
- package/dist/ai-team-types.js +32 -0
- package/dist/attention.d.ts +40 -0
- package/dist/attention.js +86 -0
- package/dist/build-info.json +4 -4
- package/dist/claude-sdk-runner.d.ts +5 -0
- package/dist/claude-sdk-runner.js +2 -1
- package/dist/commit-task-archive.d.ts +3 -0
- package/dist/commit-task-archive.js +43 -0
- package/dist/config.js +0 -2
- package/dist/git-quick-commit.d.ts +2 -1
- package/dist/git-quick-commit.js +72 -60
- package/dist/message-truncator.d.ts +41 -5
- package/dist/message-truncator.js +255 -38
- package/dist/missions.d.ts +4 -2
- package/dist/missions.js +2 -2
- package/dist/process-manager.d.ts +1 -0
- package/dist/process-manager.js +17 -8
- package/dist/prompt-optimizer.js +13 -13
- package/dist/server-ai-team-routes.d.ts +11 -0
- package/dist/server-ai-team-routes.js +283 -0
- package/dist/server-attention-routes.d.ts +9 -0
- package/dist/server-attention-routes.js +26 -0
- package/dist/server-file-routes.js +1 -1
- package/dist/server-session-routes.js +41 -5
- package/dist/server-settings-routes.js +1 -1
- package/dist/server-task-routes.d.ts +2 -0
- package/dist/server-task-routes.js +6 -40
- package/dist/server-workspace-routes.js +13 -5
- package/dist/server.js +43 -0
- package/dist/session-topic.js +13 -13
- package/dist/session-transport.d.ts +3 -0
- package/dist/session-transport.js +1 -0
- package/dist/storage.d.ts +54 -1
- package/dist/storage.js +413 -2
- package/dist/structured-claude-adapter.d.ts +5 -0
- package/dist/structured-claude-adapter.js +13 -2
- package/dist/structured-claude-protocol.d.ts +2 -0
- package/dist/structured-claude-protocol.js +4 -0
- package/dist/structured-codex-adapter.js +2 -2
- package/dist/structured-grok-adapter.js +2 -1
- package/dist/structured-opencode-adapter.js +2 -2
- package/dist/structured-pi-adapter.js +2 -1
- package/dist/structured-provider-common.d.ts +17 -0
- package/dist/structured-provider-common.js +52 -0
- package/dist/structured-qoder-adapter.js +2 -1
- package/dist/structured-session-manager.d.ts +18 -1
- package/dist/structured-session-manager.js +67 -10
- package/dist/system-ai.d.ts +13 -2
- package/dist/system-ai.js +9 -5
- package/dist/task-title.js +10 -10
- package/dist/types.d.ts +25 -2
- package/dist/wand-task-sync.d.ts +1 -0
- package/dist/wand-task-sync.js +1 -0
- package/dist/web-ui/content/ai-teams.js +12 -0
- package/dist/web-ui/content/scripts.js +86 -5301
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -0
- package/dist/web-ui/embedded-assets.js +3 -2
- package/dist/web-ui/scripts.d.ts +8 -0
- package/dist/web-ui/scripts.js +28 -2
- package/dist/ws-broadcast.js +52 -19
- package/package.json +1 -1
|
@@ -22,56 +22,272 @@ const MESSAGE_WINDOW_SIZE = 40;
|
|
|
22
22
|
*/
|
|
23
23
|
const MESSAGE_BLOCK_WINDOW = 60;
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* 客户端先按块翻完这条 turn 的头部,再按 turn 往前翻更早的整条。
|
|
25
|
+
* 首次下发(首屏)的载荷上限:截断后的聊天 JSON 体积。整段历史在这个体积内就不做窗口化 ——
|
|
26
|
+
* 一条提示词 + 一段长回复的会话不该因为几十个默认收起的工具块就出现「更早消息」。
|
|
27
|
+
* 超出的会话才按下面的预算从尾部切:客户端先翻这条 turn 的头部块,再按 turn 往前翻。
|
|
29
28
|
*/
|
|
30
|
-
export
|
|
29
|
+
export const MESSAGE_FIRST_PAINT_BYTES = 1024 * 1024;
|
|
30
|
+
/**
|
|
31
|
+
* 吸附回退的额外载荷上限:折叠段往回吃掉的体积超过它就改为跳过整段,
|
|
32
|
+
* 而不是把整段拉进首屏(窗口的目标是压住首屏载荷,吸附不能反过来把它撑大)。
|
|
33
|
+
*/
|
|
34
|
+
const MAX_BLOCK_SNAP_BACK_BYTES = 128 * 1024;
|
|
35
|
+
/** 每个块在客户端是否「默认收起」(思考块 + 默认收起的工具卡片,与传输截断同一口径)。 */
|
|
36
|
+
export function collapsedBlockFlags(content, cardDefaults) {
|
|
37
|
+
const toolNameById = new Map();
|
|
38
|
+
for (const block of content) {
|
|
39
|
+
if (block.type === "tool_use")
|
|
40
|
+
toolNameById.set(block.id, block.name);
|
|
41
|
+
}
|
|
42
|
+
return content.map((block) => {
|
|
43
|
+
switch (block.type) {
|
|
44
|
+
case "thinking":
|
|
45
|
+
return cardDefaults.thinking !== true;
|
|
46
|
+
case "tool_use":
|
|
47
|
+
return isToolDefaultCollapsed(block.name, cardDefaults);
|
|
48
|
+
case "tool_result":
|
|
49
|
+
return isToolDefaultCollapsed(toolNameById.get(block.tool_use_id) ?? "", cardDefaults);
|
|
50
|
+
default:
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 单块在传输层的大致字节数(与 truncateMessagesForTransport 的截断口径一致,
|
|
57
|
+
* 否则「体积预算」会把已经截断的大块算得过重)。
|
|
58
|
+
*/
|
|
59
|
+
function blockTransportBytes(block, cardDefaults, toolNameById) {
|
|
60
|
+
switch (block.type) {
|
|
61
|
+
case "text":
|
|
62
|
+
return block.text.length;
|
|
63
|
+
case "thinking":
|
|
64
|
+
return block.thinking.length;
|
|
65
|
+
case "tool_use": {
|
|
66
|
+
const use = block;
|
|
67
|
+
let bytes = use.id.length + use.name.length + 32;
|
|
68
|
+
if (use.input)
|
|
69
|
+
bytes += JSON.stringify(use.input)?.length ?? 0;
|
|
70
|
+
if (use.description)
|
|
71
|
+
bytes += use.description.length;
|
|
72
|
+
return bytes;
|
|
73
|
+
}
|
|
74
|
+
case "tool_result": {
|
|
75
|
+
const result = block;
|
|
76
|
+
const raw = getContentString(result.content);
|
|
77
|
+
const collapsed = isToolDefaultCollapsed(toolNameById.get(result.tool_use_id) ?? "", cardDefaults);
|
|
78
|
+
const truncated = collapsed && !result.is_error &&
|
|
79
|
+
!contentHasStructuredImage(result.content) && raw.length > TRUNCATION_THRESHOLD;
|
|
80
|
+
return truncated ? SUMMARY_LENGTH + 4 : raw.length;
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 一段内容在传输层的估算体积。`limit` 用来提前收尾:一旦确认超过它就不再往下算
|
|
88
|
+
* (预算判定只需要知道「超了」,不需要精确值)。
|
|
89
|
+
*/
|
|
90
|
+
export function contentTransportBytes(content, cardDefaults, limit = Number.POSITIVE_INFINITY) {
|
|
91
|
+
const toolNameById = new Map();
|
|
92
|
+
for (const block of content) {
|
|
93
|
+
if (block.type === "tool_use")
|
|
94
|
+
toolNameById.set(block.id, block.name);
|
|
95
|
+
}
|
|
96
|
+
let total = 0;
|
|
97
|
+
for (const block of content) {
|
|
98
|
+
total += blockTransportBytes(block, cardDefaults, toolNameById) + 32;
|
|
99
|
+
if (total > limit)
|
|
100
|
+
return total;
|
|
101
|
+
}
|
|
102
|
+
return total;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 整段历史(或一条 turn)在可见条数 / 载荷两个预算内是否放得下。
|
|
106
|
+
* 任一预算被突破就立即返回 false,不把整段内容算完。
|
|
107
|
+
*/
|
|
108
|
+
function fitsBudgets(turns, cardDefaults, visibleBudget, byteBudget) {
|
|
109
|
+
let visible = 0;
|
|
110
|
+
let bytes = 0;
|
|
111
|
+
for (let i = turns.length - 1; i >= 0; i -= 1) {
|
|
112
|
+
const content = turns[i].content;
|
|
113
|
+
visible += visibleBlockCount(content, cardDefaults, content.length);
|
|
114
|
+
if (visible > visibleBudget)
|
|
115
|
+
return false;
|
|
116
|
+
bytes += contentTransportBytes(content, cardDefaults, byteBudget - bytes);
|
|
117
|
+
if (bytes > byteBudget)
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 从尾部往前取到两个预算用完为止,返回该起点:
|
|
124
|
+
* 默认收起的工具 / 思考块不占可见条数预算(它们在客户端合并成一条折叠条),只算体积。
|
|
125
|
+
*/
|
|
126
|
+
function cutStartByBudget(content, cardDefaults, visibleBudget, byteBudget) {
|
|
127
|
+
if (content.length === 0)
|
|
128
|
+
return 0;
|
|
129
|
+
const collapsed = collapsedBlockFlags(content, cardDefaults);
|
|
130
|
+
const toolNameById = new Map();
|
|
131
|
+
for (const block of content) {
|
|
132
|
+
if (block.type === "tool_use")
|
|
133
|
+
toolNameById.set(block.id, block.name);
|
|
134
|
+
}
|
|
135
|
+
let visible = 0;
|
|
136
|
+
let bytes = 0;
|
|
137
|
+
let cut = content.length;
|
|
138
|
+
for (let i = content.length - 1; i >= 0; i -= 1) {
|
|
139
|
+
const nextVisible = visible + (collapsed[i] ? 0 : 1);
|
|
140
|
+
const nextBytes = bytes + blockTransportBytes(content[i], cardDefaults, toolNameById) + 32;
|
|
141
|
+
if (nextVisible > visibleBudget || nextBytes > byteBudget)
|
|
142
|
+
break;
|
|
143
|
+
visible = nextVisible;
|
|
144
|
+
bytes = nextBytes;
|
|
145
|
+
cut = i;
|
|
146
|
+
}
|
|
147
|
+
// 至少保留最后一块:单个巨块超预算时也得给客户端一点内容。
|
|
148
|
+
return cut >= content.length ? content.length - 1 : cut;
|
|
149
|
+
}
|
|
150
|
+
/** 段内最靠左的坐标:从 index 往回吃掉同一段折叠块。 */
|
|
151
|
+
function collapsedRunStart(collapsed, index) {
|
|
152
|
+
let start = index;
|
|
153
|
+
while (start > 0 && collapsed[start - 1])
|
|
154
|
+
start -= 1;
|
|
155
|
+
return start;
|
|
156
|
+
}
|
|
157
|
+
/** 段内最靠右的坐标:从 index 往后吃掉同一段折叠块。 */
|
|
158
|
+
function collapsedRunEnd(collapsed, index) {
|
|
159
|
+
let end = index;
|
|
160
|
+
while (end < collapsed.length && collapsed[end])
|
|
161
|
+
end += 1;
|
|
162
|
+
return end;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 把块级窗口 / 翻页的起点吸附到「语义干净」的位置:
|
|
166
|
+
*
|
|
167
|
+
* 1. 起点落在默认收起的折叠段中间 → 回到该段起点(整段入窗)。半截工具段在客户端
|
|
168
|
+
* 会渲染成一个少了前几个调用的折叠条,用户看着就是「更早消息缺了一块」。
|
|
169
|
+
* 2. 起点是配对的 tool_result(它的 tool_use 被切在前面)→ 回到 tool_use,否则客户端
|
|
170
|
+
* 顶部会冒出一张「无头结果」卡。
|
|
171
|
+
* 3. 需要往回吃掉的体积超过 MAX_BLOCK_SNAP_BACK_BYTES(或本页范围内吃不下)时不再
|
|
172
|
+
* 往回吸附,改为跳到该段之后(整段都是默认收起的内容,跳过去不会藏掉可见内容)。
|
|
173
|
+
*
|
|
174
|
+
* 返回吸附后的起点;起点越界时回退到 0(整条 turn 入窗,保证窗口不为空)。
|
|
175
|
+
*/
|
|
176
|
+
export function alignedBlockStart(content, cardDefaults, rawStart, forwardLimit = content.length) {
|
|
177
|
+
const total = content.length;
|
|
178
|
+
const start = Math.min(Math.max(rawStart, 0), total);
|
|
179
|
+
if (start <= 0 || total === 0)
|
|
180
|
+
return 0;
|
|
181
|
+
const collapsed = collapsedBlockFlags(content, cardDefaults);
|
|
182
|
+
const useIndexById = new Map();
|
|
183
|
+
content.forEach((block, index) => {
|
|
184
|
+
if (block.type === "tool_use")
|
|
185
|
+
useIndexById.set(block.id, index);
|
|
186
|
+
});
|
|
187
|
+
let cursor = start;
|
|
188
|
+
for (let guard = 0; guard <= total; guard += 1) {
|
|
189
|
+
let next = collapsed[cursor] ? collapsedRunStart(collapsed, cursor) : cursor;
|
|
190
|
+
const head = content[next];
|
|
191
|
+
if (head?.type === "tool_result") {
|
|
192
|
+
const useIndex = useIndexById.get(head.tool_use_id);
|
|
193
|
+
if (useIndex !== undefined && useIndex < next)
|
|
194
|
+
next = useIndex;
|
|
195
|
+
}
|
|
196
|
+
if (next === cursor)
|
|
197
|
+
break;
|
|
198
|
+
cursor = next;
|
|
199
|
+
}
|
|
200
|
+
const snapBackBytes = contentTransportBytes(content.slice(cursor, start), cardDefaults);
|
|
201
|
+
if (snapBackBytes <= MAX_BLOCK_SNAP_BACK_BYTES)
|
|
202
|
+
return cursor;
|
|
203
|
+
// 折叠段太长:不再往回吃,跳到该段之后,保证首屏载荷不因吸附膨胀。
|
|
204
|
+
const skipped = collapsed[cursor] ? collapsedRunEnd(collapsed, cursor) : cursor;
|
|
205
|
+
if (skipped >= total)
|
|
206
|
+
return 0;
|
|
207
|
+
// 跳不到更晚的位置(只可能还是往回吃)或超过本页末尾时保持原切点。
|
|
208
|
+
if (skipped <= start || skipped >= forwardLimit)
|
|
209
|
+
return start;
|
|
210
|
+
return skipped;
|
|
211
|
+
}
|
|
212
|
+
/** [0, end) 里用户可感知的块数(默认收起的工具 / 思考块不计入)。 */
|
|
213
|
+
export function visibleBlockCount(content, cardDefaults, end) {
|
|
214
|
+
const limit = Math.min(Math.max(end, 0), content.length);
|
|
215
|
+
const collapsed = collapsedBlockFlags(content, cardDefaults);
|
|
216
|
+
let count = 0;
|
|
217
|
+
for (let i = 0; i < limit; i += 1) {
|
|
218
|
+
if (!collapsed[i])
|
|
219
|
+
count += 1;
|
|
220
|
+
}
|
|
221
|
+
return count;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 块级窗口:默认取整段历史(只要它在预算内),超出的才从最新 turn 往回累计,
|
|
225
|
+
* 能整条放下就整条放,放不下的那条(最旧的入窗 turn)只取其尾部若干块,
|
|
226
|
+
* 并通过 leadingBlockOffset 告知客户端「这条 turn 还有更早的块」。
|
|
227
|
+
*
|
|
228
|
+
* 预算口径:`blockBudget` 数的是**用户可感知的条数**(默认收起的工具 / 思考块不计入),
|
|
229
|
+
* 另外叠加一层载荷上限 —— 否则一段长工具调用会把预算吃光,把用户自己的提示词挤出首屏,
|
|
230
|
+
* 一条提示词的会话也会莫名出现「更早消息」。
|
|
231
|
+
*/
|
|
232
|
+
export function blockWindowMessagesForTransport(all, cardDefaults, blockBudget = MESSAGE_BLOCK_WINDOW, byteBudget = MESSAGE_FIRST_PAINT_BYTES) {
|
|
31
233
|
const turns = all ?? [];
|
|
32
234
|
const total = turns.length;
|
|
33
235
|
if (total === 0) {
|
|
34
|
-
return {
|
|
236
|
+
return {
|
|
237
|
+
messages: [],
|
|
238
|
+
messageOffset: 0,
|
|
239
|
+
messageTotal: 0,
|
|
240
|
+
leadingBlockOffset: 0,
|
|
241
|
+
leadingBlockTotal: 0,
|
|
242
|
+
leadingVisibleCount: 0,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const visibleBudget = Math.max(1, blockBudget);
|
|
246
|
+
// 整段历史放得下就不做窗口化:短会话(一条提示词 + 一段长回复)不该出现「更早消息」。
|
|
247
|
+
if (fitsBudgets(turns, cardDefaults, visibleBudget, byteBudget)) {
|
|
248
|
+
const last = turns[total - 1];
|
|
249
|
+
return {
|
|
250
|
+
messages: truncateMessagesForTransport(turns, cardDefaults),
|
|
251
|
+
messageOffset: 0,
|
|
252
|
+
messageTotal: total,
|
|
253
|
+
leadingBlockOffset: 0,
|
|
254
|
+
leadingBlockTotal: last.content.length,
|
|
255
|
+
leadingVisibleCount: 0,
|
|
256
|
+
};
|
|
35
257
|
}
|
|
36
|
-
const budget = Math.max(1, blockBudget);
|
|
37
258
|
let startTurn = total - 1;
|
|
38
259
|
let leadingBlockOffset = 0;
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
startTurn = i;
|
|
51
|
-
leadingBlockOffset = n - budget;
|
|
52
|
-
acc = budget;
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
else if (acc + n <= budget) {
|
|
57
|
-
acc += n;
|
|
260
|
+
let accVisible = 0;
|
|
261
|
+
let accBytes = 0;
|
|
262
|
+
for (let i = total - 1; i >= 0; i -= 1) {
|
|
263
|
+
const content = turns[i].content;
|
|
264
|
+
const visible = visibleBlockCount(content, cardDefaults, content.length);
|
|
265
|
+
const bytes = contentTransportBytes(content, cardDefaults, byteBudget - accBytes);
|
|
266
|
+
if (accVisible + visible <= visibleBudget && accBytes + bytes <= byteBudget) {
|
|
267
|
+
accVisible += visible;
|
|
268
|
+
accBytes += bytes;
|
|
58
269
|
startTurn = i;
|
|
59
270
|
leadingBlockOffset = 0;
|
|
271
|
+
continue;
|
|
60
272
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
leadingBlockOffset = n - remaining;
|
|
66
|
-
acc += remaining;
|
|
67
|
-
}
|
|
273
|
+
// 放不下:按剩余预算切进这条 turn 的尾部;预算已耗尽则到此为止。
|
|
274
|
+
const remainVisible = visibleBudget - accVisible;
|
|
275
|
+
const remainBytes = byteBudget - accBytes;
|
|
276
|
+
if (i !== total - 1 && (remainVisible <= 0 || remainBytes <= 0))
|
|
68
277
|
break;
|
|
69
|
-
|
|
278
|
+
startTurn = i;
|
|
279
|
+
leadingBlockOffset = cutStartByBudget(content, cardDefaults, Math.max(remainVisible, 1), Math.max(remainBytes, 1));
|
|
280
|
+
break;
|
|
70
281
|
}
|
|
282
|
+
// 最旧入窗 turn 的切点吸附到干净边界:不切开折叠的工具段,也不留下无头 tool_result。
|
|
283
|
+
const headContent = turns[startTurn].content;
|
|
284
|
+
const startOffset = leadingBlockOffset > 0
|
|
285
|
+
? alignedBlockStart(headContent, cardDefaults, leadingBlockOffset)
|
|
286
|
+
: 0;
|
|
71
287
|
const windowedTurns = [];
|
|
72
288
|
for (let i = startTurn; i < total; i++) {
|
|
73
|
-
if (i === startTurn &&
|
|
74
|
-
windowedTurns.push({ ...turns[i], content: turns[i].content.slice(
|
|
289
|
+
if (i === startTurn && startOffset > 0) {
|
|
290
|
+
windowedTurns.push({ ...turns[i], content: turns[i].content.slice(startOffset) });
|
|
75
291
|
}
|
|
76
292
|
else {
|
|
77
293
|
windowedTurns.push(turns[i]);
|
|
@@ -81,8 +297,9 @@ export function blockWindowMessagesForTransport(all, cardDefaults, blockBudget =
|
|
|
81
297
|
messages: truncateMessagesForTransport(windowedTurns, cardDefaults),
|
|
82
298
|
messageOffset: startTurn,
|
|
83
299
|
messageTotal: total,
|
|
84
|
-
leadingBlockOffset,
|
|
85
|
-
leadingBlockTotal:
|
|
300
|
+
leadingBlockOffset: startOffset,
|
|
301
|
+
leadingBlockTotal: headContent.length,
|
|
302
|
+
leadingVisibleCount: visibleBlockCount(headContent, cardDefaults, startOffset),
|
|
86
303
|
};
|
|
87
304
|
}
|
|
88
305
|
/**
|
package/dist/missions.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { AgentActivityItem, CreateMissionInput, CreateReviewCommentInput, MissionDetails, MissionDiff, MissionReviewComment } from "./mission-types.js";
|
|
1
|
+
import type { AgentActivityItem, AgentActivityState, CreateMissionInput, CreateReviewCommentInput, MissionDetails, MissionDiff, MissionReviewComment } from "./mission-types.js";
|
|
2
2
|
import type { SessionRegistry } from "./session-registry.js";
|
|
3
3
|
import type { StructuredSessionManager } from "./structured-session-manager.js";
|
|
4
4
|
import type { WandStorage } from "./storage.js";
|
|
5
|
-
import type { ProcessEvent } from "./types.js";
|
|
5
|
+
import type { ConversationTurn, ProcessEvent, SessionSnapshot } from "./types.js";
|
|
6
|
+
export declare function hasUnansweredQuestion(messages: ConversationTurn[] | undefined): boolean;
|
|
7
|
+
export declare function activityState(snapshot: SessionSnapshot, event?: ProcessEvent): AgentActivityState;
|
|
6
8
|
/**
|
|
7
9
|
* Deep task-orchestration module. Callers create missions and review diffs
|
|
8
10
|
* without coordinating session/worktree/storage details.
|
package/dist/missions.js
CHANGED
|
@@ -60,7 +60,7 @@ function sessionSummary(snapshot) {
|
|
|
60
60
|
}
|
|
61
61
|
return cleanActivityText(snapshot.summary?.trim() || "", 160);
|
|
62
62
|
}
|
|
63
|
-
function hasUnansweredQuestion(messages) {
|
|
63
|
+
export function hasUnansweredQuestion(messages) {
|
|
64
64
|
if (!messages?.length || messages[messages.length - 1]?.role === "user")
|
|
65
65
|
return false;
|
|
66
66
|
const answered = new Set();
|
|
@@ -81,7 +81,7 @@ function hasUnansweredQuestion(messages) {
|
|
|
81
81
|
}
|
|
82
82
|
return false;
|
|
83
83
|
}
|
|
84
|
-
function activityState(snapshot, event) {
|
|
84
|
+
export function activityState(snapshot, event) {
|
|
85
85
|
if (snapshot.pendingEscalation || snapshot.permissionBlocked)
|
|
86
86
|
return "needs_permission";
|
|
87
87
|
if (hasUnansweredQuestion(snapshot.messages))
|
|
@@ -69,6 +69,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
69
69
|
workspaceId?: string;
|
|
70
70
|
workspaceTaskId?: string;
|
|
71
71
|
interactiveShell?: boolean;
|
|
72
|
+
systemPrompt?: string;
|
|
72
73
|
}): Promise<SessionSnapshot>;
|
|
73
74
|
list(): SessionSnapshot[];
|
|
74
75
|
/** Return lightweight snapshots for the session list (no output/messages). */
|
package/dist/process-manager.js
CHANGED
|
@@ -13,7 +13,7 @@ import { buildChildEnv, isRunningAsRoot } from "./env-utils.js";
|
|
|
13
13
|
import { buildLanguageDirective, buildManagedAutonomyDirective } from "./language-prompt.js";
|
|
14
14
|
import { prepareSessionWorktree } from "./git-worktree.js";
|
|
15
15
|
import { getProviderCommandSessionId, getProviderResumeCommandSessionId } from "./resume-policy.js";
|
|
16
|
-
import { normalizeThinkingEffort, thinkingEffortToClaudeCliEffort, thinkingEffortToClaudeSlashEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToGrokEffort, thinkingEffortToPiLevel, thinkingEffortToQoderEffort } from "./structured-provider-common.js";
|
|
16
|
+
import { commandWithSystemPrompt, composeSystemFallback, normalizeThinkingEffort, systemPromptFlag, thinkingEffortToClaudeCliEffort, thinkingEffortToClaudeSlashEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToGrokEffort, thinkingEffortToPiLevel, thinkingEffortToQoderEffort } from "./structured-provider-common.js";
|
|
17
17
|
import { consumePtyInputForTopic, createPtyTopicLineBuffer, provisionalSessionTopic, SessionTopicCoordinator, sessionTopicBlocklistForSnapshot, shouldAcceptGeneratedSessionTitle, shouldGenerateSessionTopicFromInput, } from "./session-topic.js";
|
|
18
18
|
import { getErrorMessage } from "./error-utils.js";
|
|
19
19
|
import { recordIterationPrompt } from "./iteration-log.js";
|
|
@@ -1034,6 +1034,12 @@ export class ProcessManager extends EventEmitter {
|
|
|
1034
1034
|
const selectedModel = opts?.model?.trim() || undefined;
|
|
1035
1035
|
const initialThinkingEffort = normalizeThinkingEffort(opts?.thinkingEffort);
|
|
1036
1036
|
let processedCommand = this.processCommandForMode(command, effectiveMode, provider, selectedModel, initialThinkingEffort);
|
|
1037
|
+
// 会话级系统提示:provider 有系统提示开关就走开关(PTY 与结构化行为一致),没有就并进首条输入。
|
|
1038
|
+
const systemPrompt = opts?.systemPrompt?.trim() || undefined;
|
|
1039
|
+
const initialInputText = initialInput && systemPrompt && !systemPromptFlag(provider)
|
|
1040
|
+
? composeSystemFallback(systemPrompt, initialInput)
|
|
1041
|
+
: initialInput;
|
|
1042
|
+
processedCommand = commandWithSystemPrompt(processedCommand, provider, systemPrompt);
|
|
1037
1043
|
const isCodexProvider = provider === "codex";
|
|
1038
1044
|
const isOpenCodeProvider = provider === "opencode";
|
|
1039
1045
|
const existingProviderSessionId = provider
|
|
@@ -1069,6 +1075,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1069
1075
|
id,
|
|
1070
1076
|
sessionSource: opts?.sessionSource ?? inheritedSessionSource ?? "interactive",
|
|
1071
1077
|
automationId: opts?.automationId ?? inheritedAutomationId,
|
|
1078
|
+
systemPrompt: systemPrompt ?? null,
|
|
1072
1079
|
workspaceId: opts?.workspaceId ?? inheritedWorkspaceId,
|
|
1073
1080
|
workspaceTaskId: opts?.workspaceTaskId ?? inheritedWorkspaceTaskId,
|
|
1074
1081
|
provider,
|
|
@@ -1189,7 +1196,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1189
1196
|
record.status = "running";
|
|
1190
1197
|
let initialInputSent = false;
|
|
1191
1198
|
const sendInitialInput = () => {
|
|
1192
|
-
if (initialInputSent || !
|
|
1199
|
+
if (initialInputSent || !initialInputText)
|
|
1193
1200
|
return;
|
|
1194
1201
|
initialInputSent = true;
|
|
1195
1202
|
const current = this.sessions.get(id);
|
|
@@ -1197,20 +1204,20 @@ export class ProcessManager extends EventEmitter {
|
|
|
1197
1204
|
process.stderr.write(`[wand] Cannot send initial input: session not ready\n`);
|
|
1198
1205
|
return;
|
|
1199
1206
|
}
|
|
1200
|
-
process.stderr.write(`[wand] Sending initial input (${
|
|
1207
|
+
process.stderr.write(`[wand] Sending initial input (${initialInputText.length} chars)\n`);
|
|
1201
1208
|
if (current.ptyBridge) {
|
|
1202
|
-
current.ptyBridge.onUserInput(
|
|
1209
|
+
current.ptyBridge.onUserInput(initialInputText);
|
|
1203
1210
|
}
|
|
1204
|
-
child.write(
|
|
1211
|
+
child.write(initialInputText);
|
|
1205
1212
|
child.write("\r");
|
|
1206
1213
|
};
|
|
1207
1214
|
this.bindTerminalProcess(record, child, (chunk) => {
|
|
1208
|
-
if (
|
|
1215
|
+
if (initialInputText && !initialInputSent && (chunk.includes("❯") || chunk.includes("›")))
|
|
1209
1216
|
sendInitialInput();
|
|
1210
1217
|
});
|
|
1211
1218
|
this.emitEvent({ type: "started", sessionId: id, data: this.snapshot(record) });
|
|
1212
|
-
if (
|
|
1213
|
-
this.maybeGenerateSessionTopic(id,
|
|
1219
|
+
if (initialInputText)
|
|
1220
|
+
this.maybeGenerateSessionTopic(id, initialInputText);
|
|
1214
1221
|
if (launchPlan.commandToWrite) {
|
|
1215
1222
|
child.write(launchPlan.commandToWrite);
|
|
1216
1223
|
child.write("\r");
|
|
@@ -1893,6 +1900,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
1893
1900
|
sessionKind: "pty",
|
|
1894
1901
|
sessionSource: record.sessionSource ?? "interactive",
|
|
1895
1902
|
automationId: record.automationId,
|
|
1903
|
+
// 会话级系统提示要跟着快照回库(persist 就是走 snapshot),否则重启后 resume 会丢掉角色与规则。
|
|
1904
|
+
systemPrompt: record.systemPrompt ?? null,
|
|
1896
1905
|
provider: record.provider,
|
|
1897
1906
|
providerCliActive: record.providerCliActive,
|
|
1898
1907
|
providerCliExitCode: record.providerCliExitCode,
|
package/dist/prompt-optimizer.js
CHANGED
|
@@ -10,17 +10,17 @@ export class PromptOptimizeError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
function buildOptimizePrompt(userInput, language) {
|
|
12
12
|
const lang = (language || "").trim() || "中文";
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
userInput
|
|
23
|
-
|
|
13
|
+
return {
|
|
14
|
+
system: [
|
|
15
|
+
`你是一名提示词优化助手。请把用户写给编码 AI 的「原始提示词」改写得更清晰、结构化、可执行,便于 AI 理解并完成任务。`,
|
|
16
|
+
`要求:`,
|
|
17
|
+
`1. 保留用户原意和所有关键信息(文件路径、变量名、技术名词、数字、约束等),不要删减事实,也不要新增臆测的需求。`,
|
|
18
|
+
`2. 必要时拆分为「目标 / 上下文 / 约束 / 验收标准」几个部分;如果原文很短或很简单,则只做语句润色,不要硬塞结构。`,
|
|
19
|
+
`3. 用${lang}输出。语气克制专业,不寒暄、不解释你做了什么。`,
|
|
20
|
+
`4. 只输出优化后的提示词正文,不要包裹在代码块或引号里,不要加任何前后缀(比如「优化后:」之类)。`,
|
|
21
|
+
].join("\n"),
|
|
22
|
+
prompt: `原始提示词:\n${userInput}`,
|
|
23
|
+
};
|
|
24
24
|
}
|
|
25
25
|
export async function optimizePrompt(rawText, language, cwd, ai = {}) {
|
|
26
26
|
const text = (rawText || "").trim();
|
|
@@ -30,10 +30,10 @@ export async function optimizePrompt(rawText, language, cwd, ai = {}) {
|
|
|
30
30
|
if (text.length > MAX_INPUT_LENGTH) {
|
|
31
31
|
throw new PromptOptimizeError(`输入过长(${text.length} 字符),请缩短到 ${MAX_INPUT_LENGTH} 以内。`, "INPUT_TOO_LONG");
|
|
32
32
|
}
|
|
33
|
-
const
|
|
33
|
+
const request = buildOptimizePrompt(text, language);
|
|
34
34
|
let raw;
|
|
35
35
|
try {
|
|
36
|
-
raw = await callConfiguredAiText(
|
|
36
|
+
raw = await callConfiguredAiText(request, cwd ?? process.cwd(), language, ai);
|
|
37
37
|
}
|
|
38
38
|
catch (error) {
|
|
39
39
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
import { type AiTeamRunner } from "./ai-team-runner.js";
|
|
3
|
+
import { type AiTeam } from "./ai-team-types.js";
|
|
4
|
+
import type { WandStorage } from "./storage.js";
|
|
5
|
+
/** 团队页「直接开工」自动建卡的标记(§4.2 / Q3):看板靠它区分团队卡与手写卡。 */
|
|
6
|
+
export declare const TEAM_DIRECT_LABEL = "team_direct";
|
|
7
|
+
export declare function parseAiTeamInput(value: unknown, existing: AiTeam | null, now: string): AiTeam;
|
|
8
|
+
export declare function registerAiTeamRoutes(app: Express, deps: {
|
|
9
|
+
storage: WandStorage;
|
|
10
|
+
runner: AiTeamRunner;
|
|
11
|
+
}): void;
|