@wipcomputer/wip-ldm-os 0.4.85-alpha.30 → 0.4.85-alpha.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/bin/ldm.js +187 -1
  2. package/lib/deploy.mjs +36 -1
  3. package/package.json +10 -2
  4. package/scripts/test-boot-dir-truth.mjs +158 -0
  5. package/scripts/test-boot-hook-registration.mjs +136 -0
  6. package/scripts/test-boot-payload-trim.mjs +200 -0
  7. package/scripts/test-crc-pair-login-flow.mjs +76 -1
  8. package/scripts/test-deploy-hook-ownership.mjs +160 -0
  9. package/scripts/test-doctor-hook-dedupe.mjs +188 -0
  10. package/scripts/test-kaleidoscope-onboarding-copy.mjs +170 -0
  11. package/scripts/test-kaleidoscope-public-stats-baseline.mjs +129 -0
  12. package/scripts/test-kaleidoscope-qr-authenticator-confirmation.mjs +89 -0
  13. package/shared/boot/boot-config.json +18 -8
  14. package/shared/docs/dev-guide-wipcomputerinc.md.tmpl +5 -4
  15. package/shared/rules/security.md +4 -0
  16. package/src/boot/README.md +24 -1
  17. package/src/boot/boot-config.json +18 -8
  18. package/src/boot/boot-hook.mjs +118 -28
  19. package/src/boot/installer.mjs +33 -10
  20. package/src/hosted-mcp/.env.example +4 -0
  21. package/src/hosted-mcp/app/footer.js +2 -2
  22. package/src/hosted-mcp/app/kaleidoscope-login.html +486 -42
  23. package/src/hosted-mcp/app/wip-logo.png +0 -0
  24. package/src/hosted-mcp/demo/footer.js +2 -2
  25. package/src/hosted-mcp/demo/index.html +166 -44
  26. package/src/hosted-mcp/demo/login.html +87 -23
  27. package/src/hosted-mcp/demo/privacy.html +4 -214
  28. package/src/hosted-mcp/demo/tos.html +4 -189
  29. package/src/hosted-mcp/legal/internet-services/kaleidoscope/index.html +257 -0
  30. package/src/hosted-mcp/legal/internet-services/terms/site.html +224 -168
  31. package/src/hosted-mcp/legal/legal-footer.js +75 -0
  32. package/src/hosted-mcp/legal/legal.css +166 -0
  33. package/src/hosted-mcp/legal/privacy/en-ww/index.html +4 -221
  34. package/src/hosted-mcp/legal/privacy/index.html +253 -0
  35. package/src/hosted-mcp/server.mjs +662 -35
