@xfey/tutti 0.1.28 → 0.1.30

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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/collaboration-state/index.d.ts +1 -1
  3. package/dist/collaboration-state/index.js +1 -1
  4. package/dist/collaboration-state/messages.d.ts +1 -0
  5. package/dist/collaboration-state/messages.js +12 -0
  6. package/dist/control-plane/index.d.ts +0 -1
  7. package/dist/control-plane/index.js +16 -49
  8. package/dist/control-plane/task-compile-output.js +2 -54
  9. package/dist/control-plane/worklist-terminal-feedback.d.ts +9 -1
  10. package/dist/control-plane/worklist-terminal-feedback.js +99 -1
  11. package/dist/server-shell/cli/args.d.ts +9 -0
  12. package/dist/server-shell/cli/args.js +55 -5
  13. package/dist/server-shell/cli/cli.js +46 -7
  14. package/dist/server-shell/cli/errors.d.ts +1 -1
  15. package/dist/server-shell/cli/errors.js +6 -1
  16. package/dist/server-shell/cli/host-lifecycle.d.ts +1 -0
  17. package/dist/server-shell/cli/host-lifecycle.js +155 -41
  18. package/dist/server-shell/cli/host-relay-connection-manager.d.ts +50 -0
  19. package/dist/server-shell/cli/host-relay-connection-manager.js +447 -0
  20. package/dist/server-shell/cli/host-relay-status.d.ts +4 -0
  21. package/dist/server-shell/cli/host-relay-status.js +74 -15
  22. package/dist/server-shell/cli/host-runtime-endpoint.js +26 -0
  23. package/dist/server-shell/cli/host-server-runtime.d.ts +2 -2
  24. package/dist/server-shell/cli/host-server-runtime.js +19 -15
  25. package/dist/server-shell/cli/launch-command.d.ts +1 -0
  26. package/dist/server-shell/cli/launch-command.js +8 -10
  27. package/dist/server-shell/cli/launch.d.ts +1 -0
  28. package/dist/server-shell/cli/launch.js +7 -3
  29. package/dist/server-shell/cli/local-control-client.d.ts +5 -0
  30. package/dist/server-shell/cli/local-control-client.js +9 -0
  31. package/dist/server-shell/cli/machine-local.d.ts +8 -0
  32. package/dist/server-shell/cli/machine-local.js +29 -0
  33. package/dist/server-shell/cli/machine-project-inspector.d.ts +81 -0
  34. package/dist/server-shell/cli/machine-project-inspector.js +205 -0
  35. package/dist/server-shell/cli/project-resolver.js +22 -32
  36. package/dist/server-shell/cli/relay-registration.d.ts +5 -2
  37. package/dist/server-shell/cli/relay-registration.js +7 -3
  38. package/dist/server-shell/cli/runtime-commands.d.ts +30 -4
  39. package/dist/server-shell/cli/runtime-commands.js +230 -113
  40. package/dist/server-shell/http/routes/local-control.d.ts +10 -0
  41. package/dist/server-shell/http/routes/local-control.js +29 -0
  42. package/migrations/README.md +1 -1
  43. package/node_modules/@tutti/relay-client/dist/host-control.d.ts +23 -0
  44. package/node_modules/@tutti/relay-client/dist/host-control.js +70 -3
  45. package/node_modules/@tutti/relay-client/dist/tunnel-types.d.ts +6 -2
  46. package/node_modules/@tutti/relay-client/dist/tunnel.js +13 -2
  47. package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +21 -0
  48. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
  49. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
  50. package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +105 -0
  51. package/node_modules/@tutti/shared/dist/schemas/api/messages.js +16 -0
  52. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +2 -1
  53. package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +21 -0
  54. package/package.json +1 -1
  55. package/prompts/prompt-flow-map.md +1 -1
  56. package/prompts/runs/README.md +1 -1
  57. package/prompts/runs/task-continuation.md +1 -1
  58. package/prompts/runs/task-retry.md +1 -1
  59. package/prompts/runs/task-run.md +1 -1
  60. package/web/assets/index-BHRlhFwi.css +1 -0
  61. package/web/assets/index-DPU2uqZl.js +29 -0
  62. package/web/index.html +2 -2
  63. package/web/assets/index-CeGTRkQn.css +0 -1
  64. package/web/assets/index-DGnDFv9O.js +0 -29
@@ -1,21 +1,12 @@
1
- import { existsSync, readdirSync } from "node:fs";
2
- import { basename, join, resolve } from "node:path";
1
+ import { existsSync } from "node:fs";
2
+ import { basename, resolve } from "node:path";
3
3
  import { ID_PREFIXES, isPrefixedId } from "@tutti/shared/ids";
