pi-adaptive-thinking 0.2.1 → 0.2.2
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/package.json +1 -7
- package/src/adaptive-thinking-lifecycle.ts +45 -10
- package/src/config.ts +0 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-adaptive-thinking",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Pi extension for adaptive reasoning-effort control",
|
|
6
6
|
"keywords": [
|
|
@@ -31,12 +31,6 @@
|
|
|
31
31
|
"access": "public",
|
|
32
32
|
"provenance": true
|
|
33
33
|
},
|
|
34
|
-
"dependencies": {
|
|
35
|
-
"proper-lockfile": "^4.1.2"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"@types/proper-lockfile": "4.1.4"
|
|
39
|
-
},
|
|
40
34
|
"peerDependencies": {
|
|
41
35
|
"@earendil-works/pi-ai": "*",
|
|
42
36
|
"@earendil-works/pi-coding-agent": "*",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { open, rm, stat } from "node:fs/promises";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
import lockfile from "proper-lockfile";
|
|
5
5
|
import type {
|
|
6
6
|
AgentEndEvent,
|
|
7
7
|
AgentToolResult,
|
|
@@ -147,20 +147,53 @@ const agentDir = () => process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi",
|
|
|
147
147
|
|
|
148
148
|
const globalSettingsPath = () => join(agentDir(), "settings.json");
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
150
|
+
// ponytail: Node's wx exclusive-create plus an asynchronous fixed-delay retry loop replaces
|
|
151
|
+
// proper-lockfile; the bound matches its previous policy (99 retries at a fixed 20 ms delay).
|
|
152
|
+
// The .lock suffix distinguishes owned lock files from the legacy always-present marker the
|
|
153
|
+
// previous implementation pre-created next to the settings document.
|
|
154
|
+
// Stale recovery assumes the critical section stays synchronous; use a heartbeat lock if it gains
|
|
155
|
+
// asynchronous work.
|
|
156
|
+
const SETTINGS_LOCK_RETRY_DELAY_MS = 20;
|
|
157
|
+
const SETTINGS_LOCK_RETRIES = 99;
|
|
158
|
+
const SETTINGS_LOCK_STALE_MS = 10_000;
|
|
159
|
+
|
|
160
|
+
const sleep = (ms: number): Promise<void> =>
|
|
161
|
+
new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
162
|
+
|
|
163
|
+
const acquireSettingsLock = async (lockPath: string): Promise<void> => {
|
|
164
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
165
|
+
try {
|
|
166
|
+
const handle = await open(lockPath, "wx");
|
|
167
|
+
await handle.close();
|
|
168
|
+
return;
|
|
169
|
+
} catch (cause) {
|
|
170
|
+
if (!(cause instanceof Error) || !("code" in cause) || cause.code !== "EEXIST") throw cause;
|
|
171
|
+
try {
|
|
172
|
+
const lock = await stat(lockPath);
|
|
173
|
+
if (lock.mtimeMs < Date.now() - SETTINGS_LOCK_STALE_MS) {
|
|
174
|
+
await rm(lockPath, { force: true });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
} catch (statCause) {
|
|
178
|
+
if (!(statCause instanceof Error) || !("code" in statCause) || statCause.code !== "ENOENT")
|
|
179
|
+
throw statCause;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (attempt >= SETTINGS_LOCK_RETRIES) throw cause;
|
|
183
|
+
}
|
|
184
|
+
await sleep(SETTINGS_LOCK_RETRY_DELAY_MS);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
154
187
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
188
|
+
const withSettingsLock = async <T>(settingsPath: string, fn: () => T): Promise<T> => {
|
|
189
|
+
mkdirSync(join(settingsPath, ".."), { recursive: true });
|
|
190
|
+
const lockPath = `${settingsPath}.adaptive-thinking.lock`;
|
|
191
|
+
await acquireSettingsLock(lockPath);
|
|
159
192
|
|
|
160
193
|
try {
|
|
161
194
|
return await fn();
|
|
162
195
|
} finally {
|
|
163
|
-
await
|
|
196
|
+
await rm(lockPath, { force: true });
|
|
164
197
|
}
|
|
165
198
|
};
|
|
166
199
|
|
|
@@ -385,6 +418,8 @@ export default function adaptiveThinkingExtension(pi: ExtensionAPI) {
|
|
|
385
418
|
onSessionStart: (handler) => pi.on("session_start", handler),
|
|
386
419
|
onToolCall: (handler) => pi.on("tool_call", handler),
|
|
387
420
|
onAgentEnd: (handler) => pi.on("agent_end", handler),
|
|
421
|
+
// ponytail: the branches look identical but narrow the union so registerTool's
|
|
422
|
+
// generics infer per concrete ToolDefinition instead of falling back to defaults.
|
|
388
423
|
registerTool: (tool) => {
|
|
389
424
|
if (isAdaptiveThinkingSetThinkingLevelTool(tool)) pi.registerTool(tool);
|
|
390
425
|
else pi.registerTool(tool);
|
package/src/config.ts
CHANGED
|
@@ -77,7 +77,3 @@ export const parseAdaptiveThinkingConfig = (
|
|
|
77
77
|
|
|
78
78
|
return { config, usedDeprecatedSystemPrompt: usesSystemPrompt };
|
|
79
79
|
};
|
|
80
|
-
|
|
81
|
-
/** Parses configuration at the public configuration seam. */
|
|
82
|
-
export const parseConfig = (input: JsonValue | undefined): AdaptiveThinkingConfig =>
|
|
83
|
-
parseAdaptiveThinkingConfig(input).config;
|