@quantiya/codevibe-codex-plugin 2.0.48 → 2.0.49
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/node_modules/@quantiya/codevibe-core/dist/index.js +453 -447
- package/node_modules/@quantiya/codevibe-core/dist/local-executor/shadow-recovery.d.ts +15 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +92 -8
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/doctor-cli.d.ts +44 -0
- package/node_modules/@quantiya/codevibe-core/package.json +2 -2
- package/package.json +3 -3
|
@@ -250,6 +250,21 @@ interface RegisteredShadowRoot extends AnchoredFsIdentity {
|
|
|
250
250
|
canonicalPath?: string;
|
|
251
251
|
birthtimeNs?: string;
|
|
252
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* FU-1 — a registered physical-copy root whose pathname is gone and whose bytes could NOT be proven reclaimed.
|
|
255
|
+
*
|
|
256
|
+
* Carries the two paths the user needs to act, because the generic surfacing cannot: the implementor-round
|
|
257
|
+
* classifier returns a closed label and never the raw message, and the durable log projection strips message,
|
|
258
|
+
* name, code and path. Both are deliberate wire-safety controls (#SR-2), so the recovery instructions travel as
|
|
259
|
+
* STRUCTURED FIELDS on the error rather than as text the classifier would have to parse or leak. Rendering these
|
|
260
|
+
* two paths in full — desktop, hosted events and durable diagnostics — is FU1-D2, an owner decision made with the
|
|
261
|
+
* trade-off stated: they are CodeVibe-registered root paths, never raw error text and never user content.
|
|
262
|
+
*/
|
|
263
|
+
export declare class StaleShadowRootError extends Error {
|
|
264
|
+
readonly stalePath: string;
|
|
265
|
+
readonly registryPath: string;
|
|
266
|
+
constructor(stalePath: string, registryPath: string);
|
|
267
|
+
}
|
|
253
268
|
/**
|
|
254
269
|
* Durable quota-scope registry. Every process sharing `stateRoot` registers its
|
|
255
270
|
* physical shadow root before copying, so the aggregate scan cannot be split by
|
|
@@ -30495,13 +30495,18 @@ async function hasUnreclaimedExposureRetention(stateRoot) {
|
|
|
30495
30495
|
}
|
|
30496
30496
|
return !1;
|
|
30497
30497
|
}
|
|
30498
|
+
var StaleShadowRootError = class extends Error {
|
|
30499
|
+
constructor(stalePath, registryPath) {
|
|
30500
|
+
super("registered physical-copy root disappeared while quota authority is retained"), this.name = "StaleShadowRootError", this.stalePath = stalePath, this.registryPath = registryPath;
|
|
30501
|
+
}
|
|
30502
|
+
};
|
|
30498
30503
|
function isSameRegisteredRootAuthority(prior, current) {
|
|
30499
30504
|
return prior.dev === current.dev && prior.ino === current.ino && prior.version === current.version && prior.canonicalPath === current.canonicalPath && prior.birthtimeNs === current.birthtimeNs;
|
|
30500
30505
|
}
|
|
30501
30506
|
function isSameRootAfterDeviceReincarnation(prior, current) {
|
|
30502
30507
|
return prior.dev !== current.dev && prior.ino === current.ino && prior.version === current.version && prior.canonicalPath === current.canonicalPath && prior.birthtimeNs === current.birthtimeNs;
|
|
30503
30508
|
}
|
|
30504
|
-
async function collectDurablyReferencedShadowRoots(stateRoot, stateRootAuthority, maxStateMarkerBytes, registeredShadowRoots, afterTaskEntry, afterMarkerEntry, afterMarkerStat, allowUnrecognizedRegularFiles = !1, allowUnregisteredReferences = !1, allowConcurrentSiblingTeardown = !1) {
|
|
30509
|
+
async function collectDurablyReferencedShadowRoots(stateRoot, stateRootAuthority, maxStateMarkerBytes, registeredShadowRoots, afterTaskEntry, afterMarkerEntry, afterMarkerStat, allowUnrecognizedRegularFiles = !1, allowUnregisteredReferences = !1, allowConcurrentSiblingTeardown = !1, markerIndex) {
|
|
30505
30510
|
let retryConcurrentSiblingTeardown = (error, target) => {
|
|
30506
30511
|
let code = error?.code, message = error instanceof Error ? error.message : String(error), isSiblingChurn = code === "ENOENT" || message.includes("workspace root changed while opening") || message.includes("workspace root cwd identity mismatch") || message.includes("workspace root changed during authority capture") || message.includes("bounded-directory target identity mismatch") || message.includes("bounded-directory parent changed while opening") || message.includes("bounded-directory parent changed while descending") || message.includes("anchored read root authority unavailable") || message.includes("anchored read root identity mismatch") || message.includes("anchored read root incarnation mismatch") || message.includes("anchored read directory chain changed") || message.includes("anchored read directory changed while descending") || message.includes("anchored read cwd identity mismatch") || message.includes("anchored read file identity mismatch") || message.includes("anchored read file changed during read") || message.includes("anchored read file path changed") || message.includes("anchored read file changed after read");
|
|
30507
30512
|
throw allowConcurrentSiblingTeardown && isSiblingChurn ? new Error(
|
|
@@ -30591,7 +30596,7 @@ async function collectDurablyReferencedShadowRoots(stateRoot, stateRootAuthority
|
|
|
30591
30596
|
if (allowUnregisteredReferences) continue;
|
|
30592
30597
|
throw new Error("physical-copy root reference marker names an unregistered root");
|
|
30593
30598
|
}
|
|
30594
|
-
referenced.add(markerRoot2);
|
|
30599
|
+
referenced.add(markerRoot2), markerIndex && markerIndex.set(markerRoot2, [...markerIndex.get(markerRoot2) ?? [], stateFile]);
|
|
30595
30600
|
continue;
|
|
30596
30601
|
}
|
|
30597
30602
|
let preliminary = value;
|
|
@@ -30602,7 +30607,7 @@ async function collectDurablyReferencedShadowRoots(stateRoot, stateRootAuthority
|
|
|
30602
30607
|
if (allowUnregisteredReferences) continue;
|
|
30603
30608
|
throw new Error("physical-copy root exposure marker names an unregistered root");
|
|
30604
30609
|
}
|
|
30605
|
-
referenced.add(markerRoot);
|
|
30610
|
+
referenced.add(markerRoot), markerIndex && markerIndex.set(markerRoot, [...markerIndex.get(markerRoot) ?? [], stateFile]);
|
|
30606
30611
|
}
|
|
30607
30612
|
}
|
|
30608
30613
|
return referenced;
|
|
@@ -30640,7 +30645,7 @@ async function registerAndListQuotaShadowRoots(stateRoot, stateRootIdentity, cur
|
|
|
30640
30645
|
else
|
|
30641
30646
|
throw new Error("physical-copy root changed identity while quota authority is retained");
|
|
30642
30647
|
if (!priorCurrent && mode === "register-current" && consumeMetadataPath(registryBudget, currentShadowRoot, "physical-copy root registry"), mode === "register-current" && byPath.set(currentShadowRoot, { path: currentShadowRoot, ...currentShadowRootIdentity }), byPath.size > 256) throw new Error("physical-copy root registry exceeds 256 roots");
|
|
30643
|
-
let retained = [], scanBudget = createPathBudget(), referencedShadowRoots = null, hasPhysicalCopyEntry = async (candidate) => {
|
|
30648
|
+
let retained = [], scanBudget = createPathBudget(), referencedShadowRoots = null, vanishedMarkerIndex = null, hasPhysicalCopyEntry = async (candidate) => {
|
|
30644
30649
|
let liveRoot;
|
|
30645
30650
|
try {
|
|
30646
30651
|
liveRoot = await captureWorkspaceRootAuthority(candidate.path);
|
|
@@ -30668,7 +30673,79 @@ async function registerAndListQuotaShadowRoots(stateRoot, stateRootIdentity, cur
|
|
|
30668
30673
|
afterQuotaReferenceTaskEntry,
|
|
30669
30674
|
afterQuotaReferenceMarkerEntry,
|
|
30670
30675
|
afterQuotaReferenceMarkerStat
|
|
30671
|
-
), !referencedShadowRoots.has(candidate.path))
|
|
30676
|
+
), !referencedShadowRoots.has(candidate.path)), vanishedRootInodeSurvives = async (candidate) => {
|
|
30677
|
+
let parent = path26.dirname(candidate.path);
|
|
30678
|
+
if (parent === candidate.path) return !0;
|
|
30679
|
+
try {
|
|
30680
|
+
await fs19.lstat(parent);
|
|
30681
|
+
} catch (err) {
|
|
30682
|
+
return err.code !== "ENOENT";
|
|
30683
|
+
}
|
|
30684
|
+
let entries;
|
|
30685
|
+
try {
|
|
30686
|
+
entries = await readDirectoryBounded(
|
|
30687
|
+
parent,
|
|
30688
|
+
scanBudget,
|
|
30689
|
+
"physical-copy root reconcile parent scan",
|
|
30690
|
+
parent,
|
|
30691
|
+
{ includeStats: !0 }
|
|
30692
|
+
);
|
|
30693
|
+
} catch {
|
|
30694
|
+
return !0;
|
|
30695
|
+
}
|
|
30696
|
+
for (let entry of entries) {
|
|
30697
|
+
let stat13 = boundedDirectoryStat(entry);
|
|
30698
|
+
if (stat13 && stat13.ino === candidate.ino) return !0;
|
|
30699
|
+
}
|
|
30700
|
+
return !1;
|
|
30701
|
+
}, canRetireVanishedRoot = async (candidate) => {
|
|
30702
|
+
if (await vanishedRootInodeSurvives(candidate)) return !1;
|
|
30703
|
+
let referenced = referencedShadowRoots, markers = vanishedMarkerIndex;
|
|
30704
|
+
if ((referenced === null || markers === null) && (markers = /* @__PURE__ */ new Map(), referenced = await collectDurablyReferencedShadowRoots(
|
|
30705
|
+
stateRoot,
|
|
30706
|
+
stateRootIdentity,
|
|
30707
|
+
maxStateMarkerBytes,
|
|
30708
|
+
new Set(byPath.keys()),
|
|
30709
|
+
afterQuotaReferenceTaskEntry,
|
|
30710
|
+
afterQuotaReferenceMarkerEntry,
|
|
30711
|
+
afterQuotaReferenceMarkerStat,
|
|
30712
|
+
// TOLERANT on purpose. (Stage 1 F4: an earlier version of this comment said "unlike the
|
|
30713
|
+
// retirement-mode call above" — that was wrong, and had been wrong on main too: the retirement-mode
|
|
30714
|
+
// call already passes all three flags true. The scan that stays STRICT is the one behind
|
|
30715
|
+
// `canRetireReincarnatedRoot`.)
|
|
30716
|
+
//
|
|
30717
|
+
// This scan answers one narrow question — is the DEAD root still referenced? — and it runs on a state root
|
|
30718
|
+
// shared by every CodeVibe session and agent on the machine. Strict mode aborts the whole reconcile on
|
|
30719
|
+
// anything unexpected anywhere in that directory, which in practice means: a stray regular file (the
|
|
30720
|
+
// registry's own `.bak` copies — including the ones `codevibe doctor --repair` used to write there before
|
|
30721
|
+
// Stage 1 F5 moved every write it makes to `~/.codevibe/shadow-root-backups/`), a marker naming a
|
|
30722
|
+
// root some other agent never cleaned up (six such directories existed on the dogfood machine), or a
|
|
30723
|
+
// sibling session tearing down concurrently. Any of those would re-wedge the user with
|
|
30724
|
+
// `physical-copy root reference state entry changed type` and no way out — which is exactly what the
|
|
30725
|
+
// real-surface E2E hit.
|
|
30726
|
+
//
|
|
30727
|
+
// Tolerating them cannot lose quota authority: an entry this scan skips is simply not counted as a
|
|
30728
|
+
// reference, and the retirement still requires the root's inode to be proven freed first.
|
|
30729
|
+
!0,
|
|
30730
|
+
// allowUnrecognizedRegularFiles
|
|
30731
|
+
!0,
|
|
30732
|
+
// allowUnregisteredReferences
|
|
30733
|
+
!0,
|
|
30734
|
+
// allowConcurrentSiblingTeardown — surfaces as a retryable enumeration change
|
|
30735
|
+
markers
|
|
30736
|
+
), referencedShadowRoots = referenced, vanishedMarkerIndex = markers), !referenced.has(candidate.path)) return !0;
|
|
30737
|
+
for (let markerFile of markers.get(candidate.path) ?? []) {
|
|
30738
|
+
await anchoredRemoveFile(markerFile);
|
|
30739
|
+
let taskStateDir = path26.dirname(markerFile);
|
|
30740
|
+
try {
|
|
30741
|
+
await anchoredRemoveEmptyDirectory(taskStateDir, void 0, { expectedParent: stateRootIdentity });
|
|
30742
|
+
} catch (err) {
|
|
30743
|
+
let code = err.code;
|
|
30744
|
+
if (code !== "ENOTEMPTY" && code !== "ENOENT" && code !== "EEXIST") throw err;
|
|
30745
|
+
}
|
|
30746
|
+
}
|
|
30747
|
+
return !0;
|
|
30748
|
+
};
|
|
30672
30749
|
if (mode === "retire-current-if-unused") {
|
|
30673
30750
|
let candidate = priorCurrent;
|
|
30674
30751
|
if (await assertWorkspaceRootAuthority(
|
|
@@ -30731,8 +30808,8 @@ async function registerAndListQuotaShadowRoots(stateRoot, stateRootIdentity, cur
|
|
|
30731
30808
|
)).some((entry) => entry.isDirectory() && !entry.isSymbolicLink() && (SHADOW_DIR_RE.test(entry.name) || SHADOW_STAGING_DIR_RE.test(entry.name) || SHADOW_TREE_TOMBSTONE_RE.test(entry.name))) ? retained.push(candidate) : reincarnatedDevice !== null && (await canRetireReincarnatedRoot(candidate) || retained.push(candidate));
|
|
30732
30809
|
} catch (err) {
|
|
30733
30810
|
if (err.code === "ENOENT") {
|
|
30734
|
-
if (!await canRetireReincarnatedRoot(candidate))
|
|
30735
|
-
throw new
|
|
30811
|
+
if (!await canRetireReincarnatedRoot(candidate) && !await canRetireVanishedRoot(candidate))
|
|
30812
|
+
throw new StaleShadowRootError(candidate.path, registryPath);
|
|
30736
30813
|
byPath.delete(candidate.path);
|
|
30737
30814
|
continue;
|
|
30738
30815
|
}
|
|
@@ -52157,6 +52234,8 @@ var AUTHORITY_REFUSAL_LABEL = {
|
|
|
52157
52234
|
function classifyImplementorRoundFailure(err) {
|
|
52158
52235
|
if (err instanceof AuthorityError)
|
|
52159
52236
|
return AUTHORITY_REFUSAL_LABEL[err.refusal.category] ?? "the implementor was refused authority";
|
|
52237
|
+
if (err instanceof StaleShadowRootError)
|
|
52238
|
+
return `the recorded workspace-copy folder ${err.stalePath} is missing and its contents could not be accounted for (they may have been moved). Run \`codevibe doctor\` to review and clear it`;
|
|
52160
52239
|
let msg = err instanceof Error ? err.message.toLowerCase() : "";
|
|
52161
52240
|
return msg.includes("enoent") || msg.includes("spawn") || msg.includes("command not found") || msg.includes("not installed") ? "the coding agent could not be started" : "an unexpected error occurred";
|
|
52162
52241
|
}
|
|
@@ -55236,7 +55315,12 @@ ${section}`);
|
|
|
55236
55315
|
}
|
|
55237
55316
|
logger.warn("[QuorumLoop] implementor round failed", {
|
|
55238
55317
|
gateId,
|
|
55239
|
-
...errorShapeOnly(err)
|
|
55318
|
+
...errorShapeOnly(err),
|
|
55319
|
+
// FU-1 — the ONE exception to the shape-only projection, decided as FU1-D2. These are CodeVibe-registered
|
|
55320
|
+
// root pathnames carried as structured fields, never raw error text and never user content; without them
|
|
55321
|
+
// the log records `messageBytes` and nothing else, and a machine-wide outage is undiagnosable after the
|
|
55322
|
+
// fact (it was, twice, on 2026-09-17).
|
|
55323
|
+
...err instanceof StaleShadowRootError ? { staleShadowRoot: err.stalePath, shadowRootRegistry: err.registryPath } : {}
|
|
55240
55324
|
});
|
|
55241
55325
|
let reason = classifyImplementorRoundFailure(err);
|
|
55242
55326
|
this.surfaceHalt(`The task could not proceed \u2014 ${reason}.`);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
interface RegisteredRoot {
|
|
2
|
+
path: string;
|
|
3
|
+
dev?: string;
|
|
4
|
+
ino?: string;
|
|
5
|
+
canonicalPath?: string;
|
|
6
|
+
birthtimeNs?: string;
|
|
7
|
+
version?: number;
|
|
8
|
+
}
|
|
9
|
+
/** What the registry says about one root, and what the filesystem says back. */
|
|
10
|
+
export interface RootDiagnosis {
|
|
11
|
+
root: RegisteredRoot;
|
|
12
|
+
/** The registered pathname resolves to a directory right now. */
|
|
13
|
+
present: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The recorded inode was found among the root's siblings — the folder was renamed and its copies still exist,
|
|
16
|
+
* so the entry is still real quota authority. `undefined` when the parent EXISTS but could not be enumerated,
|
|
17
|
+
* which is a refusal to guess rather than an answer; a parent that is gone reads `false` (F1-10).
|
|
18
|
+
*/
|
|
19
|
+
inodeSurvives?: boolean;
|
|
20
|
+
/** Durable markers that name this root (`dirname(shadowDir)` / `dirname(finalShadowDir)`). */
|
|
21
|
+
markers: string[];
|
|
22
|
+
verdict: 'healthy' | 'reconcilable' | 'bytes-may-exist' | 'unknown';
|
|
23
|
+
}
|
|
24
|
+
/** The product's own state root — never a second spelling of it (Stage 1 F7). */
|
|
25
|
+
export declare function shadowStateRoot(): string;
|
|
26
|
+
/** Read the registry. A missing or unreadable registry is reported, never repaired. */
|
|
27
|
+
export declare function readRegistry(stateRoot: string): {
|
|
28
|
+
roots: RegisteredRoot[];
|
|
29
|
+
raw: string;
|
|
30
|
+
} | null;
|
|
31
|
+
/** Every durable marker under the state root, mapped to the root it names. */
|
|
32
|
+
export declare function collectMarkers(stateRoot: string): Map<string, string[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Does the recorded inode still exist among the root's siblings?
|
|
35
|
+
*
|
|
36
|
+
* `false` — proven gone, so the entry is reconcilable. `true` — a sibling still holds it, so the bytes are real.
|
|
37
|
+
* `undefined` — the parent exists but could not be READ, which is a refusal to guess rather than an answer.
|
|
38
|
+
*/
|
|
39
|
+
export declare function inodeSurvivesInParent(root: RegisteredRoot): boolean | undefined;
|
|
40
|
+
export declare function diagnose(stateRoot: string, roots: RegisteredRoot[]): RootDiagnosis[];
|
|
41
|
+
export declare function runDoctorCli(argv: string[], opts?: {
|
|
42
|
+
stateRoot?: string;
|
|
43
|
+
}): Promise<number>;
|
|
44
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantiya/codevibe-core",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.42",
|
|
4
4
|
"description": "Core library for CodeVibe plugins - shared keychain, crypto, AppSync, and auth functionality",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"typecheck": "tsc --noEmit",
|
|
19
19
|
"emit-types": "tsc -p tsconfig.types.json --emitDeclarationOnly",
|
|
20
20
|
"build": "rm -rf dist && npm run emit-types && esbuild src/index.ts --bundle --platform=node --target=node18 --minify --packages=external --define:__CODEVIBE_TEST_BUILD__=false --outfile=dist/index.js && esbuild src/orchestration-shell/cli.ts --bundle --platform=node --target=node18 --minify-syntax --packages=external --define:__CODEVIBE_TEST_BUILD__=false --outfile=dist/orchestration-shell/cli.js --banner:js=\"#!/usr/bin/env node\" && esbuild src/orchestration-shell/web/web-extract-worker.ts --bundle --platform=node --target=node18 --minify --packages=external --outfile=dist/orchestration-shell/web-extract-worker.js && npm run verify-no-test-hooks && npm run verify-no-test-types",
|
|
21
|
-
"verify-no-test-hooks": "if grep -ERl 'CODEVIBE_TEST_FORCE_QUOTA_FAIL|CODEVIBE_TEST_FORCE_IMPLEMENTOR_AGENT|CODEVIBE_ANCHORED_PUBLICATION_AUTHORITY_ROOT|codevibe-anchored-publications-|cv-publication-authority-vitest-|codevibe\\.test\\.publication-authority-|setPublicationAuthorityRootForTestBuild|__CODEVIBE_TEST_BUILD__|_onBeforeLinkForTesting|_onBeforeSummariesLockForTesting|__codevibe_test_on_before_link|__codevibe_test_on_before_summaries_lock' dist; then echo 'ERROR: a test-only hook leaked into a published artifact (prod-unreachability invariant)'; exit 1; fi; echo 'OK
|
|
21
|
+
"verify-no-test-hooks": "if grep -ERl 'CODEVIBE_TEST_FORCE_QUOTA_FAIL|CODEVIBE_TEST_FORCE_IMPLEMENTOR_AGENT|CODEVIBE_ANCHORED_PUBLICATION_AUTHORITY_ROOT|codevibe-anchored-publications-|cv-publication-authority-vitest-|codevibe\\.test\\.publication-authority-|setPublicationAuthorityRootForTestBuild|__CODEVIBE_TEST_BUILD__|_onBeforeLinkForTesting|_onBeforeSummariesLockForTesting|__codevibe_test_on_before_link|__codevibe_test_on_before_summaries_lock' dist; then echo 'ERROR: a test-only hook leaked into a published artifact (prod-unreachability invariant)'; exit 1; fi; echo 'OK \u2014 no test-only hooks in published artifacts'",
|
|
22
22
|
"verify-no-test-types": "if find dist -type f \\( -path '*/__tests__/*' -o -name '*.test.d.ts' -o -name 'test-publication-authority-setup.d.ts' \\) | grep -q .; then echo 'verify-no-test-types: test declarations present in dist (tsconfig.types.json must exclude test sources)' >&2; exit 1; fi",
|
|
23
23
|
"clean": "rm -rf dist",
|
|
24
24
|
"prepublishOnly": "npm run build",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantiya/codevibe-codex-plugin",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Control OpenAI Codex CLI from your iPhone and Android
|
|
3
|
+
"version": "2.0.49",
|
|
4
|
+
"description": "Control OpenAI Codex CLI from your iPhone and Android \u2014 real-time sync, approve file edits, send prompts by voice. Part of CodeVibe.",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"codevibe": {
|
|
7
7
|
"companionLauncher": "./libexec/companion-launcher"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"node": ">=22.0.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@quantiya/codevibe-core": "^2.0.
|
|
50
|
+
"@quantiya/codevibe-core": "^2.0.42",
|
|
51
51
|
"@quantiya/quorum-core": "^1.0.1",
|
|
52
52
|
"chokidar": "^4.0.0",
|
|
53
53
|
"dotenv": "^16.6.1",
|