pi-soly 2.5.1 → 2.5.3
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/index.ts +18 -0
- package/nudge.ts +55 -2
- package/package.json +1 -1
- package/quota/poller.ts +25 -3
- package/skills/agent-coach/SKILL.md +1 -1
package/index.ts
CHANGED
|
@@ -353,7 +353,17 @@ export default function solyExtension(pi: ExtensionAPI) {
|
|
|
353
353
|
d.home = os.homedir();
|
|
354
354
|
const model = ctx.model as { id?: string; provider?: string; reasoning?: boolean } | undefined;
|
|
355
355
|
d.modelId = model?.id ?? null;
|
|
356
|
+
const prevProvider = d.modelProvider;
|
|
356
357
|
d.modelProvider = model?.provider ?? null;
|
|
358
|
+
// TEMP DEBUG
|
|
359
|
+
if (prevProvider !== d.modelProvider) {
|
|
360
|
+
try {
|
|
361
|
+
const fs = require("node:fs");
|
|
362
|
+
const os = require("node:os");
|
|
363
|
+
const p = require("node:path");
|
|
364
|
+
fs.appendFileSync(p.join(os.tmpdir(), "pi-soly-quota-debug.log"), `[${new Date().toISOString()}] updateChromeData: modelProvider changed ${prevProvider} -> ${d.modelProvider}, modelId=${d.modelId}\n`);
|
|
365
|
+
} catch {}
|
|
366
|
+
}
|
|
357
367
|
d.reasoning = Boolean(model?.reasoning);
|
|
358
368
|
try {
|
|
359
369
|
d.thinkingLevel = pi.getThinkingLevel();
|
|
@@ -552,6 +562,14 @@ export default function solyExtension(pi: ExtensionAPI) {
|
|
|
552
562
|
// quotaPercent/quotaResetsLabel back for the footer to render).
|
|
553
563
|
if (quotaPoller) quotaPoller.stop();
|
|
554
564
|
quotaPoller = startQuotaPoller(chrome.data, () => getActiveConfig().chrome.enabled, () => chrome.poke());
|
|
565
|
+
// TEMP DEBUG
|
|
566
|
+
try {
|
|
567
|
+
const fs = await import("node:fs");
|
|
568
|
+
const os = await import("node:os");
|
|
569
|
+
const p = await import("node:path");
|
|
570
|
+
const dbgLog = p.join(os.tmpdir(), "pi-soly-quota-debug.log");
|
|
571
|
+
fs.appendFileSync(dbgLog, `[${new Date().toISOString()}] session_start: poller started, chrome.enabled=${getActiveConfig().chrome.enabled}, modelProvider=${chrome.data.modelProvider}\n`);
|
|
572
|
+
} catch {}
|
|
555
573
|
// Editors save in bursts (write to .tmp, rename, touch). Coalesce
|
|
556
574
|
// those rapid reload events into a single sub-line event under the
|
|
557
575
|
// Working indicator (└─ reloaded 47 rules). Errors here are real
|
package/nudge.ts
CHANGED
|
@@ -28,6 +28,41 @@ const NON_TRIVIAL_VERBS =
|
|
|
28
28
|
const RESEARCH_VERBS =
|
|
29
29
|
/\b(find out|look up|check|verify|investigate|research|figure out|figure out how|discover|why does|how does|what is the best|compare|which library|which approach|benchmark|audit|review|trace|debug why)\b/i;
|
|
30
30
|
|
|
31
|
+
// Complaint patterns — the user is unhappy about code the agent wrote or a
|
|
32
|
+
// behavior it exhibits. Triggers the agent-coach skill (proposes a soly rule
|
|
33
|
+
// to prevent recurrence). RU + EN, case-insensitive. Word-boundary where
|
|
34
|
+
// possible; some RU forms don't use \b well so we anchor on the phrase.
|
|
35
|
+
const COMPLAINT_PATTERNS: RegExp[] = [
|
|
36
|
+
// Russian — explicit dissatisfaction
|
|
37
|
+
/меня напрягает/i,
|
|
38
|
+
/мне не нравится/i,
|
|
39
|
+
/не нравится/i,
|
|
40
|
+
/переделай/i,
|
|
41
|
+
/почему опять/i,
|
|
42
|
+
/опять\s+(?:эт[оа]|этот|эта)/i,
|
|
43
|
+
/не делай так/i,
|
|
44
|
+
/убирай?/i,
|
|
45
|
+
/кринж/i,
|
|
46
|
+
/бесит/i,
|
|
47
|
+
/заставь/i,
|
|
48
|
+
/хватит (?:делать|писать|так)/i,
|
|
49
|
+
/опять (?:делаешь|пишешь|создаёшь|создаешь)/i,
|
|
50
|
+
// English
|
|
51
|
+
/redo .*(?:differently|another way|other way)/i,
|
|
52
|
+
/i don't like/i,
|
|
53
|
+
/why (?:does|do) .*(?:keep|always)/i,
|
|
54
|
+
/stop doing/i,
|
|
55
|
+
/don't do that/i,
|
|
56
|
+
/annoying/i,
|
|
57
|
+
/make .* not/i,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/** Detect whether the user prompt is a complaint about agent behavior/coding.
|
|
61
|
+
* Used to inject the agent-coach skill directive so the LLM doesn't skip it. */
|
|
62
|
+
export function detectComplaint(prompt: string): boolean {
|
|
63
|
+
return COMPLAINT_PATTERNS.some((rx) => rx.test(prompt));
|
|
64
|
+
}
|
|
65
|
+
|
|
31
66
|
const URL_PATTERN = /https?:\/\/\S+/;
|
|
32
67
|
// Library/version-ish reference (e.g. v1.2.3, @scope/pkg).
|
|
33
68
|
// NB: no leading `\b` — `@` is a non-word char so `\b` won't match before it.
|
|
@@ -37,6 +72,9 @@ const VERSION_PATTERN = /(?<!\w)(v?\d+\.\d+(?:\.\d+)?|@[\w\-]+\/[\w\-]+)(?!\w)/;
|
|
|
37
72
|
export interface TaskHeuristics {
|
|
38
73
|
nonTrivial: boolean;
|
|
39
74
|
researchHeavy: boolean;
|
|
75
|
+
/** True when the prompt reads as a complaint about agent-written code or
|
|
76
|
+
* behavior — triggers the agent-coach skill directive. */
|
|
77
|
+
complaint: boolean;
|
|
40
78
|
mentions: string[];
|
|
41
79
|
suggestedAngles: string[];
|
|
42
80
|
}
|
|
@@ -48,6 +86,7 @@ export function classifyTaskHeuristics(prompt: string): TaskHeuristics {
|
|
|
48
86
|
const hasResearch = RESEARCH_VERBS.test(trimmed);
|
|
49
87
|
const hasUrl = URL_PATTERN.test(trimmed);
|
|
50
88
|
const hasVersion = VERSION_PATTERN.test(trimmed);
|
|
89
|
+
const hasComplaint = detectComplaint(trimmed);
|
|
51
90
|
|
|
52
91
|
// Extract file-ish mentions from the prompt. We don't import core's
|
|
53
92
|
// extractFilePathsFromPrompt to keep nudge.ts self-contained.
|
|
@@ -85,7 +124,7 @@ export function classifyTaskHeuristics(prompt: string): TaskHeuristics {
|
|
|
85
124
|
suggestedAngles.push("any constraints (deadline, scope, style) I should know?");
|
|
86
125
|
}
|
|
87
126
|
|
|
88
|
-
return { nonTrivial, researchHeavy, mentions, suggestedAngles };
|
|
127
|
+
return { nonTrivial, researchHeavy, complaint: hasComplaint, mentions, suggestedAngles };
|
|
89
128
|
}
|
|
90
129
|
|
|
91
130
|
// ---------------------------------------------------------------------------
|
|
@@ -140,6 +179,9 @@ export function buildNudgeSection(
|
|
|
140
179
|
if (heuristics.researchHeavy) {
|
|
141
180
|
triggers.push("research-heavy (web lookup / library decision / unknown behavior)");
|
|
142
181
|
}
|
|
182
|
+
if (heuristics.complaint) {
|
|
183
|
+
triggers.push("user complaint about code/behavior (agent-coach eligible)");
|
|
184
|
+
}
|
|
143
185
|
|
|
144
186
|
const triggerLine = triggers.length
|
|
145
187
|
? `Heuristics for this prompt: ${triggers.join("; ")}.`
|
|
@@ -213,6 +255,17 @@ export function buildNudgeSection(
|
|
|
213
255
|
? `\n\n ${confirmLevel === "scope" ? SCOPE_DIRECTIVE : ASK_DIRECTIVE}`
|
|
214
256
|
: "";
|
|
215
257
|
|
|
258
|
+
// Complaint directive: when the user is unhappy about code the agent wrote
|
|
259
|
+
// or a behavior it exhibits, inject an explicit order to load the
|
|
260
|
+
// agent-coach skill and follow its workflow. Passive skills (listed in
|
|
261
|
+
// available_skills) are unreliable — the LLM often skips them and jumps
|
|
262
|
+
// into the code. This directive lands in the system prompt right before
|
|
263
|
+
// the turn, so it can't be missed.
|
|
264
|
+
const complaintBlock =
|
|
265
|
+
heuristics.complaint
|
|
266
|
+
? `\n\n5. **⚠️ User complaint detected — use the agent-coach skill.** The user is unhappy about code behavior. DO NOT just fix this instance and move on. First read the **agent-coach** skill file (\`packages/pi-soly/skills/agent-coach/SKILL.md\`) and follow its workflow: analyze the complaint → extract the underlying pattern → check existing rules (dedup) → draft a soly rule (\`.agents/rules/[category]/[name].md\`) → propose via \`ask_pro\` → write only on confirmation. This closes the feedback loop so the mistake doesn't repeat. If a rule already covers it but was ignored, the rule is too vague — propose strengthening it instead of creating a duplicate.`
|
|
267
|
+
: "";
|
|
268
|
+
|
|
216
269
|
return `
|
|
217
270
|
|
|
218
271
|
## soly behavioral nudge (always on)
|
|
@@ -224,7 +277,7 @@ The following are user-set defaults, not project rules. They tell you how the us
|
|
|
224
277
|
|
|
225
278
|
2. **Scout with soly's own read tools.** When you need to read unfamiliar code, map a directory, or gather context, use soly's read tools — \`soly_snippet(path, offset, limit)\`, \`soly_doc_search(query)\`, \`soly_read(...)\` — plus \`grep\` / \`find\`. Prefer bounded snippets over reading whole files. soly does the work INLINE in this session; there is no separate worker or \`subagent(...)\` tool to delegate to (and none is required).
|
|
226
279
|
|
|
227
|
-
3. **Reach for soly's interaction tools.** For structured questions use \`ask_pro\` (batched, multi-select, ⭐ recommended); for design/architecture forks where the choice hinges on the concrete code shape use \`decision_deck\`; for visual output (galleries, comparisons, diagrams) use \`html_artifact\`. Give each question a concrete recommended default + rationale — don't dump open-ended prompts.${workflowPoint}
|
|
280
|
+
3. **Reach for soly's interaction tools.** For structured questions use \`ask_pro\` (batched, multi-select, ⭐ recommended); for design/architecture forks where the choice hinges on the concrete code shape use \`decision_deck\`; for visual output (galleries, comparisons, diagrams) use \`html_artifact\`. Give each question a concrete recommended default + rationale — don't dump open-ended prompts.${workflowPoint}${complaintBlock}
|
|
228
281
|
|
|
229
282
|
Treat (1) and (2) as defaults, not laws. The user can always override per-task ("just do it", "ask me everything"). When overriding, briefly acknowledge it.
|
|
230
283
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-soly",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"description": "Workflow + project management for pi-coding-agent. Plans, state, MANDATORY rules, self-review, multi-question picker. One npm install, zero config. LLM drives the workflow inline via the soly_workflow tool — no external subagent plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
package/quota/poller.ts
CHANGED
|
@@ -21,10 +21,21 @@
|
|
|
21
21
|
// It only runs while a session is active.
|
|
22
22
|
// =============================================================================
|
|
23
23
|
|
|
24
|
+
import * as fs from "node:fs";
|
|
25
|
+
import * as os from "node:os";
|
|
26
|
+
import * as path from "node:path";
|
|
24
27
|
import type { ChromeData } from "../visual/data.ts";
|
|
25
28
|
import { resolveQuotaProvider } from "./registry.ts";
|
|
26
29
|
import { formatReset } from "./format.ts";
|
|
27
30
|
|
|
31
|
+
/** TEMP DEBUG — append a line to the quota debug log. Remove after diagnosis. */
|
|
32
|
+
const DEBUG_LOG = path.join(os.tmpdir(), "pi-soly-quota-debug.log");
|
|
33
|
+
function dbg(msg: string): void {
|
|
34
|
+
try {
|
|
35
|
+
fs.appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${msg}\n`);
|
|
36
|
+
} catch { /* best effort */ }
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
/** Default poll interval: 60 seconds. */
|
|
29
40
|
const POLL_INTERVAL_MS = 60_000;
|
|
30
41
|
|
|
@@ -59,33 +70,44 @@ export function startQuotaPoller(
|
|
|
59
70
|
|
|
60
71
|
const tick = async (): Promise<void> => {
|
|
61
72
|
if (stopped) return;
|
|
73
|
+
dbg(`tick: enabled=${isEnabled()} provider=${data.modelProvider}`);
|
|
62
74
|
if (!isEnabled()) {
|
|
75
|
+
dbg("disabled, skipping");
|
|
63
76
|
scheduleNext();
|
|
64
77
|
return;
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
const providerId = data.modelProvider;
|
|
68
81
|
if (!providerId) {
|
|
82
|
+
dbg("no modelProvider");
|
|
69
83
|
scheduleNext();
|
|
70
84
|
return;
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
const provider = resolveQuotaProvider(providerId);
|
|
74
88
|
if (!provider) {
|
|
75
|
-
|
|
89
|
+
dbg(`no adapter for ${providerId}`);
|
|
76
90
|
data.quotaPercent = null;
|
|
77
91
|
data.quotaResetsLabel = null;
|
|
78
92
|
scheduleNext();
|
|
79
93
|
return;
|
|
80
94
|
}
|
|
81
95
|
|
|
82
|
-
|
|
96
|
+
let snapshot;
|
|
97
|
+
try {
|
|
98
|
+
snapshot = await provider.fetch();
|
|
99
|
+
dbg(`fetch result: ${JSON.stringify(snapshot)}`);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
dbg(`fetch threw: ${e instanceof Error ? e.message : String(e)}`);
|
|
102
|
+
snapshot = null;
|
|
103
|
+
}
|
|
83
104
|
if (snapshot) {
|
|
84
105
|
data.quotaPercent = snapshot.remainingPercent;
|
|
85
106
|
data.quotaResetsLabel = snapshot.resetsInMs !== null ? formatReset(snapshot.resetsInMs) : null;
|
|
107
|
+
dbg(`wrote data: pct=${data.quotaPercent} label=${data.quotaResetsLabel}`);
|
|
86
108
|
onUpdate();
|
|
109
|
+
dbg("called onUpdate (poke)");
|
|
87
110
|
}
|
|
88
|
-
// On null (fetch failed), keep the previous snapshot — don't clear.
|
|
89
111
|
scheduleNext();
|
|
90
112
|
};
|
|
91
113
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-coach
|
|
3
|
-
description: Use when the user complains about how the agent wrote code or behaves — phrases like "переделай X по-другому", "мне не нравится что agent делает Y", "почему опять Z", "не делай так", "agent keeps doing X", "I don't like the agent doing X", "заставь agent-а не делать X", "опять этот god switch". Analyzes the complaint, identifies the missing rule that would have prevented it, and proposes a soly rule draft (.agents/rules/*.md) for the user to confirm via ask_pro. Language-agnostic — works for any codebase. NOT for C#/.NET analyzer rules (use analyzer-coach for that).
|
|
3
|
+
description: Use when the user complains about how the agent wrote code or behaves — phrases like "переделай X по-другому", "мне не нравится что agent делает Y", "почему опять Z", "не делай так", "меня напрягает", "кринж", "убирай", "бесит", "agent keeps doing X", "I don't like the agent doing X", "заставь agent-а не делать X", "опять этот god switch", "redo differently", "annoying". Analyzes the complaint, identifies the missing rule that would have prevented it, and proposes a soly rule draft (.agents/rules/*.md) for the user to confirm via ask_pro. Language-agnostic — works for any codebase. NOT for C#/.NET analyzer rules (use analyzer-coach for that).
|
|
4
4
|
priority: high
|
|
5
5
|
---
|
|
6
6
|
|