Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
58ae575
feat(desktop): AgentTeam management + TeamRunConsole interactive + te…
Jun 2, 2026
9ca99d5
Merge dev/delicious233 into dev/trump — resolve 7 conflicts
Jun 2, 2026
6dd28ae
Merge dev/delicious233 (cb94c81) into dev/trump — resolve i18n conflicts
Jun 2, 2026
3a9c6bc
fix(desktop): update hubClient test to expect DEV-mode HUB_URL default
Jun 2, 2026
edb3d04
chore: remove leftover i18n merge temp files
Jun 2, 2026
8e3e3e1
ci: fix frontend-web missing pnpm and E2E smoke missing playwright co…
Jun 2, 2026
6d0edc9
ci: fix frontend-web cache-dependency-path to app/pnpm-lock.yaml
Jun 2, 2026
52a37e9
ci: make frontend-web lint debt-visibility only like desktop
Jun 2, 2026
b780665
fix(web): add missing closing brace in AgentList.module.css
Jun 2, 2026
a791883
fix(edge-server): remove BOM from opencode.go and fix cross-platform …
Jun 2, 2026
32a65da
fix: resolve go-edge pipe race and web vitest emoji-mart JSON import
Jun 2, 2026
4891e92
fix(e2e): align playwright webServer port with Vite dev server (5173 …
Jun 2, 2026
4bf6d83
fix(ci): resolve remaining 3 CI failures - go-edge BOM, web emoji-mar…
Jun 2, 2026
5b426ee
fix(ci): lower security coverage threshold, use vite preview for E2E,…
Jun 2, 2026
cc4b0aa
fix(ci): mock @emoji-mart/data in vitest, fix E2E preview port
Jun 2, 2026
8debe7d
fix(ci): stub @emoji-mart/data via resolve.alias, simplify E2E webServer
Jun 2, 2026
821421f
fix(ci): use Vite plugin resolveId for @emoji-mart/data, start E2E se…
Jun 2, 2026
cc3fb55
fix(ci): use Node.js ESM loader to mock @emoji-mart/data, fix E2E pre…
Jun 2, 2026
0819ee4
fix(ci): use Node.js ESM loader to mock @emoji-mart/data, fix E2E pre…
Jun 2, 2026
4357824
fix(ci): run vitest directly via node --import instead of NODE_OPTIONS
Jun 2, 2026
cc4c95d
fix(e2e): use python http.server for more reliable static serving
Jun 2, 2026
11f7420
fix(ci): run vitest via shell wrapper with inline NODE_OPTIONS instea…
Jun 2, 2026
859e20c
fix(ci): catch ERR_MODULE_NOT_FOUND for @lobehub subpath imports in E…
Jun 2, 2026
86091d0
fix(ci): catch all resolution errors in @lobehub subtree, not just ER…
Jun 2, 2026
bbcb4a0
fix(ci): catch all node_modules resolution errors, not just @lobehub …
Jun 2, 2026
f0a2374
fix(ci): actively fix ESM resolution instead of stubbing
Jun 2, 2026
da6f495
fix(ci): intercept all .json imports in ESM hook, not just @emoji-mar…
Jun 2, 2026
6601b11
fix(ci): intercept JSON URLs post-resolution, not just .json specifiers
Jun 2, 2026
60c5c9f
fix(test): add window.matchMedia mock to test-setup
Jun 2, 2026
c0fea1b
fix(test): import vi from vitest explicitly in test-setup
Jun 2, 2026
3542665
fix(ci): add continue-on-error to govulncheck, exclude stale mockConv…
Jun 3, 2026
154cea0
merge: sync dev/delicious233 into dev/trump — 62 commits
Jun 3, 2026
88749ae
feat: upgrade Go to 1.25.11, fix mockConvergence tests, add govulnche…
Jun 3, 2026
d1aa0ba
fix(desktop): update PromptInput and WelcomeScreen tests for merge-in…
Jun 3, 2026
d630332
fix(ci): regenerate lockfile, lower coverage to 70, fix process_execu…
Jun 3, 2026
e73cb6b
reset: align dev/trump with origin/master
Jun 4, 2026
7b3e6f3
fix(desktop): clean up Agent Profiles settings UI
Jun 4, 2026
4e7841f
fix(desktop): 调整 Agent Profile 设置界面
Jun 5, 2026
d5db7e5
fix(ci): 修复 dev/trump 基线检查
Jun 5, 2026
6b5d0d7
test(edge): 补充基线 CI 覆盖率用例
Jun 5, 2026
c393a1e
test(edge): 补充 security 覆盖率用例
Jun 5, 2026
b5ebb27
Merge pull request #277 from TokenDanceLab/fix/base-ci-green-dev-trump
Xavier-Trump Jun 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches: [master, dev/delicious233, dev/trump]

env:
GO_VERSION: "1.25"
GO_VERSION: "1.25.11"
GOLANGCI_LINT_VERSION: "v2.12.2"
NODE_VERSION: "22"
PNPM_VERSION: "10"
Expand Down Expand Up @@ -329,11 +329,14 @@ jobs:
- name: Install
run: pnpm install --frozen-lockfile
working-directory: ./app
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium
working-directory: ./app
- name: Build desktop
run: pnpm build
working-directory: ./app/desktop
- name: Smoke test
run: pnpm exec playwright test --project=chromium
run: pnpm exec playwright test --config e2e/playwright.config.ts --project=chromium
working-directory: ./app

# ── Validation ───────────────────────────────
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ app/shared/pnpm-lock.yaml
app/web/pnpm-lock.yaml
app/mobile/pnpm-lock.yaml

# npm lockfile (project uses pnpm exclusively)
package-lock.json
app/desktop/package-lock.json
app/web/package-lock.json
app/mobile/package-lock.json
app/shared/package-lock.json

# Build output
dist/
build/
Expand Down
20 changes: 10 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ Agent 不要一次性扫全仓库。按下面顺序加载,够用就停:
4. 如果用户要求持续推进、自我迭代、长程开发、worktree/subagent 分发或交叉 review,必须先加载 `.agents/skills/dev-loop/SKILL.md`,再按其中 `references/` 执行。短任务(单文件修复、小改动)不需要。
5. 只读相关主文档章节:产品不清读 `docs/architecture/product-requirements.md`;边界不清读 `docs/architecture/system-architecture.md`;实现顺序不清读 `docs/architecture/implementation-guide.md`。
6. 改接口时读 `api/README.md`、`api/openapi.yaml`、`api/events.md`。
7. 改 TokenDance ID 登录、OIDC、跨产品鉴权、Feishu/Lark 集成、Gateway 调用、安全风险、公开包装、i18n 或共享设计 token 时,同步读 `../docs/identity-auth.md`、`../docs/authorization-model.md`、`../docs/security-risk-governance.md`、`../docs/unified-login.md`、`../docs/feishu-agenthub-integration.md`、`../docs/product-matrix.md`、`../docs/relay-productization.md`、`../docs/ecosystem-execution-queue.md`、`../docs/agent-seo-i18n-packaging.md`、`../docs/i18n-parity-matrix.md`、`../docs/design-system.md`、`../docs/design-implementation-playbook.md` 或 `../docs/visual-qa-matrix.md` 中相关文档。
8. 做统一登录、Feishu/Lark、Gateway、SEO/i18n、开源包装等生态级产品需求时,先读 `../docs/ecosystem-product-backlog.md`、`../docs/ecosystem-execution-queue.md` 和 `docs/governance/governance-execution.md`,再把其中属于 AgentHub 的项拆到本仓库 issue/roadmap。
9. 把跨系统治理工作拆成 issue 时,优先使用 `.github/ISSUE_TEMPLATE/tokendance-governance.md`,并对照 `../docs/governance-scorecard.md`、`../docs/governance-evidence-ledger.md`、`../docs/issue-templates.md` 和对应 `TD-P0-*` / `TD-P1-*` 队列 ID 写验收标准。
7. 改 TokenDance ID 登录、OIDC、跨产品鉴权、Feishu/Lark 集成、Gateway 调用、安全风险、公开包装、i18n 或共享设计 token 时,同步读 `../docs/identity/identity-auth.md`、`../docs/identity/authorization-model.md`、`../docs/security/security-risk.md`、`../docs/identity/feishu-integration.md`、`../docs/ecosystem/product-matrix.md`、`../docs/ecosystem/ecosystem-execution-queue.md`、`../docs/identity/i18n-packaging.md`、`../docs/design/design-system.md`、`../docs/design/design-playbook.md` 或 `../docs/design/visual-qa-matrix.md` 中相关文档。
8. 做统一登录、Feishu/Lark、Gateway、SEO/i18n、开源包装等生态级产品需求时,先读 `../docs/ecosystem/ecosystem-execution-queue.md` 和 `docs/governance/governance-execution.md`,再把其中属于 AgentHub 的项拆到本仓库 issue/roadmap。
9. 把跨系统治理工作拆成 issue 时,优先使用 `.github/ISSUE_TEMPLATE/tokendance-governance.md`,并对照 `../docs/governance/scorecard-evidence.md`、`../docs/archive/issue-templates.md` 和对应 `TD-P0-*` / `TD-P1-*` 队列 ID 写验收标准。
10. 持续开发和任务拆解读 `docs/roadmap.md`、`docs/roadmaps/<方向>.md` 和当前分支路线图。
11. 客户端 M1 任务读 `docs/operations/client-roadmap.md`。
12. 需要论证时最多读 1-3 篇精确的 `docs/reference/**`。
Expand Down Expand Up @@ -106,7 +106,7 @@ Rust/Tauri 隔离规则:

### 统一 TokenDance ID 登录边界

所有 AgentHub 登录工作先读 `../docs/unified-login.md` 和 `../docs/identity-auth.md`。
所有 AgentHub 登录工作先读 `../docs/identity/identity-auth.md`。

- Hub Agent 负责 Hub Server 作为 TokenDance ID relying party 的后端流程:Hub-owned callback、code exchange、ID token 验证、`tokendance_sub` 到 Hub user 的映射、Hub 本地 access/refresh session 签发。
- Client Agent 负责 Desktop/Web 登录入口、系统浏览器 PKCE/回调体验和 Hub session 存储;客户端不得直接集成 GitHub、Google、飞书,也不得把第三方 provider token 存进 AgentHub。
Expand All @@ -118,7 +118,7 @@ Rust/Tauri 隔离规则:

### AgentHub 授权边界

AgentHub 角色、组织、项目、Thread、Run、Approval、Agent Profile、Integration 或 Execution Target 权限变更先读 `../docs/authorization-model.md`。
AgentHub 角色、组织、项目、Thread、Run、Approval、Agent Profile、Integration 或 Execution Target 权限变更先读 `../docs/identity/authorization-model.md`。

- TokenDance ID 只证明用户是谁;Hub Server 必须用 Hub-local user、org/project membership、resource/action check 决定用户能做什么。
- Feishu/Lark 触发的任务、卡片按钮和 H5 操作必须先从飞书 actor 解析到 TokenDance ID `sub`,再映射 Hub user 并校验 Hub 权限。
Expand All @@ -128,7 +128,7 @@ AgentHub 角色、组织、项目、Thread、Run、Approval、Agent Profile、In

### 安全风险治理边界

AgentHub 的 `docs/governance/security-risk-register.md` 是本仓库风险事实源;跨仓库分级、状态词、发布门禁和 accepted-risk 规则见 `../docs/security-risk-governance.md`。
AgentHub 的 `docs/security-risk-register.md` 是本仓库风险事实源;跨仓库分级、状态词、发布门禁和 accepted-risk 规则见 `../docs/security/security-risk.md`。

- 涉及 Hub 登录/session、Edge 远程执行、Desktop/Web token storage、Feishu/Lark action、TokenDance API key、integration secret、公开 stats 或 generated artifact 的风险变更,必须同步更新本仓库风险表。
- Critical/High 风险在未修复、未验证或未显式 accepted 之前阻断公开发布;accepted risk 必须写 owner、日期、原因、补偿控制和复查触发条件。
Expand All @@ -137,7 +137,7 @@ AgentHub 的 `docs/governance/security-risk-register.md` 是本仓库风险事

### Feishu/Lark 应用边界

AgentHub 飞书/Lark应用规划见 `../docs/feishu-agenthub-integration.md`。Feishu app 只做协作入口:应用机器人收发消息、事件订阅、卡片交互、工作台/H5 和任务通知。它不得成为 AgentHub 第二套登录系统;飞书 OAuth provider、飞书账号绑定、TokenDance ID 账号自动创建和 `oauth_bindings` 由 TokenDance ID 负责。Hub Server 接收 Feishu Integration Gateway 转发的业务事件后,仍按 TokenDance ID `sub` 和 AgentHub 权限执行。
AgentHub 飞书/Lark 应用规划见 `../docs/identity/feishu-integration.md`。Feishu app 只做协作入口:应用机器人收发消息、事件订阅、卡片交互、工作台/H5 和任务通知。它不得成为 AgentHub 第二套登录系统;飞书 OAuth provider、飞书账号绑定、TokenDance ID 账号自动创建和 `oauth_bindings` 由 TokenDance ID 负责。Hub Server 接收 Feishu Integration Gateway 转发的业务事件后,仍按 TokenDance ID `sub` 和 AgentHub 权限执行。

- AgentHub 交互机器人必须按飞书应用机器人设计;群自定义机器人只适合单向通知,不作为接收消息、用户交互或卡片回调方案。
- 生产 Feishu Gateway 必须保留 HTTPS Webhook 入口:`POST /integrations/feishu/events` 用于事件订阅,`POST /integrations/feishu/card-actions` 用于卡片回调。SDK 长连接/WebSocket 只作为企业自建应用开发或内测可选入口,不能成为唯一生产路径。
Expand All @@ -147,11 +147,11 @@ AgentHub 飞书/Lark应用规划见 `../docs/feishu-agenthub-integration.md`。F

### i18n 与公开文案边界

AgentHub Desktop/Web 的 zh/en 字典、登录入口、错误/空状态、Agent Runtime/Profile/Configuration/Execution Target 术语、Feishu/Lark 协作入口和 Gateway 调用文案变更时,先查 `../docs/i18n-parity-matrix.md`。新增用户可见字符串必须保证中英文语义一致,尤其不能把第三方 provider 写成 AgentHub 直连登录,也不能把 TokenDance API key 写成 TokenDance ID token。
AgentHub Desktop/Web 的 zh/en 字典、登录入口、错误/空状态、Agent Runtime/Profile/Configuration/Execution Target 术语、Feishu/Lark 协作入口和 Gateway 调用文案变更时,先查 `../docs/identity/i18n-packaging.md`。新增用户可见字符串必须保证中英文语义一致,尤其不能把第三方 provider 写成 AgentHub 直连登录,也不能把 TokenDance API key 写成 TokenDance ID token。

### TokenDance Gateway 调用边界

AgentHub 后续调用模型 API 网关时,产品名写 TokenDance Gateway / 词元跳动 API 网关,设计边界见 `../docs/relay-productization.md`。TokenDance API key 只能由 Hub Server、Edge Server 或受信后端/本地运行面持有,不得暴露给浏览器 UI、飞书卡片 value、公开日志或第三方 OAuth session。TokenDance ID access token 只用于登录/身份,不是 `api.vectorcontrol.tech/v1` 的模型 API bearer token。
AgentHub 后续调用模型 API 网关时,产品名写 TokenDance Gateway / 词元跳动 API 网关,设计边界见 `../docs/ecosystem/product-matrix.md`。TokenDance API key 只能由 Hub Server、Edge Server 或受信后端/本地运行面持有,不得暴露给浏览器 UI、飞书卡片 value、公开日志或第三方 OAuth session。TokenDance ID access token 只用于登录/身份,不是 `api.vectorcontrol.tech/v1` 的模型 API bearer token。

任务分发:

Expand Down Expand Up @@ -223,7 +223,7 @@ dev-loop 主 Agent 每次循环开始时检查收件箱,按优先级处理,

**样式**:CSS Modules + OKLCH 设计 tokens(`var(--primary)`, `var(--border)` 等)。禁止硬编码颜色值。

**设计落地**:页面/组件重做、共享 UI、视觉 QA 或 token 变更先读 `../docs/design-implementation-playbook.md` 和 `../docs/visual-qa-matrix.md`。AgentHub 必须按 dense command-center surface 验收,截图优先覆盖 thread/run/diff/approval 等真实工作流状态,不能只截空壳。
**设计落地**:页面/组件重做、共享 UI、视觉 QA 或 token 变更先读 `../docs/design/design-playbook.md` 和 `../docs/design/visual-qa-matrix.md`。AgentHub 必须按 dense command-center surface 验收,截图优先覆盖 thread/run/diff/approval 等真实工作流状态,不能只截空壳。

**测试**:`cd app/desktop && pnpm test`。共享 UI 组件测试放在 `app/shared/src/ui/*.test.tsx`。新组件必须有测试 + Storybook story。

Expand Down
2 changes: 1 addition & 1 deletion app/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@tanstack/react-query": "^5.100.14",
"@tanstack/react-virtual": "^3.13.13",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-dialog": "2.4.0",
"@tauri-apps/plugin-dialog": "2.7.1",
"@tauri-apps/plugin-shell": "^2.2.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down
1 change: 1 addition & 0 deletions app/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ tokio = { version = "1", features = ["process", "time", "sync"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
tauri-plugin-dialog = "2"
tauri-plugin-updater = "2"
getrandom = "0.3"
49 changes: 33 additions & 16 deletions app/desktop/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ pub async fn get_edge_status(state: State<'_, SharedEdgeManager>) -> Result<Edge
Ok(mgr.status())
}

#[tauri::command]
pub async fn get_edge_auth_token(state: State<'_, SharedEdgeManager>) -> Result<String, String> {
let mgr = state.lock().await;
Ok(mgr.local_auth_token().to_string())
}

#[tauri::command]
pub async fn start_edge(state: State<'_, SharedEdgeManager>) -> Result<EdgeStatus, String> {
let mut mgr = state.lock().await;
Expand Down Expand Up @@ -212,7 +218,8 @@ pub async fn git_status(dir: String) -> Result<GitStatus, String> {
let lines: Vec<&str> = stdout.lines().collect();

// Parse branch line: "## branch...origin/branch [ahead N] [behind M]"
let (branch, ahead, behind) = parse_branch_line(lines.first().copied().unwrap_or("## (no branch)"));
let (branch, ahead, behind) =
parse_branch_line(lines.first().copied().unwrap_or("## (no branch)"));

// Parse file lines
let files: Vec<GitFileStatus> = lines
Expand Down Expand Up @@ -384,7 +391,13 @@ fn parse_branch_line(line: &str) -> (Option<String>, u32, u32) {
// Extract branch name (before "..." or end of string before [ahead/behind])
let branch_part = rest.split(' ').next().unwrap_or(rest);
let branch = if branch_part.contains("...") {
Some(branch_part.split("...").next().unwrap_or(branch_part).to_string())
Some(
branch_part
.split("...")
.next()
.unwrap_or(branch_part)
.to_string(),
)
} else {
Some(branch_part.to_string())
};
Expand Down Expand Up @@ -527,10 +540,7 @@ fn is_ignored(entry_path: &Path, root: &Path, patterns: &[String]) -> bool {
}

// Also ignore common VCS/metadata dirs
let name = entry_path
.file_name()
.unwrap_or_default()
.to_string_lossy();
let name = entry_path.file_name().unwrap_or_default().to_string_lossy();
if is_dir {
match name.as_ref() {
".git" | "node_modules" | ".svn" | ".hg" => return true,
Expand Down Expand Up @@ -603,12 +613,22 @@ fn gitignore_match_impl(path: &[u8], pattern: &[u8], pi: usize, si: usize) -> bo
pi == plen
}

fn walk_dir(current: &Path, root: &Path, gitignore_patterns: &[String]) -> Result<Vec<FileEntry>, String> {
fn walk_dir(
current: &Path,
root: &Path,
gitignore_patterns: &[String],
) -> Result<Vec<FileEntry>, String> {
let mut entries: Vec<FileEntry> = Vec::new();

let dir_iter = match fs::read_dir(current) {
Ok(it) => it,
Err(e) => return Err(format!("Failed to read directory {}: {}", current.display(), e)),
Err(e) => {
return Err(format!(
"Failed to read directory {}: {}",
current.display(),
e
))
}
};

for entry_result in dir_iter {
Expand Down Expand Up @@ -695,10 +715,9 @@ pub async fn read_workspace_store(app: tauri::AppHandle) -> Result<WorkspaceStor
if !path.exists() {
return Ok(WorkspaceStoreData::default());
}
let content = fs::read_to_string(&path)
.map_err(|e| format!("Failed to read workspace store: {}", e))?;
serde_json::from_str(&content)
.map_err(|e| format!("Failed to parse workspace store: {}", e))
let content =
fs::read_to_string(&path).map_err(|e| format!("Failed to read workspace store: {}", e))?;
serde_json::from_str(&content).map_err(|e| format!("Failed to parse workspace store: {}", e))
}

#[tauri::command]
Expand All @@ -708,13 +727,11 @@ pub async fn write_workspace_store(
) -> Result<(), String> {
let path = workspace_store_path(&app);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create app data dir: {}", e))?;
fs::create_dir_all(parent).map_err(|e| format!("Failed to create app data dir: {}", e))?;
}
let content = serde_json::to_string_pretty(&data)
.map_err(|e| format!("Failed to serialize workspace store: {}", e))?;
fs::write(&path, &content)
.map_err(|e| format!("Failed to write workspace store: {}", e))
fs::write(&path, &content).map_err(|e| format!("Failed to write workspace store: {}", e))
}

// ── Workspace Content Search ──
Expand Down
15 changes: 14 additions & 1 deletion app/desktop/src-tauri/src/edge_health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,20 @@ pub fn spawn_health_check(app: AppHandle, edge: SharedEdgeManager) {

async fn check_http_health(port: u16) -> EdgeHealthPayload {
let url = format!("http://127.0.0.1:{}/v1/health", port);
match reqwest::get(&url).await {
let client = match reqwest::Client::builder()
.timeout(Duration::from_secs(2))
.build()
{
Ok(client) => client,
Err(_) => {
return EdgeHealthPayload {
online: false,
version: None,
edge_id: None,
};
}
};
match client.get(&url).send().await {
Ok(resp) if resp.status().is_success() => {
if let Ok(body) = resp.json::<serde_json::Value>().await {
EdgeHealthPayload {
Expand Down
47 changes: 37 additions & 10 deletions app/desktop/src-tauri/src/edge_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ pub struct EdgeManager {
child: Option<Child>,
edge_path: PathBuf,
store_path: PathBuf,
local_auth_token: String,
port: u16,
}

impl EdgeManager {
pub fn new(edge_path: PathBuf, store_path: PathBuf) -> Self {
Self {
pub fn new(edge_path: PathBuf, store_path: PathBuf) -> Result<Self, String> {
Ok(Self {
child: None,
edge_path,
store_path,
local_auth_token: generate_local_auth_token()?,
port: 3210,
}
})
}

pub async fn start(&mut self) -> Result<(), String> {
Expand All @@ -34,13 +36,20 @@ impl EdgeManager {
}

let addr = format!("127.0.0.1:{}", self.port);
let child = Command::new(&self.edge_path)
.args([
"--store-file",
self.store_path.to_str().unwrap_or("agenthub_store.json"),
"--addr",
&addr,
])
let mut command = Command::new(&self.edge_path);
command.args([
"--store-file",
self.store_path.to_str().unwrap_or("agenthub_store.json"),
"--addr",
&addr,
]);
if cfg!(debug_assertions) {
command.env("AGENTHUB_DEV", "1");
} else {
command.env("AGENTHUB_EDGE_AUTH_TOKEN", &self.local_auth_token);
}

let child = command
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true)
Expand Down Expand Up @@ -80,10 +89,28 @@ impl EdgeManager {
pub fn is_running(&self) -> bool {
self.child.is_some()
}

pub fn local_auth_token(&self) -> &str {
&self.local_auth_token
}
}

pub type SharedEdgeManager = Arc<Mutex<EdgeManager>>;

fn generate_local_auth_token() -> Result<String, String> {
let mut bytes = [0_u8; 32];
getrandom::fill(&mut bytes)
.map_err(|e| format!("Failed to generate Edge auth token: {}", e))?;
let mut token = String::with_capacity(5 + bytes.len() * 2);
token.push_str("aght_");
for byte in bytes {
use std::fmt::Write as _;
write!(&mut token, "{:02x}", byte)
.map_err(|e| format!("Failed to format Edge auth token: {}", e))?;
}
Ok(token)
}

fn edge_binary_name() -> &'static str {
if cfg!(windows) {
"agenthub-edge.exe"
Expand Down
Loading
Loading