@xfey/tutti 0.1.10 → 0.1.12

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 (57) hide show
  1. package/dist/control-plane/context-sync.js +1 -0
  2. package/dist/control-plane/event-publishers.js +21 -12
  3. package/dist/control-plane/execution-status.js +4 -0
  4. package/dist/control-plane/project-context-bootstrap.d.ts +2 -0
  5. package/dist/control-plane/project-context-bootstrap.js +1 -0
  6. package/dist/control-plane/reference-summary-refresh.js +4 -0
  7. package/dist/control-plane/run-start.js +1 -0
  8. package/dist/control-plane/task-compile-continuation.js +1 -0
  9. package/dist/control-plane/task-compile-start.d.ts +2 -1
  10. package/dist/control-plane/task-compile-start.js +5 -0
  11. package/dist/control-plane/types.d.ts +2 -1
  12. package/dist/control-plane/workflows/openai.js +19 -0
  13. package/dist/control-plane/workflows/types.d.ts +6 -1
  14. package/dist/procedure-engine/index.d.ts +10 -1
  15. package/dist/procedure-engine/index.js +34 -4
  16. package/dist/providers/openai/app-server/json-rpc.d.ts +6 -1
  17. package/dist/providers/openai/app-server/json-rpc.js +15 -1
  18. package/dist/providers/openai/app-server/read-only-procedure.d.ts +2 -0
  19. package/dist/providers/openai/app-server/read-only-procedure.js +13 -1
  20. package/dist/providers/openai/app-server/runtime-telemetry.d.ts +18 -0
  21. package/dist/providers/openai/app-server/runtime-telemetry.js +130 -0
  22. package/dist/providers/openai/app-server/workspace-write-run.d.ts +2 -0
  23. package/dist/providers/openai/app-server/workspace-write-run.js +13 -1
  24. package/dist/run-engine/index.d.ts +2 -1
  25. package/dist/run-engine/index.js +14 -1
  26. package/dist/run-pipeline/task-run-invocation.js +9 -0
  27. package/dist/server-shell/cli/args.d.ts +9 -9
  28. package/dist/server-shell/cli/args.js +31 -31
  29. package/dist/server-shell/cli/cli.js +27 -27
  30. package/dist/server-shell/cli/errors.d.ts +2 -1
  31. package/dist/server-shell/cli/errors.js +55 -12
  32. package/dist/server-shell/cli/host-lifecycle.js +7 -0
  33. package/dist/server-shell/cli/launch-command.d.ts +2 -2
  34. package/dist/server-shell/cli/launch-command.js +25 -11
  35. package/dist/server-shell/cli/launch.js +0 -3
  36. package/dist/server-shell/cli/managed-host.d.ts +2 -0
  37. package/dist/server-shell/cli/managed-host.js +9 -18
  38. package/dist/server-shell/cli/project-resolver.d.ts +6 -0
  39. package/dist/server-shell/cli/project-resolver.js +125 -3
  40. package/dist/server-shell/cli/provider-tui.d.ts +19 -0
  41. package/dist/server-shell/cli/provider-tui.js +64 -38
  42. package/dist/server-shell/cli/runtime-commands.d.ts +5 -5
  43. package/dist/server-shell/cli/runtime-commands.js +68 -25
  44. package/dist/server-shell/cli/terminal-qr.d.ts +5 -0
  45. package/dist/server-shell/cli/terminal-qr.js +17 -0
  46. package/dist/server-shell/http/routes/project-api/openapi-execution-routes.d.ts +14 -0
  47. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +14 -0
  48. package/dist/server-shell/http/routes/project-api/openapi.d.ts +28 -0
  49. package/dist/server-shell/http/routes/project-api/schemas.d.ts +14 -0
  50. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +50 -0
  51. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.js +25 -0
  52. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +4 -1
  53. package/package.json +1 -1
  54. package/web/assets/index-CDUT1Etl.js +29 -0
  55. package/web/assets/{index-D8xJY8Gr.css → index-DIQpGeKY.css} +1 -1
  56. package/web/index.html +2 -2
  57. package/web/assets/index-BMMQgQjz.js +0 -29
@@ -1,6 +1,7 @@
1
1
  import type { ActivityRef, TaskId } from "@tutti/shared/ids";
2
2
  import type { CodexAppServerApprovalPolicy } from "../codex-app-server.js";
3
3
  import { type CodexAppServerJsonRpcError, type CodexAppServerJsonRpcServerRequest } from "./json-rpc.js";
4
+ import { type CodexAppServerRuntimeTelemetryOptions } from "./runtime-telemetry.js";
4
5
  import { type CodexAppServerAgentContextRuntime, type CodexAppServerSkillSelectionName } from "./skills.js";
