@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.
- package/README.md +96 -0
- package/dist/agent-auth.d.ts +9 -0
- package/dist/agent-auth.js +96 -0
- package/dist/attachments.d.ts +20 -0
- package/dist/attachments.js +164 -0
- package/dist/capabilities.d.ts +64 -0
- package/dist/capabilities.js +406 -0
- package/dist/claude-binary.d.ts +16 -0
- package/dist/claude-binary.js +145 -0
- package/dist/claude-config.d.ts +11 -0
- package/dist/claude-config.js +144 -0
- package/dist/claude-print.d.ts +35 -0
- package/dist/claude-print.js +312 -0
- package/dist/claude-spawn.d.ts +40 -0
- package/dist/claude-spawn.js +220 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +137 -0
- package/dist/codex-binary.d.ts +4 -0
- package/dist/codex-binary.js +70 -0
- package/dist/codex-spawn.d.ts +47 -0
- package/dist/codex-spawn.js +320 -0
- package/dist/command-runner.d.ts +9 -0
- package/dist/command-runner.js +592 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +38 -0
- package/dist/control-server.d.ts +5 -0
- package/dist/control-server.js +292 -0
- package/dist/diskguard.d.ts +96 -0
- package/dist/diskguard.js +435 -0
- package/dist/error-classify.d.ts +20 -0
- package/dist/error-classify.js +159 -0
- package/dist/events.d.ts +26 -0
- package/dist/events.js +97 -0
- package/dist/gemini-binary.d.ts +4 -0
- package/dist/gemini-binary.js +64 -0
- package/dist/gemini-spawn.d.ts +52 -0
- package/dist/gemini-spawn.js +369 -0
- package/dist/git-identity.d.ts +18 -0
- package/dist/git-identity.js +104 -0
- package/dist/grok-binary.d.ts +4 -0
- package/dist/grok-binary.js +70 -0
- package/dist/grok-spawn.d.ts +41 -0
- package/dist/grok-spawn.js +495 -0
- package/dist/guardrails.d.ts +32 -0
- package/dist/guardrails.js +125 -0
- package/dist/hang-watchdog.d.ts +17 -0
- package/dist/hang-watchdog.js +48 -0
- package/dist/harness-registry.d.ts +55 -0
- package/dist/harness-registry.js +180 -0
- package/dist/heartbeat.d.ts +13 -0
- package/dist/heartbeat.js +95 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +440 -0
- package/dist/install.d.ts +63 -0
- package/dist/install.js +467 -0
- package/dist/jsonl-tail.d.ts +20 -0
- package/dist/jsonl-tail.js +192 -0
- package/dist/kimi-binary.d.ts +4 -0
- package/dist/kimi-binary.js +68 -0
- package/dist/kimi-spawn.d.ts +47 -0
- package/dist/kimi-spawn.js +319 -0
- package/dist/lockfile.d.ts +30 -0
- package/dist/lockfile.js +169 -0
- package/dist/mcp-config.d.ts +23 -0
- package/dist/mcp-config.js +133 -0
- package/dist/memory-capture.d.ts +20 -0
- package/dist/memory-capture.js +45 -0
- package/dist/memory-pack.d.ts +34 -0
- package/dist/memory-pack.js +171 -0
- package/dist/model-filter.d.ts +4 -0
- package/dist/model-filter.js +34 -0
- package/dist/node-health.d.ts +18 -0
- package/dist/node-health.js +74 -0
- package/dist/pairing.d.ts +20 -0
- package/dist/pairing.js +129 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +48 -0
- package/dist/precompact-hook.d.ts +27 -0
- package/dist/precompact-hook.js +136 -0
- package/dist/projects.d.ts +25 -0
- package/dist/projects.js +255 -0
- package/dist/prompt-keystrokes.d.ts +3 -0
- package/dist/prompt-keystrokes.js +56 -0
- package/dist/pty-helper.d.ts +26 -0
- package/dist/pty-helper.js +156 -0
- package/dist/request-pump.d.ts +12 -0
- package/dist/request-pump.js +249 -0
- package/dist/run-retry.d.ts +28 -0
- package/dist/run-retry.js +81 -0
- package/dist/self-update.d.ts +75 -0
- package/dist/self-update.js +192 -0
- package/dist/session-runner.d.ts +130 -0
- package/dist/session-runner.js +2271 -0
- package/dist/sleep-detector.d.ts +11 -0
- package/dist/sleep-detector.js +32 -0
- package/dist/state-migrate.d.ts +8 -0
- package/dist/state-migrate.js +91 -0
- package/dist/store.d.ts +5 -0
- package/dist/store.js +82 -0
- package/dist/supabase-client.d.ts +15 -0
- package/dist/supabase-client.js +119 -0
- package/dist/time-context.d.ts +10 -0
- package/dist/time-context.js +71 -0
- package/dist/tokens.d.ts +4 -0
- package/dist/tokens.js +33 -0
- package/dist/tui/app.d.ts +35 -0
- package/dist/tui/app.js +82 -0
- package/dist/tui/console-capture.d.ts +13 -0
- package/dist/tui/console-capture.js +62 -0
- package/dist/tui/dashboard.d.ts +42 -0
- package/dist/tui/dashboard.js +205 -0
- package/dist/tui/data.d.ts +57 -0
- package/dist/tui/data.js +62 -0
- package/dist/tui/harnesses.d.ts +16 -0
- package/dist/tui/harnesses.js +354 -0
- package/dist/tui/render.d.ts +21 -0
- package/dist/tui/render.js +116 -0
- package/dist/tui/requests.d.ts +18 -0
- package/dist/tui/requests.js +127 -0
- package/dist/tui/run.d.ts +13 -0
- package/dist/tui/run.js +178 -0
- package/dist/tui/transcript.d.ts +39 -0
- package/dist/tui/transcript.js +234 -0
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +314 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +3 -0
- package/dist/win.d.ts +67 -0
- package/dist/win.js +317 -0
- package/package.json +60 -0
- package/scripts/fix-pty-helper.js +28 -0
- package/scripts/precompact-capture.js +292 -0
- 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 {};
|