@lemoncat7/dsh-partner 1.13.0 → 1.14.1
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/README.md +10 -2
- package/docs/architecture.md +3 -0
- package/docs/releases/1.14.0.md +24 -0
- package/docs/releases/1.14.1.md +11 -0
- package/lib/agent-runtime.d.ts +23 -2
- package/lib/agent-runtime.d.ts.map +1 -1
- package/lib/agent-runtime.js +45 -282
- package/lib/agent-runtime.js.map +1 -1
- package/lib/api.js +34 -2
- package/lib/api.js.map +1 -1
- package/lib/autonomy.d.ts.map +1 -1
- package/lib/autonomy.js +13 -5
- package/lib/autonomy.js.map +1 -1
- package/lib/client-api.d.ts +8 -1
- package/lib/client-api.d.ts.map +1 -1
- package/lib/client-api.js.map +1 -1
- package/lib/client.js +1790 -1402
- package/lib/client.js.map +4 -4
- package/lib/companions/session-recovery.d.ts +7 -0
- package/lib/companions/session-recovery.d.ts.map +1 -0
- package/lib/companions/session-recovery.js +22 -0
- package/lib/companions/session-recovery.js.map +1 -0
- package/lib/concern-domain.d.ts +9 -0
- package/lib/concern-domain.d.ts.map +1 -1
- package/lib/concern-domain.js.map +1 -1
- package/lib/concern-recording.d.ts +20 -0
- package/lib/concern-recording.d.ts.map +1 -0
- package/lib/concern-recording.js +37 -0
- package/lib/concern-recording.js.map +1 -0
- package/lib/concern-store.d.ts +20 -1
- package/lib/concern-store.d.ts.map +1 -1
- package/lib/concern-store.js +170 -1
- package/lib/concern-store.js.map +1 -1
- package/lib/heartbeat.d.ts +16 -0
- package/lib/heartbeat.d.ts.map +1 -1
- package/lib/heartbeat.js +97 -9
- package/lib/heartbeat.js.map +1 -1
- package/lib/memory-errors.d.ts +6 -0
- package/lib/memory-errors.d.ts.map +1 -0
- package/lib/memory-errors.js +12 -0
- package/lib/memory-errors.js.map +1 -0
- package/lib/memory-journal.js +4 -4
- package/lib/memory-journal.js.map +1 -1
- package/lib/memory-reflection.d.ts.map +1 -1
- package/lib/memory-reflection.js +11 -3
- package/lib/memory-reflection.js.map +1 -1
- package/lib/memory-store.d.ts +4 -0
- package/lib/memory-store.d.ts.map +1 -1
- package/lib/memory-store.js +11 -3
- package/lib/memory-store.js.map +1 -1
- package/lib/observation-budget.d.ts +9 -0
- package/lib/observation-budget.d.ts.map +1 -0
- package/lib/observation-budget.js +9 -0
- package/lib/observation-budget.js.map +1 -0
- package/lib/observation-errors.d.ts +21 -0
- package/lib/observation-errors.d.ts.map +1 -0
- package/lib/observation-errors.js +36 -0
- package/lib/observation-errors.js.map +1 -0
- package/lib/observation-executor.d.ts +7 -0
- package/lib/observation-executor.d.ts.map +1 -0
- package/lib/observation-executor.js +23 -0
- package/lib/observation-executor.js.map +1 -0
- package/lib/observation-loop.d.ts +17 -0
- package/lib/observation-loop.d.ts.map +1 -0
- package/lib/observation-loop.js +180 -0
- package/lib/observation-loop.js.map +1 -0
- package/lib/recording-snapshot.d.ts +11 -0
- package/lib/recording-snapshot.d.ts.map +1 -0
- package/lib/recording-snapshot.js +19 -0
- package/lib/recording-snapshot.js.map +1 -0
- package/lib/ui/memory/concern-board.d.ts +4 -2
- package/lib/ui/memory/concern-board.d.ts.map +1 -1
- package/lib/ui/memory/concern-board.js +17 -6
- package/lib/ui/memory/concern-board.js.map +1 -1
- package/lib/ui/memory/concern-deletion.d.ts +12 -0
- package/lib/ui/memory/concern-deletion.d.ts.map +1 -0
- package/lib/ui/memory/concern-deletion.js +43 -0
- package/lib/ui/memory/concern-deletion.js.map +1 -0
- package/lib/ui/memory/concern-editor.d.ts +8 -0
- package/lib/ui/memory/concern-editor.d.ts.map +1 -0
- package/lib/ui/memory/concern-editor.js +37 -0
- package/lib/ui/memory/concern-editor.js.map +1 -0
- package/lib/ui/memory/concern-panel.d.ts.map +1 -1
- package/lib/ui/memory/concern-panel.js +51 -12
- package/lib/ui/memory/concern-panel.js.map +1 -1
- package/lib/ui/memory/concern-record-fields.d.ts +10 -0
- package/lib/ui/memory/concern-record-fields.d.ts.map +1 -0
- package/lib/ui/memory/concern-record-fields.js +7 -0
- package/lib/ui/memory/concern-record-fields.js.map +1 -0
- package/lib/ui/memory/job-feedback.d.ts +5 -0
- package/lib/ui/memory/job-feedback.d.ts.map +1 -0
- package/lib/ui/memory/job-feedback.js +7 -0
- package/lib/ui/memory/job-feedback.js.map +1 -0
- package/lib/ui/memory/memory-panel.d.ts.map +1 -1
- package/lib/ui/memory/memory-panel.js +4 -2
- package/lib/ui/memory/memory-panel.js.map +1 -1
- package/lib/ui/memory/record-target-picker.d.ts +7 -0
- package/lib/ui/memory/record-target-picker.d.ts.map +1 -0
- package/lib/ui/memory/record-target-picker.js +33 -0
- package/lib/ui/memory/record-target-picker.js.map +1 -0
- package/lib/ui/memory/types.d.ts +4 -0
- package/lib/ui/memory/types.d.ts.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -197,8 +197,8 @@
|
|
|
197
197
|
- 伙伴从记忆提炼或每日终审中自动新建关注后,会在当前伙伴会话中发出一次创建通知,并同步到对应的微信渠道,方便及时检查或移除
|
|
198
198
|
- 心跳只查询到期挂念,使用独立内存推理作用域观察上次检查后的变化;无到期事项时不会调用模型
|
|
199
199
|
- 心跳根据挂念选择当前可用的安全来源,并在来源内部逐层缩小范围:知识库按“找库→库内搜索→读取条目”,本地按“确定范围→定位文件/内容→读取证据”;发现阶段有独立软截止、单工具超时和整轮安全上限
|
|
200
|
-
-
|
|
201
|
-
-
|
|
200
|
+
- 关注可使用来源会话已有的 bash 工具完成文档要求的多步检查,保留 DSH 原有审批,不另行实现命令执行或自动授权。
|
|
201
|
+
- `@文件` 仅作为只读依据;记录写权限只覆盖用户在“需要记录的地方”手动选择的现存文件或笔记,不能创建其他文件或触碰伙伴私有存储
|
|
202
202
|
- 发现结果先保存为 Observation,经过去重、相关性、新鲜度、可行动性和近期打扰历史的确定性评分,再决定静默记下、下次顺手一提、进入伙伴动态或主动通知
|
|
203
203
|
- 心跳硬性排除伙伴记忆、挂念数据库、会话归档及其备份,不向无关主题扩散
|
|
204
204
|
- 未送达的重要提醒会保留并安全补发;普通对话开始时只召回与当前消息相关的延迟提及
|
|
@@ -324,3 +324,11 @@ ClawHub、LoopHub、SkillHub 是内置公共来源,默认启用且不能删除
|
|
|
324
324
|
`skillUrl` 可相对索引 URL。市场索引上限 1 MiB、单个 `SKILL.md` 上限 512 KiB;未标记为可信的市场即使声明 `context: inline`,安装后也会强制改为 `fork`。
|
|
325
325
|
|
|
326
326
|
市场页面的“代理设置”接受不含账号、密码和路径的 `http://host:port`。配置仅保存在当前客户端的伙伴状态中,同时作用于市场索引、Markdown Skill 和 ZIP 安装包下载;测试连接不会先写入配置。需要鉴权的代理应在本地网关侧处理,不要把凭据写进代理 URL。
|
|
327
|
+
|
|
328
|
+
## 关注依据与记录位置
|
|
329
|
+
|
|
330
|
+
关注使用后台轻量模型循环,不创建临时 Agent 或向聊天写入过程消息。每条关注独立执行,最多 12 轮工具交互和一轮无工具收尾,不再共用整批总时限或工具调用次数。单次模型请求最多 180 秒,单次工具最多 60 秒(工具自身可有更短限制),收尾有独立请求时间。每条完成立即保存;失败只保留未完成关注等待重试。只提供关联依据与记录目标,按文档完成代理、登录和查询;工具走来源作用域的 DSH 执行和审批,不自动批准。输出截断需按需分页。依据只读,结果仅写指定目标;检查受阻不能当作无变化。工具审计不复制参数正文。任意命令的文件隔离仍依赖 DSH 沙箱,工具名限制不等同文件系统隔离。
|
|
331
|
+
|
|
332
|
+
“交代一件事”中的 @文件或 @知识文档是只读检查依据;“需要记录的地方”另选现存本地文件或可编辑笔记。没有记录位置时不写文档,旧关注不自动把依据迁移成输出位置。有依据按依据整理,没有依据自主记录核实的现状和进展;笔记使用稳定 ID 绑定,知识库插件需提供 `dshKnowledgeNoteRecording` 宿主接口。心跳的笔记工具只接收 concernId,不允许模型选择其他笔记 ID;本地文件写权限只覆盖用户选择的现存文件。该入口不包含创建、删除、移动或重命名记录目标。首次可初始化已核实现状,后续避免重复追加,写入与通知判断相互独立。
|
|
333
|
+
|
|
334
|
+
完整核验结果通过 `heartbeat_record_checkpoint` 持久暂存,后台独立同步到记录目标。失败15分钟后仅重试同步,不重新抓取或提醒;实际写入成功才清除任务。详见 [1.14.0 发布说明](docs/releases/1.14.0.md)。
|
package/docs/architecture.md
CHANGED
|
@@ -295,3 +295,6 @@ form and save feedback, using the shared companion draft and existing UI tokens.
|
|
|
295
295
|
- `api/features/pendant-api.ts` 沿用原有 API 同源和写操作校验,只提供固定渲染资源、消息查询和批量已读。前端可见时每 4 秒条件查询(ETag);无变化不传正文,失败退避至 30 秒,隐藏或卸载时取消请求。
|
|
296
296
|
- 验证:`test/pendant-inbox.test.mjs`、`test/pendant-physics.test.mjs`;构建后设置 `PARTNER_PLAYWRIGHT_MODULE` 并运行 `node scripts/verify-pendant.mjs`,使用隔离假数据检查 WebGL、真实拖拽、拉住停顿后的回弹与反向振荡、休眠、键盘、通知导航、移动端和清理。可设置 `PARTNER_PENDANT_VIDEO_DIR` 保存连续交互录像。
|
|
297
297
|
- 卡面验证:`test/pendant-motion-glare.test.mjs` 覆盖角度往返、固定姿态十秒不漂移、四元数等价、强度边界、减少动态效果和背面关闭;`test/pendant-surface.test.mjs` 覆盖共享 uniform 与背面不变。`scripts/verify-pendant-gloss.mjs` 检查 162 组原色误差、渐变参考对照、不同倾角的光带位移与对比度保护及无额外绘制。浏览器测试覆盖 hover 不改变反光、拖拽改变角度时更新、握持静止高光不循环并正常休眠。
|
|
298
|
+
## 轻量关注执行
|
|
299
|
+
|
|
300
|
+
`observation-loop.ts` 维护私有消息缓冲,继承来源工具作用域,不启动 Agent turn 或新会话。按需暴露检查工具和指定记录工具,保留 DSH guard/审批。`heartbeat.ts` 逐条调用并立即持久化结果,单条失败不终止后续关注。每条独享 12 轮检查和一轮无工具收尾;模型请求 180 秒、工具执行 60 秒分别计时,无整批总时限和共享调用次数限制。重试退避从执行结束计算,已完成关注按自身 nextCheckAt 调度。结束必须给出 completed/blocked,失败不写成无变化。来源知识挂载沿用原 session,无需临时知识映射。记录内容不回写依据。
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 1.14.0
|
|
2
|
+
|
|
3
|
+
## 持续关注与记录
|
|
4
|
+
|
|
5
|
+
- 轻量后台检查继承来源会话权限,不创建临时 Agent 对话;检查与无工具收尾分阶段计时,保留逐轮耗时及脱敏诊断。
|
|
6
|
+
- 核验结果可通过检查点持久保存,记录同步单独执行。同步失败15分钟后重试,不重新调查来源或发送变化通知。
|
|
7
|
+
- 只写用户手动指定的现存笔记或文件,不通过知识文档引用推导写入目标。同步先读再合并,笔记使用版本校验;更换目标取消旧目标的待同步数据。
|
|
8
|
+
- 支持编辑关注说明、依据和记录位置,查看归档配置及确认删除。删除关注不删除关联文档。
|
|
9
|
+
- 手动检查异步返回,界面查询执行状态,避免长请求等待。
|
|
10
|
+
|
|
11
|
+
## 记忆与稳定性
|
|
12
|
+
|
|
13
|
+
- 同一联系人任务连续失败3次后,退避期间允许后续任务整理;失败任务仍保留并重试。
|
|
14
|
+
- 旧轮次补处理不覆盖较新的同主题记忆或日摘要。
|
|
15
|
+
- 展示完整积压数量、上次失败原因;模型错误保留脱敏错误码与HTTP状态。
|
|
16
|
+
- 会话恢复仅在确认持久化会话不存在时回退创建,避免重复会话错误。
|
|
17
|
+
|
|
18
|
+
## 升级说明
|
|
19
|
+
|
|
20
|
+
保留现有配置和数据,SQLite字段增量迁移。升级前建议备份伙伴数据。
|
|
21
|
+
旧轮次若没有完整结果快照,不能仅凭变化摘要补建文档,需重新核验。
|
|
22
|
+
同步写入仍需要有效权限和模型正常返回;外部写入与本地队列不是跨系统原子事务,不承诺严格一次执行。
|
|
23
|
+
|
|
24
|
+
验证:341项自动化测试通过;部署已确认独立笔记同步成功。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 1.14.1
|
|
2
|
+
|
|
3
|
+
正式版,适配 DSH **0.1.2-rc.1**;需要 Node.js 22.19+ 或 24+。
|
|
4
|
+
|
|
5
|
+
- 心跳逐条执行关注,每条独享 12 轮检查与一轮无工具收尾,移除整批 180 秒总时限和共享 24 次工具调用额度。
|
|
6
|
+
- 单次模型请求限时 180 秒,单次工具限时 60 秒;工具自身可设置更短时限,原有权限与审批保留。
|
|
7
|
+
- 每条完成立即保存,单条失败不阻止后续关注;重试仅检查仍到期的关注,已保存通知支持补发。
|
|
8
|
+
- 重试退避从执行结束计算,避免长检查结束后立即再次触发。
|
|
9
|
+
- 更新诊断与架构说明;不修改界面、用户配置或知识内容。
|
|
10
|
+
|
|
11
|
+
验证:342 项自动化测试通过。保留现有数据,无新增迁移。
|
package/lib/agent-runtime.d.ts
CHANGED
|
@@ -40,6 +40,18 @@ export interface HeartbeatToolTrace {
|
|
|
40
40
|
status: 'completed' | 'failed';
|
|
41
41
|
}
|
|
42
42
|
export interface HeartbeatExecution {
|
|
43
|
+
recording?: Array<{
|
|
44
|
+
concernId: string;
|
|
45
|
+
state: 'synced' | 'pending';
|
|
46
|
+
}>;
|
|
47
|
+
rounds?: Array<{
|
|
48
|
+
round: number;
|
|
49
|
+
phase: 'check' | 'final';
|
|
50
|
+
modelMs: number;
|
|
51
|
+
outputChars: number;
|
|
52
|
+
}>;
|
|
53
|
+
blocked?: string[];
|
|
54
|
+
blockedReasons?: Record<string, string>;
|
|
43
55
|
concerns: PartnerConcern[];
|
|
44
56
|
candidates: ConcernObservationCandidate[];
|
|
45
57
|
observations?: ConcernObservation[];
|
|
@@ -73,6 +85,7 @@ export declare class PartnerAgentRuntime {
|
|
|
73
85
|
private readonly preparedTurns;
|
|
74
86
|
private readonly observationJobs;
|
|
75
87
|
private readonly agentSetupJobs;
|
|
88
|
+
private readonly sessionRecoveryJobs;
|
|
76
89
|
private closed;
|
|
77
90
|
private questionAnswerer;
|
|
78
91
|
constructor(ctx: RuntimeContext, store: PartnerStore, defaultCwd: string, memory?: PartnerMemoryStore | undefined, reflection?: MemoryReflectionService | undefined, concerns?: PartnerConcernStore | undefined, composer?: PartnerAgentComposer | undefined);
|
|
@@ -99,14 +112,22 @@ export declare class PartnerAgentRuntime {
|
|
|
99
112
|
isArchived(route: ChannelSession): boolean;
|
|
100
113
|
renewSession(routeId: string): Promise<ChannelSession>;
|
|
101
114
|
heartbeat(companion: Companion, route: ChannelSession, concerns: PartnerConcern[]): Promise<HeartbeatExecution>;
|
|
102
|
-
persistKnowledgeObservations(companion: Companion, route: ChannelSession, concerns: PartnerConcern[], observations: ConcernObservation[]): Promise<void>;
|
|
103
115
|
concernSources(companion: Companion, query?: string): Promise<ConcernSource[]>;
|
|
116
|
+
recordingSources(companion: Companion, query?: string): Promise<({
|
|
117
|
+
kind: "file";
|
|
118
|
+
locator: string;
|
|
119
|
+
label: string;
|
|
120
|
+
} | {
|
|
121
|
+
kind: "note";
|
|
122
|
+
locator: string;
|
|
123
|
+
label: string;
|
|
124
|
+
})[]>;
|
|
125
|
+
validateRecordingTarget(companion: Companion, raw: unknown): Promise<PartnerConcern['recordTarget']>;
|
|
104
126
|
private runHeartbeatWhenIdle;
|
|
105
127
|
observeSessionEvent(session: Session, event: SessionEvent): Promise<void>;
|
|
106
128
|
private processSessionEvent;
|
|
107
129
|
recordConcernCreatedNotice(companion: Companion, scopeId: string, concerns: PartnerConcern[]): Promise<boolean>;
|
|
108
130
|
private driveScopedHeartbeat;
|
|
109
|
-
private driveEphemeralHeartbeat;
|
|
110
131
|
recordHeartbeatActivity(companion: Companion, route: ChannelSession, execution: HeartbeatExecution, outcome: HeartbeatOutcome, deliveryError?: string): Promise<void>;
|
|
111
132
|
resetCompanion(companionId: string): Promise<void>;
|
|
112
133
|
removeCompanionWorkspace(companionId: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-runtime.d.ts","sourceRoot":"","sources":["../src/agent-runtime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAIlE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,KAAK,EAAa,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEzD,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"agent-runtime.d.ts","sourceRoot":"","sources":["../src/agent-runtime.ts"],"names":[],"mappings":"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"}
|
package/lib/agent-runtime.js
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { mkdir, realpath, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
5
|
+
import { scopeOf, scopeParentOf } from '@deepseek-ai/dsh-scope';
|
|
6
6
|
import { SessionConfigurationIndex } from './companions/session-configuration.js';
|
|
7
|
+
import { isMissingSession, SessionRecoveryJobs } from './companions/session-recovery.js';
|
|
7
8
|
import { removeOwnedWorkspace } from './companions/workspace-cleanup.js';
|
|
8
9
|
import { assertOwnedDirectory, removeOwnedDirectory } from './companions/directory-cleanup.js';
|
|
9
|
-
import { concernObservationPrompt } from './autonomy.js';
|
|
10
10
|
import { boundedConcernCheckMinutes } from './concern-domain.js';
|
|
11
11
|
import { taskWorkContext } from './tasks/context.js';
|
|
12
12
|
import { PARTNER_MEDIA_MAX_BYTES, safeMediaName } from './channel-message.js';
|
|
13
13
|
import { listConcernFileSources } from './concern-sources.js';
|
|
14
14
|
import { CONCERN_CREATED_NOTICE, renderConcernCreatedNotice } from './concern-notification.js';
|
|
15
|
-
import {
|
|
15
|
+
import { executeObservationLoop } from './observation-loop.js';
|
|
16
16
|
import { assistantTextAfter, renderPartnerPersona, renderToolProtocol, resolvePartnerAgentOptions as resolveAgentOptions } from './execution/agent-support.js';
|
|
17
17
|
import { channelReplyPartsAfter } from './channels/delivery-policy.js';
|
|
18
18
|
import { prepareChannelReply } from './channels/outbound-media.js';
|
|
19
19
|
export { extractOutboundAttachments } from './channels/outbound-media.js';
|
|
20
20
|
export { renderToolProtocol, resolvePartnerAgentOptions as resolveAgentOptions } from './execution/agent-support.js';
|
|
21
|
-
const HEARTBEAT_TIMEOUT_MS = 115_000;
|
|
22
|
-
const HEARTBEAT_DISCOVERY_BUDGET_MS = 72_000;
|
|
23
|
-
const HEARTBEAT_TOOL_TIMEOUT_MS = 18_000;
|
|
24
|
-
const HEARTBEAT_WEB_TOOL_TIMEOUT_MS = 30_000;
|
|
25
|
-
const HEARTBEAT_TOOL_RESULT_LIMIT = 5_000;
|
|
26
|
-
const HEARTBEAT_KNOWLEDGE_RESULT_LIMIT = 8_000;
|
|
27
|
-
const HEARTBEAT_WEB_FETCH_RESULT_LIMIT = 40_000;
|
|
28
|
-
const HEARTBEAT_WEB_SOURCE_RESULT_LIMIT = 125_000;
|
|
29
|
-
const HEARTBEAT_AUDIT_INPUT_LIMIT = 320;
|
|
30
|
-
const HEARTBEAT_AUDIT_OUTPUT_LIMIT = 520;
|
|
31
21
|
const PARTNER_TASK_TIMEOUT_MS = 30 * 60_000;
|
|
32
22
|
const HEARTBEAT_READ_ONLY_TOOLS = new Set([
|
|
33
23
|
'knowledge_base_search',
|
|
@@ -41,8 +31,6 @@ const HEARTBEAT_READ_ONLY_TOOLS = new Set([
|
|
|
41
31
|
'read',
|
|
42
32
|
]);
|
|
43
33
|
const HEARTBEAT_FILE_UPDATE_TOOLS = new Set(['write', 'edit', 'str_replace_editor']);
|
|
44
|
-
const HEARTBEAT_TOOLS = new Set([...HEARTBEAT_READ_ONLY_TOOLS, ...HEARTBEAT_FILE_UPDATE_TOOLS]);
|
|
45
|
-
HEARTBEAT_TOOLS.add(HEARTBEAT_LOCAL_COMMAND);
|
|
46
34
|
export class PartnerAgentRuntime {
|
|
47
35
|
ctx;
|
|
48
36
|
store;
|
|
@@ -66,6 +54,7 @@ export class PartnerAgentRuntime {
|
|
|
66
54
|
preparedTurns = new WeakMap();
|
|
67
55
|
observationJobs = new Map();
|
|
68
56
|
agentSetupJobs = new Map();
|
|
57
|
+
sessionRecoveryJobs = new SessionRecoveryJobs();
|
|
69
58
|
closed = false;
|
|
70
59
|
questionAnswerer;
|
|
71
60
|
constructor(ctx, store, defaultCwd, memory, reflection, concerns, composer) {
|
|
@@ -328,44 +317,6 @@ export class PartnerAgentRuntime {
|
|
|
328
317
|
this.heartbeatQueues.delete(key);
|
|
329
318
|
}
|
|
330
319
|
}
|
|
331
|
-
async persistKnowledgeObservations(companion, route, concerns, observations) {
|
|
332
|
-
const service = this.ctx.get('dshKnowledgeTracking');
|
|
333
|
-
if (service === undefined || observations.length === 0)
|
|
334
|
-
return;
|
|
335
|
-
const agent = await this.ensureAgent(companion, route);
|
|
336
|
-
const byId = new Map(concerns.map(item => [item.id, item]));
|
|
337
|
-
const controller = new AbortController();
|
|
338
|
-
const timeout = setTimeout(() => controller.abort(new Error('知识库观察记录超时')), 30_000);
|
|
339
|
-
timeout.unref?.();
|
|
340
|
-
try {
|
|
341
|
-
for (const observation of observations) {
|
|
342
|
-
if (observation.decision === 'drop')
|
|
343
|
-
continue;
|
|
344
|
-
const concern = byId.get(observation.concernId);
|
|
345
|
-
if (concern === undefined)
|
|
346
|
-
continue;
|
|
347
|
-
const references = concern.resources.filter(item => item.kind === 'knowledge').map(item => item.locator).slice(0, 4);
|
|
348
|
-
if (references.length === 0 && concern.watchKind !== 'knowledge')
|
|
349
|
-
continue;
|
|
350
|
-
for (const reference of references.length > 0 ? references : [undefined]) {
|
|
351
|
-
await service.record(agent, {
|
|
352
|
-
id: observation.id,
|
|
353
|
-
subject: concern.subject,
|
|
354
|
-
event: observation.event,
|
|
355
|
-
evidence: observation.evidence,
|
|
356
|
-
source: observation.source,
|
|
357
|
-
...(reference === undefined ? {} : { reference }),
|
|
358
|
-
at: observation.createdAt,
|
|
359
|
-
}, controller.signal).catch(error => {
|
|
360
|
-
this.ctx.logger.warn(`dsh-partner: knowledge observation fell back to local storage: ${errorMessage(error)}`);
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
finally {
|
|
366
|
-
clearTimeout(timeout);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
320
|
async concernSources(companion, query = '') {
|
|
370
321
|
const routes = this.store.snapshot().sessions
|
|
371
322
|
.filter(item => item.companionId === companion.id)
|
|
@@ -390,6 +341,24 @@ export class PartnerAgentRuntime {
|
|
|
390
341
|
clearTimeout(timeout);
|
|
391
342
|
}
|
|
392
343
|
}
|
|
344
|
+
async recordingSources(companion, query = '') {
|
|
345
|
+
const route = this.store.snapshot().sessions.filter(item => item.companionId === companion.id).sort((a, b) => b.lastMessageAt - a.lastMessageAt)[0];
|
|
346
|
+
const files = await listConcernFileSources(route?.cwd ?? partnerCwd(this.defaultCwd, companion.id), query, 24);
|
|
347
|
+
const bridge = this.ctx.get('dshKnowledgeNoteRecording');
|
|
348
|
+
const notes = bridge?.version === 1 ? await bridge.list(query, AbortSignal.timeout(8000)) : [];
|
|
349
|
+
return [...files.map(file => ({ kind: 'file', locator: file.label, label: file.label })), ...notes.map(note => ({ kind: 'note', locator: note.id, label: note.name }))];
|
|
350
|
+
}
|
|
351
|
+
async validateRecordingTarget(companion, raw) {
|
|
352
|
+
if (raw === undefined || raw === null)
|
|
353
|
+
return undefined;
|
|
354
|
+
if (!isRecord(raw) || typeof raw.locator !== 'string' || typeof raw.label !== 'string')
|
|
355
|
+
throw new Error('记录位置无效,请重新选择');
|
|
356
|
+
const choices = await this.recordingSources(companion, raw.kind === 'file' ? raw.locator : raw.label);
|
|
357
|
+
const selected = choices.find(item => item.kind === raw.kind && item.locator === raw.locator);
|
|
358
|
+
if (!selected)
|
|
359
|
+
throw new Error('记录位置已不存在或不可写,请重新选择');
|
|
360
|
+
return selected;
|
|
361
|
+
}
|
|
393
362
|
async runHeartbeatWhenIdle(key, companion, route, concerns) {
|
|
394
363
|
for (;;) {
|
|
395
364
|
await this.queues.get(key)?.catch(() => { });
|
|
@@ -486,38 +455,9 @@ export class PartnerAgentRuntime {
|
|
|
486
455
|
return true;
|
|
487
456
|
}
|
|
488
457
|
async driveScopedHeartbeat(conversation, companion, concerns) {
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
finally {
|
|
494
|
-
await scope.dispose();
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
async driveEphemeralHeartbeat(agent, companion, concerns) {
|
|
498
|
-
const controller = new AbortController();
|
|
499
|
-
const timeout = setTimeout(() => controller.abort(new Error('伙伴心跳超过安全时限')), HEARTBEAT_TIMEOUT_MS);
|
|
500
|
-
timeout.unref?.();
|
|
501
|
-
try {
|
|
502
|
-
const execution = await runHeartbeatInference(this.ctx, agent, companion, concerns, controller.signal);
|
|
503
|
-
return controller.signal.aborted
|
|
504
|
-
? { ...execution, error: '伙伴心跳超过安全时限,已收束并终止' }
|
|
505
|
-
: execution;
|
|
506
|
-
}
|
|
507
|
-
catch (error) {
|
|
508
|
-
const now = Date.now();
|
|
509
|
-
return {
|
|
510
|
-
concerns,
|
|
511
|
-
candidates: [],
|
|
512
|
-
startedAt: now,
|
|
513
|
-
completedAt: now,
|
|
514
|
-
tools: [],
|
|
515
|
-
error: controller.signal.aborted ? '伙伴心跳超过安全时限,已收束并终止' : errorMessage(error),
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
finally {
|
|
519
|
-
clearTimeout(timeout);
|
|
520
|
-
}
|
|
458
|
+
const access = await resolveHeartbeatFileAccess(conversation.session.header.cwd, concerns);
|
|
459
|
+
return executeObservationLoop({ ctx: this.ctx, conversation, companion, concerns, guard: (name, args) => heartbeatToolDenial(name, args, access), parse: parseConcernObservations, checkpoint: async (item, snapshot) => { if (!this.concerns)
|
|
460
|
+
throw new Error('关注存储不可用'); await this.concerns.checkpointRecording(item, snapshot); } });
|
|
521
461
|
}
|
|
522
462
|
async recordHeartbeatActivity(companion, route, execution, outcome, deliveryError) {
|
|
523
463
|
const conversation = await this.ensureAgent(companion, route);
|
|
@@ -734,7 +674,7 @@ export class PartnerAgentRuntime {
|
|
|
734
674
|
const id = companion.id;
|
|
735
675
|
this.agentSetupJobs.set(id, (this.agentSetupJobs.get(id) ?? 0) + 1);
|
|
736
676
|
try {
|
|
737
|
-
return await this.ensureAgentReady(companion, route);
|
|
677
|
+
return await this.sessionRecoveryJobs.run(route.sessionId, () => this.ensureAgentReady(companion, route));
|
|
738
678
|
}
|
|
739
679
|
finally {
|
|
740
680
|
const left = (this.agentSetupJobs.get(id) ?? 1) - 1;
|
|
@@ -779,7 +719,10 @@ export class PartnerAgentRuntime {
|
|
|
779
719
|
setup,
|
|
780
720
|
});
|
|
781
721
|
}
|
|
782
|
-
catch {
|
|
722
|
+
catch (error) {
|
|
723
|
+
// Never turn a preset/loading/permission failure into a duplicate create.
|
|
724
|
+
if (!isMissingSession(error, route.sessionId))
|
|
725
|
+
throw error;
|
|
783
726
|
handle = await this.ctx.agents.create({
|
|
784
727
|
sessionId: route.sessionId,
|
|
785
728
|
meta: {
|
|
@@ -960,123 +903,13 @@ export function renewedSession(previous, now = Date.now()) {
|
|
|
960
903
|
export function partnerCwd(root, companionId) {
|
|
961
904
|
return join(root, 'partners', companionId);
|
|
962
905
|
}
|
|
963
|
-
async function createHeartbeatScope(ctx, conversation, concerns) {
|
|
964
|
-
const parent = scopeOf(conversation.ctx);
|
|
965
|
-
if (parent === undefined)
|
|
966
|
-
throw new Error('伙伴心跳无法解析当前 Agent 作用域');
|
|
967
|
-
const availableTools = [...HEARTBEAT_TOOLS].filter(name => ctx.tools.get(name, conversation) !== undefined);
|
|
968
|
-
const policy = heartbeatToolPolicy(concerns, availableTools);
|
|
969
|
-
const allowedTools = [...policy.allowed];
|
|
970
|
-
const cwd = conversation.session.header.cwd;
|
|
971
|
-
if (!cwd)
|
|
972
|
-
throw new Error('伙伴心跳缺少当前伙伴工作目录');
|
|
973
|
-
const fileAccess = await resolveHeartbeatFileAccess(cwd, concerns);
|
|
974
|
-
const agent = {};
|
|
975
|
-
const scope = createScope(ctx, agent, { parent });
|
|
976
|
-
const agentCtx = scope.ctx.extend({ agent });
|
|
977
|
-
Object.assign(agent, {
|
|
978
|
-
id: conversation.id,
|
|
979
|
-
options: conversation.options,
|
|
980
|
-
session: conversation.session,
|
|
981
|
-
inbox: conversation.inbox,
|
|
982
|
-
status: 'idle',
|
|
983
|
-
ctx: agentCtx,
|
|
984
|
-
cancel: () => { },
|
|
985
|
-
whenIdle: async () => { },
|
|
986
|
-
runMaintenance: async (job) => job(new AbortController().signal),
|
|
987
|
-
send: () => { throw new Error('心跳作用域不接受会话消息'); },
|
|
988
|
-
followup: () => { throw new Error('心跳作用域不接受会话消息'); },
|
|
989
|
-
steer: () => { throw new Error('心跳作用域不接受会话消息'); },
|
|
990
|
-
inject: () => { throw new Error('心跳作用域不接受会话消息'); },
|
|
991
|
-
});
|
|
992
|
-
agentCtx.tools.presentAs('native');
|
|
993
|
-
agentCtx.tools.register(heartbeatLocalCommandTool(cwd));
|
|
994
|
-
if (allowedTools.length > 0)
|
|
995
|
-
agentCtx.tools.restrict({ allow: allowedTools });
|
|
996
|
-
agentCtx.tools.guard(exec => {
|
|
997
|
-
if (exec.name !== HEARTBEAT_LOCAL_COMMAND && !policy.allowed.has(exec.name))
|
|
998
|
-
return `当前挂念的来源策略不允许调用 ${exec.name}`;
|
|
999
|
-
return heartbeatToolDenial(exec.name, exec.arguments, fileAccess);
|
|
1000
|
-
});
|
|
1001
|
-
return { agent, dispose: () => scope.dispose() };
|
|
1002
|
-
}
|
|
1003
906
|
export function heartbeatToolPolicy(concerns, available) {
|
|
1004
907
|
const installed = new Set(available);
|
|
1005
|
-
const hasLinkedFile = concerns.some(item => item.
|
|
908
|
+
const hasLinkedFile = concerns.some(item => item.recordTarget?.kind === 'file');
|
|
1006
909
|
return {
|
|
1007
910
|
allowed: new Set([...installed].filter(name => HEARTBEAT_READ_ONLY_TOOLS.has(name) || (hasLinkedFile && HEARTBEAT_FILE_UPDATE_TOOLS.has(name)))),
|
|
1008
911
|
};
|
|
1009
912
|
}
|
|
1010
|
-
async function runHeartbeatInference(ctx, agent, companion, concerns, signal) {
|
|
1011
|
-
const startedAt = Date.now();
|
|
1012
|
-
const traces = [];
|
|
1013
|
-
const discoveryDeadline = startedAt + HEARTBEAT_DISCOVERY_BUDGET_MS;
|
|
1014
|
-
try {
|
|
1015
|
-
const selection = resolveAgentOptions(ctx.agentDefaultModel, companion);
|
|
1016
|
-
if (!selection.provider || !selection.model)
|
|
1017
|
-
throw new Error('伙伴心跳没有可用的模型路由');
|
|
1018
|
-
const tools = ctx.tools.schemas(agent).filter(tool => HEARTBEAT_TOOLS.has(tool.name));
|
|
1019
|
-
if (tools.length === 0)
|
|
1020
|
-
throw new Error('伙伴心跳没有可用的只读发现工具');
|
|
1021
|
-
const messages = [createUserMessage({
|
|
1022
|
-
content: [{ type: 'text', text: concernObservationPrompt(concerns, agent.session.header.cwd) }],
|
|
1023
|
-
source: { kind: 'plugin', plugin: '@lemoncat7/dsh-partner', form: 'notice', summary: '伙伴观察挂念变化' },
|
|
1024
|
-
})];
|
|
1025
|
-
for (;;) {
|
|
1026
|
-
const discoveryOpen = Date.now() < discoveryDeadline;
|
|
1027
|
-
const prepared = await ctx.llm.prepareCall(selection, signal);
|
|
1028
|
-
const assembler = new BlockAssembler();
|
|
1029
|
-
for await (const chunk of prepared.stream({
|
|
1030
|
-
...prepared.config,
|
|
1031
|
-
messages,
|
|
1032
|
-
system: renderPartnerPersona(companion, 'heartbeat'),
|
|
1033
|
-
tools: discoveryOpen ? tools : [],
|
|
1034
|
-
signal,
|
|
1035
|
-
}))
|
|
1036
|
-
assembler.push(chunk);
|
|
1037
|
-
const finish = assembler.finish;
|
|
1038
|
-
if (finish.kind === 'error' || finish.kind === 'aborted') {
|
|
1039
|
-
throw new Error(`伙伴心跳模型调用失败:${finish.failure.message}`);
|
|
1040
|
-
}
|
|
1041
|
-
const response = assembler.message({ kind: 'model', provider: prepared.config.provider, model: prepared.config.model });
|
|
1042
|
-
messages.push(response);
|
|
1043
|
-
const pending = response.content.filter((block) => block.type === 'tool-call');
|
|
1044
|
-
if (pending.length === 0) {
|
|
1045
|
-
const output = textContent(response.content);
|
|
1046
|
-
return {
|
|
1047
|
-
concerns,
|
|
1048
|
-
candidates: parseConcernObservations(output, new Set(concerns.map(item => item.id))),
|
|
1049
|
-
startedAt,
|
|
1050
|
-
completedAt: Date.now(),
|
|
1051
|
-
output,
|
|
1052
|
-
tools: traces,
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
for (const call of pending) {
|
|
1056
|
-
const argumentsValue = boundHeartbeatToolArguments(call.name, parseHeartbeatToolArguments(call));
|
|
1057
|
-
const toolStartedAt = Date.now();
|
|
1058
|
-
const remainingDiscoveryMs = discoveryDeadline - toolStartedAt;
|
|
1059
|
-
const result = remainingDiscoveryMs <= 0
|
|
1060
|
-
? { isError: true, content: [{ type: 'text', text: '本轮发现阶段的时间预算已用完,请根据已有结果立即输出最终 Observation JSON。' }] }
|
|
1061
|
-
: await executeHeartbeatTool(ctx, agent, call, argumentsValue, signal, remainingDiscoveryMs);
|
|
1062
|
-
const toolCompletedAt = Date.now();
|
|
1063
|
-
const visibleContent = heartbeatToolContent(call.name, result.content);
|
|
1064
|
-
traces.push({
|
|
1065
|
-
name: call.name,
|
|
1066
|
-
input: auditValue(argumentsValue, HEARTBEAT_AUDIT_INPUT_LIMIT),
|
|
1067
|
-
output: auditText(textContent(visibleContent) || '工具返回了非文本结果', HEARTBEAT_AUDIT_OUTPUT_LIMIT),
|
|
1068
|
-
startedAt: toolStartedAt,
|
|
1069
|
-
completedAt: toolCompletedAt,
|
|
1070
|
-
status: result.isError ? 'failed' : 'completed',
|
|
1071
|
-
});
|
|
1072
|
-
messages.push(createToolResultMessage({ callId: call.id, content: visibleContent, isError: result.isError }));
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
catch (error) {
|
|
1077
|
-
return { concerns, candidates: [], startedAt, completedAt: Date.now(), tools: traces, error: errorMessage(error) };
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
913
|
export function parseConcernObservations(raw, allowedIds) {
|
|
1081
914
|
const match = raw.trim().match(/\{[\s\S]*\}/);
|
|
1082
915
|
if (!match)
|
|
@@ -1115,18 +948,10 @@ export function parseConcernObservations(raw, allowedIds) {
|
|
|
1115
948
|
function boundedScore(value) {
|
|
1116
949
|
return typeof value === 'number' && Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : .5;
|
|
1117
950
|
}
|
|
1118
|
-
function parseHeartbeatToolArguments(call) {
|
|
1119
|
-
try {
|
|
1120
|
-
return JSON.parse(call.arguments);
|
|
1121
|
-
}
|
|
1122
|
-
catch {
|
|
1123
|
-
return { __invalidArguments: call.arguments };
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
951
|
async function resolveHeartbeatFileAccess(cwd, concerns) {
|
|
1127
952
|
const root = await realpath(cwd);
|
|
1128
953
|
const writable = new Set();
|
|
1129
|
-
const locators = concerns.flatMap(item => item.
|
|
954
|
+
const locators = concerns.flatMap(item => item.recordTarget?.kind === 'file' ? [item.recordTarget.locator] : []);
|
|
1130
955
|
for (const locator of new Set(locators)) {
|
|
1131
956
|
const lexical = resolve(root, locator);
|
|
1132
957
|
if (!pathInside(root, lexical) || isPartnerMemoryPath(relative(root, lexical)))
|
|
@@ -1144,6 +969,8 @@ async function resolveHeartbeatFileAccess(cwd, concerns) {
|
|
|
1144
969
|
return { root, writable };
|
|
1145
970
|
}
|
|
1146
971
|
export function heartbeatToolDenial(name, argumentsValue, access) {
|
|
972
|
+
if (['knowledge_note_references', 'knowledge_note_read', 'knowledge_note_update'].includes(name))
|
|
973
|
+
return '关注不处理依据中的笔记引用;记录仅使用手动配置的 recordTarget 和专用记录工具。';
|
|
1147
974
|
if (!isRecord(argumentsValue))
|
|
1148
975
|
return undefined;
|
|
1149
976
|
const isRead = ['glob', 'grep', 'read'].includes(name);
|
|
@@ -1165,70 +992,10 @@ export function heartbeatToolDenial(name, argumentsValue, access) {
|
|
|
1165
992
|
}
|
|
1166
993
|
return undefined;
|
|
1167
994
|
}
|
|
1168
|
-
async function executeHeartbeatTool(ctx, agent, call, argumentsValue, signal, remainingDiscoveryMs) {
|
|
1169
|
-
const configuredTimeoutMs = call.name === 'web_fetch' || call.name === 'web_source' ? HEARTBEAT_WEB_TOOL_TIMEOUT_MS : HEARTBEAT_TOOL_TIMEOUT_MS;
|
|
1170
|
-
const timeoutMs = Math.max(1, Math.min(configuredTimeoutMs, remainingDiscoveryMs));
|
|
1171
|
-
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
1172
|
-
const toolSignal = AbortSignal.any([signal, timeoutSignal]);
|
|
1173
|
-
try {
|
|
1174
|
-
return await ctx.tools.execute({ callId: call.id, name: call.name, arguments: argumentsValue, agent, signal: toolSignal });
|
|
1175
|
-
}
|
|
1176
|
-
catch (error) {
|
|
1177
|
-
const message = timeoutSignal.aborted && !signal.aborted
|
|
1178
|
-
? `工具 ${call.name} 超过 ${timeoutMs / 1_000} 秒,已跳过;请按既有规则判断来源不可用,或根据已有结果收束。`
|
|
1179
|
-
: `工具 ${call.name} 调用失败:${errorMessage(error)}`;
|
|
1180
|
-
return { isError: true, content: [{ type: 'text', text: message }] };
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
995
|
function pathInside(root, target) {
|
|
1184
996
|
const value = relative(root, target);
|
|
1185
997
|
return value === '' || (value !== '..' && !value.startsWith(`..${sep}`) && !isAbsolute(value));
|
|
1186
998
|
}
|
|
1187
|
-
function boundHeartbeatToolArguments(name, value) {
|
|
1188
|
-
if (!isRecord(value))
|
|
1189
|
-
return value;
|
|
1190
|
-
const bounded = { ...value };
|
|
1191
|
-
if (name === 'knowledge_base_search' || name === 'knowledge_search')
|
|
1192
|
-
bounded.limit = boundedInteger(value.limit, 4);
|
|
1193
|
-
if (name === 'knowledge_read')
|
|
1194
|
-
bounded.maxChars = boundedInteger(value.maxChars, HEARTBEAT_KNOWLEDGE_RESULT_LIMIT);
|
|
1195
|
-
if (name === 'web_source')
|
|
1196
|
-
bounded.max_chars = boundedInteger(value.max_chars, 120_000);
|
|
1197
|
-
if (name === 'read')
|
|
1198
|
-
bounded.limit = boundedInteger(value.limit, 80);
|
|
1199
|
-
return bounded;
|
|
1200
|
-
}
|
|
1201
|
-
function heartbeatToolContent(name, content) {
|
|
1202
|
-
let remaining = heartbeatToolResultLimit(name);
|
|
1203
|
-
const bounded = [];
|
|
1204
|
-
for (const block of content) {
|
|
1205
|
-
if (remaining <= 0)
|
|
1206
|
-
break;
|
|
1207
|
-
if (block.type !== 'text') {
|
|
1208
|
-
bounded.push(block);
|
|
1209
|
-
continue;
|
|
1210
|
-
}
|
|
1211
|
-
const sanitized = name === 'glob'
|
|
1212
|
-
? block.text.split('\n').filter(line => !isPartnerMemoryPath(line.trim())).join('\n')
|
|
1213
|
-
: block.text;
|
|
1214
|
-
const text = sanitized.length <= remaining ? sanitized : `${sanitized.slice(0, Math.max(0, remaining - 1))}…`;
|
|
1215
|
-
if (text)
|
|
1216
|
-
bounded.push({ ...block, text });
|
|
1217
|
-
remaining -= text.length;
|
|
1218
|
-
}
|
|
1219
|
-
if (bounded.length === 0 && name === 'glob')
|
|
1220
|
-
return [{ type: 'text', text: '未发现可供心跳检查的普通工作文件;伙伴记忆与会话归档已从结果中排除。' }];
|
|
1221
|
-
return bounded;
|
|
1222
|
-
}
|
|
1223
|
-
function heartbeatToolResultLimit(name) {
|
|
1224
|
-
if (name === 'knowledge_read')
|
|
1225
|
-
return HEARTBEAT_KNOWLEDGE_RESULT_LIMIT;
|
|
1226
|
-
if (name === 'web_fetch')
|
|
1227
|
-
return HEARTBEAT_WEB_FETCH_RESULT_LIMIT;
|
|
1228
|
-
if (name === 'web_source')
|
|
1229
|
-
return HEARTBEAT_WEB_SOURCE_RESULT_LIMIT;
|
|
1230
|
-
return HEARTBEAT_TOOL_RESULT_LIMIT;
|
|
1231
|
-
}
|
|
1232
999
|
function isPartnerMemoryPath(value) {
|
|
1233
1000
|
const normalized = value.replaceAll('\\', '/').replace(/^\.\//, '/');
|
|
1234
1001
|
return /(?:^|\/)(?:memory(?:\/|-backup(?:-|\/)|$)|concerns(?:\/|$))/i.test(normalized);
|
|
@@ -1240,45 +1007,41 @@ function isRecord(value) {
|
|
|
1240
1007
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1241
1008
|
}
|
|
1242
1009
|
export function renderHeartbeatActivity(execution, outcome, deliveryError) {
|
|
1243
|
-
const status = outcome === 'notified' ? '已通过微信提醒' : outcome === 'quiet' ? '
|
|
1010
|
+
const status = execution.blocked?.length ? '检查受阻' : outcome === 'notified' ? '已通过微信提醒' : outcome === 'quiet' ? '检查完成,无需主动提醒' : '执行失败';
|
|
1244
1011
|
const lines = [
|
|
1245
1012
|
`状态:${status}`,
|
|
1246
1013
|
`本轮挂念:${execution.concerns.map(item => item.subject).join(';')}`,
|
|
1247
1014
|
`用时:${formatDuration(execution.completedAt - execution.startedAt)}`,
|
|
1248
1015
|
`工具:${execution.tools.length > 0 ? `${execution.tools.length} 次调用` : '未调用'}`,
|
|
1016
|
+
...(execution.recording ?? []).map(item => `记录同步:${execution.concerns.find(concern => concern.id === item.concernId)?.subject ?? item.concernId} · ${item.state === 'synced' ? '已写入手动指定位置' : '待同步(本轮未完成写入,下轮继续处理)'}`),
|
|
1017
|
+
...(execution.rounds?.length ? [`模型轮次:${execution.rounds.map(item => `${item.round}${item.phase === 'final' ? '(收尾)' : ''} / ${(item.modelMs / 1000).toFixed(1)}秒 / ${item.outputChars}字`).join(';')}`] : []),
|
|
1249
1018
|
];
|
|
1250
1019
|
execution.tools.forEach((trace, index) => {
|
|
1251
1020
|
lines.push('', `${index + 1}. ${trace.name} · ${trace.status === 'completed' ? '完成' : '失败'} · ${formatDuration(trace.completedAt - trace.startedAt)}`, `输入:${trace.input}`, `结果:${trace.output}`);
|
|
1252
1021
|
});
|
|
1022
|
+
for (const id of execution.blocked ?? [])
|
|
1023
|
+
lines.push(`受阻事项:${execution.concerns.find(item => item.id === id)?.subject ?? id};${execution.blockedReasons?.[id] ?? '目标数据尚未核实'}`);
|
|
1253
1024
|
const changed = execution.observations ?? [];
|
|
1254
1025
|
const conclusion = execution.error || deliveryError
|
|
1255
1026
|
? `失败原因:${deliveryError ?? execution.error}`
|
|
1256
1027
|
: changed.length > 0
|
|
1257
1028
|
? `新变化:\n${changed.map(item => `- ${item.event}(${decisionLabel(item.decision)} · ${Math.round(item.interruptScore * 100)})`).join('\n')}`
|
|
1258
|
-
: '最终结论:本轮没有发现经过校验的新变化。';
|
|
1029
|
+
: execution.concerns.some(item => item.recordingSnapshot) ? '最终结论:本轮仅执行记录同步,未抓取来源或发送变化提醒。' : '最终结论:本轮没有发现经过校验的新变化。';
|
|
1259
1030
|
lines.push('', conclusion);
|
|
1260
1031
|
return lines.join('\n');
|
|
1261
1032
|
}
|
|
1262
1033
|
function decisionLabel(value) {
|
|
1263
1034
|
return value === 'notify' ? '已提醒' : value === 'feed' ? '伙伴动态' : value === 'defer' ? '顺手一提' : value === 'remember' ? '静默记下' : '忽略';
|
|
1264
1035
|
}
|
|
1265
|
-
function auditValue(value, limit) {
|
|
1266
|
-
try {
|
|
1267
|
-
return auditText(JSON.stringify(value), limit);
|
|
1268
|
-
}
|
|
1269
|
-
catch {
|
|
1270
|
-
return auditText(String(value), limit);
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
function auditText(value, limit) {
|
|
1274
|
-
const text = value.trim().replace(/\n{3,}/g, '\n\n');
|
|
1275
|
-
return text.length <= limit ? text : `${text.slice(0, limit - 1)}…`;
|
|
1276
|
-
}
|
|
1277
1036
|
function formatDuration(milliseconds) {
|
|
1278
1037
|
if (milliseconds < 1_000)
|
|
1279
1038
|
return `${Math.max(0, milliseconds)} ms`;
|
|
1280
1039
|
return `${(milliseconds / 1_000).toFixed(milliseconds < 10_000 ? 1 : 0)} 秒`;
|
|
1281
1040
|
}
|
|
1041
|
+
function auditText(value, limit) {
|
|
1042
|
+
const text = value.trim().replace(/\n{3,}/g, '\n\n');
|
|
1043
|
+
return text.length <= limit ? text : `${text.slice(0, limit - 1)}…`;
|
|
1044
|
+
}
|
|
1282
1045
|
function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
|
|
1283
1046
|
function textContent(content) {
|
|
1284
1047
|
return content.filter((block) => block.type === 'text' && typeof block.text === 'string')
|