5
6
  import { type OpenAiTokenUsage } from "../token-usage.js";
6
7
  export type CodexAppServerApprovalsReviewer = "user" | "auto_review" | "guardian_subagent";
@@ -53,6 +54,7 @@ export type CodexAppServerWorkspaceWriteRunInput<TOutput> = {
53
54
  requestTimeoutMs?: number;
54
55
  turnTimeoutMs?: number;
55
56
  baseEnv?: NodeJS.ProcessEnv;
57
+ runtimeTelemetry?: CodexAppServerRuntimeTelemetryOptions;
56
58
  };
57
59
  export type CodexAppServerWorkspaceWriteRunResult<TOutput> = {
58
60
  output: TOutput;
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { buildCodexAppServerProcessPlan } from "../codex-app-server.js";
6
6
  import { DEFAULT_OPENAI_MODEL } from "../model-config.js";
7
7
  import { CodexAppServerJsonRpcClient, CodexAppServerProtocolError, } from "./json-rpc.js";
8
+ import { createCodexAppServerRuntimeObserver, } from "./runtime-telemetry.js";
8
9
  import { createWorkspaceWriteSandboxPolicy } from "./sandbox-policy.js";
9
10
  import { buildCodexAppServerProviderConfigOverrides, resolveCodexAppServerModelProvider, } from "./provider-config.js";
10
11
  import { buildCodexAppServerTurnInput, resolveCodexAppServerContextRuntimeHint, resolveCodexAppServerSkillsListWarmup, resolveCodexAppServerSkillInputs, CodexAppServerSkillSelectionError, } from "./skills.js";
@@ -201,7 +202,17 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
201
202
  env: createCodexSpawnEnv(codexHome, options.baseEnv ?? process.env),
202
203
  stdio: "pipe",
203
204
  });
204
- const client = new CodexAppServerJsonRpcClient(child);
205
+ const runtimeObserver = options.runtimeTelemetry === undefined
206
+ ? undefined
207
+ : createCodexAppServerRuntimeObserver(options.runtimeTelemetry);
208
+ const client = new CodexAppServerJsonRpcClient(child, {
209
+ ...(runtimeObserver === undefined
210
+ ? {}
211
+ : {
212
+ onNotification: runtimeObserver.notification,
213
+ onServerRequest: runtimeObserver.serverRequest,
214
+ }),
215
+ });
205
216
  const sensitiveValues = [
206
217
  options.apiKey,
207
218
  tempRoot,
@@ -370,6 +381,7 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
370
381
  throw new CodexAppServerWorkspaceWriteRunError("app_server_protocol_error", redactWithKnownValues(message, sensitiveValues));
371
382
  }
372
383
  finally {
384
+ runtimeObserver?.flush();
373
385
  client.dispose();
374
386
  rmSync(tempRoot, { recursive: true, force: true });
375
387
  }
@@ -1,6 +1,6 @@
1
1
  import { type ActivityRef, type TaskId } from "@tutti/shared/ids";
2
2
  import type { RunLineage } from "@tutti/shared/domain";
