@xfey/tutti 0.1.52 → 0.1.54
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/artifacts/candidate-validator.d.ts +2 -3
- package/dist/artifacts/candidate-validator.js +0 -40
- 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/run-pipeline/openai.js +7 -12
- package/dist/server-shell/cli/host-server-runtime.js +20 -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/workspace-ops/index.d.ts +1 -1
- package/dist/workspace-ops/index.js +1 -1
- package/dist/workspace-ops/project-workspace.d.ts +0 -1
- package/dist/workspace-ops/project-workspace.js +0 -20
- package/dist/workspace-ops/types.d.ts +0 -2
- 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 +1 -1
- package/prompts/prompt-flow-map.md +4 -4
- package/prompts/runs/README.md +2 -1
- package/prompts/runs/task-continuation.md +1 -0
- package/prompts/runs/task-retry.md +3 -2
- package/prompts/runs/task-run.md +1 -0
- package/web/assets/{homepage-motion-scene-ENUzAyyp.js → homepage-motion-scene-C96Na8TV.js} +1 -1
- package/web/assets/{index-DK8LBgHp.js → index-CM41hrQd.js} +3 -3
- package/web/assets/index-nWy1QP97.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/dist/run-pipeline/candidate-diff.d.ts +0 -3
- package/dist/run-pipeline/candidate-diff.js +0 -4
- package/web/assets/index-CWWkCr48.css +0 -1
- package/web/assets/tutti_avatar-BBhaZGi3.png +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, posix, relative, resolve } from "node:path";
|
|
2
3
|
import { VIEWER_HARD_READ_LIMIT_BYTES } from "../workspace-ops/constants.js";
|
|
3
4
|
import { WorkspaceOpsError } from "../workspace-ops/errors.js";
|
|
4
|
-
import { entryKind, lsPath, readBlob, requireFileEntry } from "../workspace-ops/viewer-git.js";
|
|
5
5
|
import { requireNormalizedViewerPath } from "../workspace-ops/viewer-paths.js";
|
|
6
|
-
import {
|
|
6
|
+
import { readLiveArtifactDeclaration, } from "./live-manifest.js";
|
|
7
7
|
export const ARTIFACT_PREVIEW_CSP = [
|
|
8
8
|
"default-src 'none'",
|
|
9
9
|
"script-src 'self' https: 'unsafe-inline' 'unsafe-eval' blob:",
|
|
@@ -139,10 +139,7 @@ function requireDeclaredStaticArtifact(declaration) {
|
|
|
139
139
|
return declaration;
|
|
140
140
|
}
|
|
141
141
|
export function readArtifactStaticPreview(options) {
|
|
142
|
-
const declaration = requireDeclaredStaticArtifact(
|
|
143
|
-
workspaceRoot: options.workspaceRoot,
|
|
144
|
-
...(options.now === undefined ? {} : { now: options.now }),
|
|
145
|
-
}));
|
|
142
|
+
const declaration = requireDeclaredStaticArtifact(options.declaration ?? readLiveArtifactDeclaration({ workspaceRoot: options.workspaceRoot }));
|
|
146
143
|
const runtime = declaration.runtime;
|
|
147
144
|
if (runtime.kind !== "static") {
|
|
148
145
|
throw new WorkspaceOpsError("validation_failed", "Artifact is not static");
|
|
@@ -150,30 +147,31 @@ export function readArtifactStaticPreview(options) {
|
|
|
150
147
|
const requestedPath = requireSafeArtifactAssetPath(decodePath(options.path));
|
|
151
148
|
const relativePath = requestedPath === "" ? runtime.entry_path : requestedPath;
|
|
152
149
|
const repoPath = runtime.root_path === "" ? relativePath : posix.join(runtime.root_path, relativePath);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
let filePath;
|
|
151
|
+
let entrySize;
|
|
152
|
+
try {
|
|
153
|
+
const workspaceRealPath = realpathSync.native(options.workspaceRoot);
|
|
154
|
+
filePath = realpathSync.native(resolve(workspaceRealPath, repoPath));
|
|
155
|
+
const relativePath = relative(workspaceRealPath, filePath);
|
|
156
|
+
if ((relativePath !== "" && (relativePath.startsWith("..") || isAbsolute(relativePath))) ||
|
|
157
|
+
!lstatSync(filePath).isFile()) {
|
|
158
|
+
throw new Error("Artifact preview path is not a contained file");
|
|
159
|
+
}
|
|
160
|
+
entrySize = lstatSync(filePath).size;
|
|
159
161
|
}
|
|
160
|
-
|
|
161
|
-
throw new WorkspaceOpsError("
|
|
162
|
+
catch {
|
|
163
|
+
throw new WorkspaceOpsError("not_found", "Artifact preview file was not found");
|
|
162
164
|
}
|
|
163
|
-
if (
|
|
165
|
+
if (entrySize > VIEWER_HARD_READ_LIMIT_BYTES) {
|
|
164
166
|
throw new WorkspaceOpsError("validation_failed", "Artifact preview file is too large");
|
|
165
167
|
}
|
|
166
|
-
const fileEntry = requireFileEntry({
|
|
167
|
-
workspaceRoot: options.workspaceRoot,
|
|
168
|
-
path: repoPath,
|
|
169
|
-
});
|
|
170
168
|
const mediaType = mediaTypeFor(repoPath);
|
|
171
169
|
return {
|
|
172
170
|
media_type: mediaType,
|
|
173
171
|
content: rewriteArtifactPreviewResourceUrls({
|
|
174
172
|
mediaType,
|
|
175
173
|
resourcePath: relativePath,
|
|
176
|
-
content:
|
|
174
|
+
content: readFileSync(filePath),
|
|
177
175
|
}),
|
|
178
176
|
};
|
|
179
177
|
}
|
|
@@ -15,6 +15,7 @@ export declare class Phase5ControlPlane {
|
|
|
15
15
|
private readonly readActiveApproval;
|
|
16
16
|
private readonly logger;
|
|
17
17
|
private readonly projectContext;
|
|
18
|
+
private readonly runLifecycle;
|
|
18
19
|
private readonly now;
|
|
19
20
|
private readonly scratchpadAutoRefreshScheduler;
|
|
20
21
|
private lastRecoveryConclusion;
|
|
@@ -27,6 +27,7 @@ export class Phase5ControlPlane {
|
|
|
27
27
|
readActiveApproval;
|
|
28
28
|
logger;
|
|
29
29
|
projectContext;
|
|
30
|
+
runLifecycle;
|
|
30
31
|
now;
|
|
31
32
|
scratchpadAutoRefreshScheduler;
|
|
32
33
|
lastRecoveryConclusion = null;
|
|
@@ -39,6 +40,7 @@ export class Phase5ControlPlane {
|
|
|
39
40
|
this.readActiveApproval = options.readActiveApproval;
|
|
40
41
|
this.logger = options.logger;
|
|
41
42
|
this.projectContext = options.projectContext;
|
|
43
|
+
this.runLifecycle = options.runLifecycle;
|
|
42
44
|
this.now = options.now ?? (() => new Date());
|
|
43
45
|
this.engine = new ProcedureEngine({ now: this.now });
|
|
44
46
|
this.runEngine = new RunEngine({ now: this.now });
|
|
@@ -203,7 +205,7 @@ export class Phase5ControlPlane {
|
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
207
|
startRunForTask(input) {
|
|
206
|
-
|
|
208
|
+
const result = startControlPlaneRunForTask({
|
|
207
209
|
runEngine: this.runEngine,
|
|
208
210
|
store: this.store,
|
|
209
211
|
run: input,
|
|
@@ -211,10 +213,19 @@ export class Phase5ControlPlane {
|
|
|
211
213
|
resolveWorkflowRunner: this.resolveWorkflowRunner,
|
|
212
214
|
logger: this.logger,
|
|
213
215
|
now: this.now,
|
|
214
|
-
publishRunTransition: (state) =>
|
|
216
|
+
publishRunTransition: (state) => {
|
|
217
|
+
this.publishRunTransition(state);
|
|
218
|
+
if (state.transition_kind === "terminal") {
|
|
219
|
+
this.runLifecycle?.settled();
|
|
220
|
+
}
|
|
221
|
+
},
|
|
215
222
|
publishTaskRunStarted: (task, activityRef) => this.publishTaskRunStarted(task, activityRef),
|
|
216
223
|
publishRunResultRecorded: (recorded) => this.publishRunResultRecorded(recorded),
|
|
217
224
|
});
|
|
225
|
+
if (result.kind === "accepted") {
|
|
226
|
+
this.runLifecycle?.started();
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
218
229
|
}
|
|
219
230
|
readActiveRun() {
|
|
220
231
|
const runStatus = this.runEngine.getExecutionStatus();
|
|
@@ -19,6 +19,10 @@ export type Phase5ControlPlaneOptions = {
|
|
|
19
19
|
workspaceRoot: string;
|
|
20
20
|
projectName: string;
|
|
21
21
|
};
|
|
22
|
+
runLifecycle?: {
|
|
23
|
+
started: () => void;
|
|
24
|
+
settled: () => void;
|
|
25
|
+
};
|
|
22
26
|
now?: () => Date;
|
|
23
27
|
};
|
|
24
28
|
export type ControlPlaneLogger = Pick<RuntimeLogger, "info">;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createNodeNpmCheckPlan, executeCheckPlan } from "../checks/index.js";
|
|
2
|
-
import { candidateChangedAfterChecks } from "./candidate-diff.js";
|
|
3
2
|
import { runArtifactCandidateGate, } from "./artifact-applicability.js";
|
|
4
3
|
import { checkSkipFields, checkStatus, checksSummary, completedWithoutRepoChanges, docsStatusForChangedPaths, failedBeforeChecks, failedFinishedResult, promotionNotAttempted, promotionNotPromoted, successfulRunSummary, } from "./result-projections.js";
|
|
5
4
|
import { runWorkspaceWriteTask } from "./task-run-invocation.js";
|
|
@@ -94,7 +93,7 @@ function stageProgressSummary(stage) {
|
|
|
94
93
|
case "checks":
|
|
95
94
|
return "running project checks.";
|
|
96
95
|
case "post_checks_diff_collect":
|
|
97
|
-
return "
|
|
96
|
+
return "collecting the final project changes after checks.";
|
|
98
97
|
case "accepted_checkpoint":
|
|
99
98
|
return "saving the validated project state as an accepted checkpoint.";
|
|
100
99
|
case "self_correction":
|
|
@@ -358,21 +357,18 @@ async function runTaskAttempt(options, input) {
|
|
|
358
357
|
const postChecksDiff = loggedSyncStage(options, input, "post_checks_diff_collect", () => collectProjectSourceDiff(options.workspaceRoot), (result) => ({ changed_path_count: result.changed_paths.length }));
|
|
359
358
|
changedPaths = postChecksDiff.changed_paths;
|
|
360
359
|
docs = docsStatusForChangedPaths(changedPaths);
|
|
361
|
-
if (
|
|
360
|
+
if (!postChecksDiff.has_changes) {
|
|
362
361
|
const result = failedFinishedResult({
|
|
363
|
-
summary: "Checks
|
|
364
|
-
checks
|
|
365
|
-
status: "failed",
|
|
366
|
-
commands: checks.commands,
|
|
367
|
-
},
|
|
362
|
+
summary: "Checks completed but no candidate changes remain for acceptance.",
|
|
363
|
+
checks,
|
|
368
364
|
docs,
|
|
369
365
|
changedPaths,
|
|
370
|
-
promotion: promotionNotAttempted("
|
|
366
|
+
promotion: promotionNotAttempted("no_candidate_changes", "No candidate changes were available for promotion after checks."),
|
|
371
367
|
});
|
|
372
368
|
return requestSelfCorrection({
|
|
373
369
|
result,
|
|
374
|
-
reasonCode: "
|
|
375
|
-
guidance: "
|
|
370
|
+
reasonCode: "no_candidate_changes",
|
|
371
|
+
guidance: "Produce a concrete final candidate diff for the frozen task contract.",
|
|
376
372
|
});
|
|
377
373
|
}
|
|
378
374
|
const checkpoint = loggedSyncStage(options, input, "accepted_checkpoint", () => createAcceptedCheckpoint({
|
|
@@ -380,7 +376,6 @@ async function runTaskAttempt(options, input) {
|
|
|
380
376
|
taskId: input.task.id,
|
|
381
377
|
activityRef: input.activity_ref,
|
|
382
378
|
summary: `Complete Tutti task: ${input.task.title}`,
|
|
383
|
-
expectedSourceFingerprint: postChecksDiff.fingerprint,
|
|
384
379
|
}), (result) => ({
|
|
385
380
|
promoted_commit_oid: result.promoted_commit_oid,
|
|
386
381
|
checkpoint_disposition: result.disposition,
|
|
@@ -2,6 +2,7 @@ import { basename, join } from "node:path";
|
|
|
2
2
|
import { serializeRuntimeLogLine } from "@tutti/shared/utils";
|
|
3
3
|
import { completeRelayUpload, resolveRelayUpload } from "@tutti/relay-client";
|
|
4
4
|
import { ApprovalRequestManager } from "../../approvals/index.js";
|
|
5
|
+
import { ArtifactPreviewManager } from "../../artifacts/index.js";
|
|
5
6
|
import { ReadOnlyChatAssistant } from "../../chat-assistant/index.js";
|
|
6
7
|
import { ensureInitialProjectWelcomeMessage } from "../../collaboration-state/index.js";
|
|
7
8
|
import { Phase5ControlPlane } from "../../control-plane/index.js";
|
|
@@ -208,12 +209,30 @@ export async function startForegroundHostServer(options) {
|
|
|
208
209
|
events: workspaceEvents,
|
|
209
210
|
now,
|
|
210
211
|
});
|
|
212
|
+
const artifactPreviewManager = new ArtifactPreviewManager({
|
|
213
|
+
onStatusChange: () => {
|
|
214
|
+
workspaceEvents.publish({
|
|
215
|
+
event_type: "query.invalidate",
|
|
216
|
+
payload: {
|
|
217
|
+
reason: "bulk_state_change",
|
|
218
|
+
message: "Artifacts changed.",
|
|
219
|
+
},
|
|
220
|
+
invalidates: [{ kind: "artifacts" }],
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
});
|
|
211
224
|
const controlPlane = new Phase5ControlPlane({
|
|
212
225
|
store,
|
|
213
226
|
events: workspaceEvents,
|
|
214
227
|
now,
|
|
215
228
|
logger: controlPlaneLogger,
|
|
216
229
|
projectContext,
|
|
230
|
+
runLifecycle: {
|
|
231
|
+
started: () => artifactPreviewManager.beginWorkspaceUpdate(),
|
|
232
|
+
settled: () => artifactPreviewManager.settleWorkspaceUpdate({
|
|
233
|
+
workspaceRoot: options.preparation.workspace_root,
|
|
234
|
+
}),
|
|
235
|
+
},
|
|
217
236
|
readActiveApproval: () => approvalManager.getActiveApproval(),
|
|
218
237
|
readMainlineStatus: () => ({
|
|
219
238
|
mainline_clean: readHostRepoProjection(options.preparation.workspace_root).mainline_clean,
|
|
@@ -362,6 +381,7 @@ export async function startForegroundHostServer(options) {
|
|
|
362
381
|
: { refreshJoinUrl: options.refreshRelayJoinUrl }),
|
|
363
382
|
},
|
|
364
383
|
trustedMetadataStore,
|
|
384
|
+
artifactPreviewManager,
|
|
365
385
|
store,
|
|
366
386
|
events: workspaceEvents,
|
|
367
387
|
controlPlane,
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
export { DEFAULT_VIEWER_MAX_BYTES, MAX_REFERENCE_UPLOAD_BYTES, MAX_VIEWER_MAX_BYTES, PROJECT_DOC_PATHS, REFERENCE_IMAGE_ASSET_CACHE_VERSION, REFERENCE_IMAGE_PREVIEW_MAX_EDGE_PX, REFERENCE_IMAGE_THUMBNAIL_MAX_EDGE_PX, REFERENCE_DIRECTORY_PATH, REFERENCE_FILES_DIRECTORY_PATH, REFERENCE_IMAGES_DIRECTORY_PATH, REFERENCE_INDEX_PATH, REFERENCE_TUTTI_DIRECTORY_PATH, VIEWER_ASSET_READ_LIMIT_BYTES, VIEWER_HARD_READ_LIMIT_BYTES, WORKSPACE_OPS_MAINLINE_BRANCH, } from "./constants.js";
|
|
2
2
|
export { WorkspaceOpsError } from "./errors.js";
|
|
3
3
|
export { commitOwnedPaths, requireOwnedPathsWriteReady } from "./system-commits.js";
|
|
4
|
-
export { collectProjectSourceDiff, createAcceptedCheckpoint,
|
|
4
|
+
export { collectProjectSourceDiff, createAcceptedCheckpoint, inspectProjectWorkspace, recoverAcceptedCheckpoint, } from "./project-workspace.js";
|
|
5
5
|
export { initializeProjectDocs, readProjectDocsInitializationStatus, syncProjectDocs, } from "./project-docs.js";
|
|
6
6
|
export { uploadReferenceFile } from "./reference-files.js";
|
|
7
7
|
export { referenceFileCategoryForPath, referenceFileSourceForPath, referenceMediaTypeForPath, referenceTuttiGroupForPath, } from "./reference-metadata.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { DEFAULT_VIEWER_MAX_BYTES, MAX_REFERENCE_UPLOAD_BYTES, MAX_VIEWER_MAX_BYTES, PROJECT_DOC_PATHS, REFERENCE_IMAGE_ASSET_CACHE_VERSION, REFERENCE_IMAGE_PREVIEW_MAX_EDGE_PX, REFERENCE_IMAGE_THUMBNAIL_MAX_EDGE_PX, REFERENCE_DIRECTORY_PATH, REFERENCE_FILES_DIRECTORY_PATH, REFERENCE_IMAGES_DIRECTORY_PATH, REFERENCE_INDEX_PATH, REFERENCE_TUTTI_DIRECTORY_PATH, VIEWER_ASSET_READ_LIMIT_BYTES, VIEWER_HARD_READ_LIMIT_BYTES, WORKSPACE_OPS_MAINLINE_BRANCH, } from "./constants.js";
|
|
2
2
|
export { WorkspaceOpsError } from "./errors.js";
|
|
3
3
|
export { commitOwnedPaths, requireOwnedPathsWriteReady } from "./system-commits.js";
|
|
4
|
-
export { collectProjectSourceDiff, createAcceptedCheckpoint,
|
|
4
|
+
export { collectProjectSourceDiff, createAcceptedCheckpoint, inspectProjectWorkspace, recoverAcceptedCheckpoint, } from "./project-workspace.js";
|
|
5
5
|
export { initializeProjectDocs, readProjectDocsInitializationStatus, syncProjectDocs, } from "./project-docs.js";
|
|
6
6
|
export { uploadReferenceFile } from "./reference-files.js";
|
|
7
7
|
export { referenceFileCategoryForPath, referenceFileSourceForPath, referenceMediaTypeForPath, referenceTuttiGroupForPath, } from "./reference-metadata.js";
|