fix(agent): address code review issues
- Add empty goal guard in parser (returns done instead of passthrough) - Replace `as any` casts in pipeline.ts with proper ActionDecision types - Add runtime type guards for untrusted LLM output in classifier - Add intent action to dynamic prompt so UI agent can fire intents Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -62,12 +62,16 @@ export async function classifyGoal(
|
|||||||
|
|
||||||
switch (parsed.type) {
|
switch (parsed.type) {
|
||||||
case "intent": {
|
case "intent": {
|
||||||
|
const rawExtras = parsed.extras;
|
||||||
|
const validExtras = (rawExtras && typeof rawExtras === "object" && !Array.isArray(rawExtras))
|
||||||
|
? rawExtras as Record<string, string>
|
||||||
|
: undefined;
|
||||||
const intent: IntentCommand = {
|
const intent: IntentCommand = {
|
||||||
intentAction: (parsed.intentAction as string) ?? "",
|
intentAction: typeof parsed.intentAction === "string" ? parsed.intentAction : "",
|
||||||
uri: parsed.uri as string | undefined,
|
uri: typeof parsed.uri === "string" ? parsed.uri : undefined,
|
||||||
intentType: parsed.intentType as string | undefined,
|
intentType: typeof parsed.intentType === "string" ? parsed.intentType : undefined,
|
||||||
extras: parsed.extras as Record<string, string> | undefined,
|
extras: validExtras,
|
||||||
packageName: parsed.packageName as string | undefined,
|
packageName: typeof parsed.packageName === "string" ? parsed.packageName : undefined,
|
||||||
};
|
};
|
||||||
if (!intent.intentAction) {
|
if (!intent.intentAction) {
|
||||||
console.warn("[Classifier] Intent missing intentAction, falling through");
|
console.warn("[Classifier] Intent missing intentAction, falling through");
|
||||||
|
|||||||
@@ -293,7 +293,8 @@ Scrolling:
|
|||||||
|
|
||||||
App Control:
|
App Control:
|
||||||
{"action": "launch", "package": "com.app.name", "reason": "Open app"}
|
{"action": "launch", "package": "com.app.name", "reason": "Open app"}
|
||||||
{"action": "switch_app", "package": "com.app.name", "reason": "Switch app"}`;
|
{"action": "switch_app", "package": "com.app.name", "reason": "Switch app"}
|
||||||
|
{"action": "intent", "intentAction": "android.intent.action.VIEW", "uri": "tel:123", "reason": "Fire Android intent directly"}`;
|
||||||
|
|
||||||
// Multi-step actions (always useful)
|
// Multi-step actions (always useful)
|
||||||
actions += `
|
actions += `
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ export function parseGoal(
|
|||||||
caps: DeviceCapabilities
|
caps: DeviceCapabilities
|
||||||
): PipelineResult {
|
): PipelineResult {
|
||||||
const trimmed = goal.trim();
|
const trimmed = goal.trim();
|
||||||
|
if (!trimmed) return { stage: "parser", type: "done", reason: "Empty goal" };
|
||||||
for (const matcher of PATTERNS) {
|
for (const matcher of PATTERNS) {
|
||||||
const result = matcher(trimmed, caps);
|
const result = matcher(trimmed, caps);
|
||||||
if (result) return result;
|
if (result) return result;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import type {
|
import type {
|
||||||
InstalledApp,
|
InstalledApp,
|
||||||
PipelineResult,
|
PipelineResult,
|
||||||
|
ActionDecision,
|
||||||
} from "@droidclaw/shared";
|
} from "@droidclaw/shared";
|
||||||
import { sessions } from "../ws/sessions.js";
|
import { sessions } from "../ws/sessions.js";
|
||||||
import { db } from "../db.js";
|
import { db } from "../db.js";
|
||||||
@@ -185,7 +186,7 @@ export async function runPipeline(
|
|||||||
|
|
||||||
onStep?.({
|
onStep?.({
|
||||||
stepNumber: 1,
|
stepNumber: 1,
|
||||||
action: { action: parseResult.type, reason: `Parser: ${parseResult.type}` } as any,
|
action: { action: parseResult.type, reason: `Parser: ${parseResult.type}` } as unknown as ActionDecision,
|
||||||
reasoning: `Parser: direct ${parseResult.type} action`,
|
reasoning: `Parser: direct ${parseResult.type} action`,
|
||||||
screenHash: "",
|
screenHash: "",
|
||||||
});
|
});
|
||||||
@@ -228,7 +229,7 @@ export async function runPipeline(
|
|||||||
|
|
||||||
onStep?.({
|
onStep?.({
|
||||||
stepNumber: 1,
|
stepNumber: 1,
|
||||||
action: { action: "intent", reason: "Classifier: intent" } as any,
|
action: { action: "intent", intentAction: classResult.intent.intentAction, uri: classResult.intent.uri, reason: "Classifier: intent" } as unknown as ActionDecision,
|
||||||
reasoning: `Classifier: ${classResult.intent.intentAction}`,
|
reasoning: `Classifier: ${classResult.intent.intentAction}`,
|
||||||
screenHash: "",
|
screenHash: "",
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user