@oharness/engine 0.10.0 → 0.11.0
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/dist/acp/claude.d.ts +34 -0
- package/dist/acp/claude.js +122 -0
- package/dist/acp/claude.js.map +1 -0
- package/dist/acp/client.d.ts +193 -0
- package/dist/acp/client.js +279 -0
- package/dist/acp/client.js.map +1 -0
- package/dist/acp/runner.d.ts +80 -0
- package/dist/acp/runner.js +159 -0
- package/dist/acp/runner.js.map +1 -0
- package/dist/agent/agent.d.ts +51 -1
- package/dist/agent/agent.js +322 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/hooks.js +6 -6
- package/dist/agent/hooks.js.map +1 -1
- package/dist/agent/session.d.ts +2 -0
- package/dist/agent/session.js +19 -2
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/subagent.d.ts +9 -0
- package/dist/agent/subagent.js +6 -0
- package/dist/agent/subagent.js.map +1 -1
- package/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.js +3 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/manager.js +4 -0
- package/dist/auth/manager.js.map +1 -1
- package/dist/auth/oauth.d.ts +33 -0
- package/dist/auth/oauth.js +59 -8
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/providers/anthropic.d.ts +25 -5
- package/dist/auth/providers/anthropic.js +47 -6
- package/dist/auth/providers/anthropic.js.map +1 -1
- package/dist/auth/providers/chatgpt.d.ts +8 -0
- package/dist/auth/providers/chatgpt.js +180 -0
- package/dist/auth/providers/chatgpt.js.map +1 -0
- package/dist/auth/providers/openrouter.d.ts +2 -0
- package/dist/auth/providers/openrouter.js +101 -0
- package/dist/auth/providers/openrouter.js.map +1 -0
- package/dist/auth/types.d.ts +15 -0
- package/dist/auth/types.js.map +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/plugin.d.ts +6 -1
- package/dist/browser/plugin.js.map +1 -1
- package/dist/browser/session.d.ts +47 -0
- package/dist/browser/session.js +177 -2
- package/dist/browser/session.js.map +1 -1
- package/dist/browser/tools.js +139 -18
- package/dist/browser/tools.js.map +1 -1
- package/dist/computer/cua-driver.d.ts +60 -1
- package/dist/computer/cua-driver.js +149 -5
- package/dist/computer/cua-driver.js.map +1 -1
- package/dist/computer/helper-client.d.ts +3 -1
- package/dist/computer/helper-client.js +3 -0
- package/dist/computer/helper-client.js.map +1 -1
- package/dist/computer/index.d.ts +3 -2
- package/dist/computer/index.js +1 -1
- package/dist/computer/index.js.map +1 -1
- package/dist/computer/mode.d.ts +15 -0
- package/dist/computer/mode.js +11 -1
- package/dist/computer/mode.js.map +1 -1
- package/dist/computer/session.d.ts +36 -7
- package/dist/computer/session.js +118 -17
- package/dist/computer/session.js.map +1 -1
- package/dist/computer/target.d.ts +19 -0
- package/dist/computer/target.js.map +1 -1
- package/dist/computer/tools.js +135 -8
- package/dist/computer/tools.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.js.map +1 -1
- package/dist/harness.d.ts +52 -0
- package/dist/harness.js +113 -12
- package/dist/harness.js.map +1 -1
- package/dist/mcp/manager.d.ts +15 -0
- package/dist/mcp/manager.js +28 -1
- package/dist/mcp/manager.js.map +1 -1
- package/dist/providers/catalog.json +14 -3
- package/dist/providers/models-dev.d.ts +15 -0
- package/dist/providers/models-dev.js +14 -0
- package/dist/providers/models-dev.js.map +1 -1
- package/dist/providers/piai.d.ts +38 -1
- package/dist/providers/piai.js +70 -2
- package/dist/providers/piai.js.map +1 -1
- package/dist/providers/registry.d.ts +16 -1
- package/dist/providers/registry.js +42 -11
- package/dist/providers/registry.js.map +1 -1
- package/dist/runtime-plugins/builtin/studio.d.ts +7 -0
- package/dist/runtime-plugins/builtin/studio.js +69 -4
- package/dist/runtime-plugins/builtin/studio.js.map +1 -1
- package/dist/tools/builtin/bash.js +6 -10
- package/dist/tools/builtin/bash.js.map +1 -1
- package/dist/util/shell.d.ts +30 -0
- package/dist/util/shell.js +70 -0
- package/dist/util/shell.js.map +1 -1
- package/package.json +3 -3
- package/skills/media-generation/SKILL.md +22 -1
package/dist/agent/agent.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { GOAL_MAX_TURNS, GOAL_TOOL, MAX_GOAL_NUDGES, activeGoal, goalNudge, goalPromptSection, goalTool } from "./goal.js";
|
|
2
|
-
import { COMPUTER_KEY, computerOn, computerPromptSection, isComputerTool } from "../computer/mode.js";
|
|
4
|
+
import { COMPUTER_ENABLE_TOOL, COMPUTER_KEY, computerOn, computerPromptSection, isComputerTool } from "../computer/mode.js";
|
|
3
5
|
import { randomUUID } from "node:crypto";
|
|
4
6
|
import { errorMessage } from "../core/errors.js";
|
|
5
7
|
import { estimateMessagesTokens, estimateStringTokens, estimateToolsTokens } from "../core/tokens.js";
|
|
@@ -28,6 +30,7 @@ import { createSkillTool } from "../tools/builtin/skill.js";
|
|
|
28
30
|
import { createMcpResourceTools } from "../tools/builtin/mcp-resource.js";
|
|
29
31
|
import { HookRegistry } from "./hooks.js";
|
|
30
32
|
import { buildSystemPrompt } from "./prompt.js";
|
|
33
|
+
import { AcpSignInRequired, AcpUnavailable, runAcpTurn } from "../acp/runner.js";
|
|
31
34
|
import { ASK_USER_TOOL } from "../tools/questions.js";
|
|
32
35
|
import { SubagentRegistry } from "./subagent.js";
|
|
33
36
|
function withoutSignal(opts) {
|
|
@@ -643,6 +646,8 @@ export class Agent {
|
|
|
643
646
|
// background tasks: it has none of its own.
|
|
644
647
|
const childTools = definition.tools ? this.tools.filtered(definition.tools) : this.tools.clone();
|
|
645
648
|
childTools.unregister(ASK_USER_TOOL);
|
|
649
|
+
// Nor may it switch the screen on for itself; see the COMPUTER_KEY rule above.
|
|
650
|
+
childTools.unregister(COMPUTER_ENABLE_TOOL);
|
|
646
651
|
childTools.unregister(TASK_OUTPUT_TOOL);
|
|
647
652
|
childTools.unregister(TASK_STOP_TOOL);
|
|
648
653
|
childTools.unregister(TASK_MESSAGE_TOOL);
|
|
@@ -689,6 +694,8 @@ export class Agent {
|
|
|
689
694
|
* the task say what the subagent is doing now.
|
|
690
695
|
*/
|
|
691
696
|
async runChild(child, input, signal, report) {
|
|
697
|
+
if (child.definition.runtime?.kind === "acp")
|
|
698
|
+
return this.runExternalChild(child, input, signal, report);
|
|
692
699
|
let text = "";
|
|
693
700
|
let turns = 0;
|
|
694
701
|
let usage = emptyUsage();
|
|
@@ -723,6 +730,279 @@ export class Agent {
|
|
|
723
730
|
});
|
|
724
731
|
return { text, turns, usage: { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens } };
|
|
725
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* One run of a subagent whose loop is an external agent's (ACP).
|
|
735
|
+
*
|
|
736
|
+
* The child session is still kept, as the record of the task: the prompt
|
|
737
|
+
* and the agent's answer go into it, so a background task's transcript and
|
|
738
|
+
* a follow-up after a restart read the same as any other subagent's. The
|
|
739
|
+
* agent itself starts fresh each run, so what came before rides in the
|
|
740
|
+
* prompt.
|
|
741
|
+
*/
|
|
742
|
+
async runExternalChild(child, input, signal, report) {
|
|
743
|
+
const runtime = child.definition.runtime;
|
|
744
|
+
const session = child.agent.session;
|
|
745
|
+
const earlier = session.messages
|
|
746
|
+
.map((message) => `${message.role === "user" ? "Asked" : "You answered"}: ${messageText(message).trim()}`)
|
|
747
|
+
.filter((line) => !line.endsWith(": "));
|
|
748
|
+
const prompt = earlier.length
|
|
749
|
+
? `Earlier in this task:\n\n${earlier.join("\n\n")}\n\nNow: ${input}`
|
|
750
|
+
: input;
|
|
751
|
+
// The agent's own session from the last run, when it ran in this folder:
|
|
752
|
+
// a follow-up resumes it, so the agent keeps what it learned, and only
|
|
753
|
+
// the new message is sent. Another folder (a replaced worktree) is not
|
|
754
|
+
// the same session, whatever its id.
|
|
755
|
+
const last = [...session.messages].reverse().find((message) => message.role === "assistant");
|
|
756
|
+
const previous = last?.meta?.["acpSession"];
|
|
757
|
+
const resume = typeof previous?.id === "string" && previous.cwd === session.cwd
|
|
758
|
+
? { sessionId: previous.id, prompt: input }
|
|
759
|
+
: undefined;
|
|
760
|
+
const servers = await this.forwardedMcpServers();
|
|
761
|
+
await session.addMessage({ role: "user", content: [{ type: "text", text: input }], meta: { timestamp: Date.now() } });
|
|
762
|
+
report?.turn();
|
|
763
|
+
let text;
|
|
764
|
+
let acpSession;
|
|
765
|
+
try {
|
|
766
|
+
const result = await runAcpTurn({
|
|
767
|
+
runtime,
|
|
768
|
+
cwd: session.cwd,
|
|
769
|
+
prompt,
|
|
770
|
+
...(resume ? { resume } : {}),
|
|
771
|
+
...(servers.length ? { mcpServers: servers } : {}),
|
|
772
|
+
// Its file reads and writes through the same policy as ours: in the
|
|
773
|
+
// project (or its worktree), and nowhere it was not allowed to go.
|
|
774
|
+
fs: child.agent.externalFs(),
|
|
775
|
+
...(signal ? { signal } : {}),
|
|
776
|
+
askPermission: (title, toolInput, toolCallId) => child.agent.askForExternal(title, toolInput, toolCallId, signal),
|
|
777
|
+
onActivity: (title) => report?.activity(title),
|
|
778
|
+
});
|
|
779
|
+
text = result.text || (result.stopReason === "cancelled" ? "Stopped." : "The agent finished without saying anything.");
|
|
780
|
+
acpSession = { id: result.sessionId, cwd: session.cwd };
|
|
781
|
+
}
|
|
782
|
+
catch (err) {
|
|
783
|
+
// Said as the answer rather than thrown: "sign in to Claude Code" is
|
|
784
|
+
// what the parent has to pass on, not a crash to report.
|
|
785
|
+
if (err instanceof AcpSignInRequired || err instanceof AcpUnavailable)
|
|
786
|
+
text = err.message;
|
|
787
|
+
else
|
|
788
|
+
throw err;
|
|
789
|
+
}
|
|
790
|
+
await session.addMessage({
|
|
791
|
+
role: "assistant",
|
|
792
|
+
content: [{ type: "text", text }],
|
|
793
|
+
meta: { timestamp: Date.now(), ...(acpSession ? { acpSession } : {}) },
|
|
794
|
+
});
|
|
795
|
+
await this.hooks.run({
|
|
796
|
+
event: "SubagentStop",
|
|
797
|
+
sessionId: this.session.id,
|
|
798
|
+
agentId: `${this.agentId}/${child.definition.name}`,
|
|
799
|
+
cwd: this.session.cwd,
|
|
800
|
+
});
|
|
801
|
+
// No usage: the external agent bills its own account and reports none.
|
|
802
|
+
return { text, turns: 1, usage: { inputTokens: 0, outputTokens: 0 } };
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* File access for an external agent, through this agent's workspace policy:
|
|
806
|
+
* reads where ours may read, writes where ours may write, nowhere else.
|
|
807
|
+
*/
|
|
808
|
+
externalFs() {
|
|
809
|
+
return {
|
|
810
|
+
read: async (file, line, limit) => {
|
|
811
|
+
const text = await readFile(this.resolvePath(file, "read"), "utf8");
|
|
812
|
+
if (line === undefined && limit === undefined)
|
|
813
|
+
return text;
|
|
814
|
+
const lines = text.split("\n");
|
|
815
|
+
const start = Math.max(0, (line ?? 1) - 1);
|
|
816
|
+
return lines.slice(start, limit === undefined ? undefined : start + limit).join("\n");
|
|
817
|
+
},
|
|
818
|
+
write: async (file, content) => {
|
|
819
|
+
const target = this.resolvePath(file, "write");
|
|
820
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
821
|
+
await writeFile(target, content);
|
|
822
|
+
},
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* `@claude …`: a message addressed to an external agent by name.
|
|
827
|
+
*
|
|
828
|
+
* Only an agent with an ACP runtime answers to this, and only at the start
|
|
829
|
+
* of what was typed — or of the paragraph after a reference note, which the
|
|
830
|
+
* page puts above the prompt. The note stays with the message; the name
|
|
831
|
+
* does not.
|
|
832
|
+
*/
|
|
833
|
+
directTarget(input) {
|
|
834
|
+
if (typeof input !== "string")
|
|
835
|
+
return undefined;
|
|
836
|
+
const match = /(^|\n\n)@([\w./-]+)[ \t]+(\S[\s\S]*)$/.exec(input);
|
|
837
|
+
if (!match)
|
|
838
|
+
return undefined;
|
|
839
|
+
const definition = this.subagents.get(match[2]);
|
|
840
|
+
if (definition?.runtime?.kind !== "acp")
|
|
841
|
+
return undefined;
|
|
842
|
+
const note = input.slice(0, match.index).trim();
|
|
843
|
+
return { definition, text: note ? `${note}\n\n${match[3]}` : match[3] };
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* A turn answered by an external agent instead of the model.
|
|
847
|
+
*
|
|
848
|
+
* What makes `@claude` worth having: the message goes straight to the
|
|
849
|
+
* user's own Claude Code, so it is paid for by their Claude account and
|
|
850
|
+
* nothing is spent on this harness's model in between. The agent's reply
|
|
851
|
+
* streams into the conversation as text, its tool calls as tool cards, its
|
|
852
|
+
* questions as the usual permission card; the exchange is kept as an
|
|
853
|
+
* ordinary user/assistant pair, and the agent's session id rides on the
|
|
854
|
+
* reply so the next `@claude` picks up where this one left off.
|
|
855
|
+
*/
|
|
856
|
+
async *runDirect(target, raw, signal) {
|
|
857
|
+
const { definition, text } = target;
|
|
858
|
+
const runtime = definition.runtime;
|
|
859
|
+
const finishRun = this.opts.onRunStart?.();
|
|
860
|
+
let stopReason = "end_turn";
|
|
861
|
+
try {
|
|
862
|
+
await this.session.init();
|
|
863
|
+
await this.session.addMessage(userMessage(raw));
|
|
864
|
+
yield { type: "turn_start", turn: 1, model: definition.name };
|
|
865
|
+
const previous = [...this.session.messages]
|
|
866
|
+
.reverse()
|
|
867
|
+
.find((message) => message.role === "assistant" && message.meta?.["agent"] === definition.name)
|
|
868
|
+
?.meta?.["acpSession"];
|
|
869
|
+
const resume = typeof previous?.id === "string" && previous.cwd === this.session.cwd
|
|
870
|
+
? { sessionId: previous.id, prompt: text }
|
|
871
|
+
: undefined;
|
|
872
|
+
const events = channel();
|
|
873
|
+
const started = new Map();
|
|
874
|
+
let reply = "";
|
|
875
|
+
const servers = await this.forwardedMcpServers();
|
|
876
|
+
const turn = runAcpTurn({
|
|
877
|
+
runtime,
|
|
878
|
+
cwd: this.session.cwd,
|
|
879
|
+
prompt: text,
|
|
880
|
+
...(resume ? { resume } : {}),
|
|
881
|
+
...(servers.length ? { mcpServers: servers } : {}),
|
|
882
|
+
fs: this.externalFs(),
|
|
883
|
+
signal,
|
|
884
|
+
askPermission: (title, input, toolCallId) => this.askForExternal(title, input, toolCallId, signal, definition.name),
|
|
885
|
+
onActivity: (title) => {
|
|
886
|
+
if (title === "Starting the agent…")
|
|
887
|
+
events.push({ type: "notice", message: `Starting ${definition.name}…` });
|
|
888
|
+
},
|
|
889
|
+
onUpdate: (update) => {
|
|
890
|
+
if (update.sessionUpdate === "agent_message_chunk" || update.sessionUpdate === "agent_thought_chunk") {
|
|
891
|
+
const content = update.content;
|
|
892
|
+
if (content?.type !== "text" || !content.text)
|
|
893
|
+
return;
|
|
894
|
+
if (update.sessionUpdate === "agent_thought_chunk")
|
|
895
|
+
return events.push({ type: "thinking_delta", text: content.text });
|
|
896
|
+
reply += content.text;
|
|
897
|
+
events.push({ type: "text_delta", text: content.text });
|
|
898
|
+
}
|
|
899
|
+
else if (update.sessionUpdate === "tool_call") {
|
|
900
|
+
const call = update;
|
|
901
|
+
const id = `${definition.name}:${call.toolCallId}`;
|
|
902
|
+
started.set(id, Date.now());
|
|
903
|
+
events.push({
|
|
904
|
+
type: "tool_start",
|
|
905
|
+
toolCallId: id,
|
|
906
|
+
toolName: call.kind || "tool",
|
|
907
|
+
title: call.title || call.kind || "tool",
|
|
908
|
+
input: call.rawInput ?? {},
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
else if (update.sessionUpdate === "tool_call_update") {
|
|
912
|
+
const call = update;
|
|
913
|
+
const id = `${definition.name}:${call.toolCallId}`;
|
|
914
|
+
if ((call.status !== "completed" && call.status !== "failed") || !started.has(id))
|
|
915
|
+
return;
|
|
916
|
+
const began = started.get(id);
|
|
917
|
+
started.delete(id);
|
|
918
|
+
events.push({
|
|
919
|
+
type: "tool_end",
|
|
920
|
+
toolCallId: id,
|
|
921
|
+
toolName: call.kind || "tool",
|
|
922
|
+
result: { content: [{ type: "text", text: call.status }], ...(call.status === "failed" ? { isError: true } : {}) },
|
|
923
|
+
durationMs: Date.now() - began,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
});
|
|
928
|
+
void turn.then(() => events.close(), () => events.close());
|
|
929
|
+
for await (const event of events.drain())
|
|
930
|
+
yield event;
|
|
931
|
+
let acpSession;
|
|
932
|
+
try {
|
|
933
|
+
const result = await turn;
|
|
934
|
+
acpSession = { id: result.sessionId, cwd: this.session.cwd };
|
|
935
|
+
if (result.stopReason === "cancelled" || signal.aborted)
|
|
936
|
+
stopReason = "aborted";
|
|
937
|
+
if (!reply.trim())
|
|
938
|
+
reply = result.text;
|
|
939
|
+
}
|
|
940
|
+
catch (err) {
|
|
941
|
+
if (err instanceof AcpSignInRequired || err instanceof AcpUnavailable) {
|
|
942
|
+
reply = err.message;
|
|
943
|
+
yield { type: "text_delta", text: err.message };
|
|
944
|
+
}
|
|
945
|
+
else if (signal.aborted) {
|
|
946
|
+
stopReason = "aborted";
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
stopReason = "error";
|
|
950
|
+
yield { type: "error", error: `${definition.name}: ${errorMessage(err)}`, fatal: false };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (reply.trim()) {
|
|
954
|
+
const message = {
|
|
955
|
+
role: "assistant",
|
|
956
|
+
content: [{ type: "text", text: reply }],
|
|
957
|
+
meta: { timestamp: Date.now(), agent: definition.name, ...(acpSession ? { acpSession } : {}) },
|
|
958
|
+
};
|
|
959
|
+
await this.session.addMessage(message);
|
|
960
|
+
yield { type: "assistant_message", message };
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
finally {
|
|
964
|
+
this.running = false;
|
|
965
|
+
await this.session.persist().catch(() => { });
|
|
966
|
+
finishRun?.();
|
|
967
|
+
}
|
|
968
|
+
yield { type: "done", stopReason, usage: this.session.usage, costUsd: this.session.costUsd, turns: 1 };
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* This conversation's MCP servers, in the shape ACP's `session/new` takes,
|
|
972
|
+
* so an external agent works with the same tools. HTTP ones are filtered
|
|
973
|
+
* out later for an agent that does not take them.
|
|
974
|
+
*/
|
|
975
|
+
async forwardedMcpServers() {
|
|
976
|
+
const pairs = (values) => Object.entries(values ?? {}).map(([name, value]) => ({ name, value }));
|
|
977
|
+
const found = await this.opts.mcp?.forwardable().catch(() => []) ?? [];
|
|
978
|
+
return found.map(({ name, config, headers }) => config.type === "stdio"
|
|
979
|
+
? { name, command: config.command, args: config.args ?? [], env: pairs(config.env) }
|
|
980
|
+
: { type: "http", name, url: config.url, headers: pairs({ ...config.headers, ...headers }) });
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* An external agent's own tool call, put to the person the way this
|
|
984
|
+
* harness's calls are — the same card, placed in the conversation that
|
|
985
|
+
* started the task and labelled with who is asking. Plan mode and a
|
|
986
|
+
* read-only workspace refuse; bypass allows, as it would for our own tools.
|
|
987
|
+
*/
|
|
988
|
+
async askForExternal(title, input, toolCallId, signal, from) {
|
|
989
|
+
const mode = this.session.permissions.getMode();
|
|
990
|
+
if (this.opts.workspace?.mode === "read-only" || mode === "plan" || !this.opts.permissionPrompt)
|
|
991
|
+
return false;
|
|
992
|
+
if (mode === "bypassPermissions")
|
|
993
|
+
return true;
|
|
994
|
+
const request = {
|
|
995
|
+
toolName: "external",
|
|
996
|
+
input,
|
|
997
|
+
title,
|
|
998
|
+
...this.interactionPlace(),
|
|
999
|
+
...(from ? { from } : {}),
|
|
1000
|
+
toolCallId,
|
|
1001
|
+
};
|
|
1002
|
+
const prompt = this.opts.permissionPrompt;
|
|
1003
|
+
const ask = () => prompt(request, signal);
|
|
1004
|
+
return signal ? untilAborted(signal, ask, false, false) : ask();
|
|
1005
|
+
}
|
|
726
1006
|
/** What a subagent left in its worktree, when it left anything. */
|
|
727
1007
|
async worktreeNote(child) {
|
|
728
1008
|
if (!child.worktree)
|
|
@@ -767,6 +1047,12 @@ export class Agent {
|
|
|
767
1047
|
this.running = false;
|
|
768
1048
|
throw new Error("Agent host is closed");
|
|
769
1049
|
}
|
|
1050
|
+
// Addressed to an external agent by name: its turn, not the model's.
|
|
1051
|
+
const direct = this.directTarget(input);
|
|
1052
|
+
if (direct) {
|
|
1053
|
+
yield* this.runDirect(direct, input, signal);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
770
1056
|
// Fired once per session rather than per turn: hooks use it to seed
|
|
771
1057
|
// context or announce the session, which should not repeat every prompt.
|
|
772
1058
|
if (!this.sessionStarted) {
|
|
@@ -1301,6 +1587,9 @@ export class Agent {
|
|
|
1301
1587
|
return this.tools
|
|
1302
1588
|
.visible(this.opts.toolFilter)
|
|
1303
1589
|
.filter((tool) => (goal || tool.name !== GOAL_TOOL) && (computer || !isComputerTool(tool.name)))
|
|
1590
|
+
// Asking for the computer is for a conversation without it and with
|
|
1591
|
+
// somebody there: a routine or a headless run has nobody to answer.
|
|
1592
|
+
.filter((tool) => tool.name !== COMPUTER_ENABLE_TOOL || (!computer && Boolean(this.opts.permissionPrompt)))
|
|
1304
1593
|
.filter((tool) => worktrees || tool.name !== WORKTREE_START_TOOL)
|
|
1305
1594
|
.filter((tool) => background || ![TASK_OUTPUT_TOOL, TASK_STOP_TOOL, TASK_MESSAGE_TOOL].includes(tool.name));
|
|
1306
1595
|
}
|
|
@@ -1778,4 +2067,36 @@ function touchesSkills(toolName, input) {
|
|
|
1778
2067
|
// what is advertised, so re-scanning for those would be noise.
|
|
1779
2068
|
return target.includes("skills") && target.endsWith("SKILL.md");
|
|
1780
2069
|
}
|
|
2070
|
+
/**
|
|
2071
|
+
* A queue a callback pushes into and a generator drains, for turning an
|
|
2072
|
+
* external agent's callbacks into a stream of events as they happen.
|
|
2073
|
+
*/
|
|
2074
|
+
function channel() {
|
|
2075
|
+
const items = [];
|
|
2076
|
+
let wake;
|
|
2077
|
+
let closed = false;
|
|
2078
|
+
const poke = () => {
|
|
2079
|
+
wake?.();
|
|
2080
|
+
wake = undefined;
|
|
2081
|
+
};
|
|
2082
|
+
return {
|
|
2083
|
+
push(item) {
|
|
2084
|
+
items.push(item);
|
|
2085
|
+
poke();
|
|
2086
|
+
},
|
|
2087
|
+
close() {
|
|
2088
|
+
closed = true;
|
|
2089
|
+
poke();
|
|
2090
|
+
},
|
|
2091
|
+
async *drain() {
|
|
2092
|
+
for (;;) {
|
|
2093
|
+
while (items.length)
|
|
2094
|
+
yield items.shift();
|
|
2095
|
+
if (closed)
|
|
2096
|
+
return;
|
|
2097
|
+
await new Promise((resolve) => (wake = resolve));
|
|
2098
|
+
}
|
|
2099
|
+
},
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
1781
2102
|
//# sourceMappingURL=agent.js.map
|