@entrorove/skill 0.1.34 → 0.1.36
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/CAPABILITY-MAP.md +21 -8
- package/CHANGELOG.md +12 -2
- package/SKILL.md +17 -3
- package/automatic-feedback-client.mjs +40 -3
- package/cli.mjs +13 -3
- package/cloud-watch-client.mjs +5 -1
- package/ide-presentation.mjs +9 -2
- package/local-autocoord17-controller.mjs +6 -4
- package/local-cli.mjs +2 -0
- package/local-peer-status.mjs +6 -0
- package/local-swarm-contracts.mjs +3 -0
- package/local-swarm.mjs +32 -1
- package/manifest.json +1 -1
- package/native-client.mjs +61 -0
- package/native-notification.mjs +76 -0
- package/package.json +5 -2
- package/release-state.json +20 -17
- package/release.mjs +4 -0
- package/releases.json +14 -2
- package/remote-client.mjs +9 -4
- package/remote-transport.mjs +3 -1
- package/scope-runner-client.mjs +2 -1
package/CAPABILITY-MAP.md
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
# 能力对账图(WP-02 / AC-20)
|
|
2
2
|
|
|
3
|
-
## 2026-10-
|
|
3
|
+
## 2026-10-13 接入:协作观测、原生规划与任务通知
|
|
4
|
+
|
|
5
|
+
| 能力 | 正常公开入口 | 已执行验证与边界 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| 协作状态 | `local peer-status` | 原签名授权和原协调绑定;等待、活动、过期、跨任务拒绝;只读查询不写日志、不发写入权限 |
|
|
8
|
+
| Aimlock 规划 | `native aimlock brain-plan/brain-check/brain-validate/brain-status` | 原账号计划入库,检查范围与依赖;重启后按原 request 恢复,跨账号及自造计划拒绝;只认证规划,不认证业务交付 |
|
|
9
|
+
| 八域直接调用 | `native <domain> <operation>` | 真实 discovery → schema 校验 → 单次原生调用 → 原回执核验;八域帮助和状态观察遵循服务端合同,不要求不存在的评价 |
|
|
10
|
+
| IDE 系统通知 | `notifications configure/status/test` | 默认关闭;账号绑定、状态去重、凭据变更失效;仅真实结果、失败与需要确认事件;不推送推广 |
|
|
11
|
+
| 网页与手机通知 | 登录后的任务通知按钮 | 同一加密 Web Push 协议,账号与设备隔离;真实任务事务队列、持久重试、失效注销与到期清理;手机可见投递仍需设备本人授权与验收 |
|
|
12
|
+
| 失败后接续 | `scope-connect` 返回 `nextInput`;`feedback <requestId> [generation]` | 范围门禁使用同一排序路径集;新执行保存新一代回执,原失败回执不可改,默认只读最新一代 |
|
|
13
|
+
|
|
14
|
+
本地公开 schema 已增加到 **101 项**(原 100 项加 `peer-status`)。Aimlock 运行时由 7.0.44 更新为 **7.0.45**,公开操作由 20 项增加到 24 项;其余七域的运行时版本保持原值。下方历史表保留原对账日期与当时验收边界,不表示当前仍缺上述入口。发布件与生产部署需独立核验,不能从仓库测试推断。
|
|
15
|
+
|
|
16
|
+
## 2026-10-12 基线:八域 IDE 调用链
|
|
4
17
|
|
|
5
18
|
逐项对照旧版八个技能的已安装说明、入口和服务实现,本次补回以下共同保证。后面的 AC-20~24 保留其历史基线与当时测试边界。
|
|
6
19
|
|
|
@@ -10,13 +23,13 @@
|
|
|
10
23
|
| 实际进度必须进入 IDE | `cloud-watch` 订阅本人任务 SSE,输出独立 IDE 事件;调用方必须把公开消息和摘要展示到对话进度 |
|
|
11
24
|
| 执行提示与推广可见 | `智能执行中,请勿关闭!` 与服务端选中的真实推广独立输出;保留推广 HTTPS 链接,按事件 ID 去重 |
|
|
12
25
|
| 断线恢复原任务 | `cloud-watch` 带原 `taskId/afterId` 只读续接;`status` 与 `feedback` 查询原请求,不重发业务 POST |
|
|
13
|
-
|
|
|
26
|
+
| 调用结束立即确认评价回传 | 调用完成事务保存本人原 request 的评价与结果摘要;CLI 核验后立即回传原回执身份,服务端返回 IDE 接收确认,不重复写评价或计用量;只读恢复保留原执行代次 |
|
|
14
27
|
| 未验证不可记好评 | intake、模型摘要、reported、不完整证据无评分;阻断、失败、P0/P1 不产生好评;发现和状态查询不生成新评价 |
|
|
15
28
|
| 实际 IDE 与原调用绑定 | `ENTROROVE_IDE` 或宿主检测写入原调用标签;每个 OS 用户继续复用受限凭据存储 |
|
|
16
29
|
| 最新官方技能说明生效 | 新调用使用 `npx --yes --prefer-online @entrorove/skill@latest`;安装更新后重新读取 managed `SKILL.md` |
|
|
17
30
|
| 调用次数与沙箱次数分开 | 普通调用与旧式 UUID 记录技能用量;`cloud-task` 才创建沙箱。所有查询和评价按账号权限过滤 |
|
|
18
31
|
|
|
19
|
-
|
|
32
|
+
自动技术评价是私有调用记录。公开用户评价仍须使用本人原话、真实评分和明确公开同意。原生系统通知与手机推送已按 2026-10-13 的公开入口接入;通知权限由用户开启。确认继续遵循 IDE 的明确问题与答案。思考详情只展示服务端公开的规划摘要。
|
|
20
33
|
|
|
21
34
|
本表把旧八域的远端公开操作与当前本地执行器分开统计。旧版 `SKILL.md` 只规定入口、状态和边界,没有逐域列出操作名;远端操作名采用随包锁定的 v2 runtime catalog 交叉核对,本地操作名直接来自 `local-contracts.mjs` 的 `LOCAL_OPERATION_SCHEMAS`。两个分母不能合并。
|
|
22
35
|
|
|
@@ -43,16 +56,16 @@ Entrorove 只治理通过技能入口执行的动作:路径、通行证、快
|
|
|
43
56
|
|
|
44
57
|
| 域 | 旧 runtime 公开操作 | 当前映射 | 语义边界 |
|
|
45
58
|
| --- | --- | --- | --- |
|
|
46
|
-
| aimlock | `capabilities`, `help`, `intake`, `classify`, `scope-contract`, `skill-route`, `propose-nodes`, `accept-nodes`, `snapshot-plan`, `mutate-gate`, `continuity-check`, `interrupt`, `keep-alive`, `delivery-doc`, `validate-json`, `snapshot-verify`, `run-status`, `chain-plan`, `chain-status`, `feedback` | discovery、任务路由、快照、门控、状态、取消和交付证据 | `brain-plan/check/validate/status`
|
|
59
|
+
| aimlock | `capabilities`, `help`, `intake`, `classify`, `scope-contract`, `skill-route`, `propose-nodes`, `accept-nodes`, `snapshot-plan`, `mutate-gate`, `continuity-check`, `interrupt`, `keep-alive`, `delivery-doc`, `validate-json`, `snapshot-verify`, `run-status`, `chain-plan`, `chain-status`, `feedback` | discovery、任务路由、快照、门控、状态、取消和交付证据 | 新增 `brain-plan/check/validate/status` 已接入 Aimlock 7.0.45,见上方最新接入表 |
|
|
47
60
|
| blueprint | `capabilities`, `help`, `intake`, `validate`, `compile-inline`, `acceptance-report` | `blueprint` runtime;验收证据由调用方提供,不能自升 `verified` | `acceptance-report` 不是自报绿灯 |
|
|
48
61
|
| calctool | `capabilities`, `help`, `intake`, `validate`, `compile-inline` | `calctool` runtime;公式执行和 final-gate 在本地 Decimal 层 | `research` 是 pipeline 内部阶段,不是独立公开操作 |
|
|
49
62
|
| swarm | `capabilities`, `help`, `intake`, `org-chart`, `blueprint-bridge`, `dispatch`, `claim`, `report`, `accept`, `swarm-status`, `traffic-light`, `security-check`, `validate-json`, `heartbeat`, `reclaim` | 服务端路由、租约和 `reported`/`verified` 状态 | 本地 peer/coordinate 是统一本地层,不能冒充旧 swarm 远端 |
|
|
50
63
|
| validator | `capabilities`, `help`, `intake`, `plan`, `validate-structure`, `security-scan`, `compliance-audit`, `functional-verify`, `verdict`;local-runner:`sandbox-run`, `fuzz-input`, `perf-benchmark`, `intrusive-test` | pure validator 与受控 runner 分开 | 生产验证需独立回执;`verdict` 不能把 reported 变绿 |
|
|
51
64
|
| mergeguard | `capabilities`, `help`, `intake`, `resolve-propose`, `ruleguard-scan`, `ruleguard-compile`;local-runner:`branch-create`, `branch-list`, `branch-switch`, `diff-report`, `preflight`, `merge-verified`, `rollback`, `ledger-query` | pure mergeguard 与本地合并闸门分开 | 虚拟分支不会创建 Git 分支/worktree |
|
|
52
65
|
| archguard | `capabilities`, `help`, `intake`, `contract-create`, `contract-get`, `contract-update`, `template-list`, `checkpoint`, `rules-scan`, `complexity-report`, `drift-status`, `ledger-query` | `archguard-*` 本地合同、快照、范围和账本 | 本地写入仍受路径和基线绑定 |
|
|
53
|
-
| confirm-protocol | `capabilities`, `help`, `interaction-request`, `interaction-answer`, `chat-render`, `memory-set`, `memory-list`, `memory-clear`, `batch-request`, `audit-query` | 纯协议操作 |
|
|
66
|
+
| confirm-protocol | `capabilities`, `help`, `interaction-request`, `interaction-answer`, `chat-render`, `memory-set`, `memory-list`, `memory-clear`, `batch-request`, `audit-query` | 纯协议操作 | 通知由独立账号与设备适配层接入;高风险交互默认不批准、不记忆、不批量 |
|
|
54
67
|
|
|
55
|
-
##
|
|
68
|
+
## 本地历史基线(2026-10-07,`LOCAL_OPERATION_SCHEMAS` 100 项)
|
|
56
69
|
|
|
57
70
|
`scope-connect <absolute repositoryRoot>` 是 CLI 编排入口,不改变这 100 项本地操作。它串联远端 `scope-runner-prepare/register/challenge/attest` 与已有真实快照、签名检查,为原账号与任务提交范围证据;不授予执行证据签名或写入权限。全部远端操作以实时 discovery 为准。
|
|
58
71
|
|
|
@@ -85,11 +98,11 @@ Entrorove 只治理通过技能入口执行的动作:路径、通行证、快
|
|
|
85
98
|
|
|
86
99
|
| 疑点 | 结论 | 证据与理由 |
|
|
87
100
|
| --- | --- | --- |
|
|
88
|
-
| `peer-status` |
|
|
101
|
+
| `peer-status` | **已接入并验证** | 公开 schema/CLI 经 `local-peer-status.mjs` 查询原授权;`peer-status-public.test.mjs` 与 `item17-autocoord-behavior.test.mjs` 通过。 |
|
|
89
102
|
| `interrupt` | **已实现(仓库)** | `assets/aimlock-runtime-v7.0.44.mjs` operation catalog 与 policy 均有;发布件需 clean-install 后复测。 |
|
|
90
103
|
| `delivery-doc` | **已实现(仓库)** | 同一 aimlock runtime/source 有 operation、schema、entry;发布面保持未验证。 |
|
|
91
104
|
| calctool `research` | **有意不独立公开(理由)** | `calctool-runtime-v7.0.43.mjs` 将其作为 pipeline 内部 partition kind;它没有独立请求合同,拆成公开 operation 会重复 intake/验证边界。若需求改为独立调用,再开变更。 |
|
|
92
|
-
| aimlock 自有 `brain plan/check/validate/status` |
|
|
105
|
+
| aimlock 自有 `brain plan/check/validate/status` | **已接入并验证** | Aimlock 7.0.45 的四个公开操作、原计划持久化与账号隔离已由真实 HTTP 和重启回归验证;规划不授予执行或交付认证。 |
|
|
93
106
|
|
|
94
107
|
## 发布与校验边界
|
|
95
108
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.36 — 2026-10-13
|
|
4
|
+
|
|
5
|
+
- 技能调用结束立即确认 IDE 评价回传,提供原账号、原调用与执行代次绑定的接收回执;完善原回执只读恢复与公开合同校验。
|
|
6
|
+
- Confirmed IDE evaluation acknowledgements immediately after skill calls, bound reception receipts to the original account, invocation and generation, and improved read-only recovery and published contract validation.
|
|
7
|
+
|
|
8
|
+
## 0.1.35 — 2026-10-13
|
|
9
|
+
|
|
10
|
+
- 新增协作状态查询、Aimlock 专用规划与八域原生调用;接入账号和设备隔离的任务通知,修复失败接续的评价回执冲突,并完善范围门禁接续。
|
|
11
|
+
- Add read-only peer status, Aimlock planning and native invocation across eight domains; support account/device-bound task notifications, preserve feedback receipts across authorized retries, and correct scope-gate continuation.
|
|
12
|
+
|
|
3
13
|
## 0.1.34 — 2026-10-12
|
|
4
14
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
15
|
+
- 完善 IDE 云端规划选择、实时执行进度与独立推广提示;服务端自动评价绑定原调用并返回可核验回执,支持只读恢复;修复 UUID 调用用量统计,保持账号隔离及沙箱/调用计数分离。
|
|
16
|
+
- Improve IDE cloud planning choices, live execution progress and separate promotion notices. Bind automatic server evaluations to their original invocations with verifiable receipts and read-only recovery; correct UUID invocation usage while keeping accounts and skill/sandbox counters separate.
|
|
7
17
|
|
|
8
18
|
## 0.1.33 — 2026-10-12
|
|
9
19
|
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: An engineering skill that understands a task, chooses a fitting wor
|
|
|
5
5
|
|
|
6
6
|
# entrorove · 熵巡
|
|
7
7
|
|
|
8
|
-
Version: 0.1.
|
|
8
|
+
Version: 0.1.36.
|
|
9
9
|
|
|
10
10
|
Read the user's goal and repository instructions first. Preserve the original task through planning, execution and verification. Use the workflow only where it helps; a small, low-risk edit can proceed directly. Never claim a reported step is a verified result.
|
|
11
11
|
|
|
@@ -53,11 +53,15 @@ For `workType: "merge"`, include `baselineRef` with the concrete branch, tag, co
|
|
|
53
53
|
|
|
54
54
|
The v2 capability map is recorded in [`CAPABILITY-MAP.md`](CAPABILITY-MAP.md). It maps every legacy domain and operation to its v2 equivalent, narrower semantics, or an explicitly documented uncovered area; adapters must not silently substitute an uncovered operation.
|
|
55
55
|
|
|
56
|
+
For an explicit domain operation, use `npx @entrorove/skill native <domain> <operation>` with its input object on stdin. The supported domains are `aimlock`, `blueprint`, `calctool`, `swarm`, `validator`, `mergeguard`, `archguard` and `confirm-protocol`. This public adapter discovers that domain's actual server schema, validates the input, invokes the registered operation once, and verifies any required automatic feedback. Discovery, help and status observations follow the server's published feedback contract. A failed gate or unknown outcome must never be replaced with another executor or reported as local success; fix the original cause and recover the original request through its read-only status.
|
|
57
|
+
|
|
58
|
+
Aimlock exposes `brain-plan`, `brain-check`, `brain-validate` and `brain-status`. Create a plan with the actual goal, scope `contract`, `requirements` and ordered `steps`; retain its returned `requestId` and `plan.digest`. Subsequent operations take only that original `planRequestId` (plus `paths` for `brain-check`); the server loads the authenticated account's stored plan. Never submit a reconstructed plan or another account's request. Planning validation checks scope, requirements and dependencies; it grants no mutation authority and never certifies business delivery.
|
|
59
|
+
|
|
56
60
|
For a returned `flowId`, continue with `{"operation":"continue","input":{"flowId":"<returned ID>","operation":"<next operation>","payload":{}}}`. Fill `payload` with the next operation's required input. Keep the same task and evidence in the IDE. For later work, `input.task.phase` may be `plan`, `coordinate`, `architecture`, `verify`, `merge` or `decision`; it remains the same skill entry point. Never treat an unknown request outcome as permission to retry a paid or mutating action; query the original request ID when the workflow supports it.
|
|
57
61
|
|
|
58
62
|
The v2 progress sequence is durable and ordered: the first runtime step reports `intake`, then the applicability `gate` is recorded and verified, then the selected contract or plan steps run. Every step can be `pending`, `running`, `reported`, `verified`, `blocked` or `failed`; only a completed verdict with trusted evidence is green.
|
|
59
63
|
|
|
60
|
-
When intake returns `workflow-gate`, connect the current repository before invoking that gate: run `npx @entrorove/skill scope-connect <absolute repositoryRoot>` with JSON `{"flowId":"<original flowId>","paths":["<existing source file>"]}` on stdin. Use the concrete existing source files in the task's scope; new files belong in the later plan's declared artifacts. The adapter enrolls an OS-private Ed25519 scope signer for this accepted account and task, answers a server challenge, copies and verifies real source files, and submits the signed facts. It sends hashes and scope metadata rather than source contents or the private key. Enrollment grants scope inspection only; execution evidence and write passes retain their separate authority checks. An operator-configured signer cannot be replaced. After a successful connection,
|
|
64
|
+
When intake returns `workflow-gate`, connect the current repository before invoking that gate: run `npx @entrorove/skill scope-connect <absolute repositoryRoot>` with JSON `{"flowId":"<original flowId>","paths":["<existing source file>"]}` on stdin. Use the concrete existing source files in the task's scope; new files belong in the later plan's declared artifacts. The adapter enrolls an OS-private Ed25519 scope signer for this accepted account and task, answers a server challenge, copies and verifies real source files, and submits the signed facts. It sends hashes and scope metadata rather than source contents or the private key. Enrollment grants scope inspection only; execution evidence and write passes retain their separate authority checks. An operator-configured signer cannot be replaced. After a successful connection, use the returned `nextInput` for `continue`, or explicitly send `payload: {"targetFiles":["<same existing source file>"]}` to the original `workflow-gate`. The connector uses `paths`; the gate uses `targetFiles`. Refresh an expired snapshot by reconnecting the same flow and path set. Show an explicit blocked result; query the original request ID after an unknown transport outcome before considering another attempt.
|
|
61
65
|
|
|
62
66
|
Scope inspection resolves aliases from actual `tsconfig.json` or `jsconfig.json`, including relative `extends`. For plugin source compiled by a host project whose config is outside the source's ancestors, include `importContexts: [{"sourcePrefix":"<plugin source directory>","configPath":"<actual host tsconfig.json>"}]` in `scope-connect` input. Inspect the real build configuration first; never invent an alias or mark an unresolved import as external. These context paths remain inside the repository, their hashes enter the signed facts, and the local task binding freezes them.
|
|
63
67
|
|
|
@@ -71,9 +75,19 @@ The local execution layer is available through `npx @entrorove/skill local <oper
|
|
|
71
75
|
|
|
72
76
|
Local CLI failures use the machine-readable `entrorove.local-error/1.0` envelope `{schemaVersion,status,code,disposition,message}` on stderr. `status` is `failed` for invalid input or computation errors and `blocked` for missing mutation, lease, snapshot or handoff authority. Formula `add` and `mul` require at least one argument; `div`, `safeDivide` and `percentOf` reject a zero denominator with `DIV_ZERO`. The formula engine declares `round` as half-away-from-zero with two decimal places by default; an engine may declare another `decimalPlaces` value from 0 through 28 in its `roundingPolicy`.
|
|
73
77
|
|
|
78
|
+
### Read-only peer status
|
|
79
|
+
|
|
80
|
+
`local peer-status` observes the original signed worker grant, task and lease, or the registered coordination task/agent/chain/intent binding. It reports only that worker's active, waiting or expired state, own blocked/ready paths and own checkpoint requests. Queries do not issue leases, change ownership, write a coordination journal or grant permission to write. Use `local capabilities` for the exact supported input variants.
|
|
81
|
+
|
|
82
|
+
### Optional task notifications
|
|
83
|
+
|
|
84
|
+
System notifications are disabled until this account explicitly enables them. `npx @entrorove/skill notifications configure` accepts only `{"enabled":true}` or `{"enabled":false}` on stdin; `notifications status` observes the account's setting and `notifications test` submits a generic test only when enabled. macOS and Linux use their existing notification service. Real IDE events notify output availability, verification, failure or required confirmation, deduplicated by original task/request and state. Promotion and keep-alive messages remain visible in the IDE and never trigger system notifications. Changing the stored account credential disables the previous account's notification setting. OS acceptance does not prove that permission allows a visible notification.
|
|
85
|
+
|
|
86
|
+
The website's signed-in bell control enables encrypted Web Push for the current account and device. Desktop browsers and supported phones use this same push protocol; iPhone requires a Home Screen web app. Ask permission only through the user's enable action. Logout clears the browser binding and subscription. Messages contain only generic task status, with no prompts, credentials, model output or promotion. Delivery retries keep the original event and device binding; expired endpoints and temporary tasks are cleaned up automatically. Never describe an unsupported platform or failed delivery as a successful notification.
|
|
87
|
+
|
|
74
88
|
### Automatic invocation evaluation and optional user reviews
|
|
75
89
|
|
|
76
|
-
Every completed or failed execution call receives a server-generated `feedback` receipt bound to the original account, request hash and result digest. Durable calls commit this evaluation in the same transaction as the result;
|
|
90
|
+
Every completed or failed execution call receives a server-generated `feedback` receipt bound to the original account, request hash and result digest. Durable calls commit this evaluation in the same transaction as the result; domain adapters return an idempotent receipt with the same HTTP response. The CLI verifies the original identity and evaluation digest, then immediately returns that exact receipt identity to the authenticated acknowledgement endpoint before finishing the call. Cloud watches do the same as soon as the original evaluation is available. A confirmed `acknowledgement` records the IDE and server reception time without changing the evaluation or counting another invocation. Preserve `receiptId`, `requestId` and `evaluation.digest`; never generate a score or comment locally. Immediately display the confirmed evaluation acknowledgement in visible IDE commentary before the task's final answer, deduplicated by receipt ID. Tool output alone is insufficient. Say “已回传” only with a confirmed acknowledgement; an upload failure must remain visible and cannot become successful feedback. Read the saved receipt with `npx @entrorove/skill feedback <original requestId>` after a connection loss; this is GET only and never repeats execution or submits another evaluation. An authorized retry before side effects keeps the original request but gets a new server execution generation and immutable receipt. Default feedback reads the latest generation; `feedback <original requestId> <generation>` reads a preserved earlier receipt. Never overwrite a failed receipt or use it as the current retry result. Discovery/status queries create no evaluations.
|
|
77
91
|
|
|
78
92
|
Only server-verified progress or a validated pure operation earns a positive automatic evaluation. Reported steps, intake, model summaries and incomplete evidence remain `pending` with no score. Blocked/failed results and P0/P1 findings cannot become positive. These are private technical evaluations; they do not create a public user review or masquerade as the user's words. If a server advertising this extension returns no valid receipt, show the missing receipt and query its original ID. Do not silently claim that evaluation was reported.
|
|
79
93
|
|
|
@@ -12,7 +12,8 @@ export function verifyAutomaticFeedback(feedback, requestId, requestHash) {
|
|
|
12
12
|
if (!feedback || feedback.schemaVersion !== 'entrorove.automatic-feedback/1.0' || feedback.stored !== true
|
|
13
13
|
|| !/^[0-9a-f-]{36}$/i.test(feedback.receiptId) || feedback.requestId !== requestId
|
|
14
14
|
|| !/^[a-f0-9]{64}$/.test(feedback.requestHash) || requestHash && feedback.requestHash !== requestHash
|
|
15
|
-
|| !/^[a-f0-9]{64}$/.test(feedback.resultDigest) ||
|
|
15
|
+
|| !/^[a-f0-9]{64}$/.test(feedback.resultDigest) || feedback.generation !== undefined
|
|
16
|
+
&& (!Number.isSafeInteger(feedback.generation) || feedback.generation < 1) || !evaluation
|
|
16
17
|
|| evaluation.schemaVersion !== 'entrorove.automatic-evaluation/1.0' || evaluation.requestId !== requestId
|
|
17
18
|
|| !['good','bad','pending'].includes(evaluation.disposition)
|
|
18
19
|
|| !['passed','failed','incomplete'].includes(evaluation.validation)
|
|
@@ -32,12 +33,48 @@ export function verifyAutomaticFeedback(feedback, requestId, requestHash) {
|
|
|
32
33
|
}
|
|
33
34
|
return feedback;
|
|
34
35
|
}
|
|
35
|
-
export async function queryAutomaticFeedback(requestId, dependencies) {
|
|
36
|
+
export async function queryAutomaticFeedback(requestId, dependencies, generation) {
|
|
36
37
|
if (typeof requestId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(requestId)) throw new Error('feedback requires its original request ID');
|
|
37
38
|
const token = await dependencies.readCredential(dependencies.home);
|
|
38
|
-
|
|
39
|
+
if (generation !== undefined && (!/^[1-9][0-9]{0,14}$/.test(generation) || !Number.isSafeInteger(Number(generation)))) throw new Error('Invalid original execution generation');
|
|
40
|
+
const response = await requestTransport(trustedOrigin(dependencies.apiOrigin()),`/api/skill/feedback/${requestId}${generation === undefined ? '' : `?generation=${generation}`}`,
|
|
39
41
|
{ headers: { authorization: `Bearer ${token}` } });
|
|
40
42
|
const result = parseTransportResponse(response,'automatic feedback');
|
|
41
43
|
if (!response.ok) throw new Error(`Automatic feedback unavailable: HTTP ${response.status}; requestId=${requestId}`);
|
|
42
44
|
return verifyAutomaticFeedback(result.feedback,requestId);
|
|
43
45
|
}
|
|
46
|
+
|
|
47
|
+
function confirmedAcknowledgement(value, original, expectedIde) {
|
|
48
|
+
verifyAutomaticFeedback(value,original.requestId,original.requestHash);
|
|
49
|
+
const ack = value.acknowledgement, generation = original.generation ?? 1;
|
|
50
|
+
if (value.receiptId !== original.receiptId || value.resultDigest !== original.resultDigest
|
|
51
|
+
|| value.evaluation.digest !== original.evaluation.digest || (value.generation ?? 1) !== generation
|
|
52
|
+
|| !ack || ack.schemaVersion !== 'entrorove.feedback-acknowledgement/1.0' || ack.stored !== true
|
|
53
|
+
|| ack.receiptId !== original.receiptId || ack.requestId !== original.requestId || ack.generation !== generation
|
|
54
|
+
|| typeof ack.ide !== 'string' || !/^[\p{L}\p{N} .+_-]{2,48}$/u.test(ack.ide)
|
|
55
|
+
|| expectedIde && ack.ide !== expectedIde || !Number.isFinite(Date.parse(ack.receivedAt))) {
|
|
56
|
+
throw new Error(`IDE feedback is not confirmed; query feedback ${original.requestId}. Do not repeat the invocation.`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function acknowledgeAutomaticFeedback(feedback, origin, token, ideHeaders) {
|
|
62
|
+
verifyAutomaticFeedback(feedback,feedback.requestId,feedback.requestHash);
|
|
63
|
+
if (feedback.acknowledgement) return confirmedAcknowledgement(feedback,feedback);
|
|
64
|
+
const route = `/api/skill/feedback/${feedback.requestId}`;
|
|
65
|
+
const headers = { authorization: `Bearer ${token}`,...ideHeaders };
|
|
66
|
+
let response;
|
|
67
|
+
try {
|
|
68
|
+
response = await requestTransport(trustedOrigin(origin),`${route}/ack`, { method: 'POST',
|
|
69
|
+
headers: { ...headers,'content-type': 'application/json' },body: JSON.stringify({ receiptId: feedback.receiptId,
|
|
70
|
+
requestHash: feedback.requestHash,resultDigest: feedback.resultDigest,evaluationDigest: feedback.evaluation.digest,
|
|
71
|
+
generation: feedback.generation ?? 1 }) });
|
|
72
|
+
} catch (cause) {
|
|
73
|
+
if (!cause.established) throw new Error(`IDE feedback was not sent; query feedback ${feedback.requestId}. Do not repeat the invocation.`);
|
|
74
|
+
// A lost acknowledgement response is recovered only by reading this
|
|
75
|
+
// immutable receipt. Neither the execution nor its acknowledgement POST is replayed.
|
|
76
|
+
response = await requestTransport(trustedOrigin(origin),`${route}?generation=${feedback.generation ?? 1}`, { headers });
|
|
77
|
+
}
|
|
78
|
+
if (!response.ok) throw new Error(`IDE feedback acknowledgement failed: HTTP ${response.status}; query feedback ${feedback.requestId}. Do not repeat the invocation.`);
|
|
79
|
+
return confirmedAcknowledgement(parseTransportResponse(response,'IDE feedback acknowledgement').feedback,feedback,ideHeaders?.['x-entrorove-ide']);
|
|
80
|
+
}
|
package/cli.mjs
CHANGED
|
@@ -8,6 +8,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import { compareVersions } from './version.mjs';
|
|
9
9
|
import { credentialPath, readCredential, writeCredential } from './local-credential.mjs';
|
|
10
10
|
import { runLocal } from './local-cli.mjs';
|
|
11
|
+
import { runNative } from './native-client.mjs';
|
|
12
|
+
import { configureNativeNotifications, notificationSettings, sendNativeNotification } from './native-notification.mjs';
|
|
11
13
|
import { runTaskMessageCli } from './task-message-cli.mjs';
|
|
12
14
|
import { runScopeConnect } from './scope-runner-client.mjs';
|
|
13
15
|
import { apiUrl, buildInvocation, invokeRemote, queryFlows, queryStatus } from './remote-client.mjs';
|
|
@@ -440,6 +442,14 @@ async function main() {
|
|
|
440
442
|
process.stdout.write(JSON.stringify({ accepted, version: agreementVersion }) + '\n');
|
|
441
443
|
} else if (command === 'invoke' && args.length === 0) {
|
|
442
444
|
await invoke(await stdinJson());
|
|
445
|
+
} else if (command === 'native' && args.length === 2) {
|
|
446
|
+
await runNative(args[0], args[1], await stdinJson(), remoteDependencies());
|
|
447
|
+
} else if (command === 'notifications' && args.length === 1) {
|
|
448
|
+
const result = args[0] === 'configure' ? await configureNativeNotifications(await stdinJson())
|
|
449
|
+
: args[0] === 'status' ? await notificationSettings()
|
|
450
|
+
: args[0] === 'test' && (await notificationSettings()).enabled ? await sendNativeNotification('test') : null;
|
|
451
|
+
if (!result) throw new Error('Use notifications configure|status|test; enable this account before sending a test');
|
|
452
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
443
453
|
} else if (command === 'scope-connect' && args.length === 1) {
|
|
444
454
|
const result = await runScopeConnect(args[0], await stdinJson(), remoteDependencies());
|
|
445
455
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
@@ -452,8 +462,8 @@ async function main() {
|
|
|
452
462
|
await runCloudStatus(await stdinJson());
|
|
453
463
|
} else if (command === 'cloud-watch' && args.length === 0) {
|
|
454
464
|
await runCloudWatch(await stdinJson());
|
|
455
|
-
} else if (command === 'feedback' && args.length
|
|
456
|
-
process.stdout.write(`${JSON.stringify(await queryAutomaticFeedback(args[0],remoteDependencies()))}\n`);
|
|
465
|
+
} else if (command === 'feedback' && [1,2].includes(args.length)) {
|
|
466
|
+
process.stdout.write(`${JSON.stringify(await queryAutomaticFeedback(args[0],remoteDependencies(),args[1]))}\n`);
|
|
457
467
|
} else if (command === 'review' && args.length === 0) {
|
|
458
468
|
await submitReview(await stdinJson());
|
|
459
469
|
} else if (command === 'status' && args.length === 1) {
|
|
@@ -475,7 +485,7 @@ async function main() {
|
|
|
475
485
|
} else if (command === 'local') {
|
|
476
486
|
await runLocal(args);
|
|
477
487
|
} else {
|
|
478
|
-
throw new Error('Usage: entrorove install | configure | agreement | invoke | scope-connect <repositoryRoot> | host-message [adapterModulePath] | cloud-task | cloud-watch | cloud-status | feedback <requestId> | status <requestId> | flows | review | local (JSON on stdin)');
|
|
488
|
+
throw new Error('Usage: entrorove install | configure | agreement | invoke | native <domain> <operation> | notifications configure|status|test | scope-connect <repositoryRoot> | host-message [adapterModulePath] | cloud-task | cloud-watch | cloud-status | feedback <requestId> [generation] | status <requestId> | flows | review | local (JSON on stdin)');
|
|
479
489
|
}
|
|
480
490
|
}
|
|
481
491
|
if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
|
package/cloud-watch-client.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { trustedOrigin } from './remote-transport.mjs';
|
|
|
2
2
|
import { consumeCloudStream } from './cloud-event-stream.mjs';
|
|
3
3
|
import { automaticFeedbackIdeEvent, cloudIdeEvent, displayIdeEvent, ideClientHeaders } from './ide-presentation.mjs';
|
|
4
4
|
import { queryCloudTask } from './cloud-task-client.mjs';
|
|
5
|
+
import { acknowledgeAutomaticFeedback } from './automatic-feedback-client.mjs';
|
|
5
6
|
|
|
6
7
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7
8
|
function inputOptions(input) {
|
|
@@ -49,7 +50,10 @@ export async function watchCloudTask(input, dependencies) {
|
|
|
49
50
|
message: '云端沙箱输出完毕;实际验证状态以任务回执为准。',separateLine: true });
|
|
50
51
|
const current = await queryCloudTask(taskId,dependencies);
|
|
51
52
|
if (current.status === 'observed') taskStatus = current.taskStatus;
|
|
52
|
-
if (current.feedback)
|
|
53
|
+
if (current.feedback) {
|
|
54
|
+
current.feedback = await acknowledgeAutomaticFeedback(current.feedback,dependencies.apiOrigin(),token,ideClientHeaders());
|
|
55
|
+
emit(automaticFeedbackIdeEvent(current.feedback));
|
|
56
|
+
}
|
|
53
57
|
emit({ schemaVersion: 'entrorove.ide-event/1.0',taskId,kind: 'status',phase: 'exit',
|
|
54
58
|
message: settled || taskStatus !== 'running' ? '云端沙箱正在退出本次进度订阅' : '云端沙箱仍在执行,下次从当前进度继续读取',separateLine: true });
|
|
55
59
|
return { schemaVersion: 'entrorove.cloud-watch/1.0',status: current.status,taskId,taskStatus,afterId,outputAvailable,
|
package/ide-presentation.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const UUID = /^[0-9a-f-]{36}$/i;
|
|
2
|
+
import { notifyIdeEvent } from './native-notification.mjs';
|
|
2
3
|
export function ideClientHeaders(environment = process.env) {
|
|
3
4
|
const ide = environment.ENTROROVE_IDE ?? (environment.CODEX_THREAD_ID ? 'Codex'
|
|
4
5
|
: environment.CURSOR_TRACE_ID ? 'Cursor' : environment.CLAUDE_CODE_ENTRYPOINT ? 'Claude Code' : 'Entrorove CLI');
|
|
@@ -8,6 +9,7 @@ export function ideClientHeaders(environment = process.env) {
|
|
|
8
9
|
export function displayIdeEvent(event, sink) {
|
|
9
10
|
if (sink) sink(event);
|
|
10
11
|
else process.stderr.write(`${event.message}\n${event.detail ? event.detail + '\n' : ''}`);
|
|
12
|
+
void notifyIdeEvent(event).catch(() => process.stderr.write('Entrorove system notification unavailable; IDE progress remains visible.\n'));
|
|
11
13
|
}
|
|
12
14
|
function text(value) {
|
|
13
15
|
return typeof value === 'string' ? value.replace(/[\u0000-\u0008\u000b-\u001f\u007f]/gu, '').slice(0,12000) : '';
|
|
@@ -25,6 +27,7 @@ export function cloudIdeEvent(value, taskId) {
|
|
|
25
27
|
else if (message === 'Task classification completed.') message = '云端沙箱思考正在输出';
|
|
26
28
|
const event = { schemaVersion: 'entrorove.ide-event/1.0',taskId,eventId: value.id,kind: value.kind,
|
|
27
29
|
state: metadata.state ?? 'running',phase,message,separateLine: true };
|
|
30
|
+
if (metadata.requiresUserInput === true || phase === 'confirm' && event.state === 'pending') event.requiresConfirmation = true;
|
|
28
31
|
if (typeof metadata.summary === 'string' && metadata.summary !== value.content
|
|
29
32
|
&& value.content !== 'Cloud model classification started.') event.detail = text(metadata.summary);
|
|
30
33
|
if (value.kind === 'notice' && metadata.url !== undefined) {
|
|
@@ -44,9 +47,12 @@ export function invocationIdeNotices(output) {
|
|
|
44
47
|
}
|
|
45
48
|
export function presentInvocationNotices(result, sink) {
|
|
46
49
|
const messages = invocationIdeNotices(result?.output);
|
|
47
|
-
if (result?.feedback) messages.push(automaticFeedbackIdeEvent(result.feedback).message);
|
|
48
50
|
for (const message of messages) displayIdeEvent({ schemaVersion: 'entrorove.ide-event/1.0',
|
|
49
51
|
requestId: result.output.requestId,kind: 'notice',message,separateLine: true },sink);
|
|
52
|
+
if (result?.feedback) {
|
|
53
|
+
const event = automaticFeedbackIdeEvent(result.feedback);
|
|
54
|
+
messages.push(event.message); displayIdeEvent(event,sink);
|
|
55
|
+
}
|
|
50
56
|
return messages.length ? { ...result,ideMessages: messages } : result;
|
|
51
57
|
}
|
|
52
58
|
export function automaticFeedbackIdeEvent(feedback) {
|
|
@@ -54,5 +60,6 @@ export function automaticFeedbackIdeEvent(feedback) {
|
|
|
54
60
|
const status = disposition === 'good' ? '当前调用验证通过' : disposition === 'bad'
|
|
55
61
|
? '当前调用被阻断或验证失败' : '交付验证尚未完成,暂不评分';
|
|
56
62
|
return { schemaVersion: 'entrorove.ide-event/1.0',requestId: feedback.requestId,receiptId: feedback.receiptId,
|
|
57
|
-
kind: 'evaluation',phase: 'feedback',
|
|
63
|
+
kind: 'evaluation',phase: 'feedback',disposition,
|
|
64
|
+
message: `${feedback.acknowledgement?.stored === true ? '本次调用评价已回传至服务器' : '服务器已保存本次调用评价'}:${status}。`,separateLine: true };
|
|
58
65
|
}
|
|
@@ -67,15 +67,17 @@ export async function coordinateLocalPeer17(rawInput) {
|
|
|
67
67
|
return scanIntent17(fresh.state, current, intent, await physicalPeers17(current));
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
|
-
export async function statusLocalPeer17(rawInput) {
|
|
70
|
+
export async function statusLocalPeer17(rawInput, options = {}) {
|
|
71
71
|
const input = input17(rawInput, ['intentId']); await storageBinding(input);
|
|
72
|
-
|
|
72
|
+
const observe = async loaded => {
|
|
73
73
|
const task = coordTask17(loaded.state, input), intent = loaded.state.intents.find(value => value.intentId === input.intentId && value.taskId === task.taskId);
|
|
74
74
|
if (!intent) coordFail17('ENTROROVE_COORD_INTENT_BINDING', 'peer status must name its original task intent');
|
|
75
75
|
await coordNative17(task.material, false);
|
|
76
76
|
const view = scanIntent17(loaded.state, task, intent, await physicalPeers17(task));
|
|
77
|
-
return { ...view,
|
|
78
|
-
|
|
77
|
+
return { ...view, ...(options.readOnly ? { readOnly: true, sequence: loaded.sequence, head: loaded.head } : {}),
|
|
78
|
+
spawnRequest: loaded.state.spawnRequests.find(value => value.spawnRequestId === view.spawnRequestId) ?? null };
|
|
79
|
+
};
|
|
80
|
+
return options.readOnly ? observe(await readCoordination17(input)) : withCoordination17(input, 'peer-status', observe);
|
|
79
81
|
}
|
|
80
82
|
export async function completeLocalPeer17(rawInput) {
|
|
81
83
|
const input = input17(rawInput, ['intentId']); await storageBinding(input);
|
package/local-cli.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { withLocalInvocation } from './local-invocations.mjs';
|
|
2
|
+
import { readLocalPeerStatus } from './local-peer-status.mjs';
|
|
2
3
|
import { peerCoordinate, spawnBind, completePeer, verifyCoordinationLease } from './local-swarm.mjs';
|
|
3
4
|
import { attestMutationSnapshot14 } from './local-mutation-binding.mjs';
|
|
4
5
|
import { recordMutationDenied14 } from './local-mutation-audit.mjs';
|
|
@@ -42,6 +43,7 @@ const operations = {
|
|
|
42
43
|
'budget-extend': budgetExtend, 'peer-coordinate': peerCoordinate, coordinate: peerCoordinate,
|
|
43
44
|
'spawn-bind': spawnBind, 'peer-complete': completePeer, complete: completePeer,
|
|
44
45
|
'lease-verify': verifyCoordinationLease,
|
|
46
|
+
'peer-status': readLocalPeerStatus,
|
|
45
47
|
'evidence-sign': signEvidence, 'evidence-verify': verifyEvidence, 'merge-verified': mergeVerified,
|
|
46
48
|
rollback, 'merge-freeze': prepareMergeArtifacts, 'merge-preflight': produceMergeArchguardPreflight,
|
|
47
49
|
'merge-journal-head': produceMergeJournalHead, 'final-gate': finalGate,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { peerStatus } from './local-swarm.mjs';
|
|
2
|
+
import { statusLocalPeer17 } from './local-autocoord17-controller.mjs';
|
|
3
|
+
|
|
4
|
+
export function readLocalPeerStatus(input) {
|
|
5
|
+
return Object.hasOwn(input, 'coordinationHome') ? statusLocalPeer17(input, { readOnly: true }) : peerStatus(input);
|
|
6
|
+
}
|
|
@@ -33,9 +33,12 @@ const spawn = object({ ...common, priority, parentWorkerId: nullable(text) }, [.
|
|
|
33
33
|
const complete = object({ ...common, status: enumeration(['completed', 'failed', 'blocked']),
|
|
34
34
|
approvalPath: nullable(text), signerConfigPath: nullable(text) }, required);
|
|
35
35
|
const verify = object(common, [...required, 'paths']);
|
|
36
|
+
const peerQuery = object({ ...common, intentId: sha256, priority, workerCount: integer(0, 50) }, required);
|
|
37
|
+
const coordinationQuery = exact({ repositoryRoot: text, coordinationHome: text, taskId: id, agentId: id, chainId: id, intentId: id });
|
|
36
38
|
export const SWARM_LOCAL_OPERATION_SCHEMAS = Object.freeze({
|
|
37
39
|
'peer-coordinate': coordinate, coordinate, 'spawn-bind': spawn,
|
|
38
40
|
'peer-complete': complete, complete, 'lease-verify': verify,
|
|
41
|
+
'peer-status': { oneOf: [peerQuery, coordinationQuery] },
|
|
39
42
|
});
|
|
40
43
|
// Capture the authority before verification; the gate must verify this exact
|
|
41
44
|
// binding before signing it into the existing pass. This never issues a grant.
|
package/local-swarm.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { withStateLock } from './local-state-lock.mjs';
|
|
|
8
8
|
import { withTaskHandoffIssueFence, withTaskHandoffReadFence } from './local-task-handoff-fence.mjs';
|
|
9
9
|
import { captureSwarmCompletion, captureSwarmOwnershipCompletions, revalidateSwarmCompletion } from './local-swarm-completion.mjs';
|
|
10
10
|
import { claimSwarmWriteIntent, closeSwarmWriteIntent, readSwarmCheckpointRequests,
|
|
11
|
-
registerSwarmWriteIntent, validateSwarmWriteIntents } from './local-swarm-priority.mjs';
|
|
11
|
+
readSwarmWriteIntentHistory, registerSwarmWriteIntent, validateSwarmWriteIntents } from './local-swarm-priority.mjs';
|
|
12
12
|
|
|
13
13
|
function fail(code, message, details = {}) { throw Object.assign(new Error(message), { code, ...details }); }
|
|
14
14
|
function freshState(authority) {
|
|
@@ -173,6 +173,37 @@ async function operate(rawInput, operation) {
|
|
|
173
173
|
}));
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
|
+
|
|
177
|
+
export async function peerStatus(input) {
|
|
178
|
+
const original = await readSwarmCloudAuthority(input, 'report');
|
|
179
|
+
const storage = await swarmStorage(original, 'read');
|
|
180
|
+
return withStateLock(storage.ownershipFile, () => withStateLock(storage.stateFile, async () => {
|
|
181
|
+
const authority = await readSwarmCloudAuthority(input, 'report');
|
|
182
|
+
const ownership = await validateSwarmOwnership(await readSwarmOwnership(storage, authority, 'read'), authority);
|
|
183
|
+
const { index, verified } = await readSwarmWriteIntentHistory(ownership, authority);
|
|
184
|
+
const state = validateStoredWorkers(await readSwarmJson(storage.stateFile, null), authority);
|
|
185
|
+
const intentId = sha256(canonical(input.delegationGrant));
|
|
186
|
+
if (input.intentId !== undefined && input.intentId !== intentId) fail('ENTROROVE_SWARM_INTENT_BINDING', 'status must query the original signed intent');
|
|
187
|
+
const intent = index.intents.find(value => value.intentId === intentId);
|
|
188
|
+
if (!intent || !verified.has(intentId)) fail('ENTROROVE_SWARM_INTENT_BINDING', 'the original peer intent has not been coordinated');
|
|
189
|
+
const worker = state.workers.find(value => value.workerId === authority.grant.workerId && value.leaseId === authority.lease.leaseId);
|
|
190
|
+
if (worker && worker.grantDigest !== intentId) fail('ENTROROVE_SWARM_LEASE_INVALID', 'worker no longer matches the original intent');
|
|
191
|
+
const expired = Date.parse(intent.expiresAt) <= Date.now();
|
|
192
|
+
const conflicting = ownership.records.filter(value => value.grantDigest !== intentId);
|
|
193
|
+
const blockedPaths = intent.paths.filter(target => conflicting.some(value => value.paths.some(candidate =>
|
|
194
|
+
candidate === target || candidate.startsWith(`${target}/`) || target.startsWith(`${candidate}/`))));
|
|
195
|
+
const checkpoints = index.checkpointRequests.filter(value => value.targetGrantDigest === intentId);
|
|
196
|
+
await readSwarmCloudAuthority(input, 'report');
|
|
197
|
+
return { schemaVersion: 'entrorove.local-execution/1.0', status: 'reported', readOnly: true,
|
|
198
|
+
flowId: authority.plan.flowId, taskId: authority.grant.taskId, workerId: authority.grant.workerId,
|
|
199
|
+
leaseId: authority.lease.leaseId, intentId, intentStatus: intent.status,
|
|
200
|
+
executionStatus: expired && worker?.status === 'active' ? 'expired' : worker?.status ?? (expired ? 'expired' : 'waiting'), expiresAt: intent.expiresAt,
|
|
201
|
+
priority: intent.priority, paths: [...intent.paths], blockedPaths,
|
|
202
|
+
readyPaths: intent.paths.filter(target => !blockedPaths.includes(target)),
|
|
203
|
+
checkpointRequests: checkpoints.map(value => ({ requestId: value.requestId, paths: value.paths, reason: value.reason })),
|
|
204
|
+
leaseActive: !expired && worker?.status === 'active', writeAuthorized: false, taskCompletionVerified: false };
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
176
207
|
export async function peerCoordinate(input) { return operate(input, 'coordinate'); }
|
|
177
208
|
export async function spawnBind(input) { return operate(input, 'spawn'); }
|
|
178
209
|
export async function completePeer(input) { return operate(input, 'complete'); }
|
package/manifest.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Ajv from 'ajv';
|
|
2
|
+
import { canonical, sha256 } from './local-integrity.mjs';
|
|
3
|
+
import { postInvocation } from './remote-client.mjs';
|
|
4
|
+
import { presentInvocationNotices } from './ide-presentation.mjs';
|
|
5
|
+
import { trustedOrigin } from './remote-transport.mjs';
|
|
6
|
+
|
|
7
|
+
const domains = new Set(['aimlock', 'blueprint', 'calctool', 'swarm', 'validator', 'mergeguard', 'archguard', 'confirm-protocol']);
|
|
8
|
+
const runtimeCodes = { aimlock: 'R3mQ8kWpXn', blueprint: 'wvz6zmRWmX', calctool: 'KKyA6xljUX', swarm: 'zj7fTPVh4p',
|
|
9
|
+
validator: 'Xx9ZkQmW3p', mergeguard: 'Mm7GnPqR2v', archguard: 'Ag4Ch8Rd2K', 'confirm-protocol': 'Cf8Pr7Tm2Q' };
|
|
10
|
+
export function buildNativeInvocation(domain, operation, input, origin = 'https://entrorove.com') {
|
|
11
|
+
if (!domains.has(domain) || typeof operation !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(operation)
|
|
12
|
+
|| !input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Invalid native domain, operation or input');
|
|
13
|
+
const requestId = sha256(`${domain}:${operation}:${canonical(input)}`);
|
|
14
|
+
const body = { schemaVersion: `${domain}.skill.request/${domain === 'aimlock' ? '1.1' : '1.0'}`, requestId, operation, input };
|
|
15
|
+
const runtimeCode = runtimeCodes[domain];
|
|
16
|
+
const transport = { flowId: `native:${runtimeCode}`, operation,
|
|
17
|
+
payload: { schemaVersion: 'entrorove.native-request-transport/1.0', runtimeCode, request: body } };
|
|
18
|
+
return { url: `${trustedOrigin(origin)}/api/skills/${domain}/invoke`, requestId, body,
|
|
19
|
+
feedbackHashes: [sha256('continue' + canonical(transport)), sha256(operation + canonical(input))] };
|
|
20
|
+
}
|
|
21
|
+
function discoveredContract(output, operation) {
|
|
22
|
+
const payload = output.output ?? output;
|
|
23
|
+
const capabilities = payload.capabilities ?? payload;
|
|
24
|
+
const operations = capabilities.operations ?? capabilities.operationStatus?.implementedPure;
|
|
25
|
+
const schemas = payload.operationSchemas ?? capabilities.operationSchemas;
|
|
26
|
+
const schema = Array.isArray(schemas) ? schemas.find(value => value.operation === operation) : schemas?.[operation];
|
|
27
|
+
if (!operations?.includes(operation) || !(schema?.input ?? schema?.inputSchema)) throw new Error(`Native capability does not support ${operation}`);
|
|
28
|
+
return schema.input ?? schema.inputSchema;
|
|
29
|
+
}
|
|
30
|
+
export async function invokeNativeResult(domain, operation, input, dependencies) {
|
|
31
|
+
const { home } = dependencies;
|
|
32
|
+
await dependencies.install();
|
|
33
|
+
if (!await dependencies.agreementAccepted(home)) throw Object.assign(new Error('Entrorove agreement requires explicit acceptance'),
|
|
34
|
+
{ interaction: dependencies.agreementRequired(home) });
|
|
35
|
+
const call = buildNativeInvocation(domain, operation, input, dependencies.apiOrigin());
|
|
36
|
+
const token = await dependencies.readCredential(home);
|
|
37
|
+
let feedbackRequired = operation !== 'capabilities';
|
|
38
|
+
if (operation !== 'capabilities') {
|
|
39
|
+
const discovery = await postInvocation(buildNativeInvocation(domain, 'capabilities', {}, dependencies.apiOrigin()), token, home);
|
|
40
|
+
if (!discovery.response.ok || discovery.result.output.status !== 'succeeded') throw new Error('Native capability discovery failed');
|
|
41
|
+
const found = discovery.result.output;
|
|
42
|
+
const extension = (found.capabilities ?? found).responseExtensions;
|
|
43
|
+
if (extension?.automaticFeedback === 'entrorove.automatic-feedback/1.0') feedbackRequired = !extension.automaticFeedbackObservations.includes(operation);
|
|
44
|
+
// JSON Schema permits object keywords without an adjacent type keyword.
|
|
45
|
+
// Validate their actual semantics rather than rejecting the published
|
|
46
|
+
// contract through Ajv's optional strictTypes authoring convention.
|
|
47
|
+
const ajv = new Ajv({ strict: true, strictRequired: false, strictTypes: false, allErrors: true });
|
|
48
|
+
ajv.addKeyword({ keyword: 'optional', schemaType: 'boolean', valid: true });
|
|
49
|
+
ajv.addFormat('uuid', /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
50
|
+
ajv.addFormat('date-time', value => Number.isFinite(Date.parse(value)));
|
|
51
|
+
const validate = ajv.compile(discoveredContract(discovery.result.output, operation));
|
|
52
|
+
if (!validate(input)) throw new Error(`Invalid native ${operation} input: ${ajv.errorsText(validate.errors)}`);
|
|
53
|
+
}
|
|
54
|
+
return postInvocation(call, token, home, feedbackRequired);
|
|
55
|
+
}
|
|
56
|
+
export async function runNative(domain, operation, input, dependencies) {
|
|
57
|
+
const { response, result } = await invokeNativeResult(domain, operation, input, dependencies);
|
|
58
|
+
process.stdout.write(`${JSON.stringify(presentInvocationNotices(result, dependencies.onIdeEvent))}\n`);
|
|
59
|
+
if (!response.ok || ['blocked', 'failed', 'incomplete'].includes(result.output.status)) process.exitCode = 2;
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { constants, promises as fs } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { sha256 } from './local-integrity.mjs';
|
|
6
|
+
import { readCredential } from './local-credential.mjs';
|
|
7
|
+
import { withStateLock } from './local-state-lock.mjs';
|
|
8
|
+
|
|
9
|
+
const messageKinds = { confirmed: '任务需要你确认。', failed: '任务执行失败,请查看 IDE 中的状态。',
|
|
10
|
+
blocked: '任务已阻断,请查看 IDE 中的所需操作。',
|
|
11
|
+
reported: '任务输出已生成,请查看 IDE 中的结果。', verified: '任务验证已完成。', test: 'Entrorove 系统通知测试。' };
|
|
12
|
+
function controls(home) { return path.join(home, '.entrorove', 'notifications.json'); }
|
|
13
|
+
async function readControls(home) {
|
|
14
|
+
const file = controls(home);
|
|
15
|
+
let stat;
|
|
16
|
+
try { stat = await fs.lstat(file); } catch (error) { if (error.code === 'ENOENT') return null; throw error; }
|
|
17
|
+
if (!stat.isFile() || stat.isSymbolicLink() || process.getuid && stat.uid !== process.getuid()
|
|
18
|
+
|| process.platform !== 'win32' && stat.mode & 0o077) throw new Error('Notification settings must be private');
|
|
19
|
+
const handle = await fs.open(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
20
|
+
try {
|
|
21
|
+
const state = JSON.parse(await handle.readFile('utf8'));
|
|
22
|
+
if (state.schemaVersion !== 'entrorove.native-notifications/1.0' || typeof state.enabled !== 'boolean'
|
|
23
|
+
|| !/^[a-f0-9]{64}$/.test(state.accountBinding) || !Array.isArray(state.seen) || state.seen.length > 100) throw new Error('Invalid notification settings');
|
|
24
|
+
return state;
|
|
25
|
+
} finally { await handle.close(); }
|
|
26
|
+
}
|
|
27
|
+
async function saveControls(home, state) {
|
|
28
|
+
const file = controls(home), temp = `${file}.tmp-${process.pid}`;
|
|
29
|
+
await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
30
|
+
try { await fs.writeFile(temp, JSON.stringify(state) + '\n', { flag: 'wx', mode: 0o600 }); await fs.rename(temp, file); }
|
|
31
|
+
finally { await fs.rm(temp, { force: true }); }
|
|
32
|
+
}
|
|
33
|
+
export async function notificationSettings(home = os.homedir()) {
|
|
34
|
+
const state = await readControls(home), binding = sha256(await readCredential(home));
|
|
35
|
+
return { enabled: state?.enabled === true && state.accountBinding === binding,
|
|
36
|
+
platform: process.platform, accountBound: Boolean(state && state.accountBinding === binding) };
|
|
37
|
+
}
|
|
38
|
+
export async function configureNativeNotifications(input, home = os.homedir()) {
|
|
39
|
+
if (!input || Object.keys(input).length !== 1 || typeof input.enabled !== 'boolean') throw new Error('Expected {enabled:boolean}');
|
|
40
|
+
const state = { schemaVersion: 'entrorove.native-notifications/1.0', enabled: input.enabled,
|
|
41
|
+
accountBinding: sha256(await readCredential(home)), seen: [] };
|
|
42
|
+
await withStateLock(controls(home), () => saveControls(home, state));
|
|
43
|
+
return notificationSettings(home);
|
|
44
|
+
}
|
|
45
|
+
export async function sendNativeNotification(kind, { platform = process.platform, execute = execFile } = {}) {
|
|
46
|
+
const message = messageKinds[kind];
|
|
47
|
+
if (!message) throw new Error('Invalid notification kind');
|
|
48
|
+
let file, args;
|
|
49
|
+
if (platform === 'darwin') {
|
|
50
|
+
file = '/usr/bin/osascript';
|
|
51
|
+
args = ['-e', 'on run argv\ndisplay notification (item 1 of argv) with title "Entrorove"\nend run', message];
|
|
52
|
+
} else if (platform === 'linux') { file = 'notify-send'; args = ['--app-name=Entrorove', 'Entrorove', message]; }
|
|
53
|
+
else throw new Error('Native notifications require macOS or a Linux notification service; this platform is unsupported');
|
|
54
|
+
await new Promise((resolve, reject) => execute(file, args, { timeout: 5000, windowsHide: true }, error => {
|
|
55
|
+
if (error) reject(new Error('System notification command failed; check OS notification permissions'));
|
|
56
|
+
else resolve();
|
|
57
|
+
}));
|
|
58
|
+
return { submitted: true, displayVerified: false };
|
|
59
|
+
}
|
|
60
|
+
export async function notifyIdeEvent(event, { home = os.homedir(), send = sendNativeNotification } = {}) {
|
|
61
|
+
const kind = event.requiresConfirmation && event.kind !== 'notice' ? 'confirmed'
|
|
62
|
+
: event.kind === 'evaluation' ? event.disposition === 'bad' ? 'failed' : null
|
|
63
|
+
: event.state === 'blocked' ? event.requiresConfirmation ? 'confirmed' : 'blocked'
|
|
64
|
+
: event.phase === 'workflow' && ['reported', 'verified', 'failed'].includes(event.state) ? event.state : null;
|
|
65
|
+
if (!kind || event.kind === 'notice') return { submitted: false };
|
|
66
|
+
const id = event.taskId ?? event.requestId;
|
|
67
|
+
if (typeof id !== 'string' || !/^[A-Za-z0-9._:-]{1,128}$/.test(id)) return { submitted: false };
|
|
68
|
+
return withStateLock(controls(home), async () => {
|
|
69
|
+
const state = await readControls(home);
|
|
70
|
+
if (!state?.enabled || state.accountBinding !== sha256(await readCredential(home))) return { submitted: false };
|
|
71
|
+
const key = sha256(`${id}:${kind}`);
|
|
72
|
+
if (state.seen.includes(key)) return { submitted: false, duplicate: true };
|
|
73
|
+
state.seen = [...state.seen.slice(-99), key]; await saveControls(home, state);
|
|
74
|
+
return send(kind);
|
|
75
|
+
});
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"entrorove": "cli.mjs"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"local-swarm-state.mjs",
|
|
25
25
|
"local-swarm-storage.mjs",
|
|
26
26
|
"local-swarm.mjs",
|
|
27
|
+
"local-peer-status.mjs",
|
|
27
28
|
"local-autocoord17-authority.mjs",
|
|
28
29
|
"local-autocoord17-controller.mjs",
|
|
29
30
|
"local-autocoord17-peers.mjs",
|
|
@@ -189,7 +190,9 @@
|
|
|
189
190
|
"local-final-gate16-state.mjs",
|
|
190
191
|
"local-final-gate16-proof.mjs",
|
|
191
192
|
"local-final-gate16-contract.mjs",
|
|
192
|
-
"local-final-gate16-release.mjs"
|
|
193
|
+
"local-final-gate16-release.mjs",
|
|
194
|
+
"native-client.mjs",
|
|
195
|
+
"native-notification.mjs"
|
|
193
196
|
],
|
|
194
197
|
"scripts": {
|
|
195
198
|
"test": "node --test --test-concurrency=1 test/*.test.mjs",
|
package/release-state.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"digest": "
|
|
3
|
+
"version": "0.1.36",
|
|
4
|
+
"digest": "31125fc03752d64a87f994186ebf770a40966984d65a99358239ef71ab926723",
|
|
5
5
|
"files": {
|
|
6
6
|
"local-final-gate16.mjs": "b30caab08441449ad556a6e9a3cefbf91190f0b3f7370f46d2a21156375aa8e3",
|
|
7
7
|
"local-final-gate16-checks.mjs": "1dd30e9666d458d8b12ca6e4975f6f0ac8450c3ea9cbecc7401ae037f66b589a",
|
|
@@ -30,30 +30,30 @@
|
|
|
30
30
|
"local-swarm-authority.mjs": "3d4c149b82c465a81419da40e6edd93c88e68af7bc5aa09c76aac6516c4596eb",
|
|
31
31
|
"local-swarm-baseline.mjs": "e6c22f2ab0876ce43296147ae255343e37f728aac80afd4adb5e58cfc022b7d5",
|
|
32
32
|
"local-swarm-completion.mjs": "2ade4ebe7e7612a22630784c82a33b982143aa56243e7d006f2b3f96d8c5368b",
|
|
33
|
-
"local-swarm-contracts.mjs": "
|
|
33
|
+
"local-swarm-contracts.mjs": "f9fd409ab76da0a2ed0bee8a918fdd250b9368840ef0f5e797db6d1cc7dde0f6",
|
|
34
34
|
"local-swarm-mutation.mjs": "15b8d0c4c4ea5bd17b4653d84968fc02252d9ccdca2b137ec71b0f08717f90f5",
|
|
35
35
|
"local-swarm-priority.mjs": "7dc595177f6bcfadc83457e9603edcd731758bfe42e5d517e156610e2cddd732",
|
|
36
36
|
"local-swarm-state.mjs": "ff84c5f35ef3e4a0ad9887d145e404f5786ae0dc92496116699d031b98e90c6a",
|
|
37
37
|
"local-swarm-storage.mjs": "4b506481913fe22769f47a14a024a2cc4c0f1e986bb52ef606d14c7844226086",
|
|
38
|
-
"local-swarm.mjs": "
|
|
38
|
+
"local-swarm.mjs": "8bb6e22f96b7677be9cc24e7fbbd11dfea331d6d607868ebaa3399f8a519782b",
|
|
39
39
|
"local-autocoord17-authority.mjs": "86b79798f6019523b1260b819622070a5b0d39670f607da930fb6923259dff1d",
|
|
40
|
-
"local-autocoord17-controller.mjs": "
|
|
40
|
+
"local-autocoord17-controller.mjs": "ada914d07d8773e2eb1fb45a35378effd00def697eeb37f209c81834880a2e4b",
|
|
41
41
|
"local-autocoord17-peers.mjs": "103d6a6634bbd3b4b850c5db7b571e3f05736b94e59a8fd7da63741281c903a4",
|
|
42
42
|
"local-autocoord17-store.mjs": "b48847a4a40c3aeb16bc1431b6a16221fb5ffdc0a6ba5c263acf196279d80ed1",
|
|
43
43
|
"local-autocoord17-tasks.mjs": "44b9f2d326ab3060c36657685b48b50a98711644e5baf47d4e8cb72c4c45f2a7",
|
|
44
44
|
"local-autocoord17-waits.mjs": "2b0a713987822548a94e1ed3a9a891532db45383797dfc5d5bc8d1e35593cf25",
|
|
45
45
|
"local-validator-isolation17.mjs": "48e94604e28b8fddd567dfb35ff60b73fd094a5803bd49f076388b7f310f92aa",
|
|
46
46
|
"local-calctool-composition17.mjs": "ac89fcb41ab0d0d9b47c1b5696945281b0894eb064820775e36a011c76314e6a",
|
|
47
|
-
"cli.mjs": "
|
|
47
|
+
"cli.mjs": "f0a3104ef9f4431ded2a7049c08ae6435b09ed53c4cba486c2bd52db2728d999",
|
|
48
48
|
"review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
|
|
49
49
|
"version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
|
|
50
|
-
"SKILL.md": "
|
|
51
|
-
"CAPABILITY-MAP.md": "
|
|
50
|
+
"SKILL.md": "f06fd87e2008e5951c778c2e206cb42eed668d21d5c0f2a12108a0a031fde9ad",
|
|
51
|
+
"CAPABILITY-MAP.md": "40755edd3c698bbbbd4f49bc47a21c5ad9bbb8eb05e9542dd7d3a11a0254ea35",
|
|
52
52
|
"AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
|
|
53
|
-
"package.json": "
|
|
53
|
+
"package.json": "9a7f12414556bcc150d57e64881694aeddfe0b760e98454491ac5a6585ed0421",
|
|
54
54
|
"manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
|
|
55
|
-
"release.mjs": "
|
|
56
|
-
"local-cli.mjs": "
|
|
55
|
+
"release.mjs": "eca6032eae240fb90c7646e0b1509ea6b4c94c23b90aca87e4091996c5b9b5f8",
|
|
56
|
+
"local-cli.mjs": "9eca8aefcb086cdce792105f9558cc26e34a0c88befaf0a56f4c7bdbd35f0a05",
|
|
57
57
|
"local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
|
|
58
58
|
"local-execution.mjs": "cdada6df6dc8831cebb58a629b56c4b3f8e1bf9aae4e3fdc121521ba20839383",
|
|
59
59
|
"local-integrity.mjs": "4f325376e536999c3b00245f66aac9ef1ae762c33012b363c231622008f72805",
|
|
@@ -118,14 +118,17 @@
|
|
|
118
118
|
"local-scope-state.mjs": "8b600a91fc0068d7a8c0bb752ddba7ed30ada93daafb118d6cba2368fd94cb27",
|
|
119
119
|
"local-scope-workspaces.mjs": "a44c34537e5ed2dcaf193a985e5bc05957989fbb160a650f9e37ba7d66eb2de8",
|
|
120
120
|
"local-task-routing.mjs": "e26d6f1ba134ffe1da2a49b4fb6bb6dfa5617671f774a08e719d04f202cf5eca",
|
|
121
|
-
"remote-client.mjs": "
|
|
121
|
+
"remote-client.mjs": "886ca19718b1d23209807ee3ce7d336053a17c0ad2190bb71f4c96371694dff4",
|
|
122
122
|
"cloud-task-client.mjs": "ecb6124ea5e6249f31fb95cb68d13d821d2daea8768fe47ddf6dcf82fc3392cc",
|
|
123
|
-
"scope-runner-client.mjs": "
|
|
124
|
-
"
|
|
123
|
+
"scope-runner-client.mjs": "646ab4f80d4a8209e922fd45f40efd968f0744d9dfc03188b222c1a2e11e83b8",
|
|
124
|
+
"native-client.mjs": "ea4d9c687b62858dc4e8fb6bceca59bb049cba0c2762380be460821393e025db",
|
|
125
|
+
"native-notification.mjs": "f116772178140d1a2a7aac4a8e361a76543b1f47c84b3823a26a797f11a8bfbb",
|
|
126
|
+
"local-peer-status.mjs": "a971bcc69d60bb32b875ca5bb4297d948631d5d2821b812c279613ea059a8b4f",
|
|
127
|
+
"cloud-watch-client.mjs": "f9629e67d1f14f50e9fb52f65001059e07999352fb224b7c7e28590b95609a8a",
|
|
125
128
|
"cloud-event-stream.mjs": "e0eec9fc3e0613094523929634f25030df5ca5c5b5fe2c1e1a4586ab10574808",
|
|
126
|
-
"ide-presentation.mjs": "
|
|
127
|
-
"automatic-feedback-client.mjs": "
|
|
128
|
-
"remote-transport.mjs": "
|
|
129
|
+
"ide-presentation.mjs": "d8f54505cf559af98cd1a93a739817c13df3e1e68d2dff9fde90554ea14cb10c",
|
|
130
|
+
"automatic-feedback-client.mjs": "75e92f50d6fbfa4f482af36e16c9ce5dd323a02e99eb9b434dcfa3faa846a532",
|
|
131
|
+
"remote-transport.mjs": "93caaca646fca298a4e08142678b134ecc3a657e00de5b9939e2364b2db9d6c4",
|
|
129
132
|
"local-credential.mjs": "2a302a7f67b5ea8dc73453fc96ab33db349b59ca11fa9456e48ca1d9669d877c",
|
|
130
133
|
"local-invocations.mjs": "f6ac4c41c1a5ec34de358c4cca210ca8e403b3a913150a43bb55ec9be55d9998",
|
|
131
134
|
"local-task-handoff-fence.mjs": "5532a359ee29b885f7ce0cf7178f76dba0fc9245bdd92ef460982275baf0d557",
|
package/release.mjs
CHANGED
|
@@ -92,6 +92,7 @@ const sourceFiles = [
|
|
|
92
92
|
'local-scope-workspaces.mjs',
|
|
93
93
|
'local-task-routing.mjs', 'remote-client.mjs', 'cloud-task-client.mjs',
|
|
94
94
|
'scope-runner-client.mjs',
|
|
95
|
+
'native-client.mjs', 'native-notification.mjs', 'local-peer-status.mjs',
|
|
95
96
|
'cloud-watch-client.mjs', 'cloud-event-stream.mjs', 'ide-presentation.mjs', 'automatic-feedback-client.mjs',
|
|
96
97
|
'remote-transport.mjs', 'local-credential.mjs', 'local-invocations.mjs',
|
|
97
98
|
'local-task-handoff-fence.mjs',
|
|
@@ -352,6 +353,9 @@ function releaseLabels() {
|
|
|
352
353
|
'workflow-local-client.mjs': { zh: '工作流真实执行授权', en: 'workflow actual execution authorization' },
|
|
353
354
|
'workflow-local-request.mjs': { zh: '工作流原执行请求恢复', en: 'workflow original execution request recovery' },
|
|
354
355
|
'remote-client.mjs': { zh: '远端恢复客户端', en: 'remote recovery client' },
|
|
356
|
+
'native-client.mjs': { zh: '八域原生调用与评价核验', en: 'Eight-domain native invocation and evaluation verification' },
|
|
357
|
+
'native-notification.mjs': { zh: '账号独立的可选系统通知', en: 'Optional account-bound system notifications' },
|
|
358
|
+
'local-peer-status.mjs': { zh: '协作任务状态只读入口', en: 'Read-only coordination task status entry' },
|
|
355
359
|
'task-message-entry.mjs': { zh: '任务消息实际投递入口', en: 'task message host delivery entry' },
|
|
356
360
|
'task-message-cli.mjs': { zh: '任务消息命令与适配器', en: 'task message command and adapter' },
|
|
357
361
|
'cloud-task-client.mjs': { zh: '云端沙箱任务路由与逐任务选择', en: 'cloud sandbox task routing and per-task choice' },
|
package/releases.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.1.36",
|
|
4
|
+
"date": "2026-10-13",
|
|
5
|
+
"zh": "技能调用结束立即确认 IDE 评价回传,提供原账号、原调用与执行代次绑定的接收回执;完善原回执只读恢复与公开合同校验。",
|
|
6
|
+
"en": "Confirmed IDE evaluation acknowledgements immediately after skill calls, bound reception receipts to the original account, invocation and generation, and improved read-only recovery and published contract validation."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.1.35",
|
|
10
|
+
"date": "2026-10-13",
|
|
11
|
+
"zh": "新增协作状态查询、Aimlock 专用规划与八域原生调用;接入账号和设备隔离的任务通知,修复失败接续的评价回执冲突,并完善范围门禁接续。",
|
|
12
|
+
"en": "Add read-only peer status, Aimlock planning and native invocation across eight domains; support account/device-bound task notifications, preserve feedback receipts across authorized retries, and correct scope-gate continuation."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.1.34",
|
|
4
16
|
"date": "2026-10-12",
|
|
5
|
-
"zh": "
|
|
6
|
-
"en": "
|
|
17
|
+
"zh": "完善 IDE 云端规划选择、实时执行进度与独立推广提示;服务端自动评价绑定原调用并返回可核验回执,支持只读恢复;修复 UUID 调用用量统计,保持账号隔离及沙箱/调用计数分离。",
|
|
18
|
+
"en": "Improve IDE cloud planning choices, live execution progress and separate promotion notices. Bind automatic server evaluations to their original invocations with verifiable receipts and read-only recovery; correct UUID invocation usage while keeping accounts and skill/sandbox counters separate."
|
|
7
19
|
},
|
|
8
20
|
{
|
|
9
21
|
"version": "0.1.33",
|
package/remote-client.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import Ajv from 'ajv';
|
|
3
3
|
import { parseTransportResponse, requestTransport, trustedOrigin } from './remote-transport.mjs';
|
|
4
4
|
import { appendInvocation } from './local-invocations.mjs';
|
|
5
|
-
import { verifyAutomaticFeedback } from './automatic-feedback-client.mjs';
|
|
5
|
+
import { acknowledgeAutomaticFeedback, verifyAutomaticFeedback } from './automatic-feedback-client.mjs';
|
|
6
6
|
import { ideClientHeaders, presentInvocationNotices } from './ide-presentation.mjs';
|
|
7
7
|
|
|
8
8
|
export const apiUrl = 'https://entrorove.com/api/skill/invoke';
|
|
@@ -98,7 +98,7 @@ async function invokeDiscovered(data, token, { home, apiOrigin }) {
|
|
|
98
98
|
return postInvocation(call, token, home,feedbackRequired);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
async function postInvocation(call, token, home, feedbackRequired = false) {
|
|
101
|
+
export async function postInvocation(call, token, home, feedbackRequired = false) {
|
|
102
102
|
const metadata = { kind: 'remote', operation: call.body.operation, requestId: call.requestId };
|
|
103
103
|
const logged = async value => {
|
|
104
104
|
try { return await appendInvocation({ ...metadata, ...value }, home); }
|
|
@@ -107,7 +107,7 @@ async function postInvocation(call, token, home, feedbackRequired = false) {
|
|
|
107
107
|
await appendInvocation({ ...metadata, event: 'started' }, home);
|
|
108
108
|
let response; let result;
|
|
109
109
|
try {
|
|
110
|
-
response = await requestTransport(new URL(call.url).origin,
|
|
110
|
+
response = await requestTransport(new URL(call.url).origin, new URL(call.url).pathname, {
|
|
111
111
|
method: 'POST', timeoutMs: 135_000,
|
|
112
112
|
headers: { 'content-type': 'application/json', authorization: `Bearer ${token}`,...ideClientHeaders() }, body: JSON.stringify(call.body),
|
|
113
113
|
});
|
|
@@ -131,7 +131,12 @@ async function postInvocation(call, token, home, feedbackRequired = false) {
|
|
|
131
131
|
await logged({ event: 'outcome-unknown' }); throw unknownInvocation(call.requestId);
|
|
132
132
|
}
|
|
133
133
|
await logged({ event: response.ok ? 'completed' : 'rejected', usage: result.usage ?? [] });
|
|
134
|
-
if (result.feedback)
|
|
134
|
+
if (result.feedback) {
|
|
135
|
+
const expected = call.feedbackHashes ?? [call.requestId];
|
|
136
|
+
if (!expected.includes(result.feedback.requestHash)) throw new Error('Automatic feedback lost its original invocation binding');
|
|
137
|
+
verifyAutomaticFeedback(result.feedback,call.requestId,result.feedback.requestHash);
|
|
138
|
+
result.feedback = await acknowledgeAutomaticFeedback(result.feedback,new URL(call.url).origin,token,ideClientHeaders());
|
|
139
|
+
}
|
|
135
140
|
else if (feedbackRequired && !['running','pending'].includes(result.output.status)) {
|
|
136
141
|
throw new Error(`Automatic feedback receipt is missing; query feedback ${call.requestId}. Do not repeat the invocation.`);
|
|
137
142
|
}
|
package/remote-transport.mjs
CHANGED
|
@@ -57,8 +57,10 @@ export async function requestTransport(origin, pathname, options = {}) {
|
|
|
57
57
|
const trusted = trustedOrigin(origin);
|
|
58
58
|
if (typeof pathname !== 'string' || !pathname.startsWith('/api/') || pathname.includes('://')) throw new Error('Invalid Entrorove API path');
|
|
59
59
|
const url = new URL(pathname, trusted);
|
|
60
|
-
if (url.origin !== trusted || url.search || url.hash) throw new Error('Invalid Entrorove API path');
|
|
61
60
|
const configured = { timeoutMs: 15_000, method: 'GET', ...options };
|
|
61
|
+
const originalGeneration = configured.method === 'GET' && /^\/api\/skill\/feedback\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(url.pathname)
|
|
62
|
+
&& /^\?generation=[1-9][0-9]{0,14}$/.test(url.search) && Number.isSafeInteger(Number(url.searchParams.get('generation')));
|
|
63
|
+
if (url.origin !== trusted || url.hash || url.search && !originalGeneration) throw new Error('Invalid Entrorove API path');
|
|
62
64
|
if (!['GET', 'POST'].includes(configured.method)) throw new Error('Invalid Entrorove transport method');
|
|
63
65
|
for (let number = 1; number <= 3; number++) {
|
|
64
66
|
try {
|
package/scope-runner-client.mjs
CHANGED
|
@@ -84,7 +84,8 @@ async function connect(identity, directory, dependencies) {
|
|
|
84
84
|
const accepted = await remote('scope-runner-attest', { flowId: identity.flowId, attestation: observed.attestation }, dependencies);
|
|
85
85
|
return { schemaVersion: 'entrorove.scope-connection/1.0', status: 'succeeded', flowId: identity.flowId,
|
|
86
86
|
runnerId: binding.runnerId, scopeEvidence: accepted.scopeEvidence, grantsExecutionAuthority: false,
|
|
87
|
-
nextOperation: 'workflow-gate'
|
|
87
|
+
nextOperation: 'workflow-gate', nextInput: { flowId: identity.flowId, operation: 'workflow-gate',
|
|
88
|
+
payload: { targetFiles: [...identity.targetPaths] } } };
|
|
88
89
|
}
|
|
89
90
|
export async function connectScopeRunner(repositoryRoot, input, dependencies) {
|
|
90
91
|
if (!input || Object.keys(input).some(field => !['flowId', 'paths', 'importContexts'].includes(field))
|