@linimin/pi-letscook 0.1.51 → 0.1.53
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/CHANGELOG.md +15 -0
- package/README.md +17 -11
- package/extensions/completion/driver.ts +51 -6
- package/extensions/completion/index.ts +15 -7
- package/extensions/completion/input-routing.ts +514 -45
- package/extensions/completion/prompt-surfaces.ts +298 -22
- package/extensions/completion/role-runner.ts +35 -11
- package/extensions/completion/types.ts +76 -4
- package/package.json +1 -1
- package/scripts/cook-trigger-routing-test.sh +857 -49
- package/scripts/release-check.sh +30 -26
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import { promises as fsp } from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
3
4
|
import { runCookEntry, type CompletionDriverDeps } from "./driver";
|
|
4
5
|
import {
|
|
5
|
-
|
|
6
|
+
buildCookTriggerClarificationLayout,
|
|
7
|
+
buildCookTriggerConfirmationLayout,
|
|
8
|
+
buildCookTriggerRecoveryLayout,
|
|
9
|
+
maybeWriteCookTriggerClarificationSnapshot,
|
|
6
10
|
maybeWriteCookTriggerConfirmationSnapshot,
|
|
11
|
+
maybeWriteCookTriggerRecoverySnapshot,
|
|
7
12
|
maybeWriteCookTriggerRoutingSnapshot,
|
|
8
13
|
} from "./prompt-surfaces";
|
|
9
14
|
import {
|
|
10
15
|
collectRecentDiscussionEntries,
|
|
11
16
|
hasRecentDiscussionImplementationIntent,
|
|
17
|
+
hasStructuredContextProposalSignal,
|
|
12
18
|
stripCodeBlocks,
|
|
13
19
|
} from "./proposal";
|
|
14
20
|
import {
|
|
@@ -18,9 +24,15 @@ import {
|
|
|
18
24
|
import { asString, loadCompletionSnapshot } from "./state-store";
|
|
19
25
|
import type {
|
|
20
26
|
CompletionStateSnapshot,
|
|
27
|
+
CookNaturalLanguageHandoff,
|
|
28
|
+
CookTriggerAdoptedArtifact,
|
|
29
|
+
CookTriggerClarificationAction,
|
|
30
|
+
CookTriggerClarificationCapsule,
|
|
21
31
|
CookTriggerClassification,
|
|
22
32
|
CookTriggerConfirmationAction,
|
|
23
33
|
CookTriggerDecision,
|
|
34
|
+
CookTriggerRecoveryAction,
|
|
35
|
+
CookTriggerWorkflowBias,
|
|
24
36
|
NaturalLanguageCookTriggerMode,
|
|
25
37
|
} from "./types";
|
|
26
38
|
|
|
@@ -41,13 +53,33 @@ type InputRoutingContext = {
|
|
|
41
53
|
hasPendingMessages: () => boolean;
|
|
42
54
|
};
|
|
43
55
|
|
|
56
|
+
type ContextProposal = Awaited<ReturnType<CompletionDriverDeps["deriveCookContextProposal"]>>;
|
|
57
|
+
|
|
58
|
+
type RecentSessionMessage = {
|
|
59
|
+
role: "user" | "assistant" | "custom";
|
|
60
|
+
text: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
44
63
|
const MAX_TRIGGER_CANDIDATE_LENGTH = 120;
|
|
45
64
|
const MAX_TRIGGER_CANDIDATE_LINES = 3;
|
|
65
|
+
const ADOPTED_ARTIFACT_PREVIEW_LIMIT = 280;
|
|
66
|
+
const ROUTER_BYPASS_REPLAY_PREFIX = "__pi_completion_router_bypass__:";
|
|
67
|
+
const ROUTER_FAILURE_RETRY_LIMIT = 2;
|
|
46
68
|
const CLEAR_TRIGGER_PATTERNS = [
|
|
47
|
-
/^(?:go ahead|please go ahead|proceed|let'?s do it|let'?s start|start(?: implementing| implementation)?|begin(?: implementing| implementation)?|continue(?: with implementation| implementing)?|
|
|
48
|
-
/^(?:開始(
|
|
69
|
+
/^(?:go ahead|please go ahead|proceed|let'?s do it|let'?s start|start(?: implementing| implementation| the workflow| the next round)?|begin(?: implementing| implementation| the workflow| the next round)?|continue(?: with implementation| implementing| the workflow)?|resume(?: the workflow| where we left off)?|next step|work on it|do it|ship it|let'?s do this instead|switch to this)\b/i,
|
|
70
|
+
/^(?:開始(?:做|實作|实现|落地|下一輪)|开始(?:做|实作|实现|落地|下一轮)|那就做吧|照(?:剛剛|刚刚|這個|这个|上述|上面的)?(?:討論|讨论|方向).*(?:做|實作|实现|落地)|可以開始(?:做|實作|实现|下一輪)?|可以开始(?:做|实作|实现|下一轮)?|繼續(?:做|實作|实现|往下做)|继续(?:做|实作|实现|往下做)|接著(?:做|實作|实现)|接着(?:做|实作|实现)|下一步|那改做(?:這個|这个)|先做新的那個方向|先做新的那个方向|好,開始做這個|好,开始做这个)/u,
|
|
71
|
+
];
|
|
72
|
+
const ADOPTED_PLAN_TRIGGER_PATTERNS = [
|
|
73
|
+
/^(?:use|follow|start from|begin from|work from|go with|implement from)\b.*\b(?:plan|proposal|spec|summary|notes|[\w./-]+\.md)\b/i,
|
|
74
|
+
/^(?:start|begin|implement|do)\b.*\b(?:from|using)\b.*\b(?:plan|proposal|spec|summary|[\w./-]+\.md)\b/i,
|
|
75
|
+
/^(?:照|依|按照|就照|跟著|跟着|用).*(?:剛剛|刚刚|最新|上面|上述|那份|這份|这份|這個|这个|方案|計劃|计划|提案|規格|规格|總結|总结|[\w./-]+\.md).*(?:做|開始|开始|實作|实现|落地)/u,
|
|
76
|
+
];
|
|
77
|
+
const EXPLICIT_ARTIFACT_ADOPTION_PATTERNS = [
|
|
78
|
+
/(?:\b(?:use|follow|start from|begin from|work from|go with|implement from)\b.*\b(?:plan|proposal|spec|summary|notes|[\w./-]+\.md)\b)/i,
|
|
79
|
+
/(?:照|依|按照|就照|跟著|跟着|用).*(?:剛剛|刚刚|最新|上面|上述|那份|這份|这份|這個|这个|方案|計劃|计划|提案|規格|规格|總結|总结|[\w./-]+\.md)/u,
|
|
49
80
|
];
|
|
50
81
|
const AMBIGUOUS_ACK_PATTERNS = [/^(?:ok|okay|sure|fine|yes|yeah|yep)$/i, /^(?:好|好的|可以|嗯|那就這樣|那就这样|就這樣|就这样|先這樣|先这样|收到)$/u];
|
|
82
|
+
const MARKDOWN_PATH_PATTERN = /(?:^|[\s("'`])((?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+\.md)(?=$|[\s)"'`.,;:])/g;
|
|
51
83
|
|
|
52
84
|
function roleFromEnv(): string | undefined {
|
|
53
85
|
return asString(process.env.PI_COMPLETION_ROLE);
|
|
@@ -57,12 +89,15 @@ function configuredTriggerMode(): NaturalLanguageCookTriggerMode {
|
|
|
57
89
|
const raw =
|
|
58
90
|
asString(process.env.PI_COMPLETION_TEST_TRIGGER_MODE)?.toLowerCase() ??
|
|
59
91
|
asString(process.env.PI_COMPLETION_TRIGGER_MODE)?.toLowerCase() ??
|
|
60
|
-
"
|
|
61
|
-
|
|
92
|
+
"router";
|
|
93
|
+
if (raw === "off") return "off";
|
|
94
|
+
if (raw === "router" || raw === "auto" || raw === "assist") return "router";
|
|
95
|
+
return "router";
|
|
62
96
|
}
|
|
63
97
|
|
|
64
|
-
function effectiveTriggerMode(mode: NaturalLanguageCookTriggerMode): "off" | "
|
|
65
|
-
|
|
98
|
+
function effectiveTriggerMode(mode: NaturalLanguageCookTriggerMode): "off" | "router" {
|
|
99
|
+
if (mode === "off") return "off";
|
|
100
|
+
return "router";
|
|
66
101
|
}
|
|
67
102
|
|
|
68
103
|
function triggerRoutingSnapshotPath(): string | undefined {
|
|
@@ -73,12 +108,41 @@ function triggerConfirmationSnapshotPath(): string | undefined {
|
|
|
73
108
|
return asString(process.env.PI_COMPLETION_TEST_TRIGGER_CONFIRMATION_PATH);
|
|
74
109
|
}
|
|
75
110
|
|
|
111
|
+
function triggerClarificationSnapshotPath(): string | undefined {
|
|
112
|
+
return asString(process.env.PI_COMPLETION_TEST_TRIGGER_CLARIFICATION_PATH);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function triggerRecoverySnapshotPath(): string | undefined {
|
|
116
|
+
return asString(process.env.PI_COMPLETION_TEST_TRIGGER_RECOVERY_PATH);
|
|
117
|
+
}
|
|
118
|
+
|
|
76
119
|
function triggerConfirmationOverride(): CookTriggerConfirmationAction | undefined {
|
|
77
120
|
const raw = asString(process.env.PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION)?.toLowerCase();
|
|
78
121
|
if (!raw) return undefined;
|
|
79
|
-
if (raw === "start" || raw === "start_cook" || raw === "cook") return "
|
|
80
|
-
if (raw === "
|
|
81
|
-
if (raw === "cancel") return "cancel";
|
|
122
|
+
if (raw === "start" || raw === "start_cook" || raw === "start_workflow" || raw === "cook") return "start_workflow";
|
|
123
|
+
if (raw === "send_as_normal_chat" || raw === "send-as-normal-chat" || raw === "normal_chat" || raw === "normal-chat") return "send_as_normal_chat";
|
|
124
|
+
if (raw === "cancel" || raw === "dismiss") return "cancel";
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function triggerClarificationOverride(): CookTriggerClarificationAction | undefined {
|
|
129
|
+
const raw = asString(process.env.PI_COMPLETION_TEST_TRIGGER_CLARIFICATION_ACTION)?.toLowerCase();
|
|
130
|
+
if (!raw) return undefined;
|
|
131
|
+
if (raw === "startup" || raw === "route_startup") return "route_startup";
|
|
132
|
+
if (raw === "resume" || raw === "route_resume") return "route_resume";
|
|
133
|
+
if (raw === "refocus" || raw === "route_refocus") return "route_refocus";
|
|
134
|
+
if (raw === "next_round" || raw === "next-round" || raw === "route_next_round") return "route_next_round";
|
|
135
|
+
if (raw === "send_as_normal_chat" || raw === "send-as-normal-chat" || raw === "normal_chat" || raw === "normal-chat") return "send_as_normal_chat";
|
|
136
|
+
if (raw === "cancel" || raw === "dismiss") return "cancel";
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function triggerRecoveryOverride(): CookTriggerRecoveryAction | undefined {
|
|
141
|
+
const raw = asString(process.env.PI_COMPLETION_TEST_TRIGGER_RECOVERY_ACTION)?.toLowerCase();
|
|
142
|
+
if (!raw) return undefined;
|
|
143
|
+
if (raw === "retry" || raw === "retry_routing" || raw === "retry-routing") return "retry_routing";
|
|
144
|
+
if (raw === "send_as_normal_chat" || raw === "send-as-normal-chat" || raw === "normal_chat" || raw === "normal-chat") return "send_as_normal_chat";
|
|
145
|
+
if (raw === "cancel" || raw === "dismiss") return "cancel";
|
|
82
146
|
return undefined;
|
|
83
147
|
}
|
|
84
148
|
|
|
@@ -86,6 +150,11 @@ function normalizeTriggerText(text: string): string {
|
|
|
86
150
|
return text.replace(/\s+/g, " ").trim();
|
|
87
151
|
}
|
|
88
152
|
|
|
153
|
+
function truncateInline(text: string, maxLength = ADOPTED_ARTIFACT_PREVIEW_LIMIT): string {
|
|
154
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
155
|
+
return normalized.length > maxLength ? `${normalized.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…` : normalized;
|
|
156
|
+
}
|
|
157
|
+
|
|
89
158
|
function hasImages(event: InputRoutingEvent): boolean {
|
|
90
159
|
return Array.isArray(event.images) && event.images.length > 0;
|
|
91
160
|
}
|
|
@@ -94,6 +163,10 @@ function activeWorkflowContext(snapshot: CompletionStateSnapshot | undefined): b
|
|
|
94
163
|
return Boolean(snapshot) && asString(snapshot?.state?.continuation_policy) !== "done";
|
|
95
164
|
}
|
|
96
165
|
|
|
166
|
+
function isExplicitArtifactAdoption(text: string): boolean {
|
|
167
|
+
return EXPLICIT_ARTIFACT_ADOPTION_PATTERNS.some((pattern) => pattern.test(text));
|
|
168
|
+
}
|
|
169
|
+
|
|
97
170
|
function looksLikeTriggerCandidate(text: string): boolean {
|
|
98
171
|
const normalized = normalizeTriggerText(text);
|
|
99
172
|
if (!normalized) return false;
|
|
@@ -101,7 +174,7 @@ function looksLikeTriggerCandidate(text: string): boolean {
|
|
|
101
174
|
if (text.split(/\r?\n/).length > MAX_TRIGGER_CANDIDATE_LINES) return false;
|
|
102
175
|
if (normalized.startsWith("/") || normalized.startsWith("!")) return false;
|
|
103
176
|
if (AMBIGUOUS_ACK_PATTERNS.some((pattern) => pattern.test(normalized))) return false;
|
|
104
|
-
return CLEAR_TRIGGER_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
177
|
+
return CLEAR_TRIGGER_PATTERNS.some((pattern) => pattern.test(normalized)) || ADOPTED_PLAN_TRIGGER_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
105
178
|
}
|
|
106
179
|
|
|
107
180
|
function hasRecentImplementationContext(entries: Array<{ text: string }>): boolean {
|
|
@@ -132,7 +205,8 @@ function writeRoutingDecision(event: InputRoutingEvent, decision: CookTriggerDec
|
|
|
132
205
|
action: decision.action,
|
|
133
206
|
reason: decision.reason,
|
|
134
207
|
bypassReason: decision.bypassReason ?? null,
|
|
135
|
-
|
|
208
|
+
classificationDecision: decision.classification?.decision ?? null,
|
|
209
|
+
workflowBias: decision.classification?.workflowBias ?? null,
|
|
136
210
|
confidence: decision.classification?.confidence ?? null,
|
|
137
211
|
classifierReason: decision.classification?.reason ?? null,
|
|
138
212
|
focusHint: decision.classification?.focusHint ?? null,
|
|
@@ -156,13 +230,223 @@ function classifierFailureReason(result: CookTriggerClassifierResult): string {
|
|
|
156
230
|
}
|
|
157
231
|
}
|
|
158
232
|
|
|
233
|
+
function classifierFailureLabel(result: CookTriggerClassifierResult): string {
|
|
234
|
+
switch (result.status) {
|
|
235
|
+
case "timeout":
|
|
236
|
+
return "The router classifier timed out before it could decide whether /cook should take over.";
|
|
237
|
+
case "invalid_output":
|
|
238
|
+
return "The router classifier returned invalid JSON output, so the router refused to guess.";
|
|
239
|
+
case "error":
|
|
240
|
+
default:
|
|
241
|
+
return result.errorMessage?.trim() || "The router classifier failed before it could return a valid decision.";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function routerBypassReplayText(text: string): string {
|
|
246
|
+
return `${ROUTER_BYPASS_REPLAY_PREFIX}${text}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function consumeRouterBypassReplay(event: InputRoutingEvent): string | undefined {
|
|
250
|
+
if (event.source !== "extension") return undefined;
|
|
251
|
+
if (typeof event.text !== "string" || !event.text.startsWith(ROUTER_BYPASS_REPLAY_PREFIX)) return undefined;
|
|
252
|
+
return event.text.slice(ROUTER_BYPASS_REPLAY_PREFIX.length);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function replayOriginalMessageToPrimaryAgent(
|
|
256
|
+
pi: ExtensionAPI,
|
|
257
|
+
event: InputRoutingEvent,
|
|
258
|
+
): Promise<void> {
|
|
259
|
+
await pi.sendUserMessage(routerBypassReplayText(event.text));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function extractMessageText(content: unknown): string {
|
|
263
|
+
if (typeof content === "string") return content.trim();
|
|
264
|
+
if (!Array.isArray(content)) return "";
|
|
265
|
+
return content
|
|
266
|
+
.map((item) => {
|
|
267
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) return "";
|
|
268
|
+
return item.type === "text" && typeof item.text === "string" ? item.text.trim() : "";
|
|
269
|
+
})
|
|
270
|
+
.filter((item) => item.length > 0)
|
|
271
|
+
.join("\n")
|
|
272
|
+
.trim();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function recentSessionMessages(ctx: InputRoutingContext, limit = 12): RecentSessionMessage[] {
|
|
276
|
+
const branch = ctx.sessionManager?.getBranch?.() ?? [];
|
|
277
|
+
const entries: RecentSessionMessage[] = [];
|
|
278
|
+
for (let index = branch.length - 1; index >= 0; index -= 1) {
|
|
279
|
+
const entry = branch[index];
|
|
280
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
281
|
+
if (entry.type !== "message" || typeof entry.message !== "object" || entry.message === null || Array.isArray(entry.message)) continue;
|
|
282
|
+
const role = asString(entry.message.role);
|
|
283
|
+
if (role !== "user" && role !== "assistant" && role !== "custom") continue;
|
|
284
|
+
const text = extractMessageText(entry.message.content);
|
|
285
|
+
if (!text || /^\/(?:cook|complete)\b/i.test(text)) continue;
|
|
286
|
+
entries.push({ role, text });
|
|
287
|
+
if (entries.length >= limit) break;
|
|
288
|
+
}
|
|
289
|
+
return entries;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function extractMarkdownPath(text: string): string | undefined {
|
|
293
|
+
let match: RegExpExecArray | null;
|
|
294
|
+
while ((match = MARKDOWN_PATH_PATTERN.exec(text)) !== null) {
|
|
295
|
+
const candidate = match[1]?.trim();
|
|
296
|
+
if (candidate) return candidate;
|
|
297
|
+
}
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function readRepoMarkdownArtifact(root: string, candidatePath: string): Promise<CookTriggerAdoptedArtifact | undefined> {
|
|
302
|
+
const resolved = path.resolve(root, candidatePath);
|
|
303
|
+
const relative = path.relative(root, resolved);
|
|
304
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return undefined;
|
|
305
|
+
try {
|
|
306
|
+
const raw = await fsp.readFile(resolved, "utf8");
|
|
307
|
+
return {
|
|
308
|
+
kind: "repo_markdown",
|
|
309
|
+
basis: "explicit_user_adoption",
|
|
310
|
+
title: candidatePath,
|
|
311
|
+
path: candidatePath,
|
|
312
|
+
preview: truncateInline(raw),
|
|
313
|
+
};
|
|
314
|
+
} catch {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function findRecentPlanArtifact(recentMessages: RecentSessionMessage[]): CookTriggerAdoptedArtifact | undefined {
|
|
320
|
+
for (const entry of recentMessages) {
|
|
321
|
+
if (entry.role !== "assistant" && entry.role !== "custom") continue;
|
|
322
|
+
if (!hasStructuredContextProposalSignal(entry.text, stripCodeBlocks) && !/(?:plan|proposal|spec|方案|計劃|计划|提案|規格|规格)/iu.test(entry.text)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
kind: "recent_plan",
|
|
327
|
+
basis: "explicit_user_adoption",
|
|
328
|
+
title: entry.role === "assistant" ? "latest discussed assistant plan" : "latest discussed plan",
|
|
329
|
+
preview: truncateInline(entry.text),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function detectExplicitAdoptedArtifact(
|
|
336
|
+
eventText: string,
|
|
337
|
+
ctx: InputRoutingContext,
|
|
338
|
+
root: string,
|
|
339
|
+
recentMessages: RecentSessionMessage[],
|
|
340
|
+
): Promise<CookTriggerAdoptedArtifact | undefined> {
|
|
341
|
+
if (!isExplicitArtifactAdoption(eventText)) return undefined;
|
|
342
|
+
const markdownPath = extractMarkdownPath(eventText);
|
|
343
|
+
if (markdownPath) {
|
|
344
|
+
return readRepoMarkdownArtifact(root, markdownPath);
|
|
345
|
+
}
|
|
346
|
+
return findRecentPlanArtifact(recentMessages);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function buildAdoptedArtifactHint(adoptedArtifact: CookTriggerAdoptedArtifact | undefined): string | undefined {
|
|
350
|
+
if (!adoptedArtifact) return undefined;
|
|
351
|
+
const lines = [`User explicitly adopted ${adoptedArtifact.kind === "repo_markdown" ? "repo markdown artifact" : "recent plan"}: ${adoptedArtifact.title}`];
|
|
352
|
+
if (adoptedArtifact.path) lines.push(`Artifact path: ${adoptedArtifact.path}`);
|
|
353
|
+
if (adoptedArtifact.preview) lines.push(`Artifact preview: ${adoptedArtifact.preview}`);
|
|
354
|
+
return lines.join("\n");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function buildClarificationWorkflowBiases(
|
|
358
|
+
snapshot: CompletionStateSnapshot | undefined,
|
|
359
|
+
proposal: ContextProposal,
|
|
360
|
+
): CookTriggerWorkflowBias[] {
|
|
361
|
+
if (!snapshot) return ["startup"];
|
|
362
|
+
if (!activeWorkflowContext(snapshot)) return ["next_round"];
|
|
363
|
+
const currentMission = asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor);
|
|
364
|
+
if (proposal?.mission && currentMission && proposal.mission.trim() === currentMission.trim()) {
|
|
365
|
+
return ["resume"];
|
|
366
|
+
}
|
|
367
|
+
if (proposal?.mission && currentMission && proposal.mission.trim() !== currentMission.trim()) {
|
|
368
|
+
return ["resume", "refocus"];
|
|
369
|
+
}
|
|
370
|
+
return ["resume"];
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function clarificationBiasFromAction(action: CookTriggerClarificationAction): CookTriggerWorkflowBias | undefined {
|
|
374
|
+
switch (action) {
|
|
375
|
+
case "route_startup":
|
|
376
|
+
return "startup";
|
|
377
|
+
case "route_resume":
|
|
378
|
+
return "resume";
|
|
379
|
+
case "route_refocus":
|
|
380
|
+
return "refocus";
|
|
381
|
+
case "route_next_round":
|
|
382
|
+
return "next_round";
|
|
383
|
+
default:
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function buildClarificationCapsule(
|
|
389
|
+
action: CookTriggerClarificationAction,
|
|
390
|
+
classification: CookTriggerClassification,
|
|
391
|
+
proposal: ContextProposal,
|
|
392
|
+
): CookTriggerClarificationCapsule | undefined {
|
|
393
|
+
const selectedWorkflowBias = clarificationBiasFromAction(action);
|
|
394
|
+
if (!selectedWorkflowBias) return undefined;
|
|
395
|
+
return {
|
|
396
|
+
selectedWorkflowBias,
|
|
397
|
+
reason: classification.reason,
|
|
398
|
+
goal: proposal?.mission ?? classification.focusHint,
|
|
399
|
+
scope: proposal?.scope?.slice(0, 3),
|
|
400
|
+
nonGoal: proposal?.constraints?.slice(0, 2),
|
|
401
|
+
doneWhen: proposal?.acceptance?.slice(0, 2),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function routingExtrasForArtifact(adoptedArtifact: CookTriggerAdoptedArtifact | undefined): Record<string, unknown> {
|
|
406
|
+
return adoptedArtifact
|
|
407
|
+
? {
|
|
408
|
+
adoptedArtifactKind: adoptedArtifact.kind,
|
|
409
|
+
adoptedArtifactBasis: adoptedArtifact.basis,
|
|
410
|
+
adoptedArtifactTitle: adoptedArtifact.title,
|
|
411
|
+
adoptedArtifactPath: adoptedArtifact.path ?? null,
|
|
412
|
+
adoptedArtifactPreview: adoptedArtifact.preview ?? null,
|
|
413
|
+
}
|
|
414
|
+
: {
|
|
415
|
+
adoptedArtifactKind: null,
|
|
416
|
+
adoptedArtifactBasis: null,
|
|
417
|
+
adoptedArtifactTitle: null,
|
|
418
|
+
adoptedArtifactPath: null,
|
|
419
|
+
adoptedArtifactPreview: null,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function routingExtrasForClarification(clarificationCapsule: CookNaturalLanguageHandoff["clarificationCapsule"] | undefined): Record<string, unknown> {
|
|
424
|
+
return clarificationCapsule
|
|
425
|
+
? {
|
|
426
|
+
clarificationSelectedBias: clarificationCapsule.selectedWorkflowBias,
|
|
427
|
+
clarificationReason: clarificationCapsule.reason,
|
|
428
|
+
clarificationGoal: clarificationCapsule.goal ?? null,
|
|
429
|
+
clarificationScope: clarificationCapsule.scope ?? [],
|
|
430
|
+
clarificationNonGoal: clarificationCapsule.nonGoal ?? [],
|
|
431
|
+
clarificationDoneWhen: clarificationCapsule.doneWhen ?? [],
|
|
432
|
+
}
|
|
433
|
+
: {
|
|
434
|
+
clarificationSelectedBias: null,
|
|
435
|
+
clarificationReason: null,
|
|
436
|
+
clarificationGoal: null,
|
|
437
|
+
clarificationScope: [],
|
|
438
|
+
clarificationNonGoal: [],
|
|
439
|
+
clarificationDoneWhen: [],
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
159
443
|
async function promptCookTriggerTakeover(
|
|
160
444
|
ctx: InputRoutingContext,
|
|
161
445
|
classification: CookTriggerClassification,
|
|
162
446
|
deps: CompletionDriverDeps,
|
|
163
447
|
): Promise<CookTriggerConfirmationAction> {
|
|
164
448
|
const override = triggerConfirmationOverride();
|
|
165
|
-
const layout =
|
|
449
|
+
const layout = buildCookTriggerConfirmationLayout({
|
|
166
450
|
classification,
|
|
167
451
|
mainChatRerunGuidance: deps.mainChatRerunGuidance,
|
|
168
452
|
});
|
|
@@ -180,11 +464,64 @@ async function promptCookTriggerTakeover(
|
|
|
180
464
|
return index >= 0 ? layout.actions[index].id : "cancel";
|
|
181
465
|
}
|
|
182
466
|
|
|
183
|
-
function
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
467
|
+
async function promptCookTriggerClarification(
|
|
468
|
+
ctx: InputRoutingContext,
|
|
469
|
+
snapshot: CompletionStateSnapshot | undefined,
|
|
470
|
+
proposal: ContextProposal,
|
|
471
|
+
adoptedArtifact: CookTriggerAdoptedArtifact | undefined,
|
|
472
|
+
deps: CompletionDriverDeps,
|
|
473
|
+
): Promise<CookTriggerClarificationAction> {
|
|
474
|
+
const workflowBiases = buildClarificationWorkflowBiases(snapshot, proposal);
|
|
475
|
+
const override = triggerClarificationOverride();
|
|
476
|
+
const layout = buildCookTriggerClarificationLayout({
|
|
477
|
+
currentMission: asString(snapshot?.state?.mission_anchor) ?? asString(snapshot?.plan?.mission_anchor),
|
|
478
|
+
candidateMission: proposal?.mission,
|
|
479
|
+
workflowBiases,
|
|
480
|
+
mainChatRerunGuidance: deps.mainChatRerunGuidance,
|
|
481
|
+
adoptedArtifact,
|
|
482
|
+
});
|
|
483
|
+
maybeWriteCookTriggerClarificationSnapshot(layout, triggerClarificationSnapshotPath());
|
|
484
|
+
if (override) return override;
|
|
485
|
+
if (!ctx.hasUI || !ctx.ui) return "cancel";
|
|
486
|
+
const choices = layout.actions.map((action) => `${action.label}\n\n${action.description}`);
|
|
487
|
+
const titleParts = [layout.title, "", layout.intro];
|
|
488
|
+
if (layout.currentMissionHeading && layout.currentMissionBody) titleParts.push("", layout.currentMissionHeading, layout.currentMissionBody);
|
|
489
|
+
if (layout.candidateMissionHeading && layout.candidateMissionBody) titleParts.push("", layout.candidateMissionHeading, layout.candidateMissionBody);
|
|
490
|
+
if (layout.adoptedArtifactHeading && layout.adoptedArtifactBody) titleParts.push("", layout.adoptedArtifactHeading, layout.adoptedArtifactBody);
|
|
491
|
+
const choice = await ctx.ui.select(titleParts.join("\n"), choices);
|
|
492
|
+
if (!choice) return "cancel";
|
|
493
|
+
const index = choices.indexOf(choice);
|
|
494
|
+
return index >= 0 ? layout.actions[index].id : "cancel";
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function promptCookTriggerRecovery(
|
|
498
|
+
ctx: InputRoutingContext,
|
|
499
|
+
result: CookTriggerClassifierResult,
|
|
500
|
+
deps: CompletionDriverDeps,
|
|
501
|
+
): Promise<CookTriggerRecoveryAction> {
|
|
502
|
+
const override = triggerRecoveryOverride();
|
|
503
|
+
const layout = buildCookTriggerRecoveryLayout({
|
|
504
|
+
failureLabel: classifierFailureLabel(result),
|
|
505
|
+
mainChatRerunGuidance: deps.mainChatRerunGuidance,
|
|
506
|
+
});
|
|
507
|
+
maybeWriteCookTriggerRecoverySnapshot(layout, triggerRecoverySnapshotPath());
|
|
508
|
+
if (override) return override;
|
|
509
|
+
if (!ctx.hasUI || !ctx.ui) return "cancel";
|
|
510
|
+
const choices = layout.actions.map((action) => `${action.label}\n\n${action.description}`);
|
|
511
|
+
const titleParts = [layout.title, "", layout.intro];
|
|
512
|
+
if (layout.failureHeading && layout.failureBody) titleParts.push("", layout.failureHeading, layout.failureBody);
|
|
513
|
+
const choice = await ctx.ui.select(titleParts.join("\n"), choices);
|
|
514
|
+
if (!choice) return "cancel";
|
|
515
|
+
const index = choices.indexOf(choice);
|
|
516
|
+
return index >= 0 ? layout.actions[index].id : "cancel";
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function buildHandoffHintText(
|
|
520
|
+
classification: CookTriggerClassification,
|
|
521
|
+
clarificationCapsule: CookNaturalLanguageHandoff["clarificationCapsule"] | undefined,
|
|
522
|
+
adoptedArtifact: CookTriggerAdoptedArtifact | undefined,
|
|
523
|
+
): string | undefined {
|
|
524
|
+
return clarificationCapsule?.goal ?? classification.focusHint ?? adoptedArtifact?.title;
|
|
188
525
|
}
|
|
189
526
|
|
|
190
527
|
export async function handleCookNaturalLanguageTrigger(
|
|
@@ -192,7 +529,12 @@ export async function handleCookNaturalLanguageTrigger(
|
|
|
192
529
|
event: InputRoutingEvent,
|
|
193
530
|
ctx: InputRoutingContext,
|
|
194
531
|
deps: CompletionDriverDeps,
|
|
195
|
-
): Promise<{ action: "continue" | "handled" }> {
|
|
532
|
+
): Promise<{ action: "continue" | "handled" } | { action: "transform"; text: string; images?: unknown[] }> {
|
|
533
|
+
const replayText = consumeRouterBypassReplay(event);
|
|
534
|
+
if (replayText !== undefined) {
|
|
535
|
+
return { action: "transform", text: replayText, images: event.images };
|
|
536
|
+
}
|
|
537
|
+
|
|
196
538
|
const configuredMode = configuredTriggerMode();
|
|
197
539
|
const mode = effectiveTriggerMode(configuredMode);
|
|
198
540
|
if (mode === "off") {
|
|
@@ -246,78 +588,194 @@ export async function handleCookNaturalLanguageTrigger(
|
|
|
246
588
|
}
|
|
247
589
|
|
|
248
590
|
const snapshot = await loadCompletionSnapshot(ctx.cwd);
|
|
591
|
+
const root = snapshot?.files.root ?? ctx.cwd;
|
|
592
|
+
const projectName = path.basename(root);
|
|
249
593
|
const recentEntries = collectRecentDiscussionEntries(ctx, {
|
|
250
594
|
asString,
|
|
251
595
|
isRecord: (value) => typeof value === "object" && value !== null && !Array.isArray(value),
|
|
252
596
|
}, 6);
|
|
253
|
-
|
|
597
|
+
const recentMessages = recentSessionMessages(ctx, 12);
|
|
598
|
+
const adoptedArtifact = await detectExplicitAdoptedArtifact(event.text, ctx, root, recentMessages);
|
|
599
|
+
const routerMode = mode === "router";
|
|
600
|
+
if (!routerMode && !activeWorkflowContext(snapshot) && !hasRecentImplementationContext(recentEntries) && !adoptedArtifact) {
|
|
254
601
|
writeRoutingDecision(event, {
|
|
255
602
|
mode: configuredMode,
|
|
256
603
|
action: "continue",
|
|
257
604
|
reason: "no_workflow_or_recent_implementation_context",
|
|
258
605
|
bypassReason: "no_workflow_or_recent_implementation_context",
|
|
259
|
-
});
|
|
606
|
+
}, routingExtrasForArtifact(adoptedArtifact));
|
|
260
607
|
return { action: "continue" };
|
|
261
608
|
}
|
|
262
|
-
if (!looksLikeTriggerCandidate(event.text)) {
|
|
609
|
+
if (!routerMode && !looksLikeTriggerCandidate(event.text)) {
|
|
263
610
|
writeRoutingDecision(event, {
|
|
264
611
|
mode: configuredMode,
|
|
265
612
|
action: "continue",
|
|
266
613
|
reason: "not_candidate",
|
|
267
614
|
bypassReason: "not_candidate",
|
|
268
|
-
});
|
|
615
|
+
}, routingExtrasForArtifact(adoptedArtifact));
|
|
269
616
|
return { action: "continue" };
|
|
270
617
|
}
|
|
271
618
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
619
|
+
let classifier: CookTriggerClassifierResult | undefined;
|
|
620
|
+
for (let attempt = 0; attempt < ROUTER_FAILURE_RETRY_LIMIT; attempt += 1) {
|
|
621
|
+
classifier = await classifyCookTriggerIntentWithAgent({
|
|
622
|
+
ctx,
|
|
623
|
+
projectName,
|
|
624
|
+
inputText: normalizeTriggerText(event.text),
|
|
625
|
+
recentEntries,
|
|
626
|
+
workflowContextLines: buildTriggerWorkflowContextLines(snapshot),
|
|
627
|
+
});
|
|
628
|
+
if (classifier.status === "classified" && classifier.classification) break;
|
|
629
|
+
const recovery = await promptCookTriggerRecovery(ctx, classifier, deps);
|
|
630
|
+
if (recovery === "retry_routing" && attempt + 1 < ROUTER_FAILURE_RETRY_LIMIT) {
|
|
631
|
+
deps.emitCommandText(ctx, "Retrying workflow-aware router once before deciding whether /cook should take over.", "info");
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (recovery === "send_as_normal_chat") {
|
|
635
|
+
await replayOriginalMessageToPrimaryAgent(pi, event);
|
|
636
|
+
deps.emitCommandText(ctx, "Replayed the original message once to the main chat path and bypassed router interception for that replay.", "info");
|
|
637
|
+
writeRoutingDecision(event, {
|
|
638
|
+
mode: configuredMode,
|
|
639
|
+
action: "handled",
|
|
640
|
+
reason: `${classifierFailureReason(classifier)}_send_as_normal_chat`,
|
|
641
|
+
}, {
|
|
642
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
643
|
+
recoveryAction: recovery,
|
|
644
|
+
errorMessage: classifier.errorMessage ?? null,
|
|
645
|
+
rawOutput: classifier.rawOutput ?? null,
|
|
646
|
+
replayedToPrimaryAgent: true,
|
|
647
|
+
replayBypassMarkerApplied: true,
|
|
648
|
+
});
|
|
649
|
+
return { action: "handled" };
|
|
650
|
+
}
|
|
651
|
+
deps.emitCommandText(
|
|
652
|
+
ctx,
|
|
653
|
+
"Cancelled router recovery without replaying the original message. If you want the completion workflow boundary, rerun /cook explicitly.",
|
|
654
|
+
"info",
|
|
655
|
+
);
|
|
281
656
|
writeRoutingDecision(event, {
|
|
282
657
|
mode: configuredMode,
|
|
283
658
|
action: "handled",
|
|
284
|
-
reason: classifierFailureReason(classifier)
|
|
659
|
+
reason: `${classifierFailureReason(classifier)}_cancelled`,
|
|
285
660
|
}, {
|
|
661
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
662
|
+
recoveryAction: recovery,
|
|
286
663
|
errorMessage: classifier.errorMessage ?? null,
|
|
287
664
|
rawOutput: classifier.rawOutput ?? null,
|
|
665
|
+
replayedToPrimaryAgent: false,
|
|
666
|
+
replayBypassMarkerApplied: false,
|
|
667
|
+
});
|
|
668
|
+
return { action: "handled" };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (!classifier || classifier.status !== "classified" || !classifier.classification) {
|
|
672
|
+
deps.emitCommandText(ctx, "Router recovery stopped without replaying the original message. If you still want the workflow boundary, rerun /cook explicitly.", "info");
|
|
673
|
+
writeRoutingDecision(event, {
|
|
674
|
+
mode: configuredMode,
|
|
675
|
+
action: "handled",
|
|
676
|
+
reason: classifier ? `${classifierFailureReason(classifier)}_retry_exhausted` : "classifier_error_retry_exhausted",
|
|
677
|
+
}, {
|
|
678
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
679
|
+
recoveryAction: "retry_routing",
|
|
680
|
+
errorMessage: classifier?.errorMessage ?? null,
|
|
681
|
+
rawOutput: classifier?.rawOutput ?? null,
|
|
682
|
+
replayedToPrimaryAgent: false,
|
|
683
|
+
replayBypassMarkerApplied: false,
|
|
288
684
|
});
|
|
289
685
|
return { action: "handled" };
|
|
290
686
|
}
|
|
291
687
|
|
|
292
688
|
const classification = classifier.classification;
|
|
293
|
-
if (classification.
|
|
689
|
+
if (classification.decision === "normal_prompt") {
|
|
294
690
|
writeRoutingDecision(event, {
|
|
295
691
|
mode: configuredMode,
|
|
296
692
|
action: "continue",
|
|
297
693
|
reason: "classifier_normal_prompt",
|
|
298
694
|
classification,
|
|
299
|
-
});
|
|
695
|
+
}, routingExtrasForArtifact(adoptedArtifact));
|
|
300
696
|
return { action: "continue" };
|
|
301
697
|
}
|
|
302
|
-
|
|
698
|
+
|
|
699
|
+
const proposalHint = buildAdoptedArtifactHint(adoptedArtifact);
|
|
700
|
+
if (classification.decision === "unclear") {
|
|
701
|
+
const proposal = await deps.deriveCookContextProposal(ctx, projectName, proposalHint);
|
|
702
|
+
const clarification = await promptCookTriggerClarification(ctx, snapshot, proposal, adoptedArtifact, deps);
|
|
703
|
+
if (clarification === "send_as_normal_chat") {
|
|
704
|
+
await replayOriginalMessageToPrimaryAgent(pi, event);
|
|
705
|
+
deps.emitCommandText(ctx, "Replayed the original message once to the main chat path and bypassed router interception for that clarification replay.", "info");
|
|
706
|
+
writeRoutingDecision(event, {
|
|
707
|
+
mode: configuredMode,
|
|
708
|
+
action: "handled",
|
|
709
|
+
reason: "user_sent_as_normal_chat_after_clarification",
|
|
710
|
+
classification,
|
|
711
|
+
}, {
|
|
712
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
713
|
+
clarificationAction: clarification,
|
|
714
|
+
replayedToPrimaryAgent: true,
|
|
715
|
+
replayBypassMarkerApplied: true,
|
|
716
|
+
});
|
|
717
|
+
return { action: "handled" };
|
|
718
|
+
}
|
|
719
|
+
if (clarification === "cancel") {
|
|
720
|
+
deps.emitCommandText(
|
|
721
|
+
ctx,
|
|
722
|
+
"Cancelled commandless workflow clarification. If you want the workflow boundary, rerun /cook explicitly.",
|
|
723
|
+
"info",
|
|
724
|
+
);
|
|
725
|
+
writeRoutingDecision(event, {
|
|
726
|
+
mode: configuredMode,
|
|
727
|
+
action: "handled",
|
|
728
|
+
reason: triggerClarificationOverride() ? "user_cancelled_clarification" : ctx.hasUI ? "user_cancelled_clarification" : "clarification_unavailable",
|
|
729
|
+
classification,
|
|
730
|
+
}, {
|
|
731
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
732
|
+
clarificationAction: clarification,
|
|
733
|
+
replayedToPrimaryAgent: false,
|
|
734
|
+
replayBypassMarkerApplied: false,
|
|
735
|
+
});
|
|
736
|
+
return { action: "handled" };
|
|
737
|
+
}
|
|
738
|
+
const clarificationCapsule = buildClarificationCapsule(clarification, classification, proposal);
|
|
739
|
+
const selectedBias = clarificationBiasFromAction(clarification) ?? classification.workflowBias;
|
|
740
|
+
deps.emitCommandText(ctx, "Routing clarified natural-language handoff into /cook.", "info");
|
|
303
741
|
writeRoutingDecision(event, {
|
|
304
742
|
mode: configuredMode,
|
|
305
|
-
action: "
|
|
306
|
-
reason: "
|
|
743
|
+
action: "routed_to_cook",
|
|
744
|
+
reason: "clarification_resolved",
|
|
307
745
|
classification,
|
|
746
|
+
}, {
|
|
747
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
748
|
+
...routingExtrasForClarification(clarificationCapsule),
|
|
749
|
+
clarificationAction: clarification,
|
|
308
750
|
});
|
|
309
|
-
|
|
751
|
+
await runCookEntry(pi, ctx, deps, {
|
|
752
|
+
origin: "natural-language-trigger",
|
|
753
|
+
hintText: buildHandoffHintText(classification, clarificationCapsule, adoptedArtifact),
|
|
754
|
+
originalInput: event.text,
|
|
755
|
+
triggerText: event.text,
|
|
756
|
+
preferredRoutingBias: selectedBias,
|
|
757
|
+
clarificationCapsule,
|
|
758
|
+
adoptedArtifact,
|
|
759
|
+
});
|
|
760
|
+
return { action: "handled" };
|
|
310
761
|
}
|
|
311
762
|
|
|
312
763
|
const confirmation = await promptCookTriggerTakeover(ctx, classification, deps);
|
|
313
|
-
if (confirmation === "
|
|
764
|
+
if (confirmation === "send_as_normal_chat") {
|
|
765
|
+
await replayOriginalMessageToPrimaryAgent(pi, event);
|
|
766
|
+
deps.emitCommandText(ctx, "Replayed the original message once to the main chat path and bypassed router interception for that workflow-offer replay.", "info");
|
|
314
767
|
writeRoutingDecision(event, {
|
|
315
768
|
mode: configuredMode,
|
|
316
|
-
action: "
|
|
317
|
-
reason: "
|
|
769
|
+
action: "handled",
|
|
770
|
+
reason: "user_sent_as_normal_chat",
|
|
318
771
|
classification,
|
|
772
|
+
}, {
|
|
773
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
774
|
+
confirmationAction: confirmation,
|
|
775
|
+
replayedToPrimaryAgent: true,
|
|
776
|
+
replayBypassMarkerApplied: true,
|
|
319
777
|
});
|
|
320
|
-
return { action: "
|
|
778
|
+
return { action: "handled" };
|
|
321
779
|
}
|
|
322
780
|
if (confirmation === "cancel") {
|
|
323
781
|
deps.emitCommandText(
|
|
@@ -328,8 +786,13 @@ export async function handleCookNaturalLanguageTrigger(
|
|
|
328
786
|
writeRoutingDecision(event, {
|
|
329
787
|
mode: configuredMode,
|
|
330
788
|
action: "handled",
|
|
331
|
-
reason: ctx.hasUI ? "user_cancelled_takeover" : "
|
|
789
|
+
reason: ctx.hasUI ? "user_cancelled_takeover" : "router_confirmation_unavailable",
|
|
332
790
|
classification,
|
|
791
|
+
}, {
|
|
792
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
793
|
+
confirmationAction: confirmation,
|
|
794
|
+
replayedToPrimaryAgent: false,
|
|
795
|
+
replayBypassMarkerApplied: false,
|
|
333
796
|
});
|
|
334
797
|
return { action: "handled" };
|
|
335
798
|
}
|
|
@@ -340,11 +803,17 @@ export async function handleCookNaturalLanguageTrigger(
|
|
|
340
803
|
action: "routed_to_cook",
|
|
341
804
|
reason: "accepted_takeover",
|
|
342
805
|
classification,
|
|
806
|
+
}, {
|
|
807
|
+
...routingExtrasForArtifact(adoptedArtifact),
|
|
808
|
+
confirmationAction: confirmation,
|
|
343
809
|
});
|
|
344
810
|
await runCookEntry(pi, ctx, deps, {
|
|
345
811
|
origin: "natural-language-trigger",
|
|
346
|
-
hintText: classification
|
|
812
|
+
hintText: buildHandoffHintText(classification, undefined, adoptedArtifact),
|
|
347
813
|
originalInput: event.text,
|
|
814
|
+
triggerText: event.text,
|
|
815
|
+
preferredRoutingBias: classification.workflowBias,
|
|
816
|
+
adoptedArtifact,
|
|
348
817
|
});
|
|
349
818
|
return { action: "handled" };
|
|
350
819
|
}
|