Repository navigation
Add 心屿 Xinyu Island (xinyu-daily) - #27
Conversation
A cozy daily-mood Mini App that plays like an indie game: inspiration cards shaped by the user's answers, mood stickers and journal, a joy jar, a decision slot machine, a Book of Answers, and 19 hidden island secrets. Local only: writes one state file in context.dataDir, no network requests, no child processes, no Agent turns.
There was a problem hiding this comment.
Thanks for the submission — the island is lovely and clearly built with care. I ran npm run check on the merged tree (Node 22: xinyu-daily: OK, 69/69 pass), ran the Node entry in a process with fs, child_process, net, http and fetch all stubbed, and used headless Chrome to walk all 4 tabs and every write endpoint. Everything matches the README: it only writes context.dataDir/xinyu-state.json (temp file + rename), spawns no child processes, makes no outside network requests, and the page makes no outside requests either. HTML entered into the nickname, notes, joy jar and Book of Answers is never executed; esc() is used well. SSE cleanup and dispose are fine too.
Required changes
handle()inminiapp/node/server.mjsdoesn't checkHostorOriginright now. Without the Host's loopback credential, I tested two requests:GET /api/statewithHost: attacker.example:<port>returned 200 with the raw mood notes, andPOST /api/resetwithOrigin: https://evil.exampleandContent-Type: text/plainalso returned 200 and wiped the data. Since MiniMax Code 3.1.2 the Host adds a credential to the port, but this package can be installed on older clients (like the 3.1.1-preview you tested on), which don't have that layer. Please follow cq-viewer's approach in this repo (the loopback check inplugins/hanzijie/cq-viewer/miniapp/node/server.mjs):Hostmust be127.0.0.1,localhostor[::1], otherwise reject; ifOriginis present it must behttp://<loopback>:<context.listen.port>, otherwise return 403. Write endpoints could also requireContent-Type: application/jsonwhile you're at it.
Suggestions (optional)
domain.mjsonly ignores ENOENT when loading state: ifxinyu-state.jsonis truncated JSON ornull,start()throws and the app can never open again, and users won't know how to find dataDir to fix it. Suggest renaming the bad file as a backup, starting from an empty state, and logging a warning.mutate()changes memory before writing to disk, so a failed write leaves memory and disk out of sync. You could modify a copy and swap it in after the write succeeds.- The README says localStorage only holds view preferences, but
app.jsalso storesxinyu.awoke. Either mention it in the README or use the existingflags.awokein state. - This PR and #30 both add a row at the same spot in the root README table, so whichever merges first will give the other a one-line conflict; a rebase will fix it.
| * @param {import('node:http').ServerResponse} response | ||
| */ | ||
| async function handle(request, response) { | ||
| const url = new URL(request.url ?? '/', 'http://miniapp.local'); |
There was a problem hiding this comment.
Please check here that request.headers.host is a loopback address and that Origin (if present) equals http://<loopback>:${context.listen.port}, returning 403 otherwise. Without Host credentials (e.g. on a 3.1.1 client), a cross-site POST /api/reset (text/plain) returned 200 and wiped the data in my test, and GET /api/state with Host: attacker.example could read the raw notes.
| if (!state.secrets || typeof state.secrets !== 'object') state.secrets = {}; | ||
| if (!state.flags || typeof state.flags !== 'object') state.flags = {}; | ||
| } catch (error) { | ||
| if (/** @type {any} */ (error)?.code !== 'ENOENT') throw error; |
There was a problem hiding this comment.
Optional: when the JSON is corrupt or the content is null, this makes start() fail (SyntaxError and TypeError respectively in my test), and the app will never open. Suggest renaming the bad file as a backup and starting with emptyState().
| /** 串行化所有写操作:修改 → 落盘 → 广播 */ | ||
| function mutate(fn) { | ||
| const run = queue.then(async () => { | ||
| const result = fn(); |
There was a problem hiding this comment.
Optional: fn() modifies memory first, so if persist() fails, memory and disk are out of sync. Consider modifying a copy and swapping it in after persist succeeds.
| - Agent turns: none. No `agentAccess`. | ||
| - State stored in `xinyu-state.json`: nickname, birthday, daily mood check-ins and notes, drawn | ||
| cards and favourites, small joys, Book of Answers history, quiz result, about-you answers, | ||
| discovered secrets, and one-time onboarding flags. The browser's `localStorage` keeps only view |
There was a problem hiding this comment.
Optional: app.js also writes xinyu.awoke to localStorage; worth adding here.
Optional daily letter, chat, notebook, and mood replies written by the user's own model through the 3.1.2 Mini App Agent API (agentAccess session.run, agentConcurrency 1). Off until the user opts in from the mailbox invitation or Settings; tool, permission, and question events cancel the turn. Redrawn mascot, 20 secrets, new icon and preview.
…om a corrupt state file Addresses the review on MiniMax-AI#27: - Every route checks that Host is loopback and that a browser Origin, when sent, is this listener (same check as cq-viewer); anything else gets 403. Writes require application/json (415). - A truncated, null, or non-object xinyu-state.json is renamed to xinyu-state.broken-<time>.json and the app starts empty with a warn log; top-level fields of the wrong type fall back to defaults. - mutate() edits a copy and swaps it in only after the write succeeds, so a failed write leaves memory and disk in step. - The page sends the JSON content type on every write, including bodyless ones.
|
@MyPrototypeWhat 感谢这么细的审查,几条都改了,在 b5dad3d(同时推上来的 76543d2 是 1.1.0 的新功能:可选的小屿陪伴和重画的小屿形象)。 需要修改:Host / Origin 检查
可选建议
|
MyPrototypeWhat
left a comment
There was a problem hiding this comment.
Thanks for the follow-up. I re-reviewed b5dad3d (the 1.1.0 companion commit plus the review-fix commit on top of 11ada93). Locally on Node 22, npm run check passes 69/69, with xinyu-daily at 0 errors and the 1 known warning.
The Host/Origin gap from last round is fixed: every request now goes through isLoopbackRequest first. Retested without Host credentials: a read with a forged Host: attacker.example gets 403; POST /api/reset with a cross-site Origin gets 403 and the data is untouched; text/plain writes get 415. A corrupt xinyu-state.json (truncated / null / array) is renamed to xinyu-state.broken-*.json and the app starts empty, and mutate() now edits a copy and only swaps it in after the write succeeds. The companion also looks careful: turns are cancelled on tool, permission and question events, allowOnce is never answered, and output is parsed and length-capped.
Optional
npm run checkstill reportsREADME_MIN_VERSION_MISSING. Naming MiniMax Code 3.1.2 in## Tested environment(e.g. "requires ≥3.1.2; tested on 3.1.1-preview, which already ships the Agent API") would match Install and clear the warning.- The companion has only been run against a mock Agent, so a quick smoke test on a real 3.1.2+ client with a real model (letter / chat / toggling it off) would be good after this lands.
- When
Hostis missing,isLoopbackRequestonly checks Origin. Low risk in practice, but you could require a loopbackHost. - The root README row will conflict with #30; whichever lands second needs a rebase.
心屿 Xinyu Island (
xinyu-daily) 1.1.0A cozy daily-mood Mini App that plays like a small indie game. A floating island grows as you tell its keeper, a cloud named Xiaoyu, more about yourself. Daily inspiration card, mood stickers and journal, joy jar, decision slot machine, Book of Answers, and 20 hidden secrets guarded by Hengheng, a grumpy crab "island bully". Interface in Chinese.
New in 1.1.0: 小屿陪伴 (optional). Xiaoyu writes you a letter each day, chats with you, keeps a small notebook about you, and answers your mood sticker, using your own model through the 3.1.2 Mini App Agent API. Xiaoyu is also redrawn to be cuter: round mochi cloud, big sparkly eyes, little arms, five mood expressions.
Data & access
xinyu-state.json, insidecontext.dataDir(temp file + rename). A corrupt file is renamed toxinyu-state.broken-<time>.jsonand the app starts empty. Reads only its own client files.surface.window.Hostmust be loopback, and a browserOrigin, when sent, must be this listener (403 otherwise). Writes accept onlyapplication/json(415).agentAccess: ["session.run"],agentConcurrency: 1. Turns run from the Node entry only after the user turns the companion on (mailbox invitation or Settings), and ask for plain text only: the daily letter, chat replies, one line about the mood sticker. Tool, permission, and question events cancel the turn; the app never answersallowOnce. Timeouts 240/120/90 s; up to 20 chat messages and 3 mood replies a day. The README lists every field sent in the prompt.Tested environment
runcontract (55 scripted cases: invitation, letters, chat, notebook, mood replies, fallbacks, tool cancellation, timeouts, limits, session reuse). It has not yet been run against a real model on a device.Checks
npm run check(Node 22.22.2): 69/69 tests pass;plugins/yujiachen-y/xinyu-daily: 0 errors, 1 warning. The warning isREADME_MIN_VERSION_MISSING: testing was on 3.1.1-preview, so the README does not claim a 3.1.2 run; the Install section states 3.1.2 or later is required.Need help on this PR? Tag
@codesmith-botwith what you need.