@rubytech/create-realagent-code 0.1.605 → 0.1.607
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__/claude-trust-seed.test.js +125 -0
- package/dist/claude-trust-seed.js +52 -0
- package/dist/index.js +108 -34
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +16 -1
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +9 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +13 -0
- package/payload/platform/plugins/docs/references/platform.md +2 -0
- package/payload/platform/services/claude-session-manager/dist/claude-trust-census.d.ts +34 -0
- package/payload/platform/services/claude-session-manager/dist/claude-trust-census.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/claude-trust-census.js +83 -0
- package/payload/platform/services/claude-session-manager/dist/claude-trust-census.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.js +13 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/agents.html +5 -5
- package/payload/server/public/assets/AdminLoginScreens-DuQ3ax5U.js +1 -0
- package/payload/server/public/assets/AdminLoginScreens-DuQ3ax5U.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-DuQ3ax5U.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-1HyATV_t.js +3 -0
- package/payload/server/public/assets/AdminShell-1HyATV_t.js.br +0 -0
- package/payload/server/public/assets/AdminShell-1HyATV_t.js.gz +0 -0
- package/payload/server/public/assets/activity-C2qWlcJ5.js +1 -0
- package/payload/server/public/assets/activity-C2qWlcJ5.js.br +0 -0
- package/payload/server/public/assets/activity-C2qWlcJ5.js.gz +0 -0
- package/payload/server/public/assets/admin-DqpfY8-t.js +1 -0
- package/payload/server/public/assets/admin-DqpfY8-t.js.br +0 -0
- package/payload/server/public/assets/admin-DqpfY8-t.js.gz +0 -0
- package/payload/server/public/assets/agents-DoGVAjIT.js +1 -0
- package/payload/server/public/assets/agents-DoGVAjIT.js.br +0 -0
- package/payload/server/public/assets/agents-DoGVAjIT.js.gz +0 -0
- package/payload/server/public/assets/{browser-CqDBcKwU.js → browser-DjqS897k.js} +1 -1
- package/payload/server/public/assets/browser-DjqS897k.js.br +0 -0
- package/payload/server/public/assets/browser-DjqS897k.js.gz +0 -0
- package/payload/server/public/assets/calendar-DJeOm0WS.js +1 -0
- package/payload/server/public/assets/calendar-DJeOm0WS.js.br +0 -0
- package/payload/server/public/assets/calendar-DJeOm0WS.js.gz +0 -0
- package/payload/server/public/assets/chat-BrRE0Dck.js +1 -0
- package/payload/server/public/assets/chat-BrRE0Dck.js.br +0 -0
- package/payload/server/public/assets/chat-BrRE0Dck.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-D9z1aK3p.js +1 -0
- package/payload/server/public/assets/chevron-left-D9z1aK3p.js.br +0 -0
- package/payload/server/public/assets/clock-BqHWMICF.js +1 -0
- package/payload/server/public/assets/clock-BqHWMICF.js.br +0 -0
- package/payload/server/public/assets/clock-BqHWMICF.js.gz +0 -0
- package/payload/server/public/assets/copy-p9ZM9fnn.js +1 -0
- package/payload/server/public/assets/copy-p9ZM9fnn.js.br +0 -0
- package/payload/server/public/assets/copy-p9ZM9fnn.js.gz +0 -0
- package/payload/server/public/assets/data-CPsqW0qW.js +1 -0
- package/payload/server/public/assets/data-CPsqW0qW.js.br +0 -0
- package/payload/server/public/assets/data-CPsqW0qW.js.gz +0 -0
- package/payload/server/public/assets/{field-X4OVA3vx.js → field-WRk67lvr.js} +1 -1
- package/payload/server/public/assets/field-WRk67lvr.js.br +0 -0
- package/payload/server/public/assets/field-WRk67lvr.js.gz +0 -0
- package/payload/server/public/assets/file-text-BJDCb334.js +1 -0
- package/payload/server/public/assets/file-text-BJDCb334.js.br +0 -0
- package/payload/server/public/assets/file-text-BJDCb334.js.gz +0 -0
- package/payload/server/public/assets/{graph-l7tzHqvm.js → graph-CAU2xh2Z.js} +2 -2
- package/payload/server/public/assets/graph-CAU2xh2Z.js.br +0 -0
- package/payload/server/public/assets/graph-CAU2xh2Z.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-CEDmX7qW.js +1 -0
- package/payload/server/public/assets/graph-labels-CEDmX7qW.js.br +0 -0
- package/payload/server/public/assets/graph-labels-CEDmX7qW.js.gz +0 -0
- package/payload/server/public/assets/{jsx-runtime-bq0g4NlA.css → jsx-runtime-hPFGo4rC.css} +1 -1
- package/payload/server/public/assets/jsx-runtime-hPFGo4rC.css.br +0 -0
- package/payload/server/public/assets/{jsx-runtime-bq0g4NlA.css.gz → jsx-runtime-hPFGo4rC.css.gz} +0 -0
- package/payload/server/public/assets/{operator-Dg-dwf7a.js → operator-1WFztEQA.js} +1 -1
- package/payload/server/public/assets/operator-1WFztEQA.js.br +0 -0
- package/payload/server/public/assets/operator-1WFztEQA.js.gz +0 -0
- package/payload/server/public/assets/page-B72XigcU.js +32 -0
- package/payload/server/public/assets/page-B72XigcU.js.br +0 -0
- package/payload/server/public/assets/page-B72XigcU.js.gz +0 -0
- package/payload/server/public/assets/page-nT8l_cX1.js +1 -0
- package/payload/server/public/assets/page-nT8l_cX1.js.br +0 -0
- package/payload/server/public/assets/page-nT8l_cX1.js.gz +0 -0
- package/payload/server/public/assets/play-CYqOCqm6.js +1 -0
- package/payload/server/public/assets/play-CYqOCqm6.js.br +0 -0
- package/payload/server/public/assets/play-CYqOCqm6.js.gz +0 -0
- package/payload/server/public/assets/public-DAea6w7w.js +1 -0
- package/payload/server/public/assets/public-DAea6w7w.js.br +0 -0
- package/payload/server/public/assets/public-DAea6w7w.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-CqlRlGsY.js +1 -0
- package/payload/server/public/assets/rotate-ccw-CqlRlGsY.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-CqlRlGsY.js.gz +0 -0
- package/payload/server/public/assets/{routines-6fxZNacQ.js → routines-vy5xsyqx.js} +2 -2
- package/payload/server/public/assets/routines-vy5xsyqx.js.br +0 -0
- package/payload/server/public/assets/routines-vy5xsyqx.js.gz +0 -0
- package/payload/server/public/assets/{skills-51mqx-s5.js → skills-X7FTilVy.js} +1 -1
- package/payload/server/public/assets/skills-X7FTilVy.js.br +0 -0
- package/payload/server/public/assets/skills-X7FTilVy.js.gz +0 -0
- package/payload/server/public/assets/tasks-BXx_jk_p.js +3 -0
- package/payload/server/public/assets/tasks-BXx_jk_p.js.br +0 -0
- package/payload/server/public/assets/tasks-BXx_jk_p.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-CuWcGaRo.js +1 -0
- package/payload/server/public/assets/triangle-alert-CuWcGaRo.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-CuWcGaRo.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-BG7h37Rx.js → useCopyFeedback-DLSKs6r2.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-DLSKs6r2.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-DLSKs6r2.js.gz +0 -0
- package/payload/server/public/assets/useMediaQuery-C_1CV_iW.js +6 -0
- package/payload/server/public/assets/useMediaQuery-C_1CV_iW.js.br +0 -0
- package/payload/server/public/assets/useMediaQuery-C_1CV_iW.js.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-DBzM_HIx.js +2 -0
- package/payload/server/public/assets/useVoiceRecorder-DBzM_HIx.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-DBzM_HIx.js.gz +0 -0
- package/payload/server/public/assets/wrench-nDMQ6x-6.js +1 -0
- package/payload/server/public/assets/wrench-nDMQ6x-6.js.br +0 -0
- package/payload/server/public/assets/wrench-nDMQ6x-6.js.gz +0 -0
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +8 -8
- package/payload/server/public/chat.html +14 -14
- package/payload/server/public/data.html +12 -12
- package/payload/server/public/field.html +3 -3
- package/payload/server/public/graph.html +10 -10
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +15 -15
- package/payload/server/public/public.html +14 -14
- package/payload/server/public/routines.html +8 -8
- package/payload/server/public/skills.html +6 -6
- package/payload/server/public/tasks.html +7 -7
- package/payload/server/server.js +1 -1
- package/payload/server/public/assets/AdminLoginScreens-DI5bNlX8.js +0 -1
- package/payload/server/public/assets/AdminLoginScreens-DI5bNlX8.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-DI5bNlX8.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-B-iX9ahJ.js +0 -3
- package/payload/server/public/assets/AdminShell-B-iX9ahJ.js.br +0 -0
- package/payload/server/public/assets/AdminShell-B-iX9ahJ.js.gz +0 -0
- package/payload/server/public/assets/activity-CqgFwANo.js +0 -1
- package/payload/server/public/assets/activity-CqgFwANo.js.br +0 -0
- package/payload/server/public/assets/activity-CqgFwANo.js.gz +0 -0
- package/payload/server/public/assets/admin-K63n6ybW.js +0 -1
- package/payload/server/public/assets/admin-K63n6ybW.js.br +0 -0
- package/payload/server/public/assets/admin-K63n6ybW.js.gz +0 -0
- package/payload/server/public/assets/agents-cue5Zf8X.js +0 -1
- package/payload/server/public/assets/agents-cue5Zf8X.js.br +0 -0
- package/payload/server/public/assets/agents-cue5Zf8X.js.gz +0 -0
- package/payload/server/public/assets/browser-CqDBcKwU.js.br +0 -0
- package/payload/server/public/assets/browser-CqDBcKwU.js.gz +0 -0
- package/payload/server/public/assets/calendar-BgzuW5Rt.js +0 -1
- package/payload/server/public/assets/calendar-BgzuW5Rt.js.br +0 -0
- package/payload/server/public/assets/calendar-BgzuW5Rt.js.gz +0 -0
- package/payload/server/public/assets/chat-HYZViu2u.js +0 -1
- package/payload/server/public/assets/chat-HYZViu2u.js.br +0 -3
- package/payload/server/public/assets/chat-HYZViu2u.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-C5FXH9r_.js +0 -1
- package/payload/server/public/assets/chevron-left-C5FXH9r_.js.br +0 -0
- package/payload/server/public/assets/clock-DxPtyejC.js +0 -1
- package/payload/server/public/assets/clock-DxPtyejC.js.br +0 -0
- package/payload/server/public/assets/clock-DxPtyejC.js.gz +0 -0
- package/payload/server/public/assets/copy-BNhZ0flH.js +0 -1
- package/payload/server/public/assets/copy-BNhZ0flH.js.br +0 -0
- package/payload/server/public/assets/copy-BNhZ0flH.js.gz +0 -0
- package/payload/server/public/assets/data-kCBB8Z4Z.js +0 -1
- package/payload/server/public/assets/data-kCBB8Z4Z.js.br +0 -0
- package/payload/server/public/assets/data-kCBB8Z4Z.js.gz +0 -0
- package/payload/server/public/assets/field-X4OVA3vx.js.br +0 -0
- package/payload/server/public/assets/field-X4OVA3vx.js.gz +0 -0
- package/payload/server/public/assets/file-text-BddO_2EI.js +0 -1
- package/payload/server/public/assets/file-text-BddO_2EI.js.br +0 -0
- package/payload/server/public/assets/file-text-BddO_2EI.js.gz +0 -0
- package/payload/server/public/assets/graph-l7tzHqvm.js.br +0 -0
- package/payload/server/public/assets/graph-l7tzHqvm.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-CtFFq5W2.js +0 -1
- package/payload/server/public/assets/graph-labels-CtFFq5W2.js.br +0 -0
- package/payload/server/public/assets/graph-labels-CtFFq5W2.js.gz +0 -0
- package/payload/server/public/assets/jsx-runtime-bq0g4NlA.css.br +0 -0
- package/payload/server/public/assets/operator-Dg-dwf7a.js.br +0 -7
- package/payload/server/public/assets/operator-Dg-dwf7a.js.gz +0 -0
- package/payload/server/public/assets/page-Ci1bfIbs.js +0 -1
- package/payload/server/public/assets/page-Ci1bfIbs.js.br +0 -0
- package/payload/server/public/assets/page-Ci1bfIbs.js.gz +0 -0
- package/payload/server/public/assets/page-Y9JmFq0Q.js +0 -32
- package/payload/server/public/assets/page-Y9JmFq0Q.js.br +0 -0
- package/payload/server/public/assets/page-Y9JmFq0Q.js.gz +0 -0
- package/payload/server/public/assets/play-BSZVGpQT.js +0 -1
- package/payload/server/public/assets/play-BSZVGpQT.js.br +0 -0
- package/payload/server/public/assets/play-BSZVGpQT.js.gz +0 -0
- package/payload/server/public/assets/public-m66FAza_.js +0 -1
- package/payload/server/public/assets/public-m66FAza_.js.br +0 -0
- package/payload/server/public/assets/public-m66FAza_.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-DTOJmOxB.js +0 -1
- package/payload/server/public/assets/rotate-ccw-DTOJmOxB.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-DTOJmOxB.js.gz +0 -0
- package/payload/server/public/assets/routines-6fxZNacQ.js.br +0 -0
- package/payload/server/public/assets/routines-6fxZNacQ.js.gz +0 -0
- package/payload/server/public/assets/skills-51mqx-s5.js.br +0 -0
- package/payload/server/public/assets/skills-51mqx-s5.js.gz +0 -0
- package/payload/server/public/assets/tasks-D4y-Cwa3.js +0 -3
- package/payload/server/public/assets/tasks-D4y-Cwa3.js.br +0 -0
- package/payload/server/public/assets/tasks-D4y-Cwa3.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-BzCjDFts.js +0 -1
- package/payload/server/public/assets/triangle-alert-BzCjDFts.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-BzCjDFts.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-BG7h37Rx.js.br +0 -2
- package/payload/server/public/assets/useCopyFeedback-BG7h37Rx.js.gz +0 -0
- package/payload/server/public/assets/useMediaQuery-CqwktQr_.js +0 -6
- package/payload/server/public/assets/useMediaQuery-CqwktQr_.js.br +0 -0
- package/payload/server/public/assets/useMediaQuery-CqwktQr_.js.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-CAZcUc1s.js +0 -2
- package/payload/server/public/assets/useVoiceRecorder-CAZcUc1s.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-CAZcUc1s.js.gz +0 -0
- package/payload/server/public/assets/wrench-B2WKLLQG.js +0 -1
- package/payload/server/public/assets/wrench-B2WKLLQG.js.br +0 -0
- package/payload/server/public/assets/wrench-B2WKLLQG.js.gz +0 -0
- /package/payload/server/public/assets/{jsx-runtime-UK6hgBWO.js → jsx-runtime-CvVXOszc.js} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-UK6hgBWO.js.br → jsx-runtime-CvVXOszc.js.br} +0 -0
- /package/payload/server/public/assets/{jsx-runtime-UK6hgBWO.js.gz → jsx-runtime-CvVXOszc.js.gz} +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { seedClaudeTrust, formatTrustSeedLine } from "../claude-trust-seed.js";
|
|
4
|
+
const ROOT = "/home/neo/maxy-code/data/accounts";
|
|
5
|
+
const A = `${ROOT}/aaa`;
|
|
6
|
+
const B = `${ROOT}/bbb`;
|
|
7
|
+
test("seeds every account directory that exists, alongside $HOME", () => {
|
|
8
|
+
const r = seedClaudeTrust({
|
|
9
|
+
state: {},
|
|
10
|
+
homeWorkspace: "/home/neo",
|
|
11
|
+
accountsRoot: ROOT,
|
|
12
|
+
accountDirs: [A, B],
|
|
13
|
+
});
|
|
14
|
+
const projects = r.state.projects;
|
|
15
|
+
assert.equal(projects["/home/neo"].hasTrustDialogAccepted, true);
|
|
16
|
+
assert.equal(projects[A].hasTrustDialogAccepted, true);
|
|
17
|
+
assert.equal(projects[B].hasTrustDialogAccepted, true);
|
|
18
|
+
assert.equal(r.state.hasCompletedOnboarding, true);
|
|
19
|
+
assert.deepEqual({ accounts: r.accounts, seeded: r.seeded, alreadyTrusted: r.alreadyTrusted, stale: r.stale }, { accounts: 2, seeded: 2, alreadyTrusted: 0, stale: 0 });
|
|
20
|
+
});
|
|
21
|
+
test("preserves other keys on an account entry and counts an existing true as already-set", () => {
|
|
22
|
+
const r = seedClaudeTrust({
|
|
23
|
+
state: {
|
|
24
|
+
numStartups: 519,
|
|
25
|
+
projects: {
|
|
26
|
+
[A]: { hasTrustDialogAccepted: true, exampleFilesGenerated: true },
|
|
27
|
+
[B]: { hasTrustDialogAccepted: false, lastCost: 1.5 },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
homeWorkspace: "/home/neo",
|
|
31
|
+
accountsRoot: ROOT,
|
|
32
|
+
accountDirs: [A, B],
|
|
33
|
+
});
|
|
34
|
+
const projects = r.state.projects;
|
|
35
|
+
assert.equal(r.state.numStartups, 519);
|
|
36
|
+
assert.equal(projects[A].exampleFilesGenerated, true);
|
|
37
|
+
assert.equal(projects[B].lastCost, 1.5);
|
|
38
|
+
assert.equal(projects[B].hasTrustDialogAccepted, true);
|
|
39
|
+
assert.equal(r.alreadyTrusted, 1);
|
|
40
|
+
assert.equal(r.seeded, 1);
|
|
41
|
+
});
|
|
42
|
+
test("an entry whose directory is gone keeps its value and counts as stale", () => {
|
|
43
|
+
const gone = `${ROOT}/deleted`;
|
|
44
|
+
const r = seedClaudeTrust({
|
|
45
|
+
state: { projects: { [gone]: { hasTrustDialogAccepted: false } } },
|
|
46
|
+
homeWorkspace: "/home/neo",
|
|
47
|
+
accountsRoot: ROOT,
|
|
48
|
+
accountDirs: [A],
|
|
49
|
+
});
|
|
50
|
+
const projects = r.state.projects;
|
|
51
|
+
assert.equal(projects[gone].hasTrustDialogAccepted, false);
|
|
52
|
+
assert.equal(r.stale, 1);
|
|
53
|
+
assert.equal(r.accounts, 1);
|
|
54
|
+
});
|
|
55
|
+
// The CLI records an entry for any directory a session ran in, so an account's
|
|
56
|
+
// own subdirectories sit under the accounts root too. Counting them as stale
|
|
57
|
+
// accounts would inflate the field for every operator who worked inside one.
|
|
58
|
+
test("a subdirectory of a live account is not counted as a stale account", () => {
|
|
59
|
+
const r = seedClaudeTrust({
|
|
60
|
+
state: {
|
|
61
|
+
projects: {
|
|
62
|
+
[A]: { hasTrustDialogAccepted: true },
|
|
63
|
+
[`${A}/sites/portal`]: { hasTrustDialogAccepted: true },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
homeWorkspace: "/home/neo",
|
|
67
|
+
accountsRoot: ROOT,
|
|
68
|
+
accountDirs: [A],
|
|
69
|
+
});
|
|
70
|
+
assert.equal(r.stale, 0);
|
|
71
|
+
});
|
|
72
|
+
test("no account directories is accounts=0, not a throw", () => {
|
|
73
|
+
const r = seedClaudeTrust({
|
|
74
|
+
state: {},
|
|
75
|
+
homeWorkspace: "/home/neo",
|
|
76
|
+
accountsRoot: ROOT,
|
|
77
|
+
accountDirs: [],
|
|
78
|
+
});
|
|
79
|
+
assert.equal(r.accounts, 0);
|
|
80
|
+
assert.equal(r.state.projects["/home/neo"] !== undefined, true);
|
|
81
|
+
});
|
|
82
|
+
// deployPayload() runs before setupAccount(), so on a FRESH install the first
|
|
83
|
+
// pass sees no accounts directory at all and the account that setupAccount
|
|
84
|
+
// then creates would never be trusted. The installer therefore primes twice;
|
|
85
|
+
// this pins the shape that second pass depends on.
|
|
86
|
+
test("fresh-install shape: an empty first pass then a second pass seeds the new account", () => {
|
|
87
|
+
const first = seedClaudeTrust({
|
|
88
|
+
state: {},
|
|
89
|
+
homeWorkspace: "/home/neo",
|
|
90
|
+
accountsRoot: ROOT,
|
|
91
|
+
accountDirs: [],
|
|
92
|
+
});
|
|
93
|
+
assert.equal(first.accounts, 0);
|
|
94
|
+
assert.equal(first.seeded, 0);
|
|
95
|
+
const second = seedClaudeTrust({
|
|
96
|
+
state: first.state,
|
|
97
|
+
homeWorkspace: "/home/neo",
|
|
98
|
+
accountsRoot: ROOT,
|
|
99
|
+
accountDirs: [A],
|
|
100
|
+
});
|
|
101
|
+
const projects = second.state.projects;
|
|
102
|
+
assert.equal(projects[A].hasTrustDialogAccepted, true);
|
|
103
|
+
assert.equal(second.accounts, 1);
|
|
104
|
+
assert.equal(second.seeded, 1);
|
|
105
|
+
assert.equal(projects["/home/neo"].hasTrustDialogAccepted, true);
|
|
106
|
+
});
|
|
107
|
+
test("the install line names which pass ran", () => {
|
|
108
|
+
const r = seedClaudeTrust({
|
|
109
|
+
state: {},
|
|
110
|
+
homeWorkspace: "/home/neo",
|
|
111
|
+
accountsRoot: ROOT,
|
|
112
|
+
accountDirs: [A],
|
|
113
|
+
});
|
|
114
|
+
const line = formatTrustSeedLine("/cfg/.claude.json", "/home/neo", r, "none", "post-account");
|
|
115
|
+
assert.equal(line, " [install] op=claude-trust-seed phase=post-account file=/cfg/.claude.json home=/home/neo" +
|
|
116
|
+
" accounts=1 seeded=1 alreadyTrusted=0 stale=0 error=none");
|
|
117
|
+
});
|
|
118
|
+
test("second run over the first run's output is byte-identical", () => {
|
|
119
|
+
const input = { state: {}, homeWorkspace: "/home/neo", accountsRoot: ROOT, accountDirs: [A, B] };
|
|
120
|
+
const first = seedClaudeTrust(input);
|
|
121
|
+
const second = seedClaudeTrust({ ...input, state: first.state });
|
|
122
|
+
assert.equal(JSON.stringify(second.state), JSON.stringify(first.state));
|
|
123
|
+
assert.equal(second.seeded, 0);
|
|
124
|
+
assert.equal(second.alreadyTrusted, 2);
|
|
125
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Task 2720 — Claude Code scopes folder trust to the git repository root, and
|
|
2
|
+
// provision-account-dir.sh gives every account directory its own repo, so
|
|
3
|
+
// trusting $HOME does not reach them. An untrusted workspace halts the
|
|
4
|
+
// PTY-spawned `claude --remote-control` on the folder-trust dialog before it
|
|
5
|
+
// binds, which reads downstream as `op=bind-signal-absent … pidFileSeen=false`
|
|
6
|
+
// and, because /rc-spawn answers 200, reaches the operator as silence.
|
|
7
|
+
//
|
|
8
|
+
// Pure so the counts and the preservation invariant are testable without a
|
|
9
|
+
// filesystem; the caller owns the read and the write.
|
|
10
|
+
export function seedClaudeTrust(input) {
|
|
11
|
+
const state = input.state;
|
|
12
|
+
state.hasCompletedOnboarding = true;
|
|
13
|
+
state.theme = state.theme ?? "auto";
|
|
14
|
+
const projects = (state.projects ?? {});
|
|
15
|
+
const homeEntry = projects[input.homeWorkspace] ?? {};
|
|
16
|
+
homeEntry.hasTrustDialogAccepted = true;
|
|
17
|
+
projects[input.homeWorkspace] = homeEntry;
|
|
18
|
+
let seeded = 0;
|
|
19
|
+
let alreadyTrusted = 0;
|
|
20
|
+
for (const dir of input.accountDirs) {
|
|
21
|
+
const entry = projects[dir] ?? {};
|
|
22
|
+
if (entry.hasTrustDialogAccepted === true) {
|
|
23
|
+
alreadyTrusted += 1;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
entry.hasTrustDialogAccepted = true;
|
|
27
|
+
seeded += 1;
|
|
28
|
+
}
|
|
29
|
+
projects[dir] = entry;
|
|
30
|
+
}
|
|
31
|
+
// A deleted account cannot be spawned into, so its entry is left exactly as
|
|
32
|
+
// the CLI wrote it. Counting it separately keeps the install line's
|
|
33
|
+
// `seeded + alreadyTrusted === accounts` invariant meaningful.
|
|
34
|
+
//
|
|
35
|
+
// Direct children only. The CLI records an entry for ANY directory a session
|
|
36
|
+
// ran in, so an account's own subdirectory (a `sites/<project>` tree, say)
|
|
37
|
+
// also sits under this prefix; counting those as stale accounts would inflate
|
|
38
|
+
// the field every time somebody worked inside an account.
|
|
39
|
+
const onDisk = new Set(input.accountDirs);
|
|
40
|
+
const prefix = `${input.accountsRoot}/`;
|
|
41
|
+
const stale = Object.keys(projects).filter((k) => k.startsWith(prefix) && !k.slice(prefix.length).includes("/") && !onDisk.has(k)).length;
|
|
42
|
+
state.projects = projects;
|
|
43
|
+
return { state, accounts: input.accountDirs.length, seeded, alreadyTrusted, stale };
|
|
44
|
+
}
|
|
45
|
+
/** `phase` distinguishes the two passes: `deploy` seeds an upgrade's existing
|
|
46
|
+
* accounts, `post-account` seeds the directory a fresh install just created.
|
|
47
|
+
* Without it the two lines are byte-identical and the operator cannot tell
|
|
48
|
+
* which pass did the work. */
|
|
49
|
+
export function formatTrustSeedLine(file, home, r, error, phase) {
|
|
50
|
+
return (` [install] op=claude-trust-seed phase=${phase} file=${file} home=${home} accounts=${r.accounts}` +
|
|
51
|
+
` seeded=${r.seeded} alreadyTrusted=${r.alreadyTrusted} stale=${r.stale} error=${error}`);
|
|
52
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, statSync, realpathSync, accessSync, constants as fsConstants } from "node:fs";
|
|
4
4
|
import { clearBrandAgentRoot } from "./brand-agent-root.js";
|
|
5
5
|
import { seedBypassPermissionsSettings, assertBypassPermissionsSeed } from "./permissions-seed.js";
|
|
6
|
-
import { resolve, join, dirname } from "node:path";
|
|
6
|
+
import { resolve, join, dirname, basename } from "node:path";
|
|
7
7
|
import { randomBytes } from "node:crypto";
|
|
8
8
|
import { resolveInstallPortFromFs, buildMaxyUnitFile, buildClaudeSessionManagerUnitFile, buildEmailSamplerUnitFile, buildClaudePtysSliceUnitFile, buildCloudflaredSliceUnitFile, buildDarwinServiceEnvPath } from "./port-resolution.js";
|
|
9
9
|
import { buildBrandCronBlock, mergeBrandCronBlock } from "./cron-registration.js";
|
|
@@ -27,6 +27,7 @@ import { parseSwVers, isSupportedMacosVersion } from "./macos-version.js";
|
|
|
27
27
|
import { decideChromiumAction, isSnapConfinedPath } from "./snap-chromium.js";
|
|
28
28
|
import { classifyPortHolder } from "./preflight-port-classifier.js";
|
|
29
29
|
import { memoryControllerMissing, cmdlineAlreadyPatched, patchCmdline } from "./cgroup-memory-controller.js";
|
|
30
|
+
import { seedClaudeTrust, formatTrustSeedLine } from "./claude-trust-seed.js";
|
|
30
31
|
import { parsePluginList, computeInstallActions, parseExternalPlugins, findUnregisteredResyncs, discoverMarketplaceCandidateDirs, } from "./lib/plugin-install.js";
|
|
31
32
|
import { findPremiumMcpDirs } from "./lib/premium-mcp-discover.js";
|
|
32
33
|
import { pickBindDecision, pickEdgeBindDecision, formatEdgeBindDecision, pickWebsockifyBind, formatWebsockifyBindFile, mergeSmbConf, formatSambaMarker, SAMBA_ENABLE_UNITS, } from "./samba-provision.js";
|
|
@@ -569,6 +570,7 @@ function ensureNonSnapChromium() {
|
|
|
569
570
|
}
|
|
570
571
|
RESOLVED_CHROMIUM_BIN = decision.resolvedPath;
|
|
571
572
|
}
|
|
573
|
+
RESOLVED_CHROMIUM_BIN = preferUnwrappedChromium(RESOLVED_CHROMIUM_BIN);
|
|
572
574
|
// Defensive: never persist a snap-confined path. If realpath of the resolved
|
|
573
575
|
// binary still lands under /snap/ (e.g. apt landed a snap package by mistake
|
|
574
576
|
// on a misconfigured device), throw before writeChromiumBinaryPathFile sees
|
|
@@ -621,6 +623,44 @@ function runChromiumPostInstallGate(chromiumBin) {
|
|
|
621
623
|
console.log(" Chromium post-install gate passed.");
|
|
622
624
|
logFile(` [snap-chromium] post-install gate ok: ${chromiumBin} exit=0`);
|
|
623
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Prefer the real Chromium ELF binary over a distro shell wrapper.
|
|
628
|
+
*
|
|
629
|
+
* Debian and Raspberry Pi OS ship `/usr/bin/chromium` as a POSIX shell script
|
|
630
|
+
* that appends its own flags and then `exec $LIBDIR/$APPNAME "$@"`, where
|
|
631
|
+
* `LIBDIR=/usr/lib/$APPNAME` — so the real binary is derivable from the
|
|
632
|
+
* wrapper's own layout rather than guessed.
|
|
633
|
+
*
|
|
634
|
+
* On aarch64 with a page size above 4096 that wrapper adds
|
|
635
|
+
* `--js-flags=--no-decommit-pooled-pages` (its workaround for Debian #1089647).
|
|
636
|
+
* Current Chromium's V8 has removed that flag, so V8 exits "unrecognized flag",
|
|
637
|
+
* the GPU process dies with it, and the installer's CDP check then correctly
|
|
638
|
+
* finds nothing listening and aborts the upgrade. Measured on the beacons Pi
|
|
639
|
+
* 2026-08-14: Chromium 148.0.7778.167, `getconf PAGESIZE` 16384, and
|
|
640
|
+
* `chromium --js-flags=--no-decommit-pooled-pages` reproduces it directly.
|
|
641
|
+
*
|
|
642
|
+
* Execing the binary skips the wrapper and every flag it would inject. The
|
|
643
|
+
* wrapper is returned unchanged whenever the shape does not match — not a
|
|
644
|
+
* script, no binary at the derived path, or that path is not ELF — so a distro
|
|
645
|
+
* laying Chromium out differently is untouched.
|
|
646
|
+
*/
|
|
647
|
+
function preferUnwrappedChromium(binPath) {
|
|
648
|
+
const fileType = (p) => {
|
|
649
|
+
const r = spawnSync("file", ["-b", p], { encoding: "utf-8", stdio: "pipe", timeout: 5_000 });
|
|
650
|
+
return r.status === 0 ? (r.stdout ?? "").trim() : "";
|
|
651
|
+
};
|
|
652
|
+
if (!/shell script/i.test(fileType(binPath)))
|
|
653
|
+
return binPath;
|
|
654
|
+
const appName = basename(binPath);
|
|
655
|
+
const real = `/usr/lib/${appName}/${appName}`;
|
|
656
|
+
if (!existsSync(real) || !/^ELF/.test(fileType(real))) {
|
|
657
|
+
logFile(` [chromium-wrapper] ${binPath} is a wrapper but ${real} is not a usable binary — keeping the wrapper`);
|
|
658
|
+
return binPath;
|
|
659
|
+
}
|
|
660
|
+
console.log(` Chromium wrapper bypassed: ${binPath} → ${real}`);
|
|
661
|
+
logFile(` [chromium-wrapper] bypassed wrapper=${binPath} binary=${real}`);
|
|
662
|
+
return real;
|
|
663
|
+
}
|
|
624
664
|
/**
|
|
625
665
|
* write the resolved Chromium absolute path to
|
|
626
666
|
* `<INSTALL_DIR>/platform/config/chromium-binary.path` so vnc.sh and
|
|
@@ -2488,6 +2528,67 @@ function runInstallInvariantCheck(usersFile, accountsDir) {
|
|
|
2488
2528
|
}
|
|
2489
2529
|
console.log(` ${TAG} check complete divergences=${divergences}`);
|
|
2490
2530
|
}
|
|
2531
|
+
// pre-populate the brand `.claude.json` so the manager's PTY-spawned
|
|
2532
|
+
// `claude --verbose --remote-control` boots straight into the session URL
|
|
2533
|
+
// without stopping at the first-run theme picker or the workspace-trust
|
|
2534
|
+
// dialog. Both prompts ate the manager's 30 s url-capture window on
|
|
2535
|
+
// 0.1.6 / 0.1.8 installs (Pi 192.168.88.16 verified). The markers required:
|
|
2536
|
+
// - hasCompletedOnboarding: true (suppresses theme picker)
|
|
2537
|
+
// - projects[<dir>].hasTrustDialogAccepted: true, for $HOME AND for every
|
|
2538
|
+
// account directory (Task 2720). Claude Code scopes folder trust to the
|
|
2539
|
+
// git repository root, and provision-account-dir.sh gives each account
|
|
2540
|
+
// directory its own repo, so $HOME's trust does not reach them. Seeding
|
|
2541
|
+
// $HOME alone left every spawn halted on the trust dialog.
|
|
2542
|
+
// Existing fields (firstStartTime, oauthAccount, migrationVersion, …) are
|
|
2543
|
+
// preserved so a brand re-install does not clobber state written by claude
|
|
2544
|
+
// during the previous run.
|
|
2545
|
+
//
|
|
2546
|
+
// Called TWICE, and both calls are load-bearing. `deploy` runs inside
|
|
2547
|
+
// deployPayload, which is where an upgrade's already-existing account
|
|
2548
|
+
// directories are seeded; `post-account` runs after setupAccount(), which is
|
|
2549
|
+
// the only point a FRESH install has an account directory on disk at all.
|
|
2550
|
+
// Running deploy alone would leave every fresh install untrusted and hang its
|
|
2551
|
+
// first session. The transform is idempotent, so the second pass rewrites
|
|
2552
|
+
// nothing the first already did (`seeded=0` when there was nothing new).
|
|
2553
|
+
function primeClaudeState(phase) {
|
|
2554
|
+
const persistentDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
|
|
2555
|
+
const persistentClaudeDir = join(persistentDir, ".claude");
|
|
2556
|
+
mkdirSync(persistentClaudeDir, { recursive: true });
|
|
2557
|
+
const claudeStateFile = join(persistentClaudeDir, ".claude.json");
|
|
2558
|
+
const homeWorkspace = process.env.HOME ?? "/root";
|
|
2559
|
+
let claudeState = {};
|
|
2560
|
+
if (existsSync(claudeStateFile)) {
|
|
2561
|
+
try {
|
|
2562
|
+
claudeState = JSON.parse(readFileSync(claudeStateFile, "utf-8"));
|
|
2563
|
+
}
|
|
2564
|
+
catch {
|
|
2565
|
+
claudeState = {};
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
// An absent accounts directory is a first install, not an error; a listing
|
|
2569
|
+
// that throws is reported on the line rather than aborting the install.
|
|
2570
|
+
const accountsRoot = join(INSTALL_DIR, "data/accounts");
|
|
2571
|
+
let accountDirs = [];
|
|
2572
|
+
let seedError = "none";
|
|
2573
|
+
try {
|
|
2574
|
+
if (existsSync(accountsRoot)) {
|
|
2575
|
+
accountDirs = readdirSync(accountsRoot, { withFileTypes: true })
|
|
2576
|
+
.filter((e) => e.isDirectory())
|
|
2577
|
+
.map((e) => join(accountsRoot, e.name));
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
catch (err) {
|
|
2581
|
+
seedError = err instanceof Error ? err.message : String(err);
|
|
2582
|
+
}
|
|
2583
|
+
const seedResult = seedClaudeTrust({
|
|
2584
|
+
state: claudeState,
|
|
2585
|
+
homeWorkspace,
|
|
2586
|
+
accountsRoot,
|
|
2587
|
+
accountDirs,
|
|
2588
|
+
});
|
|
2589
|
+
writeFileSync(claudeStateFile, JSON.stringify(seedResult.state, null, 2) + "\n", { mode: 0o600 });
|
|
2590
|
+
console.log(formatTrustSeedLine(claudeStateFile, homeWorkspace, seedResult, seedError, phase));
|
|
2591
|
+
}
|
|
2491
2592
|
function deployPayload() {
|
|
2492
2593
|
log("8", TOTAL, `Deploying ${BRAND.productName}...`);
|
|
2493
2594
|
if (!existsSync(PAYLOAD_DIR)) {
|
|
@@ -2511,39 +2612,8 @@ function deployPayload() {
|
|
|
2511
2612
|
// `claude` SDK subprocess and admin server read from there via the
|
|
2512
2613
|
// Environment=CLAUDE_CONFIG_DIR= block in port-resolution.ts. Fresh
|
|
2513
2614
|
// installs prompt the operator to run `claude /login`.
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
// pre-populate the brand `.claude.json` so the manager's
|
|
2517
|
-
// PTY-spawned `claude --verbose --remote-control` boots straight into the
|
|
2518
|
-
// session URL without stopping at the first-run theme picker or the
|
|
2519
|
-
// workspace-trust dialog. Both prompts ate the manager's 30 s url-capture
|
|
2520
|
-
// window on 0.1.6 / 0.1.8 installs (Pi 192.168.88.16 verified). The two
|
|
2521
|
-
// markers required are:
|
|
2522
|
-
// - hasCompletedOnboarding: true (suppresses theme picker)
|
|
2523
|
-
// - projects[$HOME].hasTrustDialogAccepted: true
|
|
2524
|
-
// Existing fields (firstStartTime, oauthAccount, migrationVersion, …) are
|
|
2525
|
-
// preserved so a brand re-install does not clobber state written by claude
|
|
2526
|
-
// during the previous run.
|
|
2527
|
-
const claudeStateFile = join(persistentClaudeDir, ".claude.json");
|
|
2528
|
-
const homeWorkspace = process.env.HOME ?? "/root";
|
|
2529
|
-
let claudeState = {};
|
|
2530
|
-
if (existsSync(claudeStateFile)) {
|
|
2531
|
-
try {
|
|
2532
|
-
claudeState = JSON.parse(readFileSync(claudeStateFile, "utf-8"));
|
|
2533
|
-
}
|
|
2534
|
-
catch {
|
|
2535
|
-
claudeState = {};
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
claudeState.hasCompletedOnboarding = true;
|
|
2539
|
-
claudeState.theme = claudeState.theme ?? "auto";
|
|
2540
|
-
const projects = (claudeState.projects ?? {});
|
|
2541
|
-
const homeEntry = projects[homeWorkspace] ?? {};
|
|
2542
|
-
homeEntry.hasTrustDialogAccepted = true;
|
|
2543
|
-
projects[homeWorkspace] = homeEntry;
|
|
2544
|
-
claudeState.projects = projects;
|
|
2545
|
-
writeFileSync(claudeStateFile, JSON.stringify(claudeState, null, 2) + "\n", { mode: 0o600 });
|
|
2546
|
-
console.log(` [install] claude-state primed: ${claudeStateFile} hasCompletedOnboarding=true trustedWorkspace=${homeWorkspace}`);
|
|
2615
|
+
// primeClaudeState creates that directory, so there is no separate mkdir here.
|
|
2616
|
+
primeClaudeState("deploy");
|
|
2547
2617
|
// Brand isolation: installer does not read ~/.maxy/, ~/.cloudflared/, or
|
|
2548
2618
|
// ~/.cloudflare/ on non-default brands. These are peer-brand or
|
|
2549
2619
|
// shared-singleton paths.
|
|
@@ -5458,6 +5528,10 @@ try {
|
|
|
5458
5528
|
registerLocalAndExternalPlugins(); // install-time plugin registration
|
|
5459
5529
|
setupVncViewer();
|
|
5460
5530
|
setupAccount();
|
|
5531
|
+
// Task 2720 — the account directory only exists from here on a fresh
|
|
5532
|
+
// install, and provision-account-dir.sh has just made it a git repo root,
|
|
5533
|
+
// so this is the pass that trusts it. Idempotent on the upgrade path.
|
|
5534
|
+
primeClaudeState("post-account");
|
|
5461
5535
|
if (ENTITLEMENT_PAYLOAD) {
|
|
5462
5536
|
writeEntitlementFromFlag(ENTITLEMENT_PAYLOAD);
|
|
5463
5537
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Real Agent ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:72ceb87984dc863b94b0bb40ef8aead338b2d093e44caf456504d6d09ea0a2c6
|
|
5
5
|
brand: realagent-code
|
|
6
6
|
product-name: Real Agent
|
|
7
7
|
---
|
|
@@ -325,6 +325,8 @@ If you suspect background processes are piling up, run `grep '\[reaper\]' ~/.{br
|
|
|
325
325
|
|
|
326
326
|
Every install seeds `permissions.allow:[]` plus `defaultMode:"bypassPermissions"` into both the brand-scoped settings file (`~/.{brand}/.claude/settings.json`) and every account-scoped one (`<install>/data/accounts/<id>/.claude/settings.json`). `bypassPermissions` alone stops Claude Code from sending tool calls to its remote auto-classifier (it skips all permission checks), which would otherwise surface a permission prompt in the chat that an unattended session never answers. The `allow` array stays empty and **must never contain `"*"`**: Claude Code ≥ 2.1.167 rejects `"*"` as an allow token and renders a blocking Settings Warning that, under `--remote-control`, stops every `/rc-spawn` from binding. What each subagent is allowed to use is still controlled by the `tools:` line in its agent file, not by a top-level allowlist. To verify after an install: `cat ~/.{brand}/.claude/settings.json | jq '.permissions'` (expect `allow: []`). To repair an install seeded before this change: `bash <install>/platform/scripts/backfill-bypass-permissions.sh ~/.{brand}`.
|
|
327
327
|
|
|
328
|
+
**Workspace trust.** Separate from permissions, and its own way to stop a spawn dead. Claude Code refuses to start in a directory it does not trust, and it scopes that trust to the **git repository root** of the working directory rather than to any trusted ancestor. Every account directory is its own repository (`provision-account-dir.sh` runs `git init` there so Claude Code finds the account's own `.claude/` hook set), so trusting the home directory does not reach them. Every install therefore seeds `projects[<dir>].hasTrustDialogAccepted: true` into `~/.{brand}/.claude/.claude.json` for the home directory **and for every directory under `<install>/data/accounts`**, preserving all other fields the CLI wrote. Without it the PTY-spawned session halts on "Quick safety check: Is this a project you created or one you trust?" and never binds, which surfaces as a session that starts and answers nothing, with no error in the interface. The seeding runs twice per install and both passes matter: the `deploy` pass trusts the accounts an upgrade already has, and the `post-account` pass, which runs straight after account setup, trusts the account a brand-new install has only just created. The install line is `[install] op=claude-trust-seed phase=<deploy|post-account> file=… accounts=<N> seeded=<n> alreadyTrusted=<n> stale=<n> error=<none|msg>`; `seeded + alreadyTrusted` equals `accounts`, and a re-run reads `seeded=0`. Because an account added *after* an install has an untrusted repository root that no event announces, the session manager reconciles the two every hour and logs `[claude-trust-census] accounts=<N> trusted=<n> untrusted=<n> untrustedDirs=<csv|none> homeTrusted=<bool> onboarded=<bool>` — a non-zero `untrusted` names the directories whose next session will hang. To verify after an install: `node -e 'const j=require(process.env.HOME+"/.{brand}/.claude/.claude.json");const p=j.projects||{};for(const k of Object.keys(p))if(k.includes("/data/accounts/"))console.log(k,p[k].hasTrustDialogAccepted)'` (expect `true` for every account directory that exists).
|
|
329
|
+
|
|
328
330
|
**`autoMode` trust block.** The same brand-scoped writer ([`permissions-seed.ts`](../../../packages/create-maxy-code/src/permissions-seed.ts), `AUTO_MODE_BLOCK`) also seeds the harness `autoMode` block. `bypassPermissions` skips the auto-mode classifier entirely, so the block is **belt-and-suspenders**: it only matters when a session is toggled into `auto` mode, where the classifier otherwise soft-denies the doctrine-sanctioned Cloudflare scoped-token mint ("permission/credential escalation") because the install's own Cloudflare infra is not in the classifier's default trusted environment. The block keeps `"$defaults"` first in `environment` and `allow` (so the built-in Data-Exfiltration / force-push / prod-deploy guards stay intact) and adds prose declaring `api.cloudflare.com` the install's own provider endpoint plus an allow for minting/persisting narrow per-scope tokens. It is seeded **only** into the brand/user settings (`~/.{brand}/.claude/settings.json`) — the classifier never reads `autoMode` from the shared project (account-scoped) settings, so `setup-account.sh` does not carry it. An auto-mode agent cannot seed this itself (the default `hard_deny` "Auto-Mode Bypass" blocks any agent editing `.claude/settings*.json`); installer seed is the only path. To verify after an install: `CLAUDE_CONFIG_DIR=~/.{brand}/.claude claude auto-mode config | jq '.environment, .allow'` (expect the Cloudflare entries, `$defaults` expanded) and `jq '.autoMode' ~/.{brand}/.claude/settings.json`.
|
|
329
331
|
|
|
330
332
|
---
|
|
@@ -4095,6 +4097,19 @@ which fails `prebuild` if any emoji codepoint appears in a `.ts`/`.tsx` file
|
|
|
4095
4097
|
under `app/` (tests excluded). Inline an icon at the surrounding text size and
|
|
4096
4098
|
colour (`size={14}` matches body text; Lucide inherits `currentColor`).
|
|
4097
4099
|
|
|
4100
|
+
**A long message shows its first 8 lines.** On `/chat`, the WhatsApp and
|
|
4101
|
+
Telegram readers and the public visitor chat, a message body taller than 8 lines
|
|
4102
|
+
is cut at 8 and carries a "Show more…" control that reveals the rest; "Show
|
|
4103
|
+
less" puts it back. The control appears only when there is genuinely more to
|
|
4104
|
+
see, so a short message looks exactly as it did before. Expanding one message
|
|
4105
|
+
expands only that message, and scrolling back through older messages does not
|
|
4106
|
+
carry an expansion onto a different one. Two things are never cut: an error the
|
|
4107
|
+
agent reports, and the map on a shared location. The reader's search also opens
|
|
4108
|
+
any message whose match would otherwise sit in the hidden part, so a search
|
|
4109
|
+
never finds something it then hides. See
|
|
4110
|
+
[`.docs/message-body-clamp.md`](../../../.docs/message-body-clamp.md) for the
|
|
4111
|
+
render sites and the census that watches it.
|
|
4112
|
+
|
|
4098
4113
|
## Admin Hono routes
|
|
4099
4114
|
|
|
4100
4115
|
Every admin sub-app is mounted by
|
|
@@ -9,6 +9,15 @@ Invoked by the admin agent directly.
|
|
|
9
9
|
|
|
10
10
|
This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
|
|
11
11
|
|
|
12
|
+
## 2026-08-14 (0.1.607)
|
|
13
|
+
|
|
14
|
+
- Upgrades no longer fail on a Raspberry Pi with a 16K page size. The system's own Chromium launcher adds a setting that current Chromium has removed, so the browser exited on startup and the install stopped when it could not reach it. The installer now runs Chromium directly, skipping that launcher.
|
|
15
|
+
|
|
16
|
+
## 2026-08-14 (0.1.606)
|
|
17
|
+
|
|
18
|
+
- Conversations start after an install again. The installer marked only your home folder as trusted, and the assistant now asks about the folder each account actually runs from, so a session would stop at a trust prompt nobody could answer.
|
|
19
|
+
- A long chat message is capped at eight lines with a control to show the rest, instead of filling the pane.
|
|
20
|
+
|
|
12
21
|
## 2026-08-14 (0.1.605)
|
|
13
22
|
|
|
14
23
|
- Five more routines now check whether there is anything to do before waking the assistant. Mail triage, appointment reminders, the operational and finance reconciles, and the two health reports all previously ran on the clock and did the deciding after starting up.
|
|
@@ -37,6 +37,19 @@ which fails `prebuild` if any emoji codepoint appears in a `.ts`/`.tsx` file
|
|
|
37
37
|
under `app/` (tests excluded). Inline an icon at the surrounding text size and
|
|
38
38
|
colour (`size={14}` matches body text; Lucide inherits `currentColor`).
|
|
39
39
|
|
|
40
|
+
**A long message shows its first 8 lines.** On `/chat`, the WhatsApp and
|
|
41
|
+
Telegram readers and the public visitor chat, a message body taller than 8 lines
|
|
42
|
+
is cut at 8 and carries a "Show more…" control that reveals the rest; "Show
|
|
43
|
+
less" puts it back. The control appears only when there is genuinely more to
|
|
44
|
+
see, so a short message looks exactly as it did before. Expanding one message
|
|
45
|
+
expands only that message, and scrolling back through older messages does not
|
|
46
|
+
carry an expansion onto a different one. Two things are never cut: an error the
|
|
47
|
+
agent reports, and the map on a shared location. The reader's search also opens
|
|
48
|
+
any message whose match would otherwise sit in the hidden part, so a search
|
|
49
|
+
never finds something it then hides. See
|
|
50
|
+
[`.docs/message-body-clamp.md`](../../../.docs/message-body-clamp.md) for the
|
|
51
|
+
render sites and the census that watches it.
|
|
52
|
+
|
|
40
53
|
## Admin Hono routes
|
|
41
54
|
|
|
42
55
|
Every admin sub-app is mounted by
|
|
@@ -198,4 +198,6 @@ If you suspect background processes are piling up, run `grep '\[reaper\]' ~/.{br
|
|
|
198
198
|
|
|
199
199
|
Every install seeds `permissions.allow:[]` plus `defaultMode:"bypassPermissions"` into both the brand-scoped settings file (`~/.{brand}/.claude/settings.json`) and every account-scoped one (`<install>/data/accounts/<id>/.claude/settings.json`). `bypassPermissions` alone stops Claude Code from sending tool calls to its remote auto-classifier (it skips all permission checks), which would otherwise surface a permission prompt in the chat that an unattended session never answers. The `allow` array stays empty and **must never contain `"*"`**: Claude Code ≥ 2.1.167 rejects `"*"` as an allow token and renders a blocking Settings Warning that, under `--remote-control`, stops every `/rc-spawn` from binding. What each subagent is allowed to use is still controlled by the `tools:` line in its agent file, not by a top-level allowlist. To verify after an install: `cat ~/.{brand}/.claude/settings.json | jq '.permissions'` (expect `allow: []`). To repair an install seeded before this change: `bash <install>/platform/scripts/backfill-bypass-permissions.sh ~/.{brand}`.
|
|
200
200
|
|
|
201
|
+
**Workspace trust.** Separate from permissions, and its own way to stop a spawn dead. Claude Code refuses to start in a directory it does not trust, and it scopes that trust to the **git repository root** of the working directory rather than to any trusted ancestor. Every account directory is its own repository (`provision-account-dir.sh` runs `git init` there so Claude Code finds the account's own `.claude/` hook set), so trusting the home directory does not reach them. Every install therefore seeds `projects[<dir>].hasTrustDialogAccepted: true` into `~/.{brand}/.claude/.claude.json` for the home directory **and for every directory under `<install>/data/accounts`**, preserving all other fields the CLI wrote. Without it the PTY-spawned session halts on "Quick safety check: Is this a project you created or one you trust?" and never binds, which surfaces as a session that starts and answers nothing, with no error in the interface. The seeding runs twice per install and both passes matter: the `deploy` pass trusts the accounts an upgrade already has, and the `post-account` pass, which runs straight after account setup, trusts the account a brand-new install has only just created. The install line is `[install] op=claude-trust-seed phase=<deploy|post-account> file=… accounts=<N> seeded=<n> alreadyTrusted=<n> stale=<n> error=<none|msg>`; `seeded + alreadyTrusted` equals `accounts`, and a re-run reads `seeded=0`. Because an account added *after* an install has an untrusted repository root that no event announces, the session manager reconciles the two every hour and logs `[claude-trust-census] accounts=<N> trusted=<n> untrusted=<n> untrustedDirs=<csv|none> homeTrusted=<bool> onboarded=<bool>` — a non-zero `untrusted` names the directories whose next session will hang. To verify after an install: `node -e 'const j=require(process.env.HOME+"/.{brand}/.claude/.claude.json");const p=j.projects||{};for(const k of Object.keys(p))if(k.includes("/data/accounts/"))console.log(k,p[k].hasTrustDialogAccepted)'` (expect `true` for every account directory that exists).
|
|
202
|
+
|
|
201
203
|
**`autoMode` trust block.** The same brand-scoped writer ([`permissions-seed.ts`](../../../packages/create-maxy-code/src/permissions-seed.ts), `AUTO_MODE_BLOCK`) also seeds the harness `autoMode` block. `bypassPermissions` skips the auto-mode classifier entirely, so the block is **belt-and-suspenders**: it only matters when a session is toggled into `auto` mode, where the classifier otherwise soft-denies the doctrine-sanctioned Cloudflare scoped-token mint ("permission/credential escalation") because the install's own Cloudflare infra is not in the classifier's default trusted environment. The block keeps `"$defaults"` first in `environment` and `allow` (so the built-in Data-Exfiltration / force-push / prod-deploy guards stay intact) and adds prose declaring `api.cloudflare.com` the install's own provider endpoint plus an allow for minting/persisting narrow per-scope tokens. It is seeded **only** into the brand/user settings (`~/.{brand}/.claude/settings.json`) — the classifier never reads `autoMode` from the shared project (account-scoped) settings, so `setup-account.sh` does not carry it. An auto-mode agent cannot seed this itself (the default `hard_deny` "Auto-Mode Bypass" blocks any agent editing `.claude/settings*.json`); installer seed is the only path. To verify after an install: `CLAUDE_CONFIG_DIR=~/.{brand}/.claude claude auto-mode config | jq '.environment, .allow'` (expect the Cloudflare entries, `$defaults` expanded) and `jq '.autoMode' ~/.{brand}/.claude/settings.json`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type Logger = (line: string) => void;
|
|
2
|
+
export interface TrustFacts {
|
|
3
|
+
accounts: number;
|
|
4
|
+
trusted: number;
|
|
5
|
+
untrusted: number;
|
|
6
|
+
untrustedDirs: string[];
|
|
7
|
+
homeTrusted: boolean;
|
|
8
|
+
onboarded: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Pure. An account directory with no entry counts as untrusted, not absent. */
|
|
11
|
+
export declare function collectTrustFacts(input: {
|
|
12
|
+
state: Record<string, unknown>;
|
|
13
|
+
homeWorkspace: string;
|
|
14
|
+
accountDirs: string[];
|
|
15
|
+
}): TrustFacts;
|
|
16
|
+
/** Pure. */
|
|
17
|
+
export declare function formatTrustCensus(f: TrustFacts): string;
|
|
18
|
+
export interface TrustCensusDeps {
|
|
19
|
+
claudeJsonPath: string;
|
|
20
|
+
accountsDir: string;
|
|
21
|
+
homeWorkspace: string;
|
|
22
|
+
logger: Logger;
|
|
23
|
+
intervalMs: number;
|
|
24
|
+
/** Injected in tests so the collector runs without a filesystem. */
|
|
25
|
+
readState?: () => Record<string, unknown>;
|
|
26
|
+
listAccountDirs?: () => string[];
|
|
27
|
+
}
|
|
28
|
+
export interface TrustCensus {
|
|
29
|
+
tickOnce(): void;
|
|
30
|
+
start(): void;
|
|
31
|
+
stop(): void;
|
|
32
|
+
}
|
|
33
|
+
export declare function createClaudeTrustCensus(deps: TrustCensusDeps): TrustCensus;
|
|
34
|
+
//# sourceMappingURL=claude-trust-census.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-trust-census.d.ts","sourceRoot":"","sources":["../src/claude-trust-census.ts"],"names":[],"mappings":"AAuBA,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;AAE3C,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB,GAAG,UAAU,CAWb;AAED,YAAY;AACZ,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAMvD;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACzC,eAAe,CAAC,EAAE,MAAM,MAAM,EAAE,CAAA;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,IAAI,IAAI,CAAA;IAChB,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CA6C1E"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Task 2720 — standing reconciliation of account directories against the
|
|
2
|
+
// workspace-trust entries in the brand's `.claude.json`.
|
|
3
|
+
//
|
|
4
|
+
// Claude Code scopes folder trust to the git repository root, and
|
|
5
|
+
// provision-account-dir.sh gives every account directory its own repo, so a
|
|
6
|
+
// directory with no `hasTrustDialogAccepted: true` entry halts the spawned
|
|
7
|
+
// child on the folder-trust dialog before it binds.
|
|
8
|
+
//
|
|
9
|
+
// Two of that defect's failure modes emit no event and do not reproduce on
|
|
10
|
+
// demand: an installer enumeration that skipped a directory, and an account
|
|
11
|
+
// created after the install (a fresh repo root with no entry). Neither
|
|
12
|
+
// produces a log line until somebody tries to spawn a session there, and by
|
|
13
|
+
// then the symptom is a 60 s bind timeout that `/rc-spawn`'s 200 turns into
|
|
14
|
+
// silence. Only a periodic reconcile of expected against actual surfaces them.
|
|
15
|
+
//
|
|
16
|
+
// Shape mirrors the other standing censuses in this service (a pure collector,
|
|
17
|
+
// a pure formatter, and a start/stop factory whose setInterval is unref'd):
|
|
18
|
+
// see `role-census.ts`.
|
|
19
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
const TAG = '[claude-trust-census]';
|
|
22
|
+
/** Pure. An account directory with no entry counts as untrusted, not absent. */
|
|
23
|
+
export function collectTrustFacts(input) {
|
|
24
|
+
const projects = (input.state.projects ?? {});
|
|
25
|
+
const untrustedDirs = input.accountDirs.filter((d) => projects[d]?.hasTrustDialogAccepted !== true);
|
|
26
|
+
return {
|
|
27
|
+
accounts: input.accountDirs.length,
|
|
28
|
+
trusted: input.accountDirs.length - untrustedDirs.length,
|
|
29
|
+
untrusted: untrustedDirs.length,
|
|
30
|
+
untrustedDirs,
|
|
31
|
+
homeTrusted: projects[input.homeWorkspace]?.hasTrustDialogAccepted === true,
|
|
32
|
+
onboarded: input.state.hasCompletedOnboarding === true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Pure. */
|
|
36
|
+
export function formatTrustCensus(f) {
|
|
37
|
+
const dirs = f.untrustedDirs.length === 0 ? 'none' : f.untrustedDirs.join(',');
|
|
38
|
+
return (`${TAG} accounts=${f.accounts} trusted=${f.trusted} untrusted=${f.untrusted}` +
|
|
39
|
+
` untrustedDirs=${dirs} homeTrusted=${f.homeTrusted} onboarded=${f.onboarded}`);
|
|
40
|
+
}
|
|
41
|
+
export function createClaudeTrustCensus(deps) {
|
|
42
|
+
let timer = null;
|
|
43
|
+
const readState = deps.readState ??
|
|
44
|
+
(() => JSON.parse(readFileSync(deps.claudeJsonPath, 'utf-8')));
|
|
45
|
+
const listAccountDirs = deps.listAccountDirs ??
|
|
46
|
+
(() => readdirSync(deps.accountsDir, { withFileTypes: true })
|
|
47
|
+
.filter((e) => e.isDirectory())
|
|
48
|
+
.map((e) => join(deps.accountsDir, e.name)));
|
|
49
|
+
function tickOnce() {
|
|
50
|
+
try {
|
|
51
|
+
const facts = collectTrustFacts({
|
|
52
|
+
state: readState(),
|
|
53
|
+
homeWorkspace: deps.homeWorkspace,
|
|
54
|
+
accountDirs: listAccountDirs(),
|
|
55
|
+
});
|
|
56
|
+
// Emitted on a clean result too. A quiet interval is itself the signal
|
|
57
|
+
// that the census is alive, so a missing line means the audit stopped
|
|
58
|
+
// rather than that every directory is trusted.
|
|
59
|
+
deps.logger(formatTrustCensus(facts));
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
deps.logger(`${TAG} op=census-failed message=${err instanceof Error ? err.message : String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
tickOnce,
|
|
67
|
+
start() {
|
|
68
|
+
if (timer)
|
|
69
|
+
return;
|
|
70
|
+
timer = setInterval(tickOnce, deps.intervalMs);
|
|
71
|
+
if (typeof timer.unref === 'function')
|
|
72
|
+
timer.unref();
|
|
73
|
+
deps.logger(`${TAG} op=census-started intervalMs=${deps.intervalMs}`);
|
|
74
|
+
},
|
|
75
|
+
stop() {
|
|
76
|
+
if (timer) {
|
|
77
|
+
clearInterval(timer);
|
|
78
|
+
timer = null;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=claude-trust-census.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-trust-census.js","sourceRoot":"","sources":["../src/claude-trust-census.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,6 +36,7 @@ import { createPermissionModeTail } from './permission-mode-tail.js';
|
|
|
36
36
|
import { createReaper } from './reaper.js';
|
|
37
37
|
import { createReachabilityAudit } from './public-agent-reachability.js';
|
|
38
38
|
import { createRoleCensus } from './role-census.js';
|
|
39
|
+
import { createClaudeTrustCensus } from './claude-trust-census.js';
|
|
39
40
|
import { createPublicPromptSurfaceCensus } from './public-prompt-surface.js';
|
|
40
41
|
import { createPublicKnowledgeAudit, readPublicAgentKnowledge } from './public-knowledge-audit.js';
|
|
41
42
|
import { createPtyCensus } from './pty-census.js';
|
|
@@ -1264,6 +1265,18 @@ async function main() {
|
|
|
1264
1265
|
intervalMs: 900_000,
|
|
1265
1266
|
});
|
|
1266
1267
|
quoteReconcile.start();
|
|
1268
|
+
// Task 2720 — hourly reconcile of account directories against their
|
|
1269
|
+
// workspace-trust entries in the brand's `.claude.json`. An account created
|
|
1270
|
+
// after the install is a fresh git repo root with no trust entry, and
|
|
1271
|
+
// nothing announces it: the first spawn there halts on the folder-trust
|
|
1272
|
+
// dialog and /rc-spawn's 200 hides the failure. Purely filesystem.
|
|
1273
|
+
createClaudeTrustCensus({
|
|
1274
|
+
claudeJsonPath: join(claudeConfigDirResolved, '.claude.json'),
|
|
1275
|
+
accountsDir: dirname(config.accountDir),
|
|
1276
|
+
homeWorkspace: process.env.HOME ?? '/root',
|
|
1277
|
+
logger: log,
|
|
1278
|
+
intervalMs: 3_600_000,
|
|
1279
|
+
}).start();
|
|
1267
1280
|
// Task 183 — recorder JSONL tail. Posts [recorder-session-init] and
|
|
1268
1281
|
// [recorder-turn] lines for every database-operator specialist spawn,
|
|
1269
1282
|
// routed through the platform UI's /api/admin/log-ingest loopback
|