@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolsService, ToolContext } from "../../src/services/Tools";
|
|
1
|
+
import { ToolsService, ToolContext, ToolCallContext } from "../../src/services/Tools";
|
|
2
2
|
import { Tool, ToolCall } from "../../src/clients/types";
|
|
3
3
|
import { createPatternMatcher } from "../../src/services/types";
|
|
4
4
|
import { AgentService } from "../../src/services/AgentService";
|
|
@@ -268,7 +268,11 @@ describe("ToolsService", () => {
|
|
|
268
268
|
|
|
269
269
|
const result = await toolsService.callTool(mockToolCall);
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
// Non-positional tools receive `_ctx` merged onto the args object.
|
|
272
|
+
expect(mockFunction).toHaveBeenCalledWith(
|
|
273
|
+
expect.objectContaining({ input: "test value" })
|
|
274
|
+
);
|
|
275
|
+
expect(mockFunction.mock.calls[0][0]._ctx).toBeDefined();
|
|
272
276
|
expect(result.toolMessages).toHaveLength(1);
|
|
273
277
|
expect(result.toolMessages[0]).toEqual({
|
|
274
278
|
tool_call_id: "call_123",
|
|
@@ -312,7 +316,12 @@ describe("ToolsService", () => {
|
|
|
312
316
|
|
|
313
317
|
await toolsService.callTool(positionalCall);
|
|
314
318
|
|
|
315
|
-
|
|
319
|
+
// Positional tools receive `_ctx` as the LAST positional argument.
|
|
320
|
+
expect(mockFunction).toHaveBeenCalledWith(
|
|
321
|
+
"hello",
|
|
322
|
+
42,
|
|
323
|
+
expect.objectContaining({ Tools: expect.anything() })
|
|
324
|
+
);
|
|
316
325
|
});
|
|
317
326
|
|
|
318
327
|
it("should handle tool not enabled error", async () => {
|
|
@@ -433,7 +442,10 @@ describe("ToolsService", () => {
|
|
|
433
442
|
|
|
434
443
|
await toolsService.callTool(toolCallWithStringArgs);
|
|
435
444
|
|
|
436
|
-
expect(mockFunction).toHaveBeenCalledWith(
|
|
445
|
+
expect(mockFunction).toHaveBeenCalledWith(
|
|
446
|
+
expect.objectContaining({ input: "escaped\nstring" })
|
|
447
|
+
);
|
|
448
|
+
expect(mockFunction.mock.calls[0][0]._ctx).toBeDefined();
|
|
437
449
|
});
|
|
438
450
|
});
|
|
439
451
|
describe("Tool Override System", () => {
|
|
@@ -492,8 +504,9 @@ describe("ToolsService", () => {
|
|
|
492
504
|
|
|
493
505
|
const result = await toolsService.callTool(toolCall);
|
|
494
506
|
|
|
507
|
+
// Non-positional args now include `_ctx`; the override receives them in an array.
|
|
495
508
|
expect(overrideFunction).toHaveBeenCalledWith(
|
|
496
|
-
[{ input: "test" }],
|
|
509
|
+
[expect.objectContaining({ input: "test" })],
|
|
497
510
|
expect.any(Object)
|
|
498
511
|
);
|
|
499
512
|
expect(originalFunction).not.toHaveBeenCalled();
|
|
@@ -627,7 +640,9 @@ describe("ToolsService", () => {
|
|
|
627
640
|
const result = await toolsService.callTool(toolCall);
|
|
628
641
|
|
|
629
642
|
expect(wrapperFunction).toHaveBeenCalled();
|
|
630
|
-
expect(originalFunction).toHaveBeenCalledWith(
|
|
643
|
+
expect(originalFunction).toHaveBeenCalledWith(
|
|
644
|
+
expect.objectContaining({ input: "test" })
|
|
645
|
+
);
|
|
631
646
|
expect(result.functionResp).toBe("wrapped: original result");
|
|
632
647
|
});
|
|
633
648
|
|
|
@@ -841,7 +856,8 @@ describe("ToolsService", () => {
|
|
|
841
856
|
|
|
842
857
|
const result = await toolsService.callTool(noParamCall);
|
|
843
858
|
|
|
844
|
-
expect(mockFunction).toHaveBeenCalledWith(
|
|
859
|
+
expect(mockFunction).toHaveBeenCalledWith(expect.any(Object));
|
|
860
|
+
expect(mockFunction.mock.calls[0][0]._ctx).toBeDefined();
|
|
845
861
|
expect(result.functionResp).toBe("no param result");
|
|
846
862
|
});
|
|
847
863
|
|
|
@@ -897,14 +913,16 @@ describe("ToolsService", () => {
|
|
|
897
913
|
|
|
898
914
|
const result = await toolsService.callTool(complexCall);
|
|
899
915
|
|
|
900
|
-
expect(mockFunction).toHaveBeenCalledWith(
|
|
916
|
+
expect(mockFunction).toHaveBeenCalledWith(
|
|
917
|
+
expect.objectContaining({
|
|
901
918
|
config: {
|
|
902
919
|
settings: [
|
|
903
920
|
{ key: "timeout", value: 5000 },
|
|
904
921
|
{ key: "retries", value: 3 },
|
|
905
922
|
],
|
|
906
923
|
},
|
|
907
|
-
|
|
924
|
+
})
|
|
925
|
+
);
|
|
908
926
|
expect(result.functionResp).toBe("complex result");
|
|
909
927
|
});
|
|
910
928
|
|
|
@@ -1570,4 +1588,149 @@ describe("ToolsService", () => {
|
|
|
1570
1588
|
expect(foundTools.map(t => t.function.name)).toContain("tool2");
|
|
1571
1589
|
});
|
|
1572
1590
|
});
|
|
1591
|
+
|
|
1592
|
+
describe("_ctx per-call context threading", () => {
|
|
1593
|
+
const positionalTool: Tool = {
|
|
1594
|
+
type: "function",
|
|
1595
|
+
function: {
|
|
1596
|
+
name: "posCtxTool",
|
|
1597
|
+
parameters: {
|
|
1598
|
+
type: "object",
|
|
1599
|
+
positional: true,
|
|
1600
|
+
properties: {
|
|
1601
|
+
arg1: { type: "string" },
|
|
1602
|
+
},
|
|
1603
|
+
required: ["arg1"],
|
|
1604
|
+
},
|
|
1605
|
+
},
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
const nonPositionalTool: Tool = {
|
|
1609
|
+
type: "function",
|
|
1610
|
+
function: {
|
|
1611
|
+
name: "nonPosCtxTool",
|
|
1612
|
+
parameters: {
|
|
1613
|
+
type: "object",
|
|
1614
|
+
properties: {
|
|
1615
|
+
input: { type: "string" },
|
|
1616
|
+
},
|
|
1617
|
+
required: ["input"],
|
|
1618
|
+
},
|
|
1619
|
+
},
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
it("passes _ctx as the last positional argument for positional tools", async () => {
|
|
1623
|
+
const fn = jest.fn().mockResolvedValue("ok");
|
|
1624
|
+
toolsService.addTool(positionalTool);
|
|
1625
|
+
toolsService.setFunction("posCtxTool", fn);
|
|
1626
|
+
|
|
1627
|
+
const caller = { name: "TestAgent" };
|
|
1628
|
+
await toolsService.callTool(
|
|
1629
|
+
{
|
|
1630
|
+
id: "c1",
|
|
1631
|
+
type: "function",
|
|
1632
|
+
function: {
|
|
1633
|
+
name: "posCtxTool",
|
|
1634
|
+
arguments: JSON.stringify({ arg1: "hello" }),
|
|
1635
|
+
},
|
|
1636
|
+
},
|
|
1637
|
+
undefined,
|
|
1638
|
+
{ caller, taskId: "task-123" }
|
|
1639
|
+
);
|
|
1640
|
+
|
|
1641
|
+
const callArgs = fn.mock.calls[0];
|
|
1642
|
+
expect(callArgs[0]).toBe("hello");
|
|
1643
|
+
const ctx = callArgs[1] as ToolCallContext;
|
|
1644
|
+
expect(ctx).toBeDefined();
|
|
1645
|
+
expect(ctx.caller).toBe(caller);
|
|
1646
|
+
expect(ctx.taskId).toBe("task-123");
|
|
1647
|
+
expect(ctx.Tools).toBe(toolsService);
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
it("merges _ctx onto the args object for non-positional tools", async () => {
|
|
1651
|
+
const fn = jest.fn().mockResolvedValue("ok");
|
|
1652
|
+
toolsService.addTool(nonPositionalTool);
|
|
1653
|
+
toolsService.setFunction("nonPosCtxTool", fn);
|
|
1654
|
+
|
|
1655
|
+
const caller = { name: "TestAgent" };
|
|
1656
|
+
await toolsService.callTool(
|
|
1657
|
+
{
|
|
1658
|
+
id: "c2",
|
|
1659
|
+
type: "function",
|
|
1660
|
+
function: {
|
|
1661
|
+
name: "nonPosCtxTool",
|
|
1662
|
+
arguments: JSON.stringify({ input: "value" }),
|
|
1663
|
+
},
|
|
1664
|
+
},
|
|
1665
|
+
undefined,
|
|
1666
|
+
{ caller, taskId: "task-456" }
|
|
1667
|
+
);
|
|
1668
|
+
|
|
1669
|
+
const argObj = fn.mock.calls[0][0];
|
|
1670
|
+
expect(argObj.input).toBe("value");
|
|
1671
|
+
expect(argObj._ctx).toBeDefined();
|
|
1672
|
+
expect(argObj._ctx.caller).toBe(caller);
|
|
1673
|
+
expect(argObj._ctx.taskId).toBe("task-456");
|
|
1674
|
+
expect(argObj._ctx.Tools).toBe(toolsService);
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
it("still provides a _ctx (with Tools) when no callContext is passed", async () => {
|
|
1678
|
+
const fn = jest.fn().mockResolvedValue("ok");
|
|
1679
|
+
toolsService.addTool(nonPositionalTool);
|
|
1680
|
+
toolsService.setFunction("nonPosCtxTool", fn);
|
|
1681
|
+
|
|
1682
|
+
await toolsService.callTool({
|
|
1683
|
+
id: "c3",
|
|
1684
|
+
type: "function",
|
|
1685
|
+
function: {
|
|
1686
|
+
name: "nonPosCtxTool",
|
|
1687
|
+
arguments: JSON.stringify({ input: "value" }),
|
|
1688
|
+
},
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
const argObj = fn.mock.calls[0][0];
|
|
1692
|
+
expect(argObj._ctx).toBeDefined();
|
|
1693
|
+
expect(argObj._ctx.Tools).toBe(toolsService);
|
|
1694
|
+
expect(argObj._ctx.caller).toBeUndefined();
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1697
|
+
it("does not leak _ctx into the multi_tool_use.parallel extraction", async () => {
|
|
1698
|
+
// The parallel path should extract calls from the ORIGINAL args, not the
|
|
1699
|
+
// _ctx-augmented ones.
|
|
1700
|
+
const parallelTool: Tool = {
|
|
1701
|
+
type: "function",
|
|
1702
|
+
function: {
|
|
1703
|
+
name: "multi_tool_use.parallel",
|
|
1704
|
+
parameters: { type: "object", properties: {} },
|
|
1705
|
+
},
|
|
1706
|
+
};
|
|
1707
|
+
const inner = jest
|
|
1708
|
+
.fn()
|
|
1709
|
+
.mockResolvedValue(["r1", "r2"]);
|
|
1710
|
+
toolsService.addTool(parallelTool);
|
|
1711
|
+
toolsService.setFunction("multi_tool_use.parallel", inner);
|
|
1712
|
+
|
|
1713
|
+
const result = await toolsService.callTool(
|
|
1714
|
+
{
|
|
1715
|
+
id: "c4",
|
|
1716
|
+
type: "function",
|
|
1717
|
+
function: {
|
|
1718
|
+
name: "multi_tool_use.parallel",
|
|
1719
|
+
arguments: JSON.stringify([
|
|
1720
|
+
{ recipient_name: "functions.a", parameters: {} },
|
|
1721
|
+
{ recipient_name: "functions.b", parameters: {} },
|
|
1722
|
+
]),
|
|
1723
|
+
},
|
|
1724
|
+
},
|
|
1725
|
+
undefined,
|
|
1726
|
+
{ caller: {}, taskId: "t" }
|
|
1727
|
+
);
|
|
1728
|
+
|
|
1729
|
+
// Two tool messages, one per parallel call — proves extraction used the
|
|
1730
|
+
// original array shape rather than an _ctx-wrapped object.
|
|
1731
|
+
expect(result.toolMessages).toHaveLength(2);
|
|
1732
|
+
expect(result.toolMessages[0].name).toBe("a");
|
|
1733
|
+
expect(result.toolMessages[1].name).toBe("b");
|
|
1734
|
+
});
|
|
1735
|
+
});
|
|
1573
1736
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as os from "os";
|
|
2
|
+
import * as path from "path";
|
|
1
3
|
import { Config } from "../../../src/types";
|
|
2
4
|
|
|
3
5
|
// Mock config before any other imports that depend on it
|
|
@@ -233,4 +235,71 @@ describe("ModulesService.loadModulesFromConfig", () => {
|
|
|
233
235
|
expect(context.Agents.registerAgent).toHaveBeenCalledTimes(2);
|
|
234
236
|
spy.mockRestore();
|
|
235
237
|
});
|
|
238
|
+
|
|
239
|
+
it("resolves global-config modules against ~/.knowhow/node_modules first, local against cwd/.knowhow/node_modules first", async () => {
|
|
240
|
+
mockGetGlobalConfig.mockResolvedValue({
|
|
241
|
+
modules: ["global-pkg"],
|
|
242
|
+
} as unknown as Config);
|
|
243
|
+
mockGetConfig.mockResolvedValue({
|
|
244
|
+
modules: ["local-pkg"],
|
|
245
|
+
} as unknown as Config);
|
|
246
|
+
|
|
247
|
+
const service = new ModulesService();
|
|
248
|
+
const context = makeContext();
|
|
249
|
+
|
|
250
|
+
const calls: { modules: string[]; resolvePaths: string[] }[] = [];
|
|
251
|
+
const spy = jest
|
|
252
|
+
.spyOn(service as any, "loadModulesFrom")
|
|
253
|
+
.mockImplementation(async (...args: any[]) => {
|
|
254
|
+
const [cfg, , resolvePaths] = args;
|
|
255
|
+
calls.push({ modules: cfg.modules, resolvePaths });
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
await service.loadModulesFromConfig(context);
|
|
259
|
+
|
|
260
|
+
const globalKnowhow = path.join(os.homedir(), ".knowhow", "node_modules");
|
|
261
|
+
const localKnowhow = path.join(process.cwd(), ".knowhow", "node_modules");
|
|
262
|
+
|
|
263
|
+
const globalCall = calls.find((c) => c.modules.includes("global-pkg"));
|
|
264
|
+
const localCall = calls.find((c) => c.modules.includes("local-pkg"));
|
|
265
|
+
|
|
266
|
+
expect(globalCall).toBeDefined();
|
|
267
|
+
expect(localCall).toBeDefined();
|
|
268
|
+
// Global-config module: global node_modules is first in the search order
|
|
269
|
+
expect(globalCall!.resolvePaths[0]).toBe(globalKnowhow);
|
|
270
|
+
// Local-config module: local project node_modules is first in the search order
|
|
271
|
+
expect(localCall!.resolvePaths[0]).toBe(localKnowhow);
|
|
272
|
+
|
|
273
|
+
spy.mockRestore();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("does not load a local module again if it is already declared globally (avoids duplicate command registration)", async () => {
|
|
277
|
+
mockGetGlobalConfig.mockResolvedValue({
|
|
278
|
+
modules: ["shared-pkg"],
|
|
279
|
+
} as unknown as Config);
|
|
280
|
+
mockGetConfig.mockResolvedValue({
|
|
281
|
+
modules: ["shared-pkg", "local-only-pkg"],
|
|
282
|
+
} as unknown as Config);
|
|
283
|
+
|
|
284
|
+
const service = new ModulesService();
|
|
285
|
+
const context = makeContext();
|
|
286
|
+
|
|
287
|
+
const loadedModuleLists: string[][] = [];
|
|
288
|
+
const spy = jest
|
|
289
|
+
.spyOn(service as any, "loadModulesFrom")
|
|
290
|
+
.mockImplementation(async (...args: any[]) => {
|
|
291
|
+
const [cfg] = args;
|
|
292
|
+
loadedModuleLists.push(cfg.modules);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
await service.loadModulesFromConfig(context);
|
|
296
|
+
|
|
297
|
+
const allLoaded = loadedModuleLists.flat();
|
|
298
|
+
// shared-pkg loaded exactly once (via the global pass)
|
|
299
|
+
expect(allLoaded.filter((m) => m === "shared-pkg")).toHaveLength(1);
|
|
300
|
+
// local-only-pkg still loaded
|
|
301
|
+
expect(allLoaded).toContain("local-only-pkg");
|
|
302
|
+
|
|
303
|
+
spy.mockRestore();
|
|
304
|
+
});
|
|
236
305
|
});
|
package/ts_build/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.141",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"knowhow": "bin/knowhow.js"
|
|
8
8
|
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20"
|
|
11
|
+
},
|
|
9
12
|
"scripts": {
|
|
10
13
|
"test": "jest --testTimeout 300000",
|
|
11
14
|
"test:debug": "node --inspect-brk ../../node_modules/jest/bin/jest.js --detectOpenHandles --forceExit --testTimeout 300000",
|
|
@@ -39,7 +42,7 @@
|
|
|
39
42
|
"@types/express": "^4.17.13",
|
|
40
43
|
"@types/jest": "^29.5.13",
|
|
41
44
|
"@types/mocha": "^10.0.8",
|
|
42
|
-
"@types/node": "^
|
|
45
|
+
"@types/node": "^24.0.0",
|
|
43
46
|
"@types/ws": "^8.18.1",
|
|
44
47
|
"jest": "^29.1.1",
|
|
45
48
|
"prettier": "2.6.2",
|
|
@@ -21,6 +21,22 @@ export interface ToolCallEvent {
|
|
|
21
21
|
toolCall: ToolCall;
|
|
22
22
|
functionResp: any;
|
|
23
23
|
}
|
|
24
|
+
export interface ObservationSource {
|
|
25
|
+
label: string;
|
|
26
|
+
start(emit: (datum: unknown) => void): void | (() => void) | Promise<void | (() => void)>;
|
|
27
|
+
}
|
|
28
|
+
export interface ObserveOpts {
|
|
29
|
+
onlyOnChange?: boolean;
|
|
30
|
+
maxUpdates?: number;
|
|
31
|
+
maxDurationMs?: number;
|
|
32
|
+
}
|
|
33
|
+
interface ActiveObservation {
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
updates: number;
|
|
37
|
+
teardownPromise: Promise<void | (() => void)>;
|
|
38
|
+
expiry?: NodeJS.Timeout;
|
|
39
|
+
}
|
|
24
40
|
export declare abstract class BaseAgent implements IAgent {
|
|
25
41
|
abstract name: string;
|
|
26
42
|
abstract description: string;
|
|
@@ -46,14 +62,17 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
46
62
|
protected totalOutputTokens: number;
|
|
47
63
|
protected totalCacheReadTokens: number;
|
|
48
64
|
protected totalCacheWriteTokens: number;
|
|
65
|
+
protected lastPromptTokens: number;
|
|
49
66
|
protected compressThreshold: number;
|
|
50
67
|
protected compressMinMessages: number;
|
|
68
|
+
protected _forceCompact: boolean;
|
|
51
69
|
private _interruptResolve;
|
|
52
70
|
private _interruptToken;
|
|
53
|
-
private _pendingInterrupt;
|
|
54
71
|
protected threads: Message[][];
|
|
55
72
|
protected pendingUserMessages: Message[];
|
|
56
73
|
protected pendingMessages: Message[];
|
|
74
|
+
protected _observations: Map<string, ActiveObservation>;
|
|
75
|
+
private _observationSeq;
|
|
57
76
|
protected taskBreakdown: string;
|
|
58
77
|
protected summaries: string[];
|
|
59
78
|
protected currentTaskId: string | null;
|
|
@@ -87,6 +106,7 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
87
106
|
constructor(context: AgentContext);
|
|
88
107
|
protected log(message: string, level?: "info" | "warn" | "error"): void;
|
|
89
108
|
setCompressThreshold(threshold: number): void;
|
|
109
|
+
requestCompact(): void;
|
|
90
110
|
getCompressThreshold(): number;
|
|
91
111
|
setMaxTurns(maxTurns: number | null): void;
|
|
92
112
|
setMaxSpend(maxSpend: number | null): void;
|
|
@@ -155,7 +175,10 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
155
175
|
logStatus(): void;
|
|
156
176
|
addPendingMessage(message: Message): void;
|
|
157
177
|
addPendingUserMessage(message: Message): void;
|
|
178
|
+
observe(source: ObservationSource, opts?: ObserveOpts): string;
|
|
179
|
+
stopObserving(id?: string, reason?: string): string;
|
|
158
180
|
getMessagesLength(messages: Message[]): number;
|
|
181
|
+
getContextTokenCount(messages: Message[]): number;
|
|
159
182
|
detectTruncatedToolCalls(toolCalls: ToolCall[], response: CompletionResponse): {
|
|
160
183
|
role: string;
|
|
161
184
|
content: string;
|
|
@@ -30,14 +30,17 @@ class BaseAgent {
|
|
|
30
30
|
totalOutputTokens = 0;
|
|
31
31
|
totalCacheReadTokens = 0;
|
|
32
32
|
totalCacheWriteTokens = 0;
|
|
33
|
+
lastPromptTokens = 0;
|
|
33
34
|
compressThreshold = 30000;
|
|
34
35
|
compressMinMessages = 30;
|
|
36
|
+
_forceCompact = false;
|
|
35
37
|
_interruptResolve = null;
|
|
36
38
|
_interruptToken = 0;
|
|
37
|
-
_pendingInterrupt = false;
|
|
38
39
|
threads = [];
|
|
39
40
|
pendingUserMessages = [];
|
|
40
41
|
pendingMessages = [];
|
|
42
|
+
_observations = new Map();
|
|
43
|
+
_observationSeq = 0;
|
|
41
44
|
taskBreakdown = "";
|
|
42
45
|
summaries = [];
|
|
43
46
|
currentTaskId = null;
|
|
@@ -105,6 +108,9 @@ class BaseAgent {
|
|
|
105
108
|
setCompressThreshold(threshold) {
|
|
106
109
|
this.compressThreshold = threshold;
|
|
107
110
|
}
|
|
111
|
+
requestCompact() {
|
|
112
|
+
this._forceCompact = true;
|
|
113
|
+
}
|
|
108
114
|
getCompressThreshold() {
|
|
109
115
|
if (this.compressThreshold !== contextLimits_1.DEFAULT_CONTEXT_LIMIT) {
|
|
110
116
|
return this.compressThreshold;
|
|
@@ -112,7 +118,7 @@ class BaseAgent {
|
|
|
112
118
|
const result = this.clientService.getContextLimit(this.getProvider(), this.getModel());
|
|
113
119
|
const contextLimit = result?.contextLimit ?? contextLimits_1.DEFAULT_CONTEXT_LIMIT;
|
|
114
120
|
const threshold = result?.threshold ?? contextLimit;
|
|
115
|
-
return Math.floor(threshold * 0.
|
|
121
|
+
return Math.floor(threshold * 0.7);
|
|
116
122
|
}
|
|
117
123
|
setMaxTurns(maxTurns) {
|
|
118
124
|
this.maxTurns = maxTurns;
|
|
@@ -133,12 +139,14 @@ class BaseAgent {
|
|
|
133
139
|
this.totalOutputTokens = 0;
|
|
134
140
|
this.totalCacheReadTokens = 0;
|
|
135
141
|
this.totalCacheWriteTokens = 0;
|
|
142
|
+
this.lastPromptTokens = 0;
|
|
136
143
|
this.status = this.eventTypes.inProgress;
|
|
137
144
|
this.turnCount = 0;
|
|
138
145
|
this.startTimeMs = Date.now();
|
|
139
146
|
this.currentTaskId = taskId || this.startTimeMs.toString();
|
|
140
147
|
this._interruptResolve = null;
|
|
141
|
-
this.
|
|
148
|
+
this._forceCompact = false;
|
|
149
|
+
this.stopObserving(undefined, "new task started");
|
|
142
150
|
const id = taskId || this.startTimeMs.toString();
|
|
143
151
|
this.events.emit(this.eventTypes.agentNewTask, {
|
|
144
152
|
taskId: id,
|
|
@@ -318,6 +326,10 @@ class BaseAgent {
|
|
|
318
326
|
usage.prompt_tokens_details?.cached_tokens ??
|
|
319
327
|
0;
|
|
320
328
|
const cacheWriteTokens = usage.cache_creation_input_tokens ?? usage.cache_write_tokens ?? 0;
|
|
329
|
+
const promptTokens = inputTokens + cacheReadTokens + cacheWriteTokens;
|
|
330
|
+
if (promptTokens > 0) {
|
|
331
|
+
this.lastPromptTokens = promptTokens;
|
|
332
|
+
}
|
|
321
333
|
this.totalInputTokens += inputTokens;
|
|
322
334
|
this.totalOutputTokens += outputTokens;
|
|
323
335
|
this.totalCacheReadTokens += cacheReadTokens;
|
|
@@ -385,7 +397,10 @@ class BaseAgent {
|
|
|
385
397
|
},
|
|
386
398
|
],
|
|
387
399
|
};
|
|
388
|
-
const { functionResp, toolMessages } = await this.makeInterruptible(this.tools.callTool(toolCall, this.getEnabledToolNames()
|
|
400
|
+
const { functionResp, toolMessages } = await this.makeInterruptible(this.tools.callTool(toolCall, this.getEnabledToolNames(), {
|
|
401
|
+
caller: this,
|
|
402
|
+
taskId: this.currentTaskId,
|
|
403
|
+
}), interruptResult);
|
|
389
404
|
this.agentEvents.emit(this.eventTypes.toolUsed, {
|
|
390
405
|
toolCall,
|
|
391
406
|
functionResp,
|
|
@@ -521,11 +536,6 @@ class BaseAgent {
|
|
|
521
536
|
fn();
|
|
522
537
|
};
|
|
523
538
|
this._interruptResolve = () => finish(() => resolve(interruptValue));
|
|
524
|
-
if (this._pendingInterrupt) {
|
|
525
|
-
this._pendingInterrupt = false;
|
|
526
|
-
finish(() => resolve(interruptValue));
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
539
|
promise
|
|
530
540
|
.then((result) => {
|
|
531
541
|
finish(() => resolve(result));
|
|
@@ -547,8 +557,7 @@ class BaseAgent {
|
|
|
547
557
|
this._interruptResolve();
|
|
548
558
|
}
|
|
549
559
|
else {
|
|
550
|
-
this.
|
|
551
|
-
this.log("No active interruptible operation — queued interrupt for next step", "warn");
|
|
560
|
+
this.log("No active interruptible operation to interrupt", "warn");
|
|
552
561
|
}
|
|
553
562
|
}
|
|
554
563
|
async call(userInput, _messages, retryCount = 0) {
|
|
@@ -608,8 +617,12 @@ class BaseAgent {
|
|
|
608
617
|
tools: this.getEnabledTools(),
|
|
609
618
|
tool_choice: "auto",
|
|
610
619
|
long_ttl_cache: this.runTime() > 300_000,
|
|
611
|
-
...(this.reasoningEffort !== undefined && {
|
|
612
|
-
|
|
620
|
+
...(this.reasoningEffort !== undefined && {
|
|
621
|
+
reasoning_effort: this.reasoningEffort,
|
|
622
|
+
}),
|
|
623
|
+
...(this.summarizeReasoning !== undefined && {
|
|
624
|
+
reasoning_summary: this.summarizeReasoning,
|
|
625
|
+
}),
|
|
613
626
|
}, { interruptValue: interruptResponse });
|
|
614
627
|
if (this.status === this.eventTypes.pause) {
|
|
615
628
|
this.log("Agent was paused after completion, waiting before processing tool calls");
|
|
@@ -710,12 +723,18 @@ class BaseAgent {
|
|
|
710
723
|
this.agentEvents.emit(this.eventTypes.done, firstMessage.content);
|
|
711
724
|
return firstMessage.content;
|
|
712
725
|
}
|
|
713
|
-
|
|
714
|
-
|
|
726
|
+
const contextTokens = this.getContextTokenCount(messages);
|
|
727
|
+
const overThreshold = contextTokens > this.getCompressThreshold() &&
|
|
728
|
+
messages.length > this.compressMinMessages;
|
|
729
|
+
if (overThreshold || this._forceCompact) {
|
|
715
730
|
const taskBreakdown = await this.getTaskBreakdown(messages);
|
|
716
|
-
this.log(
|
|
731
|
+
this.log(this._forceCompact
|
|
732
|
+
? `Compacting messages (manual /compact request): ${contextTokens} tokens`
|
|
733
|
+
: `Compressing messages: ${contextTokens} tokens exceeds ${this.getCompressThreshold()}`);
|
|
734
|
+
this._forceCompact = false;
|
|
717
735
|
messages = await this.compressMessages(messages, startIndex, endIndex);
|
|
718
736
|
this.startNewThread(messages);
|
|
737
|
+
this.lastPromptTokens = 0;
|
|
719
738
|
}
|
|
720
739
|
if (["assistant", "tool"].includes(messages[messages.length - 1].role)) {
|
|
721
740
|
const statusMessage = this.getStatusMessage();
|
|
@@ -836,9 +855,85 @@ class BaseAgent {
|
|
|
836
855
|
}
|
|
837
856
|
this.events.emit(this.eventTypes.userSay, message.content);
|
|
838
857
|
}
|
|
858
|
+
observe(source, opts = {}) {
|
|
859
|
+
const { onlyOnChange = true, maxUpdates = 20, maxDurationMs = 10 * 60 * 1000, } = opts;
|
|
860
|
+
this._observationSeq += 1;
|
|
861
|
+
const safeLabel = source.label.replace(/[^A-Za-z0-9]+/g, "_").slice(0, 24);
|
|
862
|
+
const id = `obs_${safeLabel}_${Date.now()}_${this._observationSeq}`;
|
|
863
|
+
let last;
|
|
864
|
+
const emit = (datum) => {
|
|
865
|
+
if (this.status === this.eventTypes.done) {
|
|
866
|
+
this.stopObserving(id);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const str = typeof datum === "string" ? datum : JSON.stringify(datum);
|
|
870
|
+
if (onlyOnChange && str === last)
|
|
871
|
+
return;
|
|
872
|
+
last = str;
|
|
873
|
+
const obs = this._observations.get(id);
|
|
874
|
+
if (!obs)
|
|
875
|
+
return;
|
|
876
|
+
obs.updates += 1;
|
|
877
|
+
this.addPendingMessage({
|
|
878
|
+
role: "user",
|
|
879
|
+
content: `[observe:${id}] ${source.label} (update ${obs.updates}/${maxUpdates}):\n${str}`,
|
|
880
|
+
});
|
|
881
|
+
if (obs.updates >= maxUpdates) {
|
|
882
|
+
this.stopObserving(id, "max updates reached");
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
const teardownPromise = Promise.resolve(source.start(emit)).catch((e) => {
|
|
886
|
+
this.log(`observe(${source.label}) start failed: ${e?.message ?? e}`, "warn");
|
|
887
|
+
return undefined;
|
|
888
|
+
});
|
|
889
|
+
const expiry = maxDurationMs
|
|
890
|
+
? setTimeout(() => this.stopObserving(id, "max duration reached"), maxDurationMs)
|
|
891
|
+
: undefined;
|
|
892
|
+
if (expiry && typeof expiry.unref === "function") {
|
|
893
|
+
expiry.unref();
|
|
894
|
+
}
|
|
895
|
+
this._observations.set(id, {
|
|
896
|
+
id,
|
|
897
|
+
label: source.label,
|
|
898
|
+
updates: 0,
|
|
899
|
+
teardownPromise,
|
|
900
|
+
expiry,
|
|
901
|
+
});
|
|
902
|
+
return id;
|
|
903
|
+
}
|
|
904
|
+
stopObserving(id, reason = "stopped") {
|
|
905
|
+
const ids = id ? [id] : [...this._observations.keys()];
|
|
906
|
+
let stopped = 0;
|
|
907
|
+
for (const oid of ids) {
|
|
908
|
+
const obs = this._observations.get(oid);
|
|
909
|
+
if (!obs)
|
|
910
|
+
continue;
|
|
911
|
+
if (obs.expiry)
|
|
912
|
+
clearTimeout(obs.expiry);
|
|
913
|
+
Promise.resolve(obs.teardownPromise)
|
|
914
|
+
.then((fn) => {
|
|
915
|
+
if (typeof fn === "function")
|
|
916
|
+
fn();
|
|
917
|
+
})
|
|
918
|
+
.catch(() => { });
|
|
919
|
+
this._observations.delete(oid);
|
|
920
|
+
stopped += 1;
|
|
921
|
+
}
|
|
922
|
+
return id
|
|
923
|
+
? stopped
|
|
924
|
+
? `Stopped observer ${id} (${reason}).`
|
|
925
|
+
: `No active observer found with id: ${id}.`
|
|
926
|
+
: `Stopped ${stopped} active observer(s) (${reason}).`;
|
|
927
|
+
}
|
|
839
928
|
getMessagesLength(messages) {
|
|
840
929
|
return JSON.stringify(messages).split(" ").length;
|
|
841
930
|
}
|
|
931
|
+
getContextTokenCount(messages) {
|
|
932
|
+
if (this.lastPromptTokens > 0) {
|
|
933
|
+
return this.lastPromptTokens;
|
|
934
|
+
}
|
|
935
|
+
return this.getMessagesLength(messages);
|
|
936
|
+
}
|
|
842
937
|
detectTruncatedToolCalls(toolCalls, response) {
|
|
843
938
|
const rawOutputTokens = response?.usage?.completion_tokens || 0;
|
|
844
939
|
const thinkingTokens = (response?.usage?.output_tokens_details?.thinking_tokens ?? 0) +
|