@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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Minimal MCP catalog + per-session filtering. Lifted from the desktop's
|
|
3
|
+
// mcp-skills.ts and trimmed: the runtime only needs (a) "is this MCP name
|
|
4
|
+
// installed in the relevant scope?" so we can decide whether to filter,
|
|
5
|
+
// and (b) the ability to write a temp `.mcp.json` whitelisting just the
|
|
6
|
+
// caller-requested servers.
|
|
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.CLAUDE_HOME = void 0;
|
|
42
|
+
exports.listMcps = listMcps;
|
|
43
|
+
exports.writeSessionMcpConfig = writeSessionMcpConfig;
|
|
44
|
+
exports.cleanupSessionMcpConfig = cleanupSessionMcpConfig;
|
|
45
|
+
exports.cleanupAllSessionMcpConfigs = cleanupAllSessionMcpConfigs;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const paths_1 = require("./paths");
|
|
50
|
+
Object.defineProperty(exports, "CLAUDE_HOME", { enumerable: true, get: function () { return paths_1.CLAUDE_HOME; } });
|
|
51
|
+
const CLAUDE_CONFIG = path.join(os.homedir(), '.claude.json');
|
|
52
|
+
function readJson(filePath) {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function extract(obj, scope) {
|
|
61
|
+
if (!obj)
|
|
62
|
+
return [];
|
|
63
|
+
const servers = (obj.mcpServers ?? {});
|
|
64
|
+
return Object.entries(servers)
|
|
65
|
+
.filter(([name]) => name !== 'codeflows-mcp-server')
|
|
66
|
+
.map(([name, s]) => ({ name, scope, command: s.command, args: s.args ?? [], env: s.env ?? {} }));
|
|
67
|
+
}
|
|
68
|
+
/** Best-effort MCP catalog. Used to decide what to whitelist when the
|
|
69
|
+
* caller asks for a subset. */
|
|
70
|
+
function listMcps(workingDirectory) {
|
|
71
|
+
const global = extract(readJson(CLAUDE_CONFIG), 'global');
|
|
72
|
+
const projectDotMcp = extract(readJson(path.join(workingDirectory, '.mcp.json')), 'project');
|
|
73
|
+
// Project-scope wins on name collision (matches desktop behavior).
|
|
74
|
+
const byName = new Map();
|
|
75
|
+
for (const s of global)
|
|
76
|
+
byName.set(s.name, s);
|
|
77
|
+
for (const s of projectDotMcp)
|
|
78
|
+
byName.set(s.name, s);
|
|
79
|
+
return Array.from(byName.values());
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Write a temp `.mcp.json` containing ONLY the caller-allowed servers,
|
|
83
|
+
* suitable for `claude --mcp-config <path> --strict-mcp-config`. Returns
|
|
84
|
+
* null when no filtering is needed (caller wants all visible MCPs).
|
|
85
|
+
*/
|
|
86
|
+
function writeSessionMcpConfig(ptyId, workingDirectory, enabled) {
|
|
87
|
+
if (enabled === null)
|
|
88
|
+
return null;
|
|
89
|
+
const all = listMcps(workingDirectory);
|
|
90
|
+
const allow = new Set(enabled);
|
|
91
|
+
// Whitelist matches everything visible → no filter needed.
|
|
92
|
+
if (allow.size === all.length && all.every(s => allow.has(s.name)))
|
|
93
|
+
return null;
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const s of all) {
|
|
96
|
+
if (!allow.has(s.name))
|
|
97
|
+
continue;
|
|
98
|
+
const entry = { command: s.command, args: s.args ?? [] };
|
|
99
|
+
if (s.env && Object.keys(s.env).length > 0)
|
|
100
|
+
entry.env = s.env;
|
|
101
|
+
out[s.name] = entry;
|
|
102
|
+
}
|
|
103
|
+
if (!fs.existsSync(paths_1.RUNTIME_SESSIONS_DIR)) {
|
|
104
|
+
fs.mkdirSync(paths_1.RUNTIME_SESSIONS_DIR, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
const target = path.join(paths_1.RUNTIME_SESSIONS_DIR, `${ptyId}.mcp.json`);
|
|
107
|
+
fs.writeFileSync(target, JSON.stringify({ mcpServers: out }, null, 2));
|
|
108
|
+
return target;
|
|
109
|
+
}
|
|
110
|
+
function cleanupSessionMcpConfig(filePath) {
|
|
111
|
+
if (!filePath)
|
|
112
|
+
return;
|
|
113
|
+
try {
|
|
114
|
+
fs.unlinkSync(filePath);
|
|
115
|
+
}
|
|
116
|
+
catch { /* already gone */ }
|
|
117
|
+
}
|
|
118
|
+
// Used at startup to remove stale per-session configs from a previous run.
|
|
119
|
+
function cleanupAllSessionMcpConfigs() {
|
|
120
|
+
try {
|
|
121
|
+
if (!fs.existsSync(paths_1.RUNTIME_SESSIONS_DIR))
|
|
122
|
+
return;
|
|
123
|
+
for (const name of fs.readdirSync(paths_1.RUNTIME_SESSIONS_DIR)) {
|
|
124
|
+
if (name.endsWith('.mcp.json')) {
|
|
125
|
+
try {
|
|
126
|
+
fs.unlinkSync(path.join(paths_1.RUNTIME_SESSIONS_DIR, name));
|
|
127
|
+
}
|
|
128
|
+
catch { }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface CapturePayload {
|
|
2
|
+
entity_id: string;
|
|
3
|
+
request_id: string;
|
|
4
|
+
surface: string;
|
|
5
|
+
turn: {
|
|
6
|
+
user_text: string;
|
|
7
|
+
assistant_text: string;
|
|
8
|
+
};
|
|
9
|
+
transcript_ref: {
|
|
10
|
+
kind: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
host?: string;
|
|
13
|
+
} | null;
|
|
14
|
+
}
|
|
15
|
+
export declare function buildCapturePayload(req: {
|
|
16
|
+
entityId: string;
|
|
17
|
+
requestId: string;
|
|
18
|
+
surface: string;
|
|
19
|
+
}, userText: string, assistantText: string, transcriptRef: CapturePayload['transcript_ref']): CapturePayload;
|
|
20
|
+
export declare function postCapture(baseUrl: string, serviceToken: string, payload: CapturePayload, timeoutMs?: number): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// +Ai Memory (Task 5) — turn-capture payload builder and poster.
|
|
3
|
+
//
|
|
4
|
+
// Called from session-runner.ts's setStatus() 'completed' branch (same
|
|
5
|
+
// hook point as notifySelfImproveIfEnabled) so the DB's
|
|
6
|
+
// entity_studio_aggregate_assistant_text trigger has already aggregated
|
|
7
|
+
// assistant_text for this request before we read it back. Best-effort:
|
|
8
|
+
// postCapture must NEVER throw or slow down request processing — a failed
|
|
9
|
+
// or slow capture must not affect the run in any way.
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.buildCapturePayload = buildCapturePayload;
|
|
12
|
+
exports.postCapture = postCapture;
|
|
13
|
+
function buildCapturePayload(req, userText, assistantText, transcriptRef) {
|
|
14
|
+
return {
|
|
15
|
+
entity_id: req.entityId,
|
|
16
|
+
request_id: req.requestId,
|
|
17
|
+
surface: req.surface,
|
|
18
|
+
turn: { user_text: userText.slice(0, 8000), assistant_text: assistantText.slice(0, 12000) },
|
|
19
|
+
transcript_ref: transcriptRef,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function postCapture(baseUrl, serviceToken, payload, timeoutMs = 5000) {
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(`${baseUrl}/functions/v1/entity-memory-capture`, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'content-type': 'application/json',
|
|
28
|
+
apikey: serviceToken,
|
|
29
|
+
authorization: `Bearer ${serviceToken}`,
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify(payload),
|
|
32
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
// fire-and-forget: no retries (capture is lower-stakes than
|
|
36
|
+
// self-improve), but a non-2xx must never be silently discarded.
|
|
37
|
+
const text = await res.text().catch(() => '');
|
|
38
|
+
console.error('[memory-capture] http', res.status, text.slice(0, 200));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// fire-and-forget: log, never throw (capture must not break runs)
|
|
43
|
+
console.error('[memory-capture] failed:', err?.message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const MEMORY_FALLBACK_MARKER = "\n\n[memory pack unavailable \u2014 memory fallback active]\n";
|
|
2
|
+
export declare function replaceMemoryBlock(systemPrompt: string, packBlock: string): string;
|
|
3
|
+
export declare function buildPackBody(args: {
|
|
4
|
+
entityId: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
budgetTokens?: number;
|
|
7
|
+
memberProfileId?: string | null;
|
|
8
|
+
}): Record<string, unknown>;
|
|
9
|
+
export declare function fetchMemoryPack(baseUrl: string, serviceToken: string, args: {
|
|
10
|
+
entityId: string;
|
|
11
|
+
prompt: string;
|
|
12
|
+
budgetTokens?: number;
|
|
13
|
+
memberProfileId?: string | null;
|
|
14
|
+
}, timeoutMs?: number): Promise<{
|
|
15
|
+
block: string;
|
|
16
|
+
degraded: boolean;
|
|
17
|
+
flagged: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function resolvePackEntityId(req: {
|
|
20
|
+
entity_studio_entity_id?: string | null;
|
|
21
|
+
entity_id?: string | null;
|
|
22
|
+
}): string | null;
|
|
23
|
+
export declare function applyMemoryPack(req: {
|
|
24
|
+
entity_studio_entity_id?: string | null;
|
|
25
|
+
entity_id?: string | null;
|
|
26
|
+
prompt?: string | null;
|
|
27
|
+
member_profile_id?: string | null;
|
|
28
|
+
}, systemPrompt: string | null, baseUrl: string, serviceToken: string, deps: {
|
|
29
|
+
/** Overridable for tests; defaults to the real fetchMemoryPack above. */
|
|
30
|
+
fetchPack?: typeof fetchMemoryPack;
|
|
31
|
+
buildLegacyManifest: () => Promise<string | null>;
|
|
32
|
+
onFallback?: () => void;
|
|
33
|
+
onFlagged?: (flagged: boolean) => void;
|
|
34
|
+
}): Promise<string | null>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MEMORY_FALLBACK_MARKER = void 0;
|
|
4
|
+
exports.replaceMemoryBlock = replaceMemoryBlock;
|
|
5
|
+
exports.buildPackBody = buildPackBody;
|
|
6
|
+
exports.fetchMemoryPack = fetchMemoryPack;
|
|
7
|
+
exports.resolvePackEntityId = resolvePackEntityId;
|
|
8
|
+
exports.applyMemoryPack = applyMemoryPack;
|
|
9
|
+
// +Ai Memory (Task 7) — semantic memory pack fetcher.
|
|
10
|
+
//
|
|
11
|
+
// Called from session-runner.ts's runRequest in place of the legacy flat
|
|
12
|
+
// DocFlows manifest (buildMemoryManifest). Hits the entity-memory-pack edge
|
|
13
|
+
// function (T6, deployed) which authenticates like entity-self-improve /
|
|
14
|
+
// entity-memory-capture: gateway JWT (anon key as Bearer).
|
|
15
|
+
//
|
|
16
|
+
// Contract with the edge function:
|
|
17
|
+
// - block === '' → entity has no memory_v2 pack (unflagged, or
|
|
18
|
+
// genuinely nothing to say) → caller falls back to
|
|
19
|
+
// the legacy buildMemoryManifest().
|
|
20
|
+
// - block non-empty → use it INSTEAD of the legacy manifest.
|
|
21
|
+
// - any failure (network error, timeout, non-2xx, bad body) → fail LOUD:
|
|
22
|
+
// return the fallback marker with degraded=true so the caller can both
|
|
23
|
+
// (a) still inject a marker block into the system prompt so the model
|
|
24
|
+
// knows its memory is degraded, and (b) emit a `memory_fallback`
|
|
25
|
+
// runtime event so the failure is visible/observable, never silent.
|
|
26
|
+
exports.MEMORY_FALLBACK_MARKER = '\n\n[memory pack unavailable — memory fallback active]\n';
|
|
27
|
+
// +Ai Memory (post-final-review Finding 2) — dedupe the '## Memory' block.
|
|
28
|
+
//
|
|
29
|
+
// The DB dispatch trigger (entity_memory_recall_inject,
|
|
30
|
+
// supabase/migrations/20260716110000_memory_v2_pack_trigger.sql, in the
|
|
31
|
+
// addai-entity-studio repo) already appends its own budgeted keyword pack
|
|
32
|
+
// to req.system_prompt at INSERT time for every memory_v2-flagged entity —
|
|
33
|
+
// that block always starts with the literal sequence '\n\n## Memory\n' and
|
|
34
|
+
// runs to the end of the string (the trigger appends it last). When this
|
|
35
|
+
// daemon then fetches the richer edge-fn pack (fetchMemoryPack above) and
|
|
36
|
+
// it comes back non-degraded and non-empty, appending it naively gives the
|
|
37
|
+
// model two '## Memory' sections. This helper strips the trigger's
|
|
38
|
+
// trailing block (found via the LAST occurrence of the marker, since the
|
|
39
|
+
// trigger always appends last) before appending the edge-fn pack in its
|
|
40
|
+
// place. Only call this when replacing with a real, non-degraded,
|
|
41
|
+
// non-empty pack — degraded/empty paths must leave system_prompt untouched
|
|
42
|
+
// by this function (empty packBlock is a no-op below).
|
|
43
|
+
function replaceMemoryBlock(systemPrompt, packBlock) {
|
|
44
|
+
if (!packBlock)
|
|
45
|
+
return systemPrompt;
|
|
46
|
+
const base = systemPrompt ?? '';
|
|
47
|
+
const idx = base.lastIndexOf('\n\n## Memory\n');
|
|
48
|
+
const stripped = idx !== -1 ? base.slice(0, idx) : base;
|
|
49
|
+
return stripped && stripped.trim().length > 0 ? `${stripped}\n\n${packBlock}` : packBlock;
|
|
50
|
+
}
|
|
51
|
+
// +Ai Memory (Phase 3 Task 2 — member delivery).
|
|
52
|
+
//
|
|
53
|
+
// Builds the entity-memory-pack POST body. Extracted from fetchMemoryPack
|
|
54
|
+
// so it's independently unit-testable: the null-member path must produce a
|
|
55
|
+
// body with EXACTLY the same keys as pre-phase-3 (byte-identical contract),
|
|
56
|
+
// which is easiest to assert directly on the plain object rather than by
|
|
57
|
+
// scraping a captured HTTP request. `requester` is omitted entirely (not
|
|
58
|
+
// sent as null) when there is no member, so a memberless dispatch's POST
|
|
59
|
+
// body is indistinguishable from the pre-phase-3 shape.
|
|
60
|
+
function buildPackBody(args) {
|
|
61
|
+
return {
|
|
62
|
+
entity_id: args.entityId,
|
|
63
|
+
prompt: args.prompt,
|
|
64
|
+
budget_tokens: args.budgetTokens,
|
|
65
|
+
...(args.memberProfileId ? { requester: { profile_id: args.memberProfileId } } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function fetchMemoryPack(baseUrl, serviceToken, args,
|
|
69
|
+
// edge fn steady-state ~600ms, cold start ~950ms; 3s bounds worst-case
|
|
70
|
+
// spawn latency
|
|
71
|
+
timeoutMs = 3000) {
|
|
72
|
+
try {
|
|
73
|
+
const r = await fetch(`${baseUrl}/functions/v1/entity-memory-pack`, {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
headers: {
|
|
76
|
+
'content-type': 'application/json',
|
|
77
|
+
apikey: serviceToken,
|
|
78
|
+
authorization: `Bearer ${serviceToken}`,
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify(buildPackBody(args)),
|
|
81
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
82
|
+
});
|
|
83
|
+
if (!r.ok)
|
|
84
|
+
throw new Error(`pack ${r.status}`);
|
|
85
|
+
const j = (await r.json());
|
|
86
|
+
// Phase 4 T7: entity-memory-pack returns `skipped:"flag"` distinctly
|
|
87
|
+
// from a genuinely-empty pack (block === '' either way) — this is the
|
|
88
|
+
// one reliable, already-happening-every-turn signal for "is this
|
|
89
|
+
// entity memory_v2-flagged", reused (not re-asked) to gate the
|
|
90
|
+
// PreCompact hook write (see precompact-hook.ts / session-runner.ts).
|
|
91
|
+
return { block: j.block ?? '', degraded: false, flagged: j.skipped !== 'flag' };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error('[memory-pack] failed:', err?.message);
|
|
95
|
+
// Unknown on failure — conservatively NOT flagged, so a pack-fetch
|
|
96
|
+
// outage never causes the PreCompact hook to be written when we
|
|
97
|
+
// couldn't actually confirm the entity is on the pack.
|
|
98
|
+
return { block: exports.MEMORY_FALLBACK_MARKER, degraded: true, flagged: false };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// +Ai Memory — runtime "entity_id alias" fix (final phase-3 review finding).
|
|
102
|
+
//
|
|
103
|
+
// runtime_pick_next_request (addai-entity-studio/supabase/migrations/
|
|
104
|
+
// 20260718101000_memory_v3_pick_member.sql) `returns entity_runtime_requests`
|
|
105
|
+
// — the BARE table rowtype, via `returning * into v_req; return v_req;`. No
|
|
106
|
+
// jsonb_build_object, no column aliasing. PostgREST therefore serializes the
|
|
107
|
+
// owning-entity column under its real name, `entity_studio_entity_id` — an
|
|
108
|
+
// `entity_id` key never appears on a row from that RPC. The daemon's pack-
|
|
109
|
+
// fetch gate previously read `req.entity_id` only, which meant the richer
|
|
110
|
+
// edge-fn pack fetch below NEVER fired in production; memory only reached
|
|
111
|
+
// entities via the DB dispatch trigger's already-injected '## Memory' block
|
|
112
|
+
// (entity_memory_recall_inject). `entity_id` is kept as a secondary fallback
|
|
113
|
+
// here — never populated by the current pick RPC, but cheap insurance
|
|
114
|
+
// against any future/legacy shape (e.g. runtime_request_get_meta, which
|
|
115
|
+
// DOES alias the column to `entity_id` via jsonb_build_object) that hands
|
|
116
|
+
// this same row shape through a different code path.
|
|
117
|
+
function resolvePackEntityId(req) {
|
|
118
|
+
return req.entity_studio_entity_id ?? req.entity_id ?? null;
|
|
119
|
+
}
|
|
120
|
+
// Extracted from session-runner.ts's runRequest so the gate → fetch →
|
|
121
|
+
// three-way-switch → replaceMemoryBlock interaction is unit-testable without
|
|
122
|
+
// exercising the rest of the daemon (spawn, install, cwd resolution, …).
|
|
123
|
+
// Mirrors the inline logic byte-for-byte; behavior must not change when this
|
|
124
|
+
// is swapped in at the call site — only WHICH entity id field is read
|
|
125
|
+
// changes (see resolvePackEntityId above).
|
|
126
|
+
//
|
|
127
|
+
// 1. degraded → fail LOUD: fold the fallback marker AND the legacy
|
|
128
|
+
// manifest into system_prompt (never silently drop
|
|
129
|
+
// what was already there), and let the caller know
|
|
130
|
+
// via onFallback() so it can emit the runtime event.
|
|
131
|
+
// 2. non-empty block → the richer edge-fn pack REPLACES the DB dispatch
|
|
132
|
+
// trigger's already-injected '## Memory' block
|
|
133
|
+
// (replaceMemoryBlock, phase-1 B1 dedupe fix) —
|
|
134
|
+
// never doubles the section.
|
|
135
|
+
// 3. block === '' → entity has no pack (unflagged / nothing to
|
|
136
|
+
// recall) → legacy manifest, same as before.
|
|
137
|
+
// no entity id at all → pack fetch never fires; behaves exactly like case
|
|
138
|
+
// 3 (legacy manifest, or no-op if that's empty/null
|
|
139
|
+
// too) — preserves pre-fix behavior for non-entity
|
|
140
|
+
// runs byte-for-byte.
|
|
141
|
+
async function applyMemoryPack(req, systemPrompt, baseUrl, serviceToken, deps) {
|
|
142
|
+
const entityId = resolvePackEntityId(req);
|
|
143
|
+
const fetchPack = deps.fetchPack ?? fetchMemoryPack;
|
|
144
|
+
const pack = entityId
|
|
145
|
+
? await fetchPack(baseUrl, serviceToken, {
|
|
146
|
+
entityId,
|
|
147
|
+
prompt: req.prompt ?? '',
|
|
148
|
+
memberProfileId: req.member_profile_id ?? null,
|
|
149
|
+
})
|
|
150
|
+
: { block: '', degraded: false, flagged: false };
|
|
151
|
+
deps.onFlagged?.(pack.flagged);
|
|
152
|
+
if (pack.degraded) {
|
|
153
|
+
deps.onFallback?.();
|
|
154
|
+
const legacy = await deps.buildLegacyManifest();
|
|
155
|
+
const degradedManifest = legacy ? `${pack.block}\n\n${legacy}` : pack.block;
|
|
156
|
+
if (!degradedManifest)
|
|
157
|
+
return systemPrompt;
|
|
158
|
+
return systemPrompt && systemPrompt.trim().length > 0
|
|
159
|
+
? `${systemPrompt}\n\n${degradedManifest}`
|
|
160
|
+
: degradedManifest;
|
|
161
|
+
}
|
|
162
|
+
if (pack.block !== '') {
|
|
163
|
+
return replaceMemoryBlock(systemPrompt ?? '', pack.block);
|
|
164
|
+
}
|
|
165
|
+
const legacyManifest = await deps.buildLegacyManifest();
|
|
166
|
+
if (!legacyManifest)
|
|
167
|
+
return systemPrompt;
|
|
168
|
+
return systemPrompt && systemPrompt.trim().length > 0
|
|
169
|
+
? `${systemPrompt}\n\n${legacyManifest}`
|
|
170
|
+
: legacyManifest;
|
|
171
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type ModelAgent = 'claude' | 'codex' | 'kimi' | 'gemini' | 'grok';
|
|
2
|
+
/** Returns the model if it plausibly belongs to `agent`, else undefined
|
|
3
|
+
* (= use the agent's default). Blank/null → undefined. */
|
|
4
|
+
export declare function modelForAgent(agent: ModelAgent, model: string | null | undefined): string | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Guard against stale cross-agent model ids.
|
|
3
|
+
//
|
|
4
|
+
// Chats store a `model` override. When the user switches a chat's AGENT
|
|
5
|
+
// (claude → grok etc.), the stored model tags along and the daemon would
|
|
6
|
+
// pass e.g. `grok --model claude-sonnet-4-5`, which exits 1 ("unknown
|
|
7
|
+
// model id") and the whole turn fails with an opaque error.
|
|
8
|
+
//
|
|
9
|
+
// Every provider's model ids carry a recognisable family prefix, so we
|
|
10
|
+
// drop any override that clearly belongs to a different agent and let the
|
|
11
|
+
// CLI use its own default — a working reply on the agent's default model
|
|
12
|
+
// beats a dead chat. Unknown/future ids that DO match the family pass
|
|
13
|
+
// through untouched; the CLI remains the source of truth for validity.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.modelForAgent = modelForAgent;
|
|
16
|
+
const FAMILY = {
|
|
17
|
+
claude: /^(claude|sonnet|opus|haiku|fable)/i,
|
|
18
|
+
codex: /^(gpt|o\d|codex)/i,
|
|
19
|
+
kimi: /^(kimi|moonshot)/i,
|
|
20
|
+
gemini: /^gemini/i,
|
|
21
|
+
grok: /^grok/i,
|
|
22
|
+
};
|
|
23
|
+
/** Returns the model if it plausibly belongs to `agent`, else undefined
|
|
24
|
+
* (= use the agent's default). Blank/null → undefined. */
|
|
25
|
+
function modelForAgent(agent, model) {
|
|
26
|
+
const m = (model ?? '').trim();
|
|
27
|
+
if (!m)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (!FAMILY[agent].test(m)) {
|
|
30
|
+
console.log(`[model-filter] dropping model '${m}' — not a ${agent} model (stale from an agent switch); using ${agent}'s default`);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return m;
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface HealthSignals {
|
|
2
|
+
/** Percentage of the scratch volume used (0-100), or null if unreadable. */
|
|
3
|
+
diskUsedPct: number | null;
|
|
4
|
+
}
|
|
5
|
+
export interface HealthVerdict {
|
|
6
|
+
healthy: boolean;
|
|
7
|
+
blockers: Array<{
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export interface HealthOpts {
|
|
13
|
+
/** Refuse pickup at/above this used-% on the scratch volume. */
|
|
14
|
+
diskCriticalPct?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function evaluateNodeHealth(signals: HealthSignals, opts?: HealthOpts): HealthVerdict;
|
|
17
|
+
/** Read used-% of the volume holding `root` via statfs. null if unreadable. */
|
|
18
|
+
export declare function readDiskUsedPct(root: string): number | null;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Pre-flight node health used to gate request pickup. The point is NOT to
|
|
3
|
+
// diagnose everything — it's to stop a node that physically cannot finish a
|
|
4
|
+
// run from ACCEPTING it. When the disk is full, every codex/claude spawn on
|
|
5
|
+
// this node dies with ENOSPC ("No space left on device") — 57+ failures in
|
|
6
|
+
// prod. If we refuse the pickup instead, the request stays `pending` and the
|
|
7
|
+
// server-side failover hands it to a healthy backup, turning a guaranteed
|
|
8
|
+
// failure into a clean failover.
|
|
9
|
+
//
|
|
10
|
+
// Fail-open is the rule: we only block on a signal we can actually read and
|
|
11
|
+
// that is unambiguously fatal. Missing/unreadable info never blocks.
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.evaluateNodeHealth = evaluateNodeHealth;
|
|
47
|
+
exports.readDiskUsedPct = readDiskUsedPct;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const DEFAULT_DISK_CRITICAL_PCT = 97;
|
|
50
|
+
function evaluateNodeHealth(signals, opts = {}) {
|
|
51
|
+
const blockers = [];
|
|
52
|
+
const critical = opts.diskCriticalPct ?? DEFAULT_DISK_CRITICAL_PCT;
|
|
53
|
+
if (signals.diskUsedPct != null && signals.diskUsedPct >= critical) {
|
|
54
|
+
blockers.push({
|
|
55
|
+
code: 'disk_critical',
|
|
56
|
+
message: `scratch disk ${signals.diskUsedPct.toFixed(0)}% full (>= ${critical}%) — refusing new runs so they fail over to a healthy node`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return { healthy: blockers.length === 0, blockers };
|
|
60
|
+
}
|
|
61
|
+
/** Read used-% of the volume holding `root` via statfs. null if unreadable. */
|
|
62
|
+
function readDiskUsedPct(root) {
|
|
63
|
+
try {
|
|
64
|
+
const st = fs.statfsSync(root);
|
|
65
|
+
const blocks = Number(st.blocks);
|
|
66
|
+
const bavail = Number(st.bavail);
|
|
67
|
+
if (!Number.isFinite(blocks) || blocks <= 0)
|
|
68
|
+
return null;
|
|
69
|
+
return (1 - bavail / blocks) * 100;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface FinalizeResult {
|
|
2
|
+
runtime_id: string;
|
|
3
|
+
daemon_token: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Request a fresh 5-letter pairing code.
|
|
7
|
+
* Returns the code; the caller is responsible for displaying it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function requestPairCode(): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Block until the user has claimed the code at vault.add.ai or until the
|
|
12
|
+
* code expires. Returns the runtime_id + raw daemon_token on success.
|
|
13
|
+
* Saves the pairing to ~/.ainode/state.json.
|
|
14
|
+
*/
|
|
15
|
+
export declare function awaitPairing(code: string): Promise<FinalizeResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Print the human-facing instructions for a fresh pairing.
|
|
18
|
+
*/
|
|
19
|
+
export declare function printPairingInstructions(code: string): void;
|
|
20
|
+
export {};
|