in2-agent-server 0.20260821.185429
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 +369 -0
- package/agents/plan-requirement.md +32 -0
- package/dist/campfire/configItem.js +201 -0
- package/dist/campfire/configItemDescription.js +379 -0
- package/dist/campfire/deriveFromBundle.js +1013 -0
- package/dist/cli.js +465 -0
- package/dist/core/betaFeedback.js +493 -0
- package/dist/core/chapterInstructions.js +473 -0
- package/dist/core/claudeAssets.js +61 -0
- package/dist/core/gate.js +201 -0
- package/dist/core/ids.js +9 -0
- package/dist/core/install.js +60 -0
- package/dist/core/operatorQa.js +462 -0
- package/dist/core/orgCallMeter.js +178 -0
- package/dist/core/parcels.js +1235 -0
- package/dist/core/paths.js +105 -0
- package/dist/core/phases.js +225 -0
- package/dist/core/planFile.js +251 -0
- package/dist/core/planFragments.js +119 -0
- package/dist/core/readsLog.js +398 -0
- package/dist/core/registration.js +24 -0
- package/dist/core/result.js +38 -0
- package/dist/core/resume.js +233 -0
- package/dist/core/revisionChain.js +351 -0
- package/dist/core/richText.js +61 -0
- package/dist/core/semanticHash.js +89 -0
- package/dist/core/sessionOrg.js +60 -0
- package/dist/core/sfQuery.js +19 -0
- package/dist/core/spawn.js +182 -0
- package/dist/core/supervisorContract.js +230 -0
- package/dist/core/telemetry.js +1297 -0
- package/dist/core/telemetryContext.js +154 -0
- package/dist/core/telemetrySpool.js +244 -0
- package/dist/core/transcriptUpload.js +516 -0
- package/dist/core/update.js +363 -0
- package/dist/core/viewer.js +117 -0
- package/dist/core/workspace.js +92 -0
- package/dist/core/writeGrants.js +93 -0
- package/dist/core/writebackManifest.js +92 -0
- package/dist/direct/applyLoop.js +2516 -0
- package/dist/direct/batchReport.js +510 -0
- package/dist/direct/comparatorNoise.js +75 -0
- package/dist/direct/containers.js +85 -0
- package/dist/direct/contamination.js +221 -0
- package/dist/direct/crud.js +56 -0
- package/dist/direct/destructive.js +168 -0
- package/dist/direct/knownTokens.js +94 -0
- package/dist/direct/lint.js +382 -0
- package/dist/direct/metadataSchema.generated.json +1 -0
- package/dist/direct/metadataSchema.js +179 -0
- package/dist/direct/operatorPage.js +349 -0
- package/dist/direct/orgRepo.js +1531 -0
- package/dist/direct/orgRepoSpawnCount.js +21 -0
- package/dist/direct/plannedScope.js +119 -0
- package/dist/direct/preImageTargets.js +63 -0
- package/dist/direct/refVerify.js +191 -0
- package/dist/direct/registry.js +129 -0
- package/dist/direct/removals.js +262 -0
- package/dist/direct/rollback.js +1513 -0
- package/dist/direct/rollbackRun.js +1360 -0
- package/dist/direct/sessionProjection.js +126 -0
- package/dist/direct/snapshotComparability.js +165 -0
- package/dist/direct/sourceTree.js +113 -0
- package/dist/direct/unstatedChanges.js +106 -0
- package/dist/direct/waveAssembly.js +556 -0
- package/dist/direct/waveCounts.js +19 -0
- package/dist/direct/waveDerivation.js +152 -0
- package/dist/direct/waveGrade.js +185 -0
- package/dist/index.js +90 -0
- package/dist/orgread/customFieldSnapshot.js +48 -0
- package/dist/orgread/fieldMetadata.js +178 -0
- package/dist/orgread/genericSnapshot.js +61 -0
- package/dist/orgread/layoutSnapshot.js +151 -0
- package/dist/orgread/salesforceOrgReader.js +184 -0
- package/dist/orgread/snapshotTypes.js +9 -0
- package/dist/projection/bridge.js +26 -0
- package/dist/projection/diff.js +141 -0
- package/dist/projection/mapmd.js +213 -0
- package/dist/projection/nameAmbiguity.js +106 -0
- package/dist/projection/precedent.js +136 -0
- package/dist/projection/project.js +214 -0
- package/dist/projection/scope.js +110 -0
- package/dist/projection/types.js +5 -0
- package/dist/tools/analyze.js +322 -0
- package/dist/tools/auth.js +915 -0
- package/dist/tools/campfire.js +1742 -0
- package/dist/tools/closeBatch.js +154 -0
- package/dist/tools/closeText.js +35 -0
- package/dist/tools/direct.js +1613 -0
- package/dist/tools/feedback.js +318 -0
- package/dist/tools/operator.js +287 -0
- package/dist/tools/org.js +739 -0
- package/dist/tools/phase.js +249 -0
- package/dist/tools/projection.js +172 -0
- package/dist/tools/session.js +313 -0
- package/dist/tools/update.js +82 -0
- package/dist/tools/waves.js +793 -0
- package/package.json +48 -0
- package/reference/campfire-configuration-items-guide.md +710 -0
- package/reference/deployability-cribs.md +119 -0
- package/reference/operator-voice.md +141 -0
- package/reference/org-read-cribs.md +34 -0
- package/reference/scoped-dml-interim.md +68 -0
- package/reference/supervisor-playbook.md +313 -0
- package/reference/templates.md +233 -0
- package/skills/in2-delivery/SKILL.md +322 -0
- package/skills/in2-delivery/references/campfire-configuration-items-guide.md +710 -0
- package/skills/in2-delivery/references/deployability-cribs.md +119 -0
- package/skills/in2-delivery/references/operator-voice.md +141 -0
- package/skills/in2-delivery/references/org-read-cribs.md +34 -0
- package/skills/in2-delivery/references/scoped-dml-interim.md +68 -0
- package/skills/in2-delivery/references/templates.md +233 -0
- package/skills/plan-requirement/SKILL.md +188 -0
- package/viewer/index.html +899 -0
- package/viewer/server.mjs +433 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { runProcess, sfCliEnv } from "./spawn.js";
|
|
2
|
+
import { isOrgGranted } from "./writeGrants.js";
|
|
3
|
+
// Execution-gate semantics copied from apps/in2-interactive/lib/flue/execution-gate.ts:
|
|
4
|
+
// default-closed, enforced in code before any mutating handler runs, org allowlist by Org ID.
|
|
5
|
+
const T_PINTO_ORG_ID = "00Dfn00000BFjbBEAT";
|
|
6
|
+
export class GateError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.name = "GateError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const orgIdCache = new Map();
|
|
15
|
+
export async function defaultOrgIdResolver(alias) {
|
|
16
|
+
const cached = orgIdCache.get(alias);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
let run = await runProcess("sf", ["org", "display", "--target-org", alias, "--json"], {
|
|
20
|
+
env: sfCliEnv(),
|
|
21
|
+
timeoutMs: 60_000
|
|
22
|
+
});
|
|
23
|
+
if (run.timedOut) {
|
|
24
|
+
// Read-only — one retry on a stalled CLI child is safe (live-caught intermittent hang).
|
|
25
|
+
run = await runProcess("sf", ["org", "display", "--target-org", alias, "--json"], {
|
|
26
|
+
env: sfCliEnv(),
|
|
27
|
+
timeoutMs: 60_000
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (run.exitCode !== 0)
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(run.stdout);
|
|
34
|
+
const id = parsed.result?.id;
|
|
35
|
+
if (typeof id === "string" && id.length >= 15) {
|
|
36
|
+
orgIdCache.set(alias, id);
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* DELIBERATELY UNCACHED, unlike `defaultOrgIdResolver` above.
|
|
47
|
+
*
|
|
48
|
+
* Which org an alias points at is stable for a process lifetime, so memoizing it is free. Whether
|
|
49
|
+
* that org's credential still works is the opposite kind of fact: it is exactly what changes
|
|
50
|
+
* underneath a long run, and the whole reason this probe exists. `orgIdCache` memoizing a successful
|
|
51
|
+
* resolution is already why a mid-run auth death moves the failure downstream into the SOQL/PATCH
|
|
52
|
+
* itself, as a less legible error, once the gate has passed from cache once.
|
|
53
|
+
*/
|
|
54
|
+
export async function defaultOrgLivenessProbe(alias) {
|
|
55
|
+
const run = await runProcess("sf", ["org", "display", "--target-org", alias, "--json"], {
|
|
56
|
+
env: sfCliEnv(),
|
|
57
|
+
timeoutMs: 60_000
|
|
58
|
+
});
|
|
59
|
+
if (run.timedOut)
|
|
60
|
+
return null;
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(run.stdout);
|
|
63
|
+
if (parsed.result)
|
|
64
|
+
return { ...(parsed.result.connectedStatus ? { connectedStatus: parsed.result.connectedStatus } : {}) };
|
|
65
|
+
// A parseable answer with no result is the CLI saying it has no auth for this alias — a real
|
|
66
|
+
// answer, and a negative one. Distinct from the unparseable case below, which is no answer.
|
|
67
|
+
return parsed.message ? { connectedStatus: parsed.message } : null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function normalizeOrgId(orgId) {
|
|
74
|
+
return orgId.slice(0, 15);
|
|
75
|
+
}
|
|
76
|
+
// Allowlist wildcard for INTERNAL DEV TESTING. The per-org allowlist is fixed at server spawn,
|
|
77
|
+
// but an org's id is only knowable AFTER logging into it — so testing a fresh org forced a
|
|
78
|
+
// re-registration plus a new session every time, which is pure drag when the whole point is to
|
|
79
|
+
// try the harness against many orgs. Setting CC_MCP_DIRECT_ALLOWLIST=* opts out ONCE: any org
|
|
80
|
+
// the sf CLI is authenticated to becomes writable, no re-registration.
|
|
81
|
+
//
|
|
82
|
+
// This deliberately stays an explicit opt-in rather than the default — default-closed is worth
|
|
83
|
+
// keeping — and it NEVER overrides the consultant-org refusal, which is checked first and by
|
|
84
|
+
// org id, so the real shared org is unreachable no matter what the allowlist says.
|
|
85
|
+
const ALLOWLIST_WILDCARD = "*";
|
|
86
|
+
function allowlistIsOpen(raw) {
|
|
87
|
+
if (!raw)
|
|
88
|
+
return false;
|
|
89
|
+
return raw.split(",").map((entry) => entry.trim()).includes(ALLOWLIST_WILDCARD);
|
|
90
|
+
}
|
|
91
|
+
export function directAllowlistOpen() {
|
|
92
|
+
return allowlistIsOpen(process.env.CC_MCP_DIRECT_ALLOWLIST);
|
|
93
|
+
}
|
|
94
|
+
export function assertTargetOrg(targetOrg) {
|
|
95
|
+
if (!targetOrg || !targetOrg.trim()) {
|
|
96
|
+
throw new GateError("TARGET_ORG_REQUIRED", "Every live-org tool call requires an explicit targetOrg alias. There is no default org.");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// The consultant/Campfire org (VF-partial) is REAL and SHARED — writes there have the
|
|
100
|
+
// largest blast radius in the system, larger than target-org deploys. They ride their
|
|
101
|
+
// OWN gate, deliberately separate from the direct tier: opening live deploys to a mock
|
|
102
|
+
// client org must never implicitly authorize writes to the shared consultant org.
|
|
103
|
+
export const VF_PARTIAL_ORG_ID = "00DRu00000Mpj50MAB";
|
|
104
|
+
// True when an org id is the REAL shared consultant org, which the direct tier refuses
|
|
105
|
+
// regardless of allowlist. Exported so guidance never suggests allowlisting it.
|
|
106
|
+
export function isConsultantOrg(orgId) {
|
|
107
|
+
return orgId.slice(0, 15) === VF_PARTIAL_ORG_ID.slice(0, 15);
|
|
108
|
+
}
|
|
109
|
+
export function campfireWriteEnabled() {
|
|
110
|
+
return process.env.CC_MCP_CAMPFIRE_WRITE === "1";
|
|
111
|
+
}
|
|
112
|
+
export function campfireAllowlistedOrgIds() {
|
|
113
|
+
const raw = process.env.CC_MCP_CAMPFIRE_ALLOWLIST?.trim();
|
|
114
|
+
const ids = raw ? raw.split(",").map((entry) => entry.trim()).filter(Boolean) : [VF_PARTIAL_ORG_ID];
|
|
115
|
+
return ids.map(normalizeOrgId);
|
|
116
|
+
}
|
|
117
|
+
export async function assertCampfireWriteAllowed(consultantOrg, resolveOrgId = defaultOrgIdResolver) {
|
|
118
|
+
assertTargetOrg(consultantOrg);
|
|
119
|
+
if (!campfireWriteEnabled()) {
|
|
120
|
+
throw new GateError("CAMPFIRE_WRITE_NOT_AUTHORIZED", "Campfire writes are default-closed (the consultant org is real and shared). Start the server with CC_MCP_CAMPFIRE_WRITE=1 to enable them — this gate is deliberately separate from CC_MCP_EXECUTION.");
|
|
121
|
+
}
|
|
122
|
+
const orgId = await resolveOrgId(consultantOrg);
|
|
123
|
+
if (!orgId) {
|
|
124
|
+
throw new GateError("ORG_ID_UNRESOLVED", `Could not resolve the Org ID for consultant alias ${JSON.stringify(consultantOrg)}. Refusing fail-closed.`);
|
|
125
|
+
}
|
|
126
|
+
const allowlist = campfireAllowlistedOrgIds();
|
|
127
|
+
if (!allowlist.includes(normalizeOrgId(orgId))) {
|
|
128
|
+
throw new GateError("CONSULTANT_ORG_NOT_ALLOWLISTED", `Org ${orgId} (alias ${consultantOrg}) is not on the Campfire write allowlist [${allowlist.join(", ")}].`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Direct working tier (Amendment A, plan 2026-07-22-001): raw metadata writes with
|
|
132
|
+
// mandatory lint -> checkOnly -> deploy -> re-project -> three-way diff. Own gate,
|
|
133
|
+
// own allowlist (default T-Pinto). The consultant org is HARD-refused by id even if
|
|
134
|
+
// allowlisted — the direct tier must never touch the shared org.
|
|
135
|
+
export function directWriteEnabled() {
|
|
136
|
+
return process.env.CC_MCP_DIRECT_WRITE === "1";
|
|
137
|
+
}
|
|
138
|
+
export function directAllowlistedOrgIds() {
|
|
139
|
+
const raw = process.env.CC_MCP_DIRECT_ALLOWLIST?.trim();
|
|
140
|
+
const ids = raw ? raw.split(",").map((entry) => entry.trim()).filter(Boolean) : [T_PINTO_ORG_ID];
|
|
141
|
+
return ids.map(normalizeOrgId);
|
|
142
|
+
}
|
|
143
|
+
export async function assertDirectWriteAllowed(targetOrg, resolveOrgId = defaultOrgIdResolver, orgIsGranted = isOrgGranted, probeLiveness = defaultOrgLivenessProbe) {
|
|
144
|
+
assertTargetOrg(targetOrg);
|
|
145
|
+
if (!directWriteEnabled()) {
|
|
146
|
+
throw new GateError("DIRECT_WRITE_NOT_AUTHORIZED", "The direct working tier is default-closed. Start the server with CC_MCP_DIRECT_WRITE=1 to enable it (org authority then comes from activate_session, or from CC_MCP_DIRECT_ALLOWLIST for internal testing).");
|
|
147
|
+
}
|
|
148
|
+
const orgId = await resolveOrgId(targetOrg);
|
|
149
|
+
if (!orgId) {
|
|
150
|
+
throw new GateError("ORG_ID_UNRESOLVED", `Could not resolve the Org ID for alias ${JSON.stringify(targetOrg)}. Refusing fail-closed.`);
|
|
151
|
+
}
|
|
152
|
+
// The consultant refusal is checked BEFORE any grant or wildcard on purpose — no allowlist
|
|
153
|
+
// setting and no activation can ever make the real shared org writable by this tier.
|
|
154
|
+
if (normalizeOrgId(orgId) === normalizeOrgId(VF_PARTIAL_ORG_ID)) {
|
|
155
|
+
throw new GateError("CONSULTANT_ORG_FORBIDDEN", "The consultant org (VF-partial) is hard-refused by the direct tier regardless of allowlist — it is real and shared.");
|
|
156
|
+
}
|
|
157
|
+
// The customer path: activate_session authenticated THIS org and verified the CLI landed on the
|
|
158
|
+
// org the launch service named. That is a narrower and better-evidenced claim than any env var —
|
|
159
|
+
// it names one org, proven at the moment of authentication, rather than a list guessed at spawn.
|
|
160
|
+
//
|
|
161
|
+
// AUTHORITY IS DECIDED BEFORE LIVENESS, and the order is doctrine rather than taste: the two
|
|
162
|
+
// refusals send the operator to different places. DIRECT_ORG_NOT_ALLOWLISTED means "this install
|
|
163
|
+
// was never authorized here" and the fix is a fresh Campfire activation; ORG_AUTH_EXPIRED means
|
|
164
|
+
// "you hold authority over an org you can no longer reach" and the fix is a re-auth of an org they
|
|
165
|
+
// already have. Reporting the second when the first is true has them re-authenticate their way
|
|
166
|
+
// into the same refusal.
|
|
167
|
+
const authorized = (await orgIsGranted(orgId)) ||
|
|
168
|
+
directAllowlistOpen() || // CC_MCP_DIRECT_ALLOWLIST=* — INTERNAL DEV ONLY, never shipped
|
|
169
|
+
directAllowlistedOrgIds().includes(normalizeOrgId(orgId));
|
|
170
|
+
if (!authorized) {
|
|
171
|
+
const allowlist = directAllowlistedOrgIds();
|
|
172
|
+
throw new GateError("DIRECT_ORG_NOT_ALLOWLISTED", `Org ${orgId} (alias ${targetOrg}) is not writable by this install. It has no activation grant and is not on CC_MCP_DIRECT_ALLOWLIST [${allowlist.join(", ")}].\n` +
|
|
173
|
+
`Normal fix: re-send the requirements from Campfire and redeem the code with activate_session — that authenticates the org and grants write to exactly it.\n` +
|
|
174
|
+
`Internal testing only: add the id to CC_MCP_DIRECT_ALLOWLIST (server env is fixed at spawn, so that needs a re-register + fresh session).`);
|
|
175
|
+
}
|
|
176
|
+
// THE GRANT AND THE TOKEN IT AUTHORIZES ARE ON DIFFERENT CLOCKS, and until this check existed the
|
|
177
|
+
// gate read only the slower one. A grant lasts 30 days (`writeGrants.ts`); the activation refresh
|
|
178
|
+
// token it authorizes lasts about ONE (`docs/orgs-and-credentials.md`). So for 29 of 30 days an
|
|
179
|
+
// install believed it could write to an org it could no longer reach, and the truth arrived from
|
|
180
|
+
// Salesforce as `Session expired` mid-deploy — discovered on the first request rather than at
|
|
181
|
+
// connect (`direct/rollbackRun.ts` header), which is the worst possible moment for it.
|
|
182
|
+
//
|
|
183
|
+
// A liveness probe rather than a shorter calendar TTL, deliberately. The doctrine's own
|
|
184
|
+
// first-choice recovery is `sf org login web --alias <the session alias>`, which re-mints a
|
|
185
|
+
// durable CLI token under the SAME alias without touching the grant. Shortening the TTL would
|
|
186
|
+
// revoke authority the operator had just legitimately restored. Calendar time bounds the
|
|
187
|
+
// ENGAGEMENT; the probe answers whether the org is reachable right now.
|
|
188
|
+
//
|
|
189
|
+
// ONLY A DEFINITE NEGATIVE REFUSES. A probe that returns nothing, or an answer that names no
|
|
190
|
+
// status at all, is the CLI declining to say — which is the same evidence state as not having
|
|
191
|
+
// looked, and this file's own doctrine is that "I could not verify that" is only true after you
|
|
192
|
+
// looked. Refusing on silence would turn a flaky child process, or an org whose auth shape does
|
|
193
|
+
// not report a status, into a blocked delivery.
|
|
194
|
+
const liveness = await probeLiveness(targetOrg);
|
|
195
|
+
if (liveness?.connectedStatus && liveness.connectedStatus !== "Connected") {
|
|
196
|
+
throw new GateError("ORG_AUTH_EXPIRED", `Org ${orgId} (alias ${targetOrg}) is authorized by this install, but its stored sign-in is no longer valid (${liveness.connectedStatus}). ` +
|
|
197
|
+
`Activation-minted tokens last about a day, so a session resumed the next morning reads this way — the write authority outlives the credential it authorizes.\n` +
|
|
198
|
+
`Fix, either way: sign in directly with \`sf org login web --alias ${targetOrg} --instance-url <their org url>\` (one clean attempt — rapid retries trip Salesforce's integration-user auto-freeze), or re-send the requirements from Campfire and redeem a fresh code with activate_session.\n` +
|
|
199
|
+
`Refusing fail-closed: nothing was deployed, so there is nothing to undo.`);
|
|
200
|
+
}
|
|
201
|
+
}
|
package/dist/core/ids.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { semanticContentHash } from "./semanticHash.js";
|
|
2
|
+
// Matches @in2/contracts OpaqueIdSchema: /^[A-Za-z][A-Za-z0-9_-]{5,127}$/
|
|
3
|
+
export const OPAQUE_ID_RE = /^[A-Za-z][A-Za-z0-9_-]{5,127}$/;
|
|
4
|
+
export function opaqueId(prefix, value) {
|
|
5
|
+
return `${prefix}_${semanticContentHash(value).slice(0, 24)}`;
|
|
6
|
+
}
|
|
7
|
+
export function isoNow() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Where this install lives, and which version it is running.
|
|
2
|
+
//
|
|
3
|
+
// The server is DISTRIBUTED VIA NPM (`npm install -g in2-agent-server`). npm owns the layout, the
|
|
4
|
+
// bin links, and the replace-on-update semantics — the bespoke ~/.in2-agent/versions/<v> layout,
|
|
5
|
+
// its launcher shims, and the in-process hot swap are gone with the updater that needed them
|
|
6
|
+
// (docs/plans/2026-08-07-017-npm-distribution-decision.md, resolved 2026-08-21 as Option B).
|
|
7
|
+
//
|
|
8
|
+
// Everything here is derived from where this module actually is, never from configuration. A stored
|
|
9
|
+
// path is a path that can be wrong.
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
// .../node_modules/in2-agent-server/dist/core/install.js -> .../node_modules/in2-agent-server
|
|
15
|
+
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
export const SERVER_ROOT = path.resolve(MODULE_DIR, "..", "..");
|
|
17
|
+
/** The published package name — the one string the updater and the registry check share. */
|
|
18
|
+
export const PACKAGE_NAME = "in2-agent-server";
|
|
19
|
+
// Durable machine state (grants, telemetry outboxes, session workspaces, org mirrors) lives here,
|
|
20
|
+
// NOT under the install. An npm update replaces the package directory wholesale, and `npm i -g`'s
|
|
21
|
+
// prefix moves when the consultant's Node does — state that lived beside the code would silently
|
|
22
|
+
// relocate or vanish on exactly the machines least able to notice.
|
|
23
|
+
export const IN2_HOME = process.env.IN2_HOME?.trim() || path.join(os.homedir(), ".in2-agent");
|
|
24
|
+
// MEMOIZED, and that is a correctness decision, not a performance one: npm installs a new build
|
|
25
|
+
// IN PLACE (close_batch's deferred update), so after it runs the package.json on disk names a
|
|
26
|
+
// version this process is NOT running. A re-read would make a second activation in the same
|
|
27
|
+
// conversation report the new version as current — silently defeating the staleness gate for a
|
|
28
|
+
// run still executing the old modules. The running build cannot change; neither may this answer.
|
|
29
|
+
let cachedInstalledVersion;
|
|
30
|
+
export function installedVersion() {
|
|
31
|
+
if (cachedInstalledVersion !== undefined)
|
|
32
|
+
return cachedInstalledVersion;
|
|
33
|
+
try {
|
|
34
|
+
const pkg = JSON.parse(readFileSync(path.join(SERVER_ROOT, "package.json"), "utf8"));
|
|
35
|
+
cachedInstalledVersion = pkg.version ?? "0.0.0";
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
cachedInstalledVersion = "0.0.0";
|
|
39
|
+
}
|
|
40
|
+
return cachedInstalledVersion;
|
|
41
|
+
}
|
|
42
|
+
// True when this process runs from an npm-installed copy — the layout `npm install -g` can replace.
|
|
43
|
+
// A repo checkout is NOT updatable this way (updating it means `git pull`), so the updater refuses
|
|
44
|
+
// rather than pretending.
|
|
45
|
+
export function isNpmInstall() {
|
|
46
|
+
return SERVER_ROOT.split(path.sep).includes("node_modules");
|
|
47
|
+
}
|
|
48
|
+
// Compare dotted numeric versions. Deliberately not semver-complete: releases are plain numeric
|
|
49
|
+
// triples (0.YYYYMMDD.N), and pulling a comparator dependency into the update path — the one path
|
|
50
|
+
// that must keep working when everything else is broken — is not a trade worth making.
|
|
51
|
+
export function isNewer(candidate, current) {
|
|
52
|
+
const parse = (v) => v.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
53
|
+
const [a, b] = [parse(candidate), parse(current)];
|
|
54
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
55
|
+
const [x, y] = [a[i] ?? 0, b[i] ?? 0];
|
|
56
|
+
if (x !== y)
|
|
57
|
+
return x > y;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|