@primitive.ai/prim 0.1.0-alpha.28 → 0.1.0-alpha.29
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.
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getClient
|
|
2
|
+
getClient,
|
|
3
|
+
getSiteUrl
|
|
3
4
|
} from "./chunk-26VA3ADF.js";
|
|
4
5
|
import {
|
|
5
6
|
daemonRequest
|
|
@@ -9,9 +10,11 @@ import {
|
|
|
9
10
|
var DAEMON_HTTP_TIMEOUT_MS = 1e4;
|
|
10
11
|
var DAEMON_PROBE_TIMEOUT_MS = 250;
|
|
11
12
|
async function daemonOrDirect(method, params, direct) {
|
|
12
|
-
const fromDaemon = await daemonRequest(
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const fromDaemon = await daemonRequest(
|
|
14
|
+
method,
|
|
15
|
+
{ ...params, callerEnv: getSiteUrl() },
|
|
16
|
+
{ timeoutMs: DAEMON_PROBE_TIMEOUT_MS }
|
|
17
|
+
);
|
|
15
18
|
if (fromDaemon !== null) {
|
|
16
19
|
return fromDaemon;
|
|
17
20
|
}
|
package/dist/daemon/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getClient,
|
|
4
|
+
getSiteUrl,
|
|
4
5
|
getTokenExpiresAt,
|
|
5
6
|
refreshToken
|
|
6
7
|
} from "../chunk-26VA3ADF.js";
|
|
@@ -10,6 +11,24 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "
|
|
|
10
11
|
import { createServer } from "net";
|
|
11
12
|
import { homedir } from "os";
|
|
12
13
|
import { join } from "path";
|
|
14
|
+
|
|
15
|
+
// src/daemon/env-binding.ts
|
|
16
|
+
function normalizeApiUrl(url) {
|
|
17
|
+
return url.trim().replace(/\/+$/, "");
|
|
18
|
+
}
|
|
19
|
+
function apiUrlsMatch(a, b) {
|
|
20
|
+
return normalizeApiUrl(a) === normalizeApiUrl(b);
|
|
21
|
+
}
|
|
22
|
+
function isCrossEnv(callerEnv, boundEnv) {
|
|
23
|
+
return typeof callerEnv === "string" && !apiUrlsMatch(callerEnv, boundEnv);
|
|
24
|
+
}
|
|
25
|
+
function assertCallerEnvMatches(callerEnv, boundEnv) {
|
|
26
|
+
if (isCrossEnv(callerEnv, boundEnv)) {
|
|
27
|
+
throw new Error(`env mismatch: daemon is bound to ${boundEnv}, caller targets ${callerEnv}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/daemon/server.ts
|
|
13
32
|
var CONFIG_DIR = join(homedir(), ".config", "prim");
|
|
14
33
|
var SOCK_PATH = join(CONFIG_DIR, "sock");
|
|
15
34
|
var PID_PATH = join(CONFIG_DIR, "daemon.pid");
|
|
@@ -94,6 +113,7 @@ async function ensureTokenFresh() {
|
|
|
94
113
|
}
|
|
95
114
|
}
|
|
96
115
|
async function handleConflictCheck(params) {
|
|
116
|
+
assertCallerEnvMatches(params.callerEnv, getSiteUrl());
|
|
97
117
|
if (typeof params.file !== "string") {
|
|
98
118
|
throw new Error("conflict_check requires `file: string`");
|
|
99
119
|
}
|
|
@@ -111,18 +131,31 @@ function assertEndpointPath(path, endpoint) {
|
|
|
111
131
|
}
|
|
112
132
|
}
|
|
113
133
|
async function proxyGet(params, allowedPrefix) {
|
|
134
|
+
assertCallerEnvMatches(params.callerEnv, getSiteUrl());
|
|
114
135
|
const path = pathParam(params);
|
|
115
136
|
assertEndpointPath(path, allowedPrefix);
|
|
116
137
|
return await client.get(path, { signal: AbortSignal.timeout(HTTP_PROXY_TIMEOUT_MS) });
|
|
117
138
|
}
|
|
118
|
-
function handleStatusSnapshot() {
|
|
119
|
-
const
|
|
120
|
-
const presenceStale = lastOkAtLocal !== void 0 && !presenceFresh;
|
|
121
|
-
return {
|
|
139
|
+
function handleStatusSnapshot(params) {
|
|
140
|
+
const base = {
|
|
122
141
|
pid: process.pid,
|
|
123
142
|
uptimeMs: Date.now() - startedAt,
|
|
124
143
|
sessionId: activeSessionId,
|
|
125
|
-
lastHeartbeatAt
|
|
144
|
+
lastHeartbeatAt
|
|
145
|
+
};
|
|
146
|
+
if (isCrossEnv(params.callerEnv, getSiteUrl())) {
|
|
147
|
+
return {
|
|
148
|
+
...base,
|
|
149
|
+
onlineCount: void 0,
|
|
150
|
+
onlineNames: void 0,
|
|
151
|
+
presenceStale: false,
|
|
152
|
+
envMismatch: true
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const presenceFresh = lastOkAtLocal !== void 0 && Date.now() - lastOkAtLocal < PRESENCE_FRESH_WINDOW_MS;
|
|
156
|
+
const presenceStale = lastOkAtLocal !== void 0 && !presenceFresh;
|
|
157
|
+
return {
|
|
158
|
+
...base,
|
|
126
159
|
// Withhold a frozen count/names once they're no longer fresh; the
|
|
127
160
|
// statusline shows "presence: stale" rather than a confident, wrong list.
|
|
128
161
|
onlineCount: presenceFresh ? lastOnlineCount : void 0,
|
|
@@ -166,7 +199,7 @@ async function dispatchRequest(req) {
|
|
|
166
199
|
return { id, ok: true, result: { ack: true } };
|
|
167
200
|
}
|
|
168
201
|
case "status_snapshot":
|
|
169
|
-
return { id, ok: true, result: handleStatusSnapshot() };
|
|
202
|
+
return { id, ok: true, result: handleStatusSnapshot(req.params ?? {}) };
|
|
170
203
|
case "ping":
|
|
171
204
|
return { id, ok: true, result: { pong: true } };
|
|
172
205
|
default:
|
package/dist/hooks/pre-commit.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
getClient
|
|
3
|
+
getClient,
|
|
4
|
+
getSiteUrl
|
|
4
5
|
} from "../chunk-26VA3ADF.js";
|
|
5
6
|
import {
|
|
6
7
|
parseAgent
|
|
@@ -199,7 +200,9 @@ function emit(output) {
|
|
|
199
200
|
async function checkOneFile(file) {
|
|
200
201
|
const fromDaemon = await daemonRequest(
|
|
201
202
|
"conflict_check",
|
|
202
|
-
|
|
203
|
+
// callerEnv lets a staging-bound daemon refuse a prod-context gate check
|
|
204
|
+
// (and vice versa) so we fall through to a direct call against our own env.
|
|
205
|
+
{ file, callerEnv: getSiteUrl() },
|
|
203
206
|
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
204
207
|
);
|
|
205
208
|
if (fromDaemon) {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getSiteUrl
|
|
4
|
+
} from "../chunk-26VA3ADF.js";
|
|
2
5
|
import {
|
|
3
6
|
parseAgent
|
|
4
7
|
} from "../chunk-7YRBACIE.js";
|
|
@@ -71,7 +74,9 @@ async function main() {
|
|
|
71
74
|
if (parseAgent(process.argv) === "codex") {
|
|
72
75
|
const snapshot = await daemonRequest(
|
|
73
76
|
"status_snapshot",
|
|
74
|
-
|
|
77
|
+
// callerEnv: a cross-env daemon withholds onlineCount, so a prod Codex
|
|
78
|
+
// session never gets a staging daemon's team count injected.
|
|
79
|
+
{ callerEnv: getSiteUrl() },
|
|
75
80
|
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
76
81
|
);
|
|
77
82
|
if (snapshot && !snapshot.presenceStale && typeof snapshot.onlineCount === "number") {
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
checkAffectedDecisions,
|
|
11
11
|
daemonOrDirectGet,
|
|
12
12
|
formatDecisionsWarning
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-UWAJCF7P.js";
|
|
14
14
|
import {
|
|
15
15
|
HttpError,
|
|
16
16
|
REFRESH_TOKEN_PATH,
|
|
@@ -851,7 +851,7 @@ ${line("project", result.project)}`);
|
|
|
851
851
|
|
|
852
852
|
// src/commands/daemon.ts
|
|
853
853
|
import { spawn } from "child_process";
|
|
854
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
854
|
+
import { closeSync as closeSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync as openSync2, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
855
855
|
import { homedir as homedir3 } from "os";
|
|
856
856
|
import { join as join4 } from "path";
|
|
857
857
|
|
|
@@ -871,8 +871,12 @@ function formatTeammates(names, cap) {
|
|
|
871
871
|
|
|
872
872
|
// src/commands/daemon.ts
|
|
873
873
|
var DAEMON_BIN = "prim-daemon-server";
|
|
874
|
-
var
|
|
875
|
-
var
|
|
874
|
+
var CONFIG_DIR = join4(homedir3(), ".config", "prim");
|
|
875
|
+
var PID_PATH = join4(CONFIG_DIR, "daemon.pid");
|
|
876
|
+
var SOCK_PATH = join4(CONFIG_DIR, "sock");
|
|
877
|
+
var LOG_PATH = join4(CONFIG_DIR, "daemon.log");
|
|
878
|
+
var CONFIG_DIR_MODE = 448;
|
|
879
|
+
var LOG_FILE_MODE = 384;
|
|
876
880
|
var STOP_TIMEOUT_MS = 5e3;
|
|
877
881
|
var STOP_POLL_MS = 100;
|
|
878
882
|
var STATUS_PROBE_TIMEOUT_MS = 500;
|
|
@@ -921,6 +925,12 @@ function spawnDaemon(options) {
|
|
|
921
925
|
const file = binFile(DAEMON_BIN);
|
|
922
926
|
return file ? spawn(process.execPath, [file], options) : spawn(DAEMON_BIN, [], options);
|
|
923
927
|
}
|
|
928
|
+
function openDaemonLog(configDir = CONFIG_DIR) {
|
|
929
|
+
if (!existsSync4(configDir)) {
|
|
930
|
+
mkdirSync3(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
931
|
+
}
|
|
932
|
+
return openSync2(join4(configDir, "daemon.log"), "a", LOG_FILE_MODE);
|
|
933
|
+
}
|
|
924
934
|
async function waitForReady() {
|
|
925
935
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
926
936
|
while (Date.now() < deadline) {
|
|
@@ -949,8 +959,20 @@ async function daemonStart(opts) {
|
|
|
949
959
|
});
|
|
950
960
|
return;
|
|
951
961
|
}
|
|
952
|
-
|
|
962
|
+
let logFd;
|
|
963
|
+
try {
|
|
964
|
+
logFd = openDaemonLog();
|
|
965
|
+
} catch {
|
|
966
|
+
logFd = void 0;
|
|
967
|
+
}
|
|
968
|
+
const child = spawnDaemon({
|
|
969
|
+
detached: true,
|
|
970
|
+
stdio: logFd === void 0 ? ["ignore", "ignore", "ignore"] : ["ignore", logFd, logFd]
|
|
971
|
+
});
|
|
953
972
|
child.unref();
|
|
973
|
+
if (logFd !== void 0) {
|
|
974
|
+
closeSync2(logFd);
|
|
975
|
+
}
|
|
954
976
|
const live = await waitForReady();
|
|
955
977
|
if (live) {
|
|
956
978
|
const after = readPidfile();
|
|
@@ -962,7 +984,7 @@ async function daemonStart(opts) {
|
|
|
962
984
|
return;
|
|
963
985
|
}
|
|
964
986
|
process.stderr.write(
|
|
965
|
-
`[prim] \u2717 daemon start: spawned but the socket did not respond within ${READY_TIMEOUT_MS}ms (check that \`${DAEMON_BIN}\` resolves, and see
|
|
987
|
+
`[prim] \u2717 daemon start: spawned but the socket did not respond within ${READY_TIMEOUT_MS}ms (check that \`${DAEMON_BIN}\` resolves, and see ${LOG_PATH})
|
|
966
988
|
`
|
|
967
989
|
);
|
|
968
990
|
console.log(JSON.stringify({ started: false }, null, 2));
|
|
@@ -1862,7 +1884,7 @@ function registerDecisionsCommands(program2) {
|
|
|
1862
1884
|
|
|
1863
1885
|
// src/commands/hooks.ts
|
|
1864
1886
|
import { execSync as execSync2 } from "child_process";
|
|
1865
|
-
import { existsSync as existsSync5, mkdirSync as
|
|
1887
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1866
1888
|
import { resolve } from "path";
|
|
1867
1889
|
import { Option } from "commander";
|
|
1868
1890
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
@@ -1964,7 +1986,7 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
|
1964
1986
|
const hooksDir = resolve(gitRoot, ".git", "hooks");
|
|
1965
1987
|
const hookPath = resolve(hooksDir, spec.hookName);
|
|
1966
1988
|
if (!existsSync5(hooksDir)) {
|
|
1967
|
-
|
|
1989
|
+
mkdirSync4(hooksDir, { recursive: true });
|
|
1968
1990
|
}
|
|
1969
1991
|
if (existsSync5(hookPath)) {
|
|
1970
1992
|
const existing = readFileSync5(hookPath, "utf-8");
|
|
@@ -2045,7 +2067,7 @@ function registerHooksCommands(program2) {
|
|
|
2045
2067
|
}
|
|
2046
2068
|
|
|
2047
2069
|
// src/commands/moves.ts
|
|
2048
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
2070
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2049
2071
|
import { join as join5 } from "path";
|
|
2050
2072
|
|
|
2051
2073
|
// src/flusher.ts
|
|
@@ -2157,7 +2179,7 @@ function registerMovesCommands(program2) {
|
|
|
2157
2179
|
moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
|
|
2158
2180
|
const dir = join5(process.cwd(), ".prim");
|
|
2159
2181
|
if (!existsSync6(dir)) {
|
|
2160
|
-
|
|
2182
|
+
mkdirSync5(dir, { recursive: true, mode: DIR_MODE });
|
|
2161
2183
|
}
|
|
2162
2184
|
const file = join5(process.cwd(), WORKSPACE_FILE);
|
|
2163
2185
|
writeFileSync4(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
|
|
@@ -2261,7 +2283,7 @@ function registerReconcileCommands(program2) {
|
|
|
2261
2283
|
// src/commands/session.ts
|
|
2262
2284
|
import {
|
|
2263
2285
|
existsSync as existsSync7,
|
|
2264
|
-
mkdirSync as
|
|
2286
|
+
mkdirSync as mkdirSync6,
|
|
2265
2287
|
readFileSync as readFileSync6,
|
|
2266
2288
|
readdirSync,
|
|
2267
2289
|
unlinkSync as unlinkSync5,
|
|
@@ -2272,7 +2294,7 @@ var DIR_MODE2 = 448;
|
|
|
2272
2294
|
var FILE_MODE3 = 384;
|
|
2273
2295
|
function ensureDir() {
|
|
2274
2296
|
if (!existsSync7(SESSIONS_DIR)) {
|
|
2275
|
-
|
|
2297
|
+
mkdirSync6(SESSIONS_DIR, { recursive: true, mode: DIR_MODE2 });
|
|
2276
2298
|
}
|
|
2277
2299
|
}
|
|
2278
2300
|
function markerPath(sessionId) {
|
|
@@ -2413,10 +2435,10 @@ function registerSetupCommand(program2) {
|
|
|
2413
2435
|
|
|
2414
2436
|
// src/commands/skill.ts
|
|
2415
2437
|
import {
|
|
2416
|
-
closeSync as
|
|
2438
|
+
closeSync as closeSync3,
|
|
2417
2439
|
existsSync as existsSync8,
|
|
2418
2440
|
fsyncSync as fsyncSync2,
|
|
2419
|
-
openSync as
|
|
2441
|
+
openSync as openSync3,
|
|
2420
2442
|
readFileSync as readFileSync7,
|
|
2421
2443
|
renameSync as renameSync3,
|
|
2422
2444
|
writeFileSync as writeFileSync6
|
|
@@ -2474,11 +2496,11 @@ function removeBlock(existing) {
|
|
|
2474
2496
|
function atomicWrite2(target, content) {
|
|
2475
2497
|
const tmp = `${target}.tmp`;
|
|
2476
2498
|
writeFileSync6(tmp, content);
|
|
2477
|
-
const fd =
|
|
2499
|
+
const fd = openSync3(tmp, "r+");
|
|
2478
2500
|
try {
|
|
2479
2501
|
fsyncSync2(fd);
|
|
2480
2502
|
} finally {
|
|
2481
|
-
|
|
2503
|
+
closeSync3(fd);
|
|
2482
2504
|
}
|
|
2483
2505
|
renameSync3(tmp, target);
|
|
2484
2506
|
}
|
|
@@ -2602,13 +2624,18 @@ async function renderStatusline() {
|
|
|
2602
2624
|
const version = readPackageVersion();
|
|
2603
2625
|
const snapshot = await daemonRequest(
|
|
2604
2626
|
"status_snapshot",
|
|
2605
|
-
|
|
2627
|
+
// callerEnv lets the daemon withhold presence when it is bound to a different
|
|
2628
|
+
// deployment than this statusline targets.
|
|
2629
|
+
{ callerEnv: getSiteUrl() },
|
|
2606
2630
|
{ timeoutMs: STATUSLINE_TIMEOUT_MS }
|
|
2607
2631
|
);
|
|
2608
2632
|
if (!snapshot) {
|
|
2609
2633
|
debug("daemon snapshot missing");
|
|
2610
2634
|
return `primitive ${version} (daemon: down)`;
|
|
2611
2635
|
}
|
|
2636
|
+
if (snapshot.envMismatch) {
|
|
2637
|
+
return `primitive ${version} (daemon: live \xB7 presence: other env)`;
|
|
2638
|
+
}
|
|
2612
2639
|
if (snapshot.presenceStale) {
|
|
2613
2640
|
return `primitive ${version} (daemon: live \xB7 presence: stale)`;
|
|
2614
2641
|
}
|
package/package.json
CHANGED