solo-cto-agent 1.3.0 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -121,6 +121,8 @@ non-interactive verify in CI, and tear it all down with one command.
121
121
 
122
122
  ## Unreleased
123
123
 
124
+ * chore: vscode extension packaging verified (icon, license, gitignore)
125
+
124
126
  * fix: routine.js readTier import from personalization (not core)
125
127
 
126
128
  * fix: resolve 2 hanging tests + add vitest timeout config
@@ -123,8 +123,76 @@ function buildRoutineSchedules() {
123
123
  }
124
124
 
125
125
  // ============================================================================
126
- // CLAUDE MANAGED AGENTS
126
+ // CLAUDE MANAGED AGENTS (v2 — real API, April 2026)
127
127
  // ============================================================================
128
+ //
129
+ // Flow: create agent → create environment → create session → send event → poll
130
+ // Docs: https://platform.claude.com/docs/en/managed-agents/overview
131
+ // Beta header: managed-agents-2026-04-01
132
+ // Endpoints: /v1/agents, /v1/environments, /v1/sessions, /v1/sessions/{id}/events
133
+ // ============================================================================
134
+
135
+ /**
136
+ * Helper: make an HTTPS JSON request to the Anthropic API.
137
+ * Returns { statusCode, body } where body is parsed JSON.
138
+ */
139
+ function _apiRequest(method, urlPath, apiKey, payload) {
140
+ return new Promise((resolve) => {
141
+ const body = payload ? JSON.stringify(payload) : undefined;
142
+ const req = https.request({
143
+ hostname: C.API_HOSTS.anthropic,
144
+ path: urlPath,
145
+ method,
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ "x-api-key": apiKey,
149
+ "anthropic-version": C.ANTHROPIC_API_VERSION,
150
+ "anthropic-beta": C.BETA_HEADERS.managedAgents,
151
+ },
152
+ }, (res) => {
153
+ let data = "";
154
+ res.on("data", (chunk) => (data += chunk));
155
+ res.on("end", () => {
156
+ try {
157
+ resolve({ statusCode: res.statusCode, body: JSON.parse(data) });
158
+ } catch {
159
+ resolve({ statusCode: res.statusCode, body: { raw: data } });
160
+ }
161
+ });
162
+ });
163
+ req.on("error", (e) => resolve({ statusCode: 0, body: { error: e.message } }));
164
+ req.setTimeout(C.TIMEOUTS.managedAgent, () => {
165
+ req.destroy(new Error("request timeout"));
166
+ });
167
+ if (body) req.write(body);
168
+ req.end();
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Poll session until status is "idle" (agent finished) or timeout.
174
+ * Returns the full session object on success, null on timeout/error.
175
+ */
176
+ async function _pollSession(sessionId, apiKey, timeoutMs) {
177
+ const deadline = Date.now() + timeoutMs;
178
+ const pollInterval = 3000; // 3s
179
+
180
+ while (Date.now() < deadline) {
181
+ const { statusCode, body } = await _apiRequest("GET", `/v1/sessions/${sessionId}`, apiKey);
182
+ if (statusCode !== 200) {
183
+ logWarn(`Poll failed (${statusCode}): ${JSON.stringify(body).slice(0, 200)}`);
184
+ return null;
185
+ }
186
+ if (body.status === "idle") return body;
187
+ if (body.status === "error" || body.status === "failed") {
188
+ logError(`Session entered error state: ${body.status}`);
189
+ return null;
190
+ }
191
+ await new Promise((r) => setTimeout(r, pollInterval));
192
+ }
193
+ logError(`Session poll timed out after ${timeoutMs / 1000}s`);
194
+ return null;
195
+ }
128
196
 
129
197
  async function managedAgentReview(options = {}) {
130
198
  const tier = readTier();
@@ -193,92 +261,142 @@ ${errorPatterns}
193
261
  [SUMMARY] ...
194
262
  [NEXT ACTION] ...`;
195
263
 
264
+ const timeoutMs = CONFIG.managedAgents.sessionTimeoutMs || C.TIMEOUTS.managedAgent;
265
+
196
266
  if (options.dryRun) {
197
267
  logSection("Managed Agent Review — DRY RUN");
198
268
  logInfo(`Model: ${model}`);
199
269
  logInfo(`Diff size: ${(Buffer.byteLength(diff, "utf8") / 1024).toFixed(0)}KB`);
200
- logInfo(`Timeout: ${CONFIG.managedAgents.sessionTimeoutMs / 1000}s`);
201
- logInfo(`Beta header: ${CONFIG.managedAgents.betaHeader}`);
270
+ logInfo(`Timeout: ${timeoutMs / 1000}s`);
271
+ logInfo(`Beta header: ${C.BETA_HEADERS.managedAgents}`);
202
272
  logInfo(`Cost: standard token rates + $0.08/session-hour`);
273
+ logInfo("API flow: create agent → create env → create session → send event → poll");
203
274
  return null;
204
275
  }
205
276
 
206
277
  logSection("Managed Agent Deep Review");
207
- logInfo(`Model: ${model} | Timeout: ${CONFIG.managedAgents.sessionTimeoutMs / 1000}s`);
278
+ logInfo(`Model: ${model} | Timeout: ${timeoutMs / 1000}s`);
208
279
  logInfo("Cost: standard token rates + $0.08/session-hour active runtime");
209
280
 
210
281
  const startTime = Date.now();
211
282
 
212
- return new Promise((resolve, reject) => {
213
- const body = JSON.stringify({
214
- model,
283
+ // ── Step 1: Create or reuse agent ──
284
+ let agentId = options.agentId || CONFIG.managedAgents.agentId;
285
+ if (!agentId) {
286
+ logInfo("Creating agent...");
287
+ const agentRes = await _apiRequest("POST", "/v1/agents", apiKey, {
288
+ name: "solo-cto-deep-reviewer",
289
+ description: "CTO-level deep code reviewer for solo-cto-agent CLI.",
290
+ model: { id: model },
215
291
  system: systemPrompt,
216
- messages: [{ role: "user", content: `Review this diff:\n\`\`\`diff\n${diff}\n\`\`\`` }],
217
- max_tokens: C.LIMITS.maxTokensDeep,
218
- tools: [{ type: "computer_20250124", name: "computer" }],
292
+ tools: [{ type: "agent_toolset_20260401" }],
219
293
  });
