pi-usereq 0.50.0 → 0.55.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/package.json +6 -5
- package/scripts/lib/sdk-smoke.ts +62 -4
- package/src/core/extension-status.ts +16 -5
- package/src/core/pi-notify.ts +1 -1
- package/src/core/prompt-command-runtime.ts +3 -3
- package/src/core/prompts.ts +17 -18
- package/src/core/settings-menu.ts +2 -2
- package/src/index.ts +165 -74
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-usereq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Ogekuri/PI-useReq.git"
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
35
|
+
"@earendil-works/pi-ai": "^0.80.4",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "^0.80.4",
|
|
37
|
+
"@earendil-works/pi-tui": "^0.80.4",
|
|
38
38
|
"@sinclair/typebox": "^0.34.49"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"protobufjs@7.5.4": true,
|
|
57
57
|
"esbuild": true,
|
|
58
58
|
"koffi": true,
|
|
59
|
-
"protobufjs": true
|
|
59
|
+
"protobufjs": true,
|
|
60
|
+
"@google/genai": true
|
|
60
61
|
}
|
|
61
62
|
}
|
package/scripts/lib/sdk-smoke.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file
|
|
3
3
|
* @brief Implements SDK-parity probing and comparison for the standalone debug harness.
|
|
4
|
-
* @details Dynamically loads the official pi SDK when available, inventories extension-owned commands and tools from the runtime surface, normalizes provenance metadata, and compares the result against the offline recorder snapshot. Runtime is O(c + t) in command and
|
|
4
|
+
* @details Dynamically loads the official pi SDK when available, inventories extension-owned commands and tools from the runtime surface, passes the 0.80.4+ `authPath` and `modelsPath` `createAgentSession` options, probes support for the new 0.80.4+ event surface, normalizes provenance metadata, and compares the result against the offline recorder snapshot. Runtime is O(c + t + e) in command, tool, and probed-event counts plus the cost of SDK session creation. Side effects are limited to dynamic module loading, optional SDK-managed filesystem reads, and any extension-owned startup behavior triggered by the official runtime.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import path from "node:path";
|
|
@@ -53,6 +53,7 @@ export interface SdkContractSnapshot {
|
|
|
53
53
|
tools: NormalizedToolRecord[];
|
|
54
54
|
activeTools: string[];
|
|
55
55
|
runtimeShape: string;
|
|
56
|
+
supportedEvents: string[];
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/**
|
|
@@ -96,6 +97,60 @@ interface SdkApiLike {
|
|
|
96
97
|
getActiveTools?: () => string[];
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @brief Lists the 0.80.4+ pi event names probed for host support by the SDK parity probe.
|
|
102
|
+
* @details The probe registers a no-op handler for each new-event name on the extension runtime and treats a returned unsubscribe function as proof that the host emits the event, mirroring the capability contract used by prompt-end finalization. Lookup complexity is O(1).
|
|
103
|
+
* @satisfies REQ-356
|
|
104
|
+
*/
|
|
105
|
+
const PI_EVENT_SURFACE_PROBE_NAMES = [
|
|
106
|
+
"agent_settled",
|
|
107
|
+
"project_trust",
|
|
108
|
+
"session_info_changed",
|
|
109
|
+
"session_compact_failed",
|
|
110
|
+
"before_provider_headers",
|
|
111
|
+
"after_provider_response",
|
|
112
|
+
"ui_prompt_start",
|
|
113
|
+
"ui_prompt_end",
|
|
114
|
+
"thinking_level_select",
|
|
115
|
+
] as const;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @brief Probes the official SDK runtime for support of the 0.80.4+ pi event surface.
|
|
119
|
+
* @details Locates an object exposing an `on(...)` registration method on the `extensionsResult.runtime` surface, registers a no-op handler for each new-event name, and records those whose registration returns an unsubscribe function (the 0.80.4+ contract). Handlers are unsubscribed immediately after the probe and unsupported or unavailable surfaces yield an empty list. Runtime is O(e) in probed event count. No external state is mutated.
|
|
120
|
+
* @param[in] createAgentSessionResult {unknown} Raw `createAgentSession(...)` result.
|
|
121
|
+
* @return {string[]} Names of supported new pi events, possibly empty.
|
|
122
|
+
* @satisfies REQ-356
|
|
123
|
+
*/
|
|
124
|
+
function probePiEventSurface(createAgentSessionResult: unknown): string[] {
|
|
125
|
+
if (!createAgentSessionResult || typeof createAgentSessionResult !== "object") {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const candidateRoot = createAgentSessionResult as Record<string, unknown>;
|
|
129
|
+
const runtime = (candidateRoot.extensionsResult as Record<string, unknown> | undefined)?.runtime;
|
|
130
|
+
const host = runtime && typeof runtime === "object" ? runtime as Record<string, unknown> : undefined;
|
|
131
|
+
const onMethod = host?.on;
|
|
132
|
+
if (typeof onMethod !== "function") {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
const probeHost = host as unknown as {
|
|
136
|
+
on(event: string, handler: (event: unknown, ctx: unknown) => unknown): unknown;
|
|
137
|
+
};
|
|
138
|
+
const supported: string[] = [];
|
|
139
|
+
for (const eventName of PI_EVENT_SURFACE_PROBE_NAMES) {
|
|
140
|
+
let unsubscribe: unknown;
|
|
141
|
+
try {
|
|
142
|
+
unsubscribe = probeHost.on(eventName, () => undefined);
|
|
143
|
+
} catch {
|
|
144
|
+
unsubscribe = undefined;
|
|
145
|
+
}
|
|
146
|
+
if (typeof unsubscribe === "function") {
|
|
147
|
+
supported.push(eventName);
|
|
148
|
+
(unsubscribe as () => void)();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return supported;
|
|
152
|
+
}
|
|
153
|
+
|
|
99
154
|
/**
|
|
100
155
|
* @brief Normalizes one path relative to the requested project root.
|
|
101
156
|
* @details Converts absolute paths under the project root to slash-normalized relative paths and leaves non-project or pseudo-path values unchanged. Runtime is O(p) in path length. No external state is mutated.
|
|
@@ -414,18 +469,18 @@ export function buildParityReport(offline: OfflineContractSnapshot, sdk: SdkCont
|
|
|
414
469
|
|
|
415
470
|
/**
|
|
416
471
|
* @brief Loads the official pi SDK runtime and extracts the extension-owned command and tool inventories.
|
|
417
|
-
* @details Dynamically imports `@
|
|
472
|
+
* @details Dynamically imports `@earendil-works/pi-coding-agent`, creates a `DefaultResourceLoader` with the requested extension path, creates an SDK session with the 0.80.4+ `authPath` and `modelsPath` options, extracts inventory methods from the returned runtime surface, probes support for the new 0.80.4+ event surface, and filters to extension-owned commands and tools only. Runtime is dominated by SDK startup. Side effects include SDK-managed resource loading and extension startup behavior.
|
|
418
473
|
* @param[in] cwd {string | undefined} Requested working directory.
|
|
419
474
|
* @param[in] extensionPath {string | undefined} Requested extension entry path.
|
|
420
475
|
* @return {Promise<SdkContractSnapshot>} Normalized SDK inventory snapshot.
|
|
421
476
|
* @throws {ReqError} Throws when the SDK package is unavailable, runtime extraction fails, or session creation fails.
|
|
422
|
-
* @satisfies REQ-050, REQ-056, REQ-058
|
|
477
|
+
* @satisfies REQ-050, REQ-056, REQ-058, REQ-356
|
|
423
478
|
*/
|
|
424
479
|
export async function probeSdkRuntime(cwd?: string, extensionPath?: string): Promise<SdkContractSnapshot> {
|
|
425
480
|
const paths = resolveHarnessPaths(cwd, extensionPath);
|
|
426
481
|
let sdkModule: Record<string, unknown>;
|
|
427
482
|
try {
|
|
428
|
-
sdkModule = await import("@
|
|
483
|
+
sdkModule = await import("@earendil-works/pi-coding-agent") as Record<string, unknown>;
|
|
429
484
|
} catch (error) {
|
|
430
485
|
throw new ReqError(`Error: SDK parity loading failed: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
431
486
|
}
|
|
@@ -454,6 +509,8 @@ export async function probeSdkRuntime(cwd?: string, extensionPath?: string): Pro
|
|
|
454
509
|
resourceLoader,
|
|
455
510
|
sessionManager: SessionManager.inMemory(),
|
|
456
511
|
settingsManager: typeof SettingsManager?.inMemory === "function" ? SettingsManager.inMemory({}) : undefined,
|
|
512
|
+
authPath: path.join(paths.cwd, ".pi-usereq-agent-auth.json"),
|
|
513
|
+
modelsPath: path.join(paths.cwd, ".pi-usereq-agent-models.json"),
|
|
457
514
|
});
|
|
458
515
|
} catch (error) {
|
|
459
516
|
throw new ReqError(`Error: SDK parity loading failed: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
@@ -485,6 +542,7 @@ export async function probeSdkRuntime(cwd?: string, extensionPath?: string): Pro
|
|
|
485
542
|
tools,
|
|
486
543
|
activeTools,
|
|
487
544
|
runtimeShape: extracted.runtimeShape,
|
|
545
|
+
supportedEvents: probePiEventSurface(createAgentSessionResult),
|
|
488
546
|
};
|
|
489
547
|
}
|
|
490
548
|
|
|
@@ -12,9 +12,9 @@ import type {
|
|
|
12
12
|
ContextUsage,
|
|
13
13
|
ExtensionContext,
|
|
14
14
|
ThemeColor,
|
|
15
|
-
} from "@
|
|
15
|
+
} from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import type { UseReqConfig } from "./config.js";
|
|
17
|
-
import type { PiNotifySoundLevel } from "./pi-notify.js";
|
|
17
|
+
import type { PiNotifyOutcome, PiNotifySoundLevel } from "./pi-notify.js";
|
|
18
18
|
import type { PromptCommandExecutionPlan } from "./prompt-command-runtime.js";
|
|
19
19
|
import {
|
|
20
20
|
restorePersistedPromptCommandRuntimeStateForSession,
|
|
@@ -77,6 +77,7 @@ export const PI_USEREQ_STATUS_HOOK_NAMES = [
|
|
|
77
77
|
"before_agent_start",
|
|
78
78
|
"agent_start",
|
|
79
79
|
"agent_end",
|
|
80
|
+
"agent_settled",
|
|
80
81
|
"turn_start",
|
|
81
82
|
"turn_end",
|
|
82
83
|
"message_start",
|
|
@@ -124,6 +125,8 @@ export interface PiUsereqStatusState {
|
|
|
124
125
|
runtimeSoundLevel: PiNotifySoundLevel | undefined;
|
|
125
126
|
pendingPromptRequest: PiUsereqPromptRequest | undefined;
|
|
126
127
|
activePromptRequest: PiUsereqPromptRequest | undefined;
|
|
128
|
+
pendingFinalizationOutcome: PiNotifyOutcome | undefined;
|
|
129
|
+
agentSettledEventSupported: boolean | undefined;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
/**
|
|
@@ -680,6 +683,8 @@ export function createPiUsereqStatusController(): PiUsereqStatusController {
|
|
|
680
683
|
runtimeSoundLevel: undefined,
|
|
681
684
|
pendingPromptRequest: undefined,
|
|
682
685
|
activePromptRequest: undefined,
|
|
686
|
+
pendingFinalizationOutcome: undefined,
|
|
687
|
+
agentSettledEventSupported: undefined,
|
|
683
688
|
},
|
|
684
689
|
tickHandle: undefined,
|
|
685
690
|
};
|
|
@@ -802,7 +807,7 @@ export function setPiUsereqWorkflowState(
|
|
|
802
807
|
|
|
803
808
|
/**
|
|
804
809
|
* @brief Updates mutable status state for one intercepted lifecycle hook.
|
|
805
|
-
* @details Refreshes stored context usage on every hook, resets or restores persisted elapsed counters during `session_start`, loads the active runtime sound level from persisted config during `session_start`, restores persisted prompt-command metadata when the active session matches a forked execution session, resynchronizes that metadata on later lifecycle hooks so post-switch workflow transitions performed by the initiating command handler become visible to the replacement-session runtime, resets workflow state to `idle` for documented session-start reasons, starts run timing on `agent_start`, promotes pending prompt-request metadata into the active run, captures non-aborted run duration on `agent_end`, accumulates successful runtime into `Σ`, preserves in-memory prompt-command state plus process-scoped persistence across switch-triggered `session_shutdown`, tolerates stale post-replacement render contexts, synchronizes the live ticker, and re-renders the status bar with the runtime extension identity prefix when configuration is available. Runtime is O(n) in `agent_end` message count and otherwise O(1). Side effects include in-memory state mutation, interval scheduling, process-scoped persistence mutation, and footer-status updates.
|
|
810
|
+
* @details Refreshes stored context usage on every hook, resets or restores persisted elapsed counters during `session_start`, loads the active runtime sound level from persisted config during `session_start`, restores persisted prompt-command metadata when the active session matches a forked execution session, resynchronizes that metadata on later lifecycle hooks so post-switch workflow transitions performed by the initiating command handler become visible to the replacement-session runtime, resets workflow state to `idle` for documented session-start reasons, starts run timing on `agent_start`, promotes pending prompt-request metadata into the active run, captures non-aborted run duration on `agent_end` or `agent_settled`, accumulates successful runtime into `Σ`, preserves in-memory prompt-command state plus process-scoped persistence across switch-triggered `session_shutdown`, tolerates stale post-replacement render contexts, synchronizes the live ticker, and re-renders the status bar with the runtime extension identity prefix when configuration is available. Runtime is O(n) in `agent_end` message count and otherwise O(1). Side effects include in-memory state mutation, interval scheduling, process-scoped persistence mutation, and footer-status updates.
|
|
806
811
|
* @param[in,out] controller {PiUsereqStatusController} Mutable status controller.
|
|
807
812
|
* @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
|
|
808
813
|
* @param[in] event {unknown} Hook payload forwarded from the wrapper.
|
|
@@ -851,9 +856,15 @@ export function updateExtensionStatus(
|
|
|
851
856
|
controller.state.pendingPromptRequest = undefined;
|
|
852
857
|
}
|
|
853
858
|
|
|
854
|
-
if (
|
|
859
|
+
if (
|
|
860
|
+
(hookName === "agent_end" || hookName === "agent_settled")
|
|
861
|
+
&& controller.state.runStartTimeMs !== undefined
|
|
862
|
+
) {
|
|
855
863
|
const durationMs = nowMs - controller.state.runStartTimeMs;
|
|
856
|
-
|
|
864
|
+
const agentEndMessages = hookName === "agent_end"
|
|
865
|
+
? (event as AgentEndEvent).messages ?? []
|
|
866
|
+
: [];
|
|
867
|
+
if (!didAgentEndAbort(agentEndMessages)) {
|
|
857
868
|
controller.state.lastRunDurationMs = durationMs;
|
|
858
869
|
controller.state.totalRunDurationMs = controller.state.totalRunDurationMs === undefined
|
|
859
870
|
? durationMs
|
package/src/core/pi-notify.ts
CHANGED
|
@@ -8,7 +8,7 @@ import os from "node:os";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import * as https from "node:https";
|
|
11
|
-
import type { AgentEndEvent } from "@
|
|
11
|
+
import type { AgentEndEvent } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { getInstallationPath, normalizePathSlashes } from "./path-context.js";
|
|
13
13
|
import type { UseReqConfig } from "./config.js";
|
|
14
14
|
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
setRuntimeGitPath,
|
|
29
29
|
setRuntimeWorktreePathState,
|
|
30
30
|
} from "./path-context.js";
|
|
31
|
-
import { SessionManager } from "@
|
|
31
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
32
32
|
import { resolveRuntimeGitPath } from "./runtime-project-paths.js";
|
|
33
33
|
import {
|
|
34
34
|
clearPersistedPromptCommandSessionContext,
|
|
@@ -1945,11 +1945,11 @@ export async function finalizePromptCommandExecution(
|
|
|
1945
1945
|
/**
|
|
1946
1946
|
* @brief Maps one `agent_end` payload into the canonical prompt-worktree finalization outcome.
|
|
1947
1947
|
* @details Delegates to the shared notification outcome classifier so worktree merge and fork-session retention decisions stay aligned with prompt-end notification routing. Runtime is O(m) in assistant message count. No external state is mutated.
|
|
1948
|
-
* @param[in] event {Pick<import("@
|
|
1948
|
+
* @param[in] event {Pick<import("@earendil-works/pi-coding-agent").AgentEndEvent, "messages">} Agent-end payload subset.
|
|
1949
1949
|
* @return {PiNotifyOutcome} Canonical prompt-end outcome.
|
|
1950
1950
|
*/
|
|
1951
1951
|
export function classifyPromptCommandOutcome(
|
|
1952
|
-
event: Pick<import("@
|
|
1952
|
+
event: Pick<import("@earendil-works/pi-coding-agent").AgentEndEvent, "messages">,
|
|
1953
1953
|
): PiNotifyOutcome {
|
|
1954
1954
|
return classifyPiNotifyOutcome(event);
|
|
1955
1955
|
}
|
package/src/core/prompts.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file
|
|
3
3
|
* @brief Renders bundled pi-usereq prompts for the current project context.
|
|
4
|
-
* @details Applies placeholder substitution, legacy tool-name rewrites, and conditional pi.dev governance guidance before prompt text is sent to the agent. Runtime is linear in prompt size plus replacement count. Side effects are limited to filesystem reads used for
|
|
4
|
+
* @details Applies placeholder substitution, legacy tool-name rewrites, and conditional pi.dev governance guidance before prompt text is sent to the agent. Runtime is linear in prompt size plus replacement count. Side effects are limited to filesystem reads used for the coding-agent-docs directory check and bundled prompt loading.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from "node:fs";
|
|
@@ -61,8 +61,8 @@ const PI_DEV_AWARE_PROMPT_NAMES = new Set<string>([
|
|
|
61
61
|
"refactor",
|
|
62
62
|
]);
|
|
63
63
|
/**
|
|
64
|
-
* @brief Stores the repository-relative pi.dev manifest path used in prompt guidance.
|
|
65
|
-
* @details The constant lets rendered prompts cite the authoritative documentation manifest with a deterministic path. Lookup complexity is O(1).
|
|
64
|
+
* @brief Stores the repository-relative optional pi.dev manifest path used in prompt guidance.
|
|
65
|
+
* @details The constant lets rendered prompts cite the authoritative documentation manifest when present with a deterministic path. The governance block is not gated on this file; it is an optional contract source. Lookup complexity is O(1).
|
|
66
66
|
*/
|
|
67
67
|
const PI_DEV_MANIFEST_PROMPT_PATH = "docs/pi.dev/agent-document-manifest.json";
|
|
68
68
|
/**
|
|
@@ -82,37 +82,36 @@ const PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH = `${PI_DEV_DOCS_PROMPT_PATH}/coding-
|
|
|
82
82
|
const PI_DEV_SOURCE_PROMPT_PATH = "pi.dev-src/pi-mono";
|
|
83
83
|
/**
|
|
84
84
|
* @brief Defines the injected pi.dev governance guidance block.
|
|
85
|
-
* @details The block requires read-only handling for documentation and pi client sources,
|
|
85
|
+
* @details The block requires read-only handling for documentation and pi client sources, coding-agent-document review, coding-agent-document compliance, optional manifest-referenced document handling, and pi client source validation for ambiguous or bug-fix interface work. Construction happens once at module load. Access complexity is O(1).
|
|
86
86
|
* @satisfies REQ-033, REQ-034, REQ-108, REQ-273, REQ-274, REQ-275
|
|
87
87
|
*/
|
|
88
88
|
const PI_DEV_CONFORMANCE_BLOCK = [
|
|
89
89
|
"- Treat every path under `docs/` as read-only; do NOT modify "
|
|
90
|
-
+
|
|
90
|
+
+ "any documentation file, including those under `docs/pi.dev/`.",
|
|
91
91
|
"- Treat every path under `pi.dev-src/` as read-only; do NOT modify "
|
|
92
92
|
+ `\`${PI_DEV_SOURCE_PROMPT_PATH}\` or any other pi client source.`,
|
|
93
93
|
"- If the task creates or modifies software that interfaces with the "
|
|
94
|
-
+ `pi.dev CLI,
|
|
95
|
-
+ "
|
|
96
|
-
|
|
97
|
-
`- Treat \`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` and documents `
|
|
98
|
-
+ `referenced by \`${PI_DEV_MANIFEST_PROMPT_PATH}\` as the `
|
|
94
|
+
+ `pi.dev CLI, review \`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` `
|
|
95
|
+
+ "before analysis, implementation, verification, or bug fixing.",
|
|
96
|
+
`- Treat \`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` as the `
|
|
99
97
|
+ "authoritative read-only interface contract; new or modified "
|
|
100
98
|
+ "pi.dev CLI integrations MUST comply with the APIs they describe.",
|
|
101
|
-
`-
|
|
102
|
-
"- If manifest or "
|
|
103
|
-
+ `\`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` guidance is `
|
|
99
|
+
`- If \`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` guidance is `
|
|
104
100
|
+ "ambiguous for extension-to-pi-client interface behavior, validate "
|
|
105
101
|
+ `the produced source code by analyzing \`${PI_DEV_SOURCE_PROMPT_PATH}\`.`,
|
|
106
102
|
"- For bug fixes or problem resolution influenced by extension-to-pi-client "
|
|
107
103
|
+ "interface implementations, validate the produced source code by "
|
|
108
104
|
+ `analyzing \`${PI_DEV_SOURCE_PROMPT_PATH}\`.`,
|
|
105
|
+
`- If \`${PI_DEV_MANIFEST_PROMPT_PATH}\` exists under \`${PI_DEV_DOCS_PROMPT_PATH}/\`, `
|
|
106
|
+
+ "treat every document path it references as part of the read-only "
|
|
107
|
+
+ "interface contract.",
|
|
109
108
|
].join("\n");
|
|
110
109
|
|
|
111
110
|
/**
|
|
112
111
|
* @brief Builds the conditional pi.dev governance block for one rendered prompt.
|
|
113
|
-
* @details Emits the
|
|
112
|
+
* @details Emits the coding-agent-document-driven governance rules only when the selected bundled prompt can analyze or mutate source code and the project root contains the `docs/pi.dev/coding-agent-docs/` directory; the manifest file is optional and its absence does not suppress the block. Time complexity O(1). No filesystem writes.
|
|
114
113
|
* @param[in] promptName {string} Bundled prompt identifier.
|
|
115
|
-
* @param[in] projectBase {string} Absolute project root used for
|
|
114
|
+
* @param[in] projectBase {string} Absolute project root used for coding-agent-docs directory existence checks.
|
|
116
115
|
* @return {string} Markdown bullet block or the empty string when injection is not applicable.
|
|
117
116
|
* @satisfies REQ-032, REQ-033, REQ-034, REQ-108, REQ-273, REQ-274, REQ-275
|
|
118
117
|
*/
|
|
@@ -120,8 +119,8 @@ function buildPiDevConformanceBlock(promptName: string, projectBase: string): st
|
|
|
120
119
|
if (!PI_DEV_AWARE_PROMPT_NAMES.has(promptName)) {
|
|
121
120
|
return "";
|
|
122
121
|
}
|
|
123
|
-
const
|
|
124
|
-
if (!fs.existsSync(
|
|
122
|
+
const codingAgentDocsPath = path.join(projectBase, PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH);
|
|
123
|
+
if (!fs.existsSync(codingAgentDocsPath) || !fs.statSync(codingAgentDocsPath).isDirectory()) {
|
|
125
124
|
return "";
|
|
126
125
|
}
|
|
127
126
|
return PI_DEV_CONFORMANCE_BLOCK;
|
|
@@ -132,7 +131,7 @@ function buildPiDevConformanceBlock(promptName: string, projectBase: string): st
|
|
|
132
131
|
* @details Inserts the block immediately after the `## Behavior` heading so downstream agents evaluate the rule before workflow steps. Leaves prompts unchanged when no behavior section exists or the block is already present. Time complexity O(n).
|
|
133
132
|
* @param[in] text {string} Prompt markdown after placeholder replacement.
|
|
134
133
|
* @param[in] promptName {string} Bundled prompt identifier.
|
|
135
|
-
* @param[in] projectBase {string} Absolute project root used for
|
|
134
|
+
* @param[in] projectBase {string} Absolute project root used for coding-agent-docs directory existence checks.
|
|
136
135
|
* @return {string} Prompt markdown with zero or one injected conformance block.
|
|
137
136
|
* @satisfies REQ-032, REQ-033, REQ-034, REQ-108, REQ-273, REQ-274, REQ-275
|
|
138
137
|
*/
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* @details Wraps `SettingsList` in one extension-command helper that exposes right-aligned current values, built-in circular scrolling, bottom-line descriptions, and a deterministic bridge for offline test harnesses. Runtime is O(n) in visible choice count plus user interaction cost. Side effects are limited to transient custom-UI rendering.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { getSettingsListTheme, type ThemeColor, type ExtensionCommandContext } from "@
|
|
8
|
-
import { Container, SettingsList, Text, type Component, type SettingItem, type SettingsListTheme } from "@
|
|
7
|
+
import { getSettingsListTheme, type ThemeColor, type ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { Container, SettingsList, Text, type Component, type SettingItem, type SettingsListTheme } from "@earendil-works/pi-tui";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @brief Describes one selectable pi-usereq settings-menu choice.
|
package/src/index.ts
CHANGED
|
@@ -18,8 +18,8 @@ import type {
|
|
|
18
18
|
ExtensionCommandContext,
|
|
19
19
|
ExtensionContext,
|
|
20
20
|
ToolInfo,
|
|
21
|
-
} from "@
|
|
22
|
-
import { Text } from "@
|
|
21
|
+
} from "@earendil-works/pi-coding-agent";
|
|
22
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
23
23
|
import { Type } from "@sinclair/typebox";
|
|
24
24
|
import {
|
|
25
25
|
buildMonolithicToolExecuteResult,
|
|
@@ -153,6 +153,7 @@ import {
|
|
|
153
153
|
setPiUsereqWorkflowState,
|
|
154
154
|
shouldPreservePromptCommandStateOnShutdown,
|
|
155
155
|
updateExtensionStatus,
|
|
156
|
+
type PiUsereqPromptRequest,
|
|
156
157
|
type PiUsereqStatusController,
|
|
157
158
|
type PiUsereqStatusHookName,
|
|
158
159
|
} from "./core/extension-status.js";
|
|
@@ -1247,9 +1248,138 @@ function applyConfiguredPiUsereqTools(pi: ExtensionAPI, config: UseReqConfig): v
|
|
|
1247
1248
|
pi.setActiveTools(allTools.map((tool) => tool.name).filter((toolName) => nextActive.has(toolName)));
|
|
1248
1249
|
}
|
|
1249
1250
|
|
|
1251
|
+
/**
|
|
1252
|
+
* @brief Probes whether the running pi host emits the `agent_settled` event and caches the result on the status controller state.
|
|
1253
|
+
* @details The 0.80.4+ `ExtensionAPI.on(...)` contract returns an unsubscribe function, and `agent_settled` is introduced in that same release; therefore a registration that returns a callable proves the host supports the event, while a `void` registration means a legacy host that never emits it. The probe registers a no-op handler once per status controller, unsubscribes it when the new contract returns a function, and caches the boolean so all later lookups are O(1). No external state is retained beyond the controller-scoped cached boolean.
|
|
1254
|
+
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller that caches the probed capability.
|
|
1255
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance used to probe event registration.
|
|
1256
|
+
* @return {boolean} `true` when the host emits `agent_settled`, `false` on legacy hosts.
|
|
1257
|
+
* @satisfies REQ-354, REQ-355
|
|
1258
|
+
*/
|
|
1259
|
+
function isPiAgentSettledEventSupported(
|
|
1260
|
+
statusController: PiUsereqStatusController,
|
|
1261
|
+
pi: ExtensionAPI,
|
|
1262
|
+
): boolean {
|
|
1263
|
+
if (statusController.state.agentSettledEventSupported !== undefined) {
|
|
1264
|
+
return statusController.state.agentSettledEventSupported;
|
|
1265
|
+
}
|
|
1266
|
+
const probeHost = pi as ExtensionAPI & {
|
|
1267
|
+
on(event: string, handler: (event: unknown, ctx: unknown) => unknown): unknown;
|
|
1268
|
+
};
|
|
1269
|
+
const registration = probeHost.on("agent_settled", () => undefined);
|
|
1270
|
+
const supported = typeof registration === "function";
|
|
1271
|
+
if (supported) {
|
|
1272
|
+
(registration as () => void)();
|
|
1273
|
+
}
|
|
1274
|
+
statusController.state.agentSettledEventSupported = supported;
|
|
1275
|
+
return supported;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* @brief Finalizes a matched successful worktree-backed prompt at the current lifecycle point.
|
|
1280
|
+
* @details Executes the deferred stash-assisted merge, transcript preservation, base-path restore, and worktree plus branch deletion through `finalizePromptCommandExecution(...)`, surfaces `error` or warning-only notifications, clears the pending finalization outcome plus prompt state, and transitions workflow state through `merging` to `idle`. Reused by the `agent_settled` handler on 0.80.4+ hosts and directly by the `agent_end` fallback on legacy hosts that never emit `agent_settled`. Runtime is dominated by git finalization. Side effects include branch merges, worktree deletion, notifications, and workflow-state transitions.
|
|
1281
|
+
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller whose pending and active prompt state is cleared.
|
|
1282
|
+
* @param[in] promptRequest {PiUsereqPromptRequest} Matched successful worktree-backed prompt execution plan.
|
|
1283
|
+
* @param[in] ctx {ExtensionContext} Active extension context used for finalization and notifications.
|
|
1284
|
+
* @return {Promise<void>} Promise resolved when finalization and state transitions complete.
|
|
1285
|
+
* @satisfies REQ-208, REQ-228, REQ-229, REQ-230, REQ-282, REQ-291, REQ-292, REQ-354, REQ-355
|
|
1286
|
+
*/
|
|
1287
|
+
async function finalizeMatchedPromptSuccess(
|
|
1288
|
+
statusController: PiUsereqStatusController,
|
|
1289
|
+
promptRequest: PiUsereqPromptRequest,
|
|
1290
|
+
ctx: ExtensionContext,
|
|
1291
|
+
): Promise<void> {
|
|
1292
|
+
const debugConfig = statusController.config;
|
|
1293
|
+
let promptContext = ctx;
|
|
1294
|
+
let finalization:
|
|
1295
|
+
| {
|
|
1296
|
+
mergeAttempted: boolean;
|
|
1297
|
+
mergeSucceeded: boolean;
|
|
1298
|
+
cleanupSucceeded: boolean;
|
|
1299
|
+
errorMessage?: string;
|
|
1300
|
+
warningMessage?: string;
|
|
1301
|
+
activeContext?: unknown;
|
|
1302
|
+
}
|
|
1303
|
+
| undefined;
|
|
1304
|
+
try {
|
|
1305
|
+
finalization = await finalizePromptCommandExecution(
|
|
1306
|
+
promptRequest,
|
|
1307
|
+
promptContext,
|
|
1308
|
+
debugConfig
|
|
1309
|
+
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1310
|
+
: undefined,
|
|
1311
|
+
);
|
|
1312
|
+
promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
1315
|
+
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
1316
|
+
let cleanupSucceeded = false;
|
|
1317
|
+
try {
|
|
1318
|
+
promptContext = (await restorePromptCommandExecution(
|
|
1319
|
+
promptRequest,
|
|
1320
|
+
promptContext,
|
|
1321
|
+
debugConfig
|
|
1322
|
+
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1323
|
+
: undefined,
|
|
1324
|
+
) ?? promptContext) as typeof ctx;
|
|
1325
|
+
cleanupSucceeded = true;
|
|
1326
|
+
} catch (restoreError) {
|
|
1327
|
+
promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
|
|
1328
|
+
errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
1329
|
+
}
|
|
1330
|
+
finalization = {
|
|
1331
|
+
mergeAttempted: false,
|
|
1332
|
+
mergeSucceeded: false,
|
|
1333
|
+
cleanupSucceeded,
|
|
1334
|
+
errorMessage,
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
if (
|
|
1338
|
+
finalization.errorMessage
|
|
1339
|
+
&& (!finalization.cleanupSucceeded || !finalization.mergeSucceeded)
|
|
1340
|
+
) {
|
|
1341
|
+
if (debugConfig) {
|
|
1342
|
+
transitionPromptWorkflowState(
|
|
1343
|
+
statusController,
|
|
1344
|
+
promptContext,
|
|
1345
|
+
promptRequest.basePath,
|
|
1346
|
+
debugConfig,
|
|
1347
|
+
promptRequest.promptName,
|
|
1348
|
+
"error",
|
|
1349
|
+
);
|
|
1350
|
+
} else {
|
|
1351
|
+
setPiUsereqWorkflowState(statusController, "error", promptContext);
|
|
1352
|
+
}
|
|
1353
|
+
notifyContextSafely(promptContext, finalization.errorMessage, "error");
|
|
1354
|
+
}
|
|
1355
|
+
if (
|
|
1356
|
+
finalization.warningMessage
|
|
1357
|
+
&& finalization.cleanupSucceeded
|
|
1358
|
+
&& finalization.mergeSucceeded
|
|
1359
|
+
&& !finalization.errorMessage
|
|
1360
|
+
) {
|
|
1361
|
+
notifyContextSafely(promptContext, finalization.warningMessage, "info");
|
|
1362
|
+
}
|
|
1363
|
+
statusController.state.pendingFinalizationOutcome = undefined;
|
|
1364
|
+
statusController.state.pendingPromptRequest = undefined;
|
|
1365
|
+
statusController.state.activePromptRequest = undefined;
|
|
1366
|
+
if (debugConfig) {
|
|
1367
|
+
transitionPromptWorkflowState(
|
|
1368
|
+
statusController,
|
|
1369
|
+
promptContext,
|
|
1370
|
+
promptRequest.basePath,
|
|
1371
|
+
debugConfig,
|
|
1372
|
+
promptRequest.promptName,
|
|
1373
|
+
"idle",
|
|
1374
|
+
);
|
|
1375
|
+
} else {
|
|
1376
|
+
setPiUsereqWorkflowState(statusController, "idle", promptContext);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1250
1380
|
/**
|
|
1251
1381
|
* @brief Handles one intercepted pi lifecycle hook for pi-usereq status updates.
|
|
1252
|
-
* @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics,
|
|
1382
|
+
* @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics, classifies the prompt outcome, and for every matched successful worktree-backed completion defers the restore switch, stash-assisted merge, and worktree deletion to `agent_settled` when the running pi host supports that 0.80.4+ event (because its `switchSession` awaits the active agent run to become idle and would deadlock inside `agent_end`), or executes the finalization directly at `agent_end` when the host does not emit `agent_settled`. On `agent_settled`, reuses persisted replacement-session command contexts when event contexts omit `switchSession()`, executes the deferred stash-assisted merge-and-delete finalization path, emits a warning-only notification when restored `base-path` changes are reapplied after merge, tolerates stale replacement-session notification contexts after session replacement, retains the worktree plus notifies closure failure for interrupted or failed outcomes, logs selected prompt workflow transitions, and transitions workflow state through `merging`, `error`, and `idle` as required. On `session_shutdown`, captures pre-update prompt snapshots so workflow-shutdown diagnostics and same-runtime command continuation preserve the active prompt workflow state across switch-triggered rebinding, then disposes the shared controller. Runtime is dominated by configuration loading during `session_start` and git finalization during matched successful closure handling; all other hooks are O(1). Side effects include resource checks, active-tool mutation, active-session replacement, status updates, live-ticker disposal on shutdown, optional child-process spawning, outbound HTTPS requests, branch merges, worktree deletion, and optional debug-log writes.
|
|
1253
1383
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
1254
1384
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
1255
1385
|
* @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
|
|
@@ -1373,65 +1503,14 @@ async function handleExtensionStatusEvent(
|
|
|
1373
1503
|
);
|
|
1374
1504
|
}
|
|
1375
1505
|
if (shouldFinalizeMatchedSuccess) {
|
|
1376
|
-
if (
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
);
|
|
1385
|
-
} else {
|
|
1386
|
-
setPiUsereqWorkflowState(statusController, "merging", promptContext);
|
|
1387
|
-
}
|
|
1388
|
-
let finalization:
|
|
1389
|
-
| {
|
|
1390
|
-
mergeAttempted: boolean;
|
|
1391
|
-
mergeSucceeded: boolean;
|
|
1392
|
-
cleanupSucceeded: boolean;
|
|
1393
|
-
errorMessage?: string;
|
|
1394
|
-
warningMessage?: string;
|
|
1395
|
-
activeContext?: unknown;
|
|
1396
|
-
}
|
|
1397
|
-
| undefined;
|
|
1398
|
-
try {
|
|
1399
|
-
finalization = await finalizePromptCommandExecution(
|
|
1400
|
-
activePromptRequest,
|
|
1401
|
-
promptContext,
|
|
1402
|
-
debugConfig
|
|
1403
|
-
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1404
|
-
: undefined,
|
|
1405
|
-
);
|
|
1406
|
-
promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
|
|
1407
|
-
} catch (error) {
|
|
1408
|
-
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
1409
|
-
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
1410
|
-
let cleanupSucceeded = false;
|
|
1411
|
-
try {
|
|
1412
|
-
promptContext = (await restorePromptCommandExecution(
|
|
1413
|
-
activePromptRequest,
|
|
1414
|
-
promptContext,
|
|
1415
|
-
debugConfig
|
|
1416
|
-
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1417
|
-
: undefined,
|
|
1418
|
-
) ?? promptContext) as typeof ctx;
|
|
1419
|
-
cleanupSucceeded = true;
|
|
1420
|
-
} catch (restoreError) {
|
|
1421
|
-
promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
|
|
1422
|
-
errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
1423
|
-
}
|
|
1424
|
-
finalization = {
|
|
1425
|
-
mergeAttempted: false,
|
|
1426
|
-
mergeSucceeded: false,
|
|
1427
|
-
cleanupSucceeded,
|
|
1428
|
-
errorMessage,
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
if (
|
|
1432
|
-
finalization.errorMessage
|
|
1433
|
-
&& (!finalization.cleanupSucceeded || !finalization.mergeSucceeded)
|
|
1434
|
-
) {
|
|
1506
|
+
if (isPiAgentSettledEventSupported(statusController, pi)) {
|
|
1507
|
+
// Defer the restore switch, merge, and worktree deletion to
|
|
1508
|
+
// `agent_settled`. The pi 0.80.4+ `switchSession` implementation
|
|
1509
|
+
// awaits the active agent run to become idle before replacing the
|
|
1510
|
+
// session, and that idle transition only happens at `agent_settled`.
|
|
1511
|
+
// Calling `switchSession` here would deadlock the `agent_end` handler
|
|
1512
|
+
// and leave the workflow parked in `merging` forever.
|
|
1513
|
+
statusController.state.pendingFinalizationOutcome = outcome;
|
|
1435
1514
|
if (debugConfig) {
|
|
1436
1515
|
transitionPromptWorkflowState(
|
|
1437
1516
|
statusController,
|
|
@@ -1439,20 +1518,21 @@ async function handleExtensionStatusEvent(
|
|
|
1439
1518
|
activePromptRequest.basePath,
|
|
1440
1519
|
debugConfig,
|
|
1441
1520
|
activePromptRequest.promptName,
|
|
1442
|
-
"
|
|
1521
|
+
"merging",
|
|
1443
1522
|
);
|
|
1444
1523
|
} else {
|
|
1445
|
-
setPiUsereqWorkflowState(statusController, "
|
|
1524
|
+
setPiUsereqWorkflowState(statusController, "merging", promptContext);
|
|
1446
1525
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1526
|
+
} else {
|
|
1527
|
+
// Legacy hosts never emit `agent_settled`, so no idle-waiting
|
|
1528
|
+
// `switchSession` deadlock exists; finalize the matched success
|
|
1529
|
+
// directly at `agent_end` to avoid parking in `merging` forever.
|
|
1530
|
+
statusController.state.pendingFinalizationOutcome = outcome;
|
|
1531
|
+
await finalizeMatchedPromptSuccess(
|
|
1532
|
+
statusController,
|
|
1533
|
+
activePromptRequest,
|
|
1534
|
+
promptContext,
|
|
1535
|
+
);
|
|
1456
1536
|
}
|
|
1457
1537
|
} else if (closureFailureMessage !== undefined) {
|
|
1458
1538
|
// Worktree-backed run that ended interrupted, failed, aborted, or
|
|
@@ -1500,7 +1580,7 @@ async function handleExtensionStatusEvent(
|
|
|
1500
1580
|
notifyContextSafely(promptContext, error instanceof Error ? error.message : String(error), "error");
|
|
1501
1581
|
}
|
|
1502
1582
|
}
|
|
1503
|
-
if (closureFailureMessage === undefined) {
|
|
1583
|
+
if (closureFailureMessage === undefined && !shouldFinalizeMatchedSuccess) {
|
|
1504
1584
|
statusController.state.pendingPromptRequest = undefined;
|
|
1505
1585
|
statusController.state.activePromptRequest = undefined;
|
|
1506
1586
|
if (debugConfig) {
|
|
@@ -1518,6 +1598,17 @@ async function handleExtensionStatusEvent(
|
|
|
1518
1598
|
}
|
|
1519
1599
|
}
|
|
1520
1600
|
}
|
|
1601
|
+
if (hookName === "agent_settled") {
|
|
1602
|
+
const settledPromptRequest = statusController.state.activePromptRequest;
|
|
1603
|
+
const pendingOutcome = statusController.state.pendingFinalizationOutcome;
|
|
1604
|
+
if (
|
|
1605
|
+
settledPromptRequest !== undefined
|
|
1606
|
+
&& settledPromptRequest.worktreeDir !== undefined
|
|
1607
|
+
&& pendingOutcome === "completed"
|
|
1608
|
+
) {
|
|
1609
|
+
await finalizeMatchedPromptSuccess(statusController, settledPromptRequest, ctx);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1521
1612
|
if (hookName === "session_shutdown") {
|
|
1522
1613
|
if (shutdownPromptRequest !== undefined && statusController.config) {
|
|
1523
1614
|
logPromptWorkflowEvent(
|