clearai-dsh 0.1.2 → 0.1.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/CHANGELOG.md +68 -0
- package/README.md +28 -24
- package/README.zh-CN.md +28 -24
- package/bin/clearai.mjs +128 -12
- package/lib/client.js +95 -95
- package/lib/fold.js +151 -66
- package/lib/host.js +20 -18
- package/package.json +9 -3
- package/presets/clearai/agent.cordis.yml +107 -36
- package/presets/clearai/plugins/brain.js +2 -2
- package/presets/clearai/plugins/clearai-kernel.js +552 -284
- package/presets/clearai/plugins/commands.js +199 -0
- package/presets/clearai/plugins/ontology.js +3 -3
- package/presets/clearai/plugins/prompts.js +51 -25
package/lib/fold.js
CHANGED
|
@@ -25,12 +25,12 @@ export const MUTATION_KIND = 'clearai'
|
|
|
25
25
|
* 状态版本:形状一变就 +1。
|
|
26
26
|
* v2 → v3:多了「合并技能目录」与「本会话用了几次技能」两块(技能面)。
|
|
27
27
|
* v3 → v4:多了「运行档」(人在场 / 无人值守)这块。
|
|
28
|
-
* (
|
|
29
|
-
* v4 → v5:多了「项目章程」的读数(占位还有几条 /
|
|
28
|
+
* (更正:它**不是**"可被人切换的事实"而是部署预设的初值——面板那个开关已删。)
|
|
29
|
+
* v4 → v5:多了「项目章程」的读数(占位还有几条 / 章程的当前阶段填没填 / 最后改动)。
|
|
30
30
|
* v5 → v6:多了「续跑窗口」的账(`continuation`):我们**对宿主说过的话**——布防、暂停、收兵、
|
|
31
31
|
* 以及「它不在了,而那不是我们干的」。它以前记在内核的进程内存里,重启或分叉就说不清了。
|
|
32
32
|
* v6 → v7:那条账多一个 `label`:我们最后一次在平台对象上写下的那句身份文本。
|
|
33
|
-
*
|
|
33
|
+
* 它的用途只有一个:**分清「身份变了」与「人改写过了」**——前者我们换窗口,
|
|
34
34
|
* 后者以人为准、一个字都不动。没有它,这两种情形在外部长得一模一样。
|
|
35
35
|
* 投影缓存按版本判定,所以旧缓存会被丢弃、从日志重折一遍——用量是**从日志折出来的**,重折才完整。
|
|
36
36
|
*/
|
|
@@ -63,6 +63,13 @@ export function emptyState() {
|
|
|
63
63
|
forks: [],
|
|
64
64
|
/** 系统按触发派出去的侦察(子角色不是模型自由委派):结论进资料面,这里留一条记录 */
|
|
65
65
|
scouts: [],
|
|
66
|
+
/**
|
|
67
|
+
* **运行时结算通知**(原生):子会话落定时,运行时把它的收尾消息作为一条 user message
|
|
68
|
+
* 投给父会话(`source.kind === 'subagent-settled'`)。它同时是两件事的证据:
|
|
69
|
+
* 模型**读到了**结论(它就是模型上下文里的文本),以及「这条子 run 结束了」这个事实。
|
|
70
|
+
* 折进来之后,账本侧的结算不必再去读子会话日志。
|
|
71
|
+
*/
|
|
72
|
+
notices: [],
|
|
66
73
|
/** 工作区里等人采纳的候选技能(内核扫描后落的事实)与已采纳的记录 */
|
|
67
74
|
brainCandidates: [],
|
|
68
75
|
skillPromotions: [],
|
|
@@ -84,27 +91,29 @@ export function emptyState() {
|
|
|
84
91
|
blocks: {},
|
|
85
92
|
releases: [],
|
|
86
93
|
/**
|
|
87
|
-
* 运行档(人在场 / 无人值守)
|
|
88
|
-
* · `
|
|
89
|
-
* · `
|
|
90
|
-
*
|
|
94
|
+
* 运行档(人在场 / 无人值守)。**它是部署预设的初值,不是可切换的开关**:
|
|
95
|
+
* · 人门动词 `set_autonomy` 已摘除,面板上那个开关已经不存在;
|
|
96
|
+
* · `effective` = 内核随投影下发的那一份(只有内核知道组合配置),**只用于展示**;
|
|
97
|
+
* · `override` = 历史日志里可能留下的人门记录。读取侧保留它只是为了旧会话仍然读得通,
|
|
98
|
+
* 当前**没有任何写入者**——不要据此认为现在还能切换档位。
|
|
99
|
+
* · 它也不影响续跑:要不要继续由门状态算出来(见内核 turnDemand)。
|
|
91
100
|
*/
|
|
92
101
|
autonomy: { override: null, effective: null },
|
|
93
102
|
/**
|
|
94
103
|
* 项目章程(`PROJECT.md`)的读数(内核扫描后随投影下发)。
|
|
95
104
|
* 为什么要有它:章程的**读**侧早就接在原生指令文件上了,但**写**侧只有一句提示词——
|
|
96
|
-
*
|
|
105
|
+
* 结果是跑完几轮、交付了产物,章程还是铺工作区那天的模板,谁也没发现——不提醒它就会一直漂着。
|
|
97
106
|
* 事实摆在这里,人与模型都看得见「它还是不是空壳」。
|
|
98
107
|
*/
|
|
99
108
|
constitution: null,
|
|
100
109
|
/**
|
|
101
|
-
*
|
|
110
|
+
* **本体形状**:内核随投影下发的那份声明(对象/状态/边/等级)。
|
|
102
111
|
* 为什么进投影而不是在界面里手抄:面板那一格的页眉要从**声明**生成——
|
|
103
112
|
* 抄一份就会漂移,而漂移的界面比没有界面更坏。它与目录同一条纪律:变了才发。
|
|
104
113
|
*/
|
|
105
114
|
ontology: null,
|
|
106
115
|
/**
|
|
107
|
-
*
|
|
116
|
+
* **续跑窗口的账**:我们向平台说过的那句话——「这个窗口归我布防 / 我按了暂停 /
|
|
108
117
|
* 我收兵了 / 它不在了而那不是我们干的」。形状见 `applyMutation` 的 `continuation/set`。
|
|
109
118
|
*
|
|
110
119
|
* 为什么它必须在投影里而不是内核内存里:宿主的 `paused` 相位分不清「人按的」与
|
|
@@ -258,12 +267,14 @@ export function applyMutation(state, mutation) {
|
|
|
258
267
|
next.goal.status = mutation.status
|
|
259
268
|
next.goal.closedAt = at
|
|
260
269
|
next.goal.closeVerdict = mutation.verdict ?? null
|
|
270
|
+
// 结案时如实记下「没被任何证据触及的假设」:未判不是「没问题」,是「没看过」。
|
|
271
|
+
next.goal.unjudged = Array.isArray(mutation.unjudged) ? mutation.unjudged.slice() : []
|
|
261
272
|
}
|
|
262
273
|
break
|
|
263
274
|
}
|
|
264
275
|
case 'hypothesis/superseded': {
|
|
265
276
|
/**
|
|
266
|
-
* **终态黏住**(
|
|
277
|
+
* **终态黏住**(与本体里「推翻不可撤销」同一条原则):
|
|
267
278
|
* 旧写法把任何还没被替代的行都改成 `superseded` —— 于是一条**已被推翻**的假设,
|
|
268
279
|
* 会因为下一版假设清单里没再列它而变成「已被替代」。而「被推翻」与「被替代」是两件事:
|
|
269
280
|
* 前者说这条猜想错了,后者说这条猜想不再被提。后者不该改写前者。
|
|
@@ -293,9 +304,11 @@ export function applyMutation(state, mutation) {
|
|
|
293
304
|
blocked: undefined,
|
|
294
305
|
/**
|
|
295
306
|
* 授权记号(ClearAI `plan.confirmed_at` / `confirmed_by`)。
|
|
296
|
-
* 两种取得方式,都可考:**显式动作**(
|
|
297
|
-
* **行为**(交付过一步 → `by:'progress'
|
|
298
|
-
*
|
|
307
|
+
* 两种取得方式,都可考:**显式动作**(人在原生审阅卡上批准 → `by:'user'`)与
|
|
308
|
+
* **行为**(交付过一步 → `by:'progress'`,见内核 AdvancePlan)。
|
|
309
|
+
* 第三种来源 `by:'autonomy'`(无人值守档在立约时自动确认)**已删除**:
|
|
310
|
+
* 那会让「计划经人确认」这条证据变成系统自己签的。读取侧不需要兼容它,
|
|
311
|
+
* 因为删除发生在写入侧,历史日志里最多出现 `user` 与 `progress`。
|
|
299
312
|
* 注意这是一个**记号**,不是闸门:没确认的计划照样能被交付推起来,那一刻记号按事实补写。
|
|
300
313
|
*/
|
|
301
314
|
confirmed_at: mutation.confirmed_at ?? null,
|
|
@@ -365,6 +378,7 @@ export function applyMutation(state, mutation) {
|
|
|
365
378
|
digest: mutation.digest ?? null,
|
|
366
379
|
bytes: mutation.bytes ?? null,
|
|
367
380
|
note: mutation.note ?? null,
|
|
381
|
+
path: mutation.path ?? null,
|
|
368
382
|
step: mutation.step ?? null,
|
|
369
383
|
branch: mutation.branch ?? null,
|
|
370
384
|
at,
|
|
@@ -410,10 +424,10 @@ export function applyMutation(state, mutation) {
|
|
|
410
424
|
basis: mutation.basis ?? null,
|
|
411
425
|
refs: mutation.refs ?? [],
|
|
412
426
|
/**
|
|
413
|
-
*
|
|
427
|
+
* **出处**:记账那一刻解析好的四类入口
|
|
414
428
|
* (`artifact` / `audit-card` / `evaluator-session` / `approval-record`)。
|
|
415
429
|
* 界面只渲染它,不再自己猜「这一串是路径还是材料 id」——
|
|
416
|
-
* 正是那种猜法让整排出处点不开(
|
|
430
|
+
* 正是那种猜法让整排出处点不开(真数据踩过这个坑)。
|
|
417
431
|
*/
|
|
418
432
|
origins: Array.isArray(mutation.origins) ? mutation.origins : [],
|
|
419
433
|
anchor: mutation.anchor ?? 'artifact',
|
|
@@ -433,7 +447,7 @@ export function applyMutation(state, mutation) {
|
|
|
433
447
|
}
|
|
434
448
|
case 'human/released': {
|
|
435
449
|
/**
|
|
436
|
-
*
|
|
450
|
+
* 放行绑在**哪条轴**上要说清:主线绑步骤、世界线绑分支。
|
|
437
451
|
* `via` 记它凭什么算数——现在只有 `approval`(原生审批栈的权威记录),不推断。
|
|
438
452
|
*/
|
|
439
453
|
next.releases.push({
|
|
@@ -449,7 +463,7 @@ export function applyMutation(state, mutation) {
|
|
|
449
463
|
}
|
|
450
464
|
case 'fact/promoted': {
|
|
451
465
|
/**
|
|
452
|
-
*
|
|
466
|
+
* 事实的字段:`scope` 是它的**边界**(推翻条件)——没有边界的事实下一轮没人敢用;
|
|
453
467
|
* `level` 是它被支持到哪一级(判者可查)。两者都来自升格那一刻的假设,不事后补。
|
|
454
468
|
*/
|
|
455
469
|
next.facts.push({
|
|
@@ -543,6 +557,7 @@ export function applyMutation(state, mutation) {
|
|
|
543
557
|
digest: mutation.digest ?? null,
|
|
544
558
|
conclusion: null,
|
|
545
559
|
note: null,
|
|
560
|
+
path: null,
|
|
546
561
|
at,
|
|
547
562
|
})
|
|
548
563
|
break
|
|
@@ -552,6 +567,7 @@ export function applyMutation(state, mutation) {
|
|
|
552
567
|
if (scout !== undefined) {
|
|
553
568
|
scout.conclusion = mutation.conclusion ?? null
|
|
554
569
|
scout.note = mutation.note ?? null
|
|
570
|
+
scout.path = mutation.path ?? null
|
|
555
571
|
}
|
|
556
572
|
break
|
|
557
573
|
}
|
|
@@ -670,11 +686,11 @@ export function applyMutation(state, mutation) {
|
|
|
670
686
|
}
|
|
671
687
|
case 'continuation/set': {
|
|
672
688
|
/**
|
|
673
|
-
*
|
|
689
|
+
* 续跑窗口的账。四种状态就是「我们说过的话」的全部:
|
|
674
690
|
* · `armed` —— 我们布了防(或按策略恢复了它):此刻它在为我们跑。
|
|
675
691
|
* · `paused` —— **我们**按下的暂停,`why` 是真实理由(等裁决 / 等人门 / 阶段边界)。
|
|
676
692
|
* · `stopped` —— 我们收的兵(目标达成 / 如实放弃 / 计划触礁)。
|
|
677
|
-
* · `withdrawn` —— 它不在了,而**不是我们清的**(
|
|
693
|
+
* · `withdrawn` —— 它不在了,而**不是我们清的**(我们的清除永远与建同拍):
|
|
678
694
|
* 这是平台上唯一说得出口的解释,所以它也只说这些,不说「谁」干的。
|
|
679
695
|
* 一条记录:**窗口在会话里是单数**,所以后一条覆盖前一条,不留历史(历史在会话日志里)。
|
|
680
696
|
*/
|
|
@@ -684,7 +700,7 @@ export function applyMutation(state, mutation) {
|
|
|
684
700
|
target: mutation.target ?? null,
|
|
685
701
|
why: mutation.why ?? null,
|
|
686
702
|
/**
|
|
687
|
-
*
|
|
703
|
+
* 平台对象上那句身份文本的**最后一次观测值**:我们写下它时记我们的,
|
|
688
704
|
* 别人改写之后重记他的。判据是「平台上的文本 === 这条记录」⇒ 还是我们的。
|
|
689
705
|
*/
|
|
690
706
|
label: mutation.label ?? null,
|
|
@@ -721,12 +737,26 @@ export function applyEvent(state, event) {
|
|
|
721
737
|
// 内核观察到的事实(候选技能、采纳记录、合并目录)走**插件消息的结构化 section**——
|
|
722
738
|
// 记在会话日志里,所以状态仍然可以从日志重放出来,而不是靠一句散文。
|
|
723
739
|
const source = event.data?.source
|
|
740
|
+
/**
|
|
741
|
+
* 原生结算通知:先折它。结论 = 通知里「Its closing message:」之后的那些文本块
|
|
742
|
+
* (前面是运行时写的一行摘要,不是子会话说的话)。
|
|
743
|
+
*/
|
|
744
|
+
if (source !== null && typeof source === 'object' && source.kind === 'subagent-settled') {
|
|
745
|
+
const blocks = (event.data?.content ?? []).filter((block) => block?.type === 'text').map((block) => String(block.text ?? ''))
|
|
746
|
+
const label = blocks.findIndex((text) => /closing message/i.test(text))
|
|
747
|
+
const conclusion = (label === -1 ? blocks.slice(1) : blocks.slice(label + 1)).join('\n').trim()
|
|
748
|
+
const child = source.senderSessionId === null || source.senderSessionId === undefined ? null : String(source.senderSessionId)
|
|
749
|
+
// `next` 必须在这一支里自己 clone:这道门里它还没被声明(旁边那段注释警告过同一个坑)。
|
|
750
|
+
const next = clone(state)
|
|
751
|
+
next.notices = [...(next.notices ?? []), { child, summary: String(source.summary ?? blocks[0] ?? ''), conclusion, at: typeof event.time === 'number' ? event.time : Date.now() }]
|
|
752
|
+
return next
|
|
753
|
+
}
|
|
724
754
|
if (source !== null && typeof source === 'object' && source.kind === 'plugin' && Array.isArray(source.sections)) {
|
|
725
755
|
/**
|
|
726
|
-
* 一条插件消息里可能**同时**带好几件事实(内核一次 pre-step
|
|
727
|
-
* 所以这里是「逐件折」而不是「找到一件就 return
|
|
728
|
-
* 原来找到目录就 return
|
|
729
|
-
*
|
|
756
|
+
* 一条插件消息里可能**同时**带好几件事实(内核一次 pre-step 把目录、运行档、候选一起发)。
|
|
757
|
+
* 所以这里是「逐件折」而不是「找到一件就 return」——早退会漏掉后面的事件:
|
|
758
|
+
* 原来找到目录就 return,于是同一条消息里的**运行档被吃掉**,
|
|
759
|
+
* 表现是投影里的 effective 档永远停在「还没定档」,而机制那边早就按新档跑了。
|
|
730
760
|
*/
|
|
731
761
|
const brain = source.sections.find((section) => section?.name === 'clearai/brain')
|
|
732
762
|
const tier = source.sections.find((section) => section?.name === 'clearai/autonomy')
|
|
@@ -738,7 +768,7 @@ export function applyEvent(state, event) {
|
|
|
738
768
|
* 注意它必须**在同一道门里**折:一条插件消息可能同时带目录、当档与事实变更,
|
|
739
769
|
* 而 `next` 是这道门里才 clone 出来的。第一版把这段写在门**外**并引用了 `next`,
|
|
740
770
|
* 抛出的 ReferenceError 被自己的 try/catch 吞掉——于是它一直是空操作
|
|
741
|
-
* (
|
|
771
|
+
* (失效模式:世界线结论回灌落账了,投影里却看不见)。
|
|
742
772
|
*/
|
|
743
773
|
const ontologySection = source.sections.find((section) => section?.name === 'clearai/ontology')
|
|
744
774
|
if (ontologySection !== undefined && typeof ontologySection.text === 'string') {
|
|
@@ -812,7 +842,9 @@ export function applyEvent(state, event) {
|
|
|
812
842
|
const next = clone(state)
|
|
813
843
|
for (const name of quoted) recordSkillUse(next, name, 'human', at, stepPointer(state))
|
|
814
844
|
if (gate === null) return next
|
|
815
|
-
//
|
|
845
|
+
// **旧日志容忍分支**(写入者已摘除,只有旧日志可能带着):人切运行档曾经是一条人门动作,旧会话里可能留着。
|
|
846
|
+
// 读到它就照旧记成一条**人的事实**,让历史会话仍然读得通;当前面板上已经没有这个开关,
|
|
847
|
+
// 新的会话不会再产生这一条。不要据此认为"现在还能切档"——要删这个分支得先确认没有旧日志。
|
|
816
848
|
if (gate.action === 'set_autonomy') {
|
|
817
849
|
const value = String(gate.value ?? '')
|
|
818
850
|
if (!AUTONOMY_VALUES.includes(value)) return next
|
|
@@ -898,7 +930,7 @@ export function derive(state) {
|
|
|
898
930
|
.reduce((best, item) => Math.max(best, levelIndex(item.level)), -1)
|
|
899
931
|
let status = hypothesis.status
|
|
900
932
|
if (status === 'proposed' && rows.length > 0) status = 'alive'
|
|
901
|
-
//
|
|
933
|
+
// 被推翻是黏性终态:「已被替代」不该改写「已被推翻」——两件事。
|
|
902
934
|
if (status !== 'superseded' && status !== 'refuted' && refutations > 0) status = 'refuted'
|
|
903
935
|
return { ...hypothesis, status, supportedLevel: supportedLevel < 0 ? null : `L${supportedLevel}`, refutations, inconclusive }
|
|
904
936
|
})
|
|
@@ -925,7 +957,7 @@ export function derive(state) {
|
|
|
925
957
|
/**
|
|
926
958
|
* 完成度(派生,不存)。
|
|
927
959
|
*
|
|
928
|
-
*
|
|
960
|
+
* 一处自相矛盾的由来:目标已经 achieved、两条事实都升格了,卡片却写「完成度 0%」。
|
|
929
961
|
* 两个原因:① 计划一关,活跃计划为 null,口径回落到假设;② 而**升格**(`fact/promoted`)
|
|
930
962
|
* 是一条独立路径,它不改假设状态,于是分子是 0。
|
|
931
963
|
*
|
|
@@ -949,7 +981,7 @@ export function derive(state) {
|
|
|
949
981
|
}
|
|
950
982
|
|
|
951
983
|
/**
|
|
952
|
-
* 世界线的**派生状态**(
|
|
984
|
+
* 世界线的**派生状态**(语义:见 recon 的「四条结束方式」):
|
|
953
985
|
*
|
|
954
986
|
* · `failed` —— 执行者没跑成(`worldline/executed{ok:false}`)。**它不是落选**:
|
|
955
987
|
* 落选意味着它跑完了、被尺子排到了后面;失败是「世界没给它机会」。原来的实现在这两件事上
|
|
@@ -981,7 +1013,7 @@ export function derive(state) {
|
|
|
981
1013
|
failed: branch.status === 'exploring' && branch.execution !== null && branch.execution !== undefined && branch.execution.ok === false,
|
|
982
1014
|
orphaned: ownerVoided && !fork.settled && !fork.abandoned && branch.status === 'exploring',
|
|
983
1015
|
/**
|
|
984
|
-
* **执行者未归**(
|
|
1016
|
+
* **执行者未归**(与「失败」「孤儿」同一族的假话——不说破就会被当成还在跑):
|
|
985
1017
|
*
|
|
986
1018
|
* 执行者是异步派的,所以「它在跑」是一条会过期的状态。分叉一旦收口(收敛或放弃),
|
|
987
1019
|
* 那条世界线就**没有归宿**了——回灌也进不了任何决定。可 `execution.ok` 仍是 `null`,
|
|
@@ -1000,7 +1032,7 @@ export function derive(state) {
|
|
|
1000
1032
|
* 不可能残留成僵尸。
|
|
1001
1033
|
* 条目只带**分诊信息**(标题/摘要/指向),不带全部正文:收件箱是分诊,不是问诊。
|
|
1002
1034
|
*
|
|
1003
|
-
*
|
|
1035
|
+
* 这里**只放真门**:不拍板就真的推不动的那种。计划确认不再是条目(它是记号,
|
|
1004
1036
|
* 不是闸门;见 HUMAN_GATE_ACTIONS 的收敛记录)——收件箱里的每一条都经得起「等人是必须的吗」。
|
|
1005
1037
|
*/
|
|
1006
1038
|
const inbox = []
|
|
@@ -1030,7 +1062,7 @@ export function derive(state) {
|
|
|
1030
1062
|
// 复核不是一次点击:要么认可(什么都不用做),要么让人/模型改判据重做一条。
|
|
1031
1063
|
human_action: null,
|
|
1032
1064
|
/**
|
|
1033
|
-
*
|
|
1065
|
+
* 它是**要一句话**的门 ✗ —— "认可就什么都不用做"的写法有个洞:
|
|
1034
1066
|
* 门开着 ⇒ 续跑停着(`hasOpenGate`)⇒ 什么都不做的话,系统就一直等 ✗。
|
|
1035
1067
|
* 所以人得**说一声**;界面上必须把这件事说出来(以前既没按钮也没提示 ✗)。
|
|
1036
1068
|
*/
|
|
@@ -1095,23 +1127,27 @@ export function derive(state) {
|
|
|
1095
1127
|
* · **agent 不可达**:这些动词**没有工具 schema**——模型能调的工具面里不存在它们,
|
|
1096
1128
|
* 它只能看见「人做了什么」这条事实。
|
|
1097
1129
|
*
|
|
1098
|
-
*
|
|
1130
|
+
* 动词表只有 4 个(奥卡姆:砍掉的两个都是**重复**):
|
|
1099
1131
|
* · `confirm_plan` —— 原生 `dsh-plan-mode` 就是「用户复核的出口」;而我们自己的
|
|
1100
1132
|
* `planIsAuthorized` 本来就承认「交付第一步即授权」。计划确认从来不是闸门(它是记号),
|
|
1101
1133
|
* 少一个假装成闸门的按钮,界面就不再暗示一条不存在的约束。需要人拍板时用原生
|
|
1102
1134
|
* `ask_user_question`(它同样把人的答复留在日志里,署名一样是人)。
|
|
1103
1135
|
* · `invoke_skill` —— 原生 `/` 技能触发器(`dsh-client-ui-skill` 注册 trigger `/`,
|
|
1104
1136
|
* `dsh-client-ui-input-trigger` 出候选菜单)做的正是同一件事,而且带候选菜单。
|
|
1105
|
-
*
|
|
1137
|
+
* 留下的三个各有原生没有的职责:
|
|
1106
1138
|
* · `adopt_branch`/`abandon_fork` —— `ConvergeFork` 只认「算术」或「人门」两条路,
|
|
1107
1139
|
* 算不出来时这是唯一的结构化人裁决通道(不做 NLU 是纪律,不是懒);
|
|
1108
|
-
* · `promote_skill` —— 候选技能扶正是**只有人能触发**的写动作(模型不能自举)
|
|
1109
|
-
*
|
|
1140
|
+
* · `promote_skill` —— 候选技能扶正是**只有人能触发**的写动作(模型不能自举)。
|
|
1141
|
+
*
|
|
1142
|
+
* 已摘除:`set_autonomy`。「在场与否」是**运行时状态**(有没有门开着、有没有裁决在飞),
|
|
1143
|
+
* 不是人在面板上按的一个开关;那个档位还顺手把「计划经人确认」变成系统自己签的。
|
|
1144
|
+
* 折法里仍留一条**只读**容忍分支(见 `applyEvent` 的注释):旧会话日志里可能有一条这样的记录,
|
|
1145
|
+
* 而历史必须继续读得通——但**当前没有任何写入者**,面板上也没有这个开关。
|
|
1110
1146
|
*/
|
|
1111
1147
|
export const HUMAN_GATE_MARK = '[clearai·人门]'
|
|
1112
1148
|
/** 面板上允许出现的动词。表外的动词一律拒(与贡献表同一套「表外的名字不许出现」)。 */
|
|
1113
1149
|
export const HUMAN_GATE_ACTIONS = ['adopt_branch', 'abandon_fork', 'promote_skill']
|
|
1114
|
-
/**
|
|
1150
|
+
/** 运行档的两个取值。**只用于读取旧日志**里的 `set_autonomy` 记录;当前没有写入口。 */
|
|
1115
1151
|
export const AUTONOMY_VALUES = ['attended', 'unattended']
|
|
1116
1152
|
|
|
1117
1153
|
/** 从一条用户消息里认出人门标记;不是标记就返回 null。 */
|
|
@@ -1174,7 +1210,7 @@ function skillUsageView(state) {
|
|
|
1174
1210
|
.sort((left, right) => String(left.name).localeCompare(String(right.name)))
|
|
1175
1211
|
}
|
|
1176
1212
|
|
|
1177
|
-
/**
|
|
1213
|
+
/** 面板契约。浏览器读的就是这个,一字不改。 */
|
|
1178
1214
|
export function view(state, sessionId) {
|
|
1179
1215
|
const derived = derive(state)
|
|
1180
1216
|
const plan = derived.activePlan ?? derived.closedPlans[derived.closedPlans.length - 1] ?? null
|
|
@@ -1187,8 +1223,10 @@ export function view(state, sessionId) {
|
|
|
1187
1223
|
inbox: derived.inbox,
|
|
1188
1224
|
hasOpenGate: derived.hasOpenGate,
|
|
1189
1225
|
/**
|
|
1190
|
-
* 运行档:`value` = 当档(内核下发的那份)、`source` 说明它从哪来(部署预设 /
|
|
1191
|
-
*
|
|
1226
|
+
* 运行档:`value` = 当档(内核下发的那份)、`source` 说明它从哪来(部署预设 / 旧日志里的人门记录)。
|
|
1227
|
+
* 面板与卡片都读这里 —— 投影是唯一真相。
|
|
1228
|
+
* 注意**它不是可切换的开关**:`set_autonomy` 已摘除,现在只有部署预设会下发新值;
|
|
1229
|
+
* `source === 'session'` 只可能来自旧日志。
|
|
1192
1230
|
*/
|
|
1193
1231
|
/** 项目章程的读数(面板「技能 · 记忆」页签顶部那一行;点开走原生预览)。 */
|
|
1194
1232
|
constitution: state.constitution ?? null,
|
|
@@ -1213,7 +1251,7 @@ export function view(state, sessionId) {
|
|
|
1213
1251
|
/** 本体形状(面板页眉据此生成,不手抄)。 */
|
|
1214
1252
|
ontology: state.ontology ?? null,
|
|
1215
1253
|
/**
|
|
1216
|
-
*
|
|
1254
|
+
* 续跑窗口的账:面板读它,于是「续跑停着——等裁决」这种**平台说不出来的话**
|
|
1217
1255
|
* 有地方说。轮数与相位仍然只在原生 dock 上出现(一个事实只在**一块**面上说),
|
|
1218
1256
|
* 这里交出去的只有:状态、它服务的事实对象、以及停下来的真实理由。
|
|
1219
1257
|
*/
|
|
@@ -1243,6 +1281,9 @@ export function view(state, sessionId) {
|
|
|
1243
1281
|
phase: derived.phase,
|
|
1244
1282
|
progress: derived.progress,
|
|
1245
1283
|
revision: state.goal.revision,
|
|
1284
|
+
// 结案留痕:没被任何证据触及的假设。未判不是「没问题」,是「没看过」。
|
|
1285
|
+
unjudged: Array.isArray(state.goal.unjudged) ? state.goal.unjudged.slice() : [],
|
|
1286
|
+
closeVerdict: state.goal.closeVerdict ?? null,
|
|
1246
1287
|
hypotheses: derived.hypotheses.map((hypothesis) => ({
|
|
1247
1288
|
id: hypothesis.id,
|
|
1248
1289
|
claim: hypothesis.claim,
|
|
@@ -1255,7 +1296,7 @@ export function view(state, sessionId) {
|
|
|
1255
1296
|
})),
|
|
1256
1297
|
},
|
|
1257
1298
|
/**
|
|
1258
|
-
*
|
|
1299
|
+
* **全部计划**:一个对话会有多个世界树。活动的那份照旧在 `plan` 里,
|
|
1259
1300
|
* 已收尾的留在这里 —— 它们没有被删(文档也归档在 `clear/goals/plans/<id>.md`),
|
|
1260
1301
|
* 只是原先**没有入口**切回去看 ✗。
|
|
1261
1302
|
*/
|
|
@@ -1284,7 +1325,7 @@ export function view(state, sessionId) {
|
|
|
1284
1325
|
: {
|
|
1285
1326
|
id: plan.id,
|
|
1286
1327
|
status: plan.status,
|
|
1287
|
-
/** 计划自己的一句话(模型建计划时写的)。**给人看的名字**用它,内部 id 退回 tooltip
|
|
1328
|
+
/** 计划自己的一句话(模型建计划时写的)。**给人看的名字**用它,内部 id 退回 tooltip。 */
|
|
1288
1329
|
brief: plan.brief ?? '',
|
|
1289
1330
|
blocked: plan.blocked ?? null,
|
|
1290
1331
|
// 授权记号(ClearAI `plan.confirmed_at` / `confirmed_by`):面板据此显示计划门。
|
|
@@ -1312,10 +1353,10 @@ export function view(state, sessionId) {
|
|
|
1312
1353
|
totalCount: plan.steps.filter((step) => step.status !== 'void').length,
|
|
1313
1354
|
},
|
|
1314
1355
|
/**
|
|
1315
|
-
*
|
|
1356
|
+
* **跨计划的步骤索引**:步骤 id → 它的判据/归属。
|
|
1316
1357
|
*
|
|
1317
1358
|
* 为什么必须有它(真数据踩出来的):计划会改版、收尾、重开——**命题的验证步常常留在旧计划里**
|
|
1318
|
-
* (
|
|
1359
|
+
* (成因:一场里多条计划,命题的 `tests` 可能在已收尾的那条上,而 `view.plan` 只交当前活动的那条)
|
|
1319
1360
|
* ⇒ 只查活动计划时,证据→步骤→命题这条链**全断**,面板上五个命题的证据与判者全显示「—」。
|
|
1320
1361
|
* 索引是纯派生,不新增存储。
|
|
1321
1362
|
*/
|
|
@@ -1332,14 +1373,14 @@ export function view(state, sessionId) {
|
|
|
1332
1373
|
stepId: item.step,
|
|
1333
1374
|
planId: item.plan,
|
|
1334
1375
|
/**
|
|
1335
|
-
* `branch` 与 `anchor`
|
|
1376
|
+
* `branch` 与 `anchor` **必须交出去**:面板要给每条证据指一个**出处**——
|
|
1336
1377
|
* 独立证据指评估卡(文件)、自判证据指它锚定的产物。少了这两个字段,
|
|
1337
|
-
* 「评估卡」那一项永远出不来(
|
|
1378
|
+
* 「评估卡」那一项永远出不来(成因:世界线证据的评估卡按 `分支` 归属,
|
|
1338
1379
|
* 与证据的 `stepId` 不是同一个 id,只看 stepId 会对不上)。
|
|
1339
1380
|
*/
|
|
1340
1381
|
branch: item.branch ?? null,
|
|
1341
1382
|
anchor: item.anchor ?? 'artifact',
|
|
1342
|
-
/**
|
|
1383
|
+
/** 记账时定下的出处。旧日志没有这个字段 ⇒ 客户端走只读回退。 */
|
|
1343
1384
|
origins: item.origins ?? [],
|
|
1344
1385
|
basisReviewable: item.basis_reviewable !== false,
|
|
1345
1386
|
verdict: item.verdict,
|
|
@@ -1360,7 +1401,18 @@ export function view(state, sessionId) {
|
|
|
1360
1401
|
capability: item.capability ?? null,
|
|
1361
1402
|
at: item.at,
|
|
1362
1403
|
})),
|
|
1363
|
-
materials: state.materials.map((item) => ({
|
|
1404
|
+
materials: state.materials.map((item) => ({
|
|
1405
|
+
id: item.id,
|
|
1406
|
+
ref: item.ref,
|
|
1407
|
+
source: item.source,
|
|
1408
|
+
digest: item.digest,
|
|
1409
|
+
note: item.note ?? null,
|
|
1410
|
+
// 落盘路径:评估者与人靠它读全文(模型侧由卡片的资料面给出同一个指针)。
|
|
1411
|
+
path: item.path ?? null,
|
|
1412
|
+
bytes: item.bytes ?? null,
|
|
1413
|
+
step: item.step ?? null,
|
|
1414
|
+
at: item.at,
|
|
1415
|
+
})),
|
|
1364
1416
|
facts: state.facts.map((item) => ({
|
|
1365
1417
|
id: item.id,
|
|
1366
1418
|
text: item.text,
|
|
@@ -1374,10 +1426,11 @@ export function view(state, sessionId) {
|
|
|
1374
1426
|
/**
|
|
1375
1427
|
* 侦察记录:**结局要能看出来**。
|
|
1376
1428
|
*
|
|
1377
|
-
*
|
|
1429
|
+
* 失效模式:中断一次跑动之后「状态上全部显示执行完成」,而宿主其实报了失败。
|
|
1378
1430
|
* 两处都修了:内核改成按 `stopReason` 落账(aborted/error 不算完成),这里把 `note` 交出去
|
|
1379
1431
|
* 并派生成三态——跑着 / 正常回灌 / 没正常结束。面板与资料面据此分诊,而不是一律当"完成"。
|
|
1380
1432
|
*/
|
|
1433
|
+
notices: (state.notices ?? []).map((item) => ({ child: item.child, summary: item.summary, conclusion: item.conclusion, at: item.at })),
|
|
1381
1434
|
scouts: (state.scouts ?? []).map((item) => ({
|
|
1382
1435
|
id: item.id,
|
|
1383
1436
|
stepId: item.step,
|
|
@@ -1388,6 +1441,7 @@ export function view(state, sessionId) {
|
|
|
1388
1441
|
conclusion: item.conclusion,
|
|
1389
1442
|
at: item.at,
|
|
1390
1443
|
note: item.note ?? null,
|
|
1444
|
+
path: item.path ?? null,
|
|
1391
1445
|
status: item.conclusion === null || item.conclusion === undefined ? 'running' : item.note === null || item.note === undefined ? 'settled' : 'failed',
|
|
1392
1446
|
})),
|
|
1393
1447
|
settlement: derived.settlement,
|
|
@@ -1468,7 +1522,7 @@ export function renderCard(state) {
|
|
|
1468
1522
|
if (autonomy !== null) {
|
|
1469
1523
|
lines.push(
|
|
1470
1524
|
/**
|
|
1471
|
-
*
|
|
1525
|
+
* 档位**不再是面板上的开关**:它只是部署预设写的初值。
|
|
1472
1526
|
* 所以卡片如实说"这件事是怎么配的",不再承诺"你可以在面板上切"(那是已删的能力 ✗)。
|
|
1473
1527
|
*/
|
|
1474
1528
|
`- 运行档:${autonomy.value === 'unattended' ? '无人值守' : '人在场'}(部署预设写的;它不是"要不要人参与"的开关——要不要人由**门**决定:计划待确认/等裁决/有人在等)`,
|
|
@@ -1485,10 +1539,14 @@ export function renderCard(state) {
|
|
|
1485
1539
|
if (derived.hypotheses.length > 0) {
|
|
1486
1540
|
lines.push('- 假设状态(由证据算出):')
|
|
1487
1541
|
for (const hypothesis of derived.hypotheses) {
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1542
|
+
/**
|
|
1543
|
+
* 三样全零 = **没人碰过它**,与「判过但无法判定」是两回事:
|
|
1544
|
+
* 前者要如实说「未触及」,后者本来就有「无法判定 n」这个读数。
|
|
1545
|
+
* 不逼 verdict,但也不许把「没看过」写成「没问题」。
|
|
1546
|
+
*/
|
|
1547
|
+
const untouched = (hypothesis.supportedLevel === null || hypothesis.supportedLevel === undefined) && (hypothesis.refutations ?? 0) === 0 && (hypothesis.inconclusive ?? 0) === 0
|
|
1548
|
+
const readings = untouched ? '(未触及)' : `(支持到 ${hypothesis.supportedLevel ?? '—'} · 推翻 ${hypothesis.refutations} · 无法判定 ${hypothesis.inconclusive})`
|
|
1549
|
+
lines.push(` · ${hypothesis.id} [${hypothesis.status}] ${hypothesis.claim} — 推翻条件:${hypothesis.refute_when}${readings}`)
|
|
1492
1550
|
}
|
|
1493
1551
|
}
|
|
1494
1552
|
if (plan === null) {
|
|
@@ -1496,21 +1554,21 @@ export function renderCard(state) {
|
|
|
1496
1554
|
} else {
|
|
1497
1555
|
if (derived.planConfirmationPending) {
|
|
1498
1556
|
/**
|
|
1499
|
-
*
|
|
1557
|
+
* 授权记号的语义:它是**归属**,不是闸门。
|
|
1500
1558
|
*
|
|
1501
|
-
*
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1559
|
+
* 记号没落账时,自动续跑会 hold(turnDemand),但显式推进不被阻止——
|
|
1560
|
+
* 第一次交付会在同一条变更里按事实补写 by='progress'(行为即授权)。
|
|
1561
|
+
* 卡片只交代机理,不劝人走哪条路:劝告会让人(和模型)以为有一道必须走的门。
|
|
1562
|
+
* 卡片不出现字段名(plan_confirmation_pending / confirmed_at 是账本词汇,
|
|
1563
|
+
* 不是给人与模型读的语言)——事实用一句人话说。
|
|
1504
1564
|
*/
|
|
1505
|
-
lines.push('-
|
|
1506
|
-
lines.push('- confirmed_at:(null — 授权记号未落账;第一次交付会按事实补写(行为即授权)。不需要任何人先按什么)')
|
|
1565
|
+
lines.push('- 授权:记号未落账。未经人批准的计划不会自动续跑;显式推进时,第一次交付会按事实补写归属(行为即授权)')
|
|
1507
1566
|
} else if (plan.confirmed_at === null) {
|
|
1508
1567
|
// 记号没落账,但计划事实上已经推进过:授权已经发生。照旧写「等人确认」会与上一行
|
|
1509
1568
|
// 当场矛盾,而同一段里两句打架的话比一句错话更糟(ClearAI 的原话)。
|
|
1510
|
-
lines.push('-
|
|
1511
|
-
lines.push('- confirmed_at:(null,但本计划已推进过——授权已经发生,继续执行)')
|
|
1569
|
+
lines.push('- 授权:记号未落账,但本计划已推进过——授权已经发生(行为即授权),继续执行')
|
|
1512
1570
|
} else {
|
|
1513
|
-
lines.push(`-
|
|
1571
|
+
lines.push(`- 授权:已于 ${plan.confirmed_at} 落账(${plan.confirmed_by === 'user' ? '人显式批准' : plan.confirmed_by === 'progress' ? '据推进事实补写归属' : String(plan.confirmed_by)})`)
|
|
1514
1572
|
}
|
|
1515
1573
|
lines.push(`- 当前计划(${plan.id}${plan.goal === null ? '' : ` · 目标 ${plan.goal} 的一个阶段`})步骤:`)
|
|
1516
1574
|
for (const step of plan.steps) {
|
|
@@ -1530,7 +1588,7 @@ export function renderCard(state) {
|
|
|
1530
1588
|
for (const branch of fork.branches) {
|
|
1531
1589
|
const reading = branch.reading === null || branch.reading === undefined ? '未报读数' : `读数 ${branch.reading}${branch.validity === 'usable' ? '' : '(不可用)'}`
|
|
1532
1590
|
/**
|
|
1533
|
-
* 执行者是**异步**派的(
|
|
1591
|
+
* 执行者是**异步**派的(ForkPlan 不空等结论),所以「它跑完了没有」是一条
|
|
1534
1592
|
* 独立的事实——不写出来,模型会以为分支在闲着,或者以为结论已经到手。
|
|
1535
1593
|
*/
|
|
1536
1594
|
const executing = branch.execution ?? null
|
|
@@ -1571,6 +1629,33 @@ export function renderCard(state) {
|
|
|
1571
1629
|
lines.push(`- 最近一条证据:${last.id} ${last.verdict}(${last.evaluator} · ${last.level})`)
|
|
1572
1630
|
}
|
|
1573
1631
|
if (state.facts.length > 0) lines.push(`- 已升格事实:${state.facts.length} 条`)
|
|
1632
|
+
// 结案时留的痕:未判的假设不是「没问题」,是「没看过」——结案之后也要看得见。
|
|
1633
|
+
if (Array.isArray(goal?.unjudged) && goal.unjudged.length > 0) lines.push(`- 结案留痕:有 ${goal.unjudged.length} 条假设没有被任何证据触及(${goal.unjudged.join(', ')})`)
|
|
1634
|
+
/**
|
|
1635
|
+
* **资料面**:外脑送回来的观测(不是你写的那些)**与还在跑的侦察**。
|
|
1636
|
+
*
|
|
1637
|
+
* 为什么必须有它:模型每一步唯一读到的窗口就是这张卡。从前结论只进 `view()`
|
|
1638
|
+
* (面板读得到、模型读不到),于是判据写成「与侦察结论一致」时,模型与独立评估者
|
|
1639
|
+
* 都无处可读,只能裁 inconclusive。这里给**指针 + 摘要**:全文落在
|
|
1640
|
+
* `clear/knowledge/materials/<id>.md`,要细节就 `read` 它——卡片不背长文。
|
|
1641
|
+
*/
|
|
1642
|
+
{
|
|
1643
|
+
const foreign = state.materials.filter((material) => material.source !== 'self')
|
|
1644
|
+
const flying = state.scouts.filter((scout) => scout.conclusion === null || scout.conclusion === undefined)
|
|
1645
|
+
if (foreign.length > 0 || flying.length > 0) {
|
|
1646
|
+
lines.push('- 资料面(外脑送来的观测 + 还在跑的侦察;全文在工作区文件里,要细节就 read):')
|
|
1647
|
+
for (const material of foreign.slice(-3)) {
|
|
1648
|
+
const note = String(material.note ?? '')
|
|
1649
|
+
const excerpt = note.replace(/\s+/g, ' ').slice(0, 120)
|
|
1650
|
+
const where = material.path === null || material.path === undefined ? `账本 ${material.ref}` : material.path
|
|
1651
|
+
lines.push(` · [${material.source}] ${where}${material.bytes === null || material.bytes === undefined ? '' : `(${material.bytes} 字)`}:${excerpt}${note.length > 120 ? '…' : ''}`)
|
|
1652
|
+
}
|
|
1653
|
+
if (foreign.length > 3) lines.push(` · (还有 ${foreign.length - 3} 条更早的,全在 clear/knowledge/materials/ 下)`)
|
|
1654
|
+
for (const scout of flying) {
|
|
1655
|
+
lines.push(` · [在跑] 侦察 ${scout.id}${scout.trigger === null || scout.trigger === undefined ? '' : `(${scout.trigger})`}:结论回来时会作为观测送到你面前`)
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1574
1659
|
lines.push('- 提醒:进度、阶段、假设状态都是系统算出来的;你不能声明它们,只能通过交付与裁决推进。')
|
|
1575
1660
|
return lines.join('\n')
|
|
1576
1661
|
}
|