@tyvm/knowhow 0.0.137 → 0.0.140
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/hash.ts +3 -2
- package/src/commands/misc.ts +6 -0
- 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/hash.js +1 -1
- package/ts_build/src/commands/hash.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/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
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { Tool } from "../../clients/types";
|
|
4
|
+
import { ToolCallContext } from "../../services/Tools";
|
|
5
|
+
|
|
6
|
+
/** Resolve the agents dir lazily so tests (and cwd changes) are respected. */
|
|
7
|
+
function agentsDir(): string {
|
|
8
|
+
return path.join(process.cwd(), ".knowhow", "processes", "agents");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const TERMINAL_STATUSES = new Set([
|
|
12
|
+
"completed",
|
|
13
|
+
"failed",
|
|
14
|
+
"killed",
|
|
15
|
+
"error",
|
|
16
|
+
"done",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
interface AgentTaskResult {
|
|
20
|
+
taskId: string;
|
|
21
|
+
status: string;
|
|
22
|
+
costUsd: number;
|
|
23
|
+
finalAnswer?: string;
|
|
24
|
+
artifacts?: string[];
|
|
25
|
+
elapsedMs?: number;
|
|
26
|
+
timedOut?: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readMeta(taskId: string): any | null {
|
|
31
|
+
try {
|
|
32
|
+
const metaPath = path.join(agentsDir(), taskId, "metadata.json");
|
|
33
|
+
if (!fs.existsSync(metaPath)) return null;
|
|
34
|
+
return JSON.parse(fs.readFileSync(metaPath, "utf8"));
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readStatusFile(taskId: string): string | null {
|
|
41
|
+
try {
|
|
42
|
+
const statusPath = path.join(agentsDir(), taskId, "status.txt");
|
|
43
|
+
if (!fs.existsSync(statusPath)) return null;
|
|
44
|
+
return fs.readFileSync(statusPath, "utf8").trim();
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Consolidated status read. metadata.status is the live/canonical value; the
|
|
52
|
+
* status.txt file is a fallback signal. If metadata says the task is no longer
|
|
53
|
+
* inProgress but has no explicit terminal status, treat it as completed.
|
|
54
|
+
*/
|
|
55
|
+
function resolveStatus(meta: any | null, statusFile: string | null): string {
|
|
56
|
+
if (meta?.status) return meta.status;
|
|
57
|
+
if (statusFile) return statusFile;
|
|
58
|
+
if (meta && meta.inProgress === false) return "completed";
|
|
59
|
+
return "unknown";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Extract the last assistant message from a thread as a fallback finalAnswer. */
|
|
63
|
+
function lastAssistantText(threads: any): string | undefined {
|
|
64
|
+
if (!threads) return undefined;
|
|
65
|
+
// threads may be an array of message arrays, or a flat message array.
|
|
66
|
+
let messages: any[] = [];
|
|
67
|
+
if (Array.isArray(threads)) {
|
|
68
|
+
if (threads.length && Array.isArray(threads[0])) {
|
|
69
|
+
// array of threads — flatten
|
|
70
|
+
messages = threads.flat();
|
|
71
|
+
} else {
|
|
72
|
+
messages = threads;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
76
|
+
const msg = messages[i];
|
|
77
|
+
if (msg?.role === "assistant") {
|
|
78
|
+
if (typeof msg.content === "string" && msg.content.trim()) {
|
|
79
|
+
return msg.content.trim();
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(msg.content)) {
|
|
82
|
+
const text = msg.content
|
|
83
|
+
.filter((c: any) => c?.type === "text")
|
|
84
|
+
.map((c: any) => c.text)
|
|
85
|
+
.join("\n")
|
|
86
|
+
.trim();
|
|
87
|
+
if (text) return text;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check whether a process id is still alive. Used as a fallback liveness check
|
|
96
|
+
* so we don't wait forever on a task whose process crashed without writing a
|
|
97
|
+
* terminal status.
|
|
98
|
+
*/
|
|
99
|
+
function pidAlive(pid: number): boolean {
|
|
100
|
+
try {
|
|
101
|
+
// signal 0 doesn't kill — it only checks for existence/permission.
|
|
102
|
+
process.kill(pid, 0);
|
|
103
|
+
return true;
|
|
104
|
+
} catch (e: any) {
|
|
105
|
+
// ESRCH = no such process. EPERM = exists but not ours (still alive).
|
|
106
|
+
return e?.code === "EPERM";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Discover the pid of a spawned agent task. AgentSyncFs.createTask records the
|
|
112
|
+
* agent subprocess's own `process.pid` into metadata.json, so we read the
|
|
113
|
+
* `pid` (or legacy `processId`) field from there. If it's absent (e.g. an older
|
|
114
|
+
* task created before this was persisted), we skip the liveness check.
|
|
115
|
+
*/
|
|
116
|
+
function metaPid(meta: any | null): number | undefined {
|
|
117
|
+
const pid = meta?.pid ?? meta?.processId;
|
|
118
|
+
return typeof pid === "number" ? pid : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
122
|
+
|
|
123
|
+
export interface WaitForAgentCompletedParams {
|
|
124
|
+
/** The taskId of the agent to wait for. */
|
|
125
|
+
taskId: string;
|
|
126
|
+
/** Max time to wait in milliseconds before returning timedOut. Default 30 min. */
|
|
127
|
+
timeoutMs?: number;
|
|
128
|
+
/** How often to poll status in milliseconds. Default 2000ms. */
|
|
129
|
+
pollIntervalMs?: number;
|
|
130
|
+
_ctx?: ToolCallContext;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Block until a spawned agent task reaches a terminal status, then return a
|
|
135
|
+
* structured result: { status, costUsd, finalAnswer, artifacts }.
|
|
136
|
+
*
|
|
137
|
+
* This is the native "join" primitive for orchestration — it must be a real
|
|
138
|
+
* tool (not a script polling loop) because the script sandbox caps `sleep` and
|
|
139
|
+
* burns a tool call per poll. It reads the consolidated status from
|
|
140
|
+
* metadata.json / status.txt and falls back to a process-liveness check so a
|
|
141
|
+
* crashed child doesn't hang the parent forever.
|
|
142
|
+
*/
|
|
143
|
+
export async function waitForAgentCompleted(
|
|
144
|
+
params: WaitForAgentCompletedParams
|
|
145
|
+
): Promise<string> {
|
|
146
|
+
const {
|
|
147
|
+
taskId,
|
|
148
|
+
timeoutMs = 30 * 60 * 1000,
|
|
149
|
+
pollIntervalMs = 2000,
|
|
150
|
+
} = params;
|
|
151
|
+
|
|
152
|
+
if (!taskId) {
|
|
153
|
+
return JSON.stringify({
|
|
154
|
+
taskId,
|
|
155
|
+
status: "error",
|
|
156
|
+
costUsd: 0,
|
|
157
|
+
error: "taskId is required for waitForAgentCompleted",
|
|
158
|
+
} as AgentTaskResult);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const taskDir = path.join(agentsDir(), taskId);
|
|
162
|
+
if (!fs.existsSync(taskDir)) {
|
|
163
|
+
return JSON.stringify({
|
|
164
|
+
taskId,
|
|
165
|
+
status: "error",
|
|
166
|
+
costUsd: 0,
|
|
167
|
+
error: `No agent task directory found for taskId: ${taskId}`,
|
|
168
|
+
} as AgentTaskResult);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const start = Date.now();
|
|
172
|
+
const interval = Math.max(250, pollIntervalMs);
|
|
173
|
+
let missingProcessPolls = 0;
|
|
174
|
+
|
|
175
|
+
while (true) {
|
|
176
|
+
const meta = readMeta(taskId);
|
|
177
|
+
const statusFile = readStatusFile(taskId);
|
|
178
|
+
const status = resolveStatus(meta, statusFile);
|
|
179
|
+
|
|
180
|
+
if (TERMINAL_STATUSES.has(status)) {
|
|
181
|
+
const finalAnswer =
|
|
182
|
+
meta?.result ?? lastAssistantText(meta?.threads);
|
|
183
|
+
const result: AgentTaskResult = {
|
|
184
|
+
taskId,
|
|
185
|
+
status,
|
|
186
|
+
costUsd: meta?.totalCostUsd ?? 0,
|
|
187
|
+
finalAnswer,
|
|
188
|
+
artifacts: meta?.artifacts,
|
|
189
|
+
elapsedMs: Date.now() - start,
|
|
190
|
+
};
|
|
191
|
+
return JSON.stringify(result);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Liveness fallback: if metadata exposes a pid and it is no longer alive,
|
|
195
|
+
// but no terminal status was written, treat the task as failed after a
|
|
196
|
+
// couple of confirming polls (avoids a race where the process just exited
|
|
197
|
+
// but hasn't flushed its final status yet).
|
|
198
|
+
const pid = metaPid(meta);
|
|
199
|
+
if (pid !== undefined && !pidAlive(pid)) {
|
|
200
|
+
missingProcessPolls++;
|
|
201
|
+
if (missingProcessPolls >= 2) {
|
|
202
|
+
const finalAnswer =
|
|
203
|
+
meta?.result ?? lastAssistantText(meta?.threads);
|
|
204
|
+
return JSON.stringify({
|
|
205
|
+
taskId,
|
|
206
|
+
status: meta?.result ? "completed" : "failed",
|
|
207
|
+
costUsd: meta?.totalCostUsd ?? 0,
|
|
208
|
+
finalAnswer,
|
|
209
|
+
artifacts: meta?.artifacts,
|
|
210
|
+
elapsedMs: Date.now() - start,
|
|
211
|
+
error: meta?.result
|
|
212
|
+
? undefined
|
|
213
|
+
: "Agent process exited without writing a terminal status",
|
|
214
|
+
} as AgentTaskResult);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
missingProcessPolls = 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (Date.now() - start >= timeoutMs) {
|
|
221
|
+
return JSON.stringify({
|
|
222
|
+
taskId,
|
|
223
|
+
status,
|
|
224
|
+
costUsd: meta?.totalCostUsd ?? 0,
|
|
225
|
+
finalAnswer: meta?.result ?? lastAssistantText(meta?.threads),
|
|
226
|
+
artifacts: meta?.artifacts,
|
|
227
|
+
elapsedMs: Date.now() - start,
|
|
228
|
+
timedOut: true,
|
|
229
|
+
} as AgentTaskResult);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await sleep(interval);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const waitForAgentCompletedDefinition: Tool = {
|
|
237
|
+
type: "function",
|
|
238
|
+
function: {
|
|
239
|
+
name: "waitForAgentCompleted",
|
|
240
|
+
description:
|
|
241
|
+
"Block until a spawned agent task (started via startAgentTask or runGenerate) reaches a " +
|
|
242
|
+
"terminal status (completed/failed/killed), then return a structured JSON result with " +
|
|
243
|
+
"{ status, costUsd, finalAnswer, artifacts, elapsedMs }. This is the native way to 'join' a " +
|
|
244
|
+
"subagent — prefer it over shell polling loops with sleep. Returns immediately if the task is " +
|
|
245
|
+
"already finished, and gives up with timedOut:true after timeoutMs.",
|
|
246
|
+
parameters: {
|
|
247
|
+
type: "object",
|
|
248
|
+
positional: false,
|
|
249
|
+
properties: {
|
|
250
|
+
taskId: {
|
|
251
|
+
type: "string",
|
|
252
|
+
description:
|
|
253
|
+
"The taskId of the agent task to wait for (as returned/used by startAgentTask).",
|
|
254
|
+
},
|
|
255
|
+
timeoutMs: {
|
|
256
|
+
type: "number",
|
|
257
|
+
description:
|
|
258
|
+
"Maximum time to wait in milliseconds before returning with timedOut:true. Default: 1800000 (30 min).",
|
|
259
|
+
},
|
|
260
|
+
pollIntervalMs: {
|
|
261
|
+
type: "number",
|
|
262
|
+
description:
|
|
263
|
+
"How often to poll the task status in milliseconds. Default: 2000.",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
required: ["taskId"],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
};
|
package/src/ai.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { readFile } from "./utils";
|
|
|
4
4
|
/** Options forwarded to startAgentTask when a generation source has an `agent` field */
|
|
5
5
|
export type AgentOptions = {
|
|
6
6
|
syncFs?: boolean;
|
|
7
|
+
syncRemote?: boolean;
|
|
7
8
|
taskId?: string;
|
|
8
9
|
maxTimeLimit?: number;
|
|
9
10
|
maxSpendLimit?: number;
|
|
@@ -143,6 +143,32 @@ export class AgentModule extends BaseChatModule {
|
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
145
|
},
|
|
146
|
+
{
|
|
147
|
+
name: "compact",
|
|
148
|
+
description:
|
|
149
|
+
"Force the attached agent to compact (summarize) its conversation now, dropping back to a much cheaper context size",
|
|
150
|
+
modes: ["agent:attached"],
|
|
151
|
+
handler: async (_args: string[]): Promise<void> => {
|
|
152
|
+
if (!this.attachedAgent) {
|
|
153
|
+
console.log("No attached agent to compact.");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (typeof this.attachedAgent.requestCompact !== "function") {
|
|
157
|
+
console.log(
|
|
158
|
+
"This agent does not support manual compaction (remote/watcher-backed agents compact automatically)."
|
|
159
|
+
);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.attachedAgent.requestCompact();
|
|
163
|
+
// Do NOT interrupt — compaction should not cancel the in-progress
|
|
164
|
+
// tool call / AI completion. The _forceCompact flag is picked up at
|
|
165
|
+
// the compression check on the next loop iteration, once the current
|
|
166
|
+
// step finishes naturally.
|
|
167
|
+
console.log(
|
|
168
|
+
"🗜 Compaction requested — the agent will summarize and shrink its context on the next step."
|
|
169
|
+
);
|
|
170
|
+
},
|
|
171
|
+
},
|
|
146
172
|
{
|
|
147
173
|
name: "reasoning_effort",
|
|
148
174
|
description:
|
|
@@ -654,7 +680,6 @@ export class AgentModule extends BaseChatModule {
|
|
|
654
680
|
agentName,
|
|
655
681
|
input: resumePrompt,
|
|
656
682
|
messageId: session.knowhowMessageId,
|
|
657
|
-
existingKnowhowTaskId: session.knowhowTaskId,
|
|
658
683
|
chatHistory: [],
|
|
659
684
|
run: false, // Don't run yet, we need to set up event listeners first
|
|
660
685
|
});
|
|
@@ -724,7 +749,7 @@ export class AgentModule extends BaseChatModule {
|
|
|
724
749
|
input: string;
|
|
725
750
|
messageId?: string;
|
|
726
751
|
syncFs?: boolean;
|
|
727
|
-
|
|
752
|
+
syncRemote?: boolean;
|
|
728
753
|
provider?: string;
|
|
729
754
|
model?: string;
|
|
730
755
|
maxTimeLimit?: number; // in minutes
|
|
@@ -732,6 +757,7 @@ export class AgentModule extends BaseChatModule {
|
|
|
732
757
|
chatHistory?: ChatInteraction[];
|
|
733
758
|
run?: boolean; // whether to run immediately
|
|
734
759
|
taskId?: string; // optional pre-generated taskId
|
|
760
|
+
parentTaskId?: string; // taskId of the parent agent that spawned this task
|
|
735
761
|
}) {
|
|
736
762
|
const { input, chatHistory = [], agentName } = options;
|
|
737
763
|
const agentContext = services().Agents.getAgentContext();
|
|
@@ -773,7 +799,9 @@ export class AgentModule extends BaseChatModule {
|
|
|
773
799
|
let taskInfo: TaskInfo = {
|
|
774
800
|
taskId,
|
|
775
801
|
knowhowMessageId: options.messageId,
|
|
776
|
-
|
|
802
|
+
// When pushing to a remote task via --sync-remote, the taskId IS the
|
|
803
|
+
// remote task ID. Otherwise it will be set after creating a chat task.
|
|
804
|
+
knowhowTaskId: options.syncRemote ? taskId : undefined,
|
|
777
805
|
agentName,
|
|
778
806
|
agent,
|
|
779
807
|
initialInput: input,
|
|
@@ -799,8 +827,9 @@ export class AgentModule extends BaseChatModule {
|
|
|
799
827
|
prompt: input,
|
|
800
828
|
messageId: options.messageId,
|
|
801
829
|
syncFs: options.syncFs,
|
|
802
|
-
|
|
830
|
+
syncRemote: options.syncRemote,
|
|
803
831
|
agentName,
|
|
832
|
+
parentTaskId: options.parentTaskId,
|
|
804
833
|
});
|
|
805
834
|
|
|
806
835
|
const webTaskId = syncer.getCreatedWebTaskId();
|
|
@@ -947,33 +976,27 @@ export class AgentModule extends BaseChatModule {
|
|
|
947
976
|
role: "assistant",
|
|
948
977
|
});
|
|
949
978
|
};
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
979
|
+
// Only register these inline render listeners for the direct CLI path
|
|
980
|
+
// (options.run === true). The interactive chat / resume / attach paths
|
|
981
|
+
// call attachedAgentChatLoop -> wireAgentRendering, which registers its
|
|
982
|
+
// own keyed render listeners. Registering both would render every
|
|
983
|
+
// toolCall / toolResult / agentMessage twice.
|
|
984
|
+
if (options.run) {
|
|
985
|
+
if (agent.eventTypes.toolCall) {
|
|
986
|
+
agent.agentEvents.on(agent.eventTypes.toolCall, toolCallHandler);
|
|
987
|
+
}
|
|
988
|
+
if (agent.eventTypes.toolUsed) {
|
|
989
|
+
agent.agentEvents.on(agent.eventTypes.toolUsed, toolResultHandler);
|
|
990
|
+
}
|
|
991
|
+
if (agent.eventTypes.agentSay) {
|
|
992
|
+
agent.agentEvents.on(agent.eventTypes.agentSay, agentSayHandler);
|
|
993
|
+
}
|
|
958
994
|
}
|
|
959
995
|
// Track the active task on the renderer so it doesn't filter out events
|
|
960
996
|
this.renderer.setActiveTaskId(taskId);
|
|
961
997
|
this.activeAgentTaskId = taskId;
|
|
962
998
|
|
|
963
999
|
const taskCompleted = new Promise<string>((resolve) => {
|
|
964
|
-
// Remove tool rendering listeners
|
|
965
|
-
if (agent.eventTypes.toolCall) {
|
|
966
|
-
agent.agentEvents.removeListener(agent.eventTypes.toolCall, toolCallHandler);
|
|
967
|
-
}
|
|
968
|
-
if (agent.eventTypes.toolUsed) {
|
|
969
|
-
agent.agentEvents.removeListener(agent.eventTypes.toolUsed, toolResultHandler);
|
|
970
|
-
}
|
|
971
|
-
if (agent.eventTypes.agentSay) {
|
|
972
|
-
agent.agentEvents.removeListener(agent.eventTypes.agentSay, agentSayHandler);
|
|
973
|
-
}
|
|
974
|
-
// Clear active task on renderer
|
|
975
|
-
this.renderer.setActiveTaskId(undefined);
|
|
976
|
-
this.activeAgentTaskId = undefined;
|
|
977
1000
|
agent.agentEvents.once(agent.eventTypes.done, async (doneMsg) => {
|
|
978
1001
|
console.log("🎯 [AgentModule] Task Completed");
|
|
979
1002
|
done = true;
|
|
@@ -994,6 +1017,19 @@ export class AgentModule extends BaseChatModule {
|
|
|
994
1017
|
);
|
|
995
1018
|
// Update task info
|
|
996
1019
|
taskInfo = this.taskRegistry.get(taskId);
|
|
1020
|
+
// Remove tool rendering listeners
|
|
1021
|
+
if (agent.eventTypes.toolCall) {
|
|
1022
|
+
agent.agentEvents.removeListener(agent.eventTypes.toolCall, toolCallHandler);
|
|
1023
|
+
}
|
|
1024
|
+
if (agent.eventTypes.toolUsed) {
|
|
1025
|
+
agent.agentEvents.removeListener(agent.eventTypes.toolUsed, toolResultHandler);
|
|
1026
|
+
}
|
|
1027
|
+
if (agent.eventTypes.agentSay) {
|
|
1028
|
+
agent.agentEvents.removeListener(agent.eventTypes.agentSay, agentSayHandler);
|
|
1029
|
+
}
|
|
1030
|
+
// Clear active task on renderer
|
|
1031
|
+
this.renderer.setActiveTaskId(undefined);
|
|
1032
|
+
this.activeAgentTaskId = undefined;
|
|
997
1033
|
|
|
998
1034
|
// Wait for AgentSync to finish before resolving
|
|
999
1035
|
await syncer.waitForFinalization();
|
|
@@ -1092,6 +1128,7 @@ export class AgentModule extends BaseChatModule {
|
|
|
1092
1128
|
threads: Message[][];
|
|
1093
1129
|
messageId?: string;
|
|
1094
1130
|
taskId?: string;
|
|
1131
|
+
interactive?: boolean;
|
|
1095
1132
|
}): Promise<{ taskCompleted: Promise<string> }> {
|
|
1096
1133
|
const { agentName, input, threads, messageId, taskId } = options;
|
|
1097
1134
|
|
|
@@ -1147,11 +1184,36 @@ export class AgentModule extends BaseChatModule {
|
|
|
1147
1184
|
agentName,
|
|
1148
1185
|
input: resumePrompt,
|
|
1149
1186
|
messageId,
|
|
1150
|
-
|
|
1187
|
+
taskId,
|
|
1188
|
+
// When resuming a remote task (no messageId), keep pushing work to the
|
|
1189
|
+
// remote task identified by taskId.
|
|
1190
|
+
syncRemote: !messageId && !!taskId,
|
|
1151
1191
|
run: false,
|
|
1152
1192
|
});
|
|
1153
1193
|
|
|
1154
|
-
//
|
|
1194
|
+
// Interactive (chat REPL) resume: route through the attached chat loop so the
|
|
1195
|
+
// renderer is wired the same way as a normal chat interaction / /attach, and
|
|
1196
|
+
// the user gets agent:attached mode commands (/poke, /detach, /kill, /logs).
|
|
1197
|
+
if (options.interactive) {
|
|
1198
|
+
await this.attachedAgentChatLoop(
|
|
1199
|
+
result.taskId,
|
|
1200
|
+
result.agent,
|
|
1201
|
+
resumePrompt,
|
|
1202
|
+
resumeMessages
|
|
1203
|
+
);
|
|
1204
|
+
return { taskCompleted: result.taskCompleted };
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Non-interactive (headless CLI `knowhow agent --resume`) path: setupAgent was
|
|
1208
|
+
// called with run:false, so no inline render listeners were registered. Wire
|
|
1209
|
+
// rendering here (same keyed listeners as attachedAgentChatLoop) so the CLI
|
|
1210
|
+
// still shows the agent's messages / tool calls, then start the agent.
|
|
1211
|
+
this.wireAgentRendering(
|
|
1212
|
+
result.taskId,
|
|
1213
|
+
result.agent.agentEvents,
|
|
1214
|
+
result.agent.eventTypes,
|
|
1215
|
+
result.agent.name
|
|
1216
|
+
);
|
|
1155
1217
|
result.agent.call(resumePrompt, resumeMessages);
|
|
1156
1218
|
|
|
1157
1219
|
return { taskCompleted: result.taskCompleted };
|
|
@@ -395,6 +395,7 @@ export class SessionsModule extends BaseChatModule {
|
|
|
395
395
|
taskId: id,
|
|
396
396
|
threads: normalizedThreads,
|
|
397
397
|
input: additionalContext?.trim() || initialInput || "",
|
|
398
|
+
interactive: true,
|
|
398
399
|
});
|
|
399
400
|
return;
|
|
400
401
|
} catch (e: any) {
|
|
@@ -770,18 +771,15 @@ export class SessionsModule extends BaseChatModule {
|
|
|
770
771
|
await watcher.startWatching(taskId);
|
|
771
772
|
this.agentModule.setActiveSyncedWatcher(watcher);
|
|
772
773
|
|
|
773
|
-
// Wire rendering via AgentModule utility (handles cleanup on detach)
|
|
774
|
-
this.agentModule.wireAgentRendering(taskId, watcher.agentEvents, watcher.eventTypes, watcher.agentName);
|
|
775
|
-
watcher.agentEvents.once(watcher.eventTypes.done, (output) => {
|
|
776
|
-
console.log(Marked.parse(output));
|
|
777
|
-
});
|
|
778
|
-
|
|
779
774
|
const context = this.chatService?.getContext();
|
|
780
775
|
if (context) context.activeAgentTaskId = taskId;
|
|
781
776
|
|
|
782
777
|
console.log(`📁 Attached to filesystem agent: ${taskId}`);
|
|
783
778
|
|
|
784
779
|
// Enter interactive loop — this sets mode to "agent:attached" and blocks until detach/done/kill
|
|
780
|
+
// attachedAgentChatLoop calls wireAgentRendering internally — do NOT call it here too,
|
|
781
|
+
// otherwise two sets of listeners are registered and output is doubled (or, after detach,
|
|
782
|
+
// the wrong agentEvents object holds the listeners).
|
|
785
783
|
const fsWatcherAgent = new WatcherBackedAgent(watcher);
|
|
786
784
|
await this.agentModule.attachedAgentChatLoop(taskId, fsWatcherAgent);
|
|
787
785
|
}
|
|
@@ -811,15 +809,13 @@ export class SessionsModule extends BaseChatModule {
|
|
|
811
809
|
await watcher.startWatching(taskId);
|
|
812
810
|
this.agentModule.setActiveSyncedWatcher(watcher);
|
|
813
811
|
|
|
814
|
-
// Wire rendering via AgentModule utility (handles cleanup on detach)
|
|
815
|
-
this.agentModule.wireAgentRendering(taskId, watcher.agentEvents, watcher.eventTypes, watcher.agentName);
|
|
816
|
-
|
|
817
812
|
const context = this.chatService?.getContext();
|
|
818
813
|
if (context) context.activeAgentTaskId = taskId;
|
|
819
814
|
|
|
820
815
|
console.log(`🌐 Attached to web agent: ${taskId}`);
|
|
821
816
|
|
|
822
|
-
// Enter interactive loop —
|
|
817
|
+
// Enter interactive loop — wireAgentRendering is called inside attachedAgentChatLoop.
|
|
818
|
+
// Do NOT call it here too (would double-register listeners).
|
|
823
819
|
const webWatcherAgent = new WatcherBackedAgent(watcher);
|
|
824
820
|
await this.agentModule.attachedAgentChatLoop(taskId, webWatcherAgent);
|
|
825
821
|
}
|
|
@@ -86,6 +86,9 @@ export const ContextLimits: Record<string, number> = {
|
|
|
86
86
|
// ─── Anthropic ────────────────────────────────────────────────────────────
|
|
87
87
|
[Models.anthropic.Opus4_8Fast]: 1_000_000,
|
|
88
88
|
[Models.anthropic.Opus4_8]: 1_000_000,
|
|
89
|
+
// Claude Opus 5 (launched 2026-06-09, 1M context window)
|
|
90
|
+
[Models.anthropic.Opus5]: 1_000_000,
|
|
91
|
+
[Models.anthropic.Opus5Fast]: 1_000_000,
|
|
89
92
|
// Claude 5 generation (launched 2026-06-09, 1M context window)
|
|
90
93
|
[Models.anthropic.Fable5]: 1_000_000,
|
|
91
94
|
[Models.anthropic.Sonnet5]: 1_000_000,
|
|
@@ -101,6 +104,10 @@ export const ContextLimits: Record<string, number> = {
|
|
|
101
104
|
[Models.anthropic.Sonnet4]: 200_000,
|
|
102
105
|
[Models.anthropic.Sonnet4_5]: 200_000,
|
|
103
106
|
[Models.anthropic.Haiku4_5]: 200_000,
|
|
107
|
+
// Short aliases (resolve to their latest dated version)
|
|
108
|
+
[Models.anthropic.Sonnet4_5Short]: 200_000,
|
|
109
|
+
[Models.anthropic.Haiku4_5Short]: 200_000,
|
|
110
|
+
[Models.anthropic.Opus4_5Short]: 1_000_000,
|
|
104
111
|
[Models.anthropic.Sonnet3_7]: 200_000,
|
|
105
112
|
[Models.anthropic.Sonnet3_5]: 200_000,
|
|
106
113
|
[Models.anthropic.Opus3]: 200_000,
|
|
@@ -115,6 +122,20 @@ export const ContextLimits: Record<string, number> = {
|
|
|
115
122
|
[Models.anthropic.Sonnet4_0]: 200_000,
|
|
116
123
|
|
|
117
124
|
// ─── Google ───────────────────────────────────────────────────────────────
|
|
125
|
+
// Gemini 3.6 / 3.5 (text)
|
|
126
|
+
[Models.google.Gemini_36_Flash]: 1_000_000,
|
|
127
|
+
[Models.google.Gemini_35_Flash]: 1_000_000,
|
|
128
|
+
[Models.google.Gemini_35_Flash_Lite]: 1_000_000,
|
|
129
|
+
[Models.google.Gemini_35_Live_Translate_Preview]: 1_000_000,
|
|
130
|
+
// Gemini 3.x (text / multimodal reasoning)
|
|
131
|
+
[Models.google.Gemini_31_Flash_Lite]: 1_000_000,
|
|
132
|
+
[Models.google.Gemini_31_Flash_TTS_Preview]: 1_000_000,
|
|
133
|
+
[Models.google.Gemini_3_Pro_Preview]: 1_000_000,
|
|
134
|
+
[Models.google.Gemini_Omni_Flash_Preview]: 1_000_000,
|
|
135
|
+
[Models.google.Gemini_25_Computer_Use_Preview]: 1_000_000,
|
|
136
|
+
[Models.google.Gemini_20_Flash_Lite]: 1_000_000,
|
|
137
|
+
[Models.google.Gemini_Robotics_ER_16_Preview]: 1_000_000,
|
|
138
|
+
[Models.google.Gemini_Robotics_ER_15_Preview]: 1_000_000,
|
|
118
139
|
[Models.google.Gemini_31_Pro_Preview]: 1_000_000,
|
|
119
140
|
[Models.google.Gemini_31_Flash_Image_Preview]: 1_000_000,
|
|
120
141
|
[Models.google.Gemini_31_Flash_Lite_Preview]: 1_000_000,
|
|
@@ -142,6 +163,11 @@ export const ContextLimits: Record<string, number> = {
|
|
|
142
163
|
[Models.google.Veo_3_Fast]: 0,
|
|
143
164
|
[Models.google.Veo_3_1]: 0,
|
|
144
165
|
[Models.google.Veo_3_1_Fast]: 0,
|
|
166
|
+
[Models.google.Veo_3_1_Lite]: 0,
|
|
167
|
+
// Google image generation models — no text context window; use 0
|
|
168
|
+
[Models.google.Gemini_31_Flash_Image]: 0,
|
|
169
|
+
[Models.google.Gemini_31_Flash_Lite_Image]: 0,
|
|
170
|
+
[Models.google.Gemini_3_Pro_Image]: 0,
|
|
145
171
|
[Models.google.Gemini_20_Flash]: 1_000_000,
|
|
146
172
|
[Models.google.Gemini_20_Flash_Preview_Image_Generation]: 1_000_000,
|
|
147
173
|
[Models.google.Gemini_20_Flash_Live]: 1_000_000,
|
|
@@ -20,6 +20,8 @@ export const AnthropicModels = {
|
|
|
20
20
|
Sonnet5: "claude-sonnet-5",
|
|
21
21
|
Mythos5: "claude-mythos-5",
|
|
22
22
|
MythosPreview: "claude-mythos-preview",
|
|
23
|
+
Opus5: "claude-opus-5",
|
|
24
|
+
Opus5Fast: "claude-opus-5-fast",
|
|
23
25
|
Opus4_8: "claude-opus-4-8",
|
|
24
26
|
Opus4_8Fast: "claude-opus-4-8-fast",
|
|
25
27
|
Opus4_7: "claude-opus-4-7",
|
|
@@ -52,6 +54,7 @@ export const AnthropicTextModels: string[] = [
|
|
|
52
54
|
AnthropicModels.Opus4_8,
|
|
53
55
|
AnthropicModels.Fable5,
|
|
54
56
|
AnthropicModels.Sonnet5,
|
|
57
|
+
AnthropicModels.Opus5,
|
|
55
58
|
AnthropicModels.Opus4_7,
|
|
56
59
|
AnthropicModels.Opus4_6,
|
|
57
60
|
AnthropicModels.Sonnet4_6,
|
|
@@ -64,6 +67,7 @@ export const AnthropicTextModels: string[] = [
|
|
|
64
67
|
// Models in our catalog but not yet publicly available
|
|
65
68
|
export const AnthropicLimitedAvailabilityModels: string[] = [
|
|
66
69
|
AnthropicModels.Opus4_8Fast, // fast mode pricing
|
|
70
|
+
AnthropicModels.Opus5Fast, // fast mode pricing (research preview)
|
|
67
71
|
AnthropicModels.Mythos5, // invitation-only (Project Glasswing)
|
|
68
72
|
AnthropicModels.MythosPreview, // invitation-only (defensive cybersecurity)
|
|
69
73
|
AnthropicModels.Opus4_6Fast, // 404 – not publicly available yet
|
|
@@ -74,6 +78,11 @@ export const AnthropicLimitedAvailabilityModels: string[] = [
|
|
|
74
78
|
const AnthropicAllModels: string[] = [
|
|
75
79
|
...AnthropicTextModels,
|
|
76
80
|
...AnthropicLimitedAvailabilityModels,
|
|
81
|
+
// Short aliases (resolve to their latest dated version) — kept in the catalog
|
|
82
|
+
// so cost tracking works whether the user passes the alias or the dated id.
|
|
83
|
+
AnthropicModels.Sonnet4_5Short,
|
|
84
|
+
AnthropicModels.Haiku4_5Short,
|
|
85
|
+
AnthropicModels.Opus4_5Short,
|
|
77
86
|
// Deprecated (retirement 2026-06-15)
|
|
78
87
|
AnthropicModels.Opus4,
|
|
79
88
|
AnthropicModels.Sonnet4,
|
|
@@ -95,6 +104,9 @@ const AnthropicAllModels: string[] = [
|
|
|
95
104
|
export const AnthropicTextPricing: Record<string, ModelPricing> = {
|
|
96
105
|
[AnthropicModels.Opus4_8]: { input: 5.0, cache_write: 6.25, cache_hit: 0.5, output: 25.0 },
|
|
97
106
|
[AnthropicModels.Opus4_8Fast]: { input: 10.0, cache_write: 12.5, cache_hit: 1.0, output: 50.0, limitedAvailability: true },
|
|
107
|
+
// Claude Opus 5 (launched 2026-06-09) — recommended for complex agentic coding
|
|
108
|
+
[AnthropicModels.Opus5]: { input: 5.0, cache_write: 6.25, cache_hit: 0.5, output: 25.0 },
|
|
109
|
+
[AnthropicModels.Opus5Fast]: { input: 10.0, cache_write: 12.5, cache_hit: 1.0, output: 50.0, limitedAvailability: true },
|
|
98
110
|
// Claude 5 generation (launched 2026-06-09)
|
|
99
111
|
[AnthropicModels.Fable5]: { input: 10.0, cache_write: 12.5, cache_hit: 1.0, output: 50.0 },
|
|
100
112
|
[AnthropicModels.Sonnet5]: { input: 3.0, cache_write: 3.75, cache_hit: 0.3, output: 15.0 },
|
package/src/commands/agent.ts
CHANGED
|
@@ -67,6 +67,10 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
|
|
|
67
67
|
)
|
|
68
68
|
.option("--message-id <messageId>", "Knowhow message ID for task tracking")
|
|
69
69
|
.option("--sync-fs", "Enable filesystem-based synchronization")
|
|
70
|
+
.option(
|
|
71
|
+
"--sync-remote",
|
|
72
|
+
"Push this agent's work to a remote Knowhow task identified by --task-id (creates one if --message-id is given)"
|
|
73
|
+
)
|
|
70
74
|
.option(
|
|
71
75
|
"--behavior-id <id>",
|
|
72
76
|
"Force a specific behavior by its ID (skips trigger matching)"
|
|
@@ -79,6 +83,10 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
|
|
|
79
83
|
"--task-id <taskId>",
|
|
80
84
|
"Pre-generated task ID (used with --sync-fs for predictable agent directory path)"
|
|
81
85
|
)
|
|
86
|
+
.option(
|
|
87
|
+
"--parent-task-id <taskId>",
|
|
88
|
+
"The taskId of the parent agent that spawned this task (for reporting back)"
|
|
89
|
+
)
|
|
82
90
|
.option("--prompt-file <path>", "Custom prompt template file with {text}")
|
|
83
91
|
.option("--input <text>", "Task input (fallback to stdin if not provided)")
|
|
84
92
|
.option(
|
|
@@ -139,6 +147,20 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
|
|
|
139
147
|
process.exit(1);
|
|
140
148
|
}
|
|
141
149
|
|
|
150
|
+
// If this agent was spawned by a parent agent, inject a note into its
|
|
151
|
+
// prompt so it knows it has a parent it can communicate with — regardless
|
|
152
|
+
// of how it was launched. This is idempotent: startAgentTask no longer
|
|
153
|
+
// adds the parent line itself, so this is the single source of truth.
|
|
154
|
+
if (options.parentTaskId) {
|
|
155
|
+
input =
|
|
156
|
+
input +
|
|
157
|
+
`\n\n---\n` +
|
|
158
|
+
`PARENT AGENT: You were spawned by a parent agent (task "${options.parentTaskId}"). ` +
|
|
159
|
+
`Use the \`replyToParent\` tool to report progress, ask questions, or send your final result back to it. ` +
|
|
160
|
+
`The parent may also send you follow-up messages via your input.txt.\n` +
|
|
161
|
+
`---\n`;
|
|
162
|
+
}
|
|
163
|
+
|
|
142
164
|
await agentModule.initialize(chatService);
|
|
143
165
|
|
|
144
166
|
// Match a behavior from local disk (.knowhow/behaviors/)
|