@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,11 @@
1
+ export declare function isSleepGap(intervalMs: number, actualElapsedMs: number, thresholdMs: number): boolean;
2
+ export interface SleepMonitorOpts {
3
+ intervalMs: number;
4
+ thresholdMs: number;
5
+ onWake: (gapMs: number) => void;
6
+ now?: () => number;
7
+ }
8
+ export interface SleepMonitor {
9
+ stop(): void;
10
+ }
11
+ export declare function startSleepMonitor(opts: SleepMonitorOpts): SleepMonitor;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // Detects that the machine slept (laptop lid closed mid-run — the most
3
+ // likely cause of Hannah's d1dddb15: her runtime is a MacBook). A suspended
4
+ // process's timers don't fire; on wake they all fire at once, far later than
5
+ // scheduled. We schedule a probe every `intervalMs`; if a probe fires more
6
+ // than `thresholdMs` late, wall-clock jumped forward while we were suspended
7
+ // — i.e. we slept. On wake we re-heartbeat immediately (so the server doesn't
8
+ // keep us marked offline) and reclaim our own in-flight runs (they went stale
9
+ // while suspended) so they re-dispatch, to a backup if we're still groggy.
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.isSleepGap = isSleepGap;
12
+ exports.startSleepMonitor = startSleepMonitor;
13
+ function isSleepGap(intervalMs, actualElapsedMs, thresholdMs) {
14
+ return actualElapsedMs - intervalMs > thresholdMs;
15
+ }
16
+ function startSleepMonitor(opts) {
17
+ const now = opts.now ?? Date.now;
18
+ let last = now();
19
+ const timer = setInterval(() => {
20
+ const t = now();
21
+ const elapsed = t - last;
22
+ last = t;
23
+ if (isSleepGap(opts.intervalMs, elapsed, opts.thresholdMs)) {
24
+ try {
25
+ opts.onWake(elapsed);
26
+ }
27
+ catch { /* best effort */ }
28
+ }
29
+ }, opts.intervalMs);
30
+ timer.unref?.();
31
+ return { stop() { clearInterval(timer); } };
32
+ }
@@ -0,0 +1,8 @@
1
+ export declare function migrateLegacyStateDir(opts?: {
2
+ home?: string;
3
+ legacy?: string;
4
+ }): {
5
+ migrated: boolean;
6
+ copied: string[];
7
+ moved: string[];
8
+ };
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ // One-time move from ~/.entities-runtime to ~/.ainode (the +Ai Node rename).
3
+ //
4
+ // Two classes of state, handled differently on purpose:
5
+ //
6
+ // * state.json / runtime.json are tiny and precious — COPIED, so the
7
+ // legacy dir still holds a working pairing token if we roll back.
8
+ // * tools/ (~100 MB of harness installs) and projects/ (git clones, 1.5 GB
9
+ // on this author's box) are MOVED. Copying them would duplicate gigabytes
10
+ // on every node and make first boot crawl. Both live under $HOME, so the
11
+ // rename is an instant same-filesystem inode move; a cross-device EXDEV
12
+ // falls back to a copy.
13
+ //
14
+ // The whole migration is skipped once the new home has its own state.json —
15
+ // a node that already migrated (or paired fresh) must never be clobbered by
16
+ // stale legacy state.
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.migrateLegacyStateDir = migrateLegacyStateDir;
52
+ const fs = __importStar(require("fs"));
53
+ const path = __importStar(require("path"));
54
+ const paths_1 = require("./paths");
55
+ const COPY_ENTRIES = ['state.json', 'runtime.json'];
56
+ const MOVE_ENTRIES = ['tools', 'sessions', 'projects'];
57
+ function migrateLegacyStateDir(opts = {}) {
58
+ const home = opts.home ?? paths_1.RUNTIME_HOME;
59
+ const legacy = opts.legacy ?? paths_1.LEGACY_RUNTIME_HOME;
60
+ const copied = [];
61
+ const moved = [];
62
+ if (!fs.existsSync(legacy))
63
+ return { migrated: false, copied, moved };
64
+ if (fs.existsSync(path.join(home, 'state.json')))
65
+ return { migrated: false, copied, moved };
66
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 });
67
+ for (const entry of COPY_ENTRIES) {
68
+ const from = path.join(legacy, entry);
69
+ if (!fs.existsSync(from))
70
+ continue;
71
+ fs.cpSync(from, path.join(home, entry), { recursive: true });
72
+ copied.push(entry);
73
+ }
74
+ for (const entry of MOVE_ENTRIES) {
75
+ const from = path.join(legacy, entry);
76
+ const to = path.join(home, entry);
77
+ if (!fs.existsSync(from) || fs.existsSync(to))
78
+ continue;
79
+ try {
80
+ fs.renameSync(from, to);
81
+ }
82
+ catch (err) {
83
+ if (err.code !== 'EXDEV')
84
+ throw err;
85
+ fs.cpSync(from, to, { recursive: true });
86
+ fs.rmSync(from, { recursive: true, force: true });
87
+ }
88
+ moved.push(entry);
89
+ }
90
+ return { migrated: copied.length > 0 || moved.length > 0, copied, moved };
91
+ }
@@ -0,0 +1,5 @@
1
+ import type { RuntimePairingState } from './types';
2
+ export declare function readPairing(): RuntimePairingState | null;
3
+ export declare function writePairing(state: RuntimePairingState): void;
4
+ export declare function clearPairing(): void;
5
+ export declare function isPaired(): boolean;
package/dist/store.js ADDED
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ // Pairing state: ~/.ainode/state.json.
3
+ //
4
+ // Once paired with +Ai (Supabase), this file holds the runtimeId +
5
+ // daemonToken the daemon uses to authenticate to RPCs. While unpaired
6
+ // it doesn't exist; the CLI's first-run flow creates it.
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.readPairing = readPairing;
42
+ exports.writePairing = writePairing;
43
+ exports.clearPairing = clearPairing;
44
+ exports.isPaired = isPaired;
45
+ const fs = __importStar(require("fs"));
46
+ const paths_1 = require("./paths");
47
+ const win_1 = require("./win");
48
+ function ensureHome() {
49
+ if (!fs.existsSync(paths_1.RUNTIME_HOME)) {
50
+ fs.mkdirSync(paths_1.RUNTIME_HOME, { recursive: true, mode: 0o700 });
51
+ }
52
+ }
53
+ function readPairing() {
54
+ if (!fs.existsSync(paths_1.RUNTIME_STATE_FILE))
55
+ return null;
56
+ try {
57
+ const raw = fs.readFileSync(paths_1.RUNTIME_STATE_FILE, 'utf-8');
58
+ const parsed = JSON.parse(raw);
59
+ if (!parsed.runtimeId || !parsed.daemonToken)
60
+ return null;
61
+ return parsed;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ function writePairing(state) {
68
+ ensureHome();
69
+ const tmp = paths_1.RUNTIME_STATE_FILE + '.tmp';
70
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 0o600 });
71
+ // rename-over-open-file fails EPERM/EBUSY on Windows — retried there.
72
+ (0, win_1.safeRenameSync)(tmp, paths_1.RUNTIME_STATE_FILE);
73
+ }
74
+ function clearPairing() {
75
+ try {
76
+ fs.unlinkSync(paths_1.RUNTIME_STATE_FILE);
77
+ }
78
+ catch { /* already gone */ }
79
+ }
80
+ function isPaired() {
81
+ return readPairing() !== null;
82
+ }
@@ -0,0 +1,15 @@
1
+ export declare class RpcError extends Error {
2
+ readonly code: string | undefined;
3
+ readonly status: number;
4
+ constructor(message: string, status: number, code?: string);
5
+ }
6
+ export declare function rpcBreakerOpen(): boolean;
7
+ /** Pollers should call this at the top of each tick. If it returns true,
8
+ * skip the RPC and wait for the next tick — the breaker is open and the
9
+ * endpoint is presumed unhealthy. */
10
+ export declare function rpcShouldSkip(): boolean;
11
+ /**
12
+ * Call a Supabase RPC and return its JSON body.
13
+ * Throws RpcError if the request fails or the server returns 4xx/5xx.
14
+ */
15
+ export declare function rpc<T>(name: string, args?: Record<string, unknown>): Promise<T>;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ // Tiny PostgREST RPC client. We hit Supabase RPC endpoints directly
3
+ // instead of pulling in @supabase/supabase-js — the daemon only needs
4
+ // a handful of calls and shipping the full SDK is unnecessary weight.
5
+ //
6
+ // Auth model: the anon JWT in config.ts gives us access to call
7
+ // SECURITY DEFINER RPCs. Each RPC validates the caller's identity
8
+ // via its arguments (pair_code or daemon_token).
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.RpcError = void 0;
11
+ exports.rpcBreakerOpen = rpcBreakerOpen;
12
+ exports.rpcShouldSkip = rpcShouldSkip;
13
+ exports.rpc = rpc;
14
+ const config_1 = require("./config");
15
+ class RpcError extends Error {
16
+ code;
17
+ status;
18
+ constructor(message, status, code) {
19
+ super(message);
20
+ this.code = code;
21
+ this.status = status;
22
+ }
23
+ }
24
+ exports.RpcError = RpcError;
25
+ // Hard timeout for any single RPC call. 10s used to be the value but it
26
+ // caused a death spiral when Supabase got slow: each stalled call held
27
+ // a pooler connection for the full 10s, the pump kept tick()-ing and
28
+ // piling on more, and the connection pool exhausted. 4s frees the
29
+ // connection fast enough that pooler stays healthy even if individual
30
+ // calls fail — the daemon's pickup loop already exponentially backs
31
+ // off on failure, so trading a longer timeout for fewer held
32
+ // connections is the right call here.
33
+ const DEFAULT_RPC_TIMEOUT_MS = 4_000;
34
+ // Shared circuit-breaker state. Every RPC failure increments the counter;
35
+ // every success resets it. When the counter passes BREAKER_THRESHOLD,
36
+ // every poller in the daemon (pump, projects, heartbeat) is expected to
37
+ // pause new RPCs until BREAKER_PAUSE_MS has elapsed, then probe with a
38
+ // SINGLE rpc — if that succeeds, traffic resumes; if it fails the breaker
39
+ // re-opens with the same pause. The whole point is: when Supabase melts,
40
+ // we don't keep hammering it from 3 separate timers and amplify the
41
+ // outage. This module is the single source of truth; pollers call
42
+ // `breakerCheckBeforeRpc()` at the top of their tick.
43
+ const BREAKER_THRESHOLD = 4;
44
+ const BREAKER_PAUSE_MS = 30_000;
45
+ let consecutiveRpcFailures = 0;
46
+ let breakerOpenUntil = 0;
47
+ function rpcBreakerOpen() {
48
+ return Date.now() < breakerOpenUntil;
49
+ }
50
+ /** Pollers should call this at the top of each tick. If it returns true,
51
+ * skip the RPC and wait for the next tick — the breaker is open and the
52
+ * endpoint is presumed unhealthy. */
53
+ function rpcShouldSkip() {
54
+ return rpcBreakerOpen();
55
+ }
56
+ /**
57
+ * Call a Supabase RPC and return its JSON body.
58
+ * Throws RpcError if the request fails or the server returns 4xx/5xx.
59
+ */
60
+ async function rpc(name, args = {}) {
61
+ const url = `${config_1.SUPABASE_URL}/rest/v1/rpc/${name}`;
62
+ const ac = new AbortController();
63
+ const timer = setTimeout(() => ac.abort(), DEFAULT_RPC_TIMEOUT_MS);
64
+ let res;
65
+ try {
66
+ res = await fetch(url, {
67
+ method: 'POST',
68
+ headers: {
69
+ apikey: config_1.SUPABASE_ANON_KEY,
70
+ authorization: `Bearer ${config_1.SUPABASE_ANON_KEY}`,
71
+ 'content-type': 'application/json',
72
+ accept: 'application/json',
73
+ },
74
+ body: JSON.stringify(args),
75
+ signal: ac.signal,
76
+ });
77
+ }
78
+ catch (err) {
79
+ clearTimeout(timer);
80
+ // Timeout + network errors both indicate Supabase distress — open the
81
+ // circuit breaker after a few in a row.
82
+ consecutiveRpcFailures++;
83
+ if (consecutiveRpcFailures >= BREAKER_THRESHOLD) {
84
+ breakerOpenUntil = Date.now() + BREAKER_PAUSE_MS;
85
+ }
86
+ if (err?.name === 'AbortError') {
87
+ throw new RpcError(`rpc ${name} timed out after ${DEFAULT_RPC_TIMEOUT_MS}ms`, 0, 'timeout');
88
+ }
89
+ throw err;
90
+ }
91
+ clearTimeout(timer);
92
+ const text = await res.text();
93
+ let parsed = undefined;
94
+ if (text) {
95
+ try {
96
+ parsed = JSON.parse(text);
97
+ }
98
+ catch { /* leave as undefined */ }
99
+ }
100
+ if (!res.ok) {
101
+ const errBody = parsed;
102
+ // 4xx other than 429 = "our request is wrong"; don't count those as
103
+ // breaker failures (they don't indicate Supabase distress). 5xx, 429,
104
+ // 502/503/504 + network errors (caught above) do.
105
+ if (res.status >= 500 || res.status === 429) {
106
+ consecutiveRpcFailures++;
107
+ if (consecutiveRpcFailures >= BREAKER_THRESHOLD) {
108
+ breakerOpenUntil = Date.now() + BREAKER_PAUSE_MS;
109
+ }
110
+ }
111
+ throw new RpcError(errBody?.message ?? text ?? `rpc ${name} failed`, res.status, errBody?.code);
112
+ }
113
+ // Success — close the breaker.
114
+ if (consecutiveRpcFailures > 0) {
115
+ consecutiveRpcFailures = 0;
116
+ breakerOpenUntil = 0;
117
+ }
118
+ return parsed;
119
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The system-prompt block. `now`/`timeZone` are injectable for tests; callers
3
+ * omit them to get the machine clock and zone at spawn time.
4
+ */
5
+ export declare function currentTimeBlock(now?: Date, timeZone?: string): string;
6
+ /**
7
+ * Append the time block to a request's system prompt (or stand alone when the
8
+ * request has none). Used at every agent spawn site in session-runner.
9
+ */
10
+ export declare function appendTimeContext(systemPrompt: string | null | undefined, now?: Date, timeZone?: string): string;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ // Current-time context injection.
3
+ //
4
+ // Entities had no "now" signal beyond a bare date: the studio-built system
5
+ // prompt carries no clock, and the timestamps they DO see (chat history,
6
+ // background-activity lines) are UTC. With nothing local to anchor on, the
7
+ // model read the UTC clock and called it UK time — a constant 1-hour error
8
+ // all through BST, and an offset error year-round for any non-UTC workspace.
9
+ //
10
+ // The daemon machine's clock and zoneinfo are the source of truth (verified
11
+ // correct where this bit), so every spawned agent turn gets a system-prompt
12
+ // block stating the local wall-clock time, timezone and UTC offset at spawn.
13
+ // Each request is a fresh CLI invocation, so the block is per-turn fresh.
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.currentTimeBlock = currentTimeBlock;
16
+ exports.appendTimeContext = appendTimeContext;
17
+ function machineTimeZone() {
18
+ try {
19
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
20
+ }
21
+ catch {
22
+ return 'UTC';
23
+ }
24
+ }
25
+ function formatParts(now, timeZone) {
26
+ const parts = new Intl.DateTimeFormat('en-GB', {
27
+ timeZone,
28
+ weekday: 'long', day: 'numeric', month: 'long', year: 'numeric',
29
+ hour: '2-digit', minute: '2-digit', hourCycle: 'h23',
30
+ timeZoneName: 'short',
31
+ }).formatToParts(now);
32
+ const get = (type) => parts.find((p) => p.type === type)?.value ?? '';
33
+ const stamp = `${get('weekday')} ${get('day')} ${get('month')} ${get('year')}, ${get('hour')}:${get('minute')}`;
34
+ const zone = get('timeZoneName');
35
+ const offsetParts = new Intl.DateTimeFormat('en-GB', {
36
+ timeZone, hour: '2-digit', minute: '2-digit', timeZoneName: 'longOffset',
37
+ }).formatToParts(now);
38
+ const rawOffset = offsetParts.find((p) => p.type === 'timeZoneName')?.value ?? 'GMT';
39
+ // 'GMT+01:00' → 'UTC+01:00'; bare 'GMT' (zero offset) → 'UTC+00:00'.
40
+ const offset = rawOffset === 'GMT' ? 'UTC+00:00' : rawOffset.replace(/^GMT/, 'UTC');
41
+ return { stamp, zone, offset };
42
+ }
43
+ /**
44
+ * The system-prompt block. `now`/`timeZone` are injectable for tests; callers
45
+ * omit them to get the machine clock and zone at spawn time.
46
+ */
47
+ function currentTimeBlock(now = new Date(), timeZone = machineTimeZone()) {
48
+ let tz = timeZone;
49
+ let fmt;
50
+ try {
51
+ fmt = formatParts(now, tz);
52
+ }
53
+ catch {
54
+ tz = 'UTC';
55
+ fmt = formatParts(now, tz);
56
+ }
57
+ return [
58
+ '## Current date & time',
59
+ `It is now ${fmt.stamp} ${fmt.zone} (${tz}, ${fmt.offset}). Treat this as "now" — it is the local wall-clock time where you are running, and the time to use when telling users what time it is or when something will happen.`,
60
+ 'Timestamps on chat messages, records and logs may be stored in UTC — convert them to this timezone before repeating them to users. Never read a UTC timestamp as if it were local time.',
61
+ ].join('\n');
62
+ }
63
+ /**
64
+ * Append the time block to a request's system prompt (or stand alone when the
65
+ * request has none). Used at every agent spawn site in session-runner.
66
+ */
67
+ function appendTimeContext(systemPrompt, now, timeZone) {
68
+ const block = currentTimeBlock(now, timeZone);
69
+ const sp = (systemPrompt ?? '').trim();
70
+ return sp ? `${systemPrompt}\n\n${block}` : block;
71
+ }
@@ -0,0 +1,4 @@
1
+ export declare function generateEndpointToken(): string;
2
+ export declare function hashToken(token: string): string;
3
+ export declare function tokensMatch(token: string, expectedHash: string): boolean;
4
+ export declare function tokenPrefix(token: string): string;
package/dist/tokens.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateEndpointToken = generateEndpointToken;
4
+ exports.hashToken = hashToken;
5
+ exports.tokensMatch = tokensMatch;
6
+ exports.tokenPrefix = tokenPrefix;
7
+ const crypto_1 = require("crypto");
8
+ const ENDPOINT_PREFIX = 'cf_';
9
+ function generateEndpointToken() {
10
+ return ENDPOINT_PREFIX + (0, crypto_1.randomBytes)(32).toString('hex');
11
+ }
12
+ function hashToken(token) {
13
+ return (0, crypto_1.createHash)('sha256').update(token).digest('hex');
14
+ }
15
+ function tokensMatch(token, expectedHash) {
16
+ if (!token || !expectedHash)
17
+ return false;
18
+ let actual;
19
+ let expected;
20
+ try {
21
+ actual = Buffer.from(hashToken(token), 'hex');
22
+ expected = Buffer.from(expectedHash, 'hex');
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ if (actual.length !== expected.length || actual.length === 0)
28
+ return false;
29
+ return (0, crypto_1.timingSafeEqual)(actual, expected);
30
+ }
31
+ function tokenPrefix(token) {
32
+ return token.slice(0, 8);
33
+ }
@@ -0,0 +1,35 @@
1
+ export interface Key {
2
+ name?: string;
3
+ ctrl?: boolean;
4
+ shift?: boolean;
5
+ sequence?: string;
6
+ }
7
+ export interface Screen {
8
+ id: string;
9
+ title: string;
10
+ render(width: number): string[];
11
+ onKey?(key: Key): void | Promise<void>;
12
+ poll?(): Promise<void>;
13
+ pollMs?(): number;
14
+ /** True while the screen is consuming raw text (a filter prompt), so the
15
+ * global q-to-go-back binding must not steal the keystroke. */
16
+ capturesKeys?(): boolean;
17
+ }
18
+ export interface AppHost {
19
+ push(s: Screen): void;
20
+ pop(): void;
21
+ replace(s: Screen): void;
22
+ current(): Screen;
23
+ depth(): number;
24
+ quit(): void;
25
+ redraw(): void;
26
+ note(msg: string | null): void;
27
+ }
28
+ export declare function createApp(root: Screen, hooks?: {
29
+ onQuit?: () => void;
30
+ onRedraw?: () => void;
31
+ }): AppHost & {
32
+ handleKey(k: Key): Promise<void>;
33
+ frame(width: number): string[];
34
+ lastError(): string | null;
35
+ };
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ // Screen stack + key dispatch for the node console.
3
+ //
4
+ // Deliberately free of terminal I/O so the whole interaction model is
5
+ // unit-testable: run.ts owns stdin/stdout and calls handleKey/frame.
6
+ //
7
+ // The hard rule this file enforces: a screen may throw, and the daemon
8
+ // must not care. Every screen call is fenced.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.createApp = createApp;
11
+ const render_1 = require("./render");
12
+ function createApp(root, hooks = {}) {
13
+ const stack = [root];
14
+ let error = null;
15
+ let noteMsg = null;
16
+ const host = {
17
+ push(s) { stack.push(s); host.redraw(); },
18
+ pop() { if (stack.length > 1) {
19
+ stack.pop();
20
+ host.redraw();
21
+ } },
22
+ replace(s) { stack[stack.length - 1] = s; host.redraw(); },
23
+ current: () => stack[stack.length - 1],
24
+ depth: () => stack.length,
25
+ quit() { hooks.onQuit?.(); },
26
+ redraw() { hooks.onRedraw?.(); },
27
+ note(msg) { noteMsg = msg; host.redraw(); },
28
+ };
29
+ async function toScreen(k) {
30
+ try {
31
+ await host.current().onKey?.(k);
32
+ }
33
+ catch (err) {
34
+ error = err.message;
35
+ host.redraw();
36
+ }
37
+ }
38
+ return {
39
+ ...host,
40
+ async handleKey(k) {
41
+ if (!k)
42
+ return;
43
+ // Ctrl-C is unconditional: it must work even from a filter prompt.
44
+ if (k.ctrl && k.name === 'c') {
45
+ host.quit();
46
+ return;
47
+ }
48
+ let capturing = false;
49
+ try {
50
+ capturing = host.current().capturesKeys?.() === true;
51
+ }
52
+ catch {
53
+ capturing = false;
54
+ }
55
+ if (k.name === 'q' && !capturing) {
56
+ if (stack.length > 1)
57
+ host.pop();
58
+ else
59
+ host.quit();
60
+ return;
61
+ }
62
+ await toScreen(k);
63
+ },
64
+ frame(width) {
65
+ let body;
66
+ try {
67
+ body = host.current().render(width);
68
+ }
69
+ catch (err) {
70
+ error = err.message;
71
+ body = [(0, render_1.red)(`screen error: ${err.message}`)];
72
+ }
73
+ const footer = [];
74
+ if (noteMsg)
75
+ footer.push('', noteMsg);
76
+ if (error)
77
+ footer.push('', (0, render_1.dim)(`last error: ${error}`));
78
+ return [...body, ...footer];
79
+ },
80
+ lastError: () => error,
81
+ };
82
+ }
@@ -0,0 +1,13 @@
1
+ export interface CapturedLog {
2
+ level: 'log' | 'warn' | 'error';
3
+ text: string;
4
+ at: number;
5
+ }
6
+ export interface ConsoleCapture {
7
+ lines(): CapturedLog[];
8
+ last(): CapturedLog | null;
9
+ count(): number;
10
+ /** Restore the real console and replay everything captured. */
11
+ restoreAndReplay(): void;
12
+ }
13
+ export declare function captureConsole(): ConsoleCapture;