Merge pull request 'feat: workflow-agent-claude-code' (#393) from feat/391-workflow-agent-claude-code into main
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import type { AgentContext } from "@uncaged/workflow-agent-kit";
|
||||||
|
import { buildClaudeCodePrompt } from "../src/claude-code.js";
|
||||||
|
|
||||||
|
function makeCtx(overrides: Partial<AgentContext> = {}): AgentContext {
|
||||||
|
return {
|
||||||
|
workflow: {
|
||||||
|
roles: {
|
||||||
|
developer: {
|
||||||
|
description: "TDD implementation per test spec",
|
||||||
|
goal: "Write code",
|
||||||
|
capabilities: ["coding"],
|
||||||
|
procedure: "1. Read spec\n2. Write code",
|
||||||
|
output: "List files changed",
|
||||||
|
frontmatter: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
conditions: {},
|
||||||
|
graph: {},
|
||||||
|
},
|
||||||
|
role: "developer",
|
||||||
|
start: { prompt: "Fix the bug", workflowHash: "abc123", threadId: "t1" },
|
||||||
|
steps: [],
|
||||||
|
store: {} as AgentContext["store"],
|
||||||
|
outputFormatInstruction: "Use YAML frontmatter",
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("buildClaudeCodePrompt", () => {
|
||||||
|
test("assembles outputFormatInstruction + role prompt + task prompt", () => {
|
||||||
|
const result = buildClaudeCodePrompt(makeCtx());
|
||||||
|
expect(result).toMatch(/^Use YAML frontmatter/);
|
||||||
|
expect(result).toContain("Write code");
|
||||||
|
expect(result).toContain("## Task\nFix the bug");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("includes previous steps as history summary", () => {
|
||||||
|
const ctx = makeCtx({
|
||||||
|
steps: [{ role: "planner", output: '{"plan":"do X"}', agent: "hermes" }],
|
||||||
|
});
|
||||||
|
const result = buildClaudeCodePrompt(ctx);
|
||||||
|
expect(result).toContain("## Previous Steps");
|
||||||
|
expect(result).toContain("Step 1: planner");
|
||||||
|
expect(result).toContain("do X");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("omits history section when steps array is empty", () => {
|
||||||
|
const result = buildClaudeCodePrompt(makeCtx({ steps: [] }));
|
||||||
|
expect(result).not.toContain("## Previous Steps");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("works without outputFormatInstruction", () => {
|
||||||
|
const result = buildClaudeCodePrompt(makeCtx({ outputFormatInstruction: "" }));
|
||||||
|
expect(result).not.toMatch(/^\s*\n/);
|
||||||
|
expect(result).toContain("Write code");
|
||||||
|
expect(result).toContain("## Task");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { createMemoryStore, walk } from "@uncaged/json-cas";
|
||||||
|
import {
|
||||||
|
parseClaudeCodeJsonOutput,
|
||||||
|
storeClaudeCodeDetail,
|
||||||
|
storeClaudeCodeRawOutput,
|
||||||
|
} from "../src/session-detail.js";
|
||||||
|
import type { ClaudeCodeParsedResult } from "../src/types.js";
|
||||||
|
|
||||||
|
describe("parseClaudeCodeJsonOutput", () => {
|
||||||
|
test("parses valid claude -p --output-format json output", () => {
|
||||||
|
const stdout = JSON.stringify({
|
||||||
|
type: "result",
|
||||||
|
subtype: "success",
|
||||||
|
result: "Done fixing bug",
|
||||||
|
session_id: "75e2167f-abc",
|
||||||
|
num_turns: 3,
|
||||||
|
total_cost_usd: 0.08,
|
||||||
|
duration_ms: 10276,
|
||||||
|
});
|
||||||
|
const parsed = parseClaudeCodeJsonOutput(stdout);
|
||||||
|
expect(parsed).not.toBeNull();
|
||||||
|
expect(parsed!.type).toBe("result");
|
||||||
|
expect(parsed!.subtype).toBe("success");
|
||||||
|
expect(parsed!.result).toBe("Done fixing bug");
|
||||||
|
expect(parsed!.sessionId).toBe("75e2167f-abc");
|
||||||
|
expect(parsed!.numTurns).toBe(3);
|
||||||
|
expect(parsed!.totalCostUsd).toBe(0.08);
|
||||||
|
expect(parsed!.durationMs).toBe(10276);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("parses error_max_turns result", () => {
|
||||||
|
const stdout = JSON.stringify({
|
||||||
|
type: "result",
|
||||||
|
subtype: "error_max_turns",
|
||||||
|
result: "Ran out of turns",
|
||||||
|
session_id: "abc-def",
|
||||||
|
num_turns: 90,
|
||||||
|
total_cost_usd: 1.5,
|
||||||
|
duration_ms: 50000,
|
||||||
|
});
|
||||||
|
const parsed = parseClaudeCodeJsonOutput(stdout);
|
||||||
|
expect(parsed).not.toBeNull();
|
||||||
|
expect(parsed!.subtype).toBe("error_max_turns");
|
||||||
|
expect(parsed!.result).toBe("Ran out of turns");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns null for non-JSON output", () => {
|
||||||
|
const parsed = parseClaudeCodeJsonOutput("Some random text\nwithout JSON");
|
||||||
|
expect(parsed).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns null when session_id is missing", () => {
|
||||||
|
const stdout = JSON.stringify({ type: "result", result: "hi", subtype: "success" });
|
||||||
|
const parsed = parseClaudeCodeJsonOutput(stdout);
|
||||||
|
expect(parsed).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("storeClaudeCodeDetail", () => {
|
||||||
|
test("stores claude-code-detail CAS node and returns output + detailHash", async () => {
|
||||||
|
const store = createMemoryStore();
|
||||||
|
const parsed: ClaudeCodeParsedResult = {
|
||||||
|
type: "result",
|
||||||
|
subtype: "success",
|
||||||
|
result: "The answer",
|
||||||
|
sessionId: "abc-123",
|
||||||
|
numTurns: 5,
|
||||||
|
totalCostUsd: 0.12,
|
||||||
|
durationMs: 15000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { detailHash, output, sessionId } = await storeClaudeCodeDetail(store, parsed);
|
||||||
|
expect(detailHash).toHaveLength(13);
|
||||||
|
expect(output).toBe("The answer");
|
||||||
|
expect(sessionId).toBe("abc-123");
|
||||||
|
|
||||||
|
const node = await store.get(detailHash);
|
||||||
|
expect(node).not.toBeNull();
|
||||||
|
expect(node!.payload.sessionId).toBe("abc-123");
|
||||||
|
expect(node!.payload.numTurns).toBe(5);
|
||||||
|
expect(node!.payload.totalCostUsd).toBe(0.12);
|
||||||
|
expect(node!.payload.durationMs).toBe(15000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("detail node is walkable from root", async () => {
|
||||||
|
const store = createMemoryStore();
|
||||||
|
const parsed: ClaudeCodeParsedResult = {
|
||||||
|
type: "result",
|
||||||
|
subtype: "success",
|
||||||
|
result: "walkable test",
|
||||||
|
sessionId: "walk-123",
|
||||||
|
numTurns: 1,
|
||||||
|
totalCostUsd: 0.01,
|
||||||
|
durationMs: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { detailHash } = await storeClaudeCodeDetail(store, parsed);
|
||||||
|
const visited: string[] = [];
|
||||||
|
walk(store, detailHash, (hash) => visited.push(hash));
|
||||||
|
expect(visited.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("storeClaudeCodeRawOutput", () => {
|
||||||
|
test("stores raw text when JSON parsing fails", async () => {
|
||||||
|
const store = createMemoryStore();
|
||||||
|
const rawText = "Claude produced plain text without JSON";
|
||||||
|
const hash = await storeClaudeCodeRawOutput(store, rawText);
|
||||||
|
expect(hash).toHaveLength(13);
|
||||||
|
const node = await store.get(hash);
|
||||||
|
expect(node).not.toBeNull();
|
||||||
|
expect(node!.payload.text).toBe(rawText);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "@uncaged/workflow-agent-claude-code",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"dist",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"uwf-claude-code": "./src/cli.ts"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"bun": "./src/index.ts",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "bun test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@uncaged/json-cas": "^0.4.0",
|
||||||
|
"@uncaged/workflow-agent-kit": "workspace:^"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import type { Store } from "@uncaged/json-cas";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type AgentContext,
|
||||||
|
type AgentRunResult,
|
||||||
|
buildRolePrompt,
|
||||||
|
createAgent,
|
||||||
|
} from "@uncaged/workflow-agent-kit";
|
||||||
|
|
||||||
|
import { parseClaudeCodeJsonOutput, storeClaudeCodeDetail } from "./session-detail.js";
|
||||||
|
|
||||||
|
const CLAUDE_COMMAND = "claude";
|
||||||
|
const CLAUDE_MAX_TURNS = 90;
|
||||||
|
|
||||||
|
function buildHistorySummary(steps: AgentContext["steps"]): string {
|
||||||
|
if (steps.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines: string[] = ["## Previous Steps"];
|
||||||
|
for (let i = 0; i < steps.length; i++) {
|
||||||
|
const step = steps[i];
|
||||||
|
if (step === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lines.push("");
|
||||||
|
lines.push(`### Step ${i + 1}: ${step.role}`);
|
||||||
|
lines.push(`Output: ${JSON.stringify(step.output)}`);
|
||||||
|
lines.push(`Agent: ${step.agent}`);
|
||||||
|
}
|
||||||
|
return lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Assemble system prompt, task, and prior step outputs for Claude Code. */
|
||||||
|
export function buildClaudeCodePrompt(ctx: AgentContext): string {
|
||||||
|
const roleDef = ctx.workflow.roles[ctx.role];
|
||||||
|
const rolePrompt = roleDef !== undefined ? buildRolePrompt(roleDef) : "";
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (ctx.outputFormatInstruction !== undefined && ctx.outputFormatInstruction !== "") {
|
||||||
|
parts.push(ctx.outputFormatInstruction, "");
|
||||||
|
}
|
||||||
|
parts.push(rolePrompt, "", "## Task", ctx.start.prompt);
|
||||||
|
const historyBlock = buildHistorySummary(ctx.steps);
|
||||||
|
if (historyBlock !== "") {
|
||||||
|
parts.push("", historyBlock);
|
||||||
|
}
|
||||||
|
return parts.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnClaude(args: string[]): Promise<{ stdout: string; stderr: string }> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(CLAUDE_COMMAND, args, {
|
||||||
|
env: process.env,
|
||||||
|
shell: false,
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
|
||||||
|
let stdout = "";
|
||||||
|
let stderr = "";
|
||||||
|
child.stdout?.on("data", (chunk: Buffer) => {
|
||||||
|
stdout += chunk.toString();
|
||||||
|
});
|
||||||
|
child.stderr?.on("data", (chunk: Buffer) => {
|
||||||
|
stderr += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("error", (cause) => {
|
||||||
|
const message = cause instanceof Error ? cause.message : String(cause);
|
||||||
|
reject(new Error(`claude spawn failed: ${message}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("close", (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve({ stdout, stderr });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const detail = stderr.trim() !== "" ? ` stderr=${stderr.trim()}` : "";
|
||||||
|
reject(new Error(`claude exited with code ${code ?? "null"}${detail}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnClaudeRun(prompt: string): Promise<{ stdout: string; stderr: string }> {
|
||||||
|
return spawnClaude([
|
||||||
|
"-p",
|
||||||
|
prompt,
|
||||||
|
"--output-format",
|
||||||
|
"json",
|
||||||
|
"--dangerously-skip-permissions",
|
||||||
|
"--max-turns",
|
||||||
|
String(CLAUDE_MAX_TURNS),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnClaudeResume(
|
||||||
|
sessionId: string,
|
||||||
|
message: string,
|
||||||
|
): Promise<{ stdout: string; stderr: string }> {
|
||||||
|
return spawnClaude([
|
||||||
|
"-p",
|
||||||
|
message,
|
||||||
|
"--resume",
|
||||||
|
sessionId,
|
||||||
|
"--output-format",
|
||||||
|
"json",
|
||||||
|
"--dangerously-skip-permissions",
|
||||||
|
"--max-turns",
|
||||||
|
String(CLAUDE_MAX_TURNS),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processClaudeOutput(stdout: string, store: Store): Promise<AgentRunResult> {
|
||||||
|
const parsed = parseClaudeCodeJsonOutput(stdout);
|
||||||
|
|
||||||
|
if (parsed !== null) {
|
||||||
|
const { detailHash, output, sessionId } = await storeClaudeCodeDetail(store, parsed);
|
||||||
|
return { output, detailHash, sessionId };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Claude Code returned non-JSON output (first 200 chars): ${stdout.slice(0, 200)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runClaudeCode(ctx: AgentContext): Promise<AgentRunResult> {
|
||||||
|
const fullPrompt = buildClaudeCodePrompt(ctx);
|
||||||
|
const { stdout } = await spawnClaudeRun(fullPrompt);
|
||||||
|
return processClaudeOutput(stdout, ctx.store);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function continueClaudeCode(
|
||||||
|
sessionId: string,
|
||||||
|
message: string,
|
||||||
|
store: Store,
|
||||||
|
): Promise<AgentRunResult> {
|
||||||
|
const { stdout } = await spawnClaudeResume(sessionId, message);
|
||||||
|
return processClaudeOutput(stdout, store);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Agent CLI factory: parses argv, runs Claude Code, extracts output, writes StepNode. */
|
||||||
|
export function createClaudeCodeAgent(): () => Promise<void> {
|
||||||
|
return createAgent({
|
||||||
|
name: "claude-code",
|
||||||
|
run: runClaudeCode,
|
||||||
|
continue: continueClaudeCode,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import { createClaudeCodeAgent } from "./claude-code.js";
|
||||||
|
|
||||||
|
const main = createClaudeCodeAgent();
|
||||||
|
void main();
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export { buildClaudeCodePrompt, createClaudeCodeAgent } from "./claude-code.js";
|
||||||
|
export {
|
||||||
|
parseClaudeCodeJsonOutput,
|
||||||
|
storeClaudeCodeDetail,
|
||||||
|
storeClaudeCodeRawOutput,
|
||||||
|
} from "./session-detail.js";
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import type { JSONSchema } from "@uncaged/json-cas";
|
||||||
|
|
||||||
|
export const CLAUDE_CODE_DETAIL_SCHEMA: JSONSchema = {
|
||||||
|
title: "claude-code-detail",
|
||||||
|
type: "object",
|
||||||
|
required: ["sessionId", "numTurns", "totalCostUsd", "durationMs", "subtype"],
|
||||||
|
properties: {
|
||||||
|
sessionId: { type: "string" },
|
||||||
|
numTurns: { type: "integer" },
|
||||||
|
totalCostUsd: { type: "number" },
|
||||||
|
durationMs: { type: "integer" },
|
||||||
|
subtype: { type: "string" },
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CLAUDE_CODE_RAW_OUTPUT_SCHEMA: JSONSchema = {
|
||||||
|
title: "claude-code-raw-output",
|
||||||
|
type: "object",
|
||||||
|
required: ["text"],
|
||||||
|
properties: {
|
||||||
|
text: { type: "string" },
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
};
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { bootstrap, putSchema, type Store } from "@uncaged/json-cas";
|
||||||
|
|
||||||
|
import { CLAUDE_CODE_DETAIL_SCHEMA, CLAUDE_CODE_RAW_OUTPUT_SCHEMA } from "./schemas.js";
|
||||||
|
import type { ClaudeCodeDetailPayload, ClaudeCodeParsedResult } from "./types.js";
|
||||||
|
|
||||||
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse Claude Code JSON stdout (`claude -p --output-format json`). */
|
||||||
|
export function parseClaudeCodeJsonOutput(stdout: string): ClaudeCodeParsedResult | null {
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(stdout.trim());
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isRecord(parsed)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionId = parsed.session_id;
|
||||||
|
const result = parsed.result;
|
||||||
|
const subtype = parsed.subtype;
|
||||||
|
|
||||||
|
if (typeof sessionId !== "string" || typeof result !== "string" || typeof subtype !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: typeof parsed.type === "string" ? parsed.type : "result",
|
||||||
|
subtype: subtype as ClaudeCodeParsedResult["subtype"],
|
||||||
|
result,
|
||||||
|
sessionId,
|
||||||
|
numTurns: typeof parsed.num_turns === "number" ? parsed.num_turns : 0,
|
||||||
|
totalCostUsd: typeof parsed.total_cost_usd === "number" ? parsed.total_cost_usd : 0,
|
||||||
|
durationMs: typeof parsed.duration_ms === "number" ? parsed.duration_ms : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClaudeCodeSchemaHashes = {
|
||||||
|
detail: string;
|
||||||
|
rawOutput: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function registerSchemas(store: Store): Promise<ClaudeCodeSchemaHashes> {
|
||||||
|
await bootstrap(store);
|
||||||
|
const [detail, rawOutput] = await Promise.all([
|
||||||
|
putSchema(store, CLAUDE_CODE_DETAIL_SCHEMA),
|
||||||
|
putSchema(store, CLAUDE_CODE_RAW_OUTPUT_SCHEMA),
|
||||||
|
]);
|
||||||
|
return { detail, rawOutput };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Store parsed Claude Code result as a CAS detail node. */
|
||||||
|
export async function storeClaudeCodeDetail(
|
||||||
|
store: Store,
|
||||||
|
parsed: ClaudeCodeParsedResult,
|
||||||
|
): Promise<{ detailHash: string; output: string; sessionId: string }> {
|
||||||
|
const schemas = await registerSchemas(store);
|
||||||
|
|
||||||
|
const detail: ClaudeCodeDetailPayload = {
|
||||||
|
sessionId: parsed.sessionId,
|
||||||
|
numTurns: parsed.numTurns,
|
||||||
|
totalCostUsd: parsed.totalCostUsd,
|
||||||
|
durationMs: parsed.durationMs,
|
||||||
|
subtype: parsed.subtype,
|
||||||
|
};
|
||||||
|
|
||||||
|
const detailHash = await store.put(schemas.detail, detail);
|
||||||
|
return { detailHash, output: parsed.result, sessionId: parsed.sessionId };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fallback: store raw text output when JSON parsing fails. */
|
||||||
|
export async function storeClaudeCodeRawOutput(store: Store, rawOutput: string): Promise<string> {
|
||||||
|
const schemas = await registerSchemas(store);
|
||||||
|
return store.put(schemas.rawOutput, { text: rawOutput });
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export type ClaudeCodeResultSubtype = "success" | "error_max_turns" | "error_budget";
|
||||||
|
|
||||||
|
export type ClaudeCodeParsedResult = {
|
||||||
|
type: string;
|
||||||
|
subtype: ClaudeCodeResultSubtype;
|
||||||
|
result: string;
|
||||||
|
sessionId: string;
|
||||||
|
numTurns: number;
|
||||||
|
totalCostUsd: number;
|
||||||
|
durationMs: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ClaudeCodeDetailPayload = {
|
||||||
|
sessionId: string;
|
||||||
|
numTurns: number;
|
||||||
|
totalCostUsd: number;
|
||||||
|
durationMs: number;
|
||||||
|
subtype: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": { "rootDir": "src", "outDir": "dist" },
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "../workflow-agent-kit" }]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user