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

Making a forge says what each follow mode does: tell-me opens an inbox card, take-updates opens a pull — not "recorded for now" #7216

Merged opened this

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

Merged

torque/forge:torque/7199-making-a-forge-says-what-each-follow-mod → main 1 commit fork 1 thread · 1 unresolved

  • Merged. torque/7199-making-a-forge-says-what-each-follow-mod was merged into main as b165dc8.

Review

Sign in to review this change.
7 files changed +51 −11
docs/MCP.md +1 −1
@@ -222,7 +222,7 @@ longer sentences:
222222
223223 - `dryRun`: plans unless it is `false`; anything else changes nothing. `limb` applying takes an admin token.
224224 - `tell.from`: a path on this forge the issue links to, like `/alice/notes/pulls/3`. `tell.context`: where this was told from, for the builder.
225 - `spawn.reveal`, `claim_link.reveal`: put the live claim link in the answer (default false); it is a secret, only when its person reads it. `spawn.fresh`: a fresh app beside the copied ones. `spawn.followMode`: `tell-me` (default) or `take-updates`. `spawn.size`: recorded only, not enforced. `spawn.keepAccess`: stay an owner once claimed.
225 - `spawn.reveal`, `claim_link.reveal`: put the live claim link in the answer (default false); it is a secret, only when its person reads it. `spawn.fresh`: a fresh app beside the copied ones. `spawn.followMode`: `tell-me` (default) tells the owner in their inbox when this forge's code moves; `take-updates` opens a pull with the new code, and a person merges it. `spawn.size`: recorded only, not enforced. `spawn.keepAccess`: stay an owner once claimed.
226226 - `commits.ref`: branch, tag or commit; the default branch when absent. `report.page`: the http(s) address where the problem was seen. `plan.issues.pillar`: the pillar served, as `"P0"` or `"P0 Trust"`; `later: true` means no pillar and needs `why`.
227227 - `explore.query`: words, plus `kind:app|person|forge` and `sort:score|forks|ships|use|fresh|name`, `updated:1d|7d|30d` (only what moved in that window) and `archived:yes`.
228228 - `archive.repos`: several repositories, as `owner/name`, to archive in one operation.
src/http/mcp/tools/spawn.ts +2
@@ -1,4 +1,5 @@
11 import { spawnDoor } from "../../../spawn/door.ts";
2 import { FOLLOW_SHORT } from "../../../spawn/follow_words.ts";
23 import { good, bad, type Tool, APP_ARG, REVEAL_ARG, LINK_KEPT, LINK_REVEALED } from "../shared.ts";
34
45 export const tool: Tool = {
@@ -30,6 +31,7 @@ export const tool: Tool = {
3031 followMode: {
3132 type: "string",
3233 enum: ["tell-me", "take-updates"],
34 description: FOLLOW_SHORT,
3335 },
3436 size: {
3537 type: "string",
src/spawn/follow_words.ts added +26
@@ -0,0 +1,26 @@
1 /**
2 * Follow words (#7199): what each follow mode does, in one plain line, so
3 * `/forges/new`, the terminal's new-forge screen and MCP `spawn` say the
4 * same thing. They describe `src/spawn/upstream.ts`: `tell-me` sends the
5 * owner an inbox card when this forge's code moves; `take-updates` opens the
6 * one `forge/take-updates` pull (`src/spawn/take.ts`), which a person merges.
7 */
8
9 import type { FollowMode } from "./chain.ts";
10
11 export const FOLLOW_LABELS: Readonly<Record<FollowMode, string>> = {
12 "tell-me": "Tell me",
13 "take-updates": "Take updates",
14 };
15
16 export const FOLLOW_WORDS: Readonly<Record<FollowMode, string>> = {
17 "tell-me": "tells the owner in their inbox when this forge's code moves.",
18 "take-updates": "opens a pull with the new code, and a person merges it.",

Fine as a hint. To be complete: on a conflict, take.ts opens an issue that names the files instead of a pull. If this line ever grows, that is the case to add.

19 };
20
21 /** One line naming both modes, for the terminal's hint. */
22 export const FOLLOW_LINE =
23 `tell-me (default) ${FOLLOW_WORDS["tell-me"]} take-updates ${FOLLOW_WORDS["take-updates"]}`;
24
25 /** The same, short enough for an MCP field (60 bytes); docs/MCP.md has the long words. */
26 export const FOLLOW_SHORT = "tell-me: an inbox card; take-updates: a pull a person merges";
src/tui/screens/newforge.ts +5 −2
@@ -10,6 +10,8 @@
1010 import { DEFAULT_SIZE, SIZES, type Size } from "../../spawn/sizes.ts";
1111 import { childUrlOf, spawnDoor } from "../../spawn/door.ts";
1212 import { quotaFull } from "../../spawn/quota.ts";
13 import { FOLLOW_LINE, FOLLOW_WORDS } from "../../spawn/follow_words.ts";
14 import { isFollowMode } from "../../spawn/chain.ts";
1315 import { MAX_BUDGET_USD, MIN_BUDGET_USD } from "../../spawn/plan.ts";
1416 import type { Ctx, Screen } from "../app/app.ts";
1517 import { binding } from "../app/keys.ts";
@@ -39,7 +41,7 @@ const SPECS: readonly Spec[] = [
3941 { key: "modelKey", label: "Model key", hint: "Sealed in the child, never shown again. Optional.", secret: true },
4042 { key: "modelEndpoint", label: "API base URL", hint: "A gateway for the key; empty for Anthropic. Optional." },
4143 { key: "budgetUsd", label: "Budget in dollars", hint: `${MIN_BUDGET_USD}–${MAX_BUDGET_USD}, for the key's whole life. Needed with a key.` },
42 { key: "followMode", label: "Follows this forge's code", hint: "tell-me (default) or take-updates. Recorded for now: nothing tells you or takes updates yet." },
44 { key: "followMode", label: "Follows this forge's code", hint: FOLLOW_LINE },
4345 { key: "size", label: "Size", hint: "small (default), medium or large: left and right choose, or type it.", choices: SIZE_CHOICES },
4446 ];
4547
@@ -119,9 +121,10 @@ export function newForgeScreen(f: Session, ctx: Ctx): Screen {
119121 const bytes = Number(body["bytes"] ?? 0);
120122 const budget = (body["plan"] as { budgetUsd?: number | null } | undefined)?.budgetUsd ?? null;
121123 const size = (body["plan"] as { size?: string } | undefined)?.size;
124 const mode = val("followMode") === "" ? "tell-me" : val("followMode");
122125 plan = [
123126 `A forge at ${childUrlOf(f.app, val("name"))}.`,
124 `Recorded for now, not acted on: it follows this forge's code as ${val("followMode") === "" ? "tell-me" : val("followMode")}.`,
127 `It follows this forge's code as ${mode}${isFollowMode(mode) ? `: it ${FOLLOW_WORDS[mode]}` : "."}`,
125128 "Whoever opens the link becomes its admin.",
126129 ...(budget === null ? [] : [`Its crew runs on your key, up to $${budget} for the key's whole life.`]),
127130 "Copied into it:",
src/web/pages/new-forge.ts +4 −3
@@ -24,6 +24,7 @@ import {
2424 MIN_BUDGET_USD,
2525 } from "../../spawn/plan.ts";
2626 import { DEFAULT_SIZE, SIZES, sizesView } from "../../spawn/sizes.ts";
27 import { FOLLOW_LABELS, FOLLOW_WORDS } from "../../spawn/follow_words.ts";
2728 import { notFoundPage, pageHead } from "../chrome.ts";
2829 import { field, readForm, type Form } from "../form.ts";
2930 import { html, type Html } from "../html.ts";
@@ -220,7 +221,7 @@ function formView(
220221 ...(errors["fresh"] ? { error: errors["fresh"] } : {}),
221222 })}
222223 <fieldset class="field" ${errors["followMode"] ? html`data-invalid` : null}>
223 <legend>How it follows this forge's code (recorded for now; nothing acts on it yet)</legend>
224 <legend>How it follows this forge's code</legend>
224225 <label class="check">
225226 <input
226227 type="radio"
@@ -229,7 +230,7 @@ function formView(
229230 ${values.followMode !== "take-updates" ? html`checked` : null}
230231 />
231232 <span
232 >Recorded for now: tell me <span class="muted small">your choice is kept; nothing tells you yet.</span></span
233 >${FOLLOW_LABELS["tell-me"]} <span class="muted small">${FOLLOW_WORDS["tell-me"]}</span></span
233234 >
234235 </label>
235236 <label class="check">
@@ -240,7 +241,7 @@ function formView(
240241 ${values.followMode === "take-updates" ? html`checked` : null}
241242 />
242243 <span
243 >Recorded for now: take updates <span class="muted small">your choice is kept; nothing acts on it yet.</span></span
244 >${FOLLOW_LABELS["take-updates"]} <span class="muted small">${FOLLOW_WORDS["take-updates"]}</span></span
244245 >
245246 </label>
246247 ${errors["followMode"]
test/new-forge.test.ts +10 −3
@@ -20,6 +20,7 @@ import { usePlacement, useProbe } from "../src/spawn/door.ts";
2020 import { childrenForMemory, maxChildren, setCapacity } from "../src/core/capacity.ts";
2121 import { hostedPlacement, memoryBodies } from "../src/spawn/placement.ts";
2222 import { quietLogs } from "./helpers/globals.ts";
23 import { tool as spawnTool } from "../src/http/mcp/tools/spawn.ts";
2324
2425 quietLogs();
2526
@@ -302,13 +303,19 @@ describe("+ New forge", () => {
302303
303304 test("the page promises no update that does not happen", async () => {
304305 const html = (await (await get(admin, "/forges/new")).text()).replace(/\s+/g, " ");
305 expect(html).toContain("Recorded for now: tell me");
306 expect(html).toContain("Recorded for now: take updates");
307 expect(html).toContain("nothing acts on it yet");
306 expect(html).toContain("Tell me <span class=\"muted small\">tells the owner in their inbox when this forge&#39;s code moves.</span>");
307 expect(html).toContain("Take updates <span class=\"muted small\">opens a pull with the new code, and a person merges it.</span>");
308 expect(html).toContain("<legend>How it follows this forge's code</legend>");
309 expect(html).not.toMatch(/recorded for now|nothing acts on it yet/i);
308310 expect(html).not.toContain("when this forge changes");
309311 expect(html).not.toMatch(/as long as its own changes merge/);
310312 });
311313
314 test("MCP spawn says what each follow mode does, as the page does", () => {
315 const followMode = (spawnTool.inputSchema.properties as Record<string, { description?: string }>)["followMode"];
316 expect(followMode?.description).toBe("tell-me: an inbox card; take-updates: a pull a person merges");
317 });
318
312319 test("a forge at its depth limit shows no door, and its page says why", async () => {
313320 useProbe(app, () => async () => true);
314321 usePlacement(app, hostedPlacement({ host: "forge.test", depth: 2 }));
test/tui-newforge.test.ts +3 −2
@@ -87,11 +87,12 @@ describe("the new forge screen", () => {
8787 test("the screen and its plan promise no update that does not happen", () =>
8888 withScreen(admin, async ({ tui, terminal }) => {
8989 const form = tui.screen.toText();
90 expect(form).toContain("Recorded for now");
90 expect(form).toContain("tell-me (default) tells the owner in their inbox");
9191 terminal.feed("kai\x10");
9292 await settleApp(tui);
9393 const text = form + tui.screen.toText();
94 expect(tui.screen.toText()).toContain("Recorded for now, not acted on");
94 expect(tui.screen.toText()).toContain("It follows this forge's code as tell-me: it tells the owner in their inbox");
95 expect(text).not.toMatch(/recorded for now/i);
9596 expect(text).not.toMatch(/keeps? (itself )?up to date|updates itself|automatically/i);
9697 }));
9798

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs