@desplega.ai/agent-swarm 1.2.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +20 -1
- package/.env.docker.example +22 -1
- package/.env.example +17 -0
- package/.github/workflows/docker-publish.yml +92 -0
- package/CONTRIBUTING.md +270 -0
- package/DEPLOYMENT.md +391 -0
- package/Dockerfile.worker +29 -1
- package/FAQ.md +19 -0
- package/LICENSE +21 -0
- package/MCP.md +249 -0
- package/README.md +103 -207
- package/assets/agent-swarm-logo-orange.png +0 -0
- package/assets/agent-swarm-logo.png +0 -0
- package/docker-compose.example.yml +137 -0
- package/docker-entrypoint.sh +223 -7
- package/package.json +8 -3
- package/{cc-plugin → plugin}/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/codebase-analyzer.md +143 -0
- package/plugin/agents/codebase-locator.md +122 -0
- package/plugin/agents/codebase-pattern-finder.md +227 -0
- package/plugin/agents/web-search-researcher.md +109 -0
- package/plugin/commands/create-plan.md +415 -0
- package/plugin/commands/implement-plan.md +89 -0
- package/plugin/commands/research.md +200 -0
- package/plugin/commands/start-leader.md +101 -0
- package/plugin/commands/start-worker.md +56 -0
- package/plugin/commands/swarm-chat.md +78 -0
- package/plugin/commands/todos.md +66 -0
- package/plugin/commands/work-on-task.md +44 -0
- package/plugin/skills/.gitkeep +0 -0
- package/scripts/generate-mcp-docs.ts +415 -0
- package/slack-manifest.json +69 -0
- package/src/be/db.ts +1431 -25
- package/src/cli.tsx +135 -11
- package/src/commands/lead.ts +13 -0
- package/src/commands/runner.ts +255 -0
- package/src/commands/worker.ts +8 -220
- package/src/hooks/hook.ts +102 -14
- package/src/http.ts +361 -5
- package/src/prompts/base-prompt.ts +131 -0
- package/src/server.ts +56 -0
- package/src/slack/app.ts +73 -0
- package/src/slack/commands.ts +88 -0
- package/src/slack/handlers.ts +281 -0
- package/src/slack/index.ts +3 -0
- package/src/slack/responses.ts +175 -0
- package/src/slack/router.ts +170 -0
- package/src/slack/types.ts +20 -0
- package/src/slack/watcher.ts +119 -0
- package/src/tools/create-channel.ts +80 -0
- package/src/tools/get-tasks.ts +54 -21
- package/src/tools/join-swarm.ts +28 -4
- package/src/tools/list-channels.ts +37 -0
- package/src/tools/list-services.ts +110 -0
- package/src/tools/poll-task.ts +46 -3
- package/src/tools/post-message.ts +87 -0
- package/src/tools/read-messages.ts +192 -0
- package/src/tools/register-service.ts +118 -0
- package/src/tools/send-task.ts +80 -7
- package/src/tools/store-progress.ts +9 -3
- package/src/tools/task-action.ts +211 -0
- package/src/tools/unregister-service.ts +110 -0
- package/src/tools/update-profile.ts +105 -0
- package/src/tools/update-service-status.ts +118 -0
- package/src/types.ts +110 -3
- package/src/utils/pretty-print.ts +224 -0
- package/thoughts/shared/plans/.gitkeep +0 -0
- package/thoughts/shared/plans/2025-12-18-inverse-teleport.md +1142 -0
- package/thoughts/shared/plans/2025-12-18-slack-integration.md +1195 -0
- package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +732 -0
- package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +361 -0
- package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +501 -0
- package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +560 -0
- package/thoughts/shared/research/.gitkeep +0 -0
- package/thoughts/shared/research/2025-12-18-slack-integration.md +442 -0
- package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +339 -0
- package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +390 -0
- package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +376 -0
- package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +264 -0
- package/tsconfig.json +3 -1
- package/ui/bun.lock +692 -0
- package/ui/index.html +22 -0
- package/ui/package.json +32 -0
- package/ui/pnpm-lock.yaml +3034 -0
- package/ui/postcss.config.js +6 -0
- package/ui/public/logo.png +0 -0
- package/ui/src/App.tsx +43 -0
- package/ui/src/components/ActivityFeed.tsx +415 -0
- package/ui/src/components/AgentDetailPanel.tsx +534 -0
- package/ui/src/components/AgentsPanel.tsx +549 -0
- package/ui/src/components/ChatPanel.tsx +1820 -0
- package/ui/src/components/ConfigModal.tsx +232 -0
- package/ui/src/components/Dashboard.tsx +534 -0
- package/ui/src/components/Header.tsx +168 -0
- package/ui/src/components/ServicesPanel.tsx +612 -0
- package/ui/src/components/StatsBar.tsx +288 -0
- package/ui/src/components/StatusBadge.tsx +124 -0
- package/ui/src/components/TaskDetailPanel.tsx +807 -0
- package/ui/src/components/TasksPanel.tsx +575 -0
- package/ui/src/hooks/queries.ts +170 -0
- package/ui/src/index.css +235 -0
- package/ui/src/lib/api.ts +161 -0
- package/ui/src/lib/config.ts +35 -0
- package/ui/src/lib/theme.ts +214 -0
- package/ui/src/lib/utils.ts +48 -0
- package/ui/src/main.tsx +32 -0
- package/ui/src/types/api.ts +164 -0
- package/ui/src/vite-env.d.ts +1 -0
- package/ui/tailwind.config.js +35 -0
- package/ui/tsconfig.json +31 -0
- package/ui/vite.config.ts +22 -0
- package/cc-plugin/README.md +0 -49
- package/cc-plugin/commands/setup-leader.md +0 -73
- package/cc-plugin/commands/start-worker.md +0 -64
- package/docker-compose.worker.yml +0 -35
- package/example-req-meta.json +0 -24
- /package/{cc-plugin → plugin}/hooks/hooks.json +0 -0
package/src/types.ts
CHANGED
|
@@ -1,21 +1,56 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Task status - includes new unassigned and offered states
|
|
4
|
+
export const AgentTaskStatusSchema = z.enum([
|
|
5
|
+
"unassigned", // Task pool - no owner yet
|
|
6
|
+
"offered", // Offered to agent, awaiting accept/reject
|
|
7
|
+
"pending", // Assigned/accepted, waiting to start
|
|
8
|
+
"in_progress",
|
|
9
|
+
"completed",
|
|
10
|
+
"failed",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export const AgentTaskSourceSchema = z.enum(["mcp", "slack", "api"]);
|
|
14
|
+
export type AgentTaskSource = z.infer<typeof AgentTaskSourceSchema>;
|
|
4
15
|
|
|
5
16
|
export const AgentTaskSchema = z.object({
|
|
6
17
|
id: z.uuid(),
|
|
7
|
-
agentId: z.uuid(),
|
|
18
|
+
agentId: z.uuid().nullable(), // Nullable for unassigned tasks
|
|
19
|
+
creatorAgentId: z.uuid().optional(), // Who created this task (optional for Slack/API)
|
|
8
20
|
task: z.string().min(1),
|
|
9
21
|
status: AgentTaskStatusSchema,
|
|
22
|
+
source: AgentTaskSourceSchema.default("mcp"),
|
|
23
|
+
|
|
24
|
+
// Task metadata
|
|
25
|
+
taskType: z.string().max(50).optional(), // e.g., "bug", "feature", "chore"
|
|
26
|
+
tags: z.array(z.string()).default([]), // e.g., ["urgent", "frontend"]
|
|
27
|
+
priority: z.number().int().min(0).max(100).default(50),
|
|
28
|
+
dependsOn: z.array(z.uuid()).default([]), // Task IDs this depends on
|
|
10
29
|
|
|
30
|
+
// Acceptance tracking
|
|
31
|
+
offeredTo: z.uuid().optional(), // Agent the task was offered to
|
|
32
|
+
offeredAt: z.iso.datetime().optional(),
|
|
33
|
+
acceptedAt: z.iso.datetime().optional(),
|
|
34
|
+
rejectionReason: z.string().optional(),
|
|
35
|
+
|
|
36
|
+
// Timestamps
|
|
11
37
|
createdAt: z.iso.datetime().default(() => new Date().toISOString()),
|
|
12
38
|
lastUpdatedAt: z.iso.datetime().default(() => new Date().toISOString()),
|
|
13
|
-
|
|
14
39
|
finishedAt: z.iso.datetime().optional(),
|
|
15
40
|
|
|
41
|
+
// Completion data
|
|
16
42
|
failureReason: z.string().optional(),
|
|
17
43
|
output: z.string().optional(),
|
|
18
44
|
progress: z.string().optional(),
|
|
45
|
+
|
|
46
|
+
// Slack-specific metadata (optional)
|
|
47
|
+
slackChannelId: z.string().optional(),
|
|
48
|
+
slackThreadTs: z.string().optional(),
|
|
49
|
+
slackUserId: z.string().optional(),
|
|
50
|
+
|
|
51
|
+
// Mention-to-task metadata (optional)
|
|
52
|
+
mentionMessageId: z.uuid().optional(),
|
|
53
|
+
mentionChannelId: z.uuid().optional(),
|
|
19
54
|
});
|
|
20
55
|
|
|
21
56
|
export const AgentStatusSchema = z.enum(["idle", "busy", "offline"]);
|
|
@@ -26,6 +61,11 @@ export const AgentSchema = z.object({
|
|
|
26
61
|
isLead: z.boolean().default(false),
|
|
27
62
|
status: AgentStatusSchema,
|
|
28
63
|
|
|
64
|
+
// Profile fields
|
|
65
|
+
description: z.string().optional(),
|
|
66
|
+
role: z.string().max(100).optional(), // Free-form, e.g., "frontend dev"
|
|
67
|
+
capabilities: z.array(z.string()).default([]), // e.g., ["typescript", "react"]
|
|
68
|
+
|
|
29
69
|
createdAt: z.iso.datetime().default(() => new Date().toISOString()),
|
|
30
70
|
lastUpdatedAt: z.iso.datetime().default(() => new Date().toISOString()),
|
|
31
71
|
});
|
|
@@ -41,6 +81,62 @@ export type AgentStatus = z.infer<typeof AgentStatusSchema>;
|
|
|
41
81
|
export type Agent = z.infer<typeof AgentSchema>;
|
|
42
82
|
export type AgentWithTasks = z.infer<typeof AgentWithTasksSchema>;
|
|
43
83
|
|
|
84
|
+
// Channel Types
|
|
85
|
+
export const ChannelTypeSchema = z.enum(["public", "dm"]);
|
|
86
|
+
|
|
87
|
+
export const ChannelSchema = z.object({
|
|
88
|
+
id: z.uuid(),
|
|
89
|
+
name: z.string().min(1).max(100),
|
|
90
|
+
description: z.string().max(500).optional(),
|
|
91
|
+
type: ChannelTypeSchema.default("public"),
|
|
92
|
+
createdBy: z.uuid().optional(),
|
|
93
|
+
participants: z.array(z.uuid()).default([]), // For DMs
|
|
94
|
+
createdAt: z.iso.datetime(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const ChannelMessageSchema = z.object({
|
|
98
|
+
id: z.uuid(),
|
|
99
|
+
channelId: z.uuid(),
|
|
100
|
+
agentId: z.uuid().nullable(), // Null for human users
|
|
101
|
+
agentName: z.string().optional(), // Denormalized for convenience, "Human" when agentId is null
|
|
102
|
+
content: z.string().min(1).max(4000),
|
|
103
|
+
replyToId: z.uuid().optional(),
|
|
104
|
+
mentions: z.array(z.uuid()).default([]), // Agent IDs mentioned
|
|
105
|
+
createdAt: z.iso.datetime(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export type ChannelType = z.infer<typeof ChannelTypeSchema>;
|
|
109
|
+
export type Channel = z.infer<typeof ChannelSchema>;
|
|
110
|
+
export type ChannelMessage = z.infer<typeof ChannelMessageSchema>;
|
|
111
|
+
|
|
112
|
+
// Service Types (for PM2/background services)
|
|
113
|
+
export const ServiceStatusSchema = z.enum(["starting", "healthy", "unhealthy", "stopped"]);
|
|
114
|
+
|
|
115
|
+
export const ServiceSchema = z.object({
|
|
116
|
+
id: z.uuid(),
|
|
117
|
+
agentId: z.uuid(),
|
|
118
|
+
name: z.string().min(1).max(50),
|
|
119
|
+
port: z.number().int().min(1).max(65535).default(3000),
|
|
120
|
+
description: z.string().optional(),
|
|
121
|
+
url: z.string().url().optional(),
|
|
122
|
+
healthCheckPath: z.string().default("/health"),
|
|
123
|
+
status: ServiceStatusSchema.default("starting"),
|
|
124
|
+
|
|
125
|
+
// PM2 configuration (required for ecosystem-based restart)
|
|
126
|
+
script: z.string().min(1), // Path to script (required)
|
|
127
|
+
cwd: z.string().optional(), // Working directory (defaults to script dir)
|
|
128
|
+
interpreter: z.string().optional(), // e.g., "node", "bun" (auto-detected if not set)
|
|
129
|
+
args: z.array(z.string()).optional(), // Command line arguments
|
|
130
|
+
env: z.record(z.string(), z.string()).optional(), // Environment variables
|
|
131
|
+
|
|
132
|
+
metadata: z.record(z.string(), z.unknown()).default({}),
|
|
133
|
+
createdAt: z.iso.datetime(),
|
|
134
|
+
lastUpdatedAt: z.iso.datetime(),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
export type ServiceStatus = z.infer<typeof ServiceStatusSchema>;
|
|
138
|
+
export type Service = z.infer<typeof ServiceSchema>;
|
|
139
|
+
|
|
44
140
|
// Agent Log Types
|
|
45
141
|
export const AgentLogEventTypeSchema = z.enum([
|
|
46
142
|
"agent_joined",
|
|
@@ -49,6 +145,17 @@ export const AgentLogEventTypeSchema = z.enum([
|
|
|
49
145
|
"task_created",
|
|
50
146
|
"task_status_change",
|
|
51
147
|
"task_progress",
|
|
148
|
+
// Task pool events
|
|
149
|
+
"task_offered",
|
|
150
|
+
"task_accepted",
|
|
151
|
+
"task_rejected",
|
|
152
|
+
"task_claimed",
|
|
153
|
+
"task_released",
|
|
154
|
+
"channel_message",
|
|
155
|
+
// Service registry events
|
|
156
|
+
"service_registered",
|
|
157
|
+
"service_unregistered",
|
|
158
|
+
"service_status_change",
|
|
52
159
|
]);
|
|
53
160
|
|
|
54
161
|
export const AgentLogSchema = z.object({
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pretty print utilities for Claude CLI output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ANSI color codes
|
|
6
|
+
const colors = {
|
|
7
|
+
reset: "\x1b[0m",
|
|
8
|
+
bold: "\x1b[1m",
|
|
9
|
+
dim: "\x1b[2m",
|
|
10
|
+
italic: "\x1b[3m",
|
|
11
|
+
underline: "\x1b[4m",
|
|
12
|
+
|
|
13
|
+
// Foreground colors
|
|
14
|
+
black: "\x1b[30m",
|
|
15
|
+
red: "\x1b[31m",
|
|
16
|
+
green: "\x1b[32m",
|
|
17
|
+
yellow: "\x1b[33m",
|
|
18
|
+
blue: "\x1b[34m",
|
|
19
|
+
magenta: "\x1b[35m",
|
|
20
|
+
cyan: "\x1b[36m",
|
|
21
|
+
white: "\x1b[37m",
|
|
22
|
+
|
|
23
|
+
// Bright colors
|
|
24
|
+
brightBlack: "\x1b[90m",
|
|
25
|
+
brightRed: "\x1b[91m",
|
|
26
|
+
brightGreen: "\x1b[92m",
|
|
27
|
+
brightYellow: "\x1b[93m",
|
|
28
|
+
brightBlue: "\x1b[94m",
|
|
29
|
+
brightMagenta: "\x1b[95m",
|
|
30
|
+
brightCyan: "\x1b[96m",
|
|
31
|
+
brightWhite: "\x1b[97m",
|
|
32
|
+
|
|
33
|
+
// Background colors
|
|
34
|
+
bgBlue: "\x1b[44m",
|
|
35
|
+
bgMagenta: "\x1b[45m",
|
|
36
|
+
bgCyan: "\x1b[46m",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const c = colors;
|
|
40
|
+
|
|
41
|
+
/** Truncate string with ellipsis */
|
|
42
|
+
function truncate(str: string, maxLen: number): string {
|
|
43
|
+
if (str.length <= maxLen) return str;
|
|
44
|
+
return `${str.slice(0, maxLen - 3)}...`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Format a tool name nicely */
|
|
48
|
+
function formatToolName(name: string): string {
|
|
49
|
+
// Shorten MCP tool names
|
|
50
|
+
if (name.startsWith("mcp__")) {
|
|
51
|
+
const parts = name.split("__");
|
|
52
|
+
return parts.length >= 3 ? `${parts[1]}:${parts[2]}` : name;
|
|
53
|
+
}
|
|
54
|
+
return name;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Format input parameters for tool calls */
|
|
58
|
+
function formatToolInput(input: Record<string, unknown>): string {
|
|
59
|
+
const entries = Object.entries(input);
|
|
60
|
+
if (entries.length === 0) return "";
|
|
61
|
+
|
|
62
|
+
const formatted = entries
|
|
63
|
+
.map(([k, v]) => {
|
|
64
|
+
const value = typeof v === "string" ? truncate(v, 50) : JSON.stringify(v);
|
|
65
|
+
return `${c.dim}${k}=${c.reset}${truncate(String(value), 60)}`;
|
|
66
|
+
})
|
|
67
|
+
.join(", ");
|
|
68
|
+
|
|
69
|
+
return ` (${formatted})`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Pretty print a single JSON line from Claude output */
|
|
73
|
+
export function prettyPrintLine(line: string, role: string): void {
|
|
74
|
+
if (!line.trim()) return;
|
|
75
|
+
|
|
76
|
+
let json: Record<string, unknown>;
|
|
77
|
+
try {
|
|
78
|
+
json = JSON.parse(line.trim());
|
|
79
|
+
} catch {
|
|
80
|
+
// Raw output - just print it
|
|
81
|
+
console.log(`${c.dim}[${role}]${c.reset} ${line.trim()}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const type = json.type as string;
|
|
86
|
+
const prefix = `${c.dim}[${role}]${c.reset}`;
|
|
87
|
+
|
|
88
|
+
switch (type) {
|
|
89
|
+
case "system": {
|
|
90
|
+
const subtype = json.subtype as string;
|
|
91
|
+
if (subtype === "init") {
|
|
92
|
+
const model = json.model as string;
|
|
93
|
+
const tools = json.tools as string[];
|
|
94
|
+
console.log(
|
|
95
|
+
`${prefix} ${c.cyan}●${c.reset} ${c.bold}Session started${c.reset} ${c.dim}(${model}, ${tools?.length || 0} tools)${c.reset}`,
|
|
96
|
+
);
|
|
97
|
+
} else if (subtype === "hook_response") {
|
|
98
|
+
const hookName = json.hook_name as string;
|
|
99
|
+
const stdout = json.stdout as string;
|
|
100
|
+
console.log(`${prefix} ${c.yellow}⚡${c.reset} Hook: ${c.yellow}${hookName}${c.reset}`);
|
|
101
|
+
if (stdout) {
|
|
102
|
+
const lines = stdout.split("\n").filter((l) => l.trim());
|
|
103
|
+
for (const l of lines.slice(0, 3)) {
|
|
104
|
+
console.log(`${prefix} ${c.dim}${truncate(l, 80)}${c.reset}`);
|
|
105
|
+
}
|
|
106
|
+
if (lines.length > 3) {
|
|
107
|
+
console.log(`${prefix} ${c.dim}... (${lines.length - 3} more lines)${c.reset}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
const msg = (json.message as string) || (json.content as string) || "";
|
|
112
|
+
console.log(
|
|
113
|
+
`${prefix} ${c.cyan}ℹ${c.reset} System${subtype ? ` (${subtype})` : ""}: ${truncate(msg, 100)}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case "assistant": {
|
|
120
|
+
const message = json.message as Record<string, unknown>;
|
|
121
|
+
if (!message) break;
|
|
122
|
+
|
|
123
|
+
const content = message.content as Array<Record<string, unknown>>;
|
|
124
|
+
if (!content) break;
|
|
125
|
+
|
|
126
|
+
for (const block of content) {
|
|
127
|
+
if (block.type === "text") {
|
|
128
|
+
const text = block.text as string;
|
|
129
|
+
console.log(`${prefix} ${c.green}◆${c.reset} ${c.bold}Assistant:${c.reset}`);
|
|
130
|
+
// Print text with nice indentation, truncate long lines
|
|
131
|
+
const lines = text.split("\n");
|
|
132
|
+
for (const l of lines.slice(0, 5)) {
|
|
133
|
+
console.log(`${prefix} ${truncate(l, 100)}`);
|
|
134
|
+
}
|
|
135
|
+
if (lines.length > 5) {
|
|
136
|
+
console.log(`${prefix} ${c.dim}... (${lines.length - 5} more lines)${c.reset}`);
|
|
137
|
+
}
|
|
138
|
+
} else if (block.type === "tool_use") {
|
|
139
|
+
const toolName = formatToolName((block.name as string) || "unknown");
|
|
140
|
+
const input = (block.input as Record<string, unknown>) || {};
|
|
141
|
+
console.log(
|
|
142
|
+
`${prefix} ${c.magenta}▶${c.reset} Tool: ${c.magenta}${toolName}${c.reset}${formatToolInput(input)}`,
|
|
143
|
+
);
|
|
144
|
+
} else if (block.type === "thinking") {
|
|
145
|
+
const thinking = block.thinking as string;
|
|
146
|
+
console.log(`${prefix} ${c.blue}💭${c.reset} ${c.dim}Thinking...${c.reset}`);
|
|
147
|
+
if (thinking) {
|
|
148
|
+
console.log(`${prefix} ${c.dim}${truncate(thinking, 80)}${c.reset}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case "user": {
|
|
156
|
+
const message = json.message as Record<string, unknown>;
|
|
157
|
+
const toolResult = json.tool_use_result as string;
|
|
158
|
+
|
|
159
|
+
if (toolResult) {
|
|
160
|
+
const isError = toolResult.includes("Error") || toolResult.includes("error");
|
|
161
|
+
const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
162
|
+
console.log(`${prefix} ${icon} Result: ${truncate(toolResult, 100)}`);
|
|
163
|
+
} else if (message) {
|
|
164
|
+
const content = message.content as Array<Record<string, unknown>>;
|
|
165
|
+
if (content) {
|
|
166
|
+
for (const block of content) {
|
|
167
|
+
if (block.type === "tool_result") {
|
|
168
|
+
const result = block.content as string;
|
|
169
|
+
const isError = block.is_error as boolean;
|
|
170
|
+
const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
171
|
+
console.log(`${prefix} ${icon} Result: ${truncate(result || "", 100)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case "result": {
|
|
180
|
+
const subtype = json.subtype as string;
|
|
181
|
+
const isError = json.is_error as boolean;
|
|
182
|
+
const duration = json.duration_ms as number;
|
|
183
|
+
const cost = json.total_cost_usd as number;
|
|
184
|
+
const numTurns = json.num_turns as number;
|
|
185
|
+
const result = json.result as string;
|
|
186
|
+
|
|
187
|
+
const icon = isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
188
|
+
const durationStr = duration ? `${(duration / 1000).toFixed(1)}s` : "";
|
|
189
|
+
const costStr = cost ? `$${cost.toFixed(4)}` : "";
|
|
190
|
+
|
|
191
|
+
console.log(
|
|
192
|
+
`${prefix} ${icon} ${c.bold}Done${c.reset} ${c.dim}(${subtype}, ${numTurns} turns, ${durationStr}, ${costStr})${c.reset}`,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (result) {
|
|
196
|
+
const lines = result.split("\n").filter((l) => l.trim());
|
|
197
|
+
for (const l of lines.slice(0, 3)) {
|
|
198
|
+
console.log(`${prefix} ${truncate(l, 100)}`);
|
|
199
|
+
}
|
|
200
|
+
if (lines.length > 3) {
|
|
201
|
+
console.log(`${prefix} ${c.dim}... (${lines.length - 3} more lines)${c.reset}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case "error": {
|
|
208
|
+
const error = (json.error as string) || (json.message as string) || JSON.stringify(json);
|
|
209
|
+
console.log(`${prefix} ${c.red}✗ Error:${c.reset} ${truncate(error, 100)}`);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
default: {
|
|
214
|
+
// Unknown type - print a summary
|
|
215
|
+
console.log(`${prefix} ${c.dim}[${type}]${c.reset} ${truncate(JSON.stringify(json), 100)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Pretty print stderr output */
|
|
221
|
+
export function prettyPrintStderr(text: string, role: string): void {
|
|
222
|
+
const prefix = `${c.dim}[${role}]${c.reset}`;
|
|
223
|
+
console.error(`${prefix} ${c.red}stderr:${c.reset} ${truncate(text.trim(), 100)}`);
|
|
224
|
+
}
|
|
File without changes
|