@tyvm/knowhow 0.0.108-dev.c7d102c → 0.0.108-dev.ed88cf4

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 (156) hide show
  1. package/package.json +2 -3
  2. package/src/agents/tools/index.ts +0 -1
  3. package/src/agents/tools/list.ts +0 -2
  4. package/src/chat/CliChatService.ts +10 -1
  5. package/src/chat/renderer/CompactRenderer.ts +20 -0
  6. package/src/chat/renderer/ConsoleRenderer.ts +19 -0
  7. package/src/chat/renderer/FancyRenderer.ts +19 -0
  8. package/src/chat/renderer/types.ts +11 -0
  9. package/src/cli.ts +91 -664
  10. package/src/clients/index.ts +6 -5
  11. package/src/commands/agent.ts +246 -0
  12. package/src/commands/misc.ts +174 -0
  13. package/src/commands/modules.ts +182 -0
  14. package/src/commands/services.ts +77 -0
  15. package/src/commands/workers.ts +160 -0
  16. package/src/config.ts +37 -0
  17. package/src/index.ts +1 -0
  18. package/src/logger.ts +197 -0
  19. package/src/plugins/plugins.ts +0 -21
  20. package/src/processors/JsonCompressor.ts +3 -3
  21. package/src/services/EventService.ts +61 -1
  22. package/src/services/modules/index.ts +70 -50
  23. package/src/services/modules/types.ts +4 -0
  24. package/src/tunnel.ts +216 -0
  25. package/src/types.ts +0 -1
  26. package/src/worker.ts +65 -336
  27. package/src/workers/auth/WsMiddleware.ts +99 -0
  28. package/src/workers/auth/authMiddleware.ts +104 -0
  29. package/src/workers/auth/types.ts +14 -2
  30. package/tests/unit/commands/github-credentials.test.ts +211 -0
  31. package/tests/unit/modules/moduleLoading.test.ts +39 -37
  32. package/tests/unit/plugins/pluginLoading.test.ts +0 -85
  33. package/ts_build/package.json +2 -3
  34. package/ts_build/src/agents/tools/index.d.ts +0 -1
  35. package/ts_build/src/agents/tools/index.js +0 -1
  36. package/ts_build/src/agents/tools/index.js.map +1 -1
  37. package/ts_build/src/agents/tools/list.js +0 -2
  38. package/ts_build/src/agents/tools/list.js.map +1 -1
  39. package/ts_build/src/chat/CliChatService.js +13 -1
  40. package/ts_build/src/chat/CliChatService.js.map +1 -1
  41. package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
  42. package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
  43. package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
  44. package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
  45. package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
  46. package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
  47. package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
  48. package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
  49. package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
  50. package/ts_build/src/chat/renderer/types.d.ts +2 -0
  51. package/ts_build/src/cli.js +47 -525
  52. package/ts_build/src/cli.js.map +1 -1
  53. package/ts_build/src/clients/index.js +2 -4
  54. package/ts_build/src/clients/index.js.map +1 -1
  55. package/ts_build/src/commands/agent.d.ts +6 -0
  56. package/ts_build/src/commands/agent.js +229 -0
  57. package/ts_build/src/commands/agent.js.map +1 -0
  58. package/ts_build/src/commands/misc.d.ts +10 -0
  59. package/ts_build/src/commands/misc.js +197 -0
  60. package/ts_build/src/commands/misc.js.map +1 -0
  61. package/ts_build/src/commands/modules.d.ts +3 -0
  62. package/ts_build/src/commands/modules.js +160 -0
  63. package/ts_build/src/commands/modules.js.map +1 -0
  64. package/ts_build/src/commands/services.d.ts +5 -0
  65. package/ts_build/src/commands/services.js +87 -0
  66. package/ts_build/src/commands/services.js.map +1 -0
  67. package/ts_build/src/commands/workers.d.ts +6 -0
  68. package/ts_build/src/commands/workers.js +163 -0
  69. package/ts_build/src/commands/workers.js.map +1 -0
  70. package/ts_build/src/config.d.ts +1 -0
  71. package/ts_build/src/config.js +32 -0
  72. package/ts_build/src/config.js.map +1 -1
  73. package/ts_build/src/index.d.ts +1 -0
  74. package/ts_build/src/index.js +3 -1
  75. package/ts_build/src/index.js.map +1 -1
  76. package/ts_build/src/logger.d.ts +21 -0
  77. package/ts_build/src/logger.js +106 -0
  78. package/ts_build/src/logger.js.map +1 -0
  79. package/ts_build/src/plugins/plugins.d.ts +0 -2
  80. package/ts_build/src/plugins/plugins.js +0 -11
  81. package/ts_build/src/plugins/plugins.js.map +1 -1
  82. package/ts_build/src/processors/JsonCompressor.js +1 -1
  83. package/ts_build/src/services/EventService.d.ts +6 -1
  84. package/ts_build/src/services/EventService.js +29 -0
  85. package/ts_build/src/services/EventService.js.map +1 -1
  86. package/ts_build/src/services/modules/index.d.ts +33 -0
  87. package/ts_build/src/services/modules/index.js +46 -45
  88. package/ts_build/src/services/modules/index.js.map +1 -1
  89. package/ts_build/src/services/modules/types.d.ts +4 -0
  90. package/ts_build/src/tunnel.d.ts +27 -0
  91. package/ts_build/src/tunnel.js +112 -0
  92. package/ts_build/src/tunnel.js.map +1 -0
  93. package/ts_build/src/types.d.ts +0 -1
  94. package/ts_build/src/types.js.map +1 -1
  95. package/ts_build/src/worker.d.ts +1 -4
  96. package/ts_build/src/worker.js +38 -244
  97. package/ts_build/src/worker.js.map +1 -1
  98. package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
  99. package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
  100. package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
  101. package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
  102. package/ts_build/src/workers/auth/authMiddleware.js +60 -0
  103. package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
  104. package/ts_build/src/workers/auth/types.d.ts +8 -1
  105. package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
  106. package/ts_build/tests/unit/commands/github-credentials.test.js +146 -0
  107. package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
  108. package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
  109. package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
  110. package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
  111. package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
  112. package/src/agents/tools/executeScript/README.md +0 -94
  113. package/src/agents/tools/executeScript/definition.ts +0 -79
  114. package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
  115. package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
  116. package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
  117. package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
  118. package/src/agents/tools/executeScript/index.ts +0 -98
  119. package/src/services/script-execution/SandboxContext.ts +0 -282
  120. package/src/services/script-execution/ScriptExecutor.ts +0 -441
  121. package/src/services/script-execution/ScriptPolicy.ts +0 -194
  122. package/src/services/script-execution/ScriptTracer.ts +0 -249
  123. package/src/services/script-execution/types.ts +0 -134
  124. package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
  125. package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
  126. package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
  127. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
  128. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
  129. package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
  130. package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
  131. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
  132. package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
  133. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
  134. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
  135. package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
  136. package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
  137. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
  138. package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
  139. package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
  140. package/ts_build/src/agents/tools/executeScript/index.js +0 -72
  141. package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
  142. package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
  143. package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
  144. package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
  145. package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
  146. package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
  147. package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
  148. package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
  149. package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
  150. package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
  151. package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
  152. package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
  153. package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
  154. package/ts_build/src/services/script-execution/types.d.ts +0 -108
  155. package/ts_build/src/services/script-execution/types.js +0 -3
  156. package/ts_build/src/services/script-execution/types.js.map +0 -1
