@baique/pi-web-sky 0.1.56 → 0.1.57
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +5 -5
- package/.next/build-manifest.json +3 -3
- package/.next/react-loadable-manifest.json +2 -2
- package/.next/required-server-files.js +3 -2
- package/.next/required-server-files.json +3 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +3 -3
- package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/bash-output/route.js +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/running/route.js +1 -1
- package/.next/server/app/api/app-update/route.js +1 -1
- package/.next/server/app/api/board-assets/[...path]/route.js +1 -1
- package/.next/server/app/api/board-assets/route.js +1 -1
- package/.next/server/app/api/boards/[id]/add-session/route.js +1 -1
- package/.next/server/app/api/boards/[id]/reconcile/route.js +1 -1
- package/.next/server/app/api/boards/[id]/route.js +1 -1
- package/.next/server/app/api/boards/reorder/route.js +1 -1
- package/.next/server/app/api/boards/route.js +1 -1
- package/.next/server/app/api/cwd/validate/route.js +1 -1
- package/.next/server/app/api/default-cwd/route.js +1 -1
- package/.next/server/app/api/file-index/route.js +1 -1
- package/.next/server/app/api/files/[...path]/route.js +2 -2
- package/.next/server/app/api/mcp/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/project-trust/route.js +1 -1
- package/.next/server/app/api/search/route.js +2 -2
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +2 -2
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +2 -2
- package/.next/server/app/api/sessions/[id]/export/route.js +2 -2
- package/.next/server/app/api/sessions/[id]/minimap/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/todos/route.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/summary/route.js +1 -1
- package/.next/server/app/api/skills/install/route.js +1 -1
- package/.next/server/app/api/subagents/[id]/route.js +1 -1
- package/.next/server/app/api/subagents/profiles/route.js +1 -1
- package/.next/server/app/api/task-card-questions/[id]/answer/route.js +1 -1
- package/.next/server/app/api/task-card-questions/route.js +1 -1
- package/.next/server/app/api/task-cards/[id]/route.js +1 -1
- package/.next/server/app/api/task-cards/route.js +1 -1
- package/.next/server/app/api/task-cards/unbind/route.js +1 -1
- package/.next/server/app/api/task-scheduler/status/route.js +1 -1
- package/.next/server/app/api/tasks/[id]/assign-session/route.js +2 -12
- package/.next/server/app/api/tasks/[id]/board/route.js +1 -1
- package/.next/server/app/api/tasks/[id]/route.js +1 -1
- package/.next/server/app/api/tasks/reorder/route.js +2 -12
- package/.next/server/app/api/tasks/route.js +2 -12
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +5 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page.js +10 -10
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +5 -5
- package/.next/server/chunks/1937.js +14 -13
- package/.next/server/chunks/2524.js +2 -2
- package/.next/server/chunks/3500.js +1 -1
- package/.next/server/chunks/402.js +21 -10
- package/.next/server/chunks/4457.js +3 -13
- package/.next/server/chunks/542.js +8 -8
- package/.next/server/chunks/5794.js +1 -1
- package/.next/server/chunks/5940.js +1 -1
- package/.next/server/chunks/6429.js +4 -4
- package/.next/server/chunks/6525.js +18 -7
- package/.next/server/chunks/6583.js +12 -11
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/752.bafff304e6b00987.js +1 -0
- package/.next/static/chunks/8088.39866dc5f1d15868.js +1 -0
- package/.next/static/chunks/app/{layout-87087b955e0c9507.js → layout-fae2b226b3c78448.js} +1 -1
- package/.next/static/chunks/app/page-d5af3bd535589daf.js +256 -0
- package/.next/static/chunks/{webpack-4de8e8dd9f2f3173.js → webpack-1ce35e1249cfd931.js} +1 -1
- package/.next/static/css/{c6d32b6031181be7.css → c7d127e07b1a786c.css} +1 -1
- package/.next/trace +8 -7
- package/.next/trace-build +1 -1
- package/lib/board-reconcile.test.mjs +26 -8
- package/lib/board-reconcile.ts +3 -1
- package/lib/board-store.test.mjs +2 -2
- package/lib/ime-guard.test.mjs +52 -0
- package/lib/ime-guard.ts +54 -0
- package/lib/rpc-manager.activity.test.mjs +223 -0
- package/lib/rpc-manager.test.mjs +22 -0
- package/lib/rpc-manager.title.test.mjs +152 -0
- package/lib/rpc-manager.ts +95 -8
- package/lib/session-activity.test.mjs +123 -0
- package/lib/session-activity.ts +77 -0
- package/lib/session-delete.test.mjs +45 -41
- package/lib/session-delete.ts +8 -34
- package/lib/session-index-scanner.test.mjs +364 -2
- package/lib/session-index-scanner.ts +162 -57
- package/lib/session-reader.noscan.test.mjs +371 -0
- package/lib/session-reader.project.test.mjs +30 -52
- package/lib/session-reader.test.mjs +2 -65
- package/lib/session-reader.ts +177 -213
- package/lib/session-scanner.test.mjs +151 -38
- package/lib/session-scanner.ts +140 -61
- package/lib/session-search.ts +6 -5
- package/lib/sqlite-db.test.mjs +26 -0
- package/lib/sqlite-db.ts +10 -1
- package/lib/subagent-runtime.ts +42 -0
- package/lib/subagent-session-title.test.mjs +120 -0
- package/lib/task-scheduler.test.mjs +27 -0
- package/lib/task-scheduler.ts +8 -1
- package/lib/task-store.activity.test.mjs +141 -0
- package/lib/task-store.meta-row.test.mjs +25 -5
- package/lib/task-store.partial-row.test.mjs +198 -0
- package/lib/task-store.subtree.test.mjs +172 -0
- package/lib/task-store.test.mjs +46 -20
- package/lib/task-store.ts +344 -68
- package/next.config.ts +3 -1
- package/package.json +1 -1
- package/.next/static/chunks/752.adce8bc3ebbaf4b6.js +0 -1
- package/.next/static/chunks/8088.0cad1ff2e09fd8ff.js +0 -1
- package/.next/static/chunks/app/page-efca6bf51a5f8ccb.js +0 -256
- /package/.next/static/{k8ZFqwz7d-bnK0ZiUVgCc → MPBMmQqmDG8zo8tM_bkDy}/_buildManifest.js +0 -0
- /package/.next/static/{k8ZFqwz7d-bnK0ZiUVgCc → MPBMmQqmDG8zo8tM_bkDy}/_ssgManifest.js +0 -0
- /package/lib/{subagent-isolation.test.mjs → worktree-isolation.test.mjs} +0 -0
package/lib/task-store.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
2
|
import { getDb } from "./sqlite-db";
|
|
3
3
|
import { deleteBoardCascade, renameTaskBoard } from "./board-store";
|
|
4
|
+
import { projectIdentityKey } from "./project-identity";
|
|
5
|
+
import { scanOneSessionHead } from "./session-scanner";
|
|
6
|
+
import { resolveProject } from "./worktree";
|
|
4
7
|
|
|
5
8
|
export interface Task {
|
|
6
9
|
id: string;
|
|
@@ -60,6 +63,60 @@ export function listAllTaskSessionIds(): Set<string> {
|
|
|
60
63
|
return new Set(rows.map((r) => r.session_id));
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
/** 一次全表读出的父子链(parentOf 上溯、childrenOf 下探)。
|
|
67
|
+
* updateTask 的多个根共用同一份快照——否则每个入参 id 都调一次 listDescendantIds,
|
|
68
|
+
* 每次一条全表 SELECT(O(成员数 × 全表))。 */
|
|
69
|
+
function readParentLinks(): { parentOf: Map<string, string | null>; childrenOf: Map<string, string[]> } {
|
|
70
|
+
const parentOf = new Map<string, string | null>();
|
|
71
|
+
const childrenOf = new Map<string, string[]>();
|
|
72
|
+
for (const r of getDb().prepare("SELECT session_id, parent_id FROM session_meta").all() as Array<{ session_id: string; parent_id: string | null }>) {
|
|
73
|
+
parentOf.set(r.session_id, r.parent_id);
|
|
74
|
+
if (r.parent_id !== null) {
|
|
75
|
+
const arr = childrenOf.get(r.parent_id) ?? [];
|
|
76
|
+
arr.push(r.session_id);
|
|
77
|
+
childrenOf.set(r.parent_id, arr);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { parentOf, childrenOf };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 按 childrenOf 递归收集后代 id(不含自身)。广度优先、visited 去重防环。 */
|
|
84
|
+
function collectDescendants(childrenOf: Map<string, string[]>, sessionId: string): string[] {
|
|
85
|
+
const out: string[] = [];
|
|
86
|
+
const seen = new Set<string>([sessionId]);
|
|
87
|
+
const queue = [...(childrenOf.get(sessionId) ?? [])];
|
|
88
|
+
while (queue.length) {
|
|
89
|
+
const cur = queue.shift()!;
|
|
90
|
+
if (seen.has(cur)) continue;
|
|
91
|
+
seen.add(cur);
|
|
92
|
+
out.push(cur);
|
|
93
|
+
queue.push(...(childrenOf.get(cur) ?? []));
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 按 parent_id 递归收集后代 id(不含自身)。 */
|
|
99
|
+
export function listDescendantIds(sessionId: string): string[] {
|
|
100
|
+
return collectDescendants(readParentLinks().childrenOf, sessionId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** 成员集合归一化:若某成员在库里还有祖先、而该祖先不在集合里,则它也不能留
|
|
104
|
+
* (归属按整棵子树存,父子不跨任务)。循环到不动点,兼容多层子树。
|
|
105
|
+
* 祖先行已不存在(外部删除留下的悬空 parent_id)不算「祖先在库内」——此时它
|
|
106
|
+
* 就是本任务的根,剪掉会把合法成员误踢出任务。就地修改传入的集合。 */
|
|
107
|
+
function normalizeMemberSet(members: Set<string>, parentOf: Map<string, string | null>): void {
|
|
108
|
+
let pruned = true;
|
|
109
|
+
while (pruned) {
|
|
110
|
+
pruned = false;
|
|
111
|
+
for (const sessionId of members) {
|
|
112
|
+
const parent = parentOf.get(sessionId);
|
|
113
|
+
if (!parent || members.has(parent) || !parentOf.has(parent)) continue;
|
|
114
|
+
members.delete(sessionId);
|
|
115
|
+
pruned = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
63
120
|
function getTaskRow(id: string): TaskRow | undefined {
|
|
64
121
|
return getDb()
|
|
65
122
|
.prepare(
|
|
@@ -120,10 +177,20 @@ export function createTask(projectKey: string, name: string): Task {
|
|
|
120
177
|
}
|
|
121
178
|
|
|
122
179
|
/**
|
|
123
|
-
* Rename and/or replace task membership. `sessionIds` is a full-replace list
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
180
|
+
* Rename and/or replace task membership. `sessionIds` is a full-replace list of
|
|
181
|
+
* subtree roots: each id and all of its descendants (session_meta.parent_id) are
|
|
182
|
+
* added; members whose ancestor falls outside that closure fall back to temp
|
|
183
|
+
* (NULL). Membership is stored per subtree, so a PATCH whose roots close over
|
|
184
|
+
* another task's nodes moves them here (子树压过单节点归属):两个任务的闭包重叠时,
|
|
185
|
+
* 以最后一个 PATCH 的闭包为准,而不是「永不碰其它任务」。
|
|
186
|
+
*
|
|
187
|
+
* 不变量:子会话不能单独移出任务——祖先仍是本任务成员时,子会话就保持在任务内
|
|
188
|
+
* (提交 [根] 时闭包会连带子孙;宁可不对「单独移出子会话」作出响应,也不让聊天区
|
|
189
|
+
* 出现父不在、子单飞的孤儿根)。
|
|
190
|
+
*
|
|
191
|
+
* 「移出任务」由这里的成员规范化承担:提交的剩余成员列表里,祖先已不在集合的成员
|
|
192
|
+
* 一律被剪掉(整棵子树一起走),不需要单独的 unassign 子树入口。
|
|
193
|
+
* Returns null when the task does not exist.
|
|
127
194
|
*/
|
|
128
195
|
export function updateTask(
|
|
129
196
|
id: string,
|
|
@@ -157,22 +224,26 @@ export function updateTask(
|
|
|
157
224
|
);
|
|
158
225
|
}
|
|
159
226
|
if (patch.sessionIds !== undefined) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
"INSERT INTO session_meta (session_id, task_id, updated) VALUES (?, ?, ?) " +
|
|
165
|
-
"ON CONFLICT(session_id) DO UPDATE SET task_id = excluded.task_id, updated = excluded.updated",
|
|
166
|
-
);
|
|
167
|
-
const unassign = db.prepare(
|
|
168
|
-
"UPDATE session_meta SET task_id = NULL, updated = ? WHERE session_id = ?",
|
|
169
|
-
);
|
|
227
|
+
// 入参是「根 id 列表」的闭包视图:加/减都以整棵子树为单位,避免子会话留在任务里当孤儿根。
|
|
228
|
+
// 父子链只读一次(多个根的展开 + 归一化共用同一份快照)。
|
|
229
|
+
const { parentOf, childrenOf } = readParentLinks();
|
|
230
|
+
const next = new Set<string>();
|
|
170
231
|
for (const sessionId of patch.sessionIds) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
for (const sessionId of current) {
|
|
174
|
-
if (!next.has(sessionId)) unassign.run(ts, sessionId);
|
|
232
|
+
next.add(sessionId);
|
|
233
|
+
for (const d of collectDescendants(childrenOf, sessionId)) next.add(d);
|
|
175
234
|
}
|
|
235
|
+
// 归一化:入参里「祖先已被剔除」的成员不能留。前端拖出父会话时提交的是
|
|
236
|
+
// flat 剩余成员列表(仍含子会话 id),不归一化就会父走子留、子会话在任务区当根。
|
|
237
|
+
normalizeMemberSet(next, parentOf);
|
|
238
|
+
const current = new Set(listTaskSessionIds(id));
|
|
239
|
+
const ts = now();
|
|
240
|
+
// 加侧只 UPDATE:行由创建链路(ensureSessionMetaRow)/扫描器/路由层
|
|
241
|
+
// (ensureSessionRows)建全列行。此处不能 INSERT——同步事务里 await 不了文件 I/O,
|
|
242
|
+
// 而插缺列局部行(缺 path/project_key/created)会让会话从聊天区消失、任务区显示 1970。
|
|
243
|
+
const assign = db.prepare("UPDATE session_meta SET task_id = ?, updated = ? WHERE session_id = ?");
|
|
244
|
+
const unassign = db.prepare("UPDATE session_meta SET task_id = NULL, updated = ? WHERE session_id = ?");
|
|
245
|
+
for (const sessionId of next) if (!current.has(sessionId)) assign.run(id, ts, sessionId);
|
|
246
|
+
for (const sessionId of current) if (!next.has(sessionId)) unassign.run(ts, sessionId);
|
|
176
247
|
db.prepare("UPDATE tasks SET updated = ? WHERE id = ?").run(ts, id);
|
|
177
248
|
}
|
|
178
249
|
db.exec("COMMIT");
|
|
@@ -256,15 +327,13 @@ export function taskNameForSession(sessionId: string): string | null {
|
|
|
256
327
|
return row?.name ?? null;
|
|
257
328
|
}
|
|
258
329
|
|
|
259
|
-
/**
|
|
260
|
-
* 把一个会话原子归属到任务(任务不存在返回 false)。会刷新任务的 updated
|
|
261
|
-
* 使置顶/最近排序生效;会话原本在其他任务下则移动(ON CONFLICT 更新)。
|
|
262
|
-
* 会话创建完成时由服务端调用,避免前端两跳 PATCH 造成的“先临时区后任务”窗口。
|
|
263
|
-
*/
|
|
264
330
|
/** 新建会话落盘即建全列索引行(先于会话真正运行)。
|
|
265
331
|
* 调用方拿到的 path/cwd/project_key/parent 等此时全部已知——会话从出生就是
|
|
266
332
|
* 完整索引行,读取(列表/任务区/看板)不再依赖扫描器补列,也不退化扫盘。
|
|
267
|
-
* ON CONFLICT 只补索引列、不碰 task_id/pinned/title
|
|
333
|
+
* ON CONFLICT 只补索引列、不碰 task_id/pinned/title(归属/置顶/改名各自管);
|
|
334
|
+
* `first_message` 只补空(COALESCE)——调用方不传就保留库里已有的首条消息。
|
|
335
|
+
* `taskId` 只在 INSERT 时写入(归属继承用):已存在的行归属各自管,upsert 不覆盖。
|
|
336
|
+
* INSERT 列清单显式列全 13 列(`last_reply` 显式 NULL)——不依赖建表默认值。 */
|
|
268
337
|
export function ensureSessionMetaRow(sessionId: string, row: {
|
|
269
338
|
path: string;
|
|
270
339
|
cwd: string;
|
|
@@ -272,46 +341,154 @@ export function ensureSessionMetaRow(sessionId: string, row: {
|
|
|
272
341
|
parentId?: string;
|
|
273
342
|
firstMessage?: string;
|
|
274
343
|
title?: string;
|
|
344
|
+
taskId?: string | null;
|
|
275
345
|
}): void {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
346
|
+
getDb()
|
|
347
|
+
.prepare(
|
|
348
|
+
`INSERT INTO session_meta
|
|
349
|
+
(session_id, task_id, updated, pinned, path, cwd, project_key, title, first_message, parent_id, created, modified, last_reply)
|
|
350
|
+
VALUES (?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?, NULL)
|
|
351
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
352
|
+
path = excluded.path,
|
|
353
|
+
cwd = excluded.cwd,
|
|
354
|
+
project_key = excluded.project_key,
|
|
355
|
+
-- first_message 只补空:调用方不传(undefined)时不再把已有的首条消息擦成 NULL。
|
|
356
|
+
first_message = COALESCE(session_meta.first_message, excluded.first_message),
|
|
357
|
+
parent_id = excluded.parent_id,
|
|
358
|
+
created = excluded.created,
|
|
359
|
+
modified = excluded.modified`,
|
|
360
|
+
)
|
|
361
|
+
.run(
|
|
362
|
+
sessionId,
|
|
363
|
+
row.taskId ?? null,
|
|
364
|
+
now(),
|
|
365
|
+
row.path,
|
|
366
|
+
row.cwd,
|
|
367
|
+
row.projectKey,
|
|
368
|
+
row.title ?? null,
|
|
369
|
+
row.firstMessage ?? null,
|
|
370
|
+
row.parentId ?? null,
|
|
371
|
+
now(),
|
|
372
|
+
now(),
|
|
373
|
+
);
|
|
301
374
|
invalidateSessionListCache();
|
|
302
375
|
}
|
|
303
376
|
|
|
304
|
-
|
|
377
|
+
/**
|
|
378
|
+
* 给「会话已经在磁盘上、库里却还没行」的场景补一条**全列**行:定位文件 → 读 header → upsert。
|
|
379
|
+
* 先查行(有行直接返回,完全不碰文件);无行才按 id 解析文件。
|
|
380
|
+
*
|
|
381
|
+
* 为什么不能退回插「局部行」:只写 session_id/updated/title(pinned) 的行缺 path/project_key/created,
|
|
382
|
+
* 会被聊天区的 `project_key = ?` 过滤掉(会话消失到下一轮扫描)、任务区显示 1970-01-01(V4)。
|
|
383
|
+
* 同时补 `parent_id`(磁盘 header.parentSession 反查):归属守卫(hasForeignTaskAncestor)
|
|
384
|
+
* 只看库内 parent_id——补行时不写父,「库里还没行」的会话在守卫眼里就是无祖先的顶层行
|
|
385
|
+
* (带上跨任务祖先拖入别的任务会被错误放行)。反查不到父(父行未建/父文件已消失)则留空。
|
|
386
|
+
* 文件也找不到时返回 false 且报错可见——调用方保持原语义(不抛错),但绝不写半行。
|
|
387
|
+
*/
|
|
388
|
+
async function ensureRowForSession(sessionId: string): Promise<boolean> {
|
|
389
|
+
if (getDb().prepare("SELECT 1 FROM session_meta WHERE session_id = ?").get(sessionId)) return true;
|
|
390
|
+
// 动态导入避开 session-reader ↔ task-store 的循环加载(session-reader 反向动态引本模块)。
|
|
391
|
+
const { resolveSessionPath, resolveSessionIdByPath } = await import("./session-reader");
|
|
392
|
+
const filePath = await resolveSessionPath(sessionId);
|
|
393
|
+
if (!filePath) {
|
|
394
|
+
console.error(`[pi-web] 会话行补建失败:找不到会话文件 session=${sessionId}`);
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
const head = scanOneSessionHead(filePath);
|
|
398
|
+
if (!head) {
|
|
399
|
+
console.error(`[pi-web] 会话行补建失败:会话文件无有效 header session=${sessionId} path=${filePath}`);
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
const project = await resolveProject(head.cwd);
|
|
403
|
+
const parentId = head.parentSessionPath
|
|
404
|
+
? ((await resolveSessionIdByPath(head.parentSessionPath)) ?? undefined)
|
|
405
|
+
: undefined;
|
|
406
|
+
ensureSessionMetaRow(sessionId, {
|
|
407
|
+
path: filePath,
|
|
408
|
+
cwd: head.cwd,
|
|
409
|
+
projectKey: projectIdentityKey(project?.projectRoot ?? head.cwd),
|
|
410
|
+
parentId,
|
|
411
|
+
firstMessage: head.firstMessage || undefined,
|
|
412
|
+
});
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** 批量为「库内无行」的会话补全列行(PATCH 全量替换前由路由层调用)。
|
|
417
|
+
* 为什么在事务外:建行要读会话文件(`resolveSessionPath` miss 时会目录扫描),
|
|
418
|
+
* 在 `updateTask` 的事务里 await 文件 I/O 会长时间持写锁。
|
|
419
|
+
* 单个 id 失败不抛错也不阻塞其余 id(失败已由 ensureRowForSession 报错可见)。 */
|
|
420
|
+
export async function ensureSessionRows(sessionIds: string[]): Promise<void> {
|
|
421
|
+
for (const sessionId of sessionIds) {
|
|
422
|
+
try {
|
|
423
|
+
await ensureRowForSession(sessionId);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
console.error(
|
|
426
|
+
`[pi-web] 会话行补建异常 session=${sessionId}:`,
|
|
427
|
+
error instanceof Error ? error.message : String(error),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** 归属写入前的祖先守卫:该会话沿 session_meta.parent_id 上溯的任一祖先,是否带着
|
|
434
|
+
* **非空且不属于 taskId** 的归属。
|
|
435
|
+
*
|
|
436
|
+
* 为什么归属入口必须查:「归属按整棵子树存」是写库侧的不变量,但把「父在任务 A 的
|
|
437
|
+
* 会话」归到任务 B 会造出跨任务父子——而 updateTask 的成员规范化与扫描器的归属收敛
|
|
438
|
+
* 会反过来把它剪掉/拉回,两个方向互相纠正、永不收敛(看板画布落点、聊天区拖拽都是
|
|
439
|
+
* 可达入口)。上溯时用 visited 防脏环;祖先行不在库里(悬空 parent_id)不算冲突,
|
|
440
|
+
* 它就是本任务的根。
|
|
441
|
+
* 导出供路由在 assign* 拒绝后给出准确的状态码/原因(409 vs 404)。 */
|
|
442
|
+
export function hasForeignTaskAncestor(sessionId: string, taskId: string): boolean {
|
|
443
|
+
// 一次全表读同时拿 parent 链与归属(本函数在每次归属写入前跑,别读两遍库)。
|
|
444
|
+
const parentOf = new Map<string, string | null>();
|
|
445
|
+
const taskOf = new Map<string, string | null>();
|
|
446
|
+
for (const r of getDb().prepare("SELECT session_id, parent_id, task_id FROM session_meta").all() as Array<{ session_id: string; parent_id: string | null; task_id: string | null }>) {
|
|
447
|
+
parentOf.set(r.session_id, r.parent_id);
|
|
448
|
+
taskOf.set(r.session_id, r.task_id);
|
|
449
|
+
}
|
|
450
|
+
const seen = new Set<string>([sessionId]);
|
|
451
|
+
let current = parentOf.get(sessionId) ?? null;
|
|
452
|
+
while (current && !seen.has(current)) {
|
|
453
|
+
seen.add(current);
|
|
454
|
+
const ancestorTask = taskOf.get(current) ?? null;
|
|
455
|
+
if (ancestorTask !== null && ancestorTask !== taskId) return true;
|
|
456
|
+
current = parentOf.get(current) ?? null;
|
|
457
|
+
}
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* 把一个会话原子归属到任务(任务不存在返回 false)。会刷新任务的 updated
|
|
463
|
+
* 使置顶/最近排序生效;会话原本在其他任务下则移动。
|
|
464
|
+
* 会话创建完成时由服务端调用,避免前端两跳 PATCH 造成的“先临时区后任务”窗口。
|
|
465
|
+
*
|
|
466
|
+
* 无行会话先在**同一调用内**补全列行再归属(见 ensureRowForSession);补不出行返回 false,
|
|
467
|
+
* 保证「返回 true」永远等价于「归属已落库」。
|
|
468
|
+
* 祖先已属于其它任务时拒绝(返回 false + console.error,不写任何行):见 hasForeignTaskAncestor。
|
|
469
|
+
* 补行**先于**守卫:库里原本没行的会话,其父链只能靠补行(读磁盘 header)写进 parent_id,
|
|
470
|
+
* 顺序反了守卫就是盲的(无行会话会被当成无祖先的顶层行放行)。
|
|
471
|
+
*/
|
|
472
|
+
export async function assignSessionToTask(sessionId: string, taskId: string): Promise<boolean> {
|
|
305
473
|
const db = getDb();
|
|
306
474
|
const task = getTaskRow(taskId);
|
|
307
475
|
if (!task) return false;
|
|
476
|
+
// 归属不能靠「先插行为空、等扫描补列」:那正是 V4 的缺列局部行,也是 V1 的病灶。
|
|
477
|
+
if (!(await ensureRowForSession(sessionId))) return false;
|
|
478
|
+
if (hasForeignTaskAncestor(sessionId, taskId)) {
|
|
479
|
+
console.error(
|
|
480
|
+
`[pi-web] 归属被拒绝:祖先属于其它任务 session=${sessionId} task=${taskId}`,
|
|
481
|
+
);
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
308
484
|
const ts = now();
|
|
309
485
|
db.exec("BEGIN");
|
|
310
486
|
try {
|
|
311
|
-
db.prepare(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
487
|
+
db.prepare("UPDATE session_meta SET task_id = ?, updated = ? WHERE session_id = ?").run(
|
|
488
|
+
taskId,
|
|
489
|
+
ts,
|
|
490
|
+
sessionId,
|
|
491
|
+
);
|
|
315
492
|
db.prepare("UPDATE tasks SET updated = ? WHERE id = ?").run(ts, taskId);
|
|
316
493
|
db.exec("COMMIT");
|
|
317
494
|
} catch (error) {
|
|
@@ -322,24 +499,79 @@ export function assignSessionToTask(sessionId: string, taskId: string): boolean
|
|
|
322
499
|
return true;
|
|
323
500
|
}
|
|
324
501
|
|
|
325
|
-
/**
|
|
326
|
-
|
|
502
|
+
/** 归属一条会话及其整棵子树(写库先于任何 UI 刷新;一次事务)。
|
|
503
|
+
* 子孙一律跟随父:否则「父在任务、子 task_id 为空」会让子会话同时出现在
|
|
504
|
+
* 任务区(按树渲染)与聊天区(按 task_id 过滤),即 V1 的两区重复。
|
|
505
|
+
* 根会话无行时同样先补全列行(UPDATE 对无行会话影响 0 行 = 静默写 0 行)。
|
|
506
|
+
* 根的祖先已属于其它任务时拒绝(返回 false + console.error,不写任何行):否则会造出
|
|
507
|
+
* 跨任务父子,被规范化/收敛反向拉回。补行先于守卫(与 assignSessionToTask 同规则):
|
|
508
|
+
* 无行会话的父链只能靠补行写进 parent_id,守卫才有得看。 */
|
|
509
|
+
export async function assignSessionSubtreeToTask(sessionId: string, taskId: string): Promise<boolean> {
|
|
510
|
+
if (!getTaskRow(taskId)) return false;
|
|
511
|
+
if (!(await ensureRowForSession(sessionId))) return false;
|
|
512
|
+
if (hasForeignTaskAncestor(sessionId, taskId)) {
|
|
513
|
+
console.error(
|
|
514
|
+
`[pi-web] 子树归属被拒绝:祖先属于其它任务 session=${sessionId} task=${taskId}`,
|
|
515
|
+
);
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
const ts = now();
|
|
327
519
|
const db = getDb();
|
|
328
|
-
db.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
520
|
+
db.exec("BEGIN");
|
|
521
|
+
try {
|
|
522
|
+
const stmt = db.prepare("UPDATE session_meta SET task_id = ?, updated = ? WHERE session_id = ?");
|
|
523
|
+
for (const id of [sessionId, ...listDescendantIds(sessionId)]) stmt.run(taskId, ts, id);
|
|
524
|
+
db.prepare("UPDATE tasks SET updated = ? WHERE id = ?").run(ts, taskId);
|
|
525
|
+
db.exec("COMMIT");
|
|
526
|
+
} catch (error) {
|
|
527
|
+
db.exec("ROLLBACK");
|
|
528
|
+
throw error;
|
|
529
|
+
}
|
|
332
530
|
invalidateSessionListCache();
|
|
531
|
+
return true;
|
|
333
532
|
}
|
|
334
533
|
|
|
335
|
-
/**
|
|
336
|
-
*
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
534
|
+
/** 库内直接子行在磁盘上的文件路径(只用非空 path)。
|
|
535
|
+
* 给「删父会话时同步改写子行磁盘 header」用:子会话文件可能在子目录里
|
|
536
|
+
* (`<proj>/<session>/forks/*.jsonl` 等),只平铺一层 `readdirSync(dirname(父文件))`
|
|
537
|
+
* 会漏掉它们,漏斗就会「库已写祖父、磁盘仍指已删父」——下一轮扫描按磁盘把它改回 NULL。
|
|
538
|
+
* 库里有父链与 path,路径就是事实(不新写目录递归)。 */
|
|
539
|
+
export function listChildPaths(sessionId: string): string[] {
|
|
540
|
+
const rows = getDb()
|
|
541
|
+
.prepare("SELECT path FROM session_meta WHERE parent_id = ? AND path IS NOT NULL AND path != ''")
|
|
542
|
+
.all(sessionId) as Array<{ path: string }>;
|
|
543
|
+
return rows.map((row) => row.path);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** 会话被删除时,把库内直接子行的 parent_id 改写为祖父(拿不到写入 NULL)。
|
|
547
|
+
* 为什么必须同请求写库:T5 之后「根」判定全来自 session_meta.parent_id——只管磁盘
|
|
548
|
+
* header 的话,库里子行仍指向已删 id,子会话会以根行呈现(≤30s 的窗口期)。 */
|
|
549
|
+
export function reparentSessionChildren(sessionId: string, newParentId: string | null): void {
|
|
550
|
+
getDb()
|
|
551
|
+
.prepare("UPDATE session_meta SET parent_id = ? WHERE parent_id = ?")
|
|
552
|
+
.run(newParentId, sessionId);
|
|
553
|
+
invalidateSessionListCache();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Pin / unpin a session (region-relative: inside its task group, or in chat).
|
|
557
|
+
* 无行老会话先补全列行(缺列行会被聊天区 project_key 过滤掉),补不出行则不写。 */
|
|
558
|
+
export async function setSessionPinned(sessionId: string, pinned: boolean): Promise<void> {
|
|
559
|
+
if (!(await ensureRowForSession(sessionId))) return;
|
|
560
|
+
// 只写 pinned,不刷 updated——与原先 ON CONFLICT SET pinned 的语义一致
|
|
561
|
+
// (updated 是扫描器「年轻行保护」的依据,置顶不该给幽灵行续命)。
|
|
562
|
+
getDb()
|
|
563
|
+
.prepare("UPDATE session_meta SET pinned = ? WHERE session_id = ?")
|
|
564
|
+
.run(pinned ? 1 : 0, sessionId);
|
|
565
|
+
invalidateSessionListCache();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** 改名同步写 session_meta.title(列表索引的标题源)。无行老会话先补全列行
|
|
569
|
+
* (path/project_key/created 齐全),补不出行则只报错不写半行。 */
|
|
570
|
+
export async function setSessionTitle(sessionId: string, title: string): Promise<void> {
|
|
571
|
+
if (!(await ensureRowForSession(sessionId))) return;
|
|
572
|
+
getDb()
|
|
573
|
+
.prepare("UPDATE session_meta SET title = ?, updated = ? WHERE session_id = ?")
|
|
574
|
+
.run(title, now(), sessionId);
|
|
343
575
|
invalidateSessionListCache();
|
|
344
576
|
}
|
|
345
577
|
|
|
@@ -347,4 +579,48 @@ export function setSessionTitle(sessionId: string, title: string): void {
|
|
|
347
579
|
export function unassignSession(sessionId: string): void {
|
|
348
580
|
getDb().prepare("DELETE FROM session_meta WHERE session_id = ?").run(sessionId);
|
|
349
581
|
invalidateSessionListCache();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ── 事件链路写入(agent_start / agent_settled)───────────────────────────────
|
|
585
|
+
// 三个函数都**只 UPDATE、不 INSERT**:会话行由创建链路(ensureSessionMetaRow)
|
|
586
|
+
// 或扫描器建立,事件不负责补行——否则会留下缺列局部行(path/project_key 为空的
|
|
587
|
+
// 1970 行),读取期就得再回退扫盘。库里没行时静默 no-op(UPDATE 影响 0 行)。
|
|
588
|
+
|
|
589
|
+
/** last_reply 入库上限(与 types.ts 的「截断」语义一致,防大消息撑爆行)。 */
|
|
590
|
+
export const LAST_REPLY_MAX = 4000;
|
|
591
|
+
|
|
592
|
+
/** 会话活跃时间前移(agent_start:运行中会话立刻浮顶)。 */
|
|
593
|
+
export function touchSessionActivity(sessionId: string, at: number = now()): void {
|
|
594
|
+
getDb().prepare("UPDATE session_meta SET modified = ? WHERE session_id = ?").run(at, sessionId);
|
|
595
|
+
invalidateSessionListCache();
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* 一轮循环真正结束(agent_settled,含用户取消)时落最后一条 assistant 文本 + 活跃时间。
|
|
600
|
+
* 空文本(本轮只有工具调用 / 刚发出就被取消)**不清库**:退化为只刷 modified,
|
|
601
|
+
* 保留库里已有的 last_reply——空字符串不是「一条消息」。
|
|
602
|
+
*/
|
|
603
|
+
export function recordSessionOutcome(sessionId: string, outcome: { lastReply: string; at: number }): void {
|
|
604
|
+
const text = outcome.lastReply.trim();
|
|
605
|
+
if (!text) {
|
|
606
|
+
touchSessionActivity(sessionId, outcome.at);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const stored = text.length > LAST_REPLY_MAX ? text.slice(0, LAST_REPLY_MAX) : text;
|
|
610
|
+
getDb()
|
|
611
|
+
.prepare("UPDATE session_meta SET last_reply = ?, modified = ? WHERE session_id = ?")
|
|
612
|
+
.run(stored, outcome.at, sessionId);
|
|
613
|
+
invalidateSessionListCache();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** 首条用户消息回填(读取路径不再读文件,改由事件补齐;只补空行)。 */
|
|
617
|
+
export function fillFirstMessageIfEmpty(sessionId: string, firstMessage: string): void {
|
|
618
|
+
const text = firstMessage.trim();
|
|
619
|
+
if (!text) return;
|
|
620
|
+
getDb()
|
|
621
|
+
.prepare(
|
|
622
|
+
"UPDATE session_meta SET first_message = ? WHERE session_id = ? AND (first_message IS NULL OR first_message = '')",
|
|
623
|
+
)
|
|
624
|
+
.run(text, sessionId);
|
|
625
|
+
invalidateSessionListCache();
|
|
350
626
|
}
|
package/next.config.ts
CHANGED
|
@@ -23,7 +23,9 @@ const nextConfig: NextConfig = {
|
|
|
23
23
|
// platform-specific prebuilt package resolves at runtime, not bundle time.
|
|
24
24
|
"@lydell/node-pty",
|
|
25
25
|
],
|
|
26
|
-
|
|
26
|
+
// 局域网调试:dev 模式下 Next 会拦跨源 dev 资源(JS chunk/HMR),导致从局域网 IP 打开时白屏。
|
|
27
|
+
// 覆盖本机常见网段:127.0.0.1(回环)、192.168.*(家用/办公常见)、172.18.*(本机 en0 所在网段)。
|
|
28
|
+
allowedDevOrigins: ["127.0.0.1", "192.168.*.*", "172.18.*.*"],
|
|
27
29
|
async headers() {
|
|
28
30
|
return [
|
|
29
31
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@baique/pi-web-sky",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.57",
|
|
4
4
|
"description": "Web UI for the pi coding agent — Sky skin edition (fork of @agegr/pi-web / earendil-labs pi-web, MIT)",
|
|
5
5
|
"homepage": "https://github.com/baique/pi-web-sky#readme",
|
|
6
6
|
"repository": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[752],{15934:(e,t,r)=>{r.d(t,{$T:()=>d,Ho:()=>c,PH:()=>a,PK:()=>i,Po:()=>h,Qp:()=>o,RF:()=>p,Wd:()=>s,mf:()=>f,sV:()=>u,z6:()=>l});let n="pi-web:board-rename-node";function i(e,t){window.dispatchEvent(new CustomEvent("pi-web:board-open-file",{detail:{sessionId:e,filePath:t}}))}function s(e,t){window.dispatchEvent(new CustomEvent("pi-web:board-session-forked",{detail:{sessionId:e,newSessionId:t}}))}function a(e,t){window.dispatchEvent(new CustomEvent("pi-web:board-session-created",{detail:{sessionId:e,nodeId:t}}))}function o(e){window.dispatchEvent(new CustomEvent("pi-web:board-session-deleted",{detail:{sessionId:e}}))}function l(e,t){window.dispatchEvent(new CustomEvent("pi-web:board-session-renamed",{detail:{sessionId:e,name:t}}))}function d(e){window.dispatchEvent(new CustomEvent("pi-web:board-cwd-switch",{detail:{cwd:e}}))}function c(e,t){window.dispatchEvent(new CustomEvent("pi-web:board-agent-end",{detail:{sessionId:e,sessionName:t}}))}function u(e,t){window.dispatchEvent(new CustomEvent("pi-web:board-attention-needed",{detail:{sessionId:e,...t}}))}function p(){window.dispatchEvent(new CustomEvent("pi-web:board-tasks-changed",{detail:{}}))}function h(e){window.dispatchEvent(new CustomEvent(n,{detail:{nodeId:e}}))}function f(e){let t=t=>e(t.detail.nodeId);return window.addEventListener(n,t),()=>window.removeEventListener(n,t)}},24456:(e,t,r)=>{r.d(t,{l:()=>c});var n=r(95155),i=r(12115),s=r(12669);let a=null,o=null,l=null;function d(e){a?.(e),a=null,l?.unmount(),o?.remove(),l=null,o=null}function c(e){return new Promise(t=>{a&&d(!1),a=t;let r=o;r||(r=document.createElement("div"),document.body.appendChild(r),o=r);let i=l;i||(l=i=(0,s.createRoot)(r)),i.render((0,n.jsx)(u,{opts:e,onClose:d}))})}function u({opts:e,onClose:t}){let[r]=(0,i.useState)(!1);return(0,n.jsx)("div",{role:"alertdialog","aria-modal":"true",onPointerDown:()=>t(!1),style:{position:"fixed",inset:0,zIndex:2e3,display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(0,0,0,0.35)"},children:(0,n.jsxs)("div",{onPointerDown:e=>e.stopPropagation(),style:{width:360,maxWidth:"calc(100vw - 48px)",borderRadius:"var(--bubble-radius, 14px)",background:"var(--board-card-glass)",backdropFilter:"blur(var(--board-blur)) saturate(var(--glass-saturate))",WebkitBackdropFilter:"blur(var(--board-blur)) saturate(var(--glass-saturate))",border:"1px solid color-mix(in srgb, var(--border) 60%, transparent)",boxShadow:"0 10px 40px -12px rgba(0,0,0,0.4)",color:"var(--text)",padding:"16px 18px",display:"flex",flexDirection:"column",gap:10},children:[e.title&&(0,n.jsx)("div",{style:{fontSize:14,fontWeight:600},children:e.title}),(0,n.jsx)("div",{style:{fontSize:12.5,lineHeight:1.6,color:"var(--text-muted)",whiteSpace:"pre-wrap"},children:e.message}),(0,n.jsxs)("div",{style:{display:"flex",justifyContent:"flex-end",gap:8,marginTop:6},children:[(0,n.jsx)("button",{type:"button",onClick:()=>t(!1),style:{border:"none",background:"color-mix(in srgb, var(--border) 30%, transparent)",color:"var(--text-muted)",borderRadius:6,padding:"5px 14px",fontSize:12,cursor:"pointer"},children:e.cancelText??"取消"}),(0,n.jsx)("button",{type:"button",disabled:r,onClick:()=>t(!0),style:{border:"none",background:!1===e.danger?"var(--accent)":"color-mix(in srgb, #ef4444 85%, transparent)",color:"#fff",borderRadius:6,padding:"5px 14px",fontSize:12,fontWeight:600,cursor:"pointer"},children:e.confirmText??"删除"})]})]})})}},25535:(e,t,r)=>{r.d(t,{U:()=>o,u:()=>a});var n=r(95155),i=r(12115);let s=(0,i.createContext)(null);function a({value:e,children:t}){return(0,n.jsx)(s.Provider,{value:e,children:t})}function o(){let e=(0,i.useContext)(s);return e||{updateNode:()=>{},updateNodeDebounced:()=>{},deleteNode:()=>{},normalizeNodeId:()=>{},deleteEdge:()=>{},addEdge:()=>{},updateEdge:()=>{},addNode:()=>{},setSnapLines:()=>{},boardId:null,isTaskBoard:!1}}},56314:(e,t,r)=>{r.d(t,{Lv:()=>s,Nc:()=>i,ZY:()=>a});var n=r(12115);let i=(0,n.createContext)(null);function s(){return(0,n.useContext)(i)?.boardId??null}function a(){return(0,n.useContext)(i)?.defaultCwd??null}},60752:(e,t,r)=>{r.r(t),r.d(t,{SessionCanvas:()=>R});var n=r(95155),i=r(12115),s=r(37206),a=r(81750),o=r(96793),l=r(39247),d=r(64536),c=r(71309),u=r(34229),p=r(84876),h=r(56314),f=r(24456),x=r(71459);function g({editor:e,inputRef:t,nodes:r,onViewportSave:s}){let{t:o}=(0,l.s)(),{setHighlight:d}=(0,c.kz)(),{setCenter:u,getViewport:p,getNodes:h}=(0,a.VH)(),[f,b]=(0,i.useState)(""),[y,v]=(0,i.useState)(!1),[w,k]=(0,i.useState)(-1),[j,S]=(0,i.useState)(null),C=(0,i.useRef)(null),I=t??C,R=(0,i.useRef)(null),E=(0,i.useMemo)(()=>{let e=r??h(),t=[];for(let r of e){if("session-card"!==r.type)continue;let e=String(r.data.sessionId??"");e&&t.push(e)}return t},[r,h]).join(",");(0,i.useEffect)(()=>{let e=f.trim();if(!e)return void S(null);if(!E)return void S([]);let t=setTimeout(()=>{R.current?.abort();let t=new AbortController;R.current=t,fetch(`/api/search?q=${encodeURIComponent(e)}&limit=30&sessionIds=${encodeURIComponent(E)}`,{signal:t.signal}).then(e=>e.ok?e.json():null).then(e=>{e&&S((e.results??[]).filter(e=>"session"===e.kind&&e.result).map(e=>{let t=e.result.session;return{sessionId:t.id,title:t.name||(t.firstMessage||"").slice(0,50)||t.id.slice(0,12),snippet:e.result.snippet??"",titleMatch:!!e.result.titleMatch}}))}).catch(()=>S([]))},350);return()=>{clearTimeout(t),R.current?.abort()}},[f,E]);let z=(0,i.useMemo)(()=>{let e=r??h(),t=[];for(let r of e)if("session-card"===r.type){let e=String(r.data.title??"").trim();e&&t.push({nodeId:r.id,kind:"session-card",text:e})}else if("sticky-note"===r.type||"text"===r.type||"text-node"===r.type){let e=String(r.data.text??"").trim();e&&t.push({nodeId:r.id,kind:"sticky-note",text:e})}return t},[r,h]),T=(0,i.useMemo)(()=>{let e=f.trim().toLowerCase().split(/\s+/).filter(Boolean);return 0===e.length?[]:z.filter(t=>{let r=t.text.toLowerCase();return e.every(e=>r.includes(e))}).map(t=>({item:t,snippet:function(e,t,r=44){let n=t.trim().toLowerCase(),i=e.toLowerCase().indexOf(n);if(i<0)return e.length>r?`${e.slice(0,r)}…`:e;let s=Math.max(0,i-Math.floor((r-n.length)/2)),a=Math.min(e.length,s+r),o=s>0?"…":"",l=a<e.length?"…":"";return`${o}${e.slice(s,a)}${l}`}(t.text,e[0])}))},[z,f]),A=(0,i.useCallback)(e=>{let t=h().find(t=>t.id===e);if(!t)return;let r=t.measured?.width??340,n=t.measured?.height??160;Promise.resolve(u(t.position.x+r/2,t.position.y+n/2,{zoom:p().zoom})).then(()=>{s?.(p())}),d(e)},[h,p,u,d,s]),L=T.length+(j?.length??0),M=(0,i.useCallback)(e=>{if(e<T.length){let t=T[e];t&&A(t.item.nodeId)}else{let t=j?.[e-T.length];if(!t)return;let r=h().find(e=>"session-card"===e.type&&String(e.data.sessionId??"")===t.sessionId)?.id;if(!r)return;A(r)}k(e)},[T,j,A,h]),W=(0,i.useCallback)(()=>{0!==L&&M((w+1)%L)},[L,w,M]),$=(0,i.useCallback)(()=>{0!==L&&M((w-1+L)%L)},[L,w,M]),P=(0,i.useCallback)(e=>{"Enter"===e.key||"ArrowDown"===e.key?(e.preventDefault(),W()):"ArrowUp"===e.key?(e.preventDefault(),$()):"Escape"===e.key&&(e.preventDefault(),y?v(!1):(b(""),k(-1),I.current?.blur()))},[y,W,$,I]),D=y&&f.trim().length>0,N=null===j,B=T.length>0,H=(j?.length??0)>0;return(0,n.jsxs)("div",{style:{position:"relative",width:320},"data-testid":"board-search",children:[(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:6,height:34,padding:"0 8px 0 10px",borderRadius:999,...x.r,border:"1px solid color-mix(in srgb, var(--border) 60%, transparent)",boxShadow:"0 2px 12px -6px rgba(0,0,0,0.18)"},children:[(0,n.jsxs)("svg",{width:"13",height:"13",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",style:{color:"var(--text-dim)",flexShrink:0},children:[(0,n.jsx)("circle",{cx:"11",cy:"11",r:"8"}),(0,n.jsx)("path",{d:"m21 21-4.35-4.35"})]}),(0,n.jsx)("input",{ref:I,value:f,onChange:e=>{b(e.target.value),k(-1),v(!0)},onKeyDown:P,onFocus:()=>{f.trim()&&v(!0)},onBlur:()=>v(!1),onPointerDown:e=>e.stopPropagation(),placeholder:o("boards.searchPlaceholder"),spellCheck:!1,"aria-label":o("boards.searchPlaceholder"),style:{flex:1,minWidth:0,border:"none",outline:"none",background:"transparent",color:"var(--text)",fontSize:12.5,fontFamily:"inherit"}}),f&&(0,n.jsx)("button",{type:"button",title:o("common.close"),onPointerDown:e=>e.stopPropagation(),onClick:()=>{b(""),k(-1),I.current?.focus()},style:{flexShrink:0,display:"flex",alignItems:"center",justifyContent:"center",width:18,height:18,padding:0,border:"none",borderRadius:"50%",background:"color-mix(in srgb, var(--border) 35%, transparent)",color:"var(--text-muted)",cursor:"pointer",fontSize:11,lineHeight:1},children:"\xd7"})]}),D&&(0,n.jsxs)("div",{"data-testid":"board-search-dropdown",onPointerDown:e=>e.stopPropagation(),style:{position:"absolute",top:40,left:0,right:0,zIndex:60,maxHeight:320,overflowY:"auto",borderRadius:12,padding:4,...x.r,border:"1px solid color-mix(in srgb, var(--border) 60%, transparent)",boxShadow:"0 4px 18px -6px rgba(0,0,0,0.28)"},children:[!B&&!H&&!N&&(0,n.jsx)("div",{style:{padding:"8px 12px",fontSize:12,color:"var(--text-muted)"},children:o("boards.searchNoResults")}),N&&(0,n.jsx)("div",{style:{padding:"8px 12px",fontSize:12,color:"var(--text-muted)"},children:o("search.indexing")}),B&&(0,n.jsxs)(n.Fragment,{children:[H&&(0,n.jsx)(m,{text:o("boards.searchGroupNodes")}),T.map((e,t)=>{let r=t===w;return(0,n.jsxs)("button",{type:"button",onMouseDown:e=>e.preventDefault(),onClick:()=>M(t),style:{display:"flex",alignItems:"center",gap:8,width:"100%",padding:"6px 10px",border:"none",borderRadius:8,background:r?"color-mix(in srgb, var(--accent) 14%, transparent)":"transparent",color:"var(--text)",fontSize:12.5,textAlign:"left",cursor:"pointer"},children:[(0,n.jsx)("span",{"aria-hidden":!0,style:{flexShrink:0,display:"inline-flex",color:r?"var(--accent)":"var(--text-dim)"},children:"session-card"===e.item.kind?(0,n.jsx)("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:(0,n.jsx)("path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"})}):(0,n.jsxs)("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,n.jsx)("path",{d:"M15.5 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V8.5L15.5 3z"}),(0,n.jsx)("path",{d:"M15 3v6h6"})]})}),(0,n.jsx)("span",{style:{minWidth:0,flex:1,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:e.snippet}),(0,n.jsx)("span",{style:{flexShrink:0,fontSize:10,color:"var(--text-dim)"},children:"session-card"===e.item.kind?o("boards.searchSession"):o("boards.searchNote")})]},e.item.nodeId)})]}),H&&(0,n.jsxs)(n.Fragment,{children:[B&&(0,n.jsx)(m,{text:o("boards.searchGroupBody")}),j.map((e,t)=>{let r,i,s=T.length+t,a=s===w;return(0,n.jsxs)("button",{type:"button",onMouseDown:e=>e.preventDefault(),onClick:()=>M(s),style:{display:"flex",flexDirection:"column",gap:2,width:"100%",padding:"6px 10px",border:"none",borderRadius:8,background:a?"color-mix(in srgb, var(--accent) 14%, transparent)":"transparent",color:"var(--text)",fontSize:12.5,textAlign:"left",cursor:"pointer"},children:[(0,n.jsxs)("span",{style:{display:"flex",alignItems:"center",gap:6,minWidth:0},children:[e.titleMatch&&(0,n.jsx)("span",{style:{flexShrink:0,fontSize:9,fontFamily:"var(--font-mono)",color:"var(--accent)",background:"color-mix(in srgb, var(--accent) 12%, transparent)",border:"1px solid color-mix(in srgb, var(--accent) 30%, transparent)",borderRadius:3,padding:"0 4px",lineHeight:"15px"},children:o("search.title")}),(0,n.jsx)("span",{style:{minWidth:0,flex:1,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",fontWeight:500},children:e.title}),(0,n.jsx)("span",{style:{flexShrink:0,fontSize:10,color:"var(--text-dim)"},children:o("boards.searchBodyTag")})]}),e.snippet&&(0,n.jsx)("span",{style:{minWidth:0,fontSize:11,color:"var(--text-muted)",lineHeight:1.5,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:(r=e.snippet.split(/([\[\]])/g),i=!1,r.map((e,t)=>"["===e?(i=!0,null):"]"===e?(i=!1,null):e?i?(0,n.jsx)("mark",{style:{background:"color-mix(in srgb, var(--accent) 22%, transparent)",color:"var(--text)",borderRadius:2},children:e},t):(0,n.jsx)("span",{children:e},t):null))})]},e.sessionId)})]})]})]})}function m({text:e}){return(0,n.jsx)("div",{style:{padding:"6px 12px 2px",fontSize:10,fontWeight:700,color:"var(--text-dim)",textTransform:"uppercase",letterSpacing:.5},children:e})}function b({boardName:e,isTaskBoard:t,reloading:r,onReload:s,onClear:o,onAddSessionCard:l,wallSettings:d,updateWallSettings:c}){let{screenToFlowPosition:u}=(0,a.VH)(),[p,h]=(0,i.useState)(!1),[f,g]=(0,i.useState)(!1),m=(0,i.useRef)(null);return(0,i.useEffect)(()=>()=>{m.current&&clearTimeout(m.current)},[]),(0,n.jsxs)("div",{style:{position:"absolute",top:12,left:12,zIndex:40,display:"flex",flexDirection:"column",alignItems:"flex-start",gap:6,maxWidth:"min(420px, calc(100% - 320px))"},children:[(0,n.jsxs)("div",{style:{...x.r,display:"flex",alignItems:"center",gap:4,height:36,padding:"0 6px 0 12px",borderRadius:999,border:"1px solid color-mix(in srgb, var(--border) 60%, transparent)",boxShadow:"0 2px 12px -6px rgba(0,0,0,0.18)",color:"var(--text)"},children:[(0,n.jsxs)("svg",{width:"13",height:"13",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",style:{flexShrink:0,opacity:.7},children:[(0,n.jsx)("rect",{x:"3",y:"3",width:"7",height:"7",rx:"1.5"}),(0,n.jsx)("rect",{x:"14",y:"3",width:"7",height:"7",rx:"1.5"}),(0,n.jsx)("rect",{x:"3",y:"14",width:"7",height:"7",rx:"1.5"}),(0,n.jsx)("rect",{x:"14",y:"14",width:"7",height:"7",rx:"1.5"})]}),(0,n.jsx)("span",{title:e,style:{fontSize:12.5,fontWeight:600,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",maxWidth:220},children:e||"看板"}),(0,n.jsx)("button",{type:"button",onClick:s,title:"刷新画布","aria-label":"刷新画布",style:{...y,cursor:r?"default":"pointer"},...r?{}:v(),children:(0,n.jsxs)("svg",{width:"13",height:"13",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",style:{animation:r?"spin 0.8s linear infinite":void 0},children:[(0,n.jsx)("path",{d:"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"}),(0,n.jsx)("path",{d:"M21 3v5h-5"})]})}),(0,n.jsx)("span",{style:{flexShrink:0,width:1,height:18,background:"color-mix(in srgb, var(--border) 70%, transparent)",margin:"0 2px"}}),(0,n.jsx)("button",{type:"button",onClick:()=>{if(f)return;let e=document.querySelector(".react-flow__pane"),t=e?.getBoundingClientRect();if(!t)return;let r=u({x:t.left+t.width/2,y:t.top+t.height/2});g(!0),m.current&&clearTimeout(m.current),m.current=setTimeout(()=>g(!1),600),l(r)},title:"新建会话","aria-label":"新建会话",disabled:f,style:{...y,opacity:f?.7:1},...f?{}:v(),children:f?(0,n.jsx)("span",{"aria-hidden":!0,style:{width:13,height:13,borderRadius:"50%",border:"2px solid color-mix(in srgb, var(--accent) 25%, transparent)",borderTopColor:"var(--accent)",animation:"spin 0.8s linear infinite"}}):(0,n.jsx)("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:(0,n.jsx)("path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"})})}),(0,n.jsx)("button",{type:"button",onClick:()=>h(e=>!e),title:"磨砂调节","aria-label":"磨砂调节","aria-expanded":p,style:{...y,color:p?"var(--accent)":"var(--text-muted)",background:p?"color-mix(in srgb, var(--accent) 12%, transparent)":void 0},...p?{}:v(),children:(0,n.jsxs)("svg",{width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:[(0,n.jsx)("path",{d:"M12 3v2"}),(0,n.jsx)("path",{d:"M12 19v2"}),(0,n.jsx)("path",{d:"M5 7l1.5 1.5"}),(0,n.jsx)("path",{d:"M17.5 15.5L19 17"}),(0,n.jsx)("path",{d:"M3 12h2"}),(0,n.jsx)("path",{d:"M19 12h2"}),(0,n.jsx)("path",{d:"M5 17l1.5-1.5"}),(0,n.jsx)("path",{d:"M17.5 8.5L19 7"}),(0,n.jsx)("circle",{cx:"12",cy:"12",r:"3"})]})}),(0,n.jsx)("button",{type:"button",onClick:o,title:t?"清空画布(任务卡片保留)":"清空画布","aria-label":"清空画布",style:y,...v(),children:(0,n.jsxs)("svg",{width:"15",height:"15",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:[(0,n.jsx)("path",{d:"M18 6 6 18"}),(0,n.jsx)("path",{d:"m6 6 12 12"})]})})]}),p&&(0,n.jsx)("div",{style:w,children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8},children:[(0,n.jsx)("span",{style:{width:14,flexShrink:0,textAlign:"center",fontSize:12},children:"❄"}),(0,n.jsx)("span",{style:{flexShrink:0,fontSize:12.5},children:"磨砂"}),(0,n.jsx)("input",{type:"range",min:0,max:30,value:d.scrimBlur,onChange:e=>c({scrimBlur:Number(e.target.value)}),style:{flex:1,minWidth:120,accentColor:"var(--accent)",cursor:"pointer"},"aria-label":"磨砂强度"}),(0,n.jsxs)("span",{style:{width:30,flexShrink:0,textAlign:"right",fontSize:11,color:"var(--text)"},children:[d.scrimBlur,"px"]})]})})]})}let y={display:"flex",alignItems:"center",justifyContent:"center",gap:2,width:30,height:30,padding:0,flexShrink:0,border:"none",background:"transparent",color:"var(--text-muted)",cursor:"pointer",borderRadius:999,transition:"background 0.12s, color 0.12s"};function v(){return{onMouseEnter:e=>{e.currentTarget.style.background="color-mix(in srgb, var(--text) 8%, transparent)"},onMouseLeave:e=>{e.currentTarget.style.background="transparent"}}}let w={...x.r,display:"flex",flexDirection:"column",gap:6,padding:"8px 10px",borderRadius:12,border:"1px solid color-mix(in srgb, var(--border) 60%, transparent)",boxShadow:"0 2px 12px -6px rgba(0,0,0,0.18)",color:"var(--text)",whiteSpace:"nowrap"},k={dispatch:"正在派发",resume:"续会话",review:"AI 审核中",blockcheck:"阻塞巡检"};function j({nodes:e,onViewportSave:t}){let{setCenter:r,getViewport:s,getNodes:o}=(0,a.VH)(),{setHighlight:l}=(0,c.kz)(),[d,u]=(0,i.useState)(null),[p,h]=(0,i.useState)(!1),f=(0,i.useRef)(null);(0,i.useEffect)(()=>{let e=!1,t=null,r=async()=>{try{let t=await fetch("/api/task-scheduler/status",{cache:"no-store"});if(!t.ok)return;let r=await t.json();e||u(r.status)}catch{}},n=async()=>{await r(),e||(t=setTimeout(n,2500))};return n(),()=>{e=!0,t&&clearTimeout(t)}},[]),(0,i.useEffect)(()=>{if(!p)return;let e=e=>{f.current&&!f.current.contains(e.target)&&h(!1)};return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[p]);let g=(0,i.useMemo)(()=>{let t=new Map;for(let r of e){let e=r.data;e.cardId&&t.set(e.cardId,r.id)}return t},[e]),m=!!d?.activity,b=d?d.activity?k[d.activity.kind]??"工作中":d.running.length>0?`运行中 ${d.running.length}`:d.queue.review.length>0?`待审核 ${d.queue.review.length}`:null:null;return(0,n.jsxs)("div",{ref:f,style:{position:"absolute",top:12,right:12,zIndex:40,display:"flex",flexDirection:"column",alignItems:"flex-end"},children:[(0,n.jsxs)("button",{type:"button",onClick:()=>h(e=>!e),"aria-expanded":p,"aria-label":"调度器状态",title:"调度器状态",style:{...x.r,display:"flex",alignItems:"center",gap:7,height:36,padding:"0 6px 0 14px",borderRadius:999,border:"1px solid color-mix(in srgb, var(--border) 60%, transparent)",boxShadow:"0 2px 12px -6px rgba(0,0,0,0.18)",color:"var(--text)",cursor:"pointer",whiteSpace:"nowrap"},children:[(0,n.jsx)("span",{"aria-hidden":!0,style:{width:8,height:8,borderRadius:"50%",flexShrink:0,background:m?"#f59e0b":(d?.running.length??0)>0?"#10b981":"var(--text-dim)",boxShadow:m?"0 0 6px 1px rgba(245,158,11,0.6)":(d?.running.length??0)>0?"0 0 6px 1px rgba(16,185,129,0.5)":"none",animation:m?"pulse 1.6s ease-in-out infinite":void 0}}),(0,n.jsx)("span",{style:{fontSize:12.5,fontWeight:700,color:"var(--text)"},children:"调度器"}),d?.started===!1&&(0,n.jsx)("span",{style:{fontSize:10.5,color:"var(--text-muted)"},children:"未启动"}),d?.started===!0&&d?.leader===!1&&(0,n.jsx)("span",{style:{fontSize:10.5,color:"var(--text-muted)"},children:"跟随"}),b&&(0,n.jsx)("span",{style:{fontSize:11,fontWeight:600,color:"var(--text-muted)"},children:b}),(0,n.jsx)("svg",{width:"13",height:"13",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",style:{color:"var(--text-muted)",transform:p?"rotate(180deg)":"none",transition:"transform 0.15s",flexShrink:0},children:(0,n.jsx)("polyline",{points:"6 9 12 15 18 9"})})]}),p&&(0,n.jsxs)("div",{style:{...x.r,display:"flex",flexDirection:"column",marginTop:6,padding:"8px 0",width:340,borderRadius:14,border:"1px solid color-mix(in srgb, var(--border) 60%, transparent)",boxShadow:"0 8px 30px -8px rgba(0,0,0,0.3)",color:"var(--text)"},children:[(0,n.jsxs)("div",{style:{padding:"2px 14px 8px",borderBottom:"1px solid color-mix(in srgb, var(--border) 50%, transparent)",display:"flex",alignItems:"center",justifyContent:"space-between"},children:[(0,n.jsx)("span",{style:{fontSize:13,fontWeight:700,color:"var(--text)"},children:"调度器"}),(0,n.jsx)("span",{style:{fontSize:10.5,color:"var(--text-muted)"},children:d?.started===!1?"未启动":d?.started===!0&&d?.leader===!1?"跟随(非调度者)":m?"工作中":"休眠"})]}),(0,n.jsxs)("div",{style:{display:"grid",gridTemplateColumns:"1fr 1fr",gap:6,padding:"10px 12px 8px"},children:[(0,n.jsx)(S,{label:"派发",active:d?.activity?.kind==="dispatch"||d?.activity?.kind==="resume",activityLabel:d?.activity&&("dispatch"===d.activity.kind||"resume"===d.activity.kind)?k[d.activity.kind]:void 0}),(0,n.jsx)(S,{label:"审查",active:d?.activity?.kind==="review"||d?.activity?.kind==="blockcheck",activityLabel:d?.activity&&("review"===d.activity.kind||"blockcheck"===d.activity.kind)?k[d.activity.kind]:void 0})]}),d&&(0,n.jsx)(C,{title:"任务队列",groups:[{label:"运行中",color:"#10b981",cards:d.running},{label:"待审核",color:"#f59e0b",cards:d.queue.review},{label:"待派发",color:"var(--accent)",cards:d.queue.dispatchable},{label:"等回复",color:"var(--text-muted)",cards:d.queue.waitingReply},{label:"失败",color:"#ef4444",cards:d.queue.failed}],nodeIdByCardId:g,onLocate:e=>{let n=o().find(t=>t.id===e);if(!n)return;let i=n.measured?.width??n.style?.width??380,a=n.measured?.height??n.style?.height??270;Promise.resolve(r(n.position.x+i/2,n.position.y+a/2,{zoom:s().zoom})).then(()=>{t?.(s())}),l(e)}}),!d&&(0,n.jsx)("div",{style:{padding:"10px 14px",fontSize:12,color:"var(--text-muted)"},children:"加载中…"})]})]})}function S({label:e,active:t,activityLabel:r}){return(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:6,padding:"7px 8px",borderRadius:8,border:"1px solid color-mix(in srgb, var(--border) 55%, transparent)",background:t?"color-mix(in srgb, var(--accent) 8%, transparent)":"transparent"},children:[(0,n.jsx)("span",{"aria-hidden":!0,style:{width:7,height:7,borderRadius:"50%",flexShrink:0,background:t?"#f59e0b":"var(--text-dim)",boxShadow:t?"0 0 6px 1px rgba(245,158,11,0.6)":"none",animation:t?"pulse 1.6s ease-in-out infinite":void 0}}),(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",minWidth:0},children:[(0,n.jsx)("span",{style:{fontSize:12,fontWeight:700,color:"var(--text)",lineHeight:1.2},children:e}),(0,n.jsx)("span",{style:{fontSize:10,fontWeight:600,color:"var(--text-muted)",whiteSpace:"nowrap"},children:r??"休眠"})]})]})}function C({title:e,groups:t,nodeIdByCardId:r,onLocate:i}){let s=t.filter(e=>e.cards.length>0),a=t.reduce((e,t)=>e+t.cards.length,0);return(0,n.jsxs)("div",{style:{padding:"8px 12px 4px"},children:[(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",padding:"0 2px 4px"},children:[(0,n.jsx)("span",{style:{fontSize:10.5,fontWeight:700,color:"var(--text-muted)",letterSpacing:.3},children:e}),(0,n.jsxs)("span",{style:{fontSize:10.5,fontWeight:600,color:"var(--text-dim)"},children:["共 ",a]})]}),0===s.length?(0,n.jsx)("div",{style:{fontSize:12,color:"var(--text-dim)",padding:"2px 2px 6px"},children:"队列空闲"}):s.map(e=>(0,n.jsxs)("div",{style:{marginBottom:6},children:[(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:5,padding:"0 2px 3px"},children:[(0,n.jsx)("span",{style:{width:6,height:6,borderRadius:"50%",background:e.color,flexShrink:0}}),(0,n.jsx)("span",{style:{fontSize:10.5,fontWeight:700,color:"var(--text-muted)"},children:e.label}),(0,n.jsx)("span",{style:{fontSize:10,color:"var(--text-dim)"},children:e.cards.length})]}),(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:1},children:[e.cards.slice(0,4).map(e=>{let t=r.get(e.id);return(0,n.jsxs)("button",{type:"button",onClick:()=>{t&&i(t)},disabled:!t,title:t?"点击定位到本看板任务卡":"该任务不在当前看板",style:{display:"flex",alignItems:"center",gap:6,width:"100%",padding:"4px 6px",border:"none",borderRadius:6,background:"transparent",color:"var(--text)",fontSize:12,textAlign:"left",cursor:t?"pointer":"default",opacity:t?1:.55},children:[(0,n.jsxs)("span",{style:{minWidth:0,flex:1,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:[(0,n.jsxs)("span",{style:{color:"var(--text-muted)",fontWeight:600},children:["#",e.number]})," ",e.name]}),!t&&(0,n.jsx)("span",{style:{flexShrink:0,fontSize:9.5,color:"var(--text-dim)"},children:"其它看板"})]},e.id)}),e.cards.length>4&&(0,n.jsxs)("div",{style:{padding:"2px 6px",fontSize:10,color:"var(--text-dim)"},children:["…共 ",e.cards.length," 个"]})]})]},e.label))]})}let I=(0,s.default)(()=>Promise.all([r.e(6126),r.e(5004),r.e(277),r.e(2666),r.e(3922),r.e(8088)]).then(r.bind(r,28088)).then(e=>e.CanvasStage),{loadableGenerated:{webpack:()=>[28088]},ssr:!1,loading:()=>(0,n.jsx)(p.N,{label:"正在加载画布…"})});function R({boardId:e,taskId:t,newSessionCwd:r,onRunningSessionIdsChange:s,wallSettings:p,updateWallSettings:x}){let{t:m}=(0,l.s)(),{isDark:y}=(0,o.D)(),v=(0,d.C6)({boardId:e,taskId:t,newSessionCwd:r}),w=!!(v.board?.taskId??t),k=(0,i.useMemo)(()=>({getStatus:e=>v.taskCardStatus[e],register:v.registerVisibleTaskCard,unregister:v.unregisterVisibleTaskCard}),[v.taskCardStatus,v.registerVisibleTaskCard,v.unregisterVisibleTaskCard]),S=(0,i.useMemo)(()=>({getRunning:e=>v.sessionRunning[e],getSummary:e=>v.sessionTitles[e]}),[v.sessionRunning,v.sessionTitles]),C=(0,i.useRef)(null);(0,i.useEffect)(()=>{let e=e=>{if(!(e.ctrlKey||e.metaKey)||"f"!==e.key.toLowerCase())return;let t=document.activeElement;if(t&&t!==C.current){let e=t.tagName;if("INPUT"===e||"TEXTAREA"===e||t.isContentEditable)return}e.preventDefault(),C.current?.focus(),C.current?.select()};return window.addEventListener("keydown",e,!0),()=>window.removeEventListener("keydown",e,!0)},[]);let[E,z]=(0,i.useState)(!1),T=(0,i.useCallback)(async()=>{if(!E){z(!0);try{v.loadSessionSummaries(),await v.reloadCanvas()}finally{setTimeout(()=>z(!1),800)}}},[v,E]);return(0,i.useEffect)(()=>{s?.(new Set(v.running?.runningSessionIds??[]))},[v.running,s]),(0,n.jsx)("div",{"data-glass-scope":"board",style:{height:"100%",display:"flex",flexDirection:"column",overflow:"hidden",position:"relative"},children:(0,n.jsx)(u.I,{value:"board",children:(0,n.jsx)(h.Nc.Provider,{value:{boardId:v.board?.id??null,defaultCwd:r??null},children:(0,n.jsx)(c.eR,{children:(0,n.jsx)(a.Ln,{children:(0,n.jsx)(d.x_,{value:k,children:(0,n.jsxs)(d.XK,{value:S,children:[!v.loading&&(0,n.jsx)(b,{boardName:v.board?.name??"",isTaskBoard:w,reloading:E,onReload:()=>void T(),onClear:()=>{(0,f.l)({message:w?m("boards.clearTaskConfirm"):m("boards.clearConfirm"),confirmText:m("boards.clear")}).then(e=>{e&&v.clearBoard()})},onAddSessionCard:e=>v.addNewSessionCard(e),wallSettings:p,updateWallSettings:x}),(0,n.jsx)(j,{nodes:v.nodes,onViewportSave:v.saveViewport}),!v.loading&&(0,n.jsx)("div",{style:{position:"absolute",top:12,left:0,right:0,display:"flex",justifyContent:"center",pointerEvents:"none",zIndex:40},children:(0,n.jsx)("div",{style:{pointerEvents:"auto"},children:(0,n.jsx)(g,{inputRef:C,nodes:v.nodes,onViewportSave:v.saveViewport})})}),(0,n.jsx)(I,{board:v,isDark:y})]})})})})})})})}},64536:(e,t,r)=>{r.d(t,{C6:()=>b,Nz:()=>C,Uh:()=>I,VX:()=>k,XK:()=>S,ZI:()=>p,eo:()=>h,rD:()=>w,x_:()=>v});var n=r(12115),i=r(67816),s=r(44706),a=r(81750),o=r(15934),l=r(24456),d=r(94315),c=r(23657),u=r(41463);let p=340,h=160,f=u.env.NEXT_PUBLIC_SYNC_WS??`${"https:"===window.location.protocol?"wss":"ws"}://${window.location.host}`,x="pi-web:yjs-doc-version";async function g(){try{let e=await fetch("/api/yjs-version",{cache:"no-store"});if(!e.ok)return;let{version:t}=await e.json(),r=localStorage.getItem(x);localStorage.setItem(x,String(t)),null!==r&&r!==String(t)&&location.reload()}catch{}}async function m(e){try{let t=await fetch(`/api/sessions/${encodeURIComponent(e)}`,{method:"DELETE"});t.ok?(0,o.Qp)(e):console.warn(`[board] 清理新会话卡关联会话 ${e} 失败 HTTP ${t.status}`)}catch(t){console.warn(`[board] 清理新会话卡关联会话 ${e} 异常`,t)}}function b({boardId:e,taskId:t,newSessionCwd:r}){let[u,x]=(0,n.useState)(null),[y,v]=(0,n.useState)(null),[w,k]=(0,n.useState)(!1),[j,S]=(0,n.useState)(!1);(0,n.useEffect)(()=>{S(!1);let e=setTimeout(()=>S(!0),450);return()=>clearTimeout(e)},[e]);let[C,I]=(0,n.useState)([]),[R,E]=(0,n.useState)([]),[z,T]=(0,n.useState)({x:0,y:0,zoom:1}),[A,L]=(0,n.useState)(null),M=(0,n.useRef)(null),W=(0,n.useRef)(null),$=(0,n.useRef)(null),P=(0,n.useRef)(null),D=(0,n.useRef)(!1);D.current=w;let N=(0,n.useRef)(null),B=(0,n.useRef)(e);B.current=e;let H=(0,n.useRef)(new Set),U=(0,n.useRef)(null);U.current=u;let O=(0,n.useRef)(t??null);O.current=t??null;let _=(0,n.useRef)(r);_.current=r;let V=(0,n.useMemo)(()=>`${f}/connect`,[]);(0,n.useEffect)(()=>{let t=new i.m({url:V,name:e,forceSyncInterval:!1,flushDelay:500});M.current=t,L(t);let r=t.document.getMap("nodes"),n=t.document.getMap("edges"),a=t.document.getMap("view");W.current=r,$.current=n,P.current=a;let l=e=>{let t=e?new Set(Array.from(e.keys.keys())):null;if(e&&D.current){let t=null;for(let[n,i]of e.keys){if(i?.action!=="add")continue;let e=r.get(n);if(e?.type!=="session-card")continue;let s=e.data;s?.sessionId&&!s.cwd&&(H.current.has(s.sessionId)||(H.current.add(s.sessionId),t=t??s.sessionId))}t&&(0,o.PH)(t)}I(e=>{let n=new Set(e.filter(e=>e.selected).map(e=>e.id)),i=new Set(e.filter(e=>e.dragging).map(e=>e.id)),s=ed.current,a=new Map(e.map(e=>[e.id,e]));return Array.from(r.values()).map(e=>{if(t&&!t.has(e.id)){let t=a.get(e.id);if(t)return t}let r={...e};if(s.has(e.id)){let t=a.get(e.id);t&&(r.position=t.position)}return delete r.selected,delete r.dragging,n.has(e.id)&&(r.selected=!0),i.has(e.id)&&(r.dragging=!0),r})})},d=()=>E(Array.from(n.values())),c=()=>{let e=P.current;e&&T({x:e.get("x")??0,y:e.get("y")??0,zoom:e.get("zoom")??1})},u=()=>{c(),k(!0),g()};r.observe(l),n.observe(d),a.observe(c),t.on("synced",u),l(),d();let p=new s.ad([r,n],{captureTimeout:500,trackedOrigins:new Set([null])});return N.current=p,()=>{p.destroy(),N.current=null,r.unobserve(l),n.unobserve(d),a.unobserve(c),t.off("synced",u),t.flushPendingUpdates?.(),t.destroy(),M.current=null,W.current=null,$.current=null,P.current=null,k(!1)}},[V,e]);let F=(0,n.useCallback)(async()=>{try{let t=await fetch(`/api/boards/${encodeURIComponent(e)}`,{cache:"no-store"});if(!t.ok)return void v(`HTTP ${t.status}`);let r=await t.json();x(r.board)}catch(e){v(e instanceof Error?e.message:String(e))}},[e]);(0,n.useEffect)(()=>{F()},[F]);let q=(0,n.useCallback)(async()=>{try{await fetch(`/api/boards/${encodeURIComponent(e)}/reconcile`,{method:"POST"})}catch{}await F()},[e,F]),[K,X]=(0,n.useState)(()=>globalThis.__piRunningSnapshot??null),J=(0,n.useCallback)(e=>{globalThis.__piRunningSnapshot=e,X(e)},[]),G=(0,n.useRef)(!1),Q=(0,n.useRef)(new Set),[Y,Z]=(0,n.useState)({}),ee=(0,n.useCallback)(e=>{Q.current.add(e)},[]),et=(0,n.useCallback)(e=>{Q.current.delete(e)},[]),[er,en]=(0,n.useState)({});(0,n.useEffect)(()=>{let e=!1,t=null,r=async()=>{if(!e&&!G.current&&"visible"===document.visibilityState){G.current=!0;try{let t=Array.from(Q.current),r=t.length>0&&B.current?`?boardId=${encodeURIComponent(B.current)}&cardIds=${encodeURIComponent(t.join(","))}`:"",n=await fetch(`/api/agent/running${r}`,{cache:"no-store"});if(!n.ok)return;let i=await n.json();if(e)return;J(i);let s=e=>{Z(t=>{let r=e(t);if(r===t)return t;let n=Object.keys(t),i=Object.keys(r);return n.length===i.length&&n.every(e=>r[e]===t[e])?t:r})};i.taskCards.length>0?s(e=>{let t=!1,r={...e};for(let e of i.taskCards)r[e.cardId]!==e&&(r[e.cardId]=e,t=!0);return t?r:e}):t.length>0&&s(e=>{let r=!1,n={...e};for(let e of t)n[e]&&(delete n[e],r=!0);return r?n:e});let a=W.current,o={};if(a)for(let e of Array.from(a.values())){if("session-card"!==e.type)continue;let t=e.data;if(!t.sessionId)continue;let r=i.states[t.sessionId];if(i.runningSessionIds.includes(t.sessionId)&&r){let e=r.phase??"waiting_model",n=r.startedAt?Date.now()-r.startedAt:0;o[t.sessionId]={phase:e,runningMs:n,endedAt:0}}else o[t.sessionId]={phase:"idle",runningMs:0,endedAt:0}}en(e=>{let t=!1,r={...e};for(let[n,i]of Object.entries(o)){let s=e[n];s&&s.phase===i.phase&&s.runningMs===i.runningMs&&s.endedAt===i.endedAt||(r[n]=i,t=!0)}return t?r:e})}catch{}finally{G.current=!1}}},n=()=>{e||(t=setTimeout(async()=>{await r(),n()},2500))};r(),n();let i=()=>{"visible"===document.visibilityState&&r()};return document.addEventListener("visibilitychange",i),()=>{e=!0,t&&clearTimeout(t),document.removeEventListener("visibilitychange",i)}},[J]);let[ei,es]=(0,n.useState)({}),ea=(0,n.useRef)({});ea.current=ei;let eo=(0,n.useRef)(!1),el=(0,n.useCallback)(async()=>{if(!eo.current){eo.current=!0;try{let e=W.current;if(!e)return;let t=[];for(let r of Array.from(e.values())){if(r?.type!=="session-card")continue;let e=r.data;e?.sessionId&&!e.cwd&&t.push(e.sessionId)}if(0===t.length)return void es({});let r=await fetch("/api/sessions/summary",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({ids:t}),cache:"no-store"});if(!r.ok)return;let n=await r.json(),i={};for(let e of n.sessions)i[e.id]={title:e.name??e.firstMessage??"Untitled",messageCount:e.messageCount??0,projectName:e.projectKey??e.projectRoot??"",lastReply:e.lastReply??"",lastActivityAt:e.modified?Date.parse(e.modified):0,projectRoot:e.projectRoot,cwd:e.cwd,worktreeBranch:e.worktreeBranch,isWorktree:e.isWorktree};es(i)}catch{}finally{eo.current=!1}}},[]);(0,n.useEffect)(()=>{let e,t=!1,r=async()=>{await el(),t||(e=setTimeout(r,5e3))};r();let n=()=>{"visible"===document.visibilityState&&(e&&clearTimeout(e),r())};return document.addEventListener("visibilitychange",n),()=>{t=!0,e&&clearTimeout(e),document.removeEventListener("visibilitychange",n)}},[el]),(0,n.useEffect)(()=>{let e=M.current,t=W.current;e&&t&&D.current&&0!==Object.keys(ei).length&&e.document.transact(()=>{for(let e of Array.from(t.values())){if("session-card"!==e.type)continue;let r=e.data,n=ei[r.sessionId];n&&(r.title!==n.title||r.lastReply!==n.lastReply||r.messageCount!==n.messageCount||r.lastActivityAt!==n.lastActivityAt)&&t.set(e.id,{...e,data:{...r,title:n.title,lastReply:n.lastReply,messageCount:n.messageCount,lastActivityAt:n.lastActivityAt}})}},"board-summary")},[ei,w]);let ed=(0,n.useRef)(new Set),ec=(0,n.useCallback)(e=>{let t=W.current;if(!t)return;let r=e.filter(e=>"select"===e.type);r.length&&I(e=>(0,a._0)(r,e));let n=e.filter(e=>"select"!==e.type);if(0===n.length)return;let i=Array.from(t.values()),s=(0,a._0)(n,i),o=new Set(n.filter(e=>"dimensions"===e.type&&!0===e.resizing).map(e=>e.id));for(let e of n)if("add"===e.type||"replace"===e.type)t.set(e.item.id,e.item);else if("remove"===e.type&&t.has(e.id)){t.delete(e.id);let r=$.current;if(r)for(let t of Array.from(r.values()))(t.source===e.id||t.target===e.id)&&r.delete(t.id)}else if("position"===e.type){!0===e.dragging?ed.current.add(e.id):ed.current.delete(e.id);let r=s.find(t=>t.id===e.id);r&&(!0===e.dragging||o.has(e.id)?I(t=>t.map(t=>t.id===e.id?{...t,position:r.position}:t)):(t.set(e.id,{...function(e){let{selected:t,dragging:r,resizing:n,...i}=e;return i}(r),position:r.position}),I(t=>t.map(t=>t.id===e.id?{...t,position:r.position}:t))))}else"dimensions"===e.type&&I(t=>(0,a._0)([e],t))},[]),eu=(0,n.useCallback)(e=>{let t=$.current;if(!t)return;let r=Array.from(t.values()),n=(0,a.zW)(e,r);for(let r of e)if("add"===r.type||"replace"===r.type)t.set(r.item.id,r.item);else if("remove"===r.type&&t.has(r.id)){let e=t.get(r.id);if(e&&e.data?.execLink||e&&e.data?.forkLink||e&&e.data?.taskLink)continue;t.delete(r.id)}else{let e=n.find(e=>e.id===r.id);e&&t.set(r.id,e)}},[]),ep=(0,n.useCallback)(e=>{let t=$.current,r=W.current;if(!t||!r)return;let n=`edge-${e.source}-${e.target}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`,i=r.get(e.source),s=r.get(e.target),a=(0,d.dk)(i),o=(0,d.dk)(s),l=i?.type==="session-card",c=s?.type==="session-card",u=a&&c||l&&o;t.set(n,{id:n,source:e.source,target:e.target,type:u?"send-note":"default",data:u?{sendNote:!0,sent:!1}:void 0,markerEnd:{type:"arrowclosed"},style:{strokeWidth:1.5,stroke:"#8b8fa3"}})},[]),eh=(0,n.useCallback)(e=>{let t=W.current;if(!t)return;let r=(0,c.S)(),n=`session-${r}`,i=e?.x??60,s=e?.y??60,a=i-420+t.size%3*24,o=s-300+t.size%3*24;t.set(n,{id:n,type:"session-card",position:{x:a,y:o},style:{width:840,height:600},data:{sessionId:r,title:"",emoji:"\uD83D\uDCAC",projectName:"",messageCount:0,lastReply:"",phase:"idle",runningMs:0,endedAt:0,lastActivityAt:0,stale:!1,expanded:!0,cwd:_.current??"",taskId:O.current??U.current?.taskId??"",w:840,h:600,expandedW:0,expandedH:0,collapsedW:0,collapsedH:0}})},[]),ef=(0,n.useCallback)(async(e,t,r,n)=>{let i=W.current;if(!i)return;let s=O.current??U.current?.taskId??null;if(s){try{let t=await fetch(`/api/tasks/${encodeURIComponent(s)}/assign-session`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:e})});if(!t.ok)throw Error(`HTTP ${t.status}`)}catch(t){console.warn(`[board] 拖入会话归属任务失败,未落卡 ${e}:`,t instanceof Error?t.message:t);return}(0,o.PH)(e)}let a=ea.current[e],l=`session-${e}`,d=i.get(l);d?i.set(l,{...d,position:{x:t,y:r}}):i.set(l,{id:l,type:"session-card",position:{x:t,y:r},style:{width:p,height:h},data:{sessionId:e,title:n??a?.title??"Untitled",emoji:"\uD83D\uDCAC",projectName:a?.projectName??"",messageCount:a?.messageCount??0,lastReply:a?.lastReply??"",lastActivityAt:a?.lastActivityAt??0,phase:"idle",runningMs:0,endedAt:0,stale:!1,expanded:!1,taskId:s??"",w:p,h:h,expandedW:0,expandedH:0,collapsedW:0,collapsedH:0}})},[]),ex=(0,n.useCallback)(async e=>{let t=W.current,r=$.current;if(t&&r)if("session-card"===e.type){let n=e.data;if(!n.sessionId||n.cwd){for(let i of(n.sessionId&&await m(n.sessionId),t.delete(e.id),Array.from(r.values())))(i.source===e.id||i.target===e.id)&&r.delete(i.id);return}if(O.current??U.current?.taskId){if(!await (0,l.l)({message:"删除该会话?\n将同时删除会话文件并移除画布卡片。此操作不可撤销。"}))return;await fetch(`/api/sessions/${encodeURIComponent(n.sessionId)}`,{method:"DELETE"}).then(()=>(0,o.Qp)(n.sessionId)).catch(e=>console.warn(`[board] 删除会话 ${n.sessionId} 异常`,e))}else{if(!await (0,l.l)({message:"移除该会话卡片?\n会话本身将保留在会话列表中,可随时重新拖入。"}))return;await fetch("/api/task-cards/unbind",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:n.sessionId})}).catch(()=>{})}for(let n of(t.delete(e.id),Array.from(r.values())))(n.source===e.id||n.target===e.id)&&r.delete(n.id)}else if("task-card"===e.type){let n=e.data;if(!n.cardId)return void t.delete(e.id);if(!await (0,l.l)({message:"删除该任务卡?\n将删除任务卡、依赖线与执行会话连线;关联的执行会话保留。此操作不可撤销。"}))return;try{let e=await fetch(`/api/task-cards/${encodeURIComponent(n.cardId)}`,{method:"DELETE"});if(!e.ok)return void console.warn(`[board] 删除任务卡 ${n.cardId} 失败 HTTP ${e.status},保留画布卡`)}catch(e){console.warn(`[board] 删除任务卡 ${n.cardId} 异常,保留画布卡`,e);return}for(let n of(t.delete(e.id),Array.from(r.values())))(n.source===e.id||n.target===e.id)&&r.delete(n.id)}else{for(let n of(t.delete(e.id),Array.from(r.values())))(n.source===e.id||n.target===e.id)&&r.delete(n.id);if("image-node"===e.type){let r=e.data.src,n=r?.match(/^\/api\/board-assets\/([^/?#]+)$/)?.[1];n&&(Array.from(t.values()).some(t=>t.id!==e.id&&t.data?.src?.includes(n))||fetch(`/api/board-assets/${encodeURIComponent(n)}`,{method:"DELETE"}).catch(e=>console.warn(`[board] 回收图片资产 ${n} 异常`,e)))}}},[]),eg=(0,n.useCallback)((e,t)=>{let r=W.current;if(!r)return;let n=r.get(e);n&&r.set(e,{...n,...t,data:t.data?{...n.data,...t.data}:n.data,style:t.style?{...n.style??{},...t.style}:n.style})},[]),em=(0,n.useRef)(new Map),eb=(0,n.useRef)(new Map),ey=(0,n.useCallback)((e,t,r=400)=>{let n=W.current;if(!n||!n.has(e))return;let i=em.current.get(e)??{},s={...i,...t,data:{...i.data,...t.data}};em.current.set(e,s);let a=eb.current.get(e);a&&clearTimeout(a),eb.current.set(e,setTimeout(()=>{let t=em.current.get(e);em.current.delete(e),eb.current.delete(e),t&&eg(e,t)},r))},[eg]),ev=(0,n.useCallback)((e,t)=>{let r=W.current;if(!r||!e||e===t)return;let n=r.get(e);if(!n)return;r.delete(e),r.set(t,{...n,id:t});let i=$.current;if(i)for(let r of Array.from(i.values()))(r.source===e||r.target===e)&&i.set(r.id,{...r,source:r.source===e?t:r.source,target:r.target===e?t:r.target})},[]),ew=(0,n.useCallback)(e=>{let t=$.current;t&&t.set(e.id,e)},[]),ek=(0,n.useCallback)((e,t)=>{let r=$.current;if(!r)return;let n=r.get(e);n&&r.set(e,{...n,...t,data:{...n.data,...t.data}})},[]),ej=(0,n.useCallback)(e=>{let t=W.current;t&&t.set(e.id,e)},[]),eS=(0,n.useCallback)(async()=>{let e=W.current,t=$.current;if(!e||!t)return;let r=!!(O.current??U.current?.taskId),n=Array.from(e.values()).filter(e=>!r||"session-card"!==e.type);if(!r)for(let e of n){if("session-card"!==e.type)continue;let t=e.data;t.sessionId&&t.cwd&&await m(t.sessionId)}for(let r of n)for(let n of(e.delete(r.id),Array.from(t.values())))(n.source===r.id||n.target===r.id)&&t.delete(n.id)},[]),eC=(0,n.useCallback)(()=>{N.current?.undo()},[]),eI=(0,n.useCallback)(()=>{N.current?.redo()},[]),eR=!w||!j,eE=(0,n.useCallback)(e=>{let t=P.current;t&&(t.get("x")!==e.x||t.get("y")!==e.y||t.get("zoom")!==e.zoom)&&(t.set("x",e.x),t.set("y",e.y),t.set("zoom",e.zoom))},[]);return(0,n.useMemo)(()=>({board:u,loading:eR,error:y,running:K,runningCount:K?.runningSessionIds.length??0,taskCardStatus:Y,sessionRunning:er,registerVisibleTaskCard:ee,unregisterVisibleTaskCard:et,nodes:C,edges:R,viewport:z,saveViewport:eE,onNodesChange:ec,onEdgesChange:eu,onConnect:ep,provider:A,ready:w,addSessionNode:ef,addNewSessionCard:eh,deleteNodeWithConfirm:ex,updateNode:eg,updateNodeDebounced:ey,normalizeNodeId:ev,addEdge:ew,updateEdge:ek,addNode:ej,clearBoard:eS,sessionTitles:ei,loadSessionSummaries:el,reloadCanvas:q,undo:eC,redo:eI}),[u,eR,y,K,C,R,z,eE,ec,eu,ep,A,w,ef,eh,ex,eg,ey,ev,ew,ek,ej,eS,ei,el,q,F,Y,er,ee,et,eC,eI])}let y=(0,n.createContext)(null);function v({value:e,children:t}){return(0,n.createElement)(y.Provider,{value:e},t)}function w(e){let t=(0,n.useContext)(y);return e&&t?t.getStatus(e):void 0}function k(){let e=(0,n.useContext)(y);return{register:e?.register??(()=>{}),unregister:e?.unregister??(()=>{})}}let j=(0,n.createContext)(null);function S({value:e,children:t}){return(0,n.createElement)(j.Provider,{value:e},t)}function C(e){let t=(0,n.useContext)(j);return e&&t?t.getRunning(e):void 0}function I(e){let t=(0,n.useContext)(j);return e&&t?t.getSummary(e):void 0}},71309:(e,t,r)=>{r.d(t,{eR:()=>o,kz:()=>l,u:()=>s});var n=r(95155),i=r(12115);let s="0 0 0 3px var(--accent), 0 0 24px color-mix(in srgb, var(--accent) 45%, transparent)",a=(0,i.createContext)(null);function o({children:e}){let[t,r]=(0,i.useState)(null),s=(0,i.useRef)(null),l=(0,i.useCallback)(e=>{s.current&&clearTimeout(s.current),s.current=null,r(e),e&&(s.current=setTimeout(()=>r(null),1800))},[]);return(0,i.useEffect)(()=>()=>{s.current&&clearTimeout(s.current)},[]),(0,n.jsx)(a.Provider,{value:{highlightId:t,setHighlight:l},children:e})}function l(){let e=(0,i.useContext)(a);return e||{highlightId:null,setHighlight:()=>{}}}},71459:(e,t,r)=>{r.d(t,{r:()=>n});let n={backgroundImage:"linear-gradient(var(--board-card-glass), var(--board-card-glass)), var(--glass-bg-image-card, var(--glass-bg-image, none))",backgroundAttachment:"fixed",backgroundSize:"100% 100%, 100% 100%",backgroundRepeat:"no-repeat, no-repeat",backgroundPosition:"0 0, 0 0"}},94315:(e,t,r)=>{r.d(t,{OX:()=>u,dk:()=>c});var n=r(95155),i=r(12115),s=r(81750),a=r(85610),o=r(32383),l=r(25535);let d=new Set(["sticky-note","text","text-node"]);function c(e){return!!(e&&d.has(e.type??""))}function u({id:e,source:t,target:r,sourceX:d,sourceY:p,targetX:h,targetY:f,sourcePosition:x,targetPosition:g,markerEnd:m,style:b,data:y}){let{getNodes:v}=(0,s.VH)(),{updateEdge:w}=(0,l.U)(),[k,j,S]=(0,a.Fp)({sourceX:d,sourceY:p,sourcePosition:x,targetX:h,targetY:f,targetPosition:g}),C=!!y?.sent,[I,R]=(0,i.useState)(!1),[E,z]=(0,i.useState)(!1),T=(0,i.useRef)(!1),A=v(),L=A.find(e=>e.id===t&&c(e))??A.find(e=>e.id===r&&c(e)),M=A.find(e=>e.id===r&&"session-card"===e.type)??A.find(e=>e.id===t&&"session-card"===e.type),W=M?.data?.sessionId,$=!!M?.data?.cwd,P=(L?.data?.text??"").trim(),D=(0,i.useCallback)(async()=>{if(C||T.current)return;let n=v(),i=n.find(e=>e.id===t&&c(e))??n.find(e=>e.id===r&&c(e)),s=n.find(e=>e.id===r&&"session-card"===e.type)??n.find(e=>e.id===t&&"session-card"===e.type),a=s?.data?.sessionId,l=s?.data;if(!i||!s||!a||l?.cwd)return;let d=(i.data?.text??"").trim();if(d){T.current=!0,R(!0),z(!1),w(e,{data:{sent:!0}});try{await (0,o.$J)(a,{type:"prompt",message:d})}catch(t){w(e,{data:{sent:!1}}),z(!0),console.warn("[board] 发送便笺内容到会话失败:",t instanceof Error?t.message:t)}T.current=!1,R(!1)}},[e,t,r,C,w,v]),N="发送",B="把便笺内容作为消息发送给该会话",H=!1;return C?(N="✓ 已发送",B="已发送(每条连线只发送一次)",H=!0):I?(N="发送中…",H=!0):E?(N="重试",B="上次发送失败,点击重试"):P?W?$&&(B="新会话卡尚未创建会话,无法发送",H=!0):(B="会话不存在",H=!0):(B="便笺内容为空,无法发送",H=!0),(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.tE,{path:k,markerEnd:m,style:b}),(0,n.jsx)(s.rV,{children:(0,n.jsx)("div",{className:"nodrag nopan",style:{position:"absolute",transform:`translate(-50%, -50%) translate(${j}px, ${S}px)`,pointerEvents:"all",zIndex:1e3},children:(0,n.jsx)("button",{type:"button",onClick:D,disabled:H,title:B,"data-testid":`send-note-${e}`,style:{display:"flex",alignItems:"center",gap:4,padding:"2px 10px",borderRadius:999,border:H?"1px solid color-mix(in srgb, var(--border) 60%, transparent)":"1px solid color-mix(in srgb, var(--accent) 45%, transparent)",background:"var(--board-card-glass)",backdropFilter:"blur(var(--board-blur)) saturate(var(--glass-saturate))",WebkitBackdropFilter:"blur(var(--board-blur)) saturate(var(--glass-saturate))",color:H?"var(--text-muted)":"var(--accent)",fontSize:11,fontWeight:600,cursor:H?"default":"pointer",whiteSpace:"nowrap",boxShadow:"0 1px 6px -2px rgba(0,0,0,0.25)",userSelect:"none"},children:N})})})]})}}}]);
|