dsh-completion-guard 0.5.0 → 0.5.2
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 +38 -0
- package/CHANGELOG.zh-CN.md +38 -0
- package/README.md +12 -8
- package/README.zh-CN.md +12 -8
- package/bin/dsh-completion-guard-host-lock.mjs +16 -0
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/index.js +2 -2
- package/dist/{domain-Cx8vpxSj.js → domain-RuX9l07o.js} +2616 -581
- package/dist/{index-Cd3wXBLi.d.ts → index-BTaiOcE-.d.ts} +491 -31
- package/dist/index.d.ts +3 -2
- package/dist/index.js +270 -18
- package/docs/ARCHITECTURE.md +7 -2
- package/docs/COMPATIBILITY.md +65 -5
- package/docs/HOST_LOCK_UPGRADE.md +42 -5
- package/docs/LOCAL_ACCEPTANCE.md +20 -2
- package/docs/PORTING_NOTES.md +22 -0
- package/docs/SEMANTIC_COMPATIBILITY.md +24 -0
- package/docs/distribution.md +12 -6
- package/docs/upstream-deltas.json +30 -4
- package/manifests/supported-host.v1.json +298 -89
- package/package.json +38 -32
|
@@ -614,7 +614,7 @@ const ORDERED_TEXT_RULES = [
|
|
|
614
614
|
["verify", /验证|确认|确保|\bverif(?:y|ies|ied|ying)\b|\bconfirm\b/i]
|
|
615
615
|
];
|
|
616
616
|
function semanticActionFromText(text) {
|
|
617
|
-
if (/^\s*(
|
|
617
|
+
if (/^\s*(?:验证|校验|确认|确保|核对|verif(?:y|ies|ied|ying)\b|confirm\b)/i.test(text)) return "verify";
|
|
618
618
|
for (const [action, pattern] of ORDERED_TEXT_RULES) if (pattern.test(text)) return action;
|
|
619
619
|
return "generic_run";
|
|
620
620
|
}
|
|
@@ -776,21 +776,1048 @@ function npmEscapedPackageName(packageId) {
|
|
|
776
776
|
}
|
|
777
777
|
|
|
778
778
|
//#endregion
|
|
779
|
-
//#region src/domain/
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
779
|
+
//#region src/domain/semantics.ts
|
|
780
|
+
/** A clause whose head verb demands a verification rather than a change. */
|
|
781
|
+
const ACCEPTANCE_LEAD = /^(?:验收|验证|确认|确保|核对|检查|verify|confirm|ensure|check)/i;
|
|
782
|
+
/**
|
|
783
|
+
* The contract kind a scope maps to. A prohibition and an acceptance keep their
|
|
784
|
+
* own lanes; everything else is a requirement. Acceptance is decided from the
|
|
785
|
+
* clause's own head verb, so "确保构建通过" stays an acceptance while a
|
|
786
|
+
* conditional or prohibition clause is never mislabelled.
|
|
787
|
+
*/
|
|
788
|
+
function kindOfScope(directive, body = "") {
|
|
789
|
+
if (directive === "prohibition") return "prohibition";
|
|
790
|
+
if (directive === "directive" && ACCEPTANCE_LEAD.test(body.trim())) return "acceptance";
|
|
791
|
+
return "requirement";
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Blank out inline-code spans while preserving every byte offset, so a caller
|
|
795
|
+
* can classify authority against masked text and still slice the original.
|
|
796
|
+
* Backticks are Markdown emphasis, but they are also how a log line or a
|
|
797
|
+
* command is quoted — and a quoted command is data, never an order.
|
|
798
|
+
*/
|
|
799
|
+
const MASK_CACHE = /* @__PURE__ */ new Map();
|
|
800
|
+
const MASK_CACHE_LIMIT = 64;
|
|
801
|
+
function maskCodeSpans(text) {
|
|
802
|
+
const cached = MASK_CACHE.get(text);
|
|
803
|
+
if (cached !== void 0) return cached;
|
|
804
|
+
const masked = computeMaskedSpans(text);
|
|
805
|
+
if (MASK_CACHE.size >= MASK_CACHE_LIMIT) MASK_CACHE.clear();
|
|
806
|
+
MASK_CACHE.set(text, masked);
|
|
807
|
+
return masked;
|
|
808
|
+
}
|
|
809
|
+
function computeMaskedSpans(text) {
|
|
810
|
+
const characters = text.split("");
|
|
811
|
+
let cursor = 0;
|
|
812
|
+
while (cursor < text.length) {
|
|
813
|
+
if (text[cursor] !== "`") {
|
|
814
|
+
cursor += 1;
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
const end = text.indexOf("`", cursor + 1);
|
|
818
|
+
if (end < 0) break;
|
|
819
|
+
for (let index = cursor; index <= end; index += 1) characters[index] = " ";
|
|
820
|
+
cursor = end + 1;
|
|
821
|
+
}
|
|
822
|
+
return characters.join("");
|
|
823
|
+
}
|
|
824
|
+
/** Actions Guard can name, shared with the command-surface manifest. */
|
|
825
|
+
/**
|
|
826
|
+
* The manifest's operation verbs. Each alternative is wrapped with word
|
|
827
|
+
* boundaries, so an English verb never matches inside another word ("check"
|
|
828
|
+
* inside "change"); a Chinese alternative is left alone because a Han character
|
|
829
|
+
* has no word boundary to assert.
|
|
830
|
+
*/
|
|
831
|
+
const CJK_VERBS = [
|
|
832
|
+
"创建",
|
|
833
|
+
"生成",
|
|
834
|
+
"新建",
|
|
835
|
+
"写入",
|
|
836
|
+
"修改",
|
|
837
|
+
"编辑",
|
|
838
|
+
"更改",
|
|
839
|
+
"读取",
|
|
840
|
+
"阅读",
|
|
841
|
+
"打开",
|
|
842
|
+
"验证",
|
|
843
|
+
"校验",
|
|
844
|
+
"确认",
|
|
845
|
+
"确保",
|
|
846
|
+
"检查",
|
|
847
|
+
"核对",
|
|
848
|
+
"运行",
|
|
849
|
+
"执行",
|
|
850
|
+
"拉取",
|
|
851
|
+
"同步",
|
|
852
|
+
"更新",
|
|
853
|
+
"下载",
|
|
854
|
+
"安装",
|
|
855
|
+
"部署",
|
|
856
|
+
"上传",
|
|
857
|
+
"提交",
|
|
858
|
+
"推送",
|
|
859
|
+
"发布",
|
|
860
|
+
"升级",
|
|
861
|
+
"重启",
|
|
862
|
+
"重新启动",
|
|
863
|
+
"重载",
|
|
864
|
+
"合并",
|
|
865
|
+
"继续",
|
|
866
|
+
"撤销",
|
|
867
|
+
"删除"
|
|
868
|
+
];
|
|
869
|
+
/** Every CJK action word, longest first so 重新启动 wins over 新. */
|
|
870
|
+
const CJK_VERB_PATTERN = `(?:${[...CJK_VERBS].sort((a, b) => b.length - a.length).join("|")})`;
|
|
871
|
+
/** The same words as literal strings, for exact scanning without regex escapes. */
|
|
872
|
+
const CJK_VERB_WORDS = [...CJK_VERBS].sort((a, b) => b.length - a.length);
|
|
873
|
+
const ACTION_VERB_PATTERN = `(?:${COMMAND_SURFACE_MANIFEST.operationVerbs.map((entry) => entry.pattern.split("|").map((alternative) => /^[A-Za-z]/.test(alternative.trim()) ? `\\b${alternative.trim()}\\b` : alternative.trim()).join("|")).join("|")}|${CJK_VERB_PATTERN})`;
|
|
874
|
+
const ACTION_VERB = new RegExp(ACTION_VERB_PATTERN, "i");
|
|
875
|
+
/** Operation verbs beyond the guard action surface (local work and diagnosis). */
|
|
876
|
+
const WORK_VERB = /创建|生成|新建|写入|修改|编辑|运行|执行|编写|撰写|部署|安装|升级|提交|下载|上传|拉取|同步|重启|测试|检查|验证|确认|修复|更新|清理|整理|记录|构建|编译|重构|迁移|删除|回滚|发布|推送|合并|继续|恢复|还原|回滚|实现|\b(?:build|create|write|modify|change|edit|run|fix|update|install|push|publish|test|verify|check|commit|deploy|migrate|remove|delete|restart|revert|refactor|inspect|fetch|pull|implement)\b/i;
|
|
877
|
+
/** Explanatory framings: an action named afterwards is an object, not an order. */
|
|
878
|
+
const EXPLAIN_VERB = /解释|说明|讲解|介绍|阐述|分析|讨论|描述|科普|什么意思|是什么意思|有什么(?:作用|影响|区别)|\bexplain\b|\bdescribe\b|\bclarify\b|\bwhat\s+does\b|\bwhat\s+is\b|\bhow\s+does\b|\bmeaning\s+of\b/i;
|
|
879
|
+
/** Interrogative framings that make a scope a question rather than an order. */
|
|
880
|
+
const QUESTION_SCOPE = /[??]|是否|是不是|为什么|为何|怎么|如何|什么|哪些|哪一种|能否|可否|要不要|该不该|由谁|是谁|\b(?:whether|which|why|should|could|would)\b/i;
|
|
881
|
+
const NEGATORS = [
|
|
882
|
+
["不要", "zh"],
|
|
883
|
+
["不用", "zh"],
|
|
884
|
+
["不得", "zh"],
|
|
885
|
+
["不许", "zh"],
|
|
886
|
+
["不准", "zh"],
|
|
887
|
+
["不能", "zh"],
|
|
888
|
+
["不必", "zh"],
|
|
889
|
+
["无需", "zh"],
|
|
890
|
+
["毋须", "zh"],
|
|
891
|
+
["勿", "zh"],
|
|
892
|
+
["别", "zh"],
|
|
893
|
+
["甭", "zh"],
|
|
894
|
+
["不", "zh"],
|
|
895
|
+
["do not", "en"],
|
|
896
|
+
["does not", "en"],
|
|
897
|
+
["did not", "en"],
|
|
898
|
+
["don't", "en"],
|
|
899
|
+
["doesn't", "en"],
|
|
900
|
+
["won't", "en"],
|
|
901
|
+
["can't", "en"],
|
|
902
|
+
["cannot", "en"],
|
|
903
|
+
["never", "en"],
|
|
904
|
+
["avoid", "en"],
|
|
905
|
+
["without", "en"],
|
|
906
|
+
["no longer", "en"]
|
|
907
|
+
];
|
|
908
|
+
/** Characters that end one coordinated scope and may begin the next. */
|
|
909
|
+
const SEPARATORS = new Set([
|
|
910
|
+
",",
|
|
911
|
+
",",
|
|
912
|
+
"、",
|
|
913
|
+
";",
|
|
914
|
+
";",
|
|
915
|
+
"。",
|
|
916
|
+
".",
|
|
917
|
+
"!",
|
|
918
|
+
"!",
|
|
919
|
+
"?",
|
|
920
|
+
"?",
|
|
921
|
+
":",
|
|
922
|
+
":",
|
|
923
|
+
"\n",
|
|
924
|
+
"\r"
|
|
925
|
+
]);
|
|
926
|
+
const CONNECTORS = [
|
|
927
|
+
"但是",
|
|
928
|
+
"不过",
|
|
929
|
+
"然而",
|
|
930
|
+
"同时",
|
|
931
|
+
"并且",
|
|
932
|
+
"而且",
|
|
933
|
+
"以及",
|
|
934
|
+
"然后",
|
|
935
|
+
"接着",
|
|
936
|
+
"而是",
|
|
937
|
+
"但",
|
|
938
|
+
"而",
|
|
939
|
+
"也",
|
|
940
|
+
"并",
|
|
941
|
+
"且",
|
|
942
|
+
"又",
|
|
943
|
+
"再",
|
|
944
|
+
"就",
|
|
945
|
+
"则"
|
|
946
|
+
];
|
|
947
|
+
const ENGLISH_CONNECTORS = [
|
|
948
|
+
"but",
|
|
949
|
+
"and",
|
|
950
|
+
"then",
|
|
951
|
+
"also",
|
|
952
|
+
"however",
|
|
953
|
+
"yet"
|
|
954
|
+
];
|
|
955
|
+
const CONNECTOR_PATTERN = `(?:${[...CONNECTORS].sort((a, b) => b.length - a.length).join("|")}|${ENGLISH_CONNECTORS.join("|")})`;
|
|
956
|
+
const CONTINUATION_AFTER_SEPARATOR = new RegExp(`^\\s*${CONNECTOR_PATTERN}`, "i");
|
|
957
|
+
/**
|
|
958
|
+
* Instruction openings that make the text after a bare conjunction its own
|
|
959
|
+
* clause. "并检查 GUI 效果" is a second instruction; "并在本地仓库记录" is
|
|
960
|
+
* handled separately as a locative, and anything else stays one object list.
|
|
961
|
+
*/
|
|
962
|
+
const CROSS_CLAUSE_HEAD = /^\s*(?:检查|查看|确认|验证|测试|运行|执行|安装|应用|更新|升级|记录|提交|推送|发布|部署|重启|重新启动|创建|新建|生成|修改|编辑|拉取|抓取|删除|回滚|清理|整理|实现|完成)/u;
|
|
963
|
+
/**
|
|
964
|
+
* Openings that make the text after a conjunction a DISTINCT instruction rather
|
|
965
|
+
* than the second half of one action. "并确认全部通过" completes the action
|
|
966
|
+
* before it, so 确认 is deliberately absent here.
|
|
967
|
+
*/
|
|
968
|
+
const DISTINCT_CLAUSE_HEAD = /^\s*(?:检查|查看|测试|验证|运行|执行|安装|应用|更新|升级|提交|推送|发布|部署|重启|重新启动|创建|新建|生成|修改|编辑|拉取|抓取|删除|回滚|清理|整理)/u;
|
|
969
|
+
/**
|
|
970
|
+
* A place clause that follows a coordinating conjunction: the shape of "并在
|
|
971
|
+
* 本地仓库记录", where the conjunction joins an action to where it happens
|
|
972
|
+
* rather than to a second action.
|
|
973
|
+
*/
|
|
974
|
+
const LOCATIVE_CLAUSE = /^\s*在.{1,40}?(?:记录|保存|写入)$/u;
|
|
975
|
+
const USER_ACTOR_PATTERNS = [
|
|
976
|
+
/(?:由|让|给|请)\s*(?:我|本人|我们)/,
|
|
977
|
+
/(?:我|我们)(?:自己|本人)?\s*(?:来|去|会|将|要)?\s*(?:手动|亲自|自行)?\s*(?:重启|重新启动|升级|安装|更新|执行|运行|操作|完成|处理|部署|发布|推送|合并|确认|登录|审批|提供|准备|搭建|检查|验证|测试)/,
|
|
978
|
+
/\bI(?:'ll| will| am going to| myself)\b/i,
|
|
979
|
+
/\b(?:on my own|by myself)\b/i
|
|
980
|
+
];
|
|
981
|
+
const AGENT_ACTOR_PATTERNS = [
|
|
982
|
+
/(?:由|让|请)\s*(?:你|您|助手|代理)/,
|
|
983
|
+
/(?:你|您)(?:来|去|会|将|要|负责|自己)/,
|
|
984
|
+
/\byou (?:should|must|need to|will|are to)\b/i
|
|
985
|
+
];
|
|
986
|
+
const OUTPUT_NOUN = /命令|脚本|指令|步骤|清单|说明|文档|模板|command|script|instructions?|checklist|snippet/i;
|
|
987
|
+
const OUTPUT_REQUEST = /(?:给|帮|替|为)(?:我|我们)?\s*(?:写|生成|整理|列|准备|提供|输出|来)|生成(?:一|两|几)?(?:条|个|份)|输出(?:一|个|份)?|列出|列一(?:下|个)|\b(?:provide|write|generate|outline|list|draft)\b|give\s+me/i;
|
|
988
|
+
const CONDITION_MARKERS = [
|
|
989
|
+
["如果", "prefix"],
|
|
990
|
+
["假如", "prefix"],
|
|
991
|
+
["倘若", "prefix"],
|
|
992
|
+
["若是", "prefix"],
|
|
993
|
+
["一旦", "prefix"],
|
|
994
|
+
["除非", "prefix"],
|
|
995
|
+
["只有", "prefix"],
|
|
996
|
+
["只要", "prefix"],
|
|
997
|
+
["等到", "prefix"],
|
|
998
|
+
["若", "prefix"],
|
|
999
|
+
["在", "prefix"],
|
|
1000
|
+
["if", "prefix"],
|
|
1001
|
+
["unless", "prefix"],
|
|
1002
|
+
["once", "prefix"],
|
|
1003
|
+
["when", "prefix"],
|
|
1004
|
+
["provided that", "prefix"],
|
|
1005
|
+
["after", "prefix"],
|
|
1006
|
+
["之后", "suffix"],
|
|
1007
|
+
["以后", "suffix"],
|
|
1008
|
+
["才", "suffix"],
|
|
1009
|
+
["再", "suffix"]
|
|
1010
|
+
];
|
|
1011
|
+
const RESUME_MARKER = /(?:收到|得到|等到|等待|经)\s*.{0,12}?(?:明确|显式|最终)?\s*(?:回报|回复|答复|确认|批准|同意|授权|指示|通知)|(?:我|用户)(?:明确|最终)?\s*(?:确认|回复|回报|批准|同意|授权)(?:后再|之后|后|以后)?|after\s+(?:I|the user)\s+(?:confirm|reply|approve|authorize)|once\s+(?:I|the user)\s+(?:confirm|reply|approve)|waiting\s+for\s+(?:the\s+)?(?:user|you)/i;
|
|
1012
|
+
/**
|
|
1013
|
+
* The resumption event itself, without the request prefix a scope may open
|
|
1014
|
+
* with. Used to locate the event inside a scope rather than at its start.
|
|
1015
|
+
*/
|
|
1016
|
+
const RESUMPTION_EVENT = /(?:收到|得到|等到|等待)\s*.{0,12}?(?:确认|回复|回报|批准|同意|授权|指示|通知)\s*(?:后再|之后|后|以后|再)|(?:我|用户)(?:明确|最终)?\s*(?:确认|回复|回报|批准|同意|授权)\s*(?:后再|之后|后|以后|再)|(?:after|once)\s+(?:I|the user)\s+(?:confirm|reply|approve|authorize)|waiting\s+for\s+(?:the\s+)?(?:user|you)/i;
|
|
1017
|
+
/**
|
|
1018
|
+
* A scope that OPENS with the resumption event it waits on. Anchored at the
|
|
1019
|
+
* start and greedy, so the match runs to the end of the event itself
|
|
1020
|
+
* ("收到我的确认后"): the condition is what the scope says after it.
|
|
1021
|
+
*/
|
|
1022
|
+
const RESUME_SCOPE_MARKER = /^(?:请在|请|麻烦|帮我|需要你|务必)?\s*(?:(?:收到|得到|等到|等待)\s*.{0,12}?(?:确认|回复|回报|批准|同意|授权|指示|通知)\s*(?:后再|之后|后|以后|再)|(?:我|用户)(?:明确|最终)?\s*(?:确认|回复|回报|批准|同意|授权)\s*(?:后再|之后|后|以后|再)|(?:after|once)\s+(?:I|the user)\s+(?:confirm|reply|approve|authorize)|waiting\s+for\s+(?:the\s+)?(?:user|you))/i;
|
|
1023
|
+
const NARRATIVE_PAST = /(?:已经|已|刚刚|刚才|此前|之前)(?:经)?(?:推送|发布|提交|安装|升级|重启|合并|完成|修改|更新|删除|创建|写入)|\b(?:already|have|has|had)\s+(?:been\s+)?(?:pushed|published|committed|installed|upgraded|restarted|merged|completed|finished|modified|updated)\b/i;
|
|
1024
|
+
/**
|
|
1025
|
+
* Completion aspects that turn a clause into a report: a verb finished with
|
|
1026
|
+
* 了/过/完了/好了 states what happened, so it orders nothing. A directive never
|
|
1027
|
+
* carries them ("修改 README" is an order, "修改了 README" is a report).
|
|
1028
|
+
*/
|
|
1029
|
+
const NARRATIVE_ASPECT = /(?:完了|好了|过了)|(?:已经|已|刚刚|刚才|此前|之前)[\p{Script=Han}]{0,4}(?:了|过)|\b(?:was|were|has been|have been)\b/iu;
|
|
1030
|
+
const NARRATIVE_DIRECTIVE = /请|需要你|帮我|麻烦|务必|\b(?:please|must)\b/i;
|
|
1031
|
+
const UNRESOLVED_SCOPE = /^(?:看看|看一下|瞅瞅|研究一下|了解|随便|maybe|perhaps|somehow|figure\s+out)/i;
|
|
1032
|
+
/**
|
|
1033
|
+
* A completed confirmation receipt: the root reports that the event it was
|
|
1034
|
+
* waiting for already happened. It reserves nothing, so it must not mint a
|
|
1035
|
+
* wait, and it is not work either.
|
|
1036
|
+
*/
|
|
1037
|
+
const CONFIRMATION_RECEIPT = /^(?:我)?\s*(?:已|已经)?\s*(?:收到|得到|等到|等待)(?:了|过)?\s*(?:我|你|您|用户)?\s*的?\s*.{0,12}?(?:确认|回复|回报|批准|同意|授权|指示|通知)\s*(?:了|啦|过|收到)\s*[。..!!]?$/u;
|
|
1038
|
+
const SENTENCE_END = new Set([
|
|
1039
|
+
"。",
|
|
1040
|
+
"!",
|
|
1041
|
+
"?",
|
|
1042
|
+
"!",
|
|
1043
|
+
"?",
|
|
1044
|
+
"\n",
|
|
1045
|
+
"\r"
|
|
1046
|
+
]);
|
|
1047
|
+
function isWordBoundary(text, index) {
|
|
1048
|
+
if (index <= 0) return true;
|
|
1049
|
+
return !/[\p{L}\p{N}_]/u.test(text[index - 1]);
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* The first negator in `text` at or after `from`.
|
|
1053
|
+
*
|
|
1054
|
+
* A multi-word English negator is matched at BOTH of its words ("do not"), so a
|
|
1055
|
+
* caller scanning for a negated verb does not have to know where the phrase
|
|
1056
|
+
* began. `index` 0 is always a boundary; later positions are boundaries only
|
|
1057
|
+
* when the preceding character is not a word character.
|
|
1058
|
+
*/
|
|
1059
|
+
function firstNegation(text, from = 0) {
|
|
1060
|
+
for (let cursor = from; cursor < text.length; cursor += 1) {
|
|
1061
|
+
const token = negatorAt(text, cursor);
|
|
1062
|
+
if (token) return {
|
|
1063
|
+
index: cursor,
|
|
1064
|
+
token
|
|
788
1065
|
};
|
|
789
1066
|
}
|
|
1067
|
+
}
|
|
1068
|
+
/** Match a negator at exactly `index`, the longest alternative winning. */
|
|
1069
|
+
function negatorAt(text, index) {
|
|
1070
|
+
const lower = text.toLowerCase();
|
|
1071
|
+
const candidates = NEGATORS.filter(([token]) => lower.startsWith(token, index)).sort((a, b) => b[0].length - a[0].length || a[0].localeCompare(b[0]));
|
|
1072
|
+
for (const [token] of candidates) {
|
|
1073
|
+
if (token.length === 1 && /[\u3400-\u9fff]/.test(token)) {
|
|
1074
|
+
if (!/[\p{Script=Han}\p{L}\p{N}]/u.test(text[index + 1] ?? "")) continue;
|
|
1075
|
+
}
|
|
1076
|
+
if (/^[a-z]/.test(token)) {
|
|
1077
|
+
if (!isWordBoundary(text, index)) continue;
|
|
1078
|
+
if (/[\p{L}\p{N}_-]/u.test(text[index + token.length] ?? "")) continue;
|
|
1079
|
+
const after = text[index + token.length] ?? "";
|
|
1080
|
+
if (/[./@\\]/u.test(after) && !/\s/u.test(text[index + token.length + 1] ?? "")) continue;
|
|
1081
|
+
}
|
|
1082
|
+
return token;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Index of the first action verb at or after `offset`.
|
|
1087
|
+
*
|
|
1088
|
+
* A vocabulary entry that a multi-character action immediately continues is the
|
|
1089
|
+
* first character of that word rather than a verb of its own — the 升 of 升级,
|
|
1090
|
+
* the 然 of 然后 — so the longer action is chosen instead. Without that rule
|
|
1091
|
+
* "然后完成…" reads as two verbs and every condition analysis downstream anchors
|
|
1092
|
+
* on the wrong one.
|
|
1093
|
+
*/
|
|
1094
|
+
function firstActionVerb(text, offset = 0, before = text.length) {
|
|
1095
|
+
const matches = actionVerbMatches(text, offset, before);
|
|
1096
|
+
return matches.length > 0 ? matches[0].index : -1;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Every action word in `[offset, before)`, ordered by position, with the words
|
|
1100
|
+
* that are only a prefix of a longer action removed (the 升 of 升级, the 然 of
|
|
1101
|
+
* 然后). The remaining candidates are the verbs an instruction can be about.
|
|
1102
|
+
*/
|
|
1103
|
+
function actionVerbMatches(text, offset = 0, before = text.length) {
|
|
1104
|
+
const span = text.slice(offset, before);
|
|
1105
|
+
const earliest = [];
|
|
1106
|
+
for (const pattern of [ACTION_VERB, WORK_VERB]) {
|
|
1107
|
+
const match = pattern.exec(span);
|
|
1108
|
+
if (match && !(match[0].length === 1 && /[A-Za-z]/.test(match[0]))) earliest.push({
|
|
1109
|
+
index: offset + match.index,
|
|
1110
|
+
length: match[0].length
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
return earliest.sort((a, b) => a.index - b.index);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* True when a negator's scope covers the verb starting at `index`.
|
|
1117
|
+
*
|
|
1118
|
+
* The negator has to be phrase-initial, so the 不 of 手动 and the 无 of 无论 are
|
|
1119
|
+
* not read as bans; a contrast or list separator between the negator and the
|
|
1120
|
+
* verb ends its scope ("不仅…而且运行" keeps the run positive).
|
|
1121
|
+
*/
|
|
1122
|
+
function verbIsNegated(text, index) {
|
|
1123
|
+
const ceiling = Math.min(index, 12);
|
|
1124
|
+
for (let back = 1; back <= ceiling; back += 1) {
|
|
1125
|
+
const at = index - back;
|
|
1126
|
+
const token = negatorAt(text, at);
|
|
1127
|
+
if (!token || at + token.length > index) continue;
|
|
1128
|
+
if (at > 0 && /[\u3400-\u9fff]/.test(text[at - 1])) continue;
|
|
1129
|
+
if (/[,,、;;。!!??\n\r]/.test(text.slice(at + token.length, index))) continue;
|
|
1130
|
+
return true;
|
|
1131
|
+
}
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
/** True when an unnegated operation verb occurs inside `[from, to)`. */
|
|
1135
|
+
function hasPositiveVerb(text, from, to) {
|
|
1136
|
+
const index = firstActionVerb(text, from, to);
|
|
1137
|
+
if (index < 0) return false;
|
|
1138
|
+
return !verbIsNegated(text, index);
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* The verb a negator bans. A Chinese negator may put an adverb between itself
|
|
1142
|
+
* and its verb ("不正式发布"), so candidate verbs are walked in order and the
|
|
1143
|
+
* first one that is a real word rather than part of the preceding word wins.
|
|
1144
|
+
*/
|
|
1145
|
+
function bannedVerbIndex(text, afterNegator, before) {
|
|
1146
|
+
const span = text.slice(afterNegator, before);
|
|
1147
|
+
for (const word of CJK_VERB_WORDS) {
|
|
1148
|
+
const at = span.indexOf(word);
|
|
1149
|
+
if (at < 0) continue;
|
|
1150
|
+
return afterNegator + at;
|
|
1151
|
+
}
|
|
1152
|
+
const candidates = actionVerbMatches(text, afterNegator, before);
|
|
1153
|
+
for (const candidate of actionVerbMatches(text, 0, afterNegator)) candidates.push(candidate);
|
|
1154
|
+
if (candidates.length === 0) return -1;
|
|
1155
|
+
return candidates.map((candidate) => candidate.index).reduce((best, index) => Math.abs(index - afterNegator) < Math.abs(best - afterNegator) ? index : best);
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* A resumption condition: everything a scope says before the event that ends
|
|
1159
|
+
* the wait ("收到我的确认后再推送" waits for the confirmation, so the push is
|
|
1160
|
+
* not executable yet). Leading request words are not part of the condition, and
|
|
1161
|
+
* a marker separated from the scope start by more than a clause belongs to a
|
|
1162
|
+
* different statement.
|
|
1163
|
+
*/
|
|
1164
|
+
function resumptionConditionOf(scope) {
|
|
1165
|
+
const text = scope.text;
|
|
1166
|
+
if (scope.directive === "conditional") return text.replace(/^(?:请在|请|麻烦|帮我|需要你|务必)\s*/u, "").trim() || void 0;
|
|
1167
|
+
return leadingResumptionCondition(text, firstActionVerb(maskCodeSpans(text)) >= 0);
|
|
1168
|
+
}
|
|
1169
|
+
function leadingResumptionCondition(text, hasAction) {
|
|
1170
|
+
const masked = maskCodeSpans(text);
|
|
1171
|
+
const marker = RESUME_SCOPE_MARKER.exec(masked);
|
|
1172
|
+
if (!marker) return void 0;
|
|
1173
|
+
const guarded = masked.slice(marker[0].length).replace(/^[\s,,、::]+/u, "").replace(/^(?:再|才|就|则|即)\s*/u, "").trim().replace(/[。..!!??]+$/u, "");
|
|
1174
|
+
if (guarded && /[;;。]/u.test(guarded)) return void 0;
|
|
1175
|
+
if (!hasAction) return void 0;
|
|
1176
|
+
return marker[0].replace(/^(?:请在|请|麻烦|帮我|需要你|务必)\s*/u, "").trim() || void 0;
|
|
1177
|
+
}
|
|
1178
|
+
/** End index of the negated span beginning at `start`. */
|
|
1179
|
+
function negatedSpanEnd(text, start) {
|
|
1180
|
+
for (let cursor = start + 1; cursor < text.length; cursor += 1) {
|
|
1181
|
+
const character = text[cursor];
|
|
1182
|
+
if (character === "\n" || character === "\r") return cursor;
|
|
1183
|
+
if (character === "。" || character === "!" || character === "?" || character === "!" || character === "?") return cursor;
|
|
1184
|
+
if (character === "." && (cursor + 1 >= text.length || /\s/.test(text[cursor + 1]))) return cursor;
|
|
1185
|
+
if (character === "但" && text[cursor + 1] !== "是") return cursor;
|
|
1186
|
+
if (character === "而" && text[cursor + 1] === "是") return cursor;
|
|
1187
|
+
if (character === ";" || character === ";") return cursor;
|
|
1188
|
+
if (negatorAt(text, cursor)) return cursor;
|
|
1189
|
+
if (!SEPARATORS.has(character)) continue;
|
|
1190
|
+
const rest = text.slice(cursor + 1);
|
|
1191
|
+
if (CONTINUATION_AFTER_SEPARATOR.test(rest)) continue;
|
|
1192
|
+
if (character === "," || character === "," || character === "、") return cursor + 1;
|
|
1193
|
+
if (hasPositiveVerb(text, cursor + 1, text.length)) return cursor;
|
|
1194
|
+
}
|
|
1195
|
+
return text.length;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Whether a run that follows a negator names an action directly ("推送、不
|
|
1199
|
+
* 发布" → true, "任何改动" → false). Only the guard's own action surface counts:
|
|
1200
|
+
* consultative verbs such as 完成 are deliberately absent, so "尚未完成" stays a
|
|
1201
|
+
* statement instead of becoming a ban.
|
|
1202
|
+
*/
|
|
1203
|
+
function namesActionSpan(text) {
|
|
1204
|
+
const match = /^[^\p{Script=Han}A-Za-z]*([\p{Script=Han}A-Za-z][\p{Script=Han}A-Za-z0-9_-]*)/u.exec(text);
|
|
1205
|
+
if (!match) return false;
|
|
1206
|
+
const head = match[1];
|
|
1207
|
+
for (const entry of COMMAND_SURFACE_MANIFEST.operationVerbs) if (new RegExp(`^(?:${entry.pattern})$`, "i").test(head)) return true;
|
|
1208
|
+
return false;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Split one message into scopes, in source order.
|
|
1212
|
+
*
|
|
1213
|
+
* The working list holds `[text, offset]` runs of the original message. A run is
|
|
1214
|
+
* resolved into one scope as soon as a rule matches; otherwise the runner splits
|
|
1215
|
+
* it into a head and a tail and pushes the tail back, so the split is iterative
|
|
1216
|
+
* and no run is ever re-read out of order.
|
|
1217
|
+
*
|
|
1218
|
+
* A negation opens a scope covering every action it governs — the scope ends at
|
|
1219
|
+
* a new positive verb, at a contrast, or (for a coordinated ban such as
|
|
1220
|
+
* "不推送、不发布") at the end of the run. A separator that is *followed by a
|
|
1221
|
+
* coordinating conjunction* also ends the current scope: "修复代码,但不推送"
|
|
1222
|
+
* is a task plus a ban, while "更新皮肤中心并在本地仓库记录" stays one
|
|
1223
|
+
* coordinated scope until the conjunction itself.
|
|
1224
|
+
*/
|
|
1225
|
+
function scopeOf(raw, options = {}) {
|
|
1226
|
+
const source = raw.trim();
|
|
1227
|
+
if (!source) return [];
|
|
1228
|
+
const pending = [{
|
|
1229
|
+
text: source,
|
|
1230
|
+
offset: 0
|
|
1231
|
+
}];
|
|
1232
|
+
const resolved = [];
|
|
1233
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
1234
|
+
const push = (entry) => {
|
|
1235
|
+
const key = `${entry.offset}\u0000${entry.scope.directive}\u0000${entry.scope.text}`;
|
|
1236
|
+
if (emitted.has(key)) return;
|
|
1237
|
+
emitted.add(key);
|
|
1238
|
+
resolved.push(entry);
|
|
1239
|
+
};
|
|
1240
|
+
while (pending.length > 0) {
|
|
1241
|
+
const run = pending.pop();
|
|
1242
|
+
const text = run.text.trim();
|
|
1243
|
+
if (!text) continue;
|
|
1244
|
+
const inheritedCondition = run.inherited;
|
|
1245
|
+
const offset = run.offset + run.text.indexOf(text);
|
|
1246
|
+
const masked = maskCodeSpans(text);
|
|
1247
|
+
const conditionPrefix = prefixConditionIndex(masked.toLowerCase());
|
|
1248
|
+
const negation = firstNegation(masked);
|
|
1249
|
+
let earliestVerb = firstActionVerb(masked, conditionPrefix !== void 0 ? lastBoundaryIndex(text, conditionPrefix) : 0);
|
|
1250
|
+
if (earliestVerb < 0 && conditionPrefix !== void 0) {
|
|
1251
|
+
const tail$1 = expressionTailVerb(masked, conditionPrefix);
|
|
1252
|
+
if (tail$1 > conditionPrefix) earliestVerb = tail$1;
|
|
1253
|
+
}
|
|
1254
|
+
const negationIndex = negation ? negation.index : -1;
|
|
1255
|
+
const banScanEnd = masked.length;
|
|
1256
|
+
const bannedVerb = negation ? bannedVerbIndex(masked, negationIndex + negation.token.length, banScanEnd) : -1;
|
|
1257
|
+
const negationBansAction = negation !== void 0 && (bannedVerb >= 0 || namesActionSpan(masked.slice(negationIndex + negation.token.length, banScanEnd)));
|
|
1258
|
+
negation !== void 0 && bannedVerb >= 0 && /^[\s::,,、]*$/u.test(masked.slice(negationIndex + negation.token.length, bannedVerb));
|
|
1259
|
+
const earliestNegation = negationBansAction ? negationIndex : -1;
|
|
1260
|
+
const earliestNegationToken = negationBansAction ? negation.token : "";
|
|
1261
|
+
const locativePrefix = conditionPrefix !== void 0 && text[conditionPrefix] === "在" && /^在.{1,24}?(?:记录|保存|写入|提交|运行|执行|测试|检查|验证|完成)/u.test(text.slice(conditionPrefix, earliestVerb));
|
|
1262
|
+
if (conditionPrefix !== void 0 && earliestNegation < 0 && !locativePrefix) {
|
|
1263
|
+
const conditional = conditionSplit(text, conditionPrefix, earliestVerb, options);
|
|
1264
|
+
if (conditional) {
|
|
1265
|
+
for (const scope of conditional) push({
|
|
1266
|
+
scope,
|
|
1267
|
+
offset: offset + (scope.start ?? 0)
|
|
1268
|
+
});
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
if (earliestNegation >= 0) {
|
|
1273
|
+
const head$1 = text.slice(0, earliestNegation).trim();
|
|
1274
|
+
const end$1 = negatedSpanEnd(masked, earliestNegation);
|
|
1275
|
+
const banText = text.slice(earliestNegation, end$1).trim();
|
|
1276
|
+
const tail$1 = text.slice(end$1).replace(/^[\s。..!!??,,;;、]+/, "").trim();
|
|
1277
|
+
if (tail$1) pending.push({
|
|
1278
|
+
text: tail$1,
|
|
1279
|
+
offset: offset + end$1,
|
|
1280
|
+
...inheritedCondition ? { inherited: inheritedCondition } : {}
|
|
1281
|
+
});
|
|
1282
|
+
if (banText) {
|
|
1283
|
+
const clauseStart = conditionPrefix !== void 0 && conditionPrefix < earliestNegation ? lastBoundaryIndex(text, conditionPrefix) : -1;
|
|
1284
|
+
const conditionText = inheritedCondition ?? (clauseStart >= 0 ? stripConditionConnector(text.slice(clauseStart, earliestNegation)) : "");
|
|
1285
|
+
push({
|
|
1286
|
+
offset,
|
|
1287
|
+
scope: {
|
|
1288
|
+
text: banText,
|
|
1289
|
+
body: stripNegators(banText, earliestNegationToken) || banText,
|
|
1290
|
+
directive: "prohibition",
|
|
1291
|
+
...conditionText ? { condition: conditionText } : {}
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
if (head$1) pending.push({
|
|
1296
|
+
text: head$1,
|
|
1297
|
+
offset
|
|
1298
|
+
});
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
const end = positiveScopeEnd(masked, options);
|
|
1302
|
+
const head = text.slice(0, end).trim();
|
|
1303
|
+
const tail = text.slice(end).trim();
|
|
1304
|
+
if (tail) pending.push({
|
|
1305
|
+
text: tail,
|
|
1306
|
+
offset: offset + end
|
|
1307
|
+
});
|
|
1308
|
+
if (head) {
|
|
1309
|
+
const inherited = conditionPrefix !== void 0 && conditionPrefix < head.length ? text.slice(conditionPrefix, head.length).replace(/^[\s,,、;;::]+/, "").trim() : "";
|
|
1310
|
+
push({
|
|
1311
|
+
offset,
|
|
1312
|
+
scope: {
|
|
1313
|
+
text: head,
|
|
1314
|
+
body: stripConnectors(head),
|
|
1315
|
+
directive: classifyPositive(head),
|
|
1316
|
+
...inherited ? { condition: inherited } : {}
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1322
|
+
return resolved.sort((a, b) => a.offset - b.offset).filter((entry) => {
|
|
1323
|
+
const key = `${entry.offset}\u0000${entry.scope.directive}\u0000${entry.scope.text}`;
|
|
1324
|
+
if (seen.has(key)) return false;
|
|
1325
|
+
seen.add(key);
|
|
1326
|
+
return true;
|
|
1327
|
+
}).map((entry) => entry.scope).filter((scope) => /[\p{L}\p{N}]/u.test(scope.body) && /[\p{L}\p{N}]/u.test(scope.text));
|
|
1328
|
+
}
|
|
1329
|
+
function positiveScopeEnd(masked, options = {}) {
|
|
1330
|
+
const limit = masked.length;
|
|
1331
|
+
let cursor = 0;
|
|
1332
|
+
while (cursor < limit && (masked[cursor] === "," || masked[cursor] === "," || masked[cursor] === "、" || masked[cursor] === "并" || masked[cursor] === "且" || /\s/u.test(masked[cursor]))) cursor += 1;
|
|
1333
|
+
while (cursor < limit) {
|
|
1334
|
+
const character = masked.slice(cursor, cursor + 1);
|
|
1335
|
+
if (SENTENCE_END.has(character)) return cursor + 1;
|
|
1336
|
+
if (character === ";" || character === ";") return cursor + 1;
|
|
1337
|
+
if (!(character === "," || character === "," || character === "、" || character === "并" || character === "且")) {
|
|
1338
|
+
cursor += 1;
|
|
1339
|
+
continue;
|
|
1340
|
+
}
|
|
1341
|
+
const rest = masked.slice(cursor + 1);
|
|
1342
|
+
if (options.coordinationSplit === false) {
|
|
1343
|
+
cursor += 1;
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (/^\s*(?:直到|直至|一直到)\s*/u.test(rest)) {
|
|
1347
|
+
cursor += 1;
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
if (CONTINUATION_AFTER_SEPARATOR.test(rest)) return cursor + 1;
|
|
1351
|
+
if (/^\s*(?:由|让|请|给)\s*(?:你|您|我|本人)/u.test(rest)) return cursor + 1;
|
|
1352
|
+
CROSS_CLAUSE_HEAD.test(rest);
|
|
1353
|
+
const locative = LOCATIVE_CLAUSE.test(rest);
|
|
1354
|
+
const conjunctionSeparator = character === "并" || character === "且";
|
|
1355
|
+
const comma = character === "," || character === ",";
|
|
1356
|
+
const enumeration = character === "、";
|
|
1357
|
+
if (comma) {
|
|
1358
|
+
if (!(conjunctionSeparator || CONTINUATION_AFTER_SEPARATOR.test(rest))) {
|
|
1359
|
+
cursor += 1;
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
return cursor;
|
|
1363
|
+
}
|
|
1364
|
+
if (enumeration) {
|
|
1365
|
+
if (!locative) {
|
|
1366
|
+
cursor += 1;
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
return cursor;
|
|
1370
|
+
}
|
|
1371
|
+
if (!(DISTINCT_CLAUSE_HEAD.test(rest) || locative)) {
|
|
1372
|
+
cursor += 1;
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
return cursor;
|
|
1376
|
+
}
|
|
1377
|
+
return masked.length;
|
|
1378
|
+
}
|
|
1379
|
+
function conditionSplit(text, conditionPrefix, verb, options = {}) {
|
|
1380
|
+
const masked = maskCodeSpans(text);
|
|
1381
|
+
const lower = masked.toLowerCase();
|
|
1382
|
+
if (conditionPrefix === void 0) return suffixConditionSplit(text, lower, masked, options);
|
|
1383
|
+
if (verb < 0) return void 0;
|
|
1384
|
+
const candidates = conditionCandidates(text, lower, masked, conditionPrefix);
|
|
1385
|
+
if (candidates.length === 0) return void 0;
|
|
1386
|
+
const guardedScope = candidates[0];
|
|
1387
|
+
const markerIndex = guardedScope.start ?? 0;
|
|
1388
|
+
const guardedStart = guardedScope.start ?? 0;
|
|
1389
|
+
const condition = guardedScope.condition ?? "";
|
|
1390
|
+
if (!condition.trim() || !guardedScope.text.trim()) return void 0;
|
|
1391
|
+
const scopes = [];
|
|
1392
|
+
const clauseStart = lastBoundaryIndex(text, markerIndex);
|
|
1393
|
+
const lead = text.slice(0, clauseStart).trim();
|
|
1394
|
+
if (lead) scopes.push(...scopeOf(lead, options));
|
|
1395
|
+
const conditionClause = text.slice(clauseStart, guardedStart).trim();
|
|
1396
|
+
if (conditionClause) scopes.push({
|
|
1397
|
+
text: conditionClause,
|
|
1398
|
+
body: conditionClause,
|
|
1399
|
+
directive: "conditional",
|
|
1400
|
+
condition: condition.trim(),
|
|
1401
|
+
start: clauseStart
|
|
1402
|
+
});
|
|
1403
|
+
scopes.push({
|
|
1404
|
+
text: guardedScope.text.trim(),
|
|
1405
|
+
body: guardedScope.text.replace(/^[\s,,、;;::]+/, "").replace(/^(?:才|再|就|则|即)\s*/, "").trim(),
|
|
1406
|
+
directive: "directive",
|
|
1407
|
+
condition: condition.trim(),
|
|
1408
|
+
start: guardedStart
|
|
1409
|
+
});
|
|
1410
|
+
return scopes;
|
|
1411
|
+
}
|
|
1412
|
+
/** Index just past the last clause separator at or before `index`. */
|
|
1413
|
+
function lastBoundaryIndex(text, index) {
|
|
1414
|
+
let cursor = index;
|
|
1415
|
+
while (cursor > 0) {
|
|
1416
|
+
const character = text[cursor - 1];
|
|
1417
|
+
if (character === "在") break;
|
|
1418
|
+
if (character === ";" || character === ";" || character === "。" || character === "!" || character === "?" || character === "!" || character === "?" || character === "\n" || character === "\r") return cursor;
|
|
1419
|
+
cursor -= 1;
|
|
1420
|
+
}
|
|
1421
|
+
return 0;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* A trailing condition marker ("…才…") needs no prefix marker when it sits
|
|
1425
|
+
* directly before the action it guards: "收到我的明确回报后再继续".
|
|
1426
|
+
*/
|
|
1427
|
+
function suffixConditionSplit(text, lower, masked, options = {}) {
|
|
1428
|
+
const verb = firstActionVerb(masked);
|
|
1429
|
+
if (verb < 0) return void 0;
|
|
1430
|
+
const marker = /(?:之后|以后|后再|后才|再继续|才继续|再|才)/u.exec(text.slice(verb));
|
|
1431
|
+
if (!marker) return void 0;
|
|
1432
|
+
const condition = trimConditionTail(text.slice(0, verb + marker.index));
|
|
1433
|
+
const guarded = text.slice(verb + marker[0].length);
|
|
1434
|
+
if (!condition || !guarded.trim()) return void 0;
|
|
1435
|
+
return [{
|
|
1436
|
+
text: condition,
|
|
1437
|
+
body: condition,
|
|
1438
|
+
directive: "conditional",
|
|
1439
|
+
condition
|
|
1440
|
+
}, {
|
|
1441
|
+
text: guarded.trim(),
|
|
1442
|
+
body: guarded.replace(/^[\s,,、;;::]+/, "").replace(/^(?:才|再|就|则|即)\s*/, "").trim(),
|
|
1443
|
+
directive: "directive",
|
|
1444
|
+
condition,
|
|
1445
|
+
start: verb + marker[0].length
|
|
1446
|
+
}];
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* The condition clauses inside one clause run, each paired with the action it
|
|
1450
|
+
* guards. A marker that appears after the action's own verb but allows nothing
|
|
1451
|
+
* before that verb is not a condition at all — "confirm" contains "if", and
|
|
1452
|
+
* "We ship after the test passes" carries a subject the marker does not guard.
|
|
1453
|
+
*/
|
|
1454
|
+
function conditionCandidates(text, lower, masked, conditionPrefix) {
|
|
1455
|
+
const clauseStart = lastBoundaryIndex(text, conditionPrefix);
|
|
1456
|
+
const clause = lower.slice(clauseStart);
|
|
1457
|
+
const candidates = [];
|
|
1458
|
+
for (const [token, kind] of CONDITION_MARKERS) {
|
|
1459
|
+
if (kind !== "prefix" || token === "在") continue;
|
|
1460
|
+
const index = prefixIndexOf(clause, token);
|
|
1461
|
+
if (index < 0) continue;
|
|
1462
|
+
const absolute = clauseStart + index;
|
|
1463
|
+
if (firstNegation(masked.slice(absolute))) continue;
|
|
1464
|
+
let after = firstActionVerb(masked, absolute + token.length);
|
|
1465
|
+
if (after < 0) {
|
|
1466
|
+
const tail = expressionTailVerb(masked, absolute + token.length);
|
|
1467
|
+
if (tail >= absolute + token.length) after = tail;
|
|
1468
|
+
}
|
|
1469
|
+
const before = firstActionVerb(masked, clauseStart, absolute);
|
|
1470
|
+
if (after >= 0 && masked.slice(absolute + token.length, after).trim().length === 0) {
|
|
1471
|
+
candidates.push({
|
|
1472
|
+
markerAt: absolute,
|
|
1473
|
+
condition: trimConditionTail(text.slice(clauseStart, absolute)),
|
|
1474
|
+
guarded: text.slice(after),
|
|
1475
|
+
guardedAt: after
|
|
1476
|
+
});
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
if (after < 0) {
|
|
1480
|
+
if (before < 0 || !/^(?:[\p{L}\p{N}]+[\s]*){0,3}[\p{L}\p{N}]+$/u.test(text.slice(clauseStart, absolute).trim())) continue;
|
|
1481
|
+
candidates.push({
|
|
1482
|
+
markerAt: absolute,
|
|
1483
|
+
condition: trimConditionTail(text.slice(absolute + token.length)),
|
|
1484
|
+
guarded: text.slice(clauseStart, absolute).trim(),
|
|
1485
|
+
guardedAt: clauseStart
|
|
1486
|
+
});
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
if (absolute >= before && before >= 0) continue;
|
|
1490
|
+
candidates.push({
|
|
1491
|
+
markerAt: absolute,
|
|
1492
|
+
condition: trimConditionTail(text.slice(absolute + token.length, after)),
|
|
1493
|
+
guarded: text.slice(after),
|
|
1494
|
+
guardedAt: after
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
if (candidates.length === 0) return [];
|
|
1498
|
+
const best = candidates.reduce((left, right) => right.markerAt < left.markerAt ? right : left);
|
|
1499
|
+
const guarded = {
|
|
1500
|
+
text: best.guarded.trim(),
|
|
1501
|
+
body: best.guarded.replace(/^[\s,,、;;::]+/, "").replace(/^(?:才|再|就|则|即)\s*/, "").trim(),
|
|
1502
|
+
directive: "directive",
|
|
1503
|
+
condition: best.condition,
|
|
1504
|
+
start: best.guardedAt
|
|
1505
|
+
};
|
|
1506
|
+
return best.markerAt > 0 ? [guarded] : [guarded];
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* The condition a marker supplies: everything between the clause start and the
|
|
1510
|
+
* guarded action, without the connector that introduces the ban ("除非…否则不要
|
|
1511
|
+
* 合并" → "除非我明确说可以").
|
|
1512
|
+
*/
|
|
1513
|
+
function stripConditionConnector(value) {
|
|
1514
|
+
return value.replace(/^[\s,,、;;::]+/, "").replace(/[\s,,、;;::]*(?:否则|不然|then|otherwise)[\s,,、;;:]*$/i, "").trim();
|
|
1515
|
+
}
|
|
1516
|
+
/** Drop the temporal tail a condition marker may leave behind ("之后", "以后"). */
|
|
1517
|
+
function trimConditionTail(value) {
|
|
1518
|
+
return value.replace(/[\s,,、;;::]+$/, "").replace(/(?:之后|以后|后)$/, "").trim();
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* A known action word at the end of the clause, used when the guarded action is
|
|
1522
|
+
* expressed as a plain Chinese verb: "若…才推送" ends in 推送, which the action
|
|
1523
|
+
* surface does not treat as a verb because it is the object of 才. Only a closed
|
|
1524
|
+
* vocabulary is accepted, so ordinary prose is never mistaken for an action.
|
|
1525
|
+
*/
|
|
1526
|
+
const BOUND_ACTION_TAIL = /(创建|生成|写入|修改|编辑|运行|执行|编写|撰写|部署|安装|升级|提交|下载|上传|拉取|同步|重启|测试|检查|验证|确认|修复|更新|清理|整理|记录|构建|编译|重构|迁移|删除|回滚|发布|推送|实现|合并|提交|回退|检查)[。..!!??,,;;、\s]*$/u;
|
|
1527
|
+
function expressionTailVerb(masked, from) {
|
|
1528
|
+
const slice = masked.slice(from);
|
|
1529
|
+
const match = BOUND_ACTION_TAIL.exec(slice);
|
|
1530
|
+
return match ? from + match.index : -1;
|
|
1531
|
+
}
|
|
1532
|
+
/** Index of the first prefix condition marker, skipping a locative 在. */
|
|
1533
|
+
function prefixConditionIndex(lower) {
|
|
1534
|
+
const head = lower;
|
|
1535
|
+
let best;
|
|
1536
|
+
for (const [token, kind] of CONDITION_MARKERS) {
|
|
1537
|
+
if (kind !== "prefix" || token === "在") continue;
|
|
1538
|
+
const index = prefixIndexOf(head, token);
|
|
1539
|
+
if (index < 0) continue;
|
|
1540
|
+
if (best === void 0 || index < best) best = index;
|
|
1541
|
+
}
|
|
1542
|
+
return best;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Word-bounded matcher per English marker, compiled once. Building the pattern
|
|
1546
|
+
* inside the scan recompiled it for every marker of every scope, which
|
|
1547
|
+
* dominated capture cost on long messages.
|
|
1548
|
+
*/
|
|
1549
|
+
const ENGLISH_MARKER_MATCHERS = /* @__PURE__ */ new Map();
|
|
1550
|
+
function englishMarkerMatcher(token) {
|
|
1551
|
+
let matcher = ENGLISH_MARKER_MATCHERS.get(token);
|
|
1552
|
+
if (!matcher) {
|
|
1553
|
+
matcher = new RegExp(`(?:^|[^\\p{L}\\p{N}_])${escapeRegExp(token)}(?![\\p{L}\\p{N}_])`, "iu");
|
|
1554
|
+
ENGLISH_MARKER_MATCHERS.set(token, matcher);
|
|
1555
|
+
}
|
|
1556
|
+
return matcher;
|
|
1557
|
+
}
|
|
1558
|
+
function prefixIndexOf(text, token) {
|
|
1559
|
+
if (/^[a-z]/.test(token)) {
|
|
1560
|
+
const match = englishMarkerMatcher(token).exec(text);
|
|
1561
|
+
return match ? match.index + (match[0].length - token.length) : -1;
|
|
1562
|
+
}
|
|
1563
|
+
if (token === "在") {
|
|
1564
|
+
const match = /在[^。!?;]{0,24}?(?:之前|以前)/.exec(text);
|
|
1565
|
+
return match ? match.index : -1;
|
|
1566
|
+
}
|
|
1567
|
+
return text.indexOf(token);
|
|
1568
|
+
}
|
|
1569
|
+
function escapeRegExp(value) {
|
|
1570
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1571
|
+
}
|
|
1572
|
+
function stripNegators(text, matched) {
|
|
1573
|
+
let value = text.trim();
|
|
1574
|
+
value = value.replace(new RegExp(`^${CONNECTOR_PATTERN}\\s*`, "i"), "").trim();
|
|
1575
|
+
if (matched && value.toLowerCase().startsWith(matched.toLowerCase())) value = value.slice(matched.length);
|
|
1576
|
+
return stripNegatorsPrefix(value).trim();
|
|
1577
|
+
}
|
|
1578
|
+
function stripNegatorsPrefix(value) {
|
|
1579
|
+
let text = value;
|
|
1580
|
+
for (let guard = 0; guard < 8; guard += 1) {
|
|
1581
|
+
const trimmed = text.replace(/^[\s,,、;;::]+/, "");
|
|
1582
|
+
let changed = trimmed !== text;
|
|
1583
|
+
text = trimmed;
|
|
1584
|
+
for (const [token] of NEGATORS) {
|
|
1585
|
+
if (!text.toLowerCase().startsWith(token.toLowerCase())) continue;
|
|
1586
|
+
if (/^[a-z]/.test(token) && /[\p{L}\p{N}_]/u.test(text[token.length] ?? "")) continue;
|
|
1587
|
+
text = text.slice(token.length);
|
|
1588
|
+
changed = true;
|
|
1589
|
+
break;
|
|
1590
|
+
}
|
|
1591
|
+
if (!changed) break;
|
|
1592
|
+
}
|
|
1593
|
+
return text;
|
|
1594
|
+
}
|
|
1595
|
+
function stripConnectors(text) {
|
|
1596
|
+
return text.replace(new RegExp(`^${CONNECTOR_PATTERN}\\s*`, "i"), "").trim();
|
|
1597
|
+
}
|
|
1598
|
+
/** Classify a non-negated scope. */
|
|
1599
|
+
function classifyPositive(text) {
|
|
1600
|
+
const masked = maskCodeSpans(text);
|
|
1601
|
+
if (firstActionVerb(masked) < 0 && firstActionVerb(text) >= 0) return "informational";
|
|
1602
|
+
const explain = EXPLAIN_VERB.exec(masked);
|
|
1603
|
+
if (explain) {
|
|
1604
|
+
const verb = firstActionVerb(masked);
|
|
1605
|
+
if (verb < 0 || verb >= explain.index) return "informational";
|
|
1606
|
+
}
|
|
1607
|
+
if (QUESTION_SCOPE.test(masked)) return "informational";
|
|
1608
|
+
if (UNRESOLVED_SCOPE.test(masked)) return "informational";
|
|
1609
|
+
if ((NARRATIVE_PAST.test(masked) || NARRATIVE_ASPECT.test(masked)) && !NARRATIVE_DIRECTIVE.test(masked)) return "narrative";
|
|
1610
|
+
if (CONFIRMATION_RECEIPT.test(masked.trim())) return "narrative";
|
|
1611
|
+
return "directive";
|
|
1612
|
+
}
|
|
1613
|
+
function executeeOf(text, directive) {
|
|
1614
|
+
if (directive === "informational" || directive === "narrative" || directive === "conditional") return "unresolved";
|
|
1615
|
+
const masked = maskCodeSpans(text);
|
|
1616
|
+
if (USER_ACTOR_PATTERNS.some((pattern) => pattern.test(masked))) return "user";
|
|
1617
|
+
if (AGENT_ACTOR_PATTERNS.some((pattern) => pattern.test(masked))) return "agent";
|
|
1618
|
+
return "agent";
|
|
1619
|
+
}
|
|
1620
|
+
/** True when the scope asks for command/instruction TEXT rather than execution. */
|
|
1621
|
+
function isOutputRequest(text) {
|
|
1622
|
+
const masked = maskCodeSpans(text);
|
|
1623
|
+
return OUTPUT_NOUN.test(masked) && OUTPUT_REQUEST.test(masked);
|
|
1624
|
+
}
|
|
1625
|
+
function dispositionOf(scope, executee) {
|
|
1626
|
+
if (scope.directive === "prohibition") return "prohibition";
|
|
1627
|
+
if (scope.directive === "informational" || scope.directive === "narrative") return "informational";
|
|
1628
|
+
if (scope.directive === "conditional") return "conditional_wait";
|
|
1629
|
+
if (scope.condition) return "conditional_wait";
|
|
1630
|
+
if (executee === "user") return "human_actor";
|
|
1631
|
+
if (isOutputRequest(scope.text)) return "informational";
|
|
1632
|
+
return "executable_now";
|
|
1633
|
+
}
|
|
1634
|
+
function resumeEventOf(scope) {
|
|
1635
|
+
const match = RESUME_MARKER.exec(scope.condition ?? scope.text);
|
|
1636
|
+
return match ? match[0].trim() : void 0;
|
|
1637
|
+
}
|
|
1638
|
+
function interpret(scope) {
|
|
1639
|
+
const executee = executeeOf(scope.text, scope.directive);
|
|
1640
|
+
const resumption = scope.condition === void 0 && (scope.directive === "directive" || scope.directive === "conditional") ? resumptionConditionOf(scope) : void 0;
|
|
1641
|
+
const conditioned = resumption ? {
|
|
1642
|
+
...scope,
|
|
1643
|
+
condition: resumption
|
|
1644
|
+
} : scope;
|
|
1645
|
+
const authorityDisposition = dispositionOf(conditioned, executee);
|
|
1646
|
+
const resumeEvent = scope.directive === "directive" ? resumeEventOf(conditioned) : void 0;
|
|
1647
|
+
const method = scope.directive === "prohibition" ? void 0 : semanticMethod(scope.body);
|
|
790
1648
|
return {
|
|
791
|
-
|
|
792
|
-
body:
|
|
1649
|
+
text: scope.text,
|
|
1650
|
+
body: scope.body,
|
|
1651
|
+
directive: scope.directive,
|
|
1652
|
+
executee,
|
|
1653
|
+
...conditioned.condition ? { condition: conditioned.condition } : {},
|
|
1654
|
+
...resumeEvent ? { resumeEvent } : {},
|
|
1655
|
+
immediatelyExecutable: authorityDisposition === "executable_now",
|
|
1656
|
+
authorityDisposition,
|
|
1657
|
+
...method ? { method } : {},
|
|
1658
|
+
fingerprint: fingerprintOf([
|
|
1659
|
+
scope.text,
|
|
1660
|
+
scope.body,
|
|
1661
|
+
scope.directive,
|
|
1662
|
+
executee,
|
|
1663
|
+
authorityDisposition,
|
|
1664
|
+
conditioned.condition ?? "",
|
|
1665
|
+
resumeEvent ?? "",
|
|
1666
|
+
method ?? ""
|
|
1667
|
+
].join("\0"))
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
function semanticMethod(text) {
|
|
1671
|
+
const match = /(?:用|使用|通过|借助|利用|以)\s*([A-Za-z][A-Za-z0-9_-]*)/.exec(text) ?? /\b(?:via|using|use|with)\s+(?:the\s+)?([A-Za-z][A-Za-z0-9_-]*)/i.exec(text);
|
|
1672
|
+
return match ? match[1].toLowerCase() : void 0;
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* A short, stable identity for one interpretation: a content hash of the
|
|
1676
|
+
* interpretation itself, so a replay of identical bytes reproduces it exactly
|
|
1677
|
+
* and two different readings never collide.
|
|
1678
|
+
*/
|
|
1679
|
+
function fingerprintOf(source) {
|
|
1680
|
+
let hash = 2166136261;
|
|
1681
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1682
|
+
hash ^= source.charCodeAt(index);
|
|
1683
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
1684
|
+
}
|
|
1685
|
+
return `i${hash.toString(16).padStart(8, "0")}`;
|
|
1686
|
+
}
|
|
1687
|
+
/** Interpret one already-segmented clause. */
|
|
1688
|
+
function interpretClause(text, options = {}) {
|
|
1689
|
+
const normalized = normalizeClause(text);
|
|
1690
|
+
const scopes = scopeOf(normalized, options);
|
|
1691
|
+
if (scopes.length === 0) return interpret({
|
|
1692
|
+
text: normalized,
|
|
1693
|
+
body: normalized,
|
|
1694
|
+
directive: "informational"
|
|
1695
|
+
});
|
|
1696
|
+
if (scopes.length === 1) return interpret(scopes[0]);
|
|
1697
|
+
const directive = scopes.some((scope) => scope.directive === "directive") ? "directive" : scopes.some((scope) => scope.directive === "prohibition") ? "prohibition" : "informational";
|
|
1698
|
+
return interpret({
|
|
1699
|
+
text: normalized,
|
|
1700
|
+
body: scopes.map((scope) => scope.body).join(";"),
|
|
1701
|
+
directive
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
/** Interpret a whole message into independent scopes, in source order. */
|
|
1705
|
+
function interpretMessage(text, options = {}) {
|
|
1706
|
+
return scopeOf(normalizeClause(text), options).flatMap((scope) => splitTrailingResumption(scope)).map((scope) => interpret(scope));
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Whether the item is an executable obligation right now. A prohibition is a
|
|
1710
|
+
* standing constraint, a human-owned action belongs to the user, a conditional
|
|
1711
|
+
* action waits for its condition, and an explanation is not work. None of them
|
|
1712
|
+
* may block completion or be certified as agent work.
|
|
1713
|
+
*
|
|
1714
|
+
* An item without an interpretation is a legacy or fixture item created before
|
|
1715
|
+
* this module existed; it keeps its historical executable reading.
|
|
1716
|
+
*/
|
|
1717
|
+
function isExecutableItem(item) {
|
|
1718
|
+
if (item.kind === "prohibition") return false;
|
|
1719
|
+
if (item.waitAuthorization !== void 0) return false;
|
|
1720
|
+
if (item.authorityDisposition === void 0) return true;
|
|
1721
|
+
if (item.authorityDisposition !== "executable_now") return false;
|
|
1722
|
+
return item.executee === void 0 || item.executee === "agent";
|
|
1723
|
+
}
|
|
1724
|
+
/** Whether an item is an open obligation for certification purposes. */
|
|
1725
|
+
function isOpenObligation(item) {
|
|
1726
|
+
return item.status === "pending" && isExecutableItem(item);
|
|
1727
|
+
}
|
|
1728
|
+
/**
|
|
1729
|
+
* The action a scope names. `semanticActionFromText` maps the command surface,
|
|
1730
|
+
* but a prohibition keeps a bare verb as its body ("不要提交并推送" → 提交并推送),
|
|
1731
|
+
* and the closed CJK vocabulary is consulted first so such a ban is still
|
|
1732
|
+
* recorded against the action it forbids.
|
|
1733
|
+
*/
|
|
1734
|
+
function semanticActionOfScope(body, source = body, isProhibition = false) {
|
|
1735
|
+
const masked = maskCodeSpans(source);
|
|
1736
|
+
const negation = isProhibition ? {
|
|
1737
|
+
index: 0,
|
|
1738
|
+
token: ""
|
|
1739
|
+
} : firstNegation(masked);
|
|
1740
|
+
if (negation) {
|
|
1741
|
+
const banned = bannedVerbIndex(masked, negation.index + negation.token.length, masked.length);
|
|
1742
|
+
if (banned >= 0) {
|
|
1743
|
+
const action = semanticActionFromText(CJK_VERB_WORDS.find((entry) => masked.startsWith(entry, banned)) ?? masked.slice(banned, banned + 2));
|
|
1744
|
+
if (action !== "generic_run") return action;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
return semanticActionFromText(body);
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Split a scope whose action is stated after a resumption clause: "请先测试,
|
|
1751
|
+
* 收到我的确认后再推送" runs the test now and reserves the push for the
|
|
1752
|
+
* confirmation. Only a comma-separated split is used, so the earlier action
|
|
1753
|
+
* keeps its own executable meaning and the later one waits.
|
|
1754
|
+
*/
|
|
1755
|
+
function splitTrailingResumption(scope) {
|
|
1756
|
+
if (scope.condition !== void 0 || scope.directive === "prohibition") return [scope];
|
|
1757
|
+
const masked = maskCodeSpans(scope.text);
|
|
1758
|
+
const marker = RESUMPTION_EVENT.exec(masked);
|
|
1759
|
+
if (!marker || marker.index === 0) return [scope];
|
|
1760
|
+
if (firstActionVerb(masked.slice(0, marker.index)) < 0) return [scope];
|
|
1761
|
+
const boundary = masked.slice(0, marker.index).search(/[,,][^,,]*$/);
|
|
1762
|
+
if (boundary < 0) return [scope];
|
|
1763
|
+
const head = scope.text.slice(0, boundary + 1).trim();
|
|
1764
|
+
const rest = scope.text.slice(boundary + 1).trim();
|
|
1765
|
+
if (!head || !rest) return [scope];
|
|
1766
|
+
if (firstActionVerb(maskCodeSpans(head)) < 0) return [scope];
|
|
1767
|
+
if (firstActionVerb(maskCodeSpans(rest)) < 0) return [scope];
|
|
1768
|
+
return [{
|
|
1769
|
+
text: head,
|
|
1770
|
+
body: head,
|
|
1771
|
+
directive: classifyPositive(head)
|
|
1772
|
+
}, {
|
|
1773
|
+
text: rest,
|
|
1774
|
+
body: rest,
|
|
1775
|
+
directive: "directive"
|
|
1776
|
+
}];
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Every stateful action the clause names, in source order. A clause may order
|
|
1780
|
+
* more than one ("安装插件,重启 DSH"); each is a separate evidence obligation
|
|
1781
|
+
* even though the clause stays one top-level item.
|
|
1782
|
+
*/
|
|
1783
|
+
function statefulActionsOfScope(body) {
|
|
1784
|
+
const masked = maskCodeSpans(body);
|
|
1785
|
+
const found = [];
|
|
1786
|
+
const consider = (at, word) => {
|
|
1787
|
+
const action = semanticActionFromText(word);
|
|
1788
|
+
if (isStatefulAction(action)) found.push({
|
|
1789
|
+
at,
|
|
1790
|
+
action
|
|
1791
|
+
});
|
|
793
1792
|
};
|
|
1793
|
+
for (const word of CJK_VERB_WORDS) {
|
|
1794
|
+
let at = masked.indexOf(word);
|
|
1795
|
+
while (at >= 0) {
|
|
1796
|
+
consider(at, word);
|
|
1797
|
+
at = masked.indexOf(word, at + word.length);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
for (const match of masked.matchAll(/\b(?:install|apply|restart|reload|commit|push|publish|pull|fetch|create|modify|edit)\b/gi)) consider(match.index, match[0]);
|
|
1801
|
+
found.sort((a, b) => a.at - b.at);
|
|
1802
|
+
const ordered = [];
|
|
1803
|
+
for (const entry of found) if (ordered.at(-1) !== entry.action) ordered.push(entry.action);
|
|
1804
|
+
return ordered;
|
|
1805
|
+
}
|
|
1806
|
+
/** Actions this interpretation names, in source order (diagnostics only). */
|
|
1807
|
+
function namedActions(text) {
|
|
1808
|
+
return interpretMessage(text).map((scope) => semanticActionOfScope(scope.body)).filter((action) => action !== "generic_run");
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
//#endregion
|
|
1812
|
+
//#region src/domain/capture.ts
|
|
1813
|
+
/**
|
|
1814
|
+
* Whether a clause opens with an explicit ban. The lane question ("is this a
|
|
1815
|
+
* constraint or a duty?") is answered by {@link ScopeInterpretation}; this stays
|
|
1816
|
+
* exported because the framing/segmentation callers ask it directly.
|
|
1817
|
+
*/
|
|
1818
|
+
function classifyClause(text) {
|
|
1819
|
+
const [first] = interpretMessage(normalizeClause(text));
|
|
1820
|
+
return first ? kindOfScope(first.directive, first.body) : "requirement";
|
|
794
1821
|
}
|
|
795
1822
|
const METHOD_TOOL = "(?:bash|shell|powershell|pwsh|git|read|write|edit|node|python|python3|npm|pnpm|tsc|vitest)";
|
|
796
1823
|
const METHOD_ALIASES = {
|
|
@@ -845,21 +1872,71 @@ function isInformationalMessage(text) {
|
|
|
845
1872
|
function extractOperation(text) {
|
|
846
1873
|
for (const [operation, pattern] of OPERATION_PATTERNS) if (pattern.test(text)) return operation;
|
|
847
1874
|
}
|
|
848
|
-
|
|
1875
|
+
/**
|
|
1876
|
+
* A target token, as a human writes it.
|
|
1877
|
+
*
|
|
1878
|
+
* A path and a bare word do not start the same way: "/work/repo" and "./repo"
|
|
1879
|
+
* open with a separator, so a leading character class of letters, digits and
|
|
1880
|
+
* "@" cannot match them at all, and the field silently falls back to an
|
|
1881
|
+
* unrelated value. Paths therefore get their own branch, which requires the
|
|
1882
|
+
* separator plus at least one more character — a lone "/" is punctuation, not
|
|
1883
|
+
* a path.
|
|
1884
|
+
*/
|
|
1885
|
+
const TARGET_TAIL = "[\\p{L}\\p{N}@._/\\\\:+%?&=#\\[\\]-]";
|
|
1886
|
+
const TARGET_TOKEN = `(?:\`[^\`]+\`|"[^"]+"|'[^']+'|${`[.~]*[\\\\/]${TARGET_TAIL}+`}|${`[\\p{L}\\p{N}@]${TARGET_TAIL}*`})`;
|
|
849
1887
|
function unquoteTargetToken(value) {
|
|
850
1888
|
if (!value) return void 0;
|
|
851
1889
|
const trimmed = value.trim().replace(/[.,;,。;]+$/, "");
|
|
852
1890
|
const unquoted = /^(?:`([^`]+)`|"([^"]+)"|'([^']+)')$/.exec(trimmed);
|
|
853
1891
|
return (unquoted?.[1] ?? unquoted?.[2] ?? unquoted?.[3] ?? trimmed) || void 0;
|
|
854
1892
|
}
|
|
1893
|
+
/**
|
|
1894
|
+
* The value of a labelled field ("repository X", "版本:1.2.3").
|
|
1895
|
+
*
|
|
1896
|
+
* The label must END where it ends: a label that is only a prefix of a longer
|
|
1897
|
+
* word is skipped, so the literal word "repository" is never read as the label
|
|
1898
|
+
* "repo" followed by the value "sitory".
|
|
1899
|
+
*/
|
|
855
1900
|
function labeledToken(text, labels) {
|
|
856
|
-
|
|
1901
|
+
const label = new RegExp(`(?:${labels})`, "iu");
|
|
1902
|
+
const after = new RegExp(`^(?![\\p{L}\\p{N}_])\\s*(?:[:=:]|为|是)?\\s*(${TARGET_TOKEN})`, "iu");
|
|
1903
|
+
const OTHER_LABEL = /^(?:to|from|on|into|with|at|using|version|profile|registry|remote|refspec|branch|service|repository|repo|包|插件|制品|服务|仓库|版本|配置档|远端|分支|注册表)$/i;
|
|
1904
|
+
let cursor = 0;
|
|
1905
|
+
while (cursor <= text.length) {
|
|
1906
|
+
const match = label.exec(text.slice(cursor));
|
|
1907
|
+
if (!match) return void 0;
|
|
1908
|
+
cursor = cursor + match.index + match[0].length;
|
|
1909
|
+
const value = unquoteTargetToken(after.exec(text.slice(cursor))?.[1]);
|
|
1910
|
+
if (value && !OTHER_LABEL.test(value)) return value;
|
|
1911
|
+
if (cursor >= text.length) return void 0;
|
|
1912
|
+
}
|
|
857
1913
|
}
|
|
1914
|
+
/**
|
|
1915
|
+
* The object a verb acts on. The verb is matched first, then — separately — an
|
|
1916
|
+
* optional noun that has to end at a word boundary, and only the text AFTER
|
|
1917
|
+
* that noun is the target. Matching the noun and the token in one pattern let
|
|
1918
|
+
* the noun eat a prefix of the real word ("repository" consumed as "repo" +
|
|
1919
|
+
* "sitory"), which captured "sitory" as a repository name.
|
|
1920
|
+
*/
|
|
858
1921
|
function actionObjectToken(text, verbs, nouns) {
|
|
859
|
-
const
|
|
1922
|
+
const verb = new RegExp(`(?:${verbs})`, "iu").exec(text);
|
|
1923
|
+
if (!verb) return void 0;
|
|
1924
|
+
let cursor = verb.index + verb[0].length;
|
|
1925
|
+
const noun = new RegExp(`^\\s*(?:${nouns})(?![\\p{L}\\p{N}_])`, "iu").exec(text.slice(cursor));
|
|
1926
|
+
if (noun) cursor += noun[0].length;
|
|
1927
|
+
else cursor += text.slice(cursor).match(/^\s*[\p{Script=Han}]{0,2}\s*/u)?.[0].length ?? 0;
|
|
1928
|
+
const rest = text.slice(cursor).replace(/^\s*(?:[:=:]|为)?\s*/u, "");
|
|
1929
|
+
const token = unquoteTargetToken(new RegExp(`^(${TARGET_TOKEN})`, "u").exec(rest)?.[1]);
|
|
860
1930
|
if (!token || /^(?:the|a|an|this|that|to|from|in|on|into|with|package|plugin|artifact|service|repository|repo|包|插件|制品|服务|仓库)$/i.test(token)) return void 0;
|
|
861
1931
|
return token;
|
|
862
1932
|
}
|
|
1933
|
+
/** The verbs that name each repository-facing action, for unlabelled objects. */
|
|
1934
|
+
const GIT_OBJECT_VERB = {
|
|
1935
|
+
push: "push|推送",
|
|
1936
|
+
pull: "pull|拉取",
|
|
1937
|
+
fetch: "fetch|抓取|获取",
|
|
1938
|
+
commit: "commit|提交"
|
|
1939
|
+
};
|
|
863
1940
|
function splitPackageSpec(spec) {
|
|
864
1941
|
if (!spec) return {};
|
|
865
1942
|
const at = spec.lastIndexOf("@");
|
|
@@ -927,7 +2004,7 @@ function captureRequestedTarget(action, text, subject, surface) {
|
|
|
927
2004
|
} };
|
|
928
2005
|
}
|
|
929
2006
|
if (action === "pull" || action === "fetch" || action === "commit" || action === "push") {
|
|
930
|
-
const repository = labeledToken(text, "repository|repo|仓库") ?? (subject !== "scope" ? subject : void 0);
|
|
2007
|
+
const repository = labeledToken(text, "repository|repo|仓库") ?? actionObjectToken(text, GIT_OBJECT_VERB[action], "repository|repo|仓库") ?? (subject !== "scope" ? subject : void 0);
|
|
931
2008
|
if (!repository) return {
|
|
932
2009
|
target: {},
|
|
933
2010
|
reasonCode: "requested_target_repository_missing"
|
|
@@ -966,29 +2043,29 @@ function extractArtifactPaths(text) {
|
|
|
966
2043
|
}
|
|
967
2044
|
return [...found];
|
|
968
2045
|
}
|
|
969
|
-
function segmentClauses(text,
|
|
2046
|
+
function segmentClauses(text, options = {}) {
|
|
970
2047
|
const normalized = normalizeClause(text);
|
|
971
2048
|
if (!normalized) return [];
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
paths: extractArtifactPaths(body)
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
return segments;
|
|
2049
|
+
return interpretMessage(normalized, options).filter((interpretation) => interpretation.text.trim().length > 0).map((interpretation) => ({
|
|
2050
|
+
kind: kindOfScope(interpretation.directive, interpretation.body),
|
|
2051
|
+
body: interpretation.body,
|
|
2052
|
+
text: interpretation.text,
|
|
2053
|
+
paths: extractArtifactPaths(interpretation.body),
|
|
2054
|
+
interpretation
|
|
2055
|
+
}));
|
|
983
2056
|
}
|
|
984
2057
|
/**
|
|
985
2058
|
* Build a GuardItem from an already-classified clause body and a resolved
|
|
986
2059
|
* verification subject/surface.
|
|
2060
|
+
*
|
|
2061
|
+
* The optional `interpretation` carries the scope reading taken from the same
|
|
2062
|
+
* bytes. It is passed through rather than re-derived, so the obligation lane and
|
|
2063
|
+
* the authority of one clause cannot disagree between callers.
|
|
987
2064
|
*/
|
|
988
|
-
function captureItem(kind, body, sourceMessageId, id, revision, subject, surface, method, operation,
|
|
2065
|
+
function captureItem(kind, body, sourceMessageId, id, revision, subject, surface, method, operation, interpretation) {
|
|
989
2066
|
const sanitized = sanitizeClauseText(body);
|
|
990
|
-
const unsupportedVisual =
|
|
991
|
-
const semanticAction = unsupportedVisual ? "generic_run" :
|
|
2067
|
+
const unsupportedVisual = /\bGUI\b|界面|视觉|截图|颜色|布局|视觉效果/i.test(sanitized);
|
|
2068
|
+
const semanticAction = unsupportedVisual ? "generic_run" : semanticActionOfScope(sanitized, interpretation?.text ?? sanitized, kind === "prohibition");
|
|
992
2069
|
const capturedTarget = captureRequestedTarget(semanticAction, sanitized, subject, surface);
|
|
993
2070
|
const effectiveOperation = semanticAction === "verify" ? "verify" : operation;
|
|
994
2071
|
const item = {
|
|
@@ -1015,9 +2092,18 @@ function captureItem(kind, body, sourceMessageId, id, revision, subject, surface
|
|
|
1015
2092
|
targetCaptureStatus: capturedTarget.reasonCode ? "clarification_required" : "resolved",
|
|
1016
2093
|
...capturedTarget.reasonCode ? { targetCaptureReasonCode: capturedTarget.reasonCode } : {},
|
|
1017
2094
|
taskKind: kind === "prohibition" ? void 0 : classifyTaskIntent(sanitized),
|
|
1018
|
-
authority: "root_instruction"
|
|
2095
|
+
authority: "root_instruction",
|
|
2096
|
+
...kind === "requirement" ? buildActionPlan(sanitized, subject, surface, semanticAction) : {},
|
|
2097
|
+
...interpretation ? {
|
|
2098
|
+
directive: interpretation.directive,
|
|
2099
|
+
executee: interpretation.executee,
|
|
2100
|
+
authorityDisposition: interpretation.authorityDisposition,
|
|
2101
|
+
...interpretation.condition ? { condition: interpretation.condition } : {},
|
|
2102
|
+
...interpretation.resumeEvent ? { resumeEvent: interpretation.resumeEvent } : {},
|
|
2103
|
+
interpretationFingerprint: interpretation.fingerprint
|
|
2104
|
+
} : {}
|
|
1019
2105
|
};
|
|
1020
|
-
if (/(?:等待|暂停|等).{0,12}(?:用户|你|您|我).{0,12}(?:选择|确认|输入)(?:.{0,8}(?:后|再)?继续)?|收到.{0,8}(?:用户|你|您|我)?的?确认.{0,8}(?:后)?再继续|\bwait for (?:the )?(?:user|your)\b|\bcontinue only after (?:the )?(?:user's?|your) confirmation\b/i.test(sanitized)) item.waitAuthorization = {
|
|
2106
|
+
if ((interpretation ? interpretation.authorityDisposition !== "executable_now" && (interpretation.resumeEvent !== void 0 || interpretation.authorityDisposition === "conditional_wait") : false) || /(?:等待|暂停|等).{0,12}(?:用户|你|您|我).{0,12}(?:选择|确认|输入)(?:.{0,8}(?:后|再)?继续)?|收到.{0,8}(?:用户|你|您|我)?的?确认.{0,8}(?:后)?再继续|\bwait for (?:the )?(?:user|your)\b|\bcontinue only after (?:the )?(?:user's?|your) confirmation\b/i.test(sanitized)) item.waitAuthorization = {
|
|
1021
2107
|
kind: "root_explicit_wait",
|
|
1022
2108
|
id: `wait:${id}:${sha256(sanitized).slice(0, 12)}`
|
|
1023
2109
|
};
|
|
@@ -1035,16 +2121,33 @@ function captureItem(kind, body, sourceMessageId, id, revision, subject, surface
|
|
|
1035
2121
|
};
|
|
1036
2122
|
return item;
|
|
1037
2123
|
}
|
|
2124
|
+
/** The stateful actions a clause names, with the target captured for each. */
|
|
2125
|
+
function buildActionPlan(body, subject, surface, primary) {
|
|
2126
|
+
const actions = statefulActionsOfScope(body);
|
|
2127
|
+
if (actions.length === 0 && isStatefulAction(primary)) actions.push(primary);
|
|
2128
|
+
if (actions.length <= 1) return {};
|
|
2129
|
+
return { actionPlan: actions.map((action) => {
|
|
2130
|
+
const captured = captureRequestedTarget(action, body, subject, surface);
|
|
2131
|
+
return {
|
|
2132
|
+
action,
|
|
2133
|
+
requestedTarget: captured.target,
|
|
2134
|
+
targetCaptureStatus: captured.reasonCode ? "clarification_required" : "resolved",
|
|
2135
|
+
...captured.reasonCode ? { targetCaptureReasonCode: captured.reasonCode } : {}
|
|
2136
|
+
};
|
|
2137
|
+
}) };
|
|
2138
|
+
}
|
|
1038
2139
|
/**
|
|
1039
2140
|
* Capture one contract clause. Every captured item receives a concrete
|
|
1040
2141
|
* verification contract: a named artifact path (artifact surface) or the
|
|
1041
2142
|
* session scope (scope surface), so an unrelated file read can never close it.
|
|
1042
2143
|
*/
|
|
1043
|
-
function captureClause(text, sourceMessageId, id, revision, scope = {}) {
|
|
1044
|
-
const
|
|
2144
|
+
function captureClause(text, sourceMessageId, id, revision, scope = {}, options = {}) {
|
|
2145
|
+
const [interpretation] = interpretMessage(text, options);
|
|
2146
|
+
const kind = interpretation ? kindOfScope(interpretation.directive, interpretation.body) : "requirement";
|
|
2147
|
+
const body = interpretation?.body ?? text;
|
|
1045
2148
|
const path$1 = extractArtifactPaths(sanitizeClauseText(body))[0] ?? "";
|
|
1046
2149
|
const surface = path$1 ? "artifact" : "scope";
|
|
1047
|
-
return captureItem(kind, body, sourceMessageId, id, revision, path$1 || scope.cwd || "scope", surface, extractMethod(body), extractOperation(body));
|
|
2150
|
+
return captureItem(kind, body, sourceMessageId, id, revision, path$1 || scope.cwd || "scope", surface, interpretation?.method ?? extractMethod(body), extractOperation(body), interpretation);
|
|
1048
2151
|
}
|
|
1049
2152
|
|
|
1050
2153
|
//#endregion
|
|
@@ -1115,6 +2218,19 @@ function deriveItemDiagnosis(p, item) {
|
|
|
1115
2218
|
},
|
|
1116
2219
|
attempt_fingerprint: fingerprint(p, item, "certified")
|
|
1117
2220
|
};
|
|
2221
|
+
if (item.status === "pending" && item.waitAuthorization?.kind === "root_explicit_wait") return {
|
|
2222
|
+
...base,
|
|
2223
|
+
certification: "unavailable",
|
|
2224
|
+
reason_code: "root_condition_pending",
|
|
2225
|
+
repairability: "user_input_required",
|
|
2226
|
+
missing_fields: [],
|
|
2227
|
+
missing_facets: [],
|
|
2228
|
+
next_action: {
|
|
2229
|
+
kind: "none",
|
|
2230
|
+
resume_condition: `Wait for the matching trusted root input: ${item.resumeEvent ?? item.condition ?? item.normalizedText}. Keep this obligation pending; do not execute it or collect effect evidence before release.`
|
|
2231
|
+
},
|
|
2232
|
+
attempt_fingerprint: fingerprint(p, item, "root_condition_pending")
|
|
2233
|
+
};
|
|
1118
2234
|
if (action !== "generic_run" && !item.legacyFlags?.length && item.targetCaptureStatus === "clarification_required") {
|
|
1119
2235
|
const missingFields = item.targetCaptureReasonCode ? [TARGET_FIELD_REASONS[item.targetCaptureReasonCode] ?? item.targetCaptureReasonCode] : [];
|
|
1120
2236
|
return {
|
|
@@ -1630,11 +2746,11 @@ function rebindResponse(p, args) {
|
|
|
1630
2746
|
revision: item.revision,
|
|
1631
2747
|
kind: item.kind,
|
|
1632
2748
|
status: item.status,
|
|
1633
|
-
semantic_action: item.semanticAction,
|
|
1634
|
-
target_capture_status: item.targetCaptureStatus
|
|
2749
|
+
...item.semanticAction !== void 0 ? { semantic_action: item.semanticAction } : {},
|
|
2750
|
+
...item.targetCaptureStatus !== void 0 ? { target_capture_status: item.targetCaptureStatus } : {}
|
|
1635
2751
|
},
|
|
1636
2752
|
diagnosis: deriveItemDiagnosis(p, item),
|
|
1637
|
-
pending_proposal_id: pendingProposal
|
|
2753
|
+
...pendingProposal ? { pending_proposal_id: pendingProposal.id } : {}
|
|
1638
2754
|
};
|
|
1639
2755
|
}
|
|
1640
2756
|
const proposal = p.rebindProposals.get(args.proposal_id ?? "");
|
|
@@ -1810,6 +2926,8 @@ function createProjection() {
|
|
|
1810
2926
|
lastGuardEventSeq: -1,
|
|
1811
2927
|
continuationAttempts: /* @__PURE__ */ new Map(),
|
|
1812
2928
|
persistenceCorrectionAttempts: /* @__PURE__ */ new Map(),
|
|
2929
|
+
noProgressClaims: /* @__PURE__ */ new Map(),
|
|
2930
|
+
handledControlSeqs: /* @__PURE__ */ new Set(),
|
|
1813
2931
|
rebindRejections: /* @__PURE__ */ new Map(),
|
|
1814
2932
|
integrity: "valid"
|
|
1815
2933
|
};
|
|
@@ -1836,52 +2954,593 @@ function currentContractDigest(projection) {
|
|
|
1836
2954
|
}
|
|
1837
2955
|
|
|
1838
2956
|
//#endregion
|
|
1839
|
-
//#region src/domain/
|
|
1840
|
-
/**
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
"
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2957
|
+
//#region src/domain/boundary.ts
|
|
2958
|
+
/** Bounded, replay-derived qualifications that callers may cite verbatim. */
|
|
2959
|
+
function availableBoundaryQualifications(projection) {
|
|
2960
|
+
const rows = [];
|
|
2961
|
+
for (const item of projection.items.values()) {
|
|
2962
|
+
if (item.status !== "pending") continue;
|
|
2963
|
+
if (item.waitAuthorization) rows.push({
|
|
2964
|
+
id: item.waitAuthorization.id,
|
|
2965
|
+
kind: item.waitAuthorization.kind,
|
|
2966
|
+
disposition: "user_wait",
|
|
2967
|
+
source: "root_contract",
|
|
2968
|
+
status: "pending"
|
|
2969
|
+
});
|
|
2970
|
+
if (item.deferAuthorization) rows.push({
|
|
2971
|
+
id: item.deferAuthorization.id,
|
|
2972
|
+
kind: item.deferAuthorization.kind,
|
|
2973
|
+
disposition: "deferred",
|
|
2974
|
+
source: "root_contract",
|
|
2975
|
+
status: "pending"
|
|
2976
|
+
});
|
|
2977
|
+
}
|
|
2978
|
+
for (const operation of projection.externalOperations.values()) {
|
|
2979
|
+
if (operation.epoch !== projection.epoch || operation.status !== "pending" && operation.status !== "running") continue;
|
|
2980
|
+
rows.push({
|
|
2981
|
+
id: operation.id,
|
|
2982
|
+
kind: "external_operation_pending",
|
|
2983
|
+
disposition: "external_wait",
|
|
2984
|
+
source: "trusted_adapter",
|
|
2985
|
+
status: operation.status
|
|
2986
|
+
});
|
|
2987
|
+
}
|
|
2988
|
+
return rows.sort((a, b) => a.id.localeCompare(b.id)).slice(0, 32);
|
|
2989
|
+
}
|
|
2990
|
+
function qualificationReason(projection, request) {
|
|
2991
|
+
const ids = new Set(request.qualificationIds);
|
|
2992
|
+
if (ids.size !== request.qualificationIds.length || ids.size === 0) return "boundary_qualification_ids_invalid";
|
|
2993
|
+
if (request.disposition === "user_wait") {
|
|
2994
|
+
if (request.qualificationKind !== "root_explicit_wait" && request.qualificationKind !== "user_decision_item") return "boundary_qualification_kind_mismatch";
|
|
2995
|
+
const known$1 = new Set([...projection.items.values()].filter((item) => item.status === "pending" && item.waitAuthorization?.kind === request.qualificationKind).map((item) => item.waitAuthorization.id));
|
|
2996
|
+
return request.qualificationIds.every((id) => known$1.has(id)) ? void 0 : "boundary_disposition_unqualified";
|
|
2997
|
+
}
|
|
2998
|
+
if (request.disposition === "external_wait") {
|
|
2999
|
+
if (request.qualificationKind !== "external_operation_pending") return "boundary_qualification_kind_mismatch";
|
|
3000
|
+
return request.qualificationIds.every((id) => {
|
|
3001
|
+
const operation = projection.externalOperations.get(id);
|
|
3002
|
+
return operation?.epoch === projection.epoch && (operation.status === "running" || operation.status === "pending");
|
|
3003
|
+
}) ? void 0 : "boundary_disposition_unqualified";
|
|
3004
|
+
}
|
|
3005
|
+
if (request.disposition === "guard_bounded_stop") {
|
|
3006
|
+
if (request.qualificationKind !== "guard_no_progress") return "boundary_qualification_kind_mismatch";
|
|
3007
|
+
const fingerprint$1 = request.qualificationIds.length === 1 ? request.qualificationIds[0] : void 0;
|
|
3008
|
+
if (!fingerprint$1 || fingerprint$1 !== progressFingerprint(projection)) return "boundary_disposition_unqualified";
|
|
3009
|
+
return (projection.noProgressClaims.get(fingerprint$1)?.size ?? 0) >= NO_PROGRESS_TURNS_BEFORE_STOP - 1 ? void 0 : "boundary_disposition_unqualified";
|
|
3010
|
+
}
|
|
3011
|
+
if (request.qualificationKind !== "root_explicit_defer") return "boundary_qualification_kind_mismatch";
|
|
3012
|
+
const known = new Set([...projection.items.values()].filter((item) => item.status === "pending" && item.deferAuthorization?.kind === request.qualificationKind).map((item) => item.deferAuthorization.id));
|
|
3013
|
+
return request.qualificationIds.every((id) => known.has(id)) ? void 0 : "boundary_disposition_unqualified";
|
|
3014
|
+
}
|
|
3015
|
+
function qualifyBoundary(projection, request) {
|
|
3016
|
+
const contractSha256 = currentContractDigest(projection);
|
|
3017
|
+
const reason = projection.integrity !== "valid" ? "boundary_integrity_invalid" : projection.hostStatus !== "supported" && projection.currentGoalRef ? "boundary_host_lock_unsupported" : qualificationReason(projection, request);
|
|
3018
|
+
const manifest = {
|
|
3019
|
+
protocolVersion: "1",
|
|
3020
|
+
disposition: request.disposition,
|
|
3021
|
+
qualificationKind: request.qualificationKind,
|
|
3022
|
+
qualificationIds: [...request.qualificationIds].sort(),
|
|
3023
|
+
epoch: projection.epoch,
|
|
3024
|
+
contractRevision: projection.contractRevision,
|
|
3025
|
+
contractSha256,
|
|
3026
|
+
goalRef: projection.currentGoalRef ?? null
|
|
3027
|
+
};
|
|
3028
|
+
const candidateSha256 = sha256(JSON.stringify(manifest));
|
|
3029
|
+
return {
|
|
3030
|
+
protocolVersion: "1",
|
|
3031
|
+
id: `B${projection.boundaries.length + 1}`,
|
|
3032
|
+
disposition: request.disposition,
|
|
3033
|
+
qualificationKind: request.qualificationKind,
|
|
3034
|
+
qualificationIds: [...request.qualificationIds],
|
|
3035
|
+
epoch: projection.epoch,
|
|
3036
|
+
contractRevision: projection.contractRevision,
|
|
3037
|
+
contractSha256,
|
|
3038
|
+
...projection.currentGoalRef ? { goalRef: { ...projection.currentGoalRef } } : {},
|
|
3039
|
+
candidateSha256,
|
|
3040
|
+
...request.callId ? { callId: request.callId } : {},
|
|
3041
|
+
persistedResult: reason ? "rejected" : "accepted",
|
|
3042
|
+
reasonCode: reason ?? "boundary_persisted_accepted"
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Reconstruct the immutable candidate against the latest replay projection.
|
|
3047
|
+
* A persisted acceptance is not effectuation authority after any contract,
|
|
3048
|
+
* Goal, epoch, or qualification change.
|
|
3049
|
+
*/
|
|
3050
|
+
function isCurrentAcceptedBoundary(projection, boundary) {
|
|
3051
|
+
if (boundary.persistedResult !== "accepted" || boundary.epoch !== projection.epoch || boundary.contractRevision !== projection.contractRevision || boundary.contractSha256 !== currentContractDigest(projection)) return false;
|
|
3052
|
+
const currentGoal = projection.currentGoalRef;
|
|
3053
|
+
if (boundary.goalRef ? !currentGoal || !sameRef(currentGoal, boundary.goalRef) : currentGoal !== void 0) return false;
|
|
3054
|
+
const reconstructed = qualifyBoundary(projection, {
|
|
3055
|
+
disposition: boundary.disposition,
|
|
3056
|
+
qualificationKind: boundary.qualificationKind,
|
|
3057
|
+
qualificationIds: boundary.qualificationIds,
|
|
3058
|
+
...boundary.callId ? { callId: boundary.callId } : {}
|
|
3059
|
+
});
|
|
3060
|
+
return reconstructed.persistedResult === "accepted" && reconstructed.candidateSha256 === boundary.candidateSha256;
|
|
3061
|
+
}
|
|
3062
|
+
function sameRef(state, ref) {
|
|
3063
|
+
return state?.id === ref.id && state.revision === ref.revision;
|
|
3064
|
+
}
|
|
3065
|
+
/**
|
|
3066
|
+
* Effectuate only a replay-confirmed accepted boundary. The first disarm result
|
|
3067
|
+
* and an independent get() must both read the same active Goal ref as disarmed.
|
|
3068
|
+
* A failure after disarm may have taken effect is never auto-rearmed.
|
|
3069
|
+
*/
|
|
3070
|
+
async function effectuateBoundary(boundary, access) {
|
|
3071
|
+
const base = {
|
|
3072
|
+
boundaryId: boundary.id,
|
|
3073
|
+
...boundary.goalRef ? { goalRef: boundary.goalRef } : {}
|
|
3074
|
+
};
|
|
3075
|
+
if (boundary.persistedResult !== "accepted") return {
|
|
3076
|
+
...base,
|
|
3077
|
+
reasonCode: "boundary_not_accepted",
|
|
3078
|
+
stopAllowed: false,
|
|
3079
|
+
resumeRequired: false
|
|
3080
|
+
};
|
|
3081
|
+
if (access.requalify) try {
|
|
3082
|
+
if (!await access.requalify()) return {
|
|
3083
|
+
...base,
|
|
3084
|
+
reasonCode: "boundary_pre_effect_failure",
|
|
3085
|
+
stopAllowed: false,
|
|
3086
|
+
resumeRequired: false
|
|
3087
|
+
};
|
|
3088
|
+
} catch {
|
|
3089
|
+
return {
|
|
3090
|
+
...base,
|
|
3091
|
+
reasonCode: "boundary_pre_effect_failure",
|
|
3092
|
+
stopAllowed: false,
|
|
3093
|
+
resumeRequired: false
|
|
3094
|
+
};
|
|
3095
|
+
}
|
|
3096
|
+
if (!boundary.goalRef) return {
|
|
3097
|
+
...base,
|
|
3098
|
+
reasonCode: "boundary_no_goal_safe_yield",
|
|
3099
|
+
stopAllowed: true,
|
|
3100
|
+
resumeRequired: false
|
|
3101
|
+
};
|
|
3102
|
+
let before;
|
|
3103
|
+
try {
|
|
3104
|
+
before = await access.get();
|
|
3105
|
+
} catch {
|
|
3106
|
+
return {
|
|
3107
|
+
...base,
|
|
3108
|
+
reasonCode: "boundary_pre_effect_failure",
|
|
3109
|
+
stopAllowed: false,
|
|
3110
|
+
resumeRequired: false
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
if (!sameRef(before, boundary.goalRef) || before?.phase !== "active") return {
|
|
3114
|
+
...base,
|
|
3115
|
+
reasonCode: "boundary_goal_ref_stale",
|
|
3116
|
+
stopAllowed: false,
|
|
3117
|
+
resumeRequired: false
|
|
3118
|
+
};
|
|
3119
|
+
if (before.activation === "disarmed") return {
|
|
3120
|
+
...base,
|
|
3121
|
+
reasonCode: "boundary_already_disarmed",
|
|
3122
|
+
stopAllowed: true,
|
|
3123
|
+
resumeRequired: false
|
|
3124
|
+
};
|
|
3125
|
+
let firstReadback;
|
|
3126
|
+
try {
|
|
3127
|
+
firstReadback = await access.disarm();
|
|
3128
|
+
} catch {
|
|
3129
|
+
return {
|
|
3130
|
+
...base,
|
|
3131
|
+
reasonCode: "boundary_post_effect_unknown",
|
|
3132
|
+
stopAllowed: false,
|
|
3133
|
+
resumeRequired: true
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
if (!firstReadback || !sameRef(firstReadback, boundary.goalRef) || firstReadback.phase !== "active") return {
|
|
3137
|
+
...base,
|
|
3138
|
+
reasonCode: "boundary_post_effect_unknown",
|
|
3139
|
+
stopAllowed: false,
|
|
3140
|
+
resumeRequired: true
|
|
3141
|
+
};
|
|
3142
|
+
if (firstReadback.activation !== "disarmed") return {
|
|
3143
|
+
...base,
|
|
3144
|
+
reasonCode: "boundary_readback_still_armed",
|
|
3145
|
+
stopAllowed: false,
|
|
3146
|
+
resumeRequired: false
|
|
3147
|
+
};
|
|
3148
|
+
try {
|
|
3149
|
+
const independent = await access.get();
|
|
3150
|
+
if (!sameRef(independent, boundary.goalRef) || independent?.phase !== "active") return {
|
|
3151
|
+
...base,
|
|
3152
|
+
reasonCode: "boundary_post_effect_unknown",
|
|
3153
|
+
stopAllowed: false,
|
|
3154
|
+
resumeRequired: true
|
|
3155
|
+
};
|
|
3156
|
+
if (independent.activation !== "disarmed") return {
|
|
3157
|
+
...base,
|
|
3158
|
+
reasonCode: "boundary_readback_still_armed",
|
|
3159
|
+
stopAllowed: false,
|
|
3160
|
+
resumeRequired: false
|
|
3161
|
+
};
|
|
3162
|
+
} catch {
|
|
3163
|
+
return {
|
|
3164
|
+
...base,
|
|
3165
|
+
reasonCode: "boundary_post_effect_unknown",
|
|
3166
|
+
stopAllowed: false,
|
|
3167
|
+
resumeRequired: true
|
|
3168
|
+
};
|
|
3169
|
+
}
|
|
3170
|
+
return {
|
|
3171
|
+
...base,
|
|
3172
|
+
reasonCode: "boundary_effectuated",
|
|
3173
|
+
stopAllowed: true,
|
|
3174
|
+
resumeRequired: false
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
//#endregion
|
|
3179
|
+
//#region src/domain/goal-gate.ts
|
|
3180
|
+
function hasCurrentCertificate(projection) {
|
|
3181
|
+
const checkpoint = projection.checkpoints.at(-1);
|
|
3182
|
+
let reason;
|
|
3183
|
+
if (projection.integrity !== "valid") reason = "integrity_invalid";
|
|
3184
|
+
else if (projection.hostStatus !== "supported") reason = "host_lock_unsupported";
|
|
3185
|
+
else if (!checkpoint || checkpoint.result !== "certified") reason = "certificate_missing";
|
|
3186
|
+
else if (checkpoint.epoch !== projection.epoch) reason = "stale_epoch";
|
|
3187
|
+
else if (checkpoint.sessionRefDigest !== projection.sessionRefDigest) reason = "foreign_session";
|
|
3188
|
+
else if (checkpoint.hostLockDigest !== projection.hostLockDigest) reason = "stale_host_lock";
|
|
3189
|
+
else if (checkpoint.contractRevision !== projection.contractRevision) reason = "stale_contract_revision";
|
|
3190
|
+
else if (projection.currentGoalRef ? checkpoint.goalRef?.id !== projection.currentGoalRef.id || checkpoint.goalRef.revision !== projection.currentGoalRef.revision : checkpoint.goalRef !== void 0) reason = "stale_goal_ref";
|
|
3191
|
+
projection.certificateStatusReason = reason;
|
|
3192
|
+
return reason === void 0;
|
|
3193
|
+
}
|
|
3194
|
+
/**
|
|
3195
|
+
* Denies `update_goal(action=complete)` while the guard is enabled and no
|
|
3196
|
+
* current completion certificate exists. The gate itself has no bypass; a
|
|
3197
|
+
* workflow that genuinely finished but cannot certify (for example a contract
|
|
3198
|
+
* polluted by session-layer talk, or evidence that lives in another session)
|
|
3199
|
+
* has three explicit remediation routes:
|
|
3200
|
+
*
|
|
3201
|
+
* 1. `/context-guard off` disables the guard, so completion is no longer
|
|
3202
|
+
* gated. Use only after the user confirms the work is actually done.
|
|
3203
|
+
* 2. `/context-guard clear` supersedes every pending requirement and
|
|
3204
|
+
* acceptance under a `CLEAR:<revision>` sentinel (prohibitions are
|
|
3205
|
+
* retained) and bumps the contract revision; an empty-binding checkpoint
|
|
3206
|
+
* can then certify while the guard stays enabled.
|
|
3207
|
+
* 3. `update_goal(action=blocked)` records the blocker truthfully, which is
|
|
3208
|
+
* never denied by this gate.
|
|
3209
|
+
*/
|
|
3210
|
+
function goalCompletionDenial(projection, toolName, argumentsValue, configuredToolName = "update_goal") {
|
|
3211
|
+
if (toolName !== configuredToolName || typeof argumentsValue !== "object" || argumentsValue === null) return void 0;
|
|
3212
|
+
if (argumentsValue.action !== "complete") return void 0;
|
|
3213
|
+
if (!projection.enabled) return void 0;
|
|
3214
|
+
const args = argumentsValue;
|
|
3215
|
+
if (projection.hostStatus !== "supported") return `Context Guard denial [stale_host]: host lock is unsupported or unavailable (${projection.hostReasonCode ?? "unknown_host"}).`;
|
|
3216
|
+
if (!projection.currentGoalRef) return "Context Guard denial [no_goal]: no current Goal reference is available.";
|
|
3217
|
+
if (args.goal_id !== projection.currentGoalRef.id || args.revision !== projection.currentGoalRef.revision) return "Context Guard denial [stale_goal_ref]: update_goal must use the exact current goal_id and revision.";
|
|
3218
|
+
if (hasCurrentCertificate(projection)) return void 0;
|
|
3219
|
+
if (projection.certificateStatusReason === "stale_host_lock") return "Context Guard denial [stale_host]: the completion certificate belongs to a different host identity.";
|
|
3220
|
+
if (projection.certificateStatusReason === "stale_goal_ref") return "Context Guard denial [stale_goal_ref]: the completion certificate belongs to a different Goal reference.";
|
|
3221
|
+
return projection.integrity === "valid" ? "Context Guard denial [certificate_missing]: a current completion certificate is required." : "Context Guard denial [certificate_missing]: integrity is unknown or corrupt, so no current certificate is usable.";
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
//#endregion
|
|
3225
|
+
//#region src/domain/stop-policy.ts
|
|
3226
|
+
/**
|
|
3227
|
+
* What "relevant progress" means, as one value.
|
|
3228
|
+
*
|
|
3229
|
+
* The inputs are the recorded state a caller could not have faked without
|
|
3230
|
+
* changing the work itself: the epoch and contract revision, the open items and
|
|
3231
|
+
* their blockers, the qualified evidence set, the boundary qualifications
|
|
3232
|
+
* available right now, and the Goal's identity and activation. Deliberately
|
|
3233
|
+
* absent: timestamps, event counts, wording, checkpoint bodies, and the Goal
|
|
3234
|
+
* *revision* — editing a Goal's text is not progress, and treating it as such
|
|
3235
|
+
* would let a re-statement reset the stop budget.
|
|
3236
|
+
*/
|
|
3237
|
+
/**
|
|
3238
|
+
* How many times the same progress fingerprint must be observed at a turn
|
|
3239
|
+
* boundary before Guard stops the automatic continuation.
|
|
3240
|
+
*
|
|
3241
|
+
* The first sighting is a baseline, not a stalled turn: it is the state a turn
|
|
3242
|
+
* either advanced to or started from, and the host's driver owns continuation
|
|
3243
|
+
* there. The second sighting is the first turn that produced nothing new, which
|
|
3244
|
+
* earns the one diagnosis and correction opportunity. The third is the bounded
|
|
3245
|
+
* stop. The count is a resource bound on repetition, never a way to declare the
|
|
3246
|
+
* task finished.
|
|
3247
|
+
*/
|
|
3248
|
+
const NO_PROGRESS_TURNS_BEFORE_STOP = 3;
|
|
3249
|
+
/** Marks the durable no-progress record; replay reads the budget from these. */
|
|
3250
|
+
const NO_PROGRESS_RECORD_PREFIX = "Context Guard no-progress record: ";
|
|
3251
|
+
/**
|
|
3252
|
+
* The identity of the turn boundary a decision is taken at.
|
|
3253
|
+
*
|
|
3254
|
+
* Guard does not own the host's turn counter, and a retry must be recognisable
|
|
3255
|
+
* as the same boundary rather than as a new one. The last durable event is that
|
|
3256
|
+
* identity: it is derivable from the log alone, it is stable across a reload,
|
|
3257
|
+
* and it only advances when the session actually records something new.
|
|
3258
|
+
*/
|
|
3259
|
+
function decisionBoundaryKey(projection) {
|
|
3260
|
+
return projection.hostTurn;
|
|
3261
|
+
}
|
|
3262
|
+
function progressFingerprint(projection) {
|
|
3263
|
+
const open = [...projection.items.values()].filter((item) => item.status === "pending").map((item) => `${item.id}:${item.revision}:${item.normalizedText}`).sort();
|
|
3264
|
+
const evidence = [...projection.evidence.values()].filter((row) => row.epoch === projection.epoch && row.outcome === "success").map((row) => row.id).sort();
|
|
3265
|
+
const qualifications = availableBoundaryQualifications(projection).map((row) => `${row.id}:${row.status}`).sort();
|
|
3266
|
+
return JSON.stringify({
|
|
3267
|
+
epoch: projection.epoch,
|
|
3268
|
+
contractRevision: projection.contractRevision,
|
|
3269
|
+
open,
|
|
3270
|
+
evidence,
|
|
3271
|
+
qualifications,
|
|
3272
|
+
goal: projection.currentGoalRef?.id ?? null
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3275
|
+
const QUOTED = /["'“”‘’`].*?(?:complete|done|finished|完成|做完|搞定).*?["'“”‘’`]/i;
|
|
3276
|
+
const EXAMPLE = /\b(?:for example|e\.g\.|such as|like saying|例如|比如|举例|作为一个例子)\b/i;
|
|
3277
|
+
const QUESTION = /\?[ \t]*$|\b(?:should|could|would|can|will|what|how|whether)\b.*\?/i;
|
|
3278
|
+
const TRAILING_NEGATION = /\b(?:not (?:yet |quite |fully )?(?:complete|done|finished)|isn'?t (?:complete|done|finished)|hasn'?t (?:been )?(?:completed|finished)|尚未完成|还没完成|未完成|没有完成|还未完成)\b/i;
|
|
3279
|
+
const CONDITIONAL = /\b(?:if|unless|once|when|whenever|provided that|只要|如果|假如|一旦|除非)\b/i;
|
|
3280
|
+
const PARTIAL_ONLY = /\b(?:step|phase|stage|milestone)\s+\d+\b|第[一二三四五六七八九十\d]+\s*(?:步|阶段|环节)|(?:第一步|第二步|第三步)/i;
|
|
3281
|
+
const WHOLE_COMPLETION_EN = /\b(?:the )?(?:task|work|job|everything|all tasks?|all work) (?:is|are) (?:now )?(?:complete|done|finished|completed)\b|\b(?:task|work) (?:has been )?(?:completed|finished)\b|\ball (?:tasks|work|requirements) (?:have been )?(?:completed|done|met)\b/i;
|
|
3282
|
+
const WHOLE_COMPLETION_ZH = /(?:任务|工作|所有任务|全部工作|整体)(?:已经|已)?(?:全部)?(?:完成|搞定|做完)|(?:已|已经)(?:全部|所有)?(?:完成|搞定)(?:了)?(?:全部|所有)?(?:任务|工作)?/i;
|
|
3283
|
+
/** Bare completion confirmations, e.g. "Done." or "搞定了。" */
|
|
3284
|
+
const BARE_COMPLETION = /^(?:done|finished|completed|all\s+done)[.!]?$|^(?:已完成|完成了|搞定了|搞定|完成|done)[。..!!]?$/i;
|
|
3285
|
+
/** Continuation intent following a claim makes it partial, not whole-task. */
|
|
3286
|
+
const CONTINUATION = /接下来|下一步|然后|接着|继续|再去|最后再|还差|剩下|剩余|第二步|第三步|,\s*(?:next|then|after that|moving on)\b/i;
|
|
3287
|
+
function looksQuotedOrExemplary(text) {
|
|
3288
|
+
return QUOTED.test(text) || EXAMPLE.test(text);
|
|
3289
|
+
}
|
|
3290
|
+
function isWholeTaskCompletionClaim(text) {
|
|
3291
|
+
const normalized = normalizeClause(text);
|
|
3292
|
+
if (!normalized) return false;
|
|
3293
|
+
if (QUESTION.test(normalized)) return false;
|
|
3294
|
+
if (TRAILING_NEGATION.test(normalized)) return false;
|
|
3295
|
+
if (CONDITIONAL.test(normalized)) return false;
|
|
3296
|
+
if (CONTINUATION.test(normalized)) return false;
|
|
3297
|
+
if (looksQuotedOrExemplary(normalized)) return false;
|
|
3298
|
+
if (PARTIAL_ONLY.test(normalized) && !WHOLE_COMPLETION_EN.test(normalized) && !WHOLE_COMPLETION_ZH.test(normalized)) return false;
|
|
3299
|
+
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
|
|
3300
|
+
if (BARE_COMPLETION.test(normalizeTitleLine(firstLine))) return leadingBareCompletionClaim(text);
|
|
3301
|
+
return BARE_COMPLETION.test(normalized) || WHOLE_COMPLETION_EN.test(normalized) || WHOLE_COMPLETION_ZH.test(normalized);
|
|
3302
|
+
}
|
|
3303
|
+
const DECORATION_LEAD = /^\s*(?:[\p{Extended_Pictographic}\u2764\u2705\u2714\u2716\u2728\u274C\u26A0\u2611\u2612\u2713\u2717\u274E\u2B50\u2B55\u2022\u00B7\u25E6\u25AA\u25AB\u25CF\u25CB\u25A0\u25A1\u2013\u2014-]|\uFE0F|\uFE0E|\u200D)+/u;
|
|
3304
|
+
/** Strip a leading run of decorative glyphs from a title line. */
|
|
3305
|
+
function stripDecorationPrefix(text) {
|
|
3306
|
+
let value = text;
|
|
3307
|
+
let previous = "";
|
|
3308
|
+
while (value !== previous) {
|
|
3309
|
+
previous = value;
|
|
3310
|
+
value = value.replace(DECORATION_LEAD, "");
|
|
3311
|
+
}
|
|
3312
|
+
return value.replace(/^\s+/, "");
|
|
3313
|
+
}
|
|
3314
|
+
/**
|
|
3315
|
+
* Normalize a title line for the bare-completion test. Markdown heading markers,
|
|
3316
|
+
* fully-wrapping emphasis (`**…**`, `__…__`, `*…*`, `_…_`), and a leading run of
|
|
3317
|
+
* decorative glyphs are removed ITERATIVELY until stable, because stripping one
|
|
3318
|
+
* layer may expose another (`## ✅ **完成。**`). Blockquotes (`>`), quoted
|
|
3319
|
+
* titles, and examples are left untouched so they still fail closed.
|
|
3320
|
+
*/
|
|
3321
|
+
function normalizeTitleLine(line) {
|
|
3322
|
+
let value = line.trim();
|
|
3323
|
+
if (value.startsWith(">")) return value;
|
|
3324
|
+
let previous = "";
|
|
3325
|
+
while (value !== previous) {
|
|
3326
|
+
previous = value;
|
|
3327
|
+
value = value.replace(/^#{1,6}\s+/, "").replace(/^\*\*(.+?)\*\*$/, "$1").replace(/^__(.+?)__$/, "$1").replace(/^\*(.+?)\*$/, "$1").replace(/^_(.+?)_$/, "$1");
|
|
3328
|
+
value = stripDecorationPrefix(value);
|
|
3329
|
+
}
|
|
3330
|
+
return value;
|
|
3331
|
+
}
|
|
3332
|
+
/**
|
|
3333
|
+
* A reply whose first non-empty line is a standalone bare completion ("完成。"
|
|
3334
|
+
* or "Done.") followed by a results summary. The whole text no longer matches
|
|
3335
|
+
* the single-line BARE_COMPLETION anchor, but the summary must still be treated
|
|
3336
|
+
* as a whole-task completion claim.
|
|
3337
|
+
*/
|
|
3338
|
+
function leadingBareCompletionClaim(text) {
|
|
3339
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
3340
|
+
const first = lines[0];
|
|
3341
|
+
if (!first || !BARE_COMPLETION.test(normalizeTitleLine(first))) return false;
|
|
3342
|
+
const rest = normalizeClause(lines.slice(1).join("\n"));
|
|
3343
|
+
if (!rest) return true;
|
|
3344
|
+
if (CONTINUATION.test(rest)) return false;
|
|
3345
|
+
if (TRAILING_NEGATION.test(rest)) return false;
|
|
3346
|
+
if (CONDITIONAL.test(rest)) return false;
|
|
3347
|
+
if (looksQuotedOrExemplary(rest)) return false;
|
|
3348
|
+
if (PARTIAL_ONLY.test(rest)) return false;
|
|
3349
|
+
return true;
|
|
3350
|
+
}
|
|
3351
|
+
function classifyCompletionClaim(text) {
|
|
3352
|
+
const normalized = normalizeClause(text);
|
|
3353
|
+
if (/waiting for (?:you|the user|input|your)|please (?:review|confirm|approve)|等待(?:您|你|用户)|请(?:确认|审阅|批准)/i.test(normalized)) return "user_wait";
|
|
3354
|
+
if (/waiting for (?:the )?(?:result|output|response|build|test|deployment)|等待(?:结果|输出|构建|测试|部署|响应)/i.test(normalized)) return "external_wait";
|
|
3355
|
+
if (isWholeTaskCompletionClaim(normalized)) return "complete";
|
|
3356
|
+
return "report";
|
|
3357
|
+
}
|
|
3358
|
+
/** Assistant prose is retained only as a bounded diagnostic observation. */
|
|
3359
|
+
function observeAssistantOutcome(text) {
|
|
3360
|
+
const disposition = classifyCompletionClaim(text);
|
|
3361
|
+
if (disposition === "complete") return {
|
|
3362
|
+
kind: "completion_claim",
|
|
3363
|
+
reasonCode: "assistant_completion_claim_observed"
|
|
3364
|
+
};
|
|
3365
|
+
if (disposition === "user_wait") return {
|
|
3366
|
+
kind: "user_wait_claim",
|
|
3367
|
+
reasonCode: "assistant_user_wait_claim_observed"
|
|
3368
|
+
};
|
|
3369
|
+
if (disposition === "external_wait") return {
|
|
3370
|
+
kind: "external_wait_claim",
|
|
3371
|
+
reasonCode: "assistant_external_wait_claim_observed"
|
|
3372
|
+
};
|
|
3373
|
+
return {
|
|
3374
|
+
kind: "report",
|
|
3375
|
+
reasonCode: "assistant_report_observed"
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
/**
|
|
3379
|
+
* Stop Protocol 2.0 decision. This function deliberately has no assistant-text
|
|
3380
|
+
* parameter: completion wording, quotation, negation and translation cannot
|
|
3381
|
+
* steer the protocol. A structured root persistence authorization may request
|
|
3382
|
+
* one fallback correction; subsequent attempts safe-yield. An active, armed
|
|
3383
|
+
* Goal remains exclusively owned by the host Goal Round Driver.
|
|
3384
|
+
*/
|
|
3385
|
+
function decideTurnBoundary(projection) {
|
|
3386
|
+
if (!projection.enabled) return {
|
|
3387
|
+
action: "stop",
|
|
3388
|
+
reason: "guard_disabled"
|
|
3389
|
+
};
|
|
3390
|
+
if (projection.integrity !== "valid") return {
|
|
3391
|
+
action: "stop",
|
|
3392
|
+
reason: "integrity_invalid_safe_yield"
|
|
3393
|
+
};
|
|
3394
|
+
if (hasCurrentCertificate(projection)) return {
|
|
3395
|
+
action: "stop",
|
|
3396
|
+
reason: "current_certificate"
|
|
3397
|
+
};
|
|
3398
|
+
const boundary = projection.boundaries.at(-1);
|
|
3399
|
+
if (boundary?.persistedResult === "accepted" && boundary.epoch === projection.epoch && boundary.contractRevision === projection.contractRevision) return {
|
|
3400
|
+
action: "stop",
|
|
3401
|
+
reason: "accepted_boundary_pending_effectuation"
|
|
3402
|
+
};
|
|
3403
|
+
if (projection.currentGoalPhase === "active" && projection.currentGoalActivation === "armed") {
|
|
3404
|
+
const fingerprint$1 = progressFingerprint(projection);
|
|
3405
|
+
const claims = projection.noProgressClaims.get(fingerprint$1) ?? /* @__PURE__ */ new Map();
|
|
3406
|
+
const hostTurn = decisionBoundaryKey(projection);
|
|
3407
|
+
if (hostTurn === void 0) return {
|
|
3408
|
+
action: "stop",
|
|
3409
|
+
reason: "no_progress_identity_unavailable"
|
|
3410
|
+
};
|
|
3411
|
+
const boundaryKey = String(hostTurn);
|
|
3412
|
+
const prior = [...claims].filter(([key]) => key !== boundaryKey).length;
|
|
3413
|
+
const claim = {
|
|
3414
|
+
fingerprint: fingerprint$1,
|
|
3415
|
+
boundaryKey,
|
|
3416
|
+
attempt: prior + 1
|
|
3417
|
+
};
|
|
3418
|
+
if (prior === 0) return {
|
|
3419
|
+
action: "stop",
|
|
3420
|
+
reason: "goal_round_driver_owns_continuation",
|
|
3421
|
+
noProgressClaim: claim
|
|
3422
|
+
};
|
|
3423
|
+
if (prior < NO_PROGRESS_TURNS_BEFORE_STOP - 1) return {
|
|
3424
|
+
action: "continue",
|
|
3425
|
+
reason: "no_progress_diagnosis_steer",
|
|
3426
|
+
noProgressClaim: claim
|
|
3427
|
+
};
|
|
3428
|
+
return {
|
|
3429
|
+
action: "stop",
|
|
3430
|
+
reason: "no_progress_bounded_disarm"
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
if (projection.currentGoalRef) return {
|
|
3434
|
+
action: "stop",
|
|
3435
|
+
reason: projection.currentGoalPhase === "paused" ? "goal_paused_by_user_safe_yield" : "goal_not_continuable_safe_yield"
|
|
3436
|
+
};
|
|
3437
|
+
if ([...projection.items.values()].some((item) => item.status === "pending" && item.persistenceAuthorization)) {
|
|
3438
|
+
const key = `${projection.epoch}:${projection.contractRevision}`;
|
|
3439
|
+
const attempts = projection.persistenceCorrectionAttempts.get(key) ?? 0;
|
|
3440
|
+
if (attempts < 1) {
|
|
3441
|
+
projection.persistenceCorrectionAttempts.set(key, attempts + 1);
|
|
3442
|
+
return {
|
|
3443
|
+
action: "continue",
|
|
3444
|
+
reason: "protocol_correction_steer"
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
return {
|
|
3449
|
+
action: "stop",
|
|
3450
|
+
reason: "safe_yield_pending_preserved"
|
|
3451
|
+
};
|
|
3452
|
+
}
|
|
3453
|
+
function decideTurnStopping(projection, _assistantText, _turn, _maxAttempts) {
|
|
3454
|
+
return decideTurnBoundary(projection);
|
|
3455
|
+
}
|
|
3456
|
+
/**
|
|
3457
|
+
* Whether the last trusted ROOT instruction asked to pause.
|
|
3458
|
+
*
|
|
3459
|
+
* The source filter is the contract, not a heuristic: a quoted log, a tool
|
|
3460
|
+
* result, a plugin notice or a model message is not a `user/message` with
|
|
3461
|
+
* `source.kind === 'user'`, so none of them can reach this function at all, and
|
|
3462
|
+
* neither can the model's own summary of one. A negated pause ("不要暂停") is not
|
|
3463
|
+
* a pause request, and the check is anchored to a clause head so a pause word
|
|
3464
|
+
* mentioned inside a longer instruction is not a control request.
|
|
3465
|
+
*/
|
|
3466
|
+
function latestRootInstruction(events) {
|
|
3467
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
3468
|
+
const event = events[index];
|
|
3469
|
+
if (event.type !== "user/message") continue;
|
|
3470
|
+
const data = event.data;
|
|
3471
|
+
if (data.source?.kind !== "user") continue;
|
|
3472
|
+
const text = (data.content ?? []).filter((part) => part?.type === "text").map((part) => part.text ?? "").join("\n");
|
|
3473
|
+
if (text.trim()) return {
|
|
3474
|
+
text,
|
|
3475
|
+
seq: event.seq ?? 0
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
/** Marks a root control request Guard has already carried to the host. */
|
|
3480
|
+
const CONTROL_RECORD_PREFIX = "Context Guard control record: ";
|
|
3481
|
+
const PAUSE_REQUEST = /(?:^|[。!?;;,,、\s])(?:请|麻烦)?\s*(?:先)?\s*(?:暂停|停一下|停一停|先停|暂时停止)(?:一下|下|吧)?\s*(?:[。!?;;,,、]|$)|\b(?:please\s+)?(?:pause|hold\s+on|stop\s+for\s+now)\b/i;
|
|
3482
|
+
const NEGATED_PAUSE = /(?:不要|不用|别|无需|不必)\s*(?:先)?\s*(?:暂停|停)|\b(?:do\s+not|don't|never)\s+(?:pause|stop)\b/i;
|
|
3483
|
+
function isRootPauseRequest(text) {
|
|
3484
|
+
if (NEGATED_PAUSE.test(text)) return false;
|
|
3485
|
+
return PAUSE_REQUEST.test(text);
|
|
3486
|
+
}
|
|
3487
|
+
function latestAssistantText(events) {
|
|
3488
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
3489
|
+
const event = events[index];
|
|
3490
|
+
if (event.type !== "assistant/message") continue;
|
|
3491
|
+
const text = event.data.message?.content?.filter((block$1) => block$1.type === "text").map((block$1) => block$1.text ?? "").join("\n") ?? "";
|
|
3492
|
+
if (text.trim()) return text;
|
|
3493
|
+
}
|
|
3494
|
+
return "";
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
//#endregion
|
|
3498
|
+
//#region src/domain/digest.ts
|
|
3499
|
+
/**
|
|
3500
|
+
* Digest v3 canonical manifest derivation for Context Guard certificates.
|
|
3501
|
+
*
|
|
3502
|
+
* This is the DSH-side implementation of the frozen cross-language digest
|
|
3503
|
+
* contract documented in `docs/SEMANTIC_COMPATIBILITY.md`. The canonical
|
|
3504
|
+
* fixture lives in codex-context-guard (`tests/fixtures/conformance/digest_v3`)
|
|
3505
|
+
* and is byte-mirrored under `tests/fixtures/conformance/digest_v3` together
|
|
3506
|
+
* with `UPSTREAM_PIN.json`; the vitest suite re-derives all 29 golden vectors
|
|
3507
|
+
* and fails on any byte difference. Any change to the algorithm, separators,
|
|
3508
|
+
* typed token language, allowlists, or serialization is a new digest version
|
|
3509
|
+
* and must regenerate the vectors in both repositories.
|
|
3510
|
+
*
|
|
3511
|
+
* Fail-closed rules pinned here: lone surrogates are rejected before hashing,
|
|
3512
|
+
* values are never Unicode-normalized, dynamic keys must match the snake_case
|
|
3513
|
+
* grammar, collections reject duplicate members, canonical maps sort by
|
|
3514
|
+
* semantic key bytes (never by encoded field bytes), and predicate digests are
|
|
3515
|
+
* always recomputed from the actual parameter payload.
|
|
3516
|
+
*/
|
|
3517
|
+
var DigestError = class extends Error {};
|
|
3518
|
+
const MAX_ENCODED_NAME_BYTES = 256;
|
|
3519
|
+
const MAX_SEMANTIC_KEY_BYTES = 64;
|
|
3520
|
+
const MAX_VALUE_BYTES = 4096;
|
|
3521
|
+
const MAX_FIELDS_PER_RECORD = 128;
|
|
3522
|
+
const MAX_PRED_PARAMS_BYTES = 4096;
|
|
3523
|
+
const DYNAMIC_KEY_RE = /^[a-z0-9_]{1,64}$/;
|
|
3524
|
+
const PACKAGE_NAME_RE = /^[@a-z0-9._/-]{1,128}$/;
|
|
3525
|
+
const ENUM_TOKEN_RE = /^[a-z0-9][a-z0-9_-]*$/;
|
|
3526
|
+
const HEX_RE = /^[0-9a-f]+$/;
|
|
3527
|
+
const SURFACE_ENUM = [
|
|
3528
|
+
"artifact",
|
|
3529
|
+
"ui",
|
|
3530
|
+
"visual",
|
|
3531
|
+
"scope"
|
|
3532
|
+
];
|
|
3533
|
+
const OUTCOME_ENUM = [
|
|
3534
|
+
"success",
|
|
3535
|
+
"failure",
|
|
3536
|
+
"unknown",
|
|
3537
|
+
"durability-unknown"
|
|
3538
|
+
];
|
|
3539
|
+
const EVIDENCE_ROLE_ENUM = [
|
|
3540
|
+
"resolution",
|
|
3541
|
+
"effect",
|
|
3542
|
+
"state"
|
|
3543
|
+
];
|
|
1885
3544
|
const PRED_PARAMS_KIND_ENUM = ["inline", "manifest"];
|
|
1886
3545
|
/** Frozen canonical key vocabulary; product manifests draw allowlists from it. */
|
|
1887
3546
|
const PRODUCT_KEY_VOCABULARY = [
|
|
@@ -2777,6 +4436,10 @@ function renderRecoveryPacket(projection, options = {}) {
|
|
|
2777
4436
|
};
|
|
2778
4437
|
const requirement = (item) => {
|
|
2779
4438
|
const diagnosis = deriveItemDiagnosis(projection, item);
|
|
4439
|
+
if (diagnosis.reason_code === "root_condition_pending") {
|
|
4440
|
+
if (add(`[${clip(item.id, 20)}] root_condition_pending; wait for trusted root: ${item.resumeEvent ?? item.condition ?? item.normalizedText}; do not execute before release`, compact ? 160 : 310)) count++;
|
|
4441
|
+
return;
|
|
4442
|
+
}
|
|
2780
4443
|
const remedy = diagnosis.repairability === "agent_repairable" ? "Collect matching evidence; checkpoint" : diagnosis.repairability === "historical_gap" ? "Read back observed state; do not re-execute" : diagnosis.certification === "unsupported" ? "Deliver honestly; stays uncertified unless a fresh instruction names a supported action" : "Restore audited host/adapter capability";
|
|
2781
4444
|
if (add(`[${clip(item.id, 20)}] ${diagnosis.reason_code}; ${compact ? remedy : diagnosis.next_action.resume_condition ?? remedy}; ${clip(item.normalizedText, 70)}`, compact ? 110 : 310)) count++;
|
|
2782
4445
|
};
|
|
@@ -2866,8 +4529,9 @@ function evidenceProblem(projection, item, binding) {
|
|
|
2866
4529
|
offendingEvidenceIds: notSuccess
|
|
2867
4530
|
};
|
|
2868
4531
|
const requiredAction = item.semanticAction ?? "generic_run";
|
|
4532
|
+
const compatibleWith = [requiredAction, ...(item.actionPlan ?? []).map((entry) => entry.action)];
|
|
2869
4533
|
const facts = citedEvidence(projection, binding);
|
|
2870
|
-
const incompatible = facts.filter((fact) => !actionCompatible(
|
|
4534
|
+
const incompatible = facts.filter((fact) => !compatibleWith.some((action) => actionCompatible(action, fact.semanticAction ?? "generic_run")));
|
|
2871
4535
|
if (incompatible.length) {
|
|
2872
4536
|
const compatibleCount = facts.length - incompatible.length;
|
|
2873
4537
|
return {
|
|
@@ -3205,6 +4869,13 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
|
|
|
3205
4869
|
});
|
|
3206
4870
|
continue;
|
|
3207
4871
|
}
|
|
4872
|
+
if (item.actionPlan && item.actionPlan.length > 0) {
|
|
4873
|
+
const planProblem = bindingActionPlanProblem(projection, item, binding);
|
|
4874
|
+
if (planProblem) {
|
|
4875
|
+
rejectedBindings.push(planProblem);
|
|
4876
|
+
continue;
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
3208
4879
|
if (!binding.evidenceIds.length) {
|
|
3209
4880
|
rejectedBindings.push({
|
|
3210
4881
|
itemId: item.id,
|
|
@@ -3302,230 +4973,97 @@ function certifyCheckpoint(projection, bindings, id, commit = true) {
|
|
|
3302
4973
|
};
|
|
3303
4974
|
}
|
|
3304
4975
|
}
|
|
3305
|
-
function openItems(projection) {
|
|
3306
|
-
return [...projection.items.values()].filter((item) => item.status === "pending" && item.kind !== "prohibition").map((item) => item.id);
|
|
3307
|
-
}
|
|
3308
|
-
|
|
3309
|
-
//#endregion
|
|
3310
|
-
//#region src/domain/boundary.ts
|
|
3311
|
-
/** Bounded, replay-derived qualifications that callers may cite verbatim. */
|
|
3312
|
-
function availableBoundaryQualifications(projection) {
|
|
3313
|
-
const rows = [];
|
|
3314
|
-
for (const item of projection.items.values()) {
|
|
3315
|
-
if (item.status !== "pending") continue;
|
|
3316
|
-
if (item.waitAuthorization) rows.push({
|
|
3317
|
-
id: item.waitAuthorization.id,
|
|
3318
|
-
kind: item.waitAuthorization.kind,
|
|
3319
|
-
disposition: "user_wait",
|
|
3320
|
-
source: "root_contract",
|
|
3321
|
-
status: "pending"
|
|
3322
|
-
});
|
|
3323
|
-
if (item.deferAuthorization) rows.push({
|
|
3324
|
-
id: item.deferAuthorization.id,
|
|
3325
|
-
kind: item.deferAuthorization.kind,
|
|
3326
|
-
disposition: "deferred",
|
|
3327
|
-
source: "root_contract",
|
|
3328
|
-
status: "pending"
|
|
3329
|
-
});
|
|
3330
|
-
}
|
|
3331
|
-
for (const operation of projection.externalOperations.values()) {
|
|
3332
|
-
if (operation.epoch !== projection.epoch || operation.status !== "pending" && operation.status !== "running") continue;
|
|
3333
|
-
rows.push({
|
|
3334
|
-
id: operation.id,
|
|
3335
|
-
kind: "external_operation_pending",
|
|
3336
|
-
disposition: "external_wait",
|
|
3337
|
-
source: "trusted_adapter",
|
|
3338
|
-
status: operation.status
|
|
3339
|
-
});
|
|
3340
|
-
}
|
|
3341
|
-
return rows.sort((a, b) => a.id.localeCompare(b.id)).slice(0, 32);
|
|
3342
|
-
}
|
|
3343
|
-
function qualificationReason(projection, request) {
|
|
3344
|
-
const ids = new Set(request.qualificationIds);
|
|
3345
|
-
if (ids.size !== request.qualificationIds.length || ids.size === 0) return "boundary_qualification_ids_invalid";
|
|
3346
|
-
if (request.disposition === "user_wait") {
|
|
3347
|
-
if (request.qualificationKind !== "root_explicit_wait" && request.qualificationKind !== "user_decision_item") return "boundary_qualification_kind_mismatch";
|
|
3348
|
-
const known$1 = new Set([...projection.items.values()].filter((item) => item.status === "pending" && item.waitAuthorization?.kind === request.qualificationKind).map((item) => item.waitAuthorization.id));
|
|
3349
|
-
return request.qualificationIds.every((id) => known$1.has(id)) ? void 0 : "boundary_disposition_unqualified";
|
|
3350
|
-
}
|
|
3351
|
-
if (request.disposition === "external_wait") {
|
|
3352
|
-
if (request.qualificationKind !== "external_operation_pending") return "boundary_qualification_kind_mismatch";
|
|
3353
|
-
return request.qualificationIds.every((id) => {
|
|
3354
|
-
const operation = projection.externalOperations.get(id);
|
|
3355
|
-
return operation?.epoch === projection.epoch && (operation.status === "running" || operation.status === "pending");
|
|
3356
|
-
}) ? void 0 : "boundary_disposition_unqualified";
|
|
3357
|
-
}
|
|
3358
|
-
if (request.qualificationKind !== "root_explicit_defer") return "boundary_qualification_kind_mismatch";
|
|
3359
|
-
const known = new Set([...projection.items.values()].filter((item) => item.status === "pending" && item.deferAuthorization?.kind === request.qualificationKind).map((item) => item.deferAuthorization.id));
|
|
3360
|
-
return request.qualificationIds.every((id) => known.has(id)) ? void 0 : "boundary_disposition_unqualified";
|
|
3361
|
-
}
|
|
3362
|
-
function qualifyBoundary(projection, request) {
|
|
3363
|
-
const contractSha256 = currentContractDigest(projection);
|
|
3364
|
-
const reason = projection.integrity !== "valid" ? "boundary_integrity_invalid" : projection.hostStatus !== "supported" && projection.currentGoalRef ? "boundary_host_lock_unsupported" : qualificationReason(projection, request);
|
|
3365
|
-
const manifest = {
|
|
3366
|
-
protocolVersion: "1",
|
|
3367
|
-
disposition: request.disposition,
|
|
3368
|
-
qualificationKind: request.qualificationKind,
|
|
3369
|
-
qualificationIds: [...request.qualificationIds].sort(),
|
|
3370
|
-
epoch: projection.epoch,
|
|
3371
|
-
contractRevision: projection.contractRevision,
|
|
3372
|
-
contractSha256,
|
|
3373
|
-
goalRef: projection.currentGoalRef ?? null
|
|
3374
|
-
};
|
|
3375
|
-
const candidateSha256 = sha256(JSON.stringify(manifest));
|
|
3376
|
-
return {
|
|
3377
|
-
protocolVersion: "1",
|
|
3378
|
-
id: `B${projection.boundaries.length + 1}`,
|
|
3379
|
-
disposition: request.disposition,
|
|
3380
|
-
qualificationKind: request.qualificationKind,
|
|
3381
|
-
qualificationIds: [...request.qualificationIds],
|
|
3382
|
-
epoch: projection.epoch,
|
|
3383
|
-
contractRevision: projection.contractRevision,
|
|
3384
|
-
contractSha256,
|
|
3385
|
-
...projection.currentGoalRef ? { goalRef: { ...projection.currentGoalRef } } : {},
|
|
3386
|
-
candidateSha256,
|
|
3387
|
-
...request.callId ? { callId: request.callId } : {},
|
|
3388
|
-
persistedResult: reason ? "rejected" : "accepted",
|
|
3389
|
-
reasonCode: reason ?? "boundary_persisted_accepted"
|
|
3390
|
-
};
|
|
3391
|
-
}
|
|
3392
|
-
/**
|
|
3393
|
-
* Reconstruct the immutable candidate against the latest replay projection.
|
|
3394
|
-
* A persisted acceptance is not effectuation authority after any contract,
|
|
3395
|
-
* Goal, epoch, or qualification change.
|
|
3396
|
-
*/
|
|
3397
|
-
function isCurrentAcceptedBoundary(projection, boundary) {
|
|
3398
|
-
if (boundary.persistedResult !== "accepted" || boundary.epoch !== projection.epoch || boundary.contractRevision !== projection.contractRevision || boundary.contractSha256 !== currentContractDigest(projection)) return false;
|
|
3399
|
-
const currentGoal = projection.currentGoalRef;
|
|
3400
|
-
if (boundary.goalRef ? !currentGoal || !sameRef(currentGoal, boundary.goalRef) : currentGoal !== void 0) return false;
|
|
3401
|
-
const reconstructed = qualifyBoundary(projection, {
|
|
3402
|
-
disposition: boundary.disposition,
|
|
3403
|
-
qualificationKind: boundary.qualificationKind,
|
|
3404
|
-
qualificationIds: boundary.qualificationIds,
|
|
3405
|
-
...boundary.callId ? { callId: boundary.callId } : {}
|
|
3406
|
-
});
|
|
3407
|
-
return reconstructed.persistedResult === "accepted" && reconstructed.candidateSha256 === boundary.candidateSha256;
|
|
3408
|
-
}
|
|
3409
|
-
function sameRef(state, ref) {
|
|
3410
|
-
return state?.id === ref.id && state.revision === ref.revision;
|
|
3411
|
-
}
|
|
3412
4976
|
/**
|
|
3413
|
-
*
|
|
3414
|
-
*
|
|
3415
|
-
*
|
|
4977
|
+
* Per-action closure check for a multi-action clause. Each planned action needs
|
|
4978
|
+
* a matching closure whose resolved target matches the target captured for that
|
|
4979
|
+
* action, whose cited evidence succeeded, and whose evidence is not older than
|
|
4980
|
+
* the item revision it is closing.
|
|
3416
4981
|
*/
|
|
3417
|
-
|
|
3418
|
-
const
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
if (!
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
resumeRequired: false
|
|
3434
|
-
};
|
|
3435
|
-
} catch {
|
|
3436
|
-
return {
|
|
3437
|
-
...base,
|
|
3438
|
-
reasonCode: "boundary_pre_effect_failure",
|
|
3439
|
-
stopAllowed: false,
|
|
3440
|
-
resumeRequired: false
|
|
3441
|
-
};
|
|
3442
|
-
}
|
|
3443
|
-
if (!boundary.goalRef) return {
|
|
3444
|
-
...base,
|
|
3445
|
-
reasonCode: "boundary_no_goal_safe_yield",
|
|
3446
|
-
stopAllowed: true,
|
|
3447
|
-
resumeRequired: false
|
|
3448
|
-
};
|
|
3449
|
-
let before;
|
|
3450
|
-
try {
|
|
3451
|
-
before = await access.get();
|
|
3452
|
-
} catch {
|
|
3453
|
-
return {
|
|
3454
|
-
...base,
|
|
3455
|
-
reasonCode: "boundary_pre_effect_failure",
|
|
3456
|
-
stopAllowed: false,
|
|
3457
|
-
resumeRequired: false
|
|
3458
|
-
};
|
|
3459
|
-
}
|
|
3460
|
-
if (!sameRef(before, boundary.goalRef) || before?.phase !== "active") return {
|
|
3461
|
-
...base,
|
|
3462
|
-
reasonCode: "boundary_goal_ref_stale",
|
|
3463
|
-
stopAllowed: false,
|
|
3464
|
-
resumeRequired: false
|
|
3465
|
-
};
|
|
3466
|
-
if (before.activation === "disarmed") return {
|
|
3467
|
-
...base,
|
|
3468
|
-
reasonCode: "boundary_already_disarmed",
|
|
3469
|
-
stopAllowed: true,
|
|
3470
|
-
resumeRequired: false
|
|
3471
|
-
};
|
|
3472
|
-
let firstReadback;
|
|
3473
|
-
try {
|
|
3474
|
-
firstReadback = await access.disarm();
|
|
3475
|
-
} catch {
|
|
3476
|
-
return {
|
|
3477
|
-
...base,
|
|
3478
|
-
reasonCode: "boundary_post_effect_unknown",
|
|
3479
|
-
stopAllowed: false,
|
|
3480
|
-
resumeRequired: true
|
|
4982
|
+
function bindingActionPlanProblem(projection, item, binding) {
|
|
4983
|
+
const plan = item.actionPlan ?? [];
|
|
4984
|
+
const closures = binding.actionBindings ?? [];
|
|
4985
|
+
if (closures.length !== plan.length) return {
|
|
4986
|
+
itemId: item.id,
|
|
4987
|
+
reason: `the clause orders ${plan.map((entry) => entry.action).join(" + ")}; ${closures.length} action closure(s) supplied`,
|
|
4988
|
+
reasonCode: "action_plan_incomplete",
|
|
4989
|
+
hint: closingHint(projection, item)
|
|
4990
|
+
};
|
|
4991
|
+
const ordered = [...closures].sort((a, b) => a.order - b.order);
|
|
4992
|
+
for (const [index, planned] of plan.entries()) {
|
|
4993
|
+
const closure = ordered[index];
|
|
4994
|
+
if (!closure || closure.action !== planned.action) return {
|
|
4995
|
+
itemId: item.id,
|
|
4996
|
+
reason: `action closure ${index + 1} must be '${planned.action}' in the clause's order`,
|
|
4997
|
+
reasonCode: "action_plan_order_mismatch"
|
|
3481
4998
|
};
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
resumeRequired: true
|
|
3488
|
-
};
|
|
3489
|
-
if (firstReadback.activation !== "disarmed") return {
|
|
3490
|
-
...base,
|
|
3491
|
-
reasonCode: "boundary_readback_still_armed",
|
|
3492
|
-
stopAllowed: false,
|
|
3493
|
-
resumeRequired: false
|
|
3494
|
-
};
|
|
3495
|
-
try {
|
|
3496
|
-
const independent = await access.get();
|
|
3497
|
-
if (!sameRef(independent, boundary.goalRef) || independent?.phase !== "active") return {
|
|
3498
|
-
...base,
|
|
3499
|
-
reasonCode: "boundary_post_effect_unknown",
|
|
3500
|
-
stopAllowed: false,
|
|
3501
|
-
resumeRequired: true
|
|
4999
|
+
if (planned.targetCaptureStatus !== "resolved") return {
|
|
5000
|
+
itemId: item.id,
|
|
5001
|
+
reason: `the clause does not identify an exact target for '${planned.action}'`,
|
|
5002
|
+
reasonCode: planned.targetCaptureReasonCode ?? "action_plan_target_missing",
|
|
5003
|
+
hint: closingHint(projection, item)
|
|
3502
5004
|
};
|
|
3503
|
-
if (
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
resumeRequired: false
|
|
5005
|
+
if (!tuplesEqual(planned.requestedTarget, closure.resolvedTarget)) return {
|
|
5006
|
+
itemId: item.id,
|
|
5007
|
+
reason: `the closure for '${planned.action}' resolves a different target than the clause captured`,
|
|
5008
|
+
reasonCode: "action_plan_target_mismatch"
|
|
3508
5009
|
};
|
|
3509
|
-
|
|
3510
|
-
return {
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
5010
|
+
const reused = closure.evidenceIds.filter((id) => closures.some((other) => other !== closure && other.evidenceIds.includes(id)));
|
|
5011
|
+
if (reused.length > 0) return {
|
|
5012
|
+
itemId: item.id,
|
|
5013
|
+
reason: `evidence cited for '${planned.action}' also closes another action`,
|
|
5014
|
+
reasonCode: "action_plan_evidence_reused",
|
|
5015
|
+
offendingEvidenceIds: reused
|
|
3515
5016
|
};
|
|
5017
|
+
if (closure.evidenceIds.length === 0) return {
|
|
5018
|
+
itemId: item.id,
|
|
5019
|
+
reason: `no evidence cited for '${planned.action}'`,
|
|
5020
|
+
reasonCode: "action_plan_evidence_missing"
|
|
5021
|
+
};
|
|
5022
|
+
const cited = closure.evidenceIds.map((id) => projection.evidence.get(id));
|
|
5023
|
+
const missing = closure.evidenceIds.filter((id) => !projection.evidence.has(id));
|
|
5024
|
+
if (missing.length > 0) return {
|
|
5025
|
+
itemId: item.id,
|
|
5026
|
+
reason: `cited evidence for '${planned.action}' is missing`,
|
|
5027
|
+
reasonCode: "evidence_missing",
|
|
5028
|
+
offendingEvidenceIds: missing
|
|
5029
|
+
};
|
|
5030
|
+
for (const [position, evidence] of cited.entries()) {
|
|
5031
|
+
if (!evidence) continue;
|
|
5032
|
+
if (evidence.epoch !== projection.epoch) return {
|
|
5033
|
+
itemId: item.id,
|
|
5034
|
+
reason: `evidence for '${planned.action}' belongs to another epoch`,
|
|
5035
|
+
reasonCode: "evidence_wrong_epoch",
|
|
5036
|
+
offendingEvidenceIds: [closure.evidenceIds[position]]
|
|
5037
|
+
};
|
|
5038
|
+
if (evidence.outcome !== "success") return {
|
|
5039
|
+
itemId: item.id,
|
|
5040
|
+
reason: `evidence for '${planned.action}' did not succeed`,
|
|
5041
|
+
reasonCode: "action_plan_evidence_not_successful",
|
|
5042
|
+
offendingEvidenceIds: [closure.evidenceIds[position]]
|
|
5043
|
+
};
|
|
5044
|
+
if (evidence.toolResultSeq < 0) return {
|
|
5045
|
+
itemId: item.id,
|
|
5046
|
+
reason: `evidence for '${planned.action}' predates the item`,
|
|
5047
|
+
reasonCode: "action_plan_evidence_predates_item",
|
|
5048
|
+
offendingEvidenceIds: [closure.evidenceIds[position]]
|
|
5049
|
+
};
|
|
5050
|
+
if (evidence.semanticAction && evidence.semanticAction !== planned.action) return {
|
|
5051
|
+
itemId: item.id,
|
|
5052
|
+
reason: `evidence for '${planned.action}' records '${evidence.semanticAction}'`,
|
|
5053
|
+
reasonCode: "action_plan_action_mismatch",
|
|
5054
|
+
offendingEvidenceIds: [closure.evidenceIds[position]]
|
|
5055
|
+
};
|
|
5056
|
+
}
|
|
3516
5057
|
}
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
stopAllowed: true,
|
|
3521
|
-
resumeRequired: false
|
|
3522
|
-
};
|
|
5058
|
+
}
|
|
5059
|
+
function openItems(projection) {
|
|
5060
|
+
return [...projection.items.values()].filter((item) => item.status === "pending" && item.kind !== "prohibition").map((item) => item.id);
|
|
3523
5061
|
}
|
|
3524
5062
|
|
|
3525
5063
|
//#endregion
|
|
3526
5064
|
//#region src/domain/contract-segment.ts
|
|
3527
5065
|
const REFERENCE_FRAME = /(?:以下|下面|下列|附上|粘贴|提供).{0,12}(?:报告|材料|内容|记录|日志).{0,12}(?:供参考|参考|如下)|(?:for reference|pasted|attached|following).{0,16}(?:report|material|log)/i;
|
|
3528
|
-
const INSTRUCTION_SIGNAL = /(
|
|
5066
|
+
const INSTRUCTION_SIGNAL = /(?:请|需要|必须|务必|禁止|不要|不得|运行|执行|修改|创建|读取|验证|检查|安装|应用|拉取|抓取|获取|同步|提交|推送|发布|重启|升级|更新)|\b(?:please|must|shall|do not|run|execute|modify|create|read|verify|check|install|apply|pull|fetch|commit|push|publish|restart|upgrade|update)\b/i;
|
|
3529
5067
|
const ADOPTION_SIGNAL = /(?:按照|依照|采用|执行).{0,16}(?:下面|以下|报告|材料|第\s*([0-9一二三四五六七八九十]+)\s*节).{0,16}(?:全部执行|执行|作为验收|作为要求)|(?:把|将).{0,16}(?:上一条|前述|上述).{0,8}(?:报告|材料).{0,12}第\s*([0-9一二三四五六七八九十]+)\s*节.{0,20}(?:执行|采用)|(?:adopt|follow|apply).{0,20}(?:section\s+(\d+)|below|report)/i;
|
|
3530
5068
|
const PREVIOUS_REFERENCE_ADOPTION = /(?:把|将).{0,16}(?:上一条|前述|上述).{0,8}(?:报告|材料).{0,12}第\s*[0-9一二三四五六七八九十]+\s*节.{0,20}(?:执行|采用)|(?:adopt|follow|apply).{0,16}(?:the\s+)?(?:previous|above).{0,12}(?:report|material).{0,12}section\s+\d+/i;
|
|
3531
5069
|
function chineseNumber(value) {
|
|
@@ -4049,14 +5587,502 @@ const RC1_HOST_PACKAGES = [
|
|
|
4049
5587
|
integrity
|
|
4050
5588
|
}));
|
|
4051
5589
|
|
|
5590
|
+
//#endregion
|
|
5591
|
+
//#region src/domain/rc015-host.ts
|
|
5592
|
+
/**
|
|
5593
|
+
* Exact 33-row DSH 0.1.5-rc.1 core graph.
|
|
5594
|
+
*
|
|
5595
|
+
* Provenance: every row is the npm registry `dist.integrity` of the exact
|
|
5596
|
+
* published tarball for the named version, read from
|
|
5597
|
+
* `https://registry.npmjs.org/<name>/0.1.5-rc.1` (and `4.0.2` for
|
|
5598
|
+
* `@deepseek-ai/cordis`, which is versioned independently of DSH). The single
|
|
5599
|
+
* resolver for this graph is an isolated DSH installation plus the repository
|
|
5600
|
+
* worktree lockfile, both installed from the public registry.
|
|
5601
|
+
*
|
|
5602
|
+
* This is a REGISTRY-DERIVED graph, not a natively audited one: the cohort
|
|
5603
|
+
* carries `auditedPlatforms: []` until a native macOS/Windows host audit runs,
|
|
5604
|
+
* and `auditProvenance: 'registry-derived-pending-native-audit'` is bound into
|
|
5605
|
+
* the host-lock digest so a certificate can never claim a native pass this round
|
|
5606
|
+
* did not produce. (`acceptedPlatforms` is the separate, wider gate: this cohort
|
|
5607
|
+
* accepts evaluation on both platforms while claiming an audit on neither.)
|
|
5608
|
+
* `dshmarket` is deliberately absent: market identity is verified independently
|
|
5609
|
+
* by the action adapter and never participates in the core lock.
|
|
5610
|
+
*
|
|
5611
|
+
* The row-name set is unchanged from the historical 0.1.2-rc.1 cohort's 33
|
|
5612
|
+
* core rows: no package entered or left the audited core graph, so a future
|
|
5613
|
+
* reader must not infer a graph change from the version bump alone. The count
|
|
5614
|
+
* is asserted from this list, never assumed.
|
|
5615
|
+
*/
|
|
5616
|
+
const RC015_HOST_PACKAGES = [
|
|
5617
|
+
[
|
|
5618
|
+
"@deepseek-ai/cordis",
|
|
5619
|
+
"4.0.2",
|
|
5620
|
+
"sha512-asOnXP1TzFSFQlHb1iegDZp0z/8WD1c7YNrwJR/Tx2bzNuMXfcekE/I67Iv6SQXeLB4csxqCngzQKANP7gdw0g=="
|
|
5621
|
+
],
|
|
5622
|
+
[
|
|
5623
|
+
"@deepseek-ai/dsh",
|
|
5624
|
+
"0.1.5-rc.1",
|
|
5625
|
+
"sha512-rmNmzQCg3oIc1z8xH7izRSOuy1TNzq+/NILyfM+7e8DKOyV+yBtg47WEsqR2SiIe1ATec3L/rUa1YhIcfQ2XEg=="
|
|
5626
|
+
],
|
|
5627
|
+
[
|
|
5628
|
+
"@deepseek-ai/dsh-agent",
|
|
5629
|
+
"0.1.5-rc.1",
|
|
5630
|
+
"sha512-obIPyTSjq1y0Yhasm3mLhK5BW6Ge0VQoRT8FBt0ooLsct2+pFAjgyd7GP3KzFaz7zaEhQJ/NNEmCaU0KOsYutg=="
|
|
5631
|
+
],
|
|
5632
|
+
[
|
|
5633
|
+
"@deepseek-ai/dsh-agent-loop",
|
|
5634
|
+
"0.1.5-rc.1",
|
|
5635
|
+
"sha512-FcpsiXMHR7M3UwZtC6CYzhmU9xhvbFuuEQisfUqW7c+G6oVhrX9kg8ytI50jXZempcdmVHZLyNeUcuDmgGtx7Q=="
|
|
5636
|
+
],
|
|
5637
|
+
[
|
|
5638
|
+
"@deepseek-ai/dsh-attachment",
|
|
5639
|
+
"0.1.5-rc.1",
|
|
5640
|
+
"sha512-uTBtB/LDlYgPI6i9Ac9jaK/bV1wN8cDTZBUkec80Yg3qMzW6K74wvBv5lPmoiXQgp4q3eOmDNEmodSS2ZxxVdg=="
|
|
5641
|
+
],
|
|
5642
|
+
[
|
|
5643
|
+
"@deepseek-ai/dsh-bash-sandbox",
|
|
5644
|
+
"0.1.5-rc.1",
|
|
5645
|
+
"sha512-mQ+/0Fo3LTIX+4k3m+P4y9e9IA6/BeNHrWW19U+Un4hBo5JtTwWhAaJN1nCdV6mG5tveXbIe2tpiv65GJdvt8w=="
|
|
5646
|
+
],
|
|
5647
|
+
[
|
|
5648
|
+
"@deepseek-ai/dsh-commands",
|
|
5649
|
+
"0.1.5-rc.1",
|
|
5650
|
+
"sha512-OMk0uVNbr2RdsItcIigp/2boGulqjei1uoQH3/DZYHB+aWWRXEYa6rk6vW3t82lnkd/6nvyQFV8eccQWZ2PQXQ=="
|
|
5651
|
+
],
|
|
5652
|
+
[
|
|
5653
|
+
"@deepseek-ai/dsh-fs",
|
|
5654
|
+
"0.1.5-rc.1",
|
|
5655
|
+
"sha512-F+loGiwsT09YpRONuFv0+bevEdfmbKBFjxxM8JkDOXpgwk1JXdGNSy7Kp4vXXVh3GcmYkd8VA7iB7NuVp51ytQ=="
|
|
5656
|
+
],
|
|
5657
|
+
[
|
|
5658
|
+
"@deepseek-ai/dsh-fs-local",
|
|
5659
|
+
"0.1.5-rc.1",
|
|
5660
|
+
"sha512-Qyqs9l+ZENq4PL7EwBpisvXcqJvCxGTrD/zH0Zj+S0eZee1kXrQjkY0gjW6dxAosGlLL7hZu0kdaD7TPxtKVcA=="
|
|
5661
|
+
],
|
|
5662
|
+
[
|
|
5663
|
+
"@deepseek-ai/dsh-fs-observation-policy",
|
|
5664
|
+
"0.1.5-rc.1",
|
|
5665
|
+
"sha512-TGu/2UrZS8KWr6x3sKLce7u0KoGrq5l+RK7ITd79n2WNzSCcAdzN5tsxUEo8JgfHij5S0QRgID0Q8DOx/6iQew=="
|
|
5666
|
+
],
|
|
5667
|
+
[
|
|
5668
|
+
"@deepseek-ai/dsh-fs-sandbox",
|
|
5669
|
+
"0.1.5-rc.1",
|
|
5670
|
+
"sha512-np+3EdQ86w609DwyaEUFGEHjSQ5i2NFypQxcM9sB+zX6DVSUR9sA2W/fmnStFJdsSzvgXYTCnJiBhlKnAXPf0g=="
|
|
5671
|
+
],
|
|
5672
|
+
[
|
|
5673
|
+
"@deepseek-ai/dsh-goal",
|
|
5674
|
+
"0.1.5-rc.1",
|
|
5675
|
+
"sha512-RF+cHqV0O7xkoqkhIst6NhMAwqXXAjTf7Z+H7FLHtZBvFAawU8zC7n7/tmS4P7rZYV+XYFYCDdNoI0GwPReCYA=="
|
|
5676
|
+
],
|
|
5677
|
+
[
|
|
5678
|
+
"@deepseek-ai/dsh-host-plugin-inventory",
|
|
5679
|
+
"0.1.5-rc.1",
|
|
5680
|
+
"sha512-xCOJ1nTW2s5etl18QhBBGpcOxiDfGxofe+4pd90/ZX+vW1vAhaHqyTYSRucOQVGyJb9zvdWCg7R3GcBYn6pUrQ=="
|
|
5681
|
+
],
|
|
5682
|
+
[
|
|
5683
|
+
"@deepseek-ai/dsh-host-webserver",
|
|
5684
|
+
"0.1.5-rc.1",
|
|
5685
|
+
"sha512-5kOu9kb0AuRN60/zwPTRcki801ozgnWAFwS1QtQ4ZNgCYIbAiU8gwJHY1//qEpUOuHS+26k+Tqq5/WCJmLGE6Q=="
|
|
5686
|
+
],
|
|
5687
|
+
[
|
|
5688
|
+
"@deepseek-ai/dsh-jobs",
|
|
5689
|
+
"0.1.5-rc.1",
|
|
5690
|
+
"sha512-0AWlZLcIpwdtV9A9fVeJ7b9jpXX0494fPL594gE/Kp1q9jHYyerIulrMHZa17kpu9W59cb1AJNPQy2xN6VDX7g=="
|
|
5691
|
+
],
|
|
5692
|
+
[
|
|
5693
|
+
"@deepseek-ai/dsh-jobs-local",
|
|
5694
|
+
"0.1.5-rc.1",
|
|
5695
|
+
"sha512-19sCxqUKduNO8E3YICSzOfajpBLPXbK/3p40GlxR6bGcOhxqhyH3TPdQS6UQjwNa5bUXHiW9GyB1xUWUUFGAJA=="
|
|
5696
|
+
],
|
|
5697
|
+
[
|
|
5698
|
+
"@deepseek-ai/dsh-llm",
|
|
5699
|
+
"0.1.5-rc.1",
|
|
5700
|
+
"sha512-KPKJFTNLjURphuF4NlS8DRK94CUYL/dKB8Hzg/22jtxAFO2paX3ifL0vhdPq9xPbcyplaF7LYlf0+3+pXFTnTg=="
|
|
5701
|
+
],
|
|
5702
|
+
[
|
|
5703
|
+
"@deepseek-ai/dsh-pwsh-sandbox",
|
|
5704
|
+
"0.1.5-rc.1",
|
|
5705
|
+
"sha512-QRD6PfcQuaRwUTn9EMIx15l1erRqk0erUamcAB3sxPyZOMWIFP7w2cp44Va8RGGDEUb9ZB1vRUH6cF4FAKSZRw=="
|
|
5706
|
+
],
|
|
5707
|
+
[
|
|
5708
|
+
"@deepseek-ai/dsh-sandbox",
|
|
5709
|
+
"0.1.5-rc.1",
|
|
5710
|
+
"sha512-xT+oTsSE7tRZVqqcj2qDZJARoK6A+Dmhf3CWPqlaFG/83Zh41kwcW2YWE4sA+vCt2pI2iqLYRw1SftSGawOtVQ=="
|
|
5711
|
+
],
|
|
5712
|
+
[
|
|
5713
|
+
"@deepseek-ai/dsh-sandbox-policy",
|
|
5714
|
+
"0.1.5-rc.1",
|
|
5715
|
+
"sha512-jLeny81NVsAiEWW8+MqmtkXJfu8CSFDPiuR8W4I/bZ7TNHrPTN7jPuk1/JlphkU4bq1N1a3iLvsDyHK+56ZLVA=="
|
|
5716
|
+
],
|
|
5717
|
+
[
|
|
5718
|
+
"@deepseek-ai/dsh-session",
|
|
5719
|
+
"0.1.5-rc.1",
|
|
5720
|
+
"sha512-0YBBrzkCbVEJolS/OpD0DZMSozmYxUTZopiG76MXInjOFBW9J4ca1a8WUjJjqelP1nJTmWGKXp92HcvNEny0Dg=="
|
|
5721
|
+
],
|
|
5722
|
+
[
|
|
5723
|
+
"@deepseek-ai/dsh-shell",
|
|
5724
|
+
"0.1.5-rc.1",
|
|
5725
|
+
"sha512-8V7iGmfsXDFMyftwQXemh1QLqcUysvy+bYWXr620/1B/sMn3oU8dhXvT8i4uD6VkMy2rds3XScUQ3XBl7ByMLA=="
|
|
5726
|
+
],
|
|
5727
|
+
[
|
|
5728
|
+
"@deepseek-ai/dsh-shell-env",
|
|
5729
|
+
"0.1.5-rc.1",
|
|
5730
|
+
"sha512-OO4AmGqqHUWRPK1PSroO/TJG3rNwoAgIMx56S3aiM7v4cUtNmUtMQ71lv9kOezcW+2VlHxfup5jhzpYIQAIiSw=="
|
|
5731
|
+
],
|
|
5732
|
+
[
|
|
5733
|
+
"@deepseek-ai/dsh-subprocess-local",
|
|
5734
|
+
"0.1.5-rc.1",
|
|
5735
|
+
"sha512-TKcqaIf1fJzjraXhwmSAQAqkPMvIjS0Y7b9fC4n7+G8eQpb3gaF/eJXn6Tx4OgFSDV5R/NLUqHaU/ogxTjdWhQ=="
|
|
5736
|
+
],
|
|
5737
|
+
[
|
|
5738
|
+
"@deepseek-ai/dsh-system-prompt",
|
|
5739
|
+
"0.1.5-rc.1",
|
|
5740
|
+
"sha512-RAdO9biQoga1vAVTQY9J7THexiOE1FOd1Nli021MQt+Zf73c83BSd2DwXb0WDilLaMeSxZPaIRRqoXpJlpmLIA=="
|
|
5741
|
+
],
|
|
5742
|
+
[
|
|
5743
|
+
"@deepseek-ai/dsh-tool-bash",
|
|
5744
|
+
"0.1.5-rc.1",
|
|
5745
|
+
"sha512-BfZ4R40I7AJFcjHgMkzh3unrp5S7mZT+szUUz4tkbMrAkgHfOdkIHhQ/BTgxWBuFzD18OfAMJ/RnegrBCFVKWw=="
|
|
5746
|
+
],
|
|
5747
|
+
[
|
|
5748
|
+
"@deepseek-ai/dsh-tool-fs",
|
|
5749
|
+
"0.1.5-rc.1",
|
|
5750
|
+
"sha512-BWLWJCJxCECFHmS8gHbnyNJlSTG+KVbVMz73Qduoo+ABeDvWj6cFVXTewAUA9jFEIS3xzJcNilsV1g5DGaEnPw=="
|
|
5751
|
+
],
|
|
5752
|
+
[
|
|
5753
|
+
"@deepseek-ai/dsh-tool-goal",
|
|
5754
|
+
"0.1.5-rc.1",
|
|
5755
|
+
"sha512-5NCniCOoCeXYXMZNPCmGlrOYIGjnGddTJVOCXNqqAcwxtOxHAoEHTtphaohIa/4Vy7mmRIHgO1KUii443ky1Bw=="
|
|
5756
|
+
],
|
|
5757
|
+
[
|
|
5758
|
+
"@deepseek-ai/dsh-tool-jobs",
|
|
5759
|
+
"0.1.5-rc.1",
|
|
5760
|
+
"sha512-SIgxnjQHl6KE+kpt7VjYCI3aw5DCeztCKGxuJzpLdqJkSoVtewp1Ofz0/Pg1R4DIIaGR8unRyqpZH8qf8uIpzA=="
|
|
5761
|
+
],
|
|
5762
|
+
[
|
|
5763
|
+
"@deepseek-ai/dsh-tool-pwsh",
|
|
5764
|
+
"0.1.5-rc.1",
|
|
5765
|
+
"sha512-UmWePfsJIUfFVj2UFyh8wacqxSXYrABGoBHXWjYFlfqpXt7rfJL31rOl8N1+uYypAVCxe4O2IquuJxfYAVhBLA=="
|
|
5766
|
+
],
|
|
5767
|
+
[
|
|
5768
|
+
"@deepseek-ai/dsh-tools",
|
|
5769
|
+
"0.1.5-rc.1",
|
|
5770
|
+
"sha512-I5AUxKTqUrC0nvRO4UcpU+f65P+nKs5BUrS2nqZehhFZ2rVxhUAJ7YdORcY6pVkBTB15nPr5gK0WPgwyfS217w=="
|
|
5771
|
+
],
|
|
5772
|
+
[
|
|
5773
|
+
"@deepseek-ai/dsh-user-approval",
|
|
5774
|
+
"0.1.5-rc.1",
|
|
5775
|
+
"sha512-fSxEBvHQnozIh5HV31t2BNodYzyv7iE2srna7p5k0Y/3R9c6DdoZyQZ9momcN9gloraq8HK5+cvrYHzgc4LYPQ=="
|
|
5776
|
+
],
|
|
5777
|
+
[
|
|
5778
|
+
"@deepseek-ai/dsh-web-app",
|
|
5779
|
+
"0.1.5-rc.1",
|
|
5780
|
+
"sha512-9V2GPqEs0A+LFJVVPt7FQK//U8oM9S0TDhl6MqO7zQftimfnH8ruQZkEZXg9zXXEWULCW0vY1DtsPjvMepA8Mw=="
|
|
5781
|
+
]
|
|
5782
|
+
].map(([name, version, integrity]) => ({
|
|
5783
|
+
name,
|
|
5784
|
+
version,
|
|
5785
|
+
integrity
|
|
5786
|
+
}));
|
|
5787
|
+
|
|
5788
|
+
//#endregion
|
|
5789
|
+
//#region src/domain/rc015-rc2-host.ts
|
|
5790
|
+
/** Exact npm registry identities for DSH 0.1.5-rc.2 (Cordis 4.0.2).
|
|
5791
|
+
* Native acceptance is recorded separately; these rows are registry-derived.
|
|
5792
|
+
*/
|
|
5793
|
+
const RC015_RC2_HOST_PACKAGES = [
|
|
5794
|
+
{
|
|
5795
|
+
"name": "@deepseek-ai/cordis",
|
|
5796
|
+
"version": "4.0.2",
|
|
5797
|
+
"integrity": "sha512-asOnXP1TzFSFQlHb1iegDZp0z/8WD1c7YNrwJR/Tx2bzNuMXfcekE/I67Iv6SQXeLB4csxqCngzQKANP7gdw0g=="
|
|
5798
|
+
},
|
|
5799
|
+
{
|
|
5800
|
+
"name": "@deepseek-ai/dsh",
|
|
5801
|
+
"version": "0.1.5-rc.2",
|
|
5802
|
+
"integrity": "sha512-8Xc8hCQHcIWRmTCVU/xZdp6/qMsWMeAd2ObChKDEsfhUPJFXx6H0lgeb1DxUMD86HZrrVN+1bCvn1ppjZ/fOxw=="
|
|
5803
|
+
},
|
|
5804
|
+
{
|
|
5805
|
+
"name": "@deepseek-ai/dsh-agent",
|
|
5806
|
+
"version": "0.1.5-rc.2",
|
|
5807
|
+
"integrity": "sha512-SlUL1riZmVLwMUR3jo9CP/R1cxov9dHkCJDh6JQW3fSZJVCIdPygBRlAweCUDvHxAEmPpFHxE/U3NmSUbX+vQQ=="
|
|
5808
|
+
},
|
|
5809
|
+
{
|
|
5810
|
+
"name": "@deepseek-ai/dsh-agent-loop",
|
|
5811
|
+
"version": "0.1.5-rc.2",
|
|
5812
|
+
"integrity": "sha512-24wvqVlqFmdqJ2Bhcku/vKeNy+qWSmeEeN7lvcUB+WkhcF0/Ra2G3WwcVmnahJA4+w0AKdW3W7v+YYcO/jKJpg=="
|
|
5813
|
+
},
|
|
5814
|
+
{
|
|
5815
|
+
"name": "@deepseek-ai/dsh-attachment",
|
|
5816
|
+
"version": "0.1.5-rc.2",
|
|
5817
|
+
"integrity": "sha512-S6b8/WjqzGw+dMDLRXnq+tbijDGkQh38yE+zpQytX2/w/mPR3VzGj5r6McS01WwD76vXR8WFoheSCLyCAro8WQ=="
|
|
5818
|
+
},
|
|
5819
|
+
{
|
|
5820
|
+
"name": "@deepseek-ai/dsh-bash-sandbox",
|
|
5821
|
+
"version": "0.1.5-rc.2",
|
|
5822
|
+
"integrity": "sha512-y8vwK6jf4gPq8mG71zWure803NjfqJ66Nvzr1FTB8An+IF68hT2eU+hHJCByvP0u6FuiLPRptuX/im2CxcNo1g=="
|
|
5823
|
+
},
|
|
5824
|
+
{
|
|
5825
|
+
"name": "@deepseek-ai/dsh-commands",
|
|
5826
|
+
"version": "0.1.5-rc.2",
|
|
5827
|
+
"integrity": "sha512-ODc9h2Jig+Lo4XLdxqHpHjSXsBFeUCth6Y/rToor4KVRWQMedUARlq5otPyB1lYHyQh2DonNs2uf7g3mvag57A=="
|
|
5828
|
+
},
|
|
5829
|
+
{
|
|
5830
|
+
"name": "@deepseek-ai/dsh-fs",
|
|
5831
|
+
"version": "0.1.5-rc.2",
|
|
5832
|
+
"integrity": "sha512-6DHTquXPbpYdykGayqYaXSI9t668tDCoswH/bDezV+nwj4LxjrfY/smEtgp7nC4ubyoKf1hmjpNfUonGC9e7aA=="
|
|
5833
|
+
},
|
|
5834
|
+
{
|
|
5835
|
+
"name": "@deepseek-ai/dsh-fs-local",
|
|
5836
|
+
"version": "0.1.5-rc.2",
|
|
5837
|
+
"integrity": "sha512-akUTz9D/N0ruSOzytZ8SZ330SdzG75fd7DzHsJ7KJzSif/QM0Y+RpOmGMnjlJzit4HDV2A4Etn80wSzstyp82A=="
|
|
5838
|
+
},
|
|
5839
|
+
{
|
|
5840
|
+
"name": "@deepseek-ai/dsh-fs-observation-policy",
|
|
5841
|
+
"version": "0.1.5-rc.2",
|
|
5842
|
+
"integrity": "sha512-AntY5dfkTL8WugNGHkJxCEffaTFHqdXaYm7ZrerexnLiZQscDRRjf9zI00JwOmlFM/IrioCuLf5cERfhZN5GYw=="
|
|
5843
|
+
},
|
|
5844
|
+
{
|
|
5845
|
+
"name": "@deepseek-ai/dsh-fs-sandbox",
|
|
5846
|
+
"version": "0.1.5-rc.2",
|
|
5847
|
+
"integrity": "sha512-eUxNsnM+TsjGw5OleOIcAhMnFhmQ4OAZoBYeiRMSeOMuCKWEjhxUGN8S8Hg1HxPaZVeIrUV7qFsNQzhehKj7wg=="
|
|
5848
|
+
},
|
|
5849
|
+
{
|
|
5850
|
+
"name": "@deepseek-ai/dsh-goal",
|
|
5851
|
+
"version": "0.1.5-rc.2",
|
|
5852
|
+
"integrity": "sha512-atFJaoijwAz5yZ119f82I7jMx3tGCwXOz6qoY0Likb2c5DpumWZTJgs5L19OhKbhvEW+r2MAC4MYKaUxtrbb0Q=="
|
|
5853
|
+
},
|
|
5854
|
+
{
|
|
5855
|
+
"name": "@deepseek-ai/dsh-host-plugin-inventory",
|
|
5856
|
+
"version": "0.1.5-rc.2",
|
|
5857
|
+
"integrity": "sha512-U7RTRLRs+O18ru6KzUy7LvMk/IgXnkWSalogg0abfA+QU020XZi+UpwZqM567RzC7MtpxijQk8bulGunu0lifw=="
|
|
5858
|
+
},
|
|
5859
|
+
{
|
|
5860
|
+
"name": "@deepseek-ai/dsh-host-webserver",
|
|
5861
|
+
"version": "0.1.5-rc.2",
|
|
5862
|
+
"integrity": "sha512-lFgGm9wDrHiTBANzsdoWzdfPSjWYuDFwCoNQ4Uko57Fo5XASL2unfRHGm1xZ828rwEYuGwRvJHMOuoP/17VmlA=="
|
|
5863
|
+
},
|
|
5864
|
+
{
|
|
5865
|
+
"name": "@deepseek-ai/dsh-jobs",
|
|
5866
|
+
"version": "0.1.5-rc.2",
|
|
5867
|
+
"integrity": "sha512-C3rBEuWhtDBlxMeKykFvSfBwjSPxkLsvKCFq8BrFdjDmZC1lI9GooMjPZkPxXVbogVrcOBaYVtJdOYJ4+rIpQg=="
|
|
5868
|
+
},
|
|
5869
|
+
{
|
|
5870
|
+
"name": "@deepseek-ai/dsh-jobs-local",
|
|
5871
|
+
"version": "0.1.5-rc.2",
|
|
5872
|
+
"integrity": "sha512-PCDLSktONJ+If3QCcPlRskVLXIa8hG/l0pBIgKNllz4mb7CmYlJ5w1H9pWyrFZllMO0Wm94ZS9aP3SnUcJ8R1g=="
|
|
5873
|
+
},
|
|
5874
|
+
{
|
|
5875
|
+
"name": "@deepseek-ai/dsh-llm",
|
|
5876
|
+
"version": "0.1.5-rc.2",
|
|
5877
|
+
"integrity": "sha512-Z7BVsBkK24SE4EItQeow8PHms/9GP0DSTi337vTAa/RY7tNg2Snz3INcXUj6CPZfvntQr1in9op9wLI+rfNsqA=="
|
|
5878
|
+
},
|
|
5879
|
+
{
|
|
5880
|
+
"name": "@deepseek-ai/dsh-pwsh-sandbox",
|
|
5881
|
+
"version": "0.1.5-rc.2",
|
|
5882
|
+
"integrity": "sha512-AYTAiy8wQwVoHO47e+hnG73GvHdmg+bKy3bFnrPDANCqnRZh9TJIbkxHxtJnfpBnNCcs2fhhCo6yHtGFYl7KHg=="
|
|
5883
|
+
},
|
|
5884
|
+
{
|
|
5885
|
+
"name": "@deepseek-ai/dsh-sandbox",
|
|
5886
|
+
"version": "0.1.5-rc.2",
|
|
5887
|
+
"integrity": "sha512-OTOR6Jj9cey5YkhALG0TBwZ/Z3t986aczH6fLbzoIIegix+gwNaEBOqCWs+exVJ0Z2QuN/ItXzn+xHxW8Y0dcA=="
|
|
5888
|
+
},
|
|
5889
|
+
{
|
|
5890
|
+
"name": "@deepseek-ai/dsh-sandbox-policy",
|
|
5891
|
+
"version": "0.1.5-rc.2",
|
|
5892
|
+
"integrity": "sha512-QyQSCyLFxljkvmsVWJG0xUrYTiXr1DVOxHWURf7EHnbmvYZgg2B+nFcI58+IeUB2qbB35B1ClW5NQsKjOgDm2g=="
|
|
5893
|
+
},
|
|
5894
|
+
{
|
|
5895
|
+
"name": "@deepseek-ai/dsh-session",
|
|
5896
|
+
"version": "0.1.5-rc.2",
|
|
5897
|
+
"integrity": "sha512-y+klWiGAWR4m4cc4ylurA0cW63673B4N8cr2ANMimweDZAfxL4XVBC7WiD/5DT2DtIhYmVZhz/niyS/WbniUTA=="
|
|
5898
|
+
},
|
|
5899
|
+
{
|
|
5900
|
+
"name": "@deepseek-ai/dsh-shell",
|
|
5901
|
+
"version": "0.1.5-rc.2",
|
|
5902
|
+
"integrity": "sha512-BfmNN6X0NHN2XleW0fCbtFXedXEppeDQ2oa3WsOTuhvnBftl6QWMWWMM59weE4xXker5fzqOnoJdeBR8D9qR9Q=="
|
|
5903
|
+
},
|
|
5904
|
+
{
|
|
5905
|
+
"name": "@deepseek-ai/dsh-shell-env",
|
|
5906
|
+
"version": "0.1.5-rc.2",
|
|
5907
|
+
"integrity": "sha512-fFSrfhxfvVYfDxsOuV0cjAeC/PWweW+86uOJWT+2paHXOSgM6MSDe3eTd5DHRDYnjb5XutTYu32pR49cVBHMug=="
|
|
5908
|
+
},
|
|
5909
|
+
{
|
|
5910
|
+
"name": "@deepseek-ai/dsh-subprocess-local",
|
|
5911
|
+
"version": "0.1.5-rc.2",
|
|
5912
|
+
"integrity": "sha512-DmG3lcQlAh8bTKfeyYM44cfRyJktbLL8drXrVfOvGrFk0zSs5eTSLcfhIKLT3jFJ5CKFriHYZPLdRT6Alvyy4w=="
|
|
5913
|
+
},
|
|
5914
|
+
{
|
|
5915
|
+
"name": "@deepseek-ai/dsh-system-prompt",
|
|
5916
|
+
"version": "0.1.5-rc.2",
|
|
5917
|
+
"integrity": "sha512-VtmZVKqBMJ7kzskHu0jY+Jth7jSuKMG8QB3MBPzJe9M0LL4YPMWsGKt9gGky9RXolk/ugAy4YZEv1gUqouVofA=="
|
|
5918
|
+
},
|
|
5919
|
+
{
|
|
5920
|
+
"name": "@deepseek-ai/dsh-tool-bash",
|
|
5921
|
+
"version": "0.1.5-rc.2",
|
|
5922
|
+
"integrity": "sha512-f4LmiZkZSfJfvBcEzV4q5J83VL79l/+ncLkwnJyHzsjvJbW5qFxzCy4p5FXfY/CflG0taG14/p42UJzb9qEhrQ=="
|
|
5923
|
+
},
|
|
5924
|
+
{
|
|
5925
|
+
"name": "@deepseek-ai/dsh-tool-fs",
|
|
5926
|
+
"version": "0.1.5-rc.2",
|
|
5927
|
+
"integrity": "sha512-/3AUx+V1UxVfl24fm10hwRbJnHwpBkRDHniOeocGknMvASheRiKnHpnnj9EszFRLYSpe2PRFSMuyuhXgYyd3MQ=="
|
|
5928
|
+
},
|
|
5929
|
+
{
|
|
5930
|
+
"name": "@deepseek-ai/dsh-tool-goal",
|
|
5931
|
+
"version": "0.1.5-rc.2",
|
|
5932
|
+
"integrity": "sha512-nZ0NkUxvtAsrPAd9NMXt+4kS7WPn8xIvXCwVunKfBwbrnGnFCNmIsTQoHF44J6q9VbMeSCN2eyj07w6ej3Hf+g=="
|
|
5933
|
+
},
|
|
5934
|
+
{
|
|
5935
|
+
"name": "@deepseek-ai/dsh-tool-jobs",
|
|
5936
|
+
"version": "0.1.5-rc.2",
|
|
5937
|
+
"integrity": "sha512-v4y56H3FsVBF2jUJLGLHdPeKaKJxLT4Zx/oii45UmJv5t8Bp3uCTVUoq/mfSFVuFY66q6rHJwgIOKWc4ZO3ySw=="
|
|
5938
|
+
},
|
|
5939
|
+
{
|
|
5940
|
+
"name": "@deepseek-ai/dsh-tool-pwsh",
|
|
5941
|
+
"version": "0.1.5-rc.2",
|
|
5942
|
+
"integrity": "sha512-rnHM3Jqlr7rthwfPYysXPq6zH+K8JTDqbE+xzjkbo1WIBKBZeSGr8kPjqdCZa7eUtWhmV/RFBP1qmBgDjEZaWg=="
|
|
5943
|
+
},
|
|
5944
|
+
{
|
|
5945
|
+
"name": "@deepseek-ai/dsh-tools",
|
|
5946
|
+
"version": "0.1.5-rc.2",
|
|
5947
|
+
"integrity": "sha512-k2yZuJJtszaU9lzr2aBtdeFMINrkdlk4ellbtrMokA2oySVqJmAM8dv+u9RtzduD3aRRqyr2i2hWrTACz0qOrA=="
|
|
5948
|
+
},
|
|
5949
|
+
{
|
|
5950
|
+
"name": "@deepseek-ai/dsh-user-approval",
|
|
5951
|
+
"version": "0.1.5-rc.2",
|
|
5952
|
+
"integrity": "sha512-8UpMEnEyMo6mYEVELBo0DC2iG7aJJfFMTNkU+DKD3c5Ut7ySRDzt1iRr1qni/CzqlTIpQBAkGaG77qk4q/v1bg=="
|
|
5953
|
+
},
|
|
5954
|
+
{
|
|
5955
|
+
"name": "@deepseek-ai/dsh-web-app",
|
|
5956
|
+
"version": "0.1.5-rc.2",
|
|
5957
|
+
"integrity": "sha512-Ng7YVDt9txh2BlLmu6B+V677c1bihbh/rq3EOtZK4b0JWtgdNgYb1KPIED8+aK2i+FghrCrIy6X60AyJ45eOvw=="
|
|
5958
|
+
}
|
|
5959
|
+
];
|
|
5960
|
+
|
|
5961
|
+
//#endregion
|
|
5962
|
+
//#region src/domain/host-version.ts
|
|
5963
|
+
/**
|
|
5964
|
+
* DSH host version support policy.
|
|
5965
|
+
*
|
|
5966
|
+
* Context Guard 0.5.2 supports exactly the two registered DSH host releases:
|
|
5967
|
+
* `0.1.5-rc.2` (latest) and `0.1.5-rc.1` (verified minimum). Package discovery,
|
|
5968
|
+
* npm installation, and the exported support range use the same newest-first
|
|
5969
|
+
* exact union, so an unregistered stable or future prerelease is never advertised
|
|
5970
|
+
* merely because it sorts above the minimum.
|
|
5971
|
+
*
|
|
5972
|
+
* The minimum comparison remains a diagnostic layer for distinguishing an old
|
|
5973
|
+
* host from an at-or-above-floor but unregistered host. It never substitutes for
|
|
5974
|
+
* the exact support set or the complete 33-package host graph.
|
|
5975
|
+
*/
|
|
5976
|
+
/** Lowest supported DSH host version. DSH packages version independently of Cordis. */
|
|
5977
|
+
const MIN_SUPPORTED_HOST_VERSION = "0.1.5-rc.1";
|
|
5978
|
+
/** Latest DSH release with a registered complete host graph. */
|
|
5979
|
+
const LATEST_SUPPORTED_HOST_VERSION = "0.1.5-rc.2";
|
|
5980
|
+
/** Exact endpoints supported by the current release, newest first. */
|
|
5981
|
+
const SUPPORTED_HOST_VERSIONS = [LATEST_SUPPORTED_HOST_VERSION, MIN_SUPPORTED_HOST_VERSION];
|
|
5982
|
+
/** Exact npm range shared by package discovery and peer dependency declarations. */
|
|
5983
|
+
const SUPPORTED_HOST_RANGE = SUPPORTED_HOST_VERSIONS.join(" || ");
|
|
5984
|
+
const VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
5985
|
+
function parseHostVersion(value) {
|
|
5986
|
+
const match = VERSION_PATTERN.exec(value.trim());
|
|
5987
|
+
if (!match) return void 0;
|
|
5988
|
+
const parts = [
|
|
5989
|
+
match[1],
|
|
5990
|
+
match[2],
|
|
5991
|
+
match[3]
|
|
5992
|
+
].map(Number);
|
|
5993
|
+
if (parts.some((part) => !Number.isSafeInteger(part) || part < 0)) return void 0;
|
|
5994
|
+
const prerelease = match[4] ? match[4].split(".") : [];
|
|
5995
|
+
if (prerelease.some((identifier) => identifier.length === 0)) return void 0;
|
|
5996
|
+
return {
|
|
5997
|
+
major: parts[0],
|
|
5998
|
+
minor: parts[1],
|
|
5999
|
+
patch: parts[2],
|
|
6000
|
+
prerelease
|
|
6001
|
+
};
|
|
6002
|
+
}
|
|
6003
|
+
function comparePrerelease(a, b) {
|
|
6004
|
+
if (a.length === 0 && b.length === 0) return 0;
|
|
6005
|
+
if (a.length === 0) return 1;
|
|
6006
|
+
if (b.length === 0) return -1;
|
|
6007
|
+
const length = Math.max(a.length, b.length);
|
|
6008
|
+
for (let index = 0; index < length; index += 1) {
|
|
6009
|
+
const left = a[index];
|
|
6010
|
+
const right = b[index];
|
|
6011
|
+
if (left === void 0) return -1;
|
|
6012
|
+
if (right === void 0) return 1;
|
|
6013
|
+
const leftNumeric = /^\d+$/.test(left);
|
|
6014
|
+
const rightNumeric = /^\d+$/.test(right);
|
|
6015
|
+
if (leftNumeric && rightNumeric) {
|
|
6016
|
+
const difference = Number(left) - Number(right);
|
|
6017
|
+
if (difference !== 0) return difference < 0 ? -1 : 1;
|
|
6018
|
+
continue;
|
|
6019
|
+
}
|
|
6020
|
+
if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1;
|
|
6021
|
+
if (left !== right) return left < right ? -1 : 1;
|
|
6022
|
+
}
|
|
6023
|
+
return 0;
|
|
6024
|
+
}
|
|
6025
|
+
/**
|
|
6026
|
+
* SemVer precedence comparison, including the prerelease rules. Returns
|
|
6027
|
+
* `undefined` for a value that is not a version this module can order, so an
|
|
6028
|
+
* unparseable host version fails closed rather than sorting as "newer".
|
|
6029
|
+
*/
|
|
6030
|
+
function compareHostVersions(a, b) {
|
|
6031
|
+
const left = parseHostVersion(a);
|
|
6032
|
+
const right = parseHostVersion(b);
|
|
6033
|
+
if (!left || !right) return void 0;
|
|
6034
|
+
if (left.major !== right.major) return left.major < right.major ? -1 : 1;
|
|
6035
|
+
if (left.minor !== right.minor) return left.minor < right.minor ? -1 : 1;
|
|
6036
|
+
if (left.patch !== right.patch) return left.patch < right.patch ? -1 : 1;
|
|
6037
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
6038
|
+
}
|
|
6039
|
+
/** Decide the version-policy half of host support. Never a substitute for the graph lock. */
|
|
6040
|
+
function evaluateMinimumHostVersion(version, minimum = MIN_SUPPORTED_HOST_VERSION) {
|
|
6041
|
+
const comparison = compareHostVersions(version, minimum);
|
|
6042
|
+
if (comparison === void 0) return {
|
|
6043
|
+
status: "unparseable",
|
|
6044
|
+
version,
|
|
6045
|
+
minimum,
|
|
6046
|
+
reasonCode: "host_version_unparseable"
|
|
6047
|
+
};
|
|
6048
|
+
return comparison < 0 ? {
|
|
6049
|
+
status: "below_minimum",
|
|
6050
|
+
version,
|
|
6051
|
+
minimum,
|
|
6052
|
+
reasonCode: "host_version_below_minimum"
|
|
6053
|
+
} : {
|
|
6054
|
+
status: "supported",
|
|
6055
|
+
version,
|
|
6056
|
+
minimum,
|
|
6057
|
+
reasonCode: "host_version_supported"
|
|
6058
|
+
};
|
|
6059
|
+
}
|
|
6060
|
+
/** Whether npm's exact public support union admits this host version. */
|
|
6061
|
+
function satisfiesSupportedHostRange(version) {
|
|
6062
|
+
const normalized = version.trim();
|
|
6063
|
+
if (!parseHostVersion(normalized)) return false;
|
|
6064
|
+
return SUPPORTED_HOST_VERSIONS.some((supported) => compareHostVersions(normalized, supported) === 0);
|
|
6065
|
+
}
|
|
6066
|
+
|
|
4052
6067
|
//#endregion
|
|
4053
6068
|
//#region src/domain/host-lock.ts
|
|
4054
6069
|
/**
|
|
4055
|
-
* Capability expectations shared by every
|
|
4056
|
-
*
|
|
4057
|
-
*
|
|
4058
|
-
*
|
|
4059
|
-
*
|
|
6070
|
+
* Capability expectations shared by every registered cohort.
|
|
6071
|
+
*
|
|
6072
|
+
* Every row is a host contract Guard actually consumes, re-checked against the
|
|
6073
|
+
* 0.1.5-rc.1 package surfaces: `ctx.sessions.flush()` still returns whether a
|
|
6074
|
+
* durability listener participated; `tools.guard()` is still a monotonic
|
|
6075
|
+
* post-policy denial; the Goal service still exposes `get`/`disarm` with a
|
|
6076
|
+
* disarming `pause`; the `update_goal` tool is still the pinned pre-commit gate;
|
|
6077
|
+
* `ctx.jobs.get()` still yields the `dsh.jobs.v1` status vocabulary; and
|
|
6078
|
+
* `dsh-tool-fs` still registers `read`/`write`/`edit` with the same parameter
|
|
6079
|
+
* and result contract (`dsh.fs-tools.v1`).
|
|
6080
|
+
*
|
|
6081
|
+
* What is NOT a row, because it changed rather than stayed compatible: the
|
|
6082
|
+
* Session event API and vocabulary. Guard 0.5.1 supports only V3
|
|
6083
|
+
* `snapshotEvents()` and refuses a session that does not expose it, so a V2
|
|
6084
|
+
* host is rejected by the cohort's exact package rows before any capability row
|
|
6085
|
+
* is consulted.
|
|
4060
6086
|
*/
|
|
4061
6087
|
const AUDITED_CAPABILITY_ROWS = [
|
|
4062
6088
|
{
|
|
@@ -4116,20 +6142,32 @@ const AUDITED_CAPABILITY_ROWS = [
|
|
|
4116
6142
|
}
|
|
4117
6143
|
}))
|
|
4118
6144
|
];
|
|
4119
|
-
function defineCohort(id, supportedGoalVersions, auditedPlatforms, packages) {
|
|
6145
|
+
function defineCohort(id, supportedGoalVersions, auditedPlatforms, packages, auditProvenance = "native-audited", acceptedPlatforms = auditedPlatforms) {
|
|
4120
6146
|
return {
|
|
4121
6147
|
id,
|
|
4122
6148
|
manifestVersion: 1,
|
|
4123
6149
|
supportedGoalVersions,
|
|
4124
6150
|
auditedPlatforms,
|
|
6151
|
+
acceptedPlatforms,
|
|
6152
|
+
auditProvenance,
|
|
4125
6153
|
packages,
|
|
4126
|
-
capabilities: [
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
6154
|
+
capabilities: [
|
|
6155
|
+
{
|
|
6156
|
+
name: "host_cohort",
|
|
6157
|
+
value: {
|
|
6158
|
+
k: "s",
|
|
6159
|
+
v: id
|
|
6160
|
+
}
|
|
6161
|
+
},
|
|
6162
|
+
{
|
|
6163
|
+
name: "host_audit_provenance",
|
|
6164
|
+
value: {
|
|
6165
|
+
k: "s",
|
|
6166
|
+
v: auditProvenance
|
|
6167
|
+
}
|
|
6168
|
+
},
|
|
6169
|
+
...AUDITED_CAPABILITY_ROWS
|
|
6170
|
+
]
|
|
4133
6171
|
};
|
|
4134
6172
|
}
|
|
4135
6173
|
/**
|
|
@@ -4325,8 +6363,8 @@ const ALPHA2_DSHMARKET_139_HOST_PACKAGES = ALPHA2_HOST_PACKAGES.map((row) => row
|
|
|
4325
6363
|
/**
|
|
4326
6364
|
* Historical audited host cohort registry. Every entry keeps the exact package
|
|
4327
6365
|
* identities audited natively for a past Guard release (CG-DSH-001 whole-graph
|
|
4328
|
-
* contracts). These are historical verification facts only: since 0.5.
|
|
4329
|
-
* active support
|
|
6366
|
+
* contracts). These are historical verification facts only: since 0.5.1 the
|
|
6367
|
+
* active support targets are `0.1.5-rc.1` and `0.1.5-rc.2`, so an installed graph from any of
|
|
4330
6368
|
* these cohorts — including previous RCs and alphas — is no longer an active
|
|
4331
6369
|
* support entry and fails closed in `evaluateHostLock`.
|
|
4332
6370
|
*/
|
|
@@ -4506,15 +6544,22 @@ const LEGACY_HOST_COHORTS = [
|
|
|
4506
6544
|
defineCohort("dsh-0.1.2-alpha.2", ["0.1.2-alpha.2"], ["posix", "windows"], ALPHA2_HOST_PACKAGES),
|
|
4507
6545
|
defineCohort("dsh-0.1.2-alpha.2-dshmarket-1.39.0", ["0.1.2-alpha.2"], ["posix", "windows"], ALPHA2_DSHMARKET_139_HOST_PACKAGES),
|
|
4508
6546
|
defineCohort("dsh-0.1.2-alpha.3", ["0.1.2-alpha.3"], ["posix", "windows"], ALPHA3_HOST_PACKAGES),
|
|
4509
|
-
defineCohort("dsh-0.1.2-rc.1", ["0.1.2-rc.1"], ["posix", "windows"], RC1_HOST_PACKAGES)
|
|
6547
|
+
defineCohort("dsh-0.1.2-rc.1", ["0.1.2-rc.1"], ["posix", "windows"], RC1_HOST_PACKAGES),
|
|
6548
|
+
defineCohort("dsh-0.1.5-rc.1", ["0.1.5-rc.1"], [], RC015_HOST_PACKAGES, "registry-derived-pending-native-audit", ["posix", "windows"])
|
|
4510
6549
|
];
|
|
6550
|
+
/** Baseline cohort retained for callers that need a default fixture. */
|
|
6551
|
+
const ACTIVE_HOST_COHORT_ID = "dsh-0.1.5-rc.1";
|
|
6552
|
+
const ACTIVE_HOST_COHORT_IDS = [ACTIVE_HOST_COHORT_ID, "dsh-0.1.5-rc.2"];
|
|
4511
6553
|
/** Core-lock/v1 separates optional market identity from the audited DSH graph.
|
|
4512
|
-
* The active support
|
|
6554
|
+
* The active support targets are the exact registered rc.1 and rc.2 graphs:
|
|
4513
6555
|
* historical cohorts stay in `LEGACY_HOST_COHORTS` as verification data but are
|
|
4514
6556
|
* never silently re-labelled as accepted active locks, and an installed
|
|
4515
|
-
* historical graph fails closed under `evaluateHostLock`.
|
|
6557
|
+
* historical graph fails closed under `evaluateHostLock`. The version policy
|
|
6558
|
+
* (the exact rc.2-or-rc.1 public set) and the graph lock are separate
|
|
6559
|
+
* judgments: a host that has not been registered here is "unverified / pending
|
|
6560
|
+
* audit", never supported by version order alone.
|
|
4516
6561
|
*/
|
|
4517
|
-
const HOST_COHORTS = LEGACY_HOST_COHORTS
|
|
6562
|
+
const HOST_COHORTS = [...LEGACY_HOST_COHORTS, defineCohort("dsh-0.1.5-rc.2", ["0.1.5-rc.2"], [], RC015_RC2_HOST_PACKAGES, "registry-derived-pending-native-audit", ["posix", "windows"])].filter((cohort) => ACTIVE_HOST_COHORT_IDS.includes(cohort.id)).map((cohort) => ({
|
|
4518
6563
|
...cohort,
|
|
4519
6564
|
id: `${cohort.id}-core-v1`,
|
|
4520
6565
|
manifestVersion: 2,
|
|
@@ -4534,17 +6579,30 @@ const HOST_COHORTS = LEGACY_HOST_COHORTS.filter((cohort) => cohort.id === "dsh-0
|
|
|
4534
6579
|
v: "dsh-core/v1"
|
|
4535
6580
|
}
|
|
4536
6581
|
},
|
|
6582
|
+
{
|
|
6583
|
+
name: "host_audit_provenance",
|
|
6584
|
+
value: {
|
|
6585
|
+
k: "s",
|
|
6586
|
+
v: cohort.auditProvenance
|
|
6587
|
+
}
|
|
6588
|
+
},
|
|
4537
6589
|
...AUDITED_CAPABILITY_ROWS
|
|
4538
6590
|
]
|
|
4539
6591
|
}));
|
|
4540
6592
|
/**
|
|
4541
|
-
*
|
|
4542
|
-
*
|
|
4543
|
-
*
|
|
4544
|
-
*
|
|
4545
|
-
*
|
|
6593
|
+
* Baseline fixture package identities (DSH 0.1.5-rc.1). The cohort
|
|
6594
|
+
* is an atomic whole-graph contract (CG-DSH-001): any drifted, duplicated,
|
|
6595
|
+
* unknown-version, unbound, OR MISSING row fails the whole lock closed
|
|
6596
|
+
* (`host_lock_missing`); no capability inherits independence from a partially
|
|
6597
|
+
* present graph.
|
|
4546
6598
|
*/
|
|
4547
6599
|
const EXPECTED_HOST_PACKAGES = HOST_COHORTS[0].packages;
|
|
6600
|
+
/**
|
|
6601
|
+
* The `@deepseek-ai/dsh` launcher version of the baseline fixture, read from the
|
|
6602
|
+
* cohort rows rather than hardcoded, so a cohort bump cannot leave a stale
|
|
6603
|
+
* literal behind in the target-inspection path.
|
|
6604
|
+
*/
|
|
6605
|
+
const ACTIVE_HOST_LAUNCHER_VERSION = EXPECTED_HOST_PACKAGES.find((row) => row.name === "@deepseek-ai/dsh")?.version;
|
|
4548
6606
|
const packageNames = (...names) => new Set(names);
|
|
4549
6607
|
const BASE_HOST_PACKAGES = packageNames("@deepseek-ai/cordis", "@deepseek-ai/dsh-agent", "@deepseek-ai/dsh-commands", "@deepseek-ai/dsh-llm", "@deepseek-ai/dsh-session", "@deepseek-ai/dsh-tools");
|
|
4550
6608
|
const GOAL_HOST_PACKAGES = packageNames("@deepseek-ai/dsh-goal", "@deepseek-ai/dsh-tool-goal");
|
|
@@ -4559,6 +6617,16 @@ const HOST_CAPABILITY_PACKAGE_GROUPS = {
|
|
|
4559
6617
|
filesystem: packageNames("@deepseek-ai/dsh-tool-fs", "@deepseek-ai/dsh-fs", "@deepseek-ai/dsh-fs-local", "@deepseek-ai/dsh-fs-sandbox", "@deepseek-ai/dsh-fs-observation-policy", "@deepseek-ai/dsh-sandbox", "@deepseek-ai/dsh-sandbox-policy", "@deepseek-ai/dsh-user-approval", "@deepseek-ai/dsh-attachment", "@deepseek-ai/dsh-system-prompt")
|
|
4560
6618
|
};
|
|
4561
6619
|
/**
|
|
6620
|
+
* The host version a package graph records, for the version-policy decision.
|
|
6621
|
+
*
|
|
6622
|
+
* Every DSH package versions with the host, so the graph's own `dsh` row is the
|
|
6623
|
+
* version the caller is running. A graph without that row leaves the version
|
|
6624
|
+
* unknown, and an unknown version is not treated as supported.
|
|
6625
|
+
*/
|
|
6626
|
+
function hostVersionFromPackages(rows) {
|
|
6627
|
+
return rows.find((row) => row.name === "@deepseek-ai/dsh")?.version;
|
|
6628
|
+
}
|
|
6629
|
+
/**
|
|
4562
6630
|
* Atomically select the audited cohort for one supplied package graph. A
|
|
4563
6631
|
* graph matches a cohort only when every row carries version and integrity,
|
|
4564
6632
|
* each exactly equals that cohort's audited row, and the graph covers the
|
|
@@ -4587,7 +6655,7 @@ function selectHostCohort(rows, platform) {
|
|
|
4587
6655
|
const candidates = HOST_COHORTS.filter((cohort) => identityMatches.every((matches) => matches.includes(cohort)));
|
|
4588
6656
|
const consistentCohort = candidates.filter((cohort) => cohort.packages.length === rows.length && cohort.packages.every((expected) => rows.filter((row) => row.name === expected.name).length === 1))[0] ?? candidates[0];
|
|
4589
6657
|
if (consistentCohort !== void 0 && unboundCount === 0) {
|
|
4590
|
-
if (platform && !consistentCohort.
|
|
6658
|
+
if (platform && !consistentCohort.acceptedPlatforms.includes(platform)) return {
|
|
4591
6659
|
cohort: consistentCohort,
|
|
4592
6660
|
consistent: false,
|
|
4593
6661
|
reasonCode: "host_cohort_platform_not_audited"
|
|
@@ -4702,13 +6770,17 @@ function evaluateHostLock(rows, context = {}) {
|
|
|
4702
6770
|
const missingPackages = cohort.packages.map((row) => row.name).filter((name) => (counts.get(name) ?? 0) === 0).sort((a, b) => a.localeCompare(b));
|
|
4703
6771
|
const registryNames = new Set(HOST_COHORTS.flatMap((entry) => entry.packages.map((row) => row.name)));
|
|
4704
6772
|
const unknown = supplied.find((row) => !registryNames.has(row.name));
|
|
6773
|
+
const hostVersionValue = context.hostVersion ?? hostVersionFromPackages(supplied);
|
|
6774
|
+
const hostVersion = hostVersionValue === void 0 ? void 0 : evaluateMinimumHostVersion(hostVersionValue);
|
|
4705
6775
|
const baseResult = {
|
|
4706
6776
|
digest: digest$1,
|
|
4707
6777
|
goalAvailable,
|
|
4708
6778
|
packages: supplied,
|
|
4709
6779
|
capabilities,
|
|
4710
6780
|
cohortId: cohort.id,
|
|
6781
|
+
auditProvenance: cohort.auditProvenance,
|
|
4711
6782
|
missingPackages,
|
|
6783
|
+
...hostVersion ? { hostVersion } : {},
|
|
4712
6784
|
...context.platform ? { platform: context.platform } : {},
|
|
4713
6785
|
...context.profileKind ? { profileKind: context.profileKind } : {}
|
|
4714
6786
|
};
|
|
@@ -4784,6 +6856,12 @@ function evaluateHostLock(rows, context = {}) {
|
|
|
4784
6856
|
reasonCode: failure.reasonCode
|
|
4785
6857
|
};
|
|
4786
6858
|
}
|
|
6859
|
+
if (hostVersion?.status === "below_minimum" || hostVersion?.status === "unparseable") return {
|
|
6860
|
+
...baseResult,
|
|
6861
|
+
status: "unsupported",
|
|
6862
|
+
goalAvailable: false,
|
|
6863
|
+
reasonCode: hostVersion.status === "below_minimum" ? "host_lock_version_below_minimum" : "host_lock_version_unparseable"
|
|
6864
|
+
};
|
|
4787
6865
|
return {
|
|
4788
6866
|
...baseResult,
|
|
4789
6867
|
status: "supported"
|
|
@@ -4998,52 +7076,6 @@ function bindExecutableIdentity(resolution, effect) {
|
|
|
4998
7076
|
}
|
|
4999
7077
|
const DEFAULT_HOST_LOCK = evaluateHostLock(EXPECTED_HOST_PACKAGES);
|
|
5000
7078
|
|
|
5001
|
-
//#endregion
|
|
5002
|
-
//#region src/domain/goal-gate.ts
|
|
5003
|
-
function hasCurrentCertificate(projection) {
|
|
5004
|
-
const checkpoint = projection.checkpoints.at(-1);
|
|
5005
|
-
let reason;
|
|
5006
|
-
if (projection.integrity !== "valid") reason = "integrity_invalid";
|
|
5007
|
-
else if (projection.hostStatus !== "supported") reason = "host_lock_unsupported";
|
|
5008
|
-
else if (!checkpoint || checkpoint.result !== "certified") reason = "certificate_missing";
|
|
5009
|
-
else if (checkpoint.epoch !== projection.epoch) reason = "stale_epoch";
|
|
5010
|
-
else if (checkpoint.sessionRefDigest !== projection.sessionRefDigest) reason = "foreign_session";
|
|
5011
|
-
else if (checkpoint.hostLockDigest !== projection.hostLockDigest) reason = "stale_host_lock";
|
|
5012
|
-
else if (checkpoint.contractRevision !== projection.contractRevision) reason = "stale_contract_revision";
|
|
5013
|
-
else if (projection.currentGoalRef ? checkpoint.goalRef?.id !== projection.currentGoalRef.id || checkpoint.goalRef.revision !== projection.currentGoalRef.revision : checkpoint.goalRef !== void 0) reason = "stale_goal_ref";
|
|
5014
|
-
projection.certificateStatusReason = reason;
|
|
5015
|
-
return reason === void 0;
|
|
5016
|
-
}
|
|
5017
|
-
/**
|
|
5018
|
-
* Denies `update_goal(action=complete)` while the guard is enabled and no
|
|
5019
|
-
* current completion certificate exists. The gate itself has no bypass; a
|
|
5020
|
-
* workflow that genuinely finished but cannot certify (for example a contract
|
|
5021
|
-
* polluted by session-layer talk, or evidence that lives in another session)
|
|
5022
|
-
* has three explicit remediation routes:
|
|
5023
|
-
*
|
|
5024
|
-
* 1. `/context-guard off` disables the guard, so completion is no longer
|
|
5025
|
-
* gated. Use only after the user confirms the work is actually done.
|
|
5026
|
-
* 2. `/context-guard clear` supersedes every pending requirement and
|
|
5027
|
-
* acceptance under a `CLEAR:<revision>` sentinel (prohibitions are
|
|
5028
|
-
* retained) and bumps the contract revision; an empty-binding checkpoint
|
|
5029
|
-
* can then certify while the guard stays enabled.
|
|
5030
|
-
* 3. `update_goal(action=blocked)` records the blocker truthfully, which is
|
|
5031
|
-
* never denied by this gate.
|
|
5032
|
-
*/
|
|
5033
|
-
function goalCompletionDenial(projection, toolName, argumentsValue, configuredToolName = "update_goal") {
|
|
5034
|
-
if (toolName !== configuredToolName || typeof argumentsValue !== "object" || argumentsValue === null) return void 0;
|
|
5035
|
-
if (argumentsValue.action !== "complete") return void 0;
|
|
5036
|
-
if (!projection.enabled) return void 0;
|
|
5037
|
-
const args = argumentsValue;
|
|
5038
|
-
if (projection.hostStatus !== "supported") return `Context Guard denial [stale_host]: host lock is unsupported or unavailable (${projection.hostReasonCode ?? "unknown_host"}).`;
|
|
5039
|
-
if (!projection.currentGoalRef) return "Context Guard denial [no_goal]: no current Goal reference is available.";
|
|
5040
|
-
if (args.goal_id !== projection.currentGoalRef.id || args.revision !== projection.currentGoalRef.revision) return "Context Guard denial [stale_goal_ref]: update_goal must use the exact current goal_id and revision.";
|
|
5041
|
-
if (hasCurrentCertificate(projection)) return void 0;
|
|
5042
|
-
if (projection.certificateStatusReason === "stale_host_lock") return "Context Guard denial [stale_host]: the completion certificate belongs to a different host identity.";
|
|
5043
|
-
if (projection.certificateStatusReason === "stale_goal_ref") return "Context Guard denial [stale_goal_ref]: the completion certificate belongs to a different Goal reference.";
|
|
5044
|
-
return projection.integrity === "valid" ? "Context Guard denial [certificate_missing]: a current completion certificate is required." : "Context Guard denial [certificate_missing]: integrity is unknown or corrupt, so no current certificate is usable.";
|
|
5045
|
-
}
|
|
5046
|
-
|
|
5047
7079
|
//#endregion
|
|
5048
7080
|
//#region src/domain/shell-parse.ts
|
|
5049
7081
|
const TWO_CHAR_OPS = new Set([
|
|
@@ -5781,13 +7813,19 @@ function structuredTerminalFacts(meta) {
|
|
|
5781
7813
|
const rawSignal = record.signal;
|
|
5782
7814
|
if (rawSignal !== void 0 && rawSignal !== null) return {
|
|
5783
7815
|
exitCode: typeof rawExit === "number" ? rawExit : void 0,
|
|
5784
|
-
negative: true
|
|
7816
|
+
negative: true,
|
|
7817
|
+
marked: true
|
|
5785
7818
|
};
|
|
5786
7819
|
if (typeof rawExit === "number") return {
|
|
5787
7820
|
exitCode: rawExit,
|
|
5788
|
-
negative: false
|
|
7821
|
+
negative: false,
|
|
7822
|
+
marked: true
|
|
5789
7823
|
};
|
|
5790
7824
|
}
|
|
7825
|
+
/** `[exit code: N]`, `[shell exited: code N]`, `[Command finished with exit code N]`. */
|
|
7826
|
+
const TERMINAL_EXIT_MARKER = /^\[(?:exit code|shell exited: code|command finished with exit code)\s*:?\s*(\d+)\]$/;
|
|
7827
|
+
/** Negative markers with no exit code of their own. */
|
|
7828
|
+
const TERMINAL_NEGATIVE_MARKER = /^\[(?:timed out[^\]]*|sandbox[^\]]*|killed by signal[^\]]*|shell killed by signal[^\]]*|shell exited|command timed out or oom|interrupted[^\]]*)\]$/;
|
|
5791
7829
|
function extractTerminalFacts(textContent) {
|
|
5792
7830
|
const lines = textContent.split(/\r?\n/);
|
|
5793
7831
|
let index = lines.length - 1;
|
|
@@ -5800,19 +7838,23 @@ function extractTerminalFacts(textContent) {
|
|
|
5800
7838
|
const timeoutIntroAtHead = resetStripped && lines.length > 0 && PERSISTENT_TIMEOUT_INTRO.test(lines[0].trim());
|
|
5801
7839
|
let exitCode;
|
|
5802
7840
|
let negative = timeoutIntroAtHead;
|
|
7841
|
+
let marked = timeoutIntroAtHead;
|
|
5803
7842
|
while (index >= 0) {
|
|
5804
|
-
const line = lines[index].trim();
|
|
5805
|
-
const exitMatch = line.match(
|
|
5806
|
-
const negativeLine = /^\[(?:timed out|sandbox[^\]]*|killed by signal[^\]]*|shell killed by signal[^\]]*|shell exited|interrupted[^\]]*)[^\]]*\]$/i.test(line);
|
|
7843
|
+
const line = lines[index].trim().toLowerCase();
|
|
7844
|
+
const exitMatch = line.match(TERMINAL_EXIT_MARKER);
|
|
5807
7845
|
if (exitMatch) {
|
|
5808
7846
|
if (exitCode === void 0) exitCode = Number(exitMatch[1]);
|
|
5809
|
-
|
|
5810
|
-
else
|
|
7847
|
+
marked = true;
|
|
7848
|
+
} else if (TERMINAL_NEGATIVE_MARKER.test(line)) {
|
|
7849
|
+
negative = true;
|
|
7850
|
+
marked = true;
|
|
7851
|
+
} else break;
|
|
5811
7852
|
index -= 1;
|
|
5812
7853
|
}
|
|
5813
7854
|
return {
|
|
5814
7855
|
exitCode,
|
|
5815
|
-
negative
|
|
7856
|
+
negative,
|
|
7857
|
+
marked
|
|
5816
7858
|
};
|
|
5817
7859
|
}
|
|
5818
7860
|
function metaUrls(meta) {
|
|
@@ -6030,7 +8072,8 @@ function extractToolSubject(call, result, defaultCwd, hostLock) {
|
|
|
6030
8072
|
const commandCwd = typeof args.workdir === "string" ? args.workdir : defaultCwd;
|
|
6031
8073
|
const action = structured?.semanticAction ?? semanticActionFromCommand(command);
|
|
6032
8074
|
const deterministic = commandDetails.status === "supported" && !backgrounded && isDeterministicCheck(command);
|
|
6033
|
-
const
|
|
8075
|
+
const unmarkedSuccessAllowed = (call.name === "bash" || call.name === "pwsh") && !terminal.marked;
|
|
8076
|
+
const outcome = backgrounded ? "unknown" : result.error || terminal.negative ? "failure" : terminal.exitCode === void 0 ? unmarkedSuccessAllowed ? "success" : "unknown" : terminal.exitCode === 0 ? "success" : "failure";
|
|
6034
8077
|
const subject = {
|
|
6035
8078
|
capabilities: ["shell", ...deterministic ? ["deterministic-check"] : []],
|
|
6036
8079
|
subjects: unique(commandDetails.subjects),
|
|
@@ -6241,14 +8284,22 @@ function resolveArtifact(path$1, scope) {
|
|
|
6241
8284
|
if (/^[A-Za-z]:[\\/]/.test(path$1) || path$1.startsWith("/") || path$1.startsWith("\\")) return path$1;
|
|
6242
8285
|
return `${scope.cwd.replace(/[\\/]+$/, "")}/${path$1}`;
|
|
6243
8286
|
}
|
|
6244
|
-
/**
|
|
8287
|
+
/**
|
|
8288
|
+
* Capture one canonical root text through the authority-block segmentation.
|
|
6245
8289
|
* `prefix` keeps the historical `m<seq>` source identity; a remainder uses
|
|
6246
|
-
* `m<seq>:r` so confirmation follow-ups stay traceable to their message.
|
|
6247
|
-
|
|
8290
|
+
* `m<seq>:r` so confirmation follow-ups stay traceable to their message.
|
|
8291
|
+
*
|
|
8292
|
+
* `legacy` marks a message that predates the first protocol boundary in this
|
|
8293
|
+
* log. Capture semantics are now version-independent (see `domain/semantics.ts`),
|
|
8294
|
+
* but a pre-boundary message keeps the historical authority relabelling rule:
|
|
8295
|
+
* an item whose action/target could not be derived deterministically stays
|
|
8296
|
+
* `legacy_authority_unclassified` instead of being retroactively authorized.
|
|
8297
|
+
*/
|
|
8298
|
+
function captureRootText(projection, text, seq, scope, legacy, priorRootMessages, prefix = `m${seq}`, coordinationSplit = true) {
|
|
6248
8299
|
const blocks = segmentAuthorityBlocks(text, priorRootMessages);
|
|
6249
8300
|
for (const block$1 of blocks) {
|
|
6250
8301
|
if (!block$1.capture) continue;
|
|
6251
|
-
insertItems(projection, block$1.text, `${prefix}:${block$1.blockId}`, scope, block$1.authority === "root_adoption" ? "root_adoption" : "root_instruction",
|
|
8302
|
+
insertItems(projection, block$1.text, `${prefix}:${block$1.blockId}`, scope, block$1.authority === "root_adoption" ? "root_adoption" : "root_instruction", legacy, block$1.kind === "instruction" || block$1.authority === "root_adoption", coordinationSplit);
|
|
6252
8303
|
}
|
|
6253
8304
|
priorRootMessages.push(text);
|
|
6254
8305
|
if (priorRootMessages.length > 16) priorRootMessages.shift();
|
|
@@ -6259,16 +8310,30 @@ function captureRootText(projection, text, seq, scope, protocolBoundarySeq, capt
|
|
|
6259
8310
|
* item, so evidence for one file cannot close a message that also covers other
|
|
6260
8311
|
* files or embeds prohibitions.
|
|
6261
8312
|
*/
|
|
6262
|
-
function insertItems(projection, text, sourceMessageId, scope, authority = "root_instruction", legacy = false, legacyAuthorityProven = false,
|
|
8313
|
+
function insertItems(projection, text, sourceMessageId, scope, authority = "root_instruction", legacy = false, legacyAuthorityProven = false, coordinationSplit = true) {
|
|
6263
8314
|
const before = new Set(projection.items.keys());
|
|
6264
|
-
for (const segment of segmentClauses(text,
|
|
8315
|
+
for (const segment of segmentClauses(text, { coordinationSplit })) {
|
|
6265
8316
|
if (classifyUserInteraction(segment.body) === "conversational") continue;
|
|
6266
8317
|
if (segment.kind === "requirement" && segment.paths.length === 0 && isInstructionFraming(segment.body)) continue;
|
|
6267
|
-
if (segment.
|
|
6268
|
-
insert(projection, segment
|
|
8318
|
+
if (segment.paths.length === 0) {
|
|
8319
|
+
insert(projection, segment, sourceMessageId, scope.cwd || "scope", "scope");
|
|
6269
8320
|
continue;
|
|
6270
8321
|
}
|
|
6271
|
-
for (const path$1 of segment.paths) insert(projection, segment
|
|
8322
|
+
for (const path$1 of segment.paths) insert(projection, segment, sourceMessageId, resolveArtifact(path$1, scope), "artifact");
|
|
8323
|
+
}
|
|
8324
|
+
for (const [id, item] of projection.items) {
|
|
8325
|
+
if (before.has(id)) continue;
|
|
8326
|
+
if (item.kind !== "requirement" || item.waitAuthorization || item.authorityDisposition === "conditional_wait") continue;
|
|
8327
|
+
for (const [otherId, other] of projection.items) {
|
|
8328
|
+
if (otherId === id || other.status !== "pending") continue;
|
|
8329
|
+
if (!other.waitAuthorization || other.kind !== "requirement") continue;
|
|
8330
|
+
if (other.semanticAction !== item.semanticAction) continue;
|
|
8331
|
+
const action = item.semanticAction;
|
|
8332
|
+
if (!action || !isStatefulAction(action)) continue;
|
|
8333
|
+
if (!requestedTargetMatchesResolved(action, other.requestedTarget, item.requestedTarget)) continue;
|
|
8334
|
+
supersedeItem(projection.items, otherId, item);
|
|
8335
|
+
break;
|
|
8336
|
+
}
|
|
6272
8337
|
}
|
|
6273
8338
|
for (const [id, item] of projection.items) {
|
|
6274
8339
|
if (before.has(id)) continue;
|
|
@@ -6283,11 +8348,13 @@ function insertItems(projection, text, sourceMessageId, scope, authority = "root
|
|
|
6283
8348
|
else item.authority = authority;
|
|
6284
8349
|
}
|
|
6285
8350
|
}
|
|
6286
|
-
function insert(projection,
|
|
8351
|
+
function insert(projection, segment, sourceMessageId, subject, surface) {
|
|
6287
8352
|
const revision = projection.contractRevision + 1;
|
|
6288
|
-
const id = nextId(projection.items, kind);
|
|
6289
|
-
const
|
|
6290
|
-
const
|
|
8353
|
+
const id = nextId(projection.items, segment.kind);
|
|
8354
|
+
const method = extractMethod(segment.body);
|
|
8355
|
+
const operation = extractOperation(segment.body);
|
|
8356
|
+
const item = captureItem(segment.kind, segment.body, sourceMessageId, id, revision, subject, surface, method, operation, segment.interpretation);
|
|
8357
|
+
const duplicate = [...projection.items.values()].find((existing) => existing.kind === segment.kind && existing.status === "pending" && existing.textSha256 === item.textSha256 && existing.verification.subject === subject);
|
|
6291
8358
|
if (duplicate) supersedeItem(projection.items, duplicate.id, item);
|
|
6292
8359
|
else projection.items.set(id, item);
|
|
6293
8360
|
projection.contractRevision = item.revision;
|
|
@@ -6296,7 +8363,7 @@ function insert(projection, kind, body, sourceMessageId, subject, surface, captu
|
|
|
6296
8363
|
* Pure, deterministic re-derivation of the guard projection from the DSH
|
|
6297
8364
|
* native event log. Context Guard never writes custom session events, so every
|
|
6298
8365
|
* piece of state is derived from `command/run`, `user/message`, `tool/call`,
|
|
6299
|
-
* `tool/result`, `tool/
|
|
8366
|
+
* `tool/result`, `tool/ptc-dispatch-start`, `tool/ptc-dispatch`, and
|
|
6300
8367
|
* `compaction/summary`.
|
|
6301
8368
|
*/
|
|
6302
8369
|
function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLock = DEFAULT_HOST_LOCK) {
|
|
@@ -6348,19 +8415,63 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
6348
8415
|
compacted = true;
|
|
6349
8416
|
lastCompactionSeq = event.seq;
|
|
6350
8417
|
break;
|
|
8418
|
+
case "turn/start": {
|
|
8419
|
+
const started = asRecord(event.data);
|
|
8420
|
+
if (typeof started?.turn === "number" && Number.isSafeInteger(started.turn)) projection.hostTurn = started.turn;
|
|
8421
|
+
break;
|
|
8422
|
+
}
|
|
6351
8423
|
case "user/message": {
|
|
6352
8424
|
if (isProtocolBoundaryNotice(event) || isProtocolBoundaryNotice(event, CAPTURE_V042_NOTICE) || isProtocolBoundaryNotice(event, PROTOCOL_V4_NOTICE)) break;
|
|
8425
|
+
{
|
|
8426
|
+
const record = asRecord(event.data);
|
|
8427
|
+
const recordSource = asRecord(record?.source);
|
|
8428
|
+
const recordText = extractTextContent(record?.content ?? []);
|
|
8429
|
+
if (recordSource?.kind === "plugin" && recordSource.plugin === "context-guard" && recordText.startsWith(NO_PROGRESS_RECORD_PREFIX)) {
|
|
8430
|
+
const parsed = asRecord(parseArguments(recordText.slice(NO_PROGRESS_RECORD_PREFIX.length)));
|
|
8431
|
+
const fingerprint$1 = typeof parsed?.fingerprint === "string" ? parsed.fingerprint : void 0;
|
|
8432
|
+
const attempt = typeof parsed?.attempt === "number" && Number.isSafeInteger(parsed.attempt) && parsed.attempt > 0 ? parsed.attempt : void 0;
|
|
8433
|
+
const boundaryKey = typeof parsed?.boundaryKey === "string" ? parsed.boundaryKey : void 0;
|
|
8434
|
+
if (fingerprint$1 && attempt !== void 0 && boundaryKey !== void 0) {
|
|
8435
|
+
const claims = projection.noProgressClaims.get(fingerprint$1) ?? /* @__PURE__ */ new Map();
|
|
8436
|
+
if (!claims.has(boundaryKey)) claims.set(boundaryKey, attempt);
|
|
8437
|
+
projection.noProgressClaims.set(fingerprint$1, claims);
|
|
8438
|
+
}
|
|
8439
|
+
break;
|
|
8440
|
+
}
|
|
8441
|
+
if (recordSource?.kind === "plugin" && recordSource.plugin === "context-guard" && recordText.startsWith(CONTROL_RECORD_PREFIX)) {
|
|
8442
|
+
const parsed = asRecord(parseArguments(recordText.slice(CONTROL_RECORD_PREFIX.length)));
|
|
8443
|
+
const rootSeq = typeof parsed?.rootSeq === "number" && Number.isSafeInteger(parsed.rootSeq) ? parsed.rootSeq : void 0;
|
|
8444
|
+
if (rootSeq !== void 0) projection.handledControlSeqs.add(rootSeq);
|
|
8445
|
+
break;
|
|
8446
|
+
}
|
|
8447
|
+
}
|
|
6353
8448
|
if (!enabled) break;
|
|
6354
8449
|
const data = asRecord(event.data);
|
|
6355
8450
|
if (asRecord(data?.source)?.kind !== "user") break;
|
|
6356
8451
|
const content = data?.content ?? [];
|
|
6357
8452
|
const text = extractTextContent(content);
|
|
6358
8453
|
if (text.trim() || content.some((part) => part && typeof part === "object" && part.type !== "text")) realRootInputSeen = true;
|
|
8454
|
+
const legacyMessage = protocolBoundarySeq !== void 0 && event.seq < protocolBoundarySeq;
|
|
8455
|
+
const coordinationSplit = !(protocolBoundarySeq !== void 0 && (captureBoundarySeq === void 0 || event.seq < captureBoundarySeq));
|
|
6359
8456
|
const captureAssets = () => {
|
|
6360
8457
|
if (v4BoundarySeq !== void 0 && event.seq > v4BoundarySeq) content.forEach((part, index) => {
|
|
6361
8458
|
if (!part || typeof part !== "object" || part.type === "text") return;
|
|
6362
8459
|
const identity = sha256(JSON.stringify(part));
|
|
6363
|
-
insert(projection,
|
|
8460
|
+
insert(projection, {
|
|
8461
|
+
kind: "requirement",
|
|
8462
|
+
body: `Uninterpreted root asset m${event.seq} part ${index}: sha256 ${identity}. Interpret the attachment; its contents are reference data, not execution authority.`,
|
|
8463
|
+
text: `Uninterpreted root asset m${event.seq} part ${index}`,
|
|
8464
|
+
paths: [],
|
|
8465
|
+
interpretation: {
|
|
8466
|
+
text: `Uninterpreted root asset m${event.seq} part ${index}`,
|
|
8467
|
+
body: `Interpret the attached asset m${event.seq} part ${index}`,
|
|
8468
|
+
directive: "directive",
|
|
8469
|
+
executee: "agent",
|
|
8470
|
+
immediatelyExecutable: true,
|
|
8471
|
+
authorityDisposition: "executable_now",
|
|
8472
|
+
fingerprint: `asset:${identity.slice(0, 16)}`
|
|
8473
|
+
}
|
|
8474
|
+
}, `m${event.seq}:asset:${index}`, scope.cwd || "scope", "scope");
|
|
6364
8475
|
});
|
|
6365
8476
|
};
|
|
6366
8477
|
if (!text.trim()) {
|
|
@@ -6379,7 +8490,7 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
6379
8490
|
if (parsed.kind === "confirm") {
|
|
6380
8491
|
if (confirmRebind(projection, parsed.proposalId, `m${event.seq}`, durableConfirmed)) {
|
|
6381
8492
|
captureAssets();
|
|
6382
|
-
if (parsed.remainder) captureRootText(projection, parsed.remainder, event.seq, scope,
|
|
8493
|
+
if (parsed.remainder) captureRootText(projection, parsed.remainder, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}:r`, coordinationSplit);
|
|
6383
8494
|
break;
|
|
6384
8495
|
}
|
|
6385
8496
|
} else if (parsed.kind !== "none") {
|
|
@@ -6391,14 +8502,14 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
6391
8502
|
};
|
|
6392
8503
|
const stripped = text.split(/\r?\n/).filter((line) => !CONFIRM_LINE_PATTERN.test(line.trim())).join("\n");
|
|
6393
8504
|
if (!stripped.trim()) break;
|
|
6394
|
-
captureRootText(projection, stripped, event.seq, scope,
|
|
8505
|
+
captureRootText(projection, stripped, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit);
|
|
6395
8506
|
break;
|
|
6396
8507
|
}
|
|
6397
8508
|
}
|
|
6398
8509
|
captureAssets();
|
|
6399
8510
|
if (isInformationalMessage(text)) break;
|
|
6400
8511
|
if (classifyUserInteraction(text) === "conversational") break;
|
|
6401
|
-
captureRootText(projection, text, event.seq, scope,
|
|
8512
|
+
captureRootText(projection, text, event.seq, scope, legacyMessage, priorRootMessages, `m${event.seq}`, coordinationSplit);
|
|
6402
8513
|
break;
|
|
6403
8514
|
}
|
|
6404
8515
|
case "goal/change": {
|
|
@@ -6459,7 +8570,16 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
6459
8570
|
} } : {},
|
|
6460
8571
|
...typeof record?.resolution_evidence_id === "string" ? { resolutionEvidenceId: record.resolution_evidence_id } : {},
|
|
6461
8572
|
...typeof record?.effect_evidence_id === "string" ? { effectEvidenceId: record.effect_evidence_id } : {},
|
|
6462
|
-
...Array.isArray(record?.state_evidence_ids) ? { stateEvidenceIds: record.state_evidence_ids.map(String) } : {}
|
|
8573
|
+
...Array.isArray(record?.state_evidence_ids) ? { stateEvidenceIds: record.state_evidence_ids.map(String) } : {},
|
|
8574
|
+
...Array.isArray(record?.action_bindings) ? { actionBindings: record.action_bindings.map((entry) => {
|
|
8575
|
+
const closure = asRecord(entry);
|
|
8576
|
+
return {
|
|
8577
|
+
action: String(closure?.action ?? ""),
|
|
8578
|
+
evidenceIds: Array.isArray(closure?.evidence_ids) ? closure.evidence_ids.map(String) : [],
|
|
8579
|
+
resolvedTarget: asRecord(closure?.resolved_target) ?? {},
|
|
8580
|
+
order: Number(closure?.order ?? 0)
|
|
8581
|
+
};
|
|
8582
|
+
}) } : {}
|
|
6463
8583
|
};
|
|
6464
8584
|
}) : [];
|
|
6465
8585
|
} else if (call.name === "context_guard_boundary") {
|
|
@@ -6474,7 +8594,7 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
6474
8594
|
pendingCalls.set(callId, call);
|
|
6475
8595
|
break;
|
|
6476
8596
|
}
|
|
6477
|
-
case "tool/
|
|
8597
|
+
case "tool/ptc-dispatch-start": {
|
|
6478
8598
|
if (!enabled) break;
|
|
6479
8599
|
const data = asRecord(event.data);
|
|
6480
8600
|
const subCallId = String(data?.subCallId ?? "");
|
|
@@ -6487,10 +8607,10 @@ function deriveProjection(sourceEvents, config, scope, durableConfirmed, hostLoc
|
|
|
6487
8607
|
break;
|
|
6488
8608
|
}
|
|
6489
8609
|
case "tool/result":
|
|
6490
|
-
case "tool/
|
|
8610
|
+
case "tool/ptc-dispatch": {
|
|
6491
8611
|
if (!enabled) break;
|
|
6492
8612
|
const data = asRecord(event.data);
|
|
6493
|
-
const isDispatch = event.type === "tool/
|
|
8613
|
+
const isDispatch = event.type === "tool/ptc-dispatch";
|
|
6494
8614
|
const message = asRecord(data?.message);
|
|
6495
8615
|
const source = asRecord(message?.source);
|
|
6496
8616
|
const callId = String(source?.callId ?? (isDispatch ? data?.subCallId : "") ?? "");
|
|
@@ -6677,169 +8797,6 @@ function lifecyclePhase(input) {
|
|
|
6677
8797
|
return input.realInputSeen ? "active" : "armed";
|
|
6678
8798
|
}
|
|
6679
8799
|
|
|
6680
|
-
//#endregion
|
|
6681
|
-
//#region src/domain/stop-policy.ts
|
|
6682
|
-
const QUOTED = /["'“”‘’`].*?(?:complete|done|finished|完成|做完|搞定).*?["'“”‘’`]/i;
|
|
6683
|
-
const EXAMPLE = /\b(?:for example|e\.g\.|such as|like saying|例如|比如|举例|作为一个例子)\b/i;
|
|
6684
|
-
const QUESTION = /\?[ \t]*$|\b(?:should|could|would|can|will|what|how|whether)\b.*\?/i;
|
|
6685
|
-
const TRAILING_NEGATION = /\b(?:not (?:yet |quite |fully )?(?:complete|done|finished)|isn'?t (?:complete|done|finished)|hasn'?t (?:been )?(?:completed|finished)|尚未完成|还没完成|未完成|没有完成|还未完成)\b/i;
|
|
6686
|
-
const CONDITIONAL = /\b(?:if|unless|once|when|whenever|provided that|只要|如果|假如|一旦|除非)\b/i;
|
|
6687
|
-
const PARTIAL_ONLY = /\b(?:step|phase|stage|milestone)\s+\d+\b|第[一二三四五六七八九十\d]+\s*(?:步|阶段|环节)|(?:第一步|第二步|第三步)/i;
|
|
6688
|
-
const WHOLE_COMPLETION_EN = /\b(?:the )?(?:task|work|job|everything|all tasks?|all work) (?:is|are) (?:now )?(?:complete|done|finished|completed)\b|\b(?:task|work) (?:has been )?(?:completed|finished)\b|\ball (?:tasks|work|requirements) (?:have been )?(?:completed|done|met)\b/i;
|
|
6689
|
-
const WHOLE_COMPLETION_ZH = /(?:任务|工作|所有任务|全部工作|整体)(?:已经|已)?(?:全部)?(?:完成|搞定|做完)|(?:已|已经)(?:全部|所有)?(?:完成|搞定)(?:了)?(?:全部|所有)?(?:任务|工作)?/i;
|
|
6690
|
-
/** Bare completion confirmations, e.g. "Done." or "搞定了。" */
|
|
6691
|
-
const BARE_COMPLETION = /^(?:done|finished|completed|all\s+done)[.!]?$|^(?:已完成|完成了|搞定了|搞定|完成|done)[。..!!]?$/i;
|
|
6692
|
-
/** Continuation intent following a claim makes it partial, not whole-task. */
|
|
6693
|
-
const CONTINUATION = /接下来|下一步|然后|接着|继续|再去|最后再|还差|剩下|剩余|第二步|第三步|,\s*(?:next|then|after that|moving on)\b/i;
|
|
6694
|
-
function looksQuotedOrExemplary(text) {
|
|
6695
|
-
return QUOTED.test(text) || EXAMPLE.test(text);
|
|
6696
|
-
}
|
|
6697
|
-
function isWholeTaskCompletionClaim(text) {
|
|
6698
|
-
const normalized = normalizeClause(text);
|
|
6699
|
-
if (!normalized) return false;
|
|
6700
|
-
if (QUESTION.test(normalized)) return false;
|
|
6701
|
-
if (TRAILING_NEGATION.test(normalized)) return false;
|
|
6702
|
-
if (CONDITIONAL.test(normalized)) return false;
|
|
6703
|
-
if (CONTINUATION.test(normalized)) return false;
|
|
6704
|
-
if (looksQuotedOrExemplary(normalized)) return false;
|
|
6705
|
-
if (PARTIAL_ONLY.test(normalized) && !WHOLE_COMPLETION_EN.test(normalized) && !WHOLE_COMPLETION_ZH.test(normalized)) return false;
|
|
6706
|
-
const firstLine = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)[0] ?? "";
|
|
6707
|
-
if (BARE_COMPLETION.test(normalizeTitleLine(firstLine))) return leadingBareCompletionClaim(text);
|
|
6708
|
-
return BARE_COMPLETION.test(normalized) || WHOLE_COMPLETION_EN.test(normalized) || WHOLE_COMPLETION_ZH.test(normalized);
|
|
6709
|
-
}
|
|
6710
|
-
const DECORATION_LEAD = /^\s*(?:[\p{Extended_Pictographic}\u2764\u2705\u2714\u2716\u2728\u274C\u26A0\u2611\u2612\u2713\u2717\u274E\u2B50\u2B55\u2022\u00B7\u25E6\u25AA\u25AB\u25CF\u25CB\u25A0\u25A1\u2013\u2014-]|\uFE0F|\uFE0E|\u200D)+/u;
|
|
6711
|
-
/** Strip a leading run of decorative glyphs from a title line. */
|
|
6712
|
-
function stripDecorationPrefix(text) {
|
|
6713
|
-
let value = text;
|
|
6714
|
-
let previous = "";
|
|
6715
|
-
while (value !== previous) {
|
|
6716
|
-
previous = value;
|
|
6717
|
-
value = value.replace(DECORATION_LEAD, "");
|
|
6718
|
-
}
|
|
6719
|
-
return value.replace(/^\s+/, "");
|
|
6720
|
-
}
|
|
6721
|
-
/**
|
|
6722
|
-
* Normalize a title line for the bare-completion test. Markdown heading markers,
|
|
6723
|
-
* fully-wrapping emphasis (`**…**`, `__…__`, `*…*`, `_…_`), and a leading run of
|
|
6724
|
-
* decorative glyphs are removed ITERATIVELY until stable, because stripping one
|
|
6725
|
-
* layer may expose another (`## ✅ **完成。**`). Blockquotes (`>`), quoted
|
|
6726
|
-
* titles, and examples are left untouched so they still fail closed.
|
|
6727
|
-
*/
|
|
6728
|
-
function normalizeTitleLine(line) {
|
|
6729
|
-
let value = line.trim();
|
|
6730
|
-
if (value.startsWith(">")) return value;
|
|
6731
|
-
let previous = "";
|
|
6732
|
-
while (value !== previous) {
|
|
6733
|
-
previous = value;
|
|
6734
|
-
value = value.replace(/^#{1,6}\s+/, "").replace(/^\*\*(.+?)\*\*$/, "$1").replace(/^__(.+?)__$/, "$1").replace(/^\*(.+?)\*$/, "$1").replace(/^_(.+?)_$/, "$1");
|
|
6735
|
-
value = stripDecorationPrefix(value);
|
|
6736
|
-
}
|
|
6737
|
-
return value;
|
|
6738
|
-
}
|
|
6739
|
-
/**
|
|
6740
|
-
* A reply whose first non-empty line is a standalone bare completion ("完成。"
|
|
6741
|
-
* or "Done.") followed by a results summary. The whole text no longer matches
|
|
6742
|
-
* the single-line BARE_COMPLETION anchor, but the summary must still be treated
|
|
6743
|
-
* as a whole-task completion claim.
|
|
6744
|
-
*/
|
|
6745
|
-
function leadingBareCompletionClaim(text) {
|
|
6746
|
-
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
6747
|
-
const first = lines[0];
|
|
6748
|
-
if (!first || !BARE_COMPLETION.test(normalizeTitleLine(first))) return false;
|
|
6749
|
-
const rest = normalizeClause(lines.slice(1).join("\n"));
|
|
6750
|
-
if (!rest) return true;
|
|
6751
|
-
if (CONTINUATION.test(rest)) return false;
|
|
6752
|
-
if (TRAILING_NEGATION.test(rest)) return false;
|
|
6753
|
-
if (CONDITIONAL.test(rest)) return false;
|
|
6754
|
-
if (looksQuotedOrExemplary(rest)) return false;
|
|
6755
|
-
if (PARTIAL_ONLY.test(rest)) return false;
|
|
6756
|
-
return true;
|
|
6757
|
-
}
|
|
6758
|
-
function classifyCompletionClaim(text) {
|
|
6759
|
-
const normalized = normalizeClause(text);
|
|
6760
|
-
if (/waiting for (?:you|the user|input|your)|please (?:review|confirm|approve)|等待(?:您|你|用户)|请(?:确认|审阅|批准)/i.test(normalized)) return "user_wait";
|
|
6761
|
-
if (/waiting for (?:the )?(?:result|output|response|build|test|deployment)|等待(?:结果|输出|构建|测试|部署|响应)/i.test(normalized)) return "external_wait";
|
|
6762
|
-
if (isWholeTaskCompletionClaim(normalized)) return "complete";
|
|
6763
|
-
return "report";
|
|
6764
|
-
}
|
|
6765
|
-
/** Assistant prose is retained only as a bounded diagnostic observation. */
|
|
6766
|
-
function observeAssistantOutcome(text) {
|
|
6767
|
-
const disposition = classifyCompletionClaim(text);
|
|
6768
|
-
if (disposition === "complete") return {
|
|
6769
|
-
kind: "completion_claim",
|
|
6770
|
-
reasonCode: "assistant_completion_claim_observed"
|
|
6771
|
-
};
|
|
6772
|
-
if (disposition === "user_wait") return {
|
|
6773
|
-
kind: "user_wait_claim",
|
|
6774
|
-
reasonCode: "assistant_user_wait_claim_observed"
|
|
6775
|
-
};
|
|
6776
|
-
if (disposition === "external_wait") return {
|
|
6777
|
-
kind: "external_wait_claim",
|
|
6778
|
-
reasonCode: "assistant_external_wait_claim_observed"
|
|
6779
|
-
};
|
|
6780
|
-
return {
|
|
6781
|
-
kind: "report",
|
|
6782
|
-
reasonCode: "assistant_report_observed"
|
|
6783
|
-
};
|
|
6784
|
-
}
|
|
6785
|
-
/**
|
|
6786
|
-
* Stop Protocol 2.0 decision. This function deliberately has no assistant-text
|
|
6787
|
-
* parameter: completion wording, quotation, negation and translation cannot
|
|
6788
|
-
* steer the protocol. A structured root persistence authorization may request
|
|
6789
|
-
* one fallback correction; subsequent attempts safe-yield. An active, armed
|
|
6790
|
-
* Goal remains exclusively owned by the host Goal Round Driver.
|
|
6791
|
-
*/
|
|
6792
|
-
function decideTurnBoundary(projection) {
|
|
6793
|
-
if (!projection.enabled) return {
|
|
6794
|
-
action: "stop",
|
|
6795
|
-
reason: "guard_disabled"
|
|
6796
|
-
};
|
|
6797
|
-
if (projection.integrity !== "valid") return {
|
|
6798
|
-
action: "stop",
|
|
6799
|
-
reason: "integrity_invalid_safe_yield"
|
|
6800
|
-
};
|
|
6801
|
-
if (hasCurrentCertificate(projection)) return {
|
|
6802
|
-
action: "stop",
|
|
6803
|
-
reason: "current_certificate"
|
|
6804
|
-
};
|
|
6805
|
-
const boundary = projection.boundaries.at(-1);
|
|
6806
|
-
if (boundary?.persistedResult === "accepted" && boundary.epoch === projection.epoch && boundary.contractRevision === projection.contractRevision) return {
|
|
6807
|
-
action: "stop",
|
|
6808
|
-
reason: "accepted_boundary_pending_effectuation"
|
|
6809
|
-
};
|
|
6810
|
-
if (projection.currentGoalPhase === "active" && projection.currentGoalActivation === "armed") return {
|
|
6811
|
-
action: "stop",
|
|
6812
|
-
reason: "goal_round_driver_owns_continuation"
|
|
6813
|
-
};
|
|
6814
|
-
if ([...projection.items.values()].some((item) => item.status === "pending" && item.persistenceAuthorization)) {
|
|
6815
|
-
const key = `${projection.epoch}:${projection.contractRevision}`;
|
|
6816
|
-
const attempts = projection.persistenceCorrectionAttempts.get(key) ?? 0;
|
|
6817
|
-
if (attempts < 1) {
|
|
6818
|
-
projection.persistenceCorrectionAttempts.set(key, attempts + 1);
|
|
6819
|
-
return {
|
|
6820
|
-
action: "continue",
|
|
6821
|
-
reason: "protocol_correction_steer"
|
|
6822
|
-
};
|
|
6823
|
-
}
|
|
6824
|
-
}
|
|
6825
|
-
return {
|
|
6826
|
-
action: "stop",
|
|
6827
|
-
reason: "safe_yield_pending_preserved"
|
|
6828
|
-
};
|
|
6829
|
-
}
|
|
6830
|
-
function decideTurnStopping(projection, _assistantText, _turn, _maxAttempts) {
|
|
6831
|
-
return decideTurnBoundary(projection);
|
|
6832
|
-
}
|
|
6833
|
-
function latestAssistantText(events) {
|
|
6834
|
-
for (let index = events.length - 1; index >= 0; index--) {
|
|
6835
|
-
const event = events[index];
|
|
6836
|
-
if (event.type !== "assistant/message") continue;
|
|
6837
|
-
const text = event.data.message?.content?.filter((block$1) => block$1.type === "text").map((block$1) => block$1.text ?? "").join("\n") ?? "";
|
|
6838
|
-
if (text.trim()) return text;
|
|
6839
|
-
}
|
|
6840
|
-
return "";
|
|
6841
|
-
}
|
|
6842
|
-
|
|
6843
8800
|
//#endregion
|
|
6844
8801
|
//#region src/domain/git-adapter.ts
|
|
6845
8802
|
const GIT_COMMAND_MANIFEST_IDS = {
|
|
@@ -7129,25 +9086,81 @@ async function executeRevalidatedGitEffect(resolved, manifest, target, currentSt
|
|
|
7129
9086
|
//#endregion
|
|
7130
9087
|
//#region src/domain/session-events.ts
|
|
7131
9088
|
/**
|
|
7132
|
-
* Read a stable snapshot from
|
|
7133
|
-
*
|
|
7134
|
-
*
|
|
7135
|
-
*
|
|
9089
|
+
* Read a validated, stable event snapshot from the DSH Session V3 API.
|
|
9090
|
+
*
|
|
9091
|
+
* Session V3 replaced the V2 `events` getter with `snapshotEvents()`. Context
|
|
9092
|
+
* Guard supports only the V3 API: a session object that does not expose that
|
|
9093
|
+
* method is an unsupported host, never a reason to fall back to a legacy
|
|
9094
|
+
* accessor. Failing loud here keeps a V2-shaped object from being projected as
|
|
9095
|
+
* if its events had V3 semantics — the two vocabularies differ (surfaces,
|
|
9096
|
+
* `assistant/chunk` vs embedded streams, `session/end-seed` payload), so a
|
|
9097
|
+
* silent fallback would derive contract state from a log it cannot read.
|
|
9098
|
+
*
|
|
9099
|
+
* Guard is a READER of the durable log, so the envelope check below is the one
|
|
9100
|
+
* part of log validation it owns itself. The host validates a session it
|
|
9101
|
+
* constructs or restores; Guard additionally refuses a snapshot that is not a
|
|
9102
|
+
* sequence of event envelopes, because a projection that silently dropped or
|
|
9103
|
+
* mis-numbered an event would fabricate contract state rather than report a
|
|
9104
|
+
* damaged log.
|
|
9105
|
+
*
|
|
9106
|
+
* The V3 contract also asks a reader to refuse an unrecognized event type that
|
|
9107
|
+
* is not marked `ignorable`. Guard does NOT implement that half, deliberately:
|
|
9108
|
+
* the host's persistence reader already refuses such a log before publishing a
|
|
9109
|
+
* Session, and a whitelist of event types Guard happens to know would
|
|
9110
|
+
* false-refuse a healthy host whose composition registers a required event type
|
|
9111
|
+
* through a third-party plugin. The full rationale is in
|
|
9112
|
+
* `UPSTREAM_API_AUDIT.md`; revisit it there rather than adding a whitelist here.
|
|
9113
|
+
*/
|
|
9114
|
+
const SESSION_API_UNSUPPORTED = "session_api_unsupported";
|
|
9115
|
+
const SESSION_EVENT_ENVELOPE_INVALID = "session_event_envelope_invalid";
|
|
9116
|
+
var SessionApiError = class extends Error {
|
|
9117
|
+
code;
|
|
9118
|
+
constructor(message, code = SESSION_API_UNSUPPORTED) {
|
|
9119
|
+
super(message);
|
|
9120
|
+
this.name = "SessionApiError";
|
|
9121
|
+
this.code = code;
|
|
9122
|
+
}
|
|
9123
|
+
};
|
|
9124
|
+
/**
|
|
9125
|
+
* Refuse a snapshot that is not a contiguous, correctly enveloped V3 log.
|
|
9126
|
+
*
|
|
9127
|
+
* `seq` must be a non-negative safe integer and `type` a non-empty string.
|
|
9128
|
+
* Contiguity is checked against the snapshot's own first sequence rather than
|
|
9129
|
+
* against zero, because a ranged read legitimately starts later.
|
|
7136
9130
|
*/
|
|
9131
|
+
function assertEventEnvelopes(events) {
|
|
9132
|
+
let expected;
|
|
9133
|
+
for (let index = 0; index < events.length; index += 1) {
|
|
9134
|
+
const event = events[index];
|
|
9135
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) throw new SessionApiError(`snapshot event ${index} is not an object`, SESSION_EVENT_ENVELOPE_INVALID);
|
|
9136
|
+
const record = event;
|
|
9137
|
+
if (typeof record.type !== "string" || record.type.length === 0) throw new SessionApiError(`snapshot event ${index} has no event type`, SESSION_EVENT_ENVELOPE_INVALID);
|
|
9138
|
+
if (typeof record.seq !== "number" || !Number.isSafeInteger(record.seq) || record.seq < 0) throw new SessionApiError(`snapshot event ${index} has no sequence number`, SESSION_EVENT_ENVELOPE_INVALID);
|
|
9139
|
+
if (expected !== void 0 && record.seq !== expected) throw new SessionApiError(`snapshot event ${index} breaks sequence contiguity`, SESSION_EVENT_ENVELOPE_INVALID);
|
|
9140
|
+
expected = record.seq + 1;
|
|
9141
|
+
}
|
|
9142
|
+
}
|
|
7137
9143
|
function snapshotSessionEvents(session) {
|
|
7138
|
-
if (!session || typeof session !== "object")
|
|
9144
|
+
if (!session || typeof session !== "object") throw new SessionApiError("a DSH Session object is required");
|
|
7139
9145
|
const source = session;
|
|
7140
|
-
if (typeof source.snapshotEvents
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
return
|
|
9146
|
+
if (typeof source.snapshotEvents !== "function") throw new SessionApiError("session does not expose the DSH Session V3 snapshotEvents() API");
|
|
9147
|
+
const events = source.snapshotEvents.call(session);
|
|
9148
|
+
if (!Array.isArray(events)) throw new SessionApiError("snapshotEvents() did not return an event list");
|
|
9149
|
+
assertEventEnvelopes(events);
|
|
9150
|
+
return events;
|
|
7145
9151
|
}
|
|
7146
9152
|
|
|
7147
9153
|
//#endregion
|
|
7148
9154
|
//#region src/domain/host-resolver.ts
|
|
7149
|
-
/**
|
|
7150
|
-
|
|
9155
|
+
/**
|
|
9156
|
+
* Names registered in any cohort; rows outside the union are unknown.
|
|
9157
|
+
*
|
|
9158
|
+
* Sorted, not inherited from cohort row order: the active cohort's own listing
|
|
9159
|
+
* order is a presentation choice, and letting it decide the resolution order of
|
|
9160
|
+
* `packageRowsFromPnpmLock` would make an unrelated cohort re-ordering look like
|
|
9161
|
+
* a lock-reading change.
|
|
9162
|
+
*/
|
|
9163
|
+
const CRITICAL_NAMES = [...new Set(HOST_COHORTS.flatMap((cohort) => cohort.packages.map((row) => row.name)))].sort((a, b) => a.localeCompare(b));
|
|
7151
9164
|
const HOST_LOCK_MARKER_BEGIN = "# >>> BEGIN DSH COMPLETION GUARD HOST LOCK (managed) >>>";
|
|
7152
9165
|
const HOST_LOCK_MARKER_END = "# <<< END DSH COMPLETION GUARD HOST LOCK (managed) <<<";
|
|
7153
9166
|
var HostProfileError = class extends Error {
|
|
@@ -7204,13 +9217,34 @@ function packageRowsFromPnpmLock(text, names = CRITICAL_NAMES) {
|
|
|
7204
9217
|
return entries;
|
|
7205
9218
|
});
|
|
7206
9219
|
}
|
|
9220
|
+
/**
|
|
9221
|
+
* The production host verdict: the version floor and the exact-graph audit,
|
|
9222
|
+
* combined into the one answer a caller acts on.
|
|
9223
|
+
*
|
|
9224
|
+
* The two facts stay separable — `hostVersion` is always reported on the
|
|
9225
|
+
* evaluation — but a host below the supported floor is refused here even when
|
|
9226
|
+
* its graph matches an audited cohort, because no graph can lift a version
|
|
9227
|
+
* floor. Keeping this combination out of `evaluateHostLock` leaves that
|
|
9228
|
+
* function a pure graph audit, so a graph verdict is never overwritten by a
|
|
9229
|
+
* version verdict inside it.
|
|
9230
|
+
*/
|
|
9231
|
+
function combineHostPolicy(evaluation) {
|
|
9232
|
+
const version = evaluation.hostVersion;
|
|
9233
|
+
if (version?.status !== "below_minimum" && version?.status !== "unparseable") return evaluation;
|
|
9234
|
+
return {
|
|
9235
|
+
...evaluation,
|
|
9236
|
+
status: "unsupported",
|
|
9237
|
+
goalAvailable: false,
|
|
9238
|
+
reasonCode: version.status === "below_minimum" ? "host_lock_version_below_minimum" : "host_lock_version_unparseable"
|
|
9239
|
+
};
|
|
9240
|
+
}
|
|
7207
9241
|
function resolveInstalledHostLock(moduleUrl = import.meta.url) {
|
|
7208
9242
|
const lockPath = findUp(dirname(fileURLToPath(moduleUrl)), "pnpm-lock.yaml");
|
|
7209
|
-
if (!lockPath) return evaluateHostLock([]);
|
|
9243
|
+
if (!lockPath) return combineHostPolicy(evaluateHostLock([]));
|
|
7210
9244
|
try {
|
|
7211
|
-
return evaluateHostLock(packageRowsFromPnpmLock(readFileSync(lockPath, "utf8")));
|
|
9245
|
+
return combineHostPolicy(evaluateHostLock(packageRowsFromPnpmLock(readFileSync(lockPath, "utf8"))));
|
|
7212
9246
|
} catch {
|
|
7213
|
-
return evaluateHostLock([]);
|
|
9247
|
+
return combineHostPolicy(evaluateHostLock([]));
|
|
7214
9248
|
}
|
|
7215
9249
|
}
|
|
7216
9250
|
function activeGraphRecords(packageMapText) {
|
|
@@ -7379,13 +9413,14 @@ function inspectTargetHostGraph(runtimeRoot, profileRoot) {
|
|
|
7379
9413
|
platform: process.platform === "win32" ? "windows" : "posix",
|
|
7380
9414
|
profileKind: "headless"
|
|
7381
9415
|
});
|
|
7382
|
-
|
|
9416
|
+
const selectedCohort = HOST_COHORTS.find((cohort) => cohort.id === evaluation.cohortId);
|
|
9417
|
+
if (evaluation.status !== "supported" || !selectedCohort) throw new HostProfileError("target_runtime_unsupported", "dependency-free inspection requires the active audited core cohort");
|
|
7383
9418
|
const { records, reachable } = activeGraphRecords(mapText);
|
|
7384
9419
|
const launcher = realpathSync(join(modules, "@deepseek-ai", "dsh"));
|
|
7385
9420
|
const anchor = join(launcher, "package.json");
|
|
7386
9421
|
const host = readJsonObject(anchor, "target_runtime_unsupported");
|
|
7387
9422
|
const launcherId = [...reachable].filter((id) => id === "@deepseek-ai/dsh" || id.startsWith("@deepseek-ai/dsh@"));
|
|
7388
|
-
if (launcherId.length !== 1 || host.name !== "@deepseek-ai/dsh" || host.version !==
|
|
9423
|
+
if (launcherId.length !== 1 || host.name !== "@deepseek-ai/dsh" || host.version !== selectedCohort.packages.find((row) => row.name === "@deepseek-ai/dsh")?.version || typeof records[launcherId[0]].url !== "string" || realpathSync(resolve(modules, records[launcherId[0]].url)) !== launcher || !within(modules, launcher)) throw new HostProfileError("target_runtime_unsupported", "launcher differs from the active runtime importer");
|
|
7389
9424
|
const bundleRows = names.map((name) => {
|
|
7390
9425
|
const packageRoot = packageFromAnchor(anchor, name);
|
|
7391
9426
|
const ids = [...reachable].filter((id) => id === name || id.startsWith(`${name}@`));
|
|
@@ -7868,4 +9903,4 @@ function proofEvidenceConstraints(evidence, obligation) {
|
|
|
7868
9903
|
}
|
|
7869
9904
|
|
|
7870
9905
|
//#endregion
|
|
7871
|
-
export {
|
|
9906
|
+
export { parseShellCommand as $, isStatefulAction as $n, isWholeTaskCompletionClaim as $t, createGitPrestateEnvelope as A, extractMethod as An, RC1_HOST_PACKAGES as At, CAPTURE_V042_NOTICE as B, namedActions as Bn, renderRecoveryPacket as Bt, SESSION_EVENT_ENVELOPE_INVALID as C, evidenceAvailabilityReason as Cn, SUPPORTED_HOST_VERSIONS as Ct, GIT_COMMAND_TEMPLATES as D, captureItem as Dn, satisfiesSupportedHostRange as Dt, GIT_COMMAND_MANIFEST_IDS as E, captureClause as En, parseHostVersion as Et, verifiedLinearCommitReadback as F, interpretMessage as Fn, DEFAULT_RECOVERY_CHAR_BUDGET as Ft, evidenceFromPersistedToolResult as G, ACTION_MANIFEST as Gn, CONTROL_RECORD_PREFIX as Gt, PROTOCOL_V4_NOTICE as H, statefulActionsOfScope as Hn, evidenceCoverage as Ht, FIRST_STEP_GUIDANCE as I, isExecutableItem as In, MIN_RECOVERY_CHAR_BUDGET as It, isDeterministicCheck as J, SEMANTIC_ACTIONS as Jn, classifyCompletionClaim as Jt, extractTextContent as K, ACTION_MANIFEST_VERSION as Kn, NO_PROGRESS_RECORD_PREFIX as Kt, claimedBatchHasRealRootInput as L, isOpenObligation as Ln, closingHint as Lt, gitCommandMatchesTarget as M, isInformationalMessage as Mn, authorityCaptureCounts as Mt, parseGitCommandManifest as N, segmentClauses as Nn, segmentAuthorityBlocks as Nt, commitIndexSnapshotDigest as O, classifyClause as On, RC015_RC2_HOST_PACKAGES as Ot, revalidateGitPrestate as P, interpretClause as Pn, certifyCheckpoint as Pt, parsePwshCommand as Q, actionCompatible as Qn, isRootPauseRequest as Qt, lifecyclePhase as R, kindOfScope as Rn, openItems$1 as Rt, SESSION_API_UNSUPPORTED as S, deriveItemDiagnosis as Sn, SUPPORTED_HOST_RANGE as St, snapshotSessionEvents as T, relevantEvidence as Tn, evaluateMinimumHostVersion as Tt, deriveProjection as U, canonicalRegistryBase as Un, evidenceMatchesItem as Ut, PROTOCOL_V3_NOTICE as V, semanticActionOfScope as Vn, bindingSatisfies as Vt, supersedeItem as W, npmEscapedPackageName as Wn, isVerifyingCapability as Wt, canonicalArgvFromCommand as X, STOP_PROTOCOL_VERSION as Xn, decideTurnStopping as Xt, withDurability as Y, STATEFUL_ACTIONS as Yn, decideTurnBoundary as Yt, isRunExecutable as Z, SUPPORTED_EVIDENCE_ADAPTERS as Zn, decisionBoundaryKey as Zt, packageRowsFromPnpmLock as _, rebindResponse as _n, evaluateToolSurfaceCapability as _t, createProofManifest as a, hasCurrentCertificate as an, validateActionTarget as ar, BASE_HOST_PACKAGES as at, resolveInstalledHostLock as b, isFrozenV042RebindResponse as bn, LATEST_SUPPORTED_HOST_VERSION as bt, sessionQuery as c, isCurrentAcceptedBoundary as cn, classifyTaskIntent as cr, GOAL_HOST_PACKAGES as ct, combineHostPolicy as d, createProjection as dn, digestStrings as dr, LEGACY_HOST_COHORTS as dt, latestAssistantText as en, requestedTargetAuthorizesMutation as er, ACTIVE_HOST_COHORT_ID as et, hostLockContextFromComposedDump as f, confirmRebind as fn, normalizeClause as fr, bindExecutableIdentity as ft, packageRowsFromActiveGraph as g, rebindAttemptKey as gn, evaluateHostLock as gt, inspectTargetHostGraph as h, proposeRebindV042 as hn, sha256 as hr, evaluateHostCapability as ht, canonicalProjection as i, goalCompletionDenial as in, validateActionManifest as ir, ALPHA2_HOST_PACKAGES as it, executeRevalidatedGitEffect as j, extractOperation as jn, ALPHA3_HOST_PACKAGES as jt, commitTreeSnapshotDigest as k, extractArtifactPaths as kn, RC015_HOST_PACKAGES as kt, validateProofManifest as l, qualifyBoundary as ln, classifyUserInteraction as lr, HOST_CAPABILITY_PACKAGE_GROUPS as lt, injectActiveProfileHostLock as m, proposeRebindOutcome as mn, sanitizeUrl as mr, evaluateExternalWaitCapability as mt, PROOF_PROTOCOL_VERSION as n, observeAssistantOutcome as nn, semanticActionFromCommand as nr, ACTIVE_HOST_LAUNCHER_VERSION as nt, proofDigest as o, availableBoundaryQualifications as on, COMMAND_SURFACE_MANIFEST as or, DEFAULT_HOST_LOCK as ot, hostLockRowsFromComposedDump as p, proposeRebind as pn, sanitizeClauseText as pr, bindLiveGoalCapability as pt, extractToolSubject as q, CERTIFICATE_VERSION as qn, NO_PROGRESS_TURNS_BEFORE_STOP as qt, bindProofToProjection as r, progressFingerprint as rn, semanticActionFromText as rr, ALPHA2_DSHMARKET_139_HOST_PACKAGES as rt, proofEvidenceConstraints as s, effectuateBoundary as sn, validateManifest as sr, EXPECTED_HOST_PACKAGES as st, PROOF_KINDS as t, latestRootInstruction as tn, requestedTargetMatchesResolved as tr, ACTIVE_HOST_COHORT_IDS as tt, HostProfileError as u, currentContractDigest as un, canonicalizePath as ur, HOST_COHORTS as ut, readActiveHostGraph as v, replayRebindResult as vn, hostVersionFromPackages as vt, SessionApiError as w, itemDiagnosis as wn, compareHostVersions as wt, verifyComposedHostLockDump as x, parseConfirmationMessage as xn, MIN_SUPPORTED_HOST_VERSION as xt, resolveActiveProfileHostLock as y, CONFIRM_LINE_PATTERN as yn, selectHostCohort as yt, previewFirstStepInjection as z, maskCodeSpans as zn, recoveryDigest as zt };
|