@xfey/tutti 0.1.34 → 0.1.36
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/host-runtime-endpoint.d.ts +5 -0
- package/dist/server-shell/cli/host-runtime-endpoint.js +3 -0
- package/dist/server-shell/cli/host-server-runtime.d.ts +1 -1
- package/dist/server-shell/cli/host-server-runtime.js +7 -1
- package/dist/server-shell/cli/launch.js +1 -0
- package/dist/server-shell/cli/local-control-client.d.ts +5 -1
- package/dist/server-shell/cli/local-control-client.js +22 -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/managed-host.d.ts +1 -0
- package/dist/server-shell/cli/managed-host.js +4 -1
- 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/http/routes/local-control.d.ts +2 -0
- package/dist/server-shell/http/routes/local-control.js +78 -1
- package/dist/server-shell/local-console/project-service.d.ts +14 -2
- package/dist/server-shell/local-console/project-service.js +172 -15
- package/dist/server-shell/local-console/server.js +4 -0
- package/package.json +1 -1
- package/web/assets/index-D9jQgROm.js +29 -0
- package/web/assets/{index-C7RDpM1L.css → index-DnBL91c3.css} +1 -1
- package/web/index.html +2 -2
- package/web/assets/index-B84rQ4YJ.js +0 -29
|
@@ -19,6 +19,11 @@ export declare function requestExistingHostShutdown(options: {
|
|
|
19
19
|
probeRuntimeEndpoint: HostRuntimeEndpointProbe;
|
|
20
20
|
shutdownWaitMs: number;
|
|
21
21
|
}): Promise<void>;
|
|
22
|
+
export declare function waitForHostShutdown(options: {
|
|
23
|
+
endpoint: MachineRuntimeEndpointRecord;
|
|
24
|
+
probeRuntimeEndpoint: HostRuntimeEndpointProbe;
|
|
25
|
+
shutdownWaitMs: number;
|
|
26
|
+
}): Promise<void>;
|
|
22
27
|
export declare function readExistingHostLaunchStatus(options: {
|
|
23
28
|
endpoint: MachineRuntimeEndpointRecord;
|
|
24
29
|
fetchImpl: FetchLike;
|
|
@@ -85,6 +85,9 @@ export async function requestExistingHostShutdown(options) {
|
|
|
85
85
|
if (!response.ok) {
|
|
86
86
|
throw new LaunchError("takeover_failed", `The existing host server rejected takeover shutdown with HTTP ${response.status}`, "Stop the existing host server manually, then run `tutti launch` again.");
|
|
87
87
|
}
|
|
88
|
+
await waitForHostShutdown(options);
|
|
89
|
+
}
|
|
90
|
+
export async function waitForHostShutdown(options) {
|
|
88
91
|
const startedAt = Date.now();
|
|
89
92
|
while (Date.now() - startedAt < options.shutdownWaitMs) {
|
|
90
93
|
const probe = await options.probeRuntimeEndpoint(options.endpoint);
|
|
@@ -7,7 +7,7 @@ import { type TrustedRelaySessionMetadataStore } from "../session/relay-session-
|
|
|
7
7
|
import type { HostRelayConnectionManager } from "./host-relay-connection-manager.js";
|
|
8
8
|
import type { LaunchPreparationResult } from "./launch.js";
|
|
9
9
|
import { type MachineRuntimeEndpointRecord } from "./machine-local.js";
|
|
10
|
-
export declare const HOST_SERVER_VERSION
|
|
10
|
+
export declare const HOST_SERVER_VERSION: string;
|
|
11
11
|
export type HostServerFactory = typeof createHostServer;
|
|
12
12
|
export type HostServerHandle = {
|
|
13
13
|
app: FastifyInstance;
|
|
@@ -18,10 +18,11 @@ import { readHostRepoProjection } from "../http/routes/project-api.js";
|
|
|
18
18
|
import { WorkspaceEventBus } from "../http/workspace-events.js";
|
|
19
19
|
import { createTrustedRelaySessionMetadataStore, } from "../session/relay-session-context.js";
|
|
20
20
|
import { LaunchError } from "./errors.js";
|
|
21
|
+
import { readCliVersion } from "./version.js";
|
|
21
22
|
import { relayStateFromLaunchStatus } from "./host-relay-status.js";
|
|
22
23
|
import { appendHostLogLine, readHostRegistrationSecret, createMachineRuntimeEndpointToken, deleteMachineRuntimeEndpoint, ensureHostLogFile, getHostLogFilePath, updateMachineProjectDisplayNameSnapshot, writeMachineRuntimeEndpoint, } from "./machine-local.js";
|
|
23
24
|
import { readProjectDescription, readProjectDisplayName, writeProjectDescription, writeProjectDisplayName, } from "./project-identity.js";
|
|
24
|
-
export const HOST_SERVER_VERSION =
|
|
25
|
+
export const HOST_SERVER_VERSION = readCliVersion();
|
|
25
26
|
function createHostRuntimeLogger(options) {
|
|
26
27
|
return {
|
|
27
28
|
info(fields, message) {
|
|
@@ -262,6 +263,11 @@ export async function startForegroundHostServer(options) {
|
|
|
262
263
|
let relayConnectionManager;
|
|
263
264
|
const localControl = {
|
|
264
265
|
token,
|
|
266
|
+
requestIdleShutdown: () => {
|
|
267
|
+
const execution = controlPlane.getExecutionStatus();
|
|
268
|
+
const hasActiveActivities = execution.active_activity !== undefined || (execution.active_activities?.length ?? 0) > 0;
|
|
269
|
+
return hasActiveActivities ? "busy" : "accepted";
|
|
270
|
+
},
|
|
265
271
|
requestShutdown: () => {
|
|
266
272
|
void close();
|
|
267
273
|
},
|
|
@@ -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) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HostLocalLaunchStatus, HostLocalProjectProjection, HostLocalProviderConfigBody } from "../http/routes/local-control.js";
|
|
1
|
+
import type { HostLocalIdleShutdownDisposition, HostLocalLaunchStatus, HostLocalProjectProjection, HostLocalProviderConfigBody } from "../http/routes/local-control.js";
|
|
2
2
|
import type { HostProviderConfigProjection } from "../http/routes/project-api/types.js";
|
|
3
3
|
import type { MachineRuntimeEndpointRecord } from "./machine-local.js";
|
|
4
4
|
export type LocalControlFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
@@ -32,4 +32,8 @@ export declare function requestHostLocalShutdown(options: {
|
|
|
32
32
|
endpoint: MachineRuntimeEndpointRecord;
|
|
33
33
|
fetchImpl?: LocalControlFetch;
|
|
34
34
|
}): Promise<void>;
|
|
35
|
+
export declare function requestHostLocalIdleShutdown(options: {
|
|
36
|
+
endpoint: MachineRuntimeEndpointRecord;
|
|
37
|
+
fetchImpl?: LocalControlFetch;
|
|
38
|
+
}): Promise<HostLocalIdleShutdownDisposition>;
|
|
35
39
|
//# sourceMappingURL=local-control-client.d.ts.map
|
|
@@ -79,4 +79,26 @@ export async function requestHostLocalShutdown(options) {
|
|
|
79
79
|
path: "/host-local/v1/shutdown",
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
+
export async function requestHostLocalIdleShutdown(options) {
|
|
83
|
+
const response = await fetchWithTimeout(options.fetchImpl ?? fetch, new URL("/host-local/v1/shutdown-if-idle", options.endpoint.base_url), {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: {
|
|
86
|
+
accept: "application/json",
|
|
87
|
+
authorization: `Bearer ${options.endpoint.token}`,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
if (response.status === 404) {
|
|
91
|
+
return "unsupported";
|
|
92
|
+
}
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(`host-local shutdown-if-idle returned HTTP ${response.status}`);
|
|
95
|
+
}
|
|
96
|
+
const body = (await response.json());
|
|
97
|
+
if (body.disposition !== "accepted" &&
|
|
98
|
+
body.disposition !== "busy" &&
|
|
99
|
+
body.disposition !== "unsupported") {
|
|
100
|
+
throw new Error("host-local shutdown-if-idle returned an invalid response");
|
|
101
|
+
}
|
|
102
|
+
return body.disposition;
|
|
103
|
+
}
|
|
82
104
|
//# sourceMappingURL=local-control-client.js.map
|
|
@@ -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,
|
|
@@ -17,6 +17,7 @@ export declare function waitForManagedHostReady(options: {
|
|
|
17
17
|
projectId: ProjectId;
|
|
18
18
|
workspaceRoot: string;
|
|
19
19
|
fetchImpl?: FetchLike;
|
|
20
|
+
inviteMode?: "required" | "preserve";
|
|
20
21
|
timeoutMs?: number;
|
|
21
22
|
}): Promise<ManagedHostReadyResult>;
|
|
22
23
|
//# sourceMappingURL=managed-host.d.ts.map
|
|
@@ -66,7 +66,7 @@ export async function waitForManagedHostReady(options) {
|
|
|
66
66
|
endpoint,
|
|
67
67
|
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
68
68
|
});
|
|
69
|
-
if (status.relay?.join_url === undefined) {
|
|
69
|
+
if (status.relay?.join_url === undefined && options.inviteMode !== "preserve") {
|
|
70
70
|
status = await rotateHostLocalInvite({
|
|
71
71
|
endpoint,
|
|
72
72
|
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
@@ -84,6 +84,9 @@ export async function waitForManagedHostReady(options) {
|
|
|
84
84
|
: { join_token_reusable: status.relay.join_token_reusable }),
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
+
if (options.inviteMode === "preserve" && status.relay !== undefined) {
|
|
88
|
+
return { endpoint };
|
|
89
|
+
}
|
|
87
90
|
lastReason = "host is running but Relay invite is not visible yet";
|
|
88
91
|
}
|
|
89
92
|
catch (error) {
|
|
@@ -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
|
}
|
|
@@ -14,8 +14,10 @@ export type HostLocalControlOptions = {
|
|
|
14
14
|
get: () => ProviderConfigProjection;
|
|
15
15
|
configure: (input: HostLocalProviderConfigBody) => Promise<ProviderConfigProjection>;
|
|
16
16
|
};
|
|
17
|
+
requestIdleShutdown?: () => HostLocalIdleShutdownDisposition;
|
|
17
18
|
requestShutdown: () => void;
|
|
18
19
|
};
|
|
20
|
+
export type HostLocalIdleShutdownDisposition = "accepted" | "busy" | "unsupported";
|
|
19
21
|
export type HostLocalLaunchStatus = {
|
|
20
22
|
project_id?: ProjectId;
|
|
21
23
|
relay_connection?: {
|
|
@@ -31,6 +31,14 @@ class HostLocalProjectValidationError extends Error {
|
|
|
31
31
|
this.name = "HostLocalProjectValidationError";
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
class HostLocalDrainingError extends Error {
|
|
35
|
+
statusCode = 409;
|
|
36
|
+
code = "conflict";
|
|
37
|
+
constructor() {
|
|
38
|
+
super("Host is draining for a safe restart");
|
|
39
|
+
this.name = "HostLocalDrainingError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
34
42
|
const HostLocalShutdownResponseSchema = {
|
|
35
43
|
type: "object",
|
|
36
44
|
required: ["ok"],
|
|
@@ -39,6 +47,14 @@ const HostLocalShutdownResponseSchema = {
|
|
|
39
47
|
ok: { type: "boolean", enum: [true] },
|
|
40
48
|
},
|
|
41
49
|
};
|
|
50
|
+
const HostLocalIdleShutdownResponseSchema = {
|
|
51
|
+
type: "object",
|
|
52
|
+
required: ["disposition"],
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
properties: {
|
|
55
|
+
disposition: { type: "string", enum: ["accepted", "busy", "unsupported"] },
|
|
56
|
+
},
|
|
57
|
+
};
|
|
42
58
|
const HostLocalProviderConfigBodySchema = {
|
|
43
59
|
type: "object",
|
|
44
60
|
required: ["api_key"],
|
|
@@ -190,6 +206,46 @@ function normalizeProjectBody(input) {
|
|
|
190
206
|
return { display_name: displayName };
|
|
191
207
|
}
|
|
192
208
|
export function registerHostLocalControlRoutes(app, options) {
|
|
209
|
+
const mutationTracked = Symbol("hostLocalMutationTracked");
|
|
210
|
+
const shutdownPath = "/host-local/v1/shutdown";
|
|
211
|
+
const idleShutdownPath = "/host-local/v1/shutdown-if-idle";
|
|
212
|
+
let activeMutations = 0;
|
|
213
|
+
let draining = false;
|
|
214
|
+
function releaseTrackedMutation(request) {
|
|
215
|
+
const trackedRequest = request;
|
|
216
|
+
if (trackedRequest[mutationTracked] !== true) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
trackedRequest[mutationTracked] = false;
|
|
220
|
+
activeMutations = Math.max(0, activeMutations - 1);
|
|
221
|
+
}
|
|
222
|
+
app.addHook("onRequest", (request, _reply, done) => {
|
|
223
|
+
const path = request.url.split("?", 1)[0];
|
|
224
|
+
const mutation = request.method !== "GET" &&
|
|
225
|
+
request.method !== "HEAD" &&
|
|
226
|
+
request.method !== "OPTIONS" &&
|
|
227
|
+
path !== shutdownPath &&
|
|
228
|
+
path !== idleShutdownPath;
|
|
229
|
+
if (!mutation) {
|
|
230
|
+
done();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (draining) {
|
|
234
|
+
done(new HostLocalDrainingError());
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
activeMutations += 1;
|
|
238
|
+
Object.assign(request, { [mutationTracked]: true });
|
|
239
|
+
done();
|
|
240
|
+
});
|
|
241
|
+
app.addHook("onResponse", (request, _reply, done) => {
|
|
242
|
+
releaseTrackedMutation(request);
|
|
243
|
+
done();
|
|
244
|
+
});
|
|
245
|
+
app.addHook("onRequestAbort", (request, done) => {
|
|
246
|
+
releaseTrackedMutation(request);
|
|
247
|
+
done();
|
|
248
|
+
});
|
|
193
249
|
function requireLocalToken(authorization) {
|
|
194
250
|
const token = readBearerToken(authorization);
|
|
195
251
|
if (!tokenEquals(token, options.token)) {
|
|
@@ -286,7 +342,28 @@ export function registerHostLocalControlRoutes(app, options) {
|
|
|
286
342
|
}
|
|
287
343
|
return options.project.update(normalizeProjectBody(request.body));
|
|
288
344
|
});
|
|
289
|
-
app.post(
|
|
345
|
+
app.post(idleShutdownPath, {
|
|
346
|
+
schema: {
|
|
347
|
+
response: {
|
|
348
|
+
200: HostLocalIdleShutdownResponseSchema,
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
}, (request) => {
|
|
352
|
+
requireLocalToken(request.headers.authorization);
|
|
353
|
+
if (draining) {
|
|
354
|
+
return { disposition: "accepted" };
|
|
355
|
+
}
|
|
356
|
+
if (activeMutations > 0) {
|
|
357
|
+
return { disposition: "busy" };
|
|
358
|
+
}
|
|
359
|
+
const disposition = options.requestIdleShutdown?.() ?? "unsupported";
|
|
360
|
+
if (disposition === "accepted") {
|
|
361
|
+
draining = true;
|
|
362
|
+
setImmediate(options.requestShutdown);
|
|
363
|
+
}
|
|
364
|
+
return { disposition };
|
|
365
|
+
});
|
|
366
|
+
app.post(shutdownPath, {
|
|
290
367
|
schema: {
|
|
291
368
|
response: {
|
|
292
369
|
202: HostLocalShutdownResponseSchema,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ProjectId, type RelayProjectRef } from "@tutti/shared/ids";
|
|
2
|
+
import { type FetchLike } from "../cli/host-runtime-endpoint.js";
|
|
2
3
|
import { type RuntimeProjectRow } from "../cli/runtime-commands.js";
|
|
3
4
|
import { type LocalConsoleInvocationContext } from "./invocation-context.js";
|
|
4
5
|
export type LocalConsoleProject = {
|
|
@@ -7,9 +8,12 @@ export type LocalConsoleProject = {
|
|
|
7
8
|
workspace_path: string;
|
|
8
9
|
status: RuntimeProjectRow["status"];
|
|
9
10
|
provider_status: "configured" | "not_configured" | "invalid";
|
|
10
|
-
|
|
11
|
+
provider_model?: string;
|
|
12
|
+
relay_project_ref?: RelayProjectRef;
|
|
11
13
|
join_url?: string;
|
|
14
|
+
open_url?: string;
|
|
12
15
|
relay_connection_status?: string;
|
|
16
|
+
created_at?: string;
|
|
13
17
|
activity_at?: string;
|
|
14
18
|
host_version?: string;
|
|
15
19
|
update_required?: boolean;
|
|
@@ -21,9 +25,15 @@ export type LocalConsoleProviderInput = {
|
|
|
21
25
|
};
|
|
22
26
|
export type LocalConsoleLaunchResult = {
|
|
23
27
|
project: LocalConsoleProject;
|
|
28
|
+
project_created: boolean;
|
|
24
29
|
join_url: string;
|
|
25
30
|
join_token_expires_at?: string;
|
|
26
31
|
};
|
|
32
|
+
export type LocalConsoleOpenResult = {
|
|
33
|
+
project: LocalConsoleProject;
|
|
34
|
+
open_url: string;
|
|
35
|
+
disposition: "opened_current" | "updated" | "deferred_busy" | "deferred_unsupported" | "deferred_unavailable";
|
|
36
|
+
};
|
|
27
37
|
export declare class LocalConsoleProjectError extends Error {
|
|
28
38
|
readonly code: string;
|
|
29
39
|
readonly statusCode: number;
|
|
@@ -34,6 +44,7 @@ export declare class LocalConsoleProjectService {
|
|
|
34
44
|
constructor(options: {
|
|
35
45
|
tuttiHome: string;
|
|
36
46
|
serviceEnvironment?: NodeJS.ProcessEnv;
|
|
47
|
+
fetchImpl?: FetchLike;
|
|
37
48
|
});
|
|
38
49
|
listProjects(context: LocalConsoleInvocationContext): Promise<LocalConsoleProject[]>;
|
|
39
50
|
discoverModels(input: {
|
|
@@ -50,6 +61,7 @@ export declare class LocalConsoleProjectService {
|
|
|
50
61
|
workspacePath: string;
|
|
51
62
|
provider?: LocalConsoleProviderInput;
|
|
52
63
|
}, context: LocalConsoleInvocationContext): Promise<LocalConsoleLaunchResult>;
|
|
64
|
+
openProject(projectId: string, context: LocalConsoleInvocationContext): Promise<LocalConsoleOpenResult>;
|
|
53
65
|
refreshInvite(projectId: string, context: LocalConsoleInvocationContext): Promise<{
|
|
54
66
|
join_url: string;
|
|
55
67
|
expires_at?: string;
|