opencode-session-port 0.1.2 → 0.1.4
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 +5 -5
- package/dist/tui.js +35 -3
- package/package.json +2 -2
- package/src/tui/adapters/location-resolver.ts +1 -1
- package/src/tui/wiring/composition.ts +2 -2
- package/src/tui/wiring/experimental-profile.ts +12 -5
- package/src/tui/wiring/index.ts +6 -5
- package/src/tui/wiring/install.ts +25 -15
- package/src/tui/wiring/reports.ts +51 -3
- package/src/tui.ts +19 -13
- package/tui.ts +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
SessionPort is a local plugin for the OpenCode TUI. It captures the sessions of your current project into a single password-encrypted artifact and restores them in another checkout, worktree, or machine. Artifacts travel with Git or any other channel you choose — SessionPort never runs Git commands itself.
|
|
6
6
|
|
|
7
|
-
> **Status: experimental pre-release (0.1.
|
|
7
|
+
> **Status: experimental pre-release (0.1.4).** SessionPort completed one bounded synthetic checkpoint/restore round trip on Windows with OpenCode v2.0.22. That test used test-only password and project-selector seams and staged source, not the npm tarball. On one real Windows OpenCode v2.0.22 host, a bounded diagnostic observed both commands registered (`commands=yes`) but hidden from the command palette and slash list until the keymap layer opted into global mode; 0.1.2 applies that global-mode registration fix, with focused command-registration tests and the TUI build passing. 0.1.3 additionally enables the commands on macOS and Linux through an explicit platform allowlist (`win32`/`darwin`/`linux`). 0.1.2 and 0.1.3 are published to npm (0.1.3 latest, per a read-only registry check); 0.1.4 is the current local candidate. 0.1.4 is a diagnostics/copy patch only: a failed checkpoint collection now appends a fixed sanitized reason and the known machine code (`Reason: <fixed reason> (<code>).`) for the 17 current collection codes; unknown codes keep the generic text and raw host/exception text is never shown. Collector behavior is unchanged, and no root cause is established for an earlier Windows collection failure; a user later reported a local checkpoint succeeding from the repository checkout, which is not attributed to this patch. macOS has one user-reported successful checkpoint only; Linux is untested; restore and cross-OS artifact interchange remain unestablished. End-to-end execution from the packed package has not been host-tested. Real modal interactions and loading the packed package in OpenCode have not been verified. The checkpoint format may change; no compatibility, security, or platform-support guarantee is made.
|
|
8
8
|
|
|
9
9
|
## Commands
|
|
10
10
|
|
|
@@ -16,7 +16,7 @@ Both commands are registered by the plugin as `sessionport.checkpoint` and `sess
|
|
|
16
16
|
## Requirements
|
|
17
17
|
|
|
18
18
|
- OpenCode with the v2 TUI plugin API (exercised with OpenCode v2.0.22).
|
|
19
|
-
- Windows. The
|
|
19
|
+
- Windows, macOS, or Linux. The build enables the commands on all three through an explicit platform allowlist (`win32`/`darwin`/`linux`); other platforms are not enabled. Windows has the bounded host evidence above; macOS and Linux are enabled but NOT host-tested, and cross-OS artifact interchange is UNVERIFIED.
|
|
20
20
|
|
|
21
21
|
## Install
|
|
22
22
|
|
|
@@ -24,17 +24,17 @@ Add the plugin to the `plugins` array in your global CLI-only config (`cli.json`
|
|
|
24
24
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
|
-
"plugins": ["opencode-session-port@0.1.
|
|
27
|
+
"plugins": ["opencode-session-port@0.1.4"]
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Or install the package plugin with the OpenCode CLI:
|
|
32
32
|
|
|
33
33
|
```sh
|
|
34
|
-
opencode plugin add opencode-session-port@0.1.
|
|
34
|
+
opencode plugin add opencode-session-port@0.1.4
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
`cli.json` is the documented OpenCode v2 config for CLI-only plugins: they run locally in the terminal and remain active when the CLI connects to a remote server. The published
|
|
37
|
+
`cli.json` is the documented OpenCode v2 config for CLI-only plugins: they run locally in the terminal and remain active when the CLI connects to a remote server. The npm registry shows 0.1.2 and 0.1.3 published (0.1.3 latest; read-only registry check); no published artifact has been loaded by OpenCode here, and no local package-verification record exists for the published 0.1.3 tarball. The 0.1.1 tarball passed npm consumer checks, but a direct Bun package-manager install timed out in the verification environment, so that install path remains unverified. The 0.1.2 tarball passed manifest/allowlist and packed-entry checks only. The 0.1.4 candidate tarball passed the same local manifest/allowlist and packed-entry checks (including the sanitized collection-failure copy); it is not published.
|
|
38
38
|
|
|
39
39
|
## Usage
|
|
40
40
|
|
package/dist/tui.js
CHANGED
|
@@ -5866,7 +5866,7 @@ function reportCheckpointFailure(error) {
|
|
|
5866
5866
|
case "capability":
|
|
5867
5867
|
return checkpointFailure("This OpenCode host does not expose the required session-listing capability; no artifact was written.");
|
|
5868
5868
|
case "collection":
|
|
5869
|
-
return checkpointFailure(
|
|
5869
|
+
return checkpointFailure(checkpointCollectionFailureMessage(error));
|
|
5870
5870
|
case "path-exists":
|
|
5871
5871
|
return checkpointFailure("An artifact already exists at that path; nothing was overwritten.");
|
|
5872
5872
|
case "artifact-path":
|
|
@@ -5885,6 +5885,15 @@ function reportCheckpointFailure(error) {
|
|
|
5885
5885
|
function checkpointFailure(message) {
|
|
5886
5886
|
return { title: "Checkpoint failed", message, variant: "error" };
|
|
5887
5887
|
}
|
|
5888
|
+
function checkpointCollectionFailureMessage(error) {
|
|
5889
|
+
if (!(error instanceof SessionCollectionError))
|
|
5890
|
+
return CHECKPOINT_COLLECTION_FAILURE;
|
|
5891
|
+
const code = error.code;
|
|
5892
|
+
if (!Object.hasOwn(CHECKPOINT_COLLECTION_REASONS, code))
|
|
5893
|
+
return CHECKPOINT_COLLECTION_FAILURE;
|
|
5894
|
+
const reason = CHECKPOINT_COLLECTION_REASONS[code];
|
|
5895
|
+
return `${CHECKPOINT_COLLECTION_FAILURE} Reason: ${reason} (${code}).`;
|
|
5896
|
+
}
|
|
5888
5897
|
function reportRestoreUsage() {
|
|
5889
5898
|
return {
|
|
5890
5899
|
title: "Restore needs an artifact path",
|
|
@@ -6039,6 +6048,7 @@ function classifyFailure(error) {
|
|
|
6039
6048
|
}
|
|
6040
6049
|
return "unknown";
|
|
6041
6050
|
}
|
|
6051
|
+
var CHECKPOINT_COLLECTION_FAILURE = "Collecting the project's sessions failed; no artifact was written.", CHECKPOINT_COLLECTION_REASONS;
|
|
6042
6052
|
var init_reports = __esm(() => {
|
|
6043
6053
|
init_files();
|
|
6044
6054
|
init_adapters();
|
|
@@ -6047,6 +6057,25 @@ var init_reports = __esm(() => {
|
|
|
6047
6057
|
init_planner();
|
|
6048
6058
|
init_sessions();
|
|
6049
6059
|
init_orchestration();
|
|
6060
|
+
CHECKPOINT_COLLECTION_REASONS = {
|
|
6061
|
+
"invalid-request": "the session collection request was invalid",
|
|
6062
|
+
"invalid-policy": "the session collection policy was invalid",
|
|
6063
|
+
"session-list-failed": "the host session list could not be read",
|
|
6064
|
+
"message-list-failed": "a session's message history could not be read",
|
|
6065
|
+
"malformed-session-page": "the host returned a malformed session list page",
|
|
6066
|
+
"malformed-message-page": "the host returned a malformed message list page",
|
|
6067
|
+
"malformed-session-record": "the host returned a malformed session record",
|
|
6068
|
+
"malformed-message-record": "the host returned a malformed message record",
|
|
6069
|
+
"session-project-mismatch": "a listed session did not belong to the requested project",
|
|
6070
|
+
"duplicate-session-id": "the host returned a session more than once",
|
|
6071
|
+
"duplicate-message-id": "the host returned a message more than once",
|
|
6072
|
+
"repeated-session-cursor": "the host repeated a session list cursor",
|
|
6073
|
+
"repeated-message-cursor": "the host repeated a message list cursor",
|
|
6074
|
+
"session-limit-exceeded": "the session count exceeded the configured limit",
|
|
6075
|
+
"message-limit-exceeded": "a session's message count exceeded the configured limit",
|
|
6076
|
+
"total-message-limit-exceeded": "the total message count exceeded the configured limit",
|
|
6077
|
+
"unsettled-message": "the project history contains an unfinished message"
|
|
6078
|
+
};
|
|
6050
6079
|
});
|
|
6051
6080
|
|
|
6052
6081
|
// src/tui/wiring/composition.ts
|
|
@@ -10194,7 +10223,7 @@ var init_sessionport_commands = __esm(() => {
|
|
|
10194
10223
|
|
|
10195
10224
|
// src/tui/wiring/install.ts
|
|
10196
10225
|
async function installSessionPortWiring(context, options = {}) {
|
|
10197
|
-
if ((options.platform ?? process.platform)
|
|
10226
|
+
if (!SUPPORTED_PLATFORMS.includes(options.platform ?? process.platform))
|
|
10198
10227
|
return;
|
|
10199
10228
|
const cryptoFactory = await (options.createCryptoFactory ?? createExperimentalCryptoFactory)();
|
|
10200
10229
|
const idDeriver = await (options.createIdDeriver ?? createRemappedIdDeriver)();
|
|
@@ -10222,6 +10251,7 @@ async function installSessionPortWiring(context, options = {}) {
|
|
|
10222
10251
|
async function createExperimentalCryptoFactory() {
|
|
10223
10252
|
return createCheckpointCryptoFactory([EXPERIMENTAL_CRYPTO_POLICY_DEFINITION]);
|
|
10224
10253
|
}
|
|
10254
|
+
var SUPPORTED_PLATFORMS;
|
|
10225
10255
|
var init_install = __esm(() => {
|
|
10226
10256
|
init_crypto();
|
|
10227
10257
|
init_restore();
|
|
@@ -10231,6 +10261,7 @@ var init_install = __esm(() => {
|
|
|
10231
10261
|
init_sessionport_commands();
|
|
10232
10262
|
init_composition();
|
|
10233
10263
|
init_experimental_profile();
|
|
10264
|
+
SUPPORTED_PLATFORMS = ["win32", "darwin", "linux"];
|
|
10234
10265
|
});
|
|
10235
10266
|
|
|
10236
10267
|
// src/tui/wiring/index.ts
|
|
@@ -10263,10 +10294,11 @@ var init_wiring = __esm(() => {
|
|
|
10263
10294
|
|
|
10264
10295
|
// src/tui.ts
|
|
10265
10296
|
import { Plugin } from "@opencode/plugin/tui";
|
|
10297
|
+
var SUPPORTED_PLATFORMS2 = ["win32", "darwin", "linux"];
|
|
10266
10298
|
var tui_default = Plugin.define({
|
|
10267
10299
|
id: "sessionport",
|
|
10268
10300
|
async setup(context) {
|
|
10269
|
-
if (process.platform
|
|
10301
|
+
if (!SUPPORTED_PLATFORMS2.includes(process.platform))
|
|
10270
10302
|
return;
|
|
10271
10303
|
const { installSessionPortWiring: installSessionPortWiring2 } = await Promise.resolve().then(() => (init_wiring(), exports_wiring));
|
|
10272
10304
|
return installSessionPortWiring2(context);
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-session-port",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"description": "SessionPort: experimental
|
|
6
|
+
"description": "SessionPort: experimental OpenCode TUI plugin for password-encrypted session checkpoint and restore.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"opencode",
|
|
9
9
|
"opencode-plugin",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Experimental project-location resolver over the public TUI client
|
|
3
|
-
* `context.client.location.get` method (
|
|
3
|
+
* `context.client.location.get` method (experimental wiring lane).
|
|
4
4
|
*
|
|
5
5
|
* Scope and boundaries (owner decision 2026-10-11, decision-log F):
|
|
6
6
|
* - `Plugin.Context.location` carries only a directory, so the experimental
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SessionPort TUI command composition (Phase 2 experimental
|
|
2
|
+
* SessionPort TUI command composition (Phase 2 experimental wiring).
|
|
3
3
|
*
|
|
4
4
|
* `createSessionPortHandlers` composes the completed Phase 2-4 seams into the
|
|
5
5
|
* two injected command callbacks consumed by `installSessionPortCommands`:
|
|
@@ -112,7 +112,7 @@ export interface SessionPortHandlerDependencies {
|
|
|
112
112
|
readonly showRestoreReport: (model: RestoreReportPresentationModel) => void;
|
|
113
113
|
/** Masked password provider (real UI or a fake in tests). */
|
|
114
114
|
readonly requestPassword: SessionPortPasswordProvider;
|
|
115
|
-
/** Initialized crypto factory; production uses the experimental
|
|
115
|
+
/** Initialized crypto factory; production uses the experimental profile (historical `-win-` selector name). */
|
|
116
116
|
readonly cryptoFactory: CheckpointCryptoFactory;
|
|
117
117
|
/** Remapped-ID deriver for restore planning. */
|
|
118
118
|
readonly idDeriver: RemappedIdDeriver;
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Experimental runtime defaults (Phase 2 wiring; originally recorded for the
|
|
3
|
+
* Windows-first lane).
|
|
4
|
+
*
|
|
5
|
+
* The `sessionport-experimental-win-v1` selector name is historical and is NOT
|
|
6
|
+
* portability evidence: enabling macOS/Linux changes no profile, format, or cap
|
|
7
|
+
* value below.
|
|
3
8
|
*
|
|
4
9
|
* These are the explicitly selected, owner-authorized experimental values
|
|
5
|
-
* recorded in `docs/v1/decision-log.md` (2026-10-11 entries) for
|
|
6
|
-
* experimental build
|
|
7
|
-
*
|
|
10
|
+
* recorded in `docs/v1/decision-log.md` (2026-10-11 entries) for the Windows
|
|
11
|
+
* experimental build; they now apply unchanged to the experimental macOS/Linux
|
|
12
|
+
* enablement, which is NOT host-tested. They are NOT a production approval, not
|
|
13
|
+
* a format freeze, and not a general memory budget, compatibility, or support
|
|
14
|
+
* claim.
|
|
8
15
|
*
|
|
9
16
|
* - Crypto profile `sessionport-experimental-win-v1`: profile code 1,
|
|
10
17
|
* Argon2id13 `crypto_pwhash`, `opslimit=2`, `memlimit=128 MiB`, the current
|
|
@@ -22,7 +29,7 @@ import type { CryptoPolicy } from "../../core/contracts/index.ts";
|
|
|
22
29
|
import type { CryptoPolicyDefinition } from "../../core/crypto/index.ts";
|
|
23
30
|
import type { SessionCollectionPolicy } from "../../core/sessions/index.ts";
|
|
24
31
|
|
|
25
|
-
/** Explicit allow-listed policy id for the
|
|
32
|
+
/** Explicit allow-listed policy id for the experimental profile (historical `-win-` name). */
|
|
26
33
|
export const EXPERIMENTAL_CRYPTO_POLICY_ID = "sessionport-experimental-win-v1";
|
|
27
34
|
|
|
28
35
|
/** The explicit policy selector passed to the crypto factory; never read from an artifact. */
|
package/src/tui/wiring/index.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* SessionPort TUI wiring (Phase 2 experimental Windows lane).
|
|
3
3
|
*
|
|
4
4
|
* Public surface:
|
|
5
|
-
* - `installSessionPortWiring(context, options?)` —
|
|
6
|
-
* pinned-sodium initialization,
|
|
7
|
-
*
|
|
8
|
-
* slot disposer returned unchanged.
|
|
5
|
+
* - `installSessionPortWiring(context, options?)` — install root gated to the
|
|
6
|
+
* explicit `win32`/`darwin`/`linux` allowlist: pinned-sodium initialization,
|
|
7
|
+
* real adapters/artifact I/O/password modal, command registration through the
|
|
8
|
+
* existing component-scoped seam, and the slot disposer returned unchanged.
|
|
9
9
|
* - `createSessionPortHandlers(dependencies)` — fully injected composition of
|
|
10
10
|
* the `/checkpoint` and `/restore` command callbacks (fake-based testable).
|
|
11
11
|
* - Experimental runtime defaults (crypto profile + collection bounds +
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* is written, only encrypted artifacts are read, and every failure report is
|
|
17
17
|
* generic/sanitized. No production approval, format freeze, or platform-support
|
|
18
18
|
* claim is made (O-01/O-02/O-03 remain PARTIALLY RESOLVED for the experimental
|
|
19
|
-
*
|
|
19
|
+
* build only — the `-win-` profile selector name is historical; OQ-16 stays
|
|
20
|
+
* OPEN).
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
export {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/**
|
|
3
|
-
* SessionPort TUI install/composition root (Phase 2 experimental
|
|
3
|
+
* SessionPort TUI install/composition root (Phase 2 experimental wiring).
|
|
4
4
|
*
|
|
5
5
|
* `installSessionPortWiring` is the only host-facing composition entry:
|
|
6
6
|
*
|
|
7
|
-
* - **
|
|
8
|
-
* `undefined` immediately: no sodium
|
|
9
|
-
* no command registration, and no
|
|
10
|
-
*
|
|
7
|
+
* - **Explicit platform allowlist (`win32`/`darwin`/`linux`), experimental
|
|
8
|
+
* only.** On any other platform it returns `undefined` immediately: no sodium
|
|
9
|
+
* initialization, no host-context access, no command registration, and no
|
|
10
|
+
* platform-support claim. Windows has bounded actual-host evidence; macOS and
|
|
11
|
+
* Linux are enabled but NOT host-tested, and cross-OS artifact interchange is
|
|
12
|
+
* UNVERIFIED.
|
|
13
|
+
* - On an allowed platform it initializes the pinned `libsodium-wrappers-sumo@0.8.4`
|
|
11
14
|
* runtime first (`await sodium.ready` plus primitive/constant validation
|
|
12
15
|
* through `createCheckpointCryptoFactory`; `createRemappedIdDeriver` also
|
|
13
16
|
* awaits/validates it) and fails closed: a rejected initialization aborts
|
|
@@ -25,8 +28,8 @@
|
|
|
25
28
|
* This module statically imports the designer-owned UI modules (password modal
|
|
26
29
|
* and restore report dialog), which import the optional UI peers
|
|
27
30
|
* (`@opentui/solid`, `solid-js`). The entry (`src/tui.ts`) imports this module
|
|
28
|
-
* dynamically only after its
|
|
29
|
-
*
|
|
31
|
+
* dynamically only after its own allowlist check, so unsupported-platform
|
|
32
|
+
* plugin setup never evaluates those modules.
|
|
30
33
|
*
|
|
31
34
|
* Test seams (platform/factory/deriver/password provider) exist only for
|
|
32
35
|
* fake-based tests; production calls use the real defaults. The real password
|
|
@@ -49,7 +52,7 @@ import { EXPERIMENTAL_CRYPTO_POLICY_DEFINITION } from "./experimental-profile.ts
|
|
|
49
52
|
export interface SessionPortInstallOptions {
|
|
50
53
|
/** Platform gate override for tests; defaults to `process.platform`. */
|
|
51
54
|
readonly platform?: string;
|
|
52
|
-
/** Crypto-factory seam; the default creates the experimental
|
|
55
|
+
/** Crypto-factory seam; the default creates the experimental profile (historical `-win-` selector name) and awaits pinned sodium. */
|
|
53
56
|
readonly createCryptoFactory?: () => Promise<CheckpointCryptoFactory>;
|
|
54
57
|
/** Remapped-ID deriver seam; the default awaits/validates pinned sodium. */
|
|
55
58
|
readonly createIdDeriver?: () => Promise<RemappedIdDeriver>;
|
|
@@ -57,18 +60,21 @@ export interface SessionPortInstallOptions {
|
|
|
57
60
|
readonly requestPassword?: SessionPortPasswordProvider;
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
/** Explicit platform allowlist; unsupported platforms fail closed before any host access. */
|
|
64
|
+
const SUPPORTED_PLATFORMS: readonly string[] = ["win32", "darwin", "linux"];
|
|
65
|
+
|
|
60
66
|
/**
|
|
61
|
-
* Installs the SessionPort commands for the experimental
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
67
|
+
* Installs the SessionPort commands for the experimental lane and returns the
|
|
68
|
+
* slot disposer, or `undefined` when this platform is not registered. Fails
|
|
69
|
+
* closed (rejects) when the pinned runtime or the host command surface is
|
|
70
|
+
* unavailable; nothing is registered in that case.
|
|
65
71
|
*/
|
|
66
72
|
export async function installSessionPortWiring(
|
|
67
73
|
context: Plugin.Context,
|
|
68
74
|
options: SessionPortInstallOptions = {},
|
|
69
75
|
): Promise<(() => void) | undefined> {
|
|
70
|
-
//
|
|
71
|
-
if ((options.platform ?? process.platform)
|
|
76
|
+
// Explicit allowlist gate: no registration and no host access on unsupported platforms.
|
|
77
|
+
if (!SUPPORTED_PLATFORMS.includes(options.platform ?? process.platform)) return undefined;
|
|
72
78
|
|
|
73
79
|
// Pinned-runtime initialization (fail closed): the default factory awaits
|
|
74
80
|
// `sodium.ready` and validates every primitive the provisional format needs;
|
|
@@ -102,7 +108,11 @@ export async function installSessionPortWiring(
|
|
|
102
108
|
return installSessionPortCommands(context, handlers);
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
/**
|
|
111
|
+
/**
|
|
112
|
+
* Default factory: the experimental profile over the pinned runtime. The
|
|
113
|
+
* `sessionport-experimental-win-v1` selector name is historical and is not
|
|
114
|
+
* portability evidence; macOS/Linux enablement changes no profile value.
|
|
115
|
+
*/
|
|
106
116
|
async function createExperimentalCryptoFactory(): Promise<CheckpointCryptoFactory> {
|
|
107
117
|
return createCheckpointCryptoFactory([EXPERIMENTAL_CRYPTO_POLICY_DEFINITION]);
|
|
108
118
|
}
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
* Every function here returns a fixed, generic notification object; raw host
|
|
5
5
|
* or exception text is NEVER interpolated. Classification uses stable error
|
|
6
6
|
* classes/codes only, never message text, so a failure report cannot leak
|
|
7
|
-
* session IDs, paths, host internals, or password material.
|
|
7
|
+
* session IDs, paths, host internals, or password material. Checkpoint
|
|
8
|
+
* collection failures additionally append a fixed human reason and the exact
|
|
9
|
+
* machine code, but only for `SessionCollectionError` codes on an explicit
|
|
10
|
+
* allowlist; unknown or forged codes keep the generic text. Exact user-facing
|
|
8
11
|
* copy remains OPEN (OQ-15); these strings are the wiring lane's generic
|
|
9
12
|
* sanitized categories.
|
|
10
13
|
*
|
|
@@ -31,7 +34,7 @@ import { HostAdapterError, ProjectDestinationError } from "../adapters/index.ts"
|
|
|
31
34
|
import { LocationResolutionError } from "../adapters/location-resolver.ts";
|
|
32
35
|
import { CryptoContainerError } from "../../core/crypto/errors.ts";
|
|
33
36
|
import { RestorePlanError } from "../../core/restore/planner.ts";
|
|
34
|
-
import { SessionCollectionError } from "../../core/sessions/index.ts";
|
|
37
|
+
import { SessionCollectionError, type SessionCollectionErrorCode } from "../../core/sessions/index.ts";
|
|
35
38
|
import { RESTORE_PASSWORD_ATTEMPT_LIMIT } from "../orchestration/index.ts";
|
|
36
39
|
import type { CheckpointOutcome, RestoreExecutionResult } from "../orchestration/index.ts";
|
|
37
40
|
import type {
|
|
@@ -94,7 +97,7 @@ export function reportCheckpointFailure(error: unknown): SessionPortNotification
|
|
|
94
97
|
case "capability":
|
|
95
98
|
return checkpointFailure("This OpenCode host does not expose the required session-listing capability; no artifact was written.");
|
|
96
99
|
case "collection":
|
|
97
|
-
return checkpointFailure(
|
|
100
|
+
return checkpointFailure(checkpointCollectionFailureMessage(error));
|
|
98
101
|
case "path-exists":
|
|
99
102
|
return checkpointFailure("An artifact already exists at that path; nothing was overwritten.");
|
|
100
103
|
case "artifact-path":
|
|
@@ -116,6 +119,51 @@ function checkpointFailure(message: string): SessionPortNotification {
|
|
|
116
119
|
return { title: "Checkpoint failed", message, variant: "error" };
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
/** Existing generic checkpoint collection failure copy; also the fallback for unknown codes. */
|
|
123
|
+
const CHECKPOINT_COLLECTION_FAILURE = "Collecting the project's sessions failed; no artifact was written.";
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Fixed human reason per known `SessionCollectionError` code. This record is
|
|
127
|
+
* the explicit allowlist: the raw code is echoed to the user only when it
|
|
128
|
+
* exactly matches one of these keys, and the raw `error.message` (which can
|
|
129
|
+
* contain session/project IDs, paths, and host error text) is never used.
|
|
130
|
+
* Adding a code to `SessionCollectionErrorCode` fails compilation until a
|
|
131
|
+
* fixed reason is added here.
|
|
132
|
+
*/
|
|
133
|
+
const CHECKPOINT_COLLECTION_REASONS: Readonly<Record<SessionCollectionErrorCode, string>> = {
|
|
134
|
+
"invalid-request": "the session collection request was invalid",
|
|
135
|
+
"invalid-policy": "the session collection policy was invalid",
|
|
136
|
+
"session-list-failed": "the host session list could not be read",
|
|
137
|
+
"message-list-failed": "a session's message history could not be read",
|
|
138
|
+
"malformed-session-page": "the host returned a malformed session list page",
|
|
139
|
+
"malformed-message-page": "the host returned a malformed message list page",
|
|
140
|
+
"malformed-session-record": "the host returned a malformed session record",
|
|
141
|
+
"malformed-message-record": "the host returned a malformed message record",
|
|
142
|
+
"session-project-mismatch": "a listed session did not belong to the requested project",
|
|
143
|
+
"duplicate-session-id": "the host returned a session more than once",
|
|
144
|
+
"duplicate-message-id": "the host returned a message more than once",
|
|
145
|
+
"repeated-session-cursor": "the host repeated a session list cursor",
|
|
146
|
+
"repeated-message-cursor": "the host repeated a message list cursor",
|
|
147
|
+
"session-limit-exceeded": "the session count exceeded the configured limit",
|
|
148
|
+
"message-limit-exceeded": "a session's message count exceeded the configured limit",
|
|
149
|
+
"total-message-limit-exceeded": "the total message count exceeded the configured limit",
|
|
150
|
+
"unsettled-message": "the project history contains an unfinished message",
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Checkpoint collection failure copy: the existing generic message plus
|
|
155
|
+
* `Reason: <fixed human reason> (<known code>).` for a `SessionCollectionError`
|
|
156
|
+
* whose code is on the allowlist. Any other error or code keeps the generic
|
|
157
|
+
* text; only allowlisted literal code strings are ever echoed.
|
|
158
|
+
*/
|
|
159
|
+
function checkpointCollectionFailureMessage(error: unknown): string {
|
|
160
|
+
if (!(error instanceof SessionCollectionError)) return CHECKPOINT_COLLECTION_FAILURE;
|
|
161
|
+
const code: string = error.code;
|
|
162
|
+
if (!Object.hasOwn(CHECKPOINT_COLLECTION_REASONS, code)) return CHECKPOINT_COLLECTION_FAILURE;
|
|
163
|
+
const reason = CHECKPOINT_COLLECTION_REASONS[code as SessionCollectionErrorCode];
|
|
164
|
+
return `${CHECKPOINT_COLLECTION_FAILURE} Reason: ${reason} (${code}).`;
|
|
165
|
+
}
|
|
166
|
+
|
|
119
167
|
/* ------------------------------------------------------------------------- */
|
|
120
168
|
/* Restore reports */
|
|
121
169
|
/* ------------------------------------------------------------------------- */
|
package/src/tui.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Plugin } from "@opencode/plugin/tui"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* SessionPort TUI plugin entrypoint (Phase 2 experimental
|
|
4
|
+
* SessionPort TUI plugin entrypoint (Phase 2 experimental wiring).
|
|
5
5
|
*
|
|
6
|
-
* Setup checks `process.platform` first and only on
|
|
6
|
+
* Setup checks `process.platform` first and only on an explicitly allowed
|
|
7
|
+
* platform (`win32`, `darwin`, or `linux`) dynamically imports
|
|
7
8
|
* `installSessionPortWiring`, which:
|
|
8
|
-
* - registers `/checkpoint` and `/restore` only on
|
|
9
|
-
*
|
|
10
|
-
* platform-support claim;
|
|
9
|
+
* - registers `/checkpoint` and `/restore` only on those platforms; every other
|
|
10
|
+
* platform gets no registration and no platform-support claim;
|
|
11
11
|
* - initializes the pinned `libsodium-wrappers-sumo@0.8.4` runtime first and
|
|
12
12
|
* fails closed if it is unavailable (setup rejects; nothing is registered);
|
|
13
13
|
* - registers commands inside the existing app-slot/keymap component scope
|
|
@@ -15,19 +15,25 @@ import { Plugin } from "@opencode/plugin/tui"
|
|
|
15
15
|
*
|
|
16
16
|
* The platform gate deliberately runs before the dynamic import: the wiring
|
|
17
17
|
* module statically imports the designer-owned UI modules, which import the
|
|
18
|
-
* optional UI peers (`@opentui/solid`, `solid-js`).
|
|
19
|
-
* therefore never evaluates them, and no OpenTUI dependency is added to
|
|
20
|
-
* package surface.
|
|
18
|
+
* optional UI peers (`@opentui/solid`, `solid-js`). Unsupported-platform plugin
|
|
19
|
+
* setup therefore never evaluates them, and no OpenTUI dependency is added to
|
|
20
|
+
* the package surface.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* Platform status: Windows has bounded actual-host evidence; macOS and Linux
|
|
23
|
+
* are owner-approved experimental targets enabled here but NOT host-tested, and
|
|
24
|
+
* cross-OS artifact interchange is UNVERIFIED. Command handlers own their
|
|
25
|
+
* errors and report sanitized copy; no raw host error crosses to the host. The
|
|
26
|
+
* active experimental defaults are recorded in `docs/v1/decision-log.md`; this
|
|
27
|
+
* is not a production approval or format freeze.
|
|
26
28
|
*/
|
|
29
|
+
|
|
30
|
+
/** Explicit allowlist; unsupported platforms fail closed before any host call. */
|
|
31
|
+
const SUPPORTED_PLATFORMS: readonly string[] = ["win32", "darwin", "linux"]
|
|
32
|
+
|
|
27
33
|
export default Plugin.define({
|
|
28
34
|
id: "sessionport",
|
|
29
35
|
async setup(context) {
|
|
30
|
-
if (process.platform
|
|
36
|
+
if (!SUPPORTED_PLATFORMS.includes(process.platform)) return undefined
|
|
31
37
|
const { installSessionPortWiring } = await import("./tui/wiring/index.ts")
|
|
32
38
|
return installSessionPortWiring(context)
|
|
33
39
|
},
|
package/tui.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* SessionPort TUI entrypoint shim for the host's directory-based plugin
|
|
3
3
|
* resolution (`Host.resolve` probes `<directory>/tui` by path, before any
|
|
4
4
|
* package `exports` lookup). It re-exports the product entrypoint unchanged;
|
|
5
|
-
* all behavior (
|
|
5
|
+
* all behavior (platform allowlist gate, pinned-sodium initialization, deferred
|
|
6
6
|
* `./tui/wiring/index.ts` import) stays in `src/tui.ts`.
|
|
7
7
|
*
|
|
8
8
|
* This file is part of the packaged `files` allowlist so an installed copy of
|