@stackmemoryai/stackmemory 1.0.1 → 1.2.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.
Files changed (31) hide show
  1. package/dist/src/cli/commands/audit.js +134 -0
  2. package/dist/src/cli/commands/bench.js +252 -0
  3. package/dist/src/cli/commands/dashboard.js +2 -1
  4. package/dist/src/cli/commands/stats.js +118 -0
  5. package/dist/src/cli/index.js +6 -0
  6. package/dist/src/core/config/feature-flags.js +7 -1
  7. package/dist/src/core/context/enhanced-rehydration.js +24 -5
  8. package/dist/src/core/extensions/cerebras-adapter.js +28 -0
  9. package/dist/src/core/extensions/deepinfra-adapter.js +32 -0
  10. package/dist/src/core/extensions/provider-adapter.js +33 -240
  11. package/dist/src/core/models/complexity-scorer.js +154 -0
  12. package/dist/src/core/models/model-router.js +230 -36
  13. package/dist/src/core/models/provider-pricing.js +63 -0
  14. package/dist/src/core/models/sensitive-guard.js +112 -0
  15. package/dist/src/core/monitoring/feedback-loops.js +88 -0
  16. package/dist/src/hooks/schemas.js +12 -1
  17. package/dist/src/integrations/anthropic/batch-client.js +256 -0
  18. package/dist/src/integrations/anthropic/client.js +87 -72
  19. package/dist/src/integrations/claude-code/subagent-client.js +133 -12
  20. package/dist/src/integrations/graphiti/client.js +16 -4
  21. package/dist/src/integrations/mcp/handlers/index.js +25 -1
  22. package/dist/src/integrations/mcp/handlers/provider-handlers.js +227 -0
  23. package/dist/src/integrations/mcp/server.js +207 -1
  24. package/dist/src/integrations/mcp/tool-definitions.js +38 -1
  25. package/dist/src/orchestrators/multimodal/baselines.js +128 -0
  26. package/dist/src/orchestrators/multimodal/constants.js +9 -1
  27. package/dist/src/orchestrators/multimodal/harness.js +86 -6
  28. package/dist/src/orchestrators/multimodal/providers.js +113 -2
  29. package/dist/src/skills/recursive-agent-orchestrator.js +15 -10
  30. package/dist/src/utils/fuzzy-edit.js +162 -0
  31. package/package.json +5 -1
