@rubytech/create-maxy-code 0.1.88 → 0.1.91
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/dist/__tests__/entitlement-flag.test.js +43 -0
- package/dist/__tests__/tier-flag.test.js +53 -0
- package/dist/index.js +99 -0
- package/dist/port-resolution.js +1 -1
- package/dist/tier-flag.js +85 -0
- package/package.json +1 -1
- package/payload/platform/lib/admin-conversation-purge/dist/index.d.ts +54 -0
- package/payload/platform/lib/admin-conversation-purge/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/admin-conversation-purge/dist/index.js +84 -0
- package/payload/platform/lib/admin-conversation-purge/dist/index.js.map +1 -0
- package/payload/platform/lib/admin-conversation-purge/src/index.ts +120 -0
- package/payload/platform/lib/admin-conversation-purge/tsconfig.json +8 -0
- package/payload/platform/lib/graph-trash/dist/index.d.ts +12 -1
- package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-trash/dist/index.js +8 -1
- package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-trash/src/index.ts +20 -2
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -0
- package/payload/platform/plugins/admin/hooks/__tests__/askuserquestion-investigate-gate.test.sh +176 -0
- package/payload/platform/plugins/admin/hooks/askuserquestion-investigate-gate.sh +257 -0
- package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +2 -0
- package/payload/platform/plugins/docs/references/admin-session.md +5 -1
- package/payload/platform/plugins/docs/references/linkedin-extension.md +49 -0
- package/payload/platform/plugins/linkedin-extension/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/linkedin-extension/PLUGIN.md +58 -0
- package/payload/platform/plugins/linkedin-extension/extension/README.md +44 -0
- package/payload/platform/plugins/linkedin-extension/extension/__tests__/fixtures/profile.html +34 -0
- package/payload/platform/plugins/linkedin-extension/extension/__tests__/fixtures/thread.html +36 -0
- package/payload/platform/plugins/linkedin-extension/extension/assets/pill.css +52 -0
- package/payload/platform/plugins/linkedin-extension/extension/background/sw.js +60 -0
- package/payload/platform/plugins/linkedin-extension/extension/content/extractors.js +127 -0
- package/payload/platform/plugins/linkedin-extension/extension/content/profile.js +82 -0
- package/payload/platform/plugins/linkedin-extension/extension/content/thread.js +84 -0
- package/payload/platform/plugins/linkedin-extension/extension/manifest.json +32 -0
- package/payload/platform/plugins/linkedin-extension/extension/options/options.html +33 -0
- package/payload/platform/plugins/linkedin-extension/extension/options/options.js +30 -0
- package/payload/platform/plugins/linkedin-extension/skills/linkedin-extension/SKILL.md +91 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts +14 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js +28 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/neo4j.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.js +168 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-empty-trash-admin-conversation.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +22 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js +61 -13
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -1
- package/payload/platform/scripts/seed-neo4j.sh +12 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +3 -1
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -2
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/make-brochure/SKILL.md +1 -1
- package/payload/server/public/assets/{ChatInput-BzjU6OoA.css → ChatInput-dLWJ0Q8I.css} +1 -1
- package/payload/server/public/assets/{Checkbox-BWLS5Isf.js → Checkbox-Bx8mRHQ3.js} +1 -1
- package/payload/server/public/assets/{admin-DjU0q63n.js → admin-C57bDUgs.js} +6 -6
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-lcncWUol.js → architectureDiagram-Q4EWVU46-BEW_bD50.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-SqlxCp6m.js → blockDiagram-DXYQGD6D-DFLT98C3.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-DWzvXvDc.js → c4Diagram-AHTNJAMY-BemKtROA.js} +1 -1
- package/payload/server/public/assets/channel-CfpByn-m.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O-Bz-tPZhS.js → chunk-336JU56O-BL8fGJ7_.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-MYkbqx1u.js → chunk-426QAEUC-BD6by0MP.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-DfZRQtaZ.js → chunk-4TB4RGXK-DX-cb6Nd.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-BFMrOiNe.js → chunk-5FUZZQ4R-B2QqtIZS.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-64laDovl.js → chunk-5PVQY5BW-CzJWmsmd.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-dClAHBNd.js → chunk-EDXVE4YY-DP9vcvEU.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-Bz4gXG1p.js → chunk-ENJZ2VHE-D5xWEjF0.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-D5pWrJcN.js → chunk-ICPOFSXX-AKduf9o4.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-BCiWtqaK.js → chunk-OYMX7WX6-D6jsVNyQ.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-Brql314T.js → chunk-U2HBQHQK-BNAnTYh8.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-C-79tMym.js → chunk-X2U36JSP-UdQhYDnh.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-G4idHEoZ.js → chunk-YZCP3GAM-DSSmSGmS.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-DyHGASIn.js → chunk-ZZ45TVLE-Czm7IDMC.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DNc9R1IX.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DM1DQh8N.js +1 -0
- package/payload/server/public/assets/clone-BSrNdvLW.js +1 -0
- package/payload/server/public/assets/{dagre-KV5264BT--Bba_VRf.js → dagre-KV5264BT-CBuRCJE7.js} +1 -1
- package/payload/server/public/assets/{dagre-DfJ8a8tG.js → dagre-hSIMHJcO.js} +1 -1
- package/payload/server/public/assets/data-3n20lir5.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-CfUotMr9.js → diagram-5BDNPKRD-dbMrH0le.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-BWZJChPS.js → diagram-G4DWMVQ6-DBc6hs9a.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-BExS6IWb.js → diagram-MMDJMWI5-ubSGybUk.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-DQ4t_wIt.js → diagram-TYMM5635-WxL3cv-9.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-f1sfB-AQ.js → erDiagram-SMLLAGMA-CrBBOAN8.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-CZexxT3v.js → flowDiagram-DWJPFMVM-CXUq5Hzt.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BNcFUX4K.js → ganttDiagram-T4ZO3ILL-vTCBnYVS.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-soyylUaR.js → gitGraphDiagram-UUTBAWPF-BHrj_bRa.js} +1 -1
- package/payload/server/public/assets/graph-ChwMpxn_.js +1 -0
- package/payload/server/public/assets/graph-labels-CUgJgIwl.js +1 -0
- package/payload/server/public/assets/{graphlib-B2ezMQY4.js → graphlib-CDO75l2X.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-4xmenbOP.js → infoDiagram-42DDH7IO-CcQdaOhi.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-C_D6CqVq.js → ishikawaDiagram-UXIWVN3A-3KvltFgV.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DUDx0c35.js → journeyDiagram-VCZTEJTY-DZCXaGFE.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BNr7ErhZ.js → kanban-definition-6JOO6SKY-DAIAKy1-.js} +1 -1
- package/payload/server/public/assets/{lib-iKom7FFe.js → lib-CCLURO3E.js} +1 -1
- package/payload/server/public/assets/{line-Dvs5wrj5.js → line-C8iCE2Wx.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-D0ksZQlW.js → mermaid-parser.core-ElvUhnV9.js} +1 -1
- package/payload/server/public/assets/{mermaid.core--Btu4mYy.js → mermaid.core-7dSH31Ao.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DfyZTghB.js → mindmap-definition-QFDTVHPH-FZ0rcfac.js} +1 -1
- package/payload/server/public/assets/{page-BnBmdRbo.js → page-D3bFmqF4.js} +1 -1
- package/payload/server/public/assets/{page-x6ZXWwMx.js → page-DNjUtT5q.js} +18 -17
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-BiG9YoLj.js → pieDiagram-DEJITSTG-CDuvMZbO.js} +1 -1
- package/payload/server/public/assets/{public-DW7yINk7.js → public-g_2skbnY.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CVufUsie.js → quadrantDiagram-34T5L4WZ-xDjhkqNc.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-UKCptCPk.js → requirementDiagram-MS252O5E-BPKpyT0n.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-e8iZYL5P.js → sankeyDiagram-XADWPNL6-BvFIrihC.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-CfZqVRxe.js → sequenceDiagram-FGHM5R23-C6G1oAE0.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DrWX8OY8.js → stateDiagram-FHFEXIEX-DbOJT_Xh.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Ce4GFk-H.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-Blec2Yhc.js → timeline-definition-GMOUNBTQ-D1DSxHh6.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-BtACuGgu.js → vennDiagram-DHZGUBPP-BYoSeMdo.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-BWT7orYV.js → wardleyDiagram-NUSXRM2D-DH3kZdea.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-DBzoIRiJ.js → xychartDiagram-5P7HB3ND-C6MROMLG.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/server-init.cjs +35 -3
- package/payload/server/server.js +547 -118
- package/payload/server/public/assets/channel-dZdJT-Ou.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CP8AWLB9.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-bUe6tAo9.js +0 -1
- package/payload/server/public/assets/clone-gXHQCPU0.js +0 -1
- package/payload/server/public/assets/data-Bt6BTjvP.js +0 -1
- package/payload/server/public/assets/graph-labels-BeDsoEVY.js +0 -1
- package/payload/server/public/assets/graph-szVcmw8D.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CvKXSO97.js +0 -1
- /package/payload/server/public/assets/{ChatInput-AR8ym-HW.js → ChatInput-DoS80kaC.js} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Acceptance gate for the founder-side `--entitlement-base64 <b64>` installer
|
|
2
|
+
// flag (Task 240). Pure-module coverage: base64 round-trip validation, mode
|
|
3
|
+
// rejection, decoded-payload non-emptiness, path resolution. Live entitlement
|
|
4
|
+
// delivery to a Pi is exercised by manual install per task verification §4.
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { validateEntitlementBase64, entitlementPath, } from "../tier-flag.js";
|
|
8
|
+
// A small but realistic envelope shape — looks like what
|
|
9
|
+
// platform/scripts/generate-entitlement-fixture.mjs sign produces.
|
|
10
|
+
const SAMPLE_PAYLOAD = JSON.stringify({
|
|
11
|
+
payload: { accountId: "x", tier: "family" },
|
|
12
|
+
signature: "deadbeef",
|
|
13
|
+
});
|
|
14
|
+
const SAMPLE_B64 = Buffer.from(SAMPLE_PAYLOAD, "utf-8").toString("base64");
|
|
15
|
+
test("validateEntitlementBase64 accepts a well-formed payload when commercialMode is true", () => {
|
|
16
|
+
const decoded = validateEntitlementBase64(SAMPLE_B64, true);
|
|
17
|
+
assert.equal(decoded.toString("utf-8"), SAMPLE_PAYLOAD);
|
|
18
|
+
});
|
|
19
|
+
test("validateEntitlementBase64 tolerates embedded whitespace (newlines, tabs, spaces)", () => {
|
|
20
|
+
// SSH transit and operator-side paste can introduce \r, \n, or stray spaces.
|
|
21
|
+
// The stripper must normalise them before the round-trip check fires.
|
|
22
|
+
const noisy = SAMPLE_B64.slice(0, 8) + "\n" + SAMPLE_B64.slice(8, 16) + " \t" + SAMPLE_B64.slice(16);
|
|
23
|
+
const decoded = validateEntitlementBase64(noisy, true);
|
|
24
|
+
assert.equal(decoded.toString("utf-8"), SAMPLE_PAYLOAD);
|
|
25
|
+
});
|
|
26
|
+
test("validateEntitlementBase64 rejects garbage characters", () => {
|
|
27
|
+
assert.throws(() => validateEntitlementBase64("not-valid-base64@#$", true), /not valid base64/);
|
|
28
|
+
});
|
|
29
|
+
test("validateEntitlementBase64 rejects empty value and flag-shaped value", () => {
|
|
30
|
+
assert.throws(() => validateEntitlementBase64(undefined, true), /requires a base64-encoded value/);
|
|
31
|
+
assert.throws(() => validateEntitlementBase64("", true), /requires a base64-encoded value/);
|
|
32
|
+
assert.throws(() => validateEntitlementBase64("--port", true), /requires a base64-encoded value/);
|
|
33
|
+
});
|
|
34
|
+
test("validateEntitlementBase64 rejects whitespace-only value", () => {
|
|
35
|
+
assert.throws(() => validateEntitlementBase64(" \n\t ", true), /empty after whitespace stripping/);
|
|
36
|
+
});
|
|
37
|
+
test("validateEntitlementBase64 rejects --entitlement-base64 when brand is personal-mode", () => {
|
|
38
|
+
assert.throws(() => validateEntitlementBase64(SAMPLE_B64, false), /Personal mode — use --tier <enum>/);
|
|
39
|
+
});
|
|
40
|
+
test("entitlementPath joins homeDir + configDir + entitlement.json", () => {
|
|
41
|
+
assert.equal(entitlementPath("/home/admin", ".realagent-code"), "/home/admin/.realagent-code/entitlement.json");
|
|
42
|
+
assert.equal(entitlementPath("/home/me", ".maxy-code"), "/home/me/.maxy-code/entitlement.json");
|
|
43
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Acceptance gate for the founder-side `--tier <enum>` installer flag (Task 240).
|
|
2
|
+
// Pure-module coverage: validation rules, mode-mismatch rejection, config merge.
|
|
3
|
+
// Wiring through index.ts argv is exercised by manual install per task verification §1.
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { validateTierFlag, applyTierToAccountConfig, isValidTier, } from "../tier-flag.js";
|
|
7
|
+
test("validateTierFlag accepts solo|family|pro when commercialMode is false", () => {
|
|
8
|
+
assert.equal(validateTierFlag("solo", false), "solo");
|
|
9
|
+
assert.equal(validateTierFlag("family", false), "family");
|
|
10
|
+
assert.equal(validateTierFlag("pro", false), "pro");
|
|
11
|
+
});
|
|
12
|
+
test("validateTierFlag rejects values outside the enum", () => {
|
|
13
|
+
assert.throws(() => validateTierFlag("enterprise", false), /--tier value must be one of: solo, family, pro/);
|
|
14
|
+
assert.throws(() => validateTierFlag("max", false), /--tier value must be one of: solo, family, pro/);
|
|
15
|
+
});
|
|
16
|
+
test("validateTierFlag rejects empty or flag-shaped value", () => {
|
|
17
|
+
assert.throws(() => validateTierFlag(undefined, false), /--tier requires a value/);
|
|
18
|
+
assert.throws(() => validateTierFlag("", false), /--tier requires a value/);
|
|
19
|
+
assert.throws(() => validateTierFlag("--hostname", false), /--tier requires a value/);
|
|
20
|
+
});
|
|
21
|
+
test("validateTierFlag rejects --tier when brand is commercial-mode", () => {
|
|
22
|
+
assert.throws(() => validateTierFlag("family", true), /Commercial mode — use --entitlement-base64/);
|
|
23
|
+
// Mode mismatch fires before enum check — operator gets the redirect, not "invalid enum".
|
|
24
|
+
assert.throws(() => validateTierFlag("enterprise", true), /Commercial mode — use --entitlement-base64/);
|
|
25
|
+
});
|
|
26
|
+
test("applyTierToAccountConfig stamps tier and preserves every other key", () => {
|
|
27
|
+
const before = {
|
|
28
|
+
accountId: "abc-123",
|
|
29
|
+
enabledPlugins: ["whatsapp", "outlook"],
|
|
30
|
+
outputStyle: "default",
|
|
31
|
+
thinkingView: "default",
|
|
32
|
+
};
|
|
33
|
+
const after = applyTierToAccountConfig(before, "family");
|
|
34
|
+
assert.equal(after.tier, "family");
|
|
35
|
+
assert.equal(after.accountId, "abc-123");
|
|
36
|
+
assert.deepEqual(after.enabledPlugins, ["whatsapp", "outlook"]);
|
|
37
|
+
assert.equal(after.outputStyle, "default");
|
|
38
|
+
assert.equal(after.thinkingView, "default");
|
|
39
|
+
// Input not mutated — caller can safely roundtrip.
|
|
40
|
+
assert.equal(before.tier, undefined);
|
|
41
|
+
});
|
|
42
|
+
test("applyTierToAccountConfig overwrites a previously-stamped tier", () => {
|
|
43
|
+
const after = applyTierToAccountConfig({ tier: "solo", accountId: "x" }, "pro");
|
|
44
|
+
assert.equal(after.tier, "pro");
|
|
45
|
+
assert.equal(after.accountId, "x");
|
|
46
|
+
});
|
|
47
|
+
test("isValidTier narrows correctly", () => {
|
|
48
|
+
assert.equal(isValidTier("solo"), true);
|
|
49
|
+
assert.equal(isValidTier("family"), true);
|
|
50
|
+
assert.equal(isValidTier("pro"), true);
|
|
51
|
+
assert.equal(isValidTier("enterprise"), false);
|
|
52
|
+
assert.equal(isValidTier(""), false);
|
|
53
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { registerSpecialistAgentsAt, SpecialistSymlinkCollision } from "./specia
|
|
|
5
5
|
import { resolve, join, dirname } from "node:path";
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { resolveInstallPortFromFs, buildMaxyUnitFile, buildClaudeSessionManagerUnitFile } from "./port-resolution.js";
|
|
8
|
+
import { validateTierFlag, validateEntitlementBase64, applyTierToAccountConfig, entitlementPath } from "./tier-flag.js";
|
|
8
9
|
import { parseOsRelease, isUbuntuLike as isUbuntuLikePure, parseAptCacheCandidate, decideAptResolution, } from "./apt-resolve.js";
|
|
9
10
|
import { findPeerBrandOnDefaultNeo4jPort } from "./peer-brand-detect.js";
|
|
10
11
|
import { runInitLogging } from "./init-logging.js";
|
|
@@ -2350,9 +2351,17 @@ function writeInstallDefaults(accountId) {
|
|
|
2350
2351
|
config.enabledPlugins = enabledPlugins;
|
|
2351
2352
|
config.outputStyle = "default";
|
|
2352
2353
|
config.thinkingView = "default";
|
|
2354
|
+
if (TIER_FLAG) {
|
|
2355
|
+
config = applyTierToAccountConfig(config, TIER_FLAG);
|
|
2356
|
+
}
|
|
2353
2357
|
writeFileSync(accountJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
2354
2358
|
console.log(` [install-defaults] account-json plugins=${enabledPlugins.length} outputStyle=default thinkingView=default`);
|
|
2355
2359
|
logFile(` [install-defaults] account-json plugins=${enabledPlugins.length} outputStyle=default thinkingView=default`);
|
|
2360
|
+
if (TIER_FLAG) {
|
|
2361
|
+
const tierLine = ` [install-defaults] tier=${TIER_FLAG} mode=personal source=cli-flag`;
|
|
2362
|
+
console.log(tierLine);
|
|
2363
|
+
logFile(tierLine);
|
|
2364
|
+
}
|
|
2356
2365
|
// Admin SOUL.md is sourced from the bundled template with ${PRODUCT_NAME}
|
|
2357
2366
|
// substituted at install time. The template carries the Phase 1 standing
|
|
2358
2367
|
// offer (brochure / preval / Loop CRM) so the agent ships with a stated
|
|
@@ -2397,6 +2406,22 @@ function writeInstallDefaults(accountId) {
|
|
|
2397
2406
|
logFile(` [install-defaults] public-config path=${publicConfigDst}`);
|
|
2398
2407
|
}
|
|
2399
2408
|
}
|
|
2409
|
+
/**
|
|
2410
|
+
* Commercial-mode entitlement delivery (Task 240). Writes the decoded payload
|
|
2411
|
+
* bytes verbatim to ~/<BRAND.configDir>/entitlement.json. Signature and shape
|
|
2412
|
+
* validation happen at the verifier at session start (entitlement lib); the
|
|
2413
|
+
* installer is a dumb delivery channel — a malformed payload that survived
|
|
2414
|
+
* --entitlement-base64 parsing will be caught there and degrade the install
|
|
2415
|
+
* to anonymous-fallback, which is the correct loud-fail per doctrine.
|
|
2416
|
+
*/
|
|
2417
|
+
function writeEntitlementFromFlag(payload) {
|
|
2418
|
+
const target = entitlementPath(process.env.HOME ?? "/root", BRAND.configDir);
|
|
2419
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
2420
|
+
writeFileSync(target, payload);
|
|
2421
|
+
const line = ` [install-defaults] entitlement-delivered bytes=${payload.length} path=${target} source=cli-flag`;
|
|
2422
|
+
console.log(line);
|
|
2423
|
+
logFile(line);
|
|
2424
|
+
}
|
|
2400
2425
|
// ---------------------------------------------------------------------------
|
|
2401
2426
|
// Tunnel script shortcuts
|
|
2402
2427
|
//
|
|
@@ -3103,6 +3128,37 @@ WantedBy=multi-user.target
|
|
|
3103
3128
|
spawnSync("sudo", ["loginctl", "enable-linger", currentUser], { stdio: "inherit" });
|
|
3104
3129
|
}
|
|
3105
3130
|
catch { /* not critical */ }
|
|
3131
|
+
// Task 249: persist the systemd journal so per-unit lifecycle events
|
|
3132
|
+
// (watchdog timeouts, SIGTERM/SIGKILL transitions, restart reasons) survive
|
|
3133
|
+
// reboots and are queryable via `journalctl --user -u <brand>.service`.
|
|
3134
|
+
//
|
|
3135
|
+
// Bookworm-canonical mechanism: journald uses persistent storage when
|
|
3136
|
+
// `/var/log/journal/<machine-id>/` exists. No user-level
|
|
3137
|
+
// systemd-journald.service exists by default on Debian; the system instance
|
|
3138
|
+
// stores user-unit events in the same tree, filtered at read time. We only
|
|
3139
|
+
// restart journald when we just created the directory — if the path already
|
|
3140
|
+
// existed, persistence was already active and restarting would needlessly
|
|
3141
|
+
// churn the system journal during the install.
|
|
3142
|
+
try {
|
|
3143
|
+
const machineId = readFileSync("/etc/machine-id", "utf-8").trim();
|
|
3144
|
+
if (machineId) {
|
|
3145
|
+
const journalPath = `/var/log/journal/${machineId}`;
|
|
3146
|
+
const alreadyPersistent = existsSync(journalPath);
|
|
3147
|
+
if (!alreadyPersistent) {
|
|
3148
|
+
console.log(" [privileged] mkdir /var/log/journal");
|
|
3149
|
+
shell("mkdir", ["-p", journalPath], { sudo: true });
|
|
3150
|
+
console.log(" [privileged] systemctl restart systemd-journald");
|
|
3151
|
+
spawnSync("sudo", ["systemctl", "restart", "systemd-journald"], { stdio: "inherit", timeout: 15_000 });
|
|
3152
|
+
logFile(` journald persistence: enabled (created ${journalPath} + restarted journald)`);
|
|
3153
|
+
}
|
|
3154
|
+
else {
|
|
3155
|
+
logFile(` journald persistence: already active (${journalPath} present)`);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
catch (err) {
|
|
3160
|
+
console.error(` WARNING: failed to enable journald persistence: ${err instanceof Error ? err.message : String(err)}`);
|
|
3161
|
+
}
|
|
3106
3162
|
// Reload and (re)start.
|
|
3107
3163
|
//
|
|
3108
3164
|
// ordering: on upgrades, the old main brand service still holds
|
|
@@ -3376,6 +3432,46 @@ if (hostnameIdx !== -1) {
|
|
|
3376
3432
|
}
|
|
3377
3433
|
HOSTNAME_FLAG = raw;
|
|
3378
3434
|
}
|
|
3435
|
+
// ---------------------------------------------------------------------------
|
|
3436
|
+
// Founder-side tier-set flags (Task 240).
|
|
3437
|
+
//
|
|
3438
|
+
// `--tier <solo|family|pro>` stamps account.json.tier directly when the brand
|
|
3439
|
+
// is personal-mode (today's default for both maxy-code and realagent-code).
|
|
3440
|
+
// `--entitlement-base64 <b64>` decodes a signed payload and writes it to
|
|
3441
|
+
// ~/<BRAND.configDir>/entitlement.json when the brand is commercial-mode.
|
|
3442
|
+
// The verifier at session start consumes whichever path the brand is on.
|
|
3443
|
+
//
|
|
3444
|
+
// Mode-mismatch combinations reject loudly at parse time — operator gets a
|
|
3445
|
+
// redirect message pointing at the correct flag. The installer is the sole
|
|
3446
|
+
// doctrine-sanctioned device-mutation channel; the admin agent has no tier-
|
|
3447
|
+
// upgrade surface by design.
|
|
3448
|
+
// ---------------------------------------------------------------------------
|
|
3449
|
+
let TIER_FLAG;
|
|
3450
|
+
let ENTITLEMENT_PAYLOAD;
|
|
3451
|
+
const tierIdx = _args.indexOf("--tier");
|
|
3452
|
+
if (tierIdx !== -1) {
|
|
3453
|
+
try {
|
|
3454
|
+
TIER_FLAG = validateTierFlag(_args[tierIdx + 1], BRAND.commercialMode === true);
|
|
3455
|
+
}
|
|
3456
|
+
catch (err) {
|
|
3457
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3458
|
+
console.error(`Setup failed: ${msg}`);
|
|
3459
|
+
console.error(`Error: ${msg}`);
|
|
3460
|
+
process.exit(1);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
const entitlementIdx = _args.indexOf("--entitlement-base64");
|
|
3464
|
+
if (entitlementIdx !== -1) {
|
|
3465
|
+
try {
|
|
3466
|
+
ENTITLEMENT_PAYLOAD = validateEntitlementBase64(_args[entitlementIdx + 1], BRAND.commercialMode === true);
|
|
3467
|
+
}
|
|
3468
|
+
catch (err) {
|
|
3469
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3470
|
+
console.error(`Setup failed: ${msg}`);
|
|
3471
|
+
console.error(`Error: ${msg}`);
|
|
3472
|
+
process.exit(1);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3379
3475
|
let DISPLAY_MODE = "virtual";
|
|
3380
3476
|
let DISPLAY_MODE_SOURCE = "default";
|
|
3381
3477
|
const displayIdx = _args.indexOf("--display");
|
|
@@ -3638,6 +3734,9 @@ try {
|
|
|
3638
3734
|
registerLocalAndExternalPlugins(); // install-time plugin registration
|
|
3639
3735
|
setupVncViewer();
|
|
3640
3736
|
setupAccount();
|
|
3737
|
+
if (ENTITLEMENT_PAYLOAD) {
|
|
3738
|
+
writeEntitlementFromFlag(ENTITLEMENT_PAYLOAD);
|
|
3739
|
+
}
|
|
3641
3740
|
installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
|
|
3642
3741
|
installService();
|
|
3643
3742
|
// Samba on the LAN. Runs after the brand systemd unit is up so
|
package/dist/port-resolution.js
CHANGED
|
@@ -97,7 +97,7 @@ ExecStartPre=-/bin/bash ${o.installDir}/platform/scripts/resume-tunnel.sh
|
|
|
97
97
|
ExecStart=/usr/bin/node --require ./server-init.cjs server.js
|
|
98
98
|
Restart=on-failure
|
|
99
99
|
RestartSec=5
|
|
100
|
-
WatchdogSec=
|
|
100
|
+
WatchdogSec=120
|
|
101
101
|
TimeoutStartSec=60
|
|
102
102
|
TimeoutStopSec=10
|
|
103
103
|
Environment=NODE_ENV=production
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Pure validation + path helpers for the founder-side tier-set installer flags
|
|
2
|
+
// (Task 240). Two flags wired into the installer at src/index.ts:
|
|
3
|
+
// --tier <solo|family|pro> personal-mode (brand.commercialMode === false)
|
|
4
|
+
// --entitlement-base64 <b64> commercial-mode (brand.commercialMode === true)
|
|
5
|
+
//
|
|
6
|
+
// Both helpers throw on invalid input; the caller exits non-zero and surfaces the
|
|
7
|
+
// thrown message. No filesystem or console side effects — index.ts owns those.
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
const VALID_TIERS = new Set(["solo", "family", "pro"]);
|
|
10
|
+
export function isValidTier(value) {
|
|
11
|
+
return VALID_TIERS.has(value);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validate the `--tier <enum>` argv value and the brand mode.
|
|
15
|
+
*
|
|
16
|
+
* Throws when:
|
|
17
|
+
* - raw is missing / starts with `--` (no value supplied after the flag)
|
|
18
|
+
* - raw is not in {solo, family, pro}
|
|
19
|
+
* - brand is commercial-mode (operator must use --entitlement-base64 instead)
|
|
20
|
+
*/
|
|
21
|
+
export function validateTierFlag(raw, commercialMode) {
|
|
22
|
+
if (commercialMode) {
|
|
23
|
+
throw new Error("--tier is for personal mode. Commercial mode — use --entitlement-base64 to deliver a signed payload.");
|
|
24
|
+
}
|
|
25
|
+
if (!raw || raw.startsWith("--")) {
|
|
26
|
+
throw new Error("--tier requires a value (one of: solo, family, pro).");
|
|
27
|
+
}
|
|
28
|
+
if (!isValidTier(raw)) {
|
|
29
|
+
throw new Error(`--tier value must be one of: solo, family, pro (got: ${raw}).`);
|
|
30
|
+
}
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate the `--entitlement-base64 <b64>` argv value and the brand mode.
|
|
35
|
+
*
|
|
36
|
+
* Round-trips through Buffer to detect malformed base64 — Buffer.from is loose
|
|
37
|
+
* (silently drops invalid chars), so an explicit re-encode comparison is the
|
|
38
|
+
* cheapest way to confirm the input is real base64. Whitespace (newlines from
|
|
39
|
+
* SSH transit, stray \r) is stripped first.
|
|
40
|
+
*
|
|
41
|
+
* Throws when:
|
|
42
|
+
* - brand is personal-mode (operator must use --tier instead)
|
|
43
|
+
* - raw is missing / starts with `--`
|
|
44
|
+
* - stripped base64 fails the round-trip check (malformed chars)
|
|
45
|
+
* - decoded bytes are empty
|
|
46
|
+
*
|
|
47
|
+
* Returns the decoded Buffer; caller writes it to entitlement.json verbatim.
|
|
48
|
+
* No JSON parse, no signature validation — that's the verifier's job at session
|
|
49
|
+
* start. The installer is a dumb delivery channel.
|
|
50
|
+
*/
|
|
51
|
+
export function validateEntitlementBase64(raw, commercialMode) {
|
|
52
|
+
if (!commercialMode) {
|
|
53
|
+
throw new Error("--entitlement-base64 is for commercial mode. Personal mode — use --tier <enum> to set tier directly.");
|
|
54
|
+
}
|
|
55
|
+
if (!raw || raw.startsWith("--")) {
|
|
56
|
+
throw new Error("--entitlement-base64 requires a base64-encoded value.");
|
|
57
|
+
}
|
|
58
|
+
const stripped = raw.replace(/\s/g, "");
|
|
59
|
+
if (stripped.length === 0) {
|
|
60
|
+
throw new Error("--entitlement-base64 value is empty after whitespace stripping.");
|
|
61
|
+
}
|
|
62
|
+
const decoded = Buffer.from(stripped, "base64");
|
|
63
|
+
if (decoded.toString("base64") !== stripped) {
|
|
64
|
+
throw new Error("--entitlement-base64 value is not valid base64.");
|
|
65
|
+
}
|
|
66
|
+
if (decoded.length === 0) {
|
|
67
|
+
throw new Error("--entitlement-base64 decoded to zero bytes.");
|
|
68
|
+
}
|
|
69
|
+
return decoded;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Build the absolute path where the installer writes entitlement.json. Matches
|
|
73
|
+
* the verifier's `resolve(brand.configDir, "entitlement.json")` (entitlement
|
|
74
|
+
* lib src/index.ts:140) so writer + reader agree on a single location.
|
|
75
|
+
*/
|
|
76
|
+
export function entitlementPath(homeDir, configDir) {
|
|
77
|
+
return resolve(homeDir, configDir, "entitlement.json");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Stamp the validated tier onto an account.json config object. Pure merge;
|
|
81
|
+
* preserves every other key (enabledPlugins, outputStyle, thinkingView, etc.).
|
|
82
|
+
*/
|
|
83
|
+
export function applyTierToAccountConfig(config, tier) {
|
|
84
|
+
return { ...config, tier };
|
|
85
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin-conversation JSONL purge — Task 237.
|
|
3
|
+
*
|
|
4
|
+
* Shared primitive that fires the claude-session-manager's
|
|
5
|
+
* `DELETE /:sessionId` per attached JSONL. Pure JSONL-only: it does not
|
|
6
|
+
* touch the graph. Callers (the `cascadeAdminConversationDelete` engine
|
|
7
|
+
* and `memory-empty-trash`'s defense-in-depth `onEmpty`) own the graph
|
|
8
|
+
* side and decide whether to proceed after the JSONL outcome lands.
|
|
9
|
+
*
|
|
10
|
+
* Contract:
|
|
11
|
+
* - manager 204 (purged) or 404 (manager never knew the JSONL) both count
|
|
12
|
+
* as "transcript no longer exists" and proceed to the next sessionId.
|
|
13
|
+
* - manager 409 is the structural `pty-still-alive` stop check; the loop
|
|
14
|
+
* halts and the caller surfaces the live sessionId.
|
|
15
|
+
* - any other status or transport failure halts the loop with
|
|
16
|
+
* `jsonl-purge-failed`, naming the failing sessionId and the ids already
|
|
17
|
+
* purged at the failure point.
|
|
18
|
+
*
|
|
19
|
+
* Log lines mirror the Task 217 cascade so a single grep
|
|
20
|
+
* (`[admin-conversation-delete]`) covers both call sites.
|
|
21
|
+
*/
|
|
22
|
+
export interface PurgeAdminConversationJsonlsArgs {
|
|
23
|
+
/** Used only for log id8; not for routing. */
|
|
24
|
+
conversationId: string;
|
|
25
|
+
/** Used only for log line context; the helper does not enforce isolation
|
|
26
|
+
* (the caller has already account-scoped the candidate). */
|
|
27
|
+
accountId: string;
|
|
28
|
+
/** Loopback manager base URL — `http://127.0.0.1:${CLAUDE_SESSION_MANAGER_PORT}`. */
|
|
29
|
+
managerBase: string;
|
|
30
|
+
/** Pre-resolved sessionIds to purge. Caller chooses the lookup strategy
|
|
31
|
+
* (`getConversationClaudeSessionIds` for live conversations,
|
|
32
|
+
* elementId-based walk for trashed ones). */
|
|
33
|
+
claudeSessionIds: string[];
|
|
34
|
+
fetchImpl?: typeof fetch;
|
|
35
|
+
logger?: (line: string) => void;
|
|
36
|
+
}
|
|
37
|
+
export type PurgeAdminConversationJsonlsOutcome = {
|
|
38
|
+
ok: true;
|
|
39
|
+
claudeSessionIdsPurged: string[];
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
reason: "pty-still-alive";
|
|
43
|
+
claudeSessionId: string;
|
|
44
|
+
claudeSessionIdsPurged: string[];
|
|
45
|
+
} | {
|
|
46
|
+
ok: false;
|
|
47
|
+
reason: "jsonl-purge-failed";
|
|
48
|
+
claudeSessionId: string;
|
|
49
|
+
claudeSessionIdsPurged: string[];
|
|
50
|
+
status: number;
|
|
51
|
+
message: string;
|
|
52
|
+
};
|
|
53
|
+
export declare function purgeAdminConversationJsonls(args: PurgeAdminConversationJsonlsArgs): Promise<PurgeAdminConversationJsonlsOutcome>;
|
|
54
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,gCAAgC;IAC/C,8CAA8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB;iEAC6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,WAAW,EAAE,MAAM,CAAC;IACpB;;kDAE8C;IAC9C,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,mCAAmC,GAC3C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,sBAAsB,EAAE,MAAM,EAAE,CAAA;CAAE,GAC9C;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,iBAAiB,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,EAAE,CAAC;CAClC,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,oBAAoB,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,gCAAgC,GACrC,OAAO,CAAC,mCAAmC,CAAC,CA8D9C"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Admin-conversation JSONL purge — Task 237.
|
|
4
|
+
*
|
|
5
|
+
* Shared primitive that fires the claude-session-manager's
|
|
6
|
+
* `DELETE /:sessionId` per attached JSONL. Pure JSONL-only: it does not
|
|
7
|
+
* touch the graph. Callers (the `cascadeAdminConversationDelete` engine
|
|
8
|
+
* and `memory-empty-trash`'s defense-in-depth `onEmpty`) own the graph
|
|
9
|
+
* side and decide whether to proceed after the JSONL outcome lands.
|
|
10
|
+
*
|
|
11
|
+
* Contract:
|
|
12
|
+
* - manager 204 (purged) or 404 (manager never knew the JSONL) both count
|
|
13
|
+
* as "transcript no longer exists" and proceed to the next sessionId.
|
|
14
|
+
* - manager 409 is the structural `pty-still-alive` stop check; the loop
|
|
15
|
+
* halts and the caller surfaces the live sessionId.
|
|
16
|
+
* - any other status or transport failure halts the loop with
|
|
17
|
+
* `jsonl-purge-failed`, naming the failing sessionId and the ids already
|
|
18
|
+
* purged at the failure point.
|
|
19
|
+
*
|
|
20
|
+
* Log lines mirror the Task 217 cascade so a single grep
|
|
21
|
+
* (`[admin-conversation-delete]`) covers both call sites.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.purgeAdminConversationJsonls = purgeAdminConversationJsonls;
|
|
25
|
+
async function purgeAdminConversationJsonls(args) {
|
|
26
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
27
|
+
const log = args.logger ?? ((line) => console.log(line));
|
|
28
|
+
const id8 = args.conversationId.slice(0, 8);
|
|
29
|
+
const purged = [];
|
|
30
|
+
for (const sessionId of args.claudeSessionIds) {
|
|
31
|
+
const url = `${args.managerBase}/${encodeURIComponent(sessionId)}`;
|
|
32
|
+
let res;
|
|
33
|
+
try {
|
|
34
|
+
res = await fetchImpl(url, { method: "DELETE" });
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
log(`[admin-conversation-delete] conversationId=${id8} reason=jsonl-purge-failed claudeSessionId=${sessionId.slice(0, 8)} message=${message}`);
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
reason: "jsonl-purge-failed",
|
|
42
|
+
claudeSessionId: sessionId,
|
|
43
|
+
claudeSessionIdsPurged: purged,
|
|
44
|
+
status: 0,
|
|
45
|
+
message,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (res.status === 204 || res.status === 404) {
|
|
49
|
+
purged.push(sessionId);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (res.status === 409) {
|
|
53
|
+
log(`[admin-conversation-delete] conversationId=${id8} reason=pty-still-alive claudeSessionId=${sessionId.slice(0, 8)}`);
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: "pty-still-alive",
|
|
57
|
+
claudeSessionId: sessionId,
|
|
58
|
+
claudeSessionIdsPurged: purged,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
let message = `manager returned ${res.status}`;
|
|
62
|
+
try {
|
|
63
|
+
const body = (await res.json());
|
|
64
|
+
if (typeof body.detail === "string")
|
|
65
|
+
message = body.detail;
|
|
66
|
+
else if (typeof body.error === "string")
|
|
67
|
+
message = body.error;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// body isn't JSON — keep status-coded message
|
|
71
|
+
}
|
|
72
|
+
log(`[admin-conversation-delete] conversationId=${id8} reason=jsonl-purge-failed claudeSessionId=${sessionId.slice(0, 8)} message=${message}`);
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
reason: "jsonl-purge-failed",
|
|
76
|
+
claudeSessionId: sessionId,
|
|
77
|
+
claudeSessionIdsPurged: purged,
|
|
78
|
+
status: res.status,
|
|
79
|
+
message,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return { ok: true, claudeSessionIdsPurged: purged };
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin-conversation JSONL purge — Task 237.
|
|
3
|
+
*
|
|
4
|
+
* Shared primitive that fires the claude-session-manager's
|
|
5
|
+
* `DELETE /:sessionId` per attached JSONL. Pure JSONL-only: it does not
|
|
6
|
+
* touch the graph. Callers (the `cascadeAdminConversationDelete` engine
|
|
7
|
+
* and `memory-empty-trash`'s defense-in-depth `onEmpty`) own the graph
|
|
8
|
+
* side and decide whether to proceed after the JSONL outcome lands.
|
|
9
|
+
*
|
|
10
|
+
* Contract:
|
|
11
|
+
* - manager 204 (purged) or 404 (manager never knew the JSONL) both count
|
|
12
|
+
* as "transcript no longer exists" and proceed to the next sessionId.
|
|
13
|
+
* - manager 409 is the structural `pty-still-alive` stop check; the loop
|
|
14
|
+
* halts and the caller surfaces the live sessionId.
|
|
15
|
+
* - any other status or transport failure halts the loop with
|
|
16
|
+
* `jsonl-purge-failed`, naming the failing sessionId and the ids already
|
|
17
|
+
* purged at the failure point.
|
|
18
|
+
*
|
|
19
|
+
* Log lines mirror the Task 217 cascade so a single grep
|
|
20
|
+
* (`[admin-conversation-delete]`) covers both call sites.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface PurgeAdminConversationJsonlsArgs {
|
|
24
|
+
/** Used only for log id8; not for routing. */
|
|
25
|
+
conversationId: string;
|
|
26
|
+
/** Used only for log line context; the helper does not enforce isolation
|
|
27
|
+
* (the caller has already account-scoped the candidate). */
|
|
28
|
+
accountId: string;
|
|
29
|
+
/** Loopback manager base URL — `http://127.0.0.1:${CLAUDE_SESSION_MANAGER_PORT}`. */
|
|
30
|
+
managerBase: string;
|
|
31
|
+
/** Pre-resolved sessionIds to purge. Caller chooses the lookup strategy
|
|
32
|
+
* (`getConversationClaudeSessionIds` for live conversations,
|
|
33
|
+
* elementId-based walk for trashed ones). */
|
|
34
|
+
claudeSessionIds: string[];
|
|
35
|
+
fetchImpl?: typeof fetch;
|
|
36
|
+
logger?: (line: string) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type PurgeAdminConversationJsonlsOutcome =
|
|
40
|
+
| { ok: true; claudeSessionIdsPurged: string[] }
|
|
41
|
+
| {
|
|
42
|
+
ok: false;
|
|
43
|
+
reason: "pty-still-alive";
|
|
44
|
+
claudeSessionId: string;
|
|
45
|
+
claudeSessionIdsPurged: string[];
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
ok: false;
|
|
49
|
+
reason: "jsonl-purge-failed";
|
|
50
|
+
claudeSessionId: string;
|
|
51
|
+
claudeSessionIdsPurged: string[];
|
|
52
|
+
status: number;
|
|
53
|
+
message: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export async function purgeAdminConversationJsonls(
|
|
57
|
+
args: PurgeAdminConversationJsonlsArgs,
|
|
58
|
+
): Promise<PurgeAdminConversationJsonlsOutcome> {
|
|
59
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
60
|
+
const log = args.logger ?? ((line: string) => console.log(line));
|
|
61
|
+
const id8 = args.conversationId.slice(0, 8);
|
|
62
|
+
|
|
63
|
+
const purged: string[] = [];
|
|
64
|
+
for (const sessionId of args.claudeSessionIds) {
|
|
65
|
+
const url = `${args.managerBase}/${encodeURIComponent(sessionId)}`;
|
|
66
|
+
let res: Response;
|
|
67
|
+
try {
|
|
68
|
+
res = await fetchImpl(url, { method: "DELETE" });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
71
|
+
log(
|
|
72
|
+
`[admin-conversation-delete] conversationId=${id8} reason=jsonl-purge-failed claudeSessionId=${sessionId.slice(0, 8)} message=${message}`,
|
|
73
|
+
);
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
reason: "jsonl-purge-failed",
|
|
77
|
+
claudeSessionId: sessionId,
|
|
78
|
+
claudeSessionIdsPurged: purged,
|
|
79
|
+
status: 0,
|
|
80
|
+
message,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (res.status === 204 || res.status === 404) {
|
|
84
|
+
purged.push(sessionId);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (res.status === 409) {
|
|
88
|
+
log(
|
|
89
|
+
`[admin-conversation-delete] conversationId=${id8} reason=pty-still-alive claudeSessionId=${sessionId.slice(0, 8)}`,
|
|
90
|
+
);
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
reason: "pty-still-alive",
|
|
94
|
+
claudeSessionId: sessionId,
|
|
95
|
+
claudeSessionIdsPurged: purged,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
let message = `manager returned ${res.status}`;
|
|
99
|
+
try {
|
|
100
|
+
const body = (await res.json()) as { error?: string; detail?: string };
|
|
101
|
+
if (typeof body.detail === "string") message = body.detail;
|
|
102
|
+
else if (typeof body.error === "string") message = body.error;
|
|
103
|
+
} catch {
|
|
104
|
+
// body isn't JSON — keep status-coded message
|
|
105
|
+
}
|
|
106
|
+
log(
|
|
107
|
+
`[admin-conversation-delete] conversationId=${id8} reason=jsonl-purge-failed claudeSessionId=${sessionId.slice(0, 8)} message=${message}`,
|
|
108
|
+
);
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
reason: "jsonl-purge-failed",
|
|
112
|
+
claudeSessionId: sessionId,
|
|
113
|
+
claudeSessionIdsPurged: purged,
|
|
114
|
+
status: res.status,
|
|
115
|
+
message,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { ok: true, claudeSessionIdsPurged: purged };
|
|
120
|
+
}
|
|
@@ -59,7 +59,18 @@ export interface EmptyTrashParams {
|
|
|
59
59
|
dryRun?: boolean;
|
|
60
60
|
/** Optional label whitelist — only nodes carrying any of these labels are eligible. */
|
|
61
61
|
labels?: string[];
|
|
62
|
-
/** Side-effect callback invoked
|
|
62
|
+
/** Side-effect callback invoked before the `DETACH DELETE` for each
|
|
63
|
+
* emptied node, e.g. to clean disk attachments or purge sibling JSONL
|
|
64
|
+
* transcripts.
|
|
65
|
+
*
|
|
66
|
+
* Contract (Task 237): if the callback **throws**, the candidate is
|
|
67
|
+
* **skipped this run** — no `DETACH DELETE` runs and no `[trash:emptied]`
|
|
68
|
+
* line emits. The thrown message lands in
|
|
69
|
+
* `[trash:empty-run] reason=onEmpty-failed` and the candidate becomes
|
|
70
|
+
* eligible again on the next sweep. Pre-Task-237 consumers
|
|
71
|
+
* (`removeAttachmentDir` for `:KnowledgeDocument`) cannot throw because
|
|
72
|
+
* they wrap `fs.rm({force:true})`, so the contract change is invisible
|
|
73
|
+
* to them. */
|
|
63
74
|
onEmpty?: (node: TrashCandidate) => Promise<void>;
|
|
64
75
|
}
|
|
65
76
|
export interface TrashCandidate {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAsC5C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAyIzE;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA6G/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAsC5C,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAyIzE;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA6G/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;;;;;;;;;mBAWe;IACf,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiFpF;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAI9D;AAED,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAAqB,CAAC"}
|