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.
Files changed (115) hide show
  1. package/README.md +369 -0
  2. package/agents/plan-requirement.md +32 -0
  3. package/dist/campfire/configItem.js +201 -0
  4. package/dist/campfire/configItemDescription.js +379 -0
  5. package/dist/campfire/deriveFromBundle.js +1013 -0
  6. package/dist/cli.js +465 -0
  7. package/dist/core/betaFeedback.js +493 -0
  8. package/dist/core/chapterInstructions.js +473 -0
  9. package/dist/core/claudeAssets.js +61 -0
  10. package/dist/core/gate.js +201 -0
  11. package/dist/core/ids.js +9 -0
  12. package/dist/core/install.js +60 -0
  13. package/dist/core/operatorQa.js +462 -0
  14. package/dist/core/orgCallMeter.js +178 -0
  15. package/dist/core/parcels.js +1235 -0
  16. package/dist/core/paths.js +105 -0
  17. package/dist/core/phases.js +225 -0
  18. package/dist/core/planFile.js +251 -0
  19. package/dist/core/planFragments.js +119 -0
  20. package/dist/core/readsLog.js +398 -0
  21. package/dist/core/registration.js +24 -0
  22. package/dist/core/result.js +38 -0
  23. package/dist/core/resume.js +233 -0
  24. package/dist/core/revisionChain.js +351 -0
  25. package/dist/core/richText.js +61 -0
  26. package/dist/core/semanticHash.js +89 -0
  27. package/dist/core/sessionOrg.js +60 -0
  28. package/dist/core/sfQuery.js +19 -0
  29. package/dist/core/spawn.js +182 -0
  30. package/dist/core/supervisorContract.js +230 -0
  31. package/dist/core/telemetry.js +1297 -0
  32. package/dist/core/telemetryContext.js +154 -0
  33. package/dist/core/telemetrySpool.js +244 -0
  34. package/dist/core/transcriptUpload.js +516 -0
  35. package/dist/core/update.js +363 -0
  36. package/dist/core/viewer.js +117 -0
  37. package/dist/core/workspace.js +92 -0
  38. package/dist/core/writeGrants.js +93 -0
  39. package/dist/core/writebackManifest.js +92 -0
  40. package/dist/direct/applyLoop.js +2516 -0
  41. package/dist/direct/batchReport.js +510 -0
  42. package/dist/direct/comparatorNoise.js +75 -0
  43. package/dist/direct/containers.js +85 -0
  44. package/dist/direct/contamination.js +221 -0
  45. package/dist/direct/crud.js +56 -0
  46. package/dist/direct/destructive.js +168 -0
  47. package/dist/direct/knownTokens.js +94 -0
  48. package/dist/direct/lint.js +382 -0
  49. package/dist/direct/metadataSchema.generated.json +1 -0
  50. package/dist/direct/metadataSchema.js +179 -0
  51. package/dist/direct/operatorPage.js +349 -0
  52. package/dist/direct/orgRepo.js +1531 -0
  53. package/dist/direct/orgRepoSpawnCount.js +21 -0
  54. package/dist/direct/plannedScope.js +119 -0
  55. package/dist/direct/preImageTargets.js +63 -0
  56. package/dist/direct/refVerify.js +191 -0
  57. package/dist/direct/registry.js +129 -0
  58. package/dist/direct/removals.js +262 -0
  59. package/dist/direct/rollback.js +1513 -0
  60. package/dist/direct/rollbackRun.js +1360 -0
  61. package/dist/direct/sessionProjection.js +126 -0
  62. package/dist/direct/snapshotComparability.js +165 -0
  63. package/dist/direct/sourceTree.js +113 -0
  64. package/dist/direct/unstatedChanges.js +106 -0
  65. package/dist/direct/waveAssembly.js +556 -0
  66. package/dist/direct/waveCounts.js +19 -0
  67. package/dist/direct/waveDerivation.js +152 -0
  68. package/dist/direct/waveGrade.js +185 -0
  69. package/dist/index.js +90 -0
  70. package/dist/orgread/customFieldSnapshot.js +48 -0
  71. package/dist/orgread/fieldMetadata.js +178 -0
  72. package/dist/orgread/genericSnapshot.js +61 -0
  73. package/dist/orgread/layoutSnapshot.js +151 -0
  74. package/dist/orgread/salesforceOrgReader.js +184 -0
  75. package/dist/orgread/snapshotTypes.js +9 -0
  76. package/dist/projection/bridge.js +26 -0
  77. package/dist/projection/diff.js +141 -0
  78. package/dist/projection/mapmd.js +213 -0
  79. package/dist/projection/nameAmbiguity.js +106 -0
  80. package/dist/projection/precedent.js +136 -0
  81. package/dist/projection/project.js +214 -0
  82. package/dist/projection/scope.js +110 -0
  83. package/dist/projection/types.js +5 -0
  84. package/dist/tools/analyze.js +322 -0
  85. package/dist/tools/auth.js +915 -0
  86. package/dist/tools/campfire.js +1742 -0
  87. package/dist/tools/closeBatch.js +154 -0
  88. package/dist/tools/closeText.js +35 -0
  89. package/dist/tools/direct.js +1613 -0
  90. package/dist/tools/feedback.js +318 -0
  91. package/dist/tools/operator.js +287 -0
  92. package/dist/tools/org.js +739 -0
  93. package/dist/tools/phase.js +249 -0
  94. package/dist/tools/projection.js +172 -0
  95. package/dist/tools/session.js +313 -0
  96. package/dist/tools/update.js +82 -0
  97. package/dist/tools/waves.js +793 -0
  98. package/package.json +48 -0
  99. package/reference/campfire-configuration-items-guide.md +710 -0
  100. package/reference/deployability-cribs.md +119 -0
  101. package/reference/operator-voice.md +141 -0
  102. package/reference/org-read-cribs.md +34 -0
  103. package/reference/scoped-dml-interim.md +68 -0
  104. package/reference/supervisor-playbook.md +313 -0
  105. package/reference/templates.md +233 -0
  106. package/skills/in2-delivery/SKILL.md +322 -0
  107. package/skills/in2-delivery/references/campfire-configuration-items-guide.md +710 -0
  108. package/skills/in2-delivery/references/deployability-cribs.md +119 -0
  109. package/skills/in2-delivery/references/operator-voice.md +141 -0
  110. package/skills/in2-delivery/references/org-read-cribs.md +34 -0
  111. package/skills/in2-delivery/references/scoped-dml-interim.md +68 -0
  112. package/skills/in2-delivery/references/templates.md +233 -0
  113. package/skills/plan-requirement/SKILL.md +188 -0
  114. package/viewer/index.html +899 -0
  115. 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
+ }
@@ -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
+ }