@tyvm/knowhow 0.0.129 → 0.0.131

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 (164) hide show
  1. package/package.json +4 -4
  2. package/src/agents/base/base.ts +40 -5
  3. package/src/agents/configurable/ConfigAgent.ts +0 -1
  4. package/src/agents/tools/index.ts +1 -0
  5. package/src/agents/tools/list.ts +2 -0
  6. package/src/agents/tools/skills.ts +79 -0
  7. package/src/agents/tools/startAgentTask.ts +17 -13
  8. package/src/ai.ts +55 -24
  9. package/src/chat/CliChatService.ts +7 -1
  10. package/src/chat/modules/AgentModule.ts +156 -15
  11. package/src/chat/renderer/PlainRenderer.ts +218 -0
  12. package/src/chat/renderer/index.ts +1 -0
  13. package/src/chat/renderer/loadRenderer.ts +2 -0
  14. package/src/chat.ts +2 -2
  15. package/src/cli.ts +10 -0
  16. package/src/clients/anthropic.ts +68 -2
  17. package/src/clients/fireworks.ts +10 -1
  18. package/src/clients/index.ts +5 -3
  19. package/src/clients/meta.ts +9 -0
  20. package/src/clients/openai.ts +28 -5
  21. package/src/clients/pricing/anthropic.ts +11 -0
  22. package/src/clients/pricing/fireworks.ts +12 -0
  23. package/src/clients/pricing/openai.ts +3 -0
  24. package/src/clients/types.ts +27 -4
  25. package/src/clients/xai.ts +2 -2
  26. package/src/commands/agent.ts +70 -18
  27. package/src/commands/agents.ts +556 -0
  28. package/src/commands/behaviors.ts +47 -0
  29. package/src/commands/hash.ts +124 -0
  30. package/src/commands/misc.ts +157 -3
  31. package/src/commands/services.ts +6 -2
  32. package/src/commands/skills.ts +107 -0
  33. package/src/config.ts +51 -1
  34. package/src/generate.ts +507 -0
  35. package/src/index.ts +34 -216
  36. package/src/plugins/PluginBase.ts +0 -2
  37. package/src/plugins/SkillsPlugin.ts +11 -124
  38. package/src/plugins/language.ts +0 -1
  39. package/src/plugins/plugins.ts +24 -13
  40. package/src/plugins/types.ts +8 -2
  41. package/src/services/BehaviorsService.ts +464 -0
  42. package/src/services/KnowhowClient.ts +61 -0
  43. package/src/services/SkillsService.ts +820 -0
  44. package/src/services/Tools.ts +2 -0
  45. package/src/services/index.ts +8 -2
  46. package/src/services/modules/types.ts +2 -0
  47. package/src/types.ts +36 -4
  48. package/ts_build/package.json +4 -4
  49. package/ts_build/src/agents/base/base.d.ts +9 -1
  50. package/ts_build/src/agents/base/base.js +29 -4
  51. package/ts_build/src/agents/base/base.js.map +1 -1
  52. package/ts_build/src/agents/configurable/ConfigAgent.js.map +1 -1
  53. package/ts_build/src/agents/tools/index.d.ts +1 -0
  54. package/ts_build/src/agents/tools/index.js +1 -0
  55. package/ts_build/src/agents/tools/index.js.map +1 -1
  56. package/ts_build/src/agents/tools/list.js +2 -0
  57. package/ts_build/src/agents/tools/list.js.map +1 -1
  58. package/ts_build/src/agents/tools/skills.d.ts +33 -0
  59. package/ts_build/src/agents/tools/skills.js +62 -0
  60. package/ts_build/src/agents/tools/skills.js.map +1 -0
  61. package/ts_build/src/agents/tools/startAgentTask.d.ts +2 -2
  62. package/ts_build/src/agents/tools/startAgentTask.js +13 -12
  63. package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
  64. package/ts_build/src/ai.d.ts +10 -4
  65. package/ts_build/src/ai.js +27 -25
  66. package/ts_build/src/ai.js.map +1 -1
  67. package/ts_build/src/chat/CliChatService.d.ts +1 -1
  68. package/ts_build/src/chat/CliChatService.js +4 -1
  69. package/ts_build/src/chat/CliChatService.js.map +1 -1
  70. package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
  71. package/ts_build/src/chat/modules/AgentModule.js +130 -4
  72. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  73. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
  74. package/ts_build/src/chat/renderer/PlainRenderer.d.ts +28 -0
  75. package/ts_build/src/chat/renderer/PlainRenderer.js +169 -0
  76. package/ts_build/src/chat/renderer/PlainRenderer.js.map +1 -0
  77. package/ts_build/src/chat/renderer/index.d.ts +1 -0
  78. package/ts_build/src/chat/renderer/index.js +3 -1
  79. package/ts_build/src/chat/renderer/index.js.map +1 -1
  80. package/ts_build/src/chat/renderer/loadRenderer.js +2 -0
  81. package/ts_build/src/chat/renderer/loadRenderer.js.map +1 -1
  82. package/ts_build/src/chat.d.ts +1 -1
  83. package/ts_build/src/chat.js +2 -2
  84. package/ts_build/src/chat.js.map +1 -1
  85. package/ts_build/src/cli.js +9 -0
  86. package/ts_build/src/cli.js.map +1 -1
  87. package/ts_build/src/clients/anthropic.d.ts +2 -0
  88. package/ts_build/src/clients/anthropic.js +39 -1
  89. package/ts_build/src/clients/anthropic.js.map +1 -1
  90. package/ts_build/src/clients/fireworks.js +7 -0
  91. package/ts_build/src/clients/fireworks.js.map +1 -1
  92. package/ts_build/src/clients/index.js +3 -1
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/meta.d.ts +2 -0
  95. package/ts_build/src/clients/meta.js +5 -0
  96. package/ts_build/src/clients/meta.js.map +1 -1
  97. package/ts_build/src/clients/openai.d.ts +1 -0
  98. package/ts_build/src/clients/openai.js +25 -5
  99. package/ts_build/src/clients/openai.js.map +1 -1
  100. package/ts_build/src/clients/pricing/anthropic.d.ts +3 -0
  101. package/ts_build/src/clients/pricing/anthropic.js +6 -0
  102. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/pricing/fireworks.d.ts +1 -0
  104. package/ts_build/src/clients/pricing/fireworks.js +5 -1
  105. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  106. package/ts_build/src/clients/pricing/openai.js +3 -0
  107. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  108. package/ts_build/src/clients/types.d.ts +6 -1
  109. package/ts_build/src/clients/xai.js +1 -1
  110. package/ts_build/src/clients/xai.js.map +1 -1
  111. package/ts_build/src/commands/agent.js +58 -15
  112. package/ts_build/src/commands/agent.js.map +1 -1
  113. package/ts_build/src/commands/agents.d.ts +2 -0
  114. package/ts_build/src/commands/agents.js +460 -0
  115. package/ts_build/src/commands/agents.js.map +1 -0
  116. package/ts_build/src/commands/behaviors.d.ts +2 -0
  117. package/ts_build/src/commands/behaviors.js +43 -0
  118. package/ts_build/src/commands/behaviors.js.map +1 -0
  119. package/ts_build/src/commands/hash.d.ts +2 -0
  120. package/ts_build/src/commands/hash.js +117 -0
  121. package/ts_build/src/commands/hash.js.map +1 -0
  122. package/ts_build/src/commands/misc.js +108 -2
  123. package/ts_build/src/commands/misc.js.map +1 -1
  124. package/ts_build/src/commands/services.js +4 -2
  125. package/ts_build/src/commands/services.js.map +1 -1
  126. package/ts_build/src/commands/skills.d.ts +2 -0
  127. package/ts_build/src/commands/skills.js +85 -0
  128. package/ts_build/src/commands/skills.js.map +1 -0
  129. package/ts_build/src/config.js +34 -0
  130. package/ts_build/src/config.js.map +1 -1
  131. package/ts_build/src/generate.d.ts +16 -0
  132. package/ts_build/src/generate.js +304 -0
  133. package/ts_build/src/generate.js.map +1 -0
  134. package/ts_build/src/index.d.ts +3 -3
  135. package/ts_build/src/index.js +13 -118
  136. package/ts_build/src/index.js.map +1 -1
  137. package/ts_build/src/plugins/PluginBase.d.ts +0 -2
  138. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  139. package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
  140. package/ts_build/src/plugins/SkillsPlugin.js +6 -135
  141. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
  142. package/ts_build/src/plugins/language.js.map +1 -1
  143. package/ts_build/src/plugins/plugins.d.ts +4 -1
  144. package/ts_build/src/plugins/plugins.js +17 -11
  145. package/ts_build/src/plugins/plugins.js.map +1 -1
  146. package/ts_build/src/plugins/types.d.ts +3 -2
  147. package/ts_build/src/services/BehaviorsService.d.ts +48 -0
  148. package/ts_build/src/services/BehaviorsService.js +382 -0
  149. package/ts_build/src/services/BehaviorsService.js.map +1 -0
  150. package/ts_build/src/services/KnowhowClient.d.ts +8 -0
  151. package/ts_build/src/services/KnowhowClient.js +20 -0
  152. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  153. package/ts_build/src/services/SkillsService.d.ts +60 -0
  154. package/ts_build/src/services/SkillsService.js +641 -0
  155. package/ts_build/src/services/SkillsService.js.map +1 -0
  156. package/ts_build/src/services/Tools.d.ts +2 -0
  157. package/ts_build/src/services/Tools.js.map +1 -1
  158. package/ts_build/src/services/index.d.ts +3 -0
  159. package/ts_build/src/services/index.js +8 -2
  160. package/ts_build/src/services/index.js.map +1 -1
  161. package/ts_build/src/services/modules/index.d.ts +4 -2
  162. package/ts_build/src/services/modules/types.d.ts +2 -0
  163. package/ts_build/src/types.d.ts +14 -4
  164. package/ts_build/src/types.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.129",
