@a9i5k4/dsh-auto-memory 2.2.3 → 2.2.4
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/docs/USER-GUIDE.zh-CN.md +237 -52
- package/lib/client.js +272 -118
- package/lib/index.js +329 -60
- package/lib/subagent-gc.js +239 -0
- package/lib/water-window.js +88 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -270,8 +270,8 @@ window.__ModuleLoader__.load({
|
|
|
270
270
|
fConsScheduleDays: '固化回看天数', fConsScheduleDaysHint: '定时固化读取最近 N 天日志;默认 7。',
|
|
271
271
|
fMaintSchedule: '定时 30 天蒸馏', fMaintScheduleHint: '每天到点自动把超过 30 天的旧日志蒸馏归档(等价 memory_maintain);无旧日志时零成本跳过。',
|
|
272
272
|
fMaintScheduleTime: '蒸馏触发时间', fMaintScheduleTimeHint: 'HH:MM,默认 10:00(与固化时间错开)。',
|
|
273
|
-
waterCardTitle: '上下文水位', waterAutoSrc: '自动检测', waterManualSrc: '手动设定', waterMeterOfficial: '官方计量', waterMeterHeuristic: '启发式降级', waterThresholdHint: '水位达到 {t} 时注入交接建议并自动补写账本;计量优先用官方 token-meter(与聊天框 context ring 同源)
|
|
274
|
-
autoContTitle: '自动接续', autoContHint: '
|
|
273
|
+
waterCardTitle: '上下文水位', waterAutoSrc: '自动检测', waterManualSrc: '手动设定', waterOfficialSrc: '官方路由容量', waterFallbackSrc: '回退默认值', waterMeterOfficial: '官方计量', waterMeterHeuristic: '启发式降级', waterThresholdHint: '水位达到 {t} 时注入交接建议并自动补写账本;计量优先用官方 token-meter(与聊天框 context ring 同源),不可用时降级启发式估算;窗口优先取官方路由容量(request/context 的 contextWindow),其次按当前模型查 settings.yaml contextWindow,也可在下方手动覆盖。',
|
|
274
|
+
autoContTitle: '自动接续', autoContHint: '开启后:水位达到阈值、且旧会话已空闲(harness 权威 running 位在轮次边界转为 false,取代旧「两轮水位持平」)时,弹出确认卡——同意=立即接续,拒绝=本轮跳过(同一边界不再提示),30-40 秒无操作视为挂机自动接续;接续前先让旧 Agent 刷新白板 PLAN 与交接账本,再建新会话(沿用工作区/模型/思考档位)并注入分层交接材料;触发后默认 30 分钟内不重复。', autoContOn: '自动接续:开', autoContOff: '自动接续:关', autoContThreshold: '阈值', autoContCountdown: '⏳ {s} 秒后自动接续…', autoContCancel: '取消', autoContConfirm: '本会话水位已达 {p}%,旧会话已空闲(轮次边界)——现在接续到新会话?', autoContAgree: '同意接续', autoContReject: '拒绝', autoContTimeout: '{s} 秒后自动接续(视为挂机)', autoContRejected: '已跳过本次接续(下一个轮次边界再提醒)',
|
|
275
275
|
continueCardTitle: '一键接续', continueCardHint: '写好交接账本→创建新会话→把交接材料作为首条消息预载,并自动切换到新会话继续任务(接续材料含白板节选+最新账本;当前工作区无账本时自动取全局最近一篇)。', continueBtn: '一键接续到新会话', continueBusy: '处理中…',
|
|
276
276
|
fStyle: '反思风格', fStyleHint: '生活化 / 专业性 / 由内容决定。',
|
|
277
277
|
fLocale: '界面语言', fLocaleHint: '默认跟随 DSH 系统语言;也可手动指定中文 / English。', followSystem: '跟随系统语言',
|
|
@@ -418,8 +418,8 @@ window.__ModuleLoader__.load({
|
|
|
418
418
|
fConsScheduleDays: 'Consolidation lookback (days)', fConsScheduleDaysHint: 'How many recent days of logs the scheduled consolidation reads; default 7.',
|
|
419
419
|
fMaintSchedule: 'Scheduled 30-day distill', fMaintScheduleHint: 'Once a day at the set time, distill & archive logs older than 30 days (same flow as memory_maintain); zero cost when nothing to archive.',
|
|
420
420
|
fMaintScheduleTime: 'Distill time', fMaintScheduleTimeHint: 'HH:MM, default 10:00 (staggered from the consolidation time).',
|
|
421
|
-
waterCardTitle: 'Context water level', waterAutoSrc: 'auto-detected', waterManualSrc: 'manual', waterMeterOfficial: 'official meter', waterMeterHeuristic: 'heuristic fallback', waterThresholdHint: 'Past {t} of the window, the handoff advisory is injected and the skeleton ledger is backfilled; metering prefers the official token-meter (same source as the chat context ring), falling back to the heuristic when unavailable; the window
|
|
422
|
-
autoContTitle: 'Auto continue', autoContHint: 'When on: once the water level passes the threshold
|
|
421
|
+
waterCardTitle: 'Context water level', waterAutoSrc: 'auto-detected', waterManualSrc: 'manual', waterOfficialSrc: 'route capacity', waterFallbackSrc: 'fallback default', waterMeterOfficial: 'official meter', waterMeterHeuristic: 'heuristic fallback', waterThresholdHint: 'Past {t} of the window, the handoff advisory is injected and the skeleton ledger is backfilled; metering prefers the official token-meter (same source as the chat context ring), falling back to the heuristic when unavailable; the window prefers the official route capacity (request/context contextWindow), then the active model in settings.yaml, or your manual override below.',
|
|
422
|
+
autoContTitle: 'Auto continue', autoContHint: 'When on: once the water level passes the threshold AND the old session is idle (the authoritative harness running bit flipped to false at a turn boundary, replacing the old "level unchanged across two polls"), a confirm card appears — Agree = continue now, Decline = skip this boundary (no re-prompt for it), no answer for 30-40s = unattended, continue automatically. Before continuing, the old agent refreshes PLAN + ledger; the new session keeps workspace/model/reasoning effort and receives layered handoff material; suppressed for 30 minutes after each run.', autoContOn: 'Auto continue: on', autoContOff: 'Auto continue: off', autoContThreshold: 'Threshold', autoContCountdown: '⏳ Auto-continue in {s}s…', autoContCancel: 'Cancel', autoContConfirm: 'Water level at {p}% and the old session is idle (turn boundary) — continue into a new session now?', autoContAgree: 'Continue', autoContReject: 'Decline', autoContTimeout: 'auto-continue in {s}s (unattended)', autoContRejected: 'Skipped this handoff (will ask again at the next turn boundary)',
|
|
423
423
|
continueCardTitle: 'One-click continue', continueCardHint: 'Writes the handoff ledger → creates a new session → injects the handoff material as the first message → switches to the new session automatically (material = PLAN excerpt + latest ledger; falls back to the most recent global ledger when this workspace has none).', continueBtn: 'Continue in a new session', continueBusy: 'Working…',
|
|
424
424
|
fStyle: 'Reflection style', fStyleHint: 'Life-style / Professional / Auto.',
|
|
425
425
|
fLocale: 'UI language', fLocaleHint: 'Follows the DSH system language by default; you can also pin Chinese / English.', followSystem: 'Follow system language',
|
|
@@ -446,6 +446,21 @@ window.__ModuleLoader__.load({
|
|
|
446
446
|
}
|
|
447
447
|
var sessions = null
|
|
448
448
|
var remoteFace = null
|
|
449
|
+
// 官方 session.create 返回形态多样(裸字符串 / {sessionId} / {id} / {value} / {ok,value:{sessionId}}),统一提取会话 id
|
|
450
|
+
function extractSessionId(created) {
|
|
451
|
+
if (created == null) return null
|
|
452
|
+
if (typeof created === 'string') return created
|
|
453
|
+
if (typeof created !== 'object') return null
|
|
454
|
+
if (typeof created.sessionId === 'string') return created.sessionId
|
|
455
|
+
if (typeof created.id === 'string') return created.id
|
|
456
|
+
var v = created.ok && created.value !== undefined ? created.value : (created.value !== undefined ? created.value : null)
|
|
457
|
+
if (v && typeof v === 'string') return v
|
|
458
|
+
if (v && typeof v === 'object') {
|
|
459
|
+
if (typeof v.sessionId === 'string') return v.sessionId
|
|
460
|
+
if (typeof v.id === 'string') return v.id
|
|
461
|
+
}
|
|
462
|
+
return null
|
|
463
|
+
}
|
|
449
464
|
// 当前会话工作区(跟随 GUI 切换工作区):从 sessions 服务拿,供 state/summarize/greet 请求使用
|
|
450
465
|
function currentWs() {
|
|
451
466
|
try {
|
|
@@ -457,6 +472,32 @@ window.__ModuleLoader__.load({
|
|
|
457
472
|
return (s && typeof s.cwd === 'string') ? s.cwd : ''
|
|
458
473
|
} catch (e) { return '' }
|
|
459
474
|
}
|
|
475
|
+
// 轮次边界探测(①,2026-09-08 2.2.4):读 harness 权威 running 位(sessions.list 快照,官方 buildListSnapshot 投影),
|
|
476
|
+
// 取代旧「连续两轮水位不增长=空闲」——长工具调用会被误判空闲、真挂机又永不触发。
|
|
477
|
+
function currentRunningInfo() {
|
|
478
|
+
try {
|
|
479
|
+
var snap = sessions && sessions.list && sessions.list.getSnapshot()
|
|
480
|
+
if (!snap) return null
|
|
481
|
+
var id = snap.current
|
|
482
|
+
if (id === undefined || id === null) return null
|
|
483
|
+
var s = snap.byId && snap.byId[id]
|
|
484
|
+
if (!s) return null
|
|
485
|
+
return { sessionId: id, running: !!s.running }
|
|
486
|
+
} catch (e) { return null }
|
|
487
|
+
}
|
|
488
|
+
function runningOfSession(sessionId) {
|
|
489
|
+
try {
|
|
490
|
+
var snap = sessions && sessions.list && sessions.list.getSnapshot()
|
|
491
|
+
var s = snap && snap.byId ? snap.byId[sessionId] : null
|
|
492
|
+
return s ? !!s.running : null
|
|
493
|
+
} catch (e) { return null }
|
|
494
|
+
}
|
|
495
|
+
function amCtzValue() {
|
|
496
|
+
return (typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat === 'function') ? (Intl.DateTimeFormat().resolvedOptions().timeZone || undefined) : undefined
|
|
497
|
+
}
|
|
498
|
+
function amReqId(prefix) {
|
|
499
|
+
return (window.crypto && typeof crypto.randomUUID === 'function') ? crypto.randomUUID() : (prefix || 'amc') + '-' + Date.now() + '-' + Math.random().toString(36).slice(2)
|
|
500
|
+
}
|
|
460
501
|
var fontScale = 'lg'
|
|
461
502
|
var FONT_SCALES = { sm: '小', md: '标准', lg: '大', xl: '特大' }
|
|
462
503
|
// 与服务端 lib/index.js DEFAULT_PROMPT_LAYERS 保持一致(设置页显示各层默认文案)
|
|
@@ -501,6 +542,22 @@ window.__ModuleLoader__.load({
|
|
|
501
542
|
|
|
502
543
|
// ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
|
|
503
544
|
var CHANGELOG = {
|
|
545
|
+
'2.2.4': { zh: [
|
|
546
|
+
'★ 修复:一键接续的新会话终于落进旧会话所属工作区——官方 session.create 只有传 workspaceId 才会绑定工作区,只传 cwd 仅设工作目录(故此前恒落「未分组」);现 host 先解析旧会话的 workspaceId 再建会话,解析不到才回退 cwd。',
|
|
547
|
+
'★ 修复:新会话沿用旧会话的模型与思考档位——模型信息在会话日志的 request/header.header.config(旧版查错了事件类型,恒取空 → 回落默认模型)。',
|
|
548
|
+
'★ 自动接续 v3:触发条件改为「水位≥阈值 且 harness 权威 running==false(轮次边界)」,弃用「两轮水位持平」——长工具调用不再误触发,真挂机一定触发;达阈值先弹确认卡:同意=立即接续 / 拒绝=本轮跳过(同一边界不再提示)/ 30-40 秒无操作=视为挂机自动接续。',
|
|
549
|
+
'★ 接续前刷新仪式:确认后先让旧 Agent 把白板 PLAN 与交接账本刷到最新,再用最新材料组装交接(可在配置里关掉);新窗口不再拿到过时白板。',
|
|
550
|
+
'★ 修复:上下文水位虚高(显示「500,998 / 131,072 token · 150%」)——模型窗口解析两条路同时失效:① settings.yaml 的 llm-deepseek 段是 flow 风格 YAML,旧解析器只认 block 风格;② 官方 request/context 的 contextWindow 只在会话开头出现(实测 2999 条事件里仅 2 条),旧代码只扫最近 256 条 → 1M 窗口被当成 128K。现在优先取官方路由容量、全量扫描并按会话缓存,设置页如实标出「官方路由容量 / 回退默认值」,百分比也不再截断到 150%。',
|
|
551
|
+
'★ 接续材料分层压缩:第0层指令+白板 / 第1层交接账本 / 第2层近期线程(最近 20 条 × 700 字,保留角色与工具标记)/ 第3层完整转写按需 read;材料带时间戳与「白板比账本旧」过期提示。',
|
|
552
|
+
'★ 新增子代理痕迹回收:自动沉淀 / 时段总结 / 问候 / 蒸馏等一次性子代理每跑一次都会在 ~/.dsh/sessions 下留下一个持久化会话(实测全机 686 个子代理会话里 638 个来自本插件),积累上千后会拖慢会话列表;现在子代理一结束就把痕迹移入 ~/.dsh/subagent-gc-backup(只移动不删除,可回滚),另有每日兜底巡检,设置页可开关并调整保留天数。',
|
|
553
|
+
], en: [
|
|
554
|
+
'★ Fix: one-click continue now lands the new session in the old session\'s workspace — the official session.create only binds a workspace when workspaceId is passed (cwd alone just sets the working directory, which is why it always landed in "Ungrouped"); the host now resolves the old session\'s workspaceId first and only falls back to cwd.',
|
|
555
|
+
'★ Fix: the new session now inherits the old session\'s model and reasoning effort — that info lives in request/header.header.config (the old code read the wrong event type and always got nothing, falling back to the default model).',
|
|
556
|
+
'★ Auto-continue v3: triggers on "water level ≥ threshold AND the authoritative harness running bit flipped to false (turn boundary)" instead of "level unchanged across two polls" — long tool calls no longer misfire and genuinely unattended sessions always fire; a confirm card appears first: Agree = continue now, Decline = skip this boundary (no re-prompt), 30-40s of silence = unattended, continue automatically.',
|
|
557
|
+
'★ Refresh ritual before continuing: the old agent refreshes PLAN + ledger first so the new session never starts from a stale whiteboard (configurable).',
|
|
558
|
+
'★ Layered handoff material: layer 0 instructions + PLAN / layer 1 ledger / layer 2 recent thread (last 20 entries × 700 chars, roles and tool markers kept) / layer 3 full transcript read on demand; timestamps and a "PLAN older than ledger" staleness hint included.',
|
|
559
|
+
'★ New subagent trace recycle: every one-shot subagent this plugin spawns (auto-consolidation, scheduled summary, greeting, distillation) left a persisted session under ~/.dsh/sessions (638 of the 686 subagent sessions on this machine came from this plugin), and enough of them slow the session list down. Each trace is now moved into ~/.dsh/subagent-gc-backup right after the task ends (moved, never deleted — fully reversible), plus a daily fallback sweep; toggle and retention days live in Settings.',
|
|
560
|
+
] },
|
|
504
561
|
'2.2.3': { zh: [
|
|
505
562
|
'打包修复:npm 包 files 现包含 docs/,用户文档随包发布——README 的「📖 用户文档」链接在 GitHub 与 npm 包页均可打开。',
|
|
506
563
|
], en: [
|
|
@@ -880,7 +937,7 @@ window.__ModuleLoader__.load({
|
|
|
880
937
|
'[data-dam-banner] { border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #e6a23c) 55%, transparent);',
|
|
881
938
|
' background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #e6a23c) 13%, transparent); border-radius: 9px; padding: 8px 10px; margin-bottom: 10px; font-size: calc(12.5px * var(--dam-scale)); }',
|
|
882
939
|
'[data-dam-input], [data-dam-select] { width: 100%; box-sizing: border-box; background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent); color: inherit; border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.3)) 60%, transparent); border-radius: 7px; padding: 6px 8px; font: inherit; }',
|
|
883
|
-
'[data-dam-row] { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }',
|
|
940
|
+
'[data-dam-row] { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }',
|
|
884
941
|
'[data-dam-row] label { flex: 0 0 110px; opacity: .8; font-size: calc(12.5px * var(--dam-scale)); }',
|
|
885
942
|
'[data-dam-hint] { opacity: .5; font-size: calc(11.5px * var(--dam-scale)); margin-top: 2px; }',
|
|
886
943
|
'[data-dam-sidebar-btn] { display: flex; align-items: center; gap: 6px; width: 100%; border: none; background: transparent; color: inherit; cursor: pointer; padding: 6px 10px; border-radius: 8px; font: inherit; font-size: 13px; opacity: .8; }',
|
|
@@ -1855,7 +1912,101 @@ window.__ModuleLoader__.load({
|
|
|
1855
1912
|
}
|
|
1856
1913
|
|
|
1857
1914
|
// M-CM6-C 自动接续跨渲染状态(apply 作用域,插件生命周期存活;组件重挂不重置)
|
|
1858
|
-
|
|
1915
|
+
// v3(2.2.4):lastRunning/edgeAt=轮次边界;rejectedEdgeAt=拒绝后同一边界不重复提示;suppressUntil=接续进行中不重复触发
|
|
1916
|
+
var autoState = { cdIv: null, lastTokens: null, lastRunning: null, edgeAt: 0, edgeSession: '', rejectedEdgeAt: 0, promptVisible: false, suppressUntil: 0 }
|
|
1917
|
+
|
|
1918
|
+
// ───────── 接续执行器(一键接续 + 自动接续共用,2026-09-08 2.2.4)─────────
|
|
1919
|
+
// 流程:③刷新仪式(旧 Agent 先刷 PLAN+账本)→ 取材料 → 修A(workspaceId 建会话)→ 序号标题
|
|
1920
|
+
// → 修B(沿用模型+思考档位)→ 打开 → 注入材料。
|
|
1921
|
+
var continueFlowBusy = false
|
|
1922
|
+
function continueCreateArgs(d) {
|
|
1923
|
+
// 修A(2026-09-08):官方 session.create 只接受 workspaceId 或 cwd 之一(同传报 bad-request),
|
|
1924
|
+
// 且只有 workspaceId 分支会 workspace.attachSession()(api-session-controller/lib/index.js:566-588);
|
|
1925
|
+
// 只传 cwd 仅设工作目录 → 新会话永远落「未分组工作区」。故优先 workspaceId,解析不到才回退 cwd。
|
|
1926
|
+
if (d && d.workspaceId) return { workspaceId: d.workspaceId, agentPreset: d.agentPreset || undefined }
|
|
1927
|
+
return d && d.ws ? { cwd: d.ws, agentPreset: d.agentPreset || undefined } : { agentPreset: (d && d.agentPreset) || undefined }
|
|
1928
|
+
}
|
|
1929
|
+
async function refreshOldSession(onMsg) {
|
|
1930
|
+
// ③接续前刷新仪式:让旧 Agent 把 PLAN.md 与交接账本刷到最新,host 再用最新材料组装交接。
|
|
1931
|
+
// 任何失败/超时都 fail-soft(用现有材料继续),绝不阻塞接续。
|
|
1932
|
+
try {
|
|
1933
|
+
var st = await apiGet(API.handoffState)
|
|
1934
|
+
var rf = st && st.refresh
|
|
1935
|
+
if (!rf || !rf.sessionId || !rf.prompt) return { ok: false, skipped: true }
|
|
1936
|
+
if (!remoteFace || !remoteFace.session || typeof remoteFace.session.prompt !== 'function') return { ok: false, skipped: true }
|
|
1937
|
+
var before = String((st.planMtime || 0)) + '|' + String((st.ledgers && st.ledgers[0] && st.ledgers[0].name) || '')
|
|
1938
|
+
if (onMsg) onMsg(locale === 'zh' ? '刷新仪式:请旧 Agent 更新白板 PLAN 与交接账本…' : 'Refresh ritual: asking the old agent to update PLAN + ledger…')
|
|
1939
|
+
await remoteFace.session.prompt({ requestId: amReqId('amr'), sessionId: rf.sessionId, mode: 'queue', content: [{ type: 'text', text: rf.prompt }], clientTimeZone: amCtzValue() })
|
|
1940
|
+
var waited = await waitForRefresh(rf.sessionId, before)
|
|
1941
|
+
return { ok: true, waited: waited }
|
|
1942
|
+
} catch (eRf) { return { ok: false, error: String(eRf && eRf.message ? eRf.message : eRf) } }
|
|
1943
|
+
}
|
|
1944
|
+
async function waitForRefresh(oldId, before) {
|
|
1945
|
+
// 结束条件(任一):材料变了 / 旧会话跑完这一轮(true→false) / 超时(默认 90s,配置可调)。
|
|
1946
|
+
var t0 = Date.now()
|
|
1947
|
+
var timeoutMs = 90000
|
|
1948
|
+
try {
|
|
1949
|
+
var cfg = await apiGet(API.config)
|
|
1950
|
+
var sec = cfg && Number(cfg.autoContinueRefreshTimeoutSeconds)
|
|
1951
|
+
if (sec >= 15 && sec <= 600) timeoutMs = sec * 1000
|
|
1952
|
+
} catch (eC) {}
|
|
1953
|
+
var sawRunning = false
|
|
1954
|
+
while (Date.now() - t0 < timeoutMs) {
|
|
1955
|
+
await new Promise(function (r) { setTimeout(r, 2500) })
|
|
1956
|
+
var run = runningOfSession(oldId)
|
|
1957
|
+
if (run === true) sawRunning = true
|
|
1958
|
+
var st2 = null
|
|
1959
|
+
try { st2 = await apiGet(API.handoffState) } catch (eG) {}
|
|
1960
|
+
var now = String((st2 && st2.planMtime) || 0) + '|' + String((st2 && st2.ledgers && st2.ledgers[0] && st2.ledgers[0].name) || '')
|
|
1961
|
+
if (now !== before) return 'updated'
|
|
1962
|
+
if (sawRunning && run === false) return 'turn-ended'
|
|
1963
|
+
}
|
|
1964
|
+
return 'timeout'
|
|
1965
|
+
}
|
|
1966
|
+
async function executeContinue(d, onMsg) {
|
|
1967
|
+
var rf = remoteFace
|
|
1968
|
+
if (!rf || !rf.session || typeof rf.session.create !== 'function') throw new Error(locale === 'zh' ? 'harness 未提供 remote.session(版本过旧?)' : 'remote.session unavailable (harness too old?)')
|
|
1969
|
+
if (onMsg) onMsg(locale === 'zh' ? '正在创建新会话(沿用旧工作区与模型)…' : 'Creating session (same workspace & model)…')
|
|
1970
|
+
var created
|
|
1971
|
+
try { created = await rf.session.create(continueCreateArgs(d)) } catch (eCreate) {
|
|
1972
|
+
// workspaceId 已失效(工作区被删)或旧 harness 不认 → 回退 cwd,再退纯 agentPreset
|
|
1973
|
+
created = await rf.session.create(d && d.ws ? { cwd: d.ws, agentPreset: d.agentPreset || undefined } : { agentPreset: (d && d.agentPreset) || undefined })
|
|
1974
|
+
}
|
|
1975
|
+
// 官方 create 返回裸 SessionId 字符串(而非对象),兼容字符串/{sessionId}/{id}/{value}/{ok,value}
|
|
1976
|
+
var newId = extractSessionId(created)
|
|
1977
|
+
if (!newId) throw new Error('create: no sessionId')
|
|
1978
|
+
// 接续序号标题:让用户一眼区分新会话 vs 延续会话
|
|
1979
|
+
if (d.contSeq && rf.session.rename && typeof rf.session.rename === 'function') {
|
|
1980
|
+
var contTitle = (locale === 'zh' ? '接续 #' : 'Cont.#') + String(d.contSeq) + (d.wsBase ? ' · ' + d.wsBase : '')
|
|
1981
|
+
try { await rf.session.rename({ sessionId: newId, title: contTitle }) } catch (eRen) {}
|
|
1982
|
+
}
|
|
1983
|
+
// 沿用旧会话模型(provider/model/reasoningEffort 由 host 从旧会话 request/header 折叠而来;不可用则静默回退路由默认)
|
|
1984
|
+
if (d.provider && d.model && typeof rf.session.selectModel === 'function') {
|
|
1985
|
+
if (onMsg) onMsg(locale === 'zh' ? '正在沿用旧模型 ' + d.model + (d.reasoningEffort ? '(' + d.reasoningEffort + ')' : '') + '…' : 'Restoring model ' + d.model + '…')
|
|
1986
|
+
try { await rf.session.selectModel({ sessionId: newId, provider: d.provider, model: d.model, reasoningEffort: d.reasoningEffort || undefined }) } catch (eMdl) {}
|
|
1987
|
+
}
|
|
1988
|
+
try { if (sessions && typeof sessions.open === 'function') sessions.open(newId) } catch (eOpen) {}
|
|
1989
|
+
await rf.session.prompt({ requestId: amReqId('amc'), sessionId: newId, mode: 'queue', content: [{ type: 'text', text: d.carryText }], clientTimeZone: amCtzValue() })
|
|
1990
|
+
return newId
|
|
1991
|
+
}
|
|
1992
|
+
async function runContinueFlow(opts) {
|
|
1993
|
+
var onMsg = (opts && opts.onProgress) || function () {}
|
|
1994
|
+
if (continueFlowBusy) return { ok: false, error: 'busy' }
|
|
1995
|
+
continueFlowBusy = true
|
|
1996
|
+
autoState.suppressUntil = Date.now() + 5 * 60 * 1000
|
|
1997
|
+
try {
|
|
1998
|
+
var cfg = null
|
|
1999
|
+
try { cfg = await apiGet(API.config) } catch (eCfg) {}
|
|
2000
|
+
var ritual = !(cfg && cfg.autoContinueRefreshRitual === false) && !(opts && opts.skipRefresh)
|
|
2001
|
+
if (ritual) { try { await refreshOldSession(onMsg) } catch (eRf) {} }
|
|
2002
|
+
onMsg(locale === 'zh' ? '正在构造交接材料(含旧会话转写)…' : 'Preparing handoff material (incl. prev-session transcript)…')
|
|
2003
|
+
var d = await apiPost(API.handoffContinue, {})
|
|
2004
|
+
if (!d || !d.ok) throw new Error((d && d.error) || 'no handoff material')
|
|
2005
|
+
var newId = await executeContinue(d, onMsg)
|
|
2006
|
+
try { localStorage.setItem('dsh-auto-memory.autoCont.last', String(Date.now())) } catch (eL) {}
|
|
2007
|
+
return { ok: true, sessionId: newId, workspaceId: d.workspaceId || '', model: d.model || '', reasoningEffort: d.reasoningEffort || '' }
|
|
2008
|
+
} finally { continueFlowBusy = false }
|
|
2009
|
+
}
|
|
1859
2010
|
|
|
1860
2011
|
function PlanTab() {
|
|
1861
2012
|
var dataPair = useState(null)
|
|
@@ -1874,13 +2025,10 @@ window.__ModuleLoader__.load({
|
|
|
1874
2025
|
var contMsgPair = useState('')
|
|
1875
2026
|
var contMsg = contMsgPair[0]
|
|
1876
2027
|
var setContMsg = contMsgPair[1]
|
|
1877
|
-
// M-CM6-C 自动接续(
|
|
2028
|
+
// M-CM6-C 自动接续(①轮次边界触发 + ②确认卡由全局 AutoContinueHost 执行;这里只读/写开关与阈值)
|
|
1878
2029
|
var autoPair = useState(null)
|
|
1879
2030
|
var autoCfg = autoPair[0]
|
|
1880
2031
|
var setAutoCfg = autoPair[1]
|
|
1881
|
-
var cdPair = useState(0)
|
|
1882
|
-
var cd = cdPair[0]
|
|
1883
|
-
var setCd = cdPair[1]
|
|
1884
2032
|
useEffect(function () {
|
|
1885
2033
|
apiGet(API.config).then(function (c) {
|
|
1886
2034
|
setAutoCfg({ enabled: !(c && c.autoContinueEnabled === false), threshold: (c && Number(c.autoContinueThreshold)) || 0.8 })
|
|
@@ -1890,47 +2038,13 @@ window.__ModuleLoader__.load({
|
|
|
1890
2038
|
setAutoCfg(function (p) { return Object.assign({}, p, patch) })
|
|
1891
2039
|
apiPost(API.config, patch).catch(function () {})
|
|
1892
2040
|
}
|
|
1893
|
-
var autoCancel = function () {
|
|
1894
|
-
if (autoState.cdIv) { clearInterval(autoState.cdIv); autoState.cdIv = null }
|
|
1895
|
-
setCd(0)
|
|
1896
|
-
}
|
|
1897
|
-
var autoStartCountdown = function () {
|
|
1898
|
-
if (autoState.cdIv) return
|
|
1899
|
-
var left = 30
|
|
1900
|
-
setCd(left)
|
|
1901
|
-
autoState.cdIv = setInterval(function () {
|
|
1902
|
-
left -= 1
|
|
1903
|
-
if (left <= 0) {
|
|
1904
|
-
clearInterval(autoState.cdIv); autoState.cdIv = null; setCd(0)
|
|
1905
|
-
try { localStorage.setItem('dsh-auto-memory.autoCont.last', String(Date.now())) } catch (eL) {}
|
|
1906
|
-
oneClickContinue()
|
|
1907
|
-
} else setCd(left)
|
|
1908
|
-
}, 1000)
|
|
1909
|
-
}
|
|
1910
2041
|
async function oneClickContinue() {
|
|
2042
|
+
// 一键接续 = 共用执行器(刷新仪式 → 材料 → workspaceId 建会话 → 沿用模型 → 注入),与自动接续同一路径
|
|
1911
2043
|
if (contBusy) return
|
|
1912
2044
|
setContBusy(true)
|
|
1913
|
-
setContMsg(locale === 'zh' ? '正在构造交接材料(含旧会话转写)…' : 'Preparing handoff material (incl. prev-session transcript)…')
|
|
1914
2045
|
try {
|
|
1915
|
-
var
|
|
1916
|
-
|
|
1917
|
-
var rf = remoteFace
|
|
1918
|
-
if (!rf || !rf.session || typeof rf.session.create !== 'function') throw new Error(locale === 'zh' ? 'harness 未提供 remote.session(版本过旧?)' : 'remote.session unavailable (harness too old?)')
|
|
1919
|
-
setContMsg(locale === 'zh' ? '正在创建新会话(沿用旧工作区与权限)…' : 'Creating session (same workspace & permissions)…')
|
|
1920
|
-
// M-CM6-B v2:同 cwd 创建 → 工作区绑定/权限状态与旧会话一致;失败回退无参创建
|
|
1921
|
-
var created
|
|
1922
|
-
try { created = await rf.session.create(d.ws ? { cwd: d.ws } : {}) } catch (eCwd) { created = await rf.session.create({}) }
|
|
1923
|
-
var newId = created && (created.sessionId || created.id)
|
|
1924
|
-
if (!newId) throw new Error('create: no sessionId')
|
|
1925
|
-
// 沿用旧会话模型(provider/model 由 host 从旧会话 request/context 折叠而来;不可用则静默回退路由默认)
|
|
1926
|
-
if (d.provider && d.model && typeof rf.session.selectModel === 'function') {
|
|
1927
|
-
setContMsg(locale === 'zh' ? '正在沿用旧模型 ' + d.model + '…' : 'Restoring model ' + d.model + '…')
|
|
1928
|
-
try { await rf.session.selectModel({ sessionId: newId, provider: d.provider, model: d.model }) } catch (eMdl) {}
|
|
1929
|
-
}
|
|
1930
|
-
try { if (sessions && typeof sessions.open === 'function') sessions.open(newId) } catch (eOpen) {}
|
|
1931
|
-
var reqId = (window.crypto && typeof crypto.randomUUID === 'function') ? crypto.randomUUID() : 'amc-' + Date.now() + '-' + Math.random().toString(36).slice(2)
|
|
1932
|
-
await rf.session.prompt({ requestId: reqId, sessionId: newId, mode: 'queue', content: [{ type: 'text', text: d.carryText }] })
|
|
1933
|
-
setContMsg(locale === 'zh' ? '✓ 已创建新会话(模型/工作区已沿用)并预载交接材料' : '✓ Session created (model/workspace restored) with ledger injected')
|
|
2046
|
+
var r = await runContinueFlow({ onProgress: setContMsg })
|
|
2047
|
+
setContMsg((locale === 'zh' ? '✓ 已创建新会话(工作区/模型已沿用)并预载交接材料' : '✓ Session created (workspace/model restored) with ledger injected') + (r && r.model ? ' · ' + r.model + (r.reasoningEffort ? '/' + r.reasoningEffort : '') : ''))
|
|
1934
2048
|
} catch (e) {
|
|
1935
2049
|
setContMsg('✗ ' + String(e && e.message ? e.message : e))
|
|
1936
2050
|
} finally { setContBusy(false) }
|
|
@@ -1946,41 +2060,32 @@ window.__ModuleLoader__.load({
|
|
|
1946
2060
|
apiGet(API.handoffState, { file: open }).then(function (d) { if (alive) setContent(d.text || '') }).catch(function () {})
|
|
1947
2061
|
return function () { alive = false }
|
|
1948
2062
|
}, [open])
|
|
1949
|
-
|
|
1950
|
-
if (!autoCfg || !autoCfg.enabled) return
|
|
1951
|
-
var alive = true
|
|
1952
|
-
var tick = function () {
|
|
1953
|
-
apiGet(API.handoffState).then(function (d) {
|
|
1954
|
-
if (!alive || !d || !d.enabled) return
|
|
1955
|
-
var wl = d.waterLevel || {}
|
|
1956
|
-
if (!wl.window || !(wl.ratio >= (autoCfg.threshold || 0.7))) { autoState.lastTokens = null; return }
|
|
1957
|
-
var idle = autoState.lastTokens !== null && wl.tokens === autoState.lastTokens
|
|
1958
|
-
autoState.lastTokens = wl.tokens
|
|
1959
|
-
if (!idle) return
|
|
1960
|
-
var last = 0
|
|
1961
|
-
try { last = Number(localStorage.getItem('dsh-auto-memory.autoCont.last') || 0) } catch (eL) {}
|
|
1962
|
-
if (Date.now() - last < 30 * 60 * 1000) return
|
|
1963
|
-
autoStartCountdown()
|
|
1964
|
-
}).catch(function () {})
|
|
1965
|
-
}
|
|
1966
|
-
var iv = setInterval(tick, 20000)
|
|
1967
|
-
tick()
|
|
1968
|
-
return function () { alive = false; clearInterval(iv) }
|
|
1969
|
-
}, [autoCfg && autoCfg.enabled, autoCfg && autoCfg.threshold])
|
|
2063
|
+
// 自动触发已移交全局 AutoContinueHost(①轮次边界 + ②确认卡);面板这里只保留手动一键接续与开关/阈值。
|
|
1970
2064
|
useEffect(function () {
|
|
1971
2065
|
return function () { if (autoState.cdIv) { clearInterval(autoState.cdIv); autoState.cdIv = null } }
|
|
1972
2066
|
}, [])
|
|
2067
|
+
// 账本/计划版本按钮:窄面板下长文件名会被折成竖排,只显示短标签(时间),完整名放悬浮提示
|
|
2068
|
+
function shortLedgerName(name) {
|
|
2069
|
+
var m = /^(handoff|PLAN)-(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})\.md$/.exec(name || '')
|
|
2070
|
+
if (m) { var t = m[5] + ':' + m[6] + (m[1] === 'handoff' ? ':' + m[7] : ''); return (m[1] === 'PLAN' ? 'PLAN ' : '') + m[3] + '-' + m[4] + ' ' + t }
|
|
2071
|
+
var p = /^prev-session-.+-(\d{2})(\d{2})(\d{2})\.md$/.exec(name || '')
|
|
2072
|
+
if (p) return '↻ ' + p[1] + ':' + p[2] + ':' + p[3]
|
|
2073
|
+
return name
|
|
2074
|
+
}
|
|
1973
2075
|
if (!data) return h(Loading)
|
|
1974
2076
|
if (!data.enabled) return h(Card, { title: t('planTitle') }, h('div', { 'data-dam-hint': '' }, t('planDisabled')))
|
|
1975
2077
|
var wl = data.waterLevel || {}
|
|
1976
2078
|
var rows = [h(Card, { title: t('planTitle') }, h('div', { 'data-dam-content': '' }, data.plan || t('planEmpty')))]
|
|
1977
2079
|
if (wl.window > 0) {
|
|
1978
|
-
|
|
1979
|
-
var
|
|
2080
|
+
// 2.2.4:百分比显示真实水位(旧版截断到 150% 会把「窗口算错」伪装成「刚好超一点」);进度条宽度仍按 100% 截断。
|
|
2081
|
+
var rawPct = Math.round((wl.ratio || 0) * 100)
|
|
2082
|
+
var pct = Math.min(rawPct, 999)
|
|
2083
|
+
var barColor = rawPct >= 80 ? '#d4744f' : rawPct >= 50 ? '#d4a94f' : 'var(--dam-accent, #4f7cff)'
|
|
2084
|
+
var srcLabel = !wl.source ? '' : wl.source.indexOf('auto') === 0 ? t('waterAutoSrc') + ': ' + wl.source.slice(5) : (wl.source === 'official-context' ? t('waterOfficialSrc') : (wl.source === 'fallback' ? t('waterFallbackSrc') : t('waterManualSrc')))
|
|
1980
2085
|
rows.unshift(h(Card, { title: t('waterCardTitle') }, h('div', { 'data-dam-content': '' }, [
|
|
1981
2086
|
h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', marginBottom: '6px' } },
|
|
1982
2087
|
(wl.tokens || 0).toLocaleString() + ' / ' + wl.window.toLocaleString() + ' token · ' + pct + '%',
|
|
1983
|
-
' ', h('span', { 'data-dam-hint': '' },
|
|
2088
|
+
' ', h('span', { 'data-dam-hint': '' }, srcLabel),
|
|
1984
2089
|
' ', h('span', { 'data-dam-hint': '' }, wl.meter ? (wl.meter.indexOf('official') === 0 ? '· ' + t('waterMeterOfficial') + '(' + String(wl.meter).slice(9) + ')' : '· ' + t('waterMeterHeuristic')) : '')),
|
|
1985
2090
|
h('div', { style: { height: '6px', borderRadius: '3px', background: 'color-mix(in srgb, currentColor 12%, transparent)', overflow: 'hidden' } },
|
|
1986
2091
|
h('div', { style: { height: '100%', width: Math.min(pct, 100) + '%', background: barColor, borderRadius: '3px' } })),
|
|
@@ -1991,10 +2096,9 @@ window.__ModuleLoader__.load({
|
|
|
1991
2096
|
rows.unshift(h(Card, { title: t('autoContTitle') }, h('div', { 'data-dam-content': '' }, [
|
|
1992
2097
|
h('div', { 'data-dam-hint': '', style: { marginBottom: '6px' } }, t('autoContHint')),
|
|
1993
2098
|
h('div', { 'data-dam-row': '' },
|
|
1994
|
-
h('button', { 'data-dam-btn': '', onClick: function () {
|
|
2099
|
+
h('button', { 'data-dam-btn': '', onClick: function () { autoSave({ autoContinueEnabled: !autoCfg.enabled }) } }, autoCfg.enabled ? t('autoContOn') : t('autoContOff')),
|
|
1995
2100
|
h('label', { 'data-dam-hint': '', style: { marginLeft: '8px' } }, t('autoContThreshold') + ' ',
|
|
1996
2101
|
h('input', { 'data-dam-input': '', type: 'number', min: 0.5, max: 0.95, step: 0.05, value: String(autoCfg.threshold), style: { width: '64px' }, onChange: function (e) { var v = Number(e.target.value); if (v >= 0.5 && v <= 0.95) autoSave({ autoContinueThreshold: v }) } }))),
|
|
1997
|
-
cd > 0 ? h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: '#d4744f' } }, t('autoContCountdown').replace('{s}', String(cd)) + ' ', h('button', { 'data-dam-btn': '', onClick: autoCancel }, t('autoContCancel'))) : null,
|
|
1998
2102
|
])))
|
|
1999
2103
|
}
|
|
2000
2104
|
if (open) {
|
|
@@ -2003,11 +2107,11 @@ window.__ModuleLoader__.load({
|
|
|
2003
2107
|
} else {
|
|
2004
2108
|
if (data.planVersions && data.planVersions.length) {
|
|
2005
2109
|
rows.push(h(Card, { title: t('planVersions') }, h('div', { 'data-dam-row': '' }, data.planVersions.map(function (v) {
|
|
2006
|
-
return h('button', { 'data-dam-btn': '', key: v.name, onClick: function () { setOpen(v.name) } }, v.name)
|
|
2110
|
+
return h('button', { 'data-dam-btn': '', key: v.name, title: v.name, onClick: function () { setOpen(v.name) } }, shortLedgerName(v.name))
|
|
2007
2111
|
}))))
|
|
2008
2112
|
}
|
|
2009
2113
|
rows.push(h(Card, { title: t('planLedgers') }, h('div', { 'data-dam-row': '' }, (data.ledgers || []).length ? data.ledgers.map(function (v) {
|
|
2010
|
-
return h('button', { 'data-dam-btn': '', key: v.name, onClick: function () { setOpen(v.name) } }, v.name)
|
|
2114
|
+
return h('button', { 'data-dam-btn': '', key: v.name, title: v.name, onClick: function () { setOpen(v.name) } }, shortLedgerName(v.name))
|
|
2011
2115
|
}) : h('div', { 'data-dam-muted': '' }, t('empty')))))
|
|
2012
2116
|
// M-CM6-B 一键接续:写账本→建新会话→预载交接材料→自动切换
|
|
2013
2117
|
rows.push(h(Card, { title: t('continueCardTitle') }, h('div', { 'data-dam-content': '' }, [
|
|
@@ -3286,9 +3390,10 @@ window.__ModuleLoader__.load({
|
|
|
3286
3390
|
// ───────────────────────── 设置页 ─────────────────────────
|
|
3287
3391
|
var STYLE_IDS = ['auto', 'life', 'professional']
|
|
3288
3392
|
var LOCALE_IDS_LIST = ['system', 'zh', 'en']
|
|
3289
|
-
// M-CM6-C
|
|
3290
|
-
//
|
|
3291
|
-
//
|
|
3393
|
+
// M-CM6-C v3·常驻自动接续 watcher(2026-09-08 2.2.4):全局生效,与面板/页签是否打开无关。
|
|
3394
|
+
// ①触发 = autoContinueEnabled !== false && 水位≥阈值 && harness 权威 running==false(轮次边界,取代旧"两轮水位持平")
|
|
3395
|
+
// + 3s 短确认(防刚结束又起新一轮)→ ②确认卡(同意 / 拒绝 / 超时 N 秒自动接续;拒绝记录在案,同一边界不再提示)
|
|
3396
|
+
// → ③刷新仪式(旧 Agent 刷 PLAN+账本)→ 一键接续(workspaceId 建会话 → 沿用模型 → 注入材料)。
|
|
3292
3397
|
function AutoContinueHost() {
|
|
3293
3398
|
var cfgP = useState(null)
|
|
3294
3399
|
var acCfg = cfgP[0]
|
|
@@ -3302,6 +3407,9 @@ window.__ModuleLoader__.load({
|
|
|
3302
3407
|
var bsP = useState(false)
|
|
3303
3408
|
var acBusy = bsP[0]
|
|
3304
3409
|
var setAcBusy = bsP[1]
|
|
3410
|
+
var cfP = useState(null)
|
|
3411
|
+
var acConfirm = cfP[0]
|
|
3412
|
+
var setAcConfirm = cfP[1]
|
|
3305
3413
|
useEffect(function () {
|
|
3306
3414
|
var alive = true
|
|
3307
3415
|
var load = function () {
|
|
@@ -3315,73 +3423,115 @@ window.__ModuleLoader__.load({
|
|
|
3315
3423
|
var on = !(acCfg && acCfg.autoContinueEnabled === false)
|
|
3316
3424
|
if (!on) return
|
|
3317
3425
|
var thr = (acCfg && Number(acCfg.autoContinueThreshold)) || 0.8
|
|
3426
|
+
var confirmSec = Number(acCfg && acCfg.autoContinueConfirmSeconds)
|
|
3427
|
+
if (!(confirmSec >= 10 && confirmSec <= 120)) confirmSec = 35
|
|
3428
|
+
var cooldownMin = Number(acCfg && acCfg.autoContinueCooldownMinutes)
|
|
3429
|
+
if (!(cooldownMin > 0)) cooldownMin = 30
|
|
3318
3430
|
var alive = true
|
|
3319
|
-
var
|
|
3431
|
+
var maybePrompt = function (edgeAt) {
|
|
3432
|
+
if (!alive || acBusy || autoState.cdIv || autoState.promptVisible) return
|
|
3433
|
+
if (!edgeAt || Date.now() - edgeAt > 10 * 60 * 1000) return // 边界太旧不再提示
|
|
3434
|
+
if (Date.now() < autoState.suppressUntil) return
|
|
3435
|
+
if (autoState.rejectedEdgeAt === edgeAt) return // ②拒绝过同一边界
|
|
3320
3436
|
apiGet(API.handoffState).then(function (d) {
|
|
3321
3437
|
if (!alive || !d || !d.enabled) return
|
|
3322
3438
|
var wl = d.waterLevel || {}
|
|
3323
|
-
if (!wl.window || !(wl.ratio >= thr))
|
|
3324
|
-
var idle = autoState.lastTokens !== null && wl.tokens === autoState.lastTokens
|
|
3325
|
-
autoState.lastTokens = wl.tokens
|
|
3326
|
-
if (!idle || autoState.cdIv || acBusy) return
|
|
3439
|
+
if (!wl.window || !(wl.ratio >= thr)) return
|
|
3327
3440
|
var last = 0
|
|
3328
3441
|
try { last = Number(localStorage.getItem('dsh-auto-memory.autoCont.last') || 0) } catch (eL) {}
|
|
3329
|
-
if (Date.now() - last <
|
|
3330
|
-
|
|
3331
|
-
|
|
3442
|
+
if (Date.now() - last < cooldownMin * 60 * 1000) return
|
|
3443
|
+
if (autoState.promptVisible || autoState.cdIv) return
|
|
3444
|
+
setAcConfirm({ ratio: Number(wl.ratio) || 0, tokens: Number(wl.tokens) || 0, window: Number(wl.window) || 0, edgeAt: edgeAt })
|
|
3445
|
+
setAcSt('')
|
|
3446
|
+
autoState.promptVisible = true
|
|
3447
|
+
var left = confirmSec
|
|
3332
3448
|
setAcCd(left)
|
|
3333
3449
|
autoState.cdIv = setInterval(function () {
|
|
3334
3450
|
left -= 1
|
|
3335
3451
|
if (left <= 0) {
|
|
3336
3452
|
clearInterval(autoState.cdIv); autoState.cdIv = null; setAcCd(0)
|
|
3337
|
-
|
|
3453
|
+
autoState.promptVisible = false; setAcConfirm(null)
|
|
3454
|
+
runAuto() // ③超时 = 挂机 → 自动接续兜底
|
|
3338
3455
|
} else setAcCd(left)
|
|
3339
3456
|
}, 1000)
|
|
3340
3457
|
}).catch(function () {})
|
|
3341
3458
|
}
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3459
|
+
// 轮次边界:running true→false 边沿 + 3s 短确认
|
|
3460
|
+
var verifyEdge = function (edgeAt) {
|
|
3461
|
+
if (!alive || autoState.edgeAt !== edgeAt) return
|
|
3462
|
+
var info = currentRunningInfo()
|
|
3463
|
+
if (info && info.running) return // 短确认失败:新一轮已开始,不算边界
|
|
3464
|
+
maybePrompt(edgeAt)
|
|
3465
|
+
}
|
|
3466
|
+
var armEdge = function () {
|
|
3467
|
+
autoState.edgeAt = Date.now()
|
|
3468
|
+
var info = currentRunningInfo()
|
|
3469
|
+
autoState.edgeSession = info ? info.sessionId : ''
|
|
3470
|
+
var edgeAt = autoState.edgeAt
|
|
3471
|
+
setTimeout(function () { verifyEdge(edgeAt) }, 3000)
|
|
3472
|
+
}
|
|
3473
|
+
var observe = function () {
|
|
3474
|
+
if (!alive) return
|
|
3475
|
+
var info = currentRunningInfo()
|
|
3476
|
+
if (!info) return
|
|
3477
|
+
if (autoState.lastRunning === null) {
|
|
3478
|
+
autoState.lastRunning = info.running
|
|
3479
|
+
if (!info.running) armEdge() // 首次观察即空闲(挂机/刚进会话):按边界处理
|
|
3480
|
+
return
|
|
3481
|
+
}
|
|
3482
|
+
if (autoState.lastRunning === true && info.running === false) armEdge()
|
|
3483
|
+
autoState.lastRunning = info.running
|
|
3484
|
+
}
|
|
3485
|
+
var unsub = null
|
|
3486
|
+
try { if (sessions && sessions.list && typeof sessions.list.subscribe === 'function') unsub = sessions.list.subscribe(observe) } catch (eSub) {}
|
|
3487
|
+
observe()
|
|
3488
|
+
var iv = setInterval(function () { observe(); maybePrompt(autoState.edgeAt) }, 20000) // 兜底:水位后到 / 订阅不可用
|
|
3489
|
+
return function () {
|
|
3490
|
+
alive = false
|
|
3491
|
+
clearInterval(iv)
|
|
3492
|
+
if (unsub) { try { unsub() } catch (eU) {} }
|
|
3493
|
+
}
|
|
3494
|
+
}, [acCfg && acCfg.autoContinueEnabled === false, acCfg && (Number(acCfg.autoContinueThreshold) || 0.8), acCfg && acCfg.autoContinueConfirmSeconds, acCfg && acCfg.autoContinueCooldownMinutes, acBusy])
|
|
3346
3495
|
var cancelAc = function () {
|
|
3347
3496
|
if (autoState.cdIv) { clearInterval(autoState.cdIv); autoState.cdIv = null }
|
|
3497
|
+
autoState.promptVisible = false
|
|
3348
3498
|
setAcCd(0)
|
|
3349
3499
|
}
|
|
3350
|
-
var
|
|
3500
|
+
var runAuto = function () {
|
|
3351
3501
|
if (acBusy) return
|
|
3502
|
+
cancelAc()
|
|
3503
|
+
setAcConfirm(null)
|
|
3352
3504
|
if (!remoteFace || !remoteFace.session || typeof remoteFace.session.create !== 'function') {
|
|
3353
3505
|
setAcSt(locale === 'zh' ? '✗ remote.session 不可用——请重启 dsh web 后重试' : '✗ remote.session unavailable — restart dsh web')
|
|
3354
3506
|
return
|
|
3355
3507
|
}
|
|
3356
3508
|
setAcBusy(true)
|
|
3357
|
-
setAcSt(locale === 'zh' ? '
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
var p
|
|
3361
|
-
try { p = remoteFace.session.create(d.ws ? { cwd: d.ws } : {}) } catch (eC) { p = remoteFace.session.create({}) }
|
|
3362
|
-
return p.then(function (created) {
|
|
3363
|
-
var newId = created && (created.sessionId || created.id)
|
|
3364
|
-
if (!newId) throw new Error('create: no sessionId')
|
|
3365
|
-
var chain = Promise.resolve()
|
|
3366
|
-
if (d.provider && d.model && typeof remoteFace.session.selectModel === 'function') {
|
|
3367
|
-
chain = chain.then(function () { try { return remoteFace.session.selectModel({ sessionId: newId, provider: d.provider, model: d.model }) } catch (eM) { return null } })
|
|
3368
|
-
}
|
|
3369
|
-
return chain.then(function () {
|
|
3370
|
-
try { if (sessions && typeof sessions.open === 'function') sessions.open(newId) } catch (eO) {}
|
|
3371
|
-
var reqId = (window.crypto && typeof crypto.randomUUID === 'function') ? crypto.randomUUID() : 'ac-' + Date.now() + '-' + Math.random().toString(36).slice(2)
|
|
3372
|
-
return remoteFace.session.prompt({ requestId: reqId, sessionId: newId, mode: 'queue', content: [{ type: 'text', text: d.carryText }] })
|
|
3373
|
-
})
|
|
3374
|
-
})
|
|
3375
|
-
}).then(function () {
|
|
3376
|
-
try { localStorage.setItem('dsh-auto-memory.autoCont.last', String(Date.now())) } catch (eL) {}
|
|
3377
|
-
setAcSt(locale === 'zh' ? '✓ 已自动接续到新会话,可直接上手' : '✓ Auto-continued into a new session')
|
|
3509
|
+
setAcSt(locale === 'zh' ? '正在自动接续:刷新白板/账本 → 建新会话 → 注入材料…' : 'Auto-continuing: refresh PLAN/ledger → new session → inject…')
|
|
3510
|
+
runContinueFlow({ onProgress: function (m) { setAcSt(m) } }).then(function (r) {
|
|
3511
|
+
setAcSt((locale === 'zh' ? '✓ 已自动接续到新会话,可直接上手' : '✓ Auto-continued into a new session') + (r && r.model ? ' · ' + r.model + (r.reasoningEffort ? '/' + r.reasoningEffort : '') : ''))
|
|
3378
3512
|
setAcBusy(false)
|
|
3379
3513
|
}).catch(function (e) { setAcSt('✗ ' + String(e && e.message ? e.message : e)); setAcBusy(false) })
|
|
3380
3514
|
}
|
|
3381
|
-
|
|
3515
|
+
var onAgree = function () { runAuto() }
|
|
3516
|
+
var onReject = function () {
|
|
3517
|
+
var edgeAt = (acConfirm && acConfirm.edgeAt) || autoState.edgeAt || 0
|
|
3518
|
+
cancelAc()
|
|
3519
|
+
setAcConfirm(null)
|
|
3520
|
+
autoState.rejectedEdgeAt = edgeAt
|
|
3521
|
+
setAcSt(t('autoContRejected'))
|
|
3522
|
+
}
|
|
3523
|
+
if (acConfirm || acCd > 0 || acSt) {
|
|
3382
3524
|
return h('div', { 'data-dam-autocont': '', style: { position: 'fixed', right: '16px', bottom: '16px', zIndex: 9000, maxWidth: 'min(440px, calc(100vw - 32px))', boxSizing: 'border-box', padding: '10px 12px', borderRadius: '12px', border: '1px solid rgba(128,128,128,.35)', background: 'rgba(18,22,30,.93)', color: '#e8eaed', fontSize: '12px', lineHeight: '1.55', fontFamily: 'system-ui', boxShadow: '0 10px 30px rgba(0,0,0,.35)', backdropFilter: 'blur(10px)' } },
|
|
3383
|
-
|
|
3384
|
-
|
|
3525
|
+
acConfirm ? h('div', { 'data-dam-autocont-confirm': '' }, [
|
|
3526
|
+
h('div', { style: { marginBottom: '4px' } }, t('autoContConfirm').replace('{p}', String(Math.round((acConfirm.ratio || 0) * 100)))),
|
|
3527
|
+
h('div', { 'data-dam-hint': '' }, (acConfirm.tokens || 0).toLocaleString() + ' / ' + (acConfirm.window || 0).toLocaleString() + ' token'),
|
|
3528
|
+
h('div', { style: { marginTop: '8px' } },
|
|
3529
|
+
h('button', { 'data-dam-btn': '', onClick: onAgree }, t('autoContAgree')),
|
|
3530
|
+
h('button', { 'data-dam-btn': '', style: { marginLeft: '6px' }, onClick: onReject }, t('autoContReject')),
|
|
3531
|
+
acCd > 0 ? h('span', { 'data-dam-hint': '', style: { marginLeft: '8px' } }, t('autoContTimeout').replace('{s}', String(acCd))) : null),
|
|
3532
|
+
]) : null,
|
|
3533
|
+
!acConfirm && acCd > 0 ? h('div', null, t('autoContCountdown').replace('{s}', String(acCd)), ' ', h('button', { style: { marginLeft: '6px', border: '1px solid rgba(232,234,237,.45)', borderRadius: '8px', background: 'transparent', color: '#e8eaed', cursor: 'pointer', padding: '2px 10px' }, onClick: cancelAc }, t('autoContCancel'))) : null,
|
|
3534
|
+
acSt ? h('div', { style: (acConfirm || acCd > 0) ? { marginTop: '4px' } : {} }, acSt) : null)
|
|
3385
3535
|
}
|
|
3386
3536
|
return null
|
|
3387
3537
|
}
|
|
@@ -3912,7 +4062,11 @@ window.__ModuleLoader__.load({
|
|
|
3912
4062
|
field(t('fWaterWindow'), h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.waterLevelWindowTokens === undefined ? 65536 : cfg.waterLevelWindowTokens, onChange: function (e) { set('waterLevelWindowTokens', Number(e.target.value) || 0) } }), t('fWaterWindowHint')),
|
|
3913
4063
|
field(t('fWaterThreshold'), h('input', { 'data-dam-input': '', type: 'number', min: 0.1, max: 1.5, step: 0.05, value: cfg.waterLevelThreshold === undefined ? 0.8 : cfg.waterLevelThreshold, onChange: function (e) { set('waterLevelThreshold', Number(e.target.value) || 0.8) } }), t('fWaterThresholdHint')),
|
|
3914
4064
|
field(t('fWaterAdvisory'), h('input', { type: 'checkbox', checked: cfg.waterLevelAdvisory !== false, onChange: function (e) { set('waterLevelAdvisory', e.target.checked) } }), t('fWaterAdvisoryHint')),
|
|
3915
|
-
field(t('fWaterAuto'), h('input', { type: 'checkbox', checked: cfg.waterLevelAutoHandoff !== false, onChange: function (e) { set('waterLevelAutoHandoff', e.target.checked) } }), t('fWaterAutoHint'))
|
|
4065
|
+
field(t('fWaterAuto'), h('input', { type: 'checkbox', checked: cfg.waterLevelAutoHandoff !== false, onChange: function (e) { set('waterLevelAutoHandoff', e.target.checked) } }), t('fWaterAutoHint')),
|
|
4066
|
+
field(locale === 'zh' ? '子代理痕迹回收' : 'Subagent trace recycle', h('input', { type: 'checkbox', checked: cfg.subagentGcEnabled !== false, onChange: function (e) { set('subagentGcEnabled', e.target.checked) } }),
|
|
4067
|
+
locale === 'zh' ? '本插件的一次性子代理(自动沉淀 / 时段总结 / 问候 / 蒸馏)每跑一次都会在 ~/.dsh/sessions 留下一个持久化会话;数量上千后会话列表会明显变慢。开启后任务一结束就把这些痕迹移入 ~/.dsh/subagent-gc-backup 备份(可回滚),不影响子代理结果。默认开。' : 'Every one-shot subagent this plugin spawns (auto-consolidation, scheduled summary, greeting, distillation) leaves a persisted session under ~/.dsh/sessions; hundreds of them slow the session list down. When on, each trace is moved into ~/.dsh/subagent-gc-backup right after the task ends (reversible), never affecting the subagent result. On by default.'),
|
|
4068
|
+
field(locale === 'zh' ? '兜底回收保留天数' : 'Fallback recycle keep days', h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.subagentGcKeepDays === undefined ? 3 : cfg.subagentGcKeepDays, onChange: function (e) { set('subagentGcKeepDays', Number(e.target.value) || 0) } }),
|
|
4069
|
+
locale === 'zh' ? '每天巡检一次,回收超过该天数仍残留的痕迹(比如任务异常中断没删掉的);0 = 不按时间,只靠任务结束即删。' : 'A daily sweep recycles traces older than this many days (e.g. leftovers from interrupted tasks); 0 = rely on the end-of-task recycle only.')]),
|
|
3916
4070
|
section('maintenance', sectionLabels.maintenance, [field(t('fVersion'), h('div', { 'data-dam-row': '' },
|
|
3917
4071
|
h('span', { style: { flex: 1 } }, verInfo ? (verInfo.current || '?') + (verInfo.latest ? ' → ' + verInfo.latest + (verInfo.upToDate ? ' ' + t('upToDate') : ' ' + t('hasUpdate')) : '') : (checkingUpdate ? t('checking') : '—')),
|
|
3918
4072
|
h('button', { 'data-dam-btn': '', onClick: checkUpdate, disabled: checkingUpdate }, checkingUpdate ? t('checking') : t('checkUpdate')),
|
|
@@ -4172,7 +4326,7 @@ window.__ModuleLoader__.load({
|
|
|
4172
4326
|
console.log('[dsh-auto-memory] client ready: sidebar entry + panel + settings page')
|
|
4173
4327
|
}
|
|
4174
4328
|
|
|
4175
|
-
exports.inject = ['slots', 'sessions']
|
|
4329
|
+
exports.inject = ['slots', 'sessions', 'remote', 'remote.session']
|
|
4176
4330
|
exports.apply = apply
|
|
4177
4331
|
return module.exports
|
|
4178
4332
|
},
|