4
4
  import { readOpenAiProviderConfigProjection, readProjectOpenAiProviderConfig, resolveTuttiHome, } from "../../providers/openai/index.js";
5
5
  import { LaunchError } from "./errors.js";
6
6
  import { assertSafeProjectRoot } from "./git-bootstrap.js";
7
+ import { inspectMachineProjectInventory, inspectMachineProjectInventories, } from "./machine-project-inspector.js";
7
8
  import { readMachineProjectBinding, readMachineRuntimeEndpoint, } from "./machine-local.js";
8
9
  import { readProjectDisplayName, readProjectIdentity } from "./project-identity.js";
9
- function projectIdsFromTuttiHome(tuttiHome) {
10
- const projectsRoot = join(tuttiHome, "projects");
11
- if (!existsSync(projectsRoot)) {
12
- return [];
13
- }
14
- return readdirSync(projectsRoot, { withFileTypes: true })
15
- .filter((entry) => entry.isDirectory() && isPrefixedId(entry.name, ID_PREFIXES.project))
16
- .map((entry) => entry.name)
17
- .sort();
18
- }
19
10
  function readRuntimeEndpoint(tuttiHome, projectId) {
20
11
  try {
21
12
  return readMachineRuntimeEndpoint(tuttiHome, projectId);
@@ -32,33 +23,33 @@ function readBinding(tuttiHome, projectId) {
32
23
  return null;
33
24
  }
34
25
  }
35
- function readProjectTargetEntry(tuttiHome, projectId) {
36
- const binding = readBinding(tuttiHome, projectId);
37
- const endpoint = readRuntimeEndpoint(tuttiHome, projectId);
38
- const workspaceRoot = binding?.workspace_root ?? endpoint?.workspace_root;
26
+ function projectTargetEntryFromInspection(inspection) {
27
+ const workspaceRoot = inspection.workspace_root;
39
28
  if (workspaceRoot === undefined) {
40
29
  return null;
41
30
  }
42
- const workspaceName = basename(workspaceRoot) || projectId;
43
- const displayName = readProjectDisplayName(workspaceRoot) ?? workspaceName;
44
- const aliases = Array.from(new Set([displayName, workspaceName].filter((alias) => alias.trim() !== "")));
31
+ const workspaceName = basename(workspaceRoot) || inspection.project_id;
32
+ const aliases = inspection.workspace.kind === "matching"
33
+ ? Array.from(new Set([inspection.display_name, workspaceName].filter((alias) => alias.trim() !== "")))
34
+ : [];
45
35
  return {
46
- project_id: projectId,
36
+ project_id: inspection.project_id,
47
37
  workspace_root: workspaceRoot,
48
- display_name: displayName,
38
+ display_name: inspection.display_name,
49
39
  aliases,
40
+ workspace_state: inspection.workspace.kind,
50
41
  };
51
42
  }
52
43
  function listProjectTargetEntries(tuttiHome) {
53
- return projectIdsFromTuttiHome(tuttiHome)
54
- .map((projectId) => readProjectTargetEntry(tuttiHome, projectId))
44
+ return inspectMachineProjectInventories(tuttiHome)
45
+ .map(projectTargetEntryFromInspection)
55
46
  .filter((entry) => entry !== null);
56
47
  }
57
48
  function normalizeTargetName(value) {
58
49
  return value.trim().toLowerCase();
59
50
  }
60
51
  function formatTargetMatch(entry) {
61
- return `${entry.project_id} ${entry.display_name} ${entry.workspace_root}`;
52
+ return `${entry.project_id} ${entry.workspace_state} ${entry.display_name}`;
62
53
  }
63
54
  function resolveSingleTargetMatch(target, matches) {
64
55
  if (matches.length === 1) {
@@ -81,15 +72,13 @@ function resolveProjectTargetEntry(options) {
81
72
  if (existsSync(pathCandidate)) {
82
73
  return readProjectTargetEntryFromWorkspace(pathCandidate);
83
74
  }
84
- const entries = listProjectTargetEntries(options.tuttiHome);
85
- const pathMatches = entries.filter((entry) => resolve(entry.workspace_root) === pathCandidate);
86
- if (pathMatches.length > 0) {
87
- return resolveSingleTargetMatch(target, pathMatches);
88
- }
89
- const exactId = entries.filter((entry) => entry.project_id === target);
90
- if (exactId.length > 0) {
91
- return resolveSingleTargetMatch(target, exactId);
75
+ if (isPrefixedId(target, ID_PREFIXES.project)) {
76
+ const exactEntry = projectTargetEntryFromInspection(inspectMachineProjectInventory(target, options.tuttiHome));
77
+ if (exactEntry !== null) {
78
+ return exactEntry;
79
+ }
92
80
  }
81
+ const entries = listProjectTargetEntries(options.tuttiHome);
93
82
  if (target.startsWith(ID_PREFIXES.project)) {
94
83
  const idPrefix = entries.filter((entry) => entry.project_id.startsWith(target));
95
84
  if (idPrefix.length > 0) {
@@ -113,6 +102,7 @@ function readProjectTargetEntryFromWorkspace(workspaceRoot) {
113
102
  workspace_root: workspaceRoot,
114
103
  display_name: displayName,
115
104
  aliases: Array.from(new Set([displayName, workspaceName].filter((alias) => alias.trim() !== ""))),
105
+ workspace_state: "matching",
116
106
  };
117
107
  }
118
108
  export function resolveLaunchWorkspacePath(options) {
@@ -1,5 +1,5 @@
1
1
  import { type ConnectRelayHostTunnelOptions, type RelayHostTunnelHandle, type RegisterRelayHostConnectionOptions, type RegisterRelayHostConnectionResponse } from "@tutti/relay-client";
2
- import type { HostConnectionRef, RelayProjectRef } from "@tutti/shared/ids";
2
+ import { type HostConnectionRef, type RelayProjectRef } from "@tutti/shared/ids";
3
3
  import { LaunchError } from "./errors.js";
4
4
  import type { LaunchPreparationResult } from "./launch.js";
5
5
  type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
@@ -19,12 +19,15 @@ export type RegisteredRelayHostConnection = {
19
19
  summary: HostLaunchRelaySummary;
20
20
  hostConnectionToken: string;
21
21
  };
22
+ export type HostRelayRegistrationPreparation = Pick<LaunchPreparationResult, "tutti_home" | "project_id" | "workspace_root" | "relay_url">;
22
23
  export declare function relayErrorToLaunchError(error: unknown, relayUrl: string): LaunchError;
23
24
  export declare function registerHostWithRelay(options: {
24
- preparation: LaunchPreparationResult;
25
+ preparation: HostRelayRegistrationPreparation;
25
26
  fetchImpl: FetchLike;
26
27
  registerRelayHostConnection: RelayHostConnectionRegistrar;
27
28
  serverVersion: string;
29
+ joinTokenMode?: "reuse_active" | "rotate";
30
+ signal?: AbortSignal;
28
31
  now?: () => Date;
29
32
  }): Promise<RegisteredRelayHostConnection>;
30
33
  export {};
@@ -1,5 +1,6 @@
1
1
  import { basename } from "node:path";
2
2
  import { RelayHostControlClientError, } from "@tutti/relay-client";
3
+ import { createIdempotencyKey, } from "@tutti/shared/ids";
3
4
  import { readHostRegistrationSecret, updateMachineProjectRelayBinding } from "./machine-local.js";
4
5
  import { LaunchError } from "./errors.js";
5
6
  import { readProjectDisplayName } from "./project-identity.js";
@@ -26,8 +27,10 @@ export async function registerHostWithRelay(options) {
26
27
  displayName: readProjectDisplayName(options.preparation.workspace_root) ??
27
28
  (basename(options.preparation.workspace_root) || options.preparation.project_id),
28
29
  serverVersion: options.serverVersion,
29
- joinToken: { mode: "rotate" },
30
+ commandId: createIdempotencyKey(),
31
+ joinToken: { mode: options.joinTokenMode ?? "rotate" },
30
32
  fetch: options.fetchImpl,
33
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
31
34
  ...(options.now === undefined ? {} : { issuedAt: options.now() }),
32
35
  };
33
36
  const response = await options.registerRelayHostConnection(registrationOptions);
@@ -35,7 +38,8 @@ export async function registerHostWithRelay(options) {
35
38
  throw new LaunchError("relay_registration_failed", "Relay host registration did not complete", "Retry `tutti launch` with a new registration command.");
36
39
  }
37
40
  const result = response.result;
38
- if (result.join_token.visibility !== "visible_once" || result.join_token.join_url === undefined) {
41
+ if ((options.joinTokenMode ?? "rotate") === "rotate" &&
42
+ (result.join_token.visibility !== "visible_once" || result.join_token.join_url === undefined)) {
39
43
  throw new LaunchError("relay_registration_failed", "Relay did not return a visible join URL for this launch", "Retry `tutti launch` so the host can rotate a fresh visible-once join URL.");
40
44
  }
41
45
  updateMachineProjectRelayBinding({
@@ -48,7 +52,7 @@ export async function registerHostWithRelay(options) {
48
52
  summary: {
49
53
  relay_project_ref: result.relay_project.relay_project_ref,
50
54
  host_connection_ref: result.host_connection.host_connection_ref,
51
- join_url: result.join_token.join_url,
55
+ ...(result.join_token.join_url === undefined ? {} : { join_url: result.join_token.join_url }),
52
56
  join_url_visibility: result.join_token.visibility,
53
57
  join_token_expires_at: result.join_token.expires_at,
54
58
  join_token_reusable: result.join_token.reusable,
@@ -1,40 +1,66 @@
1
- import { type ProjectId } from "@tutti/shared/ids";
1
+ import type { ProjectId } from "@tutti/shared/ids";
2
2
  import { type FetchLike } from "./host-runtime-endpoint.js";
3
3
  import { type MachineRuntimeEndpointRecord } from "./machine-local.js";
4
4
  import type { LocalControlFetch } from "./local-control-client.js";
5
5
  export type RuntimeProjectRow = {
6
6
  project_id: ProjectId;
7
- status: "online" | "offline" | "stale";
7
+ status: "online" | "reconnecting" | "relay-unavailable" | "stale" | "stopped" | "workspace-missing" | "workspace-replaced" | "invalid";
8
8
  display_name: string;
9
9
  workspace_root: string;
10
10
  endpoint?: string;
11
11
  provider_status?: string;
12
12
  relay_project_ref?: string;
13
13
  join_url?: string;
14
+ relay_connection_status?: string;
15
+ last_connected_at?: string;
16
+ next_retry_at?: string;
17
+ relay_reason_code?: string;
14
18
  };
15
19
  export declare function formatProjectIdForDisplay(projectId: ProjectId, projectIds: readonly ProjectId[]): string;
16
20
  export declare function listRuntimeProjects(options?: {
17
21
  env?: NodeJS.ProcessEnv;
18
22
  cwd?: string;
19
23
  fetchImpl?: FetchLike;
24
+ all?: boolean;
20
25
  }): Promise<RuntimeProjectRow[]>;
21
- export declare function runPsCommand(): Promise<string>;
26
+ export declare function runPsCommand(options?: Parameters<typeof listRuntimeProjects>[0]): Promise<string>;
22
27
  export declare function runPsManageCommand(options?: {
23
28
  stdin?: NodeJS.ReadStream;
24
29
  stdout?: NodeJS.WriteStream;
30
+ all?: boolean;
25
31
  }): Promise<void>;
26
32
  export declare function runStopCommand(target?: string, options?: {
27
33
  cwd?: string;
28
34
  env?: NodeJS.ProcessEnv;
29
35
  fetchImpl?: LocalControlFetch;
30
36
  }): Promise<string>;
37
+ export declare function runArchiveCommand(target: string, options?: {
38
+ yes?: boolean;
39
+ interactive?: boolean;
40
+ confirm?: (message: string) => Promise<boolean>;
41
+ cwd?: string;
42
+ env?: NodeJS.ProcessEnv;
43
+ fetchImpl?: LocalControlFetch;
44
+ shutdownWaitMs?: number;
45
+ now?: () => Date;
46
+ }): Promise<string>;
47
+ export declare function runForgetCommand(target: string, options?: {
48
+ yes?: boolean;
49
+ interactive?: boolean;
50
+ confirm?: (message: string) => Promise<boolean>;
51
+ cwd?: string;
52
+ env?: NodeJS.ProcessEnv;
53
+ fetchImpl?: LocalControlFetch;
54
+ shutdownWaitMs?: number;
55
+ }): Promise<string>;
31
56
  export declare function runInviteCommand(target?: string): Promise<string>;
32
57
  export declare function runProviderStatusCommand(target?: string): string;
33
58
  export declare function runLogsCommand(target?: string, tailLines?: number): string;
34
59
  export declare function runDoctorCommand(target?: string, options?: {
35
60
  cwd?: string;
36
61
  env?: NodeJS.ProcessEnv;
37
- }): string;
62
+ fetchImpl?: LocalControlFetch;
63
+ }): Promise<string>;
38
64
  export declare function readRuntimeEndpointForProject(tuttiHome: string, projectId: ProjectId): MachineRuntimeEndpointRecord | null;
39
65
  export declare function runtimeEndpointPathForProject(tuttiHome: string, projectId: ProjectId): string;
40
66
  export declare function projectLocalStoreRoot(tuttiHome: string, projectId: ProjectId): string;