@groeponline/pi-wishcraft 1.15.0 → 1.16.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/AGENTS.md +2 -0
- package/CHANGELOG.md +14 -1
- package/build-stamp.json +1 -1
- package/package.json +3 -1
- package/src/extension/core/peer-guard.ts +222 -0
- package/src/extension/queue/queue-integration.ts +21 -1
- package/src/extension/session/activate.ts +12 -0
- package/src/extension/session/session-lifecycle.ts +2 -0
- package/src/working-vibes/provider.ts +6 -2
package/AGENTS.md
CHANGED
|
@@ -252,6 +252,8 @@ function createRuntimeState(hooks: {
|
|
|
252
252
|
| **Circular import check** | `madge --circular src index.ts bash-mode queue` (via `npm run circular`) |
|
|
253
253
|
| **Import style** | `.ts` extensions on all imports, `node:`-prefixed builtins |
|
|
254
254
|
| **Pi CLI** | Installed at `^0.99.2` (`pi-coding-agent`, `pi-ai`, `pi-tui` move together — the agent depends on matching `pi-ai`/`pi-tui`, so bumping one alone splits the tree); wrapped to run under Node 24 via nvm |
|
|
255
|
+
| **Peer floor** | `peerDependencies` are `"*"` for the three Pi packages because Pi's own docs require it ("declare the host-provided packages listed above in peerDependencies with a `*` range"), and `scripts/verify-pi-package-contract.mjs` enforces it. `"*"` therefore cannot express a floor, so the real one is only enforced by the code using the SDK: `normalizeContext` (added in `pi-ai` 0.86.0, used by `src/working-vibes/provider.ts`) is the binding symbol — probed across every SDK release, all 12 runtime symbols resolve from 0.86.0 up. A consumer on 0.84.3 installs without warning, but the activation-time peer guard (`src/extension/core/peer-guard.ts`) names every missing symbol with its feature and floor, notifies at session start, and turns the `/vibe` failure into an actionable error; `tests/peer-guard.test.ts` keeps the guarded catalog in sync with the published import surface in both directions. |
|
|
256
|
+
| **Dependency audit** | `npm run audit` (`scripts/audit-gate.mjs`) is the gate. `audit-baseline.json` lists accepted advisories, each pinned to its GHSA id **and** the install path(s) it covers, with a required written reason; new high/critical advisories fail even on an already-accepted package, a second vulnerable copy of an accepted package fails, a baselined advisory that stops reporting as high/critical also fails, an `npm audit` that errors out fails the gate instead of reading as "zero findings", and the baseline is excluded from the published tarball. |
|
|
255
257
|
| **Docker** | `fuse-overlayfs` storage driver for nested VM testing |
|
|
256
258
|
| **Compound Engineering** | `.compound-engineering/` overlay; portable skills via `~/.agents/skills/ce-*`; native Cursor plugin disabled |
|
|
257
259
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.16.0] - 2026-10-03
|
|
6
|
+
|
|
7
|
+
### Known limitations
|
|
8
|
+
- **`peerDependencies: "*"` cannot express a supported-version floor.** Pi's own docs require the host-provided packages to be declared with a `"*"` range and `verify-pi-package-contract.mjs` enforces it, so the constraint is upstream and cannot be tightened here. The consequence is measured rather than assumed: probing every SDK release for the 12 runtime symbols this package imports, all of them resolve from 0.86.0 up, and the binding one is `normalizeContext` (`src/working-vibes/provider.ts`), which arrived in `pi-ai` 0.86.0. A consumer on 0.84.3 therefore installs this package without warning or resolution error. The manifest side of this stays open — the floor cannot be expressed there at all — but the silent failure is closed: the extension now checks the symbol set at activation and reports the gap (see Fixed).
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **A Pi SDK below 0.86.0 broke `/vibe` with a bare `TypeError`.** `peerDependencies` are `"*"` by upstream contract, so an old host installs this package without complaint and the breakage surfaced as "normalizeContext is not a function" at the first `/vibe` call. The extension now checks the runtime symbol set it actually imports from the three Pi peers at activation (`src/extension/core/peer-guard.ts`): every missing symbol is named with the feature that depends on it and the version floor to upgrade to, the first session surfaces the same as a UI notification, and the `/vibe` call site fails with that actionable message instead of a TypeError. The guard checks symbol presence rather than version strings because symbols are what the code depends on — a semver comparison would lie across backports or version-skewed trees. `tests/peer-guard.test.ts` keeps the guarded catalog in exact sync with the runtime imports of every published source file in both directions: a newly used SDK symbol that is not guarded fails the suite, as does a catalog entry nothing imports anymore. Each guard was verified against an injected regression rather than only against the passing state.
|
|
12
|
+
- **The dependency audit could never fail CI, so it caught nothing.** The step ran `npm audit --audit-level=high` behind `continue-on-error: true`, labelled "informational" — and it exits 1 today, because of the one finding below. A step that cannot fail is a step that also cannot fail on anything *new*: the `ws@7.5.10` advisory, or the next one, would have shipped silently. The same shape as the coverage gate that printed thresholds while collecting no coverage. `npm run audit` (`scripts/audit-gate.mjs`) now audits against `audit-baseline.json`: a new high or critical advisory fails, the justified inherited advisories are tolerated, a baselined advisory that stops reporting as high/critical fails too (so it cannot become a permanent blanket exemption), and every accepted entry must carry a written reason naming the mechanism that makes it unreachable, its GHSA id, and the install path(s) it covers — so neither a new advisory nor a second vulnerable copy of the same package inherits the exemption. An `npm audit` that errors out (registry down, npm config policy) fails the gate rather than reading as "zero findings". Verified by injecting `ws@7.5.10` into the real tree — the gate named it as new and blocked while still passing the baselined finding.
|
|
13
|
+
- `audit-baseline.json` was shipping inside the npm tarball: `files` contains a `*.json` glob, so a new root-level JSON file is published by default. Excluded, and `tests/audit-gate.test.ts` now reads the real `npm pack` output rather than trusting the `files` array.
|
|
14
|
+
- **The remaining `npm audit` finding was half ours.** The 1.15.0 note recorded that `brace-expansion` "has no fix available at any installable version" — that was wrong on both counts, and it buried a fixable bug under a false exemption. There are two copies in the tree and they have different causes. The top-level one (pulled in via `madge` → `minimatch`) was sitting on 5.0.9 through a stale lockfile pin and is ours: it is now 5.0.12. The copy nested under `@earendil-works/pi-coding-agent` really is out of reach, but not because no fix exists — `brace-expansion@5.0.12` satisfies the `^5.0.8` range the peer's own `minimatch` declares, so the peer can fix it with a lockfile bump alone, no API change. It is unreachable from here for a different reason: the peer publishes an `npm-shrinkwrap.json`, which is authoritative for consumers, and npm installs that pinned tree verbatim without re-resolving it. Confirmed empirically that `overrides` cannot reach it — both a top-level `"brace-expansion": "^5.0.12"` and a path-scoped `"@earendil-works/pi-coding-agent": { "brace-expansion": "^5.0.12" }` left the nested copy at 5.0.9 — so no override is shipped rather than one that would advertise a guarantee it cannot keep.
|
|
15
|
+
- `tests/dependency-provenance.test.ts` guards the distinction: it fails if a vulnerable copy appears anywhere outside the peer's subtree (that one is ours, and names `npm audit fix`), if an unreachable override is ever added, and if the peer's shrinkwrap moves to a fixed version (the finding is then gone and the CHANGELOG needs updating). Each guard was verified against an injected regression rather than only against the passing state. The same file now also guards the general shape: we ship no shrinkwrap ourselves, an `overrides` entry never targets a package that ships one (such an override is a silent no-op), and every baselined advisory names a mechanism rather than just asserting "inherited".
|
|
16
|
+
- Audited the rest of the tree for the same pattern and found no second exemption: one high finding total, no deprecated packages, all 346 registry signatures and 90 attestations verified. A synthetic consumer install (`pi-wishcraft@1.15.0` alongside the three Pi packages) reports exactly the same single finding, so the published package surfaces nothing extra. Of the 146 versions the peer shrinkwraps, 25 are absent from a linux-x64 tree and all 25 are platform-specific `@esbuild/*` binaries with no advisory surface — noted as a real blind spot on other platforms rather than a clean bill of health.
|
|
17
|
+
|
|
5
18
|
## [1.15.0] - 2026-10-01
|
|
6
19
|
|
|
7
20
|
### Changed
|
|
8
21
|
- **Pi SDK upgraded `0.84.3` -> `0.99.2`** (`@earendil-works/pi-coding-agent`, `pi-ai`, `pi-tui` moved together). These must be bumped as a set: the agent depends on matching `pi-ai`/`pi-tui`, so bumping it alone leaves a version-skewed tree — which is exactly what the queued Dependabot PR #118 does (its lockfile keeps top-level `pi-ai` at `0.84.3` under a `0.99.2` agent). On the coordinated bump npm still nests copies, but at identical versions, so nothing is skewed. One real API change: `Provider.stream()` now takes a branded `TranscriptContext` produced only by `normalizeContext()`, which folds `systemPrompt`/`tools` into a leading system message — `src/working-vibes/provider.ts` now calls it, and the working-vibes tests assert the prompt the way the SDK delivers it (as `messages[0]`) rather than through a `context.systemPrompt` field that no longer exists.
|
|
9
|
-
- Audit findings drop from 3 (1 moderate, 2 high) to 1
|
|
22
|
+
- Audit findings drop from 3 (1 moderate, 2 high) to 1 high; the `undici` advisories are cleared. The remaining `brace-expansion` finding is inherited from the peer package's own dependency tree. (Corrected after release: the claim that it had no fix at any installable version was wrong — see [Unreleased].)
|
|
10
23
|
|
|
11
24
|
## [1.14.0] - 2026-10-01
|
|
12
25
|
|
package/build-stamp.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"source_sha":"
|
|
1
|
+
{"source_sha":"1957ea6e4334e28e25770cba4114e33c69062632"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@groeponline/pi-wishcraft",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Pi coding agent extension: operator cockpit with a live powerline status bar, idea queue, skill search, sticky Bash, hooks, policy guardrails, and TUI session UX.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"*.ts",
|
|
11
11
|
"*.md",
|
|
12
12
|
"*.json",
|
|
13
|
+
"!audit-baseline.json",
|
|
13
14
|
"banner.png",
|
|
14
15
|
"docs/**/*.md",
|
|
15
16
|
"src/**/*.ts",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"prepublishOnly": "tsc --noEmit && node scripts/verify-package.mjs && npm run verify:pi-package",
|
|
65
66
|
"verify:package": "node scripts/verify-package.mjs && node scripts/verify-pi-package-contract.mjs",
|
|
66
67
|
"circular": "madge --circular src index.ts bash-mode queue",
|
|
68
|
+
"audit": "node scripts/audit-gate.mjs",
|
|
67
69
|
"preview": "node --experimental-strip-types scripts/preview.mjs",
|
|
68
70
|
"verify:pi-package": "node scripts/verify-pi-package-contract.mjs"
|
|
69
71
|
},
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// peer-guard.ts
|
|
2
|
+
// Activation-time guard for the host-provided Pi SDK peers.
|
|
3
|
+
//
|
|
4
|
+
// `peerDependencies` for the three @earendil-works packages must stay "*" (Pi's
|
|
5
|
+
// package contract requires it and `verify-pi-package-contract.mjs` enforces
|
|
6
|
+
// it), so the manifest cannot express the version floor this extension really
|
|
7
|
+
// has. Every runtime symbol it imports exists from 0.86.0 up, and
|
|
8
|
+
// `normalizeContext` (pi-ai) is the binding one — it arrived last. Without a
|
|
9
|
+
// guard, a host on 0.84.3 loads the extension cleanly and only fails at first
|
|
10
|
+
// use: `/vibe` died on a bare `TypeError: normalizeContext is not a function`
|
|
11
|
+
// deep inside provider code.
|
|
12
|
+
//
|
|
13
|
+
// The guard checks the imported *symbol set* rather than a version string:
|
|
14
|
+
// symbol presence is what the code actually depends on, and it survives
|
|
15
|
+
// backports, republishes, and version-skewed trees where a semver comparison
|
|
16
|
+
// would lie. Warn-only by design — an under-floor host still runs every other
|
|
17
|
+
// surface, so blocking activation would break more than it fixes.
|
|
18
|
+
|
|
19
|
+
import * as piAi from "@earendil-works/pi-ai";
|
|
20
|
+
import * as piCodingAgent from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import * as piTui from "@earendil-works/pi-tui";
|
|
22
|
+
|
|
23
|
+
/** Oldest Pi SDK release where every imported symbol exists (probed per release). */
|
|
24
|
+
export const PEER_FLOOR_VERSION = "0.86.0";
|
|
25
|
+
|
|
26
|
+
export type PeerPackage =
|
|
27
|
+
| "@earendil-works/pi-ai"
|
|
28
|
+
| "@earendil-works/pi-coding-agent"
|
|
29
|
+
| "@earendil-works/pi-tui";
|
|
30
|
+
|
|
31
|
+
export interface PeerSymbolRequirement {
|
|
32
|
+
pkg: PeerPackage;
|
|
33
|
+
symbol: string;
|
|
34
|
+
/** Feature that degrades when the symbol is absent. */
|
|
35
|
+
usedBy: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// The full runtime (non-type) import surface of this package. Type-only
|
|
39
|
+
// imports cannot fail at runtime and are deliberately absent; the structural
|
|
40
|
+
// test in tests/peer-guard.test.ts keeps this list in exact sync with the
|
|
41
|
+
// imports in every published source file, in both directions.
|
|
42
|
+
export const PEER_SYMBOL_REQUIREMENTS: readonly PeerSymbolRequirement[] = [
|
|
43
|
+
{
|
|
44
|
+
pkg: "@earendil-works/pi-ai",
|
|
45
|
+
symbol: "normalizeContext",
|
|
46
|
+
usedBy: "/vibe working messages",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
pkg: "@earendil-works/pi-ai",
|
|
50
|
+
symbol: "createAssistantMessageEventStream",
|
|
51
|
+
usedBy: "studio advice pane",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
pkg: "@earendil-works/pi-coding-agent",
|
|
55
|
+
symbol: "CustomEditor",
|
|
56
|
+
usedBy: "sticky Bash mode",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
pkg: "@earendil-works/pi-coding-agent",
|
|
60
|
+
symbol: "copyToClipboard",
|
|
61
|
+
usedBy: "copy shortcuts and /vibe test",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
pkg: "@earendil-works/pi-coding-agent",
|
|
65
|
+
symbol: "loadSkills",
|
|
66
|
+
usedBy: "skill registry",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
pkg: "@earendil-works/pi-coding-agent",
|
|
70
|
+
symbol: "SessionManager",
|
|
71
|
+
usedBy: "/cd",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
pkg: "@earendil-works/pi-tui",
|
|
75
|
+
symbol: "matchesKey",
|
|
76
|
+
usedBy: "keyboard shortcuts",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
pkg: "@earendil-works/pi-tui",
|
|
80
|
+
symbol: "isKeyRelease",
|
|
81
|
+
usedBy: "keyboard shortcuts",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
pkg: "@earendil-works/pi-tui",
|
|
85
|
+
symbol: "visibleWidth",
|
|
86
|
+
usedBy: "status line rendering",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
pkg: "@earendil-works/pi-tui",
|
|
90
|
+
symbol: "truncateToWidth",
|
|
91
|
+
usedBy: "status line rendering",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
pkg: "@earendil-works/pi-tui",
|
|
95
|
+
symbol: "SelectList",
|
|
96
|
+
usedBy: "select overlays",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
pkg: "@earendil-works/pi-tui",
|
|
100
|
+
symbol: "TUI_KEYBINDINGS",
|
|
101
|
+
usedBy: "default keybindings",
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve a symbol from a peer module namespace. Looks on the namespace itself
|
|
107
|
+
* and, for interop shapes where named exports land under `default` (CJS-style
|
|
108
|
+
* wrapping during extension load), there too.
|
|
109
|
+
*/
|
|
110
|
+
function lookupExport(moduleNs: unknown, symbol: string): unknown {
|
|
111
|
+
if (moduleNs === null || typeof moduleNs !== "object") return undefined;
|
|
112
|
+
const ns = moduleNs as Record<string, unknown>;
|
|
113
|
+
const direct = ns[symbol];
|
|
114
|
+
if (direct !== undefined) return direct;
|
|
115
|
+
const fallback = ns.default;
|
|
116
|
+
if (fallback !== null && typeof fallback === "object") {
|
|
117
|
+
return (fallback as Record<string, unknown>)[symbol];
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Which required symbols are absent from the given peer namespaces? */
|
|
123
|
+
export function findMissingPeerSymbols(
|
|
124
|
+
exportsByPkg: Readonly<Partial<Record<PeerPackage, unknown>>>,
|
|
125
|
+
requirements: readonly PeerSymbolRequirement[] = PEER_SYMBOL_REQUIREMENTS,
|
|
126
|
+
): PeerSymbolRequirement[] {
|
|
127
|
+
return requirements.filter(
|
|
128
|
+
({ pkg, symbol }) => lookupExport(exportsByPkg[pkg], symbol) === undefined,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The live peer namespaces, collected for the runtime check. */
|
|
133
|
+
export function collectPeerExports(): Record<PeerPackage, unknown> {
|
|
134
|
+
return {
|
|
135
|
+
"@earendil-works/pi-ai": piAi,
|
|
136
|
+
"@earendil-works/pi-coding-agent": piCodingAgent,
|
|
137
|
+
"@earendil-works/pi-tui": piTui,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The runtime check against the peers this process actually loaded. */
|
|
142
|
+
export function checkPeerRuntime(): PeerSymbolRequirement[] {
|
|
143
|
+
return findMissingPeerSymbols(collectPeerExports());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Multi-line console warning naming every missing symbol and its blast radius. */
|
|
147
|
+
export function formatPeerGuardWarning(
|
|
148
|
+
missing: readonly PeerSymbolRequirement[],
|
|
149
|
+
): string {
|
|
150
|
+
const lines = missing.map(
|
|
151
|
+
(m) => ` - ${m.symbol} (${m.pkg}) — needed by ${m.usedBy}`,
|
|
152
|
+
);
|
|
153
|
+
return [
|
|
154
|
+
`Pi SDK below the supported floor: ${missing.length} imported symbol${missing.length === 1 ? " is" : "s are"} missing (all of them exist from ${PEER_FLOOR_VERSION} up).`,
|
|
155
|
+
...lines,
|
|
156
|
+
`The extension stays loaded and affected features fall back; upgrade @earendil-works/pi-coding-agent with its matching pi-ai/pi-tui to ${PEER_FLOOR_VERSION} or newer.`,
|
|
157
|
+
].join("\n");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** One-line variant for `ctx.ui.notify`. */
|
|
161
|
+
export function formatPeerGuardNotice(
|
|
162
|
+
missing: readonly PeerSymbolRequirement[],
|
|
163
|
+
): string {
|
|
164
|
+
const symbols = missing.map((m) => m.symbol).join(", ");
|
|
165
|
+
return `Pi SDK < ${PEER_FLOOR_VERSION}: missing ${symbols} — upgrade the Pi SDK to restore full functionality`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Actionable error for a call site that cannot degrade gracefully. */
|
|
169
|
+
export function missingSymbolError(symbol: string): Error {
|
|
170
|
+
const requirement = PEER_SYMBOL_REQUIREMENTS.find(
|
|
171
|
+
(r) => r.symbol === symbol,
|
|
172
|
+
);
|
|
173
|
+
const pkg = requirement?.pkg ?? "@earendil-works/pi-*";
|
|
174
|
+
const feature = requirement?.usedBy ?? symbol;
|
|
175
|
+
return new Error(
|
|
176
|
+
`${feature} needs ${symbol} from ${pkg}, which the installed Pi SDK does not provide — upgrade @earendil-works/pi-* to ${PEER_FLOOR_VERSION} or newer`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Call-site guard: fail with the actionable message instead of letting a
|
|
182
|
+
* missing export surface later as "x is not a function".
|
|
183
|
+
*/
|
|
184
|
+
export function requirePeerSymbol<T>(
|
|
185
|
+
value: T | undefined | null,
|
|
186
|
+
symbol: string,
|
|
187
|
+
): T {
|
|
188
|
+
if (value === undefined || value === null) throw missingSymbolError(symbol);
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let noticeDelivered = false;
|
|
193
|
+
|
|
194
|
+
/** Minimal slice of Pi's ExtensionContext the warning needs. */
|
|
195
|
+
export interface PeerGuardContext {
|
|
196
|
+
hasUI?: boolean;
|
|
197
|
+
ui?: {
|
|
198
|
+
notify(message: string, level?: "warning" | "error" | "info"): void;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* One-shot UI warning at session start. `check` is injectable so tests can
|
|
204
|
+
* drive the missing-symbol path without an under-floor SDK.
|
|
205
|
+
*/
|
|
206
|
+
export function maybeNotifyPeerGuard(
|
|
207
|
+
ctx: PeerGuardContext | undefined,
|
|
208
|
+
check: () => PeerSymbolRequirement[] = checkPeerRuntime,
|
|
209
|
+
): void {
|
|
210
|
+
if (noticeDelivered || !ctx?.hasUI || typeof ctx.ui?.notify !== "function") {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const missing = check();
|
|
214
|
+
if (missing.length === 0) return;
|
|
215
|
+
noticeDelivered = true;
|
|
216
|
+
ctx.ui.notify(formatPeerGuardNotice(missing), "warning");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Reset the one-shot latch (tests only). */
|
|
220
|
+
export function resetPeerGuardNotice(): void {
|
|
221
|
+
noticeDelivered = false;
|
|
222
|
+
}
|
|
@@ -36,13 +36,33 @@ export function queueItemDescription(item: PowerlineQueueItem): string {
|
|
|
36
36
|
return item.target.alias ? `@${item.target.alias}` : item.target.cwd;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Pi's working indicator (spinner frame + "Working...") can end up at the start
|
|
41
|
+
* of captured text when the editor buffer is captured while the agent runs.
|
|
42
|
+
* Strip that prefix so ideas never render as a permanently "working" card.
|
|
43
|
+
*/
|
|
44
|
+
const CAPTURE_NOISE_PREFIX =
|
|
45
|
+
/^[\u2800-\u28FF][\u2800-\u28FF ]*\s*Working[.…]+\s*/;
|
|
46
|
+
|
|
47
|
+
export function stripCaptureNoisePrefix(text: string): string {
|
|
48
|
+
let out = text;
|
|
49
|
+
while (CAPTURE_NOISE_PREFIX.test(out)) {
|
|
50
|
+
out = out.replace(CAPTURE_NOISE_PREFIX, "");
|
|
51
|
+
}
|
|
52
|
+
return out.trimStart();
|
|
53
|
+
}
|
|
54
|
+
|
|
39
55
|
export function captureQueueItem(
|
|
40
56
|
rt: RuntimeState,
|
|
41
57
|
ctx: any,
|
|
42
|
-
|
|
58
|
+
rawText: string,
|
|
43
59
|
intent: QueueIntent,
|
|
44
60
|
target: QueueTarget,
|
|
45
61
|
): PowerlineQueueItem {
|
|
62
|
+
const text = stripCaptureNoisePrefix(rawText);
|
|
63
|
+
if (!text.trim()) {
|
|
64
|
+
throw new Error("Nothing to capture");
|
|
65
|
+
}
|
|
46
66
|
const item = rt.queueStore.add({
|
|
47
67
|
text,
|
|
48
68
|
intent,
|
|
@@ -15,8 +15,20 @@ import {
|
|
|
15
15
|
PRESET_NAMES,
|
|
16
16
|
setConfig,
|
|
17
17
|
} from "../core/state.ts";
|
|
18
|
+
import {
|
|
19
|
+
checkPeerRuntime,
|
|
20
|
+
formatPeerGuardWarning,
|
|
21
|
+
} from "../core/peer-guard.ts";
|
|
18
22
|
|
|
19
23
|
export default function powerlineFooter(pi: ExtensionAPI) {
|
|
24
|
+
// peerDependencies are "*" by contract, so the manifest cannot express the
|
|
25
|
+
// real SDK floor. Check the imported symbol set up front and say what is
|
|
26
|
+
// missing, instead of letting an under-floor host fail at first use.
|
|
27
|
+
const missingPeers = checkPeerRuntime();
|
|
28
|
+
if (missingPeers.length > 0) {
|
|
29
|
+
console.warn(`[wishcraft] ${formatPeerGuardWarning(missingPeers)}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
const startupSettings = readSettings();
|
|
21
33
|
// Ambient UI language: resolved before anything renders, so the very first
|
|
22
34
|
// frame of the status line and welcome overlay is already in the right
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
setCustomCompactionEnabled,
|
|
54
54
|
} from "../core/state.ts";
|
|
55
55
|
import { CONTEXT_STATUS_RENDER_MS } from "../core/constants.ts";
|
|
56
|
+
import { maybeNotifyPeerGuard } from "../core/peer-guard.ts";
|
|
56
57
|
import type { RuntimeState } from "../core/types.ts";
|
|
57
58
|
import { isStaleExtensionContextError } from "./stale-context.ts";
|
|
58
59
|
import { dismissWelcome } from "../welcome/welcome-control.ts";
|
|
@@ -139,6 +140,7 @@ export function registerSessionLifecycle(
|
|
|
139
140
|
registerCustomSegments(config.segments);
|
|
140
141
|
registerCustomPresets(config.presets);
|
|
141
142
|
warnInvalidSegmentSettings(ctx);
|
|
143
|
+
maybeNotifyPeerGuard(ctx);
|
|
142
144
|
rt.stashedPromptHistory = readPersistedStashHistory();
|
|
143
145
|
rt.bashModeActive = false;
|
|
144
146
|
rt.bashTranscript = new BashTranscriptStore(rt.bashModeSettings);
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ProviderStreamOptions,
|
|
10
10
|
} from "@earendil-works/pi-ai";
|
|
11
11
|
import { normalizeContext } from "@earendil-works/pi-ai";
|
|
12
|
+
import { requirePeerSymbol } from "../extension/core/peer-guard.ts";
|
|
12
13
|
import { VIBE_SYSTEM_PROMPT, vibeState } from "./storage.ts";
|
|
13
14
|
|
|
14
15
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -41,8 +42,11 @@ export async function completeVibe(
|
|
|
41
42
|
// `Provider.stream()` now takes a branded `TranscriptContext`, which only
|
|
42
43
|
// `normalizeContext()` produces. It folds `systemPrompt` and `tools` into a
|
|
43
44
|
// leading system message; the brand exists so a raw `Context` cannot reach
|
|
44
|
-
// provider code unnormalized.
|
|
45
|
-
|
|
45
|
+
// provider code unnormalized. `requirePeerSymbol` turns a missing export on
|
|
46
|
+
// an under-floor Pi SDK into an actionable error instead of
|
|
47
|
+
// "normalizeContext is not a function".
|
|
48
|
+
const normalize = requirePeerSymbol(normalizeContext, "normalizeContext");
|
|
49
|
+
return provider.stream(requestModel, normalize(context), options).result();
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
// ═══════════════════════════════════════════════════════════════════════════
|