3
- import type { ActivityProjection, ExecutionStatusProjection, TaskProjection } from "@tutti/shared/schemas/api";
3
+ import type { ActivityProjection, ExecutionRuntimeSnapshot, ExecutionStatusProjection, TaskProjection } from "@tutti/shared/schemas/api";
4
4
  export type RunTerminalConclusion = {
5
5
  kind: "completed";
6
6
  summary: string;
@@ -20,6 +20,7 @@ export type RunExecutionContext = {
20
20
  export type RunProgressUpdate = {
21
21
  summary: string;
22
22
  phase?: string;
23
+ runtime?: ExecutionRuntimeSnapshot;
23
24
  };
24
25
  export type StartRunOptions = {
25
26
  activity_ref?: ActivityRef;
@@ -57,7 +57,7 @@ export class RunEngine {
57
57
  }
58
58
  getExecutionStatus() {
59
59
  if (this.active !== null) {
60
- return {
60
+ const status = {
61
61
  status: "running",
62
62
  summary: this.active.activity.summary,
63
63
  active_task: this.active.task,
@@ -70,6 +70,10 @@ export class RunEngine {
70
70
  },
71
71
  updated_at: this.now().toISOString(),
72
72
  };
73
+ if (this.active.runtime !== undefined) {
74
+ status.runtime = this.active.runtime;
75
+ }
76
+ return status;
73
77
  }
74
78
  const idle = {
75
79
  status: "idle",
@@ -106,6 +110,15 @@ export class RunEngine {
106
110
  return;
107
111
  }
108
112
  active.activity.summary = progress.summary;
113
+ if (progress.runtime !== undefined) {
114
+ active.runtime = progress.runtime;
115
+ }
116
+ else if (progress.phase !== undefined && active.runtime !== undefined) {
117
+ active.runtime = {
118
+ ...active.runtime,
119
+ stage_id: progress.phase,
120
+ };
121
+ }
109
122
  onTransition?.(this.state("progress", progress));
110
123
  }
111
124
  finishActive(activityRef, conclusion, onTransition) {
@@ -109,6 +109,15 @@ export async function runWorkspaceWriteTask(options, input, handle) {
109
109
  : { requestTimeoutMs: options.requestTimeoutMs }),
110
110
  ...(options.turnTimeoutMs === undefined ? {} : { turnTimeoutMs: options.turnTimeoutMs }),
111
111
  ...(options.baseEnv === undefined ? {} : { baseEnv: options.baseEnv }),
112
+ runtimeTelemetry: {
113
+ activityRef: input.activity_ref,
114
+ stageId: "provider_task_run",
115
+ onSnapshot: (snapshot) => input.on_stage?.({
116
+ stage: "provider_task_run",
117
+ summary: "running Codex task implementation.",
118
+ runtime: snapshot,
119
+ }),
120
+ },
112
121
  });
113
122
  }
114
123
  finally {
@@ -5,7 +5,7 @@ export type CliCommand = {
5
5
  kind: "version";
6
6
  } | {
7
7
  kind: "launch";
8
- workspacePath: string;
8
+ target?: string;
9
9
  yes: boolean;
10
10
  foreground: boolean;
11
11
  } | {
@@ -17,29 +17,29 @@ export type CliCommand = {
17
17
  manage: boolean;
18
18
  } | {
19
19
  kind: "stop";
20
- workspacePath?: string;
20
+ target?: string;
21
21
  } | {
22
22
  kind: "invite";
23
- workspacePath?: string;
23
+ target?: string;
24
24
  } | {
25
25
  kind: "logs";
26
- workspacePath?: string;
26
+ target?: string;
27
27
  tail: number;
28
28
  } | {
29
29
  kind: "provider-setup";
30
- workspacePath?: string;
30
+ target?: string;
31
31
  } | {
32
32
  kind: "provider-status";
33
- workspacePath?: string;
33
+ target?: string;
34
34
  } | {
35
35
  kind: "doctor";
36
- workspacePath?: string;
36
+ target?: string;
37
37
  };
38
38
  export type LaunchCliArgs = {
39
39
  command: "launch";
40
- workspacePath: string;
40
+ target?: string;
41
41
  yes: boolean;
42
42
  };
43
- export declare function parseLaunchCliArgs(argv: readonly string[], cwd?: string): LaunchCliArgs;
43
+ export declare function parseLaunchCliArgs(argv: readonly string[]): LaunchCliArgs;
44
44
  export declare function parseCliArgs(argv: readonly string[], cwd?: string): CliCommand;
45
45
  //# sourceMappingURL=args.d.ts.map
@@ -6,13 +6,13 @@ function isHelpArg(arg) {
6
6
  function hasHelpArg(args) {
7
7
  return args.some((arg) => isHelpArg(arg));
8
8
  }
9
- export function parseLaunchCliArgs(argv, cwd = process.cwd()) {
9
+ export function parseLaunchCliArgs(argv) {
10
10
  const args = [...argv];
11
11
  const command = args.shift();
12
12
  if (command !== "launch") {
13
- throw new LaunchError("git_bootstrap_failed", "Expected command: launch", "Run `tutti launch [workspace_path] [--yes]`.");
13
+ throw new LaunchError("git_bootstrap_failed", "Expected command: launch", "Run `tutti launch [target] [--yes]`.");
14
14
  }
15
- let workspacePath;
15
+ let target;
16
16
  let yes = false;
17
17
  for (const arg of args) {
18
18
  if (arg === "--yes") {
@@ -22,29 +22,29 @@ export function parseLaunchCliArgs(argv, cwd = process.cwd()) {
22
22
  if (arg.startsWith("--")) {
23
23
  throw new LaunchError("git_bootstrap_failed", `Unsupported option: ${arg}`, "Use only `--yes` with `tutti launch` in the MVP command surface.");
24
24
  }
25
- if (workspacePath !== undefined) {
26
- throw new LaunchError("git_bootstrap_failed", "Only one workspace_path argument is supported", "Run `tutti launch [workspace_path] [--yes]`.");
25
+ if (target !== undefined) {
26
+ throw new LaunchError("git_bootstrap_failed", "Only one target argument is supported", "Run `tutti launch [target] [--yes]`.");
27
27
  }
28
- workspacePath = arg;
28
+ target = arg;
29
29
  }
30
30
  return {
31
31
  command: "launch",
32
- workspacePath: resolve(cwd, workspacePath ?? "."),
32
+ ...(target === undefined ? {} : { target }),
33
33
  yes,
34
34
  };
35
35
  }
36
- function readOptionalWorkspace(args, cwd) {
37
- let workspacePath;
36
+ function readOptionalTarget(args) {
37
+ let target;
38
38
  for (const arg of args) {
39
39
  if (arg.startsWith("--")) {
40
40
  throw new LaunchError("git_bootstrap_failed", `Unsupported option: ${arg}`, "Run `tutti --help` to see supported commands.");
41
41
  }
42
- if (workspacePath !== undefined) {
43
- throw new LaunchError("git_bootstrap_failed", "Only one workspace_path argument is supported", "Run `tutti --help` to see supported commands.");
42
+ if (target !== undefined) {
43
+ throw new LaunchError("git_bootstrap_failed", "Only one target argument is supported", "Run `tutti --help` to see supported commands.");
44
44
  }
45
- workspacePath = resolve(cwd, arg);
45
+ target = arg;
46
46
  }
47
- return workspacePath;
47
+ return target;
48
48
  }
49
49
  export function parseCliArgs(argv, cwd = process.cwd()) {
50
50
  const args = [...argv];
@@ -59,7 +59,7 @@ export function parseCliArgs(argv, cwd = process.cwd()) {
59
59
  return { kind: "version" };
60
60
  }
61
61
  if (command === "launch") {
62
- let workspacePath;
62
+ let target;
63
63
  let yes = false;
64
64
  let foreground = false;
65
65
  for (const arg of args) {
@@ -77,12 +77,12 @@ export function parseCliArgs(argv, cwd = process.cwd()) {
77
77
  if (arg.startsWith("--")) {
78
78
  throw new LaunchError("git_bootstrap_failed", `Unsupported option: ${arg}`, "Use `tutti launch --help` to see supported launch options.");
79
79
  }
80
- if (workspacePath !== undefined) {
81
- throw new LaunchError("git_bootstrap_failed", "Only one workspace_path argument is supported", "Run `tutti launch [workspace_path] [--yes] [--foreground]`.");
80
+ if (target !== undefined) {
81
+ throw new LaunchError("git_bootstrap_failed", "Only one target argument is supported", "Run `tutti launch [target] [--yes] [--foreground]`.");
82
82
  }
83
- workspacePath = resolve(cwd, arg);
83
+ target = arg;
84
84
  }
85
- return { kind: "launch", workspacePath: workspacePath ?? resolve(cwd, "."), yes, foreground };
85
+ return { kind: "launch", ...(target === undefined ? {} : { target }), yes, foreground };
86
86
  }
87
87
  if (command === "internal") {
88
88
  const internalCommand = args.shift();
@@ -131,15 +131,15 @@ export function parseCliArgs(argv, cwd = process.cwd()) {
131
131
  if (hasHelpArg(args)) {
132
132
  return { kind: "help", topic: "stop" };
133
133
  }
134
- const workspacePath = readOptionalWorkspace(args, cwd);
135
- return { kind: "stop", ...(workspacePath === undefined ? {} : { workspacePath }) };
134
+ const target = readOptionalTarget(args);
135
+ return { kind: "stop", ...(target === undefined ? {} : { target }) };
136
136
  }
137
137
  if (command === "invite") {
138
138
  if (hasHelpArg(args)) {
139
139
  return { kind: "help", topic: "invite" };
140
140
  }
141
- const workspacePath = readOptionalWorkspace(args, cwd);
142
- return { kind: "invite", ...(workspacePath === undefined ? {} : { workspacePath }) };
141
+ const target = readOptionalTarget(args);
142
+ return { kind: "invite", ...(target === undefined ? {} : { target }) };
143
143
  }
144
144
  if (command === "logs") {
145
145
  if (hasHelpArg(args)) {
@@ -153,7 +153,7 @@ export function parseCliArgs(argv, cwd = process.cwd()) {
153
153
  const value = args[index + 1];
154
154
  const parsed = Number(value);
155
155
  if (!Number.isInteger(parsed) || parsed <= 0) {
156
- throw new LaunchError("git_bootstrap_failed", "--tail requires a positive integer", "Run `tutti logs [workspace_path] --tail 120`.");
156
+ throw new LaunchError("git_bootstrap_failed", "--tail requires a positive integer", "Run `tutti logs [target] --tail 120`.");
157
157
  }
158
158
  tail = parsed;
159
159
  index += 1;
@@ -162,8 +162,8 @@ export function parseCliArgs(argv, cwd = process.cwd()) {
162
162
  positional.push(arg ?? "");
163
163
  }
164
164
  }
165
- const workspacePath = readOptionalWorkspace(positional, cwd);
166
- return { kind: "logs", tail, ...(workspacePath === undefined ? {} : { workspacePath }) };
165
+ const target = readOptionalTarget(positional);
166
+ return { kind: "logs", tail, ...(target === undefined ? {} : { target }) };
167
167
  }
168
168
  if (command === "provider") {
169
169
  const providerCommand = args.shift();
@@ -174,15 +174,15 @@ export function parseCliArgs(argv, cwd = process.cwd()) {
174
174
  if (hasHelpArg(args)) {
175
175
  return { kind: "help", topic: "provider" };
176
176
  }
177
- const workspacePath = readOptionalWorkspace(args, cwd);
178
- return { kind: "provider-setup", ...(workspacePath === undefined ? {} : { workspacePath }) };
177
+ const target = readOptionalTarget(args);
178
+ return { kind: "provider-setup", ...(target === undefined ? {} : { target }) };
179
179
  }
180
180
  if (providerCommand === "status") {
181
181
  if (hasHelpArg(args)) {
182
182
  return { kind: "help", topic: "provider" };
183
183
  }
184
- const workspacePath = readOptionalWorkspace(args, cwd);
185
- return { kind: "provider-status", ...(workspacePath === undefined ? {} : { workspacePath }) };
184
+ const target = readOptionalTarget(args);
185
+ return { kind: "provider-status", ...(target === undefined ? {} : { target }) };
186
186
  }
187
187
  return { kind: "help", topic: "provider" };
188
188
  }
@@ -190,8 +190,8 @@ export function parseCliArgs(argv, cwd = process.cwd()) {
190
190
  if (hasHelpArg(args)) {
191
191
  return { kind: "help", topic: "doctor" };
192
192
  }
193
- const workspacePath = readOptionalWorkspace(args, cwd);
194
- return { kind: "doctor", ...(workspacePath === undefined ? {} : { workspacePath }) };
193
+ const target = readOptionalTarget(args);
194
+ return { kind: "doctor", ...(target === undefined ? {} : { target }) };
195
195
  }
196
196
  throw new LaunchError("git_bootstrap_failed", `Unsupported command: ${command}`, "Run `tutti --help` to see supported commands.");
197
197
  }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { formatCliError } from "./errors.js";
2
+ import { formatCliError, formatCliErrorReason } from "./errors.js";
3
3
  import { parseCliArgs } from "./args.js";
4
4
  import { runBackgroundLaunchCommand, runForegroundLaunchCommand, runInternalHostRunCommand, } from "./launch-command.js";
5
5
  import { resolveExistingProjectContext } from "./project-resolver.js";
@@ -7,14 +7,14 @@ import { runProviderSetupTui } from "./provider-tui.js";
7
7
  import { runInviteCommand, runLogsCommand, runProviderStatusCommand, runPsManageCommand, runPsCommand, runStopCommand, } from "./runtime-commands.js";
8
8
  import { readCliVersion } from "./version.js";
9
9
  const HELP_TEXT = `Usage:
10
- tutti launch [workspace_path] [--yes] [--foreground]
10
+ tutti launch [target] [--yes] [--foreground]
11
11
  tutti ps [--manage]
12
- tutti invite [workspace_path]
13
- tutti stop [workspace_path]
14
- tutti logs [workspace_path] [--tail 120]
15
- tutti provider setup [workspace_path]
16
- tutti provider status [workspace_path]
17
- tutti doctor [workspace_path]
12
+ tutti invite [target]
13
+ tutti stop [target]
14
+ tutti logs [target] [--tail 120]
15
+ tutti provider setup [target]
16
+ tutti provider status [target]
17
+ tutti doctor [target]
18
18
 
19
19
  Options:
20
20
  -h, --help Show help.
@@ -25,7 +25,7 @@ Notes:
25
25
  provider setup stores credentials in the machine-local Tutti provider store.
26
26
  `;
27
27
  const LAUNCH_HELP_TEXT = `Usage:
28
- tutti launch [workspace_path] [--yes] [--foreground]
28
+ tutti launch [target] [--yes] [--foreground]
29
29
 
30
30
  Starts or reconnects a Tutti project host.
31
31
 
@@ -34,8 +34,8 @@ Options:
34
34
  --foreground Keep the host attached to this terminal for diagnostics.
35
35
  `;
36
36
  const PROVIDER_HELP_TEXT = `Usage:
37
- tutti provider setup [workspace_path]
38
- tutti provider status [workspace_path]
37
+ tutti provider setup [target]
38
+ tutti provider status [target]
39
39
 
40
40
  Commands:
41
41
  setup Configure or replace the OpenAI-compatible provider credential.
@@ -48,22 +48,22 @@ Options:
48
48
  --manage Open the keyboard project manager.
49
49
  `;
50
50
  const INVITE_HELP_TEXT = `Usage:
51
- tutti invite [workspace_path]
51
+ tutti invite [target]
52
52
 
53
53
  Rotates a fresh join link and renders it as a terminal QR code.
54
54
  `;
55
55
  const STOP_HELP_TEXT = `Usage:
56
- tutti stop [workspace_path]
56
+ tutti stop [target]
57
57
 
58
58
  Stops the background Tutti host for a project.
59
59
  `;
60
60
  const LOGS_HELP_TEXT = `Usage:
61
- tutti logs [workspace_path] [--tail 120]
61
+ tutti logs [target] [--tail 120]
62
62
 
63
63
  Shows recent host log lines from the machine-local Tutti log file.
64
64
  `;
65
65
  const DOCTOR_HELP_TEXT = `Usage:
66
- tutti doctor [workspace_path]
66
+ tutti doctor [target]
67
67
 
68
68
  Prints local runtime, project, provider, and host diagnostics.
69
69
  `;
@@ -91,9 +91,9 @@ function helpForTopic(topic) {
91
91
  }
92
92
  return HELP_TEXT;
93
93
  }
94
- async function runProviderSetupCommand(workspacePath) {
94
+ async function runProviderSetupCommand(target) {
95
95
  const project = resolveExistingProjectContext({
96
- ...(workspacePath === undefined ? {} : { workspacePath }),
96
+ ...(target === undefined ? {} : { target }),
97
97
  });
98
98
  const result = await runProviderSetupTui({
99
99
  tuttiHome: project.tutti_home,
@@ -102,20 +102,19 @@ async function runProviderSetupCommand(workspacePath) {
102
102
  process.stdout.write([
103
103
  "",
104
104
  "Provider configured.",
105
- `Base URL: ${result.base_url}`,
106
105
  `Model: ${result.default_model}`,
107
106
  `Key: ${result.redacted_key}`,
108
107
  `Validated: ${result.validated_at}`,
109
108
  ].join("\n"));
110
109
  process.stdout.write("\n");
111
110
  }
112
- function runDoctorCommand(workspacePath) {
111
+ function runDoctorCommand(target) {
113
112
  const checks = [];
114
113
  checks.push(`Node: ${process.version}`);
115
114
  checks.push(`Tutti: ${readCliVersion()}`);
116
115
  try {
117
116
  const project = resolveExistingProjectContext({
118
- ...(workspacePath === undefined ? {} : { workspacePath }),
117
+ ...(target === undefined ? {} : { target }),
119
118
  });
120
119
  checks.push(`Project: ${project.display_name}`);
121
120
  checks.push(`Project ID: ${project.project_id}`);
@@ -123,7 +122,8 @@ function runDoctorCommand(workspacePath) {
123
122
  checks.push(`Host: ${project.runtime_endpoint === null ? "not running" : project.runtime_endpoint.base_url}`);
124
123
  }
125
124
  catch (error) {
126
- checks.push(`Project: unavailable (${error instanceof Error ? error.message : "unknown"})`);
125
+ checks.push("Project: unavailable");
126
+ checks.push(`Project detail: ${formatCliErrorReason(error)}`);
127
127
  }
128
128
  process.stdout.write(`${checks.join("\n")}\n`);
129
129
  }
@@ -156,22 +156,22 @@ try {
156
156
  }
157
157
  break;
158
158
  case "stop":
159
- process.stdout.write(`${await runStopCommand(command.workspacePath)}\n`);
159
+ process.stdout.write(`${await runStopCommand(command.target)}\n`);
160
160
  break;
161
161
  case "invite":
162
- process.stdout.write(`${await runInviteCommand(command.workspacePath)}\n`);
162
+ process.stdout.write(`${await runInviteCommand(command.target)}\n`);
163
163
  break;
164
164
  case "logs":
165
- process.stdout.write(`${runLogsCommand(command.workspacePath, command.tail)}\n`);
165
+ process.stdout.write(`${runLogsCommand(command.target, command.tail)}\n`);
166
166
  break;
167
167
  case "provider-setup":
168
- await runProviderSetupCommand(command.workspacePath);
168
+ await runProviderSetupCommand(command.target);
169
169
  break;
170
170
  case "provider-status":
171
- process.stdout.write(`${runProviderStatusCommand(command.workspacePath)}\n`);
171
+ process.stdout.write(`${runProviderStatusCommand(command.target)}\n`);
172
172
  break;
173
173
  case "doctor":
174
- runDoctorCommand(command.workspacePath);
174
+ runDoctorCommand(command.target);
175
175
  break;
176
176
  }
177
177
  }
@@ -1,4 +1,4 @@
1
- export type LaunchErrorCode = "unsafe_project_root" | "unsupported_tutti_schema" | "project_identity_conflict" | "git_bootstrap_failed" | "sensitive_file_detected" | "relay_unavailable" | "relay_registration_failed" | "provider_configuration_required" | "provider_setup_cancelled" | "provider_validation_failed" | "store_migration_failed" | "existing_server_unhealthy" | "takeover_rejected" | "takeover_failed" | "port_unavailable";
1
+ export type LaunchErrorCode = "unsafe_project_root" | "unsupported_tutti_schema" | "project_identity_conflict" | "git_bootstrap_failed" | "sensitive_file_detected" | "relay_unavailable" | "relay_registration_failed" | "host_not_running" | "project_target_ambiguous" | "project_target_not_found" | "provider_configuration_required" | "provider_setup_cancelled" | "provider_validation_failed" | "store_migration_failed" | "existing_server_unhealthy" | "takeover_rejected" | "takeover_failed" | "port_unavailable";
2
2
  export declare class LaunchError extends Error {
3
3
  readonly code: LaunchErrorCode;
4
4
  readonly next: string;
@@ -7,4 +7,5 @@ export declare class LaunchError extends Error {
7
7
  }
8
8
  export declare function formatLaunchError(error: unknown): string;
9
9
  export declare function formatCliError(error: unknown): string;
10
+ export declare function formatCliErrorReason(error: unknown): string;
10
11
  //# sourceMappingURL=errors.d.ts.map
@@ -1,4 +1,4 @@
1
- import { redactError, redactText } from "@tutti/shared/utils";
1
+ import { redactAndTruncateText, redactError, redactText } from "@tutti/shared/utils";
2
2
  export class LaunchError extends Error {
3
3
  code;
4
4
  next;
@@ -20,24 +20,22 @@ export function formatLaunchError(error) {
20
20
  "Tutti launch failed",
21
21
  "",
22
22
  `Code: ${error.code}`,
23
- `Reason: ${redactText(error.message)}`,
23
+ `Reason: ${formatBoundedText(error.message)}`,
24
24
  ];
25
25
  const detailLines = Object.entries(error.details)
26
26
  .filter(([, value]) => value !== undefined)
27
- .map(([key, value]) => `Detail ${key}: ${redactText(formatDetailValue(value))}`);
27
+ .map(([key, value]) => `Detail ${key}: ${formatBoundedDetail(value)}`);
28
28
  if (detailLines.length > 0) {
29
29
  lines.push(...detailLines);
30
30
  }
31
31
  lines.push(`Next: ${redactText(error.next)}`);
32
32
  return lines.join("\n");
33
33
  }
34
- const redactedError = redactError(error);
35
- const reason = typeof redactedError === "string" ? redactedError : JSON.stringify(redactedError);
36
34
  return [
37
35
  "Tutti launch failed",
38
36
  "",
39
37
  "Code: internal_error",
40
- `Reason: ${reason ?? "Unknown error"}`,
38
+ `Reason: ${formatUnknownErrorReason(error)}`,
41
39
  "Next: Retry after checking the local debug logs.",
42
40
  ].join("\n");
43
41
  }
@@ -46,26 +44,71 @@ export function formatCliError(error) {
46
44
  if (error.code === "provider_setup_cancelled") {
47
45
  return ["Tutti command cancelled", "", `Reason: ${redactText(error.message)}`].join("\n");
48
46
  }
49
- const lines = ["Tutti command failed", "", `Code: ${error.code}`, `Reason: ${redactText(error.message)}`];
47
+ const lines = ["Tutti command failed", "", `Code: ${error.code}`, `Reason: ${formatBoundedText(error.message)}`];
50
48
  const detailLines = Object.entries(error.details)
51
49
  .filter(([, value]) => value !== undefined)
52
- .map(([key, value]) => `Detail ${key}: ${redactText(formatDetailValue(value))}`);
50
+ .map(([key, value]) => `Detail ${key}: ${formatBoundedDetail(value)}`);
53
51
  if (detailLines.length > 0) {
54
52
  lines.push(...detailLines);
55
53
  }
56
54
  lines.push(`Next: ${redactText(error.next)}`);
57
55
  return lines.join("\n");
58
56
  }
59
- const redactedError = redactError(error);
60
- const reason = typeof redactedError === "string" ? redactedError : JSON.stringify(redactedError);
61
57
  return [
62
58
  "Tutti command failed",
63
59
  "",
64
60
  "Code: internal_error",
65
- `Reason: ${reason ?? "Unknown error"}`,
61
+ `Reason: ${formatUnknownErrorReason(error)}`,
66
62
  "Next: Retry after checking the local debug logs.",
67
63
  ].join("\n");
68
64
  }
65
+ const MAX_REASON_LENGTH = 320;
66
+ const MAX_DETAIL_LENGTH = 720;
67
+ function formatBoundedText(value) {
68
+ return redactAndTruncateText(value, MAX_REASON_LENGTH);
69
+ }
70
+ function formatBoundedDetail(value) {
71
+ return redactAndTruncateText(formatDetailValue(value), MAX_DETAIL_LENGTH);
72
+ }
73
+ function isRecord(value) {
74
+ return typeof value === "object" && value !== null && !Array.isArray(value);
75
+ }
76
+ export function formatCliErrorReason(error) {
77
+ const redactedError = redactError(error);
78
+ if (typeof redactedError === "string" && redactedError.trim() !== "") {
79
+ return formatBoundedText(redactedError);
80
+ }
81
+ if (isRecord(redactedError)) {
82
+ const message = redactedError.message;
83
+ if (typeof message === "string" && message.trim() !== "") {
84
+ return formatBoundedText(message);
85
+ }
86
+ const name = redactedError.name;
87
+ if (typeof name === "string" && name.trim() !== "") {
88
+ return formatBoundedText(name);
89
+ }
90
+ }
91
+ return "Unexpected error.";
92
+ }
93
+ function formatUnknownErrorReason(error) {
94
+ return formatCliErrorReason(error);
95
+ }
96
+ function stripStackFields(value) {
97
+ if (Array.isArray(value)) {
98
+ return value.map((entry) => stripStackFields(entry));
99
+ }
100
+ if (isRecord(value)) {
101
+ const stripped = {};
102
+ for (const [key, nested] of Object.entries(value)) {
103
+ if (key.toLowerCase() === "stack") {
104
+ continue;
105
+ }
106
+ stripped[key] = stripStackFields(nested);
107
+ }
108
+ return stripped;
109
+ }
110
+ return value;
111
+ }
69
112
  function formatDetailValue(value) {
70
113
  if (Array.isArray(value)) {
71
114
  return value.map((entry) => formatDetailValue(entry)).join(", ");
@@ -76,6 +119,6 @@ function formatDetailValue(value) {
76
119
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
77
120
  return String(value);
78
121
  }
79
- return JSON.stringify(value) ?? "undefined";
122
+ return JSON.stringify(stripStackFields(value)) ?? "undefined";
80
123
  }
81
124
  //# sourceMappingURL=errors.js.map
@@ -10,6 +10,7 @@ import { HOST_SERVER_VERSION, startForegroundHostServer, } from "./host-server-r
10
10
  import { deleteMachineRuntimeEndpoint, readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "./machine-local.js";
11
11
  import { prepareLaunchProject, resolveLaunchLocalContext, } from "./launch.js";
12
12
  import { registerHostWithRelay, relayErrorToLaunchError, } from "./relay-registration.js";
13
+ import { formatJoinLinkValidity } from "./terminal-qr.js";
13
14
  export { createRuntimeEndpointProbe } from "./host-runtime-endpoint.js";
14
15
  const DEFAULT_SHUTDOWN_WAIT_MS = 2_000;
15
16
  const OSC8_START = "\u001B]8;;";
@@ -277,6 +278,12 @@ export function formatLaunchLifecycleResult(result, options = {}) {
277
278
  }
278
279
  if (summary.relay?.join_url !== undefined) {
279
280
  fields.push(urlLine("Join URL", summary.relay.join_url, options));
281
+ fields.push(formatJoinLinkValidity({
282
+ ...(summary.relay.join_token_expires_at === undefined
283
+ ? {}
284
+ : { expiresAt: summary.relay.join_token_expires_at }),
285
+ reusable: summary.relay.join_token_reusable,
286
+ }));
280
287
  }
281
288
  else if (summary.relay?.join_url_visibility === "not_recoverable") {
282
289
  fields.push("Join URL: not recoverable; rotate a fresh invite from the host.");
@@ -1,7 +1,7 @@
1
1
  import type { LaunchConfirmationRequest } from "./git-bootstrap.js";
2
2
  export declare function confirmFromTty(request: LaunchConfirmationRequest): Promise<boolean>;
3
3
  export declare function runForegroundLaunchCommand(options: {
4
- workspacePath: string;
4
+ target?: string;
5
5
  yes: boolean;
6
6
  }): Promise<void>;
7
7
  export declare function runInternalHostRunCommand(options: {
@@ -9,7 +9,7 @@ export declare function runInternalHostRunCommand(options: {
9
9
  yes: boolean;
10
10
  }): Promise<void>;
11
11
  export declare function runBackgroundLaunchCommand(options: {
12
- workspacePath: string;
12
+ target?: string;
13
13
  yes: boolean;
14
14
  }): Promise<void>;
15
15
  //# sourceMappingURL=launch-command.d.ts.map