sage-mem 0.6.4 → 0.7.0

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/lib/client.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * sage-mem — 客户端半:DSH 设置页「记忆管理」界面(文件管理器)。
3
3
  *
4
- * 手写 ModuleLoader bundle(与官方 dsh-client-* 包的构建产物同格式):
4
+ * 手写 ModuleLoader bundle(`window.__ModuleLoader__.load({ id, factory })` 格式):
5
5
  * window.__ModuleLoader__.load({ id, factory }),factory 内 require('react')
6
6
  * 可用,module.exports 导出标准 Cordis 插件(apply/inject)。
7
7
  *
@@ -1311,7 +1311,7 @@ const StarmapRuntime = (function (require, _react) {
1311
1311
 
1312
1312
  /**
1313
1313
  * Client 插件:$mount strict remote contribution(第三方 remote 不在
1314
- * dsh-api-remotes 的固定列表里,必须自己挂),再注册 footer 按钮与 overlay。
1314
+ * 宿主的固定 remote 清单里,必须自己挂),再注册 footer 按钮与 overlay。
1315
1315
  */
1316
1316
  async function apply(ctx) {
1317
1317
  appCtx = ctx;
@@ -1366,7 +1366,7 @@ const StarmapRuntime = (function (require, _react) {
1366
1366
  const starmapListSchema = { parse(v) { if (!v || typeof v !== "object") throw new Error("expected object"); if (!Array.isArray(v.stars)) throw new Error("expected stars array"); v.stars.forEach((f) => starmapStarSchema.parse(f)); return v; } };
1367
1367
  const starmapReadSchema = parseSchema({ "name": isStr, "content": isStr });
1368
1368
 
1369
- // ── dream 的 codec ──
1369
+ // ── autodream 的 codec ──
1370
1370
  // 刻意写得宽:这几个方法返回的是配置 / 状态对象,字段会随版本增删。
1371
1371
  // 用 parseSchema 逐字段硬写一遍的代价是 —— host 那边加一个字段,client 的旧
1372
1372
  // schema 就把整条调用当「缺字段」拒掉(parseSchema 会 throw missing field),
@@ -1383,12 +1383,6 @@ const StarmapRuntime = (function (require, _react) {
1383
1383
  return v;
1384
1384
  },
1385
1385
  };
1386
- const boolCodec = {
1387
- parse(v) {
1388
- if (typeof v !== "boolean") throw new Error("sage-mem: expected boolean");
1389
- return v;
1390
- },
1391
- };
1392
1386
 
1393
1387
  const TYPERT_REMOTE = {
1394
1388
  package: "sage-mem",
@@ -1462,87 +1456,139 @@ const StarmapRuntime = (function (require, _react) {
1462
1456
  result: { mode: "strict", typeSymbol: "sage-mem/types#StarmapRead", schema: starmapReadSchema, create: () => starmapReadSchema },
1463
1457
  sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1464
1458
  },
1465
- // ── dream(做梦)──
1466
- // 2026-09-27 踩到的坑:namespace 不会自动出现。dsh-api-remotes 里那份
1467
- // remote 清单是**官方构建期生成的产物**,只有 memory / starmap 在里面;
1468
- // 后加的 dream 不在,于是 `ctx.get("remote.dream")` 永远 undefined ——
1469
- // 界面上表现为「remote.dream 不可用」,而 host 半日志明确写着
1470
- // 「dream gateway mounted ok」(服务是真的挂上了,只是 client 不知道)。
1471
- // 修法就是这七条:和 memory / starmap 一样,自己声明 + $mount。
1459
+ // ── autodream(自动做梦)──
1460
+ // 踩到的坑:namespace 不会自动出现。宿主那份 remote 清单是**构建期生成的
1461
+ // 产物**,只有 memory / starmap 在里面;
1462
+ // 后加的 autodream 不在,于是 `ctx.get("remote.autodream")` 永远 undefined ——
1463
+ // 界面上表现为「remote.autodream 不可用」,而 host 半日志明确写着
1464
+ // 「autodream gateway mounted ok」(服务是真的挂上了,只是 client 不知道)。
1465
+ // 修法就是自己声明 + $mount,和 memory / starmap 一样(现在共十一条:
1466
+ // 七个原有方法 + listRuns / readDeclaration / rollback / listModels)。
1472
1467
  {
1473
- id: "sage-mem#dream/getConfig",
1474
- service: "dream",
1475
- namespace: "dream",
1468
+ id: "sage-mem#autodream/getConfig",
1469
+ service: "autodream",
1470
+ namespace: "autodream",
1476
1471
  method: "getConfig",
1477
1472
  invocation: { kind: "direct" },
1478
1473
  parameters: [],
1479
- result: { mode: "strict", typeSymbol: "sage-mem/types#DreamConfigResult", schema: objCodec, create: () => objCodec },
1474
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamConfigResult", schema: objCodec, create: () => objCodec },
1480
1475
  sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1481
1476
  },
1482
1477
  {
1483
- id: "sage-mem#dream/setConfig",
1484
- service: "dream",
1485
- namespace: "dream",
1478
+ id: "sage-mem#autodream/setConfig",
1479
+ service: "autodream",
1480
+ namespace: "autodream",
1486
1481
  method: "setConfig",
1487
1482
  invocation: { kind: "direct" },
1488
1483
  parameters: [
1489
- { name: "patch", wire: "patch", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#DreamPatch", schema: objCodec, create: () => objCodec } },
1484
+ { name: "patch", wire: "patch", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamPatch", schema: objCodec, create: () => objCodec } },
1490
1485
  ],
1491
- result: { mode: "strict", typeSymbol: "sage-mem/types#DreamConfigResult", schema: objCodec, create: () => objCodec },
1486
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamConfigResult", schema: objCodec, create: () => objCodec },
1492
1487
  sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1493
1488
  },
1494
1489
  {
1495
- id: "sage-mem#dream/status",
1496
- service: "dream",
1497
- namespace: "dream",
1490
+ id: "sage-mem#autodream/status",
1491
+ service: "autodream",
1492
+ namespace: "autodream",
1498
1493
  method: "status",
1499
1494
  invocation: { kind: "direct" },
1500
1495
  parameters: [],
1501
- result: { mode: "strict", typeSymbol: "sage-mem/types#DreamStatus", schema: objCodec, create: () => objCodec },
1496
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamStatus", schema: objCodec, create: () => objCodec },
1502
1497
  sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1503
1498
  },
1504
1499
  {
1505
- id: "sage-mem#dream/runNow",
1506
- service: "dream",
1507
- namespace: "dream",
1500
+ id: "sage-mem#autodream/runNow",
1501
+ service: "autodream",
1502
+ namespace: "autodream",
1508
1503
  method: "runNow",
1509
1504
  invocation: { kind: "direct" },
1510
1505
  parameters: [
1511
- { name: "force", wire: "force", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#Boolean", schema: boolCodec, create: () => boolCodec } },
1506
+ // 契约:runNow({reason})。前端只负责给一个缘由字符串(落进
1507
+ // manifest.reason 给人看);「手动触发跳过门控」由 host 半决定。
1508
+ { name: "opts", wire: "opts", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamOpts", schema: objCodec, create: () => objCodec } },
1512
1509
  ],
1513
- result: { mode: "strict", typeSymbol: "sage-mem/types#DreamRunResult", schema: objCodec, create: () => objCodec },
1510
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamRunResult", schema: objCodec, create: () => objCodec },
1514
1511
  sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1515
1512
  },
1516
1513
  {
1517
- id: "sage-mem#dream/listReports",
1518
- service: "dream",
1519
- namespace: "dream",
1514
+ id: "sage-mem#autodream/listReports",
1515
+ service: "autodream",
1516
+ namespace: "autodream",
1520
1517
  method: "listReports",
1521
1518
  invocation: { kind: "direct" },
1522
1519
  parameters: [],
1523
- result: { mode: "strict", typeSymbol: "sage-mem/types#DreamReportList", schema: arrCodec, create: () => arrCodec },
1520
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamReportList", schema: arrCodec, create: () => arrCodec },
1524
1521
  sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1525
1522
  },
1526
1523
  {
1527
- id: "sage-mem#dream/readReport",
1528
- service: "dream",
1529
- namespace: "dream",
1524
+ id: "sage-mem#autodream/readReport",
1525
+ service: "autodream",
1526
+ namespace: "autodream",
1530
1527
  method: "readReport",
1531
1528
  invocation: { kind: "direct" },
1532
1529
  parameters: [
1533
- { name: "name", wire: "name", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#FileName", schema: strSchema, create: () => strSchema } },
1530
+ { name: "name", wire: "name", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamReportRef", schema: strSchema, create: () => strSchema } },
1534
1531
  ],
1535
1532
  result: { mode: "strict", typeSymbol: "sage-mem/types#FileContent", schema: objCodec, create: () => objCodec },
1536
1533
  sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1537
1534
  },
1538
1535
  {
1539
- id: "sage-mem#dream/listSnapshots",
1540
- service: "dream",
1541
- namespace: "dream",
1536
+ id: "sage-mem#autodream/listSnapshots",
1537
+ service: "autodream",
1538
+ namespace: "autodream",
1542
1539
  method: "listSnapshots",
1543
1540
  invocation: { kind: "direct" },
1544
1541
  parameters: [],
1545
- result: { mode: "strict", typeSymbol: "sage-mem/types#DreamSnapshotList", schema: arrCodec, create: () => arrCodec },
1542
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamSnapshotList", schema: arrCodec, create: () => arrCodec },
1543
+ sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1544
+ },
1545
+ // ── 2026-10-01 新增:运行记录 / 整理声明 / 回滚 / 模型目录 ──
1546
+ // 都是「单个对象参数」形态(与 setConfig(patch) 同形),契约第二节的
1547
+ // {limit} / {runId} / {snapshotId, scope} 逐字对应这个对象的字段。
1548
+ {
1549
+ id: "sage-mem#autodream/listRuns",
1550
+ service: "autodream",
1551
+ namespace: "autodream",
1552
+ method: "listRuns",
1553
+ invocation: { kind: "direct" },
1554
+ parameters: [
1555
+ { name: "opts", wire: "opts", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamOpts", schema: objCodec, create: () => objCodec } },
1556
+ ],
1557
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamRunList", schema: arrCodec, create: () => arrCodec },
1558
+ sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1559
+ },
1560
+ {
1561
+ id: "sage-mem#autodream/readDeclaration",
1562
+ service: "autodream",
1563
+ namespace: "autodream",
1564
+ method: "readDeclaration",
1565
+ invocation: { kind: "direct" },
1566
+ parameters: [
1567
+ { name: "opts", wire: "opts", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamOpts", schema: objCodec, create: () => objCodec } },
1568
+ ],
1569
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamDeclaration", schema: objCodec, create: () => objCodec },
1570
+ sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1571
+ },
1572
+ {
1573
+ id: "sage-mem#autodream/rollback",
1574
+ service: "autodream",
1575
+ namespace: "autodream",
1576
+ method: "rollback",
1577
+ invocation: { kind: "direct" },
1578
+ parameters: [
1579
+ { name: "opts", wire: "opts", source: "json", codec: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamOpts", schema: objCodec, create: () => objCodec } },
1580
+ ],
1581
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamRollbackResult", schema: objCodec, create: () => objCodec },
1582
+ sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1583
+ },
1584
+ {
1585
+ id: "sage-mem#autodream/listModels",
1586
+ service: "autodream",
1587
+ namespace: "autodream",
1588
+ method: "listModels",
1589
+ invocation: { kind: "direct" },
1590
+ parameters: [],
1591
+ result: { mode: "strict", typeSymbol: "sage-mem/types#AutodreamModelList", schema: objCodec, create: () => objCodec },
1546
1592
  sourceLocation: { "file": "workspace/sage-mem/lib/index.js", "line": 1, "column": 1 },
1547
1593
  },
1548
1594
  ],
@@ -1557,7 +1603,7 @@ const StarmapRuntime = (function (require, _react) {
1557
1603
  ".smem-muted{color:var(--dsw-alias-label-secondary);font-size:12px;}",
1558
1604
  ".smem-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}",
1559
1605
  // 筛选条:类型 + 自定义标签两组 chip,与搜索框是 AND 关系。
1560
- // 只消费宿主变量,浅色深色同一套语言(这是融入型界面,见 frontend-craft)。
1606
+ // 只消费宿主 CSS 变量,不另造配色 —— 浅色深色共用同一套语言。
1561
1607
  ".smem-filters{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}",
1562
1608
  ".smem-filters-label{font-size:11.5px;color:var(--dsw-alias-label-secondary);flex:none;}",
1563
1609
  ".smem-sep{width:1px;height:15px;background:var(--dsw-alias-border-l2);flex:none;margin:0 3px;}",
@@ -1602,14 +1648,14 @@ const StarmapRuntime = (function (require, _react) {
1602
1648
  '.smem-tab--on{color:var(--dsw-alias-label-primary);}',
1603
1649
  '.smem-tab--on:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-label-primary);}',
1604
1650
  // 高度预算压到 62vh(原 70vh):设置弹窗本身高度有限,
1605
- // 70vh 在 1050px 视口下会把星图底部的图例和计数挤出面板之外(实测截图被裁)。
1651
+ // 70vh 在较矮的设置弹窗视口下会把星图底部的图例和计数挤出面板之外。
1606
1652
  ".smem-starmap{display:flex;flex-direction:column;height:min(62vh,660px);min-height:400px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;overflow:hidden;}",
1607
- // ── 「做梦」面板 ──
1653
+ // ── 「自动做梦」面板 ──
1608
1654
  // 融入型:全部走宿主变量,控件复用已有的 chip / input / btn 语言,不另造一套。
1609
- ".smem-dream{display:flex;flex-direction:column;gap:12px;}",
1610
- ".smem-dream-sec{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);padding:10px 12px;display:flex;flex-direction:column;gap:9px;}",
1611
- ".smem-dream-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;}",
1612
- ".smem-dream-key{font-size:12px;color:var(--dsw-alias-label-secondary);flex:none;min-width:78px;}",
1655
+ ".smem-autodream{display:flex;flex-direction:column;gap:12px;}",
1656
+ ".smem-autodream-sec{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-layer-1);padding:10px 12px;display:flex;flex-direction:column;gap:9px;}",
1657
+ ".smem-autodream-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;}",
1658
+ ".smem-autodream-key{font-size:12px;color:var(--dsw-alias-label-secondary);flex:none;min-width:78px;}",
1613
1659
  ".smem-switch{border:1px solid var(--dsw-alias-border-l2);background:transparent;border-radius:999px;width:38px;height:21px;position:relative;cursor:pointer;flex:none;padding:0;}",
1614
1660
  '.smem-switch::after{content:"";position:absolute;top:2px;left:2px;width:15px;height:15px;border-radius:50%;background:var(--dsw-alias-label-secondary);transition:left .15s,background .15s;}',
1615
1661
  '.smem-switch[data-on="1"]{border-color:var(--dsw-alias-brand-primary,#4d76e6);background:var(--dsw-alias-brand-primary,#4d76e6);}',
@@ -1617,14 +1663,31 @@ const StarmapRuntime = (function (require, _react) {
1617
1663
  ".smem-num{width:66px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-base);color:inherit;padding:4px 8px;font-size:12px;font-family:inherit;outline:none;}",
1618
1664
  ".smem-num:focus{border-color:var(--dsw-alias-brand-primary,#4d76e6);}",
1619
1665
  ".smem-status{font-size:12px;color:var(--dsw-alias-label-secondary);}",
1620
- ".smem-dream-note{font-size:11.5px;color:var(--dsw-alias-label-secondary);line-height:1.5;}",
1621
- ".smem-dream-list{display:flex;flex-direction:column;gap:6px;}",
1666
+ ".smem-autodream-note{font-size:11.5px;color:var(--dsw-alias-label-secondary);line-height:1.5;}",
1667
+ ".smem-autodream-list{display:flex;flex-direction:column;gap:6px;}",
1622
1668
  ".smem-report{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:6px 9px;font-size:12px;cursor:pointer;background:transparent;color:inherit;text-align:left;font-family:inherit;}",
1623
1669
  ".smem-report:hover{border-color:var(--dsw-alias-border-l2);}",
1624
1670
  '.smem-report[data-on="1"]{border-color:var(--dsw-alias-brand-primary,#4d76e6);}',
1625
1671
  ".smem-report-pre{max-height:320px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:10px 12px;background:var(--dsw-alias-bg-layer-2);font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.6;}",
1626
- ".smem-dream-adv>summary{cursor:pointer;font-size:12px;color:var(--dsw-alias-label-secondary);}",
1627
- ".smem-dream-adv>div{display:flex;flex-direction:column;gap:8px;padding-top:9px;}",
1672
+ ".smem-autodream-adv>summary{cursor:pointer;font-size:12px;color:var(--dsw-alias-label-secondary);}",
1673
+ ".smem-autodream-adv>div{display:flex;flex-direction:column;gap:8px;padding-top:9px;}",
1674
+ // ── 2026-10-01 新增:回滚点区 / 整理声明区 ──
1675
+ // 新元素一律 .smem-ad-* 前缀,不动主题外的全局样式(契约第六节第 6 条)。
1676
+ ".smem-ad-select{flex:1;min-width:190px;}",
1677
+ ".smem-ad-err{color:var(--dsw-alias-state-error-primary,#ef4444);}",
1678
+ ".smem-ad-wrap{display:flex;flex-direction:column;gap:6px;}",
1679
+ ".smem-ad-snap{display:flex;align-items:center;gap:8px;flex-wrap:wrap;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:6px 9px;font-size:12px;}",
1680
+ ".smem-ad-main{flex:1;min-width:140px;overflow-wrap:anywhere;}",
1681
+ ".smem-ad-sub{color:var(--dsw-alias-label-secondary);font-size:11.5px;overflow-wrap:anywhere;}",
1682
+ ".smem-ad-badge{display:inline-flex;align-items:center;font-size:11px;color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;padding:0 5px;white-space:nowrap;}",
1683
+ ".smem-ad-badge--warn{color:var(--dsw-alias-state-warning-primary,#d97706);border-color:var(--dsw-alias-state-warning-primary,#d97706);}",
1684
+ ".smem-ad-badge--legacy{opacity:.78;}",
1685
+ ".smem-ad-confirm{border:1px solid var(--dsw-alias-state-error-primary,#ef4444);border-radius:8px;padding:7px 9px;font-size:12px;line-height:1.55;display:flex;flex-direction:column;gap:6px;overflow-wrap:anywhere;}",
1686
+ ".smem-ad-rb{font-size:12px;line-height:1.5;overflow-wrap:anywhere;}",
1687
+ ".smem-ad-run{display:flex;align-items:center;gap:8px;flex-wrap:wrap;width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:6px 9px;background:transparent;color:inherit;text-align:left;font-family:inherit;font-size:12px;cursor:pointer;}",
1688
+ ".smem-ad-run:hover{border-color:var(--dsw-alias-border-l2);}",
1689
+ '.smem-ad-run[data-on="1"]{border-color:var(--dsw-alias-brand-primary,#4d76e6);}',
1690
+ ".smem-ad-md{max-height:360px;overflow:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:10px 12px;background:var(--dsw-alias-bg-layer-2);font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;line-height:1.6;}",
1628
1691
  ].join("\n");
1629
1692
 
1630
1693
  const TYPE_META = {
@@ -1635,7 +1698,7 @@ const StarmapRuntime = (function (require, _react) {
1635
1698
  };
1636
1699
  const TYPE_ORDER = ["user", "feedback", "project", "reference"];
1637
1700
  // 筛选条上的显示名:emoji 在筛选条里太吵,只用文字;special 是星图的兜底类,
1638
- // 文件列表里出现它说明 frontmatter 写了 type: special(本机有 2 条)。
1701
+ // 文件列表里出现它说明 frontmatter 写了 type: special。
1639
1702
  const TYPE_LABEL = { user: "用户", feedback: "反馈", project: "项目", reference: "参考", special: "特殊" };
1640
1703
  const TYPE_SORT = ["user", "feedback", "project", "reference", "special"];
1641
1704
 
@@ -1663,7 +1726,7 @@ const StarmapRuntime = (function (require, _react) {
1663
1726
  // 保存一次就把 baseline / node_type / originSessionId / updated 全抹掉
1664
1727
  // (baseline 一丢,「每次会话必读」的记忆从此不再注入)。
1665
1728
  // 2) 类型正则必须锚行首:` node_type: memory` 里的 `type:` 否则会被先命中,
1666
- // 捕获到 memory → 不在 TYPE_ORDER 里 → 兜底成 reference(全库 45 条中招)。
1729
+ // 捕获到 memory → 不在 TYPE_ORDER 里 → 兜底成 reference。
1667
1730
  // 3) 换行必须容忍 CRLF(Windows 编辑器碰一次就是 \r\n);回写沿用原文件的
1668
1731
  // 换行风格,从而 LF / CRLF 都能逐字节 round-trip。
1669
1732
 
@@ -1713,8 +1776,8 @@ const StarmapRuntime = (function (require, _react) {
1713
1776
  }
1714
1777
 
1715
1778
  /**
1716
- * 兜底:读 node_type。本机有两份老文件没有 type 行,node_type 直接就是类型
1717
- * (project_ui-lab.md → project、user_courses-2026fall.md → user)。
1779
+ * 兜底:读 node_type。个别老文件没有 type 行,node_type 直接就是类型
1780
+ * (例如 project_example.md → project)。
1718
1781
  * 只由调用方在 detectType 返回空时使用 —— detectType 本身绝不认 node_type。
1719
1782
  */
1720
1783
  function detectNodeType(text) {
@@ -1959,17 +2022,37 @@ const StarmapRuntime = (function (require, _react) {
1959
2022
  }
1960
2023
 
1961
2024
  /**
1962
- * 「做梦」面板 —— 让记忆自己整理一遍(引擎在 host 半 lib/dream.js)。
2025
+ * 模型路线的编码:`<select>` 的 value ↔ {provider, model}。
2026
+ *
2027
+ * 用 JSON 数组而不是 "provider/model" 拼串 —— provider 名里带斜杠的情况
2028
+ * (比如 openrouter 的 "deepseek/deepseek-chat")会把分隔符解析弄错。
2029
+ * 空串代表「跟随当前会话默认模型」(值是 ['', ''])。
2030
+ */
2031
+ function encodeRoute(provider, model) {
2032
+ return JSON.stringify([String(provider == null ? "" : provider), String(model == null ? "" : model)]);
2033
+ }
2034
+ function decodeRoute(v) {
2035
+ try {
2036
+ const a = JSON.parse(String(v == null ? "" : v));
2037
+ if (Array.isArray(a) && a.length === 2) {
2038
+ return { provider: String(a[0] == null ? "" : a[0]), model: String(a[1] == null ? "" : a[1]) };
2039
+ }
2040
+ } catch (_) {}
2041
+ return { provider: "", model: "" };
2042
+ }
2043
+
2044
+ /**
2045
+ * 「自动做梦」面板 —— 让记忆自己整理一遍(引擎在 host 半 lib/autodream.js)。
1963
2046
  *
1964
- * 数据全走 remote.dream(host 半的 DreamGateway)。两条实现约定:
2047
+ * 数据全走 remote.autodream(host 半的 AutodreamGateway)。两条实现约定:
1965
2048
  * 1. **轮询而不是等待**。「立即整理」启动后台任务后立刻返回 —— 一趟可能跑
1966
2049
  * 几分钟,把 remote 调用挂在那儿等会撞上前端超时。进度靠每 2 秒拉一次
1967
2050
  * status() 显示。
1968
2051
  * 2. **不替用户做判断**。触发方式 / 改动方式 / 输入源三件事各自明说代价
1969
2052
  * (比如「记忆 + 会话记录」会把私密对话送进模型),默认全是最保守的那一档。
1970
2053
  */
1971
- function DreamPanel(props) {
1972
- const remote = props.ctx && props.ctx.get ? props.ctx.get("remote.dream") : null;
2054
+ function AutodreamPanel(props) {
2055
+ const remote = props.ctx && props.ctx.get ? props.ctx.get("remote.autodream") : null;
1973
2056
  const sCfg = react.useState(null); const cfg = sCfg[0], setCfg = sCfg[1];
1974
2057
  const sSt = react.useState(null); const st = sSt[0], setSt = sSt[1];
1975
2058
  const sErr = react.useState(null); const err = sErr[0], setErr = sErr[1];
@@ -1978,153 +2061,291 @@ const StarmapRuntime = (function (require, _react) {
1978
2061
  const sReports = react.useState([]); const reports = sReports[0], setReports = sReports[1];
1979
2062
  const sOpen = react.useState(null); const open = sOpen[0], setOpen = sOpen[1];
1980
2063
  const sBody = react.useState(""); const body = sBody[0], setBody = sBody[1];
1981
- const sModelEdit = react.useState(false); const modelEdit = sModelEdit[0], setModelEdit = sModelEdit[1];
1982
2064
  const sProv = react.useState(""); const prov = sProv[0], setProv = sProv[1];
1983
2065
  const sModel = react.useState(""); const model = sModel[0], setModel = sModel[1];
2066
+ // ── 2026-10-01 新增:模型目录 / 回滚点 / 整理记录 / 整理声明 ──
2067
+ const sModels = react.useState(null); const models = sModels[0], setModels = sModels[1];
2068
+ const sSnaps = react.useState([]); const snaps = sSnaps[0], setSnaps = sSnaps[1];
2069
+ const sRuns = react.useState([]); const runs = sRuns[0], setRuns = sRuns[1];
2070
+ const sDeclOpen = react.useState(null); const declOpen = sDeclOpen[0], setDeclOpen = sDeclOpen[1];
2071
+ const sDeclBody = react.useState(""); const declBody = sDeclBody[0], setDeclBody = sDeclBody[1];
2072
+ const sConfirm = react.useState(null); const confirmSnap = sConfirm[0], setConfirmSnap = sConfirm[1];
2073
+ const sRbBusy = react.useState(null); const rbBusy = sRbBusy[0], setRbBusy = sRbBusy[1];
2074
+ const sRbMsg = react.useState(null); const rbMsg = sRbMsg[0], setRbMsg = sRbMsg[1];
2075
+ const sListErr = react.useState(null); const listErr = sListErr[0], setListErr = sListErr[1];
2076
+ const sWasRunning = react.useState(false); const wasRunning = sWasRunning[0], setWasRunning = sWasRunning[1];
2077
+
2078
+ // 派生量放在 effect 之前:下面几个 effect 的依赖数组要在渲染期就能取到值。
2079
+ const c = cfg || {};
2080
+ const running = !!(st && st.running);
2081
+ const last = (st && st.lastResult) || null;
2082
+ const route = (st && st.route) || null;
2083
+ const rollbackScope = c.rollbackScope === "all" ? "all" : "files";
1984
2084
 
1985
2085
  react.useEffect(function () {
1986
- if (!remote) { setErr("remote.dream 不可用 —— 插件的 host 半可能没加载成功,看 DSH 启动日志"); return undefined; }
2086
+ if (!remote) { setErr("remote.autodream 不可用 —— 插件的 host 半可能没加载成功,看 DSH 启动日志"); return undefined; }
1987
2087
  let alive = true;
1988
2088
  function pull() {
1989
- remote.status().then(function (r) {
1990
- if (!alive) return;
1991
- const d = unwrap(r);
1992
- if (d) { setSt(d); if (d.config) setCfg(d.config); }
2089
+ callRemote("status", [], function (d) {
2090
+ if (!alive || !d) return;
2091
+ setSt(d); if (d.config) setCfg(d.config);
1993
2092
  setErr(null);
1994
- }).catch(function (e) { if (alive) setErr(detail(e)); });
2093
+ }, function (m) { if (alive) setErr(m); });
1995
2094
  }
1996
2095
  pull();
1997
- remote.listReports().then(function (r) { if (alive) setReports(unwrap(r) || []); }).catch(function () {});
2096
+ refreshLists();
2097
+ loadModels();
1998
2098
  const id = setInterval(pull, 2000);
1999
2099
  return function () { alive = false; clearInterval(id); };
2000
2100
  }, [remote]);
2001
2101
 
2102
+ // 一趟跑完(running true → false)就把回滚点 / 整理记录 / 报告刷一遍:
2103
+ // 刚跑完那一刻列表还是旧的,等下一次进面板才更新会让人以为没生效。
2104
+ react.useEffect(function () {
2105
+ if (wasRunning && !running) refreshLists();
2106
+ if (wasRunning !== running) setWasRunning(running);
2107
+ }, [running]);
2108
+
2109
+ // 模型行的手填框:配置里的 provider/model 变了就同步过去(保存后回填)。
2110
+ react.useEffect(function () {
2111
+ setProv(c.provider || "");
2112
+ setModel(c.model || "");
2113
+ }, [c.provider, c.model]);
2114
+
2115
+ /**
2116
+ * 所有 remote 调用的统一出口。
2117
+ *
2118
+ * 契约的硬约束是「每个调用都 try/catch,失败显示错误文案、不白屏」,而
2119
+ * 这里还有第三种失败:host 半是旧版本、方法根本不在 remote 对象上
2120
+ * (typeof !== "function")—— 那会在 `.then` 之前就抛 TypeError。
2121
+ * 三种都收敛到 onErr,面板只会多一行红字。
2122
+ */
2123
+ function callRemote(name, args, onOk, onErr) {
2124
+ const fn = remote ? remote[name] : null;
2125
+ if (typeof fn !== "function") { if (onErr) onErr("host 半没有 " + name + " 接口(版本较旧?)"); return; }
2126
+ let p;
2127
+ try { p = fn.apply(remote, args || []); }
2128
+ catch (e) { if (onErr) onErr(detail(e)); return; }
2129
+ Promise.resolve(p).then(function (r) { if (onOk) onOk(unwrap(r)); })
2130
+ .catch(function (e) { if (onErr) onErr(detail(e)); });
2131
+ }
2132
+
2002
2133
  function patch(p) {
2003
2134
  setErr(null); setNotice("");
2004
- remote.setConfig(p).then(function (r) {
2005
- const d = unwrap(r);
2135
+ callRemote("setConfig", [p], function (d) {
2006
2136
  if (d && d.config) setCfg(d.config);
2007
- }).catch(function (e) { setErr(detail(e)); });
2137
+ }, function (m) { setErr(m); });
2138
+ }
2139
+
2140
+ /** 报告 / 回滚点 / 整理记录三份列表一起刷。 */
2141
+ function refreshLists() {
2142
+ callRemote("listReports", [], function (d) {
2143
+ setReports(Array.isArray(d) ? d : []);
2144
+ }, function () {});
2145
+ callRemote("listSnapshots", [], function (d) {
2146
+ setSnaps(Array.isArray(d) ? d : []);
2147
+ }, function (m) { setListErr(m); });
2148
+ callRemote("listRuns", [{ limit: 20 }], function (d) {
2149
+ setRuns(Array.isArray(d) ? d : []);
2150
+ }, function (m) { setListErr(m); });
2151
+ }
2152
+
2153
+ function loadModels() {
2154
+ callRemote("listModels", [], function (d) {
2155
+ setModels(d && typeof d === "object" && !Array.isArray(d) ? d : { catalogAvailable: false, routes: [], note: "" });
2156
+ }, function (m) {
2157
+ setModels({ catalogAvailable: false, routes: [], note: "模型目录不可用(" + m + ")—— 请在下面手填 provider 与 model。" });
2158
+ });
2008
2159
  }
2009
2160
 
2010
2161
  function run() {
2011
2162
  setErr(null); setNotice(""); setBusy(true);
2012
- remote.runNow(true).then(function (r) {
2013
- const d = unwrap(r);
2163
+ // 契约:runNow({ reason })。reason 只落进 manifest 给人看;门控的豁免
2164
+ // 由 host 半负责(手动点了按钮就不该被「还没到 N 小时」挡回来)。
2165
+ callRemote("runNow", [{ reason: "设置页手动触发" }], function (d) {
2014
2166
  if (d && d.ok === false) setErr(d.error || "启动失败");
2015
- else setNotice("已开始整理 —— 进度每 2 秒刷新一次");
2016
- return remote.listReports().then(function (rr) { setReports(unwrap(rr) || []); });
2017
- }).catch(function (e) { setErr(detail(e)); }).then(function () { setBusy(false); });
2167
+ else { setNotice("已开始整理 —— 进度每 2 秒刷新一次"); refreshLists(); }
2168
+ setBusy(false);
2169
+ }, function (m) { setErr(m); setBusy(false); });
2018
2170
  }
2019
2171
 
2020
2172
  function openReport(name) {
2021
2173
  if (name === null) { setOpen(null); setBody(""); return; }
2022
2174
  setOpen(name); setBody("载入中…");
2023
- remote.readReport(name).then(function (r) {
2024
- const d = unwrap(r);
2175
+ callRemote("readReport", [name], function (d) {
2025
2176
  setBody((d && d.content) || "(空)");
2026
- }).catch(function (e) { setBody("读取失败:" + detail(e)); });
2177
+ }, function (m) { setBody("读取失败:" + m); });
2027
2178
  }
2028
2179
 
2029
- const c = cfg || {};
2030
- const running = !!(st && st.running);
2031
- const last = (st && st.lastResult) || null;
2180
+ /** 整理声明:点开才拉,拉回来的是 Markdown 原文,直接用 pre 呈现。 */
2181
+ function openDecl(runId) {
2182
+ if (declOpen === runId) { setDeclOpen(null); setDeclBody(""); return; }
2183
+ setDeclOpen(runId); setDeclBody("载入中…");
2184
+ callRemote("readDeclaration", [{ runId: runId }], function (d) {
2185
+ setDeclBody((d && d.markdown) || "(这一趟没有留下声明正文)");
2186
+ }, function (m) { setDeclBody("读取失败:" + m); });
2187
+ }
2032
2188
 
2033
- return h("div", { className: "smem-dream" }, [
2034
- h("div", { className: "smem-dream-sec", key: "top" }, [
2035
- h("div", { className: "smem-dream-row", key: "r" }, [
2189
+ /**
2190
+ * 回滚。二次确认在 JSX 里(点了「回滚」先展开确认条),这里只负责调用。
2191
+ * 结果就地回显 —— 恢复几个、移走几个、保护快照 id,全都写在同一行里。
2192
+ */
2193
+ function doRollback(sp) {
2194
+ const scope = rollbackScope;
2195
+ setErr(null); setNotice(""); setRbMsg(null); setRbBusy(sp.name);
2196
+ callRemote("rollback", [{ snapshotId: sp.name, scope: scope }], function (d) {
2197
+ const x = d || {};
2198
+ if (x.ok === false) {
2199
+ setRbMsg({ bad: true, text: "回滚失败:" + (x.error || "未知原因") });
2200
+ } else {
2201
+ setRbMsg({ bad: false, text: "已回滚到 " + String(sp.atHuman || "") + ":恢复 " +
2202
+ (x.restored == null ? "?" : x.restored) + " 个文件,移走 " + (x.parked == null ? "?" : x.parked) + " 个" +
2203
+ // skipped = 想动但没动的(archive/ 已有同名、或回滚点里没那一版)。
2204
+ // 不显示它等于「部分成功」被说成「成功」——回滚这种事不能含糊。
2205
+ (x.skipped ? ",跳过 " + x.skipped + " 个(详见回滚报告)" : "") +
2206
+ (x.protection ? " · 保护快照 " + x.protection : "") + (x.report ? " · 报告 " + x.report : "") });
2207
+ setConfirmSnap(null);
2208
+ }
2209
+ setRbBusy(null);
2210
+ refreshLists();
2211
+ }, function (m) { setRbMsg({ bad: true, text: "回滚失败:" + m }); setRbBusy(null); });
2212
+ }
2213
+
2214
+ // 实际生效路线(契约:route 可能为 null;source==='none' 时 error 里是原因)。
2215
+ const routeBad = !route || route.source === "none" || !route.provider || !route.model;
2216
+ const routeInfo = routeBad
2217
+ ? ("实际生效路线:拿不到 —— " + ((route && route.error) || "宿主没能解析出 provider / model(配置留空且取不到会话默认模型)"))
2218
+ : ("实际生效路线:" + route.provider + " / " + route.model + (route.fromDefault ? "(跟随默认)" : "(配置指定)"));
2219
+
2220
+ // 模型 <select> 的取值:''=跟随默认;否则是 "provider::model" 的 JSON 编码。
2221
+ // 配置里指定的路线万一下架了(不在目录里),也要让它显示出来 —— 否则
2222
+ // 下拉框会悄悄跳到「跟随默认」,与下面那行「实际生效路线」自相矛盾。
2223
+ const modelRoutes = (models && Array.isArray(models.routes)) ? models.routes : [];
2224
+ const modelValue = (c.provider && c.model) ? encodeRoute(c.provider, c.model) : "";
2225
+ const modelValueInCatalog = modelValue === "" || modelRoutes.some(function (r) {
2226
+ return encodeRoute(r.provider, r.model) === modelValue;
2227
+ });
2228
+
2229
+ return h("div", { className: "smem-autodream" }, [
2230
+ h("div", { className: "smem-autodream-sec", key: "top" }, [
2231
+ h("div", { className: "smem-autodream-row", key: "r" }, [
2036
2232
  h("button", {
2037
2233
  type: "button", key: "sw", className: "smem-switch",
2038
2234
  "data-on": c.enabled ? "1" : "0",
2039
2235
  title: c.enabled ? "点击停用" : "点击启用",
2040
- "aria-label": "启用或停用做梦",
2236
+ "aria-label": "启用或停用自动做梦",
2041
2237
  onClick: function () { patch({ enabled: !c.enabled }); },
2042
2238
  }),
2043
- h("span", { key: "t", style: { fontWeight: "600" } }, "做梦"),
2239
+ h("span", { key: "t", style: { fontWeight: "600" } }, "自动做梦"),
2044
2240
  h("span", { className: "smem-status", key: "s" },
2045
2241
  running ? ("运行中 · " + String((st && st.phase) || "准备中")) : (c.enabled ? "已启用 · 空闲" : "未启用")),
2046
2242
  ]),
2047
- h("div", { className: "smem-dream-note", key: "n" },
2243
+ h("div", { className: "smem-autodream-note", key: "n" },
2048
2244
  "把记忆目录通读一遍:合并重复的、删掉被推翻的、修索引与断链、把相对日期换成绝对日期。"),
2049
2245
  ]),
2050
- h("div", { className: "smem-dream-sec", key: "mode" }, [
2051
- h("div", { className: "smem-dream-row", key: "a" }, [
2052
- h("span", { className: "smem-dream-key", key: "k" }, "触发"),
2246
+ h("div", { className: "smem-autodream-sec", key: "mode" }, [
2247
+ h("div", { className: "smem-autodream-row", key: "a" }, [
2248
+ h("span", { className: "smem-autodream-key", key: "k" }, "触发"),
2053
2249
  h(Seg, {
2054
2250
  key: "v", value: c.trigger,
2055
2251
  options: [{ value: "manual", label: "只手动" }, { value: "auto", label: "满足条件自动跑" }],
2056
2252
  onChange: function (v) { patch({ trigger: v }); },
2057
2253
  }),
2058
2254
  ]),
2059
- h("div", { className: "smem-dream-row", key: "b" }, [
2060
- h("span", { className: "smem-dream-key", key: "k" }, "改动方式"),
2255
+ h("div", { className: "smem-autodream-row", key: "b" }, [
2256
+ h("span", { className: "smem-autodream-key", key: "k" }, "改动方式"),
2061
2257
  h(Seg, {
2062
2258
  key: "v", value: c.apply ? "apply" : "report",
2063
2259
  options: [{ value: "report", label: "只出报告" }, { value: "apply", label: "直接改写(先快照)" }],
2064
2260
  onChange: function (v) { patch({ apply: v === "apply" }); },
2065
2261
  }),
2066
2262
  ]),
2067
- h("div", { className: "smem-dream-row", key: "c" }, [
2068
- h("span", { className: "smem-dream-key", key: "k" }, "输入源"),
2263
+ h("div", { className: "smem-autodream-row", key: "c" }, [
2264
+ h("span", { className: "smem-autodream-key", key: "k" }, "输入源"),
2069
2265
  h(Seg, {
2070
2266
  key: "v", value: c.source,
2071
2267
  options: [{ value: "memory", label: "仅记忆目录" }, { value: "memory+sessions", label: "记忆 + 会话记录" }],
2072
2268
  onChange: function (v) { patch({ source: v }); },
2073
2269
  }),
2074
2270
  ]),
2075
- h("div", { className: "smem-dream-note", key: "w" },
2271
+ h("div", { className: "smem-autodream-note", key: "w" },
2076
2272
  c.source === "memory+sessions"
2077
2273
  ? "会解压会话记录做定向搜索 —— 私密对话会进入模型上下文,token 成本也更高。"
2078
2274
  : "只看记忆文件本身,不碰会话记录。"),
2079
- h("div", { className: "smem-dream-note", key: "w2" },
2275
+ h("div", { className: "smem-autodream-note", key: "w2" },
2080
2276
  c.apply
2081
- ? "改写前会自动把整个记忆目录快照一份(memory 上一层 .sage-mem/snapshots/),改坏可回退。"
2277
+ ? "改写前会自动把整个记忆目录快照一份(memory 上一层 .sage-mem/autodream/snapshots/),改坏可回退。"
2082
2278
  : "只读模式:这一趟连写工具都不会挂给模型,所以它不可能改到任何文件。"),
2083
2279
  ]),
2084
- h("details", { className: "smem-dream-adv", key: "adv" }, [
2280
+ h("details", { className: "smem-autodream-adv", key: "adv" }, [
2085
2281
  h("summary", { key: "s" }, "高级设置"),
2086
2282
  h("div", { key: "d" }, [
2087
- c.trigger === "auto" ? h("div", { className: "smem-dream-row", key: "gate" }, [
2088
- h("span", { className: "smem-dream-key", key: "k" }, "自动触发"),
2283
+ c.trigger === "auto" ? h("div", { className: "smem-autodream-row", key: "gate" }, [
2284
+ h("span", { className: "smem-autodream-key", key: "k" }, "自动触发"),
2089
2285
  h("span", { className: "smem-status", key: "a" }, "距上次 ≥"),
2090
2286
  h("input", { className: "smem-num", key: "h", type: "number", min: "1", max: "720", value: c.minHours, onChange: function (e) { patch({ minHours: Number(e.target.value) }); } }),
2091
2287
  h("span", { className: "smem-status", key: "b" }, "小时,且期间 ≥"),
2092
2288
  h("input", { className: "smem-num", key: "m", type: "number", min: "1", max: "200", value: c.minSessions, onChange: function (e) { patch({ minSessions: Number(e.target.value) }); } }),
2093
2289
  h("span", { className: "smem-status", key: "c" }, "个会话有更新"),
2094
2290
  ]) : null,
2095
- h("div", { className: "smem-dream-row", key: "model" }, [
2096
- h("span", { className: "smem-dream-key", key: "k" }, "模型"),
2097
- modelEdit
2098
- ? [
2099
- h("input", { className: "smem-input", key: "p", style: { flex: "1", minWidth: "110px" }, value: prov, placeholder: "provider(留空=默认)", onChange: function (e) { setProv(e.target.value); } }),
2100
- h("input", { className: "smem-input", key: "m", style: { flex: "1", minWidth: "110px" }, value: model, placeholder: "model(留空=默认)", onChange: function (e) { setModel(e.target.value); } }),
2101
- h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "ok", onClick: function () { patch({ provider: prov, model: model }); setModelEdit(false); setNotice("模型已保存"); } }, "保存"),
2102
- h("button", { type: "button", className: "smem-btn", key: "no", onClick: function () { setModelEdit(false); } }, "取消"),
2103
- ]
2104
- : [
2105
- h("span", { className: "smem-status", key: "cur" },
2106
- st && st.route ? (st.route.provider + " / " + st.route.model + (st.route.fromDefault ? "(跟随聊天页默认)" : "")) : "(拿不到默认模型)"),
2107
- h("button", {
2108
- type: "button", className: "smem-btn", key: "e",
2109
- onClick: function () { setProv(c.provider || ""); setModel(c.model || ""); setModelEdit(true); },
2110
- }, "改"),
2111
- ],
2291
+ h("div", { className: "smem-autodream-row", key: "model" }, [
2292
+ h("span", { className: "smem-autodream-key", key: "k" }, "模型"),
2293
+ // 有可枚举的模型目录就给 <select>(首项 = 跟随会话默认,值 "":"");
2294
+ // 目录不可用就退化成两个文本框(契约第六节第 2 条)。
2295
+ (models && models.catalogAvailable)
2296
+ ? h("select", {
2297
+ className: "smem-input smem-ad-select", key: "sel",
2298
+ value: modelValue,
2299
+ onChange: function (e) {
2300
+ const v = e.target.value;
2301
+ if (!v) { patch({ provider: "", model: "" }); return; }
2302
+ const r = decodeRoute(v);
2303
+ patch({ provider: r.provider, model: r.model });
2304
+ },
2305
+ }, [h("option", { key: "__def", value: "" },
2306
+ "跟随当前会话默认模型" + ((models.defaultRoute && models.defaultRoute.provider && models.defaultRoute.model)
2307
+ ? ("(" + String(models.defaultRoute.provider) + " / " + String(models.defaultRoute.model) + ")")
2308
+ : ""),
2309
+ )].concat(
2310
+ modelValueInCatalog ? [] : [h("option", { key: "__cur", value: modelValue }, String(c.provider) + " / " + String(c.model) + "(配置里指定,不在目录中)")],
2311
+ modelRoutes.map(function (r) {
2312
+ return h("option", { key: String(r.provider) + "/" + String(r.model), value: encodeRoute(r.provider, r.model) },
2313
+ r.label || (String(r.provider) + " / " + String(r.model)));
2314
+ }),
2315
+ ))
2316
+ : models
2317
+ ? [
2318
+ h("input", { className: "smem-input", key: "p", style: { flex: "1", minWidth: "110px" }, value: prov, placeholder: "provider(留空=跟随默认)", onChange: function (e) { setProv(e.target.value); } }),
2319
+ h("input", { className: "smem-input", key: "m", style: { flex: "1", minWidth: "110px" }, value: model, placeholder: "model(留空=跟随默认)", onChange: function (e) { setModel(e.target.value); } }),
2320
+ h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "ok", onClick: function () { patch({ provider: prov, model: model }); setNotice("模型已保存"); } }, "保存"),
2321
+ ]
2322
+ : [
2323
+ h("span", { className: "smem-status", key: "load" }, "模型目录载入中…"),
2324
+ h("button", { type: "button", className: "smem-btn", key: "man", onClick: function () { setModels({ catalogAvailable: false, routes: [], note: "" }); } }, "手填"),
2325
+ ],
2112
2326
  ]),
2113
- h("div", { className: "smem-dream-row", key: "steps" }, [
2114
- h("span", { className: "smem-dream-key", key: "k" }, "最多轮数"),
2327
+ h("div", {
2328
+ className: "smem-autodream-note" + (routeBad ? " smem-ad-err" : ""),
2329
+ key: "route",
2330
+ }, (routeBad ? "✗ " : "✓ ") + routeInfo),
2331
+ models && models.catalogAvailable === false && models.note
2332
+ ? h("div", { className: "smem-autodream-note", key: "mnote" }, String(models.note))
2333
+ : null,
2334
+ h("div", { className: "smem-autodream-row", key: "steps" }, [
2335
+ h("span", { className: "smem-autodream-key", key: "k" }, "最多轮数"),
2115
2336
  h("input", { className: "smem-num", key: "v", type: "number", min: "1", max: "80", value: c.maxSteps, onChange: function (e) { patch({ maxSteps: Number(e.target.value) }); } }),
2116
2337
  h("span", { className: "smem-status", key: "s" }, "一轮 = 一次模型调用 + 它要的工具调用"),
2117
2338
  ]),
2118
- h("div", { className: "smem-dream-row", key: "keep" }, [
2119
- h("span", { className: "smem-dream-key", key: "k" }, "保留快照"),
2339
+ h("div", { className: "smem-autodream-row", key: "keep" }, [
2340
+ h("span", { className: "smem-autodream-key", key: "k" }, "保留快照"),
2120
2341
  h("input", { className: "smem-num", key: "v", type: "number", min: "1", max: "50", value: c.maxSnapshotKeep, onChange: function (e) { patch({ maxSnapshotKeep: Number(e.target.value) }); } }),
2121
2342
  h("span", { className: "smem-status", key: "s" }, "份"),
2122
2343
  ]),
2123
2344
  h("div", { className: "smem-status", key: "paths" }, "配置与状态:" + ((st && st.paths && st.paths.configPath) || "—")),
2124
2345
  ]),
2125
2346
  ]),
2126
- h("div", { className: "smem-dream-sec", key: "run" }, [
2127
- h("div", { className: "smem-dream-row", key: "r" }, [
2347
+ h("div", { className: "smem-autodream-sec", key: "run" }, [
2348
+ h("div", { className: "smem-autodream-row", key: "r" }, [
2128
2349
  h("button", {
2129
2350
  type: "button", className: "smem-btn smem-btn-primary", key: "go",
2130
2351
  disabled: busy || running || !remote,
@@ -2136,7 +2357,7 @@ const StarmapRuntime = (function (require, _react) {
2136
2357
  : (st && st.hoursSince != null ? ("距上次整理 " + st.hoursSince.toFixed(1) + " 小时") : "从未整理过")),
2137
2358
  ]),
2138
2359
  running && st && st.steps && st.steps.length
2139
- ? h("div", { className: "smem-dream-note", key: "prog" }, st.steps.map(function (x) {
2360
+ ? h("div", { className: "smem-autodream-note", key: "prog" }, st.steps.map(function (x) {
2140
2361
  return "第 " + x.step + " 轮:" + (x.tools.length ? x.tools.join("、") : "结束");
2141
2362
  }).join(" · "))
2142
2363
  : null,
@@ -2150,22 +2371,119 @@ const StarmapRuntime = (function (require, _react) {
2150
2371
  (last.report ? " · 报告 " + last.report : ""))
2151
2372
  : null,
2152
2373
  ]),
2153
- h("div", { className: "smem-dream-sec", key: "rep" }, [
2154
- h("div", { className: "smem-dream-row", key: "h" }, [
2374
+ // ── 回滚点(新)──────────────────────────────────────────────
2375
+ // 只改这一个文件、只用既有 h() 写法;数据全走 remote.autodream。
2376
+ h("div", { className: "smem-autodream-sec", key: "snap" }, [
2377
+ h("div", { className: "smem-autodream-row", key: "h" }, [
2378
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "回滚点"),
2379
+ h("span", { className: "smem-status", key: "c" }, String(snaps.length) + " 个"),
2380
+ h("button", { type: "button", className: "smem-btn", key: "rf", onClick: refreshLists }, "刷新"),
2381
+ ]),
2382
+ h("div", { className: "smem-autodream-row", key: "scope" }, [
2383
+ h("span", { className: "smem-autodream-key", key: "k" }, "回滚范围"),
2384
+ h(Seg, {
2385
+ key: "v", value: rollbackScope,
2386
+ options: [{ value: "files", label: "只恢复本次动过的文件" }, { value: "all", label: "整目录回到该时点" }],
2387
+ onChange: function (v) { patch({ rollbackScope: v }); },
2388
+ }),
2389
+ ]),
2390
+ h("div", { className: "smem-autodream-note", key: "n" },
2391
+ "回滚前会自动把整个记忆目录再存一份保护快照 —— 所以回滚本身也能再退回来。多出来的文件只移进 archive/,不删除。"),
2392
+ snaps.length === 0
2393
+ ? h("div", { className: "smem-status", key: "e" }, "还没有回滚点 —— 用「直接改写」跑过一次就有了。")
2394
+ : h("div", { className: "smem-autodream-list", key: "l" }, snaps.slice(0, 15).map(function (sp) {
2395
+ const label = sp.legacy ? "旧版快照" : (sp.runId ? ("运行 " + String(sp.runId)) : "回滚保护");
2396
+ return h("div", { className: "smem-ad-wrap", key: String(sp.name) }, [
2397
+ h("div", { className: "smem-ad-snap", key: "row" }, [
2398
+ h("div", { className: "smem-ad-main", key: "m" }, [
2399
+ h("div", { key: "l1" }, String(sp.atHuman || "") + " · " + (sp.files == null ? "?" : sp.files) + " 个文件"),
2400
+ h("div", { className: "smem-ad-sub", key: "l2" }, label + " · " + String(sp.name || "")),
2401
+ ]),
2402
+ sp.legacy ? h("span", { className: "smem-ad-badge smem-ad-badge--legacy", key: "lg" }, "旧版") : null,
2403
+ confirmSnap === sp.name
2404
+ ? null
2405
+ : h("button", {
2406
+ type: "button", className: "smem-btn", key: "go",
2407
+ disabled: rbBusy !== null,
2408
+ onClick: function () { setRbMsg(null); setConfirmSnap(sp.name); },
2409
+ }, "回滚"),
2410
+ ]),
2411
+ confirmSnap === sp.name
2412
+ ? h("div", { className: "smem-ad-confirm", key: "cf" }, [
2413
+ h("div", { key: "t", style: { fontWeight: "600" } }, "确认回滚到 " + String(sp.atHuman || "") + "?"),
2414
+ h("div", { key: "d" }, "范围:" + (rollbackScope === "all"
2415
+ ? "整目录回到该时点 —— 快照里没有、当前目录里有的文件会被移进 archive/(不删除)"
2416
+ : "只恢复这次运行动过的文件 —— 该次新建的移进 archive/,该次归档的移回顶层") +
2417
+ "。回滚前会先存一份保护快照,所以这一步本身也能退。"),
2418
+ h("div", { className: "smem-autodream-row", key: "acts" }, [
2419
+ h("button", {
2420
+ type: "button", className: "smem-btn smem-btn-danger", key: "yes",
2421
+ disabled: rbBusy === sp.name,
2422
+ onClick: function () { doRollback(sp); },
2423
+ }, rbBusy === sp.name ? "回滚中…" : "确认回滚"),
2424
+ h("button", { type: "button", className: "smem-btn", key: "no", onClick: function () { setConfirmSnap(null); } }, "取消"),
2425
+ ]),
2426
+ ])
2427
+ : null,
2428
+ ]);
2429
+ })),
2430
+ rbMsg ? h("div", { className: "smem-ad-rb " + (rbMsg.bad ? "smem-ad-err" : "smem-ok"), key: "rb" }, (rbMsg.bad ? "⚠ " : "✓ ") + rbMsg.text) : null,
2431
+ ]),
2432
+ // ── 整理声明(新)──────────────────────────────────────────
2433
+ h("div", { className: "smem-autodream-sec", key: "runs" }, [
2434
+ h("div", { className: "smem-autodream-row", key: "h" }, [
2435
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "整理记录"),
2436
+ h("span", { className: "smem-status", key: "c" }, String(runs.length) + " 条"),
2437
+ h("button", { type: "button", className: "smem-btn", key: "rf", onClick: refreshLists }, "刷新"),
2438
+ ]),
2439
+ h("div", { className: "smem-autodream-note", key: "n" },
2440
+ "每一趟都留一份整理声明:改了什么、为什么改、哪些想法被拒了。点一条看全文。"),
2441
+ runs.length === 0
2442
+ ? h("div", { className: "smem-status", key: "e" }, "还没有整理记录 —— 跑一次就有了。")
2443
+ : h("div", { className: "smem-autodream-list", key: "l" }, runs.slice(0, 15).map(function (rn) {
2444
+ return h("div", { className: "smem-ad-wrap", key: String(rn.runId) }, [
2445
+ h("button", {
2446
+ type: "button", className: "smem-ad-run",
2447
+ "data-on": declOpen === rn.runId ? "1" : "0",
2448
+ onClick: function () { openDecl(rn.runId); },
2449
+ }, [
2450
+ h("span", { key: "t" }, String(rn.atHuman || "")),
2451
+ h("span", { className: "smem-ad-badge", key: "m" }, rn.mode === "apply" ? "直接改写" : "只出报告"),
2452
+ h("span", { key: "c" }, "改 " + (rn.changeCount == null ? "?" : rn.changeCount) + " 条"),
2453
+ rn.report ? h("span", { className: "smem-ad-sub", key: "r" }, "报告 " + String(rn.report)) : null,
2454
+ rn.rolledBackAt
2455
+ ? h("span", { className: "smem-ad-badge smem-ad-badge--warn", key: "rb" },
2456
+ "已回滚" + (rn.rolledBackAtHuman ? (" · " + String(rn.rolledBackAtHuman)) : ""))
2457
+ : null,
2458
+ h("span", { className: "smem-ad-sub", key: "x" }, declOpen === rn.runId ? "收起" : "看声明"),
2459
+ ]),
2460
+ declOpen === rn.runId
2461
+ ? h("pre", { className: "smem-ad-md", key: "body" }, declBody)
2462
+ : null,
2463
+ ]);
2464
+ })),
2465
+ ]),
2466
+ h("div", { className: "smem-autodream-sec", key: "rep" }, [
2467
+ h("div", { className: "smem-autodream-row", key: "h" }, [
2155
2468
  h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "历史报告"),
2156
2469
  h("span", { className: "smem-status", key: "c" }, String(reports.length) + " 份"),
2157
2470
  ]),
2158
2471
  reports.length === 0
2159
- ? h("div", { className: "smem-status", key: "e" }, "还没有报告 —— 跑一次就有了。报告写在 memory/dream/ 下,不会混进记忆列表。")
2160
- : h("div", { className: "smem-dream-list", key: "l" }, reports.slice(0, 12).map(function (r) {
2472
+ ? h("div", { className: "smem-status", key: "e" }, "还没有报告 —— 跑一次就有了。报告写在 memory/autodream/ 下,不会混进记忆列表。")
2473
+ : h("div", { className: "smem-autodream-list", key: "l" }, reports.slice(0, 12).map(function (r) {
2161
2474
  return h("button", {
2162
2475
  type: "button", key: r.name, className: "smem-report",
2163
2476
  "data-on": open === r.name ? "1" : "0",
2164
2477
  onClick: function () { openReport(open === r.name ? null : r.name); },
2165
- }, String(r.atHuman || "") + " · " + r.name + " · " + fmtSize(r.bytes) + (open === r.name ? " · 收起" : " · 查看"));
2478
+ }, [
2479
+ h("span", { key: "t" }, String(r.atHuman || "") + " · " + String(r.name) + " · " + fmtSize(r.bytes)),
2480
+ r.legacy ? h("span", { className: "smem-ad-badge smem-ad-badge--legacy", key: "lg", style: { marginLeft: "6px" } }, "旧版") : null,
2481
+ h("span", { className: "smem-ad-sub", key: "x", style: { marginLeft: "6px" } }, open === r.name ? "收起" : "查看"),
2482
+ ]);
2166
2483
  })),
2167
2484
  open && body ? h("pre", { className: "smem-report-pre", key: "body" }, body) : null,
2168
2485
  ]),
2486
+ listErr && !err ? h("div", { className: "smem-err", key: "lerr" }, "⚠ " + listErr) : null,
2169
2487
  err ? h("div", { className: "smem-err", key: "err" }, "⚠ " + err) : null,
2170
2488
  notice && !err ? h("div", { className: "smem-ok", key: "ok" }, "✓ " + notice) : null,
2171
2489
  ]);
@@ -2387,7 +2705,7 @@ const StarmapRuntime = (function (require, _react) {
2387
2705
  h("div", { className: "smem-tabs", key: "tabs" }, [
2388
2706
  h("button", { type: "button", className: "smem-tab" + (tab === "files" ? " smem-tab--on" : ""), key: "f", onClick: function () { setTab("files"); } }, "文件列表"),
2389
2707
  h("button", { type: "button", className: "smem-tab" + (tab === "star" ? " smem-tab--on" : ""), key: "s", onClick: function () { setTab("star"); } }, "记忆星图"),
2390
- h("button", { type: "button", className: "smem-tab" + (tab === "dream" ? " smem-tab--on" : ""), key: "d", onClick: function () { setTab("dream"); } }, "做梦"),
2708
+ h("button", { type: "button", className: "smem-tab" + (tab === "autodream" ? " smem-tab--on" : ""), key: "d", onClick: function () { setTab("autodream"); } }, "自动做梦"),
2391
2709
  ]),
2392
2710
  h("div", { className: "smem-head", key: "head" }, [
2393
2711
  h("span", { className: "smem-title", key: "t" }, "记忆文件(sage-mem)"),
@@ -2398,7 +2716,7 @@ const StarmapRuntime = (function (require, _react) {
2398
2716
  : "共 " + String(items.length) + " 个文件"),
2399
2717
  ]),
2400
2718
  // 搜索 / 刷新 / 添加 / 筛选都只属于「文件列表」这个视图。
2401
- // 放在 tab 分支外面,会让星图和做梦页也顶着一排用不上的控件
2719
+ // 放在 tab 分支外面,会让星图和自动做梦页也顶着一排用不上的控件
2402
2720
  // (2026-09-27 截图走查时发现:「+ 添加记忆」出现在星图页上)。
2403
2721
  tab === "files" ? h("div", { className: "smem-toolbar", key: "bar" }, [
2404
2722
  h("input", { className: "smem-search", key: "q", value: query, placeholder: "搜索文件名 / 描述 / 类型 / 标签…", onChange: function (e) { setQuery(e.target.value); } }),
@@ -2450,11 +2768,11 @@ const StarmapRuntime = (function (require, _react) {
2450
2768
  })),
2451
2769
  ]),
2452
2770
  h("label", { className: "smem-label", key: "ln" }, "文件名(.md,留空自动 " + formType + "_note.md)"),
2453
- h("input", { className: "smem-input", key: "tn", value: formName, onChange: function (e) { setFormName(e.target.value); }, placeholder: "例如 project_traveler.md" }),
2771
+ h("input", { className: "smem-input", key: "tn", value: formName, onChange: function (e) { setFormName(e.target.value); }, placeholder: "例如 project_example.md" }),
2454
2772
  h("label", { className: "smem-label", key: "ld" }, "描述(写进 frontmatter,检索与列表都靠它)"),
2455
2773
  h("input", { className: "smem-input", key: "td", value: formDesc, onChange: function (e) { setFormDesc(e.target.value); }, placeholder: "一句话说清这条记忆是什么" }),
2456
2774
  h("label", { className: "smem-label", key: "lt" }, "自定义标签(可选,逗号分隔;会写进 frontmatter 的 tags,列表页可按它筛)"),
2457
- h("input", { className: "smem-input", key: "tt", value: formTags, onChange: function (e) { setFormTags(e.target.value); }, placeholder: "例如 发布, 脱敏, dsh" }),
2775
+ h("input", { className: "smem-input", key: "tt", value: formTags, onChange: function (e) { setFormTags(e.target.value); }, placeholder: "例如 部署, 笔记, dsh" }),
2458
2776
  h("label", { className: "smem-label", key: "lb" }, "正文(可选,frontmatter 自动生成)"),
2459
2777
  h("textarea", { className: "smem-textarea", key: "tb", rows: 6, value: formBody, onChange: function (e) { setFormBody(e.target.value); }, placeholder: "要长期记住的细节" }),
2460
2778
  h("div", { className: "smem-row", key: "btns" }, [
@@ -2463,8 +2781,8 @@ const StarmapRuntime = (function (require, _react) {
2463
2781
  ]),
2464
2782
  ])
2465
2783
  : null,
2466
- tab === "dream"
2467
- ? h(DreamPanel, { key: "dream", ctx: props.ctx })
2784
+ tab === "autodream"
2785
+ ? h(AutodreamPanel, { key: "autodream", ctx: props.ctx })
2468
2786
  : tab === "star"
2469
2787
  ? h("div", { className: "smem-starmap", key: "starmap" },
2470
2788
  h(StarmapRuntime.StarMap, { ctx: props.ctx }))
@@ -2499,7 +2817,7 @@ const StarmapRuntime = (function (require, _react) {
2499
2817
 
2500
2818
  /**
2501
2819
  * Client 插件:$mount strict remote contribution(第三方 remote 不在
2502
- * dsh-api-remotes 的固定列表里,必须自己挂),再注册设置页签。
2820
+ * 宿主的固定 remote 清单里,必须自己挂),再注册设置页签。
2503
2821
  */
2504
2822
  async function apply(ctx) {
2505
2823
  await ctx.remote.$mount(TYPERT_REMOTE);