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