pi-codex-marketplace 0.2.0 → 0.3.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 +8 -8
- package/extensions/pi/git-registration.ts +10 -2
- package/extensions/pi/journal.ts +4 -1
- package/extensions/pi/registration.ts +7 -2
- package/extensions/pi/transaction-sheet.ts +4 -0
- package/extensions/pi/ui-strings.ts +12 -0
- package/package.json +3 -2
- package/src/bridge-state/migrate.ts +15 -0
- package/src/bridge-state/types.ts +11 -1
- package/src/cache/source-cache.ts +2 -1
- package/src/compatibility/profile.ts +295 -145
- package/src/installation/inspection.ts +62 -11
- package/src/lifecycle/rebind.ts +9 -0
- package/src/lifecycle/refresh.ts +279 -111
- package/src/lifecycle/update.ts +17 -0
- package/src/projection/exposure.ts +41 -9
- package/src/registration/budget.ts +3 -3
- package/src/registration/catalog.ts +35 -1
- package/src/registration/claude-catalog.ts +359 -0
- package/src/registration/entry-acquisition.ts +854 -0
- package/src/registration/findings.ts +6 -0
- package/src/registration/flow.ts +95 -12
- package/src/registration/format.ts +63 -0
- package/src/registration/git-flow.ts +76 -12
- package/src/registration/index.ts +2 -0
- package/src/registration/receipt.ts +11 -0
- package/src/registration/source-key.ts +2 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-codex-marketplace
|
|
2
2
|
|
|
3
|
-
Bridge Package for Codex Marketplace compatibility in Pi (`Pi 0.84.2`).
|
|
3
|
+
Bridge Package for Codex and Claude Marketplace compatibility in Pi (`Pi 0.84.2`).
|
|
4
4
|
|
|
5
5
|
> **One-line:** `pi install npm:pi-codex-marketplace` → `/codex-marketplace` opens a responsive Bridge Ledger with Global authority, guarded lifecycle transactions, and three-orthogonal Receipt reporting.
|
|
6
6
|
|
|
@@ -42,9 +42,9 @@ Every mutation uses the shared transaction sequence **`Intent → Validation →
|
|
|
42
42
|
|
|
43
43
|
The Ledger exposes these existing capabilities without changing their lifecycle semantics:
|
|
44
44
|
|
|
45
|
-
- **檢視 Global 分區** — single `Global Scope` (`{getAgentDir()}/codex-marketplace/state.json`) workspace. Shows `schemaVersion` (
|
|
46
|
-
- **註冊本地 / Git Marketplace…** — local or Git Marketplace Source Registration. Local uses canonical real path; Git uses Canonical Git Locator (credential-free) + Normalized Git Selector (`default` / `branch refs/heads/*` / `tag refs/tags/*` / `commit` lower 40/64 hex) → Resolved Revision. Validation Disclosure shows source, Marketplace name, State Revision, Validation Snapshot fingerprint, entry outcomes, findings summary. **Registration Confirmation** is snapshot+revision bound, Default No. Blocking Findings (Contained Path / Contained Symlink / Budget / duplicate Source Key / locator/selector trust) block the attempt; concurrent attempt is blocked by Attempt Fence (`FENCE-01`); changed State Revision or snapshot yields `Rejected as Stale` (`STALE-01/02`).
|
|
47
|
-
- **安裝 Compatible Plugin…** — the Plugins ledger is keyed directly by stable **Marketplace Entry ID** (`/plugins/<序號>`), classification, skills, Invocation Policies, and resources; each compatible row exposes `Install Disabled` and `Install and Enable` without reopening a display-label Entry selector. Unavailable rows remain visible with their exact reason (unsupported source kind / parse failure / Invalid / Incompatible / Plugin ID collision). Compatibility Profile
|
|
45
|
+
- **檢視 Global 分區** — single `Global Scope` (`{getAgentDir()}/codex-marketplace/state.json`) workspace. Shows `schemaVersion` (v3), `stateRevision`, registrations/installations, empty-state guidance, and provenance notes.
|
|
46
|
+
- **註冊本地 / Git Marketplace…** — local or Git Marketplace Source Registration supporting dual Marketplace Formats (`codex` via `.agents/plugins/marketplace.json` and `claude` via `.claude-plugin/marketplace.json`, with codex precedence when both exist; missing both fails closed as `CATALOG_MISSING`). The derived `format` is fixed to the Registration record. Local uses canonical real path; Git uses Canonical Git Locator (credential-free) + Normalized Git Selector (`default` / `branch refs/heads/*` / `tag refs/tags/*` / `commit` lower 40/64 hex) → Resolved Revision. Validation Disclosure shows source, Marketplace name, Marketplace Format, State Revision, Validation Snapshot fingerprint, entry outcomes, findings summary. **Registration Confirmation** is snapshot+revision bound, Default No. Blocking Findings (Contained Path / Contained Symlink / Budget / duplicate Source Key / locator/selector trust) block the attempt; concurrent attempt is blocked by Attempt Fence (`FENCE-01`); changed State Revision or snapshot yields `Rejected as Stale` (`STALE-01/02`).
|
|
47
|
+
- **安裝 Compatible Plugin…** — the Plugins ledger is keyed directly by stable **Marketplace Entry ID** (`/plugins/<序號>`), classification, skills, Invocation Policies, and resources; each compatible row exposes `Install Disabled` and `Install and Enable` without reopening a display-label Entry selector. Unavailable rows remain visible with their exact reason (unsupported source kind / entry-defined strict:false / parse failure / Invalid / Incompatible / Plugin ID collision). Compatibility Profile v2 classifies every Plugin atomically across Codex and Claude manifests as Compatible / Incompatible / Invalid; collision never changes classification. `Install Disabled` persists provenance without Activation Confirmation; `Install and Enable` discloses exact Plugin, skill list, Skill Resources, Invocation Policies, `Pi → Global` precedence and findings, then requires a separate **Default No** Activation Confirmation (bound to same snapshot+revision). Re-enabling a disabled Installation repeats validation and confirmation; disabling preserves its Installation ID.
|
|
48
48
|
- **管理已安裝 Plugin(Enable / Disable)…** — toggle Installation State; disabling preserves provenance, enabling re-validates under current profile/ruleset/budget.
|
|
49
49
|
- **檢視 Effective State 與 Projected Skills…** — read-time derived Effective State (Global Scope enabled installations) and Projected Skills with Runtime Skill Collision resolution (`Pi → Global Scope` exact name layering: pre-existing Pi skills reserve their names; non-colliding Bridge skills are projected; colliding Bridge skills within the same layer are unavailable). Whole-Plugin Blocking Findings block the Plugin; collision affects only that skill; `Available` is established only by independent host evidence (`AVAIL-01`).
|
|
50
50
|
- **Refresh / 更新 Registration… (Marketplace Refresh → Update Candidate → Update Plan Checklist → Apply Update)** — Refresh is non-mutating and produces an Update Candidate when the validated source state differs (Plugin version alone does not; full-commit Git selector ref movement alone does not). Update Plan Checklist requires fresh **Registration Confirmation** + one explicit outcome per Installation (`update` / `disable` / `remove`, with `update` only when a Compatible candidate exists) + **Activation Confirmation** per enabled installation that remains enabled. Commit is a single atomic Lifecycle Operation replacing the Registration's Validation Snapshot and applying every disclosed consequence without mixing revisions.
|
|
@@ -61,9 +61,9 @@ Bridge State is the sole authority, stored in a **single Global Scope document**
|
|
|
61
61
|
|
|
62
62
|
```jsonc
|
|
63
63
|
{
|
|
64
|
-
"schemaVersion":
|
|
64
|
+
"schemaVersion": 3,
|
|
65
65
|
"stateRevision": "1", // opaque monotonic (string "0" -> "1" -> "2" ...)
|
|
66
|
-
"registrations": [], // immutable Registration ID = UUIDv4
|
|
66
|
+
"registrations": [], // immutable Registration ID = UUIDv4, with format: "codex" | "claude"
|
|
67
67
|
"installations": [] // Installed Plugins (enabled/disabled), each bound to a Validation Snapshot
|
|
68
68
|
}
|
|
69
69
|
```
|
|
@@ -73,7 +73,7 @@ Only authoritative fields are persisted. `Effective State`, catalogs, compatibil
|
|
|
73
73
|
- `State Revision` increments monotonically under file lock; commit is CAS-guarded by `expectedStateRevision` when supplied.
|
|
74
74
|
- Writes are **atomic**: `write-to-temp → fsync → rename` + `fsync` parent dir + **file lock** (`.lock` sibling) + **WAL** (`state.json.wal`) + **read-after-verify**.
|
|
75
75
|
- Cross-process concurrency is safe: lock serializes RMW; rename prevents torn reads; per-fingerprint `flock` guards Source Cache fetches with `p50 <200ms` on cache hit.
|
|
76
|
-
- **WAL migration** (`src/bridge-state/migrate.ts`): supported forward migrations are applied atomically via WAL (`state.json.wal` fsynced before commit, replayed on the next read after a crash, cleaned after commit success). Migrations are **non-waivable, opt-in per version**, require no implicit activation, and preserve the active recovery chain. Schema v1 → v2 migration strips legacy `scopeOverrides` (non-empty overrides generate a non-blocking `MIGRATE-01` diagnostic finding without fail-closed) and normalizes Installation IDs (stripping legacy `global/` prefix). Unknown/older-without-path and newer (`schemaVersion > CURRENT_SCHEMA_VERSION`) versions are treated as **incompatible** — fail-closed, no auto-migration, no rollback. **Downgrade never writes back** (`isDowngradeAttempt` guard): a newer durable file is never overwritten by an older Bridge Package; the operator must update the package first.
|
|
76
|
+
- **WAL migration** (`src/bridge-state/migrate.ts`): supported forward migrations are applied atomically via WAL (`state.json.wal` fsynced before commit, replayed on the next read after a crash, cleaned after commit success). Migrations are **non-waivable, opt-in per version**, require no implicit activation, and preserve the active recovery chain. Schema v1 → v2 migration strips legacy `scopeOverrides` (non-empty overrides generate a non-blocking `MIGRATE-01` diagnostic finding without fail-closed) and normalizes Installation IDs (stripping legacy `global/` prefix). Schema v2 → v3 migration populates `format: "codex"` on existing registrations without data loss while newly written registrations store `format: "codex" | "claude"`. Unknown/older-without-path and newer (`schemaVersion > CURRENT_SCHEMA_VERSION`) versions are treated as **incompatible** — fail-closed, no auto-migration, no rollback. **Downgrade never writes back** (`isDowngradeAttempt` guard): a newer durable file is never overwritten by an older Bridge Package; the operator must update the package first.
|
|
77
77
|
- Corrupted / unknown `schemaVersion` → classified as **corrupted / incompatible (Persistence Indeterminate)**, **never auto-rollback** — fail-closed. `validateSchema` and `migrateForward` enforce the closed set `CORRUPTED_JSON` / `INVALID_SCHEMA` / `INCOMPATIBLE_SCHEMA_VERSION` / `UNKNOWN_OLD_VERSION` / `MIGRATION_FAILED`.
|
|
78
78
|
|
|
79
79
|
See `src/bridge-state/` for `store.ts`, `atomic.ts`, `schema.ts`, `migrate.ts`, `paths.ts`.
|
|
@@ -113,7 +113,7 @@ Every row is a **release blocker**: `v*` may not publish unless the full matrix
|
|
|
113
113
|
| E2E (highest seam — **TUI**) | pinned | macOS + Linux | 22.19.0 | **0.84.2** | full lifecycle (Register → Install Disabled / Install and Enable → Disable/Enable → Refresh → Update Plan Checklist → Apply Update / Rebind → Removal) with fence/cache/external observability |
|
|
114
114
|
| E2E (highest seam — **TUI**) | adversarial | macOS + Linux | 22.19.0 | **0.84.2** | collision (`Pi → Global`), cache (`offline exact fingerprint hit` vs `stale never success`) |
|
|
115
115
|
|
|
116
|
-
Fixtures: `tests/fixtures/synthetic
|
|
116
|
+
Fixtures: `tests/fixtures/synthetic/` (including synthetic Claude and mattpocock-shaped marketplace fixtures), `tests/fixtures/pinned/` (captured `SamWang32191/codex-plugins@98e78ca` snapshot + fingerprint manifest), `tests/fixtures/adversarial/` (path-escape / symlink-loop / budget-overflow / malformed frontmatter corpora). See `tests/acceptance/` for the matrix runner that enforces per-row gating (any row failure blocks publish).
|
|
117
117
|
|
|
118
118
|
Run locally:
|
|
119
119
|
|
|
@@ -14,9 +14,10 @@ import type { ExtensionCommandContext, ExtensionUIContext } from '@earendil-work
|
|
|
14
14
|
import {
|
|
15
15
|
preflightGitRegistration,
|
|
16
16
|
confirmGitRegistration,
|
|
17
|
+
type GitRegistrationFlowOptions,
|
|
17
18
|
} from '../../src/registration/git-flow.js';
|
|
18
19
|
import type { GitSelectorInput } from '../../src/registration/git-selector.js';
|
|
19
|
-
import { uiText } from './ui-strings.js';
|
|
20
|
+
import { marketplaceFormatText, uiText } from './ui-strings.js';
|
|
20
21
|
import {
|
|
21
22
|
fullValidationDisclosureLines,
|
|
22
23
|
reportOutcome,
|
|
@@ -36,8 +37,14 @@ async function transactionStep(
|
|
|
36
37
|
return false;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/**
|
|
41
|
+
* One interactive git registration flow invocation.
|
|
42
|
+
* The optional flow options thread the Acquisition Trust Base seams (executor / trust / cache)
|
|
43
|
+
* for tests and embedders; production callers rely on the real Git executor.
|
|
44
|
+
*/
|
|
39
45
|
export async function runGitRegistrationFlow(
|
|
40
46
|
ctx: ExtensionCommandContext,
|
|
47
|
+
flowOpts: Pick<GitRegistrationFlowOptions, 'executor' | 'trust' | 'cache'> = {},
|
|
41
48
|
): Promise<void> {
|
|
42
49
|
const ui: ExtensionUIContext = ctx.ui;
|
|
43
50
|
|
|
@@ -99,7 +106,7 @@ export async function runGitRegistrationFlow(
|
|
|
99
106
|
],
|
|
100
107
|
})) return;
|
|
101
108
|
|
|
102
|
-
const opts = {};
|
|
109
|
+
const opts: GitRegistrationFlowOptions = { ...flowOpts };
|
|
103
110
|
const res = await preflightGitRegistration(locator, selectorInput, opts);
|
|
104
111
|
if (!res.ok) {
|
|
105
112
|
await reportTerminalPreflightOutcome(ctx, res.outcome);
|
|
@@ -121,6 +128,7 @@ export async function runGitRegistrationFlow(
|
|
|
121
128
|
}),
|
|
122
129
|
uiText('reg.git.detail.resolvedRevision', { revision: quoteTerminalText(pf.resolvedRevision) }),
|
|
123
130
|
uiText('reg.detail.marketplace', { name: quoteTerminalText(pf.marketplaceName) }),
|
|
131
|
+
uiText('reg.detail.format', { format: marketplaceFormatText(pf.format) }),
|
|
124
132
|
uiText('reg.detail.entries', {
|
|
125
133
|
total: pf.catalog.entries.length,
|
|
126
134
|
locatable: pf.catalog.entries.filter((entry) => entry.available).length,
|
package/extensions/pi/journal.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { acquireAttemptFence } from '../../src/registration/fence.js';
|
|
|
18
18
|
import { blocking, CODE, notice, RULE, type ValidationFinding } from '../../src/registration/findings.js';
|
|
19
19
|
import { createReceipt, type AttemptReceipt } from '../../src/registration/receipt.js';
|
|
20
20
|
import { fullValidationDisclosureLines, reportOutcome } from './registration.js';
|
|
21
|
-
import { attemptSummaryText, uiText } from './ui-strings.js';
|
|
21
|
+
import { attemptSummaryText, marketplaceFormatText, uiText } from './ui-strings.js';
|
|
22
22
|
import { quoteTerminalText } from './terminal-presentation.js';
|
|
23
23
|
import { openTransactionSheet, type TransactionSheetModel } from './transaction-sheet.js';
|
|
24
24
|
|
|
@@ -478,6 +478,9 @@ export async function runReceiptJournalView(
|
|
|
478
478
|
expected: quote(rc.expectedStateRevision),
|
|
479
479
|
observed: quote(rc.observedStateRevision ?? rc.targetStateRevision ?? '?'),
|
|
480
480
|
}));
|
|
481
|
+
if (rc.marketplaceFormat) {
|
|
482
|
+
lines.push(' ' + uiText('journal.view.receipt.format', { format: marketplaceFormatText(rc.marketplaceFormat) }));
|
|
483
|
+
}
|
|
481
484
|
if (rc.recoversReceiptId) {
|
|
482
485
|
lines.push(' ' + uiText('journal.view.receipt.recovers', { receiptId: quote(rc.recoversReceiptId) }));
|
|
483
486
|
}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from '../../src/registration/flow.js';
|
|
18
18
|
import { sortFindings, type ValidationFinding } from '../../src/registration/findings.js';
|
|
19
19
|
import type { AttemptReceipt } from '../../src/registration/receipt.js';
|
|
20
|
-
import { attemptSummaryText, findingOutcomeText, uiText, verdictText } from './ui-strings.js';
|
|
20
|
+
import { attemptSummaryText, findingOutcomeText, marketplaceFormatText, uiText, verdictText } from './ui-strings.js';
|
|
21
21
|
import { quoteTerminalText } from './terminal-presentation.js';
|
|
22
22
|
import { openTransactionSheet, type TransactionSheetModel } from './transaction-sheet.js';
|
|
23
23
|
|
|
@@ -101,6 +101,7 @@ export async function runLocalRegistrationFlow(
|
|
|
101
101
|
uiText('reg.detail.registrationId', { id: quoteTerminalText(pf.registrationId) }),
|
|
102
102
|
uiText('reg.detail.source', { source: quoteTerminalText(pf.canonicalPath) }),
|
|
103
103
|
uiText('reg.detail.marketplace', { name: quoteTerminalText(pf.marketplaceName) }),
|
|
104
|
+
uiText('reg.detail.format', { format: marketplaceFormatText(pf.format) }),
|
|
104
105
|
uiText('reg.detail.entries', {
|
|
105
106
|
total: pf.catalog.entries.length,
|
|
106
107
|
locatable: pf.catalog.entries.filter((entry) => entry.available).length,
|
|
@@ -210,8 +211,12 @@ export async function reportOutcome(
|
|
|
210
211
|
validationSnapshot: rc.validationSnapshot,
|
|
211
212
|
receipt: rc,
|
|
212
213
|
});
|
|
214
|
+
const notifyMessage = rc.marketplaceFormat
|
|
215
|
+
? `${uiText('reg.outcome.notify', { summary: attemptSummaryText(rc.summary), receiptId: quoteTerminalText(rc.id) })} · ${
|
|
216
|
+
uiText('reg.outcome.notify.formatSuffix', { format: marketplaceFormatText(rc.marketplaceFormat) })}`
|
|
217
|
+
: uiText('reg.outcome.notify', { summary: attemptSummaryText(rc.summary), receiptId: quoteTerminalText(rc.id) });
|
|
213
218
|
ctx.ui.notify(
|
|
214
|
-
|
|
219
|
+
notifyMessage,
|
|
215
220
|
notifyType,
|
|
216
221
|
);
|
|
217
222
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
attemptSummaryGloss,
|
|
14
14
|
closedValue,
|
|
15
15
|
findingOutcomeText,
|
|
16
|
+
marketplaceFormatText,
|
|
16
17
|
recoveryActionGloss,
|
|
17
18
|
transactionStepLabel,
|
|
18
19
|
uiText,
|
|
@@ -98,6 +99,9 @@ function receiptAxes(receipt: AttemptReceipt, theme: TransactionSheetTheme): [st
|
|
|
98
99
|
field(theme, uiText('sheet.field.kind'), receipt.kind),
|
|
99
100
|
field(theme, uiText('sheet.field.operation'), receipt.operation),
|
|
100
101
|
field(theme, uiText('sheet.field.trigger'), receipt.trigger),
|
|
102
|
+
...(receipt.marketplaceFormat === undefined
|
|
103
|
+
? []
|
|
104
|
+
: [field(theme, uiText('sheet.field.marketplaceFormat'), marketplaceFormatText(receipt.marketplaceFormat))]),
|
|
101
105
|
];
|
|
102
106
|
return [durable, findings, runtime];
|
|
103
107
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { AttemptSummary, RecoveryAction } from '../../src/registration/receipt.js';
|
|
18
|
+
import type { MarketplaceFormat } from '../../src/bridge-state/types.js';
|
|
18
19
|
import type { ValidationFinding } from '../../src/registration/findings.js';
|
|
19
20
|
import type { TransactionStep } from './transaction-sheet.js';
|
|
20
21
|
|
|
@@ -52,6 +53,8 @@ const zhTW = {
|
|
|
52
53
|
|
|
53
54
|
// --- closed-value glosses (canonical always rendered first) ---------------
|
|
54
55
|
|
|
56
|
+
'format.gloss.codex': 'Codex 格式',
|
|
57
|
+
'format.gloss.claude': 'Claude 格式',
|
|
55
58
|
'summary.gloss.Completed': '完成',
|
|
56
59
|
'summary.gloss.Completed with diagnostics': '完成但有診斷',
|
|
57
60
|
'summary.gloss.Declined': '已婉拒',
|
|
@@ -114,6 +117,7 @@ const zhTW = {
|
|
|
114
117
|
'sheet.field.kind': '種類',
|
|
115
118
|
'sheet.field.operation': '操作',
|
|
116
119
|
'sheet.field.trigger': '觸發',
|
|
120
|
+
'sheet.field.marketplaceFormat': 'Marketplace Format',
|
|
117
121
|
'sheet.attemptSummary': 'Attempt Summary',
|
|
118
122
|
'sheet.recoveryActions': 'Recovery Actions(復原動作)',
|
|
119
123
|
'sheet.keys': 'Enter:繼續|Esc/q/Ctrl-C:取消',
|
|
@@ -225,6 +229,7 @@ const zhTW = {
|
|
|
225
229
|
'reg.detail.registrationId': 'Registration ID {id}',
|
|
226
230
|
'reg.detail.source': '來源 {source}',
|
|
227
231
|
'reg.detail.marketplace': 'Marketplace {name}',
|
|
232
|
+
'reg.detail.format': 'Marketplace Format {format}',
|
|
228
233
|
'reg.detail.entries': 'Entries {total}(可定位 {locatable} / 無法定位 {unavailable})',
|
|
229
234
|
'reg.detail.profile': 'Compatibility Profile {profile}',
|
|
230
235
|
'reg.detail.ruleset': 'Validation Ruleset {ruleset}',
|
|
@@ -263,6 +268,7 @@ const zhTW = {
|
|
|
263
268
|
'reg.commit.persist': '寫入 Registration ID {id}',
|
|
264
269
|
'reg.commit.authority': '於 {scope} 以 State Revision {revision} 寫入授權文件',
|
|
265
270
|
'reg.outcome.notify': 'Attempt Summary:{summary} · Receipt {receiptId}',
|
|
271
|
+
'reg.outcome.notify.formatSuffix': 'Marketplace Format {format}',
|
|
266
272
|
|
|
267
273
|
// --- installation.ts ---------------------------------------------------------
|
|
268
274
|
'inst.select.registered': '選擇已註冊 Marketplace',
|
|
@@ -390,6 +396,7 @@ const zhTW = {
|
|
|
390
396
|
'journal.view.recent.empty': '(Journal 為空)',
|
|
391
397
|
'journal.view.receipt.summary': '摘要:{summary}|持久:{durable}|運行時:{runtime}',
|
|
392
398
|
'journal.view.receipt.revision': 'State Revision:{expected} → {observed}',
|
|
399
|
+
'journal.view.receipt.format': 'Marketplace Format:{format}',
|
|
393
400
|
'journal.view.receipt.recovers': '復原對象:{receiptId}',
|
|
394
401
|
'journal.view.receipt.findings': 'Findings:{findings}',
|
|
395
402
|
'journal.notFound': 'Receipt Journal 找不到精確 Receipt ID {receiptId}。',
|
|
@@ -560,6 +567,11 @@ export function recoveryActionGloss(action: RecoveryAction): string {
|
|
|
560
567
|
return glossId ? uiText(glossId) : '';
|
|
561
568
|
}
|
|
562
569
|
|
|
570
|
+
/** Marketplace Format closed value presented canonical-first with its zh_TW gloss. */
|
|
571
|
+
export function marketplaceFormatText(format: MarketplaceFormat): string {
|
|
572
|
+
return closedValue(format, uiText(format === 'claude' ? 'format.gloss.claude' : 'format.gloss.codex'));
|
|
573
|
+
}
|
|
574
|
+
|
|
563
575
|
const STEP_GLOSS: Record<TransactionStep, MessageId> = {
|
|
564
576
|
Intent: 'step.gloss.Intent',
|
|
565
577
|
Validation: 'step.gloss.Validation',
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-codex-marketplace",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Bridge Package for Codex Marketplace compatibility in Pi — Global Bridge State, atomic persistence, and /codex-marketplace TUI",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Bridge Package for Codex and Claude Marketplace compatibility in Pi — Global Bridge State, atomic persistence, and /codex-marketplace TUI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
8
|
"pi",
|
|
9
9
|
"pi-coding-agent",
|
|
10
10
|
"codex",
|
|
11
|
+
"claude",
|
|
11
12
|
"marketplace"
|
|
12
13
|
],
|
|
13
14
|
"license": "MIT",
|
|
@@ -48,6 +48,21 @@ const MIGRATIONS: Record<number, Migrator> = {
|
|
|
48
48
|
}
|
|
49
49
|
return { state: cloned, findings };
|
|
50
50
|
},
|
|
51
|
+
2: (state: any) => {
|
|
52
|
+
// Issue #44 — registration `format` attribute (codex | claude) persistence foundation.
|
|
53
|
+
// Backfill a lossless 'codex' default onto every Registration missing a valid format; any
|
|
54
|
+
// already-declared 'codex' | 'claude' value is preserved untouched (no content loss).
|
|
55
|
+
const cloned = structuredClone(state);
|
|
56
|
+
if (Array.isArray(cloned.registrations)) {
|
|
57
|
+
cloned.registrations = cloned.registrations.map((reg: any) => {
|
|
58
|
+
if (typeof reg !== 'object' || reg === null) return reg;
|
|
59
|
+
if (reg.format === 'codex' || reg.format === 'claude') return reg;
|
|
60
|
+
return { ...reg, format: 'codex' };
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
cloned.schemaVersion = 3;
|
|
64
|
+
return { state: cloned };
|
|
65
|
+
},
|
|
51
66
|
};
|
|
52
67
|
|
|
53
68
|
export interface MigrationResult {
|
|
@@ -11,15 +11,23 @@
|
|
|
11
11
|
|
|
12
12
|
import type { ValidationFinding } from '../registration/findings.js';
|
|
13
13
|
|
|
14
|
-
export const CURRENT_SCHEMA_VERSION =
|
|
14
|
+
export const CURRENT_SCHEMA_VERSION = 3;
|
|
15
15
|
|
|
16
16
|
/** Opaque monotonic identifier. Stored as decimal string, incremented on each successful commit. */
|
|
17
17
|
export type StateRevision = string;
|
|
18
18
|
|
|
19
|
+
/** Marketplace Format — codex | claude (persistence foundation, issue #44).
|
|
20
|
+
* Format detection / claude parsing is delivered by later tickets; the persistence
|
|
21
|
+
* layer carries the value through and legacy registrations are backfilled to 'codex'
|
|
22
|
+
* by the v2→v3 migration. Absence (pre-v3) always reads back as 'codex' after migration. */
|
|
23
|
+
export type MarketplaceFormat = 'codex' | 'claude';
|
|
24
|
+
|
|
19
25
|
/** Minimal shape for scaffold — later tickets extend with Source Key, Validation Snapshot, etc. */
|
|
20
26
|
export interface Registration {
|
|
21
27
|
/** Immutable lowercase UUIDv4, allocated before preflight */
|
|
22
28
|
id: string;
|
|
29
|
+
/** Marketplace Format carried by this Registration. v2→v3 migration backfills 'codex' for legacy records. */
|
|
30
|
+
format?: MarketplaceFormat;
|
|
23
31
|
/** Human-readable alias, derived from marketplace name */
|
|
24
32
|
alias?: string;
|
|
25
33
|
/** Declared marketplace name (kebab-case) */
|
|
@@ -54,6 +62,8 @@ export interface Registration {
|
|
|
54
62
|
resolvedRevision?: string;
|
|
55
63
|
/** Validation Snapshot fingerprint bound to Registration Confirmation */
|
|
56
64
|
validationSnapshot?: string;
|
|
65
|
+
/** Per-entry Validation Snapshot fingerprints bound to Registration Confirmation (issue #50). */
|
|
66
|
+
entrySnapshots?: Record<string, string>;
|
|
57
67
|
/** Bound Compatibility Profile / Ruleset / Budget ids at confirmation time */
|
|
58
68
|
snapshotBinds?: { profile?: string; ruleset?: string; budget?: string };
|
|
59
69
|
}
|
|
@@ -59,6 +59,7 @@ export interface CacheIndexRecord {
|
|
|
59
59
|
|
|
60
60
|
export interface PendingUpdateRecord {
|
|
61
61
|
registrationId: string;
|
|
62
|
+
entryId?: string;
|
|
62
63
|
fingerprint: string;
|
|
63
64
|
recordedAtMs: number;
|
|
64
65
|
}
|
|
@@ -254,7 +255,7 @@ export class SourceCache {
|
|
|
254
255
|
// ---- Pending Update Candidate registry ---------------------------------
|
|
255
256
|
|
|
256
257
|
recordPendingUpdate(rec: Omit<PendingUpdateRecord, 'recordedAtMs'>): void {
|
|
257
|
-
const records = this.pendingUpdates().filter((r) => !(r.registrationId === rec.registrationId));
|
|
258
|
+
const records = this.pendingUpdates().filter((r) => !(r.registrationId === rec.registrationId && r.entryId === rec.entryId));
|
|
258
259
|
records.push({ ...rec, recordedAtMs: this.now() });
|
|
259
260
|
atomicWriteFile(getCachePendingPath(this.root), JSON.stringify(records));
|
|
260
261
|
}
|