abelien-harness 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -0
- package/README.md +9 -3
- package/package.json +1 -1
- package/skills/abelien-3dgen/SKILL.md +4 -2
- package/skills/abelien-3dgen/providers/tripo.json +64 -1
- package/skills/abelien-3dgen/references/engineering-lowpoly-qualification.md +20 -0
- package/skills/abelien-3dgen/references/host-api.md +128 -0
- package/skills/abelien-3dgen/references/provider-profile.md +4 -0
- package/skills/abelien-appearance/SKILL.md +2 -0
- package/skills/abelien-appearance/references/local-component-edit.md +31 -0
- package/skills/abelien-clarify/SKILL.md +8 -3
- package/skills/abelien-clarify/references/examples.md +9 -0
- package/skills/abelien-clarify/scripts/check_result.py +53 -1
- package/skills/abelien-clarify/scripts/test_check_result.py +40 -0
- package/skills/abelien-guide/SKILL.md +2 -0
- package/skills/abelien-guide/references/shared-response-rules.md +2 -2
- package/skills/abelien-hardware/SKILL.md +12 -2
- package/skills/abelien-hardware/domain.json +4 -4
- package/skills/abelien-hardware/references/search-index-format.md +41 -2
- package/skills/abelien-hardware/references/search-index-release.md +12 -1
- package/skills/abelien-hardware/references/selection-service-contract.md +3 -0
- package/skills/abelien-hardware/references/web-evidence-contract.md +2 -2
- package/skills/abelien-hardware/scripts/build-search-index.mjs +50 -6
- package/skills/abelien-hardware/scripts/dimensions.mjs +184 -0
- package/skills/abelien-hardware/scripts/electrical.mjs +125 -0
- package/skills/abelien-hardware/scripts/finalize-selection.mjs +15 -0
- package/skills/abelien-hardware/scripts/provider-chain-contract.mjs +10 -6
- package/skills/abelien-hardware/scripts/publish-search-index.mjs +2 -1
- package/skills/abelien-hardware/scripts/search-index-release.mjs +45 -19
- package/skills/abelien-hardware/scripts/tos-wiki-search.mjs +40 -20
- package/skills/abelien-hardware/scripts/volc-web-search.mjs +6 -1
- package/skills/abelien-hardware/scripts/web-common.mjs +4 -1
- package/skills/abelien-hardware/scripts/web-provider-chain.mjs +8 -2
- package/skills/abelien-hardware/scripts/web-search-plan.mjs +16 -1
- package/skills/abelien-hardware/scripts/web-search.mjs +4 -2
- package/skills/abelien-hardware-selection/SKILL.md +1 -1
- package/skills/abelien-hardware-selection/domain.json +2 -2
- package/skills/abelien-hardware-selection/references/selection-service-contract.md +32 -2
- package/skills/abelien-hardware-selection/scripts/select.mjs +35 -7
- package/skills/abelien-harness/references/workflow-spec.md +15 -2
- package/skills/abelien-harness/scripts/generate3d-host.mjs +398 -0
- package/skills/abelien-harness/scripts/generate3d.mjs +54 -15
- package/skills/abelien-harness/scripts/receipt.mjs +8 -2
- package/skills/abelien-harness/scripts/studio.mjs +18 -10
- package/skills/abelien-structure/SKILL.md +3 -1
- package/skills/abelien-structure/references/build123d-execution.md +1 -1
- package/skills/abelien-structure/scripts/run_stage.py +8 -1
- package/skills/abelien-workspace/SKILL.md +2 -0
- package/skills/abelien-workspace/domain.json +1 -1
- package/skills/abelien-workspace/references/hardware-journey-orchestration.md +1 -1
- package/skills/abelien-workspace/workflows/definitions/0852e4949f5a6246880c1a26bf5ccaa42f8cda06b960e7c7ce234a0a176878f9.json +229 -0
- package/skills/abelien-workspace/workflows/definitions/7c69ea0d51099b1ae947f08b056ea67f5cad6b40ae8bcb00efb496fa5b9d5ed3.json +149 -0
- package/skills/abelien-workspace/workflows/definitions/9b5c65ad03fdecc0148931a7677cfa684a3fb04ab2738d6f7a1086a2c79ac86c.json +149 -0
- package/skills/abelien-workspace/workflows/definitions/a6e8254e1839d94530ae243cba779e423953b7169cbce338f0503d748d44800b.json +175 -0
- package/skills/abelien-workspace/workflows/definitions/b37f3b47e1e8bd1208859de57456152b149416e21949297b22bba937c51a5670.json +149 -0
- package/skills/abelien-workspace/workflows/definitions/b780bf280d61596b43eb6dbc6a417cf8df1ec9a7ae79a6de10f048da9ed58729.json +229 -0
- package/skills/abelien-workspace/workflows/definitions/c1b5c6c653f0c4761d51efee923aafbbeea05512e7f35c60f7f7d36ec49fc1d1.json +229 -0
- package/skills/abelien-workspace/workflows/definitions/e391e14c14cb39408a0a9e226ed54ffab7a36badea305ae4a3eafbac8124fc02.json +175 -0
- package/skills/abelien-workspace/workflows/definitions/fd8ce7074e2044985778446e93153864940bb9465471c1c71c41328cefff70bf.json +175 -0
- package/skills/abelien-workspace/workflows/user-journey-hardware-algorithm.json +3 -3
- package/skills/abelien-workspace/workflows/user-journey-hardware-candidates.json +3 -3
- package/skills/abelien-workspace/workflows/user-journey-hardware.json +3 -3
package/.env.example
CHANGED
|
@@ -31,6 +31,9 @@ ABELIEN_HARDWARE_REVISE_URL=
|
|
|
31
31
|
ABELIEN_HARDWARE_SELECT_TOKEN=
|
|
32
32
|
|
|
33
33
|
# Fixed prismer_then_volc policy; both Keys are trusted host-only.
|
|
34
|
+
# Prismer query, URL-load and Parse are temporarily off (P2-05.02): enabled|disabled, default disabled.
|
|
35
|
+
# When disabled, Volc searches directly and sources are read over public HTTPS (PDF/image unavailable).
|
|
36
|
+
ABELIEN_HARDWARE_WEB_PRISMER=disabled
|
|
34
37
|
# Volc subscription/pay-as-you-go search Keys are distinct from Ark generation Keys.
|
|
35
38
|
PRISMER_API_KEY=
|
|
36
39
|
WEB_SEARCH_API_KEY=
|
package/README.md
CHANGED
|
@@ -101,15 +101,21 @@ preview 父候选与可信 definitionDigest 等待身份。预览通过、相同
|
|
|
101
101
|
|
|
102
102
|
托管 Agent 调 Studio 工具时只使用上述 `ab studio` 封闭命令。Web 宿主将 server-fixed Web origin、
|
|
103
103
|
draft/attempt/binding fence 和短期委托 token 写入 attempt 不可变文件,再由 EaaS 内 launcher 以
|
|
104
|
-
0600 临时文件、fsync、rename 激活固定
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
0600 临时文件、fsync、rename 激活固定 `/home/agent/workspace/abelien-runtime-delegation/current.json`;
|
|
105
|
+
CLI 默认读取同一路径,不回退到旧 `/tmp` 路径。launcher 按 generation、credential epoch 和
|
|
106
|
+
数据库 attempt 创建时间拒绝晚到旧 attempt 覆盖。CLI 不接受命令参数、环境变量或模型提供的 origin、token 或 context
|
|
107
|
+
路径,并在联网前逐项核对 context 与 token claims;受信本地宿主保留模块级 `studioCmd(...,{contextFile})` 注入。
|
|
108
|
+
Agent 可见输出会移除 policy 身份,provider、
|
|
107
109
|
account、price 不进入该响应。
|
|
108
110
|
|
|
109
111
|
POST 只对完全相同的序列化正文做一次有界回放;仍未知时返回 `STUDIO_WRITE_UNKNOWN`,调用方只能
|
|
110
112
|
读取原 creation/modification/invocation,或以同一 command/operation 身份精确重放,不能换 ID。工具回执
|
|
111
113
|
仍只用 `receipt get`,绝不调用 `workflow advance`。
|
|
112
114
|
|
|
115
|
+
G02 默认路径回归见 [Studio 测试](test/studio.test.mjs):受控文件边界覆盖真实解析、未注入路径的命令、
|
|
116
|
+
不回退与安全校验,属于本地单元证据。SR-SPEC-09-01 仍待发布包和真实 EaaS driver 写入后由裸 CLI
|
|
117
|
+
读取的容器证据;本次源码修正不代表已发布、Web 安装 pin 已变更或容器验收通过。
|
|
118
|
+
|
|
113
119
|
当前接线边界:Web 已为普通首轮 production EaaS turn 实现固定 Harness release 安装、
|
|
114
120
|
actionScope/definitionDigest 选择、`state.probe` attempt 投递和同一远端回合内 marker 回调;
|
|
115
121
|
tool-receipt successor 与已有可信 runtime decision 也可以取得后续 context。npm 上仍无可安装的
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: abelien-3dgen
|
|
3
|
-
description: 3D 构件生成域包:四家 provider(Meshy/Hunyuan/Tripo/Ark)声明式 profile+
|
|
3
|
+
description: 3D 构件生成域包:四家 provider(Meshy/Hunyuan/Tripo/Ark)声明式 profile+22 款模型目录(18 款图生+4 款文生),经内核 ab generate 3d 命令面按参考图或文字描述提交 3D 任务、查询状态并下载 glb 产物。请求形状以官方接入指引为准。
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility:
|
|
6
6
|
- claude-code
|
|
@@ -21,11 +21,13 @@ allowed-tools:
|
|
|
21
21
|
自动发现)。引擎实现与架构详见 `docs/architecture/generate3d.md`;profile schema
|
|
22
22
|
规范与逐 provider 来源/偏差表见 `references/provider-profile.md`。
|
|
23
23
|
|
|
24
|
+
局部外观编辑由 appearance 主导,默认单图,Tripo 可作固定配置的默认供应商;使用[局部交接](../abelien-appearance/references/local-component-edit.md)。Web 的 mesh.generate@1 必须走[受信宿主 API](references/host-api.md),复用原 F4 身份与预算;模糊提交保持 unknown,不能再次提交。普通纹理单图 tripo-image-v3.1 仍 verified:false;独立无纹理低面数 tripo-image-v3.1-engineering-lowpoly 已经单次真实 API/30 credits 对账及有界工程资格验证,见[资格证据与限制](references/engineering-lowpoly-qualification.md)。该验证只覆盖声明的请求与单样例,2000面只是请求参数,不保证任意网格的拓扑、接口或制造质量。显式 operator qualification 仅走受信 host dependency,不能通过 CLI/env 绕过。
|
|
25
|
+
|
|
24
26
|
## 四条命令
|
|
25
27
|
|
|
26
28
|
```bash
|
|
27
29
|
AB=<skills目录>/abelien-harness/scripts/ab.mjs; WD=<工作目录>
|
|
28
|
-
node "$AB" --workdir "$WD" generate 3d providers # 列 4 provider/
|
|
30
|
+
node "$AB" --workdir "$WD" generate 3d providers # 列 4 provider/22 模型+基准元数据+凭证在场布尔+verified+openItems
|
|
29
31
|
node "$AB" --workdir "$WD" generate 3d submit <provider> <model> --image a.png [--image b.png …] \
|
|
30
32
|
[--confirm-spend] [--dry-run] # 图生:按参考图提交(双闸见下)
|
|
31
33
|
node "$AB" --workdir "$WD" generate 3d submit <provider> <model> --prompt '白色圆润的杯子,15 厘米高' \
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"官方 models-and-versions 文档页(dated 模型值:v3.1-20260211 / v3.0-20250812 / v2.5-20250123 / P1-20260311;texture_quality/geometry_quality 仅 ≥v3.0-20250812 合法;2026-09-23 核对)",
|
|
9
9
|
"免费探测实录(2026-09-24,零花费):空体 submit 报错 message 在线枚举 allowed values 与上列四个 dated 值全数吻合(另存在 P2-20260801,无基准数据不入 16 模型目录);GET /v3/account/balance 封套 {code,status,data:{balance,frozen}};请求级错误封套为顶层 {code,status:\"error\",message,suggestion?,request_id}(poll.errorPath 据此补顶层 message 键)",
|
|
10
10
|
"真实冒烟实录(2026-09-24,tripo-p1 四图):upload POST /v3/files multipart 换 file_token、viewKeyed inputs 实发、dated P1-20260311 均一次通过,端到端 79.3s(基准 74.4s);实际扣费 50 credits(balance 215.00→165.00)≈ 基准估值 $0.50(官方价签 20–30 credits 区间低估 P1)",
|
|
11
|
-
"https://developers.tripo3d.ai/en/docs/generation-text-to-model/standard.md(官方文档原文,2026-09-26 逐字核对):POST {base}/v3/generation/text-to-model,prompt 必填≤1024、model 必填(v3.1-20260211/v3.0-20250812/v2.5-20250123),返回 {code,data:{task_id}}、任务体 data.status=success、data.output.model_url;另核官方 SDK master 的 text_to_model(v2 端点,与本包 v3 路由不同源,仅作字段名旁证)"
|
|
11
|
+
"https://developers.tripo3d.ai/en/docs/generation-text-to-model/standard.md(官方文档原文,2026-09-26 逐字核对):POST {base}/v3/generation/text-to-model,prompt 必填≤1024、model 必填(v3.1-20260211/v3.0-20250812/v2.5-20250123),返回 {code,data:{task_id}}、任务体 data.status=success、data.output.model_url;另核官方 SDK master 的 text_to_model(v2 端点,与本包 v3 路由不同源,仅作字段名旁证)",
|
|
12
|
+
"https://developers.tripo3d.ai/en/docs/generation-image-to-model/standard (2026-10-05 read-only official documentation: one input file_token, dated v3.1-20260211; local fixtures are not paid smoke verification)"
|
|
12
13
|
],
|
|
13
14
|
"openItems": [],
|
|
14
15
|
"transport": {
|
|
@@ -251,6 +252,68 @@
|
|
|
251
252
|
"texture_quality": "detailed"
|
|
252
253
|
}
|
|
253
254
|
}
|
|
255
|
+
},
|
|
256
|
+
"tripo-image-v3.1": {
|
|
257
|
+
"verified": false,
|
|
258
|
+
"openItems": [
|
|
259
|
+
"Official single-image request documented on 2026-10-05; no paid smoke or accounting verification yet. Keep this model blocked until controlled verification."
|
|
260
|
+
],
|
|
261
|
+
"input": {
|
|
262
|
+
"count": 1,
|
|
263
|
+
"views": [
|
|
264
|
+
"front"
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
"submit": {
|
|
268
|
+
"method": "POST",
|
|
269
|
+
"path": "/v3/generation/image-to-model",
|
|
270
|
+
"idPath": [
|
|
271
|
+
"data.task_id",
|
|
272
|
+
"data.id",
|
|
273
|
+
"task_id",
|
|
274
|
+
"id"
|
|
275
|
+
],
|
|
276
|
+
"body": {
|
|
277
|
+
"input": "{{images.0}}",
|
|
278
|
+
"model": "v3.1-20260211",
|
|
279
|
+
"face_limit": 50000,
|
|
280
|
+
"texture": true,
|
|
281
|
+
"pbr": true,
|
|
282
|
+
"texture_quality": "standard"
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"tripo-image-v3.1-engineering-lowpoly": {
|
|
287
|
+
"verified": true,
|
|
288
|
+
"openItems": [],
|
|
289
|
+
"input": {
|
|
290
|
+
"count": 1,
|
|
291
|
+
"views": [
|
|
292
|
+
"front"
|
|
293
|
+
]
|
|
294
|
+
},
|
|
295
|
+
"submit": {
|
|
296
|
+
"method": "POST",
|
|
297
|
+
"path": "/v3/generation/image-to-model",
|
|
298
|
+
"idPath": [
|
|
299
|
+
"data.task_id",
|
|
300
|
+
"data.id",
|
|
301
|
+
"task_id",
|
|
302
|
+
"id"
|
|
303
|
+
],
|
|
304
|
+
"body": {
|
|
305
|
+
"input": "{{images.0}}",
|
|
306
|
+
"model": "v3.1-20260211",
|
|
307
|
+
"face_limit": 2000,
|
|
308
|
+
"geometry_quality": "standard",
|
|
309
|
+
"smart_low_poly": true,
|
|
310
|
+
"texture": false,
|
|
311
|
+
"pbr": false,
|
|
312
|
+
"quad": false,
|
|
313
|
+
"generate_parts": false,
|
|
314
|
+
"auto_size": false
|
|
315
|
+
}
|
|
316
|
+
}
|
|
254
317
|
}
|
|
255
318
|
}
|
|
256
319
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Single-image engineering low-poly qualification
|
|
2
|
+
|
|
3
|
+
On 2026-10-05, the exact `tripo-image-v3.1-engineering-lowpoly` model profile completed one operator-controlled provider qualification. Only this model's verification flag changes. The existing textured single-image and text-to-model profiles remain unverified; old multiview requests and old recovery identities are unchanged.
|
|
4
|
+
|
|
5
|
+
The official [image-to-model API](https://developers.tripo3d.ai/en/docs/generation-image-to-model/standard) accepts one uploaded file token and returns one task ID. The qualified request was `POST /v3/generation/image-to-model` with model `v3.1-20260211`, `face_limit:2000`, `geometry_quality:'standard'`, `smart_low_poly:true`, and `texture`, `pbr`, `quad`, `generate_parts`, `auto_size` all false. The input field contains the token for the hash-bound single PNG; it is not a prompt or a duplicated multiview input.
|
|
6
|
+
|
|
7
|
+
The controller used the existing hosted durable send claim and one exact trusted qualification manifest. The immutable qualification release digest was `bd51948fbb96b7c625a5a5e8c7c60c14b2166f2d4edd5810744887933e15aac8`; provider configuration digest was `25e8e5a03c1fe0fee4c724c84b0f389c6d2e6d3c6d0987a4158a29dfd3b31bb1`. Updating verified evidence does not change that request configuration digest. The old qualification release remains immutable and owns original-ID recovery.
|
|
8
|
+
|
|
9
|
+
- Input PNG SHA256: `41976023753e0fe79b61796422b58cb8e2bc8fcd3629b2f6f50dda0473eca554`.
|
|
10
|
+
- Original task ID: `4494dd45-a961-4b0e-9542-7c646759f2aa`; exactly one paid create attempt.
|
|
11
|
+
- Successful task `credits_consumed`: 30; `/v3/account/usage` independently matched that same task with 30 credits. Balance changed 115 -> 85, frozen balance 0. This is actual provider accounting, not a conversion from internal product budget or a promise of future prices.
|
|
12
|
+
- Original GLB: 85,756 bytes, SHA256 `2c8bb6450cf63d91cb8a83c9b80d7a93352a70ae7aa71951b5b7c77d196abc3b`, 2,428 triangles. The requested face limit is not the measured triangle count.
|
|
13
|
+
|
|
14
|
+
The raw mesh had 20 actual OCCT self-interferences and is not an engineerable solid without repair. An independently reviewed geometry worker performed one explicitly recorded local position-only repair: 46/1,216 vertices (3.7829%), maximum .128507869 mm, unchanged topology/index/node/non-POSITION bytes, with 0 remaining self-interferences. Derived GLB SHA256 is `d9281792a1e75f916368397958b415a508d682bcf19a196164f035150c73fb7c`. Original provider bytes remain retained; they are not replaced or relabeled as the derived artifact.
|
|
15
|
+
|
|
16
|
+
A declared two-hole carrier/base fixture passed a real BRep union, independent fresh STEP reimport, interface/seat/hole protection, unchanged occurrences, static clearance and planar insertion. The independent three-case native suite passed with zero skips. Worker identity `beb4999aa1de1232521bee802e7b5fa503e56b47c6c1986eb569cd5c2690d788` binds the tested implementation and policy. Its fixed repair is at most one .25 Laplacian step, at most 5% logical vertices and displacement min(.25 mm, height × 1%); unsupported or over-budget meshes are rejected.
|
|
17
|
+
|
|
18
|
+
This is a single provider/engineering qualification, **not product acceptance or a general manufacturing guarantee**. A real draft must still pass the original F4 budget/authority, immutable source bindings, the engineering operation, review of the final STEP's own rendered images and explicit adoption. Strength, manufacturability, physical assembly, local thin features, texture fidelity and actual hardware dimensions remain unverified. Envelopes are checked as modeled only. No numerical visual score, FX conversion or invented benchmark is assigned.
|
|
19
|
+
|
|
20
|
+
Private controller evidence is retained under the appearance-edit qualification run: original request/manifest/release identity, bounded redacted HTTP traces, account reconciliation, raw GLB, independent geometry log, final STEP and repair metrics. Those private records, rather than this summary alone, substantiate the verification decision.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Trusted hosted generation API v1
|
|
2
|
+
|
|
3
|
+
Import `abelien-harness/scripts/generate3d-host.mjs`; require `HOSTED_GENERATION_PROTOCOL === 1`.
|
|
4
|
+
This module is a private library, never a shell/model tool. The Web gateway owns authentication,
|
|
5
|
+
fixed provider/model configuration, source access, current baseline/target checks, active lease,
|
|
6
|
+
F4 reservation/settlement and result receipts. Use the original invocation/upstreamKey as requestId.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
const deps = { workdir, env, fetchImpl, signal, maxArtifactBytes, onDiagnostic, beforePaidSubmit };
|
|
10
|
+
const request = {
|
|
11
|
+
requestId, inputDigest, provider, model,
|
|
12
|
+
images: [{ path: absolutePrivateImagePath, sha256: sourceSha256 }],
|
|
13
|
+
confirmSpend: true,
|
|
14
|
+
};
|
|
15
|
+
const modelInfo = inspectHostedModel(deps, { provider, model });
|
|
16
|
+
const submitted = await submitHostedGeneration(deps, request);
|
|
17
|
+
const identity = { requestId, inputDigest };
|
|
18
|
+
const persisted = lookupHostedGeneration(deps, identity); // synchronous, offline, null if absent
|
|
19
|
+
const status = await statusHostedGeneration(deps, identity); // bounded status operation
|
|
20
|
+
const result = await fetchHostedGeneration(deps, identity); // only after succeeded
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`inputDigest` and image hashes are lowercase SHA-256 hex. Workdir is absolute, private, durable,
|
|
24
|
+
and scoped to this invocation; do not delete it while F4 needs recovery, and do not share it
|
|
25
|
+
between concurrent different invocations (the generic resource store is not a multiwriter DB).
|
|
26
|
+
The host supplies a guarded fetchImpl enforcing its existing outbound DNS/origin rules. The library
|
|
27
|
+
forces env-only credentials, redirect rejection, bounded provider JSON / artifact streaming,
|
|
28
|
+
self-contained GLB validation and host cancellation. No credential file or CLI fallback is read.
|
|
29
|
+
Source images are hash-verified and snapshotted before uploads, at most 20 MiB each. Default artifact
|
|
30
|
+
limit is 128 MiB; the host may lower it. Status errors expose codes only, never provider bodies. Optional trusted onDiagnostic receives private caught-error code/message/stack/details with known env values, bearer tokens and URLs redacted; observer failure does not affect execution. The host guarded fetch records complete credential-redacted request/response success and failure traces through its existing private diagnostics sink.
|
|
31
|
+
|
|
32
|
+
Return shape is `{ id, requestId, inputDigest, requestDigest, profileDigest, provider, model,
|
|
33
|
+
providerTaskId?, status, recovery, providerIdempotency:false, errorCode?, artifact? }`.
|
|
34
|
+
`artifact` is `{file:'model.glb', path, sha256, bytes}`; signed URL never leaves the download call.
|
|
35
|
+
Status is submitted/running/succeeded/failed/unknown. recovery is poll for a known provider task,
|
|
36
|
+
none for a definite failure before paid send, unknown otherwise. This is provider state only,
|
|
37
|
+
not a fit/adoption permission. Cached artifacts are hash-verified on every fetch.
|
|
38
|
+
|
|
39
|
+
inspectHostedModel returns input count/views, verified, configuration profileDigest and
|
|
40
|
+
estCostUsd (nullable, always marked historicalEstimate:true). F4 pricing authority stays in Web;
|
|
41
|
+
a historical estimate is never a current quote or spend consent. Profile drift blocks polling
|
|
42
|
+
rather than interpreting the old task under a different API configuration.
|
|
43
|
+
|
|
44
|
+
## One paid send and recovery
|
|
45
|
+
|
|
46
|
+
The module adds an immutable exclusive send claim under `.abelien/gen3d-send-claims/` and stores
|
|
47
|
+
all evolving provider state in the existing gen3d resource store. Claim publication uses fsync
|
|
48
|
+
and atomic no-replace linking. This is a per-invocation send guard, not another product lifecycle.
|
|
49
|
+
Repeated submits compare the recorded request/profile/source digests against the retried request and
|
|
50
|
+
return the original result. That comparison uses the digests the claim already recorded, so it still
|
|
51
|
+
works after the host has discarded its staged source images; a changed declared source digest is an
|
|
52
|
+
identity conflict, never a resend, and no repeat path reads or re-uploads source bytes.
|
|
53
|
+
Network error, timeout, 5xx, invalid JSON, missing or invalid provider ID after dispatch all mean
|
|
54
|
+
unknown. No retry, changed identity, regenerated input or reconstructed provider ID is allowed.
|
|
55
|
+
A crash after claim but before a task ID leaves unknown even if no send actually happened; safety
|
|
56
|
+
wins over guessing. Corrupt claim/store data fails closed. With a provider task ID, only poll and
|
|
57
|
+
fetch can continue. Cancellation never removes the claim or refunds/settles on its own.
|
|
58
|
+
|
|
59
|
+
The existing CLI keeps its commands, flags and JSON result contract, and does not gain this hosted
|
|
60
|
+
request interface. One shared behavior does change: artifact download now rejects redirects instead
|
|
61
|
+
of following them (the POST and upload paths always did), so a 3xx artifact response fails as
|
|
62
|
+
`DOWNLOAD_FAILED` on both surfaces. A signed artifact URL is a provider-issued credential, and
|
|
63
|
+
following a redirect would hand it to an unvetted host; verify a provider's artifact endpoint
|
|
64
|
+
resolves directly before adopting it. CLI paid POST also has zero automatic retries; consumers
|
|
65
|
+
needing restart safety must use the host API.
|
|
66
|
+
|
|
67
|
+
## Single image Tripo verification gate
|
|
68
|
+
|
|
69
|
+
The new `tripo-image-v3.1` profile is based on
|
|
70
|
+
[official image-to-model documentation](https://developers.tripo3d.ai/en/docs/generation-image-to-model/standard),
|
|
71
|
+
read 2026-10-05. One image is uploaded to `/v3/files`; its file_token becomes `input` on
|
|
72
|
+
`POST /v3/generation/image-to-model`, model `v3.1-20260211`. The configured output is textured GLB,
|
|
73
|
+
50,000 face limit, standard texture quality. It has no copied multiview benchmark. It stays
|
|
74
|
+
`verified:false` until controlled provider/accounting verification; local fixtures cannot remove
|
|
75
|
+
that gate. Four-view Tripo still requires front/left/back/right true images and remains optional.
|
|
76
|
+
|
|
77
|
+
The separate `tripo-image-v3.1-engineering-lowpoly` profile requests one image, standard geometry,
|
|
78
|
+
smart_low_poly:true, face_limit:2000, texture/pbr/quad/generate_parts/auto_size:false. It does not
|
|
79
|
+
change the older textured single-image request or borrow its evidence. This exact profile is now
|
|
80
|
+
verified after one real provider qualification, reconciled 30-credit charge and independently tested
|
|
81
|
+
bounded engineering adaptation; see [qualification evidence and limits](engineering-lowpoly-qualification.md).
|
|
82
|
+
It has no invented pricing or quality benchmark. Requested face count is neither an actual triangle
|
|
83
|
+
count nor proof of topology, fit or manufacturing quality. Other single-image/text profiles keep
|
|
84
|
+
their existing verification gates.
|
|
85
|
+
|
|
86
|
+
## Explicit operator qualification
|
|
87
|
+
|
|
88
|
+
Ordinary product submit and Web preflight always reject an unverified profile. A separate,
|
|
89
|
+
explicit provider qualification may use `HOSTED_GENERATION_QUALIFICATION_PROTOCOL === 1` and
|
|
90
|
+
trusted `deps.qualification = {manifest, releaseDigest}`. This dependency is absent from model
|
|
91
|
+
ToolInput, `ab generate`, environment configuration and the production Web adapter. It is not F4
|
|
92
|
+
or a draft acceptance result. The controller must verify the actual immutable loaded skills tree
|
|
93
|
+
and supply its digest independently as `releaseDigest`; matching two unverified strings is not
|
|
94
|
+
release verification. The manifest is an operator-owned private file with exactly:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
{schemaVersion:1, purpose:'provider_qualification', requestId, inputDigest,
|
|
98
|
+
provider, model, profileDigest, imageSha256:[sourceSha256], releaseDigest,
|
|
99
|
+
expiresAt:'<UTC ISO time>', maxCreates:1, maxCredits:30}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The host freezes this manifest, rejects any request/profile/image/release/expiry mismatch before
|
|
103
|
+
upload, and records its digest and contents in the existing immutable send claim. It grants only
|
|
104
|
+
this call an in-memory verification override; the installed catalog remains unchanged. The
|
|
105
|
+
required trusted `beforePaidSubmit` callback must verify the original operator authorization,
|
|
106
|
+
immutable release, current quoted charge within maxCredits, balance and cancellation immediately
|
|
107
|
+
before sending. This library is not a pricing service and cannot itself enforce provider charges.
|
|
108
|
+
Expiry is checked again after that callback. Each manifest binds one request ID in one durable
|
|
109
|
+
private workdir; controllers must not copy it into new workdirs or create alternate IDs to retry.
|
|
110
|
+
Repeat submit, status and fetch recover the original claim even after admission expires. Ambiguous
|
|
111
|
+
send without provider ID remains unknown and consumes the sole attempted create; no replacement
|
|
112
|
+
identity is allowed. Ordinary submits without admission still reject verified:false.
|
|
113
|
+
|
|
114
|
+
From a reviewed source checkout, `node scripts/prepare-provider-qualification.mjs` accepts only
|
|
115
|
+
`--workdir`, `--image`, `--provider`, `--model`, `--request-id`, `--release-digest`, `--expires-at`,
|
|
116
|
+
and `--max-credits`. All are required; paths are absolute. It emits a zero-network dry-run plan,
|
|
117
|
+
hash-bound request (`confirmSpend:false`) and draft manifest to stdout. It cannot submit, write a
|
|
118
|
+
claim, read credentials or verify the supplied release digest. The controller stores the reviewed
|
|
119
|
+
manifest with mode0600 and invokes the trusted library only after explicit bounded authorization.
|
|
120
|
+
Qualification retains provider task ID, raw private response, valid self-contained GLB hash, actual
|
|
121
|
+
topology/geometry results and authoritative charge plus before/after balance. Only successful
|
|
122
|
+
reconciliation and independent review permit updating that exact profile's verified evidence.
|
|
123
|
+
Then install a new fixed release for a separate real same-draft F4 run. The two results remain
|
|
124
|
+
distinct. No public bypass flag, inherited multiview verification or pre-evidence catalog flip.
|
|
125
|
+
|
|
126
|
+
`beforePaidSubmit` is a required trusted async host callback for a new submission, invoked after all image uploads and immediately before the one paid creation request. Bind it to the original F4 lease/fence, baseline, deadline and cancellation revalidation. A rejection or abort before it completes persists a definite pre-send failure (`recovery:none`); the send marker is written only after it passes. This callback is never sourced from tool input and is never invoked by lookup/status/fetch or a repeated submission of the original identity.
|
|
127
|
+
|
|
128
|
+
Hosts must require both `HOSTED_GENERATION_PROTOCOL === 1` and `HOSTED_GENERATION_AUTHORITY_PROTOCOL === 1` before advertising a paid adapter. The additive authority marker distinguishes older protocol-1 copies that lack the final boundary hook. A new submission without a callable hook fails before upload/claim with `GENERATION_AUTHORITY_REQUIRED`; offline recovery and known-task read operations do not require the hook. CLI submitFlow remains a separate command surface.
|
|
@@ -163,3 +163,7 @@ submit 本就不含 body 的情形(ark:内容面全在模型级 `params`)
|
|
|
163
163
|
| `tripo-text-v3.1` | `POST /v3/generation/text-to-model` 的 `prompt`/`model` 必填取自官方文档页 `generation-text-to-model/standard` | 该端点是否收与图生相同的 dated `model` 值、`face_limit`/`texture_quality` 是否同域合法 |
|
|
164
164
|
| `hunyuan3d-text-express` | 官方措辞为 `prompt`/`image_base64`/`image_url` 三选一且 prompt 与图互斥 | **prompt-only 是否真被网关接受**(措辞为三选一,实际必填面以实测为准);Express 的 `result_format:"GLB"` 沿用图生实测大写 |
|
|
165
165
|
| `seed3d-text-2.0` | 纯文本 `content`(`type:"text"`、无 `image_url` 项)为官方文档所列合法组合之一 | 本机 `arkcli` 方可核实(离线无从核实);`subdivision_level`/`file_format` 以 `--flags` 混排进 `content.text` 的弱校验形态是否被接受 |
|
|
166
|
+
|
|
167
|
+
## Single-image Tripo addition (2026-10-05)
|
|
168
|
+
|
|
169
|
+
`tripo-image-v3.1` has input `{count:1,views:["front"]}` and a model-level submit override: `POST /v3/generation/image-to-model`, body `input:{{images.0}}` (uploaded file token), `model:v3.1-20260211`, face_limit 50000, texture/pbr true, texture_quality standard. Source: [official H-series image-to-model](https://developers.tripo3d.ai/en/docs/generation-image-to-model/standard). It inherits upload and poll only, does not inherit multiview inputs, has no historical benchmark, and stays `verified:false` pending real controlled verification. See [host API](host-api.md) for the recovery and activation gate.
|
|
@@ -32,6 +32,8 @@ compatibility:
|
|
|
32
32
|
6. **实际视觉审查。** 读[视觉审查](references/visual-review.md),真正查看目标参考与本版实际渲染;分别记录 pass、conflict、unknown、not_checked。脚本通过或文字自述“很像”不能替代看图。
|
|
33
33
|
7. **迭代或交回。** 可在授权范围内修正外观目标,受工程约束影响的方案回到 structure。只有影响用户目标或需要其取舍时进入 B2;完成后把同版提案、证据和视觉结论交回 workspace。
|
|
34
34
|
|
|
35
|
+
局部猫头、拟生外壳和装饰件修改采用[局部自由造型交接](references/local-component-edit.md):默认单张目标件参考图,appearance 主导新外形,structure 保留精确接口;整机预览、fit 与采用分别记录。
|
|
36
|
+
|
|
35
37
|
## 边界
|
|
36
38
|
|
|
37
39
|
- appearance 决定外观意图与视觉是否符合;structure 决定如何实现以及几何是否成立。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 局部自由造型外观件编辑
|
|
2
|
+
|
|
3
|
+
这是 appearance、structure、3dgen 与 workspace 的交接契约;不是新的作品状态机。
|
|
4
|
+
|
|
5
|
+
## 输入与默认体验
|
|
6
|
+
|
|
7
|
+
用户选定一个可定位的外观件并描述变化,例如猫耳、拟生表面或装饰纹理。默认只需一张目标部件参考图:可由当前模型的目标视角渲染/已保存参考图取得,用户上传图片是可选项。原图若仍是旧外观,先经现有图像修改能力产生新外观参考并保存来源;不能忽略编辑意图直接把旧图送去图生 3D。四视图仅在用户选择或明显遮挡时增强,不复制同一张图冒充多个视角。
|
|
8
|
+
|
|
9
|
+
宿主解析并冻结 owner、draft、inputRevision、baseline candidate/design digest、target occurrence、原模型引用和图像资产哈希。目标必须属于该基线;未采用但已 ready 的候选与 adopted current 使用各自现有谱系,不能伪造 adopted revision。用户 prompt 和模型工具输入不能授予接口保留、付费、采用权限。
|
|
10
|
+
|
|
11
|
+
## 职责和执行
|
|
12
|
+
|
|
13
|
+
1. appearance 确认改变哪些可见区域,保留哪些特征,调用现有 image.generate 作单图修改,再交 3dgen 执行 mesh.generate@1(purpose=appearance_component)。Tripo 是自由造型的可选默认供应商,实际模型/预算/可用性由固定宿主配置决定;不覆盖别的已选供应商。
|
|
14
|
+
2. 3dgen 返回真实自包含 GLB、来源图哈希、provider/model、原请求与任务身份。生成候选不得声称具备精确尺寸、安装接口或 CAD 源码。单图 tripo-image-v3.1 与四视图模型是不同请求形状,模型级 verified 门独立生效。
|
|
15
|
+
3. structure 通过独立 `structure.adapt` 步骤调用 `cad.build@2`(purpose=appearance_adaptation):宿主解析同一 creation 已结算的 meshInvocationId、原件精确几何、单位/坐标系、不可变接口及 attachment 证据。模型只能引用冻结的 baselineCandidateId/designDigest、targetOccurrenceId、meshInvocationId 和原 inputRevision/manifestDigest,不能提供文件路径、安装约束或“接口已通过”声明。旧 cad.build@1 的 Build123d 流程保持独立。
|
|
16
|
+
4. 把新外观实际装回同一整机候选,分别报告尺寸、原生比例、保护区域、接口、碰撞、间隙与壁厚的 pass/conflict/unknown。对齐坐标、compatibilityKey 或画面像装上了均不证明接口匹配。缺真实接口约束时只允许预览;不得缩放精确原件来凑齐。
|
|
17
|
+
5. appearance 查看同版整机实际渲染并做视觉审查。workspace 使用既有候选/采用事务;旧基线、错误目标、unknown 适用工程项不得进入 CAD 确认或导出捷径。网格预览、fit 通过与已采用是三个事实。
|
|
18
|
+
|
|
19
|
+
## 第一版工程适配范围
|
|
20
|
+
|
|
21
|
+
`analytic_carrier_faceted_decoration` 保留完整原始解析 CAD 承载件(包括孔、安装座和接口面),把单个封闭流形装饰网格转换为 faceted BRep,通过有界连接体及真实 OCCT 布尔并集连接。不得把整机 AABB、坐标对齐或 compatibilityKey 当作接口验收,也不能缩放原解析件来迁就 Tripo。
|
|
22
|
+
|
|
23
|
+
必须重新读回最终 STEP,检查原承载件及实际接口曲面保留、连续实体连接、同装配静态碰撞/间隙以及约定的平面分离插入路径。渲染与导出均来自该同版成品;原 Tripo GLB 只是来源,不能冒充最终实体。初始配置请求2000面,实际输入需满足宿主明确的面数上限及流形条件;超限、多组件、非流形、无可信 attachment 或其他不支持情况均保持未通过,不作补造。
|
|
24
|
+
|
|
25
|
+
这仅覆盖约定几何适配;强度、制造工艺、打印及实物装配仍未验证。mesh.generate 的预览候选不可直接采用;须经 structure.adapt 的同版工程产物和后续真实视觉审查,再由既有事务采用。
|
|
26
|
+
|
|
27
|
+
## 恢复和验收
|
|
28
|
+
|
|
29
|
+
工具不新建预算、回执或采用系统。宿主使用原 F4 invocation/upstreamKey、租约与预算,provider 恢复走 [host generation API](../../abelien-3dgen/references/host-api.md)。刷新、取消、超时和模糊结果只能查原任务;拿不到 provider ID 时保持 unknown,不重新生成。
|
|
30
|
+
|
|
31
|
+
真实验收需同一 draft/version 下的目标件选择、单图编辑、实际 provider 结果和结算、同整机预览、几何/视觉结论与采用回读。HTTP fixtures、本地 GLB 或技能说明不构成产品验收。
|
|
@@ -30,10 +30,13 @@ compatibility:
|
|
|
30
30
|
需求只用以下结构;source 区分用户事实与系统假设:
|
|
31
31
|
|
|
32
32
|
```ts
|
|
33
|
+
type Envelope =
|
|
34
|
+
| {shape: "box", max_mm: [number, number, number], min_mm?: [number, number, number], wall_mm?: {minimum: number, estimate?: number}}
|
|
35
|
+
| {shape: "cylinder", max_diameter_mm: number, max_height_mm: number, min_diameter_mm?: number, min_height_mm?: number, wall_mm?: {minimum: number, estimate?: number}};
|
|
33
36
|
Requirements = {
|
|
34
37
|
context?: {text: string, source: "user" | "system"};
|
|
35
38
|
reaction: {id: string, text: string, source: "user" | "system"}[];
|
|
36
|
-
limits: {id: string, text: string, source: "user" | "system", strength: "hard" | "preference"}[];
|
|
39
|
+
limits: {id: string, text: string, source: "user" | "system", strength: "hard" | "preference", envelope?: Envelope}[];
|
|
37
40
|
}
|
|
38
41
|
```
|
|
39
42
|
|
|
@@ -43,7 +46,7 @@ Requirements = {
|
|
|
43
46
|
|
|
44
47
|
纯咨询只返回 note;明确修改同时夹带咨询时,result 只承载明确修改,note 回应咨询或说明待核对的能力。“这样可以,颜色改蓝”先交修改,不把前半句当成新版本的确认。“改回去”必须能定位旧内容;否则保留现状并澄清对象。
|
|
45
48
|
|
|
46
|
-
**2. 整理需求。** context 记录落点、使用者与影响选择的场景;reaction 记录完整的事件或状态与反应,保留对象、方向、持续性和时间;limits 记录明确限制或偏好,数值保留单位。明确的尺寸、运动方式等限制写 limits
|
|
49
|
+
**2. 整理需求。** context 记录落点、使用者与影响选择的场景;reaction 记录完整的事件或状态与反应,保留对象、方向、持续性和时间;limits 记录明确限制或偏好,数值保留单位。明确的尺寸、运动方式等限制写 limits;可确定为盒体或圆柱的尺寸另附同一 limits 项的 envelope,原 text/id/source/strength 保留,不从正文猜造工程数据。几点、话术和停止规则写进对应 reaction;颜色、灯光等外观配置按用户表达登记到 limits,保留偏好或硬要求的原强度。
|
|
47
50
|
|
|
48
51
|
展示摘要由当前需求生成:一个放在 [落点] 的东西,在 [事件或状态] 下,做出 [可感知的输出]。摘要不代替完整 reaction;标签只用于分类初筛。
|
|
49
52
|
|
|
@@ -65,7 +68,9 @@ ID 是不透明标识,不能按数组位置重新编号;新 ID 不得与现
|
|
|
65
68
|
|
|
66
69
|
能说明拟探索行为的落点、事件/状态和输出,且不存在阻塞这次选择的歧义,就可以交给总控推进。其他行为与未定项继续保留,不生成全局 ready / complete 状态;“可以开始搜索”不代表需求已满足。一个假设会改变用户明确要求、替换保留的底座或突破已有授权时,不能用“可撤销”绕过用户决定。
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
尺寸是推导变量,从落点与行为提出可见、可修改的范围;可提出 source=system 的可见估算,必须说明依据且不得冒充用户确认、实测或图片比例尺。envelope 所有尺寸用毫米有限正数,范围最小值不超过最大值;精确值可令 min=max。只使用匹配 shape 的字段,不混用盒体和圆柱字段。wall_mm.minimum 是明确保证的非负壁厚下界,未提供 wall_mm 时按 0;estimate 不得小于 minimum,只参与风险提示,不硬拒绝。保证下界扣除后最大腔体各轴必须为正;范围较小端或估算壁厚不可行仍保留供风险评审。只有 source=user 且 strength=hard 的最大范围具有硬筛选权限;系统估算和偏好不能硬拒绝。未知形状不填写 envelope,不编造 box。
|
|
72
|
+
|
|
73
|
+
没有明确尺寸要求,不主动索要尺寸;“必须塞进这个格子”缺尺寸且挡住选型时才问。品类默认和器件分类用输入中的数据,不在 skill 固化型号、价位或默认值表。工程资料缺失交给总控补充,不让用户提供接口、料号或测试证明。
|
|
69
74
|
|
|
70
75
|
只提出现在必须由用户决定的问题:闭环歧义、落点歧义或挡住当前选择的硬要求缺值/相互矛盾;缺少 PRD 字段本身不是问题。提问前能在 note 中说明:不回答会卡住哪个选择、为何现有事实和授权不足以代定。默认一题,相互独立才合并,最多三题;每题最多三个实质选项,表达遵循已注入的共用规则。用户意图选项可以来自原话支持的不同解释,不冒充已有硬件或工程可行候选。
|
|
71
76
|
|
|
@@ -90,3 +90,12 @@
|
|
|
90
90
|
## 验证方式
|
|
91
91
|
|
|
92
92
|
将上述当前需求和输出分别保存为临时 JSON,运行 `scripts/check_result.py`;检查 ID、完整快照及差异。单元测试只覆盖这些确定性约束。模型验收还要核对原话完整性、假设依据、提问必要性、卡片上下文与真实授权;本文件未宣称模型已经通过这些案例。
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## 结构化尺寸与可见估算
|
|
96
|
+
|
|
97
|
+
用户说“必须放进长 100、宽 80、高 60 毫米的格子”,保留原 limit 的 id/text/source=user/strength=hard,并附 `envelope: {"shape":"box","max_mm":[100,80,60]}`。未保证壁厚时不补一个经验下界;省略 wall_mm 相当于保证下界 0。用户修改高度时延续同一 ID 并保留其他要求。
|
|
98
|
+
|
|
99
|
+
仅从“桌面设备”提出大约 80–120 毫米的探索范围时,应是 source=system 的建议并在 note 写明依据与可修改性;不能让用户确认方案、图片看起来相似或系统默认升级为实测或 user。估算壁厚比容器大只说明估算需要修订,不能用来硬剔除器件。
|
|
100
|
+
|
|
101
|
+
圆柱外形使用 `{"shape":"cylinder","max_diameter_mm":80,"max_height_mm":120,"wall_mm":{"minimum":0,"estimate":2}}`。保留原话来源,不混入 box.max_mm。完整快照和 ID 差分均须保留 envelope;脚本 valid 只证明字段合法,不证明可装下或得到授权。
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import json
|
|
6
|
+
import math
|
|
6
7
|
import sys
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
@@ -41,6 +42,55 @@ def text_field(value: dict, key: str, path: str, errors: list) -> None:
|
|
|
41
42
|
issue(errors, f"{path}.{key}", "必须是非空字符串")
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
def finite_number(value: Any, minimum: float = 0, inclusive: bool = False) -> bool:
|
|
46
|
+
return (type(value) in (int, float) and math.isfinite(value)
|
|
47
|
+
and (value >= minimum if inclusive else value > minimum))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def check_envelope(value: Any, path: str, errors: list) -> None:
|
|
51
|
+
if not isinstance(value, dict) or value.get("shape") not in ("box", "cylinder"):
|
|
52
|
+
issue(errors, path, "shape 必须是 box 或 cylinder")
|
|
53
|
+
return
|
|
54
|
+
box = value["shape"] == "box"
|
|
55
|
+
required = ("shape", "max_mm") if box else ("shape", "max_diameter_mm", "max_height_mm")
|
|
56
|
+
optional = ("min_mm", "wall_mm") if box else ("min_diameter_mm", "min_height_mm", "wall_mm")
|
|
57
|
+
object_fields(value, required, optional, path, errors)
|
|
58
|
+
maxima = []
|
|
59
|
+
if box:
|
|
60
|
+
for key in ("max_mm", "min_mm"):
|
|
61
|
+
if key == "min_mm" and key not in value:
|
|
62
|
+
continue
|
|
63
|
+
item = value.get(key)
|
|
64
|
+
if not isinstance(item, list) or len(item) != 3 or not all(finite_number(n) for n in item):
|
|
65
|
+
issue(errors, f"{path}.{key}", "必须是三个有限正数毫米值")
|
|
66
|
+
elif key == "max_mm":
|
|
67
|
+
maxima = item
|
|
68
|
+
elif maxima and any(n > m for n, m in zip(item, maxima)):
|
|
69
|
+
issue(errors, f"{path}.{key}", "最小值不得超过最大值")
|
|
70
|
+
else:
|
|
71
|
+
for axis in ("diameter", "height"):
|
|
72
|
+
maximum = value.get(f"max_{axis}_mm")
|
|
73
|
+
minimum = value.get(f"min_{axis}_mm")
|
|
74
|
+
if not finite_number(maximum):
|
|
75
|
+
issue(errors, path, "圆柱直径和高度必须是有限正数毫米值")
|
|
76
|
+
else:
|
|
77
|
+
maxima.append(maximum)
|
|
78
|
+
if f"min_{axis}_mm" in value and (not finite_number(minimum) or (finite_number(maximum) and minimum > maximum)):
|
|
79
|
+
issue(errors, path, "圆柱范围必须为有限正数且最小值不超过最大值")
|
|
80
|
+
if "wall_mm" in value:
|
|
81
|
+
wall = value["wall_mm"]
|
|
82
|
+
if not object_fields(wall, ("minimum",), ("estimate",), f"{path}.wall_mm", errors):
|
|
83
|
+
return
|
|
84
|
+
minimum, estimate = wall.get("minimum"), wall.get("estimate")
|
|
85
|
+
if not finite_number(minimum, inclusive=True):
|
|
86
|
+
issue(errors, f"{path}.wall_mm.minimum", "保证的最小壁厚必须是有限非负值")
|
|
87
|
+
else:
|
|
88
|
+
if any(n - 2 * minimum <= 0 for n in maxima):
|
|
89
|
+
issue(errors, path, "保证的最小壁厚不能消耗全部最大腔体")
|
|
90
|
+
if "estimate" in wall and (not finite_number(estimate, inclusive=True) or estimate < minimum):
|
|
91
|
+
issue(errors, f"{path}.wall_mm.estimate", "估算壁厚必须有限且不小于保证下界")
|
|
92
|
+
|
|
93
|
+
|
|
44
94
|
def check_requirements(value: Any, path: str, errors: list) -> None:
|
|
45
95
|
if not object_fields(value, ("reaction", "limits"), ("context",), path, errors):
|
|
46
96
|
return
|
|
@@ -60,7 +110,7 @@ def check_requirements(value: Any, path: str, errors: list) -> None:
|
|
|
60
110
|
fields = ("id", "text", "source") + (("strength",) if group == "limits" else ())
|
|
61
111
|
for index, entry in enumerate(entries):
|
|
62
112
|
location = f"{path}.{group}[{index}]"
|
|
63
|
-
if not object_fields(entry, fields, (), location, errors):
|
|
113
|
+
if not object_fields(entry, fields, ("envelope",) if group == "limits" else (), location, errors):
|
|
64
114
|
continue
|
|
65
115
|
text_field(entry, "id", location, errors)
|
|
66
116
|
text_field(entry, "text", location, errors)
|
|
@@ -71,6 +121,8 @@ def check_requirements(value: Any, path: str, errors: list) -> None:
|
|
|
71
121
|
seen_ids.add(entry_id)
|
|
72
122
|
if entry.get("source") not in SOURCES:
|
|
73
123
|
issue(errors, f"{location}.source", "必须是 user 或 system")
|
|
124
|
+
if group == "limits" and "envelope" in entry:
|
|
125
|
+
check_envelope(entry["envelope"], f"{location}.envelope", errors)
|
|
74
126
|
if group == "limits" and entry.get("strength") not in STRENGTHS:
|
|
75
127
|
issue(errors, f"{location}.strength", "必须是 hard 或 preference")
|
|
76
128
|
|
|
@@ -29,6 +29,46 @@ def current_requirements():
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class ContractTests(unittest.TestCase):
|
|
32
|
+
def test_envelope_is_preserved_in_id_based_diff(self):
|
|
33
|
+
current = current_requirements()
|
|
34
|
+
current["limits"][0]["envelope"] = {"shape": "box", "max_mm": [100, 80, 60]}
|
|
35
|
+
result = copy.deepcopy(current)
|
|
36
|
+
result["limits"][0]["envelope"]["max_mm"][0] = 90
|
|
37
|
+
report = validate({"result": result}, current)
|
|
38
|
+
self.assertTrue(report["valid"], report["errors"])
|
|
39
|
+
self.assertEqual(report["changes"][0]["before"]["envelope"]["max_mm"][0], 100)
|
|
40
|
+
self.assertEqual(report["changes"][0]["after"]["envelope"]["max_mm"][0], 90)
|
|
41
|
+
self.assertEqual(result["limits"][0]["text"], current["limits"][0]["text"])
|
|
42
|
+
|
|
43
|
+
def test_envelope_invalid_values_and_unknown_fields_fail(self):
|
|
44
|
+
cases = [
|
|
45
|
+
{"shape": "box", "max_mm": [0, 1, 1]},
|
|
46
|
+
{"shape": "box", "max_mm": [True, 1, 1]},
|
|
47
|
+
{"shape": "box", "max_mm": [float("inf"), 1, 1]},
|
|
48
|
+
{"shape": "box", "max_mm": [10, 10, 10], "min_mm": [11, 1, 1]},
|
|
49
|
+
{"shape": "box", "max_mm": [10, 10, 10], "wall_mm": {"minimum": 5}},
|
|
50
|
+
{"shape": "box", "max_mm": [10, 10, 10], "wall_mm": {"minimum": 2, "estimate": 1}},
|
|
51
|
+
{"shape": "box", "max_mm": [10, 10, 10], "max_height_mm": 1},
|
|
52
|
+
{"shape": "cylinder", "max_diameter_mm": 10, "max_height_mm": 10, "min_height_mm": 11},
|
|
53
|
+
{"shape": "cylinder", "max_diameter_mm": 10, "max_height_mm": 10, "wall_mm": {"estimate": 1}},
|
|
54
|
+
{"shape": "unknown"},
|
|
55
|
+
]
|
|
56
|
+
for envelope in cases:
|
|
57
|
+
with self.subTest(envelope=envelope):
|
|
58
|
+
result = current_requirements()
|
|
59
|
+
result["limits"][0]["envelope"] = envelope
|
|
60
|
+
self.assertFalse(validate({"result": result})["valid"])
|
|
61
|
+
|
|
62
|
+
def test_envelope_range_maximum_wall_and_visible_system_estimate(self):
|
|
63
|
+
for envelope in [
|
|
64
|
+
{"shape": "box", "max_mm": [10, 10, 10], "min_mm": [1, 1, 1], "wall_mm": {"minimum": 1, "estimate": 20}},
|
|
65
|
+
{"shape": "cylinder", "max_diameter_mm": 10, "max_height_mm": 20, "wall_mm": {"minimum": 0}},
|
|
66
|
+
]:
|
|
67
|
+
result = current_requirements()
|
|
68
|
+
result["limits"][0].update(source="system", envelope=envelope)
|
|
69
|
+
self.assertFalse(validate({"result": result})["valid"])
|
|
70
|
+
self.assertTrue(validate({"result": result, "note": "可修改的系统估算,仍需审查"})["valid"])
|
|
71
|
+
|
|
32
72
|
def test_partial_requirements_and_questions_can_coexist(self):
|
|
33
73
|
proposal = {
|
|
34
74
|
"result": {"reaction": [], "limits": []},
|
|
@@ -25,6 +25,8 @@ compatibility:
|
|
|
25
25
|
|
|
26
26
|
图片、链接或“做同款”仍走同一入口,不自动开启另一条整机还原流程。改轮廓、颜色、材质、表面语言或参考匹配时调用 [appearance](../abelien-appearance/SKILL.md);空间约束、布局、装配、Build123d 或几何检查调用 [structure](../abelien-structure/SKILL.md)。外壳形状同时涉及两者时,由 appearance 定义目标、structure 实现并回传实际渲染、appearance 做视觉验收。只把必须由用户决定的差异交回用户,不让内部视觉审查成为额外的必答关卡。
|
|
27
27
|
|
|
28
|
+
用户要让局部外观件更有趣时,调用 appearance 的[局部自由造型流程](../abelien-appearance/references/local-component-edit.md)。默认由当前部件图或视角渲染生成单张修改参考,上传图可选,四视图只作增强;整机和未选部件保留。先解析真实目标和基线,不能把一句“改成猫头”变成全产品重新生成。
|
|
29
|
+
|
|
28
30
|
## 交互邀请与成果呈现
|
|
29
31
|
|
|
30
32
|
交互细化与收尾采用 workspace §4.1–§4.2;下列话术补充共用表达规则的起点开口,具体时机以 workspace 为准。共用表达文件是上游同步快照,本包特有的引导在此维护。
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
问题选项写用户能感知的后果,基于真实候选;有适用图片则配图,无图用文字,始终允许自由描述。提问的必要性、授权范围和发布规则由主控及对应 skill 处理。
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
当前作品说明来源、沿用内容、待改/待核验内容。2026-10-07:默认创作不先选整机;首次展示本作品外观时可作一次简洁邀请,普通修改轮不重复。用户明确二创时再说明所选来源:
|
|
27
27
|
|
|
28
|
-
>
|
|
28
|
+
> 先按 [你的描述或参考图] 做这个外观方向,[哪些已形成预览、哪些还待核验]。你可以继续改样子或功能;硬件会逐项核对,预览还不代表方案完成。
|
|
29
29
|
|
|
30
30
|
这段是非阻塞邀请,不是待答卡、不占问题额度、不等待回答;界面可提供“改样子 / 改提醒方式 / 加功能 / 就这样”。用户改过外观后展示修改结果,仅在当前确认前提满足时提示“可以确认方案”。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: abelien-hardware
|
|
3
|
-
description: 处理 Abelien 硬件分类、TOS Wiki
|
|
3
|
+
description: 处理 Abelien 硬件分类、TOS Wiki 实时检索、英文 Volc 搜索加来源直读的来源绑定补选(Prismer 默认关闭)、整机复用、型号语义选型和已有方案局部修订。用于需求已澄清后的硬件阶段,不用于需求澄清或外观3D生成。
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility:
|
|
6
6
|
- claude-code
|
|
@@ -48,8 +48,18 @@ TOS 读取所需变量是 `TOS_REGION`、`TOS_ENDPOINT`、`TOS_BUCKET`、`TOS_AC
|
|
|
48
48
|
|
|
49
49
|
## Online supplemental candidates
|
|
50
50
|
|
|
51
|
-
Read [web evidence contract](references/web-evidence-contract.md). Library recall is followed by suitability review. Every zero-candidate or all-unsuitable slot uses `hardware.web-search` to persist
|
|
51
|
+
Read [web evidence contract](references/web-evidence-contract.md). Library recall is followed by suitability review. Every zero-candidate or all-unsuitable slot uses `hardware.web-search` to persist English Volc search results (Count ≤ 5) and independently read source documents (Prismer query and reads are off by default, so sources are read directly), then `hardware.web-merge` consumes that evidence. Keep all user requirements and quantities; catalog absence does not justify dropping rechargeable batteries or wireless charging. Suitable library slots do zero network I/O and are omitted from unresolved_slots; never use empty query/count0 placeholders. All suitable returns []. The trusted host runs the shared [pure search-plan preflight](references/web-evidence-contract.md#trusted-host-search-plan-preflight) before committing the review, so invalid plans remain correctable at the same pause. Extract each physical part separately; missing dimensions, authority and engineering remain unknown, `envelope_only` never means true component CAD. The same formal `hardware.select` consumes `merge-hardware-web.resource.id`; external choices cannot become verified/adopted/delivered. System reuse is optional when it is a good match; concept visuals can be shown first.
|
|
52
52
|
|
|
53
53
|
The trusted host alone supplies PRISMER_API_KEY/WEB_SEARCH_API_KEY. Only a stored definitive primary denial, successful empty query or unsent missing Key permits the fixed fallback; sent unknown/ambiguous or storage failures block it. Safe direct HTML/plaintext follows each original source denial; PDF/image without Parse remains unavailable. Do not choose providers/endpoints or change an unknown invocation. Full schema and original runId-only recovery are in the web evidence contract.
|
|
54
54
|
|
|
55
55
|
An ordinary failure of an authorized public read is recorded as an unavailable source without a response or usable text; other original sources/slots continue. Its saved failure is reused without another GET. This does not recover historical failed/unknown evidence or permit resending paid requests.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## 结构化尺寸粗筛
|
|
59
|
+
|
|
60
|
+
数据库尺寸快照是尺寸编辑源;生成与迁移见 [search-index-format](references/search-index-format.md)。搜索先对全部分类索引做保守筛选再取 Top-K,unknown/estimate 保留。使用候选的 dimension_check 和来源,不从正文猜 dimensions,不把 clear、占用比例、用户确认或 CAD 可用性当作工程验证;all_indexed_candidates_rejected 只指已评估分类。risk 候选排在可装/未知候选之后且不占 Top-K 名额:有满足功能与硬约束的非 risk 候选时选它;只有都不满足才选 risk 件,并在 selection_reason 写明可能放不下的原因。正式选择会重算尺寸,incompatible 必须换候选;只有 decision_required(用户硬尺寸上限+实测/datasheet 尺寸,且没有轴对齐装法)返回待确认,交用户在换件与放宽尺寸间取舍;unknown 与其他 risk 只随 dimension_check 提示,不阻塞。
|
|
61
|
+
|
|
62
|
+
## 电气兼容
|
|
63
|
+
|
|
64
|
+
多型号选型的电源与接口由确定性检查先判(星形拓扑,主控为中心),规则见 [selection-service-contract](../abelien-hardware-selection/references/selection-service-contract.md#电气兼容与兼容报告)。可信数据证明总线缺失或数量不足时必须替换一侧;`ELECTRICAL_SUPPLY_GAP` 表示要补电源或稳压件;结构、散热、软件只作提示。不要从正文猜电压或接口,电气数据只来自候选的 `electrical` 字段。
|
|
65
|
+
|