pi-agent-extensions 0.3.2 → 0.3.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/extensions/answer/index.ts +12 -10
- package/extensions/ask-user/modes/print.ts +1 -1
- package/extensions/control/index.ts +15 -18
- package/extensions/cwd-history/index.ts +0 -4
- package/extensions/files/index.ts +6 -5
- package/extensions/handoff/index.ts +7 -6
- package/extensions/loop/index.ts +21 -17
- package/extensions/nvidia-nim/index.ts +1 -1
- package/extensions/review/index.ts +0 -4
- package/extensions/shared/auth.ts +40 -0
- package/extensions/todos/index.ts +3 -3
- package/extensions/whimsical/index.ts +1 -1
- package/package.json +2 -2
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { complete, type Model, type Api, type UserMessage } from "@mariozechner/pi-ai";
|
|
14
14
|
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
15
|
+
import { getRequestAuth, hasRequestAuth } from "../shared/auth.js";
|
|
15
16
|
import { BorderedLoader } from "@mariozechner/pi-coding-agent";
|
|
16
17
|
import {
|
|
17
18
|
type Component,
|
|
@@ -77,15 +78,17 @@ async function selectExtractionModel(
|
|
|
77
78
|
currentModel: Model<Api>,
|
|
78
79
|
modelRegistry: {
|
|
79
80
|
find: (provider: string, modelId: string) => Model<Api> | undefined;
|
|
80
|
-
|
|
81
|
+
getApiKeyAndHeaders: (
|
|
82
|
+
model: Model<Api>,
|
|
83
|
+
) => Promise<
|
|
84
|
+
| { ok: true; apiKey?: string; headers?: Record<string, string> }
|
|
85
|
+
| { ok: false; error: string }
|
|
86
|
+
>;
|
|
81
87
|
},
|
|
82
88
|
): Promise<Model<Api>> {
|
|
83
89
|
const codexModel = modelRegistry.find("openai-codex", CODEX_MODEL_ID);
|
|
84
|
-
if (codexModel) {
|
|
85
|
-
|
|
86
|
-
if (apiKey) {
|
|
87
|
-
return codexModel;
|
|
88
|
-
}
|
|
90
|
+
if (codexModel && (await hasRequestAuth(modelRegistry, codexModel))) {
|
|
91
|
+
return codexModel;
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
const haikuModel = modelRegistry.find("anthropic", HAIKU_MODEL_ID);
|
|
@@ -93,8 +96,7 @@ async function selectExtractionModel(
|
|
|
93
96
|
return currentModel;
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
|
|
97
|
-
if (!apiKey) {
|
|
99
|
+
if (!(await hasRequestAuth(modelRegistry, haikuModel))) {
|
|
98
100
|
return currentModel;
|
|
99
101
|
}
|
|
100
102
|
|
|
@@ -457,7 +459,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
457
459
|
loader.onAbort = () => done(null);
|
|
458
460
|
|
|
459
461
|
const doExtract = async () => {
|
|
460
|
-
const
|
|
462
|
+
const requestAuth = await getRequestAuth(ctx.modelRegistry, extractionModel);
|
|
461
463
|
const userMessage: UserMessage = {
|
|
462
464
|
role: "user",
|
|
463
465
|
content: [{ type: "text", text: lastAssistantText! }],
|
|
@@ -467,7 +469,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
467
469
|
const response = await complete(
|
|
468
470
|
extractionModel,
|
|
469
471
|
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
|
470
|
-
{
|
|
472
|
+
{ ...requestAuth, signal: loader.signal },
|
|
471
473
|
);
|
|
472
474
|
|
|
473
475
|
if (response.stopReason === "aborted") {
|
|
@@ -40,7 +40,7 @@ export async function createPendingFile(params: AskUserParams, ctx: ExtensionCon
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
// Write file
|
|
43
|
-
await writeFile(pendingFile, JSON.stringify(pending, null, 2), "utf-8");
|
|
43
|
+
await writeFile(pendingFile, JSON.stringify(pending, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
44
44
|
|
|
45
45
|
return pendingFile;
|
|
46
46
|
}
|
|
@@ -38,9 +38,11 @@
|
|
|
38
38
|
import type { ExtensionAPI, ExtensionContext, TurnEndEvent, MessageRenderer } from "@mariozechner/pi-coding-agent";
|
|
39
39
|
import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
|
|
40
40
|
import { complete, type Model, type Api, type UserMessage, type TextContent } from "@mariozechner/pi-ai";
|
|
41
|
+
import { getRequestAuth, hasRequestAuth } from "../shared/auth.js";
|
|
41
42
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
42
43
|
import { Box, Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
|
|
43
44
|
import { Type } from "@sinclair/typebox";
|
|
45
|
+
import crypto from "node:crypto";
|
|
44
46
|
import { promises as fs } from "node:fs";
|
|
45
47
|
import * as net from "node:net";
|
|
46
48
|
import * as os from "node:os";
|
|
@@ -155,20 +157,19 @@ async function selectSummarizationModel(
|
|
|
155
157
|
currentModel: Model<Api> | undefined,
|
|
156
158
|
modelRegistry: {
|
|
157
159
|
find: (provider: string, modelId: string) => Model<Api> | undefined;
|
|
158
|
-
|
|
160
|
+
getApiKeyAndHeaders: (
|
|
161
|
+
model: Model<Api>,
|
|
162
|
+
) => Promise<
|
|
163
|
+
| { ok: true; apiKey?: string; headers?: Record<string, string> }
|
|
164
|
+
| { ok: false; error: string }
|
|
165
|
+
>;
|
|
159
166
|
},
|
|
160
167
|
): Promise<Model<Api> | undefined> {
|
|
161
168
|
const codexModel = modelRegistry.find("openai-codex", CODEX_MODEL_ID);
|
|
162
|
-
if (codexModel)
|
|
163
|
-
const apiKey = await modelRegistry.getApiKey(codexModel);
|
|
164
|
-
if (apiKey) return codexModel;
|
|
165
|
-
}
|
|
169
|
+
if (codexModel && (await hasRequestAuth(modelRegistry, codexModel))) return codexModel;
|
|
166
170
|
|
|
167
171
|
const haikuModel = modelRegistry.find("anthropic", HAIKU_MODEL_ID);
|
|
168
|
-
if (haikuModel)
|
|
169
|
-
const apiKey = await modelRegistry.getApiKey(haikuModel);
|
|
170
|
-
if (apiKey) return haikuModel;
|
|
171
|
-
}
|
|
172
|
+
if (haikuModel && (await hasRequestAuth(modelRegistry, haikuModel))) return haikuModel;
|
|
172
173
|
|
|
173
174
|
return currentModel;
|
|
174
175
|
}
|
|
@@ -603,7 +604,7 @@ async function handleCommand(
|
|
|
603
604
|
// Subscribe to turn_end
|
|
604
605
|
if (command.type === "subscribe") {
|
|
605
606
|
if (command.event === "turn_end") {
|
|
606
|
-
const subscriptionId = id ?? `sub_${
|
|
607
|
+
const subscriptionId = id ?? `sub_${crypto.randomBytes(16).toString("hex")}`;
|
|
607
608
|
state.turnEndSubscriptions.push({ socket, subscriptionId });
|
|
608
609
|
|
|
609
610
|
const cleanup = () => {
|
|
@@ -645,9 +646,9 @@ async function handleCommand(
|
|
|
645
646
|
return;
|
|
646
647
|
}
|
|
647
648
|
|
|
648
|
-
const
|
|
649
|
-
if (!
|
|
650
|
-
respond(false, "get_summary", undefined, "No
|
|
649
|
+
const requestAuth = await getRequestAuth(ctx.modelRegistry, model);
|
|
650
|
+
if (!requestAuth) {
|
|
651
|
+
respond(false, "get_summary", undefined, "No auth available for summarization model");
|
|
651
652
|
return;
|
|
652
653
|
}
|
|
653
654
|
|
|
@@ -665,7 +666,7 @@ async function handleCommand(
|
|
|
665
666
|
const response = await complete(
|
|
666
667
|
model,
|
|
667
668
|
{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: [userMessage] },
|
|
668
|
-
|
|
669
|
+
requestAuth,
|
|
669
670
|
);
|
|
670
671
|
|
|
671
672
|
if (response.stopReason === "aborted" || response.stopReason === "error") {
|
|
@@ -1004,10 +1005,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
1004
1005
|
await refreshServer(ctx);
|
|
1005
1006
|
});
|
|
1006
1007
|
|
|
1007
|
-
pi.on("session_switch", async (_event, ctx) => {
|
|
1008
|
-
await refreshServer(ctx);
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
1008
|
pi.on("session_shutdown", async () => {
|
|
1012
1009
|
if (state.aliasTimer) {
|
|
1013
1010
|
clearInterval(state.aliasTimer);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
10
|
+
import crypto from "node:crypto";
|
|
10
11
|
import {
|
|
11
12
|
existsSync,
|
|
12
13
|
mkdtempSync,
|
|
@@ -693,10 +694,10 @@ const openPath = async (pi: ExtensionAPI, ctx: ExtensionContext, target: FileEnt
|
|
|
693
694
|
};
|
|
694
695
|
|
|
695
696
|
const openExternalEditor = (tui: TUI, editorCmd: string, content: string): string | null => {
|
|
696
|
-
const tmpFile = path.join(os.tmpdir(), `pi-files-edit-${
|
|
697
|
+
const tmpFile = path.join(os.tmpdir(), `pi-files-edit-${crypto.randomBytes(16).toString("hex")}.txt`);
|
|
697
698
|
|
|
698
699
|
try {
|
|
699
|
-
writeFileSync(tmpFile, content, "utf8");
|
|
700
|
+
writeFileSync(tmpFile, content, { encoding: "utf8", mode: 0o600 });
|
|
700
701
|
tui.stop();
|
|
701
702
|
|
|
702
703
|
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
@@ -813,15 +814,15 @@ const openDiff = async (pi: ExtensionAPI, ctx: ExtensionContext, target: FileEnt
|
|
|
813
814
|
ctx.ui.notify(errorMessage, "error");
|
|
814
815
|
return;
|
|
815
816
|
}
|
|
816
|
-
writeFileSync(tmpFile, result.stdout ?? "", "utf8");
|
|
817
|
+
writeFileSync(tmpFile, result.stdout ?? "", { encoding: "utf8", mode: 0o600 });
|
|
817
818
|
} else {
|
|
818
|
-
writeFileSync(tmpFile, "", "utf8");
|
|
819
|
+
writeFileSync(tmpFile, "", { encoding: "utf8", mode: 0o600 });
|
|
819
820
|
}
|
|
820
821
|
|
|
821
822
|
let workingPath = target.resolvedPath;
|
|
822
823
|
if (!existsSync(target.resolvedPath)) {
|
|
823
824
|
workingPath = path.join(tmpDir, `pi-files-working-${path.basename(target.displayPath)}`);
|
|
824
|
-
writeFileSync(workingPath, "", "utf8");
|
|
825
|
+
writeFileSync(workingPath, "", { encoding: "utf8", mode: 0o600 });
|
|
825
826
|
}
|
|
826
827
|
|
|
827
828
|
const openResult = await pi.exec("code", ["--diff", tmpFile, workingPath], { cwd: gitRoot });
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
serializeConversation,
|
|
23
23
|
} from "@mariozechner/pi-coding-agent";
|
|
24
24
|
|
|
25
|
+
import { getRequestAuthOrThrow } from "../shared/auth.js";
|
|
25
26
|
import { loadConfig, validateGoal } from "./config.js";
|
|
26
27
|
import { ProgressLoader, EXTRACTION_PHASES } from "./progress.js";
|
|
27
28
|
import {
|
|
@@ -290,7 +291,7 @@ async function doExtraction(
|
|
|
290
291
|
model: Model<any>,
|
|
291
292
|
signal?: AbortSignal,
|
|
292
293
|
): Promise<ExtractionResult> {
|
|
293
|
-
const
|
|
294
|
+
const requestAuth = await getRequestAuthOrThrow(ctx.modelRegistry, model);
|
|
294
295
|
|
|
295
296
|
// Build user message
|
|
296
297
|
const userMessage: Message = {
|
|
@@ -305,7 +306,7 @@ async function doExtraction(
|
|
|
305
306
|
const response = await complete(
|
|
306
307
|
model,
|
|
307
308
|
{ systemPrompt: EXTRACTION_SYSTEM_PROMPT, messages: [userMessage] },
|
|
308
|
-
{
|
|
309
|
+
{ ...requestAuth, signal },
|
|
309
310
|
);
|
|
310
311
|
|
|
311
312
|
if (response.stopReason === "aborted") {
|
|
@@ -347,7 +348,7 @@ async function doExtraction(
|
|
|
347
348
|
systemPrompt: EXTRACTION_SYSTEM_PROMPT,
|
|
348
349
|
messages: [userMessage, assistantMessage, retryMessage],
|
|
349
350
|
},
|
|
350
|
-
{
|
|
351
|
+
{ ...requestAuth, signal },
|
|
351
352
|
);
|
|
352
353
|
|
|
353
354
|
if (retryResponse.stopReason === "aborted") {
|
|
@@ -383,7 +384,7 @@ async function doExtractionWithPhases(
|
|
|
383
384
|
signal: AbortSignal,
|
|
384
385
|
onPhase: (phase: string) => void,
|
|
385
386
|
): Promise<ExtractionResult> {
|
|
386
|
-
const
|
|
387
|
+
const requestAuth = await getRequestAuthOrThrow(ctx.modelRegistry, model);
|
|
387
388
|
|
|
388
389
|
// Phase 1: Analyzing conversation
|
|
389
390
|
onPhase(EXTRACTION_PHASES[0]);
|
|
@@ -404,7 +405,7 @@ async function doExtractionWithPhases(
|
|
|
404
405
|
const response = await complete(
|
|
405
406
|
model,
|
|
406
407
|
{ systemPrompt: EXTRACTION_SYSTEM_PROMPT, messages: [userMessage] },
|
|
407
|
-
{
|
|
408
|
+
{ ...requestAuth, signal },
|
|
408
409
|
);
|
|
409
410
|
|
|
410
411
|
if (response.stopReason === "aborted") {
|
|
@@ -451,7 +452,7 @@ async function doExtractionWithPhases(
|
|
|
451
452
|
systemPrompt: EXTRACTION_SYSTEM_PROMPT,
|
|
452
453
|
messages: [userMessage, assistantMessage, retryMessage],
|
|
453
454
|
},
|
|
454
|
-
{
|
|
455
|
+
{ ...requestAuth, signal },
|
|
455
456
|
);
|
|
456
457
|
|
|
457
458
|
if (retryResponse.stopReason === "aborted") {
|
package/extensions/loop/index.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import { Type } from "@sinclair/typebox";
|
|
10
10
|
import { complete, type Api, type Model, type UserMessage } from "@mariozechner/pi-ai";
|
|
11
|
-
import type { ExtensionAPI, ExtensionContext
|
|
11
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
12
12
|
import { compact } from "@mariozechner/pi-coding-agent";
|
|
13
13
|
import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
|
|
14
14
|
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
|
15
|
+
import { getRequestAuth, hasRequestAuth } from "../shared/auth.js";
|
|
15
16
|
|
|
16
17
|
type LoopMode = "tests" | "custom" | "self";
|
|
17
18
|
|
|
@@ -87,22 +88,22 @@ function getConditionText(mode: LoopMode, condition?: string): string {
|
|
|
87
88
|
|
|
88
89
|
async function selectSummaryModel(
|
|
89
90
|
ctx: ExtensionContext,
|
|
90
|
-
): Promise<{ model: Model<Api>;
|
|
91
|
+
): Promise<{ model: Model<Api>; requestAuth: { apiKey?: string; headers?: Record<string, string> } } | null> {
|
|
91
92
|
if (!ctx.model) return null;
|
|
92
93
|
|
|
93
94
|
if (ctx.model.provider === "anthropic") {
|
|
94
95
|
const haikuModel = ctx.modelRegistry.find("anthropic", HAIKU_MODEL_ID);
|
|
95
|
-
if (haikuModel) {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
return { model: haikuModel,
|
|
96
|
+
if (haikuModel && (await hasRequestAuth(ctx.modelRegistry, haikuModel))) {
|
|
97
|
+
const requestAuth = await getRequestAuth(ctx.modelRegistry, haikuModel);
|
|
98
|
+
if (requestAuth) {
|
|
99
|
+
return { model: haikuModel, requestAuth };
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
const
|
|
104
|
-
if (!
|
|
105
|
-
return { model: ctx.model,
|
|
104
|
+
const requestAuth = await getRequestAuth(ctx.modelRegistry, ctx.model);
|
|
105
|
+
if (!requestAuth) return null;
|
|
106
|
+
return { model: ctx.model, requestAuth };
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
async function summarizeBreakoutCondition(
|
|
@@ -124,7 +125,7 @@ async function summarizeBreakoutCondition(
|
|
|
124
125
|
const response = await complete(
|
|
125
126
|
selection.model,
|
|
126
127
|
{ systemPrompt: SUMMARY_SYSTEM_PROMPT, messages: [userMessage] },
|
|
127
|
-
|
|
128
|
+
selection.requestAuth,
|
|
128
129
|
);
|
|
129
130
|
|
|
130
131
|
if (response.stopReason === "aborted" || response.stopReason === "error") {
|
|
@@ -400,15 +401,22 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
400
401
|
|
|
401
402
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
402
403
|
if (!loopState.active || !loopState.mode || !ctx.model) return;
|
|
403
|
-
const
|
|
404
|
-
if (!
|
|
404
|
+
const requestAuth = await getRequestAuth(ctx.modelRegistry, ctx.model);
|
|
405
|
+
if (!requestAuth) return;
|
|
405
406
|
|
|
406
407
|
const instructionParts = [event.customInstructions, getCompactionInstructions(loopState.mode, loopState.condition)]
|
|
407
408
|
.filter(Boolean)
|
|
408
409
|
.join("\n\n");
|
|
409
410
|
|
|
410
411
|
try {
|
|
411
|
-
const compaction = await compact(
|
|
412
|
+
const compaction = await compact(
|
|
413
|
+
event.preparation,
|
|
414
|
+
ctx.model,
|
|
415
|
+
requestAuth.apiKey ?? "",
|
|
416
|
+
requestAuth.headers,
|
|
417
|
+
instructionParts,
|
|
418
|
+
event.signal,
|
|
419
|
+
);
|
|
412
420
|
return { compaction };
|
|
413
421
|
} catch (error) {
|
|
414
422
|
if (ctx.hasUI) {
|
|
@@ -439,8 +447,4 @@ export default function loopExtension(pi: ExtensionAPI): void {
|
|
|
439
447
|
pi.on("session_start", async (_event, ctx) => {
|
|
440
448
|
await restoreLoopState(ctx);
|
|
441
449
|
});
|
|
442
|
-
|
|
443
|
-
pi.on("session_switch", async (_event: SessionSwitchEvent, ctx) => {
|
|
444
|
-
await restoreLoopState(ctx);
|
|
445
|
-
});
|
|
446
450
|
}
|
|
@@ -69,7 +69,7 @@ export function loadModelsConfigSync(): NvidiaModelsConfig | null {
|
|
|
69
69
|
export async function saveModelsConfig(config: NvidiaModelsConfig): Promise<void> {
|
|
70
70
|
const configPath = getConfigPath();
|
|
71
71
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
72
|
-
await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
72
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** Parse model IDs from editor text, ignoring comments and blank lines.
|
|
@@ -485,10 +485,6 @@ export default function reviewExtension(pi: ExtensionAPI) {
|
|
|
485
485
|
applyReviewState(ctx);
|
|
486
486
|
});
|
|
487
487
|
|
|
488
|
-
pi.on("session_switch", (_event, ctx) => {
|
|
489
|
-
applyReviewState(ctx);
|
|
490
|
-
});
|
|
491
|
-
|
|
492
488
|
pi.on("session_tree", (_event, ctx) => {
|
|
493
489
|
applyReviewState(ctx);
|
|
494
490
|
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Api, Model, ProviderStreamOptions } from "@mariozechner/pi-ai";
|
|
2
|
+
|
|
3
|
+
export type RequestAuth = Pick<ProviderStreamOptions, "apiKey" | "headers">;
|
|
4
|
+
|
|
5
|
+
type ModelRegistryWithRequestAuth = {
|
|
6
|
+
getApiKeyAndHeaders: (
|
|
7
|
+
model: Model<Api>,
|
|
8
|
+
) => Promise<
|
|
9
|
+
| { ok: true; apiKey?: string; headers?: Record<string, string> }
|
|
10
|
+
| { ok: false; error: string }
|
|
11
|
+
>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export async function getRequestAuth(
|
|
15
|
+
modelRegistry: ModelRegistryWithRequestAuth,
|
|
16
|
+
model: Model<Api>,
|
|
17
|
+
): Promise<RequestAuth | undefined> {
|
|
18
|
+
const auth = await modelRegistry.getApiKeyAndHeaders(model);
|
|
19
|
+
if (!auth.ok) return undefined;
|
|
20
|
+
return { apiKey: auth.apiKey, headers: auth.headers };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function getRequestAuthOrThrow(
|
|
24
|
+
modelRegistry: ModelRegistryWithRequestAuth,
|
|
25
|
+
model: Model<Api>,
|
|
26
|
+
): Promise<RequestAuth> {
|
|
27
|
+
const auth = await modelRegistry.getApiKeyAndHeaders(model);
|
|
28
|
+
if (!auth.ok) {
|
|
29
|
+
throw new Error(auth.error);
|
|
30
|
+
}
|
|
31
|
+
return { apiKey: auth.apiKey, headers: auth.headers };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function hasRequestAuth(
|
|
35
|
+
modelRegistry: ModelRegistryWithRequestAuth,
|
|
36
|
+
model: Model<Api>,
|
|
37
|
+
): Promise<boolean> {
|
|
38
|
+
const auth = await modelRegistry.getApiKeyAndHeaders(model);
|
|
39
|
+
return auth.ok;
|
|
40
|
+
}
|
|
@@ -923,7 +923,7 @@ async function readTodoFile(filePath: string, idFallback: string): Promise<TodoR
|
|
|
923
923
|
}
|
|
924
924
|
|
|
925
925
|
async function writeTodoFile(filePath: string, todo: TodoRecord) {
|
|
926
|
-
await fs.writeFile(filePath, serializeTodo(todo), "utf8");
|
|
926
|
+
await fs.writeFile(filePath, serializeTodo(todo), { encoding: "utf8", mode: 0o600 });
|
|
927
927
|
}
|
|
928
928
|
|
|
929
929
|
async function generateTodoId(todosDir: string): Promise<string> {
|
|
@@ -955,14 +955,14 @@ async function acquireLock(
|
|
|
955
955
|
|
|
956
956
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
957
957
|
try {
|
|
958
|
-
const handle = await fs.open(lockPath, "wx");
|
|
958
|
+
const handle = await fs.open(lockPath, "wx", 0o600);
|
|
959
959
|
const info: LockInfo = {
|
|
960
960
|
id,
|
|
961
961
|
pid: process.pid,
|
|
962
962
|
session,
|
|
963
963
|
created_at: new Date(now).toISOString(),
|
|
964
964
|
};
|
|
965
|
-
await handle.writeFile(JSON.stringify(info, null, 2), "utf8");
|
|
965
|
+
await handle.writeFile(JSON.stringify(info, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
966
966
|
await handle.close();
|
|
967
967
|
return async () => {
|
|
968
968
|
try {
|
|
@@ -263,7 +263,7 @@ async function saveStateToSettings(): Promise<void> {
|
|
|
263
263
|
} satisfies PersistedWhimsyConfig;
|
|
264
264
|
|
|
265
265
|
await fs.mkdir(dir, { recursive: true });
|
|
266
|
-
await fs.writeFile(settingsPath, JSON.stringify(parsed, null, 2), "utf-8");
|
|
266
|
+
await fs.writeFile(settingsPath, JSON.stringify(parsed, null, 2), { encoding: "utf-8", mode: 0o600 });
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
async function ensureStateLoaded(): Promise<void> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agent-extensions",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Collection of extensions for pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@sinclair/typebox": "^0.34.48",
|
|
65
|
-
"tsx": "^4.
|
|
65
|
+
"tsx": "^4.21.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"test": "node --import tsx --test 'tests/**/*.test.ts' 'tests/*.test.ts'"
|