@signalridge/pi-workflows 1.11.0 → 1.11.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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.11.1
4
+ ### Patch Changes
5
+
6
+ - da9ed0f: - Reject rename hardlink aliases and retargeted queue identities before writes; support all LSP newline styles and identify possibly partially modified targets on write failure.
7
+ - Fence pending physical Plan model mutations at cancellable navigation/replacement gates, reconcile handoffs by native selection chronology, and scope thinking-only restoration to its originating model.
8
+ - Compose Goal guidance through an owned structured prompt section. Resolve overflow usage against physical response models and invalidate stale size evidence after persisted context edits in each independent continuation helper.
9
+ - Persist queued Rules reminders/offers only at authenticated native admission, match file URL paths, and provide usable rule-file references when read_rule is unavailable.
10
+ - Inherit nested workers' thinking from their immediate spawning session while preserving explicit policy precedence.
11
+ - Refresh skill discovery for custom Analytics runs without consuming pending user attribution, and finalize successful Codex diagnostics only after UI cleanup and the final owned result fence.
12
+ - da9ed0f: Restore workflow keyword and standing `/effort high|ultra` authorization on Pi 1.2 and later. Those hosts report the session as busy while its own run starts, so the hidden guidance that authorizes the `workflow` tool was silently dropped for every idle prompt. The idle check now applies only on hosts that do not identify the starting run, and runs started by a custom message never consume an armed prompt.
13
+ - da9ed0f: Stop settled workflow results from waking a session whose context overflowed the model window. While the latest response on the branch is a context overflow with no compaction after it, results are still delivered and acknowledged as context but no wake is sent, and one warning per overflow episode asks to run `/compact` (or `/codex-compact` when installed) or start a new session. A compaction or a later successful response restores normal wakes. Previously each wake resent the same over-window context and failed again. The package now declares `@earendil-works/pi-ai` as a host peer dependency.
14
+ - Updated dependencies [da9ed0f]
15
+ - @signalridge/pi-ui@1.4.1
16
+
3
17
  ## 1.11.0
4
18
  ### Minor Changes
5
19
 
package/README.md CHANGED
@@ -306,6 +306,14 @@ awaits while navigation or shutdown retires the owner, the obsolete
306
306
  extension-sourced wake is handled without starting a turn. Interactive/RPC
307
307
  input and other extensions' messages are not consumed by this guard.
308
308
 
309
+ No wake is sent while the session's latest meaningful response overflowed the model's
310
+ context window and no compaction followed it: that turn would resend the same
311
+ over-window context and fail again. Results are still delivered and acknowledged
312
+ as context, and one warning per overflow episode asks to run `/compact` (or
313
+ `/codex-compact` when installed) or start a new session. A compaction or a later
314
+ successful response restores normal wakes. Aborted responses and transient errors
315
+ (including HTTP 503 and rate limits) do not prove context recovery.
316
+
309
317
  This is a handoff check, not a navigation lock: public hooks cannot fence an
310
318
  arbitrary later async input or preparation handler that navigates after our
311
319
  input hook has accepted the wake. Earlier transforms must preserve the private
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalridge/pi-workflows",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "description": "Deterministic JavaScript orchestration workflows backed by pi-subagents managed spawning.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,6 +15,7 @@
15
15
  "lifecycle": "stable"
16
16
  },
17
17
  "peerDependencies": {
18
+ "@earendil-works/pi-ai": "*",
18
19
  "@earendil-works/pi-coding-agent": "*",
19
20
  "@earendil-works/pi-tui": "*",
20
21
  "@signalridge/pi-subagents": ">=1.9.0",
@@ -22,7 +23,7 @@
22
23
  },
23
24
  "dependencies": {
24
25
  "@signalridge/pi-subagents-protocol": "^1.6.0",
25
- "@signalridge/pi-ui": "^1.4.0",
26
+ "@signalridge/pi-ui": "^1.4.1",
26
27
  "acorn": "^8.16.0"
27
28
  },
