@north-light/crouter 0.3.320 → 0.3.322
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/bin/runtime-selector.mjs +52 -3
- package/dist/api/client.d.ts +31 -17
- package/dist/api/client.js +2 -2
- package/dist/api/dto/broker-ops.d.ts +0 -1
- package/dist/api/dto/modelauth.d.ts +15 -0
- package/dist/api/dto/node-outcomes.d.ts +6 -0
- package/dist/api/dto/nodes.d.ts +4 -3
- package/dist/api/dto/review-comments.d.ts +8 -0
- package/dist/api/errors.d.ts +6 -3
- package/dist/api/errors.js +1 -1
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +1 -1
- package/dist/api/node-transport.d.ts +18 -0
- package/dist/api/node-transport.js +1 -0
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +1 -1
- package/dist/builtin-memory/crouter-sdk.md +86 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +1 -1
- package/dist/cli.js +8 -2
- package/dist/clients/attach/render/chat-view.d.ts +5 -1
- package/dist/clients/attach/render/chat-view.js +1 -1
- package/dist/clients/attach/session/frames.d.ts +1 -0
- package/dist/clients/attach/session/frames.js +1 -1
- package/dist/clients/attach/viewer.js +606 -606
- package/dist/clients/inbox/review/comments-client.d.ts +8 -1
- package/dist/clients/inbox/review/comments-client.js +1 -1
- package/dist/clients/inbox/review/companion-pane.d.ts +17 -0
- package/dist/clients/inbox/review/companion-pane.js +1 -1
- package/dist/clients/inbox/review/document-surface.d.ts +10 -0
- package/dist/clients/inbox/review/document-surface.js +2 -2
- package/dist/clients/inbox/review/frame.js +1 -1
- package/dist/clients/inbox/review/keys.d.ts +2 -0
- package/dist/clients/inbox/review/keys.js +1 -1
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/human/prompts.js +1 -1
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/sys/branch.js +1 -1
- package/dist/commands/sys/connect.d.ts +1 -0
- package/dist/commands/sys/connect.js +3 -0
- package/dist/commands/sys/daemon.js +1 -1
- package/dist/commands/sys/panels/provider-panel.js +1 -1
- package/dist/commands/sys/provider-login.d.ts +8 -0
- package/dist/commands/sys/provider-login.js +9 -0
- package/dist/commands/sys/setup-core.d.ts +4 -0
- package/dist/commands/sys/setup-core.js +4 -4
- package/dist/commands/sys.js +1 -1
- package/dist/core/canvas/browse/app.js +3 -3
- package/dist/core/canvas/canvas.d.ts +3 -3
- package/dist/core/canvas/canvas.js +17 -14
- package/dist/core/canvas/migrations.js +34 -27
- package/dist/core/canvas/node-sql-migration.d.ts +1 -0
- package/dist/core/canvas/node-sql-migration.js +28 -2
- package/dist/core/canvas/paths.d.ts +8 -4
- package/dist/core/canvas/paths.js +1 -1
- package/dist/core/canvas/recovery.d.ts +1 -0
- package/dist/core/canvas/recovery.js +1 -1
- package/dist/core/canvas/worktrees.d.ts +1 -0
- package/dist/core/canvas/worktrees.js +1 -1
- package/dist/core/config.js +1 -1
- package/dist/core/human/requests.d.ts +1 -0
- package/dist/core/human/requests.js +17 -13
- package/dist/core/review/fork.d.ts +8 -0
- package/dist/core/review/fork.js +5 -0
- package/dist/core/runtime/broker/daemon-ops.js +1 -1
- package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
- package/dist/core/runtime/broker/extension-abort.js +1 -1
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/spawn.d.ts +5 -1
- package/dist/core/runtime/spawn.js +2 -2
- package/dist/core/runtime/stop-guard.d.ts +1 -3
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/stop-signals.d.ts +0 -1
- package/dist/core/runtime/stop-signals.js +1 -1
- package/dist/core/secrets.d.ts +3 -0
- package/dist/core/secrets.js +2 -2
- package/dist/core/subscription-state.d.ts +1 -1
- package/dist/core/subscription-state.js +3 -3
- package/dist/core/termrender/termrender.d.ts +3 -1
- package/dist/core/termrender/termrender.js +13 -13
- package/dist/core/user-settings.d.ts +4 -0
- package/dist/core/user-settings.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +1 -1
- package/dist/daemon/api/handlers/health.d.ts +1 -1
- package/dist/daemon/api/handlers/health.js +14 -1
- package/dist/daemon/api/handlers/inbox.js +1 -1
- package/dist/daemon/api/handlers/modelauth.js +1 -1
- package/dist/daemon/api/handlers/review-comments.js +1 -1
- package/dist/daemon/api/map.d.ts +4 -2
- package/dist/daemon/api/map.js +2 -2
- package/dist/daemon/api/server.d.ts +5 -1
- package/dist/daemon/api/server.js +4 -4
- package/dist/daemon/companion-retire.d.ts +3 -1
- package/dist/daemon/companion-retire.js +1 -1
- package/dist/daemon/crtrd-cli.js +1 -1
- package/dist/daemon/crtrd.js +7 -7
- package/dist/daemon/human/finish.d.ts +4 -1
- package/dist/daemon/human/finish.js +7 -7
- package/dist/daemon/human/settle.d.ts +8 -6
- package/dist/daemon/human/settle.js +1 -1
- package/dist/daemon/manage.d.ts +1 -0
- package/dist/daemon/manage.js +2 -2
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
- package/dist/pi-extensions/canvas-stophook.js +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.js +1 -1
- package/docs/sdk/README.md +54 -0
- package/docs/sdk/client.md +92 -0
- package/docs/sdk/docker.md +63 -0
- package/docs/sdk/errors.md +85 -0
- package/docs/sdk/getting-started.md +160 -0
- package/docs/sdk/memory.md +107 -0
- package/docs/sdk/migration.md +108 -0
- package/docs/sdk/nodes.md +193 -0
- package/docs/sdk/resources.md +71 -0
- package/docs/sdk/streaming.md +124 -0
- package/package.json +1 -1
- package/runtime.lock.json +6 -6
|
@@ -33,12 +33,13 @@ export type ReviewRequest = <T>(operation: () => Promise<T>) => Promise<T>;
|
|
|
33
33
|
export interface CommentAuthority {
|
|
34
34
|
anchorBase(): Promise<AnchorBase>;
|
|
35
35
|
list(): Promise<AnchoredComment[]>;
|
|
36
|
+
/** Resolves to the new comment's id, which forking it needs. */
|
|
36
37
|
create(p: {
|
|
37
38
|
line: number;
|
|
38
39
|
endLine: number;
|
|
39
40
|
text: string;
|
|
40
41
|
docHash: string;
|
|
41
|
-
}): Promise<
|
|
42
|
+
}): Promise<string>;
|
|
42
43
|
edit(p: {
|
|
43
44
|
id: string;
|
|
44
45
|
text: string;
|
|
@@ -61,6 +62,12 @@ export interface CommentAuthority {
|
|
|
61
62
|
detached: true;
|
|
62
63
|
}>;
|
|
63
64
|
}): Promise<void>;
|
|
65
|
+
/** Hand one comment to its own fork of the review companion; resolves to the
|
|
66
|
+
* newborn fork and the cwd a viewer for it runs in. */
|
|
67
|
+
fork(commentId: string): Promise<{
|
|
68
|
+
nodeId: string;
|
|
69
|
+
cwd: string;
|
|
70
|
+
}>;
|
|
64
71
|
onChanged(fn: () => void): () => void;
|
|
65
72
|
}
|
|
66
73
|
/** The terminal's complete daemon seam for one review's comment projection. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=Object.defineProperty;var
|
|
1
|
+
var c=Object.defineProperty;var i=(t,n)=>c(t,"name",{value:n,configurable:!0});import{ApiError as s}from"../../../api/errors.js";import{cliClient as o}from"../../../commands/api-client.js";import{watchReviewActivity as d}from"../../../core/review/signal.js";class l extends Error{static{i(this,"ReviewDocumentError")}code;details;constructor(n,e,a){super(e),this.name="ReviewDocumentError",this.code=n,this.details=a}}function v(t,n){return{async anchorBase(){const e=await n(()=>o().getReviewDocumentBase(t));return{file:e.file,text:e.anchor_base_text,docHash:e.anchor_base_sha256}},async list(){return(await n(()=>o().listReviewComments(t,{status:"all"}))).comments.map(h)},async create(e){return(await r(()=>n(()=>o().createReviewComment(t,{text:e.text,anchor:{line:e.line,end_line:e.endLine},doc_hash:e.docHash,actor_node_id:null})))).comment.comment_id},async edit(e){await r(()=>n(()=>o().editReviewComment(e.id,{text:e.text,if_revision:e.ifRevision,actor_node_id:null})))},async resolve(e){await r(()=>n(()=>o().resolveReviewComment(e,{actor_node_id:null})))},async reopen(e){await r(()=>n(()=>o().reopenReviewComment(e,{actor_node_id:null})))},async remove(e){await r(()=>n(()=>o().deleteReviewComment(e,{actor_node_id:null})))},async remapRanges(e){await r(()=>n(()=>o().updateReviewCommentRanges(t,{from_doc_hash:e.fromDocHash,to_doc_hash:e.toDocHash,entries:e.entries.map(a=>"detached"in a?{comment_id:a.id,detached:!0}:{comment_id:a.id,anchor:{line:a.anchor.line,end_line:a.anchor.endLine}})})))},async fork(e){const a=await n(()=>o().forkReviewComment(e));return{nodeId:a.node_id,cwd:a.cwd}},onChanged(e){return d(e)}}}i(v,"createCommentAuthority");function h(t){const n={line:t.anchor.line,endLine:t.anchor.end_line};return{id:t.comment_id,revision:t.revision,...n,lineText:t.anchor_line_text,comment:t.text,status:t.status,authorKind:t.author.kind,createdAt:t.created_at,anchorStatus:t.anchor_state==="detached"?"detached":"live",...t.anchor_state==="detached"?{originalRange:n}:{}}}i(h,"projectComment");async function r(t){try{return await t()}catch(n){throw n instanceof s&&_(n.code)?new l(n.code,n.message,n.details):n}}i(r,"translateDocumentError");function _(t){return t==="stale_document"||t==="stale_comment"||t==="range_batch_incomplete"||t==="range_batch_stale"}i(_,"isDocumentErrorCode");export{l as ReviewDocumentError,v as createCommentAuthority};
|
|
@@ -3,6 +3,9 @@ export declare const MIN_COMPANION_COLS = 40;
|
|
|
3
3
|
export declare const MIN_COMPANION_ROWS = 12;
|
|
4
4
|
export declare const COMPANION_WIDTH_RATIO = 0.42;
|
|
5
5
|
export declare const COMPANION_HEIGHT_RATIO = 0.4;
|
|
6
|
+
/** A fork column narrower than this cannot hold a readable conversation, so the
|
|
7
|
+
* split is refused rather than shipping an unusable pane. */
|
|
8
|
+
export declare const MIN_FORK_COLS = 52;
|
|
6
9
|
export type CompanionPaneOrigin = 'opened' | 'adopted';
|
|
7
10
|
export type CompanionPaneResult = {
|
|
8
11
|
ok: true;
|
|
@@ -20,6 +23,20 @@ export declare function openCompanionPane(args: {
|
|
|
20
23
|
cwd: string;
|
|
21
24
|
callerPane: string | undefined;
|
|
22
25
|
}): CompanionPaneResult;
|
|
26
|
+
/** Open a column for a comment's fork, splitting the rightmost review pane —
|
|
27
|
+
* the newest fork, else the companion — so columns accumulate to the right and
|
|
28
|
+
* the document keeps its width. */
|
|
29
|
+
export declare function openForkPane(args: {
|
|
30
|
+
nodeId: string;
|
|
31
|
+
cwd: string;
|
|
32
|
+
splitFrom: string | undefined;
|
|
33
|
+
}): {
|
|
34
|
+
ok: true;
|
|
35
|
+
paneId: string;
|
|
36
|
+
} | {
|
|
37
|
+
ok: false;
|
|
38
|
+
reason: 'no_tmux' | 'no_caller_pane' | 'split_refused' | 'no_room';
|
|
39
|
+
};
|
|
23
40
|
/** True while tmux can still describe `paneId` and its process is alive.
|
|
24
41
|
* Deliberately tag-free: a companion pane carries `@crtr_node` only once its
|
|
25
42
|
* viewer has connected, so a tag check would call a still-booting pane dead
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var M=Object.defineProperty;var a=(e,o)=>M(e,"name",{value:o,configurable:!0});import{inTmux as p,liveViewerPanes as O,paneRunning as d,paneSize as _,splitWindow as m,paneExists as I,getPaneOption as N,closePane as k}from"../../../core/runtime/placement-tmux.js";import{viewerEnv as P}from"../../../commands/surface/node/placement.js";const h=99,C=40,v=12,w=.42,S=.4,z=52;function L(e){const{companionNodeId:o,cwd:s,callerPane:n}=e;if(!p())return{ok:!1,reason:"no_tmux"};const t=O().filter(r=>r.nodeId===o&&d(r.pane)).sort((r,x)=>x.stampMs-r.stampMs);if(t.length>0)return{ok:!0,paneId:t[0].pane,origin:"adopted"};if(n===void 0||n==="")return{ok:!1,reason:"no_caller_pane"};const l=_(n);if(l===null)return{ok:!1,reason:"split_refused"};let c,u;l.cols>=h?(c=!1,u=Math.max(C,Math.round(l.cols*w))):(c=!0,u=Math.max(v,Math.round(l.rows*S)));const f=m(n,{cwd:s,env:P(),command:`crtr surface attach to ${o}`,vertical:c,size:u});return f===null?{ok:!1,reason:"split_refused"}:{ok:!0,paneId:f,origin:"opened"}}a(L,"openCompanionPane");function R(e){const{nodeId:o,cwd:s,splitFrom:n}=e;if(!p())return{ok:!1,reason:"no_tmux"};if(n===void 0||n==="")return{ok:!1,reason:"no_caller_pane"};const i=_(n);if(i===null)return{ok:!1,reason:"split_refused"};if(Math.floor(i.cols/2)<z)return{ok:!1,reason:"no_room"};const t=m(n,{cwd:s,env:P(),command:`crtr surface attach to ${o}`,vertical:!1,size:Math.floor(i.cols/2)});return t===null?{ok:!1,reason:"split_refused"}:{ok:!0,paneId:t}}a(R,"openForkPane");function E(e){return I(e)&&d(e)}a(E,"companionPaneLive");function F(e,o){!I(e)||N(e,"@crtr_node")!==o||k(e)}a(F,"closeCompanionPane");export{S as COMPANION_HEIGHT_RATIO,w as COMPANION_WIDTH_RATIO,C as MIN_COMPANION_COLS,v as MIN_COMPANION_ROWS,z as MIN_FORK_COLS,h as SPLIT_MIN_COLS,F as closeCompanionPane,E as companionPaneLive,L as openCompanionPane,R as openForkPane};
|
|
@@ -62,6 +62,9 @@ export declare class ReviewDocumentSurface implements SurfaceComponent {
|
|
|
62
62
|
* `paneOrigin` is status-row copy only: teardown reads `paneId`. */
|
|
63
63
|
private paneId;
|
|
64
64
|
private paneOrigin;
|
|
65
|
+
/** Fork columns this surface opened, oldest first. The newest live one is
|
|
66
|
+
* what the next fork splits against, so columns accumulate rightward. */
|
|
67
|
+
private forkPanes;
|
|
65
68
|
/** Row increments of a half-page move still to be painted, and the timer
|
|
66
69
|
* walking them. A fresh press settles whatever is in flight before starting
|
|
67
70
|
* its own move, so a held key cannot pile up overlapping animations. */
|
|
@@ -126,6 +129,13 @@ export declare class ReviewDocumentSurface implements SurfaceComponent {
|
|
|
126
129
|
* live pane already and there is no live companion viewer to adopt. A second
|
|
127
130
|
* call therefore keeps the bound pane, whatever state its viewer is in. */
|
|
128
131
|
openCompanion(): void;
|
|
132
|
+
/** The pane a new fork column splits from: the newest live fork, else the
|
|
133
|
+
* companion, else this viewer. */
|
|
134
|
+
private rightmostReviewPane;
|
|
135
|
+
/** Hand one comment to its own fork of the companion and place its column.
|
|
136
|
+
* The comment is already saved by the time this runs, so every failure here
|
|
137
|
+
* is stated as a notice rather than failing the comment. */
|
|
138
|
+
private forkComment;
|
|
129
139
|
/** The one path out of a review that has reached a terminal state — approved,
|
|
130
140
|
* canceled here, or decided elsewhere. The pane goes with the review, never
|
|
131
141
|
* with Esc: closing the document leaves the conversation standing so the
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var J=Object.defineProperty;var d=(o,e)=>J(o,"name",{value:e,configurable:!0});import{relative as Q,resolve as E}from"node:path";import{CURSOR_MARKER as H,Key as Z,matchesKey as k}from"@earendil-works/pi-tui";import{documentLines as ee}from"../../../core/document-lines.js";import{codeRenderedDoc as te}from"../../../core/termrender/code-doc.js";import{deriveAnchorUnits as $,remapUnitIndex as j,unitIndexForLine as se,unitRangeForSpan as T}from"./anchors.js";import{centerHorizontal as W,clipLine as w,CYAN as A,DIM as p,RESET as l,REVERSE as ie,sanitize as N,truncate as ne,visibleWidth as b,YELLOW as O}from"../tui/ansi.js";import{ReviewDocumentError as oe}from"./comments-client.js";import{closeCompanionPane as re,companionPaneLive as ce,openCompanionPane as ae}from"./companion-pane.js";import{PAN_STEP as he,docSurface as de,maxHScroll as le,panBy as ue,renderReviewDoc as fe,renderReviewFrame as me,renderWidthForCols as pe,reservedRows as F}from"./frame.js";import{matchReviewKey as B,reviewKeyText as C}from"./keys.js";import{renderInputBuffer as ge}from"../tui/render.js";import{openReviewedFileInEditor as we}from"./nvim.js";import{buildLineMap as K,remapRange as ve}from"./remap.js";import{cleanPastedText as be,closeCompose as Le,closeHelp as _,closeList as Re,commitCompose as xe,ensureAnchorVisible as g,halfPageRows as Se,initReviewState as $e,moveActiveUnit as V,moveListIndex as q,moveViewRows as Ae,openComposeEdit as Ce,openComposeNew as Me,openHelp as ye,openList as Ie,scrollBy as De,textBackspace as Pe,textDelete as Ue,textEnd as Ee,textHome as He,textInsert as M,textVerticalWrapped as je,textWordBackspace as Te,textWordLeft as We,textWordRight as Ne,deleteAtListIndex as Oe,undoLast as Fe}from"./state.js";const Be=126,Ke=4,_e=24;class ut{static{d(this,"ReviewDocumentSurface")}absFile;fileLabel;companionNodeId;companionCwd;callerPane;kind;source;comments;services;unsubscribeComments;ready;cols=0;hostRows=0;flowedRows=-1;_focused=!1;closed=!1;spaceArmed=!1;escapeConfirmArmed=!1;cancelArmed=!1;tornDown=!1;tornDownNotice;handle;paneId;paneOrigin;scrollSteps=[];scrollTimer;state;cache;projection=[];status="preparing";problem;composeAnchorLost=!1;remapChain=Promise.resolve();constructor(e){this.absFile=e.review.file,this.fileLabel=Ge(e.review.cwd,e.review.file),this.companionNodeId=e.review.companionNodeId,this.companionCwd=e.review.cwd,this.callerPane=e.callerPane,this.kind=de(e.review.file),this.source=e.source,this.comments=e.comments,this.services=e.services,this.source.onChange((t,s)=>{this.onSourceChanged(t,s)}),this.unsubscribeComments=this.comments.onChanged(()=>{this.refreshProjection()}),this.ready=this.align().catch(t=>{this.closed||(this.status="refused",this.problem=R(t),this.repaint())})}open(){return this.ready}hasUnsentCompose(){const e=this.state;return e?.mode==="compose"&&e.compose!==null&&e.compose.buffer.length>0}get focused(){return this._focused}set focused(e){this._focused!==e&&(this._focused=e,this.repaint())}setRows(e){this.hostRows=Math.max(0,Math.floor(e))}render(e){const t=Math.max(0,Math.floor(e));if(this.hostRows<=0)return[];const s=Math.min(t,Be);(s!==this.cols||this.hostRows!==this.flowedRows)&&(this.cols=s,this.flowedRows=this.hostRows,this.reflow());const i=this.rows<=0||this.cols<=0?[]:this.renderDocument();return[...qe(i,this.rows,t),this.statusRow(t)]}get rows(){return Math.max(0,this.hostRows-1)}renderDocument(){if(this.status!=="ready"||this.state===void 0)return this.renderPlaceholder();const e=this.document(this.cols,this.state.sourceRevision),t=me(this.state,this.fileLabel,e,this.cols,this.rows,{lineNumbers:this.kind.doc==="code",...this.footerLine()===void 0?{}:{notice:this.footerLine()}}),s=this.state.mode==="list"&&this.state.comments.some(n=>n.anchorStatus==="detached")?this.renderGroupedList(t):t,i=this.state.mode==="compose"&&this.composeAnchorLost?this.statedComposeAnchor(s):s;return this.focused?this.markCursorLocation(i,this.state):i}handleInput(e){if(!this.closed){if(this.cancelArmed){this.cancelArmed=!1;const t=B(e);if(t!==null&&C(t)==="x"){this.services.cancel();return}}if(k(e,Z.escape)){if(this.escapeConfirmArmed){if(this.escapeConfirmArmed=!1,this.consume(e))return;this.services.requestClose();return}if(this.hasUnsentCompose()){this.escapeConfirmArmed=!0,this.services.notice("unsent comment \u2014 press esc again to discard"),this.repaint();return}if(this.consume(e))return;this.services.requestClose();return}this.escapeConfirmArmed&&(this.escapeConfirmArmed=!1),this.consume(e)}}consume(e){if(this.status!=="ready")return!1;const t=this.state;if(t===void 0)return!1;const s=B(e);if(s===null)return!1;if(t.mode==="compose")return this.handleComposeKey(s),this.repaint(),!0;if(this.spaceArmed)return this.handleLeaderKey(s),!0;if(t.mode==="list"){const i=this.handleListKey(s);return this.repaint(),i}return t.mode==="help"?s.type==="help"||s.type==="cancel"&&s.text===void 0?(this.state=_(t),this.repaint(),!0):s.type==="cancel"?(this.state=_(t),this.repaint(),!0):!1:this.handleViewKey(s)}handleWheel(e,t){if(this.closed)return!1;const s=this.state;if(this.status!=="ready"||s===void 0)return!0;this.settleScrollAnimation();const i=this.state??s,n=this.document(this.cols,i.sourceRevision),r=(e==="up"?-1:1)*Math.max(1,Math.floor(t));return this.state=De(i,r,this.bodyHeight(i),n.lines.length),this.repaint(),!0}startScrollAnimation(e){this.settleScrollAnimation();const t=Ve(e),s=t.shift();s!==void 0&&(this.applyScrollRows(s),this.scrollSteps=t,this.scheduleScrollStep())}scheduleScrollStep(){this.scrollSteps.length!==0&&(this.scrollTimer=setTimeout(()=>{this.scrollTimer=void 0;const e=this.scrollSteps.shift();e===void 0||this.closed||(this.applyScrollRows(e),this.repaint(),this.scheduleScrollStep())},_e))}settleScrollAnimation(){this.stopScrollAnimation();const e=this.scrollSteps;this.scrollSteps=[];for(const t of e)this.applyScrollRows(t)}stopScrollAnimation(){this.scrollTimer!==void 0&&(clearTimeout(this.scrollTimer),this.scrollTimer=void 0)}applyScrollRows(e){const t=this.state;if(this.status!=="ready"||t===void 0)return;const s=this.document(this.cols,t.sourceRevision);this.state=Ae(t,e,this.bodyHeight(t),s.lines.length)}invalidate(){this.closed||this.handle?.invalidate()}setHandle(e){if(this.handle===void 0&&(this.handle=e,e.onClose(()=>this.close()),this.tornDown)){e.close();const t=this.tornDownNotice;this.tornDownNotice=void 0,t!==void 0&&this.services.notice(t.text,t.opts)}}summary(){if(this.status==="refused")return`${this.fileLabel} \u2014 unavailable`;if(this.status==="preparing")return`${this.fileLabel} \u2014 preparing`;const e=this.projection.filter(i=>i.status==="open").length,t=this.projection.filter(i=>i.anchorStatus==="detached").length,s=[this.fileLabel,`${e} open`];return t>0&&s.push(`${t} detached`),this.source.current().status==="stale"&&s.push(`stale (${this.source.current().staleReason??"unreadable"})`),s.join(" \xB7 ")}companionSegment(){return this.paneOrigin===void 0?"companion none":`companion ${this.paneOrigin}`}statusRow(e){if(e<=0)return"";const t=w(this.companionSegment(),e),s=w(this.summary(),Math.max(0,e-b(t)-1)),i=Math.max(0,e-b(s)-b(t));return`${p}${s}${" ".repeat(i)}${t}${l}`}openCompanion(){if(this.closed||this.tornDown||this.paneId!==void 0&&ce(this.paneId))return;const e=ae({companionNodeId:this.companionNodeId,cwd:this.companionCwd,callerPane:this.callerPane});if(e.ok){this.paneId=e.paneId,this.paneOrigin=e.origin,this.repaint();return}if(e.reason==="split_refused"){this.services.notice("tmux refused the companion split \u2014 press space a to retry");return}this.services.notice(`The review companion needs tmux \u2014 run: crtr surface attach to ${this.companionNodeId}`,{sticky:!0})}teardown(e,t){this.tornDown||(this.tornDown=!0,this.paneId!==void 0&&re(this.paneId,this.companionNodeId),this.paneId=void 0,this.paneOrigin=void 0,this.handle===void 0&&(this.tornDownNotice={text:e,...t===void 0?{}:{opts:t}}),this.handle?.close(),this.services.notice(e,t))}close(){this.closed||(this.source.close(),this.closed=!0,this.stopScrollAnimation(),this.scrollSteps=[],this.unsubscribeComments(),this.cache=void 0)}async align(){for(;;){const e=await this.comments.anchorBase();if(this.closed)return;if(E(e.file)!==E(this.absFile))throw new Error(`review document mismatch \u2014 the daemon's canonical file is ${e.file}, not ${this.absFile}`);const t=this.source.current();if(t.status==="stale")throw new Error(`the reviewed file cannot be read (${t.staleReason??"unreadable"})`);if(e.docHash!==t.sha256){const i=await this.comments.list();if(this.closed)return;if(this.movedSince(t))continue;const n=K(ee(e.text),t.lines);if(await this.comments.remapRanges({fromDocHash:e.docHash,toDocHash:t.sha256,entries:Je(y(i),n)}),this.closed)return}const s=await this.comments.list();if(this.closed)return;if(!this.movedSince(t)){this.projection=y(s),this.status="ready",this.buildState(t),this.repaint();return}}}movedSince(e){const t=this.source.current();return t.status==="live"&&t.sha256!==e.sha256}async realign(){if(!this.closed)try{await this.align()}catch(e){if(this.closed)return;this.status="refused",this.problem=R(e),this.repaint()}}buildState(e){const t=this.document(this.cols,e.revision);this.state=$e(e.lines,$(t),I(this.projection),e.revision),this.state=g(this.state,t,this.bodyHeight(this.state))}onSourceChanged(e,t){if(this.closed||this.status!=="ready")return;const s=this.state;if(s===void 0)return;if(e.status==="stale"){this.problem=void 0,this.repaint();return}if(e.text===t.text){this.problem=void 0,this.repaint();return}const i=K(t.lines,e.lines),n=new Map;for(const a of this.projection)n.set(a.id,a.anchorStatus==="detached"?{range:a.originalRange??{line:a.line,endLine:a.endLine},detached:!0}:L(i,{line:a.line,endLine:a.endLine}));const r=s.compose===null||this.composeAnchorLost?void 0:L(i,s.compose.anchor),c=L(i,z(s,s.activeUnit)),u=s.selectionAnchor===null?void 0:L(i,z(s,s.selectionAnchor)),m=Ye(i,this.topVisibleSourceLine(s),t.lines.length),v=this.document(this.cols,e.revision),{units:f,rowUnits:h}=$(v),G=T(f,c.range.line,c.range.endLine).hi,Y=u===void 0?null:T(f,u.range.line,u.range.endLine).lo,x=this.projection.map(a=>{const U=n.get(a.id);return U===void 0?a:Xe(a,U)});this.projection=x;const P={};for(const a of x)a.anchorStatus==="detached"&&(P[a.id]=a.originalRange??{line:a.line,endLine:a.endLine});this.composeAnchorLost=this.composeAnchorLost||r?.detached===!0;const X=s.compose===null||r===void 0?s.compose:{...s.compose,anchor:r.range};let S={...s,sourceLines:e.lines,units:f,rowUnits:h,comments:I(x),sourceRevision:e.revision,detached:P,activeUnit:G,selectionAnchor:Y,compose:X,listIndex:Math.max(0,Math.min(s.listIndex,x.length-1)),scroll:f[se(f,m)]?.firstRow??0};S=g(S,v,this.bodyHeight(S)),this.state=S,this.problem=this.composeAnchorLost?"the commented lines were removed \u2014 this comment has no anchor":void 0,this.repaint(),this.remapChain=this.remapChain.then(()=>this.submitBatch(t.sha256,e.sha256,n),()=>this.submitBatch(t.sha256,e.sha256,n))}async submitBatch(e,t,s){if(this.closed)return;const i=[...s].map(([n,r])=>r.detached?{id:n,detached:!0}:{id:n,anchor:r.range});try{if(await this.comments.remapRanges({fromDocHash:e,toDocHash:t,entries:i}),this.closed)return;await this.refreshProjection()}catch(n){if(this.closed)return;this.problem=`comment ranges were rejected \u2014 ${R(n)}`,this.repaint(),await this.realign()}}async refreshProjection(){if(!(this.closed||this.status==="refused"))try{const e=y(await this.comments.list());if(this.closed)return;this.projection=e;const t=this.state;if(t!==void 0){const s=I(e);this.state={...t,comments:s,detached:Object.fromEntries(s.flatMap(i=>i.anchorStatus==="detached"?[[i.id,i.originalRange??{line:i.line,endLine:i.endLine}]]:[])),listIndex:Math.max(0,Math.min(t.listIndex,s.length-1))}}this.repaint()}catch(e){if(this.closed)return;this.problem=`comments could not be read \u2014 ${R(e)}`,this.repaint()}}async mutate(e){if(!this.closed){try{if(await e(),this.closed)return;this.problem=void 0}catch(t){if(this.closed)return;this.problem=t instanceof oe?`${t.code.replace(/_/g," ")} \u2014 the document moved under this comment`:R(t),this.repaint()}await this.refreshProjection()}}handleComposeKey(e){const t=this.state,s=t?.compose;if(t===void 0||s===null||s===void 0)return;if(e.type==="cancel"&&e.text===void 0){this.composeAnchorLost=!1,this.state=Le(t);return}if(e.type==="confirm"){if(this.composeAnchorLost){this.problem="comment not saved \u2014 its anchored lines were removed";return}const n=xe(t);this.composeAnchorLost=!1,this.state=n.state;const r=n.commit;if(r===null)return;const c=this.source.current().sha256;if(r.editingId===null){this.mutate(()=>this.comments.create({line:r.anchor.line,endLine:r.anchor.endLine,text:r.text,docHash:c}));return}const u=this.projection.find(m=>m.id===r.editingId);if(u===void 0){this.problem="that comment no longer exists";return}this.mutate(()=>this.comments.edit({id:u.id,text:r.text,ifRevision:u.revision}));return}if(e.type==="composeNewline"){this.state={...t,compose:{...s,...M(s.buffer,s.cursor,`
|
|
2
|
-
`)}};return}if(e.type==="composeWordBackspace"){this.state={...t,compose:{...s,...Te(s.buffer,s.cursor)}};return}if(e.type==="composeBackspace"){this.state={...t,compose:{...s,...
|
|
1
|
+
var Z=Object.defineProperty;var d=(o,e)=>Z(o,"name",{value:e,configurable:!0});import{relative as k,resolve as H}from"node:path";import{CURSOR_MARKER as j,Key as ee,matchesKey as te}from"@earendil-works/pi-tui";import{documentLines as se}from"../../../core/document-lines.js";import{codeRenderedDoc as ie}from"../../../core/termrender/code-doc.js";import{deriveAnchorUnits as $,remapUnitIndex as F,unitIndexForLine as ne,unitRangeForSpan as T}from"./anchors.js";import{centerHorizontal as W,clipLine as v,CYAN as A,DIM as g,RESET as l,REVERSE as oe,sanitize as N,truncate as re,visibleWidth as L,YELLOW as O}from"../tui/ansi.js";import{ReviewDocumentError as ce}from"./comments-client.js";import{closeCompanionPane as _,companionPaneLive as C,openCompanionPane as ae,openForkPane as he}from"./companion-pane.js";import{PAN_STEP as de,docSurface as le,maxHScroll as ue,panBy as fe,renderReviewDoc as me,renderReviewFrame as pe,renderWidthForCols as ge,reservedRows as B}from"./frame.js";import{matchReviewKey as K,reviewKeyText as I}from"./keys.js";import{renderInputBuffer as we}from"../tui/render.js";import{openReviewedFileInEditor as ve}from"./nvim.js";import{buildLineMap as V,remapRange as be}from"./remap.js";import{cleanPastedText as Le,closeCompose as Re,closeHelp as q,closeList as xe,commitCompose as Se,ensureAnchorVisible as w,halfPageRows as $e,initReviewState as Ae,moveActiveUnit as z,moveListIndex as G,moveViewRows as Ce,openComposeEdit as Ie,openComposeNew as ye,openHelp as Me,openList as Pe,scrollBy as De,textBackspace as Ue,textDelete as Ee,textEnd as He,textHome as je,textInsert as y,textVerticalWrapped as Fe,textWordBackspace as Te,textWordLeft as We,textWordRight as Ne,deleteAtListIndex as Oe,undoLast as _e}from"./state.js";const Be=126,Ke=4,Ve=24;class ft{static{d(this,"ReviewDocumentSurface")}absFile;fileLabel;companionNodeId;companionCwd;callerPane;kind;source;comments;services;unsubscribeComments;ready;cols=0;hostRows=0;flowedRows=-1;_focused=!1;closed=!1;spaceArmed=!1;escapeConfirmArmed=!1;cancelArmed=!1;tornDown=!1;tornDownNotice;handle;paneId;paneOrigin;forkPanes=[];scrollSteps=[];scrollTimer;state;cache;projection=[];status="preparing";problem;composeAnchorLost=!1;remapChain=Promise.resolve();constructor(e){this.absFile=e.review.file,this.fileLabel=Ye(e.review.cwd,e.review.file),this.companionNodeId=e.review.companionNodeId,this.companionCwd=e.review.cwd,this.callerPane=e.callerPane,this.kind=le(e.review.file),this.source=e.source,this.comments=e.comments,this.services=e.services,this.source.onChange((t,s)=>{this.onSourceChanged(t,s)}),this.unsubscribeComments=this.comments.onChanged(()=>{this.refreshProjection()}),this.ready=this.align().catch(t=>{this.closed||(this.status="refused",this.problem=b(t),this.repaint())})}open(){return this.ready}hasUnsentCompose(){const e=this.state;return e?.mode==="compose"&&e.compose!==null&&e.compose.buffer.length>0}get focused(){return this._focused}set focused(e){this._focused!==e&&(this._focused=e,this.repaint())}setRows(e){this.hostRows=Math.max(0,Math.floor(e))}render(e){const t=Math.max(0,Math.floor(e));if(this.hostRows<=0)return[];const s=Math.min(t,Be);(s!==this.cols||this.hostRows!==this.flowedRows)&&(this.cols=s,this.flowedRows=this.hostRows,this.reflow());const i=this.rows<=0||this.cols<=0?[]:this.renderDocument();return[...ze(i,this.rows,t),this.statusRow(t)]}get rows(){return Math.max(0,this.hostRows-1)}renderDocument(){if(this.status!=="ready"||this.state===void 0)return this.renderPlaceholder();const e=this.document(this.cols,this.state.sourceRevision),t=pe(this.state,this.fileLabel,e,this.cols,this.rows,{lineNumbers:this.kind.doc==="code",...this.footerLine()===void 0?{}:{notice:this.footerLine()}}),s=this.state.mode==="list"&&this.state.comments.some(n=>n.anchorStatus==="detached")?this.renderGroupedList(t):t,i=this.state.mode==="compose"&&this.composeAnchorLost?this.statedComposeAnchor(s):s;return this.focused?this.markCursorLocation(i,this.state):i}handleInput(e){if(!this.closed){if(this.cancelArmed){this.cancelArmed=!1;const t=K(e);if(t!==null&&I(t)==="x"){this.services.cancel();return}}if(te(e,ee.escape)){if(this.escapeConfirmArmed){if(this.escapeConfirmArmed=!1,this.consume(e))return;this.services.requestClose();return}if(this.hasUnsentCompose()){this.escapeConfirmArmed=!0,this.services.notice("unsent comment \u2014 press esc again to discard"),this.repaint();return}if(this.consume(e))return;this.services.requestClose();return}this.escapeConfirmArmed&&(this.escapeConfirmArmed=!1),this.consume(e)}}consume(e){if(this.status!=="ready")return!1;const t=this.state;if(t===void 0)return!1;const s=K(e);if(s===null)return!1;if(t.mode==="compose")return this.handleComposeKey(s),this.repaint(),!0;if(this.spaceArmed)return this.handleLeaderKey(s),!0;if(t.mode==="list"){const i=this.handleListKey(s);return this.repaint(),i}return t.mode==="help"?s.type==="help"||s.type==="cancel"&&s.text===void 0?(this.state=q(t),this.repaint(),!0):s.type==="cancel"?(this.state=q(t),this.repaint(),!0):!1:this.handleViewKey(s)}handleWheel(e,t){if(this.closed)return!1;const s=this.state;if(this.status!=="ready"||s===void 0)return!0;this.settleScrollAnimation();const i=this.state??s,n=this.document(this.cols,i.sourceRevision),r=(e==="up"?-1:1)*Math.max(1,Math.floor(t));return this.state=De(i,r,this.bodyHeight(i),n.lines.length),this.repaint(),!0}startScrollAnimation(e){this.settleScrollAnimation();const t=qe(e),s=t.shift();s!==void 0&&(this.applyScrollRows(s),this.scrollSteps=t,this.scheduleScrollStep())}scheduleScrollStep(){this.scrollSteps.length!==0&&(this.scrollTimer=setTimeout(()=>{this.scrollTimer=void 0;const e=this.scrollSteps.shift();e===void 0||this.closed||(this.applyScrollRows(e),this.repaint(),this.scheduleScrollStep())},Ve))}settleScrollAnimation(){this.stopScrollAnimation();const e=this.scrollSteps;this.scrollSteps=[];for(const t of e)this.applyScrollRows(t)}stopScrollAnimation(){this.scrollTimer!==void 0&&(clearTimeout(this.scrollTimer),this.scrollTimer=void 0)}applyScrollRows(e){const t=this.state;if(this.status!=="ready"||t===void 0)return;const s=this.document(this.cols,t.sourceRevision);this.state=Ce(t,e,this.bodyHeight(t),s.lines.length)}invalidate(){this.closed||this.handle?.invalidate()}setHandle(e){if(this.handle===void 0&&(this.handle=e,e.onClose(()=>this.close()),this.tornDown)){e.close();const t=this.tornDownNotice;this.tornDownNotice=void 0,t!==void 0&&this.services.notice(t.text,t.opts)}}summary(){if(this.status==="refused")return`${this.fileLabel} \u2014 unavailable`;if(this.status==="preparing")return`${this.fileLabel} \u2014 preparing`;const e=this.projection.filter(i=>i.status==="open").length,t=this.projection.filter(i=>i.anchorStatus==="detached").length,s=[this.fileLabel,`${e} open`];return t>0&&s.push(`${t} detached`),this.source.current().status==="stale"&&s.push(`stale (${this.source.current().staleReason??"unreadable"})`),s.join(" \xB7 ")}companionSegment(){const e=this.paneOrigin===void 0?"companion none":`companion ${this.paneOrigin}`,t=this.forkPanes.length;return t===0?e:`${e} \xB7 ${t} fork${t===1?"":"s"}`}statusRow(e){if(e<=0)return"";const t=v(this.companionSegment(),e),s=v(this.summary(),Math.max(0,e-L(t)-1)),i=Math.max(0,e-L(s)-L(t));return`${g}${s}${" ".repeat(i)}${t}${l}`}openCompanion(){if(this.closed||this.tornDown||this.paneId!==void 0&&C(this.paneId))return;const e=ae({companionNodeId:this.companionNodeId,cwd:this.companionCwd,callerPane:this.callerPane});if(e.ok){this.paneId=e.paneId,this.paneOrigin=e.origin,this.repaint();return}if(e.reason==="split_refused"){this.services.notice("tmux refused the companion split \u2014 press space a to retry");return}this.services.notice(`The review companion needs tmux \u2014 run: crtr surface attach to ${this.companionNodeId}`,{sticky:!0})}rightmostReviewPane(){for(let e=this.forkPanes.length-1;e>=0;e-=1){const t=this.forkPanes[e];if(C(t.paneId))return t.paneId}return this.paneId!==void 0&&C(this.paneId)?this.paneId:this.callerPane}async forkComment(e){if(this.closed||this.tornDown)return;let t;try{t=await this.comments.fork(e)}catch(i){this.services.notice(`the comment was saved, but its fork could not start \u2014 ${b(i)}`);return}if(this.closed||this.tornDown)return;const s=he({nodeId:t.nodeId,cwd:t.cwd,splitFrom:this.rightmostReviewPane()});if(s.ok){this.forkPanes.push({paneId:s.paneId,nodeId:t.nodeId}),this.repaint();return}this.services.notice(s.reason==="no_room"?`no room for another fork column \u2014 the fork is running: crtr surface attach to ${t.nodeId}`:`the fork is running, but tmux gave it no column \u2014 run: crtr surface attach to ${t.nodeId}`,{sticky:!0})}teardown(e,t){if(!this.tornDown){this.tornDown=!0;for(const s of this.forkPanes)_(s.paneId,s.nodeId);this.forkPanes=[],this.paneId!==void 0&&_(this.paneId,this.companionNodeId),this.paneId=void 0,this.paneOrigin=void 0,this.handle===void 0&&(this.tornDownNotice={text:e,...t===void 0?{}:{opts:t}}),this.handle?.close(),this.services.notice(e,t)}}close(){this.closed||(this.source.close(),this.closed=!0,this.stopScrollAnimation(),this.scrollSteps=[],this.unsubscribeComments(),this.cache=void 0)}async align(){for(;;){const e=await this.comments.anchorBase();if(this.closed)return;if(H(e.file)!==H(this.absFile))throw new Error(`review document mismatch \u2014 the daemon's canonical file is ${e.file}, not ${this.absFile}`);const t=this.source.current();if(t.status==="stale")throw new Error(`the reviewed file cannot be read (${t.staleReason??"unreadable"})`);if(e.docHash!==t.sha256){const i=await this.comments.list();if(this.closed)return;if(this.movedSince(t))continue;const n=V(se(e.text),t.lines);if(await this.comments.remapRanges({fromDocHash:e.docHash,toDocHash:t.sha256,entries:Qe(M(i),n)}),this.closed)return}const s=await this.comments.list();if(this.closed)return;if(!this.movedSince(t)){this.projection=M(s),this.status="ready",this.buildState(t),this.repaint();return}}}movedSince(e){const t=this.source.current();return t.status==="live"&&t.sha256!==e.sha256}async realign(){if(!this.closed)try{await this.align()}catch(e){if(this.closed)return;this.status="refused",this.problem=b(e),this.repaint()}}buildState(e){const t=this.document(this.cols,e.revision);this.state=Ae(e.lines,$(t),P(this.projection),e.revision),this.state=w(this.state,t,this.bodyHeight(this.state))}onSourceChanged(e,t){if(this.closed||this.status!=="ready")return;const s=this.state;if(s===void 0)return;if(e.status==="stale"){this.problem=void 0,this.repaint();return}if(e.text===t.text){this.problem=void 0,this.repaint();return}const i=V(t.lines,e.lines),n=new Map;for(const a of this.projection)n.set(a.id,a.anchorStatus==="detached"?{range:a.originalRange??{line:a.line,endLine:a.endLine},detached:!0}:R(i,{line:a.line,endLine:a.endLine}));const r=s.compose===null||this.composeAnchorLost?void 0:R(i,s.compose.anchor),c=R(i,Y(s,s.activeUnit)),f=s.selectionAnchor===null?void 0:R(i,Y(s,s.selectionAnchor)),u=Xe(i,this.topVisibleSourceLine(s),t.lines.length),m=this.document(this.cols,e.revision),{units:p,rowUnits:h}=$(m),X=T(p,c.range.line,c.range.endLine).hi,J=f===void 0?null:T(p,f.range.line,f.range.endLine).lo,x=this.projection.map(a=>{const E=n.get(a.id);return E===void 0?a:Je(a,E)});this.projection=x;const U={};for(const a of x)a.anchorStatus==="detached"&&(U[a.id]=a.originalRange??{line:a.line,endLine:a.endLine});this.composeAnchorLost=this.composeAnchorLost||r?.detached===!0;const Q=s.compose===null||r===void 0?s.compose:{...s.compose,anchor:r.range};let S={...s,sourceLines:e.lines,units:p,rowUnits:h,comments:P(x),sourceRevision:e.revision,detached:U,activeUnit:X,selectionAnchor:J,compose:Q,listIndex:Math.max(0,Math.min(s.listIndex,x.length-1)),scroll:p[ne(p,u)]?.firstRow??0};S=w(S,m,this.bodyHeight(S)),this.state=S,this.problem=this.composeAnchorLost?"the commented lines were removed \u2014 this comment has no anchor":void 0,this.repaint(),this.remapChain=this.remapChain.then(()=>this.submitBatch(t.sha256,e.sha256,n),()=>this.submitBatch(t.sha256,e.sha256,n))}async submitBatch(e,t,s){if(this.closed)return;const i=[...s].map(([n,r])=>r.detached?{id:n,detached:!0}:{id:n,anchor:r.range});try{if(await this.comments.remapRanges({fromDocHash:e,toDocHash:t,entries:i}),this.closed)return;await this.refreshProjection()}catch(n){if(this.closed)return;this.problem=`comment ranges were rejected \u2014 ${b(n)}`,this.repaint(),await this.realign()}}async refreshProjection(){if(!(this.closed||this.status==="refused"))try{const e=M(await this.comments.list());if(this.closed)return;this.projection=e;const t=this.state;if(t!==void 0){const s=P(e);this.state={...t,comments:s,detached:Object.fromEntries(s.flatMap(i=>i.anchorStatus==="detached"?[[i.id,i.originalRange??{line:i.line,endLine:i.endLine}]]:[])),listIndex:Math.max(0,Math.min(t.listIndex,s.length-1))}}this.repaint()}catch(e){if(this.closed)return;this.problem=`comments could not be read \u2014 ${b(e)}`,this.repaint()}}async mutate(e){if(!this.closed){try{if(await e(),this.closed)return;this.problem=void 0}catch(t){if(this.closed)return;this.problem=t instanceof ce?`${t.code.replace(/_/g," ")} \u2014 the document moved under this comment`:b(t),this.repaint()}await this.refreshProjection()}}handleComposeKey(e){const t=this.state,s=t?.compose;if(t===void 0||s===null||s===void 0)return;if(e.type==="cancel"&&e.text===void 0){this.composeAnchorLost=!1,this.state=Re(t);return}if(e.type==="confirm"||e.type==="confirmFork"){if(this.composeAnchorLost){this.problem="comment not saved \u2014 its anchored lines were removed";return}const n=Se(t);this.composeAnchorLost=!1,this.state=n.state;const r=n.commit;if(r===null)return;const c=e.type==="confirmFork",f=this.source.current().sha256;if(r.editingId===null){this.mutate(async()=>{const m=await this.comments.create({line:r.anchor.line,endLine:r.anchor.endLine,text:r.text,docHash:f});c&&await this.forkComment(m)});return}const u=this.projection.find(m=>m.id===r.editingId);if(u===void 0){this.problem="that comment no longer exists";return}this.mutate(async()=>{await this.comments.edit({id:u.id,text:r.text,ifRevision:u.revision}),c&&await this.forkComment(u.id)});return}if(e.type==="composeNewline"){this.state={...t,compose:{...s,...y(s.buffer,s.cursor,`
|
|
2
|
+
`)}};return}if(e.type==="composeWordBackspace"){this.state={...t,compose:{...s,...Te(s.buffer,s.cursor)}};return}if(e.type==="composeBackspace"){this.state={...t,compose:{...s,...Ue(s.buffer,s.cursor)}};return}if(e.type==="composeDelete"){this.state={...t,compose:{...s,...Ee(s.buffer,s.cursor)}};return}if(e.type==="composeMove"){const n=e.direction==="wordLeft"?We(s.buffer,s.cursor):Ne(s.buffer,s.cursor);this.state={...t,compose:{...s,cursor:n}};return}if(e.type==="arrow"&&e.direction==="left"){this.state={...t,compose:{...s,cursor:Math.max(0,s.cursor-1)}};return}if(e.type==="arrow"&&e.direction==="right"){this.state={...t,compose:{...s,cursor:Math.min([...s.buffer].length,s.cursor+1)}};return}if(e.type==="composeHome"){this.state={...t,compose:{...s,cursor:je(s.buffer,s.cursor)}};return}if(e.type==="composeEnd"){this.state={...t,compose:{...s,cursor:He(s.buffer,s.cursor)}};return}if(e.type==="arrow"&&(e.direction==="up"||e.direction==="down")){const n=e.direction==="up"?-1:1;this.state={...t,compose:{...s,cursor:Fe(s.buffer,s.cursor,n,this.composeWidth())}};return}const i=I(e);if(i!==void 0){if(e.type==="paste"){const n=Le(i);if(n.length===0)return;this.state={...t,compose:{...s,...y(s.buffer,s.cursor,n)}};return}this.state={...t,compose:{...s,...y(s.buffer,s.cursor,i)}}}}handleLeaderKey(e){const t=this.state;if(this.spaceArmed=!1,t===void 0)return;const s=I(e);if(s==="c"){const i=ye(t);this.composeAnchorLost=!1,this.state=w(i,this.document(this.cols,i.sourceRevision),this.bodyHeight(i))}else if(s==="l")this.state=Pe(t);else if(s==="u"){const i=_e(t);if(i.id!==null){const n=i.id;this.mutate(()=>this.comments.remove(n))}}else if(s==="e")this.editInEditor();else if(s==="s"){const i=this.source.current();i.status==="stale"?this.problem=`approval refused while the source is ${i.staleReason??"unreadable"}`:this.services.submit()}else s==="a"?this.openCompanion():s==="x"&&this.armCancel();this.repaint()}armCancel(){this.cancelArmed=!0,this.services.notice("cancel this review? press x again to confirm")}handleListKey(e){const t=this.state;if(t===void 0)return!1;if(e.type==="cancel")return this.state=xe(t),!0;if(e.type==="moveUnit"&&e.text==="j"||e.type==="arrow"&&e.direction==="down")return this.state=G(t,1),!0;if(e.type==="moveUnit"&&e.text==="k"||e.type==="arrow"&&e.direction==="up")return this.state=G(t,-1),!0;if(e.type==="listEdit"||e.type==="confirm"){if(t.comments.length>0){const s=Ie(t,t.listIndex);this.composeAnchorLost=!1,this.state=w(s,this.document(this.cols,s.sourceRevision),this.bodyHeight(s))}return!0}if(e.type==="scrollPage"&&e.text==="d"&&t.comments.length>0){const s=Oe(t);if(s.id!==null){const i=s.id;this.mutate(()=>this.comments.remove(i))}return!0}return e.type==="leader"?(this.spaceArmed=!0,!0):!1}handleViewKey(e){const t=this.state;if(t===void 0)return!1;const s=this.document(this.cols,t.sourceRevision),i=this.bodyHeight(t);if(e.type==="leader")return this.spaceArmed=!0,!0;if(e.type==="moveUnit"||e.type==="arrow"&&(e.direction==="up"||e.direction==="down")){const n=e.type==="moveUnit"?e.delta:e.direction==="down"?1:-1;this.state=w(z(t,n,!1),s,i)}else if(e.type==="extendUnit")this.state=w(z(t,e.delta,!0),s,i);else if(e.type==="scrollPage")this.startScrollAnimation(e.delta*$e(i));else if((e.type==="pan"||e.type==="arrow"&&(e.direction==="left"||e.direction==="right"))&&ue(t,s,this.cols,this.rows,this.gutterWidth())>0){const n=e.type==="pan"?e.delta:e.direction==="right"?1:-1;this.state=fe(t,n*de,s,this.cols,this.rows,this.gutterWidth())}else if(e.type==="help")this.state=Me(t);else return e.type==="cancel"&&e.text===void 0||e.type==="cancel",!1;return this.repaint(),!0}editInEditor(){const e=this.state;if(e===void 0)return;const t=e.units[e.activeUnit]?.start??1,s=this.services.suspendForChild();try{const i=ve(this.absFile,t);i.error!==void 0&&(this.problem=`editor failed \u2014 ${i.error}`)}finally{s()}this.source.refreshNow()}markCursorLocation(e,t){if(t.mode==="compose")return D(e,oe);if(t.mode==="list"){const i=D(e,`${A}\u25B8${l}`);if(i!==e)return i}const s=this.activeAnchorFrameRow(t);return s===void 0?e:D(e,`${A}\u258C${l}`,s)}activeAnchorFrameRow(e){const t=Math.max(1,this.rows-B(e,this.cols)),s=e.rowUnits.length,i=Math.max(0,Math.min(e.scroll,Math.max(0,s-t)));for(let n=0;n<t;n++){const r=i+n;if(!(n===0&&i>0||n===t-1&&r<s-1)&&e.rowUnits[r]===e.activeUnit)return 3+n}}reflow(){const e=this.state;if(e===void 0||this.cols<=0)return;const t=e.units[e.activeUnit],s=e.selectionAnchor===null?void 0:e.units[e.selectionAnchor],i=this.document(this.cols,e.sourceRevision),{units:n,rowUnits:r}=$(i),c={...e,units:n,rowUnits:r,activeUnit:t===void 0?0:F(n,t),selectionAnchor:s===void 0?null:F(n,s)};this.state=w(c,i,this.bodyHeight(c))}document(e,t){const s=this.cache;if(s!==void 0&&s.width===e&&s.sourceRevision===t)return s.doc;const i=this.source.current().text,n=this.kind.doc==="code"?ie(i,this.kind.language??"text",ge(e)):me(i,e);return this.cache={width:e,sourceRevision:t,doc:n},n}bodyHeight(e){const t=e.mode==="list"&&e.comments.some(s=>s.anchorStatus==="detached")?1+(e.comments.some(s=>s.anchorStatus!=="detached")?1:0):0;return Math.max(1,this.rows-B(e,this.cols)-t)}composeWidth(){return Math.max(10,Math.min(Math.max(20,this.cols-4),120)-2)}gutterWidth(){return this.kind.doc==="code"?String(this.source.current().lines.length).length:0}footerLine(){if(this.problem!==void 0)return this.problem;const e=this.source.current();if(e.status==="stale")return`source ${e.staleReason??"unreadable"} \u2014 showing the last good read; approval is refused`}renderPlaceholder(){const t=["",` ${this.status==="refused"?`Review unavailable \u2014 ${this.problem??"the document could not be prepared"}`:`Preparing ${this.fileLabel}\u2026`}`];for(;t.length<this.rows;)t.push("");return t.slice(0,this.rows).map(s=>v(s,this.cols))}renderGroupedList(e){const t=this.state,s=Math.min(Math.max(20,this.cols-4),120),i=t.comments.filter(h=>h.anchorStatus==="detached"),n=t.comments.filter(h=>h.anchorStatus!=="detached"),r=[` ${O}Detached${l}`];for(const h of i)r.push(this.commentListRow(h,t.comments.indexOf(h),s,!0));if(n.length>0){r.push(` ${g}Comments${l}`);for(const h of n)r.push(this.commentListRow(h,t.comments.indexOf(h),s,!1))}r.push(""),r.push(` ${g}j/k${l} move ${g}enter/e${l} edit ${g}d${l} delete ${g}q/esc${l} close`);const c=Math.max(0,this.rows-3),f=r.slice(0,c),u=Math.max(3,this.rows-f.length),m=[...e],p=W(f,this.cols,Math.min(this.cols,s+2));for(let h=0;h<p.length;h++)m[u+h]=v(p[h]??"",this.cols);return m.slice(0,this.rows)}statedComposeAnchor(e){const t=this.state;if(t?.compose==null)return e;const s=Math.min(Math.max(20,this.cols-4),120),i=we(t.compose.buffer,t.compose.cursor,Math.max(10,s-2)).length,n=this.rows-(i+3);if(n<0||n>=e.length)return e;const r=[...e];return r[n]=v(W([` ${O}Comment anchor removed by an edit \u2014 esc, then re-anchor:${l}`],this.cols,Math.min(this.cols,s+2))[0]??"",this.cols),r}commentListRow(e,t,s,i){const n=i?e.originalRange??{line:e.line,endLine:e.endLine}:{line:e.line,endLine:e.endLine},r=n.line===n.endLine?`L${n.line}`:`L${n.line}-${n.endLine}`,c=t===this.state.listIndex?`${A}\u25B8${l}`:" ",u=`${i?`\u201C${N(e.lineText).replace(/\s+/g," ").trim()}\u201D \xB7 `:""}${N(e.comment).replace(/\s+/g," ").trim()}`;return` ${c} ${g}[${r}]${l} ${re(u,Math.max(10,s-14))}`}topVisibleSourceLine(e){const t=e.rowUnits[e.scroll];if(t!=null)return e.units[t]?.start??1;for(let s=e.scroll;s<e.rowUnits.length;s++){const i=e.rowUnits[s];if(i!=null)return e.units[i]?.start??1}return 1}repaint(){this.invalidate()}}function qe(o){const e=Math.abs(o);if(e===0)return[];const t=Math.sign(o),s=Math.min(Ke,e),i=[];let n=0;for(let r=1;r<=s;r++){const c=Math.round(e*r/s);i.push((c-n)*t),n=c}return i}d(qe,"splitScrollSteps");function ze(o,e,t){const s=o.slice(0,e).map(i=>Ge(i,t));for(;s.length<e;)s.push(" ".repeat(t));return s}d(ze,"exactRows");function Ge(o,e){const t=o.split(j).join(""),s=L(t);if(s>e){const i=v(t,e);return i+" ".repeat(Math.max(0,e-L(i)))}return o+" ".repeat(e-s)}d(Ge,"padTo");function Ye(o,e){const t=k(o,e);return t===""||t.startsWith("..")?e:t}d(Ye,"labelFor");function M(o){return o.filter(e=>e.status!=="deleted")}d(M,"nonDeleted");function P(o){const e=o.filter(s=>s.anchorStatus==="detached"),t=o.filter(s=>s.anchorStatus!=="detached");return[...e,...t]}d(P,"orderedForDisplay");function Y(o,e){const t=o.units[e];return t===void 0?{line:1,endLine:1}:{line:t.start,endLine:t.end}}d(Y,"unitRange");function R(o,e){const t=be(o,{line:e.line-1,endLine:e.endLine});return t.kind==="detached"?{range:e,detached:!0}:{range:{line:t.line+1,endLine:Math.max(t.line+1,t.endLine)},detached:!1}}d(R,"mapRange");function Xe(o,e,t){const s=e-1,i=o.get(s);if(i!==void 0)return i+1;for(let n=1;n<t;n++){const r=o.get(s+n);if(r!==void 0)return r+1;const c=o.get(s-n);if(c!==void 0)return c+1}return 1}d(Xe,"mapLine");function Je(o,e){if(e.detached)return{...o,anchorStatus:"detached",originalRange:o.originalRange??{line:o.line,endLine:o.endLine}};if(o.anchorStatus==="detached")return o;const t=e.range.line!==o.line||e.range.endLine!==o.endLine;return{...o,line:e.range.line,endLine:e.range.endLine,anchorStatus:t?"shifted":o.anchorStatus}}d(Je,"applyMappedRange");function Qe(o,e){return o.map(t=>{if(t.anchorStatus==="detached")return{id:t.id,detached:!0};const s=R(e,{line:t.line,endLine:t.endLine});return s.detached?{id:t.id,detached:!0}:{id:t.id,anchor:s.range}})}d(Qe,"batchEntries");function D(o,e,t){const s=t??o.findIndex(c=>c.includes(e)),i=o[s];if(i===void 0)return o;const n=i.indexOf(e);if(n===-1)return o;const r=[...o];return r[s]=`${i.slice(0,n)}${j}${i.slice(n)}`,r}d(D,"markAtToken");function b(o){return o instanceof Error?o.message:String(o)}d(b,"describe");export{ft as ReviewDocumentSurface};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var q=Object.defineProperty;var $=(e,s)=>q(e,"name",{value:s,configurable:!0});import G from"string-width";import{renderMarkdownBlockAware as J}from"../../../core/termrender/termrender.js";import{fenceLanguageFor as K,isMarkdownFile as V}from"../../../core/termrender/code-doc.js";import{renderInputBuffer as
|
|
1
|
+
var q=Object.defineProperty;var $=(e,s)=>q(e,"name",{value:s,configurable:!0});import G from"string-width";import{renderMarkdownBlockAware as J}from"../../../core/termrender/termrender.js";import{fenceLanguageFor as K,isMarkdownFile as V}from"../../../core/termrender/code-doc.js";import{renderInputBuffer as B}from"../tui/render.js";import{BOLD as Q,CYAN as L,DIM as o,ESC as X,RESET as n,YELLOW as W,centerHorizontal as S,clipLine as Z,hline as ee,panLine as ne,sanitize as oe,truncate as te,visibleWidth as k}from"../tui/ansi.js";import{commentedUnitSet as re,selectedUnitBounds as se}from"./state.js";function E(e){return Math.min(Math.max(20,e-6),120)}$(E,"renderWidthForCols");function F(e){return Math.max(20,e-4)}$(F,"contentWidthForCols");function I(e,s=0){return Math.max(1,F(e)-(s>0?s+1:0))}$(I,"bodyWidth");function fe(e,s){return J(e,E(s),F(s))}$(fe,"renderReviewDoc");function C(e,s,c){const t=[];for(let l=0;l<c;l++){const h=s+l;t.push(h<e.lines.length?{abs:h,line:e.lines[h]}:{abs:null,line:""})}s>0&&t.length>0&&(t[0]={abs:null,line:` ${o}\u2191 ${s} more above${n}`});const a=e.lines.length-(s+c);return a>0&&t.length>0&&(t[t.length-1]={abs:null,line:` ${o}\u2193 ${a} more below${n}`}),t}$(C,"paintedBodyRows");function T(e,s,c){let t=0;for(const a of C(e,s,c))t=Math.max(t,k(a.line));return t}$(T,"widestVisibleRow");function ie(e,s,c,t,a=0){const l=Math.max(1,t-ae(e,c)),h=Math.max(0,s.lines.length-l),d=Math.max(0,Math.min(e.scroll,h));return Math.max(0,T(s,d,l)-I(c,a))}$(ie,"maxHScroll");function xe(e,s,c,t,a,l=0){const h=ie(e,c,t,a,l),d=Math.max(0,Math.min(e.hscroll+s,h));return d===e.hscroll?e:{...e,hscroll:d}}$(xe,"panBy");const ge=8,A=`${X}48;5;236m`;function ce(e,s){const c=Math.max(0,s-G(oe(e)));return A+e.replaceAll(n,n+A)+" ".repeat(c)+n}$(ce,"tintRow");function ae(e,s){if(e.mode==="view")return 4;if(e.mode==="compose"){const t=e.compose,a=Math.min(Math.max(20,s-4),120);return 6+(t?B(t.buffer,t.cursor,Math.max(10,a-2)).length:1)}return e.mode==="list"?3+Math.max(1,e.comments.length)+2:12}$(ae,"reservedRows");function Me(e,s,c,t,a,l){const h=Math.min(Math.max(20,t-4),120),d=E(t),R=["",` ${Q}${L}Review \u2014 ${s}${n} ${o}${e.comments.length} comment${e.comments.length===1?"":"s"}${n}`,` ${o}${ee(h)}${n}`],i=[];if(e.mode==="compose"&&e.compose){const r=e.compose,m=r.anchor.line===r.anchor.endLine?`L${r.anchor.line}`:`L${r.anchor.line}-${r.anchor.endLine}`;i.push(` ${W}${r.editingId!==null?"Edit comment":"Comment"} on ${m}:${n}`);for(const u of B(r.buffer,r.cursor,Math.max(10,h-2)))i.push(` ${u}`);i.push(""),i.push(` ${o}enter${n} save ${o}^\u23CE/^F${n} save + fork ${o}^J/\u2325\u23CE${n} newline ${o}esc${n} cancel`)}else e.mode==="list"?(e.comments.length===0?i.push(` ${o}(no comments yet \u2014 space c to add one)${n}`):e.comments.forEach((r,m)=>{const u=r.line===r.endLine?`L${r.line}`:`L${r.line}-${r.endLine}`,w=m===e.listIndex?`${L}\u25B8${n}`:" ",x=te(r.comment.replace(/\n/g," / "),Math.max(10,h-14));i.push(` ${w} ${o}[${u}]${n} ${x}`)}),i.push(""),i.push(` ${o}j/k${n} move ${o}enter/e${n} edit ${o}d${n} delete ${o}q/esc${n} close`)):e.mode==="help"?(i.push(" Keys:"),i.push(` ${o}j/k${n} move anchor ${o}shift-j/k${n} extend selection`),i.push(` ${o}u/d${n} move half a page ${o}h/l${n} pan visible wide diagrams`),i.push(` ${o}space c${n} compose comment ${o}space l${n} list comments`),i.push(` ${o}space u${n} undo last comment ${o}space e${n} edit in $EDITOR`),i.push(` ${o}space s${n} submit review`),i.push(` ${o}space a${n} open the companion pane`),i.push(` ${o}^\u23CE/^F${n} while composing: save the comment and hand it to its own fork`),i.push(` ${o}space x${n} cancel review`),i.push(` ${o}?${n} toggle this help ${o}esc${n} close/cancel`)):i.push(` ${o}space c${n} \xB7 ${o}space l${n} \xB7 ${o}space u${n} \xB7 ${o}space e${n} \xB7 ${o}space a${n} companion \xB7 ${o}space s${n} submit \xB7 ${o}space x${n} cancel \xB7 ${o}?${n} \xB7 ${o}esc${n}`);l?.notice!==void 0&&i.length>0&&(i[i.length-1]=` ${W}${l.notice}${n}`);const D=R.length+i.length,g=Math.max(1,a-D),N=Math.max(0,c.lines.length-g),y=Math.max(0,Math.min(e.scroll,N)),p=l?.lineNumbers===!0?String(c.lines.length).length:0,b=I(t,p),j=T(c,y,g),H=Math.max(0,j-b),O=Math.max(0,Math.min(e.hscroll,H)),{lo:U,hi:z}=se(e),P=re(e),f=[];for(const r of C(c,y,g)){if(r.abs===null){f.push(` ${" ".repeat(p>0?p+1:0)}${r.line}`);continue}const m=ne(r.line,O,b),u=e.rowUnits[r.abs]??null,w=c.spans[r.abs]?.[0],x=p>0?w===void 0?" ".repeat(p+1):`${o}${String(w).padStart(p)}${n} `:"",_=Math.min(b,Math.max(d,k(m)));u!==null&&u>=U&&u<=z?f.push(` ${L}\u258C${n} ${x}${ce(m,_)}`):u!==null&&P.has(u)?f.push(` ${W}\u258E${n} ${x}${m}`):f.push(` ${x}${m}`)}for(;f.length<g;)f.push("");const M=Math.min(t,Math.max(h+2,d+4+(p>0?p+1:0))),Y=f.map(r=>{const m=k(r)>M?t:M;return S([r],t,m)[0]??""}),v=[...S(R,t,M),...Y,...S(i,t,M)];for(;v.length<a;)v.push("");return v.slice(0,a).map(r=>Z(r,t))}$(Me,"renderReviewFrame");function we(e){return V(e)?{doc:"markdown"}:{doc:"code",language:K(e)}}$(we,"docSurface");export{ge as PAN_STEP,I as bodyWidth,F as contentWidthForCols,we as docSurface,ie as maxHScroll,xe as panBy,fe as renderReviewDoc,Me as renderReviewFrame,E as renderWidthForCols,ae as reservedRows,T as widestVisibleRow};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var o=Object.defineProperty;var c=(n,e)=>o(n,"name",{value:e,configurable:!0});import{Key as r,decodeKittyPrintable as a,matchesKey as t,parseKey as u}from"@earendil-works/pi-tui";import{normalizeShiftedMeta as s}from"../../../core/keybindings/index.js";function x(n){switch(n.type){case"moveUnit":case"extendUnit":case"leader":case"help":case"listEdit":case"printable":case"paste":return n.text;case"scrollPage":case"pan":case"cancel":return n.text;default:return}}c(x,"reviewKeyText");function g(n){const e=s(n);if(t(e,r.ctrl("j"))||t(e,r.alt("enter")))return{type:"composeNewline"};if(t(e,r.alt("backspace"))||t(e,r.ctrl("w")))return{type:"composeWordBackspace"};if(t(e,r.backspace))return{type:"composeBackspace"};if(t(e,r.delete))return{type:"composeDelete"};if(t(e,r.alt("left"))||t(e,r.ctrl("left"))||t(e,r.super("left"))||t(e,r.alt("b")))return{type:"composeMove",direction:"wordLeft"};if(t(e,r.alt("right"))||t(e,r.ctrl("right"))||t(e,r.super("right"))||t(e,r.alt("f")))return{type:"composeMove",direction:"wordRight"};if(t(e,r.home)||t(e,r.ctrl("a")))return{type:"composeHome"};if(t(e,r.end)||t(e,r.ctrl("e")))return{type:"composeEnd"};if(t(e,r.shift("tab")))return{type:"tab",reverse:!0};if(t(e,r.tab))return{type:"tab",reverse:!1};if(t(e,r.ctrl("enter"))||t(e,r.ctrl("f")))return{type:"confirmFork"};if(t(e,r.enter))return{type:"confirm"};if(t(e,r.escape))return{type:"cancel"};if(t(e,r.up))return{type:"arrow",direction:"up"};if(t(e,r.down))return{type:"arrow",direction:"down"};if(t(e,r.left))return{type:"arrow",direction:"left"};if(t(e,r.right))return{type:"arrow",direction:"right"};if(t(e,r.pageUp))return{type:"scrollPage",delta:-1};if(t(e,r.pageDown))return{type:"scrollPage",delta:1};if(t(e,r.shift("j")))return{type:"extendUnit",delta:1,text:"J"};if(t(e,r.shift("k")))return{type:"extendUnit",delta:-1,text:"K"};if(t(e,"j"))return{type:"moveUnit",delta:1,text:"j"};if(t(e,"k"))return{type:"moveUnit",delta:-1,text:"k"};if(t(e,"u"))return{type:"scrollPage",delta:-1,text:"u"};if(t(e,"d"))return{type:"scrollPage",delta:1,text:"d"};if(t(e,"h"))return{type:"pan",delta:-1,text:"h"};if(t(e,"l"))return{type:"pan",delta:1,text:"l"};if(t(e,r.space))return{type:"leader",text:" "};if(t(e,r.question))return{type:"help",text:"?"};if(t(e,"e"))return{type:"listEdit",text:"e"};if(t(e,"q"))return{type:"cancel",text:"q"};const p=u(e),i=d(n,p);return i==="?"?{type:"help",text:i}:i!==void 0?{type:"printable",text:i}:p!==void 0?null:[...n].length>1?{type:"paste",text:n}:null}c(g,"matchReviewKey");function d(n,e){const p=a(n);if(p!==void 0&&f(p))return p;if(e!==void 0){const i=e.startsWith("shift+"),l=i?e.slice(6):e;if([...l].length===1&&f(l))return i&&/^[a-z]$/.test(l)?l.toUpperCase():l}return[...n].length===1&&f(n)?n:void 0}c(d,"printableText");function f(n){return!new RegExp("\\p{C}","u").test(n)}c(f,"isPrintable");export{g as matchReviewKey,x as reviewKeyText};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
2
|
-
`).filter(
|
|
1
|
+
var m=Object.defineProperty;var a=(n,t)=>m(n,"name",{value:t,configurable:!0});import{closeSync as p,fstatSync as w,openSync as b,readSync as g}from"node:fs";import{join as _}from"node:path";import{ApiError as u}from"../api/index.js";import{localClient as d}from"../api/node-transport.js";import{apiErrorToCliError as y}from"../core/io.js";import{autostartDisabled as C,ensureDaemon as S}from"../daemon/manage.js";import{DAEMON_STARTUP_WINDOW_MS as N}from"../daemon/startup-policy.js";import{readState as v}from"../core/config.js";import{resolveRemoteCanvasTarget as T}from"../core/remote-canvas-target.js";import{RemoteCanvasSource as A}from"../core/canvas/remote-canvas-source.js";import{crtrHome as I}from"../core/canvas/paths.js";function l(n){return{...n,fault:n.fault??null}}a(l,"withRowFault");function M(n,t){throw n instanceof u?y(n,t):n}a(M,"rethrowAsCliError");async function V(n){try{return await h().getNode(n)}catch(t){if(t instanceof u&&t.status===404)return null;M(t)}}a(V,"getNodeOrNull");const O=4096,D=20;function R(){let n;try{n=b(_(I(),"crtrd.log"),"r");const{size:t}=w(n),e=Math.max(0,t-O),r=t-e;if(r<=0)return;const s=Buffer.alloc(r);g(n,s,0,r,e);const o=s.toString("utf8").split(`
|
|
2
|
+
`).filter(c=>c.trim()!=="").slice(-D).join(`
|
|
3
3
|
`).trim();return o===""?void 0:`crtrd.log (tail):
|
|
4
|
-
${o}`}catch{return}finally{if(
|
|
4
|
+
${o}`}catch{return}finally{if(n!==void 0)try{p(n)}catch{}}}a(R,"readColdStartDiagnostic");function h(n){return C()?d({autostart:!1,...n?.timeoutMs!==void 0?{timeoutMs:n.timeoutMs}:{}}):d({...n?.timeoutMs!==void 0?{timeoutMs:n.timeoutMs}:{},onColdSocket:a(async()=>{S()},"onColdSocket"),coldStartDiagnostic:R,coldStartPollWindowMs:N})}a(h,"cliClient");const x=2e3;class E{static{a(this,"ApiCanvasSource")}client;rosterCache=null;rosterInFlight=null;constructor(t=h()){this.client=t}async roster(){const t=Date.now();return this.rosterCache!==null&&t-this.rosterCache.at<x?this.rosterCache.index:this.rosterInFlight!==null?this.rosterInFlight:(this.rosterInFlight=this.client.canvasRoster().then(e=>{const r={subscriptions:new Map,subscribers:new Map};for(const s of e.edges){const i={node_id:s.to_id,active:s.active,created:s.created},o={node_id:s.from_id,active:s.active,created:s.created},c=r.subscriptions.get(s.from_id);c===void 0?r.subscriptions.set(s.from_id,[i]):c.push(i);const f=r.subscribers.get(s.to_id);f===void 0?r.subscribers.set(s.to_id,[o]):f.push(o)}return this.rosterCache={at:Date.now(),index:r},r}).finally(()=>{this.rosterInFlight=null}),this.rosterInFlight)}async orNull(t){try{return await t()}catch(e){if(e instanceof u&&e.status===404)return null;throw e}}async getNode(t){const e=await this.orNull(()=>this.client.getNode(t));return e===null?null:l(e)}async getRow(t){const e=await this.orNull(()=>this.client.getNode(t));return e===null?null:l(e)}async listNodes(t){const e=t?.status;let r;if(Array.isArray(e)){const s=new Set(e);r=(await this.client.listNodes()).filter(o=>s.has(o.status))}else e!==void 0?r=await this.client.listNodes({status:e}):r=await this.client.listNodes();return r.map(s=>l(s))}async subscriptionsOf(t){return this.roster().then(e=>e.subscriptions.get(t)??[])}async subscribersOf(t){return this.roster().then(e=>e.subscribers.get(t)??[])}async view(t){const e=new Set([t]),r=[],s=(await this.subscriptionsOf(t)).map(i=>i.node_id);for(;s.length>0;){const i=s.shift();if(!e.has(i)){e.add(i),r.push(i);for(const o of await this.subscriptionsOf(i))e.has(o.node_id)||s.push(o.node_id)}}return r}async ticketCountsForNodes(t){return(await this.client.attentionCounts([...t])).counts}async ticketCountsForView(t){return this.ticketCountsForNodes([t,...await this.view(t)])}async hasActiveLiveSubscription(t){const e=(await this.subscriptionsOf(t)).filter(r=>r.active);for(const r of e){const s=await this.getRow(r.node_id);if(s&&(s.status==="active"||s.status==="idle"))return!0}return!1}}function Y(n){const t=n?.scope??"user",e=n?.canvasName??v(t).activeCanvas??null;if(!e)return new E;const{previewEndpoint:r,relayToken:s}=T(e,t);return new A(r,s)}a(Y,"cliCanvasSource");export{E as ApiCanvasSource,O as COLD_START_DIAGNOSTIC_TAIL_BYTES,Y as cliCanvasSource,h as cliClient,V as getNodeOrNull,R as readColdStartDiagnostic,M as rethrowAsCliError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var A=Object.defineProperty;var i=(n,e)=>A(n,"name",{value:e,configurable:!0});import{envNodeId as S}from"../../shared/env.js";import{defineLeaf as v}from"../../core/command.js";import{InputError as o}from"../../core/io.js";import{cliClient as l,rethrowAsCliError as N}from"../api-client.js";import{join as _}from"node:path";import{mkdtempSync as j,rmSync as F,writeFileSync as W}from"node:fs";import{tmpdir as C}from"node:os";import{resolvePageComponents as E}from"../../core/config.js";import{markStopSignal as D}from"../../core/runtime/stop-signals.js";import{preparePage as q}from"../../core/human/page-authoring.js";import{PageAuthoringError as K}from"../../core/human/page.js";import{isResponseBearingSlot as U,validateAuthoredPageManifest as $}from"../../core/human/page-schema.js";import{display as M}from"../../core/termrender/display.js";import{pageHelpText as P,resolveMaxPanes as Q}from"./shared.js";const B=P().pageHint;function k(){return S()??null}i(k,"askingNode");async function G(){const n=k();if(n===null||n==="")return{source:{},lifecycle:null};try{const e=l(),t=await e.getNode(n),a=t.name.trim(),r=t.profile_id===null?void 0:(await e.getProfile(t.profile_id)).name;return{source:{nodeId:n,...a===""?{}:{sessionName:a},...r===void 0?{}:{profileName:r}},lifecycle:t.lifecycle}}catch{return{source:{nodeId:n},lifecycle:null}}}i(G,"sender");function R(){return E()}i(R,"pageProductKinds");function L(n,e,t){const a=t==="terminal"||n.inbox===!0?!0:n.noInbox===!0?!1:e;return{placement:n.placement??"inline",inbox:a,reply:e}}i(L,"deliveryFrom");function g(n){try{return n()}catch(e){throw e instanceof K?new o({error:"invalid_page",message:e.message,field:"page",next:"Fix the authored page and submit again; the reported location is in the authored `.tsx` source."}):e}}i(g,"submitAuthoredPage");async function z(n){try{const e=await l().getHumanRequest(n);if(e.kind!=="page")throw new o({error:"unknown_page",message:`no page ${n} on this canvas`,field:"replace",next:"Pass the page_id a previous send returned, or omit --replace to raise a new page."});return(await l().getHumanInboxPage(e.inbox_ticket_id)).page}catch(e){if(e instanceof o)throw e;N(e,"Pass the page_id a previous send returned, or omit --replace to raise a new page.")}}i(z,"replacedPage");function J(n){const e=j(_(C(),"crtr-human-page-")),t=_(e,"page.tsx");return W(t,n,{encoding:"utf8",mode:384}),{dir:e,path:t}}i(J,"writePipedPage");function O(n){const e=n.page,t=n.document,a=typeof t=="string"&&t.trim()!==""?t:void 0;if(e!==void 0&&a!==void 0)throw new o({error:"bad_invocation",message:"received both --page and a piped page document",field:"page, document",next:"Pass exactly one page source: --page PATH or the document on stdin."});if(e===void 0&&a===void 0)throw new o({error:"missing_parameter",message:"a page source is required",field:"page, document",next:"Pass exactly one page source: --page PATH or the document on stdin."});if(e!==void 0){if(typeof e!="string")throw new o({error:"invalid_type",message:"--page must be a path",field:"page",next:"Retry with --page PATH."});return{sourceFile:e}}if(typeof a!="string")throw new o({error:"invalid_type",message:"stdin must contain a page document",field:"document",next:"Pipe the page document on stdin."});const r=J(a);return{sourceFile:r.path,tempDir:r.dir}}i(O,"pageInput");function V(){return"Queued for the user. Their answer is pushed to your inbox and wakes you when they respond \u2014 never poll, verify it opened, or block."}i(V,"queuedFollowUp");function pe(n=!1){const e=P(n);return v({name:"send",description:"send a durable page with explicit delivery",whenToUse:e.sendWhenToUse,help:{name:"human send",summary:e.sendSummary,guide:"Page authoring: `crtr human -h`.",params:[{kind:"stdin",name:"document",required:!1,allowPositional:!1,constraint:e.stdinConstraint},{kind:"flag",name:"page",type:"path",required:!1,constraint:`${B} ${e.pageFlagConstraint}`},{kind:"flag",name:"placement",type:"enum",choices:["inline","panel"],required:!1,default:"inline",constraint:"Where the page is delivered. inline renders in this conversation's transcript; panel opens from the inbox beside it."},{kind:"flag",name:"inbox",type:"bool",required:!1,constraint:"Project this page into the inbox. Mutually exclusive with --no-inbox."},{kind:"flag",name:"no-inbox",type:"bool",required:!1,constraint:"Do not project this page into the inbox, leaving this conversation's transcript as its only route to the user. Mutually exclusive with --inbox, and refused from a terminal node, whose transcript nobody reads once it finishes."},{kind:"flag",name:"replace",type:"string",required:!1,constraint:"Page id returned by an earlier send. Rewrites source in place while retaining its delivery and transcript anchor. Refused once the page is settled or canceled. Not combinable with delivery flags."}],output:[{name:"page_id",type:"string",required:!0,constraint:"Identifier for the page; pass it as --replace to revise source in place."},{name:"dir",type:"string",required:!0,constraint:e.dirConstraint},{name:"placement",type:"inline | panel",required:!0,constraint:"Resolved page placement."},{name:"inbox",type:"boolean",required:!0,constraint:"Whether the page is projected into the inbox."},{name:"reply",type:"boolean",required:!0,constraint:"Whether completion is delivered to the asking request identity. Not a flag: true exactly when the page has response-bearing components."},{name:"follow_up",type:"string",required:!0,constraint:"What happens next; never instructs the caller to poll."}],outputKind:"object",effects:["Writes one durable page request. Inbox projection follows the delivery flags, except from a terminal node, which always projects; completion delivery follows the page's own components.","A display-only page's inbox entry is a notice, not a task: it clears itself once the person has actually seen the page, wherever they see it.","A reply-bearing page records the asking node on the request and delivers completion directly to that node.","--replace rewrites source in place while retaining delivery and emitted time; open surfaces reload it without creating another page."]},run:i(async(t,a)=>{const{sourceFile:r,tempDir:f}=O(t),c=t.replace,T=["placement","inbox","noInbox"];if(t.inbox===!0&&t.noInbox===!0)throw new o({error:"bad_invocation",message:"received conflicting delivery flags",field:"inbox",next:"Pass at most one of --inbox and --no-inbox."});if(c!==void 0&&T.some(h=>a?.providedParams.has(h)===!0))throw new o({error:"bad_invocation",message:"--replace cannot change delivery",field:"replace",next:"Remove delivery flags: replacement retains the page placement, inbox, reply, and emitted time."});try{if(c!==void 0){const u=await z(c);if((await l().getHumanRequest(c)).state!=="pending")throw new o({error:"page_settled",message:`page ${c} already has a terminal outcome`,field:"replace",next:"The page was answered or canceled; nothing on it can change now. Raise a new page if more is needed."});const m=g(()=>q({sourceFile:r,source:u.source,productKinds:R(),delivery:u.delivery})),I=await l().replaceHumanRequest(c,{page:{dialect:m.manifest.dialect,source:m.document}});return{page_id:c,dir:I.request_dir,...u.delivery,follow_up:"Replaced in place. Every open surface reloads it; delivery and emitted time are unchanged."}}const{source:h,lifecycle:w}=await G();if(w==="terminal"&&t.noInbox===!0)throw new o({error:"invalid_page_delivery",message:"a terminal node cannot send a page --no-inbox",field:"inbox",next:"Drop --no-inbox. This node finishes, and nobody reads a finished node's transcript, so a page kept out of the inbox reaches no one."});const y=R(),d=g(()=>q({sourceFile:r,source:h,productKinds:y})),H=d.manifest.slots.some(U),s=L(t,H,w);if(d.manifest=g(()=>$({...d.manifest,delivery:s},y)),!s.reply){const u=await l().createHumanRequest({page:{dialect:d.manifest.dialect,source:d.document},delivery:s,source:d.manifest.source,requester_node_id:null,creator_cwd:process.cwd()}),x=u.request_dir,m=s.placement==="inline"&&s.inbox?"Shown in this conversation and queued in the user's inbox.":s.placement==="inline"?"Shown in this conversation as passive page content with no inbox entry.":s.inbox?"Queued in the inbox; it opens in the panel and waits until dismissed.":"Published as passive page content with no inbox or response lifecycle.";return{page_id:u.request_id,dir:x,...s,follow_up:m}}const p=k();if(p===null||p==="")throw new o({error:"no_requester",message:"a response-bearing page must be sent from a crouter node",field:"page",next:"Run `crtr human send` from the node that should receive the answer."});const b=await l().createHumanRequest({page:{dialect:d.manifest.dialect,source:d.document},delivery:s,source:d.manifest.source,requester_node_id:p,creator_cwd:process.cwd()});return D(p,"requestedHumanReply"),{page_id:b.request_id,dir:b.request_dir,...s,follow_up:V()}}finally{f!==void 0&&F(f,{recursive:!0,force:!0})}},"run")})}i(pe,"humanSend");const me=v({name:"show",description:"put a file live on the user's screen",whenToUse:"displaying a doc on screen while the user comments",help:{name:"human show",summary:"put a file live on screen in a tmux pane with crouter's renderer",guide:"The pane always watches the file and live-updates on every save \u2014 a displayed doc is a live view by definition, so point it at a file something keeps rewriting (a status board, a running summary) and it stays current. The file is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid). To put a rich document in front of the user as a durable inbox page, use `crtr human send --page PATH`; a page with no response-bearing components is a standalone announcement.",params:[{kind:"positional",name:"path",type:"path",required:!0,constraint:"Path to the file to render."},{kind:"flag",name:"window",type:"enum",choices:["auto","split","new"],required:!1,default:"auto",constraint:"Placement. Default auto."}],output:[{name:"pane_id",type:"string | null",required:!0,constraint:"Tmux pane id, or null when not displayed."},{name:"reason",type:"string | null",required:!0,constraint:"Why no pane was created, or null on success."}],outputKind:"object",effects:["Spawns a live-watch tmux pane when possible. No job. Always exits 0."]},run:i(async n=>{const e=n.path,t=n.window,a=t!==void 0?t:"auto";try{const r=M(e,{window:a,maxPanes:Q()});return r.paneId!==void 0?{pane_id:r.paneId,reason:null}:{pane_id:null,reason:r.reason}}catch(r){return{pane_id:null,reason:`display failed: ${r instanceof Error?r.message:String(r)}`}}},"run")});export{pe as humanSend,me as humanShow,G as sender};
|
|
1
|
+
var A=Object.defineProperty;var i=(n,e)=>A(n,"name",{value:e,configurable:!0});import{envNodeId as S}from"../../shared/env.js";import{defineLeaf as v}from"../../core/command.js";import{InputError as o}from"../../core/io.js";import{cliClient as l,rethrowAsCliError as N}from"../api-client.js";import{join as _}from"node:path";import{mkdtempSync as j,rmSync as F,writeFileSync as W}from"node:fs";import{tmpdir as C}from"node:os";import{resolvePageComponents as E}from"../../core/config.js";import{preparePage as q}from"../../core/human/page-authoring.js";import{PageAuthoringError as D}from"../../core/human/page.js";import{isResponseBearingSlot as K,validateAuthoredPageManifest as U}from"../../core/human/page-schema.js";import{display as $}from"../../core/termrender/display.js";import{pageHelpText as P,resolveMaxPanes as M}from"./shared.js";const Q=P().pageHint;function k(){return S()??null}i(k,"askingNode");async function B(){const n=k();if(n===null||n==="")return{source:{},lifecycle:null};try{const e=l(),t=await e.getNode(n),a=t.name.trim(),r=t.profile_id===null?void 0:(await e.getProfile(t.profile_id)).name;return{source:{nodeId:n,...a===""?{}:{sessionName:a},...r===void 0?{}:{profileName:r}},lifecycle:t.lifecycle}}catch{return{source:{nodeId:n},lifecycle:null}}}i(B,"sender");function T(){return E()}i(T,"pageProductKinds");function G(n,e,t){const a=t==="terminal"||n.inbox===!0?!0:n.noInbox===!0?!1:e;return{placement:n.placement??"inline",inbox:a,reply:e}}i(G,"deliveryFrom");function g(n){try{return n()}catch(e){throw e instanceof D?new o({error:"invalid_page",message:e.message,field:"page",next:"Fix the authored page and submit again; the reported location is in the authored `.tsx` source."}):e}}i(g,"submitAuthoredPage");async function L(n){try{const e=await l().getHumanRequest(n);if(e.kind!=="page")throw new o({error:"unknown_page",message:`no page ${n} on this canvas`,field:"replace",next:"Pass the page_id a previous send returned, or omit --replace to raise a new page."});return(await l().getHumanInboxPage(e.inbox_ticket_id)).page}catch(e){if(e instanceof o)throw e;N(e,"Pass the page_id a previous send returned, or omit --replace to raise a new page.")}}i(L,"replacedPage");function z(n){const e=j(_(C(),"crtr-human-page-")),t=_(e,"page.tsx");return W(t,n,{encoding:"utf8",mode:384}),{dir:e,path:t}}i(z,"writePipedPage");function J(n){const e=n.page,t=n.document,a=typeof t=="string"&&t.trim()!==""?t:void 0;if(e!==void 0&&a!==void 0)throw new o({error:"bad_invocation",message:"received both --page and a piped page document",field:"page, document",next:"Pass exactly one page source: --page PATH or the document on stdin."});if(e===void 0&&a===void 0)throw new o({error:"missing_parameter",message:"a page source is required",field:"page, document",next:"Pass exactly one page source: --page PATH or the document on stdin."});if(e!==void 0){if(typeof e!="string")throw new o({error:"invalid_type",message:"--page must be a path",field:"page",next:"Retry with --page PATH."});return{sourceFile:e}}if(typeof a!="string")throw new o({error:"invalid_type",message:"stdin must contain a page document",field:"document",next:"Pipe the page document on stdin."});const r=z(a);return{sourceFile:r.path,tempDir:r.dir}}i(J,"pageInput");function O(){return"Queued for the user. Their answer is pushed to your inbox and wakes you when they respond \u2014 never poll, verify it opened, or block."}i(O,"queuedFollowUp");function le(n=!1){const e=P(n);return v({name:"send",description:"send a durable page with explicit delivery",whenToUse:e.sendWhenToUse,help:{name:"human send",summary:e.sendSummary,guide:"Page authoring: `crtr human -h`.",params:[{kind:"stdin",name:"document",required:!1,allowPositional:!1,constraint:e.stdinConstraint},{kind:"flag",name:"page",type:"path",required:!1,constraint:`${Q} ${e.pageFlagConstraint}`},{kind:"flag",name:"placement",type:"enum",choices:["inline","panel"],required:!1,default:"inline",constraint:"Where the page is delivered. inline renders in this conversation's transcript; panel opens from the inbox beside it."},{kind:"flag",name:"inbox",type:"bool",required:!1,constraint:"Project this page into the inbox. Mutually exclusive with --no-inbox."},{kind:"flag",name:"no-inbox",type:"bool",required:!1,constraint:"Do not project this page into the inbox, leaving this conversation's transcript as its only route to the user. Mutually exclusive with --inbox, and refused from a terminal node, whose transcript nobody reads once it finishes."},{kind:"flag",name:"replace",type:"string",required:!1,constraint:"Page id returned by an earlier send. Rewrites source in place while retaining its delivery and transcript anchor. Refused once the page is settled or canceled. Not combinable with delivery flags."}],output:[{name:"page_id",type:"string",required:!0,constraint:"Identifier for the page; pass it as --replace to revise source in place."},{name:"dir",type:"string",required:!0,constraint:e.dirConstraint},{name:"placement",type:"inline | panel",required:!0,constraint:"Resolved page placement."},{name:"inbox",type:"boolean",required:!0,constraint:"Whether the page is projected into the inbox."},{name:"reply",type:"boolean",required:!0,constraint:"Whether completion is delivered to the asking request identity. Not a flag: true exactly when the page has response-bearing components."},{name:"follow_up",type:"string",required:!0,constraint:"What happens next; never instructs the caller to poll."}],outputKind:"object",effects:["Writes one durable page request. Inbox projection follows the delivery flags, except from a terminal node, which always projects; completion delivery follows the page's own components.","A display-only page's inbox entry is a notice, not a task: it clears itself once the person has actually seen the page, wherever they see it.","A reply-bearing page records the asking node on the request and delivers completion directly to that node.","--replace rewrites source in place while retaining delivery and emitted time; open surfaces reload it without creating another page."]},run:i(async(t,a)=>{const{sourceFile:r,tempDir:m}=J(t),c=t.replace,R=["placement","inbox","noInbox"];if(t.inbox===!0&&t.noInbox===!0)throw new o({error:"bad_invocation",message:"received conflicting delivery flags",field:"inbox",next:"Pass at most one of --inbox and --no-inbox."});if(c!==void 0&&R.some(f=>a?.providedParams.has(f)===!0))throw new o({error:"bad_invocation",message:"--replace cannot change delivery",field:"replace",next:"Remove delivery flags: replacement retains the page placement, inbox, reply, and emitted time."});try{if(c!==void 0){const u=await L(c);if((await l().getHumanRequest(c)).state!=="pending")throw new o({error:"page_settled",message:`page ${c} already has a terminal outcome`,field:"replace",next:"The page was answered or canceled; nothing on it can change now. Raise a new page if more is needed."});const p=g(()=>q({sourceFile:r,source:u.source,productKinds:T(),delivery:u.delivery})),I=await l().replaceHumanRequest(c,{page:{dialect:p.manifest.dialect,source:p.document}});return{page_id:c,dir:I.request_dir,...u.delivery,follow_up:"Replaced in place. Every open surface reloads it; delivery and emitted time are unchanged."}}const{source:f,lifecycle:w}=await B();if(w==="terminal"&&t.noInbox===!0)throw new o({error:"invalid_page_delivery",message:"a terminal node cannot send a page --no-inbox",field:"inbox",next:"Drop --no-inbox. This node finishes, and nobody reads a finished node's transcript, so a page kept out of the inbox reaches no one."});const y=T(),d=g(()=>q({sourceFile:r,source:f,productKinds:y})),H=d.manifest.slots.some(K),s=G(t,H,w);if(d.manifest=g(()=>U({...d.manifest,delivery:s},y)),!s.reply){const u=await l().createHumanRequest({page:{dialect:d.manifest.dialect,source:d.document},delivery:s,source:d.manifest.source,requester_node_id:null,creator_cwd:process.cwd()}),x=u.request_dir,p=s.placement==="inline"&&s.inbox?"Shown in this conversation and queued in the user's inbox.":s.placement==="inline"?"Shown in this conversation as passive page content with no inbox entry.":s.inbox?"Queued in the inbox; it opens in the panel and waits until dismissed.":"Published as passive page content with no inbox or response lifecycle.";return{page_id:u.request_id,dir:x,...s,follow_up:p}}const h=k();if(h===null||h==="")throw new o({error:"no_requester",message:"a response-bearing page must be sent from a crouter node",field:"page",next:"Run `crtr human send` from the node that should receive the answer."});const b=await l().createHumanRequest({page:{dialect:d.manifest.dialect,source:d.document},delivery:s,source:d.manifest.source,requester_node_id:h,creator_cwd:process.cwd()});return{page_id:b.request_id,dir:b.request_dir,...s,follow_up:O()}}finally{m!==void 0&&F(m,{recursive:!0,force:!0})}},"run")})}i(le,"humanSend");const ue=v({name:"show",description:"put a file live on the user's screen",whenToUse:"displaying a doc on screen while the user comments",help:{name:"human show",summary:"put a file live on screen in a tmux pane with crouter's renderer",guide:"The pane always watches the file and live-updates on every save \u2014 a displayed doc is a live view by definition, so point it at a file something keeps rewriting (a status board, a running summary) and it stays current. The file is directive-flavored markdown rendered by termrender (panels, columns, trees, callouts, mermaid). To put a rich document in front of the user as a durable inbox page, use `crtr human send --page PATH`; a page with no response-bearing components is a standalone announcement.",params:[{kind:"positional",name:"path",type:"path",required:!0,constraint:"Path to the file to render."},{kind:"flag",name:"window",type:"enum",choices:["auto","split","new"],required:!1,default:"auto",constraint:"Placement. Default auto."}],output:[{name:"pane_id",type:"string | null",required:!0,constraint:"Tmux pane id, or null when not displayed."},{name:"reason",type:"string | null",required:!0,constraint:"Why no pane was created, or null on success."}],outputKind:"object",effects:["Spawns a live-watch tmux pane when possible. No job. Always exits 0."]},run:i(async n=>{const e=n.path,t=n.window,a=t!==void 0?t:"auto";try{const r=$(e,{window:a,maxPanes:M()});return r.paneId!==void 0?{pane_id:r.paneId,reason:null}:{pane_id:null,reason:r.reason}}catch(r){return{pane_id:null,reason:`display failed: ${r instanceof Error?r.message:String(r)}`}}},"run")});export{le as humanSend,ue as humanShow,B as sender};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var
|
|
1
|
+
var L=Object.defineProperty;var i=(e,r)=>L(e,"name",{value:r,configurable:!0});import{envNodeId as F}from"../../shared/env.js";import{localClient as I}from"../../api/node-transport.js";import{defineLeaf as O}from"../../core/command.js";import{memoryExtensionEffectiveCatalog as K,projectEffectiveMemoryExtensions as Y}from"../../core/memory/extensions.js";import{docLinkNames as U}from"../../core/memory/doc-link-grammar.js";import{ambientMemoryTarget as E,createMemoryDocSnapshot as V,listAllMemoryDocs as W,loadMemoryStoreView as X,loadMemoryTargetView as $,readMemoryDocContent as R,resolveMemoryDocs as z}from"../../core/memory-resolver.js";import{expandShellBlocks as G,hasShellBlocks as H,makeNodeShellRunner as J}from"../../core/runtime/shell-expansion.js";import{emptyContextExposureState as Q,exposureTarget as _,loadContextExposureState as S,registerDocumentExposure as Z,registerExposure as ee,saveContextExposureState as D}from"../../core/substrate/injected-store.js";import{dirDedupKey as te,docsByName as g,isDirName as j,renderDirListing as ne}from"../../core/substrate/listings.js";import{contentReadAttachments as re}from"../../core/substrate/on-read.js";import{renderResult as oe}from"../../core/render.js";import{effectiveDocKind as ae}from"../../core/substrate/schema.js";import{canonicalSegments as ie}from"../../core/memory/identity.js";import{MEMORY_KINDS as se,requireMountedStore as de,resolveReadSelector as ce,selectorParam as M}from"./shared.js";async function le(e){if(e===void 0)return null;try{return await I({autostart:!1,coldStartPollWindowMs:0}).nodeSubject(e)}catch{return null}}i(le,"fetchSubject");function me(){try{return W(void 0,!0,!0)}catch{return[]}}i(me,"corpusDocs");function he(e,r){return{winner:e===r,scope:e.scope,store_root:e.root,physical_relative_path:e.physicalRelativePath,path:e.path}}i(he,"candidateRow");const ue=O({name:"read",description:"load a memory document body by name",whenToUse:"a task in front of you matches a stored document and you already know its canonical name \u2014 read it before improvising. Resolution is exact and takes the full canonical name (a project document is `<namespace>/<local name>`); among documents sharing one name the winner is the nearest scope (node > project stack > profile > user > builtin). A directory name is a valid target too: it answers with that directory\u2019s own document, when it has one, together with its immediate listing, and walking directories is the sanctioned browse move. You name the document by its crtr identifier, never a file path \u2014 do not cat or find the markdown off disk. Reach for `crtr memory find` first when you do not yet know which document applies.",help:{name:"memory read",summary:"resolve an exact canonical name to its document body (frontmatter stripped unless --frontmatter), plus the directory listing when that name has members",params:[{kind:"positional",name:"name",required:!0,constraint:"Full canonical memory name \u2014 a project document is `<namespace>/<local name>`, every other store\u2019s local names are already canonical, where a nested store's namespace includes its path under the repository root. Resolution is exact: a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail. A name that carries members returns its listing alongside its body; a name that is only a directory returns the listing alone."},{kind:"flag",name:"kind",type:"enum",choices:[...se],required:!1,constraint:"Narrows resolution when the name is ambiguous across kinds."},{kind:"flag",name:"frontmatter",type:"bool",required:!1,constraint:"When present, includes the YAML frontmatter in the returned body. Off by default \u2014 only the body is returned."},M("scope-read"),M("dir",{},"Reads that store alone: the name resolves against its documents only, and the listing shows its members only.")],output:[{name:"name",type:"string",required:!0,constraint:"Resolved canonical document name, or the directory name on a directory read."},{name:"local_name",type:"string",required:!1,constraint:"Store-local identity, before the store namespace composed the canonical name. Absent on a directory read."},{name:"kind",type:"string",required:!1,constraint:"Resolved kind: knowledge or preference. Absent on a directory read."},{name:"scope",type:"string",required:!1,constraint:"Scope the document was resolved from: node, project, profile, user, or builtin. Absent on a directory read."},{name:"path",type:"string",required:!1,constraint:"Absolute path to the document on disk. Revise it with `crtr memory edit`, never by editing this file \u2014 an edit records why the change happened and lands in the doc\u2019s revision history. Absent on a directory read."},{name:"store_root",type:"string",required:!1,constraint:"Absolute memory store root the document loaded from. Absent on a directory read."},{name:"physical_relative_path",type:"string",required:!1,constraint:"Store-relative physical markdown path, ordering prefixes and any `INDEX.md` kept. Absent on a directory read."},{name:"representation",type:"string",required:!1,constraint:"Physical representation of this identity: leaf-file or directory-index. Absent on a directory read."},{name:"candidates",type:"object[]",required:!1,constraint:"Present only when several physical documents share this canonical name: one row per candidate in source order, {winner, scope, store_root, physical_relative_path, path}. The winner is what this read returned; reach a non-winning one with `--dir <its project>`."},{name:"content",type:"string",required:!1,constraint:"Document body. Frontmatter stripped unless --frontmatter is set. A document may embed shell as `!`cmd`` or a ```! fenced block; each runs once per read, in the current working directory, and is replaced by its output. Read `path` off disk when you need the literal unexecuted text. May be preceded by one `<auto-loaded-context>` block carrying the doc\u2019s neighborhood listings and any docs routed to this read. Absent on a directory read."},{name:"listing",type:"string[]",required:!1,constraint:"Present when the name has members \u2014 a directory document returns its body AND this listing, a bare directory returns the listing alone: one `[[name]]: <when-and-why>` line per member doc, one bare `[[name]]` per subdirectory. Follow any line with `crtr memory read <name>`."},{name:"extensions",type:"object",required:!1,constraint:"Effective valid plugin-owned metadata, keyed by plugin namespace then field. Includes enabled declaration defaults without writing them to frontmatter; disabled and unresolved namespaces are omitted. Present only on a document read."},{name:"links",type:"string[]",required:!1,constraint:"Canonical names this document links to via `[[name]]` that resolve in the current corpus \u2014 further reading, loaded only on demand with `crtr memory read <name>`. A directory link returns that directory\u2019s listing. Omitted when the body carries no resolvable links."},{name:"follow_up",type:"string",required:!1,constraint:"Present whenever the read returned a listing or resolvable body links."}],outputKind:"object",effects:["Reads the document. A document that embeds shell (`!`cmd`` or a ```! block) runs those commands once, in the current working directory.","Records a content delivery for this node: a document whose profile project caps automatic boot and workspace-open disclosure below content is still returned whole here, and unsolicited channels stay quiet about it afterwards."]},run:i(async e=>{const r=e.name,l=e.kind,N=e.frontmatter,m=ce({scope:e.scope,dir:e.dir}),d=l===void 0?{}:{kind:l};let s=null,p,c,h;if(m.store!==null){const t=X(de(m.store),!0);c=g(t.docs),h=g([...t.docs,...me()]),s=t.candidates(r,d),p=i(()=>t.resolve(r,d),"fail")}else{const t={...d,...m.scope===void 0?{}:{scope:m.scope},includeDescendants:!0},a=$(E(),{...t,quiet:!0,reportDiscovery:!0});c=h=g(a.docs),s=a.candidates(r,d),p=i(()=>a.resolve(r,d),"fail")}const o=F()||void 0;if(s===null){const t=ie(r.trim()).join("/");if(t!==""&&j(c,t)){const a=o!==void 0?S(o):null,b=a===null?null:_(a,"transcript"),B=ne(c,t,b,!1);return o!==void 0&&a!==null&&b!==null&&(ee(b,te(t),"preview"),D(o,a)),{name:t,listing:B,follow_up:"Each line is readable with `crtr memory read <name>` \u2014 a `[[name]]: <line>` entry is a doc, a bare `[[name]]` is a subdirectory whose read browses deeper. Browse the whole inventory with `crtr memory list`."}}p()}const n=s.winner,P=ae(n),y=R(n.path,N),k=H(y)?await G(y,J({cwd:process.cwd()})):y,u=U(n.body).filter(t=>h.has(t)||j(h,t)),v=o!==void 0?S(o):Q(),x=_(v,"transcript"),C=await le(o);Z(x,n.path,n.body,"content");const f=re(n,{subject:C,target:x,byName:c,memoryTarget:E(),listings:"blocks",explicit:!0});o!==void 0&&D(o,v);const T=f.blocks.length===0?k:`<auto-loaded-context>
|
|
2
2
|
${f.blocks.join(`
|
|
3
3
|
`)}
|
|
4
4
|
</auto-loaded-context>
|
|
5
5
|
|
|
6
|
-
${k}`,w=f.listing,q=w!==void 0&&w.length>0,A=q?u.length>0?"Each `[[name]]` line in `listing` is one of this directory\u2019s immediate members, and the `[[name]]` links in the body are further reading \u2014 follow either with `crtr memory read <name>` only when the task needs that depth.":"Each `[[name]]` line in `listing` is one of this directory\u2019s immediate members, readable with `crtr memory read <name>` \u2014 follow one only when the task needs that depth.":u.length>0?"The `[[name]]` links in the body are further reading \u2014 follow one with `crtr memory read <name>` only when the task needs that depth.":null;return{name:n.name,local_name:n.localName,kind:
|
|
6
|
+
${k}`,w=f.listing,q=w!==void 0&&w.length>0,A=q?u.length>0?"Each `[[name]]` line in `listing` is one of this directory\u2019s immediate members, and the `[[name]]` links in the body are further reading \u2014 follow either with `crtr memory read <name>` only when the task needs that depth.":"Each `[[name]]` line in `listing` is one of this directory\u2019s immediate members, readable with `crtr memory read <name>` \u2014 follow one only when the task needs that depth.":u.length>0?"The `[[name]]` links in the body are further reading \u2014 follow one with `crtr memory read <name>` only when the task needs that depth.":null;return{name:n.name,local_name:n.localName,kind:P,scope:n.scope,path:n.path,store_root:n.root,physical_relative_path:n.physicalRelativePath,representation:n.representation,...s.candidates.length>1?{candidates:s.candidates.map(t=>he(t,n))}:{},content:T,...q?{listing:w}:{},extensions:Y(n.frontmatter?.extensions,K(n)),...u.length>0?{links:u}:{},...A===null?{}:{follow_up:A}}},"run"),render:i(e=>{const{extensions:r,...l}=e;return oe(l,ue.help)},"render")});export{he as candidateRow,V as createMemoryDocSnapshot,ue as readLeaf,R as readMemoryDocContent,z as resolveMemoryDocs};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var s=Object.defineProperty;var e=(n,t)=>s(n,"name",{value:t,configurable:!0});import{defineBranch as o}from"../../core/command.js";const r={concept:"crtr configuration, diagnostics, and self-management",desc:"settings, config, setup, doctor, context, sync, migrate, update, version, feedback, logs, support, daemon",useWhen:"managing the crtr installation, inspecting diagnostics, or operating the crtrd supervisor process"},a={name:"sys",summary:"crtr system configuration, settings, diagnostics, and self-management",model:"Configuration and settings own durable behavior; diagnostics and support observe it. Setup, migration, sync, and update change installed or generated state, while daemon control owns the supervisor lifecycle."};function d(n){return o({name:"sys",rootEntry:r,help:{...a},children:n})}e(d,"defineSysBranch");export{d as defineSysBranch};
|
|
1
|
+
var s=Object.defineProperty;var e=(n,t)=>s(n,"name",{value:t,configurable:!0});import{defineBranch as o}from"../../core/command.js";const r={concept:"crtr configuration, diagnostics, and self-management",desc:"settings, config, connect, setup, doctor, context, sync, migrate, update, version, feedback, logs, support, daemon",useWhen:"managing the crtr installation, inspecting diagnostics, or operating the crtrd supervisor process"},a={name:"sys",summary:"crtr system configuration, settings, diagnostics, and self-management",model:"Configuration and settings own durable behavior; diagnostics and support observe it. Setup, migration, sync, and update change installed or generated state, while daemon control owns the supervisor lifecycle."};function d(n){return o({name:"sys",rootEntry:r,help:{...a},children:n})}e(d,"defineSysBranch");export{d as defineSysBranch};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const sysConnectLeaf: import("../../core/command.js").LeafDef;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var v=Object.defineProperty;var s=(e,t)=>v(e,"name",{value:t,configurable:!0});import{randomBytes as k}from"node:crypto";import C from"node:path";import{defineLeaf as _}from"../../core/command.js";import{readConfig as h,updateConfig as m}from"../../core/config.js";import{emit as P,InputError as u,isJsonOutput as $}from"../../core/io.js";import{renderResult as R}from"../../core/render.js";import{getApiToken as g,setApiToken as x}from"../../core/secrets.js";import{envCrtrdTcp as L,envCrtrdToken as w,envNodeId as I}from"../../shared/env.js";import{shellQuote as d}from"../../shared/shell-quote.js";import{cliClient as y}from"../api-client.js";const A="127.0.0.1:8787",b=3e4,E=100;function D(){return k(32).toString("hex")}s(D,"token");function N(e){$()?P(e):process.stdout.write(R(e,O.help)+`
|
|
2
|
+
`)}s(N,"printCredentials");function S(e){const t=e.lastIndexOf(":");return t===-1||Number(e.slice(t+1))!==0?e:`${e.slice(0,t+1)}8787`}s(S,"fixedTcpPort");async function q(){const e=y(),t=Date.now()+b;let n;for(;Date.now()<t;){try{const r=await e.status();if(r.listeners.tcp!==null)return r}catch(r){n=r}await new Promise(r=>setTimeout(r,E))}throw new u({error:"listener_unavailable",message:`the successor did not expose a TCP listener within ${b}ms${n instanceof Error?`: ${n.message}`:""}`,next:"Run `crtr sys daemon status` to inspect the daemon, then retry `crtr sys connect`."})}s(q,"awaitTcpListener");async function T(e,t){const n=await e.getModelAuthReadiness(t);if(n.credential==="ready")return;process.stderr.write(`Logging in to ${n.provider} for ${n.model}.
|
|
3
|
+
`);const{loginProvider:r,openProviderLoginRuntime:o,terminalAuthInteraction:a}=await import("./provider-login.js");await r(await o(),n.provider,a());const i=await e.getModelAuthReadiness(t);if(i.credential!=="ready")throw new u({error:"provider_login_incomplete",message:`provider ${i.provider} credential is ${i.credential} after login`,next:`Run \`${M(t)}\` again and complete the provider login.`})}s(T,"ensureProviderCredential");function M(e){return["crtr sys connect",e.profile===void 0?void 0:`--profile ${d(e.profile)}`,e.cwd===void 0?void 0:`--cwd ${d(e.cwd)}`,e.kind===void 0?void 0:`--kind ${d(e.kind)}`,e.model===void 0?void 0:`--model ${d(e.model)}`].filter(t=>t!==void 0).join(" ")}s(M,"connectCommand");const O=_({name:"connect",description:"enable browser access to this daemon",whenToUse:"an application needs a browser-reachable daemon URL and bearer token; this enables the local TCP listener and reports the credentials to paste into that application.",help:{name:"sys connect",summary:"enable authenticated browser access and log in to the selected model provider when needed; a handover reports browser credentials before it begins",params:[{kind:"flag",name:"profile",type:"string",required:!1,constraint:"Profile setting selecting the provider a root launch with these settings would use."},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Directory setting selecting the provider a root launch with these settings would use. Default: this directory."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Node kind setting selecting the provider a root launch with these settings would use."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Model setting selecting the provider a root launch with these settings would use."}],output:[{name:"base_url",type:"string",required:!0,constraint:"The HTTP URL configured for the TCP listener."},{name:"token",type:"string",required:!0,constraint:"The bearer token accepted by that listener; paste it only into the application that needs daemon access."}],outputKind:"object",effects:["Stores a missing listener address in user settings and a missing bearer token in the 0600 user secrets store.","When the listener is not already up with both settings present, asks the daemon to hand over to a successor so it starts the listener.","After the listener is available, resolves the provider selected by these params (default: this directory) and runs its existing OAuth login flow when that provider has no usable credential."]},run:s(async e=>{const t={profile:e.profile,cwd:C.resolve(e.cwd??process.cwd()),kind:e.kind,model:e.model},n=I(),o=h("user").api.tcp,a=g("user"),i=y(),p=await i.status();o===""?m("user",l=>{l.api.tcp=A}):Number(o.slice(o.lastIndexOf(":")+1))===0&&m("user",l=>{l.api.tcp=S(o)});const c=L()??h("user").api.tcp;if(Number(c.slice(c.lastIndexOf(":")+1))===0)throw new u({error:"dynamic_tcp_port",message:"CRTRD_TCP must use a fixed port for `crtr sys connect`",next:"Set CRTRD_TCP to a host and fixed port, then run `crtr sys connect` again."});if(o!==""&&a!==void 0&&p.listeners.tcp!==null)return await T(i,t),{base_url:`http://${p.listeners.tcp}`,token:w()??a};a===void 0&&x(D(),"user");const f=w()??g("user");if(f===void 0)throw new Error("TCP listener cannot start without a bearer token");N({base_url:`http://${c}`,token:f}),await i.restartDaemon(),await q(),await T(i,t),n!==void 0&&await i.sendMessage(n,{body:"Browser access is ready.",tier:"urgent",from:n})},"run")});export{O as sysConnectLeaf};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var c=Object.defineProperty;var s=(r,e)=>c(r,"name",{value:e,configurable:!0});import{defineLeaf as d,defineBranch as w}from"../../core/command.js";import{InputError as o}from"../../core/io.js";import{parseDurationMs as f}from"../../core/wake.js";import{DaemonNotPreparedError as g,probeDaemonHealth as u,spawnDaemon as h,stopDaemonProcess as y,sweepStrayDaemons as b}from"../../daemon/manage.js";import{isDaemonRunning as v,readPidfile as m,isPidAlive as l}from"../../daemon/pidfile.js";import{cliClient as k}from"../api-client.js";import{CrtrClient as _}from"../../api/index.js";import{defineSysBranch as T}from"./branch.js";const q=d({name:"start",description:"start the daemon in the background",whenToUse:"bringing the crtrd supervisor up for the first time in a session so node window-exits get handled (no-op only when its Unix API socket already serves)",help:{name:"sys daemon start",summary:"start the crtrd supervisor daemon in the background (no-op only if its Unix API socket serves)",params:[{kind:"flag",name:"prepare",type:"bool",required:!1,constraint:"Hold normal daemon work after storage preparation until `sys daemon admit`; omit for ordinary startup."}],output:[{name:"started",type:"boolean",required:!0,constraint:"True when a new daemon process was spawned; false when one was already running."},{name:"pid",type:"number",required:!1,constraint:"PID of the newly spawned daemon."},{name:"existing_pid",type:"number",required:!1,constraint:"PID of the already-running daemon (when started=false)."},{name:"running",type:"boolean",required:!1,constraint:"True only when a live pidfile owner does not serve its Unix API socket."},{name:"serving",type:"boolean",required:!1,constraint:"False only when a live pidfile owner does not answer /healthz."},{name:"probe_error",type:"string",required:!1,constraint:"The final /healthz probe failure when a live owner missed readiness."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon phase when it was reachable."}],outputKind:"object",effects:["Spawns dist/daemon/crtrd-cli.js detached + unref'd; it outlives this process.","Writes a pidfile at CRTR_PIDFILE if set, otherwise crtrHome()/crtrd.pid."]},run:s(async r=>{try{return await h({targetPhase:r.prepare===!0?"prepared":"ready"})}catch(e){throw e instanceof g?new o({error:"daemon_not_prepared",message:e.message,next:"Stop the live daemon, then start a new one with `crtr sys daemon start --prepare` before setup."}):e}},"run")}),x=100;async function D(r,e){const n=Date.now(),a=n+e;let i="initializing",p="the daemon socket was not reachable";for(;Date.now()<a;){try{const t=await u(Math.min(1e3,Math.max(1,a-Date.now())));if(i=t.startup_phase,p=t.startup_blocked??`daemon is ${t.startup_phase}`,t.startup_phase==="blocked")throw new o({error:"startup_blocked",message:t.startup_blocked??"daemon startup is blocked",received:t.startup_phase,field:"phase",next:"Repair the reported blocker paths with `crtr sys migrate`, then retry the await."});if(t.startup_phase===r)return{startup_phase:r,waited_ms:Date.now()-n};if(r==="prepared"&&t.startup_phase==="ready")throw new o({error:"await_missed_phase",message:"daemon is already ready and cannot return to prepared in this process",received:t.startup_phase,field:"phase",next:"Start a new daemon with `crtr sys daemon start --prepare` before awaiting prepared."})}catch(t){if(t instanceof o)throw t;p=t instanceof Error?t.message:String(t)}await new Promise(t=>setTimeout(t,Math.min(x,Math.max(0,a-Date.now()))))}throw new o({error:"await_timeout",message:`daemon did not reach ${r} within ${e}ms (last phase: ${i}; last observation: ${p})`,received:i,field:"phase",next:"Inspect `crtr sys daemon status` and resolve any reported startup blocker before retrying."})}s(D,"awaitDaemonPhase");const I=d({name:"await",description:"wait for a daemon startup phase",whenToUse:"a caller started or was given a daemon that must reach prepared or ready before its next step.",help:{name:"sys daemon await",summary:"wait for the running daemon to report one startup phase",params:[{kind:"flag",name:"phase",type:"enum",choices:["prepared","ready"],required:!0,constraint:"The phase that permits the caller\u2019s next step."},{kind:"flag",name:"timeout",type:"string",required:!1,default:"5m",constraint:"Maximum wait as a positive duration such as 90s, 5m, or 1h30m."}],output:[{name:"startup_phase",type:"string",required:!0,constraint:"The requested phase."},{name:"waited_ms",type:"number",required:!0,constraint:"Elapsed wall-clock time before the phase was observed."}],outputKind:"object",effects:["Read-only: polls /healthz until the requested phase is observed or the caller-bounded timeout expires."]},run:s(async r=>{const e=r.timeout,n=f(e);if(n===null||n<=0)throw new o({error:"invalid_duration",message:`not a positive duration: ${e}`,received:e,field:"timeout",next:"Use a positive duration such as 90s, 5m, or 1h30m."});return D(r.phase,n)},"run")}),S=d({name:"admit",description:"admit normal work after daemon preparation",whenToUse:"the caller has completed its setup against a prepared daemon and is ready for normal daemon work to start.",help:{name:"sys daemon admit",summary:"ask a prepared daemon to start normal services",params:[],output:[{name:"admitted",type:"boolean",required:!0,constraint:"True only when this request began admission."},{name:"already_admitted",type:"boolean",required:!0,constraint:"True when admission had already begun or completed."},{name:"startup_phase",type:"string",required:!0,constraint:"The phase after admission settles."}],outputKind:"object",effects:["Starts normal daemon services when the daemon is prepared."]},run:s(async r=>await _.forLocalSocket({autostart:!1}).admitDaemon(),"run")}),E=d({name:"status",description:"check whether the daemon is running",whenToUse:"operator/dev diagnostic \u2014 probing whether the crtrd supervisor process is alive while troubleshooting revive/supervision behavior",help:{name:"sys daemon status",summary:"check whether the crtrd supervisor daemon is currently running",params:[],output:[{name:"running",type:"boolean",required:!0,constraint:"True when the daemon pidfile owner is alive."},{name:"serving",type:"boolean",required:!1,constraint:"True when the live daemon answers /healthz; false when one probe does not."},{name:"pid",type:"number",required:!1,constraint:"PID of the running daemon."},{name:"probe_error",type:"string",required:!1,constraint:"The failed /healthz observation when the live owner did not answer."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon startup phase when the socket answered."}],outputKind:"object",effects:["Read-only: reads the pidfile, probes the pid, and requests /healthz without autostart."]},run:s(async r=>{const e=m();if(!(e!==null&&l(e)))return{running:!1};try{const a=await u();return{running:!0,serving:a.ok,pid:e,...a.startup_phase===void 0?{}:{startup_phase:a.startup_phase}}}catch(a){return{running:!0,serving:!1,pid:e,probe_error:a.message}}},"run")}),P=d({name:"restart",description:"put the selected runtime generation in service via a daemon-owned handover",whenToUse:"adopting a newly installed runtime generation \u2014 the routine restart. Use this instead of stop+start: the daemon answers before it hands over, so a caller running inside a node (whose broker the handover tears down) still gets a settled result and is resumed automatically.",help:{name:"sys daemon restart",summary:"ask the running daemon to replace itself with a successor on the selected runtime generation",params:[],output:[{name:"restarted",type:"boolean",required:!0,constraint:"True when a running daemon accepted the handover. False when none was running \u2014 one was started instead."},{name:"started",type:"boolean",required:!1,constraint:"True when no daemon was running and this call started one (restarted=false)."},{name:"pid",type:"number",required:!1,constraint:"The daemon that accepted the handover, or the one just started."},{name:"grace_ms",type:"number",required:!1,constraint:"How long the daemon waits after answering before teardown begins."},{name:"note",type:"string",required:!1,constraint:"What happens next to the calling node. Read it before acting on an interrupted turn."}],outputKind:"object",effects:["The daemon acknowledges immediately, then after a short grace tears its whole broker fleet down, releases its claim, spawns a successor on the runtime generation currently selected, and exits.","Each torn-down broker hands its foreground bash commands to the file-backed background job system; they keep running and report completion by urgent inbox message.","Every node it tore down \u2014 including the caller \u2014 is resolved by the successor through the ordinary row-driven supervision tick.","The calling process is killed as part of that teardown, AFTER this result is returned."]},run:s(async r=>{if(!v()){const e=await h();return{restarted:!1,started:e.started,...e.pid!==void 0?{pid:e.pid}:{},...e.existing_pid!==void 0?{pid:e.existing_pid}:{},note:"No daemon was running, so there was nothing to hand over; one was started on the selected runtime generation."}}try{const e=await k().restartDaemon();return{restarted:e.accepted,pid:e.predecessor_pid,grace_ms:e.grace_ms,note:"Handover accepted. If you are running inside a node, your broker is about to be torn down and resumed on the new generation \u2014 THIS RESTART ALREADY SUCCEEDED. When your turn resumes with this result in the transcript, do not re-run it."}}catch(e){throw new o({error:"daemon_restart_failed",message:`the daemon did not accept a handover: ${e.message}`,next:"Run `crtr sys daemon status` to inspect the daemon."})}},"run")}),R=d({name:"stop",description:"stop the running daemon",whenToUse:"shutting the crtrd supervisor down for good, ending auto-revival of nodes on window exit. Not the way to pick up a new runtime generation \u2014 `restart` is, because stopping kills every broker of this epoch, including the caller's own, so this command cannot report its result to an agent that runs it.",help:{name:"sys daemon stop",summary:"stop the crtrd supervisor daemon and confirm it exited",params:[],output:[{name:"stopped",type:"boolean",required:!0,constraint:"True when a daemon (recorded or stray) was found and killed. False when none was running."},{name:"pid",type:"number",required:!1,constraint:"The recorded pidfile pid that exited (when one was recorded and alive)."},{name:"strays_killed",type:"array",required:!1,constraint:"PIDs of stray crtrd daemons (alive but not in the pidfile) swept alongside the recorded one \u2014 the backstop against a daemon that survived a prior stop."},{name:"force_killed",type:"boolean",required:!1,constraint:"True when the recorded daemon ignored SIGTERM and had to be SIGKILLed."}],outputKind:"object",effects:["Sends SIGTERM to the recorded pid and waits for it to exit and release its pidfile, escalating to SIGKILL if it does not.","Then sweeps any OTHER live crtrd daemon process (SIGTERM, escalating to SIGKILL after 2s) so a stray daemon orphaned from the pidfile cannot keep supervising."]},run:s(async r=>{const e=m(),n=e!==null&&l(e);try{let a=!1;n&&(a=await y(e)==="killed");const i=b(null);return!n&&i.length===0?{stopped:!1}:{stopped:!0,...n?{pid:e}:{},...i.length>0?{strays_killed:i}:{},...a?{force_killed:!0}:{}}}catch(a){throw new o({error:"daemon_stop_failed",message:`failed to stop daemon${n?` pid ${e}`:""}: ${a.message}`,next:"Run `crtr sys daemon status` to inspect the daemon before retrying."})}},"run")}),A=w({name:"daemon",description:"manage the crtrd supervisor process",whenToUse:"managing the crtrd supervisor that auto-revives nodes on window exit \u2014 start it, restart it onto a new runtime generation, check its status, or stop it",help:{name:"sys daemon",summary:"manage the crtrd canvas supervisor daemon",model:"crtrd is a thin background daemon that polls active+idle nodes and acts on window exit: crashed windows become dead; refresh-yield windows get a fresh respawn. It holds no orchestration logic \u2014 just process supervision. These are operator/dev self-management, not something a normal agent turn needs to check. Picking up a newly installed runtime generation is `restart` (a handover the daemon performs on itself); `stop` is the terminal one, and it kills the caller along with the rest of the fleet."},children:[q,I,S,P,E,R]});function G(){return T([A])}s(G,"registerDaemonControlSys");export{A as daemonBranch,G as registerDaemonControlSys};
|
|
1
|
+
var c=Object.defineProperty;var s=(r,e)=>c(r,"name",{value:e,configurable:!0});import{defineLeaf as d,defineBranch as w}from"../../core/command.js";import{InputError as o}from"../../core/io.js";import{parseDurationMs as f}from"../../core/wake.js";import{DaemonNotPreparedError as g,probeDaemonHealth as u,probeErrorMessage as y,spawnDaemon as h,stopDaemonProcess as b,sweepStrayDaemons as v}from"../../daemon/manage.js";import{isDaemonRunning as _,readPidfile as m,isPidAlive as l}from"../../daemon/pidfile.js";import{cliClient as k}from"../api-client.js";import{localClient as T}from"../../api/node-transport.js";import{defineSysBranch as q}from"./branch.js";const x=d({name:"start",description:"start the daemon in the background",whenToUse:"bringing the crtrd supervisor up for the first time in a session so node window-exits get handled (no-op only when its Unix API socket already serves)",help:{name:"sys daemon start",summary:"start the crtrd supervisor daemon in the background (no-op only if its Unix API socket serves)",params:[{kind:"flag",name:"prepare",type:"bool",required:!1,constraint:"Hold normal daemon work after storage preparation until `sys daemon admit`; omit for ordinary startup."}],output:[{name:"started",type:"boolean",required:!0,constraint:"True when a new daemon process was spawned; false when one was already running."},{name:"pid",type:"number",required:!1,constraint:"PID of the newly spawned daemon."},{name:"existing_pid",type:"number",required:!1,constraint:"PID of the already-running daemon (when started=false)."},{name:"running",type:"boolean",required:!1,constraint:"True only when a live pidfile owner does not serve its Unix API socket."},{name:"serving",type:"boolean",required:!1,constraint:"False only when a live pidfile owner does not answer /healthz."},{name:"probe_error",type:"string",required:!1,constraint:"The final /healthz probe failure when a live owner missed readiness."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon phase when it was reachable."}],outputKind:"object",effects:["Spawns dist/daemon/crtrd-cli.js detached + unref'd; it outlives this process.","Writes a pidfile at CRTR_PIDFILE if set, otherwise crtrHome()/crtrd.pid."]},run:s(async r=>{try{return await h({targetPhase:r.prepare===!0?"prepared":"ready"})}catch(e){throw e instanceof g?new o({error:"daemon_not_prepared",message:e.message,next:"Stop the live daemon, then start a new one with `crtr sys daemon start --prepare` before setup."}):e}},"run")}),D=100;async function I(r,e){const n=Date.now(),a=n+e;let i="initializing",p="the daemon socket was not reachable";for(;Date.now()<a;){try{const t=await u(Math.min(1e3,Math.max(1,a-Date.now())));if(i=t.startup_phase,p=t.startup_blocked??`daemon is ${t.startup_phase}`,t.startup_phase==="blocked")throw new o({error:"startup_blocked",message:t.startup_blocked??"daemon startup is blocked",received:t.startup_phase,field:"phase",next:"Repair the reported blocker paths with `crtr sys migrate`, then retry the await."});if(t.startup_phase===r)return{startup_phase:r,waited_ms:Date.now()-n};if(r==="prepared"&&t.startup_phase==="ready")throw new o({error:"await_missed_phase",message:"daemon is already ready and cannot return to prepared in this process",received:t.startup_phase,field:"phase",next:"Start a new daemon with `crtr sys daemon start --prepare` before awaiting prepared."})}catch(t){if(t instanceof o)throw t;p=t instanceof Error?t.message:String(t)}await new Promise(t=>setTimeout(t,Math.min(D,Math.max(0,a-Date.now()))))}throw new o({error:"await_timeout",message:`daemon did not reach ${r} within ${e}ms (last phase: ${i}; last observation: ${p})`,received:i,field:"phase",next:"Inspect `crtr sys daemon status` and resolve any reported startup blocker before retrying."})}s(I,"awaitDaemonPhase");const S=d({name:"await",description:"wait for a daemon startup phase",whenToUse:"a caller started or was given a daemon that must reach prepared or ready before its next step.",help:{name:"sys daemon await",summary:"wait for the running daemon to report one startup phase",params:[{kind:"flag",name:"phase",type:"enum",choices:["prepared","ready"],required:!0,constraint:"The phase that permits the caller\u2019s next step."},{kind:"flag",name:"timeout",type:"string",required:!1,default:"5m",constraint:"Maximum wait as a positive duration such as 90s, 5m, or 1h30m."}],output:[{name:"startup_phase",type:"string",required:!0,constraint:"The requested phase."},{name:"waited_ms",type:"number",required:!0,constraint:"Elapsed wall-clock time before the phase was observed."}],outputKind:"object",effects:["Read-only: polls /healthz until the requested phase is observed or the caller-bounded timeout expires."]},run:s(async r=>{const e=r.timeout,n=f(e);if(n===null||n<=0)throw new o({error:"invalid_duration",message:`not a positive duration: ${e}`,received:e,field:"timeout",next:"Use a positive duration such as 90s, 5m, or 1h30m."});return I(r.phase,n)},"run")}),E=d({name:"admit",description:"admit normal work after daemon preparation",whenToUse:"the caller has completed its setup against a prepared daemon and is ready for normal daemon work to start.",help:{name:"sys daemon admit",summary:"ask a prepared daemon to start normal services",params:[],output:[{name:"admitted",type:"boolean",required:!0,constraint:"True only when this request began admission."},{name:"already_admitted",type:"boolean",required:!0,constraint:"True when admission had already begun or completed."},{name:"startup_phase",type:"string",required:!0,constraint:"The phase after admission settles."}],outputKind:"object",effects:["Starts normal daemon services when the daemon is prepared."]},run:s(async r=>await T({autostart:!1}).admitDaemon(),"run")}),P=d({name:"status",description:"check whether the daemon is running",whenToUse:"operator/dev diagnostic \u2014 probing whether the crtrd supervisor process is alive while troubleshooting revive/supervision behavior",help:{name:"sys daemon status",summary:"check whether the crtrd supervisor daemon is currently running",params:[],output:[{name:"running",type:"boolean",required:!0,constraint:"True when the daemon pidfile owner is alive."},{name:"serving",type:"boolean",required:!1,constraint:"True when the live daemon answers /healthz; false when one probe does not."},{name:"pid",type:"number",required:!1,constraint:"PID of the running daemon."},{name:"probe_error",type:"string",required:!1,constraint:"The failed /healthz observation when the live owner did not answer."},{name:"startup_phase",type:"string",required:!1,constraint:"The observed daemon startup phase when the socket answered."}],outputKind:"object",effects:["Read-only: reads the pidfile, probes the pid, and requests /healthz without autostart."]},run:s(async r=>{const e=m();if(!(e!==null&&l(e)))return{running:!1};try{const a=await u();return{running:!0,serving:a.ok,pid:e,...a.startup_phase===void 0?{}:{startup_phase:a.startup_phase}}}catch(a){return{running:!0,serving:!1,pid:e,probe_error:y(a)}}},"run")}),R=d({name:"restart",description:"put the selected runtime generation in service via a daemon-owned handover",whenToUse:"adopting a newly installed runtime generation \u2014 the routine restart. Use this instead of stop+start: the daemon answers before it hands over, so a caller running inside a node (whose broker the handover tears down) still gets a settled result and is resumed automatically.",help:{name:"sys daemon restart",summary:"ask the running daemon to replace itself with a successor on the selected runtime generation",params:[],output:[{name:"restarted",type:"boolean",required:!0,constraint:"True when a running daemon accepted the handover. False when none was running \u2014 one was started instead."},{name:"started",type:"boolean",required:!1,constraint:"True when no daemon was running and this call started one (restarted=false)."},{name:"pid",type:"number",required:!1,constraint:"The daemon that accepted the handover, or the one just started."},{name:"grace_ms",type:"number",required:!1,constraint:"How long the daemon waits after answering before teardown begins."},{name:"note",type:"string",required:!1,constraint:"What happens next to the calling node. Read it before acting on an interrupted turn."}],outputKind:"object",effects:["The daemon acknowledges immediately, then after a short grace tears its whole broker fleet down, releases its claim, spawns a successor on the runtime generation currently selected, and exits.","Each torn-down broker hands its foreground bash commands to the file-backed background job system; they keep running and report completion by urgent inbox message.","Every node it tore down \u2014 including the caller \u2014 is resolved by the successor through the ordinary row-driven supervision tick.","The calling process is killed as part of that teardown, AFTER this result is returned."]},run:s(async r=>{if(!_()){const e=await h();return{restarted:!1,started:e.started,...e.pid!==void 0?{pid:e.pid}:{},...e.existing_pid!==void 0?{pid:e.existing_pid}:{},note:"No daemon was running, so there was nothing to hand over; one was started on the selected runtime generation."}}try{const e=await k().restartDaemon();return{restarted:e.accepted,pid:e.predecessor_pid,grace_ms:e.grace_ms,note:"Handover accepted. If you are running inside a node, your broker is about to be torn down and resumed on the new generation \u2014 THIS RESTART ALREADY SUCCEEDED. When your turn resumes with this result in the transcript, do not re-run it."}}catch(e){throw new o({error:"daemon_restart_failed",message:`the daemon did not accept a handover: ${e.message}`,next:"Run `crtr sys daemon status` to inspect the daemon."})}},"run")}),A=d({name:"stop",description:"stop the running daemon",whenToUse:"shutting the crtrd supervisor down for good, ending auto-revival of nodes on window exit. Not the way to pick up a new runtime generation \u2014 `restart` is, because stopping kills every broker of this epoch, including the caller's own, so this command cannot report its result to an agent that runs it.",help:{name:"sys daemon stop",summary:"stop the crtrd supervisor daemon and confirm it exited",params:[],output:[{name:"stopped",type:"boolean",required:!0,constraint:"True when a daemon (recorded or stray) was found and killed. False when none was running."},{name:"pid",type:"number",required:!1,constraint:"The recorded pidfile pid that exited (when one was recorded and alive)."},{name:"strays_killed",type:"array",required:!1,constraint:"PIDs of stray crtrd daemons (alive but not in the pidfile) swept alongside the recorded one \u2014 the backstop against a daemon that survived a prior stop."},{name:"force_killed",type:"boolean",required:!1,constraint:"True when the recorded daemon ignored SIGTERM and had to be SIGKILLed."}],outputKind:"object",effects:["Sends SIGTERM to the recorded pid and waits for it to exit and release its pidfile, escalating to SIGKILL if it does not.","Then sweeps any OTHER live crtrd daemon process (SIGTERM, escalating to SIGKILL after 2s) so a stray daemon orphaned from the pidfile cannot keep supervising."]},run:s(async r=>{const e=m(),n=e!==null&&l(e);try{let a=!1;n&&(a=await b(e)==="killed");const i=v(null);return!n&&i.length===0?{stopped:!1}:{stopped:!0,...n?{pid:e}:{},...i.length>0?{strays_killed:i}:{},...a?{force_killed:!0}:{}}}catch(a){throw new o({error:"daemon_stop_failed",message:`failed to stop daemon${n?` pid ${e}`:""}: ${a.message}`,next:"Run `crtr sys daemon status` to inspect the daemon before retrying."})}},"run")}),U=w({name:"daemon",description:"manage the crtrd supervisor process",whenToUse:"managing the crtrd supervisor that auto-revives nodes on window exit \u2014 start it, restart it onto a new runtime generation, check its status, or stop it",help:{name:"sys daemon",summary:"manage the crtrd canvas supervisor daemon",model:"crtrd is a thin background daemon that polls active+idle nodes and acts on window exit: crashed windows become dead; refresh-yield windows get a fresh respawn. It holds no orchestration logic \u2014 just process supervision. These are operator/dev self-management, not something a normal agent turn needs to check. Picking up a newly installed runtime generation is `restart` (a handover the daemon performs on itself); `stop` is the terminal one, and it kills the caller along with the rest of the fleet."},children:[x,S,E,R,P,A]});function F(){return q([U])}s(F,"registerDaemonControlSys");export{U as daemonBranch,F as registerDaemonControlSys};
|