Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ jobs:
options: >-
--health-cmd="rabbitmq-diagnostics -q ping"
--health-interval=10s --health-timeout=5s --health-retries=12
qdrant:
image: qdrant/qdrant:v1.19.2
env:
QDRANT__TELEMETRY_DISABLED: 'true'
ports:
- 6333/tcp
steps:
- name: Checkout harness
uses: actions/checkout@v4
Expand Down Expand Up @@ -124,4 +130,5 @@ jobs:
TRITON_MYSQL_URL: mysql+pymysql://root:ci-only-password@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/triton_agent_test
TRITON_TEST_REDIS_URL: redis://127.0.0.1:${{ job.services.redis.ports['6379'] }}/0
TRITON_TEST_AMQP_URL: amqp://ci:ci-only-password@127.0.0.1:${{ job.services.rabbitmq.ports['5672'] }}/
TRITON_TEST_QDRANT_URL: http://127.0.0.1:${{ job.services.qdrant.ports['6333'] }}
run: pnpm test:all --reporter=verbose
3 changes: 2 additions & 1 deletion packages/dsh-triton-riscv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,14 @@ See [backend options](python/codex_agent/README.md#redis-options).

### Development Tests

Vitest drives JavaScript, Python and native-host integration tests. After setup,
Vitest drives JavaScript, existing Python regressions, frontend and native-host integration tests. After setup,
from the plugin directory, configure **disposable** services (never production):

```sh
export TRITON_MYSQL_URL='mysql+pymysql://USER:PASSWORD@127.0.0.1:3306/triton_agent_test'
export TRITON_TEST_REDIS_URL='redis://127.0.0.1:6379/0'
export TRITON_TEST_AMQP_URL='amqp://guest:guest@127.0.0.1:5672/'
export TRITON_TEST_QDRANT_URL='http://127.0.0.1:6333'
.venv/bin/python -I -m codex_agent.storage upgrade
npm test
```
Expand Down
274 changes: 162 additions & 112 deletions packages/dsh-triton-riscv/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { Bot, Check, Menu, PanelRight, Pin, PinOff, Play, Plus, Send, Trash2, UserRound, X } from 'lucide-react'
import {
Bot,
Check,
GitBranch,
Menu,
PanelRight,
Pin,
PinOff,
Play,
Plus,
Send,
Trash2,
UserRound,
X,
} from 'lucide-react'
import { FormEvent, KeyboardEvent, useEffect, useRef, useState } from 'react'
import { api } from './api'
import { WorkflowPanel } from './WorkflowPanel'
import { workflowApi } from './workflow-api'
import { MessageText, shortDate } from './format'
import type { Bootstrap, Run, RunEvent, ProposalReview, Session, SessionBundle, ValidationPlanReview } from './types'

Expand Down Expand Up @@ -288,6 +304,8 @@ function CommandApproval({
export default function App() {
const [bootstrap, setBootstrap] = useState<Bootstrap | null>(null)
const [sessions, setSessions] = useState<Session[]>([])
const [workflowEnabled, setWorkflowEnabled] = useState(false)
const [mode, setMode] = useState<'chat' | 'workflow'>('chat')
const [activeSessionId, setActiveSessionId] = useState<string | null>(null)
const [bundle, setBundle] = useState<SessionBundle | null>(null)
const [activeRun, setActiveRun] = useState<Run | null>(null)
Expand Down Expand Up @@ -413,6 +431,12 @@ export default function App() {

useEffect(() => {
let cancelled = false
void workflowApi
.config()
.then(config => {
if (!cancelled) setWorkflowEnabled(config.enabled)
})
.catch(() => {})
const start = async () => {
try {
const [initialBootstrap, initialSessions] = await Promise.all([api.bootstrap(), api.listSessions()])
Expand Down Expand Up @@ -651,17 +675,37 @@ export default function App() {
<h2>{bundle?.session.title || '新对话'}</h2>
</div>
<div className="chat-heading-actions">
<span className="mode-badge">DeepSeek Harness</span>
<button
className="icon-button"
type="button"
title={drawer === 'inspector' ? '关闭任务详情' : '打开任务详情'}
aria-label={drawer === 'inspector' ? '关闭任务详情' : '打开任务详情'}
aria-expanded={drawer === 'inspector'}
onClick={() => setDrawer(current => (current === 'inspector' ? null : 'inspector'))}
>
<PanelRight size={18} />
</button>
<div className="workflow-modes" role="tablist" aria-label="Agent 模式">
<button role="tab" aria-selected={mode === 'chat'} title="Harness 对话" onClick={() => setMode('chat')}>
<Bot size={15} />
对话
</button>
<button
role="tab"
aria-selected={mode === 'workflow'}
title={workflowEnabled ? '多智能体任务' : '后端尚未启用多智能体'}
disabled={!workflowEnabled}
onClick={() => {
setMode('workflow')
setDrawer(null)
}}
>
<GitBranch size={15} />
多智能体
</button>
</div>
{mode === 'chat' && (
<button
className="icon-button"
type="button"
title={drawer === 'inspector' ? '关闭任务详情' : '打开任务详情'}
aria-label={drawer === 'inspector' ? '关闭任务详情' : '打开任务详情'}
aria-expanded={drawer === 'inspector'}
onClick={() => setDrawer(current => (current === 'inspector' ? null : 'inspector'))}
>
<PanelRight size={18} />
</button>
)}
</div>
</div>
{error && (
Expand All @@ -672,115 +716,121 @@ export default function App() {
</button>
</div>
)}
<div className="messages" aria-live="polite">
{!bundle?.messages.length ? (
<EmptyConversation />
) : (
bundle.messages.map(message => {
return (
<article className={`message ${message.role}`} key={message.id}>
{message.role === 'assistant' && (
<div className="message-avatar agent-avatar" role="img" aria-label="Agent">
<Bot size={17} />
</div>
)}
{mode === 'workflow' && activeSessionId ? (
<WorkflowPanel key={activeSessionId} sessionId={activeSessionId} enabled={workflowEnabled} />
) : (
<>
<div className="messages" aria-live="polite">
{!bundle?.messages.length ? (
<EmptyConversation />
) : (
bundle.messages.map(message => {
return (
<article className={`message ${message.role}`} key={message.id}>
{message.role === 'assistant' && (
<div className="message-avatar agent-avatar" role="img" aria-label="Agent">
<Bot size={17} />
</div>
)}
<div className="message-content">
<div className="message-body">
<MessageText value={message.content} />
</div>
</div>
{message.role === 'user' && (
<div className="message-avatar user-avatar" role="img" aria-label="用户">
<UserRound size={17} />
</div>
)}
</article>
)
})
)}
{streamingText && (
<article className="message assistant streaming-message" aria-label="Agent 正在回复">
<div className="message-avatar agent-avatar" role="img" aria-label="Agent">
<Bot size={17} />
</div>
<div className="message-content">
<div className="message-body">
<MessageText value={message.content} />
<MessageText value={streamingText} />
<span className="typing-cursor" aria-hidden="true" />
</div>
</div>
{message.role === 'user' && (
<div className="message-avatar user-avatar" role="img" aria-label="用户">
<UserRound size={17} />
</article>
)}
{agentWorking && !streamingText && (
<article className="message assistant streaming-message" aria-label="Agent 正在处理任务">
<div className="message-avatar agent-avatar" role="img" aria-label="Agent">
<Bot size={17} />
</div>
<div className="message-content">
<div className="message-body typing-indicator" aria-hidden="true">
<span />
<span />
<span />
</div>
)}
</div>
</article>
)
})
)}
{streamingText && (
<article className="message assistant streaming-message" aria-label="Agent 正在回复">
<div className="message-avatar agent-avatar" role="img" aria-label="Agent">
<Bot size={17} />
</div>
<div className="message-content">
<div className="message-body">
<MessageText value={streamingText} />
<span className="typing-cursor" aria-hidden="true" />
</div>
</div>
</article>
)}
{agentWorking && !streamingText && (
<article className="message assistant streaming-message" aria-label="Agent 正在处理任务">
<div className="message-avatar agent-avatar" role="img" aria-label="Agent">
<Bot size={17} />
</div>
<div className="message-content">
<div className="message-body typing-indicator" aria-hidden="true">
<span />
<span />
<span />
</div>
)}
<div ref={messagesEnd} />
</div>
{approvalEvents.length > 0 && (
<div className="approval-dock" aria-label="待审批提案">
{approvalEvents.map(event =>
event.payload.validation_run_id ? (
<CommandApproval
event={event}
key={event.id}
onResolved={dismissApproval}
onExecute={executeValidation}
/>
) : (
<ProposalApproval
event={event}
key={event.id}
onResolved={dismissApproval}
onContinue={continueProposal}
/>
),
)}
</div>
</article>
)}
<div ref={messagesEnd} />
</div>
{approvalEvents.length > 0 && (
<div className="approval-dock" aria-label="待审批提案">
{approvalEvents.map(event =>
event.payload.validation_run_id ? (
<CommandApproval
event={event}
key={event.id}
onResolved={dismissApproval}
onExecute={executeValidation}
/>
) : (
<ProposalApproval
event={event}
key={event.id}
onResolved={dismissApproval}
onContinue={continueProposal}
/>
),
)}
</div>
<div className="suggestions">
{bootstrap?.suggestions.map(suggestion => (
<button
className="suggestion-button"
type="button"
title={suggestion}
key={suggestion}
onClick={() => setInput(suggestion)}
>
{suggestion}
</button>
))}
</div>
<form className="composer" onSubmit={submit}>
<textarea
value={input}
rows={2}
maxLength={12000}
placeholder="描述你想检查、开发或分析的 Triton-RISCV 任务..."
aria-label="任务描述"
onChange={event => setInput(event.target.value)}
onKeyDown={inputKeyDown}
/>
<button
className="send-button"
type="submit"
title="发送任务"
aria-label="发送任务"
disabled={busy || !input.trim()}
>
<Send size={18} />
</button>
</form>
</>
)}
<div className="suggestions">
{bootstrap?.suggestions.map(suggestion => (
<button
className="suggestion-button"
type="button"
title={suggestion}
key={suggestion}
onClick={() => setInput(suggestion)}
>
{suggestion}
</button>
))}
</div>
<form className="composer" onSubmit={submit}>
<textarea
value={input}
rows={2}
maxLength={12000}
placeholder="描述你想检查、开发或分析的 Triton-RISCV 任务..."
aria-label="任务描述"
onChange={event => setInput(event.target.value)}
onKeyDown={inputKeyDown}
/>
<button
className="send-button"
type="submit"
title="发送任务"
aria-label="发送任务"
disabled={busy || !input.trim()}
>
<Send size={18} />
</button>
</form>
</main>

<aside className="inspector-panel">
Expand Down
Loading
Loading