28
29
  "pi": {
@@ -2,7 +2,7 @@
2
2
  name: workflow-authoring
3
3
  description: Design, write, review, and debug Pi JavaScript workflows that coordinate multiple subagents. Use when a request needs named dependencies, bounded fan-out, per-item pipelines, quality gates, saved or nested workflows, pause/resume, or workflow progress; do not use it merely to run an existing named workflow.
4
4
  metadata:
5
- version: "1.11.0"
5
+ version: "1.11.1"
6
6
  ---
7
7
 
8
8
  # Workflow authoring
@@ -2,7 +2,7 @@
2
2
  # Workflow capability index
3
3
 
4
4
  Contract format: `2.0.0`<br>
5
- Contract content / skill / extension: `1.11.0`
5
+ Contract content / skill / extension: `1.11.1`
6
6
 
7
7
  This compact generated index covers supported runtime globals and workflow-tool inputs. For constraints, compatibility behavior, internal boundaries, and dynamic-reference ownership, follow the [exhaustive generated facts](capability-details.md).
8
8
 
@@ -2,7 +2,7 @@
2
2
  # Exhaustive workflow capability facts
3
3
 
4
4
  Contract format: `2.0.0`<br>
5
- Contract content / skill / extension: `1.11.0`
5
+ Contract content / skill / extension: `1.11.1`
6
6
 
7
7
  Every exact fact below is projected from the installed extension's capability contract. Explanatory judgment belongs in the hand-written references next to this file.
8
8
 
@@ -2,7 +2,7 @@
2
2
  name: workflow-patterns
3
3
  description: Choose and run one of Pi's built-in multi-agent workflows for research, adversarial review, code review, multi-perspective analysis, or codebase auditing. Use when the request matches a named pattern; use workflow-authoring instead for a new script or custom topology.
4
4
  metadata:
5
- version: "1.11.0"
5
+ version: "1.11.1"
6
6
  ---
7
7
 
8
8
  # Built-in workflow patterns
@@ -2,7 +2,7 @@
2
2
  name: workflow-review
3
3
  description: Audit an existing Pi JavaScript workflow for topology, prompt/data flow, limits, failure handling, cancellation, resume safety, and publication compatibility. Use when reviewing or debugging a workflow script or saved run; do not use to run a standard named workflow.
4
4
  metadata:
5
- version: "1.11.0"
5
+ version: "1.11.1"
6
6
  ---
7
7
 
8
8
  # Workflow review
package/src/arming.ts CHANGED
@@ -235,15 +235,21 @@ export class WorkflowTurnArming {
235
235
  }
236
236
 
237
237
  consume(event: BeforeAgentStartEvent, ctx: ExtensionContext): void {
238
+ // Pi 1.2+ also announces idle custom-message runs; only a user prompt can
239
+ // carry the armed input, and earlier hosts never reached here for them.
240
+ if (event.messageType === "custom") return;
238
241
  const pending = this.pending;
239
242
  const ambiguous = this.ambiguous;
240
243
  this.pending = undefined; // one-use, also for mismatched/consumed prompts
241
244
  this.current = undefined;
242
245
  try {
246
+ // Pi 1.2+ (which reports messageType) counts the session busy while its
247
+ // own run prepares and queues concurrent sends into it. Earlier hosts were
248
+ // still idle here, so a busy session meant another run had started.
243
249
  if (
244
250
  ambiguous ||
245
251
  !pending?.content ||
246
- !ctx.isIdle() ||
252
+ (event.messageType === undefined && !ctx.isIdle()) ||
247
253
  event.prompt !== pending.text ||
248
254
  inputKey("", event.images) !== pending.images ||
249
255
  ctx.sessionManager.getSessionId() !== pending.sessionId ||
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Overflow-aware waking. After a response overflowed the model's context window and no
3
+ * compaction followed, a turn this extension starts resends the same over-window context and
4
+ * fails again, and each failure is still a billed request. Wakes are held until a compaction
5
+ * or a later successful response ends the episode.
6
+ */
7
+ import { isContextOverflow } from "@earendil-works/pi-ai";
8
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
9
+
10
+ type EpisodeContext = Pick<ExtensionContext, "sessionManager"> & {
11
+ modelRegistry?: { find(provider: string, model: string): { contextWindow?: number } | undefined };
12
+ };
13
+ export type OverflowContext = EpisodeContext & Pick<ExtensionContext, "hasUI" | "ui">;
14
+
15
+ /**
16
+ * The first overflowed response of the current episode: the latest meaningful response on the
17
+ * branch overflowed and neither compaction nor a successful response followed. Aborted and
18
+ * transient error responses say nothing about the context size and are skipped.
19
+ */
20
+ export function overflowEpisode(ctx: EpisodeContext): string | undefined {
21
+ try {
22
+ const branch = ctx.sessionManager.getBranch();
23
+ const projection = ctx.sessionManager.buildSessionProjection?.();
24
+ const projected = projection
25
+ ? new Set(
26
+ projection.entries
27
+ .filter((entry) => entry.messages.some((message) => message.role === "assistant"))
28
+ .map((entry) => entry.sourceEntry.id),
29
+ )
30
+ : undefined;
31
+ let contextEdited = false;
32
+ let episode: string | undefined;
33
+ for (let index = branch.length - 1; index >= 0; index--) {
34
+ const entry = branch[index];
35
+ if (entry.type === "compaction") break;
36
+ if (entry.type === "context_edit") {
37
+ contextEdited = true;
38
+ continue;
39
+ }
40
+ if (entry.type !== "message" || entry.message.role !== "assistant" || entry.message.stopReason === "aborted")
41
+ continue;
42
+ const explicit = entry.message.stopReason === "error" && isContextOverflow(entry.message);
43
+ if (entry.message.stopReason === "error" && !explicit) continue;
44
+ // Pi can omit a failed recovery attempt before a cancelled compaction.
45
+ // That alone does not repair its oversized input; invalidate usage only.
46
+ if (!explicit && projected && !projected.has(entry.id)) break;
47
+ const contextWindow = ctx.modelRegistry?.find(entry.message.provider, entry.message.model)?.contextWindow;
48
+ if (!explicit && (contextEdited || !isContextOverflow(entry.message, contextWindow))) {
49
+ if (entry.message.stopReason === "error") continue;
50
+ break;
51
+ }
52
+ episode = entry.id;
53
+ }
54
+ return episode;
55
+ } catch {
56
+ // A replaced session's context is stale; never hold a wake on a guess.
57
+ return undefined;
58
+ }
59
+ }
60
+
61
+ export function overflowNotice(held: string, codexCompact: boolean): string {
62
+ return (
63
+ `Context exceeds the model's window; ${held} without starting a turn. ` +
64
+ `Run ${codexCompact ? "/compact or /codex-compact" : "/compact"}, or start a new session.`
65
+ );
66
+ }
67
+
68
+ /** One guard per extension instance, so the user sees one notice per overflow episode. */
69
+ export class OverflowWakeGuard {
70
+ private noticed: string | undefined;
71
+ constructor(
72
+ private readonly pi: Pick<ExtensionAPI, "getCommands">,
73
+ private readonly held: string,
74
+ ) {}
75
+
76
+ /** True while a turn must not start; the first hold of an episode tells the user why. */
77
+ holds(ctx: OverflowContext | undefined): boolean {
78
+ const episode = ctx ? overflowEpisode(ctx) : undefined;
79
+ if (!ctx || episode === undefined) return false;
80
+ if (episode !== this.noticed) {
81
+ this.noticed = episode;
82
+ try {
83
+ if (ctx.hasUI) ctx.ui.notify(overflowNotice(this.held, this.hasCommand("codex-compact")), "warning");
84
+ } catch {
85
+ // A disposed UI must not block the delivery that follows.
86
+ }
87
+ }
88
+ return true;
89
+ }
90
+
91
+ private hasCommand(name: string): boolean {
92
+ try {
93
+ return this.pi.getCommands().some((command) => command.name === name);
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import { Type } from "@sinclair/typebox";
8
8
  import { DEFAULT_TRIGGER_WORD, hasTriggerWord, WORKFLOW_ARMING_MESSAGE_TYPE, WorkflowTurnArming } from "./arming.js";
9
9
  import { BUILTIN_WORKFLOWS, validateBuiltinArgs } from "./builtins.js";
10
10
  import { type CommandResult, resolveCodeReviewScope } from "./code-review-scope.js";
11
+ import { OverflowWakeGuard } from "./context-overflow.js";
11
12
  import { type ScriptStartOptions, type ScriptStartResult, WorkflowEngine, WorkflowWaitAbortedError } from "./engine.js";
12
13
  import { JOURNAL_ENTRY_TYPE, type SessionEntryLike } from "./journal.js";
13
14
  import {
@@ -361,6 +362,7 @@ export default function piWorkflows(pi: ExtensionAPI): void {
361
362
  let keywordTriggerEnabled = workflowSettings.keywordTriggerEnabled;
362
363
  /** Public sends are void: acceptance is checked against the owning branch. */
363
364
  let deliveryContext: ExtensionContext | undefined;
365
+ const overflowWakes = new OverflowWakeGuard(pi, "workflow results are added to the conversation");
364
366
  let deliveryTimer: ReturnType<typeof setTimeout> | undefined;
365
367
  let inputPreparing = false;
366
368
  const turnArming = new WorkflowTurnArming();
@@ -1808,7 +1810,8 @@ export default function piWorkflows(pi: ExtensionAPI): void {
1808
1810
  try {
1809
1811
  if (!isCurrent()) return;
1810
1812
  // Never use a custom-message triggerTurn: that skips input preparation
1811
- // and before_agent_start. The void API is NOT an acceptance promise.
1813
+ // (and, before Pi 1.2, before_agent_start). The void API is NOT an
1814
+ // acceptance promise.
1812
1815
  const missing = batch.filter(([runId]) => !persisted(runId));
1813
1816
  if (missing.length > 0)
1814
1817
  pi.sendMessage(
@@ -1842,7 +1845,9 @@ export default function piWorkflows(pi: ExtensionAPI): void {
1842
1845
  }
1843
1846
  // An earlier input hook can await while the same manager navigates. Keep
1844
1847
  // exact owner evidence until our input hook consumes this private wake.
1845
- if (isCurrent() && engine) {
1848
+ // An overflowed parent would resend the same over-window context: the
1849
+ // acknowledged results above wait for the next run instead.
1850
+ if (isCurrent() && engine && !overflowWakes.holds(ctx)) {
1846
1851
  const wake = `${DELIVERY_WAKE_PREFIX}${randomUUID()}] ${DELIVERY_WAKE_TEXT}`;
1847
1852
  wakeAdmissions.set(wake, { generation, context: ctx, engine, anchor: deliveryBranchAnchor });
1848
1853
  while (wakeAdmissions.size > MAX_WAKE_ADMISSIONS) {