@xfey/tutti 0.1.11 → 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 (53) 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 +24 -24
  30. package/dist/server-shell/cli/errors.d.ts +1 -1
  31. package/dist/server-shell/cli/host-lifecycle.js +7 -0
  32. package/dist/server-shell/cli/launch-command.d.ts +2 -2
  33. package/dist/server-shell/cli/launch-command.js +24 -3
  34. package/dist/server-shell/cli/managed-host.d.ts +2 -0
  35. package/dist/server-shell/cli/managed-host.js +6 -0
  36. package/dist/server-shell/cli/project-resolver.d.ts +6 -0
  37. package/dist/server-shell/cli/project-resolver.js +125 -3
  38. package/dist/server-shell/cli/runtime-commands.d.ts +5 -5
  39. package/dist/server-shell/cli/runtime-commands.js +50 -19
  40. package/dist/server-shell/cli/terminal-qr.d.ts +5 -0
  41. package/dist/server-shell/cli/terminal-qr.js +17 -0
  42. package/dist/server-shell/http/routes/project-api/openapi-execution-routes.d.ts +14 -0
  43. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +14 -0
  44. package/dist/server-shell/http/routes/project-api/openapi.d.ts +28 -0
  45. package/dist/server-shell/http/routes/project-api/schemas.d.ts +14 -0
  46. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +50 -0
  47. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.js +25 -0
  48. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +4 -1
  49. package/package.json +1 -1
  50. package/web/assets/index-CDUT1Etl.js +29 -0
  51. package/web/assets/{index-D8xJY8Gr.css → index-DIQpGeKY.css} +1 -1
  52. package/web/index.html +2 -2
  53. 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
  }
@@ -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,
@@ -108,13 +108,13 @@ async function runProviderSetupCommand(workspacePath) {
108
108
  ].join("\n"));
109
109
  process.stdout.write("\n");
110
110
  }
111
- function runDoctorCommand(workspacePath) {
111
+ function runDoctorCommand(target) {
112
112
  const checks = [];
113
113
  checks.push(`Node: ${process.version}`);
114
114
  checks.push(`Tutti: ${readCliVersion()}`);
115
115
  try {
116
116
  const project = resolveExistingProjectContext({
117
- ...(workspacePath === undefined ? {} : { workspacePath }),
117
+ ...(target === undefined ? {} : { target }),
118
118
  });
119
119
  checks.push(`Project: ${project.display_name}`);
120
120
  checks.push(`Project ID: ${project.project_id}`);
@@ -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" | "host_not_running" | "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;
@@ -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
@@ -2,7 +2,8 @@ import { createInterface } from "node:readline/promises";
2
2
  import { formatLaunchLifecycleResult, startLaunchProject, waitForForegroundHostShutdown } from "./host-lifecycle.js";
3
3
  import { prepareLaunchProject } from "./launch.js";
4
4
  import { spawnDetachedHost, waitForManagedHostReady } from "./managed-host.js";
5
- import { renderTerminalQr } from "./terminal-qr.js";
5
+ import { resolveLaunchWorkspacePath } from "./project-resolver.js";
6
+ import { formatJoinLinkValidity, renderTerminalQr } from "./terminal-qr.js";
6
7
  import { renderTuttiTerminalLogo } from "./terminal-logo.js";
7
8
  import { runProviderSetupTui } from "./provider-tui.js";
8
9
  import { LaunchError } from "./errors.js";
@@ -45,6 +46,14 @@ function renderCompletionPage(options) {
45
46
  "Tutti is running in the background.",
46
47
  "",
47
48
  `Join URL: ${options.joinUrl}`,
49
+ formatJoinLinkValidity({
50
+ ...(options.joinTokenExpiresAt === undefined
51
+ ? {}
52
+ : { expiresAt: options.joinTokenExpiresAt }),
53
+ ...(options.joinTokenReusable === undefined
54
+ ? {}
55
+ : { reusable: options.joinTokenReusable }),
56
+ }),
48
57
  "",
49
58
  renderTerminalQr(options.joinUrl),
50
59
  "",
@@ -64,8 +73,11 @@ async function ensureProviderConfigured(preparation) {
64
73
  });
65
74
  }
66
75
  export async function runForegroundLaunchCommand(options) {
76
+ const workspacePath = resolveLaunchWorkspacePath({
77
+ ...(options.target === undefined ? {} : { target: options.target }),
78
+ });
67
79
  const result = await startLaunchProject({
68
- workspacePath: options.workspacePath,
80
+ workspacePath,
69
81
  yes: options.yes,
70
82
  confirm: confirmFromTty,
71
83
  });
@@ -84,8 +96,11 @@ export async function runInternalHostRunCommand(options) {
84
96
  }
85
97
  }
86
98
  export async function runBackgroundLaunchCommand(options) {
99
+ const workspacePath = resolveLaunchWorkspacePath({
100
+ ...(options.target === undefined ? {} : { target: options.target }),
101
+ });
87
102
  const preparation = await prepareLaunchProject({
88
- workspacePath: options.workspacePath,
103
+ workspacePath,
89
104
  yes: options.yes,
90
105
  confirm: confirmFromTty,
91
106
  });
@@ -103,6 +118,12 @@ export async function runBackgroundLaunchCommand(options) {
103
118
  }
104
119
  process.stdout.write(`${renderCompletionPage({
105
120
  joinUrl: ready.join_url,
121
+ ...(ready.join_token_expires_at === undefined
122
+ ? {}
123
+ : { joinTokenExpiresAt: ready.join_token_expires_at }),
124
+ ...(ready.join_token_reusable === undefined
125
+ ? {}
126
+ : { joinTokenReusable: ready.join_token_reusable }),
106
127
  })}\n`);
107
128
  await waitForEnter();
108
129
  }
@@ -4,6 +4,8 @@ import type { ProjectId } from "@tutti/shared/ids";
4
4
  export type ManagedHostReadyResult = {
5
5
  endpoint: MachineRuntimeEndpointRecord;
6
6
  join_url?: string;
7
+ join_token_expires_at?: string;
8
+ join_token_reusable?: boolean;
7
9
  };
8
10
  export declare function spawnDetachedHost(options: {
9
11
  workspaceRoot: string;
@@ -74,6 +74,12 @@ export async function waitForManagedHostReady(options) {
74
74
  return {
75
75
  endpoint,
76
76
  join_url: status.relay.join_url,
77
+ ...(status.relay.join_token_expires_at === undefined
78
+ ? {}
79
+ : { join_token_expires_at: status.relay.join_token_expires_at }),
80
+ ...(status.relay.join_token_reusable === undefined
81
+ ? {}
82
+ : { join_token_reusable: status.relay.join_token_reusable }),
77
83
  };
78
84
  }
79
85
  lastReason = "host is running but Relay invite is not visible yet";
@@ -11,7 +11,13 @@ export type ExistingProjectContext = {
11
11
  provider_config: OpenAiProviderConfigProjection;
12
12
  provider_base_url?: string;
13
13
  };
14
+ export declare function resolveLaunchWorkspacePath(options: {
15
+ target?: string;
16
+ cwd?: string;
17
+ env?: NodeJS.ProcessEnv;
18
+ }): string;
14
19
  export declare function resolveExistingProjectContext(options: {
20
+ target?: string;
15
21
  workspacePath?: string;
16
22
  cwd?: string;
17
23
  env?: NodeJS.ProcessEnv;