@@ -0,0 +1,256 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
6
+ import { join } from "path";
7
+ import { homedir } from "os";
8
+ import { logger } from "../../core/monitoring/logger.js";
9
+ const BATCH_JOBS_PATH = join(homedir(), ".stackmemory", "batch-jobs.json");
10
+ const DEFAULT_POLL_INTERVAL_MS = 3e4;
11
+ const DEFAULT_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
12
+ class AnthropicBatchClient {
13
+ apiKey;
14
+ baseUrl;
15
+ mockMode;
16
+ constructor(config) {
17
+ this.apiKey = config?.apiKey !== void 0 ? config.apiKey : process.env["ANTHROPIC_API_KEY"] || "";
18
+ this.baseUrl = config?.baseUrl || "https://api.anthropic.com";
19
+ this.mockMode = config?.mockMode ?? !this.apiKey;
20
+ if (this.mockMode) {
21
+ logger.warn("AnthropicBatchClient: no API key, using mock mode");
22
+ }
23
+ }
24
+ /**
25
+ * Submit a batch of requests
26
+ */
27
+ async submit(requests, description) {
28
+ if (this.mockMode) {
29
+ const batchId = `batch_mock_${Date.now()}`;
30
+ this.persistJob({
31
+ batchId,
32
+ status: "ended",
33
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
34
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
35
+ requestCount: requests.length,
36
+ description
37
+ });
38
+ return batchId;
39
+ }
40
+ const response = await fetch(`${this.baseUrl}/v1/messages/batches`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ "x-api-key": this.apiKey,
45
+ "anthropic-version": "2023-06-01"
46
+ },
47
+ body: JSON.stringify({ requests })
48
+ });
49
+ if (!response.ok) {
50
+ const errText = await response.text();
51
+ throw new Error(`Batch submit failed: ${response.status} ${errText}`);
52
+ }
53
+ const job = await response.json();
54
+ this.persistJob({
55
+ batchId: job.id,
56
+ status: job.processing_status,
57
+ createdAt: job.created_at,
58
+ requestCount: requests.length,
59
+ description
60
+ });
61
+ logger.info("Batch submitted", { batchId: job.id, count: requests.length });
62
+ return job.id;
63
+ }
64
+ /**
65
+ * Poll batch job status
66
+ */
67
+ async poll(batchId) {
68
+ if (this.mockMode) {
69
+ return this.mockBatchJob(batchId);
70
+ }
71
+ const response = await fetch(
72
+ `${this.baseUrl}/v1/messages/batches/${batchId}`,
73
+ {
74
+ headers: {
75
+ "x-api-key": this.apiKey,
76
+ "anthropic-version": "2023-06-01"
77
+ }
78
+ }
79
+ );
80
+ if (!response.ok) {
81
+ const errText = await response.text();
82
+ throw new Error(`Batch poll failed: ${response.status} ${errText}`);
83
+ }
84
+ const job = await response.json();
85
+ this.updateJobStatus(batchId, job.processing_status, job.ended_at);
86
+ return job;
87
+ }
88
+ /**
89
+ * Retrieve batch results
90
+ */
91
+ async retrieve(batchId) {
92
+ if (this.mockMode) {
93
+ return this.mockBatchResults(batchId);
94
+ }
95
+ const job = await this.poll(batchId);
96
+ if (job.processing_status !== "ended") {
97
+ throw new Error(
98
+ `Batch ${batchId} not finished: ${job.processing_status}`
99
+ );
100
+ }
101
+ if (!job.results_url) {
102
+ throw new Error(`Batch ${batchId} has no results URL`);
103
+ }
104
+ const response = await fetch(job.results_url, {
105
+ headers: {
106
+ "x-api-key": this.apiKey,
107
+ "anthropic-version": "2023-06-01"
108
+ }
109
+ });
110
+ if (!response.ok) {
111
+ throw new Error(`Batch retrieve failed: ${response.status}`);
112
+ }
113
+ const text = await response.text();
114
+ return text.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
115
+ }
116
+ /**
117
+ * Poll until batch completes or times out
118
+ */
119
+ async waitForCompletion(batchId, timeoutMs = DEFAULT_TIMEOUT_MS, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS) {
120
+ const deadline = Date.now() + timeoutMs;
121
+ while (Date.now() < deadline) {
122
+ const job = await this.poll(batchId);
123
+ if (job.processing_status === "ended") {
124
+ return job;
125
+ }
126
+ const remaining = deadline - Date.now();
127
+ const waitTime = Math.min(pollIntervalMs, remaining);
128
+ if (waitTime <= 0) break;
129
+ await new Promise((resolve) => {
130
+ const timer = setTimeout(() => resolve(), waitTime);
131
+ if (typeof timer === "object" && "unref" in timer) {
132
+ timer.unref();
133
+ }
134
+ });
135
+ }
136
+ throw new Error(`Batch ${batchId} timed out after ${timeoutMs}ms`);
137
+ }
138
+ /**
139
+ * Submit and wait for results (convenience)
140
+ */
141
+ async submitAndWait(requests, timeoutMs = DEFAULT_TIMEOUT_MS, description) {
142
+ const batchId = await this.submit(requests, description);
143
+ await this.waitForCompletion(batchId, timeoutMs);
144
+ return this.retrieve(batchId);
145
+ }
146
+ /**
147
+ * Cancel a batch job
148
+ */
149
+ async cancel(batchId) {
150
+ if (this.mockMode) {
151
+ return this.mockBatchJob(batchId, "canceling");
152
+ }
153
+ const response = await fetch(
154
+ `${this.baseUrl}/v1/messages/batches/${batchId}/cancel`,
155
+ {
156
+ method: "POST",
157
+ headers: {
158
+ "x-api-key": this.apiKey,
159
+ "anthropic-version": "2023-06-01"
160
+ }
161
+ }
162
+ );
163
+ if (!response.ok) {
164
+ throw new Error(`Batch cancel failed: ${response.status}`);
165
+ }
166
+ const job = await response.json();
167
+ this.updateJobStatus(batchId, job.processing_status);
168
+ return job;
169
+ }
170
+ /**
171
+ * List stored batch jobs
172
+ */
173
+ listJobs() {
174
+ return this.loadStoredJobs();
175
+ }
176
+ // ── Persistence ───────────────────────────────────────────────────────
177
+ persistJob(job) {
178
+ const jobs = this.loadStoredJobs();
179
+ const existing = jobs.findIndex((j) => j.batchId === job.batchId);
180
+ if (existing >= 0) {
181
+ jobs[existing] = job;
182
+ } else {
183
+ jobs.push(job);
184
+ }
185
+ const trimmed = jobs.slice(-50);
186
+ this.saveStoredJobs(trimmed);
187
+ }
188
+ updateJobStatus(batchId, status, endedAt) {
189
+ const jobs = this.loadStoredJobs();
190
+ const job = jobs.find((j) => j.batchId === batchId);
191
+ if (job) {
192
+ job.status = status;
193
+ if (endedAt) job.endedAt = endedAt;
194
+ this.saveStoredJobs(jobs);
195
+ }
196
+ }
197
+ loadStoredJobs() {
198
+ try {
199
+ if (existsSync(BATCH_JOBS_PATH)) {
200
+ return JSON.parse(readFileSync(BATCH_JOBS_PATH, "utf8"));
201
+ }
202
+ } catch {
203
+ }
204
+ return [];
205
+ }
206
+ saveStoredJobs(jobs) {
207
+ try {
208
+ const dir = join(homedir(), ".stackmemory");
209
+ if (!existsSync(dir)) {
210
+ mkdirSync(dir, { recursive: true });
211
+ }
212
+ writeFileSync(BATCH_JOBS_PATH, JSON.stringify(jobs, null, 2));
213
+ } catch {
214
+ }
215
+ }
216
+ // ── Mock mode ─────────────────────────────────────────────────────────
217
+ mockBatchJob(batchId, status = "ended") {
218
+ return {
219
+ id: batchId,
220
+ type: "message_batch",
221
+ processing_status: status,
222
+ request_counts: {
223
+ processing: 0,
224
+ succeeded: 1,
225
+ errored: 0,
226
+ canceled: 0,
227
+ expired: 0
228
+ },
229
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
230
+ ended_at: status === "ended" ? (/* @__PURE__ */ new Date()).toISOString() : void 0
231
+ };
232
+ }
233
+ mockBatchResults(batchId) {
234
+ const jobs = this.loadStoredJobs();
235
+ const job = jobs.find((j) => j.batchId === batchId);
236
+ const count = job?.requestCount || 1;
237
+ return Array.from({ length: count }, (_, i) => ({
238
+ custom_id: `req_${i}`,
239
+ result: {
240
+ type: "succeeded",
241
+ message: {
242
+ id: `msg_mock_${i}`,
243
+ content: [
244
+ { type: "text", text: `Mock batch response for request ${i}` }
245
+ ],
246
+ model: "claude-sonnet-4-5-20250929",
247
+ stop_reason: "end_turn",
248
+ usage: { input_tokens: 100, output_tokens: 50 }
249
+ }
250
+ }
251
+ }));
252
+ }
253
+ }
254
+ export {
255
+ AnthropicBatchClient
256
+ };
@@ -3,6 +3,7 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { logger } from "../../core/monitoring/logger.js";
6
+ import { STRUCTURED_RESPONSE_SUFFIX } from "../../orchestrators/multimodal/constants.js";
6
7
  class AnthropicClient {
7
8
  apiKey;
8
9
  baseURL;
@@ -31,17 +32,21 @@ class AnthropicClient {
31
32
  * Send completion request to Claude
32
33
  */
33
34
  async complete(request) {
35
+ const enhancedRequest = {
36
+ ...request,
37
+ systemPrompt: request.systemPrompt + STRUCTURED_RESPONSE_SUFFIX
38
+ };
34
39
  await this.checkRateLimit();
35
40
  logger.debug("Sending completion request", {
36
- model: request.model,
37
- promptLength: request.prompt.length,
38
- maxTokens: request.maxTokens
41
+ model: enhancedRequest.model,
42
+ promptLength: enhancedRequest.prompt.length,
43
+ maxTokens: enhancedRequest.maxTokens
39
44
  });
40
45
  if (!this.apiKey) {
41
- return this.mockComplete(request);
46
+ return this.mockComplete(enhancedRequest);
42
47
  }
43
48
  try {
44
- const response = await this.sendRequest(request);
49
+ const response = await this.sendRequest(enhancedRequest);
45
50
  return response.content;
46
51
  } catch (error) {
47
52
  logger.error("Anthropic API error", { error });
@@ -57,7 +62,9 @@ class AnthropicClient {
57
62
  } catch (error) {
58
63
  if (attempt < this.maxRetries) {
59
64
  const delay = Math.pow(2, attempt) * 1e3;
60
- logger.warn(`Retrying after ${delay}ms (attempt ${attempt}/${this.maxRetries})`);
65
+ logger.warn(
66
+ `Retrying after ${delay}ms (attempt ${attempt}/${this.maxRetries})`
67
+ );
61
68
  await this.delay(delay);
62
69
  return this.sendRequest(request, attempt + 1);
63
70
  }
@@ -85,44 +92,48 @@ class AnthropicClient {
85
92
  * Mock response generators
86
93
  */
87
94
  mockPlanningResponse(prompt) {
88
- return JSON.stringify({
89
- plan: {
90
- type: "sequential",
91
- tasks: [
92
- {
93
- id: "task-1",
94
- description: "Analyze requirements",
95
- agent: "context",
96
- dependencies: []
97
- },
98
- {
99
- id: "task-2",
100
- type: "parallel",
101
- description: "Implementation phase",
102
- children: [
103
- {
104
- id: "task-2a",
105
- description: "Write core logic",
106
- agent: "code",
107
- dependencies: ["task-1"]
108
- },
109
- {
110
- id: "task-2b",
111
- description: "Write tests",
112
- agent: "testing",
113
- dependencies: ["task-1"]
114
- }
115
- ]
116
- },
117
- {
118
- id: "task-3",
119
- description: "Review and improve",
120
- agent: "review",
121
- dependencies: ["task-2"]
122
- }
123
- ]
124
- }
125
- }, null, 2);
95
+ return JSON.stringify(
96
+ {
97
+ plan: {
98
+ type: "sequential",
99
+ tasks: [
100
+ {
101
+ id: "task-1",
102
+ description: "Analyze requirements",
103
+ agent: "context",
104
+ dependencies: []
105
+ },
106
+ {
107
+ id: "task-2",
108
+ type: "parallel",
109
+ description: "Implementation phase",
110
+ children: [
111
+ {
112
+ id: "task-2a",
113
+ description: "Write core logic",
114
+ agent: "code",
115
+ dependencies: ["task-1"]
116
+ },
117
+ {
118
+ id: "task-2b",
119
+ description: "Write tests",
120
+ agent: "testing",
121
+ dependencies: ["task-1"]
122
+ }
123
+ ]
124
+ },
125
+ {
126
+ id: "task-3",
127
+ description: "Review and improve",
128
+ agent: "review",
129
+ dependencies: ["task-2"]
130
+ }
131
+ ]
132
+ }
133
+ },
134
+ null,
135
+ 2
136
+ );
126
137
  }
127
138
  mockCodeResponse(prompt) {
128
139
  return `
@@ -166,34 +177,38 @@ describe('validateInput', () => {
166
177
  `.trim();
167
178
  }
168
179
  mockReviewResponse(prompt) {
169
- return JSON.stringify({
170
- quality: 0.75,
171
- issues: [
172
- "Missing error handling in processTask function",
173
- "No input validation before processing",
174
- "Tests could cover more edge cases"
175
- ],
176
- suggestions: [
177
- "Add try-catch block in processTask",
178
- "Validate input length and type",
179
- "Add tests for special characters and long inputs",
180
- "Consider adding performance tests"
181
- ],
182
- improvements: [
183
- {
184
- file: "implementation.ts",
185
- line: 3,
186
- suggestion: "Add input validation",
187
- priority: "high"
188
- },
189
- {
190
- file: "tests.ts",
191
- line: 15,
192
- suggestion: "Add edge case tests",
193
- priority: "medium"
194
- }
195
- ]
196
- }, null, 2);
180
+ return JSON.stringify(
181
+ {
182
+ quality: 0.75,
183
+ issues: [
184
+ "Missing error handling in processTask function",
185
+ "No input validation before processing",
186
+ "Tests could cover more edge cases"
187
+ ],
188
+ suggestions: [
189
+ "Add try-catch block in processTask",
190
+ "Validate input length and type",
191
+ "Add tests for special characters and long inputs",
192
+ "Consider adding performance tests"
193
+ ],
194
+ improvements: [
195
+ {
196
+ file: "implementation.ts",
197
+ line: 3,
198
+ suggestion: "Add input validation",
199
+ priority: "high"
200
+ },
201
+ {
202
+ file: "tests.ts",
203
+ line: 15,
204
+ suggestion: "Add edge case tests",
205
+ priority: "medium"
206
+ }
207
+ ]
208
+ },
209
+ null,
210
+ 2
211
+ );
197
212
  }
198
213
  /**
199
214
  * Create mock response object
@@ -3,11 +3,21 @@ import { dirname as __pathDirname } from 'path';
3
3
  const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { logger } from "../../core/monitoring/logger.js";
6
+ import { STRUCTURED_RESPONSE_SUFFIX } from "../../orchestrators/multimodal/constants.js";
6
7
  import { exec } from "child_process";
7
8
  import { promisify } from "util";
8
9
  import * as fs from "fs";
9
10
  import * as path from "path";
10
11
  import * as os from "os";
12
+ import { isFeatureEnabled } from "../../core/config/feature-flags.js";
13
+ import {
14
+ getOptimalProvider
15
+ } from "../../core/models/model-router.js";
16
+ import { scoreComplexity } from "../../core/models/complexity-scorer.js";
17
+ import {
18
+ createProvider
19
+ } from "../../core/extensions/provider-adapter.js";
20
+ import { AnthropicBatchClient } from "../anthropic/batch-client.js";
11
21
  const execAsync = promisify(exec);
12
22
  class ClaudeCodeSubagentClient {
13
23
  tempDir;
@@ -25,8 +35,9 @@ class ClaudeCodeSubagentClient {
25
35
  });
26
36
  }
27
37
  /**
28
- * Execute a subagent task using Claude Code's Task tool
29
- * This will spawn a new Claude instance with specific instructions
38
+ * Execute a subagent task.
39
+ * When multiProvider is enabled, routes cheap tasks to external providers.
40
+ * Falls back to Claude Code CLI path when disabled or for complex tasks.
30
41
  */
31
42
  async executeSubagent(request) {
32
43
  const startTime = Date.now();
@@ -39,6 +50,107 @@ class ClaudeCodeSubagentClient {
39
50
  if (this.mockMode) {
40
51
  return this.getMockResponse(request, startTime, subagentId);
41
52
  }
53
+ if (isFeatureEnabled("multiProvider")) {
54
+ const taskType = request.type;
55
+ const complexity = scoreComplexity(request.task, request.context);
56
+ const optimal = getOptimalProvider(taskType, void 0, {
57
+ task: request.task,
58
+ context: request.context
59
+ });
60
+ logger.debug("Complexity-based routing", {
61
+ subagentId,
62
+ taskType,
63
+ complexity: complexity.tier,
64
+ score: complexity.score,
65
+ signals: complexity.signals,
66
+ routed: optimal.provider
67
+ });
68
+ if (optimal.provider !== "anthropic" && optimal.provider !== "anthropic-batch") {
69
+ return this.executeDirectAPI(request, optimal, startTime, subagentId);
70
+ }
71
+ if (optimal.provider === "anthropic-batch") {
72
+ return this.executeBatch(request, startTime, subagentId);
73
+ }
74
+ }
75
+ return this.executeSubagentViaCLI(request, startTime, subagentId);
76
+ }
77
+ /**
78
+ * Execute via external provider (Cerebras, DeepInfra, etc.) using OpenAI-compatible API
79
+ */
80
+ async executeDirectAPI(request, optimal, startTime, subagentId) {
81
+ try {
82
+ const apiKey = process.env[optimal.apiKeyEnv] || "";
83
+ if (!apiKey) {
84
+ logger.warn(
85
+ `No API key for ${optimal.provider}, falling back to Claude`
86
+ );
87
+ return this.executeSubagentViaCLI(request, startTime, subagentId);
88
+ }
89
+ const adapter = createProvider(optimal.provider, {
90
+ apiKey,
91
+ baseUrl: optimal.baseUrl
92
+ });
93
+ const prompt = this.buildSubagentPrompt(request);
94
+ const result = await adapter.complete(
95
+ [{ role: "user", content: prompt }],
96
+ { model: optimal.model, maxTokens: 4096 }
97
+ );
98
+ const text = result.content.filter((c) => c.type === "text").map((c) => c.text).join("");
99
+ let parsed;
100
+ try {
101
+ parsed = JSON.parse(text);
102
+ } catch {
103
+ parsed = { rawOutput: text };
104
+ }
105
+ return {
106
+ success: true,
107
+ result: parsed,
108
+ output: text,
109
+ duration: Date.now() - startTime,
110
+ subagentType: request.type,
111
+ tokens: result.usage.inputTokens + result.usage.outputTokens
112
+ };
113
+ } catch (error) {
114
+ logger.warn(`Direct API failed for ${optimal.provider}, falling back`, {
115
+ error: error.message
116
+ });
117
+ return this.executeSubagentViaCLI(request, startTime, subagentId);
118
+ }
119
+ }
120
+ /**
121
+ * Execute via Anthropic Batch API (async, 50% discount)
122
+ */
123
+ async executeBatch(request, startTime, subagentId) {
124
+ try {
125
+ const batchClient = new AnthropicBatchClient();
126
+ const prompt = this.buildSubagentPrompt(request);
127
+ const batchReq = {
128
+ custom_id: subagentId,
129
+ params: {
130
+ model: "claude-sonnet-4-5-20250929",
131
+ max_tokens: 4096,
132
+ messages: [{ role: "user", content: prompt }]
133
+ }
134
+ };
135
+ const batchId = await batchClient.submit([batchReq], request.type);
136
+ return {
137
+ success: true,
138
+ result: { batchId, status: "submitted", custom_id: subagentId },
139
+ output: `Batch submitted: ${batchId}`,
140
+ duration: Date.now() - startTime,
141
+ subagentType: request.type
142
+ };
143
+ } catch (error) {
144
+ logger.warn("Batch submit failed, falling back to sync", {
145
+ error: error.message
146
+ });
147
+ return this.executeSubagentViaCLI(request, startTime, subagentId);
148
+ }
149
+ }
150
+ /**
151
+ * Original CLI-based subagent execution (unchanged behavior)
152
+ */
153
+ async executeSubagentViaCLI(request, startTime, subagentId) {
42
154
  try {
43
155
  const prompt = this.buildSubagentPrompt(request);
44
156
  const contextFile = path.join(this.tempDir, `${subagentId}-context.json`);
@@ -47,14 +159,19 @@ class ClaudeCodeSubagentClient {
47
159
  JSON.stringify(request.context, null, 2)
48
160
  );
49
161
  const resultFile = path.join(this.tempDir, `${subagentId}-result.json`);
50
- const taskCommand = this.buildTaskCommand(request, prompt, contextFile, resultFile);
162
+ const taskCommand = this.buildTaskCommand(
163
+ request,
164
+ prompt,
165
+ contextFile,
166
+ resultFile
167
+ );
51
168
  const result = await this.executeTaskTool(taskCommand, request.timeout);
52
169
  let subagentResult = {};
53
170
  if (fs.existsSync(resultFile)) {
54
171
  const resultContent = await fs.promises.readFile(resultFile, "utf-8");
55
172
  try {
56
173
  subagentResult = JSON.parse(resultContent);
57
- } catch (e) {
174
+ } catch {
58
175
  subagentResult = { rawOutput: resultContent };
59
176
  }
60
177
  }
@@ -68,7 +185,10 @@ class ClaudeCodeSubagentClient {
68
185
  tokens: this.estimateTokens(prompt + JSON.stringify(subagentResult))
69
186
  };
70
187
  } catch (error) {
71
- logger.error(`Subagent execution failed: ${request.type}`, { error, subagentId });
188
+ logger.error(`Subagent CLI execution failed: ${request.type}`, {
189
+ error,
190
+ subagentId
191
+ });
72
192
  return {
73
193
  success: false,
74
194
  result: null,
@@ -224,7 +344,7 @@ class ClaudeCodeSubagentClient {
224
344
 
225
345
  Output the release plan and commands.`
226
346
  };
227
- return request.systemPrompt || prompts[request.type] || prompts.planning;
347
+ return (request.systemPrompt || prompts[request.type] || prompts.planning) + STRUCTURED_RESPONSE_SUFFIX;
228
348
  }
229
349
  /**
230
350
  * Build Task tool command
@@ -292,7 +412,9 @@ echo '{"status": "completed", "type": "${request.type}"}' > "${resultFile}"
292
412
  * Get mock response for testing
293
413
  */
294
414
  async getMockResponse(request, startTime, subagentId) {
295
- await new Promise((resolve) => setTimeout(resolve, Math.random() * 20 + 10));
415
+ await new Promise(
416
+ (resolve) => setTimeout(resolve, Math.random() * 20 + 10)
417
+ );
296
418
  const mockResponses = {
297
419
  planning: {
298
420
  tasks: [
@@ -357,10 +479,7 @@ function greetUser(name: string): string {
357
479
  }
358
480
  return \`Hello, \${name}!\`;
359
481
  }`,
360
- changes_made: [
361
- "Added JSDoc documentation",
362
- "Improved error message"
363
- ]
482
+ changes_made: ["Added JSDoc documentation", "Improved error message"]
364
483
  },
365
484
  context: {
366
485
  relevant_files: ["src/greet.ts", "test/greet.test.ts"],
@@ -415,7 +534,9 @@ function greetUser(name: string): string {
415
534
  */
416
535
  async mockTaskToolExecution(request) {
417
536
  const startTime = Date.now();
418
- await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 2e3));
537
+ await new Promise(
538
+ (resolve) => setTimeout(resolve, 1e3 + Math.random() * 2e3)
539
+ );
419
540
  const mockResponses = {
420
541
  planning: {
421
542
  tasks: [