@xiaobuyu/nodesign 0.0.6 → 0.0.7
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/package.json +1 -1
- package/server/api/assets/helpers.js +18 -0
- package/server/api/assets/notes.js +50 -0
- package/server/api/assets.js +11 -6
- package/server/api/instruction.js +3 -3
- package/server/engine/agent/events.js +11 -1
- package/server/engine/agent/hooks/failure.js +11 -1
- package/server/engine/agent/hooks/lifecycle.js +10 -39
- package/server/engine/agent/hooks/post-guidance.js +4 -38
- package/server/engine/agent/hooks/user-prompt-submit.js +11 -40
- package/server/engine/agent/hooks.js +4 -8
- package/server/engine/agent/memory-config.js +61 -0
- package/server/engine/agent/prompts/nodesign-prelude.md +14 -12
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +24 -7
- package/server/engine/agent/prompts/tools/site-reference.md +13 -3
- package/server/engine/agent/session-loop.js +29 -18
- package/server/engine/agents/explorer.md +1 -1
- package/server/engine/agents/index.js +1 -1
- package/server/engine/mcp/index.js +4 -11
- package/server/engine/mcp/tools/arrange-on-board.js +3 -1
- package/server/engine/mcp/tools/browse-computer.js +2 -1
- package/server/engine/mcp/tools/browse-find-batch.js +20 -3
- package/server/engine/mcp/tools/browse.js +24 -2
- package/server/engine/mcp/tools/crystallize-skill.js +1 -1
- package/server/engine/mcp/tools/generate-image.js +50 -70
- package/server/engine/mcp/tools/helpers/codex-imagegen.js +139 -0
- package/server/engine/mcp/tools/helpers/rembg.js +7 -2
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +109 -0
- package/server/engine/mcp/tools/list-pages.js +11 -5
- package/server/engine/mcp/tools/organize-board.js +26 -1
- package/server/engine/mcp/tools/pin-to-board.js +26 -17
- package/server/engine/mcp/tools/read-board.js +19 -5
- package/server/engine/mcp/tools/relate-on-board.js +42 -4
- package/server/engine/mcp/tools/report-issue.js +14 -7
- package/server/engine/mcp/tools/screenshot.js +17 -7
- package/server/engine/mcp/tools/sketch-on-board.js +13 -5
- package/server/engine/mcp/tools/web-search.js +11 -15
- package/server/engine/mcp/tools/write-on-board.js +15 -6
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +3 -4
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +10 -1
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +16 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +4 -4
- package/server/index.js +0 -2
- package/server/lib/artifact-target.js +31 -14
- package/server/lib/board-kind-sizes.js +2 -3
- package/server/lib/canvas-id.js +29 -4
- package/server/lib/export-collect.js +3 -1
- package/server/lib/issues-store.js +3 -1
- package/server/lib/kinds/docx.js +11 -4
- package/server/lib/kinds/index.js +6 -16
- package/server/lib/kinds/site.js +40 -7
- package/server/lib/rewrite-refs.js +110 -0
- package/server/lib/ssrf-guard.js +9 -0
- package/server/lib/task-scan.js +18 -0
- package/server/projects/board-store.js +42 -1
- package/server/projects/memory-migration.js +85 -0
- package/server/projects/move-entry.js +11 -1
- package/server/projects/ui-config.js +2 -1
- package/server/projects/workspace-templates.js +13 -10
- package/server/projects/workspace.js +15 -15
- package/server/services/rembg-launcher.js +5 -2
- package/web/dist/assets/{BrowserWindow-P_vIdh-G.js → BrowserWindow-BoOBaIyz.js} +1 -1
- package/web/dist/assets/DeckWindow-Bg3rgkvi.js +11 -0
- package/web/dist/assets/{DocxWindow-DSAU1-tD.js → DocxWindow-B_u6aX9o.js} +1 -1
- package/web/dist/assets/{MermaidBlock-DR1GikCv.js → MermaidBlock-BO-46Du5.js} +6 -6
- package/web/dist/assets/{SiteWindow-92uBuRRR.js → SiteWindow-ConPmzdM.js} +1 -1
- package/web/dist/assets/{abnfDiagram-VCTEODGH-uA8Esix4.js → abnfDiagram-VCTEODGH-FCO9yf-n.js} +1 -1
- package/web/dist/assets/{arc-D_khH_xN.js → arc-BLcEOSRX.js} +1 -1
- package/web/dist/assets/{architectureDiagram-5GKGNRK7-DydJHqOt.js → architectureDiagram-5GKGNRK7-kxiX4TeN.js} +1 -1
- package/web/dist/assets/{blockDiagram-NRAW4CY4-B7wBbKiW.js → blockDiagram-NRAW4CY4-CptFksix.js} +1 -1
- package/web/dist/assets/{c4Diagram-UCG6FXSJ-6mrH0x7Y.js → c4Diagram-UCG6FXSJ-Dxmxj34F.js} +1 -1
- package/web/dist/assets/channel-DFyiuGL0.js +1 -0
- package/web/dist/assets/{chunk-2Q5K7J3B-Cuikho5Q.js → chunk-2Q5K7J3B-D9inx5oo.js} +1 -1
- package/web/dist/assets/{chunk-5VM5RSS4-3-KZ5AUz.js → chunk-5VM5RSS4-DydfHlEc.js} +1 -1
- package/web/dist/assets/{chunk-F27PBJKO-Bkwq2gGU.js → chunk-F27PBJKO-BO7GxhSv.js} +1 -1
- package/web/dist/assets/{chunk-G27WJ6UU-BLyQzdGE.js → chunk-G27WJ6UU-BzP6YiQ_.js} +1 -1
- package/web/dist/assets/{chunk-JWPE2WC7-B7HxxQds.js → chunk-JWPE2WC7-CHcgcd0s.js} +1 -1
- package/web/dist/assets/{chunk-LCL6LL3I-CtXrnUwF.js → chunk-LCL6LL3I-D2Wg5JtX.js} +1 -1
- package/web/dist/assets/{chunk-POPQ4Y6H-dxddZlXt.js → chunk-POPQ4Y6H-B2fCRfpM.js} +1 -1
- package/web/dist/assets/{chunk-SVP7TREG-DpkmrgHn.js → chunk-SVP7TREG-JSLcsRlZ.js} +1 -1
- package/web/dist/assets/{chunk-XXDRQBXY-BwZ7RbpQ.js → chunk-XXDRQBXY-BCjvaeHm.js} +1 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-Aw1f8z-b.js +1 -0
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-Aw1f8z-b.js +1 -0
- package/web/dist/assets/{cose-bilkent-JH36ORCC-DGFOsMUt.js → cose-bilkent-JH36ORCC-0uKv6oJ7.js} +1 -1
- package/web/dist/assets/{cynefin-OW5HDTMX-DUwn3ehS.js → cynefin-OW5HDTMX-Bvt-gEmA.js} +1 -1
- package/web/dist/assets/{cynefinDiagram-5FMLGOSQ-DlLAOZ9x.js → cynefinDiagram-5FMLGOSQ-C9Ht02qC.js} +1 -1
- package/web/dist/assets/{dagre-3AP2YEHR-CyourLfz.js → dagre-3AP2YEHR-BfxZny4s.js} +1 -1
- package/web/dist/assets/{diagram-S7CK7UJ4-BTMbx2np.js → diagram-S7CK7UJ4-CrE0ZETE.js} +1 -1
- package/web/dist/assets/{diagram-UQ7AKVKN-UK94_1-P.js → diagram-UQ7AKVKN-BO8VQ4Fx.js} +1 -1
- package/web/dist/assets/{diagram-VSXAHHWV-C7zS-v9v.js → diagram-VSXAHHWV-CHUZnR9F.js} +1 -1
- package/web/dist/assets/{diagram-VX7I27RA-DKJRa6CK.js → diagram-VX7I27RA-DYQAz1Vu.js} +1 -1
- package/web/dist/assets/{diagram-Z3DM3KII-kUbAfGAp.js → diagram-Z3DM3KII-BDjivgLr.js} +1 -1
- package/web/dist/assets/{ebnfDiagram-PWID7BFC-CVW16_vb.js → ebnfDiagram-PWID7BFC-Bpe_QVbH.js} +1 -1
- package/web/dist/assets/{erDiagram-SSCWMZ5O-BRQMFQ5y.js → erDiagram-SSCWMZ5O-BxhwuAu1.js} +1 -1
- package/web/dist/assets/{flowDiagram-A5DVABFB-S34LpLtK.js → flowDiagram-A5DVABFB-CCw2H08-.js} +1 -1
- package/web/dist/assets/{ganttDiagram-EL5Y4UJY-BFiqk1-G.js → ganttDiagram-EL5Y4UJY-DtTc7MyI.js} +1 -1
- package/web/dist/assets/{gitGraphDiagram-WWUBYQGX-LZsrdyyv.js → gitGraphDiagram-WWUBYQGX-BvORxMd2.js} +1 -1
- package/web/dist/assets/index-DGG5wIZs.js +2118 -0
- package/web/dist/assets/{infoDiagram-RXCK75RN-0DvN6KIp.js → infoDiagram-RXCK75RN-DMcrkypD.js} +1 -1
- package/web/dist/assets/{ishikawaDiagram-5VMMS53U-CaY3A-Bx.js → ishikawaDiagram-5VMMS53U-CZkTIbe-.js} +1 -1
- package/web/dist/assets/{journeyDiagram-EYS64GPL-BBeiaCrj.js → journeyDiagram-EYS64GPL-DR0XazdE.js} +1 -1
- package/web/dist/assets/{kanban-definition-3QL26DDD-ux42DRXQ.js → kanban-definition-3QL26DDD-DN8VbwVu.js} +1 -1
- package/web/dist/assets/{layout-CnXiF_NB.js → layout-9_paCbyt.js} +1 -1
- package/web/dist/assets/{linear-BMW0u6G6.js → linear-g6Ct012e.js} +1 -1
- package/web/dist/assets/{mindmap-definition-FBJOCRG2-hUxSpdKn.js → mindmap-definition-FBJOCRG2-BGt4omYC.js} +1 -1
- package/web/dist/assets/{pegDiagram-XKGWAZYB-aCaQPH0w.js → pegDiagram-XKGWAZYB-uSfS-PBE.js} +1 -1
- package/web/dist/assets/{pending-edit-apply-AcdOQ9Ij.js → pending-edit-apply-uP4uvrOD.js} +1 -1
- package/web/dist/assets/{pieDiagram-E7YTZNPT-CmOEZ8zg.js → pieDiagram-E7YTZNPT-DpeI935H.js} +1 -1
- package/web/dist/assets/{quadrantDiagram-AXDQQJYC-VW6fAhsV.js → quadrantDiagram-AXDQQJYC-B7IuNvWa.js} +1 -1
- package/web/dist/assets/{railroadDiagram-O6MQD6OU-CKJ7Ba5K.js → railroadDiagram-O6MQD6OU-ARzYpPQI.js} +1 -1
- package/web/dist/assets/{requirementDiagram-EFPCY7ZU-pNGkq9Vw.js → requirementDiagram-EFPCY7ZU-Cj4upkAo.js} +1 -1
- package/web/dist/assets/{rotate-cw-KAfChbGV.js → rotate-cw-CXJcyHWk.js} +1 -1
- package/web/dist/assets/{sankeyDiagram-P5KCCOFB-BIMmDTfZ.js → sankeyDiagram-P5KCCOFB-OaVwSnLP.js} +1 -1
- package/web/dist/assets/{sequenceDiagram-WJ2MYXX4-BBW7wzKY.js → sequenceDiagram-WJ2MYXX4-1yrUbq4t.js} +1 -1
- package/web/dist/assets/{sizeCapture-X5ZJPWSS-DdkXmLYR.js → sizeCapture-X5ZJPWSS-nYH5p9D6.js} +1 -1
- package/web/dist/assets/{square-dashed-mouse-pointer-CDUlhUP7.js → square-dashed-mouse-pointer-CTtTVKoH.js} +1 -1
- package/web/dist/assets/{stateDiagram-HBIQ2CUA-W313Uz16.js → stateDiagram-HBIQ2CUA-Dj5HvyNq.js} +1 -1
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-kJ1m_zJT.js +1 -0
- package/web/dist/assets/{swimlanes-XN3QIQJK-C29TL26K.js → swimlanes-XN3QIQJK-umeT0HpB.js} +1 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-DPSkMbbl.js +8 -0
- package/web/dist/assets/{timeline-definition-24CTP7MA-CF0JMObX.js → timeline-definition-24CTP7MA-MqnQfHKB.js} +1 -1
- package/web/dist/assets/{vennDiagram-4TSXK5OY-DrDFsaH3.js → vennDiagram-4TSXK5OY-b2liyZra.js} +1 -1
- package/web/dist/assets/{wardleyDiagram-VM6X3IG4-CVrnRIS_.js → wardleyDiagram-VM6X3IG4-QMK-zNh1.js} +1 -1
- package/web/dist/assets/{xychartDiagram-S5SC5T6Z-BvkN1zsT.js → xychartDiagram-S5SC5T6Z-D5Mc6tfA.js} +1 -1
- package/web/dist/index.html +1 -1
- package/server/api/memory.js +0 -185
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +0 -583
- package/server/engine/mcp/tools/record-decision.js +0 -140
- package/web/dist/assets/DeckWindow-BpLv5082.js +0 -16
- package/web/dist/assets/channel-DGkccMO1.js +0 -1
- package/web/dist/assets/classDiagram-DTDB5LWJ-BGscw3OD.js +0 -1
- package/web/dist/assets/classDiagram-v2-JRS7N3AN-BGscw3OD.js +0 -1
- package/web/dist/assets/index-GAamh1p4.js +0 -2152
- package/web/dist/assets/stateDiagram-v2-4QOOHH4V-DDhJBZzf.js +0 -1
- package/web/dist/assets/swimlanesDiagram-VK2B7HYN-BfN-Xdmu.js +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiaobuyu/nodesign",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "NoDesign: an infinite canvas workbench for independent creators. An agent builds sites, decks, documents, images and video as real files, on your machine, with your own model key.",
|
|
@@ -40,3 +40,21 @@ export function parseNoteFrontmatter(raw) {
|
|
|
40
40
|
const sm = /(?:^|\n)session:\s*([A-Za-z0-9-]{8,64})\s*(?:\n|$)/.exec(m[1]);
|
|
41
41
|
return { body: raw.slice(m[0].length).replace(/^\n+/, ''), sessionId: sm ? sm[1] : null };
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
/** 能上预览的文本类文件(08-24:md/json 等文件卡从细条升级成带内容预览的卡) */
|
|
45
|
+
export const PREVIEW_EXTS = new Set(['.md', '.markdown', '.txt', '.json', '.csv', '.yaml', '.yml']);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 文本文件预览上卡(08-24):截前 1KB 进 `preview` 字段(不是 `text` —— 那是
|
|
49
|
+
* 便签卡的正文真相,复用会把 NoteFaces 的分面语义漏到文件卡上)。frontmatter
|
|
50
|
+
* 头藏掉(记忆/ 主题文件的 SDK 头对预览是噪音)。完整内容走 artifact-file。
|
|
51
|
+
*/
|
|
52
|
+
export async function decorateFilePreview(item, absPath) {
|
|
53
|
+
try {
|
|
54
|
+
const raw = await fs.readFile(absPath, 'utf8');
|
|
55
|
+
const head = /^---\n[\s\S]{0,1200}?\n---\n?/.exec(raw)?.[0] || '';
|
|
56
|
+
const body = raw.slice(head.length).trimStart();
|
|
57
|
+
item.preview = body.length > 1024 ? body.slice(0, 1024) + '…' : body;
|
|
58
|
+
} catch { /* 读不到就没有预览,卡退回细条 */ }
|
|
59
|
+
return item;
|
|
60
|
+
}
|
|
@@ -14,6 +14,7 @@ import path from 'node:path';
|
|
|
14
14
|
import { promises as fs } from 'node:fs';
|
|
15
15
|
import { safeSegment, parseNoteFrontmatter } from './helpers.js';
|
|
16
16
|
import { patchBoard } from '../../projects/board-store.js';
|
|
17
|
+
import { MEMORY_DIR_NAME } from '../../engine/agent/memory-config.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* @param {object} deps
|
|
@@ -173,6 +174,55 @@ export function mountNotesRoutes({ router, guardProject, getSharedDir, ensurePro
|
|
|
173
174
|
} catch (err) { next(err); }
|
|
174
175
|
});
|
|
175
176
|
|
|
177
|
+
/**
|
|
178
|
+
* 记忆(记忆/<name>.md,2026-08-24 记忆体系改版):用户侧改 / 删。
|
|
179
|
+
* 记忆是 agent 的长期状态,画布可见的设计承诺是"用户看得到也**能改**、
|
|
180
|
+
* 改了算数"——这两条路由就是"能改"的那半。frontmatter(SDK 的
|
|
181
|
+
* name/description/metadata 头)与板书同一套保头逻辑:正文只收正文。
|
|
182
|
+
* ⚠️ MEMORY.md 是索引不在这条路上编辑(索引由 agent 按 SDK 合同维护)。
|
|
183
|
+
*/
|
|
184
|
+
const resolveMemoryNote = (pid, name) => {
|
|
185
|
+
const base = path.join(getSharedDir(pid), MEMORY_DIR_NAME);
|
|
186
|
+
const file = path.resolve(base, name);
|
|
187
|
+
return file.startsWith(base + path.sep) ? file : null;
|
|
188
|
+
};
|
|
189
|
+
router.put('/:pid/memory-notes/:name', express.json(), async (req, res, next) => {
|
|
190
|
+
try {
|
|
191
|
+
if (!guardProject(req, res)) return;
|
|
192
|
+
const { name } = req.params;
|
|
193
|
+
if (!safeNoteSegment(name, { md: true })) return res.status(400).json({ error: 'invalid filename' });
|
|
194
|
+
if (name === 'MEMORY.md') return res.status(400).json({ error: 'MEMORY.md is the index — edit topic files instead' });
|
|
195
|
+
const text = String(req.body?.text ?? '');
|
|
196
|
+
if (!text.trim()) return res.status(400).json({ error: 'text required' });
|
|
197
|
+
if (text.length > 40_000) return res.status(400).json({ error: 'too long (max 40k chars)' });
|
|
198
|
+
const file = resolveMemoryNote(req.params.pid, name);
|
|
199
|
+
if (!file) return res.status(400).json({ error: 'invalid path' });
|
|
200
|
+
let head = '';
|
|
201
|
+
try { head = /^---\n[\s\S]{0,1200}?\n---\n?/.exec(await fs.readFile(file, 'utf8'))?.[0] || ''; } catch { /* 新文件 */ }
|
|
202
|
+
const incomingHead = /^---\n[\s\S]{0,1200}?\n---\n?/.exec(text)?.[0] || '';
|
|
203
|
+
const body = text.slice(incomingHead.length).replace(/^\n+/, '');
|
|
204
|
+
if (!body.trim()) return res.status(400).json({ error: 'text required' });
|
|
205
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
206
|
+
await fs.writeFile(file, `${head || incomingHead}${head || incomingHead ? '\n' : ''}${body.replace(/\n+$/, '')}\n`, 'utf8');
|
|
207
|
+
res.json({ ok: true, path: `${MEMORY_DIR_NAME}/${name}` });
|
|
208
|
+
} catch (err) { next(err); }
|
|
209
|
+
});
|
|
210
|
+
router.delete('/:pid/memory-notes/:name', async (req, res, next) => {
|
|
211
|
+
try {
|
|
212
|
+
if (!guardProject(req, res)) return;
|
|
213
|
+
const { name } = req.params;
|
|
214
|
+
if (!safeNoteSegment(name, { md: true })) return res.status(400).json({ error: 'invalid filename' });
|
|
215
|
+
const file = resolveMemoryNote(req.params.pid, name);
|
|
216
|
+
if (!file) return res.status(400).json({ error: 'invalid path' });
|
|
217
|
+
try { await fs.unlink(file); } catch (err) {
|
|
218
|
+
if (err.code === 'ENOENT') return res.status(404).json({ error: 'not found' });
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
221
|
+
try { await patchBoard(req.params.pid, { objects: { [`${MEMORY_DIR_NAME}/${name}`]: null } }); } catch { /* 清不掉不挡删除 */ }
|
|
222
|
+
res.status(204).end();
|
|
223
|
+
} catch (err) { next(err); }
|
|
224
|
+
});
|
|
225
|
+
|
|
176
226
|
/** DELETE /:pid/task-notes/:filename — 删便利贴 */
|
|
177
227
|
router.delete('/:pid/task-notes/:filename', async (req, res, next) => {
|
|
178
228
|
try {
|
package/server/api/assets.js
CHANGED
|
@@ -18,17 +18,18 @@ import { guardProject } from './_guard.js';
|
|
|
18
18
|
import {
|
|
19
19
|
getSharedDir, ensureProjectWorkspace, removeSessionWorkspace, commitWorkspace,
|
|
20
20
|
} from '../projects/workspace.js';
|
|
21
|
-
import { patchBoard, readBoard, reconcileBoardRenames, forwardId, forwardPath, renameBoardPaths } from '../projects/board-store.js';
|
|
21
|
+
import { patchBoard, readBoard, reconcileBoardRenames, pruneDanglingBindings, forwardId, forwardPath, renameBoardPaths } from '../projects/board-store.js';
|
|
22
22
|
import { moveEntry, MoveError } from '../projects/move-entry.js';
|
|
23
23
|
import { reconcileAutoRefsThrottled } from '../lib/auto-relations.js';
|
|
24
24
|
import { taskManifest, ENTRY_FILE, KIND_SITE, docxClaimedFiles, isDirArtifact } from '../lib/artifact-target.js';
|
|
25
|
-
import { RESERVED_DIRS, HARD_IGNORE_DIRS, DRAFTS_DIR, isReservedFile } from '../lib/task-scan.js';
|
|
25
|
+
import { RESERVED_DIRS, HARD_IGNORE_DIRS, DRAFTS_DIR, isReservedFile, loadIgnore } from '../lib/task-scan.js';
|
|
26
|
+
import { OUTPUT_DIRS } from '../lib/kinds/site.js';
|
|
26
27
|
import { listReferences } from '../lib/reference-assets.js';
|
|
27
28
|
import { resolveArtifactFile, isServablePath } from '../lib/artifact-file-path.js';
|
|
28
29
|
import { getProjectCover } from '../lib/cover.js';
|
|
29
30
|
import { makeDocxPageHandler, makeDocxPdfHandler } from './assets/docx-page.js';
|
|
30
31
|
import { mountNotesRoutes } from './assets/notes.js';
|
|
31
|
-
import { safeSegment, decorateNoteText } from './assets/helpers.js';
|
|
32
|
+
import { safeSegment, decorateNoteText, decorateFilePreview, PREVIEW_EXTS } from './assets/helpers.js';
|
|
32
33
|
import { CHALK_DIR } from '../lib/chalk.js';
|
|
33
34
|
import {
|
|
34
35
|
sendImage, isThumbPath, findOriginalForThumbnail, imageCacheControl,
|
|
@@ -254,10 +255,10 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
254
255
|
// 那个接口,而迁移一旦晚于第一次渲染,用户会先看到一个叫 `tasks` 的文件夹
|
|
255
256
|
// 套着他的文件夹。跑过之后是三次 stat 的事(幂等早退),不值得省。
|
|
256
257
|
await ensureProjectWorkspace(req.params.pid);
|
|
257
|
-
// 跟上 agent
|
|
258
|
-
// 是为了让用户看到的第一帧就是对齐过的。没有新 commit 时是一次 rev-parse。
|
|
258
|
+
// 跟上 agent 在画布背后做的改名(挂在这让第一帧就对齐;无新 commit 时只一次 rev-parse)
|
|
259
259
|
await reconcileBoardRenames(req.params.pid).catch(
|
|
260
260
|
(err) => console.warn('[board] 改名对账失败:', err.message));
|
|
261
|
+
await pruneDanglingBindings(req.params.pid).catch(() => {}); // 悬空线 30s 节流清扫(理由见 board-store)
|
|
261
262
|
|
|
262
263
|
const assetsDir = path.join(getSharedDir(req.params.pid), 'assets');
|
|
263
264
|
const artifacts = [];
|
|
@@ -349,8 +350,9 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
349
350
|
item.meta = JSON.parse(metaRaw);
|
|
350
351
|
} catch { /* 无 sidecar(旧图)→ 无 meta */ }
|
|
351
352
|
}
|
|
352
|
-
//
|
|
353
|
+
// 便签/板书带正文、文本文件带 1KB 预览(完整内容都走 artifact-file;见 helpers)
|
|
353
354
|
if (kind === 'note' && ext === '.md') await decorateNoteText(item, path.join(dir, e.name));
|
|
355
|
+
else if (kind === 'task-file' && PREVIEW_EXTS.has(ext)) await decorateFilePreview(item, path.join(dir, e.name));
|
|
354
356
|
artifacts.push(item);
|
|
355
357
|
}
|
|
356
358
|
};
|
|
@@ -376,6 +378,7 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
376
378
|
// 磁盘上的位置是同一个字符串 —— 这样 agent `mv` 一个文件之后,git 的改名
|
|
377
379
|
// 检测能直接翻译成画布 id 的改名(见 board-store 的 reconcileBoardRenames)。
|
|
378
380
|
const workspaceRoot = getSharedDir(req.params.pid);
|
|
381
|
+
const rootIgnore = await loadIgnore(workspaceRoot);
|
|
379
382
|
const tasks = [];
|
|
380
383
|
let hasRootSite = false;
|
|
381
384
|
// 根站认领的一级子目录(pages 顶层段)—— 散文件过滤要用同一口径
|
|
@@ -474,6 +477,8 @@ router.get('/:pid/artifacts', async (req, res, next) => {
|
|
|
474
477
|
if (!e.isDirectory() || e.name.startsWith('.')) continue;
|
|
475
478
|
if (RESERVED_DIRS.has(e.name) || HARD_IGNORE_DIRS.has(e.name)) continue;
|
|
476
479
|
if (e.name === DRAFTS_DIR) continue; // 站点试作,由 site 解析器管
|
|
480
|
+
// 构建目录不当独立站/收纳夹(site:dist 案);递归也吃根 .ndignore(与页面清单同规则)
|
|
481
|
+
if (OUTPUT_DIRS.includes(e.name) || rootIgnore(under(rel, e.name), true)) continue;
|
|
477
482
|
if (claimed.has(e.name)) continue; // 已经是一件产物了
|
|
478
483
|
folders.push(under(rel, e.name));
|
|
479
484
|
await collect(path.join(dir, e.name), under(rel, e.name), depth + 1);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* server/api/instruction.js — 项目 Instruction 文件读写
|
|
3
3
|
*
|
|
4
|
-
* Instruction = workspace
|
|
4
|
+
* Instruction = workspace/CLAUDE.md(2026-08-24 挪到工作区根:画布可见 + SDK 根目录也原生注入)。SDK 启用 settingSources:
|
|
5
5
|
* ['project'] 后 agent 进每次 session 自动读到 system prompt。S1 已经
|
|
6
6
|
* 在 ensureProjectWorkspace 写入模板 starter,本阶段加 GET/PUT 让前端
|
|
7
7
|
* "项目背景" tab 直接编辑该文件。
|
|
@@ -37,7 +37,7 @@ router.get('/:pid/instruction', async (req, res, next) => {
|
|
|
37
37
|
// ensureProjectWorkspace 幂等补齐(老 project 第一次进 instruction tab 也能读到)
|
|
38
38
|
await ensureProjectWorkspace(req.params.pid);
|
|
39
39
|
|
|
40
|
-
const filePath = path.join(getSharedDir(req.params.pid),
|
|
40
|
+
const filePath = path.join(getSharedDir(req.params.pid), INSTRUCTION_FILE);
|
|
41
41
|
try {
|
|
42
42
|
const content = await fs.readFile(filePath, 'utf8');
|
|
43
43
|
res.json({ content, exists: true });
|
|
@@ -66,7 +66,7 @@ router.put('/:pid/instruction', async (req, res, next) => {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
await ensureProjectWorkspace(req.params.pid);
|
|
69
|
-
const filePath = path.join(getSharedDir(req.params.pid),
|
|
69
|
+
const filePath = path.join(getSharedDir(req.params.pid), INSTRUCTION_FILE);
|
|
70
70
|
await fs.writeFile(filePath, content, 'utf8');
|
|
71
71
|
res.json({ ok: true });
|
|
72
72
|
} catch (err) { next(err); }
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* run.rate_limit { info } rate limit 变化
|
|
42
42
|
* run.image_generated { path, sizeBytes, prompt, assetRole, ... } generate_image 完成(Phase A)
|
|
43
43
|
* board.updated { objectId, zoneId, summary } pin_to_board 改画布布局(sessionId:null 广播,前端整份重拉 board.json)
|
|
44
|
-
* board.focus { rect, tag, layer }
|
|
44
|
+
* board.focus { rect, tag, layer, soft, chalk } agent 落了草图/板书(sessionId:null 广播;黑板模式跟镜头,chalk 进精灵追踪)
|
|
45
45
|
* project.active_session { activeSessionId } 项目级会话指针变更(故意不带 sessionId —— 见构造器注释)
|
|
46
46
|
*
|
|
47
47
|
* 外层把 EventBus 桥接到:
|
|
@@ -271,6 +271,16 @@ export const Events = {
|
|
|
271
271
|
// C4 FileChanged hook → 前端 reload iframe
|
|
272
272
|
fileChanged: (filePath, event) => ({ type: 'run.file_changed', filePath, event }),
|
|
273
273
|
|
|
274
|
+
/**
|
|
275
|
+
* 板书/草图落定广播(sessionId:null)。原是 write-on-board / sketch-on-board
|
|
276
|
+
* 里两份内联对象 —— 08-24 板书进精灵追踪链后它有了第三个读者(在场 reducer
|
|
277
|
+
* 认 chalk 字段),形状收进构造器钉住(前端 parity 测试对着这里逐字校)。
|
|
278
|
+
* chalk = 板书文件的工作区相对路径(就是它的画布 id);草图不传。
|
|
279
|
+
*/
|
|
280
|
+
boardFocus: (rect, { tag = null, layer = '', soft = false, chalk = null } = {}) => (
|
|
281
|
+
{ type: 'board.focus', sessionId: null, rect, tag, layer, soft, chalk }
|
|
282
|
+
),
|
|
283
|
+
|
|
274
284
|
// ── Phase 1 翻译补全 ──
|
|
275
285
|
|
|
276
286
|
// SDKAPIRetryMessage(sdk.d.ts:2322):API 请求失败可重试。
|
|
@@ -34,11 +34,14 @@ export function makePostToolUseFailureHandler({ ctx, projectId, sessionId }) {
|
|
|
34
34
|
// 表面上活儿还是干完的,于是"某个工具本周失败 40 次"没人知道。
|
|
35
35
|
// fail-soft:记录本身绝不能变成新的故障源。
|
|
36
36
|
try {
|
|
37
|
+
// Bash 把命令带进 detail(不进指纹):exit 144 那类静默死的命令,detail 里
|
|
38
|
+
// 只有一句 "Exit code N",事后连是谁死的都考证不了(08-24 案,转录已清)
|
|
39
|
+
const cmd = tool === 'Bash' ? String(input?.tool_input?.command || '').slice(0, 300) : '';
|
|
37
40
|
recordIssue({
|
|
38
41
|
source: 'auto',
|
|
39
42
|
toolName: tool,
|
|
40
43
|
summary: `${tool} 失败:${error.slice(0, 120)}`,
|
|
41
|
-
detail: error,
|
|
44
|
+
detail: cmd ? `${error}\n[cmd] ${cmd}` : error,
|
|
42
45
|
projectId,
|
|
43
46
|
sessionId,
|
|
44
47
|
signature: signatureOf(`${tool}|${error}`),
|
|
@@ -70,6 +73,13 @@ export function makePostToolUseFailureHandler({ ctx, projectId, sessionId }) {
|
|
|
70
73
|
+ ' 1. sandbox 拦截(命令访问越界文件 / 不允许的网络)→ 换 Read / Glob / Grep / MCP 工具\n'
|
|
71
74
|
+ ' 2. cwd 越界 → 路径相对 workspace\n'
|
|
72
75
|
+ ' 3. 命令本身错(参数 / 文件不存在)→ 检查 stderr';
|
|
76
|
+
} else if (/_batch$/.test(tool)) {
|
|
77
|
+
// batch 一步失败整批标错,但失败步之前的动作(click / type 这类非幂等的)
|
|
78
|
+
// **已经执行过了** —— 兜底那句"先重试 1 次"对 batch 是错的,会重放前面的步骤。
|
|
79
|
+
advice =
|
|
80
|
+
`${tool} 失败:${error.slice(0, 200)}\n`
|
|
81
|
+
+ '返回文本第一行标了失败在第几步。**不要整批重跑** —— 失败步之前的动作已经执行过了;\n'
|
|
82
|
+
+ '看当前状态(返回末尾的截图),只从失败那一步起继续(单独调用或开一个新 batch)。';
|
|
73
83
|
} else if (tool === 'Write' || tool === 'Edit') {
|
|
74
84
|
advice =
|
|
75
85
|
`${tool} 失败。检查:\n`
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Events } from '../events.js';
|
|
6
6
|
import { getQuery } from '../../runs/active-runs.js';
|
|
7
|
-
import { mutateSpecJson } from '../../../projects/workspace.js';
|
|
8
7
|
import { listWorkspaceArtifacts } from '../../../lib/artifact-target.js';
|
|
9
8
|
import { resetTurnMemory } from './turn-state-memory.js';
|
|
10
9
|
|
|
@@ -104,11 +103,11 @@ export function makeStopReflectionHandler({ ctx, workspaceRoot }) {
|
|
|
104
103
|
const remain = Math.max(0, realMax - used).toLocaleString();
|
|
105
104
|
let body;
|
|
106
105
|
if (hit.tone === 'soft') {
|
|
107
|
-
body = `上下文已用 ${percent}%(${usedStr}/${maxStr} tokens
|
|
106
|
+
body = `上下文已用 ${percent}%(${usedStr}/${maxStr} tokens),还能再写一阵;下一个段落收尾时把当前进度记进记忆(记忆/ 主题文件+MEMORY.md 一行)或落一张便利贴(Write notes/<slug>.md),避免后续 compact 丢上下文。`;
|
|
108
107
|
} else if (hit.tone === 'firm') {
|
|
109
108
|
body = `上下文已用 ${percent}%(${usedStr}/${maxStr},剩 ~${remain})。从下一轮开始整理结论 / 落档,避免被自动 compact 硬切。`;
|
|
110
109
|
} else {
|
|
111
|
-
body = `上下文已用 ${percent}%(${usedStr}/${maxStr})。已逼近 SDK auto-compact 触发线(90
|
|
110
|
+
body = `上下文已用 ${percent}%(${usedStr}/${maxStr})。已逼近 SDK auto-compact 触发线(90%);立即收尾并把结论存进记忆(记忆/)或便利贴(notes/),否则下一轮可能被压缩中断当前思路。`;
|
|
112
111
|
}
|
|
113
112
|
warnContextUsage = `<system-reminder>\n[context-usage] ${body}\n</system-reminder>`;
|
|
114
113
|
}
|
|
@@ -129,46 +128,18 @@ export function makeStopReflectionHandler({ ctx, workspaceRoot }) {
|
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/**
|
|
132
|
-
* PostCompact handler
|
|
131
|
+
* PostCompact handler —— compact 后重置每轮注入的状态指纹(摘要把上一轮状态吞了,
|
|
132
|
+
* "同上轮"没有所指,下一轮要重新全量)。
|
|
133
133
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* 失败 fail-soft:spec.json 写不进去 console.warn 但不抛错(不阻塞 query)。
|
|
134
|
+
* 历史:这里曾把 compact_summary 持久化进 spec.history[](auto-memory 启用前的
|
|
135
|
+
* 平行记忆方案)。2026-08-24 记忆体系改版拆除 —— 压缩前的要点保存交给 SDK 的
|
|
136
|
+
* auto-memory(系统提示明写"存记忆要在本轮回复里完成"),spec.json 不再当记忆载体。
|
|
137
|
+
* run.compact_persisted 事件随之停发(前端消费已同批拆)。
|
|
139
138
|
*/
|
|
140
|
-
export function makePostCompactHandler({ ctx, workspaceRoot, sessionId }) {
|
|
141
|
-
return async (
|
|
139
|
+
export function makePostCompactHandler({ ctx: _ctx, workspaceRoot: _ws, sessionId }) {
|
|
140
|
+
return async (_input, _toolUseId, _options) => {
|
|
142
141
|
try {
|
|
143
|
-
// 压缩后上一轮的状态块已经被摘要吞了,"同上轮"没有所指 —— 让下一轮重新全量
|
|
144
142
|
resetTurnMemory(sessionId);
|
|
145
|
-
if (!workspaceRoot) return {};
|
|
146
|
-
const summary = input?.compact_summary;
|
|
147
|
-
if (!summary || typeof summary !== 'string') return {};
|
|
148
|
-
|
|
149
|
-
// 串行 read-modify-write 防 spec.json 三路并发覆盖(详见 workspace.js mutateSpecJson)
|
|
150
|
-
// historyCount 在回调内 capture 出去 —— 之前 emit 里直接引用 spec 是 ReferenceError,
|
|
151
|
-
// 整段被 try 静默吞,导致 run.compact_persisted 事件永远不发。
|
|
152
|
-
let historyCount = 0;
|
|
153
|
-
await mutateSpecJson(workspaceRoot, (spec) => {
|
|
154
|
-
if (!Array.isArray(spec.history)) spec.history = [];
|
|
155
|
-
spec.history.push({
|
|
156
|
-
ts: new Date().toISOString(),
|
|
157
|
-
source: 'compact',
|
|
158
|
-
trigger: input.trigger || 'auto',
|
|
159
|
-
summary,
|
|
160
|
-
});
|
|
161
|
-
historyCount = spec.history.length;
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
ctx.emit({
|
|
166
|
-
type: 'run.compact_persisted',
|
|
167
|
-
trigger: input.trigger || 'auto',
|
|
168
|
-
summaryLength: summary.length,
|
|
169
|
-
historyCount,
|
|
170
|
-
});
|
|
171
|
-
} catch { /* emit fail-safe */ }
|
|
172
143
|
} catch (err) {
|
|
173
144
|
console.warn(`[hooks/PostCompact] handler threw:`, err.message);
|
|
174
145
|
}
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* SDK 默认行为。如未来观察到 agent 反复 record_decision 信号稀释,再考虑
|
|
10
10
|
* 加回(那时改成更精准的 anti-loop 检测,不是无脑注引导)。
|
|
11
11
|
*/
|
|
12
|
-
import path from 'node:path';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* PostToolUse(screenshot_canvas) handler — agent 截图后引导它做视觉自检。
|
|
@@ -167,40 +166,7 @@ export function makePostToolUseGenerateImageRegenWatchdog() {
|
|
|
167
166
|
};
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
* 只在这一笔上注、每 session 一次:
|
|
175
|
-
* - 品牌档案 `agent-memory/brand/memory.md` —— 前端 BrandCard 会把色板 /
|
|
176
|
-
* 字体渲染出来,是结构化资产,agent 不写就永远空着
|
|
177
|
-
* - 项目指引 `.claude/CLAUDE.md` —— SDK 每次 session 自动读进 system prompt,
|
|
178
|
-
* 但只有用户能决定要不要固化,所以是"问一句"不是"直接写"
|
|
179
|
-
*
|
|
180
|
-
* 通用偏好不在这儿管:那是 SDK 自动记忆的活(autoMemoryDirectory 已指到
|
|
181
|
-
* .claude/agent-memory/auto,前端记忆卡直接显示)。
|
|
182
|
-
*/
|
|
183
|
-
export function makePostToolUseStyleAnchorNudge({ sharedRoot }) {
|
|
184
|
-
let nudged = false;
|
|
185
|
-
const ANCHOR_RE = /(style-anchor|风格锚|锚定|视觉基调|palette|配色方案)/i;
|
|
186
|
-
return async (input, _toolUseId, _options) => {
|
|
187
|
-
if (nudged) return {};
|
|
188
|
-
const t = input?.tool_input || {};
|
|
189
|
-
const blob = `${t.topic || ''} ${t.title || ''} ${t.decision || ''} ${t.rationale || ''}`;
|
|
190
|
-
if (!ANCHOR_RE.test(blob)) return {};
|
|
191
|
-
nudged = true;
|
|
192
|
-
const guidePath = sharedRoot ? path.join(sharedRoot, '.claude', 'CLAUDE.md') : '.claude/CLAUDE.md';
|
|
193
|
-
return {
|
|
194
|
-
hookSpecificOutput: {
|
|
195
|
-
hookEventName: 'PostToolUse',
|
|
196
|
-
additionalContext:
|
|
197
|
-
'这一笔看起来是在锚定这个项目的视觉方向。顺手做两件事(都只做一次):\n'
|
|
198
|
-
+ '1. 把这版风格写进 `.claude/agent-memory/brand/memory.md`(色号 / 字体链 / 版式语言 / 动效预算;⚠️ 带 .claude/ 前缀,写错位置 BrandCard 读不到),'
|
|
199
|
-
+ '前端品牌档案卡会把色板和字体渲染出来给用户看,不写就一直空着。\n'
|
|
200
|
-
+ `2. 如果这次定下来的还包含**项目级约束**(不只这一个 deck 适用,比如"这个项目一律不用 emoji"),`
|
|
201
|
-
+ `在收尾时问用户一句要不要写进项目指引(${guidePath},SDK 每次 session 自动读它)。用户点头你再写。\n`
|
|
202
|
-
+ '用户的通用偏好不用你手动记,系统的自动记忆会管。',
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
};
|
|
206
|
-
}
|
|
169
|
+
// (makePostToolUseStyleAnchorNudge 2026-08-24 随 record_decision 一起拆除:
|
|
170
|
+
// 风格落盘提醒并进 auto-memory 追加指导;品牌档案文件本身已并入工作区根
|
|
171
|
+
// CLAUDE.md 的「风格档案」节,BrandCard 同批退役。)
|
|
172
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UserPromptSubmit handler — 每次用户输入前注入工作区**状态**(2026-08-21 重做)
|
|
3
3
|
*
|
|
4
|
-
* 注的是状态不是指令:cwd
|
|
4
|
+
* 注的是状态不是指令:cwd、素材清单、便利贴、画布关系线、产物清单。
|
|
5
5
|
* 怎么用这些东西(路径表、"先看有没有现成的素材"、工具怎么选)住 prelude,那是缓存的
|
|
6
6
|
* system prompt;每轮再说一遍是上下文里的 N 倍重复。
|
|
7
7
|
*
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import fs from 'node:fs/promises';
|
|
25
25
|
import path from 'node:path';
|
|
26
|
-
import { readUiConfigFile } from '../../../projects/ui-config.js';
|
|
26
|
+
import { readUiConfigFile, withUiDefaults } from '../../../projects/ui-config.js';
|
|
27
27
|
import { readAssetsSummary } from '../../../projects/assets-summary.js';
|
|
28
28
|
import { relationsDigest } from '../../../lib/board-relations.js';
|
|
29
29
|
import { getViewpoint, describeViewpoint } from '../../../projects/viewpoint-store.js';
|
|
@@ -37,11 +37,6 @@ import {
|
|
|
37
37
|
} from '../../../lib/artifact-target.js';
|
|
38
38
|
import { getTurnMemory, setTurnMemory, fingerprint, diffItems } from './turn-state-memory.js';
|
|
39
39
|
|
|
40
|
-
/** UserPromptSubmit hook 读取 spec.json 时的最大字节数 */
|
|
41
|
-
const SPEC_JSON_MAX_BYTES = 200 * 1024;
|
|
42
|
-
/** spec.json.decisions 注入摘要时取最近 N 条 */
|
|
43
|
-
const SPEC_DECISIONS_TAIL = 5;
|
|
44
|
-
|
|
45
40
|
/** PDF/Office 文档的解法(系统自带工具;python 包没装且装不上 —— 08-19 上报实锤)。只在首次出现二进制文档时说一遍。 */
|
|
46
41
|
const BINARY_DOC_HINT = 'PDF / PPTX / DOCX / XLSX 直接 Read 拿不到结构化内容(二进制或 zip 包)。用系统自带的工具解'
|
|
47
42
|
+ '(pdfplumber/PyPDF2/python-docx/openpyxl 这些 python 包**没装且装不上**,别试):'
|
|
@@ -72,25 +67,7 @@ async function collectSections({ workspaceRoot, sessionId, projectId }) {
|
|
|
72
67
|
}
|
|
73
68
|
} catch { /* 素材读不到就沉默 */ }
|
|
74
69
|
|
|
75
|
-
// spec.json
|
|
76
|
-
try {
|
|
77
|
-
const specPath = path.join(workspaceRoot, 'spec.json');
|
|
78
|
-
const stat = await fs.stat(specPath);
|
|
79
|
-
if (stat.size <= SPEC_JSON_MAX_BYTES) {
|
|
80
|
-
const spec = JSON.parse(await fs.readFile(specPath, 'utf8'));
|
|
81
|
-
const decisions = Array.isArray(spec?.decisions) ? spec.decisions : [];
|
|
82
|
-
if (decisions.length > 0) {
|
|
83
|
-
const recent = decisions.slice(-SPEC_DECISIONS_TAIL);
|
|
84
|
-
const lines = recent.map((d, i) => {
|
|
85
|
-
const idx = decisions.length - recent.length + i + 1;
|
|
86
|
-
const title = (d?.title || '(无标题)').slice(0, 80);
|
|
87
|
-
const rationale = (d?.rationale || '').slice(0, 200);
|
|
88
|
-
return ` ${idx}. ${title}${rationale ? ` — ${rationale}` : ''}`;
|
|
89
|
-
}).join('\n');
|
|
90
|
-
sections.push({ key: 'decisions', title: '旧决策档案', text: `旧决策档案(spec.json 遗产,共 ${decisions.length} 条,最近 ${recent.length} 条;新决策一律走 record_decision → 任务便利贴):\n${lines}` });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} catch { /* spec.json 不存在 / 解析失败:noop */ }
|
|
70
|
+
// (spec.json 决策档案注入 2026-08-24 拆除:决策体系退役,长期事实走 CLAUDE.md/记忆)
|
|
94
71
|
|
|
95
72
|
// 便利贴:metadata-not-content,只列文件和首行标题
|
|
96
73
|
try {
|
|
@@ -160,8 +137,8 @@ async function collectSections({ workspaceRoot, sessionId, projectId }) {
|
|
|
160
137
|
}
|
|
161
138
|
} catch { /* 扫不动就不说 */ }
|
|
162
139
|
|
|
163
|
-
//
|
|
164
|
-
//
|
|
140
|
+
// 最近板书(2026-08-23;08-24 记忆改版时被误删,同日修回):你/用户在画布上
|
|
141
|
+
// 说过的最近几句 —— 对话在板上,得记得板上说了什么
|
|
165
142
|
try {
|
|
166
143
|
const recent = await recentChalk(workspaceRoot, { limit: 8 });
|
|
167
144
|
if (recent.length) {
|
|
@@ -169,10 +146,11 @@ async function collectSections({ workspaceRoot, sessionId, projectId }) {
|
|
|
169
146
|
sections.push({ key: 'chalk', title: '最近板书', text: `画布上最近的板书(${CHALK_DIR}/,新在前;正文 Read 文件):\n${lines.join('\n')}`, items: recent.map(c => c.path) });
|
|
170
147
|
}
|
|
171
148
|
} catch { /* 板书读不到就沉默 */ }
|
|
172
|
-
|
|
149
|
+
|
|
150
|
+
// 黑板模式(2026-08-23;08-24 起默认开 —— 没写过这个键的按开算,显式 false 才算关)
|
|
173
151
|
try {
|
|
174
|
-
const cfg = await readUiConfigFile(workspaceRoot);
|
|
175
|
-
if (cfg
|
|
152
|
+
const cfg = withUiDefaults(await readUiConfigFile(workspaceRoot));
|
|
153
|
+
if (cfg.blackboard_mode === true) {
|
|
176
154
|
sections.push({ key: 'blackboard', title: '黑板模式', text:
|
|
177
155
|
'【黑板模式:开】用户此刻在画布上专注思考。这一轮默认这么做:想事情就画成图(sketch_on_board,'
|
|
178
156
|
+ '小改动用 edit_sketch 原地改别重画);做完一件东西在它旁边写一条板书(write_on_board near=);'
|
|
@@ -180,15 +158,8 @@ async function collectSections({ workspaceRoot, sessionId, projectId }) {
|
|
|
180
158
|
+ '尺寸守规范(0.8 倍一屏可读、正文 md 起、一条板书说一件事);画完 look_at_board 看一眼再收。' });
|
|
181
159
|
}
|
|
182
160
|
} catch { /* 读失败:不注入 */ }
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (cfg) {
|
|
186
|
-
const on = cfg?.tweaks_mode_enabled !== false;
|
|
187
|
-
sections.push({ key: 'tweaks', title: 'Tweaks 开关', text: on
|
|
188
|
-
? '【Tweaks 模式:启用】用户偏好"可调产品"——deck 形态稳定后,建议调 expose_tweaks 暴露核心微调参数(颜色 / 字号 / 排版密度等)让用户拖滑杆即时改样式。'
|
|
189
|
-
: '【Tweaks 模式:禁用】用户在 toolbar 关闭了 Tweaks——这次跳过 expose_tweaks,按对话方式让用户提需求你来 Edit。已暴露的 controls 保留不动,不新增 / 不重 expose。' });
|
|
190
|
-
}
|
|
191
|
-
} catch { /* 读失败:默认行为,不注入 */ }
|
|
161
|
+
|
|
162
|
+
// (tweaks 开关注入 2026-08-24 随 expose_tweaks 暂退役一起摘除;工具升级后再回来)
|
|
192
163
|
|
|
193
164
|
return sections;
|
|
194
165
|
}
|
|
@@ -84,7 +84,6 @@ import {
|
|
|
84
84
|
makePostToolUseScreenshotHandler,
|
|
85
85
|
makePostToolUseExportHandler,
|
|
86
86
|
makePostToolUseGenerateImageRegenWatchdog,
|
|
87
|
-
makePostToolUseStyleAnchorNudge,
|
|
88
87
|
} from './hooks/post-guidance.js';
|
|
89
88
|
import { makePostToolUseCanvasValidationHandler } from './hooks/canvas-validate.js';
|
|
90
89
|
import { makePostToolUseSiteValidationHandler } from './hooks/site-validate.js';
|
|
@@ -294,13 +293,10 @@ export function createHooks({ ctx, workspaceRoot, sharedRoot, sessionId, project
|
|
|
294
293
|
makePostToolUseSiteValidationHandler({ workspaceRoot }),
|
|
295
294
|
],
|
|
296
295
|
},
|
|
297
|
-
// record_decision
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
matcher: 'mcp__nodesign__record_decision',
|
|
302
|
-
hooks: [makePostToolUseStyleAnchorNudge({ sharedRoot })],
|
|
303
|
-
},
|
|
296
|
+
// (record_decision 及其 style-anchor nudge 2026-08-24 整条目拆除 ——
|
|
297
|
+
// 风格落盘的提醒并进 auto-memory 的追加指导(session-loop 的
|
|
298
|
+
// CLAUDE_COWORK_MEMORY_EXTRA_GUIDELINES)。删的是**整个 {matcher, hooks}
|
|
299
|
+
// 条目**,空壳条目会让 SDK initialize 静默灭门,见 assertHooksWellFormed。)
|
|
304
300
|
],
|
|
305
301
|
|
|
306
302
|
// PostToolUseFailure —— 任意工具失败时统一处理:emit 事件 + 给 agent
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent/memory-config.js — auto-memory 的产品化配置(2026-08-24 记忆体系改版)
|
|
3
|
+
*
|
|
4
|
+
* 记忆两层的分工(用户拍板):
|
|
5
|
+
* - `记忆/`(工作区根下,画布可见):SDK auto-memory 的家。提示词、写入、
|
|
6
|
+
* MEMORY.md 索引加载、权限放行全跟 autoMemoryDirectory 一个值走(二进制 yh(),
|
|
7
|
+
* 08-24 探明)。用户看得到每一条、也可能直接改。
|
|
8
|
+
* - 根 `CLAUDE.md`:人工筛选的项目档案(指引/风格/习惯),SDK 每会话
|
|
9
|
+
* 确定性全量注入(settingSources 'project' 原生行为,根目录与 .claude/ 两处都读)。
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ EXTRA_GUIDELINES 是**追加**不是替换:SDK 自己的记忆合同(frontmatter 契约、
|
|
12
|
+
* 两步写入、200 行索引上限与截断提醒)原样保留。别换成 CLAUDE_COWORK_MEMORY_GUIDELINES
|
|
13
|
+
* 整段替换 —— 那会把截断提醒一起抹掉,而截断逻辑仍在跑,索引爆了没人说话。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
18
|
+
/** 记忆目录名(工作区根相对)。画布可见是设计要求,不是巧合。 */
|
|
19
|
+
export const MEMORY_DIR_NAME = '记忆';
|
|
20
|
+
|
|
21
|
+
export function memoryDirFor(sharedRoot) {
|
|
22
|
+
return path.join(sharedRoot, MEMORY_DIR_NAME);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 追加进 SDK 记忆提示末尾的产品口径(env CLAUDE_COWORK_MEMORY_EXTRA_GUIDELINES) */
|
|
26
|
+
export const MEMORY_EXTRA_GUIDELINES = [
|
|
27
|
+
'This memory directory (记忆/) is VISIBLE on the user\'s canvas — they can read',
|
|
28
|
+
'every file and may edit them between sessions; treat edits as ground truth.',
|
|
29
|
+
'Keep file names as short English kebab-case slugs (the SDK contract), but write',
|
|
30
|
+
'descriptions and content in the user\'s language (中文 for this product).',
|
|
31
|
+
'When a style decision is settled (palette / fonts / materials / art direction),',
|
|
32
|
+
'record it as a `type: project` memory right away — style anchors are the most',
|
|
33
|
+
'expensive thing to lose between sessions.',
|
|
34
|
+
'Hard constraints and curated project guidance live in the workspace-root',
|
|
35
|
+
'CLAUDE.md (deterministically injected every session) — put lasting rules there',
|
|
36
|
+
'(with the user\'s consent), and put evolving facts here.',
|
|
37
|
+
].join(' ');
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 把我们的 settings 跟 isolationOptions.settings **深合并**成 SDK 的最终 settings。
|
|
41
|
+
*
|
|
42
|
+
* ⛔ 存在的理由(08-24 案):08-15 起 buildIsolationOptions 的返回值里也有
|
|
43
|
+
* settings 键,query options 里对象展开写在后面,把独立写的 settings 整个覆盖 ——
|
|
44
|
+
* autoMemoryEnabled / autoMemoryDirectory / skipWebFetchPreflight 八天没送到
|
|
45
|
+
* SDK 手里(agent 照 SDK 默认路径写记忆被沙盒拒、~/.claude 下堆了 187 个空目录)。
|
|
46
|
+
* 「两处同名键静默互吞」没有任何报错,出口断言在这里兜。
|
|
47
|
+
*/
|
|
48
|
+
export function mergeAgentSettings(isolationSettings, { skipWebFetchPreflight, sharedRoot }) {
|
|
49
|
+
const settings = {
|
|
50
|
+
...isolationSettings,
|
|
51
|
+
skipWebFetchPreflight,
|
|
52
|
+
...(sharedRoot ? {
|
|
53
|
+
autoMemoryEnabled: true,
|
|
54
|
+
autoMemoryDirectory: memoryDirFor(sharedRoot),
|
|
55
|
+
} : {}),
|
|
56
|
+
};
|
|
57
|
+
if (sharedRoot && !settings.autoMemoryDirectory) {
|
|
58
|
+
throw new Error('[memory-config] settings.autoMemoryDirectory 被吞了 —— 检查 settings 合并处');
|
|
59
|
+
}
|
|
60
|
+
return settings;
|
|
61
|
+
}
|