toolroll 0.9.88 → 0.9.90

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 (154) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +16 -10
  3. package/THIRD_PARTY_NOTICES.md +37 -0
  4. package/dist/agent-board.d.ts +9 -0
  5. package/dist/agent-board.js +20 -0
  6. package/dist/agent-fence.js +20 -2
  7. package/dist/approval-authority.d.ts +7 -0
  8. package/dist/approval-authority.js +13 -2
  9. package/dist/browser/THIRD_PARTY_NOTICES.txt +692 -28
  10. package/dist/browser/terminal.js +42 -42
  11. package/dist/browser/workspace.css +1 -1
  12. package/dist/browser/workspace.js +30 -30
  13. package/dist/browser-workspace.d.ts +13 -0
  14. package/dist/builder.js +5 -2
  15. package/dist/chat-actions.d.ts +5 -0
  16. package/dist/chat-actions.js +7 -1
  17. package/dist/chat-channel.js +11 -1
  18. package/dist/chat-delivery.js +2 -0
  19. package/dist/chat-task-actions.d.ts +1 -0
  20. package/dist/check-access.d.ts +3 -0
  21. package/dist/check-access.js +4 -1
  22. package/dist/cli-help.d.ts +1 -1
  23. package/dist/cli-help.js +6 -2
  24. package/dist/cli.d.ts +6 -0
  25. package/dist/cli.js +23 -0
  26. package/dist/coding-ui.js +4 -2
  27. package/dist/connect-cli.js +2 -1
  28. package/dist/console-instance.d.ts +34 -1
  29. package/dist/console-instance.js +124 -15
  30. package/dist/contracts/cli.d.ts +72 -0
  31. package/dist/contracts/cli.js +13 -0
  32. package/dist/contracts/console-api.d.ts +9 -2
  33. package/dist/contracts/console-api.js +9 -2
  34. package/dist/desktop-host.js +8 -3
  35. package/dist/desktop-update.js +2 -1
  36. package/dist/dispatch.d.ts +3 -0
  37. package/dist/dispatch.js +22 -2
  38. package/dist/easy-start-args.d.ts +13 -0
  39. package/dist/easy-start-args.js +24 -0
  40. package/dist/easy-start.d.ts +77 -0
  41. package/dist/easy-start.js +321 -0
  42. package/dist/egress.js +5 -0
  43. package/dist/first-run.d.ts +77 -0
  44. package/dist/first-run.js +45 -0
  45. package/dist/flow-starters.js +3 -0
  46. package/dist/flow-triggers.js +4 -0
  47. package/dist/folder-gate.d.ts +32 -0
  48. package/dist/folder-gate.js +43 -0
  49. package/dist/guides.js +1 -1
  50. package/dist/invoke.js +11 -6
  51. package/dist/laptop-background.d.ts +171 -0
  52. package/dist/laptop-background.js +550 -0
  53. package/dist/laptop-service.d.ts +111 -0
  54. package/dist/laptop-service.js +268 -0
  55. package/dist/lead-cli.js +2 -1
  56. package/dist/lead-doors.d.ts +6 -0
  57. package/dist/lead-doors.js +20 -2
  58. package/dist/live-records.d.ts +1 -1
  59. package/dist/live-records.js +3 -12
  60. package/dist/local-owner.d.ts +124 -0
  61. package/dist/local-owner.js +212 -0
  62. package/dist/login-secrets.d.ts +60 -0
  63. package/dist/login-secrets.js +195 -0
  64. package/dist/mcp-oauth.d.ts +4 -0
  65. package/dist/mcp-oauth.js +13 -7
  66. package/dist/model-choices.d.ts +43 -0
  67. package/dist/model-choices.js +80 -0
  68. package/dist/operate.d.ts +46 -1
  69. package/dist/operate.js +461 -40
  70. package/dist/person-proof.js +3 -0
  71. package/dist/project-access.js +15 -1
  72. package/dist/project-admission.d.ts +6 -1
  73. package/dist/project-admission.js +44 -7
  74. package/dist/project-identity.d.ts +8 -0
  75. package/dist/project-identity.js +43 -0
  76. package/dist/project-knowledge.d.ts +7 -7
  77. package/dist/project.d.ts +1 -0
  78. package/dist/project.js +6 -0
  79. package/dist/proposal.js +11 -1
  80. package/dist/quick-start.d.ts +3 -1
  81. package/dist/quick-start.js +6 -1
  82. package/dist/recipes.js +12 -1
  83. package/dist/release-cli.js +2 -1
  84. package/dist/runner-process.d.ts +9 -0
  85. package/dist/runner-process.js +18 -0
  86. package/dist/sample-project.d.ts +31 -0
  87. package/dist/sample-project.js +141 -0
  88. package/dist/scope.d.ts +24 -2
  89. package/dist/scope.js +74 -3
  90. package/dist/serve.js +157 -28
  91. package/dist/server/chat.js +10 -3
  92. package/dist/server/chrome.d.ts +10 -2
  93. package/dist/server/chrome.js +7 -5
  94. package/dist/server/console.css +10 -0
  95. package/dist/server/guards.d.ts +3 -0
  96. package/dist/server/guards.js +22 -2
  97. package/dist/server/http.d.ts +22 -0
  98. package/dist/server/http.js +58 -0
  99. package/dist/server/live.js +4 -14
  100. package/dist/server/mutation-key.d.ts +70 -0
  101. package/dist/server/mutation-key.js +142 -0
  102. package/dist/server/pages.js +12 -9
  103. package/dist/server/people-tokens.js +240 -20
  104. package/dist/server/remote.d.ts +2 -0
  105. package/dist/server/remote.js +19 -4
  106. package/dist/server/render-new-task.js +5 -1
  107. package/dist/server/render-pages.d.ts +2 -1
  108. package/dist/server/render-pages.js +1 -1
  109. package/dist/server/render-people.d.ts +36 -0
  110. package/dist/server/render-people.js +65 -1
  111. package/dist/server/render-projects.js +4 -2
  112. package/dist/server/render-tasks.d.ts +12 -0
  113. package/dist/server/render-tasks.js +16 -6
  114. package/dist/server/request-context.d.ts +6 -0
  115. package/dist/server/route-table.d.ts +16 -0
  116. package/dist/server/route-table.js +121 -0
  117. package/dist/server/runtime.d.ts +6 -4
  118. package/dist/server/session.d.ts +31 -1
  119. package/dist/server/session.js +12 -2
  120. package/dist/server/settings.js +63 -8
  121. package/dist/server/task-views.js +10 -1
  122. package/dist/server/tasks.d.ts +10 -0
  123. package/dist/server/tasks.js +59 -9
  124. package/dist/server/terminals.js +15 -2
  125. package/dist/server/welcome.d.ts +22 -16
  126. package/dist/server/welcome.js +150 -52
  127. package/dist/settings-groups-ui.d.ts +7 -1
  128. package/dist/settings-groups-ui.js +23 -2
  129. package/dist/settings-proofs.js +1 -0
  130. package/dist/setup-guide.d.ts +2 -2
  131. package/dist/setup-guide.js +6 -0
  132. package/dist/store.d.ts +124 -1
  133. package/dist/store.js +245 -12
  134. package/dist/surface.d.ts +10 -0
  135. package/dist/surface.js +40 -1
  136. package/dist/task-text.d.ts +6 -0
  137. package/dist/task-text.js +17 -0
  138. package/dist/teardown-git.js +16 -1
  139. package/dist/telegram-lead.js +2 -0
  140. package/dist/terminal-text.d.ts +11 -0
  141. package/dist/terminal-text.js +26 -0
  142. package/dist/toolroll-update.d.ts +8 -1
  143. package/dist/toolroll-update.js +151 -4
  144. package/dist/tui-contract.d.ts +118 -0
  145. package/dist/tui-contract.js +9 -0
  146. package/dist/tui-source.d.ts +14 -0
  147. package/dist/tui-source.js +182 -0
  148. package/dist/tui.js +129 -0
  149. package/dist/up-processes.d.ts +13 -1
  150. package/dist/up-processes.js +13 -3
  151. package/dist/welcome-facts.d.ts +18 -0
  152. package/dist/welcome-facts.js +79 -0
  153. package/dist/youiyoux-intake.js +11 -1
  154. package/package.json +4 -2
