@xfey/tutti 0.1.53 → 0.1.55
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/README.md +5 -2
- package/desktop-assets/README.md +13 -0
- package/desktop-assets/Tutti-square.png +0 -0
- package/desktop-assets/Tutti.icns +0 -0
- package/desktop-assets/macos-handler.applescript +24 -0
- package/dist/artifacts/index.d.ts +3 -3
- package/dist/artifacts/index.js +3 -3
- package/dist/artifacts/live-manifest.d.ts +23 -0
- package/dist/artifacts/live-manifest.js +115 -0
- package/dist/artifacts/preview-runtime.d.ts +34 -36
- package/dist/artifacts/preview-runtime.js +215 -273
- package/dist/artifacts/static-preview.d.ts +2 -1
- package/dist/artifacts/static-preview.js +19 -21
- package/dist/control-plane/index.d.ts +1 -0
- package/dist/control-plane/index.js +13 -2
- package/dist/control-plane/types.d.ts +4 -0
- package/dist/server-shell/cli/args.d.ts +7 -0
- package/dist/server-shell/cli/args.js +32 -0
- package/dist/server-shell/cli/cli.js +76 -1
- package/dist/server-shell/cli/errors.d.ts +1 -1
- package/dist/server-shell/cli/host-server-runtime.js +20 -0
- package/dist/server-shell/cli/version.d.ts +6 -0
- package/dist/server-shell/cli/version.js +11 -2
- package/dist/server-shell/desktop-integration/index.d.ts +5 -0
- package/dist/server-shell/desktop-integration/index.js +5 -0
- package/dist/server-shell/desktop-integration/linux.d.ts +12 -0
- package/dist/server-shell/desktop-integration/linux.js +186 -0
- package/dist/server-shell/desktop-integration/macos.d.ts +9 -0
- package/dist/server-shell/desktop-integration/macos.js +138 -0
- package/dist/server-shell/desktop-integration/manager.d.ts +40 -0
- package/dist/server-shell/desktop-integration/manager.js +223 -0
- package/dist/server-shell/desktop-integration/platform.d.ts +23 -0
- package/dist/server-shell/desktop-integration/platform.js +21 -0
- package/dist/server-shell/desktop-integration/protocol.d.ts +9 -0
- package/dist/server-shell/desktop-integration/protocol.js +28 -0
- package/dist/server-shell/desktop-integration/record.d.ts +17 -0
- package/dist/server-shell/desktop-integration/record.js +74 -0
- package/dist/server-shell/http/routes/project-api/artifacts-routes.js +43 -110
- package/dist/server-shell/http/routes/project-api/openapi-artifacts-routes.d.ts +5 -8
- package/dist/server-shell/http/routes/project-api/openapi-artifacts-routes.js +5 -33
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +17 -8
- package/dist/server-shell/http/routes/project-api/types.d.ts +3 -2
- package/dist/server-shell/local-console/invocation-context.d.ts +7 -0
- package/dist/server-shell/local-console/invocation-context.js +8 -9
- package/dist/server-shell/local-console/managed-console.d.ts +1 -0
- package/dist/server-shell/local-console/managed-console.js +10 -5
- package/dist/server-shell/local-console/project-service.js +1 -1
- package/dist/server-shell/local-console/server.js +23 -6
- package/node_modules/@tutti/shared/dist/schemas/api/artifacts.d.ts +7 -88
- package/node_modules/@tutti/shared/dist/schemas/api/artifacts.js +3 -48
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +1 -10
- package/package.json +2 -1
- package/web/assets/{homepage-motion-scene-F4ibPvI9.js → homepage-motion-scene-D0-wLq3D.js} +1 -1
- package/web/assets/{index-B1mBtL9x.js → index-BAsLSZ5U.js} +14 -14
- package/web/assets/index-DMaiDZ9k.css +1 -0
- package/web/assets/tutti_avatar-_0Yc-cMJ.png +0 -0
- package/web/index.html +2 -2
- package/dist/artifacts/manifest.d.ts +0 -41
- package/dist/artifacts/manifest.js +0 -169
- package/web/assets/index-OFlcQ3eS.css +0 -1
- package/web/assets/tutti_avatar-BBhaZGi3.png +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_CSP, ArtifactPreviewManager, isArtifactPreviewHttpMethod,
|
|
1
|
+
import { GetArtifactsResponseSchema, } from "@tutti/shared/schemas/api";
|
|
2
|
+
import { ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_CSP, ArtifactPreviewManager, isArtifactPreviewHttpMethod, } from "../../../../artifacts/index.js";
|
|
3
3
|
import { WorkspaceOpsError } from "../../../../workspace-ops/index.js";
|
|
4
|
-
import { executeIdempotentCommand } from "../../../command-idempotency/index.js";
|
|
5
4
|
import { HOST_PROJECT_API_BASE_PATH } from "./constants.js";
|
|
6
|
-
import { requireHostProjectSession,
|
|
5
|
+
import { requireHostProjectSession, requireWorkspaceRoot, } from "./session-requirements.js";
|
|
7
6
|
function artifactInvalidates() {
|
|
8
7
|
return [{ kind: "artifacts" }];
|
|
9
8
|
}
|
|
@@ -32,27 +31,27 @@ function previewRequestBody(body) {
|
|
|
32
31
|
}
|
|
33
32
|
return Buffer.from(JSON.stringify(body), "utf8");
|
|
34
33
|
}
|
|
35
|
-
function
|
|
36
|
-
const marker = new URL(request.url, "http://artifact.invalid").searchParams.get("
|
|
37
|
-
if (marker !==
|
|
38
|
-
throw new WorkspaceOpsError("not_found", "Artifact preview
|
|
34
|
+
function requireCurrentArtifactGeneration(request, previewGeneration) {
|
|
35
|
+
const marker = new URL(request.url, "http://artifact.invalid").searchParams.get("g");
|
|
36
|
+
if (marker !== previewGeneration) {
|
|
37
|
+
throw new WorkspaceOpsError("not_found", "Artifact preview generation is stale");
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
42
|
-
const artifactPreviewManager =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
const artifactPreviewManager = options.artifactPreviewManager ??
|
|
42
|
+
new ArtifactPreviewManager({
|
|
43
|
+
...options.artifactPreview,
|
|
44
|
+
onStatusChange: () => {
|
|
45
|
+
workspaceEvents.publish({
|
|
46
|
+
event_type: "query.invalidate",
|
|
47
|
+
payload: {
|
|
48
|
+
reason: "bulk_state_change",
|
|
49
|
+
message: "Artifacts changed.",
|
|
50
|
+
},
|
|
51
|
+
invalidates: artifactInvalidates(),
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
});
|
|
56
55
|
app.addHook("onClose", async () => {
|
|
57
56
|
await artifactPreviewManager.stopAll();
|
|
58
57
|
});
|
|
@@ -68,6 +67,23 @@ export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
|
68
67
|
workspaceRoot: requireWorkspaceRoot(options),
|
|
69
68
|
});
|
|
70
69
|
});
|
|
70
|
+
app.post(`${HOST_PROJECT_API_BASE_PATH}/artifacts/preview/lease`, {
|
|
71
|
+
schema: {
|
|
72
|
+
response: {
|
|
73
|
+
204: { type: "null" },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}, (request, reply) => {
|
|
77
|
+
if (request.body !== undefined) {
|
|
78
|
+
throw new WorkspaceOpsError("bad_request", "Artifact preview lease does not accept a request body");
|
|
79
|
+
}
|
|
80
|
+
const session = requireHostProjectSession(request, options);
|
|
81
|
+
artifactPreviewManager.renewLease({
|
|
82
|
+
viewerId: session.client_session.client_session_id,
|
|
83
|
+
workspaceRoot: requireWorkspaceRoot(options),
|
|
84
|
+
});
|
|
85
|
+
void reply.code(204).send();
|
|
86
|
+
});
|
|
71
87
|
app.all("/artifacts/preview/*", {
|
|
72
88
|
bodyLimit: ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES,
|
|
73
89
|
}, async (request, reply) => {
|
|
@@ -81,12 +97,9 @@ export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
|
81
97
|
return;
|
|
82
98
|
}
|
|
83
99
|
const workspaceRoot = requireWorkspaceRoot(options);
|
|
84
|
-
const projection =
|
|
85
|
-
workspaceRoot,
|
|
86
|
-
...(options.now === undefined ? {} : { now: options.now }),
|
|
87
|
-
});
|
|
100
|
+
const projection = artifactPreviewManager.readProjection();
|
|
88
101
|
if (projection.artifact.status === "declared") {
|
|
89
|
-
|
|
102
|
+
requireCurrentArtifactGeneration(request, projection.artifact.preview_generation);
|
|
90
103
|
}
|
|
91
104
|
if (projection.artifact.status === "declared" &&
|
|
92
105
|
projection.artifact.artifact.kind === "server") {
|
|
@@ -118,10 +131,11 @@ export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
|
118
131
|
.send("Static Artifact previews only support GET and HEAD");
|
|
119
132
|
return;
|
|
120
133
|
}
|
|
121
|
-
const
|
|
134
|
+
const queryIndex = request.url.indexOf("?");
|
|
135
|
+
const preview = artifactPreviewManager.readStaticPreview({
|
|
122
136
|
workspaceRoot,
|
|
123
137
|
path: request.params["*"],
|
|
124
|
-
...(
|
|
138
|
+
...(queryIndex < 0 ? {} : { query: request.url.slice(queryIndex + 1) }),
|
|
125
139
|
});
|
|
126
140
|
void reply
|
|
127
141
|
.header("content-type", preview.media_type)
|
|
@@ -130,86 +144,5 @@ export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
|
130
144
|
.header("cache-control", "private, max-age=60")
|
|
131
145
|
.send(preview.content);
|
|
132
146
|
});
|
|
133
|
-
app.post(`${HOST_PROJECT_API_BASE_PATH}/artifacts/preview/start`, {
|
|
134
|
-
schema: {
|
|
135
|
-
body: StartArtifactPreviewBodySchema,
|
|
136
|
-
response: {
|
|
137
|
-
200: StartArtifactPreviewResponseSchema,
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
}, (request) => {
|
|
141
|
-
requireHostProjectSession(request, options);
|
|
142
|
-
const store = requireProjectStore(options);
|
|
143
|
-
const invalidates = artifactInvalidates();
|
|
144
|
-
const execution = executeIdempotentCommand({
|
|
145
|
-
db: store.db,
|
|
146
|
-
command_id: request.body.command_id,
|
|
147
|
-
command_name: "artifacts.preview.start",
|
|
148
|
-
target_ref: "artifacts:active",
|
|
149
|
-
payload: request.body.payload,
|
|
150
|
-
...(options.now === undefined ? {} : { now: options.now }),
|
|
151
|
-
execute: () => artifactPreviewManager.start({ workspaceRoot: requireWorkspaceRoot(options) }),
|
|
152
|
-
});
|
|
153
|
-
return {
|
|
154
|
-
command_id: execution.command_id,
|
|
155
|
-
disposition: execution.disposition,
|
|
156
|
-
...(execution.result === undefined ? {} : { result: execution.result }),
|
|
157
|
-
invalidates,
|
|
158
|
-
};
|
|
159
|
-
});
|
|
160
|
-
app.post(`${HOST_PROJECT_API_BASE_PATH}/artifacts/preview/heartbeat`, {
|
|
161
|
-
schema: {
|
|
162
|
-
body: HeartbeatArtifactPreviewBodySchema,
|
|
163
|
-
response: {
|
|
164
|
-
200: HeartbeatArtifactPreviewResponseSchema,
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
}, (request) => {
|
|
168
|
-
const session = requireHostProjectSession(request, options);
|
|
169
|
-
const store = requireProjectStore(options);
|
|
170
|
-
const invalidates = artifactInvalidates();
|
|
171
|
-
const execution = executeIdempotentCommand({
|
|
172
|
-
db: store.db,
|
|
173
|
-
command_id: request.body.command_id,
|
|
174
|
-
command_name: "artifacts.preview.heartbeat",
|
|
175
|
-
target_ref: `artifacts:active:${session.client_session.client_session_id}`,
|
|
176
|
-
payload: request.body.payload,
|
|
177
|
-
...(options.now === undefined ? {} : { now: options.now }),
|
|
178
|
-
execute: () => artifactPreviewManager.heartbeat({ workspaceRoot: requireWorkspaceRoot(options) }),
|
|
179
|
-
});
|
|
180
|
-
return {
|
|
181
|
-
command_id: execution.command_id,
|
|
182
|
-
disposition: execution.disposition,
|
|
183
|
-
...(execution.result === undefined ? {} : { result: execution.result }),
|
|
184
|
-
invalidates,
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
app.post(`${HOST_PROJECT_API_BASE_PATH}/artifacts/preview/stop`, {
|
|
188
|
-
schema: {
|
|
189
|
-
body: StopArtifactPreviewBodySchema,
|
|
190
|
-
response: {
|
|
191
|
-
200: StopArtifactPreviewResponseSchema,
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
}, (request) => {
|
|
195
|
-
requireHostProjectSession(request, options);
|
|
196
|
-
const store = requireProjectStore(options);
|
|
197
|
-
const invalidates = artifactInvalidates();
|
|
198
|
-
const execution = executeIdempotentCommand({
|
|
199
|
-
db: store.db,
|
|
200
|
-
command_id: request.body.command_id,
|
|
201
|
-
command_name: "artifacts.preview.stop",
|
|
202
|
-
target_ref: "artifacts:active",
|
|
203
|
-
payload: request.body.payload,
|
|
204
|
-
...(options.now === undefined ? {} : { now: options.now }),
|
|
205
|
-
execute: () => artifactPreviewManager.stop({ workspaceRoot: requireWorkspaceRoot(options) }),
|
|
206
|
-
});
|
|
207
|
-
return {
|
|
208
|
-
command_id: execution.command_id,
|
|
209
|
-
disposition: execution.disposition,
|
|
210
|
-
...(execution.result === undefined ? {} : { result: execution.result }),
|
|
211
|
-
invalidates,
|
|
212
|
-
};
|
|
213
|
-
});
|
|
214
147
|
}
|
|
215
148
|
//# sourceMappingURL=artifacts-routes.js.map
|
|
@@ -11,41 +11,38 @@ export declare const HOST_PROJECT_ARTIFACTS_OPENAPI_ROUTES: ({
|
|
|
11
11
|
status: import("@sinclair/typebox").TLiteral<"not_declared">;
|
|
12
12
|
}>, import("@sinclair/typebox").TObject<{
|
|
13
13
|
status: import("@sinclair/typebox").TLiteral<"invalid">;
|
|
14
|
-
manifest_path: import("@sinclair/typebox").TLiteral<"tutti.artifact.json">;
|
|
15
14
|
error: import("@sinclair/typebox").TObject<{
|
|
16
15
|
code: import("@sinclair/typebox").TString;
|
|
17
16
|
message: import("@sinclair/typebox").TString;
|
|
18
17
|
}>;
|
|
19
18
|
}>, import("@sinclair/typebox").TObject<{
|
|
20
19
|
status: import("@sinclair/typebox").TLiteral<"declared">;
|
|
21
|
-
|
|
22
|
-
manifest_blob_oid: import("@sinclair/typebox").TString;
|
|
20
|
+
preview_generation: import("@sinclair/typebox").TString;
|
|
23
21
|
artifact: import("@sinclair/typebox").TObject<{
|
|
24
22
|
title: import("@sinclair/typebox").TString;
|
|
25
23
|
kind: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"static">, import("@sinclair/typebox").TLiteral<"server">]>;
|
|
26
24
|
network: import("@sinclair/typebox").TBoolean;
|
|
27
25
|
}>;
|
|
28
26
|
preview: import("@sinclair/typebox").TObject<{
|
|
29
|
-
status: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"ready">, import("@sinclair/typebox").TLiteral<"starting">, import("@sinclair/typebox").TLiteral<"
|
|
27
|
+
status: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"ready">, import("@sinclair/typebox").TLiteral<"starting">, import("@sinclair/typebox").TLiteral<"idle">, import("@sinclair/typebox").TLiteral<"failed">]>;
|
|
30
28
|
url: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
31
29
|
error_message: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
32
30
|
}>;
|
|
33
31
|
}>]>;
|
|
34
32
|
}>;
|
|
35
33
|
};
|
|
34
|
+
204?: never;
|
|
36
35
|
};
|
|
37
|
-
requestBody?: never;
|
|
38
36
|
} | {
|
|
39
37
|
method: "post";
|
|
40
38
|
path: string;
|
|
41
39
|
summary: string;
|
|
42
40
|
tags: string[];
|
|
43
|
-
requestBody: import("@sinclair/typebox").TSchema;
|
|
44
41
|
responses: {
|
|
45
|
-
|
|
42
|
+
204: {
|
|
46
43
|
description: string;
|
|
47
|
-
schema: import("@sinclair/typebox").TSchema;
|
|
48
44
|
};
|
|
45
|
+
200?: never;
|
|
49
46
|
};
|
|
50
47
|
})[];
|
|
51
48
|
//# sourceMappingURL=openapi-artifacts-routes.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GetArtifactsResponseSchema,
|
|
1
|
+
import { GetArtifactsResponseSchema, } from "@tutti/shared/schemas/api";
|
|
2
2
|
import { HOST_PROJECT_API_BASE_PATH } from "./constants.js";
|
|
3
3
|
export const HOST_PROJECT_ARTIFACTS_OPENAPI_ROUTES = [
|
|
4
4
|
{
|
|
@@ -15,40 +15,12 @@ export const HOST_PROJECT_ARTIFACTS_OPENAPI_ROUTES = [
|
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
method: "post",
|
|
18
|
-
path: `${HOST_PROJECT_API_BASE_PATH}/artifacts/preview/
|
|
19
|
-
summary: "
|
|
18
|
+
path: `${HOST_PROJECT_API_BASE_PATH}/artifacts/preview/lease`,
|
|
19
|
+
summary: "Acquire or renew the current viewer's Artifact preview lease.",
|
|
20
20
|
tags: ["artifacts"],
|
|
21
|
-
requestBody: StartArtifactPreviewBodySchema,
|
|
22
21
|
responses: {
|
|
23
|
-
|
|
24
|
-
description: "Artifact preview
|
|
25
|
-
schema: StartArtifactPreviewResponseSchema,
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
method: "post",
|
|
31
|
-
path: `${HOST_PROJECT_API_BASE_PATH}/artifacts/preview/heartbeat`,
|
|
32
|
-
summary: "Record a viewer lease heartbeat for the current artifact preview.",
|
|
33
|
-
tags: ["artifacts"],
|
|
34
|
-
requestBody: HeartbeatArtifactPreviewBodySchema,
|
|
35
|
-
responses: {
|
|
36
|
-
200: {
|
|
37
|
-
description: "Artifact preview heartbeat disposition.",
|
|
38
|
-
schema: HeartbeatArtifactPreviewResponseSchema,
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
method: "post",
|
|
44
|
-
path: `${HOST_PROJECT_API_BASE_PATH}/artifacts/preview/stop`,
|
|
45
|
-
summary: "Stop the current server artifact preview.",
|
|
46
|
-
tags: ["artifacts"],
|
|
47
|
-
requestBody: StopArtifactPreviewBodySchema,
|
|
48
|
-
responses: {
|
|
49
|
-
200: {
|
|
50
|
-
description: "Artifact preview stop disposition.",
|
|
51
|
-
schema: StopArtifactPreviewResponseSchema,
|
|
22
|
+
204: {
|
|
23
|
+
description: "Artifact preview lease renewed.",
|
|
52
24
|
},
|
|
53
25
|
},
|
|
54
26
|
},
|
|
@@ -124,41 +124,38 @@ export declare const HOST_PROJECT_OPENAPI_ROUTES: ({
|
|
|
124
124
|
status: import("@sinclair/typebox").TLiteral<"not_declared">;
|
|
125
125
|
}>, import("@sinclair/typebox").TObject<{
|
|
126
126
|
status: import("@sinclair/typebox").TLiteral<"invalid">;
|
|
127
|
-
manifest_path: import("@sinclair/typebox").TLiteral<"tutti.artifact.json">;
|
|
128
127
|
error: import("@sinclair/typebox").TObject<{
|
|
129
128
|
code: import("@sinclair/typebox").TString;
|
|
130
129
|
message: import("@sinclair/typebox").TString;
|
|
131
130
|
}>;
|
|
132
131
|
}>, import("@sinclair/typebox").TObject<{
|
|
133
132
|
status: import("@sinclair/typebox").TLiteral<"declared">;
|
|
134
|
-
|
|
135
|
-
manifest_blob_oid: import("@sinclair/typebox").TString;
|
|
133
|
+
preview_generation: import("@sinclair/typebox").TString;
|
|
136
134
|
artifact: import("@sinclair/typebox").TObject<{
|
|
137
135
|
title: import("@sinclair/typebox").TString;
|
|
138
136
|
kind: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"static">, import("@sinclair/typebox").TLiteral<"server">]>;
|
|
139
137
|
network: import("@sinclair/typebox").TBoolean;
|
|
140
138
|
}>;
|
|
141
139
|
preview: import("@sinclair/typebox").TObject<{
|
|
142
|
-
status: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"ready">, import("@sinclair/typebox").TLiteral<"starting">, import("@sinclair/typebox").TLiteral<"
|
|
140
|
+
status: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"ready">, import("@sinclair/typebox").TLiteral<"starting">, import("@sinclair/typebox").TLiteral<"idle">, import("@sinclair/typebox").TLiteral<"failed">]>;
|
|
143
141
|
url: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
144
142
|
error_message: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
145
143
|
}>;
|
|
146
144
|
}>]>;
|
|
147
145
|
}>;
|
|
148
146
|
};
|
|
147
|
+
204?: never;
|
|
149
148
|
};
|
|
150
|
-
requestBody?: never;
|
|
151
149
|
} | {
|
|
152
150
|
method: "post";
|
|
153
151
|
path: string;
|
|
154
152
|
summary: string;
|
|
155
153
|
tags: string[];
|
|
156
|
-
requestBody: import("@sinclair/typebox").TSchema;
|
|
157
154
|
responses: {
|
|
158
|
-
|
|
155
|
+
204: {
|
|
159
156
|
description: string;
|
|
160
|
-
schema: import("@sinclair/typebox").TSchema;
|
|
161
157
|
};
|
|
158
|
+
200?: never;
|
|
162
159
|
};
|
|
163
160
|
} | {
|
|
164
161
|
method: "get";
|
|
@@ -1867,6 +1864,18 @@ export declare const HOST_PROJECT_OPENAPI_ROUTES: ({
|
|
|
1867
1864
|
};
|
|
1868
1865
|
};
|
|
1869
1866
|
requestBody?: never;
|
|
1867
|
+
} | {
|
|
1868
|
+
method: "post";
|
|
1869
|
+
path: string;
|
|
1870
|
+
summary: string;
|
|
1871
|
+
tags: string[];
|
|
1872
|
+
requestBody: import("@sinclair/typebox").TSchema;
|
|
1873
|
+
responses: {
|
|
1874
|
+
200: {
|
|
1875
|
+
description: string;
|
|
1876
|
+
schema: import("@sinclair/typebox").TSchema;
|
|
1877
|
+
};
|
|
1878
|
+
};
|
|
1870
1879
|
} | {
|
|
1871
1880
|
method: "get";
|
|
1872
1881
|
path: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReadOnlyChatAssistantHandle } from "../../../../chat-assistant/index.js";
|
|
2
2
|
import type { Phase5ControlPlane } from "../../../../control-plane/index.js";
|
|
3
3
|
import type { ApprovalRequestManager } from "../../../../approvals/index.js";
|
|
4
|
-
import type { ArtifactPreviewManagerOptions } from "../../../../artifacts/index.js";
|
|
4
|
+
import type { ArtifactPreviewManager, ArtifactPreviewManagerOptions } from "../../../../artifacts/index.js";
|
|
5
5
|
import type { HostProjectStore } from "../../../../store/index.js";
|
|
6
6
|
import type { ProjectId, RelayProjectRef } from "@tutti/shared/ids";
|
|
7
7
|
import type { AccountAvatarProjection } from "@tutti/shared/domain";
|
|
@@ -73,7 +73,8 @@ export type HostProjectApiCapabilities = {
|
|
|
73
73
|
clarification: boolean;
|
|
74
74
|
};
|
|
75
75
|
export type HostProjectApiRouteOptions = {
|
|
76
|
-
artifactPreview?: Omit<ArtifactPreviewManagerOptions, "
|
|
76
|
+
artifactPreview?: Omit<ArtifactPreviewManagerOptions, "onStatusChange">;
|
|
77
|
+
artifactPreviewManager?: ArtifactPreviewManager;
|
|
77
78
|
project: {
|
|
78
79
|
project_id: ProjectId;
|
|
79
80
|
display_name: string;
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
export declare const LOCAL_CONSOLE_INVOCATION_ENV_KEYS: readonly ["DBUS_SESSION_BUS_ADDRESS", "DISPLAY", "LANG", "LC_ALL", "LC_CTYPE", "PATH", "TUTTI_HOST_ENDPOINT_HOST", "TUTTI_HOST_ENDPOINT_PORT", "TUTTI_RELAY_URL", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP"];
|
|
2
2
|
export type LocalConsoleInvocationEnvironment = Partial<Record<(typeof LOCAL_CONSOLE_INVOCATION_ENV_KEYS)[number], string>>;
|
|
3
3
|
export type LocalConsoleInvocationContext = {
|
|
4
|
+
source: "terminal";
|
|
4
5
|
currentDirectory: string;
|
|
5
6
|
environment: LocalConsoleInvocationEnvironment;
|
|
7
|
+
} | {
|
|
8
|
+
source: "desktop";
|
|
9
|
+
environment: LocalConsoleInvocationEnvironment;
|
|
6
10
|
};
|
|
7
11
|
export declare function collectLocalConsoleInvocationEnvironment(env: NodeJS.ProcessEnv): LocalConsoleInvocationEnvironment;
|
|
8
12
|
export declare function createLocalConsoleInvocationContext(options: {
|
|
9
13
|
cwd: string;
|
|
10
14
|
env: NodeJS.ProcessEnv;
|
|
11
15
|
}): LocalConsoleInvocationContext;
|
|
16
|
+
export declare function createDesktopLocalConsoleInvocationContext(options: {
|
|
17
|
+
env: NodeJS.ProcessEnv;
|
|
18
|
+
}): LocalConsoleInvocationContext;
|
|
12
19
|
export declare function createLocalConsoleServiceEnvironment(options: {
|
|
13
20
|
env: NodeJS.ProcessEnv;
|
|
14
21
|
tuttiHome: string;
|
|
@@ -24,21 +24,20 @@ export function collectLocalConsoleInvocationEnvironment(env) {
|
|
|
24
24
|
}
|
|
25
25
|
export function createLocalConsoleInvocationContext(options) {
|
|
26
26
|
return {
|
|
27
|
+
source: "terminal",
|
|
27
28
|
currentDirectory: resolve(options.cwd),
|
|
28
29
|
environment: collectLocalConsoleInvocationEnvironment(options.env),
|
|
29
30
|
};
|
|
30
31
|
}
|
|
32
|
+
export function createDesktopLocalConsoleInvocationContext(options) {
|
|
33
|
+
return {
|
|
34
|
+
source: "desktop",
|
|
35
|
+
environment: collectLocalConsoleInvocationEnvironment(options.env),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
31
38
|
export function createLocalConsoleServiceEnvironment(options) {
|
|
32
39
|
const environment = { TUTTI_HOME: options.tuttiHome };
|
|
33
|
-
for (const key of [
|
|
34
|
-
"HOME",
|
|
35
|
-
"LANG",
|
|
36
|
-
"LC_ALL",
|
|
37
|
-
"LC_CTYPE",
|
|
38
|
-
"LOGNAME",
|
|
39
|
-
"TMPDIR",
|
|
40
|
-
"USER",
|
|
41
|
-
]) {
|
|
40
|
+
for (const key of ["HOME", "LANG", "LC_ALL", "LC_CTYPE", "LOGNAME", "TMPDIR", "USER"]) {
|
|
42
41
|
const value = options.env[key];
|
|
43
42
|
if (value !== undefined && value !== "") {
|
|
44
43
|
environment[key] = value;
|
|
@@ -3,7 +3,7 @@ import { chmodSync, closeSync, existsSync, mkdirSync, openSync, renameSync, stat
|
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { resolveTuttiHome } from "../../providers/openai/index.js";
|
|
5
5
|
import { readCliVersion } from "../cli/version.js";
|
|
6
|
-
import { createLocalConsoleInvocationContext, createLocalConsoleServiceEnvironment, } from "./invocation-context.js";
|
|
6
|
+
import { createLocalConsoleInvocationContext, createDesktopLocalConsoleInvocationContext, createLocalConsoleServiceEnvironment, } from "./invocation-context.js";
|
|
7
7
|
import { withLocalConsoleLifecycleLock } from "./lifecycle-lock.js";
|
|
8
8
|
import { deleteLocalConsoleRuntimeEndpoint, readLocalConsoleRuntimeEndpoint, } from "./runtime-endpoint.js";
|
|
9
9
|
import { LOCAL_CONSOLE_API_BASE } from "./server.js";
|
|
@@ -208,7 +208,6 @@ async function ensureConsoleEndpointUnlocked(options) {
|
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
async function issueBrowserAccess(options) {
|
|
211
|
-
const context = createLocalConsoleInvocationContext({ cwd: options.cwd, env: options.env });
|
|
212
211
|
const response = await options.fetchImpl(`${options.endpoint.base_url}${LOCAL_CONSOLE_API_BASE}/access-tokens`, {
|
|
213
212
|
method: "POST",
|
|
214
213
|
headers: {
|
|
@@ -217,8 +216,11 @@ async function issueBrowserAccess(options) {
|
|
|
217
216
|
"content-type": "application/json",
|
|
218
217
|
},
|
|
219
218
|
body: JSON.stringify({
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
source: options.context.source,
|
|
220
|
+
...(options.context.source === "terminal"
|
|
221
|
+
? { current_directory: options.context.currentDirectory }
|
|
222
|
+
: {}),
|
|
223
|
+
environment: options.context.environment,
|
|
222
224
|
}),
|
|
223
225
|
signal: AbortSignal.timeout(3_000),
|
|
224
226
|
});
|
|
@@ -240,9 +242,12 @@ export async function ensureLocalConsole(options = {}) {
|
|
|
240
242
|
tuttiHome,
|
|
241
243
|
run: async () => await ensureConsoleEndpointUnlocked({ cwd, env, tuttiHome, fetchImpl }),
|
|
242
244
|
});
|
|
245
|
+
const context = options.source === "desktop"
|
|
246
|
+
? createDesktopLocalConsoleInvocationContext({ env })
|
|
247
|
+
: createLocalConsoleInvocationContext({ cwd, env });
|
|
243
248
|
return {
|
|
244
249
|
endpoint,
|
|
245
|
-
url: await issueBrowserAccess({ endpoint,
|
|
250
|
+
url: await issueBrowserAccess({ endpoint, context, fetchImpl }),
|
|
246
251
|
};
|
|
247
252
|
}
|
|
248
253
|
export async function readLocalConsoleServiceStatus(options = {}) {
|
|
@@ -164,7 +164,7 @@ export class LocalConsoleProjectService {
|
|
|
164
164
|
}
|
|
165
165
|
#operationOptions(context) {
|
|
166
166
|
return {
|
|
167
|
-
cwd: context.currentDirectory,
|
|
167
|
+
cwd: context.source === "terminal" ? context.currentDirectory : this.#tuttiHome,
|
|
168
168
|
env: createLocalConsoleOperationEnvironment({
|
|
169
169
|
serviceEnvironment: this.#serviceEnvironment,
|
|
170
170
|
invocationEnvironment: context.environment,
|
|
@@ -159,9 +159,10 @@ export function createLocalConsoleServer(options) {
|
|
|
159
159
|
schema: {
|
|
160
160
|
body: {
|
|
161
161
|
type: "object",
|
|
162
|
-
required: ["
|
|
162
|
+
required: ["source"],
|
|
163
163
|
additionalProperties: false,
|
|
164
164
|
properties: {
|
|
165
|
+
source: { type: "string", enum: ["terminal", "desktop"] },
|
|
165
166
|
current_directory: { type: "string", minLength: 1, maxLength: 4096 },
|
|
166
167
|
environment: {
|
|
167
168
|
type: "object",
|
|
@@ -176,10 +177,21 @@ export function createLocalConsoleServer(options) {
|
|
|
176
177
|
},
|
|
177
178
|
}, (request) => {
|
|
178
179
|
requireControl(request);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
180
|
+
if ((request.body.source === "terminal" && request.body.current_directory === undefined) ||
|
|
181
|
+
(request.body.source === "desktop" && request.body.current_directory !== undefined)) {
|
|
182
|
+
throw new LocalConsoleHttpError(400, "bad_request", "Local Console invocation context is invalid.");
|
|
183
|
+
}
|
|
184
|
+
const context = request.body.source === "terminal"
|
|
185
|
+
? {
|
|
186
|
+
source: "terminal",
|
|
187
|
+
currentDirectory: resolve(request.body.current_directory),
|
|
188
|
+
environment: request.body.environment ?? {},
|
|
189
|
+
}
|
|
190
|
+
: {
|
|
191
|
+
source: "desktop",
|
|
192
|
+
environment: request.body.environment ?? {},
|
|
193
|
+
};
|
|
194
|
+
const access = sessions.issueAccessToken(context);
|
|
183
195
|
return { access_token: access.token, expires_at: access.expires_at };
|
|
184
196
|
});
|
|
185
197
|
app.post(`${LOCAL_CONSOLE_API_BASE}/session`, {
|
|
@@ -212,7 +224,12 @@ export function createLocalConsoleServer(options) {
|
|
|
212
224
|
const session = requireSession(request);
|
|
213
225
|
return {
|
|
214
226
|
csrf_token: session.csrfToken,
|
|
215
|
-
|
|
227
|
+
invocation: session.context.source === "terminal"
|
|
228
|
+
? {
|
|
229
|
+
source: "terminal",
|
|
230
|
+
current_directory: session.context.currentDirectory,
|
|
231
|
+
}
|
|
232
|
+
: { source: "desktop" },
|
|
216
233
|
platform: process.platform,
|
|
217
234
|
folder_picker: detectLocalFolderPicker(process.platform, session.context.environment).kind,
|
|
218
235
|
};
|