wile 0.8.0 → 1.0.1

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.
Files changed (31) hide show
  1. package/dist/agent/Dockerfile +1 -1
  2. package/dist/agent/README.md +20 -20
  3. package/dist/agent/entrypoint.sh +33 -21
  4. package/dist/agent/scripts/claude-stream.ts +159 -0
  5. package/dist/agent/scripts/codex-stream.ts +167 -0
  6. package/dist/agent/scripts/gemini-stream.ts +97 -0
  7. package/dist/agent/scripts/mock-claude.sh +48 -21
  8. package/dist/agent/scripts/mock-codex.sh +48 -21
  9. package/dist/agent/scripts/mock-gemini.sh +48 -21
  10. package/dist/agent/scripts/opencode-stream.ts +93 -0
  11. package/dist/agent/scripts/prompt-compact.md +36 -34
  12. package/dist/agent/scripts/prompt-preflight.md +0 -7
  13. package/dist/agent/scripts/prompt.md +26 -24
  14. package/dist/agent/scripts/test-additional-instructions.sh +4 -2
  15. package/dist/agent/scripts/test-env-project-docker.sh +5 -4
  16. package/dist/agent/scripts/test-iteration-limit.sh +4 -2
  17. package/dist/agent/scripts/test-prd-validation-docker.sh +151 -0
  18. package/dist/agent/scripts/test-preflight-claude-docker.sh +5 -4
  19. package/dist/agent/scripts/test-preflight-docker.sh +16 -13
  20. package/dist/agent/scripts/validate-compact.ts +134 -0
  21. package/dist/agent/scripts/validate-prd.ts +280 -0
  22. package/dist/agent/scripts/wile-compact.sh +6 -6
  23. package/dist/agent/scripts/wile-preflight.sh +4 -4
  24. package/dist/agent/scripts/wile.sh +4 -4
  25. package/dist/cli.js +236 -58
  26. package/package.json +10 -1
  27. package/dist/agent/scripts/claude-stream.js +0 -88
  28. package/dist/agent/scripts/codex-stream.js +0 -125
  29. package/dist/agent/scripts/gemini-stream.js +0 -63
  30. package/dist/agent/scripts/opencode-stream.js +0 -52
  31. package/dist/agent/scripts/validate-compact.js +0 -135
@@ -34,7 +34,7 @@ JSON
34
34
  RUN cd /opt/agent-browser \
35
35
  && bun add agent-browser \
36
36
  && node /opt/agent-browser/node_modules/agent-browser/scripts/postinstall.js \
37
- && ln -s /opt/agent-browser/node_modules/agent-browser/bin/agent-browser /usr/local/bin/agent-browser
37
+ && ln -s /opt/agent-browser/node_modules/agent-browser/bin/agent-browser.js /usr/local/bin/agent-browser
38
38
 
39
39
  # Create non-root user
40
40
  RUN useradd -m -s /bin/bash wile \
@@ -22,24 +22,24 @@ docker compose up --build
22
22
  1. Container clones your repository
23
23
  2. Checks out the specified branch
24
24
  3. **Iteration 0 (Setup)**: Sets up .gitignore, initializes progress.txt
25
- 4. Reads `.wile/prd.json` from repo
26
- 5. Loops through user stories, implementing one per iteration:
27
- - Picks highest priority story where `passes: false`
25
+ 4. Reads `.wile/prd.json`
26
+ 5. Loops through stories, implementing one per iteration:
27
+ - Picks first runnable story in array order where `status: "pending"` and dependencies are done
28
28
  - Implements the feature/fix
29
29
  - Runs tests and typecheck
30
- - Commits and pushes
31
- - Marks story as complete
30
+ - Marks story as done
32
31
  - Logs learnings
33
- 6. Repeats until all stories pass or max iterations reached
32
+ - Commits changes
33
+ 6. Repeats until all stories are done or max iterations reached
34
34
 
35
35
  ## Project Structure
36
36
 
37
- Your repository must have a `.wile/` folder:
37
+ Your repository must have a `.wile/prd.json` file:
38
38
 
39
39
  ```
40
40
  your-repo/
41
41
  ├── .wile/
42
- │ ├── prd.json # User stories (required)
42
+ │ ├── prd.json # Stories backlog (required)
43
43
  │ ├── progress.txt # Learnings log (created by agent)
44
44
  │ └── screenshots/ # Visual verification (created by agent)
45
45
  └── ... your code ...
@@ -49,18 +49,18 @@ your-repo/
49
49
 
50
50
  ```json
