@immediately-run/sdk 0.56.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/editor.ts"],"sourcesContent":["import { protocolRequest } from './sandboxUtils';\nimport { SCHEMES } from './protocolSchemes';\nimport { PROTOCOL_EDITOR } from './generated/protocol';\n\n/**\n * Open a working-tree file in the immediately.run host editor (UI_AS_APPS_SPEC §4 —\n * the file explorer's click-to-open). This is an INTENT: the app asks, the HOST\n * validates the path and drives the CodeMirror editor — the editor itself stays\n * host-owned (§2 recursion boundary), so an app can never own or script it beyond\n * \"please show this file\".\n *\n * Requires the elevated `editor:open` capability — a previewed app does not hold it\n * (it must not move the host's focus), so only a system app whose binding grants it\n * (the file explorer) can call this; anyone else is refused at the gate.\n */\n\n/** An error from {@link openInEditor}, carrying a machine-readable `.code`. */\nexport interface EditorOpenError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:open` (or `editor:reveal`, for a `reveal`)\n | 'not-found' // no such file in the live working tree (the host never creates)\n | 'invalid-params' // the path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session to open files in\n | 'unknown';\n}\n\ntype EditorResult = { ok: true; data: unknown } | { ok: false; code: string; message: string };\n\nconst editorRequest = async (method: string, arg: Record<string, unknown>): Promise<void> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_EDITOR], method, [arg])) as EditorResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `editor ${method} failed`) as EditorWriteError;\n err.code = (res?.code as EditorWriteError['code']) ?? 'unknown';\n throw err;\n }\n};\n\n/** Where in a file to land when opening it (R3-388). 1-indexed `line`, matching every\n * diagnostic producer that feeds it (`tsc`, `eslint`, `BuildError`) and both VS Code\n * and IntelliJ. A `line` past end-of-file CLAMPS to the last line rather than\n * erroring — a diagnostic outlives the edit that shortened the file, and landing\n * close beats refusing to navigate. */\nexport interface EditorSelection {\n line: number;\n column?: number;\n}\n\n/** Options for {@link openInEditor} (R3-389). */\nexport interface EditorOpenOptions {\n /** Also bring the user to the editor, ACROSS activities (TOOLS_ACTIVITY_SPEC §5.2).\n * An app that owns the main pane (the Tools activity's runner sits where the editor\n * would) cannot rely on the file simply becoming visible — the editor is not on\n * screen — so this asks the host to switch to the activity that owns it.\n *\n * This is the elevated `editor:reveal` capability, not `editor:open`: a frame\n * without it is refused `forbidden` for the whole call (the file is NOT opened —\n * never silently opened-without-moving). The host decides whether the user actually\n * moves: it needs a real user gesture (a click in your frame within the last few\n * seconds counts; a call on a timer or on run completion does not) and is\n * rate-limited. The promise resolves the same either way, so treat a resolved\n * reveal as \"asked\", not \"moved\", and keep a visible fallback control. Where the\n * host owns the editor activity is host state; nothing here can name it. */\n reveal?: boolean;\n}\n\n/**\n * Ask the host to open `path` (a repo-relative working-tree path, e.g. `src/App.tsx`\n * or `/src/App.tsx`) in the editor. Resolves once the editor switches to it; rejects\n * with an {@link EditorOpenError} (`.code`) if the path is invalid, missing, or this\n * app may not open files.\n *\n * Pass `selection` to land the caret on a specific line — what a problems list needs\n * to make a diagnostic clickable. It widens nothing: a selection says where to look\n * inside a file the caller could already open, and the capability is unchanged\n * (`editor:open`).\n *\n * Pass `{ reveal: true }` to ALSO bring the user to the editor across activities —\n * see {@link EditorOpenOptions.reveal}; that one does need the elevated\n * `editor:reveal`, and is refused outright without it.\n *\n * Older hosts ignore `selection` and open the file at its existing position, so a\n * caller may pass it unconditionally. `reveal` is only sent when true, so a host that\n * predates it sees a plain open.\n */\nexport const openInEditor = (path: string, selection?: EditorSelection, opts?: EditorOpenOptions): Promise<void> =>\n editorRequest('open', {\n path,\n ...(selection ? { selection } : {}),\n ...(opts?.reveal === true ? { reveal: true } : {}),\n });\n\n/**\n * Where to land when entering the edit experience (EDITOR_FIRST_EDITING_SPEC §6\n * Delta A). v1 supports only an optional repo-relative `path` in the CURRENT repo\n * (self-scoped — the app you are already running; the host navigates within the\n * current route, never to another repo). A URI or `..` path is refused\n * `invalid-params`. Editing a file in one of your *mounts* (a space) is the\n * `edit-file` task, not this.\n */\nexport interface EditTarget {\n /** A repo-relative working-tree path in the current repo to focus once in edit\n * mode (e.g. `src/App.tsx`). Omit to edit the current route's entry. */\n path?: string;\n}\n\n/** An error from {@link requestEdit}, carrying a machine-readable `.code`. */\nexport interface RequestEditError extends Error {\n code:\n | 'read-only' // editing isn't possible here (a `ro` mount / anonymous viewer) — HIDE the affordance\n | 'forbidden' // the host refuses (e.g. a cross-repo / out-of-scope target)\n | 'invalid-params' // the target was malformed (URI / `..` / a non-current repo)\n | 'no-target' // there is no host editor session to enter\n | 'unknown';\n}\n\n/**\n * Ask the host to enter the **edit experience** for the app you are running —\n * the present→edit transition (`/present/...` → `/edit/...`) an app cannot make\n * itself. This is an INTENT (§2 recursion boundary): the app asks, the HOST\n * performs the visible, user-observable navigation and draws all editor chrome;\n * the app never navigates or paints chrome.\n *\n * Use it to offer an \"edit this\" affordance from a run/present-mode app that opens\n * the app's own source in the platform editor — instead of shipping a bespoke\n * in-app editor (EDITOR_FIRST_EDITING_SPEC §1).\n *\n * Resolves once the host begins the transition; rejects with a\n * {@link RequestEditError} (`.code`). Treat `read-only`/`forbidden` as \"editing is\n * not available — hide the affordance,\" never as an error to surface to the user.\n */\nexport const requestEdit = (target?: EditTarget): Promise<void> =>\n editorRequest('requestEdit', target ? { ...target } : {});\n\n// ---------------------------------------------------------------------------\n// Editor SESSION management (EDITOR_AS_APP_SPEC §5.1; editor-as-app plan Phase\n// 03). Unlike `openInEditor` (the explorer's cross-app intent, `editor:open`),\n// these drive the editor's OWN open-tab set + active file, so they are gated by\n// the editor app's `editor:document` capability — a file explorer holding only\n// `editor:open` cannot call them. The host re-validates the path against the live\n// working tree; the editor itself stays host-owned (§2 recursion boundary).\n// ---------------------------------------------------------------------------\n\n/** An error from a session intent ({@link setActiveFile} / {@link closeFile}),\n * carrying a machine-readable `.code`. */\nexport interface EditorSessionError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:document`\n | 'not-found' // no such file in the live working tree\n | 'invalid-params' // the path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session\n | 'unknown';\n}\n\n/** Switch the editor's active file to `path`, opening it (adding a tab) if it is\n * not already open — native `setActiveFile` parity. Rejects with an\n * {@link EditorSessionError} (`.code`) if the path is missing/invalid or this app\n * lacks `editor:document`. */\nexport const setActiveFile = (path: string): Promise<void> => editorRequest('setActive', { path });\n\n/** Close `path`'s tab in the editor (remove it from the open set) — native\n * `closeFile` parity. Rejects with an {@link EditorSessionError} (`.code`). */\nexport const closeFile = (path: string): Promise<void> => editorRequest('close', { path });\n\n// ---------------------------------------------------------------------------\n// Working-tree mutation (UI_AS_APPS_SPEC §4 / EDITOR_AS_APP_SPEC §5.2). The file\n// explorer NAMES a working-tree path and the HOST performs the COW write (and\n// refreshes the preview) — the app holds no write port; it asks. Gated by the\n// first-party `editor:write` capability, so only a first-party chrome app (the\n// file explorer) can call these; anyone else is refused at the gate.\n// ---------------------------------------------------------------------------\n\n/** An error from a working-tree mutation, carrying a machine-readable `.code`. */\nexport interface EditorWriteError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:write` (first-party-only)\n | 'not-found' // the target file/folder does not exist (delete/rename)\n | 'exists' // the target already exists (create/rename would clobber)\n | 'protected' // the host refuses to delete this file (e.g. package.json)\n | 'too-large' // an upload exceeds the host's size limit\n | 'invalid-params' // a path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session\n | 'unknown';\n}\n\n/** Create an empty working-tree file at `path` and open it. Rejects `exists` if a\n * file is already there. */\nexport const createFile = (path: string): Promise<void> => editorRequest('createFile', { path });\n\n/** Create a working-tree folder at `path` (materialised with a `.gitkeep`). */\nexport const createFolder = (path: string): Promise<void> => editorRequest('createFolder', { path });\n\n/** Delete a working-tree file, or a folder and everything under it. Rejects\n * `protected` for files the host won't remove, `not-found` if absent. */\nexport const deleteEntry = (path: string): Promise<void> => editorRequest('deleteEntry', { path });\n\n/** Rename/move a working-tree file from `from` to `to`. Rejects `exists` if `to`\n * is taken, `not-found` if `from` is absent. */\nexport const renameEntry = (from: string, to: string): Promise<void> => editorRequest('rename', { from, to });\n\n/** Upload binary/text `bytes` to a working-tree file at `path`. Rejects\n * `too-large` past the host's size limit. The bytes are transferred (zero-copy). */\nexport const uploadFile = (path: string, bytes: Uint8Array): Promise<void> => editorRequest('upload', { path, bytes });\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAgC;AAChC,6BAAwB;AACxB,sBAAgC;AA0BhC,MAAM,gBAAgB,OAAO,QAAgB,QAAgD;AAC3F,QAAM,MAAO,UAAM,qCAAgB,+BAAQ,+BAAe,GAAG,QAAQ,CAAC,GAAG,CAAC;AAC1E,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU,MAAM,SAAS;AAC/D,QAAI,OAAQ,KAAK,QAAqC;AACtD,UAAM;AAAA,EACR;AACF;AAiDO,MAAM,eAAe,CAAC,MAAc,WAA6B,SACtE,cAAc,QAAQ;AAAA,EACpB;AAAA,EACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,EACjC,GAAI,MAAM,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;AAClD,CAAC;AAyCI,MAAM,cAAc,CAAC,WAC1B,cAAc,eAAe,SAAS,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;AA0BnD,MAAM,gBAAgB,CAAC,SAAgC,cAAc,aAAa,EAAE,KAAK,CAAC;AAI1F,MAAM,YAAY,CAAC,SAAgC,cAAc,SAAS,EAAE,KAAK,CAAC;AAyBlF,MAAM,aAAa,CAAC,SAAgC,cAAc,cAAc,EAAE,KAAK,CAAC;AAGxF,MAAM,eAAe,CAAC,SAAgC,cAAc,gBAAgB,EAAE,KAAK,CAAC;AAI5F,MAAM,cAAc,CAAC,SAAgC,cAAc,eAAe,EAAE,KAAK,CAAC;AAI1F,MAAM,cAAc,CAAC,MAAc,OAA8B,cAAc,UAAU,EAAE,MAAM,GAAG,CAAC;AAIrG,MAAM,aAAa,CAAC,MAAc,UAAqC,cAAc,UAAU,EAAE,MAAM,MAAM,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/editor.ts"],"sourcesContent":["import { protocolRequest } from './sandboxUtils';\nimport { SCHEMES } from './protocolSchemes';\nimport { PROTOCOL_EDITOR } from './generated/protocol';\n\n/**\n * Open a working-tree file in the immediately.run host editor (UI_AS_APPS_SPEC §4 —\n * the file explorer's click-to-open). This is an INTENT: the app asks, the HOST\n * validates the path and drives the CodeMirror editor — the editor itself stays\n * host-owned (§2 recursion boundary), so an app can never own or script it beyond\n * \"please show this file\".\n *\n * Requires the elevated `editor:open` capability — a previewed app does not hold it\n * (it must not move the host's focus), so only a system app whose binding grants it\n * (the file explorer) can call this; anyone else is refused at the gate.\n */\n\n/** An error from {@link openInEditor}, carrying a machine-readable `.code`. */\nexport interface EditorOpenError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:open` (or `editor:reveal`, for a `reveal`)\n | 'not-found' // no such file in the live working tree (the host never creates)\n | 'invalid-params' // the path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session to open files in\n | 'unknown';\n}\n\ntype EditorResult = { ok: true; data: unknown } | { ok: false; code: string; message: string };\n\nconst editorRequest = async (method: string, arg: Record<string, unknown>): Promise<void> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_EDITOR], method, [arg])) as EditorResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `editor ${method} failed`) as EditorWriteError;\n err.code = (res?.code as EditorWriteError['code']) ?? 'unknown';\n throw err;\n }\n};\n\n/** Where in a file to land when opening it (R3-388). 1-indexed `line`, matching every\n * diagnostic producer that feeds it (`tsc`, `eslint`, `BuildError`) and both VS Code\n * and IntelliJ. A `line` past end-of-file CLAMPS to the last line rather than\n * erroring — a diagnostic outlives the edit that shortened the file, and landing\n * close beats refusing to navigate. */\nexport interface EditorSelection {\n line: number;\n column?: number;\n}\n\n/** Options for {@link openInEditor} (R3-389). */\nexport interface EditorOpenOptions {\n /** Also bring the user to the editor, ACROSS activities (TOOLS_ACTIVITY_SPEC §5.2).\n * An app that owns the main pane (the Tools activity's runner sits where the editor\n * would) cannot rely on the file simply becoming visible — the editor is not on\n * screen — so this asks the host to switch to the activity that owns it.\n *\n * This is the elevated `editor:reveal` capability, not `editor:open`: a frame\n * without it is refused `forbidden` for the whole call (the file is NOT opened —\n * never silently opened-without-moving). The host decides whether the user actually\n * moves: it needs a real user gesture (a click in your frame within the last few\n * seconds counts; a call on a timer or on run completion does not) and is\n * rate-limited. The promise resolves the same either way, so treat a resolved\n * reveal as \"asked\", not \"moved\", and keep a visible fallback control. Where the\n * host owns the editor activity is host state; nothing here can name it. */\n reveal?: boolean;\n}\n\n/**\n * Ask the host to open `path` (a repo-relative working-tree path, e.g. `src/App.tsx`\n * or `/src/App.tsx`) in the editor. Resolves once the editor switches to it; rejects\n * with an {@link EditorOpenError} (`.code`) if the path is invalid, missing, or this\n * app may not open files.\n *\n * Pass `selection` to land the caret on a specific line — what a problems list needs\n * to make a diagnostic clickable. It widens nothing: a selection says where to look\n * inside a file the caller could already open, and the capability is unchanged\n * (`editor:open`).\n *\n * Pass `{ reveal: true }` to ALSO bring the user to the editor across activities —\n * see {@link EditorOpenOptions.reveal}; that one does need the elevated\n * `editor:reveal`, and is refused outright without it.\n *\n * Older hosts ignore `selection` and open the file at its existing position, so a\n * caller may pass it unconditionally. `reveal` is only sent when true, so a host that\n * predates it sees a plain open.\n */\nexport const openInEditor = (path: string, selection?: EditorSelection, opts?: EditorOpenOptions): Promise<void> =>\n editorRequest('open', {\n path,\n ...(selection ? { selection } : {}),\n ...(opts?.reveal === true ? { reveal: true } : {}),\n });\n\n/**\n * Where to land when entering the edit experience (EDITOR_FIRST_EDITING_SPEC §6).\n *\n * Two target classes, and **at most one** may be given — supplying both is refused\n * `invalid-params`. Omit both to edit the current route's entry.\n *\n * - **Own-source** (`path`): a repo-relative path in the CURRENT repo. Self-scoped —\n * the app you are already running; the host navigates within the current route,\n * never to another repo.\n * - **Mount-file** (`file`): a file in a mount you ALREADY HOLD, opened in the main\n * edit experience (§9, settled 2026-08-28). You can only name a mount you hold: an\n * unheld one is `forbidden`, and indistinguishably so from one that does not exist,\n * because an app must not be able to probe for mounts (no existence oracle).\n * Editing a file *outside* your mounts stays picker-mediated (`pick-file`).\n *\n * A URI, a `..` segment, or a NUL is refused `invalid-params` in either class.\n */\nexport interface EditTarget {\n /** A repo-relative working-tree path in the current repo to focus once in edit\n * mode (e.g. `src/App.tsx`). Omit to edit the current route's entry. */\n path?: string;\n /** A file in one of YOUR OWN mounts, by the portable mount reference. `relPath` is\n * mount-relative and leading-slash (e.g. `/notes/idea.mdx`). Mutually exclusive\n * with {@link EditTarget.path}.\n *\n * There is no `mode` here on purpose: writability is the HOST's live reading of\n * the mount, not the caller's claim. A `ro` mount — which is also how an anonymous\n * share-link viewer surfaces — is refused `read-only` at call time, before the\n * editor is entered, so you never land in an editor that cannot save. */\n file?: { mountId: string; relPath: string };\n}\n\n/** An error from {@link requestEdit}, carrying a machine-readable `.code`. */\nexport interface RequestEditError extends Error {\n code:\n | 'read-only' // editing isn't possible here (a `ro` mount / anonymous viewer) — HIDE the affordance\n | 'forbidden' // the host refuses: a cross-repo target, or a mount you do not hold\n | 'invalid-params' // the target was malformed (URI / `..` / NUL / both target classes at once)\n | 'not-found' // the mount-file target does not exist — and asking did NOT create it\n | 'no-target' // there is no host editor session to enter\n | 'unknown';\n}\n\n/**\n * Ask the host to enter the **edit experience** for the app you are running —\n * the present→edit transition (`/present/...` → `/edit/...`) an app cannot make\n * itself. This is an INTENT (§2 recursion boundary): the app asks, the HOST\n * performs the visible, user-observable navigation and draws all editor chrome;\n * the app never navigates or paints chrome.\n *\n * Use it to offer an \"edit this\" affordance from a run/present-mode app that opens\n * the app's own source in the platform editor — instead of shipping a bespoke\n * in-app editor (EDITOR_FIRST_EDITING_SPEC §1).\n *\n * Also opens a file from a mount you already hold, in the main edit experience:\n *\n * await requestEdit({ file: { mountId: 'space:abc', relPath: '/notes/idea.mdx' } });\n *\n * Resolves once the host begins the transition; rejects with a\n * {@link RequestEditError} (`.code`). Treat `read-only`/`forbidden` as \"editing is\n * not available — hide the affordance,\" never as an error to surface to the user.\n * `not-found` means the file is not there; nothing is created by asking to edit it.\n */\nexport const requestEdit = (target?: EditTarget): Promise<void> =>\n editorRequest('requestEdit', target ? { ...target } : {});\n\n// ---------------------------------------------------------------------------\n// Editor SESSION management (EDITOR_AS_APP_SPEC §5.1; editor-as-app plan Phase\n// 03). Unlike `openInEditor` (the explorer's cross-app intent, `editor:open`),\n// these drive the editor's OWN open-tab set + active file, so they are gated by\n// the editor app's `editor:document` capability — a file explorer holding only\n// `editor:open` cannot call them. The host re-validates the path against the live\n// working tree; the editor itself stays host-owned (§2 recursion boundary).\n// ---------------------------------------------------------------------------\n\n/** An error from a session intent ({@link setActiveFile} / {@link closeFile}),\n * carrying a machine-readable `.code`. */\nexport interface EditorSessionError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:document`\n | 'not-found' // no such file in the live working tree\n | 'invalid-params' // the path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session\n | 'unknown';\n}\n\n/** Switch the editor's active file to `path`, opening it (adding a tab) if it is\n * not already open — native `setActiveFile` parity. Rejects with an\n * {@link EditorSessionError} (`.code`) if the path is missing/invalid or this app\n * lacks `editor:document`. */\nexport const setActiveFile = (path: string): Promise<void> => editorRequest('setActive', { path });\n\n/** Close `path`'s tab in the editor (remove it from the open set) — native\n * `closeFile` parity. Rejects with an {@link EditorSessionError} (`.code`). */\nexport const closeFile = (path: string): Promise<void> => editorRequest('close', { path });\n\n// ---------------------------------------------------------------------------\n// Working-tree mutation (UI_AS_APPS_SPEC §4 / EDITOR_AS_APP_SPEC §5.2). The file\n// explorer NAMES a working-tree path and the HOST performs the COW write (and\n// refreshes the preview) — the app holds no write port; it asks. Gated by the\n// first-party `editor:write` capability, so only a first-party chrome app (the\n// file explorer) can call these; anyone else is refused at the gate.\n// ---------------------------------------------------------------------------\n\n/** An error from a working-tree mutation, carrying a machine-readable `.code`. */\nexport interface EditorWriteError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:write` (first-party-only)\n | 'not-found' // the target file/folder does not exist (delete/rename)\n | 'exists' // the target already exists (create/rename would clobber)\n | 'protected' // the host refuses to delete this file (e.g. package.json)\n | 'too-large' // an upload exceeds the host's size limit\n | 'invalid-params' // a path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session\n | 'unknown';\n}\n\n/** Create an empty working-tree file at `path` and open it. Rejects `exists` if a\n * file is already there. */\nexport const createFile = (path: string): Promise<void> => editorRequest('createFile', { path });\n\n/** Create a working-tree folder at `path` (materialised with a `.gitkeep`). */\nexport const createFolder = (path: string): Promise<void> => editorRequest('createFolder', { path });\n\n/** Delete a working-tree file, or a folder and everything under it. Rejects\n * `protected` for files the host won't remove, `not-found` if absent. */\nexport const deleteEntry = (path: string): Promise<void> => editorRequest('deleteEntry', { path });\n\n/** Rename/move a working-tree file from `from` to `to`. Rejects `exists` if `to`\n * is taken, `not-found` if `from` is absent. */\nexport const renameEntry = (from: string, to: string): Promise<void> => editorRequest('rename', { from, to });\n\n/** Upload binary/text `bytes` to a working-tree file at `path`. Rejects\n * `too-large` past the host's size limit. The bytes are transferred (zero-copy). */\nexport const uploadFile = (path: string, bytes: Uint8Array): Promise<void> => editorRequest('upload', { path, bytes });\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAgC;AAChC,6BAAwB;AACxB,sBAAgC;AA0BhC,MAAM,gBAAgB,OAAO,QAAgB,QAAgD;AAC3F,QAAM,MAAO,UAAM,qCAAgB,+BAAQ,+BAAe,GAAG,QAAQ,CAAC,GAAG,CAAC;AAC1E,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU,MAAM,SAAS;AAC/D,QAAI,OAAQ,KAAK,QAAqC;AACtD,UAAM;AAAA,EACR;AACF;AAiDO,MAAM,eAAe,CAAC,MAAc,WAA6B,SACtE,cAAc,QAAQ;AAAA,EACpB;AAAA,EACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,EACjC,GAAI,MAAM,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;AAClD,CAAC;AAiEI,MAAM,cAAc,CAAC,WAC1B,cAAc,eAAe,SAAS,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;AA0BnD,MAAM,gBAAgB,CAAC,SAAgC,cAAc,aAAa,EAAE,KAAK,CAAC;AAI1F,MAAM,YAAY,CAAC,SAAgC,cAAc,SAAS,EAAE,KAAK,CAAC;AAyBlF,MAAM,aAAa,CAAC,SAAgC,cAAc,cAAc,EAAE,KAAK,CAAC;AAGxF,MAAM,eAAe,CAAC,SAAgC,cAAc,gBAAgB,EAAE,KAAK,CAAC;AAI5F,MAAM,cAAc,CAAC,SAAgC,cAAc,eAAe,EAAE,KAAK,CAAC;AAI1F,MAAM,cAAc,CAAC,MAAc,OAA8B,cAAc,UAAU,EAAE,MAAM,GAAG,CAAC;AAIrG,MAAM,aAAa,CAAC,MAAc,UAAqC,cAAc,UAAU,EAAE,MAAM,MAAM,CAAC;","names":[]}
package/dist/editor.d.cts CHANGED
@@ -60,21 +60,42 @@ interface EditorOpenOptions {
60
60
  */
61
61
  declare const openInEditor: (path: string, selection?: EditorSelection, opts?: EditorOpenOptions) => Promise<void>;
62
62
  /**
63
- * Where to land when entering the edit experience (EDITOR_FIRST_EDITING_SPEC §6
64
- * Delta A). v1 supports only an optional repo-relative `path` in the CURRENT repo
65
- * (self-scoped — the app you are already running; the host navigates within the
66
- * current route, never to another repo). A URI or `..` path is refused
67
- * `invalid-params`. Editing a file in one of your *mounts* (a space) is the
68
- * `edit-file` task, not this.
63
+ * Where to land when entering the edit experience (EDITOR_FIRST_EDITING_SPEC §6).
64
+ *
65
+ * Two target classes, and **at most one** may be given — supplying both is refused
66
+ * `invalid-params`. Omit both to edit the current route's entry.
67
+ *
68
+ * - **Own-source** (`path`): a repo-relative path in the CURRENT repo. Self-scoped —
69
+ * the app you are already running; the host navigates within the current route,
70
+ * never to another repo.
71
+ * - **Mount-file** (`file`): a file in a mount you ALREADY HOLD, opened in the main
72
+ * edit experience (§9, settled 2026-08-28). You can only name a mount you hold: an
73
+ * unheld one is `forbidden`, and indistinguishably so from one that does not exist,
74
+ * because an app must not be able to probe for mounts (no existence oracle).
75
+ * Editing a file *outside* your mounts stays picker-mediated (`pick-file`).
76
+ *
77
+ * A URI, a `..` segment, or a NUL is refused `invalid-params` in either class.
69
78
  */
70
79
  interface EditTarget {
71
80
  /** A repo-relative working-tree path in the current repo to focus once in edit
72
81
  * mode (e.g. `src/App.tsx`). Omit to edit the current route's entry. */
73
82
  path?: string;
83
+ /** A file in one of YOUR OWN mounts, by the portable mount reference. `relPath` is
84
+ * mount-relative and leading-slash (e.g. `/notes/idea.mdx`). Mutually exclusive
85
+ * with {@link EditTarget.path}.
86
+ *
87
+ * There is no `mode` here on purpose: writability is the HOST's live reading of
88
+ * the mount, not the caller's claim. A `ro` mount — which is also how an anonymous
89
+ * share-link viewer surfaces — is refused `read-only` at call time, before the
90
+ * editor is entered, so you never land in an editor that cannot save. */
91
+ file?: {
92
+ mountId: string;
93
+ relPath: string;
94
+ };
74
95
  }
75
96
  /** An error from {@link requestEdit}, carrying a machine-readable `.code`. */
76
97
  interface RequestEditError extends Error {
77
- code: 'read-only' | 'forbidden' | 'invalid-params' | 'no-target' | 'unknown';
98
+ code: 'read-only' | 'forbidden' | 'invalid-params' | 'not-found' | 'no-target' | 'unknown';
78
99
  }
79
100
  /**
80
101
  * Ask the host to enter the **edit experience** for the app you are running —
@@ -87,9 +108,14 @@ interface RequestEditError extends Error {
87
108
  * the app's own source in the platform editor — instead of shipping a bespoke
88
109
  * in-app editor (EDITOR_FIRST_EDITING_SPEC §1).
89
110
  *
111
+ * Also opens a file from a mount you already hold, in the main edit experience:
112
+ *
113
+ * await requestEdit({ file: { mountId: 'space:abc', relPath: '/notes/idea.mdx' } });
114
+ *
90
115
  * Resolves once the host begins the transition; rejects with a
91
116
  * {@link RequestEditError} (`.code`). Treat `read-only`/`forbidden` as "editing is
92
117
  * not available — hide the affordance," never as an error to surface to the user.
118
+ * `not-found` means the file is not there; nothing is created by asking to edit it.
93
119
  */
94
120
  declare const requestEdit: (target?: EditTarget) => Promise<void>;
95
121
  /** An error from a session intent ({@link setActiveFile} / {@link closeFile}),
package/dist/editor.d.ts CHANGED
@@ -60,21 +60,42 @@ interface EditorOpenOptions {
60
60
  */
61
61
  declare const openInEditor: (path: string, selection?: EditorSelection, opts?: EditorOpenOptions) => Promise<void>;
62
62
  /**
63
- * Where to land when entering the edit experience (EDITOR_FIRST_EDITING_SPEC §6
64
- * Delta A). v1 supports only an optional repo-relative `path` in the CURRENT repo
65
- * (self-scoped — the app you are already running; the host navigates within the
66
- * current route, never to another repo). A URI or `..` path is refused
67
- * `invalid-params`. Editing a file in one of your *mounts* (a space) is the
68
- * `edit-file` task, not this.
63
+ * Where to land when entering the edit experience (EDITOR_FIRST_EDITING_SPEC §6).
64
+ *
65
+ * Two target classes, and **at most one** may be given — supplying both is refused
66
+ * `invalid-params`. Omit both to edit the current route's entry.
67
+ *
68
+ * - **Own-source** (`path`): a repo-relative path in the CURRENT repo. Self-scoped —
69
+ * the app you are already running; the host navigates within the current route,
70
+ * never to another repo.
71
+ * - **Mount-file** (`file`): a file in a mount you ALREADY HOLD, opened in the main
72
+ * edit experience (§9, settled 2026-08-28). You can only name a mount you hold: an
73
+ * unheld one is `forbidden`, and indistinguishably so from one that does not exist,
74
+ * because an app must not be able to probe for mounts (no existence oracle).
75
+ * Editing a file *outside* your mounts stays picker-mediated (`pick-file`).
76
+ *
77
+ * A URI, a `..` segment, or a NUL is refused `invalid-params` in either class.
69
78
  */
70
79
  interface EditTarget {
71
80
  /** A repo-relative working-tree path in the current repo to focus once in edit
72
81
  * mode (e.g. `src/App.tsx`). Omit to edit the current route's entry. */
73
82
  path?: string;
83
+ /** A file in one of YOUR OWN mounts, by the portable mount reference. `relPath` is
84
+ * mount-relative and leading-slash (e.g. `/notes/idea.mdx`). Mutually exclusive
85
+ * with {@link EditTarget.path}.
86
+ *
87
+ * There is no `mode` here on purpose: writability is the HOST's live reading of
88
+ * the mount, not the caller's claim. A `ro` mount — which is also how an anonymous
89
+ * share-link viewer surfaces — is refused `read-only` at call time, before the
90
+ * editor is entered, so you never land in an editor that cannot save. */
91
+ file?: {
92
+ mountId: string;
93
+ relPath: string;
94
+ };
74
95
  }
75
96
  /** An error from {@link requestEdit}, carrying a machine-readable `.code`. */
76
97
  interface RequestEditError extends Error {
77
- code: 'read-only' | 'forbidden' | 'invalid-params' | 'no-target' | 'unknown';
98
+ code: 'read-only' | 'forbidden' | 'invalid-params' | 'not-found' | 'no-target' | 'unknown';
78
99
  }
79
100
  /**
80
101
  * Ask the host to enter the **edit experience** for the app you are running —
@@ -87,9 +108,14 @@ interface RequestEditError extends Error {
87
108
  * the app's own source in the platform editor — instead of shipping a bespoke
88
109
  * in-app editor (EDITOR_FIRST_EDITING_SPEC §1).
89
110
  *
111
+ * Also opens a file from a mount you already hold, in the main edit experience:
112
+ *
113
+ * await requestEdit({ file: { mountId: 'space:abc', relPath: '/notes/idea.mdx' } });
114
+ *
90
115
  * Resolves once the host begins the transition; rejects with a
91
116
  * {@link RequestEditError} (`.code`). Treat `read-only`/`forbidden` as "editing is
92
117
  * not available — hide the affordance," never as an error to surface to the user.
118
+ * `not-found` means the file is not there; nothing is created by asking to edit it.
93
119
  */
94
120
  declare const requestEdit: (target?: EditTarget) => Promise<void>;
95
121
  /** An error from a session intent ({@link setActiveFile} / {@link closeFile}),
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/editor.ts"],"sourcesContent":["import { protocolRequest } from './sandboxUtils';\nimport { SCHEMES } from './protocolSchemes';\nimport { PROTOCOL_EDITOR } from './generated/protocol';\n\n/**\n * Open a working-tree file in the immediately.run host editor (UI_AS_APPS_SPEC §4 —\n * the file explorer's click-to-open). This is an INTENT: the app asks, the HOST\n * validates the path and drives the CodeMirror editor — the editor itself stays\n * host-owned (§2 recursion boundary), so an app can never own or script it beyond\n * \"please show this file\".\n *\n * Requires the elevated `editor:open` capability — a previewed app does not hold it\n * (it must not move the host's focus), so only a system app whose binding grants it\n * (the file explorer) can call this; anyone else is refused at the gate.\n */\n\n/** An error from {@link openInEditor}, carrying a machine-readable `.code`. */\nexport interface EditorOpenError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:open` (or `editor:reveal`, for a `reveal`)\n | 'not-found' // no such file in the live working tree (the host never creates)\n | 'invalid-params' // the path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session to open files in\n | 'unknown';\n}\n\ntype EditorResult = { ok: true; data: unknown } | { ok: false; code: string; message: string };\n\nconst editorRequest = async (method: string, arg: Record<string, unknown>): Promise<void> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_EDITOR], method, [arg])) as EditorResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `editor ${method} failed`) as EditorWriteError;\n err.code = (res?.code as EditorWriteError['code']) ?? 'unknown';\n throw err;\n }\n};\n\n/** Where in a file to land when opening it (R3-388). 1-indexed `line`, matching every\n * diagnostic producer that feeds it (`tsc`, `eslint`, `BuildError`) and both VS Code\n * and IntelliJ. A `line` past end-of-file CLAMPS to the last line rather than\n * erroring — a diagnostic outlives the edit that shortened the file, and landing\n * close beats refusing to navigate. */\nexport interface EditorSelection {\n line: number;\n column?: number;\n}\n\n/** Options for {@link openInEditor} (R3-389). */\nexport interface EditorOpenOptions {\n /** Also bring the user to the editor, ACROSS activities (TOOLS_ACTIVITY_SPEC §5.2).\n * An app that owns the main pane (the Tools activity's runner sits where the editor\n * would) cannot rely on the file simply becoming visible — the editor is not on\n * screen — so this asks the host to switch to the activity that owns it.\n *\n * This is the elevated `editor:reveal` capability, not `editor:open`: a frame\n * without it is refused `forbidden` for the whole call (the file is NOT opened —\n * never silently opened-without-moving). The host decides whether the user actually\n * moves: it needs a real user gesture (a click in your frame within the last few\n * seconds counts; a call on a timer or on run completion does not) and is\n * rate-limited. The promise resolves the same either way, so treat a resolved\n * reveal as \"asked\", not \"moved\", and keep a visible fallback control. Where the\n * host owns the editor activity is host state; nothing here can name it. */\n reveal?: boolean;\n}\n\n/**\n * Ask the host to open `path` (a repo-relative working-tree path, e.g. `src/App.tsx`\n * or `/src/App.tsx`) in the editor. Resolves once the editor switches to it; rejects\n * with an {@link EditorOpenError} (`.code`) if the path is invalid, missing, or this\n * app may not open files.\n *\n * Pass `selection` to land the caret on a specific line — what a problems list needs\n * to make a diagnostic clickable. It widens nothing: a selection says where to look\n * inside a file the caller could already open, and the capability is unchanged\n * (`editor:open`).\n *\n * Pass `{ reveal: true }` to ALSO bring the user to the editor across activities —\n * see {@link EditorOpenOptions.reveal}; that one does need the elevated\n * `editor:reveal`, and is refused outright without it.\n *\n * Older hosts ignore `selection` and open the file at its existing position, so a\n * caller may pass it unconditionally. `reveal` is only sent when true, so a host that\n * predates it sees a plain open.\n */\nexport const openInEditor = (path: string, selection?: EditorSelection, opts?: EditorOpenOptions): Promise<void> =>\n editorRequest('open', {\n path,\n ...(selection ? { selection } : {}),\n ...(opts?.reveal === true ? { reveal: true } : {}),\n });\n\n/**\n * Where to land when entering the edit experience (EDITOR_FIRST_EDITING_SPEC §6\n * Delta A). v1 supports only an optional repo-relative `path` in the CURRENT repo\n * (self-scoped — the app you are already running; the host navigates within the\n * current route, never to another repo). A URI or `..` path is refused\n * `invalid-params`. Editing a file in one of your *mounts* (a space) is the\n * `edit-file` task, not this.\n */\nexport interface EditTarget {\n /** A repo-relative working-tree path in the current repo to focus once in edit\n * mode (e.g. `src/App.tsx`). Omit to edit the current route's entry. */\n path?: string;\n}\n\n/** An error from {@link requestEdit}, carrying a machine-readable `.code`. */\nexport interface RequestEditError extends Error {\n code:\n | 'read-only' // editing isn't possible here (a `ro` mount / anonymous viewer) — HIDE the affordance\n | 'forbidden' // the host refuses (e.g. a cross-repo / out-of-scope target)\n | 'invalid-params' // the target was malformed (URI / `..` / a non-current repo)\n | 'no-target' // there is no host editor session to enter\n | 'unknown';\n}\n\n/**\n * Ask the host to enter the **edit experience** for the app you are running —\n * the present→edit transition (`/present/...` → `/edit/...`) an app cannot make\n * itself. This is an INTENT (§2 recursion boundary): the app asks, the HOST\n * performs the visible, user-observable navigation and draws all editor chrome;\n * the app never navigates or paints chrome.\n *\n * Use it to offer an \"edit this\" affordance from a run/present-mode app that opens\n * the app's own source in the platform editor — instead of shipping a bespoke\n * in-app editor (EDITOR_FIRST_EDITING_SPEC §1).\n *\n * Resolves once the host begins the transition; rejects with a\n * {@link RequestEditError} (`.code`). Treat `read-only`/`forbidden` as \"editing is\n * not available — hide the affordance,\" never as an error to surface to the user.\n */\nexport const requestEdit = (target?: EditTarget): Promise<void> =>\n editorRequest('requestEdit', target ? { ...target } : {});\n\n// ---------------------------------------------------------------------------\n// Editor SESSION management (EDITOR_AS_APP_SPEC §5.1; editor-as-app plan Phase\n// 03). Unlike `openInEditor` (the explorer's cross-app intent, `editor:open`),\n// these drive the editor's OWN open-tab set + active file, so they are gated by\n// the editor app's `editor:document` capability — a file explorer holding only\n// `editor:open` cannot call them. The host re-validates the path against the live\n// working tree; the editor itself stays host-owned (§2 recursion boundary).\n// ---------------------------------------------------------------------------\n\n/** An error from a session intent ({@link setActiveFile} / {@link closeFile}),\n * carrying a machine-readable `.code`. */\nexport interface EditorSessionError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:document`\n | 'not-found' // no such file in the live working tree\n | 'invalid-params' // the path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session\n | 'unknown';\n}\n\n/** Switch the editor's active file to `path`, opening it (adding a tab) if it is\n * not already open — native `setActiveFile` parity. Rejects with an\n * {@link EditorSessionError} (`.code`) if the path is missing/invalid or this app\n * lacks `editor:document`. */\nexport const setActiveFile = (path: string): Promise<void> => editorRequest('setActive', { path });\n\n/** Close `path`'s tab in the editor (remove it from the open set) — native\n * `closeFile` parity. Rejects with an {@link EditorSessionError} (`.code`). */\nexport const closeFile = (path: string): Promise<void> => editorRequest('close', { path });\n\n// ---------------------------------------------------------------------------\n// Working-tree mutation (UI_AS_APPS_SPEC §4 / EDITOR_AS_APP_SPEC §5.2). The file\n// explorer NAMES a working-tree path and the HOST performs the COW write (and\n// refreshes the preview) — the app holds no write port; it asks. Gated by the\n// first-party `editor:write` capability, so only a first-party chrome app (the\n// file explorer) can call these; anyone else is refused at the gate.\n// ---------------------------------------------------------------------------\n\n/** An error from a working-tree mutation, carrying a machine-readable `.code`. */\nexport interface EditorWriteError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:write` (first-party-only)\n | 'not-found' // the target file/folder does not exist (delete/rename)\n | 'exists' // the target already exists (create/rename would clobber)\n | 'protected' // the host refuses to delete this file (e.g. package.json)\n | 'too-large' // an upload exceeds the host's size limit\n | 'invalid-params' // a path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session\n | 'unknown';\n}\n\n/** Create an empty working-tree file at `path` and open it. Rejects `exists` if a\n * file is already there. */\nexport const createFile = (path: string): Promise<void> => editorRequest('createFile', { path });\n\n/** Create a working-tree folder at `path` (materialised with a `.gitkeep`). */\nexport const createFolder = (path: string): Promise<void> => editorRequest('createFolder', { path });\n\n/** Delete a working-tree file, or a folder and everything under it. Rejects\n * `protected` for files the host won't remove, `not-found` if absent. */\nexport const deleteEntry = (path: string): Promise<void> => editorRequest('deleteEntry', { path });\n\n/** Rename/move a working-tree file from `from` to `to`. Rejects `exists` if `to`\n * is taken, `not-found` if `from` is absent. */\nexport const renameEntry = (from: string, to: string): Promise<void> => editorRequest('rename', { from, to });\n\n/** Upload binary/text `bytes` to a working-tree file at `path`. Rejects\n * `too-large` past the host's size limit. The bytes are transferred (zero-copy). */\nexport const uploadFile = (path: string, bytes: Uint8Array): Promise<void> => editorRequest('upload', { path, bytes });\n"],"mappings":";AAAA,SAAS,uBAAuB;AAChC,SAAS,eAAe;AACxB,SAAS,uBAAuB;AA0BhC,MAAM,gBAAgB,OAAO,QAAgB,QAAgD;AAC3F,QAAM,MAAO,MAAM,gBAAgB,QAAQ,eAAe,GAAG,QAAQ,CAAC,GAAG,CAAC;AAC1E,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU,MAAM,SAAS;AAC/D,QAAI,OAAQ,KAAK,QAAqC;AACtD,UAAM;AAAA,EACR;AACF;AAiDO,MAAM,eAAe,CAAC,MAAc,WAA6B,SACtE,cAAc,QAAQ;AAAA,EACpB;AAAA,EACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,EACjC,GAAI,MAAM,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;AAClD,CAAC;AAyCI,MAAM,cAAc,CAAC,WAC1B,cAAc,eAAe,SAAS,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;AA0BnD,MAAM,gBAAgB,CAAC,SAAgC,cAAc,aAAa,EAAE,KAAK,CAAC;AAI1F,MAAM,YAAY,CAAC,SAAgC,cAAc,SAAS,EAAE,KAAK,CAAC;AAyBlF,MAAM,aAAa,CAAC,SAAgC,cAAc,cAAc,EAAE,KAAK,CAAC;AAGxF,MAAM,eAAe,CAAC,SAAgC,cAAc,gBAAgB,EAAE,KAAK,CAAC;AAI5F,MAAM,cAAc,CAAC,SAAgC,cAAc,eAAe,EAAE,KAAK,CAAC;AAI1F,MAAM,cAAc,CAAC,MAAc,OAA8B,cAAc,UAAU,EAAE,MAAM,GAAG,CAAC;AAIrG,MAAM,aAAa,CAAC,MAAc,UAAqC,cAAc,UAAU,EAAE,MAAM,MAAM,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/editor.ts"],"sourcesContent":["import { protocolRequest } from './sandboxUtils';\nimport { SCHEMES } from './protocolSchemes';\nimport { PROTOCOL_EDITOR } from './generated/protocol';\n\n/**\n * Open a working-tree file in the immediately.run host editor (UI_AS_APPS_SPEC §4 —\n * the file explorer's click-to-open). This is an INTENT: the app asks, the HOST\n * validates the path and drives the CodeMirror editor — the editor itself stays\n * host-owned (§2 recursion boundary), so an app can never own or script it beyond\n * \"please show this file\".\n *\n * Requires the elevated `editor:open` capability — a previewed app does not hold it\n * (it must not move the host's focus), so only a system app whose binding grants it\n * (the file explorer) can call this; anyone else is refused at the gate.\n */\n\n/** An error from {@link openInEditor}, carrying a machine-readable `.code`. */\nexport interface EditorOpenError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:open` (or `editor:reveal`, for a `reveal`)\n | 'not-found' // no such file in the live working tree (the host never creates)\n | 'invalid-params' // the path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session to open files in\n | 'unknown';\n}\n\ntype EditorResult = { ok: true; data: unknown } | { ok: false; code: string; message: string };\n\nconst editorRequest = async (method: string, arg: Record<string, unknown>): Promise<void> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_EDITOR], method, [arg])) as EditorResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `editor ${method} failed`) as EditorWriteError;\n err.code = (res?.code as EditorWriteError['code']) ?? 'unknown';\n throw err;\n }\n};\n\n/** Where in a file to land when opening it (R3-388). 1-indexed `line`, matching every\n * diagnostic producer that feeds it (`tsc`, `eslint`, `BuildError`) and both VS Code\n * and IntelliJ. A `line` past end-of-file CLAMPS to the last line rather than\n * erroring — a diagnostic outlives the edit that shortened the file, and landing\n * close beats refusing to navigate. */\nexport interface EditorSelection {\n line: number;\n column?: number;\n}\n\n/** Options for {@link openInEditor} (R3-389). */\nexport interface EditorOpenOptions {\n /** Also bring the user to the editor, ACROSS activities (TOOLS_ACTIVITY_SPEC §5.2).\n * An app that owns the main pane (the Tools activity's runner sits where the editor\n * would) cannot rely on the file simply becoming visible — the editor is not on\n * screen — so this asks the host to switch to the activity that owns it.\n *\n * This is the elevated `editor:reveal` capability, not `editor:open`: a frame\n * without it is refused `forbidden` for the whole call (the file is NOT opened —\n * never silently opened-without-moving). The host decides whether the user actually\n * moves: it needs a real user gesture (a click in your frame within the last few\n * seconds counts; a call on a timer or on run completion does not) and is\n * rate-limited. The promise resolves the same either way, so treat a resolved\n * reveal as \"asked\", not \"moved\", and keep a visible fallback control. Where the\n * host owns the editor activity is host state; nothing here can name it. */\n reveal?: boolean;\n}\n\n/**\n * Ask the host to open `path` (a repo-relative working-tree path, e.g. `src/App.tsx`\n * or `/src/App.tsx`) in the editor. Resolves once the editor switches to it; rejects\n * with an {@link EditorOpenError} (`.code`) if the path is invalid, missing, or this\n * app may not open files.\n *\n * Pass `selection` to land the caret on a specific line — what a problems list needs\n * to make a diagnostic clickable. It widens nothing: a selection says where to look\n * inside a file the caller could already open, and the capability is unchanged\n * (`editor:open`).\n *\n * Pass `{ reveal: true }` to ALSO bring the user to the editor across activities —\n * see {@link EditorOpenOptions.reveal}; that one does need the elevated\n * `editor:reveal`, and is refused outright without it.\n *\n * Older hosts ignore `selection` and open the file at its existing position, so a\n * caller may pass it unconditionally. `reveal` is only sent when true, so a host that\n * predates it sees a plain open.\n */\nexport const openInEditor = (path: string, selection?: EditorSelection, opts?: EditorOpenOptions): Promise<void> =>\n editorRequest('open', {\n path,\n ...(selection ? { selection } : {}),\n ...(opts?.reveal === true ? { reveal: true } : {}),\n });\n\n/**\n * Where to land when entering the edit experience (EDITOR_FIRST_EDITING_SPEC §6).\n *\n * Two target classes, and **at most one** may be given — supplying both is refused\n * `invalid-params`. Omit both to edit the current route's entry.\n *\n * - **Own-source** (`path`): a repo-relative path in the CURRENT repo. Self-scoped —\n * the app you are already running; the host navigates within the current route,\n * never to another repo.\n * - **Mount-file** (`file`): a file in a mount you ALREADY HOLD, opened in the main\n * edit experience (§9, settled 2026-08-28). You can only name a mount you hold: an\n * unheld one is `forbidden`, and indistinguishably so from one that does not exist,\n * because an app must not be able to probe for mounts (no existence oracle).\n * Editing a file *outside* your mounts stays picker-mediated (`pick-file`).\n *\n * A URI, a `..` segment, or a NUL is refused `invalid-params` in either class.\n */\nexport interface EditTarget {\n /** A repo-relative working-tree path in the current repo to focus once in edit\n * mode (e.g. `src/App.tsx`). Omit to edit the current route's entry. */\n path?: string;\n /** A file in one of YOUR OWN mounts, by the portable mount reference. `relPath` is\n * mount-relative and leading-slash (e.g. `/notes/idea.mdx`). Mutually exclusive\n * with {@link EditTarget.path}.\n *\n * There is no `mode` here on purpose: writability is the HOST's live reading of\n * the mount, not the caller's claim. A `ro` mount — which is also how an anonymous\n * share-link viewer surfaces — is refused `read-only` at call time, before the\n * editor is entered, so you never land in an editor that cannot save. */\n file?: { mountId: string; relPath: string };\n}\n\n/** An error from {@link requestEdit}, carrying a machine-readable `.code`. */\nexport interface RequestEditError extends Error {\n code:\n | 'read-only' // editing isn't possible here (a `ro` mount / anonymous viewer) — HIDE the affordance\n | 'forbidden' // the host refuses: a cross-repo target, or a mount you do not hold\n | 'invalid-params' // the target was malformed (URI / `..` / NUL / both target classes at once)\n | 'not-found' // the mount-file target does not exist — and asking did NOT create it\n | 'no-target' // there is no host editor session to enter\n | 'unknown';\n}\n\n/**\n * Ask the host to enter the **edit experience** for the app you are running —\n * the present→edit transition (`/present/...` → `/edit/...`) an app cannot make\n * itself. This is an INTENT (§2 recursion boundary): the app asks, the HOST\n * performs the visible, user-observable navigation and draws all editor chrome;\n * the app never navigates or paints chrome.\n *\n * Use it to offer an \"edit this\" affordance from a run/present-mode app that opens\n * the app's own source in the platform editor — instead of shipping a bespoke\n * in-app editor (EDITOR_FIRST_EDITING_SPEC §1).\n *\n * Also opens a file from a mount you already hold, in the main edit experience:\n *\n * await requestEdit({ file: { mountId: 'space:abc', relPath: '/notes/idea.mdx' } });\n *\n * Resolves once the host begins the transition; rejects with a\n * {@link RequestEditError} (`.code`). Treat `read-only`/`forbidden` as \"editing is\n * not available — hide the affordance,\" never as an error to surface to the user.\n * `not-found` means the file is not there; nothing is created by asking to edit it.\n */\nexport const requestEdit = (target?: EditTarget): Promise<void> =>\n editorRequest('requestEdit', target ? { ...target } : {});\n\n// ---------------------------------------------------------------------------\n// Editor SESSION management (EDITOR_AS_APP_SPEC §5.1; editor-as-app plan Phase\n// 03). Unlike `openInEditor` (the explorer's cross-app intent, `editor:open`),\n// these drive the editor's OWN open-tab set + active file, so they are gated by\n// the editor app's `editor:document` capability — a file explorer holding only\n// `editor:open` cannot call them. The host re-validates the path against the live\n// working tree; the editor itself stays host-owned (§2 recursion boundary).\n// ---------------------------------------------------------------------------\n\n/** An error from a session intent ({@link setActiveFile} / {@link closeFile}),\n * carrying a machine-readable `.code`. */\nexport interface EditorSessionError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:document`\n | 'not-found' // no such file in the live working tree\n | 'invalid-params' // the path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session\n | 'unknown';\n}\n\n/** Switch the editor's active file to `path`, opening it (adding a tab) if it is\n * not already open — native `setActiveFile` parity. Rejects with an\n * {@link EditorSessionError} (`.code`) if the path is missing/invalid or this app\n * lacks `editor:document`. */\nexport const setActiveFile = (path: string): Promise<void> => editorRequest('setActive', { path });\n\n/** Close `path`'s tab in the editor (remove it from the open set) — native\n * `closeFile` parity. Rejects with an {@link EditorSessionError} (`.code`). */\nexport const closeFile = (path: string): Promise<void> => editorRequest('close', { path });\n\n// ---------------------------------------------------------------------------\n// Working-tree mutation (UI_AS_APPS_SPEC §4 / EDITOR_AS_APP_SPEC §5.2). The file\n// explorer NAMES a working-tree path and the HOST performs the COW write (and\n// refreshes the preview) — the app holds no write port; it asks. Gated by the\n// first-party `editor:write` capability, so only a first-party chrome app (the\n// file explorer) can call these; anyone else is refused at the gate.\n// ---------------------------------------------------------------------------\n\n/** An error from a working-tree mutation, carrying a machine-readable `.code`. */\nexport interface EditorWriteError extends Error {\n code:\n | 'forbidden' // the frame lacks `editor:write` (first-party-only)\n | 'not-found' // the target file/folder does not exist (delete/rename)\n | 'exists' // the target already exists (create/rename would clobber)\n | 'protected' // the host refuses to delete this file (e.g. package.json)\n | 'too-large' // an upload exceeds the host's size limit\n | 'invalid-params' // a path was empty / contained `..` / looked like a URI\n | 'no-target' // there is no host editor session\n | 'unknown';\n}\n\n/** Create an empty working-tree file at `path` and open it. Rejects `exists` if a\n * file is already there. */\nexport const createFile = (path: string): Promise<void> => editorRequest('createFile', { path });\n\n/** Create a working-tree folder at `path` (materialised with a `.gitkeep`). */\nexport const createFolder = (path: string): Promise<void> => editorRequest('createFolder', { path });\n\n/** Delete a working-tree file, or a folder and everything under it. Rejects\n * `protected` for files the host won't remove, `not-found` if absent. */\nexport const deleteEntry = (path: string): Promise<void> => editorRequest('deleteEntry', { path });\n\n/** Rename/move a working-tree file from `from` to `to`. Rejects `exists` if `to`\n * is taken, `not-found` if `from` is absent. */\nexport const renameEntry = (from: string, to: string): Promise<void> => editorRequest('rename', { from, to });\n\n/** Upload binary/text `bytes` to a working-tree file at `path`. Rejects\n * `too-large` past the host's size limit. The bytes are transferred (zero-copy). */\nexport const uploadFile = (path: string, bytes: Uint8Array): Promise<void> => editorRequest('upload', { path, bytes });\n"],"mappings":";AAAA,SAAS,uBAAuB;AAChC,SAAS,eAAe;AACxB,SAAS,uBAAuB;AA0BhC,MAAM,gBAAgB,OAAO,QAAgB,QAAgD;AAC3F,QAAM,MAAO,MAAM,gBAAgB,QAAQ,eAAe,GAAG,QAAQ,CAAC,GAAG,CAAC;AAC1E,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,UAAU,MAAM,SAAS;AAC/D,QAAI,OAAQ,KAAK,QAAqC;AACtD,UAAM;AAAA,EACR;AACF;AAiDO,MAAM,eAAe,CAAC,MAAc,WAA6B,SACtE,cAAc,QAAQ;AAAA,EACpB;AAAA,EACA,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,EACjC,GAAI,MAAM,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;AAClD,CAAC;AAiEI,MAAM,cAAc,CAAC,WAC1B,cAAc,eAAe,SAAS,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC;AA0BnD,MAAM,gBAAgB,CAAC,SAAgC,cAAc,aAAa,EAAE,KAAK,CAAC;AAI1F,MAAM,YAAY,CAAC,SAAgC,cAAc,SAAS,EAAE,KAAK,CAAC;AAyBlF,MAAM,aAAa,CAAC,SAAgC,cAAc,cAAc,EAAE,KAAK,CAAC;AAGxF,MAAM,eAAe,CAAC,SAAgC,cAAc,gBAAgB,EAAE,KAAK,CAAC;AAI5F,MAAM,cAAc,CAAC,SAAgC,cAAc,eAAe,EAAE,KAAK,CAAC;AAI1F,MAAM,cAAc,CAAC,MAAc,OAA8B,cAAc,UAAU,EAAE,MAAM,GAAG,CAAC;AAIrG,MAAM,aAAa,CAAC,MAAc,UAAqC,cAAc,UAAU,EAAE,MAAM,MAAM,CAAC;","names":[]}
package/dist/version.cjs CHANGED
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  SDK_VERSION: () => SDK_VERSION
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const SDK_VERSION = "0.56.0";
24
+ const SDK_VERSION = "0.57.0";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  SDK_VERSION
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.56.0';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.57.0';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
@@ -1,4 +1,4 @@
1
1
  /** This SDK's package version, baked from package.json at build (SP2-6). */
2
- declare const SDK_VERSION = "0.56.0";
2
+ declare const SDK_VERSION = "0.57.0";
3
3
 
4
4
  export { SDK_VERSION };
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /** This SDK's package version, baked from package.json at build (SP2-6). */
2
- declare const SDK_VERSION = "0.56.0";
2
+ declare const SDK_VERSION = "0.57.0";
3
3
 
4
4
  export { SDK_VERSION };
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./chunk-VHAA22YE.js";
2
- const SDK_VERSION = "0.56.0";
2
+ const SDK_VERSION = "0.57.0";
3
3
  export {
4
4
  SDK_VERSION
5
5
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.56.0';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.57.0';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immediately-run/sdk",
3
- "version": "0.56.0",
3
+ "version": "0.57.0",
4
4
  "description": "Runtime SDK for code executing inside an immediately.run sandbox.",
5
5
  "license": "MIT",
6
6
  "repository": "github:immediately-run/immediately-run-sdk",