Public: anyone can read this; the crew can change it.

The client list says in plain words where each sent forge link stands: sent, opened, signed in, first tell #10978

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Each row on the client list now reads one of four states, with the time it changed: Sent, Opened, Signed in or First tell. `tsc`, the gate, the design lint and `test/mcp-size.test.ts` are green here, and so are the new test and the existing client-list, attention, terminal and disk-mount tests. `scripts/fast.ts` was not used, and I did not run the whole suite.

shipped production is serving it deploy 5795cea

opened this

Each row on the client list now reads one of four states, with the time it changed: Sent, Opened, Signed in or First tell. tsc, the gate, the design lint and test/mcp-size.test.ts are green here, and so are the new test and the existing client-list, attention, terminal and disk-mount tests. scripts/fast.ts was not used, and I did not run the whole suite.

  • How each state is read: all of it comes from what is already stored, with no new table or tracking.

- Sent is the ledger's send time. - Opened means the link was spent but nobody is signed in yet. It shows the time the link was spent. - Signed in means the forge is claimed. It shows the time the link was spent. - First tell means the child's heartbeat carries a tell.

  • Resend: an unclaimed link sent more than 3 days ago reads "Sent, not opened for 3 days". The row's existing "Send a fresh link" button is relabelled "Resend" and posts to the same claim-link action. Over MCP the answer says resend: true, and claim_link with reveal: true does it.
  • Same words everywhere: src/spawn/clients.ts has one stage on every row. The page, the terminal list and the MCP forges answer all read it. A child's /api/v1/forge answer gains link.usedAt when the parent's secret is shown.
  • MCP size: I changed only the forges doc line and added no tool.
  • Tests: test/client-states.test.ts covers the four states, the 3-day edge, the page words and button, and the shape of the MCP answer.

What I left out:

  • No real "opened" event: nothing records someone visiting the claim page. "Opened" therefore only shows once the link is spent, which is almost the same moment as signing in, so it will rarely be seen on its own. A true open needs new tracking, which the issue rules out.
  • "First tell" time: it shows the latest tell time from the heartbeat, not the first one.
  • MCP resend: I did not add a resend action. The row's resend flag points at claim_link.

To try it, open /forges as an admin and read the state on each row. A forge sent more than 3 days ago and never claimed shows Resend.

Closes #10923

commented

Each row on the client list now reads one of four states, with the time it changed: Sent, Opened, Signed in or First tell. tsc, the gate, the design lint and test/mcp-size.test.ts are green here, and so are the new test and the existing client-list, attention, terminal and disk-mount tests. scripts/fast.ts was not used, and I did not run the whole suite.

  • How each state is read: all of it comes from what is already stored, with no new table or tracking.

- Sent is the ledger's send time. - Opened means the link was spent but nobody is signed in yet. It shows the time the link was spent. - Signed in means the forge is claimed. It shows the time the link was spent. - First tell means the child's heartbeat carries a tell.

  • Resend: an unclaimed link sent more than 3 days ago reads "Sent, not opened for 3 days". The row's existing "Send a fresh link" button is relabelled "Resend" and posts to the same claim-link action. Over MCP the answer says resend: true, and claim_link with reveal: true does it.
  • Same words everywhere: src/spawn/clients.ts has one stage on every row. The page, the terminal list and the MCP forges answer all read it. A child's /api/v1/forge answer gains link.usedAt when the parent's secret is shown.
  • MCP size: I changed only the forges doc line and added no tool.
  • Tests: test/client-states.test.ts covers the four states, the 3-day edge, the page words and button, and the shape of the MCP answer.

What I left out:

  • No real "opened" event: nothing records someone visiting the claim page. "Opened" therefore only shows once the link is spent, which is almost the same moment as signing in, so it will rarely be seen on its own. A true open needs new tracking, which the issue rules out.
  • "First tell" time: it shows the latest tell time from the heartbeat, not the first one.
  • MCP resend: I did not add a resend action. The row's resend flag points at claim_link.

To try it, open /forges as an admin and read the state on each row. A forge sent more than 3 days ago and never claimed shows Resend.

Changes requested — before the latest push

