clearai-dsh 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +66 -77
- package/README.zh-CN.md +60 -71
- package/lib/client.js +498 -69
- package/lib/domain-language.js +740 -0
- package/lib/fold.js +185 -7
- package/lib/host.js +71 -1
- package/package.json +1 -1
- package/presets/clearai/agent.cordis.yml +3 -3
- package/presets/clearai/plugins/clearai-kernel.js +430 -6
- package/presets/clearai/plugins/prompts.js +32 -0
package/lib/fold.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* 与预设内核的契约:`meta = { kind: 'clearai', v: 1, mutation: { t, ... } }`。
|
|
17
17
|
* 词汇表由本文件的 `applyMutation` 定义;预设侧只负责产出,不负责解释。
|
|
18
18
|
*/
|
|
19
|
+
import { applyLexiconMutation, deriveConflicts, emptyLexicon, formatAssertion, graphProjection, lexiconHealth, normalizeLexicon } from './domain-language.js'
|
|
19
20
|
|
|
20
21
|
/** 世界线分支状态的秩。秩只增;一旦出现 adopted,整个分叉的分支状态冻结。 */
|
|
21
22
|
const BRANCH_RANK = { exploring: 0, evaluated: 1, adopted: 2, pruned: 2 }
|
|
@@ -35,9 +36,12 @@ export const MUTATION_KIND = 'clearai'
|
|
|
35
36
|
* v6 → v7:那条账多一个 `label`:我们最后一次在平台对象上写下的那句身份文本。
|
|
36
37
|
* 它的用途只有一个:**分清「身份变了」与「人改写过了」**——前者我们换窗口,
|
|
37
38
|
* 后者以人为准、一个字都不动。没有它,这两种情形在外部长得一模一样。
|
|
39
|
+
* v9 → v10:多了「领域词汇」(`lexicon`)与类型化事实:断言进 `fact.promoted`,
|
|
40
|
+
* 事实与假设按 id 关联(旧账本仍按主张文本)。旧日志折出来的 `lexicon` 是空表,
|
|
41
|
+
* 没有断言的事实照旧可读——形状变了才 +1,不是语义变了才 +1。
|
|
38
42
|
* 投影缓存按版本判定,所以旧缓存会被丢弃、从日志重折一遍——用量是**从日志折出来的**,重折才完整。
|
|
39
43
|
*/
|
|
40
|
-
export const STATE_VERSION =
|
|
44
|
+
export const STATE_VERSION = 10
|
|
41
45
|
|
|
42
46
|
/**
|
|
43
47
|
* **只留台账、不折进视图**的变更类型(词汇表的另一半)。
|
|
@@ -108,6 +112,15 @@ export function emptyState() {
|
|
|
108
112
|
* 抄一份就会漂移,而漂移的界面比没有界面更坏。它与目录同一条纪律:变了才发。
|
|
109
113
|
*/
|
|
110
114
|
ontology: null,
|
|
115
|
+
/**
|
|
116
|
+
* **领域词汇**(概念与谓词)与它的读面。
|
|
117
|
+
*
|
|
118
|
+
* 为什么它与上面的 `ontology` 是两个字段:`ontology` 是**过程本体**的形状
|
|
119
|
+
* (插件自己的后台流转结构,随版本走、不可编辑);`lexicon` 是**领域本体**的内容
|
|
120
|
+
* (项目自己的语言:概念、谓词、值形态),由账本里的本体事件折出来、可增删改。
|
|
121
|
+
* 名字分开,是因为权威不同——一个随发布变,一个随项目长。
|
|
122
|
+
*/
|
|
123
|
+
lexicon: emptyLexicon(),
|
|
111
124
|
/**
|
|
112
125
|
* **续跑窗口的账**:我们向平台说过的那句话——「这个窗口归我布防 / 我按了暂停 /
|
|
113
126
|
* 我收兵了 / 它不在了而那不是我们干的」。形状见 `applyMutation` 的 `continuation/set`。
|
|
@@ -196,6 +209,18 @@ function clone(value) {
|
|
|
196
209
|
return value === undefined ? undefined : JSON.parse(JSON.stringify(value))
|
|
197
210
|
}
|
|
198
211
|
|
|
212
|
+
/**
|
|
213
|
+
* 假设 ↔ 事实的关联:优先按 `hypothesis` id(新账本),旧账本按主张文本——
|
|
214
|
+
* 那时的事实还没有那个字段。顺序不能反:id 是身份,文本只是当时的措辞,
|
|
215
|
+
* 改一次措辞就断链的关联迟早会把「这条假设已被升格」读成「还没升格」。
|
|
216
|
+
*/
|
|
217
|
+
function factFromHypothesis(facts, goalId, hypothesisId, claim) {
|
|
218
|
+
return (Array.isArray(facts) ? facts : []).some((fact) => {
|
|
219
|
+
if (typeof fact?.hypothesis === 'string' && fact.hypothesis !== '') return fact.hypothesis === hypothesisId
|
|
220
|
+
return fact?.goal === goalId && String(fact.text ?? '') === String(claim ?? '')
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
199
224
|
function appendSteps(plan, rawSteps) {
|
|
200
225
|
for (const raw of rawSteps) {
|
|
201
226
|
plan.steps.push({
|
|
@@ -267,6 +292,11 @@ export function applyMutation(state, mutation) {
|
|
|
267
292
|
refute_when: hypothesis.refute_when,
|
|
268
293
|
status: 'proposed',
|
|
269
294
|
version: hypothesis.version ?? 1,
|
|
295
|
+
/**
|
|
296
|
+
* **类型化断言随假设走**:登记时校验、升格时定型。
|
|
297
|
+
* 不写断言照旧成立(宽松+校验);写了就是「这条主张用这门语言怎么说」。
|
|
298
|
+
*/
|
|
299
|
+
assertions: Array.isArray(hypothesis.assertions) ? clone(hypothesis.assertions) : null,
|
|
270
300
|
at,
|
|
271
301
|
})
|
|
272
302
|
}
|
|
@@ -290,7 +320,7 @@ export function applyMutation(state, mutation) {
|
|
|
290
320
|
* 前者说这条猜想错了,后者说这条猜想不再被提。后者不该改写前者。
|
|
291
321
|
* 已经升格成事实的(`claim` 出现在 facts 里)同理:事实在,假设就不能被悄悄换掉。
|
|
292
322
|
*/
|
|
293
|
-
const promoted = next.facts
|
|
323
|
+
const promoted = factFromHypothesis(next.facts, mutation.goal, mutation.id, mutation.claim)
|
|
294
324
|
const row = next.hypotheses.find((item) => item.id === mutation.id && item.status !== 'superseded' && item.status !== 'refuted' && !promoted)
|
|
295
325
|
if (row !== undefined) {
|
|
296
326
|
row.status = 'superseded'
|
|
@@ -475,15 +505,19 @@ export function applyMutation(state, mutation) {
|
|
|
475
505
|
/**
|
|
476
506
|
* 事实的字段:`scope` 是它的**边界**(推翻条件)——没有边界的事实下一轮没人敢用;
|
|
477
507
|
* `level` 是它被支持到哪一级(判者可查)。两者都来自升格那一刻的假设,不事后补。
|
|
508
|
+
* `hypothesis` 是产出它的那条假设(新账本才有),`assertions` 是类型化断言——
|
|
509
|
+
* 没提供就是 null:**不提供放行,提供即严校**(校验发生在落账之前)。
|
|
478
510
|
*/
|
|
479
511
|
next.facts.push({
|
|
480
512
|
id: mutation.id,
|
|
481
513
|
goal: mutation.goal,
|
|
514
|
+
hypothesis: mutation.hypothesis ?? null,
|
|
482
515
|
text: mutation.text,
|
|
483
516
|
scope: mutation.scope ?? null,
|
|
484
517
|
level: mutation.level ?? null,
|
|
485
518
|
evidence: mutation.evidence ?? [],
|
|
486
519
|
path: mutation.path ?? null,
|
|
520
|
+
assertions: Array.isArray(mutation.assertions) ? clone(mutation.assertions) : null,
|
|
487
521
|
at,
|
|
488
522
|
})
|
|
489
523
|
break
|
|
@@ -748,6 +782,22 @@ export function applyMutation(state, mutation) {
|
|
|
748
782
|
}
|
|
749
783
|
break
|
|
750
784
|
}
|
|
785
|
+
/**
|
|
786
|
+
* **领域词汇的六个事件**:接纳 / 版本化修订 / 黏性废止(概念与谓词各三条)。
|
|
787
|
+
*
|
|
788
|
+
* 折法在这里只做解释:把事件折成 `lexicon`。校验(引用是否存在、形状对不对、
|
|
789
|
+
* 语义变化有没有偷偷走修订)全部发生在**落账之前**——预设侧的工具与宿主路由
|
|
790
|
+
* 用的是 `domain-language.js` 的同一份判据;折法不重复判一遍,否则两份判据必然漂。
|
|
791
|
+
*/
|
|
792
|
+
case 'ontology/term_added':
|
|
793
|
+
case 'ontology/predicate_added':
|
|
794
|
+
case 'ontology/term_revised':
|
|
795
|
+
case 'ontology/predicate_revised':
|
|
796
|
+
case 'ontology/term_deprecated':
|
|
797
|
+
case 'ontology/predicate_deprecated': {
|
|
798
|
+
next.lexicon = applyLexiconMutation(next.lexicon ?? emptyLexicon(), mutation, at)
|
|
799
|
+
break
|
|
800
|
+
}
|
|
751
801
|
default: {
|
|
752
802
|
return state // 不认识:不感兴趣,原样返回
|
|
753
803
|
}
|
|
@@ -911,6 +961,29 @@ export function applyEvent(state, event) {
|
|
|
911
961
|
fact.review = { decision: gate.action === 'retract_fact' ? 'retracted' : 'kept', reason: gate.note ?? null, at, by: 'user' }
|
|
912
962
|
return next
|
|
913
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* **本体四动词(人的通道)**:路由侧已按同一份判据校验过(表外的值进不了日志),
|
|
966
|
+
* 折法只做两件事——落成带 `by:'user'` 的词汇事件,以及**幂等**(人可能点两下、
|
|
967
|
+
* 消息也可能重放:同 id 再登记不重复落,修订/废止的幂等由 applyLexiconMutation 自己保证)。
|
|
968
|
+
*/
|
|
969
|
+
if (gate.action === 'register_term' || gate.action === 'register_predicate') {
|
|
970
|
+
const entry = gate.entry ?? {}
|
|
971
|
+
const taken = (next.lexicon.terms ?? []).some((item) => item.id === entry.id) || (next.lexicon.predicates ?? []).some((item) => item.id === entry.id)
|
|
972
|
+
if (taken === true) return next
|
|
973
|
+
next.lexicon = applyLexiconMutation(next.lexicon, { t: gate.action === 'register_term' ? 'ontology/term_added' : 'ontology/predicate_added', ...entry, by: 'user' }, at)
|
|
974
|
+
return next
|
|
975
|
+
}
|
|
976
|
+
if (gate.action === 'revise_term') {
|
|
977
|
+
const entry = gate.entry ?? {}
|
|
978
|
+
next.lexicon = applyLexiconMutation(next.lexicon, { t: 'ontology/term_revised', ...entry, by: 'user' }, at)
|
|
979
|
+
return next
|
|
980
|
+
}
|
|
981
|
+
if (gate.action === 'deprecate_entry') {
|
|
982
|
+
const entry = gate.entry ?? {}
|
|
983
|
+
const kind = (next.lexicon.terms ?? []).some((item) => item.id === entry.id) ? 'ontology/term_deprecated' : 'ontology/predicate_deprecated'
|
|
984
|
+
next.lexicon = applyLexiconMutation(next.lexicon, { t: kind, ...entry, by: 'user' }, at)
|
|
985
|
+
return next
|
|
986
|
+
}
|
|
914
987
|
const fork = next.forks.find((item) => item.id === (gate.fork ?? null))
|
|
915
988
|
if (fork === undefined) return next
|
|
916
989
|
fork.humanDecision = {
|
|
@@ -987,6 +1060,13 @@ export function derive(state) {
|
|
|
987
1060
|
* 读的是事实上的 `review`——人的动作落在事实那一侧,facts 是唯一事实源。
|
|
988
1061
|
*/
|
|
989
1062
|
const retractedClaims = new Set((state.facts ?? []).filter((item) => item.review?.decision === 'retracted').map((item) => String(item.text ?? '')))
|
|
1063
|
+
/** 同一件事,新的关联方式:事实带 `hypothesis` 时按 id 撤(文本只是旧账本的退路)。 */
|
|
1064
|
+
const retractedHypotheses = new Set(
|
|
1065
|
+
(state.facts ?? [])
|
|
1066
|
+
.filter((item) => item.review?.decision === 'retracted')
|
|
1067
|
+
.map((item) => item.hypothesis)
|
|
1068
|
+
.filter((id) => typeof id === 'string' && id !== ''),
|
|
1069
|
+
)
|
|
990
1070
|
const hypotheses = state.hypotheses.map((hypothesis) => {
|
|
991
1071
|
const rows = []
|
|
992
1072
|
for (const item of state.evidence) {
|
|
@@ -1000,7 +1080,7 @@ export function derive(state) {
|
|
|
1000
1080
|
.reduce((best, item) => Math.max(best, levelIndex(item.level)), -1)
|
|
1001
1081
|
let status = hypothesis.status
|
|
1002
1082
|
// 撤回优先于一切:那是人对已升格事实的裁决,后来的支持证据不复活它。
|
|
1003
|
-
if (retractedClaims.has(String(hypothesis.claim ?? ''))) status = 'retracted'
|
|
1083
|
+
if (retractedHypotheses.has(hypothesis.id) || retractedClaims.has(String(hypothesis.claim ?? ''))) status = 'retracted'
|
|
1004
1084
|
else if (status === 'proposed' && rows.length > 0) status = 'alive'
|
|
1005
1085
|
// 被推翻是黏性终态:「已被替代」不该改写「已被推翻」——两件事。
|
|
1006
1086
|
if (status !== 'superseded' && status !== 'refuted' && status !== 'retracted' && refutations > 0) status = 'refuted'
|
|
@@ -1053,6 +1133,11 @@ export function derive(state) {
|
|
|
1053
1133
|
* · **升格算数**:回落口径里,被升格成事实的假设也算「已确认」——升格本来就是它最硬的确认。
|
|
1054
1134
|
*/
|
|
1055
1135
|
const promotedClaims = new Set((state.facts ?? []).map((item) => String(item.text ?? '')))
|
|
1136
|
+
const promotedHypotheses = new Set(
|
|
1137
|
+
(state.facts ?? [])
|
|
1138
|
+
.map((item) => item.hypothesis)
|
|
1139
|
+
.filter((id) => typeof id === 'string' && id !== ''),
|
|
1140
|
+
)
|
|
1056
1141
|
const live = activePlan?.steps.filter((step) => step.status !== 'void') ?? []
|
|
1057
1142
|
if (state.goal?.status === 'achieved') progress = 1
|
|
1058
1143
|
else if (state.goal?.status === 'abandoned') progress = 0
|
|
@@ -1060,7 +1145,7 @@ export function derive(state) {
|
|
|
1060
1145
|
else {
|
|
1061
1146
|
const effective = hypotheses.filter((item) => item.status !== 'superseded' && item.status !== 'retracted')
|
|
1062
1147
|
if (effective.length > 0) {
|
|
1063
|
-
const done = effective.filter((item) => item.status === 'confirmed' || promotedClaims.has(String(item.claim ?? ''))).length
|
|
1148
|
+
const done = effective.filter((item) => item.status === 'confirmed' || promotedHypotheses.has(item.id) || promotedClaims.has(String(item.claim ?? ''))).length
|
|
1064
1149
|
progress = done / effective.length
|
|
1065
1150
|
}
|
|
1066
1151
|
}
|
|
@@ -1196,9 +1281,23 @@ export function derive(state) {
|
|
|
1196
1281
|
* · `review` ——人已经审查过(撤回 / 维持),决定连缘由一起留着。
|
|
1197
1282
|
*/
|
|
1198
1283
|
const factRows = (state.facts ?? []).map((fact) => {
|
|
1199
|
-
const
|
|
1284
|
+
const linked = typeof fact.hypothesis === 'string' && fact.hypothesis !== ''
|
|
1285
|
+
const owner = hypotheses.find((item) => (linked ? item.id === fact.hypothesis : String(item.claim ?? '') === String(fact.text ?? '')))
|
|
1200
1286
|
return { ...fact, refuted: (owner?.refutations ?? 0) > 0, review: fact.review ?? null }
|
|
1201
1287
|
})
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* **领域词汇的派生读数**(两条,都不新存东西):
|
|
1291
|
+
* · `conflicts`——同一个单值谓词、同一主体、两个不同客体的**成对**事实。它是读数,
|
|
1292
|
+
* 不是裁决:这里不撤回任何一侧,也不判断哪条为真(那是证据与人的事)。
|
|
1293
|
+
* · `lexiconHealth`——悬空引用、父链成环、没人用的条目、被废止条目仍在使用。
|
|
1294
|
+
* 全是提示,不拦任何操作。
|
|
1295
|
+
* 两者都吃 `factRows` 而不是 `state.facts`:事实的复核态与推翻标记是派生的,
|
|
1296
|
+
* 在这里重算一遍就等于第二份判据。
|
|
1297
|
+
*/
|
|
1298
|
+
const lexicon = normalizeLexicon(state.lexicon)
|
|
1299
|
+
const conflicts = deriveConflicts(factRows, lexicon)
|
|
1300
|
+
const lexiconIssues = lexiconHealth(lexicon, factRows)
|
|
1202
1301
|
for (const fact of factRows) {
|
|
1203
1302
|
/**
|
|
1204
1303
|
* **推翻证据只标记事实,撤不撤由人定**(数据本身也可能是错的)。
|
|
@@ -1232,7 +1331,26 @@ export function derive(state) {
|
|
|
1232
1331
|
}
|
|
1233
1332
|
})
|
|
1234
1333
|
|
|
1235
|
-
return {
|
|
1334
|
+
return {
|
|
1335
|
+
phase,
|
|
1336
|
+
progress,
|
|
1337
|
+
hypotheses,
|
|
1338
|
+
activePlan,
|
|
1339
|
+
closedPlans,
|
|
1340
|
+
pendingAudit,
|
|
1341
|
+
forks,
|
|
1342
|
+
factRows,
|
|
1343
|
+
settlement,
|
|
1344
|
+
stepOf,
|
|
1345
|
+
inbox,
|
|
1346
|
+
hasOpenGate,
|
|
1347
|
+
planConfirmationPending,
|
|
1348
|
+
planIsAuthorized,
|
|
1349
|
+
/** 领域词汇与它的两条派生读数(见上面那段:冲突与健康度都只是读数)。 */
|
|
1350
|
+
lexicon,
|
|
1351
|
+
conflicts,
|
|
1352
|
+
lexiconIssues,
|
|
1353
|
+
}
|
|
1236
1354
|
}
|
|
1237
1355
|
|
|
1238
1356
|
/**
|
|
@@ -1266,7 +1384,24 @@ export function derive(state) {
|
|
|
1266
1384
|
*/
|
|
1267
1385
|
export const HUMAN_GATE_MARK = '[clearai·人门]'
|
|
1268
1386
|
/** 面板上允许出现的动词。表外的动词一律拒(与贡献表同一套「表外的名字不许出现」)。 */
|
|
1269
|
-
export const HUMAN_GATE_ACTIONS = [
|
|
1387
|
+
export const HUMAN_GATE_ACTIONS = [
|
|
1388
|
+
'adopt_branch',
|
|
1389
|
+
'abandon_fork',
|
|
1390
|
+
'promote_skill',
|
|
1391
|
+
'retract_fact',
|
|
1392
|
+
'keep_fact',
|
|
1393
|
+
'confirm_provisional',
|
|
1394
|
+
/**
|
|
1395
|
+
* **本体四动词(人的通道)**:面板抽屉发的就是它们;模型有同名语义的工具
|
|
1396
|
+
* (`RegisterTerm` 等),但这两个面落的是**同一套判据与同一本账**——判据在宿主半的
|
|
1397
|
+
* `domain` 门里,词汇事件只有一种折法。这个数组在内核那一侧有一份**逐字镜像**
|
|
1398
|
+
* (预设面不能 import 这一层),两边相等由 authority-boundary 套件钉死。
|
|
1399
|
+
*/
|
|
1400
|
+
'register_term',
|
|
1401
|
+
'register_predicate',
|
|
1402
|
+
'revise_term',
|
|
1403
|
+
'deprecate_entry',
|
|
1404
|
+
]
|
|
1270
1405
|
/** 运行档的两个取值。**只用于读取旧日志**里的 `set_autonomy` 记录;当前没有写入口。 */
|
|
1271
1406
|
export const AUTONOMY_VALUES = ['attended', 'unattended']
|
|
1272
1407
|
|
|
@@ -1370,6 +1505,19 @@ export function view(state, sessionId) {
|
|
|
1370
1505
|
brain: state.brain ?? null,
|
|
1371
1506
|
/** 本体形状(面板页眉据此生成,不手抄)。 */
|
|
1372
1507
|
ontology: state.ontology ?? null,
|
|
1508
|
+
/**
|
|
1509
|
+
* **领域词汇的读面**:概念、谓词、冲突、健康读数与图投影。
|
|
1510
|
+
*
|
|
1511
|
+
* 客户端不重算——它连折法都读不到(浏览器那半是独立模块),所以图在这里算好推下去。
|
|
1512
|
+
* 于是「同一份账本 ⇒ 同一张图」是投影的性质,不是两处渲染约定出来的巧合。
|
|
1513
|
+
*/
|
|
1514
|
+
lexicon: {
|
|
1515
|
+
terms: derived.lexicon.terms,
|
|
1516
|
+
predicates: derived.lexicon.predicates,
|
|
1517
|
+
conflicts: derived.conflicts,
|
|
1518
|
+
health: derived.lexiconIssues,
|
|
1519
|
+
graph: graphProjection(state),
|
|
1520
|
+
},
|
|
1373
1521
|
/**
|
|
1374
1522
|
* 续跑窗口的账:面板读它,于是「续跑停着——等裁决」这种**平台说不出来的话**
|
|
1375
1523
|
* 有地方说。轮数与相位仍然只在原生 dock 上出现(一个事实只在**一块**面上说),
|
|
@@ -1413,6 +1561,10 @@ export function view(state, sessionId) {
|
|
|
1413
1561
|
/** 从没被走过的等级(派生):面板据此说清「这一级是跳上来的」。 */
|
|
1414
1562
|
untouchedLevels: hypothesis.untouchedLevels,
|
|
1415
1563
|
version: hypothesis.version,
|
|
1564
|
+
/** 断言随假设走(未升格):面板同画芯片(chip 同样在投影侧算好),标注「未升格」。 */
|
|
1565
|
+
assertions: Array.isArray(hypothesis.assertions)
|
|
1566
|
+
? hypothesis.assertions.map((assertion) => ({ ...assertion, chip: formatAssertion(state.lexicon, assertion) }))
|
|
1567
|
+
: null,
|
|
1416
1568
|
})),
|
|
1417
1569
|
},
|
|
1418
1570
|
/**
|
|
@@ -1542,6 +1694,11 @@ export function view(state, sessionId) {
|
|
|
1542
1694
|
evidenceIds: item.evidence,
|
|
1543
1695
|
path: item.path,
|
|
1544
1696
|
at: item.at,
|
|
1697
|
+
/**
|
|
1698
|
+
* **类型化断言**(可 null):事实的内容形态。面板用它画断言芯片——
|
|
1699
|
+
* 点开就地展开词条卡,这是「事实 ↔ 本体」那座桥的界面侧。
|
|
1700
|
+
*/
|
|
1701
|
+
assertions: Array.isArray(item.assertions) ? item.assertions : null,
|
|
1545
1702
|
/** 派生:收到过推翻证据(要复核)与人的审查决定(撤回 / 维持)。 */
|
|
1546
1703
|
refuted: item.refuted === true,
|
|
1547
1704
|
review: item.review ?? null,
|
|
@@ -1761,6 +1918,27 @@ export function renderCard(state) {
|
|
|
1761
1918
|
lines.push(`- 最近一条证据:${last.id} ${last.verdict}(${last.evaluator} · ${last.level})`)
|
|
1762
1919
|
}
|
|
1763
1920
|
if (state.facts.length > 0) lines.push(`- 已升格事实:${state.facts.length} 条`)
|
|
1921
|
+
/**
|
|
1922
|
+
* **领域词汇与类型化事实**的一句话读数。
|
|
1923
|
+
*
|
|
1924
|
+
* 为什么要进卡片:模型要靠它决定「这条结论能不能写成断言、要不要先登记词」。而**冲突**
|
|
1925
|
+
* 必须说出来、但不许自动处置——卡片只报数并指向那两条事实,撤不撤由人(或由证据)定。
|
|
1926
|
+
* 事实没有断言时也报一句:那是「这门语言还没用起来」的读数,不是错误。
|
|
1927
|
+
*/
|
|
1928
|
+
{
|
|
1929
|
+
const lexicon = derived.lexicon
|
|
1930
|
+
const typed = state.facts.filter((fact) => Array.isArray(fact.assertions) && fact.assertions.length > 0).length
|
|
1931
|
+
if (lexicon.terms.length > 0 || lexicon.predicates.length > 0) {
|
|
1932
|
+
lines.push(`- 领域词汇:${lexicon.terms.length} 个概念 · ${lexicon.predicates.length} 个谓词;已升格事实里 ${typed}/${state.facts.length} 条带类型化断言`)
|
|
1933
|
+
if (derived.lexiconIssues.some((issue) => issue.severity === 'warning')) lines.push(` · 词汇健康度有 ${derived.lexiconIssues.filter((issue) => issue.severity === 'warning').length} 条待看(悬空引用 / 成环;在面板「本体」里)`)
|
|
1934
|
+
}
|
|
1935
|
+
if (derived.conflicts.length > 0) {
|
|
1936
|
+
for (const conflict of derived.conflicts) {
|
|
1937
|
+
const sides = conflict.sides.map((side) => `${side.fact ?? '?'}(${side.value})`).join(' 对 ')
|
|
1938
|
+
lines.push(`- **冲突(${conflict.predicate} · ${conflict.subject})**:${sides}——两条都还没被撤回。它是读数不是裁决:要么用证据推翻一侧,要么由人撤回一侧;系统不替你选。`)
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1764
1942
|
// 结案时留的痕:未判的假设不是「没问题」,是「没看过」——结案之后也要看得见。
|
|
1765
1943
|
if (Array.isArray(goal?.unjudged) && goal.unjudged.length > 0) lines.push(`- 结案留痕:有 ${goal.unjudged.length} 条假设没有被任何证据触及(${goal.unjudged.join(', ')})`)
|
|
1766
1944
|
/**
|
package/lib/host.js
CHANGED
|
@@ -19,6 +19,7 @@ import { readdirSync, statSync } from 'node:fs'
|
|
|
19
19
|
import { join, relative, resolve, sep } from 'node:path'
|
|
20
20
|
import { z } from 'zod'
|
|
21
21
|
import { HUMAN_GATE_ACTIONS, HUMAN_GATE_MARK, MUTATION_KIND, STATE_VERSION, applyEvent, applyMutations, derive, emptyState, renderCard, view } from './fold.js'
|
|
22
|
+
import { describeDomainShelf, formatAssertion, validateAssertions, validatePredicate, validateTerm } from './domain-language.js'
|
|
22
23
|
import { install as installInvariants } from './invariant.js'
|
|
23
24
|
|
|
24
25
|
export const name = 'clearai-host'
|
|
@@ -394,6 +395,46 @@ export function apply(ctx) {
|
|
|
394
395
|
value: typeof request.value === 'string' ? request.value : null,
|
|
395
396
|
note: typeof request.note === 'string' ? request.note.slice(0, 200) : null,
|
|
396
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
* **本体四动词(人的通道)**:词条字段在 RPC 边界上只收表内的那几个、带长度上限——
|
|
400
|
+
* 表外的字段一律剥掉(不是拒:人门消息进日志,日志里不该出现没约定的形状)。
|
|
401
|
+
* 判据与模型工具**同一份**:校验用 `domain-language` 的纯函数,对当前词汇判,
|
|
402
|
+
* 不过就 400 并把问题清单带回界面——「点了报成功、账上一字未改」不许再出现。
|
|
403
|
+
*/
|
|
404
|
+
const ONTOLOGY_GATE_ACTIONS = ['register_term', 'register_predicate', 'revise_term', 'deprecate_entry']
|
|
405
|
+
if (ONTOLOGY_GATE_ACTIONS.includes(action)) {
|
|
406
|
+
const raw = request.entry ?? {}
|
|
407
|
+
const str = (key, cap = 300) => (typeof raw[key] === 'string' ? raw[key].slice(0, cap) : undefined)
|
|
408
|
+
detail.entry = {
|
|
409
|
+
id: str('id', 40),
|
|
410
|
+
label: str('label', 60),
|
|
411
|
+
gloss: str('gloss'),
|
|
412
|
+
basis: str('basis'),
|
|
413
|
+
parent: str('parent', 40),
|
|
414
|
+
domain: str('domain', 40),
|
|
415
|
+
reason: str('reason', 200),
|
|
416
|
+
unit: str('unit', 24),
|
|
417
|
+
aliases: Array.isArray(raw.aliases) ? raw.aliases.filter((item) => typeof item === 'string').slice(0, 8).map((item) => item.slice(0, 60)) : undefined,
|
|
418
|
+
functional: raw.functional === true ? true : undefined,
|
|
419
|
+
range:
|
|
420
|
+
raw.range !== null && typeof raw.range === 'object' && ['statement', 'quantity', 'formula', 'code', 'reference'].includes(String(raw.range.form))
|
|
421
|
+
? { form: String(raw.range.form), unit: typeof raw.range.unit === 'string' ? raw.range.unit.slice(0, 24) : undefined, term: typeof raw.range.term === 'string' ? raw.range.term.slice(0, 40) : undefined }
|
|
422
|
+
: undefined,
|
|
423
|
+
}
|
|
424
|
+
const lexicon = stateOf(sessionId).lexicon
|
|
425
|
+
let problems = []
|
|
426
|
+
if (action === 'register_term') problems = validateTerm(lexicon, detail.entry)
|
|
427
|
+
if (action === 'register_predicate') problems = validatePredicate(lexicon, detail.entry)
|
|
428
|
+
if (action === 'revise_term' || action === 'deprecate_entry') {
|
|
429
|
+
const id = detail.entry.id ?? ''
|
|
430
|
+
const known = [...(lexicon.terms ?? []), ...(lexicon.predicates ?? [])].find((item) => item.id === id)
|
|
431
|
+
if (known === undefined) problems = [`unknown_entry:词汇里没有这个条目:${id}`]
|
|
432
|
+
else if (action === 'deprecate_entry' && known.status === 'deprecated') problems = [`already_deprecated:${id} 已经是废止状态`]
|
|
433
|
+
else if ((detail.entry.reason ?? '') === '' || detail.entry.reason === undefined) problems = ['reason_required:这一步要写一句缘由']
|
|
434
|
+
else if (action === 'revise_term' && detail.entry.label === undefined && detail.entry.gloss === undefined && detail.entry.aliases === undefined) problems = ['nothing_to_revise:label / gloss / aliases 至少给一个']
|
|
435
|
+
}
|
|
436
|
+
if (problems.length > 0) return reply(400, { ok: false, error: 'entry_rejected', problems })
|
|
437
|
+
}
|
|
397
438
|
/**
|
|
398
439
|
* 技能名要先过**取值校验**,再进日志。
|
|
399
440
|
*
|
|
@@ -468,9 +509,13 @@ export function apply(ctx) {
|
|
|
468
509
|
? `人审查了被推翻的那条事实(${detail.value ?? '?'})后决定**撤回**它${detail.note === null ? '' : `,缘由:${detail.note}`}。`
|
|
469
510
|
: action === 'keep_fact'
|
|
470
511
|
? `人审查了被推翻的那条事实(${detail.value ?? '?'})后判定**证据不可靠,维持原事实**${detail.note === null ? '' : `,缘由:${detail.note}`}。`
|
|
512
|
+
: ONTOLOGY_GATE_ACTIONS.includes(action)
|
|
513
|
+
? `人在本体格里${action === 'register_term' ? `登记了概念「${detail.entry?.label ?? detail.entry?.id ?? '?'}」` : action === 'register_predicate' ? `登记了谓词「${detail.entry?.label ?? detail.entry?.id ?? '?'}」` : action === 'revise_term' ? `修订了「${detail.entry?.id ?? '?'}」的展示信息` : `废止了「${detail.entry?.id ?? '?'}」`}${detail.entry?.basis ? `,依据:${detail.entry.basis}` : ''}${detail.entry?.reason ? `,缘由:${detail.entry.reason}` : ''}。`
|
|
471
514
|
: '人在面板上做了一个动作。'
|
|
472
515
|
const followUp =
|
|
473
|
-
action
|
|
516
|
+
ONTOLOGY_GATE_ACTIONS.includes(action)
|
|
517
|
+
? '这条词汇变更已落账(`by:user`),与模型工具落的是同一本账、同一套判据;词汇货架会在下一拍同步'
|
|
518
|
+
: action === 'confirm_provisional'
|
|
474
519
|
? '这条确认已经落账(`by:user`);那道门随之消失,续跑可以继续'
|
|
475
520
|
: action === 'retract_fact' || action === 'keep_fact'
|
|
476
521
|
? '这个决定已经落账,并会写进 `clear/knowledge/facts/` 那一份(下一轮引用它之前先看那条记录)'
|
|
@@ -650,6 +695,31 @@ export function apply(ctx) {
|
|
|
650
695
|
const next = applyMutations(stateOf(sessionId), mutations)
|
|
651
696
|
return { state: next, card: renderCard(next), view: view(next) }
|
|
652
697
|
},
|
|
698
|
+
/**
|
|
699
|
+
* **领域语言层的判据**(值形状、引用存在、值域、同一事实自洽)与货架正文。
|
|
700
|
+
*
|
|
701
|
+
* 为什么由宿主半提供,而不是预设侧自己写一份:判据**只能有一份**。
|
|
702
|
+
* 预设侧的工具与这条路由要判的是同一件事,而两份实现必然漂成
|
|
703
|
+
* 「登记时放行、升格时拒绝」——那种不一致在界面上与「这条还没验」长得一模一样。
|
|
704
|
+
* 所以判据住在纯函数模块里,预设侧经这道门调用它。
|
|
705
|
+
*/
|
|
706
|
+
domain: {
|
|
707
|
+
validateTerm: (sessionId, draft) => validateTerm(stateOf(sessionId).lexicon, draft),
|
|
708
|
+
validatePredicate: (sessionId, draft) => validatePredicate(stateOf(sessionId).lexicon, draft),
|
|
709
|
+
validateAssertions: (sessionId, assertions) => validateAssertions(stateOf(sessionId).lexicon, assertions),
|
|
710
|
+
/**
|
|
711
|
+
* 货架正文。带 `mutations` 时按**这一步之后**的样子渲染——
|
|
712
|
+
* 工具在返回前就把货架写好,读的人不必等下一回合。
|
|
713
|
+
*/
|
|
714
|
+
renderShelf: (sessionId, mutations = []) => {
|
|
715
|
+
const state = applyMutations(stateOf(sessionId), Array.isArray(mutations) ? mutations : [])
|
|
716
|
+
const next = derive(state)
|
|
717
|
+
// 在途命题也传进去:词汇刚立起来时「引用 0」会让人以为没人用,而断言已经在假设上了。
|
|
718
|
+
return describeDomainShelf(state.lexicon, next.factRows, next.hypotheses)
|
|
719
|
+
},
|
|
720
|
+
/** 一条断言的一行人话(货架 / 卡片 / 查询共用同一句话,免得三处各写一套)。 */
|
|
721
|
+
format: (sessionId, assertion) => formatAssertion(stateOf(sessionId).lexicon, assertion),
|
|
722
|
+
},
|
|
653
723
|
}),
|
|
654
724
|
'clearai: read facade',
|
|
655
725
|
)
|
package/package.json
CHANGED
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
|
|
112
112
|
# ── 认识论内核(本预设的灵魂) ───────────────────────────────────────────────
|
|
113
113
|
|
|
114
|
-
#
|
|
114
|
+
# 29 件意图工具(目标 2 + 计划 8 + 世界线 6 + 侦察 2 + 外脑 2 + 账本 2 + 领域语言 7,见 `MECHANISM_TOOLS`)
|
|
115
115
|
# + 一个 guard + 每回合派生的运行态卡 + 只增不删的台账 + 面板数据路由。
|
|
116
116
|
# 逐条对照 docs/loop-philosophy.md 的五条哲学,见 ./plugins/clearai-kernel.js 的文件头。
|
|
117
117
|
#
|
|
@@ -188,8 +188,8 @@
|
|
|
188
188
|
# 当场抛错——未知机制 / 未知工具 / 未知段 / 已关机制却仍列着它的工具,四种错法都在装配期炸,
|
|
189
189
|
# 而不是静默少装一件工具、等某一轮才发现。
|
|
190
190
|
#
|
|
191
|
-
# 这里只写**机制开关**;tools / sections 缺省 = 目录全量(
|
|
192
|
-
# 同一时刻
|
|
191
|
+
# 这里只写**机制开关**;tools / sections 缺省 = 目录全量(29 件意图工具、24 段提示词定义、
|
|
192
|
+
# 同一时刻 23 段在场——澄清协议那一段由 autonomy 在两套措辞里收敛)。
|
|
193
193
|
# 这些数字不靠人眼维持:`node tools/verify-truth-table.mjs` 会拿代码算出来的数核对它们。
|
|
194
194
|
# 要裁剪就把 tools 或 sections 显式写出来:
|
|
195
195
|
# tools: [SetGoal, CreatePlan, AdvancePlan, ...] # 名字必须都在工具目录里
|