@tyvm/knowhow 0.0.138 → 0.0.141
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/autodoc/chat-guide.md +1 -0
- package/package.json +5 -2
- package/scripts/build-for-node.sh +4 -4
- package/src/agents/base/base.ts +254 -29
- package/src/agents/patcher/patcher.ts +1 -1
- package/src/agents/researcher/researcher.ts +2 -2
- package/src/agents/tools/agentCall.ts +6 -0
- package/src/agents/tools/connectAgent.ts +353 -0
- package/src/agents/tools/index.ts +6 -0
- package/src/agents/tools/list.ts +48 -0
- package/src/agents/tools/observe.ts +245 -0
- package/src/agents/tools/replyToParent.ts +122 -0
- package/src/agents/tools/runGenerate.ts +209 -0
- package/src/agents/tools/sendAgentMessage.ts +138 -0
- package/src/agents/tools/startAgentTask.ts +92 -5
- package/src/agents/tools/waitForAgentCompleted.ts +269 -0
- package/src/ai.ts +1 -0
- package/src/chat/modules/AgentModule.ts +89 -27
- package/src/chat/modules/SessionsModule.ts +6 -10
- package/src/clients/contextLimits.ts +26 -0
- package/src/clients/pricing/anthropic.ts +12 -0
- package/src/commands/agent.ts +22 -0
- package/src/commands/agents.ts +83 -0
- package/src/commands/misc.ts +6 -0
- package/src/commands/modules.ts +159 -7
- package/src/commands/services.ts +14 -0
- package/src/generate.ts +25 -3
- package/src/services/AgentService.ts +21 -3
- package/src/services/AgentSyncFs.ts +56 -0
- package/src/services/AgentSyncKnowhowWeb.ts +1 -1
- package/src/services/EventService.ts +4 -1
- package/src/services/KnowhowClient.ts +7 -0
- package/src/services/LazyToolsService.ts +7 -3
- package/src/services/MinimalToolsService.ts +15 -7
- package/src/services/SyncedAgentWatcher.ts +2 -0
- package/src/services/SyncerService.ts +35 -6
- package/src/services/Tools.ts +43 -3
- package/src/services/modules/index.ts +127 -46
- package/src/types.ts +5 -0
- package/tests/agents/interrupt.test.ts +11 -18
- package/tests/agents/observe-base.test.ts +183 -0
- package/tests/agents/tools/connectAgent.test.ts +138 -0
- package/tests/agents/tools/observe.test.ts +179 -0
- package/tests/agents/tools/replyToParent.test.ts +125 -0
- package/tests/agents/tools/sendAgentMessage.test.ts +99 -0
- package/tests/agents/tools/waitForAgentCompleted.test.ts +147 -0
- package/tests/chat/detach-attach-rendering.test.ts +315 -0
- package/tests/clients/contextLimits.test.ts +23 -1
- package/tests/clients/pricing.test.ts +20 -1
- package/tests/services/Tools.test.ts +172 -9
- package/tests/unit/modules/moduleLoading.test.ts +69 -0
- package/ts_build/package.json +5 -2
- package/ts_build/src/agents/base/base.d.ts +24 -1
- package/ts_build/src/agents/base/base.js +111 -16
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/patcher/patcher.js +1 -1
- package/ts_build/src/agents/patcher/patcher.js.map +1 -1
- package/ts_build/src/agents/researcher/researcher.js +2 -2
- package/ts_build/src/agents/researcher/researcher.js.map +1 -1
- package/ts_build/src/agents/tools/agentCall.js.map +1 -1
- package/ts_build/src/agents/tools/connectAgent.d.ts +21 -0
- package/ts_build/src/agents/tools/connectAgent.js +284 -0
- package/ts_build/src/agents/tools/connectAgent.js.map +1 -0
- package/ts_build/src/agents/tools/index.d.ts +6 -0
- package/ts_build/src/agents/tools/index.js +6 -0
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +40 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/observe.d.ts +20 -0
- package/ts_build/src/agents/tools/observe.js +144 -0
- package/ts_build/src/agents/tools/observe.js.map +1 -0
- package/ts_build/src/agents/tools/replyToParent.d.ts +4 -0
- package/ts_build/src/agents/tools/replyToParent.js +118 -0
- package/ts_build/src/agents/tools/replyToParent.js.map +1 -0
- package/ts_build/src/agents/tools/runGenerate.d.ts +19 -0
- package/ts_build/src/agents/tools/runGenerate.js +138 -0
- package/ts_build/src/agents/tools/runGenerate.js.map +1 -0
- package/ts_build/src/agents/tools/sendAgentMessage.d.ts +11 -0
- package/ts_build/src/agents/tools/sendAgentMessage.js +118 -0
- package/ts_build/src/agents/tools/sendAgentMessage.js.map +1 -0
- package/ts_build/src/agents/tools/startAgentTask.d.ts +7 -0
- package/ts_build/src/agents/tools/startAgentTask.js +53 -6
- package/ts_build/src/agents/tools/startAgentTask.js.map +1 -1
- package/ts_build/src/agents/tools/waitForAgentCompleted.d.ts +10 -0
- package/ts_build/src/agents/tools/waitForAgentCompleted.js +229 -0
- package/ts_build/src/agents/tools/waitForAgentCompleted.js.map +1 -0
- package/ts_build/src/ai.d.ts +1 -0
- package/ts_build/src/ai.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +3 -1
- package/ts_build/src/chat/modules/AgentModule.js +46 -21
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +1 -6
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/clients/contextLimits.js +21 -0
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/pricing/anthropic.d.ts +2 -0
- package/ts_build/src/clients/pricing/anthropic.js +9 -0
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/commands/agent.js +11 -0
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/agents.js +61 -0
- package/ts_build/src/commands/agents.js.map +1 -1
- package/ts_build/src/commands/misc.js +2 -0
- package/ts_build/src/commands/misc.js.map +1 -1
- package/ts_build/src/commands/modules.js +86 -7
- package/ts_build/src/commands/modules.js.map +1 -1
- package/ts_build/src/commands/services.js +10 -0
- package/ts_build/src/commands/services.js.map +1 -1
- package/ts_build/src/generate.d.ts +2 -0
- package/ts_build/src/generate.js +11 -3
- package/ts_build/src/generate.js.map +1 -1
- package/ts_build/src/services/AgentService.d.ts +4 -1
- package/ts_build/src/services/AgentService.js +9 -2
- package/ts_build/src/services/AgentService.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +2 -0
- package/ts_build/src/services/AgentSyncFs.js +35 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/AgentSyncKnowhowWeb.d.ts +1 -1
- package/ts_build/src/services/EventService.d.ts +4 -1
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.js +3 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/LazyToolsService.d.ts +2 -2
- package/ts_build/src/services/LazyToolsService.js +2 -2
- package/ts_build/src/services/LazyToolsService.js.map +1 -1
- package/ts_build/src/services/MinimalToolsService.d.ts +2 -2
- package/ts_build/src/services/MinimalToolsService.js +6 -6
- package/ts_build/src/services/MinimalToolsService.js.map +1 -1
- package/ts_build/src/services/SyncedAgentWatcher.d.ts +1 -0
- package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
- package/ts_build/src/services/SyncerService.d.ts +2 -1
- package/ts_build/src/services/SyncerService.js +8 -2
- package/ts_build/src/services/SyncerService.js.map +1 -1
- package/ts_build/src/services/Tools.d.ts +6 -1
- package/ts_build/src/services/Tools.js +9 -3
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +1 -1
- package/ts_build/src/services/modules/index.js +67 -27
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/types.d.ts +3 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/tests/agents/interrupt.test.d.ts +0 -1
- package/ts_build/tests/agents/interrupt.test.js +3 -13
- package/ts_build/tests/agents/interrupt.test.js.map +1 -1
- package/ts_build/tests/agents/observe-base.test.d.ts +1 -0
- package/ts_build/tests/agents/observe-base.test.js +149 -0
- package/ts_build/tests/agents/observe-base.test.js.map +1 -0
- package/ts_build/tests/agents/tools/connectAgent.test.d.ts +1 -0
- package/ts_build/tests/agents/tools/connectAgent.test.js +142 -0
- package/ts_build/tests/agents/tools/connectAgent.test.js.map +1 -0
- package/ts_build/tests/agents/tools/observe.test.d.ts +1 -0
- package/ts_build/tests/agents/tools/observe.test.js +148 -0
- package/ts_build/tests/agents/tools/observe.test.js.map +1 -0
- package/ts_build/tests/agents/tools/replyToParent.test.d.ts +1 -0
- package/ts_build/tests/agents/tools/replyToParent.test.js +116 -0
- package/ts_build/tests/agents/tools/replyToParent.test.js.map +1 -0
- package/ts_build/tests/agents/tools/sendAgentMessage.test.d.ts +1 -0
- package/ts_build/tests/agents/tools/sendAgentMessage.test.js +112 -0
- package/ts_build/tests/agents/tools/sendAgentMessage.test.js.map +1 -0
- package/ts_build/tests/agents/tools/waitForAgentCompleted.test.d.ts +1 -0
- package/ts_build/tests/agents/tools/waitForAgentCompleted.test.js +147 -0
- package/ts_build/tests/agents/tools/waitForAgentCompleted.test.js.map +1 -0
- package/ts_build/tests/chat/detach-attach-rendering.test.d.ts +1 -0
- package/ts_build/tests/chat/detach-attach-rendering.test.js +168 -0
- package/ts_build/tests/chat/detach-attach-rendering.test.js.map +1 -0
- package/ts_build/tests/clients/contextLimits.test.js +11 -1
- package/ts_build/tests/clients/contextLimits.test.js.map +1 -1
- package/ts_build/tests/clients/pricing.test.js +10 -1
- package/ts_build/tests/clients/pricing.test.js.map +1 -1
- package/ts_build/tests/services/Tools.test.js +126 -8
- package/ts_build/tests/services/Tools.test.js.map +1 -1
- package/ts_build/tests/unit/modules/moduleLoading.test.js +84 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
package/src/commands/agents.ts
CHANGED
|
@@ -493,6 +493,76 @@ async function cmdAttach(taskIdOrIndex: string | undefined, opts: { index?: stri
|
|
|
493
493
|
await startChat(`/attach ${resolvedId}`);
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
+
/**
|
|
497
|
+
* Print the final answer (the result of the agent's finalAnswer tool call) for
|
|
498
|
+
* a completed task. Reads `metadata.result`, falling back to the last assistant
|
|
499
|
+
* message in the thread if `result` isn't present. This lets a human read what
|
|
500
|
+
* a finished agent concluded with, even if they never attached before it ended.
|
|
501
|
+
*/
|
|
502
|
+
async function cmdAnswer(
|
|
503
|
+
taskIdOrIndex: string | undefined,
|
|
504
|
+
opts: { index?: string; raw: boolean }
|
|
505
|
+
): Promise<void> {
|
|
506
|
+
const value = opts.index ?? taskIdOrIndex;
|
|
507
|
+
if (!value) {
|
|
508
|
+
console.error("Provide a task ID or use -i <number> to select by index.");
|
|
509
|
+
process.exit(1);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const resolvedId = resolveTaskId(value, true);
|
|
513
|
+
if (!resolvedId) {
|
|
514
|
+
console.error(`Task not found: ${value}`);
|
|
515
|
+
const rows = buildRows(true);
|
|
516
|
+
if (rows.length > 0) {
|
|
517
|
+
console.error("\nAvailable tasks:");
|
|
518
|
+
rows.forEach((r, i) => console.error(` ${i + 1}. ${r.displayId}`));
|
|
519
|
+
}
|
|
520
|
+
process.exit(1);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const meta = loadMeta(resolvedId);
|
|
525
|
+
if (!meta) {
|
|
526
|
+
console.error(`No metadata found for task: ${resolvedId}`);
|
|
527
|
+
process.exit(1);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Prefer the stored finalAnswer result; fall back to the last assistant message.
|
|
532
|
+
let answer: string | undefined =
|
|
533
|
+
typeof meta.result === "string" && meta.result.trim()
|
|
534
|
+
? meta.result
|
|
535
|
+
: undefined;
|
|
536
|
+
|
|
537
|
+
if (!answer) {
|
|
538
|
+
const threads: any[][] = meta.threads ?? [];
|
|
539
|
+
if (threads.length > 0) {
|
|
540
|
+
answer = lastAssistantMessage(threads[threads.length - 1]);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (opts.raw) {
|
|
545
|
+
console.log(answer ?? "");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const status = getStatus(resolvedId, meta);
|
|
550
|
+
if (!answer) {
|
|
551
|
+
console.log(`\n📭 No final answer available for task: ${resolvedId}`);
|
|
552
|
+
console.log(` Status: ${status}`);
|
|
553
|
+
if (status !== "completed") {
|
|
554
|
+
console.log(` The agent hasn't finished yet — attach with 'knowhow agents attach' to follow it.`);
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
console.log(`\n💬 Final answer for task: ${resolvedId}`);
|
|
560
|
+
console.log(` Agent: ${meta.agentName ?? "?"} | Status: ${status} | Cost: ${meta.totalCostUsd != null ? "$" + (meta.totalCostUsd as number).toFixed(4) : "?"}`);
|
|
561
|
+
console.log("─".repeat(80));
|
|
562
|
+
console.log(answer);
|
|
563
|
+
console.log("─".repeat(80) + "\n");
|
|
564
|
+
}
|
|
565
|
+
|
|
496
566
|
|
|
497
567
|
|
|
498
568
|
// ---------------------------------------------------------------------------
|
|
@@ -553,4 +623,17 @@ export function addAgentsCommand(program: Command): void {
|
|
|
553
623
|
.action(async (taskId: string | undefined, opts: { index?: string }) => {
|
|
554
624
|
await cmdAttach(taskId, opts);
|
|
555
625
|
});
|
|
626
|
+
|
|
627
|
+
agents
|
|
628
|
+
.command("answer [taskId]")
|
|
629
|
+
.description(
|
|
630
|
+
"Print the final answer (finalAnswer result) of a completed agent task, formatted for reading. " +
|
|
631
|
+
"Reads metadata.result, falling back to the last assistant message. " +
|
|
632
|
+
"Accepts a full task ID, partial substring, or -i <number> for row index from 'agents list'."
|
|
633
|
+
)
|
|
634
|
+
.option("-i, --index <number>", "Select task by row number from 'agents list'")
|
|
635
|
+
.option("--raw", "Print only the raw answer text (no header/formatting)", false)
|
|
636
|
+
.action(async (taskId: string | undefined, opts: { index?: string; raw: boolean }) => {
|
|
637
|
+
await cmdAnswer(taskId, opts);
|
|
638
|
+
});
|
|
556
639
|
}
|
package/src/commands/misc.ts
CHANGED
|
@@ -66,6 +66,10 @@ export function addGenerateCommand(program: Command): void {
|
|
|
66
66
|
"--sync-fs",
|
|
67
67
|
"Enable filesystem sync for agent-driven sources (creates .knowhow/processes/agents/<taskId>/ so tasks appear in `knowhow agents list`)"
|
|
68
68
|
)
|
|
69
|
+
.option(
|
|
70
|
+
"--sync-remote",
|
|
71
|
+
"Push agent-driven sources' work to a remote Knowhow task (identified by the source's taskId)"
|
|
72
|
+
)
|
|
69
73
|
.option(
|
|
70
74
|
"--force",
|
|
71
75
|
"Skip hash checks and regenerate all matching sources regardless of whether inputs/outputs have changed"
|
|
@@ -83,6 +87,7 @@ export function addGenerateCommand(program: Command): void {
|
|
|
83
87
|
name?: string;
|
|
84
88
|
filter?: string;
|
|
85
89
|
syncFs?: boolean;
|
|
90
|
+
syncRemote?: boolean;
|
|
86
91
|
force?: boolean;
|
|
87
92
|
concurrency?: number;
|
|
88
93
|
plan?: boolean;
|
|
@@ -99,6 +104,7 @@ export function addGenerateCommand(program: Command): void {
|
|
|
99
104
|
name: options.name,
|
|
100
105
|
filter: options.filter,
|
|
101
106
|
syncFs: options.syncFs,
|
|
107
|
+
syncRemote: options.syncRemote,
|
|
102
108
|
force: options.force,
|
|
103
109
|
concurrency: options.concurrency,
|
|
104
110
|
});
|
package/src/commands/modules.ts
CHANGED
|
@@ -39,15 +39,146 @@ function ensureKnowhowPackageJson(knowhowDir: string): void {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Options that control how a module is installed via npm.
|
|
44
|
+
*/
|
|
45
|
+
interface InstallOptions {
|
|
46
|
+
/**
|
|
47
|
+
* When true, force npm to run the module's install/build lifecycle scripts
|
|
48
|
+
* (preinstall/install/postinstall) in the foreground.
|
|
49
|
+
*
|
|
50
|
+
* This matters for native modules like `isolated-vm`, whose `install` script
|
|
51
|
+
* (`node-gyp-build || node-gyp rebuild`) selects/copies the correct prebuilt
|
|
52
|
+
* binary (or compiles from source). npm 11 and some org/CI policies block
|
|
53
|
+
* install scripts by default, which leaves native deps in a broken/mismatched
|
|
54
|
+
* state that later aborts the process at require-time (e.g. the
|
|
55
|
+
* `IsolateSpecificSize` assertion). Passing this flag ensures those scripts
|
|
56
|
+
* actually run so the right binary is in place.
|
|
57
|
+
*/
|
|
58
|
+
allowScripts?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
42
61
|
/**
|
|
43
62
|
* Run `npm install --prefix <knowhowDir> <mod>` so that modules land in
|
|
44
63
|
* .knowhow/node_modules rather than the project's node_modules.
|
|
64
|
+
*
|
|
65
|
+
* When `allowScripts` is set, we explicitly force install lifecycle scripts to
|
|
66
|
+
* run (`--foreground-scripts --ignore-scripts=false`) so native modules build
|
|
67
|
+
* correctly even under npm 11 / restrictive org policies that skip scripts by
|
|
68
|
+
* default.
|
|
45
69
|
*/
|
|
46
|
-
function npmInstallToKnowhow(
|
|
47
|
-
|
|
70
|
+
function npmInstallToKnowhow(
|
|
71
|
+
mod: string,
|
|
72
|
+
knowhowDir: string,
|
|
73
|
+
options: InstallOptions = {}
|
|
74
|
+
): void {
|
|
75
|
+
const scriptFlags = options.allowScripts
|
|
76
|
+
? " --foreground-scripts --ignore-scripts=false"
|
|
77
|
+
: "";
|
|
78
|
+
execSync(`npm install --prefix "${knowhowDir}" ${mod}${scriptFlags}`, {
|
|
48
79
|
stdio: "inherit",
|
|
49
80
|
encoding: "utf-8",
|
|
50
81
|
});
|
|
82
|
+
|
|
83
|
+
// When scripts weren't explicitly allowed, npm 11+ (and restrictive org/CI
|
|
84
|
+
// policies) silently *skip* package install/build scripts. For native addons
|
|
85
|
+
// like isolated-vm this leaves the package present but its native binary
|
|
86
|
+
// unbuilt — which later aborts at runtime with a cryptic error. Detect that
|
|
87
|
+
// situation now and tell the user exactly how to fix it (re-run with
|
|
88
|
+
// --allow-scripts) rather than letting them hit the runtime crash.
|
|
89
|
+
if (!options.allowScripts) {
|
|
90
|
+
warnIfInstallScriptsSkipped(knowhowDir);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Names of lifecycle scripts that run during `npm install` and typically build
|
|
96
|
+
* native code. If a freshly-installed package declares any of these AND we
|
|
97
|
+
* installed without allowing scripts, they were skipped.
|
|
98
|
+
*/
|
|
99
|
+
const INSTALL_LIFECYCLE_SCRIPTS = ["preinstall", "install", "postinstall"];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Walk the installed dependency tree under <knowhowDir>/node_modules and return
|
|
103
|
+
* the names of packages that declare native/build install-lifecycle scripts.
|
|
104
|
+
*
|
|
105
|
+
* This reads the *actually installed* package.json files (rather than querying
|
|
106
|
+
* the registry) so it works offline, reflects the real resolved tree, and
|
|
107
|
+
* catches transitive dependencies (e.g. isolated-vm pulled in by a module).
|
|
108
|
+
*/
|
|
109
|
+
function findPackagesWithInstallScripts(knowhowDir: string): string[] {
|
|
110
|
+
const nodeModules = path.join(knowhowDir, "node_modules");
|
|
111
|
+
const found = new Set<string>();
|
|
112
|
+
|
|
113
|
+
const readPkgScripts = (pkgDir: string): void => {
|
|
114
|
+
const pkgJsonPath = path.join(pkgDir, "package.json");
|
|
115
|
+
if (!fs.existsSync(pkgJsonPath)) return;
|
|
116
|
+
try {
|
|
117
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
118
|
+
const scripts = pkg.scripts ?? {};
|
|
119
|
+
const hasInstallScript = INSTALL_LIFECYCLE_SCRIPTS.some(
|
|
120
|
+
(s) => typeof scripts[s] === "string" && scripts[s].trim().length > 0
|
|
121
|
+
);
|
|
122
|
+
if (hasInstallScript && pkg.name) {
|
|
123
|
+
found.add(pkg.name);
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
// ignore malformed package.json
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const scanDir = (dir: string): void => {
|
|
131
|
+
if (!fs.existsSync(dir)) return;
|
|
132
|
+
let entries: fs.Dirent[] = [];
|
|
133
|
+
try {
|
|
134
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
135
|
+
} catch {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
if (!entry.isDirectory()) continue;
|
|
140
|
+
if (entry.name === ".bin" || entry.name === ".cache") continue;
|
|
141
|
+
const full = path.join(dir, entry.name);
|
|
142
|
+
if (entry.name.startsWith("@")) {
|
|
143
|
+
// scope directory — recurse one level into the scoped packages
|
|
144
|
+
scanDir(full);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
readPkgScripts(full);
|
|
148
|
+
// Recurse into nested node_modules for hoisting edge cases.
|
|
149
|
+
const nested = path.join(full, "node_modules");
|
|
150
|
+
if (fs.existsSync(nested)) scanDir(nested);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
scanDir(nodeModules);
|
|
155
|
+
return Array.from(found).sort();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check whether the just-installed tree contains packages with install scripts
|
|
160
|
+
* (that were skipped because --allow-scripts wasn't passed) and, if so, print a
|
|
161
|
+
* clear warning telling the user to re-run with --allow-scripts.
|
|
162
|
+
*/
|
|
163
|
+
function warnIfInstallScriptsSkipped(knowhowDir: string): void {
|
|
164
|
+
let pkgsWithScripts: string[] = [];
|
|
165
|
+
try {
|
|
166
|
+
pkgsWithScripts = findPackagesWithInstallScripts(knowhowDir);
|
|
167
|
+
} catch {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (pkgsWithScripts.length === 0) return;
|
|
171
|
+
|
|
172
|
+
console.warn(
|
|
173
|
+
"\n⚠️ Some installed packages have native install/build scripts that may have\n" +
|
|
174
|
+
" been skipped (npm 11+ blocks install scripts by default):\n" +
|
|
175
|
+
pkgsWithScripts.map((p) => ` • ${p}`).join("\n") +
|
|
176
|
+
"\n\n If a module fails to load at runtime (e.g. isolated-vm), re-run the\n" +
|
|
177
|
+
" install with --allow-scripts so these build steps can run:\n\n" +
|
|
178
|
+
" knowhow modules install <module> --allow-scripts\n" +
|
|
179
|
+
" # or\n" +
|
|
180
|
+
" knowhow modules setup --allow-scripts\n"
|
|
181
|
+
);
|
|
51
182
|
}
|
|
52
183
|
|
|
53
184
|
/**
|
|
@@ -289,9 +420,14 @@ export function addModulesCommand(program: Command): void {
|
|
|
289
420
|
"Add default built-in modules to your config and install them into .knowhow/node_modules"
|
|
290
421
|
)
|
|
291
422
|
.option("--global", "Use the global config (~/.knowhow/knowhow.json)")
|
|
423
|
+
.option(
|
|
424
|
+
"--allow-scripts",
|
|
425
|
+
"Allow modules' native install/build scripts to run (needed for native deps like isolated-vm under npm 11)"
|
|
426
|
+
)
|
|
292
427
|
.action(async (opts) => {
|
|
293
428
|
try {
|
|
294
429
|
const isGlobal: boolean = opts.global ?? false;
|
|
430
|
+
const allowScripts: boolean = opts.allowScripts ?? false;
|
|
295
431
|
const cfg = isGlobal ? await getGlobalConfig() : await getConfig();
|
|
296
432
|
const configLabel = isGlobal
|
|
297
433
|
? "~/.knowhow/knowhow.json"
|
|
@@ -307,10 +443,16 @@ export function addModulesCommand(program: Command): void {
|
|
|
307
443
|
let anyChanges = false;
|
|
308
444
|
for (const mod of BUILTIN_MODULES) {
|
|
309
445
|
if (!mod.startsWith(".") && !mod.startsWith("/")) {
|
|
310
|
-
if
|
|
446
|
+
// Reinstall if the module isn't installed yet, OR if the user
|
|
447
|
+
// explicitly passed --allow-scripts. In the latter case the whole
|
|
448
|
+
// point is to (re-)run the native install/build scripts (e.g.
|
|
449
|
+
// isolated-vm's node-gyp build), so skipping because the package
|
|
450
|
+
// is "already installed" would silently do nothing — which is
|
|
451
|
+
// exactly the confusing "Nothing to do" case we want to avoid.
|
|
452
|
+
if (!isModuleInstalled(mod, knowhowDir) || allowScripts) {
|
|
311
453
|
const installTarget = await fetchLatestCompatibleVersion(mod);
|
|
312
454
|
console.log(`📦 Installing ${installTarget}...`);
|
|
313
|
-
npmInstallToKnowhow(installTarget, knowhowDir);
|
|
455
|
+
npmInstallToKnowhow(installTarget, knowhowDir, { allowScripts });
|
|
314
456
|
console.log(`✅ Installed ${mod}`);
|
|
315
457
|
anyChanges = true;
|
|
316
458
|
}
|
|
@@ -346,9 +488,14 @@ export function addModulesCommand(program: Command): void {
|
|
|
346
488
|
)
|
|
347
489
|
.option("--global", "Use the global config (~/.knowhow/knowhow.json)")
|
|
348
490
|
.option("--latest", "Force install the latest version (bypasses package-lock)")
|
|
491
|
+
.option(
|
|
492
|
+
"--allow-scripts",
|
|
493
|
+
"Allow the module's native install/build scripts to run (needed for native deps like isolated-vm under npm 11)"
|
|
494
|
+
)
|
|
349
495
|
.action(async (moduleName: string | undefined, opts) => {
|
|
350
496
|
try {
|
|
351
497
|
const isGlobal: boolean = opts.global ?? false;
|
|
498
|
+
const allowScripts: boolean = opts.allowScripts ?? false;
|
|
352
499
|
const cfg = isGlobal ? await getGlobalConfig() : await getConfig();
|
|
353
500
|
const configLabel = isGlobal
|
|
354
501
|
? "~/.knowhow/knowhow.json"
|
|
@@ -374,7 +521,7 @@ export function addModulesCommand(program: Command): void {
|
|
|
374
521
|
for (const mod of installable) {
|
|
375
522
|
console.log(` 📦 Installing ${mod}...`);
|
|
376
523
|
const installTarget = opts.latest ? await fetchLatestCompatibleVersion(mod) : mod;
|
|
377
|
-
npmInstallToKnowhow(installTarget, knowhowDir);
|
|
524
|
+
npmInstallToKnowhow(installTarget, knowhowDir, { allowScripts });
|
|
378
525
|
console.log(` ✅ Installed ${mod}`);
|
|
379
526
|
}
|
|
380
527
|
console.log(`\n🎉 All modules installed!`);
|
|
@@ -384,7 +531,7 @@ export function addModulesCommand(program: Command): void {
|
|
|
384
531
|
// Install the specified module
|
|
385
532
|
const installTarget = opts.latest ? await fetchLatestCompatibleVersion(moduleName) : moduleName;
|
|
386
533
|
console.log(`📦 Installing ${installTarget} into ${knowhowDir}/node_modules...`);
|
|
387
|
-
npmInstallToKnowhow(installTarget, knowhowDir);
|
|
534
|
+
npmInstallToKnowhow(installTarget, knowhowDir, { allowScripts });
|
|
388
535
|
console.log(`✅ Installed ${moduleName}`);
|
|
389
536
|
|
|
390
537
|
// Add to config if not already there
|
|
@@ -453,10 +600,15 @@ export function addModulesCommand(program: Command): void {
|
|
|
453
600
|
)
|
|
454
601
|
.option("--global", "Use the global config (~/.knowhow/knowhow.json)")
|
|
455
602
|
.option("-y, --yes", "Skip confirmation prompt and update all outdated modules automatically")
|
|
603
|
+
.option(
|
|
604
|
+
"--allow-scripts",
|
|
605
|
+
"Allow modules' native install/build scripts to run (needed for native deps like isolated-vm under npm 11)"
|
|
606
|
+
)
|
|
456
607
|
.action(async (opts) => {
|
|
457
608
|
try {
|
|
458
609
|
const isGlobal: boolean = opts.global ?? false;
|
|
459
610
|
const skipConfirm: boolean = opts.yes ?? false;
|
|
611
|
+
const allowScripts: boolean = opts.allowScripts ?? false;
|
|
460
612
|
const cfg = isGlobal ? await getGlobalConfig() : await getConfig();
|
|
461
613
|
const configLabel = isGlobal
|
|
462
614
|
? "~/.knowhow/knowhow.json"
|
|
@@ -540,7 +692,7 @@ export function addModulesCommand(program: Command): void {
|
|
|
540
692
|
console.log("");
|
|
541
693
|
for (const { mod, compatibleVersion } of toUpdate) {
|
|
542
694
|
console.log(` 📦 Updating ${compatibleVersion}...`);
|
|
543
|
-
npmInstallToKnowhow(compatibleVersion, knowhowDir);
|
|
695
|
+
npmInstallToKnowhow(compatibleVersion, knowhowDir, { allowScripts });
|
|
544
696
|
console.log(` ✅ Updated ${mod}`);
|
|
545
697
|
}
|
|
546
698
|
console.log(`\n🎉 Update complete! ${toUpdate.length} module(s) updated.`);
|
package/src/commands/services.ts
CHANGED
|
@@ -49,6 +49,20 @@ export async function setupServices() {
|
|
|
49
49
|
|
|
50
50
|
Tools.addContext("Mcp", Mcp);
|
|
51
51
|
|
|
52
|
+
// The `agentCall` tool + function are registered by AgentService on the
|
|
53
|
+
// global ToolsService (AllTools), not on the LazyToolsService we build here.
|
|
54
|
+
// Copy it across so agents/scripts using this Tools instance can delegate to
|
|
55
|
+
// other agents via agentCall (matching startAgentTask availability).
|
|
56
|
+
try {
|
|
57
|
+
const agentCallDef = AllTools.getTool?.("agentCall");
|
|
58
|
+
const agentCallFn = AllTools.getFunction?.("agentCall");
|
|
59
|
+
if (agentCallDef) Tools.addTool(agentCallDef);
|
|
60
|
+
if (agentCallFn) Tools.setFunction("agentCall", agentCallFn);
|
|
61
|
+
} catch (_) {
|
|
62
|
+
// Non-fatal: if agentCall isn't registered yet, agents can still use
|
|
63
|
+
// startAgentTask for delegation.
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
Agents.setAgentContext(agentContext);
|
|
53
67
|
|
|
54
68
|
console.log("🔌 Connecting to MCP...");
|
package/src/generate.ts
CHANGED
|
@@ -51,6 +51,12 @@ export interface GenerateOptions {
|
|
|
51
51
|
* so they appear in `knowhow agents list`. Overrides the per-source syncFs flag.
|
|
52
52
|
*/
|
|
53
53
|
syncFs?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* When true, agent-driven sources push their work to a remote Knowhow task
|
|
56
|
+
* (identified by the source's taskId). Overrides the per-source syncRemote
|
|
57
|
+
* flag. Requires the sources to run as agents with a known taskId.
|
|
58
|
+
*/
|
|
59
|
+
syncRemote?: boolean;
|
|
54
60
|
/** When true, skip hash checks and regenerate all matching sources regardless of changes */
|
|
55
61
|
force?: boolean;
|
|
56
62
|
/**
|
|
@@ -58,6 +64,14 @@ export interface GenerateOptions {
|
|
|
58
64
|
* dependency wave. Defaults to 3. Set to 1 for fully sequential execution.
|
|
59
65
|
*/
|
|
60
66
|
concurrency?: number;
|
|
67
|
+
/**
|
|
68
|
+
* In-memory generation sources. When provided, these are used INSTEAD of the
|
|
69
|
+
* sources defined in `.knowhow/knowhow.json`. This enables callers (e.g. an
|
|
70
|
+
* agent using the `runGenerate` tool) to define arbitrary pipelines at
|
|
71
|
+
* runtime without persisting them to config — supporting complex task
|
|
72
|
+
* orchestration with `dependsOn`, per-source `agent`, analysis, etc.
|
|
73
|
+
*/
|
|
74
|
+
sources?: GenerationSource[];
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
// ---------------------------------------------------------------------------
|
|
@@ -416,12 +430,14 @@ async function runSource(
|
|
|
416
430
|
console.log("Generating", source.input, "to", source.output);
|
|
417
431
|
|
|
418
432
|
const effectiveSyncFs = options.syncFs ?? source.syncFs;
|
|
433
|
+
const effectiveSyncRemote = options.syncRemote ?? source.syncRemote;
|
|
419
434
|
const agentOpts: AgentOptions | undefined = source.agent
|
|
420
435
|
? {
|
|
421
436
|
syncFs: effectiveSyncFs,
|
|
437
|
+
syncRemote: effectiveSyncRemote,
|
|
422
438
|
taskId:
|
|
423
439
|
source.taskId ??
|
|
424
|
-
(effectiveSyncFs
|
|
440
|
+
(effectiveSyncFs || effectiveSyncRemote
|
|
425
441
|
? `generate:${source.output.replace(/[^a-zA-Z0-9_.-]/g, "_")}`
|
|
426
442
|
: undefined),
|
|
427
443
|
maxTimeLimit: source.maxTimeLimit,
|
|
@@ -469,8 +485,14 @@ async function runSource(
|
|
|
469
485
|
// ---------------------------------------------------------------------------
|
|
470
486
|
|
|
471
487
|
export async function generate(options: GenerateOptions = {}): Promise<void> {
|
|
472
|
-
|
|
473
|
-
|
|
488
|
+
let allSources: GenerationSource[];
|
|
489
|
+
if (options.sources && options.sources.length > 0) {
|
|
490
|
+
// In-memory pipeline definition — bypass config entirely.
|
|
491
|
+
allSources = options.sources;
|
|
492
|
+
} else {
|
|
493
|
+
const config = await getConfig();
|
|
494
|
+
allSources = config.sources ?? [];
|
|
495
|
+
}
|
|
474
496
|
const sources = allSources.filter((s) => sourceMatchesFilter(s, options));
|
|
475
497
|
|
|
476
498
|
if ((options.name || options.filter) && sources.length === 0) {
|
|
@@ -104,11 +104,29 @@ export class AgentService {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Run a registered agent and return both its final answer and the cost it
|
|
109
|
+
* incurred. Returning cost (from the agent's own `getTotalCostUsd()`) lets
|
|
110
|
+
* callers — notably the script sandbox — account subagent spend against a
|
|
111
|
+
* budget. The `answer` string preserves the previous plain-string behavior
|
|
112
|
+
* for consumers that only care about the response.
|
|
113
|
+
*/
|
|
114
|
+
public async callAgent(
|
|
115
|
+
name: string,
|
|
116
|
+
query: string
|
|
117
|
+
): Promise<{ answer: string; costUsd: number }> {
|
|
108
118
|
const agent = this.agents.get(name);
|
|
109
119
|
if (!agent) {
|
|
110
|
-
return
|
|
120
|
+
return {
|
|
121
|
+
answer: `Agent ${name} not found. Options are: ${this.listAgents()}`,
|
|
122
|
+
costUsd: 0,
|
|
123
|
+
};
|
|
111
124
|
}
|
|
112
|
-
|
|
125
|
+
const answer = await agent.call(query);
|
|
126
|
+
const costUsd =
|
|
127
|
+
typeof (agent as any).getTotalCostUsd === "function"
|
|
128
|
+
? (agent as any).getTotalCostUsd()
|
|
129
|
+
: 0;
|
|
130
|
+
return { answer, costUsd };
|
|
113
131
|
}
|
|
114
132
|
}
|
|
@@ -11,6 +11,8 @@ export interface FsSyncOptions {
|
|
|
11
11
|
taskId: string;
|
|
12
12
|
prompt: string;
|
|
13
13
|
agentName?: string;
|
|
14
|
+
/** The taskId of the parent agent that spawned this task, if any. */
|
|
15
|
+
parentTaskId?: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -62,8 +64,15 @@ export class AgentSyncFs {
|
|
|
62
64
|
await this.writeInput("");
|
|
63
65
|
await this.writeMetadata({
|
|
64
66
|
taskId: this.taskId,
|
|
67
|
+
// Record the OS process id of this agent subprocess so orchestrating
|
|
68
|
+
// tools (e.g. waitForAgentCompleted) can detect a crashed-but-not-
|
|
69
|
+
// finalized task via process-liveness. This is the child's own pid,
|
|
70
|
+
// so it needs no coordination with the spawner and never touches
|
|
71
|
+
// execCommand's child processes.
|
|
72
|
+
pid: process.pid,
|
|
65
73
|
prompt: options.prompt,
|
|
66
74
|
agentName: options.agentName || "unknown",
|
|
75
|
+
...(options.parentTaskId ? { parentTaskId: options.parentTaskId } : {}),
|
|
67
76
|
startTime: new Date().toISOString(),
|
|
68
77
|
status: "running",
|
|
69
78
|
});
|
|
@@ -492,11 +501,58 @@ export class AgentSyncFs {
|
|
|
492
501
|
console.error(`❌ Error cleaning up ${entry.name}:`, error);
|
|
493
502
|
}
|
|
494
503
|
}
|
|
504
|
+
|
|
505
|
+
// Also clean up old execCommand background log files in .knowhow/processes/*.txt
|
|
506
|
+
await AgentSyncFs.cleanupOldProcessLogs(threeDaysMs);
|
|
495
507
|
} catch (error) {
|
|
496
508
|
console.error(`❌ Error during cleanup:`, error);
|
|
497
509
|
}
|
|
498
510
|
}
|
|
499
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Clean up old execCommand background log files (.knowhow/processes/*.txt).
|
|
514
|
+
* These are created by the execCommand tool when running background/long-lived
|
|
515
|
+
* commands, and are never cleaned up otherwise.
|
|
516
|
+
*/
|
|
517
|
+
private static async cleanupOldProcessLogs(maxAgeMs: number): Promise<void> {
|
|
518
|
+
try {
|
|
519
|
+
// execCommand writes logs to the parent of the agents dir: .knowhow/processes
|
|
520
|
+
const processesPath = path.dirname(AgentSyncFs.sharedBasePath);
|
|
521
|
+
|
|
522
|
+
try {
|
|
523
|
+
await fs.access(processesPath);
|
|
524
|
+
} catch {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const entries = await fs.readdir(processesPath, { withFileTypes: true });
|
|
529
|
+
const now = Date.now();
|
|
530
|
+
|
|
531
|
+
for (const entry of entries) {
|
|
532
|
+
// Only target the .txt log files that execCommand creates; leave the
|
|
533
|
+
// agents/ directory (and any other subdirectories) alone.
|
|
534
|
+
if (!entry.isFile() || !entry.name.endsWith(".txt")) continue;
|
|
535
|
+
|
|
536
|
+
const logPath = path.join(processesPath, entry.name);
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
const stats = await fs.stat(logPath);
|
|
540
|
+
const age = now - stats.mtimeMs;
|
|
541
|
+
|
|
542
|
+
if (age > maxAgeMs) {
|
|
543
|
+
console.log(`🧹 Cleaning up old process log file: ${entry.name}`);
|
|
544
|
+
await fs.rm(logPath, { force: true });
|
|
545
|
+
}
|
|
546
|
+
} catch (error) {
|
|
547
|
+
// Skip if we can't stat or delete
|
|
548
|
+
console.error(`❌ Error cleaning up log ${entry.name}:`, error);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.error(`❌ Error during process log cleanup:`, error);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
500
556
|
/**
|
|
501
557
|
* Start periodic cleanup process
|
|
502
558
|
*/
|
|
@@ -255,7 +255,10 @@ export class EventService extends EventEmitter {
|
|
|
255
255
|
this.emit(this.eventTypes.agentsRegister, { name: agent.name, agent });
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
callAgent(
|
|
258
|
+
callAgent(
|
|
259
|
+
name: string,
|
|
260
|
+
query: string
|
|
261
|
+
): Promise<{ answer: string; costUsd: number }> {
|
|
259
262
|
return new Promise((resolve, reject) => {
|
|
260
263
|
this.emit(this.eventTypes.agentsCall, { name, query, resolve, reject });
|
|
261
264
|
});
|
|
@@ -122,6 +122,13 @@ export interface GitCredentialResponse {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
export function loadKnowhowJwt(): string {
|
|
125
|
+
// Check env var first — this is how cloud workers/CI runners get auth
|
|
126
|
+
// (injected via KNOWHOW_JWT env var in the runner's .env file by GithubRunnerService).
|
|
127
|
+
// This avoids depending on a potentially stale .knowhow/.jwt file from the snapshot.
|
|
128
|
+
if (process.env.KNOWHOW_JWT) {
|
|
129
|
+
return process.env.KNOWHOW_JWT;
|
|
130
|
+
}
|
|
131
|
+
|
|
125
132
|
const jwtFile = path.join(process.cwd(), ".knowhow", ".jwt");
|
|
126
133
|
if (!fs.existsSync(jwtFile)) {
|
|
127
134
|
return "";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { minimatch } from "minimatch";
|
|
2
|
-
import { ToolsService, ToolContext } from "./Tools";
|
|
2
|
+
import { ToolsService, ToolContext, ToolCallContext } from "./Tools";
|
|
3
3
|
import { Tool } from "../clients/types";
|
|
4
4
|
import { ToolCall } from "../clients/types";
|
|
5
5
|
import {
|
|
@@ -140,7 +140,11 @@ export class LazyToolsService extends ToolsService {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Override callTool to auto-enable tools that are in allTools but not yet enabled
|
|
143
|
-
async callTool(
|
|
143
|
+
async callTool(
|
|
144
|
+
toolCall: ToolCall,
|
|
145
|
+
enabledTools?: string[],
|
|
146
|
+
callContext?: ToolCallContext
|
|
147
|
+
) {
|
|
144
148
|
const functionName = toolCall.function.name;
|
|
145
149
|
|
|
146
150
|
// If the tool isn't currently visible but exists in allTools, auto-enable it.
|
|
@@ -160,7 +164,7 @@ export class LazyToolsService extends ToolsService {
|
|
|
160
164
|
|
|
161
165
|
// Always use the current enabled tool names after any auto-enable above,
|
|
162
166
|
// so the base class check sees the freshly-enabled tool in the allowed list.
|
|
163
|
-
return super.callTool(toolCall, this.getToolNames());
|
|
167
|
+
return super.callTool(toolCall, this.getToolNames(), callContext);
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
// Internal: Update visible tools based on patterns
|