From b959252306910f9a729b5ce0bf3f97df96d6b3b2 Mon Sep 17 00:00:00 2001 From: yi-ge Date: Sun, 25 Oct 2026 17:42:03 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E9=BD=90=20TaskBoard=20?= =?UTF-8?q?=E4=B8=8E=20S3=20=E8=AF=BB=E5=8F=96=E6=8E=A5=E5=8F=A3=E5=8F=8A?= =?UTF-8?q?=E5=8F=8C=E8=AF=AD=E6=8E=A5=E5=85=A5=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/README.md | 13 +- api/endpoint-catalog.json | 493 +- api/examples/agent-client/README.md | 37 +- api/examples/agent-client/s3.mjs | 133 + api/examples/agent-client/taskboards.mjs | 120 + api/examples/agent-client/test/s3.test.mjs | 147 + .../agent-client/test/source-s3.fixture.ts | 131 + .../test/source-taskboard.fixture.ts | 120 + .../agent-client/test/taskboards.test.mjs | 109 + api/taskboard-s3-verification-2026-10-25.md | 47 + docs/07-api/03-errors-and-troubleshooting.md | 7 + docs/07-api/07-openapi.md | 12 + docs/07-api/11-taskboards.md | 249 + docs/07-api/12-object-storage.md | 179 + docs/07-api/index.md | 2 + .../07-api/03-errors-and-troubleshooting.md | 7 + .../current/07-api/07-openapi.md | 12 + .../current/07-api/11-taskboards.md | 249 + .../current/07-api/12-object-storage.md | 179 + .../current/07-api/index.md | 2 + scripts/check-api-docs.mjs | 2 +- static/api-examples/s3.mjs | 133 + static/api-examples/taskboards.mjs | 120 + static/openapi/agent-service.json | 18328 ++++++++++++---- tsconfig.json | 2 +- 25 files changed, 16474 insertions(+), 4359 deletions(-) create mode 100644 api/examples/agent-client/s3.mjs create mode 100644 api/examples/agent-client/taskboards.mjs create mode 100644 api/examples/agent-client/test/s3.test.mjs create mode 100644 api/examples/agent-client/test/source-s3.fixture.ts create mode 100644 api/examples/agent-client/test/source-taskboard.fixture.ts create mode 100644 api/examples/agent-client/test/taskboards.test.mjs create mode 100644 api/taskboard-s3-verification-2026-10-25.md create mode 100644 docs/07-api/11-taskboards.md create mode 100644 docs/07-api/12-object-storage.md create mode 100644 i18n/en/docusaurus-plugin-content-docs/current/07-api/11-taskboards.md create mode 100644 i18n/en/docusaurus-plugin-content-docs/current/07-api/12-object-storage.md create mode 100644 static/api-examples/s3.mjs create mode 100644 static/api-examples/taskboards.mjs diff --git a/api/README.md b/api/README.md index 3785a41..6a76fb0 100644 --- a/api/README.md +++ b/api/README.md @@ -4,7 +4,7 @@ ## 公开范围 -11 组中英文页面覆盖接入认证、Agent Webhook 管理与触发、只读 Agent/Run/回执、HTTP + Socket.IO 对话、会话历史与消息、附件与媒体、模型代理、工作流与 OpenAPI 下载。40 个 HTTP 操作按服务分成三个自包含 OpenAPI 3.1 文件;对话事件和恢复规则维护在指南与可运行 Node.js 示例中。 +13 组中英文页面覆盖接入认证、Agent Webhook 管理与触发、只读 Agent/Run/回执、HTTP + Socket.IO 对话、会话历史与消息、附件与媒体、实例诊断与用量、TaskBoard 与 S3 读取、模型代理、工作流与 OpenAPI 下载。50 个 HTTP 操作按服务分成三个自包含 OpenAPI 3.1 文件;对话事件和恢复规则维护在指南与可运行 Node.js 示例中。 Webhook 所有者管理依赖握手 `capabilities.webhookManagement: true`,由可信入口选择实际工具并生成文件范围快照;旧服务仅支持已有事件。该支持条件没有被改写成未经核对的客户端发行号。现有 Write/Bash 与审批模型保留,不把配置文件宣称为不可修改的安全边界。 @@ -106,3 +106,14 @@ RunMeta五态与runtime-state HTTP四态/转交404的差异仍存在,公开文 本批新增输入配置、网络状态、聚合历史、近期工具调用和用量汇总5操作,Agent Service32、Compat3、Workflow5。新增双语实例诊断与用量页,扩展history all读取器及observe.mjs;observe维护文件与static下载逐字一致,校验器覆盖该镜像。源码ID与字段投影修复、旧索引过渡和本批定向验证见[本批记录](./observation-verification-2026-10-25.md)。 Worker两项未加入公开清单:独立隔离夹具确认其早期进度/旧结果重投影与entry游标说明仍需修复。该专项结果不扩写为跨用户泄漏或真实数据事故;见验证记录的范围说明。此前生产/搜索验收保留原始批次,不代表本批已部署。 + + +## 2026-10-25 TaskBoard 与 S3 读取扩展 + +本批固定源码 `ba6a550aeb397c377f6c3c6e620863d06d526628`、内部文档 `9fbe5f4668d223553b38ebd4e7a2a49450000a9b`,增加 TaskBoard 7 项及 S3 3 项读取操作。当前公开范围为 Agent Service 42、Compat Proxy 3、Workflow Service 5,共50操作、13组双语页面。Agent Service描述修订为1.3.0;其它两规范的来源和修订保持原事实。 + +TaskBoard 整板activity只提供handoff证据;单任务activity使用固定throughRevision的事件前缀,同时每页关联owner摘要仍为当前投影。S3连接摘要不验证凭据;link/binary错误和响应分别建模,public链接无expires_in、末页通常省略续读令牌。相关文档及可运行示例见TaskBoard与对象存储页;两个POST查询不会创建任务板或发起执行。 + +`taskboards.mjs`、`s3.mjs`及static镜像由api:check逐字核对,示例包不增加站点依赖。隔离源码fixture维护在示例test目录,必须复制到注明的源码位置后使用该仓的Vitest执行;它们不属于站点TypeScript编译。详细验证及未覆盖范围见[本批记录](./taskboard-s3-verification-2026-10-25.md)。 + +Worker投影修复单独提交源码PR,本批没有将Worker两项加入公开清单或规范。此前发布与搜索验收保留其历史基线,不代表本批已部署或收录。 diff --git a/api/endpoint-catalog.json b/api/endpoint-catalog.json index e55261d..4acde46 100644 --- a/api/endpoint-catalog.json +++ b/api/endpoint-catalog.json @@ -3,16 +3,11 @@ "reviewBaseline": { "reviewedAt": "2026-10-25", "clientRepository": "desirecore/desirecore", - "clientRevision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", - "internalDocsRevision": "f8e1005d1cdfbeae37d3374de8631307b3d94534", - "publicDocsBaseRevision": "c76753613425d236d54360b111efb708b1349131" + "clientRevision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "internalDocsRevision": "9fbe5f4668d223553b38ebd4e7a2a49450000a9b", + "publicDocsBaseRevision": "b8fc9329b935f85a20ab8bd253cf3afbbe99020b" }, "pages": [ - { - "id": "api/index", - "file": "docs/07-api/index.md", - "slug": "/api" - }, { "id": "api/connection-and-auth", "file": "docs/07-api/01-connection-and-auth.md", @@ -62,6 +57,21 @@ "id": "api/observability", "file": "docs/07-api/10-observability.md", "slug": "/api/observability" + }, + { + "id": "api/taskboards", + "file": "docs/07-api/11-taskboards.md", + "slug": "/api/taskboards" + }, + { + "id": "api/object-storage", + "file": "docs/07-api/12-object-storage.md", + "slug": "/api/object-storage" + }, + { + "id": "api/index", + "file": "docs/07-api/index.md", + "slug": "/api" } ], "endpoints": [ @@ -80,7 +90,7 @@ "resultDelivery": "Synchronous JSON metadata. A 200 response verifies this authenticated request, not every optional capability.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/client-handshake-routes.ts", "packages/agent-service/src/service-metadata.ts", @@ -120,7 +130,7 @@ "resultDelivery": "200 {accepted:true,agentId,event} acknowledges receipt, with no Run ID or delivery deduplication. Only explicit authorized SendUserMessage output is delivered to the user; final model answers are not automatically returned to the caller or added to chat.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/webhook-routes.ts", "packages/agent-service/src/routes/webhook-state.ts", @@ -161,7 +171,7 @@ "resultDelivery": "Synchronous read; live state is observational, not a query completion signal.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/agent-routes.ts", "packages/agent-service/src/agent/list-projection.ts" @@ -197,7 +207,7 @@ "resultDelivery": "Synchronous read; live state is observational, not a query completion signal.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/runs-routes.ts", "packages/agent-service/src/session/run-manager.ts" @@ -233,7 +243,7 @@ "resultDelivery": "Synchronous read; live state is observational, not a query completion signal.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/runs-routes.ts", "packages/agent-service/src/session/run-meta-read.ts" @@ -269,7 +279,7 @@ "resultDelivery": "Synchronous read; live state is observational, not a query completion signal.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/runs-routes.ts" ] @@ -304,7 +314,7 @@ "resultDelivery": "Synchronous read; live state is observational, not a query completion signal.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/runs-routes.ts", "packages/agent-service/src/receipts/reader.ts" @@ -340,7 +350,7 @@ "resultDelivery": "HTTP admission or lookup JSON; query completion and incremental messages arrive through correlated Socket.IO events.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/query-routes.ts", "packages/agent-service/src/query/client-request-registry.ts" @@ -376,7 +386,7 @@ "resultDelivery": "HTTP admission or lookup JSON; query completion and incremental messages arrive through correlated Socket.IO events.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/query-routes.ts", "packages/agent-service/src/query/client-request-registry.ts" @@ -412,7 +422,7 @@ "resultDelivery": "HTTP admission or lookup JSON; query completion and incremental messages arrive through correlated Socket.IO events.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/query-routes.ts", "packages/agent-service/src/query/client-request-registry.ts" @@ -448,7 +458,7 @@ "resultDelivery": "Synchronous configuration or explicit delivery JSON. First configuration and rotation disclose a secret once; GET does not return it.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/webhook-management-routes.ts", "packages/agent-service/src/auth/governed-endpoint-auth.ts" @@ -484,7 +494,7 @@ "resultDelivery": "Synchronous configuration or explicit delivery JSON. First configuration and rotation disclose a secret once; GET does not return it.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/webhook-management-routes.ts", "packages/agent-service/src/auth/governed-endpoint-auth.ts" @@ -520,7 +530,7 @@ "resultDelivery": "Synchronous configuration or explicit delivery JSON. First configuration and rotation disclose a secret once; GET does not return it.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/webhook-management-routes.ts", "packages/agent-service/src/auth/governed-endpoint-auth.ts" @@ -556,7 +566,7 @@ "resultDelivery": "Synchronous configuration or explicit delivery JSON. First configuration and rotation disclose a secret once; GET does not return it.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/webhook-management-routes.ts", "packages/agent-service/src/auth/governed-endpoint-auth.ts" @@ -592,7 +602,7 @@ "resultDelivery": "Synchronous configuration or explicit delivery JSON. First configuration and rotation disclose a secret once; GET does not return it.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/webhook-management-routes.ts", "packages/agent-service/src/auth/governed-endpoint-auth.ts" @@ -628,7 +638,7 @@ "resultDelivery": "Synchronous {items,total,limit,offset}; default limit50, maximum200. Items may include other authorized background deliveries.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/schedule-routes.ts", "packages/agent-service/src/auth/governed-endpoint-auth.ts" @@ -935,7 +945,7 @@ "resultDelivery": "Complete persisted attachment receipt.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/attachment-routes.ts", "packages/schemas/src/agent-service/attachments.ts", @@ -974,7 +984,7 @@ "resultDelivery": "Original file bytes with no-store and nosniff.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/attachment-routes.ts", "packages/schemas/src/agent-service/attachments.ts", @@ -1011,7 +1021,7 @@ "resultDelivery": "Direct media metadata.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/media-routes.ts", "packages/agent-service/src/media/media-store.ts", @@ -1049,7 +1059,7 @@ "resultDelivery": "Links and missing IDs; no universal success/data wrapper.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/media-routes.ts", "packages/agent-service/src/media/media-store.ts", @@ -1089,7 +1099,7 @@ "resultDelivery": "Full media bytes.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/media-routes.ts", "packages/agent-service/src/media/media-store.ts", @@ -1130,7 +1140,7 @@ "resultDelivery": "Lists ordinary user-visible history by updatedAt descending. Excludes delegated, manual and dedicated schedule/Webhook/Mention timelines. Preserves fork generations; do not concatenate branches as a deduplicated transcript. Pagination is not an immutable snapshot.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/history-routes.ts", "packages/agent-service/src/session/store.ts", @@ -1168,7 +1178,7 @@ "resultDelivery": "Checks trusted user ownership before body/image reads. Missing, cleared and unowned Runs return 404. Delegated Runs return 403 delegated_run_requires_inspect_view; use the messages inspection view instead. pendingAssistant is temporary and separate from durable messages. Returned configuration is not execution authority. Sensitive tool results may be withheld in the display projection; body references can be removed without changing the underlying execution outcome.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/history-routes.ts", "packages/agent-service/src/session/store.ts", @@ -1209,7 +1219,7 @@ "resultDelivery": "Checks ownership before content reads. Delegated Runs require view=inspect-run. total counts source rows, not returned messages; advance offset by the requested limit and stop at total, even if a page is empty. Optional live is a temporary delegated display checkpoint, not a durable message or terminal. There is no cross-page consistency revision. Sensitive tool results may be withheld in the display projection; body references can be removed without changing the underlying execution outcome.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/history-routes.ts", "packages/agent-service/src/session/store.ts", @@ -1250,7 +1260,7 @@ "resultDelivery": "Matches extracted message text case-insensitively, newest timestamp first. Includes independent manual conversations; excludes archived and delegated Runs. Default-conversation fork families select the visible history window. preferredRunId is neither a credential nor an exact-Run filter. limit/offset and their sum must be safe nonnegative integers. limit=0 returns all remaining hits and hasMore=false. Match original text before cropping previews.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/history-routes.ts", "packages/agent-service/src/session/store.ts", @@ -1289,7 +1299,7 @@ "resultDelivery": "Reads the instance's current local profile, not a caller-supplied tenant ID. Intended for the deployment owner. Returns all active matters and at most ten recent entries, not a complete transcript. No pagination. An empty overview does not establish Agent existence or absence of history.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/conversation-routes.ts", "packages/agent-service/src/conversation-memory/overview-projection.ts", @@ -1327,7 +1337,7 @@ "resultDelivery": "Reads the instance owner's local-profile conversation memory. Supports active, recent and archived source data. Underlying Run reads recheck that user and Agent ownership. Reads at most 120 source messages and sets truncated=true if more exist; projection may omit entries. No pagination or limit-override parameter. Entries are not guaranteed to have the per-Run message shape. Sensitive tool results may be withheld in the display projection; body references can be removed without changing the underlying execution outcome.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/conversation-routes.ts", "packages/agent-service/src/conversation-memory/conversation-store.ts", @@ -1367,7 +1377,7 @@ "resultDelivery": "Synchronous JSON observation; no execution request or automatic replay.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/agent-input-config-route.ts", "packages/agent-service/src/routes/agent-input-config-observation.ts", @@ -1405,7 +1415,7 @@ "resultDelivery": "Synchronous JSON observation; no execution request or automatic replay.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/history-routes.ts", "packages/agent-service/src/session/store.ts", @@ -1443,7 +1453,7 @@ "resultDelivery": "Synchronous JSON observation; no execution request or automatic replay.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/network-routes.ts", "packages/agent-service/src/connection/network-health.ts", @@ -1480,7 +1490,7 @@ "resultDelivery": "Synchronous JSON observation; no execution request or automatic replay.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/runs-routes.ts", "packages/agent-service/src/session/runs-index.ts", @@ -1519,7 +1529,7 @@ "resultDelivery": "Synchronous JSON observation; no execution request or automatic replay.", "source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", "files": [ "packages/agent-service/src/routes/runs-routes.ts", "packages/agent-service/src/session/runs-index.ts", @@ -1540,6 +1550,409 @@ "securitySchemes": [ "InstanceToken" ] + }, + { + "operationId": "listTaskBoards", + "service": "agent-service", + "method": "GET", + "path": "/api/taskboards", + "doc": "api/taskboards", + "deploymentModes": [ + "standalone-owner" + ], + "authentication": "Existing instance-owner X-Agent-Service-Token.", + "permissions": "Authenticated human user; global TaskBoard capability enabled, owner scope and current Team/Board ACL checks. Request fields do not grant actor or team authority.", + "resultDelivery": "No pagination. Can include archived boards; counts exclude archived/deleted tasks. Team and owner visibility is rechecked.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/taskboard-routes.ts", + "packages/agent-service/src/taskboard/service.ts", + "packages/agent-service/src/taskboard/policy.ts", + "packages/agent-service/src/taskboard/query-index.ts", + "packages/agent-service/src/taskboard/session-board.ts", + "packages/agent-service/src/taskboard/integrations/http-capability-port.ts", + "packages/agent-service/src/auth/instance-auth.ts", + "deploy/Caddyfile" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/taskboard.ts:taskBoardListResponseSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Fixed-source contract review; independent local HTTP example fixtures. Real route/service/store checks are recorded separately; no real Agent/model execution." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "queryTaskBoards", + "service": "agent-service", + "method": "POST", + "path": "/api/taskboards/query", + "doc": "api/taskboards", + "deploymentModes": [ + "standalone-owner" + ], + "authentication": "Existing instance-owner X-Agent-Service-Token.", + "permissions": "Authenticated human user; global TaskBoard capability enabled, owner scope and current Team/Board ACL checks. Request fields do not grant actor or team authority.", + "resultDelivery": "Read-only POST. Default pageSize 50 (1..200), updatedAt desc, active boards/tasks. Opaque nextCursor must retain filters/sort. 409 versionConflict requires discarding old pages and restarting. Claim tokens are always omitted; current liveness can change without invalidating the cursor. Missing/invisible boardIds are silently omitted.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/taskboard-routes.ts", + "packages/agent-service/src/taskboard/service.ts", + "packages/agent-service/src/taskboard/policy.ts", + "packages/agent-service/src/taskboard/query-index.ts", + "packages/agent-service/src/taskboard/session-board.ts", + "packages/agent-service/src/taskboard/integrations/http-capability-port.ts", + "packages/agent-service/src/auth/instance-auth.ts", + "deploy/Caddyfile" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/taskboard-workbench.ts:taskBoardQueryRequestSchema", + "packages/schemas/src/agent-service/taskboard-workbench.ts:taskBoardQueryResponseSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Fixed-source contract review; independent local HTTP example fixtures. Real route/service/store checks are recorded separately; no real Agent/model execution." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "getTaskBoard", + "service": "agent-service", + "method": "GET", + "path": "/api/taskboards/:id", + "doc": "api/taskboards", + "deploymentModes": [ + "standalone-owner" + ], + "authentication": "Existing instance-owner X-Agent-Service-Token.", + "permissions": "Authenticated human user; global TaskBoard capability enabled, owner scope and current Team/Board ACL checks. Request fields do not grant actor or team authority.", + "resultDelivery": "Actor-specific projection. Deleted tasks excluded; archived tasks may remain. Redacted comment bodies and private claim tokens are withheld as documented. Direct arbitrary missing/invisible Board IDs normally return 403. Authorized missing scope aliases can return exists:false.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/taskboard-routes.ts", + "packages/agent-service/src/taskboard/service.ts", + "packages/agent-service/src/taskboard/policy.ts", + "packages/agent-service/src/taskboard/query-index.ts", + "packages/agent-service/src/taskboard/session-board.ts", + "packages/agent-service/src/taskboard/integrations/http-capability-port.ts", + "packages/agent-service/src/auth/instance-auth.ts", + "deploy/Caddyfile" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/taskboard.ts:taskBoardDetailResponseSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Fixed-source contract review; independent local HTTP example fixtures. Real route/service/store checks are recorded separately; no real Agent/model execution." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "getTaskBoardTask", + "service": "agent-service", + "method": "GET", + "path": "/api/taskboards/:id/tasks/:taskId", + "doc": "api/taskboards", + "deploymentModes": [ + "standalone-owner" + ], + "authentication": "Existing instance-owner X-Agent-Service-Token.", + "permissions": "Authenticated human user; global TaskBoard capability enabled, owner scope and current Team/Board ACL checks. Request fields do not grant actor or team authority.", + "resultDelivery": "Board access check runs first. A missing/deleted task within an authorized Board returns 200 exists:false. Available actions are a current actor snapshot, not a grant.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/taskboard-routes.ts", + "packages/agent-service/src/taskboard/service.ts", + "packages/agent-service/src/taskboard/policy.ts", + "packages/agent-service/src/taskboard/query-index.ts", + "packages/agent-service/src/taskboard/session-board.ts", + "packages/agent-service/src/taskboard/integrations/http-capability-port.ts", + "packages/agent-service/src/auth/instance-auth.ts", + "deploy/Caddyfile" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/taskboard-workbench.ts:taskDirectReadResponseSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Fixed-source contract review; independent local HTTP example fixtures. Real route/service/store checks are recorded separately; no real Agent/model execution." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "listTaskBoardHandoffEvidence", + "service": "agent-service", + "method": "GET", + "path": "/api/taskboards/:id/activity", + "doc": "api/taskboards", + "deploymentModes": [ + "standalone-owner" + ], + "authentication": "Existing instance-owner X-Agent-Service-Token.", + "permissions": "Authenticated human user; global TaskBoard capability enabled, owner scope and current Team/Board ACL checks. Request fields do not grant actor or team authority.", + "resultDelivery": "Only handoff evidence; not a complete Board activity log. Optional taskId restricts visible tasks; no pagination.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/taskboard-routes.ts", + "packages/agent-service/src/taskboard/service.ts", + "packages/agent-service/src/taskboard/policy.ts", + "packages/agent-service/src/taskboard/query-index.ts", + "packages/agent-service/src/taskboard/session-board.ts", + "packages/agent-service/src/taskboard/integrations/http-capability-port.ts", + "packages/agent-service/src/auth/instance-auth.ts", + "deploy/Caddyfile" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/taskboard-workflow.ts:taskBoardHandoffEvidenceSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Fixed-source contract review; independent local HTTP example fixtures. Real route/service/store checks are recorded separately; no real Agent/model execution." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "getTaskBoardTaskActivity", + "service": "agent-service", + "method": "GET", + "path": "/api/taskboards/:id/tasks/:taskId/activity", + "doc": "api/taskboards", + "deploymentModes": [ + "standalone-owner" + ], + "authentication": "Existing instance-owner X-Agent-Service-Token.", + "permissions": "Authenticated human user; global TaskBoard capability enabled, owner scope and current Team/Board ACL checks. Request fields do not grant actor or team authority.", + "resultDelivery": "Canonical task events use Board revision continuation. First page captures throughRevision; preserve it with nextAfterRevision on all later pages. Positive afterRevision requires throughRevision. null nextAfterRevision means complete, even if items is empty earlier. pageSize bounds candidate scans, not total returned items. Owner projections are current summaries, can repeat/change on every page, and are not frozen by throughRevision.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/taskboard-routes.ts", + "packages/agent-service/src/taskboard/service.ts", + "packages/agent-service/src/taskboard/policy.ts", + "packages/agent-service/src/taskboard/query-index.ts", + "packages/agent-service/src/taskboard/session-board.ts", + "packages/agent-service/src/taskboard/integrations/http-capability-port.ts", + "packages/agent-service/src/auth/instance-auth.ts", + "deploy/Caddyfile" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/taskboard-workbench.ts:taskActivityPageRequestSchema", + "packages/schemas/src/agent-service/taskboard-workbench.ts:taskActivityResponseSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Fixed-source contract review; independent local HTTP example fixtures. Real route/service/store checks are recorded separately; no real Agent/model execution." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "resolveSessionTaskBoard", + "service": "agent-service", + "method": "POST", + "path": "/api/taskboards/session-board/resolve", + "doc": "api/taskboards", + "deploymentModes": [ + "standalone-owner" + ], + "authentication": "Existing instance-owner X-Agent-Service-Token.", + "permissions": "Authenticated human user; global TaskBoard capability enabled, owner scope and current Team/Board ACL checks. Request fields do not grant actor or team authority.", + "resultDelivery": "Read-only POST, 4 KiB body limit. Requires runId or sessionId; accepts no claimed session list. Current session with exactly one visible board wins, including archived boards; multiple current boards have no default. Otherwise choose newest unarchived board from another persisted session of the same Run. No default returns 200 exists:false. IDs do not expand current visibility.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/taskboard-routes.ts", + "packages/agent-service/src/taskboard/service.ts", + "packages/agent-service/src/taskboard/policy.ts", + "packages/agent-service/src/taskboard/query-index.ts", + "packages/agent-service/src/taskboard/session-board.ts", + "packages/agent-service/src/taskboard/integrations/http-capability-port.ts", + "packages/agent-service/src/auth/instance-auth.ts", + "deploy/Caddyfile" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/taskboard-session-board.ts:taskBoardSessionBoardResolveRequestSchema", + "packages/schemas/src/agent-service/taskboard.ts:taskBoardDetailResponseSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Fixed-source contract review; independent local HTTP example fixtures. Real route/service/store checks are recorded separately; no real Agent/model execution." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "listS3Connections", + "service": "agent-service", + "method": "GET", + "path": "/api/s3/connections", + "doc": "api/object-storage", + "deploymentModes": [ + "standalone-owner", + "standalone-external" + ], + "authentication": "X-Agent-Service-Token from the deployment administrator", + "permissions": "Current user Object Storage capability must be enabled. Uses instance-configured connections and credentials; no separate object/prefix grant. Provider list/read permission is required for object operations.", + "resultDelivery": "Configured connection summaries; an empty configuration returns an empty connections array.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/s3-routes.ts", + "packages/agent-service/src/routes/object-storage-capability-gate.ts", + "packages/agent-service/src/object-storage/index.ts", + "packages/agent-service/src/object-storage/s3-connection-resolver.ts", + "packages/agent-service/src/object-storage/s3-client.ts", + "packages/agent-service/src/capabilities/error.ts" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/s3-operations.ts:s3ConnectionSummarySchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Static source review plus source-handler fixture with synthetic capability/config/provider substitutes and Node HTTP client fixtures; no real S3 provider.", + "baselineNote": "Reviewed at ba6a550aeb397c377f6c3c6e620863d06d526628; 9 real-handler fixture checks passed with synthetic capability/config/provider substitutes. See the record for boundaries." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "listS3Objects", + "service": "agent-service", + "method": "GET", + "path": "/api/s3/list", + "doc": "api/object-storage", + "deploymentModes": [ + "standalone-owner", + "standalone-external" + ], + "authentication": "X-Agent-Service-Token from the deployment administrator", + "permissions": "Current user Object Storage capability must be enabled. Uses instance-configured connections and credentials; no separate object/prefix grant. Provider list/read permission is required for object operations.", + "resultDelivery": "One provider page. Final pages omit a missing continuation token rather than returning null.", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/s3-routes.ts", + "packages/agent-service/src/routes/object-storage-capability-gate.ts", + "packages/agent-service/src/object-storage/index.ts", + "packages/agent-service/src/object-storage/s3-connection-resolver.ts", + "packages/agent-service/src/object-storage/s3-client.ts", + "packages/agent-service/src/capabilities/error.ts" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/s3-operations.ts:s3ListResponseSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Static source review plus source-handler fixture with synthetic capability/config/provider substitutes and Node HTTP client fixtures; no real S3 provider.", + "baselineNote": "Reviewed at ba6a550aeb397c377f6c3c6e620863d06d526628; 9 real-handler fixture checks passed with synthetic capability/config/provider substitutes. See the record for boundaries." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] + }, + { + "operationId": "downloadS3Object", + "service": "agent-service", + "method": "GET", + "path": "/api/s3/download", + "doc": "api/object-storage", + "deploymentModes": [ + "standalone-owner", + "standalone-external" + ], + "authentication": "X-Agent-Service-Token from the deployment administrator", + "permissions": "Current user Object Storage capability must be enabled. Uses instance-configured connections and credentials; no separate object/prefix grant. Provider list/read permission is required for object operations.", + "resultDelivery": "Link mode returns JSON; binary mode returns arbitrary object bytes, which can themselves contain JSON or HTML. Check HTTP status before saving. Optional link metadata can be absent (including content_length for an empty object).", + "source": { + "repository": "desirecore/desirecore", + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628", + "files": [ + "packages/agent-service/src/routes/s3-routes.ts", + "packages/agent-service/src/routes/object-storage-capability-gate.ts", + "packages/agent-service/src/object-storage/index.ts", + "packages/agent-service/src/object-storage/s3-connection-resolver.ts", + "packages/agent-service/src/object-storage/s3-client.ts", + "packages/agent-service/src/capabilities/error.ts" + ] + }, + "schemaRefs": [ + "packages/schemas/src/agent-service/s3-operations.ts:s3DownloadLinkResponseSchema" + ], + "effectiveVersion": null, + "versionEvidence": null, + "verification": { + "record": "api/taskboard-s3-verification-2026-10-25.md", + "scope": "Static source review plus source-handler fixture with synthetic capability/config/provider substitutes and Node HTTP client fixtures; no real S3 provider.", + "baselineNote": "Reviewed at ba6a550aeb397c377f6c3c6e620863d06d526628; 9 real-handler fixture checks passed with synthetic capability/config/provider substitutes. See the record for boundaries." + }, + "spec": "static/openapi/agent-service.json", + "securitySchemes": [ + "InstanceToken" + ] } ] } diff --git a/api/examples/agent-client/README.md b/api/examples/agent-client/README.md index 463f662..91dcfa1 100644 --- a/api/examples/agent-client/README.md +++ b/api/examples/agent-client/README.md @@ -1,6 +1,6 @@ -# DesireCore Node conversation client +# DesireCore Node client examples -These examples call an administrator's existing standalone Agent Service using the same owner credential for HTTP and Socket.IO. Obtain its HTTPS origin, existing instance token, and target Agent ID from the administrator. The Agent needs a working model configuration and permissions appropriate for your prompt. +These examples call an administrator's existing standalone Agent Service. Obtain its HTTPS origin and existing owner token from the administrator. The conversation client also needs a target Agent ID, a working model configuration and permissions appropriate for the prompt; it uses the same owner credential for HTTP and Socket.IO. Read examples use HTTP and do not require a model call. Use Node.js 22 or newer. Install dependencies **in this directory**, independently of the documentation website: @@ -105,3 +105,36 @@ node history.mjs all --agent-id your-agent-id --limit 100 --all-pages ``` Merged history buffers one revision per attempt. A 409 discards that attempt and restarts from page zero within the configured restart budget. No rejected attempt is emitted as a successful page set. Source-only Mention IDs remain display identifiers and are never passed to Run or execution APIs. `limit=0` returns metadata with an empty page rather than all messages. Current validation is recorded in `api/observation-verification-2026-10-25.md`. + + +## TaskBoard reads + +`taskboards.mjs` uses the configured TaskBoard capability and the authenticated owner's readable scope. It requires Node.js 22+, with no npm dependencies. See [TaskBoard reads](https://docs.desirecore.com/en/api/taskboards) for request and response contracts. + +```bash +node taskboards.mjs list +node taskboards.mjs query --filter '{"statuses":["pending"]}' --page-size 50 +node taskboards.mjs board --board-id BOARD_ID +node taskboards.mjs task --board-id BOARD_ID --task-id TASK_ID +node taskboards.mjs handoff --board-id BOARD_ID +node taskboards.mjs activity --board-id BOARD_ID --task-id TASK_ID --page-size 50 +node taskboards.mjs resolve --run-id RUN_ID +``` + +Query cursors remain opaque. Activity freezes `throughRevision` across pages, which bounds the event prefix; owner summary projections are still current per page. The example buffers query/activity pages and emits only after paging succeeds. A conflict or bounded-page failure discards the buffered output; it does not retry, modify tasks or create a missing session board. Board `handoff` returns handoff evidence without activity pagination. Treat task descriptions and returned data as private. + +## Object-storage reads + +`s3.mjs` uses an installed, enabled Object Storage capability and an existing configured S3 connection. It requires Node.js 22+, with no npm dependencies or AK/SK in the calling program. See [Object-storage reads](https://docs.desirecore.com/en/api/object-storage). + +```bash +node s3.mjs connections +node s3.mjs list --connection-id CONNECTION_ID --prefix reports/ --all +node s3.mjs download --connection-id CONNECTION_ID --key reports/summary.txt +node s3.mjs download --connection-id CONNECTION_ID --key reports/summary.txt \ + --mode binary --output ./summary.txt --max-bytes 10485760 +``` + +The list command reads one page unless `--all` is specified; all-page output is streamed and can be partial on failure. It preserves provider continuation tokens and stops on missing/repeated tokens or the page cap. Link mode only prints the returned public/presigned link; it never fetches that URL or sends the instance token to it. Binary mode requests the authenticated instance directly, rejects redirects, bounds bytes and creates the explicitly selected output file without overwriting an existing file. Incomplete output from the current download is removed on failure. Object contents, keys and signed links may be private. + +Maintained `taskboards.mjs` and `s3.mjs` are byte-identical to their files under `static/api-examples/`, as checked by `pnpm api:check`. Their HTTP tests use a synthetic local server and do not prove real TaskBoard or S3 provider availability. diff --git a/api/examples/agent-client/s3.mjs b/api/examples/agent-client/s3.mjs new file mode 100644 index 0000000..d7f8e3d --- /dev/null +++ b/api/examples/agent-client/s3.mjs @@ -0,0 +1,133 @@ +#!/usr/bin/env node +// Standalone object-storage reads. Node.js 22+, no packages required. +import { open, unlink } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const HELP = `Usage: + node s3.mjs connections + node s3.mjs list [--connection-id ID] [--prefix PATH] [--delimiter /] [--max-keys 100] [--all] [--max-pages 100] + node s3.mjs download --key KEY [--connection-id ID] [--mode link] [--expires-in 3600] + node s3.mjs download --key KEY --mode binary --output FILE [--max-bytes 104857600] +Environment: DESIRECORE_URL, DESIRECORE_TOKEN, optional DESIRECORE_S3_CONNECTION_ID. +Options: --timeout-ms 10000. Remote origins must use HTTPS. +Returned links are printed, never followed; signed links can grant access. Existing output files are never overwritten. +`; + +function originFor(value) { + let url; + try { url = new URL(value); } catch { throw new Error('Set DESIRECORE_URL to the instance origin.'); } + if (url.username || url.password || url.pathname !== '/' || url.search || url.hash + || (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)))) { + throw new Error('Use an HTTPS origin without credentials, path, query or fragment; HTTP is allowed only on loopback.'); + } + return url.origin; +} +function integer(value, label, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) { + if (value === undefined) return fallback; + if (!/^[0-9]+$/.test(String(value)) || !Number.isSafeInteger(Number(value)) || Number(value) < min || Number(value) > max) throw new Error(`${label} must be a decimal integer from ${min} to ${max}.`); + return Number(value); +} +export function parseS3Args(argv) { + if (argv.includes('--help') || argv.includes('-h')) return { help: true }; + const options = { command: argv[0] }; + const flags = { '--connection-id': 'connectionId', '--prefix': 'prefix', '--delimiter': 'delimiter', '--max-keys': 'maxKeys', '--continuation-token': 'continuationToken', '--max-pages': 'maxPages', '--key': 'key', '--mode': 'mode', '--expires-in': 'expiresIn', '--output': 'output', '--max-bytes': 'maxBytes', '--timeout-ms': 'timeoutMs' }; + for (let i = 1; i < argv.length; i++) { + if (argv[i] === '--all') { if (options.all) throw new Error(HELP); options.all = true; continue; } + const key = flags[argv[i]]; + if (!key || Object.hasOwn(options, key) || argv[i + 1] === undefined || argv[i + 1].startsWith('--')) throw new Error(HELP); + options[key] = argv[++i]; + } + return options; +} +export async function runS3(input, { env = process.env, emit = value => process.stdout.write(`${JSON.stringify(value)}\n`) } = {}) { + if (input.help) { process.stdout.write(HELP); return; } + const { command } = input; + if (!['connections', 'list', 'download'].includes(command)) throw new Error(HELP); + const origin = originFor(env.DESIRECORE_URL); + if (typeof env.DESIRECORE_TOKEN !== 'string' || !env.DESIRECORE_TOKEN.trim()) throw new Error('Set DESIRECORE_TOKEN.'); + const allowed = new Set(['command', 'help', 'timeoutMs', ...(command === 'list' ? ['connectionId', 'prefix', 'delimiter', 'maxKeys', 'continuationToken', 'all', 'maxPages'] : command === 'download' ? ['connectionId', 'key', 'mode', 'expiresIn', 'output', 'maxBytes'] : [])]); + if (Object.keys(input).some(key => !allowed.has(key))) throw new Error('An option does not apply to this command.'); + const connectionId = command === 'connections' ? undefined : input.connectionId ?? env.DESIRECORE_S3_CONNECTION_ID; + if (connectionId !== undefined && (typeof connectionId !== 'string' || !connectionId)) throw new Error('Use a nonempty connection ID returned by this instance.'); + const timeoutMs = integer(input.timeoutMs, 'timeoutMs', 10_000, 1, 600_000); + const maxKeys = integer(input.maxKeys, 'maxKeys', 100, 1, 1000); + const maxPages = integer(input.maxPages, 'maxPages', 100, 1, 10000); + const expiresIn = integer(input.expiresIn, 'expiresIn', 3600, 60, 604800); + const maxBytes = integer(input.maxBytes, 'maxBytes', 104857600); + const mode = input.mode ?? 'link'; + if (command === 'download' && (typeof input.key !== 'string' || !input.key || !['link', 'binary'].includes(mode))) throw new Error('Download requires --key and mode link or binary.'); + if (command === 'download' && (mode === 'binary' ? !input.output || input.expiresIn !== undefined : input.output !== undefined || input.maxBytes !== undefined)) throw new Error('Binary mode requires --output and accepts --max-bytes; link mode accepts --expires-in.'); + if (input.maxPages !== undefined && !input.all) throw new Error('--max-pages requires --all.'); + async function request(path) { + return fetch(origin + path, { headers: { 'X-Agent-Service-Token': env.DESIRECORE_TOKEN }, redirect: 'error', signal: AbortSignal.timeout(timeoutMs) }); + } + async function json(response) { + if (!response.headers.get('content-type')?.toLowerCase().includes('application/json')) throw new Error(`HTTP ${response.status}: expected JSON from the instance API.`); + let value; + try { value = await response.json(); } catch { throw new Error(`HTTP ${response.status}: invalid JSON from the instance API.`); } + if (!response.ok) { + const code = typeof value?.code === 'string' && /^[A-Za-z0-9_-]{1,100}$/.test(value.code) ? ` ${value.code}` : ''; + throw new Error(`Object-storage read rejected: HTTP ${response.status}${code}`); + } + if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Expected an API response object.'); + return value; + } + const metadata = await json(await request('/api/client-handshake')); + if (metadata.service !== 'desirecore-agent-service' || metadata.apiProtocolVersion !== '1' || typeof metadata.instanceId !== 'string' || !metadata.instanceId) throw new Error('The handshake did not identify a compatible DesireCore instance.'); + const query = new URLSearchParams(); + if (connectionId !== undefined) query.set('connection_id', connectionId); + const suffix = () => query.size ? `?${query}` : ''; + if (command === 'connections') { + const data = await json(await request('/api/s3/connections')); + if (data.success !== true || !Array.isArray(data.connections)) throw new Error('Invalid connection summary.'); + emit({ kind: command, instanceId: metadata.instanceId, data }); + } else if (command === 'list') { + query.set('max_keys', String(maxKeys)); + for (const [key, value] of Object.entries({ prefix: input.prefix, delimiter: input.delimiter, continuation_token: input.continuationToken })) if (value !== undefined) query.set(key, value); + const seen = new Set(input.continuationToken ? [input.continuationToken] : []); + for (let page = 1; ; page++) { + const data = await json(await request('/api/s3/list' + suffix())); + if (data.success !== true || !Array.isArray(data.objects) || !Array.isArray(data.common_prefixes) || typeof data.is_truncated !== 'boolean') throw new Error('Invalid object list.'); + emit({ kind: command, instanceId: metadata.instanceId, page, data }); + if (!input.all || !data.is_truncated) break; + const next = data.next_continuation_token; + if (typeof next !== 'string' || !next || seen.has(next)) throw new Error('Missing or repeated continuation token; stop rather than loop.'); + if (page >= maxPages) throw new Error('Reached --max-pages with more results remaining; output is partial.'); + seen.add(next); query.set('continuation_token', next); + } + } else { + query.set('key', input.key); query.set('mode', mode); + if (mode === 'link') { + query.set('expires_in', String(expiresIn)); + const data = await json(await request('/api/s3/download' + suffix())); + if (data.success !== true || !['public', 'presigned'].includes(data.url_type) || typeof data.download_url !== 'string') throw new Error('Invalid download link response.'); + emit({ kind: 'download-link', instanceId: metadata.instanceId, data }); + return; + } + const response = await request('/api/s3/download' + suffix()); + if (!response.ok) { await json(response); return; } + if (!response.body) throw new Error('Binary response has no body.'); + const advertised = response.headers.get('content-length'); + if (advertised !== null && Number(advertised) > maxBytes) { await response.body.cancel(); throw new Error('Binary response exceeds --max-bytes.'); } + let file; + let bytes = 0; + try { + file = await open(resolve(input.output), 'wx', 0o600); + for await (const chunk of response.body) { + bytes += chunk.byteLength; + if (bytes > maxBytes) throw new Error('Binary response exceeds --max-bytes.'); + await file.writeFile(chunk); + } + await file.close(); file = undefined; + } catch (error) { + await response.body.cancel().catch(() => {}); + if (file) { await file.close(); await unlink(resolve(input.output)); } + throw error; + } + emit({ kind: 'download-binary', instanceId: metadata.instanceId, output: resolve(input.output), bytes, contentType: response.headers.get('content-type') }); + } +} +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + runS3(parseS3Args(process.argv.slice(2))).catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }); +} diff --git a/api/examples/agent-client/taskboards.mjs b/api/examples/agent-client/taskboards.mjs new file mode 100644 index 0000000..ef9b1ad --- /dev/null +++ b/api/examples/agent-client/taskboards.mjs @@ -0,0 +1,120 @@ +#!/usr/bin/env node +// TaskBoard reads. Node.js 22+, no packages required. Output may contain private task content. +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const HELP = `Usage: + node taskboards.mjs list + node taskboards.mjs query [--filter '{"statuses":["pending"]}'] [--sort '{"field":"updatedAt","direction":"desc"}'] [--page-size 50] + node taskboards.mjs board --board-id ID + node taskboards.mjs task --board-id ID --task-id ID + node taskboards.mjs handoff --board-id ID [--task-id ID] + node taskboards.mjs activity --board-id ID --task-id ID [--page-size 50] + node taskboards.mjs resolve --run-id ID [--session-id ID] +Environment: DESIRECORE_URL, DESIRECORE_TOKEN. +Options: --timeout-ms 10000; query/activity: --max-pages 100. +Remote origins must use HTTPS. Reads only; no automatic retry or mutation. +`; + +export function parseTaskboardsArgs(argv) { + if (argv.includes('--help') || argv.includes('-h')) return { help: true }; + const input = { command: argv[0] }; + const flags = { '--board-id': 'boardId', '--task-id': 'taskId', '--run-id': 'runId', '--session-id': 'sessionId', '--filter': 'filter', '--sort': 'sort', '--page-size': 'pageSize', '--max-pages': 'maxPages', '--timeout-ms': 'timeoutMs' }; + for (let i = 1; i < argv.length; i++) { + const key = flags[argv[i]]; + if (!key || Object.hasOwn(input, key) || !argv[i + 1] || argv[i + 1].startsWith('--')) throw new Error(HELP); + input[key] = argv[++i]; + } + return input; +} + +export async function runTaskboards(input, { env = process.env, emit = value => process.stdout.write(`${JSON.stringify(value)}\n`) } = {}) { + if (input.help) { process.stdout.write(HELP); return; } + const options = { + list: [], query: ['filter', 'sort', 'pageSize', 'maxPages'], board: ['boardId'], task: ['boardId', 'taskId'], + handoff: ['boardId', 'taskId'], activity: ['boardId', 'taskId', 'pageSize', 'maxPages'], resolve: ['runId', 'sessionId'], + }; + if (!Object.hasOwn(options, input.command)) throw new Error(HELP); + if (Object.keys(input).some(key => !['command', 'help', 'timeoutMs', ...options[input.command]].includes(key))) throw new Error('An option does not apply to this command.'); + let origin; + try { + const url = new URL(env.DESIRECORE_URL); + if (url.username || url.password || url.pathname !== '/' || url.search || url.hash || (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)))) throw new Error(); + origin = url.origin; + } catch { throw new Error('Set DESIRECORE_URL to an HTTPS origin; HTTP is allowed only on loopback.'); } + if (typeof env.DESIRECORE_TOKEN !== 'string' || !env.DESIRECORE_TOKEN.trim()) throw new Error('Set DESIRECORE_TOKEN.'); + const integer = (value, fallback, max, label) => { + if (value === undefined) return fallback; + if (!/^[1-9][0-9]*$/.test(String(value)) || !Number.isSafeInteger(Number(value)) || Number(value) > max) throw new Error(`${label} must be a positive decimal integer no greater than ${max}.`); + return Number(value); + }; + const timeoutMs = integer(input.timeoutMs, 10000, 2147483647, 'timeoutMs'); + const maxPages = integer(input.maxPages, 100, 10000, 'maxPages'); + const pageSize = integer(input.pageSize, 50, input.command === 'activity' ? 100 : 200, 'pageSize'); + const required = ['board', 'task', 'handoff', 'activity'].includes(input.command) ? ['boardId'] : []; + if (['task', 'activity'].includes(input.command)) required.push('taskId'); + for (const key of ['boardId', 'taskId']) if ((required.includes(key) && !input[key]) || (input[key] !== undefined && (typeof input[key] !== 'string' || !input[key].trim() || input[key].length > 256 || /[\/\\\u0000-\u001f]/.test(input[key]) || ['.', '..'].includes(input[key])))) throw new Error(`Use the ${key} returned by the instance.`); + for (const key of ['runId', 'sessionId']) if (input[key] !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(input[key])) throw new Error(`Invalid ${key}.`); + if (input.command === 'resolve' && !input.runId && !input.sessionId) throw new Error('resolve requires --run-id or --session-id.'); + const object = (raw, label) => { + if (raw === undefined) return undefined; + let value; + try { value = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { throw new Error(`${label} must be a JSON object.`); } + if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be a JSON object.`); + return value; + }; + const filter = object(input.filter, 'filter'), sort = object(input.sort, 'sort'); + async function read(path, body) { + const response = await fetch(origin + path, { method: body === undefined ? 'GET' : 'POST', headers: { 'X-Agent-Service-Token': env.DESIRECORE_TOKEN, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) }, body: body === undefined ? undefined : JSON.stringify(body), redirect: 'error', signal: AbortSignal.timeout(timeoutMs) }); + if (!response.headers.get('content-type')?.toLowerCase().includes('application/json')) throw new Error(`HTTP ${response.status}: expected JSON from the instance API.`); + const data = await response.json(); + if (!response.ok || data?.ok === false) { + const rawCode = data?.error?.code ?? data?.code; + const code = typeof rawCode === 'string' && /^[A-Za-z0-9_-]{1,100}$/.test(rawCode) ? ` ${rawCode}` : ''; + throw new Error(`TaskBoard read rejected: HTTP ${response.status}${code}${response.status === 409 ? '; discard these pages and restart from the first page.' : ''}`); + } + if (!data || typeof data !== 'object' || Array.isArray(data)) throw new Error('Expected a TaskBoard response object.'); + return data; + } + const metadata = await read('/api/client-handshake'); + if (metadata.service !== 'desirecore-agent-service' || metadata.apiProtocolVersion !== '1' || typeof metadata.instanceId !== 'string' || !metadata.instanceId) throw new Error('The handshake did not identify a compatible DesireCore instance.'); + const base = `/api/taskboards/${encodeURIComponent(input.boardId ?? '')}`; + let data; + if (input.command === 'query' || input.command === 'activity') { + const pages = []; + let cursor, afterRevision = 0, throughRevision; + const seen = new Set(); + for (let count = 0; ; count++) { + if (count >= maxPages) throw new Error('maxPages reached; no partial result was emitted. Increase --max-pages and restart.'); + if (input.command === 'query') { + const page = await read('/api/taskboards/query', { ...(filter === undefined ? {} : { filter }), ...(sort === undefined ? {} : { sort }), pageSize, ...(cursor === undefined ? {} : { cursor }) }); + if (page.schemaVersion !== 3 || !Array.isArray(page.items) || typeof page.hasMore !== 'boolean') throw new Error('Invalid TaskBoard query response.'); + pages.push(page); + if (!page.hasMore) break; + if (typeof page.nextCursor !== 'string' || !page.nextCursor || seen.has(page.nextCursor)) throw new Error('Missing or repeated query cursor; no partial result was emitted.'); + cursor = page.nextCursor; seen.add(cursor); + } else { + const query = new URLSearchParams({ pageSize: String(pageSize), afterRevision: String(afterRevision), ...(throughRevision === undefined ? {} : { throughRevision: String(throughRevision) }) }); + const page = await read(`${base}/tasks/${encodeURIComponent(input.taskId)}/activity?${query}`); + if (page.schemaVersion !== 1 || !Array.isArray(page.items) || !page.page || !Number.isSafeInteger(page.page.throughRevision) || page.page.throughRevision < 1 || page.page.afterRevision !== afterRevision) throw new Error('Invalid Task activity page.'); + if (throughRevision !== undefined && page.page.throughRevision !== throughRevision) throw new Error('Task activity history prefix changed; no partial result was emitted.'); + throughRevision = page.page.throughRevision; + pages.push(page); + const next = page.page.nextAfterRevision; + if (next === null) break; + if (!Number.isSafeInteger(next) || next <= afterRevision || next > throughRevision) throw new Error('Task activity page did not advance; no partial result was emitted.'); + afterRevision = next; + } + } + data = { pages }; + } else { + const paths = { list: '/api/taskboards', board: base, task: `${base}/tasks/${encodeURIComponent(input.taskId ?? '')}`, handoff: `${base}/activity${input.taskId ? `?${new URLSearchParams({ taskId: input.taskId })}` : ''}`, resolve: '/api/taskboards/session-board/resolve' }; + data = await read(paths[input.command], input.command === 'resolve' ? { ...(input.runId ? { runId: input.runId } : {}), ...(input.sessionId ? { sessionId: input.sessionId } : {}) } : undefined); + if (input.command === 'list' ? data.schemaVersion !== 3 || !Array.isArray(data.boards) : input.command === 'handoff' ? !Array.isArray(data.evidence) : data.schemaVersion !== 3 || typeof data.exists !== 'boolean') throw new Error('Invalid TaskBoard read response.'); + } + emit({ kind: input.command, instanceId: metadata.instanceId, data }); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + runTaskboards(parseTaskboardsArgs(process.argv.slice(2))).catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }); +} diff --git a/api/examples/agent-client/test/s3.test.mjs b/api/examples/agent-client/test/s3.test.mjs new file mode 100644 index 0000000..293e791 --- /dev/null +++ b/api/examples/agent-client/test/s3.test.mjs @@ -0,0 +1,147 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import http from 'node:http'; +import { once } from 'node:events'; +import { mkdtemp, readFile, writeFile, rm, readdir } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { parseS3Args, runS3 } from '../s3.mjs'; + +const handshake = { service: 'desirecore-agent-service', apiProtocolVersion: '1', instanceId: 'inst_s3_fixture', capabilities: { objectStorageCapability: true } }; +function json(res, body, status = 200) { res.writeHead(status, { 'content-type': 'application/json' }); res.end(JSON.stringify(body)); } +async function fixture(t, handler, metadata = handshake) { + const requests = []; + const server = http.createServer((req, res) => { + requests.push({ path: req.url, token: req.headers['x-agent-service-token'], method: req.method }); + if (req.url === '/api/client-handshake') json(res, metadata); + else handler(req, res); + }); + server.listen(0, '127.0.0.1'); await once(server, 'listening'); + t.after(() => { server.closeAllConnections(); server.close(); }); + return { requests, env: { DESIRECORE_URL: `http://127.0.0.1:${server.address().port}`, DESIRECORE_TOKEN: 'synthetic-s3-private-token' } }; +} +async function directory(t) { + const dir = await mkdtemp(join(tmpdir(), 'docs-s3-')); + t.after(() => rm(dir, { recursive: true, force: true })); + return dir; +} +const list = (more = false, extra = {}) => ({ success: true, objects: [], common_prefixes: [], key_count: 0, is_truncated: more, bucket: 'fixture', ...extra }); + +test('summary reads verify identity, use only GET and preserve empty connections', async t => { + const f = await fixture(t, (_req, res) => json(res, { success: true, connections: [] })); + const out = []; + await runS3({ command: 'connections' }, { env: f.env, emit: v => out.push(v) }); + assert.deepEqual(out[0].data.connections, []); + assert.equal(out[0].instanceId, handshake.instanceId); + assert.deepEqual(f.requests.map(v => v.path), ['/api/client-handshake', '/api/s3/connections']); + assert.ok(f.requests.every(v => v.method === 'GET' && v.token === f.env.DESIRECORE_TOKEN)); +}); + +test('opaque token pagination keeps query scope, encodes keys and preserves omitted final token', async t => { + const f = await fixture(t, (req, res) => { + const q = new URL(req.url, 'http://fixture').searchParams; + assert.equal(q.get('connection_id'), 'storage&one'); assert.equal(q.get('prefix'), 'reports/汉字 &'); + assert.equal(q.get('delimiter'), ''); assert.equal(q.get('max_keys'), '2'); + if (!q.has('continuation_token')) json(res, list(true, { next_continuation_token: 'x+/=&汉字' })); + else { assert.equal(q.get('continuation_token'), 'x+/=&汉字'); json(res, list()); } + }); + const out = []; + await runS3({ command: 'list', prefix: 'reports/汉字 &', delimiter: '', maxKeys: '2', all: true }, { env: { ...f.env, DESIRECORE_S3_CONNECTION_ID: 'storage&one' }, emit: v => out.push(v) }); + assert.equal(out.length, 2); assert.equal(out[1].page, 2); assert.equal(Object.hasOwn(out[1].data, 'next_continuation_token'), false); +}); + +test('one-page listing does not follow tokens without --all', async t => { + const f = await fixture(t, (_req, res) => json(res, list(true, { next_continuation_token: 'next' }))); + await runS3({ command: 'list' }, { env: f.env, emit: () => {} }); + assert.equal(f.requests.length, 2); +}); + +test('pagination refuses missing and repeated tokens and reports partial page limits', async t => { + for (const token of [undefined, 'repeat']) { + const f = await fixture(t, (_req, res) => json(res, list(true, token ? { next_continuation_token: token } : {}))); + const out = []; + await assert.rejects(runS3({ command: 'list', all: true }, { env: f.env, emit: v => out.push(v) }), /continuation token/); + assert.equal(out.length, token ? 2 : 1); + } + const f = await fixture(t, (_req, res) => json(res, list(true, { next_continuation_token: 'next' }))); + await assert.rejects(runS3({ command: 'list', all: true, maxPages: '1' }, { env: f.env, emit: () => {} }), /output is partial/); + assert.equal(f.requests.length, 2); +}); + +test('public and presigned links are emitted without following remote URLs', async t => { + for (const urlType of ['public', 'presigned']) { + const f = await fixture(t, (req, res) => { + const q = new URL(req.url, 'http://fixture').searchParams; + assert.equal(q.get('key'), 'reports/a & 汉字.json'); assert.equal(q.get('expires_in'), '120'); + json(res, { success: true, key: q.get('key'), url_type: urlType, download_url: 'https://untrusted.invalid/file?signature=secret', ...(urlType === 'presigned' ? { expires_in: 120 } : {}) }); + }); + const out = []; + await runS3({ command: 'download', key: 'reports/a & 汉字.json', expiresIn: '120' }, { env: f.env, emit: v => out.push(v) }); + assert.equal(out[0].kind, 'download-link'); assert.equal(out[0].data.url_type, urlType); + assert.equal(Object.hasOwn(out[0].data, 'expires_in'), urlType === 'presigned'); assert.equal(f.requests.length, 2); + } +}); + +test('binary download stores exact bytes, including JSON, using an explicit filename', async t => { + const dir = await directory(t); const output = join(dir, 'chosen.json'); + const bytes = Buffer.from('{"success":false,"this":"is object content"}\u0000'); + const f = await fixture(t, (_req, res) => { res.writeHead(200, { 'content-type': 'application/json', 'content-disposition': 'attachment; filename="../../wrong"', 'content-length': bytes.length }); res.end(bytes); }); + const out = []; + await runS3({ command: 'download', key: 'some.json', mode: 'binary', output }, { env: f.env, emit: v => out.push(v) }); + assert.deepEqual(await readFile(output), bytes); assert.equal(out[0].bytes, bytes.length); + assert.deepEqual(await readdir(dir), ['chosen.json']); +}); + +test('binary mode refuses overwrite and preserves existing file', async t => { + const dir = await directory(t); const output = join(dir, 'existing'); await writeFile(output, 'keep'); + const f = await fixture(t, (_req, res) => { res.writeHead(200, { 'content-type': 'application/octet-stream' }); res.end('replace'); }); + await assert.rejects(runS3({ command: 'download', key: 'k', mode: 'binary', output }, { env: f.env }), /EEXIST/); + assert.equal(await readFile(output, 'utf8'), 'keep'); +}); + +test('advertised and streamed byte limits leave no incomplete output', async t => { + const dir = await directory(t); + for (const length of [true, false]) { + const output = join(dir, String(length)); + const f = await fixture(t, (_req, res) => { res.writeHead(200, { 'content-type': 'application/octet-stream', ...(length ? { 'content-length': 5 } : {}) }); res.write('123'); res.end('45'); }); + await assert.rejects(runS3({ command: 'download', key: 'k', mode: 'binary', output, maxBytes: '4' }, { env: f.env }), /max-bytes/); + assert.deepEqual(await readdir(dir), []); + } +}); + +test('capability and domain errors are not retried or written as files, and do not expose server details', async t => { + const dir = await directory(t); const output = join(dir, 'error'); + for (const [status, code] of [[403, 'capabilityDisabled'], [400, 'NO_CONNECTION'], [500, 'DOWNLOAD_FAILED']]) { + const f = await fixture(t, (_req, res) => json(res, { code, error: 'synthetic-s3-private-token and private provider details' }, status)); + await assert.rejects(runS3({ command: 'download', key: 'k', mode: 'binary', output }, { env: f.env }), error => error.message.includes(`HTTP ${status} ${code}`) && !error.message.includes('private')); + assert.equal(f.requests.length, 2); assert.deepEqual(await readdir(dir), []); + } +}); + +test('wrong handshake, HTML API responses and redirects stop reads', async t => { + const wrong = await fixture(t, () => assert.fail('read after wrong service'), { ...handshake, service: 'other' }); + await assert.rejects(runS3({ command: 'connections' }, { env: wrong.env }), /handshake/); assert.equal(wrong.requests.length, 1); + const html = await fixture(t, (_req, res) => { res.writeHead(200, { 'content-type': 'text/html' }); res.end('login'); }); + await assert.rejects(runS3({ command: 'connections' }, { env: html.env }), /expected JSON/); + const redirect = await fixture(t, (_req, res) => { res.writeHead(302, { location: 'https://untrusted.invalid/' }); res.end(); }); + await assert.rejects(runS3({ command: 'connections' }, { env: redirect.env }), /fetch failed/); assert.equal(redirect.requests.length, 2); +}); + +test('unsafe origin or invalid parameters fail before any request', async t => { + const f = await fixture(t, () => assert.fail('unexpected storage read')); + for (const input of [ + { command: 'list', maxKeys: '1.5' }, { command: 'list', maxKeys: '0' }, { command: 'list', maxPages: '2' }, + { command: 'connections', connectionId: 'x' }, { command: 'download', key: '' }, + { command: 'download', key: 'k', expiresIn: '59' }, { command: 'download', key: 'k', mode: 'unknown' }, + { command: 'download', key: 'k', mode: 'binary' }, { command: 'download', key: 'k', output: 'wrong-mode' }, + ]) await assert.rejects(runS3(input, { env: f.env })); + await assert.rejects(runS3({ command: 'connections' }, { env: { ...f.env, DESIRECORE_URL: 'http://remote.example' } })); + assert.equal(f.requests.length, 0); +}); + +test('argument parser preserves explicit empty delimiter and rejects duplicate flags', () => { + assert.deepEqual(parseS3Args(['list', '--delimiter', '', '--all']), { command: 'list', delimiter: '', all: true }); + assert.throws(() => parseS3Args(['list', '--all', '--all'])); + assert.throws(() => parseS3Args(['download', '--key', 'a', '--key', 'b'])); + assert.throws(() => parseS3Args(['download', '--output'])); +}); diff --git a/api/examples/agent-client/test/source-s3.fixture.ts b/api/examples/agent-client/test/source-s3.fixture.ts new file mode 100644 index 0000000..ca2770a --- /dev/null +++ b/api/examples/agent-client/test/source-s3.fixture.ts @@ -0,0 +1,131 @@ +/** Copy to packages/agent-service/src/__tests__/api-docs-s3.resource.test.ts. + * Real Fastify handler, instance-token authentication, capability gate and error serialization. + * The owner profile, capability fact assertion, connection resolver and S3 provider functions are synthetic substitutes. + * No real S3 request, credential read, bucket operation or host bridge is performed. + * Complements existing s3-routes.integration and object-storage-capability-gate tests with exact public field contracts. + */ +import Fastify, { type FastifyInstance } from 'fastify' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const state = vi.hoisted(() => ({ assert: vi.fn(), resolve: vi.fn(), summaries: vi.fn(), get: vi.fn(), list: vi.fn(), head: vi.fn(), url: vi.fn() })) +vi.mock('../agent/agentfs', async original => ({ ...(await original()), readUserProfile: () => ({ id: 's3-docs-owner' }), ensureUserProfile: () => ({ id: 's3-docs-owner' }) })) +vi.mock('../capabilities', async original => ({ ...(await original()), assertUserCapability: state.assert })) +vi.mock('../object-storage/s3-client', async original => ({ ...(await original()), getObject: state.get, listObjects: state.list, headObject: state.head, generateDownloadUrl: state.url })) +vi.mock('../object-storage/s3-connection-resolver', () => ({ resolveS3Connection: state.resolve, listAvailableConnections: state.summaries })) + +import { instanceAuthOnRequest, INSTANCE_TOKEN_HEADER } from '../auth/instance-auth' +import { UserCapabilityDisabledError } from '../capabilities' +import { registerS3Routes } from '../routes/s3-routes' +const TOKEN = 'synthetic-s3-docs-token' +const headers = { [INSTANCE_TOKEN_HEADER]: TOKEN } +const config = { endpoint: 'https://provider.invalid', bucket: 'fixture-bucket', region: 'fixture-region', accessKeyId: 'SYNTHETIC-AK', secretAccessKey: 'SYNTHETIC-SK' } +const summary = { id: 'storage-main', label: 'Fixture', bucket: 'fixture-bucket', region: 'fixture-region', status: 'connected', isDefault: true, hasPublicUrl: true, accessKeyId: 'SYNTHETIC-MUST-NOT-RETURN' } +let app: FastifyInstance + +describe('public S3 read handler field contracts', () => { + beforeEach(async () => { + vi.clearAllMocks() + vi.stubEnv('AGENT_SERVICE_MODE', 'standalone') + vi.stubEnv('AGENT_SERVICE_TOKEN', TOKEN) + state.assert.mockImplementation(() => undefined) + state.resolve.mockReturnValue(config) + state.summaries.mockReturnValue([summary]) + state.list.mockResolvedValue({ objects: [], commonPrefixes: [], keyCount: 0, isTruncated: false }) + state.head.mockResolvedValue({ exists: true, contentType: 'text/plain', contentLength: 3 }) + state.url.mockResolvedValue({ url: 'https://files.invalid/k', type: 'public', verified: true }) + state.get.mockResolvedValue({ body: Buffer.from('abc'), contentType: 'text/plain' }) + app = Fastify() + app.addHook('onRequest', instanceAuthOnRequest) + await registerS3Routes(app) + }) + afterEach(async () => { await app.close(); vi.unstubAllEnvs() }) + + it('real instance authentication rejects missing/wrong token on all three reads before domain access', async () => { + for (const url of ['/api/s3/connections', '/api/s3/list', '/api/s3/download?key=k']) { + for (const credential of [undefined, 'wrong']) { + const result = await app.inject({ method: 'GET', url, headers: credential ? { [INSTANCE_TOKEN_HEADER]: credential } : {} }) + expect(result.statusCode).toBe(401) + } + } + expect(state.assert).not.toHaveBeenCalled() + expect(state.resolve).not.toHaveBeenCalled() + }) + it('actual capability gate passes trusted identity and fails closed before all domain reads', async () => { + state.assert.mockImplementation(() => { throw new UserCapabilityDisabledError('object-storage', 'disabled') }) + for (const url of ['/api/s3/connections', '/api/s3/list', '/api/s3/download?key=k']) { + const result = await app.inject({ method: 'GET', url, headers }) + expect(result.statusCode).toBe(403) + expect(result.json()).toEqual({ error: '用户能力已关闭。', code: 'capabilityDisabled', capability: 'object-storage', retryable: false }) + } + expect(state.assert).toHaveBeenCalledWith('object-storage', 's3-docs-owner', { mode: 'linearized' }) + expect(state.resolve).not.toHaveBeenCalled() + expect(state.summaries).not.toHaveBeenCalled() + expect(state.list).not.toHaveBeenCalled() + }) + it('connection response projects summary fields without credentials and preserves empty configurations', async () => { + const response = await app.inject({ method: 'GET', url: '/api/s3/connections', headers }) + expect(response.json()).toEqual({ success: true, connections: [{ id: 'storage-main', label: 'Fixture', bucket: 'fixture-bucket', region: 'fixture-region', status: 'connected', is_default: true, has_public_url: true }] }) + expect(response.body).not.toContain('SYNTHETIC') + state.summaries.mockReturnValue([]) + expect((await app.inject({ method: 'GET', url: '/api/s3/connections', headers })).json()).toEqual({ success: true, connections: [] }) + }) + it('list preserves opaque tokens/scope, projects provider fields and omits missing optional fields', async () => { + state.list.mockResolvedValue({ objects: [{ key: 'reports/a', size: 1, lastModified: '', etag: 'private-extra' }], commonPrefixes: [{ prefix: 'reports/sub/' }], keyCount: 2, isTruncated: true, nextContinuationToken: 'next+/=&' }) + const response = await app.inject({ method: 'GET', url: '/api/s3/list?connection_id=storage-main&prefix=reports%2F&delimiter=&max_keys=2&continuation_token=opaque%2B%2F%3D%26', headers }) + expect(state.resolve).toHaveBeenCalledWith('storage-main') + expect(state.list).toHaveBeenCalledWith(config, { prefix: 'reports/', delimiter: undefined, maxKeys: 2, continuationToken: 'opaque+/=&' }) + expect(response.json()).toEqual({ success: true, objects: [{ key: 'reports/a', size: 1, last_modified: '' }], common_prefixes: [{ prefix: 'reports/sub/' }], key_count: 2, is_truncated: true, next_continuation_token: 'next+/=&', bucket: 'fixture-bucket', prefix: 'reports/' }) + state.list.mockResolvedValue({ objects: [], commonPrefixes: [], keyCount: 0, isTruncated: false }) + const final = (await app.inject({ method: 'GET', url: '/api/s3/list', headers })).json() + expect(final).not.toHaveProperty('next_continuation_token') + expect(final).not.toHaveProperty('prefix') + }) + it('list numeric normalization uses defaults/clamps without claiming strict input validation', async () => { + for (const [value, expected] of [['0', 100], ['bad', 100], ['-1', 1], ['1001', 1000], ['2.5', 2.5]]) { + expect((await app.inject({ method: 'GET', url: '/api/s3/list?max_keys=' + value, headers })).statusCode).toBe(200) + expect(state.list).toHaveBeenLastCalledWith(config, expect.objectContaining({ maxKeys: expected, delimiter: '/' })) + } + }) + it('download validates key and connection resolution before contacting the provider', async () => { + const missing = await app.inject({ method: 'GET', url: '/api/s3/download', headers }) + expect(missing.statusCode).toBe(400); expect(missing.json().code).toBe('INVALID_REQUEST') + expect(state.resolve).not.toHaveBeenCalled() + state.resolve.mockReturnValue(null) + for (const url of ['/api/s3/list?connection_id=missing', '/api/s3/download?key=k&connection_id=missing']) { + const result = await app.inject({ method: 'GET', url, headers }) + expect(result.statusCode).toBe(400); expect(result.json().code).toBe('NO_CONNECTION') + } + expect(state.head).not.toHaveBeenCalled(); expect(state.list).not.toHaveBeenCalled() + }) + it('public links omit expires_in and optional metadata; presigned links include effective expiry', async () => { + state.head.mockResolvedValue({ exists: true }) + const publicResult = await app.inject({ method: 'GET', url: '/api/s3/download?key=k&mode=unexpected&expires_in=0', headers }) + expect(publicResult.json()).toEqual({ success: true, key: 'k', download_url: 'https://files.invalid/k', url_type: 'public' }) + expect(state.url).toHaveBeenLastCalledWith(config, 'k', { expiresIn: 3600 }) + state.url.mockResolvedValue({ url: 'https://provider.invalid/k?signature=synthetic', type: 'presigned', expiresIn: 60 }) + const signed = await app.inject({ method: 'GET', url: '/api/s3/download?key=k&expires_in=1', headers }) + expect(state.url).toHaveBeenLastCalledWith(config, 'k', { expiresIn: 60 }) + expect(signed.json()).toMatchObject({ success: true, url_type: 'presigned', expires_in: 60 }) + }) + it('binary mode returns exact arbitrary bytes and download headers without a JSON wrapper', async () => { + const body = Buffer.from('{"success":false,"file":"content"}\0') + state.get.mockResolvedValue({ body, contentType: 'application/json' }) + const response = await app.inject({ method: 'GET', url: '/api/s3/download?key=folder%2Fdata.json&mode=binary', headers }) + expect(response.statusCode).toBe(200); expect(response.rawPayload).toEqual(body) + expect(response.headers['content-type']).toContain('application/json') + expect(response.headers['content-length']).toBe(String(body.length)) + expect(response.headers['content-disposition']).toBe('attachment; filename="data.json"') + expect(state.head).not.toHaveBeenCalled(); expect(state.url).not.toHaveBeenCalled() + }) + it('missing metadata produces link 404 while provider errors including binary absence produce domain 500', async () => { + state.head.mockResolvedValue({ exists: false }) + const missing = await app.inject({ method: 'GET', url: '/api/s3/download?key=k', headers }) + expect(missing.statusCode).toBe(404); expect(missing.json().code).toBe('NOT_FOUND') + state.get.mockRejectedValue(new Error('Synthetic missing object')) + const binary = await app.inject({ method: 'GET', url: '/api/s3/download?key=k&mode=binary', headers }) + expect(binary.statusCode).toBe(500); expect(binary.json()).toMatchObject({ success: false, code: 'DOWNLOAD_FAILED' }) + state.list.mockRejectedValue(new Error('Synthetic denied')) + const listResult = await app.inject({ method: 'GET', url: '/api/s3/list', headers }) + expect(listResult.statusCode).toBe(500); expect(listResult.json()).toMatchObject({ success: false, code: 'LIST_FAILED' }) + }) +}) diff --git a/api/examples/agent-client/test/source-taskboard.fixture.ts b/api/examples/agent-client/test/source-taskboard.fixture.ts new file mode 100644 index 0000000..0e90f2d --- /dev/null +++ b/api/examples/agent-client/test/source-taskboard.fixture.ts @@ -0,0 +1,120 @@ +/** Copy to packages/agent-service/src/taskboard/public-reads.resource.test.ts; run resource project. */ +import { mkdtempSync, realpathSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import Fastify, { type FastifyRequest } from 'fastify' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { CanonicalTaskBoardService } from './service' +import { TaskBoardStore } from './store' +import type { TaskBoardPrincipal } from './types' +import { registerTaskboardRoutes } from '../routes/taskboard-routes' + +const runtime = vi.hoisted(() => ({ service: undefined as CanonicalTaskBoardService | undefined })) +vi.mock('./index', () => ({ getTaskBoardService: () => { if (!runtime.service) throw new Error('fixture service missing'); return runtime.service } })) +vi.mock('../team/team-service', () => ({ getTeamService: () => ({ async listTeams() { return [] } }) })) + +const USER = 'public-board-owner' +const principal: TaskBoardPrincipal = { userId: USER, actor: { type: 'human', id: USER } } +let root: string +let server: ReturnType +let service: CanonicalTaskBoardService + +beforeEach(async () => { + root = realpathSync.native(mkdtempSync(join(tmpdir(), 'public-taskboard-reads-'))) + vi.stubEnv('DESIRECORE_TEST_ROOT', root) + let clock = Date.parse('2026-10-25T00:00:00Z') + service = new CanonicalTaskBoardService(new TaskBoardStore(join(root, 'taskboards')), { now: () => new Date(clock += 1000).toISOString() }) + runtime.service = service + server = Fastify() + // Simulate only identity already authenticated by instance-auth. All domain reads are real. + server.addHook('onRequest', async (request: FastifyRequest) => { + const user = request.headers['x-fixture-trusted-user'] + if (typeof user === 'string') (request as typeof request & { identity: { userId: string } }).identity = { userId: user } + }) + await registerTaskboardRoutes(server) + await server.ready() +}) +afterEach(async () => { await server.close(); runtime.service = undefined; vi.unstubAllEnvs(); rmSync(root, { recursive: true, force: true }) }) + +const headers = { 'x-fixture-trusted-user': USER } +async function seed() { + const board = await service.createBoard(principal, { name: 'API reads', scope: { type: 'user', id: USER } }) + if (!board.ok || !board.board) throw new Error('seed board') + const first = await service.createTask(principal, board.board.id, { expectedBoardRevision: board.boardRevision, subject: 'First', description: 'Synthetic content', status: 'pending' }) + if (!first.ok || !first.task) throw new Error('seed task') + const second = await service.createTask(principal, board.board.id, { expectedBoardRevision: first.boardRevision, subject: 'Second', description: 'Synthetic content', status: 'pending' }) + if (!second.ok || !second.task) throw new Error('seed second') + return { boardId: board.board.id, taskId: first.task.id, taskVersion: first.task.version, revision: second.boardRevision } +} + +describe('Public TaskBoard seven read contracts: actual Fastify routes, service and store', () => { + it('reads all seven routes, distinguishes handoff evidence, and does not create when resolve has no match', async () => { + const f = await seed() + const agent: TaskBoardPrincipal = { userId: USER, actor: { type: 'agent', id: 'synthetic-agent' }, sessionIds: ['session-fixture'] } + const session = await service.createBoard(agent, { name: 'Conversation', scope: { type: 'session', id: 'session-fixture' } }) + expect(session.ok).toBe(true) + const list = await server.inject({ url: '/api/taskboards', headers }) + expect(list.statusCode).toBe(200); expect(list.json().boards).toHaveLength(2) + expect((await server.inject({ method: 'POST', url: '/api/taskboards/query', headers, payload: { filter: { boardIds: [f.boardId] }, pageSize: 1 } })).json()).toMatchObject({ schemaVersion: 3, totalCount: 2, hasMore: true }) + expect((await server.inject({ url: `/api/taskboards/${f.boardId}`, headers })).json()).toMatchObject({ schemaVersion: 3, exists: true, allDone: false }) + expect((await server.inject({ url: `/api/taskboards/${f.boardId}/tasks/${f.taskId}`, headers })).json()).toMatchObject({ schemaVersion: 3, exists: true, task: { id: f.taskId } }) + expect((await server.inject({ url: `/api/taskboards/${f.boardId}/activity`, headers })).json()).toEqual({ evidence: [] }) + expect((await server.inject({ url: `/api/taskboards/${f.boardId}/tasks/${f.taskId}/activity?pageSize=1`, headers })).json()).toMatchObject({ schemaVersion: 1, page: { pageSize: 1 } }) + const resolved = await server.inject({ method: 'POST', url: '/api/taskboards/session-board/resolve', headers, payload: { sessionId: 'session-fixture' } }) + expect(resolved.statusCode).toBe(200); expect(resolved.json()).toMatchObject({ exists: true, meta: { scope: { type: 'session', id: 'session-fixture' } } }) + expect((await server.inject({ method: 'POST', url: '/api/taskboards/session-board/resolve', headers, payload: { runId: 'absent-run' } })).json()).toEqual({ schemaVersion: 3, exists: false }) + expect((await service.listBoards(principal)).boards).toHaveLength(2) + }) + + it('requires trusted identity, applies current owner ACL, omits invisible query IDs, and projects claim tokens by actor', async () => { + const f = await seed() + expect((await server.inject('/api/taskboards')).statusCode).toBe(401) + const other = { 'x-fixture-trusted-user': 'other-owner' } + expect((await server.inject({ url: '/api/taskboards', headers: other })).json().boards).toEqual([]) + expect((await server.inject({ url: `/api/taskboards/${f.boardId}`, headers: other })).statusCode).toBe(403) + expect((await server.inject({ url: '/api/taskboards/arbitrary-missing-board', headers })).statusCode).toBe(403) + expect((await server.inject({ method: 'POST', url: '/api/taskboards/query', headers: other, payload: { filter: { boardIds: [f.boardId] } } })).json().items).toEqual([]) + expect((await server.inject({ url: `/api/taskboards/${f.boardId}/tasks/missing-task`, headers })).json()).toEqual({ schemaVersion: 3, exists: false }) + const claimed = await service.actOnTask(principal, f.boardId, f.taskId, { action: 'claim', expectedVersion: f.taskVersion }) + expect(claimed.ok).toBe(true) + const direct = (await server.inject({ url: `/api/taskboards/${f.boardId}/tasks/${f.taskId}`, headers })).json() + expect(direct.task.claim.token).toBeTruthy() + expect(direct.availableActions.every((action: { expectedVersion: number }) => action.expectedVersion === direct.task.version)).toBe(true) + const query = (await server.inject({ method: 'POST', url: '/api/taskboards/query', headers, payload: {} })).json() + expect(query.items.find((row: { task: { id: string } }) => row.task.id === f.taskId).task.claim.token).toBeUndefined() + }) + + it('passes opaque query cursors unchanged, detects version changes, and rejects claimed actor fields', async () => { + const f = await seed() + const request = { filter: { boardIds: [f.boardId] }, sort: { field: 'subject', direction: 'asc' }, pageSize: 1 } + const first = (await server.inject({ method: 'POST', url: '/api/taskboards/query', headers, payload: request })).json() + const second = (await server.inject({ method: 'POST', url: '/api/taskboards/query', headers, payload: { ...request, cursor: first.nextCursor } })).json() + expect(second.hasMore).toBe(false); expect(second.nextCursor).toBeUndefined() + expect(second.items[0].task.id).not.toBe(first.items[0].task.id) + const mismatched = await server.inject({ method: 'POST', url: '/api/taskboards/query', headers, payload: { ...request, filter: {}, cursor: first.nextCursor } }) + expect(mismatched.statusCode).toBe(400) + const changed = await service.updateTask(principal, f.boardId, f.taskId, { expectedVersion: f.taskVersion, patch: { subject: 'Changed' } }) + expect(changed.ok).toBe(true) + expect((await server.inject({ method: 'POST', url: '/api/taskboards/query', headers, payload: { ...request, cursor: first.nextCursor } })).statusCode).toBe(409) + expect((await server.inject({ method: 'POST', url: '/api/taskboards/query', headers, payload: { actor: { type: 'human', id: 'other' } } })).statusCode).toBe(400) + expect((await server.inject({ method: 'POST', url: '/api/taskboards/session-board/resolve', headers, payload: { runId: 'r', sessionIds: ['fake'] } })).statusCode).toBe(400) + }) + + it('pins task activity prefix across appends and validates range/current Board access on every page', async () => { + const f = await seed() + const comment = await service.addComment(principal, f.boardId, f.taskId, { expectedVersion: f.taskVersion, kind: 'comment', body: 'Synthetic comment' }) + expect(comment.ok).toBe(true) + const path = `/api/taskboards/${f.boardId}/tasks/${f.taskId}/activity` + const first = (await server.inject({ url: `${path}?pageSize=1`, headers })).json() + expect(first.page.nextAfterRevision).not.toBeNull() + const prefix = first.page.throughRevision + const later = await service.addComment(principal, f.boardId, f.taskId, { expectedVersion: comment.ok ? comment.task!.version : 0, kind: 'comment', body: 'Later synthetic comment' }) + expect(later.ok).toBe(true) + const nextUrl = `${path}?pageSize=1&afterRevision=${first.page.nextAfterRevision}&throughRevision=${prefix}` + const next = (await server.inject({ url: nextUrl, headers })).json() + expect(next.page.throughRevision).toBe(prefix) + expect(next.items.every((item: { taskEvent?: { boardRevision: number } }) => !item.taskEvent || item.taskEvent.boardRevision <= prefix)).toBe(true) + expect((await server.inject({ url: nextUrl, headers: { 'x-fixture-trusted-user': 'other-owner' } })).statusCode).toBe(403) + for (const suffix of ['?pageSize=101', '?afterRevision=1', '?afterRevision=9&throughRevision=2', '?unknown=1']) expect((await server.inject({ url: path + suffix, headers })).statusCode).toBe(400) + }) +}) diff --git a/api/examples/agent-client/test/taskboards.test.mjs b/api/examples/agent-client/test/taskboards.test.mjs new file mode 100644 index 0000000..a71c928 --- /dev/null +++ b/api/examples/agent-client/test/taskboards.test.mjs @@ -0,0 +1,109 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import http from 'node:http'; +import { once } from 'node:events'; +import { runTaskboards, parseTaskboardsArgs } from '../taskboards.mjs'; + +const handshake = { service: 'desirecore-agent-service', apiProtocolVersion: '1', instanceId: 'inst_task_fixture' }; +function json(res, value, status = 200) { res.writeHead(status, { 'content-type': 'application/json' }); res.end(JSON.stringify(value)); } +async function fixture(t, handler, metadata = handshake) { + const requests = []; + const server = http.createServer(async (req, res) => { + let text = ''; for await (const part of req) text += part; + const record = { path: req.url, method: req.method, token: req.headers['x-agent-service-token'], body: text ? JSON.parse(text) : undefined }; + requests.push(record); + if (req.url === '/api/client-handshake') json(res, metadata); else handler(record, res); + }); + server.listen(0, '127.0.0.1'); await once(server, 'listening'); + t.after(() => { server.closeAllConnections(); server.close(); }); + return { requests, env: { DESIRECORE_URL: `http://127.0.0.1:${server.address().port}`, DESIRECORE_TOKEN: 'fixture-private-token' } }; +} + +test('five basic commands preserve exists:false and authenticate every request', async t => { + const f = await fixture(t, (req, res) => json(res, req.path === '/api/taskboards' ? { schemaVersion: 3, boards: [] } : req.path.includes('/activity') ? { evidence: [] } : { schemaVersion: 3, exists: false })); + const output = []; + for (const input of [{ command: 'list' }, { command: 'board', boardId: 'board a' }, { command: 'task', boardId: 'board a', taskId: 'task:a' }, { command: 'handoff', boardId: 'board a', taskId: 'task:a' }, { command: 'resolve', runId: 'run-1', sessionId: 'session-1' }]) await runTaskboards(input, { env: f.env, emit: v => output.push(v) }); + assert.equal(output.length, 5); assert.equal(output[1].data.exists, false); + assert.ok(f.requests.every(req => req.token === f.env.DESIRECORE_TOKEN)); + assert.deepEqual(f.requests.at(-1).body, { runId: 'run-1', sessionId: 'session-1' }); + assert.equal(f.requests[3].path, '/api/taskboards/board%20a'); + assert.equal(f.requests[7].path, '/api/taskboards/board%20a/activity?taskId=task%3Aa'); +}); + +test('query passes opaque cursor unchanged with the same filters and sorting, and stops at hasMore=false', async t => { + const cursor = 'opaque:+/=='; + const f = await fixture(t, (req, res) => json(res, { schemaVersion: 3, items: [{ task: { id: req.body.cursor ? 'b' : 'a' } }], hasMore: !req.body.cursor, ...(req.body.cursor ? {} : { nextCursor: cursor }) })); + const output = []; + const filter = { boardIds: ['board-1'], taskArchive: 'any' }, sort = { field: 'subject', direction: 'asc' }; + await runTaskboards({ command: 'query', filter: JSON.stringify(filter), sort: JSON.stringify(sort), pageSize: '1' }, { env: f.env, emit: v => output.push(v) }); + assert.deepEqual(f.requests.slice(1).map(req => req.body), [{ filter, sort, pageSize: 1 }, { filter, sort, pageSize: 1, cursor }]); + assert.equal(output[0].data.pages.length, 2); + assert.ok(f.requests.slice(1).every(req => req.method === 'POST')); +}); + +test('activity continues through empty pages and pins throughRevision; owner projections remain separate page envelopes', async t => { + const f = await fixture(t, (req, res) => { + const q = new URL(req.path, 'http://fixture').searchParams; + const after = Number(q.get('afterRevision')); + assert.equal(q.get('throughRevision'), after ? '20' : null); + json(res, { schemaVersion: 1, items: after ? [{ source: 'ownerProjection', activityId: 'owner:a' }] : [], page: { pageSize: 1, afterRevision: after, throughRevision: 20, nextAfterRevision: after ? null : 7 } }); + }); + const output = []; + await runTaskboards({ command: 'activity', boardId: 'board', taskId: 'task', pageSize: '1' }, { env: f.env, emit: v => output.push(v) }); + assert.equal(output[0].data.pages.length, 2); assert.equal(f.requests.length, 3); +}); + +test('query 409 discards already read pages and never retries or exposes server body', async t => { + const f = await fixture(t, (req, res) => req.body.cursor ? json(res, { ok: false, error: { code: 'versionConflict', message: 'fixture-private-token' } }, 409) : json(res, { schemaVersion: 3, items: [], hasMore: true, nextCursor: 'a' })); + const output = []; + await assert.rejects(runTaskboards({ command: 'query' }, { env: f.env, emit: v => output.push(v) }), e => /HTTP 409 versionConflict.*restart/.test(e.message) && !e.message.includes(f.env.DESIRECORE_TOKEN)); + assert.equal(output.length, 0); assert.equal(f.requests.length, 3); +}); + +test('bounded paging and repeated or absent cursors fail without emitting partial results', async t => { + for (const response of [{ schemaVersion: 3, items: [], hasMore: true }, { schemaVersion: 3, items: [], hasMore: true, nextCursor: 'same' }]) { + const f = await fixture(t, (_req, res) => json(res, response)); + const output = []; + await assert.rejects(runTaskboards({ command: 'query' }, { env: f.env, emit: v => output.push(v) }), /cursor/); + assert.equal(output.length, 0); + } + const f = await fixture(t, (_req, res) => json(res, { schemaVersion: 3, items: [], hasMore: true, nextCursor: 'next' })); + await assert.rejects(runTaskboards({ command: 'query', maxPages: '1' }, { env: f.env }), /maxPages/); + assert.equal(f.requests.length, 2); +}); + +test('activity rejects changing prefixes and non-advancing revisions', async t => { + for (const next of [0, 21, '7', undefined]) { + const f = await fixture(t, (_req, res) => json(res, { schemaVersion: 1, items: [], page: { afterRevision: 0, throughRevision: 20, nextAfterRevision: next } })); + await assert.rejects(runTaskboards({ command: 'activity', boardId: 'b', taskId: 't' }, { env: f.env }), /advance/); + } + const f = await fixture(t, (req, res) => { const after = Number(new URL(req.path, 'http://fixture').searchParams.get('afterRevision')); json(res, { schemaVersion: 1, items: [], page: { afterRevision: after, throughRevision: after ? 21 : 20, nextAfterRevision: after ? null : 7 } }); }); + await assert.rejects(runTaskboards({ command: 'activity', boardId: 'b', taskId: 't' }, { env: f.env }), /prefix changed/); +}); + +test('capabilityDisabled and forbidden are reported without retry', async t => { + for (const code of ['capabilityDisabled', 'forbidden']) { + const f = await fixture(t, (_req, res) => json(res, { ok: false, error: { code, message: 'private', retryable: false } }, 403)); + await assert.rejects(runTaskboards({ command: 'list' }, { env: f.env }), new RegExp(`HTTP 403 ${code}`)); + assert.equal(f.requests.length, 2); + } +}); + +test('handshake mismatch, HTML and redirects stop reads without forwarding credentials elsewhere', async t => { + const wrong = await fixture(t, () => assert.fail('read after wrong handshake'), { ...handshake, service: 'other' }); + await assert.rejects(runTaskboards({ command: 'list' }, { env: wrong.env }), /handshake/); + assert.equal(wrong.requests.length, 1); + const html = await fixture(t, (_req, res) => { res.writeHead(200, { 'content-type': 'text/html' }); res.end('login'); }); + await assert.rejects(runTaskboards({ command: 'list' }, { env: html.env }), /expected JSON/); + const redirect = await fixture(t, (_req, res) => { res.writeHead(302, { location: 'https://another.example' }); res.end(); }); + await assert.rejects(runTaskboards({ command: 'list' }, { env: redirect.env }), /fetch failed/); + assert.equal(redirect.requests.length, 2); +}); + +test('invalid options are rejected before network access', async t => { + const f = await fixture(t, () => assert.fail('unexpected request')); + for (const input of [{ command: 'list', pageSize: '1' }, { command: 'board', boardId: '..' }, { command: 'task', boardId: 'b' }, { command: 'resolve' }, { command: 'resolve', runId: '../x' }, { command: 'query', filter: '[]' }, { command: 'query', pageSize: '201' }, { command: 'activity', boardId: 'b', taskId: 't', pageSize: '101' }, { command: 'query', maxPages: '1x' }]) await assert.rejects(runTaskboards(input, { env: f.env })); + await assert.rejects(runTaskboards({ command: 'list' }, { env: { ...f.env, DESIRECORE_URL: 'http://remote.example' } })); + assert.throws(() => parseTaskboardsArgs(['list', '--board-id', 'b', '--board-id', 'c'])); + assert.equal(f.requests.length, 0); +}); diff --git a/api/taskboard-s3-verification-2026-10-25.md b/api/taskboard-s3-verification-2026-10-25.md new file mode 100644 index 0000000..fd4fb0f --- /dev/null +++ b/api/taskboard-s3-verification-2026-10-25.md @@ -0,0 +1,47 @@ +# TaskBoard 与 S3 读取扩展验证(2026-10-25) + +## 来源与公开范围 + +固定源码 `ba6a550aeb397c377f6c3c6e620863d06d526628`,内部文档 gitlink `9fbe5f4668d223553b38ebd4e7a2a49450000a9b`,公开 main 基线 `b8fc9329b935f85a20ab8bd253cf3afbbe99020b`。原32项 Agent Service 的51个直接source文件与上一批 a0186f6f 逐blob相同,因此仅提升已静态复核的来源,不把历史实跑结果改成在新版本重跑。 + +新增 TaskBoard 7 操作、S3 3 操作,合计 **50 HTTP 操作**(Agent42、Compat3、Workflow5),**13组双语页面**。Agent Service OpenAPI1.3.0;Compat/Workflow未改内容或来源。Worker修复独立提交,不作为本批公开接口或TaskBoard/S3文档的依赖。 + +## 合同核验 + +- TaskBoard:可信human actor、用户能力及当前Board/Team ACL,聚合查询省略不可见资源,详情可403或200 exists:false。两个POST仅查询,不创建任务板或执行Agent。query cursor原样传回;条件错400、版本变409后丢弃旧页。 +- 整板activity只返回handoff evidence;单任务activity按throughRevision冻结canonical事件前缀,ownerProjection摘要仍每页当前读取,items长度不能代替续页位置。session resolver只选现有可见默认板,归属来自真实保存记录。 +- S3:启用的用户能力、已有连接和供应商权限;摘要不验证secret或连通性。默认连接失败不扫描其它连接,调用方显式保存connection_id。public链接省略expires_in;末页通常省略token而非null;数字参数保留现行Number/默认/夹限语义。 +- link404表示未确认可读,可能包含供应商拒绝;binary可500 DOWNLOAD_FAILED。binary对象可为任意MIME及任意JSON,规范不能只凭application/json强制当链接外壳。示例拒绝重定向、链接不自动访问、不向返回URL转发实例token,输出限量且不覆盖已有文件。 + +## 实际执行结果 + +### 源码读取专项 + +TaskBoard **60项通过**:现有unit三文件45项、session-board/history-service-c resource两文件11项、新增真实7路由/Service/Store fixture4项。后者只替换身份注入、Team枚举及service单例装配,持久化在独立临时根。首次夹具把TaskUpdate.patch错误写成顶层字段,修正测试准备后4/4通过,未为通过测试修改生产TaskBoard源码。 + +S3 **9项通过**:真实Fastify注册、instance-token认证、能力gate与handler;owner profile、能力事实、连接解析和S3 Provider函数为合成替身。覆盖认证及能力拒绝优先、无凭据摘要、空配置、token/字段映射、数值兼容、缺key/连接、公有/签名链接与binary错误及字节。未读取真实凭据或连接真实S3。 + +维护夹具在 `api/examples/agent-client/test/source-taskboard.fixture.ts`、`source-s3.fixture.ts`,各自文件首注释给出复制位置。复制到固定源码版本并使用其现有Vitest resource project执行;完成后移除临时副本,避免把维护夹具混入独立Worker PR。它们从站点TypeScript范围排除,不在Node示例测试数字内。 + +### 可运行示例 + +6个Node测试文件 **75项通过**(原54、新TaskBoard9、新S312)。真实本机HTTP listener返回合成协议响应,验证认证头、握手、精确请求路径与参数、分页/异常停止、限量下载/清理、拒绝覆盖和重定向;不是实际业务实例或供应商验收。源码与static六份脚本逐字一致,由api:check维护。 + +### 文档、规范与预览 + +- docs typecheck、api:check与双语build、api:check --build通过:13组/50操作/96个JSON块。 +- AJV Draft7编译 **142个组件Schema**,验证 **59个规范JSON示例**,通过。 +- TaskBoard评论的Draft7属性dependencies转换成等价if/then,以适配OpenAPI工具;穷举256种字段存在组合,原约束与转换结果全部一致。没有降低评论关联字段要求。S3 403错误变体使用anyOf,避免与开放的Origin错误Schema构成不实互斥承诺。 +- Redocly CLI2.63.0三规范 **0错误、87告警**;告警含条件Schema分支required、许可字段及独立链接组件未被响应直接引用。保留告警,不添加忽略配置或伪造许可。 +- 最终本地HTTP验收 **26个页面、18份双语下载全部200、0失败**,包含canonical/hreflang、DocSearch语言/current/tag、sitemaps/robots和新增正文;下载逐字匹配static源。规范调整后已重新构建并复核全部18份下载字节。 +- 浏览器检查新增4个中英文页面、TaskBoard→英文语言切换及代表锚点;1280宽无整页横溢。390宽检查中文TaskBoard与英文S3:整页宽390,宽表格在358宽容器内滚动。已恢复视口,未修改样式或搜索配置。 + +## Worker 独立修复边界 + +源码修复覆盖敏感中途进度在flush前移除、旧记录读取的敏感input/result/引用重投影、entry/index字段允许表、明确媒体载荷和普通业务JSON的区别,以及seq与有效entry位置分页的契约。七文件回归 **284项通过**,独立真实helper **14项通过**,受影响类型边界及消费闭包13项通过。身份hook为合成,不称完整认证、公网或真实用户数据验收。 + +这些结果不将Worker自动列入本公开包;后续公开需要独立说明其记录与分页合同。旧原文件不原地改写,执行输入不变;投影不是任意自由正文的通用敏感检测。 + +## 发布状态 + +本记录为提交PR前的本地核验,尚未合并或部署本批公开文档,没有重抓Algolia或宣称生产搜索已收录。此前生产/搜索报告保留原始批次;合并后再按既有自动部署与每日爬虫流程验收。 diff --git a/docs/07-api/03-errors-and-troubleshooting.md b/docs/07-api/03-errors-and-troubleshooting.md index a7f11fd..4fffe77 100644 --- a/docs/07-api/03-errors-and-troubleshooting.md +++ b/docs/07-api/03-errors-and-troubleshooting.md @@ -113,3 +113,10 @@ HTTP `200` 表示读取成功,不保证返回完整的工具结果正文。敏 ## 网络连接正常但上游不可用 Socket.IO 已连接并不保证实例可访问模型供应商。先读取[上游网络状态](./10-observability.md#network-health),结合 `reason`、`since` 和供应商范围排查;`unknown` 不是离线,历史 `online` 也不证明当前请求成功。近期调用和用量只是观察信息,不据此自动重放执行。 + + +## TaskBoard 与对象存储读取 + +认证握手成功不会自动启用可选应用,也不验证存储连接。收到 `403 capabilityDisabled` 时,在“我的应用”检查对应应用的安装与启用状态,不持续重试已关闭的能力。TaskBoard 还受可读资源范围限制,不存在或无权读取的任务板可能返回 `403`;请按 [TaskBoard 错误与分页规则](./11-taskboards.md)处理任务缺失及分页冲突,不把它们一概当成成功的空查询。 + +S3 链接模式和二进制模式的响应及错误合同不同。链接模式的 `404 NOT_FOUND` 表示实例未能确认对象可读,需要检查对象键、供应商权限与连接;二进制模式下,同类供应商失败可能返回 `500 DOWNLOAD_FAILED`。按[对象存储读取](./12-object-storage.md)处理实际 HTTP 状态,分享日志时移除签名下载链接。 diff --git a/docs/07-api/07-openapi.md b/docs/07-api/07-openapi.md index fbee975..cc279b3 100644 --- a/docs/07-api/07-openapi.md +++ b/docs/07-api/07-openapi.md @@ -49,6 +49,16 @@ sidebar_position: 7 | `POST /api/media/upload` | 上传图片、音频或视频 | [附件与媒体](./09-attachments-and-media.md) | | `POST /api/media/access-urls` | 签发短期媒体访问链接 | [附件与媒体](./09-attachments-and-media.md) | | `GET /media/{filename}` | 使用签名链接读取媒体与 Range | [附件与媒体](./09-attachments-and-media.md) | +| `GET /api/taskboards` | 列出可读任务板 | [TaskBoard 读取](./11-taskboards.md) | +| `POST /api/taskboards/query` | 查询任务板与任务 | [TaskBoard 读取](./11-taskboards.md) | +| `GET /api/taskboards/{id}` | 读取任务板投影 | [TaskBoard 读取](./11-taskboards.md) | +| `GET /api/taskboards/{id}/tasks/{taskId}` | 读取单个任务 | [TaskBoard 读取](./11-taskboards.md) | +| `GET /api/taskboards/{id}/activity` | 读取任务板交接证据 | [TaskBoard 读取](./11-taskboards.md) | +| `GET /api/taskboards/{id}/tasks/{taskId}/activity` | 分页读取任务活动 | [TaskBoard 读取](./11-taskboards.md) | +| `POST /api/taskboards/session-board/resolve` | 解析现有会话任务板 | [TaskBoard 读取](./11-taskboards.md) | +| `GET /api/s3/connections` | 读取已配置连接摘要 | [S3 对象读取](./12-object-storage.md) | +| `GET /api/s3/list` | 列出存储对象 | [S3 对象读取](./12-object-storage.md) | +| `GET /api/s3/download` | 下载对象字节或取得链接 | [S3 对象读取](./12-object-storage.md) | 花括号表示需要替换的路径参数,对应端点指南中的冒号写法。Webhook 管理操作要求认证握手中的 `capabilities.webhookManagement` 为 `true`,并使用实例所有者凭据。旧实例缺少该能力时,使用管理员已启用并验证的事件。运行列表和详情按部署所有者的实例读取范围使用;回执读取另外检查可信用户归属。 @@ -107,3 +117,5 @@ Socket.IO 的连接、订阅与异步事件需要配套客户端逻辑。OpenAPI 媒体原生播放使用签发端点返回的完整 `/media/{filename}?expires=…&signature=…` 链接;该读取入口不使用实例令牌,链接应按短期凭据保管。附件下载仍使用实例认证。历史、话题和媒体的权限与响应范围见各自指南。 观察和聚合历史的可运行读取示例分别为 **[observe.mjs](pathname:///api-examples/observe.mjs)** 与 **[history.mjs](pathname:///api-examples/history.mjs)**。聚合历史的 revision 冲突需要重新读取,不能混拼旧页;用量聚合不等于供应商最终账单。 + +TaskBoard 与 S3 的读取示例分别为 **[taskboards.mjs](pathname:///api-examples/taskboards.mjs)** 和 **[s3.mjs](pathname:///api-examples/s3.mjs)**。查询游标、活动修订、对象续读令牌和下载模式按对应指南处理。 diff --git a/docs/07-api/11-taskboards.md b/docs/07-api/11-taskboards.md new file mode 100644 index 0000000..3f28a8f --- /dev/null +++ b/docs/07-api/11-taskboards.md @@ -0,0 +1,249 @@ +--- +title: 任务看板读取 +description: 查询有权访问的 TaskBoard、任务详情、交接证据和分页活动记录,并按会话解析默认看板。 +slug: /api/taskboards +sidebar_position: 11 +keywords: [API, TaskBoard, 任务看板, 活动记录, 分页] +--- + +# 任务看板读取 + +使用已有实例地址和 `X-Agent-Service-Token`,可将任务列表、详情及活动记录接入自己的看板。先完成[连接验证](./01-connection-and-auth.md)。实例中需要开启当前用户的全局 TaskBoard 能力;关闭时返回 `403 capabilityDisabled`,请在客户端启用该能力或联系实例管理员。 + +这些接口按已认证用户及其真实团队权限筛选,HTTP 请求作为该用户的 human actor 读取。请求体中的 `userId`、`actor` 或自报团队不会授予权限。知道 Board、Task、Run 或 Session ID 也不会扩大可读范围。本文的两个 `POST` 都是查询,不创建看板或任务,也不发起 Agent 执行。 + +| 目的 | 方法和路径 | +|---|---| +| 列出可见看板摘要 | `GET /api/taskboards` | +| 跨看板筛选任务并分页 | `POST /api/taskboards/query` | +| 读取一个看板及其可见任务 | `GET /api/taskboards/:id` | +| 按稳定 ID 读取单个任务 | `GET /api/taskboards/:id/tasks/:taskId` | +| 读取看板内任务的交接证据 | `GET /api/taskboards/:id/activity` | +| 分页读取一个任务的活动 | `GET /api/taskboards/:id/tasks/:taskId/activity` | +| 解析会话的默认看板 | `POST /api/taskboards/session-board/resolve` | + +路径中的 ID 使用接口返回的原值,并分别按 URL path segment 编码。Board revision、Task version、查询游标和活动分页位置用途不同,不能互换。 + +## 看板列表和详情 {#boards} + +### `GET /api/taskboards` + +无查询参数和请求体。返回 `{schemaVersion: 3, generatedAt, boards}`,其中 `boards` 仅包含当前用户可见的看板;列表没有分页参数。每项包含: + +| 字段 | 用途 | +|---|---| +| `meta` | 看板 ID、名称、`scope`、`policy`、`revision` 和时间等元数据 | +| `statusCounts` | 未归档、未删除任务按状态统计的 `{status, count}` 数组 | +| `openCount` | 未归档、未删除任务中尚未进入看板 policy 终态或隐藏类别的数量 | +| `archiveAction` | 当前用户的归档动作快照,包含 `enabled`、`archived`、`expectedBoardRevision`,不可用时可能有 `reason` | + +列表可包含已归档看板;不要把 `openCount: 0` 当作看板不存在或所有历史任务都已删除。`scope.type` 可以是 `user`、`team`、`agent` 或 `session`。团队失去访问权限后,聚合列表会省略相应看板。 + +```bash +curl --silent --show-error \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + "${BASE_URL}/api/taskboards" +``` + +```json +{ + "schemaVersion": 3, + "generatedAt": "2026-10-25T08:00:00.000Z", + "boards": [] +} +``` + +### `GET /api/taskboards/:id` + +无查询参数和请求体。成功详情为 `{schemaVersion: 3, exists: true, meta, tasks, comments, resourceBindings, availableActions, allDone}`,可能另有 `fanOutDispatches`。 + +- `tasks` 不包含已删除任务,可以包含已归档任务。`comments` 是这些任务的当前评论投影;已撤回的正文显示为 `[redacted]`,不再附带原正文历史、附件或 Mention 内容。 +- `availableActions` 按 `{taskRef, actions}` 列出当前 actor 的动作。读取此快照不授予执行权限;后续操作仍需重新鉴权。不要仅按任务状态、负责人或 `ownerUserId` 猜测动作。 +- `allDone` 表示没有未归档、未删除的开放任务;空看板也可能为 `true`,不证明任何一项外部工作已成功。 +- `resourceBindings`、`runRefs`、`receiptRefs` 和深链是关联信息,不是读取关联资源的凭据。`fanOutDispatches` 的已创建子任务也不等于对应 Agent Run 已经启动。 +- 不属于当前 claim actor 的 `claim.token` 会省略;查询接口始终省略该 token。不要把这些读取结果作为可共享的公开数据。 + +直接读取任意不存在或不可读的 Board ID 通常返回 `403 forbidden`。某些有权探测、但尚未建板的作用域别名可返回 `200`、`exists: false`。调用方必须同时处理这两种情况,不能承诺“所有不存在都返回 404”。 + +## 跨看板查询任务 {#query} + +### `POST /api/taskboards/query` + +请求为 JSON 对象,省略请求体等价 `{}`。只接受 `filter`、`sort`、`pageSize`、`cursor`;未知字段返回 `400 invalidRequest`。 + +```json +{ + "filter": { + "boardIds": ["your-board-id"], + "statuses": ["pending", "inProgress"], + "taskArchive": "active" + }, + "sort": {"field": "updatedAt", "direction": "desc"}, + "pageSize": 50 +} +``` + +| 参数 | 规则 | +|---|---| +| `pageSize` | 整数 `1..200`,默认 `50` | +| `sort` | 同时给 `field` 和 `direction`;field 为 `updatedAt`、`createdAt`、`dueAt`、`priority`、`position`、`subject`,direction 为 `asc` 或 `desc`;默认 `updatedAt desc` | +| `cursor` | 上一页返回的不透明字符串,原样传回;不是 offset,也不是可编辑 JSON | +| `filter` | 可选对象,支持下表中的组合条件 | + +| 筛选字段 | 可用值及含义 | +|---|---| +| `keyword` | 最多 512 字符,大小写不敏感地匹配任务 ID、标题、描述、标签及关联引用等字段 | +| `boardIds` | 最多 256 个唯一 Board ID;不可见或不存在的 ID 静默省略,空数组不限制看板 | +| `scopeTypes` | `user`、`team`、`agent`、`session` | +| `statuses` | `backlog`、`pending`、`inProgress`、`blocked`、`inReview`、`completed`、`failed`、`canceled`、`deleted`;已删除任务不进入当前可见任务查询 | +| `priorities` | `high`、`normal`、`low` | +| `labels` / `labelMatch` | 最多 128 个唯一标签;`any`(默认)或 `all` | +| `participantIds` | 最多 256 个唯一 ID,匹配负责人、claim actor、审核人、贡献者或关注者 | +| `reviewStates` | `notSubmitted`、`awaitingReview`、`accepted`、`changesRequested` | +| `due` | `overdue`、`upcoming`、`none`、`any`;以首屏查询时刻判断到期范围,不启动调度 | +| `sourceOwnerTypes` | `automation`、`workflow`、`conversation`、`connector`、`import`、`manual` | +| `integrationTypes` | `automation`、`workflow`、`conversation`、`workdir`、`run` | +| `boardArchive` / `taskArchive` | 分别为 `active`(默认)、`archived` 或 `any` | + +数组不允许重复值。没有筛选命中时可以返回: + +```json +{ + "schemaVersion": 3, + "generatedAt": "2026-10-25T08:00:00.000Z", + "items": [], + "pageSize": 50, + "hasMore": false, + "totalCount": 0, + "statusCounts": [] +} +``` + +实际每条 `items` 包含 `board`、`task`、`commentCount`、`deepLink`、`availableActions`、`archiveAction`。`deepLink.route` 是应用内地址,不是 API 路径。`totalCount` 和 `statusCounts` 统计全部筛选命中项,不只统计当前页。可选 `indexGeneration` 仅供诊断,不是 Board revision 或授权证明。 + +### 正确翻页 + +1. 首屏省略 `cursor`,保存原始筛选及排序条件。 +2. 只有 `hasMore: true` 时,将 `nextCursor` 原样放入下一次请求;保持筛选和排序不变。末页省略 `nextCursor`。 +3. 筛选或排序与游标不匹配,返回 `400 invalidRequest`。授权可见数据或版本变化导致游标失效时返回 `409 versionConflict`;丢弃已收集的旧页,从首屏重新读取。 + +每页重新检查权限。游标不建立跨用户授权或长期快照,也不能作为增量同步日志;不要解码、合成或跨用户复用。 + +## 单任务详情 {#task-detail} + +### `GET /api/taskboards/:id/tasks/:taskId` + +无查询参数和请求体。存在时返回 `{schemaVersion: 3, exists: true, board, task, comments, resourceBindings, availableActions, archiveAction, deepLink}`。与整板详情不同,这里的 `availableActions` 直接为动作数组,`comments` 仅包含此任务。 + +Board 已获准读取、但任务不存在或已删除时返回: + +```json +{"schemaVersion": 3, "exists": false} +``` + +Board 本身的权限检查仍可先返回 `403`。不要以 `exists: false` 自动创建任务,或把一个任务的 `version` 用在另一任务上。 + +## 交接证据和任务活动 {#activity} + +这两个路径返回不同内容,不能互相代替。 + +### `GET /api/taskboards/:id/activity` + +这是看板内任务的**交接证据**,不是整个看板的完整事件流。可带 `taskId` 查询参数筛选一个任务;没有分页参数。返回 `{evidence: [...]}`。每项含 `eventId`、`kind: "handoff"`、`taskRef`、`fromActor`、`toActor`、`occurredAt`,可选 `reason`。 + +```json +{"evidence": []} +``` + +空数组可以表示没有交接,或所筛选任务不在当前可见范围;它不表示任务没有其他活动。需要评论、状态变化等活动时使用下面的单任务接口。 + +### `GET /api/taskboards/:id/tasks/:taskId/activity` + +| 查询参数 | 规则 | +|---|---| +| `pageSize` | 整数 `1..100`,默认 `50`;限制本页读取的任务事件候选数 | +| `afterRevision` | 安全非负整数,默认 `0`;排除此 Board revision 及之前的任务事件 | +| `throughRevision` | 正安全整数;首屏省略以固定当前 Board 历史前缀,后续沿用返回值 | + +`afterRevision > 0` 必须同时提供 `throughRevision`。`afterRevision > throughRevision` 或 `throughRevision` 大于当前 Board revision 返回 `400 invalidRequest`。未知查询参数也返回 `400`。建议使用规范十进制数字;不要依赖字符串强转的兼容细节。 + +```bash +curl --silent --show-error --get \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + --data-urlencode 'pageSize=50' \ + "${BASE_URL}/api/taskboards/${BOARD_ID}/tasks/${TASK_ID}/activity" +``` + +```json +{ + "schemaVersion": 1, + "taskRef": {"boardId": "your-board-id", "taskId": "your-task-id"}, + "generatedAt": "2026-10-25T08:00:00.000Z", + "items": [], + "page": {"pageSize": 50, "afterRevision": 0, "throughRevision": 12, "nextAfterRevision": null} +} +``` + +`items` 的 `source` 区分两类活动: + +- `taskEvent`:含 `taskEvent`,本页内按 Board revision 升序排列,受 `throughRevision` 限制。 +- `ownerProjection`:含 `ownerProjection`,是关联服务当前提供的摘要,先于任务事件返回;可能每页重复,也可能变化,不受历史前缀固定。它不是已归档的事件正文。 + +用 `activityId` 区分重复展示项。`items.length` 可以小于 `pageSize`,也可以因额外摘要大于它;空页仍可能有下一页。只有 `page.nextAfterRevision: null` 才表示固定前缀读完,否则下一页使用该位置并保留同一 `throughRevision`。不要从最后一条活动的时间、Task version 或 `items.length` 推导下一页。 + +新增任务事件不会扩大已固定的前缀。需要最新活动时重新从首屏读取,并重新取得 `throughRevision`。前缀只约束任务历史,不冻结当前权限和关联服务摘要。任务不存在时可返回 `404 taskNotFound`;先行 Board 权限检查仍可能返回 `403`。 + +## 按会话解析默认看板 {#session-board} + +### `POST /api/taskboards/session-board/resolve` + +请求体最多 4 KiB,至少提供 `runId` 或 `sessionId`;只接受这两个字段。标识需匹配 `^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$`。 + +```json +{"runId": "your-run-id", "sessionId": "your-current-session-id"} +``` + +优先使用 Run ID。服务端按已保存的会话归属解析同一 Run 的各代 Session,避免会话重建后误认为看板丢失: + +1. 当前 Session 恰有一个可见看板时,选择它;它可以是已归档看板。 +2. 当前 Session 有多个看板时,不选择默认看板。 +3. 当前 Session 没有看板时,选择同一 Run 其他 Session 中未归档、最近更新的一块。 + +`sessionId` 只影响当前 Session 优先级;服务端确认其属于另一个 Run 时会忽略这项优先级。只提供 `sessionId` 时,可反查所属 Run;会话归属不可读时可能仅按当前 Session 选择。请求不能自报 Session 列表扩大范围。 + +成功沿用看板详情形状。没有可选择的默认看板返回 `200 {"schemaVersion":3,"exists":false}`,不创建看板,也不代表 Run 不存在、没有其他看板或对话已结束。 + +## 可运行示例 {#taskboards-example} + +下载 **[taskboards.mjs](pathname:///api-examples/taskboards.mjs)**。使用 Node.js 22 或更高版本,无需安装 npm 包: + +```bash +export DESIRECORE_URL='https://your-instance.example' +export DESIRECORE_TOKEN='your-existing-owner-token' + +node taskboards.mjs list +node taskboards.mjs query --filter '{"statuses":["pending","inProgress"]}' --page-size 50 +node taskboards.mjs board --board-id your-board-id +node taskboards.mjs task --board-id your-board-id --task-id your-task-id +node taskboards.mjs handoff --board-id your-board-id --task-id your-task-id +node taskboards.mjs activity --board-id your-board-id --task-id your-task-id --page-size 50 +node taskboards.mjs resolve --run-id your-run-id +``` + +示例验证握手后输出 `{kind, instanceId, data}`。`query` 和 `activity` 自动续读,保留每页原始信封在 `data.pages`,默认最多 100 页,可用 `--max-pages` 调整;查询冲突或分页异常时不输出半份结果,提示从首屏重读。它不自动重试、不去重关联服务摘要,也不执行任何任务动作。输出可能包含任务正文和业务关联信息,请妥善保管。 + +## 错误处理 + +TaskBoard 领域错误使用 `{ok:false,error:{code,message,retryable,...}}`;实例认证和服务就绪错误可能使用[通用错误形状](./03-errors-and-troubleshooting.md)。 + +| HTTP / 错误码 | 处理方式 | +|---|---| +| `401` / `unauthenticated` | 检查实例令牌及其可信用户身份,不通过请求体自报身份 | +| `403 capabilityDisabled` | 在客户端启用全局 TaskBoard 能力;`retryable:false` 时不要自动循环 | +| `403 forbidden` | 检查看板访问权限和 ID;不能据此区分任意 ID 是否实际存在 | +| `400 invalidRequest` | 检查字段、类型、游标条件及活动修订范围 | +| `409 versionConflict`(查询翻页) | 丢弃旧页和游标,按原筛选从首屏重读 | +| `404 taskNotFound` / `boardNotFound`(活动读取等) | 重新读取可见看板和任务,不自动创建或重放任务 | +| `503 migrationFailed` / `500 internalError` | 保存路径、状态、错误码和时间供管理员排查,避免记录令牌或业务正文 | + +读取可以重建查询缓存或完成存储维护,但不会因此发起模型请求或任务执行。接口字段和完整 DTO 见 [OpenAPI 下载](./07-openapi.md)。 diff --git a/docs/07-api/12-object-storage.md b/docs/07-api/12-object-storage.md new file mode 100644 index 0000000..5e796d5 --- /dev/null +++ b/docs/07-api/12-object-storage.md @@ -0,0 +1,179 @@ +--- +title: 对象存储读取 +description: 使用已配置的 S3 兼容连接读取连接摘要、分页浏览对象,并取得下载链接或文件内容。 +slug: /api/object-storage +sidebar_position: 12 +keywords: [API, S3, 对象存储, 下载, 分页, 预签名链接] +--- + +# 对象存储读取 + +通过这三个接口,外部程序可以使用实例已有的 S3 兼容连接浏览对象、取得下载链接或下载内容。先完成[连接与认证](./01-connection-and-auth.md),所有请求使用实例的 `X-Agent-Service-Token`。 + +## 使用条件 + +- 当前用户已在“我的应用”中安装并启用“对象存储”。关闭、卸载或无法确认可用状态时,请求返回 `403 capabilityDisabled`。 +- 在客户端的 **资源 → 对象存储** 中添加连接,配置存储桶及其访问凭据;执行读取前确认该账号拥有列出、读取目标对象的供应商权限。 +- 调用方使用实例已有连接的 `id`。请求中不提交 AK/SK,连接摘要也不返回凭据;实例令牌可访问该实例配置的连接,不是某个对象或前缀的独立访问授权。 + +握手中的 `capabilities.objectStorageCapability` 表示实例支持对象存储的用户级开关,不表示当前用户已启用,也不证明某个连接可用。以接口实际响应为准。 + +| 用途 | 接口 | +|---|---| +| 查找连接 ID 与配置摘要 | `GET /api/s3/connections` | +| 按前缀分页浏览对象 | `GET /api/s3/list` | +| 取得链接或文件字节 | `GET /api/s3/download` | + +## 连接摘要 {#connections} + +### `GET /api/s3/connections` + +无查询参数。返回配置中的连接摘要;没有连接时返回 `200` 和空数组。 + +```bash +curl --silent --show-error \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + "${BASE_URL}/api/s3/connections" +``` + +```json +{ + "success": true, + "connections": [ + { + "id": "storage-main", + "label": "项目文件", + "bucket": "project-files", + "region": "us-east-1", + "status": "connected", + "is_default": true, + "has_public_url": true + } + ] +} +``` + +`status` 是连接保存的状态,不是本次实时探测结果;`has_public_url` 只表示已配置公开访问地址。摘要存在不保证凭据仍有效、对象可读或最终使用公开链接。 + +后续请求可带 `connection_id`。省略时按**默认连接 → 首个 `connected` 连接 → 首个连接**选择;若选中的连接缺少配置或凭据,返回 `400 NO_CONNECTION`,不会逐个重试其他连接。为避免默认连接变化造成读取范围变化,集成程序应保存并显式传递选定的 ID。 + +## 浏览对象 {#list-objects} + +### `GET /api/s3/list` + +| 查询参数 | 规则 | +|---|---| +| `connection_id` | 可选,连接 ID;省略时按上述顺序选择 | +| `prefix` | 可选,对象键前缀,例如 `reports/` | +| `delimiter` | 默认 `/`,将下一层公共前缀放入 `common_prefixes`;传空字符串可不按目录分组 | +| `max_keys` | 默认 `100`,有效范围 `1`–`1000`;建议传十进制整数 | +| `continuation_token` | 可选,原样传回上一页的 `next_continuation_token` | + +```bash +curl --silent --show-error --get \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + --data-urlencode 'connection_id=storage-main' \ + --data-urlencode 'prefix=reports/' \ + --data-urlencode 'max_keys=100' \ + "${BASE_URL}/api/s3/list" +``` + +```json +{ + "success": true, + "objects": [ + { "key": "reports/summary.txt", "size": 120, "last_modified": "2026-10-25T00:00:00.000Z" } + ], + "common_prefixes": [{ "prefix": "reports/archive/" }], + "key_count": 2, + "is_truncated": true, + "next_continuation_token": "opaque-provider-token", + "bucket": "project-files", + "prefix": "reports/" +} +``` + +`objects` 是对象项,`common_prefixes` 是分组前缀,均不是文件系统路径。`key_count` 沿用供应商的页计数,可能包含公共前缀,不是总量,也不保证等于 `objects.length`。`last_modified` 沿用供应商的时间字符串,缺失时可能为空。 + +当 `is_truncated` 为 `true` 时,保持同一连接、前缀、分隔符和页大小,原样带回令牌继续读取。不要解码令牌或用对象数量计算下一页。末页通常省略 `next_continuation_token`,不是返回 `null`;没有非空 `prefix` 时也省略 `prefix`。若提示还有后续页却没有令牌,停止并报告异常,避免循环。分页期间对象可能变化,接口不提供固定快照或总数。 + +服务端对 `max_keys` 先作数值转换;缺省、`0` 或无法转换时使用 `100`,之后夹限到 `1`–`1000`,不承诺对小数或格式错误统一返回 `400`。示例程序会提前拒绝非整数。 + +## 下载对象 {#download-object} + +### `GET /api/s3/download` + +| 查询参数 | 规则 | +|---|---| +| `key` | 必填,完整对象键,保留大小写及 `/` | +| `connection_id` | 可选,连接 ID | +| `mode` | 推荐显式传 `link` 或 `binary`,默认 `link`;只有精确的 `binary` 值返回文件,其他值按链接模式处理 | +| `expires_in` | 链接模式的签名有效秒数,默认 `3600`,范围 `60`–`604800`;公开链接不使用此有效期 | + +### 取得链接 + +```bash +curl --silent --show-error --get \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + --data-urlencode 'connection_id=storage-main' \ + --data-urlencode 'key=reports/summary.txt' \ + --data-urlencode 'mode=link' \ + --data-urlencode 'expires_in=3600' \ + "${BASE_URL}/api/s3/download" +``` + +```json +{ + "success": true, + "key": "reports/summary.txt", + "download_url": "https://files.example.com/reports/summary.txt", + "url_type": "public", + "content_type": "text/plain", + "content_length": 120 +} +``` + +- `url_type: public`:已配置公开地址且本次可达性检查通过;响应省略 `expires_in`。API 不为该链接设置过期时间,实际可用性仍由对象和公开访问配置决定。 +- `url_type: presigned`:返回有时限的签名链接,并包含 `expires_in`。公开地址未配置或不可达时会使用此方式;链接仍可能因凭据失效、供应商策略或对象变化提前不可用。 +- `content_type`、`content_length` 可省略,例如供应商未提供元数据或长度为零。`expires_in` 的数值转换方式与列表类似:缺省、`0` 或无法转换时用 `3600`,然后夹限到 `60`–`604800`。 + +下载链接可能授予持有者读取权限,请限制转发范围。访问 S3/CDN 链接时不要附带实例的 `X-Agent-Service-Token`;这里的链接不是[附件媒体的签名地址](./09-attachments-and-media.md)。 + +### 直接下载字节 + +`mode=binary` 成功返回文件内容,而不是 JSON。响应带 `Content-Type`(缺失时为 `application/octet-stream`)、`Content-Length` 和附件下载的 `Content-Disposition`。对象可能本身就是 JSON、HTML 或其他任意内容,不能仅凭 MIME 类型当作 API 状态对象解析。当前下载接口不提供 Range 或断点续传合同。 + +使用下方示例显式指定本地输出文件,不根据服务端文件名选择落盘路径。收到非成功 HTTP 状态时先处理错误,不把错误正文保存成目标文件。 + +## 可运行读取示例 {#s3-example} + +下载 **[s3.mjs](pathname:///api-examples/s3.mjs)**,使用 Node.js 22 或更高版本,无需安装 npm 包: + +```bash +export DESIRECORE_URL='https://your-instance.example' +export DESIRECORE_TOKEN='your-existing-owner-token' + +node s3.mjs connections +node s3.mjs list --connection-id storage-main --prefix reports/ --all +node s3.mjs download --connection-id storage-main --key reports/summary.txt +node s3.mjs download --connection-id storage-main --key reports/summary.txt \ + --mode binary --output ./summary.txt --max-bytes 10485760 +``` + +示例先验证实例握手,再读取对象接口。列表默认只读一页,`--all` 最多读取 100 页(可用 `--max-pages` 调整);缺少或重复令牌、达到页数上限时明确退出,已输出的页面仍是部分结果。`--delimiter ''` 可读取不按目录分组的列表。 + +链接模式只输出链接,不自动访问;二进制模式只向已验证的实例请求,拒绝重定向,默认最多保存 100 MiB,使用明确的输出路径且不覆盖已有文件。发生中途下载错误时删除本次未完成的文件。示例不重试、不上传或删除对象;输出的对象键和签名链接请按业务隐私保管。 + +## 常见错误 + +| 响应 | 处理方式 | +|---|---| +| `401` | 检查实例地址与实例令牌 | +| `403 capabilityDisabled` | 在“我的应用”检查对象存储是否安装、启用;响应为 `{error, code, capability, retryable}`,没有 `success` 字段,`retryable` 为 `false` | +| `400 INVALID_REQUEST` | 下载缺少非空 `key`;使用查询参数传递完整键 | +| `400 NO_CONNECTION` | 检查连接 ID、S3 配置及已保存凭据;摘要存在也可能发生此错误 | +| `404 NOT_FOUND`(链接模式) | 实例未能确认对象可读;检查键、供应商读取权限及连接状态,不能只据此认定对象已删除 | +| `500 LIST_FAILED` / `DOWNLOAD_FAILED` | 检查供应商权限、网络和实例日志;二进制模式的对象缺失也可能返回 `DOWNLOAD_FAILED` | +| `503 service_not_ready` | 根据 `Retry-After` 等待后重读 | + +S3 领域错误使用 `{success:false, error, code}`;能力错误及认证错误外壳不同。错误文字可能包含连接或供应商信息,排查时保留状态与代码,分享日志前移除令牌和签名链接。更多连接排查见[错误与排查](./03-errors-and-troubleshooting.md),结构定义见 [OpenAPI 下载](./07-openapi.md)。 diff --git a/docs/07-api/index.md b/docs/07-api/index.md index 67bb19d..4a6dc53 100644 --- a/docs/07-api/index.md +++ b/docs/07-api/index.md @@ -24,6 +24,8 @@ keywords: [API, 集成, Webhook, Agent Service, 接入, 认证] | 将 HTTP 合同导入工具 | 选择对应服务的规范文件 | [OpenAPI 下载](./07-openapi.md) | | 让 DesireCore 调用你的业务服务 | 通过 MCP、工具或技能连接 | [MCP 服务器连接](../02-user-guide/09-capabilities/03-mcp-integration.md)与[集成能力概览](../02-user-guide/09-capabilities/06-integrations-overview.md) | | 排查上游网络、观察近期工具调用和已记录用量 | 实例所有者访问权限 | [实例诊断与用量](./10-observability.md) | +| 读取任务板、任务详情及活动 | 实例所有者访问权限、任务板能力 | [TaskBoard 读取](./11-taskboards.md) | +| 列出对象或从已配置存储下载 | 实例所有者访问权限、对象存储能力和已配置连接 | [S3 对象读取](./12-object-storage.md) | ## 凭据与结果 diff --git a/i18n/en/docusaurus-plugin-content-docs/current/07-api/03-errors-and-troubleshooting.md b/i18n/en/docusaurus-plugin-content-docs/current/07-api/03-errors-and-troubleshooting.md index 5ac58e6..b961938 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/07-api/03-errors-and-troubleshooting.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/07-api/03-errors-and-troubleshooting.md @@ -113,3 +113,10 @@ A per-Run message's `offset` is a byte position, whereas the pagination request' ## A connected client cannot reach upstream services A connected Socket.IO client does not establish that the instance can reach a model provider. Read [upstream network health](./10-observability.md#network-health) and inspect `reason`, `since` and provider scope. `unknown` is not offline, and a historical `online` does not prove the current request succeeded. Recent calls and usage are observations, not instructions to replay work. + + +## TaskBoard and object-storage reads + +A successful authenticated handshake does not enable optional applications or validate a storage connection. For `403 capabilityDisabled`, check the corresponding application in My Apps; do not keep retrying the same disabled capability. TaskBoard access also depends on the readable resource scope, and a missing or inaccessible board may return `403`. Read the [TaskBoard error and paging rules](./11-taskboards.md) before treating a missing task or a pagination conflict as an empty successful query. + +S3 link and binary modes have different response and error contracts. A link-mode `404 NOT_FOUND` means the instance could not confirm the object is readable; check the key, provider permissions and connection. Binary mode can report the same provider failure as `500 DOWNLOAD_FAILED`. Follow [object-storage reads](./12-object-storage.md), preserve the HTTP status, and keep signed download links out of shared logs. diff --git a/i18n/en/docusaurus-plugin-content-docs/current/07-api/07-openapi.md b/i18n/en/docusaurus-plugin-content-docs/current/07-api/07-openapi.md index 1378353..19372ff 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/07-api/07-openapi.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/07-api/07-openapi.md @@ -49,6 +49,16 @@ Also available: **[model protocol proxy OpenAPI JSON](pathname:///openapi/compat | `POST /api/media/upload` | Upload an image, audio, or video | [Attachments and media](./09-attachments-and-media.md) | | `POST /api/media/access-urls` | Issue short-lived media access URLs | [Attachments and media](./09-attachments-and-media.md) | | `GET /media/{filename}` | Read signed media, including byte ranges | [Attachments and media](./09-attachments-and-media.md) | +| `GET /api/taskboards` | List readable task boards | [TaskBoard reads](./11-taskboards.md) | +| `POST /api/taskboards/query` | Query boards and tasks | [TaskBoard reads](./11-taskboards.md) | +| `GET /api/taskboards/{id}` | Read a board projection | [TaskBoard reads](./11-taskboards.md) | +| `GET /api/taskboards/{id}/tasks/{taskId}` | Read one task | [TaskBoard reads](./11-taskboards.md) | +| `GET /api/taskboards/{id}/activity` | Read board handoff evidence | [TaskBoard reads](./11-taskboards.md) | +| `GET /api/taskboards/{id}/tasks/{taskId}/activity` | Page task activity | [TaskBoard reads](./11-taskboards.md) | +| `POST /api/taskboards/session-board/resolve` | Resolve an existing session board | [TaskBoard reads](./11-taskboards.md) | +| `GET /api/s3/connections` | Read configured connection summaries | [S3 object reads](./12-object-storage.md) | +| `GET /api/s3/list` | List stored objects | [S3 object reads](./12-object-storage.md) | +| `GET /api/s3/download` | Download bytes or request an object link | [S3 object reads](./12-object-storage.md) | Braces identify path parameters to replace, corresponding to colon notation in the endpoint guides. Webhook management requires `capabilities.webhookManagement: true` from the authenticated handshake and the instance owner's credential. Older instances without that capability use administrator-enabled and verified events. Run lists and details use the deployment owner's instance read access; receipt access additionally checks trusted-user ownership. @@ -107,3 +117,5 @@ The file's `info.version` is the version of this HTTP interface description. It For native media playback, use the complete `/media/{filename}?expires=…&signature=…` URL returned by the issuing endpoint. This read endpoint uses its short-lived signature rather than an instance token. Attachment downloads still require instance authentication. Consult each guide for history, topic, and media access rules. Runnable observation and merged-history readers are **[observe.mjs](pathname:///api-examples/observe.mjs)** and **[history.mjs](pathname:///api-examples/history.mjs)**. Restart merged-history reads on revision conflicts instead of combining old pages. Usage aggregates are not final provider invoices. + +TaskBoard and S3 read examples are available as **[taskboards.mjs](pathname:///api-examples/taskboards.mjs)** and **[s3.mjs](pathname:///api-examples/s3.mjs)**. Follow the corresponding guides for opaque cursors, activity revisions, object continuation tokens and download modes. diff --git a/i18n/en/docusaurus-plugin-content-docs/current/07-api/11-taskboards.md b/i18n/en/docusaurus-plugin-content-docs/current/07-api/11-taskboards.md new file mode 100644 index 0000000..6ccc803 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/07-api/11-taskboards.md @@ -0,0 +1,249 @@ +--- +title: Reading task boards +description: Query authorized TaskBoards, task details, handoff evidence and paginated activity, and resolve a conversation's default board. +slug: /api/taskboards +sidebar_position: 11 +keywords: [API, TaskBoard, tasks, activity, pagination] +--- + +# Reading task boards + +Use the existing instance address and `X-Agent-Service-Token` to integrate task lists, details and activity into your own dashboard. Complete [connection verification](./01-connection-and-auth.md) first. The current user's global TaskBoard capability must be enabled. When disabled, requests return `403 capabilityDisabled`; enable it in the client or contact the instance administrator. + +These endpoints use the authenticated user and their actual team permissions. HTTP reads act as that user's human actor. Body fields such as `userId`, `actor` or a claimed team do not grant access. Knowing a Board, Task, Run or Session ID does not expand visibility. Both `POST` endpoints below are queries: they create no board or task and start no Agent execution. + +| Purpose | Method and path | +|---|---| +| List visible board summaries | `GET /api/taskboards` | +| Filter and paginate tasks across boards | `POST /api/taskboards/query` | +| Read a board and its visible tasks | `GET /api/taskboards/:id` | +| Read one task by stable ID | `GET /api/taskboards/:id/tasks/:taskId` | +| Read handoff evidence for tasks on a board | `GET /api/taskboards/:id/activity` | +| Paginate one task's activity | `GET /api/taskboards/:id/tasks/:taskId/activity` | +| Resolve a conversation's default board | `POST /api/taskboards/session-board/resolve` | + +Use IDs returned by the API, encoding each URL path segment separately. Board revision, Task version, query cursor and activity position serve different purposes and are not interchangeable. + +## Board list and details {#boards} + +### `GET /api/taskboards` + +No query parameters or request body. Returns `{schemaVersion: 3, generatedAt, boards}` containing only visible boards, without pagination. Each board summary contains: + +| Field | Meaning | +|---|---| +| `meta` | Board ID, name, `scope`, `policy`, `revision` and timestamps | +| `statusCounts` | `{status, count}` for tasks that are neither archived nor deleted | +| `openCount` | Among those tasks, the number outside the board policy's terminal or hidden categories | +| `archiveAction` | Current user's action snapshot: `enabled`, `archived`, `expectedBoardRevision`, and possibly a `reason` when unavailable | + +The list can include archived boards. `openCount: 0` does not mean the board is absent or all historical tasks were deleted. `scope.type` can be `user`, `team`, `agent` or `session`. Aggregate reads omit team boards after access is revoked. + +```bash +curl --silent --show-error \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + "${BASE_URL}/api/taskboards" +``` + +```json +{ + "schemaVersion": 3, + "generatedAt": "2026-10-25T08:00:00.000Z", + "boards": [] +} +``` + +### `GET /api/taskboards/:id` + +No query parameters or request body. An existing board returns `{schemaVersion: 3, exists: true, meta, tasks, comments, resourceBindings, availableActions, allDone}`, optionally with `fanOutDispatches`. + +- `tasks` excludes deleted tasks and can include archived tasks. `comments` contains their current comment projections. Redacted bodies become `[redacted]`, without original body revisions, attachments or Mention content. +- `availableActions` contains `{taskRef, actions}` entries calculated for the current actor. Reading this snapshot grants no authority; later actions must be authorized again. Do not infer actions solely from status, assignee or `ownerUserId`. +- `allDone` means no unarchived, undeleted task remains open. It can be `true` for an empty board and does not prove any external work succeeded. +- `resourceBindings`, `runRefs`, `receiptRefs` and deep links are references, not credentials for related resources. A child task in `fanOutDispatches` does not prove its Agent Run has started. +- `claim.token` is omitted unless the current actor owns that claim. The query endpoint always omits it. Treat read results as private business data. + +Direct reads of arbitrary missing or inaccessible Board IDs usually return `403 forbidden`. Some authorized scope aliases for boards not yet created can return `200` with `exists: false`. Handle both cases; missing resources do not uniformly return 404. + +## Query tasks across boards {#query} + +### `POST /api/taskboards/query` + +The body is a JSON object; omitting it is equivalent to `{}`. Only `filter`, `sort`, `pageSize` and `cursor` are accepted. Unknown properties return `400 invalidRequest`. + +```json +{ + "filter": { + "boardIds": ["your-board-id"], + "statuses": ["pending", "inProgress"], + "taskArchive": "active" + }, + "sort": {"field": "updatedAt", "direction": "desc"}, + "pageSize": 50 +} +``` + +| Parameter | Rules | +|---|---| +| `pageSize` | Integer `1..200`; default `50` | +| `sort` | Both `field` and `direction` required. Field: `updatedAt`, `createdAt`, `dueAt`, `priority`, `position` or `subject`; direction: `asc` or `desc`. Default: `updatedAt desc` | +| `cursor` | Opaque string from the previous response, passed unchanged; not an offset or editable JSON | +| `filter` | Optional object combining the filters below | + +| Filter | Values and meaning | +|---|---| +| `keyword` | Up to 512 characters; case insensitive match against task ID, subject, description, labels and related references | +| `boardIds` | Up to 256 unique Board IDs; missing/invisible boards are silently omitted. An empty array does not restrict boards | +| `scopeTypes` | `user`, `team`, `agent`, `session` | +| `statuses` | `backlog`, `pending`, `inProgress`, `blocked`, `inReview`, `completed`, `failed`, `canceled`, `deleted`; deleted tasks are excluded from the current visible query | +| `priorities` | `high`, `normal`, `low` | +| `labels` / `labelMatch` | Up to 128 unique labels; `any` (default) or `all` | +| `participantIds` | Up to 256 unique IDs matching assignee, claim actor, reviewer, contributors or watchers | +| `reviewStates` | `notSubmitted`, `awaitingReview`, `accepted`, `changesRequested` | +| `due` | `overdue`, `upcoming`, `none`, `any`; evaluated against the first page's query time, without scheduling work | +| `sourceOwnerTypes` | `automation`, `workflow`, `conversation`, `connector`, `import`, `manual` | +| `integrationTypes` | `automation`, `workflow`, `conversation`, `workdir`, `run` | +| `boardArchive` / `taskArchive` | Independently `active` (default), `archived` or `any` | + +Arrays reject duplicate values. An empty result can be: + +```json +{ + "schemaVersion": 3, + "generatedAt": "2026-10-25T08:00:00.000Z", + "items": [], + "pageSize": 50, + "hasMore": false, + "totalCount": 0, + "statusCounts": [] +} +``` + +Each actual item contains `board`, `task`, `commentCount`, `deepLink`, `availableActions` and `archiveAction`. `deepLink.route` is an in-app route, not an API path. `totalCount` and `statusCounts` cover all matching results rather than only the current page. Optional `indexGeneration` is diagnostic metadata, not a Board revision or authorization proof. + +### Pagination + +1. Omit `cursor` on the first request and retain the original filters and sorting. +2. Only when `hasMore: true`, pass `nextCursor` unchanged to the next request with the same filters and sorting. The final page omits `nextCursor`. +3. A cursor used with different filters or sorting returns `400 invalidRequest`. Changed visible data or versions can invalidate it with `409 versionConflict`; discard collected pages and restart from the first page. + +Every page checks current access. A cursor grants no cross-user access, lasting snapshot or incremental change log. Do not decode, construct or reuse it across users. + +## Single task details {#task-detail} + +### `GET /api/taskboards/:id/tasks/:taskId` + +No query parameters or request body. An existing task returns `{schemaVersion: 3, exists: true, board, task, comments, resourceBindings, availableActions, archiveAction, deepLink}`. Unlike board details, `availableActions` is directly an array of actions and `comments` includes only this task. + +After Board access is authorized, an absent or deleted task returns: + +```json +{"schemaVersion": 3, "exists": false} +``` + +The Board access check can still return `403` first. Do not automatically create a task on `exists: false` or reuse one task's `version` for another. + +## Handoff evidence and task activity {#activity} + +These paths return different information. + +### `GET /api/taskboards/:id/activity` + +Returns **handoff evidence** for tasks on the board, not a complete board event stream. Optional query parameter `taskId` restricts it to one task. There are no pagination parameters. Returns `{evidence: [...]}`; each item has `eventId`, `kind: "handoff"`, `taskRef`, `fromActor`, `toActor`, `occurredAt`, and optional `reason`. + +```json +{"evidence": []} +``` + +An empty array can mean no handoff, or that the filtered task is outside the visible set. It does not prove the task has no other activity. Use the single-task endpoint below for comments, state changes and other activity. + +### `GET /api/taskboards/:id/tasks/:taskId/activity` + +| Query parameter | Rules | +|---|---| +| `pageSize` | Integer `1..100`; default `50`; bounds task event candidates scanned for this page | +| `afterRevision` | Nonnegative safe integer; default `0`; excludes task events at or before this Board revision | +| `throughRevision` | Positive safe integer; omit on the first page to capture the current Board history prefix, then retain the returned value | + +`afterRevision > 0` requires `throughRevision`. `afterRevision > throughRevision`, a prefix beyond the current Board revision, or unknown query parameters return `400 invalidRequest`. Send canonical decimal strings rather than relying on coercion details. + +```bash +curl --silent --show-error --get \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + --data-urlencode 'pageSize=50' \ + "${BASE_URL}/api/taskboards/${BOARD_ID}/tasks/${TASK_ID}/activity" +``` + +```json +{ + "schemaVersion": 1, + "taskRef": {"boardId": "your-board-id", "taskId": "your-task-id"}, + "generatedAt": "2026-10-25T08:00:00.000Z", + "items": [], + "page": {"pageSize": 50, "afterRevision": 0, "throughRevision": 12, "nextAfterRevision": null} +} +``` + +`items.source` distinguishes: + +- `taskEvent`: contains `taskEvent`; ordered by ascending Board revision within the page and bounded by `throughRevision`. +- `ownerProjection`: contains `ownerProjection`; a current summary from a related service, returned before task events. It can repeat or change across pages and is not frozen by the history prefix. It is not archived event content. + +Use `activityId` to identify repeated display entries. `items.length` can be below `pageSize` or exceed it because of extra summaries. Even an empty page can have a continuation. Only `page.nextAfterRevision: null` means the fixed prefix is exhausted. Otherwise pass that position with the same `throughRevision`. Do not derive the next page from the last timestamp, Task version or item count. + +New task events do not expand a captured prefix. To refresh, restart at the first page and capture a new `throughRevision`. The prefix freezes task history, not current permissions or related service summaries. An absent task can return `404 taskNotFound`; the preceding Board check can still return `403`. + +## Resolve a conversation's default board {#session-board} + +### `POST /api/taskboards/session-board/resolve` + +Body limit: 4 KiB. Provide at least `runId` or `sessionId`; no other properties are accepted. IDs must match `^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$`. + +```json +{"runId": "your-run-id", "sessionId": "your-current-session-id"} +``` + +Prefer the Run ID. The service uses saved session ownership to find sessions belonging to the same Run, so rebuilding a session does not make its board appear lost: + +1. Exactly one visible board for the current Session wins, even if archived. +2. Multiple boards for the current Session produce no default. +3. When the current Session has no board, select the most recently updated unarchived board from another Session of the same Run. + +`sessionId` only affects current-session priority; that priority is ignored when the service proves it belongs to another Run. With only `sessionId`, the service can resolve its Run. If saved ownership cannot be read, selection may use only the current Session. A caller cannot supply a session list to expand visibility. + +Success uses the board detail shape. When no default can be selected, returns `200 {"schemaVersion":3,"exists":false}` without creating a board. This does not establish that the Run is absent, no other board exists, or the conversation has ended. + +## Runnable example {#taskboards-example} + +Download **[taskboards.mjs](pathname:///api-examples/taskboards.mjs)**. Requires Node.js 22 or later, with no npm packages: + +```bash +export DESIRECORE_URL='https://your-instance.example' +export DESIRECORE_TOKEN='your-existing-owner-token' + +node taskboards.mjs list +node taskboards.mjs query --filter '{"statuses":["pending","inProgress"]}' --page-size 50 +node taskboards.mjs board --board-id your-board-id +node taskboards.mjs task --board-id your-board-id --task-id your-task-id +node taskboards.mjs handoff --board-id your-board-id --task-id your-task-id +node taskboards.mjs activity --board-id your-board-id --task-id your-task-id --page-size 50 +node taskboards.mjs resolve --run-id your-run-id +``` + +After verifying the handshake, the example emits `{kind, instanceId, data}`. `query` and `activity` follow continuations and retain original page envelopes in `data.pages`, up to 100 pages by default; adjust with `--max-pages`. Conflicts or invalid pagination emit no partial result and require a fresh first-page read. The example does not automatically retry, deduplicate related service summaries or execute task actions. Output can contain task content and business references; store it securely. + +## Error handling + +TaskBoard domain errors use `{ok:false,error:{code,message,retryable,...}}`. Instance authentication and readiness errors can use the [common error shapes](./03-errors-and-troubleshooting.md). + +| HTTP / code | Action | +|---|---| +| `401` / `unauthenticated` | Check the instance token and its trusted user identity; do not claim identity in the body | +| `403 capabilityDisabled` | Enable the global TaskBoard capability in the client; do not automatically loop on `retryable:false` | +| `403 forbidden` | Check Board access and ID; this does not distinguish existence of arbitrary IDs | +| `400 invalidRequest` | Check properties, types, cursor conditions and activity revision bounds | +| `409 versionConflict` (query pagination) | Discard old pages and cursor; restart with the original filters | +| `404 taskNotFound` / `boardNotFound` (activity reads and similar cases) | Reload visible boards/tasks; do not automatically create or replay tasks | +| `503 migrationFailed` / `500 internalError` | Save path, status, code and time for the administrator; omit tokens and business content | + +Reads can rebuild query caches or perform storage maintenance without starting a model request or task execution. See [OpenAPI downloads](./07-openapi.md) for complete DTOs. diff --git a/i18n/en/docusaurus-plugin-content-docs/current/07-api/12-object-storage.md b/i18n/en/docusaurus-plugin-content-docs/current/07-api/12-object-storage.md new file mode 100644 index 0000000..cdfe323 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/07-api/12-object-storage.md @@ -0,0 +1,179 @@ +--- +title: Object storage reads +description: Use configured S3 compatible connections to read connection summaries, browse objects by page, and obtain download links or file content. +slug: /api/object-storage +sidebar_position: 12 +keywords: [API, S3, object storage, download, pagination, presigned URL] +--- + +# Object storage reads + +These three endpoints let an external program browse objects and obtain links or content using the instance's existing S3 compatible connections. Complete [connection and authentication](./01-connection-and-auth.md) first. Every request uses the instance's `X-Agent-Service-Token`. + +## Requirements + +- The current user has installed and enabled **Object Storage** in **My Apps**. Disabled, uninstalled, or uncertain availability returns `403 capabilityDisabled`. +- Add a connection in the client under **Resources → Object Storage**, with the bucket and access credentials. Confirm that the provider account can list and read the target objects. +- Use an existing connection's `id`. Requests do not submit an access key or secret key, and connection summaries do not return credentials. An instance token accesses the instance's configured connections; it is not a separate grant for one object or prefix. + +The handshake's `capabilities.objectStorageCapability` indicates support for the user capability switch. It does not mean the current user enabled the feature or that a connection works. Use the actual endpoint response. + +| Purpose | Endpoint | +|---|---| +| Find connection IDs and configuration summaries | `GET /api/s3/connections` | +| Browse objects by prefix and page | `GET /api/s3/list` | +| Obtain a link or file bytes | `GET /api/s3/download` | + +## Connection summaries {#connections} + +### `GET /api/s3/connections` + +No query parameters. Returns configured connection summaries. An instance with no connections returns `200` with an empty array. + +```bash +curl --silent --show-error \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + "${BASE_URL}/api/s3/connections" +``` + +```json +{ + "success": true, + "connections": [ + { + "id": "storage-main", + "label": "Project files", + "bucket": "project-files", + "region": "us-east-1", + "status": "connected", + "is_default": true, + "has_public_url": true + } + ] +} +``` + +`status` is the saved connection state, not a fresh probe. `has_public_url` only means a public address is configured. A summary does not prove that credentials are valid, an object is readable, or a download will use a public URL. + +Pass `connection_id` on subsequent requests. When omitted, selection follows **default connection → first `connected` connection → first connection**. If the selected connection lacks configuration or credentials, the request returns `400 NO_CONNECTION` without trying other connections. Store and explicitly pass the selected ID so that a default change does not change your read target. + +## Browse objects {#list-objects} + +### `GET /api/s3/list` + +| Query parameter | Rules | +|---|---| +| `connection_id` | Optional connection ID; selection when omitted follows the order above | +| `prefix` | Optional object key prefix, such as `reports/` | +| `delimiter` | Defaults to `/`, grouping the next level into `common_prefixes`; an empty string disables directory grouping | +| `max_keys` | Defaults to `100`; range `1`–`1000`; use a decimal integer | +| `continuation_token` | Optional; pass back the previous page's `next_continuation_token` unchanged | + +```bash +curl --silent --show-error --get \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + --data-urlencode 'connection_id=storage-main' \ + --data-urlencode 'prefix=reports/' \ + --data-urlencode 'max_keys=100' \ + "${BASE_URL}/api/s3/list" +``` + +```json +{ + "success": true, + "objects": [ + { "key": "reports/summary.txt", "size": 120, "last_modified": "2026-10-25T00:00:00.000Z" } + ], + "common_prefixes": [{ "prefix": "reports/archive/" }], + "key_count": 2, + "is_truncated": true, + "next_continuation_token": "opaque-provider-token", + "bucket": "project-files", + "prefix": "reports/" +} +``` + +`objects` contains object entries and `common_prefixes` contains grouped prefixes. They are not filesystem paths. `key_count` preserves the provider's page count and may include common prefixes; it is neither a total nor necessarily `objects.length`. `last_modified` preserves the provider's timestamp string and can be empty when unavailable. + +When `is_truncated` is `true`, keep the connection, prefix, delimiter and page size unchanged, and pass the token back. Do not decode it or calculate a next page from object counts. The final page normally omits `next_continuation_token`, rather than returning `null`. `prefix` is also omitted without a nonempty prefix. If a truncated page has no token, stop and report the inconsistency. Objects may change between pages; there is no fixed snapshot or total count contract. + +The server converts `max_keys` to a number. An omitted value, `0`, or a value that cannot be converted uses `100`, then the result is clamped to `1`–`1000`. Decimal fractions and malformed input are not uniformly rejected with `400`. The example rejects nonintegers before connecting. + +## Download objects {#download-object} + +### `GET /api/s3/download` + +| Query parameter | Rules | +|---|---| +| `key` | Required full object key; preserve case and `/` | +| `connection_id` | Optional connection ID | +| `mode` | Explicit `link` or `binary` is recommended; defaults to `link`. Only the exact value `binary` returns bytes; other values use link mode | +| `expires_in` | Signed link lifetime in seconds; defaults to `3600`, range `60`–`604800`; does not set a lifetime on public URLs | + +### Obtain a link + +```bash +curl --silent --show-error --get \ + --header "X-Agent-Service-Token: ${AGENT_SERVICE_TOKEN}" \ + --data-urlencode 'connection_id=storage-main' \ + --data-urlencode 'key=reports/summary.txt' \ + --data-urlencode 'mode=link' \ + --data-urlencode 'expires_in=3600' \ + "${BASE_URL}/api/s3/download" +``` + +```json +{ + "success": true, + "key": "reports/summary.txt", + "download_url": "https://files.example.com/reports/summary.txt", + "url_type": "public", + "content_type": "text/plain", + "content_length": 120 +} +``` + +- `url_type: public`: a configured public address passed this request's reachability check. The response omits `expires_in`. The API sets no expiry on this link; availability still depends on the object and public access configuration. +- `url_type: presigned`: a time limited signed URL, with `expires_in`. This is used when a public address is absent or unreachable. Credentials, provider policy or object changes may invalidate it earlier. +- `content_type` and `content_length` can be omitted, for example when provider metadata is absent or length is zero. Numeric conversion for `expires_in` follows the same pattern as listing: an omitted value, `0`, or an unconvertible value uses `3600`, then it is clamped to `60`–`604800`. + +A download URL can grant read access to its holder. Limit sharing, and do not send the instance's `X-Agent-Service-Token` to an S3/CDN URL. These URLs are separate from [signed attachment media URLs](./09-attachments-and-media.md). + +### Download bytes directly + +With `mode=binary`, success returns file content instead of a JSON wrapper. Headers include `Content-Type` (falling back to `application/octet-stream`), `Content-Length`, and an attachment `Content-Disposition`. An object can itself contain JSON, HTML or any other data; its MIME type alone does not identify an API status response. This endpoint provides no Range or resume contract. + +The example below requires an explicit local filename and does not derive a destination from a server filename. Handle unsuccessful HTTP responses before writing a target file. + +## Runnable read example {#s3-example} + +Download **[s3.mjs](pathname:///api-examples/s3.mjs)**. Use Node.js 22 or later; no npm packages are required: + +```bash +export DESIRECORE_URL='https://your-instance.example' +export DESIRECORE_TOKEN='your-existing-owner-token' + +node s3.mjs connections +node s3.mjs list --connection-id storage-main --prefix reports/ --all +node s3.mjs download --connection-id storage-main --key reports/summary.txt +node s3.mjs download --connection-id storage-main --key reports/summary.txt \ + --mode binary --output ./summary.txt --max-bytes 10485760 +``` + +The example verifies the instance handshake before reading storage endpoints. Listing reads one page by default. `--all` reads at most 100 pages, adjustable with `--max-pages`. Missing or repeated tokens and page limits produce an error; previously emitted pages remain partial results. Use `--delimiter ''` for an ungrouped list. + +Link mode prints the returned URL without visiting it. Binary mode requests only the verified instance, refuses redirects, defaults to a 100 MiB limit, requires an explicit output path and never overwrites existing files. An interrupted download removes the incomplete file created by that request. The example does not retry, upload or delete objects. Protect object keys and signed links in its output as business data. + +## Common errors + +| Response | Action | +|---|---| +| `401` | Check the instance address and token | +| `403 capabilityDisabled` | Check that Object Storage is installed and enabled in My Apps. The body is `{error, code, capability, retryable}` without `success`; `retryable` is `false` | +| `400 INVALID_REQUEST` | Download requires a nonempty `key` query parameter | +| `400 NO_CONNECTION` | Check the connection ID, S3 configuration and saved credentials; an existing summary can still produce this error | +| `404 NOT_FOUND` (link mode) | The instance could not confirm readable object metadata. Check the key, provider read permissions and connection state; this response alone does not prove deletion | +| `500 LIST_FAILED` / `DOWNLOAD_FAILED` | Check provider permissions, network and instance logs; a missing object in binary mode can also return `DOWNLOAD_FAILED` | +| `503 service_not_ready` | Wait according to `Retry-After` before reading again | + +S3 domain errors use `{success:false, error, code}`. Capability and authentication errors have different wrappers. Error text can contain connection or provider details. Keep status and code when investigating, and remove tokens and signed links before sharing logs. See [errors and troubleshooting](./03-errors-and-troubleshooting.md) and the [OpenAPI downloads](./07-openapi.md) for more details. diff --git a/i18n/en/docusaurus-plugin-content-docs/current/07-api/index.md b/i18n/en/docusaurus-plugin-content-docs/current/07-api/index.md index e842081..8e0af73 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/07-api/index.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/07-api/index.md @@ -24,6 +24,8 @@ Connect your scripts or services to DesireCore using the address and credentials | Import HTTP contracts into a tool | Select the spec for your service | [OpenAPI downloads](./07-openapi.md) | | Let DesireCore call your business service | MCP server, tools or skills | [MCP integration](../02-user-guide/09-capabilities/03-mcp-integration.md) and [integration overview](../02-user-guide/09-capabilities/06-integrations-overview.md) | | Diagnose upstream connectivity and observe recent tool calls and recorded usage | Instance-owner access | [Instance Diagnostics and Usage](./10-observability.md) | +| Read task boards, task details and activity | Instance owner access and the TaskBoard capability | [TaskBoard reads](./11-taskboards.md) | +| List objects or download from configured storage | Instance owner access, Object Storage capability and a configured connection | [S3 object reads](./12-object-storage.md) | ## Credentials and results diff --git a/scripts/check-api-docs.mjs b/scripts/check-api-docs.mjs index aee384e..2712f0c 100644 --- a/scripts/check-api-docs.mjs +++ b/scripts/check-api-docs.mjs @@ -215,7 +215,7 @@ async function checkSpecs(catalog, pages) { } async function checkClientExampleMirror() { - for (const [maintained, published] of [['client.mjs', 'agent-client.mjs'], ['history.mjs', 'history.mjs'], ['attachments.mjs', 'attachments.mjs'], ['observe.mjs', 'observe.mjs']]) { + for (const [maintained, published] of [['client.mjs', 'agent-client.mjs'], ['history.mjs', 'history.mjs'], ['attachments.mjs', 'attachments.mjs'], ['observe.mjs', 'observe.mjs'], ['taskboards.mjs', 'taskboards.mjs'], ['s3.mjs', 's3.mjs']]) { const sourceFile = `api/examples/agent-client/${maintained}` const source = await readBytes(sourceFile) const downloadFile = `static/api-examples/${published}` diff --git a/static/api-examples/s3.mjs b/static/api-examples/s3.mjs new file mode 100644 index 0000000..d7f8e3d --- /dev/null +++ b/static/api-examples/s3.mjs @@ -0,0 +1,133 @@ +#!/usr/bin/env node +// Standalone object-storage reads. Node.js 22+, no packages required. +import { open, unlink } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const HELP = `Usage: + node s3.mjs connections + node s3.mjs list [--connection-id ID] [--prefix PATH] [--delimiter /] [--max-keys 100] [--all] [--max-pages 100] + node s3.mjs download --key KEY [--connection-id ID] [--mode link] [--expires-in 3600] + node s3.mjs download --key KEY --mode binary --output FILE [--max-bytes 104857600] +Environment: DESIRECORE_URL, DESIRECORE_TOKEN, optional DESIRECORE_S3_CONNECTION_ID. +Options: --timeout-ms 10000. Remote origins must use HTTPS. +Returned links are printed, never followed; signed links can grant access. Existing output files are never overwritten. +`; + +function originFor(value) { + let url; + try { url = new URL(value); } catch { throw new Error('Set DESIRECORE_URL to the instance origin.'); } + if (url.username || url.password || url.pathname !== '/' || url.search || url.hash + || (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)))) { + throw new Error('Use an HTTPS origin without credentials, path, query or fragment; HTTP is allowed only on loopback.'); + } + return url.origin; +} +function integer(value, label, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) { + if (value === undefined) return fallback; + if (!/^[0-9]+$/.test(String(value)) || !Number.isSafeInteger(Number(value)) || Number(value) < min || Number(value) > max) throw new Error(`${label} must be a decimal integer from ${min} to ${max}.`); + return Number(value); +} +export function parseS3Args(argv) { + if (argv.includes('--help') || argv.includes('-h')) return { help: true }; + const options = { command: argv[0] }; + const flags = { '--connection-id': 'connectionId', '--prefix': 'prefix', '--delimiter': 'delimiter', '--max-keys': 'maxKeys', '--continuation-token': 'continuationToken', '--max-pages': 'maxPages', '--key': 'key', '--mode': 'mode', '--expires-in': 'expiresIn', '--output': 'output', '--max-bytes': 'maxBytes', '--timeout-ms': 'timeoutMs' }; + for (let i = 1; i < argv.length; i++) { + if (argv[i] === '--all') { if (options.all) throw new Error(HELP); options.all = true; continue; } + const key = flags[argv[i]]; + if (!key || Object.hasOwn(options, key) || argv[i + 1] === undefined || argv[i + 1].startsWith('--')) throw new Error(HELP); + options[key] = argv[++i]; + } + return options; +} +export async function runS3(input, { env = process.env, emit = value => process.stdout.write(`${JSON.stringify(value)}\n`) } = {}) { + if (input.help) { process.stdout.write(HELP); return; } + const { command } = input; + if (!['connections', 'list', 'download'].includes(command)) throw new Error(HELP); + const origin = originFor(env.DESIRECORE_URL); + if (typeof env.DESIRECORE_TOKEN !== 'string' || !env.DESIRECORE_TOKEN.trim()) throw new Error('Set DESIRECORE_TOKEN.'); + const allowed = new Set(['command', 'help', 'timeoutMs', ...(command === 'list' ? ['connectionId', 'prefix', 'delimiter', 'maxKeys', 'continuationToken', 'all', 'maxPages'] : command === 'download' ? ['connectionId', 'key', 'mode', 'expiresIn', 'output', 'maxBytes'] : [])]); + if (Object.keys(input).some(key => !allowed.has(key))) throw new Error('An option does not apply to this command.'); + const connectionId = command === 'connections' ? undefined : input.connectionId ?? env.DESIRECORE_S3_CONNECTION_ID; + if (connectionId !== undefined && (typeof connectionId !== 'string' || !connectionId)) throw new Error('Use a nonempty connection ID returned by this instance.'); + const timeoutMs = integer(input.timeoutMs, 'timeoutMs', 10_000, 1, 600_000); + const maxKeys = integer(input.maxKeys, 'maxKeys', 100, 1, 1000); + const maxPages = integer(input.maxPages, 'maxPages', 100, 1, 10000); + const expiresIn = integer(input.expiresIn, 'expiresIn', 3600, 60, 604800); + const maxBytes = integer(input.maxBytes, 'maxBytes', 104857600); + const mode = input.mode ?? 'link'; + if (command === 'download' && (typeof input.key !== 'string' || !input.key || !['link', 'binary'].includes(mode))) throw new Error('Download requires --key and mode link or binary.'); + if (command === 'download' && (mode === 'binary' ? !input.output || input.expiresIn !== undefined : input.output !== undefined || input.maxBytes !== undefined)) throw new Error('Binary mode requires --output and accepts --max-bytes; link mode accepts --expires-in.'); + if (input.maxPages !== undefined && !input.all) throw new Error('--max-pages requires --all.'); + async function request(path) { + return fetch(origin + path, { headers: { 'X-Agent-Service-Token': env.DESIRECORE_TOKEN }, redirect: 'error', signal: AbortSignal.timeout(timeoutMs) }); + } + async function json(response) { + if (!response.headers.get('content-type')?.toLowerCase().includes('application/json')) throw new Error(`HTTP ${response.status}: expected JSON from the instance API.`); + let value; + try { value = await response.json(); } catch { throw new Error(`HTTP ${response.status}: invalid JSON from the instance API.`); } + if (!response.ok) { + const code = typeof value?.code === 'string' && /^[A-Za-z0-9_-]{1,100}$/.test(value.code) ? ` ${value.code}` : ''; + throw new Error(`Object-storage read rejected: HTTP ${response.status}${code}`); + } + if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Expected an API response object.'); + return value; + } + const metadata = await json(await request('/api/client-handshake')); + if (metadata.service !== 'desirecore-agent-service' || metadata.apiProtocolVersion !== '1' || typeof metadata.instanceId !== 'string' || !metadata.instanceId) throw new Error('The handshake did not identify a compatible DesireCore instance.'); + const query = new URLSearchParams(); + if (connectionId !== undefined) query.set('connection_id', connectionId); + const suffix = () => query.size ? `?${query}` : ''; + if (command === 'connections') { + const data = await json(await request('/api/s3/connections')); + if (data.success !== true || !Array.isArray(data.connections)) throw new Error('Invalid connection summary.'); + emit({ kind: command, instanceId: metadata.instanceId, data }); + } else if (command === 'list') { + query.set('max_keys', String(maxKeys)); + for (const [key, value] of Object.entries({ prefix: input.prefix, delimiter: input.delimiter, continuation_token: input.continuationToken })) if (value !== undefined) query.set(key, value); + const seen = new Set(input.continuationToken ? [input.continuationToken] : []); + for (let page = 1; ; page++) { + const data = await json(await request('/api/s3/list' + suffix())); + if (data.success !== true || !Array.isArray(data.objects) || !Array.isArray(data.common_prefixes) || typeof data.is_truncated !== 'boolean') throw new Error('Invalid object list.'); + emit({ kind: command, instanceId: metadata.instanceId, page, data }); + if (!input.all || !data.is_truncated) break; + const next = data.next_continuation_token; + if (typeof next !== 'string' || !next || seen.has(next)) throw new Error('Missing or repeated continuation token; stop rather than loop.'); + if (page >= maxPages) throw new Error('Reached --max-pages with more results remaining; output is partial.'); + seen.add(next); query.set('continuation_token', next); + } + } else { + query.set('key', input.key); query.set('mode', mode); + if (mode === 'link') { + query.set('expires_in', String(expiresIn)); + const data = await json(await request('/api/s3/download' + suffix())); + if (data.success !== true || !['public', 'presigned'].includes(data.url_type) || typeof data.download_url !== 'string') throw new Error('Invalid download link response.'); + emit({ kind: 'download-link', instanceId: metadata.instanceId, data }); + return; + } + const response = await request('/api/s3/download' + suffix()); + if (!response.ok) { await json(response); return; } + if (!response.body) throw new Error('Binary response has no body.'); + const advertised = response.headers.get('content-length'); + if (advertised !== null && Number(advertised) > maxBytes) { await response.body.cancel(); throw new Error('Binary response exceeds --max-bytes.'); } + let file; + let bytes = 0; + try { + file = await open(resolve(input.output), 'wx', 0o600); + for await (const chunk of response.body) { + bytes += chunk.byteLength; + if (bytes > maxBytes) throw new Error('Binary response exceeds --max-bytes.'); + await file.writeFile(chunk); + } + await file.close(); file = undefined; + } catch (error) { + await response.body.cancel().catch(() => {}); + if (file) { await file.close(); await unlink(resolve(input.output)); } + throw error; + } + emit({ kind: 'download-binary', instanceId: metadata.instanceId, output: resolve(input.output), bytes, contentType: response.headers.get('content-type') }); + } +} +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + runS3(parseS3Args(process.argv.slice(2))).catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }); +} diff --git a/static/api-examples/taskboards.mjs b/static/api-examples/taskboards.mjs new file mode 100644 index 0000000..ef9b1ad --- /dev/null +++ b/static/api-examples/taskboards.mjs @@ -0,0 +1,120 @@ +#!/usr/bin/env node +// TaskBoard reads. Node.js 22+, no packages required. Output may contain private task content. +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const HELP = `Usage: + node taskboards.mjs list + node taskboards.mjs query [--filter '{"statuses":["pending"]}'] [--sort '{"field":"updatedAt","direction":"desc"}'] [--page-size 50] + node taskboards.mjs board --board-id ID + node taskboards.mjs task --board-id ID --task-id ID + node taskboards.mjs handoff --board-id ID [--task-id ID] + node taskboards.mjs activity --board-id ID --task-id ID [--page-size 50] + node taskboards.mjs resolve --run-id ID [--session-id ID] +Environment: DESIRECORE_URL, DESIRECORE_TOKEN. +Options: --timeout-ms 10000; query/activity: --max-pages 100. +Remote origins must use HTTPS. Reads only; no automatic retry or mutation. +`; + +export function parseTaskboardsArgs(argv) { + if (argv.includes('--help') || argv.includes('-h')) return { help: true }; + const input = { command: argv[0] }; + const flags = { '--board-id': 'boardId', '--task-id': 'taskId', '--run-id': 'runId', '--session-id': 'sessionId', '--filter': 'filter', '--sort': 'sort', '--page-size': 'pageSize', '--max-pages': 'maxPages', '--timeout-ms': 'timeoutMs' }; + for (let i = 1; i < argv.length; i++) { + const key = flags[argv[i]]; + if (!key || Object.hasOwn(input, key) || !argv[i + 1] || argv[i + 1].startsWith('--')) throw new Error(HELP); + input[key] = argv[++i]; + } + return input; +} + +export async function runTaskboards(input, { env = process.env, emit = value => process.stdout.write(`${JSON.stringify(value)}\n`) } = {}) { + if (input.help) { process.stdout.write(HELP); return; } + const options = { + list: [], query: ['filter', 'sort', 'pageSize', 'maxPages'], board: ['boardId'], task: ['boardId', 'taskId'], + handoff: ['boardId', 'taskId'], activity: ['boardId', 'taskId', 'pageSize', 'maxPages'], resolve: ['runId', 'sessionId'], + }; + if (!Object.hasOwn(options, input.command)) throw new Error(HELP); + if (Object.keys(input).some(key => !['command', 'help', 'timeoutMs', ...options[input.command]].includes(key))) throw new Error('An option does not apply to this command.'); + let origin; + try { + const url = new URL(env.DESIRECORE_URL); + if (url.username || url.password || url.pathname !== '/' || url.search || url.hash || (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)))) throw new Error(); + origin = url.origin; + } catch { throw new Error('Set DESIRECORE_URL to an HTTPS origin; HTTP is allowed only on loopback.'); } + if (typeof env.DESIRECORE_TOKEN !== 'string' || !env.DESIRECORE_TOKEN.trim()) throw new Error('Set DESIRECORE_TOKEN.'); + const integer = (value, fallback, max, label) => { + if (value === undefined) return fallback; + if (!/^[1-9][0-9]*$/.test(String(value)) || !Number.isSafeInteger(Number(value)) || Number(value) > max) throw new Error(`${label} must be a positive decimal integer no greater than ${max}.`); + return Number(value); + }; + const timeoutMs = integer(input.timeoutMs, 10000, 2147483647, 'timeoutMs'); + const maxPages = integer(input.maxPages, 100, 10000, 'maxPages'); + const pageSize = integer(input.pageSize, 50, input.command === 'activity' ? 100 : 200, 'pageSize'); + const required = ['board', 'task', 'handoff', 'activity'].includes(input.command) ? ['boardId'] : []; + if (['task', 'activity'].includes(input.command)) required.push('taskId'); + for (const key of ['boardId', 'taskId']) if ((required.includes(key) && !input[key]) || (input[key] !== undefined && (typeof input[key] !== 'string' || !input[key].trim() || input[key].length > 256 || /[\/\\\u0000-\u001f]/.test(input[key]) || ['.', '..'].includes(input[key])))) throw new Error(`Use the ${key} returned by the instance.`); + for (const key of ['runId', 'sessionId']) if (input[key] !== undefined && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(input[key])) throw new Error(`Invalid ${key}.`); + if (input.command === 'resolve' && !input.runId && !input.sessionId) throw new Error('resolve requires --run-id or --session-id.'); + const object = (raw, label) => { + if (raw === undefined) return undefined; + let value; + try { value = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { throw new Error(`${label} must be a JSON object.`); } + if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be a JSON object.`); + return value; + }; + const filter = object(input.filter, 'filter'), sort = object(input.sort, 'sort'); + async function read(path, body) { + const response = await fetch(origin + path, { method: body === undefined ? 'GET' : 'POST', headers: { 'X-Agent-Service-Token': env.DESIRECORE_TOKEN, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) }, body: body === undefined ? undefined : JSON.stringify(body), redirect: 'error', signal: AbortSignal.timeout(timeoutMs) }); + if (!response.headers.get('content-type')?.toLowerCase().includes('application/json')) throw new Error(`HTTP ${response.status}: expected JSON from the instance API.`); + const data = await response.json(); + if (!response.ok || data?.ok === false) { + const rawCode = data?.error?.code ?? data?.code; + const code = typeof rawCode === 'string' && /^[A-Za-z0-9_-]{1,100}$/.test(rawCode) ? ` ${rawCode}` : ''; + throw new Error(`TaskBoard read rejected: HTTP ${response.status}${code}${response.status === 409 ? '; discard these pages and restart from the first page.' : ''}`); + } + if (!data || typeof data !== 'object' || Array.isArray(data)) throw new Error('Expected a TaskBoard response object.'); + return data; + } + const metadata = await read('/api/client-handshake'); + if (metadata.service !== 'desirecore-agent-service' || metadata.apiProtocolVersion !== '1' || typeof metadata.instanceId !== 'string' || !metadata.instanceId) throw new Error('The handshake did not identify a compatible DesireCore instance.'); + const base = `/api/taskboards/${encodeURIComponent(input.boardId ?? '')}`; + let data; + if (input.command === 'query' || input.command === 'activity') { + const pages = []; + let cursor, afterRevision = 0, throughRevision; + const seen = new Set(); + for (let count = 0; ; count++) { + if (count >= maxPages) throw new Error('maxPages reached; no partial result was emitted. Increase --max-pages and restart.'); + if (input.command === 'query') { + const page = await read('/api/taskboards/query', { ...(filter === undefined ? {} : { filter }), ...(sort === undefined ? {} : { sort }), pageSize, ...(cursor === undefined ? {} : { cursor }) }); + if (page.schemaVersion !== 3 || !Array.isArray(page.items) || typeof page.hasMore !== 'boolean') throw new Error('Invalid TaskBoard query response.'); + pages.push(page); + if (!page.hasMore) break; + if (typeof page.nextCursor !== 'string' || !page.nextCursor || seen.has(page.nextCursor)) throw new Error('Missing or repeated query cursor; no partial result was emitted.'); + cursor = page.nextCursor; seen.add(cursor); + } else { + const query = new URLSearchParams({ pageSize: String(pageSize), afterRevision: String(afterRevision), ...(throughRevision === undefined ? {} : { throughRevision: String(throughRevision) }) }); + const page = await read(`${base}/tasks/${encodeURIComponent(input.taskId)}/activity?${query}`); + if (page.schemaVersion !== 1 || !Array.isArray(page.items) || !page.page || !Number.isSafeInteger(page.page.throughRevision) || page.page.throughRevision < 1 || page.page.afterRevision !== afterRevision) throw new Error('Invalid Task activity page.'); + if (throughRevision !== undefined && page.page.throughRevision !== throughRevision) throw new Error('Task activity history prefix changed; no partial result was emitted.'); + throughRevision = page.page.throughRevision; + pages.push(page); + const next = page.page.nextAfterRevision; + if (next === null) break; + if (!Number.isSafeInteger(next) || next <= afterRevision || next > throughRevision) throw new Error('Task activity page did not advance; no partial result was emitted.'); + afterRevision = next; + } + } + data = { pages }; + } else { + const paths = { list: '/api/taskboards', board: base, task: `${base}/tasks/${encodeURIComponent(input.taskId ?? '')}`, handoff: `${base}/activity${input.taskId ? `?${new URLSearchParams({ taskId: input.taskId })}` : ''}`, resolve: '/api/taskboards/session-board/resolve' }; + data = await read(paths[input.command], input.command === 'resolve' ? { ...(input.runId ? { runId: input.runId } : {}), ...(input.sessionId ? { sessionId: input.sessionId } : {}) } : undefined); + if (input.command === 'list' ? data.schemaVersion !== 3 || !Array.isArray(data.boards) : input.command === 'handoff' ? !Array.isArray(data.evidence) : data.schemaVersion !== 3 || typeof data.exists !== 'boolean') throw new Error('Invalid TaskBoard read response.'); + } + emit({ kind: input.command, instanceId: metadata.instanceId, data }); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { + runTaskboards(parseTaskboardsArgs(process.argv.slice(2))).catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }); +} diff --git a/static/openapi/agent-service.json b/static/openapi/agent-service.json index 05f9b58..cb98c55 100644 --- a/static/openapi/agent-service.json +++ b/static/openapi/agent-service.json @@ -3,7 +3,7 @@ "jsonSchemaDialect": "http://json-schema.org/draft-07/schema#", "info": { "title": "DesireCore Agent Service — public HTTP subset", - "version": "1.2.0", + "version": "1.3.0", "description": "Selected externally callable endpoints for an administrator-provided standalone instance. This document is a subset of the service, and is not an application release declaration. Consult the linked guides for deployment, authorization and asynchronous results." }, "servers": [ @@ -50,6 +50,14 @@ { "name": "Observability", "description": "Upstream connectivity, user-scoped recent tool calls and recorded usage. Observations do not authorize or replay work." + }, + { + "name": "TaskBoard", + "description": "Owner-authorized task boards, query pages, handoff evidence and fixed-prefix task activity." + }, + { + "name": "Object storage", + "description": "Read connection summaries, provider object lists, links and bytes through existing S3 compatible connections." } ], "paths": { @@ -5195,5192 +5203,14884 @@ } } } - } - }, - "components": { - "securitySchemes": { - "InstanceToken": { - "type": "apiKey", - "in": "header", - "name": "X-Agent-Service-Token", - "description": "Existing owner credential supplied by the instance administrator." - }, - "WebhookSecret": { - "type": "http", - "scheme": "bearer", - "description": "Dedicated secret for the target Agent Webhook; not a provider API key or JWT." - }, - "SignedMediaSignature": { - "type": "apiKey", - "in": "query", - "name": "signature", - "description": "Five-minute HMAC-SHA-256 capability issued by POST /api/media/access-urls. 43-character canonical base64url signature bound to the filename and required expires query value. Reusable until expiry; no instance header required for this GET. Expired/invalid links return 401. Application authority, configured Origin restrictions, and readiness are checked separately." - } }, - "schemas": { - "ClientHandshake": { - "type": "object", - "required": [ - "service", - "apiProtocolVersion", - "serverVersion", - "instanceId", - "deploymentMode", - "clientCompatibility", - "capabilities", - "authentication", - "serverTime" + "/api/taskboards": { + "get": { + "operationId": "listTaskBoards", + "tags": [ + "TaskBoard" ], - "properties": { - "service": { - "type": "string", - "const": "desirecore-agent-service", - "description": "固定服务标记,用于拒绝非 DesireCore Agent Service 目标。" + "summary": "List visible TaskBoard summaries", + "description": "No pagination. Can include archived boards; counts exclude archived/deleted tasks. Team and owner visibility is rechecked. Requires the authenticated deployment owner's global TaskBoard capability and current Board/Team access; HTTP acts as the authenticated human actor. No Task execution is started.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/taskboards" + }, + "security": [ + { + "InstanceToken": [] + } + ], + "responses": { + "200": { + "description": "Authorized synchronous read result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardListResponse" + } + } + } }, - "apiProtocolVersion": { - "type": "string", - "const": "1", - "description": "客户端握手和实例连接协议版本;不等同于应用版本。" + "400": { + "description": "Invalid request, cursor, filter/sort combination or activity range.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "serverVersion": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", - "description": "当前 Agent Service 随客户端发布的语义版本号。" + "401": { + "description": "Missing/invalid instance credential or trusted user identity. Authentication errors can use the common service error shape." }, - "instanceId": { - "type": "string", - "pattern": "^inst_[A-Za-z0-9_-]{32}$", - "description": "Agent Service 数据空间的稳定不透明身份。由服务端随机生成并持久化,不得由路径、用户、设备或 token 推导。" + "403": { + "description": "TaskBoard capability disabled or Board access denied. Arbitrary missing Board IDs can also return forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "deploymentMode": { - "type": "string", - "enum": [ - "embedded", - "owner", - "external" - ], - "description": "Agent Service 运行形态:embedded=Electron 内嵌;owner=standalone owner 模式;external=standalone external 模式。" + "500": { + "description": "TaskBoard internal read failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "clientCompatibility": { - "type": "object", - "required": [ - "minimumVersion", - "maximumVersion" - ], - "properties": { - "minimumVersion": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", - "description": "允许连接本服务的最低 DesireCore 客户端版本(含)。" - }, - "maximumVersion": { - "type": [ - "string", - "null" - ], - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", - "description": "允许连接本服务的最高 DesireCore 客户端版本(含);null 表示不设上限。" + "503": { + "description": "Storage migration/readiness failure. General service readiness errors can use the common service error shape." + } + } + } + }, + "/api/taskboards/query": { + "post": { + "operationId": "queryTaskBoards", + "tags": [ + "TaskBoard" + ], + "summary": "Query visible tasks across boards", + "description": "Read-only POST. Default pageSize 50 (1..200), updatedAt desc, active boards/tasks. Opaque nextCursor must retain filters/sort. 409 versionConflict requires discarding old pages and restarting. Claim tokens are always omitted; current liveness can change without invalidating the cursor. Missing/invisible boardIds are silently omitted. Requires the authenticated deployment owner's global TaskBoard capability and current Board/Team access; HTTP acts as the authenticated human actor. No Task execution is started.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/taskboards" + }, + "security": [ + { + "InstanceToken": [] + } + ], + "responses": { + "200": { + "description": "Authorized synchronous read result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardQueryResponse" + } } - }, - "additionalProperties": false + } }, - "capabilities": { - "type": "object", - "required": [ - "workflow", - "mediaSignedUrl" - ], - "properties": { - "workflow": { - "type": "boolean", - "description": "当前部署是否具备可用且完整配置的 Workflow Service 能力。" - }, - "pdfRendering": { - "type": "boolean", - "description": "服务端宿主是否提供 HTML 转 PDF 引擎;缺席表示未知,不代表客户端具有打印传输。" - }, - "mediaSignedUrl": { - "type": "boolean", - "description": "当前实例是否可以签发供原生媒体标签使用的短期 HTTPS URL。" - }, - "queryRequestIdEcho": { - "type": "boolean", - "description": "服务端是否回显客户端 query 的 clientRequestId;缺席表示未知,客户端只能按实际事件观察,禁止按版本推断。" - }, - "webhookManagement": { - "type": "boolean", - "description": "是否支持所有者通过 Webhook 管理 API 配置事件、选择现有工具和轮换 secret;缺席或 false 表示该配置入口不可用。" - }, - "localPairing": { - "type": "boolean" - }, - "localAttachSecret": { - "type": "boolean" - }, - "objectStorageCapability": { - "type": "boolean" - }, - "mailCapability": { - "type": "boolean" + "400": { + "description": "Invalid request, cursor, filter/sort combination or activity range.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } } - }, - "additionalProperties": { - "type": "boolean", - "description": "未来新增的布尔能力位;旧客户端应忽略不认识的能力名。" } }, - "authentication": { - "type": "object", - "required": [ - "kind", - "scopes" - ], - "properties": { - "kind": { - "type": "string", - "enum": [ - "embedded-capability", - "instance-token" - ], - "description": "本次服务形态使用的客户端鉴权类型:embedded-capability 或 standalone instance-token。" - }, - "scopes": { - "type": "array", - "minItems": 1, - "maxItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "enum": [ - "owner" - ] - }, - "description": "认证凭据授予的实例作用域;第一版固定为 owner。" + "401": { + "description": "Missing/invalid instance credential or trusted user identity. Authentication errors can use the common service error shape." + }, + "403": { + "description": "TaskBoard capability disabled or Board access denied. Arbitrary missing Board IDs can also return forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } } - }, - "additionalProperties": false + } }, - "serverTime": { - "type": "string", - "format": "date-time", - "description": "服务端生成响应时的 ISO 8601 时间,用于诊断时钟偏差,不参与身份判断。" + "409": { + "description": "Visible canonical data changed; discard all old pages and restart from the first page.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "product": { - "type": "object", - "required": [ - "editionId", - "appId", - "productName", - "buildKind", - "appVersion" - ], - "properties": { - "editionId": { - "type": "string", - "pattern": "^[a-z0-9][a-z0-9-]*$", - "maxLength": 128, - "description": "服务所在应用的发行版 ID;同机接入发现据此标注候选实例属于哪个发行版。" - }, - "appId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "服务所在应用的 appId(macOS bundle id、Windows AUMID 前缀)。" - }, - "productName": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "description": "服务所在应用的显示名。" - }, - "buildKind": { - "type": "string", - "enum": [ - "dev", - "prod" - ], - "description": "服务所在应用的构建形态:Electron 打包版为 prod,开发版为 dev;standalone 按运行环境判断。" - }, - "appVersion": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", - "description": "服务所在应用的版本号,与 serverVersion 相同。" - } - }, - "additionalProperties": false - }, - "capabilityDiagnostics": { - "type": "object", - "required": [ - "pdfRendering" - ], - "properties": { - "pdfRendering": { - "type": [ - "string", - "null" - ], - "enum": [ - "pdf_renderer_unavailable", - null - ] + "500": { + "description": "TaskBoard internal read failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } } - }, - "additionalProperties": false, - "description": "仅协商 deployment-capabilities 时返回;原因是部署能力事实,不含配置、路径或凭据。" + } }, - "queryDispatch": { - "type": "object", - "required": [ - "serverInstanceId" - ], - "properties": { - "serverInstanceId": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "description": "当前服务进程启动代际,与 recovery.serverInstanceId 同源;重启即变化,不能当持久 instanceId 或认证凭证。" + "503": { + "description": "Storage migration/readiness failure. General service readiness errors can use the common service error shape." + } + }, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardQueryRequest" } - }, - "additionalProperties": false, - "description": "仅协商 query-dispatch 扩展时返回;旧服务缺席本字段时不得伪造进程身份。" + } } + } + } + }, + "/api/taskboards/{id}": { + "get": { + "operationId": "getTaskBoard", + "tags": [ + "TaskBoard" + ], + "summary": "Read an authorized board", + "description": "Actor-specific projection. Deleted tasks excluded; archived tasks may remain. Redacted comment bodies and private claim tokens are withheld as documented. Direct arbitrary missing/invisible Board IDs normally return 403. Authorized missing scope aliases can return exists:false. Requires the authenticated deployment owner's global TaskBoard capability and current Board/Team access; HTTP acts as the authenticated human actor. No Task execution is started.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/taskboards" }, - "additionalProperties": false - }, - "WebhookRequest": { - "type": "object", - "required": [ - "event" + "security": [ + { + "InstanceToken": [] + } ], - "properties": { - "event": { - "type": "string", - "description": "事件标识,如 \"github.push\"" + "responses": { + "200": { + "description": "Authorized synchronous read result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardDetailResponse" + } + } + } }, - "payload": { - "type": "object", - "additionalProperties": {}, - "description": "事件负载,prompt_template 中的 {{key}} 从此处提取" - } - }, - "additionalProperties": false - }, - "WebhookAccepted": { - "type": "object", - "properties": { - "accepted": { - "type": "boolean", - "const": true + "400": { + "description": "Invalid request, cursor, filter/sort combination or activity range.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "agentId": { - "type": "string" + "401": { + "description": "Missing/invalid instance credential or trusted user identity. Authentication errors can use the common service error shape." }, - "event": { - "type": "string" - } - }, - "required": [ - "accepted", - "agentId", - "event" - ], - "additionalProperties": false - }, - "InstanceAuthenticationError": { - "type": "object", - "properties": { - "error": { - "type": "string", - "const": "unauthorized" + "403": { + "description": "TaskBoard capability disabled or Board access denied. Arbitrary missing Board IDs can also return forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "message": { - "type": "string" + "500": { + "description": "TaskBoard internal read failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } + }, + "503": { + "description": "Storage migration/readiness failure. General service readiness errors can use the common service error shape." } }, - "required": [ - "error", - "message" - ], - "additionalProperties": false - }, - "OriginRejection": { - "type": "object", - "properties": { - "error": { - "type": "string", - "const": "origin_not_allowed" - }, - "message": { - "type": "string" + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Opaque id returned by this instance; encode as one path segment.", + "schema": { + "type": "string", + "minLength": 1 + } } + ] + } + }, + "/api/taskboards/{id}/tasks/{taskId}": { + "get": { + "operationId": "getTaskBoardTask", + "tags": [ + "TaskBoard" + ], + "summary": "Read one authorized task", + "description": "Board access check runs first. A missing/deleted task within an authorized Board returns 200 exists:false. Available actions are a current actor snapshot, not a grant. Requires the authenticated deployment owner's global TaskBoard capability and current Board/Team access; HTTP acts as the authenticated human actor. No Task execution is started.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/taskboards" }, - "required": [ - "error" + "security": [ + { + "InstanceToken": [] + } ], - "additionalProperties": true - }, - "ServiceStarting": { - "type": "object", - "properties": { - "status": { - "type": "string", - "const": "starting" + "responses": { + "200": { + "description": "Authorized synchronous read result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskDirectReadResponse" + } + } + } }, - "service": { - "type": "string", - "const": "agent-service" + "400": { + "description": "Invalid request, cursor, filter/sort combination or activity range.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "code": { - "type": "string", - "const": "service_not_ready" + "401": { + "description": "Missing/invalid instance credential or trusted user identity. Authentication errors can use the common service error shape." + }, + "403": { + "description": "TaskBoard capability disabled or Board access denied. Arbitrary missing Board IDs can also return forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } + }, + "500": { + "description": "TaskBoard internal read failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } + }, + "503": { + "description": "Storage migration/readiness failure. General service readiness errors can use the common service error shape." } }, - "required": [ - "status", - "service", - "code" - ], - "additionalProperties": false - }, - "InstanceTokenUninitialized": { - "type": "object", - "properties": { - "error": { - "type": "string", - "const": "instance_token_uninitialized" + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Opaque id returned by this instance; encode as one path segment.", + "schema": { + "type": "string", + "minLength": 1 + } }, - "message": { - "type": "string" + { + "name": "taskId", + "in": "path", + "required": true, + "description": "Opaque taskId returned by this instance; encode as one path segment.", + "schema": { + "type": "string", + "minLength": 1 + } } - }, - "required": [ - "error", - "message" + ] + } + }, + "/api/taskboards/{id}/activity": { + "get": { + "operationId": "listTaskBoardHandoffEvidence", + "tags": [ + "TaskBoard" ], - "additionalProperties": false - }, - "WebhookAuthenticationError": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Invalid webhook secret", - "Invalid embedded instance token" - ] - } + "summary": "Read task handoff evidence on a board", + "description": "Only handoff evidence; not a complete Board activity log. Optional taskId restricts visible tasks; no pagination. Requires the authenticated deployment owner's global TaskBoard capability and current Board/Team access; HTTP acts as the authenticated human actor. No Task execution is started.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/taskboards" }, - "required": [ - "error" - ], - "additionalProperties": false - }, - "WebhookSecretRequired": { - "type": "object", - "properties": { - "error": { - "type": "string", - "const": "webhook_secret_required" - }, - "message": { - "type": "string" + "security": [ + { + "InstanceToken": [] } - }, - "required": [ - "error", - "message" ], - "additionalProperties": false - }, - "WebhookNotFound": { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "Agent not found", - "Webhook not enabled for this agent" - ] + "responses": { + "200": { + "description": "Authorized synchronous read result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardHandoffEvidenceResponse" + } + } + } + }, + "400": { + "description": "Invalid request, cursor, filter/sort combination or activity range.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } + }, + "401": { + "description": "Missing/invalid instance credential or trusted user identity. Authentication errors can use the common service error shape." + }, + "403": { + "description": "TaskBoard capability disabled or Board access denied. Arbitrary missing Board IDs can also return forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } + }, + "500": { + "description": "TaskBoard internal read failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } + }, + "503": { + "description": "Storage migration/readiness failure. General service readiness errors can use the common service error shape." } }, - "required": [ - "error" - ], - "additionalProperties": false - }, - "WebhookInvalidBody": { - "type": "object", - "properties": { - "error": { - "type": "string", - "const": "Invalid request body" + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Opaque id returned by this instance; encode as one path segment.", + "schema": { + "type": "string", + "minLength": 1 + } }, - "details": { - "type": "array", - "items": {} + { + "name": "taskId", + "in": "query", + "required": false, + "schema": { + "type": "string", + "minLength": 1 + }, + "description": "Restrict handoff evidence to a currently visible task; no pagination." } - }, - "required": [ - "error", - "details" + ] + } + }, + "/api/taskboards/{id}/tasks/{taskId}/activity": { + "get": { + "operationId": "getTaskBoardTaskActivity", + "tags": [ + "TaskBoard" ], - "additionalProperties": false - }, - "WebhookUnknownEvent": { - "type": "object", - "properties": { - "error": { - "type": "string", - "pattern": "^Unknown event type: " - } + "summary": "Read a fixed prefix of task event history", + "description": "Canonical task events use Board revision continuation. First page captures throughRevision; preserve it with nextAfterRevision on all later pages. Positive afterRevision requires throughRevision. null nextAfterRevision means complete, even if items is empty earlier. pageSize bounds candidate scans, not total returned items. Owner projections are current summaries, can repeat/change on every page, and are not frozen by throughRevision. Requires the authenticated deployment owner's global TaskBoard capability and current Board/Team access; HTTP acts as the authenticated human actor. No Task execution is started.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/taskboards" }, - "required": [ - "error" - ], - "additionalProperties": false - }, - "WebhookConfigurationError": { - "type": "object", - "properties": { - "error": { - "type": "string", - "const": "Failed to parse agent.json" + "security": [ + { + "InstanceToken": [] } - }, - "required": [ - "error" ], - "additionalProperties": false - }, - "FrameworkError": { - "type": "object", - "properties": { - "statusCode": { - "type": "integer" + "responses": { + "200": { + "description": "Authorized synchronous read result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskActivityResponse" + } + } + } }, - "error": { - "type": "string" + "400": { + "description": "Invalid request, cursor, filter/sort combination or activity range.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "message": { - "type": "string" + "401": { + "description": "Missing/invalid instance credential or trusted user identity. Authentication errors can use the common service error shape." }, - "code": { - "type": "string" - } - }, - "required": [ - "statusCode", - "error", - "message" - ], - "additionalProperties": true - }, - "AgentSummary": { - "type": "object", - "required": [ - "id", - "name", - "skillsCount", - "toolsCount", - "heartbeatEnabled", - "hasGit", - "isCoreAgent" - ], - "properties": { - "id": { - "type": "string", - "description": "智能体 ID(目录名)" + "403": { + "description": "TaskBoard capability disabled or Board access denied. Arbitrary missing Board IDs can also return forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "name": { - "type": "string", - "description": "智能体显示名称" + "404": { + "description": "Task or Board no longer available after Board authorization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "description": { - "type": "string", - "description": "智能体描述" + "500": { + "description": "TaskBoard internal read failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "ui": { - "type": "object", - "description": "该智能体的界面呈现配置,仅作用于其会话,不改变平台设置", - "properties": { - "chat_bubble_mode": { - "type": "string", - "enum": [ - "flat", - "classic" - ], - "description": "消息样式:flat 为通栏,classic 为气泡;省略时跟随平台消息样式" - } - }, - "additionalProperties": false - }, - "isCoreAgent": { - "type": "boolean" - }, - "skillsCount": { - "type": "number", - "description": "技能数量", - "minimum": 0 - }, - "toolsCount": { - "type": "number", - "description": "工具数量", - "minimum": 0 - }, - "heartbeatEnabled": { - "type": "boolean", - "description": "心跳是否启用" - }, - "engine": { - "type": "string", - "description": "运行时引擎" - }, - "hasGit": { - "type": "boolean", - "description": "是否有 git 仓库" - }, - "hasRemote": { - "type": "boolean" - }, - "status": { - "type": "string", - "description": "Agent 运行时状态(从 AgentRegistry 获取)", - "enum": [ - "online", - "busy", - "idle", - "offline", - "error", - "recovery", - "shutdown" - ] + "503": { + "description": "Storage migration/readiness failure. General service readiness errors can use the common service error shape." + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Opaque id returned by this instance; encode as one path segment.", + "schema": { + "type": "string", + "minLength": 1 + } }, - "metadataError": { - "type": "string", - "enum": [ - "missing", - "unreadable", - "invalid_json", - "invalid_shape" - ] + { + "name": "taskId", + "in": "path", + "required": true, + "description": "Opaque taskId returned by this instance; encode as one path segment.", + "schema": { + "type": "string", + "minLength": 1 + } }, - "syncStatus": { - "type": "string", - "description": "Git 同步状态:synced(已同步)、ahead(本地有未推送提交)、behind(远程有更新)、conflict(存在冲突)、local_only(纯本地,无远程仓库)、uninitialized(未初始化 Git)", - "enum": [ - "synced", - "ahead", - "behind", - "conflict", - "local_only", - "uninitialized" - ] + { + "name": "pageSize", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50, + "description": "本页最多扫描的canonical事件候选数,缺省50;Owner当前摘要另行返回。" + }, + "description": "本页最多扫描的canonical事件候选数,缺省50;Owner当前摘要另行返回。" }, - "avatar": { - "type": "object", - "description": "头像配置(来自 agent.json)", - "properties": { - "char": { - "type": "string", - "description": "显示字符" - }, - "color": { - "type": "string", - "description": "色系键名", - "enum": [ - "green", - "blue", - "purple", - "orange", - "red" - ] - }, - "image": { - "type": "object", - "description": "图片头像配置(agent.json 原样透传)。**这里只说明配置存在,不代表文件可用**——判断能否渲染图片请看顶层 avatarImage", - "required": [ - "path" - ], - "properties": { - "path": { - "type": "string", - "description": "相对于 Agent 根目录的图片路径" - } - }, - "additionalProperties": false - }, - "model": { - "type": "object", - "description": "沉浸式交互使用的 Agent 自定义 3D 模型配置", - "required": [ - "path" - ], - "properties": { - "path": { - "type": "string", - "description": "相对于 Agent 根目录的 GLB 文件路径" - }, - "body": { - "type": "string", - "description": "TalkingHead 模型体型提示", - "enum": [ - "F", - "M" - ] - } - }, - "additionalProperties": false - } + { + "name": "afterRevision", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991, + "default": 0, + "description": "排除此Board revision及之前的事件;首请求缺省0,续页使用上一响应nextAfterRevision。" }, - "additionalProperties": false + "description": "排除此Board revision及之前的事件;首请求缺省0,续页使用上一响应nextAfterRevision。" }, - "avatarImage": { - "type": "object", - "required": [ - "version" - ], - "properties": { - "version": { - "type": "string", - "description": "缓存版本戳(`${size}-${mtimeMs}`),与该端点的 ETag 同源。值变化即表示图片已更换,客户端应丢弃旧缓存重新取图。不写进 agent.json:那会产生 git diff 噪声,且 Agent 直接覆盖图片文件时字段不会变,必然漂移。" - }, - "mimeType": { - "type": "string", - "description": "经 magic bytes 嗅探确认的真实 MIME 类型(不信任扩展名)", - "enum": [ - "image/png", - "image/jpeg", - "image/webp" - ] - } + { + "name": "throughRevision", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "本次浏览固定的Board历史前缀;首请求缺省冻结当前revision,续页与回首页保留该值。" }, - "additionalProperties": false + "description": "本次浏览固定的Board历史前缀;首请求缺省冻结当前revision,续页与回首页保留该值。" + } + ] + } + }, + "/api/taskboards/session-board/resolve": { + "post": { + "operationId": "resolveSessionTaskBoard", + "tags": [ + "TaskBoard" + ], + "summary": "Resolve the default board for a conversation", + "description": "Read-only POST, 4 KiB body limit. Requires runId or sessionId; accepts no claimed session list. Current session with exactly one visible board wins, including archived boards; multiple current boards have no default. Otherwise choose newest unarchived board from another persisted session of the same Run. No default returns 200 exists:false. IDs do not expand current visibility. Requires the authenticated deployment owner's global TaskBoard capability and current Board/Team access; HTTP acts as the authenticated human actor. No Task execution is started.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/taskboards" + }, + "security": [ + { + "InstanceToken": [] + } + ], + "responses": { + "200": { + "description": "Authorized synchronous read result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardDetailResponse" + } + } + } }, - "unread": { - "type": "number", - "description": "该 Agent 当前未读消息计数(手动多会话模式下为各话题未读之和,非手动模式为单会话计数);无未读时不返回此字段" + "400": { + "description": "Invalid request, cursor, filter/sort combination or activity range.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "pinned": { - "type": "boolean", - "description": "该 Agent 是否被用户置顶在侧栏最上方(来自用户级 preferences/agent-list.json);未置顶时不返回此字段" + "401": { + "description": "Missing/invalid instance credential or trusted user identity. Authentication errors can use the common service error shape." }, - "pinnedAt": { - "type": "string", - "description": "置顶时间 ISO 8601,多个置顶 Agent 之间的排序依据;未置顶时不返回此字段" + "403": { + "description": "TaskBoard capability disabled or Board access denied. Arbitrary missing Board IDs can also return forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "folded": { - "type": "boolean", - "description": "该 Agent 是否被收进侧栏的「折叠的聊天」分组(来自用户级 preferences/agent-list.json);未折叠时不返回此字段" + "413": { + "description": "Request body exceeds 4 KiB." }, - "groupOverride": { - "type": "string", - "enum": [ - "general", - "specialist" - ], - "description": "用户手动指定的侧栏分组,覆盖默认派生(默认派生 = 团队组长自动提升为通用,其余按 Agent 自身性质);未手动指定时不返回此字段" + "500": { + "description": "TaskBoard internal read failure.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardReadError" + } + } + } }, - "nestMembers": { - "type": "boolean", - "description": "该 Agent 作为团队组长时,其成员是否收纳到组长名下;用户级 preferences/agent-list.json 的显式选择优先,否则遵循团队 memberDisplay 默认值。未开启时不返回此字段" + "503": { + "description": "Storage migration/readiness failure. General service readiness errors can use the common service error shape." } }, - "additionalProperties": false - }, - "RunMeta": { - "type": "object", - "required": [ - "run_id", - "user_id", - "agent_id", - "started_at", - "status" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskBoardSessionBoardResolveRequest" + } + } + } + } + } + }, + "/api/s3/connections": { + "get": { + "operationId": "listS3Connections", + "tags": [ + "Object storage" ], - "properties": { - "run_id": { - "type": "string", - "description": "Run UUID,全局唯一标识符,用于查询和关联 Receipt" - }, - "user_id": { - "type": "string", - "description": "发起用户 ID" + "summary": "Read configured S3 connection summaries", + "description": "Requires the current user Object Storage capability. This is not an anonymous API. Summaries do not contain access keys or secrets and do not verify credentials or provider reachability. No per-object or prefix scoped token is issued.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/object-storage#connections" + }, + "security": [ + { + "InstanceToken": [] + } + ], + "parameters": [], + "responses": { + "200": { + "description": "Configured connection summaries; an empty configuration returns an empty connections array.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/S3ConnectionSummaries" + } + } + } }, - "agent_id": { - "type": "string", - "description": "Agent ID(对应 AgentFS 中的目录名)" + "401": { + "description": "Instance authentication failed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceAuthenticationError" + } + } + } }, - "conversation_id": { - "type": "string", - "description": "对话 ID,用于关联同一对话的多次 Run(支持多轮对话场景)" + "403": { + "description": "Object Storage is disabled, uninstalled or availability is uncertain; or browser Origin is denied. Capability errors have no success field.", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObjectStorageCapabilityDisabled" + }, + { + "$ref": "#/components/schemas/OriginRejection" + } + ] + } + } + } }, - "title": { - "type": "string", - "description": "会话标题(手动多会话模式下使用):用户重命名后的自定义标题,或系统据首条消息生成的摘要标题" + "500": { + "description": "Domain operation failed or an unexpected service error occurred.", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/S3ReadError" + }, + { + "$ref": "#/components/schemas/FrameworkError" + } + ] + } + } + } }, - "title_source": { - "type": "string", - "enum": [ - "custom", - "auto" - ], - "description": "title 的来源:custom=用户手动重命名,auto=系统据首条用户消息自动生成(截断或 LLM 摘要)" + "503": { + "description": "Startup is incomplete or instance authentication is not initialized.", + "headers": { + "Retry-After": { + "schema": { + "type": "string" + }, + "description": "Present for service_not_ready." + } + }, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ServiceStarting" + }, + { + "$ref": "#/components/schemas/InstanceTokenUninitialized" + } + ] + } + } + } }, - "origin": { - "type": "string", - "enum": [ - "user", - "delegation" - ], - "description": "会话创建来源(手动多会话模式下使用):user=用户手动新建,delegation=被其他 Agent 委派时自动新建" + "default": { + "description": "Other service or reverse-proxy failures; preserve status and Content-Type.", + "content": { + "application/json": { + "schema": {} + }, + "text/plain": { + "schema": { + "type": "string" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/s3/list": { + "get": { + "operationId": "listS3Objects", + "tags": [ + "Object storage" + ], + "summary": "Browse objects using an existing connection", + "description": "Requires the current user Object Storage capability and configured provider credentials with list access. Keep connection, prefix, delimiter and page size unchanged when passing opaque continuation tokens. No fixed snapshot or total count.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/object-storage#list-objects" + }, + "security": [ + { + "InstanceToken": [] + } + ], + "parameters": [ + { + "name": "connection_id", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Existing connection ID. When omitted or empty: default, then first connected, then first connection. An unusable selected connection returns NO_CONNECTION without trying alternatives." }, - "background": { - "type": "boolean" + { + "name": "prefix", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Object key prefix; empty or absent prefix is omitted from the response." }, - "inbound": { - "type": "boolean", - "description": "这一轮是不是「另一个 Agent 发来的消息」(SendMessage 来信)。**与 background 正交**:background 回答「该不该抢会话的前台控制槽」,本字段回答「这一轮在做什么」。来信已回归前台语义(与用户消息同路径),因此不再带 background;但「对方正在处理别人的来信」与「对方正在与用户对话」对**是否该打断**的判断截然不同,需要一个独立标记(见 agent/agent-activity.ts 的 kind 派生)。同 background,它描述的是**当前这一轮**,同一条 run 被复用时按当轮改写。" + { + "name": "delimiter", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "/" + }, + "description": "Grouping delimiter; empty string disables common-prefix grouping." }, - "started_at": { - "type": "string", - "description": "开始时间(ISO 8601 格式,如 2024-01-15T10:30:00Z)", - "format": "date-time" + { + "name": "max_keys", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "100" + }, + "description": "Recommended decimal integer 1–1000. Number(value) or 100, then clamped to 1–1000; malformed values and zero use 100. Not strict integer validation." }, - "turn_started_at": { - "type": "string", - "description": "**当前这一轮**开始执行的时间(ISO 8601)。同一条 run 会跨轮复用,started_at 停在第一次创建的时刻——一条聊了三天的会话,它的 started_at 就是三天前,拿来算「已运行多久」会报出「已运行 3 天」这种荒谬值,并把忙碌载荷的等待/打断建议带偏。每次把 run 置为 running 的路径(创建、复用、activateRun、activateSessionMetaRun)都要刷新它。updatedAt 不能替代:它会被每次 appendMessage 推后,代表的是「最后一次活动」而非「本轮起点」。", - "format": "date-time" + { + "name": "continuation_token", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Pass the previous next_continuation_token unchanged." + } + ], + "responses": { + "200": { + "description": "One provider page. Final pages omit a missing continuation token rather than returning null.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/S3ObjectList" + } + } + } }, - "completed_at": { - "type": "string", - "description": "完成时间(ISO 8601 格式),仅 status 为 completed 或 failed 时有值", - "format": "date-time" + "401": { + "description": "Instance authentication failed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceAuthenticationError" + } + } + } }, - "status": { - "type": "string", - "description": "Run lifecycle state. handed_off means the conversation was handed off, not that the target completed or the submitted turn succeeded.", - "enum": [ - "running", - "idle", - "completed", - "failed", - "handed_off" - ] + "403": { + "description": "Object Storage is disabled, uninstalled or availability is uncertain; or browser Origin is denied. Capability errors have no success field.", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObjectStorageCapabilityDisabled" + }, + { + "$ref": "#/components/schemas/OriginRejection" + } + ] + } + } + } }, - "failure_reason": { - "type": "string", - "description": "失败原因(仅 status=failed 时有值):process_restart=进程重启时被回收的孤儿 run;timeout=执行超时被强制终结;stale=历史兼容值(旧版本曾把超过复用窗口的 idle run 错标为失败);error=查询过程抛出错误", - "enum": [ - "process_restart", - "timeout", - "stale", - "error" - ] - }, - "interruption_reason": { - "type": "string", - "description": "最近一次未完成轮次的主动中止原因。Run 会回到 idle 以保留多轮会话复用能力,完整审计信息同时写入 Receipt", - "enum": [ - "user_abort", - "agent_interrupt", - "reset", - "system_suspend", - "process_shutdown", - "process_restart", - "orphan_reclaimed", - "background_timeout" - ] - }, - "interrupted_at": { - "type": "string", - "description": "最近一次主动中止发生时间(ISO 8601)。仅 interruption_reason 存在时有值", - "format": "date-time" - }, - "pending_synthesis": { - "type": "object", - "required": [ - "status", - "interruption_reason", - "created_at", - "completed_tool_calls" - ], - "properties": { - "status": { - "type": "string", - "const": "required", - "description": "固定为 required:已完成工具事实存在,但最终业务解释尚未正常收口" - }, - "interruption_reason": { - "type": "string", - "enum": [ - "user_abort", - "agent_interrupt", - "reset", - "system_suspend", - "process_shutdown", - "process_restart", - "orphan_reclaimed", - "background_timeout" - ] - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "待综合状态创建时间(ISO 8601)" - }, - "completed_tool_calls": { - "type": "array", - "minItems": 1, - "maxItems": 128, - "items": { - "type": "object", - "required": [ - "invocation_id", - "tool_name" - ], - "properties": { - "invocation_id": { - "type": "string", - "maxLength": 256, - "description": "已经成功完成的工具调用 ID,用于恢复时识别既成事实并避免重复执行" + "500": { + "description": "Domain operation failed or an unexpected service error occurred.", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/S3ReadError" }, - "tool_name": { - "type": "string", - "maxLength": 256, - "description": "已经成功完成的模型可见工具名称,供审计和恢复提示使用" + { + "$ref": "#/components/schemas/FrameworkError" } - }, - "additionalProperties": false + ] + } + } + } + }, + "503": { + "description": "Startup is incomplete or instance authentication is not initialized.", + "headers": { + "Retry-After": { + "schema": { + "type": "string" }, - "description": "恢复时必须复用、不得仅为重取同一结果而再次执行的成功工具调用" + "description": "Present for service_not_ready." } }, - "additionalProperties": false, - "description": "系统中断发生在工具成功之后、最终解释完成之前时持久化的恢复状态;正常综合成功后清除" - }, - "interrupted_turn_facts": { - "type": "object", - "required": [ - "interruption_reason", - "recorded_at", - "deliveries" - ], - "properties": { - "interruption_reason": { - "type": "string", - "description": "被记录轮次的中止原因(与 interruption_reason 字段同一枚举)。user_abort 时消费方可下「被用户停止」断言,其余原因只能弱化归因", - "enum": [ - "user_abort", - "agent_interrupt", - "reset", - "system_suspend", - "process_shutdown", - "process_restart", - "orphan_reclaimed", - "background_timeout" - ] - }, - "recorded_at": { - "type": "string", - "format": "date-time" - }, - "deliveries": { - "type": "array", - "maxItems": 64, - "items": { - "type": "object", - "required": [ - "tool_name", - "preview", - "attachment_count", - "delivery_status" - ], - "properties": { - "tool_name": { - "type": "string", - "maxLength": 256, - "description": "交付类工具的模型可见名称(builtin-tools/tool-effects 的 effect.class===\"delivery\" 派生名单成员,含历史 alias)" - }, - "preview": { - "type": "string", - "maxLength": 200, - "description": "已投递内容的开头片段(取调用 input.message / input.content 的前 50 字符)。空串表示 input 缺失或非字符串(如 MailOperations 这类通用代理),消费方必须按「无法确认实际送达」的弱化措辞处理,不得下已送达断言" - }, - "attachment_count": { - "type": "integer", - "minimum": 0, - "description": "调用 input.attachments 数组长度;0 表示无附件" + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ServiceStarting" }, - "delivery_status": { - "type": "string", - "enum": [ - "completed", - "failed", - "interrupted" - ] + { + "$ref": "#/components/schemas/InstanceTokenUninitialized" } - }, - "additionalProperties": false - }, - "description": "被中断轮内交付类工具调用的终态清单(tool-effects 派生名单 + 已结算终态;同 toolCallId 去重;超上限时保留最近的条目)。空数组是权威事实「该轮没有任何交付」,消费方不得再退回尾窗启发式反推" + ] + } } - }, - "additionalProperties": false - }, - "engine": { - "type": "string", - "description": "运行时引擎" - }, - "session_id": { - "type": "string", - "description": "关联的 Socket.IO session ID,用于实时通信和中断控制" + } }, - "messageCount": { - "type": "number", - "description": "消息总数(由 SessionStore 维护)" + "default": { + "description": "Other service or reverse-proxy failures; preserve status and Content-Type.", + "content": { + "application/json": { + "schema": {} + }, + "text/plain": { + "schema": { + "type": "string" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + } }, - "updatedAt": { - "type": "string", - "description": "最后更新时间(由 SessionStore 维护)" + "400": { + "description": "NO_CONNECTION: no selected connection with usable configuration and credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/S3ReadError" + } + } + } + } + } + } + }, + "/api/s3/download": { + "get": { + "operationId": "downloadS3Object", + "tags": [ + "Object storage" + ], + "summary": "Obtain an S3 download link or object bytes", + "description": "Requires Object Storage capability and configured provider read permissions. Only exact mode=binary returns bytes; all other values use link mode. Public URL reachability is checked before falling back to a presigned URL. The instance token must not be forwarded to returned S3/CDN URLs. Binary reads buffer the object on the server; no Range or resume contract.", + "externalDocs": { + "url": "https://docs.desirecore.com/api/object-storage#download-object" + }, + "security": [ + { + "InstanceToken": [] + } + ], + "parameters": [ + { + "name": "connection_id", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Existing connection ID. When omitted or empty: default, then first connected, then first connection. An unusable selected connection returns NO_CONNECTION without trying alternatives." }, - "cleared": { - "type": "boolean", - "description": "Tombstone 标记:用户清除对话后置为 true。会话列表和恢复逻辑会过滤掉此类 run,但 receipts/ 子目录保留,活动记录仍能读到该 run 的真实 token 消耗凭据(审计用)" + { + "name": "key", + "in": "query", + "required": true, + "schema": { + "type": "string", + "minLength": 1 + }, + "description": "Complete nonempty object key, preserving case and slashes." }, - "cleared_at": { - "type": "string", - "description": "Tombstone 写入时间(ISO 8601)。检查点清理按此时间计算 deletedRunRetentionMinutes;旧数据缺失时兼容 cleared=true", - "format": "date-time" + { + "name": "mode", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "link" + }, + "description": "Use link or binary. Only exact binary changes the response to raw object bytes; other values use link mode." }, - "archived_at": { - "type": "string", - "description": "会话归档时间(ISO 8601)。归档是独立于运行 status 的历史生命周期,归档 run 不再自动恢复,但消息和审计记录仍保留", - "format": "date-time" + { + "name": "expires_in", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "3600" + }, + "description": "Link mode: recommended integer 60–604800 seconds. Number(value) or 3600, then clamped to 60–604800. Public URLs omit expires_in." + } + ], + "responses": { + "200": { + "description": "Link mode returns JSON; binary mode returns arbitrary object bytes, which can themselves contain JSON or HTML. Check HTTP status before saving. Optional link metadata can be absent (including content_length for an empty object).", + "headers": { + "Content-Disposition": { + "description": "Binary mode: attachment filename from the final key segment; use an explicit safe local filename.", + "schema": { + "type": "string" + } + }, + "Content-Length": { + "description": "Binary mode byte count.", + "schema": { + "type": "integer", + "minimum": 0 + } + } + }, + "content": { + "application/json": { + "schema": { + "description": "In link mode, use the S3DownloadLink component. In binary mode an application/json object may contain any bytes, including arbitrary JSON objects, arrays or scalars; do not parse it as an API wrapper based on MIME type. OpenAPI cannot choose a response schema by the mode query parameter." + }, + "examples": { + "publicLink": { + "summary": "Link mode with a public URL; expires_in is absent.", + "value": { + "success": true, + "key": "reports/summary.txt", + "download_url": "https://files.example.com/reports/summary.txt", + "url_type": "public", + "content_type": "text/plain", + "content_length": 120 + } + }, + "presignedLink": { + "summary": "Link mode with a signed URL.", + "value": { + "success": true, + "key": "reports/summary.txt", + "download_url": "https://s3.example.com/reports/summary.txt?signature=example", + "url_type": "presigned", + "expires_in": 3600 + } + } + } + }, + "*/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } }, - "handoffFrom": { + "401": { + "description": "Instance authentication failed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceAuthenticationError" + } + } + } + }, + "403": { + "description": "Object Storage is disabled, uninstalled or availability is uncertain; or browser Origin is denied. Capability errors have no success field.", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObjectStorageCapabilityDisabled" + }, + { + "$ref": "#/components/schemas/OriginRejection" + } + ] + } + } + } + }, + "500": { + "description": "Domain operation failed or an unexpected service error occurred.", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/S3ReadError" + }, + { + "$ref": "#/components/schemas/FrameworkError" + } + ] + } + } + } + }, + "503": { + "description": "Startup is incomplete or instance authentication is not initialized.", + "headers": { + "Retry-After": { + "schema": { + "type": "string" + }, + "description": "Present for service_not_ready." + } + }, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ServiceStarting" + }, + { + "$ref": "#/components/schemas/InstanceTokenUninitialized" + } + ] + } + } + } + }, + "default": { + "description": "Other service or reverse-proxy failures; preserve status and Content-Type.", + "content": { + "application/json": { + "schema": {} + }, + "text/plain": { + "schema": { + "type": "string" + } + }, + "text/html": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "INVALID_REQUEST for missing/empty key; NO_CONNECTION for unresolved connection, missing configuration or credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/S3ReadError" + } + } + } + }, + "404": { + "description": "Link mode NOT_FOUND when the metadata HEAD does not succeed. This can include a provider denial and is not proof of deletion. Binary mode missing objects instead use 500 DOWNLOAD_FAILED.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/S3ReadError" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "InstanceToken": { + "type": "apiKey", + "in": "header", + "name": "X-Agent-Service-Token", + "description": "Existing owner credential supplied by the instance administrator." + }, + "WebhookSecret": { + "type": "http", + "scheme": "bearer", + "description": "Dedicated secret for the target Agent Webhook; not a provider API key or JWT." + }, + "SignedMediaSignature": { + "type": "apiKey", + "in": "query", + "name": "signature", + "description": "Five-minute HMAC-SHA-256 capability issued by POST /api/media/access-urls. 43-character canonical base64url signature bound to the filename and required expires query value. Reusable until expiry; no instance header required for this GET. Expired/invalid links return 401. Application authority, configured Origin restrictions, and readiness are checked separately." + } + }, + "schemas": { + "ClientHandshake": { + "type": "object", + "required": [ + "service", + "apiProtocolVersion", + "serverVersion", + "instanceId", + "deploymentMode", + "clientCompatibility", + "capabilities", + "authentication", + "serverTime" + ], + "properties": { + "service": { "type": "string", - "readOnly": true, - "description": "Source Run ID for this handoff. A link grants no access or continuation permission." + "const": "desirecore-agent-service", + "description": "固定服务标记,用于拒绝非 DesireCore Agent Service 目标。" }, - "handoffTo": { + "apiProtocolVersion": { "type": "string", - "readOnly": true, - "description": "Target Run ID for this handoff. Target admission and completion require their own evidence." + "const": "1", + "description": "客户端握手和实例连接协议版本;不等同于应用版本。" }, - "team_id": { + "serverVersion": { "type": "string", - "description": "本 run 执行时所处的团队作用域。委派续跑(DelegateControl action=\"resume\")必须按它重建执行环境——用调用方**当前**的 teamId 会让续跑落进另一个团队的 prompt 与共享工作目录,带着旧团队的历史在新环境里接力;缺省表示该 run 不属于任何团队。" + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", + "description": "当前 Agent Service 随客户端发布的语义版本号。" }, - "work_context_id": { + "instanceId": { "type": "string", - "minLength": 1, - "description": "本 Run 所属的稳定 Work Context ID。continue/resume 必须沿用,isolated/fork 必须记录新建结果;旧 Run 可缺省并由幂等 legacy backfill 建段。" + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "Agent Service 数据空间的稳定不透明身份。由服务端随机生成并持久化,不得由路径、用户、设备或 token 推导。" }, - "execution_attachment": { + "deploymentMode": { + "type": "string", + "enum": [ + "embedded", + "owner", + "external" + ], + "description": "Agent Service 运行形态:embedded=Electron 内嵌;owner=standalone owner 模式;external=standalone external 模式。" + }, + "clientCompatibility": { "type": "object", "required": [ - "userId", - "agentId", - "runId" + "minimumVersion", + "maximumVersion" ], "properties": { - "userId": { + "minimumVersion": { "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$" + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", + "description": "允许连接本服务的最低 DesireCore 客户端版本(含)。" }, - "agentId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "执行该 Run 的权威 Agent ID。" + "maximumVersion": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", + "description": "允许连接本服务的最高 DesireCore 客户端版本(含);null 表示不设上限。" + } + }, + "additionalProperties": false + }, + "capabilities": { + "type": "object", + "required": [ + "workflow", + "mediaSignedUrl" + ], + "properties": { + "workflow": { + "type": "boolean", + "description": "当前部署是否具备可用且完整配置的 Workflow Service 能力。" }, - "runId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "承载本次执行的 DesireCore Run ID。" - }, - "conversationId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的用户可见 Conversation ID;缺失表示该 Run 不属于对话时间线。" + "pdfRendering": { + "type": "boolean", + "description": "服务端宿主是否提供 HTML 转 PDF 引擎;缺席表示未知,不代表客户端具有打印传输。" }, - "workContextId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的稳定 Work Context ID,用于跨续跑保持同一工作语境。" + "mediaSignedUrl": { + "type": "boolean", + "description": "当前实例是否可以签发供原生媒体标签使用的短期 HTTPS URL。" }, - "workflowRunId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的 Workflow Run ID;与 DesireCore Run ID 正交。" + "queryRequestIdEcho": { + "type": "boolean", + "description": "服务端是否回显客户端 query 的 clientRequestId;缺席表示未知,客户端只能按实际事件观察,禁止按版本推断。" }, - "cellId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的执行 Cell ID,表示服务实例所在的逻辑隔离单元。" + "webhookManagement": { + "type": "boolean", + "description": "是否支持所有者通过 Webhook 管理 API 配置事件、选择现有工具和轮换 secret;缺席或 false 表示该配置入口不可用。" }, - "serviceInstanceId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的 Agent Service 实例 ID;出现时必须同时提供其所属 cellId。" + "localPairing": { + "type": "boolean" }, - "jobId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$" + "localAttachSecret": { + "type": "boolean" }, - "attemptId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$" + "objectStorageCapability": { + "type": "boolean" }, - "capsuleId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$" + "mailCapability": { + "type": "boolean" } }, - "allOf": [ - { - "if": { - "required": [ - "serviceInstanceId" - ] - }, - "then": { - "required": [ - "cellId" - ] - } - }, - { - "if": { - "required": [ - "attemptId" - ] - }, - "then": { - "required": [ - "jobId" - ] - } - }, - { - "if": { - "required": [ - "capsuleId" - ] - }, - "then": { - "required": [ - "jobId", - "attemptId" - ] - } - } - ], - "additionalProperties": false, - "description": "本 Run 的权威执行归属;新 Run 由服务端构造,旧 Run 可缺失。其 user/agent/run/conversation/work-context 必须与外围 RunMeta 一致。" + "additionalProperties": { + "type": "boolean", + "description": "未来新增的布尔能力位;旧客户端应忽略不认识的能力名。" + } }, - "work_context_selection": { + "authentication": { "type": "object", "required": [ - "context_mode", - "origin" + "kind", + "scopes" ], "properties": { - "context_mode": { + "kind": { "type": "string", "enum": [ - "continue", - "isolated", - "fork" + "embedded-capability", + "instance-token" ], - "description": "必填且无默认值:continue=继续明确存在的同一任务上下文;isolated=不同新任务;fork=同一任务有意分叉。" + "description": "本次服务形态使用的客户端鉴权类型:embedded-capability 或 standalone instance-token。" }, - "origin": { + "scopes": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "owner" + ] + }, + "description": "认证凭据授予的实例作用域;第一版固定为 owner。" + } + }, + "additionalProperties": false + }, + "serverTime": { + "type": "string", + "format": "date-time", + "description": "服务端生成响应时的 ISO 8601 时间,用于诊断时钟偏差,不参与身份判断。" + }, + "product": { + "type": "object", + "required": [ + "editionId", + "appId", + "productName", + "buildKind", + "appVersion" + ], + "properties": { + "editionId": { "type": "string", - "enum": [ - "delegate", - "mention", - "task", - "workflow", - "schedule", - "webhook", - "decision", - "legacy" - ] + "pattern": "^[a-z0-9][a-z0-9-]*$", + "maxLength": 128, + "description": "服务所在应用的发行版 ID;同机接入发现据此标注候选实例属于哪个发行版。" }, - "work_context_id": { + "appId": { "type": "string", "minLength": 1, "maxLength": 256, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", - "description": "continue 必填:要继续的稳定 Work Context ID;禁止回退到目标 Agent 最近一次会话。" + "description": "服务所在应用的 appId(macOS bundle id、Windows AUMID 前缀)。" }, - "source_work_context_id": { + "productName": { "type": "string", "minLength": 1, - "maxLength": 256, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", - "description": "fork 必填:有意分叉的来源 Work Context ID。" + "maxLength": 128, + "description": "服务所在应用的显示名。" }, - "task_ref": { - "type": "object", - "required": [ - "board_id", - "task_id" + "buildKind": { + "type": "string", + "enum": [ + "dev", + "prod" ], - "properties": { - "board_id": { - "type": "string", - "minLength": 1 - }, - "task_id": { - "type": "string", - "minLength": 1, - "description": "TaskBoard 内稳定 Task ID。" - }, - "task_version": { - "type": "integer", - "minimum": 1, - "description": "选择时观察到的 Task 版本快照;身份查重只使用 board_id + task_id。" - } - }, - "additionalProperties": false + "description": "服务所在应用的构建形态:Electron 打包版为 prod,开发版为 dev;standalone 按运行环境判断。" }, - "intent_id": { + "appVersion": { "type": "string", - "minLength": 1, - "maxLength": 256 - }, - "context_reason": { + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$", + "description": "服务所在应用的版本号,与 serverVersion 相同。" + } + }, + "additionalProperties": false + }, + "capabilityDiagnostics": { + "type": "object", + "required": [ + "pdfRendering" + ], + "properties": { + "pdfRendering": { + "type": [ + "string", + "null" + ], + "enum": [ + "pdf_renderer_unavailable", + null + ] + } + }, + "additionalProperties": false, + "description": "仅协商 deployment-capabilities 时返回;原因是部署能力事实,不含配置、路径或凭据。" + }, + "queryDispatch": { + "type": "object", + "required": [ + "serverInstanceId" + ], + "properties": { + "serverInstanceId": { "type": "string", "minLength": 1, - "maxLength": 2000, - "description": "isolated/fork 必填:说明为何这是不同新任务或为何允许对同一 Task 有意分叉。" + "maxLength": 128, + "description": "当前服务进程启动代际,与 recovery.serverInstanceId 同源;重启即变化,不能当持久 instanceId 或认证凭证。" } }, - "allOf": [ - { - "if": { - "properties": { - "context_mode": { - "const": "continue" - } - }, - "required": [ - "context_mode" - ] - }, - "then": { - "required": [ - "work_context_id" - ], - "not": { - "anyOf": [ - { - "required": [ - "source_work_context_id" - ] - }, - { - "required": [ - "intent_id" - ] - } - ] - } - } - }, - { - "if": { - "properties": { - "context_mode": { - "const": "isolated" - } - }, - "required": [ - "context_mode" - ] - }, - "then": { - "required": [ - "context_reason" - ], - "anyOf": [ - { - "required": [ - "task_ref" - ] - }, - { - "required": [ - "intent_id" - ] - } - ], - "not": { - "anyOf": [ - { - "required": [ - "work_context_id" - ] - }, - { - "required": [ - "source_work_context_id" - ] - } - ] - } - } - }, - { - "if": { - "properties": { - "context_mode": { - "const": "fork" - } - }, - "required": [ - "context_mode" - ] - }, - "then": { - "required": [ - "source_work_context_id", - "task_ref", - "context_reason" - ], - "not": { - "anyOf": [ - { - "required": [ - "work_context_id" - ] - }, - { - "required": [ - "intent_id" - ] - } - ] - } - } - } - ], "additionalProperties": false, - "description": "启动此 Run 时调用方显式提交并经 preflight/commit 接受的 Work Context 选择快照。" + "description": "仅协商 query-dispatch 扩展时返回;旧服务缺席本字段时不得伪造进程身份。" + } + }, + "additionalProperties": false + }, + "WebhookRequest": { + "type": "object", + "required": [ + "event" + ], + "properties": { + "event": { + "type": "string", + "description": "事件标识,如 \"github.push\"" }, - "work_context_task_ref": { + "payload": { "type": "object", - "required": [ - "board_id", - "task_id" - ], - "properties": { - "board_id": { - "type": "string", - "minLength": 1 - }, - "task_id": { - "type": "string", - "minLength": 1, - "description": "TaskBoard 内稳定 Task ID。" - }, - "task_version": { - "type": "integer", - "minimum": 1, - "description": "选择时观察到的 Task 版本快照;身份查重只使用 board_id + task_id。" - } - }, - "additionalProperties": false + "additionalProperties": {}, + "description": "事件负载,prompt_template 中的 {{key}} 从此处提取" + } + }, + "additionalProperties": false + }, + "WebhookAccepted": { + "type": "object", + "properties": { + "accepted": { + "type": "boolean", + "const": true }, - "work_context_intent_id": { - "type": "string", - "minLength": 1, - "maxLength": 256 + "agentId": { + "type": "string" }, - "work_context_origin": { + "event": { + "type": "string" + } + }, + "required": [ + "accepted", + "agentId", + "event" + ], + "additionalProperties": false + }, + "InstanceAuthenticationError": { + "type": "object", + "properties": { + "error": { "type": "string", - "enum": [ - "delegate", - "mention", - "task", - "workflow", - "schedule", - "webhook", - "decision", - "legacy" - ], - "description": "此 Run 进入 Work Context 的权威入口快照。" - }, - "child_context": { - "type": "object", - "description": "原委派对子 Agent 上下文组装施加的**收紧**覆盖(Delegate 的 childContext 解析结果)。续跑必须按它重建,否则「只传 run_id 的裸续跑」会让原本被刻意收窄的上下文(memoryScope: none、关掉的能力、section 过滤)在续跑时全部放开——收紧只能保持或更严。形状与 ContextAssemblyOptions 的对应覆盖字段一致;缺省表示原委派未收紧。", - "additionalProperties": true + "const": "unauthorized" }, - "child_tool_ceiling": { - "type": "object", - "description": "委派执行时的工具键与文件访问范围快照。tools 仅记录执行方当时的工具作为审计证据,不是父智能体的授权上限;持久 Agent 续跑使用执行方当前工具权限,并保留防递归限制。allowedReadDirs / allowedReadPaths / allowedWriteDirs 继续与调用方当前范围取交集;缺省的文件维度表示无限制。旧快照的工具键同样不再裁剪目标工具,形状错误仍拒绝恢复。", - "additionalProperties": true + "message": { + "type": "string" + } + }, + "required": [ + "error", + "message" + ], + "additionalProperties": false + }, + "OriginRejection": { + "type": "object", + "properties": { + "error": { + "type": "string", + "const": "origin_not_allowed" }, - "work_dir": { + "message": { + "type": "string" + } + }, + "required": [ + "error" + ], + "additionalProperties": true + }, + "ServiceStarting": { + "type": "object", + "properties": { + "status": { "type": "string", - "description": "本 run 起跑时解析出的**基准**工作目录(团队共享目录 > 手动话题 Project > Agent/全局 effectiveCwd;不含运行时 worktree 绑定那层派生覆盖)。委派续跑据此确认「原来那次是在哪个目录里跑的」:承接来的历史里全是对旧目录中文件的读写,工作目录偏好在两次之间改过的话,续跑就会带着旧上下文在另一个项目里接力。⚠ 与 worktree_cwd 同一约定:**空字符串表示「起跑时确实没解析出工作目录」,字段缺席表示「该 run 创建于本字段引入之前」**——两者混为一谈的话,「当初没配、现在配了工作目录」会被闸门当成老数据跳过,而承接来的历史其实来自当时的进程默认目录。" + "const": "starting" }, - "worktree_cwd": { + "service": { "type": "string", - "description": "本 run 起跑时实际附着的 Owner worktree 目录(`work_dir` 之上的派生覆盖)。与 work_dir 分开记:前者是用户偏好解析出的基准目录,这一条是运行时按 Owner 绑定的链接工作树。委派续跑两者都要一致——只比基准目录的话,源 run 当初挂在某个 worktree 上、而绑定后来被退出或改挂时闸门会放行,续跑于是回到主 checkout 或另一个工作树里接力。⚠ **空字符串表示「起跑时确实没有附着任何 worktree」,字段缺席表示「该 run 创建于本字段引入之前,无从得知」**——两者必须可区分:都写成缺席的话,「当初没挂、现在挂上了」这一格会被当成老数据跳过比较,而它恰恰是要拦的(历史来自主 checkout,续跑却跑进新工作树)。因此起跑路径**总是**写这个字段。" + "const": "agent-service" }, - "resumedFrom": { + "code": { "type": "string", - "description": "同一执行连续性的来源 Run ID:由 DelegateControl(action=\"resume\") 或已接纳 Work Context continue 承接该 run。承接来的历史只作为运行时上下文喂给模型,**不落进本 run 的 messages.jsonl**,因此二次续跑必须沿这条链回溯收集,否则每续一次就丢掉更早那一段的历史与工具结果。与 handoffFrom 的区别:那是会话转交(用户换了对话对象),这是同一委派任务/Work Context 的接力。" + "const": "service_not_ready" + } + }, + "required": [ + "status", + "service", + "code" + ], + "additionalProperties": false + }, + "InstanceTokenUninitialized": { + "type": "object", + "properties": { + "error": { + "type": "string", + "const": "instance_token_uninitialized" }, - "delegation": { + "message": { + "type": "string" + } + }, + "required": [ + "error", + "message" + ], + "additionalProperties": false + }, + "WebhookAuthenticationError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "Invalid webhook secret", + "Invalid embedded instance token" + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + }, + "WebhookSecretRequired": { + "type": "object", + "properties": { + "error": { + "type": "string", + "const": "webhook_secret_required" + }, + "message": { + "type": "string" + } + }, + "required": [ + "error", + "message" + ], + "additionalProperties": false + }, + "WebhookNotFound": { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "Agent not found", + "Webhook not enabled for this agent" + ] + } + }, + "required": [ + "error" + ], + "additionalProperties": false + }, + "WebhookInvalidBody": { + "type": "object", + "properties": { + "error": { + "type": "string", + "const": "Invalid request body" + }, + "details": { + "type": "array", + "items": {} + } + }, + "required": [ + "error", + "details" + ], + "additionalProperties": false + }, + "WebhookUnknownEvent": { + "type": "object", + "properties": { + "error": { + "type": "string", + "pattern": "^Unknown event type: " + } + }, + "required": [ + "error" + ], + "additionalProperties": false + }, + "WebhookConfigurationError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "const": "Failed to parse agent.json" + } + }, + "required": [ + "error" + ], + "additionalProperties": false + }, + "FrameworkError": { + "type": "object", + "properties": { + "statusCode": { + "type": "integer" + }, + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "statusCode", + "error", + "message" + ], + "additionalProperties": true + }, + "AgentSummary": { + "type": "object", + "required": [ + "id", + "name", + "skillsCount", + "toolsCount", + "heartbeatEnabled", + "hasGit", + "isCoreAgent" + ], + "properties": { + "id": { + "type": "string", + "description": "智能体 ID(目录名)" + }, + "name": { + "type": "string", + "description": "智能体显示名称" + }, + "description": { + "type": "string", + "description": "智能体描述" + }, + "ui": { "type": "object", - "description": "委派溯源:仅由 Delegate 工具创建/复用的 run 有值,记录本 run 由哪个 Agent 的哪个父 run 委派产生。async 场景下父消息 metadata.delegate.children 在工具返回时为空,可通过此字段反查委派链(InspectRuns 工具 action=list 依赖它过滤子 run)", - "required": [ - "fromAgentId" - ], + "description": "该智能体的界面呈现配置,仅作用于其会话,不改变平台设置", "properties": { - "fromAgentId": { - "type": "string", - "description": "发起委派的 Agent ID(delegation.fromAgentId,用户直接发起时为 owner)" - }, - "parentRunId": { - "type": "string", - "description": "委派方发起委派时所在的父 run ID。由父 sessionId 经 getRunIdBySession 反查;反查失败时缺省" - }, - "parentClientRequestId": { - "type": "string", - "minLength": 1, - "description": "委派子 Run 创建时,由 ToolExecutor 从父 Agent **当前轮次**注入的可信 clientRequestId。它与可复用的 parentRunId 分开记录,用于证明子 Run 确实由父 Run 的这一轮产生;旧 Run 可缺省,但需要轮次级授权的新入口必须在缺失时失败关闭。" - }, - "mode": { + "chat_bubble_mode": { "type": "string", "enum": [ - "sync", - "async", - "via-messaging", - "fan-out", - "subtask" + "flat", + "classic" ], - "description": "委派模式(handoff 不写 delegation,走 handoffFrom/handoffTo 链路;worker 无独立 run)。subtask=继承发起方会话上下文的子体,历史来源另记在 subtask_inherited_from。" + "description": "消息样式:flat 为通栏,classic 为气泡;省略时跟随平台消息样式" } }, "additionalProperties": false }, - "delivery_routing": { + "isCoreAgent": { + "type": "boolean" + }, + "skillsCount": { + "type": "number", + "description": "技能数量", + "minimum": 0 + }, + "toolsCount": { + "type": "number", + "description": "工具数量", + "minimum": 0 + }, + "heartbeatEnabled": { + "type": "boolean", + "description": "心跳是否启用" + }, + "engine": { + "type": "string", + "description": "运行时引擎" + }, + "hasGit": { + "type": "boolean", + "description": "是否有 git 仓库" + }, + "hasRemote": { + "type": "boolean" + }, + "status": { + "type": "string", + "description": "Agent 运行时状态(从 AgentRegistry 获取)", + "enum": [ + "online", + "busy", + "idle", + "offline", + "error", + "recovery", + "shutdown" + ] + }, + "metadataError": { + "type": "string", + "enum": [ + "missing", + "unreadable", + "invalid_json", + "invalid_shape" + ] + }, + "syncStatus": { + "type": "string", + "description": "Git 同步状态:synced(已同步)、ahead(本地有未推送提交)、behind(远程有更新)、conflict(存在冲突)、local_only(纯本地,无远程仓库)、uninitialized(未初始化 Git)", + "enum": [ + "synced", + "ahead", + "behind", + "conflict", + "local_only", + "uninitialized" + ] + }, + "avatar": { "type": "object", - "required": [ - "group", - "audience" - ], + "description": "头像配置(来自 agent.json)", "properties": { - "group": { + "char": { "type": "string", - "enum": [ - "delegator-governed", - "user-handoff", - "run-inherited" - ], - "description": "委派交付路由分组:delegator-governed=先回委派方;user-handoff=目标会话直接服务用户;run-inherited=沿用目标 Run 已冻结受众。" + "description": "显示字符" }, - "audience": { - "oneOf": [ - { - "type": "object", - "required": [ - "type", - "conversationId" - ], - "properties": { - "type": { - "type": "string", - "const": "user", - "description": "交付给当前用户可见会话。" - }, - "conversationId": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "用户接收交付的稳定会话 ID。" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "type", - "agentId" - ], - "properties": { - "type": { - "type": "string", - "const": "delegator", - "description": "交付给发起委派的主 Agent。" - }, - "agentId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "委派方规范 Agent ID。" - }, - "parentRunId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "发起委派时的父 Run ID。" - }, - "conversationId": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "异步结果必须回到的父会话 ID。" - }, - "correlationId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "请求与最终交付的稳定关联 ID。" - } - }, - "additionalProperties": false - } + "color": { + "type": "string", + "description": "色系键名", + "enum": [ + "green", + "blue", + "purple", + "orange", + "red" + ] + }, + "image": { + "type": "object", + "description": "图片头像配置(agent.json 原样透传)。**这里只说明配置存在,不代表文件可用**——判断能否渲染图片请看顶层 avatarImage", + "required": [ + "path" ], - "description": "准入时冻结的真实交付受众;只允许用户会话或委派方 Agent。" - } - }, - "additionalProperties": false, - "allOf": [ - { - "if": { - "properties": { - "group": { - "const": "delegator-governed" - } - }, - "required": [ - "group" - ] - }, - "then": { - "properties": { - "audience": { - "type": "object", - "required": [ - "type", - "agentId" - ], - "properties": { - "type": { - "type": "string", - "const": "delegator", - "description": "交付给发起委派的主 Agent。" - }, - "agentId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "委派方规范 Agent ID。" - }, - "parentRunId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "发起委派时的父 Run ID。" - }, - "conversationId": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "异步结果必须回到的父会话 ID。" - }, - "correlationId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "请求与最终交付的稳定关联 ID。" - } - }, - "additionalProperties": false - } + "properties": { + "path": { + "type": "string", + "description": "相对于 Agent 根目录的图片路径" } - } + }, + "additionalProperties": false }, - { - "if": { - "properties": { - "group": { - "const": "user-handoff" - } + "model": { + "type": "object", + "description": "沉浸式交互使用的 Agent 自定义 3D 模型配置", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string", + "description": "相对于 Agent 根目录的 GLB 文件路径" }, - "required": [ - "group" - ] - }, - "then": { - "properties": { - "audience": { - "type": "object", - "required": [ - "type", - "conversationId" - ], - "properties": { - "type": { - "type": "string", - "const": "user", - "description": "交付给当前用户可见会话。" - }, - "conversationId": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "用户接收交付的稳定会话 ID。" - } - }, - "additionalProperties": false - } + "body": { + "type": "string", + "description": "TalkingHead 模型体型提示", + "enum": [ + "F", + "M" + ] } - } + }, + "additionalProperties": false } - ], - "description": "本 Run 在准入时冻结的交付路由分组与真实受众。委派/异步恢复/interject 不得根据当前激活会话重新推断;旧 Run 可缺省并按受控兼容规则只读派生。" + }, + "additionalProperties": false }, - "subtask_inherited_from": { + "avatarImage": { "type": "object", - "description": "本 run 作为 subtask 子体时,继承的那份对话历史的来源坐标。与 resumedFrom 是**两条不同的边**:resumedFrom 是「同一委派任务的接力」(承接子 run 自己的历史),本字段是「继承发起方会话的上下文」(历史来自另一条时间线)。混用会让委派续跑的链校验把 subtask 的来源当成祖先去比对归属五元组,而两者的归属口径本就不同。与 resumedFrom 同一约定:继承来的历史只作为运行时上下文喂给模型,**不落进本 run 的 messages.jsonl**,因此这是事后追溯「这条 run 看过谁的上下文」的唯一依据。", + "required": [ + "version" + ], "properties": { - "conversation_id": { + "version": { "type": "string", - "description": "历史来源会话 ID(发起方有话题时用这条;跨 run 归并后的规范视图)" + "description": "缓存版本戳(`${size}-${mtimeMs}`),与该端点的 ETag 同源。值变化即表示图片已更换,客户端应丢弃旧缓存重新取图。不写进 agent.json:那会产生 git diff 噪声,且 Agent 直接覆盖图片文件时字段不会变,必然漂移。" }, - "run_id": { + "mimeType": { "type": "string", - "description": "历史来源 run ID(发起方本身是委派子 run、不属于任何会话时用这条)" + "description": "经 magic bytes 嗅探确认的真实 MIME 类型(不信任扩展名)", + "enum": [ + "image/png", + "image/jpeg", + "image/webp" + ] } }, "additionalProperties": false }, - "handoff_pending": { + "unread": { + "type": "number", + "description": "该 Agent 当前未读消息计数(手动多会话模式下为各话题未读之和,非手动模式为单会话计数);无未读时不返回此字段" + }, + "pinned": { + "type": "boolean", + "description": "该 Agent 是否被用户置顶在侧栏最上方(来自用户级 preferences/agent-list.json);未置顶时不返回此字段" + }, + "pinnedAt": { "type": "string", - "readOnly": true, - "description": "Server-recorded marker for a pending handoff. Not an access credential or a replay token." + "description": "置顶时间 ISO 8601,多个置顶 Agent 之间的排序依据;未置顶时不返回此字段" }, - "handoff_attempted": { + "folded": { "type": "boolean", - "readOnly": true, - "description": "Target startup reached a stage that may have side effects. Does not establish target completion." + "description": "该 Agent 是否被收进侧栏的「折叠的聊天」分组(来自用户级 preferences/agent-list.json);未折叠时不返回此字段" }, - "handoff_accepted": { + "groupOverride": { + "type": "string", + "enum": [ + "general", + "specialist" + ], + "description": "用户手动指定的侧栏分组,覆盖默认派生(默认派生 = 团队组长自动提升为通用,其余按 Agent 自身性质);未手动指定时不返回此字段" + }, + "nestMembers": { "type": "boolean", - "readOnly": true, - "description": "The target runtime accepted the handoff. Does not establish target business completion." + "description": "该 Agent 作为团队组长时,其成员是否收纳到组长名下;用户级 preferences/agent-list.json 的显式选择优先,否则遵循团队 memberDisplay 默认值。未开启时不返回此字段" } }, "additionalProperties": false }, - "RunRuntimeState": { + "RunMeta": { "type": "object", "required": [ "run_id", + "user_id", "agent_id", + "started_at", "status" ], "properties": { "run_id": { "type": "string", - "description": "被查询的 Run ID;兼容普通 snake_case Run 与 checkpoint/rewind 产生的 camelCase fork Run" + "description": "Run UUID,全局唯一标识符,用于查询和关联 Receipt" + }, + "user_id": { + "type": "string", + "description": "发起用户 ID" }, "agent_id": { "type": "string", - "description": "该 Run 的权威 Agent 归属,用于审阅等破坏性操作在客户端预检时核对来源" + "description": "Agent ID(对应 AgentFS 中的目录名)" + }, + "conversation_id": { + "type": "string", + "description": "对话 ID,用于关联同一对话的多次 Run(支持多轮对话场景)" + }, + "title": { + "type": "string", + "description": "会话标题(手动多会话模式下使用):用户重命名后的自定义标题,或系统据首条消息生成的摘要标题" + }, + "title_source": { + "type": "string", + "enum": [ + "custom", + "auto" + ], + "description": "title 的来源:custom=用户手动重命名,auto=系统据首条用户消息自动生成(截断或 LLM 摘要)" + }, + "origin": { + "type": "string", + "enum": [ + "user", + "delegation" + ], + "description": "会话创建来源(手动多会话模式下使用):user=用户手动新建,delegation=被其他 Agent 委派时自动新建" + }, + "background": { + "type": "boolean" + }, + "inbound": { + "type": "boolean", + "description": "这一轮是不是「另一个 Agent 发来的消息」(SendMessage 来信)。**与 background 正交**:background 回答「该不该抢会话的前台控制槽」,本字段回答「这一轮在做什么」。来信已回归前台语义(与用户消息同路径),因此不再带 background;但「对方正在处理别人的来信」与「对方正在与用户对话」对**是否该打断**的判断截然不同,需要一个独立标记(见 agent/agent-activity.ts 的 kind 派生)。同 background,它描述的是**当前这一轮**,同一条 run 被复用时按当轮改写。" + }, + "started_at": { + "type": "string", + "description": "开始时间(ISO 8601 格式,如 2024-01-15T10:30:00Z)", + "format": "date-time" + }, + "turn_started_at": { + "type": "string", + "description": "**当前这一轮**开始执行的时间(ISO 8601)。同一条 run 会跨轮复用,started_at 停在第一次创建的时刻——一条聊了三天的会话,它的 started_at 就是三天前,拿来算「已运行多久」会报出「已运行 3 天」这种荒谬值,并把忙碌载荷的等待/打断建议带偏。每次把 run 置为 running 的路径(创建、复用、activateRun、activateSessionMetaRun)都要刷新它。updatedAt 不能替代:它会被每次 appendMessage 推后,代表的是「最后一次活动」而非「本轮起点」。", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "description": "完成时间(ISO 8601 格式),仅 status 为 completed 或 failed 时有值", + "format": "date-time" }, "status": { "type": "string", - "description": "Raw state returned by this HTTP endpoint: running, idle, completed or failed. A handed_off Run currently returns 404 because the route filters that state; do not infer missing data, failure or completion from 404.", + "description": "Run lifecycle state. handed_off means the conversation was handed off, not that the target completed or the submitted turn succeeded.", "enum": [ "running", "idle", "completed", - "failed" + "failed", + "handed_off" ] - } - }, - "additionalProperties": false - }, - "RunReceipts": { - "type": "object", - "required": [ - "run_id", - "receipts" - ], - "properties": { - "run_id": { + }, + "failure_reason": { "type": "string", - "minLength": 1, - "description": "当前可信用户有权读取的 Run ID。" + "description": "失败原因(仅 status=failed 时有值):process_restart=进程重启时被回收的孤儿 run;timeout=执行超时被强制终结;stale=历史兼容值(旧版本曾把超过复用窗口的 idle run 错标为失败);error=查询过程抛出错误", + "enum": [ + "process_restart", + "timeout", + "stale", + "error" + ] }, - "receipts": { - "type": "array", - "items": { - "type": "object", - "required": [ - "run_id", - "goal", - "input", - "output", - "tool_calls", - "outcome", - "started_at", - "completed_at" - ], - "properties": { - "receipt_id": { - "type": "string", - "minLength": 1 - }, - "run_id": { - "type": "string", - "description": "关联的 Run ID" - }, - "execution_attachment": { + "interruption_reason": { + "type": "string", + "description": "最近一次未完成轮次的主动中止原因。Run 会回到 idle 以保留多轮会话复用能力,完整审计信息同时写入 Receipt", + "enum": [ + "user_abort", + "agent_interrupt", + "reset", + "system_suspend", + "process_shutdown", + "process_restart", + "orphan_reclaimed", + "background_timeout" + ] + }, + "interrupted_at": { + "type": "string", + "description": "最近一次主动中止发生时间(ISO 8601)。仅 interruption_reason 存在时有值", + "format": "date-time" + }, + "pending_synthesis": { + "type": "object", + "required": [ + "status", + "interruption_reason", + "created_at", + "completed_tool_calls" + ], + "properties": { + "status": { + "type": "string", + "const": "required", + "description": "固定为 required:已完成工具事实存在,但最终业务解释尚未正常收口" + }, + "interruption_reason": { + "type": "string", + "enum": [ + "user_abort", + "agent_interrupt", + "reset", + "system_suspend", + "process_shutdown", + "process_restart", + "orphan_reclaimed", + "background_timeout" + ] + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "待综合状态创建时间(ISO 8601)" + }, + "completed_tool_calls": { + "type": "array", + "minItems": 1, + "maxItems": 128, + "items": { "type": "object", "required": [ - "userId", - "agentId", - "runId" + "invocation_id", + "tool_name" ], "properties": { - "userId": { + "invocation_id": { "type": "string", - "minLength": 1, "maxLength": 256, - "pattern": "^\\S+$" + "description": "已经成功完成的工具调用 ID,用于恢复时识别既成事实并避免重复执行" }, - "agentId": { + "tool_name": { "type": "string", - "minLength": 1, "maxLength": 256, - "pattern": "^\\S+$", - "description": "执行该 Run 的权威 Agent ID。" - }, - "runId": { + "description": "已经成功完成的模型可见工具名称,供审计和恢复提示使用" + } + }, + "additionalProperties": false + }, + "description": "恢复时必须复用、不得仅为重取同一结果而再次执行的成功工具调用" + } + }, + "additionalProperties": false, + "description": "系统中断发生在工具成功之后、最终解释完成之前时持久化的恢复状态;正常综合成功后清除" + }, + "interrupted_turn_facts": { + "type": "object", + "required": [ + "interruption_reason", + "recorded_at", + "deliveries" + ], + "properties": { + "interruption_reason": { + "type": "string", + "description": "被记录轮次的中止原因(与 interruption_reason 字段同一枚举)。user_abort 时消费方可下「被用户停止」断言,其余原因只能弱化归因", + "enum": [ + "user_abort", + "agent_interrupt", + "reset", + "system_suspend", + "process_shutdown", + "process_restart", + "orphan_reclaimed", + "background_timeout" + ] + }, + "recorded_at": { + "type": "string", + "format": "date-time" + }, + "deliveries": { + "type": "array", + "maxItems": 64, + "items": { + "type": "object", + "required": [ + "tool_name", + "preview", + "attachment_count", + "delivery_status" + ], + "properties": { + "tool_name": { "type": "string", - "minLength": 1, "maxLength": 256, - "pattern": "^\\S+$", - "description": "承载本次执行的 DesireCore Run ID。" + "description": "交付类工具的模型可见名称(builtin-tools/tool-effects 的 effect.class===\"delivery\" 派生名单成员,含历史 alias)" }, - "conversationId": { + "preview": { "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的用户可见 Conversation ID;缺失表示该 Run 不属于对话时间线。" + "maxLength": 200, + "description": "已投递内容的开头片段(取调用 input.message / input.content 的前 50 字符)。空串表示 input 缺失或非字符串(如 MailOperations 这类通用代理),消费方必须按「无法确认实际送达」的弱化措辞处理,不得下已送达断言" }, - "workContextId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的稳定 Work Context ID,用于跨续跑保持同一工作语境。" + "attachment_count": { + "type": "integer", + "minimum": 0, + "description": "调用 input.attachments 数组长度;0 表示无附件" }, - "workflowRunId": { + "delivery_status": { "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的 Workflow Run ID;与 DesireCore Run ID 正交。" - }, - "cellId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的执行 Cell ID,表示服务实例所在的逻辑隔离单元。" - }, - "serviceInstanceId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$", - "description": "可选的 Agent Service 实例 ID;出现时必须同时提供其所属 cellId。" - }, - "jobId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$" - }, - "attemptId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$" - }, - "capsuleId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^\\S+$" - } - }, - "allOf": [ - { - "if": { - "required": [ - "serviceInstanceId" - ] - }, - "then": { - "required": [ - "cellId" - ] - } - }, - { - "if": { - "required": [ - "attemptId" - ] - }, - "then": { - "required": [ - "jobId" - ] - } - }, - { - "if": { - "required": [ - "capsuleId" - ] - }, - "then": { - "required": [ - "jobId", - "attemptId" - ] - } - } - ], - "additionalProperties": false, - "description": "生成本回执的完整 ID-only 执行归属;旧回执可缺失。" - }, - "goal": { - "type": "string", - "description": "执行目标(从用户输入中提取的任务描述)" - }, - "input": { - "type": "string", - "description": "用户原始输入(完整的用户消息)" - }, - "output": { - "type": "string", - "description": "最终输出文本(Agent 的完整响应)" - }, - "tool_calls": { - "type": "array", - "description": "工具调用列表(按执行顺序排列)", - "items": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "invocation_id": { - "type": "string", - "maxLength": 256, - "description": "工具调用的全局唯一标识;用于在同名、重复或并行调用中准确关联开始与结束记录" - }, - "name": { - "type": "string", - "description": "工具名称(如 read_file、execute_command)" - }, - "input": { - "description": "工具输入审计摘要;新记录仅保存 param_keys,不保存参数值或凭据,旧回执可保留原结构" - }, - "output_summary": { - "type": "string", - "description": "工具输出摘要(用于快速了解工具执行结果,非完整输出)" - }, - "duration_ms": { - "type": "number", - "description": "执行耗时(毫秒),用于性能分析", - "minimum": 0 - }, - "started_at": { - "type": "string", - "description": "该工具调用开始时间(ISO 8601)", - "format": "date-time" - }, - "completed_at": { - "type": "string", - "description": "该工具调用结束时间(ISO 8601)", - "format": "date-time" - }, - "status": { - "type": "string", - "description": "工具调用执行结果:success(成功)、error(执行出错)", - "enum": [ - "success", - "error" - ] - } - }, - "additionalProperties": false - } - }, - "evidence": { - "type": "string", - "description": "决策依据(来自 thinking 或 reasoning 过程,解释 Agent 为何做出某些决策)" - }, - "outcome": { - "type": "object", - "required": [ - "status", - "auto_completed" - ], - "properties": { - "status": { - "type": "string", - "description": "结果状态:success(成功完成)、partial(部分完成)、failed(执行失败)", "enum": [ - "success", - "partial", - "failed" + "completed", + "failed", + "interrupted" ] - }, - "auto_completed": { - "type": "boolean", - "description": "是否无人工干预完成(true 表示完全自动执行,false 表示需要用户确认或修正)" - }, - "user_edit_count": { - "type": "number", - "minimum": 0 - }, - "partial_result_available": { - "type": "boolean", - "description": "partial 回执是否包含可直接展示的已完成阶段结果,而不是空占位" - }, - "pending_synthesis": { - "type": "boolean", - "description": "是否仍需基于已完成工具事实生成最终业务解释;恢复时不得因此重复执行成功工具" - } - }, - "additionalProperties": false - }, - "started_at": { - "type": "string", - "description": "开始时间(ISO 8601 格式)", - "format": "date-time" - }, - "completed_at": { - "type": "string", - "description": "完成时间(ISO 8601 格式)", - "format": "date-time" - }, - "duration_ms": { - "type": "number", - "description": "总耗时(毫秒),从开始到完成的完整时间", - "minimum": 0 - }, - "usage": { - "type": "object", - "properties": { - "input_tokens": { - "type": "number", - "description": "本次查询的非缓存输入 token 数(不含 cache_read / cache_write;per-query delta,不累计)", - "minimum": 0 - }, - "output_tokens": { - "type": "number", - "description": "本次查询的输出 token 数(per-query delta,不累计)", - "minimum": 0 - }, - "cost_usd": { - "type": "number", - "description": "本次查询的成本(美元,已包含缓存费用;per-query delta,不累计)", - "minimum": 0 - }, - "provider": { - "type": "string", - "description": "使用的提供商标识(如 openai、anthropic)" - }, - "model": { - "type": "string", - "description": "使用的模型 ID(如 gpt-5-mini、claude-sonnet-4)" - }, - "cache_read_tokens": { - "type": "number", - "description": "本次查询从 prompt 缓存读取的 token 数(per-query delta,不累计)", - "minimum": 0 - }, - "cache_write_tokens": { - "type": "number", - "description": "本次查询写入 prompt 缓存的 token 数(per-query delta,不累计)", - "minimum": 0 } }, "additionalProperties": false }, - "route_decision": { - "type": "object", - "additionalProperties": true - }, - "interruption_reason": { - "type": "string", - "description": "本条 partial 回执对应的主动中止原因。旧回执无此字段,保持向后兼容", - "enum": [ - "user_abort", - "agent_interrupt", - "reset", - "system_suspend", - "process_shutdown", - "process_restart", - "orphan_reclaimed", - "background_timeout" - ] - } - }, - "additionalProperties": false + "description": "被中断轮内交付类工具调用的终态清单(tool-effects 派生名单 + 已结算终态;同 toolCallId 去重;超上限时保留最近的条目)。空数组是权威事实「该轮没有任何交付」,消费方不得再退回尾窗启发式反推" + } }, - "description": "该 Run 的结构化回执,按写入顺序。" + "additionalProperties": false }, - "total": { - "type": "integer", - "minimum": 0, - "description": "分页读取时该 Run 的有效 Receipt 总数。" + "engine": { + "type": "string", + "description": "运行时引擎" }, - "offset": { - "type": "integer", - "minimum": 0, - "description": "从最新 Receipt 向前跳过的数量。" + "session_id": { + "type": "string", + "description": "关联的 Socket.IO session ID,用于实时通信和中断控制" }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "description": "本页最多返回的 Receipt 数。" + "messageCount": { + "type": "number", + "description": "消息总数(由 SessionStore 维护)" }, - "has_more": { + "updatedAt": { + "type": "string", + "description": "最后更新时间(由 SessionStore 维护)" + }, + "cleared": { "type": "boolean", - "description": "是否仍有更早的 Receipt 可按需加载。" + "description": "Tombstone 标记:用户清除对话后置为 true。会话列表和恢复逻辑会过滤掉此类 run,但 receipts/ 子目录保留,活动记录仍能读到该 run 的真实 token 消耗凭据(审计用)" }, - "skipped": { - "type": "integer", - "minimum": 0, - "description": "流式读取中跳过的损坏或超大行数。" - } - }, - "additionalProperties": false, - "description": "单个用户可见 Run 的 Receipt 响应。" - }, - "AgentList": { - "type": "object", - "required": [ - "agents" - ], - "properties": { - "agents": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AgentSummary" - } - } - }, - "additionalProperties": false - }, - "RunList": { - "type": "object", - "required": [ - "runs" - ], - "properties": { - "runs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RunMeta" - } - } - }, - "additionalProperties": false - }, - "OperationsError": { - "type": "object", - "required": [ - "error" - ], - "properties": { - "error": { - "type": "string" - } - }, - "additionalProperties": false - }, - "QueryStartRequest": { - "type": "object", - "anyOf": [ - { - "required": [ - "prompt" - ], - "properties": { - "prompt": { - "type": "string", - "minLength": 1 - } - } + "cleared_at": { + "type": "string", + "description": "Tombstone 写入时间(ISO 8601)。检查点清理按此时间计算 deletedRunRetentionMinutes;旧数据缺失时兼容 cleared=true", + "format": "date-time" }, - { - "required": [ - "mediaRefs" - ], - "properties": { - "mediaRefs": { - "type": "array", - "minItems": 1 - } - } - } - ], - "properties": { - "prompt": { + "archived_at": { "type": "string", - "description": "当前用户提交的提示词。" + "description": "会话归档时间(ISO 8601)。归档是独立于运行 status 的历史生命周期,归档 run 不再自动恢复,但消息和审计记录仍保留", + "format": "date-time" }, - "options": { + "handoffFrom": { + "type": "string", + "readOnly": true, + "description": "Source Run ID for this handoff. A link grants no access or continuation permission." + }, + "handoffTo": { + "type": "string", + "readOnly": true, + "description": "Target Run ID for this handoff. Target admission and completion require their own evidence." + }, + "team_id": { + "type": "string", + "description": "本 run 执行时所处的团队作用域。委派续跑(DelegateControl action=\"resume\")必须按它重建执行环境——用调用方**当前**的 teamId 会让续跑落进另一个团队的 prompt 与共享工作目录,带着旧团队的历史在新环境里接力;缺省表示该 run 不属于任何团队。" + }, + "work_context_id": { + "type": "string", + "minLength": 1, + "description": "本 Run 所属的稳定 Work Context ID。continue/resume 必须沿用,isolated/fork 必须记录新建结果;旧 Run 可缺省并由幂等 legacy backfill 建段。" + }, + "execution_attachment": { "type": "object", + "required": [ + "userId", + "agentId", + "runId" + ], "properties": { - "routingMode": { + "userId": { "type": "string", - "enum": [ - "smart", - "fixed" - ], - "description": "模型路由模式。" + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$" }, - "cwd": { + "agentId": { "type": "string", - "description": "当前轮授权工作目录。" + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "执行该 Run 的权威 Agent ID。" }, - "systemPrompt": { + "runId": { "type": "string", - "description": "调用方附加的系统提示词。" - }, - "allowedTools": { - "type": "array", - "items": { - "type": "string" - }, - "description": "允许的工具 ID。" - }, - "disallowedTools": { - "type": "array", - "items": { - "type": "string" - }, - "description": "禁止的工具 ID。" - }, - "maxTurns": { - "type": "number", - "description": "最大 Agent 回合数。" + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "承载本次执行的 DesireCore Run ID。" }, - "model": { + "conversationId": { "type": "string", - "description": "模型名称。" + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的用户可见 Conversation ID;缺失表示该 Run 不属于对话时间线。" }, - "engine": { - "const": "desirecore-engine", - "description": "运行时引擎。" + "workContextId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的稳定 Work Context ID,用于跨续跑保持同一工作语境。" }, - "provider": { + "workflowRunId": { "type": "string", - "description": "Provider 协议族。" + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的 Workflow Run ID;与 DesireCore Run ID 正交。" }, - "providerId": { + "cellId": { "type": "string", - "description": "Provider 唯一 ID。" + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的执行 Cell ID,表示服务实例所在的逻辑隔离单元。" }, - "apiKey": { + "serviceInstanceId": { "type": "string", - "deprecated": true, - "description": "Compatibility-only provider credential override. Configure provider credentials on the instance; this field does not authenticate the HTTP request." + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的 Agent Service 实例 ID;出现时必须同时提供其所属 cellId。" }, - "reasoning": { + "jobId": { "type": "string", - "description": "本轮 reasoning 模式。" + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$" }, - "reasoningDefault": { - "type": "string" + "attemptId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$" }, - "suppressThinking": { - "type": "boolean", - "description": "仅隐藏公开思考展示,不改变模型思考强度或服务端私有协议回放。" + "capsuleId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$" + } + }, + "allOf": [ + { + "if": { + "required": [ + "serviceInstanceId" + ] + }, + "then": { + "required": [ + "cellId" + ] + } }, - "immersiveVoice": { - "type": "object", - "additionalProperties": false, - "required": [ - "modelSource", - "reasoning" - ], - "properties": { - "modelSource": { - "type": "string", - "enum": [ - "conversation", - "fast", - "specified" - ], - "description": "当前对话、低延迟默认映射或人类指定的精确模型;低延迟不代表低费用。" - }, - "providerId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "specified 时使用的本地 Provider 接入面 ID。" - }, - "modelName": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "description": "specified 时使用的精确模型 ID。" - }, - "reasoning": { - "type": "string", - "enum": [ - "follow", - "low-latency", - "auto", - "off", - "minimal", - "low", - "medium", - "high", - "xhigh", - "max" - ], - "description": "follow 沿用对话;low-latency 只选择模型显式支持的最低档位;其他值为独立思考强度,不受显示思考开关影响。" - } + { + "if": { + "required": [ + "attemptId" + ] + }, + "then": { + "required": [ + "jobId" + ] } }, - "voiceSelectionRef": { - "type": "string", - "minLength": 1, - "maxLength": 256 - }, - "agentId": { - "type": "string", - "description": "当前 Agent ID。" - }, - "conversationHistory": { - "type": "array", - "items": { - "$ref": "#/components/schemas/QueryStartRequest/definitions/conversationMessage" + { + "if": { + "required": [ + "capsuleId" + ] }, - "description": "多轮连续性使用的结构化历史。" - }, - "conversationId": { + "then": { + "required": [ + "jobId", + "attemptId" + ] + } + } + ], + "additionalProperties": false, + "description": "本 Run 的权威执行归属;新 Run 由服务端构造,旧 Run 可缺失。其 user/agent/run/conversation/work-context 必须与外围 RunMeta 一致。" + }, + "work_context_selection": { + "type": "object", + "required": [ + "context_mode", + "origin" + ], + "properties": { + "context_mode": { "type": "string", - "description": "对话稳定 ID。" - }, - "resumeFromDisk": { - "type": "boolean", - "description": "是否优先从磁盘恢复权威结构化历史。" + "enum": [ + "continue", + "isolated", + "fork" + ], + "description": "必填且无默认值:continue=继续明确存在的同一任务上下文;isolated=不同新任务;fork=同一任务有意分叉。" }, - "preferredRunId": { + "origin": { "type": "string", - "description": "用户显式选择的恢复分支 run ID。" - }, - "excludeMessageIds": { - "type": "array", - "items": { - "type": "string" - }, - "description": "恢复时剔除的本轮重发消息 ID。" + "enum": [ + "delegate", + "mention", + "task", + "workflow", + "schedule", + "webhook", + "decision", + "legacy" + ] }, - "channel": { + "work_context_id": { "type": "string", - "description": "消息来源渠道。" - }, - "skillIds": { - "type": "array", - "items": { - "type": "string" - }, - "description": "本轮显式选择的 Skill ID。" - }, - "skillArgs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "本轮 Skill 命令参数。" - }, - "fileReferences": { - "type": "array", - "items": { - "type": "string" - }, - "description": "用户引用的文件路径。" + "minLength": 1, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "continue 必填:要继续的稳定 Work Context ID;禁止回退到目标 Agent 最近一次会话。" }, - "activeDocPath": { + "source_work_context_id": { "type": "string", - "description": "本次请求的可选文件参考路径。自动采集仅来自本窗口、属于当前会话的可见文档;显式传入或重试沿用已冻结的引用,不按当下界面重选。读取仍须授权,引用本身不表示编辑或附件交付请求。" + "minLength": 1, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "fork 必填:有意分叉的来源 Work Context ID。" }, - "file": { + "task_ref": { "type": "object", "required": [ - "name", - "size", - "type" + "board_id", + "task_id" ], "properties": { - "name": { + "board_id": { "type": "string", - "description": "附件名称。" + "minLength": 1 }, - "size": { + "task_id": { "type": "string", - "description": "附件展示大小。" + "minLength": 1, + "description": "TaskBoard 内稳定 Task ID。" }, - "type": { - "type": "string", - "description": "附件类型。" + "task_version": { + "type": "integer", + "minimum": 1, + "description": "选择时观察到的 Task 版本快照;身份查重只使用 board_id + task_id。" } }, "additionalProperties": false }, - "teamId": { + "intent_id": { "type": "string", - "description": "当前团队 ID。" + "minLength": 1, + "maxLength": 256 + }, + "context_reason": { + "type": "string", + "minLength": 1, + "maxLength": 2000, + "description": "isolated/fork 必填:说明为何这是不同新任务或为何允许对同一 Task 有意分叉。" } }, - "additionalProperties": false - }, - "agentReferences": { - "type": "array", - "items": { - "type": "object", - "required": [ - "occurrence_id", - "target", - "range" - ], - "properties": { - "occurrence_id": { - "type": "string", - "minLength": 8, - "maxLength": 256, - "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", - "description": "客户端生成、在一次来源 revision 内稳定的 occurrence ID;同一 Agent 可出现多次。" - }, - "target": { - "type": "object", - "required": [ - "agent_id", - "display_name_snapshot" - ], + "allOf": [ + { + "if": { "properties": { - "agent_id": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", - "description": "目标 Agent 的稳定语义 ID;仅允许 ASCII 字母、数字、下划线和连字符。" - }, - "display_name_snapshot": { - "type": "string", - "minLength": 1, - "description": "服务端按 agent_id 从可信 Agent Registry 重建的显示快照;客户端同名字段只用于提交时 range 校验,不参与身份或 system prompt。" + "context_mode": { + "const": "continue" } }, - "additionalProperties": false, - "description": "稳定 Agent 引用;是否执行由来源契约决定,显示名不能单独触发投递。" + "required": [ + "context_mode" + ] }, - "range": { - "type": "object", + "then": { "required": [ - "start", - "end" + "work_context_id" ], - "properties": { - "start": { - "type": "integer", - "minimum": 0, - "description": "正文 UTF-16 code unit 的起始偏移,包含。" - }, - "end": { - "type": "integer", - "minimum": 1, - "description": "正文 UTF-16 code unit 的结束偏移,不包含。" - } - }, - "additionalProperties": false, - "description": "结构化 token 在来源正文中的半开区间;服务端校验 start < end 且与正文匹配。" + "not": { + "anyOf": [ + { + "required": [ + "source_work_context_id" + ] + }, + { + "required": [ + "intent_id" + ] + } + ] + } } }, - "additionalProperties": false, - "description": "picker 生成的结构化 Agent token;聊天中为非执行引用,看板中可由来源 owner 形成 Mention。" - }, - "description": "非执行 Agent 引用。" - }, - "mentionOccurrences": { - "type": "array", - "items": { - "type": "object", - "required": [ - "occurrence_id", - "target", - "range" - ], - "properties": { - "occurrence_id": { - "type": "string", - "minLength": 8, - "maxLength": 256, - "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", - "description": "客户端生成、在一次来源 revision 内稳定的 occurrence ID;同一 Agent 可出现多次。" - }, - "target": { - "type": "object", - "required": [ - "agent_id", - "display_name_snapshot" - ], + { + "if": { "properties": { - "agent_id": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", - "description": "目标 Agent 的稳定语义 ID;仅允许 ASCII 字母、数字、下划线和连字符。" - }, - "display_name_snapshot": { - "type": "string", - "minLength": 1, - "description": "服务端按 agent_id 从可信 Agent Registry 重建的显示快照;客户端同名字段只用于提交时 range 校验,不参与身份或 system prompt。" + "context_mode": { + "const": "isolated" } }, - "additionalProperties": false, - "description": "稳定 Agent 引用;是否执行由来源契约决定,显示名不能单独触发投递。" + "required": [ + "context_mode" + ] }, - "range": { - "type": "object", + "then": { "required": [ - "start", - "end" + "context_reason" ], - "properties": { - "start": { - "type": "integer", - "minimum": 0, - "description": "正文 UTF-16 code unit 的起始偏移,包含。" + "anyOf": [ + { + "required": [ + "task_ref" + ] }, - "end": { - "type": "integer", - "minimum": 1, - "description": "正文 UTF-16 code unit 的结束偏移,不包含。" + { + "required": [ + "intent_id" + ] } - }, - "additionalProperties": false, - "description": "结构化 token 在来源正文中的半开区间;服务端校验 start < end 且与正文匹配。" + ], + "not": { + "anyOf": [ + { + "required": [ + "work_context_id" + ] + }, + { + "required": [ + "source_work_context_id" + ] + } + ] + } } }, - "additionalProperties": false, - "description": "picker 生成的结构化 Agent token;聊天中为非执行引用,看板中可由来源 owner 形成 Mention。" - }, - "description": "Retired execution input; the HTTP route rejects this field. Use non-executable agentReferences.", - "deprecated": true - }, - "mentionRecipientRequests": { - "type": "array", - "items": { - "type": "object", - "required": [ - "occurrence_ids", - "recipient_agent_id", - "route" - ], - "properties": { - "occurrence_ids": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 8, - "maxLength": 256 + { + "if": { + "properties": { + "context_mode": { + "const": "fork" + } }, - "description": "同一来源 revision 中指向该 recipient 的全部 occurrence IDs;只产生一个 delivery。" - }, - "recipient_agent_id": { - "type": "string", - "minLength": 1, - "description": "必须与 occurrence.target.agent_id 一致的稳定 Agent ID。" + "required": [ + "context_mode" + ] }, - "route": { - "type": "string", - "enum": [ - "current_conversation", - "external_agent" + "then": { + "required": [ + "source_work_context_id", + "task_ref", + "context_reason" ], - "description": "当前 Agent 走普通 conversation;非当前 Agent 走独立 Mention delivery。" - }, - "context_selection": { - "allOf": [ - { - "type": "object", - "required": [ - "context_mode", - "origin" - ], - "properties": { - "context_mode": { - "type": "string", - "enum": [ - "continue", - "isolated", - "fork" - ], - "description": "必填且无默认值:continue=继续明确存在的同一任务上下文;isolated=不同新任务;fork=同一任务有意分叉。" - }, - "origin": { - "type": "string", - "enum": [ - "delegate", - "mention", - "task", - "workflow", - "schedule", - "webhook", - "decision", - "legacy" - ] - }, - "work_context_id": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", - "description": "continue 必填:要继续的稳定 Work Context ID;禁止回退到目标 Agent 最近一次会话。" - }, - "source_work_context_id": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", - "description": "fork 必填:有意分叉的来源 Work Context ID。" - }, - "task_ref": { - "type": "object", - "required": [ - "board_id", - "task_id" - ], - "properties": { - "board_id": { - "type": "string", - "minLength": 1 - }, - "task_id": { - "type": "string", - "minLength": 1, - "description": "TaskBoard 内稳定 Task ID。" - }, - "task_version": { - "type": "integer", - "minimum": 1, - "description": "选择时观察到的 Task 版本快照;身份查重只使用 board_id + task_id。" - } - }, - "additionalProperties": false - }, - "intent_id": { - "type": "string", - "minLength": 1, - "maxLength": 256 - }, - "context_reason": { - "type": "string", - "minLength": 1, - "maxLength": 2000, - "description": "isolated/fork 必填:说明为何这是不同新任务或为何允许对同一 Task 有意分叉。" - } + "not": { + "anyOf": [ + { + "required": [ + "work_context_id" + ] }, - "allOf": [ - { - "if": { - "properties": { - "context_mode": { - "const": "continue" - } - }, - "required": [ - "context_mode" + { + "required": [ + "intent_id" + ] + } + ] + } + } + } + ], + "additionalProperties": false, + "description": "启动此 Run 时调用方显式提交并经 preflight/commit 接受的 Work Context 选择快照。" + }, + "work_context_task_ref": { + "type": "object", + "required": [ + "board_id", + "task_id" + ], + "properties": { + "board_id": { + "type": "string", + "minLength": 1 + }, + "task_id": { + "type": "string", + "minLength": 1, + "description": "TaskBoard 内稳定 Task ID。" + }, + "task_version": { + "type": "integer", + "minimum": 1, + "description": "选择时观察到的 Task 版本快照;身份查重只使用 board_id + task_id。" + } + }, + "additionalProperties": false + }, + "work_context_intent_id": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "work_context_origin": { + "type": "string", + "enum": [ + "delegate", + "mention", + "task", + "workflow", + "schedule", + "webhook", + "decision", + "legacy" + ], + "description": "此 Run 进入 Work Context 的权威入口快照。" + }, + "child_context": { + "type": "object", + "description": "原委派对子 Agent 上下文组装施加的**收紧**覆盖(Delegate 的 childContext 解析结果)。续跑必须按它重建,否则「只传 run_id 的裸续跑」会让原本被刻意收窄的上下文(memoryScope: none、关掉的能力、section 过滤)在续跑时全部放开——收紧只能保持或更严。形状与 ContextAssemblyOptions 的对应覆盖字段一致;缺省表示原委派未收紧。", + "additionalProperties": true + }, + "child_tool_ceiling": { + "type": "object", + "description": "委派执行时的工具键与文件访问范围快照。tools 仅记录执行方当时的工具作为审计证据,不是父智能体的授权上限;持久 Agent 续跑使用执行方当前工具权限,并保留防递归限制。allowedReadDirs / allowedReadPaths / allowedWriteDirs 继续与调用方当前范围取交集;缺省的文件维度表示无限制。旧快照的工具键同样不再裁剪目标工具,形状错误仍拒绝恢复。", + "additionalProperties": true + }, + "work_dir": { + "type": "string", + "description": "本 run 起跑时解析出的**基准**工作目录(团队共享目录 > 手动话题 Project > Agent/全局 effectiveCwd;不含运行时 worktree 绑定那层派生覆盖)。委派续跑据此确认「原来那次是在哪个目录里跑的」:承接来的历史里全是对旧目录中文件的读写,工作目录偏好在两次之间改过的话,续跑就会带着旧上下文在另一个项目里接力。⚠ 与 worktree_cwd 同一约定:**空字符串表示「起跑时确实没解析出工作目录」,字段缺席表示「该 run 创建于本字段引入之前」**——两者混为一谈的话,「当初没配、现在配了工作目录」会被闸门当成老数据跳过,而承接来的历史其实来自当时的进程默认目录。" + }, + "worktree_cwd": { + "type": "string", + "description": "本 run 起跑时实际附着的 Owner worktree 目录(`work_dir` 之上的派生覆盖)。与 work_dir 分开记:前者是用户偏好解析出的基准目录,这一条是运行时按 Owner 绑定的链接工作树。委派续跑两者都要一致——只比基准目录的话,源 run 当初挂在某个 worktree 上、而绑定后来被退出或改挂时闸门会放行,续跑于是回到主 checkout 或另一个工作树里接力。⚠ **空字符串表示「起跑时确实没有附着任何 worktree」,字段缺席表示「该 run 创建于本字段引入之前,无从得知」**——两者必须可区分:都写成缺席的话,「当初没挂、现在挂上了」这一格会被当成老数据跳过比较,而它恰恰是要拦的(历史来自主 checkout,续跑却跑进新工作树)。因此起跑路径**总是**写这个字段。" + }, + "resumedFrom": { + "type": "string", + "description": "同一执行连续性的来源 Run ID:由 DelegateControl(action=\"resume\") 或已接纳 Work Context continue 承接该 run。承接来的历史只作为运行时上下文喂给模型,**不落进本 run 的 messages.jsonl**,因此二次续跑必须沿这条链回溯收集,否则每续一次就丢掉更早那一段的历史与工具结果。与 handoffFrom 的区别:那是会话转交(用户换了对话对象),这是同一委派任务/Work Context 的接力。" + }, + "delegation": { + "type": "object", + "description": "委派溯源:仅由 Delegate 工具创建/复用的 run 有值,记录本 run 由哪个 Agent 的哪个父 run 委派产生。async 场景下父消息 metadata.delegate.children 在工具返回时为空,可通过此字段反查委派链(InspectRuns 工具 action=list 依赖它过滤子 run)", + "required": [ + "fromAgentId" + ], + "properties": { + "fromAgentId": { + "type": "string", + "description": "发起委派的 Agent ID(delegation.fromAgentId,用户直接发起时为 owner)" + }, + "parentRunId": { + "type": "string", + "description": "委派方发起委派时所在的父 run ID。由父 sessionId 经 getRunIdBySession 反查;反查失败时缺省" + }, + "parentClientRequestId": { + "type": "string", + "minLength": 1, + "description": "委派子 Run 创建时,由 ToolExecutor 从父 Agent **当前轮次**注入的可信 clientRequestId。它与可复用的 parentRunId 分开记录,用于证明子 Run 确实由父 Run 的这一轮产生;旧 Run 可缺省,但需要轮次级授权的新入口必须在缺失时失败关闭。" + }, + "mode": { + "type": "string", + "enum": [ + "sync", + "async", + "via-messaging", + "fan-out", + "subtask" + ], + "description": "委派模式(handoff 不写 delegation,走 handoffFrom/handoffTo 链路;worker 无独立 run)。subtask=继承发起方会话上下文的子体,历史来源另记在 subtask_inherited_from。" + } + }, + "additionalProperties": false + }, + "delivery_routing": { + "type": "object", + "required": [ + "group", + "audience" + ], + "properties": { + "group": { + "type": "string", + "enum": [ + "delegator-governed", + "user-handoff", + "run-inherited" + ], + "description": "委派交付路由分组:delegator-governed=先回委派方;user-handoff=目标会话直接服务用户;run-inherited=沿用目标 Run 已冻结受众。" + }, + "audience": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "conversationId" + ], + "properties": { + "type": { + "type": "string", + "const": "user", + "description": "交付给当前用户可见会话。" + }, + "conversationId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "用户接收交付的稳定会话 ID。" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "type", + "agentId" + ], + "properties": { + "type": { + "type": "string", + "const": "delegator", + "description": "交付给发起委派的主 Agent。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "委派方规范 Agent ID。" + }, + "parentRunId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "发起委派时的父 Run ID。" + }, + "conversationId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "异步结果必须回到的父会话 ID。" + }, + "correlationId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "请求与最终交付的稳定关联 ID。" + } + }, + "additionalProperties": false + } + ], + "description": "准入时冻结的真实交付受众;只允许用户会话或委派方 Agent。" + } + }, + "additionalProperties": false, + "allOf": [ + { + "if": { + "properties": { + "group": { + "const": "delegator-governed" + } + }, + "required": [ + "group" + ] + }, + "then": { + "properties": { + "audience": { + "type": "object", + "required": [ + "type", + "agentId" + ], + "properties": { + "type": { + "type": "string", + "const": "delegator", + "description": "交付给发起委派的主 Agent。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "委派方规范 Agent ID。" + }, + "parentRunId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "发起委派时的父 Run ID。" + }, + "conversationId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "异步结果必须回到的父会话 ID。" + }, + "correlationId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "请求与最终交付的稳定关联 ID。" + } + }, + "additionalProperties": false + } + } + } + }, + { + "if": { + "properties": { + "group": { + "const": "user-handoff" + } + }, + "required": [ + "group" + ] + }, + "then": { + "properties": { + "audience": { + "type": "object", + "required": [ + "type", + "conversationId" + ], + "properties": { + "type": { + "type": "string", + "const": "user", + "description": "交付给当前用户可见会话。" + }, + "conversationId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "用户接收交付的稳定会话 ID。" + } + }, + "additionalProperties": false + } + } + } + } + ], + "description": "本 Run 在准入时冻结的交付路由分组与真实受众。委派/异步恢复/interject 不得根据当前激活会话重新推断;旧 Run 可缺省并按受控兼容规则只读派生。" + }, + "subtask_inherited_from": { + "type": "object", + "description": "本 run 作为 subtask 子体时,继承的那份对话历史的来源坐标。与 resumedFrom 是**两条不同的边**:resumedFrom 是「同一委派任务的接力」(承接子 run 自己的历史),本字段是「继承发起方会话的上下文」(历史来自另一条时间线)。混用会让委派续跑的链校验把 subtask 的来源当成祖先去比对归属五元组,而两者的归属口径本就不同。与 resumedFrom 同一约定:继承来的历史只作为运行时上下文喂给模型,**不落进本 run 的 messages.jsonl**,因此这是事后追溯「这条 run 看过谁的上下文」的唯一依据。", + "properties": { + "conversation_id": { + "type": "string", + "description": "历史来源会话 ID(发起方有话题时用这条;跨 run 归并后的规范视图)" + }, + "run_id": { + "type": "string", + "description": "历史来源 run ID(发起方本身是委派子 run、不属于任何会话时用这条)" + } + }, + "additionalProperties": false + }, + "handoff_pending": { + "type": "string", + "readOnly": true, + "description": "Server-recorded marker for a pending handoff. Not an access credential or a replay token." + }, + "handoff_attempted": { + "type": "boolean", + "readOnly": true, + "description": "Target startup reached a stage that may have side effects. Does not establish target completion." + }, + "handoff_accepted": { + "type": "boolean", + "readOnly": true, + "description": "The target runtime accepted the handoff. Does not establish target business completion." + } + }, + "additionalProperties": false + }, + "RunRuntimeState": { + "type": "object", + "required": [ + "run_id", + "agent_id", + "status" + ], + "properties": { + "run_id": { + "type": "string", + "description": "被查询的 Run ID;兼容普通 snake_case Run 与 checkpoint/rewind 产生的 camelCase fork Run" + }, + "agent_id": { + "type": "string", + "description": "该 Run 的权威 Agent 归属,用于审阅等破坏性操作在客户端预检时核对来源" + }, + "status": { + "type": "string", + "description": "Raw state returned by this HTTP endpoint: running, idle, completed or failed. A handed_off Run currently returns 404 because the route filters that state; do not infer missing data, failure or completion from 404.", + "enum": [ + "running", + "idle", + "completed", + "failed" + ] + } + }, + "additionalProperties": false + }, + "RunReceipts": { + "type": "object", + "required": [ + "run_id", + "receipts" + ], + "properties": { + "run_id": { + "type": "string", + "minLength": 1, + "description": "当前可信用户有权读取的 Run ID。" + }, + "receipts": { + "type": "array", + "items": { + "type": "object", + "required": [ + "run_id", + "goal", + "input", + "output", + "tool_calls", + "outcome", + "started_at", + "completed_at" + ], + "properties": { + "receipt_id": { + "type": "string", + "minLength": 1 + }, + "run_id": { + "type": "string", + "description": "关联的 Run ID" + }, + "execution_attachment": { + "type": "object", + "required": [ + "userId", + "agentId", + "runId" + ], + "properties": { + "userId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "执行该 Run 的权威 Agent ID。" + }, + "runId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "承载本次执行的 DesireCore Run ID。" + }, + "conversationId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的用户可见 Conversation ID;缺失表示该 Run 不属于对话时间线。" + }, + "workContextId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的稳定 Work Context ID,用于跨续跑保持同一工作语境。" + }, + "workflowRunId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的 Workflow Run ID;与 DesireCore Run ID 正交。" + }, + "cellId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的执行 Cell ID,表示服务实例所在的逻辑隔离单元。" + }, + "serviceInstanceId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$", + "description": "可选的 Agent Service 实例 ID;出现时必须同时提供其所属 cellId。" + }, + "jobId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$" + }, + "attemptId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$" + }, + "capsuleId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^\\S+$" + } + }, + "allOf": [ + { + "if": { + "required": [ + "serviceInstanceId" + ] + }, + "then": { + "required": [ + "cellId" + ] + } + }, + { + "if": { + "required": [ + "attemptId" + ] + }, + "then": { + "required": [ + "jobId" + ] + } + }, + { + "if": { + "required": [ + "capsuleId" + ] + }, + "then": { + "required": [ + "jobId", + "attemptId" + ] + } + } + ], + "additionalProperties": false, + "description": "生成本回执的完整 ID-only 执行归属;旧回执可缺失。" + }, + "goal": { + "type": "string", + "description": "执行目标(从用户输入中提取的任务描述)" + }, + "input": { + "type": "string", + "description": "用户原始输入(完整的用户消息)" + }, + "output": { + "type": "string", + "description": "最终输出文本(Agent 的完整响应)" + }, + "tool_calls": { + "type": "array", + "description": "工具调用列表(按执行顺序排列)", + "items": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "invocation_id": { + "type": "string", + "maxLength": 256, + "description": "工具调用的全局唯一标识;用于在同名、重复或并行调用中准确关联开始与结束记录" + }, + "name": { + "type": "string", + "description": "工具名称(如 read_file、execute_command)" + }, + "input": { + "description": "工具输入审计摘要;新记录仅保存 param_keys,不保存参数值或凭据,旧回执可保留原结构" + }, + "output_summary": { + "type": "string", + "description": "工具输出摘要(用于快速了解工具执行结果,非完整输出)" + }, + "duration_ms": { + "type": "number", + "description": "执行耗时(毫秒),用于性能分析", + "minimum": 0 + }, + "started_at": { + "type": "string", + "description": "该工具调用开始时间(ISO 8601)", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "description": "该工具调用结束时间(ISO 8601)", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "工具调用执行结果:success(成功)、error(执行出错)", + "enum": [ + "success", + "error" + ] + } + }, + "additionalProperties": false + } + }, + "evidence": { + "type": "string", + "description": "决策依据(来自 thinking 或 reasoning 过程,解释 Agent 为何做出某些决策)" + }, + "outcome": { + "type": "object", + "required": [ + "status", + "auto_completed" + ], + "properties": { + "status": { + "type": "string", + "description": "结果状态:success(成功完成)、partial(部分完成)、failed(执行失败)", + "enum": [ + "success", + "partial", + "failed" + ] + }, + "auto_completed": { + "type": "boolean", + "description": "是否无人工干预完成(true 表示完全自动执行,false 表示需要用户确认或修正)" + }, + "user_edit_count": { + "type": "number", + "minimum": 0 + }, + "partial_result_available": { + "type": "boolean", + "description": "partial 回执是否包含可直接展示的已完成阶段结果,而不是空占位" + }, + "pending_synthesis": { + "type": "boolean", + "description": "是否仍需基于已完成工具事实生成最终业务解释;恢复时不得因此重复执行成功工具" + } + }, + "additionalProperties": false + }, + "started_at": { + "type": "string", + "description": "开始时间(ISO 8601 格式)", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "description": "完成时间(ISO 8601 格式)", + "format": "date-time" + }, + "duration_ms": { + "type": "number", + "description": "总耗时(毫秒),从开始到完成的完整时间", + "minimum": 0 + }, + "usage": { + "type": "object", + "properties": { + "input_tokens": { + "type": "number", + "description": "本次查询的非缓存输入 token 数(不含 cache_read / cache_write;per-query delta,不累计)", + "minimum": 0 + }, + "output_tokens": { + "type": "number", + "description": "本次查询的输出 token 数(per-query delta,不累计)", + "minimum": 0 + }, + "cost_usd": { + "type": "number", + "description": "本次查询的成本(美元,已包含缓存费用;per-query delta,不累计)", + "minimum": 0 + }, + "provider": { + "type": "string", + "description": "使用的提供商标识(如 openai、anthropic)" + }, + "model": { + "type": "string", + "description": "使用的模型 ID(如 gpt-5-mini、claude-sonnet-4)" + }, + "cache_read_tokens": { + "type": "number", + "description": "本次查询从 prompt 缓存读取的 token 数(per-query delta,不累计)", + "minimum": 0 + }, + "cache_write_tokens": { + "type": "number", + "description": "本次查询写入 prompt 缓存的 token 数(per-query delta,不累计)", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "route_decision": { + "type": "object", + "additionalProperties": true + }, + "interruption_reason": { + "type": "string", + "description": "本条 partial 回执对应的主动中止原因。旧回执无此字段,保持向后兼容", + "enum": [ + "user_abort", + "agent_interrupt", + "reset", + "system_suspend", + "process_shutdown", + "process_restart", + "orphan_reclaimed", + "background_timeout" + ] + } + }, + "additionalProperties": false + }, + "description": "该 Run 的结构化回执,按写入顺序。" + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "分页读取时该 Run 的有效 Receipt 总数。" + }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "从最新 Receipt 向前跳过的数量。" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "本页最多返回的 Receipt 数。" + }, + "has_more": { + "type": "boolean", + "description": "是否仍有更早的 Receipt 可按需加载。" + }, + "skipped": { + "type": "integer", + "minimum": 0, + "description": "流式读取中跳过的损坏或超大行数。" + } + }, + "additionalProperties": false, + "description": "单个用户可见 Run 的 Receipt 响应。" + }, + "AgentList": { + "type": "object", + "required": [ + "agents" + ], + "properties": { + "agents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AgentSummary" + } + } + }, + "additionalProperties": false + }, + "RunList": { + "type": "object", + "required": [ + "runs" + ], + "properties": { + "runs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RunMeta" + } + } + }, + "additionalProperties": false + }, + "OperationsError": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + }, + "QueryStartRequest": { + "type": "object", + "anyOf": [ + { + "required": [ + "prompt" + ], + "properties": { + "prompt": { + "type": "string", + "minLength": 1 + } + } + }, + { + "required": [ + "mediaRefs" + ], + "properties": { + "mediaRefs": { + "type": "array", + "minItems": 1 + } + } + } + ], + "properties": { + "prompt": { + "type": "string", + "description": "当前用户提交的提示词。" + }, + "options": { + "type": "object", + "properties": { + "routingMode": { + "type": "string", + "enum": [ + "smart", + "fixed" + ], + "description": "模型路由模式。" + }, + "cwd": { + "type": "string", + "description": "当前轮授权工作目录。" + }, + "systemPrompt": { + "type": "string", + "description": "调用方附加的系统提示词。" + }, + "allowedTools": { + "type": "array", + "items": { + "type": "string" + }, + "description": "允许的工具 ID。" + }, + "disallowedTools": { + "type": "array", + "items": { + "type": "string" + }, + "description": "禁止的工具 ID。" + }, + "maxTurns": { + "type": "number", + "description": "最大 Agent 回合数。" + }, + "model": { + "type": "string", + "description": "模型名称。" + }, + "engine": { + "const": "desirecore-engine", + "description": "运行时引擎。" + }, + "provider": { + "type": "string", + "description": "Provider 协议族。" + }, + "providerId": { + "type": "string", + "description": "Provider 唯一 ID。" + }, + "apiKey": { + "type": "string", + "deprecated": true, + "description": "Compatibility-only provider credential override. Configure provider credentials on the instance; this field does not authenticate the HTTP request." + }, + "reasoning": { + "type": "string", + "description": "本轮 reasoning 模式。" + }, + "reasoningDefault": { + "type": "string" + }, + "suppressThinking": { + "type": "boolean", + "description": "仅隐藏公开思考展示,不改变模型思考强度或服务端私有协议回放。" + }, + "immersiveVoice": { + "type": "object", + "additionalProperties": false, + "required": [ + "modelSource", + "reasoning" + ], + "properties": { + "modelSource": { + "type": "string", + "enum": [ + "conversation", + "fast", + "specified" + ], + "description": "当前对话、低延迟默认映射或人类指定的精确模型;低延迟不代表低费用。" + }, + "providerId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "specified 时使用的本地 Provider 接入面 ID。" + }, + "modelName": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "specified 时使用的精确模型 ID。" + }, + "reasoning": { + "type": "string", + "enum": [ + "follow", + "low-latency", + "auto", + "off", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "description": "follow 沿用对话;low-latency 只选择模型显式支持的最低档位;其他值为独立思考强度,不受显示思考开关影响。" + } + } + }, + "voiceSelectionRef": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "agentId": { + "type": "string", + "description": "当前 Agent ID。" + }, + "conversationHistory": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QueryStartRequest/definitions/conversationMessage" + }, + "description": "多轮连续性使用的结构化历史。" + }, + "conversationId": { + "type": "string", + "description": "对话稳定 ID。" + }, + "resumeFromDisk": { + "type": "boolean", + "description": "是否优先从磁盘恢复权威结构化历史。" + }, + "preferredRunId": { + "type": "string", + "description": "用户显式选择的恢复分支 run ID。" + }, + "excludeMessageIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "恢复时剔除的本轮重发消息 ID。" + }, + "channel": { + "type": "string", + "description": "消息来源渠道。" + }, + "skillIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "本轮显式选择的 Skill ID。" + }, + "skillArgs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "本轮 Skill 命令参数。" + }, + "fileReferences": { + "type": "array", + "items": { + "type": "string" + }, + "description": "用户引用的文件路径。" + }, + "activeDocPath": { + "type": "string", + "description": "本次请求的可选文件参考路径。自动采集仅来自本窗口、属于当前会话的可见文档;显式传入或重试沿用已冻结的引用,不按当下界面重选。读取仍须授权,引用本身不表示编辑或附件交付请求。" + }, + "file": { + "type": "object", + "required": [ + "name", + "size", + "type" + ], + "properties": { + "name": { + "type": "string", + "description": "附件名称。" + }, + "size": { + "type": "string", + "description": "附件展示大小。" + }, + "type": { + "type": "string", + "description": "附件类型。" + } + }, + "additionalProperties": false + }, + "teamId": { + "type": "string", + "description": "当前团队 ID。" + } + }, + "additionalProperties": false + }, + "agentReferences": { + "type": "array", + "items": { + "type": "object", + "required": [ + "occurrence_id", + "target", + "range" + ], + "properties": { + "occurrence_id": { + "type": "string", + "minLength": 8, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", + "description": "客户端生成、在一次来源 revision 内稳定的 occurrence ID;同一 Agent 可出现多次。" + }, + "target": { + "type": "object", + "required": [ + "agent_id", + "display_name_snapshot" + ], + "properties": { + "agent_id": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", + "description": "目标 Agent 的稳定语义 ID;仅允许 ASCII 字母、数字、下划线和连字符。" + }, + "display_name_snapshot": { + "type": "string", + "minLength": 1, + "description": "服务端按 agent_id 从可信 Agent Registry 重建的显示快照;客户端同名字段只用于提交时 range 校验,不参与身份或 system prompt。" + } + }, + "additionalProperties": false, + "description": "稳定 Agent 引用;是否执行由来源契约决定,显示名不能单独触发投递。" + }, + "range": { + "type": "object", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "integer", + "minimum": 0, + "description": "正文 UTF-16 code unit 的起始偏移,包含。" + }, + "end": { + "type": "integer", + "minimum": 1, + "description": "正文 UTF-16 code unit 的结束偏移,不包含。" + } + }, + "additionalProperties": false, + "description": "结构化 token 在来源正文中的半开区间;服务端校验 start < end 且与正文匹配。" + } + }, + "additionalProperties": false, + "description": "picker 生成的结构化 Agent token;聊天中为非执行引用,看板中可由来源 owner 形成 Mention。" + }, + "description": "非执行 Agent 引用。" + }, + "mentionOccurrences": { + "type": "array", + "items": { + "type": "object", + "required": [ + "occurrence_id", + "target", + "range" + ], + "properties": { + "occurrence_id": { + "type": "string", + "minLength": 8, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", + "description": "客户端生成、在一次来源 revision 内稳定的 occurrence ID;同一 Agent 可出现多次。" + }, + "target": { + "type": "object", + "required": [ + "agent_id", + "display_name_snapshot" + ], + "properties": { + "agent_id": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", + "description": "目标 Agent 的稳定语义 ID;仅允许 ASCII 字母、数字、下划线和连字符。" + }, + "display_name_snapshot": { + "type": "string", + "minLength": 1, + "description": "服务端按 agent_id 从可信 Agent Registry 重建的显示快照;客户端同名字段只用于提交时 range 校验,不参与身份或 system prompt。" + } + }, + "additionalProperties": false, + "description": "稳定 Agent 引用;是否执行由来源契约决定,显示名不能单独触发投递。" + }, + "range": { + "type": "object", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "integer", + "minimum": 0, + "description": "正文 UTF-16 code unit 的起始偏移,包含。" + }, + "end": { + "type": "integer", + "minimum": 1, + "description": "正文 UTF-16 code unit 的结束偏移,不包含。" + } + }, + "additionalProperties": false, + "description": "结构化 token 在来源正文中的半开区间;服务端校验 start < end 且与正文匹配。" + } + }, + "additionalProperties": false, + "description": "picker 生成的结构化 Agent token;聊天中为非执行引用,看板中可由来源 owner 形成 Mention。" + }, + "description": "Retired execution input; the HTTP route rejects this field. Use non-executable agentReferences.", + "deprecated": true + }, + "mentionRecipientRequests": { + "type": "array", + "items": { + "type": "object", + "required": [ + "occurrence_ids", + "recipient_agent_id", + "route" + ], + "properties": { + "occurrence_ids": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 8, + "maxLength": 256 + }, + "description": "同一来源 revision 中指向该 recipient 的全部 occurrence IDs;只产生一个 delivery。" + }, + "recipient_agent_id": { + "type": "string", + "minLength": 1, + "description": "必须与 occurrence.target.agent_id 一致的稳定 Agent ID。" + }, + "route": { + "type": "string", + "enum": [ + "current_conversation", + "external_agent" + ], + "description": "当前 Agent 走普通 conversation;非当前 Agent 走独立 Mention delivery。" + }, + "context_selection": { + "allOf": [ + { + "type": "object", + "required": [ + "context_mode", + "origin" + ], + "properties": { + "context_mode": { + "type": "string", + "enum": [ + "continue", + "isolated", + "fork" + ], + "description": "必填且无默认值:continue=继续明确存在的同一任务上下文;isolated=不同新任务;fork=同一任务有意分叉。" + }, + "origin": { + "type": "string", + "enum": [ + "delegate", + "mention", + "task", + "workflow", + "schedule", + "webhook", + "decision", + "legacy" + ] + }, + "work_context_id": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "continue 必填:要继续的稳定 Work Context ID;禁止回退到目标 Agent 最近一次会话。" + }, + "source_work_context_id": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "fork 必填:有意分叉的来源 Work Context ID。" + }, + "task_ref": { + "type": "object", + "required": [ + "board_id", + "task_id" + ], + "properties": { + "board_id": { + "type": "string", + "minLength": 1 + }, + "task_id": { + "type": "string", + "minLength": 1, + "description": "TaskBoard 内稳定 Task ID。" + }, + "task_version": { + "type": "integer", + "minimum": 1, + "description": "选择时观察到的 Task 版本快照;身份查重只使用 board_id + task_id。" + } + }, + "additionalProperties": false + }, + "intent_id": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "context_reason": { + "type": "string", + "minLength": 1, + "maxLength": 2000, + "description": "isolated/fork 必填:说明为何这是不同新任务或为何允许对同一 Task 有意分叉。" + } + }, + "allOf": [ + { + "if": { + "properties": { + "context_mode": { + "const": "continue" + } + }, + "required": [ + "context_mode" + ] + }, + "then": { + "required": [ + "work_context_id" + ], + "not": { + "anyOf": [ + { + "required": [ + "source_work_context_id" + ] + }, + { + "required": [ + "intent_id" + ] + } + ] + } + } + }, + { + "if": { + "properties": { + "context_mode": { + "const": "isolated" + } + }, + "required": [ + "context_mode" + ] + }, + "then": { + "required": [ + "context_reason" + ], + "anyOf": [ + { + "required": [ + "task_ref" + ] + }, + { + "required": [ + "intent_id" + ] + } + ], + "not": { + "anyOf": [ + { + "required": [ + "work_context_id" + ] + }, + { + "required": [ + "source_work_context_id" + ] + } + ] + } + } + }, + { + "if": { + "properties": { + "context_mode": { + "const": "fork" + } + }, + "required": [ + "context_mode" ] }, - "then": { - "required": [ - "work_context_id" - ], - "not": { - "anyOf": [ + "then": { + "required": [ + "source_work_context_id", + "task_ref", + "context_reason" + ], + "not": { + "anyOf": [ + { + "required": [ + "work_context_id" + ] + }, + { + "required": [ + "intent_id" + ] + } + ] + } + } + } + ], + "additionalProperties": false, + "description": "一次执行如何选择 Work Context;调用方必须显式提交,系统只能推荐不能代填。" + }, + { + "type": "object", + "required": [ + "origin" + ], + "properties": { + "origin": { + "type": "string", + "const": "mention", + "description": "Mention 外部投递只能创建 origin=mention 的 Work Context lineage。" + } + } + } + ], + "description": "非当前 recipient 单独提交的显式 Work Context selection;没有默认模式。" + }, + "retry_of_delivery_id": { + "type": "string", + "minLength": 1, + "description": "用户显式重新呼叫时引用旧 delivery;普通传输重试不得填写。" + } + }, + "allOf": [ + { + "if": { + "properties": { + "route": { + "const": "current_conversation" + } + }, + "required": [ + "route" + ] + }, + "then": { + "not": { + "anyOf": [ + { + "required": [ + "context_selection" + ] + }, + { + "required": [ + "retry_of_delivery_id" + ] + } + ] + } + } + }, + { + "if": { + "properties": { + "route": { + "const": "external_agent" + } + }, + "required": [ + "route" + ] + }, + "then": { + "required": [ + "context_selection" + ] + } + } + ], + "additionalProperties": false, + "description": "每个唯一 recipient 的确定性路由;多个显示 occurrence 共用一套显式 selection。" + }, + "description": "Retired execution input; the HTTP route rejects this field.", + "deprecated": true + }, + "mediaRefs": { + "type": "array", + "items": { + "type": "object", + "required": [ + "type", + "mediaId", + "name", + "mimeType", + "size" + ], + "properties": { + "type": { + "type": "string", + "description": "媒体业务类型。" + }, + "mediaId": { + "type": "string", + "description": "媒体存储引用。" + }, + "name": { + "type": "string", + "description": "原始文件名。" + }, + "mimeType": { + "type": "string", + "description": "媒体 MIME 类型。" + }, + "size": { + "type": "number", + "description": "媒体字节数。" + } + }, + "additionalProperties": true + }, + "description": "本轮媒体附件引用。" + }, + "images": { + "type": "array", + "items": { + "type": "object", + "required": [ + "data", + "mimeType" + ], + "properties": { + "data": { + "type": "string", + "description": "已废弃 Base64 图片数据。" + }, + "mimeType": { + "type": "string", + "description": "图片 MIME 类型。" + } + }, + "additionalProperties": false + }, + "description": "已废弃内联图片附件。" + }, + "clientRequestId": { + "type": "string", + "description": "客户端一次提交及其传输重试的稳定 ID。" + }, + "userMessageId": { + "type": "string" + }, + "requestDigest": { + "type": "string", + "maxLength": 128, + "description": "Optional digest produced by the shared request-body algorithm. The service recomputes it to reject reuse of a request ID with a changed business body." + }, + "originSocketId": { + "type": "string", + "description": "Current connected Socket.IO socket.id for precise event delivery; HTTP and Socket authentication must identify the same principal." + }, + "uiContextAdmission": { + "type": "object", + "required": [ + "version", + "admissionRef" + ], + "properties": { + "version": { + "type": "integer", + "const": 1, + "description": "Version of the durable UI context admission-reference envelope." + }, + "admissionRef": { + "type": "string", + "minLength": 56, + "maxLength": 56, + "pattern": "^ui_admission_[A-Za-z0-9_-]{43}$", + "description": "Opaque durable reference to an immutable admitted turn-context sidecar; it contains no snapshot, token, host identity, path, URL, or user text." + } + }, + "additionalProperties": false, + "description": "A valid context admission reference from an authorized UI flow. Ordinary standalone scripts omit it." + }, + "appWorkAdmissionRef": { + "type": "string", + "pattern": "^apa_[A-Za-z0-9_-]{43}$", + "maxLength": 512, + "description": "Opaque reference supplied by an authorized application proposal flow. Standalone integration examples omit it; an arbitrary reference does not grant authority." + } + }, + "definitions": { + "conversationContentBlock": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "text", + "tool_use", + "tool_result", + "image", + "thinking" + ], + "description": "结构化历史内容块类型。" + }, + "text": { + "type": "string", + "description": "文本块正文。" + }, + "id": { + "type": "string", + "description": "tool_use 块 ID。" + }, + "name": { + "type": "string", + "description": "工具名称。" + }, + "input": { + "description": "工具输入 JSON。" + }, + "tool_use_id": { + "type": "string", + "description": "tool_result 对应的 tool_use ID。" + }, + "content": { + "description": "工具结果文本或嵌套结构化内容块。", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/QueryStartRequest/definitions/conversationContentBlock" + } + } + ] + }, + "is_error": { + "type": "boolean", + "description": "工具结果是否表示错误。" + }, + "source": { + "type": "object", + "required": [ + "type", + "media_type", + "data" + ], + "properties": { + "type": { + "const": "base64", + "description": "图片数据编码类型。" + }, + "media_type": { + "type": "string", + "description": "图片 MIME 类型。" + }, + "data": { + "type": "string", + "description": "Base64 图片数据。" + } + }, + "additionalProperties": false + }, + "thinking": { + "type": "string", + "description": "Provider thinking 正文。" + } + }, + "additionalProperties": false, + "description": "Query conversationHistory 中的结构化内容块。" + }, + "conversationMessage": { + "type": "object", + "required": [ + "role", + "content" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "user", + "assistant", + "system" + ], + "description": "消息角色;system 仅用于受控上下文边界 marker。" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QueryStartRequest/definitions/conversationContentBlock" + }, + "description": "结构化消息内容。" + }, + "timestamp": { + "type": "number", + "description": "消息时间戳(毫秒)。" + }, + "id": { + "type": "string", + "description": "SessionMessage 空间中的稳定消息 ID。" + }, + "compactCutoff": { + "type": "boolean", + "description": "压缩摘要边界 marker。" + }, + "summarizedCount": { + "type": "number", + "description": "本次摘要覆盖的消息数。" + }, + "compactedAt": { + "type": "string", + "format": "date-time", + "description": "压缩发生时间。" + }, + "compactAnchorMessageId": { + "type": "string", + "description": "压缩后保留区第一条消息 ID。" + }, + "compactKeptTurns": { + "type": "number", + "description": "压缩后保留的对话轮数。" + }, + "compactCoverStartMessageId": { + "type": "string", + "description": "本 marker 覆盖区第一条消息 ID。" + }, + "compactRunId": { + "type": "string", + "description": "老 marker 的覆盖 run。" + }, + "compactMerged": { + "type": "boolean", + "description": "是否为摘要链二级合并产物。" + }, + "compactSupersedes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "被本 marker 取代的 marker ID 列表。" + }, + "compactTrigger": { + "type": "string", + "enum": [ + "auto", + "manual" + ], + "description": "压缩由自动阈值还是用户显式触发。" + }, + "compactWindow": { + "type": "number", + "description": "生成自动压缩 marker 时的有效上下文窗口。" + }, + "contextReset": { + "type": "boolean", + "description": "不携带摘要的显式遗忘边界。" + }, + "resetAnchorMessageId": { + "type": "string", + "description": "触发 reset 的 user 消息 ID。" + }, + "resetReason": { + "type": "string", + "description": "reset 的用户可见原因。" + }, + "fileReferences": { + "type": "array", + "items": { + "type": "string" + }, + "description": "用户引用的文件路径。" + }, + "mediaRefs": { + "type": "array", + "items": { + "type": "object", + "required": [ + "mediaId" + ], + "properties": { + "mediaId": { + "type": "string", + "description": "媒体存储中的稳定引用。" + }, + "mimeType": { + "type": "string", + "description": "媒体 MIME 类型。" + }, + "type": { + "type": "string", + "description": "媒体业务类型。" + } + }, + "additionalProperties": false + }, + "description": "历史消息引用的媒体。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "not": { + "anyOf": [ + { + "required": [ + "mentionOccurrences" + ] + }, + { + "required": [ + "mentionRecipientRequests" + ] + } + ] + } + }, + "QueryAdmission": { + "type": "object", + "required": [ + "ok" + ], + "properties": { + "ok": { + "type": "boolean", + "description": "请求是否已被准入。" + }, + "clientRequestId": { + "type": "string", + "description": "对应的客户端请求 ID。" + }, + "duplicate": { + "type": "boolean", + "description": "是否命中幂等重复请求。" + }, + "busy": { + "type": "boolean", + "description": "准入被竞争挡下而非失败,仅两种来源:会话清除窗口(query:start、HTTP start、follow-up;清除结束后可用同一 clientRequestId 重试)与同一 session 已有 continue 在执行(仅 query:continue)。start 准入只有清除窗口会置位,客户端据此区分清除与其它 retryable 拒绝;缺省或 false 不代表清除。" + }, + "error": { + "type": "string", + "description": "拒绝时的稳定错误说明。" + }, + "code": { + "type": "string", + "description": "稳定拒绝码,如 CLIENT_REQUEST_BODY_MISMATCH(同 ID 不同请求体,不可重试)。" + }, + "retryable": { + "type": "boolean", + "description": "是否允许用同一 clientRequestId 重试。" + }, + "uiContextFaultCode": { + "type": "string", + "enum": [ + "snapshot_missing", + "snapshot_stale", + "surface_mismatch", + "auth_required", + "human_required", + "redaction_blocked", + "adapter_invalid", + "snapshot_conflict", + "protocol_unsupported", + "host_unavailable", + "action_unavailable", + "action_failed", + "action_outcome_unknown", + "action_capacity_exceeded" + ], + "description": "Stable fail-closed UI context protocol fault code." + }, + "sessionId": { + "type": "string", + "description": "已建立或已存在的 session ID。" + } + }, + "additionalProperties": false, + "description": "Socket.IO query:start 与 HTTP start 的准入响应。" + }, + "QueryFollowUpRequest": { + "type": "object", + "required": [ + "sessionId", + "originSocketId", + "message" + ], + "properties": { + "sessionId": { + "type": "string" + }, + "originSocketId": { + "type": "string", + "minLength": 1, + "description": "Current connected socket.id, authenticated as the same principal as the HTTP request." + }, + "message": { + "type": "object", + "required": [ + "role", + "content" + ], + "properties": { + "id": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "user", + "assistant" + ] + }, + "content": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + }, + "additionalProperties": true + }, + "clientRequestId": { + "type": "string" + }, + "requestDigest": { + "type": "string" + }, + "mediaRefs": { + "type": "array", + "items": { + "type": "object", + "required": [ + "type", + "mediaId", + "name", + "mimeType", + "size" + ], + "properties": { + "type": { + "type": "string", + "description": "媒体业务类型。" + }, + "mediaId": { + "type": "string", + "description": "媒体存储引用。" + }, + "name": { + "type": "string", + "description": "原始文件名。" + }, + "mimeType": { + "type": "string", + "description": "媒体 MIME 类型。" + }, + "size": { + "type": "number", + "description": "媒体字节数。" + } + }, + "additionalProperties": true + }, + "description": "本轮媒体附件引用。" + }, + "images": { + "type": "array", + "items": { + "type": "object", + "required": [ + "data", + "mimeType" + ], + "properties": { + "data": { + "type": "string", + "description": "已废弃 Base64 图片数据。" + }, + "mimeType": { + "type": "string", + "description": "图片 MIME 类型。" + } + }, + "additionalProperties": false + }, + "description": "已废弃内联图片附件。" + }, + "fileReferences": { + "type": "array", + "items": { + "type": "string" + } + }, + "activeDocPath": { + "type": "string" + }, + "reasoning": { + "type": "string" + }, + "agentReferences": { + "type": "array", + "items": { + "type": "object", + "required": [ + "occurrence_id", + "target", + "range" + ], + "properties": { + "occurrence_id": { + "type": "string", + "minLength": 8, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", + "description": "客户端生成、在一次来源 revision 内稳定的 occurrence ID;同一 Agent 可出现多次。" + }, + "target": { + "type": "object", + "required": [ + "agent_id", + "display_name_snapshot" + ], + "properties": { + "agent_id": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", + "description": "目标 Agent 的稳定语义 ID;仅允许 ASCII 字母、数字、下划线和连字符。" + }, + "display_name_snapshot": { + "type": "string", + "minLength": 1, + "description": "服务端按 agent_id 从可信 Agent Registry 重建的显示快照;客户端同名字段只用于提交时 range 校验,不参与身份或 system prompt。" + } + }, + "additionalProperties": false, + "description": "稳定 Agent 引用;是否执行由来源契约决定,显示名不能单独触发投递。" + }, + "range": { + "type": "object", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "integer", + "minimum": 0, + "description": "正文 UTF-16 code unit 的起始偏移,包含。" + }, + "end": { + "type": "integer", + "minimum": 1, + "description": "正文 UTF-16 code unit 的结束偏移,不包含。" + } + }, + "additionalProperties": false, + "description": "结构化 token 在来源正文中的半开区间;服务端校验 start < end 且与正文匹配。" + } + }, + "additionalProperties": false, + "description": "picker 生成的结构化 Agent token;聊天中为非执行引用,看板中可由来源 owner 形成 Mention。" + }, + "description": "非执行 Agent 引用。" + }, + "uiContextAdmission": { + "type": "object", + "required": [ + "version", + "admissionRef" + ], + "properties": { + "version": { + "type": "integer", + "const": 1, + "description": "Version of the durable UI context admission-reference envelope." + }, + "admissionRef": { + "type": "string", + "minLength": 56, + "maxLength": 56, + "pattern": "^ui_admission_[A-Za-z0-9_-]{43}$", + "description": "Opaque durable reference to an immutable admitted turn-context sidecar; it contains no snapshot, token, host identity, path, URL, or user text." + } + }, + "additionalProperties": false, + "description": "Only UI context object allowed on an actual query or pending record; omitting the entire field preserves the legacy no-context path." + } + }, + "additionalProperties": true, + "description": "Documented request subset from the route interface. Additional fields do not imply authorization or guaranteed support.", + "not": { + "anyOf": [ + { + "required": [ + "mentionOccurrences" + ] + }, + { + "required": [ + "mentionRecipientRequests" + ] + } + ] + } + }, + "ClientRequestLookupRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "agentId", + "requests" + ], + "description": "按 clientRequestId 只读查询服务端接纳状态;不占位、不清理、不产生任何副作用。", + "properties": { + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "\\S" + }, + "conversationId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "\\S" + }, + "requests": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "description": "要查询的提交列表,每项一个 clientRequestId。", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "clientRequestId" + ], + "properties": { + "clientRequestId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "\\S", + "description": "客户端一次提交及其全部重发共用的稳定 ID。" + }, + "userMessageId": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "\\S" + } + } + } + } + } + }, + "ClientRequestLookupResponse": { + "type": "object", + "additionalProperties": false, + "required": [ + "protocolVersion", + "serverInstanceId", + "observedAt", + "autoReplayRetentionMs", + "tombstoneRetentionMs", + "results" + ], + "description": "只读接纳查询结果。", + "properties": { + "protocolVersion": { + "type": "integer", + "const": 1, + "description": "查询协议版本。" + }, + "serverInstanceId": { + "type": "string", + "description": "当前 Agent Service 进程代际 ID;与恢复快照同源,重启即变化。" + }, + "observedAt": { + "type": "string", + "format": "date-time", + "description": "服务端观察时刻(ISO 8601),客户端用于抵御本地墙钟回拨。" + }, + "autoReplayRetentionMs": { + "type": "integer", + "minimum": 0, + "description": "客户端同 ID 自动重放窗口(毫秒)。" + }, + "tombstoneRetentionMs": { + "type": "integer", + "minimum": 0, + "description": "服务端幂等 tombstone 保护窗口(毫秒);超出后查询结论不可信。" + }, + "results": { + "type": "array", + "description": "与请求一一对应(按 clientRequestId)。", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "clientRequestId", + "status" + ], + "properties": { + "clientRequestId": { + "type": "string", + "description": "被查询的 clientRequestId。" + }, + "status": { + "type": "string", + "enum": [ + "committed", + "reserved", + "absent", + "unknown" + ], + "description": "committed=已持久提交(至多一次,原 ID 不会再执行);reserved=当前服务进程内占位中(排队或正在做提交前复检);absent=截至此刻既无提交也无占位(原请求可能仍在路上,必须同 ID 重发由服务端裁决);unknown=无法判定(旧版无作用域记录、存储不可读或注册表 fail closed)。" + }, + "committedAt": { + "type": "string", + "format": "date-time", + "description": "status=committed 时的持久提交时刻。" + }, + "sessionId": { + "type": "string" + }, + "committedServerInstance": { + "type": "string", + "enum": [ + "current", + "previous", + "unknown" + ] + }, + "executionWitness": { + "type": "string", + "enum": [ + "user-message", + "none", + "unknown" + ], + "description": "user-message=磁盘已有该提交的用户消息(执行过);none=没有;unknown=无法查证(缺 userMessageId 或存储不可读)。" + } + } + } + } + } + }, + "WebhookManagement": { + "type": "object", + "required": [ + "enabled", + "hasSecret", + "events", + "availableTools" + ], + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "hasSecret": { + "type": "boolean", + "description": "是否已有非空 secret;读取不返回其值。" + }, + "events": { + "type": "array", + "items": { + "type": "object", + "required": [ + "event", + "promptTemplate", + "maxTurns", + "toolIds" + ], + "additionalProperties": false, + "properties": { + "event": { + "type": "string" + }, + "promptTemplate": { + "type": "string" + }, + "maxTurns": { + "type": "number" + }, + "toolIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "availableTools": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "name", + "riskLevel", + "requiresConfirmation" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "riskLevel": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + }, + "requiresConfirmation": { + "type": "boolean" + } + } + } + } + } + }, + "WebhookEventWrite": { + "type": "object", + "required": [ + "promptTemplate", + "toolIds" + ], + "properties": { + "promptTemplate": { + "type": "string", + "minLength": 1, + "maxLength": 32768, + "pattern": "\\S", + "description": "事件提示模板,{{key}} 从 payload 的顶层字段取值。" + }, + "toolIds": { + "type": "array", + "maxItems": 512, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "description": "从管理读取返回的 availableTools 中选择工具 ID;空数组表示不授权任何工具。" + }, + "maxTurns": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "事件最多执行的模型轮次数,省略时为 5。", + "default": 5 + } + }, + "additionalProperties": false + }, + "WebhookEventWritten": { + "type": "object", + "required": [ + "success", + "event", + "enabled" + ], + "additionalProperties": false, + "properties": { + "success": { + "const": true + }, + "event": { + "type": "string" + }, + "enabled": { + "const": true + }, + "secret": { + "type": "string", + "description": "仅在本次首次生成 secret 时返回;保存修改已有事件不会再次返回。" + } + } + }, + "WebhookEnabledRequest": { + "type": "object", + "required": [ + "enabled" + ], + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean", + "description": "开启或暂停该智能体已有 Webhook 事件。" + } + } + }, + "WebhookEnabledResponse": { + "type": "object", + "required": [ + "success", + "enabled" + ], + "additionalProperties": false, + "properties": { + "success": { + "const": true + }, + "enabled": { + "type": "boolean" + } + } + }, + "WebhookSecretRotation": { + "type": "object", + "required": [ + "secret" + ], + "additionalProperties": false, + "properties": { + "secret": { + "type": "string", + "minLength": 43, + "maxLength": 43 + } + } + }, + "ManagementError": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + }, + "details": { + "type": "array", + "items": {} + } + }, + "additionalProperties": false + }, + "ScheduleMessage": { + "type": "object", + "additionalProperties": false, + "description": "完整的可见调度消息契约;授权快照、正文、附件引用和收件人均接受运行时验证。", + "required": [ + "id", + "scheduleId", + "authorizationScope", + "text", + "isError", + "timestamp" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "可信结果路由给出的稳定、非授权标识。" + }, + "recipientUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "可信结果路由给出的稳定、非授权标识。" + }, + "origin": { + "enum": [ + "schedule", + "webhook" + ], + "description": "旧记录未声明时按 schedule 显示。" + }, + "scheduleId": { + "type": "string", + "description": "所属调度标识;不是执行授权。" + }, + "occurrenceId": { + "type": "string", + "description": "结果所属稳定发生标识,不能单凭它重新执行。" + }, + "authorizationScope": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "kind" + ], + "properties": { + "kind": { + "const": "agent", + "description": "Agent 作用域;结果还需精确收件人校验。" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "boardId", + "bindingId" + ], + "properties": { + "kind": { + "const": "taskboard", + "description": "保持原 TaskBoard 来源 ACL,不能退化为普通 Agent 作用域。" + }, + "boardId": { + "type": "string", + "description": "来源 TaskBoard 标识。" + }, + "bindingId": { + "type": "string", + "description": "冻结的资源绑定标识。" + } + } + } + ], + "description": "不可变来源授权快照;读取和清除都重新核验当前权限。" + }, + "displayName": { + "type": "string", + "description": "可选任务显示名。" + }, + "text": { + "type": "string", + "description": "受存储边界截断的可见摘要,不是执行指令。" + }, + "isError": { + "type": "boolean", + "description": "是否为失败结果;不会改变脚本真实终态。" + }, + "timestamp": { + "type": "number", + "description": "冻结的原交付时间,重放不得使用当前时间替换。" + }, + "delivery": { + "type": "object", + "additionalProperties": false, + "required": [ + "message", + "status", + "sentAt" + ], + "description": "既有 SendUserMessage 用户交付形状,仅保存附件引用。", + "properties": { + "message": { + "type": "string", + "description": "受存储边界截断的用户交付正文。" + }, + "status": { + "enum": [ + "normal", + "proactive" + ], + "description": "普通回复或主动告知。" + }, + "sentAt": { + "type": "string", + "description": "工具侧原交付时刻。" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "附件解析降级告警。" + }, + "attachments": { + "type": "array", + "description": "引用型附件,不存二进制正文。", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "name", + "mimeType", + "size", + "isImage" + ], + "properties": { + "path": { + "type": "string", + "description": "已解析的附件路径,读取仍受资源准入限制。" + }, + "name": { + "type": "string", + "description": "附件文件名。" + }, + "mimeType": { + "type": "string", + "description": "附件 MIME 类型。" + }, + "size": { + "type": "number", + "description": "附件字节数。" + }, + "isImage": { + "type": "boolean", + "description": "是否为图片。" + }, + "mediaId": { + "type": "string", + "description": "可选媒体存储引用。" + }, + "width": { + "type": "number", + "description": "可选图片宽度。" + }, + "height": { + "type": "number", + "description": "可选图片高度。" + } + } + } + } + } + } + } + }, + "ScheduleMessagePage": { + "type": "object", + "required": [ + "items", + "total", + "limit", + "offset" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScheduleMessage" + } + }, + "total": { + "type": "integer", + "minimum": 0 + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200 + }, + "offset": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "AttachmentReceipt": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "required": [ + "attachmentId", + "name", + "mimeType", + "size", + "sha256", + "fileReference" + ], + "properties": { + "attachmentId": { + "type": "string", + "format": "uuid", + "description": "服务端随机签发的附件ID。" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "description": "不含路径分隔符/控制符的显示文件名。" + }, + "mimeType": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "上传时的内容类型,只作描述,下载禁止嗅探执行。" + }, + "size": { + "type": "integer", + "minimum": 1, + "maximum": 20971520, + "description": "完整已持久化的字节数。" + }, + "sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "文件内容摘要;未完成上传不返回成功。" + }, + "fileReference": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "Server-side file reference. Pass unchanged to options.fileReferences of a Query start request on the same instance; it is not a public URL or a local client path." + } + } + }, + "AttachmentMediaError": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "description": "Route error. Framework, origin, readiness, and proxy errors may carry additional fields; there is no universal response wrapper." + }, + "MediaMetadata": { + "type": "object", + "required": [ + "type", + "mediaId", + "name", + "mimeType", + "size" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "image", + "audio", + "video" + ] + }, + "mediaId": { + "type": "string", + "pattern": "^[a-f0-9-]+\\.\\w+$", + "description": "Service-issued media filename. Values containing two consecutive dots are rejected." + }, + "name": { + "type": "string" + }, + "mimeType": { + "type": "string", + "enum": [ + "image/jpeg", + "image/png", + "image/gif", + "image/webp", + "audio/mpeg", + "audio/mp3", + "audio/opus", + "audio/aac", + "audio/flac", + "audio/wav", + "audio/ogg", + "video/mp4", + "video/webm" + ] + }, + "size": { + "type": "integer", + "minimum": 1, + "maximum": 2147483648 + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + } + }, + "description": "Direct upload response, manually represented from MediaMeta and saveAuthorizedMediaRaw. Dimensions are optional; no additional response wrapper." + }, + "MediaAccessUrlsRequest": { + "type": "object", + "required": [ + "mediaIds" + ], + "additionalProperties": false, + "properties": { + "mediaIds": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "items": { + "type": "string" + }, + "description": "Duplicates removed in first-occurrence order; IDs are validated by the route." + } + } + }, + "MediaAccessUrlsResponse": { + "type": "object", + "required": [ + "items", + "missing" + ], + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "mediaId", + "url", + "expiresAt" + ], + "properties": { + "mediaId": { + "type": "string", + "pattern": "^[a-f0-9-]+\\.\\w+$", + "description": "Service-issued media filename. Values containing two consecutive dots are rejected." + }, + "url": { + "type": "string", + "description": "Service-relative URL with expires and signature. Use only the actual returned link." + }, + "expiresAt": { + "type": "string", + "format": "date-time" + } + } + } + }, + "missing": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "description": "Missing files do not prevent other readable IDs from receiving links. Any ownership failure for an existing object rejects the request rather than returning partial items." + }, + "HistoryError": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "code": { + "type": "string" + } + }, + "required": [ + "error" + ], + "additionalProperties": true + }, + "HistorySessionSummary": { + "type": "object", + "properties": { + "runId": { + "type": "string" + }, + "agentId": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "idle", + "completed", + "failed", + "handed_off" + ], + "description": "Historical display status, not live Run execution state or a request terminal." + }, + "messageCount": { + "type": "number" + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "preview": { + "type": "string" + }, + "archivedAt": { + "type": "string" + }, + "forkedFrom": { + "type": "string" + }, + "conversationId": { + "type": "string" + }, + "title": { + "type": "string" + }, + "titleSource": { + "type": "string", + "enum": [ + "custom", + "auto" + ] + }, + "origin": { + "type": "string", + "enum": [ + "user", + "delegation" + ] + }, + "background": { + "type": "boolean" + }, + "delegated": { + "type": "boolean" + } + }, + "required": [ + "runId", + "agentId", + "status", + "messageCount", + "createdAt", + "updatedAt" + ], + "additionalProperties": true, + "description": "Known SessionSummary fields from sessionSummarySchema. This HTTP projection is intentionally open." + }, + "HistoryMessage": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "user", + "assistant", + "system" + ] + }, + "content": { + "description": "Display content; commonly a string or content-block array. Do not reconstruct Provider protocol from it." + }, + "timestamp": { + "type": "number", + "description": "Unix milliseconds when present." + }, + "lineIndex": { + "type": "integer", + "minimum": 0, + "description": "Source row position, not stable identity." + }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Byte position, not a pagination query offset." + }, + "toolUse": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "summary": { + "type": "string", + "description": "Display summary. Sensitive results may be replaced by a placeholder; running/interrupted summaries may be absent. Do not parse outcome or replay instructions from this text." + }, + "summaryRef": { + "type": "object", + "properties": { + "filename": { + "type": "string" + } + }, + "additionalProperties": true, + "description": "Optional body reference for public output. Withheld results omit this reference; its absence does not mean the tool did not run." + }, + "isError": { + "type": "boolean", + "description": "When present, preserve the actual tool error fact even if its summary is withheld." + }, + "status": { + "type": "string", + "description": "Tool-call state when present. Preserve this independently from display summaries; it is not the Query terminal or a child Run/Worker status." + } + }, + "additionalProperties": true + } + }, + "images": { + "type": "array", + "items": {} + }, + "media": { + "type": "array", + "items": {} + }, + "file": {}, + "thinking": { + "type": "string" + }, + "mentionSourceOnly": { + "type": "boolean", + "description": "true marks a Conversation source without current-Agent Run ownership. Display only; do not feed into model replay." + }, + "mentionSource": { + "type": "object", + "description": "Conversation-owned Mention source reference, when present.", + "additionalProperties": true + }, + "mentionOccurrences": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Structured Mention occurrences, when present." + }, + "mentionRecipientAgentIds": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "mentionDeliveryIds": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "description": "Delivery references, not proof of successful execution." + } + }, + "additionalProperties": true, + "description": "Known fields of the public SessionMessage projection, derived from sessionMessageSchema. Runtime-private recovery fields are not part of this contract. Additional public fields remain allowed. Sensitive tool results and unverifiable protocol results may be withheld; privacy placeholders are display text, not execution outcomes. Body references may be omitted." + }, + "HistoryLiveInspectionTool": { + "type": "object", + "properties": { + "id": { + "type": "string", + "maxLength": 256 + }, + "name": { + "type": "string", + "maxLength": 256 + }, + "isError": { + "type": "boolean" + }, + "activity": { + "description": "Optional bounded public Tool activity; no Tool input or output." + } + }, + "required": [ + "id", + "name", + "isError" + ], + "additionalProperties": true + }, + "HistoryLiveInspection": { + "type": "object", + "properties": { + "turnId": { + "type": "string", + "maxLength": 256 + }, + "text": { + "type": "string", + "maxLength": 16384 + }, + "toolCalls": { + "type": "array", + "maxItems": 128, + "items": { + "$ref": "#/components/schemas/HistoryLiveInspectionTool" + } + }, + "timestamp": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "text", + "toolCalls", + "timestamp" + ], + "additionalProperties": true, + "description": "Temporary delegated-Run display checkpoint. It is not durable history, a receipt or proof of completion; replace it instead of appending it to messages." + }, + "HistorySessionSnapshot": { + "type": "object", + "properties": { + "runId": { + "type": "string" + }, + "agentId": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "engine": { + "type": "string", + "enum": [ + "desirecore-engine" + ] + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryMessage" + } + }, + "options": { + "type": "object", + "additionalProperties": true, + "description": "Display/recovery configuration. Returned configuration grants no execution authority." + }, + "status": { + "type": "string", + "enum": [ + "idle", + "completed", + "failed", + "handed_off" + ], + "description": "Historical display status, not live Run execution state or a request terminal." + }, + "createdAt": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "conversationId": { + "type": "string" + }, + "forkedFrom": { + "type": "string" + }, + "teamId": { + "type": "string" + }, + "workDir": { + "type": "string" + }, + "worktreeCwd": { + "type": "string" + }, + "pendingAssistant": { + "$ref": "#/components/schemas/HistoryMessage" + }, + "runtimeSession": { + "type": "object", + "properties": { + "sessionId": { + "type": "string" + }, + "revision": { + "type": "integer", + "minimum": 1 + }, + "scopeEpoch": { + "type": "integer", + "minimum": 0 + }, + "scopeSequence": { + "type": "integer", + "minimum": 0 + }, + "boundModel": { + "type": "object", + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "required": [ + "runId", + "agentId", + "userId", + "engine", + "messages", + "options", + "status", + "createdAt", + "updatedAt" + ], + "additionalProperties": true, + "description": "Known public fields derived from sessionSnapshotSchema and the public session projection; additional public fields are allowed. Images may be expanded to data URLs. Tool summaries and result blocks may be replaced by privacy placeholders with body references removed." + }, + "HistorySessionPage": { + "type": "object", + "properties": { + "sessions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistorySessionSummary" + } + }, + "total": { + "type": "integer", + "minimum": 0 + }, + "hasMore": { + "type": "boolean" + } + }, + "required": [ + "sessions", + "total", + "hasMore" + ], + "additionalProperties": true + }, + "HistoryMessagePage": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistoryMessage" + } + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Source-record row count. Omitted or damaged rows mean returned messages can be fewer than the requested window." + }, + "live": { + "$ref": "#/components/schemas/HistoryLiveInspection" + } + }, + "required": [ + "messages", + "total" + ], + "additionalProperties": true + }, + "HistorySearchHit": { + "type": "object", + "properties": { + "runId": { + "type": "string" + }, + "messageIndex": { + "type": "integer", + "minimum": 0, + "description": "Source row position within this Run." + }, + "offset": { + "type": "integer", + "minimum": 0, + "description": "Byte position, not the next search-page offset." + }, + "messageId": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "user", + "assistant", + "system" + ] + }, + "text": { + "type": "string" + }, + "timestamp": { + "type": "number" + }, + "hasImages": { + "type": "boolean", + "description": "Based on legacy images or media fields; not proof that every attachment is an image." + }, + "hasFiles": { + "type": "boolean" + }, + "hasToolUse": { + "type": "boolean" + }, + "toolNames": { + "type": "array", + "items": { + "type": "string" + } + }, + "conversationId": { + "type": "string" + } + }, + "required": [ + "runId", + "messageIndex", + "offset", + "messageId", + "role", + "text", + "timestamp", + "hasImages", + "hasFiles", + "hasToolUse" + ], + "additionalProperties": true + }, + "HistorySearchPage": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HistorySearchHit" + } + }, + "total": { + "type": "integer", + "minimum": 0 + }, + "hasMore": { + "type": "boolean" + } + }, + "required": [ + "items", + "total", + "hasMore" + ], + "additionalProperties": true + }, + "ConversationMatterOverview": { + "type": "object", + "properties": { + "agentId": { + "type": "string" + }, + "activeMatters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "title", + "type" + ], + "additionalProperties": true + } + }, + "recentMatters": { + "type": "array", + "maxItems": 10, + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "completedAt": { + "type": "string" + }, + "summary": { + "type": "string" + } + }, + "required": [ + "id", + "title", + "type" + ], + "additionalProperties": true + } + }, + "totalMatters": { + "type": "integer", + "minimum": 0 + }, + "totalArchived": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "agentId", + "activeMatters", + "recentMatters", + "totalMatters", + "totalArchived" + ], + "additionalProperties": true + }, + "ConversationMatterMessages": { + "type": "object", + "properties": { + "matterId": { + "type": "string" + }, + "messages": { + "type": "array", + "maxItems": 120, + "items": {}, + "description": "Public source entries; not guaranteed to have the per-Run SessionMessage shape. Sensitive or unverifiable tool-result bodies may be replaced and external result references removed." + }, + "truncated": { + "type": "boolean", + "const": true, + "description": "Present and true when the source read exceeded 120 messages. Public projection may return fewer entries." + } + }, + "required": [ + "matterId", + "messages" + ], + "additionalProperties": true + }, + "NetworkHealthState": { + "type": "object", + "required": [ + "status", + "reason", + "since", + "seq" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "online", + "degraded", + "offline", + "unknown" + ], + "description": "网络健康状态:online=上游请求或探测成功;degraded=出现网络类失败但未确认公网断开(或仅个别 Provider 不可达);offline=通用连通性探测失败且近期失败的上游也探测不通,或多 host DNS 失败,确认网络不可达;unknown=启动后尚无样本(或休眠唤醒后待重新确认),UI 不应据此显示异常" + }, + "scope": { + "type": "string", + "enum": [ + "global", + "provider" + ], + "description": "异常范围(仅 degraded/offline 时有意义):global=公网整体不可达;provider=公网通但个别模型服务商不可达(通用探测成功、Provider origin 探测失败)" + }, + "affectedProviders": { + "type": "array", + "items": { + "type": "string" + }, + "description": "scope=provider 时受影响的 Provider ID 列表(供前端展示\"服务商暂不可达\")" + }, + "reason": { + "type": "string", + "enum": [ + "init", + "request_ok", + "probe_ok", + "request_failed", + "probe_failed", + "stream_stall", + "multi_host_dns", + "suspend_resume" + ], + "description": "进入当前状态的原因:init=启动初始;request_ok/probe_ok=真实请求或探测成功;request_failed=上游请求网络类失败;probe_failed=通用连通性探测失败;stream_stall=流式空闲超时;multi_host_dns=窗口期内多个不同 host DNS 失败(最强离线证据);suspend_resume=系统唤醒后重置待确认" + }, + "subcategory": { + "type": "string", + "enum": [ + "dns", + "tls", + "tcp_reset", + "refused", + "unreachable", + "timeout", + "stream_interrupted" + ], + "description": "触发本次状态变化的网络错误子类(若有),供前端给出针对性提示(如 dns → 检查 DNS/代理设置)" + }, + "since": { + "type": "string", + "description": "进入当前状态的 ISO 8601 时间;消费方据此判断状态新鲜度(无活跃请求时状态允许 stale)" + }, + "seq": { + "type": "number", + "minimum": 0, + "description": "单调递增序号;前端用于拒绝乱序/迟到的旧状态事件(快照与实时事件以 seq 大者为准)" + } + }, + "additionalProperties": false + }, + "OperationsRecentCallsResponse": { + "type": "object", + "required": [ + "calls" + ], + "properties": { + "calls": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "timestamp", + "agentId", + "runId", + "toolName", + "status", + "durationMs" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "不透明的稳定调用 ID;优先 invocation_id,旧记录由 Run/Receipt 身份和工具序号确定。不得解析为 Run ID,也不是恢复或授权凭据。" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "优先工具 started_at;旧记录缺失时回退 Receipt started_at,再回退 Run started_at。不是保证精确的工具开始时间。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "description": "执行 Agent ID。" + }, + "runId": { + "type": "string", + "minLength": 1, + "description": "关联 Run ID;详情跳转不得从展示 ID 猜测。" + }, + "toolName": { + "type": "string", + "minLength": 1, + "description": "工具名称。" + }, + "status": { + "type": "string", + "enum": [ + "success", + "warning", + "error" + ], + "description": "优先工具 success/error;旧记录缺失时按 Receipt outcome 的 failed→error、partial→warning、其他→success 投影,不代表 Query 或 Run 终态。" + }, + "durationMs": { + "type": "number", + "minimum": 0, + "description": "已记录工具耗时(毫秒);旧记录缺失时为 0,不表示真实零耗时。" + } + }, + "additionalProperties": false + }, + "description": "当前可信用户最近的工具调用,按时间倒序。" + } + }, + "additionalProperties": false, + "description": "运行中心活动分区的用户隔离工具调用列表。" + }, + "OperationsUsageSummaryResponse": { + "type": "object", + "required": [ + "total", + "byModel", + "byDate", + "byAgent" + ], + "properties": { + "total": { + "type": "object", + "required": [ + "inputTokens", + "outputTokens", + "cacheReadTokens", + "cacheWriteTokens", + "costUsd", + "queryCount" + ], + "properties": { + "inputTokens": { + "type": "number", + "minimum": 0, + "description": "非缓存输入 Token 总数。" + }, + "outputTokens": { + "type": "number", + "minimum": 0, + "description": "输出 Token 总数。" + }, + "cacheReadTokens": { + "type": "number", + "minimum": 0, + "description": "缓存读取 Token 总数。" + }, + "cacheWriteTokens": { + "type": "number", + "minimum": 0, + "description": "缓存写入 Token 总数。" + }, + "costUsd": { + "type": "number", + "minimum": 0, + "description": "已记录的美元成本总额。" + }, + "queryCount": { + "type": "number", + "minimum": 0, + "description": "带用量数据的查询次数。" + } + }, + "additionalProperties": false, + "description": "当前可信用户的用量汇总。" + }, + "byModel": { + "type": "array", + "items": { + "type": "object", + "required": [ + "inputTokens", + "outputTokens", + "cacheReadTokens", + "cacheWriteTokens", + "costUsd", + "queryCount", + "provider", + "model" + ], + "properties": { + "provider": { + "type": "string", + "description": "模型 Provider 标识。" + }, + "model": { + "type": "string", + "description": "模型 ID。" + }, + "inputTokens": { + "type": "number", + "minimum": 0, + "description": "非缓存输入 Token 总数。" + }, + "outputTokens": { + "type": "number", + "minimum": 0, + "description": "输出 Token 总数。" + }, + "cacheReadTokens": { + "type": "number", + "minimum": 0, + "description": "缓存读取 Token 总数。" + }, + "cacheWriteTokens": { + "type": "number", + "minimum": 0, + "description": "缓存写入 Token 总数。" + }, + "costUsd": { + "type": "number", + "minimum": 0, + "description": "已记录的美元成本总额。" + }, + "queryCount": { + "type": "number", + "minimum": 0, + "description": "带用量数据的查询次数。" + } + }, + "additionalProperties": false + }, + "description": "按 Provider/模型聚合的用量。" + }, + "byDate": { + "type": "array", + "items": { + "type": "object", + "required": [ + "inputTokens", + "outputTokens", + "cacheReadTokens", + "cacheWriteTokens", + "costUsd", + "queryCount", + "date" + ], + "properties": { + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "description": "聚合日期(YYYY-MM-DD);UTC 日桶可按其中点映射到请求时区,不是逐事件的精确本地日结。" + }, + "inputTokens": { + "type": "number", + "minimum": 0, + "description": "非缓存输入 Token 总数。" + }, + "outputTokens": { + "type": "number", + "minimum": 0, + "description": "输出 Token 总数。" + }, + "cacheReadTokens": { + "type": "number", + "minimum": 0, + "description": "缓存读取 Token 总数。" + }, + "cacheWriteTokens": { + "type": "number", + "minimum": 0, + "description": "缓存写入 Token 总数。" + }, + "costUsd": { + "type": "number", + "minimum": 0, + "description": "已记录的美元成本总额。" + }, + "queryCount": { + "type": "number", + "minimum": 0, + "description": "带用量数据的查询次数。" + } + }, + "additionalProperties": false + }, + "description": "按 UTC 日桶聚合的用量;有效 timezone 将桶的 UTC 12:00 映射到本地日期,无法恢复桶内每条记录的本地日界。" + }, + "byAgent": { + "type": "array", + "items": { + "type": "object", + "required": [ + "inputTokens", + "outputTokens", + "cacheReadTokens", + "cacheWriteTokens", + "costUsd", + "queryCount", + "agentId" + ], + "properties": { + "agentId": { + "type": "string", + "minLength": 1, + "description": "关联 Agent ID。" + }, + "inputTokens": { + "type": "number", + "minimum": 0, + "description": "非缓存输入 Token 总数。" + }, + "outputTokens": { + "type": "number", + "minimum": 0, + "description": "输出 Token 总数。" + }, + "cacheReadTokens": { + "type": "number", + "minimum": 0, + "description": "缓存读取 Token 总数。" + }, + "cacheWriteTokens": { + "type": "number", + "minimum": 0, + "description": "缓存写入 Token 总数。" + }, + "costUsd": { + "type": "number", + "minimum": 0, + "description": "已记录的美元成本总额。" + }, + "queryCount": { + "type": "number", + "minimum": 0, + "description": "带用量数据的查询次数。" + } + }, + "additionalProperties": false + }, + "description": "按 Agent 聚合的用量。" + } + }, + "additionalProperties": false, + "description": "运行中心活动分区的用户隔离用量摘要。" + }, + "AgentInputConfig": { + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "agentId", + "manual", + "configRevision" + ], + "properties": { + "version": { + "type": "integer", + "const": 1, + "description": "Projection protocol version; unknown versions do not unlock ordinary input." + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "Actual Agent read; match it against the selected target." + }, + "manual": { + "type": "boolean", + "description": "Current separate-conversation manual mode. Valid omission in Agent configuration defaults to false." + }, + "configRevision": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Opaque digest of configuration content. Equality only; not a permission, lock, timestamp or sequence." + } + } + }, + "AgentInputConfigError": { + "type": "object", + "required": [ + "code", + "error" + ], + "properties": { + "code": { + "type": "string", + "enum": [ + "AGENT_INPUT_CONFIG_INVALID_ID", + "AGENT_NOT_FOUND", + "AGENT_INPUT_CONFIG_CHANGED", + "AGENT_INPUT_CONFIG_INVALID", + "AGENT_INPUT_CONFIG_UNREADABLE" + ] + }, + "error": { + "type": "string" + } + }, + "additionalProperties": true + }, + "MergedHistoryItem": { + "type": "object", + "additionalProperties": false, + "required": [ + "runId", + "messageIndex", + "sessionTime", + "message" + ], + "properties": { + "runId": { + "type": "string", + "description": "Owned source Run ID, or __mention_source:SOURCE_ID for a Conversation source-only entry. Synthetic IDs cannot be used with Run, follow-up or recovery APIs." + }, + "messageIndex": { + "type": "number", + "description": "Source row navigation position; source-only entries use 0. Not the merged page offset." + }, + "offset": { + "type": "number", + "minimum": 0, + "description": "Optional source byte position for persisted Run messages. Not a page offset." + }, + "sessionTime": { + "type": "string", + "description": "Source session creation time or source creation time, in ISO 8601 form." + }, + "message": { + "$ref": "#/components/schemas/HistoryMessage" + } + } + }, + "MergedHistoryPage": { + "type": "object", + "additionalProperties": false, + "required": [ + "items", + "total", + "hasMore", + "historyRevision" + ], + "properties": { + "items": { + "type": "array", + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/MergedHistoryItem" + }, + "description": "Public display projection; sensitive result bodies and references may be withheld." + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Total entries in this complete merged public view, not source rows across Runs." + }, + "hasMore": { + "type": "boolean", + "description": "Whether more entries remain for a positive limit. Always false for limit=0." + }, + "historyRevision": { + "type": "string", + "pattern": "^h1:[0-9a-f]{64}$", + "description": "Current complete public view digest including read scope and order. Later pages send expectedRevision; changes yield 409. Not a persistent cursor or transaction snapshot." + } + } + }, + "TaskBoardListResponse": { + "description": "全局看板使用的授权 TaskBoard 聚合列表。", + "type": "object", + "required": [ + "schemaVersion", + "generatedAt", + "boards" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "全局任务板列表响应版本。" + }, + "generatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "boards": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskBoardSummary" + }, + "description": "当前请求主体有权查看的 TaskBoard;global 视图只聚合此列表。" + } + }, + "additionalProperties": false + }, + "TaskBoardDetailResponse": { + "description": "TaskBoard 的权威详情和服务端可用动作。", + "type": "object", + "required": [ + "schemaVersion", + "exists" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "TaskBoard 详情响应版本。" + }, + "exists": { + "type": "boolean", + "description": "false 表示授权作用域内不存在此 TaskBoard。" + }, + "meta": { + "$ref": "#/components/schemas/TaskBoardMeta" + }, + "tasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskBoardPublicTaskItem" + }, + "description": "当前请求主体有权读取的 Task 列表。" + }, + "comments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskBoardPublicComment" + }, + "description": "当前 TaskBoard 的基础评论列表。" + }, + "resourceBindings": { + "type": "array", + "items": { + "type": "object", + "required": [ + "schemaVersion", + "bindingId", + "taskIdentity", + "taskVersionAtMutation", + "role", + "resourceRef", + "version", + "lifecycle", + "createdByCommandId", + "createdAt", + "updatedByCommandId", + "updatedAt" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "TaskResourceBinding 契约版本;当前固定为 1。" + }, + "bindingId": { + "type": "string", + "minLength": 47, + "maxLength": 47, + "pattern": "^trb_[A-Za-z0-9_-]{43}$", + "description": "绑定的 ID,由 TaskBoard 生成,不能自行构造或从显示内容推出。" + }, + "taskIdentity": { + "type": "object", + "required": [ + "boardId", + "taskId" + ], + "properties": { + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 所在 TaskBoard 的 ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 的 ID。" + } + }, + "additionalProperties": false, + "description": "用 {boardId, taskId} 指明 Task,不带版本,Task 版本变化后仍然有效;写入时另传带版本的 taskTarget。" + }, + "taskVersionAtMutation": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "最后一次关系提交所观察到的 exact Task version;它是审计锚点,不属于稳定父身份,也不会把后续 Task 版本推进变成失联。" + }, + "role": { + "type": "string", + "enum": [ + "navigation", + "attention", + "input", + "output", + "evidence", + "decision", + "execution", + "workspace", + "view" + ], + "description": "资源在这个 Task 上的用途。navigation、attention 是导航链接,resourceRef 可用 current pin(跟随最新版本);其余角色必须用 exact pin。" + }, + "resourceRef": { + "description": "外部 owner 的窄引用;不包含正文、权限或运行态,并且每次读取/导航/执行仍须由 owner 重鉴权。", + "oneOf": [ + { + "description": "只允许导航到 owner 当前版本的封闭 GovernedResourceRef 联合。", + "oneOf": [ + { + "type": "object", + "description": "指向 Task 最新版本的引用,只用于导航。", + "required": [ + "schemaVersion", + "resourceType", + "boardId", + "taskId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "task", + "description": "资源类型:TaskBoard 上的 Task。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 所在 TaskBoard 的 ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 的 ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到 Work Context 当前版本的引用。", + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "targetAgentId", + "workContextId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "work-context", + "description": "资源由 Context/Session owner 持有。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "拥有此 Work Context 数据根的用户 ID。" + }, + "targetAgentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "执行此 Work Context 的稳定 Agent ID。" + }, + "workContextId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Context/Session owner 中的稳定 Work Context ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到 Workflow 定义当前版本的 portable 引用。", + "required": [ + "schemaVersion", + "resourceType", + "workflowId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "workflow-definition", + "description": "资源由 Workflow Service 持有。" + }, + "workflowId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Workflow Service 中的稳定 Workflow 定义 ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到 DecisionWorkspace 当前 revision 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "workspaceId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-workspace", + "description": "资源由 DecisionWorkspace owner 持有。" + }, + "scope": { + "description": "解析 workspace 时必须重新鉴权的 user/team scope。", + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "user=个人,team=Agent 团队。项目级可信 principal 尚未接入前不公开 project 假能力。" + }, + "id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$", + "description": "稳定 ID;仅允许字母、数字、点、下划线和短横线,且不得用作展示名称。" + } + }, + "additionalProperties": false + }, + "workspaceId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionWorkspace owner 中的稳定 workspace ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到已登记文件资源当前内容的引用。", + "required": [ + "schemaVersion", + "resourceType", + "fileRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "file", + "description": "资源由 WorkDir/FileWorkbench owner 持有。" + }, + "fileRef": { + "type": "object", + "required": [ + "schemaVersion", + "instanceId", + "agentId", + "rootId", + "path" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "文件资源引用格式版本;当前固定为 1。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发并解析该资源引用的 Agent Service 稳定实例身份。" + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "解析该资源时使用的 Agent 权限上下文;不是显示名称。" + }, + "rootId": { + "type": "string", + "minLength": 16, + "description": "目标实例为当前已登记工作目录签发的不透明根目录 ID。" + }, + "path": { + "type": "string", + "description": "相对 rootId 的规范 POSIX 路径;根目录为空字符串,不允许绝对路径、反斜线、空段、`.`、`..` 或 NUL。" + } + }, + "additionalProperties": false, + "description": "FileWorkbench 已签发的实例、Agent、root 与 canonical relative path;resolver 仍须重新检查实例、根、realpath 与 containment。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "为 DecisionTree owner 预留的 current 导航引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "treeId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-tree", + "description": "资源由 DecisionTree owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "treeId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionTree owner 中的稳定 tree ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "为自由 Board owner 预留的 current 导航引用。", + "required": [ + "schemaVersion", + "resourceType", + "boardId", + "fileRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "board", + "description": "资源由 Board/FileWorkbench owner 持有。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Board sidecar 中的稳定 board ID。" + }, + "fileRef": { + "type": "object", + "required": [ + "schemaVersion", + "instanceId", + "agentId", + "rootId", + "path" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "文件资源引用格式版本;当前固定为 1。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发并解析该资源引用的 Agent Service 稳定实例身份。" + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "解析该资源时使用的 Agent 权限上下文;不是显示名称。" + }, + "rootId": { + "type": "string", + "minLength": 16, + "description": "目标实例为当前已登记工作目录签发的不透明根目录 ID。" + }, + "path": { + "type": "string", + "description": "相对 rootId 的规范 POSIX 路径;根目录为空字符串,不允许绝对路径、反斜线、空段、`.`、`..` 或 NUL。" + } + }, + "additionalProperties": false, + "description": "承载 `.drawnix` 字节的 owner-issued FileResourceRef;裸绝对路径不进入 Board identity。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到持久 ViewDocument 当前 revision 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "viewId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "view-document", + "description": "资源由 Projection ViewDocument owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "viewId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Projection ViewDocument owner 中的稳定 view ID;source resource ref 不进入 view identity。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + { + "description": "可用于精确读取、绑定或受治理命令的封闭 GovernedResourceRef 联合。", + "oneOf": [ + { + "type": "object", + "description": "固定到 Task 某个版本的引用。", + "required": [ + "schemaVersion", + "resourceType", + "boardId", + "taskId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "task", + "description": "资源类型:TaskBoard 上的 Task。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 所在 TaskBoard 的 ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 的 ID。" + }, + "pin": { + "type": "object", + "description": "固定到 Task 的一个确定版本。", + "required": [ + "mode", + "taskVersion" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "固定到 taskVersion 指定的版本。" + }, + "taskVersion": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Task 的版本号,即读取 Task 时返回的 version。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到 Work Context 精确 version 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "targetAgentId", + "workContextId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "work-context", + "description": "资源由 Context/Session owner 持有。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "拥有此 Work Context 数据根的用户 ID。" + }, + "targetAgentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "执行此 Work Context 的稳定 Agent ID。" + }, + "workContextId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Context/Session owner 中的稳定 Work Context ID。" + }, + "pin": { + "type": "object", + "description": "固定 Work Context owner 中的精确乐观并发版本。", + "required": [ + "mode", + "workContextVersion" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确 Work Context 版本。" + }, + "workContextVersion": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Work Context owner 的正安全整数 version。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到 Workflow 定义精确 revision/hash 的 portable 引用。", + "required": [ + "schemaVersion", + "resourceType", + "workflowId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "workflow-definition", + "description": "资源由 Workflow Service 持有。" + }, + "workflowId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Workflow Service 中的稳定 Workflow 定义 ID。" + }, + "pin": { + "type": "object", + "description": "固定可迁移 Workflow 定义的 revision 与内容摘要;canonical 文件路径属于 owner 解析结果,不进入 portable ref。", + "required": [ + "mode", + "definitionRevision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确 Workflow 定义版本。" + }, + "definitionRevision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Workflow owner 的正安全整数定义 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到 DecisionWorkspace 精确 revision/hash 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "workspaceId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-workspace", + "description": "资源由 DecisionWorkspace owner 持有。" + }, + "scope": { + "description": "解析 workspace 时必须重新鉴权的 user/team scope。", + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "user=个人,team=Agent 团队。项目级可信 principal 尚未接入前不公开 project 假能力。" + }, + "id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$", + "description": "稳定 ID;仅允许字母、数字、点、下划线和短横线,且不得用作展示名称。" + } + }, + "additionalProperties": false + }, + "workspaceId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionWorkspace owner 中的稳定 workspace ID。" + }, + "pin": { + "type": "object", + "description": "固定版本化领域文档的不可变 revision 与 canonical 内容摘要。", + "required": [ + "mode", + "revision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确不可变 revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "领域 owner 的正安全整数单调 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到已登记文件资源精确内容 ETag 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "fileRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "file", + "description": "资源由 WorkDir/FileWorkbench owner 持有。" + }, + "fileRef": { + "type": "object", + "required": [ + "schemaVersion", + "instanceId", + "agentId", + "rootId", + "path" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "文件资源引用格式版本;当前固定为 1。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发并解析该资源引用的 Agent Service 稳定实例身份。" + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "解析该资源时使用的 Agent 权限上下文;不是显示名称。" + }, + "rootId": { + "type": "string", + "minLength": 16, + "description": "目标实例为当前已登记工作目录签发的不透明根目录 ID。" + }, + "path": { + "type": "string", + "description": "相对 rootId 的规范 POSIX 路径;根目录为空字符串,不允许绝对路径、反斜线、空段、`.`、`..` 或 NUL。" + } + }, + "additionalProperties": false, + "description": "FileWorkbench 已签发的实例、Agent、root 与 canonical relative path;resolver 仍须重新检查实例、根、realpath 与 containment。" + }, + "pin": { + "type": "object", + "description": "固定 FileWorkbench 已读取文件字节的强内容 ETag。", + "required": [ + "mode", + "etag" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确文件内容版本。" + }, + "etag": { + "type": "string", + "pattern": "^\"sha256-[A-Za-z0-9_-]{43}\"$", + "description": "FileWorkbench 对文件内容签发的强 SHA-256 ETag;stat-only 弱标识不能作为 exact pin。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "由 WorkDir owner 的不可变 workdirId 标识的资源;路径可变化但不进入 identity,owner 每次按当前配置重新解析。", + "required": [ + "schemaVersion", + "resourceType", + "instanceId", + "ownerScope", + "workdirId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "workdir", + "description": "资源由 WorkDir 配置 owner 持有。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发此本机 WorkDir 引用的 Agent Service 数据空间。" + }, + "ownerScope": { + "type": "object", + "description": "WorkDir owner 的稳定作用域;不包含绝对路径。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "agent", + "team" + ], + "description": "WorkDir 配置的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户、Agent 或 Team 的稳定 owner ID。" + } + }, + "additionalProperties": false + }, + "workdirId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "WorkDir owner 分配且在删除前保持稳定的 ID。" + }, + "pin": { + "type": "object", + "description": "资源 ID 本身已指向不可变事实或唯一活动绑定时使用的 exact pin。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确引用,不解析为同 owner 的其他资源。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "精确指向当前仍存活的 owner-level Git worktree binding;Exit 或 replacement 后同一 ref 必须失效。", + "required": [ + "schemaVersion", + "resourceType", + "instanceId", + "owner", + "bindingId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "bound-worktree", + "description": "资源由 Worktree binding owner 持有。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "持有该 worktree binding 的 Agent Service 数据空间。" + }, + "owner": { + "oneOf": [ + { + "type": "object", + "description": "默认会话管理模式下的 Agent 级 worktree 归属", + "required": [ + "type", + "userId", + "agentId" + ], + "properties": { + "type": { + "const": "agent", + "description": "归属类型:Agent" + }, + "userId": { + "type": "string", + "minLength": 1, + "description": "拥有该 Agent 的用户 ID" + }, + "agentId": { + "type": "string", + "minLength": 1, + "description": "worktree 绑定的 Agent ID" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "手动会话管理模式下的独立会话级 worktree 归属", + "required": [ + "type", + "userId", + "agentId", + "conversationId" + ], + "properties": { + "type": { + "const": "manual-conversation", + "description": "归属类型:手动会话" + }, + "userId": { + "type": "string", + "minLength": 1, + "description": "拥有该会话的用户 ID" + }, + "agentId": { + "type": "string", + "minLength": 1, + "description": "该手动会话所属的 Agent ID" + }, + "conversationId": { + "type": "string", + "pattern": "^__manual:.+", + "description": "跨 Run 稳定的手动会话 ID,必须使用 __manual: 前缀" + } + }, + "additionalProperties": false + } + ], + "description": "绑定的完整稳定 owner;不能用当前 Run/session 推测。" + }, + "bindingId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "绑定随机稳定 ID;用于拒绝同 owner 在旧绑定退出后创建的 replacement。" + }, + "pin": { + "type": "object", + "description": "资源 ID 本身已指向不可变事实或唯一活动绑定时使用的 exact pin。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确引用,不解析为同 owner 的其他资源。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "精确 Run identity;状态变化不改变 Run ID。", + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "agentId", + "runId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "run", + "description": "资源由 RunManager 持有。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Run meta 中的权威 user owner,resolver 必须与可信 principal 比对。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Run meta 中的权威 Agent ID。" + }, + "runId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "RunManager 中全局稳定的 Run ID。" + }, + "pin": { + "type": "object", + "description": "资源 ID 本身已指向不可变事实或唯一活动绑定时使用的 exact pin。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确引用,不解析为同 owner 的其他资源。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "精确 Receipt identity;Receipt owner 必须先复验父 Run 的 user/Agent 归属。", + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "agentId", + "runId", + "receiptId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "receipt", + "description": "资源由 Receipt owner 持有。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "父 Run meta 中的权威 user owner。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "父 Run meta 中的权威 Agent ID。" + }, + "runId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "保存该 Receipt 的父 Run ID。" + }, + "receiptId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "父 Run 的 Receipt owner 中的稳定回执 ID。" + }, + "pin": { + "type": "object", + "description": "资源 ID 本身已指向不可变事实或唯一活动绑定时使用的 exact pin。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确引用,不解析为同 owner 的其他资源。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "为 DecisionTree owner 预留的精确 revision/hash 引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "treeId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-tree", + "description": "资源由 DecisionTree owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "treeId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionTree owner 中的稳定 tree ID。" + }, + "pin": { + "type": "object", + "description": "固定版本化领域文档的不可变 revision 与 canonical 内容摘要。", + "required": [ + "mode", + "revision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确不可变 revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "领域 owner 的正安全整数单调 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "schemaVersion", + "resourceType", + "treeRef", + "runId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-tree-run", + "description": "DecisionTree Run Owner 持有的只读不可变事件前缀;与聊天Run独立。" + }, + "treeRef": { + "type": "object", + "description": "此Run启动时固定的定义修订与内容哈希;不随定义当前版本变化。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "treeId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-tree", + "description": "资源由 DecisionTree owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "treeId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionTree owner 中的稳定 tree ID。" + }, + "pin": { + "type": "object", + "description": "固定版本化领域文档的不可变 revision 与 canonical 内容摘要。", + "required": [ + "mode", + "revision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确不可变 revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "领域 owner 的正安全整数单调 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "runId": { + "type": "string", + "pattern": "^dtrun_[A-Za-z0-9_-]{43}$", + "description": "DecisionTree Owner签发的持久Run ID,不接受聊天Run ID。" + }, + "pin": { + "type": "object", + "required": [ + "mode", + "sequence", + "runChainHash" + ], + "additionalProperties": false, + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "只支持精确事件前缀,不隐式追随当前tip。" + }, + "sequence": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "本次读取最后一个已提交事件的序号;后续追加不改变此前缀。" + }, + "runChainHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "截至sequence的权威事件链哈希;Owner读取时必须核对。" + } + }, + "description": "固定已提交事件前缀,仅用于只读投影,不作为追加事件或回答的命令授权。" + } + }, + "additionalProperties": false, + "description": "决策树Run不可变前缀的独立精确资源引用;不携带答案、事件正文或执行权限。" + }, + { + "type": "object", + "description": "为自由 Board owner 预留的精确 sidecar revision/hash/file ETag 引用。", + "required": [ + "schemaVersion", + "resourceType", + "boardId", + "fileRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "board", + "description": "资源由 Board/FileWorkbench owner 持有。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Board sidecar 中的稳定 board ID。" + }, + "fileRef": { + "type": "object", + "required": [ + "schemaVersion", + "instanceId", + "agentId", + "rootId", + "path" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "文件资源引用格式版本;当前固定为 1。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发并解析该资源引用的 Agent Service 稳定实例身份。" + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "解析该资源时使用的 Agent 权限上下文;不是显示名称。" + }, + "rootId": { + "type": "string", + "minLength": 16, + "description": "目标实例为当前已登记工作目录签发的不透明根目录 ID。" + }, + "path": { + "type": "string", + "description": "相对 rootId 的规范 POSIX 路径;根目录为空字符串,不允许绝对路径、反斜线、空段、`.`、`..` 或 NUL。" + } + }, + "additionalProperties": false, + "description": "承载 `.drawnix` 字节的 owner-issued FileResourceRef;裸绝对路径不进入 Board identity。" + }, + "pin": { + "type": "object", + "description": "固定 Board sidecar revision、canonical Drawnix 内容摘要与对应文件字节。", + "required": [ + "mode", + "revision", + "contentHash", + "etag" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确 Board revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Board owner 的正安全整数单调 sidecar revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + }, + "etag": { + "type": "string", + "pattern": "^\"sha256-[A-Za-z0-9_-]{43}\"$", + "description": "与此 Board revision 一致的 `.drawnix` 文件强内容 ETag。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到持久 ViewDocument 精确 revision/hash 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "viewId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "view-document", + "description": "资源由 Projection ViewDocument owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "viewId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Projection ViewDocument owner 中的稳定 view ID;source resource ref 不进入 view identity。" + }, + "pin": { + "type": "object", + "description": "固定版本化领域文档的不可变 revision 与 canonical 内容摘要。", + "required": [ + "mode", + "revision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确不可变 revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "领域 owner 的正安全整数单调 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "resourceType", + "objectRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "const": "application-object", + "description": "应用业务owner的精确对象;引用不授予全文访问。" + }, + "objectRef": { + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "sourceId", + "appId", + "installationId", + "namespace", + "objectId", + "revision" + ], + "properties": { + "schemaVersion": { + "const": 1, + "description": "应用对象引用版本。" + }, + "sourceId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "对象所属应用的真实来源。" + }, + "appId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "逻辑产品身份,不用显示名。" + }, + "installationId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "对象所属精确实例。" + }, + "namespace": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "业务owner定义的对象命名空间。" + }, + "objectId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "稳定业务对象身份。" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "业务owner核验的版本,不是UI epoch或可见采集时间。" + } + }, + "description": "业务对象身份,不授予读取全文/修改权限;owner每次重新鉴权并CAS。" + }, + "pin": { + "type": "object", + "required": [ + "mode", + "revision" + ], + "additionalProperties": false, + "description": "精确业务对象版本;revision 必须与 objectRef.revision 相同,引用仍须原 owner 重新鉴权。", + "properties": { + "mode": { + "const": "exact", + "description": "固定业务对象版本,不随UI选择切换。" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "业务owner核验的版本,不是UI epoch或可见采集时间。" + } + } + } + }, + "description": "正式应用对象的有限owner资源适配;所有字段由业务owner重新核验。" + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "hostInstanceId", + "sourceId", + "appId", + "scope", + "proposalRef", + "clientRequestId", + "agentId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "const": "application-work", + "description": "应用提案owner已冻结的精确用户工作意图,不是新Task。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "hostInstanceId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "sourceId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "appId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "scope": { + "oneOf": [ + { + "type": "object", + "required": [ + "kind", + "installationId", + "installationRevision" + ], + "additionalProperties": false, + "properties": { + "kind": { + "const": "installation", + "description": "正式安装运行。" + }, + "installationId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "有界稳定身份;来源、实例、对象和请求不得相互替代。" + }, + "installationRevision": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "真实当前安装 CAS 修订,不能使用 none。" + } + } + }, + { + "type": "object", + "required": [ + "kind", + "previewId", + "draftId", + "previewRevision" + ], + "additionalProperties": false, + "properties": { + "kind": { + "const": "preview", + "description": "独立开发运行。" + }, + "previewId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "有界稳定身份;来源、实例、对象和请求不得相互替代。" + }, + "draftId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "有界稳定身份;来源、实例、对象和请求不得相互替代。" + }, + "previewRevision": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "预览 owner 的当前修订。" + } + } + } + ], + "description": "当前可信运行所绑定的精确版本作用域。" + }, + "proposalRef": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "clientRequestId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "pin": { + "type": "object", + "required": [ + "mode", + "bodyDigest" + ], + "additionalProperties": false, + "description": "固定原 owner 冻结提案的 bodyDigest;只接纳该提案的真实用户动作,不授予其他执行权限。", + "properties": { + "mode": { + "const": "exact", + "description": "最终动作绑定精确提案摘要。" + }, + "bodyDigest": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + } + } + }, + "description": "真实宿主human-interaction的窄目标,只支持该原提案request操作。" + } + ] + } + ] + }, + "version": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "关系记录自身的单调 CAS version,与 Task version 和资源 revision 相互独立。" + }, + "lifecycle": { + "type": "string", + "enum": [ + "active", + "removed" + ], + "description": "active=关系有效;removed=不可变删除墓碑,保留完整因果与最后引用。" + }, + "createdByCommandId": { + "type": "string", + "minLength": 48, + "maxLength": 48, + "pattern": "^gcmd_[A-Za-z0-9_-]{43}$", + "description": "首次建立关系的 S18 GovernedCommand ID;不复制 actor/channel/approval。" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "由 TaskBoard 关系 owner 记录的带时区 ISO 8601 时间。" + }, + "updatedByCommandId": { + "type": "string", + "minLength": 48, + "maxLength": 48, + "pattern": "^gcmd_[A-Za-z0-9_-]{43}$", + "description": "最后一次更新或移除关系的 S18 GovernedCommand ID;不复制 actor/channel/approval。" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "由 TaskBoard 关系 owner 记录的带时区 ISO 8601 时间。" + }, + "removedAt": { + "type": "string", + "format": "date-time", + "description": "removed 墓碑的发生时间,必须与该版本 updatedAt 相同。" + }, + "removalReasonCode": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "pattern": "^[a-z][a-z0-9]*(?:[._:-][a-z0-9]+)*$", + "description": "有界、非正文的移除原因代码;不可承载用户内容或资源摘要。" + } + }, + "additionalProperties": false, + "description": "TaskBoard 拥有的关系事实。它只记录 Task↔resource 的角色、版本和 S18 命令因果,不复制资源正文、权限、运行态或 actor。" + }, + "description": "TaskBoard-owned narrow resource relationship facts; external owner bodies and authority are not copied." + }, + "availableActions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "taskRef", + "actions" + ], + "properties": { + "taskRef": { + "$ref": "#/components/schemas/TaskRef" + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskAvailableAction" + }, + "description": "该 Task 的服务端可用动作。" + } + }, + "additionalProperties": false, + "description": "单个 Task 的动作投影。" + }, + "description": "服务端按当前 actor 计算的动作;前端不得自行猜权限。" + }, + "fanOutDispatches": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskFanOutDispatch" + }, + "description": "当前 TaskBoard 中已授权 fan-out intents 的持久投递状态;不包含秘密 claim token。" + }, + "allDone": { + "type": "boolean", + "description": "是否不存在任何开放中的 Task。" + } + }, + "allOf": [ + { + "if": { + "properties": { + "exists": { + "const": true + } + }, + "required": [ + "exists" + ] + }, + "then": { + "required": [ + "meta", + "tasks", + "comments", + "resourceBindings", + "availableActions", + "allDone" + ] + } + } + ], + "additionalProperties": false + }, + "TaskDirectReadResponse": { + "description": "稳定 Task 深链使用的精确授权读取,不要求客户端先加载整板。", + "type": "object", + "required": [ + "schemaVersion", + "exists" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "TaskBoard contract 版本。" + }, + "exists": { + "type": "boolean", + "description": "false 对调用方不区分不存在与不可见的细节。" + }, + "board": { + "$ref": "#/components/schemas/TaskBoardMeta" + }, + "task": { + "$ref": "#/components/schemas/TaskBoardPublicTaskItem" + }, + "comments": { + "type": "array", + "description": "此 Task 的 canonical 评论最新投影。", + "items": { + "$ref": "#/components/schemas/TaskBoardPublicComment" + } + }, + "resourceBindings": { + "type": "array", + "description": "此 Task 的 owner-issued resource binding 事实;引用不是 capability。", + "items": { + "type": "object", + "required": [ + "schemaVersion", + "bindingId", + "taskIdentity", + "taskVersionAtMutation", + "role", + "resourceRef", + "version", + "lifecycle", + "createdByCommandId", + "createdAt", + "updatedByCommandId", + "updatedAt" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "TaskResourceBinding 契约版本;当前固定为 1。" + }, + "bindingId": { + "type": "string", + "minLength": 47, + "maxLength": 47, + "pattern": "^trb_[A-Za-z0-9_-]{43}$", + "description": "绑定的 ID,由 TaskBoard 生成,不能自行构造或从显示内容推出。" + }, + "taskIdentity": { + "type": "object", + "required": [ + "boardId", + "taskId" + ], + "properties": { + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 所在 TaskBoard 的 ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 的 ID。" + } + }, + "additionalProperties": false, + "description": "用 {boardId, taskId} 指明 Task,不带版本,Task 版本变化后仍然有效;写入时另传带版本的 taskTarget。" + }, + "taskVersionAtMutation": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "最后一次关系提交所观察到的 exact Task version;它是审计锚点,不属于稳定父身份,也不会把后续 Task 版本推进变成失联。" + }, + "role": { + "type": "string", + "enum": [ + "navigation", + "attention", + "input", + "output", + "evidence", + "decision", + "execution", + "workspace", + "view" + ], + "description": "资源在这个 Task 上的用途。navigation、attention 是导航链接,resourceRef 可用 current pin(跟随最新版本);其余角色必须用 exact pin。" + }, + "resourceRef": { + "description": "外部 owner 的窄引用;不包含正文、权限或运行态,并且每次读取/导航/执行仍须由 owner 重鉴权。", + "oneOf": [ + { + "description": "只允许导航到 owner 当前版本的封闭 GovernedResourceRef 联合。", + "oneOf": [ + { + "type": "object", + "description": "指向 Task 最新版本的引用,只用于导航。", + "required": [ + "schemaVersion", + "resourceType", + "boardId", + "taskId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "task", + "description": "资源类型:TaskBoard 上的 Task。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 所在 TaskBoard 的 ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 的 ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到 Work Context 当前版本的引用。", + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "targetAgentId", + "workContextId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "work-context", + "description": "资源由 Context/Session owner 持有。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "拥有此 Work Context 数据根的用户 ID。" + }, + "targetAgentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "执行此 Work Context 的稳定 Agent ID。" + }, + "workContextId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Context/Session owner 中的稳定 Work Context ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到 Workflow 定义当前版本的 portable 引用。", + "required": [ + "schemaVersion", + "resourceType", + "workflowId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "workflow-definition", + "description": "资源由 Workflow Service 持有。" + }, + "workflowId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Workflow Service 中的稳定 Workflow 定义 ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到 DecisionWorkspace 当前 revision 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "workspaceId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-workspace", + "description": "资源由 DecisionWorkspace owner 持有。" + }, + "scope": { + "description": "解析 workspace 时必须重新鉴权的 user/team scope。", + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "user=个人,team=Agent 团队。项目级可信 principal 尚未接入前不公开 project 假能力。" + }, + "id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$", + "description": "稳定 ID;仅允许字母、数字、点、下划线和短横线,且不得用作展示名称。" + } + }, + "additionalProperties": false + }, + "workspaceId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionWorkspace owner 中的稳定 workspace ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到已登记文件资源当前内容的引用。", + "required": [ + "schemaVersion", + "resourceType", + "fileRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "file", + "description": "资源由 WorkDir/FileWorkbench owner 持有。" + }, + "fileRef": { + "type": "object", + "required": [ + "schemaVersion", + "instanceId", + "agentId", + "rootId", + "path" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "文件资源引用格式版本;当前固定为 1。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发并解析该资源引用的 Agent Service 稳定实例身份。" + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "解析该资源时使用的 Agent 权限上下文;不是显示名称。" + }, + "rootId": { + "type": "string", + "minLength": 16, + "description": "目标实例为当前已登记工作目录签发的不透明根目录 ID。" + }, + "path": { + "type": "string", + "description": "相对 rootId 的规范 POSIX 路径;根目录为空字符串,不允许绝对路径、反斜线、空段、`.`、`..` 或 NUL。" + } + }, + "additionalProperties": false, + "description": "FileWorkbench 已签发的实例、Agent、root 与 canonical relative path;resolver 仍须重新检查实例、根、realpath 与 containment。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "为 DecisionTree owner 预留的 current 导航引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "treeId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-tree", + "description": "资源由 DecisionTree owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "treeId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionTree owner 中的稳定 tree ID。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "为自由 Board owner 预留的 current 导航引用。", + "required": [ + "schemaVersion", + "resourceType", + "boardId", + "fileRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "board", + "description": "资源由 Board/FileWorkbench owner 持有。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Board sidecar 中的稳定 board ID。" + }, + "fileRef": { + "type": "object", + "required": [ + "schemaVersion", + "instanceId", + "agentId", + "rootId", + "path" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "文件资源引用格式版本;当前固定为 1。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发并解析该资源引用的 Agent Service 稳定实例身份。" + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "解析该资源时使用的 Agent 权限上下文;不是显示名称。" + }, + "rootId": { + "type": "string", + "minLength": 16, + "description": "目标实例为当前已登记工作目录签发的不透明根目录 ID。" + }, + "path": { + "type": "string", + "description": "相对 rootId 的规范 POSIX 路径;根目录为空字符串,不允许绝对路径、反斜线、空段、`.`、`..` 或 NUL。" + } + }, + "additionalProperties": false, + "description": "承载 `.drawnix` 字节的 owner-issued FileResourceRef;裸绝对路径不进入 Board identity。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "导航到持久 ViewDocument 当前 revision 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "viewId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "view-document", + "description": "资源由 Projection ViewDocument owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "viewId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Projection ViewDocument owner 中的稳定 view ID;source resource ref 不进入 view identity。" + }, + "pin": { + "type": "object", + "description": "解析 owner 当前版本的导航 pin;只能用于导航,不能作为绑定、修改、执行或 decision-grade 输入。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "current", + "description": "显式选择 owner 当前版本。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + { + "description": "可用于精确读取、绑定或受治理命令的封闭 GovernedResourceRef 联合。", + "oneOf": [ + { + "type": "object", + "description": "固定到 Task 某个版本的引用。", + "required": [ + "schemaVersion", + "resourceType", + "boardId", + "taskId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "task", + "description": "资源类型:TaskBoard 上的 Task。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 所在 TaskBoard 的 ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Task 的 ID。" + }, + "pin": { + "type": "object", + "description": "固定到 Task 的一个确定版本。", + "required": [ + "mode", + "taskVersion" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "固定到 taskVersion 指定的版本。" + }, + "taskVersion": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Task 的版本号,即读取 Task 时返回的 version。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到 Work Context 精确 version 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "targetAgentId", + "workContextId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "work-context", + "description": "资源由 Context/Session owner 持有。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "拥有此 Work Context 数据根的用户 ID。" + }, + "targetAgentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "执行此 Work Context 的稳定 Agent ID。" + }, + "workContextId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Context/Session owner 中的稳定 Work Context ID。" + }, + "pin": { + "type": "object", + "description": "固定 Work Context owner 中的精确乐观并发版本。", + "required": [ + "mode", + "workContextVersion" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确 Work Context 版本。" + }, + "workContextVersion": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Work Context owner 的正安全整数 version。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到 Workflow 定义精确 revision/hash 的 portable 引用。", + "required": [ + "schemaVersion", + "resourceType", + "workflowId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "workflow-definition", + "description": "资源由 Workflow Service 持有。" + }, + "workflowId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Workflow Service 中的稳定 Workflow 定义 ID。" + }, + "pin": { + "type": "object", + "description": "固定可迁移 Workflow 定义的 revision 与内容摘要;canonical 文件路径属于 owner 解析结果,不进入 portable ref。", + "required": [ + "mode", + "definitionRevision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确 Workflow 定义版本。" + }, + "definitionRevision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Workflow owner 的正安全整数定义 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到 DecisionWorkspace 精确 revision/hash 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "workspaceId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-workspace", + "description": "资源由 DecisionWorkspace owner 持有。" + }, + "scope": { + "description": "解析 workspace 时必须重新鉴权的 user/team scope。", + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "user=个人,team=Agent 团队。项目级可信 principal 尚未接入前不公开 project 假能力。" + }, + "id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$", + "description": "稳定 ID;仅允许字母、数字、点、下划线和短横线,且不得用作展示名称。" + } + }, + "additionalProperties": false + }, + "workspaceId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionWorkspace owner 中的稳定 workspace ID。" + }, + "pin": { + "type": "object", + "description": "固定版本化领域文档的不可变 revision 与 canonical 内容摘要。", + "required": [ + "mode", + "revision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确不可变 revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "领域 owner 的正安全整数单调 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到已登记文件资源精确内容 ETag 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "fileRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "file", + "description": "资源由 WorkDir/FileWorkbench owner 持有。" + }, + "fileRef": { + "type": "object", + "required": [ + "schemaVersion", + "instanceId", + "agentId", + "rootId", + "path" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "文件资源引用格式版本;当前固定为 1。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发并解析该资源引用的 Agent Service 稳定实例身份。" + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "解析该资源时使用的 Agent 权限上下文;不是显示名称。" + }, + "rootId": { + "type": "string", + "minLength": 16, + "description": "目标实例为当前已登记工作目录签发的不透明根目录 ID。" + }, + "path": { + "type": "string", + "description": "相对 rootId 的规范 POSIX 路径;根目录为空字符串,不允许绝对路径、反斜线、空段、`.`、`..` 或 NUL。" + } + }, + "additionalProperties": false, + "description": "FileWorkbench 已签发的实例、Agent、root 与 canonical relative path;resolver 仍须重新检查实例、根、realpath 与 containment。" + }, + "pin": { + "type": "object", + "description": "固定 FileWorkbench 已读取文件字节的强内容 ETag。", + "required": [ + "mode", + "etag" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确文件内容版本。" + }, + "etag": { + "type": "string", + "pattern": "^\"sha256-[A-Za-z0-9_-]{43}\"$", + "description": "FileWorkbench 对文件内容签发的强 SHA-256 ETag;stat-only 弱标识不能作为 exact pin。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "由 WorkDir owner 的不可变 workdirId 标识的资源;路径可变化但不进入 identity,owner 每次按当前配置重新解析。", + "required": [ + "schemaVersion", + "resourceType", + "instanceId", + "ownerScope", + "workdirId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "workdir", + "description": "资源由 WorkDir 配置 owner 持有。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发此本机 WorkDir 引用的 Agent Service 数据空间。" + }, + "ownerScope": { + "type": "object", + "description": "WorkDir owner 的稳定作用域;不包含绝对路径。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "agent", + "team" + ], + "description": "WorkDir 配置的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户、Agent 或 Team 的稳定 owner ID。" + } + }, + "additionalProperties": false + }, + "workdirId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "WorkDir owner 分配且在删除前保持稳定的 ID。" + }, + "pin": { + "type": "object", + "description": "资源 ID 本身已指向不可变事实或唯一活动绑定时使用的 exact pin。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确引用,不解析为同 owner 的其他资源。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "精确指向当前仍存活的 owner-level Git worktree binding;Exit 或 replacement 后同一 ref 必须失效。", + "required": [ + "schemaVersion", + "resourceType", + "instanceId", + "owner", + "bindingId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "bound-worktree", + "description": "资源由 Worktree binding owner 持有。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "持有该 worktree binding 的 Agent Service 数据空间。" + }, + "owner": { + "oneOf": [ { + "type": "object", + "description": "默认会话管理模式下的 Agent 级 worktree 归属", "required": [ - "source_work_context_id" - ] + "type", + "userId", + "agentId" + ], + "properties": { + "type": { + "const": "agent", + "description": "归属类型:Agent" + }, + "userId": { + "type": "string", + "minLength": 1, + "description": "拥有该 Agent 的用户 ID" + }, + "agentId": { + "type": "string", + "minLength": 1, + "description": "worktree 绑定的 Agent ID" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "手动会话管理模式下的独立会话级 worktree 归属", + "required": [ + "type", + "userId", + "agentId", + "conversationId" + ], + "properties": { + "type": { + "const": "manual-conversation", + "description": "归属类型:手动会话" + }, + "userId": { + "type": "string", + "minLength": 1, + "description": "拥有该会话的用户 ID" + }, + "agentId": { + "type": "string", + "minLength": 1, + "description": "该手动会话所属的 Agent ID" + }, + "conversationId": { + "type": "string", + "pattern": "^__manual:.+", + "description": "跨 Run 稳定的手动会话 ID,必须使用 __manual: 前缀" + } + }, + "additionalProperties": false + } + ], + "description": "绑定的完整稳定 owner;不能用当前 Run/session 推测。" + }, + "bindingId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "绑定随机稳定 ID;用于拒绝同 owner 在旧绑定退出后创建的 replacement。" + }, + "pin": { + "type": "object", + "description": "资源 ID 本身已指向不可变事实或唯一活动绑定时使用的 exact pin。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确引用,不解析为同 owner 的其他资源。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "精确 Run identity;状态变化不改变 Run ID。", + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "agentId", + "runId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "run", + "description": "资源由 RunManager 持有。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Run meta 中的权威 user owner,resolver 必须与可信 principal 比对。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Run meta 中的权威 Agent ID。" + }, + "runId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "RunManager 中全局稳定的 Run ID。" + }, + "pin": { + "type": "object", + "description": "资源 ID 本身已指向不可变事实或唯一活动绑定时使用的 exact pin。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确引用,不解析为同 owner 的其他资源。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "精确 Receipt identity;Receipt owner 必须先复验父 Run 的 user/Agent 归属。", + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "agentId", + "runId", + "receiptId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "receipt", + "description": "资源由 Receipt owner 持有。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "父 Run meta 中的权威 user owner。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "父 Run meta 中的权威 Agent ID。" + }, + "runId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "保存该 Receipt 的父 Run ID。" + }, + "receiptId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "父 Run 的 Receipt owner 中的稳定回执 ID。" + }, + "pin": { + "type": "object", + "description": "资源 ID 本身已指向不可变事实或唯一活动绑定时使用的 exact pin。", + "required": [ + "mode" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确引用,不解析为同 owner 的其他资源。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "为 DecisionTree owner 预留的精确 revision/hash 引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "treeId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-tree", + "description": "资源由 DecisionTree owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "treeId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionTree owner 中的稳定 tree ID。" + }, + "pin": { + "type": "object", + "description": "固定版本化领域文档的不可变 revision 与 canonical 内容摘要。", + "required": [ + "mode", + "revision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确不可变 revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "领域 owner 的正安全整数单调 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "schemaVersion", + "resourceType", + "treeRef", + "runId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-tree-run", + "description": "DecisionTree Run Owner 持有的只读不可变事件前缀;与聊天Run独立。" + }, + "treeRef": { + "type": "object", + "description": "此Run启动时固定的定义修订与内容哈希;不随定义当前版本变化。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "treeId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "decision-tree", + "description": "资源由 DecisionTree owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "treeId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "DecisionTree owner 中的稳定 tree ID。" + }, + "pin": { + "type": "object", + "description": "固定版本化领域文档的不可变 revision 与 canonical 内容摘要。", + "required": [ + "mode", + "revision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确不可变 revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "领域 owner 的正安全整数单调 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "runId": { + "type": "string", + "pattern": "^dtrun_[A-Za-z0-9_-]{43}$", + "description": "DecisionTree Owner签发的持久Run ID,不接受聊天Run ID。" + }, + "pin": { + "type": "object", + "required": [ + "mode", + "sequence", + "runChainHash" + ], + "additionalProperties": false, + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "只支持精确事件前缀,不隐式追随当前tip。" + }, + "sequence": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "本次读取最后一个已提交事件的序号;后续追加不改变此前缀。" + }, + "runChainHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "截至sequence的权威事件链哈希;Owner读取时必须核对。" + } + }, + "description": "固定已提交事件前缀,仅用于只读投影,不作为追加事件或回答的命令授权。" + } + }, + "additionalProperties": false, + "description": "决策树Run不可变前缀的独立精确资源引用;不携带答案、事件正文或执行权限。" + }, + { + "type": "object", + "description": "为自由 Board owner 预留的精确 sidecar revision/hash/file ETag 引用。", + "required": [ + "schemaVersion", + "resourceType", + "boardId", + "fileRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "board", + "description": "资源由 Board/FileWorkbench owner 持有。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Board sidecar 中的稳定 board ID。" + }, + "fileRef": { + "type": "object", + "required": [ + "schemaVersion", + "instanceId", + "agentId", + "rootId", + "path" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "文件资源引用格式版本;当前固定为 1。" + }, + "instanceId": { + "type": "string", + "pattern": "^inst_[A-Za-z0-9_-]{32}$", + "description": "签发并解析该资源引用的 Agent Service 稳定实例身份。" + }, + "agentId": { + "type": "string", + "pattern": "^[A-Za-z0-9_-]+$", + "description": "解析该资源时使用的 Agent 权限上下文;不是显示名称。" + }, + "rootId": { + "type": "string", + "minLength": 16, + "description": "目标实例为当前已登记工作目录签发的不透明根目录 ID。" + }, + "path": { + "type": "string", + "description": "相对 rootId 的规范 POSIX 路径;根目录为空字符串,不允许绝对路径、反斜线、空段、`.`、`..` 或 NUL。" + } + }, + "additionalProperties": false, + "description": "承载 `.drawnix` 字节的 owner-issued FileResourceRef;裸绝对路径不进入 Board identity。" + }, + "pin": { + "type": "object", + "description": "固定 Board sidecar revision、canonical Drawnix 内容摘要与对应文件字节。", + "required": [ + "mode", + "revision", + "contentHash", + "etag" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确 Board revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Board owner 的正安全整数单调 sidecar revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + }, + "etag": { + "type": "string", + "pattern": "^\"sha256-[A-Za-z0-9_-]{43}\"$", + "description": "与此 Board revision 一致的 `.drawnix` 文件强内容 ETag。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "绑定到持久 ViewDocument 精确 revision/hash 的引用。", + "required": [ + "schemaVersion", + "resourceType", + "scope", + "viewId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "type": "string", + "const": "view-document", + "description": "资源由 Projection ViewDocument owner 持有。" + }, + "scope": { + "type": "object", + "description": "新版本化共管文档的长期 user/team scope;授权由领域 owner 实时判定。", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "team" + ], + "description": "共管文档的 owner scope 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "用户或 Team 的稳定 scope ID。" + } + }, + "additionalProperties": false + }, + "viewId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Projection ViewDocument owner 中的稳定 view ID;source resource ref 不进入 view identity。" + }, + "pin": { + "type": "object", + "description": "固定版本化领域文档的不可变 revision 与 canonical 内容摘要。", + "required": [ + "mode", + "revision", + "contentHash" + ], + "properties": { + "mode": { + "type": "string", + "const": "exact", + "description": "精确不可变 revision。" + }, + "revision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "领域 owner 的正安全整数单调 revision。" + }, + "contentHash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "resourceType", + "objectRef", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" + }, + "resourceType": { + "const": "application-object", + "description": "应用业务owner的精确对象;引用不授予全文访问。" + }, + "objectRef": { + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "sourceId", + "appId", + "installationId", + "namespace", + "objectId", + "revision" + ], + "properties": { + "schemaVersion": { + "const": 1, + "description": "应用对象引用版本。" + }, + "sourceId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "对象所属应用的真实来源。" + }, + "appId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "逻辑产品身份,不用显示名。" + }, + "installationId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "对象所属精确实例。" + }, + "namespace": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "业务owner定义的对象命名空间。" }, - { - "required": [ - "intent_id" - ] + "objectId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "稳定业务对象身份。" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "业务owner核验的版本,不是UI epoch或可见采集时间。" } - ] - } - } - }, - { - "if": { - "properties": { - "context_mode": { - "const": "isolated" - } - }, - "required": [ - "context_mode" - ] - }, - "then": { - "required": [ - "context_reason" - ], - "anyOf": [ - { - "required": [ - "task_ref" - ] }, - { - "required": [ - "intent_id" - ] - } - ], - "not": { - "anyOf": [ - { - "required": [ - "work_context_id" - ] + "description": "业务对象身份,不授予读取全文/修改权限;owner每次重新鉴权并CAS。" + }, + "pin": { + "type": "object", + "required": [ + "mode", + "revision" + ], + "additionalProperties": false, + "description": "精确业务对象版本;revision 必须与 objectRef.revision 相同,引用仍须原 owner 重新鉴权。", + "properties": { + "mode": { + "const": "exact", + "description": "固定业务对象版本,不随UI选择切换。" }, - { - "required": [ - "source_work_context_id" - ] + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "业务owner核验的版本,不是UI epoch或可见采集时间。" } - ] + } } - } + }, + "description": "正式应用对象的有限owner资源适配;所有字段由业务owner重新核验。" }, { - "if": { - "properties": { - "context_mode": { - "const": "fork" - } + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "resourceType", + "ownerUserId", + "hostInstanceId", + "sourceId", + "appId", + "scope", + "proposalRef", + "clientRequestId", + "agentId", + "pin" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "GovernedResourceRef 契约版本;当前固定为 1。" }, - "required": [ - "context_mode" - ] - }, - "then": { - "required": [ - "source_work_context_id", - "task_ref", - "context_reason" - ], - "not": { - "anyOf": [ + "resourceType": { + "const": "application-work", + "description": "应用提案owner已冻结的精确用户工作意图,不是新Task。" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "hostInstanceId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "sourceId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "appId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "scope": { + "oneOf": [ { + "type": "object", "required": [ - "work_context_id" - ] + "kind", + "installationId", + "installationRevision" + ], + "additionalProperties": false, + "properties": { + "kind": { + "const": "installation", + "description": "正式安装运行。" + }, + "installationId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "有界稳定身份;来源、实例、对象和请求不得相互替代。" + }, + "installationRevision": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "真实当前安装 CAS 修订,不能使用 none。" + } + } }, { + "type": "object", "required": [ - "intent_id" - ] + "kind", + "previewId", + "draftId", + "previewRevision" + ], + "additionalProperties": false, + "properties": { + "kind": { + "const": "preview", + "description": "独立开发运行。" + }, + "previewId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "有界稳定身份;来源、实例、对象和请求不得相互替代。" + }, + "draftId": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "有界稳定身份;来源、实例、对象和请求不得相互替代。" + }, + "previewRevision": { + "type": "string", + "minLength": 1, + "maxLength": 200, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]*$", + "description": "预览 owner 的当前修订。" + } + } } - ] + ], + "description": "当前可信运行所绑定的精确版本作用域。" + }, + "proposalRef": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "clientRequestId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^(?!.*[\\u0000-\\u001F\\u007F])\\S(?:.*\\S)?$", + "description": "Owner 域内的稳定资源 ID;不得使用显示名称代替,不接受首尾空白或 ASCII 控制字符。" + }, + "pin": { + "type": "object", + "required": [ + "mode", + "bodyDigest" + ], + "additionalProperties": false, + "description": "固定原 owner 冻结提案的 bodyDigest;只接纳该提案的真实用户动作,不授予其他执行权限。", + "properties": { + "mode": { + "const": "exact", + "description": "最终动作绑定精确提案摘要。" + }, + "bodyDigest": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "Owner 对 canonical 内容计算的 64 个小写十六进制字符(SHA-256)。" + } + } } - } - } - ], - "additionalProperties": false, - "description": "一次执行如何选择 Work Context;调用方必须显式提交,系统只能推荐不能代填。" - }, - { - "type": "object", - "required": [ - "origin" - ], - "properties": { - "origin": { - "type": "string", - "const": "mention", - "description": "Mention 外部投递只能创建 origin=mention 的 Work Context lineage。" + }, + "description": "真实宿主human-interaction的窄目标,只支持该原提案request操作。" } - } + ] } + ] + }, + "version": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "关系记录自身的单调 CAS version,与 Task version 和资源 revision 相互独立。" + }, + "lifecycle": { + "type": "string", + "enum": [ + "active", + "removed" ], - "description": "非当前 recipient 单独提交的显式 Work Context selection;没有默认模式。" + "description": "active=关系有效;removed=不可变删除墓碑,保留完整因果与最后引用。" }, - "retry_of_delivery_id": { + "createdByCommandId": { + "type": "string", + "minLength": 48, + "maxLength": 48, + "pattern": "^gcmd_[A-Za-z0-9_-]{43}$", + "description": "首次建立关系的 S18 GovernedCommand ID;不复制 actor/channel/approval。" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "由 TaskBoard 关系 owner 记录的带时区 ISO 8601 时间。" + }, + "updatedByCommandId": { + "type": "string", + "minLength": 48, + "maxLength": 48, + "pattern": "^gcmd_[A-Za-z0-9_-]{43}$", + "description": "最后一次更新或移除关系的 S18 GovernedCommand ID;不复制 actor/channel/approval。" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "由 TaskBoard 关系 owner 记录的带时区 ISO 8601 时间。" + }, + "removedAt": { + "type": "string", + "format": "date-time", + "description": "removed 墓碑的发生时间,必须与该版本 updatedAt 相同。" + }, + "removalReasonCode": { "type": "string", "minLength": 1, - "description": "用户显式重新呼叫时引用旧 delivery;普通传输重试不得填写。" + "maxLength": 128, + "pattern": "^[a-z][a-z0-9]*(?:[._:-][a-z0-9]+)*$", + "description": "有界、非正文的移除原因代码;不可承载用户内容或资源摘要。" } }, - "allOf": [ - { - "if": { - "properties": { - "route": { - "const": "current_conversation" - } - }, - "required": [ - "route" - ] + "additionalProperties": false, + "description": "TaskBoard 拥有的关系事实。它只记录 Task↔resource 的角色、版本和 S18 命令因果,不复制资源正文、权限、运行态或 actor。" + } + }, + "availableActions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskAvailableAction" + }, + "description": "当前 principal 对此 Task 的服务端动作。" + }, + "archiveAction": { + "$ref": "#/components/schemas/TaskArchiveAction" + }, + "deepLink": { + "$ref": "#/components/schemas/TaskDeepLink" + } + }, + "allOf": [ + { + "if": { + "properties": { + "exists": { + "const": true + } + }, + "required": [ + "exists" + ] + }, + "then": { + "required": [ + "board", + "task", + "comments", + "resourceBindings", + "availableActions", + "archiveAction", + "deepLink" + ] + } + } + ], + "additionalProperties": false + }, + "TaskBoardQueryRequest": { + "description": "Global/List/Board 共用的授权 Task 查询请求。", + "type": "object", + "properties": { + "filter": { + "$ref": "#/components/schemas/TaskBoardQueryFilter" + }, + "sort": { + "$ref": "#/components/schemas/TaskBoardQuerySort" + }, + "pageSize": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "description": "单页数量;省略时服务端使用 50。" + }, + "cursor": { + "type": "string", + "minLength": 1, + "maxLength": 4096, + "description": "上一次响应返回的不透明 keyset 游标;不得跨筛选/排序请求复用。" + } + }, + "additionalProperties": false + }, + "TaskBoardQueryResponse": { + "description": "授权、稳定分页的 TaskBoard 查询结果。", + "type": "object", + "required": [ + "schemaVersion", + "generatedAt", + "items", + "pageSize", + "hasMore", + "totalCount", + "statusCounts" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "TaskBoard contract 版本。" + }, + "generatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskBoardQueryItem" + }, + "description": "当前页授权结果。" + }, + "pageSize": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "description": "本次采用的页大小。" + }, + "hasMore": { + "type": "boolean", + "description": "是否还有下一页。" + }, + "totalCount": { + "type": "integer", + "minimum": 0, + "description": "当前组合过滤命中的授权 Task 总数。" + }, + "statusCounts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskStatusCount" + }, + "description": "当前组合过滤命中结果按状态的总数;不受当前分页截断影响。" + }, + "nextCursor": { + "type": "string", + "minLength": 1, + "description": "下一页不透明游标;无下一页时省略。" + }, + "indexGeneration": { + "type": "string", + "minLength": 1, + "description": "可重建索引的内容代号,仅用于诊断,不是 canonical revision。" + } + }, + "additionalProperties": false + }, + "TaskActivityResponse": { + "description": "Task workspace Activity 列表;canonical TaskEvent 与 owner projection 共享 envelope。", + "type": "object", + "required": [ + "schemaVersion", + "taskRef", + "generatedAt", + "items" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "Activity response 契约版本。" + }, + "taskRef": { + "$ref": "#/components/schemas/TaskRef" + }, + "generatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskActivityEnvelope" + }, + "description": "当前Owner摘要优先;带page时canonical事件按Board revision升序,未分页历史响应仍按occurredAt排序。" + }, + "page": { + "$ref": "#/components/schemas/TaskActivityPage" + } + }, + "additionalProperties": false + }, + "TaskBoardSessionBoardResolveRequest": { + "description": "按会话解析聊天悬浮面板的会话看板(只读)。选择规则与 Task 工具默认选板一致:当前 session 的看板优先,当前 session 没有看板时沿用同一 Run 其他代际 session 中未归档、最近更新的一块。run→session 归属只取服务端会话持久状态,不接受客户端自报的 session 列表;鉴权沿用请求主体,不扩权。", + "type": "object", + "properties": { + "runId": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$", + "description": "会话所属 Run ID(会话级,session 轮换时不变)。服务端据此列出该 Run 历代已持久化的 runtime session。" + }, + "sessionId": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$", + "description": "客户端当前前台 runtime session(可选),只决定「当前 session 的看板优先」;服务端证明它属于另一个 Run 时不作为当前代际。未给 runId 时按会话持久状态索引反查其 Run(兼容旧 session- 别名)。" + } + }, + "anyOf": [ + { + "required": [ + "runId" + ] + }, + { + "required": [ + "sessionId" + ] + } + ], + "additionalProperties": false + }, + "TaskBoardReadError": { + "description": "HTTP 与 Task tools 共用的失败 mutation result。", + "type": "object", + "required": [ + "ok", + "error" + ], + "properties": { + "ok": { + "type": "boolean", + "const": false, + "description": "写事务是否成功。" + }, + "error": { + "$ref": "#/components/schemas/TaskBoardError" + } + }, + "additionalProperties": false + }, + "TaskBoardSummary": { + "description": "全局看板列表中的单个授权 TaskBoard 摘要。", + "type": "object", + "required": [ + "meta", + "statusCounts", + "openCount" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/TaskBoardMeta" + }, + "statusCounts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskStatusCount" + }, + "description": "按状态聚合的授权任务数。" + }, + "openCount": { + "type": "integer", + "minimum": 0, + "description": "未进入 terminal/hidden 类别的任务数。" + }, + "archiveAction": { + "$ref": "#/components/schemas/TaskBoardArchiveAction" + } + }, + "additionalProperties": false + }, + "TaskBoardMeta": { + "description": "TaskBoard V3 的作用域、策略和提交 revision 元数据。", + "type": "object", + "required": [ + "schemaVersion", + "id", + "name", + "scope", + "ownerUserId", + "policy", + "revision", + "createdBy", + "createdAt", + "updatedAt" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "TaskBoard 数据格式版本。" + }, + "id": { + "type": "string", + "minLength": 1, + "description": "稳定 TaskBoard ID。" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "任务板展示名称。" + }, + "scope": { + "$ref": "#/components/schemas/TaskBoardScope" + }, + "ownerUserId": { + "type": "string", + "minLength": 1, + "description": "拥有此数据根的用户 ID;所有入口必须据请求身份校验。" + }, + "policy": { + "$ref": "#/components/schemas/TaskBoardPolicy" + }, + "workdirRef": { + "description": "Board 默认工作目录的 owner 验证引用;目录/worktree 生命周期仍由 WorkDir owner 管理。", + "type": "object", + "required": [ + "workdirId", + "ownerScope" + ], + "properties": { + "workdirId": { + "type": "string", + "minLength": 1, + "description": "WorkDir owner 中的稳定 ID。" + }, + "ownerScope": { + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "agent", + "team" + ], + "description": "WorkDir 配置的 owner scope。" }, - "then": { - "not": { - "anyOf": [ - { - "required": [ - "context_selection" - ] - }, - { - "required": [ - "retry_of_delivery_id" - ] - } - ] - } + "id": { + "type": "string", + "minLength": 1, + "description": "用户、Agent 或 Team 的稳定 owner ID。" } }, - { - "if": { - "properties": { - "route": { - "const": "external_agent" - } - }, - "required": [ - "route" - ] - }, - "then": { - "required": [ - "context_selection" - ] - } - } - ], - "additionalProperties": false, - "description": "每个唯一 recipient 的确定性路由;多个显示 occurrence 共用一套显式 selection。" + "additionalProperties": false, + "description": "解析 workdirId 所需的 owner scope;不包含绝对路径。" + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "授权写入时的安全展示快照。" + }, + "repositoryId": { + "type": "string", + "minLength": 1, + "description": "可选的 CodeRepo owner 稳定仓库 ID。" + } + }, + "additionalProperties": false + }, + "revision": { + "type": "integer", + "minimum": 0, + "description": "任务板提交序号,每个原子写事务递增一次。" + }, + "createdBy": { + "$ref": "#/components/schemas/ActorRef" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "archivedAt": { + "type": "string", + "format": "date-time", + "description": "Board 归档时间;归档后默认查询隐藏,但 canonical Task 和审计历史保留。" + } + }, + "additionalProperties": false + }, + "TaskBoardPublicTaskItem": { + "description": "Canonical Task V3 权威事实。", + "type": "object", + "required": [ + "schemaVersion", + "id", + "boardId", + "subject", + "description", + "status", + "priority", + "position", + "labels", + "blockedBy", + "blocks", + "runRefs", + "receiptRefs", + "workContextRefs", + "review", + "createdBy", + "createdAt", + "updatedAt", + "version" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "Task 数据格式版本。" + }, + "id": { + "type": "string", + "minLength": 1, + "description": "TaskBoard 内稳定 Task ID。" + }, + "boardId": { + "type": "string", + "minLength": 1, + "description": "所属 canonical TaskBoard ID。" + }, + "subject": { + "type": "string", + "minLength": 1, + "description": "面向执行和列表展示的短标题。" + }, + "description": { + "type": "string", + "description": "任务目标、边界和验收信息。" + }, + "activeForm": { + "type": "string", + "minLength": 1, + "description": "进行中展示文案。" + }, + "status": { + "type": "string", + "enum": [ + "backlog", + "pending", + "inProgress", + "blocked", + "inReview", + "completed", + "failed", + "canceled", + "deleted" + ], + "description": "策略状态机中的当前状态。" + }, + "priority": { + "type": "string", + "enum": [ + "high", + "normal", + "low" + ], + "description": "任务优先级。" + }, + "position": { + "type": "number", + "description": "同状态列内的稳定排序值。" + }, + "labels": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "description": "用于筛选的标签。" + }, + "description": "任务标签列表。" + }, + "assignee": { + "$ref": "#/components/schemas/TaskParticipantRef" + }, + "responsibilityEpoch": { + "type": "integer", + "minimum": 1, + "description": "每次 assign/handoff 递增的责任世代;旧 owner 的动作不得跨 epoch 生效。" + }, + "contributors": { + "type": "array", + "maxItems": 128, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskParticipantRef" + }, + "description": "参与者可评论/处理自己的 child task,不自动获得主 Task 完成权。" + }, + "watchers": { + "type": "array", + "maxItems": 256, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskParticipantRef" + }, + "description": "订阅者投影;通知与评论权每次按当前 scope/policy 重新鉴权。" + }, + "claim": { + "$ref": "#/components/schemas/TaskClaim" + }, + "parent": { + "$ref": "#/components/schemas/TaskRef" + }, + "blockedBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskRef" + }, + "description": "必须先完成的 canonical Task 引用。" + }, + "blocks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskRef" + }, + "description": "被此 Task 阻塞的 canonical Task 引用。" + }, + "relatedTo": { + "type": "array", + "maxItems": 256, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskRef" + }, + "description": "同 board 中无向 related 关系的对端 Task 引用。" + }, + "attachmentRefs": { + "type": "array", + "maxItems": 128, + "items": { + "$ref": "#/components/schemas/TaskAttachmentRef" + }, + "description": "MediaStore 拥有的附件引用;读取/下载时重新鉴权。" + }, + "approvalRefs": { + "type": "array", + "maxItems": 64, + "items": { + "$ref": "#/components/schemas/TaskApprovalRef" + }, + "description": "Approval owner 拥有的审批引用;TaskBoard 只投影不伪造结果。" + }, + "runRefs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RunRef" + }, + "description": "按 Run ID 去重的来源/执行导航引用,保留首次关联快照;不授予 claim 权限。逐轮归属见 TaskEvent.runRef。" + }, + "receiptRefs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReceiptRef" + }, + "description": "Receipt owner 中的关联回执引用。" + }, + "workContextRefs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkContextRef" + }, + "description": "Context/Session domain 拥有的 Work Context 引用;写入时重新鉴权并校验 canonical Task 一致性。" + }, + "sourceRef": { + "$ref": "#/components/schemas/TaskSourceRef" + }, + "automationBindingRefs": { + "type": "array", + "maxItems": 64, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskAutomationBindingRef" + }, + "description": "Automation/Scheduler owner 验证后的 bindings;时间规则和运行状态不在 TaskBoard 复制。" + }, + "workflowRefs": { + "type": "array", + "maxItems": 64, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskWorkflowRef" }, - "description": "Retired execution input; the HTTP route rejects this field.", - "deprecated": true + "description": "Workflow owner 验证后的稳定引用。" }, - "mediaRefs": { + "conversationRefs": { "type": "array", + "maxItems": 64, + "uniqueItems": true, "items": { - "type": "object", - "required": [ - "type", - "mediaId", - "name", - "mimeType", - "size" - ], - "properties": { - "type": { - "type": "string", - "description": "媒体业务类型。" - }, - "mediaId": { - "type": "string", - "description": "媒体存储引用。" - }, - "name": { - "type": "string", - "description": "原始文件名。" - }, - "mimeType": { - "type": "string", - "description": "媒体 MIME 类型。" - }, - "size": { - "type": "number", - "description": "媒体字节数。" - } - }, - "additionalProperties": true + "$ref": "#/components/schemas/TaskConversationRef" }, - "description": "本轮媒体附件引用。" + "description": "Conversation owner 验证后的稳定引用。" }, - "images": { + "workdirRef": { + "$ref": "#/components/schemas/TaskWorkDirRef" + }, + "mentionOccurrences": { "type": "array", "items": { - "type": "object", - "required": [ - "data", - "mimeType" - ], - "properties": { - "data": { - "type": "string", - "description": "已废弃 Base64 图片数据。" - }, - "mimeType": { - "type": "string", - "description": "图片 MIME 类型。" - } - }, - "additionalProperties": false + "$ref": "#/components/schemas/TaskBoardMentionOccurrence" }, - "description": "已废弃内联图片附件。" + "description": "Task description 当前 revision 的 camelCase Mention occurrence read projection。" }, - "clientRequestId": { + "mentionDeliveryRefs": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + }, + "description": "Mention domain 拥有的 description delivery IDs;不复制状态、Run 或正文。" + }, + "review": { + "$ref": "#/components/schemas/TaskReviewState" + }, + "resultSummary": { "type": "string", - "description": "客户端一次提交及其传输重试的稳定 ID。" + "description": "已完成、失败或取消后的结果摘要。" }, - "userMessageId": { - "type": "string" + "dueAt": { + "type": "string", + "format": "date-time", + "description": "计划到期时间;PR1 仅持久化,不提供自动调度。" }, - "requestDigest": { + "recurrenceRef": { "type": "string", - "maxLength": 128, - "description": "Optional digest produced by the shared request-body algorithm. The service recomputes it to reject reuse of a request ID with a changed business body." + "minLength": 1, + "readOnly": true, + "description": "旧版只读兼容引用;新写入必须使用 Automation/Scheduler owner 的 typed binding,禁止发展成第二套调度器。" }, - "originSocketId": { + "archivedAt": { "type": "string", - "description": "Current connected Socket.IO socket.id for precise event delivery; HTTP and Socket authentication must identify the same principal." + "format": "date-time", + "description": "归档时间;归档不改变审计事实。" }, - "uiContextAdmission": { - "type": "object", - "required": [ - "version", - "admissionRef" - ], - "properties": { - "version": { - "type": "integer", - "const": 1, - "description": "Version of the durable UI context admission-reference envelope." - }, - "admissionRef": { - "type": "string", - "minLength": 56, - "maxLength": 56, - "pattern": "^ui_admission_[A-Za-z0-9_-]{43}$", - "description": "Opaque durable reference to an immutable admitted turn-context sidecar; it contains no snapshot, token, host identity, path, URL, or user text." - } - }, - "additionalProperties": false, - "description": "A valid context admission reference from an authorized UI flow. Ordinary standalone scripts omit it." + "deletedAt": { + "type": "string", + "format": "date-time", + "description": "软删除时间。" }, - "appWorkAdmissionRef": { + "createdBy": { + "$ref": "#/components/schemas/ActorRef" + }, + "createdAt": { "type": "string", - "pattern": "^apa_[A-Za-z0-9_-]{43}$", - "maxLength": 512, - "description": "Opaque reference supplied by an authorized application proposal flow. Standalone integration examples omit it; an arbitrary reference does not grant authority." + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "version": { + "type": "integer", + "minimum": 1, + "description": "Task 乐观并发版本,每次成功写入递增。" } }, - "definitions": { - "conversationContentBlock": { - "type": "object", - "required": [ - "type" + "additionalProperties": false + }, + "TaskBoardPublicComment": { + "description": "TaskBoard domain 拥有的基础协作或 review 评论。", + "type": "object", + "required": [ + "schemaVersion", + "id", + "taskRef", + "kind", + "body", + "actor", + "version", + "createdAt", + "updatedAt" + ], + "properties": { + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "Task comment 数据格式版本。" + }, + "id": { + "type": "string", + "minLength": 1, + "description": "TaskBoard 内稳定 comment ID。" + }, + "taskRef": { + "$ref": "#/components/schemas/TaskRef" + }, + "kind": { + "type": "string", + "enum": [ + "comment", + "review" ], - "properties": { - "type": { - "type": "string", - "enum": [ - "text", - "tool_use", - "tool_result", - "image", - "thinking" - ], - "description": "结构化历史内容块类型。" - }, - "text": { - "type": "string", - "description": "文本块正文。" - }, - "id": { - "type": "string", - "description": "tool_use 块 ID。" - }, - "name": { - "type": "string", - "description": "工具名称。" - }, - "input": { - "description": "工具输入 JSON。" - }, - "tool_use_id": { - "type": "string", - "description": "tool_result 对应的 tool_use ID。" - }, - "content": { - "description": "工具结果文本或嵌套结构化内容块。", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/QueryStartRequest/definitions/conversationContentBlock" - } - } - ] - }, - "is_error": { - "type": "boolean", - "description": "工具结果是否表示错误。" - }, - "source": { - "type": "object", - "required": [ - "type", - "media_type", - "data" - ], - "properties": { - "type": { - "const": "base64", - "description": "图片数据编码类型。" - }, - "media_type": { - "type": "string", - "description": "图片 MIME 类型。" - }, - "data": { - "type": "string", - "description": "Base64 图片数据。" - } - }, - "additionalProperties": false - }, - "thinking": { - "type": "string", - "description": "Provider thinking 正文。" - } + "description": "普通协作评论或验收/返工评论。" + }, + "body": { + "type": "string", + "minLength": 1, + "maxLength": 65536, + "description": "评论正文;只有结构化 occurrence 才触发 Mention,裸 @name 始终是普通文本。" + }, + "actor": { + "$ref": "#/components/schemas/ActorRef" + }, + "attachmentRefs": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskAttachmentRef" }, - "additionalProperties": false, - "description": "Query conversationHistory 中的结构化内容块。" + "description": "当前 comment revision 的 MediaStore owner-safe 附件引用。" }, - "conversationMessage": { + "revisionHistory": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/TaskCommentRevision" + }, + "description": "按 version 升序保存的不可变正文历史;旧快照缺失时读取层可按 version=1 兼容。" + }, + "runRef": { + "$ref": "#/components/schemas/RunRef" + }, + "receiptRef": { + "$ref": "#/components/schemas/ReceiptRef" + }, + "workContextRef": { + "$ref": "#/components/schemas/WorkContextRef" + }, + "mentionOccurrences": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskBoardMentionOccurrence" + }, + "description": "Comment 当前 revision 的 camelCase Mention occurrence read projection。" + }, + "mentionDeliveryRefs": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "description": "Mention domain 拥有的 delivery ID。" + }, + "description": "此来源 comment 发出的 delivery IDs;状态、Run 与 receipt 仍由各 owner 持有。" + }, + "originMentionDeliveryId": { + "type": "string", + "minLength": 1, + "description": "Agent 自动回评来自哪个 Mention delivery;与本评论向外发出的 delivery refs 分离。" + }, + "originMentionSource": { "type": "object", "required": [ - "role", - "content" + "kind", + "sourceId", + "sourceRevision", + "taskRef" ], "properties": { - "role": { + "kind": { "type": "string", "enum": [ - "user", - "assistant", - "system" + "taskDescription", + "taskComment" ], - "description": "消息角色;system 仅用于受控上下文边界 marker。" - }, - "content": { - "type": "array", - "items": { - "$ref": "#/components/schemas/QueryStartRequest/definitions/conversationContentBlock" - }, - "description": "结构化消息内容。" - }, - "timestamp": { - "type": "number", - "description": "消息时间戳(毫秒)。" + "description": "TaskBoard 公开来源类型。" }, - "id": { + "sourceId": { "type": "string", - "description": "SessionMessage 空间中的稳定消息 ID。" - }, - "compactCutoff": { - "type": "boolean", - "description": "压缩摘要边界 marker。" + "minLength": 1, + "description": "Task ID 或 Task comment ID。" }, - "summarizedCount": { - "type": "number", - "description": "本次摘要覆盖的消息数。" + "sourceRevision": { + "type": "integer", + "minimum": 1, + "description": "不可变来源 revision。" }, - "compactedAt": { - "type": "string", - "format": "date-time", - "description": "压缩发生时间。" + "taskRef": { + "type": "object", + "required": [ + "boardId", + "taskId" + ], + "properties": { + "boardId": { + "type": "string", + "minLength": 1, + "description": "canonical Board ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "description": "canonical Task ID。" + }, + "taskVersion": { + "type": "integer", + "minimum": 1, + "description": "可选 Task version 快照。" + } + }, + "additionalProperties": false, + "description": "TaskBoard 公开来源的 canonical TaskRef。" + } + }, + "additionalProperties": false, + "description": "Agent 自动回评来源的 camelCase TaskBoard read projection。" + }, + "replyToCommentId": { + "type": "string", + "minLength": 1, + "description": "仅 task_comment 来源的 Agent 回评必填;Task description 来源没有伪造 comment ID。" + }, + "version": { + "type": "integer", + "minimum": 1, + "description": "Comment 乐观并发版本。" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "redactedAt": { + "type": "string", + "format": "date-time", + "description": "普通 UI 开始隐藏正文的审计撤回时间。" + }, + "redactedBy": { + "$ref": "#/components/schemas/ActorRef" + }, + "redactionReason": { + "type": "string", + "minLength": 1, + "maxLength": 2000, + "description": "不包含被撤回正文的安全原因。" + }, + "deletedAt": { + "type": "string", + "format": "date-time", + "description": "评论软删除时间。" + } + }, + "allOf": [ + { + "if": { + "required": [ + "originMentionSource" + ], + "properties": { + "originMentionSource": { + "required": [ + "kind" + ], + "properties": { + "kind": { + "const": "taskComment" + } + } + } + } + }, + "then": { + "required": [ + "replyToCommentId" + ] + } + }, + { + "if": { + "required": [ + "originMentionSource" + ], + "properties": { + "originMentionSource": { + "required": [ + "kind" + ], + "properties": { + "kind": { + "const": "taskDescription" + } + } + } + } + }, + "then": { + "not": { + "required": [ + "replyToCommentId" + ] + } + } + }, + { + "if": { + "properties": { + "originMentionDeliveryId": {} }, - "compactAnchorMessageId": { - "type": "string", - "description": "压缩后保留区第一条消息 ID。" + "required": [ + "originMentionDeliveryId" + ] + }, + "then": { + "properties": { + "originMentionSource": {}, + "runRef": {}, + "receiptRef": {}, + "workContextRef": {} }, - "compactKeptTurns": { - "type": "number", - "description": "压缩后保留的对话轮数。" + "required": [ + "originMentionSource", + "runRef", + "receiptRef", + "workContextRef" + ] + } + }, + { + "if": { + "properties": { + "originMentionSource": {} }, - "compactCoverStartMessageId": { - "type": "string", - "description": "本 marker 覆盖区第一条消息 ID。" + "required": [ + "originMentionSource" + ] + }, + "then": { + "properties": { + "originMentionDeliveryId": {} }, - "compactRunId": { - "type": "string", - "description": "老 marker 的覆盖 run。" + "required": [ + "originMentionDeliveryId" + ] + } + }, + { + "if": { + "properties": { + "replyToCommentId": {} }, - "compactMerged": { - "type": "boolean", - "description": "是否为摘要链二级合并产物。" + "required": [ + "replyToCommentId" + ] + }, + "then": { + "properties": { + "originMentionDeliveryId": {} }, - "compactSupersedes": { - "type": "array", - "items": { - "type": "string" - }, - "description": "被本 marker 取代的 marker ID 列表。" + "required": [ + "originMentionDeliveryId" + ] + } + }, + { + "if": { + "properties": { + "redactedAt": {} }, - "compactTrigger": { - "type": "string", - "enum": [ - "auto", - "manual" - ], - "description": "压缩由自动阈值还是用户显式触发。" + "required": [ + "redactedAt" + ] + }, + "then": { + "properties": { + "redactedBy": {} }, - "compactWindow": { - "type": "number", - "description": "生成自动压缩 marker 时的有效上下文窗口。" + "required": [ + "redactedBy" + ] + } + }, + { + "if": { + "properties": { + "redactedBy": {} }, - "contextReset": { - "type": "boolean", - "description": "不携带摘要的显式遗忘边界。" + "required": [ + "redactedBy" + ] + }, + "then": { + "properties": { + "redactedAt": {} }, - "resetAnchorMessageId": { + "required": [ + "redactedAt" + ] + } + } + ], + "additionalProperties": false + }, + "TaskRef": { + "description": "跨会话和 Agent 稳定引用一个 canonical Task。", + "type": "object", + "required": [ + "boardId", + "taskId" + ], + "properties": { + "boardId": { + "type": "string", + "minLength": 1, + "description": "Task 所属 canonical TaskBoard ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "description": "TaskBoard 内稳定 Task ID。" + }, + "taskVersion": { + "type": "integer", + "minimum": 1, + "description": "可选的 Task CAS 版本;TaskCreate 返回时提供,可直接传给 Delegate。" + } + }, + "additionalProperties": false + }, + "TaskAvailableAction": { + "description": "服务端针对当前 actor 计算的 Task 动作。", + "type": "object", + "required": [ + "action", + "enabled", + "expectedVersion" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "create", + "update", + "publish", + "claim", + "release", + "submitForReview", + "accept", + "requestChanges", + "fail", + "cancel", + "reopen", + "delete", + "addComment", + "assign", + "addContributor", + "removeContributor", + "addWatcher", + "removeWatcher", + "forceRelease", + "handoff", + "fanOut", + "assignReviewer", + "reviewAccept", + "reviewRequestChanges", + "addRelation", + "removeRelation", + "addAttachment", + "removeAttachment" + ], + "description": "服务端评估的基础状态机或 PR4 责任动作。" + }, + "enabled": { + "type": "boolean", + "description": "当前 actor、scope、状态和 claim 下是否可执行。" + }, + "expectedVersion": { + "type": "integer", + "minimum": 1, + "description": "执行动作必须回传的 Task version。" + }, + "targetStatus": { + "type": "string", + "enum": [ + "backlog", + "pending", + "inProgress", + "blocked", + "inReview", + "completed", + "failed", + "canceled", + "deleted" + ], + "description": "动作成功后的目标状态。" + }, + "reasonCode": { + "type": "string", + "enum": [ + "invalidRequest", + "taskContextMismatch", + "taskContextUnavailable", + "unauthenticated", + "forbidden", + "boardNotFound", + "taskNotFound", + "commentNotFound", + "versionConflict", + "claimConflict", + "invalidTransition", + "dependencyBlocked", + "reviewRequired", + "reviewRoundConflict", + "commandAuthorityDenied", + "claimLivenessUnknown", + "claimStillActive", + "relationConflict", + "relationCycle", + "attachmentRejected", + "fanOutConflict", + "capabilityDisabled", + "capabilityUnavailable", + "migrationFailed", + "internalError" + ], + "description": "动作不可用时的稳定原因码。" + }, + "description": { + "type": "string", + "minLength": 1, + "description": "面向用户的安全动作说明。" + }, + "requiredInputs": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "claimToken", + "expectedClaimEpoch", + "expectedResponsibilityEpoch", + "targetActor", + "reason", + "reviewer", + "expectedReviewRound", + "expectedReviewerEpoch", + "comment", + "operationId", + "fanOutTargets", + "relation", + "attachmentRef" + ], + "description": "UI 只按服务端声明采集的输入类型。" + }, + "description": "该动作还需要的结构化输入;前端不根据状态自行猜测。" + } + }, + "additionalProperties": false + }, + "TaskFanOutDispatch": { + "description": "一个 canonical taskFannedOut intent 的持久投递状态;child Task 已提交不等于 Agent Run 已启动。", + "type": "object", + "required": [ + "intentId", + "operationId", + "parentTaskRef", + "childTaskRef", + "targetAgentId", + "state", + "attempt", + "retryable", + "updatedAt" + ], + "properties": { + "intentId": { + "type": "string", + "minLength": 1, + "description": "由 canonical source event 冻结的 Delegate intent ID。" + }, + "operationId": { + "type": "string", + "pattern": "^fanout-operation:[a-f0-9]{64}$", + "description": "一次用户 fan-out 操作的稳定 ID;刷新和重试沿用,显式新操作才更换。" + }, + "parentTaskRef": { + "description": "发起 fan-out 的 canonical parent Task。", + "type": "object", + "required": [ + "boardId", + "taskId" + ], + "properties": { + "boardId": { "type": "string", - "description": "触发 reset 的 user 消息 ID。" + "minLength": 1, + "description": "Task 所属 canonical TaskBoard ID。" }, - "resetReason": { + "taskId": { "type": "string", - "description": "reset 的用户可见原因。" - }, - "fileReferences": { - "type": "array", - "items": { - "type": "string" - }, - "description": "用户引用的文件路径。" + "minLength": 1, + "description": "TaskBoard 内稳定 Task ID。" }, - "mediaRefs": { - "type": "array", - "items": { - "type": "object", - "required": [ - "mediaId" - ], - "properties": { - "mediaId": { - "type": "string", - "description": "媒体存储中的稳定引用。" - }, - "mimeType": { - "type": "string", - "description": "媒体 MIME 类型。" - }, - "type": { - "type": "string", - "description": "媒体业务类型。" - } - }, - "additionalProperties": false - }, - "description": "历史消息引用的媒体。" + "taskVersion": { + "type": "integer", + "minimum": 1, + "description": "可选的 Task CAS 版本;TaskCreate 返回时提供,可直接传给 Delegate。" } }, "additionalProperties": false + }, + "childTaskRef": { + "$ref": "#/components/schemas/TaskRef" + }, + "targetAgentId": { + "type": "string", + "minLength": 1, + "description": "该 intent 的稳定目标 Agent ID。" + }, + "state": { + "type": "string", + "enum": [ + "pending", + "dispatching", + "dispatched", + "failed" + ], + "description": "pending/dispatching 可恢复;dispatched 已获 owner Run;failed 由 retryable 决定是否重试。" + }, + "attempt": { + "type": "integer", + "minimum": 0, + "description": "dispatcher 获得有效 lease 并尝试 owner 投递的次数。" + }, + "retryable": { + "type": "boolean", + "description": "当前状态能否由同一 intent 自动或显式重试;不允许据此创建新 child Task。" + }, + "runRef": { + "$ref": "#/components/schemas/RunRef" + }, + "receiptRef": { + "$ref": "#/components/schemas/ReceiptRef" + }, + "errorCode": { + "type": "string", + "minLength": 1, + "description": "最近失败的稳定 owner/dispatcher 错误码。" + }, + "errorMessage": { + "type": "string", + "minLength": 1, + "maxLength": 8192, + "description": "不含 claim token、路径或上下文正文的最近失败说明。" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" } }, - "additionalProperties": false, - "not": { - "anyOf": [ - { + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "dispatched" + } + }, "required": [ - "mentionOccurrences" + "state" ] }, - { + "then": { "required": [ - "mentionRecipientRequests" + "runRef" ] } - ] - } + } + ], + "additionalProperties": false }, - "QueryAdmission": { + "TaskArchiveAction": { + "description": "服务端为当前 principal 计算的 Task 归档/恢复动作。", "type": "object", "required": [ - "ok" + "enabled", + "archived", + "expectedVersion" ], "properties": { - "ok": { + "enabled": { "type": "boolean", - "description": "请求是否已被准入。" + "description": "当前 principal 是否可执行归档或恢复。" }, - "clientRequestId": { + "archived": { + "type": "boolean", + "description": "当前 Task 是否归档。" + }, + "expectedVersion": { + "type": "integer", + "minimum": 1, + "description": "动作必须回传的 Task version。" + }, + "reason": { "type": "string", - "description": "对应的客户端请求 ID。" + "minLength": 1, + "description": "不可用时的安全说明。" + } + }, + "additionalProperties": false + }, + "TaskDeepLink": { + "description": "不依赖展示名称、可复制并可在刷新后恢复的 TaskBoard 内部深链。", + "type": "object", + "required": [ + "taskRef", + "route" + ], + "properties": { + "taskRef": { + "$ref": "#/components/schemas/TaskRef" }, - "duplicate": { - "type": "boolean", - "description": "是否命中幂等重复请求。" + "route": { + "type": "string", + "pattern": "^/taskboards/[^/]+/tasks/[^/]+$", + "description": "应用内稳定 route;board/task ID 必须按 path segment 编码。" + } + }, + "additionalProperties": false + }, + "TaskBoardQueryFilter": { + "description": "服务端在已授权 canonical boards 上执行的组合过滤条件。", + "type": "object", + "properties": { + "keyword": { + "type": "string", + "maxLength": 512, + "description": "大小写不敏感匹配 Task ID、标题、描述、标签和安全 owner 引用 ID。" }, - "busy": { - "type": "boolean", - "description": "准入被竞争挡下而非失败,仅两种来源:会话清除窗口(query:start、HTTP start、follow-up;清除结束后可用同一 clientRequestId 重试)与同一 session 已有 continue 在执行(仅 query:continue)。start 准入只有清除窗口会置位,客户端据此区分清除与其它 retryable 拒绝;缺省或 false 不代表清除。" + "boardIds": { + "type": "array", + "maxItems": 256, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + }, + "description": "限制到指定 canonical Board IDs;无权读取的 ID 静默不返回。" }, - "error": { + "scopeTypes": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "user", + "team", + "agent", + "session" + ] + }, + "description": "按 Board scope 类型过滤。" + }, + "statuses": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "backlog", + "pending", + "inProgress", + "blocked", + "inReview", + "completed", + "failed", + "canceled", + "deleted" + ] + }, + "description": "按 policy 状态过滤。" + }, + "priorities": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "high", + "normal", + "low" + ] + }, + "description": "按优先级过滤。" + }, + "labels": { + "type": "array", + "maxItems": 128, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + }, + "description": "按标签过滤。" + }, + "labelMatch": { "type": "string", - "description": "拒绝时的稳定错误说明。" + "enum": [ + "any", + "all" + ], + "description": "labels 是命中任一还是全部;省略等价 any。" }, - "code": { + "participantIds": { + "type": "array", + "maxItems": 256, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + }, + "description": "匹配 assignee、claim actor、reviewer、contributors 或 watchers 的稳定 ID。" + }, + "reviewStates": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "notSubmitted", + "awaitingReview", + "accepted", + "changesRequested" + ] + }, + "description": "按 TaskBoard review projection 过滤。" + }, + "due": { "type": "string", - "description": "稳定拒绝码,如 CLIENT_REQUEST_BODY_MISMATCH(同 ID 不同请求体,不可重试)。" + "enum": [ + "overdue", + "upcoming", + "none", + "any" + ], + "description": "到期筛选;overdue/upcoming 以请求处理时刻为界,不启动调度。" }, - "retryable": { - "type": "boolean", - "description": "是否允许用同一 clientRequestId 重试。" + "sourceOwnerTypes": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "automation", + "workflow", + "conversation", + "connector", + "import", + "manual" + ] + }, + "description": "按中性 sourceRef owner 类型过滤。" }, - "uiContextFaultCode": { + "integrationTypes": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "automation", + "workflow", + "conversation", + "workdir", + "run" + ] + }, + "description": "只返回具有相应 owner 引用的 Task。" + }, + "boardArchive": { "type": "string", "enum": [ - "snapshot_missing", - "snapshot_stale", - "surface_mismatch", - "auth_required", - "human_required", - "redaction_blocked", - "adapter_invalid", - "snapshot_conflict", - "protocol_unsupported", - "host_unavailable", - "action_unavailable", - "action_failed", - "action_outcome_unknown", - "action_capacity_exceeded" + "active", + "archived", + "any" ], - "description": "Stable fail-closed UI context protocol fault code." + "description": "Board 归档筛选;省略时只返回 active Board。" }, - "sessionId": { + "taskArchive": { "type": "string", - "description": "已建立或已存在的 session ID。" + "enum": [ + "active", + "archived", + "any" + ], + "description": "Task 归档筛选;省略时只返回 active Task。" } }, - "additionalProperties": false, - "description": "Socket.IO query:start 与 HTTP start 的准入响应。" + "additionalProperties": false }, - "QueryFollowUpRequest": { + "TaskBoardQuerySort": { + "description": "稳定 keyset 分页排序;最终始终以 boardId/taskId 消歧。", "type": "object", "required": [ - "sessionId", - "originSocketId", - "message" + "field", + "direction" + ], + "properties": { + "field": { + "type": "string", + "enum": [ + "updatedAt", + "createdAt", + "dueAt", + "priority", + "position", + "subject" + ], + "description": "主排序字段。" + }, + "direction": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "description": "主排序方向。" + } + }, + "additionalProperties": false + }, + "TaskBoardQueryItem": { + "description": "由 canonical Board/Task 即时授权并从可重建索引定位的查询行。", + "type": "object", + "required": [ + "board", + "task", + "commentCount", + "deepLink", + "availableActions", + "archiveAction" ], "properties": { - "sessionId": { - "type": "string" - }, - "originSocketId": { - "type": "string", - "minLength": 1, - "description": "Current connected socket.id, authenticated as the same principal as the HTTP request." + "board": { + "$ref": "#/components/schemas/TaskBoardMeta" }, - "message": { + "task": { + "description": "当前 principal 可见的 Task 读投影。有认领时 claim.liveness 是本次查询读取时 RunManager 给出的执行存活判定(terminal 表示执行已结束,unknown 不应据此提示);claim.token 在查询结果中一律省略。liveness 不参与索引与游标比较,仅 checkedAt 变化不会让翻页报 versionConflict。", "type": "object", "required": [ - "role", - "content" + "schemaVersion", + "id", + "boardId", + "subject", + "description", + "status", + "priority", + "position", + "labels", + "blockedBy", + "blocks", + "runRefs", + "receiptRefs", + "workContextRefs", + "review", + "createdBy", + "createdAt", + "updatedAt", + "version" ], "properties": { + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "Task 数据格式版本。" + }, "id": { - "type": "string" + "type": "string", + "minLength": 1, + "description": "TaskBoard 内稳定 Task ID。" }, - "role": { + "boardId": { + "type": "string", + "minLength": 1, + "description": "所属 canonical TaskBoard ID。" + }, + "subject": { + "type": "string", + "minLength": 1, + "description": "面向执行和列表展示的短标题。" + }, + "description": { + "type": "string", + "description": "任务目标、边界和验收信息。" + }, + "activeForm": { + "type": "string", + "minLength": 1, + "description": "进行中展示文案。" + }, + "status": { "type": "string", "enum": [ - "user", - "assistant" - ] + "backlog", + "pending", + "inProgress", + "blocked", + "inReview", + "completed", + "failed", + "canceled", + "deleted" + ], + "description": "策略状态机中的当前状态。" }, - "content": { - "type": "string" + "priority": { + "type": "string", + "enum": [ + "high", + "normal", + "low" + ], + "description": "任务优先级。" }, - "timestamp": { - "type": "number" - } - }, - "additionalProperties": true - }, - "clientRequestId": { - "type": "string" - }, - "requestDigest": { - "type": "string" - }, - "mediaRefs": { - "type": "array", - "items": { - "type": "object", - "required": [ - "type", - "mediaId", - "name", - "mimeType", - "size" - ], - "properties": { - "type": { + "position": { + "type": "number", + "description": "同状态列内的稳定排序值。" + }, + "labels": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", - "description": "媒体业务类型。" + "minLength": 1, + "description": "用于筛选的标签。" }, - "mediaId": { - "type": "string", - "description": "媒体存储引用。" + "description": "任务标签列表。" + }, + "assignee": { + "$ref": "#/components/schemas/TaskParticipantRef" + }, + "responsibilityEpoch": { + "type": "integer", + "minimum": 1, + "description": "每次 assign/handoff 递增的责任世代;旧 owner 的动作不得跨 epoch 生效。" + }, + "contributors": { + "type": "array", + "maxItems": 128, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskParticipantRef" }, - "name": { - "type": "string", - "description": "原始文件名。" + "description": "参与者可评论/处理自己的 child task,不自动获得主 Task 完成权。" + }, + "watchers": { + "type": "array", + "maxItems": 256, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskParticipantRef" }, - "mimeType": { - "type": "string", - "description": "媒体 MIME 类型。" + "description": "订阅者投影;通知与评论权每次按当前 scope/policy 重新鉴权。" + }, + "claim": { + "$ref": "#/components/schemas/TaskClaim" + }, + "parent": { + "$ref": "#/components/schemas/TaskRef" + }, + "blockedBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskRef" }, - "size": { - "type": "number", - "description": "媒体字节数。" - } + "description": "必须先完成的 canonical Task 引用。" }, - "additionalProperties": true - }, - "description": "本轮媒体附件引用。" - }, - "images": { - "type": "array", - "items": { - "type": "object", - "required": [ - "data", - "mimeType" - ], - "properties": { - "data": { - "type": "string", - "description": "已废弃 Base64 图片数据。" + "blocks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskRef" }, - "mimeType": { - "type": "string", - "description": "图片 MIME 类型。" - } + "description": "被此 Task 阻塞的 canonical Task 引用。" }, - "additionalProperties": false - }, - "description": "已废弃内联图片附件。" - }, - "fileReferences": { - "type": "array", - "items": { - "type": "string" - } - }, - "activeDocPath": { - "type": "string" - }, - "reasoning": { - "type": "string" - }, - "agentReferences": { - "type": "array", - "items": { - "type": "object", - "required": [ - "occurrence_id", - "target", - "range" - ], - "properties": { - "occurrence_id": { - "type": "string", - "minLength": 8, - "maxLength": 256, - "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", - "description": "客户端生成、在一次来源 revision 内稳定的 occurrence ID;同一 Agent 可出现多次。" + "relatedTo": { + "type": "array", + "maxItems": 256, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskRef" }, - "target": { - "type": "object", - "required": [ - "agent_id", - "display_name_snapshot" - ], - "properties": { - "agent_id": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", - "description": "目标 Agent 的稳定语义 ID;仅允许 ASCII 字母、数字、下划线和连字符。" - }, - "display_name_snapshot": { - "type": "string", - "minLength": 1, - "description": "服务端按 agent_id 从可信 Agent Registry 重建的显示快照;客户端同名字段只用于提交时 range 校验,不参与身份或 system prompt。" - } - }, - "additionalProperties": false, - "description": "稳定 Agent 引用;是否执行由来源契约决定,显示名不能单独触发投递。" + "description": "同 board 中无向 related 关系的对端 Task 引用。" + }, + "attachmentRefs": { + "type": "array", + "maxItems": 128, + "items": { + "$ref": "#/components/schemas/TaskAttachmentRef" }, - "range": { - "type": "object", - "required": [ - "start", - "end" - ], - "properties": { - "start": { - "type": "integer", - "minimum": 0, - "description": "正文 UTF-16 code unit 的起始偏移,包含。" - }, - "end": { - "type": "integer", - "minimum": 1, - "description": "正文 UTF-16 code unit 的结束偏移,不包含。" - } - }, - "additionalProperties": false, - "description": "结构化 token 在来源正文中的半开区间;服务端校验 start < end 且与正文匹配。" - } + "description": "MediaStore 拥有的附件引用;读取/下载时重新鉴权。" + }, + "approvalRefs": { + "type": "array", + "maxItems": 64, + "items": { + "$ref": "#/components/schemas/TaskApprovalRef" + }, + "description": "Approval owner 拥有的审批引用;TaskBoard 只投影不伪造结果。" + }, + "runRefs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RunRef" + }, + "description": "按 Run ID 去重的来源/执行导航引用,保留首次关联快照;不授予 claim 权限。逐轮归属见 TaskEvent.runRef。" + }, + "receiptRefs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReceiptRef" + }, + "description": "Receipt owner 中的关联回执引用。" + }, + "workContextRefs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkContextRef" + }, + "description": "Context/Session domain 拥有的 Work Context 引用;写入时重新鉴权并校验 canonical Task 一致性。" + }, + "sourceRef": { + "$ref": "#/components/schemas/TaskSourceRef" + }, + "automationBindingRefs": { + "type": "array", + "maxItems": 64, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskAutomationBindingRef" + }, + "description": "Automation/Scheduler owner 验证后的 bindings;时间规则和运行状态不在 TaskBoard 复制。" + }, + "workflowRefs": { + "type": "array", + "maxItems": 64, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskWorkflowRef" + }, + "description": "Workflow owner 验证后的稳定引用。" + }, + "conversationRefs": { + "type": "array", + "maxItems": 64, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/TaskConversationRef" + }, + "description": "Conversation owner 验证后的稳定引用。" + }, + "workdirRef": { + "$ref": "#/components/schemas/TaskWorkDirRef" + }, + "mentionOccurrences": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskBoardMentionOccurrence" + }, + "description": "Task description 当前 revision 的 camelCase Mention occurrence read projection。" + }, + "mentionDeliveryRefs": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + }, + "description": "Mention domain 拥有的 description delivery IDs;不复制状态、Run 或正文。" }, - "additionalProperties": false, - "description": "picker 生成的结构化 Agent token;聊天中为非执行引用,看板中可由来源 owner 形成 Mention。" - }, - "description": "非执行 Agent 引用。" - }, - "uiContextAdmission": { - "type": "object", - "required": [ - "version", - "admissionRef" - ], - "properties": { - "version": { - "type": "integer", - "const": 1, - "description": "Version of the durable UI context admission-reference envelope." + "review": { + "$ref": "#/components/schemas/TaskReviewState" }, - "admissionRef": { + "resultSummary": { "type": "string", - "minLength": 56, - "maxLength": 56, - "pattern": "^ui_admission_[A-Za-z0-9_-]{43}$", - "description": "Opaque durable reference to an immutable admitted turn-context sidecar; it contains no snapshot, token, host identity, path, URL, or user text." + "description": "已完成、失败或取消后的结果摘要。" + }, + "dueAt": { + "type": "string", + "format": "date-time", + "description": "计划到期时间;PR1 仅持久化,不提供自动调度。" + }, + "recurrenceRef": { + "type": "string", + "minLength": 1, + "readOnly": true, + "description": "旧版只读兼容引用;新写入必须使用 Automation/Scheduler owner 的 typed binding,禁止发展成第二套调度器。" + }, + "archivedAt": { + "type": "string", + "format": "date-time", + "description": "归档时间;归档不改变审计事实。" + }, + "deletedAt": { + "type": "string", + "format": "date-time", + "description": "软删除时间。" + }, + "createdBy": { + "$ref": "#/components/schemas/ActorRef" + }, + "createdAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "version": { + "type": "integer", + "minimum": 1, + "description": "Task 乐观并发版本,每次成功写入递增。" } }, - "additionalProperties": false, - "description": "Only UI context object allowed on an actual query or pending record; omitting the entire field preserves the legacy no-context path." + "additionalProperties": false + }, + "commentCount": { + "type": "integer", + "minimum": 0, + "description": "canonical comments 中当前 Task 的未删除评论数量。" + }, + "deepLink": { + "$ref": "#/components/schemas/TaskDeepLink" + }, + "availableActions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskAvailableAction" + }, + "description": "查询时针对当前 principal 重新计算的 canonical Task 可用动作。" + }, + "archiveAction": { + "$ref": "#/components/schemas/TaskArchiveAction" } }, - "additionalProperties": true, - "description": "Documented request subset from the route interface. Additional fields do not imply authorization or guaranteed support.", - "not": { - "anyOf": [ - { - "required": [ - "mentionOccurrences" - ] - }, - { - "required": [ - "mentionRecipientRequests" - ] - } - ] - } + "additionalProperties": false }, - "ClientRequestLookupRequest": { + "TaskStatusCount": { + "description": "一个 Task 状态的授权聚合数量。", "type": "object", - "additionalProperties": false, "required": [ - "agentId", - "requests" + "status", + "count" ], - "description": "按 clientRequestId 只读查询服务端接纳状态;不占位、不清理、不产生任何副作用。", "properties": { - "agentId": { + "status": { + "type": "string", + "enum": [ + "backlog", + "pending", + "inProgress", + "blocked", + "inReview", + "completed", + "failed", + "canceled", + "deleted" + ], + "description": "被统计的 Task 状态。" + }, + "count": { + "type": "integer", + "minimum": 0, + "description": "授权范围内该状态的 Task 数量。" + } + }, + "additionalProperties": false + }, + "TaskActivityEnvelope": { + "description": "Task workspace Activity 的统一展示 envelope;canonical event 与 owner projection 明确分流。", + "type": "object", + "required": [ + "activityId", + "taskRef", + "source", + "occurredAt", + "title" + ], + "properties": { + "activityId": { "type": "string", "minLength": 1, - "maxLength": 256, - "pattern": "\\S" + "description": "UI 去重和稳定排序使用的活动 ID。" }, - "conversationId": { + "taskRef": { + "$ref": "#/components/schemas/TaskRef" + }, + "source": { + "type": "string", + "enum": [ + "taskEvent", + "ownerProjection" + ], + "description": "事实来自哪个边界。" + }, + "occurredAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "title": { "type": "string", "minLength": 1, - "maxLength": 256, - "pattern": "\\S" + "maxLength": 512, + "description": "本地化前的安全展示标题。" }, - "requests": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "description": "要查询的提交列表,每项一个 clientRequestId。", - "items": { - "type": "object", - "additionalProperties": false, + "actor": { + "$ref": "#/components/schemas/ActorRef" + }, + "taskEvent": { + "$ref": "#/components/schemas/TaskEvent" + }, + "ownerProjection": { + "$ref": "#/components/schemas/TaskActivityProjection" + } + }, + "allOf": [ + { + "if": { + "properties": { + "source": { + "const": "taskEvent" + } + }, "required": [ - "clientRequestId" + "source" + ] + }, + "then": { + "required": [ + "taskEvent" ], + "not": { + "required": [ + "ownerProjection" + ] + } + } + }, + { + "if": { "properties": { - "clientRequestId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "\\S", - "description": "客户端一次提交及其全部重发共用的稳定 ID。" - }, - "userMessageId": { - "type": "string", - "minLength": 1, - "maxLength": 256, - "pattern": "\\S" + "source": { + "const": "ownerProjection" } + }, + "required": [ + "source" + ] + }, + "then": { + "required": [ + "ownerProjection" + ], + "not": { + "required": [ + "taskEvent" + ] } } } - } + ], + "additionalProperties": false }, - "ClientRequestLookupResponse": { + "TaskActivityPage": { + "description": "当前历史页的范围与后页位置;追加新事件不扩大throughRevision,刷新重新取当前前缀。", "type": "object", - "additionalProperties": false, "required": [ - "protocolVersion", - "serverInstanceId", - "observedAt", - "autoReplayRetentionMs", - "tombstoneRetentionMs", - "results" + "pageSize", + "afterRevision", + "throughRevision", + "nextAfterRevision" ], - "description": "只读接纳查询结果。", "properties": { - "protocolVersion": { + "pageSize": { "type": "integer", - "const": 1, - "description": "查询协议版本。" + "minimum": 1, + "maximum": 100, + "description": "本页canonical候选数量上限。" }, - "serverInstanceId": { + "afterRevision": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991, + "description": "当前页排除的前序revision。" + }, + "throughRevision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "本次浏览固定的Board历史前缀。" + }, + "nextAfterRevision": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 9007199254740991, + "description": "下一页排除到的已扫描revision;null表示固定前缀已读完。" + } + }, + "additionalProperties": false + }, + "TaskBoardError": { + "description": "TaskBoard 稳定错误和可选权威冲突快照。", + "type": "object", + "required": [ + "code", + "message", + "retryable" + ], + "properties": { + "code": { "type": "string", - "description": "当前 Agent Service 进程代际 ID;与恢复快照同源,重启即变化。" + "enum": [ + "invalidRequest", + "taskContextMismatch", + "taskContextUnavailable", + "unauthenticated", + "forbidden", + "boardNotFound", + "taskNotFound", + "commentNotFound", + "versionConflict", + "claimConflict", + "invalidTransition", + "dependencyBlocked", + "reviewRequired", + "reviewRoundConflict", + "commandAuthorityDenied", + "claimLivenessUnknown", + "claimStillActive", + "relationConflict", + "relationCycle", + "attachmentRejected", + "fanOutConflict", + "capabilityDisabled", + "capabilityUnavailable", + "migrationFailed", + "internalError" + ], + "description": "稳定错误码。" }, - "observedAt": { + "message": { "type": "string", - "format": "date-time", - "description": "服务端观察时刻(ISO 8601),客户端用于抵御本地墙钟回拨。" + "minLength": 1, + "description": "不含敏感数据的错误说明。" }, - "autoReplayRetentionMs": { - "type": "integer", - "minimum": 0, - "description": "客户端同 ID 自动重放窗口(毫秒)。" + "retryable": { + "type": "boolean", + "description": "客户端刷新权威状态后是否可以重试。" }, - "tombstoneRetentionMs": { - "type": "integer", - "minimum": 0, - "description": "服务端幂等 tombstone 保护窗口(毫秒);超出后查询结论不可信。" + "taskRef": { + "$ref": "#/components/schemas/TaskRef" }, - "results": { - "type": "array", - "description": "与请求一一对应(按 clientRequestId)。", - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "clientRequestId", - "status" - ], - "properties": { - "clientRequestId": { - "type": "string", - "description": "被查询的 clientRequestId。" - }, - "status": { - "type": "string", - "enum": [ - "committed", - "reserved", - "absent", - "unknown" - ], - "description": "committed=已持久提交(至多一次,原 ID 不会再执行);reserved=当前服务进程内占位中(排队或正在做提交前复检);absent=截至此刻既无提交也无占位(原请求可能仍在路上,必须同 ID 重发由服务端裁决);unknown=无法判定(旧版无作用域记录、存储不可读或注册表 fail closed)。" - }, - "committedAt": { - "type": "string", - "format": "date-time", - "description": "status=committed 时的持久提交时刻。" - }, - "sessionId": { - "type": "string" - }, - "committedServerInstance": { - "type": "string", - "enum": [ - "current", - "previous", - "unknown" - ] + "expectedVersion": { + "type": "integer", + "minimum": 0, + "description": "请求提交的 Task version 或 board revision。" + }, + "actualVersion": { + "type": "integer", + "minimum": 0, + "description": "服务端当前 Task version 或 board revision。" + }, + "capability": { + "type": "string", + "minLength": 1, + "description": "capabilityDisabled 或 capabilityUnavailable 对应的稳定能力 ID。" + }, + "claimLiveness": { + "$ref": "#/components/schemas/TaskClaimLiveness" + }, + "deniedAction": { + "type": "string", + "enum": [ + "assign", + "addContributor", + "removeContributor", + "addWatcher", + "removeWatcher", + "forceRelease", + "handoff", + "fanOut", + "assignReviewer", + "reviewAccept", + "reviewRequestChanges", + "addRelation", + "removeRelation", + "addAttachment", + "removeAttachment" + ], + "description": "commandAuthorityDenied 对应的责任动作。" + }, + "latest": { + "type": "object", + "required": [ + "boardRevision" + ], + "properties": { + "boardRevision": { + "type": "integer", + "minimum": 0, + "description": "冲突发生时的最新 TaskBoard revision。" + }, + "task": { + "$ref": "#/components/schemas/TaskBoardPublicTaskItem" + }, + "comment": { + "$ref": "#/components/schemas/TaskBoardPublicComment" + }, + "availableActions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskAvailableAction" }, - "executionWitness": { - "type": "string", - "enum": [ - "user-message", - "none", - "unknown" - ], - "description": "user-message=磁盘已有该提交的用户消息(执行过);none=没有;unknown=无法查证(缺 userMessageId 或存储不可读)。" - } + "description": "针对最新 Task 重新计算的可用动作。" } - } + }, + "additionalProperties": false, + "description": "versionConflict 时供客户端立即回滚/重绘的权威最小快照。" } - } + }, + "additionalProperties": false }, - "WebhookManagement": { + "TaskBoardArchiveAction": { + "description": "服务端为当前 principal 计算的 Board 归档/恢复动作;UI 不根据 owner 字段猜权限。", "type": "object", "required": [ "enabled", - "hasSecret", - "events", - "availableTools" + "archived", + "expectedBoardRevision" ], - "additionalProperties": false, "properties": { "enabled": { - "type": "boolean" + "type": "boolean", + "description": "当前 principal 是否可执行归档或恢复。" }, - "hasSecret": { + "archived": { "type": "boolean", - "description": "是否已有非空 secret;读取不返回其值。" + "description": "当前 Board 是否已归档;动作会切换到相反状态。" }, - "events": { - "type": "array", - "items": { - "type": "object", - "required": [ - "event", - "promptTemplate", - "maxTurns", - "toolIds" - ], - "additionalProperties": false, - "properties": { - "event": { - "type": "string" - }, - "promptTemplate": { - "type": "string" - }, - "maxTurns": { - "type": "number" - }, - "toolIds": { - "type": "array", - "items": { - "type": "string" - } - } - } - } + "expectedBoardRevision": { + "type": "integer", + "minimum": 1, + "description": "动作必须回传的 Board revision。" }, - "availableTools": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "name", - "riskLevel", - "requiresConfirmation" - ], - "additionalProperties": false, - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "riskLevel": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "requiresConfirmation": { - "type": "boolean" - } - } - } + "reason": { + "type": "string", + "minLength": 1, + "description": "不可用时的安全说明。" } - } + }, + "additionalProperties": false }, - "WebhookEventWrite": { + "TaskBoardScope": { + "description": "TaskBoard 的持久化和授权作用域。", "type": "object", "required": [ - "promptTemplate", - "toolIds" + "type", + "id" ], "properties": { - "promptTemplate": { + "type": { + "type": "string", + "enum": [ + "user", + "team", + "agent", + "session" + ], + "description": "TaskBoard 的授权作用域类型;global 仅是授权聚合视图,不是持久化作用域。" + }, + "id": { "type": "string", "minLength": 1, - "maxLength": 32768, - "pattern": "\\S", - "description": "事件提示模板,{{key}} 从 payload 的顶层字段取值。" + "description": "作用域稳定 ID:用户、团队、Agent 或 Session ID。" + } + }, + "additionalProperties": false + }, + "TaskBoardPolicy": { + "description": "控制状态、流转、认领、验收和能力边界的数据驱动策略。", + "type": "object", + "required": [ + "id", + "version", + "description", + "claimMode", + "review", + "statuses", + "transitions", + "capabilities" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "description": "策略稳定 ID。" }, - "toolIds": { + "version": { + "type": "integer", + "minimum": 1, + "description": "策略数据版本。" + }, + "description": { + "type": "string", + "minLength": 1, + "description": "策略目标与适用边界。" + }, + "claimMode": { + "type": "string", + "enum": [ + "singleActiveClaim" + ], + "description": "同一 Task 同时只允许一个活动 claim。" + }, + "review": { + "$ref": "#/components/schemas/TaskReviewPolicy" + }, + "statuses": { "type": "array", - "maxItems": 512, - "uniqueItems": true, + "minItems": 1, "items": { - "type": "string", - "minLength": 1, - "maxLength": 256 + "$ref": "#/components/schemas/TaskStatusDefinition" }, - "description": "从管理读取返回的 availableTools 中选择工具 ID;空数组表示不授权任何工具。" + "description": "由服务端返回、供通用 UI 渲染的状态定义。" }, - "maxTurns": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "description": "事件最多执行的模型轮次数,省略时为 5。", - "default": 5 + "transitions": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/TaskTransitionDefinition" + }, + "description": "服务端执行并返回给 UI 的状态流转规则。" + }, + "capabilities": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/TaskBoardCapability" + }, + "description": "当前版本真实可用或明确不可用的能力清单。" + }, + "projections": { + "$ref": "#/components/schemas/TaskBoardProjections" } }, "additionalProperties": false }, - "WebhookEventWritten": { + "ActorRef": { + "description": "记录事实执行者的稳定身份和可选展示快照。", "type": "object", "required": [ - "success", - "event", - "enabled" + "type", + "id" ], - "additionalProperties": false, "properties": { - "success": { - "const": true - }, - "event": { - "type": "string" + "type": { + "type": "string", + "enum": [ + "human", + "agent", + "system" + ], + "description": "事实执行者类型;授权必须按稳定 ID 重新判定,不能信任展示名称。" }, - "enabled": { - "const": true + "id": { + "type": "string", + "minLength": 1, + "description": "用户 ID、Agent ID 或系统主体 ID。" }, - "secret": { + "displayName": { "type": "string", - "description": "仅在本次首次生成 secret 时返回;保存修改已有事件不会再次返回。" + "minLength": 1, + "description": "写入事实时的展示名称快照,不参与身份唯一性或授权。" } - } + }, + "additionalProperties": false }, - "WebhookEnabledRequest": { + "TaskParticipantRef": { + "description": "可承担 Task 责任、参与或订阅的 human/agent 稳定身份;system 不能成为责任主体。", "type": "object", "required": [ - "enabled" + "type", + "id" ], - "additionalProperties": false, "properties": { - "enabled": { - "type": "boolean", - "description": "开启或暂停该智能体已有 Webhook 事件。" + "type": { + "type": "string", + "enum": [ + "human", + "agent" + ], + "description": "责任主体只能是 human 或 agent。" + }, + "id": { + "type": "string", + "minLength": 1, + "description": "用户 ID、Agent ID 或系统主体 ID。" + }, + "displayName": { + "type": "string", + "minLength": 1, + "description": "写入事实时的展示名称快照,不参与身份唯一性或授权。" } - } + }, + "additionalProperties": false }, - "WebhookEnabledResponse": { + "TaskClaim": { + "description": "Task 当前唯一活动认领事实。", "type": "object", "required": [ - "success", - "enabled" + "actor", + "epoch", + "claimedAt" ], - "additionalProperties": false, "properties": { - "success": { - "const": true + "actor": { + "$ref": "#/components/schemas/TaskParticipantRef" }, - "enabled": { - "type": "boolean" + "token": { + "type": "string", + "minLength": 16, + "description": "不可预测的活动 claim token;持久事实必填,但非 claim actor 的读投影必须省略。" + }, + "epoch": { + "type": "integer", + "minimum": 1, + "description": "每次重新认领递增的 claim 世代。" + }, + "claimedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "runRef": { + "$ref": "#/components/schemas/RunRef" + }, + "workContextRef": { + "$ref": "#/components/schemas/WorkContextRef" + }, + "liveness": { + "$ref": "#/components/schemas/TaskClaimLiveness" } - } + }, + "additionalProperties": false }, - "WebhookSecretRotation": { + "TaskAttachmentRef": { + "description": "MediaStore 拥有的安全附件引用;TaskBoard 不保存原始字节或本地路径。", "type": "object", "required": [ - "secret" + "mediaId", + "name", + "mimeType", + "sizeBytes" ], - "additionalProperties": false, "properties": { - "secret": { + "mediaId": { "type": "string", - "minLength": 43, - "maxLength": 43 + "minLength": 1, + "description": "MediaStore 中稳定且重新鉴权的媒体 ID。" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "写入时的安全文件名快照。" + }, + "mimeType": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "MediaStore 校验后的 MIME 快照。" + }, + "sizeBytes": { + "type": "integer", + "minimum": 0, + "description": "MediaStore 校验后的字节数快照。" + }, + "sha256": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "可选的内容完整性摘要。" } - } + }, + "additionalProperties": false }, - "ManagementError": { + "TaskApprovalRef": { + "description": "Approval domain 拥有的审批引用及可审计快照。", "type": "object", "required": [ - "error" + "approvalId" ], "properties": { - "error": { - "type": "string" + "approvalId": { + "type": "string", + "minLength": 1, + "description": "Approval owner 中的稳定 ID。" }, - "details": { - "type": "array", - "items": {} + "stateSnapshot": { + "type": "string", + "enum": [ + "pending", + "approved", + "rejected", + "expired", + "canceled" + ], + "description": "写入时的状态快照;执行 gate 必须回 Approval owner 重新读取。" } }, "additionalProperties": false }, - "ScheduleMessage": { + "RunRef": { + "description": "引用 RunManager 拥有的执行事实。", "type": "object", - "additionalProperties": false, - "description": "完整的可见调度消息契约;授权快照、正文、附件引用和收件人均接受运行时验证。", "required": [ - "id", - "scheduleId", - "authorizationScope", - "text", - "isError", - "timestamp" + "runId" ], "properties": { - "id": { + "runId": { "type": "string", "minLength": 1, - "maxLength": 512, - "description": "可信结果路由给出的稳定、非授权标识。" + "description": "RunManager 拥有的稳定 Run ID。" }, - "recipientUserId": { + "sessionId": { "type": "string", "minLength": 1, - "maxLength": 512, - "description": "可信结果路由给出的稳定、非授权标识。" - }, - "origin": { - "enum": [ - "schedule", - "webhook" - ], - "description": "旧记录未声明时按 schedule 显示。" + "description": "Run 所属 Session ID,便于授权后跳转。" }, - "scheduleId": { + "agentId": { "type": "string", - "description": "所属调度标识;不是执行授权。" + "minLength": 1, + "description": "执行 Run 的 Agent ID 快照。" }, - "occurrenceId": { + "clientRequestId": { "type": "string", - "description": "结果所属稳定发生标识,不能单凭它重新执行。" - }, - "authorizationScope": { - "oneOf": [ - { - "type": "object", - "additionalProperties": false, - "required": [ - "kind" - ], - "properties": { - "kind": { - "const": "agent", - "description": "Agent 作用域;结果还需精确收件人校验。" - } - } - }, - { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "boardId", - "bindingId" - ], - "properties": { - "kind": { - "const": "taskboard", - "description": "保持原 TaskBoard 来源 ACL,不能退化为普通 Agent 作用域。" - }, - "boardId": { - "type": "string", - "description": "来源 TaskBoard 标识。" - }, - "bindingId": { - "type": "string", - "description": "冻结的资源绑定标识。" - } - } - } - ], - "description": "不可变来源授权快照;读取和清除都重新核验当前权限。" + "minLength": 1, + "description": "工具调用起点冻结的轮次 ID;Run/Session 可复用,禁止从其最新状态反推。旧记录可缺失。" }, - "displayName": { + "sourceMessageId": { "type": "string", - "description": "可选任务显示名。" - }, - "text": { + "minLength": 1, + "description": "同次调用冻结的已持久化用户消息锚点;旧记录或未持久化消息可缺失。" + } + }, + "additionalProperties": false + }, + "ReceiptRef": { + "description": "引用 Receipt owner 拥有的执行回执。", + "type": "object", + "required": [ + "receiptId" + ], + "properties": { + "receiptId": { "type": "string", - "description": "受存储边界截断的可见摘要,不是执行指令。" - }, - "isError": { - "type": "boolean", - "description": "是否为失败结果;不会改变脚本真实终态。" - }, - "timestamp": { - "type": "number", - "description": "冻结的原交付时间,重放不得使用当前时间替换。" + "minLength": 1, + "description": "Receipt owner 中的稳定回执 ID。" }, - "delivery": { - "type": "object", - "additionalProperties": false, - "required": [ - "message", - "status", - "sentAt" - ], - "description": "既有 SendUserMessage 用户交付形状,仅保存附件引用。", - "properties": { - "message": { - "type": "string", - "description": "受存储边界截断的用户交付正文。" - }, - "status": { - "enum": [ - "normal", - "proactive" - ], - "description": "普通回复或主动告知。" - }, - "sentAt": { - "type": "string", - "description": "工具侧原交付时刻。" - }, - "warnings": { - "type": "array", - "items": { - "type": "string" - }, - "description": "附件解析降级告警。" - }, - "attachments": { - "type": "array", - "description": "引用型附件,不存二进制正文。", - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "name", - "mimeType", - "size", - "isImage" - ], - "properties": { - "path": { - "type": "string", - "description": "已解析的附件路径,读取仍受资源准入限制。" - }, - "name": { - "type": "string", - "description": "附件文件名。" - }, - "mimeType": { - "type": "string", - "description": "附件 MIME 类型。" - }, - "size": { - "type": "number", - "description": "附件字节数。" - }, - "isImage": { - "type": "boolean", - "description": "是否为图片。" - }, - "mediaId": { - "type": "string", - "description": "可选媒体存储引用。" - }, - "width": { - "type": "number", - "description": "可选图片宽度。" - }, - "height": { - "type": "number", - "description": "可选图片高度。" - } - } - } - } - } + "runId": { + "type": "string", + "minLength": 1, + "description": "生成该回执的 Run ID 快照。" } - } + }, + "additionalProperties": false }, - "ScheduleMessagePage": { + "WorkContextRef": { + "description": "引用 Context/Session domain 拥有的 Work Context。", "type": "object", "required": [ - "items", - "total", - "limit", - "offset" + "workContextId" ], "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ScheduleMessage" - } - }, - "total": { - "type": "integer", - "minimum": 0 + "workContextId": { + "type": "string", + "minLength": 1, + "description": "Context/Session domain 拥有的稳定 Work Context ID。" }, - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 200 + "agentId": { + "type": "string", + "minLength": 1, + "description": "该 Work Context 的目标 Agent ID 快照。" }, - "offset": { - "type": "integer", - "minimum": 0 + "mode": { + "type": "string", + "enum": [ + "continue", + "isolated", + "fork" + ], + "description": "创建该上下文时显式选择的模式;V2 不提供默认值。" } }, "additionalProperties": false }, - "AttachmentReceipt": { - "$schema": "http://json-schema.org/draft-07/schema#", + "TaskSourceRef": { + "description": "外部 owner 中产生此 Task 的稳定来源身份;TaskBoard 仅用于幂等、筛选和跳转。", "type": "object", - "additionalProperties": false, "required": [ - "attachmentId", - "name", - "mimeType", - "size", - "sha256", - "fileReference" + "ownerType", + "ownerId", + "sourceKey" ], "properties": { - "attachmentId": { + "ownerType": { "type": "string", - "format": "uuid", - "description": "服务端随机签发的附件ID。" + "enum": [ + "automation", + "workflow", + "conversation", + "connector", + "import", + "manual" + ], + "description": "来源 owner 类型;manual 表示由用户或 Agent 通过公开 Task API 创建。" }, - "name": { + "ownerId": { "type": "string", "minLength": 1, - "maxLength": 240, - "description": "不含路径分隔符/控制符的显示文件名。" + "description": "来源 owner 中的稳定资源 ID。" }, - "mimeType": { + "sourceKey": { "type": "string", "minLength": 1, - "maxLength": 128, - "description": "上传时的内容类型,只作描述,下载禁止嗅探执行。" + "maxLength": 512, + "description": "owner 作用域内稳定幂等键;相同 ownerType/ownerId/sourceKey 不应创建第二个 Task。" }, - "size": { - "type": "integer", - "minimum": 1, - "maximum": 20971520, - "description": "完整已持久化的字节数。" + "occurrenceAt": { + "type": "string", + "format": "date-time", + "description": "周期或外部事件的 nominal occurrence 时间;不是 TaskBoard 调度时间。" }, - "sha256": { + "sourceUrl": { "type": "string", - "pattern": "^[0-9a-f]{64}$", - "description": "文件内容摘要;未完成上传不返回成功。" + "format": "uri", + "pattern": "^https?://", + "maxLength": 4096, + "description": "来源对象的安全 HTTP(S) 深链;只用于导航,不参与来源身份、授权或幂等。" }, - "fileReference": { + "label": { "type": "string", "minLength": 1, - "maxLength": 2048, - "description": "Server-side file reference. Pass unchanged to options.fileReferences of a Query start request on the same instance; it is not a public URL or a local client path." + "maxLength": 256, + "description": "安全展示快照,不参与授权。" } - } + }, + "additionalProperties": false }, - "AttachmentMediaError": { + "TaskAutomationBindingRef": { + "description": "Automation/Scheduler owner 的 typed resource binding;TaskBoard 不保存 cron、RRULE、重试或执行历史。", "type": "object", "required": [ - "error" + "agentId", + "scheduleId" ], "properties": { - "error": { - "type": "string" + "agentId": { + "type": "string", + "minLength": 1, + "description": "Scheduler 中拥有该 Schedule 的稳定 Agent ID。" }, - "message": { - "type": "string" + "scheduleId": { + "type": "string", + "minLength": 1, + "description": "Scheduler owner 中的稳定 Schedule ID。" }, - "code": { - "type": "string" + "automationRecipeId": { + "type": "string", + "minLength": 1, + "description": "可选的 Automation recipe/template ID;不是第二份 Schedule identity。" + }, + "bindingId": { + "type": "string", + "minLength": 1, + "description": "Automation owner 中绑定此 Task/Board 的稳定 binding ID。" + }, + "occurrenceAt": { + "type": "string", + "format": "date-time", + "description": "产生当前 Task 的 nominal occurrence 时间;真正 next/last run 从 owner 实时读取。" } }, - "description": "Route error. Framework, origin, readiness, and proxy errors may carry additional fields; there is no universal response wrapper." + "additionalProperties": false }, - "MediaMetadata": { + "TaskWorkflowRef": { + "description": "Workflow Service 拥有的定义或运行引用。", "type": "object", "required": [ - "type", - "mediaId", - "name", - "mimeType", - "size" + "workflowId" ], "properties": { - "type": { + "workflowId": { "type": "string", - "enum": [ - "image", - "audio", - "video" - ] + "minLength": 1, + "description": "Workflow Service 中的稳定 Workflow ID。" }, - "mediaId": { + "workflowRunId": { "type": "string", - "pattern": "^[a-f0-9-]+\\.\\w+$", - "description": "Service-issued media filename. Values containing two consecutive dots are rejected." - }, - "name": { - "type": "string" + "minLength": 1, + "description": "可选的稳定 Workflow Run ID。" }, - "mimeType": { + "nodeId": { "type": "string", - "enum": [ - "image/jpeg", - "image/png", - "image/gif", - "image/webp", - "audio/mpeg", - "audio/mp3", - "audio/opus", - "audio/aac", - "audio/flac", - "audio/wav", - "audio/ogg", - "video/mp4", - "video/webm" - ] - }, - "size": { - "type": "integer", - "minimum": 1, - "maximum": 2147483648 - }, - "width": { - "type": "number" - }, - "height": { - "type": "number" + "minLength": 1, + "description": "可选的 Workflow 节点 ID 快照。" } }, - "description": "Direct upload response, manually represented from MediaMeta and saveAuthorizedMediaRaw. Dimensions are optional; no additional response wrapper." + "additionalProperties": false }, - "MediaAccessUrlsRequest": { + "TaskConversationRef": { + "description": "Conversation/Run owner 中与 Task 协作相关的稳定会话引用。", "type": "object", "required": [ - "mediaIds" + "conversationId" ], - "additionalProperties": false, "properties": { - "mediaIds": { - "type": "array", - "minItems": 1, - "maxItems": 100, - "items": { - "type": "string" - }, - "description": "Duplicates removed in first-occurrence order; IDs are validated by the route." + "conversationId": { + "type": "string", + "minLength": 1, + "description": "Conversation owner 中的稳定 Conversation ID。" + }, + "agentId": { + "type": "string", + "minLength": 1, + "description": "会话当前 Agent ID 快照,仅用于展示和跳转提示。" + }, + "runId": { + "type": "string", + "minLength": 1, + "description": "可选的关联 Run ID;Run 生命周期仍由 RunManager 拥有。" } - } + }, + "additionalProperties": false }, - "MediaAccessUrlsResponse": { + "TaskWorkDirRef": { + "description": "WorkDir owner 中的稳定工作目录引用;TaskBoard 不管理目录或 worktree 生命周期。", "type": "object", "required": [ - "items", - "missing" + "workdirId", + "ownerScope" ], "properties": { - "items": { - "type": "array", - "items": { - "type": "object", - "required": [ - "mediaId", - "url", - "expiresAt" - ], - "properties": { - "mediaId": { - "type": "string", - "pattern": "^[a-f0-9-]+\\.\\w+$", - "description": "Service-issued media filename. Values containing two consecutive dots are rejected." - }, - "url": { - "type": "string", - "description": "Service-relative URL with expires and signature. Use only the actual returned link." - }, - "expiresAt": { - "type": "string", - "format": "date-time" - } + "workdirId": { + "type": "string", + "minLength": 1, + "description": "WorkDir owner 中的稳定 ID。" + }, + "ownerScope": { + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "agent", + "team" + ], + "description": "WorkDir 配置的 owner scope。" + }, + "id": { + "type": "string", + "minLength": 1, + "description": "用户、Agent 或 Team 的稳定 owner ID。" } - } + }, + "additionalProperties": false, + "description": "解析 workdirId 所需的 owner scope;不包含绝对路径。" }, - "missing": { - "type": "array", - "items": { - "type": "string" - } + "label": { + "type": "string", + "minLength": 1, + "maxLength": 256, + "description": "授权写入时的安全展示快照。" + }, + "repositoryId": { + "type": "string", + "minLength": 1, + "description": "可选的 CodeRepo owner 稳定仓库 ID。" } }, - "description": "Missing files do not prevent other readable IDs from receiving links. Any ownership failure for an existing object rejects the request rather than returning partial items." + "additionalProperties": false }, - "HistoryError": { + "TaskBoardMentionOccurrence": { "type": "object", - "properties": { - "error": { - "type": "string" - }, - "code": { - "type": "string" - } - }, "required": [ - "error" + "schemaVersion", + "occurrenceId", + "target", + "source", + "range", + "actor", + "createdAt" ], - "additionalProperties": true - }, - "HistorySessionSummary": { - "type": "object", "properties": { - "runId": { - "type": "string" - }, - "agentId": { - "type": "string" + "schemaVersion": { + "type": "integer", + "const": 1, + "description": "Mention occurrence 格式版本。" }, - "status": { + "occurrenceId": { "type": "string", - "enum": [ - "idle", - "completed", - "failed", - "handed_off" - ], - "description": "Historical display status, not live Run execution state or a request terminal." - }, - "messageCount": { - "type": "number" - }, - "createdAt": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "preview": { - "type": "string" - }, - "archivedAt": { - "type": "string" + "minLength": 8, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$", + "description": "当前 Task description/comment revision 内稳定的 occurrence ID。" }, - "forkedFrom": { - "type": "string" + "target": { + "type": "object", + "required": [ + "agentId", + "displayNameSnapshot" + ], + "properties": { + "agentId": { + "type": "string", + "minLength": 1, + "description": "Registry 中稳定的 Agent ID。" + }, + "displayNameSnapshot": { + "type": "string", + "minLength": 1, + "description": "仅用于展示的名称快照。" + } + }, + "additionalProperties": false, + "description": "canonical recipient Agent 快照。" }, - "conversationId": { - "type": "string" + "source": { + "$ref": "#/components/schemas/TaskBoardMentionSourceRef" }, - "title": { - "type": "string" + "range": { + "type": "object", + "required": [ + "start", + "end" + ], + "properties": { + "start": { + "type": "integer", + "minimum": 0, + "description": "UTF-16 半开区间起点。" + }, + "end": { + "type": "integer", + "minimum": 1, + "description": "UTF-16 半开区间终点。" + } + }, + "additionalProperties": false, + "description": "token 在正文中的 UTF-16 半开区间。" }, - "titleSource": { - "type": "string", - "enum": [ - "custom", - "auto" - ] + "actor": { + "type": "object", + "required": [ + "type", + "id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "human", + "agent", + "system" + ], + "description": "来源写入 actor 类型。" + }, + "id": { + "type": "string", + "minLength": 1, + "description": "来源写入 actor 稳定 ID。" + }, + "displayNameSnapshot": { + "type": "string", + "minLength": 1, + "description": "可选展示名称快照。" + } + }, + "additionalProperties": false, + "description": "TaskBoard 对外公开的来源 actor。" }, - "origin": { + "createdAt": { "type": "string", - "enum": [ - "user", - "delegation" - ] - }, - "background": { - "type": "boolean" - }, - "delegated": { - "type": "boolean" + "format": "date-time", + "description": "occurrence 创建时间。" } }, - "required": [ - "runId", - "agentId", - "status", - "messageCount", - "createdAt", - "updatedAt" - ], - "additionalProperties": true, - "description": "Known SessionSummary fields from sessionSummarySchema. This HTTP projection is intentionally open." + "additionalProperties": false, + "description": "TaskBoard read DTO 中的 camelCase Mention occurrence。" }, - "HistoryMessage": { + "TaskReviewState": { + "description": "Task 当前验收轮次、提交和决定。", "type": "object", + "required": [ + "state", + "round" + ], "properties": { - "id": { - "type": "string" - }, - "role": { + "state": { "type": "string", "enum": [ - "user", - "assistant", - "system" - ] - }, - "content": { - "description": "Display content; commonly a string or content-block array. Do not reconstruct Provider protocol from it." - }, - "timestamp": { - "type": "number", - "description": "Unix milliseconds when present." + "notSubmitted", + "awaitingReview", + "accepted", + "changesRequested" + ], + "description": "当前验收事实;Task status 仍是列表和流转主状态。" }, - "lineIndex": { + "round": { "type": "integer", "minimum": 0, - "description": "Source row position, not stable identity." + "description": "提交验收的轮次;每次重新提交递增。" }, - "offset": { + "submission": { + "$ref": "#/components/schemas/TaskSubmission" + }, + "decision": { + "$ref": "#/components/schemas/TaskReviewDecision" + }, + "stageIndex": { "type": "integer", "minimum": 0, - "description": "Byte position, not a pagination query offset." + "description": "当前 multi-stage policy 中的 stage 索引。" }, - "toolUse": { + "assignment": { + "$ref": "#/components/schemas/TaskReviewAssignment" + }, + "history": { "type": "array", "items": { - "type": "object", + "$ref": "#/components/schemas/TaskReviewHistoryEntry" + }, + "description": "已结束 stage/round 的审计投影;不代替 append-only Task event。" + } + }, + "allOf": [ + { + "if": { "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "summary": { - "type": "string", - "description": "Display summary. Sensitive results may be replaced by a placeholder; running/interrupted summaries may be absent. Do not parse outcome or replay instructions from this text." - }, - "summaryRef": { - "type": "object", - "properties": { - "filename": { - "type": "string" - } - }, - "additionalProperties": true, - "description": "Optional body reference for public output. Withheld results omit this reference; its absence does not mean the tool did not run." - }, - "isError": { - "type": "boolean", - "description": "When present, preserve the actual tool error fact even if its summary is withheld." - }, - "status": { - "type": "string", - "description": "Tool-call state when present. Preserve this independently from display summaries; it is not the Query terminal or a child Run/Worker status." + "state": { + "const": "notSubmitted" } }, - "additionalProperties": true + "required": [ + "state" + ] + }, + "then": { + "properties": { + "round": { + "const": 0 + } + }, + "not": { + "anyOf": [ + { + "required": [ + "submission" + ] + }, + { + "required": [ + "decision" + ] + }, + { + "required": [ + "stageIndex" + ] + }, + { + "required": [ + "assignment" + ] + } + ] + } } }, - "images": { - "type": "array", - "items": {} - }, - "media": { - "type": "array", - "items": {} - }, - "file": {}, - "thinking": { - "type": "string" - }, - "mentionSourceOnly": { - "type": "boolean", - "description": "true marks a Conversation source without current-Agent Run ownership. Display only; do not feed into model replay." - }, - "mentionSource": { - "type": "object", - "description": "Conversation-owned Mention source reference, when present.", - "additionalProperties": true + { + "if": { + "properties": { + "state": { + "const": "awaitingReview" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "submission" + ], + "properties": { + "round": { + "minimum": 1 + } + }, + "not": { + "required": [ + "decision" + ] + } + } }, - "mentionOccurrences": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": true + { + "if": { + "properties": { + "state": { + "const": "accepted" + } + }, + "required": [ + "state" + ] }, - "description": "Structured Mention occurrences, when present." + "then": { + "required": [ + "submission", + "decision" + ], + "properties": { + "round": { + "minimum": 1 + }, + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TaskReviewDecision" + }, + { + "properties": { + "outcome": { + "const": "accepted" + } + } + } + ] + } + }, + "not": { + "required": [ + "assignment" + ] + } + } }, - "mentionRecipientAgentIds": { - "type": "array", - "items": { - "type": "string" + { + "if": { + "properties": { + "state": { + "const": "changesRequested" + } + }, + "required": [ + "state" + ] }, - "uniqueItems": true + "then": { + "required": [ + "submission", + "decision" + ], + "properties": { + "round": { + "minimum": 1 + }, + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TaskReviewDecision" + }, + { + "properties": { + "outcome": { + "const": "changesRequested" + } + } + } + ] + } + }, + "not": { + "required": [ + "assignment" + ] + } + } }, - "mentionDeliveryIds": { - "type": "array", - "items": { - "type": "string" + { + "if": { + "required": [ + "assignment" + ] }, - "uniqueItems": true, - "description": "Delivery references, not proof of successful execution." + "then": { + "properties": { + "state": { + "const": "awaitingReview" + } + }, + "required": [ + "submission" + ] + } } - }, - "additionalProperties": true, - "description": "Known fields of the public SessionMessage projection, derived from sessionMessageSchema. Runtime-private recovery fields are not part of this contract. Additional public fields remain allowed. Sensitive tool results and unverifiable protocol results may be withheld; privacy placeholders are display text, not execution outcomes. Body references may be omitted." + ], + "additionalProperties": false }, - "HistoryLiveInspectionTool": { + "TaskCommentRevision": { + "description": "Comment 的不可变正文 revision;最新投影仍保留在 TaskComment.body。", "type": "object", + "required": [ + "version", + "body", + "actor", + "occurredAt" + ], "properties": { - "id": { - "type": "string", - "maxLength": 256 + "version": { + "type": "integer", + "minimum": 1, + "description": "Comment revision 版本,从 1 单调递增。" }, - "name": { + "body": { "type": "string", - "maxLength": 256 + "minLength": 1, + "maxLength": 65536, + "description": "此 revision 的完整正文。" }, - "isError": { - "type": "boolean" + "actor": { + "$ref": "#/components/schemas/ActorRef" }, - "activity": { - "description": "Optional bounded public Tool activity; no Tool input or output." - } - }, - "required": [ - "id", - "name", - "isError" - ], - "additionalProperties": true - }, - "HistoryLiveInspection": { - "type": "object", - "properties": { - "turnId": { + "occurredAt": { "type": "string", - "maxLength": 256 + "format": "date-time", + "description": "ISO 8601 日期时间。" }, - "text": { + "reason": { "type": "string", - "maxLength": 16384 + "minLength": 1, + "maxLength": 2000, + "description": "可选修订原因。" }, - "toolCalls": { + "attachmentRefs": { "type": "array", - "maxItems": 128, + "uniqueItems": true, "items": { - "$ref": "#/components/schemas/HistoryLiveInspectionTool" - } - }, - "timestamp": { - "type": "integer", - "minimum": 0 + "$ref": "#/components/schemas/TaskAttachmentRef" + }, + "description": "此 revision 引用的 MediaStore owner-safe 附件快照。" } }, - "required": [ - "text", - "toolCalls", - "timestamp" - ], - "additionalProperties": true, - "description": "Temporary delegated-Run display checkpoint. It is not durable history, a receipt or proof of completion; replace it instead of appending it to messages." + "additionalProperties": false }, - "HistorySessionSnapshot": { + "TaskEvent": { + "description": "TaskBoard 原子提交产生的 append-only 审计事件。", "type": "object", + "required": [ + "schemaVersion", + "eventId", + "boardId", + "boardRevision", + "type", + "actor", + "occurredAt", + "data" + ], "properties": { - "runId": { - "type": "string" - }, - "agentId": { - "type": "string" - }, - "userId": { - "type": "string" + "schemaVersion": { + "type": "integer", + "const": 3, + "description": "Task event 数据格式版本。" }, - "engine": { + "eventId": { "type": "string", - "enum": [ - "desirecore-engine" - ] + "minLength": 1, + "description": "幂等且稳定的事件 ID。" }, - "messages": { - "type": "array", - "items": { - "$ref": "#/components/schemas/HistoryMessage" - } + "boardId": { + "type": "string", + "minLength": 1, + "description": "事件所属 TaskBoard ID。" }, - "options": { - "type": "object", - "additionalProperties": true, - "description": "Display/recovery configuration. Returned configuration grants no execution authority." + "boardRevision": { + "type": "integer", + "minimum": 1, + "description": "提交此事件后的 TaskBoard revision。" }, - "status": { + "type": { "type": "string", "enum": [ - "idle", - "completed", - "failed", - "handed_off" + "boardCreated", + "boardArchived", + "boardRestored", + "boardPolicyChanged", + "taskCreated", + "taskArchived", + "taskRestored", + "taskUpdated", + "taskPublished", + "taskClaimed", + "taskReleased", + "taskSubmitted", + "reviewAccepted", + "reviewChangesRequested", + "taskFailed", + "taskCanceled", + "taskReopened", + "taskDeleted", + "commentAdded", + "commentRevised", + "commentRedacted", + "mentionDispatched", + "taskAssigned", + "contributorAdded", + "contributorRemoved", + "watcherAdded", + "watcherRemoved", + "claimForceReleased", + "taskHandedOff", + "taskFannedOut", + "reviewerAssigned", + "reviewStageAccepted", + "reviewStageChangesRequested", + "taskRelationAdded", + "taskRelationRemoved", + "taskAttachmentAdded", + "taskAttachmentRemoved", + "taskResourceBound", + "taskResourceReplaced", + "taskResourceRemoved", + "taskApprovalBound", + "taskApprovalUnbound", + "workflowTriggerReserved", + "workflowTriggerMaterialized", + "workflowTriggerAcknowledged" ], - "description": "Historical display status, not live Run execution state or a request terminal." - }, - "createdAt": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "conversationId": { - "type": "string" - }, - "forkedFrom": { - "type": "string" - }, - "teamId": { - "type": "string" + "description": "事件事实类型。" }, - "workDir": { - "type": "string" + "taskRef": { + "$ref": "#/components/schemas/TaskRef" }, - "worktreeCwd": { - "type": "string" + "commentId": { + "type": "string", + "minLength": 1, + "description": "关联 comment ID。" }, - "pendingAssistant": { - "$ref": "#/components/schemas/HistoryMessage" + "taskVersion": { + "type": "integer", + "minimum": 1, + "description": "提交此事件后的 Task version。" }, - "runtimeSession": { + "runRef": { + "description": "本次写入起点由可信 runtime 冻结的 Run/轮次来源;与任务原子提交,不代表 claim 授权。旧事件可缺失。", "type": "object", + "required": [ + "runId" + ], "properties": { - "sessionId": { - "type": "string" + "runId": { + "type": "string", + "minLength": 1, + "description": "RunManager 拥有的稳定 Run ID。" }, - "revision": { - "type": "integer", - "minimum": 1 + "sessionId": { + "type": "string", + "minLength": 1, + "description": "Run 所属 Session ID,便于授权后跳转。" }, - "scopeEpoch": { - "type": "integer", - "minimum": 0 + "agentId": { + "type": "string", + "minLength": 1, + "description": "执行 Run 的 Agent ID 快照。" }, - "scopeSequence": { - "type": "integer", - "minimum": 0 + "clientRequestId": { + "type": "string", + "minLength": 1, + "description": "工具调用起点冻结的轮次 ID;Run/Session 可复用,禁止从其最新状态反推。旧记录可缺失。" }, - "boundModel": { - "type": "object", - "additionalProperties": true + "sourceMessageId": { + "type": "string", + "minLength": 1, + "description": "同次调用冻结的已持久化用户消息锚点;旧记录或未持久化消息可缺失。" } }, + "additionalProperties": false + }, + "actor": { + "$ref": "#/components/schemas/ActorRef" + }, + "occurredAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "data": { + "type": "object", + "description": "事件类型特有的非敏感事实;稳定身份和 owner 事实使用顶层引用。", "additionalProperties": true } }, - "required": [ - "runId", - "agentId", - "userId", - "engine", - "messages", - "options", - "status", - "createdAt", - "updatedAt" + "allOf": [ + { + "if": { + "required": [ + "type" + ], + "properties": { + "type": { + "const": "boardPolicyChanged" + } + } + }, + "then": { + "properties": { + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "expectedBoardRevision", + "previousPolicy", + "nextPolicy" + ], + "properties": { + "expectedBoardRevision": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "调用者观察的原Board正安全整数revision;不自动替换为latest。" + }, + "previousPolicy": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "version", + "hash" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "原注册策略族ID,不是新owner或权限。" + }, + "version": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "此不可变策略快照的版本。" + }, + "hash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "完整不可变policy的canonical JSON SHA-256;不只比较id/version。" + } + }, + "description": "策略快照的窄审计身份;完整快照仍在同一canonical Board revision中。" + }, + "nextPolicy": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "version", + "hash" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "原注册策略族ID,不是新owner或权限。" + }, + "version": { + "type": "integer", + "minimum": 1, + "maximum": 9007199254740991, + "description": "此不可变策略快照的版本。" + }, + "hash": { + "type": "string", + "pattern": "^[a-f0-9]{64}$", + "description": "完整不可变policy的canonical JSON SHA-256;不只比较id/version。" + } + }, + "description": "策略快照的窄审计身份;完整快照仍在同一canonical Board revision中。" + } + }, + "description": "boardPolicyChanged原子审计数据;actor/时间/事件ID沿TaskEvent来源,不由请求自报。" + }, + "actor": { + "type": "object", + "properties": { + "type": { + "const": "human" + } + } + } + }, + "not": { + "anyOf": [ + { + "required": [ + "taskRef" + ] + }, + { + "required": [ + "taskVersion" + ] + }, + { + "required": [ + "commentId" + ] + } + ] + } + } + } ], - "additionalProperties": true, - "description": "Known public fields derived from sessionSnapshotSchema and the public session projection; additional public fields are allowed. Images may be expanded to data URLs. Tool summaries and result blocks may be replaced by privacy placeholders with body references removed." + "additionalProperties": false }, - "HistorySessionPage": { + "TaskActivityProjection": { + "description": "非 TaskBoard owner 的授权活动摘要;原 owner 事实不复制到 canonical commit。", "type": "object", + "required": [ + "ownerType", + "ownerId", + "occurrenceId", + "kind", + "occurredAt", + "summary" + ], "properties": { - "sessions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/HistorySessionSummary" - } + "ownerType": { + "type": "string", + "enum": [ + "automation", + "workflow", + "conversation", + "run", + "workContext", + "approval", + "notification" + ], + "description": "投影事实的权威 owner。" }, - "total": { - "type": "integer", - "minimum": 0 + "ownerId": { + "type": "string", + "minLength": 1, + "description": "owner 中稳定资源 ID。" }, - "hasMore": { - "type": "boolean" + "occurrenceId": { + "type": "string", + "minLength": 1, + "description": "owner 中稳定事件/运行/发生 ID。" + }, + "kind": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "owner 定义的安全活动类型。" + }, + "occurredAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" + }, + "summary": { + "type": "string", + "minLength": 1, + "maxLength": 4000, + "description": "通过 owner 授权后的安全摘要。" + }, + "deepLink": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "授权后可访问的 owner 内部深链。" } }, - "required": [ - "sessions", - "total", - "hasMore" - ], - "additionalProperties": true + "additionalProperties": false }, - "HistoryMessagePage": { + "TaskClaimLiveness": { + "description": "RunManager/Agent Activity 在读取时给出的 claim liveness 投影;TaskBoard 不成为真源。", "type": "object", + "required": [ + "state", + "checkedAt", + "source" + ], "properties": { - "messages": { - "type": "array", - "items": { - "$ref": "#/components/schemas/HistoryMessage" - } + "state": { + "type": "string", + "enum": [ + "alive", + "terminal", + "unknown" + ], + "description": "alive=权威 owner 确认活跃,terminal=确认终止,unknown=无法安全判定。" }, - "total": { - "type": "integer", - "minimum": 0, - "description": "Source-record row count. Omitted or damaged rows mean returned messages can be fewer than the requested window." + "checkedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" }, - "live": { - "$ref": "#/components/schemas/HistoryLiveInspection" + "source": { + "type": "string", + "enum": [ + "runManager", + "agentActivity", + "noRuntimeRef" + ], + "description": "本次校验使用的权威 owner 或明确无 Runtime 引用。" + }, + "reason": { + "type": "string", + "minLength": 1, + "description": "安全的非敏感判定说明。" } }, - "required": [ - "messages", - "total" - ], - "additionalProperties": true + "additionalProperties": false }, - "HistorySearchHit": { + "TaskReviewPolicy": { + "description": "Task 提交结果后的验收策略。", "type": "object", + "required": [ + "mode", + "reviewer", + "changesRequestedStatus" + ], "properties": { - "runId": { - "type": "string" - }, - "messageIndex": { - "type": "integer", - "minimum": 0, - "description": "Source row position within this Run." - }, - "offset": { - "type": "integer", - "minimum": 0, - "description": "Byte position, not the next search-page offset." - }, - "messageId": { - "type": "string" - }, - "role": { + "mode": { "type": "string", "enum": [ - "user", - "assistant", - "system" - ] - }, - "text": { - "type": "string" - }, - "timestamp": { - "type": "number" - }, - "hasImages": { - "type": "boolean", - "description": "Based on legacy images or media fields; not proof that every attachment is an image." + "none", + "agentReview", + "supervisorReview", + "humanAcceptance", + "approvalRef", + "multiStage" + ], + "description": "默认/legacy 审核模式;multiStage 时以 stages 为权威顺序。" }, - "hasFiles": { - "type": "boolean" + "reviewer": { + "type": "string", + "enum": [ + "none", + "explicitActor", + "teamSupervisor", + "ownerUser", + "approvalOwner", + "stageDefined" + ], + "description": "默认/legacy reviewer 解析策略;multiStage 使用 stageDefined。" }, - "hasToolUse": { - "type": "boolean" + "changesRequestedStatus": { + "type": "string", + "enum": [ + "pending", + "inProgress" + ], + "description": "返工后 Task 回到的状态;legacy 允许 inProgress,PR4 新 policy 必须使用 pending。" }, - "toolNames": { + "stages": { "type": "array", + "minItems": 1, + "maxItems": 8, "items": { - "type": "string" - } - }, - "conversationId": { - "type": "string" + "$ref": "#/components/schemas/TaskReviewStagePolicy" + }, + "description": "multiStage 按顺序执行的审核 gate;每个 stage 都重新鉴权。" } }, - "required": [ - "runId", - "messageIndex", - "offset", - "messageId", - "role", - "text", - "timestamp", - "hasImages", - "hasFiles", - "hasToolUse" + "allOf": [ + { + "if": { + "properties": { + "mode": { + "const": "multiStage" + } + }, + "required": [ + "mode" + ] + }, + "then": { + "required": [ + "stages" + ], + "properties": { + "reviewer": { + "const": "stageDefined" + }, + "changesRequestedStatus": { + "const": "pending" + } + } + } + } ], - "additionalProperties": true + "additionalProperties": false }, - "HistorySearchPage": { + "TaskStatusDefinition": { + "description": "供 policy 和通用 UI 共用的状态自描述数据。", "type": "object", + "required": [ + "status", + "label", + "description", + "category" + ], "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/HistorySearchHit" - } + "status": { + "type": "string", + "enum": [ + "backlog", + "pending", + "inProgress", + "blocked", + "inReview", + "completed", + "failed", + "canceled", + "deleted" + ], + "description": "状态机中的稳定状态值。" }, - "total": { - "type": "integer", - "minimum": 0 + "label": { + "type": "string", + "minLength": 1, + "description": "由服务端策略提供的默认展示名称。" }, - "hasMore": { - "type": "boolean" + "description": { + "type": "string", + "minLength": 1, + "description": "该状态代表的事实语义。" + }, + "category": { + "type": "string", + "enum": [ + "notStarted", + "active", + "review", + "terminal", + "hidden" + ], + "description": "用于通用视图分组的状态类别,不替代具体状态。" } }, - "required": [ - "items", - "total", - "hasMore" - ], - "additionalProperties": true + "additionalProperties": false }, - "ConversationMatterOverview": { + "TaskTransitionDefinition": { + "description": "Task 状态机中的一个服务端执行流转。", "type": "object", + "required": [ + "action", + "from", + "to", + "actorTypes", + "description" + ], "properties": { - "agentId": { - "type": "string" + "action": { + "type": "string", + "enum": [ + "create", + "update", + "publish", + "claim", + "release", + "submitForReview", + "accept", + "requestChanges", + "fail", + "cancel", + "reopen", + "delete", + "addComment" + ], + "description": "触发此流转的稳定动作。" }, - "activeMatters": { + "from": { "type": "array", + "minItems": 1, + "uniqueItems": true, "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "id", - "title", - "type" + "type": "string", + "enum": [ + "backlog", + "pending", + "inProgress", + "blocked", + "inReview", + "completed", + "failed", + "canceled", + "deleted" ], - "additionalProperties": true - } + "description": "允许的起始状态。" + }, + "description": "动作可接受的起始状态集合。" }, - "recentMatters": { - "type": "array", - "maxItems": 10, - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "type": { - "type": "string" - }, - "completedAt": { - "type": "string" - }, - "summary": { - "type": "string" - } - }, - "required": [ - "id", - "title", - "type" - ], - "additionalProperties": true - } + "to": { + "type": "string", + "enum": [ + "backlog", + "pending", + "inProgress", + "blocked", + "inReview", + "completed", + "failed", + "canceled", + "deleted" + ], + "description": "动作成功后的目标状态。" }, - "totalMatters": { - "type": "integer", - "minimum": 0 + "actorTypes": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "human", + "agent", + "system" + ], + "description": "可请求此动作的 actor 类型。" + }, + "description": "类型级门槛;服务端仍须校验具体 actor、scope 与 claim。" }, - "totalArchived": { - "type": "integer", - "minimum": 0 + "description": { + "type": "string", + "minLength": 1, + "description": "流转的产品语义与限制。" } }, - "required": [ - "agentId", - "activeMatters", - "recentMatters", - "totalMatters", - "totalArchived" - ], - "additionalProperties": true + "additionalProperties": false }, - "ConversationMatterMessages": { + "TaskBoardCapability": { + "description": "公开声明一个 TaskBoard 能力是否真实可用。", "type": "object", + "required": [ + "id", + "level", + "description" + ], "properties": { - "matterId": { - "type": "string" + "id": { + "type": "string", + "minLength": 1, + "description": "可被 API、Tool 和 UI 共同识别的能力 ID。" }, - "messages": { - "type": "array", - "maxItems": 120, - "items": {}, - "description": "Public source entries; not guaranteed to have the per-Run SessionMessage shape. Sensitive or unverifiable tool-result bodies may be replaced and external result references removed." + "level": { + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "description": "当前客户端真正实现的能力等级;unavailable 不得伪装成功。" }, - "truncated": { - "type": "boolean", - "const": true, - "description": "Present and true when the source read exceeded 120 messages. Public projection may return fewer entries." + "description": { + "type": "string", + "minLength": 1, + "description": "能力语义及当前边界说明。" } }, - "required": [ - "matterId", - "messages" - ], - "additionalProperties": true + "additionalProperties": false }, - "NetworkHealthState": { + "TaskBoardProjections": { + "type": "object", + "additionalProperties": false, + "description": "显式不可变来源投影合同;不代表owner端口已安装,不改变Task完成或审核规则。缺省不授权任何投影。", + "properties": { + "relation": { + "type": "object", + "additionalProperties": false, + "required": [ + "mappingVersion", + "eventTypes" + ], + "description": "仅允许指定Task事件到私有Relation owner,仍须真实source、当前capability和owner发布见证。", + "properties": { + "mappingVersion": { + "const": 1, + "description": "同commit typed Actor Relation映射版本,未知版本不可投递。" + }, + "eventTypes": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "maxItems": 3, + "items": { + "enum": [ + "taskClaimed", + "taskHandedOff", + "reviewAccepted" + ] + }, + "description": "明确列出的来源事件;非列表事件不投递。" + } + } + }, + "notification": { + "type": "object", + "additionalProperties": false, + "required": [ + "mappingVersion", + "eventTypes", + "recipientStrategy" + ], + "description": "仅声明通知来源合同,不接受subject/body模板或自报收件人,不代表通知端口已经实现。", + "properties": { + "mappingVersion": { + "const": 1, + "description": "可信Notification来源映射版本。" + }, + "eventTypes": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "maxItems": 5, + "items": { + "enum": [ + "taskAssigned", + "taskHandedOff", + "taskSubmitted", + "reviewAccepted", + "reviewChangesRequested" + ] + }, + "description": "分配、移交、提交、最终接受和要求返工事件;不包含阶段事件,返工不等于拒绝。" + }, + "recipientStrategy": { + "const": "ownerUser", + "description": "收件人只能由同commit ownerUserId派生,仍需当前收件权限验证。" + } + } + } + } + }, + "TaskBoardMentionSourceRef": { "type": "object", "required": [ - "status", - "reason", - "since", - "seq" + "kind", + "sourceId", + "sourceRevision", + "taskRef" ], "properties": { - "status": { + "kind": { "type": "string", "enum": [ - "online", - "degraded", - "offline", - "unknown" + "taskDescription", + "taskComment" ], - "description": "网络健康状态:online=上游请求或探测成功;degraded=出现网络类失败但未确认公网断开(或仅个别 Provider 不可达);offline=通用连通性探测失败且近期失败的上游也探测不通,或多 host DNS 失败,确认网络不可达;unknown=启动后尚无样本(或休眠唤醒后待重新确认),UI 不应据此显示异常" + "description": "TaskBoard 公开来源类型。" }, - "scope": { + "sourceId": { "type": "string", - "enum": [ - "global", - "provider" - ], - "description": "异常范围(仅 degraded/offline 时有意义):global=公网整体不可达;provider=公网通但个别模型服务商不可达(通用探测成功、Provider origin 探测失败)" + "minLength": 1, + "description": "Task ID 或 Task comment ID。" }, - "affectedProviders": { - "type": "array", - "items": { - "type": "string" + "sourceRevision": { + "type": "integer", + "minimum": 1, + "description": "不可变来源 revision。" + }, + "taskRef": { + "type": "object", + "required": [ + "boardId", + "taskId" + ], + "properties": { + "boardId": { + "type": "string", + "minLength": 1, + "description": "canonical Board ID。" + }, + "taskId": { + "type": "string", + "minLength": 1, + "description": "canonical Task ID。" + }, + "taskVersion": { + "type": "integer", + "minimum": 1, + "description": "可选 Task version 快照。" + } }, - "description": "scope=provider 时受影响的 Provider ID 列表(供前端展示\"服务商暂不可达\")" + "additionalProperties": false, + "description": "TaskBoard 公开来源的 canonical TaskRef。" + } + }, + "additionalProperties": false, + "description": "TaskBoard read DTO 中的 camelCase Mention source;进入 Mention owner 前显式转换。" + }, + "TaskSubmission": { + "description": "负责人提交给人类验收的结果事实。", + "type": "object", + "required": [ + "submittedBy", + "submittedAt", + "summary" + ], + "properties": { + "submittedBy": { + "$ref": "#/components/schemas/TaskParticipantRef" }, - "reason": { + "submittedAt": { "type": "string", - "enum": [ - "init", - "request_ok", - "probe_ok", - "request_failed", - "probe_failed", - "stream_stall", - "multi_host_dns", - "suspend_resume" - ], - "description": "进入当前状态的原因:init=启动初始;request_ok/probe_ok=真实请求或探测成功;request_failed=上游请求网络类失败;probe_failed=通用连通性探测失败;stream_stall=流式空闲超时;multi_host_dns=窗口期内多个不同 host DNS 失败(最强离线证据);suspend_resume=系统唤醒后重置待确认" + "format": "date-time", + "description": "ISO 8601 日期时间。" }, - "subcategory": { + "summary": { + "type": "string", + "minLength": 1, + "description": "供验收者判断结果的有界摘要。" + }, + "runRef": { + "$ref": "#/components/schemas/RunRef" + }, + "receiptRef": { + "$ref": "#/components/schemas/ReceiptRef" + }, + "workContextRef": { + "$ref": "#/components/schemas/WorkContextRef" + } + }, + "additionalProperties": false + }, + "TaskReviewDecision": { + "description": "人类对一次 Task 提交作出的验收决定。", + "type": "object", + "required": [ + "outcome", + "decidedBy", + "decidedAt" + ], + "properties": { + "outcome": { "type": "string", "enum": [ - "dns", - "tls", - "tcp_reset", - "refused", - "unreachable", - "timeout", - "stream_interrupted" + "accepted", + "changesRequested" ], - "description": "触发本次状态变化的网络错误子类(若有),供前端给出针对性提示(如 dns → 检查 DNS/代理设置)" + "description": "本轮验收结果。" }, - "since": { + "decidedBy": { + "$ref": "#/components/schemas/TaskParticipantRef" + }, + "decidedAt": { "type": "string", - "description": "进入当前状态的 ISO 8601 时间;消费方据此判断状态新鲜度(无活跃请求时状态允许 stale)" + "format": "date-time", + "description": "ISO 8601 日期时间。" }, - "seq": { - "type": "number", - "minimum": 0, - "description": "单调递增序号;前端用于拒绝乱序/迟到的旧状态事件(快照与实时事件以 seq 大者为准)" + "commentId": { + "type": "string", + "minLength": 1, + "description": "验收或返工说明对应的 Task comment ID。" } }, "additionalProperties": false }, - "OperationsRecentCallsResponse": { + "TaskReviewAssignment": { + "description": "某 review round/stage 当前唯一有效的 reviewer 授权投影。", "type": "object", "required": [ - "calls" + "stageId", + "round", + "assignmentEpoch", + "reviewer", + "assignedAt" ], "properties": { - "calls": { - "type": "array", - "items": { - "type": "object", - "required": [ - "id", - "timestamp", - "agentId", - "runId", - "toolName", - "status", - "durationMs" - ], - "properties": { - "id": { - "type": "string", - "minLength": 1, - "description": "不透明的稳定调用 ID;优先 invocation_id,旧记录由 Run/Receipt 身份和工具序号确定。不得解析为 Run ID,也不是恢复或授权凭据。" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "优先工具 started_at;旧记录缺失时回退 Receipt started_at,再回退 Run started_at。不是保证精确的工具开始时间。" - }, - "agentId": { - "type": "string", - "minLength": 1, - "description": "执行 Agent ID。" - }, - "runId": { - "type": "string", - "minLength": 1, - "description": "关联 Run ID;详情跳转不得从展示 ID 猜测。" - }, - "toolName": { - "type": "string", - "minLength": 1, - "description": "工具名称。" - }, - "status": { - "type": "string", - "enum": [ - "success", - "warning", - "error" - ], - "description": "优先工具 success/error;旧记录缺失时按 Receipt outcome 的 failed→error、partial→warning、其他→success 投影,不代表 Query 或 Run 终态。" - }, - "durationMs": { - "type": "number", - "minimum": 0, - "description": "已记录工具耗时(毫秒);旧记录缺失时为 0,不表示真实零耗时。" - } - }, - "additionalProperties": false - }, - "description": "当前可信用户最近的工具调用,按时间倒序。" + "stageId": { + "type": "string", + "minLength": 1, + "description": "当前 policy stage ID。" + }, + "round": { + "type": "integer", + "minimum": 1, + "description": "本 assignment 所属的 submission round。" + }, + "assignmentEpoch": { + "type": "integer", + "minimum": 1, + "description": "每次重新分配 reviewer 均递增,使旧 reviewer 投影失效。" + }, + "reviewer": { + "$ref": "#/components/schemas/TaskParticipantRef" + }, + "assignedAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" } }, - "additionalProperties": false, - "description": "运行中心活动分区的用户隔离工具调用列表。" + "additionalProperties": false }, - "OperationsUsageSummaryResponse": { + "TaskReviewHistoryEntry": { + "description": "已结束 review stage 的不可变审计投影。", "type": "object", "required": [ - "total", - "byModel", - "byDate", - "byAgent" + "stageId", + "round", + "assignmentEpoch", + "decision" ], "properties": { - "total": { - "type": "object", - "required": [ - "inputTokens", - "outputTokens", - "cacheReadTokens", - "cacheWriteTokens", - "costUsd", - "queryCount" + "stageId": { + "type": "string", + "minLength": 1, + "description": "已结束的 policy stage ID。" + }, + "round": { + "type": "integer", + "minimum": 1, + "description": "对应 submission round。" + }, + "assignmentEpoch": { + "type": "integer", + "minimum": 1, + "description": "对应 reviewer assignment epoch。" + }, + "decision": { + "$ref": "#/components/schemas/TaskReviewDecision" + } + }, + "additionalProperties": false + }, + "TaskReviewStagePolicy": { + "description": "多 Agent 审核流中一个按顺序执行的权威 gate。", + "type": "object", + "required": [ + "id", + "mode", + "reviewerStrategy", + "description" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "在 policy 内稳定的 review stage ID。" + }, + "mode": { + "type": "string", + "enum": [ + "agentReview", + "supervisorReview", + "humanAcceptance", + "approvalRef" ], - "properties": { - "inputTokens": { - "type": "number", - "minimum": 0, - "description": "非缓存输入 Token 总数。" - }, - "outputTokens": { - "type": "number", - "minimum": 0, - "description": "输出 Token 总数。" - }, - "cacheReadTokens": { - "type": "number", - "minimum": 0, - "description": "缓存读取 Token 总数。" - }, - "cacheWriteTokens": { - "type": "number", - "minimum": 0, - "description": "缓存写入 Token 总数。" - }, - "costUsd": { - "type": "number", - "minimum": 0, - "description": "已记录的美元成本总额。" - }, - "queryCount": { - "type": "number", - "minimum": 0, - "description": "带用量数据的查询次数。" - } - }, - "additionalProperties": false, - "description": "当前可信用户的用量汇总。" + "description": "本 gate 的权威事实来源。" }, - "byModel": { - "type": "array", - "items": { - "type": "object", - "required": [ - "inputTokens", - "outputTokens", - "cacheReadTokens", - "cacheWriteTokens", - "costUsd", - "queryCount", - "provider", - "model" - ], - "properties": { - "provider": { - "type": "string", - "description": "模型 Provider 标识。" - }, - "model": { - "type": "string", - "description": "模型 ID。" - }, - "inputTokens": { - "type": "number", - "minimum": 0, - "description": "非缓存输入 Token 总数。" - }, - "outputTokens": { - "type": "number", - "minimum": 0, - "description": "输出 Token 总数。" - }, - "cacheReadTokens": { - "type": "number", - "minimum": 0, - "description": "缓存读取 Token 总数。" - }, - "cacheWriteTokens": { - "type": "number", - "minimum": 0, - "description": "缓存写入 Token 总数。" - }, - "costUsd": { - "type": "number", - "minimum": 0, - "description": "已记录的美元成本总额。" - }, - "queryCount": { - "type": "number", - "minimum": 0, - "description": "带用量数据的查询次数。" - } - }, - "additionalProperties": false - }, - "description": "按 Provider/模型聚合的用量。" + "reviewerStrategy": { + "type": "string", + "enum": [ + "explicitActor", + "teamSupervisor", + "ownerUser", + "approvalOwner" + ], + "description": "服务端如何解析当前 round 的 reviewer;UI 不得自行猜测。" }, - "byDate": { - "type": "array", - "items": { - "type": "object", - "required": [ - "inputTokens", - "outputTokens", - "cacheReadTokens", - "cacheWriteTokens", - "costUsd", - "queryCount", - "date" - ], - "properties": { - "date": { - "type": "string", - "pattern": "^\\d{4}-\\d{2}-\\d{2}$", - "description": "聚合日期(YYYY-MM-DD);UTC 日桶可按其中点映射到请求时区,不是逐事件的精确本地日结。" - }, - "inputTokens": { - "type": "number", - "minimum": 0, - "description": "非缓存输入 Token 总数。" - }, - "outputTokens": { - "type": "number", - "minimum": 0, - "description": "输出 Token 总数。" - }, - "cacheReadTokens": { - "type": "number", - "minimum": 0, - "description": "缓存读取 Token 总数。" - }, - "cacheWriteTokens": { - "type": "number", - "minimum": 0, - "description": "缓存写入 Token 总数。" - }, - "costUsd": { - "type": "number", - "minimum": 0, - "description": "已记录的美元成本总额。" - }, - "queryCount": { - "type": "number", - "minimum": 0, - "description": "带用量数据的查询次数。" - } - }, - "additionalProperties": false - }, - "description": "按 UTC 日桶聚合的用量;有效 timezone 将桶的 UTC 12:00 映射到本地日期,无法恢复桶内每条记录的本地日界。" + "selfReviewAllowed": { + "type": "boolean", + "description": "是否允许本轮 submission actor 审核自己;多 Agent 默认应为 false。" }, - "byAgent": { - "type": "array", - "items": { - "type": "object", - "required": [ - "inputTokens", - "outputTokens", - "cacheReadTokens", - "cacheWriteTokens", - "costUsd", - "queryCount", - "agentId" - ], - "properties": { - "agentId": { - "type": "string", - "minLength": 1, - "description": "关联 Agent ID。" - }, - "inputTokens": { - "type": "number", - "minimum": 0, - "description": "非缓存输入 Token 总数。" - }, - "outputTokens": { - "type": "number", - "minimum": 0, - "description": "输出 Token 总数。" - }, - "cacheReadTokens": { - "type": "number", - "minimum": 0, - "description": "缓存读取 Token 总数。" - }, - "cacheWriteTokens": { - "type": "number", - "minimum": 0, - "description": "缓存写入 Token 总数。" - }, - "costUsd": { - "type": "number", - "minimum": 0, - "description": "已记录的美元成本总额。" - }, - "queryCount": { - "type": "number", - "minimum": 0, - "description": "带用量数据的查询次数。" - } - }, - "additionalProperties": false - }, - "description": "按 Agent 聚合的用量。" + "description": { + "type": "string", + "minLength": 1, + "description": "审核 gate 的产品语义和授权边界。" + } + }, + "additionalProperties": false + }, + "TaskBoardHandoffEvidenceResponse": { + "type": "object", + "required": [ + "evidence" + ], + "properties": { + "evidence": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskBoardHandoffEvidence" + } } }, - "additionalProperties": false, - "description": "运行中心活动分区的用户隔离用量摘要。" + "additionalProperties": false }, - "AgentInputConfig": { + "TaskBoardHandoffEvidence": { + "description": "仅由已授权 append-only taskHandedOff TaskEvent 投影的关系图证据。", "type": "object", - "additionalProperties": false, "required": [ - "version", - "agentId", - "manual", - "configRevision" + "eventId", + "kind", + "taskRef", + "fromActor", + "toActor", + "occurredAt" ], "properties": { - "version": { - "type": "integer", - "const": 1, - "description": "Projection protocol version; unknown versions do not unlock ordinary input." + "eventId": { + "type": "string", + "minLength": 1, + "description": "真实 TaskEvent.eventId。" }, - "agentId": { + "kind": { "type": "string", - "pattern": "^[A-Za-z0-9_-]+$", - "description": "Actual Agent read; match it against the selected target." + "const": "handoff", + "description": "关系证据类型。" }, - "manual": { - "type": "boolean", - "description": "Current separate-conversation manual mode. Valid omission in Agent configuration defaults to false." + "taskRef": { + "$ref": "#/components/schemas/TaskRef" }, - "configRevision": { + "fromActor": { + "$ref": "#/components/schemas/ActorRef" + }, + "toActor": { + "$ref": "#/components/schemas/ActorRef" + }, + "reason": { "type": "string", - "pattern": "^[a-f0-9]{64}$", - "description": "Opaque digest of configuration content. Equality only; not a permission, lock, timestamp or sequence." + "minLength": 1, + "description": "handoff 命令记录的可选理由。" + }, + "occurredAt": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 日期时间。" } - } + }, + "additionalProperties": false }, - "AgentInputConfigError": { + "S3ConnectionSummary": { "type": "object", - "required": [ - "code", - "error" - ], "properties": { - "code": { - "type": "string", - "enum": [ - "AGENT_INPUT_CONFIG_INVALID_ID", - "AGENT_NOT_FOUND", - "AGENT_INPUT_CONFIG_CHANGED", - "AGENT_INPUT_CONFIG_INVALID", - "AGENT_INPUT_CONFIG_UNREADABLE" - ] + "id": { + "type": "string" }, - "error": { + "label": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "region": { + "type": "string" + }, + "status": { "type": "string" + }, + "is_default": { + "type": "boolean" + }, + "has_public_url": { + "type": "boolean" } }, - "additionalProperties": true + "required": [ + "id", + "label", + "bucket", + "region", + "status", + "is_default", + "has_public_url" + ], + "additionalProperties": false, + "description": "Saved connection summary without credentials. Status and has_public_url are not fresh reachability checks." }, - "MergedHistoryItem": { + "S3ConnectionSummaries": { "type": "object", - "additionalProperties": false, + "properties": { + "success": { + "type": "boolean", + "const": true + }, + "connections": { + "type": "array", + "items": { + "$ref": "#/components/schemas/S3ConnectionSummary" + } + } + }, "required": [ - "runId", - "messageIndex", - "sessionTime", - "message" + "success", + "connections" ], + "additionalProperties": false + }, + "S3ObjectList": { + "type": "object", "properties": { - "runId": { - "type": "string", - "description": "Owned source Run ID, or __mention_source:SOURCE_ID for a Conversation source-only entry. Synthetic IDs cannot be used with Run, follow-up or recovery APIs." + "success": { + "type": "boolean", + "const": true }, - "messageIndex": { - "type": "number", - "description": "Source row navigation position; source-only entries use 0. Not the merged page offset." + "objects": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "size": { + "type": "number" + }, + "last_modified": { + "type": "string", + "description": "Provider timestamp string; can be empty when unavailable." + } + }, + "required": [ + "key", + "size", + "last_modified" + ], + "additionalProperties": false + } }, - "offset": { + "common_prefixes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "prefix": { + "type": "string" + } + }, + "required": [ + "prefix" + ], + "additionalProperties": false + } + }, + "key_count": { "type": "number", - "minimum": 0, - "description": "Optional source byte position for persisted Run messages. Not a page offset." + "description": "Provider page count, which may count common prefixes; not a total or guaranteed objects.length." }, - "sessionTime": { + "is_truncated": { + "type": "boolean" + }, + "next_continuation_token": { "type": "string", - "description": "Source session creation time or source creation time, in ISO 8601 form." + "description": "Opaque provider token when provided; absent rather than null without a token. Stop if a truncated page lacks a usable token." }, - "message": { - "$ref": "#/components/schemas/HistoryMessage" + "bucket": { + "type": "string" + }, + "prefix": { + "type": "string", + "description": "Omitted for an empty or absent prefix." } - } + }, + "required": [ + "success", + "objects", + "common_prefixes", + "key_count", + "is_truncated", + "bucket" + ], + "additionalProperties": false }, - "MergedHistoryPage": { + "S3PublicDownloadLink": { "type": "object", - "additionalProperties": false, + "properties": { + "success": { + "type": "boolean", + "const": true + }, + "key": { + "type": "string" + }, + "download_url": { + "type": "string" + }, + "url_type": { + "const": "public", + "type": "string" + }, + "content_type": { + "type": "string" + }, + "content_length": { + "type": "number" + } + }, "required": [ - "items", - "total", - "hasMore", - "historyRevision" + "success", + "key", + "download_url", + "url_type" ], + "additionalProperties": false + }, + "S3PresignedDownloadLink": { + "type": "object", "properties": { - "items": { - "type": "array", - "maxItems": 1000, - "items": { - "$ref": "#/components/schemas/MergedHistoryItem" - }, - "description": "Public display projection; sensitive result bodies and references may be withheld." + "success": { + "type": "boolean", + "const": true }, - "total": { - "type": "integer", - "minimum": 0, - "description": "Total entries in this complete merged public view, not source rows across Runs." + "key": { + "type": "string" }, - "hasMore": { + "download_url": { + "type": "string" + }, + "url_type": { + "const": "presigned", + "type": "string" + }, + "content_type": { + "type": "string" + }, + "content_length": { + "type": "number" + }, + "expires_in": { + "type": "number", + "description": "Requested lifetime in seconds after server conversion and clamping; credentials or provider policy can invalidate the URL sooner." + } + }, + "required": [ + "success", + "key", + "download_url", + "url_type", + "expires_in" + ], + "additionalProperties": false + }, + "S3DownloadLink": { + "oneOf": [ + { + "$ref": "#/components/schemas/S3PublicDownloadLink" + }, + { + "$ref": "#/components/schemas/S3PresignedDownloadLink" + } + ], + "description": "A public URL has no expires_in. A presigned URL includes expires_in; neither guarantees a future successful download." + }, + "S3ReadError": { + "type": "object", + "properties": { + "success": { "type": "boolean", - "description": "Whether more entries remain for a positive limit. Always false for limit=0." + "const": false }, - "historyRevision": { + "error": { + "type": "string" + }, + "code": { "type": "string", - "pattern": "^h1:[0-9a-f]{64}$", - "description": "Current complete public view digest including read scope and order. Later pages send expectedRevision; changes yield 409. Not a persistent cursor or transaction snapshot." + "enum": [ + "NO_CONNECTION", + "INVALID_REQUEST", + "NOT_FOUND", + "LIST_FAILED", + "DOWNLOAD_FAILED" + ] } - } + }, + "required": [ + "success", + "error", + "code" + ], + "additionalProperties": false + }, + "ObjectStorageCapabilityDisabled": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "code": { + "type": "string", + "const": "capabilityDisabled" + }, + "capability": { + "type": "string", + "const": "object-storage" + }, + "retryable": { + "type": "boolean", + "const": false + } + }, + "required": [ + "error", + "code", + "capability", + "retryable" + ], + "additionalProperties": false } } }, "x-service": "agent-service", "x-source": { "repository": "desirecore/desirecore", - "revision": "a0186f6feeaf66eb88787e8b80b8fea5559b8761" + "revision": "ba6a550aeb397c377f6c3c6e620863d06d526628" } } diff --git a/tsconfig.json b/tsconfig.json index 7b10be1..954e6e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,5 +4,5 @@ "compilerOptions": { "baseUrl": "." }, - "exclude": [".docusaurus", "build", "api/examples/agent-client/test/source-route.fixture.ts", "api/examples/agent-client/test/source-history.fixture.ts", "api/examples/agent-client/test/source-observe.fixture.ts"] + "exclude": [".docusaurus", "build", "api/examples/agent-client/test/source-*.fixture.ts"] }