@@ -0,0 +1,160 @@
1
+ import { Command } from "commander";
2
+ import { worker } from "../worker";
3
+ import { TUNNEL_MINIMAL_TOOLS } from "../tunnel";
4
+ import { fileSync } from "../fileSync";
5
+ import {
6
+ startAllWorkers,
7
+ listWorkerPaths,
8
+ unregisterWorkerPath,
9
+ clearWorkerRegistry,
10
+ } from "../workerRegistry";
11
+
12
+ export function addWorkerCommand(program: Command): void {
13
+ program
14
+ .command("worker")
15
+ .description(
16
+ "Start worker process and optionally register current directory"
17
+ )
18
+ .option("--register", "Register current directory as a worker path")
19
+ .option(
20
+ "--share",
21
+ "Share this worker with your organization (allows other users to use it)"
22
+ )
23
+ .option("--unshare", "Make this worker private (only you can use it)")
24
+ .option("--sandbox", "Run worker in a Docker container for isolation")
25
+ .option(
26
+ "--no-sandbox",
27
+ "Run worker directly on host (disable sandbox mode)"
28
+ )
29
+ .option("--passkey", "Set up passkey authentication for this worker")
30
+ .option("--passkey-reset", "Remove passkey authentication requirement")
31
+ .action(async (options) => {
32
+ const { setupServices } = await import("./services");
33
+ await setupServices();
34
+ await worker(options);
35
+ });
36
+ }
37
+
38
+ export function addWorkersCommand(program: Command): void {
39
+ program
40
+ .command("workers")
41
+ .description("Manage and start all registered workers")
42
+ .option("--list", "List all registered worker paths")
43
+ .option("--unregister <path>", "Unregister a worker path")
44
+ .option("--clear", "Clear all registered worker paths")
45
+ .action(async (options) => {
46
+ try {
47
+ if (options.list) {
48
+ const workers = await listWorkerPaths();
49
+ if (workers.length === 0) {
50
+ console.log("No workers registered.");
51
+ console.log(
52
+ "\nTo register a worker, run 'knowhow worker --register' from the worker directory."
53
+ );
54
+ } else {
55
+ console.log(`Registered workers (${workers.length}):`);
56
+ workers.forEach((workerPath, index) => {
57
+ console.log(` ${index + 1}. ${workerPath}`);
58
+ });
59
+ }
60
+ return;
61
+ }
62
+
63
+ if (options.unregister) {
64
+ await unregisterWorkerPath(options.unregister);
65
+ return;
66
+ }
67
+
68
+ if (options.clear) {
69
+ await clearWorkerRegistry();
70
+ return;
71
+ }
72
+
73
+ // Default action: start all workers
74
+ const { setupServices } = await import("./services");
75
+ await setupServices();
76
+ await startAllWorkers();
77
+ } catch (error) {
78
+ console.error("Error managing workers:", error);
79
+ process.exit(1);
80
+ }
81
+ });
82
+ }
83
+
84
+ export function addTunnelCommand(program: Command): void {
85
+ program
86
+ .command("tunnel")
87
+ .description(
88
+ "Start a minimal worker with tunnel enabled: exposes local ports to the cloud. " +
89
+ "Registers essential tools (unlock, lock, listAllowedPorts) so the backend is aware of the worker and ports. " +
90
+ "If passkey auth is configured, the tunnel is locked until unlocked via tool call or WebSocket auth protocol."
91
+ )
92
+ .option(
93
+ "--share",
94
+ "Share this tunnel with your organization (allows other users to use it)"
95
+ )
96
+ .option("--unshare", "Make this tunnel private (only you can use it)")
97
+ .action(async (options) => {
98
+ console.log("🌐 Starting tunnel (minimal worker) mode...");
99
+ console.log(` Tools: ${TUNNEL_MINIMAL_TOOLS.join(", ")}`);
100
+ await worker({
101
+ ...options,
102
+ allowedTools: TUNNEL_MINIMAL_TOOLS,
103
+ });
104
+ });
105
+ }
106
+
107
+ export function addFilesCommand(program: Command): void {
108
+ program
109
+ .command("files")
110
+ .description(
111
+ "Sync files between local filesystem and Knowhow FS (uses fileMounts config)"
112
+ )
113
+ .option("--upload", "Force upload direction for all mounts")
114
+ .option("--download", "Force download direction for all mounts")
115
+ .option("--config <path>", "Path to knowhow.json", "./knowhow.json")
116
+ .option("--dry-run", "Print what would be synced without doing it")
117
+ .action(async (options) => {
118
+ try {
119
+ await fileSync(options);
120
+ } catch (error) {
121
+ console.error("Error syncing files:", error);
122
+ process.exit(1);
123
+ }
124
+ });
125
+ }
126
+
127
+ export function addCloudWorkerCommand(program: Command): void {
128
+ program
129
+ .command("cloudworker")
130
+ .description("Create or sync a cloud worker with your local knowhow config")
131
+ .option(
132
+ "--create",
133
+ "Create a new cloud worker with synced config and files"
134
+ )
135
+ .option(
136
+ "--push <uid>",
137
+ "Push/sync local config and files to an existing cloud worker"
138
+ )
139
+ .option(
140
+ "--pull <id>",
141
+ "Pull the latest workerConfigJson from a cloud worker and update local config"
142
+ )
143
+ .option("--name <name>", "Name for the cloud worker (used with --create)")
144
+ .option("--dry-run", "Print what would be synced without doing it")
145
+ .action(async (options) => {
146
+ try {
147
+ const { cloudWorker, pullCloudWorkerConfig } = await import(
148
+ "../cloudWorker"
149
+ );
150
+ if (options.pull) {
151
+ await pullCloudWorkerConfig({ id: options.pull });
152
+ } else {
153
+ await cloudWorker(options);
154
+ }
155
+ } catch (error) {
156
+ console.error("Error running cloudworker:", error);
157
+ process.exit(1);
158
+ }
159
+ });
160
+ }
package/src/config.ts CHANGED
@@ -168,6 +168,27 @@ export async function init() {
168
168
  console.log("Initializing global knowhow config at ~/.knowhow");
169
169
  const globalConfigDir = await ensureGlobalConfigDir();
170
170
 
171
+ // Ensure the script module is registered in the global config so that
172
+ // `knowhow script` and the `executeScript` tool are available everywhere.
173
+ const SCRIPT_MODULE = "@tyvm/knowhow-module-script";
174
+ const globalConfigPath = path.join(globalConfigDir, "knowhow.json");
175
+ try {
176
+ const rawGlobal = fs.existsSync(globalConfigPath)
177
+ ? fs.readFileSync(globalConfigPath, "utf8")
178
+ : JSON.stringify(defaultConfig, null, 2);
179
+ const globalConf = JSON.parse(rawGlobal) as Config;
180
+ if (!globalConf.modules) {
181
+ globalConf.modules = [];
182
+ }
183
+ if (!globalConf.modules.includes(SCRIPT_MODULE)) {
184
+ globalConf.modules.push(SCRIPT_MODULE);
185
+ fs.writeFileSync(globalConfigPath, JSON.stringify(globalConf, null, 2));
186
+ console.log(`✅ Added ${SCRIPT_MODULE} to ~/.knowhow/knowhow.json modules`);
187
+ }
188
+ } catch (e) {
189
+ console.warn(`⚠ Could not update global config to add script module:`, e);
190
+ }
191
+
171
192
  // create the folder structure
172
193
  console.log("Initializing local knowhow config at ./.knowhow");
173
194
  await mkdir(".knowhow", { recursive: true });
@@ -287,6 +308,22 @@ export async function getGlobalConfig(): Promise<Config> {
287
308
  }
288
309
  }
