mocode-ai 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/index.js +2 -1
- package/dist/plan/index.js +1 -1
- package/dist/plan/store.js +105 -27
- package/dist/tools/builtins/todolist.js +77 -13
- package/dist/ui/layout.js +1 -1
- package/package.json +1 -1
package/dist/config/index.js
CHANGED
|
@@ -122,7 +122,8 @@ ${PLATFORM_NOTE}
|
|
|
122
122
|
## Working notepad (todolist) — checklist for complex tasks
|
|
123
123
|
- For **complex multi-step tasks** (≥3 file changes OR ≥5 tool calls expected OR user says "先计划再执行" / "plan then do" / "按步骤来"), call the \`todolist\` tool FIRST to write a plan to \`.mocode/plans/<id>.md\`, then execute step by step, calling \`todolist update\` to mark progress. For trivial single-step tasks, skip it and just execute.
|
|
124
124
|
- The plan is file-backed (survives context compression, user can see/edit). The active plan summary is auto-injected into the system prompt each turn, so you can re-read it via \`todolist read\` whenever you're unsure of your place.
|
|
125
|
-
- Single plan per session: \`todolist create\` refuses if an in-progress plan already exists — finish or abandon it first.
|
|
125
|
+
- Single plan per session: \`todolist create\` refuses if an in-progress plan already exists — finish or abandon it first.
|
|
126
|
+
- **Lifecycle** (5 actions total): \`create\` / \`read\` / \`update\` / \`add_step\` / \`finish\` for normal flow. \`finish plan_status=finished\` AUTO-ARCHIVES the plan to \`.mocode/plans/archive/<id>.md\` (history preserved, active dir stays clean). To revisit old plans: \`list scope=archived\` (or \`all\`) + \`unarchive id=<id>\` to bring back. \`delete id=<id>\` permanently removes (any location); cannot delete the currently active plan.
|
|
126
127
|
- Don't over-use it: for a single edit or a quick lookup, \`todolist\` is overhead. The threshold is "this needs ≥3 steps OR I might forget the plan after context compaction."
|
|
127
128
|
|
|
128
129
|
## Termination & Reporting
|
package/dist/plan/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 被 repl 依赖(注入 systemPrompt + 状态行 + listener 注册)+ tools/builtins/todolist 依赖。
|
|
7
7
|
export {
|
|
8
8
|
// store
|
|
9
|
-
plansDir, planPath, ensurePlansDir, newPlanId, parsePlan, serializePlan, readPlan, writePlan, deletePlan, updatePlan, listPlans, renderPlanForLLM, renderPlanChip, } from './store.js';
|
|
9
|
+
plansDir, archiveDir, planPath, ensurePlansDir, ensureArchiveDir, newPlanId, parsePlan, serializePlan, readPlan, writePlan, deletePlan, deletePlanAnywhere, updatePlan, listPlans, archivePlan, unarchivePlan, renderPlanForLLM, renderPlanChip, } from './store.js';
|
|
10
10
|
export { getActivePlan, setActivePlan, clearActivePlan, onActivePlanChange, hasActivePlan, getActivePlanSummary, } from './active.js';
|
|
11
11
|
import { getActivePlan } from './active.js';
|
|
12
12
|
import { renderPlanForLLM } from './store.js';
|
package/dist/plan/store.js
CHANGED
|
@@ -16,9 +16,13 @@ export function plansDir() {
|
|
|
16
16
|
const root = getSandboxRoot() ?? process.cwd();
|
|
17
17
|
return join(root, '.mocode', 'plans');
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
export function
|
|
21
|
-
return join(plansDir(),
|
|
19
|
+
/** 归档目录:<root>/.mocode/plans/archive/。finish 自动归档、用户手动 unarchive 都走这里。 */
|
|
20
|
+
export function archiveDir() {
|
|
21
|
+
return join(plansDir(), 'archive');
|
|
22
|
+
}
|
|
23
|
+
/** 单 plan 文件路径:<root>/.mocode/plans/<id>.md。可选 baseDir:默认 plans,也可指 archive。 */
|
|
24
|
+
export function planPath(id, baseDir = plansDir()) {
|
|
25
|
+
return join(baseDir, `${id}.md`);
|
|
22
26
|
}
|
|
23
27
|
/** 确保 plans 目录存在(惰性,工具每次写前调)。 */
|
|
24
28
|
export function ensurePlansDir() {
|
|
@@ -26,7 +30,24 @@ export function ensurePlansDir() {
|
|
|
26
30
|
if (!existsSync(d))
|
|
27
31
|
mkdirSync(d, { recursive: true });
|
|
28
32
|
}
|
|
33
|
+
/** 确保归档目录存在(惰性)。 */
|
|
34
|
+
export function ensureArchiveDir() {
|
|
35
|
+
const d = archiveDir();
|
|
36
|
+
if (!existsSync(d))
|
|
37
|
+
mkdirSync(d, { recursive: true });
|
|
38
|
+
}
|
|
29
39
|
// ── ID 生成 ──
|
|
40
|
+
/** 本地时间戳(无时区后缀),与 filename 的 YYYY-MM-DDTHH-mm-ss 风格一致。
|
|
41
|
+
* 例:2026-07-04T22:54:51.729(本地时,便于人对读 frontmatter / progress log,
|
|
42
|
+
* 且与同 plan id 文件名"2026-07-04T22-54-51"对应)。
|
|
43
|
+
* 不带 Z 后缀 → JS Date 解析会按本地时读(一致 round-trip);
|
|
44
|
+
* 反例:toISOString() 返 2026-07-04T14:54:51.729Z(UTC),跟用户当地差 8 小时,易误解。 */
|
|
45
|
+
export function localIsoTimestamp(d = new Date()) {
|
|
46
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
47
|
+
const padMs = (n) => String(n).padStart(3, '0');
|
|
48
|
+
return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
|
|
49
|
+
`T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${padMs(d.getMilliseconds())}`);
|
|
50
|
+
}
|
|
30
51
|
/** plan id:YYYY-MM-DDTHH-mm-ss-xxxxxx(本地时间,文件名安全)。xxxxxx = 4 字节随机 hex(防同秒并发撞)。 */
|
|
31
52
|
export function newPlanId(now = new Date()) {
|
|
32
53
|
const pad = (n) => String(n).padStart(2, '0');
|
|
@@ -52,7 +73,7 @@ const LOG_RE = /^-\s+(\S+)\s+(.+?)\s*$/;
|
|
|
52
73
|
* 解析成功但 step id 缺序 / 重号:按出现顺序重排为 1..N(保稳定)。
|
|
53
74
|
*/
|
|
54
75
|
export function parsePlan(raw, fallbackId) {
|
|
55
|
-
const now =
|
|
76
|
+
const now = localIsoTimestamp();
|
|
56
77
|
let meta = {};
|
|
57
78
|
let body = raw;
|
|
58
79
|
const m = FRONTMATTER_RE.exec(raw);
|
|
@@ -184,9 +205,10 @@ function checkboxMarker(s) {
|
|
|
184
205
|
}
|
|
185
206
|
}
|
|
186
207
|
// ── CRUD(直接落盘)──
|
|
187
|
-
/** 读 plan 文件;不存在 → null,坏文件 → fallbackId 最小可用 Plan(不抛,工具契约)。
|
|
188
|
-
|
|
189
|
-
|
|
208
|
+
/** 读 plan 文件;不存在 → null,坏文件 → fallbackId 最小可用 Plan(不抛,工具契约)。
|
|
209
|
+
* 可选 baseDir:默认 plans,传 archiveDir() 可读归档。 */
|
|
210
|
+
export function readPlan(id, baseDir = plansDir()) {
|
|
211
|
+
const p = planPath(id, baseDir);
|
|
190
212
|
if (!existsSync(p))
|
|
191
213
|
return null;
|
|
192
214
|
try {
|
|
@@ -210,9 +232,9 @@ export function writePlan(p) {
|
|
|
210
232
|
return false;
|
|
211
233
|
}
|
|
212
234
|
}
|
|
213
|
-
/** 删 plan 文件;不存在静默 ok
|
|
214
|
-
export function deletePlan(id) {
|
|
215
|
-
const p = planPath(id);
|
|
235
|
+
/** 删 plan 文件;不存在静默 ok。可选 baseDir:默认 plans,传 archiveDir() 删归档。 */
|
|
236
|
+
export function deletePlan(id, baseDir = plansDir()) {
|
|
237
|
+
const p = planPath(id, baseDir);
|
|
216
238
|
if (!existsSync(p))
|
|
217
239
|
return true;
|
|
218
240
|
try {
|
|
@@ -243,33 +265,89 @@ export function updatePlan(id, mutator) {
|
|
|
243
265
|
}
|
|
244
266
|
if (next === false)
|
|
245
267
|
return cur; // 无改动:返原对象
|
|
246
|
-
next.updated =
|
|
268
|
+
next.updated = localIsoTimestamp();
|
|
247
269
|
if (!writePlan(next))
|
|
248
270
|
return null;
|
|
249
271
|
return next;
|
|
250
272
|
}
|
|
251
|
-
/** 列 plans 目录下所有 plan(按 updated 倒序,最新在前)。解析失败的跳过。
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
273
|
+
/** 列 plans 目录下所有 plan(按 updated 倒序,最新在前)。解析失败的跳过。
|
|
274
|
+
* scope: 'active'(默认)=仅 plans/;'archived'=仅 archive/;'all'=合并(去重按 id)。 */
|
|
275
|
+
export function listPlans(scope = 'active') {
|
|
276
|
+
const dirs = [];
|
|
277
|
+
if (scope === 'active')
|
|
278
|
+
dirs.push(plansDir());
|
|
279
|
+
else if (scope === 'archived')
|
|
280
|
+
dirs.push(archiveDir());
|
|
281
|
+
else {
|
|
282
|
+
dirs.push(plansDir());
|
|
283
|
+
dirs.push(archiveDir());
|
|
262
284
|
}
|
|
263
285
|
const out = [];
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
286
|
+
const seen = new Set();
|
|
287
|
+
for (const dir of dirs) {
|
|
288
|
+
if (!existsSync(dir))
|
|
289
|
+
continue;
|
|
290
|
+
let names;
|
|
291
|
+
try {
|
|
292
|
+
names = readdirSync(dir).filter((n) => n.endsWith('.md'));
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
for (const n of names) {
|
|
298
|
+
const id = n.slice(0, -3);
|
|
299
|
+
if (seen.has(id))
|
|
300
|
+
continue;
|
|
301
|
+
seen.add(id);
|
|
302
|
+
const p = readPlan(id, dir);
|
|
303
|
+
if (p)
|
|
304
|
+
out.push(p);
|
|
305
|
+
}
|
|
269
306
|
}
|
|
270
307
|
out.sort((a, b) => (a.updated < b.updated ? 1 : a.updated > b.updated ? -1 : 0));
|
|
271
308
|
return out;
|
|
272
309
|
}
|
|
310
|
+
// ── 归档/还原/全删 ──
|
|
311
|
+
/** 把 plans/<id>.md mv 到 plans/archive/<id>.md(atomic rename)。
|
|
312
|
+
* 源不存在 → false(已归档 / 删了 / 从未存在都算)。 */
|
|
313
|
+
export function archivePlan(id) {
|
|
314
|
+
const src = planPath(id, plansDir());
|
|
315
|
+
if (!existsSync(src))
|
|
316
|
+
return false;
|
|
317
|
+
ensureArchiveDir();
|
|
318
|
+
const dst = planPath(id, archiveDir());
|
|
319
|
+
try {
|
|
320
|
+
renameSync(src, dst);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
/** 把 plans/archive/<id>.md 还原到 plans/<id>.md。源不存在 → false。 */
|
|
328
|
+
export function unarchivePlan(id) {
|
|
329
|
+
const src = planPath(id, archiveDir());
|
|
330
|
+
if (!existsSync(src))
|
|
331
|
+
return false;
|
|
332
|
+
ensurePlansDir();
|
|
333
|
+
const dst = planPath(id, plansDir());
|
|
334
|
+
try {
|
|
335
|
+
renameSync(src, dst);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
/** 任一处存在就都删(活跃 + 归档)。用于 todolist delete 不关心来源。 */
|
|
343
|
+
export function deletePlanAnywhere(id) {
|
|
344
|
+
let any = false;
|
|
345
|
+
if (existsSync(planPath(id, plansDir())))
|
|
346
|
+
any = deletePlan(id, plansDir()) || any;
|
|
347
|
+
if (existsSync(planPath(id, archiveDir())))
|
|
348
|
+
any = deletePlan(id, archiveDir()) || any;
|
|
349
|
+
return any;
|
|
350
|
+
}
|
|
273
351
|
// ── 渲染(给 LLM / UI 用)──
|
|
274
352
|
/** 把 Plan 渲成给 LLM 看的紧凑摘要(多行文本)。 */
|
|
275
353
|
export function renderPlanForLLM(p) {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 单 plan/会话:create 前若有 in_progress 活跃 plan → 拒绝(避免误覆盖);finish/abandoned 后可再建。
|
|
7
7
|
//
|
|
8
8
|
// 工具返回:把当前 plan 紧凑渲染给 LLM(不只返操作结果——让 LLM 单次调用后看到完整状态,无需再 read)。
|
|
9
|
-
import { newPlanId, readPlan, writePlan, updatePlan, listPlans, renderPlanForLLM, } from '../../plan/store.js';
|
|
9
|
+
import { newPlanId, readPlan, writePlan, updatePlan, listPlans, archivePlan, unarchivePlan, deletePlanAnywhere, renderPlanForLLM, localIsoTimestamp, } from '../../plan/store.js';
|
|
10
10
|
import { getActivePlan, setActivePlan, hasActivePlan, clearActivePlan } from '../../plan/active.js';
|
|
11
11
|
import { MAX_OUTPUT } from '../constants.js';
|
|
12
12
|
const VALID_STATUS = new Set([
|
|
@@ -23,14 +23,15 @@ export const todolistTool = {
|
|
|
23
23
|
'For complex multi-step tasks (≥3 file changes or ≥5 tool calls expected, OR user says "先计划再执行" / "plan then do"), CALL THIS FIRST to write the plan, then update each step as you go.',
|
|
24
24
|
'For simple single-step tasks, skip it and just execute.',
|
|
25
25
|
'Single plan per session: create refuses if an in-progress plan already exists — finish or abandon it first.',
|
|
26
|
+
'Lifecycle: finish AUTO-ARCHIVES the plan to .mocode/plans/archive/ (history preserved, active dir stays clean). To revisit, call list with scope=archived or unarchive. Use delete to permanently remove a plan (any location).',
|
|
26
27
|
].join(''),
|
|
27
28
|
parameters: {
|
|
28
29
|
type: 'object',
|
|
29
30
|
properties: {
|
|
30
31
|
action: {
|
|
31
32
|
type: 'string',
|
|
32
|
-
enum: ['create', 'read', 'update', 'add_step', 'finish'],
|
|
33
|
-
description: 'create=新计划;read=读当前活跃;update=改步骤状态;add_step=追加步骤;finish=收尾',
|
|
33
|
+
enum: ['create', 'read', 'update', 'add_step', 'finish', 'list', 'delete', 'unarchive'],
|
|
34
|
+
description: 'create=新计划;read=读当前活跃;update=改步骤状态;add_step=追加步骤;finish=收尾(自动归档);list=列计划;delete=永久删除;unarchive=从归档还原到活跃',
|
|
34
35
|
},
|
|
35
36
|
title: { type: 'string', description: 'create 必填:计划标题' },
|
|
36
37
|
goal: { type: 'string', description: 'create 可选:目标描述(写进「目标」段)' },
|
|
@@ -55,7 +56,16 @@ export const todolistTool = {
|
|
|
55
56
|
plan_status: {
|
|
56
57
|
type: 'string',
|
|
57
58
|
enum: ['finished', 'abandoned'],
|
|
58
|
-
description: 'finish 必填:finished=完成;abandoned=放弃(中途取消)',
|
|
59
|
+
description: 'finish 必填:finished=完成;abandoned=放弃(中途取消)。finished 触发自动归档。',
|
|
60
|
+
},
|
|
61
|
+
scope: {
|
|
62
|
+
type: 'string',
|
|
63
|
+
enum: ['active', 'archived', 'all'],
|
|
64
|
+
description: 'list 可选:范围(active=默认,仅进行中;archived=仅归档;all=全)。',
|
|
65
|
+
},
|
|
66
|
+
id: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'delete / unarchive 必填:目标 plan id(list 可拿到)。',
|
|
59
69
|
},
|
|
60
70
|
},
|
|
61
71
|
required: ['action'],
|
|
@@ -69,8 +79,11 @@ export const todolistTool = {
|
|
|
69
79
|
case 'update': return doUpdate(args);
|
|
70
80
|
case 'add_step': return doAddStep(args);
|
|
71
81
|
case 'finish': return doFinish(args);
|
|
82
|
+
case 'list': return doList(args);
|
|
83
|
+
case 'delete': return doDelete(args);
|
|
84
|
+
case 'unarchive': return doUnarchive(args);
|
|
72
85
|
default:
|
|
73
|
-
return `错误:未知 action「${action}」,合法值:create / read / update / add_step / finish。`;
|
|
86
|
+
return `错误:未知 action「${action}」,合法值:create / read / update / add_step / finish / list / delete / unarchive。`;
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
catch (e) {
|
|
@@ -99,7 +112,7 @@ function doCreate(args) {
|
|
|
99
112
|
return `错误:已存在进行中的 plan「${cur?.title ?? ''}」(id=${cur?.id ?? ''}),需先 finish 后再建。`;
|
|
100
113
|
}
|
|
101
114
|
// 旧 plan 残留(finished/abandoned)→ 允许新建,不删历史(用户可后续 read 历史 plan)
|
|
102
|
-
const now =
|
|
115
|
+
const now = localIsoTimestamp();
|
|
103
116
|
const plan = {
|
|
104
117
|
id: newPlanId(),
|
|
105
118
|
title,
|
|
@@ -195,7 +208,7 @@ function doFinish(args) {
|
|
|
195
208
|
const updated = updatePlan(cur.id, (p) => {
|
|
196
209
|
p.status = ps;
|
|
197
210
|
p.log.push({
|
|
198
|
-
at:
|
|
211
|
+
at: localIsoTimestamp(),
|
|
199
212
|
text: note || (ps === 'finished' ? '完成' : '放弃'),
|
|
200
213
|
});
|
|
201
214
|
return p;
|
|
@@ -203,14 +216,65 @@ function doFinish(args) {
|
|
|
203
216
|
if (!updated)
|
|
204
217
|
return '错误:finish 写盘失败。';
|
|
205
218
|
setActivePlan(updated);
|
|
219
|
+
// finished → 自动归档到 plans/archive/(历史完整保留,活跃目录保持干净)。
|
|
220
|
+
// abandoned → 不归档(用户想丢就丢,但仍在 plans/ 下,可显式 delete 删掉)。
|
|
221
|
+
let archived = false;
|
|
222
|
+
if (ps === 'finished') {
|
|
223
|
+
archived = archivePlan(updated.id);
|
|
224
|
+
}
|
|
206
225
|
// finish 后清活跃缓存(下轮 read 自动从 in_progress 列表兜底,本会话不再「活跃」)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
226
|
+
clearActivePlan();
|
|
227
|
+
const archivedNote = ps === 'finished'
|
|
228
|
+
? (archived ? '(已自动归档到 plans/archive/)' : '(⚠ 归档失败,plan 仍留在 plans/,可手动 list 排查)')
|
|
229
|
+
: '';
|
|
230
|
+
return `${renderSuccess('finish', updated)}\n${archivedNote}`.trimEnd();
|
|
231
|
+
}
|
|
232
|
+
function doList(args) {
|
|
233
|
+
const scopeRaw = String(args.scope ?? 'active');
|
|
234
|
+
const scope = scopeRaw === 'archived' || scopeRaw === 'all' ? scopeRaw : 'active';
|
|
235
|
+
const plans = listPlans(scope);
|
|
236
|
+
if (plans.length === 0) {
|
|
237
|
+
return `list: 无 ${scope === 'active' ? '进行中' : scope === 'archived' ? '已归档' : ''}plan。`;
|
|
238
|
+
}
|
|
239
|
+
// 一行一条:id | status | 标题 | 进度
|
|
240
|
+
const lines = [`list (${scope},${plans.length} 条):`];
|
|
241
|
+
for (const p of plans) {
|
|
242
|
+
const done = p.steps.filter((s) => s.status === 'done' || s.status === 'skipped').length;
|
|
243
|
+
const status = p.status === 'in_progress' ? 'live' : p.status === 'finished' ? 'done' : 'gone';
|
|
244
|
+
lines.push(` ${p.id} [${status}] ${p.title} (${done}/${p.steps.length})`);
|
|
245
|
+
}
|
|
246
|
+
return lines.join('\n');
|
|
247
|
+
}
|
|
248
|
+
function doDelete(args) {
|
|
249
|
+
const id = String(args.id ?? '').trim();
|
|
250
|
+
if (!id)
|
|
251
|
+
return '错误:delete 必填 id(从 list 拿)。';
|
|
252
|
+
// 安全护栏:active 状态下不能直接 delete 当前活跃(避免误删正在用的 plan)
|
|
253
|
+
const cur = getActivePlan();
|
|
254
|
+
if (cur && cur.id === id) {
|
|
255
|
+
return `错误:不能 delete 当前活跃 plan「${cur.title}」,需先 finish(自动归档)再 delete 归档副本。`;
|
|
256
|
+
}
|
|
257
|
+
const ok = deletePlanAnywhere(id);
|
|
258
|
+
if (!ok)
|
|
259
|
+
return `错误:delete 失败,id「${id}」在 plans/ 和 plans/archive/ 都找不到。`;
|
|
260
|
+
return `delete ✓: 已永久删除「${id}」(.mocode/plans/ 与 archive/ 都不再存在)。`;
|
|
261
|
+
}
|
|
262
|
+
function doUnarchive(args) {
|
|
263
|
+
const id = String(args.id ?? '').trim();
|
|
264
|
+
if (!id)
|
|
265
|
+
return '错误:unarchive 必填 id(从 list scope=archived 拿)。';
|
|
266
|
+
if (!unarchivePlan(id)) {
|
|
267
|
+
return `错误:unarchive 失败,id「${id}」在 plans/archive/ 找不到(可能已还原,或被 delete)。`;
|
|
268
|
+
}
|
|
269
|
+
// 还原后读一次拿回 plan 对象(若 status=in_progress 还可顺手恢复为活跃)
|
|
270
|
+
const fresh = readPlan(id);
|
|
271
|
+
if (!fresh)
|
|
272
|
+
return `unarchive ✓: 「${id}」已还原到 plans/(读取失败,但文件在)。`;
|
|
273
|
+
if (fresh.status === 'in_progress') {
|
|
274
|
+
setActivePlan(fresh);
|
|
275
|
+
return `${renderSuccess('unarchive', fresh)}\n(已自动设为活跃)。`.trimEnd();
|
|
212
276
|
}
|
|
213
|
-
return renderSuccess('
|
|
277
|
+
return `${renderSuccess('unarchive', fresh)}\n(注:该 plan 状态为 ${fresh.status},未自动激活 —— 显式 create 才激活)`;
|
|
214
278
|
}
|
|
215
279
|
// ── helpers ──
|
|
216
280
|
/** 从盘上找一个 in_progress plan(进程级 state 丢失/首次访问时兜底)。无 → null。 */
|
package/dist/ui/layout.js
CHANGED
|
@@ -774,7 +774,7 @@ function composePlanLine(status, cols) {
|
|
|
774
774
|
if (!plan)
|
|
775
775
|
return ''; // 无 plan:画空,等 paint 路径 clearLine
|
|
776
776
|
// 整行左对齐,不留右段(plan 自带进度信息,不需要 cwd)
|
|
777
|
-
return `${ui.
|
|
777
|
+
return `${ui.yellow}${plan}${ui.reset}`;
|
|
778
778
|
}
|
|
779
779
|
/** 画状态行(plan 行 + spinner 行 + model 行,三行)。RUNNING 态 spinner 频繁调。
|
|
780
780
|
* 行号(footerH=6):
|