@dynamicagents/plugins 0.12.0 → 0.13.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/README.md +58 -77
- package/dist/browser/index.d.ts +0 -2
- package/dist/browser/index.js +21 -27
- package/dist/claude-code/cards.d.ts +53 -0
- package/dist/claude-code/cards.js +272 -0
- package/dist/claude-code/config.d.ts +28 -63
- package/dist/claude-code/config.js +0 -11
- package/dist/claude-code/egress.d.ts +8 -1
- package/dist/claude-code/egress.js +42 -4
- package/dist/claude-code/events.d.ts +106 -9
- package/dist/claude-code/events.js +201 -56
- package/dist/claude-code/github.d.ts +39 -0
- package/dist/claude-code/github.js +82 -0
- package/dist/claude-code/index.d.ts +16 -90
- package/dist/claude-code/index.js +14 -276
- package/dist/claude-code/model.d.ts +152 -0
- package/dist/claude-code/model.js +342 -0
- package/dist/claude-code/run.d.ts +209 -112
- package/dist/claude-code/run.js +339 -138
- package/dist/claude-code/session.d.ts +131 -0
- package/dist/claude-code/session.js +285 -0
- package/dist/claude-code/spec.d.ts +46 -0
- package/dist/claude-code/spec.js +83 -0
- package/dist/claude-code/verified.d.ts +15 -0
- package/dist/claude-code/verified.js +15 -0
- package/dist/computer/context.d.ts +1 -5
- package/dist/computer/context.js +7 -10
- package/dist/computer/file-lock.js +5 -4
- package/dist/computer/gate.d.ts +4 -4
- package/dist/computer/gate.js +5 -5
- package/dist/computer/index.d.ts +40 -258
- package/dist/computer/index.js +71 -324
- package/dist/computer/paths.d.ts +36 -16
- package/dist/computer/paths.js +42 -29
- package/dist/computer/proxy.d.ts +45 -0
- package/dist/computer/proxy.js +234 -0
- package/dist/computer/render.d.ts +1 -40
- package/dist/computer/render.js +2 -68
- package/dist/computer/tools-exec.js +12 -13
- package/dist/computer/tools-file.d.ts +9 -1
- package/dist/computer/tools-file.js +22 -82
- package/dist/computer/{tools-find.d.ts → tools-grep.d.ts} +1 -1
- package/dist/computer/tools-grep.js +129 -0
- package/dist/repo/checkout.d.ts +4 -5
- package/dist/repo/checkout.js +31 -8
- package/dist/repo/context.d.ts +35 -9
- package/dist/repo/context.js +101 -49
- package/dist/repo/index.d.ts +73 -28
- package/dist/repo/index.js +112 -96
- package/dist/repo/tools-checks.d.ts +3 -0
- package/dist/repo/tools-checks.js +112 -0
- package/dist/repo/tools-clone.js +1 -1
- package/dist/repo/tools-pr.d.ts +14 -0
- package/dist/repo/{tools-forge.js → tools-pr.js} +53 -36
- package/dist/repo/tools-review.d.ts +9 -0
- package/dist/repo/tools-review.js +67 -47
- package/dist/repo/tools-worktree.js +144 -60
- package/dist/repo/tools-worktrees.d.ts +12 -0
- package/dist/repo/tools-worktrees.js +48 -0
- package/dist/repo/url.d.ts +2 -2
- package/dist/repo/url.js +7 -7
- package/dist/scratch/index.d.ts +4 -4
- package/dist/scratch/index.js +12 -11
- package/dist/{computer → workspace}/advisory.d.ts +4 -4
- package/dist/{computer → workspace}/advisory.js +2 -2
- package/dist/{computer/host → workspace}/ca-trust.d.ts +8 -1
- package/dist/{computer/host → workspace}/ca-trust.js +12 -3
- package/dist/{computer/host → workspace}/container-fault.d.ts +3 -3
- package/dist/{computer/host → workspace}/container-fault.js +23 -8
- package/dist/workspace/exec.d.ts +94 -0
- package/dist/workspace/exec.js +153 -0
- package/dist/workspace/format.d.ts +36 -0
- package/dist/workspace/format.js +57 -0
- package/dist/{computer/host → workspace}/git-host.d.ts +21 -4
- package/dist/{computer/host → workspace}/git-host.js +72 -2
- package/dist/{computer/host → workspace}/git-identity.d.ts +1 -1
- package/dist/{computer/host → workspace}/git-identity.js +1 -1
- package/dist/workspace/index.d.ts +43 -47
- package/dist/workspace/index.js +52 -112
- package/dist/{computer/host → workspace}/install-job.d.ts +4 -4
- package/dist/{computer/host → workspace}/install-job.js +276 -176
- package/dist/{computer → workspace}/install.d.ts +8 -8
- package/dist/{computer → workspace}/install.js +3 -3
- package/dist/{computer/host/workspace.d.ts → workspace/object.d.ts} +105 -27
- package/dist/{computer/host/workspace.js → workspace/object.js} +217 -48
- package/dist/workspace/open.d.ts +95 -0
- package/dist/workspace/open.js +63 -0
- package/dist/workspace/read.d.ts +16 -0
- package/dist/workspace/read.js +22 -0
- package/dist/{computer → workspace}/shell.d.ts +5 -5
- package/dist/{computer → workspace}/shell.js +6 -6
- package/dist/{computer/host → workspace}/wake.d.ts +1 -1
- package/package.json +16 -36
- package/dist/arc-agi/analysis.d.ts +0 -322
- package/dist/arc-agi/analysis.js +0 -802
- package/dist/arc-agi/client.d.ts +0 -82
- package/dist/arc-agi/client.js +0 -113
- package/dist/arc-agi/game-tools.d.ts +0 -24
- package/dist/arc-agi/game-tools.js +0 -26
- package/dist/arc-agi/index.d.ts +0 -40
- package/dist/arc-agi/index.js +0 -175
- package/dist/arc-agi/main-agent.d.ts +0 -24
- package/dist/arc-agi/main-agent.js +0 -62
- package/dist/arc-agi/recipe.d.ts +0 -48
- package/dist/arc-agi/recipe.js +0 -97
- package/dist/arc-agi/scorecard.d.ts +0 -118
- package/dist/arc-agi/scorecard.js +0 -163
- package/dist/arc-agi/soul.d.ts +0 -29
- package/dist/arc-agi/soul.js +0 -79
- package/dist/arc-agi/store.d.ts +0 -153
- package/dist/arc-agi/store.js +0 -215
- package/dist/arc-agi/tools.d.ts +0 -36
- package/dist/arc-agi/tools.js +0 -574
- package/dist/arc-agi/types.d.ts +0 -211
- package/dist/arc-agi/types.js +0 -19
- package/dist/claude-code/recipe.d.ts +0 -53
- package/dist/claude-code/recipe.js +0 -100
- package/dist/computer/read.d.ts +0 -133
- package/dist/computer/read.js +0 -200
- package/dist/computer/tools-find.js +0 -216
- package/dist/recall/index.d.ts +0 -105
- package/dist/recall/index.js +0 -181
- package/dist/repo/tools-forge.d.ts +0 -4
- package/dist/triage/index.d.ts +0 -360
- package/dist/triage/index.js +0 -311
- /package/dist/{computer/host → workspace}/container-deps.d.ts +0 -0
- /package/dist/{computer/host → workspace}/container-deps.js +0 -0
- /package/dist/{computer/host → workspace}/sync.d.ts +0 -0
- /package/dist/{computer/host → workspace}/sync.js +0 -0
- /package/dist/{computer/host → workspace}/wake.js +0 -0
package/dist/triage/index.js
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import { generateText, Output, tool } from "ai";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { createWorkersAI } from "workers-ai-provider";
|
|
4
|
-
import { definePlugin } from "@dynamicagents/core";
|
|
5
|
-
import { gatewayLogFields, parseTurn, sessionText } from "@dynamicagents/core/agent";
|
|
6
|
-
/**
|
|
7
|
-
* `@dynamicagents/plugins/triage` — decide whether an inbound message deserves a
|
|
8
|
-
* reply at all, *before* the tool loop runs.
|
|
9
|
-
*
|
|
10
|
-
* An agent in a shared channel sees every message, and most are not for it. Left
|
|
11
|
-
* to the main loop, that judgement is made by a model simultaneously trying to
|
|
12
|
-
* be helpful, with memory, history and half a dozen tools in view — and it
|
|
13
|
-
* degrades exactly there, and degrades **invisibly**: failing to call a
|
|
14
|
-
* decline-tool looks identical to deciding not to.
|
|
15
|
-
*
|
|
16
|
-
* Triage moves the decision somewhere it cannot be skipped. A structured-output
|
|
17
|
-
* call has no "just answer instead" escape hatch — the schema must be filled —
|
|
18
|
-
* so the question is always actually asked.
|
|
19
|
-
*
|
|
20
|
-
* Two moments, and both are needed. The gate here judges the *message*; the
|
|
21
|
-
* `no_reply` tool below lets the agent decline *late*, after looking something
|
|
22
|
-
* up and concluding there was nothing worth adding. Wiring `no_reply` into a
|
|
23
|
-
* loop is the loop's business (see {@link noReplyTool}); this module ships the
|
|
24
|
-
* pieces rather than the loop.
|
|
25
|
-
*/
|
|
26
|
-
/** Small, fast classifier. High-volume and narrow — not the chat model. */
|
|
27
|
-
const DEFAULT_TRIAGE_MODEL_ID = "@cf/qwen/qwen3-30b-a3b-fp8";
|
|
28
|
-
/** Trailing history the classifier sees, including the message being judged. */
|
|
29
|
-
const DEFAULT_HISTORY_MESSAGES = 12;
|
|
30
|
-
/** Per-message char cap in the prompt — keeps the classify call small. */
|
|
31
|
-
const DEFAULT_MESSAGE_MAX_CHARS = 500;
|
|
32
|
-
/**
|
|
33
|
-
* The classifier's output. The booleans are **not** metadata for the caller —
|
|
34
|
-
* only `should_reply` is ever read. They exist to shape the generation itself:
|
|
35
|
-
* constrained decoding fills properties in schema order, so declaring them ahead
|
|
36
|
-
* of `should_reply` forces the model to commit to the sub-judgements that
|
|
37
|
-
* determine the answer — *who is this for*, and *do I have something worth
|
|
38
|
-
* adding* — before it can name a verdict. Reordering these fields, or dropping
|
|
39
|
-
* one, changes what the model reasons about; it is not cosmetic.
|
|
40
|
-
*
|
|
41
|
-
* `reason` comes last, after the verdict, on purpose: generated afterwards it
|
|
42
|
-
* justifies the decision rather than steering it, so a logged reason can never
|
|
43
|
-
* contradict the `should_reply` it explains. That is what makes it worth reading
|
|
44
|
-
* when a misclassification needs debugging.
|
|
45
|
-
*/
|
|
46
|
-
export const triageSchema = z.object({
|
|
47
|
-
for_other_people: z
|
|
48
|
-
.boolean()
|
|
49
|
-
.describe("Is this message aimed at other people in the channel rather than at you?"),
|
|
50
|
-
for_another_agent: z
|
|
51
|
-
.boolean()
|
|
52
|
-
.describe("Is this message aimed at a different bot or agent, not at you?"),
|
|
53
|
-
can_contribute: z
|
|
54
|
-
.boolean()
|
|
55
|
-
.describe("Is there something this conversation clearly needs right now that you can add?"),
|
|
56
|
-
should_reply: z
|
|
57
|
-
.boolean()
|
|
58
|
-
.describe("Given all of the above, should you reply at all?"),
|
|
59
|
-
reason: z.string().describe("One short sentence justifying the verdict.")
|
|
60
|
-
});
|
|
61
|
-
/**
|
|
62
|
-
* The classifier's instructions. Aimed squarely at the failure this exists to
|
|
63
|
-
* fix: the agent joins a conversation legitimately, then keeps answering
|
|
64
|
-
* afterwards as unrelated messages go by. Every rule here is about *audience*
|
|
65
|
-
* and *value* — the hard, per-message calls — rather than about obeying
|
|
66
|
-
* instructions, which was never the part that broke.
|
|
67
|
-
*/
|
|
68
|
-
const TRIAGE_RULES = [
|
|
69
|
-
"You are the assistant in a shared chat channel, and you see every message — including the many that are not for you.",
|
|
70
|
-
"Decide one thing only: should you reply to the LAST message?",
|
|
71
|
-
"",
|
|
72
|
-
"Rules:",
|
|
73
|
-
"- People talking to each other, chatter, and acknowledgements need no reply from you. In a busy channel this is the common case.",
|
|
74
|
-
"- Having replied recently does not entitle you to reply now. Judge the last message on its own audience; being part of the conversation a moment ago is not a licence to answer whatever follows.",
|
|
75
|
-
"- Reply when you are directly asked something, addressed by name or @mention, or when the previous message was your own and this one answers or follows up on it.",
|
|
76
|
-
"- `can_contribute` is a high bar: something the conversation clearly needs, not merely something true you could say. Restating, agreeing, praising, or acknowledging is not contributing.",
|
|
77
|
-
"- These rules apply in every language, including when the channel is not in English.",
|
|
78
|
-
"- When you are genuinely unsure, reply. A wrong silence is invisible to the person who needed you; a wrong reply is only noise."
|
|
79
|
-
].join("\n");
|
|
80
|
-
/** Trim a message body to the triage char cap, marking it when cut. */
|
|
81
|
-
function truncate(text, max) {
|
|
82
|
-
const flat = text.trim();
|
|
83
|
-
return flat.length > max ? `${flat.slice(0, max)}…` : flat;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Render one stored message as a transcript line. User turns carry the gatekeeper's
|
|
87
|
-
* `<turn from="…">` provenance, so the speaker's name is surfaced rather than a
|
|
88
|
-
* flat "user" — without it the classifier cannot tell one participant from
|
|
89
|
-
* another, which is most of what it has to reason about. Assistant turns are
|
|
90
|
-
* labelled "you" so "the previous message was your own" is legible.
|
|
91
|
-
*/
|
|
92
|
-
function transcriptLine(message, max) {
|
|
93
|
-
const text = sessionText(message);
|
|
94
|
-
if (message.role === "assistant")
|
|
95
|
-
return `you: ${truncate(text, max)}`;
|
|
96
|
-
const turn = parseTurn(text);
|
|
97
|
-
const speaker = turn ? turn.from : "someone";
|
|
98
|
-
return `${speaker}: ${truncate(turn ? turn.body : text, max)}`;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Build the classifier prompt from the tail of the conversation.
|
|
102
|
-
*
|
|
103
|
-
* A bare message is frequently unclassifiable — "yes", "thanks", "and the second
|
|
104
|
-
* one?" only resolve against what came before — and the drift being fixed is
|
|
105
|
-
* *only* visible in context, where the classifier can see the agent has been
|
|
106
|
-
* talking and that the speaker has since turned to someone else.
|
|
107
|
-
*
|
|
108
|
-
* Exported for its own tests: it is pure, so the prompt can be asserted without
|
|
109
|
-
* an LLM.
|
|
110
|
-
*/
|
|
111
|
-
export function buildTriagePrompt(history, options = {}) {
|
|
112
|
-
const { historyMessages = DEFAULT_HISTORY_MESSAGES, messageMaxChars = DEFAULT_MESSAGE_MAX_CHARS } = options;
|
|
113
|
-
const recent = history
|
|
114
|
-
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
115
|
-
.slice(-historyMessages);
|
|
116
|
-
const transcript = recent
|
|
117
|
-
.map((m) => transcriptLine(m, messageMaxChars))
|
|
118
|
-
.join("\n");
|
|
119
|
-
return [
|
|
120
|
-
TRIAGE_RULES,
|
|
121
|
-
"",
|
|
122
|
-
"Recent messages, oldest first. The last line is the message you are judging:",
|
|
123
|
-
transcript
|
|
124
|
-
].join("\n");
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Classify whether the agent should reply to the newest message in `history`.
|
|
128
|
-
*
|
|
129
|
-
* **Fails open.** Every error is caught here and answered `true`, because the
|
|
130
|
-
* two mistakes are not symmetric: a wrong reply is noise the user can see and
|
|
131
|
-
* ignore, while a wrong silence is invisible — the person who needed the agent
|
|
132
|
-
* simply never hears back and has no way to tell that anything happened. A
|
|
133
|
-
* triage outage must therefore degrade to the previous behaviour (let the loop
|
|
134
|
-
* decide), never to a silent agent.
|
|
135
|
-
*
|
|
136
|
-
* Only `should_reply` is read; the other fields exist to steer the generation
|
|
137
|
-
* (see {@link triageSchema}) and are logged, not re-derived in code.
|
|
138
|
-
*/
|
|
139
|
-
export async function shouldReply(model, history, options = {}) {
|
|
140
|
-
try {
|
|
141
|
-
// `generateText` + `Output.object` rather than `generateObject`, which is
|
|
142
|
-
// deprecated in AI SDK 7. Same effect: the provider is sent a json_schema
|
|
143
|
-
// response format, so the model cannot answer with anything but the schema.
|
|
144
|
-
const { output } = await generateText({
|
|
145
|
-
model,
|
|
146
|
-
output: Output.object({ schema: triageSchema }),
|
|
147
|
-
prompt: buildTriagePrompt(history, options)
|
|
148
|
-
});
|
|
149
|
-
// Log the whole verdict, not just the outcome. The booleans are the
|
|
150
|
-
// reasoning — a correct verdict reached through wrong booleans means the
|
|
151
|
-
// decomposition is not carrying the decision, and will drift again on
|
|
152
|
-
// untested cases.
|
|
153
|
-
console.debug("[triage] verdict", output);
|
|
154
|
-
return output.should_reply;
|
|
155
|
-
}
|
|
156
|
-
catch (err) {
|
|
157
|
-
console.warn("[triage] classify failed, replying by default", {
|
|
158
|
-
error: String(err)
|
|
159
|
-
});
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
export function triage(config) {
|
|
164
|
-
const { ai, aiGatewayId, agentName, modelId = DEFAULT_TRIAGE_MODEL_ID, historyMessages, messageMaxChars } = config;
|
|
165
|
-
/**
|
|
166
|
-
* Built on first use, never at module scope, for the reason
|
|
167
|
-
* {@link file://../recall/index.ts} gives on its own provider — and the
|
|
168
|
-
* gateway goes on the model, never on the provider, for the reason it gives
|
|
169
|
-
* there too.
|
|
170
|
-
*
|
|
171
|
-
* The provider is what is memoized, not the model: the model carries the
|
|
172
|
-
* channel of the turn it judges, which differs turn to turn.
|
|
173
|
-
*
|
|
174
|
-
* No fallback model: triage fails open, so a second attempt on a different
|
|
175
|
-
* model buys nothing a `true` does not already buy, at twice the latency in
|
|
176
|
-
* front of every turn.
|
|
177
|
-
*/
|
|
178
|
-
let provider;
|
|
179
|
-
const classifier = (history) => {
|
|
180
|
-
if (config.model)
|
|
181
|
-
return config.model;
|
|
182
|
-
provider ??= createWorkersAI({ binding: ai });
|
|
183
|
-
if (!aiGatewayId)
|
|
184
|
-
return provider(modelId);
|
|
185
|
-
const judged = history.filter((m) => m.role === "user").at(-1);
|
|
186
|
-
const fields = gatewayLogFields({
|
|
187
|
-
agent: agentName,
|
|
188
|
-
phase: "triage",
|
|
189
|
-
channel: judged ? parseTurn(sessionText(judged))?.channel : undefined
|
|
190
|
-
});
|
|
191
|
-
return provider(modelId, { gateway: { id: aiGatewayId, ...fields } });
|
|
192
|
-
};
|
|
193
|
-
return definePlugin({
|
|
194
|
-
key: "triage",
|
|
195
|
-
/**
|
|
196
|
-
* Building the classifier is inside the guarantee, not in front of it.
|
|
197
|
-
*
|
|
198
|
-
* `shouldReply` catches its own failures, but only what happens after it is
|
|
199
|
-
* entered — and `createWorkersAI` throws synchronously when the binding is
|
|
200
|
-
* missing, before the call is ever made. A throw out of this hook is the one
|
|
201
|
-
* outcome the whole design forbids: a triage outage must degrade to *running
|
|
202
|
-
* the turn*, never to a silent agent. A host that also treats a rejected gate
|
|
203
|
-
* as `true` is a second layer, not a substitute — a plugin documenting
|
|
204
|
-
* fail-open cannot rely on its host to make that true.
|
|
205
|
-
*/
|
|
206
|
-
shouldHandleTurn: async ({ history }) => {
|
|
207
|
-
let model;
|
|
208
|
-
try {
|
|
209
|
-
model = classifier(history);
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
console.warn("[triage] classifier unavailable, replying by default", {
|
|
213
|
-
error: String(error)
|
|
214
|
-
});
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
return shouldReply(model, history, { historyMessages, messageMaxChars });
|
|
218
|
-
}
|
|
219
|
-
// No `requires`. `AI` is core's own mandatory binding, not this plugin's to
|
|
220
|
-
// demand — declaring it would report a core misconfiguration under this
|
|
221
|
-
// plugin's name.
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
// --- declining late: the `no_reply` tool ------------------------------------
|
|
225
|
-
/** The tool the model calls to end a turn without replying. */
|
|
226
|
-
export const NO_REPLY_TOOL_NAME = "no_reply";
|
|
227
|
-
/**
|
|
228
|
-
* Tool result when a `no_reply` call is ignored. This is only ever read by the
|
|
229
|
-
* model in one case: it has already sent something this turn (so the tool was
|
|
230
|
-
* withdrawn) yet named it anyway — the call is ignored and it must finish with a
|
|
231
|
-
* reply. When a `no_reply` call is instead honoured it ends the turn, and this
|
|
232
|
-
* result is discarded unread.
|
|
233
|
-
*/
|
|
234
|
-
export const NO_REPLY_IGNORED = "Ignored: you have already sent a message this turn — finish with a reply.";
|
|
235
|
-
/**
|
|
236
|
-
* End the turn without replying — the *late* counterpart to the pre-turn gate.
|
|
237
|
-
* The agent looks something up, concludes there is nothing worth adding, and
|
|
238
|
-
* declines.
|
|
239
|
-
*
|
|
240
|
-
* Deliberately an ordinary tool with an ordinary no-op `execute`: the loop, not
|
|
241
|
-
* the tool, decides what a `no_reply` call means (see {@link isNoReplyTurn}).
|
|
242
|
-
* Two things here are load-bearing:
|
|
243
|
-
*
|
|
244
|
-
* - **`execute` must exist.** Once the agent has spoken the tool is withdrawn,
|
|
245
|
-
* but the model can still name it (the SDK resolves calls against the
|
|
246
|
-
* unfiltered map); that call is ignored and the loop must continue to a real
|
|
247
|
-
* reply. `generateText` only continues while every tool call in a step
|
|
248
|
-
* produced an output, so without `execute` that step would halt with no reply
|
|
249
|
-
* text and surface as a failure. Executing normally keeps the counts equal.
|
|
250
|
-
* - **`reason` must stay optional.** A required field the model omits makes the
|
|
251
|
-
* SDK mark the call invalid and skip execution, halting the loop the same way.
|
|
252
|
-
*
|
|
253
|
-
* Wiring is the loop's: offer this tool until the agent has spoken, withdraw it
|
|
254
|
-
* after, and stop the run when {@link isNoReplyTurn} holds.
|
|
255
|
-
*/
|
|
256
|
-
export const noReplyTool = tool({
|
|
257
|
-
description: "End this turn without replying at all, when the message needs no answer from you (chatter, people talking to each other, anything not addressed to you) — or when you looked into it and there is genuinely nothing worth adding. Calling this ends the turn immediately, so do not pair it with a tool whose result you still need. Only available until you have sent anything this turn.",
|
|
258
|
-
inputSchema: z.object({
|
|
259
|
-
reason: z
|
|
260
|
-
.string()
|
|
261
|
-
.optional()
|
|
262
|
-
.describe("One short phrase: why this message needs no reply.")
|
|
263
|
-
}),
|
|
264
|
-
execute: async () => NO_REPLY_IGNORED
|
|
265
|
-
});
|
|
266
|
-
/**
|
|
267
|
-
* What the agent is told about {@link noReplyTool}, as a system-prompt suffix.
|
|
268
|
-
*
|
|
269
|
-
* It lives here rather than in a host's prompt file for the same reason a subtask
|
|
270
|
-
* type carries its own `capability`: everything the model is told about a
|
|
271
|
-
* capability is declared by whatever owns that capability, so uninstalling the
|
|
272
|
-
* plugin takes its advice with it. A host describing a `no_reply` tool it no
|
|
273
|
-
* longer installs is the exact drift those fields exist to end.
|
|
274
|
-
*
|
|
275
|
-
* Not a `capability` on the plugin, though, and the distinction is the design.
|
|
276
|
-
* `capability` is rendered into the soul once and is true for the whole
|
|
277
|
-
* conversation; this has to be **withdrawn mid-turn**, the moment the agent
|
|
278
|
-
* speaks, in the same `prepareStep` that withdraws the tool. Advice for a tool
|
|
279
|
-
* that is no longer on the call is worse than no advice — so the loop injects it,
|
|
280
|
-
* and the loop is what knows when.
|
|
281
|
-
*/
|
|
282
|
-
export const NO_REPLY_GUIDANCE = [
|
|
283
|
-
"",
|
|
284
|
-
"",
|
|
285
|
-
"You see every message in this channel, including the many that are not for you.",
|
|
286
|
-
"Call the `no_reply` tool to end your turn without replying whenever the message needs no answer from you: people talking to each other, chatter, acknowledgements, or anything you were not asked about. You may also look something up first and then call `no_reply` if, having looked, there is genuinely nothing worth adding.",
|
|
287
|
-
"Staying silent is the right default in a busy channel. Reply only when you are addressed, asked a question, or can add something the conversation clearly needs.",
|
|
288
|
-
"Calling `no_reply` ends your turn at once — don't pair it with a tool whose result you still need, and don't write any text with it (that text is discarded). It is unavailable once you have already sent something this turn."
|
|
289
|
-
].join("\n");
|
|
290
|
-
/** Whether a step calls `no_reply` at all — on its own or beside other tools. */
|
|
291
|
-
export function stepCallsNoReply(step) {
|
|
292
|
-
return step.toolCalls.some((c) => c.toolName === NO_REPLY_TOOL_NAME);
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Whether a run declined to reply: the agent has **not sent anything this turn**
|
|
296
|
-
* (`!repliedAny`) and its final step called `no_reply`. A `no_reply` call ends
|
|
297
|
-
* the turn the moment it appears — even beside another tool, which still runs
|
|
298
|
-
* but whose result is discarded — so the first step to call it is the last. A
|
|
299
|
-
* `no_reply` after the agent already streamed content is the one thing ignored.
|
|
300
|
-
*
|
|
301
|
-
* `!repliedAny` is the runtime guard that lets `no_reply` be a *late* decision
|
|
302
|
-
* while still forbidding it once the agent has spoken. Hiding the tool from the
|
|
303
|
-
* model merely hides it; the SDK still resolves a tool call against the full,
|
|
304
|
-
* unfiltered map, so a model that names `no_reply` after speaking executes it
|
|
305
|
-
* happily — this flag is what stops that from discarding a turn the user has
|
|
306
|
-
* already seen.
|
|
307
|
-
*/
|
|
308
|
-
export function isNoReplyTurn(repliedAny, steps) {
|
|
309
|
-
const last = steps.at(-1);
|
|
310
|
-
return !repliedAny && last !== undefined && stepCallsNoReply(last);
|
|
311
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|