@@ -10,7 +10,12 @@ export type UpConsoleConfig = {
10
10
  projectRoots: string[];
11
11
  registryPath: string;
12
12
  editor: boolean;
13
+ /** Easy-start C4: how `up` itself was authorised at this start ("full": a password verified; "local": local mode's
14
+ * owner; "none": nothing). The console mints a sign-in link only of this kind, whatever a later message asks for. */
15
+ startAuthority: UpStartAuthority;
13
16
  };
17
+ /** How an `up` start was authorised (resolveUpApprover). */
18
+ export type UpStartAuthority = "full" | "local" | "none";
14
19
  export type UpWorkerConfig = {
15
20
  runner: string;
16
21
  token: string;
@@ -57,10 +62,17 @@ export type ParentToRole = {
57
62
  type: "stopping";
58
63
  } | {
59
64
  type: "hard-stop";
60
- } | {
65
+ }
66
+ /** A full link names its account; a local one names none (the console reads local mode's owner itself). */
67
+ | {
61
68
  type: "sign-in-link";
62
69
  id: number;
70
+ kind: "full";
63
71
  account: string;
72
+ } | {
73
+ type: "sign-in-link";
74
+ id: number;
75
+ kind: "local";
64
76
  };
65
77
  export type RoleToParent = {
66
78
  type: "ready";
@@ -31,7 +31,16 @@ export function parentMessage(value) {
31
31
  case "repos": return texts(value["repos"]) ? { type: "repos", repos: [...value["repos"]] } : null;
32
32
  case "stopping": return { type: "stopping" };
33
33
  case "hard-stop": return { type: "hard-stop" };
34
- case "sign-in-link": return whole(value["id"]) && nonEmpty(value["account"], 256) ? { type: "sign-in-link", id: value["id"], account: value["account"] } : null;
34
+ case "sign-in-link": {
35
+ if (!whole(value["id"]))
36
+ return null;
37
+ // Exactly one of two shapes: a kind this build doesn't name, or a local request carrying an account, is no request.
38
+ if (value["kind"] === "full")
39
+ return nonEmpty(value["account"], 256) ? { type: "sign-in-link", id: value["id"], kind: "full", account: value["account"] } : null;
40
+ if (value["kind"] === "local")
41
+ return value["account"] === undefined ? { type: "sign-in-link", id: value["id"], kind: "local" } : null;
42
+ return null;
43
+ }
35
44
  default: return null;
36
45
  }
37
46
  }
@@ -70,11 +79,12 @@ export function startMessage(value) {
70
79
  return null;
71
80
  const base = { type: "start", databaseFile: value["databaseFile"], containment: value["containment"], build: value["build"] };
72
81
  if (value["role"] === "console") {
73
- const keys = ["host", "port", "allowedHosts", "publicUrl", "localRunner", "poolRoot", "repos", "projectRoots", "registryPath", "editor"];
82
+ const keys = ["host", "port", "allowedHosts", "publicUrl", "localRunner", "poolRoot", "repos", "projectRoots", "registryPath", "editor", "startAuthority"];
74
83
  if (Object.keys(config).some(key => !keys.includes(key)))
75
84
  return null;
76
85
  if (!nonEmpty(config["host"], 253) || !whole(config["port"], 0, 65_535) || !orNull(config["allowedHosts"], texts) || !orNull(config["publicUrl"], (one) => nonEmpty(one, 2_048)) ||
77
- !nonEmpty(config["localRunner"], 256) || !nonEmpty(config["poolRoot"], 4_096) || !texts(config["repos"]) || !texts(config["projectRoots"]) || !nonEmpty(config["registryPath"], 4_096) || typeof config["editor"] !== "boolean")
86
+ !nonEmpty(config["localRunner"], 256) || !nonEmpty(config["poolRoot"], 4_096) || !texts(config["repos"]) || !texts(config["projectRoots"]) || !nonEmpty(config["registryPath"], 4_096) || typeof config["editor"] !== "boolean" ||
87
+ !(config["startAuthority"] === "full" || config["startAuthority"] === "local" || config["startAuthority"] === "none"))
78
88
  return null;
79
89
  return { ...base, role: "console", config: config };
80
90
  }
@@ -0,0 +1,18 @@
1
+ import { type WelcomeModel } from "./first-run.js";
2
+ import { type ConnectionChecker } from "./provider-connection.js";
3
+ import type { Store } from "./store.js";
4
+ export type WelcomeHere = {
5
+ model: WelcomeModel;
6
+ projects: readonly string[];
7
+ };
8
+ /** The welcome model for this computer now. `check`: the agent sign-in check (tests pass their own). */
9
+ export declare function welcomeHere(store: Store, databaseFile: string, now: Date, options?: {
10
+ check?: ConnectionChecker;
11
+ platform?: NodeJS.Platform;
12
+ }): Promise<WelcomeHere>;
13
+ export type WelcomeStepKey = "agent" | "project" | "task";
14
+ export declare const WELCOME_STEPS: readonly WelcomeStepKey[];
15
+ /** One step as lines for a terminal: its number and name, Done or what to do next, in plain words. */
16
+ export declare function welcomeStepLines(model: WelcomeModel, key: WelcomeStepKey): string[];
17
+ /** The welcome as lines for a terminal: how many steps are left, then each step. */
18
+ export declare function welcomeLines(model: WelcomeModel): string[];
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The welcome's facts read from this computer, for the screens that are not the console: `toolroll welcome` and the
3
+ * terminal UI's Setup screen. The console's /welcome reads the same facts from its runtime (server/welcome.ts); both
4
+ * go through first-run.ts's one model (welcomeModel), so the three steps say the same thing everywhere.
5
+ *
6
+ * Reads only: the project registry beside the database, the store, and each agent CLI's own sign-in check (no model,
7
+ * nothing spent).
8
+ */
9
+ import { basename, dirname, join } from "node:path";
10
+ import { resolvePhaseAgent } from "./agentconfig.js";
11
+ import { builderReadyFor, firstRunBuilderRepo } from "./dispatch.js";
12
+ import { SAMPLE_FIRST_TASKS, SAMPLE_PROJECT_NAME, GENERIC_FIRST_TASKS, welcomeModel } from "./first-run.js";
13
+ import { createConnectionChecker } from "./provider-connection.js";
14
+ import { loadProjectRegistry } from "./repos.js";
15
+ const AGENT_OF = { "claude-subscription": "claude", "codex-subscription": "codex" };
16
+ /** The welcome model for this computer now. `check`: the agent sign-in check (tests pass their own). */
17
+ export async function welcomeHere(store, databaseFile, now, options = {}) {
18
+ const registry = await loadProjectRegistry(join(dirname(databaseFile), "repos.json"));
19
+ const repos = "error" in registry ? [] : registry.repos;
20
+ const root = "error" in registry ? null : registry.roots[0] ?? null;
21
+ const check = options.check ?? createConnectionChecker();
22
+ const states = await Promise.all(["claude", "codex"].map(one => check(one).then(value => value.state, () => "unverified")));
23
+ const repo = firstRunBuilderRepo(store, null, () => true, repos);
24
+ const sample = root === null ? null : `${root.replace(/\/+$/, "")}/${SAMPLE_PROJECT_NAME}`;
25
+ const build = resolvePhaseAgent(store, "build", repo, {});
26
+ const model = welcomeModel({
27
+ agents: { claude: states[0] ?? null, codex: states[1] ?? null },
28
+ inUse: AGENT_OF[store.getChatConfig()?.provider ?? ""] ?? null,
29
+ buildsOn: build.ok ? build.spec.provider : null,
30
+ // Choosing the agent is the console's act ("Use this one"); these screens show the step and its sign-in command.
31
+ canChoose: false,
32
+ projects: repos.map(path => ({ name: basename(path), path })),
33
+ projectRoot: root,
34
+ ready: builderReadyFor(store, repo, now),
35
+ hasTask: store.hasAnyWork(),
36
+ starters: repo !== null && repo === sample ? SAMPLE_FIRST_TASKS : GENERIC_FIRST_TASKS,
37
+ platform: options.platform ?? process.platform,
38
+ });
39
+ return { model, projects: repos };
40
+ }
41
+ export const WELCOME_STEPS = ["agent", "project", "task"];
42
+ /** One step as lines for a terminal: its number and name, Done or what to do next, in plain words. */
43
+ export function welcomeStepLines(model, key) {
44
+ const mark = (done) => (done ? "Done" : "To do");
45
+ const lines = [];
46
+ if (key === "agent") {
47
+ lines.push(`1. Agent · ${mark(model.agent.done)}`);
48
+ for (const agent of model.agent.agents) {
49
+ const state = agent.state === "signed-in" ? "signed in" : agent.state === "checking" ? "checking" : agent.state === "not-installed" ? "not installed" : "not signed in";
50
+ lines.push(` ${agent.name}: ${state}${agent.inUse ? " (in use)" : ""}`);
51
+ if (agent.command !== null)
52
+ lines.push(` To sign in, run: ${agent.command}`);
53
+ }
54
+ }
55
+ else if (key === "project") {
56
+ lines.push(`2. Project · ${mark(model.project.done)}`);
57
+ for (const one of model.project.projects.slice(0, 3))
58
+ lines.push(` ${one.name} ${one.path}`);
59
+ if (model.project.projects.length === 0)
60
+ lines.push(" Add a git repository: run `toolroll repos add <folder>`, or `toolroll open --web` and choose one.");
61
+ else if (model.project.notTakingWork)
62
+ lines.push(" Tasks can't start yet: nothing on this computer is taking work. Run `toolroll`.");
63
+ }
64
+ else {
65
+ lines.push(`3. First task · ${mark(model.task.done)}`);
66
+ if (model.task.done)
67
+ lines.push(" Filed. Its plan waits for your approval.");
68
+ else if (!model.task.open)
69
+ lines.push(" Add a project first.");
70
+ else
71
+ for (const one of model.task.starters)
72
+ lines.push(` Idea: ${one.draft}`);
73
+ }
74
+ return lines;
75
+ }
76
+ /** The welcome as lines for a terminal: how many steps are left, then each step. */
77
+ export function welcomeLines(model) {
78
+ return [model.left === 0 ? "Setup is done." : `${model.left} ${model.left === 1 ? "step" : "steps"} left.`, "", ...WELCOME_STEPS.flatMap(key => welcomeStepLines(model, key))];
79
+ }
@@ -74,6 +74,7 @@ import { noteAssignmentStatus } from "./assignment-status.js";
74
74
  import { completionProblem } from "./landed-cleanup.js";
