@xingjisoft/neutrino 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +6 -1
  2. package/assets/fast-planner.txt +57 -0
  3. package/assets/fast-replanner.txt +28 -0
  4. package/dist/agents.d.ts +25 -0
  5. package/dist/app.d.ts +206 -0
  6. package/dist/app.js +15 -0
  7. package/dist/background.d.ts +96 -0
  8. package/dist/bin.d.ts +2 -0
  9. package/dist/bin.js +40 -3
  10. package/dist/browser.d.ts +18 -0
  11. package/dist/clipboard.d.ts +16 -0
  12. package/dist/colors.d.ts +29 -0
  13. package/dist/compact.d.ts +52 -0
  14. package/dist/config.d.ts +221 -0
  15. package/dist/config.js +22 -7
  16. package/dist/cwd.d.ts +19 -0
  17. package/dist/fast/artifacts.d.ts +26 -0
  18. package/dist/fast/artifacts.js +109 -0
  19. package/dist/fast/engine.d.ts +78 -0
  20. package/dist/fast/engine.js +657 -0
  21. package/dist/fast/evidence.d.ts +21 -0
  22. package/dist/fast/evidence.js +68 -0
  23. package/dist/fast/planner.d.ts +10 -0
  24. package/dist/fast/planner.js +51 -0
  25. package/dist/fast/pool.d.ts +17 -0
  26. package/dist/fast/pool.js +78 -0
  27. package/dist/fast/scheduler.d.ts +54 -0
  28. package/dist/fast/scheduler.js +383 -0
  29. package/dist/fast/types.d.ts +100 -0
  30. package/dist/fast/types.js +21 -0
  31. package/dist/fast/worker.d.ts +23 -0
  32. package/dist/fast/worker.js +185 -0
  33. package/dist/header.d.ts +3 -0
  34. package/dist/index.d.ts +5 -0
  35. package/dist/llm/approval.d.ts +204 -0
  36. package/dist/llm/approval.js +14 -3
  37. package/dist/llm/client.d.ts +118 -0
  38. package/dist/llm/client.js +57 -8
  39. package/dist/llm/codeExecutor.d.ts +14 -0
  40. package/dist/llm/codemode.d.ts +15 -0
  41. package/dist/llm/context.d.ts +29 -0
  42. package/dist/llm/context.js +17 -4
  43. package/dist/llm/diagnostics.d.ts +91 -0
  44. package/dist/llm/diagnostics.js +242 -0
  45. package/dist/llm/plan.d.ts +148 -0
  46. package/dist/llm/retry.d.ts +80 -0
  47. package/dist/llm/risk.d.ts +21 -0
  48. package/dist/llm/risk.js +7 -0
  49. package/dist/llm/runtime.d.ts +189 -0
  50. package/dist/llm/runtime.js +178 -24
  51. package/dist/llm/search.d.ts +2 -0
  52. package/dist/llm/searchNetwork.d.ts +8 -0
  53. package/dist/llm/subagent.d.ts +112 -0
  54. package/dist/llm/subagent.js +25 -12
  55. package/dist/llm/tools.d.ts +20 -0
  56. package/dist/llm/tools.js +4 -1
  57. package/dist/llm/workspace.d.ts +6 -0
  58. package/dist/llm/workspace.js +271 -0
  59. package/dist/markdown.d.ts +58 -0
  60. package/dist/migration.d.ts +8 -0
  61. package/dist/persistence.d.ts +14 -0
  62. package/dist/plugins/catalog.d.ts +20 -0
  63. package/dist/plugins/catalog.js +45 -0
  64. package/dist/plugins/cli.d.ts +1 -0
  65. package/dist/plugins/cli.js +106 -0
  66. package/dist/plugins/host.d.ts +121 -0
  67. package/dist/plugins/host.js +450 -0
  68. package/dist/plugins/manager.d.ts +24 -0
  69. package/dist/plugins/manager.js +288 -0
  70. package/dist/plugins/package.d.ts +5 -0
  71. package/dist/plugins/package.js +53 -0
  72. package/dist/plugins/runner.d.ts +1 -0
  73. package/dist/plugins/runner.js +227 -0
  74. package/dist/plugins/session.d.ts +12 -0
  75. package/dist/plugins/session.js +97 -0
  76. package/dist/plugins/terminal.d.ts +22 -0
  77. package/dist/plugins/terminal.js +33 -0
  78. package/dist/plugins/types.d.ts +178 -0
  79. package/dist/plugins/types.js +6 -0
  80. package/dist/plugins/validation.d.ts +14 -0
  81. package/dist/plugins/validation.js +174 -0
  82. package/dist/plugins/web-sdk.d.ts +58 -0
  83. package/dist/plugins/web-sdk.js +1 -0
  84. package/dist/pro/agents.d.ts +34 -0
  85. package/dist/pro/agents.js +1 -1
  86. package/dist/pro/config.d.ts +75 -0
  87. package/dist/pro/config.js +3 -2
  88. package/dist/pro/preflight.d.ts +21 -0
  89. package/dist/sanitize.d.ts +34 -0
  90. package/dist/screens/agentConversation.d.ts +28 -0
  91. package/dist/screens/agentHistory.d.ts +25 -0
  92. package/dist/screens/agentSelect.d.ts +16 -0
  93. package/dist/screens/chat.d.ts +292 -0
  94. package/dist/screens/chat.js +179 -65
  95. package/dist/screens/history.d.ts +23 -0
  96. package/dist/screens/modelSelect.d.ts +29 -0
  97. package/dist/screens/plugins.d.ts +29 -0
  98. package/dist/screens/plugins.js +196 -0
  99. package/dist/screens/settings.d.ts +42 -0
  100. package/dist/screens/start.d.ts +43 -0
  101. package/dist/screens/start.js +25 -17
  102. package/dist/screens/updatePrompt.d.ts +41 -0
  103. package/dist/selection.d.ts +75 -0
  104. package/dist/state.d.ts +26 -0
  105. package/dist/terminal/buffer.d.ts +47 -0
  106. package/dist/terminal/input.d.ts +70 -0
  107. package/dist/terminal/renderer.d.ts +43 -0
  108. package/dist/terminal/restore.d.ts +41 -0
  109. package/dist/terminal/screen.d.ts +57 -0
  110. package/dist/tray.d.ts +14 -0
  111. package/dist/ui.d.ts +123 -0
  112. package/dist/update.d.ts +71 -0
  113. package/dist/updateWatch.d.ts +43 -0
  114. package/dist/web/approvalQueue.d.ts +8 -0
  115. package/dist/web/approvalQueue.js +59 -0
  116. package/dist/web/auth.d.ts +14 -0
  117. package/dist/web/clientState.d.ts +16 -0
  118. package/dist/web/folderPicker.d.ts +34 -0
  119. package/dist/web/folderPicker.js +544 -0
  120. package/dist/web/hub.d.ts +213 -0
  121. package/dist/web/hub.js +14 -2
  122. package/dist/web/localApi.d.ts +11 -0
  123. package/dist/web/localApi.js +260 -0
  124. package/dist/web/plugins.d.ts +8 -0
  125. package/dist/web/plugins.js +64 -0
  126. package/dist/web/server.d.ts +81 -0
  127. package/dist/web/server.js +127 -19
  128. package/dist/web/service.d.ts +7 -0
  129. package/dist/web/service.js +1 -0
  130. package/dist/web/session.d.ts +176 -0
  131. package/dist/web/session.js +170 -21
  132. package/dist/web/settings.d.ts +65 -0
  133. package/dist/web/terminalBridge.d.ts +15 -0
  134. package/dist/web/terminalBridge.js +8 -2
  135. package/dist/width.d.ts +6 -0
  136. package/docs/plugins/README.md +126 -0
  137. package/docs/plugins/agent-sdk.md +92 -0
  138. package/docs/plugins/architecture.md +64 -0
  139. package/docs/plugins/compatibility.md +34 -0
  140. package/docs/plugins/development.md +55 -0
  141. package/docs/plugins/manifest.md +59 -0
  142. package/docs/plugins/manifest.schema.json +286 -0
  143. package/docs/plugins/security.md +53 -0
  144. package/docs/plugins/skills-prompts.md +69 -0
  145. package/docs/plugins/ui-sdk.md +109 -0
  146. package/examples/local-api.py +105 -0
  147. package/examples/plugins/cards/card.html +6 -0
  148. package/examples/plugins/cards/neutrino.plugin.json +42 -0
  149. package/examples/plugins/composer/editor.html +10 -0
  150. package/examples/plugins/composer/neutrino.plugin.json +10 -0
  151. package/examples/plugins/function-tools/agent.js +32 -0
  152. package/examples/plugins/function-tools/neutrino.plugin.json +15 -0
  153. package/examples/plugins/menus-tabs/neutrino.plugin.json +15 -0
  154. package/examples/plugins/menus-tabs/panel.html +6 -0
  155. package/examples/plugins/skills-prompts/neutrino.plugin.json +13 -0
  156. package/examples/plugins/skills-prompts/prompts/evidence.md +1 -0
  157. package/examples/plugins/skills-prompts/prompts/reviewer.md +1 -0
  158. package/examples/plugins/skills-prompts/skills/review/SKILL.md +8 -0
  159. package/examples/plugins/theme/neutrino.plugin.json +12 -0
  160. package/package.json +66 -42
  161. package/web/LOCAL-API.md +65 -0
  162. package/web/README.md +30 -8
  163. package/web/app.js +256 -67
  164. package/web/i18n.js +18 -3
  165. package/web/index.html +3 -1
  166. package/web/plugins.css +10 -0
  167. package/web/plugins.js +230 -0
  168. package/web/style.css +12 -3
  169. package/web/turns.js +7 -8