289
310
 
311
+ export async function updateGlobalConfig(config: Config) {
312
+ if (!config || typeof config !== "object") {
313
+ throw new Error("Invalid config object");
314
+ }
315
+
316
+ const globalConfigDir = getGlobalConfigDir();
317
+ await mkdir(globalConfigDir, { recursive: true });
318
+ const globalConfigPath = path.join(globalConfigDir, "knowhow.json");
319
+
320
+ if (fs.existsSync(globalConfigPath)) {
321
+ await fs.promises.copyFile(globalConfigPath, globalConfigPath + ".bak");
322
+ }
323
+
324
+ await writeFile(globalConfigPath, JSON.stringify(config, null, 2));
325
+ }
326
+
290
327
  export async function migrateConfig() {
291
328
  // Apply migrations, used to keep config structure up to date.
292
329
  if (!fs.existsSync(".knowhow/knowhow.json")) {
package/src/index.ts CHANGED
@@ -53,6 +53,7 @@ export * as ai from "./ai";
53
53
 
54
54
  // Export module system types for external modules
55
55
  export * from "./services/modules/types";
56
+ export { ModulesService } from "./services/modules";
56
57
  // Export plugin types for external plugins
57
58
  export { PluginBase } from "./plugins/PluginBase";
58
59
  export { PluginMeta, Plugin, PluginContext } from "./plugins/types";
package/src/logger.ts ADDED
@@ -0,0 +1,197 @@
1
+ import type { LogLevel } from "./services/EventService";
2
+
3
+ /**
4
+ * App-wide logger utility.
5
+ *
6
+ * Features:
7
+ * 1. `logger.info/warn/error(source, message)` — routes through EventService
8
+ * 2. `Logger.of("ClassName")` — creates a bound logger so you don't repeat the source
9
+ * 3. `logger.installConsoleOverload()` — replaces console.log/warn/error/info with
10
+ * our closure, so ALL output (including third-party modules) goes through us
11
+ * 4. `logger.silence()` / `logger.unsilence()` — suppress all output, useful for
12
+ * commands that need clean stdout (e.g. github-credentials)
13
+ *
14
+ * Usage (module-level):
15
+ * import { logger } from "../logger";
16
+ * logger.info("MyService", "Something happened");
17
+ *
18
+ * Usage (class-level):
19
+ * import { Logger } from "../logger";
20
+ * class MyClass {
21
+ * private logger = Logger.of("MyClass");
22
+ * doThing() { this.logger.info("Something happened"); }
23
+ * }
24
+ *
25
+ * Silence mode (for clean-stdout commands):
26
+ * logger.silence(); // suppress everything
27
+ * // ... do work that must produce clean stdout ...
28
+ * logger.unsilence(); // restore
29
+ */
30
+
31
+ // ---- Internal state ---------------------------------------------------------
32
+
33
+ let silenced = false;
34
+
35
+ // Original console methods — saved before any overload is installed
36
+ const _originalConsole = {
37
+ log: console.log.bind(console),
38
+ warn: console.warn.bind(console),
39
+ error: console.error.bind(console),
40
+ info: console.info.bind(console),
41
+ };
42
+
43
+ let consoleOverloadInstalled = false;
44
+
45
+ // ---- EventService lazy accessor ---------------------------------------------
46
+
47
+ function getEvents() {
48
+ try {
49
+ const { services } = require("./services") as typeof import("./services");
50
+ return services().Events;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ // ---- Core emit logic --------------------------------------------------------
57
+
58
+ function emit(source: string, message: string, level: LogLevel): void {
59
+ if (silenced) return;
60
+
61
+ try {
62
+ const events = getEvents();
63
+ if (events) {
64
+ events.log(source, message, level);
65
+ return;
66
+ }
67
+ } catch {
68
+ // fall through to direct console output
69
+ }
70
+
71
+ // Fallback: use original console methods (bypasses any overload we installed)
72
+ const prefix = source ? `[${source}] ` : "";
73
+ if (level === "warn") _originalConsole.warn(`${prefix}${message}`);
74
+ else if (level === "error") _originalConsole.error(`${prefix}${message}`);
75
+ else _originalConsole.log(`${prefix}${message}`);
76
+ }
77
+
78
+ // ---- Bound logger (returned by Logger.of) -----------------------------------
79
+
80
+ export interface BoundLogger {
81
+ log(message: string, level?: LogLevel): void;
82
+ info(message: string): void;
83
+ warn(message: string): void;
84
+ error(message: string): void;
85
+ }
86
+
87
+ function makeBoundLogger(source: string): BoundLogger {
88
+ return {
89
+ log(message: string, level: LogLevel = "info"): void {
90
+ emit(source, message, level);
91
+ },
92
+ info(message: string): void {
93
+ emit(source, message, "info");
94
+ },
95
+ warn(message: string): void {
96
+ emit(source, message, "warn");
97
+ },
98
+ error(message: string): void {
99
+ emit(source, message, "error");
100
+ },
101
+ };
102
+ }
103
+
104
+ // ---- Public API -------------------------------------------------------------
105
+
106
+ export const logger = {
107
+ log(source: string, message: string, level: LogLevel = "info"): void {
108
+ emit(source, message, level);
109
+ },
110
+
111
+ info(source: string, message: string): void {
112
+ emit(source, message, "info");
113
+ },
114
+
115
+ warn(source: string, message: string): void {
116
+ emit(source, message, "warn");
117
+ },
118
+
119
+ error(source: string, message: string): void {
120
+ emit(source, message, "error");
121
+ },
122
+
123
+ /**
124
+ * Suppress all log output. Useful for commands that need clean stdout
125
+ * (e.g. git credential helpers). All logger.* calls and overloaded
126
+ * console.* calls become no-ops until unsilence() is called.
127
+ */
128
+ silence(): void {
129
+ silenced = true;
130
+ },
131
+
132
+ /**
133
+ * Restore log output after a silence() call.
134
+ */
135
+ unsilence(): void {
136
+ silenced = false;
137
+ },
138
+
139
+ /**
140
+ * Returns true if the logger is currently silenced.
141
+ */
142
+ isSilenced(): boolean {
143
+ return silenced;
144
+ },
145
+
146
+ /**
147
+ * Install console overload. After this call, console.log/warn/error/info
148
+ * all route through our closure (respecting silence mode).
149
+ * Safe to call multiple times — only installs once.
150
+ *
151
+ * Call this early in CLI startup (before any modules are loaded) to ensure
152
+ * third-party module logs don't bypass the silence mechanism.
153
+ */
154
+ installConsoleOverload(): void {
155
+ if (consoleOverloadInstalled) return;
156
+ consoleOverloadInstalled = true;
157
+
158
+ const route = (originalFn: (...args: any[]) => void, args: any[]) => {
159
+ if (silenced) return;
160
+ originalFn(...args);
161
+ };
162
+
163
+ console.log = (...args: any[]) => route(_originalConsole.log, args);
164
+ console.info = (...args: any[]) => route(_originalConsole.info, args);
165
+ console.warn = (...args: any[]) => route(_originalConsole.warn, args);
166
+ // Note: console.error is intentionally NOT overloaded — real errors (stack
167
+ // traces, crash reports) should always be visible. Only suppress via silence().
168
+ // If you want to suppress errors too, call logger.silence() which checks the flag
169
+ // before the overloaded console.warn/log routes reach here anyway.
170
+ },
171
+
172
+ /**
173
+ * Remove the console overload and restore original console methods.
174
+ */
175
+ uninstallConsoleOverload(): void {
176
+ if (!consoleOverloadInstalled) return;
177
+ console.log = _originalConsole.log;
178
+ console.info = _originalConsole.info;
179
+ console.warn = _originalConsole.warn;
180
+ consoleOverloadInstalled = false;
181
+ },
182
+ };
183
+
184
+ /**
185
+ * Factory for creating a bound logger with a fixed source name.
186
+ * Ideal for class-level loggers:
187
+ *
188
+ * class MyClass {
189
+ * private logger = Logger.of("MyClass");
190
+ * doThing() { this.logger.info("hello"); }
191
+ * }
192
+ */
193
+ export const Logger = {
194
+ of(source: string): BoundLogger {
195
+ return makeBoundLogger(source);
196
+ },
197
+ };
@@ -1,5 +1,4 @@
1
1
  import { Plugin, PluginContext } from "./types";
2
- import { Config } from "../types";
3
2
  import { VimPlugin } from "./vim";
4
3
  import { LinterPlugin } from "./LinterPlugin";
5
4
  import { LanguagePlugin } from "./language";
@@ -49,26 +48,6 @@ export class PluginService {
49
48
  return instance.meta.key;
50
49
  }
51
50
 
52
- /**
53
- * Load plugins from config's pluginPackages map.
54
- * Each entry maps a plugin key to an npm package name or file path.
55
- * Errors are caught and logged as warnings without crashing.
56
- */
57
- async loadPluginsFromConfig(config: Config): Promise<void> {
58
- const pluginPackages = config.pluginPackages || {};
59
- for (const [key, spec] of Object.entries(pluginPackages)) {
60
- try {
61
- await this.loadPlugin(spec);
62
- } catch (error) {
63
- this.events?.log(
64
- "PluginService",
65
- `Failed to load plugin "${key}" from "${spec}": ${error instanceof Error ? error.message : error}`,
66
- "warn"
67
- );
68
- }
69
- }
70
- }
71
-
72
51
  /** Disable a plugin by its key; returns `true` if found. */
73
52
  disablePlugin(key: string): boolean {
74
53
  const p = this.pluginMap.get(key);
@@ -415,11 +415,11 @@ export class JsonCompressor {
415
415
 
416
416
  // Store objects on FIRST occurrence so second occurrence can reference it
417
417
  // We increment seenCount above, so after increment:
418
- // seenCount=1: first occurrence (just incremented from 0 to 1), store it
419
- // seenCount>=2: we already stored it on first occurrence, should be in dedup map
418
+ // seenCount=0: first occurrence (before increment), store it
419
+ // seenCount>=1: we already stored it on first occurrence, should be in dedup map
420
420
  // Note: This means we store proactively - first occurrence gets stored AND returned in full
421
421
  // Second+ occurrences will find it in the dedup map and return a reference
422
- const isFirstOccurrence = seenCount === 1;
422
+ const isFirstOccurrence = seenCount === 0;
423
423
 
424
424
  // Process the object - apply low-signal detection
425
425
  const objWithLowSignalCompressed = this.compressObjectWithLowSignalDetection(obj, path);
@@ -1,6 +1,8 @@
1
1
  import { EventEmitter } from "events";
2
2
  import { IAgent } from "../agents/interface";
3
3
 
4
+ export type LogLevel = "info" | "warn" | "error";
5
+
4
6
  export type EventHandlerFn = (...args: any[]) => any;
5
7
 
6
8
  export interface EventHandler {
@@ -31,9 +33,38 @@ type ManagedListenerRecord = {
31
33
  blocking: boolean;
32
34
  };
33
35
 
36
+ /**
37
+ * Default console handler for plugin:log events.
38
+ * Active when no renderer has taken over (e.g. worker mode, CLI before chat starts).
39
+ * Can be suppressed by calling suppressDefaultLogger() when a renderer is active.
40
+ *
41
+ * IMPORTANT: Uses process.stdout/stderr directly to avoid infinite recursion
42
+ * with logger.installConsoleOverload() which overrides console.log/warn.
43
+ */
44
+ function defaultConsoleLogHandler(event: {
45
+ source: string;
46
+ message: string;
47
+ level: LogLevel;
48
+ }): void {
49
+ const prefix = event.source ? `[${event.source}] ` : "";
50
+ const line = `${prefix}${event.message}\n`;
51
+ switch (event.level) {
52
+ case "warn":
53
+ process.stderr.write(line);
54
+ break;
55
+ case "error":
56
+ process.stderr.write(line);
57
+ break;
58
+ default:
59
+ process.stdout.write(line);
60
+ }
61
+ }
62
+
34
63
  export class EventService extends EventEmitter {
35
64
  private blockingHandlers: Map<string, EventHandler[]> = new Map();
36
65
  private managedListeners: Map<string, ManagedListenerRecord> = new Map();
66
+ private defaultLoggerActive = true;
67
+ private boundDefaultLogHandler = defaultConsoleLogHandler;
37
68
 
38
69
  eventTypes = {
39
70
  agentMsg: "agent:msg",
@@ -45,6 +76,35 @@ export class EventService extends EventEmitter {
45
76
  constructor() {
46
77
  super();
47
78
  this.setMaxListeners(100);
79
+ // Register the default console logger so Events.log() always produces output
80
+ // even before a renderer is attached (worker mode, module loading, etc.)
81
+ this.on(this.eventTypes.pluginLog, this.boundDefaultLogHandler);
82
+ }
83
+
84
+ /**
85
+ * Suppress the default console logger.
86
+ * Call this when a renderer has taken over and will handle plugin:log events.
87
+ * This prevents double-printing when both the renderer and the default handler fire.
88
+ */
89
+ suppressDefaultLogger(): void {
90
+ if (this.defaultLoggerActive) {
91
+ this.removeListener(
92
+ this.eventTypes.pluginLog,
93
+ this.boundDefaultLogHandler
94
+ );
95
+ this.defaultLoggerActive = false;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Restore the default console logger.
101
+ * Call this when the renderer is torn down.
102
+ */
103
+ restoreDefaultLogger(): void {
104
+ if (!this.defaultLoggerActive) {
105
+ this.on(this.eventTypes.pluginLog, this.boundDefaultLogHandler);
106
+ this.defaultLoggerActive = true;
107
+ }
48
108
  }
49
109
 
50
110
  /**
@@ -232,7 +292,7 @@ export class EventService extends EventEmitter {
232
292
  log(
233
293
  source: string,
234
294
  message: string,
235
- level: "info" | "warn" | "error" = "info"
295
+ level: LogLevel = "info"
236
296
  ): void {
237
297
  this.emit(this.eventTypes.pluginLog, {
238
298
  source,