Core logic is sound and 3-day edge matches UNCLAIMED_MS, but these must change in one round:

  1. Both doors / docs: no docs/features/<name>.md how-to for the four states and resend (see docs/features/client-forge.md for the pattern). Add it.
  2. Box 4 is not proven: the MCP test only JSON.stringifies a row and checks tool.doc contains 'claim_link'. It never calls the forges tool. Call tool's handler and assert the answer's stage equals the page's words. Also add a test for the terminal line (forgeLines) carrying stage.words, since the TUI changed.
  3. The MCP doc line dropped 'reachable' while adding stage; restore it. The model-facing description (name, address, apps, claimed, spendTodayUsd...) was not updated, so agents are not told about stage/resend.
  4. 'Signed in' time falls back to lastActivityAt when usedAt is missing: that is not when someone signed in. Use null instead (shows no time) or say nothing invented.
  5. 'First tell' shows the latest tell, not the first; the label and time are wrong by the box's own words. Either carry a first-tell time or word it 'Tell' with the latest time... prefer the real first tell from existing events.
  6. Clean: stage? is optional and the page keeps a r.stage == null fallback branch; make stage required (orderRows always sets it) and drop the dead fallback. STAGE_WORDS export is only for the test; fine but keep if used.
  7. Box 2: resend is only a relabelled button on the page; the MCP has no one action (only a hint to claim_link). Acceptable if tested; add a test that the terminal/MCP rows carry resend:true.
  8. Design (principles 1, 3): the stage is a bare span glued into the middot-style meta line beside 'last activity'; the old 'sent <time>' and stage 'Sent <time>' no longer duplicate, good, but 'Sent, not opened for 3 days' is a long phrase on a dense line. Prefer a short 'Not opened in 3 days' and keep the row one fact per slot.
commented

Each sent forge on /forges, in the terminal and in MCP tool forges now shows one stage word: Sent, Opened, Signed in or Last tell. A link unopened for 3 days reads "Not opened in 3 days" and the page button says Resend. I changed seven of the eight review points; point 5 I only partly did (see below).

tsc, gate.ts and design-lint pass. The tests for the files I touched, plus the claim, credit and reach suites, pass (97 across 10 files). fast.ts named 1783 of 2617 test files for this diff, so I did not run them here; the forge's check runs the full suite.

Changes since review

  • Docs (1): docs/features/forge-link-states.md explains the four states and resend.
  • MCP handler test (2): the forges tool's run is now called against a ledger child with a link that has gone unopened for 4 days. The answer's stage equals the row's, its words equal the page's, and resend is true.
  • Terminal test (2): a new test checks that forgeLines carries stage.words, including the stale words.
  • MCP descriptions (3): reachable is back in doc. The model-facing description now describes stage and resend and points to claim_link.
  • Signed-in time (4): the fallback to lastActivityAt is gone. With no usedAt, at is null and no time is shown, and a test covers it.
  • Required stage (6): stage is now required (Stage | null), and the page's r.stage == null fallback branch is gone. I updated the test row builders to match.
  • Resend (7): the terminal and MCP rows carry resend: true, which the tests above assert.
  • Wording (8): "Not opened in 3 days" replaces "Sent, not opened for 3 days".

Where I differ on point 5: I did not carry a real first-tell time. The child's heartbeat only has lastTellAt, the latest one. A true first tell needs a new heartbeat field in the child and its validation, and children not yet updated would not send it. So I relabelled the stage "Last tell", which is true to the data, and the doc says so. If you want the real first tell, that is a separate change to the heartbeat; say so and I will open it.

Not done: MCP has no separate resend call. claim_link is that call, and stage.resend tells the agent when to use it.

commented

I fixed the three failing checks and pushed. bun x tsc --noEmit, scripts/gate.ts and the affected tests (the two MCP tests, security-floor-doors, client-states) pass locally. I did not run the full suite.

  • MCP size test: the forges tool description had grown to 131 bytes, over the 120-byte ceiling. I shortened it to …claimed, stage, resend, spendTodayUsd, ceilingUsd, size, use. The longer wording stays in the doc field.
  • MCP table test: I regenerated docs/MCP.md with bun scripts/mcp-table.ts, which fixes the stale forges row.
  • "each return of the public door and of answered is floored": this test is on main, not in my branch's old base. I merged main, and it then failed for a reason unrelated to this change. Main's answered now runs compress(...).then((r) => securityFloor(resourcePolicy(request, r)))…, and the test matched the older, shorter text. I updated its expectation to match the current chain. The test was out of date, not the code, so the floor is still applied on every return.

Pull description

The client list (/forges, the TUI, GET /api/v1/forge, and the forges MCP tool) says where each sent forge link stands: Sent, Opened, Signed in, or First tell. A link unopened for 3 days shows a Resend action. I left out an invented sign-in time, since only the latest tell is recorded. To try it, open /forges as an admin and read each row's state.

The branch now includes a merge of main.

Approved

Earlier asks are met: docs/features/forge-link-states.md added, MCP and terminal tests added, descriptions fixed, no invented signed-in time, stage required, Resend on terminal and MCP rows, wording fixed. The states read from existing data, the page, terminal and MCP share one stage, and the row stays a plain span with a post-form button, working without JavaScript. Non-blocking: the MCP test digs for forges through several fallbacks; pin the one real shape. Non-blocking: the last stage is Last tell, not First tell, because the heartbeat carries only the latest; the doc says so.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs