dsh-crwu-workbench 0.0.39 → 0.0.40
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 +8 -0
- package/README.en.md +6 -0
- package/README.md +7 -0
- package/bin/darwin-arm64/crwu +0 -0
- package/bin/manifest.json +5 -5
- package/bin/win32-x64/crwu.exe +0 -0
- package/lib/client.js +52 -35
- package/lib/index.js +226 -168
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
`cordis_define` + `cordis_run` 装配,版本号用 DSH 的 `pkg-N`);它已在本仓收尾时删除
|
|
8
8
|
(见 `0.0.1` 一节),下面 `legacy · pkg-43` 及更早的记录是它的历史。
|
|
9
9
|
|
|
10
|
+
## package · 0.0.40 · 2026-10-05 · fix · 普通 DSH 会话支持完整手工审核(协议 26)
|
|
11
|
+
|
|
12
|
+
- 修复复制提示词到新会话后,脚本、知识库、附件下载、OSS 发布、钉钉归档和本人通知因缺少审核子会话身份被统一拒绝的问题。普通会话按选定工作空间下的 `<工作空间>/<流水号>` 校验案例目录,无需登记审核记录或讨论范围;氚云访问仍使用员工凭据与远端权限。
|
|
13
|
+
- 统一案例访问入口:托管审核子会话仍校验本轮案例与附件白名单,已结束、已停止、不完整及待接管状态不得降级为普通会话;已登记讨论会话可以使用全部案例工具,记录和附件清单查询仅允许登记的 ObjectId。
|
|
14
|
+
- `crwu_run_python_script` 通过调用者 Agent 解析 DSH Python,首次手工审核无需先运行一次工作台自动审核来填充缓存。
|
|
15
|
+
- 手工兜底提示词携带选定工作空间的案例路径,先用 `crwu_audit_case_bootstrap` 刷新输入快照,再完成两阶段审核和各交付通道。普通会话遵循自己的 DSH 沙箱策略,本机访问授权与托管审核隔离保持生效。
|
|
16
|
+
- 新增普通会话、托管审核和登记讨论的工具对照回归,覆盖 Windows 路径、写后校验、通知幂等与越界零进程。
|
|
17
|
+
|
|
10
18
|
## package · 0.0.39 · 2026-10-05 · feat · 审核 Skill 默认发布最终 HTML 与 JSON 到 OSS
|
|
11
19
|
|
|
12
20
|
- 新增 `crwu-audit/references/15-oss-result-publish.md`,把默认 OSS 双文件发布、精确文件名逐项写后验证、受信目标和非阻塞失败收尾纳入 Skill,而非仅依赖插件提示词。
|
package/README.en.md
CHANGED
|
@@ -112,6 +112,12 @@ Building the tarball yourself (from the `crwu-ai` repository): `make plugin-pack
|
|
|
112
112
|
> root and child sessions are always `workspace-write` with `approval=never`, and the account-connection
|
|
113
113
|
> step also offers a read-only `.dws` checkup with a second-confirmed permission repair.
|
|
114
114
|
|
|
115
|
+
### Manual audit in a new DSH session
|
|
116
|
+
|
|
117
|
+
The fallback prompt carries the selected workspace's case directory and refreshes the record/attachment input snapshot with `crwu_audit_case_bootstrap`. Ordinary sessions can run scripts, materialize knowledge, download attachments, publish to OSS, archive to DingTalk, and notify themselves without registering an audit child or discussion session. Keep the workspace, case path, employee logins, and local-access consent ready; the session's own DSH sandbox policy still applies.
|
|
118
|
+
|
|
119
|
+
Managed audit children remain bound to their current case and attachment manifest, including after stopping. Registered discussions can use case tools and query their own report. After updating, fully quit and restart DSH, then copy the prompt again.
|
|
120
|
+
|
|
115
121
|
### Updating CRWU (self-update, from 0.0.12)
|
|
116
122
|
|
|
117
123
|
**Where to look:** the version badge in the sidebar card header and in the panel header (`dev` / `v0.0.12`)
|
package/README.md
CHANGED
|
@@ -175,6 +175,13 @@ node <仓库>/install/browser-check.mjs \
|
|
|
175
175
|
|
|
176
176
|
`DSH_PERMISSION_MODE=danger-full-access` 只在**开发自测**(§「真实验证」里那个临时 profile)才需要。
|
|
177
177
|
|
|
178
|
+
### 在 DSH 新会话中手工审核
|
|
179
|
+
|
|
180
|
+
手工兜底提示词携带选定工作空间的案例目录,并先用 `crwu_audit_case_bootstrap` 刷新记录和附件输入快照。普通会话无需登记审核子会话或讨论范围,即可运行脚本、下载知识与附件、发布 OSS、钉钉归档和通知本人。请先准备工作空间、案例路径、员工登录和本机访问授权;执行仍遵循该会话自己的 DSH 沙箱策略。
|
|
181
|
+
|
|
182
|
+
托管审核子会话继续限定本轮案例和附件清单,停止后也不得转为普通权限。已登记讨论会话可使用案例工具并查询自己的报告。更新插件后完整退出并重启 DSH,再重新复制提示词。
|
|
183
|
+
|
|
184
|
+
|
|
178
185
|
### 不需要任何 PATH 配置(2026-09-25 起,且**不要再加回来**)
|
|
179
186
|
|
|
180
187
|
`crwu` / `dws` / `ossutil` 随插件发布在包内 `bin/<平台>/`,插件用**包内绝对路径**经 `ctx.shell`
|
package/bin/darwin-arm64/crwu
CHANGED
|
Binary file
|
package/bin/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "crwu.plugin-bin-manifest.v1",
|
|
3
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"generatedAt": "2026-10-05T07:55:04.905Z",
|
|
4
4
|
"platforms": [
|
|
5
5
|
{
|
|
6
6
|
"platform": "darwin-arm64",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"source": "repo-build",
|
|
13
13
|
"sourceVersion": "0.0.1",
|
|
14
14
|
"target": "darwin/arm64",
|
|
15
|
-
"buildCommit": "
|
|
15
|
+
"buildCommit": "4a2cd69f23a20dd3424d58b1f8a35d13d2f3419e",
|
|
16
16
|
"size": 7351330,
|
|
17
|
-
"sha256": "
|
|
17
|
+
"sha256": "baecdedd7294c707c4be85679e3257f464c304d7d06e3214b710715d17f06d59"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"tool": "ossutil",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"source": "repo-build",
|
|
51
51
|
"sourceVersion": "0.0.1",
|
|
52
52
|
"target": "windows/amd64",
|
|
53
|
-
"buildCommit": "
|
|
53
|
+
"buildCommit": "4a2cd69f23a20dd3424d58b1f8a35d13d2f3419e",
|
|
54
54
|
"size": 8128000,
|
|
55
|
-
"sha256": "
|
|
55
|
+
"sha256": "d62601e00513b83a72dbccdec1b6ec18d8849b26d8e9e1325acac285e7bec6a8"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"tool": "ossutil",
|
package/bin/win32-x64/crwu.exe
CHANGED
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -7500,19 +7500,66 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
7500
7500
|
};
|
|
7501
7501
|
}
|
|
7502
7502
|
//#endregion
|
|
7503
|
+
//#region src/shared/utils/case-dir.ts
|
|
7504
|
+
/**
|
|
7505
|
+
* 一条报告的**案例目录**:`<工作空间>/<流水号>`(Windows 上是 `<工作空间>\<流水号>`)。
|
|
7506
|
+
*
|
|
7507
|
+
* 这是 Host 与 Client 都依赖的唯一约定,所以只在这里定义一次:
|
|
7508
|
+
* - Host 的审核提示词拿它告诉审核子代理「本案例目录必须是 …」(`host/audit/prompt.ts`);
|
|
7509
|
+
* - Client 的讨论 / 分析提示词拿它告诉模型「下载与读取只允许在这个目录里」。
|
|
7510
|
+
*
|
|
7511
|
+
* **为什么必须共用一份**:2026-09-25 实测到漂移的代价 —— 审核链路一直用
|
|
7512
|
+
* `<工作空间>/<流水号>`,而讨论会话的模型没人告诉它目录,于是它自己 `ls` + `find` 去猜,
|
|
7513
|
+
* 猜成了 `<工作空间>/cases/<流水号>`:既扫了用户的磁盘,又把材料下到了错的地方。
|
|
7514
|
+
* 两处各写一份拼接逻辑就是这种漂移的温床,所以宁可多一个共享模块。
|
|
7515
|
+
*
|
|
7516
|
+
* **拼接交给 `joinLocalPath`**(2026-09-28 Windows 适配):分隔符随工作空间的风格走 ——
|
|
7517
|
+
* 在 Windows 上硬拼 `/` 会得到 `C:\Work/S1` 这种混用形式,而这条路径既进提示词
|
|
7518
|
+
* (模型照着写 PowerShell 命令)又进 `ctx.shell` 的 `workdir`。
|
|
7519
|
+
*
|
|
7520
|
+
* 任一侧为空、或流水号不是安全的一段(绝对路径 / 含 `..`)时返回空串,
|
|
7521
|
+
* 调用方据此**不写这一段**(宁可不给,也不给一个半截或越界的路径)。
|
|
7522
|
+
*/
|
|
7523
|
+
function caseDirOf(workspacePath, seqNo) {
|
|
7524
|
+
const workspace = text(workspacePath);
|
|
7525
|
+
const seq = text(seqNo).trim();
|
|
7526
|
+
if (workspace === "" || seq === "") return "";
|
|
7527
|
+
try {
|
|
7528
|
+
return joinLocalPath(workspace, seq);
|
|
7529
|
+
} catch (error) {
|
|
7530
|
+
return "";
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
//#endregion
|
|
7503
7534
|
//#region src/client/features/workbench/Handoff.tsx
|
|
7504
7535
|
/** 手工提示词全文。字段缺失时给出可操作的提示,而不是留空。 */
|
|
7505
|
-
function handoffPrompt(task) {
|
|
7536
|
+
function handoffPrompt(task, workspacePath = "") {
|
|
7537
|
+
const caseDir = caseDirOf(workspacePath, task.seqNo);
|
|
7506
7538
|
return [
|
|
7507
7539
|
zhCN.handoffIntro,
|
|
7508
7540
|
"",
|
|
7509
7541
|
`- ${zhCN.handoffObjectId}${task.id === "" ? zhCN.handoffObjectIdMissing : task.id}`,
|
|
7510
7542
|
`- ${zhCN.handoffSeqNo}${task.seqNo === "" ? task.name : task.seqNo}`,
|
|
7511
|
-
`- ${zhCN.handoffProject}${task.project === "" ? zhCN.handoffProjectMissing : task.project}
|
|
7543
|
+
`- ${zhCN.handoffProject}${task.project === "" ? zhCN.handoffProjectMissing : task.project}`,
|
|
7544
|
+
"",
|
|
7545
|
+
"这是普通会话中的手工审核,无需登记为审核子会话。先准备本轮输入快照,再执行技能的完整两阶段审核。",
|
|
7546
|
+
...caseDir === "" ? ["请先在工作台选定工作空间并确认报告流水号,然后重新复制提示词;不要猜案例目录。"] : [
|
|
7547
|
+
`本次唯一案例目录:${caseDir}`,
|
|
7548
|
+
"若案例目录不存在,只在上述工作空间下创建这个目录;所有材料、脚本与交付件均放在其中,不复用旧审核产物。",
|
|
7549
|
+
...task.id === "" ? [] : [`先调用 crwu_audit_case_bootstrap(${JSON.stringify({
|
|
7550
|
+
objectId: task.id,
|
|
7551
|
+
seqNo: task.seqNo,
|
|
7552
|
+
caseDir,
|
|
7553
|
+
refresh: true
|
|
7554
|
+
})})。`, "成功后只读取它返回的 snapshotPath、attachmentsPath、metadataPath;失败则停止并报告结构化错误。"],
|
|
7555
|
+
"附件按清单通过 crwu_h3yun_file_get 逐件下载,知识清单用 crwu_audit_knowledge_materialize 实时下载。",
|
|
7556
|
+
"技能脚本用 crwu_run_python_script;运行时仅由 load_workspace_dependencies 解析,不查找或改用系统 Python。",
|
|
7557
|
+
"最终 HTML/JSON 校验通过后,分别调用 crwu_audit_oss_publish、crwu_audit_dingtalk_archive、crwu_audit_dingtalk_notify_self,独立报告各通道交付结果。"
|
|
7558
|
+
]
|
|
7512
7559
|
].join("\n");
|
|
7513
7560
|
}
|
|
7514
7561
|
function Handoff(props) {
|
|
7515
|
-
const prompt = handoffPrompt(props.task);
|
|
7562
|
+
const prompt = handoffPrompt(props.task, props.workspacePath);
|
|
7516
7563
|
const copy = () => {
|
|
7517
7564
|
const fallback = () => {
|
|
7518
7565
|
workbenchApi.clipboard({ text: prompt });
|
|
@@ -8661,37 +8708,6 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
8661
8708
|
};
|
|
8662
8709
|
}
|
|
8663
8710
|
//#endregion
|
|
8664
|
-
//#region src/shared/utils/case-dir.ts
|
|
8665
|
-
/**
|
|
8666
|
-
* 一条报告的**案例目录**:`<工作空间>/<流水号>`(Windows 上是 `<工作空间>\<流水号>`)。
|
|
8667
|
-
*
|
|
8668
|
-
* 这是 Host 与 Client 都依赖的唯一约定,所以只在这里定义一次:
|
|
8669
|
-
* - Host 的审核提示词拿它告诉审核子代理「本案例目录必须是 …」(`host/audit/prompt.ts`);
|
|
8670
|
-
* - Client 的讨论 / 分析提示词拿它告诉模型「下载与读取只允许在这个目录里」。
|
|
8671
|
-
*
|
|
8672
|
-
* **为什么必须共用一份**:2026-09-25 实测到漂移的代价 —— 审核链路一直用
|
|
8673
|
-
* `<工作空间>/<流水号>`,而讨论会话的模型没人告诉它目录,于是它自己 `ls` + `find` 去猜,
|
|
8674
|
-
* 猜成了 `<工作空间>/cases/<流水号>`:既扫了用户的磁盘,又把材料下到了错的地方。
|
|
8675
|
-
* 两处各写一份拼接逻辑就是这种漂移的温床,所以宁可多一个共享模块。
|
|
8676
|
-
*
|
|
8677
|
-
* **拼接交给 `joinLocalPath`**(2026-09-28 Windows 适配):分隔符随工作空间的风格走 ——
|
|
8678
|
-
* 在 Windows 上硬拼 `/` 会得到 `C:\Work/S1` 这种混用形式,而这条路径既进提示词
|
|
8679
|
-
* (模型照着写 PowerShell 命令)又进 `ctx.shell` 的 `workdir`。
|
|
8680
|
-
*
|
|
8681
|
-
* 任一侧为空、或流水号不是安全的一段(绝对路径 / 含 `..`)时返回空串,
|
|
8682
|
-
* 调用方据此**不写这一段**(宁可不给,也不给一个半截或越界的路径)。
|
|
8683
|
-
*/
|
|
8684
|
-
function caseDirOf(workspacePath, seqNo) {
|
|
8685
|
-
const workspace = text(workspacePath);
|
|
8686
|
-
const seq = text(seqNo).trim();
|
|
8687
|
-
if (workspace === "" || seq === "") return "";
|
|
8688
|
-
try {
|
|
8689
|
-
return joinLocalPath(workspace, seq);
|
|
8690
|
-
} catch (error) {
|
|
8691
|
-
return "";
|
|
8692
|
-
}
|
|
8693
|
-
}
|
|
8694
|
-
//#endregion
|
|
8695
8711
|
//#region src/client/features/report-audit/ReportPane.tsx
|
|
8696
8712
|
/**
|
|
8697
8713
|
* 复用的搜索组件(报告列表与 AI 审核列表是**同一个**)。
|
|
@@ -9791,6 +9807,7 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
9791
9807
|
}),
|
|
9792
9808
|
state.handoff === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Handoff, {
|
|
9793
9809
|
task: state.handoff,
|
|
9810
|
+
workspacePath: props.workspace?.path,
|
|
9794
9811
|
copied: props.handoffCopied,
|
|
9795
9812
|
onCopied: props.onHandoffCopied
|
|
9796
9813
|
}),
|
|
@@ -10742,7 +10759,7 @@ body[data-ds-dark-theme] .crwu-audit-root {
|
|
|
10742
10759
|
}
|
|
10743
10760
|
/** 宿主与客户端是不是同一代(不同代时**必须**拦住发起审核,见 AGENTS.md §7.12)。 */
|
|
10744
10761
|
function hostIsStale(snapshot) {
|
|
10745
|
-
return snapshot.protocol !== null && snapshot.protocol !==
|
|
10762
|
+
return snapshot.protocol !== null && snapshot.protocol !== 26;
|
|
10746
10763
|
}
|
|
10747
10764
|
/**
|
|
10748
10765
|
* 宿主执行的权限说明版本与客户端是否一致(协议 18)。
|
package/lib/index.js
CHANGED
|
@@ -231,7 +231,7 @@ const PLUGIN_INJECT = [
|
|
|
231
231
|
* 还会让打包器的 JSON 插件成为隐式依赖。代价是升版本时要同时改这里 ——
|
|
232
232
|
* `tests/unit/host-package.test.mjs` 有一条断言盯着它必须等于 `package.json` 的 version。
|
|
233
233
|
*/
|
|
234
|
-
const PLUGIN_VERSION = "0.0.
|
|
234
|
+
const PLUGIN_VERSION = "0.0.40";
|
|
235
235
|
/**
|
|
236
236
|
* `ping` / `boot` 应答里的版本指纹,形如 `pkg-0.0.5`。
|
|
237
237
|
*
|
|
@@ -3171,10 +3171,11 @@ function samePathText(a, b) {
|
|
|
3171
3171
|
*/
|
|
3172
3172
|
function isAuditChild(state, childId, parentSessionId = "") {
|
|
3173
3173
|
if (childId === "") return false;
|
|
3174
|
+
if (parentSessionId !== "" && parentSessionId === state.auditRoot?.sessionId) return true;
|
|
3174
3175
|
for (const record of Object.values(state.audits ?? {})) {
|
|
3175
3176
|
if (record === void 0) continue;
|
|
3176
|
-
if (record.childId === childId) return true;
|
|
3177
|
-
if (
|
|
3177
|
+
if (record.childId === childId || record.replacedChildId === childId) return true;
|
|
3178
|
+
if (parentSessionId !== "" && record.parentSessionId === parentSessionId) return true;
|
|
3178
3179
|
}
|
|
3179
3180
|
return false;
|
|
3180
3181
|
}
|
|
@@ -3189,7 +3190,7 @@ function isAuditChild(state, childId, parentSessionId = "") {
|
|
|
3189
3190
|
* 退路问的是会话存储本身:**审核根的孩子里有没有这个调用者**。
|
|
3190
3191
|
* 两条都拿不到才算"问不到",由门禁 fail closed(不猜)。
|
|
3191
3192
|
*
|
|
3192
|
-
*
|
|
3193
|
+
* Query known audit roots so replaced children retain their managed identity.
|
|
3193
3194
|
*/
|
|
3194
3195
|
async function callerParentSessionId(ctx, state, exec) {
|
|
3195
3196
|
const { childId, parentSessionId } = callerIdentity(exec);
|
|
@@ -3197,13 +3198,10 @@ async function callerParentSessionId(ctx, state, exec) {
|
|
|
3197
3198
|
if (parentSessionId !== "") return parentSessionId;
|
|
3198
3199
|
const subagents = ctx.get("subagents");
|
|
3199
3200
|
if (subagents === void 0 || typeof subagents.listChildren !== "function") return "";
|
|
3200
|
-
const candidates = Object.values(state.audits ?? {}).
|
|
3201
|
-
|
|
3202
|
-
const parentId = candidates[0]?.parentSessionId ?? "";
|
|
3203
|
-
if (parentId === "") return "";
|
|
3204
|
-
try {
|
|
3201
|
+
const candidates = new Set([text(state.auditRoot?.sessionId), ...Object.values(state.audits ?? {}).map((record) => text(record?.parentSessionId))].filter((id) => id !== ""));
|
|
3202
|
+
for (const parentId of candidates) try {
|
|
3205
3203
|
const children = await subagents.listChildren(parentId);
|
|
3206
|
-
if (!Array.isArray(children))
|
|
3204
|
+
if (!Array.isArray(children)) continue;
|
|
3207
3205
|
for (const child of children) {
|
|
3208
3206
|
if (child === null || typeof child !== "object") continue;
|
|
3209
3207
|
const record = child;
|
|
@@ -5366,6 +5364,182 @@ function nodeSize(item) {
|
|
|
5366
5364
|
}
|
|
5367
5365
|
return null;
|
|
5368
5366
|
}
|
|
5367
|
+
function createDiscussionScopeRegistry(options = {}) {
|
|
5368
|
+
const now = options.now ?? (() => Date.now());
|
|
5369
|
+
const ttlMs = options.ttlMs ?? 432e5;
|
|
5370
|
+
const limit = options.limit ?? 32;
|
|
5371
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
5372
|
+
/** 过期的一律先清掉:判据只有 `expiresAt` 与当前时刻。 */
|
|
5373
|
+
const sweep = () => {
|
|
5374
|
+
const at = now();
|
|
5375
|
+
for (const [id, scope] of scopes) if (scope.expiresAt <= at) scopes.delete(id);
|
|
5376
|
+
};
|
|
5377
|
+
/** 超出上限时丢**最久没用过**的(不是最早建的:人可能一直在用一条老会话)。 */
|
|
5378
|
+
const trim = () => {
|
|
5379
|
+
while (scopes.size > limit) {
|
|
5380
|
+
let oldestId = "";
|
|
5381
|
+
let oldestAt = Number.POSITIVE_INFINITY;
|
|
5382
|
+
for (const [id, scope] of scopes) if (scope.lastUsedAt < oldestAt) {
|
|
5383
|
+
oldestAt = scope.lastUsedAt;
|
|
5384
|
+
oldestId = id;
|
|
5385
|
+
}
|
|
5386
|
+
if (oldestId === "") return;
|
|
5387
|
+
scopes.delete(oldestId);
|
|
5388
|
+
}
|
|
5389
|
+
};
|
|
5390
|
+
return {
|
|
5391
|
+
register(input) {
|
|
5392
|
+
const at = now();
|
|
5393
|
+
const scope = {
|
|
5394
|
+
sessionId: text(input.sessionId),
|
|
5395
|
+
seqNo: text(input.seqNo),
|
|
5396
|
+
objectId: text(input.objectId),
|
|
5397
|
+
caseDir: text(input.caseDir),
|
|
5398
|
+
allowedAttachmentIds: [...input.allowedAttachmentIds].map((id) => text(id)).filter((id) => id !== ""),
|
|
5399
|
+
createdAt: at,
|
|
5400
|
+
lastUsedAt: at,
|
|
5401
|
+
expiresAt: at + ttlMs
|
|
5402
|
+
};
|
|
5403
|
+
scopes.set(scope.sessionId, scope);
|
|
5404
|
+
trim();
|
|
5405
|
+
return scope;
|
|
5406
|
+
},
|
|
5407
|
+
use(sessionId) {
|
|
5408
|
+
sweep();
|
|
5409
|
+
const id = text(sessionId);
|
|
5410
|
+
if (id === "") return void 0;
|
|
5411
|
+
const scope = scopes.get(id);
|
|
5412
|
+
if (scope === void 0) return void 0;
|
|
5413
|
+
const touched = {
|
|
5414
|
+
...scope,
|
|
5415
|
+
lastUsedAt: now()
|
|
5416
|
+
};
|
|
5417
|
+
scopes.set(id, touched);
|
|
5418
|
+
return touched;
|
|
5419
|
+
},
|
|
5420
|
+
peek(sessionId) {
|
|
5421
|
+
sweep();
|
|
5422
|
+
return scopes.get(text(sessionId));
|
|
5423
|
+
},
|
|
5424
|
+
drop(sessionId) {
|
|
5425
|
+
scopes.delete(text(sessionId));
|
|
5426
|
+
},
|
|
5427
|
+
list() {
|
|
5428
|
+
sweep();
|
|
5429
|
+
return [...scopes.values()];
|
|
5430
|
+
},
|
|
5431
|
+
clear() {
|
|
5432
|
+
scopes.clear();
|
|
5433
|
+
},
|
|
5434
|
+
size() {
|
|
5435
|
+
sweep();
|
|
5436
|
+
return scopes.size;
|
|
5437
|
+
}
|
|
5438
|
+
};
|
|
5439
|
+
}
|
|
5440
|
+
/**
|
|
5441
|
+
* 案例内 Tool 的**统一门禁**:认证身份 → 解析范围 → 校验模型提交的标识。
|
|
5442
|
+
*
|
|
5443
|
+
* 与 `requireAuditScope` 的关系:后者是"审核子会话"这一支的实现,这里在它之前再加一支
|
|
5444
|
+
* "已登记的讨论会话"。**讨论会话绝不复用审核 scope**(见文件头第 2 条)。
|
|
5445
|
+
*/
|
|
5446
|
+
async function requireMaterialScope(ctx, state, scopes, exec, args, options = {}) {
|
|
5447
|
+
const { childId } = callerIdentity(exec);
|
|
5448
|
+
if (childId === "") return { ...failure$1("policy", "无法确认调用者身份:案例内的操作只允许由本次审核的子会话或已登记的报告讨论会话发起") };
|
|
5449
|
+
if (auditScopeFor(state, childId) !== void 0) {
|
|
5450
|
+
const checked = await requireAuditScope(ctx, state, exec, args, options);
|
|
5451
|
+
if (!checked.ok) return checked;
|
|
5452
|
+
return {
|
|
5453
|
+
ok: true,
|
|
5454
|
+
material: {
|
|
5455
|
+
kind: "audit",
|
|
5456
|
+
scope: checked.scope
|
|
5457
|
+
}
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
const scope = scopes.use(childId);
|
|
5461
|
+
if (scope === void 0) return { ...failure$1("policy", "调用者既不是进行中的审核子会话,也不是已登记的报告讨论会话:案例内的操作一律拒绝(报告讨论请在工作台点「与 DeepSeek 讨论报告」重新登记材料范围)") };
|
|
5462
|
+
const seqNo = text(args.seqNo).trim();
|
|
5463
|
+
if (seqNo !== "" && seqNo !== scope.seqNo) return { ...failure$1("input", "流水号与本次登记的材料范围不一致:拒绝操作") };
|
|
5464
|
+
const objectId = text(args.objectId).trim();
|
|
5465
|
+
if (objectId !== "" && objectId !== scope.objectId) return { ...failure$1("input", "记录标识与本次登记的材料范围不一致:拒绝操作") };
|
|
5466
|
+
const caseDir = text(args.caseDir).trim();
|
|
5467
|
+
if (caseDir === "") {
|
|
5468
|
+
if (options.requireCaseDir === false) return {
|
|
5469
|
+
ok: true,
|
|
5470
|
+
material: {
|
|
5471
|
+
kind: "discussion",
|
|
5472
|
+
scope
|
|
5473
|
+
}
|
|
5474
|
+
};
|
|
5475
|
+
return { ...failure$1("input", "缺少案例目录:案例内的操作必须带上本轮案例目录") };
|
|
5476
|
+
}
|
|
5477
|
+
if (fileSystem(ctx) === void 0) return { ...failure$1("infrastructure", "Host 文件服务不可用") };
|
|
5478
|
+
try {
|
|
5479
|
+
const given = await resolveTarget(ctx, caseDir);
|
|
5480
|
+
const expected = await resolveTarget(ctx, scope.caseDir);
|
|
5481
|
+
if (text(given.targetKey) === "" || given.targetKey !== expected.targetKey) return { ...failure$1("policy", "案例目录必须是本次登记的那一个(不许用工作空间根、兄弟案例或子目录)") };
|
|
5482
|
+
} catch (error) {
|
|
5483
|
+
return { ...failure$1("infrastructure", `案例目录解析失败:${error instanceof Error ? error.message : String(error)}`) };
|
|
5484
|
+
}
|
|
5485
|
+
return {
|
|
5486
|
+
ok: true,
|
|
5487
|
+
material: {
|
|
5488
|
+
kind: "discussion",
|
|
5489
|
+
scope
|
|
5490
|
+
}
|
|
5491
|
+
};
|
|
5492
|
+
}
|
|
5493
|
+
//#endregion
|
|
5494
|
+
//#region src/host/audit/case-access.ts
|
|
5495
|
+
async function requireCaseAccess(ctx, state, discussions, exec, args) {
|
|
5496
|
+
const { childId } = callerIdentity(exec);
|
|
5497
|
+
if (childId === "") return failure$1("policy", "无法确认调用者身份:案例操作需要会话上下文");
|
|
5498
|
+
const parentId = await callerParentSessionId(ctx, state, exec);
|
|
5499
|
+
if (isAuditChild(state, childId, parentId)) {
|
|
5500
|
+
const checked = await requireAuditScope(ctx, state, exec, args);
|
|
5501
|
+
if (!checked.ok) return checked;
|
|
5502
|
+
return {
|
|
5503
|
+
ok: true,
|
|
5504
|
+
kind: "audit",
|
|
5505
|
+
casePath: checked.casePath,
|
|
5506
|
+
allowedAttachmentIds: checked.scope.allowedAttachmentIds
|
|
5507
|
+
};
|
|
5508
|
+
}
|
|
5509
|
+
const header = exec.agent?.session?.header;
|
|
5510
|
+
if (Object.values(state.audits ?? {}).some((record) => record?.pending === true && record.childId === "") && parentId === "" && header === void 0) return failure$1("policy", "审核子会话正在建立,尚无法确认调用者的会话范围:请稍后重试");
|
|
5511
|
+
if (discussions.peek(childId) !== void 0) {
|
|
5512
|
+
const checked = await requireMaterialScope(ctx, state, discussions, exec, args);
|
|
5513
|
+
if (!checked.ok) return checked;
|
|
5514
|
+
const scope = checked.material.scope;
|
|
5515
|
+
return {
|
|
5516
|
+
ok: true,
|
|
5517
|
+
kind: checked.material.kind,
|
|
5518
|
+
casePath: checked.material.kind === "audit" ? checked.material.scope.casePath : checked.material.scope.caseDir,
|
|
5519
|
+
allowedAttachmentIds: scope.allowedAttachmentIds
|
|
5520
|
+
};
|
|
5521
|
+
}
|
|
5522
|
+
const root = allowedCaseRootOf(state);
|
|
5523
|
+
const checked = await requireCaseDir(ctx, args.caseDir, { allowedRoot: root });
|
|
5524
|
+
if (!checked.ok) return checked;
|
|
5525
|
+
const seqNo = text(args.seqNo).trim() || basenameLocalPath(checked.path);
|
|
5526
|
+
if (seqNo === "." || seqNo === ".." || /[\\/]/.test(seqNo)) return failure$1("input", "流水号必须是单段案例目录名,不能包含路径分隔符");
|
|
5527
|
+
const expectedPath = caseDirOf(root, seqNo);
|
|
5528
|
+
if (expectedPath === "") return failure$1("input", "流水号必须是有效的案例目录名");
|
|
5529
|
+
try {
|
|
5530
|
+
const given = await resolveTarget(ctx, checked.path);
|
|
5531
|
+
const expected = await resolveTarget(ctx, expectedPath);
|
|
5532
|
+
const workspace = await resolveTarget(ctx, root);
|
|
5533
|
+
if (text(given.targetKey) === "" || given.targetKey === workspace.targetKey || given.targetKey !== expected.targetKey) return failure$1("policy", "案例目录必须是当前工作空间下的 `<工作空间>/<流水号>`,不能使用工作空间根或嵌套目录");
|
|
5534
|
+
} catch (error) {
|
|
5535
|
+
return failure$1("infrastructure", `案例目录解析失败:${error instanceof Error ? error.message : String(error)}`);
|
|
5536
|
+
}
|
|
5537
|
+
return {
|
|
5538
|
+
ok: true,
|
|
5539
|
+
kind: "session",
|
|
5540
|
+
casePath: checked.path
|
|
5541
|
+
};
|
|
5542
|
+
}
|
|
5369
5543
|
//#endregion
|
|
5370
5544
|
//#region src/host/dws/run.ts
|
|
5371
5545
|
/** 允许出现在产品域之前的**全局**取值 flag;值必须紧随其后。 */
|
|
@@ -5767,7 +5941,7 @@ function dingtalkTools(deps) {
|
|
|
5767
5941
|
steps
|
|
5768
5942
|
};
|
|
5769
5943
|
const ctx = toolContext(deps.ctx, exec);
|
|
5770
|
-
const caseCheck = await
|
|
5944
|
+
const caseCheck = await requireCaseAccess(ctx, deps.state, deps.discussionScopes, exec, {
|
|
5771
5945
|
caseDir: args.caseDir,
|
|
5772
5946
|
seqNo: args.seqNo
|
|
5773
5947
|
});
|
|
@@ -6153,7 +6327,7 @@ function dingtalkTools(deps) {
|
|
|
6153
6327
|
openDingId: "",
|
|
6154
6328
|
steps
|
|
6155
6329
|
};
|
|
6156
|
-
const caseCheck = await
|
|
6330
|
+
const caseCheck = await requireCaseAccess(ctx, deps.state, deps.discussionScopes, exec, {
|
|
6157
6331
|
caseDir: args.caseDir,
|
|
6158
6332
|
seqNo: args.seqNo
|
|
6159
6333
|
});
|
|
@@ -6481,136 +6655,6 @@ function locateMessage(payload, fileName) {
|
|
|
6481
6655
|
async function persistNotifyState(ctx, path, state, options) {
|
|
6482
6656
|
return (await writeCaseText(ctx, path, `${JSON.stringify(state, null, 2)}\n`, options)).ok;
|
|
6483
6657
|
}
|
|
6484
|
-
function createDiscussionScopeRegistry(options = {}) {
|
|
6485
|
-
const now = options.now ?? (() => Date.now());
|
|
6486
|
-
const ttlMs = options.ttlMs ?? 432e5;
|
|
6487
|
-
const limit = options.limit ?? 32;
|
|
6488
|
-
const scopes = /* @__PURE__ */ new Map();
|
|
6489
|
-
/** 过期的一律先清掉:判据只有 `expiresAt` 与当前时刻。 */
|
|
6490
|
-
const sweep = () => {
|
|
6491
|
-
const at = now();
|
|
6492
|
-
for (const [id, scope] of scopes) if (scope.expiresAt <= at) scopes.delete(id);
|
|
6493
|
-
};
|
|
6494
|
-
/** 超出上限时丢**最久没用过**的(不是最早建的:人可能一直在用一条老会话)。 */
|
|
6495
|
-
const trim = () => {
|
|
6496
|
-
while (scopes.size > limit) {
|
|
6497
|
-
let oldestId = "";
|
|
6498
|
-
let oldestAt = Number.POSITIVE_INFINITY;
|
|
6499
|
-
for (const [id, scope] of scopes) if (scope.lastUsedAt < oldestAt) {
|
|
6500
|
-
oldestAt = scope.lastUsedAt;
|
|
6501
|
-
oldestId = id;
|
|
6502
|
-
}
|
|
6503
|
-
if (oldestId === "") return;
|
|
6504
|
-
scopes.delete(oldestId);
|
|
6505
|
-
}
|
|
6506
|
-
};
|
|
6507
|
-
return {
|
|
6508
|
-
register(input) {
|
|
6509
|
-
const at = now();
|
|
6510
|
-
const scope = {
|
|
6511
|
-
sessionId: text(input.sessionId),
|
|
6512
|
-
seqNo: text(input.seqNo),
|
|
6513
|
-
objectId: text(input.objectId),
|
|
6514
|
-
caseDir: text(input.caseDir),
|
|
6515
|
-
allowedAttachmentIds: [...input.allowedAttachmentIds].map((id) => text(id)).filter((id) => id !== ""),
|
|
6516
|
-
createdAt: at,
|
|
6517
|
-
lastUsedAt: at,
|
|
6518
|
-
expiresAt: at + ttlMs
|
|
6519
|
-
};
|
|
6520
|
-
scopes.set(scope.sessionId, scope);
|
|
6521
|
-
trim();
|
|
6522
|
-
return scope;
|
|
6523
|
-
},
|
|
6524
|
-
use(sessionId) {
|
|
6525
|
-
sweep();
|
|
6526
|
-
const id = text(sessionId);
|
|
6527
|
-
if (id === "") return void 0;
|
|
6528
|
-
const scope = scopes.get(id);
|
|
6529
|
-
if (scope === void 0) return void 0;
|
|
6530
|
-
const touched = {
|
|
6531
|
-
...scope,
|
|
6532
|
-
lastUsedAt: now()
|
|
6533
|
-
};
|
|
6534
|
-
scopes.set(id, touched);
|
|
6535
|
-
return touched;
|
|
6536
|
-
},
|
|
6537
|
-
peek(sessionId) {
|
|
6538
|
-
sweep();
|
|
6539
|
-
return scopes.get(text(sessionId));
|
|
6540
|
-
},
|
|
6541
|
-
drop(sessionId) {
|
|
6542
|
-
scopes.delete(text(sessionId));
|
|
6543
|
-
},
|
|
6544
|
-
list() {
|
|
6545
|
-
sweep();
|
|
6546
|
-
return [...scopes.values()];
|
|
6547
|
-
},
|
|
6548
|
-
clear() {
|
|
6549
|
-
scopes.clear();
|
|
6550
|
-
},
|
|
6551
|
-
size() {
|
|
6552
|
-
sweep();
|
|
6553
|
-
return scopes.size;
|
|
6554
|
-
}
|
|
6555
|
-
};
|
|
6556
|
-
}
|
|
6557
|
-
/**
|
|
6558
|
-
* 案例内 Tool 的**统一门禁**:认证身份 → 解析范围 → 校验模型提交的标识。
|
|
6559
|
-
*
|
|
6560
|
-
* 与 `requireAuditScope` 的关系:后者是"审核子会话"这一支的实现,这里在它之前再加一支
|
|
6561
|
-
* "已登记的讨论会话"。**讨论会话绝不复用审核 scope**(见文件头第 2 条)。
|
|
6562
|
-
*/
|
|
6563
|
-
async function requireMaterialScope(ctx, state, scopes, exec, args, options = {}) {
|
|
6564
|
-
const { childId } = callerIdentity(exec);
|
|
6565
|
-
if (childId === "") return { ...failure$1("policy", "无法确认调用者身份:案例内的操作只允许由本次审核的子会话或已登记的报告讨论会话发起") };
|
|
6566
|
-
if (auditScopeFor(state, childId) !== void 0) {
|
|
6567
|
-
const checked = await requireAuditScope(ctx, state, exec, args, options);
|
|
6568
|
-
if (!checked.ok) return checked;
|
|
6569
|
-
return {
|
|
6570
|
-
ok: true,
|
|
6571
|
-
material: {
|
|
6572
|
-
kind: "audit",
|
|
6573
|
-
scope: checked.scope
|
|
6574
|
-
}
|
|
6575
|
-
};
|
|
6576
|
-
}
|
|
6577
|
-
const scope = scopes.use(childId);
|
|
6578
|
-
if (scope === void 0) return { ...failure$1("policy", "调用者既不是进行中的审核子会话,也不是已登记的报告讨论会话:案例内的操作一律拒绝(报告讨论请在工作台点「与 DeepSeek 讨论报告」重新登记材料范围)") };
|
|
6579
|
-
const seqNo = text(args.seqNo).trim();
|
|
6580
|
-
if (seqNo !== "" && seqNo !== scope.seqNo) return { ...failure$1("input", "流水号与本次登记的材料范围不一致:拒绝操作") };
|
|
6581
|
-
const objectId = text(args.objectId).trim();
|
|
6582
|
-
if (objectId !== "" && objectId !== scope.objectId) return { ...failure$1("input", "记录标识与本次登记的材料范围不一致:拒绝操作") };
|
|
6583
|
-
const caseDir = text(args.caseDir).trim();
|
|
6584
|
-
if (caseDir === "") {
|
|
6585
|
-
if (options.requireCaseDir === false) return {
|
|
6586
|
-
ok: true,
|
|
6587
|
-
material: {
|
|
6588
|
-
kind: "discussion",
|
|
6589
|
-
scope
|
|
6590
|
-
}
|
|
6591
|
-
};
|
|
6592
|
-
return { ...failure$1("input", "缺少案例目录:案例内的操作必须带上本轮案例目录") };
|
|
6593
|
-
}
|
|
6594
|
-
if (fileSystem(ctx) === void 0) return { ...failure$1("infrastructure", "Host 文件服务不可用") };
|
|
6595
|
-
try {
|
|
6596
|
-
const given = await resolveTarget(ctx, caseDir);
|
|
6597
|
-
const expected = await resolveTarget(ctx, scope.caseDir);
|
|
6598
|
-
if (text(given.targetKey) === "" || given.targetKey !== expected.targetKey) return { ...failure$1("policy", "案例目录必须是本次登记的那一个(不许用工作空间根、兄弟案例或子目录)") };
|
|
6599
|
-
} catch (error) {
|
|
6600
|
-
return { ...failure$1("infrastructure", `案例目录解析失败:${error instanceof Error ? error.message : String(error)}`) };
|
|
6601
|
-
}
|
|
6602
|
-
return {
|
|
6603
|
-
ok: true,
|
|
6604
|
-
material: {
|
|
6605
|
-
kind: "discussion",
|
|
6606
|
-
scope
|
|
6607
|
-
}
|
|
6608
|
-
};
|
|
6609
|
-
}
|
|
6610
|
-
/** 这次调用是不是**已登记**的报告讨论会话(给它开材料权限的那一条身份判据)。 */
|
|
6611
|
-
function isRegisteredDiscussion(scopes, sessionId) {
|
|
6612
|
-
return scopes.peek(sessionId) !== void 0;
|
|
6613
|
-
}
|
|
6614
6658
|
//#endregion
|
|
6615
6659
|
//#region src/host/tools/h3yun.ts
|
|
6616
6660
|
/**
|
|
@@ -6746,10 +6790,17 @@ function h3yunTools(deps) {
|
|
|
6746
6790
|
...failure$1("policy", "审核子会话不能直接查询氚云记录:本轮记录与附件清单已在输入快照里"),
|
|
6747
6791
|
...base
|
|
6748
6792
|
};
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6793
|
+
const discussion = deps.discussionScopes.peek(callerIdentity(exec).childId);
|
|
6794
|
+
if (discussion !== void 0) {
|
|
6795
|
+
const checked = await requireMaterialScope(ctx, deps.state, deps.discussionScopes, exec, {
|
|
6796
|
+
caseDir: args.caseDir,
|
|
6797
|
+
objectId
|
|
6798
|
+
}, { requireCaseDir: false });
|
|
6799
|
+
if (!checked.ok) return {
|
|
6800
|
+
...checked,
|
|
6801
|
+
...base
|
|
6802
|
+
};
|
|
6803
|
+
}
|
|
6753
6804
|
const platform = await deps.world.platform();
|
|
6754
6805
|
const gap = await requireCrwu(ctx, platform);
|
|
6755
6806
|
if (gap !== null) return {
|
|
@@ -6762,7 +6813,7 @@ function h3yunTools(deps) {
|
|
|
6762
6813
|
...base
|
|
6763
6814
|
};
|
|
6764
6815
|
const requested = text(args.caseDir).trim();
|
|
6765
|
-
let workdir = await deps.world.workdir();
|
|
6816
|
+
let workdir = discussion?.caseDir || await deps.world.workdir();
|
|
6766
6817
|
if (requested !== "") {
|
|
6767
6818
|
const caseCheck = await requireCaseDir(ctx, requested, { allowedRoot: allowedCaseRootOf(deps.state) });
|
|
6768
6819
|
if (!caseCheck.ok) return {
|
|
@@ -6885,11 +6936,18 @@ function h3yunTools(deps) {
|
|
|
6885
6936
|
count: 0,
|
|
6886
6937
|
files: []
|
|
6887
6938
|
};
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6939
|
+
const discussion = deps.discussionScopes.peek(callerIdentity(exec).childId);
|
|
6940
|
+
if (discussion !== void 0) {
|
|
6941
|
+
const checked = await requireMaterialScope(ctx, deps.state, deps.discussionScopes, exec, {
|
|
6942
|
+
caseDir: args.caseDir,
|
|
6943
|
+
objectId
|
|
6944
|
+
}, { requireCaseDir: false });
|
|
6945
|
+
if (!checked.ok) return {
|
|
6946
|
+
...checked,
|
|
6947
|
+
count: 0,
|
|
6948
|
+
files: []
|
|
6949
|
+
};
|
|
6950
|
+
}
|
|
6893
6951
|
const platform = await deps.world.platform();
|
|
6894
6952
|
const gap = await requireCrwu(ctx, platform);
|
|
6895
6953
|
if (gap !== null) return {
|
|
@@ -6904,7 +6962,7 @@ function h3yunTools(deps) {
|
|
|
6904
6962
|
files: []
|
|
6905
6963
|
};
|
|
6906
6964
|
const requested = text(args.caseDir).trim();
|
|
6907
|
-
let workdir = await deps.world.workdir();
|
|
6965
|
+
let workdir = discussion?.caseDir || await deps.world.workdir();
|
|
6908
6966
|
if (requested !== "") {
|
|
6909
6967
|
const caseCheck = await requireCaseDir(ctx, requested, { allowedRoot: allowedCaseRootOf(deps.state) });
|
|
6910
6968
|
if (!caseCheck.ok) return {
|
|
@@ -6967,7 +7025,7 @@ function h3yunTools(deps) {
|
|
|
6967
7025
|
name: TOOL_NAMES.h3yunFileGet,
|
|
6968
7026
|
description: [
|
|
6969
7027
|
"按 fileId **单附件定向下载**氚云记录附件到**本轮案例目录**内的相对路径。",
|
|
6970
|
-
"
|
|
7028
|
+
"普通会话可按员工权限下载;托管审核与已登记讨论会话只接受本次材料清单里的 fileId。",
|
|
6971
7029
|
"目标必须落在给定案例目录之下(越界直接拒绝)。",
|
|
6972
7030
|
"本工具没有「整单下载」路径:一次只取一个附件,失败就是失败,不会退化成批量下载。"
|
|
6973
7031
|
].join(" "),
|
|
@@ -6975,7 +7033,7 @@ function h3yunTools(deps) {
|
|
|
6975
7033
|
fileId: {
|
|
6976
7034
|
type: "string",
|
|
6977
7035
|
required: true,
|
|
6978
|
-
description: "附件 fileId
|
|
7036
|
+
description: "附件 fileId(托管审核与已登记讨论会话须在本次材料清单内)"
|
|
6979
7037
|
},
|
|
6980
7038
|
caseDir: {
|
|
6981
7039
|
type: "string",
|
|
@@ -7013,22 +7071,22 @@ function h3yunTools(deps) {
|
|
|
7013
7071
|
async execute(args, exec) {
|
|
7014
7072
|
const ctx = toolContext(deps.ctx, exec);
|
|
7015
7073
|
const fileId = text(args.fileId).trim();
|
|
7016
|
-
const materialCheck = await
|
|
7074
|
+
const materialCheck = await requireCaseAccess(ctx, deps.state, deps.discussionScopes, exec, { caseDir: args.caseDir });
|
|
7017
7075
|
if (!materialCheck.ok) return {
|
|
7018
7076
|
...materialCheck,
|
|
7019
7077
|
fileId,
|
|
7020
7078
|
path: "",
|
|
7021
7079
|
sizeBytes: 0
|
|
7022
7080
|
};
|
|
7023
|
-
const material = materialCheck
|
|
7024
|
-
const casePath = material.
|
|
7081
|
+
const material = materialCheck;
|
|
7082
|
+
const casePath = material.casePath;
|
|
7025
7083
|
if (fileId === "") return {
|
|
7026
7084
|
...failure$1("input", "fileId 不能为空"),
|
|
7027
7085
|
fileId,
|
|
7028
7086
|
path: "",
|
|
7029
7087
|
sizeBytes: 0
|
|
7030
7088
|
};
|
|
7031
|
-
if (!material.
|
|
7089
|
+
if (material.kind !== "session" && !material.allowedAttachmentIds?.includes(fileId)) return {
|
|
7032
7090
|
...failure$1("policy", material.kind === "audit" ? "这个附件不在本轮审核的输入快照里:只允许下载本次登记的附件" : "这个附件不在本次登记的材料范围里:只允许下载登记时取到的那批附件(请在工作台重新点「与 DeepSeek 讨论报告」刷新)"),
|
|
7033
7091
|
fileId,
|
|
7034
7092
|
path: "",
|
|
@@ -9406,7 +9464,7 @@ function knowledgeTools(deps) {
|
|
|
9406
9464
|
...failure$1("input", "paths 不能为空"),
|
|
9407
9465
|
...empty
|
|
9408
9466
|
};
|
|
9409
|
-
const caseCheck = await
|
|
9467
|
+
const caseCheck = await requireCaseAccess(ctx, deps.state, deps.discussionScopes, exec, { caseDir: args.caseDir });
|
|
9410
9468
|
if (!caseCheck.ok) return {
|
|
9411
9469
|
...caseCheck,
|
|
9412
9470
|
...empty
|
|
@@ -9983,7 +10041,7 @@ function ossTools(deps) {
|
|
|
9983
10041
|
...failure$1("input", `流水号形状不对(不放进 OSS 路径):${seqNo === "" ? "(空)" : seqNo}`),
|
|
9984
10042
|
...empty
|
|
9985
10043
|
};
|
|
9986
|
-
const caseCheck = await
|
|
10044
|
+
const caseCheck = await requireCaseAccess(ctx, deps.state, deps.discussionScopes, exec, {
|
|
9987
10045
|
caseDir: args.caseDir,
|
|
9988
10046
|
seqNo: args.seqNo
|
|
9989
10047
|
});
|
|
@@ -10410,7 +10468,7 @@ function pythonScriptTools(deps) {
|
|
|
10410
10468
|
caseDir: {
|
|
10411
10469
|
required: true,
|
|
10412
10470
|
type: "string",
|
|
10413
|
-
description: "本轮案例目录(Host
|
|
10471
|
+
description: "本轮案例目录(Host 校验工作空间与案例范围)。"
|
|
10414
10472
|
},
|
|
10415
10473
|
script: {
|
|
10416
10474
|
required: true,
|
|
@@ -10522,7 +10580,7 @@ function pythonScriptTools(deps) {
|
|
|
10522
10580
|
async execute(args, exec) {
|
|
10523
10581
|
const ctx = toolContext(deps.ctx, exec);
|
|
10524
10582
|
const base = idleResult();
|
|
10525
|
-
const caseCheck = await
|
|
10583
|
+
const caseCheck = await requireCaseAccess(ctx, deps.state, deps.discussionScopes, exec, { caseDir: args.caseDir });
|
|
10526
10584
|
if (!caseCheck.ok) return {
|
|
10527
10585
|
...base,
|
|
10528
10586
|
...caseCheck
|
|
@@ -10553,7 +10611,7 @@ function pythonScriptTools(deps) {
|
|
|
10553
10611
|
...base,
|
|
10554
10612
|
...failure$1("capability-gap", "DSH 自带 Python 解析器没有装配:本插件实例不能执行技能脚本")
|
|
10555
10613
|
};
|
|
10556
|
-
const runtime = await deps.python.check();
|
|
10614
|
+
const runtime = await deps.python.check({ agent: exec.agent });
|
|
10557
10615
|
if (!runtime.ok) return {
|
|
10558
10616
|
...base,
|
|
10559
10617
|
...failure$1("capability-gap", `DSH 自带 Python 不可用(${runtime.state}):${runtime.error}`),
|
|
@@ -16313,7 +16371,7 @@ function createCoreOperations(ctx, config, state, world, resolvers, extra = {})
|
|
|
16313
16371
|
buildKind: HOST_BUILD_KIND,
|
|
16314
16372
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16315
16373
|
builtAt: HOST_BUILD_STAMP,
|
|
16316
|
-
protocol:
|
|
16374
|
+
protocol: 26
|
|
16317
16375
|
}),
|
|
16318
16376
|
boot: async () => {
|
|
16319
16377
|
const home = await world.home();
|
|
@@ -16332,7 +16390,7 @@ function createCoreOperations(ctx, config, state, world, resolvers, extra = {})
|
|
|
16332
16390
|
});
|
|
16333
16391
|
return {
|
|
16334
16392
|
ok: true,
|
|
16335
|
-
protocol:
|
|
16393
|
+
protocol: 26,
|
|
16336
16394
|
permissionSchemaVersion: 1,
|
|
16337
16395
|
localAccess: await syncLocalAccessConsent({
|
|
16338
16396
|
ctx,
|
|
@@ -16433,7 +16491,7 @@ function createCoreOperations(ctx, config, state, world, resolvers, extra = {})
|
|
|
16433
16491
|
*/
|
|
16434
16492
|
trust: async () => ({
|
|
16435
16493
|
ok: false,
|
|
16436
|
-
error: `客户端与宿主的权限说明版本不一致:本机访问授权已改为版本化收据(协议 ${String(
|
|
16494
|
+
error: `客户端与宿主的权限说明版本不一致:本机访问授权已改为版本化收据(协议 ${String(26)}),请完全退出并重新打开 DeepSeek Harness 后在「账号连接」里按新的范围允许一次。`,
|
|
16437
16495
|
protocolMismatch: true,
|
|
16438
16496
|
consent: await readLocalAccessConsent({
|
|
16439
16497
|
ctx,
|
|
@@ -18747,7 +18805,7 @@ function apply(ctx, pluginConfig) {
|
|
|
18747
18805
|
"oss-config": ossConfigPath
|
|
18748
18806
|
},
|
|
18749
18807
|
hostVersion: PLUGIN_VERSION,
|
|
18750
|
-
protocolVersion:
|
|
18808
|
+
protocolVersion: 26
|
|
18751
18809
|
});
|
|
18752
18810
|
subscribeAuditEvents(ctx, state);
|
|
18753
18811
|
const form = new H3yunFormResolver({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-crwu-workbench",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
4
4
|
"description": "中瑞世联工作台 / CRWU audit workbench for DeepSeek Harness: pick a pending audit report, dispatch one AI audit subagent, watch and stop/restart it, auto-upload deliverables to Aliyun OSS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|