@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/dist/pairing.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// First-run pairing flow. Called when state.json doesn't exist. Walks the
|
|
3
|
+
// user through obtaining a 5-letter code and entering it at
|
|
4
|
+
// vault.add.ai/add/entity. Once finalized, persists the daemon_token.
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.requestPairCode = requestPairCode;
|
|
40
|
+
exports.awaitPairing = awaitPairing;
|
|
41
|
+
exports.printPairingInstructions = printPairingInstructions;
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const supabase_client_1 = require("./supabase-client");
|
|
44
|
+
const store_1 = require("./store");
|
|
45
|
+
const config_1 = require("./config");
|
|
46
|
+
function platform() {
|
|
47
|
+
const p = process.platform;
|
|
48
|
+
if (p === 'darwin' || p === 'linux' || p === 'win32')
|
|
49
|
+
return p;
|
|
50
|
+
return 'linux';
|
|
51
|
+
}
|
|
52
|
+
function sleep(ms) {
|
|
53
|
+
return new Promise(r => setTimeout(r, ms));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Request a fresh 5-letter pairing code.
|
|
57
|
+
* Returns the code; the caller is responsible for displaying it.
|
|
58
|
+
*/
|
|
59
|
+
async function requestPairCode() {
|
|
60
|
+
return (0, supabase_client_1.rpc)('runtime_pair_request_code', {
|
|
61
|
+
p_hostname: os.hostname(),
|
|
62
|
+
p_platform: platform(),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Block until the user has claimed the code at vault.add.ai or until the
|
|
67
|
+
* code expires. Returns the runtime_id + raw daemon_token on success.
|
|
68
|
+
* Saves the pairing to ~/.ainode/state.json.
|
|
69
|
+
*/
|
|
70
|
+
async function awaitPairing(code) {
|
|
71
|
+
const deadline = Date.now() + config_1.PAIR_TIMEOUT_MS;
|
|
72
|
+
while (Date.now() < deadline) {
|
|
73
|
+
try {
|
|
74
|
+
const rows = await (0, supabase_client_1.rpc)('runtime_pair_finalize', { p_code: code });
|
|
75
|
+
// PostgREST returns a `setof record` as an array of objects.
|
|
76
|
+
const row = Array.isArray(rows) ? rows[0] : rows;
|
|
77
|
+
if (row?.daemon_token) {
|
|
78
|
+
(0, store_1.writePairing)({
|
|
79
|
+
runtimeId: row.runtime_id,
|
|
80
|
+
daemonToken: row.daemon_token,
|
|
81
|
+
supabaseUrl: config_1.SUPABASE_URL,
|
|
82
|
+
supabaseAnonKey: config_1.SUPABASE_ANON_KEY,
|
|
83
|
+
pairedAt: Date.now(),
|
|
84
|
+
});
|
|
85
|
+
return row;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (err instanceof supabase_client_1.RpcError) {
|
|
90
|
+
// 'code_not_claimed' just means the user hasn't entered it yet.
|
|
91
|
+
// Anything else is a hard error — surface it.
|
|
92
|
+
const msg = err.message || '';
|
|
93
|
+
if (msg.includes('code_not_claimed')) {
|
|
94
|
+
// keep waiting
|
|
95
|
+
}
|
|
96
|
+
else if (msg.includes('code_expired') || msg.includes('code_not_found')) {
|
|
97
|
+
throw new Error('pair_code_expired');
|
|
98
|
+
}
|
|
99
|
+
else if (msg.includes('code_already_consumed')) {
|
|
100
|
+
// Shouldn't happen mid-poll, but treat as success-after-the-fact.
|
|
101
|
+
throw new Error('pair_code_already_consumed');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await sleep(config_1.PAIR_POLL_INTERVAL_MS);
|
|
112
|
+
}
|
|
113
|
+
throw new Error('pair_timeout');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Print the human-facing instructions for a fresh pairing.
|
|
117
|
+
*/
|
|
118
|
+
function printPairingInstructions(code) {
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(' ── Setup needed ──');
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(` 1. Open ${config_1.VAULT_PAIR_URL}`);
|
|
123
|
+
console.log(` 2. Enter this code: ${code}`);
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log(` (or click: ${config_1.VAULT_PAIR_URL}/${code})`);
|
|
126
|
+
console.log('');
|
|
127
|
+
console.log(' Waiting for pairing… (expires in 15 minutes)');
|
|
128
|
+
console.log('');
|
|
129
|
+
}
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const RUNTIME_HOME: string;
|
|
2
|
+
/** Pre-rename home. Read once at startup by state-migrate, never written. */
|
|
3
|
+
export declare const LEGACY_RUNTIME_HOME: string;
|
|
4
|
+
export declare const RUNTIME_STATE_FILE: string;
|
|
5
|
+
export declare const RUNTIME_LOCK_FILE: string;
|
|
6
|
+
export declare const RUNTIME_SESSIONS_DIR: string;
|
|
7
|
+
export declare const CLAUDE_HOME: string;
|
|
8
|
+
export declare const CLAUDE_PROJECTS_DIR: string;
|
|
9
|
+
export declare const RELAY_HOST = "codeflows-relay.shaun-395.workers.dev";
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RELAY_HOST = exports.CLAUDE_PROJECTS_DIR = exports.CLAUDE_HOME = exports.RUNTIME_SESSIONS_DIR = exports.RUNTIME_LOCK_FILE = exports.RUNTIME_STATE_FILE = exports.LEGACY_RUNTIME_HOME = exports.RUNTIME_HOME = void 0;
|
|
37
|
+
// All on-disk paths the runtime uses.
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
exports.RUNTIME_HOME = path.join(os.homedir(), '.ainode');
|
|
41
|
+
/** Pre-rename home. Read once at startup by state-migrate, never written. */
|
|
42
|
+
exports.LEGACY_RUNTIME_HOME = path.join(os.homedir(), '.entities-runtime');
|
|
43
|
+
exports.RUNTIME_STATE_FILE = path.join(exports.RUNTIME_HOME, 'state.json');
|
|
44
|
+
exports.RUNTIME_LOCK_FILE = path.join(exports.RUNTIME_HOME, 'runtime.json');
|
|
45
|
+
exports.RUNTIME_SESSIONS_DIR = path.join(exports.RUNTIME_HOME, 'sessions');
|
|
46
|
+
exports.CLAUDE_HOME = path.join(os.homedir(), '.claude');
|
|
47
|
+
exports.CLAUDE_PROJECTS_DIR = path.join(exports.CLAUDE_HOME, 'projects');
|
|
48
|
+
exports.RELAY_HOST = 'codeflows-relay.shaun-395.workers.dev';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Absolute path to the standalone hook script shipped in scripts/
|
|
3
|
+
* alongside dist/ (package.json "files": ["dist","scripts", ...]).
|
|
4
|
+
* __dirname is dist/ at runtime in both the repo checkout (npm run build)
|
|
5
|
+
* and the installed package, so '..','scripts' resolves the same way in
|
|
6
|
+
* both.
|
|
7
|
+
*/
|
|
8
|
+
export declare function precompactHookScriptPath(): string;
|
|
9
|
+
/**
|
|
10
|
+
* The settings.json `hooks` fragment for a single PreCompact command hook.
|
|
11
|
+
* Pure/deterministic — the thing this task's Step 5 asks to unit-test as
|
|
12
|
+
* "the hook-config builder (settings JSON shape)".
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildPrecompactHookSettings(scriptPath: string): {
|
|
15
|
+
hooks: {
|
|
16
|
+
PreCompact: unknown[];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Writes <cwd>/.claude/settings.json with the PreCompact hook merged in.
|
|
21
|
+
* Best-effort and idempotent: a fresh ephemeral session cwd has no prior
|
|
22
|
+
* settings.json, but this merges (never clobbers) in case a future caller
|
|
23
|
+
* pre-seeds project settings for some other reason. A write failure is
|
|
24
|
+
* logged and swallowed — the caller's spawn proceeds hook-less, the exact
|
|
25
|
+
* same outcome as an entity this function is never called for.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeSessionPrecompactHook(cwd: string): void;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// +Ai Memory Phase 4, T7 — PreCompact capture hook (claude agent only, Q7).
|
|
3
|
+
//
|
|
4
|
+
// Claude Code's PreCompact hook fires just before a session's context is
|
|
5
|
+
// summarized away by compaction — the daemon's one chance to capture the
|
|
6
|
+
// in-flight turn tail (surface:'precompact') that would otherwise be lost
|
|
7
|
+
// before the normal completed-turn capture path (notifyCaptureIfEnabled,
|
|
8
|
+
// session-runner.ts) ever sees it. Wired ONLY for memory_v2-flagged
|
|
9
|
+
// entities, ONLY into the per-request ephemeral working directory's
|
|
10
|
+
// PROJECT-scope settings.json.
|
|
11
|
+
//
|
|
12
|
+
// Why project-scope, not the simpler-looking `~/.claude/settings.json`:
|
|
13
|
+
// unlike the Grok integration's per-session sandboxed HOME (GROK_HOME),
|
|
14
|
+
// claude-spawn.ts spawns claude inheriting the daemon's REAL host HOME
|
|
15
|
+
// (os.homedir(), see paths.ts CLAUDE_HOME / claude-config.ts) — there is
|
|
16
|
+
// no per-session HOME isolation for claude on this runtime. A hook written
|
|
17
|
+
// to the user-scope settings.json would therefore apply to EVERY claude
|
|
18
|
+
// spawn on this host, flagged entity or not, violating the "byte-identical
|
|
19
|
+
// spawn env for non-flagged entities" requirement. claude-spawn.ts already
|
|
20
|
+
// passes `--setting-sources user,project`, so a `<cwd>/.claude/
|
|
21
|
+
// settings.json` file IS read for this session and this session alone
|
|
22
|
+
// (each request gets a fresh ephemeral cwd, session-runner.ts's
|
|
23
|
+
// `path.join(RUNTIME_HOME, 'sessions', requestId)`) — that is the
|
|
24
|
+
// injectable per-session seam this task's recon found. Non-flagged
|
|
25
|
+
// entities and every non-claude agent never call writeSessionPrecompactHook
|
|
26
|
+
// at all, so their spawn env is untouched, not merely "harmless" — the
|
|
27
|
+
// settings.json literally does not exist for them.
|
|
28
|
+
//
|
|
29
|
+
// The gate itself (which entities are memory_v2-flagged) stays server-side
|
|
30
|
+
// per Q7 and the house pattern established for capture/pack: the daemon
|
|
31
|
+
// learns "flagged" only as a side effect of the SAME entity-memory-pack
|
|
32
|
+
// round trip session-runner.ts already makes every turn (memory-pack.ts's
|
|
33
|
+
// onFlagged callback), never by asking a second question of its own.
|
|
34
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
35
|
+
if (k2 === undefined) k2 = k;
|
|
36
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
37
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
38
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
39
|
+
}
|
|
40
|
+
Object.defineProperty(o, k2, desc);
|
|
41
|
+
}) : (function(o, m, k, k2) {
|
|
42
|
+
if (k2 === undefined) k2 = k;
|
|
43
|
+
o[k2] = m[k];
|
|
44
|
+
}));
|
|
45
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
46
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
47
|
+
}) : function(o, v) {
|
|
48
|
+
o["default"] = v;
|
|
49
|
+
});
|
|
50
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
51
|
+
var ownKeys = function(o) {
|
|
52
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
53
|
+
var ar = [];
|
|
54
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
55
|
+
return ar;
|
|
56
|
+
};
|
|
57
|
+
return ownKeys(o);
|
|
58
|
+
};
|
|
59
|
+
return function (mod) {
|
|
60
|
+
if (mod && mod.__esModule) return mod;
|
|
61
|
+
var result = {};
|
|
62
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
63
|
+
__setModuleDefault(result, mod);
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
})();
|
|
67
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
68
|
+
exports.precompactHookScriptPath = precompactHookScriptPath;
|
|
69
|
+
exports.buildPrecompactHookSettings = buildPrecompactHookSettings;
|
|
70
|
+
exports.writeSessionPrecompactHook = writeSessionPrecompactHook;
|
|
71
|
+
const fs = __importStar(require("fs"));
|
|
72
|
+
const path = __importStar(require("path"));
|
|
73
|
+
const win_1 = require("./win");
|
|
74
|
+
/**
|
|
75
|
+
* Absolute path to the standalone hook script shipped in scripts/
|
|
76
|
+
* alongside dist/ (package.json "files": ["dist","scripts", ...]).
|
|
77
|
+
* __dirname is dist/ at runtime in both the repo checkout (npm run build)
|
|
78
|
+
* and the installed package, so '..','scripts' resolves the same way in
|
|
79
|
+
* both.
|
|
80
|
+
*/
|
|
81
|
+
function precompactHookScriptPath() {
|
|
82
|
+
return path.join(__dirname, '..', 'scripts', 'precompact-capture.js');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The settings.json `hooks` fragment for a single PreCompact command hook.
|
|
86
|
+
* Pure/deterministic — the thing this task's Step 5 asks to unit-test as
|
|
87
|
+
* "the hook-config builder (settings JSON shape)".
|
|
88
|
+
*/
|
|
89
|
+
function buildPrecompactHookSettings(scriptPath) {
|
|
90
|
+
return {
|
|
91
|
+
hooks: {
|
|
92
|
+
PreCompact: [
|
|
93
|
+
{
|
|
94
|
+
matcher: '',
|
|
95
|
+
hooks: [
|
|
96
|
+
{ type: 'command', command: `node ${JSON.stringify(scriptPath)}` },
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Writes <cwd>/.claude/settings.json with the PreCompact hook merged in.
|
|
105
|
+
* Best-effort and idempotent: a fresh ephemeral session cwd has no prior
|
|
106
|
+
* settings.json, but this merges (never clobbers) in case a future caller
|
|
107
|
+
* pre-seeds project settings for some other reason. A write failure is
|
|
108
|
+
* logged and swallowed — the caller's spawn proceeds hook-less, the exact
|
|
109
|
+
* same outcome as an entity this function is never called for.
|
|
110
|
+
*/
|
|
111
|
+
function writeSessionPrecompactHook(cwd) {
|
|
112
|
+
try {
|
|
113
|
+
const dir = path.join(cwd, '.claude');
|
|
114
|
+
if (!fs.existsSync(dir))
|
|
115
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
116
|
+
const target = path.join(dir, 'settings.json');
|
|
117
|
+
let current = {};
|
|
118
|
+
if (fs.existsSync(target)) {
|
|
119
|
+
try {
|
|
120
|
+
current = JSON.parse(fs.readFileSync(target, 'utf-8'));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
current = {};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const hookSettings = buildPrecompactHookSettings(precompactHookScriptPath());
|
|
127
|
+
const currentHooks = (current.hooks && typeof current.hooks === 'object' ? current.hooks : {});
|
|
128
|
+
const next = { ...current, hooks: { ...currentHooks, ...hookSettings.hooks } };
|
|
129
|
+
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
130
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, 2), { mode: 0o600 });
|
|
131
|
+
(0, win_1.safeRenameSync)(tmp, target);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.error(`[precompact-hook] failed to write session settings for ${cwd}: ${err.message}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip embedded credentials from URLs before logging / persisting.
|
|
3
|
+
* Git error output regularly embeds the full remote URL with PAT,
|
|
4
|
+
* e.g. `fatal: unable to access 'https://x-access-token:ghp_xxxx@github.com/…'`
|
|
5
|
+
* — and `entity_projects.status_message` is shown verbatim in the UI.
|
|
6
|
+
*/
|
|
7
|
+
export declare function redactSecrets(text: string): string;
|
|
8
|
+
interface ProjectRow {
|
|
9
|
+
id: string;
|
|
10
|
+
runtime_id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
working_directory: string;
|
|
13
|
+
git_remote: string | null;
|
|
14
|
+
git_branch: string | null;
|
|
15
|
+
enabled_mcps: string[];
|
|
16
|
+
enabled_skills: string[];
|
|
17
|
+
enabled_packages: string[];
|
|
18
|
+
status: 'pending' | 'cloning' | 'installing' | 'ready' | 'failed';
|
|
19
|
+
status_message: string | null;
|
|
20
|
+
git_credential_id: string | null;
|
|
21
|
+
}
|
|
22
|
+
export declare function start(): void;
|
|
23
|
+
export declare function stop(): void;
|
|
24
|
+
export declare function fetchProject(projectId: string): Promise<ProjectRow | null>;
|
|
25
|
+
export {};
|
package/dist/projects.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Project lifecycle on the daemon side.
|
|
3
|
+
//
|
|
4
|
+
// Polls for pending projects, clones git remotes into a stable working
|
|
5
|
+
// directory under ~/.ainode/projects/<id>/, and reports
|
|
6
|
+
// status back to Supabase. Session-runner reads project.working_directory
|
|
7
|
+
// at spawn time so requests with project_id pick up the right cwd.
|
|
8
|
+
//
|
|
9
|
+
// Phase 3: just git clone. MCP / skill / package install lands in Phase 4.
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.redactSecrets = redactSecrets;
|
|
45
|
+
exports.start = start;
|
|
46
|
+
exports.stop = stop;
|
|
47
|
+
exports.fetchProject = fetchProject;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const child_process_1 = require("child_process");
|
|
51
|
+
const supabase_client_1 = require("./supabase-client");
|
|
52
|
+
const store_1 = require("./store");
|
|
53
|
+
const paths_1 = require("./paths");
|
|
54
|
+
// Project pickup polls every 5s (was 3s). Projects are not latency-
|
|
55
|
+
// sensitive: a clone takes seconds anyway, so 2 extra seconds before
|
|
56
|
+
// the daemon notices a new project is fine. Slower interval = less
|
|
57
|
+
// pooler pressure on Supabase.
|
|
58
|
+
const PROJECT_POLL_INTERVAL_MS = 5_000;
|
|
59
|
+
/**
|
|
60
|
+
* Strip embedded credentials from URLs before logging / persisting.
|
|
61
|
+
* Git error output regularly embeds the full remote URL with PAT,
|
|
62
|
+
* e.g. `fatal: unable to access 'https://x-access-token:ghp_xxxx@github.com/…'`
|
|
63
|
+
* — and `entity_projects.status_message` is shown verbatim in the UI.
|
|
64
|
+
*/
|
|
65
|
+
function redactSecrets(text) {
|
|
66
|
+
return text
|
|
67
|
+
// userinfo URL form
|
|
68
|
+
.replace(/https?:\/\/[^\s/@:]+(?::[^@\s/]+)?@/g, (m) => m.startsWith('https') ? 'https://<redacted>@' : 'http://<redacted>@')
|
|
69
|
+
// bare GitHub PATs leaking in messages
|
|
70
|
+
.replace(/\bghp_[A-Za-z0-9]{20,}/g, '<redacted-pat>')
|
|
71
|
+
.replace(/\bgho_[A-Za-z0-9]{20,}/g, '<redacted-oauth>')
|
|
72
|
+
.replace(/\bghu_[A-Za-z0-9]{20,}/g, '<redacted-user-oauth>')
|
|
73
|
+
.replace(/\bghs_[A-Za-z0-9]{20,}/g, '<redacted-server-oauth>');
|
|
74
|
+
}
|
|
75
|
+
let timer = null;
|
|
76
|
+
let stopped = false;
|
|
77
|
+
let inflight = false;
|
|
78
|
+
function token() {
|
|
79
|
+
return (0, store_1.readPairing)()?.daemonToken ?? null;
|
|
80
|
+
}
|
|
81
|
+
async function setStatus(id, fields) {
|
|
82
|
+
const t = token();
|
|
83
|
+
if (!t)
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
await (0, supabase_client_1.rpc)('runtime_project_status_update', {
|
|
87
|
+
p_token: t,
|
|
88
|
+
p_project_id: id,
|
|
89
|
+
p_status: fields.status ?? null,
|
|
90
|
+
p_status_message: fields.status_message ?? null,
|
|
91
|
+
p_working_directory: fields.working_directory ?? null,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.error('[projects] status update failed:', err.message);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function sanitiseName(name) {
|
|
99
|
+
return name.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 64) || 'project';
|
|
100
|
+
}
|
|
101
|
+
function projectDir(p) {
|
|
102
|
+
if (p.working_directory && p.working_directory.trim())
|
|
103
|
+
return p.working_directory;
|
|
104
|
+
return path.join(paths_1.RUNTIME_HOME, 'projects', `${sanitiseName(p.name)}-${p.id.slice(0, 8)}`);
|
|
105
|
+
}
|
|
106
|
+
function run(cmd, args, opts = {}) {
|
|
107
|
+
return new Promise(resolve => {
|
|
108
|
+
(0, child_process_1.execFile)(cmd, args, { cwd: opts.cwd, timeout: opts.timeoutMs ?? 5 * 60_000, maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
109
|
+
resolve({ ok: !err, stdout: stdout.toString(), stderr: stderr.toString() });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function cloneIfNeeded(p, dir) {
|
|
114
|
+
if (fs.existsSync(path.join(dir, '.git'))) {
|
|
115
|
+
return { ok: true, message: 'git repo already present' };
|
|
116
|
+
}
|
|
117
|
+
if (!p.git_remote) {
|
|
118
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
119
|
+
return { ok: true, message: 'no git remote; dir created' };
|
|
120
|
+
}
|
|
121
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true, mode: 0o700 });
|
|
122
|
+
// If a github_token credential is linked, fetch the PAT and inject it
|
|
123
|
+
// into the clone URL. The token only lives in argv for the clone
|
|
124
|
+
// process — we strip it from .git/config immediately after.
|
|
125
|
+
let remote = p.git_remote;
|
|
126
|
+
if (p.git_credential_id) {
|
|
127
|
+
try {
|
|
128
|
+
const pairing = (0, store_1.readPairing)();
|
|
129
|
+
if (pairing) {
|
|
130
|
+
const gitToken = await (0, supabase_client_1.rpc)('runtime_get_git_token', {
|
|
131
|
+
p_token: pairing.daemonToken,
|
|
132
|
+
p_project_id: p.id,
|
|
133
|
+
});
|
|
134
|
+
if (gitToken) {
|
|
135
|
+
// https://x-access-token:<PAT>@github.com/... works for both
|
|
136
|
+
// classic and fine-grained tokens.
|
|
137
|
+
const u = new URL(remote);
|
|
138
|
+
if (u.hostname === 'github.com') {
|
|
139
|
+
u.username = 'x-access-token';
|
|
140
|
+
u.password = gitToken;
|
|
141
|
+
remote = u.toString();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
console.error('[projects] failed to resolve git token, will try anonymous clone:', err.message);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Shallow + single-branch by default. Big repos otherwise time out.
|
|
151
|
+
// core.longpaths lifts Windows' 260-char MAX_PATH; no-op elsewhere.
|
|
152
|
+
const args = process.platform === 'win32' ? ['-c', 'core.longpaths=true'] : [];
|
|
153
|
+
args.push('clone', '--depth', '1', '--single-branch');
|
|
154
|
+
if (p.git_branch)
|
|
155
|
+
args.push('--branch', p.git_branch);
|
|
156
|
+
args.push(remote, dir);
|
|
157
|
+
const r = await run('git', args, { timeoutMs: 30 * 60_000 });
|
|
158
|
+
if (!r.ok) {
|
|
159
|
+
// Git error output often embeds the full remote URL with credentials
|
|
160
|
+
// (e.g. "fatal: unable to access 'https://x-access-token:ghp_xxx@…'").
|
|
161
|
+
// Scrub before persisting — we'd otherwise leak the PAT into
|
|
162
|
+
// entity_projects.status_message and show it in the desktop UI.
|
|
163
|
+
return { ok: false, message: redactSecrets((r.stderr || r.stdout || 'git clone failed').trim()).slice(0, 1024) };
|
|
164
|
+
}
|
|
165
|
+
// Strip the token from the saved remote so it doesn't sit on disk.
|
|
166
|
+
if (remote !== p.git_remote) {
|
|
167
|
+
try {
|
|
168
|
+
await run('git', ['-C', dir, 'remote', 'set-url', 'origin', p.git_remote], { timeoutMs: 10_000 });
|
|
169
|
+
}
|
|
170
|
+
catch { /* non-fatal */ }
|
|
171
|
+
}
|
|
172
|
+
return { ok: true, message: 'cloned' };
|
|
173
|
+
}
|
|
174
|
+
async function processProject(p) {
|
|
175
|
+
const dir = projectDir(p);
|
|
176
|
+
const result = await cloneIfNeeded(p, dir);
|
|
177
|
+
if (!result.ok) {
|
|
178
|
+
await setStatus(p.id, {
|
|
179
|
+
status: 'failed',
|
|
180
|
+
status_message: result.message ?? 'clone failed',
|
|
181
|
+
working_directory: dir,
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Phase 4 will run MCP / skill / package install here.
|
|
186
|
+
await setStatus(p.id, {
|
|
187
|
+
status: 'ready',
|
|
188
|
+
status_message: result.message ?? null,
|
|
189
|
+
working_directory: dir,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
async function tick() {
|
|
193
|
+
if (stopped || inflight)
|
|
194
|
+
return;
|
|
195
|
+
// Share the daemon-wide circuit breaker — when Supabase is in distress
|
|
196
|
+
// we don't want this poller piling on either.
|
|
197
|
+
if ((0, supabase_client_1.rpcShouldSkip)())
|
|
198
|
+
return;
|
|
199
|
+
const t = token();
|
|
200
|
+
if (!t)
|
|
201
|
+
return;
|
|
202
|
+
let row = null;
|
|
203
|
+
try {
|
|
204
|
+
const r = await (0, supabase_client_1.rpc)('runtime_pick_pending_project', { p_token: t });
|
|
205
|
+
row = r && r.id ? r : null;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
if (!(err instanceof supabase_client_1.RpcError && err.status === 401)) {
|
|
209
|
+
console.error('[projects] pick failed:', err.message);
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (!row)
|
|
214
|
+
return;
|
|
215
|
+
inflight = true;
|
|
216
|
+
try {
|
|
217
|
+
await processProject(row);
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
await setStatus(row.id, { status: 'failed', status_message: err.message ?? 'project setup failed' });
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
inflight = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function start() {
|
|
227
|
+
if (timer || stopped)
|
|
228
|
+
return;
|
|
229
|
+
void tick();
|
|
230
|
+
timer = setInterval(() => { void tick(); }, PROJECT_POLL_INTERVAL_MS);
|
|
231
|
+
}
|
|
232
|
+
function stop() {
|
|
233
|
+
stopped = true;
|
|
234
|
+
if (timer) {
|
|
235
|
+
clearInterval(timer);
|
|
236
|
+
timer = null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Daemon-side helper used by session-runner to resolve a project's cwd
|
|
240
|
+
// at request time (without depending on the project pump having run yet).
|
|
241
|
+
async function fetchProject(projectId) {
|
|
242
|
+
const t = token();
|
|
243
|
+
if (!t)
|
|
244
|
+
return null;
|
|
245
|
+
try {
|
|
246
|
+
const r = await (0, supabase_client_1.rpc)('runtime_get_project', { p_token: t, p_project_id: projectId });
|
|
247
|
+
return r && r.id ? r : null;
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
if (err instanceof supabase_client_1.RpcError && err.status === 401)
|
|
251
|
+
return null;
|
|
252
|
+
console.error('[projects] fetchProject failed:', err.message);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|