@@ -0,0 +1,43 @@
1
+ import type { Buffer } from "./buffer.js";
2
+ export declare class Renderer {
3
+ private prevSig;
4
+ private disposed;
5
+ private prevW;
6
+ private prevH;
7
+ private out;
8
+ /**
9
+ * The most recent frame, kept so `repaint()` can re-emit it without re-running the
10
+ * screen's (potentially expensive) render — to heal any drift the terminal
11
+ * introduced behind our back.
12
+ */
13
+ private lastBuf;
14
+ private blocked;
15
+ private pendingForce;
16
+ private drainHandler;
17
+ init(): void;
18
+ dispose(): void;
19
+ /**
20
+ * @param forceRows rows to repaint unconditionally, even when their contents
21
+ * are unchanged. Screens use this for persistent chrome (input box, hints)
22
+ * so that terminal-side drift can never leave it stale (see
23
+ * `Screen.alwaysRepaintRows`).
24
+ */
25
+ render(buf: Buffer, forceRows?: readonly number[]): void;
26
+ /**
27
+ * Re-emit the last frame in full. Cheap (no screen re-render) and visually a
28
+ * no-op when nothing drifted, but it repairs rows the terminal moved on its
29
+ * own (resize reflow, stray scroll). Called periodically as a safety net.
30
+ */
31
+ repaint(): void;
32
+ /**
33
+ * Ring the terminal bell.
34
+ *
35
+ * Written straight to the stream rather than into a frame: BEL has no visual
36
+ * effect, so it cannot desync the diffed rows, and it must never end up in
37
+ * `lastBuf` where repaint() would replay it.
38
+ */
39
+ bell(times?: number): void;
40
+ private draw;
41
+ private renderRow;
42
+ private write;
43
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Escape sequences that undo everything the TUI switches on.
3
+ *
4
+ * Mouse tracking is disabled before the alternate screen is left, and the
5
+ * cursor is restored before the screen swap, so the shell never inherits a
6
+ * half-configured terminal. Order matters: `?1049l` must come last or the
7
+ * remaining writes would land on the main screen.
8
+ */
9
+ export declare const TUI_TEARDOWN: string;
10
+ /** Called once the renderer has entered the alternate screen and raw mode. */
11
+ export declare function markTuiActive(): void;
12
+ /**
13
+ * Puts the terminal back into a usable state, synchronously and at most once.
14
+ *
15
+ * Everything here has to work while the process is dying: no promises, no
16
+ * asynchronous writes, and every step individually guarded so one failure
17
+ * cannot skip the rest (a broken `setRawMode` must not leave the cursor
18
+ * hidden, for example).
19
+ */
20
+ export declare function restoreTerminalSync(): boolean;
21
+ /** Appends a crash entry to `~/.neutrino/crash.log` and returns the path. */
22
+ export declare function writeCrashLog(kind: string, err: unknown): string | null;
23
+ /**
24
+ * Installs process-wide handlers that restore the terminal before the process
25
+ * dies.
26
+ *
27
+ * Without this, any throw that escapes a timer, a stream callback or a floating
28
+ * promise kills the process with raw mode still on and the alternate screen
29
+ * still active. The visible result is a terminal that works but spews
30
+ * `[<b>;<x>;<y>M` mouse reports at the shell prompt, because the shell now
31
+ * receives the mouse events the TUI asked the terminal to send.
32
+ */
33
+ export interface CrashGuardOptions {
34
+ /**
35
+ * Keep SIGHUP for the caller. A detached background service must survive the
36
+ * terminal that started it closing (on POSIX that arrives as SIGHUP), while
37
+ * the terminal UI still wants a hangup to end the process.
38
+ */
39
+ surviveHangup?: boolean;
40
+ }
41
+ export declare function installCrashGuard(options?: CrashGuardOptions): void;
@@ -0,0 +1,57 @@
1
+ import type { Buffer } from "./buffer.js";
2
+ import type { Key, MouseEvent } from "./input.js";
3
+ import type { App } from "../app.js";
4
+ /**
5
+ * A screen's scrollbar, as seen by the app. Supplying this lets the app turn a
6
+ * press/drag on the scrollbar column into scrolling, the same way a GUI does.
7
+ */
8
+ export interface ScrollbarHandle {
9
+ /** Column the bar is drawn in. */
10
+ x: number;
11
+ /** Inclusive row range of the groove. */
12
+ top: number;
13
+ bottom: number;
14
+ /** Total scrollable lines and how many fit on screen. */
15
+ total: number;
16
+ viewport: number;
17
+ /** Current topmost visible line. */
18
+ offset: number;
19
+ /** Scroll to the given offset (clamped by the screen). */
20
+ scrollTo(offset: number): void;
21
+ }
22
+ export declare abstract class Screen {
23
+ app: App;
24
+ abstract onKey(key: Key): void;
25
+ onMouse(_ev: MouseEvent): void;
26
+ /**
27
+ * Describe the on-screen scrollbar so the app can drag it, or null when this
28
+ * screen has none / its content fits. Geometry is only valid right after a
29
+ * render, which is exactly when the app needs it.
30
+ */
31
+ scrollbar(): ScrollbarHandle | null;
32
+ /**
33
+ * Rows this screen draws as persistent "chrome" (an input box, key hints, …)
34
+ * that must be repainted on *every* frame, even when their contents are
35
+ * unchanged.
36
+ *
37
+ * The renderer normally emits only rows whose cells changed, which is what
38
+ * keeps redraws cheap. But the terminal can shift its own contents behind our
39
+ * back — ConPTY reflowing the buffer mid-resize, a size-race repaint, a stray
40
+ * scroll — and a diffed row would then stay stale forever (the classic
41
+ * "input box swallowed by the log" artefact). Redrawing this small strip by
42
+ * absolute position every frame makes it self-healing.
43
+ */
44
+ alwaysRepaintRows(): number[];
45
+ /**
46
+ * Handle a bracketed (or detected) paste. `text` already has its newlines
47
+ * flattened to spaces, so it can be inserted as a single line. Screens that
48
+ * have no text input can leave this as a no-op.
49
+ */
50
+ onPaste(_text: string): void;
51
+ abstract render(buf: Buffer): void;
52
+ /** Whether this screen currently shows a blinking text cursor. Used by the
53
+ * app to run (or stop) the cursor blink timer. */
54
+ showsCursor(): boolean;
55
+ onActivate(): void;
56
+ onDeactivate(): void;
57
+ }
package/dist/tray.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export interface TrayOptions {
2
+ /** Console address opened by a click. */
3
+ url: string;
4
+ /** Management endpoint that stops the service. */
5
+ stopUrl: string;
6
+ /** Bearer token for `stopUrl`. */
7
+ token: string;
8
+ /** Process id the icon belongs to; the icon exits with it. */
9
+ pid: number;
10
+ }
11
+ /** The icon itself. Only Windows has a notification area, and only it can run this. */
12
+ export declare function startBackgroundTray(options: TrayOptions): {
13
+ close(): void;
14
+ } | null;
package/dist/ui.d.ts ADDED
@@ -0,0 +1,123 @@
1
+ import type { Buffer } from "./terminal/buffer.js";
2
+ import { type RGB } from "./colors.js";
3
+ import type { ApprovalMode } from "./config.js";
4
+ /** One terminal row; shared geometry keeps rendering and hit testing aligned. */
5
+ export declare function approvalToggle(width: number, y: number, mode: ApprovalMode): {
6
+ x: number;
7
+ y: number;
8
+ w: number;
9
+ h: number;
10
+ text: string;
11
+ };
12
+ export declare function drawApprovalToggle(buf: Buffer, y: number, mode: ApprovalMode, focused?: boolean): void;
13
+ export declare function wrapText(text: string, width: number): string[];
14
+ export interface InputBoxOptions {
15
+ active?: boolean;
16
+ dimmed?: boolean;
17
+ placeholder?: string;
18
+ /** Blink phase of the text cursor. When false the cursor is hidden (the
19
+ * character underneath is shown normally). */
20
+ cursorOn?: boolean;
21
+ }
22
+ export interface InputWindow {
23
+ /** Index (in UTF-16 code points) of the first visible character. */
24
+ startCp: number;
25
+ /** Display columns before the visible window. */
26
+ prefixW: number;
27
+ /** Number of visible characters. */
28
+ visibleCount: number;
29
+ }
30
+ /**
31
+ * Compute which part of the input value is visible inside the box, given the
32
+ * cursor position. Shared by `drawInputBox` and mouse click handling so the
33
+ * rendered text and click-to-position-cursor always agree.
34
+ */
35
+ export declare function computeInputWindow(value: string, cursorPos: number, innerW: number): InputWindow;
36
+ /**
37
+ * Map a column inside the input box (0-based, relative to the inner area) to a
38
+ * cursor index in the full value.
39
+ */
40
+ export declare function cursorFromColumn(value: string, col: number, win: InputWindow): number;
41
+ export interface Rect {
42
+ x: number;
43
+ y: number;
44
+ w: number;
45
+ h: number;
46
+ }
47
+ export declare function inRect(px: number, py: number, r: Rect): boolean;
48
+ export declare function drawHint(buf: Buffer, y: number, text: string): void;
49
+ export interface ScrollbarOptions {
50
+ /** Colour of the groove. Defaults to `C.scrollTrack`. */
51
+ track?: RGB;
52
+ /** Colour of the thumb. Defaults to `C.scrollThumb`. */
53
+ thumb?: RGB;
54
+ }
55
+ /**
56
+ * Draw a vertical scrollbar in column `x`, spanning rows `top`..`bottom`
57
+ * (inclusive). It is a no-op when everything fits, so callers can render it
58
+ * unconditionally.
59
+ *
60
+ * total - total number of scrollable items/lines
61
+ * viewport - how many of them are visible at once
62
+ * offset - index of the topmost visible item (i.e. the current scrollTop)
63
+ *
64
+ * The thumb length is proportional to `viewport / total` (at least one cell)
65
+ * and its position is proportional to `offset / (total - viewport)`, matching
66
+ * the usual GUI scrollbar behaviour.
67
+ */
68
+ export interface ScrollbarGeometry {
69
+ /** Height of the groove in cells. */
70
+ trackH: number;
71
+ /** Height of the thumb in cells (at least 1). */
72
+ thumbH: number;
73
+ /** Largest allowed top row of the thumb, relative to the groove. */
74
+ maxThumbTop: number;
75
+ /** Largest valid scroll offset (total - viewport). */
76
+ maxOffset: number;
77
+ /** Current top row of the thumb, relative to the groove. */
78
+ thumbTop: number;
79
+ }
80
+ /**
81
+ * Geometry of a vertical scrollbar, shared by the drawing code and the app's
82
+ * hit-testing / dragging so the two can never disagree.
83
+ */
84
+ export declare function scrollbarGeometry(top: number, bottom: number, total: number, viewport: number, offset: number): ScrollbarGeometry;
85
+ /**
86
+ * Inverse of the thumb positioning above: the scroll offset that places the
87
+ * thumb's top at 'thumbTop'. Used while dragging the thumb; feeding the result
88
+ * back through scrollbarGeometry reproduces the same 'thumbTop'.
89
+ */
90
+ export declare function offsetFromThumbTop(g: ScrollbarGeometry, thumbTop: number): number;
91
+ export declare function drawScrollbar(buf: Buffer, x: number, top: number, bottom: number, total: number, viewport: number, offset: number, opts?: ScrollbarOptions): void;
92
+ export declare function drawInputBox(buf: Buffer, x: number, y: number, w: number, value: string, cursorPos: number, opts?: InputBoxOptions): void;
93
+ export declare function drawTabButton(buf: Buffer, x: number, y: number, w: number, label: string, selected: boolean, align?: "center" | "left"): void;
94
+ /** Label of the CLI entry point that opens the browser console's Pro workspace. */
95
+ export declare const PRO_MODE_LABEL = "\u542F\u7528Pro\u6A21\u5F0F";
96
+ /** Label of the CLI entry point that opens the browser console itself. */
97
+ export declare const WEB_MODE_LABEL = "\u6253\u5F00Web\u7AEF";
98
+ /** One of the small entry-point boxes painted in the top-left corner. */
99
+ export interface HeaderButton {
100
+ x: number;
101
+ y: number;
102
+ w: number;
103
+ h: number;
104
+ label: string;
105
+ }
106
+ /**
107
+ * The Pro-mode button: flush to the left edge with a single blank column, as
108
+ * the start screen expects.
109
+ */
110
+ export declare function proModeButton(width: number, height: number): HeaderButton | null;
111
+ /**
112
+ * The Web-mode button: glued to the right border of the Pro-mode button so the
113
+ * two read as one row of entry points. It starts where the Pro box ends, which
114
+ * also means it is hidden together with it on terminals too small for the row.
115
+ */
116
+ export declare function webModeButton(width: number, height: number): HeaderButton | null;
117
+ /**
118
+ * Draw one top-left button. It is painted as opaque chrome so content behind it
119
+ * can never bleed through; the label row turns into the highlight background
120
+ * while the pointer is over it.
121
+ */
122
+ export declare function drawHeaderButton(buf: Buffer, button: HeaderButton | null, hovered?: boolean): void;
123
+ export declare function centerVertically(totalHeight: number, contentHeight: number): number;
@@ -0,0 +1,71 @@
1
+ /** The published package whose registry version decides whether an update exists. */
2
+ export declare const UPDATE_PACKAGE = "@xingjisoft/neutrino";
3
+ /** What a user can run by hand when the automatic update fails. */
4
+ export declare const MANUAL_UPDATE_COMMAND = "npm install -g @xingjisoft/neutrino";
5
+ export interface UpdateStatus {
6
+ /** The version this process runs, read from its own package.json. */
7
+ currentVersion: string;
8
+ /** The published version; empty until a registry lookup has succeeded. */
9
+ latestVersion: string;
10
+ /** True only when the registry offers a strictly newer version. */
11
+ hasUpdate: boolean;
12
+ }
13
+ export interface UpdateCheck extends UpdateStatus {
14
+ /** Why the registry could not be read; the status then reports no update. */
15
+ error?: string;
16
+ }
17
+ export interface UpdateInstallResult {
18
+ /** Whether npm reported success. */
19
+ ok: boolean;
20
+ /** The version this run was updating to; empty when none was known. */
21
+ version: string;
22
+ /** Why the install failed; empty on success. */
23
+ error?: string;
24
+ }
25
+ export interface UpdateCheckOptions {
26
+ /**
27
+ * Ask the registry again even though an answer is already remembered. The
28
+ * settings page's manual check and the periodic background check need a
29
+ * fresh answer; waiting for the next start-up would tell them nothing new.
30
+ */
31
+ refresh?: boolean;
32
+ }
33
+ /**
34
+ * The last known answer, for the terminal screen and the Web console payload.
35
+ * Reading this never touches the network: the lookup runs once per process (see
36
+ * checkForUpdate) and costs nothing afterwards.
37
+ */
38
+ export declare function updateStatus(): UpdateStatus;
39
+ /**
40
+ * Ask npm which version is published and remember it. This never rejects: an
41
+ * offline machine, a missing npm or a registry hiccup only means "no update to
42
+ * announce", which is what the next start is for. Concurrent callers share the
43
+ * one lookup, and a successful answer is kept for the rest of the process so
44
+ * the terminal and the Web console cannot disagree.
45
+ *
46
+ * Start-up asks once and is served from that answer from then on. A caller that
47
+ * passes `refresh` — the settings button, the background timer — queries the
48
+ * registry again, so an answer from an hour ago cannot make a check look idle.
49
+ * A failed refresh changes nothing: the last good answer stays, and the caller
50
+ * learns what went wrong from `error`.
51
+ */
52
+ export declare function checkForUpdate(options?: UpdateCheckOptions): Promise<UpdateCheck>;
53
+ /**
54
+ * Install the latest published release with npm.
55
+ *
56
+ * The terminal and both Web workspaces can ask for this, so requests share one
57
+ * install instead of running npm twice over the same files. It never rejects:
58
+ * whoever asked gets a result and, on failure, the command to run by hand.
59
+ *
60
+ * The version this process runs is not replaced — the running code stays on
61
+ * disk until the user restarts — so callers must tell the user to restart.
62
+ */
63
+ export declare function installUpdate(): Promise<UpdateInstallResult>;
64
+ /**
65
+ * Semantic-version order: returns 1 when `left` is newer, -1 when older and 0
66
+ * when they are the same release. A version that cannot be parsed compares as
67
+ * equal, so a malformed registry answer can never claim an update is due.
68
+ */
69
+ export declare function compareVersions(left: string, right: string): number;
70
+ /** Whether `candidate` is a strictly newer release than `current`. */
71
+ export declare function isNewerVersion(candidate: string, current: string): boolean;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The periodic update check.
3
+ *
4
+ * A Web console can stay open for days, so the start-up lookup alone would
5
+ * never tell its windows about a release published afterwards. This module asks
6
+ * the registry again on a timer, in the process that serves those windows, and
7
+ * hands a release it has not seen before to whoever is listening: the console
8
+ * forwards it to every open browser (GET /api/update/events) and the first-open
9
+ * dialog reads the same answer.
10
+ */
11
+ import { type UpdateCheck } from "./update.js";
12
+ /** How often the background service asks the registry again. */
13
+ export declare const UPDATE_CHECK_INTERVAL_MS: number;
14
+ export interface UpdateWatchOptions {
15
+ /** Overridable so a test can watch a timer; the console never changes it. */
16
+ intervalMs?: number;
17
+ /**
18
+ * Called after every check, including the ones that found nothing or failed,
19
+ * so the background log can record a change. Never allowed to stop the timer.
20
+ */
21
+ onCheck?: (check: UpdateCheck) => void;
22
+ }
23
+ type Listener = (check: UpdateCheck) => void;
24
+ /**
25
+ * Listen for a release the periodic check finds. Returns the unsubscribe
26
+ * function.
27
+ *
28
+ * Only news is announced: a version this process already knows — from the
29
+ * start-up lookup, the settings button or an earlier tick — is not sent again,
30
+ * so the dialogs cannot reopen every half hour for a release the user has
31
+ * already declined.
32
+ */
33
+ export declare function subscribeUpdateAnnouncements(listener: Listener): () => void;
34
+ /**
35
+ * Start asking the registry every `intervalMs` (30 minutes by default).
36
+ *
37
+ * Idempotent, and the returned function stops the timer and leaves the module
38
+ * ready to start again. Checks run off the timer rather than at start-up: both
39
+ * the console and the terminal UI already look once when they start, and a
40
+ * second immediate lookup would only spend npm calls on the same answer.
41
+ */
42
+ export declare function startUpdateWatch(options?: UpdateWatchOptions): () => void;
43
+ export {};
@@ -0,0 +1,8 @@
1
+ /** Serialize human prompts without overwriting an unresolved decision. */
2
+ export declare class ApprovalQueue {
3
+ private active;
4
+ private waiting;
5
+ run(start: () => Promise<boolean | null>, signal?: AbortSignal): Promise<boolean | null>;
6
+ cancelWaiting(): void;
7
+ private next;
8
+ }
@@ -0,0 +1,59 @@
1
+ /** Serialize human prompts without overwriting an unresolved decision. */
2
+ export class ApprovalQueue {
3
+ active = false;
4
+ waiting = [];
5
+ run(start, signal) {
6
+ return new Promise((resolve, reject) => {
7
+ if (signal?.aborted) {
8
+ resolve(false);
9
+ return;
10
+ }
11
+ const item = {
12
+ cancel: () => {
13
+ const index = this.waiting.indexOf(item);
14
+ if (index >= 0) {
15
+ this.waiting.splice(index, 1);
16
+ signal?.removeEventListener("abort", item.cancel);
17
+ resolve(false);
18
+ }
19
+ },
20
+ run: () => {
21
+ signal?.removeEventListener("abort", item.cancel);
22
+ if (signal?.aborted) {
23
+ this.next();
24
+ resolve(false);
25
+ return;
26
+ }
27
+ this.active = true;
28
+ let result;
29
+ try {
30
+ result = start();
31
+ }
32
+ catch (error) {
33
+ this.next();
34
+ reject(error);
35
+ return;
36
+ }
37
+ result.then(value => { this.next(); resolve(value); }, error => { this.next(); reject(error); });
38
+ },
39
+ };
40
+ if (this.active) {
41
+ this.waiting.push(item);
42
+ signal?.addEventListener("abort", item.cancel, { once: true });
43
+ }
44
+ else
45
+ item.run();
46
+ });
47
+ }
48
+ cancelWaiting() {
49
+ for (const item of [...this.waiting])
50
+ item.cancel();
51
+ }
52
+ next() {
53
+ const next = this.waiting.shift();
54
+ if (next)
55
+ next.run();
56
+ else
57
+ this.active = false;
58
+ }
59
+ }
@@ -0,0 +1,14 @@
1
+ import type http from "node:http";
2
+ export declare const DEFAULT_WEB_PASSWORD = "114514";
3
+ export declare function validateWebPassword(password: unknown): asserts password is string;
4
+ /** Only a salted password hash is persisted; settings never return it. */
5
+ export declare function hashWebPassword(password: unknown): string;
6
+ /** Signed cookies survive restarts, but a password change invalidates all of them. */
7
+ export declare class WebAuth {
8
+ readonly enabled: boolean;
9
+ constructor(enabled: boolean);
10
+ authenticated(req: http.IncomingMessage): boolean;
11
+ login(password: unknown): Promise<boolean>;
12
+ remember(res: http.ServerResponse): void;
13
+ forget(res: http.ServerResponse): void;
14
+ }
@@ -0,0 +1,16 @@
1
+ import { type Mode } from "../config.js";
2
+ export declare const WEB_STATE_FILE: string;
3
+ /** How many finished conversations one workspace remembers, newest last. */
4
+ export declare const SEEN_LIMIT = 300;
5
+ /** Whether a value from a request is an identity this file can hold. */
6
+ export declare function isSeenIdentity(value: unknown): value is string;
7
+ /** The conversations this workspace has already shown the reader, oldest first. */
8
+ export declare function loadSeen(mode?: Mode): string[];
9
+ /**
10
+ * Record (`seen: true`) or forget (`seen: false`) the given conversations and
11
+ * write the file back. Returns the workspace's new record.
12
+ *
13
+ * Identities that are not valid are ignored rather than rejected: the route
14
+ * already validated them, so this is only a guard on what reaches the disk.
15
+ */
16
+ export declare function updateSeen(mode: Mode, identities: string[], seen: boolean): string[];
@@ -0,0 +1,34 @@
1
+ /** What the folder window reported. Closing the window is a normal outcome. */
2
+ export type FolderPick = {
3
+ picked: string;
4
+ } | {
5
+ cancelled: true;
6
+ } | {
7
+ unavailable: string;
8
+ };
9
+ /** One finished dialog process. */
10
+ export interface DialogRun {
11
+ /** Exit code, or null when the process never ran to completion. */
12
+ code: number | null;
13
+ stdout: string;
14
+ stderr: string;
15
+ /** The executable itself does not exist here. */
16
+ missing: boolean;
17
+ }
18
+ /**
19
+ * Runs one dialog command. Injectable so the Windows, macOS and Linux paths can
20
+ * be tested on any machine, without a desktop session to draw a window in.
21
+ */
22
+ export type DialogRunner = (command: string, args: string[], env: NodeJS.ProcessEnv, timeoutMs: number) => Promise<DialogRun>;
23
+ export interface FolderPickRequest {
24
+ /** Folder the window opens in. Ignored when it is not an existing folder. */
25
+ start?: string;
26
+ /** Window title, already translated by the console. */
27
+ title?: string;
28
+ platform?: NodeJS.Platform;
29
+ timeoutMs?: number;
30
+ runner?: DialogRunner;
31
+ env?: NodeJS.ProcessEnv;
32
+ }
33
+ /** Open the operating system's folder window and report what the user chose. */
34
+ export declare function pickFolder(request?: FolderPickRequest): Promise<FolderPick>;