@xzzpig/pi-openspec-x 0.2.0 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - Support Pi 1.2+ custom-message wakeups in child report handlers. A wakeup
6
+ retains the current user-dispatch attempt without allowing an unverified
7
+ message to authorize a report; a new user dispatch creates a fresh attempt.
8
+ - Include the extension's generated public type declaration in the package.
9
+
3
10
  ## 0.2.0
4
11
 
5
12
  Child report tools are now scoped to the subagents that own them, and the
@@ -0,0 +1,48 @@
1
+ import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
+
3
+ /**
4
+ * pi-openspec-x extension entry point.
5
+ *
6
+ * pi-sandbox is probed lazily through a dynamic import (dependencies.ts): on
7
+ * success the three opsx sandbox profiles (D4: opsx-planner / opsx-agent /
8
+ * opsx-reviewer) are registered into pi-sandbox's runtime registry — in-memory
9
+ * for the session lifetime, never written to a user's sandbox.json, a
10
+ * user-defined same-name profile always keeping priority. On failure the
11
+ * reason is recorded as dependency state and the extension continues: the
12
+ * official-track skills and every other capability keep working, and a later
13
+ * request for a restricted mode is refused with a typed
14
+ * OpsxDependencyMissingError (fail-closed) instead of the extension (or the
15
+ * official track) dying at load time.
16
+ *
17
+ * Registers the resources_discover handler, which:
18
+ * - generates (and caches, keyed by CLI version) the official-track
19
+ * openspec-* skills under `<agentDir>/cache/pi-openspec-x/skills/` and
20
+ * exposes the directory to pi via `skillPaths`;
21
+ * - warns once per project when static `<cwd>/.pi/skills/openspec-*`
22
+ * directories collide with the generated skills.
23
+ *
24
+ * Registers the session_start handler that clears a stale `active_agent`
25
+ * identity left in the session file by an earlier session (pi-agent-role's
26
+ * semantics; see mode.ts).
27
+ *
28
+ * Registers the three child reporting tools (report_gap_analysis,
29
+ * report_plan_review, report_work) through the role-specific child-only
30
+ * extensions declared in agents.ts (design D1): each tool is registered into
31
+ * its own subagent's process and never into the main session, so the parent's
32
+ * tool declaration and prompt rules stay free of them.
33
+ *
34
+ * pi-subagents is probed the same lazy way: on success the four opsx
35
+ * subagents (opsx-gap-analysis / opsx-plan-review / opsx-worker / opsx-reviewer) are
36
+ * registered into pi-subagents' runtime registry via the owner's event bus
37
+ * (D3). A missing pi-subagents costs only the enhanced flow's subagents —
38
+ * the official track and direct main-session implementation are unaffected.
39
+ * Because the owner listener only exists once pi-subagents has loaded (load
40
+ * order between extensions is not guaranteed), a failed init-time attempt is
41
+ * retried on session_start.
42
+ *
43
+ * Missing openspec CLI: no skills are registered and no error escapes — a
44
+ * one-time notice is sent instead.
45
+ */
46
+ declare function piOpenspecX(pi: ExtensionAPI): Promise<void>;
47
+
48
+ export { piOpenspecX as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xzzpig/pi-openspec-x",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "OpenSpec workflow for the pi coding agent: spec-driven planning and implementation via dedicated subagents, sandboxed /opsx:plan and /opsx:implement commands.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -25,6 +25,7 @@
25
25
  "files": [
26
26
  "index.ts",
27
27
  "src",
28
+ "dist",
28
29
  "README.md",
29
30
  "CHANGELOG.md",
30
31
  "LICENSE",
@@ -43,13 +44,16 @@
43
44
  "@xzzpig/pi-subagents": "*"
44
45
  },
45
46
  "devDependencies": {
46
- "@earendil-works/pi-coding-agent": "1.0.0",
47
+ "@earendil-works/pi-coding-agent": "1.1.0",
47
48
  "@types/node": "24.12.4",
49
+ "rollup": "^4.62.2",
50
+ "rollup-plugin-dts": "^6.4.1",
48
51
  "typescript": "5.9.3",
49
52
  "vitest": "4.1.8"
50
53
  },
51
54
  "scripts": {
52
55
  "test": "vitest run",
53
- "typecheck": "tsc --noEmit"
56
+ "typecheck": "tsc --noEmit",
57
+ "build:types": "rollup -c rollup.dts.config.mjs"
54
58
  }
55
59
  }
