diff --git a/README.md b/README.md index f00c285..38a03ac 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ SwiftASB helps Swift apps work with the local Codex app-server without making ap ### Status -SwiftASB has a supported v1 public API for the core local Codex app-server lifecycle. `v1.2.0` is the current released baseline. +SwiftASB has a supported v1 public API for the core local Codex app-server lifecycle. `v1.2.1` is the current released baseline. ### What This Project Is @@ -33,9 +33,9 @@ Add SwiftASB from the GitHub package URL: https://github.com/gaelic-ghost/SwiftASB -Use release `v1.2.0` or newer unless your project intentionally pins an older version. +Use release `v1.2.1` or newer unless your project intentionally pins an older version. -You also need a local Codex CLI installation with app-server support. SwiftASB looks for `codex` in the usual command-line locations, and apps can provide an exact executable path when they need stricter control. +You also need a local Codex CLI installation with app-server support. SwiftASB currently reviews against the `0.130.x` Codex CLI app-server schema window, looks for `codex` in the usual command-line locations, and apps can provide an exact executable path when they need stricter control. For copy-pasteable startup code, open the DocC getting-started guide: diff --git a/ROADMAP.md b/ROADMAP.md index c8333ea..1df697e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -41,7 +41,7 @@ | --- | --- | --- | | Bundled schema-driven wire generation | `Shipped internally` | `scripts/generate-wire-types.sh` derives from the bundled v2 schema, patches dynamic JSON to `CodexWireJSONValue`, and validates the staged Swift output. | | Promoted generated v2 wire snapshot | `Shipped internally` | `Sources/SwiftASB/Generated/CodexWire/Latest/` now contains a wider lifecycle batch covering bootstrap, stored and loaded thread reads, filesystem reads and watches, config reads, extension inventory, thread goals, and many thread, turn, item, reasoning, and tool-progress notifications, alongside the hand-owned `CodexWireInitializeResponse` shim. | -| Codex CLI schema review | `Shipped / ongoing` | The current reviewed compatibility window is `codex-cli 0.129.x`; the v0.129 schema families have been classified for the current boundary, and `scripts/dump-codex-schemas.sh` makes future versioned experimental dumps repeatable by default. Future Codex CLI schema families still need public/observable/internal decisions before promotion. | +| Codex CLI schema review | `Shipped / ongoing` | The current reviewed compatibility window is `codex-cli 0.130.x`; the v0.130 schema families have been classified for the current boundary, and `scripts/dump-codex-schemas.sh` makes future versioned experimental dumps repeatable by default. Future Codex CLI schema families still need public/observable/internal decisions before promotion. | | Stdio subprocess transport | `Shipped internally` | The transport launches `codex app-server --listen stdio://`, frames newline-delimited JSON, correlates request IDs, and captures stderr for diagnostics. | | Raw server-event fanout | `Shipped internally` | Transport can stream raw JSON-RPC notifications and server requests to higher layers. | | Typed protocol request encoding | `Shipped internally` | `initialize`, `initialized`, core thread and turn methods, archive-state actions, filesystem reads and watches, config reads, app/skill/plugin/collaboration-mode inventory, model/MCP/hook reads, MCP resource reads, and thread-goal methods are encoded through the protocol layer. | @@ -76,14 +76,14 @@ | Non-UI local history-reading helpers | `Partially shipped` | `CodexThread` now exposes a lightweight `HistoryWindow` page shape for recent local history, older or newer local windows around a known boundary turn id, centered `windowAroundTurn(...)` reads, centered `windowAroundItem(...)` reads, direct `ClosedTurn` reads for one turn, and convenience array helpers over those same windows. This gives non-UI callers an intentional path into the local history store without binding a UI-oriented observable, while still deferring a broader public cursor model, transcript search surface, and richer history-query helpers. | | Public API curation | `Shipped / ongoing` | The source-organization pass has split app-wide model, MCP, thread-management, history, and observable companion values into focused public files while preserving `CodexAppServer`, `CodexThread`, and `CodexTurnHandle` as the three real owners. The connected public-surface review closed the v1 ownership model; post-v1 curation now includes app-server-owned project identity and thread source facts for launcher UI without exposing generated wire models. Future curation should stay tied to concrete public API additions. | | DocC documentation | `Shipped / ongoing` | `Sources/SwiftASB/SwiftASB.docc/` contains a package landing page, public-handle extension pages, conceptual articles for app-wide capabilities, interactive lifecycle, thread management, history/observable companions, generated-wire boundary notes, and copy-pasteable walkthroughs for startup, progress/approval handling, diagnostics/history, and SwiftUI observable companions. The catalog is validated through Xcode `docbuild`; future work is ordinary stale-link, prose, and symbol-comment refinement as the public API grows. | -| Swift Package Index readiness | `Shipped` | `.spi.yml` declares `SwiftASB` as the documentation target, and Swift Package Index lists `gaelic-ghost/SwiftASB` with a documentation link, compatibility/build results, Package ID `9B5839D9-9551-473F-A939-841534A3FC55`, and a 2026-05-06 update timestamp for the latest confirmed indexed release. Recheck SPI after the `v1.2.0` tag is published. | +| Swift Package Index readiness | `Shipped` | `.spi.yml` declares `SwiftASB` as the documentation target, and Swift Package Index lists `gaelic-ghost/SwiftASB` with a documentation link, compatibility/build results, Package ID `9B5839D9-9551-473F-A939-841534A3FC55`, and a 2026-05-06 update timestamp for the latest confirmed indexed release. Recheck SPI after the `v1.2.1` tag is published. | | Contributor documentation split | `Shipped` | `README.md` is now focused on Swift and SwiftUI package users, while `CONTRIBUTING.md` owns contributor setup, validation, DocC, live-test flags, generated-wire refresh, and PR expectations. | | `CodexTurnHandle` live observable companion | `Partially shipped` | `CodexTurnHandle` owns a live `Minimap` companion that is attached when the handle is created and maintains current-state call snapshots for command, file-edit, dynamic-tool, collab-tool, and MCP item activity. It also now mirrors whether thread context compaction is active for the turn and supports explicit `complete()` handoff into a caller-owned sealed turn snapshot. | | Additional turn event mapping | `Partially shipped` | The public event layer covers the current interactive lifecycle plus the item-start and item-complete events needed for observable call-state mirrors. Raw command-output and file-change-output deltas now stay internal as transport detail but drive the shipped `RecentCommands` and `RecentFiles` companions, and streamed or patch-updated payloads are preserved when later completed snapshots are thinner. Richer MCP-progress detail still remains internal, while warning, guardian-warning, config-warning, deprecation, MCP-server-status, remote-control-status, model-reroute, and model-verification notifications now surface through hand-owned diagnostic events. | | Server request / approval handling | `Partially shipped` | Typed approval and elicitation request models now surface on thread and turn event streams, explicit response APIs exist on `CodexThread` and `CodexTurnHandle`, request resolution is tracked by JSON-RPC request id, and deterministic command-approval plus permissions-approval completion are covered through the real app-server with a mock Responses provider. Diagnostics are now separated from control flows: passive warning/model/guardian signals are public diagnostics, while guardian denied-action approval remains internal until SwiftASB owns a stable request/response model for it. | | Internal thread history persistence | `Partially shipped` | The package now has a Core Data-backed `ThreadHistoryStore` that persists live-built thread and turn history, hydrates stored turns from `thread/read`, `thread/resume`, `thread/fork`, and `thread/turns/list`, seeds previously unknown local threads from paged history, widens persisted turn identity to stay thread-scoped across forks, and records explicit fork lineage while preserving conservative reconciliation that keeps richer local detail when upstream stored history is thinner. Public history paging/search helpers and archive-retention policy are still open. | | Convenience run API | `Not started` | No `run(...)` or one-shot text convenience layer yet. | -| Binary discovery and compatibility policy | `Partially shipped` | Explicit binary override exists, the docs now define a current-reviewed Codex CLI support window of `0.129.x`, transport startup checks PATH, common Homebrew paths, and the npm global prefix on macOS, and `cliExecutableDiagnostics()` now exposes the resolved binary, version string, and documented support-window assessment. Any further diagnostics work is now expansion rather than a missing baseline surface. | +| Binary discovery and compatibility policy | `Partially shipped` | Explicit binary override exists, the docs now define a current-reviewed Codex CLI support window of `0.130.x`, transport startup checks PATH, common Homebrew paths, and the npm global prefix on macOS, and `cliExecutableDiagnostics()` now exposes the resolved binary, version string, and documented support-window assessment. Any further diagnostics work is now expansion rather than a missing baseline surface. | | README-level consumer docs | `Shipped / ongoing` | The README covers installation, runtime assumptions, first-use examples, the supported lifecycle, SwiftUI companion surfaces, and the current Codex CLI compatibility window. Future README work should track new public API additions rather than prerelease readiness. | | Agent workflow guidance | `Shipped / ongoing` | SwiftASB-specific Codex guidance now ships through `socket`'s [`swiftasb-skills`](https://github.com/gaelic-ghost/socket/tree/main/plugins/swiftasb-skills) plugin, with skills for explaining SwiftASB, choosing an integration shape, building SwiftUI-facing app state, and diagnosing integration failures. This repo now points package users and maintainers at that plugin while keeping SwiftASB source, DocC, tests, generated-wire review, and release notes here as the package source of truth. | | End-to-end subprocess integration tests | `Shipped / ongoing` | The package includes opt-in live Codex CLI integration tests with temp workspaces and time limits, including raw transport startup, single-turn completion, cross-thread completion, app-wide model/MCP/hook diagnostics snapshots, thread-name mutation, stored-history materialization, same-thread concurrency probing, deterministic command and permissions approvals through a mock Responses provider, a best-effort prompt-driven approval-path probe, a disposable live rollback scenario, and a multi-turn file-mutation scenario that creates, edits, and deletes files through the real CLI. The umbrella runner is `scripts/run-live-codex-integration-tests.sh`; it defaults to the release-gate set and exposes focused modes for smoke, transport, capability, thread, turn, approval, file-scenario, rollback, same-thread, and all opt-in live tests. Stored-history materialization remains in focused `thread`/`all` runs instead of the release-gate smoke group because the live app-server can delay history materialization. | @@ -104,7 +104,7 @@ The next meaningful package step is no longer proving the v1 interactive lifecycle, SPI visibility, basic history hydration, first-pass reconciliation, or command-approval completion. Those slices now exist and shipped in the -`v1.2.0` baseline. +`v1.2.1` baseline. The next meaningful work is to widen the reviewed app-server schema and protocol coverage before adding more public query descriptors. Descriptors should compile @@ -207,7 +207,7 @@ That means the current priority order is: ## V1 Readiness Checklist -This checklist records the work that made `SwiftASB` ready for the `v1.2.0` +This checklist records the work that made `SwiftASB` ready for the `v1.2.1` tag. The goal was not to make every possible app-server feature public before v1. The goal was to make the supported lifecycle honest, durable, well documented, and intentionally shaped. @@ -390,8 +390,8 @@ workflow earns them in a later feature release. ### Documentation And Examples -- [x] Update stale release references after the `v1.2.0` release. - Decision: README now names `v1.2.0` as the current released baseline and no +- [x] Update stale release references after the `v1.2.1` release. + Decision: README now names `v1.2.1` as the current released baseline and no longer describes the package as early development. - [x] Finish DocC symbol comments for the supported lifecycle, not just the conceptual articles. @@ -488,6 +488,15 @@ workflow earns them in a later feature release. Request-side `serviceTier` stays public as the existing hand-owned `CodexAppServer.ServiceTier` while the internal wire now carries open string values. +- [x] Classify the Codex CLI `v0.130.0` schema diff before promotion. + Decision: remove the generated device-key request families from the promoted + boundary, keep plugin-sharing and guardian-review timing additions internal, + preserve `skills/list` source compatibility while rejecting the removed + per-cwd extra user roots option with a descriptive error, expose the new + `thread/turns/items/list` page as a hand-owned low-level stored item API, let + `thread/turns/list` request an explicit item-detail view through + `CodexAppServer.TurnItemsView`, and promote plugin detail hook summaries as + read-only extension inventory. - [x] Confirm generated wire stays internal in docs, source organization, and public examples. Decision: generated wire remains internal scaffolding. Public docs and README @@ -497,14 +506,14 @@ workflow earns them in a later feature release. declarations expose `CodexWire...` names. - [x] Re-run schema drift fixture coverage after any promoted generated-wire refresh. - Progress: `swift test` has been rerun after the v0.129 promoted-wire refresh + Progress: `swift test` has been rerun after the v0.130 promoted-wire refresh and exercises the v0.128 permission-profile fixtures, request/response - envelopes, notification fixtures, public conversion paths, and v0.129 + envelopes, notification fixtures, public conversion paths, and v0.130 generated-wire compatibility for new thread and item lifecycle fields. - [x] Decide whether v1 should support only the latest documented rolling window or whether a shorter first-v1 compatibility promise is more honest. Decision: use a narrow latest-reviewed-minor support window, currently - `0.129.x`, and widen deliberately after generated-wire and public API review + `0.130.x`, and widen deliberately after generated-wire and public API review catches up with later Codex CLI releases. @@ -567,10 +576,10 @@ workflow earns them in a later feature release. the `release/v1.0.0` branch on 2026-05-02 and on the `release/v1.0.1-prep` branch on 2026-05-02. - [x] Decide whether another targeted `v0.9.x` patch release is needed before - `v1.2.0`, or whether the remaining work should go straight into the v1 + `v1.2.1`, or whether the remaining work should go straight into the v1 release branch. Decision: no additional `v0.9.x` patch is needed. The remaining work should go - straight into the `v1.2.0` release branch. + straight into the `v1.2.1` release branch. - [x] Prepare v1 release notes with explicit sections for public surface, intentionally internal surfaces, compatibility window, migration notes, validation performed, and known post-v1 work. @@ -614,7 +623,7 @@ workflow earns them in a later feature release. #### Compatibility Window - The compatibility promise is intentionally narrow while app-server schema is - moving quickly: reviewed support for Codex CLI `0.129.x`. + moving quickly: reviewed support for Codex CLI `0.130.x`. - SwiftASB discovers `codex` from an explicit executable URL, `PATH`, common Homebrew locations, or the npm global prefix, and exposes startup diagnostics through `cliExecutableDiagnostics()`. @@ -624,7 +633,7 @@ workflow earns them in a later feature release. #### Migration Notes - Existing `v0.9.x` consumers should update the SwiftPM dependency to - `from: "1.2.0"` once the tag is published. + `from: "1.2.1"` once the tag is published. - The v1 API surface has removed stale pre-v1 compatibility shims and phantom fields that no longer exist in the reviewed `v0.128.0` schema. - Same-thread overlapping turns are rejected client-side with @@ -649,7 +658,7 @@ workflow earns them in a later feature release. - Keep an eye on future Swift Package Index builds after compatibility-window or DocC changes; the `v1.1.1` listing and documentation link are live, and - `v1.2.0` should be rechecked after the patch tag is indexed. + `v1.2.1` should be rechecked after the patch tag is indexed. - Add broader live server-request coverage for permissions and MCP elicitation if those become stronger public runtime guarantees. - Continue tuning recent companion cache calibration, richer file previews, @@ -933,12 +942,12 @@ not as the current maintainer priority. - Centered local history reads through `windowAroundTurn(...)` and `windowAroundItem(...)` before any broader cursor or transcript-search contract. -- A `v0.129.0` experimental schema compatibility pass has refreshed the staging - generator, updated the Codex CLI compatibility window, kept plugin-sharing, - process-control, Windows-sandbox-readiness, thread-source, and turn-items-view - schema families internal, made new thread/item required fields tolerant of - v0.128 payloads, and promoted compact hook event names through the existing - hook metadata and dashboard surfaces. +- A `v0.130.0` experimental schema compatibility pass has refreshed the staging + generator, updated the Codex CLI compatibility window, kept plugin-sharing + and guardian-review timing internal, promoted stored turn-item paging and + explicit turn-item view selection, rejected the removed per-cwd extra skill + roots request option with a descriptive error, and exposed plugin detail hook + summaries through the extension inventory surface. - API curation and DocC docs good enough that a Swift consumer can understand the supported package surface without reading maintainer notes, including walkthroughs for the primary v1 lifecycle jobs. @@ -1224,7 +1233,7 @@ Completed - [x] Add version-compatibility policy notes for the local Codex binary. - [x] Refresh the compatibility window and promoted generated snapshot against the current `v0.124.0` schema dump once the added endpoint, notification, and field families have been classified. - [x] Curate the public API before v1 by splitting large source files along existing responsibility boundaries where still helpful, tightening public names/defaults, and finishing targeted source-level symbol documentation for the supported lifecycle. - Decision: completed for the `v1.2.0` boundary through the public API audit, + Decision: completed for the `v1.2.1` boundary through the public API audit, symbol inventory, source-comment pass, and focused public file organization. - [x] Add the first DocC documentation catalog before v1, including a package landing page, public-handle topic groups, and conceptual articles for the interactive lifecycle, history companions, and generated-wire boundary. - [x] Validate the DocC catalog through Xcode `docbuild` and document the maintainer command. diff --git a/Sources/SwiftASB/Generated/CodexWire/Latest/CodexLifecycleV2Batch+JSONValue.swift b/Sources/SwiftASB/Generated/CodexWire/Latest/CodexLifecycleV2Batch+JSONValue.swift index ab89396..4223b3f 100644 --- a/Sources/SwiftASB/Generated/CodexWire/Latest/CodexLifecycleV2Batch+JSONValue.swift +++ b/Sources/SwiftASB/Generated/CodexWire/Latest/CodexLifecycleV2Batch+JSONValue.swift @@ -125,6 +125,8 @@ struct CodexWireCodexLifecycleV2Batch: Codable, Equatable, Sendable { let threadStartResponse: CodexWireThreadStartResponse? let threadStatusChangedNotification: CodexWireThreadStatusChangedNotification? let threadTokenUsageUpdatedNotification: CodexWireThreadTokenUsageUpdatedNotification? + let threadTurnsItemsListParams: CodexWireThreadTurnsItemsListParams? + let threadTurnsItemsListResponse: CodexWireThreadTurnsItemsListResponse? let threadTurnsListParams: CodexWireThreadTurnsListParams? let threadTurnsListResponse: CodexWireThreadTurnsListResponse? let threadUnarchivedNotification: CodexWireThreadUnarchivedNotification? @@ -143,7 +145,7 @@ struct CodexWireCodexLifecycleV2Batch: Codable, Equatable, Sendable { case agentMessageDeltaNotification, appListUpdatedNotification, appsListParams, appsListResponse, collaborationModeListParams, collaborationModeListResponse, commandExecOutputDeltaNotification, commandExecutionOutputDeltaNotification, configReadParams, configReadResponse, configRequirementsReadResponse, configWarningNotification, contextCompactedNotification, deprecationNoticeNotification, errorNotification, externalAgentConfigImportCompletedNotification, fileChangeOutputDeltaNotification, fileChangePatchUpdatedNotification, fsChangedNotification, fsGetMetadataParams, fsGetMetadataResponse, fsReadDirectoryParams, fsReadDirectoryResponse, fsReadFileParams, fsReadFileResponse, fsUnwatchParams, fsUnwatchResponse, fsWatchParams, fsWatchResponse, guardianWarningNotification, hookCompletedNotification, hookStartedNotification, initializeParams, itemCompletedNotification, itemGuardianApprovalReviewCompletedNotification, itemGuardianApprovalReviewStartedNotification, itemStartedNotification case listMCPServerStatusParams = "listMcpServerStatusParams" case listMCPServerStatusResponse = "listMcpServerStatusResponse" - case mcpResourceReadParams, mcpResourceReadResponse, mcpServerStatusUpdatedNotification, mcpToolCallProgressNotification, modelListParams, modelListResponse, modelReroutedNotification, modelVerificationNotification, planDeltaNotification, pluginListParams, pluginListResponse, pluginReadParams, pluginReadResponse, pluginShareDeleteParams, pluginShareDeleteResponse, pluginShareListParams, pluginShareListResponse, pluginShareSaveParams, pluginShareSaveResponse, pluginShareUpdateTargetsParams, pluginShareUpdateTargetsResponse, pluginSkillReadParams, pluginSkillReadResponse, processExitedNotification, processKillParams, processKillResponse, processOutputDeltaNotification, processResizePtyParams, processResizePtyResponse, processSpawnParams, processSpawnResponse, processWriteStdinParams, processWriteStdinResponse, rawResponseItemCompletedNotification, reasoningSummaryPartAddedNotification, reasoningSummaryTextDeltaNotification, reasoningTextDeltaNotification, remoteControlStatusChangedNotification, serverRequestResolvedNotification, skillsChangedNotification, skillsListParams, skillsListResponse, threadApproveGuardianDeniedActionParams, threadApproveGuardianDeniedActionResponse, threadArchivedNotification, threadArchiveParams, threadArchiveResponse, threadClosedNotification, threadCompactStartParams, threadCompactStartResponse, threadGoalClearedNotification, threadGoalClearParams, threadGoalClearResponse, threadGoalGetParams, threadGoalGetResponse, threadGoalSetParams, threadGoalSetResponse, threadGoalUpdatedNotification, threadLoadedListParams, threadLoadedListResponse, threadMetadataUpdateParams, threadMetadataUpdateResponse, threadNameUpdatedNotification, threadRollbackParams, threadRollbackResponse, threadSetNameParams, threadSetNameResponse, threadStartedNotification, threadStartParams, threadStartResponse, threadStatusChangedNotification, threadTokenUsageUpdatedNotification, threadTurnsListParams, threadTurnsListResponse, threadUnarchivedNotification, threadUnarchiveParams, threadUnarchiveResponse, turnCompletedNotification, turnDiffUpdatedNotification, turnPlanUpdatedNotification, turnStartedNotification, turnStartParams, turnStartResponse, warningNotification, windowsSandboxReadinessResponse + case mcpResourceReadParams, mcpResourceReadResponse, mcpServerStatusUpdatedNotification, mcpToolCallProgressNotification, modelListParams, modelListResponse, modelReroutedNotification, modelVerificationNotification, planDeltaNotification, pluginListParams, pluginListResponse, pluginReadParams, pluginReadResponse, pluginShareDeleteParams, pluginShareDeleteResponse, pluginShareListParams, pluginShareListResponse, pluginShareSaveParams, pluginShareSaveResponse, pluginShareUpdateTargetsParams, pluginShareUpdateTargetsResponse, pluginSkillReadParams, pluginSkillReadResponse, processExitedNotification, processKillParams, processKillResponse, processOutputDeltaNotification, processResizePtyParams, processResizePtyResponse, processSpawnParams, processSpawnResponse, processWriteStdinParams, processWriteStdinResponse, rawResponseItemCompletedNotification, reasoningSummaryPartAddedNotification, reasoningSummaryTextDeltaNotification, reasoningTextDeltaNotification, remoteControlStatusChangedNotification, serverRequestResolvedNotification, skillsChangedNotification, skillsListParams, skillsListResponse, threadApproveGuardianDeniedActionParams, threadApproveGuardianDeniedActionResponse, threadArchivedNotification, threadArchiveParams, threadArchiveResponse, threadClosedNotification, threadCompactStartParams, threadCompactStartResponse, threadGoalClearedNotification, threadGoalClearParams, threadGoalClearResponse, threadGoalGetParams, threadGoalGetResponse, threadGoalSetParams, threadGoalSetResponse, threadGoalUpdatedNotification, threadLoadedListParams, threadLoadedListResponse, threadMetadataUpdateParams, threadMetadataUpdateResponse, threadNameUpdatedNotification, threadRollbackParams, threadRollbackResponse, threadSetNameParams, threadSetNameResponse, threadStartedNotification, threadStartParams, threadStartResponse, threadStatusChangedNotification, threadTokenUsageUpdatedNotification, threadTurnsItemsListParams, threadTurnsItemsListResponse, threadTurnsListParams, threadTurnsListResponse, threadUnarchivedNotification, threadUnarchiveParams, threadUnarchiveResponse, turnCompletedNotification, turnDiffUpdatedNotification, turnPlanUpdatedNotification, turnStartedNotification, turnStartParams, turnStartResponse, warningNotification, windowsSandboxReadinessResponse } } @@ -2134,10 +2136,14 @@ enum CodexWireThreadItemType: String, Codable, Equatable, Sendable { // MARK: - CodexWireItemGuardianApprovalReviewCompletedNotification struct CodexWireItemGuardianApprovalReviewCompletedNotification: Codable, Equatable, Sendable { let action: CodexWireGuardianApprovalReviewAction + /// Unix timestamp (in milliseconds) when this review completed. + let completedAtMS: Int? let decisionSource: CodexWireAutoReviewDecisionSource let review: CodexWireGuardianApprovalReview /// Stable identifier for this review. let reviewID: String + /// Unix timestamp (in milliseconds) when this review started. + let startedAtMS: Int? /// Identifier for the reviewed item or tool call when one exists. /// /// In most cases, one review maps to one target item. The exceptions are - execve reviews, @@ -2153,8 +2159,11 @@ struct CodexWireItemGuardianApprovalReviewCompletedNotification: Codable, Equata let threadID, turnID: String enum CodingKeys: String, CodingKey { - case action, decisionSource, review + case action + case completedAtMS = "completedAtMs" + case decisionSource, review case reviewID = "reviewId" + case startedAtMS = "startedAtMs" case targetItemID = "targetItemId" case threadID = "threadId" case turnID = "turnId" @@ -2374,6 +2383,8 @@ struct CodexWireItemGuardianApprovalReviewStartedNotification: Codable, Equatabl let review: CodexWireGuardianApprovalReview /// Stable identifier for this review. let reviewID: String + /// Unix timestamp (in milliseconds) when this review started. + let startedAtMS: Int? /// Identifier for the reviewed item or tool call when one exists. /// /// In most cases, one review maps to one target item. The exceptions are - execve reviews, @@ -2391,6 +2402,7 @@ struct CodexWireItemGuardianApprovalReviewStartedNotification: Codable, Equatabl enum CodingKeys: String, CodingKey { case action, review case reviewID = "reviewId" + case startedAtMS = "startedAtMs" case targetItemID = "targetItemId" case threadID = "threadId" case turnID = "turnId" @@ -2982,14 +2994,42 @@ struct CodexWirePluginInterface: Codable, Equatable, Sendable { struct CodexWirePluginShareContext: Codable, Equatable, Sendable { let creatorAccountUserID, creatorName: String? let remotePluginID: String + let shareTargets: [CodexWirePluginSharePrincipal]? + let shareURL: String? enum CodingKeys: String, CodingKey { case creatorAccountUserID = "creatorAccountUserId" case creatorName case remotePluginID = "remotePluginId" + case shareTargets + case shareURL = "shareUrl" + } +} + +// +// Hashable or Equatable: +// The compiler will not be able to synthesize the implementation of Hashable or Equatable +// for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be +// synthesized for types that have collections (such as arrays or dictionaries). + +// MARK: - CodexWirePluginSharePrincipal +struct CodexWirePluginSharePrincipal: Codable, Equatable, Sendable { + let name, principalID: String + let principalType: CodexWirePluginSharePrincipalType + + enum CodingKeys: String, CodingKey { + case name + case principalID = "principalId" + case principalType } } +enum CodexWirePluginSharePrincipalType: String, Codable, Equatable, Sendable { + case group = "group" + case user = "user" + case workspace = "workspace" +} + // // Hashable or Equatable: // The compiler will not be able to synthesize the implementation of Hashable or Equatable @@ -3046,6 +3086,7 @@ struct CodexWirePluginReadResponse: Codable, Equatable, Sendable { struct CodexWirePluginDetail: Codable, Equatable, Sendable { let apps: [CodexWireAppSummary] let description: String? + let hooks: [CodexWirePluginHookSummary] let marketplaceName: String let marketplacePath: String? let mcpServers: [String] @@ -3081,6 +3122,18 @@ struct CodexWireAppSummary: Codable, Equatable, Sendable { // for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be // synthesized for types that have collections (such as arrays or dictionaries). +// MARK: - CodexWirePluginHookSummary +struct CodexWirePluginHookSummary: Codable, Equatable, Sendable { + let eventName: CodexWireHookEventName + let key: String +} + +// +// Hashable or Equatable: +// The compiler will not be able to synthesize the implementation of Hashable or Equatable +// for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be +// synthesized for types that have collections (such as arrays or dictionaries). + // MARK: - CodexWireSkillSummary struct CodexWireSkillSummary: Codable, Equatable, Sendable { let description: String @@ -3189,12 +3242,6 @@ struct CodexWirePluginShareTarget: Codable, Equatable, Sendable { } } -enum CodexWirePluginSharePrincipalType: String, Codable, Equatable, Sendable { - case group = "group" - case user = "user" - case workspace = "workspace" -} - // // Hashable or Equatable: // The compiler will not be able to synthesize the implementation of Hashable or Equatable @@ -3219,15 +3266,22 @@ struct CodexWirePluginShareSaveResponse: Codable, Equatable, Sendable { // MARK: - CodexWirePluginShareUpdateTargetsParams struct CodexWirePluginShareUpdateTargetsParams: Codable, Equatable, Sendable { + let discoverability: CodexWirePluginShareUpdateDiscoverability let remotePluginID: String let shareTargets: [CodexWirePluginShareTarget] enum CodingKeys: String, CodingKey { + case discoverability case remotePluginID = "remotePluginId" case shareTargets } } +enum CodexWirePluginShareUpdateDiscoverability: String, Codable, Equatable, Sendable { + case pluginShareUpdateDiscoverabilityPRIVATE = "PRIVATE" + case unlisted = "UNLISTED" +} + // // Hashable or Equatable: // The compiler will not be able to synthesize the implementation of Hashable or Equatable @@ -3236,6 +3290,7 @@ struct CodexWirePluginShareUpdateTargetsParams: Codable, Equatable, Sendable { // MARK: - CodexWirePluginShareUpdateTargetsResponse struct CodexWirePluginShareUpdateTargetsResponse: Codable, Equatable, Sendable { + let discoverability: CodexWirePluginShareDiscoverability let principals: [CodexWirePluginSharePrincipal] } @@ -3245,24 +3300,6 @@ struct CodexWirePluginShareUpdateTargetsResponse: Codable, Equatable, Sendable { // for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be // synthesized for types that have collections (such as arrays or dictionaries). -// MARK: - CodexWirePluginSharePrincipal -struct CodexWirePluginSharePrincipal: Codable, Equatable, Sendable { - let name, principalID: String - let principalType: CodexWirePluginSharePrincipalType - - enum CodingKeys: String, CodingKey { - case name - case principalID = "principalId" - case principalType - } -} - -// -// Hashable or Equatable: -// The compiler will not be able to synthesize the implementation of Hashable or Equatable -// for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be -// synthesized for types that have collections (such as arrays or dictionaries). - // MARK: - CodexWirePluginSkillReadParams struct CodexWirePluginSkillReadParams: Codable, Equatable, Sendable { let remoteMarketplaceName, remotePluginID, skillName: String @@ -3809,20 +3846,6 @@ struct CodexWireSkillsListParams: Codable, Equatable, Sendable { let cwds: [String]? /// When true, bypass the skills cache and re-scan skills from disk. let forceReload: Bool? - /// Optional per-cwd extra roots to scan as user-scoped skills. - let perCwdExtraUserRoots: [CodexWireSkillsListExtraRootsForCwd]? -} - -// -// Hashable or Equatable: -// The compiler will not be able to synthesize the implementation of Hashable or Equatable -// for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be -// synthesized for types that have collections (such as arrays or dictionaries). - -// MARK: - CodexWireSkillsListExtraRootsForCwd -struct CodexWireSkillsListExtraRootsForCwd: Codable, Equatable, Sendable { - let cwd: String - let extraUserRoots: [String] } // @@ -4940,19 +4963,20 @@ struct CodexWireTokenUsageBreakdown: Codable, Equatable, Sendable { // for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be // synthesized for types that have collections (such as arrays or dictionaries). -// MARK: - CodexWireThreadTurnsListParams -struct CodexWireThreadTurnsListParams: Codable, Equatable, Sendable { - /// Opaque cursor to pass to the next call to continue after the last turn. +// MARK: - CodexWireThreadTurnsItemsListParams +struct CodexWireThreadTurnsItemsListParams: Codable, Equatable, Sendable { + /// Opaque cursor to pass to the next call to continue after the last item. let cursor: String? - /// Optional turn page size. + /// Optional item page size. let limit: Int? - /// Optional turn pagination direction; defaults to descending. + /// Optional item pagination direction; defaults to ascending. let sortDirection: CodexWireSortDirection? - let threadID: String + let threadID, turnID: String enum CodingKeys: String, CodingKey { case cursor, limit, sortDirection case threadID = "threadId" + case turnID = "turnId" } } @@ -4967,6 +4991,47 @@ enum CodexWireSortDirection: String, Codable, Equatable, Sendable { // for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be // synthesized for types that have collections (such as arrays or dictionaries). +// MARK: - CodexWireThreadTurnsItemsListResponse +struct CodexWireThreadTurnsItemsListResponse: Codable, Equatable, Sendable { + /// Opaque cursor to pass as `cursor` when reversing `sortDirection`. This is only populated + /// when the page contains at least one item. + let backwardsCursor: String? + let data: [CodexWireThreadItem] + /// Opaque cursor to pass to the next call to continue after the last item. if None, there + /// are no more items to return. + let nextCursor: String? +} + +// +// Hashable or Equatable: +// The compiler will not be able to synthesize the implementation of Hashable or Equatable +// for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be +// synthesized for types that have collections (such as arrays or dictionaries). + +// MARK: - CodexWireThreadTurnsListParams +struct CodexWireThreadTurnsListParams: Codable, Equatable, Sendable { + /// Opaque cursor to pass to the next call to continue after the last turn. + let cursor: String? + /// How much item detail to include for each returned turn; defaults to summary. + let itemsView: CodexWireTurnItemsView? + /// Optional turn page size. + let limit: Int? + /// Optional turn pagination direction; defaults to descending. + let sortDirection: CodexWireSortDirection? + let threadID: String + + enum CodingKeys: String, CodingKey { + case cursor, itemsView, limit, sortDirection + case threadID = "threadId" + } +} + +// +// Hashable or Equatable: +// The compiler will not be able to synthesize the implementation of Hashable or Equatable +// for types that require the use of CodexWireJSONValue, nor will the implementation of Hashable be +// synthesized for types that have collections (such as arrays or dictionaries). + // MARK: - CodexWireThreadTurnsListResponse struct CodexWireThreadTurnsListResponse: Codable, Equatable, Sendable { /// Opaque cursor to pass as `cursor` when reversing `sortDirection`. This is only populated diff --git a/Sources/SwiftASB/Protocol/CodexAppServerProtocol+Types.swift b/Sources/SwiftASB/Protocol/CodexAppServerProtocol+Types.swift index ee0954f..681c3cb 100644 --- a/Sources/SwiftASB/Protocol/CodexAppServerProtocol+Types.swift +++ b/Sources/SwiftASB/Protocol/CodexAppServerProtocol+Types.swift @@ -266,12 +266,14 @@ struct CodexProtocolThreadListResponse: Decodable, Equatable, Sendable { struct CodexProtocolThreadTurnsListParams: Encodable, Equatable, Sendable { let cursor: String? + let itemsView: CodexWireTurnItemsView? let limit: Int? let sortDirection: CodexProtocolThreadTurnsSortDirection? let threadID: String enum CodingKeys: String, CodingKey { case cursor + case itemsView case limit case sortDirection case threadID = "threadId" diff --git a/Sources/SwiftASB/Protocol/CodexAppServerProtocol.swift b/Sources/SwiftASB/Protocol/CodexAppServerProtocol.swift index 0c5c822..b8f2fa6 100644 --- a/Sources/SwiftASB/Protocol/CodexAppServerProtocol.swift +++ b/Sources/SwiftASB/Protocol/CodexAppServerProtocol.swift @@ -15,6 +15,7 @@ struct CodexAppServerProtocol { case threadStart = "thread/start" case threadMetadataUpdate = "thread/metadata/update" case threadTurnsList = "thread/turns/list" + case threadTurnsItemsList = "thread/turns/items/list" case threadLoadedList = "thread/loaded/list" case threadUnarchive = "thread/unarchive" case threadGoalGet = "thread/goal/get" @@ -190,6 +191,16 @@ struct CodexAppServerProtocol { ) } + func makeThreadTurnsItemsListRequest( + id: CodexRPCRequestID, + params: CodexWireThreadTurnsItemsListParams + ) throws -> Data { + try encodeRequest( + JSONRPCRequestEnvelope(id: id, method: .threadTurnsItemsList, params: params), + method: .threadTurnsItemsList + ) + } + func makeThreadLoadedListRequest( id: CodexRPCRequestID, params: CodexWireThreadLoadedListParams @@ -609,6 +620,18 @@ struct CodexAppServerProtocol { ) } + func decodeThreadTurnsItemsListResponse( + _ responsePayload: Data, + expectedID: CodexRPCRequestID + ) throws -> CodexWireThreadTurnsItemsListResponse { + try decodeResponse( + responsePayload, + expectedID: expectedID, + method: .threadTurnsItemsList, + resultType: CodexWireThreadTurnsItemsListResponse.self + ) + } + func decodeThreadLoadedListResponse( _ responsePayload: Data, expectedID: CodexRPCRequestID diff --git a/Sources/SwiftASB/Public/CodexAppServer+CodexExtensions.swift b/Sources/SwiftASB/Public/CodexAppServer+CodexExtensions.swift index 8ef1e46..7f15c84 100644 --- a/Sources/SwiftASB/Public/CodexAppServer+CodexExtensions.swift +++ b/Sources/SwiftASB/Public/CodexAppServer+CodexExtensions.swift @@ -84,6 +84,8 @@ public extension CodexAppServer { public var currentDirectoryPaths: [String]? public var forceReload: Bool? + /// Deprecated by Codex CLI 0.130.0. The app-server no longer accepts + /// per-cwd extra skill roots on `skills/list`. public var perCurrentDirectoryExtraUserRoots: [ExtraUserRootsForCurrentDirectory]? public init( @@ -220,6 +222,7 @@ public extension CodexAppServer { public struct PluginDetail: Sendable, Equatable { public let apps: [AppSummary] public let description: String? + public let hooks: [PluginHookSummary] public let marketplaceName: String public let marketplacePath: String? public let mcpServers: [String] @@ -227,6 +230,13 @@ public extension CodexAppServer { public let summary: PluginSummary } + public struct PluginHookSummary: Sendable, Equatable, Identifiable { + public var id: String { key } + + public let eventName: HookMetadata.EventName + public let key: String + } + public struct AppSummary: Sendable, Equatable, Identifiable { public let description: String? public let id: String @@ -500,6 +510,7 @@ extension CodexAppServer.CodexExtensions.PluginDetail { self.init( apps: wireValue.apps.map(CodexAppServer.CodexExtensions.AppSummary.init), description: wireValue.description, + hooks: wireValue.hooks.map(CodexAppServer.CodexExtensions.PluginHookSummary.init), marketplaceName: wireValue.marketplaceName, marketplacePath: wireValue.marketplacePath, mcpServers: wireValue.mcpServers, @@ -509,6 +520,38 @@ extension CodexAppServer.CodexExtensions.PluginDetail { } } +extension CodexAppServer.CodexExtensions.PluginHookSummary { + init(wireValue: CodexWirePluginHookSummary) { + self.init( + eventName: .init(wireValue: wireValue.eventName), + key: wireValue.key + ) + } +} + +extension CodexAppServer.HookMetadata.EventName { + init(wireValue: CodexWireHookEventName) { + switch wireValue { + case .permissionRequest: + self = .permissionRequest + case .postCompact: + self = .postCompact + case .postToolUse: + self = .postToolUse + case .preCompact: + self = .preCompact + case .preToolUse: + self = .preToolUse + case .sessionStart: + self = .sessionStart + case .stop: + self = .stop + case .userPromptSubmit: + self = .userPromptSubmit + } + } +} + extension CodexAppServer.CodexExtensions.AppSummary { init(wireValue: CodexWireAppSummary) { self.init( diff --git a/Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift b/Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift index 39479be..3611a67 100644 --- a/Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift +++ b/Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift @@ -407,25 +407,36 @@ extension CodexAppServer { case desc } + /// Amount of item detail to include when listing stored turns. + public enum TurnItemsView: String, Sendable, Equatable { + case full + case notLoaded + case summary + } + public struct ThreadTurnsListRequest: Sendable, Equatable { public var cursor: String? + public var itemsView: TurnItemsView? public var limit: Int? public var sortDirection: ThreadTurnsSortDirection? public var threadID: String /// Creates a paged turn-list request for a stored thread. /// - /// Nil pagination and sort fields are omitted, which keeps the - /// app-server in charge of its default page size and ordering. + /// Nil pagination, item-view, and sort fields are omitted, which keeps + /// the app-server in charge of its default page size, item detail, and + /// ordering. public init( threadID: String, limit: Int? = nil, cursor: String? = nil, + itemsView: TurnItemsView? = nil, sortDirection: ThreadTurnsSortDirection? = nil ) { self.threadID = threadID self.limit = limit self.cursor = cursor + self.itemsView = itemsView self.sortDirection = sortDirection } } @@ -437,6 +448,39 @@ extension CodexAppServer { public let turns: [TurnInfo] } + public struct ThreadTurnsItemsListRequest: Sendable, Equatable { + public var cursor: String? + public var limit: Int? + public var sortDirection: ThreadTurnsSortDirection? + public var threadID: String + public var turnID: String + + /// Creates a paged item-list request for one stored turn. + /// + /// Nil pagination and sort fields are omitted, which keeps the + /// app-server in charge of its default page size and ordering. + public init( + threadID: String, + turnID: String, + limit: Int? = nil, + cursor: String? = nil, + sortDirection: ThreadTurnsSortDirection? = nil + ) { + self.threadID = threadID + self.turnID = turnID + self.limit = limit + self.cursor = cursor + self.sortDirection = sortDirection + } + } + + /// One page of stored item-history results for a turn. + public struct ThreadTurnsItemsPage: Sendable, Equatable { + public let backwardsCursor: String? + public let items: [CodexTurnItem] + public let nextCursor: String? + } + /// Current app-server status for a thread. public struct ThreadStatus: Sendable, Equatable { public let type: ThreadStatusType diff --git a/Sources/SwiftASB/Public/CodexAppServer+WireMapping.swift b/Sources/SwiftASB/Public/CodexAppServer+WireMapping.swift index 3343ccf..5a8f2ca 100644 --- a/Sources/SwiftASB/Public/CodexAppServer+WireMapping.swift +++ b/Sources/SwiftASB/Public/CodexAppServer+WireMapping.swift @@ -837,6 +837,30 @@ extension CodexProtocolThreadTurnsSortDirection { } } +extension CodexWireSortDirection { + init(_ direction: CodexAppServer.ThreadTurnsSortDirection) { + switch direction { + case .asc: + self = .asc + case .desc: + self = .desc + } + } +} + +extension CodexWireTurnItemsView { + init(_ itemsView: CodexAppServer.TurnItemsView) { + switch itemsView { + case .full: + self = .full + case .notLoaded: + self = .notLoaded + case .summary: + self = .summary + } + } +} + extension CodexProtocolThreadListSortKey { init(_ key: CodexAppServer.ThreadListSortKey) { switch key { diff --git a/Sources/SwiftASB/Public/CodexAppServer.swift b/Sources/SwiftASB/Public/CodexAppServer.swift index f41c309..bf8662c 100644 --- a/Sources/SwiftASB/Public/CodexAppServer.swift +++ b/Sources/SwiftASB/Public/CodexAppServer.swift @@ -1100,6 +1100,11 @@ public actor CodexAppServer { _ request: CodexExtensions.SkillListRequest ) async throws -> CodexExtensions.SkillListSnapshot { try requireInitialized(for: "skills/list") + if request.perCurrentDirectoryExtraUserRoots != nil { + throw CodexAppServerError.invalidState( + reason: "Codex CLI 0.130.0 removed per-cwd extra user roots from skills/list; pass currentDirectoryPaths and forceReload only." + ) + } let requestID = CodexRPCRequestID.generated() @@ -1108,13 +1113,7 @@ public actor CodexAppServer { id: requestID, params: .init( cwds: request.currentDirectoryPaths, - forceReload: request.forceReload, - perCwdExtraUserRoots: request.perCurrentDirectoryExtraUserRoots?.map { - .init( - cwd: $0.currentDirectoryPath, - extraUserRoots: $0.extraUserRoots - ) - } + forceReload: request.forceReload ) ) let responsePayload = try await transport.send(requestPayload, id: requestID) @@ -1263,6 +1262,7 @@ public actor CodexAppServer { id: requestID, params: .init( cursor: request.cursor, + itemsView: request.itemsView.map(CodexWireTurnItemsView.init), limit: request.limit, sortDirection: request.sortDirection.map(CodexProtocolThreadTurnsSortDirection.init), threadID: request.threadID @@ -1294,6 +1294,45 @@ public actor CodexAppServer { } } + /// Reads a page of stored items for one turn directly from the app-server. + /// + /// This low-level paging API returns app-server item snapshots without + /// assuming the caller has loaded the full containing turn. Paged item reads + /// do not mutate SwiftASB's local history store because a single item page + /// does not carry enough information to safely reconcile whole-turn item + /// ordering. + public func listThreadTurnItems(_ request: ThreadTurnsItemsListRequest) async throws -> ThreadTurnsItemsPage { + try requireInitialized(for: "thread/turns/items/list") + + let requestID = CodexRPCRequestID.generated() + + do { + let requestPayload = try protocolLayer.makeThreadTurnsItemsListRequest( + id: requestID, + params: .init( + cursor: request.cursor, + limit: request.limit, + sortDirection: request.sortDirection.map(CodexWireSortDirection.init), + threadID: request.threadID, + turnID: request.turnID + ) + ) + let responsePayload = try await transport.send(requestPayload, id: requestID) + let response = try protocolLayer.decodeThreadTurnsItemsListResponse( + responsePayload, + expectedID: requestID + ) + + return .init( + backwardsCursor: response.backwardsCursor, + items: response.data.map(CodexTurnItem.init(wireValue:)), + nextCursor: response.nextCursor + ) + } catch { + throw CodexAppServerError.wrap(error, operation: "thread/turns/items/list") + } + } + /// Starts a turn from an app-server-owned request. /// /// Most consumers should prefer `CodexThread.startTurn(_:)` or diff --git a/Sources/SwiftASB/SwiftASB.docc/CodexExtensions.md b/Sources/SwiftASB/SwiftASB.docc/CodexExtensions.md index cf2326d..6815074 100644 --- a/Sources/SwiftASB/SwiftASB.docc/CodexExtensions.md +++ b/Sources/SwiftASB/SwiftASB.docc/CodexExtensions.md @@ -19,6 +19,10 @@ The namespace is read-only. Plugin install, uninstall, marketplace mutation, and skill config writes remain unpromoted until SwiftASB has a clearer permission and user-review story for those operations. +Plugin detail reads include app, skill, MCP server, and hook summaries so an +extension inspector can show which entry points a plugin contributes without +reading plugin files directly. + ## Topics ### Reads @@ -55,6 +59,7 @@ and user-review story for those operations. - ``PluginInterface`` - ``PluginReadRequest`` - ``PluginDetail`` +- ``PluginHookSummary`` - ``MarketplaceLoadError`` ### Collaboration Modes diff --git a/Sources/SwiftASB/Transport/CodexCLIExecutableResolver.swift b/Sources/SwiftASB/Transport/CodexCLIExecutableResolver.swift index 88e5695..946fef9 100644 --- a/Sources/SwiftASB/Transport/CodexCLIExecutableResolver.swift +++ b/Sources/SwiftASB/Transport/CodexCLIExecutableResolver.swift @@ -30,7 +30,7 @@ internal struct CodexCLIExecutableResolver { internal let patch: Int private static let regex = try! NSRegularExpression(pattern: #"(\d+)\.(\d+)\.(\d+)"#) - internal static let latestSupportedPublicRelease = Version(major: 0, minor: 129, patch: 0) + internal static let latestSupportedPublicRelease = Version(major: 0, minor: 130, patch: 0) internal static var documentedWindowDescription: String { let latest = latestSupportedPublicRelease diff --git a/Tests/SwiftASBTests/Protocol/CodexAppServerProtocolTests.swift b/Tests/SwiftASBTests/Protocol/CodexAppServerProtocolTests.swift index 6232461..1909a9d 100644 --- a/Tests/SwiftASBTests/Protocol/CodexAppServerProtocolTests.swift +++ b/Tests/SwiftASBTests/Protocol/CodexAppServerProtocolTests.swift @@ -342,6 +342,7 @@ struct CodexAppServerProtocolTests { id: .string("thread-turns-list-1"), params: .init( cursor: "cursor-newer", + itemsView: .full, limit: 10, sortDirection: .desc, threadID: "thread-123" @@ -355,11 +356,38 @@ struct CodexAppServerProtocolTests { let params = try #require(object["params"] as? [String: Any]) #expect(params["cursor"] as? String == "cursor-newer") + #expect(params["itemsView"] as? String == "full") #expect(params["limit"] as? Int == 10) #expect(params["sortDirection"] as? String == "desc") #expect(params["threadId"] as? String == "thread-123") } + @Test("encodes thread/turns/items/list requests with the expected method and params payload") + func encodesThreadTurnsItemsListRequest() throws { + let payload = try protocolLayer.makeThreadTurnsItemsListRequest( + id: .string("thread-turns-items-list-1"), + params: .init( + cursor: "cursor-items", + limit: 20, + sortDirection: .asc, + threadID: "thread-123", + turnID: "turn-456" + ) + ) + + let object = try #require(try JSONSerialization.jsonObject(with: payload) as? [String: Any]) + #expect(object["jsonrpc"] == nil) + #expect(object["method"] as? String == "thread/turns/items/list") + #expect(object["id"] as? String == "thread-turns-items-list-1") + + let params = try #require(object["params"] as? [String: Any]) + #expect(params["cursor"] as? String == "cursor-items") + #expect(params["limit"] as? Int == 20) + #expect(params["sortDirection"] as? String == "asc") + #expect(params["threadId"] as? String == "thread-123") + #expect(params["turnId"] as? String == "turn-456") + } + @Test("encodes thread/compact/start requests with the expected method and params payload") func encodesThreadCompactStartRequest() throws { let payload = try protocolLayer.makeThreadCompactStartRequest( @@ -584,7 +612,7 @@ struct CodexAppServerProtocolTests { let skillsPayload = try protocolLayer.makeSkillsListRequest( id: .string("skills-list-1"), - params: .init(cwds: ["/tmp/project"], forceReload: true, perCwdExtraUserRoots: nil) + params: .init(cwds: ["/tmp/project"], forceReload: true) ) let skillsRequest = try #require(try JSONSerialization.jsonObject(with: skillsPayload) as? [String: Any]) #expect(skillsRequest["method"] as? String == "skills/list") diff --git a/Tests/SwiftASBTests/Public/CodexAppServerFileSystemTests.swift b/Tests/SwiftASBTests/Public/CodexAppServerFileSystemTests.swift index 80257cb..9a6e6cf 100644 --- a/Tests/SwiftASBTests/Public/CodexAppServerFileSystemTests.swift +++ b/Tests/SwiftASBTests/Public/CodexAppServerFileSystemTests.swift @@ -309,10 +309,7 @@ extension CodexAppServerTests { let skills = try await client.extensions.listSkills( .init( currentDirectoryPaths: ["/tmp/project"], - forceReload: true, - perCurrentDirectoryExtraUserRoots: [ - .init(currentDirectoryPath: "/tmp/project", extraUserRoots: ["/tmp/extra-skills"]), - ] + forceReload: true ) ) #expect(skills.entries.first?.errors.first?.message == "Skipped duplicate skill.") @@ -336,6 +333,8 @@ extension CodexAppServerTests { #expect(plugin.marketplacePath == "/tmp/marketplaces/openai-curated.json") #expect(plugin.description == "GitHub plugin detail fixture.") #expect(plugin.apps.first?.needsAuth == true) + #expect(plugin.hooks.map(\.key) == ["github-pre-tool-use", "github-post-tool-use"]) + #expect(plugin.hooks.map(\.eventName) == [.preToolUse, .postToolUse]) #expect(plugin.skills.first?.displayName == "PR Review") #expect(plugin.summary.name == "GitHub") #expect(plugin.summary.sourceKind == .git) @@ -356,9 +355,7 @@ extension CodexAppServerTests { let skillsRequestJSON = try decodedJSONObject(from: skillsRequest) #expect(value(at: ["params", "cwds"], in: skillsRequestJSON) as? [String] == ["/tmp/project"]) #expect(value(at: ["params", "forceReload"], in: skillsRequestJSON) as? Bool == true) - let extraRoots = try #require(value(at: ["params", "perCwdExtraUserRoots"], in: skillsRequestJSON) as? [[String: Any]]) - #expect(extraRoots.first?["cwd"] as? String == "/tmp/project") - #expect(extraRoots.first?["extraUserRoots"] as? [String] == ["/tmp/extra-skills"]) + #expect(value(at: ["params", "perCwdExtraUserRoots"], in: skillsRequestJSON) == nil) let pluginsRequest = try #require(await transport.recordedRequestPayload(for: "plugin/list")) let pluginsRequestJSON = try decodedJSONObject(from: pluginsRequest) @@ -375,6 +372,50 @@ extension CodexAppServerTests { await client.stop() } + @Test("CodexExtensions rejects removed per-cwd extra skill roots option") + func codexExtensionsRejectsRemovedPerCwdExtraSkillRootsOption() async throws { + let transport = FakeCodexAppServerTransport() + let client = CodexAppServer(transport: transport) + + try await client.start() + _ = try await client.initialize( + .init( + clientInfo: .init( + name: "SwiftASBTests", + title: "SwiftASB Tests", + version: "0.1.0" + ) + ) + ) + + do { + _ = try await client.extensions.listSkills( + .init( + currentDirectoryPaths: ["/tmp/project"], + perCurrentDirectoryExtraUserRoots: [ + .init(currentDirectoryPath: "/tmp/project", extraUserRoots: ["/tmp/extra-skills"]), + ] + ) + ) + Issue.record("Expected per-cwd extra skill roots to be rejected for Codex CLI 0.130.0.") + } catch let error as CodexAppServerError { + guard case let .invalidState(reason) = error else { + Issue.record("Expected removed per-cwd extra skill roots to throw an invalidState error.") + await client.stop() + return + } + + #expect( + reason + == "Codex CLI 0.130.0 removed per-cwd extra user roots from skills/list; pass currentDirectoryPaths and forceReload only." + ) + } + + #expect(await transport.recordedRequestPayload(for: "skills/list") == nil) + + await client.stop() + } + @Test("CodexThread reads and updates app-server thread goals") func codexThreadReadsAndUpdatesAppServerThreadGoals() async throws { let transport = FakeCodexAppServerTransport() diff --git a/Tests/SwiftASBTests/Public/CodexAppServerLiveIntegrationTests.swift b/Tests/SwiftASBTests/Public/CodexAppServerLiveIntegrationTests.swift index e682905..875c948 100644 --- a/Tests/SwiftASBTests/Public/CodexAppServerLiveIntegrationTests.swift +++ b/Tests/SwiftASBTests/Public/CodexAppServerLiveIntegrationTests.swift @@ -198,7 +198,7 @@ struct CodexAppServerLiveIntegrationTests { let diagnostics = try await client.cliExecutableDiagnostics() #expect(diagnostics.resolvedExecutablePath == harness.codexExecutableURL.path) #expect(diagnostics.versionString.contains("codex-cli")) - #expect(diagnostics.compatibility == .supported(documentedWindow: "0.129.x")) + #expect(diagnostics.compatibility == .supported(documentedWindow: "0.130.x")) await client.stop() } catch { diff --git a/Tests/SwiftASBTests/Public/CodexAppServerTestSupport.swift b/Tests/SwiftASBTests/Public/CodexAppServerTestSupport.swift index 086e324..ce076ff 100644 --- a/Tests/SwiftASBTests/Public/CodexAppServerTestSupport.swift +++ b/Tests/SwiftASBTests/Public/CodexAppServerTestSupport.swift @@ -46,6 +46,7 @@ actor FakeCodexAppServerTransport: CodexAppServerTransporting { private var threadTurnsListErrorMessage: String? private var threadTurnsListResult: [String: Any]? private var threadTurnsListResultQueue: [[String: Any]] + private var threadTurnsItemsListResult: [String: Any]? private var appSnapshotResponseDelayNanoseconds: UInt64 = 0 private let resolvedExecutable: CodexCLIExecutableResolver.Resolution? private var started = false @@ -63,7 +64,8 @@ actor FakeCodexAppServerTransport: CodexAppServerTransporting { turnStartIDQueue: [String] = [], threadTurnsListErrorMessage: String? = nil, threadTurnsListResult: [String: Any]? = nil, - threadTurnsListResultQueue: [[String: Any]] = [] + threadTurnsListResultQueue: [[String: Any]] = [], + threadTurnsItemsListResult: [String: Any]? = nil ) { self.resolvedExecutable = executableResolution self.threadListResult = threadListResult @@ -76,6 +78,7 @@ actor FakeCodexAppServerTransport: CodexAppServerTransporting { self.threadTurnsListErrorMessage = threadTurnsListErrorMessage self.threadTurnsListResult = threadTurnsListResult self.threadTurnsListResultQueue = threadTurnsListResultQueue + self.threadTurnsItemsListResult = threadTurnsItemsListResult } func setThreadListResult(_ result: [String: Any]?) { @@ -812,6 +815,16 @@ actor FakeCodexAppServerTransport: CodexAppServerTransporting { ], ], "description": "GitHub plugin detail fixture.", + "hooks": [ + [ + "eventName": "preToolUse", + "key": "github-pre-tool-use", + ], + [ + "eventName": "postToolUse", + "key": "github-post-tool-use", + ], + ], "marketplaceName": "openai-curated", "marketplacePath": "/tmp/marketplaces/openai-curated.json", "mcpServers": [], @@ -1099,6 +1112,28 @@ actor FakeCodexAppServerTransport: CodexAppServerTransporting { "nextCursor": "cursor-older", ] ) + case "thread/turns/items/list": + return responsePayload( + id: id, + result: threadTurnsItemsListResult ?? [ + "backwardsCursor": "cursor-newer-items", + "data": [ + [ + "id": "item-command-1", + "command": "swift test", + "status": "completed", + "type": "commandExecution", + ], + [ + "id": "item-agent-1", + "status": "completed", + "text": "Done.", + "type": "agentMessage", + ], + ], + "nextCursor": "cursor-older-items", + ] + ) case "turn/start": let turnID = turnStartIDQueue.isEmpty ? "turn-123" : turnStartIDQueue.removeFirst() return responsePayload( diff --git a/Tests/SwiftASBTests/Public/CodexAppServerTests.swift b/Tests/SwiftASBTests/Public/CodexAppServerTests.swift index 4377e5a..8edde4d 100644 --- a/Tests/SwiftASBTests/Public/CodexAppServerTests.swift +++ b/Tests/SwiftASBTests/Public/CodexAppServerTests.swift @@ -83,8 +83,8 @@ struct CodexAppServerTests { launchArgumentsPrefix: [], resolvedExecutableURL: URL(fileURLWithPath: "/opt/homebrew/bin/codex"), source: .homebrewAppleSilicon, - versionString: "codex-cli 0.129.0", - compatibility: .supported(documentedWindow: "0.129.x") + versionString: "codex-cli 0.130.0", + compatibility: .supported(documentedWindow: "0.130.x") ) ) let client = CodexAppServer(transport: transport) @@ -94,8 +94,8 @@ struct CodexAppServerTests { let diagnostics = try await client.cliExecutableDiagnostics() #expect(diagnostics.source == .homebrewAppleSilicon) #expect(diagnostics.resolvedExecutablePath == "/opt/homebrew/bin/codex") - #expect(diagnostics.versionString == "codex-cli 0.129.0") - #expect(diagnostics.compatibility == .supported(documentedWindow: "0.129.x")) + #expect(diagnostics.versionString == "codex-cli 0.130.0") + #expect(diagnostics.compatibility == .supported(documentedWindow: "0.130.x")) await client.stop() } diff --git a/Tests/SwiftASBTests/Public/CodexAppServerThreadHydrationTests.swift b/Tests/SwiftASBTests/Public/CodexAppServerThreadHydrationTests.swift index 73b502c..6d7d63c 100644 --- a/Tests/SwiftASBTests/Public/CodexAppServerThreadHydrationTests.swift +++ b/Tests/SwiftASBTests/Public/CodexAppServerThreadHydrationTests.swift @@ -382,6 +382,49 @@ extension CodexAppServerTests { await tearDownTemporarySQLiteHistoryStore(historyStore, directory: temporaryDirectory) } + @Test("lists stored turn items without requiring a local turn snapshot") + func listsStoredTurnItems() async throws { + let transport = FakeCodexAppServerTransport() + let client = CodexAppServer(transport: transport) + + try await client.start() + _ = try await client.initialize( + .init( + clientInfo: .init( + name: "SwiftASBTests", + title: "SwiftASB Tests", + version: "0.1.0" + ) + ) + ) + + let page = try await client.listThreadTurnItems( + .init( + threadID: "thread-123", + turnID: "turn-older", + limit: 2, + sortDirection: .asc + ) + ) + + #expect(page.backwardsCursor == "cursor-newer-items") + #expect(page.nextCursor == "cursor-older-items") + #expect(page.items.map(\.id) == ["item-command-1", "item-agent-1"]) + #expect(page.items.first?.kind == .commandExecution) + #expect(page.items.first?.command == "swift test") + + let payloads = await transport.requestPayloads(for: "thread/turns/items/list") + let payload = try #require(payloads.first) + let object = try #require(try JSONSerialization.jsonObject(with: payload) as? [String: Any]) + let params = try #require(object["params"] as? [String: Any]) + #expect(params["threadId"] as? String == "thread-123") + #expect(params["turnId"] as? String == "turn-older") + #expect(params["limit"] as? Int == 2) + #expect(params["sortDirection"] as? String == "asc") + + await client.stop() + } + @Test("streams thread lifecycle notifications through CodexThread.events") func streamsThreadLifecycleNotifications() async throws { let transport = FakeCodexAppServerTransport() diff --git a/Tests/SwiftASBTests/Transport/CodexAppServerTransportTests.swift b/Tests/SwiftASBTests/Transport/CodexAppServerTransportTests.swift index 1cd324c..418db1c 100644 --- a/Tests/SwiftASBTests/Transport/CodexAppServerTransportTests.swift +++ b/Tests/SwiftASBTests/Transport/CodexAppServerTransportTests.swift @@ -192,7 +192,7 @@ private let fakeCodexScript = """ #!/bin/sh if [ "$1" = "--version" ]; then - printf '%s\\n' 'codex-cli 0.129.0' + printf '%s\\n' 'codex-cli 0.130.0' exit 0 fi diff --git a/Tests/SwiftASBTests/Transport/CodexCLIExecutableResolverTests.swift b/Tests/SwiftASBTests/Transport/CodexCLIExecutableResolverTests.swift index 8ebbc10..d4c61ca 100644 --- a/Tests/SwiftASBTests/Transport/CodexCLIExecutableResolverTests.swift +++ b/Tests/SwiftASBTests/Transport/CodexCLIExecutableResolverTests.swift @@ -23,8 +23,8 @@ struct CodexCLIExecutableResolverTests { #expect(resolution.launchExecutableURL == explicitURL) #expect(resolution.launchArgumentsPrefix.isEmpty) #expect(resolution.resolvedExecutableURL == explicitURL) - #expect(resolution.versionString == "codex-cli 0.129.0") - #expect(resolution.compatibility == .supported(documentedWindow: "0.129.x")) + #expect(resolution.versionString == "codex-cli 0.130.0") + #expect(resolution.compatibility == .supported(documentedWindow: "0.130.x")) #expect(recorder.recordedInvocations == [ .init(executablePath: explicitURL.path, arguments: ["--version"]) ]) @@ -48,7 +48,7 @@ struct CodexCLIExecutableResolverTests { #expect(resolution.launchExecutableURL.path == "/usr/bin/env") #expect(resolution.launchArgumentsPrefix == ["codex"]) #expect(resolution.resolvedExecutableURL == nil) - #expect(resolution.compatibility == .supported(documentedWindow: "0.129.x")) + #expect(resolution.compatibility == .supported(documentedWindow: "0.130.x")) #expect(recorder.recordedInvocations == [ .init(executablePath: "/usr/bin/env", arguments: ["codex", "--version"]) ]) @@ -76,7 +76,7 @@ struct CodexCLIExecutableResolverTests { #expect(resolution.launchExecutableURL.path == homebrewPath) #expect(resolution.launchArgumentsPrefix.isEmpty) #expect(resolution.resolvedExecutableURL?.path == homebrewPath) - #expect(resolution.compatibility == .supported(documentedWindow: "0.129.x")) + #expect(resolution.compatibility == .supported(documentedWindow: "0.130.x")) #expect(recorder.recordedInvocations == [ .init(executablePath: "/usr/bin/env", arguments: ["codex", "--version"]), .init(executablePath: homebrewPath, arguments: ["--version"]) @@ -106,7 +106,7 @@ struct CodexCLIExecutableResolverTests { #expect(resolution.launchExecutableURL.path == npmCodexPath) #expect(resolution.launchArgumentsPrefix.isEmpty) #expect(resolution.resolvedExecutableURL?.path == npmCodexPath) - #expect(resolution.compatibility == .supported(documentedWindow: "0.129.x")) + #expect(resolution.compatibility == .supported(documentedWindow: "0.130.x")) #expect(recorder.recordedInvocations == [ .init(executablePath: "/usr/bin/env", arguments: ["codex", "--version"]), .init(executablePath: "/usr/bin/env", arguments: ["npm", "prefix", "-g"]), @@ -139,7 +139,7 @@ struct CodexCLIExecutableResolverTests { @Test("marks supported versions inside the documented support window") func marksSupportedVersionsInsideSupportWindow() throws { let explicitURL = URL(fileURLWithPath: "/tmp/codex-explicit") - let recorder = CommandRecorder(pathVersionStandardOutput: "codex-cli 0.129.3") + let recorder = CommandRecorder(pathVersionStandardOutput: "codex-cli 0.130.3") let resolver = CodexCLIExecutableResolver( explicitExecutableURL: explicitURL, @@ -150,7 +150,7 @@ struct CodexCLIExecutableResolverTests { ) let resolution = try resolver.resolve() - #expect(resolution.compatibility == .supported(documentedWindow: "0.129.x")) + #expect(resolution.compatibility == .supported(documentedWindow: "0.130.x")) } @Test("marks older minor versions outside the documented support window") @@ -167,7 +167,7 @@ struct CodexCLIExecutableResolverTests { ) let resolution = try resolver.resolve() - #expect(resolution.compatibility == .outsideDocumentedWindow(documentedWindow: "0.129.x")) + #expect(resolution.compatibility == .outsideDocumentedWindow(documentedWindow: "0.130.x")) } @Test("marks unparseable version strings as unknown format") @@ -184,7 +184,7 @@ struct CodexCLIExecutableResolverTests { ) let resolution = try resolver.resolve() - #expect(resolution.compatibility == .unknownVersionFormat(documentedWindow: "0.129.x")) + #expect(resolution.compatibility == .unknownVersionFormat(documentedWindow: "0.130.x")) } } @@ -204,7 +204,7 @@ private final class CommandRecorder: @unchecked Sendable { init( pathVersionTerminationStatus: Int32 = 0, - pathVersionStandardOutput: String = "codex-cli 0.129.0", + pathVersionStandardOutput: String = "codex-cli 0.130.0", pathVersionStandardError: String = "", npmPrefixTerminationStatus: Int32 = 0, npmPrefixOutput: String = "/Users/galew/.npm-global", diff --git a/docs/maintainers/interactive-lifecycle-release-boundary.md b/docs/maintainers/interactive-lifecycle-release-boundary.md index 45a4c2a..14148e1 100644 --- a/docs/maintainers/interactive-lifecycle-release-boundary.md +++ b/docs/maintainers/interactive-lifecycle-release-boundary.md @@ -50,7 +50,7 @@ runtime while the app-server schema is moving quickly before v1. Current policy: - support the latest reviewed public Codex CLI minor release -- current reviewed minor release: `0.129.x` +- current reviewed minor release: `0.130.x` - widen back to a rolling window only after the latest generated-wire and public API boundaries have caught up with the current app-server shape - reassess this policy when Codex reaches a future major-version release diff --git a/docs/maintainers/quicktype-codegen-notes.md b/docs/maintainers/quicktype-codegen-notes.md index e1476e1..60260bd 100644 --- a/docs/maintainers/quicktype-codegen-notes.md +++ b/docs/maintainers/quicktype-codegen-notes.md @@ -71,21 +71,18 @@ inspecting upstream schema changes or quicktype regressions. ## Current generated batch The default generated batch currently stages against the local experimental -`v0.129.0` schema dump: +`v0.130.0` schema dump: -- `SCHEMA_VERSION=v0.129.0` +- `SCHEMA_VERSION=v0.130.0` - promoted output: `Sources/SwiftASB/Generated/CodexWire/Latest/CodexLifecycleV2Batch+JSONValue.swift` The promoted `Latest` snapshot is intentionally not swapped blindly just -because staging generation succeeds. The v0.129 experimental dump keeps the -v0.128 permission-profile shape, widens `serviceTier` from a closed generated -enum to an open string, adds plugin sharing and plugin skill-read request -families, adds standalone `process/*` request and notification families, adds -Windows sandbox readiness, adds hook compact event names, and adds thread and -turn metadata such as `threadSource`, `sessionId`, and `itemsView`. Promote -generated changes only after classifying public, observable-only, and internal -effects. +because staging generation succeeds. The v0.130 experimental dump removes the +device-key request families, adds `thread/turns/items/list`, adds approval and +guardian-review timing fields, changes plugin sharing/detail shapes, and removes +per-cwd extra user roots from `skills/list`. Promote generated changes only +after classifying public, observable-only, and internal effects. ## Compatibility Shim Policy @@ -268,7 +265,7 @@ still concentrated in a small number of fields: Those are now patched to `CodexWireJSONValue`. -The same patch step also keeps a few v0.129-required fields optional in the +The same patch step also keeps a few v0.130-required fields optional in the promoted Swift so the v0.128 drift-guard tests still decode older payloads: - `CodexWireThread.sessionID` diff --git a/docs/maintainers/v1-public-api-audit.md b/docs/maintainers/v1-public-api-audit.md index 863de96..7d53b32 100644 --- a/docs/maintainers/v1-public-api-audit.md +++ b/docs/maintainers/v1-public-api-audit.md @@ -2,7 +2,7 @@ This document is the working checklist for the `SwiftASB` v1 public API curation pass. The goal is to freeze a compact, Swift-native surface for the -supported app-server lifecycle before `v1.2.0`, not to expose every generated +supported app-server lifecycle before `v1.2.1`, not to expose every generated wire family. ## Current Public Source Inventory @@ -429,7 +429,7 @@ Use these decisions for every public symbol: - [x] Add symbol comments for every stable v1 public type and method that is not self-explanatory from its declaration. - Decision: complete for the `v1.2.0` release boundary. Default-bearing public + Decision: complete for the `v1.2.1` release boundary. Default-bearing public initializers and methods now document whether omission delegates to Codex, chooses a SwiftASB local-history/UI default, or applies an explicit safety default such as `.turn` or `.unchanged`. The source-level pass also covers the @@ -508,7 +508,7 @@ Use these decisions for every public symbol: Decision: covered by the startup, progress/approval, diagnostics/history, and SwiftUI observable companion walkthroughs in `Sources/SwiftASB/SwiftASB.docc/`. - [x] Update stale README release references before the next release. - Decision: README now names `v1.2.0` as the current released baseline. + Decision: README now names `v1.2.1` as the current released baseline. - [x] Confirm README, DocC, and this audit use the same v1 release boundary. Decision: README, DocC, and this audit now describe the same narrow v1 promise: app-server lifecycle, app-wide capability reads, stored-thread diff --git a/docs/maintainers/v1-public-api-symbol-inventory.md b/docs/maintainers/v1-public-api-symbol-inventory.md index 702d8a6..01d180e 100644 --- a/docs/maintainers/v1-public-api-symbol-inventory.md +++ b/docs/maintainers/v1-public-api-symbol-inventory.md @@ -761,7 +761,7 @@ The 2026-05-06 app-server schema promotion added several hand-owned public names - `CodexFS` now includes read-only filesystem watches: `WatchRequest`, `UnwatchRequest`, `Watch`, `ChangeEvent`, `watch(_:)`, and `unwatch(_:)`. - `CodexFS` now includes bounded file discovery and SwiftASB-owned fuzzy ranking over app-server-returned directory entries: `FileDiscoveryQD`, `FileDiscoveryResult`, `FileDiscoveryHit`, `FileDiscoveryHit.Kind`, and `discoverFiles(_:)`. - `CodexConfig` owns effective config reads: `ReadRequest`, `Snapshot`, `Layer`, `LayerMetadata`, `LayerSource`, `LayerSource.Kind`, `RequirementsSnapshot`, `read(_:)`, and `readRequirements()`. -- `CodexAppServer.CodexExtensions` owns app-server extension inventory: `AppListRequest`, `AppListPage`, `AppInfo`, `SkillListRequest`, `SkillListSnapshot`, `PluginListRequest`, `PluginListSnapshot`, `PluginReadRequest`, `PluginDetail`, `CollaborationModeList`, `listApps(_:)`, `listSkills(_:)`, `listPlugins(_:)`, `readPlugin(_:)`, and `listCollaborationModes()`. +- `CodexAppServer.CodexExtensions` owns app-server extension inventory: `AppListRequest`, `AppListPage`, `AppInfo`, `SkillListRequest`, `SkillListSnapshot`, `PluginListRequest`, `PluginListSnapshot`, `PluginReadRequest`, `PluginDetail`, `PluginHookSummary`, `CollaborationModeList`, `listApps(_:)`, `listSkills(_:)`, `listPlugins(_:)`, `readPlugin(_:)`, and `listCollaborationModes()`. - `CodexThread` now exposes thread goals: `Goal`, `Goal.Status`, `GoalSetRequest`, `readGoal()`, `setGoal(_:)`, and `clearGoal()`. - `CodexThreadEvent` now includes `.goalUpdated(_:)` and `.goalCleared(_:)` for app-server goal notifications. - `CodexThread.RecentFilesQD` and `CodexThread.RecentCommandsQD` describe repeatable recent-activity companion startup intent. diff --git a/scripts/generate-wire-types.sh b/scripts/generate-wire-types.sh old mode 100644 new mode 100755 index 06452cc..2b399fb --- a/scripts/generate-wire-types.sh +++ b/scripts/generate-wire-types.sh @@ -2,7 +2,7 @@ set -eu ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) -SCHEMA_VERSION=${SCHEMA_VERSION:-v0.129.0} +SCHEMA_VERSION=${SCHEMA_VERSION:-v0.130.0} SCHEMA_ROOT="$ROOT_DIR/codex-schemas/$SCHEMA_VERSION" DERIVED_DIR="$ROOT_DIR/tmp/derived-schemas/${SCHEMA_VERSION//./_}" OUT_DIR="$ROOT_DIR/tmp/quicktype-wire/${SCHEMA_VERSION//./_}" @@ -110,6 +110,8 @@ build_batch \ ThreadCompactStartResponse \ ThreadTurnsListParams \ ThreadTurnsListResponse \ + ThreadTurnsItemsListParams \ + ThreadTurnsItemsListResponse \ ThreadLoadedListParams \ ThreadLoadedListResponse \ ThreadGoalGetParams \ diff --git a/scripts/patch_quicktype_swift_any.py b/scripts/patch_quicktype_swift_any.py index 262a3e9..61355c5 100644 --- a/scripts/patch_quicktype_swift_any.py +++ b/scripts/patch_quicktype_swift_any.py @@ -131,7 +131,7 @@ def patch_codable_output(source: str) -> str: def patch_cross_version_compatibility(source: str) -> str: - """Keep the promoted v0.129 graph tolerant of v0.128 guardrail payloads.""" + """Keep the promoted v0.130 graph tolerant of older guardrail payloads.""" replacements = [ (" let completedAtMS: Int\n", " let completedAtMS: Int?\n"), (" let startedAtMS: Int\n", " let startedAtMS: Int?\n"),