@xfey/tutti 0.1.26 → 0.1.27
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.
|
@@ -3,6 +3,9 @@ import type { MachineRuntimeEndpointRecord } from "./machine-local.js";
|
|
|
3
3
|
export type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
4
4
|
export type HostRuntimeEndpointProbeResult = {
|
|
5
5
|
kind: "alive";
|
|
6
|
+
readiness?: {
|
|
7
|
+
relay?: "not_connected" | "connected";
|
|
8
|
+
};
|
|
6
9
|
} | {
|
|
7
10
|
kind: "stale";
|
|
8
11
|
reason: string;
|
|
@@ -5,12 +5,23 @@ function delay(ms) {
|
|
|
5
5
|
setTimeout(resolve, ms);
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
-
function
|
|
8
|
+
function parseHostHealthPayload(value) {
|
|
9
9
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
10
|
-
return
|
|
10
|
+
return null;
|
|
11
11
|
}
|
|
12
12
|
const record = value;
|
|
13
|
-
|
|
13
|
+
if (record.service !== "tutti-host" || record.status !== "ok") {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (typeof record.readiness !== "object" ||
|
|
17
|
+
record.readiness === null ||
|
|
18
|
+
Array.isArray(record.readiness)) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
const readiness = record.readiness;
|
|
22
|
+
return readiness.relay === "connected" || readiness.relay === "not_connected"
|
|
23
|
+
? { relay: readiness.relay }
|
|
24
|
+
: {};
|
|
14
25
|
}
|
|
15
26
|
async function fetchWithTimeout(fetchImpl, url, init = {}, timeoutMs = DEFAULT_PROBE_TIMEOUT_MS) {
|
|
16
27
|
const controller = new AbortController();
|
|
@@ -38,11 +49,14 @@ export function createRuntimeEndpointProbe(fetchImpl = fetch) {
|
|
|
38
49
|
if (!response.ok) {
|
|
39
50
|
return { kind: "stale", reason: `health returned HTTP ${response.status}` };
|
|
40
51
|
}
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
52
|
+
const readiness = parseHostHealthPayload(await response.json());
|
|
53
|
+
if (readiness === null) {
|
|
43
54
|
return { kind: "stale", reason: "health response is not a Tutti host" };
|
|
44
55
|
}
|
|
45
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
kind: "alive",
|
|
58
|
+
...(readiness.relay === undefined ? {} : { readiness }),
|
|
59
|
+
};
|
|
46
60
|
}
|
|
47
61
|
catch (error) {
|
|
48
62
|
const reason = error instanceof Error ? error.message : "health check failed";
|
|
@@ -4,7 +4,7 @@ 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" | "stale";
|
|
7
|
+
status: "online" | "offline" | "stale";
|
|
8
8
|
display_name: string;
|
|
9
9
|
workspace_root: string;
|
|
10
10
|
endpoint?: string;
|
|
@@ -11,6 +11,7 @@ import { resolveManagedProjectContext } from "./project-resolver.js";
|
|
|
11
11
|
import { formatJoinLinkValidity, renderTerminalQr } from "./terminal-qr.js";
|
|
12
12
|
import { resolveTuttiHome } from "../../providers/openai/index.js";
|
|
13
13
|
import { readCliVersion } from "./version.js";
|
|
14
|
+
import { readProjectIdentity } from "./project-identity.js";
|
|
14
15
|
const PROJECT_ID_DISPLAY_MIN_LENGTH = 12;
|
|
15
16
|
function resolveProject(target, options = {}) {
|
|
16
17
|
return resolveManagedProjectContext({
|
|
@@ -45,6 +46,30 @@ function readBindingFile(tuttiHome, projectId) {
|
|
|
45
46
|
return null;
|
|
46
47
|
}
|
|
47
48
|
}
|
|
49
|
+
function workspaceBelongsToProject(workspaceRoot, projectId) {
|
|
50
|
+
if (!existsSync(workspaceRoot)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const identity = readProjectIdentity(workspaceRoot);
|
|
55
|
+
return identity.kind === "present" && identity.project_id === projectId;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function cleanupOrphanedRuntimeProject(options) {
|
|
62
|
+
if (options.hostAlive) {
|
|
63
|
+
await requestHostLocalShutdown({
|
|
64
|
+
endpoint: options.endpoint,
|
|
65
|
+
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
66
|
+
}).catch(() => undefined);
|
|
67
|
+
}
|
|
68
|
+
deleteMachineProjectLocalState({
|
|
69
|
+
tuttiHome: options.tuttiHome,
|
|
70
|
+
projectId: options.projectId,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
48
73
|
export function formatProjectIdForDisplay(projectId, projectIds) {
|
|
49
74
|
for (let length = Math.min(PROJECT_ID_DISPLAY_MIN_LENGTH, projectId.length); length <= projectId.length; length += 1) {
|
|
50
75
|
const prefix = projectId.slice(0, length);
|
|
@@ -65,13 +90,27 @@ export async function listRuntimeProjects(options = {}) {
|
|
|
65
90
|
continue;
|
|
66
91
|
}
|
|
67
92
|
const binding = readBindingFile(tuttiHome, projectId);
|
|
93
|
+
const workspaceRoot = binding?.workspace_root ?? endpoint.workspace_root;
|
|
94
|
+
const displayName = binding?.workspace_root === undefined
|
|
95
|
+
? projectId
|
|
96
|
+
: binding.workspace_root.split(/[\\/]/u).pop() ?? projectId;
|
|
68
97
|
const health = await probe(endpoint);
|
|
98
|
+
if (!workspaceBelongsToProject(workspaceRoot, projectId)) {
|
|
99
|
+
await cleanupOrphanedRuntimeProject({
|
|
100
|
+
tuttiHome,
|
|
101
|
+
projectId,
|
|
102
|
+
endpoint,
|
|
103
|
+
hostAlive: health.kind === "alive",
|
|
104
|
+
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
69
108
|
if (health.kind === "stale") {
|
|
70
109
|
rows.push({
|
|
71
110
|
project_id: projectId,
|
|
72
111
|
status: "stale",
|
|
73
|
-
display_name:
|
|
74
|
-
workspace_root:
|
|
112
|
+
display_name: displayName,
|
|
113
|
+
workspace_root: workspaceRoot,
|
|
75
114
|
endpoint: endpoint.base_url,
|
|
76
115
|
});
|
|
77
116
|
continue;
|
|
@@ -84,9 +123,9 @@ export async function listRuntimeProjects(options = {}) {
|
|
|
84
123
|
]);
|
|
85
124
|
const row = {
|
|
86
125
|
project_id: projectId,
|
|
87
|
-
status: "online",
|
|
88
|
-
display_name: project.status === "fulfilled" ? project.value.display_name :
|
|
89
|
-
workspace_root:
|
|
126
|
+
status: health.readiness?.relay === "connected" ? "online" : "offline",
|
|
127
|
+
display_name: project.status === "fulfilled" ? project.value.display_name : displayName,
|
|
128
|
+
workspace_root: workspaceRoot,
|
|
90
129
|
endpoint: endpoint.base_url,
|
|
91
130
|
};
|
|
92
131
|
if (provider.status === "fulfilled") {
|