75
75
  import { COMPLETION_ACTION, LANDED_ACTOR } from "./result-completion.js";
76
76
  import { optionsProblem, PROPOSAL_ID, proposalFileOf, proposalIdOf, readYouiyouxProposal, readYouiyouxRequest, readYouiyouxRequestTask, YOUIYOUX_CARD_KIND, YOUIYOUX_FILED_VIA, YOUIYOUX_HOME_PATH } from "./contracts/youiyoux-request.js";
77
+ import { FolderUnavailable } from "./folder-gate.js";
77
78
  export { YOUIYOUX_FILED_VIA };
78
79
  /** Who files the tasks: an automation, like a flow trigger. */
79
80
  export const YOUIYOUX_ACTOR = "youiyoux";
@@ -605,7 +606,16 @@ function intake(store, given, now, io, options) {
605
606
  const pass = NOTHING();
606
607
  const none = { pass, repo: null };
607
608
  // The project as Toolroll recorded it, resolved: a folder that isn't one of its projects is never read.
608
- const repo = canonicalProject(given);
609
+ let repo;
610
+ // A folder the console's gate holds back (folder-gate.ts) isn't read this pass.
611
+ try {
612
+ repo = canonicalProject(given);
613
+ }
614
+ catch (error) {
615
+ if (error instanceof FolderUnavailable)
616
+ return none;
617
+ throw error;
618
+ }
609
619
  if (repo === null || !store.listProjects().some(one => one.path === repo))
610
620
  return none;
611
621
  const row = intakeRow(store, repo);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolroll",
3
- "version": "0.9.88",
3
+ "version": "0.9.90",
4
4
  "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
5
  "directories": {
6
6
  "doc": "docs"
@@ -14,7 +14,7 @@
14
14
  "test:git": "vitest run test/attacks/checkout-git- test/attacks/fence-git-views test/attacks/repository-context-git test/attacks/discover-dirty src/teardown-git.test.ts src/worktree.slow.test.ts",
15
15
  "check:quick": "node scripts/quick-check.mjs",
16
16
  "dev": "tsx src/cli.ts",
17
- "typecheck": "tsc --noEmit && tsc -p tsconfig.browser.json",
17
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.browser.json && tsc -p tsconfig.tui.json",
18
18
  "certify:provider": "npm run build && node scripts/provider-canary.mjs",
19
19
  "certify:recovery": "npm run build && node scripts/recovery-canary.mjs",
20
20
  "certify:crash": "npm run build && node scripts/crash-canary.mjs",
@@ -93,6 +93,8 @@
93
93
  "clsx": "2.1.1",
94
94
  "esbuild": "0.28.2",
95
95
  "happy-dom": "20.14.5",
96
+ "ink": "6.8.0",
97
+ "ink-testing-library": "4.0.0",
96
98
  "lucide-react": "1.52.0",
97
99
  "react": "19.3.0",
98
100
  "react-dom": "19.3.0",