@@ -12,12 +12,12 @@
12
12
  * ingestion; it never adds a model-fillable parameter, and the parent
13
13
  * re-validates `report` against the original tool schema.
14
14
  *
15
- * The attempt is the anti-forgery anchor: `before_agent_start` records a fresh
16
- * random `attemptId` plus the SHA-256 of the actual prompt in a
17
- * `pi-openspec-x/child-report-attempt:v1` session entry. A report is only
18
- * attributable to the turn whose attempt it names, so a resumed child that
19
- * reuses the same session file cannot smuggle an earlier run's report into the
20
- * current dispatch.
15
+ * The attempt is the anti-forgery anchor: for each user dispatch,
16
+ * `before_agent_start` records a fresh random `attemptId` plus the SHA-256 of
17
+ * the actual prompt in a `pi-openspec-x/child-report-attempt:v1` session entry.
18
+ * A report is attributable only to the dispatch whose attempt it names, so a
19
+ * resumed child that reuses the same session file cannot smuggle an earlier
20
+ * run's report into the current dispatch.
21
21
  *
22
22
  * Deliberately NOT here: any reading of pi-subagents' private `AsyncStatus`
23
23
  * or lifecycle constants (design D3). This module declares the minimal v3
@@ -164,9 +164,9 @@ export function beginChildReportAttempt(
164
164
  /** Human-readable refusal text for a report that cannot be attributed. */
165
165
  export function attemptUnavailableMessage(agent: string): string {
166
166
  return (
167
- `Report rejected: no child report attempt is recorded for ${agent} in this turn. ` +
167
+ `Report rejected: no child report attempt is recorded for ${agent} in this dispatch. ` +
168
168
  `The parent cannot attribute this report to a verified run, so it would be ignored; ` +
169
- `the attempt entry is written by this extension at the start of every turn.`
169
+ `the attempt entry is written by this extension at the start of each user dispatch.`
170
170
  );
171
171
  }
172
172
 
@@ -175,7 +175,7 @@ export function attemptUnavailableMessage(agent: string): string {
175
175
  *
176
176
  * The wrapped tool keeps the original name, schema, confirmation text and
177
177
  * `sequential` execution mode; only `details` changes into the envelope. When
178
- * the current turn has no persisted attempt, the call fails explicitly instead
178
+ * the current dispatch has no persisted attempt, the call fails explicitly instead
179
179
  * of returning a report the parent would have to discard.
180
180
  */
181
181
  export function registerChildReportTool(
@@ -185,6 +185,13 @@ export function registerChildReportTool(
185
185
  let current: ChildReportAttemptOutcome | undefined;
186
186
 
187
187
  pi.on("before_agent_start", (event: BeforeAgentStartEvent, ctx) => {
188
+ // Pi 1.2+ also fires this hook when a custom message wakes the same child.
189
+ // It is not a new user dispatch: keep its existing attempt, or continue to
190
+ // reject reports if no dispatch has been recorded. Pi 1.1 never fired this
191
+ // hook for such a wake and has no messageType field.
192
+ if ("messageType" in event && event.messageType === "custom") return;
193
+ current = undefined;
194
+ if (!("prompt" in event) || typeof event.prompt !== "string") return;
188
195
  current = beginChildReportAttempt(pi, ctx, {
189
196
  agent: options.agent,
190
197
  prompt: event.prompt,