claude-all-hands 1.0.39 → 1.0.41

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.
@@ -0,0 +1,28 @@
1
+ ---
2
+ description: Address PR review comments
3
+ ---
4
+
5
+ <objective>
6
+ Process unresolved PR review comments for the current feature branch, prioritize them, and address each based on user direction.
7
+ </objective>
8
+
9
+ <process>
10
+ 1. Read all unresolved review comments from the current feature branch's PR
11
+ 2. Group any duplicative review feedback
12
+ 3. Order concerns by priority using P1/P2/P3 indicators based on review indicators and your judgement
13
+ 4. For EACH grouped concern, use AskUserQuestion to present it separately:
14
+ - Include P<N> priority indicator
15
+ - Describe the concern and proposed resolution action
16
+ - Offer multichoice options: approve, decline, or "say something" (provide specific instructions)
17
+ 5. Execute on user's feedback for each concern
18
+ 6. Commit and push changes with an inferred commit message
19
+ 7. Resolve PR comments related to addressed issues (skip declined concerns)
20
+ 8. If any comments remain unresolved, briefly summarize and ask user what to do next
21
+ </process>
22
+
23
+ <success_criteria>
24
+ - Each concern presented as separate AskUserQuestion with multichoice options
25
+ - Only approved/instructed concerns addressed
26
+ - Declined concerns left untouched in PR
27
+ - Changes committed and pushed
28
+ </success_criteria>
@@ -10,6 +10,7 @@
10
10
  "hasInstallScript": true,
11
11
  "dependencies": {
12
12
  "@google/genai": "^0.14.0",
13
+ "@opencode-ai/sdk": "^1.1.14",
13
14
  "@upstash/context7-sdk": "^0.3.0",
14
15
  "@visheratin/tokenizers-node": "0.1.5",
15
16
  "@visheratin/web-ai-node": "^1.4.5",
@@ -498,6 +499,12 @@
498
499
  "node": ">=18.0.0"
499
500
  }
500
501
  },
