@tyvm/knowhow 0.0.106 → 0.0.108-dev.126b29e
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/README.md +45 -0
- package/package.json +8 -2
- package/scripts/publish.sh +86 -0
- package/src/agents/base/base.ts +44 -4
- package/src/agents/tools/execCommand.ts +49 -6
- package/src/chat/modules/AgentModule.ts +55 -30
- package/src/chat/modules/SessionsModule.ts +7 -2
- package/src/cli.ts +7 -2
- package/src/clients/anthropic.ts +27 -18
- package/src/clients/gemini.ts +14 -2
- package/src/clients/http.ts +4 -0
- package/src/clients/openai.ts +12 -1
- package/src/clients/pricing/openai.ts +1 -0
- package/src/clients/types.ts +35 -1
- package/src/clients/xai.ts +11 -1
- package/src/cloudWorker.ts +75 -1
- package/src/index.ts +17 -0
- package/src/plugins/embedding.ts +11 -6
- package/src/plugins/vim.ts +5 -16
- package/src/services/KnowhowClient.ts +22 -2
- package/src/services/S3.ts +10 -0
- package/src/services/modules/types.ts +2 -0
- package/src/worker.ts +65 -1
- package/src/workers/tools/index.ts +2 -0
- package/src/workers/tools/reloadConfig.ts +84 -0
- package/tests/clients/AIClient.test.ts +1 -1
- package/tests/clients/anthropic.test.ts +202 -0
- package/tests/services/WorkerReloadConfig.test.ts +141 -0
- package/ts_build/package.json +8 -2
- package/ts_build/src/agents/base/base.d.ts +1 -0
- package/ts_build/src/agents/base/base.js +31 -4
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
- package/ts_build/src/agents/tools/execCommand.js +39 -5
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +39 -19
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +7 -2
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/cli.js +8 -2
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +5 -5
- package/ts_build/src/clients/anthropic.js +27 -18
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/gemini.js +10 -1
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/http.js +3 -0
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/openai.js +11 -1
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +1 -0
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +14 -1
- package/ts_build/src/clients/xai.js +11 -1
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/cloudWorker.d.ts +9 -0
- package/ts_build/src/cloudWorker.js +36 -0
- package/ts_build/src/cloudWorker.js.map +1 -1
- package/ts_build/src/index.js +14 -0
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/plugins/embedding.js +4 -3
- package/ts_build/src/plugins/embedding.js.map +1 -1
- package/ts_build/src/plugins/vim.js +3 -9
- package/ts_build/src/plugins/vim.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +12 -0
- package/ts_build/src/services/KnowhowClient.js +11 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/S3.js +7 -0
- package/ts_build/src/services/S3.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +2 -0
- package/ts_build/src/worker.js +38 -0
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/tools/index.d.ts +2 -0
- package/ts_build/src/workers/tools/index.js +4 -1
- package/ts_build/src/workers/tools/index.js.map +1 -1
- package/ts_build/src/workers/tools/reloadConfig.d.ts +14 -0
- package/ts_build/src/workers/tools/reloadConfig.js +48 -0
- package/ts_build/src/workers/tools/reloadConfig.js.map +1 -0
- package/ts_build/tests/clients/AIClient.test.js +1 -1
- package/ts_build/tests/clients/AIClient.test.js.map +1 -1
- package/ts_build/tests/clients/anthropic.test.d.ts +1 -0
- package/ts_build/tests/clients/anthropic.test.js +159 -0
- package/ts_build/tests/clients/anthropic.test.js.map +1 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.d.ts +1 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.js +86 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.js.map +1 -0
package/README.md
CHANGED
|
@@ -362,3 +362,48 @@ For command usage and behavior details, see:
|
|
|
362
362
|
- Website: https://knowhow.tyvm.ai
|
|
363
363
|
- Twitter/X: https://x.com/micahriggan
|
|
364
364
|
- npm: https://www.npmjs.com/package/@tyvm/knowhow
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 🚧 Dev & Nightly Releases
|
|
369
|
+
|
|
370
|
+
Knowhow publishes unstable/preview builds under separate npm dist-tags so that regular users on `latest` are never affected.
|
|
371
|
+
|
|
372
|
+
### Installing a pre-release build
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
# Latest nightly (date-stamped)
|
|
376
|
+
npm install -g @tyvm/knowhow@nightly
|
|
377
|
+
|
|
378
|
+
# Latest dev snapshot (git-hash-stamped)
|
|
379
|
+
npm install -g @tyvm/knowhow@dev
|
|
380
|
+
|
|
381
|
+
# Pin a specific pre-release version
|
|
382
|
+
npm install -g @tyvm/knowhow@0.0.108-nightly.20250428
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### How dist-tags work
|
|
386
|
+
|
|
387
|
+
| Tag | Installed by default? | Audience |
|
|
388
|
+
|-----|-----------------------|---------|
|
|
389
|
+
| `latest` | ✅ Yes (`npm install @tyvm/knowhow`) | All stable users |
|
|
390
|
+
| `nightly` | ❌ No (must opt-in) | Testers / early adopters |
|
|
391
|
+
| `dev` | ❌ No (must opt-in) | Developers / contributors |
|
|
392
|
+
|
|
393
|
+
Publishing a `nightly` or `dev` release **never moves the `latest` tag**, so existing users won't receive unstable code through normal updates.
|
|
394
|
+
|
|
395
|
+
### Publishing scripts (for maintainers)
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
# Publish a date-stamped nightly (e.g. 0.0.108-nightly.20250428)
|
|
399
|
+
npm run publish:nightly
|
|
400
|
+
|
|
401
|
+
# Publish a git-hash-stamped dev snapshot (e.g. 0.0.108-dev.abc1234)
|
|
402
|
+
npm run publish:dev
|
|
403
|
+
|
|
404
|
+
# Publish a stable release to `latest`
|
|
405
|
+
npm version patch # or minor / major
|
|
406
|
+
npm run publish:stable
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
> **Note:** `publish:nightly` and `publish:dev` automatically version-bump the `package.json` with a pre-release suffix before publishing and do **not** create a git tag, keeping your git history clean.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.108-dev.126b29e",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,13 @@
|
|
|
21
21
|
"lint": "tslint ./src/**/*.ts",
|
|
22
22
|
"lint:deps": "depcheck --config=.depcheckrc",
|
|
23
23
|
"lint:all": "npm run lint && npm run lint:deps",
|
|
24
|
-
"check:model-pricing": "ts-node scripts/check-model-pricing.ts"
|
|
24
|
+
"check:model-pricing": "ts-node scripts/check-model-pricing.ts",
|
|
25
|
+
"version:nightly": "bash scripts/publish.sh version:nightly",
|
|
26
|
+
"version:dev": "bash scripts/publish.sh version:dev",
|
|
27
|
+
"publish:nightly": "bash scripts/publish.sh nightly",
|
|
28
|
+
"publish:dev": "bash scripts/publish.sh dev",
|
|
29
|
+
"publish:latest": "bash scripts/publish.sh stable",
|
|
30
|
+
"publish:stable": "bash scripts/publish.sh stable"
|
|
25
31
|
},
|
|
26
32
|
"keywords": [],
|
|
27
33
|
"author": "Micah Riggan",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
COMMAND="${1:-}"
|
|
5
|
+
|
|
6
|
+
get_current_version() {
|
|
7
|
+
node -e "process.stdout.write(require('./package.json').version)"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
version_nightly() {
|
|
11
|
+
local version
|
|
12
|
+
version=$(get_current_version)
|
|
13
|
+
# Strip any existing pre-release suffix (e.g. -dev.xxx or -nightly.xxx)
|
|
14
|
+
version=$(echo "$version" | sed 's/-.*$//')
|
|
15
|
+
local stamp
|
|
16
|
+
stamp=$(date -u +%Y%m%d)
|
|
17
|
+
local pre="${version}-nightly.${stamp}"
|
|
18
|
+
local current
|
|
19
|
+
current=$(get_current_version)
|
|
20
|
+
if [ "$current" = "$pre" ]; then
|
|
21
|
+
echo "Version already set to: $pre (no change needed)"
|
|
22
|
+
else
|
|
23
|
+
echo "Bumping version to: $pre"
|
|
24
|
+
npm version "$pre" --no-git-tag-version
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
version_dev() {
|
|
29
|
+
local version
|
|
30
|
+
version=$(get_current_version)
|
|
31
|
+
# Strip any existing pre-release suffix (e.g. -dev.xxx or -nightly.xxx)
|
|
32
|
+
version=$(echo "$version" | sed 's/-.*$//')
|
|
33
|
+
local hash
|
|
34
|
+
hash=$(git rev-parse --short HEAD)
|
|
35
|
+
local pre="${version}-dev.${hash}"
|
|
36
|
+
local current
|
|
37
|
+
current=$(get_current_version)
|
|
38
|
+
if [ "$current" = "$pre" ]; then
|
|
39
|
+
echo "Version already set to: $pre (no change needed)"
|
|
40
|
+
else
|
|
41
|
+
echo "Bumping version to: $pre"
|
|
42
|
+
npm version "$pre" --no-git-tag-version
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
case "$COMMAND" in
|
|
47
|
+
nightly)
|
|
48
|
+
echo "🌙 Publishing nightly release..."
|
|
49
|
+
npm run compile
|
|
50
|
+
bash scripts/check-isolated-deps.sh
|
|
51
|
+
version_nightly
|
|
52
|
+
npm publish --tag nightly
|
|
53
|
+
echo "✅ Nightly published!"
|
|
54
|
+
;;
|
|
55
|
+
dev)
|
|
56
|
+
echo "🔧 Publishing dev release..."
|
|
57
|
+
npm run compile
|
|
58
|
+
bash scripts/check-isolated-deps.sh
|
|
59
|
+
version_dev
|
|
60
|
+
npm publish --tag dev
|
|
61
|
+
echo "✅ Dev release published!"
|
|
62
|
+
;;
|
|
63
|
+
stable|latest)
|
|
64
|
+
echo "🚀 Publishing stable release..."
|
|
65
|
+
npm run compile
|
|
66
|
+
bash scripts/check-isolated-deps.sh
|
|
67
|
+
npm publish --tag latest
|
|
68
|
+
echo "✅ Stable release published!"
|
|
69
|
+
;;
|
|
70
|
+
version:nightly)
|
|
71
|
+
version_nightly
|
|
72
|
+
;;
|
|
73
|
+
version:dev)
|
|
74
|
+
version_dev
|
|
75
|
+
;;
|
|
76
|
+
*)
|
|
77
|
+
echo "Usage: $0 <nightly|dev|stable|version:nightly|version:dev>"
|
|
78
|
+
echo ""
|
|
79
|
+
echo " nightly - Compile, check deps, bump version with nightly stamp, publish to 'nightly' tag"
|
|
80
|
+
echo " dev - Compile, check deps, bump version with git hash, publish to 'dev' tag"
|
|
81
|
+
echo " stable - Compile, check deps, publish to 'latest' tag"
|
|
82
|
+
echo " version:nightly - Only bump version with nightly stamp (no publish)"
|
|
83
|
+
echo " version:dev - Only bump version with git hash (no publish)"
|
|
84
|
+
exit 1
|
|
85
|
+
;;
|
|
86
|
+
esac
|
package/src/agents/base/base.ts
CHANGED
|
@@ -293,6 +293,33 @@ export abstract class BaseAgent implements IAgent {
|
|
|
293
293
|
this.easyFinalAnswer = value;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Detect if the model's response is a termination signal (e.g. "Done", "Complete", "Finished", "finalAnswer")
|
|
298
|
+
* This handles the case where an agent refuses to call finalAnswer and just says a short termination word.
|
|
299
|
+
*/
|
|
300
|
+
protected isTerminationResponse(content: string): boolean {
|
|
301
|
+
const trimmed = content.trim();
|
|
302
|
+
// Short response (≤ 3 words) that matches a termination word/phrase exactly
|
|
303
|
+
const wordCount = trimmed.split(/\s+/).filter(Boolean).length;
|
|
304
|
+
if (wordCount <= 3) {
|
|
305
|
+
const terminationPattern = /^(done|complete|completed|finished|final\s*answer|task\s*complete|all\s*done|that'?s\s*(all|it)|ok(ay)?|yes)[.!]*$/i;
|
|
306
|
+
if (terminationPattern.test(trimmed)) return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check if the first 1-3 words indicate task completion (for longer responses)
|
|
310
|
+
// e.g. "Task complete: ...", "All done.", "No further changes needed.", "Confirmed complete."
|
|
311
|
+
const firstWords = trimmed.split(/\s+/).slice(0, 3).join(" ");
|
|
312
|
+
const firstWordPattern = /^(task\s*(complete|completed|done|finished)|all\s*done|no\s*(further|more|additional|changes|action)|confirmed?\s*(complete|done|finished|one\s*last)|nothing\s*(more|further|else)|standing\s*by|everything\s*is|still\s*confirmed|acknowledged|done\s*and|complete\s*(and|\.)|completed\s*successfully|no\s*additional|verified\s*and)/i;
|
|
313
|
+
if (firstWordPattern.test(firstWords)) return true;
|
|
314
|
+
|
|
315
|
+
// If easyFinalAnswer mode is on, also match response starting with "✅" or numbered confirmation lists
|
|
316
|
+
if (this.easyFinalAnswer) {
|
|
317
|
+
if (trimmed.startsWith("✅") || /^[\d\.\-\*]/.test(trimmed)) return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
|
|
296
323
|
getEnabledTools() {
|
|
297
324
|
return this.tools
|
|
298
325
|
.getTools()
|
|
@@ -686,6 +713,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
686
713
|
messages,
|
|
687
714
|
tools: this.getEnabledTools(),
|
|
688
715
|
tool_choice: "auto",
|
|
716
|
+
long_ttl_cache: this.runTime() > 300_000,
|
|
689
717
|
});
|
|
690
718
|
|
|
691
719
|
// If the agent was paused while the completion was in-flight, wait here
|
|
@@ -705,7 +733,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
705
733
|
"warn"
|
|
706
734
|
);
|
|
707
735
|
const error = response as any;
|
|
708
|
-
if ("response" in error && "data" in error.response) {
|
|
736
|
+
if (error != null && "response" in error && "data" in error.response) {
|
|
709
737
|
this.log(
|
|
710
738
|
`Response data: ${JSON.stringify(error.response.data, null, 2)}`,
|
|
711
739
|
"warn"
|
|
@@ -826,6 +854,18 @@ export abstract class BaseAgent implements IAgent {
|
|
|
826
854
|
|
|
827
855
|
// Early exit: not required to call tool
|
|
828
856
|
const firstMessage = response.choices[0].message;
|
|
857
|
+
// Auto-detect termination words: if the model is just saying "Done", "Complete", etc.
|
|
858
|
+
if (
|
|
859
|
+
response.choices.length === 1 &&
|
|
860
|
+
firstMessage.content &&
|
|
861
|
+
this.isTerminationResponse(firstMessage.content)
|
|
862
|
+
) {
|
|
863
|
+
this.log(`Termination word detected: "${firstMessage.content.trim()}", treating as finalAnswer`);
|
|
864
|
+
this.status = this.eventTypes.done;
|
|
865
|
+
this.agentEvents.emit(this.eventTypes.done, firstMessage.content);
|
|
866
|
+
return firstMessage.content;
|
|
867
|
+
}
|
|
868
|
+
|
|
829
869
|
if (
|
|
830
870
|
response.choices.length === 1 &&
|
|
831
871
|
firstMessage.content &&
|
|
@@ -879,7 +919,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
879
919
|
this.logStatus();
|
|
880
920
|
|
|
881
921
|
const continuation = `<Workflow>
|
|
882
|
-
workflow continues until you call one of ${this.requiredToolNames}.\n
|
|
922
|
+
workflow continues until you call one of ${JSON.stringify(this.requiredToolNames)}.\n
|
|
883
923
|
${statusMessage}
|
|
884
924
|
</Workflow>`;
|
|
885
925
|
|
|
@@ -925,7 +965,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
925
965
|
|
|
926
966
|
this.log(`Agent failed: ${e}`, "error");
|
|
927
967
|
|
|
928
|
-
if ("response" in e && "data" in e.response) {
|
|
968
|
+
if (e != null && typeof e === "object" && "response" in e && "data" in (e as any).response) {
|
|
929
969
|
this.log(
|
|
930
970
|
`Error response data: ${JSON.stringify(e.response.data, null, 2)}`,
|
|
931
971
|
"error"
|
|
@@ -1042,7 +1082,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1042
1082
|
toolCalls: ToolCall[],
|
|
1043
1083
|
response: CompletionResponse
|
|
1044
1084
|
): { role: string; content: string } | null {
|
|
1045
|
-
const outputTokens: number = response?.usage?.
|
|
1085
|
+
const outputTokens: number = response?.usage?.completion_tokens || 0;
|
|
1046
1086
|
const totalArgLength = toolCalls.reduce(
|
|
1047
1087
|
(sum, tc) => sum + (tc.function?.arguments?.length || 0),
|
|
1048
1088
|
0
|
|
@@ -3,6 +3,7 @@ import { promisify } from "util";
|
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import * as os from "os";
|
|
6
|
+
import { services, ToolsService } from "../../services";
|
|
6
7
|
|
|
7
8
|
export const execAsync = promisify(exec);
|
|
8
9
|
|
|
@@ -256,16 +257,32 @@ const execWithTimeout = async (
|
|
|
256
257
|
};
|
|
257
258
|
|
|
258
259
|
// Public tool
|
|
259
|
-
export
|
|
260
|
+
export async function execCommand(
|
|
260
261
|
command: string,
|
|
261
262
|
timeout?: number,
|
|
262
263
|
continueInBackground?: boolean,
|
|
263
264
|
logFileName?: string
|
|
264
|
-
): Promise<string>
|
|
265
|
-
if(!command || typeof command !== "string") {
|
|
265
|
+
): Promise<string> {
|
|
266
|
+
if (!command || typeof command !== "string") {
|
|
266
267
|
throw new Error("Invalid command. We received a non-string value. Please ensure you are sending strings of 4k tokens or less.");
|
|
267
268
|
}
|
|
268
269
|
|
|
270
|
+
// Get context from bound ToolsService (same pattern as writeFile)
|
|
271
|
+
const toolService = (
|
|
272
|
+
this instanceof ToolsService ? this : services().Tools
|
|
273
|
+
) as ToolsService;
|
|
274
|
+
const context = toolService.getContext();
|
|
275
|
+
|
|
276
|
+
// Emit pre-run blocking event — handlers can throw to block the command
|
|
277
|
+
if (context.Events) {
|
|
278
|
+
await context.Events.emitBlocking("exec:pre-run", {
|
|
279
|
+
command,
|
|
280
|
+
timeout,
|
|
281
|
+
continueInBackground,
|
|
282
|
+
logFileName,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
269
286
|
const { stdout, stderr, timedOut, killed, pid, logPath } =
|
|
270
287
|
await execWithTimeout(command, {
|
|
271
288
|
timeout,
|
|
@@ -298,6 +315,32 @@ export const execCommand = async (
|
|
|
298
315
|
* : "";
|
|
299
316
|
*/
|
|
300
317
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
318
|
+
const result = `$ ${command}${statusMsg}\n${output}`;
|
|
319
|
+
|
|
320
|
+
// Emit post-run blocking event — handlers can append extra context
|
|
321
|
+
let eventResults: any[] = [];
|
|
322
|
+
if (context.Events) {
|
|
323
|
+
eventResults = await context.Events.emitBlocking("exec:post-run", {
|
|
324
|
+
command,
|
|
325
|
+
timeout,
|
|
326
|
+
continueInBackground,
|
|
327
|
+
logFileName,
|
|
328
|
+
stdout,
|
|
329
|
+
stderr,
|
|
330
|
+
timedOut,
|
|
331
|
+
killed,
|
|
332
|
+
pid,
|
|
333
|
+
logPath,
|
|
334
|
+
output,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Append any additional context returned by post-run handlers
|
|
339
|
+
let eventResultsText = "";
|
|
340
|
+
if (eventResults && eventResults.length > 0) {
|
|
341
|
+
eventResultsText =
|
|
342
|
+
"\n\nAdditional Information:\n" + JSON.stringify(eventResults, null, 2);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return result + eventResultsText;
|
|
346
|
+
};
|
|
@@ -30,11 +30,7 @@ import {
|
|
|
30
30
|
Base64ImageProcessor,
|
|
31
31
|
} from "../../processors/index";
|
|
32
32
|
import { TaskInfo } from "../types";
|
|
33
|
-
import {
|
|
34
|
-
createAgent,
|
|
35
|
-
agentConstructors,
|
|
36
|
-
AgentName,
|
|
37
|
-
} from "../../agents";
|
|
33
|
+
import { createAgent, agentConstructors, AgentName } from "../../agents";
|
|
38
34
|
import { ToolCallEvent } from "../../agents/base/base";
|
|
39
35
|
import { KnowhowSimpleClient } from "../../services/KnowhowClient";
|
|
40
36
|
|
|
@@ -80,7 +76,6 @@ export class AgentModule extends BaseChatModule {
|
|
|
80
76
|
this.remoteSyncModule = module;
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
|
|
84
79
|
getCommands(): ChatCommand[] {
|
|
85
80
|
return [
|
|
86
81
|
{
|
|
@@ -260,7 +255,10 @@ export class AgentModule extends BaseChatModule {
|
|
|
260
255
|
if (context) {
|
|
261
256
|
// Create a temporary agent instance to read its default model/provider
|
|
262
257
|
const agentContext = services().Agents.getAgentContext();
|
|
263
|
-
const tempAgent = createAgent(
|
|
258
|
+
const tempAgent = createAgent(
|
|
259
|
+
agentName as AgentName,
|
|
260
|
+
agentContext
|
|
261
|
+
) as BaseAgent;
|
|
264
262
|
context.selectedAgent = tempAgent;
|
|
265
263
|
context.agentMode = true;
|
|
266
264
|
context.currentAgent = agentName;
|
|
@@ -455,7 +453,6 @@ export class AgentModule extends BaseChatModule {
|
|
|
455
453
|
const agentNames = Object.keys(agentConstructors);
|
|
456
454
|
|
|
457
455
|
if (agentNames.length > 0) {
|
|
458
|
-
|
|
459
456
|
console.log("\nAvailable agents:");
|
|
460
457
|
agentNames.forEach((name) => {
|
|
461
458
|
console.log(` - ${name}`);
|
|
@@ -503,39 +500,52 @@ export class AgentModule extends BaseChatModule {
|
|
|
503
500
|
console.error(`Session ${sessionId} not found.`);
|
|
504
501
|
return;
|
|
505
502
|
}
|
|
506
|
-
const lastThread = session.threads[session.threads.length - 1];
|
|
507
503
|
console.log(`\n🔄 Resuming session: ${sessionId}`);
|
|
508
504
|
console.log(`Agent: ${session.agentName}`);
|
|
509
505
|
console.log(`Original task: ${session.initialInput}`);
|
|
510
506
|
console.log(`Status: ${session.status}`);
|
|
511
507
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
508
|
+
// Build resume prompt (same pattern as resumeFromMessages)
|
|
509
|
+
const resumePrompt = [
|
|
510
|
+
"You are resuming a previously started task.",
|
|
511
|
+
session.initialInput ? `ORIGINAL REQUEST: ${session.initialInput}` : "",
|
|
512
|
+
"Please continue from where you left off and complete the original request.",
|
|
513
|
+
resumeReason ? `Reason for resuming: ${resumeReason}` : "",
|
|
514
|
+
]
|
|
515
|
+
.filter(Boolean)
|
|
516
|
+
.join("\n");
|
|
517
|
+
|
|
518
|
+
// Restore the full message history from the last thread
|
|
519
|
+
const threads = session.threads || [];
|
|
520
|
+
const lastThread = threads.length > 0 ? threads[threads.length - 1] : [];
|
|
521
|
+
const resumeMessages = [...lastThread];
|
|
522
|
+
|
|
523
|
+
// Append the resume prompt to the last user message (or add a new one)
|
|
524
|
+
const reversedIndex = [...lastThread]
|
|
525
|
+
.reverse()
|
|
526
|
+
.findIndex((e) => e.role === "user" && typeof e.content === "string");
|
|
527
|
+
|
|
528
|
+
if (reversedIndex === -1) {
|
|
529
|
+
resumeMessages.push({ role: "user", content: resumePrompt });
|
|
530
|
+
} else {
|
|
531
|
+
const actualIndex = lastThread.length - 1 - reversedIndex;
|
|
532
|
+
resumeMessages[actualIndex] = {
|
|
533
|
+
...resumeMessages[actualIndex],
|
|
534
|
+
content: resumeMessages[actualIndex].content + `\n\n<Workflow>[RESUME CONTEXT]: ${resumePrompt}</Workflow>`,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
528
537
|
|
|
529
538
|
console.log("🚀 Session resuming...");
|
|
530
539
|
const context = this.chatService?.getContext();
|
|
531
540
|
const agentName = session.agentName || context.currentAgent;
|
|
541
|
+
const previousAgentMode = context?.agentMode;
|
|
532
542
|
|
|
533
543
|
if (!agentName || !agentConstructors[agentName as AgentName]) {
|
|
534
544
|
console.error(`Agent ${agentName} not found.`);
|
|
535
545
|
return;
|
|
536
546
|
}
|
|
537
547
|
|
|
538
|
-
// Start agent with Knowhow task context
|
|
548
|
+
// Start agent with Knowhow task context and restored message history
|
|
539
549
|
const { agent, taskId } = await this.setupAgent({
|
|
540
550
|
agentName,
|
|
541
551
|
input: resumePrompt,
|
|
@@ -544,7 +554,20 @@ Please continue from where you left off and complete the original request.
|
|
|
544
554
|
chatHistory: [],
|
|
545
555
|
run: false, // Don't run yet, we need to set up event listeners first
|
|
546
556
|
});
|
|
547
|
-
|
|
557
|
+
|
|
558
|
+
// After resume finishes, revert to normal chat (non-agent mode) so the
|
|
559
|
+
// user can start a fresh conversation instead of staying locked in agent mode.
|
|
560
|
+
agent.agentEvents.once(agent.eventTypes.done, () => {
|
|
561
|
+
const ctx = this.chatService?.getContext();
|
|
562
|
+
if (ctx && !previousAgentMode) {
|
|
563
|
+
ctx.agentMode = false;
|
|
564
|
+
ctx.selectedAgent = undefined;
|
|
565
|
+
ctx.currentAgent = undefined;
|
|
566
|
+
this.chatService?.disableMode("agent");
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
await this.attachedAgentChatLoop(taskId, agent, resumePrompt, resumeMessages);
|
|
548
571
|
} catch (error) {
|
|
549
572
|
console.error(
|
|
550
573
|
`Failed to resume session ${sessionId}:`,
|
|
@@ -1033,7 +1056,8 @@ Please continue from where you left off and complete the original request.
|
|
|
1033
1056
|
async attachedAgentChatLoop(
|
|
1034
1057
|
taskId: string,
|
|
1035
1058
|
agent: AttachableAgent,
|
|
1036
|
-
initialInput?: string
|
|
1059
|
+
initialInput?: string,
|
|
1060
|
+
initialMessages?: any[]
|
|
1037
1061
|
): Promise<void> {
|
|
1038
1062
|
try {
|
|
1039
1063
|
let agentFinalOutput: string | undefined;
|
|
@@ -1075,7 +1099,7 @@ Please continue from where you left off and complete the original request.
|
|
|
1075
1099
|
|
|
1076
1100
|
if (context.chatHistory) {
|
|
1077
1101
|
const found = context.chatHistory.find((h) => h.taskId === taskId);
|
|
1078
|
-
found.output = agentFinalOutput;
|
|
1102
|
+
if (found) found.output = agentFinalOutput;
|
|
1079
1103
|
}
|
|
1080
1104
|
|
|
1081
1105
|
resolve("done");
|
|
@@ -1088,7 +1112,8 @@ Please continue from where you left off and complete the original request.
|
|
|
1088
1112
|
if (initialInput) {
|
|
1089
1113
|
const taskInfo = this.taskRegistry.get(taskId);
|
|
1090
1114
|
(agent as BaseAgent).call(
|
|
1091
|
-
taskInfo?.formattedPrompt || taskInfo?.initialInput || initialInput
|
|
1115
|
+
taskInfo?.formattedPrompt || taskInfo?.initialInput || initialInput,
|
|
1116
|
+
initialMessages
|
|
1092
1117
|
);
|
|
1093
1118
|
}
|
|
1094
1119
|
} catch (error) {
|
|
@@ -304,15 +304,20 @@ export class SessionsModule extends BaseChatModule {
|
|
|
304
304
|
async handleResumeCommand(args: string[]): Promise<void> {
|
|
305
305
|
const sessionManager = this.agentModule.getSessionManager();
|
|
306
306
|
|
|
307
|
-
if (args.length === 0) {
|
|
307
|
+
if (args.length === 0 || args[0] === "--all") {
|
|
308
308
|
// Interactive: show saved sessions for selection
|
|
309
|
-
const
|
|
309
|
+
const showAll = args[0] === "--all";
|
|
310
|
+
const allSessions = sessionManager.listAvailableSessions();
|
|
311
|
+
const savedSessions = showAll ? allSessions : allSessions.slice(0, 10);
|
|
310
312
|
if (savedSessions.length === 0) {
|
|
311
313
|
console.log("No saved sessions found to resume.");
|
|
312
314
|
return;
|
|
313
315
|
}
|
|
314
316
|
|
|
315
317
|
this.printSavedSessionsTable(savedSessions);
|
|
318
|
+
if (!showAll && allSessions.length > 10) {
|
|
319
|
+
console.log(` ... and ${allSessions.length - 10} more. Use /resume --all to see all sessions.`);
|
|
320
|
+
}
|
|
316
321
|
|
|
317
322
|
const allIds = savedSessions.map((s) => s.sessionId);
|
|
318
323
|
const selectedId = await this.selectByNumber(
|
package/src/cli.ts
CHANGED
|
@@ -530,12 +530,17 @@ async function main() {
|
|
|
530
530
|
.description("Create or sync a cloud worker with your local knowhow config")
|
|
531
531
|
.option("--create", "Create a new cloud worker with synced config and files")
|
|
532
532
|
.option("--push <uid>", "Push/sync local config and files to an existing cloud worker")
|
|
533
|
+
.option("--pull <id>", "Pull the latest workerConfigJson from a cloud worker and update local config")
|
|
533
534
|
.option("--name <name>", "Name for the cloud worker (used with --create)")
|
|
534
535
|
.option("--dry-run", "Print what would be synced without doing it")
|
|
535
536
|
.action(async (options) => {
|
|
536
537
|
try {
|
|
537
|
-
const { cloudWorker } = await import("./cloudWorker");
|
|
538
|
-
|
|
538
|
+
const { cloudWorker, pullCloudWorkerConfig } = await import("./cloudWorker");
|
|
539
|
+
if (options.pull) {
|
|
540
|
+
await pullCloudWorkerConfig({ id: options.pull });
|
|
541
|
+
} else {
|
|
542
|
+
await cloudWorker(options);
|
|
543
|
+
}
|
|
539
544
|
} catch (error) {
|
|
540
545
|
console.error("Error running cloudworker:", error);
|
|
541
546
|
process.exit(1);
|
package/src/clients/anthropic.ts
CHANGED
|
@@ -40,11 +40,11 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
handleToolCaching(tools: Anthropic.Tool[]) {
|
|
43
|
+
handleToolCaching(tools: Anthropic.Tool[], longTtl = false) {
|
|
44
44
|
const lastTool = tools[tools.length - 1];
|
|
45
45
|
|
|
46
46
|
if (lastTool) {
|
|
47
|
-
lastTool.cache_control = { type: "ephemeral" };
|
|
47
|
+
lastTool.cache_control = longTtl ? { type: "ephemeral", ttl: "1h" } as any : { type: "ephemeral" };
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -94,7 +94,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
94
94
|
return cleaned;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
transformTools(tools?: Tool[]): Anthropic.Tool[] {
|
|
97
|
+
transformTools(tools?: Tool[], longTtl = false): Anthropic.Tool[] {
|
|
98
98
|
if (!tools) {
|
|
99
99
|
return [];
|
|
100
100
|
}
|
|
@@ -104,7 +104,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
104
104
|
input_schema: this.cleanSchemaForAnthropic(tool.function.parameters) as any,
|
|
105
105
|
}));
|
|
106
106
|
|
|
107
|
-
this.handleToolCaching(transformed);
|
|
107
|
+
this.handleToolCaching(transformed, longTtl);
|
|
108
108
|
|
|
109
109
|
return transformed;
|
|
110
110
|
}
|
|
@@ -153,16 +153,16 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
153
153
|
return messages;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
cacheLastContent(message: MessageParam) {
|
|
156
|
+
cacheLastContent(message: MessageParam, longTtl = false) {
|
|
157
157
|
if (Array.isArray(message.content)) {
|
|
158
158
|
const lastMessage = message.content[message.content.length - 1];
|
|
159
159
|
if (
|
|
160
160
|
lastMessage.type !== "thinking" &&
|
|
161
161
|
lastMessage.type !== "redacted_thinking"
|
|
162
162
|
) {
|
|
163
|
-
lastMessage.cache_control =
|
|
164
|
-
type: "ephemeral",
|
|
165
|
-
|
|
163
|
+
lastMessage.cache_control = longTtl
|
|
164
|
+
? ({ type: "ephemeral", ttl: "1h" } as any)
|
|
165
|
+
: { type: "ephemeral" };
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
}
|
|
@@ -179,7 +179,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
handleMessageCaching(groupedMessages: MessageParam[]) {
|
|
182
|
+
handleMessageCaching(groupedMessages: MessageParam[], longTtl = false) {
|
|
183
183
|
this.handleClearingCache(groupedMessages);
|
|
184
184
|
|
|
185
185
|
// find the last two messages and mark them as ephemeral
|
|
@@ -189,7 +189,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
189
189
|
|
|
190
190
|
for (const m of lastTwoUserMessages) {
|
|
191
191
|
if (Array.isArray(m.content)) {
|
|
192
|
-
this.cacheLastContent(m);
|
|
192
|
+
this.cacheLastContent(m, longTtl);
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
}
|
|
@@ -203,11 +203,11 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
transformMessages(messages: Message[]): MessageParam[] {
|
|
206
|
+
transformMessages(messages: Message[], longTtl = false): MessageParam[] {
|
|
207
207
|
const toolCalls = messages.flatMap((msg) => msg.tool_calls || []);
|
|
208
208
|
const claudeMessages: MessageParam[] = messages
|
|
209
209
|
.filter((msg) => msg.role !== "system")
|
|
210
|
-
.filter((msg) => msg.content)
|
|
210
|
+
.filter((msg) => msg.content || msg.role === "tool")
|
|
211
211
|
.map((msg) => {
|
|
212
212
|
if (msg.role === "tool") {
|
|
213
213
|
const toolCall = toolCalls.find((tc) => tc.id === msg.tool_call_id);
|
|
@@ -302,7 +302,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
302
302
|
|
|
303
303
|
const groupedMessages = this.combineMessages(claudeMessages);
|
|
304
304
|
|
|
305
|
-
this.handleMessageCaching(groupedMessages);
|
|
305
|
+
this.handleMessageCaching(groupedMessages, longTtl);
|
|
306
306
|
|
|
307
307
|
return groupedMessages;
|
|
308
308
|
}
|
|
@@ -349,14 +349,15 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
349
349
|
async createChatCompletion(
|
|
350
350
|
options: CompletionOptions
|
|
351
351
|
): Promise<CompletionResponse> {
|
|
352
|
+
const longTtl = !!options.long_ttl_cache;
|
|
352
353
|
const systemMessage = options.messages
|
|
353
354
|
.filter((msg) => msg.role === "system")
|
|
354
355
|
.map((msg) => msg.content || "")
|
|
355
356
|
.join("\n");
|
|
356
357
|
|
|
357
|
-
const claudeMessages = this.transformMessages(options.messages);
|
|
358
|
+
const claudeMessages = this.transformMessages(options.messages, longTtl);
|
|
358
359
|
|
|
359
|
-
const tools = this.transformTools(options.tools);
|
|
360
|
+
const tools = this.transformTools(options.tools, longTtl);
|
|
360
361
|
try {
|
|
361
362
|
const response = await this.client.messages.create({
|
|
362
363
|
model: options.model,
|
|
@@ -365,7 +366,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
365
366
|
? [
|
|
366
367
|
{
|
|
367
368
|
text: systemMessage,
|
|
368
|
-
cache_control: { type: "ephemeral" },
|
|
369
|
+
cache_control: longTtl ? ({ type: "ephemeral", ttl: "1h" } as any) : { type: "ephemeral" },
|
|
369
370
|
type: "text",
|
|
370
371
|
},
|
|
371
372
|
]
|
|
@@ -412,11 +413,19 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
412
413
|
}),
|
|
413
414
|
|
|
414
415
|
model: options.model,
|
|
415
|
-
usage: response.usage
|
|
416
|
+
usage: response.usage ? {
|
|
417
|
+
input_tokens: response.usage.input_tokens ?? 0,
|
|
418
|
+
output_tokens: response.usage.output_tokens ?? 0,
|
|
419
|
+
prompt_tokens: response.usage.input_tokens ?? 0,
|
|
420
|
+
completion_tokens: response.usage.output_tokens ?? 0,
|
|
421
|
+
total_tokens: (response.usage.input_tokens ?? 0) + (response.usage.output_tokens ?? 0),
|
|
422
|
+
cache_creation_input_tokens: response.usage.cache_creation_input_tokens ?? 0,
|
|
423
|
+
cache_read_input_tokens: response.usage.cache_read_input_tokens ?? 0,
|
|
424
|
+
} : undefined,
|
|
416
425
|
usd_cost: this.calculateCost(options.model, response.usage),
|
|
417
426
|
};
|
|
418
427
|
} catch (err) {
|
|
419
|
-
if ("headers" in err && err.headers["x-should-retry"] === "true") {
|
|
428
|
+
if ("headers" in err && err.headers?.["x-should-retry"] === "true") {
|
|
420
429
|
console.warn("Retrying failed request", err);
|
|
421
430
|
await wait(2500);
|
|
422
431
|
return this.createChatCompletion(options);
|