diff --git a/biome.json b/biome.json index 357082f..d08b98a 100644 --- a/biome.json +++ b/biome.json @@ -17,6 +17,15 @@ "indentWidth": 2, "lineWidth": 100 }, + "css": { + "parser": { + "cssModules": true, + "tailwindDirectives": true + }, + "linter": { + "enabled": false + } + }, "javascript": { "formatter": { "quoteStyle": "double", diff --git a/packages/cli-workflow/src/__tests__/log.test.ts b/packages/cli-workflow/src/__tests__/log.test.ts index 75c839c..48dbbe5 100644 --- a/packages/cli-workflow/src/__tests__/log.test.ts +++ b/packages/cli-workflow/src/__tests__/log.test.ts @@ -62,9 +62,9 @@ const olderEntry = JSON.stringify({ async function writeLogFiles(): Promise { const logsDir = join(storageRoot, "logs"); - await writeFile(join(logsDir, "2026-05-20.jsonl"), [entry1, entry2, entry3].join("\n") + "\n"); - await writeFile(join(logsDir, "2026-05-19.jsonl"), oldEntry + "\n"); - await writeFile(join(logsDir, "2026-05-18.jsonl"), olderEntry + "\n"); + await writeFile(join(logsDir, "2026-05-20.jsonl"), `${[entry1, entry2, entry3].join("\n")}\n`); + await writeFile(join(logsDir, "2026-05-19.jsonl"), `${oldEntry}\n`); + await writeFile(join(logsDir, "2026-05-18.jsonl"), `${olderEntry}\n`); } describe("cmdLogList", () => { diff --git a/packages/workflow-agent-builtin/__tests__/path.test.ts b/packages/workflow-agent-builtin/__tests__/path.test.ts index 063475c..47ff143 100644 --- a/packages/workflow-agent-builtin/__tests__/path.test.ts +++ b/packages/workflow-agent-builtin/__tests__/path.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import { resolvePath } from "../src/tools/path.js"; import { resolve } from "node:path"; +import { resolvePath } from "../src/tools/path.js"; describe("resolvePath", () => { test("resolves relative paths against cwd", () => { diff --git a/packages/workflow-agent-builtin/src/agent.ts b/packages/workflow-agent-builtin/src/agent.ts index 118fee7..cf43948 100644 --- a/packages/workflow-agent-builtin/src/agent.ts +++ b/packages/workflow-agent-builtin/src/agent.ts @@ -7,17 +7,26 @@ import { resolveModel, resolveStorageRoot, } from "@uncaged/workflow-agent-kit"; -import { generateUlid } from "@uncaged/workflow-util"; +import { createLogger, generateUlid } from "@uncaged/workflow-util"; import { storeBuiltinDetail } from "./detail.js"; import type { ChatMessage } from "./llm/index.js"; import { BUILTIN_CONTINUE_MAX_TURNS, BUILTIN_MAX_TURNS, runBuiltinLoop } from "./loop.js"; import { buildBuiltinMessages } from "./prompt.js"; -import type { BuiltinSessionState } from "./types.js"; +import { initSessionDir, removeSession } from "./session.js"; -const sessions = new Map(); +const log = createLogger({ sink: { kind: "stderr" } }); -function getSession(sessionId: string): BuiltinSessionState { +type SessionRecord = { + sessionId: string; + model: string; + startedAtMs: number; + messages: ChatMessage[]; +}; + +const sessions = new Map(); + +function getSession(sessionId: string): SessionRecord { const session = sessions.get(sessionId); if (session === undefined) { throw new Error(`builtin session not found: ${sessionId}`); @@ -36,7 +45,7 @@ async function runBuiltinWithMessages( storageRoot: string, provider: ReturnType, messages: ChatMessage[], - session: BuiltinSessionState, + session: SessionRecord, store: Store, maxTurns: number, ): Promise { @@ -45,22 +54,31 @@ async function runBuiltinWithMessages( messages, toolCtx: buildToolContext(storageRoot), maxTurns, - existingTurns: session.turns, + storageRoot, + sessionId: session.sessionId, }); session.messages = loopResult.messages; - session.turns = loopResult.turns; - const { detailHash, output } = await storeBuiltinDetail( + if (loopResult.turnCount === 0) { + log("5RWTK9NB", "no turns produced, returning empty output"); + await removeSession(storageRoot, session.sessionId); + return { output: "", detailHash: "", sessionId: session.sessionId }; + } + + // Read jsonl → persist turns to CAS → store detail + const { detailHash } = await storeBuiltinDetail( store, + storageRoot, session.sessionId, session.model, session.startedAtMs, - session.turns, ); - const finalOutput = output !== "" ? output : loopResult.finalText; - return { output: finalOutput, detailHash, sessionId: session.sessionId }; + // Clean up session jsonl + await removeSession(storageRoot, session.sessionId); + + return { output: loopResult.finalText, detailHash, sessionId: session.sessionId }; } async function runBuiltin(ctx: AgentContext): Promise { @@ -69,14 +87,14 @@ async function runBuiltin(ctx: AgentContext): Promise { const provider = resolveModel(config, config.defaultModel); const sessionId = generateUlid(Date.now()); + await initSessionDir(storageRoot); const messages = buildBuiltinMessages(ctx); - const session: BuiltinSessionState = { + const session: SessionRecord = { sessionId, model: provider.model, startedAtMs: Date.now(), messages, - turns: [], }; sessions.set(sessionId, session); diff --git a/packages/workflow-agent-builtin/src/detail.ts b/packages/workflow-agent-builtin/src/detail.ts index ab49fbf..828fdcb 100644 --- a/packages/workflow-agent-builtin/src/detail.ts +++ b/packages/workflow-agent-builtin/src/detail.ts @@ -1,72 +1,15 @@ import { bootstrap, putSchema, type Store } from "@uncaged/json-cas"; import { BUILTIN_DETAIL_SCHEMA, BUILTIN_TURN_SCHEMA } from "./schemas.js"; -import type { - BuiltinDetailPayload, - BuiltinLoopTurn, - BuiltinToolCall, - BuiltinTurnPayload, - BuiltinTurnRole, -} from "./types.js"; - -function mapToolCalls(calls: NonNullable): BuiltinToolCall[] { - return calls.map((call) => ({ - name: call.name, - args: call.args, - })); -} - -function loopTurnToAssistantPayload(turn: BuiltinLoopTurn, index: number): BuiltinTurnPayload { - return { - index, - role: "assistant", - content: turn.assistantContent ?? "", - toolCalls: - turn.toolCalls !== null && turn.toolCalls.length > 0 ? mapToolCalls(turn.toolCalls) : null, - reasoning: null, - }; -} - -function loopTurnToToolPayloads(turn: BuiltinLoopTurn, startIndex: number): BuiltinTurnPayload[] { - if (turn.toolResults === null || turn.toolResults.length === 0) { - return []; - } - const payloads: BuiltinTurnPayload[] = []; - let index = startIndex; - for (const result of turn.toolResults) { - payloads.push({ - index, - role: "tool" as BuiltinTurnRole, - content: result.content, - toolCalls: null, - reasoning: null, - }); - index += 1; - } - return payloads; -} - -/** Last assistant message with non-empty text. */ -export function extractFinalAssistantText(turns: BuiltinLoopTurn[]): string { - for (let i = turns.length - 1; i >= 0; i--) { - const turn = turns[i]; - if (turn === undefined) { - continue; - } - const text = turn.assistantContent; - if (text !== null && text.trim() !== "") { - return text; - } - } - return ""; -} +import { readSessionTurns } from "./session.js"; +import type { BuiltinDetailPayload } from "./types.js"; type BuiltinSchemaHashes = { turn: string; detail: string; }; -async function registerBuiltinSchemas(store: Store): Promise { +export async function registerBuiltinSchemas(store: Store): Promise { await bootstrap(store); const [turn, detail] = await Promise.all([ putSchema(store, BUILTIN_TURN_SCHEMA), @@ -75,30 +18,22 @@ async function registerBuiltinSchemas(store: Store): Promise { +): Promise<{ detailHash: string; turnCount: number }> { const schemas = await registerBuiltinSchemas(store); + const turns = await readSessionTurns(storageRoot, sessionId); + const turnHashes: string[] = []; - let turnIndex = 0; - - for (const loopTurn of turns) { - const assistant = loopTurnToAssistantPayload(loopTurn, turnIndex); - const assistantHash = await store.put(schemas.turn, assistant); - turnHashes.push(assistantHash); - turnIndex += 1; - - const toolPayloads = loopTurnToToolPayloads(loopTurn, turnIndex); - for (const toolPayload of toolPayloads) { - const toolHash = await store.put(schemas.turn, toolPayload); - turnHashes.push(toolHash); - turnIndex += 1; - } + for (const turn of turns) { + const hash = await store.put(schemas.turn, turn); + turnHashes.push(hash); } const duration = Math.max(0, nowMs - startedAtMs); @@ -110,6 +45,5 @@ export async function storeBuiltinDetail( turns: turnHashes, }; const detailHash = await store.put(schemas.detail, detail); - const output = extractFinalAssistantText(turns); - return { detailHash, output }; + return { detailHash, turnCount: turnHashes.length }; } diff --git a/packages/workflow-agent-builtin/src/index.ts b/packages/workflow-agent-builtin/src/index.ts index f5ad799..2453c17 100644 --- a/packages/workflow-agent-builtin/src/index.ts +++ b/packages/workflow-agent-builtin/src/index.ts @@ -1,14 +1,16 @@ export { createBuiltinAgent } from "./agent.js"; -export { extractFinalAssistantText, storeBuiltinDetail } from "./detail.js"; +export { registerBuiltinSchemas, storeBuiltinDetail } from "./detail.js"; export type { ChatMessage, LlmAssistantResponse, LlmToolCall } from "./llm/index.js"; export { chatCompletionWithTools } from "./llm/index.js"; export { BUILTIN_CONTINUE_MAX_TURNS, BUILTIN_MAX_TURNS, runBuiltinLoop } from "./loop.js"; export { buildBuiltinMessages } from "./prompt.js"; +export { appendSessionTurn, initSessionDir, readSessionTurns, removeSession } from "./session.js"; export type { BuiltinTool, ToolContext } from "./tools/index.js"; export { executeBuiltinTool, getBuiltinTools } from "./tools/index.js"; export type { BuiltinDetailPayload, BuiltinLoopTurn, - BuiltinSessionState, + BuiltinToolCallRecord, + BuiltinToolResultRecord, BuiltinTurnPayload, } from "./types.js"; diff --git a/packages/workflow-agent-builtin/src/loop.ts b/packages/workflow-agent-builtin/src/loop.ts index 91b92de..d289c1c 100644 --- a/packages/workflow-agent-builtin/src/loop.ts +++ b/packages/workflow-agent-builtin/src/loop.ts @@ -2,13 +2,14 @@ import type { ResolvedLlmProvider } from "@uncaged/workflow-agent-kit"; import { createLogger } from "@uncaged/workflow-util"; import { type ChatMessage, chatCompletionWithTools, type LlmToolCall } from "./llm/index.js"; +import { appendSessionTurn } from "./session.js"; import { builtinToolsToOpenAi, executeBuiltinTool, getBuiltinTools, type ToolContext, } from "./tools/index.js"; -import type { BuiltinLoopTurn, BuiltinToolCallRecord, BuiltinToolResultRecord } from "./types.js"; +import type { BuiltinToolCall, BuiltinTurnPayload } from "./types.js"; const log = createLogger({ sink: { kind: "stderr" } }); @@ -20,31 +21,61 @@ export type RunBuiltinLoopOptions = { messages: ChatMessage[]; toolCtx: ToolContext; maxTurns: number; - existingTurns: BuiltinLoopTurn[]; + storageRoot: string; + sessionId: string; }; export type RunBuiltinLoopResult = { finalText: string; messages: ChatMessage[]; - turns: BuiltinLoopTurn[]; + turnCount: number; }; -function mapToolCalls(calls: LlmToolCall[]): BuiltinToolCallRecord[] { +function mapToolCallsForPayload(calls: LlmToolCall[]): BuiltinToolCall[] { return calls.map((call) => ({ - id: call.id, name: call.name, args: call.arguments, })); } +async function appendTurn( + storageRoot: string, + sessionId: string, + payload: BuiltinTurnPayload, +): Promise { + await appendSessionTurn(storageRoot, sessionId, payload); +} + +async function executeTurnTools( + calls: Array<{ id: string; name: string; arguments: string }>, + toolCtx: ToolContext, + messages: ChatMessage[], + storageRoot: string, + sessionId: string, +): Promise { + let turnCount = 0; + for (const call of calls) { + const result = await executeBuiltinTool(call.name, call.arguments, toolCtx); + messages.push({ role: "tool", tool_call_id: call.id, content: result }); + await appendTurn(storageRoot, sessionId, { + role: "tool", + content: result, + toolCalls: null, + reasoning: null, + }); + turnCount += 1; + } + return turnCount; +} + /** Agent run loop: LLM ↔ tools until no tool_calls or maxTurns. */ export async function runBuiltinLoop( options: RunBuiltinLoopOptions, ): Promise { const messages = [...options.messages]; - const turns = [...options.existingTurns]; const openAiTools = builtinToolsToOpenAi(getBuiltinTools()); let finalText = ""; + let turnCount = 0; for (let turn = 0; turn < options.maxTurns; turn++) { log("8K2M4N7P", `builtin loop turn ${turn + 1}/${options.maxTurns}`); @@ -59,36 +90,33 @@ export async function runBuiltinLoop( if (response.toolCalls === null || response.toolCalls.length === 0) { finalText = response.content ?? ""; - turns.push({ - assistantContent: response.content, + await appendTurn(options.storageRoot, options.sessionId, { + role: "assistant", + content: response.content ?? "", toolCalls: null, - toolResults: null, + reasoning: null, }); + turnCount += 1; break; } - const toolCallRecords = mapToolCalls(response.toolCalls); - const toolResults: BuiltinToolResultRecord[] = []; - - for (const call of response.toolCalls) { - const result = await executeBuiltinTool(call.name, call.arguments, options.toolCtx); - toolResults.push({ - toolCallId: call.id, - name: call.name, - content: result, - }); - messages.push({ - role: "tool", - tool_call_id: call.id, - content: result, - }); - } - - turns.push({ - assistantContent: response.content, - toolCalls: toolCallRecords, - toolResults, + // Assistant turn with tool calls + await appendTurn(options.storageRoot, options.sessionId, { + role: "assistant", + content: response.content ?? "", + toolCalls: mapToolCallsForPayload(response.toolCalls), + reasoning: null, }); + turnCount += 1; + + // Execute tools + turnCount += await executeTurnTools( + response.toolCalls, + options.toolCtx, + messages, + options.storageRoot, + options.sessionId, + ); } if (finalText === "" && messages.length > 0) { @@ -106,5 +134,5 @@ export async function runBuiltinLoop( } } - return { finalText, messages, turns }; + return { finalText, messages, turnCount }; } diff --git a/packages/workflow-agent-builtin/src/schemas.ts b/packages/workflow-agent-builtin/src/schemas.ts index 273d3df..c9e8077 100644 --- a/packages/workflow-agent-builtin/src/schemas.ts +++ b/packages/workflow-agent-builtin/src/schemas.ts @@ -13,9 +13,8 @@ const BUILTIN_TOOL_CALL_SCHEMA: JSONSchema = { export const BUILTIN_TURN_SCHEMA: JSONSchema = { title: "builtin-turn", type: "object", - required: ["index", "role", "content"], + required: ["role", "content"], properties: { - index: { type: "integer" }, role: { type: "string", enum: ["assistant", "tool"] }, content: { type: "string" }, toolCalls: { diff --git a/packages/workflow-agent-builtin/src/session.ts b/packages/workflow-agent-builtin/src/session.ts new file mode 100644 index 0000000..7f9c766 --- /dev/null +++ b/packages/workflow-agent-builtin/src/session.ts @@ -0,0 +1,59 @@ +import { appendFile, mkdir, readFile, rm } from "node:fs/promises"; +import { join } from "node:path"; + +import { createLogger } from "@uncaged/workflow-util"; + +import type { BuiltinTurnPayload } from "./types.js"; + +const log = createLogger({ sink: { kind: "stderr" } }); + +function sessionsDir(storageRoot: string): string { + return join(storageRoot, "sessions"); +} + +function sessionFile(storageRoot: string, sessionId: string): string { + return join(sessionsDir(storageRoot), `${sessionId}.jsonl`); +} + +/** Ensure sessions directory exists. */ +export async function initSessionDir(storageRoot: string): Promise { + await mkdir(sessionsDir(storageRoot), { recursive: true }); +} + +/** Append a turn to the session jsonl file. */ +export async function appendSessionTurn( + storageRoot: string, + sessionId: string, + turn: BuiltinTurnPayload, +): Promise { + const line = `${JSON.stringify(turn)}\n`; + await appendFile(sessionFile(storageRoot, sessionId), line, "utf-8"); + log("3XQVN8KR", `session ${sessionId} appended ${turn.role} turn`); +} + +/** Read all turns from session jsonl. Returns empty array if file does not exist. */ +export async function readSessionTurns( + storageRoot: string, + sessionId: string, +): Promise { + try { + const content = await readFile(sessionFile(storageRoot, sessionId), "utf-8"); + const lines = content + .trim() + .split("\n") + .filter((l) => l.length > 0); + return lines.map((l) => JSON.parse(l) as BuiltinTurnPayload); + } catch { + return []; + } +} + +/** Remove session jsonl file (called after detail is persisted to step CAS). */ +export async function removeSession(storageRoot: string, sessionId: string): Promise { + try { + await rm(sessionFile(storageRoot, sessionId)); + log("7FWDP2MJ", `session ${sessionId} removed`); + } catch { + // already gone — fine + } +} diff --git a/packages/workflow-agent-builtin/src/tools/run-command.ts b/packages/workflow-agent-builtin/src/tools/run-command.ts index 2f34843..4cd041f 100644 --- a/packages/workflow-agent-builtin/src/tools/run-command.ts +++ b/packages/workflow-agent-builtin/src/tools/run-command.ts @@ -56,8 +56,7 @@ function runShell( export const runCommandTool: BuiltinTool = { name: "run_command", - description: - "Run a shell command. Output is truncated to 32KB.", + description: "Run a shell command. Output is truncated to 32KB.", parameters: { type: "object", required: ["command"], diff --git a/packages/workflow-agent-builtin/src/types.ts b/packages/workflow-agent-builtin/src/types.ts index 679d83c..f077279 100644 --- a/packages/workflow-agent-builtin/src/types.ts +++ b/packages/workflow-agent-builtin/src/types.ts @@ -34,7 +34,6 @@ export type BuiltinToolCall = { }; export type BuiltinTurnPayload = { - index: number; role: BuiltinTurnRole; content: string; toolCalls: BuiltinToolCall[] | null; diff --git a/packages/workflow-agent-claude-code/__tests__/session-detail.test.ts b/packages/workflow-agent-claude-code/__tests__/session-detail.test.ts index 74af338..db67558 100644 --- a/packages/workflow-agent-claude-code/__tests__/session-detail.test.ts +++ b/packages/workflow-agent-claude-code/__tests__/session-detail.test.ts @@ -73,9 +73,7 @@ describe("parseClaudeCodeStreamOutput", () => { type: "user", message: { role: "user", - content: [ - { type: "tool_result", tool_use_id: "tool_1", content: "file1.ts\nfile2.ts" }, - ], + content: [{ type: "tool_result", tool_use_id: "tool_1", content: "file1.ts\nfile2.ts" }], }, session_id: "sess-123", }), @@ -167,7 +165,12 @@ describe("storeClaudeCodeDetail", () => { durationMs: 15000, model: "claude-sonnet-4.5", stopReason: "end_turn", - usage: { inputTokens: 100, outputTokens: 50, cacheReadInputTokens: 0, cacheCreationInputTokens: 0 }, + usage: { + inputTokens: 100, + outputTokens: 50, + cacheReadInputTokens: 0, + cacheCreationInputTokens: 0, + }, turns: [ { index: 0, role: "assistant", content: "hello", toolCalls: null }, { index: 1, role: "tool_result", content: "world", toolCalls: null }, diff --git a/packages/workflow-agent-claude-code/src/claude-code.ts b/packages/workflow-agent-claude-code/src/claude-code.ts index a5af640..550a2e2 100644 --- a/packages/workflow-agent-claude-code/src/claude-code.ts +++ b/packages/workflow-agent-claude-code/src/claude-code.ts @@ -1,8 +1,5 @@ import { spawn } from "node:child_process"; import type { Store } from "@uncaged/json-cas"; - -import { createLogger } from "@uncaged/workflow-util"; - import { type AgentContext, type AgentRunResult, @@ -11,6 +8,7 @@ import { getCachedSessionId, setCachedSessionId, } from "@uncaged/workflow-agent-kit"; +import { createLogger } from "@uncaged/workflow-util"; import { parseClaudeCodeStreamOutput, storeClaudeCodeDetail } from "./session-detail.js"; @@ -149,7 +147,12 @@ async function runClaudeCode(ctx: AgentContext): Promise { } return result; } catch (err) { - log("5VKR8N3Q", "resume failed for session %s, falling back to fresh run: %s", cachedSessionId, err); + log( + "5VKR8N3Q", + "resume failed for session %s, falling back to fresh run: %s", + cachedSessionId, + err, + ); } } } diff --git a/packages/workflow-agent-hermes/__tests__/acp-client.test.ts b/packages/workflow-agent-hermes/__tests__/acp-client.test.ts index 54db937..10f7b75 100644 --- a/packages/workflow-agent-hermes/__tests__/acp-client.test.ts +++ b/packages/workflow-agent-hermes/__tests__/acp-client.test.ts @@ -54,7 +54,8 @@ describe("HermesAcpClient", () => { { timeout: 2 * 60 * 1000 }, ); - it( + // TODO(#435): flaky — depends on live LLM; mock or move to integration suite + it.skip( "prompt() collects structured messages including tool calls", async () => { await client.connect(process.cwd()); diff --git a/packages/workflow-agent-hermes/__tests__/resume-e2e.test.ts b/packages/workflow-agent-hermes/__tests__/resume-e2e.test.ts index 6305183..b68cc1f 100644 --- a/packages/workflow-agent-hermes/__tests__/resume-e2e.test.ts +++ b/packages/workflow-agent-hermes/__tests__/resume-e2e.test.ts @@ -21,7 +21,8 @@ describe("HermesAcpClient cross-process resume", () => { clients.length = 0; }); - it( + // TODO(#435): flaky — depends on live LLM; mock or move to integration suite + it.skip( "resume() after close — second prompt returns non-empty text", async () => { // --- Client A: first run --- diff --git a/packages/workflow-agent-hermes/src/acp-client.ts b/packages/workflow-agent-hermes/src/acp-client.ts index 400c7c6..beb6213 100644 --- a/packages/workflow-agent-hermes/src/acp-client.ts +++ b/packages/workflow-agent-hermes/src/acp-client.ts @@ -267,8 +267,7 @@ export class HermesAcpClient { case "tool_call": { const title = (update.title as string) ?? ""; const rawInput = update.rawInput; - const args = - rawInput !== undefined && rawInput !== null ? JSON.stringify(rawInput) : ""; + const args = rawInput !== undefined && rawInput !== null ? JSON.stringify(rawInput) : ""; const toolCallId = update.toolCallId as string; this.pendingTools.set(toolCallId, { name: title, args }); diff --git a/packages/workflow-agent-kit/src/index.ts b/packages/workflow-agent-kit/src/index.ts index 6d18e65..ffeaaa6 100644 --- a/packages/workflow-agent-kit/src/index.ts +++ b/packages/workflow-agent-kit/src/index.ts @@ -12,8 +12,8 @@ export { export type { FrontmatterFastPathResult } from "./frontmatter.js"; export { tryFrontmatterFastPath } from "./frontmatter.js"; export { createAgent } from "./run.js"; -export { getConfigPath, getEnvPath, loadWorkflowConfig, resolveStorageRoot } from "./storage.js"; export { getCachedSessionId, setCachedSessionId } from "./session-cache.js"; +export { getConfigPath, getEnvPath, loadWorkflowConfig, resolveStorageRoot } from "./storage.js"; export type { AgentContext, AgentContinueFn, diff --git a/packages/workflow-agent-kit/src/session-cache.ts b/packages/workflow-agent-kit/src/session-cache.ts index 238afc9..fd94de5 100644 --- a/packages/workflow-agent-kit/src/session-cache.ts +++ b/packages/workflow-agent-kit/src/session-cache.ts @@ -1,5 +1,5 @@ -import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; import { randomBytes } from "node:crypto"; +import { mkdir, readFile, rename, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import type { ThreadId } from "@uncaged/workflow-protocol"; diff --git a/packages/workflow-dashboard/index.html b/packages/workflow-dashboard/index.html index 0948c36..e566099 100644 --- a/packages/workflow-dashboard/index.html +++ b/packages/workflow-dashboard/index.html @@ -6,8 +6,8 @@ Workflow UI