Commit Graph
118 Commits
Author SHA1 Message Date
chenchenandClaude Opus 4.8 8fe1f5e131 fix(agent,secret): enforce CRYPTO_SECRET for agent deploy (#31) and guard unscoped vault purge (#33)
#31: HeicodeDeployAgent now refuses to deploy unless CRYPTO_SECRET is explicitly
configured, so the per-agent access_token is sealed with a key that survives a
container restart. common.CryptoSecret is never literally "" (defaults to
uuid/SessionSecret), so the sealAgentToken plaintext fallback was effectively
unreachable; the real hazard is an ephemeral random seal key making tokens
undecryptable after restart. Dev-only override: HEICODE_ALLOW_PLAINTEXT_AGENT_TOKEN_IN_DEV=true.
Verified prod container has CRYPTO_SECRET set (64 chars) -> deploy stays allowed.

#33: StartSecretPurgeTask refuses to start a whole-vault purge when
HEICODE_SECRET_PURGE_NAME_PREFIX is empty unless HEICODE_SECRET_PURGE_VAULT_EXCLUSIVE=true,
so HM never permanently purges another tenant's soft-deleted secrets in a shared
vault. Logs the resolved purge scope at startup.

Both gates extracted into pure, unit-tested helpers (agentTokenSealKeyConfigured,
secretPurgeScopeAllowed). Affects: Manager only (Agent deploy + Secret lifecycle).
No Client/Swarm/billing/audit schema change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:33:47 +08:00
818f1412d5 feat(heicode): 客户端错误遥测上报端点(默认关闭)(#24) (#27)
* feat(heicode): 客户端错误遥测上报端点(默认关闭)(#24)

按客户端 winos#23 契约 + 权威 schema 实现,结合 HM 入库逻辑:

- POST /api/heicode/telemetry/events,挂 UserOrV2DeviceAuth(设备配对鉴权)。
- 接收顶层 JSON 数组(非包裹),批量 1-20、<=256KB;校验 body client_id 等于已验签
  设备(X-Heicode-Device-Id),不一致 403;无设备身份拒绝。
- 真实 4xx/5xx 码(400 非数组、413 超限、403 设备、410 关闭),让客户端"4xx 丢弃"
  语义生效;2xx 返回 {accepted:n}。
- 独立表 telemetry_events,与计费完全隔离:不写 consume log、不碰 quota。
- 宽松入库(最大化采集):未知枚举 / 哨兵 app_version(0.0.0-heicode-local)/ 缺字段
  原样入库;schema_version 缺省 1;stack_top/context 存 TEXT(JSON);记 user_id 作
  device 到 account 关联 + 服务端 received_at。
- 默认 HEICODE_TELEMETRY_ENABLED=false 时返回 410(kill switch);隐私政策更新 +
  端点下发形态确认前不开启外发。

测试用客户端仿真夹具:parseTelemetryBatch / toModel 映射与默认 / 拒绝非数组,全过。

Refs #24(上线门槛:隐私政策 §2 如实披露 + 下发形态 + 去重;见工单评论)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(heicode): GET /api/heicode/config 下发 telemetry 配置/kill-switch (#24)

按客户端 #24 拍板:选独立 config 端点(而非塞登录响应),便于 kill switch 在
会话内传导、不依赖重登录。返回 telemetry 块 {enabled, endpoint, max_batch,
flush_interval_sec};enabled 取 HEICODE_TELEMETRY_ENABLED(默认 false)。
未鉴权全局只读(同 capabilities 姿态)。

测试 heicode_config_test.go:enabled 反映 env、endpoint 与摄入路由一致、缺省 false。

Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(telemetry): 服务端二次脱敏 stack_top/context + 明确生产门槛 (#24 review)

按 Fasthei 复审意见补隐私门槛:

- 服务端纵深防御脱敏:新增导出 model.RedactText(包装已上线的 redactAuditSecrets,
  #11),在 telemetry 入库前对 stack_top / context 再脱敏一遍(sk-/Bearer/URL
  token/JSON 密钥字段),即便客户端漏脱敏也不会把明文密钥落库。
- 测试 TestTelemetryToModel_RedactsSecrets:stack_top 里的 sk-、context 里的
  Bearer token 被打码,非密钥内容保留。
- 端点默认 HEICODE_TELEMETRY_ENABLED=false,关时 410;隐私政策披露完成前生产
  不得开启外发(见 #24 评论记录产品/法务状态)。

go build / vet 干净;controller 测试通过。

Refs #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: chenchen <chenchen@xinghanlab.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:16:29 +08:00
FastheiandGitHub 758e2d0342 Merge pull request #26 from xmindlab-heicode/feat/heicode-available-models
feat(heicode): /api/heicode/available-models 客户端模型列表收口 (#25)
2026-06-09 16:29:35 +08:00
FastheiandGitHub 3e0c935ad0 Merge pull request #20 from xmindlab-heicode/fix/secret-lifecycle-purge-rotate-cleanup
fix(secret): 闭合 #4 secret 生命周期 — 账户停用全量清理 + 30天 purge + 显式轮换
2026-06-09 16:25:51 +08:00
chenchenandClaude Opus 4.8 79e0e8b7d1 feat(heicode): 新增 /api/heicode/available-models,客户端模型列表收口 (#25)
客户端需要 Manager 下发用户可用模型作唯一来源(里程碑04 收尾)。

- GET /api/heicode/available-models(挂 UserOrV2DeviceAuth,与 /api/heicode/self 同
  设备配对鉴权)。
- 服务端解析:user.Group → service.GetUserUsableGroups → model.GetGroupEnabledModels
  (与 GetUserModels 同源),去重排序。
- 客户端安全形状 {model_id, display_name, default}:绝不下发 channelId/base_url/
  api_key/provider_type/单价(buildAvailableModelItems 纯函数 + 测试断言不泄露)。
- default 标记取 defaultAgentModelID()。

可选字段 capabilities/context_window/cost_tier 暂不下发(HM 无可靠来源,避免臆造;
客户端按可选处理)。etag/updated_at 可作后续。

Fixes #25

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 15:51:21 +08:00
7d56b54324 feat(agent): 部署上限按订阅档动态化,闭合 #8 个人5/团队8 (#21)
沿用订阅体系现成先例 MaxPurchasePerUser,给 SubscriptionPlan 加 MaxAgents:

- model: SubscriptionPlan.MaxAgents(0=回退全局默认)+ GetUserMaxAgents(取用户
  active 订阅档最高 MaxAgents,无则回退默认)。
- HeicodeDeployAgent: 部署上限改 model.GetUserMaxAgents(userID, 环境默认5)。
  团队8=管理员把团队档配成8;个人5=默认;代码不硬编码 tier。
- subscription 控制器: Create/Update 校验 MaxAgents>=0;Update updateMap 补 max_agents。
- 前端管理端套餐表单(plan-form/types/drawer)加「Agent 部署上限」字段 + zh i18n。

测试 model/subscription_max_agents_test.go 全过(无订阅/团队档/0回退/过期/多档取最高/非法用户)。
go build、go vet、前端 tsc 干净。

Fixes #8

Co-authored-by: chenchen <chenchen@xinghanlab.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:28:19 +08:00
af094dde27 feat(agent): 按部署 agent 聚合模型用量,闭合 #9 用量聚合 (#22)
#9 的审计脱敏半部已由 PR#11 完成。据评审报告(L95 usage/billing 结构完整、
L112 监控 mock metrics 属 AM)核实:HM 侧通用用量聚合并不缺,真正缺的是
agent 维度用量视图。

- model.SumAgentUsage(userId, tokenName, start, end):按 agent 铸币 token 名
  'agent:<deployment_id>' 聚合 consume 日志(quota/prompt/completion/调用数),
  COALESCE+COUNT/SUM 跨 SQLite/MySQL/PG。
- GET /api/heicode/agents/:deployment_id/usage:返回该 agent 用量 + quota_per_unit
  (raw,调用方换算,与 /api/heicode/self 同契约),支持 ?start=&end= 时间窗。
- 测试 model/agent_usage_test.go:聚合正确、排除他人/非消费/不同 token、时间窗、空名。

go build/vet 干净;测试 PASS。

Refs #9 (审计脱敏半部 PR#11 已完成;监控 mock metrics 属 AM 侧)

Co-authored-by: chenchen <chenchen@xinghanlab.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:27:48 +08:00
aa4041f604 fix(web): 钱包余额直读 /api/user/self(去 502) + 部署完成提示/动画 (#23)
两处 HM web 前端修复(此前已热部署到 VM 验证):

1. 钱包 502:wallet 不再经 /api/heicode-auth → mcp-server 读余额(那条会触发
   HM GetUser 同级权限检查 → 502)。HM 本就是 new-api,直接读用户自己的
   /api/user/self(GetSelf,无角色检查)。
2. 异步部署无反馈:deploy-agent-page 加快轮询(启动中 3s/稳定 30s)+ 检测
   Pending→running 弹「✅ 部署完成,已就绪」(失败弹错误)+「启动中」徽章转圈脉冲。

tsc 干净。

Co-authored-by: chenchen <chenchen@xinghanlab.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:27:19 +08:00
chenchenandClaude Opus 4.8 57f475b15a fix(secret): 闭合 #4 secret 生命周期 — 账户停用全量清理 + 30天 purge + 显式轮换
承接 PR#13(撤销时删 KV secret)的剩余生命周期:

- 账户停用/删除全量清理:新增 revokeUserResourceSecrets(userID),撤销该用户全部
  带 secret_ref 的资源绑定并 best-effort 软删 KV 凭证材料(DB 标 revoked 为权威,
  KV 故障只记日志不阻塞)。接入 4 个账户路径:ManageUser disable/delete、
  DeleteUser(管理员硬删)、DeleteSelf(自助删)。
- 30天 purge:新增 listDeletedSecrets(GET /deletedsecrets 分页)+ secretExpired
  纯函数 + purgeExpiredVaultSecrets;StartSecretPurgeTask 每日(可配)purge 软删
  ≥30天(可配)的 secret,master 节点执行,KV 未配置则 no-op,purge-protection
  下安全 no-op(403 容错)。env:HEICODE_SECRET_PURGE_ENABLED/RETENTION_DAYS/
  INTERVAL_HOURS/NAME_PREFIX。
- 轮换显式化:UpsertResourceSecret 已有 secret 时改用 rotateSecret(同名新版本)
  并审计日志,而非每次 putSecret。

测试:parseDeletedSecretsPage / secretExpired / lastPathSegment 纯函数 +
revokeUserResourceSecrets 在 KV 未配置下仍正确标记 revoked、不误伤他人/无密钥绑定。

Refs #2 (secret_store delete/rotate/purge 部分;Manager↔Swarm 契约属 Swarm 侧)
Fixes #4

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 00:55:09 +08:00
FastheiandGitHub 03b9b24e45 Merge pull request #18 from xmindlab-heicode/fix/async-agent-deploy
fix(agent): 部署 agent 改异步,避免 Azure 网关 504 + 回滚
2026-06-08 18:08:17 +08:00
FastheiandGitHub 206fee1f78 Merge pull request #14 from xmindlab-heicode/feat/heicode-self-quota-display-type
feat(heicode-self): /api/heicode/self 返回 raw quota + quota_per_unit(与网页台同源换算)
2026-06-08 17:52:46 +08:00
zsbgnw12andGitHub 9444cdb498 fix agent deploy caps
approved
2026-06-08 17:49:35 +08:00
chenchenandClaude Opus 4.8 349957360d fix(heicode-self): quota_display_type 补 CUSTOM + 统一大写 + 加 displayQuota 测试
按 Mem0ried 复审:
- quotaDisplayUnitLabel 补 QuotaDisplayTypeCustom 分支(原落 default→"USD",
  导致自定义币种站点 display_quota 算对但 type 误报 USD、客户端配错符号)。
- tokens 标签由小写 "tokens" 改为规范常量 TOKENS,displayQuota 的判断同步用常量,
  对外 quota_display_type 统一大写(与 USD/CNY/CUSTOM 一致,客户端按常量比较不再 mismatch)。
- 新增表驱动测试 heicode_self_test.go:displayQuota 四模式(USD 除 / CNY·CUSTOM 乘 /
  TOKENS 原值)+ per_unit<=0 防除零;quotaDisplayUnitLabel 四模式(含 CUSTOM 回归)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 17:34:03 +08:00
chenchenandClaude Opus 4.8 f6da9c541e fix(agent): 异步回填加 Pending 状态守卫,避免覆盖用户 stop/delete
按 Fasthei 复审意见修并发状态机边界:
- 成功/失败回填都加 WHERE deployment_id=? AND LOWER(status)='pending',
  让 Pending->running / Pending->failed 成为 own-or-nothing 转换。
- 成功但 0 行(用户在 AM 启动窗口内 stop/delete/cancel):重读记录记日志,
  删除 AM 刚起的 orphan runtime,绝不把记录改回 running。
- 失败仅在我方拥有 Pending->failed 转换时才 revoke model token;用户已 stop/delete
  时交给对应路径(delete 已 revoke、stop 有意保留 key),避免重复/遗漏。
- 补竞态测试 StoppedDuringStart_NoResurrect:AM 启动被 hold 到记录置 stopped 后才返回,
  断言记录保持 stopped、不回填 runtime_id、orphan runtime 被删除。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 17:17:40 +08:00
chenchenandClaude Opus 4.8 da854277d3 test(agent): 异步部署的确定性测试(不阻塞 + Pending→running / →failed)
- 慢 AM mock 验证 startTemplateAgentAsync 立即返回(不阻塞 30s)。
- Pending 记录在 AM 响应后自动回填 running + runtime_id + subdomain。
- AM 失败时记录标 failed + failure_reason,不卡在 Pending。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:48:29 +08:00
chenchenandClaude Opus 4.8 a1a5b6037b fix(agent): 部署 agent 改异步,避免网关 504 + 回滚
POST /api/heicode/agents 原本在请求里同步阻塞 ~30s 等 AM 起 agent。Azure 网关
~20s 超时 → 504 → 请求 context 被取消 → AM 调用中止 → 部署回滚(agent 建不出)。

改为:先把 agent 存为 Pending 立即返回;AM 启动放到 context.Background() 的后台
goroutine(脱离请求 context),成功回填 subdomain/runtime_id/status,失败标记
status=failed 并吊销已铸的模型 key。客户端经已合并的列表刷新看 Pending→running。

go build ./... 通过;controller vet 干净。

影响面:仅 Manager(HM) 部署路径。客户端契约:deploy 现在立即返回 Pending(原为
阻塞后 running 或 504);AM 失败在列表里表现为 status=failed(原为同步
RUNTIME_UNAVAILABLE)。客户端本就轮询列表等 running。不改计费/密钥/审计。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:43:46 +08:00
zsbgnw12andGitHub b3d864b29b fix(agent): refresh list status from AM
Refresh non-terminal agent statuses before returning /api/heicode/agents.
2026-06-08 16:22:21 +08:00
chenchenandClaude Opus 4.8 ce733defcb feat(heicode-self): complete quota contract for all display types (#14 review)
Returns raw quota + the full conversion params (quota_per_unit, quota_display_type,
usd_exchange_rate) AND server-computed display_quota / display_used_quota, so the
desktop client renders correctly in USD / CNY / tokens without a ×500000 hack or a
missing-exchange-rate bug. Contract of record (server applies it):
  USD    : raw / quota_per_unit
  CNY    : raw / quota_per_unit * usd_exchange_rate
  tokens : raw (no division)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 15:36:55 +08:00
efb0016f5c fix(secret): 撤销资源时删除 Key Vault 凭证材料 (#4) (#13)
#4:撤销资源此前只把 DB 状态置 "revoked",真凭证仍留在 Azure Key Vault。
现在 DeleteResource 同时删除 KV 中的 secret(best-effort:DB 撤销已提交,
外部 KV 失败只记日志、不阻塞响应)。

- secret_store.go:新增 deleteSecret / purgeSecret / rotateSecret
  (AKV REST,api-version=7.4,与 putSecret/getJSONSecret 同风格;404 幂等)。
  ※ 该文件受组织权限策略保护、自动化无读写权,本段由 HM owner 手动粘贴,
    审阅者请核对其与本意一致。
- resource.go:DeleteResource 在撤销事务提交后调用 deleteSecret(resource.SecretRef)。

本轮为 #4 最小修复(堵住"撤销后 KV 材料残留"的洞)。后续仍待:
purgeSecret 接入 30/90 天生命周期、轮换流程。

go build ./... 与 go vet 通过。

影响面(组织规则第 10 条):仅 Manager(HM) —— 涉密钥/安全/审计。
不影响 Client / Swarm / AM 契约 / CodeGW / 计费 / 发布链路。

Refs #4

Co-authored-by: chenchen <chenchen@xinghanlab.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 15:26:47 +08:00
chenchenandClaude Opus 4.8 9027f461c4 refactor(heicode-self): 返回 raw quota + quota_per_unit(与网页台同源换算)
按 HM owner 要求:客户端换算余额应与网页台完全一致。网页台用
raw_quota / quota_per_unit(web renderQuotaCompat,quotaPerUnit 默认 500000、
系统设置可配)。原 /api/heicode/self 预先除好(quotaToDisplayUnit)反而与网页台
不一致、并迫使客户端 ×500000 回推。

改为返回原始 quota/used_quota + quota_per_unit + quota_display_type,
客户端直接 quota / quota_per_unit 即可,和网页台同一公式,去掉所有缩放 hack。

移除不再使用的 quotaToDisplayUnit。go build/vet 通过。

影响面:仅 Manager(HM),服务桌面客户端余额展示(响应字段语义调整:quota 由
展示值改为原始值,新增 quota_per_unit)。需客户端同步按 quota/quota_per_unit 换算。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:26:52 +08:00
chenchenandClaude Opus 4.8 4476851286 feat(heicode-self): GET /api/heicode/self 返回 quota_display_type
桌面客户端余额查询应统一走 /api/heicode/self,其 quota/used_quota 已由
quotaToDisplayUnit 换算成展示单位(默认 USD = raw/QuotaPerUnit)。但响应未
告知客户端当前是哪种单位(USD/CNY/tokens),客户端易再除 500000 导致量级出错。

新增 quota_display_type 字段("USD" / "CNY" / "tokens"),客户端据此直接展示
quota、不再二次换算。

影响面:仅 Manager(HM),对客户端余额展示。新增响应字段、向后兼容;不影响
计费/密钥/审计/Swarm/AM。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 01:10:22 +08:00
zsbgnw12andGitHub 6d360454dd fix(relay): return errors instead of panic for Claude conversion stubs
Replace unimplemented ConvertClaudeRequest panic stubs with explicit not implemented errors across 11 relay adaptors.

Reviewed: narrow relay hardening only; no billing/secrets/Agent-Swarm/product semantic changes.
2026-06-07 19:59:21 +08:00
chenchenandClaude Opus 4.8 f4968b8072 fix(agent): review fixes — deployable-template gating, stopped-status guard, UI consistency
Backend:
- loadAgentTemplate now requires status='active' — a known template_key can no
  longer deploy a template an admin deactivated (matches the client list).
- refreshAgentStatus no longer lets AM's eventually-consistent live status
  resurrect a user-initiated "stopped" agent.
- HeicodeStopAgent persists via field-level Updates (not a stale full-row Save),
  matching refreshAgentStatus discipline.
- Drop dead amStartResult.AccessToken field (AM's token is never used; HM mints
  its own per-agent token).

Frontend:
- deploy-agent statusLabel: add the missing pending/starting → 启动中 branch so a
  just-deployed agent isn't shown as raw English fallback.
- cockpit 最近部署: map template_id → Chinese template name (consistent with the
  deploy/status pages) instead of showing the raw key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 11:06:49 +08:00
chenchenandClaude Opus 4.8 c05b27de6a revert: drop outbound env-key logging; root cause was AM stale prod image
Token IS transmitted by HM (confirmed); the agent didn't enforce it because AM
hadn't deployed the image containing the §5 check to production. So the debug
log is unnecessary — removed. Contract §0.1 updated: token-check is "code-ready,
pending AM prod image", not a HM gap. UI access-token/direct-URL display kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:28:53 +08:00
chenchenandClaude Opus 4.8 05de8f50e4 feat(agent): log outbound env keys to AM; show direct URL + access token in 运行状态
- runtime: log the env KEY NAMES (never values) sent to AM on start, so we can
  confirm AGENT_ACCESS_TOKEN is actually transmitted without leaking secrets.
- web/运行状态: detail panel now shows the full direct-connect URL (copyable) and
  the per-agent access token (masked + reveal + copy) with a hint that the client
  sends it as X-Agent-Access-Token. These are what's actually needed to hand the
  agent to a client / test it — previously only the bare hostname was shown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:16:28 +08:00
chenchenandClaude Opus 4.8 ed46acd4d3 docs(agent): fix stale runtime comments about token injection
amStartTemplateAgent no longer claims OPENAI_API_KEY is uninjected or that
access_token is left empty for a future V2 path: the handler now mints +
injects both OPENAI_API_KEY and the per-agent AGENT_ACCESS_TOKEN (forwarded
as-is), and HM returns its own access_token to the client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:29:40 +08:00
chenchenandClaude Opus 4.8 e326964362 feat(agent): per-agent client↔agent access token for per-user authorization
HM now mints a random per-agent access token at deploy, injects it into the
agent env (AGENT_ACCESS_TOKEN + HEICODE_AGENT_ID) and returns it to the
deploying client (agent list access_token). Only the owning user receives it,
so only they can drive the agent — closing the gap where any valid sk- could
drive any agent and exfiltrate its mounted resources.

AM authorizes the caller either locally (compare to its env token) or via the
new public POST /api/heicode/agent-access/verify {agent_id, access_token} ->
{valid, user_id} (constant-time compare, no info leak on miss). AM may opt out.

Docs: AM contract §3.1 + client API §6 updated; access_token no longer empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:58:10 +08:00
chenchenandClaude Opus 4.8 3a2ad36e5d fix(web): show unsupported resources (vm) disabled; Overview uses new-model framing
1) Deploy Sub Agent: stop hiding resource types AM doesn't support yet (vm) —
   show them but disabled with a "暂不支持" badge + tooltip, so users see their
   bindings instead of wondering where the vm went.
2) Overview (概述/cockpit): replaced the old task-model status cards
   (Running/Completed/Failed/裁决 + "code delivery runs") with template-agent
   semantics — Agent 总数 / 运行中 / 启动中 / 异常, "最近部署 · 我的 Agent",
   all Chinese. Dropped unused t()/useTranslation/PlayCircle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:59:12 +08:00
chenchenandClaude Opus 4.8 c4316f7fcd feat(web): new "运行状态" page (new-model agent status, not old task fields)
Re-add a run-status menu (I shouldn't have removed it) but showing ONLY what the
template-agent model actually has — no old 待确认/sub_agile/子任务流/智能体任务图/
产物/SK快照/合并时间线. New /agent-status page:
- summary counts (total / running / starting / other)
- agent list (template Chinese name, live status badge, subdomain, #resources)
- detail panel: live status (polls /agents/{id}/status from AM), copyable
  subdomain, mounted resources (names from /api/resources), runtime_id, timestamps.
Sidebar item "运行状态" + zh key added; build + tsc clean; route registered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:46:22 +08:00
chenchenandClaude Opus 4.8 d19920d1d0 fix(web): remove old "运行总览" task UI; Overview shows new template agents
The old /deployments (运行总览) + /tasks/$id pages rendered the new template agents
with meaningless old sub-task fields (待确认/sub_agile/子任务流/智能体任务图/产物/
SK快照/合并时间线 — all empty). Removed the whole old-model UI cluster and pointed
the Overview dashboard at the real new endpoint:

- deleted features/agent-console, features/agent-hub, features/tasks and the
  routes /deployments and /tasks/$id.
- cockpit (Overview) now lists deployed template agents from /api/heicode/agents
  (status counts + recent agents), not the old listAgentDeployments.
- removed the "运行总览" sidebar item + dead /deployments links in footer /
  top-nav / sidebar-config.
- routeTree regenerated; frontend build + tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:32:03 +08:00
chenchenandClaude Opus 4.8 436b079c16 feat(agent): inject OPENAI_API_KEY (minted sk-) so the agent can call HM /v1
Deploy now also passes OPENAI_API_KEY (the 3rd of OPENAI_BASE_URL/MODEL_NAME/
OPENAI_API_KEY AM expects). HM mints a hidden, unlimited-quota sk- token for the
user per agent ("sk-"+key, billed to the user), injects it as OPENAI_API_KEY, and
stores the token id on the deployment. The token is revoked on delete and rolled
back if AM start / persist fails (no leaked keys). gateway accepts Bearer
sk-<key> (middleware strips sk-).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:44:17 +08:00
chenchenandClaude Opus 4.8 1d81002c2d fix(agent): default MODEL_NAME=gpt-5.4 for started agents (not template tier)
Preset templates carry a Claude-style frontmatter model (opus/sonnet) which is
NOT a model on the HM gateway. Passing it as MODEL_NAME would make the agent's
model calls fail. Now MODEL_NAME defaults to the gateway model gpt-5.4
(env AGENT_RUNTIME_DEFAULT_MODEL); a non-Claude-tier template model is honored.
Tests + AM contract doc updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:25:21 +08:00
chenchenandClaude Opus 4.8 b33bcf90bd feat(device): desktop client self-logout endpoint
POST /api/devices/logout (UserOrV2DeviceAuth): a device-signed client revokes its
OWN bound token via the signed X-Heicode-Device-Id (cannot touch other devices);
a session/JWT caller may pass {device_id}. Idempotent. The existing DELETE
/api/devices/:id revoke is session-only, so device clients had no self-logout —
this closes that gap. Documented in the client API doc §1.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:15:47 +08:00
chenchenandClaude Opus 4.8 557fb2b829 test(agent): fix status round-trip path assertion to /agents/{id}
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:48:09 +08:00
chenchenandClaude Opus 4.8 f1637c3ecd fix(agent): resilient delete — always remove local record even if AM delete fails
Real test showed AM's DELETE /agents/{id} currently 500s (AM bug), which left the
HM record permanently undeletable. HeicodeDeleteAgent now treats the AM delete as
best-effort: on AM failure it logs + still removes the local record and returns
runtime_cleanup:"failed" so the user can always clear their list. (Stop stays
strict — AM has no /stop endpoint yet, pending their confirmation.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:47:23 +08:00
chenchenandClaude Opus 4.8 27459bb476 fix(agent): align AM lifecycle paths to /agents/{id} (real-test: create works)
Real production test (user account) confirmed HM->AM POST /agents creates a real
agent (returned subdomain + status). But stop/delete still used the old
/api/agent/agents/{id} defaults and 404'd. Aligned status/stop/delete defaults to
the same namespace as create: /agents/{id}, /agents/{id}/stop. AM contract doc
notes these are HM's best guess pending AM's confirmation of the real lifecycle
endpoints (their doc only specified create).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:41:34 +08:00
chenchenandClaude Opus 4.8 074a3cc7e7 feat(agent): align HM to AM's coding_a2a_agent contract
Adapt HM's template-agent integration to AM's actual CODING_A2A API (per their
doc), keeping it isolated in agent_template_runtime.go:

- start payload -> AM's POST /agents { name, template:"coding_a2a_agent",
  framework:"A2A", config:{user_id,...}, env } with the template .md folded into
  env.AGENT_INSTRUCTION_TEXT, template_key -> AGENT_ROLE_NAME, model gateway via
  OPENAI_BASE_URL + MODEL_NAME (OPENAI_API_KEY left to the client per A2A request).
- response parse -> access_info.domain/external_ip -> subdomain, namespace/name
  -> runtime_id; AM issues no access_token (client uses A2A api_key).
- env names aligned to AM: GIT_DEFAULT_BRANCH, POSTGRES_* (was PG_*),
  AZURE_BLOB_ACCOUNT_NAME/CONTAINER/ACCOUNT_KEY (was BLOB_*); source keys aligned
  to the resource-binding form (db_name/username/database_password/access_key).
  Only AM-supported types (git/mysql/postgres/azure-blob); vm/redis/mongo/bucket
  now rejected as unsupported until AM adds them.
- frontend: resources page splits DB into MySQL/PostgreSQL (correct provider),
  drops vm; deploy page hides unsupported resource types.
- docs: AM contract + client doc updated to the real env names, payload, and the
  A2A direct-connect (message/send · message/stream) + api_key auth.
- tests updated for the new env names + AM payload/response shape. All green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:30:33 +08:00
chenchenandClaude Opus 4.8 ac32a2db51 fix(agent): encrypt agent access_token at rest (AES-256-GCM via CryptoSecret)
Closes the last review finding: the AM-issued access_token was stored in plaintext
in agent_deployments. Now sealed with common.SealWithCryptoSecret on write and
unsealed on read — the client still receives the plaintext token (no contract
change), but a DB dump no longer exposes live agent tokens. Legacy/plaintext
values and an unset CryptoSecret degrade gracefully (pass-through). Round-trip test added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:35:15 +08:00
chenchenandClaude Opus 4.8 7a48155568 fix(agent): apply review findings (backend data-integrity + frontend error UX)
Two review agents (backend + frontend) audited the template-agent feature. Real
findings fixed (verified the one false-positive — okOrThrow already reads the
top-level success because the response interceptor returns response, not
response.data):

Backend:
- buildAgentEnvFromBindings now filters status='active' so a revoked/disabled
  binding can't still have its KV secret resolved + injected.
- same-type binding env collision (e.g. two git repos -> GIT_REPO_URL) now errors
  instead of silently overwriting.
- refreshAgentStatus uses a field-level Updates (status/updated_at) instead of
  Save(full row) to avoid clobbering a concurrent stop/delete.
- tests added: same-type collision rejected, inactive binding rejected.

Frontend:
- deploy/stop/delete now pass noBusinessError so the global interceptor stops
  double-toasting the raw English backend message; okOrThrow+onError give one
  friendly Chinese error. Extracted okOrThrow/noBusinessError into lib/api.ts.
- resources page revoke + create + KV-secret-write now check success (okOrThrow)
  so a failed unbind / credential write no longer falsely reports success.
- clipboard copy wrapped in try/catch (no false "copied" on failure).
- resources group label shows Chinese resource-type names.

Go + frontend builds/tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:24:25 +08:00
chenchenandClaude Opus 4.8 16cca5df1c fix(agent): review fixes — success-check, template name, seed Once, update guard
Full review of the template-agent code surfaced and fixed:
- [frontend, critical] deploy/stop/delete treated HTTP-200-with-success:false as
  success (backend agentError returns 200 per new-api convention) -> a failed
  call wrongly toasted success. Added okOrThrow() that inspects the envelope and
  throws the server (or a friendly Chinese) message so onError fires.
- [frontend] "我的 Agent" showed the raw template key (architect) instead of the
  Chinese name; now resolves name via the templates list.
- [backend] ensureAgentTemplatesSeeded consumed sync.Once even when model.DB was
  nil (would permanently skip seeding) -> DB check moved outside the Once.
- [backend] AdminUpdateAgentTemplate could wipe name_zh/definition with empty
  values -> guard those critical fields.
- [security] warn when starting an agent with secret-bearing env over a non-HTTPS
  AM URL (secrets must not transit the network in clear).

Go + frontend build/tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 00:59:03 +08:00
chenchenandClaude Opus 4.8 8823bf5e5d feat(web): add "部署 Sub Agent" menu + page (pick resources + template -> deploy)
Customer-facing, plain Chinese (no env/technical jargon). New sidebar item
"部署 Sub Agent" -> /deploy-agent. The page:
- Step 1: pick which bound resources the agent may use (multi-select cards from
  /api/resources).
- Step 2: pick an agent template (cards show Chinese name + description from
  /api/heicode/agent-templates).
- Deploy -> POST /api/heicode/agents {template_id, binding_ids}.
- "我的 Agent" list (/api/heicode/agents) with status, copy-address, stop, delete.

Frontend builds clean (tsc + rsbuild; routeTree regenerated). zh label added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 00:25:12 +08:00
chenchenandClaude Opus 4.8 4605cf4f62 feat(agent): HM-maintained agent template library (md), sent to AM on deploy
Templates now live in HM (not AM). A template is a Claude-Code style subagent
.md definition; HM passes it to AM at deploy time.

- model AgentTemplate (agent_templates): template_key, name_zh / description_zh
  (Chinese display for the console), model, definition (full .md), source, status.
- 19 presets from oh-my-claudecode (MIT, NOTICE.md attribution) embedded via
  go:embed and idempotently seeded; Chinese name+desc mapping in code.
- GET /api/heicode/agent-templates now reads HM's library (Chinese name/desc),
  not AM. Admin CRUD at /api/agent-templates (AdminAuth).
- deploy loads the chosen template and sends {template_key, agent_definition (md),
  model, env, callback_url} to AM via a generic /api/agent/agents/start; removed
  the AM-template-listing path. AM adapter still isolated (amStartArgs).
- tests: frontmatter parse, seed (19 + 架构顾问), start round-trip asserts
  agent_definition in payload. All green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 00:05:08 +08:00
chenchenandClaude Opus 4.8 692506c9fc chore(agent): remove dead artifact-revision & cloud-deployment models (phase B)
After phase A removed the old client task layer, AgentArtifactRevision and
AgentCloudDeployment became unreferenced (their only consumers — the deleted
heicode_task_create/project_artifacts/artifact_edits/cloud_deploy — are gone).
Removed the structs + their AutoMigrate entries. Existing tables are left in
place (harmless, no model) and can be dropped later if desired.

Option B teardown: kept the shared platform backend still used by the web
console / runtime callbacks (admin /api/agent/*, approvals, leases, callbacks,
AgentArtifact, sk-snapshots, withDisplayStatus). Build + controller/model/router
tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:36:43 +08:00
chenchenandClaude Opus 4.8 3863296f85 chore(agent): remove old client sub-task API (phase A of legacy teardown)
The template-agent model replaces the old sub/swarm task orchestration. Removed
the entire client-facing task surface:
- routes: registerHeicodeTaskRoutes (all /heicode/{sub-agile,swarm}/tasks/*) and
  GET /heicode/deployment-targets.
- controllers deleted: heicode_task_create, heicode_client_routes,
  heicode_project_artifacts, heicode_artifact_edits, heicode_cloud_deploy
  (+ the agent_deliverable_secret_test that covered the deleted markdown-project
  parsing).

Build + controller/router/model tests green. Shared backend (AgentDeployment,
agent_runtime_client helpers, admin /api/agent/*) intentionally kept — trimmed in
the next phase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 23:29:22 +08:00
chenchenandClaude Opus 4.8 19fd4f3326 refactor(agent): built-in per-type env mapping; fix start timeout, orphan, guards
Replace the user-facing env_map mistake with a built-in env convention keyed by
resource type + provider — users never see/edit env names; they only fill plain
resource fields. Supports git (gitea/github/gitlab), vm, database
(mysql/pg/redis/mongo, with alias normalisation), storage (azure blob / bucket).
Lenient: missing optional fields are skipped; only unsupported type or a KV read
failure errors.

Other issues found in review and fixed:
- start timeout: template-agent start now uses a longer timeout (default 60s,
  AGENT_RUNTIME_START_TIMEOUT_SECONDS) since AM provisions synchronously — 5s
  would time out. amTemplateDo takes a per-call timeout.
- orphan agent: if AM start succeeds but the Manager record fails to persist, the
  orphan is rolled back (best-effort amDeleteTemplateAgent).
- findUserTemplateAgent now guards template_id<>'' so the new endpoints can't
  touch a legacy task deployment.
- binding_ids defaults to [] (not null).
- removed ResourceBinding.EnvMap field entirely.

Tests rewritten for the built-in convention (blob metadata-only, db provider
prefixes incl pg/mg aliases, git provider-agnostic names, ownership, unsupported
type, empty); adapter round-trip + router tests still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:48:29 +08:00
chenchenandClaude Opus 4.8 65a71f6b35 feat(agent): pull live template-agent status from AM (no callback needed)
- amGetAgentStatus: HM pulls a running agent's status from AM on demand
  (is it alive / crashed), isolated in the AM adapter like the rest.
- GET /api/heicode/agents/:id/status + status refresh on GET /:id detail,
  best-effort (keeps last-known status if AM is unreachable; never blocks read).
- tests: httptest-backed round-trip tests for amStartTemplateAgent and
  amGetAgentStatus against a fake AM (validates the full request/parse path);
  router registration still clean. All pass.

Note: no required-resource validation added — HM injects whatever bindings the
user selected into the agent .env (per env_map); AM reads what it needs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:04:00 +08:00
chenchenandClaude Opus 4.8 cdbc43baeb feat(agent): template-agent deploy/list/stop API (AM contract isolated)
HM-side logic for the template-agent model, written against a proposed AM
contract that is fully isolated in agent_template_runtime.go — when AM ships its
real API, only the default paths + response field names in that one file change.

- agent_template_runtime.go: AM adapter (list templates, start template agent
  with env, stop/delete) reusing the existing runtime config/url/envelope helpers.
- agent_template_handlers.go: GET /api/heicode/agent-templates; POST /api/heicode/
  agents (resolve bindings -> env, start via AM, persist subdomain+token);
  GET /agents, GET/:id, POST/:id/stop, DELETE/:id. Owner-scoped; env never logged.
- reuses AgentDeployment as the agent record (TemplateID/Subdomain/AccessToken/
  BindingIDsJSON) and buildAgentEnvFromBindings for env assembly.
- routes wired under /api/heicode (UserOrV2DeviceAuth), verified no registration
  panic (router tests pass).
- agent_template_test.go: 7 independent unit tests (env assembly metadata-only,
  ownership, secret-without-ref, unknown-source, empty; path substitution;
  response mapping) — all pass without AM/KV.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:55:24 +08:00
chenchenandClaude Opus 4.8 8ecab2c270 feat(controller): env assembly from resource bindings for template agents
buildAgentEnvFromBindings resolves selected resource bindings into a flat env map
for a template agent's .env at start: non-secret values from Metadata, secret
values resolved from Key Vault (getJSONSecret) only here, lazily, never persisted.
AM-independent foundation for the deploy-agent flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:43:34 +08:00
chenchenandClaude Opus 4.8 efdc360499 feat(model): add template-agent fields (reuse AgentDeployment + ResourceBinding.EnvMap)
Additive, AutoMigrate-friendly (new columns only, cross-DB safe). Foundation for
the template-agent + direct-connect model:
- AgentDeployment: TemplateID, Subdomain, AccessToken, BindingIDsJSON — reuse the
  existing deployment record as the deployed-agent record instead of a new table.
- ResourceBinding.EnvMap: declares which env vars a binding exposes to a template
  agent at start (non-secret from Metadata; secret resolved from KV only at start).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:41:19 +08:00
chenchenandClaude Opus 4.8 0eb754b1e1 refactor(web): Overview usage reuses WalletStatsCard (same as 模型与余额)
The wallet page already renders Current Balance / Total Usage / API Requests
from /api/user/self. Reuse that exact component (WalletStatsCard) on the
Overview instead of a hand-written block — DRY, identical numbers
($160.47 / $39.53 / 521 for the current user).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 11:08:45 +08:00