docs: align HWLAB assembly to HWPOD
This commit is contained in:
@@ -103,7 +103,7 @@ POST /api/v1/sessions/:id/storage/refresh # runner 上报 PVC 摘
|
||||
|
||||
## HWLAB v0.2 基线承接
|
||||
|
||||
Manager 只承接 HWLAB v0.2 Code Agent 的通用执行事实,不承接 HWLAB 的用户鉴权、device-pod 授权或 Workbench schema。HWLAB 侧能力吸收总表见 [spec-v01-hwlab-manual-dispatch.md](spec-v01-hwlab-manual-dispatch.md)。本服务需要把以下能力固化为 AgentRun 自身合同:
|
||||
Manager 只承接 HWLAB v0.2 Code Agent 的通用执行事实,不承接 HWLAB 的用户鉴权、HWPOD 授权或 Workbench schema。HWLAB 侧能力吸收总表见 [spec-v01-hwlab-manual-dispatch.md](spec-v01-hwlab-manual-dispatch.md)。本服务需要把以下能力固化为 AgentRun 自身合同:
|
||||
|
||||
| HWLAB 原有能力 | Manager 承接合同 | 不归 Manager 管的内容 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -32,7 +32,7 @@ Backend adapter 消费 RuntimeAssembly 中的 `BackendImageRef` 和 `ProfileRef`
|
||||
|
||||
## HWLAB v0.2 Code Agent 能力吸收
|
||||
|
||||
Backend adapter 的第一阶段实现应吸收 HWLAB v0.2 已验证的 Codex stdio 行为,而不是另写一套不兼容协议。吸收只限通用 backend 执行能力,HWLAB 的自然语言业务路由、device-pod 授权和 Workbench 展示不进入 adapter。
|
||||
Backend adapter 的第一阶段实现应吸收 HWLAB v0.2 已验证的 Codex stdio 行为,而不是另写一套不兼容协议。吸收只限通用 backend 执行能力,HWLAB 的自然语言业务路由、HWPOD 授权和 Workbench 展示不进入 adapter。
|
||||
|
||||
| HWLAB v0.2 基线 | 参考入口 | Adapter 固化规则 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
|
||||
## 在系统中的职责划分
|
||||
|
||||
- HWLAB `hwlab-cloud-api` 是业务 dispatcher:继续负责用户登录、session owner、device-pod 授权、Workbench `/v1/agent/chat` 合同、result/trace/cancel 对外接口和业务权限判断。
|
||||
- HWLAB `hwlab-cloud-api` 是业务 dispatcher:继续负责用户登录、session owner、HWPOD 授权、Workbench `/v1/agent/chat` 合同、result/trace/cancel 对外接口和业务权限判断。
|
||||
- AgentRun `agentrun-mgr` 是执行事实 authority:负责 run、command、event、runner job、backend profile、SecretRef、terminal status 和手动调度 API。
|
||||
- Provider profile API Key 管理由 HWLAB Cloud API 鉴权后委托 AgentRun manager;AgentRun 信任 HWLAB 后端服务端调用,不实现 HWLAB 用户鉴权。完整合同见 [spec-v01-provider-profile-management.md](spec-v01-provider-profile-management.md)。
|
||||
- `agentrun-runner` 是手动启动的执行者:从 manager claim run、poll command、调用 backend adapter、append events、ack command、上报 command terminal,并在同一 runner Job 生命周期内继续等待同 run 的后续 command;run terminal 只由显式 run cancel 或 runner 级不可恢复失败产生。
|
||||
- HWLAB 不直接写 AgentRun Postgres、不读取 AgentRun Secret、不直接创建 Kubernetes Job;所有跨服务操作只走 AgentRun RESTful API。
|
||||
- AgentRun 不内建 HWLAB device-pod/gateway 业务授权;涉及硬件、用户授权或 device lease 的判断仍由 HWLAB 自己完成。
|
||||
- AgentRun 不内建 HWLAB HWPOD/gateway 业务授权;涉及 HWPOD 四要素、用户授权或硬件访问凭证的判断仍由 HWLAB 自己完成。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 本阶段不要求自动 scheduler、pending scan、capacity selection 或长驻调度器。
|
||||
- 不改变 HWLAB 对外 `/v1/agent/chat`、`/result`、`/trace`、`/cancel` 用户合同。
|
||||
- 不使用 SSE、WebSocket、long-polling 或长同步 `turn` 请求替代 durable resource 模型。
|
||||
- 不把 HWLAB 的 provider Secret、device token、gateway route 或 kubeconfig 复制给业务客户端。
|
||||
- 不把 HWLAB 的 provider Secret、HWPOD token、gateway route 或 kubeconfig 复制给业务客户端。
|
||||
- 不把 mock、fixture、source-only smoke 或 dry-run 结果当作 HWLAB canary 通过证据。
|
||||
- 不把 trace、日志、诊断文案、降级原因或“已观测到缺口”当作功能完成。若 HWLAB v0.2 原有 Code Agent 需要的能力在 AgentRun v0.1 中缺失,必须补齐 AgentRun 自身能力;若实现未修复,必须修复实现本身,观测只作为定位和验收证据。
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
```text
|
||||
HWLAB Workbench
|
||||
-> hwlab-cloud-api /v1/agent/chat
|
||||
-> HWLAB 用户/session/device 权限判断
|
||||
-> HWLAB 用户/session/HWPOD 权限判断
|
||||
-> AgentRun POST /api/v1/runs
|
||||
-> AgentRun POST /api/v1/runs/:runId/commands
|
||||
-> AgentRun POST /api/v1/runs/:runId/runner-jobs
|
||||
@@ -39,7 +39,7 @@ HWLAB 应保存 `traceId -> runId/commandId/attemptId/jobName` 映射。用户
|
||||
|
||||
## HWLAB v0.2 Code Agent 能力吸收基线
|
||||
|
||||
AgentRun `v0.1` 承接 HWLAB v0.2 时,只吸收原有 Code Agent 的通用执行能力,不吸收 HWLAB 的用户鉴权、device-pod 授权、Workbench UI 或业务 trace schema。下表是后续实现时的代码追溯入口,目的是复用 HWLAB 已验证的边界和判定标准,而不是重新发明一套 Agent 行为。
|
||||
AgentRun `v0.1` 承接 HWLAB v0.2 时,只吸收原有 Code Agent 的通用执行能力,不吸收 HWLAB 的用户鉴权、HWPOD 授权、Workbench UI 或业务 trace schema。下表是后续实现时的代码追溯入口,目的是复用 HWLAB 已验证的边界和判定标准,而不是重新发明一套 Agent 行为。
|
||||
|
||||
| HWLAB v0.2 原有能力 | HWLAB 代码/文档追溯入口 | AgentRun 自身承接点 | SPEC 归属 |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -52,7 +52,7 @@ AgentRun `v0.1` 承接 HWLAB v0.2 时,只吸收原有 Code Agent 的通用执
|
||||
| 初始业务 prompt 注入 | `internal/cloud/codex-stdio-session.ts` 的 boundary instructions、`internal/cloud/codex-stdio-session-helpers.ts` 的 `buildCodexUserPrompt()` | HWLAB 把稳定业务 instruction 文件放入同一 Git bundle,并通过 `ResourceBundleRef.promptRefs` 指定;AgentRun 只在新 thread 首轮注入,resume 不重复注入 | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) |
|
||||
| skill discovery 与 skill facts | `internal/cloud/skills-store.ts`、`internal/cloud/codex-stdio-session-helpers.ts` 的 `discoverSkillsForStdio()` 和 `codexSidecarSkillsPrompt()` | HWLAB 把 required skill manifest 放入同一 Git bundle,并通过 `ResourceBundleRef.skillRefs` 指定;AgentRun 聚合到 runner workspace skill registry 并向 Codex 暴露有界 skill facts | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-agentrun-runner.md](spec-v01-agentrun-runner.md) |
|
||||
| provider profile 隔离和 Secret 不泄露 | `internal/cloud/code-agent-contract.ts`、`docs/reference/code-agent-chat-readiness.md` | `ProfileRef/SecretRef` profile-scoped 投影、缺失为 `secret-unavailable`、禁止 fallback 和泄露值 | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-backend-adapter.md](spec-v01-backend-adapter.md) |
|
||||
| device-pod 短期会话 env 注入 | `internal/cloud/server-code-agent-http.ts` 的 `codeAgentDevicePodAuthEnv()` | `runner-jobs.transientEnv` 只在本次 Kubernetes Job env 中生效;只记录 name/count,不保存或输出 value | [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md)、[spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) |
|
||||
| HWPOD/HWLAB runtime 短期 env 注入 | HWLAB Cloud API 的 Code Agent env assembly(历史实现名可能含 device-pod) | `runner-jobs.transientEnv` 只在本次 Kubernetes Job env 中生效;只记录 name/count,不保存或输出 value | [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md)、[spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) |
|
||||
| UniDesk SSH passthrough | HWLAB Code Agent 通过 `tran` 访问 G14/D601/HWLAB/GitHub 维护面 | `toolCredentials[].tool=unidesk-ssh` 注入 `UNIDESK_SSH_CLIENT_TOKEN`,`transientEnv` 只注入非敏感 `UNIDESK_MAIN_SERVER_IP`;UniDesk frontend 负责 route allowlist | [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md)、[spec-v01-secret-distribution.md](spec-v01-secret-distribution.md) |
|
||||
| provider/backend/cancel 等失败可区分 | `scripts/src/code-agent-response-contract.mjs`、`internal/cloud/code-agent-chat.ts` | failureKind 最小矩阵和 JSON 错误响应 | [spec-v01-agentrun-mgr.md](spec-v01-agentrun-mgr.md)、[spec-v01-backend-adapter.md](spec-v01-backend-adapter.md) |
|
||||
| stdout/stderr/tool 输出必须有界 | `docs/reference/code-agent-chat-readiness.md`、`internal/cloud/code-agent-trace-store.ts` | `command_output`/`tool_call` 记录摘要、字节数、截断标记和必要引用 | [spec-v01-backend-adapter.md](spec-v01-backend-adapter.md) |
|
||||
@@ -71,7 +71,7 @@ AgentRun `v0.1` 承接 HWLAB v0.2 时,只吸收原有 Code Agent 的通用执
|
||||
| `idempotencyKey` | HWLAB 必须用 `traceId`、`messageId` 或等价稳定 key;相同 key 和相同 payload 返回既有 job/attempt。 |
|
||||
| `image` / `backendImageRef` | 只能来自 manager allowlist、GitOps/catalog 或受控默认值;客户端不能传任意镜像扩大执行面。 |
|
||||
| `retention` / `ttlSecondsAfterFinished` | 可选;默认遵循 runner Job TTL 规格。 |
|
||||
| `transientEnv` | 可选,只用于本次 runner Job 的 Kubernetes env 渲染;不得写入 run/command/result/event 明文。用于承接 HWLAB 原 Code Agent 的短期 device-pod session token 和 API URL。 |
|
||||
| `transientEnv` | 可选,只用于本次 runner Job 的 Kubernetes env 渲染;不得写入 run/command/result/event 明文。用于承接 HWLAB dispatcher 生成的 owner-scoped HWPOD/runtime context,例如 `HWLAB_API_KEY`、`HWLAB_RUNTIME_API_URL`、`HWLAB_RUNTIME_WEB_URL` 和 endpoint metadata。 |
|
||||
|
||||
响应必须短返回 JSON,不等待完整模型 turn,至少包含:`runId`、`commandId`、`attemptId`、`jobName`、`namespace`、`runnerId`、`logPath` 或 `podIdentity`、后续 `commands show` 与 `events` 轮询入口。重复提交若 payload 不同,必须结构化失败,不能创建第二个同名业务 attempt。
|
||||
|
||||
@@ -88,8 +88,8 @@ HWLAB canary 创建 run 时应使用以下字段口径:
|
||||
| `providerId` | `G14`,只表示目标 provider,不授予 HWLAB 业务权限。 |
|
||||
| `backendProfile` | `deepseek`、`codex` 或 `minimax-m3`,由 HWLAB 或调度方显式选择;缺少 matching SecretRef 必须失败,不 fallback。 |
|
||||
| `workspaceRef` | 必须引用 ResourceBundleRef 中的 Git-only full commit;不得由 runner 猜 host path。 |
|
||||
| `resourceBundleRef.promptRefs[]` | 用于承接 HWLAB 稳定初始 prompt,例如 `hwlab-v02-runtime`、device-pod/no-fallback policy;必须来自同一 full commit,`inject=thread-start`,新 thread 首轮注入,resume 不注入。 |
|
||||
| `resourceBundleRef.skillRefs[]` | 用于承接 HWLAB required skills,例如 `device-pod-cli`;必须来自同一 full commit 的 `SKILL.md`,required skill 缺失时 blocked,不能让模型默认 skill 列表替代。 |
|
||||
| `resourceBundleRef.promptRefs[]` | 用于承接 HWLAB 稳定初始 prompt,例如 `hwlab-v02-runtime`、`hwpod-runtime-policy`;必须来自同一 full commit,`inject=thread-start`,新 thread 首轮注入,resume 不注入。 |
|
||||
| `resourceBundleRef.skillRefs[]` | 用于承接 HWLAB required skills,例如 `hwpod-cli`、`hwpod-ctl`;必须来自同一 full commit 的 `SKILL.md`,required skill 缺失时 blocked,不能让模型默认 skill 列表替代。 |
|
||||
| `resourceBundleRef.toolAliases[]` | 用于暴露短入口,例如 `hwpod`;缺 alias 时修 runner/bundle,不改走长路径 wrapper。 |
|
||||
| `executionPolicy` | sandbox、network、timeout、secretScope 必须显式,不得由 HWLAB 扩大 AgentRun Secret 范围。 |
|
||||
| `executionPolicy.secretScope.toolCredentials[]` | 需要 UniDesk SSH passthrough 时必须声明 `tool=unidesk-ssh`、`purpose=ssh-passthrough`、SecretRef `agentrun-v01-tool-unidesk-ssh`、projection env `UNIDESK_SSH_CLIENT_TOKEN`;不得把 token 放入 command payload 或 runner-job transientEnv。 |
|
||||
@@ -97,7 +97,7 @@ HWLAB canary 创建 run 时应使用以下字段口径:
|
||||
|
||||
`tenantId` / `projectId` 是 AgentRun manager 的 policy 边界,不是 HWLAB Workbench project。HWLAB adapter 可以把业务 project/workspace 写入 `metadata.hwlabProjectId`、`metadata.hwlabWorkspaceId` 或 `workspaceRef`,但不得覆盖 AgentRun `projectId=pikasTech/HWLAB`;否则 manager 必须按 `tenant-policy-denied` 拒绝。`providerProfile` 是 HWLAB 入口字段,进入 AgentRun 后必须映射为 `backendProfile`;同一个 HWLAB session 的后续 turn 应继承 session provider profile,不能被 stale workspace provider 覆盖。
|
||||
|
||||
Command 第一阶段要求 `type=turn` 和 `type=steer`。`turn` 保存用户原始 prompt、conversation metadata、profile 选择和 HWLAB trace correlation;稳定业务 prompt、skill 清单和工具入口不写入 command payload,而是通过 `ResourceBundleRef.promptRefs`、`skillRefs` 和 `toolAliases` 装配。`steer` 保存运行中引导文本,并由 runner 在同 run active turn 期间转发到 backend。业务 cancel 仍走 run/command cancel API,不用 `steer` 伪装。不得把 cookie、session token、provider credential、device internal token、Secret value、历史消息或大段 skill manifest 写入 payload。
|
||||
Command 第一阶段要求 `type=turn` 和 `type=steer`。`turn` 保存用户原始 prompt、conversation metadata、profile 选择和 HWLAB trace correlation;稳定业务 prompt、skill 清单和工具入口不写入 command payload,而是通过 `ResourceBundleRef.promptRefs`、`skillRefs` 和 `toolAliases` 装配。`steer` 保存运行中引导文本,并由 runner 在同 run active turn 期间转发到 backend。业务 cancel 仍走 run/command cancel API,不用 `steer` 伪装。不得把 cookie、session token、provider credential、HWPOD internal token、Secret value、历史消息或大段 skill manifest 写入 payload。
|
||||
|
||||
## 需要补齐的能力
|
||||
|
||||
@@ -160,20 +160,21 @@ HWLAB 旧 Code Agent 的业务 prompt 和 skill 注入必须迁移为 `ResourceB
|
||||
"repoUrl": "git@github.com:pikasTech/HWLAB.git",
|
||||
"commitId": "<full commit sha>",
|
||||
"toolAliases": [
|
||||
{ "name": "hwpod", "path": "tools/device-pod-cli.mjs", "kind": "node-script" }
|
||||
{ "name": "hwpod", "path": "tools/hwpod-cli.ts", "kind": "bun-script" }
|
||||
],
|
||||
"promptRefs": [
|
||||
{ "name": "hwlab-v02-runtime", "path": "internal/agent/prompts/hwlab-v02-runtime.md", "inject": "thread-start", "required": true },
|
||||
{ "name": "device-pod-policy", "path": "internal/agent/prompts/device-pod-policy.md", "inject": "thread-start", "required": true }
|
||||
{ "name": "hwpod-runtime-policy", "path": "internal/agent/prompts/hwpod-runtime-policy.md", "inject": "thread-start", "required": true }
|
||||
],
|
||||
"skillRefs": [
|
||||
{ "name": "device-pod-cli", "path": "skills/device-pod-cli/SKILL.md", "required": true, "aggregateAs": "device-pod-cli" }
|
||||
{ "name": "hwpod-cli", "path": "skills/hwpod-cli/SKILL.md", "required": true, "aggregateAs": "hwpod-cli" },
|
||||
{ "name": "hwpod-ctl", "path": "skills/hwpod-ctl/SKILL.md", "required": true, "aggregateAs": "hwpod-ctl" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这些 prompt 文件只写稳定规则,例如 HWLAB Cloud Workbench Code Agent 身份、Device Pod/D601-F103-V2/Keil/build/download/UART/debug-probe 的标准 `hwpod` 路径、禁止 lease、禁止 Cloud Web device-pod API、禁止 session-token fallback、禁止长路径 wrapper,以及历史上下文只走 Codex stdio 原生 `thread/resume`。它们不写用户本轮任务、不写会话历史、不写 Secret 或一次性 issue 过程。
|
||||
这些 prompt 文件只写稳定规则,例如 HWLAB Cloud Workbench Code Agent 身份、HWPOD 四要素(target device、workspace、debug probe、io probe)、D601-F103-V2/Keil/build/download/UART 的标准 `hwpod` 路径、禁止旧 Device Pod/profile/device-pod-cli fallback、禁止 Cloud Web 业务 API 替代 runner 内 HWPOD CLI、禁止 session-token fallback、禁止长路径 wrapper,以及历史上下文只走 Codex stdio 原生 `thread/resume`。它们不写用户本轮任务、不写会话历史、不写 Secret 或一次性 issue 过程。
|
||||
|
||||
首轮新 thread 必须自动注入这些 prompt 和 skill facts,使 Web 简短 prompt 也能识别 HWLAB 标准能力。后续 turn/resume 不重复注入;若 resume 失败,按 AgentRun Codex stdio 规则失败,不拼接历史 prompt 模拟继续。
|
||||
|
||||
@@ -187,7 +188,7 @@ HWLAB 旧 Code Agent 的业务 prompt 和 skill 注入必须迁移为 `ResourceB
|
||||
| 4 | ResourceBundleRef materialization | Git-only checkout、workspace 前缀、commit/tree 摘要、failureKind | 使用 full commit;不接受 branch/tag/HEAD;不覆盖 Secret/session/runtime home。 |
|
||||
| 5 | Resource prompt/skill assembly | `promptRefs` thread-start 注入、`skillRefs` registry 聚合、required blocker、hash/bytes 可见 | 简短 HWLAB prompt 能看到业务 instruction 和 required skill;resume 不重复注入;缺失直接 blocked。 |
|
||||
| 6 | SessionRef 持久化与 runner 多 turn | session record/store、thread resume、runner command loop、TTL/GC、profile 隔离 | 同一 conversation 连续两轮复用同一 run/runner Job;第二轮不重新 materialize bundle、不重复注入 initial prompt;不同 profile 不污染。 |
|
||||
| 7 | HWLAB v0.2 canary | HWLAB dispatcher adapter、traceId 映射、result/trace 转换 | 普通自然语言最短 turn 真实 completed 且 reply 非空;device-pod 仍由 HWLAB 授权。 |
|
||||
| 7 | HWLAB v0.2 canary | HWLAB dispatcher adapter、traceId 映射、result/trace 转换 | 普通自然语言最短 turn 真实 completed 且 reply 非空;HWPOD 仍由 HWLAB 授权。 |
|
||||
|
||||
## 测试规格
|
||||
|
||||
@@ -217,15 +218,15 @@ HWLAB 旧 Code Agent 的业务 prompt 和 skill 注入必须迁移为 `ResourceB
|
||||
|
||||
### T7 HWLAB prompt/skill 装配
|
||||
|
||||
阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),然后用 HWLAB canary `ResourceBundleRef` 指定 `promptRefs`、`skillRefs` 和 `toolAliases`。首轮 Web/CLI 简短 prompt 只写“编译 D601-F103-V2”或等价自然语言,确认 Codex turn 能看到 HWLAB runtime prompt、`device-pod-cli` skill facts 和 `hwpod` alias;第二轮 continuation 使用同一 thread resume,确认 `initialPromptInjected=false`,没有手工拼接历史;删除 required skill/prompt 后确认 blocked 而不是 fallback 到默认 Codex 5 个系统 skill。
|
||||
阅读本文和 [spec-v01-runtime-assembly.md](spec-v01-runtime-assembly.md),然后用 HWLAB canary `ResourceBundleRef` 指定 `promptRefs`、`skillRefs` 和 `toolAliases`。首轮 Web/CLI 简短 prompt 只写“编译 D601-F103-V2”或等价自然语言,确认 Codex turn 能看到 HWLAB runtime prompt、`hwpod-cli`/`hwpod-ctl` skill facts 和 `hwpod` alias;第二轮 continuation 使用同一 thread resume,确认 `initialPromptInjected=false`,没有手工拼接历史;删除 required skill/prompt 后确认 blocked 而不是 fallback 到默认 Codex 5 个系统 skill。
|
||||
|
||||
### T8 DS 短 prompt 真实验收
|
||||
|
||||
阅读本文、[spec-v01-backend-codex.md](spec-v01-backend-codex.md) 和 HWLAB `spec-v02-hwlab-cli.md`,然后必须用正式 HWLAB CLI/Web 等价短连接入口提交真实 `backendProfile=deepseek` 的短 prompt 到 AgentRun runtime,不允许用长提示词把规则补给模型。验收至少包含三条 prompt:
|
||||
|
||||
1. “不调用工具的情况下,你可见的 skill 有哪些?”确认回复包含 `ResourceBundleRef.skillRefs` 中的 HWLAB required skill,例如 `device-pod-cli`,并且不只返回 Codex 默认 5 个系统 skill。
|
||||
2. “你当前 HWLAB 初始规则里,D601-F103-V2 应该走哪个标准入口?请只回答入口和禁止路径。”确认回复能说出 `hwpod`、Cloud API/assembled env、禁止 lease、禁止 Cloud Web device-pod API、禁止 session-token fallback 和禁止长路径 wrapper。
|
||||
3. “编译 D601-F103-V2。”确认短 prompt 能按注入规则触发 `hwpod`/Device Pod 标准路径,而不是要求用户补充长 prompt。
|
||||
1. “不调用工具的情况下,你可见的 skill 有哪些?”确认回复包含 `ResourceBundleRef.skillRefs` 中的 HWLAB required skill,例如 `hwpod-cli` 和 `hwpod-ctl`,并且不只返回 Codex 默认 5 个系统 skill。
|
||||
2. “你当前 HWLAB 初始规则里,D601-F103-V2 应该走哪个标准入口?请只回答入口和禁止路径。”确认回复能说出 `hwpod`、`hwpod-cli`/`hwpod-ctl`、assembled runtime env、禁止旧 Device Pod/profile/device-pod-cli fallback、禁止 session-token fallback 和禁止长路径 wrapper。
|
||||
3. “编译 D601-F103-V2。”确认短 prompt 能按注入规则触发 `hwpod-cli -> hwpod-compiler-cli -> /v1/hwpod-node-ops -> hwpod-node` 正向链路,而不是要求用户补充长 prompt。
|
||||
|
||||
上述三条必须来自真实 DS/DeepSeek profile 的 Codex stdio `thread/start`/`turn/start` 和后续 `thread/resume` 事件;trace/result 必须显示 `initialPromptInjected=true` 的首轮、skill refs 摘要、真实 provider profile、terminal status,以及 continuation 时 `initialPromptInjected=false`。如果回复只列出 Codex 默认系统 skill、不能识别 HWLAB 初始规则或需要用户长 prompt 才能触发 `hwpod`,验收失败。
|
||||
|
||||
@@ -238,6 +239,6 @@ HWLAB 旧 Code Agent 的业务 prompt 和 skill 注入必须迁移为 `ResourceB
|
||||
| cancel | 已实现最小闭环 | 已提供 run/command cancel API;pending cancel 会阻止新 runner Job,running runner 通过轮询触发 backend abort,终态写入 event、command state 和 run status。 |
|
||||
| SessionRef | 已实现最小持久化 | run 可携带 `sessionRef`,manager 保存 session/thread,runner 会按 threadId resume,result envelope 暴露脱敏 session 摘要;TTL/GC 仍按后续运维策略细化。 |
|
||||
| SessionRef | v0.1.1 已实现/已通过 HWLAB v0.2 原入口复测 | 在「metadata-only 最小持久化」基础上把 session 真实持久化:每个 session 绑 RWO PVC(`agentrun-v01-session-<sessionId>`),runner Job 把 PVC 直接挂到 `${CODEX_HOME}/<codex_rollout_subdir>`,codex app-server 自己落盘;HWLAB 原入口已验证 runner pod 删除后同 session/thread/PVC 可以恢复,仍禁止 fake 续接。 |
|
||||
| ResourceBundleRef | 已实现 Git-only materialization/待 promptRefs 与 skillRefs | run 可携带 `repoUrl + full commitId`,runner checkout 到 `AGENTRUN_WORKSPACE_ROOT` 下的隔离目录并记录 commit/tree/workspace 摘要;`toolAliases` 已实现;上传文件和对象存储仍不进入 v0.1;HWLAB 初始 prompt 与 skill 注入按本规格待补齐。 |
|
||||
| ResourceBundleRef | 已实现 Git-only materialization/promptRefs/skillRefs 装配 | run 可携带 `repoUrl + full commitId`,runner checkout 到 `AGENTRUN_WORKSPACE_ROOT` 下的隔离目录并记录 commit/tree/workspace 摘要;`toolAliases`、`promptRefs` thread-start 注入和 `skillRefs` registry 聚合已实现;上传文件和对象存储仍不进入 v0.1。 |
|
||||
| 同 run/runner 多 turn | 已实现最小闭环 | runner Job 在 idle timeout 内持续 poll 同一 run 的后续 command;普通 turn completed 不终结 run,bundle 只 materialize 一次,command result 按 commandId 独立聚合。 |
|
||||
| HWLAB v0.2 canary | 已实现/已通过 HWLAB v0.2 原入口复测 | HWLAB dispatcher adapter 已调 AgentRun 手动调度 API,并能转换 result/trace;MiniMax-M3 显式 session、provider profile 继承和 runner pod 删除后的同 session resume 已通过原入口 CLI 复测。 |
|
||||
|
||||
@@ -48,7 +48,7 @@ P0 最小 JSON 形态:
|
||||
| Provider credential | `ProfileRef` / `executionPolicy.secretScope.providerCredentials[]` | profile-scoped 只读 Secret projection,再复制到 per-run writable `CODEX_HOME` | 只服务 `codex`/`deepseek`/`minimax-m3` backend profile;缺失为 `secret-unavailable`,不得 fallback。 |
|
||||
| Git resource credential | `ResourceBundleRef.credentialRef` | 只服务 resource materialization 的 Git fetch/checkout | 只能用于拉取 `ResourceBundleRef.repoUrl` 对应代码,不得暴露给 agent shell 作为通用 GitHub token。 |
|
||||
| Tool credential | `executionPolicy.secretScope.toolCredentials[]` | 由 runner 按 tool scope 投影为文件或 env,并只暴露给当前 run/command 允许的工具 | 用于 GitHub PR、issue、UniDesk SSH passthrough、artifact registry 等 agent shell 工具能力;不等同于 AgentRun integration,不触发 GitHub sink/OA/Event 之类外部动作记录。 |
|
||||
| Short-lived execution context | runner-job `transientEnv` | 单次 Job env,response/dry-run/event 只显示 name/hash | 只用于业务 dispatcher 生成的短期上下文,例如 HWLAB device-pod session token 和非敏感服务地址;不得承载 provider credential、GitHub token、UniDesk SSH client token、长期 SSH key 或可复用 API key。 |
|
||||
| Short-lived execution context | runner-job `transientEnv` | 单次 Job env,response/dry-run/event 只显示 name/hash | 只用于业务 dispatcher 生成的短期或 owner-scoped runtime context,例如 HWLAB HWPOD runtime API key、runtime URL 和非敏感服务地址;不得承载 provider credential、GitHub token、UniDesk SSH client token 或长期 SSH key。 |
|
||||
|
||||
`toolCredentials` 是装配 SPEC 中的受控扩展槽位,用于把 agent 运行时需要的外部工具授权从“临时 env”收敛为 SecretRef。`v0.1` 支持 GitHub PR/issue 与 UniDesk SSH passthrough 所需的最小 env projection,例如:
|
||||
|
||||
@@ -196,10 +196,10 @@ HWLAB Workbench 的 project/workspace 不属于 RuntimeAssembly 四要素,也
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "device-pod-cli",
|
||||
"path": "skills/device-pod-cli/SKILL.md",
|
||||
"name": "hwpod-cli",
|
||||
"path": "skills/hwpod-cli/SKILL.md",
|
||||
"required": true,
|
||||
"aggregateAs": "device-pod-cli"
|
||||
"aggregateAs": "hwpod-cli"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -250,7 +250,7 @@ HWLAB Workbench 的 project/workspace 不属于 RuntimeAssembly 四要素,也
|
||||
- GitHub PR、issue、UniDesk SSH passthrough 或其他 shell/tool 授权只能通过 `executionPolicy.secretScope.toolCredentials[]` 的 SecretRef 装配进入 runner。
|
||||
- CLI、Queue task、runner job response、dry-run manifest、event 和日志不得输出 token、SSH private key 或 credential 文件正文。
|
||||
- 缺少 tool credential 时,run/command 必须返回可判定的 `secret-unavailable`、`tenant-policy-denied` 或明确 blocker,不能伪装成 agent 业务失败。
|
||||
- `transientEnv` 不得用于 GitHub token、UniDesk SSH client token、长期 SSH key、provider API key 或其他可复用 credential。
|
||||
- `transientEnv` 不得用于 GitHub token、UniDesk SSH client token、长期 SSH key、provider API key 或其他 provider/tool 可复用 credential。HWLAB dispatcher 生成并限定 owner/HWPOD/runtime scope 的 `HWLAB_API_KEY` 可以作为单次 runner Job runtime context 进入 `transientEnv`,但 AgentRun 不保存、不解释、不回显其明文。
|
||||
|
||||
### A3 SessionRef 验收
|
||||
|
||||
@@ -286,5 +286,5 @@ HWLAB Workbench 的 project/workspace 不属于 RuntimeAssembly 四要素,也
|
||||
| `ProfileRef` | 已实现/已通过 HWLAB v0.2 原入口复测 | `codex`、`deepseek` 与 `minimax-m3` 已通过 SecretRef、writable runtime home 和真实 stdio turn 验证;MiniMax-M3 已通过 HWLAB 显式 session 原入口复测,后续只允许作为 profile/config/SecretRef 选择,不新增直连 backend。 |
|
||||
| `SessionRef` | 已实现最小持久化 | manager 持久化 `sessionId/conversationId/threadId`,run 创建会解析既有 session,runner 按 threadId resume;session 不保存 credential 文件,TTL/GC 后续细化。 |
|
||||
| `SessionRef` | v0.1.1 已实现/已通过 HWLAB v0.2 原入口复测 | manager 持久化 `sessionId/conversationId/threadId` + 每个 session 绑 RWO PVC(`agentrun-v01-session-<sessionId>`),runner Job 把 PVC 直接挂到 `${CODEX_HOME}/<codex_rollout_subdir>`,codex app-server 自己落盘;runner pod 删除后 replacement runner 仍复用同一 SessionRef/PVC/thread,禁止 copy/restore、replacement threadId 和 fake resume。 |
|
||||
| `ResourceBundleRef` | 已实现 Git-only materialization/待 promptRefs 与 skillRefs 实现 | `repoUrl + full commitId` 已进入 run schema 和 runner checkout,workspace 受 `AGENTRUN_WORKSPACE_ROOT` 限制,event/result 记录 commit/tree/workspace 摘要;`toolAliases` 已实现,`promptRefs` thread-start 注入和 `skillRefs` registry 聚合待实现。 |
|
||||
| `ResourceBundleRef` | 已实现 Git-only materialization/promptRefs/skillRefs 装配 | `repoUrl + full commitId` 已进入 run schema 和 runner checkout,workspace 受 `AGENTRUN_WORKSPACE_ROOT` 限制,event/result 记录 commit/tree/workspace 摘要;`toolAliases`、`promptRefs` thread-start 注入和 `skillRefs` registry 聚合已实现。 |
|
||||
| `toolCredentials` | 已实现最小 env projection | GitHub PR 和 UniDesk SSH passthrough 等 agent shell/tool 授权通过装配 SPEC 的 SecretRef 进入 runner;v0.1 支持 `tool=github` 与 `tool=unidesk-ssh`、`projection.kind=env`,runner Job 使用 `valueFrom.secretKeyRef` 注入,不用 `transientEnv` 绕过。 |
|
||||
|
||||
@@ -120,7 +120,7 @@ Run 的 `executionPolicy.secretScope` 只能包含引用,不包含值。provid
|
||||
|
||||
## runner-job transientEnv
|
||||
|
||||
`transientEnv` 用于承接调度方生成的短期、单次 runner Job 运行上下文,例如 HWLAB Code Agent 的 device-pod session token、API URL 和非敏感 UniDesk frontend 地址。它不是 provider credential、tool credential,也不是 run durable fact。
|
||||
`transientEnv` 用于承接调度方生成的短期、单次 runner Job 运行上下文,例如 HWLAB Code Agent 的 owner-scoped HWPOD/runtime API key、runtime URL 和非敏感 UniDesk frontend 地址。它不是 provider credential、tool credential,也不是 run durable fact。
|
||||
|
||||
规则:
|
||||
|
||||
@@ -128,8 +128,8 @@ Run 的 `executionPolicy.secretScope` 只能包含引用,不包含值。provid
|
||||
- manager 不对 `transientEnv` 条目数量设固定上限,只校验数组形态、env name 合法且唯一、value 非空和单值长度;runner job payload hash 只纳入 env name 与 value hash。
|
||||
- response、runner job status、event 和 dry-run manifest 只能展示 env name、count 和 `valuesPrinted=false`;dry-run manifest 中的 transient env value 必须显示为 `REDACTED`。
|
||||
- 正式 Kubernetes Job manifest 会把 value 注入到本次 runner container env;该 token 必须由调度方控制 TTL、权限和业务授权范围。
|
||||
- AgentRun 不解释 HWLAB device-pod 权限,也不把业务鉴权做成通用 policy;AgentRun 只负责不持久化、不回显、不扩散这类短期 env value。
|
||||
- GitHub token、UniDesk SSH client token、SSH private key、provider API key、registry token 等可复用 credential 不得通过 `transientEnv` 注入;必须先进入装配 SPEC 的 SecretRef 路径。
|
||||
- AgentRun 不解释 HWLAB HWPOD 权限,也不把业务鉴权做成通用 policy;AgentRun 只负责不持久化、不回显、不扩散这类短期 env value。
|
||||
- GitHub token、UniDesk SSH client token、SSH private key、provider API key、registry token 等 provider/tool 可复用 credential 不得通过 `transientEnv` 注入;必须先进入装配 SPEC 的 SecretRef 路径。HWLAB dispatcher 生成并限定 owner/HWPOD/runtime scope 的 `HWLAB_API_KEY` 属于业务运行上下文,可以作为单次 runner Job env 透传,但不得变成 AgentRun durable fact 或通用授权。
|
||||
|
||||
## 分发路径
|
||||
|
||||
|
||||
@@ -106,11 +106,11 @@ CLI 与 RESTful API 可以复用同一个真实 run 做联调。若两者观察
|
||||
| SessionRef | 连续两轮使用同一 sessionRef 或 conversation/session/thread 摘要 | 第二轮可 resume backend thread;session 不包含 credential 文件或完整 CODEX_HOME。 |
|
||||
| ResourceBundleRef | 使用 `repoUrl + full commitId` 启动 runner | runner checkout 到允许 workspace,event/result 能回答 repo、commit、workspace 摘要;不使用 branch/tag/HEAD 或 host path。 |
|
||||
| Resource prompt/skill assembly | 使用同一 `ResourceBundleRef` 指定 `promptRefs`、`skillRefs` 和 `toolAliases` | 新 thread 首轮注入 initial prompt 和 skill facts;resume 不重复注入;required prompt/skill 缺失 blocked;不使用用户长 prompt、旧硬编码 prompt、镜像 `/app/skills` 或默认 Codex skill registry 替代。 |
|
||||
| DS 短 prompt 探测 | 通过正式 CLI/Web 等价入口向真实 `backendProfile=deepseek` 发送短 prompt | “可见 skill”回复包含业务 `skillRefs` 而不只是 Codex 默认系统 skill;“当前 HWLAB 初始规则”能回答 hwpod/禁止路径;“编译 D601-F103-V2”能触发标准 Device Pod 路径。 |
|
||||
| DS 短 prompt 探测 | 通过正式 CLI/Web 等价入口向真实 `backendProfile=deepseek` 发送短 prompt | “可见 skill”回复包含业务 `skillRefs` 而不只是 Codex 默认系统 skill;“当前 HWLAB 初始规则”能回答 hwpod/HWPOD 四要素/禁止路径;“编译 D601-F103-V2”能触发 `hwpod-cli -> hwpod-compiler-cli -> /v1/hwpod-node-ops -> hwpod-node` 正向链路。 |
|
||||
| ProfileRef/SecretRef | 分别验证 `codex`、`deepseek` 与 `minimax-m3` profile | 只使用当前 profile SecretRef;缺失时 `secret-unavailable`,不 fallback,不泄露 Secret 值。 |
|
||||
| bounded output | 触发工具/命令输出摘要 | result/event 只含摘要、字节数、截断标记和必要引用,不把大 stdout/stderr 塞入单个 JSON 响应。 |
|
||||
|
||||
这组验收吸收 HWLAB v0.2 的成熟判定口径,但不验证 HWLAB 用户鉴权、device-pod 授权、Workbench UI、`traceId -> runId` 业务映射或 HWLAB result/trace schema 转换。
|
||||
这组验收吸收 HWLAB v0.2 的成熟判定口径,但不验证 HWLAB 用户鉴权、HWPOD 授权、Workbench UI、`traceId -> runId` 业务映射或 HWLAB result/trace schema 转换。
|
||||
|
||||
## 发布判定
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
image: "127.0.0.1:5000/agentrun/agentrun-mgr@sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
attemptId: "attempt_selftest",
|
||||
sourceCommit: "self-test",
|
||||
transientEnv: [{ name: "HWLAB_DEVICE_POD_SESSION_TOKEN", value: "test-token-material", sensitive: true }],
|
||||
transientEnv: [{ name: "HWLAB_API_KEY", value: "hwl_live_selftest", sensitive: true }],
|
||||
});
|
||||
assert.equal(rendered.dryRun, true);
|
||||
assert.equal(rendered.mutation, false);
|
||||
@@ -42,8 +42,8 @@ const selfTest: SelfTestCase = async (context) => {
|
||||
assertRunnerJobUsesWritableCodexHome(rendered.manifest as JsonRecord, context.codexHome, "codex-0", "/var/run/agentrun/secrets/codex-0");
|
||||
assertRunnerJobUsesToolCredential(rendered, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN");
|
||||
assertRunnerJobUsesToolCredential(rendered, "UNIDESK_SSH_CLIENT_TOKEN", "agentrun-v01-tool-unidesk-ssh", "UNIDESK_SSH_CLIENT_TOKEN");
|
||||
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "HWLAB_DEVICE_POD_SESSION_TOKEN"), "REDACTED");
|
||||
assert.deepEqual((((rendered.transientEnv as JsonRecord).names) as string[]), ["HWLAB_DEVICE_POD_SESSION_TOKEN"]);
|
||||
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "HWLAB_API_KEY"), "REDACTED");
|
||||
assert.deepEqual((((rendered.transientEnv as JsonRecord).names) as string[]), ["HWLAB_API_KEY"]);
|
||||
assertNoSecretLeak(rendered);
|
||||
|
||||
await assert.rejects(
|
||||
@@ -117,9 +117,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
commandId: jobItem.commandId,
|
||||
attemptId: "attempt_selftest_create",
|
||||
transientEnv: [
|
||||
{ name: "HWLAB_DEVICE_POD_SESSION_TOKEN", value: "test-token-material", sensitive: true },
|
||||
{ name: "HWLAB_CLOUD_API_URL", value: "http://cloud.test", sensitive: true },
|
||||
{ name: "HWLAB_DEVICE_POD_API_URL", value: "http://device-pod.test", sensitive: true },
|
||||
{ name: "HWLAB_API_KEY", value: "hwl_live_selftest", sensitive: true },
|
||||
{ name: "HWLAB_RUNTIME_API_URL", value: "http://runtime-api.test", sensitive: true },
|
||||
{ name: "HWLAB_RUNTIME_WEB_URL", value: "http://runtime-web.test", sensitive: true },
|
||||
{ name: "HWLAB_RUNTIME_NAMESPACE", value: "hwlab-v02", sensitive: true },
|
||||
@@ -132,11 +130,11 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
});
|
||||
assert.equal((created as { mutation?: unknown }).mutation, true);
|
||||
assert.equal(((created as JsonRecord).retention as JsonRecord).ttlSecondsAfterFinished, 86_400);
|
||||
assert.deepEqual((((created as JsonRecord).transientEnv as JsonRecord).names) as string[], ["HWLAB_DEVICE_POD_SESSION_TOKEN", "HWLAB_CLOUD_API_URL", "HWLAB_DEVICE_POD_API_URL", "HWLAB_RUNTIME_API_URL", "HWLAB_RUNTIME_WEB_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_RUNTIME_LANE", "HWLAB_RUNTIME_ENDPOINT_SOURCE", "HWLAB_RUNTIME_ENDPOINT_LOCKED", "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME", "UNIDESK_MAIN_SERVER_IP"]);
|
||||
assert.deepEqual((((created as JsonRecord).transientEnv as JsonRecord).names) as string[], ["HWLAB_API_KEY", "HWLAB_RUNTIME_API_URL", "HWLAB_RUNTIME_WEB_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_RUNTIME_LANE", "HWLAB_RUNTIME_ENDPOINT_SOURCE", "HWLAB_RUNTIME_ENDPOINT_LOCKED", "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME", "UNIDESK_MAIN_SERVER_IP"]);
|
||||
const manifest = JSON.parse(await readFile(createdManifest, "utf8")) as JsonRecord;
|
||||
assert.equal((manifest.spec as JsonRecord).ttlSecondsAfterFinished, 86_400);
|
||||
assert.equal(runnerEnvValue(manifest, "HWLAB_DEVICE_POD_SESSION_TOKEN"), "test-token-material");
|
||||
assert.equal(runnerEnvValue(manifest, "HWLAB_CLOUD_API_URL"), "http://cloud.test");
|
||||
assert.equal(runnerEnvValue(manifest, "HWLAB_API_KEY"), "hwl_live_selftest");
|
||||
assert.equal(runnerEnvValue(manifest, "HWLAB_RUNTIME_API_URL"), "http://runtime-api.test");
|
||||
assert.equal(runnerEnvValue(manifest, "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME"), "1");
|
||||
assert.equal(runnerEnvValue(manifest, "UNIDESK_MAIN_SERVER_IP"), "https://unidesk.example.test");
|
||||
assertRunnerJobUsesToolCredential({ manifest, toolCredentials: (created as JsonRecord).toolCredentials } as JsonRecord, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN");
|
||||
|
||||
@@ -48,7 +48,10 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
const assemblyBundle: LocalBundle = {
|
||||
...bundle,
|
||||
promptRefs: [{ name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true }],
|
||||
skillRefs: [{ name: "device-pod-cli", path: "skills/device-pod-cli/SKILL.md", required: true, aggregateAs: "device-pod-cli" }],
|
||||
skillRefs: [
|
||||
{ name: "hwpod-cli", path: "skills/hwpod-cli/SKILL.md", required: true, aggregateAs: "hwpod-cli" },
|
||||
{ name: "hwpod-ctl", path: "skills/hwpod-ctl/SKILL.md", required: true, aggregateAs: "hwpod-ctl" },
|
||||
],
|
||||
};
|
||||
const first = await createHwlabRun(client, context, bundle, "hwlab-session-1", "hello bundle", "hwlab-command-1");
|
||||
const created = await client.post(`/api/v1/runs/${first.runId}/runner-jobs`, { commandId: first.commandId, idempotencyKey: "hwlab-trace-1" }) as JsonRecord;
|
||||
@@ -86,7 +89,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
const hwpod = await execFile(path.join(resourceBin, "hwpod"), ["profile", "list"]);
|
||||
assert.match(hwpod.stdout, /"argv":\["profile","list"\]/u);
|
||||
await writeFile(path.join(resourceBin, "blocked"), "#!/usr/bin/env sh\necho existing\n", "utf8");
|
||||
const blockedRun = await createHwlabRun(client, context, { ...bundle, toolAliases: [{ name: "blocked", path: "tools/device-pod-cli.mjs", kind: "node-script" }] }, "hwlab-session-blocked-alias", "blocked alias", "hwlab-command-blocked-alias");
|
||||
const blockedRun = await createHwlabRun(client, context, { ...bundle, toolAliases: [{ name: "blocked", path: "tools/hwpod-cli.mjs", kind: "node-script" }] }, "hwlab-session-blocked-alias", "blocked alias", "hwlab-command-blocked-alias");
|
||||
const blockedResult = await runOnce({ managerUrl: server.baseUrl, runId: blockedRun.runId, codexCommand: context.fakeCodexCommand, codexArgs: context.fakeCodexArgs, codexHome: context.codexHome, env: { CODEX_HOME: context.codexHome, AGENTRUN_WORKSPACE_ROOT: path.join(context.tmp, "workspaces-blocked"), AGENTRUN_RESOURCE_BIN_PATH: resourceBin }, oneShot: true });
|
||||
assert.equal(blockedResult.terminalStatus, "blocked");
|
||||
assert.equal(blockedResult.failureKind, "schema-invalid");
|
||||
@@ -115,7 +118,10 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
const firstAssemblyInput = turnInputText(assemblyInputs[0]);
|
||||
const secondAssemblyInput = turnInputText(assemblyInputs[1]);
|
||||
assert.match(firstAssemblyInput, /HWLAB v0\.2 runtime prompt self-test/u);
|
||||
assert.match(firstAssemblyInput, /device-pod-cli/u);
|
||||
assert.match(firstAssemblyInput, /hwpod-cli/u);
|
||||
assert.match(firstAssemblyInput, /hwpod-ctl/u);
|
||||
assert.match(firstAssemblyInput, /hwpod-compiler-cli/u);
|
||||
assert.match(firstAssemblyInput, /hwpod-node-ops/u);
|
||||
assert.match(firstAssemblyInput, /hwpod/u);
|
||||
assert.match(firstAssemblyInput, /list visible bundle skills/u);
|
||||
assert.match(secondAssemblyInput, /second turn should resume/u);
|
||||
@@ -128,10 +134,10 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
|
||||
const assemblyEnvelope = await client.get(`/api/v1/runs/${assemblyRun.runId}/commands/${assemblyRun.commandId}/result`) as JsonRecord;
|
||||
const assemblyResource = assemblyEnvelope.resourceBundleRef as JsonRecord;
|
||||
assert.deepEqual(((assemblyResource.promptRefs as JsonRecord).names), ["hwlab-v02-runtime"]);
|
||||
assert.deepEqual(((assemblyResource.skillRefs as JsonRecord).names), ["device-pod-cli"]);
|
||||
assert.deepEqual(((assemblyResource.skillRefs as JsonRecord).names), ["hwpod-cli", "hwpod-ctl"]);
|
||||
const assemblyMaterialized = assemblyResource.materialized as JsonRecord;
|
||||
assert.deepEqual(((assemblyMaterialized.promptRefs as JsonRecord).names), ["hwlab-v02-runtime"]);
|
||||
assert.deepEqual(((assemblyMaterialized.skillRefs as JsonRecord).names), ["device-pod-cli"]);
|
||||
assert.deepEqual(((assemblyMaterialized.skillRefs as JsonRecord).names), ["hwpod-cli", "hwpod-ctl"]);
|
||||
assert.equal(((assemblyMaterialized.initialPrompt as JsonRecord).available), true);
|
||||
assertNoSecretLeak(assemblyEnvelope);
|
||||
|
||||
@@ -215,31 +221,43 @@ async function createLocalGitBundle(context: SelfTestContext): Promise<LocalBund
|
||||
await execFile("git", ["init"], { cwd: repo });
|
||||
await writeFile(path.join(repo, "README.md"), "HWLAB bundle self-test\n", "utf8");
|
||||
await mkdir(path.join(repo, "tools"), { recursive: true });
|
||||
await writeFile(path.join(repo, "tools", "device-pod-cli.mjs"), "console.log(JSON.stringify({ ok: true, cli: 'hwpod-selftest', argv: process.argv.slice(2) }));\n", "utf8");
|
||||
await writeFile(path.join(repo, "tools", "hwpod-cli.mjs"), "console.log(JSON.stringify({ ok: true, cli: 'hwpod-cli-selftest', argv: process.argv.slice(2) }));\n", "utf8");
|
||||
await mkdir(path.join(repo, "internal", "agent", "prompts"), { recursive: true });
|
||||
await writeFile(path.join(repo, "internal", "agent", "prompts", "hwlab-v02-runtime.md"), [
|
||||
"HWLAB v0.2 runtime prompt self-test",
|
||||
"Use the hwpod alias for device-pod work.",
|
||||
"Do not invent fallback hardware paths.",
|
||||
"HWPOD means target device, workspace, debug probe, and io probe.",
|
||||
"Use hwpod-cli and hwpod-ctl through the hwpod alias for HWLAB work.",
|
||||
"Compile D601-F103-V2 through hwpod-cli -> hwpod-compiler-cli -> /v1/hwpod-node-ops -> hwpod-node.",
|
||||
"Do not invent fallback hardware paths or legacy profile routes.",
|
||||
].join("\n"), "utf8");
|
||||
await mkdir(path.join(repo, "skills", "device-pod-cli", "scripts"), { recursive: true });
|
||||
await writeFile(path.join(repo, "skills", "device-pod-cli", "SKILL.md"), [
|
||||
await mkdir(path.join(repo, "skills", "hwpod-cli", "scripts"), { recursive: true });
|
||||
await writeFile(path.join(repo, "skills", "hwpod-cli", "SKILL.md"), [
|
||||
"---",
|
||||
"name: device-pod-cli",
|
||||
"description: Use hwpod for HWLAB device-pod compile, status, job polling, and output inspection.",
|
||||
"name: hwpod-cli",
|
||||
"description: Use hwpod for HWLAB HWPOD compile, status, job polling, and output inspection.",
|
||||
"---",
|
||||
"# device-pod-cli",
|
||||
"Run `hwpod` from PATH for all device-pod operations.",
|
||||
"# hwpod-cli",
|
||||
"Run `hwpod` from PATH for HWPOD compile and execution operations.",
|
||||
].join("\n"), "utf8");
|
||||
await writeFile(path.join(repo, "skills", "device-pod-cli", "scripts", "device-pod-cli.mjs"), "console.log(JSON.stringify({ ok: true, cli: 'device-pod-skill-selftest' }));\n", "utf8");
|
||||
await execFile("git", ["add", "README.md", "tools/device-pod-cli.mjs", "internal/agent/prompts/hwlab-v02-runtime.md", "skills/device-pod-cli/SKILL.md", "skills/device-pod-cli/scripts/device-pod-cli.mjs"], { cwd: repo });
|
||||
await writeFile(path.join(repo, "skills", "hwpod-cli", "scripts", "hwpod-cli.mjs"), "console.log(JSON.stringify({ ok: true, cli: 'hwpod-cli-skill-selftest' }));\n", "utf8");
|
||||
await mkdir(path.join(repo, "skills", "hwpod-ctl", "scripts"), { recursive: true });
|
||||
await writeFile(path.join(repo, "skills", "hwpod-ctl", "SKILL.md"), [
|
||||
"---",
|
||||
"name: hwpod-ctl",
|
||||
"description: Inspect and control HWLAB HWPOD runtime state, node jobs, probes, and traces.",
|
||||
"---",
|
||||
"# hwpod-ctl",
|
||||
"Use hwpod-ctl for HWPOD runtime inspection and control-plane state.",
|
||||
].join("\n"), "utf8");
|
||||
await writeFile(path.join(repo, "skills", "hwpod-ctl", "scripts", "hwpod-ctl.mjs"), "console.log(JSON.stringify({ ok: true, cli: 'hwpod-ctl-skill-selftest' }));\n", "utf8");
|
||||
await execFile("git", ["add", "README.md", "tools/hwpod-cli.mjs", "internal/agent/prompts/hwlab-v02-runtime.md", "skills/hwpod-cli/SKILL.md", "skills/hwpod-cli/scripts/hwpod-cli.mjs", "skills/hwpod-ctl/SKILL.md", "skills/hwpod-ctl/scripts/hwpod-ctl.mjs"], { cwd: repo });
|
||||
await execFile("git", ["-c", "user.email=selftest@example.invalid", "-c", "user.name=AgentRun SelfTest", "commit", "-m", "bundle selftest"], { cwd: repo });
|
||||
const { stdout } = await execFile("git", ["rev-parse", "HEAD"], { cwd: repo });
|
||||
return { repoUrl: repo, commitId: stdout.trim() };
|
||||
}
|
||||
|
||||
async function createHwlabRun(client: ManagerClient, context: SelfTestContext, bundle: LocalBundle, sessionId: string, prompt: string, idempotencyKey: string, timeoutMs = 15_000): Promise<{ runId: string; commandId: string }> {
|
||||
const toolAliases = bundle.toolAliases ?? [{ name: "hwpod", path: "tools/device-pod-cli.mjs", kind: "node-script" }];
|
||||
const toolAliases = bundle.toolAliases ?? [{ name: "hwpod", path: "tools/hwpod-cli.mjs", kind: "node-script" }];
|
||||
const resourceBundleRef: ResourceBundleRef = { kind: "git", repoUrl: bundle.repoUrl, commitId: bundle.commitId, toolAliases, submodules: false, lfs: false };
|
||||
if (bundle.promptRefs) resourceBundleRef.promptRefs = bundle.promptRefs;
|
||||
if (bundle.skillRefs) resourceBundleRef.skillRefs = bundle.skillRefs;
|
||||
|
||||
Reference in New Issue
Block a user