3
+ "version": "0.0.131",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -35,7 +35,6 @@
35
35
  "license": "MIT",
36
36
  "devDependencies": {
37
37
  "@babel/preset-typescript": "^7.23.3",
38
- "typescript": "^5.0.0",
39
38
  "@types/diff": "^5.2.1",
40
39
  "@types/express": "^4.17.13",
41
40
  "@types/jest": "^29.5.13",
@@ -47,10 +46,11 @@
47
46
  "ts-jest": "^29.1.1",
48
47
  "ts-node": "^10.7.0",
49
48
  "tslint": "^6.1.3",
50
- "tslint-config-prettier": "^1.18.0"
49
+ "tslint-config-prettier": "^1.18.0",
50
+ "typescript": "^5.0.0"
51
51
  },
52
52
  "dependencies": {
53
- "@anthropic-ai/sdk": "^0.39.0",
53
+ "@anthropic-ai/sdk": "^0.111.0",
54
54
  "@google/genai": "^0.14.1",
55
55
  "@inquirer/editor": "^4.2.18",
56
56
  "@modelcontextprotocol/sdk": "^1.13.3",
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from "events"; // kept for reference; agentEvents now uses EventService
2
2
  import {
3
3
  CompletionResponse,
4
+ CompletionOptions,
4
5
  GenericClient,
5
6
  Message,
6
7
  MessageContent,
@@ -26,6 +27,8 @@ export { Message, Tool, ToolCall };
26
27
  export interface ModelPreference {
27
28
  model: string;
28
29
  provider: keyof typeof Clients.clients;
30
+ reasoning_effort?: CompletionOptions["reasoning_effort"];
31
+ reasoning_summary?: boolean;
29
32
  }
30
33
 
31
34
  export interface AgentContext {
@@ -60,6 +63,8 @@ export abstract class BaseAgent implements IAgent {
60
63
  protected turnCount = 0;
61
64
  protected totalCostUsd = 0;
62
65
  protected currentThread = 0;
66
+ protected reasoningEffort: CompletionOptions["reasoning_effort"] | undefined = undefined;
67
+ protected summarizeReasoning: boolean | undefined = undefined;
63
68
  protected totalInputTokens = 0;
64
69
  protected totalOutputTokens = 0;
65
70
  protected totalCacheReadTokens = 0;
@@ -69,7 +74,7 @@ export abstract class BaseAgent implements IAgent {
69
74
  protected compressMinMessages = 30;
70
75
 
71
76
  // Interrupt support: resolves the currently awaited tool call or completion
72
- private _interruptResolve: ((value: any) => void) | null = null;
77
+ private _interruptResolve: (() => void) | null = null;
73
78
 
74
79
  protected threads = [] as Message[][];
75
80
 
@@ -233,6 +238,12 @@ export abstract class BaseAgent implements IAgent {
233
238
  updatePreferences(value: ModelPreference) {
234
239
  this.setModel(value.model);
235
240
  this.setProvider(value.provider);
241
+ if (value.reasoning_effort !== undefined) {
242
+ this.reasoningEffort = value.reasoning_effort;
243
+ }
244
+ if (value.reasoning_summary !== undefined) {
245
+ this.summarizeReasoning = value.reasoning_summary;
246
+ }
236
247
  }
237
248
 
238
249
  nextModel() {
@@ -262,6 +273,22 @@ export abstract class BaseAgent implements IAgent {
262
273
  this.client = null; // Reset client to force re-fetch
263
274
  }
264
275
 
276
+ setReasoningEffort(effort: CompletionOptions["reasoning_effort"]) {
277
+ this.reasoningEffort = effort;
278
+ }
279
+
280
+ getReasoningEffort(): CompletionOptions["reasoning_effort"] {
281
+ return this.reasoningEffort;
282
+ }
283
+
284
+ setSummarizeReasoning(value: boolean) {
285
+ this.summarizeReasoning = value;
286
+ }
287
+
288
+ getSummarizeReasoning(): boolean | undefined {
289
+ return this.summarizeReasoning;
290
+ }
291
+
265
292
  getClient() {
266
293
  if (!this.client) {
267
294
  if (this.provider) {
@@ -715,10 +742,10 @@ export abstract class BaseAgent implements IAgent {
715
742
  return new Promise<T>((resolve, reject) => {
716
743
  let interrupted = false;
717
744
 
718
- this._interruptResolve = (value: any) => {
745
+ this._interruptResolve = () => {
719
746
  interrupted = true;
720
747
  this._interruptResolve = null;
721
- resolve(value);
748
+ resolve(interruptValue);
722
749
  };
723
750
 
724
751
  promise
@@ -751,7 +778,7 @@ export abstract class BaseAgent implements IAgent {
751
778
  interrupt(message = "User interrupted this action you were waiting on") {
752
779
  this.log(`Interrupting current operation: ${message}`);
753
780
  if (this._interruptResolve) {
754
- this._interruptResolve(message);
781
+ this._interruptResolve();
755
782
  } else {
756
783
  this.log("No active interruptible operation to interrupt", "warn");
757
784
  }
@@ -848,6 +875,8 @@ export abstract class BaseAgent implements IAgent {
848
875
  tools: this.getEnabledTools(),
849
876
  tool_choice: "auto",
850
877
  long_ttl_cache: this.runTime() > 300_000,
878
+ ...(this.reasoningEffort !== undefined && { reasoning_effort: this.reasoningEffort }),
879
+ ...(this.summarizeReasoning !== undefined && { reasoning_summary: this.summarizeReasoning }),
851
880
  },
852
881
  { interruptValue: interruptResponse }
853
882
  );
@@ -1230,7 +1259,13 @@ export abstract class BaseAgent implements IAgent {
1230
1259
  toolCalls: ToolCall[],
1231
1260
  response: CompletionResponse
1232
1261
  ): { role: string; content: string } | null {
1233
- const outputTokens: number = response?.usage?.completion_tokens || 0;
1262
+ // Subtract thinking/reasoning tokens — they're billed as output tokens but
1263
+ // don't produce visible argument content, so including them inflates the heuristic.
1264
+ const rawOutputTokens: number = response?.usage?.completion_tokens || 0;
1265
+ const thinkingTokens: number =
1266
+ (response?.usage?.output_tokens_details?.thinking_tokens ?? 0) +
1267
+ (response?.usage?.output_tokens_details?.reasoning_tokens ?? 0);
1268
+ const outputTokens = Math.max(0, rawOutputTokens - thinkingTokens);
1234
1269
  const totalArgLength = toolCalls.reduce(
1235
1270
  (sum, tc) => sum + (tc.function?.arguments?.length || 0),
1236
1271
  0
@@ -1,7 +1,6 @@
1
1
  import { AgentContext, BaseAgent } from "../base/base";
2
2
  import { Message } from "../../clients/types";
3
3
  import { Assistant, Config } from "../../types";
4
- import { EventService, ToolsService } from "../../services";
5
4
  import { MessageProcessor } from "../../services/MessageProcessor";
6
5
 
7
6
  export class ConfigAgent extends BaseAgent {
@@ -23,3 +23,4 @@ export * from "./stringReplace";
23
23
  export * from "./startAgentTask";
24
24
  export * from "./ycmd";
25
25
  export * from "./mcp";
26
+ export * from "./skills";
@@ -7,6 +7,7 @@ import * as language from "./language/definitions";
7
7
  import * as mcp from "./mcp/definitions";
8
8
  import { googleSearchDefinition } from "./googleSearch";
9
9
  import { startAgentTaskDefinition } from "./startAgentTask";
10
+ import { skillsToolDefinitions } from "./skills";
10
11
 
11
12
  function getPluginNames(): string {
12
13
  try {
@@ -681,4 +682,5 @@ export const includedTools = [
681
682
  ...ycmd.definitions,
682
683
  ...language.definitions,
683
684
  ...mcp.definitions,
685
+ ...skillsToolDefinitions,
684
686
  ] as Tool[];
@@ -0,0 +1,79 @@
1
+ import { ToolsService } from "../../services/Tools";
2
+ import { services } from "../../services";
3
+
4
+ /**
5
+ * List all skills currently loaded in memory.
6
+ * Returns a formatted string of name: description lines.
7
+ */
8
+ export async function listSkills(this: ToolsService): Promise<string> {
9
+ const toolService =
10
+ this instanceof ToolsService ? (this as ToolsService) : services().Tools;
11
+
12
+ const { Behaviors } = toolService.getContext();
13
+
14
+ const skills = Behaviors.listSkills();
15
+ if (skills.length === 0) {
16
+ return "No skills available. Run `knowhow behaviors download --skills-only` to fetch skills.";
17
+ }
18
+ return skills.map((s) => `- ${s.name}: ${s.description}`).join("\n");
19
+ }
20
+
21
+ /**
22
+ * Load full instructions for a named skill from memory.
23
+ */
24
+ export async function loadSkill(
25
+ this: ToolsService,
26
+ name: string
27
+ ): Promise<string> {
28
+ const toolService =
29
+ this instanceof ToolsService ? (this as ToolsService) : services().Tools;
30
+
31
+ const { Behaviors } = toolService.getContext();
32
+
33
+ const skill = Behaviors.findSkill(name);
34
+ if (!skill) {
35
+ const names = Behaviors.listSkills()
36
+ .map((s) => s.name)
37
+ .join(", ");
38
+ return `Skill "${name}" not found. Available skills: ${names || "none"}`;
39
+ }
40
+ return `## Skill: ${skill.name}\n\n${skill.instructions}`;
41
+ }
42
+
43
+ /** Tool definitions for listSkills + loadSkill */
44
+ export const skillsToolDefinitions = [
45
+ {
46
+ type: "function" as const,
47
+ function: {
48
+ name: "listSkills",
49
+ description:
50
+ "List all available skills by name and description. Call this to discover what skills are available before loading one.",
51
+ parameters: {
52
+ type: "object" as const,
53
+ positional: true,
54
+ properties: {} as Record<string, never>,
55
+ required: [] as string[],
56
+ },
57
+ },
58
+ },
59
+ {
60
+ type: "function" as const,
61
+ function: {
62
+ name: "loadSkill",
63
+ description:
64
+ "Load the full instructions for a named skill. Use this when you need to follow a specific skill's guidance.",
65
+ parameters: {
66
+ type: "object" as const,
67
+ positional: true,
68
+ properties: {
69
+ name: {
70
+ type: "string",
71
+ description:
72
+ "The skill name to load (must match exactly or approximately)",
73
+ },
74
+ },
75
+ required: ["name"] as string[],
76
+ },
77
+ },
78
+ },
79
+ ];
@@ -3,7 +3,7 @@ import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import { spawn } from "child_process";
5
5
 
6
- interface StartAgentTaskParams {
6
+ export interface StartAgentTaskParams {
7
7
  messageId?: string;
8
8
  syncFs?: boolean;
9
9
  taskId?: string;
@@ -14,6 +14,8 @@ interface StartAgentTaskParams {
14
14
  agentName?: string;
15
15
  maxTimeLimit?: number;
16
16
  maxSpendLimit?: number;
17
+ /** When true, wait for the agent subprocess to exit before resolving (for generate pipelines). */
18
+ waitForCompletion?: boolean;
17
19
  }
18
20
 
19
21
  const PROCESSES_DIR = path.join(process.cwd(), ".knowhow", "processes");
@@ -63,6 +65,7 @@ export async function startAgentTask(params: StartAgentTaskParams): Promise<stri
63
65
  agentName,
64
66
  maxTimeLimit,
65
67
  maxSpendLimit,
68
+ waitForCompletion,
66
69
  } = params;
67
70
  if (!prompt) {
68
71
  throw new Error("prompt is required to create a chat task");
@@ -156,24 +159,25 @@ export async function startAgentTask(params: StartAgentTaskParams): Promise<stri
156
159
  `To check status, read: ${agentTaskDir}/status.txt\n`
157
160
  : "";
158
161
 
159
- // Give the agent 5 seconds to finish before detaching
160
- const detachTime = 5 * 1000;
161
- const tid = setTimeout(() => {
162
- try { child.unref(); } catch {}
163
- done(
164
- `Agent started (pid=${pid}), running in background.\n` +
165
- `Logs: ${logPath}\n` +
166
- syncFsNote
167
- );
168
- }, detachTime);
169
-
170
162
  child.once("exit", (code) => {
171
- clearTimeout(tid);
172
163
  done(
173
164
  `Agent finished with exit code ${code}.\nLogs: ${logPath}\n` +
174
165
  syncFsNote
175
166
  );
176
167
  });
168
+
169
+ if (!waitForCompletion) {
170
+ // Give the agent 5 seconds to finish before detaching (fire-and-forget mode)
171
+ const detachTime = 5 * 1000;
172
+ setTimeout(() => {
173
+ try { child.unref(); } catch {}
174
+ done(
175
+ `Agent started (pid=${pid}), running in background.\n` +
176
+ `Logs: ${logPath}\n` +
177
+ syncFsNote
178
+ );
179
+ }, detachTime);
180
+ }
177
181
  });
178
182
  }
179
183
 
package/src/ai.ts CHANGED
@@ -1,6 +1,13 @@
1
1
  import * as fs from "fs";
2
2
  import { readFile } from "./utils";
3
3
 
4
+ /** Options forwarded to startAgentTask when a generation source has an `agent` field */
5
+ export type AgentOptions = {
6
+ syncFs?: boolean;
7
+ taskId?: string;
8
+ maxTimeLimit?: number;
9
+ maxSpendLimit?: number;
10
+ };
4
11
  import OpenAI from "openai";
5
12
  import { Assistant } from "./types";
6
13
  import { convertToText } from "./conversion";
@@ -46,17 +53,24 @@ export function readPromptFile(promptFile: string, input: string) {
46
53
  return input;
47
54
  }
48
55
 
49
- export async function singlePrompt(userPrompt: string, model = "", agent = "") {
50
- const { Agents } = services();
56
+ export async function singlePrompt(
57
+ userPrompt: string,
58
+ model = "",
59
+ agent = "",
60
+ agentOptions?: AgentOptions
61
+ ) {
51
62
  if (agent) {
52
- const agentConfig = await Agents.getAgent(agent);
53
- if (!agentConfig) {
54
- throw new Error(`Agent ${agent} not found`);
55
- }
56
- if (model) {
57
- agentConfig.setModel(model);
58
- }
59
- return agentConfig.call(userPrompt);
63
+ // Route through the full AgentModule pipeline (renderer, sync-fs, limits).
64
+ // This ensures generate agent runs produce readable output and are observable
65
+ // via `knowhow agents list/tail/status`, matching `knowhow agent --input` behaviour.
66
+ const { startAgentTask } = await import("./agents/tools/startAgentTask");
67
+ return startAgentTask({
68
+ agentName: agent,
69
+ prompt: userPrompt,
70
+ model: model || undefined,
71
+ waitForCompletion: true,
72
+ ...(agentOptions ?? {}),
73
+ });
60
74
  }
61
75
 
62
76
  if (!model) {
@@ -100,6 +114,11 @@ function isContextLengthError(err: any): boolean {
100
114
  * Recursively summarize an array of texts using a split-and-summarize approach.
101
115
  * When the combined texts exceed the context window (either by estimate or actual API error),
102
116
  * split the array in half, summarize each half recursively, then combine.
117
+ *
118
+ * NOTE: when an `agent` is provided the recursive split path is skipped — agents
119
+ * can handle large inputs themselves (they can read files). Splitting would create
120
+ * multiple isolated agent runs each writing partial output. Instead the combined
121
+ * content is passed directly to the agent.
103
122
  */
104
123
  async function summarizeTextsRecursive(
105
124
  texts: string[],
@@ -107,7 +126,8 @@ async function summarizeTextsRecursive(
107
126
  model: string,
108
127
  agent: string,
109
128
  contextLimit: number,
110
- depth = 0
129
+ depth = 0,
130
+ agentOptions?: AgentOptions
111
131
  ): Promise<string> {
112
132
  const indent = " ".repeat(depth);
113
133
 
@@ -115,7 +135,7 @@ async function summarizeTextsRecursive(
115
135
  if (texts.length === 1) {
116
136
  const content = template.replaceAll("{text}", texts[0]);
117
137
  console.log(`${indent}summarizeTexts[depth=${depth}]: single text, ~${estimateTokens(content)} tokens`);
118
- return singlePrompt(content, model, agent);
138
+ return singlePrompt(content, model, agent, agentOptions);
119
139
  }
120
140
 
121
141
  // Check if combined fits in context window by estimate
@@ -127,7 +147,7 @@ async function summarizeTextsRecursive(
127
147
  // Try single combined prompt — if context error, fall through to split
128
148
  console.log(`${indent}summarizeTexts[depth=${depth}]: ${texts.length} texts, ~${estimatedTokens} tokens, trying combined`);
129
149
  try {
130
- return await singlePrompt(combinedContent, model, agent);
150
+ return await singlePrompt(combinedContent, model, agent, agentOptions);
131
151
  } catch (err: any) {
132
152
  if (!isContextLengthError(err)) throw err;
133
153
  console.log(`${indent}summarizeTexts[depth=${depth}]: API rejected (context too long), splitting in half`);
@@ -136,14 +156,22 @@ async function summarizeTextsRecursive(
136
156
  console.log(`${indent}summarizeTexts[depth=${depth}]: ${texts.length} texts, ~${estimatedTokens} tokens exceeds limit, splitting in half`);
137
157
  }
138
158
 
159
+ // When an agent is assigned, don't split — agents can handle large inputs and
160
+ // splitting would create multiple isolated agent runs that each write partial output.
161
+ // Instead, pass the combined content as-is even if it may exceed context limit.
162
+ if (agent) {
163
+ console.log(`${indent}summarizeTexts[depth=${depth}]: agent mode — skipping split, passing combined content to agent`);
164
+ return singlePrompt(combinedContent, model, agent, agentOptions);
165
+ }
166
+
139
167
  // Split texts in half and recurse
140
168
  const mid = Math.ceil(texts.length / 2);
141
169
  const left = texts.slice(0, mid);
142
170
  const right = texts.slice(mid);
143
171
 
144
172
  const [leftSummary, rightSummary] = await Promise.all([
145
- summarizeTextsRecursive(left, template, model, agent, contextLimit, depth + 1),
146
- summarizeTextsRecursive(right, template, model, agent, contextLimit, depth + 1),
173
+ summarizeTextsRecursive(left, template, model, agent, contextLimit, depth + 1, agentOptions),
174
+ summarizeTextsRecursive(right, template, model, agent, contextLimit, depth + 1, agentOptions),
147
175
  ]);
148
176
 
149
177
  // Combine the two halves with a final summary prompt
@@ -153,27 +181,28 @@ async function summarizeTextsRecursive(
153
181
  console.log(`${indent}summarizeTexts[depth=${depth}]: combining halves, ~${finalEstimate} tokens`);
154
182
 
155
183
  if (finalEstimate < contextLimit) {
156
- return singlePrompt(finalContent, model, agent);
184
+ return singlePrompt(finalContent, model, agent, agentOptions);
157
185
  }
158
186
 
159
187
  // If even the combined summaries are too long, recurse one more level
160
- return summarizeTextsRecursive([leftSummary, rightSummary], template, model, agent, contextLimit, depth + 1);
188
+ return summarizeTextsRecursive([leftSummary, rightSummary], template, model, agent, contextLimit, depth + 1, agentOptions);
161
189
  }
162
190
 
163
191
  export async function summarizeTexts(
164
192
  texts: string[],
165
193
  template: string,
166
194
  model = "",
167
- agent = ""
195
+ agent = "",
196
+ agentOptions?: AgentOptions
168
197
  ) {
169
198
  const effectiveModel = model || Models.openai.GPT_54_Nano;
170
199
  const contextLimit = getModelContextLimit(effectiveModel);
171
200
 
172
201
  console.log(
173
- `summarizeTexts: ${texts.length} text(s), context limit: ${contextLimit}, model: ${effectiveModel}`
202
+ `summarizeTexts: ${texts.length} text(s), context limit: ${contextLimit}, model: ${effectiveModel}${agent ? `, agent: ${agent}` : ""}`
174
203
  );
175
204
 
176
- return summarizeTextsRecursive(texts, template, model, agent, contextLimit).catch((err) => {
205
+ return summarizeTextsRecursive(texts, template, model, agent, contextLimit, 0, agentOptions).catch((err) => {
177
206
  return `Texts of combined length ${texts.reduce((a, t) => a + t.length, 0)} could not be summarized due to error: ${err.message}`;
178
207
  });
179
208
  }
@@ -193,23 +222,25 @@ export async function summarizeFiles(
193
222
  files: string[],
194
223
  template: string,
195
224
  model = "",
196
- agent = ""
225
+ agent = "",
226
+ agentOptions?: AgentOptions
197
227
  ) {
198
228
  const texts = [];
199
229
  for (const file of files) {
200
230
  const text = `file: ${file}\n` + (await convertToText(file));
201
231
  texts.push(text);
202
232
  }
203
- return summarizeTexts(texts, template, model, agent);
233
+ return summarizeTexts(texts, template, model, agent, agentOptions);
204
234
  }
205
235
 
206
236
  export async function summarizeFile(
207
237
  file: string,
208
238
  template: string,
209
239
  model = "",
210
- agent = ""
240
+ agent = "",
241
+ agentOptions?: AgentOptions
211
242
  ) {
212
- return await summarizeFiles([file], template, model, agent);
243
+ return await summarizeFiles([file], template, model, agent, agentOptions);
213
244
  }
214
245
 
215
246
  /*
@@ -336,12 +336,18 @@ export class CliChatService implements ChatService {
336
336
  return fullPrompt;
337
337
  }
338
338
 
339
- async startChatLoop(): Promise<void> {
339
+ async startChatLoop(initialCommand?: string): Promise<void> {
340
340
  // Display available commands like the original
341
341
  const availableCommands = this.getCommandsForActiveModes();
342
342
  const commandNames = availableCommands.map((cmd) => `/${cmd.name}`);
343
343
  console.log("Commands:", commandNames.join(", "));
344
344
 
345
+ // If an initial command was provided (e.g. from CLI), process it before
346
+ // entering the interactive loop so the user lands in the right state.
347
+ if (initialCommand) {
348
+ await this.processInput(initialCommand.trim());
349
+ }
350
+
345
351
  while (true) {
346
352
  // Recompute available commands each iteration so mode changes are reflected in autocomplete
347
353
  const currentCommandNames = this.getCommandsForActiveModes().map(