lume-dsh-plugin 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -5
- package/lib/core/ledger.js +211 -0
- package/lib/core/signals.js +64 -0
- package/lib/host/methods.js +64 -0
- package/lib/host/project.js +222 -0
- package/lib/host/reflection.js +27 -6
- package/lib/host/rpc.js +15 -0
- package/lib/host/session-runtime.js +10 -0
- package/lib/host/triggers.js +133 -0
- package/lib/index.js +358 -16
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -38,6 +38,11 @@ import { appendLumeLog } from "./host/diag.js";
|
|
|
38
38
|
import { advancePhase, buildAlignmentCorrection, buildCasualDirective, buildCompactionNotice, buildInteractionDirective, buildLongSessionGuard, buildSessionAnchor, buildTaskPhaseDirective, buildToolFailureNotice, classifyInteraction, isUserAuthored, taskPhaseForMode } from "./host/protocol.js";
|
|
39
39
|
import { buildDocumentDirective, probeDocumentCapabilities } from "./host/documents.js";
|
|
40
40
|
import { REASONING_MODEL_RE, TASK_SIGNAL_RE, selectStableThinkingProtocol } from "./host/thinking.js";
|
|
41
|
+
import { normalizeChange, normalizeContract, normalizeHypothesis, normalizeProjectFact, projectKeyOf, renderChangeLedger, renderContract, renderHypotheses, renderProjectFacts } from "./core/ledger.js";
|
|
42
|
+
import { classifyTool, readResultSignals } from "./core/signals.js";
|
|
43
|
+
import { buildContractMethodDirective, buildDocumentMethodDirective, buildImpactDirective, buildStructureHint, composeBlocks } from "./host/methods.js";
|
|
44
|
+
import { LUME_PROJECT_SPEC, ProjectStore } from "./host/project.js";
|
|
45
|
+
import { DEFAULT_TRIGGER_THRESHOLDS, applyToolSignal, applyVerifyOutcome, cooldownOk, evaluateToolTrigger, evaluateTurnTrigger } from "./host/triggers.js";
|
|
41
46
|
/** schemastery → domainTable 形参的桥接(与 identity.ts 同款)。 */
|
|
42
47
|
const recordSchema = zodLike;
|
|
43
48
|
/** 会话人设选择的持久层(键 = sessionId)。 */
|
|
@@ -186,6 +191,35 @@ export function apply(ctx, config = {}) {
|
|
|
186
191
|
}
|
|
187
192
|
})();
|
|
188
193
|
void reflectionReady.then((s) => { reflectionStore = s; });
|
|
194
|
+
// ── 项目域:任务契约 / 改动台账 / 假设台账 / 项目知识(失败降级为无载具功能)──
|
|
195
|
+
const projectMemoryOn = config.projectMemory ?? true;
|
|
196
|
+
const behaviorTriggersOn = config.behaviorTriggers ?? true;
|
|
197
|
+
const triggerThresholds = {
|
|
198
|
+
...DEFAULT_TRIGGER_THRESHOLDS,
|
|
199
|
+
inspectStreak: config.triggerInspectStreak ?? DEFAULT_TRIGGER_THRESHOLDS.inspectStreak,
|
|
200
|
+
changeStreak: config.triggerChangeStreak ?? DEFAULT_TRIGGER_THRESHOLDS.changeStreak,
|
|
201
|
+
deadPathFails: config.triggerDeadPathFails ?? DEFAULT_TRIGGER_THRESHOLDS.deadPathFails,
|
|
202
|
+
};
|
|
203
|
+
let project = null;
|
|
204
|
+
const projectReady = (async () => {
|
|
205
|
+
if (!projectMemoryOn)
|
|
206
|
+
return null;
|
|
207
|
+
try {
|
|
208
|
+
const domain = await ctx.storageDomain.open(LUME_PROJECT_SPEC);
|
|
209
|
+
ctx.effect(() => async () => { await domain.close(); }, "lume: close project domain");
|
|
210
|
+
return new ProjectStore({
|
|
211
|
+
contract: domain.table("contract"),
|
|
212
|
+
ledger: domain.table("ledger"),
|
|
213
|
+
hypotheses: domain.table("hypotheses"),
|
|
214
|
+
facts: domain.table("facts"),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
ctx.logger?.warn?.("lume: 项目域不可用,任务契约/台账/项目知识降级", error);
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
})();
|
|
222
|
+
void projectReady.then((s) => { project = s; });
|
|
189
223
|
const registry = new PersonaRegistry(builtins, () => identity);
|
|
190
224
|
ctx.logger?.warn?.(`lume: 已加载(builtins=${Object.keys(builtins).join(",") || "空!"},assets=${assetsDir})`);
|
|
191
225
|
ctx.logger?.warn?.(`lume: llmRoute 初始化策略:agentDefaultModel → settings → 回退`);
|
|
@@ -511,6 +545,9 @@ export function apply(ctx, config = {}) {
|
|
|
511
545
|
st.toolCalls++;
|
|
512
546
|
if (st.interactionMode === "execute")
|
|
513
547
|
st.taskPhase = advancePhase(st.taskPhase, "execute");
|
|
548
|
+
// 行为类别在调用阶段记账(连击),成败到结果阶段才结算。
|
|
549
|
+
st.toolKind = classifyTool(event.data?.name);
|
|
550
|
+
applyToolSignal(st.triggerCounters, st.toolKind, null);
|
|
514
551
|
break;
|
|
515
552
|
}
|
|
516
553
|
case "tool/result": {
|
|
@@ -526,6 +563,30 @@ export function apply(ctx, config = {}) {
|
|
|
526
563
|
st.toolSuccesses++;
|
|
527
564
|
if (st.interactionMode === "execute")
|
|
528
565
|
st.taskPhase = advancePhase(st.taskPhase, unknownResult || explicitError ? "diagnose" : "verify");
|
|
566
|
+
// 行为信号 → 计数器 → 触发器提醒。提醒只在这一步之后可见(尾部快照),
|
|
567
|
+
// 且每类触发器有轮级冷却:提示一多就变噪音,模型会学会忽略。
|
|
568
|
+
const signals = readResultSignals(resultText, explicitError);
|
|
569
|
+
applyVerifyOutcome(st.triggerCounters, st.toolKind, signals);
|
|
570
|
+
if (behaviorTriggersOn) {
|
|
571
|
+
const fire = evaluateToolTrigger(st.triggerCounters, {
|
|
572
|
+
turnIndex: st.turnIndex,
|
|
573
|
+
isTask: isTaskQuery(st),
|
|
574
|
+
diagnosing: st.interactionMode === "diagnosis",
|
|
575
|
+
hasContract: contractOf(sid) !== null,
|
|
576
|
+
hasHypotheses: hypothesesOf(sid).length > 0,
|
|
577
|
+
hypothesesTouched: st.hypothesesTouched,
|
|
578
|
+
}, triggerThresholds);
|
|
579
|
+
if (fire && cooldownOk(st.triggerFiredAt[fire.id], st.turnIndex)) {
|
|
580
|
+
st.triggerFiredAt[fire.id] = st.turnIndex;
|
|
581
|
+
st.triggerNudge = fire.text;
|
|
582
|
+
ctx.logger?.warn?.(`lume: [${sid}] 行为触发器 ${fire.id}(steps=${st.triggerCounters.steps},inspect=${st.triggerCounters.inspectStreak},mutate=${st.triggerCounters.mutateStreak},verifyFail=${st.triggerCounters.verifyFailStreak})`);
|
|
583
|
+
// 环境性死路自动落成项目知识:下次会话不必重踩。
|
|
584
|
+
if (fire.id === "dead-path" && signals.env && project) {
|
|
585
|
+
const key = projectKeyFor(sid, session);
|
|
586
|
+
void project.addFact(key, normalizeProjectFact({ kind: "deadend", text: `本环境验证受阻(${st.triggerCounters.verifyFailStreak} 次连续失败,环境/依赖类):换降级阶梯,不要重复同一命令` }, Date.now()), (candidate, existing) => existing.some((fact) => fact.text === candidate));
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
529
590
|
break;
|
|
530
591
|
}
|
|
531
592
|
case "compaction/summary": {
|
|
@@ -546,6 +607,37 @@ export function apply(ctx, config = {}) {
|
|
|
546
607
|
st.switchGreetingPending = false;
|
|
547
608
|
if (st.switchTurn !== null && st.turnIndex - st.switchTurn >= boundaryTurns)
|
|
548
609
|
st.switchTurn = null;
|
|
610
|
+
// ── 行为触发器(轮边界)──
|
|
611
|
+
// 连击按轮清零:新一轮是新请求,上一轮的「撒网」不该继续累加;死路连击
|
|
612
|
+
// 跨轮保留(同一环境不可用是会话级事实)。上轮的提醒到这里失效。
|
|
613
|
+
st.triggerCounters.inspectStreak = 0;
|
|
614
|
+
st.triggerCounters.mutateStreak = 0;
|
|
615
|
+
st.triggerNudge = null;
|
|
616
|
+
st.turnNudge = null;
|
|
617
|
+
st.hypothesesTouched = false;
|
|
618
|
+
if (behaviorTriggersOn && project) {
|
|
619
|
+
const fire = evaluateTurnTrigger({
|
|
620
|
+
turnIndex: st.turnIndex,
|
|
621
|
+
hasContract: contractOf(sid) !== null,
|
|
622
|
+
compactionTurn: st.compaction?.turnIndex ?? null,
|
|
623
|
+
lastDriftTurn: st.lastDriftTurn,
|
|
624
|
+
counters: st.triggerCounters,
|
|
625
|
+
knowledgePrompted: st.knowledgePrompted,
|
|
626
|
+
}, triggerThresholds);
|
|
627
|
+
if (fire && cooldownOk(st.triggerFiredAt[fire.id], st.turnIndex)) {
|
|
628
|
+
st.triggerFiredAt[fire.id] = st.turnIndex;
|
|
629
|
+
if (fire.id === "criteria-drift") {
|
|
630
|
+
// 契约对账用「交付口径」渲染原始判据:防判据随进展漂移。
|
|
631
|
+
st.lastDriftTurn = st.turnIndex;
|
|
632
|
+
st.turnNudge = renderContract(contractOf(sid), true);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
st.knowledgePrompted = true;
|
|
636
|
+
st.turnNudge = fire.text;
|
|
637
|
+
}
|
|
638
|
+
ctx.logger?.warn?.(`lume: [${sid}] 轮触发器 ${fire.id}(turn=${st.turnIndex})`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
549
641
|
// 低成本会话内纠偏:只处理明确的错误/失败信号,且要求连续轮次用户请求相同。
|
|
550
642
|
const failed = /失败|报错|错误|exception|traceback|cannot|unable|permission denied|timed out|找不到|不存在/i.test(st.assistantText);
|
|
551
643
|
const queryKey = st.userText.trim().replace(/\s+/g, " ").slice(0, 240);
|
|
@@ -602,6 +694,8 @@ export function apply(ctx, config = {}) {
|
|
|
602
694
|
const st = runtime.get(sid);
|
|
603
695
|
const turns = [...st.recentTurns];
|
|
604
696
|
runtime.delete(sid);
|
|
697
|
+
// 任务载具是会话态:任务结束即无意义,清掉避免无界增长(项目知识在另一张表,不受影响)。
|
|
698
|
+
void projectReady.then((store) => store?.clearSession(sid));
|
|
605
699
|
// 反思日志:会话结束后空闲时间跑一次小模型,零用户感知 token。
|
|
606
700
|
// 历史不够长(< 4 条消息)或路由不可用时静默跳过。
|
|
607
701
|
if (reflectionEnabled && turns.length >= 4) {
|
|
@@ -620,10 +714,89 @@ export function apply(ctx, config = {}) {
|
|
|
620
714
|
if (!score)
|
|
621
715
|
return;
|
|
622
716
|
await store.log(sid, score);
|
|
623
|
-
ctx.logger?.warn?.(`lume: 反思日志 ${sid} context=${score.context} planning=${score.planning} verification=${score.verification} review=${score.review}「${score.note}」`);
|
|
717
|
+
ctx.logger?.warn?.(`lume: 反思日志 ${sid} context=${score.context} planning=${score.planning} verification=${score.verification} review=${score.review} diagnosis=${score.diagnosis}「${score.note}」`);
|
|
624
718
|
})();
|
|
625
719
|
}
|
|
626
720
|
}), "lume: session disposal");
|
|
721
|
+
// ── 载具与项目知识的读取入口(事件处理器 / 工具 / 注入三处共用)──
|
|
722
|
+
/** 项目键:优先取会话工作目录(跨会话共享同一仓库的知识)。 */
|
|
723
|
+
function projectKeyFor(sid, source) {
|
|
724
|
+
const st = runtime.get(sid);
|
|
725
|
+
if (st.projectKey)
|
|
726
|
+
return st.projectKey;
|
|
727
|
+
// 三种调用来源:提示词 context({agent:{session}})、工具 exec({agent:{session}})、
|
|
728
|
+
// 会话事件(session 本身)。统一取到 session 再读 cwd。
|
|
729
|
+
const session = source?.agent?.session ?? source?.session ?? source;
|
|
730
|
+
const cwd = session?.cwd ?? "";
|
|
731
|
+
const key = projectKeyOf(cwd);
|
|
732
|
+
// 只有拿到真实工作目录才缓存:否则一次无 cwd 的调用会把 "unknown" 固化下来。
|
|
733
|
+
if (cwd)
|
|
734
|
+
st.projectKey = key;
|
|
735
|
+
return key;
|
|
736
|
+
}
|
|
737
|
+
function isTaskQuery(st) {
|
|
738
|
+
return TASK_SIGNAL_RE.test(st.intent?.text ?? st.userText ?? "");
|
|
739
|
+
}
|
|
740
|
+
function contractOf(sid) {
|
|
741
|
+
return project?.getContract(sid) ?? null;
|
|
742
|
+
}
|
|
743
|
+
function changesOf(sid) {
|
|
744
|
+
return project?.getChanges(sid) ?? [];
|
|
745
|
+
}
|
|
746
|
+
function hypothesesOf(sid) {
|
|
747
|
+
return project?.getHypotheses(sid) ?? [];
|
|
748
|
+
}
|
|
749
|
+
function factsOf(sid, context) {
|
|
750
|
+
return project ? project.getFacts(projectKeyFor(sid, context)) : [];
|
|
751
|
+
}
|
|
752
|
+
/** 环境里是否有符号级结构分析工具:有就让模型用它替代通篇 read。 */
|
|
753
|
+
function structureToolName(context) {
|
|
754
|
+
try {
|
|
755
|
+
const schemas = ctx.get("tools")?.schemas?.(context?.agent);
|
|
756
|
+
if (!Array.isArray(schemas))
|
|
757
|
+
return null;
|
|
758
|
+
for (const schema of schemas) {
|
|
759
|
+
const name = String(schema?.name ?? "");
|
|
760
|
+
if (/analy|tree|symbol|lsp|reference|code_map|outline/i.test(name))
|
|
761
|
+
return name;
|
|
762
|
+
}
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* 任务载具块:契约 / 改动台账 / 假设台账 / 项目知识 + 方法块 + 触发器提醒。
|
|
771
|
+
*
|
|
772
|
+
* 全部落在尾部快照(易变层)——这正是「载具」现在才做得起的原因:0.6.2 之前每步
|
|
773
|
+
* 注入一份会变的状态等于每步作废整段前缀,而快照只在内容变化时才付费(实测 58 步
|
|
774
|
+
* 只产生 9 条快照)。顺序上把「此刻最该做的一件事」(触发器提醒)放在最后。
|
|
775
|
+
*/
|
|
776
|
+
function carrierBlocks(sid, context, st, query, mode) {
|
|
777
|
+
if (!projectMemoryOn)
|
|
778
|
+
return [];
|
|
779
|
+
const isTask = TASK_SIGNAL_RE.test(query);
|
|
780
|
+
const contract = contractOf(sid);
|
|
781
|
+
const changes = changesOf(sid);
|
|
782
|
+
const docDirective = buildDocumentDirective({ query, capabilities: probeDocumentCapabilities(ctx.get("tools"), context?.agent) });
|
|
783
|
+
return [
|
|
784
|
+
// 契约:有就回显(交付轮切成对账口径),没有且是任务轮就先教它写一份。
|
|
785
|
+
{ text: renderContract(contract, st.taskPhase === "deliver") },
|
|
786
|
+
{ text: !contract && isTask ? buildContractMethodDirective() : null, droppable: true },
|
|
787
|
+
// 台账与假设:存在就回显——让模型「看见」自己的计划,而不是记在脑子里。
|
|
788
|
+
{ text: changes.length > 0 ? renderChangeLedger(changes) : null },
|
|
789
|
+
{ text: renderHypotheses(hypothesesOf(sid)) },
|
|
790
|
+
// 项目知识:只在与项目相关的轮次出现(闲聊不该背仓库事实)。
|
|
791
|
+
{ text: isTask ? renderProjectFacts(factsOf(sid, context)) : null, droppable: true },
|
|
792
|
+
// 方法块:按任务形态出现;文档方法论只在判定为文档任务时出现。
|
|
793
|
+
{ text: isTask && mode !== "question" ? buildImpactDirective() : null, droppable: true },
|
|
794
|
+
{ text: docDirective ? buildDocumentMethodDirective() : null },
|
|
795
|
+
{ text: mode === "execute" || mode === "diagnosis" ? buildStructureHint(structureToolName(context)) : null, droppable: true },
|
|
796
|
+
{ text: st.turnNudge },
|
|
797
|
+
{ text: st.triggerNudge },
|
|
798
|
+
];
|
|
799
|
+
}
|
|
627
800
|
// ── 模型可调用工具(主写入通道)──
|
|
628
801
|
// 工具 output schema 的 const 语义要求成功值恒为 { ok: true };失败一律抛错交由框架呈现。
|
|
629
802
|
// as const 让 defineTool 从字面量推断 O,三个工具共用同一份成功形状。
|
|
@@ -694,6 +867,152 @@ export function apply(ctx, config = {}) {
|
|
|
694
867
|
return { ok: true };
|
|
695
868
|
},
|
|
696
869
|
}));
|
|
870
|
+
// ── 任务载具工具(第二组写入通道)──
|
|
871
|
+
// 与人格工具一样是「模型主动调用、零额外 LLM 调用」,区别在写入对象:契约/台账/假设
|
|
872
|
+
// 属于当前任务(会话态),项目知识按工作目录跨会话累积。列表类参数统一用字符串
|
|
873
|
+
// 分隔(分号或换行),不引入数组 schema——省 schema token,也少一层校验风险。
|
|
874
|
+
const splitList = (value) => String(value ?? "")
|
|
875
|
+
.split(/[;;\n]/)
|
|
876
|
+
.map((item) => item.trim())
|
|
877
|
+
.filter(Boolean);
|
|
878
|
+
ctx.effect(() => {
|
|
879
|
+
ctx.tools.register(defineTool({
|
|
880
|
+
name: "lume_contract",
|
|
881
|
+
description: "写下或更新本任务的任务契约(需求量化的落点):目标、范围、数量、完成判据、非目标、待确认。任务型请求开工前调用一次;探索后回填实际数量;之后只传变化的字段即可(局部更新)。",
|
|
882
|
+
parameters: {
|
|
883
|
+
goal: { type: "string", description: "目标:一句话、可观察的结果" },
|
|
884
|
+
scope: { type: "string", description: "范围:路径/模块/章节,分号或换行分隔" },
|
|
885
|
+
expectCount: { type: "number", description: "预计数量(探索前先估)" },
|
|
886
|
+
actualCount: { type: "number", description: "实际数量(探索后回填)" },
|
|
887
|
+
criteria: { type: "string", description: "完成判据:可执行、可核对,分号或换行分隔" },
|
|
888
|
+
nonGoals: { type: "string", description: "非目标:明确不动的东西,分号分隔" },
|
|
889
|
+
open: { type: "string", description: "待确认:只列真正阻塞的(≤2 个),分号分隔" },
|
|
890
|
+
},
|
|
891
|
+
output: { schema: OK_OUTPUT_SCHEMA, render: () => [{ type: "text", text: "已记录任务契约" }] },
|
|
892
|
+
execute: async (args, exec) => {
|
|
893
|
+
if (!project)
|
|
894
|
+
throw new Error("lume project store is unavailable");
|
|
895
|
+
const sid = String(exec?.agent?.session?.id ?? "");
|
|
896
|
+
if (!sid)
|
|
897
|
+
throw new Error("lume_contract requires an active session");
|
|
898
|
+
const st = runtime.get(sid);
|
|
899
|
+
const normalized = normalizeContract({
|
|
900
|
+
goal: args.goal,
|
|
901
|
+
scope: splitList(args.scope),
|
|
902
|
+
expectCount: args.expectCount,
|
|
903
|
+
actualCount: args.actualCount,
|
|
904
|
+
criteria: splitList(args.criteria),
|
|
905
|
+
nonGoals: splitList(args.nonGoals),
|
|
906
|
+
open: splitList(args.open),
|
|
907
|
+
}, Date.now(), st.turnIndex);
|
|
908
|
+
const existing = project.getContract(sid);
|
|
909
|
+
if (existing) {
|
|
910
|
+
// 局部更新:未传的字段保持原值(回填数量时不该把判据清空)。
|
|
911
|
+
const patch = {};
|
|
912
|
+
if (args.goal !== undefined)
|
|
913
|
+
patch.goal = normalized.goal;
|
|
914
|
+
if (args.scope !== undefined)
|
|
915
|
+
patch.scope = normalized.scope;
|
|
916
|
+
if (args.expectCount !== undefined)
|
|
917
|
+
patch.expectCount = normalized.expectCount;
|
|
918
|
+
if (args.actualCount !== undefined)
|
|
919
|
+
patch.actualCount = normalized.actualCount;
|
|
920
|
+
if (args.criteria !== undefined)
|
|
921
|
+
patch.criteria = normalized.criteria;
|
|
922
|
+
if (args.nonGoals !== undefined)
|
|
923
|
+
patch.nonGoals = normalized.nonGoals;
|
|
924
|
+
if (args.open !== undefined)
|
|
925
|
+
patch.open = normalized.open;
|
|
926
|
+
await project.patchContract(sid, patch);
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
if (!normalized.goal)
|
|
930
|
+
throw new Error("lume_contract requires a goal on first write");
|
|
931
|
+
await project.setContract(sid, normalized);
|
|
932
|
+
}
|
|
933
|
+
return { ok: true };
|
|
934
|
+
},
|
|
935
|
+
}));
|
|
936
|
+
ctx.tools.register(defineTool({
|
|
937
|
+
name: "lume_change",
|
|
938
|
+
description: "改动台账:记录/更新一处将要改或已改的位置(文件/符号/文档章节 → 改什么 → 怎么验 → 状态)。动手前先列计划项,改完推进状态;只推进状态时可只传 target + status。文档任务用章节名当 target,形成分节记账。",
|
|
939
|
+
parameters: {
|
|
940
|
+
target: { type: "string", required: true, description: "目标位置:文件路径 / 符号 / 文档章节" },
|
|
941
|
+
change: { type: "string", description: "改什么(一句话)" },
|
|
942
|
+
why: { type: "string", description: "为什么改(对齐契约的哪一条)" },
|
|
943
|
+
verify: { type: "string", description: "怎么验(命令 / 回读 / 对照)" },
|
|
944
|
+
status: { type: "string", description: "planned | done | verified | skipped" },
|
|
945
|
+
},
|
|
946
|
+
output: { schema: OK_OUTPUT_SCHEMA, render: () => [{ type: "text", text: "已更新改动台账" }] },
|
|
947
|
+
execute: async (args, exec) => {
|
|
948
|
+
if (!project)
|
|
949
|
+
throw new Error("lume project store is unavailable");
|
|
950
|
+
const sid = String(exec?.agent?.session?.id ?? "");
|
|
951
|
+
if (!sid)
|
|
952
|
+
throw new Error("lume_change requires an active session");
|
|
953
|
+
const target = String(args.target ?? "").trim();
|
|
954
|
+
if (!target)
|
|
955
|
+
throw new Error("lume_change requires a target");
|
|
956
|
+
const status = args.status;
|
|
957
|
+
const allowed = status === "planned" || status === "done" || status === "verified" || status === "skipped" ? status : undefined;
|
|
958
|
+
if (args.change === undefined && allowed !== undefined) {
|
|
959
|
+
const hit = await project.setChangeStatus(sid, target, allowed);
|
|
960
|
+
if (!hit)
|
|
961
|
+
throw new Error(`lume_change: no ledger entry for ${target}`);
|
|
962
|
+
return { ok: true };
|
|
963
|
+
}
|
|
964
|
+
const item = normalizeChange({ target, change: args.change, why: args.why, verify: args.verify, status: allowed }, Date.now());
|
|
965
|
+
if (!item)
|
|
966
|
+
throw new Error("lume_change requires target and change");
|
|
967
|
+
await project.upsertChange(sid, item);
|
|
968
|
+
return { ok: true };
|
|
969
|
+
},
|
|
970
|
+
}));
|
|
971
|
+
ctx.tools.register(defineTool({
|
|
972
|
+
name: "lume_hypothesis",
|
|
973
|
+
description: "假设台账:记录一条正在验证的假设及其证据与状态(open/testing/confirmed/excluded)。排查类任务里每验证一次就更新状态;已排除的假设不要再重复尝试。",
|
|
974
|
+
parameters: {
|
|
975
|
+
text: { type: "string", required: true, description: "假设内容,一句话" },
|
|
976
|
+
evidence: { type: "string", description: "支持或推翻它的观察(含命令输出/时间戳摘要)" },
|
|
977
|
+
status: { type: "string", description: "open | testing | confirmed | excluded" },
|
|
978
|
+
},
|
|
979
|
+
output: { schema: OK_OUTPUT_SCHEMA, render: () => [{ type: "text", text: "已更新假设台账" }] },
|
|
980
|
+
execute: async (args, exec) => {
|
|
981
|
+
if (!project)
|
|
982
|
+
throw new Error("lume project store is unavailable");
|
|
983
|
+
const sid = String(exec?.agent?.session?.id ?? "");
|
|
984
|
+
if (!sid)
|
|
985
|
+
throw new Error("lume_hypothesis requires an active session");
|
|
986
|
+
const item = normalizeHypothesis({ text: args.text, evidence: args.evidence, status: args.status }, Date.now());
|
|
987
|
+
if (!item)
|
|
988
|
+
throw new Error("lume_hypothesis requires text");
|
|
989
|
+
await project.upsertHypothesis(sid, item);
|
|
990
|
+
runtime.get(sid).hypothesesTouched = true;
|
|
991
|
+
return { ok: true };
|
|
992
|
+
},
|
|
993
|
+
}));
|
|
994
|
+
ctx.tools.register(defineTool({
|
|
995
|
+
name: "lume_project_note",
|
|
996
|
+
description: "记录一条**稳定的项目事实**(按工作目录跨会话累积):构建/测试命令、模块数据流、仓库约定、或一条死路(试过但行不通的做法)。只记可复用、已验证的事实,不要记一次性进展。",
|
|
997
|
+
parameters: {
|
|
998
|
+
kind: { type: "string", required: true, description: "build | test | module | convention | deadend" },
|
|
999
|
+
text: { type: "string", required: true, description: "事实本身,一句话,≤200 字" },
|
|
1000
|
+
},
|
|
1001
|
+
output: { schema: OK_OUTPUT_SCHEMA, render: () => [{ type: "text", text: "已记入项目知识" }] },
|
|
1002
|
+
execute: async (args, exec) => {
|
|
1003
|
+
if (!project)
|
|
1004
|
+
throw new Error("lume project store is unavailable");
|
|
1005
|
+
const sid = String(exec?.agent?.session?.id ?? "");
|
|
1006
|
+
if (!sid)
|
|
1007
|
+
throw new Error("lume_project_note requires an active session");
|
|
1008
|
+
const fact = normalizeProjectFact({ kind: args.kind, text: args.text }, Date.now());
|
|
1009
|
+
if (!fact)
|
|
1010
|
+
throw new Error("lume_project_note requires text");
|
|
1011
|
+
await project.addFact(projectKeyFor(sid, { agent: exec?.agent }), fact, (candidate, existing) => existing.some((entry) => jaccard(entry.text, candidate) >= 0.7));
|
|
1012
|
+
return { ok: true };
|
|
1013
|
+
},
|
|
1014
|
+
}));
|
|
1015
|
+
}, "lume: carrier tools");
|
|
697
1016
|
}, "lume: persona tools");
|
|
698
1017
|
// ── 人设五段式注入 + 切换播报 ──
|
|
699
1018
|
/**
|
|
@@ -751,21 +1070,21 @@ export function apply(ctx, config = {}) {
|
|
|
751
1070
|
}).trim();
|
|
752
1071
|
// 易变的任务指令:路由、阶段、闲聊声明、长会话护栏、目标锚点、即时对齐、
|
|
753
1072
|
// 交付复核、压缩重锚、文档能力指引、失败纠偏、反思提醒——全部每步可变。
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
st.
|
|
761
|
-
st.
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
st.
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1073
|
+
// 载具与方法块(契约/台账/假设/项目知识/影响面/文档方法/触发器提醒)排在最后:
|
|
1074
|
+
// 它们是「此刻最该看的」,紧贴尾部注意力最强位;超预算时先丢可丢块(composeBlocks)。
|
|
1075
|
+
const thinkingRuntime = composeBlocks([
|
|
1076
|
+
{ text: buildInteractionDirective(mode) },
|
|
1077
|
+
{ text: buildTaskPhaseDirective(st.taskPhase) },
|
|
1078
|
+
{ text: buildCasualDirective(TASK_SIGNAL_RE.test(query)) },
|
|
1079
|
+
{ text: buildLongSessionGuard(st.turnIndex) },
|
|
1080
|
+
{ text: buildSessionAnchor(st.turnIndex, mode, query, st.recentTurns) },
|
|
1081
|
+
{ text: st.alignmentCorrection },
|
|
1082
|
+
{ text: st.postTurnReview },
|
|
1083
|
+
{ text: st.compaction ? buildCompactionNotice(st.compaction, st.turnIndex) : null },
|
|
1084
|
+
{ text: st.protocolCorrection },
|
|
1085
|
+
{ text: reflectionStore?.getFeedback() ?? null, droppable: true },
|
|
1086
|
+
...carrierBlocks(sid, context, st, query, mode),
|
|
1087
|
+
]);
|
|
769
1088
|
// 会话选择尚未就绪(启动竞态):只出任务协议,人设段留空——与旧实现一致,
|
|
770
1089
|
// 也避免把「尚未选择」误记成一次人设切换。
|
|
771
1090
|
if (!currentStore)
|
|
@@ -853,6 +1172,29 @@ export function apply(ctx, config = {}) {
|
|
|
853
1172
|
get distill() {
|
|
854
1173
|
return distillRunner;
|
|
855
1174
|
},
|
|
1175
|
+
getProjectState(sessionId) {
|
|
1176
|
+
// 诊断视图:任务载具 + 项目知识(供排查"模型到底看到了什么")。
|
|
1177
|
+
if (!project)
|
|
1178
|
+
return null;
|
|
1179
|
+
const st = runtime.get(sessionId);
|
|
1180
|
+
return {
|
|
1181
|
+
projectKey: st.projectKey,
|
|
1182
|
+
contract: project.getContract(sessionId),
|
|
1183
|
+
changes: project.getChanges(sessionId),
|
|
1184
|
+
hypotheses: project.getHypotheses(sessionId),
|
|
1185
|
+
facts: st.projectKey ? project.getFacts(st.projectKey) : [],
|
|
1186
|
+
triggers: { ...st.triggerCounters, fired: st.triggerFiredAt },
|
|
1187
|
+
};
|
|
1188
|
+
},
|
|
1189
|
+
async clearProjectFacts(sessionId) {
|
|
1190
|
+
if (!project)
|
|
1191
|
+
return false;
|
|
1192
|
+
const st = runtime.get(sessionId);
|
|
1193
|
+
if (!st.projectKey)
|
|
1194
|
+
return false;
|
|
1195
|
+
await project.clearFacts(st.projectKey);
|
|
1196
|
+
return true;
|
|
1197
|
+
},
|
|
856
1198
|
});
|
|
857
1199
|
ctx.effect(() => ctx.connection.rpc.handle(LUME_CHANNEL, async (endpoint, payload) => {
|
|
858
1200
|
currentStore ??= await storesReady;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lume-dsh-plugin",
|
|
3
|
-
"description": "微光 (Lume) — DSH Desktop
|
|
4
|
-
"version": "0.
|
|
3
|
+
"description": "微光 (Lume) — DSH Desktop 增强插件:给会话装上工程纪律与真实关系。纪律层约束「如何正确完成任务」——意图路由、阶段门控、真实工具证据、交付前复核、文档能力感知;方法层把量化需求、改动台账、假设台账与项目知识变成可检查的产出,并用行为触发器在轨迹上纠偏(撒网不收敛 / 连写不验 / 死路重撞);人设层塑造「以何种风格表达」——从聊天记录、小说、剧本、设定文档蒸馏具名角色,长期记忆与风格随对话演进。约束按需注入,闲聊不额外付 token。",
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|