pi-advisor-flow 0.1.8 → 0.1.9

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools.ts +21 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-advisor-flow",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/tools.ts CHANGED
@@ -49,11 +49,25 @@ export const ADVISOR_SYSTEM = [
49
49
  "You already have the relevant reconstructed conversation context. No question or other input from the Executor is needed for a general review.",
50
50
  "When no targeted focus is supplied, proactively review the task, risks, proposed direction, and validation from the context. Do not ask the Executor for a question, clarification, more input, or confirmation.",
51
51
  "The context may be truncated, so state any material uncertainty and make the best recommendation you can from what is present.",
52
- "You do not act or take over planning. Return only a JSON object with verdict (proceed, revise, insufficient-evidence, or blocked), criticalFindings (array of {severity, claim, evidence}), missingEvidence (string array), smallestNextStep (string), verificationRequired (string array), and escalationReason (string or null). Use blocked only for a critical issue requiring the user; never claim verification that the supplied evidence does not show.",
52
+ "You do not act or take over planning. Answer the Executor's request directly in concise, human-readable Markdown. State uncertainty plainly and never claim verification that the supplied evidence does not show.",
53
+ ].join(" ");
54
+
55
+ export const ADVISOR_DECISION_SYSTEM = [
56
+ "You are the Advisor's automatic safety gate for a repeated-tool loop.",
57
+ "Review the supplied context and decide whether the Executor may proceed.",
58
+ "Answer in concise Markdown. Your first line must be exactly `Decision: proceed`, `Decision: revise`, `Decision: insufficient-evidence`, or `Decision: blocked`.",
59
+ "Use blocked only for a critical issue requiring the user. Never claim verification that the supplied evidence does not show.",
53
60
  ].join(" ");
54
61
 
55
62
  type Advice = { verdict: AdvisorVerdict; criticalFindings: Array<{ severity: string; claim: string; evidence: string }>; missingEvidence: string[]; smallestNextStep: string; verificationRequired: string[]; escalationReason: string | null };
56
63
 
64
+ export const parseAutomaticDecision = (text: string): Advice => {
65
+ const firstLine = text.split(/\r?\n/, 1)[0];
66
+ const match = /^Decision: (proceed|revise|blocked|insufficient-evidence)$/i.exec(firstLine);
67
+ if (!match) return parseAdvice("");
68
+ return { verdict: match[1] as AdvisorVerdict, criticalFindings: [], missingEvidence: [], smallestNextStep: text.trim(), verificationRequired: [], escalationReason: null };
69
+ };
70
+
57
71
  export const parseAdvice = (text: string): Advice => {
58
72
  try {
59
73
  const value = JSON.parse(text) as Partial<Advice>;
@@ -61,7 +75,7 @@ export const parseAdvice = (text: string): Advice => {
61
75
  if (!Array.isArray(value.criticalFindings) || !Array.isArray(value.missingEvidence) || !Array.isArray(value.verificationRequired) || typeof value.smallestNextStep !== "string" || !value.missingEvidence.every((item) => typeof item === "string") || !value.verificationRequired.every((item) => typeof item === "string") || !value.criticalFindings.every((item) => item && typeof item === "object" && typeof (item as Record<string, unknown>).severity === "string" && typeof (item as Record<string, unknown>).claim === "string" && typeof (item as Record<string, unknown>).evidence === "string")) throw new Error("invalid shape");
62
76
  return { verdict: value.verdict as AdvisorVerdict, criticalFindings: value.criticalFindings as Advice["criticalFindings"], missingEvidence: value.missingEvidence as string[], smallestNextStep: value.smallestNextStep, verificationRequired: value.verificationRequired as string[], escalationReason: typeof value.escalationReason === "string" ? value.escalationReason : null };
63
77
  } catch {
64
- return { verdict: "insufficient-evidence", criticalFindings: [{ severity: "medium", claim: "Advisor response was not structured", evidence: "The response could not be parsed as the required JSON." }], missingEvidence: [], smallestNextStep: "Request a structured Advisor review before relying on this advice.", verificationRequired: [], escalationReason: null };
78
+ return { verdict: "insufficient-evidence", criticalFindings: [{ severity: "medium", claim: "Advisor response was not structured for an automatic safety decision", evidence: "The response was not valid structured decision data." }], missingEvidence: [], smallestNextStep: "Review the Advisor's guidance before retrying.", verificationRequired: [], escalationReason: null };
65
79
  }
66
80
  };
67
81
 
@@ -78,6 +92,7 @@ export const consult = async (
78
92
  question?: string,
79
93
  signal?: AbortSignal,
80
94
  onChunk?: (thinking: string, text: string) => void,
95
+ structuredMode = false,
81
96
  ) => {
82
97
  loadConfig(ctx);
83
98
  const [provider, modelId] = splitRef(advisorRef);
@@ -97,7 +112,7 @@ export const consult = async (
97
112
  let thinkingText = "";
98
113
  let responseText = "";
99
114
 
100
- const eventStream = stream(model, { systemPrompt: ADVISOR_SYSTEM, messages }, {
115
+ const eventStream = stream(model, { systemPrompt: structuredMode ? ADVISOR_DECISION_SYSTEM : ADVISOR_SYSTEM, messages }, {
101
116
  apiKey: auth.apiKey,
102
117
  headers: auth.headers,
103
118
  env: auth.env,
@@ -124,8 +139,8 @@ export const consult = async (
124
139
  .trim() || responseText;
125
140
 
126
141
  if (!advice) throw new Error("Advisor returned no advice.");
127
- const structured = parseAdvice(advice);
128
- return { advice: adviceForText(structured), thinkingText, structured };
142
+ const structured = structuredMode ? parseAutomaticDecision(advice) : parseAdvice(advice);
143
+ return { advice, thinkingText, structured };
129
144
  };
130
145
 
131
146
  export const registerAdvisorTool = (pi: ExtensionAPI) => {
@@ -170,7 +185,7 @@ export const registerAdvisorTool = (pi: ExtensionAPI) => {
170
185
  details: { question: `Loop gate: ${event.toolName} repeated ${advisorLoopThresholdRef} times` },
171
186
  }, { deliverAs: "steer" });
172
187
  try {
173
- const { advice, structured } = await consult(ctx, `${reason} Review the repeated actions and recommend the smallest safe next step.`);
188
+ const { advice, structured } = await consult(ctx, `${reason} Review the repeated actions and recommend the smallest safe next step.`, undefined, undefined, true);
174
189
  session.recordConsultation("automatic", structured.verdict);
175
190
  pi.sendMessage({
176
191
  customType: "advisor-manual-result",