@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
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # entities-runtime
2
+
3
+ A standalone Node.js daemon. Install it on any machine, pair with your
4
+ +Ai account, and your account can then run Claude Code / Codex sessions
5
+ on that machine on its behalf — from Vault, from Entity Studio, or from
6
+ anywhere else in +Ai.
7
+
8
+ ## Quick start
9
+
10
+ ```bash
11
+ npx entities-runtime
12
+ ```
13
+
14
+ The first run prints a 5-letter pairing code and a URL:
15
+
16
+ ```
17
+ Pair this runtime with your +Ai account.
18
+
19
+ https://vault.add.ai/entity/connect/HDXYC
20
+
21
+ Enter this code: HDXYC
22
+ ```
23
+
24
+ Open the URL while signed into Vault, enter the code, and the runtime
25
+ starts polling for work. Capabilities (claude, codex, git, node) are
26
+ reported every 30 s.
27
+
28
+ ## Commands
29
+
30
+ ```
31
+ entities-runtime run the daemon (foreground)
32
+ entities-runtime status pairing + capabilities + last-seen
33
+ entities-runtime sessions recent requests handled (--limit N)
34
+ entities-runtime unpair --yes disconnect from +Ai
35
+ entities-runtime version print version
36
+ ```
37
+
38
+ `status` and `sessions` work without the daemon running — they hit
39
+ Supabase directly using the daemon token in
40
+ `~/.entities-runtime/state.json`.
41
+
42
+ ## Architecture
43
+
44
+ See [`docs/architecture.md`](docs/architecture.md) for the full picture:
45
+ Supabase tables, RPC inventory, request lifecycle, and the cancel-watcher
46
+ flow.
47
+
48
+ ## DiskGuard — Codex scratch-dir disk protection
49
+
50
+ Each Codex run gets a throwaway `CODEX_HOME` under the OS temp dir
51
+ (`entities-codex-<rand>`). Codex writes its `state_*.sqlite`, logs, caches
52
+ and shell snapshots there — typically 50–350 MB per run. codex-spawn now
53
+ deletes that dir on exit, but a `SIGKILL`, a crash, or an orphan left by a
54
+ previous runtime process can skip that path. On a dedicated host doing
55
+ thousands of runs, those leftovers once grew to ~158 GiB and filled the
56
+ disk (`ENOSPC`, repeated crashes).
57
+
58
+ **DiskGuard** is the safety net. Like a CCTV DVR overwriting the oldest
59
+ footage when the disk fills, it watches free space on the volume holding
60
+ the temp dir and, when usage crosses a high-water mark, deletes the
61
+ **oldest inactive** scratch dirs — oldest first — until usage drops back
62
+ below a low-water mark, leaving a buffer for the OS. It runs at startup, on
63
+ an interval, and opportunistically right before each new Codex run.
64
+
65
+ Safety guarantees: it only touches directories directly under the temp root
66
+ whose names match the configured prefix (realpath-validated); it never
67
+ deletes a dir owned by a live session (in-process, or one whose
68
+ `session.lock` PID is still alive) or one younger than the min-age guard;
69
+ and it removes symlink *entries* without following them, so the durable
70
+ `~/.codex` targets behind `sessions`/`auth.json` are never harmed.
71
+
72
+ Note: DiskGuard can only reclaim space by deleting scratch dirs it owns. If
73
+ a disk is full of *other* data it will evict what it can and log that it
74
+ couldn't reach the target rather than touching anything it doesn't own.
75
+
76
+ Configure via environment variables (defaults shown):
77
+
78
+ | Variable | Default | Meaning |
79
+ |---|---|---|
80
+ | `DISKGUARD_ENABLED` | `true` | Master switch for the sweeper. Cleanup-on-exit still runs when `false`. |
81
+ | `DISKGUARD_HIGH_WATER_PCT` | `90` | Start evicting when volume usage reaches this %. |
82
+ | `DISKGUARD_LOW_WATER_PCT` | `80` | Stop evicting once usage drops below this %. Clamped below high-water. |
83
+ | `DISKGUARD_MIN_AGE_MINUTES` | `30` | Never evict a scratch dir younger than this. |
84
+ | `DISKGUARD_CHECK_INTERVAL_SECONDS` | `60` | Periodic sweep cadence. |
85
+ | `DISKGUARD_DRY_RUN` | `false` | Log what would be evicted without deleting anything. |
86
+ | `DISKGUARD_PREFIXES` | `entities-codex-` | Comma-separated dir-name prefixes eligible for eviction. |
87
+ | `DISKGUARD_TEMP_ROOT` | `os.tmpdir()` | Override the watched temp root (mainly for testing). |
88
+
89
+ ## Development
90
+
91
+ ```bash
92
+ npm install
93
+ npm run build
94
+ node dist/cli.js
95
+ npm test # builds, then runs the node:test suite (see test/)
96
+ ```
@@ -0,0 +1,9 @@
1
+ export type AgentKind = 'claude' | 'claude-tui' | 'claude-bypass' | 'codex' | 'kimi' | 'gemini' | 'grok';
2
+ export interface AuthCheckResult {
3
+ ok: boolean;
4
+ /** Short error code for the runtime_events row */
5
+ code?: 'auth_required';
6
+ /** Human-readable instruction the user can act on */
7
+ message?: string;
8
+ }
9
+ export declare function verifyAgentAuth(_agent: AgentKind): AuthCheckResult;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ // Pre-flight auth check for each agent CLI. Fast file-system / env probes
3
+ // only — no subprocess spawn, no network calls — so the cost is sub-ms.
4
+ //
5
+ // Why: every spawn previously assumed the CLI was logged in. If it wasn't,
6
+ // the child started, printed something cryptic to stderr (e.g. "Invalid
7
+ // API key", "401 unauthorized", "please run /login"), and exited non-zero.
8
+ // The user saw an event like `claude_stderr: 401 unauthorized` and had to
9
+ // reverse-engineer what to do.
10
+ //
11
+ // **Fail-open contract**: this check NEVER blocks a spawn unless it has
12
+ // strong evidence the agent is unauthed. The CLIs store credentials in
13
+ // many places (file, env var, macOS keychain, OS-specific config dir),
14
+ // and we can't enumerate them all from Node. So if we don't find a
15
+ // credential AND don't see an explicit env var, we still LET the spawn
16
+ // proceed — the CLI's own auth check is the source of truth. The only
17
+ // time we return ok:false is the negative-knowledge case where we have
18
+ // reason to believe the CLI is definitely unauthed; we currently never
19
+ // hit that case in production. The function exists so future
20
+ // improvements (per-agent `<cli> auth status` shell-outs) can plug in
21
+ // here without re-wiring every spawn path.
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.verifyAgentAuth = verifyAgentAuth;
57
+ const fs = __importStar(require("fs"));
58
+ const os = __importStar(require("os"));
59
+ const home = () => os.homedir();
60
+ // Treat empty/whitespace files as unauthed (sometimes credential files
61
+ // exist but contain `{}` or got truncated by an interrupted login).
62
+ function fileLooksValid(p) {
63
+ try {
64
+ const st = fs.statSync(p);
65
+ if (!st.isFile())
66
+ return false;
67
+ return st.size > 16;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ function anyFileLooksValid(paths) {
74
+ return paths.some(fileLooksValid);
75
+ }
76
+ function envSet(key) {
77
+ const v = process.env[key];
78
+ return typeof v === 'string' && v.length > 0;
79
+ }
80
+ function verifyAgentAuth(_agent) {
81
+ // Fail-open: we used to gate the spawn on the existence of a credential
82
+ // file (e.g. `~/.claude/.credentials.json`). Claude Code 2.x stores
83
+ // tokens in the macOS keychain on signed-in machines and the file
84
+ // doesn't exist — the check produced a false-negative `auth_required`
85
+ // on every working setup, hard-breaking the runtime.
86
+ //
87
+ // Letting the CLI's own auth flow surface the error is preferable: it
88
+ // emits a clear "please run /login" on stderr, which we forward to the
89
+ // user as a `<agent>_stderr` event. Not as pretty as a synthetic
90
+ // `auth_required` event, but it never produces a false-negative.
91
+ //
92
+ // The right way to do this in the future is `<cli> auth status` as a
93
+ // subprocess (which capabilities.ts already does for the heartbeat) —
94
+ // wire that in here when we're willing to add ~100ms to dispatch.
95
+ return { ok: true };
96
+ }
@@ -0,0 +1,20 @@
1
+ export declare const ATTACHMENTS_DIR = ".entity-attachments";
2
+ export interface AttachmentCandidate {
3
+ url: string;
4
+ kind: 'image' | 'file' | 'video';
5
+ }
6
+ /** Pure: pull attachment URLs out of a prompt, markers first, deduped. */
7
+ export declare function extractAttachmentUrls(prompt: string): AttachmentCandidate[];
8
+ /** Pure: derive a safe local filename for a downloaded attachment. */
9
+ export declare function localNameFor(url: string, index: number, contentType?: string): string;
10
+ export type Downloader = (url: string) => Promise<{
11
+ bytes: Uint8Array;
12
+ contentType?: string;
13
+ } | null>;
14
+ /**
15
+ * Download every attachment referenced in the prompt into
16
+ * `<cwd>/.entity-attachments/` and return the prompt with a note appended
17
+ * listing the local paths. Returns the prompt unchanged when there is
18
+ * nothing to fetch or nothing could be fetched.
19
+ */
20
+ export declare function prepareAttachments(prompt: string, cwd: string, download?: Downloader): Promise<string>;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ // Chat attachments → local files the agent can actually see.
3
+ //
4
+ // The chatflows dispatch injects attachment references into the prompt as
5
+ // markers — [Image: <url>], [File: <name> — <url>] — and older senders may
6
+ // include bare image URLs. A URL alone gives the model no vision: something
7
+ // has to fetch the bytes. Claude's Read tool renders images and PDFs
8
+ // natively, so we download each referenced file into a scratch dir inside
9
+ // the session cwd and append a note telling the agent where to look. The
10
+ // original URLs stay in the prompt untouched — tools that want URLs
11
+ // (image editing, Drive save) keep working.
12
+ //
13
+ // This replaces the 0.2.36 `--image <url>` flags, which the claude CLI does
14
+ // not support (unknown option → instant exit 1 on every image message).
15
+ //
16
+ // Failure policy: best-effort everywhere. Any download error, oversize file
17
+ // or timeout just skips that file — the prompt is still returned (with the
18
+ // URL as-is) and the run proceeds. This must never block or fail a run.
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.ATTACHMENTS_DIR = void 0;
54
+ exports.extractAttachmentUrls = extractAttachmentUrls;
55
+ exports.localNameFor = localNameFor;
56
+ exports.prepareAttachments = prepareAttachments;
57
+ const fs = __importStar(require("node:fs"));
58
+ const path = __importStar(require("node:path"));
59
+ exports.ATTACHMENTS_DIR = '.entity-attachments';
60
+ const MAX_FILES = 8;
61
+ const MAX_BYTES = 30 * 1024 * 1024; // per file
62
+ const FETCH_TIMEOUT_MS = 20_000;
63
+ const IMAGE_URL_RE = /https?:\/\/\S+\.(?:jpe?g|png|gif|webp|bmp|avif|svg)(?:\?[^\s\]]*)?(?=[\s\]]|$)/gi;
64
+ // SQL marker: [Image: <url>] / [Video: <url>] / [File: <name> — <url>]
65
+ const MARKER_RE = /\[(Image|File|Video): [^\]]*?(https?:\/\/[^\s\]]+)\]/gi;
66
+ // Content types we never download: audio is pre-transcribed by chatflows,
67
+ // video is too big to be useful to a text/vision agent.
68
+ const SKIP_CONTENT_TYPES = /^(video|audio)\//i;
69
+ const EXT_BY_CONTENT_TYPE = {
70
+ 'image/jpeg': '.jpg',
71
+ 'image/png': '.png',
72
+ 'image/gif': '.gif',
73
+ 'image/webp': '.webp',
74
+ 'image/svg+xml': '.svg',
75
+ 'application/pdf': '.pdf',
76
+ 'text/plain': '.txt',
77
+ 'text/csv': '.csv',
78
+ 'application/json': '.json',
79
+ };
80
+ /** Pure: pull attachment URLs out of a prompt, markers first, deduped. */
81
+ function extractAttachmentUrls(prompt) {
82
+ const seen = new Set();
83
+ const out = [];
84
+ for (const m of prompt.matchAll(MARKER_RE)) {
85
+ const url = m[2];
86
+ if (seen.has(url))
87
+ continue;
88
+ seen.add(url);
89
+ out.push({ url, kind: m[1].toLowerCase() });
90
+ }
91
+ for (const m of prompt.matchAll(IMAGE_URL_RE)) {
92
+ const url = m[0];
93
+ if (seen.has(url))
94
+ continue;
95
+ seen.add(url);
96
+ out.push({ url, kind: 'image' });
97
+ }
98
+ return out.filter(c => c.kind !== 'video').slice(0, MAX_FILES);
99
+ }
100
+ /** Pure: derive a safe local filename for a downloaded attachment. */
101
+ function localNameFor(url, index, contentType) {
102
+ let base = 'attachment';
103
+ try {
104
+ base = decodeURIComponent(path.posix.basename(new URL(url).pathname)) || 'attachment';
105
+ }
106
+ catch { /* keep fallback */ }
107
+ base = base.replace(/[^\w.-]+/g, '_').slice(-60);
108
+ if (!/\.[A-Za-z0-9]{1,5}$/.test(base)) {
109
+ const ext = contentType ? EXT_BY_CONTENT_TYPE[contentType.split(';')[0].trim().toLowerCase()] : undefined;
110
+ if (ext)
111
+ base += ext;
112
+ }
113
+ return `${index + 1}-${base}`;
114
+ }
115
+ const defaultDownloader = async (url) => {
116
+ const res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), redirect: 'follow' });
117
+ if (!res.ok)
118
+ return null;
119
+ const contentType = res.headers.get('content-type') ?? undefined;
120
+ if (contentType && SKIP_CONTENT_TYPES.test(contentType))
121
+ return null;
122
+ const len = Number(res.headers.get('content-length') ?? '0');
123
+ if (len > MAX_BYTES)
124
+ return null;
125
+ const bytes = new Uint8Array(await res.arrayBuffer());
126
+ if (bytes.byteLength > MAX_BYTES)
127
+ return null;
128
+ return { bytes, contentType };
129
+ };
130
+ /**
131
+ * Download every attachment referenced in the prompt into
132
+ * `<cwd>/.entity-attachments/` and return the prompt with a note appended
133
+ * listing the local paths. Returns the prompt unchanged when there is
134
+ * nothing to fetch or nothing could be fetched.
135
+ */
136
+ async function prepareAttachments(prompt, cwd, download = defaultDownloader) {
137
+ const candidates = extractAttachmentUrls(prompt);
138
+ if (candidates.length === 0)
139
+ return prompt;
140
+ const dir = path.join(cwd, exports.ATTACHMENTS_DIR);
141
+ const saved = [];
142
+ for (let i = 0; i < candidates.length; i++) {
143
+ const { url } = candidates[i];
144
+ try {
145
+ const got = await download(url);
146
+ if (!got)
147
+ continue;
148
+ if (saved.length === 0) {
149
+ fs.mkdirSync(dir, { recursive: true });
150
+ // Self-ignoring dir so attachments never land in a project repo's
151
+ // git history when the cwd is a persistent git-backed project.
152
+ fs.writeFileSync(path.join(dir, '.gitignore'), '*\n');
153
+ }
154
+ const rel = `${exports.ATTACHMENTS_DIR}/${localNameFor(url, i, got.contentType)}`;
155
+ fs.writeFileSync(path.join(cwd, rel), got.bytes);
156
+ saved.push({ rel, url });
157
+ }
158
+ catch { /* skip this file; URL stays usable in the prompt */ }
159
+ }
160
+ if (saved.length === 0)
161
+ return prompt;
162
+ const lines = saved.map(s => `- ${s.rel} (source: ${s.url})`);
163
+ return `${prompt}\n\n[Attached files downloaded locally — use the Read tool on these paths to view them:\n${lines.join('\n')}]`;
164
+ }
@@ -0,0 +1,64 @@
1
+ interface AuthShape {
2
+ authed: boolean;
3
+ account?: string;
4
+ accountKind?: string;
5
+ plan?: string;
6
+ }
7
+ interface BinaryProbe extends AuthShape {
8
+ version?: string;
9
+ available: boolean;
10
+ /** Model-effort levels this harness accepts (from harness-registry). */
11
+ efforts?: string[];
12
+ /** True when the daemon can npm-install this harness remotely. */
13
+ installable?: boolean;
14
+ /** How this harness can be logged in: pty-url-code | api-key | on-device. */
15
+ login_strategy?: string;
16
+ /** All remote login methods (primary strategy first, then alternates). */
17
+ login_methods?: string[];
18
+ }
19
+ interface CapabilitiesShape {
20
+ /** Version of @addai/node running. Lets the studio / CodeFlows
21
+ * UIs show "outdated daemon" hints and helps us debug field issues
22
+ * ("which version was this row produced by?"). */
23
+ daemon_version?: string;
24
+ claude: BinaryProbe & {
25
+ models?: string[];
26
+ };
27
+ codex: BinaryProbe & {
28
+ models?: string[];
29
+ };
30
+ kimi: BinaryProbe & {
31
+ models?: string[];
32
+ };
33
+ gemini: BinaryProbe & {
34
+ models?: string[];
35
+ };
36
+ grok: BinaryProbe & {
37
+ models?: string[];
38
+ };
39
+ git: BinaryProbe;
40
+ }
41
+ /** Codex's credential store: $CODEX_HOME (or ~/.codex) + /auth.json. */
42
+ export declare function codexHome(): string;
43
+ /**
44
+ * Read auth state straight out of codex's credential file.
45
+ *
46
+ * `codex login status` cannot be trusted as the sole signal: on some
47
+ * installs it prints its "Logged in using ChatGPT" line only when stdio is
48
+ * a TTY and stays completely silent (exit 0, both streams empty) when the
49
+ * daemon runs it with pipes — so a logged-in node reported itself logged
50
+ * out forever, and remote logins "failed" verification after succeeding
51
+ * (live incident, Lennys-Mac-mini 2026-07-29). The file is written by the
52
+ * login flow itself, which makes it the fact rather than the commentary.
53
+ *
54
+ * Shape (values redacted):
55
+ * { auth_mode: 'chatgpt', OPENAI_API_KEY: null,
56
+ * tokens: { id_token, access_token, refresh_token, account_id },
57
+ * last_refresh }
58
+ *
59
+ * Never throws: an unreadable or malformed store simply isn't proof of a
60
+ * login. Same approach probeKimi already takes for its OAuth store.
61
+ */
62
+ export declare function codexAuthFromStore(home: string | null): AuthShape;
63
+ export declare function probeCapabilities(): Promise<CapabilitiesShape>;
64
+ export {};