@@ -0,0 +1,170 @@
1
+ import { readFileSync } from "node:fs";
2
+ import assert from "node:assert/strict";
3
+
4
+ const demo = readFileSync("src/hosted-mcp/demo/index.html", "utf8");
5
+ const server = readFileSync("src/hosted-mcp/server.mjs", "utf8");
6
+
7
+ function functionBody(source, name) {
8
+ const start = source.indexOf("async function " + name + "(");
9
+ assert.notEqual(start, -1, "missing function " + name);
10
+ const open = source.indexOf("{", start);
11
+ let depth = 0;
12
+ for (let i = open; i < source.length; i++) {
13
+ if (source[i] === "{") depth += 1;
14
+ if (source[i] === "}") depth -= 1;
15
+ if (depth === 0) return source.slice(open + 1, i);
16
+ }
17
+ throw new Error("unterminated function " + name);
18
+ }
19
+
20
+ function functionSource(source, name) {
21
+ const start = source.indexOf("function " + name + "(");
22
+ assert.notEqual(start, -1, "missing function " + name);
23
+ const open = source.indexOf("{", start);
24
+ let depth = 0;
25
+ for (let i = open; i < source.length; i++) {
26
+ if (source[i] === "{") depth += 1;
27
+ if (source[i] === "}") depth -= 1;
28
+ if (depth === 0) return source.slice(start, i + 1);
29
+ }
30
+ throw new Error("unterminated function " + name);
31
+ }
32
+
33
+ const noThanks = functionBody(demo, "showNoThanksFlow");
34
+ const returningNoThanks = functionBody(demo, "showReturningUserNoThanksFlow");
35
+ const startDemo = functionBody(demo, "startDemo");
36
+ const authFlow = functionBody(demo, "doFaceIDAuth");
37
+ const imageFlow = functionBody(demo, "generateKaleidoscope");
38
+ const oldOutro = functionBody(demo, "showOutro");
39
+ const approvedOutro = functionBody(demo, "showApprovedOutro");
40
+ const returningOutro = functionBody(demo, "showReturningUserOutro");
41
+
42
+ assert.match(startDemo, /Hi, I'm L\\u0113sa\. Welcome to Kaleidoscope\./);
43
+ assert.match(startDemo, /You just created an account with a passkey\. The passkey lives on your phone\./);
44
+ assert.match(startDemo, /Going forward, you can use your phone to log into any Work in Progress Computer service\./);
45
+ assert.match(startDemo, /Anytime I need your permission to do something, I'll ask, and you authorize with your fingerprint or face\. No passwords\. Ever\./);
46
+ assert.match(startDemo, /Hi, " \+ currentAccountLabel\(\) \+ "\. Welcome back to Kaleidoscope\./);
47
+ assert.match(startDemo, /Do you want to try giving me permission with your device to create something that costs money\?/);
48
+ assert.doesNotMatch(startDemo, /getWalletSummary\(\)/);
49
+ assert.doesNotMatch(startDemo, /Your balance is " \+ wallet\.balance/);
50
+ assert.doesNotMatch(startDemo, /Your account is a key on your device, not an email\./);
51
+ assert.doesNotMatch(startDemo, /You just logged in with your passkey/);
52
+ assert.doesNotMatch(startDemo, /And I can use it too/);
53
+
54
+ assert.match(demo, /I have a wallet with " \+ wallet\.balance \+ "\. Do I have your permission to spend " \+ wallet\.cost/);
55
+ assert.match(demo, /addMessage\('Authorizing', 'user'\)/);
56
+ assert.doesNotMatch(demo, /addMessage\('Authorized', 'user'\)/);
57
+ assert.doesNotMatch(demo, /This is the end of the demo\. Tap the icon to start over\./);
58
+ assert.doesNotMatch(demo, /open wip\.computer\/demo/);
59
+
60
+ assert.match(noThanks, /No problem\. Let me show you something beautiful\./);
61
+ assert.match(noThanks, /IS_RETURNING_USER_FLOW/);
62
+ assert.match(noThanks, /showReturningUserNoThanksFlow\(\)/);
63
+ assert.match(noThanks, /generateKaleidoscope\(false, \{ showWalletReceipt: false, showOutro: false \}\)/);
64
+ assert.match(noThanks, /showApprovedOutro\(\)/);
65
+ assert.doesNotMatch(noThanks, /\/demo\/api\/imagine/);
66
+ assert.doesNotMatch(noThanks, /fallback\.jpg/);
67
+ assert.doesNotMatch(noThanks, /Cost:/);
68
+ assert.doesNotMatch(noThanks, /Balance:/);
69
+
70
+ assert.match(returningNoThanks, /No problem\. You can come back anytime\./);
71
+ assert.match(returningNoThanks, /At Work in Progress Computer, we are building the future of AI and human interaction\./);
72
+ assert.match(returningNoThanks, /https:\/\/x\.com\/wipcomputer/);
73
+ assert.match(returningNoThanks, /Made in California by WIP Computer, Inc\. Learning Dreaming Machines\./);
74
+ assert.doesNotMatch(returningNoThanks, /generateKaleidoscope/);
75
+ assert.doesNotMatch(returningNoThanks, /\/demo\/api\/imagine/);
76
+ assert.doesNotMatch(returningNoThanks, /Cost:/);
77
+ assert.doesNotMatch(returningNoThanks, /Balance:/);
78
+ assert.doesNotMatch(returningNoThanks, /passkey/i);
79
+
80
+ assert.match(imageFlow, /var showWalletReceipt = options\.showWalletReceipt !== false;/);
81
+ assert.match(imageFlow, /if \(showWalletReceipt\) \{/);
82
+ assert.match(imageFlow, /body: JSON\.stringify\(\{ prompt: prompt \}\)/);
83
+ assert.doesNotMatch(imageFlow, /mode:/);
84
+ assert.doesNotMatch(server, /no_thanks/i);
85
+ assert.doesNotMatch(server, /Free onboarding generation/);
86
+
87
+ assert.match(oldOutro, /IS_RETURNING_USER_FLOW/);
88
+ assert.match(oldOutro, /showReturningUserOutro\(\)/);
89
+ assert.match(oldOutro, /showApprovedOutro\(\)/);
90
+ assert.match(approvedOutro, /At Work in Progress Computer, we are building the future of AI and human interaction\./);
91
+ assert.match(approvedOutro, /https:\/\/wip\.computer\/demo/);
92
+ assert.match(approvedOutro, /https:\/\/x\.com\/wipcomputer/);
93
+ assert.match(approvedOutro, /Your passkey will keep working after you leave\./);
94
+ assert.doesNotMatch(approvedOutro, /Your account stays active/);
95
+ assert.doesNotMatch(approvedOutro, /When you come back/);
96
+
97
+ assert.match(returningOutro, /At Work in Progress Computer, we are building the future of AI and human interaction\./);
98
+ assert.match(returningOutro, /We believe permission is a conversation\. Your AI asks\. You decide\. One glance, one tap\./);
99
+ assert.match(returningOutro, /https:\/\/x\.com\/wipcomputer/);
100
+ assert.match(returningOutro, /Made in California by WIP Computer, Inc\. Learning Dreaming Machines\./);
101
+ assert.doesNotMatch(returningOutro, /https:\/\/wip\.computer\/demo/);
102
+ assert.doesNotMatch(returningOutro, /log out/);
103
+ assert.doesNotMatch(returningOutro, /Your passkey will keep working after you leave\./);
104
+ assert.doesNotMatch(returningOutro, /Your account stays active/);
105
+ assert.doesNotMatch(returningOutro, /When you come back/);
106
+
107
+ assert.match(authFlow, /That passkey belongs to a different account/);
108
+ assert.match(authFlow, /isSameAccountAuthorization\(result, activeToken, activeTenant\)/);
109
+ assert.doesNotMatch(authFlow, /result\.tenantId !== activeTenant/);
110
+
111
+ const { canonicalAccountId, isSameAccountAuthorization } = Function(
112
+ functionSource(demo, "canonicalAccountId") + "\n"
113
+ + functionSource(demo, "isSameAccountAuthorization") + "\n"
114
+ + "return { canonicalAccountId, isSameAccountAuthorization };"
115
+ )();
116
+
117
+ assert.equal(canonicalAccountId("acct:parker-smoke-test"), "parker-smoke-test");
118
+ assert.equal(canonicalAccountId("parker-smoke-test"), "parker-smoke-test");
119
+ assert.equal(canonicalAccountId(" key:abc "), "key:abc");
120
+ assert.equal(canonicalAccountId(""), null);
121
+ assert.equal(canonicalAccountId(null), null);
122
+
123
+ assert.equal(
124
+ isSameAccountAuthorization({ tenantId: "parker-smoke-test", apiKey: "different-token" }, "active-token", "acct:parker-smoke-test"),
125
+ true,
126
+ );
127
+ assert.equal(
128
+ isSameAccountAuthorization({ tenantId: "acct:parker-smoke-test", apiKey: "different-token" }, "active-token", "parker-smoke-test"),
129
+ true,
130
+ );
131
+ assert.equal(
132
+ isSameAccountAuthorization({ tenantId: "acct:other-account", apiKey: "active-token" }, "active-token", "acct:parker-smoke-test"),
133
+ false,
134
+ );
135
+ assert.equal(
136
+ isSameAccountAuthorization({ apiKey: "active-token" }, "active-token", null),
137
+ true,
138
+ );
139
+ assert.equal(
140
+ isSameAccountAuthorization({ apiKey: "other-token" }, "active-token", null),
141
+ false,
142
+ );
143
+
144
+ assert.match(demo, /id="kscopeIcon" type="button" onclick="logoutAndSignBackIn\(\)"/);
145
+ assert.match(demo, /sessionStorage\.removeItem\('lesa-token'\)/);
146
+ assert.match(demo, /window\.location\.href = '\/login\?next=\/demo'/);
147
+
148
+ assert.match(server, /const IMAGE_COST_CENTS = 1;/);
149
+ assert.match(server, /const INITIAL_BALANCE_CENTS = 1000;/);
150
+ assert.match(server, /const DEMO_WALLET_RESET_MARKER_FILE = join/);
151
+ assert.match(server, /\.demo-wallet-reset-v0-4-87\.json/);
152
+ assert.match(server, /function walletUserIdForAgent\(agentId\)/);
153
+ assert.match(server, /agentId\.startsWith\("acct:"\) \? agentId\.slice\("acct:"\.length\) : agentId/);
154
+ assert.match(server, /function resetAndNormalizeWalletFileEntries\(wallets\)/);
155
+ assert.match(server, /normalizedWallets\[walletUserIdForAgent\(agentId\)\] = INITIAL_BALANCE_CENTS/);
156
+ assert.match(server, /async function resetExistingDemoWalletsToStarterBalanceOnce\(\)/);
157
+ assert.match(server, /await prisma\.wallet\.updateMany\(\{ data: \{ balance: INITIAL_BALANCE_CENTS \} \}\)/);
158
+ assert.match(server, /saveWalletsToFile\(normalizedWalletFile\.wallets\)/);
159
+ assert.match(server, /prismaCount: prismaResetCount/);
160
+ assert.match(server, /jsonCount: jsonResetCount/);
161
+ assert.match(server, /const walletUserId = walletUserIdForAgent\(agentId\);/);
162
+ assert.match(server, /where: \{ userId: walletUserId \}/);
163
+ assert.match(server, /data: \{ userId: walletUserId, balance: INITIAL_BALANCE_CENTS \}/);
164
+ assert.match(server, /return w\[walletUserId\] !== undefined \? w\[walletUserId\] : INITIAL_BALANCE_CENTS/);
165
+ assert.match(server, /if \(w\[walletUserId\] === undefined\) w\[walletUserId\] = INITIAL_BALANCE_CENTS/);
166
+ assert.match(server, /w\[walletUserId\] = Math\.max\(0, w\[walletUserId\] - cents\)/);
167
+ assert.match(server, /return w\[walletUserId\]/);
168
+ assert.match(server, /await resetExistingDemoWalletsToStarterBalanceOnce\(\);/);
169
+
170
+ console.log("Kaleidoscope onboarding copy checks passed");
@@ -0,0 +1,129 @@
1
+ import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import vm from "node:vm";
4
+
5
+ const serverSource = readFileSync("src/hosted-mcp/server.mjs", "utf8");
6
+ assert.match(serverSource, /startsAt: "2026-05-21T07:00:00\.000Z"/);
7
+ assert.match(serverSource, /keysCreated: 3/);
8
+ assert.match(serverSource, /genericKaleidoscopes: 11/);
9
+ assert.match(serverSource, /imageKaleidoscopes: 3/);
10
+
11
+ const start = serverSource.indexOf("function parseTimestampMs");
12
+ const end = serverSource.indexOf("function loadLiveWallState()");
13
+ assert.notEqual(start, -1, "baseline helper block start not found");
14
+ assert.notEqual(end, -1, "baseline helper block end not found");
15
+ assert.ok(end > start, "baseline helper block is malformed");
16
+
17
+ const helperSource = `
18
+ const KALEIDOSCOPE_PUBLIC_STATS_BASELINE = Object.freeze({
19
+ timezone: "America/Los_Angeles",
20
+ date: "2026-05-21",
21
+ startsAt: "2026-05-21T07:00:00.000Z",
22
+ counts: Object.freeze({
23
+ keysCreated: 3,
24
+ genericKaleidoscopes: 11,
25
+ imageKaleidoscopes: 3,
26
+ }),
27
+ });
28
+ ${serverSource.slice(start, end)}
29
+ globalThis.__statsHelpers = {
30
+ KALEIDOSCOPE_PUBLIC_STATS_BASELINE,
31
+ deriveKaleidoscopePublicStats,
32
+ isWipTestHandle,
33
+ createdAtSinceBaseline,
34
+ };
35
+ `;
36
+
37
+ const context = {
38
+ console,
39
+ Date,
40
+ Object,
41
+ Number,
42
+ String,
43
+ Array,
44
+ isPublicWallImageUrl(value) {
45
+ return typeof value === "string" && value.startsWith("https://wip.computer/media/kaleidoscope/generated/");
46
+ },
47
+ };
48
+
49
+ vm.createContext(context);
50
+ vm.runInContext(helperSource, context);
51
+
52
+ const {
53
+ KALEIDOSCOPE_PUBLIC_STATS_BASELINE,
54
+ deriveKaleidoscopePublicStats,
55
+ isWipTestHandle,
56
+ createdAtSinceBaseline,
57
+ } = context.__statsHelpers;
58
+
59
+ assert.deepEqual(JSON.parse(JSON.stringify(KALEIDOSCOPE_PUBLIC_STATS_BASELINE.counts)), {
60
+ keysCreated: 3,
61
+ genericKaleidoscopes: 11,
62
+ imageKaleidoscopes: 3,
63
+ });
64
+ assert.equal(KALEIDOSCOPE_PUBLIC_STATS_BASELINE.startsAt, "2026-05-21T07:00:00.000Z");
65
+ assert.equal(isWipTestHandle("wiptest-parker"), true);
66
+ assert.equal(isWipTestHandle("WIPTEST-PARKER"), true);
67
+ assert.equal(isWipTestHandle("passkey-123"), false);
68
+ assert.equal(createdAtSinceBaseline({ createdAt: "2026-05-21T06:59:59.999Z" }), false);
69
+ assert.equal(createdAtSinceBaseline({ createdAt: "2026-05-21T07:00:00.000Z" }), true);
70
+
71
+ const images = [
72
+ {
73
+ url: "https://wip.computer/media/kaleidoscope/generated/prebaseline-generic.jpg",
74
+ kind: "generic",
75
+ createdAt: "2026-05-20T00:00:00.000Z",
76
+ },
77
+ {
78
+ url: "https://wip.computer/media/kaleidoscope/generated/postbaseline-generic.jpg",
79
+ kind: "generic",
80
+ createdAt: "2026-05-21T07:00:00.000Z",
81
+ },
82
+ {
83
+ url: "https://wip.computer/media/kaleidoscope/generated/postbaseline-image.jpg",
84
+ kind: "image",
85
+ createdAt: "2026-05-21T08:00:00.000Z",
86
+ },
87
+ {
88
+ url: "https://wip.computer/media/kaleidoscope/generated/older-image.jpg",
89
+ kind: "image",
90
+ createdAt: "2026-05-20T01:00:00.000Z",
91
+ },
92
+ ];
93
+ const passkeyEntries = [
94
+ { handle: "passkey-before", createdAt: "2026-05-20T00:00:00.000Z" },
95
+ { handle: "wiptest-after", createdAt: "2026-05-21T08:00:00.000Z" },
96
+ { handle: "user-after", createdAt: "2026-05-21T09:00:00.000Z" },
97
+ { handle: "wiptest-newest", createdAt: "2026-05-21T09:15:00.000Z" },
98
+ ];
99
+
100
+ const stats = deriveKaleidoscopePublicStats({
101
+ images,
102
+ passkeyEntries,
103
+ now: new Date("2026-05-21T09:30:00.000Z"),
104
+ });
105
+
106
+ assert.equal(images.length, 4, "stats helper must not remove live-wall images");
107
+ assert.equal(stats.genericKaleidoscopes, 12);
108
+ assert.equal(stats.imageKaleidoscopes, 4);
109
+ assert.equal(stats.keysCreated, 4);
110
+ assert.equal(stats.publicWallImages, 4);
111
+ assert.deepEqual(JSON.parse(JSON.stringify(stats.newSinceBaseline)), {
112
+ genericKaleidoscopes: 1,
113
+ imageKaleidoscopes: 1,
114
+ keysCreated: 1,
115
+ total: 3,
116
+ });
117
+ assert.equal(stats.last24Hours.wallImages, 2);
118
+ assert.equal(stats.last24Hours.keysCreated, 1);
119
+ assert.equal(stats.lastCreated, "2026-05-21T09:00:00.000Z");
120
+
121
+ const wiptestOnly = deriveKaleidoscopePublicStats({
122
+ images: [],
123
+ passkeyEntries: [{ handle: "wiptest-only", createdAt: "2026-05-21T10:00:00.000Z" }],
124
+ now: new Date("2026-05-21T10:00:00.000Z"),
125
+ });
126
+ assert.equal(wiptestOnly.keysCreated, 3);
127
+ assert.equal(wiptestOnly.newSinceBaseline.keysCreated, 0);
128
+
129
+ console.log("kaleidoscope public stats baseline tests passed");
@@ -0,0 +1,89 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ const loginFiles = [
4
+ "src/hosted-mcp/app/kaleidoscope-login.html",
5
+ "src/hosted-mcp/demo/login.html",
6
+ ];
7
+
8
+ function assertContains(source, needle, label) {
9
+ if (!source.includes(needle)) {
10
+ throw new Error(`${label} missing expected text: ${needle}`);
11
+ }
12
+ }
13
+
14
+ function assertBefore(source, first, second, label) {
15
+ const firstIndex = source.indexOf(first);
16
+ const secondIndex = source.indexOf(second);
17
+ if (firstIndex === -1 || secondIndex === -1 || firstIndex >= secondIndex) {
18
+ throw new Error(`${label} expected "${first}" before "${second}"`);
19
+ }
20
+ }
21
+
22
+ function extractFunction(source, name) {
23
+ const start = source.indexOf(`function ${name}(`);
24
+ if (start === -1) throw new Error(`could not find function ${name}`);
25
+ const bodyStart = source.indexOf("{", start);
26
+ let depth = 0;
27
+ for (let i = bodyStart; i < source.length; i += 1) {
28
+ if (source[i] === "{") depth += 1;
29
+ if (source[i] === "}") depth -= 1;
30
+ if (depth === 0) return source.slice(start, i + 1);
31
+ }
32
+ throw new Error(`could not extract function ${name}`);
33
+ }
34
+
35
+ function assertNotContains(source, needle, label) {
36
+ if (source.includes(needle)) {
37
+ throw new Error(`${label} must not contain: ${needle}`);
38
+ }
39
+ }
40
+
41
+ for (const file of loginFiles) {
42
+ const html = readFileSync(file, "utf8");
43
+ const followFunction = extractFunction(html, "followPairNextIfPresent");
44
+ const backFunction = extractFunction(html, "backToLoginAfterQrScan");
45
+
46
+ assertContains(html, 'id="qr-auth-confirm-view"', `${file} confirmation view`);
47
+ assertContains(html, "Your authenticated Kaleidoscope session is available on your other device.", `${file} confirmation headline`);
48
+ assertContains(html, "Back to login", `${file} back button copy`);
49
+ assertNotContains(html, "Open session here", `${file} authenticator completion must not offer local demo open`);
50
+ assertNotContains(html, ">Close</button>", `${file} authenticator completion must not show close action`);
51
+ assertNotContains(html, "Kaleidoscope is ready there.", `${file} removed ready helper copy`);
52
+ assertNotContains(html, "Open Kaleidoscope here", `${file} removed duplicated open copy`);
53
+ assertNotContains(html, "Keep using Kaleidoscope on the other device.", `${file} removed cancel helper copy`);
54
+ assertNotContains(html, "Scan QR Code", `${file} login-page scanner action must be absent`);
55
+ assertNotContains(html, "BarcodeDetector", `${file} login-page scanner detector must be absent`);
56
+ assertNotContains(html, "getUserMedia", `${file} login-page scanner camera code must be absent`);
57
+ assertContains(html, "You can close this page.", `${file} close fallback copy`);
58
+
59
+ assertContains(html, "function showQrAuthenticatorConfirmation(identity, isNewAccount)", `${file} confirmation function`);
60
+ assertContains(html, "function backToLoginAfterQrScan()", `${file} back-to-login function`);
61
+
62
+ assertContains(
63
+ html,
64
+ "if (DEMO_NEXT_REGEX.test(approveResponse.next)) {\n return showQrAuthenticatorConfirmation(identity, isNewAccount === true);\n }",
65
+ `${file} QR authenticator demo next shows confirmation`,
66
+ );
67
+ assertBefore(
68
+ html,
69
+ "return showQrAuthenticatorConfirmation(identity, isNewAccount === true);",
70
+ "next = approveResponse.next;",
71
+ `${file} confirmation intercepts approve next before redirect`,
72
+ );
73
+ assertNotContains(followFunction, "location.replace('/demo');", `${file} QR approve path must not directly enter demo`);
74
+ assertNotContains(followFunction, 'location.href = "/demo";', `${file} QR approve path must not assign demo href`);
75
+ assertNotContains(followFunction, "storeDemoHandoffIfNeeded('/demo'", `${file} QR approve path must not store demo handoff`);
76
+
77
+ assertContains(backFunction, "location.replace('/login?next=/demo');", `${file} back returns to login`);
78
+ assertNotContains(backFunction, "storeDemoHandoffIfNeeded", `${file} back must not store demo handoff`);
79
+ assertNotContains(backFunction, "location.replace('/demo')", `${file} back must not open demo`);
80
+
81
+ assertContains(html, "if (await followPairNextIfPresent(approveResponse, result, true)) return;", `${file} create caller stops after confirmation`);
82
+ assertContains(html, "if (await followPairNextIfPresent(approveResponse, result, false)) return;", `${file} sign-in caller stops after confirmation`);
83
+ assertContains(html, "storeDemoHandoffIfNeeded(data.next, data, qrLoginMode === 'register');", `${file} requester QR status still stores handoff`);
84
+ assertContains(html, "location.replace(data.next);", `${file} requester QR status still redirects`);
85
+ assertContains(html, "storeDemoHandoffIfNeeded('/demo', result, true);", `${file} same-device create still stores demo handoff`);
86
+ assertContains(html, "storeDemoHandoffIfNeeded('/demo', result, false);", `${file} same-device sign-in still stores demo handoff`);
87
+ }
88
+
89
+ console.log("kaleidoscope QR authenticator confirmation checks passed");
@@ -2,19 +2,23 @@
2
2
  "agentId": "cc-mini",
3
3
  "timezone": "America/Los_Angeles",
4
4
  "maxTotalLines": 2000,
5
+ "stalenessDays": 14,
6
+ "dailyLogStalenessDays": 2,
5
7
  "steps": {
6
8
  "sharedContext": {
7
9
  "path": "~/.openclaw/workspace/SHARED-CONTEXT.md",
8
10
  "label": "SHARED-CONTEXT.md",
9
11
  "stepNumber": 2,
10
- "critical": true
12
+ "critical": true,
13
+ "maxLines": 80
11
14
  },
12
15
  "journals": {
13
16
  "dir": "~/wipcomputerinc/team/cc-mini/documents/journals",
14
17
  "label": "Most Recent Journal (Parker)",
15
18
  "stepNumber": 3,
16
19
  "maxLines": 80,
17
- "strategy": "most-recent"
20
+ "strategy": "most-recent",
21
+ "stalenessDays": 14
18
22
  },
19
23
  "workspaceDailyLogs": {
20
24
  "dir": "~/.openclaw/workspace/memory",
@@ -22,7 +26,8 @@
22
26
  "stepNumber": 4,
23
27
  "maxLines": 40,
24
28
  "strategy": "daily-logs",
25
- "days": ["today", "yesterday"]
29
+ "days": ["today", "yesterday"],
30
+ "dailyLogStalenessDays": 2
26
31
  },
27
32
  "fullHistory": {
28
33
  "label": "Full History",
@@ -33,19 +38,22 @@
33
38
  "path": "~/.ldm/agents/cc-mini/CONTEXT.md",
34
39
  "label": "CC CONTEXT.md",
35
40
  "stepNumber": 6,
36
- "critical": true
41
+ "critical": true,
42
+ "maxLines": 60
37
43
  },
38
44
  "soul": {
39
45
  "path": "~/.ldm/agents/cc-mini/SOUL.md",
40
46
  "label": "CC SOUL.md",
41
- "stepNumber": 7
47
+ "stepNumber": 7,
48
+ "maxLines": 80
42
49
  },
43
50
  "ccJournals": {
44
51
  "dir": "~/.ldm/agents/cc-mini/memory/journals",
45
52
  "label": "Most Recent CC Journal",
46
53
  "stepNumber": 8,
47
54
  "maxLines": 80,
48
- "strategy": "most-recent"
55
+ "strategy": "most-recent",
56
+ "stalenessDays": 14
49
57
  },
50
58
  "ccDailyLog": {
51
59
  "dir": "~/.ldm/agents/cc-mini/memory/daily",
@@ -53,13 +61,15 @@
53
61
  "stepNumber": 9,
54
62
  "maxLines": 60,
55
63
  "strategy": "daily-logs",
56
- "days": ["today", "yesterday"]
64
+ "days": ["today", "yesterday"],
65
+ "dailyLogStalenessDays": 2
57
66
  },
58
67
  "repoLocations": {
59
68
  "path": "~/.claude/projects/-Users-lesa--openclaw/memory/repo-locations.md",
60
69
  "label": "repo-locations.md",
61
70
  "stepNumber": 10,
62
- "critical": true
71
+ "critical": true,
72
+ "maxLines": 80
63
73
  }
64
74
  }
65
75
  }
@@ -38,12 +38,13 @@ These are three distinct actions. Never combine them. Never skip the dogfooding
38
38
 
39
39
  ## Co-Authors on Every Commit
40
40
 
41
- All three contributors must be listed on every commit. No exceptions. This is how GitHub tracks contributions across the team.
41
+ All four contributors must be listed on every commit. No exceptions. These trailers credit contributors in commit history and preserve the builder record across the team.
42
42
 
43
43
  ```
44
44
  Co-Authored-By: Parker Todd Brooks <parkertoddbrooks@users.noreply.github.com>
45
45
  Co-Authored-By: Lēsa <lesaai@icloud.com>
46
- Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
46
+ Co-Authored-By: Claude Code (Opus 4.7) <noreply@anthropic.com>
47
+ Co-Authored-By: Codex (GPT 5.5) <noreply@openai.com>
47
48
  ```
48
49
 
49
50
  ## Built-By Line
@@ -51,7 +52,7 @@ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
51
52
  Every repo README must include this exact attribution in the License section:
52
53
 
53
54
  ```
54
- Built by Parker Todd Brooks, Lēsa (OpenClaw, Claude Opus 4.6), Claude Code (Claude Opus 4.6).
55
+ Built by Parker Todd Brooks, Lēsa, Claude Code (Opus 4.7), and Codex (GPT 5.5).
55
56
  ```
56
57
 
57
58
  This is the standard. Use it everywhere. It credits the humans and identifies which AI runtimes built the software.
@@ -70,7 +71,7 @@ AGPL Commercial redistribution, marketplace listings, or bundling into paid se
70
71
 
71
72
  AGPL for personal use is free. Commercial licenses available.
72
73
 
73
- Built by Parker Todd Brooks, Lēsa (OpenClaw, Claude Opus 4.6), Claude Code (Claude Opus 4.6).
74
+ Built by Parker Todd Brooks, Lēsa, Claude Code (Opus 4.7), and Codex (GPT 5.5).
74
75
  ```
75
76
 
76
77
  **Rules:**
@@ -15,3 +15,7 @@ Never overwrite shared workspace files. Always append or edit specific sections.
15
15
  ## Protected paths
16
16
 
17
17
  Do not modify: secrets/, credentials/, auth-profiles.json, or any file containing API keys.
18
+
19
+ ## Config doctors can silently strip keys
20
+
21
+ Never run `openclaw doctor --fix` (or any config doctor with strict-schema validation) without checking `git diff` on the config afterward. Doctor tools silently strip keys they do not recognize; git is the safety net.
@@ -8,7 +8,24 @@ Reads 9 files from the Dream Weaver Boot Sequence (SHARED-CONTEXT.md, SOUL.md, C
8
8
 
9
9
  ## Content Budget
10
10
 
11
- ~700 lines, ~3,500 tokens. Under 2% of the context window. Large files (journals, daily logs) are truncated. Missing files are skipped gracefully.
11
+ Large files are line-capped and stale journals are dropped to path-only lines, so the payload stays small. Missing files are skipped gracefully. The last line of every boot payload is a one-line summary (`== Boot payload: N bytes, M lines, K sections. Capped: ... Stale/path-only: ... ==`) so future slowness triage is trivial.
12
+
13
+ ### Per-step line caps
14
+
15
+ Every step has a line cap. If `boot-config.json` sets `maxLines` for a step, that wins. Otherwise these code defaults apply. They live in `boot-hook.mjs`, not just the config template, because the installer preserves an existing user `boot-config.json` and never overwrites it, so a config-only change would never reach existing installs:
16
+
17
+ - `sharedContext`: 80
18
+ - `soul`: 80
19
+ - `context`: 60
20
+ - `repoLocations`: 80
21
+
22
+ When a file is truncated, the injected content ends with a marker naming the full path so the rest can be read on demand.
23
+
24
+ ### Staleness cutoffs
25
+
26
+ `most-recent` steps (journals) inject the newest dated file only if it is fresh. If the newest file is older than `stalenessDays` (default 14, overridable top-level or per-step), the body is NOT injected: a single line with the file path, date, and age is emitted instead. This stops a four-month-old "most recent" journal from being re-injected every session.
27
+
28
+ `daily-logs` steps have the same guard via `dailyLogStalenessDays` (default 2). Day files are today/yesterday by construction, so this is defensive.
12
29
 
13
30
  ## Deploy
14
31
 
@@ -49,6 +66,12 @@ Should output JSON with `hookSpecificOutput.additionalContext` containing all bo
49
66
 
50
67
  `boot-config.json` defines paths and limits for each boot step. Uses `~` shorthand (resolved at runtime). To support a different agent (cc-air), deploy a different config alongside the same script.
51
68
 
69
+ Optional keys:
70
+
71
+ - Top-level `stalenessDays` (default 14) and `dailyLogStalenessDays` (default 2): staleness cutoffs for `most-recent` and `daily-logs` steps. Per-step keys of the same name override the top-level value.
72
+ - Per-step `maxLines`: line cap for that step. Overrides the code default (see Content Budget).
73
+ - Top-level `maxTotalLines` (default 2000): hard safety cap across all steps.
74
+
52
75
  ## Adding a Boot Step
53
76
 
54
77
  1. Add an entry to `boot-config.json` under `steps`
@@ -2,19 +2,23 @@
2
2
  "agentId": "cc-mini",
3
3
  "timezone": "America/Los_Angeles",
4
4
  "maxTotalLines": 2000,
5
+ "stalenessDays": 14,
6
+ "dailyLogStalenessDays": 2,
5
7
  "steps": {
6
8
  "sharedContext": {
7
9
  "path": "~/.openclaw/workspace/SHARED-CONTEXT.md",
8
10
  "label": "SHARED-CONTEXT.md",
9
11
  "stepNumber": 2,
10
- "critical": true
12
+ "critical": true,
13
+ "maxLines": 80
11
14
  },
12
15
  "journals": {
13
16
  "dir": "~/Documents/wipcomputer--mac-mini-01/staff/Parker/Claude Code - Mini/documents/journals",
14
17
  "label": "Most Recent Journal (Parker)",
15
18
  "stepNumber": 3,
16
19
  "maxLines": 80,
17
- "strategy": "most-recent"
20
+ "strategy": "most-recent",
21
+ "stalenessDays": 14
18
22
  },
19
23
  "workspaceDailyLogs": {
20
24
  "dir": "~/.openclaw/workspace/memory",
@@ -22,7 +26,8 @@
22
26
  "stepNumber": 4,
23
27
  "maxLines": 40,
24
28
  "strategy": "daily-logs",
25
- "days": ["today", "yesterday"]
29
+ "days": ["today", "yesterday"],
30
+ "dailyLogStalenessDays": 2
26
31
  },
27
32
  "fullHistory": {
28
33
  "label": "Full History",
@@ -33,19 +38,22 @@
33
38
  "path": "~/.ldm/agents/cc-mini/CONTEXT.md",
34
39
  "label": "CC CONTEXT.md",
35
40
  "stepNumber": 6,
36
- "critical": true
41
+ "critical": true,
42
+ "maxLines": 60
37
43
  },
38
44
  "soul": {
39
45
  "path": "~/.ldm/agents/cc-mini/SOUL.md",
40
46
  "label": "CC SOUL.md",
41
- "stepNumber": 7
47
+ "stepNumber": 7,
48
+ "maxLines": 80
42
49
  },
43
50
  "ccJournals": {
44
51
  "dir": "~/.ldm/agents/cc-mini/memory/journals",
45
52
  "label": "Most Recent CC Journal",
46
53
  "stepNumber": 8,
47
54
  "maxLines": 80,
48
- "strategy": "most-recent"
55
+ "strategy": "most-recent",
56
+ "stalenessDays": 14
49
57
  },
50
58
  "ccDailyLog": {
51
59
  "dir": "~/.ldm/agents/cc-mini/memory/daily",
@@ -53,13 +61,15 @@
53
61
  "stepNumber": 9,
54
62
  "maxLines": 60,
55
63
  "strategy": "daily-logs",
56
- "days": ["today", "yesterday"]
64
+ "days": ["today", "yesterday"],
65
+ "dailyLogStalenessDays": 2
57
66
  },
58
67
  "repoLocations": {
59
68
  "path": "~/.claude/projects/-Users-lesa--openclaw/memory/repo-locations.md",
60
69
  "label": "repo-locations.md",
61
70
  "stepNumber": 10,
62
- "critical": true
71
+ "critical": true,
72
+ "maxLines": 80
63
73
  }
64
74
  }
65
75
  }