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>
This commit is contained in:
2026-06-05 00:16:28 +08:00
co-authored by Claude Opus 4.8
parent c171bb70ee
commit 05de8f50e4
2 changed files with 32 additions and 4 deletions
@@ -8,6 +8,7 @@ import (
"io"
"net/http"
"net/url"
"sort"
"strings"
"time"
@@ -183,6 +184,15 @@ func amStartTemplateAgent(ctx context.Context, args amStartArgs) (amStartResult,
},
"env": env,
}
// Operational log: which env KEYS we send to AM (names only, never values) —
// lets us confirm e.g. AGENT_ACCESS_TOKEN is actually transmitted without
// leaking any secret.
envKeys := make([]string, 0, len(env))
for k := range env {
envKeys = append(envKeys, k)
}
sort.Strings(envKeys)
common.SysLog("amStartTemplateAgent name=" + name + " env_keys=[" + strings.Join(envKeys, ",") + "]")
data, err := amTemplateDo(ctx, http.MethodPost, agentTemplateStartPath(), payload, agentTemplateStartTimeout())
if err != nil {
return amStartResult{}, err
@@ -1,7 +1,7 @@
import { useMemo, useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Link } from '@tanstack/react-router'
import { Activity, Copy, Database, GitBranch, RefreshCw, Server, X } from 'lucide-react'
import { Activity, Copy, Database, Eye, EyeOff, GitBranch, RefreshCw, Server, X } from 'lucide-react'
import { toast } from 'sonner'
import { api } from '@/lib/api'
import { cn } from '@/lib/utils'
@@ -12,6 +12,7 @@ type AgentItem = {
agent_id: string
template_id: string
subdomain: string
access_token: string
binding_ids: number[]
status: string
runtime_id: string
@@ -198,6 +199,9 @@ function DetailPanel({
onClose: () => void
onCopy: (t: string) => void
}) {
const [revealToken, setRevealToken] = useState(false)
const directUrl = agent.subdomain ? `http://${agent.subdomain}` : ''
// Live status pull from AM for this agent.
const liveQ = useQuery({
queryKey: ['agent-status', 'live', agent.agent_id],
@@ -225,14 +229,28 @@ function DetailPanel({
<span className={cn('inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-semibold ring-1 ring-inset', st.cls)}>{st.text}</span>
<span className='text-muted-foreground ml-2 text-[11px]'>{liveQ.isFetching ? '刷新中…' : '每 10 秒刷新'}</span>
</Field>
<Field label='访问地址(直连)'>
<Field label='直连地址(客户端连这个)'>
<div className='flex items-center gap-2'>
<span className='min-w-0 flex-1 truncate font-mono text-xs'>{agent.subdomain || '地址生成中…'}</span>
<Button variant='outline' size='sm' className='h-7 gap-1 text-xs' disabled={!agent.subdomain} onClick={() => onCopy(agent.subdomain)}>
<span className='min-w-0 flex-1 truncate font-mono text-xs'>{directUrl || '地址生成中…'}</span>
<Button variant='outline' size='sm' className='h-7 gap-1 text-xs' disabled={!directUrl} onClick={() => onCopy(directUrl)}>
<Copy className='h-3 w-3' /> 复制
</Button>
</div>
</Field>
<Field label='访问令牌(直连鉴权 · X-Agent-Access-Token)'>
<div className='flex items-center gap-2'>
<span className='min-w-0 flex-1 truncate font-mono text-xs'>
{agent.access_token ? (revealToken ? agent.access_token : '••••••••••••••••') : '—'}
</span>
<Button variant='ghost' size='icon' className='h-7 w-7' disabled={!agent.access_token} onClick={() => setRevealToken((v) => !v)} title={revealToken ? '隐藏' : '显示'}>
{revealToken ? <EyeOff className='h-3.5 w-3.5' /> : <Eye className='h-3.5 w-3.5' />}
</Button>
<Button variant='outline' size='sm' className='h-7 gap-1 text-xs' disabled={!agent.access_token} onClick={() => onCopy(agent.access_token)}>
<Copy className='h-3 w-3' /> 复制
</Button>
</div>
<p className='text-muted-foreground mt-1 text-[10px]'>客户端直连时放进请求头 <span className='font-mono'>X-Agent-Access-Token</span>,证明只有你能调这个 Agent。</p>
</Field>
<Field label='挂载的资源'>
{(agent.binding_ids?.length ?? 0) === 0 ? (
<span className='text-muted-foreground text-xs'>未挂载资源</span>