@north-light/cli 0.3.474 → 0.3.476
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/dist/builtin-memory/northlight-apps/guides/background-runs.md +1 -8
- package/dist/builtin-memory/northlight-apps/guides/manage-your-app.md +7 -18
- package/dist/clients/attach/render/doc-links.js +1 -1
- package/dist/clients/attach/session/doc-html.d.ts +8 -0
- package/dist/clients/attach/session/doc-html.js +66 -0
- package/dist/clients/attach/session/doc-materialize.d.ts +4 -0
- package/dist/clients/attach/session/doc-materialize.js +3 -3
- package/dist/clients/attach/session/document-links.d.ts +3 -0
- package/dist/clients/attach/session/document-links.js +1 -1
- package/dist/clients/attach/viewer.js +616 -551
- package/dist/core/config.js +1 -1
- package/dist/core/user-settings.d.ts +15 -0
- package/dist/core/user-settings.js +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.js +1 -1
- package/docs/apps/guides/background-runs.md +1 -8
- package/docs/apps/guides/manage-your-app.md +7 -18
- package/docs/cli/join-northlight.md +2 -2
- package/package.json +3 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -12
|
@@ -70,11 +70,4 @@ app.post('/run-notices', async (req, res) => {
|
|
|
70
70
|
|
|
71
71
|
### Set the run notice URL
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
crtr cloud apps set-run-notice-url --app-id <client id> --url https://app.example.com/run-notices
|
|
77
|
-
crtr cloud apps set-run-notice-url --app-id <client id> --clear # stop notices
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
The URL must be https (plain http only on localhost in development) and at most 512 characters. `crtr cloud apps get --app-id <client id>` shows it as `run_notice_url`.
|
|
73
|
+
In the [console](https://account.northlight.ai/console), open your app, choose the **What it asks for** tab, and fill in **Notice URL** under **Run notice URL**. Choose **Save**. The URL must be `https` and at most 512 characters. To stop notices, empty the field and save. The full list of console fields is in [[northlight-apps/guides/manage-your-app]].
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
kind: knowledge
|
|
3
3
|
when-and-why-to-read: When you need Manage your app, this knowledge should be
|
|
4
4
|
read because What each tab of the Northlight console does for an app you have
|
|
5
|
-
registered,
|
|
5
|
+
registered, including setting the run notice URL and deleting the app.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Manage your app
|
|
@@ -33,11 +33,13 @@ Until you retire the old key it is your rollback. After you retire it, a sign-in
|
|
|
33
33
|
- **What your app asks for.** The scopes people are asked to approve. **Add permissions** searches the catalog; take scope strings from it and from `providers.tools`, and do not invent them. `crtr:act` needs `crtr:llm`. See [[northlight-apps/concepts/scopes-and-grants]].
|
|
34
34
|
- **What your app requires.** Providers the app cannot work without. A person is taken through that provider's setup the first time they sign in. See [[northlight-apps/guides/grant-changes]].
|
|
35
35
|
- **Monthly spend cap.** The most your app may spend of each person's AI subscription per month, in whole US dollars.
|
|
36
|
+
- **Run notice URL.** Where Northlight posts a signed notice when one of your app's background runs ends. Type it in **Notice URL** and choose **Save**; empty it and save to turn notices off. The URL must be `https`, at most 512 characters, with no `#fragment`. The directory refuses a plain `http` URL with `run_notice_url` as the field. The field's hint says `http://localhost` is accepted while developing, but the production directory refuses it (checked in the directory source, not yet checked on production), so test notices with a tunnel that gives you an `https` URL. What your app does with a notice is in [[northlight-apps/guides/background-runs]].
|
|
36
37
|
|
|
37
38
|
## Profile
|
|
38
39
|
|
|
39
40
|
- **How people see your app**: Name, Origin URL, Logo URL, and **Why your app needs access** (quoted on the consent page as your words). **Save details**.
|
|
40
41
|
- **Marketplace**: **Listed in the marketplace**, Price, **Sign-in start URL** and **Text welcome**. Agents can install a listed app for a person by text. A testing app stays out of the marketplace even when listed. The sign-in start URL is where Install sends people; your `/auth/login` route is a valid one, and a `localhost` URL is accepted. The text welcome is one or two plain sentences, at most 120 characters, with no links.
|
|
42
|
+
- **Delete this app.** At the bottom of the tab; see [Delete an app](#delete-an-app).
|
|
41
43
|
|
|
42
44
|
## Testers
|
|
43
45
|
|
|
@@ -55,23 +57,10 @@ Once your app is live you can keep developing on your laptop against production
|
|
|
55
57
|
|
|
56
58
|
**Register a separate development app.** Do this when you are changing what the app asks for, or need grants and data that live people never touch. Register another app in the console, for example "Texty (dev)", with a `localhost` origin and redirect URI and its own key. It starts in testing and stays there, so only you and your testers can sign in, and it never appears in the marketplace. Point your laptop's environment file at its client ID.
|
|
57
59
|
|
|
58
|
-
##
|
|
60
|
+
## Delete an app
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
On the **Profile** tab, the **Delete this app** card lists what goes: every person's connection to the app, yours included (each person's runtime deletes the app's runs, space and schedules), the app's sign-ins and keys, and its testers and marketplace listing. This cannot be undone, and the client ID is never reused.
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
To delete, type the app's full name in **Type the app's name, *name*, to confirm**. **Delete app** stays disabled until what you typed matches the name (surrounding spaces are ignored), and the directory checks the name again when you submit. After it deletes, the console returns to your app list. The app's next refresh for any person fails with `invalid_grant`, its key stops working and its page answers 404.
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
crtr cloud apps delete --app-id <client id> --confirm <client id>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
It answers `deleted: true` and `removed_grants`, the number of people's grants it removed. Each person's runtime deletes the app's runs, space and schedules, and the app's next refresh fails with `invalid_grant`. The app's key stops working, its page answers 404 and it disappears from your list. This cannot be undone. You must run it yourself in the terminal of the joined machine: an agent, including your chat agent, is refused with `provider_refused`. If you only want a clean slate for tests, changing the app's redirect URIs and keys is often simpler.
|
|
69
|
-
|
|
70
|
-
### Set the run notice URL
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
crtr cloud apps set-run-notice-url --app-id <client id> --url https://app.example.com/run-notices
|
|
74
|
-
crtr cloud apps set-run-notice-url --app-id <client id> --clear
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The URL must be https (plain http only on localhost in development) and at most 512 characters. What the app does with notices is in [[northlight-apps/guides/background-runs]].
|
|
66
|
+
The card is not shown for a provider or a first-party app, and the directory refuses to delete a provider (`app_is_provider`). If you only want a clean slate for tests, changing the app's redirect URIs and keys is often simpler.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var $=Object.defineProperty;var r=(e,n)=>$(e,"name",{value:n,configurable:!0});import{pathToFileURL as f}from"node:url";const x="crtr-doc-open",l=/\[\[([^[\]\n]+?)\]\]/g,N=/^[a-z0-9]+-[a-z0-9]+-[a-z0-9]+$/,h=r(e=>`\x1B[2m${e}\x1B[22m`,"DIM"),D=r(e=>`\x1B[4m${e}\x1B[24m`,"UNDERLINE");let d;function E(e){d=e}r(E,"setDocLinkContext");function b(e,n){const t=e.indexOf("/");if(t<=0)return{name:e};const o=e.slice(0,t);return o===n?.selfNodeId||n?.nodeName(o)!==void 0||N.test(o)?{nodeId:o,name:e.slice(t+1)}:{name:e}}r(b,"splitDocRef");function m(e,n=d){const{nodeId:t,name:o}=b(e,n);return t===void 0?{name:o}:t===n?.selfNodeId?{name:o}:{name:o,owner:n?.nodeName(t)??t.slice(t.lastIndexOf("-")+1)}}r(m,"docLinkLabel");function w(e,n=d){const t=m(e,n);return t.owner===void 0?t.name:`${t.name} \xB7 ${t.owner}`}r(w,"docLinkText");function O(e){return[...e.matchAll(l)].map(n=>n[1].trim()).filter(n=>n!=="")}r(O,"extractDocRefs");function R(e,n=d){return n===void 0||!e.includes("[[")?e:e.replace(l,(t,o)=>{const i=o.trim();if(i==="")return t;const s=m(i,n),c=n.pathFor(i),a=p(s.name),u=c===void 0?D(a):c.endsWith(".html")?`[${a}](${f(c).href})`:`[${a}](${f(c).href}#${x})`;return s.owner===void 0?u:`${u}${h(` \xB7 ${p(s.owner)}`)}`})}r(R,"rewriteProseDocLinks");function p(e){return e.replace(/([\\[\]*_`<>])/g,"\\$1")}r(p,"escapeLabel");export{x as DOC_OPEN_FRAGMENT,m as docLinkLabel,w as docLinkText,O as extractDocRefs,R as rewriteProseDocLinks,E as setDocLinkContext,b as splitDocRef};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
var n=Object.defineProperty;var o=(e,r)=>n(e,"name",{value:r,configurable:!0});import{Marked as i}from"marked";const l=/\[\[([^[\]\n]+?)\]\]/g;function a(e){return e.replace(/[&<>"']/g,r=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[r])}o(a,"escapeHtml");const p=new i({gfm:!0,renderer:{html({text:e}){return a(e)}}});function s(e){return e.split(/(<pre[\s\S]*?<\/pre>|<code>[\s\S]*?<\/code>)/).map((r,t)=>t%2===1?r:r.replace(l,(c,d)=>`<span class="doc-ref" title="${d.trim()}">${d.trim().split("/").pop()}</span>`)).join("")}o(s,"markDocRefs");function g(e){const r=e.name.split("/").pop()??e.name,t=s(p.parse(e.body,{async:!1}));return`<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>${a(r)}</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: light dark; --fg: #1f2328; --muted: #656d76; --bg: #ffffff; --panel: #f6f8fa; --border: #d0d7de; --accent: #0969da; }
|
|
9
|
+
@media (prefers-color-scheme: dark) { :root { --fg: #e6edf3; --muted: #8d96a0; --bg: #0d1117; --panel: #161b22; --border: #30363d; --accent: #4493f8; } }
|
|
10
|
+
* { box-sizing: border-box; }
|
|
11
|
+
body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
|
|
12
|
+
header { position: sticky; top: 0; display: flex; align-items: center; gap: 12px; padding: 10px 24px; background: var(--panel); border-bottom: 1px solid var(--border); }
|
|
13
|
+
header .name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
14
|
+
header .meta { color: var(--muted); font-size: 13px; white-space: nowrap; }
|
|
15
|
+
header .spacer { flex: 1; }
|
|
16
|
+
.toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
|
|
17
|
+
.toggle button { font: inherit; font-size: 13px; padding: 4px 12px; border: 0; background: transparent; color: var(--fg); cursor: pointer; }
|
|
18
|
+
.toggle button[aria-pressed="true"] { background: var(--accent); color: #fff; }
|
|
19
|
+
main { max-width: 860px; margin: 0 auto; padding: 32px 24px 80px; }
|
|
20
|
+
#raw { display: none; white-space: pre-wrap; word-wrap: break-word; font: 14px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; margin: 0; }
|
|
21
|
+
body.raw #raw { display: block; } body.raw #rendered { display: none; }
|
|
22
|
+
#rendered h1, #rendered h2 { border-bottom: 1px solid var(--border); padding-bottom: .3em; }
|
|
23
|
+
#rendered a { color: var(--accent); }
|
|
24
|
+
#rendered code { font: 85% ui-monospace, SFMono-Regular, Menlo, monospace; background: var(--panel); padding: .2em .4em; border-radius: 4px; }
|
|
25
|
+
#rendered pre { background: var(--panel); padding: 14px 16px; border-radius: 6px; overflow: auto; }
|
|
26
|
+
#rendered pre code { background: none; padding: 0; }
|
|
27
|
+
#rendered blockquote { margin: 0; padding: 0 1em; color: var(--muted); border-left: .25em solid var(--border); }
|
|
28
|
+
#rendered table { border-collapse: collapse; display: block; overflow: auto; }
|
|
29
|
+
#rendered th, #rendered td { border: 1px solid var(--border); padding: 6px 13px; }
|
|
30
|
+
#rendered tr:nth-child(2n) { background: var(--panel); }
|
|
31
|
+
#rendered img { max-width: 100%; }
|
|
32
|
+
.doc-ref { color: var(--accent); font-weight: 500; border-bottom: 1px dotted var(--accent); cursor: help; }
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<header>
|
|
37
|
+
<span class="name" title="${a(e.name)}">${a(r)}</span>
|
|
38
|
+
<span class="meta">${a(e.name)} \xB7 revision ${e.revision}</span>
|
|
39
|
+
<span class="spacer"></span>
|
|
40
|
+
<span class="toggle" role="group" aria-label="View">
|
|
41
|
+
<button type="button" data-mode="rendered" aria-pressed="true">Rendered</button>
|
|
42
|
+
<button type="button" data-mode="raw" aria-pressed="false">Raw</button>
|
|
43
|
+
</span>
|
|
44
|
+
</header>
|
|
45
|
+
<main>
|
|
46
|
+
<article id="rendered">
|
|
47
|
+
${t}
|
|
48
|
+
</article>
|
|
49
|
+
<pre id="raw">${a(e.body)}</pre>
|
|
50
|
+
</main>
|
|
51
|
+
<script>
|
|
52
|
+
(function () {
|
|
53
|
+
var KEY = 'crtr-doc-view';
|
|
54
|
+
function set(mode) {
|
|
55
|
+
document.body.classList.toggle('raw', mode === 'raw');
|
|
56
|
+
document.querySelectorAll('.toggle button').forEach(function (b) { b.setAttribute('aria-pressed', String(b.dataset.mode === mode)); });
|
|
57
|
+
try { localStorage.setItem(KEY, mode); } catch (e) {}
|
|
58
|
+
}
|
|
59
|
+
document.querySelectorAll('.toggle button').forEach(function (b) { b.addEventListener('click', function () { set(b.dataset.mode); }); });
|
|
60
|
+
var saved = null; try { saved = localStorage.getItem(KEY); } catch (e) {}
|
|
61
|
+
set(saved === 'raw' ? 'raw' : 'rendered');
|
|
62
|
+
})();
|
|
63
|
+
<\/script>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
66
|
+
`}o(g,"renderDocumentPage");export{g as renderDocumentPage};
|
|
@@ -25,6 +25,10 @@ export declare function docMaterializeRoot(): string;
|
|
|
25
25
|
/** Write the snapshot's body to its stable path (idempotent) and return the path.
|
|
26
26
|
* An existing file at that path is kept: it may hold the person's unsaved edit. */
|
|
27
27
|
export declare function materializeDocument(ref: string, snap: DocumentSnapshot, root?: string): string;
|
|
28
|
+
/** Write the snapshot's browser page beside its Markdown copy (idempotent per
|
|
29
|
+
* revision) and return the page's path. Materializes the Markdown copy and
|
|
30
|
+
* sidecar too, so the page's directory is still recognized as a document. */
|
|
31
|
+
export declare function materializeDocumentPage(ref: string, snap: DocumentSnapshot, root?: string): string;
|
|
28
32
|
/** The document a local path was materialized from, or undefined for a plain file. */
|
|
29
33
|
export declare function documentForPath(path: string, root?: string): MaterializedDoc | undefined;
|
|
30
34
|
/** Save a person's edit of a materialized document. */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
`),
|
|
3
|
-
`),{outcome:"saved",revision:
|
|
1
|
+
var v=Object.defineProperty;var o=(t,e)=>v(t,"name",{value:e,configurable:!0});import{createHash as $}from"node:crypto";import{existsSync as d,mkdirSync as S,readFileSync as y,renameSync as _,writeFileSync as p}from"node:fs";import{tmpdir as x}from"node:os";import{basename as A,dirname as g,join as c}from"node:path";import{renderDocumentPage as C}from"./doc-html.js";const z="CRTR_ATTACH_DOC_SAVEBACK",m=".crtr-doc.json";function a(){return c(x(),"crtr-docs")}o(a,"docMaterializeRoot");const f=o(t=>$("sha256").update(t).digest("hex"),"sha");function h(t){return t.replace(/[^A-Za-z0-9._-]/g,"_").slice(0,120)||"doc"}o(h,"safeSegment");function k(t,e,r=a()){const i=c(r,h(e.id),`r${e.revision}`),n=c(i,`${h(A(e.name))}.md`),s=c(i,m);if(d(n)&&d(s))return n;S(i,{recursive:!0});const l={ref:t,id:e.id,name:e.name,revision:e.revision,heads_key:e.heads_key,sha256:f(e.body)};return u(n,e.body),u(s,`${JSON.stringify(l)}
|
|
2
|
+
`),n}o(k,"materializeDocument");function R(t,e,r=a()){const n=k(t,e,r).replace(/\.md$/,".html");return d(n)||u(n,C({name:e.name,revision:e.revision,body:e.body})),n}o(R,"materializeDocumentPage");function u(t,e){const r=`${t}.${process.pid}.tmp`;p(r,e),_(r,t)}o(u,"writeAtomic");function w(t,e=a()){if(t.startsWith(`${e}/`))try{const r=JSON.parse(y(c(g(t),m),"utf8"));return typeof r.id=="string"&&typeof r.heads_key=="string"?r:void 0}catch{return}}o(w,"documentForPath");async function F(t,e,r=a()){const i=w(t,r);if(i===void 0||!d(t))return{outcome:"missing"};const n=y(t,"utf8");if(f(n)===i.sha256)return{outcome:"unchanged"};try{const s=await e(i.id,n,i.heads_key);return s==="stale"?{outcome:"stale",path:t}:(u(c(g(t),m),`${JSON.stringify({...i,sha256:f(n)})}
|
|
3
|
+
`),{outcome:"saved",revision:s.revision})}catch(s){return{outcome:"failed",message:s instanceof Error?s.message:String(s)}}}o(F,"saveDocumentEdit");function J(t,e){switch(e.outcome){case"unchanged":return;case"saved":return`Saved ${t} as revision ${e.revision}`;case"stale":return`${t} changed while you edited it \u2014 not saved; your text is kept at ${e.path}`;case"missing":return`Could not find the edited copy of ${t}`;case"failed":return`Could not save ${t}: ${e.message}`}}o(J,"saveOutcomeNotice");export{z as DOC_SAVEBACK_ENV,a as docMaterializeRoot,w as documentForPath,k as materializeDocument,R as materializeDocumentPage,F as saveDocumentEdit,J as saveOutcomeNotice};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CanvasSource } from '../../../core/canvas/source.js';
|
|
2
|
+
import type { DocLinkOpenMode } from '../../../types.js';
|
|
2
3
|
export interface DocumentLinks {
|
|
3
4
|
/** Whether a clicked local path is a materialized document. */
|
|
4
5
|
isDocumentPath(path: string): boolean;
|
|
@@ -16,6 +17,8 @@ export declare function createDocumentLinks(opts: {
|
|
|
16
17
|
selfNodeId: string;
|
|
17
18
|
remote: boolean;
|
|
18
19
|
canvasSource: CanvasSource;
|
|
20
|
+
/** Where a link points: the browser page or the editable Markdown copy. */
|
|
21
|
+
openMode: DocLinkOpenMode;
|
|
19
22
|
/** Re-render the transcript's Markdown after a name or path lands. */
|
|
20
23
|
refresh: () => void;
|
|
21
24
|
}): DocumentLinks;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var k=Object.defineProperty;var i=(n,o)=>k(n,"name",{value:o,configurable:!0});import{APIError as C}from"../../../api/errors.js";import{cliClient as v}from"../../../commands/api-client.js";import{setDocLinkContext as
|
|
1
|
+
var k=Object.defineProperty;var i=(n,o)=>k(n,"name",{value:o,configurable:!0});import{APIError as C}from"../../../api/errors.js";import{cliClient as v}from"../../../commands/api-client.js";import{setDocLinkContext as g}from"../render/doc-links.js";import{DOC_SAVEBACK_ENV as y,documentForPath as h,materializeDocument as b,materializeDocumentPage as E,saveDocumentEdit as L,saveOutcomeNotice as O}from"./doc-materialize.js";async function w(n){const o=await v().objects.read(n);if(o.state!=="object"||o.type!=="document")return`${n} is not a readable document`;const a=o.data;if(a.needs_merge||a.bodies.length!==1)return`${n} has ${a.heads.length} heads and needs a merge`;const s=o.object.owner?.handle;return{id:o.object.id,name:s===void 0?o.object.name:`${s}/${o.object.name}`,revision:a.revision,heads_key:a.heads.map(d=>d.rev_id).sort().join(","),body:a.bodies[0]}}i(w,"readSnapshot");function M(n){const o=new Map,a=new Map,s=new Set,d=new Set;let m=!1,f=!1,l=!1;const j=i(()=>{m||f||(f=!0,n.canvasSource.listNodes().then(t=>{for(const e of t)o.set(e.node_id,e.name);m=!0,l||n.refresh()}).catch(()=>{m=!0}).finally(()=>{f=!1}))},"loadRoster"),D=i(t=>{n.remote||s.has(t)||d.has(t)||(s.add(t),w(t).then(e=>{if(typeof e=="string"){d.add(t);return}a.set(t,n.openMode==="browser"?E(t,e):b(t,e)),l||n.refresh()}).catch(()=>{d.add(t)}).finally(()=>{s.delete(t)}))},"materialize"),N={selfNodeId:n.selfNodeId,nodeName:i(t=>{const e=o.get(t);return e===void 0&&j(),e},"nodeName"),pathFor:i(t=>{const e=a.get(t);return e===void 0&&D(t),e},"pathFor")};return g(N),{isDocumentPath:i(t=>h(t)!==void 0,"isDocumentPath"),async prepareOpen(t){if(n.remote)return{notice:"Opening a document is unavailable in a remote attach"};const e=h(t);if(e===void 0)return{notice:"Not a document link"};try{const r=await w(e.id);if(typeof r=="string")return{notice:r};const c=b(e.ref,r);return a.set(e.ref,c),{path:c}}catch(r){return{notice:`Could not read ${e.name}: ${r instanceof Error?r.message:String(r)}`}}},async consumeSaveBack(t){const e=process.env[y];if(e===void 0||e==="")return;delete process.env[y];const r=h(e),c=await L(e,async(S,_,$)=>{try{return{revision:(await v().docs.edit(S,{body:_,base:$,rationale:"edited by the person in the attach viewer"})).revision}}catch(u){if(u instanceof C&&u.code==="stale_revision")return"stale";throw u}}),p=O(r?.name??e,c);p!==void 0&&t(p)},dispose(){l=!0,g(void 0)}}}i(M,"createDocumentLinks");export{M as createDocumentLinks};
|