@xfey/tutti 0.1.34 → 0.1.35
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.
- package/dist/server-shell/cli/launch.js +1 -0
- package/dist/server-shell/cli/machine-local.d.ts +3 -0
- package/dist/server-shell/cli/machine-local.js +24 -3
- package/dist/server-shell/cli/runtime-commands.d.ts +2 -2
- package/dist/server-shell/cli/runtime-commands.js +4 -0
- package/dist/server-shell/local-console/project-service.d.ts +5 -2
- package/dist/server-shell/local-console/project-service.js +56 -7
- package/package.json +1 -1
- package/web/assets/index-DPuaTngs.js +29 -0
- package/web/assets/{index-C7RDpM1L.css → index-Dmbchapb.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-B84rQ4YJ.js +0 -29
|
@@ -68,6 +68,7 @@ export async function prepareLaunchProject(options) {
|
|
|
68
68
|
tuttiHome,
|
|
69
69
|
projectId,
|
|
70
70
|
workspaceRoot,
|
|
71
|
+
relayUrl,
|
|
71
72
|
displayNameSnapshot: readProjectDisplayName(workspaceRoot) ?? (basename(workspaceRoot) || projectId),
|
|
72
73
|
};
|
|
73
74
|
if (options.allowWorkspaceRootTakeover !== undefined) {
|
|
@@ -5,7 +5,9 @@ export type MachineProjectBindingRecord = {
|
|
|
5
5
|
local_store_root: string;
|
|
6
6
|
display_name_snapshot?: string;
|
|
7
7
|
relay_project_ref?: RelayProjectRef;
|
|
8
|
+
relay_url?: string;
|
|
8
9
|
host_registration_secret_ref?: string;
|
|
10
|
+
created_at?: string;
|
|
9
11
|
updated_at: string;
|
|
10
12
|
};
|
|
11
13
|
export type HostRegistrationSecretFile = {
|
|
@@ -28,6 +30,7 @@ export type EnsureMachineProjectBindingOptions = {
|
|
|
28
30
|
projectId: ProjectId;
|
|
29
31
|
workspaceRoot: string;
|
|
30
32
|
displayNameSnapshot?: string;
|
|
33
|
+
relayUrl?: string;
|
|
31
34
|
allowWorkspaceRootTakeover?: boolean;
|
|
32
35
|
now?: () => Date;
|
|
33
36
|
};
|
|
@@ -78,7 +78,10 @@ export function readMachineProjectBinding(tuttiHome, projectId) {
|
|
|
78
78
|
(typeof raw.relay_project_ref !== "string" ||
|
|
79
79
|
!isPrefixedId(raw.relay_project_ref, ID_PREFIXES.relayProject))) ||
|
|
80
80
|
(raw.host_registration_secret_ref !== undefined &&
|
|
81
|
-
typeof raw.host_registration_secret_ref !== "string")
|
|
81
|
+
typeof raw.host_registration_secret_ref !== "string") ||
|
|
82
|
+
(raw.relay_url !== undefined &&
|
|
83
|
+
(typeof raw.relay_url !== "string" || raw.relay_url.trim() === "")) ||
|
|
84
|
+
(raw.created_at !== undefined && typeof raw.created_at !== "string")) {
|
|
82
85
|
throw new LaunchError("project_identity_conflict", "Machine-local project binding is invalid or conflicts with the Git config project identity", "Inspect or remove the project binding under TUTTI_HOME after confirming it is not needed.");
|
|
83
86
|
}
|
|
84
87
|
const binding = {
|
|
@@ -93,9 +96,15 @@ export function readMachineProjectBinding(tuttiHome, projectId) {
|
|
|
93
96
|
if (raw.relay_project_ref !== undefined) {
|
|
94
97
|
binding.relay_project_ref = raw.relay_project_ref;
|
|
95
98
|
}
|
|
99
|
+
if (raw.relay_url !== undefined) {
|
|
100
|
+
binding.relay_url = raw.relay_url;
|
|
101
|
+
}
|
|
96
102
|
if (raw.host_registration_secret_ref !== undefined) {
|
|
97
103
|
binding.host_registration_secret_ref = raw.host_registration_secret_ref;
|
|
98
104
|
}
|
|
105
|
+
if (raw.created_at !== undefined) {
|
|
106
|
+
binding.created_at = raw.created_at;
|
|
107
|
+
}
|
|
99
108
|
return binding;
|
|
100
109
|
}
|
|
101
110
|
export function readMachineRuntimeEndpoint(tuttiHome, projectId) {
|
|
@@ -226,7 +235,11 @@ function ensureHostRegistrationSecret(tuttiHome, projectId, now) {
|
|
|
226
235
|
const secretPath = getHostRegistrationSecretPath(tuttiHome, projectId);
|
|
227
236
|
const ref = createHostRegistrationSecretRef(projectId);
|
|
228
237
|
if (existsSync(secretPath)) {
|
|
229
|
-
return {
|
|
238
|
+
return {
|
|
239
|
+
ref,
|
|
240
|
+
created: false,
|
|
241
|
+
createdAt: readHostRegistrationSecret(tuttiHome, projectId).created_at,
|
|
242
|
+
};
|
|
230
243
|
}
|
|
231
244
|
const timestamp = now().toISOString();
|
|
232
245
|
const secretFile = {
|
|
@@ -237,7 +250,7 @@ function ensureHostRegistrationSecret(tuttiHome, projectId, now) {
|
|
|
237
250
|
updated_at: timestamp,
|
|
238
251
|
};
|
|
239
252
|
writePrivateJsonFile(secretPath, secretFile);
|
|
240
|
-
return { ref, created: true };
|
|
253
|
+
return { ref, created: true, createdAt: timestamp };
|
|
241
254
|
}
|
|
242
255
|
export function ensureMachineProjectBinding(options) {
|
|
243
256
|
const now = options.now ?? (() => new Date());
|
|
@@ -256,6 +269,7 @@ export function ensureMachineProjectBinding(options) {
|
|
|
256
269
|
workspace_root: workspaceRoot,
|
|
257
270
|
local_store_root: localStoreRoot,
|
|
258
271
|
host_registration_secret_ref: existing?.host_registration_secret_ref ?? secret.ref,
|
|
272
|
+
created_at: existing?.created_at ?? secret.createdAt,
|
|
259
273
|
updated_at: now().toISOString(),
|
|
260
274
|
};
|
|
261
275
|
const displayNameSnapshot = options.displayNameSnapshot?.trim();
|
|
@@ -268,6 +282,13 @@ export function ensureMachineProjectBinding(options) {
|
|
|
268
282
|
if (existing?.relay_project_ref !== undefined) {
|
|
269
283
|
binding.relay_project_ref = existing.relay_project_ref;
|
|
270
284
|
}
|
|
285
|
+
const relayUrl = options.relayUrl?.trim();
|
|
286
|
+
if (relayUrl !== undefined && relayUrl !== "") {
|
|
287
|
+
binding.relay_url = relayUrl;
|
|
288
|
+
}
|
|
289
|
+
else if (existing?.relay_url !== undefined) {
|
|
290
|
+
binding.relay_url = existing.relay_url;
|
|
291
|
+
}
|
|
271
292
|
writePrivateJsonFile(bindingPath, binding);
|
|
272
293
|
return {
|
|
273
294
|
binding,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProjectId } from "@tutti/shared/ids";
|
|
1
|
+
import type { ProjectId, RelayProjectRef } 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";
|
|
@@ -9,7 +9,7 @@ export type RuntimeProjectRow = {
|
|
|
9
9
|
workspace_root: string;
|
|
10
10
|
endpoint?: string;
|
|
11
11
|
provider_status?: string;
|
|
12
|
-
relay_project_ref?:
|
|
12
|
+
relay_project_ref?: RelayProjectRef;
|
|
13
13
|
join_url?: string;
|
|
14
14
|
relay_connection_status?: string;
|
|
15
15
|
last_connected_at?: string;
|
|
@@ -101,6 +101,10 @@ export async function listRuntimeProjects(options = {}) {
|
|
|
101
101
|
if (launchStatus?.relay?.relay_project_ref !== undefined) {
|
|
102
102
|
row.relay_project_ref = launchStatus.relay.relay_project_ref;
|
|
103
103
|
}
|
|
104
|
+
else if (inspection.binding.kind === "valid" &&
|
|
105
|
+
inspection.binding.record.relay_project_ref !== undefined) {
|
|
106
|
+
row.relay_project_ref = inspection.binding.record.relay_project_ref;
|
|
107
|
+
}
|
|
104
108
|
if (launchStatus?.relay?.join_url !== undefined) {
|
|
105
109
|
row.join_url = launchStatus.relay.join_url;
|
|
106
110
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ProjectId, type RelayProjectRef } from "@tutti/shared/ids";
|
|
2
2
|
import { type RuntimeProjectRow } from "../cli/runtime-commands.js";
|
|
3
3
|
import { type LocalConsoleInvocationContext } from "./invocation-context.js";
|
|
4
4
|
export type LocalConsoleProject = {
|
|
@@ -7,9 +7,11 @@ export type LocalConsoleProject = {
|
|
|
7
7
|
workspace_path: string;
|
|
8
8
|
status: RuntimeProjectRow["status"];
|
|
9
9
|
provider_status: "configured" | "not_configured" | "invalid";
|
|
10
|
-
relay_project_ref?:
|
|
10
|
+
relay_project_ref?: RelayProjectRef;
|
|
11
11
|
join_url?: string;
|
|
12
|
+
open_url?: string;
|
|
12
13
|
relay_connection_status?: string;
|
|
14
|
+
created_at?: string;
|
|
13
15
|
activity_at?: string;
|
|
14
16
|
host_version?: string;
|
|
15
17
|
update_required?: boolean;
|
|
@@ -21,6 +23,7 @@ export type LocalConsoleProviderInput = {
|
|
|
21
23
|
};
|
|
22
24
|
export type LocalConsoleLaunchResult = {
|
|
23
25
|
project: LocalConsoleProject;
|
|
26
|
+
project_created: boolean;
|
|
24
27
|
join_url: string;
|
|
25
28
|
join_token_expires_at?: string;
|
|
26
29
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, statSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { relayProjectRouteSegment } from "@tutti/shared/ids";
|
|
3
4
|
import { configureProjectOpenAiProvider, discoverOpenAiModels, readOpenAiProviderConfigProjection, } from "../../providers/openai/index.js";
|
|
4
5
|
import { formatCliErrorReason, LaunchError } from "../cli/errors.js";
|
|
5
|
-
import { prepareLaunchProject } from "../cli/launch.js";
|
|
6
|
+
import { prepareLaunchProject, resolveRelayUrl } from "../cli/launch.js";
|
|
6
7
|
import { rotateHostLocalInvite } from "../cli/local-control-client.js";
|
|
7
|
-
import { readMachineProjectBinding, readMachineRuntimeEndpoint } from "../cli/machine-local.js";
|
|
8
|
+
import { readHostRegistrationSecret, readMachineProjectBinding, readMachineRuntimeEndpoint, } from "../cli/machine-local.js";
|
|
8
9
|
import { spawnDetachedHost, waitForManagedHostReady } from "../cli/managed-host.js";
|
|
9
10
|
import { resolveManagedProjectContext } from "../cli/project-resolver.js";
|
|
10
11
|
import { listRuntimeProjects, runStopCommand, } from "../cli/runtime-commands.js";
|
|
@@ -47,8 +48,9 @@ function validateProvider(input) {
|
|
|
47
48
|
}
|
|
48
49
|
return provider;
|
|
49
50
|
}
|
|
50
|
-
function projectFromRuntimeRow(row, providerStatus,
|
|
51
|
+
function projectFromRuntimeRow(row, providerStatus, metadata) {
|
|
51
52
|
const currentVersion = readCliVersion();
|
|
53
|
+
const openUrl = projectOpenUrl(metadata.relayUrl, row.relay_project_ref);
|
|
52
54
|
return {
|
|
53
55
|
project_id: row.project_id,
|
|
54
56
|
display_name: row.display_name,
|
|
@@ -57,17 +59,31 @@ function projectFromRuntimeRow(row, providerStatus, activityAt) {
|
|
|
57
59
|
provider_status: providerStatus,
|
|
58
60
|
...(row.relay_project_ref === undefined ? {} : { relay_project_ref: row.relay_project_ref }),
|
|
59
61
|
...(row.join_url === undefined ? {} : { join_url: row.join_url }),
|
|
62
|
+
...(openUrl === undefined ? {} : { open_url: openUrl }),
|
|
60
63
|
...(row.relay_connection_status === undefined
|
|
61
64
|
? {}
|
|
62
65
|
: { relay_connection_status: row.relay_connection_status }),
|
|
63
|
-
...(activityAt === undefined ? {} : { activity_at: activityAt }),
|
|
66
|
+
...(metadata.activityAt === undefined ? {} : { activity_at: metadata.activityAt }),
|
|
67
|
+
...(metadata.createdAt === undefined ? {} : { created_at: metadata.createdAt }),
|
|
64
68
|
...(row.host_version === undefined ? {} : { host_version: row.host_version }),
|
|
65
69
|
...(row.host_version === undefined || row.host_version === currentVersion
|
|
66
70
|
? {}
|
|
67
71
|
: { update_required: true }),
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
|
-
function
|
|
74
|
+
function projectOpenUrl(relayUrl, relayProjectRef) {
|
|
75
|
+
if (relayUrl === undefined || relayProjectRef === undefined) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(`/projects?open=${encodeURIComponent(relayProjectRouteSegment(relayProjectRef))}`, relayUrl);
|
|
80
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : undefined;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function readProjectActivityAt(tuttiHome, row, binding) {
|
|
71
87
|
if (row.last_connected_at !== undefined) {
|
|
72
88
|
return row.last_connected_at;
|
|
73
89
|
}
|
|
@@ -81,7 +97,18 @@ function readProjectActivityAt(tuttiHome, row) {
|
|
|
81
97
|
// Invalid endpoint state is already represented by the project status.
|
|
82
98
|
}
|
|
83
99
|
try {
|
|
84
|
-
return
|
|
100
|
+
return binding?.updated_at;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function relayUrlFromJoinUrl(joinUrl) {
|
|
107
|
+
if (joinUrl === undefined) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
return new URL(joinUrl).origin;
|
|
85
112
|
}
|
|
86
113
|
catch {
|
|
87
114
|
return undefined;
|
|
@@ -118,11 +145,32 @@ export class LocalConsoleProjectService {
|
|
|
118
145
|
...operation,
|
|
119
146
|
});
|
|
120
147
|
return rows.map((row) => {
|
|
148
|
+
let binding = null;
|
|
149
|
+
try {
|
|
150
|
+
binding = readMachineProjectBinding(this.#tuttiHome, row.project_id);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Invalid binding state is already represented by the project status.
|
|
154
|
+
}
|
|
155
|
+
let createdAt = binding?.created_at;
|
|
156
|
+
if (createdAt === undefined) {
|
|
157
|
+
try {
|
|
158
|
+
createdAt = readHostRegistrationSecret(this.#tuttiHome, row.project_id).created_at;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Damaged legacy metadata remains visible without an invented creation date.
|
|
162
|
+
}
|
|
163
|
+
}
|
|
121
164
|
const provider = readOpenAiProviderConfigProjection({
|
|
122
165
|
tuttiHome: this.#tuttiHome,
|
|
123
166
|
projectId: row.project_id,
|
|
124
167
|
});
|
|
125
|
-
|
|
168
|
+
const activityAt = readProjectActivityAt(this.#tuttiHome, row, binding);
|
|
169
|
+
return projectFromRuntimeRow(row, provider.status, {
|
|
170
|
+
...(activityAt === undefined ? {} : { activityAt }),
|
|
171
|
+
...(createdAt === undefined ? {} : { createdAt }),
|
|
172
|
+
relayUrl: binding?.relay_url ?? relayUrlFromJoinUrl(row.join_url) ?? resolveRelayUrl(operation.env),
|
|
173
|
+
});
|
|
126
174
|
});
|
|
127
175
|
}
|
|
128
176
|
async discoverModels(input) {
|
|
@@ -200,6 +248,7 @@ export class LocalConsoleProjectService {
|
|
|
200
248
|
}
|
|
201
249
|
return {
|
|
202
250
|
project,
|
|
251
|
+
project_created: preparation.project_identity_created,
|
|
203
252
|
join_url: ready.join_url,
|
|
204
253
|
...(ready.join_token_expires_at === undefined
|
|
205
254
|
? {}
|