summaryrefslogtreecommitdiff
path: root/frontend/src/services
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/services')
-rw-r--r--frontend/src/services/directiveApi.ts34
-rw-r--r--frontend/src/services/taskWs.ts88
2 files changed, 119 insertions, 3 deletions
diff --git a/frontend/src/services/directiveApi.ts b/frontend/src/services/directiveApi.ts
index b82f594..4d1fd82 100644
--- a/frontend/src/services/directiveApi.ts
+++ b/frontend/src/services/directiveApi.ts
@@ -35,8 +35,9 @@ export interface DirectiveStep {
taskPlan: string
dependsOn: string[]
status: string
- taskId: string
contractId: string
+ /** @deprecated Use contractId instead */
+ taskId: string
orderIndex: number
sort_order?: number
completedAt: string
@@ -124,13 +125,40 @@ export async function pauseDirective(id: string): Promise<DirectiveWithSteps> {
}
export async function getUserSetting(key: string): Promise<any> {
- const response = await apiFetch(`/api/v1/settings/${key}`)
+ const response = await apiFetch(`/api/v1/user-settings/${key}`)
return response.json()
}
export async function upsertUserSetting(key: string, value: any): Promise<void> {
- await apiFetch('/api/v1/settings', {
+ await apiFetch('/api/v1/user-settings', {
method: 'PUT',
body: JSON.stringify({ key, value }),
})
}
+
+// ---- Task control APIs ----
+
+export async function sendTaskMessage(taskId: string, message: string): Promise<void> {
+ await apiFetch(`/api/v1/mesh/tasks/${taskId}/message`, {
+ method: 'POST',
+ body: JSON.stringify({ message }),
+ })
+}
+
+export async function stopTask(taskId: string): Promise<void> {
+ await apiFetch(`/api/v1/mesh/tasks/${taskId}/stop`, {
+ method: 'POST',
+ })
+}
+
+export async function continueTask(taskId: string): Promise<void> {
+ await apiFetch(`/api/v1/mesh/tasks/${taskId}/continue`, {
+ method: 'POST',
+ })
+}
+
+export async function startTask(taskId: string): Promise<void> {
+ await apiFetch(`/api/v1/mesh/tasks/${taskId}/start`, {
+ method: 'POST',
+ })
+}
diff --git a/frontend/src/services/taskWs.ts b/frontend/src/services/taskWs.ts
new file mode 100644
index 0000000..832648e
--- /dev/null
+++ b/frontend/src/services/taskWs.ts
@@ -0,0 +1,88 @@
+import { VNWebSocket } from './ws'
+
+export interface TaskOutputMessage {
+ task_id: string
+ message_type: string
+ content: string
+ tool_name?: string
+ tool_input?: string
+ is_error?: boolean
+ cost_usd?: number
+ duration_ms?: number
+ is_partial?: boolean
+}
+
+type TaskOutputCallback = (msg: TaskOutputMessage) => void
+
+export class TaskOutputStream {
+ private ws: VNWebSocket
+ private listeners: Map<string, Set<TaskOutputCallback>> = new Map()
+ private connected = false
+
+ constructor() {
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
+ this.ws = new VNWebSocket(`${protocol}//${window.location.host}/ws/tasks`)
+
+ this.ws.on('message', (data: any) => {
+ if (data && data.type === 'TaskOutput') {
+ const payload = data.payload || data
+ const taskId = payload.task_id
+ if (taskId && this.listeners.has(taskId)) {
+ this.listeners.get(taskId)!.forEach(cb => cb(payload))
+ }
+ }
+ })
+
+ this.ws.on('open', () => {
+ this.connected = true
+ // Re-subscribe all active subscriptions on reconnect
+ for (const taskId of this.listeners.keys()) {
+ this.ws.send({ type: 'SubscribeOutput', task_id: taskId })
+ }
+ })
+
+ this.ws.on('close', () => {
+ this.connected = false
+ })
+ }
+
+ connect() {
+ this.ws.connect()
+ }
+
+ subscribe(taskId: string, callback: TaskOutputCallback) {
+ if (!this.listeners.has(taskId)) {
+ this.listeners.set(taskId, new Set())
+ // Only send subscribe if this is a new task subscription
+ this.ws.send({ type: 'SubscribeOutput', task_id: taskId })
+ }
+ this.listeners.get(taskId)!.add(callback)
+ }
+
+ unsubscribe(taskId: string, callback?: TaskOutputCallback) {
+ if (!this.listeners.has(taskId)) return
+
+ if (callback) {
+ this.listeners.get(taskId)!.delete(callback)
+ if (this.listeners.get(taskId)!.size > 0) return
+ }
+
+ this.listeners.delete(taskId)
+ this.ws.send({ type: 'UnsubscribeOutput', task_id: taskId })
+ }
+
+ close() {
+ this.ws.close()
+ this.listeners.clear()
+ }
+}
+
+let instance: TaskOutputStream | null = null
+
+export function getTaskOutputStream(): TaskOutputStream {
+ if (!instance) {
+ instance = new TaskOutputStream()
+ instance.connect()
+ }
+ return instance
+}