pi-orchestrator-config 4.4.1 → 4.5.0
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/README.md +1 -1
- package/extensions/coms/coms-p2p.ts +51 -39
- package/extensions/coms/coms-shared.ts +7 -5
- package/extensions/orchestrator/agents.ts +9 -1
- package/extensions/orchestrator/project-settings.ts +9 -29
- package/extensions/orchestrator/rules.ts +4 -1
- package/extensions/orchestrator/session-validation.ts +45 -24
- package/extensions/orchestrator/settings-source.ts +139 -0
- package/extensions/orchestrator/utils.ts +1 -1
- package/extensions/pidash/event-projection.ts +19 -0
- package/extensions/pidash/pidash-ui/dist/assets/{index-C1VH6cPk.js → index-CI6UL4g4.js} +1 -1
- package/extensions/pidash/pidash-ui/dist/index.html +1 -1
- package/extensions/pidash/pidash.ts +6 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{angular-html-ghA4VuM0.js → angular-html-Bfyy3ooe.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{angular-ts-BiEwt-Qs.js → angular-ts-nVgE7bZq.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{apl-DmYAGNOL.js → apl-tvfwqHpz.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{astro-Bju7L81l.js → astro-l8TnD3AA.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{blade-C_YtSRJN.js → blade-BdHBbmKC.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{c-DxcP9pmN.js → c-D1CypT9z.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{chapel-BjVM8zvB.js → chapel-D56VAXz2.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{cobol-CyAifE-8.js → cobol-BwRRZSIY.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{coffee-BNW4Zttb.js → coffee-Cet_1pvq.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{cpp-De6s9lN-.js → cpp-CZ8C9S-h.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{crystal-JzhjW33_.js → crystal-EOxgVG_1.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{css-DAnI1EKT.js → css-lK_4bfX4.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{edge-DlbkxEj4.js → edge-C7pTOYjM.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{elixir-BXz6vpDX.js → elixir-D1OnpXXN.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{elm-CJcSKM6_.js → elm-DPOsZNNm.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{erb-VHOaTR5g.js → erb-D_E9Tmw3.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{git-rebase-PAYwCVk-.js → git-rebase-FRK9CwsX.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glimmer-js-CspJJiU_.js → glimmer-js-BNQDK9N_.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glimmer-ts-CSMIai5o.js → glimmer-ts-TWnnEP-7.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{glsl-Cw9yKL2B.js → glsl-C90MQFKT.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{graphql-D0OMgGsM.js → graphql-hToq3IB7.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{hack-DfiAACVA.js → hack-B9KIVyIF.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{haml-DU8pAbRK.js → haml-CP3pGCyN.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{handlebars-CoaiK3xT.js → handlebars-D1bq27yj.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{html-B4C3aTRB.js → html-DDPkOyoL.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{html-derivative-C_NJUsxF.js → html-derivative-BR4J_BCH.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{http-Cr9w59E3.js → http-BmExgdIq.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{hurl-BEScLvfg.js → hurl-UvOw6UjF.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/index-Bo8LhGN_.js +2796 -0
- package/extensions/pidiff/pidiff-ui/dist/assets/{java-hb-3TTCg.js → java-CFjffq9z.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{javascript-Bc0DeQtz.js → javascript-D6jF2sRD.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jinja-DoEIYL2y.js → jinja-_MAtAn3c.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jison-C2OZYR2e.js → jison-CzjG9woH.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{json-DKeabECb.js → json-Cq0awPRn.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{jsx-Dadu-wAB.js → jsx-iffPQUDY.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{julia-CpUr0nh2.js → julia-BJh5g1V6.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{just-BysHL69Z.js → just-BiplQU-x.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{latex-iNrlK36R.js → latex-C9i8ER6l.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{liquid-g8x_A5Ru.js → liquid-CJFKQVVC.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{lua-B4BSWsUC.js → lua-CQPggDzM.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{marko-CHSFXjEi.js → marko-d7_vCT_U.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{mdc-js2C5rVG.js → mdc-5MqUEkF6.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{nginx-BblTQvRl.js → nginx-BGYrJESG.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{nim-ChxMAPPZ.js → nim-BT5d3bbB.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{org-CHoj3SWZ.js → org-Dcsoek_G.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{perl-3zusLAIc.js → perl-Dzum8ON5.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{php-DVx3dbu3.js → php-DMd6jR7H.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{pug-DITNjLsK.js → pug-zSUb8M7G.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{qml-BYmD788c.js → qml-B-xKh7qQ.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{r-Dp_qVPOX.js → r-Bjwskoo1.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{razor-DT4CEDjF.js → razor-CXf_WKrJ.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{regexp-DVFGbBOe.js → regexp-DT6fzMFy.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{rst-njZM0E3u.js → rst-9nW3vkMx.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{ruby-gu2hDDCx.js → ruby-qIjs3ahE.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{sas-Bo6NwyH7.js → sas-C5jUuuyr.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{scss-COdGMqPA.js → scss-DDLNCEB-.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{shellscript-DSg7qNC1.js → shellscript-BhivxeP8.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{shellsession-CD1RmUpx.js → shellsession-n_duFdfp.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{soy-C0QWCEMl.js → soy-Dpt5nO7G.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{sql-VoWQdTUw.js → sql-BtjLhqvp.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{stata-BEj75h4q.js → stata-D4GbpMWg.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{surrealql-Cj1XbW6j.js → surrealql-Cn3MlekA.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{svelte-HcwCBvjC.js → svelte-DkuizfXa.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{templ-D775vyzu.js → templ-Bu3VqwV1.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{tex-DNmK_71Q.js → tex-BmuNCLCK.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{ts-tags-KERjFDJj.js → ts-tags-BYWWTBIT.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{tsx-CMT8AiHA.js → tsx-DNTVrA8s.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{twig-B5uxCA6R.js → twig-CxSsuKw_.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{typescript-5zwf3bJw.js → typescript-nBJ-jOMj.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{typst-xx02l63O.js → typst-CJ8ooa07.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-BvvQRkFj.js → vue-CFh-25xK.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-html-BrBDvgBy.js → vue-html-C0o6Mt8F.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{vue-vine-1CllPWUz.js → vue-vine-CdvaqX2M.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/worker-1fjNO6T8.js +164 -0
- package/extensions/pidiff/pidiff-ui/dist/assets/{xml-C3nsfg4x.js → xml-DgTZJwkL.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{xsl-DDOShcMh.js → xsl-DpEWsE0p.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/assets/{yaml-BGNXenqm.js → yaml-DnMT_m8N.js} +1 -1
- package/extensions/pidiff/pidiff-ui/dist/index.html +1 -1
- package/extensions/pidiff/pidiff-ui/package.json +1 -1
- package/extensions/providers/acpx-driver.ts +136 -69
- package/extensions/providers/claude-driver.ts +20 -18
- package/extensions/providers/cursor-acpx-driver.ts +136 -70
- package/extensions/providers/cursor-cli-driver.ts +22 -17
- package/extensions/providers/gemini-driver.ts +20 -17
- package/extensions/providers/index.ts +6 -3
- package/extensions/shared/build-system-prompt.ts +12 -5
- package/extensions/shared/create-runtime-provider.ts +1 -1
- package/extensions/shared/file-logger.ts +17 -16
- package/extensions/shared/logger.ts +7 -3
- package/package.json +10 -2
- package/scripts/daemon-shared.ts +11 -0
- package/scripts/pidash-server.ts +2 -2
- package/scripts/pidiff-server.ts +2 -2
- package/extensions/pidiff/pidiff-ui/dist/assets/index-CisAus9r.js +0 -2801
- package/extensions/pidiff/pidiff-ui/dist/assets/worker-BX5VNpEv.js +0 -161
package/README.md
CHANGED
|
@@ -886,7 +886,7 @@ See [DEVELOPMENT.md](DEVELOPMENT.md) for tips on testing extensions locally, run
|
|
|
886
886
|
|
|
887
887
|
## Prerequisites
|
|
888
888
|
|
|
889
|
-
- [pi](https://github.com/badlogic/pi-mono) (minimum version: **0.
|
|
889
|
+
- [pi](https://github.com/badlogic/pi-mono) (minimum version: **0.87.0**). Pidash uses Pi's `ui_prompt_start` and `ui_prompt_end` events to show prompt wait separately from agent work.
|
|
890
890
|
- `gh` CLI (for GitHub operations)
|
|
891
891
|
- `uv` (for Python execution)
|
|
892
892
|
- `myk-pi-tools` (optional, for `/pr-review` and `/release`)
|
|
@@ -848,10 +848,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
848
848
|
ackOk(socket, env.msg_id);
|
|
849
849
|
}
|
|
850
850
|
|
|
851
|
-
function buildAgentCard(): AgentCard {
|
|
851
|
+
async function buildAgentCard(): Promise<AgentCard> {
|
|
852
852
|
const ctx = currentCtx;
|
|
853
853
|
const ident = identity;
|
|
854
854
|
const pct = ctx ? Math.round(ctx.getContextUsage()?.percent ?? 0) : 0;
|
|
855
|
+
log.debug("build_agent_card", { hasIdentity: !!ident });
|
|
855
856
|
return {
|
|
856
857
|
name: ident?.name ?? "unknown",
|
|
857
858
|
purpose: ident?.purpose ?? "",
|
|
@@ -859,22 +860,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
859
860
|
color: ident?.color ?? "#36F9F6",
|
|
860
861
|
context_used_pct: pct,
|
|
861
862
|
queue_depth: getPendingInboundCount(),
|
|
862
|
-
tasks_summary: readTaskSummary(currentCtx?.cwd ?? process.cwd(), currentCtx?.sessionManager?.getSessionId?.()),
|
|
863
|
+
tasks_summary: await readTaskSummary(currentCtx?.cwd ?? process.cwd(), currentCtx?.sessionManager?.getSessionId?.()),
|
|
863
864
|
};
|
|
864
865
|
}
|
|
865
866
|
|
|
866
867
|
/** Push current agent card to ping worker so it can respond independently */
|
|
867
|
-
function updatePingWorkerCard(): void {
|
|
868
|
+
async function updatePingWorkerCard(): Promise<void> {
|
|
869
|
+
log.debug("update_ping_worker_card", { ready: pingWorkerReady });
|
|
868
870
|
if (!pingWorker || !pingWorkerReady) return;
|
|
869
871
|
try {
|
|
870
|
-
const card = buildAgentCard();
|
|
872
|
+
const card = await buildAgentCard();
|
|
871
873
|
const pong: Pong = { type: "pong", msg_id: "", agent_card: card };
|
|
872
874
|
pingWorker.postMessage({ type: "update_card", pong });
|
|
873
875
|
} catch { /* ignore */ }
|
|
874
876
|
}
|
|
875
877
|
|
|
876
|
-
function handlePing(socket: net.Socket, env: PingEnvelope): void {
|
|
877
|
-
|
|
878
|
+
async function handlePing(socket: net.Socket, env: PingEnvelope): Promise<void> {
|
|
879
|
+
log.debug("handle_ping", { msgId: env.msg_id });
|
|
880
|
+
const card = await buildAgentCard();
|
|
878
881
|
const pong: Pong = { type: "pong", msg_id: env.msg_id, agent_card: card };
|
|
879
882
|
try {
|
|
880
883
|
socket.write(JSON.stringify(pong) + "\n");
|
|
@@ -883,7 +886,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
883
886
|
}
|
|
884
887
|
try { socket.end(); } catch { /* ignore */ }
|
|
885
888
|
// Also update worker with latest card
|
|
886
|
-
updatePingWorkerCard();
|
|
889
|
+
await updatePingWorkerCard();
|
|
887
890
|
}
|
|
888
891
|
|
|
889
892
|
function isValidEnvelope(obj: any): obj is Envelope {
|
|
@@ -935,7 +938,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
935
938
|
} else if (parsed.type === "response") {
|
|
936
939
|
handleResponse(socket, parsed as ResponseEnvelope);
|
|
937
940
|
} else if (parsed.type === "ping") {
|
|
938
|
-
handlePing(socket, parsed as PingEnvelope);
|
|
941
|
+
void handlePing(socket, parsed as PingEnvelope).catch(() => nack(socket, parsed.msg_id, "internal error"));
|
|
939
942
|
} else if (parsed.type === "task_update") {
|
|
940
943
|
handleTaskUpdate(socket, parsed as TaskUpdateEnvelope);
|
|
941
944
|
} else if (parsed.type === "queue_manage") {
|
|
@@ -1994,7 +1997,7 @@ Do not respond to this message.`;
|
|
|
1994
1997
|
// Append sender's active tasks for this peer to the prompt
|
|
1995
1998
|
let promptText = params.prompt;
|
|
1996
1999
|
try {
|
|
1997
|
-
const { listTasksForSession } =
|
|
2000
|
+
const { listTasksForSession } = await import("../pitasks/index.js");
|
|
1998
2001
|
const targetPiSessionId = target.pi_session_id || target.coms_session_id;
|
|
1999
2002
|
const allTasks = listTasksForSession(targetPiSessionId, target.cwd) || [];
|
|
2000
2003
|
const myTasks = allTasks.filter((t: any) =>
|
|
@@ -2021,27 +2024,8 @@ Do not respond to this message.`;
|
|
|
2021
2024
|
tasks: params.tasks ?? null,
|
|
2022
2025
|
};
|
|
2023
2026
|
|
|
2024
|
-
//
|
|
2025
|
-
|
|
2026
|
-
comsSendCalledThisTurn.add(target.coms_session_id);
|
|
2027
|
-
|
|
2028
|
-
// Auto-create tasks on the target's task store (sender-side, instant)
|
|
2029
|
-
if (params.tasks && params.tasks.length > 0 && target.coms_session_id) {
|
|
2030
|
-
try {
|
|
2031
|
-
const count = createComsInboundTasks(
|
|
2032
|
-
params.tasks,
|
|
2033
|
-
{ sender_session: identity.coms_session_id, sender_name: identity.name, sender_endpoint: identity.endpoint },
|
|
2034
|
-
target.pi_session_id || target.coms_session_id,
|
|
2035
|
-
target.cwd,
|
|
2036
|
-
);
|
|
2037
|
-
if (count > 0) {
|
|
2038
|
-
log.info("tasks_auto_created", msg_id, "to", target.name, "count", count);
|
|
2039
|
-
}
|
|
2040
|
-
} catch { /* best-effort */ }
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
// Register a pending entry whose promise the receiver-side handleResponse
|
|
2044
|
-
// (or the timeout below) will settle.
|
|
2027
|
+
// Register before sending. A peer may acknowledge and respond before
|
|
2028
|
+
// task creation's dynamic import completes.
|
|
2045
2029
|
let resolveFn!: (v: { response?: any; error?: string | null }) => void;
|
|
2046
2030
|
let rejectFn!: (e: Error) => void;
|
|
2047
2031
|
const promise = new Promise<{ response?: any; error?: string | null }>((res, rej) => {
|
|
@@ -2069,13 +2053,37 @@ Do not respond to this message.`;
|
|
|
2069
2053
|
try { (entry.timer as any).unref?.(); } catch { /* ignore */ }
|
|
2070
2054
|
pendingReplies.set(msg_id, entry);
|
|
2071
2055
|
|
|
2072
|
-
log.debug("outbound_prompt", msg_id, "to", target.name, "hops", hops);
|
|
2073
|
-
|
|
2074
|
-
// Track pending outbound
|
|
2075
2056
|
const targetKey = target.name;
|
|
2076
2057
|
if (!pendingOutbound.has(targetKey)) pendingOutbound.set(targetKey, new Set());
|
|
2077
2058
|
pendingOutbound.get(targetKey)!.add(msg_id);
|
|
2078
2059
|
|
|
2060
|
+
try {
|
|
2061
|
+
await sendEnvelope(target.endpoint, env);
|
|
2062
|
+
} catch (error) {
|
|
2063
|
+
pendingReplies.delete(msg_id);
|
|
2064
|
+
pendingOutbound.get(targetKey)?.delete(msg_id);
|
|
2065
|
+
if (pendingOutbound.get(targetKey)?.size === 0) pendingOutbound.delete(targetKey);
|
|
2066
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
2067
|
+
throw error;
|
|
2068
|
+
}
|
|
2069
|
+
comsSendCalledThisTurn.add(target.coms_session_id);
|
|
2070
|
+
|
|
2071
|
+
if (params.tasks && params.tasks.length > 0 && target.coms_session_id) {
|
|
2072
|
+
try {
|
|
2073
|
+
const count = await createComsInboundTasks(
|
|
2074
|
+
params.tasks,
|
|
2075
|
+
{ sender_session: identity.coms_session_id, sender_name: identity.name, sender_endpoint: identity.endpoint },
|
|
2076
|
+
target.pi_session_id || target.coms_session_id,
|
|
2077
|
+
target.cwd,
|
|
2078
|
+
);
|
|
2079
|
+
if (count > 0) log.info("tasks_auto_created", msg_id, "to", target.name, "count", count);
|
|
2080
|
+
} catch (error) {
|
|
2081
|
+
log.warn("tasks_auto_create_failed", { msg_id, target: target.name, error: error instanceof Error ? error.message : String(error) });
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
log.debug("outbound_prompt", msg_id, "to", target.name, "hops", hops);
|
|
2086
|
+
|
|
2079
2087
|
// Peer status from peerCards
|
|
2080
2088
|
const peerCard = peerCards.get(target.coms_session_id);
|
|
2081
2089
|
const peer_queue_depth = peerCard?.queue_depth ?? 0;
|
|
@@ -2305,6 +2313,7 @@ Do not respond to this message.`;
|
|
|
2305
2313
|
}), { description: "Tasks to create on the peer" }),
|
|
2306
2314
|
}),
|
|
2307
2315
|
async execute(_callId, params) {
|
|
2316
|
+
log.debug("coms_tasks_create_start", { target: params.target, count: params.tasks?.length ?? 0 });
|
|
2308
2317
|
if (!identity) throw new Error("coms not initialised");
|
|
2309
2318
|
if (!params.tasks || params.tasks.length === 0) throw new Error("coms_tasks_create requires at least one task");
|
|
2310
2319
|
const target = await resolveTarget(params.target);
|
|
@@ -2320,7 +2329,7 @@ Do not respond to this message.`;
|
|
|
2320
2329
|
if (identity.endpoint) meta.sender_endpoint = identity.endpoint;
|
|
2321
2330
|
|
|
2322
2331
|
try {
|
|
2323
|
-
const { createTasksForSession, listTasksForSession, updateTaskForSession } =
|
|
2332
|
+
const { createTasksForSession, listTasksForSession, updateTaskForSession } = await import("../pitasks/index.js");
|
|
2324
2333
|
const targetPiSessionId = target.pi_session_id || target.coms_session_id;
|
|
2325
2334
|
|
|
2326
2335
|
// Create all tasks in one batch
|
|
@@ -2401,12 +2410,13 @@ Do not respond to this message.`;
|
|
|
2401
2410
|
task_id: Type.String({ description: "Task ID to delete." }),
|
|
2402
2411
|
}),
|
|
2403
2412
|
async execute(_callId, params) {
|
|
2413
|
+
log.debug("coms_task_delete_start", { target: params.target, taskId: params.task_id });
|
|
2404
2414
|
if (!identity) throw new Error("coms not initialised");
|
|
2405
2415
|
const target = await resolveTarget(params.target);
|
|
2406
2416
|
if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
|
|
2407
2417
|
|
|
2408
2418
|
try {
|
|
2409
|
-
const { getTaskForSession, deleteTaskForSession } =
|
|
2419
|
+
const { getTaskForSession, deleteTaskForSession } = await import("../pitasks/index.js");
|
|
2410
2420
|
const targetPiSessionId = target.pi_session_id || target.coms_session_id;
|
|
2411
2421
|
const task = getTaskForSession(targetPiSessionId, params.task_id, target.cwd);
|
|
2412
2422
|
if (!task) throw new Error(`Task #${params.task_id} not found`);
|
|
@@ -2440,12 +2450,13 @@ Do not respond to this message.`;
|
|
|
2440
2450
|
target: Type.String({ description: "Peer name or session_id." }),
|
|
2441
2451
|
}),
|
|
2442
2452
|
async execute(_callId, params) {
|
|
2453
|
+
log.debug("coms_task_list_start", { target: params.target });
|
|
2443
2454
|
if (!identity) throw new Error("coms not initialised");
|
|
2444
2455
|
const target = await resolveTarget(params.target);
|
|
2445
2456
|
if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
|
|
2446
2457
|
|
|
2447
2458
|
try {
|
|
2448
|
-
const { listTasksForSession } =
|
|
2459
|
+
const { listTasksForSession } = await import("../pitasks/index.js");
|
|
2449
2460
|
const targetPiSessionId = target.pi_session_id || target.coms_session_id;
|
|
2450
2461
|
const tasks = listTasksForSession(targetPiSessionId, target.cwd);
|
|
2451
2462
|
log.debug("coms_task_list", target.name, "tasks", tasks?.length ?? 0);
|
|
@@ -2494,12 +2505,13 @@ Do not respond to this message.`;
|
|
|
2494
2505
|
task_id: Type.String({ description: "Task ID to retrieve." }),
|
|
2495
2506
|
}),
|
|
2496
2507
|
async execute(_callId, params) {
|
|
2508
|
+
log.debug("coms_task_get_start", { target: params.target, taskId: params.task_id });
|
|
2497
2509
|
if (!identity) throw new Error("coms not initialised");
|
|
2498
2510
|
const target = await resolveTarget(params.target);
|
|
2499
2511
|
if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
|
|
2500
2512
|
|
|
2501
2513
|
try {
|
|
2502
|
-
const { getTaskForSession } =
|
|
2514
|
+
const { getTaskForSession, listTasksForSession } = await import("../pitasks/index.js");
|
|
2503
2515
|
const targetPiSessionId = target.pi_session_id || target.coms_session_id;
|
|
2504
2516
|
const task = getTaskForSession(targetPiSessionId, params.task_id, target.cwd);
|
|
2505
2517
|
if (!task) throw new Error(`Task #${params.task_id} not found on ${target.name}`);
|
|
@@ -2512,7 +2524,6 @@ Do not respond to this message.`;
|
|
|
2512
2524
|
if (task.owner) lines.push(`Owner: ${task.owner}`);
|
|
2513
2525
|
lines.push(`Description: ${desc}`);
|
|
2514
2526
|
if (task.blockedBy?.length > 0) {
|
|
2515
|
-
const { listTasksForSession } = require("../pitasks/index.js");
|
|
2516
2527
|
const allTasks = listTasksForSession(targetPiSessionId, target.cwd);
|
|
2517
2528
|
const openBlockers = task.blockedBy.filter((bid: string) => {
|
|
2518
2529
|
const blocker = allTasks.find((bt: any) => bt.id === bid);
|
|
@@ -2549,12 +2560,13 @@ Do not respond to this message.`;
|
|
|
2549
2560
|
addBlockedBy: Type.Optional(Type.Array(Type.String(), { description: "Task IDs that block this task" })),
|
|
2550
2561
|
}),
|
|
2551
2562
|
async execute(_callId, params) {
|
|
2563
|
+
log.debug("coms_task_update_start", { target: params.target, taskId: params.task_id });
|
|
2552
2564
|
if (!identity) throw new Error("coms not initialised");
|
|
2553
2565
|
const target = await resolveTarget(params.target);
|
|
2554
2566
|
if (!target) throw new Error(`coms: no live agent matching "${params.target}"`);
|
|
2555
2567
|
|
|
2556
2568
|
try {
|
|
2557
|
-
const { getTaskForSession, updateTaskForSession } =
|
|
2569
|
+
const { getTaskForSession, updateTaskForSession } = await import("../pitasks/index.js");
|
|
2558
2570
|
const targetPiSessionId = target.pi_session_id || target.coms_session_id;
|
|
2559
2571
|
const task = getTaskForSession(targetPiSessionId, params.task_id, target.cwd);
|
|
2560
2572
|
if (!task) throw new Error(`Task #${params.task_id} not found on ${target.name}`);
|
|
@@ -461,9 +461,10 @@ export function readFrontmatterFromArgv(argv: string[]): { name?: string; descri
|
|
|
461
461
|
}
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
-
export function readTaskSummary(_cwd: string, _sessionId?: string): { total: number; completed: number; in_progress: number } | null {
|
|
464
|
+
export async function readTaskSummary(_cwd: string, _sessionId?: string): Promise<{ total: number; completed: number; in_progress: number } | null> {
|
|
465
|
+
log.debug("read_task_summary", { hasSessionId: !!_sessionId });
|
|
465
466
|
try {
|
|
466
|
-
const { listTasks } =
|
|
467
|
+
const { listTasks } = await import("../pitasks/index.js");
|
|
467
468
|
const tasks = listTasks();
|
|
468
469
|
if (!tasks || tasks.length === 0) return null;
|
|
469
470
|
let total = 0, completed = 0, in_progress = 0;
|
|
@@ -650,17 +651,18 @@ const MAX_TASK_FIELD_LEN = 2000;
|
|
|
650
651
|
* Runs on the SENDER side — uses createTaskForSession with the target's session ID.
|
|
651
652
|
* TaskStore handles locking, file format, everything.
|
|
652
653
|
*/
|
|
653
|
-
export function createComsInboundTasks(
|
|
654
|
+
export async function createComsInboundTasks(
|
|
654
655
|
tasks: Array<{ subject: string; description: string }>,
|
|
655
656
|
origin: ComsOrigin,
|
|
656
657
|
targetSessionId: string,
|
|
657
658
|
targetCwd?: string,
|
|
658
|
-
): number {
|
|
659
|
+
): Promise<number> {
|
|
660
|
+
log.debug("create_coms_inbound_tasks", { targetSessionId, count: tasks?.length ?? 0 });
|
|
659
661
|
if (!tasks || tasks.length === 0 || !targetSessionId) return 0;
|
|
660
662
|
|
|
661
663
|
let createTaskForSession: any;
|
|
662
664
|
try {
|
|
663
|
-
createTaskForSession =
|
|
665
|
+
createTaskForSession = (await import("../pitasks/index.js")).createTaskForSession;
|
|
664
666
|
} catch { return 0; }
|
|
665
667
|
if (!createTaskForSession) return 0;
|
|
666
668
|
|
|
@@ -9,7 +9,13 @@
|
|
|
9
9
|
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import * as path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
12
13
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
import { createLogger } from "../shared/logger.js";
|
|
15
|
+
|
|
16
|
+
const log = createLogger("agents");
|
|
17
|
+
|
|
18
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
19
|
|
|
14
20
|
export type AgentScope = "user" | "project" | "both";
|
|
15
21
|
|
|
@@ -103,7 +109,9 @@ function findNearestProjectAgentsDir(cwd: string): string | null {
|
|
|
103
109
|
* and the agents are in ../../agents/ relative to this file.
|
|
104
110
|
*/
|
|
105
111
|
function getPackageAgentsDir(): string {
|
|
106
|
-
|
|
112
|
+
const resolvedPath = path.resolve(MODULE_DIR, "..", "..", "agents");
|
|
113
|
+
log.debug("resolved package agents directory", { path: resolvedPath });
|
|
114
|
+
return resolvedPath;
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
|
|
@@ -19,12 +19,17 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { existsSync, statSync, readFileSync } from "node:fs";
|
|
22
|
-
import {
|
|
23
|
-
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { join } from "node:path";
|
|
24
23
|
import { homedir } from "node:os";
|
|
25
24
|
import stripJsonComments from "strip-json-comments";
|
|
26
25
|
import { resolveRepoRoot } from "./utils.js";
|
|
27
26
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
27
|
+
import {
|
|
28
|
+
findSettingsFile,
|
|
29
|
+
SETTINGS_FILENAMES,
|
|
30
|
+
SETTINGS_KEYS,
|
|
31
|
+
type SettingsKeyDef,
|
|
32
|
+
} from "./settings-source.js";
|
|
28
33
|
|
|
29
34
|
interface ProjectSettings {
|
|
30
35
|
commit_trailer?: boolean | string;
|
|
@@ -97,33 +102,8 @@ interface ProjectSettings {
|
|
|
97
102
|
pidiff_daemon_startup_timeout_s?: number;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
/**
|
|
101
|
-
export
|
|
102
|
-
description: string;
|
|
103
|
-
type: string;
|
|
104
|
-
env?: string;
|
|
105
|
-
default: unknown;
|
|
106
|
-
min?: number;
|
|
107
|
-
max?: number;
|
|
108
|
-
strict_digits?: boolean;
|
|
109
|
-
per_key_resolution?: boolean;
|
|
110
|
-
enum?: string[];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const SETTINGS_FILENAMES = ["pi-config-settings.jsonc", "pi-config-settings.json"];
|
|
114
|
-
|
|
115
|
-
/** Find the first existing settings file in a directory (.jsonc preferred over .json). */
|
|
116
|
-
export function findSettingsFile(dir: string): string | null {
|
|
117
|
-
for (const name of SETTINGS_FILENAMES) {
|
|
118
|
-
const p = join(dir, name);
|
|
119
|
-
if (existsSync(p)) return p;
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export const SETTINGS_KEYS: Record<string, SettingsKeyDef> = JSON.parse(
|
|
125
|
-
readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "settings-keys.json"), "utf-8"),
|
|
126
|
-
);
|
|
105
|
+
/** Settings metadata is loaded from settings-keys.json by the cycle-free settings source. */
|
|
106
|
+
export { findSettingsFile, SETTINGS_KEYS, type SettingsKeyDef };
|
|
127
107
|
|
|
128
108
|
/** Dev-time check: every ProjectSettings field must have a JSON definition. */
|
|
129
109
|
const PROJECT_SETTINGS_KEYS: (keyof ProjectSettings)[] = [
|
|
@@ -12,6 +12,7 @@ const log = createLogger("rules");
|
|
|
12
12
|
import * as fs from "node:fs";
|
|
13
13
|
import * as os from "node:os";
|
|
14
14
|
import * as path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
15
16
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
16
17
|
import { formatDuration } from "./async-agents.js";
|
|
17
18
|
import { buildSituationReport, estimateMemoryBudget, rebuildAndOrganize } from "./situation-report.js";
|
|
@@ -22,6 +23,8 @@ import { isComsActive } from "../shared/coms-active.js";
|
|
|
22
23
|
import { JsonlAppendLog } from "./state-jsonl.js";
|
|
23
24
|
import { getProjectDataDir } from "./utils.js";
|
|
24
25
|
|
|
26
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
|
|
25
28
|
/** Per-cwd telemetry log cache — avoids re-creating on every call. */
|
|
26
29
|
const telemetryLogCache = new Map<string, JsonlAppendLog<Record<string, unknown>>>();
|
|
27
30
|
|
|
@@ -131,7 +134,7 @@ export function registerRules(
|
|
|
131
134
|
// Load from: package rules/ → user ~/.pi/agent/rules/ → project .pi/rules/
|
|
132
135
|
// Same-filename override: project > user > package
|
|
133
136
|
if (!isSubagent) {
|
|
134
|
-
const packageRulesDir = path.resolve(
|
|
137
|
+
const packageRulesDir = path.resolve(MODULE_DIR, "..", "..", "rules");
|
|
135
138
|
const userRulesDir = path.join(os.homedir(), ".pi", "agent", "rules");
|
|
136
139
|
const projectRulesDir = path.join(ctx.cwd, ".pi", "rules");
|
|
137
140
|
|
|
@@ -6,10 +6,16 @@ import { execSync, execFileSync } from "node:child_process";
|
|
|
6
6
|
import * as crypto from "node:crypto";
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
9
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
11
|
import { setUvAvailable, isUvAvailable } from "./enforcement-helpers.js";
|
|
11
12
|
import { registerMcpc } from "./mcpc.js";
|
|
12
13
|
import { checkMinPiVersion } from "./utils.js";
|
|
14
|
+
import { createLogger } from "../shared/logger.js";
|
|
15
|
+
|
|
16
|
+
const log = createLogger("session-validation");
|
|
17
|
+
|
|
18
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
19
|
|
|
14
20
|
/** Check whether a CLI command is available on PATH. */
|
|
15
21
|
function hasCmd(cmd: string): boolean {
|
|
@@ -240,7 +246,9 @@ async function checkSessionTools(ctx: any): Promise<void> {
|
|
|
240
246
|
"prek — pre-commit wrapper (.pre-commit-config.yaml detected). Install: https://github.com/j178/prek",
|
|
241
247
|
);
|
|
242
248
|
}
|
|
243
|
-
} catch (
|
|
249
|
+
} catch (error) {
|
|
250
|
+
log.warn("tool detection failed", { cwd: ctx.cwd, operation: "checkSessionTools", error: error instanceof Error ? error.message : String(error) });
|
|
251
|
+
}
|
|
244
252
|
|
|
245
253
|
if (missing.length > 0 || optional.length > 0) {
|
|
246
254
|
const parts: string[] = [];
|
|
@@ -259,26 +267,29 @@ async function checkSessionTools(ctx: any): Promise<void> {
|
|
|
259
267
|
}
|
|
260
268
|
}
|
|
261
269
|
|
|
270
|
+
export function findPackageVersion(startDir = MODULE_DIR): string | null {
|
|
271
|
+
let searchDir = startDir;
|
|
272
|
+
for (let i = 0; i < 5; i++) {
|
|
273
|
+
const candidate = path.join(searchDir, "package.json");
|
|
274
|
+
if (fs.existsSync(candidate)) {
|
|
275
|
+
try {
|
|
276
|
+
const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
277
|
+
if (pkg.name === "pi-orchestrator-config" && pkg.version) return pkg.version;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
log.warn("package manifest read or parse failed", { path: candidate, operation: "findPackageVersion", error: error instanceof Error ? error.message : String(error) });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
searchDir = path.dirname(searchDir);
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
262
287
|
/** Check for pi-config version upgrades and show changelog notification. */
|
|
263
288
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
264
289
|
async function checkUpgradeChangelog(ctx: any): Promise<void> {
|
|
265
|
-
|
|
290
|
+
log.debug("checking upgrade changelog", { operation: "checkUpgradeChangelog" });
|
|
266
291
|
try {
|
|
267
|
-
|
|
268
|
-
let currentVersion: string | null = null;
|
|
269
|
-
for (let i = 0; i < 5; i++) {
|
|
270
|
-
const candidate = path.join(searchDir, "package.json");
|
|
271
|
-
if (fs.existsSync(candidate)) {
|
|
272
|
-
try {
|
|
273
|
-
const pkg = JSON.parse(fs.readFileSync(candidate, "utf-8"));
|
|
274
|
-
if (pkg.name === "pi-orchestrator-config" && pkg.version) {
|
|
275
|
-
currentVersion = pkg.version;
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
} catch (e: any) { console.debug("[session-validation] package.json parse failed:", e?.message || e); }
|
|
279
|
-
}
|
|
280
|
-
searchDir = path.dirname(searchDir);
|
|
281
|
-
}
|
|
292
|
+
const currentVersion = findPackageVersion();
|
|
282
293
|
if (currentVersion) {
|
|
283
294
|
const versionFile = path.join(
|
|
284
295
|
process.env.HOME || "",
|
|
@@ -288,14 +299,18 @@ async function checkUpgradeChangelog(ctx: any): Promise<void> {
|
|
|
288
299
|
let lastVersion: string | null = null;
|
|
289
300
|
try {
|
|
290
301
|
lastVersion = fs.readFileSync(versionFile, "utf-8").trim();
|
|
291
|
-
} catch (
|
|
302
|
+
} catch (error) {
|
|
303
|
+
log.debug("last version read failed", { path: versionFile, operation: "read", error: error instanceof Error ? error.message : String(error) });
|
|
304
|
+
}
|
|
292
305
|
|
|
293
306
|
if (!lastVersion) {
|
|
294
307
|
// First run — just record the version, no notification
|
|
295
308
|
try {
|
|
296
309
|
fs.mkdirSync(path.dirname(versionFile), { recursive: true });
|
|
297
310
|
fs.writeFileSync(versionFile, currentVersion, "utf-8");
|
|
298
|
-
} catch (
|
|
311
|
+
} catch (error) {
|
|
312
|
+
log.warn("version file write failed", { path: versionFile, operation: "initial-write", error: error instanceof Error ? error.message : String(error) });
|
|
313
|
+
}
|
|
299
314
|
} else if (lastVersion !== currentVersion && hasCmd("gh")) {
|
|
300
315
|
// Version changed — fetch release notes (5s timeout, no shell)
|
|
301
316
|
const tag = `v${currentVersion}`;
|
|
@@ -318,18 +333,24 @@ async function checkUpgradeChangelog(ctx: any): Promise<void> {
|
|
|
318
333
|
);
|
|
319
334
|
notified = true;
|
|
320
335
|
}
|
|
321
|
-
} catch (
|
|
336
|
+
} catch (error) {
|
|
337
|
+
log.warn("release notes fetch failed", { tag, operation: "fetch", error: error instanceof Error ? error.message : String(error) });
|
|
338
|
+
}
|
|
322
339
|
// Only update version file after successful notification
|
|
323
340
|
// so failed attempts retry on next session
|
|
324
341
|
if (notified) {
|
|
325
342
|
try {
|
|
326
343
|
fs.mkdirSync(path.dirname(versionFile), { recursive: true });
|
|
327
344
|
fs.writeFileSync(versionFile, currentVersion, "utf-8");
|
|
328
|
-
} catch (
|
|
345
|
+
} catch (error) {
|
|
346
|
+
log.warn("version file write failed", { path: versionFile, operation: "post-notify-write", error: error instanceof Error ? error.message : String(error) });
|
|
347
|
+
}
|
|
329
348
|
}
|
|
330
349
|
}
|
|
331
350
|
}
|
|
332
|
-
} catch (
|
|
351
|
+
} catch (error) {
|
|
352
|
+
log.warn("upgrade changelog check failed", { operation: "checkUpgradeChangelog", error: error instanceof Error ? error.message : String(error) });
|
|
353
|
+
}
|
|
333
354
|
}
|
|
334
355
|
|
|
335
356
|
export function registerSessionValidation(pi: ExtensionAPI): void {
|
|
@@ -342,8 +363,8 @@ export function registerSessionValidation(pi: ExtensionAPI): void {
|
|
|
342
363
|
try {
|
|
343
364
|
const uvInstalled = hasCmd("uv");
|
|
344
365
|
setUvAvailable(uvInstalled);
|
|
345
|
-
} catch (
|
|
346
|
-
|
|
366
|
+
} catch (error) {
|
|
367
|
+
log.warn("uv availability check failed", { operation: "session_start", error: error instanceof Error ? error.message : String(error) });
|
|
347
368
|
}
|
|
348
369
|
|
|
349
370
|
if (!ctx.hasUI) return;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import stripJsonComments from "strip-json-comments";
|
|
7
|
+
|
|
8
|
+
let log: { debug(message: string, context?: object): void; warn(message: string, context?: object): void } = {
|
|
9
|
+
debug: () => {}, warn: () => {},
|
|
10
|
+
};
|
|
11
|
+
export function setSettingsSourceLogger(logger: typeof log): void {
|
|
12
|
+
log = logger;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SettingsKeyDef {
|
|
16
|
+
description: string;
|
|
17
|
+
type: string;
|
|
18
|
+
env?: string;
|
|
19
|
+
default: unknown;
|
|
20
|
+
min?: number;
|
|
21
|
+
max?: number;
|
|
22
|
+
strict_digits?: boolean;
|
|
23
|
+
per_key_resolution?: boolean;
|
|
24
|
+
enum?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const SETTINGS_FILENAMES = ["pi-config-settings.jsonc", "pi-config-settings.json"];
|
|
28
|
+
|
|
29
|
+
export const SETTINGS_KEYS: Record<string, SettingsKeyDef> = JSON.parse(
|
|
30
|
+
readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "settings-keys.json"), "utf8"),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export function findSettingsFile(dir: string): string | null {
|
|
34
|
+
for (const name of SETTINGS_FILENAMES) {
|
|
35
|
+
const file = join(dir, name);
|
|
36
|
+
if (existsSync(file)) {
|
|
37
|
+
log.debug("found settings file", { path: file });
|
|
38
|
+
return file;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
log.debug("settings file not found", { path: dir });
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function readSettingsObject(file: string | null, key?: string): Record<string, unknown> {
|
|
46
|
+
if (!file) {
|
|
47
|
+
log.debug("settings read skipped", { path: null, key });
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const value = JSON.parse(stripJsonComments(readFileSync(file, "utf8")));
|
|
52
|
+
log.debug("settings read", { path: file, key, validObject: Boolean(value && typeof value === "object" && !Array.isArray(value)) });
|
|
53
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
log.warn("settings read or parse failed", { path: file, key, error: error instanceof Error ? error.message : String(error) });
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const repoRootCache = new Map<string, string>();
|
|
61
|
+
let repoRootResolver = resolveRepoRootUncached;
|
|
62
|
+
|
|
63
|
+
export function setRepoRootResolverForTests(resolver: (cwd: string) => string): void {
|
|
64
|
+
repoRootResolver = resolver;
|
|
65
|
+
repoRootCache.clear();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveRepoRoot(cwd: string): string {
|
|
69
|
+
const resolvedCwd = resolve(cwd);
|
|
70
|
+
const cached = repoRootCache.get(resolvedCwd);
|
|
71
|
+
if (cached) return cached;
|
|
72
|
+
const root = repoRootResolver(resolvedCwd);
|
|
73
|
+
repoRootCache.set(resolvedCwd, root);
|
|
74
|
+
return root;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveRepoRootUncached(cwd: string): string {
|
|
78
|
+
try {
|
|
79
|
+
const common = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
80
|
+
cwd, encoding: "utf8", timeout: 3000, stdio: ["ignore", "pipe", "ignore"],
|
|
81
|
+
}).trim();
|
|
82
|
+
const root = dirname(resolve(cwd, common));
|
|
83
|
+
log.debug("resolved standalone settings repository root", { cwd, root });
|
|
84
|
+
return root;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
let current = resolve(cwd);
|
|
87
|
+
while (true) {
|
|
88
|
+
const dotGit = join(current, ".git");
|
|
89
|
+
if (existsSync(dotGit)) {
|
|
90
|
+
if (statSync(dotGit).isDirectory()) return current;
|
|
91
|
+
const match = /^gitdir:\s*(.+)$/m.exec(readFileSync(dotGit, "utf8"));
|
|
92
|
+
if (match) {
|
|
93
|
+
const gitDir = resolve(current, match[1].trim());
|
|
94
|
+
const commonFile = join(gitDir, "commondir");
|
|
95
|
+
if (existsSync(commonFile)) {
|
|
96
|
+
const root = dirname(resolve(gitDir, readFileSync(commonFile, "utf8").trim()));
|
|
97
|
+
log.debug("resolved standalone worktree repository root", { cwd, root });
|
|
98
|
+
return root;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return current;
|
|
102
|
+
}
|
|
103
|
+
const parent = dirname(current);
|
|
104
|
+
if (parent === current) break;
|
|
105
|
+
current = parent;
|
|
106
|
+
}
|
|
107
|
+
log.debug("standalone settings repository root fallback", { cwd, error: error instanceof Error ? error.message : String(error) });
|
|
108
|
+
return cwd;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isValidCandidate(value: unknown, definition: SettingsKeyDef | undefined): boolean {
|
|
113
|
+
if (!definition) return false;
|
|
114
|
+
const typeValid = definition.type === "string" ? typeof value === "string"
|
|
115
|
+
: definition.type === "bool" ? typeof value === "boolean"
|
|
116
|
+
: definition.type === "int" ? Number.isInteger(value)
|
|
117
|
+
: true;
|
|
118
|
+
const valid = typeValid && (!definition.enum || definition.enum.includes(value as string));
|
|
119
|
+
log.debug("validated standalone setting candidate", { type: definition.type, hasEnum: Boolean(definition.enum), valid });
|
|
120
|
+
return valid;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function getStandaloneSetting(cwd: string, key: string): unknown {
|
|
124
|
+
const definition = SETTINGS_KEYS[key];
|
|
125
|
+
const root = resolveRepoRoot(cwd);
|
|
126
|
+
for (const [source, file] of [
|
|
127
|
+
["project", findSettingsFile(join(root, ".pi"))],
|
|
128
|
+
["global", findSettingsFile(join(process.env.HOME || homedir(), ".pi"))],
|
|
129
|
+
] as const) {
|
|
130
|
+
const settings = readSettingsObject(file, key);
|
|
131
|
+
if (key in settings && isValidCandidate(settings[key], definition)) {
|
|
132
|
+
log.debug("resolved standalone setting", { key, source, path: file });
|
|
133
|
+
return settings[key];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const fromEnv = definition?.env && process.env[definition.env] !== undefined;
|
|
137
|
+
log.debug("resolved standalone setting fallback", { key, source: fromEnv ? "environment" : "default" });
|
|
138
|
+
return fromEnv ? process.env[definition!.env!] : definition?.default;
|
|
139
|
+
}
|
|
@@ -148,7 +148,7 @@ export function tryGetSystemPromptOptions(ctx: any): { contextFiles?: any[]; ski
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/** Minimum pi version required by this pi-config version. */
|
|
151
|
-
export const MIN_PI_VERSION = "0.
|
|
151
|
+
export const MIN_PI_VERSION = "0.87.0";
|
|
152
152
|
|
|
153
153
|
/** Get the installed pi version from its package.json. */
|
|
154
154
|
export function getPiVersion(): string | null {
|