51
51
  {
52
- "userStories": [
52
+ "stories": [
53
53
  {
54
- "id": "US-001",
54
+ "id": 1,
55
55
  "title": "Add login form",
56
+ "description": "Email/password form with validation",
56
57
  "acceptanceCriteria": [
57
58
  "Email/password fields exist",
58
59
  "Form validates email format",
59
60
  "typecheck passes"
60
61
  ],
61
- "priority": 1,
62
- "passes": false,
63
- "notes": "Optional notes"
62
+ "dependsOn": [],
63
+ "status": "pending"
64
64
  }
65
65
  ]
66
66
  }
@@ -68,12 +68,12 @@ your-repo/
68
68
 
69
69
  ### Fields
70
70
 
71
- - `id`: Unique identifier (used in commit messages)
71
+ - `id`: Unique numeric identifier
72
72
  - `title`: Short description
73
+ - `description`: Additional context for implementation
73
74
  - `acceptanceCriteria`: List of requirements (be specific!)
74
- - `priority`: Lower number = done first
75
- - `passes`: Set to `true` by the agent when complete
76
- - `notes`: Optional context for the agent
75
+ - `dependsOn`: Array of prerequisite story IDs
76
+ - `status`: `pending` or `done`
77
77
 
78
78
  ## Tips for Success
79
79
 
@@ -134,9 +134,9 @@ For UI work, tell the agent how to verify:
134
134
 
135
135
  ## Output Files
136
136
 
137
- The `.wile/` folder is **tracked in git** (except screenshots):
137
+ Tracked artifacts:
138
138
 
139
- - `prd.json` - User stories / Product Requirements Document
139
+ - `.wile/prd.json` - Product requirements stories backlog
140
140
  - `progress.txt` - Log of completed work and learnings
141
141
  - `screenshots/` - Visual verification screenshots (gitignored)
142
142
 
@@ -157,5 +157,5 @@ git show origin/your-branch:.wile/progress.txt
157
157
  Check story status:
158
158
 
159
159
  ```bash
160
- git show origin/your-branch:.wile/prd.json | jq '.userStories[] | {id, title, passes}'
160
+ git show origin/your-branch:.wile/prd.json | jq '.stories[] | {id, title, status}'
161
161
  ```
@@ -15,6 +15,8 @@ if command -v sysctl >/dev/null 2>&1; then
15
15
  sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1 || true
16
16
  fi
17
17
 
18
+ SCRIPT_DIR="/home/wile/scripts"
19
+
18
20
  if [ "${WILE_TEST:-}" = "true" ]; then
19
21
  TEST_REPO="${WILE_TEST_REPO_PATH:-/home/wile/workspace/repo}"
20
22
 
@@ -30,18 +32,19 @@ if [ "${WILE_TEST:-}" = "true" ]; then
30
32
  exit 1
31
33
  fi
32
34
 
35
+ bun "$SCRIPT_DIR/validate-prd.ts" --path .wile/prd.json >/dev/null
36
+
33
37
  echo "TEST MODE: running mocked agent (no GitHub, no Claude)"
34
38
 
35
39
  node - << 'NODE'
36
40
  const fs = require('fs');
37
- const path = require('path');
38
-
39
- const prdPath = path.join('.wile', 'prd.json');
40
- const progressPath = path.join('.wile', 'progress.txt');
41
+ const prdPath = '.wile/prd.json';
42
+ const progressPath = '.wile/progress.txt';
41
43
 
42
44
  const prd = JSON.parse(fs.readFileSync(prdPath, 'utf8'));
43
- const stories = Array.isArray(prd.userStories) ? prd.userStories : [];
44
- const pending = stories.filter((story) => story.passes === false);
45
+ const stories = Array.isArray(prd.stories) ? prd.stories : [];
46
+ const storyById = new Map(stories.map((story) => [story.id, story]));
47
+ const pending = stories.filter((story) => story.status === 'pending');
45
48
 
46
49
  if (process.env.TEST_FORWARD) {
47
50
  console.log(`Forwarded env: ${process.env.TEST_FORWARD}`);
@@ -52,14 +55,20 @@ if (pending.length === 0) {
52
55
  process.exit(0);
53
56
  }
54
57
 
55
- pending.sort((a, b) => {
56
- const aPriority = typeof a.priority === 'number' ? a.priority : Number.MAX_SAFE_INTEGER;
57
- const bPriority = typeof b.priority === 'number' ? b.priority : Number.MAX_SAFE_INTEGER;
58
- return aPriority - bPriority;
59
- });
58
+ const story =
59
+ stories.find(
60
+ (candidate) =>
61
+ candidate.status === 'pending' &&
62
+ Array.isArray(candidate.dependsOn) &&
63
+ candidate.dependsOn.every((depId) => storyById.get(depId)?.status === 'done')
64
+ ) || null;
65
+
66
+ if (!story) {
67
+ console.error('No runnable pending stories in test mode.');
68
+ process.exit(1);
69
+ }
60
70
 
61
- const story = pending[0];
62
- story.passes = true;
71
+ story.status = 'done';
63
72
  fs.writeFileSync(prdPath, JSON.stringify(prd, null, 2) + '\n');
64
73
 
65
74
  if (!fs.existsSync(progressPath)) {
@@ -138,7 +147,6 @@ else
138
147
  fi
139
148
 
140
149
  MAX_ITERATIONS=${MAX_ITERATIONS:-25}
141
- SCRIPT_DIR="/home/wile/scripts"
142
150
  WORKSPACE="/home/wile/workspace"
143
151
 
144
152
  if [ "${WILE_MOCK_CLAUDE:-}" = "true" ] && [ "$CODING_AGENT" = "CC" ]; then
@@ -357,24 +365,28 @@ echo "Checking for .wile/prd.json..."
357
365
  if [ ! -f ".wile/prd.json" ]; then
358
366
  echo "ERROR: .wile/prd.json not found!"
359
367
  echo ""
360
- echo "Your repository must have a .wile/prd.json file at the root."
361
- echo "This file contains the user stories for Wile to implement."
368
+ echo "Your repository must have a .wile/prd.json file."
369
+ echo "This file contains the stories for Wile to implement."
362
370
  echo ""
363
371
  echo "Example structure:"
364
372
  echo ' {'
365
- echo ' "userStories": ['
373
+ echo ' "stories": ['
366
374
  echo ' {'
367
- echo ' "id": "US-001",'
375
+ echo ' "id": 1,'
368
376
  echo ' "title": "My feature",'
377
+ echo ' "description": "What this story delivers",'
369
378
  echo ' "acceptanceCriteria": ["..."],'
370
- echo ' "priority": 1,'
371
- echo ' "passes": false'
379
+ echo ' "dependsOn": [],'
380
+ echo ' "status": "pending"'
372
381
  echo ' }'
373
382
  echo ' ]'
374
383
  echo ' }'
375
384
  exit 1
376
385
  fi
377
386
 
387
+ echo "Validating .wile/prd.json..."
388
+ bun "$SCRIPT_DIR/validate-prd.ts" --path .wile/prd.json
389
+
378
390
  # Set up .wile/screenshots directory
379
391
  echo "Setting up .wile directory..."
380
392
  mkdir -p .wile/screenshots
@@ -433,7 +445,7 @@ elif [ $EXIT_CODE -eq 1 ]; then
433
445
  git commit -m "WIP: wile stopped at max iterations ($TIMESTAMP)
434
446
 
435
447
  Partial work committed. Check .wile/progress.txt for details.
436
- Some stories may still have passes=false in .wile/prd.json."
448
+ Some stories may still have status=\"pending\" in .wile/prd.json."
437
449
  git push
438
450
  fi
439
451
  elif [ $EXIT_CODE -eq 2 ]; then
@@ -0,0 +1,159 @@
1
+ #!/usr/bin/env bun
2
+ import { createInterface } from "node:readline";
3
+
4
+ type JsonObject = Record<string, unknown>;
5
+
6
+ const isObject = (value: unknown): value is JsonObject =>
7
+ typeof value === "object" && value !== null;
8
+
9
+ const getString = (obj: JsonObject, key: string): string | undefined => {
10
+ const value = obj[key];
11
+ return typeof value === "string" ? value : undefined;
12
+ };
13
+
14
+ const getObject = (obj: JsonObject, key: string): JsonObject | undefined => {
15
+ const value = obj[key];
16
+ return isObject(value) ? value : undefined;
17
+ };
18
+
19
+ const writeLine = (value: string): void => {
20
+ process.stdout.write(`${value}\n`);
21
+ };
22
+
23
+ const writeTextChunk = (text: string): void => {
24
+ process.stdout.write(text);
25
+ if (!text.endsWith("\n")) {
26
+ process.stdout.write("\n");
27
+ }
28
+ };
29
+
30
+ const extractText = (content: unknown): void => {
31
+ if (!Array.isArray(content)) {
32
+ return;
33
+ }
34
+
35
+ for (const rawChunk of content) {
36
+ if (!isObject(rawChunk)) {
37
+ continue;
38
+ }
39
+
40
+ const chunkType = getString(rawChunk, "type");
41
+ if (chunkType === "text") {
42
+ const text = getString(rawChunk, "text");
43
+ if (text) {
44
+ writeTextChunk(text);
45
+ }
46
+ continue;
47
+ }
48
+
49
+ if (chunkType === "thinking") {
50
+ const thinking = getString(rawChunk, "thinking");
51
+ if (thinking) {
52
+ writeLine(`[thinking] ${thinking}`);
53
+ }
54
+ }
55
+ }
56
+ };
57
+
58
+ const extractToolUse = (content: unknown): void => {
59
+ if (!Array.isArray(content)) {
60
+ return;
61
+ }
62
+
63
+ for (const rawChunk of content) {
64
+ if (!isObject(rawChunk) || getString(rawChunk, "type") !== "tool_use") {
65
+ continue;
66
+ }
67
+
68
+ const toolName = getString(rawChunk, "name") ?? "tool";
69
+ const input = getObject(rawChunk, "input");
70
+ const description = input ? getString(input, "description") ?? "" : "";
71
+ const command = input ? getString(input, "command") ?? "" : "";
72
+
73
+ if (description && command) {
74
+ writeLine(`[tool] ${toolName}: ${description} (${command})`);
75
+ continue;
76
+ }
77
+ if (command) {
78
+ writeLine(`[tool] ${toolName}: ${command}`);
79
+ continue;
80
+ }
81
+ if (description) {
82
+ writeLine(`[tool] ${toolName}: ${description}`);
83
+ continue;
84
+ }
85
+ writeLine(`[tool] ${toolName}`);
86
+ }
87
+ };
88
+
89
+ const rl = createInterface({
90
+ input: process.stdin,
91
+ crlfDelay: Infinity
92
+ });
93
+
94
+ rl.on("line", (line: string) => {
95
+ if (!line.trim()) {
96
+ return;
97
+ }
98
+
99
+ if (process.env.WILE_STREAM_JSON === "true") {
100
+ writeLine(line);
101
+ return;
102
+ }
103
+
104
+ let parsed: unknown;
105
+ try {
106
+ parsed = JSON.parse(line);
107
+ } catch {
108
+ return;
109
+ }
110
+
111
+ if (!isObject(parsed)) {
112
+ return;
113
+ }
114
+
115
+ const payloadType = getString(parsed, "type");
116
+
117
+ if (payloadType === "system" && getString(parsed, "subtype") === "init") {
118
+ const model = getString(parsed, "model") ?? "unknown";
119
+ writeLine(`[system] model: ${model}`);
120
+ return;
121
+ }
122
+
123
+ if (payloadType === "assistant") {
124
+ const message = getObject(parsed, "message");
125
+ if (!message) {
126
+ return;
127
+ }
128
+ const content = message["content"];
129
+ extractToolUse(content);
130
+ extractText(content);
131
+ return;
132
+ }
133
+
134
+ if (payloadType === "user") {
135
+ const message = getObject(parsed, "message");
136
+ const content = message ? message["content"] : undefined;
137
+ if (!Array.isArray(content)) {
138
+ return;
139
+ }
140
+
141
+ for (const rawChunk of content) {
142
+ if (!isObject(rawChunk) || getString(rawChunk, "type") !== "tool_result") {
143
+ continue;
144
+ }
145
+
146
+ const toolUseId = getString(rawChunk, "tool_use_id") ?? "unknown";
147
+ const isError = rawChunk["is_error"] === true;
148
+ writeLine(`[tool-result] ${toolUseId} ${isError ? "error" : "ok"}`);
149
+ }
150
+ return;
151
+ }
152
+
153
+ if (payloadType === "result") {
154
+ const result = getString(parsed, "result");
155
+ if (result) {
156
+ writeLine(result);
157
+ }
158
+ }
159
+ });
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env bun
2
+ import { createInterface } from "node:readline";
3
+
4
+ type JsonObject = Record<string, unknown>;
5
+
6
+ const isObject = (value: unknown): value is JsonObject =>
7
+ typeof value === "object" && value !== null;
8
+
9
+ const getString = (obj: JsonObject, key: string): string | undefined => {
10
+ const value = obj[key];
11
+ return typeof value === "string" ? value : undefined;
12
+ };
13
+
14
+ const getObject = (obj: JsonObject, key: string): JsonObject | undefined => {
15
+ const value = obj[key];
16
+ return isObject(value) ? value : undefined;
17
+ };
18
+
19
+ const writeLine = (value: string): void => {
20
+ process.stdout.write(`${value}\n`);
21
+ };
22
+
23
+ const writeText = (text: string): void => {
24
+ process.stdout.write(text);
25
+ if (!text.endsWith("\n")) {
26
+ process.stdout.write("\n");
27
+ }
28
+ };
29
+
30
+ const formatChanges = (changes: unknown): string => {
31
+ if (!Array.isArray(changes) || changes.length === 0) {
32
+ return "";
33
+ }
34
+
35
+ const parts: string[] = [];
36
+ for (const rawChange of changes) {
37
+ if (!isObject(rawChange)) {
38
+ continue;
39
+ }
40
+ const kind = getString(rawChange, "kind") ?? "update";
41
+ const path = getString(rawChange, "path") ?? "unknown";
42
+ parts.push(`${kind} ${path}`);
43
+ }
44
+
45
+ return parts.join(", ");
46
+ };
47
+
48
+ const rl = createInterface({
49
+ input: process.stdin,
50
+ crlfDelay: Infinity
51
+ });
52
+
53
+ rl.on("line", (line: string) => {
54
+ if (!line.trim()) {
55
+ return;
56
+ }
57
+
58
+ if (process.env.WILE_STREAM_JSON === "true") {
59
+ writeLine(line);
60
+ return;
61
+ }
62
+
63
+ let parsed: unknown;
64
+ try {
65
+ parsed = JSON.parse(line);
66
+ } catch {
67
+ return;
68
+ }
69
+
70
+ if (!isObject(parsed)) {
71
+ return;
72
+ }
73
+
74
+ const eventType = getString(parsed, "type");
75
+
76
+ if (eventType === "thread.started") {
77
+ const threadId = getString(parsed, "thread_id");
78
+ if (threadId) {
79
+ writeLine(`[system] thread: ${threadId}`);
80
+ }
81
+ return;
82
+ }
83
+
84
+ if (eventType === "turn.failed") {
85
+ const error = getObject(parsed, "error");
86
+ const message = error ? getString(error, "message") ?? "Unknown error" : "Unknown error";
87
+ writeLine(`[error] ${message}`);
88
+ return;
89
+ }
90
+
91
+ if (eventType === "error") {
92
+ const message = getString(parsed, "message") ?? "Unknown error";
93
+ writeLine(`[error] ${message}`);
94
+ return;
95
+ }
96
+
97
+ const item = getObject(parsed, "item");
98
+ if (!item) {
99
+ return;
100
+ }
101
+
102
+ const itemType = getString(item, "type");
103
+
104
+ if (eventType === "item.completed" && itemType === "agent_message") {
105
+ const text = getString(item, "text");
106
+ if (text) {
107
+ writeText(text);
108
+ }
109
+ return;
110
+ }
111
+
112
+ if (eventType === "item.completed" && itemType === "reasoning") {
113
+ writeLine(`[thinking] ${getString(item, "text") ?? ""}`);
114
+ return;
115
+ }
116
+
117
+ if (itemType === "command_execution") {
118
+ const command = getString(item, "command") ?? "command";
119
+ if (eventType === "item.started") {
120
+ writeLine(`[tool] shell: ${command}`);
121
+ return;
122
+ }
123
+
124
+ if (eventType === "item.completed") {
125
+ const status = getString(item, "status") ?? "completed";
126
+ const exitCodeValue = item["exit_code"];
127
+ const exitCode =
128
+ typeof exitCodeValue === "number" || typeof exitCodeValue === "string"
129
+ ? ` (exit ${String(exitCodeValue)})`
130
+ : "";
131
+ writeLine(`[tool-result] shell ${status}${exitCode}`);
132
+ return;
133
+ }
134
+ }
135
+
136
+ if (eventType === "item.completed" && itemType === "file_change") {
137
+ const detail = formatChanges(item["changes"]);
138
+ writeLine(`[tool] file_change${detail ? `: ${detail}` : ""}`);
139
+ return;
140
+ }
141
+
142
+ if (itemType === "mcp_tool_call") {
143
+ const server = getString(item, "server") ?? "mcp";
144
+ const tool = getString(item, "tool") ?? "tool";
145
+
146
+ if (eventType === "item.started") {
147
+ writeLine(`[tool] mcp ${server}/${tool}`);
148
+ return;
149
+ }
150
+
151
+ if (eventType === "item.completed") {
152
+ const status = getString(item, "status") ?? "completed";
153
+ writeLine(`[tool-result] mcp ${server}/${tool} ${status}`);
154
+ return;
155
+ }
156
+ }
157
+
158
+ if (itemType === "web_search" && eventType === "item.started") {
159
+ const query = getString(item, "query") ?? "";
160
+ writeLine(`[tool] web_search${query ? `: ${query}` : ""}`);
161
+ return;
162
+ }
163
+
164
+ if (eventType === "item.completed" && itemType === "error") {
165
+ writeLine(`[error] ${getString(item, "message") ?? "Unknown error"}`);
166
+ }
167
+ });
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env bun
2
+ import { createInterface } from "node:readline";
3
+
4
+ type JsonObject = Record<string, unknown>;
5
+
6
+ const isObject = (value: unknown): value is JsonObject =>
7
+ typeof value === "object" && value !== null;
8
+
9
+ const getString = (obj: JsonObject, key: string): string | undefined => {
10
+ const value = obj[key];
11
+ return typeof value === "string" ? value : undefined;
12
+ };
13
+
14
+ const getObject = (obj: JsonObject, key: string): JsonObject | undefined => {
15
+ const value = obj[key];
16
+ return isObject(value) ? value : undefined;
17
+ };
18
+
19
+ const writeLine = (value: string): void => {
20
+ process.stdout.write(`${value}\n`);
21
+ };
22
+
23
+ const writeContent = (content: string): void => {
24
+ process.stdout.write(content);
25
+ if (!content.endsWith("\n")) {
26
+ process.stdout.write("\n");
27
+ }
28
+ };
29
+
30
+ const rl = createInterface({
31
+ input: process.stdin,
32
+ crlfDelay: Infinity
33
+ });
34
+
35
+ rl.on("line", (line: string) => {
36
+ if (!line.trim()) {
37
+ return;
38
+ }
39
+
40
+ let parsed: unknown;
41
+ try {
42
+ parsed = JSON.parse(line);
43
+ } catch {
44
+ return;
45
+ }
46
+
47
+ if (!isObject(parsed)) {
48
+ return;
49
+ }
50
+
51
+ const payloadType = getString(parsed, "type");
52
+
53
+ if (payloadType === "init") {
54
+ const model = getString(parsed, "model") ?? "unknown";
55
+ writeLine(`[system] model: ${model}`);
56
+ return;
57
+ }
58
+
59
+ if (payloadType === "message") {
60
+ const role = getString(parsed, "role");
61
+ const content = getString(parsed, "content");
62
+ if (role === "assistant" && content) {
63
+ writeContent(content);
64
+ }
65
+ return;
66
+ }
67
+
68
+ if (payloadType === "tool_use") {
69
+ const tool = getString(parsed, "tool_name") ?? "tool";
70
+ const params = getObject(parsed, "parameters");
71
+ const detail = params && Object.keys(params).length > 0 ? `: ${JSON.stringify(params)}` : "";
72
+ writeLine(`[tool] ${tool}${detail}`);
73
+ return;
74
+ }
75
+
76
+ if (payloadType === "tool_result") {
77
+ const toolId = getString(parsed, "tool_id") ?? "unknown";
78
+ const status = getString(parsed, "status") ?? "unknown";
79
+ writeLine(`[tool-result] ${toolId} ${status}`);
80
+ return;
81
+ }
82
+
83
+ if (payloadType === "error") {
84
+ const message =
85
+ getString(parsed, "message") ??
86
+ (() => {
87
+ const error = getObject(parsed, "error");
88
+ if (!error) {
89
+ return undefined;
90
+ }
91
+ return getString(error, "message") ?? getString(error, "error");
92
+ })() ??
93
+ "Unknown error";
94
+
95
+ writeLine(`[error] ${message}`);
96
+ }
97
+ });