294
+ if (agentRes.statusCode >= 400 || !agentRes.body.id) {
295
+ logError(`Failed to create agent (${agentRes.statusCode}): ${JSON.stringify(agentRes.body).slice(0, 300)}`);
296
+ return null;
297
+ }
298
+ agentId = agentRes.body.id;
299
+ logInfo(`Agent created: ${agentId}`);
300
+ }
220
301
 
221
- const req = https.request({
222
- hostname: C.API_HOSTS.anthropic,
223
- path: "/v1/managed_agents/sessions",
224
- method: "POST",
225
- headers: {
226
- "Content-Type": "application/json",
227
- "x-api-key": apiKey,
228
- "anthropic-beta": CONFIG.managedAgents.betaHeader,
229
- "anthropic-version": C.ANTHROPIC_API_VERSION,
230
- },
231
- }, (res) => {
232
- let data = "";
233
- res.on("data", (chunk) => (data += chunk));
234
- res.on("end", () => {
235
- const elapsed = (Date.now() - startTime) / 1000;
236
- const sessionHours = elapsed / 3600;
237
- const runtimeCost = (sessionHours * 0.08).toFixed(4);
238
-
239
- if (res.statusCode >= 400) {
240
- logError(`Managed Agent failed (${res.statusCode}): ${data.slice(0, 300)}`);
241
- return resolve(null);
242
- }
243
- try {
244
- const reviewParser = require("../review-parser");
245
- const parseReviewResponse = reviewParser.parseReviewResponse;
246
-
247
- const parsed = JSON.parse(data);
248
- const text = parsed.content?.map(b => b.text).filter(Boolean).join("\n") || data;
249
- const review = parseReviewResponse(text);
250
-
251
- const inputTokens = parsed.usage?.input_tokens || Math.ceil(body.length / 4);
252
- const outputTokens = parsed.usage?.output_tokens || Math.ceil(text.length / 4);
253
- const tokenCost = estimateCost(inputTokens, outputTokens, model);
254
- const totalCost = (parseFloat(tokenCost) + parseFloat(runtimeCost)).toFixed(4);
255
-
256
- logSuccess(`Deep review complete (${elapsed.toFixed(1)}s)`);
257
- logInfo(`Runtime cost: $${runtimeCost} | Token cost: $${tokenCost} | Total: $${totalCost}`);
258
-
259
- resolve({
260
- ...review,
261
- raw: text,
262
- sessionHours,
263
- tokens: { input: inputTokens, output: outputTokens },
264
- cost: { token: tokenCost, runtime: runtimeCost, total: totalCost },
265
- });
266
- } catch (e) {
267
- logWarn(`Managed Agent response unparseable: ${e.message}`);
268
- resolve(null);
269
- }
270
- });
271
- });
272
- req.on("error", (e) => {
273
- logError(`Managed Agent network error: ${e.message}`);
274
- resolve(null);
275
- });
276
- req.setTimeout(CONFIG.managedAgents.sessionTimeoutMs, () => {
277
- req.destroy(new Error(`Managed Agent timeout after ${CONFIG.managedAgents.sessionTimeoutMs / 1000}s`));
302
+ // ── Step 2: Create or reuse environment ──
303
+ let envId = options.environmentId || CONFIG.managedAgents.environmentId;
304
+ if (!envId) {
305
+ logInfo("Creating environment...");
306
+ const envRes = await _apiRequest("POST", "/v1/environments", apiKey, {
307
+ name: "solo-cto-review-env",
308
+ config: { type: "cloud", networking: { type: "unrestricted" } },
278
309
  });
279
- req.write(body);
280
- req.end();
310
+ if (envRes.statusCode >= 400 || !envRes.body.id) {
311
+ logError(`Failed to create environment (${envRes.statusCode}): ${JSON.stringify(envRes.body).slice(0, 300)}`);
312
+ return null;
313
+ }
314
+ envId = envRes.body.id;
315
+ logInfo(`Environment created: ${envId}`);
316
+ }
317
+
318
+ // ── Step 3: Create session ──
319
+ logInfo("Creating session...");
320
+ const sessionRes = await _apiRequest("POST", "/v1/sessions", apiKey, {
321
+ agent: agentId,
322
+ environment_id: envId,
323
+ title: `deep-review-${new Date().toISOString().slice(0, 19)}`,
281
324
  });
325
+ if (sessionRes.statusCode >= 400 || !sessionRes.body.id) {
326
+ logError(`Failed to create session (${sessionRes.statusCode}): ${JSON.stringify(sessionRes.body).slice(0, 300)}`);
327
+ return null;
328
+ }
329
+ const sessionId = sessionRes.body.id;
330
+ logInfo(`Session created: ${sessionId}`);
331
+
332
+ // ── Step 4: Send user message event ──
333
+ logInfo("Sending diff for review...");
334
+ const eventRes = await _apiRequest("POST", `/v1/sessions/${sessionId}/events`, apiKey, {
335
+ events: [{
336
+ type: "user.message",
337
+ content: [{
338
+ type: "text",
339
+ text: `Review this diff:\n\`\`\`diff\n${diff}\n\`\`\`\n\nOutput your review in the standard format:\n[VERDICT] APPROVE | REQUEST_CHANGES | COMMENT\n[ISSUES] list each issue\n[SUMMARY] one-line summary\n[NEXT ACTION] suggested next steps`,
340
+ }],
341
+ }],
342
+ });
343
+ if (eventRes.statusCode >= 400) {
344
+ logError(`Failed to send event (${eventRes.statusCode}): ${JSON.stringify(eventRes.body).slice(0, 300)}`);
345
+ return null;
346
+ }
347
+ logInfo("Event sent — waiting for agent to complete...");
348
+
349
+ // ── Step 5: Poll until idle ──
350
+ const finalSession = await _pollSession(sessionId, apiKey, timeoutMs);
351
+ if (!finalSession) return null;
352
+
353
+ const elapsed = (Date.now() - startTime) / 1000;
354
+ const activeSeconds = finalSession.stats?.active_seconds || 0;
355
+ const sessionHours = activeSeconds / 3600;
356
+ const runtimeCost = (sessionHours * (C.PRICING.managedAgentRuntime || 0.08)).toFixed(4);
357
+
358
+ // ── Step 6: Fetch events to extract agent response ──
359
+ const eventsRes = await _apiRequest("GET", `/v1/sessions/${sessionId}/events`, apiKey);
360
+ if (eventsRes.statusCode >= 400) {
361
+ logError(`Failed to fetch events: ${eventsRes.statusCode}`);
362
+ return null;
363
+ }
364
+
365
+ const events = eventsRes.body.data || [];
366
+ const agentMessages = events.filter((e) => e.type === "agent.message");
367
+ const text = agentMessages
368
+ .flatMap((e) => (e.content || []).filter((b) => b.type === "text").map((b) => b.text))
369
+ .join("\n");
370
+
371
+ if (!text) {
372
+ logWarn("Agent session completed but no text response found.");
373
+ return null;
374
+ }
375
+
376
+ const reviewParser = require("../review-parser");
377
+ const review = reviewParser.parseReviewResponse(text);
378
+
379
+ const inputTokens = finalSession.usage?.input_tokens || 0;
380
+ const outputTokens = finalSession.usage?.output_tokens || 0;
381
+ const cacheTokens = finalSession.usage?.cache_creation_input_tokens || 0;
382
+ const tokenCost = estimateCost(inputTokens + cacheTokens, outputTokens, model);
383
+ const totalCost = (parseFloat(tokenCost) + parseFloat(runtimeCost)).toFixed(4);
384
+
385
+ logSuccess(`Deep review complete (${elapsed.toFixed(1)}s wall, ${activeSeconds.toFixed(1)}s active)`);
386
+ logInfo(`Runtime cost: $${runtimeCost} | Token cost: $${tokenCost} | Total: $${totalCost}`);
387
+ logInfo(`Session: ${sessionId} | Agent: ${agentId} | Env: ${envId}`);
388
+
389
+ return {
390
+ ...review,
391
+ raw: text,
392
+ sessionId,
393
+ agentId,
394
+ environmentId: envId,
395
+ activeSeconds,
396
+ sessionHours,
397
+ tokens: { input: inputTokens, output: outputTokens, cache: cacheTokens },
398
+ cost: { token: tokenCost, runtime: runtimeCost, total: totalCost },
399
+ };
282
400
  }
283
401
 
284
402
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solo-cto-agent",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "private": false,
5
5
  "description": "CTO-level AI agent toolkit for solo founders. Dual-agent review, circuit breakers, design quality gates, and session memory for Claude Cowork + OpenAI Codex.",
6
6
  "author": "seunghunbae-3svs",