502
+ "node_modules/@opencode-ai/sdk": {
503
+ "version": "1.1.14",
504
+ "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.14.tgz",
505
+ "integrity": "sha512-PJFu2QPxnOk0VZzlPm+IxhD1wSA41PJyCG6gkxAMI767gfAO96A0ukJJN7VK/gO6MbxLF5oTFaxBX5rAGcBRVw==",
506
+ "license": "MIT"
507
+ },
501
508
  "node_modules/@pinojs/redact": {
502
509
  "version": "0.4.0",
503
510
  "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
@@ -14,6 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@google/genai": "^0.14.0",
17
+ "@opencode-ai/sdk": "^1.1.14",
17
18
  "@upstash/context7-sdk": "^0.3.0",
18
19
  "@visheratin/tokenizers-node": "0.1.5",
19
20
  "@visheratin/web-ai-node": "^1.4.5",
@@ -2,21 +2,39 @@
2
2
  * Knowledge commands - semantic search and indexing for docs/ documentation.
3
3
  *
4
4
  * Commands:
5
- * envoy knowledge search <query> [--metadata-only]
5
+ * envoy knowledge search <query> [--metadata-only] [--force-aggregate] [--no-aggregate]
6
6
  * envoy knowledge reindex-all
7
7
  * envoy knowledge reindex-from-changes [--files <json_array>]
8
8
  */
9
9
 
10
+ import { readFileSync } from "fs";
11
+ import { dirname, join } from "path";
12
+ import { fileURLToPath } from "url";
10
13
  import { Command } from "commander";
11
14
  import { spawnSync } from "child_process";
12
15
  import { BaseCommand, CommandResult } from "./base.js";
13
16
  import { KnowledgeService, type FileChange } from "../lib/knowledge.js";
17
+ import {
18
+ AgentRunner,
19
+ type AggregatorOutput,
20
+ type SearchResult,
21
+ } from "../lib/agents/index.js";
14
22
  import { getBaseBranch } from "../lib/git.js";
15
23
 
16
24
  const getProjectRoot = (): string => {
17
25
  return process.env.PROJECT_ROOT || process.cwd();
18
26
  };
19
27
 
28
+ // Load aggregator prompt from file
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+ const AGGREGATOR_PROMPT_PATH = join(__dirname, "../lib/agents/prompts/knowledge-aggregator.md");
31
+
32
+ const getAggregatorPrompt = (): string => {
33
+ return readFileSync(AGGREGATOR_PROMPT_PATH, "utf-8");
34
+ };
35
+
36
+ const DEFAULT_TOKEN_THRESHOLD = 3500;
37
+
20
38
  /**
21
39
  * Auto-detect doc file changes since branch diverged from base.
22
40
  * Returns FileChange[] for docs/ files only.
@@ -70,37 +88,119 @@ function getDocChangesFromGit(): FileChange[] {
70
88
  }
71
89
 
72
90
  /**
73
- * Search command - semantic search against docs index
91
+ * Search command - semantic search against docs index with hybrid aggregation
74
92
  */
75
93
  class SearchCommand extends BaseCommand {
76
94
  readonly name = "search";
77
- readonly description = "Semantic search docs (use descriptive phrases, not keywords)";
95
+ readonly description = "Semantic search docs (aggregates large results automatically)";
78
96
 
79
97
  defineArguments(cmd: Command): void {
80
98
  cmd
81
99
  .argument("<query>", "Descriptive phrase (e.g. 'how to handle API authentication' not 'auth')")
82
- .option("--metadata-only", "Return only file paths and descriptions (no full content)");
100
+ .option("--metadata-only", "Return only file paths and descriptions (no full content)")
101
+ .option("--force-aggregate", "Force aggregation even below threshold")
102
+ .option("--no-aggregate", "Disable aggregation entirely");
83
103
  }
84
104
 
85
105
  async execute(args: Record<string, unknown>): Promise<CommandResult> {
86
106
  const query = args.query as string;
87
107
  const metadataOnly = !!args.metadataOnly;
108
+ const forceAggregate = !!args.forceAggregate;
109
+ const noAggregate = !!args.noAggregate;
88
110
 
89
111
  if (!query) {
90
112
  return this.error("validation_error", "query is required");
91
113
  }
92
114
 
115
+ const projectRoot = getProjectRoot();
116
+
93
117
  try {
94
- const service = new KnowledgeService(getProjectRoot());
118
+ const service = new KnowledgeService(projectRoot);
95
119
  const results = await service.search(query, 50, metadataOnly);
96
120
 
97
- return this.success({
98
- message: "Search completed",
99
- query,
100
- metadata_only: metadataOnly,
101
- results,
102
- result_count: results.length,
103
- });
121
+ // Skip aggregation if metadata-only or explicitly disabled
122
+ if (metadataOnly || noAggregate) {
123
+ return this.success({
124
+ query,
125
+ metadata_only: metadataOnly,
126
+ results,
127
+ result_count: results.length,
128
+ });
129
+ }
130
+
131
+ // Calculate total tokens
132
+ const totalTokens = results.reduce((sum, r) => sum + r.token_count, 0);
133
+ const parsedThreshold = parseInt(
134
+ process.env.KNOWLEDGE_AGGREGATOR_TOKEN_THRESHOLD ?? String(DEFAULT_TOKEN_THRESHOLD),
135
+ 10
136
+ );
137
+ const threshold = Number.isNaN(parsedThreshold) ? DEFAULT_TOKEN_THRESHOLD : parsedThreshold;
138
+
139
+ // Skip aggregation if below threshold
140
+ if (totalTokens < threshold && !forceAggregate) {
141
+ return this.success({
142
+ aggregated: false,
143
+ total_tokens: totalTokens,
144
+ threshold,
145
+ results,
146
+ result_count: results.length,
147
+ });
148
+ }
149
+
150
+ // Separate full vs minimized results
151
+ const fullResults = results.filter((r) => r.full_resource_context) as SearchResult[];
152
+ const minimizedResults = results
153
+ .filter((r) => !r.full_resource_context)
154
+ .map((r) => ({
155
+ resource_path: r.resource_path,
156
+ similarity: r.similarity,
157
+ token_count: r.token_count,
158
+ description: r.description,
159
+ relevant_files: r.relevant_files,
160
+ })) as SearchResult[];
161
+
162
+ // Run aggregator agent
163
+ try {
164
+ const runner = new AgentRunner(projectRoot);
165
+ const input = this.formatAggregatorInput(query, fullResults, minimizedResults);
166
+
167
+ const result = await runner.run<AggregatorOutput>(
168
+ {
169
+ name: "knowledge-aggregator",
170
+ systemPrompt: getAggregatorPrompt(),
171
+ timeoutMs: 60000,
172
+ },
173
+ input
174
+ );
175
+
176
+ if (!result.success || !result.data) {
177
+ // Fallback to raw results on aggregation failure
178
+ return this.success({
179
+ aggregated: false,
180
+ aggregation_error: result.error ?? "Unknown aggregation error",
181
+ total_tokens: totalTokens,
182
+ results,
183
+ result_count: results.length,
184
+ });
185
+ }
186
+
187
+ return this.success({
188
+ aggregated: true,
189
+ insight: result.data.insight,
190
+ references: result.data.references,
191
+ design_notes: result.data.design_notes,
192
+ metadata: result.metadata,
193
+ });
194
+ } catch (e) {
195
+ // Fallback to raw results on any error
196
+ return this.success({
197
+ aggregated: false,
198
+ aggregation_error: e instanceof Error ? e.message : String(e),
199
+ total_tokens: totalTokens,
200
+ results,
201
+ result_count: results.length,
202
+ });
203
+ }
104
204
  } catch (e) {
105
205
  return this.error(
106
206
  "search_error",
@@ -108,6 +208,36 @@ class SearchCommand extends BaseCommand {
108
208
  );
109
209
  }
110
210
  }
211
+
212
+ private formatAggregatorInput(
213
+ query: string,
214
+ fullResults: SearchResult[],
215
+ minimizedResults: SearchResult[]
216
+ ): string {
217
+ return `## Query
218
+ ${query}
219
+
220
+ ## Full Results (${fullResults.length} documents with complete content)
221
+
222
+ ${fullResults.map((r) => `### ${r.resource_path}
223
+ - Similarity: ${r.similarity.toFixed(3)}
224
+ - Tokens: ${r.token_count}
225
+ - Description: ${r.description}
226
+
227
+ Content:
228
+ \`\`\`
229
+ ${r.full_resource_context}
230
+ \`\`\`
231
+ `).join("\n")}
232
+
233
+ ## Minimized Results (${minimizedResults.length} documents - request expansion if needed)
234
+
235
+ ${minimizedResults.map((r) => `- **${r.resource_path}** (similarity: ${r.similarity.toFixed(3)}, ${r.token_count} tokens)
236
+ ${r.description}
237
+ `).join("\n")}
238
+
239
+ Please analyze and provide your response as JSON.`;
240
+ }
111
241
  }
112
242
 
113
243
  /**
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Sub-agent infrastructure for envoy.
3
+ * Uses OpenCode SDK to spawn agents for specialized tasks.
4
+ */
5
+
6
+ // Agent configuration
7
+ export interface AgentConfig {
8
+ name: string;
9
+ systemPrompt: string;
10
+ model?: string;
11
+ timeoutMs?: number;
12
+ }
13
+
14
+ // Agent execution result
15
+ export interface AgentResult<T = unknown> {
16
+ success: boolean;
17
+ data?: T;
18
+ error?: string;
19
+ metadata?: {
20
+ model: string;
21
+ tokens_used?: number;
22
+ duration_ms: number;
23
+ };
24
+ }
25
+
26
+ // Search result type (mirrors KnowledgeService.SearchResult)
27
+ export interface SearchResult {
28
+ resource_path: string;
29
+ similarity: number;
30
+ token_count: number;
31
+ description: string;
32
+ relevant_files: string[];
33
+ full_resource_context?: string;
34
+ }
35
+
36
+ // Knowledge aggregator input
37
+ export interface AggregatorInput {
38
+ query: string;
39
+ full_results: SearchResult[];
40
+ minimized_results: SearchResult[];
41
+ }
42
+
43
+ // Knowledge aggregator output
44
+ export interface AggregatorOutput {
45
+ insight: string;
46
+ references: Array<{
47
+ file: string;
48
+ symbol: string | null;
49
+ why: string;
50
+ }>;
51
+ design_notes?: string[];
52
+ }
53
+
54
+ export { AgentRunner } from "./runner.js";
@@ -0,0 +1,67 @@
1
+ # Knowledge Aggregator
2
+
3
+ You synthesize documentation into codebase-grounded answers. The caller is exploring a codebase - they need to understand **what exists**, **why it was built that way**, and **where to look**.
4
+
5
+ ## Core Principle
6
+
7
+ Never answer generically. Every insight must reference concrete codebase implementations:
8
+ - BAD: "An agent is a specialized execution context..."
9
+ - GOOD: "Agents in this codebase are defined in `.claude/agents/` with configs that scope their tools - see `curator.md` for the pattern of restricting WebSearch to specific agent types"
10
+
11
+ ## Input Format
12
+
13
+ You receive:
14
+ 1. A user query about the codebase
15
+ 2. Full results: Docs with complete content (high similarity)
16
+ 3. Minimized results: Docs with metadata only (may need expansion)
17
+
18
+ ## Expansion Protocol
19
+
20
+ Need content from a minimized result? Output:
21
+ ```
22
+ EXPAND: <resource_path>
23
+ ```
24
+
25
+ You'll receive the content. Max 3 expansions. Only expand if description suggests direct relevance.
26
+
27
+ ## Output Format
28
+
29
+ Return ONLY valid JSON:
30
+
31
+ ```json
32
+ {
33
+ "insight": "Codebase-grounded answer: what pattern exists, why it was chosen, how it's used. Include best practices if query implies implementation intent. 2-4 sentences max.",
34
+ "references": [
35
+ {
36
+ "file": "path/to/implementation.ts",
37
+ "symbol": "functionOrClassName",
38
+ "why": "Brief reason main agent should check this"
39
+ }
40
+ ],
41
+ "design_notes": ["Relevant architectural decisions or tradeoffs from docs"]
42
+ }
43
+ ```
44
+
45
+ ## Field Guidelines
46
+
47
+ **insight**:
48
+ - Ground every statement in the codebase
49
+ - If query implies they want to implement something, include the recommended approach
50
+ - Mention specific files/patterns by name
51
+ - Include "best practice: X" when docs encode conventions
52
+
53
+ **references** (max 5, ranked by relevance):
54
+ - `file`: Path to code file (from doc's `relevant_files` or inline references like `[ref:path:symbol:hash]`)
55
+ - `symbol`: Function/class/variable name if known from doc references, null otherwise
56
+ - `why`: One sentence - why should they look here? What will they find?
57
+
58
+ **design_notes** (optional, max 2):
59
+ - Only include if docs explicitly discuss design rationale
60
+ - Format: "[Decision]: [Rationale]" e.g. "Least-privilege tooling: agents receive only tools for their function to prevent cross-domain actions"
61
+
62
+ ## Anti-patterns
63
+
64
+ - Generic definitions not tied to this codebase
65
+ - Listing every file mentioned (keep only most relevant)
66
+ - Excerpts without actionability
67
+ - Restating the query as the answer
@@ -0,0 +1,251 @@
1
+ /**
2
+ * AgentRunner - Spawns and manages sub-agents via OpenCode SDK.
3
+ * Each run spawns a fresh server instance, executes the agent, and cleans up.
4
+ */
5
+
6
+ import { existsSync, readFileSync, statSync } from "fs";
7
+ import { join } from "path";
8
+ import { createOpencode } from "@opencode-ai/sdk";
9
+ import type { AgentConfig, AgentResult } from "./index.js";
10
+ import { logCommandComplete, logCommandStart } from "../observability.js";
11
+
12
+ const MAX_EXPANSIONS = 3;
13
+ const EXPANSION_PATTERN = /^EXPAND:\s*(.+)$/gm;
14
+ const DEFAULT_TIMEOUT_MS = 60000;
15
+
16
+ export class AgentRunner {
17
+ private readonly projectRoot: string;
18
+
19
+ constructor(projectRoot: string) {
20
+ this.projectRoot = projectRoot;
21
+ }
22
+
23
+ /**
24
+ * Execute an agent with given config and user message.
25
+ * Spawns server, creates session, sends message, handles expansions, cleans up.
26
+ */
27
+ async run<T>(config: AgentConfig, userMessage: string): Promise<AgentResult<T>> {
28
+ const startTime = Date.now();
29
+ logCommandStart("agent.run", { agent: config.name });
30
+
31
+ let server: { close: () => void } | null = null;
32
+
33
+ try {
34
+ const { client, server: srv } = await createOpencode({
35
+ timeout: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
36
+ config: config.model ? { model: config.model } : undefined,
37
+ });
38
+ server = srv;
39
+
40
+ const session = await client.session.create({
41
+ body: { title: config.name },
42
+ });
43
+
44
+ if (!session.data?.id) {
45
+ throw new Error("Failed to create session");
46
+ }
47
+
48
+ // Send system prompt as context
49
+ await client.session.prompt({
50
+ path: { id: session.data.id },
51
+ body: {
52
+ noReply: true,
53
+ parts: [{ type: "text", text: config.systemPrompt }],
54
+ },
55
+ });
56
+
57
+ // Send user message and get response
58
+ let response = await client.session.prompt({
59
+ path: { id: session.data.id },
60
+ body: {
61
+ parts: [{ type: "text", text: userMessage }],
62
+ },
63
+ });
64
+
65
+ let responseText = this.extractResponseText(response);
66
+ let expansionCount = 0;
67
+
68
+ // Handle expansion requests
69
+ while (this.hasExpansionRequest(responseText) && expansionCount < MAX_EXPANSIONS) {
70
+ const paths = this.extractExpansionPaths(responseText);
71
+ const contents = this.readFiles(paths);
72
+ const expansionMessage = this.formatExpansionResponse(contents);
73
+
74
+ response = await client.session.prompt({
75
+ path: { id: session.data.id },
76
+ body: {
77
+ parts: [{ type: "text", text: expansionMessage }],
78
+ },
79
+ });
80
+
81
+ responseText = this.extractResponseText(response);
82
+ expansionCount++;
83
+ }
84
+
85
+ const durationMs = Date.now() - startTime;
86
+ const parsed = this.parseStructuredOutput<T>(responseText);
87
+
88
+ if (!parsed) {
89
+ // Retry once asking for JSON
90
+ response = await client.session.prompt({
91
+ path: { id: session.data.id },
92
+ body: {
93
+ parts: [{ type: "text", text: "Please format your response as valid JSON matching the required schema." }],
94
+ },
95
+ });
96
+
97
+ responseText = this.extractResponseText(response);
98
+ const retryParsed = this.parseStructuredOutput<T>(responseText);
99
+
100
+ if (!retryParsed) {
101
+ throw new Error("Failed to parse agent response as JSON");
102
+ }
103
+
104
+ logCommandComplete("agent.run", "success", Date.now() - startTime, {
105
+ agent: config.name,
106
+ expansions: expansionCount,
107
+ retry: true,
108
+ });
109
+
110
+ return {
111
+ success: true,
112
+ data: retryParsed,
113
+ metadata: {
114
+ model: config.model ?? "default",
115
+ duration_ms: Date.now() - startTime,
116
+ },
117
+ };
118
+ }
119
+
120
+ logCommandComplete("agent.run", "success", durationMs, {
121
+ agent: config.name,
122
+ expansions: expansionCount,
123
+ });
124
+
125
+ return {
126
+ success: true,
127
+ data: parsed,
128
+ metadata: {
129
+ model: config.model ?? "default",
130
+ duration_ms: durationMs,
131
+ },
132
+ };
133
+ } catch (error) {
134
+ const durationMs = Date.now() - startTime;
135
+ const errorMessage = error instanceof Error ? error.message : String(error);
136
+
137
+ logCommandComplete("agent.run", "error", durationMs, {
138
+ agent: config.name,
139
+ error: errorMessage,
140
+ });
141
+
142
+ return {
143
+ success: false,
144
+ error: errorMessage,
145
+ metadata: {
146
+ model: config.model ?? "default",
147
+ duration_ms: durationMs,
148
+ },
149
+ };
150
+ } finally {
151
+ if (server) {
152
+ server.close();
153
+ }
154
+ }
155
+ }
156
+
157
+ private extractResponseText(response: unknown): string {
158
+ // Navigate SDK response structure to get text content
159
+ const resp = response as {
160
+ data?: {
161
+ parts?: Array<{ type: string; text?: string }>;
162
+ };
163
+ };
164
+
165
+ if (!resp.data?.parts) {
166
+ return "";
167
+ }
168
+
169
+ return resp.data.parts
170
+ .filter((p) => p.type === "text" && p.text)
171
+ .map((p) => p.text)
172
+ .join("\n");
173
+ }
174
+
175
+ private hasExpansionRequest(text: string): boolean {
176
+ const regex = new RegExp(EXPANSION_PATTERN.source, EXPANSION_PATTERN.flags);
177
+ return regex.test(text);
178
+ }
179
+
180
+ private extractExpansionPaths(text: string): string[] {
181
+ const paths: string[] = [];
182
+ const regex = new RegExp(EXPANSION_PATTERN.source, "gm");
183
+ let match;
184
+
185
+ while ((match = regex.exec(text)) !== null) {
186
+ paths.push(match[1].trim());
187
+ }
188
+
189
+ return paths;
190
+ }
191
+
192
+ private readFiles(paths: string[]): Map<string, string | null> {
193
+ const contents = new Map<string, string | null>();
194
+ const MAX_FILE_SIZE = 1024 * 1024; // 1MB
195
+
196
+ for (const path of paths) {
197
+ const fullPath = join(this.projectRoot, path);
198
+ if (existsSync(fullPath)) {
199
+ try {
200
+ const stats = statSync(fullPath);
201
+ if (stats.size > MAX_FILE_SIZE) {
202
+ const partial = readFileSync(fullPath, "utf-8").slice(0, MAX_FILE_SIZE);
203
+ contents.set(path, `${partial}\n\n[TRUNCATED: file exceeds 1MB limit]`);
204
+ } else {
205
+ contents.set(path, readFileSync(fullPath, "utf-8"));
206
+ }
207
+ } catch {
208
+ contents.set(path, null);
209
+ }
210
+ } else {
211
+ contents.set(path, null);
212
+ }
213
+ }
214
+
215
+ return contents;
216
+ }
217
+
218
+ private formatExpansionResponse(contents: Map<string, string | null>): string {
219
+ const parts: string[] = ["Here are the expanded file contents:\n"];
220
+
221
+ for (const [path, content] of contents) {
222
+ if (content !== null) {
223
+ parts.push(`## ${path}\n\`\`\`\n${content}\n\`\`\`\n`);
224
+ } else {
225
+ parts.push(`## ${path}\n[File not found or unreadable]\n`);
226
+ }
227
+ }
228
+
229
+ parts.push("\nPlease continue your analysis and provide the final JSON response.");
230
+ return parts.join("\n");
231
+ }
232
+
233
+ private parseStructuredOutput<T>(text: string): T | null {
234
+ // Try to extract JSON from code block
235
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
236
+ if (codeBlockMatch) {
237
+ try {
238
+ return JSON.parse(codeBlockMatch[1].trim()) as T;
239
+ } catch {
240
+ // Fall through to try raw parse
241
+ }
242
+ }
243
+
244
+ // Try raw JSON parse
245
+ try {
246
+ return JSON.parse(text.trim()) as T;
247
+ } catch {
248
+ return null;
249
+ }
250
+ }
251
+ }
@@ -114,3 +114,13 @@ export {
114
114
  } from "./notification.js";
115
115
  export type { NotifyOptions } from "./notification.js";
116
116
 
117
+ // Agent infrastructure
118
+ export { AgentRunner } from "./agents/index.js";
119
+ export type {
120
+ AgentConfig,
121
+ AgentResult,
122
+ AggregatorInput,
123
+ AggregatorOutput,
124
+ SearchResult,
125
+ } from "./agents/index.js";
126
+
@@ -5,6 +5,7 @@
5
5
  "SEARCH_SIMILARITY_THRESHOLD": "0.65",
6
6
  "SEARCH_FULL_CONTEXT_SIMILARITY_THRESHOLD": "0.82",
7
7
  "SEARCH_CONTEXT_TOKEN_LIMIT": "5000",
8
+ "KNOWLEDGE_AGGREGATOR_TOKEN_THRESHOLD": "3500",
8
9
  "MAX_LOGS_TOKENS": "10000",
9
10
  "BASH_MAX_TIMEOUT_MS": "3600000",
10
11
  "BASE_BRANCH": "main",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-all-hands",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
4
4
  "description": "CLI for syncing Claude agent configurations to all-hands repository",
5
5
  "type": "module",
6
6
  "bin": {