@addai/node 0.4.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 (133) hide show
  1. package/README.md +96 -0
  2. package/dist/agent-auth.d.ts +9 -0
  3. package/dist/agent-auth.js +96 -0
  4. package/dist/attachments.d.ts +20 -0
  5. package/dist/attachments.js +164 -0
  6. package/dist/capabilities.d.ts +64 -0
  7. package/dist/capabilities.js +406 -0
  8. package/dist/claude-binary.d.ts +16 -0
  9. package/dist/claude-binary.js +145 -0
  10. package/dist/claude-config.d.ts +11 -0
  11. package/dist/claude-config.js +144 -0
  12. package/dist/claude-print.d.ts +35 -0
  13. package/dist/claude-print.js +312 -0
  14. package/dist/claude-spawn.d.ts +40 -0
  15. package/dist/claude-spawn.js +220 -0
  16. package/dist/cli.d.ts +2 -0
  17. package/dist/cli.js +137 -0
  18. package/dist/codex-binary.d.ts +4 -0
  19. package/dist/codex-binary.js +70 -0
  20. package/dist/codex-spawn.d.ts +47 -0
  21. package/dist/codex-spawn.js +320 -0
  22. package/dist/command-runner.d.ts +9 -0
  23. package/dist/command-runner.js +592 -0
  24. package/dist/config.d.ts +7 -0
  25. package/dist/config.js +38 -0
  26. package/dist/control-server.d.ts +5 -0
  27. package/dist/control-server.js +292 -0
  28. package/dist/diskguard.d.ts +96 -0
  29. package/dist/diskguard.js +435 -0
  30. package/dist/error-classify.d.ts +20 -0
  31. package/dist/error-classify.js +159 -0
  32. package/dist/events.d.ts +26 -0
  33. package/dist/events.js +97 -0
  34. package/dist/gemini-binary.d.ts +4 -0
  35. package/dist/gemini-binary.js +64 -0
  36. package/dist/gemini-spawn.d.ts +52 -0
  37. package/dist/gemini-spawn.js +369 -0
  38. package/dist/git-identity.d.ts +18 -0
  39. package/dist/git-identity.js +104 -0
  40. package/dist/grok-binary.d.ts +4 -0
  41. package/dist/grok-binary.js +70 -0
  42. package/dist/grok-spawn.d.ts +41 -0
  43. package/dist/grok-spawn.js +495 -0
  44. package/dist/guardrails.d.ts +32 -0
  45. package/dist/guardrails.js +125 -0
  46. package/dist/hang-watchdog.d.ts +17 -0
  47. package/dist/hang-watchdog.js +48 -0
  48. package/dist/harness-registry.d.ts +55 -0
  49. package/dist/harness-registry.js +180 -0
  50. package/dist/heartbeat.d.ts +13 -0
  51. package/dist/heartbeat.js +95 -0
  52. package/dist/index.d.ts +12 -0
  53. package/dist/index.js +440 -0
  54. package/dist/install.d.ts +63 -0
  55. package/dist/install.js +467 -0
  56. package/dist/jsonl-tail.d.ts +20 -0
  57. package/dist/jsonl-tail.js +192 -0
  58. package/dist/kimi-binary.d.ts +4 -0
  59. package/dist/kimi-binary.js +68 -0
  60. package/dist/kimi-spawn.d.ts +47 -0
  61. package/dist/kimi-spawn.js +319 -0
  62. package/dist/lockfile.d.ts +30 -0
  63. package/dist/lockfile.js +169 -0
  64. package/dist/mcp-config.d.ts +23 -0
  65. package/dist/mcp-config.js +133 -0
  66. package/dist/memory-capture.d.ts +20 -0
  67. package/dist/memory-capture.js +45 -0
  68. package/dist/memory-pack.d.ts +34 -0
  69. package/dist/memory-pack.js +171 -0
  70. package/dist/model-filter.d.ts +4 -0
  71. package/dist/model-filter.js +34 -0
  72. package/dist/node-health.d.ts +18 -0
  73. package/dist/node-health.js +74 -0
  74. package/dist/pairing.d.ts +20 -0
  75. package/dist/pairing.js +129 -0
  76. package/dist/paths.d.ts +9 -0
  77. package/dist/paths.js +48 -0
  78. package/dist/precompact-hook.d.ts +27 -0
  79. package/dist/precompact-hook.js +136 -0
  80. package/dist/projects.d.ts +25 -0
  81. package/dist/projects.js +255 -0
  82. package/dist/prompt-keystrokes.d.ts +3 -0
  83. package/dist/prompt-keystrokes.js +56 -0
  84. package/dist/pty-helper.d.ts +26 -0
  85. package/dist/pty-helper.js +156 -0
  86. package/dist/request-pump.d.ts +12 -0
  87. package/dist/request-pump.js +249 -0
  88. package/dist/run-retry.d.ts +28 -0
  89. package/dist/run-retry.js +81 -0
  90. package/dist/self-update.d.ts +75 -0
  91. package/dist/self-update.js +192 -0
  92. package/dist/session-runner.d.ts +130 -0
  93. package/dist/session-runner.js +2271 -0
  94. package/dist/sleep-detector.d.ts +11 -0
  95. package/dist/sleep-detector.js +32 -0
  96. package/dist/state-migrate.d.ts +8 -0
  97. package/dist/state-migrate.js +91 -0
  98. package/dist/store.d.ts +5 -0
  99. package/dist/store.js +82 -0
  100. package/dist/supabase-client.d.ts +15 -0
  101. package/dist/supabase-client.js +119 -0
  102. package/dist/time-context.d.ts +10 -0
  103. package/dist/time-context.js +71 -0
  104. package/dist/tokens.d.ts +4 -0
  105. package/dist/tokens.js +33 -0
  106. package/dist/tui/app.d.ts +35 -0
  107. package/dist/tui/app.js +82 -0
  108. package/dist/tui/console-capture.d.ts +13 -0
  109. package/dist/tui/console-capture.js +62 -0
  110. package/dist/tui/dashboard.d.ts +42 -0
  111. package/dist/tui/dashboard.js +205 -0
  112. package/dist/tui/data.d.ts +57 -0
  113. package/dist/tui/data.js +62 -0
  114. package/dist/tui/harnesses.d.ts +16 -0
  115. package/dist/tui/harnesses.js +354 -0
  116. package/dist/tui/render.d.ts +21 -0
  117. package/dist/tui/render.js +116 -0
  118. package/dist/tui/requests.d.ts +18 -0
  119. package/dist/tui/requests.js +127 -0
  120. package/dist/tui/run.d.ts +13 -0
  121. package/dist/tui/run.js +178 -0
  122. package/dist/tui/transcript.d.ts +39 -0
  123. package/dist/tui/transcript.js +234 -0
  124. package/dist/tui.d.ts +1 -0
  125. package/dist/tui.js +314 -0
  126. package/dist/types.d.ts +65 -0
  127. package/dist/types.js +3 -0
  128. package/dist/win.d.ts +67 -0
  129. package/dist/win.js +317 -0
  130. package/package.json +60 -0
  131. package/scripts/fix-pty-helper.js +28 -0
  132. package/scripts/precompact-capture.js +292 -0
  133. package/scripts/smoke-test.sh +74 -0
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ // Per-run silence watchdog, run INSIDE the daemon. A live daemon whose agent
3
+ // child wedges (e.g. a stuck MCP tool call — grok waiting on a slow flow that
4
+ // never returns) emits no events and, today, nothing kills it until the
5
+ // server-side watchdog fires at 10 minutes. This fires much sooner: if the
6
+ // run produces no output for `idleMs`, we kill the child so the run can fail
7
+ // (and, for a transient hang, retry) instead of dangling.
8
+ //
9
+ // It is reset on every event, so a legitimately streaming run is never
10
+ // touched — only genuine silence trips it. Timer fns are injectable so the
11
+ // behaviour is unit-testable without real waits.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.createHangWatchdog = createHangWatchdog;
14
+ function createHangWatchdog(opts) {
15
+ const setT = opts.setTimer ?? ((fn, ms) => { const t = setTimeout(fn, ms); t.unref?.(); return t; });
16
+ const clearT = opts.clearTimer ?? ((h) => clearTimeout(h));
17
+ let handle = null;
18
+ let stopped = false;
19
+ let fired = false;
20
+ const arm = () => {
21
+ if (stopped || fired)
22
+ return;
23
+ if (handle != null)
24
+ clearT(handle);
25
+ handle = setT(() => {
26
+ if (stopped || fired)
27
+ return;
28
+ fired = true;
29
+ handle = null;
30
+ try {
31
+ opts.onHang();
32
+ }
33
+ catch { /* child already gone */ }
34
+ }, opts.idleMsFn ? opts.idleMsFn() : opts.idleMs);
35
+ };
36
+ // Arm immediately: a spawn that produces NOTHING at all must also trip.
37
+ arm();
38
+ return {
39
+ touch() { arm(); },
40
+ stop() {
41
+ stopped = true;
42
+ if (handle != null) {
43
+ clearT(handle);
44
+ handle = null;
45
+ }
46
+ },
47
+ };
48
+ }
@@ -0,0 +1,55 @@
1
+ export type HarnessId = 'claude' | 'codex' | 'gemini' | 'grok' | 'kimi';
2
+ export type LoginStrategy = 'pty-url-code' | 'api-key' | 'on-device';
3
+ export interface HarnessLoginSpec {
4
+ strategy: LoginStrategy;
5
+ /** Extra env for the login process (e.g. NO_BROWSER for gemini oauth). */
6
+ env?: Record<string, string>;
7
+ /** argv appended to the harness binary for pty-url-code logins. */
8
+ loginArgs?: string[];
9
+ /** Extracts the auth URL from pty output (pty-url-code). */
10
+ urlPattern?: RegExp;
11
+ /** Output that means "paste the code now" (pty-url-code). */
12
+ codePromptPattern?: RegExp;
13
+ /** Output that means the login landed (pty-url-code; exit 0 also counts). */
14
+ successPattern?: RegExp;
15
+ /** Shown in Studio / TUI for on-device (and as the api-key hint). */
16
+ instructions: string;
17
+ }
18
+ export interface HarnessLogoutSpec {
19
+ /** argv appended to the harness binary (e.g. ['auth','logout']). */
20
+ cmd?: string[];
21
+ /** Credential files to remove, ~-relative to the home dir. */
22
+ files?: string[];
23
+ /** Env var lines to strip from an env file: [envFilePath, VAR_NAME][]. */
24
+ envStrip?: Array<[string, string]>;
25
+ }
26
+ export interface HarnessSpec {
27
+ id: HarnessId;
28
+ label: string;
29
+ /** npm package for `npm i -g` installs; null = not npm-installable. */
30
+ npmPackage: string | null;
31
+ findBinary: () => string | null;
32
+ /** Model-effort levels this harness accepts (empty = no effort knob). */
33
+ efforts: string[];
34
+ login: HarnessLoginSpec;
35
+ /** Additional remote login methods (Studio offers one button each).
36
+ * Selected by issuing the command with input.method === entry.method. */
37
+ extraLogins?: Array<{
38
+ method: string;
39
+ } & HarnessLoginSpec>;
40
+ /** How to log this harness out remotely; absent = on-device only. */
41
+ logout?: HarnessLogoutSpec;
42
+ }
43
+ export declare const HARNESSES: Record<HarnessId, HarnessSpec>;
44
+ export declare const HARNESS_IDS: HarnessId[];
45
+ /** Forget every finder's cached lookup — a probe after this sees the
46
+ * machine as it is now, not as it was at daemon start. (claude's finder
47
+ * is uncached.) */
48
+ export declare function resetBinaryCaches(): void;
49
+ export declare function harness(id: string): HarnessSpec | null;
50
+ export declare function isInstallable(id: HarnessId): boolean;
51
+ /** First URL in a pty output chunk, ANSI-stripped. Exported for tests. */
52
+ export declare function extractAuthUrl(spec: HarnessSpec, text: string): string | null;
53
+ export declare function seesCodePrompt(spec: HarnessSpec, text: string): boolean;
54
+ export declare function seesSuccess(spec: HarnessSpec, text: string): boolean;
55
+ export declare function stripAnsi(s: string): string;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ // Single source of truth for everything the daemon knows about agent
3
+ // harnesses: how to find them, how to install them, how to log them in,
4
+ // and which model-effort levels each supports. capabilities.ts reports
5
+ // from here (so Studio needs no duplicate registry), command-runner.ts
6
+ // executes from here, and the `harnesses` TUI renders from here.
7
+ //
8
+ // Honesty contract: `installable`/`login.strategy` describe what the
9
+ // daemon can ACTUALLY do remotely. Harnesses whose vendors ship custom
10
+ // installers or localhost-callback OAuth are marked on-device with human
11
+ // instructions — a truthful "go to the machine" beats a fake button.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.HARNESS_IDS = exports.HARNESSES = void 0;
14
+ exports.resetBinaryCaches = resetBinaryCaches;
15
+ exports.harness = harness;
16
+ exports.isInstallable = isInstallable;
17
+ exports.extractAuthUrl = extractAuthUrl;
18
+ exports.seesCodePrompt = seesCodePrompt;
19
+ exports.seesSuccess = seesSuccess;
20
+ exports.stripAnsi = stripAnsi;
21
+ const claude_binary_1 = require("./claude-binary");
22
+ const codex_binary_1 = require("./codex-binary");
23
+ const gemini_binary_1 = require("./gemini-binary");
24
+ const grok_binary_1 = require("./grok-binary");
25
+ const kimi_binary_1 = require("./kimi-binary");
26
+ exports.HARNESSES = {
27
+ claude: {
28
+ id: 'claude',
29
+ label: 'Claude Code',
30
+ npmPackage: '@anthropic-ai/claude-code',
31
+ findBinary: claude_binary_1.findClaudeBinary,
32
+ efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
33
+ login: {
34
+ strategy: 'pty-url-code',
35
+ loginArgs: ['auth', 'login'],
36
+ // "Visit: https://claude.ai/oauth/authorize?..." (exact copy varies
37
+ // by version, so match any https URL on a line after ~"browser"/"visit",
38
+ // falling back to the first URL seen).
39
+ urlPattern: /(https:\/\/[^\s]+)/,
40
+ codePromptPattern: /paste (?:the )?code|enter (?:the )?code|authorization code/i,
41
+ successPattern: /logged in|login successful|token (?:saved|created)|success/i,
42
+ instructions: 'Open the link in your own browser, approve, and paste the code back here.',
43
+ },
44
+ logout: { cmd: ['auth', 'logout'] },
45
+ },
46
+ codex: {
47
+ id: 'codex',
48
+ label: 'Codex',
49
+ npmPackage: '@openai/codex',
50
+ findBinary: codex_binary_1.findCodexBinary,
51
+ efforts: ['minimal', 'low', 'medium', 'high'],
52
+ login: {
53
+ strategy: 'api-key',
54
+ instructions: 'Paste an OpenAI API key (applied via `codex login --with-api-key`).',
55
+ },
56
+ extraLogins: [{
57
+ method: 'chatgpt',
58
+ strategy: 'pty-url-code',
59
+ loginArgs: ['login', '--device-auth'],
60
+ urlPattern: /(https:\/\/[^\s]+)/,
61
+ successPattern: /logged in|success/i,
62
+ instructions: 'Sign in with your ChatGPT account in the browser — the CLI completes by itself.',
63
+ }],
64
+ logout: { cmd: ['logout'] },
65
+ },
66
+ gemini: {
67
+ id: 'gemini',
68
+ label: 'Gemini CLI',
69
+ npmPackage: '@google/gemini-cli',
70
+ findBinary: gemini_binary_1.findGeminiBinary,
71
+ efforts: [],
72
+ login: {
73
+ strategy: 'api-key',
74
+ instructions: 'Paste a Gemini API key (stored in ~/.gemini/.env as GEMINI_API_KEY).',
75
+ },
76
+ extraLogins: [
77
+ // Google-account OAuth, fully remote: NO_BROWSER makes the CLI print
78
+ // the accounts.google.com URL and wait for the pasted auth code.
79
+ {
80
+ method: 'google',
81
+ strategy: 'pty-url-code',
82
+ loginArgs: [],
83
+ env: { NO_BROWSER: 'true' },
84
+ urlPattern: /(https:\/\/accounts\.google\.com[^\s]+)/,
85
+ codePromptPattern: /code/i,
86
+ successPattern: /authenticated|success|logged in/i,
87
+ instructions: 'Sign in with Google in your browser, then paste the authorization code back here.',
88
+ },
89
+ // Vertex AI express mode: GOOGLE_API_KEY + GOOGLE_GENAI_USE_VERTEXAI
90
+ // in ~/.gemini/.env — a paste-a-key flow, no device interaction.
91
+ {
92
+ method: 'vertex',
93
+ strategy: 'api-key',
94
+ instructions: 'Paste a Vertex AI (express mode) API key — stored in ~/.gemini/.env as GOOGLE_API_KEY with GOOGLE_GENAI_USE_VERTEXAI=true.',
95
+ },
96
+ ],
97
+ logout: {
98
+ files: ['.gemini/oauth_creds.json', '.gemini/google_accounts.json'],
99
+ envStrip: [
100
+ ['.gemini/.env', 'GEMINI_API_KEY'],
101
+ ['.gemini/.env', 'GOOGLE_API_KEY'],
102
+ ['.gemini/.env', 'GOOGLE_GENAI_USE_VERTEXAI'],
103
+ ],
104
+ },
105
+ },
106
+ grok: {
107
+ id: 'grok',
108
+ label: 'Grok CLI',
109
+ npmPackage: null,
110
+ findBinary: grok_binary_1.findGrokBinary,
111
+ efforts: [],
112
+ login: {
113
+ strategy: 'pty-url-code',
114
+ loginArgs: ['login', '--device-auth'],
115
+ urlPattern: /(https:\/\/[^\s]+)/,
116
+ successPattern: /logged in|success|welcome/i,
117
+ instructions: 'Approve the sign-in in your browser — the CLI completes by itself. ' +
118
+ '(Install still happens on the device via the xAI installer.)',
119
+ },
120
+ logout: { files: ['.grok/auth.json'] },
121
+ },
122
+ kimi: {
123
+ id: 'kimi',
124
+ label: 'Kimi CLI',
125
+ npmPackage: '@moonshot-ai/kimi-code',
126
+ findBinary: kimi_binary_1.findKimiBinary,
127
+ efforts: [],
128
+ login: {
129
+ strategy: 'pty-url-code',
130
+ loginArgs: ['login'],
131
+ // Device-code flow: prints a verification URL (and usually a short
132
+ // user code) then polls — no paste-back into the CLI needed.
133
+ urlPattern: /(https:\/\/[^\s]+)/,
134
+ successPattern: /logged in|login successful|success|welcome/i,
135
+ instructions: 'Open the link in your browser and approve — the CLI completes by itself. ' +
136
+ '(MOONSHOT_API_KEY in the environment also works.)',
137
+ },
138
+ logout: { files: ['.kimi-code/credentials/kimi-code.json'] },
139
+ },
140
+ };
141
+ exports.HARNESS_IDS = Object.keys(exports.HARNESSES);
142
+ /** Forget every finder's cached lookup — a probe after this sees the
143
+ * machine as it is now, not as it was at daemon start. (claude's finder
144
+ * is uncached.) */
145
+ function resetBinaryCaches() {
146
+ (0, codex_binary_1.resetCache)();
147
+ (0, gemini_binary_1.resetCache)();
148
+ (0, grok_binary_1.resetCache)();
149
+ (0, kimi_binary_1.resetCache)();
150
+ }
151
+ function harness(id) {
152
+ return exports.HARNESSES[id] ?? null;
153
+ }
154
+ function isInstallable(id) {
155
+ return exports.HARNESSES[id].npmPackage !== null;
156
+ }
157
+ /** First URL in a pty output chunk, ANSI-stripped. Exported for tests. */
158
+ function extractAuthUrl(spec, text) {
159
+ if (!spec.login.urlPattern)
160
+ return null;
161
+ const clean = stripAnsi(text);
162
+ const m = spec.login.urlPattern.exec(clean);
163
+ if (!m)
164
+ return null;
165
+ // Trim trailing punctuation the regex may have swallowed.
166
+ return m[1].replace(/[)\].,'"]+$/, '');
167
+ }
168
+ function seesCodePrompt(spec, text) {
169
+ return !!spec.login.codePromptPattern?.test(stripAnsi(text));
170
+ }
171
+ function seesSuccess(spec, text) {
172
+ return !!spec.login.successPattern?.test(stripAnsi(text));
173
+ }
174
+ function stripAnsi(s) {
175
+ // CSI sequences, then OSC sequences (BEL- or ST-terminated).
176
+ // eslint-disable-next-line no-control-regex
177
+ return s
178
+ .replace(/\x1b\[[0-9;?]*[ -\/]*[@-~]/g, '')
179
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g, '');
180
+ }
@@ -0,0 +1,13 @@
1
+ export declare function setPendingCommandsHook(cb: () => void): void;
2
+ /** Announce a clean shutdown so the server flips us offline immediately
3
+ * (status='offline') instead of waiting ~90s for last_seen_at to age out —
4
+ * lets a planned restart fail over instantly. Best-effort; the stale-window
5
+ * is the backstop if this call doesn't land. */
6
+ export declare function goOffline(): Promise<void>;
7
+ /** Push one heartbeat immediately, out of band. Used on wake-from-sleep so
8
+ * the server stops treating a just-resumed runtime as offline without
9
+ * waiting for the next 30s interval. Safe to call anytime; no-ops the same
10
+ * way tick() does when unpaired or circuit-broken. */
11
+ export declare function beat(): Promise<void>;
12
+ export declare function start(): void;
13
+ export declare function stop(): void;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ // Periodic heartbeat — pushes a fresh capabilities probe up to Supabase
3
+ // every HEARTBEAT_INTERVAL_MS. Run as a long-lived loop while the daemon
4
+ // is paired.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.setPendingCommandsHook = setPendingCommandsHook;
7
+ exports.goOffline = goOffline;
8
+ exports.beat = beat;
9
+ exports.start = start;
10
+ exports.stop = stop;
11
+ const supabase_client_1 = require("./supabase-client");
12
+ const store_1 = require("./store");
13
+ const capabilities_1 = require("./capabilities");
14
+ const config_1 = require("./config");
15
+ let timer = null;
16
+ let stopped = false;
17
+ /** Hook invoked when the server reports queued runtime_commands. Wired by
18
+ * index.ts (keeps heartbeat ← command-runner import direction acyclic). */
19
+ let onPendingCommands = null;
20
+ function setPendingCommandsHook(cb) {
21
+ onPendingCommands = cb;
22
+ }
23
+ async function tick() {
24
+ // Skip if the daemon-wide circuit breaker is open. Heartbeats are
25
+ // safe to miss — vault marks the runtime offline after ~90s; the
26
+ // breaker pause is shorter than that so we resume in time.
27
+ if ((0, supabase_client_1.rpcShouldSkip)())
28
+ return;
29
+ const pairing = (0, store_1.readPairing)();
30
+ if (!pairing)
31
+ return;
32
+ try {
33
+ const capabilities = await (0, capabilities_1.probeCapabilities)();
34
+ // Older servers return void; newer ones return {pending_commands: n}
35
+ // so the daemon can drain install/login commands without a poller.
36
+ const res = await (0, supabase_client_1.rpc)('runtime_heartbeat', {
37
+ p_token: pairing.daemonToken,
38
+ p_capabilities: capabilities,
39
+ });
40
+ if (res && typeof res.pending_commands === 'number' && res.pending_commands > 0) {
41
+ onPendingCommands?.();
42
+ }
43
+ }
44
+ catch (err) {
45
+ if (err instanceof supabase_client_1.RpcError && err.status === 401) {
46
+ // Server-side has revoked us (or 42501 unauthorized). Clear our
47
+ // local pairing so the next launch goes back through the code
48
+ // flow.
49
+ console.error('[heartbeat] runtime unpaired by server; clearing state.');
50
+ (0, store_1.clearPairing)();
51
+ stop();
52
+ }
53
+ else {
54
+ // Soft fail; we'll try again next tick.
55
+ console.error('[heartbeat] failed:', err.message || err);
56
+ }
57
+ }
58
+ }
59
+ /** Announce a clean shutdown so the server flips us offline immediately
60
+ * (status='offline') instead of waiting ~90s for last_seen_at to age out —
61
+ * lets a planned restart fail over instantly. Best-effort; the stale-window
62
+ * is the backstop if this call doesn't land. */
63
+ async function goOffline() {
64
+ const pairing = (0, store_1.readPairing)();
65
+ if (!pairing)
66
+ return;
67
+ try {
68
+ await (0, supabase_client_1.rpc)('runtime_go_offline', { p_token: pairing.daemonToken });
69
+ }
70
+ catch (err) {
71
+ console.error('[shutdown] go-offline failed:', err.message || err);
72
+ }
73
+ }
74
+ /** Push one heartbeat immediately, out of band. Used on wake-from-sleep so
75
+ * the server stops treating a just-resumed runtime as offline without
76
+ * waiting for the next 30s interval. Safe to call anytime; no-ops the same
77
+ * way tick() does when unpaired or circuit-broken. */
78
+ async function beat() {
79
+ await tick();
80
+ }
81
+ function start() {
82
+ if (timer || stopped)
83
+ return;
84
+ // Fire one immediately so vault sees the runtime online without waiting
85
+ // 30s for the first interval.
86
+ void tick();
87
+ timer = setInterval(() => { void tick(); }, config_1.HEARTBEAT_INTERVAL_MS);
88
+ }
89
+ function stop() {
90
+ stopped = true;
91
+ if (timer) {
92
+ clearInterval(timer);
93
+ timer = null;
94
+ }
95
+ }
@@ -0,0 +1,12 @@
1
+ declare const VERSION: string;
2
+ export interface RunningRuntime {
3
+ port: number;
4
+ pid: number;
5
+ stop(): Promise<void>;
6
+ }
7
+ /**
8
+ * Boot the daemon. If we don't have a pairing yet, run the user through
9
+ * the 5-letter code flow first; once paired, kick off the heartbeat.
10
+ */
11
+ export declare function start(): Promise<RunningRuntime>;
12
+ export { VERSION };